c95f5e485a
The TipTap node definitions imported their node-name constants from schema.ts, while schema.ts imported the nodes to build the schema. This circular dependency evaluated the nodes before the constants were assigned, leaving sceneBreak/footnote/footnoteArea/characterMention with name 'undefined' — so they never registered in PubScriptSchema. As a result <hr class="scene-break"> parsed as StarterKit's horizontalRule instead of a scene break. - Move node-name constants, groups, MentionAttrs, and colorAttribute into a new constants.ts module that both schema.ts and the node files import from, breaking the cycle. - Remove the redundant separate History extension (already in StarterKit), which also silenced a TipTap 'Duplicate extension names: undoRedo' warn. - Drop unused TipTap extension and prosemirror-* deps (all access goes through @tiptap/pm).
104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
/**
|
|
* PubScript keyboard map — the shortcuts the writing surface exposes to the
|
|
* user, plus the ProseMirror keymap additions. Kept separate from the editor
|
|
* instance so the command palette and toolbar can render the same bindings.
|
|
*
|
|
* The TipTap/StarterKit keymap (bold/italic/lists/undo/redo) is registered by
|
|
* the extensions themselves; this module documents the PubScript-level
|
|
* shortcuts and provides the heading-cycle helper.
|
|
*/
|
|
import type { Editor as TipTapEditor } from "@tiptap/core";
|
|
|
|
import { SCENE_BREAK } from "./schema.js";
|
|
|
|
export interface ShortcutDef {
|
|
id: string;
|
|
label: string;
|
|
/** Human-readable binding, platform-aware. */
|
|
binding: string;
|
|
/** Execute the shortcut on the given editor. */
|
|
run: (editor: TipTapEditor) => boolean | void;
|
|
}
|
|
|
|
const MOD = typeof navigator !== "undefined" && navigator.platform.toLowerCase().includes("mac")
|
|
? "⌘"
|
|
: "Ctrl";
|
|
|
|
/**
|
|
* Cycle the paragraph at the cursor through Heading 3 → Heading 2 → Heading 1
|
|
* → Normal. Used by the writing surface's "cycle heading" shortcut.
|
|
*/
|
|
export function cycleHeading(editor: TipTapEditor): boolean {
|
|
const { state } = editor;
|
|
const { from } = state.selection;
|
|
const parent = state.doc.resolve(from).parent;
|
|
|
|
// Only cycle paragraph / heading blocks.
|
|
if (parent.type.name !== "paragraph" && parent.type.name !== "heading") {
|
|
return false;
|
|
}
|
|
|
|
const level = editor.isActive("heading") ? (editor.getAttributes("heading") as { level: number }).level : 0;
|
|
// 0 == paragraph. Cycle paragraph -> H3 -> H2 -> H1 -> paragraph.
|
|
const next = level === 0 ? 3 : level === 3 ? 0 : level - 1;
|
|
|
|
if (next === 0) {
|
|
editor.commands.setNode("paragraph");
|
|
} else {
|
|
editor.commands.toggleMark("heading", { level: next });
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The documented PubScript shortcuts. The toolbar and command palette render
|
|
* these so the UI and the actual bindings never drift.
|
|
*/
|
|
export const pubScriptShortcuts: ShortcutDef[] = [
|
|
{
|
|
id: "cycle-heading",
|
|
label: "Cycle heading",
|
|
binding: `${MOD}⇧H`,
|
|
run: (editor) => cycleHeading(editor),
|
|
},
|
|
{
|
|
id: "scene-break",
|
|
label: "Insert scene break",
|
|
binding: `${MOD}⇧⏎`,
|
|
run: (editor) => {
|
|
editor.commands.insertContent({ type: SCENE_BREAK });
|
|
return true;
|
|
},
|
|
},
|
|
{
|
|
id: "bold",
|
|
label: "Bold",
|
|
binding: `${MOD}B`,
|
|
run: (editor) => editor.commands.toggleMark("bold"),
|
|
},
|
|
{
|
|
id: "italic",
|
|
label: "Italic",
|
|
binding: `${MOD}I`,
|
|
run: (editor) => editor.commands.toggleMark("italic"),
|
|
},
|
|
{
|
|
id: "strike",
|
|
label: "Strikethrough",
|
|
binding: `${MOD}S`,
|
|
run: (editor) => editor.commands.toggleMark("strike"),
|
|
},
|
|
{
|
|
id: "undo",
|
|
label: "Undo",
|
|
binding: `${MOD}Z`,
|
|
run: (editor) => editor.commands.undo(),
|
|
},
|
|
{
|
|
id: "redo",
|
|
label: "Redo",
|
|
binding: `${MOD}⇧Z`,
|
|
run: (editor) => editor.commands.redo(),
|
|
},
|
|
];
|