@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.
Files changed (58) hide show
  1. package/CHANGELOG.md +344 -0
  2. package/LICENSE +85 -0
  3. package/README.md +82 -0
  4. package/dist/adapters/browser-media-resolver.d.ts +21 -0
  5. package/dist/adapters/browser-media-resolver.js +24 -0
  6. package/dist/adapters/browser-media-resolver.js.map +1 -0
  7. package/dist/commands.d.ts +35 -0
  8. package/dist/commands.js +976 -0
  9. package/dist/commands.js.map +1 -0
  10. package/dist/doc-cache.d.ts +29 -0
  11. package/dist/doc-cache.js +50 -0
  12. package/dist/doc-cache.js.map +1 -0
  13. package/dist/index.d.ts +10 -0
  14. package/dist/index.js +4534 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/markdown.d.ts +46 -0
  17. package/dist/markdown.js +1553 -0
  18. package/dist/markdown.js.map +1 -0
  19. package/dist/plugins/code-block-view.d.ts +52 -0
  20. package/dist/plugins/code-block-view.js +686 -0
  21. package/dist/plugins/code-block-view.js.map +1 -0
  22. package/dist/plugins/cursor-syntax.d.ts +27 -0
  23. package/dist/plugins/cursor-syntax.js +122 -0
  24. package/dist/plugins/cursor-syntax.js.map +1 -0
  25. package/dist/plugins/definition-list.d.ts +23 -0
  26. package/dist/plugins/definition-list.js +12 -0
  27. package/dist/plugins/definition-list.js.map +1 -0
  28. package/dist/plugins/editor-props-plugin.d.ts +36 -0
  29. package/dist/plugins/editor-props-plugin.js +1963 -0
  30. package/dist/plugins/editor-props-plugin.js.map +1 -0
  31. package/dist/plugins/emoji.d.ts +21 -0
  32. package/dist/plugins/emoji.js +42 -0
  33. package/dist/plugins/emoji.js.map +1 -0
  34. package/dist/plugins/enter-handler.d.ts +26 -0
  35. package/dist/plugins/enter-handler.js +193 -0
  36. package/dist/plugins/enter-handler.js.map +1 -0
  37. package/dist/plugins/highlight.d.ts +39 -0
  38. package/dist/plugins/highlight.js +283 -0
  39. package/dist/plugins/highlight.js.map +1 -0
  40. package/dist/plugins/inline-code-convert.d.ts +32 -0
  41. package/dist/plugins/inline-code-convert.js +173 -0
  42. package/dist/plugins/inline-code-convert.js.map +1 -0
  43. package/dist/plugins/link-text-plugin.d.ts +22 -0
  44. package/dist/plugins/link-text-plugin.js +194 -0
  45. package/dist/plugins/link-text-plugin.js.map +1 -0
  46. package/dist/plugins/mermaid-renderer.d.ts +24 -0
  47. package/dist/plugins/mermaid-renderer.js +80 -0
  48. package/dist/plugins/mermaid-renderer.js.map +1 -0
  49. package/dist/schema.d.ts +48 -0
  50. package/dist/schema.js +847 -0
  51. package/dist/schema.js.map +1 -0
  52. package/dist/setup.d.ts +104 -0
  53. package/dist/setup.js +4393 -0
  54. package/dist/setup.js.map +1 -0
  55. package/dist/types.d.ts +107 -0
  56. package/dist/types.js +10 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +121 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugins/mermaid-renderer.ts","../../src/plugins/code-block-view.ts"],"sourcesContent":["/**\n * Mermaid renderer — lazy-loads the mermaid library and provides a render API.\n *\n * This is a utility module imported on-demand by `code-block-view.ts`,\n * NOT itself a ProseMirror plugin. The mermaid library (~2.4 MB) is loaded\n * only when the first mermaid code block is encountered, via dynamic\n * `import('mermaid')`. Consumers that want mermaid support must install\n * `mermaid` as a peer dependency.\n *\n * IMPORTANT: `mermaid.render()` manipulates global DOM state and is NOT safe\n * to call concurrently. All renders go through a serial queue.\n */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet mermaidModule: any = null\nlet loadingPromise: Promise<void> | null = null\nlet renderCounter = 0\n\n// ── Serial render queue ──────────────────────────\n// Mermaid.render() creates a temp SVG in the DOM, measures text, then removes\n// it. If two renders overlap, the second corrupts the first's temp element,\n// causing \"Render failed\". This queue ensures only one render runs at a time.\nlet renderQueue: Promise<void> = Promise.resolve()\n\nfunction isDark(): boolean {\n if (typeof document === 'undefined') return false\n const dt = document.documentElement.getAttribute('data-theme')\n if (dt === 'dark') return true\n if (dt === 'light') return false\n if (typeof window === 'undefined' || !window.matchMedia) return false\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n}\n\n/** Read resolved CSS custom property values from :root */\nfunction resolveThemeColors() {\n if (typeof document === 'undefined' || typeof getComputedStyle === 'undefined') {\n return {\n primaryColor: '#4a90d9',\n primaryTextColor: '#333',\n primaryBorderColor: '#ccc',\n lineColor: '#666',\n secondaryColor: '#f5f5f5',\n tertiaryColor: '#eee',\n }\n }\n const s = getComputedStyle(document.documentElement)\n return {\n primaryColor: s.getPropertyValue('--accent-color').trim() || '#4a90d9',\n primaryTextColor: s.getPropertyValue('--text-primary').trim() || '#333',\n primaryBorderColor: s.getPropertyValue('--border-color').trim() || '#ccc',\n lineColor: s.getPropertyValue('--text-secondary').trim() || '#666',\n secondaryColor: s.getPropertyValue('--bg-secondary').trim() || '#f5f5f5',\n tertiaryColor: s.getPropertyValue('--bg-hover').trim() || '#eee',\n }\n}\n\nexport async function ensureMermaidLoaded(): Promise<void> {\n if (mermaidModule) return\n if (loadingPromise) return loadingPromise\n\n loadingPromise = (async () => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mod: any = await import(/* @vite-ignore */ 'mermaid')\n mermaidModule = mod.default\n mermaidModule.initialize({\n startOnLoad: false,\n theme: isDark() ? 'dark' : 'default',\n themeVariables: resolveThemeColors(),\n })\n })()\n\n return loadingPromise\n}\n\nexport async function renderMermaid(\n code: string,\n): Promise<{ svg: string } | { error: string }> {\n await ensureMermaidLoaded()\n\n // Enqueue: wait for previous render to finish before starting this one\n const result = new Promise<{ svg: string } | { error: string }>((resolve) => {\n renderQueue = renderQueue.then(async () => {\n const id = `mermaid-${++renderCounter}`\n try {\n const { svg } = await mermaidModule.render(id, code)\n resolve({ svg })\n } catch (e) {\n resolve({ error: e instanceof Error ? e.message : 'Render failed' })\n }\n })\n })\n\n return result\n}\n\n/**\n * Re-initialize mermaid with updated theme. Called when theme changes.\n */\nexport function updateMermaidTheme(): void {\n if (!mermaidModule) return\n mermaidModule.initialize({\n startOnLoad: false,\n theme: isDark() ? 'dark' : 'default',\n themeVariables: resolveThemeColors(),\n })\n}\n","/**\n * CodeBlock NodeView — toolbar with language label, language picker, copy button,\n * mermaid preview, and renderer-plugin preview.\n *\n * Faithful 1:1 migration from Moraya desktop `src/lib/editor/plugins/code-block-view.ts`\n * with the following DI changes (v0.60.0-pre §F2.5):\n * - `RENDERER_PLUGINS` / `loadRendererPlugin` / `rendererVersions` (moraya-internal)\n * → `RendererRegistry` injected via factory parameter\n * - `editorStore.getState().currentFilePath` (used by `isFilePathRenderer`)\n * is no longer accessed here; the consumer's RendererRegistry implementation\n * closes over its own platform context and surfaces it via the renderer\n * module's `render(source, container)` call.\n * - mermaid still has a built-in special-case path (`language === 'mermaid'`),\n * using core's `plugins/mermaid-renderer.ts` (which is itself the migrated\n * version of moraya's mermaid-renderer).\n *\n * The render dispose-instance pattern from moraya (CAD viewer etc.) is\n * implemented in core via `RendererPluginModule.destroy(container)` per §3.3:\n * - On render: call `module.render(source, container, options)` (consumer\n * stores any disposable in container or via WeakMap closure).\n * - On lang change / NodeView destroy: call `module.destroy?(container)`.\n */\n\nimport type { Node as PmNode } from 'prosemirror-model'\nimport type { EditorView } from 'prosemirror-view'\nimport type { renderMermaid as RenderFn, updateMermaidTheme as UpdateThemeFn } from './mermaid-renderer'\nimport type { RendererRegistry, RendererPluginModule } from '../types'\n\n// ── Mermaid lazy-load wrapper ─────────────────────\n\ntype MermaidApi = { renderMermaid: typeof RenderFn; updateMermaidTheme: typeof UpdateThemeFn }\nlet mermaidApi: MermaidApi | null = null\nlet mermaidLoading: Promise<MermaidApi | null> | null = null\n\nfunction loadMermaidApi() {\n if (mermaidApi) return Promise.resolve(mermaidApi)\n if (mermaidLoading) return mermaidLoading\n mermaidLoading = import('./mermaid-renderer').then(mod => {\n mermaidApi = mod\n return mermaidApi\n })\n return mermaidLoading\n}\n\n// Theme change listener: re-render all mermaid previews when theme switches\nlet themeObserverInstalled = false\nconst mermaidReRenderCallbacks = new Set<() => void>()\n\nfunction installThemeObserver() {\n if (themeObserverInstalled) return\n themeObserverInstalled = true\n if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return\n const observer = new MutationObserver(() => {\n if (mermaidApi) mermaidApi.updateMermaidTheme()\n for (const cb of mermaidReRenderCallbacks) cb()\n })\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['data-theme'],\n })\n}\n\n// ── Language registry ─────────────────────────────\n\ninterface LanguageEntry {\n id: string // primary id used in node.attrs.language\n label: string // display name\n aliases: string[] // searchable aliases\n}\n\nconst POPULAR_LANGUAGES: LanguageEntry[] = [\n { id: 'javascript', label: 'JavaScript', aliases: ['js'] },\n { id: 'typescript', label: 'TypeScript', aliases: ['ts'] },\n { id: 'python', label: 'Python', aliases: ['py'] },\n { id: 'java', label: 'Java', aliases: [] },\n { id: 'go', label: 'Go', aliases: ['golang'] },\n { id: 'rust', label: 'Rust', aliases: ['rs'] },\n { id: 'c', label: 'C', aliases: [] },\n { id: 'cpp', label: 'C++', aliases: ['c++'] },\n { id: 'ruby', label: 'Ruby', aliases: ['rb'] },\n { id: 'php', label: 'PHP', aliases: [] },\n { id: 'swift', label: 'Swift', aliases: [] },\n { id: 'kotlin', label: 'Kotlin', aliases: ['kt'] },\n { id: 'sql', label: 'SQL', aliases: [] },\n { id: 'bash', label: 'Bash', aliases: ['sh', 'shell'] },\n { id: 'json', label: 'JSON', aliases: [] },\n { id: 'yaml', label: 'YAML', aliases: ['yml'] },\n { id: 'html', label: 'HTML', aliases: ['xml'] },\n { id: 'css', label: 'CSS', aliases: [] },\n { id: 'csharp', label: 'C#', aliases: ['cs'] },\n { id: 'dart', label: 'Dart', aliases: [] },\n { id: 'r', label: 'R', aliases: [] },\n { id: 'dockerfile', label: 'Dockerfile', aliases: ['docker'] },\n { id: 'graphql', label: 'GraphQL', aliases: ['gql'] },\n { id: 'markdown', label: 'Markdown', aliases: ['md'] },\n { id: 'text', label: 'Plain Text', aliases: ['plaintext', 'txt'] },\n { id: 'prompt', label: 'Prompt', aliases: ['image-prompts', 'image-prompt'] },\n { id: 'system', label: 'System Prompt', aliases: ['system-prompt'] },\n]\n\nconst BASE_OTHER_LANGUAGES: LanguageEntry[] = [\n { id: 'scss', label: 'SCSS', aliases: [] },\n { id: 'lua', label: 'Lua', aliases: [] },\n { id: 'diff', label: 'Diff', aliases: [] },\n { id: 'perl', label: 'Perl', aliases: ['pl'] },\n { id: 'scala', label: 'Scala', aliases: [] },\n { id: 'objectivec', label: 'Objective-C', aliases: ['objc'] },\n { id: 'ini', label: 'TOML / INI', aliases: ['toml'] },\n { id: 'powershell', label: 'PowerShell', aliases: ['ps', 'ps1'] },\n { id: 'makefile', label: 'Makefile', aliases: ['make'] },\n { id: 'groovy', label: 'Groovy', aliases: [] },\n { id: 'elixir', label: 'Elixir', aliases: ['ex'] },\n { id: 'haskell', label: 'Haskell', aliases: ['hs'] },\n { id: 'protobuf', label: 'Protobuf', aliases: ['proto'] },\n { id: 'latex', label: 'LaTeX', aliases: ['tex'] },\n { id: 'nginx', label: 'Nginx', aliases: ['nginxconf'] },\n { id: 'shell', label: 'Shell Session', aliases: [] },\n { id: 'mermaid', label: 'Mermaid', aliases: [] },\n]\n\nconst POPULAR_IDS = new Set(POPULAR_LANGUAGES.map(l => l.id))\n\n/** Given a renderer registry, derive the full language lists. */\nfunction buildLanguageLists(registry?: RendererRegistry): {\n popular: LanguageEntry[]\n rendererPlugins: LanguageEntry[]\n all: LanguageEntry[]\n rendererLangIds: Set<string>\n} {\n const rendererLangIds = registry\n ? new Set(Object.keys(registry.versions))\n : new Set<string>()\n const rendererPlugins: LanguageEntry[] = registry\n ? Object.keys(registry.versions).sort().map((id) => ({\n id,\n label: id.charAt(0).toUpperCase() + id.slice(1),\n aliases: [],\n }))\n : []\n const all: LanguageEntry[] = [\n ...POPULAR_LANGUAGES,\n ...BASE_OTHER_LANGUAGES,\n ...rendererPlugins,\n ].sort((a, b) => a.label.localeCompare(b.label))\n return { popular: POPULAR_LANGUAGES, rendererPlugins, all, rendererLangIds }\n}\n\nfunction findLanguageLabel(langId: string, all: LanguageEntry[]): string {\n if (!langId) return 'text'\n const entry = all.find(\n l => l.id === langId || l.aliases.includes(langId),\n )\n return entry ? entry.label : langId\n}\n\n// ── Auto-detect language via highlight.js ─────────\n\nlet hljsAutoDetect: ((code: string) => string | null) | null = null\n\nasync function getAutoDetect(): Promise<(code: string) => string | null> {\n if (hljsAutoDetect) return hljsAutoDetect\n try {\n const hljs = (await import('highlight.js/lib/core')).default\n hljsAutoDetect = (code: string) => {\n if (!code.trim() || code.length < 10) return null\n try {\n const result = hljs.highlightAuto(code)\n if (result.language && result.relevance > 5) {\n return result.language\n }\n } catch { /* ignore */ }\n return null\n }\n } catch {\n hljsAutoDetect = () => null\n }\n return hljsAutoDetect\n}\n\n// ── Language Picker ───────────────────────────────\n\nfunction createLanguagePicker(\n container: HTMLElement,\n anchor: HTMLElement,\n currentLang: string,\n codeContent: string,\n langLists: ReturnType<typeof buildLanguageLists>,\n onSelect: (lang: string) => void,\n onDismiss?: () => void,\n): { destroy: () => void } {\n const { popular, rendererPlugins, all } = langLists\n\n const picker = document.createElement('div')\n picker.className = 'code-lang-picker'\n picker.setAttribute('contenteditable', 'false')\n picker.addEventListener('mousedown', (e) => { e.stopPropagation() })\n picker.addEventListener('click', (e) => { e.stopPropagation() })\n\n const searchWrap = document.createElement('div')\n searchWrap.className = 'code-lang-search'\n const searchInput = document.createElement('input')\n searchInput.type = 'text'\n searchInput.className = 'code-lang-search-input'\n searchInput.placeholder = 'Search language...'\n searchInput.autocomplete = 'off'\n searchInput.setAttribute('autocorrect', 'off')\n searchInput.setAttribute('autocapitalize', 'off')\n searchInput.spellcheck = false\n searchWrap.appendChild(searchInput)\n picker.appendChild(searchWrap)\n\n const listEl = document.createElement('div')\n listEl.className = 'code-lang-list'\n picker.appendChild(listEl)\n\n let detectedLang: string | null = null\n\n function renderList(filter: string) {\n listEl.innerHTML = ''\n const lowerFilter = filter.toLowerCase()\n\n const matchesFilter = (entry: LanguageEntry) => {\n if (!lowerFilter) return true\n return (\n entry.id.includes(lowerFilter) ||\n entry.label.toLowerCase().includes(lowerFilter) ||\n entry.aliases.some(a => a.includes(lowerFilter))\n )\n }\n\n if (detectedLang && !lowerFilter && detectedLang !== currentLang) {\n const label = findLanguageLabel(detectedLang, all)\n const suggestEl = document.createElement('div')\n suggestEl.className = 'code-lang-suggestion'\n suggestEl.innerHTML = `<span class=\"suggestion-icon\">✦</span> ${label} <span class=\"suggestion-hint\">detected</span>`\n suggestEl.addEventListener('mousedown', (e) => {\n e.preventDefault()\n e.stopPropagation()\n onSelect(detectedLang!)\n destroy()\n })\n listEl.appendChild(suggestEl)\n\n const divider = document.createElement('div')\n divider.className = 'code-lang-divider'\n listEl.appendChild(divider)\n }\n\n const popularMatches = popular.filter(matchesFilter)\n if (popularMatches.length > 0 && !lowerFilter) {\n const groupLabel = document.createElement('div')\n groupLabel.className = 'code-lang-group-label'\n groupLabel.textContent = 'Popular'\n listEl.appendChild(groupLabel)\n\n for (const lang of popularMatches) {\n listEl.appendChild(createOption(lang))\n }\n\n const rendererIds = new Set(rendererPlugins.map(l => l.id))\n const others = all.filter(\n l => !POPULAR_IDS.has(l.id) && !rendererIds.has(l.id) && matchesFilter(l),\n )\n if (others.length > 0) {\n const divider = document.createElement('div')\n divider.className = 'code-lang-divider'\n listEl.appendChild(divider)\n\n const allLabel = document.createElement('div')\n allLabel.className = 'code-lang-group-label'\n allLabel.textContent = 'All'\n listEl.appendChild(allLabel)\n for (const lang of others) {\n listEl.appendChild(createOption(lang))\n }\n }\n\n const rendererMatches = rendererPlugins.filter(matchesFilter)\n if (rendererMatches.length > 0) {\n const divider2 = document.createElement('div')\n divider2.className = 'code-lang-divider'\n listEl.appendChild(divider2)\n\n const rendererLabel = document.createElement('div')\n rendererLabel.className = 'code-lang-group-label'\n rendererLabel.textContent = 'Renderer Plugins'\n listEl.appendChild(rendererLabel)\n for (const lang of rendererMatches) {\n listEl.appendChild(createOption(lang))\n }\n }\n } else {\n const matches = all.filter(matchesFilter)\n for (const lang of matches) {\n listEl.appendChild(createOption(lang))\n }\n if (matches.length === 0) {\n const empty = document.createElement('div')\n empty.className = 'code-lang-empty'\n empty.textContent = 'No matches'\n listEl.appendChild(empty)\n }\n }\n }\n\n function createOption(lang: LanguageEntry): HTMLElement {\n const option = document.createElement('div')\n option.className = 'code-lang-option'\n if (lang.id === currentLang) option.classList.add('selected')\n option.textContent = lang.label\n option.addEventListener('mousedown', (e) => {\n e.preventDefault()\n e.stopPropagation()\n onSelect(lang.id)\n destroy()\n })\n return option\n }\n\n renderList('')\n\n searchInput.addEventListener('input', () => {\n renderList(searchInput.value)\n })\n\n searchInput.addEventListener('keydown', (e) => {\n e.stopPropagation()\n if (e.key === 'Escape') {\n destroy()\n }\n })\n\n // Append OUTSIDE ProseMirror's DOM tree so the editor's domObserver won't\n // detect focus moving to the search input and steal it back.\n const pickerHost = container.closest('.editor-wrapper') ?? document.body\n pickerHost.appendChild(picker)\n\n ;(function positionPicker() {\n const rect = anchor.getBoundingClientRect()\n picker.style.position = 'fixed'\n picker.style.top = `${rect.bottom + 2}px`\n picker.style.left = `${rect.left}px`\n })()\n\n requestAnimationFrame(() => searchInput.focus())\n\n getAutoDetect().then(detect => {\n detectedLang = detect(codeContent)\n if (detectedLang && !searchInput.value) {\n renderList('')\n }\n })\n\n function handleOutsideClick(e: MouseEvent) {\n if (!picker.contains(e.target as Node) && !anchor.contains(e.target as Node)) {\n destroy()\n }\n }\n\n function handleKeydown(e: KeyboardEvent) {\n if (e.key === 'Escape') {\n destroy()\n }\n }\n\n setTimeout(() => {\n document.addEventListener('mousedown', handleOutsideClick)\n document.addEventListener('keydown', handleKeydown, true)\n }, 0)\n\n function destroy() {\n document.removeEventListener('mousedown', handleOutsideClick)\n document.removeEventListener('keydown', handleKeydown, true)\n picker.remove()\n onDismiss?.()\n }\n\n return { destroy }\n}\n\n// ── Text escape helper ────────────────────────────\n\nfunction escapeText(str: string): string {\n const d = document.createElement('div')\n d.textContent = str\n return d.innerHTML\n}\n\n// ── Copy button helper ────────────────────────────\n\nfunction handleCopy(btn: HTMLButtonElement, codeEl: HTMLElement) {\n const text = codeEl.textContent || ''\n navigator.clipboard.writeText(text).then(() => {\n btn.classList.add('copied')\n btn.title = 'Copied!'\n setTimeout(() => {\n btn.classList.remove('copied')\n btn.title = 'Copy'\n }, 1500)\n })\n}\n\n// ── NodeView Factory ──────────────────────────────\n\nexport interface CodeBlockNodeViewOptions {\n rendererRegistry?: RendererRegistry\n}\n\n/**\n * NodeView factory builder. Returns the function to pass as\n * `nodeViews: { code_block: <returned> }` in EditorView config.\n *\n * Closes over a `RendererRegistry` so dispatched renderer plugins are\n * looked up via the consumer's injected registry rather than a Moraya-only\n * static map.\n */\nexport function createCodeBlockNodeViewFactory(opts: CodeBlockNodeViewOptions = {}) {\n const { rendererRegistry } = opts\n const langLists = buildLanguageLists(rendererRegistry)\n const { rendererLangIds, all: allLanguages } = langLists\n\n return function createCodeBlockNodeView(\n nodeArg: PmNode,\n view: EditorView,\n getPos: () => number | undefined,\n ) {\n let node = nodeArg\n // ── DOM structure ──\n const wrapper = document.createElement('div')\n wrapper.className = 'code-block-wrapper'\n\n const toolbar = document.createElement('div')\n toolbar.className = 'code-block-toolbar'\n toolbar.setAttribute('contenteditable', 'false')\n\n const langLabel = document.createElement('span')\n langLabel.className = 'code-lang-label'\n langLabel.textContent = findLanguageLabel((node.attrs.language as string) || '', allLanguages)\n langLabel.title = 'Change language'\n\n const toggleBtn = document.createElement('button')\n toggleBtn.className = 'mermaid-toggle-btn'\n toggleBtn.type = 'button'\n\n const copyBtn = document.createElement('button')\n copyBtn.className = 'code-copy-btn'\n copyBtn.title = 'Copy'\n copyBtn.type = 'button'\n copyBtn.innerHTML =\n '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' +\n '<rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/>' +\n '<path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/>' +\n '</svg>' +\n '<svg class=\"check-icon\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' +\n '<path d=\"M20 6L9 17l-5-5\"/>' +\n '</svg>'\n\n const toolbarRight = document.createElement('div')\n toolbarRight.className = 'code-toolbar-right'\n toolbarRight.appendChild(toggleBtn)\n toolbarRight.appendChild(copyBtn)\n\n toolbar.appendChild(langLabel)\n toolbar.appendChild(toolbarRight)\n\n const pre = document.createElement('pre')\n pre.className = 'code-block-pre'\n const code = document.createElement('code')\n code.className = 'code-block-code'\n pre.appendChild(code)\n\n const mermaidPreview = document.createElement('div')\n mermaidPreview.className = 'mermaid-preview'\n mermaidPreview.setAttribute('contenteditable', 'false')\n mermaidPreview.style.display = 'none'\n\n const rendererPreview = document.createElement('div')\n rendererPreview.className = 'renderer-preview'\n rendererPreview.setAttribute('contenteditable', 'false')\n rendererPreview.style.display = 'none'\n\n wrapper.appendChild(toolbar)\n wrapper.appendChild(pre)\n wrapper.appendChild(mermaidPreview)\n wrapper.appendChild(rendererPreview)\n\n // ── Mermaid state ──\n let isEditing = false\n let isMermaid = (node.attrs.language === 'mermaid')\n let lastRenderedCode = ''\n let renderTimer: ReturnType<typeof setTimeout> | null = null\n\n // ── Renderer plugin state ──\n let isRenderer = rendererLangIds.has((node.attrs.language as string) || '')\n let rendererEditing = false\n let lastRendererCode = ''\n let rendererTimer: ReturnType<typeof setTimeout> | null = null\n /** Last successfully loaded renderer module — kept so destroy() can be called. */\n let currentRendererModule: RendererPluginModule | null = null\n\n function syncMermaidMode() {\n const showPreview = isMermaid && !isEditing\n pre.style.display = (showPreview || (isRenderer && !rendererEditing)) ? 'none' : ''\n mermaidPreview.style.display = showPreview ? 'flex' : 'none'\n toggleBtn.style.display = (isMermaid || isRenderer) ? 'inline-flex' : 'none'\n wrapper.classList.toggle('mermaid-preview-mode', showPreview)\n if (isMermaid) {\n toggleBtn.textContent = isEditing ? '👁 Preview' : '✏️ Edit'\n if (showPreview) triggerMermaidRender()\n }\n }\n\n function triggerMermaidRender() {\n const codeText = code.textContent || ''\n if (!codeText.trim()) {\n mermaidPreview.innerHTML = '<div class=\"mermaid-empty\">Empty diagram</div>'\n lastRenderedCode = ''\n return\n }\n if (codeText === lastRenderedCode) return\n lastRenderedCode = codeText\n\n if (renderTimer) clearTimeout(renderTimer)\n renderTimer = setTimeout(async () => {\n mermaidPreview.innerHTML = '<div class=\"mermaid-loading\"><div class=\"mermaid-spinner\"></div>Loading diagram...</div>'\n try {\n const api = await loadMermaidApi()\n if (!api) return\n const result = await api.renderMermaid(codeText)\n if (code.textContent !== codeText) return\n if ('svg' in result) {\n mermaidPreview.innerHTML = result.svg\n } else {\n mermaidPreview.innerHTML = `<div class=\"mermaid-error\">${escapeText(result.error)}</div>`\n }\n } catch {\n mermaidPreview.innerHTML = '<div class=\"mermaid-error\">Render failed</div>'\n }\n }, 150)\n }\n\n // ── Renderer plugin sync ──\n function syncRendererMode() {\n const showPreview = isRenderer && !rendererEditing\n pre.style.display = (showPreview || (isMermaid && !isEditing)) ? 'none' : ''\n rendererPreview.style.display = showPreview ? 'block' : 'none'\n toggleBtn.style.display = (isMermaid || isRenderer) ? 'inline-flex' : 'none'\n wrapper.classList.toggle('renderer-preview-mode', showPreview)\n if (isRenderer) {\n toggleBtn.textContent = rendererEditing ? '👁 Preview' : '✏️ Edit'\n if (showPreview) triggerRendererRender()\n }\n }\n\n function triggerRendererRender() {\n const source = code.textContent || ''\n const lang = (node.attrs.language as string) || ''\n if (!rendererRegistry || !rendererRegistry.has(lang)) return\n\n if (!source.trim()) {\n rendererPreview.innerHTML = '<div class=\"renderer-empty\">Empty block</div>'\n lastRendererCode = ''\n return\n }\n if (source === lastRendererCode) return\n lastRendererCode = source\n\n if (rendererTimer) clearTimeout(rendererTimer)\n rendererTimer = setTimeout(async () => {\n rendererPreview.innerHTML = '<div class=\"renderer-loading\"><div class=\"renderer-spinner\"></div>Rendering...</div>'\n try {\n const module = await rendererRegistry.load(lang)\n if (code.textContent !== source) return // source changed during load\n // Dispose previous renderer (frees canvases / observers)\n if (currentRendererModule?.destroy) {\n try { currentRendererModule.destroy(rendererPreview) } catch { /* swallow per §4.5 */ }\n }\n currentRendererModule = module\n rendererPreview.innerHTML = ''\n try {\n await module.render(source, rendererPreview)\n } catch (e) {\n // §3.3 RendererPluginModule error contract: NodeView shows\n // .renderer-error fallback; serializer keeps fenced source via\n // node.attrs (not DOM), so roundtrip is safe.\n rendererPreview.innerHTML =\n `<div class=\"renderer-error\" data-language=\"${escapeText(lang)}\" data-error=\"${escapeText(String(e))}\">[Renderer ${escapeText(lang)} failed]</div>`\n }\n } catch (e) {\n rendererPreview.innerHTML =\n `<div class=\"renderer-error\" data-language=\"${escapeText(lang)}\" data-error=\"${escapeText(String(e))}\">[Renderer ${escapeText(lang)} failed]</div>`\n }\n }, 150)\n }\n\n function onThemeChange() {\n if (isMermaid && !isEditing) {\n lastRenderedCode = ''\n triggerMermaidRender()\n }\n }\n\n if (isMermaid) {\n installThemeObserver()\n mermaidReRenderCallbacks.add(onThemeChange)\n // Defer: ProseMirror populates contentDOM AFTER NodeView factory returns\n requestAnimationFrame(() => syncMermaidMode())\n } else if (isRenderer) {\n // Hide pre and show toggle button immediately, then re-trigger after content arrives\n syncRendererMode()\n requestAnimationFrame(() => syncRendererMode())\n } else {\n syncMermaidMode()\n }\n\n // ── Language picker ──\n let activePicker: { destroy: () => void } | null = null\n\n langLabel.addEventListener('mousedown', (e) => {\n e.preventDefault()\n e.stopPropagation()\n\n if (activePicker) {\n activePicker.destroy()\n activePicker = null\n wrapper.classList.remove('picker-open')\n return\n }\n\n const currentLang = (node.attrs.language as string) || ''\n const codeContent = code.textContent || ''\n wrapper.classList.add('picker-open')\n activePicker = createLanguagePicker(\n wrapper,\n langLabel,\n currentLang,\n codeContent,\n langLists,\n (newLang) => {\n activePicker = null\n wrapper.classList.remove('picker-open')\n const pos = getPos()\n if (pos === undefined) return\n view.dispatch(\n view.state.tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n language: newLang,\n }),\n )\n view.focus()\n },\n () => {\n activePicker = null\n wrapper.classList.remove('picker-open')\n },\n )\n })\n\n // ── Mermaid / Renderer toggle button ──\n toggleBtn.addEventListener('mousedown', (e) => {\n e.preventDefault()\n e.stopPropagation()\n if (isMermaid) {\n isEditing = !isEditing\n syncMermaidMode()\n } else if (isRenderer) {\n rendererEditing = !rendererEditing\n syncRendererMode()\n }\n if (isEditing || rendererEditing) view.focus()\n })\n\n // Click SVG preview → enter edit mode\n mermaidPreview.addEventListener('mousedown', (e) => {\n e.preventDefault()\n e.stopPropagation()\n isEditing = true\n syncMermaidMode()\n view.focus()\n })\n\n // ── Copy button ──\n copyBtn.addEventListener('mousedown', (e) => {\n e.preventDefault()\n e.stopPropagation()\n handleCopy(copyBtn, code)\n })\n\n return {\n dom: wrapper,\n contentDOM: code,\n\n stopEvent(event: Event) {\n const target = event.target as Node\n return !code.contains(target) && wrapper.contains(target) && target !== code\n },\n\n ignoreMutation(mutation: { target: Node }) {\n return !code.contains(mutation.target)\n },\n\n update(updatedNode: PmNode) {\n if (updatedNode.type.name !== 'code_block') return false\n node = updatedNode\n langLabel.textContent = findLanguageLabel((updatedNode.attrs.language as string) || '', allLanguages)\n\n const wasMermaid = isMermaid\n isMermaid = (updatedNode.attrs.language === 'mermaid')\n if (isMermaid !== wasMermaid) {\n isEditing = false\n if (isMermaid) {\n installThemeObserver()\n mermaidReRenderCallbacks.add(onThemeChange)\n } else {\n mermaidReRenderCallbacks.delete(onThemeChange)\n }\n }\n\n const wasRenderer = isRenderer\n isRenderer = rendererLangIds.has((updatedNode.attrs.language as string) || '')\n if (isRenderer !== wasRenderer) {\n rendererEditing = false\n lastRendererCode = ''\n rendererPreview.innerHTML = ''\n // Dispose old renderer module when switching away\n if (!isRenderer && currentRendererModule?.destroy) {\n try { currentRendererModule.destroy(rendererPreview) } catch { /* swallow */ }\n currentRendererModule = null\n }\n }\n\n if (isRenderer) {\n syncRendererMode()\n } else {\n rendererPreview.style.display = 'none'\n wrapper.classList.remove('renderer-preview-mode')\n syncMermaidMode()\n }\n return true\n },\n\n selectNode() {\n wrapper.classList.add('ProseMirror-selectednode')\n },\n\n deselectNode() {\n wrapper.classList.remove('ProseMirror-selectednode')\n },\n\n destroy() {\n if (activePicker) {\n activePicker.destroy()\n activePicker = null\n }\n if (renderTimer) clearTimeout(renderTimer)\n if (rendererTimer) clearTimeout(rendererTimer)\n if (currentRendererModule?.destroy) {\n try { currentRendererModule.destroy(rendererPreview) } catch { /* swallow */ }\n }\n currentRendererModule = null\n mermaidReRenderCallbacks.delete(onThemeChange)\n },\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,27 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+
