@meowdown/react 0.31.0 → 0.33.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 +2 -0
- package/dist/index.d.ts +126 -7
- package/dist/index.js +412 -10
- package/dist/style.css +181 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ export function App() {
|
|
|
54
54
|
|
|
55
55
|
See the full API reference [here](https://npmx.dev/package-docs/@meowdown%2Freact/).
|
|
56
56
|
|
|
57
|
+
Slash menu host items can include `keywords` to match hidden terms without changing the displayed label.
|
|
58
|
+
|
|
57
59
|
## Styling
|
|
58
60
|
|
|
59
61
|
Import both stylesheets: `@meowdown/core/style.css` (the editor theme and variables) and `@meowdown/react/style.css` (the component layout). The core theme is documented in [`@meowdown/core`](https://www.npmjs.com/package/@meowdown/core).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactElement, ReactNode, Ref } from "react";
|
|
2
|
-
import { ExitBoundaryHandler, FilePasteOptions, 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
|
|
|
@@ -24,7 +24,8 @@ interface EditorHandle {
|
|
|
24
24
|
setMarkdown: (markdown: string) => void;
|
|
25
25
|
/**
|
|
26
26
|
* Parses `markdown` and inserts it at the current selection as a single
|
|
27
|
-
* undoable edit
|
|
27
|
+
* undoable edit. An active selection collapses first and is never
|
|
28
|
+
* deleted - this is a host-initiated insert, not a paste. A lone paragraph is
|
|
28
29
|
* inserted inline at the cursor; anything else is inserted as blocks. The
|
|
29
30
|
* cursor lands at the end of the inserted content. An empty or
|
|
30
31
|
* whitespace-only string is a no-op. Unlike `setMarkdown`, it fires
|
|
@@ -48,6 +49,33 @@ interface EditorHandle {
|
|
|
48
49
|
focus: () => void;
|
|
49
50
|
/** Scrolls the selection into view. */
|
|
50
51
|
scrollIntoView: () => void;
|
|
52
|
+
/**
|
|
53
|
+
* The current selection as Markdown: block structure (list markers,
|
|
54
|
+
* headings) is serialized, and inline syntax is already literal text. A
|
|
55
|
+
* selection inside one textblock comes back as its bare text.
|
|
56
|
+
*/
|
|
57
|
+
getSelectedText: () => string;
|
|
58
|
+
/**
|
|
59
|
+
* Opens the selection menu over the current selection. A no-op when the
|
|
60
|
+
* selection is empty or `onSelectionMenuSearch` is not set.
|
|
61
|
+
*/
|
|
62
|
+
openSelectionMenu: () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Stages a pending replacement over a document range. Returns `false` when
|
|
65
|
+
* the range is invalid. Calling it again resets the accumulated text, which
|
|
66
|
+
* is how a retry starts.
|
|
67
|
+
*/
|
|
68
|
+
startPendingReplacement: (options: StartPendingReplacementOptions) => boolean;
|
|
69
|
+
/** Appends streamed text to the staged replacement. */
|
|
70
|
+
appendPendingReplacementText: (text: string) => void;
|
|
71
|
+
/**
|
|
72
|
+
* Applies the staged replacement to the document as a single edit. Pass a
|
|
73
|
+
* `mode` to override the staged placement for this accept (e.g. "Insert
|
|
74
|
+
* below" on a replace stage).
|
|
75
|
+
*/
|
|
76
|
+
acceptPendingReplacement: (options?: AcceptPendingReplacementOptions) => void;
|
|
77
|
+
/** Clears the staged replacement without touching the document. */
|
|
78
|
+
discardPendingReplacement: () => void;
|
|
51
79
|
/**
|
|
52
80
|
* Escape hatch: the underlying ProseKit editor, or `undefined` when the
|
|
53
81
|
* handle does not wrap one.
|
|
@@ -60,6 +88,8 @@ interface SlashMenuItem {
|
|
|
60
88
|
id?: string;
|
|
61
89
|
/** Display text, matched against the typed query like the built-in items. */
|
|
62
90
|
label: string;
|
|
91
|
+
/** Extra match terms beyond the label; never displayed. */
|
|
92
|
+
keywords?: string[];
|
|
63
93
|
/** Secondary text shown beside the label. */
|
|
64
94
|
detail?: string;
|
|
65
95
|
/** Runs after the menu closes and the typed `/query` text is removed. */
|
|
@@ -106,6 +136,34 @@ interface WikilinkItem {
|
|
|
106
136
|
* a promise.
|
|
107
137
|
*/
|
|
108
138
|
type WikilinkSearchHandler = (query: string) => WikilinkItem[] | Promise<WikilinkItem[]>;
|
|
139
|
+
/** The selection the selection menu was opened over. */
|
|
140
|
+
interface SelectionMenuContext {
|
|
141
|
+
/** The selected text, with block boundaries as blank lines. */
|
|
142
|
+
selectedText: string;
|
|
143
|
+
/** Start of the selection. */
|
|
144
|
+
from: number;
|
|
145
|
+
/** End of the selection. */
|
|
146
|
+
to: number;
|
|
147
|
+
}
|
|
148
|
+
/** One row in the selection menu. The host ranks the rows; the menu does not re-sort. */
|
|
149
|
+
interface SelectionMenuItem {
|
|
150
|
+
/** Stable identity for the row. */
|
|
151
|
+
id: string;
|
|
152
|
+
/** Display text. */
|
|
153
|
+
label: string;
|
|
154
|
+
/** Secondary text shown beside the label. */
|
|
155
|
+
detail?: string;
|
|
156
|
+
/** Runs when the row is picked, with the selection the menu was opened over. */
|
|
157
|
+
onSelect: (context: SelectionMenuContext) => void;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Searches commands for the selection menu. Receives the filter text typed in
|
|
161
|
+
* the menu (may be empty) and the selection the menu was opened over, and
|
|
162
|
+
* returns the rows to show, either synchronously or as a promise.
|
|
163
|
+
*/
|
|
164
|
+
type SelectionMenuSearchHandler = (query: string, context: SelectionMenuContext) => SelectionMenuItem[] | Promise<SelectionMenuItem[]>;
|
|
165
|
+
/** Reports how a pending replacement ended and its final staged value. */
|
|
166
|
+
type PendingReplacementResolveHandler = (outcome: PendingReplacementOutcome, pending: PendingReplacement) => void;
|
|
109
167
|
//#endregion
|
|
110
168
|
//#region src/components/editor.d.ts
|
|
111
169
|
type EditorMode = MarkMode;
|
|
@@ -151,13 +209,42 @@ interface EditorProps {
|
|
|
151
209
|
*/
|
|
152
210
|
onWikilinkSearch?: WikilinkSearchHandler;
|
|
153
211
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
212
|
+
* Searches commands for the selection menu, which opens over a non-empty
|
|
213
|
+
* selection via `EditorHandle.openSelectionMenu()` or the selection
|
|
214
|
+
* affordance. Receives the filter text typed in the menu (may be empty) and
|
|
215
|
+
* the selection the menu was opened over, and returns the rows to show,
|
|
216
|
+
* synchronously or as a promise. The host ranks the rows; the menu does not
|
|
217
|
+
* re-sort. Pass a stable function (e.g. from `useCallback`). Omit to disable
|
|
218
|
+
* the selection menu.
|
|
219
|
+
*/
|
|
220
|
+
onSelectionMenuSearch?: SelectionMenuSearchHandler;
|
|
221
|
+
/**
|
|
222
|
+
* Shows a small floating button on a non-empty text selection that opens the
|
|
223
|
+
* selection menu. On by default; only relevant when `onSelectionMenuSearch`
|
|
224
|
+
* is set. Ignored when `readOnly` is set.
|
|
225
|
+
*/
|
|
226
|
+
selectionMenuAffordance?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Extra controls rendered in the pending-replacement preview footer, next to
|
|
229
|
+
* the built-in Accept and Discard buttons (e.g. a retry button).
|
|
230
|
+
*/
|
|
231
|
+
pendingReplacementActions?: ReactNode;
|
|
232
|
+
/**
|
|
233
|
+
* Called when a pending replacement ends, with the outcome ('accepted' or
|
|
234
|
+
* 'discarded') and the final staged value. Use it to stop a stream that is
|
|
235
|
+
* still appending. Pass a stable function (e.g. from `useCallback`).
|
|
236
|
+
*/
|
|
237
|
+
onPendingReplacementResolve?: PendingReplacementResolveHandler;
|
|
238
|
+
/**
|
|
239
|
+
* Called with the link target on click of a rendered wiki link, or on
|
|
240
|
+
* `Mod-Enter` with the caret on one. Pass a stable function (e.g. from
|
|
241
|
+
* `useCallback`).
|
|
156
242
|
*/
|
|
157
243
|
onWikilinkClick?: WikilinkClickHandler;
|
|
158
244
|
/**
|
|
159
245
|
* Called with the link `href` on click of a rendered Markdown link
|
|
160
|
-
* (`[text](url)`)
|
|
246
|
+
* (`[text](url)`), or on `Mod-Enter` with the caret on one. Pass a stable
|
|
247
|
+
* function (e.g. from `useCallback`).
|
|
161
248
|
*/
|
|
162
249
|
onLinkClick?: LinkClickHandler;
|
|
163
250
|
/**
|
|
@@ -167,7 +254,8 @@ interface EditorProps {
|
|
|
167
254
|
onLinkCopy?: LinkCopyHandler;
|
|
168
255
|
/**
|
|
169
256
|
* Called with the tag name (without the leading `#`) on click of a rendered
|
|
170
|
-
* `#tag
|
|
257
|
+
* `#tag`, or on `Mod-Enter` with the caret on one. Pass a stable function
|
|
258
|
+
* (e.g. from `useCallback`).
|
|
171
259
|
*/
|
|
172
260
|
onTagClick?: TagClickHandler;
|
|
173
261
|
/**
|
|
@@ -186,6 +274,30 @@ interface EditorProps {
|
|
|
186
274
|
* `useCallback`).
|
|
187
275
|
*/
|
|
188
276
|
resolveImageUrl?: ImageOptions['resolveImageUrl'];
|
|
277
|
+
/**
|
|
278
|
+
* Claims a `[label](url)` link as a file: a claimed link renders as an
|
|
279
|
+
* inline pill (file icon, name, size) instead of a link, behaves as one
|
|
280
|
+
* caret unit, and reports clicks through `onFileClick` instead of
|
|
281
|
+
* `onLinkClick`. The markdown text is untouched. Must be pure (same link,
|
|
282
|
+
* same answer). Read once when the editor is created: later identity
|
|
283
|
+
* changes are ignored, like `initialMarkdown`.
|
|
284
|
+
*/
|
|
285
|
+
resolveFileLink?: FileLinkResolver;
|
|
286
|
+
/**
|
|
287
|
+
* Resolves the metadata (file size in bytes) shown on a file pill, directly
|
|
288
|
+
* or as a promise; the pill renders immediately and fills the size in when
|
|
289
|
+
* the promise settles. May be called repeatedly for the same `href`, so
|
|
290
|
+
* cache in the host when resolving is expensive. Pass a stable function
|
|
291
|
+
* (e.g. from `useCallback`).
|
|
292
|
+
*/
|
|
293
|
+
resolveFileInfo?: FileViewOptions['resolveFileInfo'];
|
|
294
|
+
/**
|
|
295
|
+
* Called when the user clicks a rendered file pill (or presses `Mod-Enter`
|
|
296
|
+
* with the caret on one), with its `href`, `name`, and the originating
|
|
297
|
+
* event. The host decides what a click does (e.g. open the file in the OS
|
|
298
|
+
* default app). Pass a stable function (e.g. from `useCallback`).
|
|
299
|
+
*/
|
|
300
|
+
onFileClick?: FileClickHandler;
|
|
189
301
|
/**
|
|
190
302
|
* Persists a pasted/dropped file and returns its markdown destination,
|
|
191
303
|
* inserted as `` for an image and as a `[name](src)` link for any
|
|
@@ -254,12 +366,19 @@ declare function MeowdownEditor({
|
|
|
254
366
|
onSlashMenuSearch,
|
|
255
367
|
onTagSearch,
|
|
256
368
|
onWikilinkSearch,
|
|
369
|
+
onSelectionMenuSearch,
|
|
370
|
+
selectionMenuAffordance,
|
|
371
|
+
pendingReplacementActions,
|
|
372
|
+
onPendingReplacementResolve,
|
|
257
373
|
onWikilinkClick,
|
|
258
374
|
onLinkClick,
|
|
259
375
|
onLinkCopy,
|
|
260
376
|
onTagClick,
|
|
261
377
|
onExitBoundary,
|
|
262
378
|
resolveImageUrl,
|
|
379
|
+
resolveFileLink,
|
|
380
|
+
resolveFileInfo,
|
|
381
|
+
onFileClick,
|
|
263
382
|
onFilePaste,
|
|
264
383
|
onFileSaveError,
|
|
265
384
|
onImageClick,
|
|
@@ -318,4 +437,4 @@ declare function MarkdownView({
|
|
|
318
437
|
className
|
|
319
438
|
}: MarkdownViewProps): ReactElement;
|
|
320
439
|
//#endregion
|
|
321
|
-
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, MarkdownView, type MarkdownViewProps, MeowdownEditor, type SelectionHint, type SelectionJSON, type SlashMenuItem, type SlashMenuSearchHandler, type TagItem, type TagSearchHandler, type TimeFormat, type WikilinkItem, type WikilinkSearchHandler, useEditor, useExtension, useKeymap };
|
|
440
|
+
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, defineFilePaste, 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, onFilePaste, onFileSaveError, onImageClick, embedPaste, bulletAfterHeading, placeholder, readOnly, wikilinkEnabled, spellCheck }) {
|
|
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,12 +235,31 @@ 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
255
|
return defineImage({ resolveImageUrl });
|
|
243
256
|
}, [resolveImageUrl]));
|
|
257
|
+
useExtension$1(useMemo(() => {
|
|
258
|
+
return defineFileView({ resolveFileInfo });
|
|
259
|
+
}, [resolveFileInfo]));
|
|
260
|
+
useExtension$1(useMemo(() => {
|
|
261
|
+
return onFileClick ? defineFileClickHandler(onFileClick) : null;
|
|
262
|
+
}, [onFileClick]));
|
|
244
263
|
useExtension$1(useMemo(() => {
|
|
245
264
|
return onFilePaste ? defineFilePaste({
|
|
246
265
|
onFilePaste,
|
|
@@ -540,6 +559,299 @@ function LinkMenu({ onLinkClick, onLinkCopy }) {
|
|
|
540
559
|
return null;
|
|
541
560
|
}
|
|
542
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
|
+
|
|
543
855
|
//#endregion
|
|
544
856
|
//#region src/utils/date-format.ts
|
|
545
857
|
/** Formats the current wall-clock time for the `/now` slash command. */
|
|
@@ -570,9 +882,9 @@ var autocomplete_menu_module_default = {
|
|
|
570
882
|
//#endregion
|
|
571
883
|
//#region src/components/slash-menu.tsx
|
|
572
884
|
const regex$2 = canUseRegexLookbehind() ? /(?<!\S)\/(\S.*)?$/u : /\/(\S.*)?$/u;
|
|
573
|
-
function SlashMenuItem({ label, detail, kbd, onSelect }) {
|
|
885
|
+
function SlashMenuItem({ label, keywords, detail, kbd, onSelect }) {
|
|
574
886
|
return /* @__PURE__ */ jsxs(AutocompleteItem, {
|
|
575
|
-
value: label,
|
|
887
|
+
value: [label, ...keywords ?? []].join(" "),
|
|
576
888
|
className: autocomplete_menu_module_default.Item,
|
|
577
889
|
onSelect,
|
|
578
890
|
children: [
|
|
@@ -693,6 +1005,7 @@ function SlashMenu({ timeFormat = "12", onSlashMenuSearch }) {
|
|
|
693
1005
|
}),
|
|
694
1006
|
hostItems.map((item) => /* @__PURE__ */ jsx(SlashMenuItem, {
|
|
695
1007
|
label: item.label,
|
|
1008
|
+
keywords: item.keywords,
|
|
696
1009
|
detail: item.detail,
|
|
697
1010
|
onSelect: item.onSelect
|
|
698
1011
|
}, item.id ?? item.label)),
|
|
@@ -1016,9 +1329,9 @@ function resolveSelection(doc, selection) {
|
|
|
1016
1329
|
return TextSelection.between(doc.resolve(anchor), doc.resolve(head));
|
|
1017
1330
|
}
|
|
1018
1331
|
}
|
|
1019
|
-
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, onFilePaste, onFileSaveError, onImageClick, embedPaste, bulletAfterHeading, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, ref, children }) {
|
|
1332
|
+
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 }) {
|
|
1020
1333
|
const [editor] = useState(() => {
|
|
1021
|
-
const editor = createEditor({ extension: union(defineEditorExtension(), defineCodeBlockView()) });
|
|
1334
|
+
const editor = createEditor({ extension: union(defineEditorExtension({ resolveFileLink }), defineCodeBlockView()) });
|
|
1022
1335
|
if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
|
|
1023
1336
|
nodes: editor.nodes,
|
|
1024
1337
|
frontmatter
|
|
@@ -1026,6 +1339,21 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1026
1339
|
return editor;
|
|
1027
1340
|
});
|
|
1028
1341
|
const suppressDocChangeRef = useRef(false);
|
|
1342
|
+
const [selectionMenuContext, setSelectionMenuContext] = useState();
|
|
1343
|
+
const hasSelectionMenu = !!onSelectionMenuSearch;
|
|
1344
|
+
const openSelectionMenu = useCallback(() => {
|
|
1345
|
+
const { state } = editor;
|
|
1346
|
+
const { from, to, empty } = state.selection;
|
|
1347
|
+
if (empty) return;
|
|
1348
|
+
setSelectionMenuContext({
|
|
1349
|
+
selectedText: getSelectedText(state),
|
|
1350
|
+
from,
|
|
1351
|
+
to
|
|
1352
|
+
});
|
|
1353
|
+
}, [editor]);
|
|
1354
|
+
const closeSelectionMenu = useCallback(() => {
|
|
1355
|
+
setSelectionMenuContext(void 0);
|
|
1356
|
+
}, []);
|
|
1029
1357
|
useImperativeHandle(ref, () => {
|
|
1030
1358
|
function getMarkdown() {
|
|
1031
1359
|
return docToMarkdown(editor.state.doc, { frontmatter });
|
|
@@ -1069,6 +1397,25 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1069
1397
|
function scrollIntoView() {
|
|
1070
1398
|
editor.view.dispatch(editor.state.tr.scrollIntoView());
|
|
1071
1399
|
}
|
|
1400
|
+
function getSelectedTextFromState() {
|
|
1401
|
+
return getSelectedText(editor.state);
|
|
1402
|
+
}
|
|
1403
|
+
function openSelectionMenuFromHandle() {
|
|
1404
|
+
if (!hasSelectionMenu) return;
|
|
1405
|
+
openSelectionMenu();
|
|
1406
|
+
}
|
|
1407
|
+
function startPendingReplacement(options) {
|
|
1408
|
+
return editor.commands.startPendingReplacement(options);
|
|
1409
|
+
}
|
|
1410
|
+
function appendPendingReplacementText(text) {
|
|
1411
|
+
editor.commands.appendPendingReplacementText(text);
|
|
1412
|
+
}
|
|
1413
|
+
function acceptPendingReplacement(options) {
|
|
1414
|
+
editor.commands.acceptPendingReplacement(options ?? {});
|
|
1415
|
+
}
|
|
1416
|
+
function discardPendingReplacement() {
|
|
1417
|
+
editor.commands.discardPendingReplacement();
|
|
1418
|
+
}
|
|
1072
1419
|
return {
|
|
1073
1420
|
getMarkdown,
|
|
1074
1421
|
setMarkdown,
|
|
@@ -1079,9 +1426,20 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1079
1426
|
setSelection,
|
|
1080
1427
|
focus,
|
|
1081
1428
|
scrollIntoView,
|
|
1429
|
+
getSelectedText: getSelectedTextFromState,
|
|
1430
|
+
openSelectionMenu: openSelectionMenuFromHandle,
|
|
1431
|
+
startPendingReplacement,
|
|
1432
|
+
appendPendingReplacementText,
|
|
1433
|
+
acceptPendingReplacement,
|
|
1434
|
+
discardPendingReplacement,
|
|
1082
1435
|
editor
|
|
1083
1436
|
};
|
|
1084
|
-
}, [
|
|
1437
|
+
}, [
|
|
1438
|
+
editor,
|
|
1439
|
+
frontmatter,
|
|
1440
|
+
hasSelectionMenu,
|
|
1441
|
+
openSelectionMenu
|
|
1442
|
+
]);
|
|
1085
1443
|
const handleDocChange = useMemo(() => {
|
|
1086
1444
|
if (!onDocChange) return;
|
|
1087
1445
|
return () => {
|
|
@@ -1104,6 +1462,8 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1104
1462
|
onTagClick,
|
|
1105
1463
|
onExitBoundary,
|
|
1106
1464
|
resolveImageUrl,
|
|
1465
|
+
resolveFileInfo,
|
|
1466
|
+
onFileClick,
|
|
1107
1467
|
onFilePaste,
|
|
1108
1468
|
onFileSaveError,
|
|
1109
1469
|
onImageClick,
|
|
@@ -1127,6 +1487,17 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1127
1487
|
}),
|
|
1128
1488
|
onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
|
|
1129
1489
|
onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
|
|
1490
|
+
onSelectionMenuSearch && !readOnly && /* @__PURE__ */ jsx(SelectionMenu, {
|
|
1491
|
+
onSelectionMenuSearch,
|
|
1492
|
+
context: selectionMenuContext,
|
|
1493
|
+
onOpen: openSelectionMenu,
|
|
1494
|
+
onClose: closeSelectionMenu,
|
|
1495
|
+
affordance: selectionMenuAffordance
|
|
1496
|
+
}),
|
|
1497
|
+
!readOnly && /* @__PURE__ */ jsx(PendingReplacementPreview, {
|
|
1498
|
+
actions: pendingReplacementActions,
|
|
1499
|
+
onResolve: onPendingReplacementResolve
|
|
1500
|
+
}),
|
|
1130
1501
|
children
|
|
1131
1502
|
]
|
|
1132
1503
|
});
|
|
@@ -1134,7 +1505,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1134
1505
|
|
|
1135
1506
|
//#endregion
|
|
1136
1507
|
//#region src/components/editor.tsx
|
|
1137
|
-
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, onFilePaste, onFileSaveError, onImageClick, embedPaste = true, bulletAfterHeading = false, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
|
|
1508
|
+
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 }) {
|
|
1138
1509
|
const childRef = useRef(null);
|
|
1139
1510
|
useImperativeHandle(handleRef, () => {
|
|
1140
1511
|
function getMarkdown() {
|
|
@@ -1172,6 +1543,24 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
|
|
|
1172
1543
|
function scrollIntoView() {
|
|
1173
1544
|
childRef.current?.scrollIntoView();
|
|
1174
1545
|
}
|
|
1546
|
+
function getSelectedText() {
|
|
1547
|
+
return childRef.current?.getSelectedText() ?? "";
|
|
1548
|
+
}
|
|
1549
|
+
function openSelectionMenu() {
|
|
1550
|
+
childRef.current?.openSelectionMenu();
|
|
1551
|
+
}
|
|
1552
|
+
function startPendingReplacement(options) {
|
|
1553
|
+
return childRef.current?.startPendingReplacement(options) ?? false;
|
|
1554
|
+
}
|
|
1555
|
+
function appendPendingReplacementText(text) {
|
|
1556
|
+
childRef.current?.appendPendingReplacementText(text);
|
|
1557
|
+
}
|
|
1558
|
+
function acceptPendingReplacement(options) {
|
|
1559
|
+
childRef.current?.acceptPendingReplacement(options);
|
|
1560
|
+
}
|
|
1561
|
+
function discardPendingReplacement() {
|
|
1562
|
+
childRef.current?.discardPendingReplacement();
|
|
1563
|
+
}
|
|
1175
1564
|
return {
|
|
1176
1565
|
getMarkdown,
|
|
1177
1566
|
setMarkdown,
|
|
@@ -1182,6 +1571,12 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
|
|
|
1182
1571
|
setSelection,
|
|
1183
1572
|
focus,
|
|
1184
1573
|
scrollIntoView,
|
|
1574
|
+
getSelectedText,
|
|
1575
|
+
openSelectionMenu,
|
|
1576
|
+
startPendingReplacement,
|
|
1577
|
+
appendPendingReplacementText,
|
|
1578
|
+
acceptPendingReplacement,
|
|
1579
|
+
discardPendingReplacement,
|
|
1185
1580
|
get editor() {
|
|
1186
1581
|
return childRef.current?.editor;
|
|
1187
1582
|
}
|
|
@@ -1197,12 +1592,19 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
|
|
|
1197
1592
|
onSlashMenuSearch,
|
|
1198
1593
|
onTagSearch,
|
|
1199
1594
|
onWikilinkSearch,
|
|
1595
|
+
onSelectionMenuSearch,
|
|
1596
|
+
selectionMenuAffordance,
|
|
1597
|
+
pendingReplacementActions,
|
|
1598
|
+
onPendingReplacementResolve,
|
|
1200
1599
|
onWikilinkClick,
|
|
1201
1600
|
onLinkClick,
|
|
1202
1601
|
onLinkCopy,
|
|
1203
1602
|
onTagClick,
|
|
1204
1603
|
onExitBoundary,
|
|
1205
1604
|
resolveImageUrl,
|
|
1605
|
+
resolveFileLink,
|
|
1606
|
+
resolveFileInfo,
|
|
1607
|
+
onFileClick,
|
|
1206
1608
|
onFilePaste,
|
|
1207
1609
|
onFileSaveError,
|
|
1208
1610
|
onImageClick,
|
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.33.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.33.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": "^19.0.0",
|