124f5a45a2
mcp had its OWN drifted copy of the converter (markdown-converter.ts ~900 lines, docmost-schema.ts ~1270 lines, markdown-document.ts) — older than the shared package, missing the git-sync fixes AND the #293 canon. This switches mcp's converter CORE to @docmost/prosemirror-markdown, so mcp jumps straight to the canonical format and the drift-generating second copy is gone. - markdown-converter.ts / markdown-document.ts / docmost-schema.ts become thin re-export shims of the package (convertProseMirrorToMarkdown, the docmost:meta envelope, docmostExtensions + docmostSchema=getSchema(docmostExtensions)). The mcp-only helpers clampCalloutType/sanitizeCssColor are preserved verbatim in the schema shim (the package doesn't expose them via its barrel). ~2170 lines of the drifted converter/schema bodies deleted. - collaboration.ts drops its own ~360-line marked pipeline (preprocessCallouts, bridgeTaskLists, extractFootnotes, the footnoteRef extension) and re-points to the package's markdownToProseMirror, keeping markdownToProseMirrorCanonical and all the yjs/collab write glue. footnote-lex/analyze doc comments updated (they now describe advisory legacy-syntax diagnostics, not an importer). Schema parity verified: the package schema is a strict SUPERSET of mcp's old schema — every node and attr mcp declared is present (the package only adds status/pageEmbed/transclusion*/subpages.recursive/etc.), so nothing is silently dropped on the switch. The switch actually FIXES two pre-existing mcp data-loss bugs its own tests documented: htmlEmbed and pageBreak now round-trip (were dropped by the old mcp converter). Footnotes: the package assembles inline ^[body] footnotes on import (sequential fn-N ids, identical bodies merged), so mcp's canonicalizeFootnotes is now an idempotent no-op after it (verified). Legacy reference footnotes [^id]/[^id]: are inert literal text (canon #2 no-backward-compat) — lossless, the text survives verbatim. Build hygiene: packages/mcp/build/ is now gitignored and untracked, matching the git-sync/prosemirror-markdown convention (private package, rebuilt in CI/Docker, so src and prod can never silently diverge). This also removes a dead untracked build/_vendored_editor_ext/ artifact that a broad `git add` would otherwise commit. Dependency: packages/mcp/package.json gains @docmost/prosemirror-markdown (workspace:*); pnpm-lock.yaml gets the matching link importer (mirrors git-sync). mcp tests updated deliberately to the canonical forms (highlight ==, math $…$, image <!--img-->, drawio/media discriminators, subpages/pageBreak comments, textAlign, inline ^[…] footnotes) with strict assertions; 4 structural safety-net round-trip tests added. mcp: node --test 454 passed; tsc clean. package: 657 passed. git-sync: 268 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.9 KiB
TypeScript
74 lines
2.9 KiB
TypeScript
/**
|
|
* Shared, fence-aware line lexer for legacy footnote markdown (MCP-internal).
|
|
*
|
|
* Since #293 STEP 5 the markdown -> ProseMirror IMPORT path lives in the shared
|
|
* `@docmost/prosemirror-markdown` package (inline `^[body]` footnotes), so this
|
|
* lexer no longer backs an mcp importer. It now backs ONLY the import-time
|
|
* diagnostics (`analyzeFootnotes` in footnote-analyze.ts), which still scan the
|
|
* raw markdown for legacy reference-style `[^id]:` definition lines and surface
|
|
* advisory warnings (duplicate/orphan definitions) about content that is now
|
|
* inert on import. Fence-awareness (a `[^id]:` line inside a ``` / ~~~ block is
|
|
* NOT a definition) is the property the analyzer relies on.
|
|
*
|
|
* NOTE: this is deliberately NOT shared with editor-ext's
|
|
* `extractFootnoteDefinitions` — that lives in a different package and the
|
|
* decoupling between the editor and the MCP mirror is intentional.
|
|
*/
|
|
|
|
/** A footnote DEFINITION line: `[^id]: text` (id + text captured). */
|
|
export const FOOTNOTE_DEF_RE = /^\[\^([^\]\s]+)\]:[ \t]*(.*)$/;
|
|
/** Every footnote REFERENCE `[^id]` in a line (global; id captured). */
|
|
export const FOOTNOTE_REF_RE_G = /\[\^([^\]\s]+)\]/g;
|
|
/** Opening/closing code fence marker (``` or ~~~). */
|
|
const FENCE_RE = /^(\s*)(`{3,}|~{3,})/;
|
|
|
|
export interface FootnoteLine {
|
|
/** The raw line, verbatim. */
|
|
line: string;
|
|
/**
|
|
* True for a code-fence marker line AND every line inside a fence — footnote
|
|
* syntax on such lines is inert (example text, not real markup). The importer
|
|
* keeps these in the body; the analyzer skips them.
|
|
*/
|
|
inFence: boolean;
|
|
/** The parsed definition, when this is a `[^id]: text` line OUTSIDE any fence. */
|
|
definition: { id: string; text: string } | null;
|
|
}
|
|
|
|
/** Classify every line of `markdown`, tracking fenced-code state. Pure. */
|
|
export function lexFootnoteLines(markdown: string): FootnoteLine[] {
|
|
const out: FootnoteLine[] = [];
|
|
let fence: string | null = null;
|
|
for (const line of markdown.split("\n")) {
|
|
const fenceMatch = FENCE_RE.exec(line);
|
|
if (fenceMatch) {
|
|
const marker = fenceMatch[2][0];
|
|
if (fence === null) fence = marker; // opening fence
|
|
else if (marker === fence) fence = null; // matching closing fence
|
|
out.push({ line, inFence: true, definition: null });
|
|
continue;
|
|
}
|
|
if (fence !== null) {
|
|
out.push({ line, inFence: true, definition: null });
|
|
continue;
|
|
}
|
|
const m = FOOTNOTE_DEF_RE.exec(line);
|
|
out.push({
|
|
line,
|
|
inFence: false,
|
|
definition: m ? { id: m[1], text: m[2] } : null,
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Scan a line for every `[^id]` reference, invoking `onRef(id)` for each. */
|
|
export function forEachFootnoteReference(
|
|
line: string,
|
|
onRef: (id: string) => void,
|
|
): void {
|
|
FOOTNOTE_REF_RE_G.lastIndex = 0;
|
|
let m: RegExpExecArray | null;
|
|
while ((m = FOOTNOTE_REF_RE_G.exec(line)) !== null) onRef(m[1]);
|
|
}
|