Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions apps/obsidian/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ For more information about Discourse Graphs, check out our website at [https://d
![BRAT](/apps/obsidian/docs/media/BRAT.png)
4. Install BRAT and enable it


#### Install DataCore via BRAT

1. Open Obsidian Settings
Expand Down Expand Up @@ -51,8 +50,25 @@ For more information about Discourse Graphs, check out our website at [https://d
- Under "Node Types," click "Add Node Type"
- Enter a name for your node type (e.g., "Claim", "Evidence", "Question")
- Add the format for your node type. eg a claim node will have page title "CLM - {content}"
![add node types](/apps/obsidian/docs/media/add-node-types.png)
- Click "Save Changes"
- **Template (Optional)**: Select a template from the dropdown to automatically apply template content when creating nodes of this type
- Templates are sourced from Obsidian's core Templates plugin
- Ensure you have the Templates plugin enabled and configured with a template folder
- The dropdown will show all available template files from your configured template folder

![add node types with template](/apps/obsidian/docs/media/choose-template.png)
- Click "Save Changes"


- To create a new template:
+ Create new folder to store templates
![new folder](/apps/obsidian/docs/media/new-folder.png)

+ Specify template folder location in plugin settings menu
![template](/apps/obsidian/docs/media/template.png)

+ Create new file in template folder (A) and add text to file (B)
![create template file](/apps/obsidian/docs/media/create-template-file.png)

#### Edit Relation Types
- Under "Relation Types," click "Add Relationship Type"
- A relation type is a kind of relationship that can exist between any two node types
Expand Down
Binary file added apps/obsidian/docs/media/choose-template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/obsidian/docs/media/new-folder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/obsidian/docs/media/template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 43 additions & 2 deletions apps/obsidian/src/components/NodeTypeSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import {
validateAllNodes,
validateNodeFormat,
Expand All @@ -9,6 +9,7 @@ import { Notice } from "obsidian";
import generateUid from "~/utils/generateUid";
import { DiscourseNode } from "~/types";
import { ConfirmationModal } from "./ConfirmationModal";
import { getTemplateFiles, getTemplatePluginInfo } from "~/utils/templates";

const NodeTypeSettings = () => {
const plugin = usePlugin();
Expand All @@ -17,6 +18,19 @@ const NodeTypeSettings = () => {
);
const [formatErrors, setFormatErrors] = useState<Record<number, string>>({});
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [templateFiles, setTemplateFiles] = useState<string[]>([]);
const [templateConfig, setTemplateConfig] = useState({
isEnabled: false,
folderPath: "",
});

useEffect(() => {
const config = getTemplatePluginInfo(plugin.app);
setTemplateConfig(config);

const files = getTemplateFiles(plugin.app);
setTemplateFiles(files);
}, [plugin.app]);

const updateErrors = (
index: number,
Expand Down Expand Up @@ -44,7 +58,12 @@ const NodeTypeSettings = () => {
const updatedNodeTypes = [...nodeTypes];
if (!updatedNodeTypes[index]) {
const newId = generateUid("node");
updatedNodeTypes[index] = { id: newId, name: "", format: "" };
updatedNodeTypes[index] = {
id: newId,
name: "",
format: "",
template: "",
};
}

updatedNodeTypes[index][field] = value;
Expand All @@ -69,6 +88,7 @@ const NodeTypeSettings = () => {
id: newId,
name: "",
format: "",
template: "",
},
];
setNodeTypes(updatedNodeTypes);
Expand Down Expand Up @@ -153,6 +173,27 @@ const NodeTypeSettings = () => {
}
className="flex-1"
/>
<select
value={nodeType.template || ""}
onChange={(e) =>
handleNodeTypeChange(index, "template", e.target.value)
}
className="flex-1"
disabled={
!templateConfig.isEnabled || !templateConfig.folderPath
}
>
<option value="">
{!templateConfig.isEnabled || !templateConfig.folderPath
? "Template folder not configured"
: "No template"}
</option>
{templateFiles.map((templateFile) => (
<option key={templateFile} value={templateFile}>
{templateFile}
</option>
))}
</select>
<button
onClick={() => confirmDeleteNodeType(index)}
className="mod-warning p-2"
Expand Down
1 change: 1 addition & 0 deletions apps/obsidian/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type DiscourseNode = {
id: string;
name: string;
format: string;
template?: string;
shortcut?: string;
color?: string;
};
Expand Down
15 changes: 15 additions & 0 deletions apps/obsidian/src/utils/createNodeFromSelectedText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { App, Editor, Notice, TFile } from "obsidian";
import { DiscourseNode } from "~/types";
import { getDiscourseNodeFormatExpression } from "./getDiscourseNodeFormatExpression";
import { checkInvalidChars } from "./validateNodeType";
import { applyTemplate } from "./templates";

export const formatNodeName = (
selectedText: string,
Expand Down Expand Up @@ -42,6 +43,20 @@ export const createDiscourseNodeFile = async ({
fm.nodeTypeId = nodeType.id;
});

if (nodeType.template && nodeType.template.trim() !== "") {
const templateApplied = await applyTemplate({
app,
targetFile: newFile,
templateName: nodeType.template,
});
if (!templateApplied) {
new Notice(
`Warning: Could not apply template "${nodeType.template}"`,
3000,
);
}
}

const notice = new DocumentFragment();
const spanEl = notice.createEl("span", {
text: "Created discourse node: ",
Expand Down
100 changes: 100 additions & 0 deletions apps/obsidian/src/utils/templates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { App, TFile, TFolder, TAbstractFile } from "obsidian";

type TemplatePluginInfo = {
isEnabled: boolean;
folderPath: string;
};

export const getTemplatePluginInfo = (app: App): TemplatePluginInfo => {
try {
const templatesPlugin = (app as any).internalPlugins?.plugins?.templates;

if (!templatesPlugin || !templatesPlugin.enabled) {
return { isEnabled: false, folderPath: "" };
}

const folderPath = templatesPlugin.instance?.options?.folder || "";

return {
isEnabled: true,
folderPath,
};
} catch (error) {
console.error("Error accessing Templates plugin:", error);
return { isEnabled: false, folderPath: "" };
}
};

export const getTemplateFiles = (app: App): string[] => {
try {
const { isEnabled, folderPath } = getTemplatePluginInfo(app);

if (!isEnabled || !folderPath) {
return [];
}

const templateFolder = app.vault.getAbstractFileByPath(folderPath);

if (!templateFolder || !(templateFolder instanceof TFolder)) {
return [];
}

const templateFiles = templateFolder.children
.filter(
(file: TAbstractFile): file is TFile =>
file instanceof TFile && file.extension === "md",
)
.map((file: TFile) => file.basename)
.sort();

return templateFiles;
} catch (error) {
console.error("Error getting template files:", error);
return [];
}
};

export const applyTemplate = async ({
app,
targetFile,
templateName,
}: {
app: App;
targetFile: TFile;
templateName: string;
}): Promise<boolean> => {
try {
const { isEnabled, folderPath } = getTemplatePluginInfo(app);

if (!isEnabled) {
console.warn("Templates plugin is not enabled");
return false;
}

if (!folderPath) {
console.warn("Template folder is not configured");
return false;
}

const templateFilePath = `${folderPath}/${templateName}.md`;
const templateFile = app.vault.getAbstractFileByPath(templateFilePath);

if (!templateFile || !(templateFile instanceof TFile)) {
console.warn(`Template file not found: ${templateFilePath}`);
return false;
}

const templateContent = await app.vault.read(templateFile);

const currentContent = await app.vault.read(targetFile);

const newContent = currentContent + templateContent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's bring this up with the UX team to see if this is the expected behavior.


await app.vault.modify(targetFile, newContent);

return true;
} catch (error) {
console.error("Error applying template:", error);
return false;
}
};