Skip to content

Commit 63e8bff

Browse files
authored
[ENG-350] Obsidian - Add template to node type setting (#179)
* UI ready * functionalities completed * add readme * fix README style * address PR comments
1 parent b99c5a5 commit 63e8bff

9 files changed

Lines changed: 178 additions & 5 deletions

File tree

apps/obsidian/README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ For more information about Discourse Graphs, check out our website at [https://d
1616
![BRAT](/apps/obsidian/docs/media/BRAT.png)
1717
4. Install BRAT and enable it
1818

19-
2019
#### Install DataCore via BRAT
2120

2221
1. Open Obsidian Settings
@@ -51,8 +50,25 @@ For more information about Discourse Graphs, check out our website at [https://d
5150
- Under "Node Types," click "Add Node Type"
5251
- Enter a name for your node type (e.g., "Claim", "Evidence", "Question")
5352
- Add the format for your node type. eg a claim node will have page title "CLM - {content}"
54-
![add node types](/apps/obsidian/docs/media/add-node-types.png)
55-
- Click "Save Changes"
53+
- **Template (Optional)**: Select a template from the dropdown to automatically apply template content when creating nodes of this type
54+
- Templates are sourced from Obsidian's core Templates plugin
55+
- Ensure you have the Templates plugin enabled and configured with a template folder
56+
- The dropdown will show all available template files from your configured template folder
57+
58+
![add node types with template](/apps/obsidian/docs/media/choose-template.png)
59+
- Click "Save Changes"
60+
61+
62+
- To create a new template:
63+
+ Create new folder to store templates
64+
![new folder](/apps/obsidian/docs/media/new-folder.png)
65+
66+
+ Specify template folder location in plugin settings menu
67+
![template](/apps/obsidian/docs/media/template.png)
68+
69+
+ Create new file in template folder (A) and add text to file (B)
70+
![create template file](/apps/obsidian/docs/media/create-template-file.png)
71+
5672
#### Edit Relation Types
5773
- Under "Relation Types," click "Add Relationship Type"
5874
- A relation type is a kind of relationship that can exist between any two node types
53.1 KB
Loading
395 KB
Loading
27.3 KB
Loading
219 KB
Loading

apps/obsidian/src/components/NodeTypeSettings.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { useState, useEffect } from "react";
22
import {
33
validateAllNodes,
44
validateNodeFormat,
@@ -9,6 +9,7 @@ import { Notice } from "obsidian";
99
import generateUid from "~/utils/generateUid";
1010
import { DiscourseNode } from "~/types";
1111
import { ConfirmationModal } from "./ConfirmationModal";
12+
import { getTemplateFiles, getTemplatePluginInfo } from "~/utils/templates";
1213

1314
const NodeTypeSettings = () => {
1415
const plugin = usePlugin();
@@ -17,6 +18,19 @@ const NodeTypeSettings = () => {
1718
);
1819
const [formatErrors, setFormatErrors] = useState<Record<number, string>>({});
1920
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
21+
const [templateFiles, setTemplateFiles] = useState<string[]>([]);
22+
const [templateConfig, setTemplateConfig] = useState({
23+
isEnabled: false,
24+
folderPath: "",
25+
});
26+
27+
useEffect(() => {
28+
const config = getTemplatePluginInfo(plugin.app);
29+
setTemplateConfig(config);
30+
31+
const files = getTemplateFiles(plugin.app);
32+
setTemplateFiles(files);
33+
}, [plugin.app]);
2034

2135
const updateErrors = (
2236
index: number,
@@ -44,7 +58,12 @@ const NodeTypeSettings = () => {
4458
const updatedNodeTypes = [...nodeTypes];
4559
if (!updatedNodeTypes[index]) {
4660
const newId = generateUid("node");
47-
updatedNodeTypes[index] = { id: newId, name: "", format: "" };
61+
updatedNodeTypes[index] = {
62+
id: newId,
63+
name: "",
64+
format: "",
65+
template: "",
66+
};
4867
}
4968

5069
updatedNodeTypes[index][field] = value;
@@ -69,6 +88,7 @@ const NodeTypeSettings = () => {
6988
id: newId,
7089
name: "",
7190
format: "",
91+
template: "",
7292
},
7393
];
7494
setNodeTypes(updatedNodeTypes);
@@ -153,6 +173,27 @@ const NodeTypeSettings = () => {
153173
}
154174
className="flex-1"
155175
/>
176+
<select
177+
value={nodeType.template || ""}
178+
onChange={(e) =>
179+
handleNodeTypeChange(index, "template", e.target.value)
180+
}
181+
className="flex-1"
182+
disabled={
183+
!templateConfig.isEnabled || !templateConfig.folderPath
184+
}
185+
>
186+
<option value="">
187+
{!templateConfig.isEnabled || !templateConfig.folderPath
188+
? "Template folder not configured"
189+
: "No template"}
190+
</option>
191+
{templateFiles.map((templateFile) => (
192+
<option key={templateFile} value={templateFile}>
193+
{templateFile}
194+
</option>
195+
))}
196+
</select>
156197
<button
157198
onClick={() => confirmDeleteNodeType(index)}
158199
className="mod-warning p-2"

apps/obsidian/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type DiscourseNode = {
22
id: string;
33
name: string;
44
format: string;
5+
template?: string;
56
shortcut?: string;
67
color?: string;
78
};

apps/obsidian/src/utils/createNodeFromSelectedText.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { App, Editor, Notice, TFile } from "obsidian";
22
import { DiscourseNode } from "~/types";
33
import { getDiscourseNodeFormatExpression } from "./getDiscourseNodeFormatExpression";
44
import { checkInvalidChars } from "./validateNodeType";
5+
import { applyTemplate } from "./templates";
56

67
export const formatNodeName = (
78
selectedText: string,
@@ -42,6 +43,20 @@ export const createDiscourseNodeFile = async ({
4243
fm.nodeTypeId = nodeType.id;
4344
});
4445

46+
if (nodeType.template && nodeType.template.trim() !== "") {
47+
const templateApplied = await applyTemplate({
48+
app,
49+
targetFile: newFile,
50+
templateName: nodeType.template,
51+
});
52+
if (!templateApplied) {
53+
new Notice(
54+
`Warning: Could not apply template "${nodeType.template}"`,
55+
3000,
56+
);
57+
}
58+
}
59+
4560
const notice = new DocumentFragment();
4661
const spanEl = notice.createEl("span", {
4762
text: "Created discourse node: ",
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { App, TFile, TFolder, TAbstractFile } from "obsidian";
2+
3+
type TemplatePluginInfo = {
4+
isEnabled: boolean;
5+
folderPath: string;
6+
};
7+
8+
export const getTemplatePluginInfo = (app: App): TemplatePluginInfo => {
9+
try {
10+
const templatesPlugin = (app as any).internalPlugins?.plugins?.templates;
11+
12+
if (!templatesPlugin || !templatesPlugin.enabled) {
13+
return { isEnabled: false, folderPath: "" };
14+
}
15+
16+
const folderPath = templatesPlugin.instance?.options?.folder || "";
17+
18+
return {
19+
isEnabled: true,
20+
folderPath,
21+
};
22+
} catch (error) {
23+
console.error("Error accessing Templates plugin:", error);
24+
return { isEnabled: false, folderPath: "" };
25+
}
26+
};
27+
28+
export const getTemplateFiles = (app: App): string[] => {
29+
try {
30+
const { isEnabled, folderPath } = getTemplatePluginInfo(app);
31+
32+
if (!isEnabled || !folderPath) {
33+
return [];
34+
}
35+
36+
const templateFolder = app.vault.getAbstractFileByPath(folderPath);
37+
38+
if (!templateFolder || !(templateFolder instanceof TFolder)) {
39+
return [];
40+
}
41+
42+
const templateFiles = templateFolder.children
43+
.filter(
44+
(file: TAbstractFile): file is TFile =>
45+
file instanceof TFile && file.extension === "md",
46+
)
47+
.map((file: TFile) => file.basename)
48+
.sort();
49+
50+
return templateFiles;
51+
} catch (error) {
52+
console.error("Error getting template files:", error);
53+
return [];
54+
}
55+
};
56+
57+
export const applyTemplate = async ({
58+
app,
59+
targetFile,
60+
templateName,
61+
}: {
62+
app: App;
63+
targetFile: TFile;
64+
templateName: string;
65+
}): Promise<boolean> => {
66+
try {
67+
const { isEnabled, folderPath } = getTemplatePluginInfo(app);
68+
69+
if (!isEnabled) {
70+
console.warn("Templates plugin is not enabled");
71+
return false;
72+
}
73+
74+
if (!folderPath) {
75+
console.warn("Template folder is not configured");
76+
return false;
77+
}
78+
79+
const templateFilePath = `${folderPath}/${templateName}.md`;
80+
const templateFile = app.vault.getAbstractFileByPath(templateFilePath);
81+
82+
if (!templateFile || !(templateFile instanceof TFile)) {
83+
console.warn(`Template file not found: ${templateFilePath}`);
84+
return false;
85+
}
86+
87+
const templateContent = await app.vault.read(templateFile);
88+
89+
const currentContent = await app.vault.read(targetFile);
90+
91+
const newContent = currentContent + templateContent;
92+
93+
await app.vault.modify(targetFile, newContent);
94+
95+
return true;
96+
} catch (error) {
97+
console.error("Error applying template:", error);
98+
return false;
99+
}
100+
};

0 commit comments

Comments
 (0)