@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,344 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@moraya/core` are documented here. SemVer.
4
+
5
+ ## [0.1.0] — 2026-05-07
6
+
7
+ Initial public release on npmjs.com. Faithful 1:1 extraction of Moraya desktop's `src/lib/editor/` markdown editor core into a host-agnostic, dependency-injected ESM package, per the iteration spec at [`v0.60.0-pre-shared-markdown-core.md`](https://github.com/zouwei/moraya/blob/main/docs/iterations/v0.60.0-pre-shared-markdown-core.md).
8
+
9
+ ### Package identity
10
+
11
+ - **Name**: `@moraya/core` (final name; renamed twice during pre-release: working name `@moraya/markdown-core` → interim `@zouwei/moraya-core` (GitHub Packages attempt) → final `@moraya/core` (npmjs.com public))
12
+ - **Repo**: https://github.com/zouwei/moraya-core (public)
13
+ - **Registry**: npmjs.com (https://registry.npmjs.org), `access: public` — anyone can `npm install @moraya/core` with no token
14
+ - **License**: PolyForm Internal Use 1.0.0
15
+
16
+ ### Distribution rationale
17
+
18
+ The original spec §5.1 chose GitHub Packages (private) for cost reasons, but GitHub Packages requires authentication even for public packages — incompatible with Moraya's open-source distribution model. The pre-release `@zouwei/moraya-core@0.1.0` published to GitHub Packages on 2026-05-06 was unpublished; `@moraya/core@0.1.0` on npmjs.com supersedes it.
19
+
20
+ ### Surface
21
+
22
+ - **Schema**: `createSchema(config)` factory + 23 nodes / 6 marks (faithful 1:1 from desktop)
23
+ - **Markdown**: `parseMarkdown` / `parseMarkdownAsync` (≥50KB threshold) / `serializeMarkdown` with cross-schema parser cache
24
+ - **Editor lifecycle**: `createEditor` / `createEditorPlugins` / `preloadEnhancementPlugins` (Tier 1 chunked dynamic import: highlight + emoji + code-block-view)
25
+ - **Plugins** (11 total): 8 base + 3 DI-coupled (highlight / editor-props / code-block-view); `review-decoration` excluded (stays in moraya for v0.30.0+ team-collab)
26
+ - **Doc cache**: `createDocCache(maxEntries?)` + `djb2Hash`
27
+ - **Commands** (14): bold/italic/strike/code/heading/blockquote/lists/code-block/table/HR/math-block/link/image
28
+ - **Adapters**: `BrowserMediaResolver` (Tauri / Electron / Capacitor adapters live in their consumer repos)
29
+ - **DI seams**: `MediaResolver` / `LinkOpener` / `RendererRegistry` / `Platform` (4 interfaces, §3.3)
30
+
31
+ ### Engineering contract
32
+
33
+ - **Pure ESM** — `dist/` contains 0 Node API imports, 0 `require()`, 0 host-specific imports (verified by 4 §1.1.4 CI gates)
34
+ - **Bundle**: main entry **34 KB gzipped** (42% of 80 KB budget)
35
+ - **Tests**: 106 vitest cases across 4 spec files
36
+ - 72 roundtrip tests (55 fixture files + 17 schema-critical data traps)
37
+ - 23 API contract tests
38
+ - 6 plugin-order fingerprint snapshots
39
+ - 5 adapter unit tests
40
+ - **Behavior parity**: §1.2.2 layer 1 (fixture roundtrip) + layer 2 (plugin order snapshot) green; layer 3 (3-note byte-diff) deferred to consumer-side smoke test
41
+
42
+ ### Tarball hygiene
43
+
44
+ `pnpm pack` produces a 58-entry tarball containing only:
45
+ - `package/LICENSE`, `package/package.json`, `package/CHANGELOG.md`, `package/README.md`
46
+ - `package/dist/**/*.{js,js.map,d.ts}`
47
+
48
+ No `*.svelte`, `src-tauri/`, `*.test.ts`, lockfile, or fixtures leak into the published artifact.
49
+
50
+ ---
51
+
52
+ ## [Unreleased / pre-release notes] — schema + markdown + plugins/setup + DI plugins migration batches (2026-05-05 — 2026-05-06)
53
+
54
+ ### DI plugins batch (2026-05-06)
55
+
56
+ Final batch of plugin migration. All 11 ProseMirror plugins from Moraya desktop's `src/lib/editor/plugins/` (excluding `review-decoration.ts` which stays in moraya/) are now in core.
57
+
58
+ #### Added (3 DI-coupled plugins migrated faithfully)
59
+
60
+ **`plugins/highlight.ts`** — full faithful 1:1 migration replaces the prior no-op stub.
61
+ - Imports + registers 39 highlight.js languages (javascript / typescript / python / rust / go / c / cpp / java / ruby / php / swift / kotlin / yaml / json / bash / sql / xml / html / css / scss / dart / r / perl / scala / objectivec / dockerfile / ini / powershell / makefile / groovy / elixir / haskell / protobuf / graphql / latex / nginx / shell / markdown / diff / lua + svelte/jsx/tsx aliases + cs / py / sh / rb / kt / yml / md / pl / objc / ex / hs / proto / gql / tex / nginxconf / ps1)
62
+ - `flattenHljsTree(rootNode)` + `scopeToClasses` walk hljs v11 emitter tree
63
+ - Per-block cache keyed by `(language, code)` with FIFO eviction at 100 entries (Moraya CLAUDE.md "Performance Coding Standards" §6 contract)
64
+ - 300 ms debounced full re-highlight via metadata-only transaction
65
+ - File-switch (`tr.getMeta('file-switch')`) and full-delete (`tr.getMeta('full-delete')`) paths rebuild from scratch
66
+ - Short-circuit when `tr.mapping` doesn't touch any code_block (saves ~90% of keystroke debounces)
67
+ - Schema-agnostic (no `$lib/` imports); zero behavior drift from desktop
68
+
69
+ **`plugins/editor-props-plugin.ts`** — full faithful 1:1 migration with `Platform` + `LinkOpener` DI.
70
+ - `editorStore.getState().currentFilePath` → `platform.getCurrentFilePath()`
71
+ - `isMacOS` from `$lib/utils/platform` → `platform.isMacOS`
72
+ - `import('@tauri-apps/plugin-opener').{openPath,openUrl}` → `linkOpener.open(href)` (consumer's LinkOpener routes URL vs local-file)
73
+ - 5-plugin merge preserved: `clipboardTextParser` + `handlePaste` (markdown image / empty link / degenerate slice fallbacks) + `transformPastedHTML` (language- → data-language) + `handleDOMEvents.{click,mousedown,keydown,keyup}` (link-hover class / Cmd+click open / math_block click / fast AllSelection delete / WKWebView Backspace fix) + `handleClick` (click-below-content append paragraph) + `handleClickOn` (image → TextSelection) + `handleKeyDown` (ArrowRight ZWSP escape / fast AllSelection delete fallback) + `decorations` (caret-empty-para macOS) + `view` lifecycle (scroll-after-paste + WKWebView empty-doc focus recovery)
74
+ - `isLocalFilePath` / `resolveLocalPath` ported as pure helpers (resolve-against-currentFile uses `platform.getCurrentFilePath()`)
75
+
76
+ **`plugins/code-block-view.ts`** — full faithful 1:1 migration with `RendererRegistry` DI.
77
+ - `RENDERER_PLUGINS` / `loadRendererPlugin` / `rendererVersions` (moraya-internal) → `RendererRegistry` injected via `createCodeBlockNodeViewFactory({ rendererRegistry })`
78
+ - Renderer plugin language list + versions derived from `registry.versions` keys
79
+ - Mermaid still has built-in special-case path (`language === 'mermaid'`) using core's `plugins/mermaid-renderer.ts`
80
+ - `LanguageEntry` registry (POPULAR_LANGUAGES + BASE_OTHER_LANGUAGES + dynamic renderer-plugin entries) + `findLanguageLabel`
81
+ - Language picker with auto-detect via `hljs.highlightAuto` (relevance > 5 threshold), grouped Popular / All / Renderer Plugins, keyboard nav, outside-click + Escape dismiss, focus-stealing-prevention via append-to-`.editor-wrapper` (not into ProseMirror DOM)
82
+ - Mermaid render path: lazy `loadMermaidApi()` → serial render queue → debounce 150 ms → SVG injection or error fallback → theme-change re-render via MutationObserver on `data-theme`
83
+ - Renderer render path: `registry.load(lang).render(source, container)` with §3.3 error contract: `<div class="renderer-error" data-language data-error>` fallback DOM, `module.destroy?(container)` called on lang-change / NodeView destroy
84
+ - Copy button + toggle-edit/preview button + `stopEvent` / `ignoreMutation` / `update` / `selectNode` / `deselectNode` / `destroy` lifecycle 1:1 from moraya
85
+ - 580 lines → ~700 lines (faithful, no shortcuts; the §1.2.4 "no simplification" rule is honored)
86
+
87
+ #### Wired into setup.ts (this batch)
88
+ - `preloadEnhancementPlugins(schema, rendererRegistry?)` now loads **3 chunks** in parallel: highlight + emoji + **code-block-view** (was 2 in previous batch)
89
+ - Cache key extended from `Schema` → `{ schema, rendererRegistry }` so consumers with different registry produce different cached factories
90
+ - `createEditorPlugins` adds `createEditorPropsPlugin({ platform, linkOpener })` between `columnResizing` and `cursorSyntax`
91
+ - `createEditor` wires `nodeViews.code_block` from `tier1.codeBlockView`
92
+ - Default `LinkOpener` (`window.open(href, '_blank', 'noopener,noreferrer')`) added when consumer doesn't inject one
93
+
94
+ #### Plugin order snapshot updates (§1.2.2)
95
+ Snapshot diffs reflect intentional changes:
96
+ - New `moraya-editor-props$` plugin added between `tableColumnResizing$` and `moraya-cursor-syntax$` (rank 8 → 9 shift for downstream plugins)
97
+ - Highlight key renamed: `moraya-highlight$` (stub) → `moraya-syntax-highlight$` (faithful)
98
+ Snapshots regenerated and committed per §1.2.2 reviewer-approval workflow.
99
+
100
+ #### Verification (this batch)
101
+ - ✅ `pnpm typecheck` clean (strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes)
102
+ - ✅ `pnpm test`: **68/68 pass** (snapshots updated + all roundtrip / api-contract / data-trap / plugin-order tests still passing)
103
+ - ✅ `pnpm build`: ESM + .d.ts; `dist/index.js` gzipped = **33.9 KB** (41% of 80 KB budget)
104
+ - ✅ §1.1.4 purity gates green (`.js` only): 0 Node API / 0 `require()` / 0 `@tauri-apps`/`@capacitor`/`electron` imports in dist
105
+ - ✅ Tier 1 chunks: `highlight.js` 2.4 KB gzipped, `code-block-view.js` 6.1 KB gzipped (hljs + mermaid live in consumer's bundle as peer deps)
106
+
107
+ #### Plugin migration progress: 11/11 ✅
108
+ | Plugin | Status |
109
+ |---|---|
110
+ | keybindings | Stays in moraya (static data, not a PM plugin) |
111
+ | definition-list | ✅ migrated |
112
+ | emoji | ✅ migrated |
113
+ | cursor-syntax | ✅ migrated |
114
+ | enter-handler | ✅ migrated |
115
+ | inline-code-convert | ✅ migrated |
116
+ | link-text | ✅ migrated |
117
+ | mermaid-renderer | ✅ migrated |
118
+ | **highlight** | ✅ migrated (this batch) |
119
+ | **editor-props-plugin** | ✅ migrated (this batch) |
120
+ | **code-block-view** | ✅ migrated (this batch) |
121
+ | review-decoration | Stays in moraya (v0.30.0+ team-collab specific) |
122
+
123
+
124
+
125
+ ### plugins + setup batch (2026-05-06)
126
+
127
+ #### Added (8 plugins migrated faithfully)
128
+ - **`plugins/definition-list.ts`** — `createDefListInputRule(schema)` factory (schema-parameterized)
129
+ - **`plugins/emoji.ts`** — `createEmojiPlugin()` converts `:shortcode:` → emoji via `node-emoji` peer dep
130
+ - **`plugins/cursor-syntax.ts`** — Typora-style source-syntax overlay (heading/blockquote prefix + paired mark delimiters for strong/em/code/strike_through)
131
+ - **`plugins/enter-handler.ts`** — unified Enter-key handler (table cell row navigation + Cmd+Enter add row + Shift+Enter hardbreak + plain Enter exits last row + code-fence detection + pipe-table detection with `parsePipeTableHeader`/`buildTableFromHeaders`)
132
+ - **`plugins/inline-code-convert.ts`** — backtick-pair collapse + ZWSP cursor target after trailing `code/strong/em/strike_through` marks + storedMarks management at code-ZWSP boundary
133
+ - **`plugins/link-text-plugin.ts`** — `[text](url)` literal-text decoration + cursor-leave collapse to link mark + cursor-enter expand-back-to-literal
134
+ - **`plugins/mermaid-renderer.ts`** — lazy-loaded mermaid render utility (serial render queue, theme-color resolution from CSS custom properties); `mermaid` declared as optional peer (`peerDependenciesMeta.mermaid.optional = true`)
135
+
136
+ All plugin code is **host-agnostic**: zero Tauri / store / DOM-singleton imports. Schema lookups go through `state.schema.nodes[name]` / `state.schema.marks[name]` so each plugin works against any consumer-injected schema produced by `createSchema(config)`.
137
+
138
+ #### Skipped (intentional — moved out of core scope)
139
+ - **`keybindings.ts`** is **NOT a ProseMirror plugin** — it's static `KeyBinding[]` data consumed only by Moraya's CommandPalette UI (file save / zoom / sidebar toggle / etc.). Heavily app-specific shortcut metadata; stays in moraya/. The v0.60.0-pre §3 file-tree listing this in `plugins/` was a documentation artifact.
140
+
141
+ #### Pending for next batch (3 DI-coupled plugins)
142
+ - `editor-props-plugin.ts` (580 lines, needs `Platform.getCurrentFilePath` + `isMacOS` + `LinkOpener.open` injection)
143
+ - `code-block-view.ts` (780 lines, needs full `RendererRegistry` integration + mermaid wiring)
144
+ - `highlight.ts` faithful hljs integration (current core stub is no-op; full migration requires per-block decoration cache from Moraya CLAUDE.md performance §6/§9 contract)
145
+
146
+ ### setup.ts batch (2026-05-06)
147
+
148
+ Replaces the 6KB minimum-viable stub with a 21KB faithful migration of Moraya desktop's editor lifecycle (730 lines).
149
+
150
+ #### Added
151
+ - `preloadEnhancementPlugins(schema)` — schema-keyed Tier 1 lazy-load cache (highlight + emoji currently; code-block-view + KaTeX CSS in next batch)
152
+ - `createImageSelectionPlugin()` — blue-overlay decoration for selected image / `<img>` html_inline nodes
153
+ - **`buildInputRules(schema, tier1)`** — 12 input rules: code-fence, blockquote, bullet/ordered list, heading 1-6, hr, math block + inline, strong (`**` / `__`), em (`*` / `_`), inline code, strike_through (`~~`), task-list checkbox, link `[text](url)`, def-list (Tier 1)
154
+ - **`buildKeymap(schema)`** — full keymap from Moraya:
155
+ - `Mod-z/y/Shift-z` → `undo` / `redo` (top-level ESM imports, replaces 3 `require('prosemirror-history')` calls per §1.1.1 Pure ESM)
156
+ - `Mod-b/i/e/Shift-x` → strong / em / code / strike_through marks
157
+ - `Mod-Alt-0..6` → paragraph / heading levels
158
+ - `Mod-Alt-c` → code_block, `Mod-Shift-b` → blockquote
159
+ - `Tab` / `Shift-Tab` / `Mod-]` / `Mod-[` → list indent/outdent
160
+ - `Mod-a` → code-block-local select-all OR doc-wide AllSelection
161
+ - `Shift-Enter` → hardbreak with `isInline: false`
162
+ - `Backspace` → 5-case WebKit contenteditable bug fix (full-doc fast delete + NodeSelection-on-atom + cursor-end-before-atom + cursor-start-after-atom + end-of-paragraph-after-inline-atom + end-of-textblock surrogate-pair-aware delete)
163
+ - `Delete` → block-atom protection (NodeSelection move + textblock-end consume)
164
+ - **listShortcutsPlugin** — `Cmd+Alt+O/U/X` → ordered/bullet/task list using `event.code` (macOS Option-key safe, Moraya CLAUDE.md "Option键快捷键处理" feedback memory)
165
+ - **`createDirtyTrackPlugin`** + **`createLazyChangePlugin`** — O(1) text-content callback vs debounced markdown-serialization callback
166
+ - Full `createEditor(opts)` + `createEditorPlugins(opts, schema?)` — assembles 13 base plugins + Tier 1 lazy-loaded plugins in faithful order:
167
+ 1. listShortcuts → 2. inputRules → 3. enter-handler → 4. buildKeymap → 5. baseKeymap →
168
+ 6. history (skippable for v0.72 Yjs) → 7. dropCursor → 8. columnResizing →
169
+ 9. cursorSyntax → 10. linkText → 11. inlineCodeConvert → 12. imageSelection →
170
+ 13. dirty/lazy-change → 14. Tier 1 highlight + emoji
171
+ - `wrapInBulletList` / `wrapInOrderedList` / `wrapInTaskList` exported from `commands.ts` (schema-agnostic via `state.schema`)
172
+
173
+ #### Plugin order fingerprint (§1.2.2)
174
+ - New `__tests__/plugin-order.spec.ts`: 6 snapshot/assertion tests covering default Web config, desktop-style config, history disabled (Yjs path), table-resize disabled, onChange/onDocChanged callback variants. **Snapshot is committed** so any reorder/add/remove triggers explicit reviewer review.
175
+
176
+ #### New peer deps
177
+ - `mermaid@^11.0.0` (optional via `peerDependenciesMeta`)
178
+ - `node-emoji@^2.0.0`
179
+ - `prosemirror-commands@^1.7.0`
180
+ - `prosemirror-dropcursor@^1.8.0`
181
+ - `prosemirror-inputrules@^1.5.0`
182
+ - `prosemirror-keymap@^1.2.0`
183
+ - `prosemirror-schema-list@^1.5.0`
184
+ - `prosemirror-tables@^1.8.0`
185
+
186
+ #### Verification (this batch)
187
+ - ✅ `pnpm typecheck` clean (strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes)
188
+ - ✅ `pnpm test`: **68/68 pass** (was 62/62; +6 plugin-order fingerprint tests)
189
+ - ✅ `pnpm build`: ESM + .d.ts; `dist/index.js` gzipped = 22.7 KB (28% of 80 KB budget)
190
+ - ✅ §1.1.4 purity gates green: 0 Node API / 0 `require()` / 0 host imports in dist
191
+ - ✅ All `require()` calls in original Moraya `setup.ts:282/286/290/343` replaced with top-level ESM imports per §1.1.1
192
+
193
+ ### markdown.ts batch (2026-05-06)
194
+
195
+ ### markdown.ts batch (2026-05-06)
196
+
197
+ #### Added
198
+ - Faithful 1:1 migration of Moraya desktop `markdown.ts` (912 lines)
199
+ - Full token override `MorayaMarkdownParser` class:
200
+ - `tr_open` dispatches to `table_header_row` (inside `<thead>`) vs `table_row` —
201
+ fixes "thead content lost to auto-inserted empty header row" bug
202
+ - `th_open/close` + `td_open/close` wrap inline content in inner `paragraph`
203
+ so `content: 'paragraph+'` cells aren't dropped by `createAndFill`
204
+ - `link_open` detects empty-text links `[]()` / `[](url)` and inserts the raw
205
+ markdown syntax as literal text instead of an empty (=removed) mark
206
+ - `html_inline` handler:
207
+ - Combines single-line `<audio>` / `<video>` opening + closing into one
208
+ `html_inline` atom node so `toDOM` can render media players
209
+ - Pre-scanned paired tags (`htmlPaired` meta) become `html_mark` open/close
210
+ so paired raw HTML like `<font>...</font>` round-trips as styled marks
211
+ - Unpaired tags stay as `html_inline` atom nodes for byte-stable roundtrip
212
+ - `html_block` handler:
213
+ - Promotes standalone `<img>` / `<video>` / `<audio>` blocks to
214
+ `paragraph(html_inline)` so they render as media instead of code blocks
215
+ - Multiple `<img>` tags in one block are joined with inline hardbreaks
216
+ - `tagPairedHtmlInline` pre-processor: scans inline tokens and tags paired
217
+ HTML opening/closing tags with `meta.htmlPaired = true`
218
+ - `preserveBlankLines` post-processor: injects empty paragraph tokens for
219
+ consecutive blank lines so multi-Enter spacing roundtrips faithfully
220
+ - Math support via `markdown-it-texmath` (peer dep): `math_inline`,
221
+ `math_inline_double` (mapped to `math_inline`), and `math_block`
222
+ - Definition list support via `markdown-it-deflist` (peer dep)
223
+ - GFM table + strikethrough enabled (`md.enable(['table', 'strikethrough'])`)
224
+ - Task list checkbox detection in `list_item` getAttrs:
225
+ - `[x]` / `[ ]` parsed from inline content into `checked: boolean | null`
226
+ - Literal checkbox text stripped from the rendered text
227
+ - `image` getAttrs decodes URL-encoded backslashes (`%5C` → `\`) so Windows
228
+ local image paths roundtrip correctly
229
+ - `link` getAttrs decodes percent-encoded non-ASCII UTF-8 sequences (e.g.
230
+ Chinese / Japanese / Korean) while leaving ASCII encodings (`%20` etc.) intact
231
+ - Pre-parse normalizers:
232
+ - `normalizeMathBlocks`: ensures `$$..$$` is surrounded by blank lines so
233
+ texmath parses it as `math_block` (not `math_inline_double`)
234
+ - `normalizeSmartQuotes`: converts curly quotes in image/link title
235
+ delimiters to straight quotes
236
+ - Post-serialize cleanup:
237
+ - Un-escapes over-escaped link syntax (`\[\](url)` → `[](url)`)
238
+ - Strips zero-width spaces (`​`) used as cursor targets
239
+
240
+ #### Serializer
241
+ - 23 node serializers (incl. `table` with alignment-aware separator,
242
+ `renderTableRow` helper using output-buffer capture, `math_inline`,
243
+ `math_block`, `defList` / `defListTerm` / `defListDescription`,
244
+ `html_block`, `html_inline`, `hardbreak` always emitting `' \n'`)
245
+ - 6 mark serializers (incl. `html_mark` writing `mark.attrs.openTag` /
246
+ `closeTag`, autolink-aware `link.open`/`close`, mixable `strike_through`)
247
+ - `serialize(doc, { tightLists: true })`
248
+
249
+ #### New fixtures (8→17)
250
+ - `09-table-aligned.md` — pipe table with left/center/right alignment
251
+ - `10-table-no-header.md` — pipe table sanity check
252
+ - `11-math-inline.md` — `$..$` inline math (KaTeX peer dep)
253
+ - `12-math-block.md` — `$$..$$` block math (multi-equation)
254
+ - `13-raw-html.md` — `<font>` paired tag (html_mark) + `<sub>` + unpaired `<br>`
255
+ - `14-def-list.md` — definition list via markdown-it-deflist
256
+ - `15-task-list.md` — GFM task list with checkbox stripping
257
+ - `16-cn-punctuation.md` — full-width CJK punctuation + bold/italic/strike on CJK
258
+ - `17-strikethrough-nested.md` — strikethrough nested inside bold + with em inside
259
+
260
+ #### New explicit data-trap tests (§4.4)
261
+ - block math `$$..$$` does NOT degrade to inline `$..$` after roundtrip
262
+ - raw HTML `<font>` preserved verbatim (no markdown conversion)
263
+ - paired `<sub>` round-trips byte-stably as `html_mark`
264
+ - table first child IS `table_header_row` (the table parsing fix)
265
+ - table cells use paragraph-wrapped content (not bare text)
266
+ - task list checkbox attrs recovered + literal `[x]`/`[ ]` stripped
267
+ - definition list parses to `defList` / `defListTerm` / `defListDescription`
268
+
269
+ #### Verification (this batch)
270
+ - ✅ `pnpm typecheck` clean
271
+ - ✅ `pnpm test`: **62/62 pass** (was 46/46; +9 fixture roundtrips, +7 data-trap tests)
272
+ - ✅ `pnpm build`: ESM + .d.ts; `dist/index.js` gzipped = 12.8 KB (16% of 80 KB budget)
273
+ - ✅ §1.1.4 purity gates: 0 Node API / 0 `require()` / 0 `@tauri-apps`/`@capacitor`/`electron` in dist
274
+ - ✅ New peer deps declared (`markdown-it-deflist@^3`, `markdown-it-texmath@^1`)
275
+ with TS shim declarations in `src/shims.d.ts`
276
+
277
+ ### Schema batch (2026-05-05)
278
+
279
+ > **Honest status**: previous "0.1.0" CHANGELOG overclaimed scope. The actual
280
+ > v0.60.0-pre §1.2 *faithful migration* is in progress and shipped per-batch
281
+ > rather than as a single drop. Below is the real state at each batch boundary.
282
+ > The package will not be tagged & published until all batches land + Moraya
283
+ > desktop bridge layer is wired up + behavior parity (§1.2.2) is verified.
284
+
285
+ ### Schema batch (this batch)
286
+
287
+ #### Added
288
+ - ProseMirror schema 1:1 faithful migration from Moraya desktop `src/lib/editor/schema.ts`:
289
+ - **23 nodes**: doc, text, paragraph, heading, blockquote, code_block,
290
+ horizontal_rule, bullet_list, ordered_list, list_item, image, hardbreak,
291
+ html_block, html_inline, table, table_header_row, table_row,
292
+ table_header, table_cell, math_inline, math_block,
293
+ defList, defListTerm, defListDescription
294
+ - **6 marks**: html_mark, strong, em, code, link, strike_through
295
+ - All node attributes match Moraya exact 1:1 (no rename / no schema simplification)
296
+ - KaTeX rendering inside `math_inline` / `math_block` `toDOM` (peer dep; no DI needed)
297
+ - HTML helpers (`extractHtmlAttr`, `extractAllHtmlAttrs`, `htmlTagToStyle`,
298
+ `showBrokenImage`, `createMediaElement`) ported as pure DOM helpers
299
+ - Path detection helpers (`isAbsoluteFilePath`, `isRelativePath`,
300
+ `resolveRelativePath`) ported as pure string ops
301
+ - Tauri `read_file_binary` IPC + `plugin-http` calls in image / video / audio
302
+ loaders are replaced by consumer-injected `MediaResolver` methods
303
+ (`loadLocalImage` / `loadLocalMedia` / `loadRemoteMedia`)
304
+ - Module-level `documentBaseDir` state preserved with public
305
+ `setDocumentBaseDir(dir)` / `getDocumentBaseDir()` exports
306
+ (pure string state, not Tauri-coupled)
307
+ - KaTeX render-error fallback marks math node with
308
+ `class="math-error" data-math-type="inline|block"` per §4.4 contract
309
+
310
+ #### Changed
311
+ - `code_block.attrs.params` → `code_block.attrs.language` (matches Moraya naming)
312
+ - `hard_break` node → `hardbreak` (matches Moraya naming + `leafText()` returns `\n`)
313
+ - `strikethrough` mark → `strike_through` (matches Moraya naming)
314
+ - `bullet_list` / `ordered_list` no longer have `tight` attr (Moraya schema doesn't track tightness on the list node)
315
+ - `image` no longer has `width` attr (Moraya encodes width in `title="width=70%"`)
316
+ - `list_item` now carries `label` / `listType` / `spread` / `checked` attrs (task-list support)
317
+ - `code_block.attrs.language` defaults to `'text'` (was `''`); fence info `''` → `'text'`
318
+ - `defaultSchema` now built from the full faithful 23N+6M, not the prior 15-node stub
319
+
320
+ ### Still pending (subsequent batches)
321
+ - 38 fixtures (currently 17): empty-replace / link-input-rule / large-async /
322
+ KaTeX error / frontmatter YAML/TOML/JSON / footnote / wikilink / hashtag /
323
+ Mermaid / blockquote-nested / autolink / hardbreak edge cases / 50KB real doc, etc.
324
+ - Moraya desktop bridge layer + `TauriMediaResolver` / `TauriLinkOpener` /
325
+ `MorayaRendererRegistry` injection + bridge schema.ts re-export shim
326
+ - Moraya desktop end-to-end smoke (`pnpm tauri dev`, save → disk byte diff vs v0.39.0 baseline)
327
+ - `@moraya/markdown-core@0.1.0` first publish to GitHub Packages (only after bridge + behavior parity)
328
+ - Behavior parity 3-layer verification (§1.2.2): plugin order fingerprint snapshot,
329
+ fixture roundtrip CI gate dual-run with Moraya desktop, hand-test 3 representative
330
+ notes save → disk byte diff
331
+ - Moraya desktop bridge layer + `TauriMediaResolver` / `MorayaRendererRegistry` injection
332
+ - `@moraya/markdown-core` first publish to GitHub Packages (only after all of above)
333
+
334
+ ### Verification (this batch)
335
+ - ✅ `pnpm typecheck`: clean (`tsc --noEmit`, strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes)
336
+ - ✅ `pnpm test`: 46/46 pass (3 spec files: api-contract, roundtrip, adapter)
337
+ - ✅ `pnpm build`: ESM + .d.ts emit, dist/index.js gzipped = 9.1 KB (89% under 80 KB budget)
338
+ - ✅ §1.1.4 purity gates: 0 Node API imports, 0 `require()`, 0 `@tauri-apps`/`@capacitor`/`electron` in dist
339
+
340
+ ### Notes
341
+ - Excludes `review-decoration.ts` (Moraya v0.30.0+ team-collab specific; stays in moraya/ desktop repo)
342
+ - Schema requires consumer-provided `MediaResolver` (no default singleton exported; see v0.60.0-pre §6.1.1)
343
+ - The internal `defaultSchema` (sentinel-tagged null resolver) is used only by
344
+ `parseMarkdown` / `serializeMarkdown` and is NOT exported via `index.ts`
package/LICENSE ADDED
@@ -0,0 +1,85 @@
1
+ PolyForm Internal Use License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/internal-use/1.0.0>
4
+
5
+ Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both
8
+ strict obligations and conditions to all your licenses.
9
+
10
+ Copyright License
11
+
12
+ The licensor grants you a copyright license for the software to do everything
13
+ you might do with the software that would otherwise infringe the licensor's
14
+ copyright in it for any permitted purpose. However, you may only distribute
15
+ the software according to Distribution License and make changes or new works
16
+ based on the software according to Changes and New Works License.
17
+
18
+ Distribution License
19
+
20
+ The licensor grants you an additional copyright license to distribute copies of
21
+ the software. Your license to distribute covers distributing the software with
22
+ changes and new works permitted by Changes and New Works License.
23
+
24
+ Changes and New Works License
25
+
26
+ The licensor grants you an additional copyright license to make changes and new
27
+ works based on the software for any permitted purpose.
28
+
29
+ Patent License
30
+
31
+ The licensor grants you a patent license for the software that covers patent
32
+ claims the licensor can license, or becomes able to license, that you would
33
+ infringe by using the software.
34
+
35
+ Internal Use
36
+
37
+ Any use you make of the software must be for the internal business operations
38
+ of you or your company. You may not use the software, in whole or in part, for
39
+ the benefit of third parties or to publicly distribute applications or
40
+ services that incorporate the software.
41
+
42
+ Patent Defense
43
+
44
+ If you make any written claim that the software infringes or contributes to
45
+ infringement of any patent, your patent license for the software granted under
46
+ these terms ends immediately. If your company makes such a claim, your patent
47
+ license ends immediately for work on behalf of your company.
48
+
49
+ Violations
50
+
51
+ The first time you are notified in writing that you have violated any of these
52
+ terms, or done anything with the software not covered by your license, your
53
+ license ends immediately. If we make a written demand, you must, within 30
54
+ days, send written documentation that all violations have stopped and that you
55
+ have taken steps to prevent recurrence. If you do not, your rights end
56
+ immediately.
57
+
58
+ No Liability
59
+
60
+ ***As far as the law allows, the software comes as is, without any warranty
61
+ or condition, and the licensor will not be liable to you for any damages
62
+ arising out of these terms or the use or nature of the software, under any
63
+ kind of legal claim.***
64
+
65
+ Definitions
66
+
67
+ The "licensor" is the entity offering these terms, and the "software" is the
68
+ software the licensor makes available under these terms.
69
+
70
+ "You" refers to the individual or entity agreeing to these terms.
71
+
72
+ "Your company" is any legal entity, sole proprietorship, or other kind of
73
+ organization that you work for, plus all organizations that have control over,
74
+ are under the control of, or are under common control with that organization.
75
+
76
+ "Control" means ownership of substantially all the assets of an entity, or the
77
+ power to direct its management and policies by vote, contract, or otherwise.
78
+ Control can be direct or indirect.
79
+
80
+ "Your licenses" are all the licenses granted to you for the software under
81
+ these terms.
82
+
83
+ "Use" means anything you do with the software requiring one of your licenses.
84
+
85
+ Copyright (c) 2026 Moraya Project. All rights reserved.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @moraya/core
2
+
3
+ Shared markdown editor core for **Moraya desktop** (Tauri + Svelte 5) and **Moraya Web** (SvelteKit SPA). Pure ESM, host-agnostic, dependency-injected.
4
+
5
+ ## Status
6
+
7
+ - v0.1.0 (initial extraction from Moraya desktop `src/lib/editor/`)
8
+ - See `CHANGELOG.md`
9
+
10
+ ## Design
11
+
12
+ Per [v0.60.0-pre-shared-markdown-core](https://github.com/zouwei/moraya/blob/main/docs/iterations/v0.60.0-pre-shared-markdown-core.md):
13
+
14
+ - **Pure ESM** — no CommonJS, no Node API, no host-specific imports
15
+ - **DOM Level 4** required (ContentEditable + ResizeObserver + IntersectionObserver)
16
+ - **Dependency-injected** — `MediaResolver` / `LinkOpener` / `RendererRegistry` / `Platform`
17
+ - **5 peer deps** — `prosemirror-*`, `markdown-it`, `katex`, `highlight.js`
18
+ - **Bundle budget** — main entry ≤ 80KB gzipped
19
+
20
+ ## Public API
21
+
22
+ ```ts
23
+ import {
24
+ // schema
25
+ createSchema, type SchemaConfig,
26
+ // markdown
27
+ parseMarkdown, parseMarkdownAsync, serializeMarkdown,
28
+ // setup
29
+ createEditor, createEditorPlugins,
30
+ type EditorPluginOptions, type CreateEditorOptions, type MorayaEditorInstance,
31
+ // doc cache
32
+ createDocCache, type DocCache,
33
+ // commands
34
+ toggleBold, toggleItalic, toggleStrikethrough, toggleCode,
35
+ setHeading, toggleBlockquote, toggleOrderedList, toggleBulletList, toggleCodeBlock,
36
+ insertTable, insertHorizontalRule, insertMathBlock,
37
+ toggleLink, insertImage,
38
+ // types
39
+ type MediaResolver, type LinkOpener, type RendererRegistry, type RendererPluginModule, type Platform,
40
+ } from '@moraya/core'
41
+
42
+ import { BrowserMediaResolver } from '@moraya/core/adapters/browser-media-resolver'
43
+ import '@moraya/core/style'
44
+ ```
45
+
46
+ ## Consumer examples
47
+
48
+ ### Moraya desktop bridge (Svelte 5 + Tauri)
49
+
50
+ ```ts
51
+ import { createSchema } from '@moraya/core'
52
+ import { tauriMediaResolver } from './tauri-media-resolver'
53
+ import { morayaRendererRegistry } from './moraya-renderer-registry'
54
+
55
+ export const schema = createSchema({
56
+ mediaResolver: tauriMediaResolver,
57
+ rendererRegistry: morayaRendererRegistry,
58
+ })
59
+ ```
60
+
61
+ ### Moraya Web (SvelteKit SPA)
62
+
63
+ ```ts
64
+ import { createSchema } from '@moraya/core'
65
+ import { BrowserMediaResolver } from '@moraya/core/adapters/browser-media-resolver'
66
+
67
+ export const schema = createSchema({
68
+ mediaResolver: new BrowserMediaResolver(),
69
+ })
70
+ ```
71
+
72
+ ## Build & test
73
+
74
+ ```bash
75
+ pnpm install
76
+ pnpm build
77
+ pnpm test
78
+ ```
79
+
80
+ ## License
81
+
82
+ PolyForm Internal Use 1.0.0 (private; not for redistribution).
@@ -0,0 +1,21 @@
1
+ import { MediaResolver } from '../types.js';
2
+
3
+ /**
4
+ * Default browser-side {@link MediaResolver}.
5
+ *
6
+ * - Local file paths are not meaningful in a pure browser (no FS access);
7
+ * returns a 1×1 transparent PNG fallback URL with a warning.
8
+ * - Remote URLs are returned as-is (browser handles via native `img.src`).
9
+ *
10
+ * Per v0.60.0-pre §3.7: errors **resolve** with a fallback URL rather than
11
+ * reject, to prevent NodeView crashes on missing assets.
12
+ */
13
+ declare class BrowserMediaResolver implements MediaResolver {
14
+ /** 1×1 transparent PNG used as fallback for missing local assets. */
15
+ static readonly FALLBACK_PNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=";
16
+ loadLocalImage(_absolutePath: string): Promise<string>;
17
+ loadLocalMedia(_absolutePath: string): Promise<string>;
18
+ loadRemoteMedia(url: string): Promise<string>;
19
+ }
20
+
21
+ export { BrowserMediaResolver };
@@ -0,0 +1,24 @@
1
+ // src/adapters/browser-media-resolver.ts
2
+ var BrowserMediaResolver = class _BrowserMediaResolver {
3
+ /** 1×1 transparent PNG used as fallback for missing local assets. */
4
+ static FALLBACK_PNG = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=";
5
+ async loadLocalImage(_absolutePath) {
6
+ if (typeof console !== "undefined" && console.warn) {
7
+ console.warn("[BrowserMediaResolver] loadLocalImage called with local path; returning fallback PNG");
8
+ }
9
+ return _BrowserMediaResolver.FALLBACK_PNG;
10
+ }
11
+ async loadLocalMedia(_absolutePath) {
12
+ if (typeof console !== "undefined" && console.warn) {
13
+ console.warn("[BrowserMediaResolver] loadLocalMedia called with local path; returning empty fallback");
14
+ }
15
+ return _BrowserMediaResolver.FALLBACK_PNG;
16
+ }
17
+ async loadRemoteMedia(url) {
18
+ return url;
19
+ }
20
+ };
21
+ export {
22
+ BrowserMediaResolver
23
+ };
24
+ //# sourceMappingURL=browser-media-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/browser-media-resolver.ts"],"sourcesContent":["import type { MediaResolver } from '../types'\n\n/**\n * Default browser-side {@link MediaResolver}.\n *\n * - Local file paths are not meaningful in a pure browser (no FS access);\n * returns a 1×1 transparent PNG fallback URL with a warning.\n * - Remote URLs are returned as-is (browser handles via native `img.src`).\n *\n * Per v0.60.0-pre §3.7: errors **resolve** with a fallback URL rather than\n * reject, to prevent NodeView crashes on missing assets.\n */\nexport class BrowserMediaResolver implements MediaResolver {\n /** 1×1 transparent PNG used as fallback for missing local assets. */\n static readonly FALLBACK_PNG =\n 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII='\n\n async loadLocalImage(_absolutePath: string): Promise<string> {\n // Pure browser cannot read local FS paths. Return fallback so the NodeView\n // renders something instead of breaking. Consumers wanting browser-side\n // upload (drag-drop) should pre-convert to data: URL or blob: URL first.\n if (typeof console !== 'undefined' && console.warn) {\n console.warn('[BrowserMediaResolver] loadLocalImage called with local path; returning fallback PNG')\n }\n return BrowserMediaResolver.FALLBACK_PNG\n }\n\n async loadLocalMedia(_absolutePath: string): Promise<string> {\n if (typeof console !== 'undefined' && console.warn) {\n console.warn('[BrowserMediaResolver] loadLocalMedia called with local path; returning empty fallback')\n }\n return BrowserMediaResolver.FALLBACK_PNG\n }\n\n async loadRemoteMedia(url: string): Promise<string> {\n // Trust the browser to fetch http(s)/data:/blob: URLs directly.\n return url\n }\n}\n"],"mappings":";AAYO,IAAM,uBAAN,MAAM,sBAA8C;AAAA;AAAA,EAEzD,OAAgB,eACd;AAAA,EAEF,MAAM,eAAe,eAAwC;AAI3D,QAAI,OAAO,YAAY,eAAe,QAAQ,MAAM;AAClD,cAAQ,KAAK,sFAAsF;AAAA,IACrG;AACA,WAAO,sBAAqB;AAAA,EAC9B;AAAA,EAEA,MAAM,eAAe,eAAwC;AAC3D,QAAI,OAAO,YAAY,eAAe,QAAQ,MAAM;AAClD,cAAQ,KAAK,wFAAwF;AAAA,IACvG;AACA,WAAO,sBAAqB;AAAA,EAC9B;AAAA,EAEA,MAAM,gBAAgB,KAA8B;AAElD,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,35 @@
1
+ import { Command } from 'prosemirror-state';
2
+ export { Command, EditorState, Transaction } from 'prosemirror-state';
3
+ export { Node } from 'prosemirror-model';
4
+
5
+ declare const toggleBold: Command;
6
+ declare const toggleItalic: Command;
7
+ declare const toggleStrikethrough: Command;
8
+ declare const toggleCode: Command;
9
+ declare function setHeading(level: 1 | 2 | 3 | 4 | 5 | 6): Command;
10
+ declare const toggleBlockquote: Command;
11
+ declare const toggleOrderedList: Command;
12
+ declare const toggleBulletList: Command;
13
+ declare const wrapInBulletList: Command;
14
+ declare const wrapInOrderedList: Command;
15
+ /**
16
+ * Wrap current block(s) in a bullet list with task-list items (checked: false).
17
+ * Two-step: first apply wrapInList against the bullet_list type, then
18
+ * post-process newly-created list_item nodes within the affected range to
19
+ * set `checked: false` so they render as task items.
20
+ */
21
+ declare const wrapInTaskList: Command;
22
+ declare const toggleCodeBlock: Command;
23
+ declare const insertHorizontalRule: Command;
24
+ /**
25
+ * Insert a 3×3 placeholder table. Note: full table support requires
26
+ * prosemirror-tables setup at editor mount time; this command falls back
27
+ * to inserting a markdown-style code block snippet if tables aren't
28
+ * registered (true for this minimal v0.1.0 schema).
29
+ */
30
+ declare const insertTable: Command;
31
+ declare const insertMathBlock: Command;
32
+ declare function toggleLink(href?: string): Command;
33
+ declare function insertImage(src: string, alt?: string): Command;
34
+
35
+ export { insertHorizontalRule, insertImage, insertMathBlock, insertTable, setHeading, toggleBlockquote, toggleBold, toggleBulletList, toggleCode, toggleCodeBlock, toggleItalic, toggleLink, toggleOrderedList, toggleStrikethrough, wrapInBulletList, wrapInOrderedList, wrapInTaskList };