@meowdown/core 0.63.1 → 0.64.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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @meowdown/core
2
2
 
3
- The engine powering the editor in [`@meowdown/react`](https://www.npmjs.com/package/@meowdown/react): a hybrid (live-preview) Markdown editor core built on ProseKit and Lezer.
3
+ The engine behind Meowdown, a hybrid (live-preview) Markdown editor: Markdown
4
+ parsing, serializing, editing commands, and a default theme. Framework-free;
5
+ pairs with [@meowdown/react](https://www.npmjs.com/package/@meowdown/react).
4
6
 
5
7
  [**Live demo**](https://meowdown.vercel.app/)
6
8
 
@@ -25,40 +27,22 @@ editor.mount(document.querySelector<HTMLElement>('#editor')!)
25
27
  const markdown = docToMarkdown(editor.state.doc)
26
28
  ```
27
29
 
28
- ## Supported Markdown features
29
-
30
- - CommonMark
31
- - ATX headings (`# Heading 1`, `## Heading 2`, etc.)
32
- - Setext headings (`Heading 1\n===`, `Heading 2\n---`)
33
- - Bullet lists (`- item`, `* item`, `+ item`)
34
- - Ordered lists (`1. item`, `2) item`, etc.)
35
- - Blockquotes (`> quote`)
36
- - Fenced code blocks (` ```lang\ncode\n``` `), keeping tilde fences (`~~~`) and fence lengths through a round-trip
37
- - Indented code blocks (four leading spaces)
38
- - Thematic breaks (`---`, `***`, `___`)
39
- - Bold (`**bold**`), italic (`*italic*`), and inline code
40
- - Inline and reference links (`[text](url)`, `[text][label]`, `[label][]`, `[label]`)
41
- - Inline and reference images (`![alt](src)`, `![alt][label]`)
42
- - Link reference definitions (`[label]: url "title"`)
43
- - Autolinks (`<https://example.com>`)
44
- - Hard line breaks
45
- - GitHub Flavored Markdown (GFM)
46
- - Tables, including column alignment (`:--`, `:-:`, `--:`)
47
- - Strikethrough (`~~text~~`)
48
- - Task lists (`- [ ]`, `- [x]`)
49
- - Autolinks for `www.`, scheme, and email URLs
50
- - Wikilinks (`[[target]]` and `[[target|alias]]`)
51
- - Highlight (`==highlight==`)
30
+ ## Markdown features
31
+
32
+ [CommonMark](https://commonmark.org/) and [GFM](https://github.github.com/gfm/), plus:
33
+
34
+ - Wikilinks (`[[target]]`, `[[target|alias]]`)
35
+ - Wiki embeds (`![[path]]`)
52
36
  - Tags (`#tag`)
53
- - Bare-domain autolinks (`google.com`, `sub.domain.io/path`)
54
- - Math, compiled by KaTeX to native MathML (no stylesheet or fonts needed)
55
- - Inline math (`$x$` and `$$x$$`), Pandoc-style delimiter rules so `$20,000 and $30,000` stays plain text
56
- - Display math (`$$` fences on their own lines), kept as `$$` through a round-trip
57
- - ` ```math ` fenced code blocks
37
+ - Highlight (`==highlight==`)
38
+ - Math (`$x$`, `$$x$$`, and ` ```math ` fenced code blocks), rendered as native MathML
39
+ - Bare-domain autolinks (`google.com`), behind a curated TLD allowlist so `README.md` and `node.js` stay plain text
58
40
 
59
41
  ## Keyboard shortcuts
60
42
 
61
- `Mod` is Cmd on macOS and Ctrl elsewhere. Each formatting shortcut inserts or removes the literal Markdown delimiters around the selection; each heading shortcut toggles the current block to that level (or back to a paragraph).
43
+ `Mod` is Cmd on macOS and Ctrl elsewhere. Formatting shortcuts insert or remove
44
+ the literal Markdown delimiters around the selection; heading shortcuts toggle
45
+ the current block to that level (or back to a paragraph).
62
46
 
63
47
  | Key | Action | Markdown |
64
48
  | ---------------------- | --------------------------------------------------------- | ------------------- |
@@ -89,21 +73,24 @@ const markdown = docToMarkdown(editor.state.doc)
89
73
  | `Shift-Meta-ArrowDown` | Select to the document end | |
90
74
  | `Escape` | Collapse the selection | |
91
75
 
92
- The list-type toggles wrap the current block, convert a list of a different kind in place, and unwrap a list of the same kind back to a paragraph. `Mod-Shift-7/8/9` follow the physical digit key, so they work on layouts where Shift+digit types punctuation. `Alt-ArrowUp`/`Alt-ArrowDown` move a list item together with its nested children, or swap a non-list block with its neighbor. The `Meta-Arrow` document-boundary motions (Cmd on macOS) are bound explicitly because browsers do not reliably perform the native move (WebKit gives up when the document starts with a list marker). Typing `[` over a selection wraps it into an open wikilink (`[[selection`) with the wikilink menu searching it.
93
-
94
- `EDITOR_KEY_BINDINGS` is a literal (`as const`) object mapping every key above to its description, for host settings UIs and keybinding-collision checks.
76
+ `Mod-Shift-7/8/9` wrap the current block, convert a list of a different kind in
77
+ place, and unwrap a list of the same kind back to a paragraph. `Alt-ArrowUp` /
78
+ `Alt-ArrowDown` move the block or list item with its nested children. Typing `[`
79
+ over a selection wraps it into an open wikilink (`[[selection`) with the
80
+ wikilink menu searching it. `EDITOR_KEY_BINDINGS` exports this table as a
81
+ literal object for host settings UIs.
95
82
 
96
83
  ## Round-trip fidelity
97
84
 
98
- [`checkRoundTrip(markdown)`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-checkRoundTrip) reports how faithfully markdown survives a parse-then-serialize round trip: `'exact'`, `'normalizing'`, or `'lossy'`. Hosts that keep markdown on disk can gate saves on it, opening lossy files read-only so a save never rewrites content.
85
+ [`checkRoundTrip(markdown)`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-checkRoundTrip) reports how faithfully Markdown survives a parse-then-serialize round trip: `'exact'`, `'normalizing'`, or `'lossy'`.
99
86
 
100
87
  ## Styling
101
88
 
102
- `@meowdown/core/style.css` ships a default editor theme. Colors use `light-dark()`, so they follow the page's `color-scheme` (set `color-scheme: light dark` on `:root` for automatic dark mode). Theme it by overriding the `--meowdown-*` variables on `:root` or any ancestor. The full list, with a one-line description and default for each, lives in the commented `:root` block at the top of [`style.css`](./src/style.css), which is the single source of truth.
89
+ `@meowdown/core/style.css` ships a default theme. Colors use `light-dark()`, so they follow the page's `color-scheme` (set `color-scheme: light dark` on `:root` for automatic dark mode). Override the `--meowdown-*` variables on `:root` or any ancestor; the full list, with a one-line description and default for each, lives in the commented `:root` block at the top of `style.css`, which is the single source of truth.
103
90
 
104
- meowdown's CSS is wrapped in a cascade layer, `@layer meowdown` (with sub-layers `meowdown.base` for the bundled ProseMirror / prosekit base styles, `meowdown.theme` for the variables, and `meowdown.editor` for the editor rules). Because an un-layered rule always beats a layered one, **any plain rule you write overrides meowdown with no `!important` and no specificity hacks** (e.g. `.ProseMirror h1 { font-size: 2rem }` wins over meowdown's layered heading rule). Put your overrides outside any `@layer`, or in a layer you declare after `meowdown`.
91
+ CSS is wrapped in `@layer meowdown` (sub-layers `meowdown.base`, `meowdown.theme`, `meowdown.editor`). An un-layered rule always beats a layered one, so any plain rule you write overrides meowdown with no `!important` and no specificity hacks. Put your overrides outside any `@layer`, or in a layer you declare after `meowdown`.
105
92
 
106
- **With Tailwind CSS v4**, import `@meowdown/core/style.css` _after_ `@import 'tailwindcss'` so the `meowdown` layer sorts after Tailwind's `theme` / `base` / `components` / `utilities`. That keeps meowdown's editor styles from being reset by Tailwind's `base` (Preflight) while your own un-layered rules still win. One caveat: with that order the `meowdown` layer also sorts after `utilities`, so a Tailwind utility _class_ placed directly on an editor element will not beat meowdown. If you need utilities to win (while meowdown still beats Preflight), declare the layer order yourself with `utilities` last, referencing the umbrella `meowdown` layer (not its sub-layers). Doing this at the top of your CSS also pins the order even when meowdown's stylesheet is code-split / lazy-loaded:
93
+ **With Tailwind CSS v4**, import `@meowdown/core/style.css` _after_ `@import 'tailwindcss'` so the `meowdown` layer sorts after Tailwind's `base` (Preflight). If you also need Tailwind utilities to win over meowdown (while meowdown still beats Preflight), declare the layer order yourself:
107
94
 
108
95
  ```css
109
96
  @layer theme, base, components, meowdown, utilities;
@@ -111,36 +98,6 @@ meowdown's CSS is wrapped in a cascade layer, `@layer meowdown` (with sub-layers
111
98
  @import '@meowdown/core/style.css';
112
99
  ```
113
100
 
114
- Two things the variable list cannot show: `--meowdown-gutter` is the horizontal editor padding, applied to the editable root's `.meowdown-content` class, not `.ProseMirror`, so the block handle's drag preview stays unpadded; floating UI such as the block handle lives inside it, so keep it at least `3.5rem`. `defineEditorExtension` puts `.meowdown-content` on the editable root itself, so every mount (the headless quick start above included) is padded from the first paint. The selection variables (`--meowdown-selection`, `--meowdown-node-outline`, `--meowdown-node-selection`) are standalone, not derived from `--meowdown-accent`, so selection can be restyled independently.
115
-
116
- Tags (`#tag`) render as pills via the `.md-tag` class, tinted from `--meowdown-accent`. Wire click handling with `defineTagClickHandler(({ tag, event }) => ...)` (or `@meowdown/react`'s `onTagClick` prop); `tag` is read from the rendered text without the leading `#`.
117
-
118
- Wikilinks (`[[target]]`/`[[target|alias]]`) render in place via a mark view as an immutable label (the alias, or the target when there is no alias), with the raw source hidden in hide and focus modes and shown dimmed in show mode. The label uses the `.md-wikilink-view-label` class, dashed-underlined and colored by `--meowdown-accent`. In every mark mode the link is a single immutable caret stop: arrowing onto it selects the whole source (ringed with `--meowdown-node-outline` in hide and focus, the native selection over the visible source in show), and Backspace/Delete remove it as a unit, and `Enter` on the selected unit fires the click handler instead of replacing it. Wire click navigation with `defineWikilinkClickHandler(({ target, event }) => ...)` (or `@meowdown/react`'s `onWikilinkClick` prop); `Mod-Enter` with the caret on a wikilink, tag, or Markdown link fires the same handler, with the `KeyboardEvent` as `event`. `defineWikilinkHoverHandler` reports the hovered target, source range, and visible anchor element, then reports `undefined` on leave, deletion, replacement, or editor teardown.
119
-
120
- Markdown links render the label as an `<a href>` with the `.md-link` class, colored by `--meowdown-accent`; the source syntax dims in show mode and hides in hide and focus modes. This includes inline links (`[text](url)`), full references (`[text][label]`), collapsed references (`[label][]`), and shortcut references (`[label]`). Definitions use CommonMark label normalization and first-definition-wins semantics. Definitions remain visible and editable as literal source in the editor, while unresolved references remain literal and preserve nested formatting. Reference destinations are read-only through the link commands and menu because editing one safely requires a definition-aware workflow. Citing links update after a 200 ms pause in definition editing, which coalesces large fan-out restyles; definition-shaped textblocks longer than 1,024 characters stay literal and are not indexed. `[[target]]` continues to use meowdown's wikilink syntax. Wire click handling with `defineLinkClickHandler(({ href, event }) => ...)` (or `@meowdown/react`'s `onLinkClick` prop).
121
-
122
- Low-level renderers can import `collectReferenceDefinitions` and `inlineTextToMarkChunksWithContext` directly from `@meowdown/core` to share the editor's definition collection and reference resolution.
123
-
124
- Bare URLs autolink without `[text](url)` brackets and share the same `.md-link` rendering and click handling: a scheme URL (`https://example.com`), an angle autolink (`<https://example.com>`), a `www.` host (`www.example.com`), an email (`me@example.com`), and a bare domain (`google.com`, `sub.domain.io/path`). Bare domains are matched against a curated list of common TLDs, so file names and prose keep their dots without linkifying (`README.md`, `node.js`, `i.e.` stay plain text); reach for `[text](url)` or `<url>` to link anything off that list. Autolinks are derived live from the text, so editing one re-evaluates it; the caret sitting inside a link never un-links it.
125
-
126
- Inline and reference images (`![alt](src)`, `![alt][label]`, `![label][]`, and `![label]`) stay literal text and render in place via a mark view, with their raw source hidden in hide and focus modes. Add an inline image with [`defineImage`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineImage) (or `@meowdown/react`'s image props) and wire click handling with [`defineImageClickHandler`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineImageClickHandler) (`@meowdown/react`'s `onImageClick` prop).
127
-
128
- Pasted or dropped files persist through [`defineFilePaste`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineFilePaste) (or `@meowdown/react`'s `onFilePaste` prop): the handler persists each file and returns its markdown destination. An image (`image/*` MIME type) inserts `![](src)`; any other file inserts a `[name](src)` link; multiple files insert one link per line, in the order they appear in the drop. Without `onFilePaste`, file events are left to the browser's default handling. A host command that inserts file links itself (e.g. an attach-file picker) can build the same markdown with [`buildFileMarkdown`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-buildFileMarkdown).
129
-
130
- A host can render chosen file links as inline **file pills**: pass `resolveFileLink` to [`defineEditorExtension`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineEditorExtension) (or `@meowdown/react`'s `resolveFileLink` prop) to claim inline or resolved reference links by their href (e.g. everything under `assets/`), and add [`defineFileView`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineFileView) to render each claimed link as a pill: a file-kind icon, the name, and the size supplied (possibly async) by `resolveFileInfo`. The markdown text is untouched, and a claimed link behaves like an image: one caret unit with an editable source, clicks (and `Enter` on the selected pill or `Mod-Enter` with the caret on it) reported through [`defineFileClickHandler`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineFileClickHandler) (`@meowdown/react`'s `onFileClick`) rather than the link click handler, and no link hover or edit menu.
131
-
132
- Rendered images are resizable: drag the corner handle and the chosen size is written back into the source as a trailing comment, `![alt](src)<!-- {"width":320,"height":240} -->`, which round-trips as plain Markdown. A comment immediately after an image is folded into its mark and drives the image's `width` and `height` attributes, so the box keeps its dimensions before the image loads; any other comment stays literal text.
133
-
134
- GFM table column alignment (`| :-: |` in the delimiter row) is kept as an `align` attribute on every cell and rendered through a `data-align` DOM attribute (`text-align` in the bundled stylesheet). Alignment is a column property: the header row's cells carry the source of truth, data cells (including rows inserted later) follow it automatically. Change it with the `setTableColumnAlign` command (`editor.commands.setTableColumnAlign('center')`, `null` resets to `---`), and read the alignment of the selection's column with [`getTableColumnAlign`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-getTableColumnAlign).
135
-
136
- Pasting a lone tweet or YouTube link can auto-embed it: [`defineEmbedPaste`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineEmbedPaste) (`@meowdown/react`'s `embedPaste` prop, on by default).
137
-
138
- The clipboard pipeline ships inside `defineEditorExtension`. Copying writes two flavors: `text/html` is standard semantic HTML (`<h3>`, `<ol><li>`, `<strong>`, real `<table>`) with the Markdown source preserved in `data-md` attributes so meowdown-to-meowdown pastes stay byte-exact, and `text/plain` is Markdown, where opening markers such as ATX heading and blockquote prefixes are kept when the selection includes the block's content start, while fenced code blocks and tables keep their markers only when selected completely; the mark mode decides the inline layer (hide strips the syntax characters, focus and show keep the full source). Pasting picks a path by flavor: meowdown's own HTML (stamped `data-meowdown`) parses natively; foreign rich-text HTML, including other ProseMirror editors, converts to Markdown through the unified (rehype / remark) pipeline; plain text follows Markdown newline semantics (a blank line separates paragraphs without inserting an empty one, a single newline stays a soft break) while a Shift-paste keeps ProseMirror's line-per-paragraph behavior; a paste landing in a code block stays plain text.
139
-
140
- Enter at the end of the document's first heading (the title line) can start a fresh empty bullet instead of a plain paragraph: [`defineBulletAfterHeading`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineBulletAfterHeading) (`@meowdown/react`'s `bulletAfterHeading` prop). Not part of `defineEditorExtension`.
141
-
142
- An arrow press that can move the caret no further can notify the host, so it can move focus elsewhere (a previous/next note or page): [`defineExitBoundaryHandler`](https://npmx.dev/package-docs/@meowdown%2Fcore#function-defineExitBoundaryHandler) (`@meowdown/react`'s `onExitBoundary` prop). Not part of `defineEditorExtension`.
143
-
144
101
  ## API
145
102
 
146
103
  See the full API reference [here](https://npmx.dev/package-docs/@meowdown%2Fcore/).
package/dist/index.d.ts CHANGED
@@ -448,6 +448,10 @@ interface FileLinkPayload {
448
448
  type FileLinkResolver = (link: FileLinkPayload) => boolean;
449
449
  /** Host options that influence inline parsing. */
450
450
  interface FileLinkOptions {
451
+ /**
452
+ * Claim `[label](url)` links as file attachments; see {@link FileLinkResolver}.
453
+ * Read once when the editor is created.
454
+ */
451
455
  resolveFileLink?: FileLinkResolver;
452
456
  }
453
457
  /** Host options that influence source-backed inline atom parsing. */
@@ -929,6 +933,11 @@ interface LinkCopyPayload {
929
933
  href: string;
930
934
  }
931
935
  type LinkCopyHandler = (payload: LinkCopyPayload) => void;
936
+ /**
937
+ * Call `onClick` when the user clicks a rendered Markdown link
938
+ * (`[text](url)`), or presses `Mod-Enter` with the caret on one. The `event`
939
+ * is the originating `MouseEvent` or `KeyboardEvent`.
940
+ */
932
941
  declare function defineLinkClickHandler(onClick: LinkClickHandler): PlainExtension;
933
942
  //#endregion
934
943
  //#region src/extensions/tag-click.d.ts
@@ -942,6 +951,11 @@ interface TagClickPayload {
942
951
  event: MouseEvent | KeyboardEvent;
943
952
  }
944
953
  type TagClickHandler = (payload: TagClickPayload) => void;
954
+ /**
955
+ * Call `onClick` when the user clicks a rendered `#tag`, or presses
956
+ * `Mod-Enter` with the caret on one. The `tag` is read from the rendered text
957
+ * without the leading `#`.
958
+ */
945
959
  declare function defineTagClickHandler(onClick: TagClickHandler): PlainExtension;
946
960
  //#endregion
947
961
  //#region src/extensions/wikilink-click.d.ts
@@ -956,6 +970,11 @@ interface WikilinkClickPayload {
956
970
  event: MouseEvent | KeyboardEvent;
957
971
  }
958
972
  type WikilinkClickHandler = (payload: WikilinkClickPayload) => void;
973
+ /**
974
+ * Call `onClick` when the user clicks a rendered wikilink label, or presses
975
+ * `Mod-Enter` with the caret on one. The `event` is the originating
976
+ * `MouseEvent` or `KeyboardEvent`.
977
+ */
959
978
  declare function defineWikilinkClickHandler(onClick: WikilinkClickHandler): PlainExtension;
960
979
  //#endregion
961
980
  //#region src/extensions/follow-link.d.ts
@@ -1021,7 +1040,13 @@ interface ImageOptions {
1021
1040
  }
1022
1041
  /** Show an `src` as-is when it is an http(s) URL, otherwise skip rendering it. */
1023
1042
  declare function defaultResolveImageUrl(src: string): string | undefined;
1024
- /** Inline image/embed rendering: a mark view on the `mdImage` mark. */
1043
+ /**
1044
+ * Inline image/embed rendering: a mark view on the `mdImage` mark. Images
1045
+ * render in place from their literal Markdown source. Drag a rendered image's
1046
+ * corner handle to write the size back into the source as a trailing comment,
1047
+ * `![alt](src)<!-- {"width":320,"height":240} -->`, which round-trips as
1048
+ * plain Markdown.
1049
+ */
1025
1050
  declare function defineImage(options?: ImageOptions): PlainExtension;
1026
1051
  //#endregion
1027
1052
  //#region src/extensions/key-bindings.d.ts
@@ -1131,6 +1156,12 @@ declare function defineViewAttributes(attributes: {
1131
1156
  * so IME, clicks, and typing keep their native behavior; only the caret pixels
1132
1157
  * are ours. Applies to every mark mode.
1133
1158
  *
1159
+ * On a touch screen, while the last input was a finger or pen
1160
+ * ({@link getIsTouchInput}), the roles flip: the native caret stays visible
1161
+ * (it carries the system touch affordances: the drag magnifier, the caret-drag
1162
+ * long-press mode) and the virtual caret draws only at positions where the
1163
+ * native caret has no geometry, such as beside hidden Markdown syntax.
1164
+ *
1134
1165
  * `layer` is the element the caret draws into. The host owns its placement:
1135
1166
  * it must live outside the contenteditable and scroll together with the
1136
1167
  * content.