Skip to content

Commit e15ca50

Browse files
authored
[ENG-327] Add description field to the setting (#282)
* add description field * add key * sm fix
1 parent 6b47cc2 commit e15ca50

3 files changed

Lines changed: 92 additions & 57 deletions

File tree

apps/obsidian/src/components/NodeTypeSettings.tsx

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ const FIELD_CONFIGS: Record<EditableFieldKey, BaseFieldConfig> = {
3131
required: true,
3232
type: "text",
3333
validate: (value, nodeType, existingNodes) =>
34-
validateNodeName(value, [nodeType, ...existingNodes]),
34+
validateNodeName({
35+
name: value,
36+
currentNode: nodeType,
37+
allNodes: existingNodes,
38+
}),
3539
placeholder: "Name",
3640
},
3741
format: {
@@ -42,9 +46,21 @@ const FIELD_CONFIGS: Record<EditableFieldKey, BaseFieldConfig> = {
4246
required: true,
4347
type: "text",
4448
validate: (value, nodeType, existingNodes) =>
45-
validateNodeFormat(value, [nodeType, ...existingNodes]),
49+
validateNodeFormat({
50+
format: value,
51+
currentNode: nodeType,
52+
allNodes: existingNodes,
53+
}),
4654
placeholder: "Format (e.g., CLM - {content})",
4755
},
56+
description: {
57+
key: "description",
58+
label: "Description",
59+
description: "A brief description of what this node type represents",
60+
required: false,
61+
type: "text",
62+
placeholder: "Enter a description",
63+
},
4864
template: {
4965
key: "template",
5066
label: "Template",
@@ -342,7 +358,7 @@ const NodeTypeSettings = () => {
342358
handleNodeTypeChange(fieldConfig.key, newValue);
343359

344360
return (
345-
<FieldWrapper fieldConfig={fieldConfig} error={error}>
361+
<FieldWrapper fieldConfig={fieldConfig} error={error} key={fieldConfig.key}>
346362
{fieldConfig.key === "template" ? (
347363
<TemplateField
348364
value={value}
@@ -371,32 +387,43 @@ const NodeTypeSettings = () => {
371387
{nodeTypes.map((nodeType, index) => (
372388
<div
373389
key={nodeType.id}
374-
className="node-type-item hover:bg-secondary-lt flex cursor-pointer items-center justify-between p-2"
390+
className="node-type-item hover:bg-secondary-lt flex cursor-pointer flex-col gap-1 p-2"
375391
onClick={() => startEditing(index)}
376392
>
377-
<span>{nodeType.name}</span>
378-
<div className="flex gap-2">
379-
<button
380-
className="icon-button"
381-
onClick={(e) => {
382-
e.stopPropagation();
383-
startEditing(index);
384-
}}
385-
aria-label="Edit node type"
386-
>
387-
<div className="icon" ref={(el) => el && setIcon(el, "pencil")} />
388-
</button>
389-
<button
390-
className="icon-button mod-warning"
391-
onClick={(e) => {
392-
e.stopPropagation();
393-
confirmDeleteNodeType(index);
394-
}}
395-
aria-label="Delete node type"
396-
>
397-
<div className="icon" ref={(el) => el && setIcon(el, "trash")} />
398-
</button>
393+
<div className="flex items-center justify-between">
394+
<span className="font-medium">{nodeType.name}</span>
395+
<div className="flex gap-2">
396+
<button
397+
className="icon-button"
398+
onClick={(e) => {
399+
e.stopPropagation();
400+
startEditing(index);
401+
}}
402+
aria-label="Edit node type"
403+
>
404+
<div
405+
className="icon"
406+
ref={(el) => el && setIcon(el, "pencil")}
407+
/>
408+
</button>
409+
<button
410+
className="icon-button mod-warning"
411+
onClick={(e) => {
412+
e.stopPropagation();
413+
confirmDeleteNodeType(index);
414+
}}
415+
aria-label="Delete node type"
416+
>
417+
<div
418+
className="icon"
419+
ref={(el) => el && setIcon(el, "trash")}
420+
/>
421+
</button>
422+
</div>
399423
</div>
424+
{nodeType.description && (
425+
<span className="text-muted text-sm">{nodeType.description}</span>
426+
)}
400427
</div>
401428
))}
402429
</div>

apps/obsidian/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type DiscourseNode = {
55
name: string;
66
format: string;
77
template?: string;
8+
description?: string;
89
shortcut?: string;
910
color?: string;
1011
};

apps/obsidian/src/utils/validateNodeType.ts

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ type ValidationResult = {
55
error?: string;
66
};
77

8-
export function validateNodeFormat(
9-
format: string,
10-
nodeTypes: DiscourseNode[],
11-
): ValidationResult {
8+
export const validateNodeFormat = ({
9+
format,
10+
currentNode,
11+
allNodes,
12+
}: {
13+
format: string;
14+
currentNode: DiscourseNode;
15+
allNodes: DiscourseNode[];
16+
}): ValidationResult => {
1217
if (!format) {
1318
return {
1419
isValid: false,
@@ -35,13 +40,17 @@ export function validateNodeFormat(
3540
return invalidCharsResult;
3641
}
3742

38-
const uniquenessResult = validateFormatUniqueness(nodeTypes);
39-
if (!uniquenessResult.isValid) {
40-
return uniquenessResult;
43+
const otherNodes = allNodes.filter((node) => node.id !== currentNode.id);
44+
const isDuplicate = otherNodes.some((node) => node.format === format);
45+
if (isDuplicate) {
46+
return {
47+
isValid: false,
48+
error: "Format must be unique across all node types",
49+
};
4150
}
4251

4352
return { isValid: true };
44-
}
53+
};
4554

4655
export const checkInvalidChars = (format: string): ValidationResult => {
4756
const INVALID_FILENAME_CHARS_REGEX = /[#^\[\]|]/;
@@ -56,31 +65,21 @@ export const checkInvalidChars = (format: string): ValidationResult => {
5665
return { isValid: true };
5766
};
5867

59-
const validateFormatUniqueness = (
60-
nodeTypes: DiscourseNode[],
61-
): ValidationResult => {
62-
const isDuplicate =
63-
new Set(nodeTypes.map((nodeType) => nodeType.format)).size !==
64-
nodeTypes.length;
65-
66-
if (isDuplicate) {
67-
return { isValid: false, error: "Format must be unique" };
68-
}
69-
70-
return { isValid: true };
71-
};
72-
73-
export const validateNodeName = (
74-
name: string,
75-
nodeTypes: DiscourseNode[],
76-
): ValidationResult => {
68+
export const validateNodeName = ({
69+
name,
70+
currentNode,
71+
allNodes,
72+
}: {
73+
name: string;
74+
currentNode: DiscourseNode;
75+
allNodes: DiscourseNode[];
76+
}): ValidationResult => {
7777
if (!name || name.trim() === "") {
7878
return { isValid: false, error: "Name is required" };
7979
}
8080

81-
const isDuplicate =
82-
new Set(nodeTypes.map((nodeType) => nodeType.name)).size !==
83-
nodeTypes.length;
81+
const otherNodes = allNodes.filter((node) => node.id !== currentNode.id);
82+
const isDuplicate = otherNodes.some((node) => node.name === name);
8483

8584
if (isDuplicate) {
8685
return { isValid: false, error: "Name must be unique" };
@@ -101,14 +100,22 @@ export const validateAllNodes = (
101100
return;
102101
}
103102

104-
const formatValidation = validateNodeFormat(nodeType.format, nodeTypes);
103+
const formatValidation = validateNodeFormat({
104+
format: nodeType.format,
105+
currentNode: nodeType,
106+
allNodes: nodeTypes,
107+
});
105108
if (!formatValidation.isValid) {
106109
errorMap[index] = formatValidation.error || "Invalid format";
107110
hasErrors = true;
108111
return;
109112
}
110113

111-
const nameValidation = validateNodeName(nodeType.name, nodeTypes);
114+
const nameValidation = validateNodeName({
115+
name: nodeType.name,
116+
currentNode: nodeType,
117+
allNodes: nodeTypes,
118+
});
112119
if (!nameValidation.isValid) {
113120
errorMap[index] = nameValidation.error || "Invalid name";
114121
hasErrors = true;

0 commit comments

Comments
 (0)