import { Fragment, Node } from 'prosemirror-model'; /** * Degrades plugin-owned nodes for office exports (issue #79, ADR 0008/0009): * pandoc receives GFM, which knows neither the `dorfteich-plugin` fence nor * the section fenced div — left alone they would surface as literal fence * text in the .docx. So before serializing the export markdown: * * - a `plugin_block` becomes a paragraph with its fallback text (the caller * resolves it from the manifest snapshot; plugins are a server-side * registry this package knows nothing about); * - a `section` becomes a blockquote of its content — the "plain quoted * block" rendition of a styled container in a format with no CSS. */ export function replacePluginNodesForExport( doc: Node, fallbackTextFor: (pluginId: string, blockType: string) => string, ): Node { const schema = doc.type.schema; function mapNode(node: Node): Node { if (node.type.name === 'plugin_block') { const text = fallbackTextFor( node.attrs.pluginId as string, node.attrs.blockType as string, ).trim(); return schema.node('paragraph', null, text === '' ? [] : [schema.text(text)]); } const children: Node[] = []; node.forEach((child) => children.push(mapNode(child))); if (node.type.name === 'section') { return schema.node('blockquote', null, Fragment.from(children)); } return node.isLeaf ? node : node.copy(Fragment.from(children)); } return mapNode(doc); } /** The plugin ids referenced by `plugin_block` nodes in `doc`, for resolving * their fallbacks in one batch before {@link replacePluginNodesForExport}. */ export function collectPluginBlockIds(doc: Node): string[] { const ids = new Set(); doc.descendants((node) => { if (node.type.name === 'plugin_block') ids.add(node.attrs.pluginId as string); return true; }); return [...ids]; }