From 16c4fc8e1993b11f84854ad10bc3eb27b9cf9abf Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Sun, 1 Feb 2026 16:23:11 +0100 Subject: [PATCH 1/4] fix: add choice rename action --- src/gui/ChoiceBuilder/choiceBuilder.ts | 14 ++++++++-- src/gui/choiceList/ChoiceList.svelte | 2 ++ src/gui/choiceList/ChoiceListItem.svelte | 1 + src/gui/choiceList/ChoiceView.svelte | 28 +++++++++++++++++++ src/gui/choiceList/MultiChoiceListItem.svelte | 2 ++ src/gui/choiceList/contextMenu.ts | 2 ++ src/styles.css | 10 +++++++ 7 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/gui/ChoiceBuilder/choiceBuilder.ts b/src/gui/ChoiceBuilder/choiceBuilder.ts index ae601948..3cb42bd9 100644 --- a/src/gui/ChoiceBuilder/choiceBuilder.ts +++ b/src/gui/ChoiceBuilder/choiceBuilder.ts @@ -1,4 +1,4 @@ -import { type App, Modal, Setting } from "obsidian"; +import { type App, Modal, Setting, setIcon } from "obsidian"; import type { SvelteComponent } from "svelte"; import { log } from "../../logger/logManager"; import type IChoice from "../../types/choices/IChoice"; @@ -85,7 +85,15 @@ export abstract class ChoiceBuilder extends Modal { const headerEl: HTMLHeadingElement = this.contentEl.createEl("h2", { cls: "choiceNameHeader", }); - headerEl.setText(choice.name); + const textEl = headerEl.createSpan({ + text: choice.name, + cls: "choiceNameHeaderText", + }); + const iconEl = headerEl.createSpan({ + cls: "choiceNameHeaderIcon", + attr: { "aria-hidden": "true" }, + }); + setIcon(iconEl, "pencil"); headerEl.addEventListener("click", async (ev) => { try { @@ -97,7 +105,7 @@ export abstract class ChoiceBuilder extends Modal { ); if (newName !== choice.name) { choice.name = newName; - headerEl.setText(newName); + textEl.setText(newName); } } catch { log.logMessage(`No new name given for ${choice.name}`); diff --git a/src/gui/choiceList/ChoiceList.svelte b/src/gui/choiceList/ChoiceList.svelte index 4f58e827..0cdab3ba 100644 --- a/src/gui/choiceList/ChoiceList.svelte +++ b/src/gui/choiceList/ChoiceList.svelte @@ -73,6 +73,7 @@ on:configureChoice on:toggleCommand on:duplicateChoice + on:renameChoice on:moveChoice startDrag={startDrag} bind:choice @@ -86,6 +87,7 @@ on:configureChoice on:toggleCommand on:duplicateChoice + on:renameChoice on:moveChoice on:reorderChoices startDrag={startDrag} diff --git a/src/gui/choiceList/ChoiceListItem.svelte b/src/gui/choiceList/ChoiceListItem.svelte index 4d0e90f4..b5db54fe 100644 --- a/src/gui/choiceList/ChoiceListItem.svelte +++ b/src/gui/choiceList/ChoiceListItem.svelte @@ -47,6 +47,7 @@ function onContextMenu(evt: MouseEvent) { showChoiceContextMenu(app, evt, choice, roots, { + onRename: () => dispatcher("renameChoice", { choice }), onToggle: () => toggleCommandForChoice(), onConfigure: () => configureChoice(), onDuplicate: () => duplicateChoice(), diff --git a/src/gui/choiceList/ChoiceView.svelte b/src/gui/choiceList/ChoiceView.svelte index 7df4e0a3..6d44c6cf 100644 --- a/src/gui/choiceList/ChoiceView.svelte +++ b/src/gui/choiceList/ChoiceView.svelte @@ -16,6 +16,7 @@ import type IChoice from "../../types/choices/IChoice"; import { AIAssistantSettingsModal } from "../AIAssistantSettingsModal"; import ObsidianIcon from "../components/ObsidianIcon.svelte"; + import GenericInputPrompt from "../GenericInputPrompt/GenericInputPrompt"; import AddChoiceBox from "./AddChoiceBox.svelte"; import ChoiceList from "./ChoiceList.svelte"; import { moveChoice as moveChoiceService } from "../../services/choiceService"; @@ -132,6 +133,31 @@ return oldChoice; } + async function handleRenameChoice(e: any) { + const { choice } = e.detail; + if (!choice) return; + + try { + const newName = await GenericInputPrompt.Prompt( + app, + choice.name, + "Choice name", + choice.name, + ); + const trimmed = newName.trim(); + if (!trimmed || trimmed === choice.name) return; + + const updatedChoice = { ...choice, name: trimmed }; + choices = choices.map((entry) => + updateChoiceHelper(entry, updatedChoice), + ); + commandRegistry.updateCommand(choice, updatedChoice); + saveChoices(choices); + } catch { + // Swallow cancellation/no input. + } + } + async function toggleCommandForChoice(e: any) { const { choice: oldChoice } = e.detail; const updatedChoice = createToggleCommandChoice(oldChoice); @@ -209,6 +235,7 @@ on:configureChoice={handleConfigureChoice} on:toggleCommand={toggleCommandForChoice} on:duplicateChoice={handleDuplicateChoice} + on:renameChoice={handleRenameChoice} on:moveChoice={handleMoveChoice} on:reorderChoices={(e) => saveChoices(e.detail.choices)} /> @@ -223,6 +250,7 @@ on:configureChoice={handleConfigureChoice} on:toggleCommand={toggleCommandForChoice} on:duplicateChoice={handleDuplicateChoice} + on:renameChoice={handleRenameChoice} on:moveChoice={handleMoveChoice} /> {/if} diff --git a/src/gui/choiceList/MultiChoiceListItem.svelte b/src/gui/choiceList/MultiChoiceListItem.svelte index 11671f03..d011a026 100644 --- a/src/gui/choiceList/MultiChoiceListItem.svelte +++ b/src/gui/choiceList/MultiChoiceListItem.svelte @@ -52,6 +52,7 @@ function onContextMenu(evt: MouseEvent) { showChoiceContextMenu(app, evt, choice, roots, { + onRename: () => dispatcher('renameChoice', { choice }), onToggle: () => toggleCommandForChoice(), onConfigure: () => configureChoice(), onDuplicate: () => duplicateChoice(), @@ -114,6 +115,7 @@ on:toggleCommand on:duplicateChoice on:moveChoice + on:renameChoice on:reorderChoices={handleNestedReorder} bind:multiChoice={choice} bind:choices={choice.choices} diff --git a/src/gui/choiceList/contextMenu.ts b/src/gui/choiceList/contextMenu.ts index abb01c2a..f1178564 100644 --- a/src/gui/choiceList/contextMenu.ts +++ b/src/gui/choiceList/contextMenu.ts @@ -49,6 +49,7 @@ function isInvalidTarget(moving: IChoice, target: IChoice): boolean { } type MenuActions = { + onRename: () => void; onToggle: () => void; onConfigure: () => void; onDuplicate: () => void; @@ -79,6 +80,7 @@ export function showChoiceContextMenu( .setIcon("zap") .onClick(actions.onToggle), ) + .addItem((item) => item.setTitle("Rename").setIcon("pencil").onClick(actions.onRename)) .addItem((item) => item.setTitle("Configure").setIcon("settings").onClick(actions.onConfigure)) .addItem((item) => item.setTitle("Duplicate").setIcon("copy").onClick(actions.onDuplicate)) .addItem((item) => item.setTitle("Delete").setIcon("trash-2").onClick(actions.onDelete)) diff --git a/src/styles.css b/src/styles.css index 534065f9..165fc793 100644 --- a/src/styles.css +++ b/src/styles.css @@ -36,12 +36,22 @@ .choiceNameHeader { text-align: center; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; } .choiceNameHeader:hover { cursor: pointer; } +.choiceNameHeaderIcon { + display: inline-flex; + align-items: center; + opacity: 0.5; +} + .folderInputContainer { display: flex; align-content: center; From d9a66e2e45d7479dc909c9cf7f39a08e1f442dbf Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Sun, 1 Feb 2026 22:31:09 +0100 Subject: [PATCH 2/4] refactor: share choice rename prompt --- src/gui/ChoiceBuilder/choiceBuilder.ts | 21 +++++-------------- src/gui/choiceList/ChoiceView.svelte | 28 +++++++++----------------- src/gui/choiceRename.ts | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+), 35 deletions(-) create mode 100644 src/gui/choiceRename.ts diff --git a/src/gui/ChoiceBuilder/choiceBuilder.ts b/src/gui/ChoiceBuilder/choiceBuilder.ts index 3cb42bd9..34240864 100644 --- a/src/gui/ChoiceBuilder/choiceBuilder.ts +++ b/src/gui/ChoiceBuilder/choiceBuilder.ts @@ -1,14 +1,13 @@ import { type App, Modal, Setting, setIcon } from "obsidian"; import type { SvelteComponent } from "svelte"; -import { log } from "../../logger/logManager"; import type IChoice from "../../types/choices/IChoice"; import type { FileViewMode2, OpenLocation } from "../../types/fileOpening"; import { normalizeFileOpening, type FileOpeningSettings, } from "../../utils/fileOpeningDefaults"; -import GenericInputPrompt from "../GenericInputPrompt/GenericInputPrompt"; import { GenericTextSuggester } from "../suggesters/genericTextSuggester"; +import { promptRenameChoice } from "../choiceRename"; export abstract class ChoiceBuilder extends Modal { private resolvePromise: (input: IChoice) => void; @@ -96,20 +95,10 @@ export abstract class ChoiceBuilder extends Modal { setIcon(iconEl, "pencil"); headerEl.addEventListener("click", async (ev) => { - try { - const newName: string = await GenericInputPrompt.Prompt( - this.app, - choice.name, - "Choice name", - choice.name, - ); - if (newName !== choice.name) { - choice.name = newName; - textEl.setText(newName); - } - } catch { - log.logMessage(`No new name given for ${choice.name}`); - } + const newName = await promptRenameChoice(this.app, choice.name); + if (!newName) return; + choice.name = newName; + textEl.setText(newName); }); } diff --git a/src/gui/choiceList/ChoiceView.svelte b/src/gui/choiceList/ChoiceView.svelte index 6d44c6cf..d0bdf77b 100644 --- a/src/gui/choiceList/ChoiceView.svelte +++ b/src/gui/choiceList/ChoiceView.svelte @@ -16,7 +16,7 @@ import type IChoice from "../../types/choices/IChoice"; import { AIAssistantSettingsModal } from "../AIAssistantSettingsModal"; import ObsidianIcon from "../components/ObsidianIcon.svelte"; - import GenericInputPrompt from "../GenericInputPrompt/GenericInputPrompt"; + import { promptRenameChoice } from "../choiceRename"; import AddChoiceBox from "./AddChoiceBox.svelte"; import ChoiceList from "./ChoiceList.svelte"; import { moveChoice as moveChoiceService } from "../../services/choiceService"; @@ -137,25 +137,15 @@ const { choice } = e.detail; if (!choice) return; - try { - const newName = await GenericInputPrompt.Prompt( - app, - choice.name, - "Choice name", - choice.name, - ); - const trimmed = newName.trim(); - if (!trimmed || trimmed === choice.name) return; + const newName = await promptRenameChoice(app, choice.name); + if (!newName) return; - const updatedChoice = { ...choice, name: trimmed }; - choices = choices.map((entry) => - updateChoiceHelper(entry, updatedChoice), - ); - commandRegistry.updateCommand(choice, updatedChoice); - saveChoices(choices); - } catch { - // Swallow cancellation/no input. - } + const updatedChoice = { ...choice, name: newName }; + choices = choices.map((entry) => + updateChoiceHelper(entry, updatedChoice), + ); + commandRegistry.updateCommand(choice, updatedChoice); + saveChoices(choices); } async function toggleCommandForChoice(e: any) { diff --git a/src/gui/choiceRename.ts b/src/gui/choiceRename.ts new file mode 100644 index 00000000..7c34dbc3 --- /dev/null +++ b/src/gui/choiceRename.ts @@ -0,0 +1,21 @@ +import type { App } from "obsidian"; +import GenericInputPrompt from "./GenericInputPrompt/GenericInputPrompt"; + +export async function promptRenameChoice( + app: App, + currentName: string, +): Promise { + try { + const newName = await GenericInputPrompt.Prompt( + app, + currentName, + "Choice name", + currentName, + ); + const trimmed = newName.trim(); + if (!trimmed || trimmed === currentName) return null; + return trimmed; + } catch { + return null; + } +} From 0e2541619200bbac2066f0b6bd298da30fe9dd91 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Sun, 1 Feb 2026 22:38:52 +0100 Subject: [PATCH 3/4] fix: correct rename prompt args --- src/gui/choiceRename.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/choiceRename.ts b/src/gui/choiceRename.ts index 7c34dbc3..8cdb3e27 100644 --- a/src/gui/choiceRename.ts +++ b/src/gui/choiceRename.ts @@ -8,8 +8,8 @@ export async function promptRenameChoice( try { const newName = await GenericInputPrompt.Prompt( app, - currentName, "Choice name", + undefined, currentName, ); const trimmed = newName.trim(); From 69f07aaf38ec022c58ef8a91893cf6d45c5e5f76 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Sun, 1 Feb 2026 22:43:09 +0100 Subject: [PATCH 4/4] fix: simplify choice builder close --- src/gui/ChoiceBuilder/choiceBuilder.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/gui/ChoiceBuilder/choiceBuilder.ts b/src/gui/ChoiceBuilder/choiceBuilder.ts index 34240864..a854896d 100644 --- a/src/gui/ChoiceBuilder/choiceBuilder.ts +++ b/src/gui/ChoiceBuilder/choiceBuilder.ts @@ -11,19 +11,15 @@ import { promptRenameChoice } from "../choiceRename"; export abstract class ChoiceBuilder extends Modal { private resolvePromise: (input: IChoice) => void; - private rejectPromise: (reason?: unknown) => void; - private input: IChoice; public waitForClose: Promise; abstract choice: IChoice; - private didSubmit = false; protected svelteElements: SvelteComponent[] = []; protected constructor(app: App) { super(app); - this.waitForClose = new Promise((resolve, reject) => { + this.waitForClose = new Promise((resolve) => { this.resolvePromise = resolve; - this.rejectPromise = reject; }); this.containerEl.addClass("quickAddModal"); @@ -208,12 +204,9 @@ export abstract class ChoiceBuilder extends Modal { onClose() { super.onClose(); - this.resolvePromise(this.choice); this.svelteElements.forEach((el) => { if (el && el.$destroy) el.$destroy(); }); - - if (!this.didSubmit) this.rejectPromise("No answer given."); - else this.resolvePromise(this.input); + this.resolvePromise(this.choice); } }