From 04b90233158d841e15d16f0e5fc1aaf9d6263a4d Mon Sep 17 00:00:00 2001 From: 01zulfi <85733202+01zulfi@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:02:13 +0500 Subject: [PATCH] editor: add keybinding to collapse/expand headings & callouts Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> --- docs/help/contents/keyboard-shortcuts.md | 2 +- packages/common/src/utils/keybindings.ts | 4 +- .../__snapshots__/callout.test.ts.snap | 5 ++ .../callout/__tests__/callout.test.ts | 60 +++++++++++++++++++ .../editor/src/extensions/callout/callout.ts | 40 ++++++++++--- .../__snapshots__/heading.test.ts.snap | 4 ++ .../heading/__tests__/heading.test.ts | 31 ++++++++++ .../editor/src/extensions/heading/heading.ts | 31 ++++++++++ .../outline-list-item.test.ts.snap | 4 ++ .../__tests__/outline-list-item.test.ts | 35 +++++++++++ .../outline-list-item/outline-list-item.ts | 2 +- 11 files changed, 207 insertions(+), 11 deletions(-) create mode 100644 packages/editor/src/extensions/callout/__tests__/__snapshots__/callout.test.ts.snap create mode 100644 packages/editor/src/extensions/callout/__tests__/callout.test.ts diff --git a/docs/help/contents/keyboard-shortcuts.md b/docs/help/contents/keyboard-shortcuts.md index 9a5a85818..35759807e 100644 --- a/docs/help/contents/keyboard-shortcuts.md +++ b/docs/help/contents/keyboard-shortcuts.md @@ -65,7 +65,7 @@ The following keyboard shortcuts will help you navigate Notesnook faster. | Insert math block | Ctrl ⇧ M | Ctrl ⇧ M | ⌘ ⇧ M | | Toggle ordered list | Ctrl ⇧ 7 | Ctrl ⇧ 7 | ⌘ ⇧ 7 | | Toggle outline list | Ctrl ⇧ O | Ctrl ⇧ O | ⌘ ⇧ O | -| Toggle outline list expand | Ctrl Space | Ctrl Space | ⌘ Space | +| Toggle callout/heading/outline list expand | Ctrl Space | Ctrl Space | ⌘ Space | | Open search | Ctrl F | Ctrl F | ⌘ F | | Open search and replace | Ctrl Alt F | Ctrl Alt F | ⌘ ⌥ F | | Toggle strike | Ctrl ⇧ S | Ctrl ⇧ S | ⌘ ⇧ S | diff --git a/packages/common/src/utils/keybindings.ts b/packages/common/src/utils/keybindings.ts index 73c1296a7..2fa6b1508 100644 --- a/packages/common/src/utils/keybindings.ts +++ b/packages/common/src/utils/keybindings.ts @@ -323,9 +323,9 @@ export const tiptapKeys = { category: "Editor", type: "tiptap" }, - toggleOutlineListExpand: { + toggleNodeExpand: { keys: "Mod-Space", - description: "Toggle outline list expand", + description: "Toggle callout/heading/outline list expand", category: "Editor", type: "tiptap" }, diff --git a/packages/editor/src/extensions/callout/__tests__/__snapshots__/callout.test.ts.snap b/packages/editor/src/extensions/callout/__tests__/__snapshots__/callout.test.ts.snap new file mode 100644 index 000000000..8b5989479 --- /dev/null +++ b/packages/editor/src/extensions/callout/__tests__/__snapshots__/callout.test.ts.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ctrl+space should collapse/expand callout when cursor is in first heading 1`] = `"
This is the callout content.
This is the callout content.
exports[`converting collapsed heading to lower level should unhide higher level headings 1`] = `"
Paragraph under level 2
"`; +exports[`ctrl+space should collapse/expand heading 1`] = `"Paragraph
"`; + +exports[`ctrl+space should collapse/expand heading 2`] = `"Paragraph
"`; + exports[`replacing collapsed heading with another heading level should not unhide content 1`] = `"Hidden paragraph
"`; exports[`replacing collapsed heading with another node (blockquote) should unhide content 1`] = `"A collpased heading
Hidden paragraph
"`; diff --git a/packages/editor/src/extensions/heading/__tests__/heading.test.ts b/packages/editor/src/extensions/heading/__tests__/heading.test.ts index 420e01ffc..6b32f2279 100644 --- a/packages/editor/src/extensions/heading/__tests__/heading.test.ts +++ b/packages/editor/src/extensions/heading/__tests__/heading.test.ts @@ -144,3 +144,34 @@ test("converting collapsed heading to lower level should unhide higher level hea expect(editor.getHTML()).toMatchSnapshot(); }); + +test("ctrl+space should collapse/expand heading", async () => { + const el = h("div", [h("h1", ["Heading"]), h("p", ["Paragraph"])]); + const { editor } = createEditor({ + extensions: { + heading: Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }) + }, + initialContent: el.outerHTML + }); + + editor.commands.setTextSelection(3); + const collapseEvent = new KeyboardEvent("keydown", { + key: " ", + code: "Space", + ctrlKey: true, + bubbles: true + }); + editor.view.dom.dispatchEvent(collapseEvent); + + expect(editor.getHTML()).toMatchSnapshot(); + + const expandEvent = new KeyboardEvent("keydown", { + key: " ", + code: "Space", + ctrlKey: true, + bubbles: true + }); + editor.view.dom.dispatchEvent(expandEvent); + + expect(editor.getHTML()).toMatchSnapshot(); +}); diff --git a/packages/editor/src/extensions/heading/heading.ts b/packages/editor/src/extensions/heading/heading.ts index d5c2720fa..e9b341afc 100644 --- a/packages/editor/src/extensions/heading/heading.ts +++ b/packages/editor/src/extensions/heading/heading.ts @@ -126,6 +126,37 @@ export const Heading = TiptapHeading.extend({ }), {} ), + [tiptapKeys.toggleNodeExpand.keys]: ({ editor }) => { + const { selection } = editor.state; + const { $from, empty } = selection; + + if (!empty) return false; + if ($from.parent.type.name !== this.name) return false; + + const headingPos = $from.before(); + const headingNode = editor.state.doc.nodeAt(headingPos); + if (!headingNode || headingNode.type.name !== this.name) return false; + + // the first callout heading's collapsibility is handled by callout itself + const callout = findParentNodeClosestToPos( + $from, + (node) => node.type.name === Callout.name + ); + if (callout?.node.firstChild === headingNode) return false; + + const isCollapsed = headingNode.attrs.collapsed; + + return editor.commands.command(({ tr }) => { + tr.setNodeAttribute(headingPos, "collapsed", !isCollapsed); + toggleNodesUnderPos( + tr, + headingPos, + headingNode.attrs.level, + !isCollapsed + ); + return true; + }); + }, Enter: ({ editor }) => { const { state, commands } = editor; const { $from } = state.selection; diff --git a/packages/editor/src/extensions/outline-list-item/__tests__/__snapshots__/outline-list-item.test.ts.snap b/packages/editor/src/extensions/outline-list-item/__tests__/__snapshots__/outline-list-item.test.ts.snap index 3aebd38b5..d81bf5cfb 100644 --- a/packages/editor/src/extensions/outline-list-item/__tests__/__snapshots__/outline-list-item.test.ts.snap +++ b/packages/editor/src/extensions/outline-list-item/__tests__/__snapshots__/outline-list-item.test.ts.snap @@ -129,4 +129,8 @@ exports[`outline list item > code block in outline list item 1`] = ` } `; +exports[`outline list item > ctrl+space should collapse/expand outline list item 1`] = `"item
sub item
item
sub item
item 1
