@meowdown/react 0.4.0 → 0.6.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
@@ -5,7 +5,7 @@ React components for Meowdown, a hybrid (live-preview) Markdown editor.
5
5
  ## Usage
6
6
 
7
7
  ```tsx
8
- import { Editor, type EditorHandle } from '@meowdown/react'
8
+ import { MeowdownEditor, type EditorHandle } from '@meowdown/react'
9
9
  import '@meowdown/react/style.css'
10
10
  import { useRef, useCallback } from 'react'
11
11
 
@@ -15,13 +15,20 @@ export function App() {
15
15
  console.log(ref.current?.getMarkdown())
16
16
  }, [])
17
17
 
18
- return <Editor ref={ref} mode="focus" initialMarkdown="# Hello" onDocChange={handleDocChange} />
18
+ return (
19
+ <MeowdownEditor
20
+ handleRef={ref}
21
+ mode="focus"
22
+ initialMarkdown="# Hello"
23
+ onDocChange={handleDocChange}
24
+ />
25
+ )
19
26
  }
20
27
  ```
21
28
 
22
29
  ## API
23
30
 
24
- ### `<Editor>`
31
+ ### `<MeowdownEditor>`
25
32
 
26
33
  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 plus button inserts an empty paragraph below the block, and the grip selects the block and can be dragged to move it, with a drop indicator line marking the target.
27
34
 
@@ -31,23 +38,46 @@ The Markdown editor component. Renders inside a `div.meowdown` wrapper that fill
31
38
  - `'hide'`: Markdown syntax is always hidden.
32
39
  - `'source'`: raw Markdown source with syntax highlighting.
33
40
  - `initialMarkdown?: string`: first render only.
