@meowdown/react 0.30.1 → 0.32.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/dist/index.d.ts +166 -13
- package/dist/index.js +478 -26
- package/dist/style.css +181 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactElement, ReactNode, Ref } from "react";
|
|
2
|
-
import { ExitBoundaryHandler, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, MarkMode, PlaceholderOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
|
|
2
|
+
import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, StartPendingReplacementOptions, 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
|
|
|
@@ -22,6 +22,15 @@ interface EditorHandle {
|
|
|
22
22
|
getMarkdown: () => string;
|
|
23
23
|
/** Replaces the whole document as a single undoable edit. */
|
|
24
24
|
setMarkdown: (markdown: string) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Parses `markdown` and inserts it at the current selection as a single
|
|
27
|
+
* undoable edit, replacing any selected content. A lone paragraph is
|
|
28
|
+
* inserted inline at the cursor; anything else is inserted as blocks. The
|
|
29
|
+
* cursor lands at the end of the inserted content. An empty or
|
|
30
|
+
* whitespace-only string is a no-op. Unlike `setMarkdown`, it fires
|
|
31
|
+
* `onDocChange`: the host cannot know the resulting document.
|
|
32
|
+
*/
|
|
33
|
+
insertMarkdown: (markdown: string) => void;
|
|
25
34
|
/** Returns the current Markdown and selection. */
|
|
26
35
|
getState: () => EditorStateSnapshot;
|
|
27
36
|
/**
|
|
@@ -39,13 +48,56 @@ interface EditorHandle {
|
|
|
39
48
|
focus: () => void;
|
|
40
49
|
/** Scrolls the selection into view. */
|
|
41
50
|
scrollIntoView: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* The current selection as Markdown: block structure (list markers,
|
|
53
|
+
* headings) is serialized, and inline syntax is already literal text. A
|
|
54
|
+
* selection inside one textblock comes back as its bare text.
|
|
55
|
+
*/
|
|
56
|
+
getSelectedText: () => string;
|
|
57
|
+
/**
|
|
58
|
+
* Opens the selection menu over the current selection. A no-op when the
|
|
59
|
+
* selection is empty or `onSelectionMenuSearch` is not set.
|
|
60
|
+
*/
|
|
61
|
+
openSelectionMenu: () => void;
|
|
62
|
+
/**
|
|
63
|
+
* Stages a pending replacement over a document range. Returns `false` when
|
|
64
|
+
* the range is invalid. Calling it again resets the accumulated text, which
|
|
65
|
+
* is how a retry starts.
|
|
66
|
+
*/
|
|
67
|
+
startPendingReplacement: (options: StartPendingReplacementOptions) => boolean;
|
|
68
|
+
/** Appends streamed text to the staged replacement. */
|
|
69
|
+
appendPendingReplacementText: (text: string) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Applies the staged replacement to the document as a single edit. Pass a
|
|
72
|
+
* `mode` to override the staged placement for this accept (e.g. "Insert
|
|
73
|
+
* below" on a replace stage).
|
|
74
|
+
*/
|
|
75
|
+
acceptPendingReplacement: (options?: AcceptPendingReplacementOptions) => void;
|
|
76
|
+
/** Clears the staged replacement without touching the document. */
|
|
77
|
+
discardPendingReplacement: () => void;
|
|
42
78
|
/**
|
|
43
79
|
* Escape hatch: the underlying ProseKit editor, or `undefined` when the
|
|
44
80
|
* handle does not wrap one.
|
|
45
81
|
*/
|
|
46
82
|
readonly editor: TypedEditor | undefined;
|
|
47
83
|
}
|
|
84
|
+
/** One row of host items in the slash menu. The host ranks the rows; the menu does not re-sort. */
|
|
85
|
+
interface SlashMenuItem {
|
|
86
|
+
/** Stable row key; defaults to `label`. */
|
|
87
|
+
id?: string;
|
|
88
|
+
/** Display text, matched against the typed query like the built-in items. */
|
|
89
|
+
label: string;
|
|
90
|
+
/** Secondary text shown beside the label. */
|
|
91
|
+
detail?: string;
|
|
92
|
+
/** Runs after the menu closes and the typed `/query` text is removed. */
|
|
93
|
+
onSelect: () => void;
|
|
94
|
+
}
|
|
48
95
|
/**
|
|
96
|
+
* Searches host items for the slash menu. Receives the query typed after `/`
|
|
97
|
+
* (lowercased, punctuation stripped; may be empty) and returns the rows to
|
|
98
|
+
* show after the built-in items, either synchronously or as a promise.
|
|
99
|
+
*/
|
|
100
|
+
type SlashMenuSearchHandler = (query: string) => SlashMenuItem[] | Promise<SlashMenuItem[]>;
|
|
49
101
|
/** One row in the tag menu. The host ranks the rows; the menu does not re-sort. */
|
|
50
102
|
interface TagItem {
|
|
51
103
|
/** Inserted as `#tag `. */
|
|
@@ -81,6 +133,34 @@ interface WikilinkItem {
|
|
|
81
133
|
* a promise.
|
|
82
134
|
*/
|
|
83
135
|
type WikilinkSearchHandler = (query: string) => WikilinkItem[] | Promise<WikilinkItem[]>;
|
|
136
|
+
/** The selection the selection menu was opened over. */
|
|
137
|
+
interface SelectionMenuContext {
|
|
138
|
+
/** The selected text, with block boundaries as blank lines. */
|
|
139
|
+
selectedText: string;
|
|
140
|
+
/** Start of the selection. */
|
|
141
|
+
from: number;
|
|
142
|
+
/** End of the selection. */
|
|
143
|
+
to: number;
|
|
144
|
+
}
|
|
145
|
+
/** One row in the selection menu. The host ranks the rows; the menu does not re-sort. */
|
|
146
|
+
interface SelectionMenuItem {
|
|
147
|
+
/** Stable identity for the row. */
|
|
148
|
+
id: string;
|
|
149
|
+
/** Display text. */
|
|
150
|
+
label: string;
|
|
151
|
+
/** Secondary text shown beside the label. */
|
|
152
|
+
detail?: string;
|
|
153
|
+
/** Runs when the row is picked, with the selection the menu was opened over. */
|
|
154
|
+
onSelect: (context: SelectionMenuContext) => void;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Searches commands for the selection menu. Receives the filter text typed in
|
|
158
|
+
* the menu (may be empty) and the selection the menu was opened over, and
|
|
159
|
+
* returns the rows to show, either synchronously or as a promise.
|
|
160
|
+
*/
|
|
161
|
+
type SelectionMenuSearchHandler = (query: string, context: SelectionMenuContext) => SelectionMenuItem[] | Promise<SelectionMenuItem[]>;
|
|
162
|
+
/** Reports how a pending replacement ended and its final staged value. */
|
|
163
|
+
type PendingReplacementResolveHandler = (outcome: PendingReplacementOutcome, pending: PendingReplacement) => void;
|
|
84
164
|
//#endregion
|
|
85
165
|
//#region src/components/editor.d.ts
|
|
86
166
|
type EditorMode = MarkMode;
|
|
@@ -100,6 +180,16 @@ interface EditorProps {
|
|
|
100
180
|
* `setState` on the handle do not fire it.
|
|
101
181
|
*/
|
|
102
182
|
onDocChange?: VoidFunction;
|
|
183
|
+
/**
|
|
184
|
+
* Searches host items for the slash menu, which opens when typing `/`.
|
|
185
|
+
* Receives the query (lowercased, punctuation stripped, may be empty) and
|
|
186
|
+
* returns the items to show after the built-in ones, synchronously or as a
|
|
187
|
+
* promise. Selecting an item removes the typed `/query` text before its
|
|
188
|
+
* `onSelect` runs, so `onSelect` can insert at the cursor (e.g. via the
|
|
189
|
+
* handle's `insertMarkdown`). Pass a stable function (e.g. from
|
|
190
|
+
* `useCallback`). Omit to show only the built-in items.
|
|
191
|
+
*/
|
|
192
|
+
onSlashMenuSearch?: SlashMenuSearchHandler;
|
|
103
193
|
/**
|
|
104
194
|
* Searches tags for the tag menu, which opens when typing `#` followed by
|
|
105
195
|
* text. Receives the query (lowercased, punctuation stripped) and returns the
|
|
@@ -116,13 +206,42 @@ interface EditorProps {
|
|
|
116
206
|
*/
|
|
117
207
|
onWikilinkSearch?: WikilinkSearchHandler;
|
|
118
208
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
209
|
+
* Searches commands for the selection menu, which opens over a non-empty
|
|
210
|
+
* selection via `EditorHandle.openSelectionMenu()` or the selection
|
|
211
|
+
* affordance. Receives the filter text typed in the menu (may be empty) and
|
|
212
|
+
* the selection the menu was opened over, and returns the rows to show,
|
|
213
|
+
* synchronously or as a promise. The host ranks the rows; the menu does not
|
|
214
|
+
* re-sort. Pass a stable function (e.g. from `useCallback`). Omit to disable
|
|
215
|
+
* the selection menu.
|
|
216
|
+
*/
|
|
217
|
+
onSelectionMenuSearch?: SelectionMenuSearchHandler;
|
|
218
|
+
/**
|
|
219
|
+
* Shows a small floating button on a non-empty text selection that opens the
|
|
220
|
+
* selection menu. On by default; only relevant when `onSelectionMenuSearch`
|
|
221
|
+
* is set. Ignored when `readOnly` is set.
|
|
222
|
+
*/
|
|
223
|
+
selectionMenuAffordance?: boolean;
|
|
224
|
+
/**
|
|
225
|
+
* Extra controls rendered in the pending-replacement preview footer, next to
|
|
226
|
+
* the built-in Accept and Discard buttons (e.g. a retry button).
|
|
227
|
+
*/
|
|
228
|
+
pendingReplacementActions?: ReactNode;
|
|
229
|
+
/**
|
|
230
|
+
* Called when a pending replacement ends, with the outcome ('accepted' or
|
|
231
|
+
* 'discarded') and the final staged value. Use it to stop a stream that is
|
|
232
|
+
* still appending. Pass a stable function (e.g. from `useCallback`).
|
|
233
|
+
*/
|
|
234
|
+
onPendingReplacementResolve?: PendingReplacementResolveHandler;
|
|
235
|
+
/**
|
|
236
|
+
* Called with the link target on click of a rendered wiki link, or on
|
|
237
|
+
* `Mod-Enter` with the caret on one. Pass a stable function (e.g. from
|
|
238
|
+
* `useCallback`).
|
|
121
239
|
*/
|
|
122
240
|
onWikilinkClick?: WikilinkClickHandler;
|
|
123
241
|
/**
|
|
124
242
|
* Called with the link `href` on click of a rendered Markdown link
|
|
125
|
-
* (`[text](url)`)
|
|
243
|
+
* (`[text](url)`), or on `Mod-Enter` with the caret on one. Pass a stable
|
|
244
|
+
* function (e.g. from `useCallback`).
|
|
126
245
|
*/
|
|
127
246
|
onLinkClick?: LinkClickHandler;
|
|
128
247
|
/**
|
|
@@ -132,7 +251,8 @@ interface EditorProps {
|
|
|
132
251
|
onLinkCopy?: LinkCopyHandler;
|
|
133
252
|
/**
|
|
134
253
|
* Called with the tag name (without the leading `#`) on click of a rendered
|
|
135
|
-
* `#tag
|
|
254
|
+
* `#tag`, or on `Mod-Enter` with the caret on one. Pass a stable function
|
|
255
|
+
* (e.g. from `useCallback`).
|
|
136
256
|
*/
|
|
137
257
|
onTagClick?: TagClickHandler;
|
|
138
258
|
/**
|
|
@@ -152,12 +272,37 @@ interface EditorProps {
|
|
|
152
272
|
*/
|
|
153
273
|
resolveImageUrl?: ImageOptions['resolveImageUrl'];
|
|
154
274
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
275
|
+
* Claims a `[label](url)` link as a file: a claimed link renders as an
|
|
276
|
+
* inline pill (file icon, name, size) instead of a link, behaves as one
|
|
277
|
+
* caret unit, and reports clicks through `onFileClick` instead of
|
|
278
|
+
* `onLinkClick`. The markdown text is untouched. Must be pure (same link,
|
|
279
|
+
* same answer). Read once when the editor is created: later identity
|
|
280
|
+
* changes are ignored, like `initialMarkdown`.
|
|
281
|
+
*/
|
|
282
|
+
resolveFileLink?: FileLinkResolver;
|
|
283
|
+
/**
|
|
284
|
+
* Resolves the metadata (file size in bytes) shown on a file pill, directly
|
|
285
|
+
* or as a promise; the pill renders immediately and fills the size in when
|
|
286
|
+
* the promise settles. May be called repeatedly for the same `href`, so
|
|
287
|
+
* cache in the host when resolving is expensive. Pass a stable function
|
|
288
|
+
* (e.g. from `useCallback`).
|
|
289
|
+
*/
|
|
290
|
+
resolveFileInfo?: FileViewOptions['resolveFileInfo'];
|
|
291
|
+
/**
|
|
292
|
+
* Called when the user clicks a rendered file pill (or presses `Mod-Enter`
|
|
293
|
+
* with the caret on one), with its `href`, `name`, and the originating
|
|
294
|
+
* event. The host decides what a click does (e.g. open the file in the OS
|
|
295
|
+
* default app). Pass a stable function (e.g. from `useCallback`).
|
|
296
|
+
*/
|
|
297
|
+
onFileClick?: FileClickHandler;
|
|
298
|
+
/**
|
|
299
|
+
* Persists a pasted/dropped file and returns its markdown destination,
|
|
300
|
+
* inserted as `` for an image and as a `[name](src)` link for any
|
|
301
|
+
* other file. Return `undefined` to decline. Pass a stable function.
|
|
157
302
|
*/
|
|
158
|
-
|
|
159
|
-
/** Called when persisting a pasted/dropped
|
|
160
|
-
|
|
303
|
+
onFilePaste?: FilePasteOptions['onFilePaste'];
|
|
304
|
+
/** Called when persisting a pasted/dropped file throws. */
|
|
305
|
+
onFileSaveError?: FilePasteOptions['onFileSaveError'];
|
|
161
306
|
/**
|
|
162
307
|
* Called when the user clicks a rendered image, with its markdown `src`,
|
|
163
308
|
* `alt`, and the originating `MouseEvent`. Pass a stable function (e.g. from
|
|
@@ -215,16 +360,24 @@ declare function MeowdownEditor({
|
|
|
215
360
|
mode,
|
|
216
361
|
initialMarkdown,
|
|
217
362
|
onDocChange,
|
|
363
|
+
onSlashMenuSearch,
|
|
218
364
|
onTagSearch,
|
|
219
365
|
onWikilinkSearch,
|
|
366
|
+
onSelectionMenuSearch,
|
|
367
|
+
selectionMenuAffordance,
|
|
368
|
+
pendingReplacementActions,
|
|
369
|
+
onPendingReplacementResolve,
|
|
220
370
|
onWikilinkClick,
|
|
221
371
|
onLinkClick,
|
|
222
372
|
onLinkCopy,
|
|
223
373
|
onTagClick,
|
|
224
374
|
onExitBoundary,
|
|
225
375
|
resolveImageUrl,
|
|
226
|
-
|
|
227
|
-
|
|
376
|
+
resolveFileLink,
|
|
377
|
+
resolveFileInfo,
|
|
378
|
+
onFileClick,
|
|
379
|
+
onFilePaste,
|
|
380
|
+
onFileSaveError,
|
|
228
381
|
onImageClick,
|
|
229
382
|
embedPaste,
|
|
230
383
|
bulletAfterHeading,
|
|
@@ -281,4 +434,4 @@ declare function MarkdownView({
|
|
|
281
434
|
className
|
|
282
435
|
}: MarkdownViewProps): ReactElement;
|
|
283
436
|
//#endregion
|
|
284
|
-
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, MarkdownView, type MarkdownViewProps, MeowdownEditor, type SelectionHint, type SelectionJSON, type TagItem, type TagSearchHandler, type TimeFormat, type WikilinkItem, type WikilinkSearchHandler, useEditor, useExtension, useKeymap };
|
|
437
|
+
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, MarkdownView, type MarkdownViewProps, MeowdownEditor, type PendingReplacementResolveHandler, type SelectionHint, type SelectionJSON, type SelectionMenuContext, type SelectionMenuItem, type SelectionMenuSearchHandler, type SlashMenuItem, type SlashMenuSearchHandler, type TagItem, type TagSearchHandler, type TimeFormat, type WikilinkItem, type WikilinkSearchHandler, useEditor, useExtension, useKeymap };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { clsx } from "clsx/lite";
|
|
2
2
|
import { Fragment, createElement, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineMarkMode, defineMarkdownCopy, definePlaceholder, defineReadonly, defineSpellCheckPlugin, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, getVirtualElementFromRange, inlineTextToMarkChunks, isSelectionInTableCell, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
|
|
3
|
+
import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineMarkMode, defineMarkdownCopy, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSpellCheckPlugin, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, getPendingReplacement, getSelectedText, getVirtualElementFromRange, inlineTextToMarkChunks, isSelectionInTableCell, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
|
|
4
4
|
import { clamp } from "@ocavue/utils";
|
|
5
|
-
import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, union } from "@prosekit/core";
|
|
5
|
+
import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, defineUpdateHandler, isTextSelection, union } from "@prosekit/core";
|
|
6
6
|
import { Selection, TextSelection } from "@prosekit/pm/state";
|
|
7
7
|
import { ProseKit, defineReactNodeView, useEditor, useEditor as useEditor$1, useEditorDerivedValue, useExtension, useExtension as useExtension$1, useKeymap } from "@prosekit/react";
|
|
8
8
|
import { Combobox } from "@base-ui/react/combobox";
|
|
9
|
-
import { CheckIcon, ChevronsUpDownIcon, CopyIcon, GripHorizontalIcon, GripVerticalIcon, PencilIcon, UnlinkIcon } from "lucide-react";
|
|
9
|
+
import { CheckIcon, ChevronsUpDownIcon, CopyIcon, GripHorizontalIcon, GripVerticalIcon, PencilIcon, SparklesIcon, UnlinkIcon } from "lucide-react";
|
|
10
10
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from "@prosekit/react/block-handle";
|
|
12
12
|
import { DropIndicator } from "@prosekit/react/drop-indicator";
|
|
@@ -216,7 +216,7 @@ function DropIndicator$1() {
|
|
|
216
216
|
|
|
217
217
|
//#endregion
|
|
218
218
|
//#region src/components/editor-extensions.tsx
|
|
219
|
-
function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl,
|
|
219
|
+
function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, bulletAfterHeading, placeholder, readOnly, wikilinkEnabled, spellCheck }) {
|
|
220
220
|
useExtension$1(useMemo(() => {
|
|
221
221
|
return defineMarkMode(markMode);
|
|
222
222
|
}, [markMode]));
|
|
@@ -235,20 +235,37 @@ function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick,
|
|
|
235
235
|
useExtension$1(useMemo(() => {
|
|
236
236
|
return onTagClick ? defineTagClickHandler(onTagClick) : null;
|
|
237
237
|
}, [onTagClick]));
|
|
238
|
+
useExtension$1(useMemo(() => {
|
|
239
|
+
return onWikilinkClick || onTagClick || onFileClick || onLinkClick ? defineFollowLinkHandler({
|
|
240
|
+
onWikilinkClick,
|
|
241
|
+
onTagClick,
|
|
242
|
+
onFileClick,
|
|
243
|
+
onLinkClick
|
|
244
|
+
}) : null;
|
|
245
|
+
}, [
|
|
246
|
+
onWikilinkClick,
|
|
247
|
+
onTagClick,
|
|
248
|
+
onFileClick,
|
|
249
|
+
onLinkClick
|
|
250
|
+
]));
|
|
238
251
|
useExtension$1(useMemo(() => {
|
|
239
252
|
return onExitBoundary ? defineExitBoundaryHandler(onExitBoundary) : null;
|
|
240
253
|
}, [onExitBoundary]));
|
|
241
254
|
useExtension$1(useMemo(() => {
|
|
242
|
-
return defineImage({
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
255
|
+
return defineImage({ resolveImageUrl });
|
|
256
|
+
}, [resolveImageUrl]));
|
|
257
|
+
useExtension$1(useMemo(() => {
|
|
258
|
+
return defineFileView({ resolveFileInfo });
|
|
259
|
+
}, [resolveFileInfo]));
|
|
260
|
+
useExtension$1(useMemo(() => {
|
|
261
|
+
return onFileClick ? defineFileClickHandler(onFileClick) : null;
|
|
262
|
+
}, [onFileClick]));
|
|
263
|
+
useExtension$1(useMemo(() => {
|
|
264
|
+
return onFilePaste ? defineFilePaste({
|
|
265
|
+
onFilePaste,
|
|
266
|
+
onFileSaveError
|
|
267
|
+
}) : null;
|
|
268
|
+
}, [onFilePaste, onFileSaveError]));
|
|
252
269
|
useExtension$1(useMemo(() => {
|
|
253
270
|
return onImageClick ? defineImageClickHandler(onImageClick) : null;
|
|
254
271
|
}, [onImageClick]));
|
|
@@ -542,6 +559,299 @@ function LinkMenu({ onLinkClick, onLinkCopy }) {
|
|
|
542
559
|
return null;
|
|
543
560
|
}
|
|
544
561
|
|
|
562
|
+
//#endregion
|
|
563
|
+
//#region src/components/pending-replacement-preview.module.css
|
|
564
|
+
var pending_replacement_preview_module_default = {
|
|
565
|
+
"AcceptButton": "meow_AcceptButton_DPJ5ia",
|
|
566
|
+
"Button": "meow_Button_DPJ5ia",
|
|
567
|
+
"Footer": "meow_Footer_DPJ5ia",
|
|
568
|
+
"Popup": "meow_Popup_DPJ5ia",
|
|
569
|
+
"Positioner": "meow_Positioner_DPJ5ia",
|
|
570
|
+
"Spacer": "meow_Spacer_DPJ5ia",
|
|
571
|
+
"Text": "meow_Text_DPJ5ia",
|
|
572
|
+
"Waiting": "meow_Waiting_DPJ5ia"
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/components/pending-replacement-preview.tsx
|
|
577
|
+
/**
|
|
578
|
+
* The preview for a staged (pending) replacement: a popover anchored to the
|
|
579
|
+
* source range showing the accumulated text, with Accept and Discard controls
|
|
580
|
+
* plus a host-provided `actions` slot. Dismissing the popover (Escape or an
|
|
581
|
+
* outside press) discards the stage; the document is only touched on accept.
|
|
582
|
+
*/
|
|
583
|
+
function PendingReplacementPreview({ actions, onResolve }) {
|
|
584
|
+
const editor = useEditor$1();
|
|
585
|
+
const [pending, setPending] = useState(null);
|
|
586
|
+
useExtension$1(useMemo(() => {
|
|
587
|
+
return definePendingReplacementHandler((event) => {
|
|
588
|
+
if (event.type === "update") setPending(event.pending);
|
|
589
|
+
else {
|
|
590
|
+
setPending(null);
|
|
591
|
+
onResolve?.(event.outcome, event.pending);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}, [onResolve]));
|
|
595
|
+
const from = pending?.from;
|
|
596
|
+
const to = pending?.to;
|
|
597
|
+
const anchor = useMemo(() => {
|
|
598
|
+
if (from == null || to == null) return;
|
|
599
|
+
return getVirtualElementFromRange(editor.view, {
|
|
600
|
+
from,
|
|
601
|
+
to
|
|
602
|
+
});
|
|
603
|
+
}, [
|
|
604
|
+
from,
|
|
605
|
+
to,
|
|
606
|
+
editor
|
|
607
|
+
]);
|
|
608
|
+
if (!pending) return null;
|
|
609
|
+
const discard = () => {
|
|
610
|
+
editor.commands.discardPendingReplacement();
|
|
611
|
+
editor.focus();
|
|
612
|
+
};
|
|
613
|
+
const accept = () => {
|
|
614
|
+
editor.commands.acceptPendingReplacement();
|
|
615
|
+
editor.focus();
|
|
616
|
+
};
|
|
617
|
+
return /* @__PURE__ */ jsx(Popover.Root, {
|
|
618
|
+
open: true,
|
|
619
|
+
onOpenChange: (next) => {
|
|
620
|
+
if (!next) discard();
|
|
621
|
+
},
|
|
622
|
+
children: /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
623
|
+
anchor,
|
|
624
|
+
side: "bottom",
|
|
625
|
+
sideOffset: 8,
|
|
626
|
+
className: pending_replacement_preview_module_default.Positioner,
|
|
627
|
+
children: /* @__PURE__ */ jsxs(Popover.Popup, {
|
|
628
|
+
className: pending_replacement_preview_module_default.Popup,
|
|
629
|
+
"data-testid": "pending-replacement",
|
|
630
|
+
initialFocus: false,
|
|
631
|
+
finalFocus: false,
|
|
632
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
633
|
+
className: pending_replacement_preview_module_default.Text,
|
|
634
|
+
"data-testid": "pending-replacement-text",
|
|
635
|
+
children: pending.text || /* @__PURE__ */ jsx("span", {
|
|
636
|
+
className: pending_replacement_preview_module_default.Waiting,
|
|
637
|
+
children: "Waiting for text..."
|
|
638
|
+
})
|
|
639
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
640
|
+
className: pending_replacement_preview_module_default.Footer,
|
|
641
|
+
children: [
|
|
642
|
+
actions,
|
|
643
|
+
/* @__PURE__ */ jsx("span", { className: pending_replacement_preview_module_default.Spacer }),
|
|
644
|
+
/* @__PURE__ */ jsx("button", {
|
|
645
|
+
type: "button",
|
|
646
|
+
className: pending_replacement_preview_module_default.Button,
|
|
647
|
+
"data-testid": "pending-replacement-discard",
|
|
648
|
+
onClick: discard,
|
|
649
|
+
children: "Discard"
|
|
650
|
+
}),
|
|
651
|
+
/* @__PURE__ */ jsx("button", {
|
|
652
|
+
type: "button",
|
|
653
|
+
className: pending_replacement_preview_module_default.AcceptButton,
|
|
654
|
+
"data-testid": "pending-replacement-accept",
|
|
655
|
+
disabled: !pending.text.trim(),
|
|
656
|
+
onClick: accept,
|
|
657
|
+
children: "Accept"
|
|
658
|
+
})
|
|
659
|
+
]
|
|
660
|
+
})]
|
|
661
|
+
})
|
|
662
|
+
}) })
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region src/components/selection-menu.module.css
|
|
668
|
+
var selection_menu_module_default = {
|
|
669
|
+
"AffordanceButton": "meow_AffordanceButton_2_4Zjq",
|
|
670
|
+
"AffordancePopup": "meow_AffordancePopup_2_4Zjq",
|
|
671
|
+
"AffordancePositioner": "meow_AffordancePositioner_2_4Zjq",
|
|
672
|
+
"Detail": "meow_Detail_2_4Zjq",
|
|
673
|
+
"Empty": "meow_Empty_2_4Zjq",
|
|
674
|
+
"Input": "meow_Input_2_4Zjq",
|
|
675
|
+
"Item": "meow_Item_2_4Zjq",
|
|
676
|
+
"Label": "meow_Label_2_4Zjq",
|
|
677
|
+
"List": "meow_List_2_4Zjq",
|
|
678
|
+
"Popup": "meow_Popup_2_4Zjq",
|
|
679
|
+
"Positioner": "meow_Positioner_2_4Zjq"
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/components/selection-menu.tsx
|
|
684
|
+
/**
|
|
685
|
+
* A command menu over the current selection: a popover with a filter input and
|
|
686
|
+
* host-supplied rows, anchored to the selected range. Opened imperatively (via
|
|
687
|
+
* `EditorHandle.openSelectionMenu`) or from the selection affordance, a small
|
|
688
|
+
* floating button that appears on a non-empty selection.
|
|
689
|
+
*/
|
|
690
|
+
function SelectionMenu({ onSelectionMenuSearch, context, onOpen, onClose, affordance = true }) {
|
|
691
|
+
const editor = useEditor$1();
|
|
692
|
+
const [selection, setSelection] = useState();
|
|
693
|
+
useExtension$1(useMemo(() => {
|
|
694
|
+
return defineUpdateHandler((view) => {
|
|
695
|
+
const { from, to, empty } = view.state.selection;
|
|
696
|
+
const anchorable = !empty && isTextSelection(view.state.selection) && !getPendingReplacement(view.state);
|
|
697
|
+
setSelection((previous) => {
|
|
698
|
+
if (previous?.from === from && previous?.to === to && previous?.anchorable === anchorable) return previous;
|
|
699
|
+
return {
|
|
700
|
+
from,
|
|
701
|
+
to,
|
|
702
|
+
anchorable
|
|
703
|
+
};
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
}, []));
|
|
707
|
+
const close = useCallback(() => {
|
|
708
|
+
onClose();
|
|
709
|
+
editor.focus();
|
|
710
|
+
}, [onClose, editor]);
|
|
711
|
+
const menuAnchor = useMemo(() => {
|
|
712
|
+
if (!context) return;
|
|
713
|
+
return getVirtualElementFromRange(editor.view, {
|
|
714
|
+
from: context.from,
|
|
715
|
+
to: context.to
|
|
716
|
+
});
|
|
717
|
+
}, [context, editor]);
|
|
718
|
+
const showAffordance = affordance && !!!context && !!selection?.anchorable;
|
|
719
|
+
const affordanceVisible = useDelayedFlag(showAffordance, 250, 0);
|
|
720
|
+
const affordanceAnchor = useMemo(() => {
|
|
721
|
+
if (!showAffordance || !selection) return;
|
|
722
|
+
return getVirtualElementFromRange(editor.view, {
|
|
723
|
+
from: selection.to,
|
|
724
|
+
to: selection.to
|
|
725
|
+
});
|
|
726
|
+
}, [
|
|
727
|
+
showAffordance,
|
|
728
|
+
selection,
|
|
729
|
+
editor
|
|
730
|
+
]);
|
|
731
|
+
if (context) return /* @__PURE__ */ jsx(Popover.Root, {
|
|
732
|
+
open: true,
|
|
733
|
+
onOpenChange: (next) => {
|
|
734
|
+
if (!next) close();
|
|
735
|
+
},
|
|
736
|
+
children: /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
737
|
+
anchor: menuAnchor,
|
|
738
|
+
side: "bottom",
|
|
739
|
+
sideOffset: 8,
|
|
740
|
+
className: selection_menu_module_default.Positioner,
|
|
741
|
+
children: /* @__PURE__ */ jsx(Popover.Popup, {
|
|
742
|
+
className: selection_menu_module_default.Popup,
|
|
743
|
+
"data-testid": "selection-menu",
|
|
744
|
+
finalFocus: false,
|
|
745
|
+
children: /* @__PURE__ */ jsx(SelectionMenuPopup, {
|
|
746
|
+
onSelectionMenuSearch,
|
|
747
|
+
context,
|
|
748
|
+
onClose: close
|
|
749
|
+
})
|
|
750
|
+
})
|
|
751
|
+
}) })
|
|
752
|
+
});
|
|
753
|
+
if (affordanceVisible && showAffordance) return /* @__PURE__ */ jsx(Popover.Root, {
|
|
754
|
+
open: true,
|
|
755
|
+
onOpenChange: () => {},
|
|
756
|
+
children: /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
757
|
+
anchor: affordanceAnchor,
|
|
758
|
+
side: "bottom",
|
|
759
|
+
sideOffset: 4,
|
|
760
|
+
className: selection_menu_module_default.AffordancePositioner,
|
|
761
|
+
children: /* @__PURE__ */ jsx(Popover.Popup, {
|
|
762
|
+
className: selection_menu_module_default.AffordancePopup,
|
|
763
|
+
"data-testid": "selection-menu-affordance",
|
|
764
|
+
initialFocus: false,
|
|
765
|
+
finalFocus: false,
|
|
766
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
767
|
+
type: "button",
|
|
768
|
+
className: selection_menu_module_default.AffordanceButton,
|
|
769
|
+
title: "Selection commands",
|
|
770
|
+
"aria-label": "Selection commands",
|
|
771
|
+
onPointerDown: (event) => event.preventDefault(),
|
|
772
|
+
onClick: onOpen,
|
|
773
|
+
children: /* @__PURE__ */ jsx(SparklesIcon, {})
|
|
774
|
+
})
|
|
775
|
+
})
|
|
776
|
+
}) })
|
|
777
|
+
});
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
/** The menu content. Mounted only while the menu is open, so its filter state
|
|
781
|
+
* resets naturally on close. */
|
|
782
|
+
function SelectionMenuPopup({ onSelectionMenuSearch, context, onClose }) {
|
|
783
|
+
const [query, setQuery] = useState("");
|
|
784
|
+
const [items, setItems] = useState([]);
|
|
785
|
+
const [loading, setLoading] = useState(false);
|
|
786
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
787
|
+
const fetchItems = useCallback(async (query, signal) => {
|
|
788
|
+
if (signal.aborted) return;
|
|
789
|
+
setLoading(true);
|
|
790
|
+
const result = await onSelectionMenuSearch(query, context);
|
|
791
|
+
if (signal.aborted) return;
|
|
792
|
+
setItems(result);
|
|
793
|
+
setActiveIndex(0);
|
|
794
|
+
setLoading(false);
|
|
795
|
+
}, [onSelectionMenuSearch, context]);
|
|
796
|
+
useEffect(() => {
|
|
797
|
+
const controller = new AbortController();
|
|
798
|
+
queueMicrotask(() => {
|
|
799
|
+
fetchItems(query, controller.signal);
|
|
800
|
+
});
|
|
801
|
+
return () => {
|
|
802
|
+
controller.abort();
|
|
803
|
+
};
|
|
804
|
+
}, [query, fetchItems]);
|
|
805
|
+
const selectItem = useCallback((item) => {
|
|
806
|
+
onClose();
|
|
807
|
+
item.onSelect(context);
|
|
808
|
+
}, [context, onClose]);
|
|
809
|
+
function onInputKeyDown(event) {
|
|
810
|
+
if (event.key === "ArrowDown") {
|
|
811
|
+
event.preventDefault();
|
|
812
|
+
setActiveIndex((index) => Math.min(index + 1, Math.max(items.length - 1, 0)));
|
|
813
|
+
} else if (event.key === "ArrowUp") {
|
|
814
|
+
event.preventDefault();
|
|
815
|
+
setActiveIndex((index) => Math.max(index - 1, 0));
|
|
816
|
+
} else if (event.key === "Enter") {
|
|
817
|
+
event.preventDefault();
|
|
818
|
+
const item = items[activeIndex];
|
|
819
|
+
if (item) selectItem(item);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("input", {
|
|
823
|
+
autoFocus: true,
|
|
824
|
+
className: selection_menu_module_default.Input,
|
|
825
|
+
value: query,
|
|
826
|
+
placeholder: "Filter commands...",
|
|
827
|
+
"data-testid": "selection-menu-input",
|
|
828
|
+
onChange: (event) => setQuery(event.target.value),
|
|
829
|
+
onKeyDown: onInputKeyDown
|
|
830
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
831
|
+
role: "listbox",
|
|
832
|
+
className: selection_menu_module_default.List,
|
|
833
|
+
children: [items.map((item, index) => /* @__PURE__ */ jsxs("button", {
|
|
834
|
+
type: "button",
|
|
835
|
+
role: "option",
|
|
836
|
+
"aria-selected": index === activeIndex,
|
|
837
|
+
className: selection_menu_module_default.Item,
|
|
838
|
+
"data-active": index === activeIndex || void 0,
|
|
839
|
+
onPointerEnter: () => setActiveIndex(index),
|
|
840
|
+
onClick: () => selectItem(item),
|
|
841
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
842
|
+
className: selection_menu_module_default.Label,
|
|
843
|
+
children: item.label
|
|
844
|
+
}), item.detail ? /* @__PURE__ */ jsx("span", {
|
|
845
|
+
className: selection_menu_module_default.Detail,
|
|
846
|
+
children: item.detail
|
|
847
|
+
}) : null]
|
|
848
|
+
}, item.id)), items.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
849
|
+
className: selection_menu_module_default.Empty,
|
|
850
|
+
children: loading ? "Loading..." : "No commands"
|
|
851
|
+
}) : null]
|
|
852
|
+
})] });
|
|
853
|
+
}
|
|
854
|
+
|
|
545
855
|
//#endregion
|
|
546
856
|
//#region src/utils/date-format.ts
|
|
547
857
|
/** Formats the current wall-clock time for the `/now` slash command. */
|
|
@@ -572,21 +882,57 @@ var autocomplete_menu_module_default = {
|
|
|
572
882
|
//#endregion
|
|
573
883
|
//#region src/components/slash-menu.tsx
|
|
574
884
|
const regex$2 = canUseRegexLookbehind() ? /(?<!\S)\/(\S.*)?$/u : /\/(\S.*)?$/u;
|
|
575
|
-
function SlashMenuItem({ label, kbd, onSelect }) {
|
|
885
|
+
function SlashMenuItem({ label, detail, kbd, onSelect }) {
|
|
576
886
|
return /* @__PURE__ */ jsxs(AutocompleteItem, {
|
|
887
|
+
value: label,
|
|
577
888
|
className: autocomplete_menu_module_default.Item,
|
|
578
889
|
onSelect,
|
|
579
|
-
children: [
|
|
890
|
+
children: [
|
|
891
|
+
/* @__PURE__ */ jsx("span", {
|
|
892
|
+
className: detail ? autocomplete_menu_module_default.Label : void 0,
|
|
893
|
+
children: label
|
|
894
|
+
}),
|
|
895
|
+
detail ? /* @__PURE__ */ jsx("span", {
|
|
896
|
+
className: autocomplete_menu_module_default.Detail,
|
|
897
|
+
children: detail
|
|
898
|
+
}) : null,
|
|
899
|
+
kbd && /* @__PURE__ */ jsx("kbd", { children: kbd })
|
|
900
|
+
]
|
|
580
901
|
});
|
|
581
902
|
}
|
|
582
903
|
function selectionInTableCell(editor) {
|
|
583
904
|
return isSelectionInTableCell(editor.state);
|
|
584
905
|
}
|
|
585
|
-
function SlashMenu({ timeFormat = "12" }) {
|
|
906
|
+
function SlashMenu({ timeFormat = "12", onSlashMenuSearch }) {
|
|
586
907
|
const editor = useEditor$1();
|
|
587
908
|
const inTableCell = useEditorDerivedValue(selectionInTableCell);
|
|
909
|
+
const [open, setOpen] = useState(false);
|
|
910
|
+
const [query, setQuery] = useState("");
|
|
911
|
+
const [hostItems, setHostItems] = useState([]);
|
|
912
|
+
const fetchHostItems = useCallback(async (query, signal) => {
|
|
913
|
+
if (!onSlashMenuSearch || signal.aborted) return;
|
|
914
|
+
const result = await onSlashMenuSearch(query);
|
|
915
|
+
if (signal.aborted) return;
|
|
916
|
+
setHostItems(result);
|
|
917
|
+
}, [onSlashMenuSearch]);
|
|
918
|
+
useEffect(() => {
|
|
919
|
+
if (!open) return;
|
|
920
|
+
const controller = new AbortController();
|
|
921
|
+
queueMicrotask(() => {
|
|
922
|
+
fetchHostItems(query, controller.signal);
|
|
923
|
+
});
|
|
924
|
+
return () => {
|
|
925
|
+
controller.abort();
|
|
926
|
+
};
|
|
927
|
+
}, [
|
|
928
|
+
open,
|
|
929
|
+
query,
|
|
930
|
+
fetchHostItems
|
|
931
|
+
]);
|
|
588
932
|
return /* @__PURE__ */ jsx(AutocompleteRoot, {
|
|
589
933
|
regex: regex$2,
|
|
934
|
+
onOpenChange: (event) => setOpen(event.detail),
|
|
935
|
+
onQueryChange: (event) => setQuery(event.detail),
|
|
590
936
|
children: /* @__PURE__ */ jsx(AutocompletePositioner, {
|
|
591
937
|
className: autocomplete_menu_module_default.Positioner,
|
|
592
938
|
children: /* @__PURE__ */ jsxs(AutocompletePopup, {
|
|
@@ -657,6 +1003,11 @@ function SlashMenu({ timeFormat = "12" }) {
|
|
|
657
1003
|
label: "Now",
|
|
658
1004
|
onSelect: () => editor.commands.insertText({ text: formatNowTime(timeFormat) })
|
|
659
1005
|
}),
|
|
1006
|
+
hostItems.map((item) => /* @__PURE__ */ jsx(SlashMenuItem, {
|
|
1007
|
+
label: item.label,
|
|
1008
|
+
detail: item.detail,
|
|
1009
|
+
onSelect: item.onSelect
|
|
1010
|
+
}, item.id ?? item.label)),
|
|
660
1011
|
/* @__PURE__ */ jsx(AutocompleteEmpty, {
|
|
661
1012
|
className: autocomplete_menu_module_default.Item,
|
|
662
1013
|
children: "No results"
|
|
@@ -977,9 +1328,9 @@ function resolveSelection(doc, selection) {
|
|
|
977
1328
|
return TextSelection.between(doc.resolve(anchor), doc.resolve(head));
|
|
978
1329
|
}
|
|
979
1330
|
}
|
|
980
|
-
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl,
|
|
1331
|
+
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, bulletAfterHeading, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, ref, children }) {
|
|
981
1332
|
const [editor] = useState(() => {
|
|
982
|
-
const editor = createEditor({ extension: union(defineEditorExtension(), defineCodeBlockView()) });
|
|
1333
|
+
const editor = createEditor({ extension: union(defineEditorExtension({ resolveFileLink }), defineCodeBlockView()) });
|
|
983
1334
|
if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
|
|
984
1335
|
nodes: editor.nodes,
|
|
985
1336
|
frontmatter
|
|
@@ -987,6 +1338,21 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
987
1338
|
return editor;
|
|
988
1339
|
});
|
|
989
1340
|
const suppressDocChangeRef = useRef(false);
|
|
1341
|
+
const [selectionMenuContext, setSelectionMenuContext] = useState();
|
|
1342
|
+
const hasSelectionMenu = !!onSelectionMenuSearch;
|
|
1343
|
+
const openSelectionMenu = useCallback(() => {
|
|
1344
|
+
const { state } = editor;
|
|
1345
|
+
const { from, to, empty } = state.selection;
|
|
1346
|
+
if (empty) return;
|
|
1347
|
+
setSelectionMenuContext({
|
|
1348
|
+
selectedText: getSelectedText(state),
|
|
1349
|
+
from,
|
|
1350
|
+
to
|
|
1351
|
+
});
|
|
1352
|
+
}, [editor]);
|
|
1353
|
+
const closeSelectionMenu = useCallback(() => {
|
|
1354
|
+
setSelectionMenuContext(void 0);
|
|
1355
|
+
}, []);
|
|
990
1356
|
useImperativeHandle(ref, () => {
|
|
991
1357
|
function getMarkdown() {
|
|
992
1358
|
return docToMarkdown(editor.state.doc, { frontmatter });
|
|
@@ -1018,6 +1384,9 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
1018
1384
|
function setMarkdown(markdown) {
|
|
1019
1385
|
setState(markdown);
|
|
1020
1386
|
}
|
|
1387
|
+
function insertMarkdown(markdown) {
|
|
1388
|
+
editor.commands.insertMarkdown(markdown);
|
|
1389
|
+
}
|
|
1021
1390
|
function setSelection(selection) {
|
|
1022
1391
|
setState(void 0, selection);
|
|
1023
1392
|
}
|
|
@@ -1027,18 +1396,49 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
1027
1396
|
function scrollIntoView() {
|
|
1028
1397
|
editor.view.dispatch(editor.state.tr.scrollIntoView());
|
|
1029
1398
|
}
|
|
1399
|
+
function getSelectedTextFromState() {
|
|
1400
|
+
return getSelectedText(editor.state);
|
|
1401
|
+
}
|
|
1402
|
+
function openSelectionMenuFromHandle() {
|
|
1403
|
+
if (!hasSelectionMenu) return;
|
|
1404
|
+
openSelectionMenu();
|
|
1405
|
+
}
|
|
1406
|
+
function startPendingReplacement(options) {
|
|
1407
|
+
return editor.commands.startPendingReplacement(options);
|
|
1408
|
+
}
|
|
1409
|
+
function appendPendingReplacementText(text) {
|
|
1410
|
+
editor.commands.appendPendingReplacementText(text);
|
|
1411
|
+
}
|
|
1412
|
+
function acceptPendingReplacement(options) {
|
|
1413
|
+
editor.commands.acceptPendingReplacement(options ?? {});
|
|
1414
|
+
}
|
|
1415
|
+
function discardPendingReplacement() {
|
|
1416
|
+
editor.commands.discardPendingReplacement();
|
|
1417
|
+
}
|
|
1030
1418
|
return {
|
|
1031
1419
|
getMarkdown,
|
|
1032
1420
|
setMarkdown,
|
|
1421
|
+
insertMarkdown,
|
|
1033
1422
|
getState,
|
|
1034
1423
|
setState,
|
|
1035
1424
|
getSelection,
|
|
1036
1425
|
setSelection,
|
|
1037
1426
|
focus,
|
|
1038
1427
|
scrollIntoView,
|
|
1428
|
+
getSelectedText: getSelectedTextFromState,
|
|
1429
|
+
openSelectionMenu: openSelectionMenuFromHandle,
|
|
1430
|
+
startPendingReplacement,
|
|
1431
|
+
appendPendingReplacementText,
|
|
1432
|
+
acceptPendingReplacement,
|
|
1433
|
+
discardPendingReplacement,
|
|
1039
1434
|
editor
|
|
1040
1435
|
};
|
|
1041
|
-
}, [
|
|
1436
|
+
}, [
|
|
1437
|
+
editor,
|
|
1438
|
+
frontmatter,
|
|
1439
|
+
hasSelectionMenu,
|
|
1440
|
+
openSelectionMenu
|
|
1441
|
+
]);
|
|
1042
1442
|
const handleDocChange = useMemo(() => {
|
|
1043
1443
|
if (!onDocChange) return;
|
|
1044
1444
|
return () => {
|
|
@@ -1061,8 +1461,10 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
1061
1461
|
onTagClick,
|
|
1062
1462
|
onExitBoundary,
|
|
1063
1463
|
resolveImageUrl,
|
|
1064
|
-
|
|
1065
|
-
|
|
1464
|
+
resolveFileInfo,
|
|
1465
|
+
onFileClick,
|
|
1466
|
+
onFilePaste,
|
|
1467
|
+
onFileSaveError,
|
|
1066
1468
|
onImageClick,
|
|
1067
1469
|
embedPaste,
|
|
1068
1470
|
bulletAfterHeading,
|
|
@@ -1074,13 +1476,27 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
1074
1476
|
blockHandle && !readOnly && /* @__PURE__ */ jsx(BlockHandle, {}),
|
|
1075
1477
|
!readOnly && /* @__PURE__ */ jsx(TableHandle, {}),
|
|
1076
1478
|
blockHandle && !readOnly && /* @__PURE__ */ jsx(DropIndicator$1, {}),
|
|
1077
|
-
/* @__PURE__ */ jsx(SlashMenu, {
|
|
1479
|
+
/* @__PURE__ */ jsx(SlashMenu, {
|
|
1480
|
+
timeFormat,
|
|
1481
|
+
onSlashMenuSearch
|
|
1482
|
+
}),
|
|
1078
1483
|
!readOnly && /* @__PURE__ */ jsx(LinkMenu, {
|
|
1079
1484
|
onLinkClick,
|
|
1080
1485
|
onLinkCopy
|
|
1081
1486
|
}),
|
|
1082
1487
|
onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
|
|
1083
1488
|
onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
|
|
1489
|
+
onSelectionMenuSearch && !readOnly && /* @__PURE__ */ jsx(SelectionMenu, {
|
|
1490
|
+
onSelectionMenuSearch,
|
|
1491
|
+
context: selectionMenuContext,
|
|
1492
|
+
onOpen: openSelectionMenu,
|
|
1493
|
+
onClose: closeSelectionMenu,
|
|
1494
|
+
affordance: selectionMenuAffordance
|
|
1495
|
+
}),
|
|
1496
|
+
!readOnly && /* @__PURE__ */ jsx(PendingReplacementPreview, {
|
|
1497
|
+
actions: pendingReplacementActions,
|
|
1498
|
+
onResolve: onPendingReplacementResolve
|
|
1499
|
+
}),
|
|
1084
1500
|
children
|
|
1085
1501
|
]
|
|
1086
1502
|
});
|
|
@@ -1088,7 +1504,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
|
|
|
1088
1504
|
|
|
1089
1505
|
//#endregion
|
|
1090
1506
|
//#region src/components/editor.tsx
|
|
1091
|
-
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl,
|
|
1507
|
+
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste = true, bulletAfterHeading = false, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
|
|
1092
1508
|
const childRef = useRef(null);
|
|
1093
1509
|
useImperativeHandle(handleRef, () => {
|
|
1094
1510
|
function getMarkdown() {
|
|
@@ -1097,6 +1513,9 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSea
|
|
|
1097
1513
|
function setMarkdown(markdown) {
|
|
1098
1514
|
childRef.current?.setMarkdown(markdown);
|
|
1099
1515
|
}
|
|
1516
|
+
function insertMarkdown(markdown) {
|
|
1517
|
+
childRef.current?.insertMarkdown(markdown);
|
|
1518
|
+
}
|
|
1100
1519
|
function getState() {
|
|
1101
1520
|
return childRef.current?.getState() ?? ["", {
|
|
1102
1521
|
type: "text",
|
|
@@ -1123,15 +1542,40 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSea
|
|
|
1123
1542
|
function scrollIntoView() {
|
|
1124
1543
|
childRef.current?.scrollIntoView();
|
|
1125
1544
|
}
|
|
1545
|
+
function getSelectedText() {
|
|
1546
|
+
return childRef.current?.getSelectedText() ?? "";
|
|
1547
|
+
}
|
|
1548
|
+
function openSelectionMenu() {
|
|
1549
|
+
childRef.current?.openSelectionMenu();
|
|
1550
|
+
}
|
|
1551
|
+
function startPendingReplacement(options) {
|
|
1552
|
+
return childRef.current?.startPendingReplacement(options) ?? false;
|
|
1553
|
+
}
|
|
1554
|
+
function appendPendingReplacementText(text) {
|
|
1555
|
+
childRef.current?.appendPendingReplacementText(text);
|
|
1556
|
+
}
|
|
1557
|
+
function acceptPendingReplacement(options) {
|
|
1558
|
+
childRef.current?.acceptPendingReplacement(options);
|
|
1559
|
+
}
|
|
1560
|
+
function discardPendingReplacement() {
|
|
1561
|
+
childRef.current?.discardPendingReplacement();
|
|
1562
|
+
}
|
|
1126
1563
|
return {
|
|
1127
1564
|
getMarkdown,
|
|
1128
1565
|
setMarkdown,
|
|
1566
|
+
insertMarkdown,
|
|
1129
1567
|
getState,
|
|
1130
1568
|
setState,
|
|
1131
1569
|
getSelection,
|
|
1132
1570
|
setSelection,
|
|
1133
1571
|
focus,
|
|
1134
1572
|
scrollIntoView,
|
|
1573
|
+
getSelectedText,
|
|
1574
|
+
openSelectionMenu,
|
|
1575
|
+
startPendingReplacement,
|
|
1576
|
+
appendPendingReplacementText,
|
|
1577
|
+
acceptPendingReplacement,
|
|
1578
|
+
discardPendingReplacement,
|
|
1135
1579
|
get editor() {
|
|
1136
1580
|
return childRef.current?.editor;
|
|
1137
1581
|
}
|
|
@@ -1144,16 +1588,24 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSea
|
|
|
1144
1588
|
markMode: mode,
|
|
1145
1589
|
initialMarkdown,
|
|
1146
1590
|
onDocChange,
|
|
1591
|
+
onSlashMenuSearch,
|
|
1147
1592
|
onTagSearch,
|
|
1148
1593
|
onWikilinkSearch,
|
|
1594
|
+
onSelectionMenuSearch,
|
|
1595
|
+
selectionMenuAffordance,
|
|
1596
|
+
pendingReplacementActions,
|
|
1597
|
+
onPendingReplacementResolve,
|
|
1149
1598
|
onWikilinkClick,
|
|
1150
1599
|
onLinkClick,
|
|
1151
1600
|
onLinkCopy,
|
|
1152
1601
|
onTagClick,
|
|
1153
1602
|
onExitBoundary,
|
|
1154
1603
|
resolveImageUrl,
|
|
1155
|
-
|
|
1156
|
-
|
|
1604
|
+
resolveFileLink,
|
|
1605
|
+
resolveFileInfo,
|
|
1606
|
+
onFileClick,
|
|
1607
|
+
onFilePaste,
|
|
1608
|
+
onFileSaveError,
|
|
1157
1609
|
onImageClick,
|
|
1158
1610
|
embedPaste,
|
|
1159
1611
|
bulletAfterHeading,
|
package/dist/style.css
CHANGED
|
@@ -344,6 +344,187 @@
|
|
|
344
344
|
outline-offset: -1px;
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
|
+
.meow_Positioner_DPJ5ia {
|
|
348
|
+
z-index: 50;
|
|
349
|
+
width: min(24rem, 100vw - 1rem);
|
|
350
|
+
display: block;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.meow_Popup_DPJ5ia {
|
|
354
|
+
box-sizing: border-box;
|
|
355
|
+
border: 1px solid var(--meowdown-border);
|
|
356
|
+
background: var(--meowdown-popover-bg);
|
|
357
|
+
border-radius: .75rem;
|
|
358
|
+
flex-direction: column;
|
|
359
|
+
width: 100%;
|
|
360
|
+
font-size: .875rem;
|
|
361
|
+
display: flex;
|
|
362
|
+
box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.meow_Text_DPJ5ia {
|
|
366
|
+
white-space: pre-wrap;
|
|
367
|
+
overflow-wrap: break-word;
|
|
368
|
+
max-height: 14rem;
|
|
369
|
+
color: var(--meowdown-text);
|
|
370
|
+
padding: .625rem .75rem;
|
|
371
|
+
overflow-y: auto;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.meow_Waiting_DPJ5ia {
|
|
375
|
+
color: var(--meowdown-muted);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.meow_Footer_DPJ5ia {
|
|
379
|
+
border-top: 1px solid var(--meowdown-border);
|
|
380
|
+
align-items: center;
|
|
381
|
+
gap: .25rem;
|
|
382
|
+
padding: .375rem;
|
|
383
|
+
display: flex;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.meow_Spacer_DPJ5ia {
|
|
387
|
+
flex: 1;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.meow_Button_DPJ5ia {
|
|
391
|
+
color: var(--meowdown-text);
|
|
392
|
+
cursor: pointer;
|
|
393
|
+
border-radius: .5rem;
|
|
394
|
+
align-items: center;
|
|
395
|
+
padding: .25rem .625rem;
|
|
396
|
+
display: inline-flex;
|
|
397
|
+
|
|
398
|
+
&:hover {
|
|
399
|
+
background: var(--meowdown-popover-hover-bg);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.meow_AcceptButton_DPJ5ia {
|
|
404
|
+
color: var(--meowdown-popover-bg);
|
|
405
|
+
background: var(--meowdown-accent);
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
border-radius: .5rem;
|
|
408
|
+
align-items: center;
|
|
409
|
+
padding: .25rem .625rem;
|
|
410
|
+
display: inline-flex;
|
|
411
|
+
|
|
412
|
+
&:hover {
|
|
413
|
+
filter: brightness(1.05);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
&:disabled {
|
|
417
|
+
opacity: .5;
|
|
418
|
+
cursor: default;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
.meow_Positioner_2_4Zjq {
|
|
422
|
+
z-index: 50;
|
|
423
|
+
width: min(18rem, 100vw - 1rem);
|
|
424
|
+
display: block;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.meow_Popup_2_4Zjq {
|
|
428
|
+
box-sizing: border-box;
|
|
429
|
+
border: 1px solid var(--meowdown-border);
|
|
430
|
+
background: var(--meowdown-popover-bg);
|
|
431
|
+
border-radius: .75rem;
|
|
432
|
+
flex-direction: column;
|
|
433
|
+
width: 100%;
|
|
434
|
+
padding: .25rem;
|
|
435
|
+
font-size: .875rem;
|
|
436
|
+
display: flex;
|
|
437
|
+
box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.meow_Input_2_4Zjq {
|
|
441
|
+
box-sizing: border-box;
|
|
442
|
+
width: 100%;
|
|
443
|
+
font: inherit;
|
|
444
|
+
color: var(--meowdown-text);
|
|
445
|
+
border: 1px solid var(--meowdown-border);
|
|
446
|
+
background: none;
|
|
447
|
+
border-radius: .5rem;
|
|
448
|
+
margin-bottom: .25rem;
|
|
449
|
+
padding: .375rem .5rem;
|
|
450
|
+
|
|
451
|
+
&:focus {
|
|
452
|
+
outline: 2px solid var(--meowdown-accent);
|
|
453
|
+
outline-offset: -1px;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.meow_List_2_4Zjq {
|
|
458
|
+
flex-direction: column;
|
|
459
|
+
max-height: 16rem;
|
|
460
|
+
display: flex;
|
|
461
|
+
overflow-y: auto;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.meow_Item_2_4Zjq {
|
|
465
|
+
text-align: left;
|
|
466
|
+
color: var(--meowdown-text);
|
|
467
|
+
cursor: pointer;
|
|
468
|
+
border-radius: .5rem;
|
|
469
|
+
align-items: baseline;
|
|
470
|
+
gap: .5rem;
|
|
471
|
+
padding: .375rem .5rem;
|
|
472
|
+
display: flex;
|
|
473
|
+
|
|
474
|
+
&[data-active] {
|
|
475
|
+
background: var(--meowdown-popover-hover-bg);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.meow_Label_2_4Zjq {
|
|
480
|
+
flex: none;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.meow_Detail_2_4Zjq {
|
|
484
|
+
white-space: nowrap;
|
|
485
|
+
text-overflow: ellipsis;
|
|
486
|
+
min-width: 0;
|
|
487
|
+
color: var(--meowdown-muted);
|
|
488
|
+
flex: 1;
|
|
489
|
+
font-size: .8125rem;
|
|
490
|
+
overflow: hidden;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.meow_Empty_2_4Zjq {
|
|
494
|
+
color: var(--meowdown-muted);
|
|
495
|
+
padding: .375rem .5rem;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.meow_AffordancePositioner_2_4Zjq {
|
|
499
|
+
z-index: 40;
|
|
500
|
+
display: block;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.meow_AffordancePopup_2_4Zjq {
|
|
504
|
+
display: flex;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.meow_AffordanceButton_2_4Zjq {
|
|
508
|
+
color: var(--meowdown-muted);
|
|
509
|
+
background: var(--meowdown-popover-bg);
|
|
510
|
+
border: 1px solid var(--meowdown-border);
|
|
511
|
+
cursor: pointer;
|
|
512
|
+
border-radius: .5rem;
|
|
513
|
+
justify-content: center;
|
|
514
|
+
align-items: center;
|
|
515
|
+
padding: .25rem;
|
|
516
|
+
display: inline-flex;
|
|
517
|
+
|
|
518
|
+
&:hover {
|
|
519
|
+
color: var(--meowdown-accent);
|
|
520
|
+
background: var(--meowdown-popover-hover-bg);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
& svg {
|
|
524
|
+
width: .875rem;
|
|
525
|
+
height: .875rem;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
347
528
|
.meow_Positioner_Dqll0G {
|
|
348
529
|
z-index: 50;
|
|
349
530
|
width: min(24rem, 100vw - 1rem);
|
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.32.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@prosekit/react": "^0.8.0-beta.15",
|
|
26
26
|
"clsx": "^2.1.1",
|
|
27
27
|
"lucide-react": "^1.21.0",
|
|
28
|
-
"@meowdown/core": "0.
|
|
28
|
+
"@meowdown/core": "0.32.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": "^19.0.0",
|