@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 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
- * Called with the link target on click of a rendered wiki link. Pass a stable
120
- * function (e.g. from `useCallback`).
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)`). Pass a stable function (e.g. from `useCallback`).
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`. Pass a stable function (e.g. from `useCallback`).
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
- * Persists a pasted/dropped image file and returns its markdown `src`. Pass a
156
- * stable function.
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 `![](src)` 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
- onImagePaste?: ImageOptions['onImagePaste'];
159
- /** Called when persisting a pasted/dropped image throws. */
160
- onImageSaveError?: ImageOptions['onImageSaveError'];
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
- onImagePaste,
227
- onImageSaveError,
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, onImagePaste, onImageSaveError, 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,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
- resolveImageUrl,
244
- onImagePaste,
245
- onImageSaveError
246
- });
247
- }, [
248
- resolveImageUrl,
249
- onImagePaste,
250
- onImageSaveError
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: [/* @__PURE__ */ jsx("span", { children: label }), kbd && /* @__PURE__ */ jsx("kbd", { children: kbd })]
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, onImagePaste, onImageSaveError, onImageClick, embedPaste, bulletAfterHeading, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, ref, children }) {
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
- }, [editor, frontmatter]);
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
- onImagePaste,
1065
- onImageSaveError,
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, { timeFormat }),
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, onImagePaste, onImageSaveError, onImageClick, embedPaste = true, bulletAfterHeading = false, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
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
- onImagePaste,
1156
- onImageSaveError,
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.30.1",
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.30.1"
28
+ "@meowdown/core": "0.32.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "react": "^19.0.0",