34
- - `onDocChange?: VoidFunction`: called on every document change.
35
- - `onTagSearch?: (query: string) => string[] | Promise<string[]>`: enables the tag menu, which opens when typing `#` followed by text in a rich mode; returns the tags to show for a query (lowercased, punctuation stripped). Omit to disable.
36
- - `onWikilinkSearch?: (query: string) => string[] | Promise<string[]>`: enables the wikilink menu, which opens as soon as `[[` is typed in a rich mode; returns the note names to show for a query (lowercased, punctuation stripped, may be empty). Selecting a note inserts `[[Note Name]]`. Omit to disable.
37
- - `ref?: Ref<EditorHandle>`
41
+ - `onDocChange?: VoidFunction`: called on every user-driven document change. Programmatic `setMarkdown` / `setState` on the handle do not fire it.
42
+ - `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.
43
+ - `onWikilinkSearch?: (query: string) => WikilinkItem[] | Promise<WikilinkItem[]>`: enables the wikilink menu, which opens as soon as `[[` is typed in a rich mode. 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.
44
+ - `onWikilinkClick?: (payload: { target: string; event: MouseEvent }) => void`: called when a rendered wiki link is clicked. A plain click inside a link the caret already sits in just places the caret; `Mod`-click always fires. Pass a stable function (e.g. from `useCallback`). Ignored in source mode.
45
+ - `resolveImageUrl?: (src: string) => string | undefined`: maps an image `src` to a displayable URL (or `undefined` to skip). Enables inline image rendering: `![alt](src)` stays literal text and the image renders beneath its line. Pass a stable function. Ignored in source mode.
46
+ - `onImagePaste?: (file: File) => Promise<string | undefined>`: persists a pasted or dropped image file and returns its markdown `src` (or `undefined` to decline). Pass a stable function. Ignored in source mode.
47
+ - `onImageSaveError?: (error: Error, file: File) => void`: called when `onImagePaste` throws. Defaults to `console.error`. Ignored in source mode.
48
+ - `placeholder?: string | ((state) => string)`: placeholder text shown in an empty block. Pass a stable function. Ignored in source mode.
49
+ - `readOnly?: boolean`: makes the editor read-only, in both the rich and source modes.
50
+ - `spellCheck?: boolean`: toggles the browser's native spell checking in the rich modes. Defaults to the browser's behavior. Ignored in source mode.
51
+ - `editorClassName?: string`: class on the editable root (the contenteditable). Rich modes only.
52
+ - `wrapperClassName?: string`: class on the outer `div.meowdown` wrapper.
53
+ - `handleRef?: Ref<EditorHandle>`
54
+ - `children?: ReactNode`: rendered inside the editor's ProseKit context, so children can call `useEditor()`. Only rendered in the rich modes; source mode ignores them.
55
+
56
+ ### `useEditor`
57
+
58
+ Re-exported from `@prosekit/react`. Call it from a component passed as `children` to read the live editor instance.
59
+
60
+ ### `checkRoundTrip`
61
+
62
+ Re-exported from `@meowdown/core`. `checkRoundTrip(markdown)` returns `'exact' | 'normalizing' | 'lossy'`, for hosts that gate saving markdown files on whether the editor reproduces them faithfully.
63
+
64
+ ### `EDITOR_KEY_BINDINGS`
65
+
66
+ Re-exported from `@meowdown/core`. A literal (`as const`) object mapping each editor shortcut (e.g. `Mod-b`, `Mod-1`) to its description, for host settings UIs and keybinding-collision checks.
38
67
 
39
68
  ### `EditorHandle`
40
69
 
41
- Imperative handle for the editor, attached via `ref`.
70
+ Imperative handle for the editor, attached via `handleRef`.
42
71
 
43
72
  - `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.
44
- - `setMarkdown(markdown: string): void`: replaces the whole document as a single undoable edit.
73
+ - `setMarkdown(markdown: string): void`: replaces the whole document as a single undoable edit. Does not fire `onDocChange`.
45
74
  - `getState(): EditorStateSnapshot`: returns `[markdown, selection]`, where `selection` is a `SelectionJSON` (`{ anchor: number, head: number, type: string }`).
46
75
  - `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())`.
47
76
  - `getSelection(): SelectionJSON`: returns the current selection.
48
77
  - `setSelection(selection: SelectionJSON | 'start' | 'end'): void`: restores a selection with the same hint semantics as `setState`.
49
78
  - `focus(): void`: focuses the editor.
50
79
  - `scrollIntoView(): void`: scrolls the selection into view.
80
+ - `editor: TypedEditor | undefined`: escape hatch for the underlying ProseKit editor, `undefined` in source mode. No stability guarantees beyond what `@meowdown/core` exports.
51
81
 
52
82
  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.
53
83
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { Ref } from "react";
2
- import { MarkMode, MarkMode as MarkMode$1, TypedEditor } from "@meowdown/core";
1
+ import { ReactNode, Ref } from "react";
2
+ import { EDITOR_KEY_BINDINGS, ImageOptions, MarkMode, MarkMode as MarkMode$1, PlaceholderOptions, RoundTripFidelity, TypedEditor, TypedEditor as TypedEditor$1, WikilinkClickHandler, checkRoundTrip } from "@meowdown/core";
3
3
  import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
4
+ import { useEditor } from "@prosekit/react";
4
5
 
5
6
  //#region src/components/types.d.ts
6
7
  /** A selection to restore: an exact JSON selection, or a document edge. */
@@ -36,19 +37,44 @@ interface EditorHandle {
36
37
  focus: () => void;
37
38
  /** Scrolls the selection into view. */
38
39
  scrollIntoView: () => void;
40
+ /** Escape hatch: the underlying ProseKit editor, or `undefined` in source mode. */
41
+ readonly editor: TypedEditor$1 | undefined;
42
+ }
43
+ /**
44
+ /** One row in the tag menu. The host ranks the rows; the menu does not re-sort. */
45
+ interface TagItem {
46
+ /** Inserted as `#tag `. */
47
+ tag: string;
48
+ /** Display text; defaults to `#tag`. */
49
+ label?: string;
50
+ /** Secondary text shown beside the label. */
51
+ detail?: string;
52
+ /** Side effect run after the tag is inserted (e.g. create the tag). */
53
+ onSelect?: () => void;
39
54
  }
40
55
  /**
41
56
  * Searches tags for the tag menu. Receives the query typed after `#`
42
- * (lowercased, punctuation stripped) and returns the tags to show, either
57
+ * (lowercased, punctuation stripped) and returns the rows to show, either
43
58
  * synchronously or as a promise.
44
59
  */
45
- type TagSearchHandler = (query: string) => string[] | Promise<string[]>;
60
+ type TagSearchHandler = (query: string) => TagItem[] | Promise<TagItem[]>;
61
+ /** One row in the wikilink menu. The host ranks the rows; the menu does not re-sort. */
62
+ interface WikilinkItem {
63
+ /** Inserted as `[[target]]`. */
64
+ target: string;
65
+ /** Display text; defaults to `target`. */
66
+ label?: string;
67
+ /** Secondary text shown beside the label. */
68
+ detail?: string;
69
+ /** Side effect run after the link is inserted (e.g. create the note). */
70
+ onSelect?: () => void;
71
+ }
46
72
  /**
47
73
  * Searches notes for the wikilink menu. Receives the query typed after
48
74
  * `[[` (lowercased, punctuation stripped, may be empty or contain spaces)
49
- * and returns the note names to show, either synchronously or as a promise.
75
+ * and returns the rows to show, either synchronously or as a promise.
50
76
  */
51
- type WikilinkSearchHandler = (query: string) => string[] | Promise<string[]>;
77
+ type WikilinkSearchHandler = (query: string) => WikilinkItem[] | Promise<WikilinkItem[]>;
52
78
  //#endregion
53
79
  //#region src/components/editor.d.ts
54
80
  type EditorMode = MarkMode$1 | 'source';
@@ -65,7 +91,10 @@ interface EditorProps {
65
91
  * first render is used; later changes are ignored.
66
92
  */
67
93
  initialMarkdown?: string;
68
- /** Called on every document change. */
94
+ /**
95
+ * Called on every user-driven document change. Programmatic `setMarkdown` and
96
+ * `setState` on the handle do not fire it.
97
+ */
69
98
  onDocChange?: VoidFunction;
70
99
  /**
71
100
  * Searches tags for the tag menu, which opens when typing `#` followed by
@@ -84,16 +113,61 @@ interface EditorProps {
84
113
  * mode.
85
114
  */
86
115
  onWikilinkSearch?: WikilinkSearchHandler;
116
+ /**
117
+ * Called with the link target on click of a rendered wiki link. Pass a stable
118
+ * function (e.g. from `useCallback`). Ignored in source mode.
119
+ */
120
+ onWikilinkClick?: WikilinkClickHandler;
121
+ /**
122
+ * Maps an image `src` to a displayable URL (or `undefined` to skip). Enables
123
+ * inline image rendering. Pass a stable function. Ignored in source mode.
124
+ */
125
+ resolveImageUrl?: ImageOptions['resolveImageUrl'];
126
+ /**
127
+ * Persists a pasted/dropped image file and returns its markdown `src`. Pass a
128
+ * stable function. Ignored in source mode.
129
+ */
130
+ onImagePaste?: ImageOptions['onImagePaste'];
131
+ /** Called when persisting a pasted/dropped image throws. Ignored in source mode. */
132
+ onImageSaveError?: ImageOptions['onImageSaveError'];
133
+ /**
134
+ * Placeholder text shown in an empty block. A function receives the editor
135
+ * state. Pass a stable function. Ignored in source mode.
136
+ */
137
+ placeholder?: PlaceholderOptions['placeholder'];
138
+ /** Makes the editor read-only, in both the rich and source modes. */
139
+ readOnly?: boolean;
140
+ /**
141
+ * Enables the browser's native spell checking in the rich modes. Defaults
142
+ * to the browser's behavior. Ignored in source mode.
143
+ */
144
+ spellCheck?: boolean;
145
+ /** Class on the editable root (the contenteditable). Rich modes only. */
146
+ editorClassName?: string;
147
+ /** Class on the outer `.meowdown` wrapper div. */
148
+ wrapperClassName?: string;
87
149
  /** Imperative handle for the editor. */
88
- ref?: Ref<EditorHandle>;
150
+ handleRef?: Ref<EditorHandle>;
151
+ /** Nodes rendered inside the editor's ProseKit context (rich modes only). */
152
+ children?: ReactNode;
89
153
  }
90
- declare function Editor({
154
+ declare function MeowdownEditor({
91
155
  mode,
92
156
  initialMarkdown,
93
157
  onDocChange,
94
158
  onTagSearch,
95
159
  onWikilinkSearch,
96
- ref
160
+ onWikilinkClick,
161
+ resolveImageUrl,
162
+ onImagePaste,
163
+ onImageSaveError,
164
+ placeholder,
165
+ readOnly,
166
+ spellCheck,
167
+ editorClassName,
168
+ wrapperClassName,
169
+ handleRef,
170
+ children
97
171
  }: EditorProps): import("react").JSX.Element;
98
172
  //#endregion
99
- export { Editor, type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, type MarkMode, type SelectionHint, type SelectionJSON, type TagSearchHandler, type TypedEditor, type WikilinkSearchHandler };
173
+ export { EDITOR_KEY_BINDINGS, type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, type MarkMode, MeowdownEditor, type RoundTripFidelity, type SelectionHint, type SelectionJSON, type TagItem, type TagSearchHandler, type TypedEditor, type WikilinkItem, type WikilinkSearchHandler, checkRoundTrip, useEditor };