Add WikilinkNode and DatabaseView schema-only nodes to @docmost/editor-ext and register them in the Hocuspocus server tiptapExtensions list. Without the shared schema, jsonToNode on the server hit a RangeError for those node types and stripUnknownNodes dropped them on every collab save, so wikilinks disappeared on page reload and database-view embeds lost their config and rendered as empty placeholders.
99 lines
2.6 KiB
TypeScript
99 lines
2.6 KiB
TypeScript
import { Node } from "@tiptap/core";
|
|
|
|
export interface WikilinkAttrs {
|
|
pageId: string | null;
|
|
slugId?: string | null;
|
|
spaceSlug?: string | null;
|
|
title: string;
|
|
alias: string | null;
|
|
}
|
|
|
|
declare module "@tiptap/core" {
|
|
interface Commands<ReturnType> {
|
|
wikilink: {
|
|
insertWikilink: (attrs: WikilinkAttrs) => ReturnType;
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shared Wikilink node (schema only, no NodeView).
|
|
*
|
|
* Lives in @docmost/editor-ext so both the Hocuspocus server and the React
|
|
* client share the exact same schema. The client extends this node to add
|
|
* the React NodeView, input rule, and suggestion plugin.
|
|
*
|
|
* Without registering this node on the server, Hocuspocus' jsonToNode strips
|
|
* wikilink nodes on save (unknown node type).
|
|
*/
|
|
export const WikilinkNode = Node.create({
|
|
name: "wikilink",
|
|
group: "inline",
|
|
inline: true,
|
|
atom: true,
|
|
selectable: true,
|
|
|
|
addAttributes() {
|
|
return {
|
|
pageId: {
|
|
default: null,
|
|
parseHTML: (el: HTMLElement) => el.getAttribute("data-page-id"),
|
|
renderHTML: (attrs) =>
|
|
attrs.pageId ? { "data-page-id": attrs.pageId } : {},
|
|
},
|
|
slugId: {
|
|
default: null,
|
|
parseHTML: (el: HTMLElement) => el.getAttribute("data-slug-id"),
|
|
renderHTML: (attrs) =>
|
|
attrs.slugId ? { "data-slug-id": attrs.slugId } : {},
|
|
},
|
|
spaceSlug: {
|
|
default: null,
|
|
parseHTML: (el: HTMLElement) => el.getAttribute("data-space-slug"),
|
|
renderHTML: (attrs) =>
|
|
attrs.spaceSlug ? { "data-space-slug": attrs.spaceSlug } : {},
|
|
},
|
|
title: {
|
|
default: "",
|
|
parseHTML: (el: HTMLElement) =>
|
|
el.getAttribute("data-title") ?? el.textContent ?? "",
|
|
renderHTML: (attrs) => ({ "data-title": attrs.title }),
|
|
},
|
|
alias: {
|
|
default: null,
|
|
parseHTML: (el: HTMLElement) => el.getAttribute("data-alias"),
|
|
renderHTML: (attrs) =>
|
|
attrs.alias ? { "data-alias": attrs.alias } : {},
|
|
},
|
|
};
|
|
},
|
|
|
|
parseHTML() {
|
|
return [{ tag: "span[data-wikilink]" }];
|
|
},
|
|
|
|
renderHTML({ HTMLAttributes, node }) {
|
|
const display = node.attrs.alias ?? node.attrs.title ?? "?";
|
|
const isBroken = !node.attrs.pageId;
|
|
return [
|
|
"span",
|
|
{
|
|
"data-wikilink": "true",
|
|
...HTMLAttributes,
|
|
class: isBroken ? "wikilink wikilink--broken" : "wikilink",
|
|
},
|
|
`[[${display}]]`,
|
|
];
|
|
},
|
|
|
|
addCommands() {
|
|
return {
|
|
insertWikilink:
|
|
(attrs: WikilinkAttrs) =>
|
|
({ commands }) =>
|
|
commands.insertContent({ type: this.name, attrs }),
|
|
};
|
|
},
|
|
});
|
|
|
|
export default WikilinkNode;
|