diff --git a/apps/obsidian/src/components/NodeTypeSettings.tsx b/apps/obsidian/src/components/NodeTypeSettings.tsx index 7c6ac7c3a..6f37d1383 100644 --- a/apps/obsidian/src/components/NodeTypeSettings.tsx +++ b/apps/obsidian/src/components/NodeTypeSettings.tsx @@ -7,6 +7,24 @@ import { DiscourseNode } from "~/types"; import { ConfirmationModal } from "./ConfirmationModal"; import { getTemplateFiles, getTemplatePluginInfo } from "~/utils/templates"; +const generateTagPlaceholder = (format: string, nodeName?: string): string => { + if (!format) return "Enter tag (e.g., clm-candidate or #clm-candidate)"; + + // Extract the prefix before " - {content}" or " -{content}" or " -{content}" etc. + const match = format.match(/^([A-Z]+)\s*-\s*\{content\}/i); + if (match && match[1]) { + const prefix = match[1].toLowerCase(); + return `Enter tag (e.g., ${prefix}-candidate)`; + } + + if (nodeName && nodeName.length >= 3) { + const prefix = nodeName.substring(0, 3).toLowerCase(); + return `Enter tag (e.g., ${prefix}-candidate)`; + } + + return "Enter tag (e.g., clm-candidate)"; +}; + type EditableFieldKey = keyof Omit; type BaseFieldConfig = { @@ -75,6 +93,29 @@ const FIELD_CONFIGS: Record = { type: "color", required: false, }, + tag: { + key: "tag", + label: "Node tag", + description: "Tags that signal a line is a node candidate", + type: "text", + required: false, + validate: (value: string) => { + if (!value.trim()) return { isValid: true }; + if (/\s/.test(value)) { + return { isValid: false, error: "Tag cannot contain spaces" }; + } + const invalidTagChars = /[^a-zA-Z0-9-]/; + const invalidCharMatch = value.match(invalidTagChars); + if (invalidCharMatch) { + return { + isValid: false, + error: `Tag contains invalid character: ${invalidCharMatch[0]}. Tags can only contain letters, numbers, and dashes.`, + }; + } + + return { isValid: true }; + }, + }, }; const FIELD_CONFIG_ARRAY = Object.values(FIELD_CONFIGS); @@ -84,20 +125,32 @@ const TextField = ({ value, error, onChange, + nodeType, }: { fieldConfig: BaseFieldConfig; value: string; error?: string; onChange: (value: string) => void; -}) => ( - onChange(e.target.value)} - placeholder={fieldConfig.placeholder} - className={`w-full ${error ? "input-error" : ""}`} - /> -); + nodeType?: DiscourseNode; +}) => { + // Generate dynamic placeholder for tag field based on node format and name + const getPlaceholder = (): string => { + if (fieldConfig.key === "tag" && nodeType?.format) { + return generateTagPlaceholder(nodeType.format, nodeType.name); + } + return fieldConfig.placeholder || ""; + }; + + return ( + onChange(e.target.value)} + placeholder={getPlaceholder()} + className={`w-full ${error ? "input-error" : ""}`} + /> + ); +}; const ColorField = ({ value, @@ -163,8 +216,12 @@ const FieldWrapper = ({
{fieldConfig.description}
- {children} - {error &&
{error}
} +
+ {children} +
+ {error &&
{error}
} +
+
); @@ -256,6 +313,7 @@ const NodeTypeSettings = () => { name: "", format: "", template: "", + tag: "", }; setEditingNodeType(newNodeType); setSelectedNodeIndex(nodeTypes.length); @@ -403,6 +461,7 @@ const NodeTypeSettings = () => { value={value} error={error} onChange={handleChange} + nodeType={editingNodeType} /> )} diff --git a/apps/obsidian/src/constants.ts b/apps/obsidian/src/constants.ts index 73258322e..a68a488f4 100644 --- a/apps/obsidian/src/constants.ts +++ b/apps/obsidian/src/constants.ts @@ -13,12 +13,14 @@ export const DEFAULT_NODE_TYPES: Record = { name: "Claim", format: "CLM - {content}", color: "#7DA13E", + tag: "#clm-candidate", }, Evidence: { id: generateUid("node"), name: "Evidence", format: "EVD - {content}", color: "#DB134A", + tag: "#evd-candidate", }, }; export const DEFAULT_RELATION_TYPES: Record = { diff --git a/apps/obsidian/src/types.ts b/apps/obsidian/src/types.ts index 981d5dc2d..9c0041d2e 100644 --- a/apps/obsidian/src/types.ts +++ b/apps/obsidian/src/types.ts @@ -8,6 +8,7 @@ export type DiscourseNode = { description?: string; shortcut?: string; color?: string; + tag?: string; }; export type DiscourseRelationType = { diff --git a/apps/obsidian/src/utils/tagNodeHandler.ts b/apps/obsidian/src/utils/tagNodeHandler.ts index 187d46814..f93a5b3e7 100644 --- a/apps/obsidian/src/utils/tagNodeHandler.ts +++ b/apps/obsidian/src/utils/tagNodeHandler.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/naming-convention */ import { App, Editor, Notice, MarkdownView } from "obsidian"; import { DiscourseNode } from "~/types"; @@ -152,15 +153,17 @@ export class TagNodeHandler { } this.plugin.settings.nodeTypes.forEach((nodeType) => { - const nodeTypeName = nodeType.name.toLowerCase(); - const tagSelector = `.cm-tag-${nodeTypeName}`; + if (!nodeType.tag) { + return; + } + + const tag = nodeType.tag as string; + const tagSelector = `.cm-tag-${tag}`; - // Check if the element itself matches if (element.matches(tagSelector)) { this.applyDiscourseTagStyling(element, nodeType); } - // Check all children const childTags = element.querySelectorAll(tagSelector); childTags.forEach((tagEl) => { if (tagEl instanceof HTMLElement) { @@ -257,7 +260,7 @@ export class TagNodeHandler { } const cleanText = sanitizeTitle( - extractedData.fullLineContent.replace(/#\w+/g, ""), + extractedData.fullLineContent.replace(/#[^\s]+/g, ""), ); new CreateNodeModal(this.app, { @@ -428,7 +431,6 @@ export class TagNodeHandler { const hideTooltip = () => { if (this.currentTooltip) { - console.log("Removing tooltip"); this.currentTooltip.remove(); this.currentTooltip = null; }