Skip to content
Prev Previous commit
Next Next commit
address PR comments
  • Loading branch information
trangdoan982 committed Feb 28, 2025
commit 9ae208c1cf6e2eb671ca6e6e51200a3f1503381d
53 changes: 35 additions & 18 deletions apps/obsidian/src/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { App, Hotkey, Modifier, PluginSettingTab, Setting } from "obsidian";
import type DiscourseGraphPlugin from "../index";
import { Root, createRoot } from "react-dom/client";
import { ContextProvider, useApp } from "./AppContext";
import { getDiscourseNodeFormatExpression } from "../utils/getDiscourseNodeFormatExpression";

const NodeTypeSettings = ({
nodeTypes,
Expand Down Expand Up @@ -81,7 +82,7 @@ const HotkeyInput = ({
onChange,
}: {
value: Hotkey;
onChange: (hotkey: Hotkey) => void;
onChange: (hotkey: Hotkey) => Promise<void>;
}) => {
const [isListening, setIsListening] = useState(false);
const [currentHotkey, setCurrentHotkey] = useState<Hotkey>(value);
Expand Down Expand Up @@ -129,6 +130,15 @@ const HotkeyInput = ({
return [...formattedModifiers, hotkey.key].join(" + ");
};

const handleSave = async () => {
try {
await onChange(currentHotkey);
setIsListening(false);
} catch (error) {
console.error("Failed to save hotkey:", error);
}
};

return (
<div>
<input type="text" value={formatHotkey(currentHotkey)} readOnly />
Expand All @@ -138,16 +148,7 @@ const HotkeyInput = ({
>
{isListening ? "Listening..." : "Edit"}
</button>
{isListening && (
<button
onClick={() => {
onChange(currentHotkey);
setIsListening(false);
}}
>
Save
</button>
)}
{isListening && <button onClick={handleSave}>Save</button>}
</div>
);
};
Expand Down Expand Up @@ -192,8 +193,15 @@ const Settings = ({ plugin }: { plugin: DiscourseGraphPlugin }) => {
}, [plugin]);

const validateFormat = (format: string): boolean => {
const formatRegex = /^\[([A-Z-]+)\]\s-\s\{content\}$/;
return formatRegex.test(format);
// TODO: fix validation format
if (!format) return true; // Empty format is valid
try {
const regex = getDiscourseNodeFormatExpression(format);
// Test with a sample string to make sure it's a valid format
return regex.test("[TEST] - Sample content");
} catch (e) {
return false;
}
};

const handleNodeTypeChange = async (
Expand All @@ -215,7 +223,7 @@ const Settings = ({ plugin }: { plugin: DiscourseGraphPlugin }) => {
setFormatErrors((prev) => ({
...prev,
[index]:
"Format must be [KEYWORD] - {content} with uppercase keyword",
"Invalid format. You can use any {variable} in your format, e.g., [TYPE] - {content}",
}));
} else {
setFormatErrors((prev) => {
Expand Down Expand Up @@ -252,6 +260,17 @@ const Settings = ({ plugin }: { plugin: DiscourseGraphPlugin }) => {
await plugin.saveSettings();
};

const handleHotkeyChange = async (newHotkey: Hotkey) => {
try {
plugin.settings.nodeTypeHotkey = newHotkey;
await plugin.saveSettings();
setNodeTypeHotkey(newHotkey);
setNodeTypeHotkey(plugin.settings.nodeTypeHotkey);
} catch (error) {
console.error("Failed to save hotkey:", error);
}
};

return (
<div>
<h2>Discourse Graph Settings</h2>
Expand All @@ -270,9 +289,7 @@ const Settings = ({ plugin }: { plugin: DiscourseGraphPlugin }) => {
<HotkeyInput
value={nodeTypeHotkey}
onChange={async (newHotkey) => {
setNodeTypeHotkey(newHotkey);
plugin.settings.nodeTypeHotkey = newHotkey;
await plugin.saveSettings();
await handleHotkeyChange(newHotkey);
}}
/>
</div>
Expand Down Expand Up @@ -310,4 +327,4 @@ export class SettingsTab extends PluginSettingTab {
</StrictMode>,
);
}
}
}
53 changes: 0 additions & 53 deletions apps/obsidian/src/components/SettingsTab.tsx

This file was deleted.

83 changes: 57 additions & 26 deletions apps/obsidian/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,72 @@
import { Plugin } from "obsidian";
import { registerCommands } from "~/utils/registerCommands";
import { App, Editor, Notice, Plugin, SuggestModal } from "obsidian";
import { SettingsTab } from "~/components/Settings";
import { Settings } from "./types";
import { DiscourseNodeType, Settings } from "./types";

const DEFAULT_SETTINGS: Settings = {
mySetting: "default",
nodeTypes: [
{
name: "Claim",
format: "[[CLM]] - {content}",
shortcut: "C",
color: "#7DA13E",
},
{
name: "Question",
format: "[[QUE]] - {content}",
shortcut: "Q",
color: "#99890e",
},
{
name: "Evidence",
format: "[[EVD]] - {content}",
shortcut: "E",
color: "#DB134A",
},
],
nodeTypes: [],
nodeTypeHotkey: {
modifiers: ["Mod", "Shift"],
key: "Backslash",
modifiers: [],
key: "",
},
};

class NodeTypeModal extends SuggestModal<DiscourseNodeType> {
constructor(
app: App,
private editor: Editor,
private nodeTypes: DiscourseNodeType[],
) {
super(app);
}

getItemText(item: DiscourseNodeType): string {
return item.name;
}

// Get all available items
getSuggestions() {
const query = this.inputEl.value.toLowerCase();
return this.nodeTypes.filter((node) =>
this.getItemText(node).toLowerCase().includes(query),
);
}

renderSuggestion(nodeType: DiscourseNodeType, el: HTMLElement) {
el.createEl("div", { text: nodeType.name });
}

onChooseSuggestion(nodeType: DiscourseNodeType) {
const selectedText = this.editor.getSelection();
// TODO: get the regex from the nodeType
const heading = nodeType.format.split(" ")[0];
const nodeFormat = `[[${heading} - ${selectedText}]]`;
this.editor.replaceSelection(nodeFormat);
}
}

export default class DiscourseGraphPlugin extends Plugin {
settings: Settings = { ...DEFAULT_SETTINGS };

private registerNodeTypeCommand() {
return this.addCommand({
id: "open-node-type-menu",
name: "Open Node Type Menu",
hotkeys: [this.settings.nodeTypeHotkey],
editorCallback: (editor: Editor) => {
if (!this.settings.nodeTypes.length) {
new Notice("No node types configured!");
return;
}

new NodeTypeModal(this.app, editor, this.settings.nodeTypes).open();
},
});
}

async onload() {
await this.loadSettings();
registerCommands(this);
this.registerNodeTypeCommand();
this.addSettingTab(new SettingsTab(this.app, this));
}

Expand All @@ -48,5 +78,6 @@ export default class DiscourseGraphPlugin extends Plugin {

async saveSettings() {
await this.saveData(this.settings);
await this.registerNodeTypeCommand();
}
}
9 changes: 9 additions & 0 deletions apps/obsidian/src/utils/getDiscourseNodeFormatExpression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const getDiscourseNodeFormatExpression = (format: string) =>
format
? new RegExp(
`^${format
.replace(/(\[|\]|\?|\.|\+)/g, "\\$1")
.replace(/{[a-zA-Z]+}/g, "(.*?)")}$`,
"s",
)
: /$^/;
39 changes: 0 additions & 39 deletions apps/obsidian/src/utils/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,6 @@ import { SampleModal } from "~/components/SampleModal";
import type DiscourseGraphPlugin from "~/index";
import { DiscourseNodeType } from "~/types";

class NodeTypeModal extends SuggestModal<{ name: string; format: string }> {
constructor(
app: App,
private editor: Editor,
private nodeTypes: DiscourseNodeType[],
) {
super(app);
}

getSuggestions(): DiscourseNodeType[] {
return this.nodeTypes;
}

renderSuggestion(nodeType: DiscourseNodeType, el: HTMLElement) {
el.createEl("div", { text: nodeType.name });
}

onChooseSuggestion(nodeType: DiscourseNodeType) {
const selectedText = this.editor.getSelection();
const heading = nodeType.format.split(" ")[0];
const nodeFormat = `[[${heading} - ${selectedText}]]`;
this.editor.replaceSelection(nodeFormat);
}
}

export const registerCommands = (plugin: DiscourseGraphPlugin) => {
// This adds a simple command that can be triggered anywhere
plugin.addCommand({
Expand Down Expand Up @@ -77,18 +52,4 @@ export const registerCommands = (plugin: DiscourseGraphPlugin) => {
}
},
});

plugin.addCommand({
id: "open-node-type-menu",
name: "Open Node Type Menu",
hotkeys: [plugin.settings.nodeTypeHotkey].filter(Boolean),
editorCallback: (editor: Editor) => {
if (!plugin.settings.nodeTypes.length) {
new Notice("No node types configured!");
return;
}

new NodeTypeModal(plugin.app, editor, plugin.settings.nodeTypes).open();
},
});
};