Add strikethrough shortcut, fenced code toggle, and CodeMirror-aware undo/redo
- Bind Ctrl+Shift+S to strikethrough - Smart code toggle: fenced block for multi-line selections, inline otherwise - Link editing: selecting an existing link now selects its URL for editing - Route undo/redo shortcuts through IPC so CodeMirror history is used when the editor has focus instead of Electron's native menu-role undo/redo - Add Indent/Outdent toolbar buttons
This commit is contained in:
parent
c09f4adc30
commit
45990bbd62
9 changed files with 185 additions and 12 deletions
|
|
@ -28,6 +28,7 @@ const {
|
|||
toggleItalic,
|
||||
toggleStrikethrough,
|
||||
toggleInlineCode,
|
||||
toggleCode,
|
||||
toggleLink,
|
||||
setHeading,
|
||||
toggleBulletList,
|
||||
|
|
@ -55,7 +56,12 @@ function target(doc, anchor, head = anchor) {
|
|||
return true;
|
||||
},
|
||||
};
|
||||
return { t, text: () => state.doc.toString(), sel: () => state.selection.main };
|
||||
return {
|
||||
t,
|
||||
text: () => state.doc.toString(),
|
||||
sel: () => state.selection.main,
|
||||
select: (a, h = a) => (state = state.update({ selection: { anchor: a, head: h } }).state),
|
||||
};
|
||||
}
|
||||
|
||||
let failures = 0;
|
||||
|
|
@ -147,6 +153,38 @@ function eq(actual, expected, msg) {
|
|||
eq(text(), "a[](url)b", "link on empty cursor inserts empty label");
|
||||
eq(sel().from, 2, "link cursor inside label brackets");
|
||||
}
|
||||
{
|
||||
const { t, text, sel } = target("see [docs](old-url) here", 4, 19);
|
||||
toggleLink(t);
|
||||
eq(text(), "see [docs](old-url) here", "link edit does not change text");
|
||||
eq(sel().from, 11, "link edit selects url (from)");
|
||||
eq(sel().to, 18, "link edit selects url (to)");
|
||||
}
|
||||
|
||||
// ---- code: inline vs fenced ----
|
||||
{
|
||||
const { t, text } = target("var x = 1;", 0, 9);
|
||||
toggleCode(t);
|
||||
eq(text(), "`var x = 1`;", "code on single line uses inline markers");
|
||||
}
|
||||
{
|
||||
const { t, text, select } = target("line one\nline two", 0, 16);
|
||||
toggleCode(t);
|
||||
eq(text(), "```\nline one\nline two\n```", "code on multiple lines uses a fenced block");
|
||||
select(0, 25);
|
||||
toggleCode(t);
|
||||
eq(text(), "line one\nline two", "fenced code toggles back off");
|
||||
}
|
||||
{
|
||||
const { t, text } = target("before\n```\ncode a\ncode b\n```\nafter", 7, 24);
|
||||
toggleCode(t);
|
||||
eq(text(), "before\ncode a\ncode b\nafter", "fenced code unwraps when selection starts at the fence");
|
||||
}
|
||||
{
|
||||
const { t, text } = target("abc", 1);
|
||||
toggleCode(t);
|
||||
eq(text(), "a``bc", "code on empty cursor inserts inline markers");
|
||||
}
|
||||
|
||||
// ---- headings ----
|
||||
{
|
||||
|
|
|
|||
|
|
@ -141,12 +141,50 @@ const BOOK_DIR = __BOOKDIR_VALUE__;
|
|||
await new Promise((r) => setTimeout(r, 50));
|
||||
out.redoOk = cm.state.doc.toString().startsWith("## ");
|
||||
|
||||
cm.dispatch({ selection: { anchor: 15, head: 20 } }); // "World"
|
||||
cm.contentDOM.dispatchEvent(
|
||||
new KeyboardEvent("keydown", {
|
||||
key: "s",
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
})
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
out.strikeShortcutOk = cm.state.doc.toString().includes("~~World~~");
|
||||
|
||||
cm.dispatch({
|
||||
changes: { from: 0, to: 0, insert: "line one\nline two\n" },
|
||||
selection: { anchor: 0, head: 16 },
|
||||
});
|
||||
document.querySelector('[data-cmd="code"]').click();
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
out.fencedCodeOk = cm.state.doc.toString().startsWith("```\nline one\nline two\n```");
|
||||
|
||||
await new Promise((r) => setTimeout(r, 1200)); // wait for autosave debounce
|
||||
out.formattingAutosaved =
|
||||
(await window.folio.getChapterContent(typedId)).includes("**Hello**");
|
||||
}
|
||||
|
||||
out.all = true;
|
||||
const okFlags = [
|
||||
"dndMoved",
|
||||
"kbMoved",
|
||||
"modalShown",
|
||||
"uiCreated",
|
||||
"hasEditor",
|
||||
"editorAutosaved",
|
||||
"toolbarBoldOk",
|
||||
"focusAfterBold",
|
||||
"shortcutBoldOk",
|
||||
"headingOk",
|
||||
"undoOk",
|
||||
"redoOk",
|
||||
"strikeShortcutOk",
|
||||
"fencedCodeOk",
|
||||
"formattingAutosaved",
|
||||
];
|
||||
out.all = okFlags.every((f) => out[f] === true);
|
||||
} catch (e) {
|
||||
out.error = String((e && e.message) || e);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue