Something went wrong. Try again.
a tool for shared writing and social publishing
Something went wrong. Try again.
7.8 kB · 315 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316import { Schema, Node, MarkSpec, NodeSpec } from "prosemirror-model";import { marks } from "prosemirror-schema-basic";import { theme } from "tailwind.config";import { classifyAtUri } from "src/utils/mentionUtils";
// Bump this whenever the schema changes in a way old clients can't represent:// adding a node or mark, or adding a *required* attr to an existing one// (prefer attrs with a `default`, which old schemas tolerate). Clients that// encounter content from a newer schema version stop editing instead of// destroying what they don't understand — see ./schemaVersion.export const SCHEMA_VERSION = 1;
let baseSchema = { marks: { strong: marks.strong, em: marks.em, code: { parseDOM: [ { tag: "code", }, ],
toDOM() { return ["code", { class: "inline-code" }, 0]; }, } as MarkSpec, underline: { parseDOM: [ { tag: "u" }, { style: "text-decoration=underline", }, { style: "text-decoration=none", clearMark: (m) => m.type.name == "underline", }, ], toDOM() { return ["u", { class: "underline" }, 0]; }, } as MarkSpec, strikethrough: { parseDOM: [ { tag: "s" }, { tag: "del" }, { style: `text-decoration=line-through`, }, { style: "text-decoration=none", clearMark: (m) => m.type.name == "strikethrough", }, ], toDOM() { return [ "s", { style: `text-decoration-color: ${theme.colors.tertiary};`, }, 0, ]; }, } as MarkSpec, highlight: { attrs: { color: { default: "1", }, }, parseDOM: [ { tag: "span.highlight", getAttrs(dom: HTMLElement) { return { color: dom.getAttribute("data-color"), }; }, }, ], toDOM(node) { let { color } = node.attrs; return [ "span", { class: "highlight", "data-color": color, style: `background-color: ${color === "1" ? theme.colors["highlight-1"] : color === "2" ? theme.colors["highlight-2"] : theme.colors["highlight-3"]}`, }, 0, ]; }, } as MarkSpec, comment: { attrs: { commentID: {}, }, inclusive: false, parseDOM: [ { tag: "span.comment-anchor", getAttrs(dom: HTMLElement) { return { commentID: dom.getAttribute("data-comment-id"), }; }, }, ], toDOM(node) { return [ "span", { class: "comment-anchor", "data-comment-id": node.attrs.commentID, }, 0, ]; }, } as MarkSpec, link: { attrs: { href: {}, }, inclusive: false, parseDOM: [ { tag: "a[href]", getAttrs(dom: HTMLElement) { return { href: dom.getAttribute("href"), }; }, }, ], toDOM(node) { let { href } = node.attrs; return [ "a", { href, target: "_blank", referrerpolicy: "no-referrer", style: "cursor: text", }, 0, ]; }, } as MarkSpec, }, nodes: { doc: { content: "block" }, paragraph: { content: "inline*", group: "block", parseDOM: [{ tag: "p" }], toDOM: () => ["p", 0] as const, }, text: { group: "inline", }, hard_break: { group: "inline", inline: true, selectable: false, parseDOM: [{ tag: "br" }], toDOM: () => ["br"] as const, }, atMention: { attrs: { atURI: {}, text: { default: "" }, href: { default: undefined }, icon: { default: undefined }, }, group: "inline", inline: true, atom: true, selectable: true, draggable: true, parseDOM: [ { tag: "span.atMention", getAttrs(dom: HTMLElement) { return { atURI: dom.getAttribute("data-at-uri"), text: dom.textContent || "", href: dom.getAttribute("data-href") || undefined, icon: dom.getAttribute("data-icon") || undefined, }; }, }, ], toDOM(node) { // NOTE: This rendering should match the AtMentionLink component in // components/AtMentionLink.tsx. If you update one, update the other. let className = "atMention mention"; const { isPublication, isDocument } = classifyAtUri(node.attrs.atURI); if (isPublication) className += " font-bold"; if (isDocument) className += " italic";
const attrs: Record<string, string> = { class: className, "data-at-uri": node.attrs.atURI, }; if (node.attrs.href) { attrs["data-href"] = node.attrs.href; } if (node.attrs.icon) { attrs["data-icon"] = node.attrs.icon; }
// Show icon for publications/documents or service results const iconSrc = isPublication || isDocument ? `/api/pub_icon?at_uri=${encodeURIComponent(node.attrs.atURI)}` : node.attrs.icon ?? null;
if (iconSrc) { return [ "span", attrs, [ "img", { src: iconSrc, class: "inline-block w-4 h-4 rounded-full mt-[3px] mr-1 align-text-top", alt: "", width: "16", height: "16", loading: "lazy", }, ], node.attrs.text, ]; }
return ["span", attrs, node.attrs.text]; }, } as NodeSpec, footnote: { attrs: { footnoteEntityID: {} }, group: "inline", inline: true, atom: true, selectable: false, draggable: false, parseDOM: [ { tag: "span.footnote-ref", getAttrs(dom: HTMLElement) { return { footnoteEntityID: dom.getAttribute("data-footnote-id"), }; }, }, { tag: "sup.footnote-ref", getAttrs(dom: HTMLElement) { return { footnoteEntityID: dom.getAttribute("data-footnote-id"), }; }, }, ], toDOM(node) { return [ "span", { class: "footnote-ref", "data-footnote-id": node.attrs.footnoteEntityID, }, ]; }, } as NodeSpec, didMention: { attrs: { did: {}, text: { default: "" }, }, group: "inline", inline: true, atom: true, selectable: true, draggable: true, parseDOM: [ { tag: "span.didMention", getAttrs(dom: HTMLElement) { return { did: dom.getAttribute("data-did"), text: dom.textContent || "", }; }, }, ], toDOM(node) { return [ "span", { class: "didMention mention", "data-did": node.attrs.did, }, node.attrs.text, ]; }, } as NodeSpec, },};export const schema = new Schema(baseSchema);
export const multiBlockSchema = new Schema({ marks: baseSchema.marks, nodes: { ...baseSchema.nodes, doc: { content: "block+" } },});