import { HocuspocusProvider } from "@hocuspocus/provider"; import { TiptapTransformer } from "@hocuspocus/transformer"; import * as Y from "yjs"; import WebSocket from "ws"; import { marked } from "marked"; import { generateJSON } from "@tiptap/html"; import { Node as PMNode } from "@tiptap/pm/model"; import { updateYFragment } from "y-prosemirror"; import { JSDOM } from "jsdom"; import { docmostExtensions, docmostSchema } from "./docmost-schema.js"; import { withPageLock } from "./page-lock.js"; import { sanitizeForYjs, findUnstorableAttr } from "./node-ops.js"; import { lexFootnoteLines } from "./footnote-lex.js"; import { summarizeChange } from "./diff.js"; /** * Build the descriptive error for an opaque Yjs encode failure ("Unexpected * content type"), shared by both encode paths (`buildYDoc` -> `toYdoc` and * `applyDocToFragment` -> `updateYFragment`) so the message wording stays in one * place. `label` names the stage that failed (diagnostic). `sanitizeForYjs` * already stripped `undefined` attrs, so a remaining failure is pinpointed via * `findUnstorableAttr`. */ function unstorableYjsError(safe, label, e) { const bad = findUnstorableAttr(safe); return new Error(`Failed to encode document to Yjs (${label}): ${e instanceof Error ? e.message : String(e)}.${bad ? ` Offending attribute: ${bad}.` : " A node/mark attribute likely holds a value Yjs cannot store (e.g. undefined)."}`); } // Setup DOM environment for Tiptap HTML parsing in Node.js const dom = new JSDOM("
"); global.window = dom.window; global.document = dom.window.document; // @ts-ignore global.Element = dom.window.Element; // @ts-ignore global.WebSocket = WebSocket; // Navigator is read-only in newer Node versions and already exists // global.navigator = dom.window.navigator; /** * Hard ceiling above which we skip callout preprocessing entirely. The linear * scanner below has no quadratic blow-up, but we still cap input defensively so * a pathological multi-megabyte payload cannot tie up the event loop; in that * case the markdown is passed through verbatim (callouts are simply not * detected) rather than risking a slow scan. */ const MAX_CALLOUT_PREPROCESS_BYTES = 4 * 1024 * 1024; // 4 MB /** Matches an opening callout fence: `:::type` (type captured, lower-cased). */ const CALLOUT_OPEN_RE = /^:::\s*(\w+)\s*$/; /** Matches a bare closing callout fence: `:::`. */ const CALLOUT_CLOSE_RE = /^:::\s*$/; /** Matches the start/end of a code fence (``` or ~~~), capturing the marker. */ const CODE_FENCE_RE = /^(\s*)(`{3,}|~{3,})/; /** * Pre-process Docmost-flavoured markdown: convert `:::type ... :::` * callout blocks (the syntax our markdown export produces) into HTML * divs that the callout extension parses. The inner content is rendered * through marked as regular markdown. * * Implemented as a single linear pass over the lines (no quadratic regex * rescan). It: * - tracks fenced code regions (```...``` and ~~~...~~~) and never treats a * `:::` line that lives inside a code fence as a callout delimiter, so a * callout body that itself contains a fenced code block with a `:::` line is * no longer corrupted; * - matches an opening `:::type` line with the next CLOSING `:::` at the SAME * nesting level, supporting NESTED callouts via a depth counter (an inner * `:::type` opens a deeper level and consumes a matching `:::`); * - emits the same `text
` * wrapper is kept inside the `
// child (the shape marked emits: `
text
${marked.parseInline(text || "")}