3
+ /**
4
+ * Cursor syntax plugin — Typora-style source-syntax overlay.
5
+ *
6
+ * Shows the source markdown delimiters (`# `, `> `, `**`, `*`, `` ` ``, `~~`)
7
+ * around the cursor position so users see the underlying syntax while editing
8
+ * rendered prose. Uses ProseMirror Decoration widgets with `side: ±1` so the
9
+ * widgets sit visually adjacent to the cursor without becoming part of the
10
+ * editable text.
11
+ *
12
+ * Block-level prefixes shown:
13
+ * - heading 1-6 → `# `, `## `, ... `###### `
14
+ * - blockquote → `> `
15
+ *
16
+ * Inline mark delimiters shown when cursor is inside the mark:
17
+ * - strong → `**` ... `**`
18
+ * - em → `*` ... `*`
19
+ * - code → `` ` `` ... `` ` ``
20
+ * - strike_through → `~~` ... `~~`
21
+ *
22
+ * Link marks are handled by `link-text-plugin` (expand/collapse pattern).
23
+ */
24
+
25
+ declare function createCursorSyntaxPlugin(): Plugin;
26
+
27
+ export { createCursorSyntaxPlugin };
@@ -0,0 +1,122 @@
1
+ // src/plugins/cursor-syntax.ts
2
+ import { Plugin, PluginKey } from "prosemirror-state";
3
+ import { Decoration, DecorationSet } from "prosemirror-view";
4
+ var pluginKey = new PluginKey("moraya-cursor-syntax");
5
+ var HEADING_PREFIX = {
6
+ 1: "# ",
7
+ 2: "## ",
8
+ 3: "### ",
9
+ 4: "#### ",
10
+ 5: "##### ",
11
+ 6: "###### "
12
+ };
13
+ var MARK_DELIMITERS = {
14
+ strong: { open: "**", close: "**" },
15
+ em: { open: "*", close: "*" },
16
+ code: { open: "`", close: "`" },
17
+ strike_through: { open: "~~", close: "~~" }
18
+ };
19
+ function makeWidget(text, className) {
20
+ return () => {
21
+ const span = document.createElement("span");
22
+ span.className = className;
23
+ span.textContent = text;
24
+ return span;
25
+ };
26
+ }
27
+ function getMarkRange(state, pos, markType) {
28
+ const $pos = state.doc.resolve(pos);
29
+ const parent = $pos.parent;
30
+ if (!parent.isTextblock) return null;
31
+ const base = $pos.start();
32
+ const runs = [];
33
+ let runFrom = -1;
34
+ let nodePos = base;
35
+ for (let i = 0; i < parent.childCount; i++) {
36
+ const child = parent.child(i);
37
+ const childEnd = nodePos + child.nodeSize;
38
+ if (markType.isInSet(child.marks)) {
39
+ if (runFrom === -1) runFrom = nodePos;
40
+ } else {
41
+ if (runFrom !== -1) {
42
+ runs.push({ from: runFrom, to: nodePos });
43
+ runFrom = -1;
44
+ }
45
+ }
46
+ nodePos = childEnd;
47
+ }
48
+ if (runFrom !== -1) runs.push({ from: runFrom, to: nodePos });
49
+ return runs.find((r) => pos >= r.from && pos < r.to) ?? null;
50
+ }
51
+ function buildDecorations(state) {
52
+ const { selection } = state;
53
+ if (!selection.empty) return DecorationSet.empty;
54
+ const $from = selection.$from;
55
+ const decorations = [];
56
+ const pos = $from.pos;
57
+ const depth = $from.depth;
58
+ const parent = $from.parent;
59
+ if (parent.type === state.schema.nodes.heading) {
60
+ const level = parent.attrs.level;
61
+ const prefix = HEADING_PREFIX[level] ?? "# ";
62
+ const contentStart = $from.start(depth);
63
+ decorations.push(
64
+ Decoration.widget(contentStart, makeWidget(prefix, "syntax-md-prefix"), {
65
+ side: -1,
66
+ key: "heading-prefix"
67
+ })
68
+ );
69
+ }
70
+ for (let d = depth - 1; d >= 1; d--) {
71
+ if ($from.node(d).type === state.schema.nodes.blockquote) {
72
+ const contentStart = $from.start(depth);
73
+ decorations.push(
74
+ Decoration.widget(contentStart, makeWidget("> ", "syntax-md-prefix"), {
75
+ side: -1,
76
+ key: "bq-prefix"
77
+ })
78
+ );
79
+ break;
80
+ }
81
+ }
82
+ for (const [markName, delim] of Object.entries(MARK_DELIMITERS)) {
83
+ const markType = state.schema.marks[markName];
84
+ if (!markType) continue;
85
+ const range = getMarkRange(state, pos, markType);
86
+ if (!range) continue;
87
+ decorations.push(
88
+ Decoration.widget(range.from, makeWidget(delim.open, "syntax-md-mark"), {
89
+ side: -1,
90
+ key: `${markName}-open`
91
+ }),
92
+ Decoration.widget(range.to, makeWidget(delim.close, "syntax-md-mark"), {
93
+ side: 1,
94
+ key: `${markName}-close`
95
+ })
96
+ );
97
+ }
98
+ return DecorationSet.create(state.doc, decorations);
99
+ }
100
+ function createCursorSyntaxPlugin() {
101
+ return new Plugin({
102
+ key: pluginKey,
103
+ state: {
104
+ init(_, state) {
105
+ return buildDecorations(state);
106
+ },
107
+ apply(tr, old, _, newState) {
108
+ if (!tr.selectionSet && !tr.docChanged) return old;
109
+ return buildDecorations(newState);
110
+ }
111
+ },
112
+ props: {
113
+ decorations(state) {
114
+ return this.getState(state);
115
+ }
116
+ }
117
+ });
118
+ }
119
+ export {
120
+ createCursorSyntaxPlugin
121
+ };
122
+ //# sourceMappingURL=cursor-syntax.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugins/cursor-syntax.ts"],"sourcesContent":["/**\n * Cursor syntax plugin — Typora-style source-syntax overlay.\n *\n * Shows the source markdown delimiters (`# `, `> `, `**`, `*`, `` ` ``, `~~`)\n * around the cursor position so users see the underlying syntax while editing\n * rendered prose. Uses ProseMirror Decoration widgets with `side: ±1` so the\n * widgets sit visually adjacent to the cursor without becoming part of the\n * editable text.\n *\n * Block-level prefixes shown:\n * - heading 1-6 → `# `, `## `, ... `###### `\n * - blockquote → `> `\n *\n * Inline mark delimiters shown when cursor is inside the mark:\n * - strong → `**` ... `**`\n * - em → `*` ... `*`\n * - code → `` ` `` ... `` ` ``\n * - strike_through → `~~` ... `~~`\n *\n * Link marks are handled by `link-text-plugin` (expand/collapse pattern).\n */\n\nimport { Plugin, PluginKey } from 'prosemirror-state'\nimport type { EditorState } from 'prosemirror-state'\nimport { Decoration, DecorationSet } from 'prosemirror-view'\nimport type { MarkType } from 'prosemirror-model'\n\nconst pluginKey = new PluginKey('moraya-cursor-syntax')\n\nconst HEADING_PREFIX: Record<number, string> = {\n 1: '# ',\n 2: '## ',\n 3: '### ',\n 4: '#### ',\n 5: '##### ',\n 6: '###### ',\n}\n\nconst MARK_DELIMITERS: Record<string, { open: string; close: string }> = {\n strong: { open: '**', close: '**' },\n em: { open: '*', close: '*' },\n code: { open: '`', close: '`' },\n strike_through: { open: '~~', close: '~~' },\n}\n\nfunction makeWidget(text: string, className: string): () => HTMLSpanElement {\n return () => {\n const span = document.createElement('span')\n span.className = className\n span.textContent = text\n return span\n }\n}\n\n/**\n * Find the contiguous range of `markType` in the current textblock that contains `pos`.\n * Returns absolute document positions {from, to}, or null if cursor is not inside that mark.\n */\nfunction getMarkRange(\n state: EditorState,\n pos: number,\n markType: MarkType,\n): { from: number; to: number } | null {\n const $pos = state.doc.resolve(pos)\n const parent = $pos.parent\n if (!parent.isTextblock) return null\n\n const base = $pos.start() // absolute position of parent content start\n const runs: Array<{ from: number; to: number }> = []\n let runFrom = -1\n let nodePos = base\n\n for (let i = 0; i < parent.childCount; i++) {\n const child = parent.child(i)\n const childEnd = nodePos + child.nodeSize\n\n if (markType.isInSet(child.marks)) {\n if (runFrom === -1) runFrom = nodePos\n } else {\n if (runFrom !== -1) {\n runs.push({ from: runFrom, to: nodePos })\n runFrom = -1\n }\n }\n nodePos = childEnd\n }\n if (runFrom !== -1) runs.push({ from: runFrom, to: nodePos })\n\n // Use half-open interval [from, to): include left boundary, exclude right boundary.\n // Position exactly at r.to is the \"just exited\" point — no decoration there prevents\n // the DOM-mutation-driven cursor bounce when moving from mark boundary to ZWSP position.\n return runs.find(r => pos >= r.from && pos < r.to) ?? null\n}\n\nfunction buildDecorations(state: EditorState): DecorationSet {\n const { selection } = state\n // Only show decorations when cursor is a single collapsed point (no selection)\n if (!selection.empty) return DecorationSet.empty\n\n const $from = selection.$from\n const decorations: Decoration[] = []\n const pos = $from.pos\n const depth = $from.depth\n const parent = $from.parent\n\n // 1. Block-level: heading prefix\n if (parent.type === state.schema.nodes.heading) {\n const level = parent.attrs.level as number\n const prefix = HEADING_PREFIX[level] ?? '# '\n const contentStart = $from.start(depth)\n decorations.push(\n Decoration.widget(contentStart, makeWidget(prefix, 'syntax-md-prefix'), {\n side: -1,\n key: 'heading-prefix',\n }),\n )\n }\n\n // 2. Block-level: blockquote prefix at start of current paragraph\n for (let d = depth - 1; d >= 1; d--) {\n if ($from.node(d).type === state.schema.nodes.blockquote) {\n const contentStart = $from.start(depth)\n decorations.push(\n Decoration.widget(contentStart, makeWidget('> ', 'syntax-md-prefix'), {\n side: -1,\n key: 'bq-prefix',\n }),\n )\n break\n }\n }\n\n // 3. Inline marks: strong, em, code, strike_through\n for (const [markName, delim] of Object.entries(MARK_DELIMITERS)) {\n const markType = state.schema.marks[markName]\n if (!markType) continue\n\n const range = getMarkRange(state, pos, markType)\n if (!range) continue\n\n decorations.push(\n Decoration.widget(range.from, makeWidget(delim.open, 'syntax-md-mark'), {\n side: -1,\n key: `${markName}-open`,\n }),\n Decoration.widget(range.to, makeWidget(delim.close, 'syntax-md-mark'), {\n side: 1,\n key: `${markName}-close`,\n }),\n )\n }\n\n // 4. Link marks are handled by link-text-plugin (expand/collapse)\n\n return DecorationSet.create(state.doc, decorations)\n}\n\nexport function createCursorSyntaxPlugin(): Plugin {\n return new Plugin({\n key: pluginKey,\n state: {\n init(_, state) {\n return buildDecorations(state)\n },\n apply(tr, old, _, newState) {\n // Only recompute when selection or document changes\n if (!tr.selectionSet && !tr.docChanged) return old\n return buildDecorations(newState)\n },\n },\n props: {\n decorations(state) {\n return this.getState(state)\n },\n },\n })\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,23 @@
1
+ import { InputRule } from 'prosemirror-inputrules';
2
+ import { Schema } from 'prosemirror-model';
3
+
4
+ /**
5
+ * Definition list input rule.
6
+ *
7
+ * Typing `: ` (colon + 3 spaces) at the start of a line wraps the current
8
+ * block in a definition description inside a definition list.
9
+ *
10
+ * Note: node schemas are defined in `schema.ts`, markdown parsing is handled
11
+ * by `markdown-it-deflist` in `markdown.ts`.
12
+ */
13
+
14
+ /**
15
+ * Create the def-list input rule against a specific schema.
16
+ *
17
+ * The schema parameter is required because the rule binds to a NodeType, and
18
+ * each consumer-injected schema (per `createSchema(config)`) is a distinct
19
+ * Schema instance with its own NodeTypes.
20
+ */
21
+ declare function createDefListInputRule(schema: Schema): InputRule;
22
+
23
+ export { createDefListInputRule };
@@ -0,0 +1,12 @@
1
+ // src/plugins/definition-list.ts
2
+ import { wrappingInputRule } from "prosemirror-inputrules";
3
+ function createDefListInputRule(schema) {
4
+ return wrappingInputRule(
5
+ /^:\s{3}$/,
6
+ schema.nodes.defListDescription
7
+ );
8
+ }
9
+ export {
10
+ createDefListInputRule
11
+ };
12
+ //# sourceMappingURL=definition-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/plugins/definition-list.ts"],"sourcesContent":["/**\n * Definition list input rule.\n *\n * Typing `: ` (colon + 3 spaces) at the start of a line wraps the current\n * block in a definition description inside a definition list.\n *\n * Note: node schemas are defined in `schema.ts`, markdown parsing is handled\n * by `markdown-it-deflist` in `markdown.ts`.\n */\n\nimport { wrappingInputRule, type InputRule } from 'prosemirror-inputrules'\nimport type { Schema } from 'prosemirror-model'\n\n/**\n * Create the def-list input rule against a specific schema.\n *\n * The schema parameter is required because the rule binds to a NodeType, and\n * each consumer-injected schema (per `createSchema(config)`) is a distinct\n * Schema instance with its own NodeTypes.\n */\nexport function createDefListInputRule(schema: Schema): InputRule {\n return wrappingInputRule(\n /^:\\s{3}$/,\n schema.nodes.defListDescription!,\n )\n}\n"],"mappings":";AAUA,SAAS,yBAAyC;AAU3C,SAAS,uBAAuB,QAA2B;AAChE,SAAO;AAAA,IACL;AAAA,IACA,OAAO,MAAM;AAAA,EACf;AACF;","names":[]}
@@ -0,0 +1,36 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ import { Platform, LinkOpener } from '../types.js';
3
+
4
+ /**
5
+ * Unified editor props plugin — merges 5 separate ProseMirror plugins into one.
6
+ *
7
+ * Faithful 1:1 migration from Moraya desktop `src/lib/editor/plugins/editor-props-plugin.ts`
8
+ * with the following DI changes (v0.60.0-pre §F2.6):
9
+ * - `editorStore.getState().currentFilePath` → `platform.getCurrentFilePath()`
10
+ * - `isMacOS` from `$lib/utils/platform` → `platform.isMacOS`
11
+ * - `import('@tauri-apps/plugin-opener').{openPath,openUrl}` → `linkOpener.open(href)`
12
+ * (the consumer's LinkOpener implementation routes to the right platform API)
13
+ *
14
+ * Consolidated props:
15
+ * - `clipboardTextParser`: parse pasted plain text as Markdown (render instead of escape)
16
+ * - `transformPastedHTML`: paste language fix (copy `class="language-xxx"` → `data-language`)
17
+ * - `handleDOMEvents.mousedown`: math_block click → prevent WebKit broken selection;
18
+ * Cmd/Ctrl+click on links → open externally via LinkOpener
19
+ * - `handleDOMEvents.keydown/keyup`: toggle link-hover cursor class on Cmd/Ctrl;
20
+ * fast AllSelection delete; WKWebView end-of-textblock Backspace fix
21
+ * - `handleClick`: click below content → append paragraph + place cursor
22
+ * - `handleClickOn`: image click → TextSelection (prevent NodeSelection blue highlight)
23
+ * - `handleKeyDown`: ArrowRight escape; fast AllSelection delete (fallback)
24
+ * - `decorations`: WKWebView caret fix for empty paragraphs (macOS only)
25
+ * - `view` lifecycle: scroll-after-paste; empty-doc focus recovery
26
+ *
27
+ * Reducing 5 plugin instances to 1 saves ~4 apply() traversals per transaction.
28
+ */
29
+
30
+ interface EditorPropsPluginOptions {
31
+ platform: Platform;
32
+ linkOpener: LinkOpener;
33
+ }
34
+ declare function createEditorPropsPlugin(opts: EditorPropsPluginOptions): Plugin;
35
+
36
+ export { type EditorPropsPluginOptions, createEditorPropsPlugin };