From e8f93396e4e7c90a1b5a758a19ec7f644a03bc76 Mon Sep 17 00:00:00 2001 From: bendsp Date: Sat, 9 May 2026 16:27:14 +0200 Subject: [PATCH 1/2] add: task check toggle command --- src/renderer/commands/commandSystem.ts | 15 ++++++++ src/renderer/components/EditorContextMenu.tsx | 6 +++ src/renderer/editor/codemirror/commands.ts | 37 +++++++++++++++++++ tests/controllerShortcuts.test.ts | 28 ++++++++++++++ 4 files changed, 86 insertions(+) diff --git a/src/renderer/commands/commandSystem.ts b/src/renderer/commands/commandSystem.ts index cbdb55f..1468b81 100644 --- a/src/renderer/commands/commandSystem.ts +++ b/src/renderer/commands/commandSystem.ts @@ -6,6 +6,7 @@ import { createWrapSelectionOrWordCommand, insertHorizontalRuleCommand, toggleBlockquoteCommand, + toggleTaskCheckCommand, toggleFencedCodeBlockCommand, toggleOrderedListCommand, toggleTaskListCommand, @@ -34,6 +35,7 @@ export type CommandId = | "insert.unorderedList" | "insert.orderedList" | "insert.taskList" + | "insert.toggleTaskCheck" | "insert.blockquote" | "insert.codeBlock" | "theme.set" @@ -58,6 +60,7 @@ export type CommandArgs = { "insert.unorderedList": void; "insert.orderedList": void; "insert.taskList": void; + "insert.toggleTaskCheck": void; "insert.blockquote": void; "insert.codeBlock": void; "theme.set": { theme: ThemeName }; @@ -179,6 +182,7 @@ const editorCommands = { unorderedList: toggleUnorderedListCommand, orderedList: toggleOrderedListCommand, taskList: toggleTaskListCommand, + taskCheck: toggleTaskCheckCommand, blockquote: toggleBlockquoteCommand, codeBlock: toggleFencedCodeBlockCommand, } as const; @@ -365,6 +369,17 @@ export const createCommandRegistry = (): CommandRegistry => { return view ? editorCommands.taskList(view) : false; }, }, + { + id: "insert.toggleTaskCheck", + title: "Toggle task check", + category: "Insert", + description: "Toggle checked state for task checklist items.", + requiresEditor: true, + run: (ctx) => { + const view = ctx.getEditorView(); + return view ? editorCommands.taskCheck(view) : false; + }, + }, { id: "insert.blockquote", title: "Quote", diff --git a/src/renderer/components/EditorContextMenu.tsx b/src/renderer/components/EditorContextMenu.tsx index 33f05ef..2f0bca0 100644 --- a/src/renderer/components/EditorContextMenu.tsx +++ b/src/renderer/components/EditorContextMenu.tsx @@ -196,6 +196,12 @@ export function EditorContextMenu({ Task list {shortcuts.taskList} + runCommand("insert.toggleTaskCheck")} + > + Toggle task check + runCommand("insert.blockquote")} diff --git a/src/renderer/editor/codemirror/commands.ts b/src/renderer/editor/codemirror/commands.ts index cc37066..9d9a04e 100644 --- a/src/renderer/editor/codemirror/commands.ts +++ b/src/renderer/editor/codemirror/commands.ts @@ -472,6 +472,43 @@ export const toggleTaskListCommand = ( prefixForLine: () => "- [ ] ", }); +export const toggleTaskCheckCommand = ( + view: import("@codemirror/view").EditorView +): boolean => { + const { start, end } = getSelectedLineNumbers(view); + const taskLines = []; + + for (let lineNumber = start; lineNumber <= end; lineNumber += 1) { + const line = view.state.doc.line(lineNumber); + const match = line.text.match(/^(\s*[-*+]\s+\[)([ xX])(\]\s+)/); + if (match) { + taskLines.push({ line, match }); + } + } + + if (taskLines.length === 0) { + return false; + } + + const shouldUncheck = taskLines.every(({ match }) => + /[xX]/.test(match[2]) + ); + const changes = taskLines.map(({ line, match }) => { + const markerOffset = match[1].length; + return { + from: line.from + markerOffset, + to: line.from + markerOffset + 1, + insert: shouldUncheck ? " " : "x", + }; + }); + + view.dispatch({ + changes, + scrollIntoView: true, + }); + return true; +}; + export const toggleBlockquoteCommand = ( view: import("@codemirror/view").EditorView ): boolean => diff --git a/tests/controllerShortcuts.test.ts b/tests/controllerShortcuts.test.ts index 46ff10c..2ef26e6 100644 --- a/tests/controllerShortcuts.test.ts +++ b/tests/controllerShortcuts.test.ts @@ -4,6 +4,7 @@ import { toggleBlockquoteCommand, toggleFencedCodeBlockCommand, toggleOrderedListCommand, + toggleTaskCheckCommand, toggleTaskListCommand, toggleUnorderedListCommand, } from "../src/renderer/editor/codemirror/commands"; @@ -174,6 +175,33 @@ runTest("task list command toggles checklist markers", () => { assert.equal(view.text, "one\ntwo"); }); +runTest("task check command toggles unchecked tasks on", () => { + const view = new FakeView("- [ ] one\n- [x] two", { from: 0, to: 19 }); + + const handled = toggleTaskCheckCommand(view as unknown as EditorView); + + assert.equal(handled, true); + assert.equal(view.text, "- [x] one\n- [x] two"); +}); + +runTest("task check command toggles checked tasks off", () => { + const view = new FakeView("- [x] one\n- [X] two", { from: 0, to: 19 }); + + const handled = toggleTaskCheckCommand(view as unknown as EditorView); + + assert.equal(handled, true); + assert.equal(view.text, "- [ ] one\n- [ ] two"); +}); + +runTest("task check command falls through outside task lines", () => { + const view = new FakeView("- one", { from: 0, to: 5 }); + + const handled = toggleTaskCheckCommand(view as unknown as EditorView); + + assert.equal(handled, false); + assert.equal(view.text, "- one"); +}); + runTest("blockquote command toggles selected lines", () => { const view = new FakeView("one\ntwo", { from: 0, to: 7 }); From d6b3b67389d93ae32a773b5efc36589bb3675e4d Mon Sep 17 00:00:00 2001 From: bendsp Date: Sat, 9 May 2026 16:37:18 +0200 Subject: [PATCH 2/2] fix: align task check command id --- src/renderer/commands/commandSystem.ts | 6 +++--- src/renderer/components/EditorContextMenu.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/commands/commandSystem.ts b/src/renderer/commands/commandSystem.ts index 1468b81..1977fe8 100644 --- a/src/renderer/commands/commandSystem.ts +++ b/src/renderer/commands/commandSystem.ts @@ -35,7 +35,7 @@ export type CommandId = | "insert.unorderedList" | "insert.orderedList" | "insert.taskList" - | "insert.toggleTaskCheck" + | "insert.taskCheck" | "insert.blockquote" | "insert.codeBlock" | "theme.set" @@ -60,7 +60,7 @@ export type CommandArgs = { "insert.unorderedList": void; "insert.orderedList": void; "insert.taskList": void; - "insert.toggleTaskCheck": void; + "insert.taskCheck": void; "insert.blockquote": void; "insert.codeBlock": void; "theme.set": { theme: ThemeName }; @@ -370,7 +370,7 @@ export const createCommandRegistry = (): CommandRegistry => { }, }, { - id: "insert.toggleTaskCheck", + id: "insert.taskCheck", title: "Toggle task check", category: "Insert", description: "Toggle checked state for task checklist items.", diff --git a/src/renderer/components/EditorContextMenu.tsx b/src/renderer/components/EditorContextMenu.tsx index 2f0bca0..d43d120 100644 --- a/src/renderer/components/EditorContextMenu.tsx +++ b/src/renderer/components/EditorContextMenu.tsx @@ -198,7 +198,7 @@ export function EditorContextMenu({ runCommand("insert.toggleTaskCheck")} + onSelect={() => runCommand("insert.taskCheck")} > Toggle task check