@moraya/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +344 -0
- package/LICENSE +85 -0
- package/README.md +82 -0
- package/dist/adapters/browser-media-resolver.d.ts +21 -0
- package/dist/adapters/browser-media-resolver.js +24 -0
- package/dist/adapters/browser-media-resolver.js.map +1 -0
- package/dist/commands.d.ts +35 -0
- package/dist/commands.js +976 -0
- package/dist/commands.js.map +1 -0
- package/dist/doc-cache.d.ts +29 -0
- package/dist/doc-cache.js +50 -0
- package/dist/doc-cache.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +4534 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +46 -0
- package/dist/markdown.js +1553 -0
- package/dist/markdown.js.map +1 -0
- package/dist/plugins/code-block-view.d.ts +52 -0
- package/dist/plugins/code-block-view.js +686 -0
- package/dist/plugins/code-block-view.js.map +1 -0
- package/dist/plugins/cursor-syntax.d.ts +27 -0
- package/dist/plugins/cursor-syntax.js +122 -0
- package/dist/plugins/cursor-syntax.js.map +1 -0
- package/dist/plugins/definition-list.d.ts +23 -0
- package/dist/plugins/definition-list.js +12 -0
- package/dist/plugins/definition-list.js.map +1 -0
- package/dist/plugins/editor-props-plugin.d.ts +36 -0
- package/dist/plugins/editor-props-plugin.js +1963 -0
- package/dist/plugins/editor-props-plugin.js.map +1 -0
- package/dist/plugins/emoji.d.ts +21 -0
- package/dist/plugins/emoji.js +42 -0
- package/dist/plugins/emoji.js.map +1 -0
- package/dist/plugins/enter-handler.d.ts +26 -0
- package/dist/plugins/enter-handler.js +193 -0
- package/dist/plugins/enter-handler.js.map +1 -0
- package/dist/plugins/highlight.d.ts +39 -0
- package/dist/plugins/highlight.js +283 -0
- package/dist/plugins/highlight.js.map +1 -0
- package/dist/plugins/inline-code-convert.d.ts +32 -0
- package/dist/plugins/inline-code-convert.js +173 -0
- package/dist/plugins/inline-code-convert.js.map +1 -0
- package/dist/plugins/link-text-plugin.d.ts +22 -0
- package/dist/plugins/link-text-plugin.js +194 -0
- package/dist/plugins/link-text-plugin.js.map +1 -0
- package/dist/plugins/mermaid-renderer.d.ts +24 -0
- package/dist/plugins/mermaid-renderer.js +80 -0
- package/dist/plugins/mermaid-renderer.js.map +1 -0
- package/dist/schema.d.ts +48 -0
- package/dist/schema.js +847 -0
- package/dist/schema.js.map +1 -0
- package/dist/setup.d.ts +104 -0
- package/dist/setup.js +4393 -0
- package/dist/setup.js.map +1 -0
- package/dist/types.d.ts +107 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +121 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/editor-props-plugin.ts","../../src/markdown.ts","../../src/schema.ts","../../src/types.ts"],"sourcesContent":["/**\n * Unified editor props plugin — merges 5 separate ProseMirror plugins into one.\n *\n * Faithful 1:1 migration from Moraya desktop `src/lib/editor/plugins/editor-props-plugin.ts`\n * with the following DI changes (v0.60.0-pre §F2.6):\n * - `editorStore.getState().currentFilePath` → `platform.getCurrentFilePath()`\n * - `isMacOS` from `$lib/utils/platform` → `platform.isMacOS`\n * - `import('@tauri-apps/plugin-opener').{openPath,openUrl}` → `linkOpener.open(href)`\n * (the consumer's LinkOpener implementation routes to the right platform API)\n *\n * Consolidated props:\n * - `clipboardTextParser`: parse pasted plain text as Markdown (render instead of escape)\n * - `transformPastedHTML`: paste language fix (copy `class=\"language-xxx\"` → `data-language`)\n * - `handleDOMEvents.mousedown`: math_block click → prevent WebKit broken selection;\n * Cmd/Ctrl+click on links → open externally via LinkOpener\n * - `handleDOMEvents.keydown/keyup`: toggle link-hover cursor class on Cmd/Ctrl;\n * fast AllSelection delete; WKWebView end-of-textblock Backspace fix\n * - `handleClick`: click below content → append paragraph + place cursor\n * - `handleClickOn`: image click → TextSelection (prevent NodeSelection blue highlight)\n * - `handleKeyDown`: ArrowRight escape; fast AllSelection delete (fallback)\n * - `decorations`: WKWebView caret fix for empty paragraphs (macOS only)\n * - `view` lifecycle: scroll-after-paste; empty-doc focus recovery\n *\n * Reducing 5 plugin instances to 1 saves ~4 apply() traversals per transaction.\n */\n\nimport { Fragment, Slice } from 'prosemirror-model'\nimport { AllSelection, Plugin, PluginKey, TextSelection } from 'prosemirror-state'\nimport { Decoration, DecorationSet } from 'prosemirror-view'\nimport { parseMarkdown } from '../markdown'\nimport type { LinkOpener, Platform } from '../types'\n\nconst editorPropsKey = new PluginKey('moraya-editor-props')\n\n/** Detect whether a URL is a local file path (absolute or relative). */\nfunction isLocalFilePath(href: string): boolean {\n // Absolute Unix/macOS paths\n if (href.startsWith('/')) return true\n // Relative paths\n if (href.startsWith('./') || href.startsWith('../')) return true\n // Windows absolute paths\n if (/^[A-Za-z]:[/\\\\]/.test(href)) return true\n // file:// protocol\n if (href.startsWith('file://')) return true\n return false\n}\n\n/** Resolve a local-path href against the platform's current file directory. */\nfunction resolveLocalPath(href: string, platform: Platform): string {\n // Strip file:// protocol and decode URL-encoded characters\n let path = href\n if (path.startsWith('file:///')) {\n path = path.slice(7) // file:///path → /path\n try { path = decodeURIComponent(path) } catch { /* keep as-is */ }\n } else if (path.startsWith('file://')) {\n path = path.slice(5) // file://path → //path (UNC)\n try { path = decodeURIComponent(path) } catch { /* keep as-is */ }\n }\n\n // Already absolute\n if (path.startsWith('/') || /^[A-Za-z]:[/\\\\]/.test(path)) return path\n\n // Relative path: resolve against current file's directory\n const currentFile = platform.getCurrentFilePath()\n if (currentFile) {\n const dir = currentFile.replace(/[/\\\\][^/\\\\]*$/, '')\n return dir + '/' + path\n }\n return path\n}\n\nexport interface EditorPropsPluginOptions {\n platform: Platform\n linkOpener: LinkOpener\n}\n\nexport function createEditorPropsPlugin(opts: EditorPropsPluginOptions): Plugin {\n const { platform, linkOpener } = opts\n const isMacOS = platform.isMacOS\n\n // scroll-after-paste state\n let pendingPaste = false\n\n return new Plugin({\n key: editorPropsKey,\n\n props: {\n /**\n * Parse pasted plain text as Markdown so syntax renders instead of\n * being inserted as escaped literal text.\n */\n clipboardTextParser(text, $context, plain) {\n if (plain || $context.parent.type.spec.code) return undefined!\n const doc = parseMarkdown(text)\n // If markdown parse produced a single empty paragraph, fall back to\n // literal text insertion to avoid replacing the current selection.\n if (doc.textContent.length === 0 && doc.content.size <= 2) return undefined!\n const content = doc.content\n // Single paragraph → extract inline content so it merges into current text\n if (content.childCount === 1 && content.firstChild!.type.name === 'paragraph') {\n return new Slice(content.firstChild!.content, 0, 0)\n }\n return new Slice(content, 0, 0)\n },\n\n /**\n * Safety net for degenerate pastes (empty markdown link, empty <a>, etc.).\n * Also routes pasted markdown image syntax through the markdown parser.\n */\n handlePaste(view, event, slice) {\n const plain = event.clipboardData?.getData('text/plain')\n if (!plain) return false\n\n // Markdown image syntax — parse so the image renders instead of being escaped\n const trimmed = plain.trim()\n if (/^!\\[/.test(trimmed)) {\n const doc = parseMarkdown(trimmed)\n if (doc.content.size > 2) {\n const content = doc.content\n const inner = (content.childCount === 1 && content.firstChild!.type.name === 'paragraph')\n ? content.firstChild!.content\n : content\n view.dispatch(\n view.state.tr.replaceSelection(new Slice(inner, 0, 0)),\n )\n pendingPaste = true\n return true\n }\n }\n\n // Link pattern with empty text or empty URL\n const linkMatch = /^\\[([^\\]]*)\\]\\(([^)]*)\\)$/.exec(trimmed)\n if (linkMatch && (!linkMatch[1] || !linkMatch[2])) {\n const textNode = view.state.schema.text(plain)\n view.dispatch(\n view.state.tr.replaceSelection(new Slice(Fragment.from(textNode), 0, 0)),\n )\n pendingPaste = true\n return true\n }\n\n // Degenerate slice (e.g. empty <a> tag from HTML clipboard)\n try {\n const sliceText = slice.content.textBetween(0, slice.content.size, '', '')\n if (sliceText.trim().length === 0 && trimmed.length > 0) {\n const textNode = view.state.schema.text(plain)\n view.dispatch(\n view.state.tr.replaceSelection(new Slice(Fragment.from(textNode), 0, 0)),\n )\n pendingPaste = true\n return true\n }\n } catch { /* malformed slice — fall through */ }\n\n return false\n },\n\n /**\n * Paste language normalization:\n * Copy class=\"language-xxx\" from <code> to data-language on parent <pre>.\n */\n transformPastedHTML(html) {\n if (!html.includes('language-')) return html\n try {\n const template = document.createElement('template')\n template.innerHTML = html\n const fragment = template.content\n for (const pre of fragment.querySelectorAll('pre')) {\n if (pre.dataset.language) continue\n const code = pre.querySelector('code')\n if (!code) continue\n const match = code.className.match(/(?:language|lang)-(\\S+)/)\n if (match && match[1]) {\n pre.dataset.language = match[1]\n }\n }\n return template.innerHTML\n } catch {\n return html\n }\n },\n\n handleDOMEvents: {\n /**\n * Safety: prevent WebView navigation on any remaining <a> clicks.\n * (Most <a> tags get expanded to literal text on mousedown, but this\n * is a fallback in case the click fires before the expand.)\n */\n click(_view, event) {\n const me = event as MouseEvent\n const target = me.target as HTMLElement | null\n if (!target) return false\n const anchor = target.closest('a[href]') as HTMLAnchorElement | null\n if (anchor) {\n me.preventDefault()\n }\n return false\n },\n\n mousedown(view, event) {\n const me = event as MouseEvent\n if (me.button !== 0) return false\n const target = me.target as HTMLElement | null\n if (!target) return false\n\n // ── Cmd/Ctrl+click on links → open externally via LinkOpener ──\n // Must be handled in mousedown BEFORE ProseMirror places cursor,\n // because link-text-plugin's appendTransaction expands link marks\n // to literal text on cursor entry, removing <a> from DOM before\n // the click event fires.\n if (me.metaKey || me.ctrlKey) {\n const anchor = target.closest('a[href]') as HTMLAnchorElement | null\n if (anchor) {\n const href = anchor.getAttribute('href')\n if (href) {\n me.preventDefault()\n const targetHref = isLocalFilePath(href)\n ? resolveLocalPath(href, platform)\n : href\n try {\n linkOpener.open(targetHref)\n } catch (e) {\n console.warn('[opener] failed:', targetHref, e)\n }\n return true // consume — don't place cursor or expand\n }\n }\n }\n\n // ── Math block click fix ──\n const mathBlock = target.closest('div[data-type=\"math_block\"]')\n if (!mathBlock) return false\n\n // Prevent WebKit from creating the broken range selection\n me.preventDefault()\n\n try {\n const pos = view.posAtDOM(mathBlock, 0)\n const $pos = view.state.doc.resolve(pos)\n\n // Walk up to find the math_block node and get its before-position\n let beforePos = pos\n for (let d = $pos.depth; d > 0; d--) {\n if ($pos.node(d).type.name === 'math_block') {\n beforePos = $pos.before(d)\n break\n }\n }\n const $before = view.state.doc.resolve(beforePos)\n if (!$before.nodeAfter || $before.nodeAfter.type.name !== 'math_block') {\n if ($pos.nodeAfter?.type.name === 'math_block') {\n beforePos = pos\n }\n }\n\n const sel = TextSelection.near(view.state.doc.resolve(beforePos), -1)\n view.dispatch(view.state.tr.setSelection(sel))\n } catch { /* ignore — focus below is the fallback */ }\n\n view.focus()\n return true\n },\n\n /**\n * Cmd/Ctrl held → add 'link-hover' class for pointer cursor on links.\n * Also handles fast AllSelection delete + WKWebView end-of-textblock\n * Backspace fix at the highest priority interception point.\n */\n keydown(view, event) {\n if (event.isComposing) return false\n\n if (event.key === 'Meta' || event.key === 'Control') {\n view.dom.classList.add('link-hover')\n }\n\n // handleDOMEvents.keydown fires BEFORE handleKeyDown and captureKeyDown\n if ((event.key === 'Backspace' || event.key === 'Delete') &&\n !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) {\n // Fast AllSelection / full-range deletion\n // On macOS Cmd+A is handled by native PredefinedMenuItem::select_all\n // which changes the DOM selection but ProseMirror's selectionchange\n // observer may NOT have synced yet. Force flush + DOM Range comparison.\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(view as any).domObserver?.flush?.()\n } catch { /* internal API */ }\n\n const docSize = view.state.doc.content.size\n let isAllSelected = false\n\n // Check 1: ProseMirror's internal selection\n const sel = view.state.selection\n if (sel instanceof AllSelection ||\n (docSize > 0 && sel.from <= 1 && sel.to >= docSize - 1)) {\n isAllSelected = true\n }\n\n // Check 2: DOM Range comparison\n if (!isAllSelected && docSize > 0) {\n try {\n const domSel = window.getSelection()\n if (domSel && !domSel.isCollapsed && domSel.rangeCount > 0) {\n const range = domSel.getRangeAt(0)\n const editorRange = document.createRange()\n editorRange.selectNodeContents(view.dom)\n if (range.compareBoundaryPoints(Range.START_TO_START, editorRange) <= 0 &&\n range.compareBoundaryPoints(Range.END_TO_END, editorRange) >= 0) {\n isAllSelected = true\n }\n }\n } catch { /* Range API edge cases */ }\n }\n\n // Check 3: Text content length comparison (last resort)\n if (!isAllSelected && docSize > 0) {\n try {\n const domSel = window.getSelection()\n if (domSel && !domSel.isCollapsed) {\n const selectedText = domSel.toString()\n const fullText = view.dom.textContent || ''\n if (selectedText.length > 0 && fullText.length > 0 &&\n selectedText.length >= fullText.length * 0.9) {\n isAllSelected = true\n }\n }\n } catch { /* ignore */ }\n }\n\n if (isAllSelected) {\n event.preventDefault()\n const paragraphType = view.state.schema.nodes.paragraph\n if (!paragraphType) return false\n const emptyParagraph = paragraphType.create()\n const tr = view.state.tr.replaceWith(0, docSize, emptyParagraph)\n tr.setSelection(TextSelection.create(tr.doc, 1))\n tr.setMeta('full-delete', true)\n view.dispatch(tr)\n return true\n }\n\n // ── WKWebView end-of-textblock Backspace fix ──\n // ProseMirror's captureKeyDown → stopNativeHorizontalDelete uses\n // view.endOfTextblock(\"backward\") which relies on WebKit's\n // Selection.modify(). In WKWebView this can return incorrect\n // results at paragraph boundaries, causing joinBackward to merge\n // paragraphs instead of deleting the character before the cursor.\n if (event.key === 'Backspace') {\n if (sel instanceof TextSelection && sel.empty && sel.$cursor) {\n const { parent, parentOffset } = sel.$cursor\n if (parent.isTextblock && parentOffset === parent.content.size && parentOffset > 0) {\n const nb = sel.$cursor.nodeBefore\n if (nb) {\n event.preventDefault()\n if (nb.isText && nb.text) {\n const code = nb.text.charCodeAt(nb.text.length - 1)\n const delLen = (code >= 0xDC00 && code <= 0xDFFF) ? 2 : 1\n view.dispatch(view.state.tr.delete(sel.from - delLen, sel.from).scrollIntoView())\n } else {\n view.dispatch(view.state.tr.delete(sel.from - nb.nodeSize, sel.from).scrollIntoView())\n }\n return true\n }\n }\n }\n }\n }\n\n return false\n },\n keyup(view, event) {\n if (event.key === 'Meta' || event.key === 'Control') {\n view.dom.classList.remove('link-hover')\n }\n return false\n },\n },\n\n /**\n * Click below content: append a paragraph and place cursor there when\n * the last node is a code_block / table / etc. and user clicks below it.\n */\n handleClick(view, _pos, event) {\n if (event.button !== 0) return false\n const { doc } = view.state\n const lastNode = doc.lastChild\n if (!lastNode || lastNode.type.name === 'paragraph') return false\n const lastNodePos = doc.content.size - lastNode.nodeSize\n const lastDOM = view.nodeDOM(lastNodePos) as HTMLElement | null\n if (!lastDOM) return false\n\n // Only trigger when clicking BELOW the last block's bottom edge.\n const rect = lastDOM.getBoundingClientRect()\n if (event.clientY <= rect.bottom) return false\n\n const paragraphType = view.state.schema.nodes.paragraph\n if (!paragraphType) return false\n const endPos = doc.content.size\n const paragraph = paragraphType.create()\n const tr = view.state.tr.insert(endPos, paragraph)\n tr.setSelection(TextSelection.create(tr.doc, endPos + 1))\n view.dispatch(tr)\n view.focus()\n return true\n },\n\n /**\n * Image click: prevent NodeSelection blue highlight, place TextSelection\n * after the image instead. (math_block is handled in mousedown above.)\n */\n handleClickOn(view, _pos, node, nodePos, event) {\n if (node.type.name !== 'image') return false\n if (event.button !== 0) return false\n\n const $pos = view.state.doc.resolve(nodePos + node.nodeSize)\n const sel = TextSelection.near($pos)\n view.dispatch(view.state.tr.setSelection(sel))\n return true\n },\n\n /**\n * Keyboard shortcuts (after keymap plugins):\n * - ArrowRight: escape formatting mark boundary\n * - Backspace/Delete on AllSelection: fast full-doc deletion\n */\n handleKeyDown(view, event) {\n if (event.isComposing) return false\n\n // ArrowRight: escape formatting mark at right boundary\n if (event.key === 'ArrowRight' &&\n !event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey) {\n const sel = view.state.selection\n if (sel.empty && sel instanceof TextSelection && sel.$cursor) {\n const $cursor = sel.$cursor\n const ZWSP_MARK_NAMES = ['code', 'strong', 'em', 'strike_through']\n const nodeBefore = $cursor.nodeBefore\n const nodeAfter = $cursor.nodeAfter\n const hasTargetMarkBefore = nodeBefore != null && ZWSP_MARK_NAMES.some(name => {\n const mt = view.state.schema.marks[name]\n return mt && nodeBefore.marks.some(m => m.type === mt)\n })\n if (hasTargetMarkBefore && nodeAfter?.isText &&\n nodeAfter.text?.startsWith('')) {\n const nextPos = $cursor.pos + nodeAfter.nodeSize\n const $next = view.state.doc.resolve(Math.min(nextPos, view.state.doc.content.size))\n const nextSel = TextSelection.near($next, 1)\n const tr = view.state.tr.setSelection(nextSel)\n tr.setStoredMarks([])\n tr.setMeta('code-escape', true)\n tr.scrollIntoView()\n view.dispatch(tr)\n return true\n }\n }\n }\n\n // Fast AllSelection / full-range deletion\n if (event.key === 'Backspace' || event.key === 'Delete') {\n const sel = view.state.selection\n const docSize = view.state.doc.content.size\n const isAllSelected =\n sel instanceof AllSelection ||\n (docSize > 0 && sel.from <= 1 && sel.to >= docSize - 1)\n if (isAllSelected) {\n event.preventDefault()\n const paragraphType = view.state.schema.nodes.paragraph\n if (!paragraphType) return false\n const emptyParagraph = paragraphType.create()\n const tr = view.state.tr.replaceWith(0, docSize, emptyParagraph)\n tr.setSelection(TextSelection.create(tr.doc, 1))\n tr.setMeta('full-delete', true)\n view.dispatch(tr)\n return true\n }\n }\n\n return false\n },\n\n /**\n * WKWebView caret fix: add 'caret-empty-para' decoration to empty\n * paragraph under cursor on macOS.\n */\n decorations(state) {\n if (!isMacOS) return DecorationSet.empty\n const { selection } = state\n if (!selection.empty) return DecorationSet.empty\n\n const { $from } = selection\n const parent = $from.parent\n if (parent.type.name === 'paragraph' && parent.content.size === 0) {\n const pos = $from.before()\n return DecorationSet.create(state.doc, [\n Decoration.node(pos, pos + parent.nodeSize, { class: 'caret-empty-para' }),\n ])\n }\n return DecorationSet.empty\n },\n },\n\n /**\n * Scroll-after-paste + empty-doc focus recovery.\n */\n view(editorView) {\n function onPaste() { pendingPaste = true }\n editorView.dom.addEventListener('paste', onPaste, true)\n\n // Remove link-hover class when window loses focus (Cmd/Ctrl release won't fire)\n function onBlur() { editorView.dom.classList.remove('link-hover') }\n window.addEventListener('blur', onBlur)\n\n return {\n update(view, prevState) {\n // WKWebView empty-doc focus recovery\n if (isMacOS && view.state.doc !== prevState.doc) {\n const docSize = view.state.doc.content.size\n const prevDocSize = prevState.doc.content.size\n if (docSize <= 4 && prevDocSize > 4) {\n requestAnimationFrame(() => {\n try {\n if (!view.hasFocus()) view.focus()\n } catch { /* ignore */ }\n })\n }\n }\n\n if (!pendingPaste || view.state.doc.eq(prevState.doc)) return\n pendingPaste = false\n requestAnimationFrame(() => {\n try {\n const { from } = view.state.selection\n const coords = view.coordsAtPos(from)\n const wrapper = view.dom.closest('.editor-wrapper') as HTMLElement | null\n if (!wrapper) return\n const rect = wrapper.getBoundingClientRect()\n if (coords.top < rect.top || coords.bottom > rect.bottom) {\n wrapper.scrollTop += coords.top - rect.top - rect.height / 2\n }\n } catch { /* ignore */ }\n })\n },\n destroy() {\n editorView.dom.removeEventListener('paste', onPaste, true)\n window.removeEventListener('blur', onBlur)\n editorView.dom.classList.remove('link-hover')\n },\n }\n },\n })\n}\n","/**\n * Markdown ↔ ProseMirror Doc roundtrip for `@moraya/core`.\n *\n * Faithful 1:1 migration from Moraya desktop `src/lib/editor/markdown.ts`.\n * Uses `prosemirror-markdown` with `markdown-it` as the tokenizer.\n *\n * Supports: CommonMark + GFM (tables, strikethrough, task lists) +\n * math (via markdown-it-texmath) + definition lists +\n * paired raw-HTML marks + frontmatter / footnote pass-through.\n *\n * Configuration matches Milkdown output conventions:\n * - bullet: '-'\n * - horizontal rule: '---'\n * - strong: '**'\n * - emphasis: '*'\n *\n * Serializer wraps `defaultSchema` (host-agnostic NullMediaResolver). The\n * schema's structural shape is identical to consumer-injected schemas\n * (same NodeSpec/MarkSpec ids), so a doc parsed against `defaultSchema`\n * round-trips through any consumer schema without rebuilding.\n */\n\nimport MarkdownIt from 'markdown-it'\nimport deflistPlugin from 'markdown-it-deflist'\nimport texmathPlugin from 'markdown-it-texmath'\nimport { MarkdownParser, MarkdownSerializer } from 'prosemirror-markdown'\nimport type { MarkdownSerializerState } from 'prosemirror-markdown'\nimport type { Node as PmNode, Mark, Schema } from 'prosemirror-model'\nimport { defaultSchema } from './schema'\n\n// ── markdown-it instance ────────────────────────────────────────\n\nconst md = new MarkdownIt({\n html: true,\n linkify: false,\n typographer: false,\n})\n .enable(['table', 'strikethrough'])\n .use(deflistPlugin)\n .use(texmathPlugin)\n\n// ── Paired HTML tag pre-processing ──────────────────────────────\n\ninterface InlineToken {\n type: string\n content: string\n children?: InlineToken[] | null\n meta?: Record<string, unknown> | null\n hidden?: boolean\n level?: number\n block?: boolean\n attrs?: unknown\n info?: string\n map?: [number, number] | null\n markup?: string\n tag?: string\n nesting?: number\n attrGet?: (name: string) => string | null\n}\n\n/**\n * Pre-scan inline tokens to identify paired HTML opening/closing tags.\n * Sets `meta.htmlPaired = true` on paired tokens so the parser converts\n * them to marks (styled rendering) instead of atom nodes (invisible).\n * Unpaired tags remain unmarked → atom nodes → exact roundtrip fidelity.\n */\nfunction tagPairedHtmlInline(tokens: InlineToken[]): void {\n const VOID_RE = /^<(?:br|hr|img|input|wbr|area|base|col|embed|link|meta|param|source|track)[\\s/>]/i\n for (const token of tokens) {\n if (token.type !== 'inline' || !token.children) continue\n const children = token.children\n const stack: { tagName: string; index: number }[] = []\n for (let i = 0; i < children.length; i++) {\n const child = children[i]\n if (!child || child.type !== 'html_inline') continue\n const content: string = child.content\n // Skip void/self-closing elements and comments\n if (VOID_RE.test(content) || /\\/>$/.test(content) || /^<!--/.test(content)) continue\n // Closing tag?\n const closeMatch = content.match(/^<\\/([a-zA-Z][a-zA-Z0-9]*)\\s*>$/)\n if (closeMatch && closeMatch[1]) {\n const tagName = closeMatch[1].toLowerCase()\n for (let j = stack.length - 1; j >= 0; j--) {\n const entry = stack[j]\n if (!entry) continue\n if (entry.tagName === tagName) {\n const opener = children[entry.index]\n if (opener) {\n opener.meta = { ...(opener.meta || {}), htmlPaired: true }\n }\n child.meta = { ...(child.meta || {}), htmlPaired: true }\n stack.splice(j, 1)\n break\n }\n }\n continue\n }\n // Opening tag?\n const openMatch = content.match(/^<([a-zA-Z][a-zA-Z0-9]*)\\b[^>]*>$/)\n if (openMatch && openMatch[1]) {\n stack.push({ tagName: openMatch[1].toLowerCase(), index: i })\n }\n }\n }\n}\n\n/**\n * Detect extra blank lines between top-level blocks and inject empty paragraph\n * tokens so ProseMirror preserves them as empty <p> nodes.\n *\n * Standard Markdown collapses consecutive blank lines into one paragraph break.\n * This post-processor restores each extra blank line as an empty paragraph,\n * giving Typora-style round-trip fidelity for multi-Enter spacing.\n */\nfunction preserveBlankLines(tokens: InlineToken[]): InlineToken[] {\n function mkToken(type: string, tag: string, nesting: number, extra?: Partial<InlineToken>): InlineToken {\n return {\n type, tag, nesting, content: '', children: null, attrs: null, info: '',\n meta: null, map: null, block: true, hidden: false, level: 0, markup: '',\n ...extra,\n }\n }\n\n const result: InlineToken[] = []\n let lastTopBlockEndLine = 0\n\n for (let i = 0; i < tokens.length; i++) {\n const tok = tokens[i]\n if (!tok) continue\n\n if (tok.map && tok.level === 0 && (tok.nesting === 1 || tok.nesting === 0)) {\n const startLine = tok.map[0] as number\n const gap = startLine - lastTopBlockEndLine\n\n if (gap > 1 && lastTopBlockEndLine > 0) {\n const extra = gap - 1\n for (let j = 0; j < extra; j++) {\n result.push(\n mkToken('paragraph_open', 'p', 1),\n mkToken('inline', '', 0, { level: 1, block: false, children: [] }),\n mkToken('paragraph_close', 'p', -1),\n )\n }\n }\n\n lastTopBlockEndLine = tok.map[1] as number\n }\n\n result.push(tok)\n }\n\n return result\n}\n\n// Patch md.parse to inject paired-tag pre-processing and blank-line preservation\n// before prosemirror-markdown processes the tokens.\nconst _origMdParse = md.parse.bind(md)\nmd.parse = function (src: string, env: unknown) {\n let tokens = _origMdParse(src, env) as unknown as InlineToken[]\n tagPairedHtmlInline(tokens)\n tokens = preserveBlankLines(tokens)\n return tokens as unknown as ReturnType<typeof _origMdParse>\n}\n\n// ── Parser ──────────────────────────────────────────────────────\n\n/**\n * Token-to-node mapping for prosemirror-markdown's MarkdownParser.\n * markdown-it token names → ProseMirror node/mark names from schema.ts\n */\nconst parserTokens: Record<string, import('prosemirror-markdown').ParseSpec> = {\n // ── Block tokens ──\n paragraph: { block: 'paragraph' },\n blockquote: { block: 'blockquote' },\n heading: {\n block: 'heading',\n getAttrs(token) {\n return { level: Number(token.tag.slice(1)) }\n },\n },\n hr: { node: 'horizontal_rule' },\n bullet_list: { block: 'bullet_list' },\n ordered_list: {\n block: 'ordered_list',\n getAttrs(token) {\n return { order: Number(token.attrGet('start') || 1) }\n },\n },\n list_item: {\n block: 'list_item',\n getAttrs(_token, tokens, index) {\n // Check for task list checkbox in the first inline child.\n // The inline content starts with [x] or [ ]\n let checked: boolean | null = null\n for (let i = index + 1; i < tokens.length; i++) {\n const t = tokens[i]\n if (!t) continue\n if (t.type === 'inline' && t.content) {\n const match = t.content.match(/^\\[( |x|X)\\]\\s?/)\n if (match) {\n checked = match[1] !== ' '\n // Strip the checkbox text from the token content\n t.content = t.content.slice(match[0].length)\n // Also update children if they exist\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const children = (t as any).children\n if (children && children.length > 0) {\n const firstChild = children[0]\n if (firstChild.type === 'text') {\n firstChild.content = firstChild.content.slice(match[0].length)\n if (!firstChild.content) {\n children.shift()\n }\n }\n }\n }\n break\n }\n if (t.type === 'list_item_close') break\n }\n return { checked }\n },\n },\n code_block: {\n block: 'code_block',\n getAttrs() {\n return { language: 'text' }\n },\n noCloseToken: true,\n },\n fence: {\n block: 'code_block',\n getAttrs(token) {\n return { language: token.info.trim() || 'text' }\n },\n noCloseToken: true,\n },\n html_block: {\n block: 'html_block',\n noCloseToken: true,\n },\n html_inline: {\n // markdown-it emits this token for inline HTML like <br>, <span>, <sup>,\n // and HTML comments <!-- ... -->. Store raw HTML in the `value` attr.\n node: 'html_inline',\n noCloseToken: true,\n getAttrs(token) {\n return { value: token.content }\n },\n },\n\n // ── Table tokens ──\n // NOTE: tr/th/td are NOT listed here — they are handled by custom tokenHandler\n // overrides in MorayaMarkdownParser below. The `block:` spec alone can't\n // handle (a) thead-row → table_header_row vs table_row dispatch, or\n // (b) wrapping inline content in the required paragraph child of each cell.\n table: { block: 'table' },\n thead: { ignore: true },\n tbody: { ignore: true },\n\n // ── Definition list tokens ──\n dl: { block: 'defList' },\n dt: { block: 'defListTerm' },\n dd: { block: 'defListDescription' },\n\n // ── Math tokens (from markdown-it-texmath) ──\n // Use block: spec (not node:) so token.content is added as text children,\n // correctly filling math_inline's `content: 'text*'`.\n math_inline: {\n block: 'math_inline',\n noCloseToken: true,\n },\n // markdown-it-texmath emits math_inline_double for $$...$$ in inline context.\n // Map to math_inline to prevent \"Token type not supported\" crash.\n math_inline_double: {\n block: 'math_inline',\n noCloseToken: true,\n },\n math_block: {\n node: 'math_block',\n noCloseToken: true,\n getAttrs(token) {\n return { value: token.content.trim() }\n },\n },\n\n // ── Inline tokens ──\n image: {\n node: 'image',\n getAttrs(token) {\n // markdown-it URL-encodes backslashes in paths (\\ → %5C),\n // which breaks Windows local paths on roundtrip.\n // Decode to preserve the original path.\n let src = token.attrGet('src') || ''\n try { src = decodeURIComponent(src) } catch { /* keep as-is */ }\n return {\n src,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n alt: ((token.children as any[]) || []).map(c => c.content).join('') || '',\n title: token.attrGet('title') || '',\n }\n },\n },\n hardbreak: { node: 'hardbreak' },\n softbreak: { node: 'hardbreak', attrs: { isInline: true } },\n\n // ── Mark tokens ──\n em: { mark: 'em' },\n strong: { mark: 'strong' },\n s: { mark: 'strike_through' },\n code_inline: { mark: 'code', noCloseToken: true },\n link: {\n mark: 'link',\n getAttrs(token) {\n let href = token.attrGet('href') || ''\n // Decode percent-encoded non-ASCII UTF-8 characters (e.g. Chinese/Japanese/Korean)\n // so URLs preserve original characters through roundtrips.\n // Only decodes multi-byte UTF-8 sequences (C2-FF start bytes + 80-BF continuations),\n // leaving ASCII encodings like %20 (space), %28/%29 (parens) intact.\n href = href.replace(\n /%[C-F][0-9A-F](?:%[89AB][0-9A-F])+/gi,\n (m) => { try { return decodeURIComponent(m) } catch { return m } },\n )\n return {\n href,\n title: token.attrGet('title') || null,\n }\n },\n },\n}\n\n/**\n * Custom MarkdownParser that correctly handles GFM table structure.\n *\n * Two problems with the default prosemirror-markdown `block:` approach for tables:\n *\n * 1. `table_header` and `table_cell` both have `content: 'paragraph+'` in our schema.\n * prosemirror-markdown opens the cell block, then adds raw inline text via addText().\n * When closeNode() calls createAndFill(attrs, [text(\"A\")]), the content match\n * for `paragraph+` cannot fit a bare text node, so createAndFill() returns null\n * and the cell (and all its content) is silently dropped → empty table.\n *\n * 2. `table: content: 'table_header_row table_row+'` requires the first child to be\n * a `table_header_row`, but the default `tr` handler always creates `table_row`.\n * ProseMirror's createAndFill() then auto-inserts an empty `table_header_row`\n * at the front, leaving the real header data in a wrongly-typed `table_row`.\n *\n * Fix: override tr/th/td tokenHandlers in the constructor.\n */\nclass MorayaMarkdownParser extends MarkdownParser {\n /**\n * The schema this parser instance is bound to. Captured for use in\n * tokenHandler overrides (tr_open / th_open / etc.) so they reference the\n * caller-provided schema rather than the module-level defaultSchema.\n */\n public readonly schema: Schema\n\n constructor(schemaArg: Schema = defaultSchema) {\n super(schemaArg, md, parserTokens)\n this.schema = schemaArg\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const h: Record<string, (state: any, tok: any, tokens: any[], i: number) => void> =\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (this as any).tokenHandlers\n\n function cellAlignment(tok: { attrGet(s: string): string | null }): string {\n const style = tok.attrGet('style') || ''\n const m = style.match(/text-align:\\s*(\\w+)/)\n return m && m[1] ? m[1] : 'left'\n }\n\n // tr_open: dispatch to table_header_row or table_row based on parent context\n h['tr_open'] = (state, _tok, tokens, i) => {\n let inThead = false\n for (let j = i - 1; j >= 0; j--) {\n if (tokens[j].type === 'thead_open') { inThead = true; break }\n if (tokens[j].type === 'thead_close' || tokens[j].type === 'tbody_open') break\n }\n state.openNode(inThead ? schemaArg.nodes.table_header_row : schemaArg.nodes.table_row, null)\n }\n h['tr_close'] = (state) => state.closeNode()\n\n // th_open/close: open table_header + inner paragraph so inline text lands correctly\n h['th_open'] = (state, tok) => {\n state.openNode(schemaArg.nodes.table_header, { alignment: cellAlignment(tok) })\n state.openNode(schemaArg.nodes.paragraph, null)\n }\n h['th_close'] = (state) => {\n state.closeNode() // close paragraph\n state.closeNode() // close table_header\n }\n\n // td_open/close: open table_cell + inner paragraph\n h['td_open'] = (state, tok) => {\n state.openNode(schemaArg.nodes.table_cell, { alignment: cellAlignment(tok) })\n state.openNode(schemaArg.nodes.paragraph, null)\n }\n h['td_close'] = (state) => {\n state.closeNode() // close paragraph\n state.closeNode() // close table_cell\n }\n\n // ── Empty link preservation ────────────────────────────────────\n // When markdown-it parses `[]()` or `[](url)`, it emits link_open → link_close\n // with no text token between them. ProseMirror discards marks with no content,\n // so the link completely disappears. Fix: detect empty-text links and insert\n // the raw markdown syntax as literal text instead of creating a mark.\n const defaultLinkOpen = h['link_open']\n const defaultLinkClose = h['link_close']\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n h['link_open'] = (state: any, tok: any, tokens: any[], i: number) => {\n // Check if there's actual content between link_open and link_close\n let hasContent = false\n for (let j = i + 1; j < tokens.length; j++) {\n if (tokens[j].type === 'link_close') break\n if (tokens[j].type === 'text' && tokens[j].content) {\n hasContent = true\n break\n }\n if (['image', 'code_inline', 'softbreak', 'hardbreak', 'html_inline'].includes(tokens[j].type)) {\n hasContent = true\n break\n }\n }\n\n if (!hasContent) {\n // Empty-text link: insert raw markdown syntax as literal text\n let href = tok.attrGet('href') || ''\n href = href.replace(\n /%[C-F][0-9A-F](?:%[89AB][0-9A-F])+/gi,\n (m: string) => { try { return decodeURIComponent(m) } catch { return m } },\n )\n const title = tok.attrGet('title')\n let literal = `[](${href}`\n if (title) literal += ` \"${title}\"`\n literal += ')'\n state.addText(literal)\n // Mark the corresponding link_close to be skipped\n for (let j = i + 1; j < tokens.length; j++) {\n if (tokens[j].type === 'link_close') {\n tokens[j].meta = { ...(tokens[j].meta || {}), skipClose: true }\n break\n }\n }\n return\n }\n\n defaultLinkOpen!(state, tok, tokens, i)\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n h['link_close'] = (state: any, tok: any, tokens: any[], i: number) => {\n if (tok.meta?.skipClose) return\n defaultLinkClose!(state, tok, tokens, i)\n }\n\n // ── Paired inline HTML → marks ─────────────────────────────────\n // Pre-scanned paired tags (meta.htmlPaired) become openMark/closeMark\n // so the visual editor renders them with styling. Unpaired tags stay\n // as html_inline atom nodes for exact roundtrip fidelity.\n //\n // Special case: <audio>/<video> inline tags (single-line, e.g.\n // `<audio src=\"...\" controls></audio>`) are combined into a single\n // html_inline atom node so toDOM renders them as media players.\n const defaultTextHandler = h['text']\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n h['text'] = (state: any, tok: any, toks: any[], ii: number) => {\n if (tok.meta?.mediaSkip) return\n defaultTextHandler!(state, tok, toks, ii)\n }\n\n h['html_inline'] = (state, tok, tokens, i) => {\n // Skip tokens already consumed by audio/video combination\n if (tok.meta?.mediaSkip) return\n\n const content: string = tok.content\n\n // <audio>/<video> inline: combine opening + closing into single atom.\n const mediaMatch = content.match(/^<(audio|video)\\b/i)\n if (mediaMatch && mediaMatch[1]) {\n const tagName = mediaMatch[1].toLowerCase()\n const closeRe = new RegExp(`^</${tagName}\\\\s*>$`, 'i')\n let fullHtml = content\n for (let j = i + 1; j < tokens.length; j++) {\n const t = tokens[j]\n if (t.type === 'html_inline' && closeRe.test(t.content.trim())) {\n fullHtml += t.content\n t.meta = { ...(t.meta || {}), mediaSkip: true }\n break\n }\n if (t.content) fullHtml += t.content\n t.meta = { ...(t.meta || {}), mediaSkip: true }\n }\n state.addNode(schemaArg.nodes.html_inline, { value: fullHtml })\n return\n }\n\n if (tok.meta?.htmlPaired) {\n const htmlMark = schemaArg.marks.html_mark\n if (!htmlMark) {\n // Schema lacks html_mark — fall through to atom-node fallback\n state.addNode(schemaArg.nodes.html_inline, { value: content })\n return\n }\n if (!content.startsWith('</')) {\n // Opening tag → open mark\n const tagMatch = content.match(/^<([a-zA-Z][a-zA-Z0-9]*)/)\n const tagName = tagMatch && tagMatch[1] ? tagMatch[1].toLowerCase() : ''\n state.openMark(htmlMark.create({\n openTag: content,\n closeTag: `</${tagName}>`,\n }))\n } else {\n // Closing tag → close mark\n state.closeMark(htmlMark)\n }\n return\n }\n // Not paired → atom node (preserves current behavior)\n state.addNode(schemaArg.nodes.html_inline, { value: content })\n }\n\n // ── HTML <img> / <video> / <audio> tag: block → inline promotion ──\n // markdown-it tokenizes standalone <img> as html_block (renders as code block).\n // Promote to paragraph(html_inline) so the toDOM can render it as an image.\n // Source format is preserved: html_inline serializes attrs.value (original HTML).\n const defaultHtmlBlock = h['html_block']\n h['html_block'] = (state, tok, tokens, i) => {\n const content = tok.content.trim()\n if (/^<img\\s/i.test(content)) {\n // Extract all <img> tags — put them in ONE paragraph with inline\n // hardbreaks between them, matching markdown image behavior.\n const imgPattern = /<img\\s[^>]*\\/?>/gi\n const imgs = content.match(imgPattern)\n state.openNode(schemaArg.nodes.paragraph, null)\n if (imgs && imgs.length > 0) {\n for (let j = 0; j < imgs.length; j++) {\n if (j > 0) {\n state.addNode(schemaArg.nodes.hardbreak, { isInline: true })\n }\n state.addNode(schemaArg.nodes.html_inline, { value: imgs[j] })\n }\n } else {\n state.addNode(schemaArg.nodes.html_inline, { value: content })\n }\n state.closeNode()\n } else if (/^<(video|audio)\\b/i.test(content)) {\n // Promote <video>/<audio> blocks to paragraph(html_inline) so toDOM\n // renders them as actual media players instead of code blocks.\n state.openNode(schemaArg.nodes.paragraph, null)\n state.addNode(schemaArg.nodes.html_inline, { value: content })\n state.closeNode()\n } else {\n defaultHtmlBlock!(state, tok, tokens, i)\n }\n }\n }\n}\n\n/** Default parser bound to {@link defaultSchema} (used when caller doesn't pass a schema). */\nconst defaultParser = new MorayaMarkdownParser(defaultSchema)\n\n/**\n * Cache of parsers keyed by schema identity. Rebuilding the parser per call\n * would re-construct token handlers + retype-overrides on every parseMarkdown\n * invocation; this WeakMap avoids that. The defaultSchema entry is pre-seeded.\n */\nconst parserCache = new WeakMap<Schema, MorayaMarkdownParser>()\nparserCache.set(defaultSchema, defaultParser)\n\nfunction getParserFor(schema: Schema | undefined): MorayaMarkdownParser {\n if (!schema || schema === defaultSchema) return defaultParser\n let p = parserCache.get(schema)\n if (!p) {\n p = new MorayaMarkdownParser(schema)\n parserCache.set(schema, p)\n }\n return p\n}\n\n// ── Serializer ──────────────────────────────────────────────────\n\nconst serializer = new MarkdownSerializer(\n {\n // ── Block nodes ──\n doc(state, node) {\n state.renderContent(node)\n },\n paragraph(state, node) {\n if (node.content.size === 0) {\n state.write('')\n } else {\n state.renderInline(node)\n }\n state.closeBlock(node)\n },\n heading(state, node) {\n state.write(`${'#'.repeat(node.attrs.level as number)} `)\n // fromBlockStart=false: the `## ` prefix already prevents text from being\n // parsed as list markers / blockquote, so don't escape `1.`, `-`, `>` etc.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ;(state.renderInline as (n: PmNode, b?: boolean) => void)(node, false)\n state.closeBlock(node)\n },\n blockquote(state, node) {\n state.wrapBlock('> ', null, node, () => state.renderContent(node))\n },\n code_block(state, node) {\n const lang = (node.attrs.language as string) || ''\n const fenceLang = lang === 'text' ? '' : lang\n state.write(`\\`\\`\\`${fenceLang}\\n`)\n state.text(node.textContent, false)\n state.ensureNewLine()\n state.write('```')\n state.closeBlock(node)\n },\n horizontal_rule(state, node) {\n state.write('---')\n state.closeBlock(node)\n },\n bullet_list(state, node) {\n state.renderList(node, ' ', () => '- ')\n },\n ordered_list(state, node) {\n const start = (node.attrs.order as number) || 1\n state.renderList(node, ' ', (i: number) => `${start + i}. `)\n },\n list_item(state, node) {\n // Task list checkbox prefix\n if (node.attrs.checked != null) {\n const checkbox = node.attrs.checked ? '[x] ' : '[ ] '\n state.write(checkbox)\n }\n state.renderContent(node)\n },\n image(state, node) {\n const alt = state.esc((node.attrs.alt as string) || '', false)\n const src = (node.attrs.src as string) || ''\n const title = node.attrs.title as string | null | undefined\n if (title) {\n state.write(`}\")`)\n } else {\n state.write(``)\n }\n },\n hardbreak(state) {\n // Always use markdown hardbreak format (two spaces + newline)\n // for consistent parsing and roundtrip fidelity\n state.write(' \\n')\n },\n html_block(state, node) {\n state.text(node.textContent, false)\n state.closeBlock(node)\n },\n html_inline(state, node) {\n // Write the raw HTML back verbatim (no escaping); value attr holds the original HTML.\n state.text(node.attrs.value as string, false)\n },\n\n // ── Table nodes ──\n table(state, node) {\n // Collect alignment from header row\n const alignments: string[] = []\n const headerRow = node.child(0)\n headerRow.forEach(cell => {\n alignments.push((cell.attrs.alignment as string) || 'left')\n })\n\n // Render header row\n renderTableRow(state, headerRow)\n\n // Render separator\n const sep = alignments.map(a => {\n switch (a) {\n case 'center': return ':---:'\n case 'right': return '---:'\n default: return '---'\n }\n })\n state.write(`| ${sep.join(' | ')} |`)\n state.ensureNewLine()\n\n // Render data rows\n for (let i = 1; i < node.childCount; i++) {\n renderTableRow(state, node.child(i))\n }\n state.closeBlock(node)\n },\n table_header_row() { /* handled by table */ },\n table_row() { /* handled by table */ },\n table_header(state, node) {\n state.renderInline(node.firstChild!)\n },\n table_cell(state, node) {\n state.renderInline(node.firstChild!)\n },\n\n // ── Math nodes ──\n math_inline(state, node) {\n state.write(`$${node.textContent}$`)\n },\n math_block(state, node) {\n state.write('$$\\n')\n state.text((node.attrs.value as string) || node.textContent, false)\n state.ensureNewLine()\n state.write('$$')\n state.closeBlock(node)\n },\n\n // ── Definition list nodes ──\n defList(state, node) {\n state.renderContent(node)\n },\n defListTerm(state, node) {\n state.renderInline(node)\n state.closeBlock(node)\n },\n defListDescription(state, node) {\n state.write(': ')\n state.renderContent(node)\n },\n\n // ── Fallback for text node (shouldn't be needed but safe) ──\n text(state, node) {\n state.text(node.text || '')\n },\n },\n {\n // ── Mark serializers ──\n strong: {\n open: '**',\n close: '**',\n mixable: true,\n expelEnclosingWhitespace: true,\n },\n em: {\n open: '*',\n close: '*',\n mixable: true,\n expelEnclosingWhitespace: true,\n },\n code: {\n open(_state: MarkdownSerializerState, mark: Mark, parent: PmNode, index: number) {\n return isPlainURL(mark, parent, index, 1) ? '' : '`'\n },\n close(_state: MarkdownSerializerState, mark: Mark, parent: PmNode, index: number) {\n return isPlainURL(mark, parent, index, -1) ? '' : '`'\n },\n escape: false,\n },\n link: {\n open(_state, mark, parent, index) {\n return isPlainURL(mark, parent, index, 1) ? '<' : '['\n },\n close(state, mark, parent, index) {\n const href = mark.attrs.href as string\n const title = mark.attrs.title as string | null | undefined\n if (isPlainURL(mark, parent, index, -1)) {\n return '>'\n }\n return title\n ? `](${href} \"${state.esc(title, false)}\")`\n : `](${href})`\n },\n mixable: false,\n },\n strike_through: {\n open: '~~',\n close: '~~',\n mixable: true,\n expelEnclosingWhitespace: true,\n },\n html_mark: {\n open(_state: MarkdownSerializerState, mark: Mark) {\n return mark.attrs.openTag as string\n },\n close(_state: MarkdownSerializerState, mark: Mark) {\n return mark.attrs.closeTag as string\n },\n },\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ({\n hardBreakNodeName: 'hardbreak',\n strict: false,\n } as any),\n)\n\n/**\n * Helper: render a table row as `| cell1 | cell2 | ... |`\n *\n * Uses ProseMirror's built-in renderInline via output-buffer capture so that\n * ALL inline content (text, marks, hard breaks, math, images, etc.) is\n * serialized correctly — the same path used for headings and paragraphs.\n */\nfunction renderTableRow(state: MarkdownSerializerState, row: PmNode) {\n const cells: string[] = []\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const s = state as any\n row.forEach(cell => {\n // Cells with 'paragraph+' content: each paragraph is one \"line\" in the cell.\n // GFM cells are single-line, so join multiple paragraphs with a space.\n const parts: string[] = []\n cell.forEach(para => {\n if (para.type.name !== 'paragraph') return\n // Capture renderInline output by swapping the serializer's output buffer.\n //\n // IMPORTANT: prosemirror-markdown's text() calls write() for every line,\n // and write() calls flushClose() which resets this.closed. We must save\n // and restore BOTH out AND closed so the pending block-separator (the\n // blank line between the preceding paragraph and this table) is not\n // accidentally consumed here — it must survive until state.write('| … |')\n // fires at the end of this function, where flushClose() will emit it.\n const savedOut: string = s.out\n const savedClosed = s.closed\n s.out = ''\n s.closed = null\n state.renderInline(para)\n const piece: string = (s.out as string).replace(/\\n/g, ' ').trim()\n s.out = savedOut\n s.closed = savedClosed\n parts.push(piece)\n })\n cells.push(parts.join(' '))\n })\n state.write(`| ${cells.join(' | ')} |`)\n state.ensureNewLine()\n}\n\n/**\n * Check if a link mark represents a plain URL (autolink style).\n * If so, serialize as `<url>` instead of `[text](url)`.\n */\nfunction isPlainURL(mark: Mark, parent: PmNode, index: number, side: number): boolean {\n if (mark.attrs.title || !/^\\w+:/.test(mark.attrs.href as string)) return false\n const content = parent.child(index + (side < 0 ? -1 : 0))\n if (\n !content.isText ||\n content.text !== mark.attrs.href ||\n content.marks[content.marks.length - 1] !== mark\n ) {\n return false\n }\n if (index === (side < 0 ? 1 : parent.childCount - 1)) return true\n const next = parent.child(index + (side < 0 ? -2 : 1))\n return !mark.isInSet(next.marks)\n}\n\n// ── Public API ──────────────────────────────────────────────────\n\n/**\n * Ensure display math blocks ($$…$$) are surrounded by blank lines so\n * markdown-it-texmath parses them as math_block, not math_inline_double.\n * Without blank lines, they get absorbed into the preceding paragraph as\n * inline tokens, causing wrong rendering and roundtrip corruption.\n */\nfunction normalizeMathBlocks(text: string): string {\n if (!text.includes('$$')) return text\n\n const lines = text.split('\\n')\n const result: string[] = []\n let inFence = false\n let inMathBlock = false\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? ''\n const trimmed = line.trim()\n\n // Skip fenced code blocks\n if (!inMathBlock && /^(`{3,}|~{3,})/.test(trimmed)) {\n inFence = !inFence\n result.push(line)\n continue\n }\n if (inFence) {\n result.push(line)\n continue\n }\n\n if (trimmed === '$$') {\n if (!inMathBlock) {\n // Opening $$: ensure blank line before\n const last = result[result.length - 1]\n if (result.length > 0 && last !== undefined && last.trim() !== '') {\n result.push('')\n }\n result.push(line)\n inMathBlock = true\n } else {\n // Closing $$\n result.push(line)\n inMathBlock = false\n // Ensure blank line after if next line is non-empty\n const next = lines[i + 1]\n if (next !== undefined && next.trim() !== '') {\n result.push('')\n }\n }\n } else {\n result.push(line)\n }\n }\n\n return result.join('\\n')\n}\n\n/**\n * Normalize smart/curly quotes to straight quotes in markdown syntax positions.\n * Only targets image/link title delimiters to preserve intentional smart quotes in prose.\n * Pattern: `](url \"title\")` or `](url 'title')` with curly quotes.\n */\nfunction normalizeSmartQuotes(text: string): string {\n // Quick bail: no curly quotes at all\n if (!/[“”„‟‘’‚‛]/.test(text)) return text\n\n return text\n .replace(\n /(\\]\\([^\\n)]*\\s)“([^”\\n]*)”(\\s*\\))/g,\n (_m, pre, title, post) => `${pre}\"${title}\"${post}`,\n )\n .replace(\n /(\\]\\([^\\n)]*\\s)“([^”\\n]*)”(\\s*\\))/g,\n (_m, pre, title, post) => `${pre}\"${title}\"${post}`,\n )\n // Also handle single curly quotes as title delimiters\n .replace(\n /(\\]\\([^\\n)]*\\s)‘([^’\\n]*)’(\\s*\\))/g,\n (_m, pre, title, post) => `${pre}'${title}'${post}`,\n )\n}\n\n/**\n * Parse a markdown string into a ProseMirror document node. Never throws (§4.5).\n *\n * @param markdown Source markdown string (may contain frontmatter, math, html, etc.).\n * @param schemaArg Optional consumer schema. When provided, the returned doc's\n * `node.type` references the consumer's NodeType identities,\n * allowing it to be loaded directly into an `EditorState.create`\n * built with that same schema. Defaults to {@link defaultSchema}.\n */\nexport function parseMarkdown(markdown: string, schemaArg?: Schema): PmNode {\n const p = getParserFor(schemaArg)\n try {\n return p.parse(normalizeSmartQuotes(normalizeMathBlocks(markdown)))\n } catch (err) {\n if (typeof console !== 'undefined' && console.warn) {\n console.warn('[parseMarkdown] best-effort fallback for malformed input:', err)\n }\n return p.schema.topNodeType.createAndFill()!\n }\n}\n\nconst ASYNC_PARSE_THRESHOLD = 50_000\n\n/**\n * Async version of parseMarkdown. For large files (≥50KB), yields to the\n * event loop via setTimeout(0) so the main thread stays responsive.\n * §4.5: never rejects.\n */\nexport function parseMarkdownAsync(markdown: string, schemaArg?: Schema): Promise<PmNode> {\n const p = getParserFor(schemaArg)\n const normalized = normalizeSmartQuotes(normalizeMathBlocks(markdown))\n if (normalized.length < ASYNC_PARSE_THRESHOLD) {\n return Promise.resolve(parseMarkdown(normalized, schemaArg))\n }\n return new Promise(resolve => setTimeout(() => {\n try {\n resolve(p.parse(normalized))\n } catch {\n resolve(p.schema.topNodeType.createAndFill()!)\n }\n }, 0))\n}\n\n/**\n * Serialize a ProseMirror document node to a markdown string. Never throws (§4.5).\n */\nexport function serializeMarkdown(doc: PmNode): string {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let result = serializer.serialize(doc, ({ tightLists: true } as any))\n // Un-escape markdown link syntax that the serializer's esc() over-escapes.\n result = result.replace(/\\\\\\[([^\\\\\\[\\]]*)\\\\\\]\\(([^)]*)\\)/g, '[$1]($2)')\n // Strip zero-width spaces used as cursor targets after inline code marks.\n result = result.replace(//g, '')\n return result\n}\n","/**\n * Unified ProseMirror Schema for `@moraya/core`.\n *\n * Faithful 1:1 migration from Moraya desktop `src/lib/editor/schema.ts`\n * with the following DI changes (v0.60.0-pre §F2.5):\n * - All Tauri IPC `read_file_binary` / `plugin-http` calls in image / media\n * loaders are replaced by consumer-injected `MediaResolver` methods.\n * - Schema NodeSpecs that depend on the resolver (image, html_inline) are\n * built inside `createSchema(config)` factory body, capturing `config`\n * in closures for `toDOM`. Other NodeSpecs are pure data.\n * - Module-level `documentBaseDir` + `setDocumentBaseDir` is preserved\n * (pure string state, not Tauri-coupled).\n * - Per §6.1.1: this module does NOT export the default schema. It is\n * used internally by parseMarkdown / serializeMarkdown only.\n *\n * Nodes (23): doc, text, paragraph, heading, blockquote, code_block,\n * horizontal_rule, bullet_list, ordered_list, list_item, image,\n * hardbreak, html_block, html_inline, table, table_header_row, table_row,\n * table_header, table_cell, math_inline, math_block,\n * defList, defListTerm, defListDescription\n *\n * Marks (6): html_mark, strong, em, code, link, strike_through\n */\n\nimport { Schema, Fragment } from 'prosemirror-model'\nimport type { NodeSpec, MarkSpec, Node as PmNode } from 'prosemirror-model'\nimport katex from 'katex'\nimport {\n type SchemaConfig,\n type MediaResolver,\n isNullMediaResolver,\n NULL_MEDIA_RESOLVER_SENTINEL,\n type NullMediaResolver,\n} from './types'\n\n// ── Helpers (pure DOM / string ops, no host coupling) ────────────\n\n/** Extract a quoted attribute value from an HTML tag string. */\nfunction extractHtmlAttr(html: string, name: string): string | null {\n const re = new RegExp(`${name}\\\\s*=\\\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\\\s>]+))`, 'i')\n const m = html.match(re)\n return m ? (m[1] ?? m[2] ?? m[3] ?? null) : null\n}\n\n/** Extract all attributes from an HTML tag string as key-value pairs. */\nfunction extractAllHtmlAttrs(html: string): Record<string, string> {\n const attrs: Record<string, string> = {}\n const re = /([a-zA-Z_][\\w:.-]*)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>]+))/gi\n let m: RegExpExecArray | null\n while ((m = re.exec(html)) !== null) {\n const name = m[1]\n if (!name) continue\n attrs[name.toLowerCase()] = m[2] ?? m[3] ?? m[4] ?? ''\n }\n return attrs\n}\n\n/** Replace element content with broken-image icon + source code display. */\nfunction showBrokenImage(container: HTMLElement, sourceText: string): void {\n container.textContent = ''\n container.className = (container.className.replace(/\\bhtml-img-wrapper\\b|\\bimage-node\\b/, '').trim()\n + ' broken-image').trim()\n const icon = document.createElement('span')\n icon.className = 'broken-image-icon'\n container.appendChild(icon)\n const code = document.createElement('code')\n code.className = 'broken-image-src'\n code.textContent = sourceText\n container.appendChild(code)\n}\n\n/** Convert HTML tag attributes to CSS inline styles for visual rendering. */\nfunction htmlTagToStyle(openTag: string): string {\n const tagMatch = openTag.match(/^<([a-zA-Z][a-zA-Z0-9]*)/)\n if (!tagMatch || !tagMatch[1]) return ''\n const tagName = tagMatch[1].toLowerCase()\n switch (tagName) {\n case 'font': {\n const parts: string[] = []\n const color = extractHtmlAttr(openTag, 'color')\n if (color) parts.push(`color: ${color}`)\n const size = extractHtmlAttr(openTag, 'size')\n if (size) {\n const sizeMap: Record<string, string> = {\n '1': '0.63em', '2': '0.82em', '3': '1em', '4': '1.13em',\n '5': '1.5em', '6': '2em', '7': '3em',\n }\n parts.push(`font-size: ${sizeMap[size] || size}`)\n }\n const face = extractHtmlAttr(openTag, 'face')\n if (face) parts.push(`font-family: ${face}`)\n return parts.join('; ')\n }\n case 'span':\n case 'div':\n return extractHtmlAttr(openTag, 'style') || ''\n default:\n return ''\n }\n}\n\n/**\n * Base directory for resolving relative image paths. Set by the consumer when\n * a document is opened so `<img src=\"./foo.png\">` can be resolved. Pure\n * string state — not Tauri-coupled.\n */\nlet documentBaseDir = ''\n\n/** Update the base directory used to resolve relative image paths. */\nexport function setDocumentBaseDir(dir: string): void {\n documentBaseDir = dir\n}\n\n/** Read the current base dir. Exposed for consumers that need to coordinate (e.g. tests). */\nexport function getDocumentBaseDir(): string {\n return documentBaseDir\n}\n\n/** Check if a path is a local file path (absolute Unix or Windows path). */\nfunction isAbsoluteFilePath(src: string): boolean {\n if (!src) return false\n if (src.startsWith('/') && !src.startsWith('//')) return true\n if (/^[A-Z]:[\\\\/]/i.test(src)) return true\n return false\n}\n\n/** Check if a src is a relative file path (not a URL scheme). */\nfunction isRelativePath(src: string): boolean {\n if (!src) return false\n if (/^(https?:|data:|blob:|javascript:|vbscript:|tauri:|\\/\\/)/i.test(src)) return false\n if (src.startsWith('/') || /^[A-Z]:[\\\\/]/i.test(src)) return false\n return true\n}\n\n/** Resolve a relative path against documentBaseDir to an absolute path. */\nfunction resolveRelativePath(src: string): string {\n if (!documentBaseDir) return src\n let rel = src.replace(/^\\.\\//, '')\n const sep = documentBaseDir.includes('\\\\') ? '\\\\' : '/'\n let base = documentBaseDir.endsWith(sep) ? documentBaseDir.slice(0, -1) : documentBaseDir\n while (rel.startsWith('../') || rel.startsWith('..\\\\')) {\n rel = rel.slice(3)\n const lastSep = base.lastIndexOf(sep)\n if (lastSep > 0) base = base.slice(0, lastSep)\n }\n return `${base}${sep}${rel}`\n}\n\n// ── Image / media DI helpers ────────────────────────────────────\n\n/**\n * Apply MediaResolver-loaded URL to an <img> element. Decodes URL-encoded\n * paths first (markdown parsers URL-encode non-ASCII; filesystem expects\n * actual Unicode characters).\n */\nfunction loadLocalImageSrc(\n img: HTMLImageElement,\n src: string,\n mediaResolver: MediaResolver\n): void {\n let path: string\n try { path = decodeURIComponent(src) } catch { path = src }\n\n mediaResolver.loadLocalImage(path).then((url) => {\n if (url) img.src = url\n else img.dispatchEvent(new Event('error'))\n }).catch(() => {\n img.dispatchEvent(new Event('error'))\n })\n}\n\n/** Apply MediaResolver-loaded URL to a <video>/<audio>/<source> element. */\nfunction setMediaSrc(\n el: HTMLMediaElement | HTMLSourceElement,\n src: string,\n mediaResolver: MediaResolver\n): void {\n if (isAbsoluteFilePath(src)) {\n mediaResolver.loadLocalMedia(src).then((url) => {\n if (!url) return\n el.src = url\n if (el instanceof HTMLMediaElement) el.load()\n }).catch(() => { /* media load failed silently */ })\n } else if (isRelativePath(src)) {\n mediaResolver.loadLocalMedia(resolveRelativePath(src)).then((url) => {\n if (!url) return\n el.src = url\n if (el instanceof HTMLMediaElement) el.load()\n }).catch(() => { /* media load failed silently */ })\n } else if (/^https?:\\/\\//i.test(src)) {\n // For <video>, set src directly so the browser can issue HTTP range requests\n // and stream playback. Tauri-HTTP-to-blob proxy used for <audio> would\n // download entire file before any frame plays — fine for a few-MB audio,\n // fatal for 10s-of-MB to GB video.\n if (el instanceof HTMLVideoElement) {\n el.src = src\n el.load()\n } else {\n mediaResolver.loadRemoteMedia(src).then((url) => {\n if (!url) return\n el.src = url\n if (el instanceof HTMLMediaElement) el.load()\n }).catch(() => { /* fetch failed */ })\n }\n } else {\n el.src = src\n }\n}\n\n/**\n * Create a <video> or <audio> element from raw HTML. Attributes from the\n * original tag are preserved. Child <source> elements are extracted.\n * Event handler attributes (on*) are stripped for XSS prevention.\n */\nfunction createMediaElement(\n tagName: 'video' | 'audio',\n value: string,\n mediaResolver: MediaResolver\n): HTMLElement {\n const wrapper = document.createElement('span')\n wrapper.dataset.type = 'html-inline'\n wrapper.dataset.value = value\n wrapper.className = 'html-media-wrapper'\n wrapper.contentEditable = 'false'\n\n const el = document.createElement(tagName)\n // Stop ProseMirror from grabbing mousedown for atom-node selection — the\n // browser's native <audio>/<video> controls (play, scrub, volume) must\n // receive events directly, otherwise clicks select the node instead of\n // triggering playback.\n const stopForControls = (ev: Event) => ev.stopPropagation()\n el.addEventListener('mousedown', stopForControls)\n el.addEventListener('click', stopForControls)\n el.addEventListener('pointerdown', stopForControls)\n\n const openTagMatch = value.match(new RegExp(`^<${tagName}\\\\b[^>]*>`, 'i'))\n const openTag = openTagMatch ? openTagMatch[0] : ''\n const attrs = extractAllHtmlAttrs(openTag)\n\n for (const [key, val] of Object.entries(attrs)) {\n if (key === 'src') continue\n if (key.startsWith('on')) continue\n el.setAttribute(key, val)\n }\n\n const strippedTag = openTag.replace(/=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/g, '')\n const boolAttrs = ['controls', 'autoplay', 'loop', 'muted', 'playsinline']\n for (const attr of boolAttrs) {\n if (!(attr in attrs) && new RegExp(`\\\\b${attr}\\\\b`, 'i').test(strippedTag)) {\n el.setAttribute(attr, '')\n }\n }\n\n if (tagName === 'audio' && !attrs.preload) {\n el.setAttribute('preload', 'auto')\n }\n\n const sourceRe = /<source\\b[^>]*\\/?>/gi\n let srcMatch: RegExpExecArray | null\n while ((srcMatch = sourceRe.exec(value)) !== null) {\n const srcAttrs = extractAllHtmlAttrs(srcMatch[0])\n if (!srcAttrs.src) continue\n const source = document.createElement('source')\n if (srcAttrs.type) source.type = srcAttrs.type\n setMediaSrc(source, srcAttrs.src, mediaResolver)\n el.appendChild(source)\n }\n\n if (attrs.src) {\n setMediaSrc(el, attrs.src, mediaResolver)\n }\n\n wrapper.appendChild(el)\n return wrapper\n}\n\n// ── Pure NodeSpecs (no MediaResolver coupling) ─────────────────\n\nconst doc: NodeSpec = {\n content: 'block+',\n}\n\nconst text: NodeSpec = { group: 'inline' }\n\nconst paragraph: NodeSpec = {\n content: 'inline*',\n group: 'block',\n parseDOM: [{ tag: 'p' }],\n toDOM() { return ['p', 0] },\n}\n\nconst heading: NodeSpec = {\n attrs: {\n id: { default: '' },\n level: { default: 1 },\n },\n content: 'inline*',\n group: 'block',\n defining: true,\n parseDOM: [1, 2, 3, 4, 5, 6].map(level => ({\n tag: `h${level}`,\n getAttrs(dom: HTMLElement) {\n return { level, id: dom.getAttribute('id') || '' }\n },\n })),\n toDOM(node) {\n const attrs: Record<string, string> = {}\n if (node.attrs.id) attrs.id = node.attrs.id as string\n return [`h${node.attrs.level as number}`, attrs, 0]\n },\n}\n\nconst blockquote: NodeSpec = {\n content: 'block+',\n group: 'block',\n defining: true,\n parseDOM: [{ tag: 'blockquote' }],\n toDOM() { return ['blockquote', 0] },\n}\n\nconst code_block: NodeSpec = {\n content: 'text*',\n group: 'block',\n marks: '',\n defining: true,\n code: true,\n attrs: {\n language: { default: 'text' },\n },\n parseDOM: [{\n tag: 'pre',\n preserveWhitespace: 'full' as const,\n getAttrs(dom: HTMLElement) {\n return { language: dom.dataset.language || 'text' }\n },\n }],\n toDOM(node) {\n return ['pre', { 'data-language': (node.attrs.language as string) || undefined }, ['code', 0]]\n },\n}\n\nconst horizontal_rule: NodeSpec = {\n group: 'block',\n parseDOM: [{ tag: 'hr' }],\n toDOM() { return ['hr'] },\n}\n\nconst bullet_list: NodeSpec = {\n content: 'list_item+',\n group: 'block',\n parseDOM: [{ tag: 'ul' }],\n toDOM() { return ['ul', 0] },\n}\n\nconst ordered_list: NodeSpec = {\n content: 'list_item+',\n group: 'block',\n attrs: {\n order: { default: 1 },\n },\n parseDOM: [{\n tag: 'ol',\n getAttrs(dom: HTMLElement) {\n return { order: dom.hasAttribute('start') ? +(dom.getAttribute('start') || 1) : 1 }\n },\n }],\n toDOM(node) {\n return node.attrs.order === 1\n ? ['ol', 0]\n : ['ol', { start: node.attrs.order as number }, 0]\n },\n}\n\nconst list_item: NodeSpec = {\n content: 'paragraph block*',\n group: 'listItem',\n defining: true,\n attrs: {\n label: { default: '•' },\n listType: { default: 'bullet' },\n spread: { default: 'true' },\n checked: { default: null },\n },\n parseDOM: [\n {\n tag: 'li[data-item-type=\"task\"]',\n getAttrs(dom: HTMLElement) {\n return {\n label: dom.dataset.label,\n listType: dom.dataset.listType,\n spread: dom.dataset.spread,\n checked: dom.dataset.checked ? dom.dataset.checked === 'true' : null,\n }\n },\n },\n {\n tag: 'li',\n getAttrs(dom: HTMLElement) {\n return {\n label: dom.dataset.label || '•',\n listType: dom.dataset.listType || 'bullet',\n spread: dom.dataset.spread || 'true',\n }\n },\n },\n ],\n toDOM(node) {\n if (node.attrs.checked != null) {\n return ['li', {\n 'data-item-type': 'task',\n 'data-label': node.attrs.label as string,\n 'data-list-type': node.attrs.listType as string,\n 'data-spread': node.attrs.spread as string,\n 'data-checked': String(node.attrs.checked),\n }, 0]\n }\n return ['li', {\n 'data-label': node.attrs.label as string,\n 'data-list-type': node.attrs.listType as string,\n 'data-spread': node.attrs.spread as string,\n }, 0]\n },\n}\n\nconst hardbreak: NodeSpec = {\n inline: true,\n group: 'inline',\n selectable: false,\n attrs: {\n isInline: { default: false },\n },\n parseDOM: [\n { tag: 'br' },\n {\n tag: 'span[data-type=\"hardbreak\"]',\n getAttrs() { return { isInline: true } },\n },\n ],\n toDOM() {\n // Always render as span with newline to maintain consistent cursor size.\n // leafText() ensures it serializes correctly.\n return ['span', { 'data-type': 'hardbreak', 'class': 'hardbreak-marker' }, '\\n']\n },\n leafText() { return '\\n' },\n}\n\nconst html_block: NodeSpec = {\n content: 'text*',\n group: 'block',\n marks: '',\n code: true,\n defining: true,\n parseDOM: [{\n tag: 'div[data-type=\"html\"]',\n preserveWhitespace: 'full' as const,\n }],\n toDOM() {\n return ['div', { 'data-type': 'html' }, ['pre', 0]]\n },\n}\n\n// ── Table NodeSpecs ─────────────────────────────────────────────\n\nconst table: NodeSpec = {\n content: 'table_header_row table_row+',\n group: 'block',\n tableRole: 'table',\n isolating: true,\n parseDOM: [{ tag: 'table' }],\n toDOM() { return ['table', ['tbody', 0]] },\n}\n\nconst table_header_row: NodeSpec = {\n content: '(table_header)*',\n tableRole: 'row',\n parseDOM: [\n { tag: 'tr[data-is-header]' },\n {\n tag: 'tr',\n getAttrs(dom: HTMLElement) {\n const hasHeader = dom.querySelector('th')\n return hasHeader ? {} : false\n },\n },\n ],\n toDOM() { return ['tr', { 'data-is-header': 'true' }, 0] },\n}\n\nconst table_row: NodeSpec = {\n content: '(table_cell)*',\n tableRole: 'row',\n parseDOM: [{ tag: 'tr' }],\n toDOM() { return ['tr', 0] },\n}\n\nconst table_header: NodeSpec = {\n content: 'paragraph+',\n tableRole: 'header_cell',\n attrs: {\n alignment: { default: 'left' },\n colspan: { default: 1 },\n rowspan: { default: 1 },\n colwidth: { default: null },\n },\n isolating: true,\n parseDOM: [{\n tag: 'th',\n getAttrs(dom: HTMLElement) {\n return {\n alignment: dom.style.textAlign || 'left',\n colspan: Number(dom.getAttribute('colspan') || 1),\n rowspan: Number(dom.getAttribute('rowspan') || 1),\n colwidth: null,\n }\n },\n }],\n toDOM(node) {\n return ['th', { style: `text-align: ${(node.attrs.alignment as string) || 'left'}` }, 0]\n },\n}\n\nconst table_cell: NodeSpec = {\n content: 'paragraph+',\n tableRole: 'cell',\n attrs: {\n alignment: { default: 'left' },\n colspan: { default: 1 },\n rowspan: { default: 1 },\n colwidth: { default: null },\n },\n isolating: true,\n parseDOM: [{\n tag: 'td',\n getAttrs(dom: HTMLElement) {\n return {\n alignment: dom.style.textAlign || 'left',\n colspan: Number(dom.getAttribute('colspan') || 1),\n rowspan: Number(dom.getAttribute('rowspan') || 1),\n colwidth: null,\n }\n },\n }],\n toDOM(node) {\n return ['td', { style: `text-align: ${(node.attrs.alignment as string) || 'left'}` }, 0]\n },\n}\n\n// ── Math NodeSpecs (KaTeX) ──────────────────────────────────────\n\nconst math_inline: NodeSpec = {\n group: 'inline',\n content: 'text*',\n inline: true,\n atom: true,\n parseDOM: [{\n tag: 'span[data-type=\"math_inline\"]',\n getContent(dom: globalThis.Node, schema: Schema) {\n if (!(dom instanceof HTMLElement)) return Fragment.empty\n const value = dom.dataset.value ?? ''\n if (!value) return Fragment.empty\n return Fragment.from(schema.text(value))\n },\n }],\n toDOM(node) {\n const code = node.textContent\n const dom = document.createElement('span')\n dom.dataset.type = 'math_inline'\n dom.dataset.value = code\n try {\n katex.render(code, dom)\n } catch {\n // §4.4 KaTeX error contract: render fallback marker; serializer reads data-tex attr.\n dom.textContent = code\n dom.classList.add('math-error')\n dom.setAttribute('data-math-type', 'inline')\n }\n return dom\n },\n}\n\nconst math_block: NodeSpec = {\n content: 'text*',\n group: 'block',\n marks: '',\n defining: true,\n atom: true,\n isolating: true,\n attrs: {\n value: { default: '' },\n },\n parseDOM: [{\n tag: 'div[data-type=\"math_block\"]',\n preserveWhitespace: 'full' as const,\n getAttrs(dom: HTMLElement) {\n return { value: dom.dataset.value ?? '' }\n },\n }],\n toDOM(node) {\n const code = node.attrs.value as string\n const dom = document.createElement('div')\n dom.dataset.type = 'math_block'\n dom.dataset.value = code\n try {\n katex.render(code, dom, { displayMode: true })\n } catch {\n dom.textContent = code\n dom.classList.add('math-error')\n dom.setAttribute('data-math-type', 'block')\n }\n return dom\n },\n}\n\n// ── Definition List NodeSpecs ───────────────────────────────────\n\nconst defList: NodeSpec = {\n content: '(defListTerm | defListDescription)+',\n group: 'block',\n defining: true,\n parseDOM: [{ tag: 'dl' }],\n toDOM() { return ['dl', { class: 'definition-list' }, 0] },\n}\n\nconst defListTerm: NodeSpec = {\n content: 'inline*',\n group: 'block',\n defining: true,\n parseDOM: [{ tag: 'dt' }],\n toDOM() { return ['dt', 0] },\n}\n\nconst defListDescription: NodeSpec = {\n content: 'block+',\n group: 'block',\n defining: true,\n parseDOM: [{ tag: 'dd' }],\n toDOM() { return ['dd', 0] },\n}\n\n// ── Marks ───────────────────────────────────────────────────────\n\nconst strong: MarkSpec = {\n parseDOM: [\n {\n tag: 'b',\n getAttrs(dom: HTMLElement) {\n return dom.style.fontWeight !== 'normal' && null\n },\n },\n { tag: 'strong' },\n {\n style: 'font-weight',\n getAttrs(value: string) {\n return /^(bold(er)?|[5-9]\\d{2,})$/.test(value) && null\n },\n },\n ],\n toDOM() { return ['strong', 0] },\n}\n\nconst em: MarkSpec = {\n parseDOM: [\n { tag: 'i' },\n { tag: 'em' },\n {\n style: 'font-style',\n getAttrs(value: string) {\n return value === 'italic' && null\n },\n },\n ],\n toDOM() { return ['em', 0] },\n}\n\nconst code: MarkSpec = {\n priority: 100,\n code: true,\n inclusive: false,\n parseDOM: [{ tag: 'code' }],\n toDOM() { return ['code', 0] },\n}\n\nconst link: MarkSpec = {\n attrs: {\n href: {},\n title: { default: null },\n },\n inclusive: false,\n parseDOM: [{\n tag: 'a[href]',\n getAttrs(dom: HTMLElement) {\n return {\n href: dom.getAttribute('href'),\n title: dom.getAttribute('title'),\n }\n },\n }],\n toDOM(mark) {\n const attrs: Record<string, string> = { href: mark.attrs.href as string }\n if (mark.attrs.title) attrs.title = mark.attrs.title as string\n return ['a', attrs, 0]\n },\n}\n\nconst strike_through: MarkSpec = {\n parseDOM: [\n { tag: 'del' },\n { tag: 's' },\n {\n style: 'text-decoration',\n getAttrs(value: string) {\n return value === 'line-through' && null\n },\n },\n ],\n toDOM() { return ['del', 0] },\n}\n\nconst html_mark: MarkSpec = {\n attrs: {\n openTag: { default: '' },\n closeTag: { default: '' },\n },\n excludes: '', // Allow nesting multiple html_marks (e.g., <font><u>text</u></font>)\n parseDOM: [{\n tag: '[data-type=\"html-mark\"]',\n getAttrs(dom: HTMLElement) {\n return {\n openTag: dom.dataset.openTag ?? '',\n closeTag: dom.dataset.closeTag ?? '',\n }\n },\n }],\n toDOM(mark) {\n const openTag = mark.attrs.openTag as string\n const tagMatch = openTag.match(/^<([a-zA-Z][a-zA-Z0-9]*)/)\n const tagName = tagMatch && tagMatch[1] ? tagMatch[1].toLowerCase() : 'span'\n\n const attrs: Record<string, string> = {\n 'data-type': 'html-mark',\n 'data-open-tag': openTag,\n 'data-close-tag': mark.attrs.closeTag as string,\n }\n\n const semanticTags = ['sub', 'sup', 'u', 'ins', 'mark', 'small', 'big', 'kbd', 'abbr']\n if (semanticTags.includes(tagName)) {\n return [tagName, attrs, 0]\n }\n\n const style = htmlTagToStyle(openTag)\n if (style) attrs.style = style\n return ['span', attrs, 0]\n },\n}\n\n// ── Resolver-coupled NodeSpec builders (image, html_inline) ─────\n\n/**\n * Build the `image` NodeSpec with a closed-over MediaResolver. Local-path\n * images are loaded via `mediaResolver.loadLocalImage`; remote URLs are\n * applied directly so the browser can stream / cache normally.\n */\nfunction buildImageNodeSpec(mediaResolver: MediaResolver): NodeSpec {\n return {\n inline: true,\n group: 'inline',\n selectable: true,\n draggable: true,\n marks: '',\n atom: true,\n defining: true,\n isolating: true,\n attrs: {\n src: { default: '' },\n alt: { default: '' },\n title: { default: '' },\n },\n parseDOM: [{\n tag: 'img[src]',\n getAttrs(dom: HTMLElement) {\n return {\n src: dom.getAttribute('src') || '',\n alt: dom.getAttribute('alt') || '',\n title: dom.getAttribute('title') || dom.getAttribute('alt') || '',\n }\n },\n }],\n toDOM(node) {\n const container = document.createElement('span')\n container.className = 'image-node'\n\n const img = document.createElement('img')\n if (node.attrs.alt) img.alt = node.attrs.alt as string\n if (node.attrs.title) img.title = node.attrs.title as string\n\n // Apply width from title attr (e.g. title=\"width=70%\")\n const titleStr = (node.attrs.title || '') as string\n const widthMatch = titleStr.match(/^width=(\\d+%?)$/)\n const widthVal = widthMatch?.[1]\n if (widthVal) {\n img.style.width = widthVal.includes('%') ? widthVal : `${widthVal}px`\n img.style.maxWidth = 'none'\n }\n\n img.onerror = () => {\n const alt = node.attrs.alt ? `![${node.attrs.alt}]` : '![]'\n const title = node.attrs.title ? ` \"${node.attrs.title}\"` : ''\n showBrokenImage(container, `${alt}(${node.attrs.src}${title})`)\n }\n\n const src = node.attrs.src as string\n if (isAbsoluteFilePath(src)) {\n loadLocalImageSrc(img, src, mediaResolver)\n } else if (isRelativePath(src)) {\n loadLocalImageSrc(img, resolveRelativePath(src), mediaResolver)\n } else {\n img.src = src\n }\n\n container.appendChild(img)\n return container\n },\n }\n}\n\n/**\n * Build the `html_inline` NodeSpec with a closed-over MediaResolver. Inline\n * <img> / <video> / <audio> tags route their src through the resolver; other\n * inline HTML (<font>, <br>, etc.) renders as a plain span carrying its\n * verbatim source for byte-stable roundtrip.\n */\nfunction buildHtmlInlineNodeSpec(mediaResolver: MediaResolver): NodeSpec {\n return {\n group: 'inline',\n inline: true,\n atom: true,\n attrs: {\n value: { default: '' },\n },\n parseDOM: [{\n tag: 'span[data-type=\"html-inline\"]',\n getAttrs(dom: HTMLElement) {\n return { value: dom.dataset.value ?? '' }\n },\n }],\n toDOM(node) {\n const value = node.attrs.value as string\n\n if (/^<img\\s/i.test(value)) {\n const wrapper = document.createElement('span')\n wrapper.dataset.type = 'html-inline'\n wrapper.dataset.value = value\n wrapper.className = 'html-img-wrapper'\n\n const attrs = extractAllHtmlAttrs(value)\n const src = attrs.src || ''\n if (src) {\n const img = document.createElement('img')\n for (const [key, val] of Object.entries(attrs)) {\n if (key === 'src') continue\n if (key === 'onerror' || key === 'onload' || key.startsWith('on')) continue\n img.setAttribute(key, val)\n }\n img.onerror = () => {\n showBrokenImage(wrapper, value)\n }\n if (isAbsoluteFilePath(src)) {\n loadLocalImageSrc(img, src, mediaResolver)\n } else if (isRelativePath(src)) {\n loadLocalImageSrc(img, resolveRelativePath(src), mediaResolver)\n } else {\n img.src = src\n }\n wrapper.appendChild(img)\n } else {\n showBrokenImage(wrapper, value)\n }\n return wrapper\n }\n\n if (/^<video\\b/i.test(value)) return createMediaElement('video', value, mediaResolver)\n if (/^<audio\\b/i.test(value)) return createMediaElement('audio', value, mediaResolver)\n\n // Default: invisible span for other inline HTML (<font>, <br>, etc.)\n return ['span', { 'data-type': 'html-inline', 'data-value': value }]\n },\n }\n}\n\n// ── Schema assembly ─────────────────────────────────────────────\n\nfunction buildNodes(mediaResolver: MediaResolver): Record<string, NodeSpec> {\n return {\n doc,\n text,\n paragraph,\n heading,\n blockquote,\n code_block,\n horizontal_rule,\n bullet_list,\n ordered_list,\n list_item,\n image: buildImageNodeSpec(mediaResolver),\n hardbreak,\n html_block,\n html_inline: buildHtmlInlineNodeSpec(mediaResolver),\n table,\n table_header_row,\n table_row,\n table_header,\n table_cell,\n math_inline,\n math_block,\n defList,\n defListTerm,\n defListDescription,\n }\n}\n\nconst marks: Record<string, MarkSpec> = {\n html_mark,\n strong,\n em,\n code,\n link,\n strike_through,\n}\n\n// ── Internal default schema (parser/serializer fallback) ────────\n\nconst nullMediaResolver: NullMediaResolver = {\n [NULL_MEDIA_RESOLVER_SENTINEL]: true,\n async loadLocalImage() { return '' },\n async loadLocalMedia() { return '' },\n async loadRemoteMedia(url: string) { return url },\n}\n\n/**\n * Internal default schema (uses {@link nullMediaResolver}).\n * Used by parseMarkdown / serializeMarkdown when no real consumer schema\n * is available. Per §6.1.1 NOT exported via index.ts — consumers must call\n * createSchema(config) with a real MediaResolver.\n */\nexport const defaultSchema = new Schema({\n nodes: buildNodes(nullMediaResolver),\n marks,\n})\n\n// ── Public factory ──────────────────────────────────────────────\n\n/** Cached config-keyed schemas. Reuses Schema instances when consumers call createSchema with the same config. */\nconst schemaCache = new WeakMap<MediaResolver, Schema>()\n\n/**\n * Create a ProseMirror Schema with consumer-injected dependencies.\n *\n * @throws TypeError if `config.mediaResolver` is missing or is the internal\n * nullMediaResolver sentinel.\n */\nexport function createSchema(config: SchemaConfig): Schema {\n if (!config || typeof config !== 'object') {\n throw new TypeError('@moraya/core: createSchema() requires a config object with a MediaResolver')\n }\n if (!config.mediaResolver) {\n throw new TypeError('@moraya/core: createSchema() requires a MediaResolver')\n }\n if (isNullMediaResolver(config.mediaResolver)) {\n throw new TypeError(\n \"@moraya/core: do not pass nullMediaResolver to createSchema(). That instance is reserved for parseMarkdown/serializeMarkdown internal use only. Provide a real MediaResolver implementation (e.g. BrowserMediaResolver from '@moraya/core/adapters/browser-media-resolver').\"\n )\n }\n const cached = schemaCache.get(config.mediaResolver)\n if (cached) return cached\n const schema = new Schema({\n nodes: buildNodes(config.mediaResolver),\n marks,\n })\n schemaCache.set(config.mediaResolver, schema)\n return schema\n}\n\nexport type { SchemaConfig, PmNode }\n","/**\n * Dependency-injection interfaces for `@moraya/core`.\n *\n * These 4 interfaces are the **only** boundary between the core package\n * (host-agnostic, pure ESM) and the consumer environment (Tauri / browser /\n * mobile WebView). All Tauri / DOM-specific APIs that previously lived inside\n * the editor source are now accessed through these injected implementations.\n *\n * See iteration spec: `moraya/docs/iterations/v0.60.0-pre-shared-markdown-core.md` §3.3.\n */\n\n/**\n * Loads local / remote media as a URL usable in `img.src` / `video.src` / etc.\n * Typically returns blob: URLs for local files (cached by the implementation).\n */\nexport interface MediaResolver {\n /** Read a local image file by absolute path; return a blob: URL (implementation caches internally). */\n loadLocalImage(absolutePath: string): Promise<string>\n\n /** Read a local audio/video file by absolute path; return a blob: URL. */\n loadLocalMedia(absolutePath: string): Promise<string>\n\n /**\n * Fetch a remote media URL.\n * Browser implementations may return the original URL directly.\n * Tauri implementations proxy through plugin-http to bypass WKWebView mixed-content.\n */\n loadRemoteMedia(url: string): Promise<string>\n}\n\n/** Opens an external link (browser = `window.open`; Tauri = plugin-opener; mobile = bridge). */\nexport interface LinkOpener {\n open(url: string): void\n}\n\n/**\n * Custom code-block renderer plugin (WaveDrom / D2 / Excalidraw, etc.).\n * Loaded dynamically via {@link RendererRegistry}.\n */\nexport interface RendererPluginModule {\n /**\n * Render `source` into `container` (async supported).\n * The caller guarantees `container` is already mounted in the DOM.\n * The implementation must clear any old content of `container` before rendering.\n *\n * Error propagation: if this method throws or rejects, the core captures the error\n * inside the NodeView (no rethrow) and inserts a fallback DOM:\n * `<div class=\"renderer-error\" data-language=\"${lang}\" data-error=\"${msg}\">[Renderer ${lang} failed]</div>`\n * Roundtrip preservation: the serializer reads the original fenced source from\n * `node.attrs.source` (NOT from the fallback DOM), so the fenced code block\n * round-trips byte-stably even if rendering fails.\n */\n render(\n source: string,\n container: HTMLElement,\n options?: { theme?: string; baseUrl?: string }\n ): void | Promise<void>\n\n /**\n * Destroy the rendered content within `container` (optional).\n * Called from NodeView.destroy() to free canvas / SVG / event listeners.\n *\n * Error propagation: if this method throws, the core catches and `console.warn`s\n * (does not report to Sentry, does not rethrow). This is a cleanup path; throwing\n * here would block NodeView destruction and cause memory leaks.\n */\n destroy?(container: HTMLElement): void\n}\n\n/** Code-block custom renderer registry. Implemented by consumers (Moraya desktop / Web). */\nexport interface RendererRegistry {\n /** Whether a renderer is registered for the given language identifier. */\n has(language: string): boolean\n\n /** Asynchronously load the renderer module (consumer handles CDN / local import). */\n load(language: string): Promise<RendererPluginModule>\n\n /**\n * Snapshot of currently registered renderer versions (language → version string).\n * Used by code-block-view to invalidate cached renders when versions change.\n */\n readonly versions: Readonly<Record<string, string>>\n}\n\n/**\n * Platform behavior parameters (carries the editor-props-plugin DI from §F2.6).\n * Desktop injects Tauri / OS truth; Web uses browser detection; mobile bridges fill.\n */\nexport interface Platform {\n /**\n * Absolute path of the currently open document (used for relative-image-path\n * resolution). Returns `null` when no document is open.\n */\n getCurrentFilePath: () => string | null\n\n /**\n * Whether the user is on macOS. Affects Option-key handling, Cmd vs Ctrl,\n * emoji popup behavior, etc.\n */\n isMacOS: boolean\n}\n\n/** SchemaConfig (re-exported from schema.ts for convenience). */\nexport interface SchemaConfig {\n mediaResolver: MediaResolver\n rendererRegistry?: RendererRegistry\n linkOpener?: LinkOpener\n}\n\n// ===== Internal sentinel for misuse detection (v0.60.0-pre §6.1.1) =====\n\n/**\n * Internal symbol-tagged null MediaResolver used by core for parseMarkdown /\n * serializeMarkdown internal fallback. Consumers must NOT pass this to\n * createSchema(); doing so throws with a descriptive error.\n */\nexport const NULL_MEDIA_RESOLVER_SENTINEL = Symbol('@moraya/core:null-media-resolver')\n\nexport interface NullMediaResolver extends MediaResolver {\n readonly [NULL_MEDIA_RESOLVER_SENTINEL]: true\n}\n\nexport function isNullMediaResolver(r: MediaResolver): r is NullMediaResolver {\n return (r as NullMediaResolver)[NULL_MEDIA_RESOLVER_SENTINEL] === true\n}\n"],"mappings":"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","names":["Fragment","code","code","text","text","doc","Fragment","code","paragraph"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Plugin } from 'prosemirror-state';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight emoji plugin.
|
|
5
|
+
*
|
|
6
|
+
* Converts emoji shortcodes like `:smile:` → 😄 using `node-emoji`.
|
|
7
|
+
* Uses system native emoji rendering (no twemoji images).
|
|
8
|
+
*
|
|
9
|
+
* - Typing `:smile:` auto-converts to 😄 when the closing `:` is typed.
|
|
10
|
+
*
|
|
11
|
+
* `node-emoji` is declared as a peer dep so consumers control its version.
|
|
12
|
+
* The conversion is purely textual via `view.state.schema.text(emoji)` so the
|
|
13
|
+
* plugin works against any consumer-injected schema.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ProseMirror plugin that converts `:shortcode:` to native emoji on typing.
|
|
18
|
+
*/
|
|
19
|
+
declare function createEmojiPlugin(): Plugin;
|
|
20
|
+
|
|
21
|
+
export { createEmojiPlugin };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/plugins/emoji.ts
|
|
2
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
|
+
import { get as getEmoji } from "node-emoji";
|
|
4
|
+
var emojiPluginKey = new PluginKey("moraya-emoji");
|
|
5
|
+
function createEmojiPlugin() {
|
|
6
|
+
return new Plugin({
|
|
7
|
+
key: emojiPluginKey,
|
|
8
|
+
props: {
|
|
9
|
+
handleTextInput(view, from, to, text) {
|
|
10
|
+
if (text !== ":") return false;
|
|
11
|
+
const { state } = view;
|
|
12
|
+
const $pos = state.doc.resolve(from);
|
|
13
|
+
const textBefore = $pos.parent.textBetween(
|
|
14
|
+
0,
|
|
15
|
+
$pos.parentOffset,
|
|
16
|
+
void 0,
|
|
17
|
+
"\uFFFC"
|
|
18
|
+
);
|
|
19
|
+
const lastColon = textBefore.lastIndexOf(":");
|
|
20
|
+
if (lastColon === -1) return false;
|
|
21
|
+
const shortcode = textBefore.slice(lastColon + 1);
|
|
22
|
+
if (!shortcode || !/^[a-zA-Z0-9_+-]+$/.test(shortcode)) return false;
|
|
23
|
+
const emoji = getEmoji(shortcode);
|
|
24
|
+
if (!emoji) return false;
|
|
25
|
+
const openColonOffset = textBefore.length - lastColon;
|
|
26
|
+
const replaceFrom = from - openColonOffset;
|
|
27
|
+
const tr = state.tr.replaceWith(
|
|
28
|
+
replaceFrom,
|
|
29
|
+
to,
|
|
30
|
+
// `to` is where the closing ":" would be inserted
|
|
31
|
+
state.schema.text(emoji)
|
|
32
|
+
);
|
|
33
|
+
view.dispatch(tr);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
createEmojiPlugin
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=emoji.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/emoji.ts"],"sourcesContent":["/**\n * Lightweight emoji plugin.\n *\n * Converts emoji shortcodes like `:smile:` → 😄 using `node-emoji`.\n * Uses system native emoji rendering (no twemoji images).\n *\n * - Typing `:smile:` auto-converts to 😄 when the closing `:` is typed.\n *\n * `node-emoji` is declared as a peer dep so consumers control its version.\n * The conversion is purely textual via `view.state.schema.text(emoji)` so the\n * plugin works against any consumer-injected schema.\n */\n\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport { get as getEmoji } from 'node-emoji'\n\nconst emojiPluginKey = new PluginKey('moraya-emoji')\n\n/**\n * ProseMirror plugin that converts `:shortcode:` to native emoji on typing.\n */\nexport function createEmojiPlugin(): Plugin {\n return new Plugin({\n key: emojiPluginKey,\n props: {\n handleTextInput(view, from, to, text) {\n // Only trigger when user types \":\"\n if (text !== ':') return false\n\n const { state } = view\n const $pos = state.doc.resolve(from)\n // Get text content of the current text block up to cursor\n const textBefore = $pos.parent.textBetween(\n 0,\n $pos.parentOffset,\n undefined,\n '',\n )\n\n // Find the last unmatched \":\" before cursor\n const lastColon = textBefore.lastIndexOf(':')\n if (lastColon === -1) return false\n\n // Extract potential shortcode name between the two colons\n const shortcode = textBefore.slice(lastColon + 1)\n\n // Validate: must be non-empty and contain only valid chars\n if (!shortcode || !/^[a-zA-Z0-9_+-]+$/.test(shortcode)) return false\n\n const emoji = getEmoji(shortcode)\n if (!emoji) return false\n\n // Calculate absolute positions\n // The opening \":\" is at: from - (textBefore.length - lastColon)\n const openColonOffset = textBefore.length - lastColon\n const replaceFrom = from - openColonOffset\n\n // Replace `:shortcode:` (including the just-typed closing `:`) with emoji\n const tr = state.tr.replaceWith(\n replaceFrom,\n to, // `to` is where the closing \":\" would be inserted\n state.schema.text(emoji),\n )\n view.dispatch(tr)\n return true\n },\n },\n })\n}\n"],"mappings":";AAaA,SAAS,QAAQ,iBAAiB;AAClC,SAAS,OAAO,gBAAgB;AAEhC,IAAM,iBAAiB,IAAI,UAAU,cAAc;AAK5C,SAAS,oBAA4B;AAC1C,SAAO,IAAI,OAAO;AAAA,IAChB,KAAK;AAAA,IACL,OAAO;AAAA,MACL,gBAAgB,MAAM,MAAM,IAAI,MAAM;AAEpC,YAAI,SAAS,IAAK,QAAO;AAEzB,cAAM,EAAE,MAAM,IAAI;AAClB,cAAM,OAAO,MAAM,IAAI,QAAQ,IAAI;AAEnC,cAAM,aAAa,KAAK,OAAO;AAAA,UAC7B;AAAA,UACA,KAAK;AAAA,UACL;AAAA,UACA;AAAA,QACF;AAGA,cAAM,YAAY,WAAW,YAAY,GAAG;AAC5C,YAAI,cAAc,GAAI,QAAO;AAG7B,cAAM,YAAY,WAAW,MAAM,YAAY,CAAC;AAGhD,YAAI,CAAC,aAAa,CAAC,oBAAoB,KAAK,SAAS,EAAG,QAAO;AAE/D,cAAM,QAAQ,SAAS,SAAS;AAChC,YAAI,CAAC,MAAO,QAAO;AAInB,cAAM,kBAAkB,WAAW,SAAS;AAC5C,cAAM,cAAc,OAAO;AAG3B,cAAM,KAAK,MAAM,GAAG;AAAA,UAClB;AAAA,UACA;AAAA;AAAA,UACA,MAAM,OAAO,KAAK,KAAK;AAAA,QACzB;AACA,aAAK,SAAS,EAAE;AAChB,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Plugin } from 'prosemirror-state';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enter key handler — unified plugin for all Enter-key variants.
|
|
5
|
+
*
|
|
6
|
+
* In table cells:
|
|
7
|
+
* - `Ctrl/Cmd+Enter` → add a new row below
|
|
8
|
+
* - `Shift+Enter` → insert hard break (`<br>`)
|
|
9
|
+
* - Plain `Enter` → move to same column in next row; exit table from last row
|
|
10
|
+
*
|
|
11
|
+
* In paragraphs:
|
|
12
|
+
* - `Enter` → split the current block into a new paragraph (no `<br/>`).
|
|
13
|
+
* - `Enter` after ` ``` ` or ` ```language ` → create code block.
|
|
14
|
+
* - `Enter` after `| col1 | col2 |` → create GFM table with header + empty data row.
|
|
15
|
+
*
|
|
16
|
+
* Uses `handleKeyDown` (props-level) which has higher priority than keymaps,
|
|
17
|
+
* ensuring this runs before the base keymap's hardbreak / splitBlock handlers.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Enter handler plugin. Operates entirely off `view.state.schema`, so it works
|
|
22
|
+
* against any consumer-injected schema produced by `createSchema(config)`.
|
|
23
|
+
*/
|
|
24
|
+
declare function createEnterHandlerPlugin(): Plugin;
|
|
25
|
+
|
|
26
|
+
export { createEnterHandlerPlugin };
|