@meowdown/react 0.23.0 → 0.24.1
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 +23 -79
- package/dist/index.d.ts +12 -1
- package/dist/index.js +9 -4
- package/dist/style.css +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
React components for Meowdown, a hybrid (live-preview) Markdown editor.
|
|
4
4
|
|
|
5
|
+
[**Live demo**](https://meowdown.vercel.app/)
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
Install the package and its peer dependencies:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @meowdown/react @meowdown/core react react-dom
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Import both stylesheets and render the editor:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import '@meowdown/core/style.css'
|
|
19
|
+
import '@meowdown/react/style.css'
|
|
20
|
+
import { MeowdownEditor } from '@meowdown/react'
|
|
21
|
+
|
|
22
|
+
export function App() {
|
|
23
|
+
return <MeowdownEditor initialMarkdown="# Hello" />
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
5
27
|
## Usage
|
|
6
28
|
|
|
7
29
|
```tsx
|
|
@@ -30,85 +52,7 @@ export function App() {
|
|
|
30
52
|
|
|
31
53
|
## API
|
|
32
54
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
The Markdown editor component. Renders inside a `div.meowdown` wrapper that fills a flex parent. In rich modes, typing `/` opens a slash menu for inserting blocks (headings, blockquote, lists, code block, table). Hovering a block shows a handle to its left: the grip selects the block and can be dragged to move it, with a drop indicator line marking the target.
|
|
36
|
-
|
|
37
|
-
- `mode?: 'focus' | 'show' | 'hide' | 'source'`: defaults to `'focus'`.
|
|
38
|
-
- `'focus'`: Markdown syntax is hidden, revealed around the cursor.
|
|
39
|
-
- `'show'`: Markdown syntax is always visible.
|
|
40
|
-
- `'hide'`: Markdown syntax is always hidden.
|
|
41
|
-
- `'source'`: raw Markdown source with syntax highlighting.
|
|
42
|
-
- `initialMarkdown?: string`: first render only.
|
|
43
|
-
- `onDocChange?: VoidFunction`: called on every user-driven document change. Programmatic `setMarkdown` / `setState` on the handle do not fire it.
|
|
44
|
-
- `onTagSearch?: (query: string) => TagItem[] | Promise<TagItem[]>`: enables the tag menu, which opens when typing `#` followed by text in a rich mode. Returns ranked rows `{ tag, label?, detail?, onSelect? }` (the menu does not re-sort). Selecting a row inserts `#tag ` then runs its `onSelect`. Omit to disable.
|
|
45
|
-
- `onWikilinkSearch?: (query: string) => WikilinkItem[] | Promise<WikilinkItem[]>`: enables the wikilink menu, which opens as soon as `[[` or `@` is typed, or `Mod-Shift-k` is pressed, in a rich mode; the shortcut seeds the query from any selected text. Returns ranked rows `{ target, label?, detail?, onSelect? }` (the menu does not re-sort). Selecting a row inserts `[[target]]` then runs its `onSelect`. Omit to disable.
|
|
46
|
-
- `onWikilinkClick?: (payload: { target: string; event: MouseEvent }) => void`: called when a rendered wiki link is clicked. Pass a stable function (e.g. from `useCallback`). Ignored in source mode.
|
|
47
|
-
- `onLinkClick?: (payload: { href: string; event: MouseEvent }) => void`: called with its `href` when a rendered Markdown link (`[text](url)`) is clicked. Pass a stable function (e.g. from `useCallback`). Ignored in source mode.
|
|
48
|
-
- `onTagClick?: (payload: { tag: string; event: MouseEvent }) => void`: called with the tag name (without the leading `#`) when a rendered `#tag` is clicked. Pass a stable function (e.g. from `useCallback`). Ignored in source mode.
|
|
49
|
-
- `resolveImageUrl?: (src: string) => string | undefined`: maps an image `src` to a displayable URL (or `undefined` to skip). Enables inline image rendering: `` stays literal text and the image renders beneath its line. Pass a stable function. Ignored in source mode.
|
|
50
|
-
- `onImagePaste?: (file: File) => string | undefined | Promise<string | undefined>`: persists a pasted or dropped image file and returns its markdown `src` (or `undefined` to decline), synchronously or as a promise. Pass a stable function. Ignored in source mode.
|
|
51
|
-
- `onImageSaveError?: (error: unknown, file: File) => void`: called when `onImagePaste` throws. Defaults to `console.error`. Ignored in source mode.
|
|
52
|
-
- `onImageClick?: (payload: { src: string; alt: string; event: MouseEvent }) => void`: called when a rendered image is clicked, with its markdown `src`, `alt`, and the originating `MouseEvent`. Pass a stable function (e.g. from `useCallback`). Ignored in source mode.
|
|
53
|
-
- `embedPaste?: boolean`: auto-embeds a pasted tweet or YouTube link as a rich embed; one undo turns the embed back into the raw link. On by default; set `false` to disable. Only takes effect when `resolveImageUrl` is set, since embeds render through the image pipeline. Ignored in source mode.
|
|
54
|
-
- `bulletAfterHeading?: boolean`: pressing Enter at the end of the document's first heading (the title line) starts a fresh empty bullet on the next line instead of a plain paragraph. Off by default. Ignored in source mode.
|
|
55
|
-
- `blockHandle?: boolean`: shows the per-block gutter handle in the rich modes (a drag grip for reordering blocks, plus the drop indicator). On by default; set `false` to hide the gutter affordance entirely, e.g. when the host does not want block reordering. Ignored in source mode and when `readOnly` is set.
|
|
56
|
-
- `placeholder?: string | ((state) => string)`: placeholder text shown when the whole document is empty. Pass a stable function. Ignored in source mode.
|
|
57
|
-
- `readOnly?: boolean`: makes the editor read-only, in both the rich and source modes.
|
|
58
|
-
- `spellCheck?: boolean`: toggles the browser's native spell checking in the rich modes. Defaults to the browser's behavior. Ignored in source mode.
|
|
59
|
-
- `editorClassName?: string`: class on the editable root (the contenteditable). Rich modes only.
|
|
60
|
-
- `wrapperClassName?: string`: class on the outer `div.meowdown` wrapper.
|
|
61
|
-
- `handleRef?: Ref<EditorHandle>`
|
|
62
|
-
- `children?: ReactNode`: rendered inside the editor's ProseKit context, so children can call `useEditor()`. Only rendered in the rich modes; source mode ignores them.
|
|
63
|
-
|
|
64
|
-
### `<MarkdownView>`
|
|
65
|
-
|
|
66
|
-
A read-only renderer that turns Markdown into a React tree styled exactly like `<MeowdownEditor>` in `hide` mode: inline marks, wiki-link chips, images, tweet/YouTube embeds, and syntax-highlighted code. It mounts no editor and holds no ProseMirror view, so it is cheap to render many times (backlink lists, search results, previews). Import `@meowdown/core/style.css` for the theme; the root carries the `ProseMirror` and `meowdown-content` classes plus `data-mark-mode`, so the editor stylesheet applies unchanged.
|
|
67
|
-
|
|
68
|
-
```tsx
|
|
69
|
-
import '@meowdown/core/style.css'
|
|
70
|
-
import { MarkdownView } from '@meowdown/react'
|
|
71
|
-
;<MarkdownView
|
|
72
|
-
markdown="See [[Some Note]] and **bold**."
|
|
73
|
-
onWikilinkClick={({ target }) => open(target)}
|
|
74
|
-
/>
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
- `markdown: string`: the Markdown to render. Live: changing it re-renders.
|
|
78
|
-
- `markMode?: 'hide' | 'focus' | 'show'`: defaults to `'hide'`.
|
|
79
|
-
- `frontmatter?: boolean`: peel a leading YAML frontmatter block first. Off by default.
|
|
80
|
-
- `resolveImageUrl?`, `onWikilinkClick?`, `onLinkClick?`, `onImageClick?`: the same shapes as the matching `<MeowdownEditor>` props. Pass stable functions.
|
|
81
|
-
- `className?: string`: extra class on the content root.
|
|
82
|
-
|
|
83
|
-
Code blocks render their content and syntax highlighting (the same `tok-*` classes as the editor) but without the editor's language picker / copy toolbar. `<MarkdownView>` requires a DOM environment; it is not a server-side HTML-string renderer.
|
|
84
|
-
|
|
85
|
-
### `useEditor`
|
|
86
|
-
|
|
87
|
-
Re-exported from `@prosekit/react`. Call it from a component passed as `children` to read the live editor instance.
|
|
88
|
-
|
|
89
|
-
### `useKeymap`
|
|
90
|
-
|
|
91
|
-
Re-exported from `@prosekit/react`. Registers a keymap on the editor from a `children` component; set its priority with `Priority` from `@meowdown/core`.
|
|
92
|
-
|
|
93
|
-
### `useExtension`
|
|
94
|
-
|
|
95
|
-
Re-exported from `@prosekit/react`. Applies an extension to the editor from a `children` component.
|
|
96
|
-
|
|
97
|
-
### `EditorHandle`
|
|
98
|
-
|
|
99
|
-
Imperative handle for the editor, attached via `handleRef`.
|
|
100
|
-
|
|
101
|
-
- `getMarkdown(): string`: serializes the current document to Markdown. Can be expensive on large documents; call it on demand (e.g. throttled) instead of on every change.
|
|
102
|
-
- `setMarkdown(markdown: string): void`: replaces the whole document as a single undoable edit. Does not fire `onDocChange`.
|
|
103
|
-
- `getState(): EditorStateSnapshot`: returns `[markdown, selection]`, where `selection` is a `SelectionJSON` (`{ anchor: number, head: number, type: string }`).
|
|
104
|
-
- `setState(markdown?: string, selection?: SelectionJSON | 'start' | 'end'): void`: replaces the document (if `markdown` is given) and restores `selection`: exactly when valid, otherwise clamped to the nearest text selection; out-of-range positions never throw. `'start'` and `'end'` jump to the document edges. Without a selection, the current one is mapped through the change. Restore a snapshot with `handle.setState(...handle.getState())`.
|
|
105
|
-
- `getSelection(): SelectionJSON`: returns the current selection.
|
|
106
|
-
- `setSelection(selection: SelectionJSON | 'start' | 'end'): void`: restores a selection with the same hint semantics as `setState`.
|
|
107
|
-
- `focus(): void`: focuses the editor.
|
|
108
|
-
- `scrollIntoView(): void`: scrolls the selection into view.
|
|
109
|
-
- `editor: TypedEditor | undefined`: escape hatch for the underlying ProseKit editor, `undefined` in source mode. No stability guarantees beyond what `@meowdown/core` exports.
|
|
110
|
-
|
|
111
|
-
Selection positions are in the mounted editor's coordinate space: ProseMirror document positions in the rich modes, character offsets in source mode. They round-trip within one mode but are not portable across a mode switch.
|
|
55
|
+
See the full API reference [here](https://npmx.dev/package-docs/@meowdown%2Freact/).
|
|
112
56
|
|
|
113
57
|
## Styling
|
|
114
58
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactElement, ReactNode, Ref } from "react";
|
|
2
|
-
import { ImageClickHandler, ImageOptions, LinkClickHandler, MarkMode, PlaceholderOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
|
|
2
|
+
import { ExitBoundaryHandler, ImageClickHandler, ImageOptions, LinkClickHandler, MarkMode, PlaceholderOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
|
|
3
3
|
import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
|
|
4
4
|
import { useEditor, useExtension, useKeymap } from "@prosekit/react";
|
|
5
5
|
|
|
@@ -130,6 +130,16 @@ interface EditorProps {
|
|
|
130
130
|
* mode.
|
|
131
131
|
*/
|
|
132
132
|
onTagClick?: TagClickHandler;
|
|
133
|
+
/**
|
|
134
|
+
* Called when the caret can move no further in the pressed arrow direction
|
|
135
|
+
* and leaves the document boundary: ArrowUp on the first visual line,
|
|
136
|
+
* ArrowDown on the last, or an arrow press on a selected node at the edge.
|
|
137
|
+
* Use it to move focus to a previous/next note or page. Receives the
|
|
138
|
+
* `direction` and the original `KeyboardEvent`. Return `false` to let the
|
|
139
|
+
* editor handle the key normally; any other return value consumes it. Pass a
|
|
140
|
+
* stable function (e.g. from `useCallback`). Ignored in source mode.
|
|
141
|
+
*/
|
|
142
|
+
onExitBoundary?: ExitBoundaryHandler;
|
|
133
143
|
/**
|
|
134
144
|
* Maps an image `src` to a displayable URL, or `undefined` to skip that image.
|
|
135
145
|
* Defaults to showing http(s) URLs as-is. Pass a stable function (e.g. from
|
|
@@ -200,6 +210,7 @@ declare function MeowdownEditor({
|
|
|
200
210
|
onWikilinkClick,
|
|
201
211
|
onLinkClick,
|
|
202
212
|
onTagClick,
|
|
213
|
+
onExitBoundary,
|
|
203
214
|
resolveImageUrl,
|
|
204
215
|
onImagePaste,
|
|
205
216
|
onImageSaveError,
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Compartment, EditorSelection, EditorState } from "@codemirror/state";
|
|
|
7
7
|
import { EditorView, keymap } from "@codemirror/view";
|
|
8
8
|
import { clamp } from "@ocavue/utils";
|
|
9
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
-
import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineMarkMode, defineMarkdownCopy, definePlaceholder, defineReadonly, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, inlineTextToMarkChunks, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
|
|
10
|
+
import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineMarkMode, defineMarkdownCopy, definePlaceholder, defineReadonly, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, inlineTextToMarkChunks, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
|
|
11
11
|
import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, union } from "@prosekit/core";
|
|
12
12
|
import { Selection, TextSelection } from "@prosekit/pm/state";
|
|
13
13
|
import { ProseKit, defineReactNodeView, useEditor, useEditor as useEditor$1, useEditorDerivedValue, useExtension, useExtension as useExtension$1, useKeymap } from "@prosekit/react";
|
|
@@ -320,7 +320,7 @@ function DropIndicator$1() {
|
|
|
320
320
|
|
|
321
321
|
//#endregion
|
|
322
322
|
//#region src/components/editor-extensions.tsx
|
|
323
|
-
function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick, onTagClick, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste, bulletAfterHeading, placeholder, readOnly, wikilinkEnabled }) {
|
|
323
|
+
function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste, bulletAfterHeading, placeholder, readOnly, wikilinkEnabled }) {
|
|
324
324
|
useExtension$1(useMemo(() => {
|
|
325
325
|
return defineMarkMode(markMode);
|
|
326
326
|
}, [markMode]));
|
|
@@ -339,6 +339,9 @@ function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick,
|
|
|
339
339
|
useExtension$1(useMemo(() => {
|
|
340
340
|
return onTagClick ? defineTagClickHandler(onTagClick) : null;
|
|
341
341
|
}, [onTagClick]));
|
|
342
|
+
useExtension$1(useMemo(() => {
|
|
343
|
+
return onExitBoundary ? defineExitBoundaryHandler(onExitBoundary) : null;
|
|
344
|
+
}, [onExitBoundary]));
|
|
342
345
|
useExtension$1(useMemo(() => {
|
|
343
346
|
return defineImage({
|
|
344
347
|
resolveImageUrl,
|
|
@@ -777,7 +780,7 @@ function resolveSelection(doc, selection) {
|
|
|
777
780
|
return TextSelection.between(doc.resolve(anchor), doc.resolve(head));
|
|
778
781
|
}
|
|
779
782
|
}
|
|
780
|
-
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onTagClick, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste, bulletAfterHeading, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, editorClassName, ref, children }) {
|
|
783
|
+
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste, bulletAfterHeading, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, editorClassName, ref, children }) {
|
|
781
784
|
const [editor] = useState(() => {
|
|
782
785
|
const editor = createEditor({ extension: union(defineEditorExtension(), defineCodeBlockView()) });
|
|
783
786
|
if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
|
|
@@ -860,6 +863,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
860
863
|
onWikilinkClick,
|
|
861
864
|
onLinkClick,
|
|
862
865
|
onTagClick,
|
|
866
|
+
onExitBoundary,
|
|
863
867
|
resolveImageUrl,
|
|
864
868
|
onImagePaste,
|
|
865
869
|
onImageSaveError,
|
|
@@ -883,7 +887,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
883
887
|
|
|
884
888
|
//#endregion
|
|
885
889
|
//#region src/components/editor.tsx
|
|
886
|
-
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onTagClick, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste = true, bulletAfterHeading = false, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, editorClassName, wrapperClassName, handleRef, children }) {
|
|
890
|
+
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste = true, bulletAfterHeading = false, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, editorClassName, wrapperClassName, handleRef, children }) {
|
|
887
891
|
const childRef = useRef(null);
|
|
888
892
|
useImperativeHandle(handleRef, () => {
|
|
889
893
|
function getMarkdown() {
|
|
@@ -950,6 +954,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSea
|
|
|
950
954
|
onWikilinkClick,
|
|
951
955
|
onLinkClick,
|
|
952
956
|
onTagClick,
|
|
957
|
+
onExitBoundary,
|
|
953
958
|
resolveImageUrl,
|
|
954
959
|
onImagePaste,
|
|
955
960
|
onImageSaveError,
|
package/dist/style.css
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meowdown/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.24.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@base-ui/react": "^1.6.0",
|
|
22
|
-
"@codemirror/commands": "^6.10.
|
|
22
|
+
"@codemirror/commands": "^6.10.4",
|
|
23
23
|
"@codemirror/lang-markdown": "^6.5.0",
|
|
24
24
|
"@codemirror/language": "^6.12.3",
|
|
25
|
-
"@codemirror/state": "^6.
|
|
25
|
+
"@codemirror/state": "^6.7.0",
|
|
26
26
|
"@codemirror/view": "^6.43.1",
|
|
27
27
|
"@ocavue/utils": "^1.7.0",
|
|
28
28
|
"@prosekit/core": "^0.13.0-beta.3",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@prosekit/react": "^0.8.0-beta.11",
|
|
31
31
|
"clsx": "^2.1.1",
|
|
32
32
|
"lucide-react": "^1.21.0",
|
|
33
|
-
"@meowdown/core": "0.
|
|
33
|
+
"@meowdown/core": "0.24.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": "^19.0.0",
|