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).
69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
import { Node } from "@tiptap/core";
|
|
import { ReactNodeViewRenderer, type ReactNodeViewProps } from "@tiptap/react";
|
|
import { SCENE_BREAK } from "../constants.js";
|
|
|
|
/**
|
|
* Scene break node view — renders a centered `• • •` divider. Clicking the
|
|
* divider re-renders it; the underlying node is an empty block whose HTML
|
|
* representation is `<hr class="scene-break">`.
|
|
*/
|
|
export const SceneBreakNode = Node.create({
|
|
name: SCENE_BREAK,
|
|
group: "block",
|
|
selectable: true,
|
|
draggable: true,
|
|
atom: true,
|
|
|
|
addAttributes() {
|
|
return {
|
|
break: {
|
|
default: "***",
|
|
parseHTML: (element) => element.getAttribute("data-break") ?? "***",
|
|
renderHTML: (attributes) => ({ "data-break": attributes.break ?? "***" }),
|
|
},
|
|
};
|
|
},
|
|
|
|
parseHTML() {
|
|
// Higher priority than StarterKit's horizontalRule (`tag: "hr"`) so that
|
|
// `<hr class="scene-break">` resolves to a scene break, not a rule.
|
|
return [{ tag: "hr.scene-break", priority: 2000 }];
|
|
},
|
|
|
|
renderHTML({ node }) {
|
|
return ["hr", { class: "scene-break", "data-break": node.attrs.break ?? "***" }];
|
|
},
|
|
|
|
addNodeView() {
|
|
return ReactNodeViewRenderer(SceneBreakComponent);
|
|
},
|
|
});
|
|
|
|
/**
|
|
* The React node view. TipTap's `ReactNodeViewRenderer` supplies the
|
|
* contentDOM/selection handling; we just render the divider.
|
|
*/
|
|
export function SceneBreakComponent({ editor, node, selected }: ReactNodeViewProps) {
|
|
return (
|
|
<div
|
|
className="ps-scene-break"
|
|
data-selected={selected ? "" : undefined}
|
|
data-break={node.attrs.break ?? "***"}
|
|
>
|
|
<button
|
|
type="button"
|
|
className="ps-scene-break-inner"
|
|
aria-label="Scene break"
|
|
tabIndex={-1}
|
|
onDoubleClick={() => {
|
|
editor.commands.updateAttributes(SCENE_BREAK, { break: "***" });
|
|
}}
|
|
>
|
|
{editor.isEditable ? "• • •" : ""}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default SceneBreakNode;
|