File size: 3,296 Bytes
561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 9b7d64e 7814104 76fc93a 0c69852 561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 76fc93a 561e6f0 76fc93a 7814104 76fc93a 0c69852 76fc93a 9b7d64e 561e6f0 76fc93a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | // ---------------------------------------------------------------------------
// Extension factory
//
// Generates TipTap Node extensions from ComponentDef declarations.
// A single factory handles both "wrapper" (editable children) and
// "atomic" (self-closing) components based on def.kind.
// ---------------------------------------------------------------------------
import { Node, mergeAttributes } from "@tiptap/core";
import { ReactNodeViewRenderer } from "@tiptap/react";
import type { ComponentDef } from "./registry";
import { makeWrapperView } from "./WrapperView";
import { makeAtomicView } from "./AtomicView";
import { MermaidNodeView } from "./MermaidView";
import { HfUserNodeView } from "./HfUserView";
import { makeHtmlEmbedView } from "../embeds/HtmlEmbedView";
import { makeIframeEmbedView } from "../embeds/IframeEmbedView";
function buildAttrSchema(fields: ComponentDef["fields"]) {
const attrs: Record<string, { default: unknown }> = {};
for (const f of fields) {
attrs[f.name] = { default: f.default ?? null };
}
return attrs;
}
function buildDefaultAttrs(fields: ComponentDef["fields"]) {
const attrs: Record<string, unknown> = {};
for (const f of fields) {
attrs[f.name] = f.default ?? null;
}
return attrs;
}
/**
* Build a TipTap Node extension for any component definition.
*
* - "wrapper" components have editable ProseMirror content (block+).
* - "atomic" components are self-closing atoms with no inner editing.
*/
export function createComponentExtension(def: ComponentDef) {
const isWrapper = def.kind === "wrapper";
const commandName = `insert${def.tag}`;
return Node.create({
name: def.name,
group: "block",
...(isWrapper
? { content: def.content || "block+", defining: true, isolating: true }
: { atom: true, draggable: true, selectable: true }),
addAttributes() {
return buildAttrSchema(def.fields);
},
parseHTML() {
return [{ tag: `div[data-component="${def.name}"]` }];
},
renderHTML({ HTMLAttributes }) {
const base = mergeAttributes(HTMLAttributes, { "data-component": def.name });
return isWrapper ? ["div", base, 0] : ["div", base];
},
addCommands() {
return {
[commandName]:
() =>
({ commands }: { commands: any }) => {
const content: any = { type: def.name, attrs: buildDefaultAttrs(def.fields) };
if (isWrapper) content.content = [{ type: "paragraph" }];
return commands.insertContent(content);
},
} as any;
},
addNodeView() {
if (isWrapper) {
return ReactNodeViewRenderer(makeWrapperView(def));
}
let View;
if (def.name === "mermaid") View = MermaidNodeView;
else if (def.name === "hfUser") View = HfUserNodeView;
else if (def.name === "htmlEmbed") View = makeHtmlEmbedView(def);
else if (def.name === "iframe") View = makeIframeEmbedView(def);
else View = makeAtomicView(def);
return ReactNodeViewRenderer(View);
},
});
}
/** @deprecated Use createComponentExtension instead */
export const createWrapperExtension = createComponentExtension;
/** @deprecated Use createComponentExtension instead */
export const createAtomicExtension = createComponentExtension;
|