@meowdown/react 0.24.1 → 0.26.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,8 +1,11 @@
1
1
  import { ReactElement, ReactNode, Ref } from "react";
2
- import { ExitBoundaryHandler, ImageClickHandler, ImageOptions, LinkClickHandler, MarkMode, PlaceholderOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
2
+ import { ExitBoundaryHandler, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, MarkMode, PlaceholderOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
3
3
  import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
4
4
  import { useEditor, useExtension, useKeymap } from "@prosekit/react";
5
5
 
6
+ //#region src/utils/date-format.d.ts
7
+ type TimeFormat = '12' | '24';
8
+ //#endregion
6
9
  //#region src/components/types.d.ts
7
10
  /** A selection to restore: an exact JSON selection, or a document edge. */
8
11
  type SelectionHint = SelectionJSON$1 | 'start' | 'end';
@@ -124,6 +127,12 @@ interface EditorProps {
124
127
  * in source mode.
125
128
  */
126
129
  onLinkClick?: LinkClickHandler;
130
+ /**
131
+ * Called after a link is copied from the link menu, with its `href`. Useful
132
+ * for a toast. Pass a stable function (e.g. from `useCallback`). Ignored in
133
+ * source mode.
134
+ */
135
+ onLinkCopy?: LinkCopyHandler;
127
136
  /**
128
137
  * Called with the tag name (without the leading `#`) on click of a rendered
129
138
  * `#tag`. Pass a stable function (e.g. from `useCallback`). Ignored in source
@@ -192,6 +201,11 @@ interface EditorProps {
192
201
  * to the browser's behavior. Ignored in source mode.
193
202
  */
194
203
  spellCheck?: boolean;
204
+ /**
205
+ * Clock format the `/now` slash command inserts: '12' for "3:45pm" or '24'
206
+ * for "15:45". Defaults to '12'. Ignored in source mode.
207
+ */
208
+ timeFormat?: TimeFormat;
195
209
  /** Class on the editable root (the contenteditable). Rich modes only. */
196
210
  editorClassName?: string;
197
211
  /** Class on the outer `.meowdown` wrapper div. */
@@ -209,6 +223,7 @@ declare function MeowdownEditor({
209
223
  onWikilinkSearch,
210
224
  onWikilinkClick,
211
225
  onLinkClick,
226
+ onLinkCopy,
212
227
  onTagClick,
213
228
  onExitBoundary,
214
229
  resolveImageUrl,
@@ -222,6 +237,7 @@ declare function MeowdownEditor({
222
237
  placeholder,
223
238
  readOnly,
224
239
  spellCheck,
240
+ timeFormat,
225
241
  editorClassName,
226
242
  wrapperClassName,
227
243
  handleRef,
@@ -269,4 +285,4 @@ declare function MarkdownView({
269
285
  className
270
286
  }: MarkdownViewProps): ReactElement;
271
287
  //#endregion
272
- export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, MarkdownView, type MarkdownViewProps, MeowdownEditor, type SelectionHint, type SelectionJSON, type TagItem, type TagSearchHandler, type WikilinkItem, type WikilinkSearchHandler, useEditor, useExtension, useKeymap };
288
+ 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 };
package/dist/index.js CHANGED
@@ -7,14 +7,15 @@ import { Compartment, EditorSelection, EditorState } from "@codemirror/state";
7
7
  import { EditorView, keymap } from "@codemirror/view";
8
8
  import { clamp } from "@ocavue/utils";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
- import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineMarkMode, defineMarkdownCopy, definePlaceholder, defineReadonly, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, inlineTextToMarkChunks, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
10
+ import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineMarkMode, defineMarkdownCopy, definePlaceholder, defineReadonly, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, getVirtualElementFromRange, inlineTextToMarkChunks, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
11
11
  import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, union } from "@prosekit/core";
12
12
  import { Selection, TextSelection } from "@prosekit/pm/state";
13
13
  import { ProseKit, defineReactNodeView, useEditor, useEditor as useEditor$1, useEditorDerivedValue, useExtension, useExtension as useExtension$1, useKeymap } from "@prosekit/react";
14
14
  import { Combobox } from "@base-ui/react/combobox";
15
- import { CheckIcon, ChevronsUpDownIcon, CopyIcon, GripHorizontalIcon, GripVerticalIcon } from "lucide-react";
15
+ import { CheckIcon, ChevronsUpDownIcon, CopyIcon, GripHorizontalIcon, GripVerticalIcon, PencilIcon, UnlinkIcon } from "lucide-react";
16
16
  import { BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from "@prosekit/react/block-handle";
17
17
  import { DropIndicator } from "@prosekit/react/drop-indicator";
18
+ import { Popover } from "@base-ui/react/popover";
18
19
  import { AutocompleteEmpty, AutocompleteItem, AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from "@prosekit/react/autocomplete";
19
20
  import { MenuItem, MenuPopup, MenuPositioner } from "@prosekit/react/menu";
20
21
  import { TableHandleColumnMenuRoot, TableHandleColumnMenuTrigger, TableHandleColumnPopup, TableHandleColumnPositioner, TableHandleDragPreview, TableHandleDropIndicator, TableHandleRoot, TableHandleRowMenuRoot, TableHandleRowMenuTrigger, TableHandleRowPopup, TableHandleRowPositioner } from "@prosekit/react/table-handle";
@@ -155,8 +156,41 @@ var code_block_view_module_default = {
155
156
  };
156
157
 
157
158
  //#endregion
158
- //#region src/components/code-block-view.tsx
159
+ //#region src/components/copy-button.tsx
159
160
  const COPIED_RESET_MS = 1500;
161
+ /**
162
+ * A copy-to-clipboard button with "copied" feedback. Shared by the code block
163
+ * toolbar and the link popover.
164
+ */
165
+ function CopyButton({ getText, label, onCopy, className, ...rest }) {
166
+ const [copied, setCopied] = useState(false);
167
+ const resetTimerRef = useRef(void 0);
168
+ const copy = async () => {
169
+ try {
170
+ await navigator.clipboard.writeText(getText());
171
+ setCopied(true);
172
+ clearTimeout(resetTimerRef.current);
173
+ resetTimerRef.current = setTimeout(() => setCopied(false), COPIED_RESET_MS);
174
+ onCopy?.();
175
+ } catch (error) {
176
+ console.warn("[meowdown] Failed to copy:", error);
177
+ }
178
+ };
179
+ return /* @__PURE__ */ jsx("button", {
180
+ type: "button",
181
+ className,
182
+ "data-copied": copied ? "" : void 0,
183
+ "aria-label": copied ? "Copied" : label,
184
+ title: copied ? "Copied" : label,
185
+ onMouseDown: (event) => event.preventDefault(),
186
+ onClick: copy,
187
+ ...rest,
188
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
189
+ });
190
+ }
191
+
192
+ //#endregion
193
+ //#region src/components/code-block-view.tsx
160
194
  function CodeBlockView(props) {
161
195
  const language = props.node.attrs.language || "";
162
196
  const selected = useMemo(() => {
@@ -179,18 +213,6 @@ function CodeBlockView(props) {
179
213
  const setLanguage = (item) => {
180
214
  props.setAttrs({ language: item?.value ?? "" });
181
215
  };
182
- const [copied, setCopied] = useState(false);
183
- const resetTimerRef = useRef(void 0);
184
- const copy = async () => {
185
- try {
186
- await navigator.clipboard.writeText(props.node.textContent);
187
- setCopied(true);
188
- clearTimeout(resetTimerRef.current);
189
- resetTimerRef.current = setTimeout(() => setCopied(false), COPIED_RESET_MS);
190
- } catch (error) {
191
- console.warn("[meowdown] Failed to copy code block:", error);
192
- }
193
- };
194
216
  return /* @__PURE__ */ jsxs("div", {
195
217
  className: code_block_view_module_default.Root,
196
218
  children: [/* @__PURE__ */ jsxs("div", {
@@ -252,16 +274,11 @@ function CodeBlockView(props) {
252
274
  ]
253
275
  })
254
276
  }) })]
255
- }), /* @__PURE__ */ jsx("button", {
256
- type: "button",
277
+ }), /* @__PURE__ */ jsx(CopyButton, {
278
+ getText: () => props.node.textContent,
279
+ label: "Copy code",
257
280
  className: code_block_view_module_default.CopyButton,
258
- "data-testid": "code-block-copy",
259
- "data-copied": copied ? "" : void 0,
260
- "aria-label": copied ? "Copied" : "Copy code",
261
- title: copied ? "Copied" : "Copy code",
262
- onMouseDown: (event) => event.preventDefault(),
263
- onClick: copy,
264
- children: copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {})
281
+ "data-testid": "code-block-copy"
265
282
  })]
266
283
  }), /* @__PURE__ */ jsx("pre", {
267
284
  ref: props.contentRef,
@@ -376,6 +393,286 @@ function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick,
376
393
  return null;
377
394
  }
378
395
 
396
+ //#endregion
397
+ //#region src/hooks/use-delayed-flag.ts
398
+ /** Delay before the flag opens, in ms. */
399
+ const OPEN_DELAY = 400;
400
+ /** Grace before the flag closes, in ms. The window lets a pointer travel from
401
+ * the hovered link onto the popover it anchors. */
402
+ const CLOSE_DELAY = 300;
403
+ /**
404
+ * Mirrors `value` into a boolean that flips true `openDelay`ms after `value`
405
+ * becomes true and false `closeDelay`ms after it becomes false, cancelling any
406
+ * pending flip on each change.
407
+ */
408
+ function useDelayedFlag(value, openDelay = OPEN_DELAY, closeDelay = CLOSE_DELAY) {
409
+ const [flag, setFlag] = useState(false);
410
+ const timerRef = useRef(void 0);
411
+ useEffect(() => {
412
+ clearTimeout(timerRef.current);
413
+ timerRef.current = setTimeout(() => setFlag(value), value ? openDelay : closeDelay);
414
+ return () => clearTimeout(timerRef.current);
415
+ }, [
416
+ value,
417
+ openDelay,
418
+ closeDelay
419
+ ]);
420
+ return flag;
421
+ }
422
+
423
+ //#endregion
424
+ //#region src/components/link-menu.module.css
425
+ var link_menu_module_default = {
426
+ "Button": "meow_Button_IjcqKG",
427
+ "Form": "meow_Form_IjcqKG",
428
+ "Input": "meow_Input_IjcqKG",
429
+ "Popup": "meow_Popup_IjcqKG",
430
+ "Positioner": "meow_Positioner_IjcqKG",
431
+ "Row": "meow_Row_IjcqKG",
432
+ "SrOnly": "meow_SrOnly_IjcqKG",
433
+ "Url": "meow_Url_IjcqKG"
434
+ };
435
+
436
+ //#endregion
437
+ //#region src/components/link-menu.tsx
438
+ /** Select the link unit so the text-backed commands target it, and keep the
439
+ * editor focused so its virtual selection stays visible behind the popover. */
440
+ function selectLinkUnit(editor, link) {
441
+ editor.commands.selectText(link.unit.from, link.unit.to);
442
+ editor.focus();
443
+ }
444
+ /** A Base UI popover anchored at `anchor`. Base UI dismisses it on an outside
445
+ * press or Escape, both routed through `onClose`. */
446
+ function LinkPopover({ anchor, onClose, onPopupHover, children }) {
447
+ return /* @__PURE__ */ jsx(Popover.Root, {
448
+ open: true,
449
+ onOpenChange: (open) => {
450
+ if (!open) onClose();
451
+ },
452
+ children: /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
453
+ anchor,
454
+ side: "bottom",
455
+ sideOffset: 8,
456
+ className: link_menu_module_default.Positioner,
457
+ children: /* @__PURE__ */ jsx(Popover.Popup, {
458
+ className: link_menu_module_default.Popup,
459
+ "data-testid": "link-popover",
460
+ initialFocus: false,
461
+ finalFocus: false,
462
+ onMouseEnter: () => onPopupHover?.(true),
463
+ onMouseLeave: () => onPopupHover?.(false),
464
+ children
465
+ })
466
+ }) })
467
+ });
468
+ }
469
+ /** The hover preview: the url plus copy, edit, and remove actions. */
470
+ function LinkInfoContent({ href, onLinkClick, onLinkCopy, onEdit, onRemove }) {
471
+ return /* @__PURE__ */ jsxs("div", {
472
+ className: link_menu_module_default.Row,
473
+ "data-testid": "link-popover-read",
474
+ children: [
475
+ /* @__PURE__ */ jsx("a", {
476
+ className: link_menu_module_default.Url,
477
+ href,
478
+ title: href,
479
+ target: "_blank",
480
+ rel: "noopener noreferrer",
481
+ onClick: (event) => {
482
+ if (!onLinkClick) return;
483
+ event.preventDefault();
484
+ onLinkClick({
485
+ href,
486
+ event: event.nativeEvent
487
+ });
488
+ },
489
+ children: href
490
+ }),
491
+ /* @__PURE__ */ jsx(CopyButton, {
492
+ getText: () => href,
493
+ label: "Copy link",
494
+ className: link_menu_module_default.Button,
495
+ onCopy: () => onLinkCopy?.({ href })
496
+ }),
497
+ /* @__PURE__ */ jsx("button", {
498
+ type: "button",
499
+ className: link_menu_module_default.Button,
500
+ title: "Edit link",
501
+ "aria-label": "Edit link",
502
+ onClick: onEdit,
503
+ children: /* @__PURE__ */ jsx(PencilIcon, {})
504
+ }),
505
+ /* @__PURE__ */ jsx("button", {
506
+ type: "button",
507
+ className: link_menu_module_default.Button,
508
+ title: "Remove link",
509
+ "aria-label": "Remove link",
510
+ onClick: onRemove,
511
+ children: /* @__PURE__ */ jsx(UnlinkIcon, {})
512
+ })
513
+ ]
514
+ });
515
+ }
516
+ /** The url and title form, opened by `Mod-k` or the preview's edit button. */
517
+ function LinkEditContent({ link, onSubmit }) {
518
+ const hrefInputRef = useRef(null);
519
+ const titleInputRef = useRef(null);
520
+ const href = link ? link.href : "";
521
+ const title = link ? link.title : "";
522
+ useEffect(() => {
523
+ hrefInputRef.current?.focus();
524
+ }, []);
525
+ return /* @__PURE__ */ jsxs("form", {
526
+ className: link_menu_module_default.Form,
527
+ "data-testid": "link-popover-edit",
528
+ onSubmit: (event) => {
529
+ event.preventDefault();
530
+ onSubmit(hrefInputRef.current?.value || "", titleInputRef.current?.value || "");
531
+ },
532
+ children: [
533
+ /* @__PURE__ */ jsx("input", {
534
+ ref: hrefInputRef,
535
+ className: link_menu_module_default.Input,
536
+ defaultValue: href,
537
+ placeholder: "Paste link...",
538
+ "data-testid": "link-popover-input"
539
+ }),
540
+ /* @__PURE__ */ jsx("input", {
541
+ ref: titleInputRef,
542
+ className: link_menu_module_default.Input,
543
+ defaultValue: title,
544
+ placeholder: "Title (optional)"
545
+ }),
546
+ /* @__PURE__ */ jsx("button", {
547
+ type: "submit",
548
+ className: link_menu_module_default.SrOnly,
549
+ "data-testid": "link-popover-submit",
550
+ children: "Save"
551
+ })
552
+ ]
553
+ });
554
+ }
555
+ /**
556
+ * Owns both link triggers and shows one popover at a time:
557
+ *
558
+ * - hovering a link opens a read-only preview that follows the pointer;
559
+ * - `Mod-k` (or the preview's edit button) opens an edit form that stays until
560
+ * it is submitted, dismissed with Escape, or pressed outside.
561
+ */
562
+ function LinkMenu({ onLinkClick, onLinkCopy }) {
563
+ const editor = useEditor$1();
564
+ const [hover, setHover] = useState();
565
+ const [onLink, setOnLink] = useState(false);
566
+ const [overPopup, setOverPopup] = useState(false);
567
+ const [edit, setEdit] = useState();
568
+ const hoverOpen = useDelayedFlag(onLink || overPopup);
569
+ useExtension$1(useMemo(() => {
570
+ return defineLinkHoverHandler((hit) => {
571
+ setOnLink(!!hit);
572
+ if (hit) setHover(hit.payload);
573
+ });
574
+ }, []));
575
+ useExtension$1(useMemo(() => {
576
+ return defineLinkEditKeymap((options) => {
577
+ setEdit(options);
578
+ });
579
+ }, []));
580
+ const closeHover = useCallback(() => {
581
+ setOnLink(false);
582
+ setOverPopup(false);
583
+ setHover(void 0);
584
+ }, []);
585
+ const closeEdit = useCallback(() => {
586
+ setEdit(void 0);
587
+ closeHover();
588
+ editor.focus();
589
+ }, [editor, closeHover]);
590
+ let rangeFrom;
591
+ let rangeTo;
592
+ if (edit) {
593
+ rangeFrom = edit.from;
594
+ rangeTo = edit.to;
595
+ } else if (hover) {
596
+ rangeFrom = hover.unit.from;
597
+ rangeTo = hover.unit.to;
598
+ }
599
+ const anchor = useMemo(() => {
600
+ if (rangeFrom == null || rangeTo == null) return;
601
+ return getVirtualElementFromRange(editor.view, {
602
+ from: rangeFrom,
603
+ to: rangeTo
604
+ });
605
+ }, [
606
+ rangeFrom,
607
+ rangeTo,
608
+ editor
609
+ ]);
610
+ if (edit) return /* @__PURE__ */ jsx(LinkPopover, {
611
+ anchor,
612
+ onClose: closeEdit,
613
+ children: /* @__PURE__ */ jsx(LinkEditContent, {
614
+ link: edit.link,
615
+ onSubmit: (href, title) => {
616
+ if (edit.link) if (href.trim()) editor.commands.updateLink({
617
+ href,
618
+ title
619
+ });
620
+ else editor.commands.removeLink();
621
+ else if (href.trim()) editor.commands.insertLink({
622
+ href,
623
+ title
624
+ });
625
+ closeEdit();
626
+ }
627
+ })
628
+ });
629
+ if (hoverOpen && hover) {
630
+ const link = hover;
631
+ return /* @__PURE__ */ jsx(LinkPopover, {
632
+ anchor,
633
+ onClose: closeHover,
634
+ onPopupHover: setOverPopup,
635
+ children: /* @__PURE__ */ jsx(LinkInfoContent, {
636
+ href: link.href,
637
+ onLinkClick,
638
+ onLinkCopy,
639
+ onEdit: () => {
640
+ selectLinkUnit(editor, link);
641
+ setEdit({
642
+ from: link.unit.from,
643
+ to: link.unit.to,
644
+ link
645
+ });
646
+ closeHover();
647
+ },
648
+ onRemove: () => {
649
+ selectLinkUnit(editor, link);
650
+ editor.commands.removeLink();
651
+ closeHover();
652
+ }
653
+ })
654
+ });
655
+ }
656
+ return null;
657
+ }
658
+
659
+ //#endregion
660
+ //#region src/utils/date-format.ts
661
+ /** Formats the current wall-clock time for the `/now` slash command. */
662
+ function formatNowTime(timeFormat) {
663
+ return formatTime(/* @__PURE__ */ new Date(), timeFormat);
664
+ }
665
+ /** Formats a given time as `3:45pm` ('12') or `15:45` ('24'). */
666
+ function formatTime(date, timeFormat) {
667
+ return timeFormat === "12" ? formatTime12(date) : formatTime24(date);
668
+ }
669
+ function formatTime12(date) {
670
+ return `${date.getHours() % 12 || 12}:${date.getMinutes().toString().padStart(2, "0")}${date.getHours() >= 12 ? "pm" : "am"}`;
671
+ }
672
+ function formatTime24(date) {
673
+ return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
674
+ }
675
+
379
676
  //#endregion
380
677
  //#region src/components/autocomplete-menu.module.css
381
678
  var autocomplete_menu_module_default = {
@@ -396,7 +693,7 @@ function SlashMenuItem({ label, kbd, onSelect }) {
396
693
  children: [/* @__PURE__ */ jsx("span", { children: label }), kbd && /* @__PURE__ */ jsx("kbd", { children: kbd })]
397
694
  });
398
695
  }
399
- function SlashMenu() {
696
+ function SlashMenu({ timeFormat = "12" }) {
400
697
  const editor = useEditor$1();
401
698
  return /* @__PURE__ */ jsx(AutocompleteRoot, {
402
699
  regex: regex$2,
@@ -464,6 +761,10 @@ function SlashMenu() {
464
761
  header: true
465
762
  })
466
763
  }),
764
+ /* @__PURE__ */ jsx(SlashMenuItem, {
765
+ label: "Now",
766
+ onSelect: () => editor.commands.insertText({ text: formatNowTime(timeFormat) })
767
+ }),
467
768
  /* @__PURE__ */ jsx(AutocompleteEmpty, {
468
769
  className: autocomplete_menu_module_default.Item,
469
770
  children: "No results"
@@ -780,7 +1081,7 @@ function resolveSelection(doc, selection) {
780
1081
  return TextSelection.between(doc.resolve(anchor), doc.resolve(head));
781
1082
  }
782
1083
  }
783
- function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste, bulletAfterHeading, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, editorClassName, ref, children }) {
1084
+ 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 }) {
784
1085
  const [editor] = useState(() => {
785
1086
  const editor = createEditor({ extension: union(defineEditorExtension(), defineCodeBlockView()) });
786
1087
  if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
@@ -877,7 +1178,11 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
877
1178
  blockHandle && !readOnly && /* @__PURE__ */ jsx(BlockHandle, {}),
878
1179
  !readOnly && /* @__PURE__ */ jsx(TableHandle, {}),
879
1180
  blockHandle && !readOnly && /* @__PURE__ */ jsx(DropIndicator$1, {}),
880
- /* @__PURE__ */ jsx(SlashMenu, {}),
1181
+ /* @__PURE__ */ jsx(SlashMenu, { timeFormat }),
1182
+ !readOnly && /* @__PURE__ */ jsx(LinkMenu, {
1183
+ onLinkClick,
1184
+ onLinkCopy
1185
+ }),
881
1186
  onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
882
1187
  onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
883
1188
  children
@@ -887,7 +1192,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onTa
887
1192
 
888
1193
  //#endregion
889
1194
  //#region src/components/editor.tsx
890
- function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSearch, onWikilinkSearch, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, onImagePaste, onImageSaveError, onImageClick, embedPaste = true, bulletAfterHeading = false, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, editorClassName, wrapperClassName, handleRef, children }) {
1195
+ 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 }) {
891
1196
  const childRef = useRef(null);
892
1197
  useImperativeHandle(handleRef, () => {
893
1198
  function getMarkdown() {
@@ -953,6 +1258,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSea
953
1258
  onWikilinkSearch,
954
1259
  onWikilinkClick,
955
1260
  onLinkClick,
1261
+ onLinkCopy,
956
1262
  onTagClick,
957
1263
  onExitBoundary,
958
1264
  resolveImageUrl,
@@ -966,6 +1272,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onTagSea
966
1272
  placeholder,
967
1273
  readOnly,
968
1274
  spellCheck,
1275
+ timeFormat,
969
1276
  editorClassName,
970
1277
  children
971
1278
  })
package/dist/style.css CHANGED
@@ -255,6 +255,95 @@
255
255
  background: var(--meowdown-accent);
256
256
  transition: all .15s;
257
257
  }
258
+ .meow_Positioner_IjcqKG {
259
+ z-index: 50;
260
+ width: min(20rem, 100vw - 1rem);
261
+ display: block;
262
+ }
263
+
264
+ .meow_Popup_IjcqKG {
265
+ box-sizing: border-box;
266
+ border: 1px solid var(--meowdown-border);
267
+ background: var(--meowdown-link-popover-bg, light-dark(#fff, #18181b));
268
+ border-radius: .75rem;
269
+ flex-direction: column;
270
+ width: 100%;
271
+ padding: .25rem;
272
+ font-size: .875rem;
273
+ display: flex;
274
+ box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
275
+ }
276
+
277
+ .meow_Row_IjcqKG {
278
+ align-items: center;
279
+ gap: .125rem;
280
+ display: flex;
281
+ }
282
+
283
+ .meow_Url_IjcqKG {
284
+ white-space: nowrap;
285
+ text-overflow: ellipsis;
286
+ min-width: 0;
287
+ color: var(--meowdown-accent);
288
+ cursor: pointer;
289
+ flex: 1;
290
+ padding: .25rem .5rem;
291
+ overflow: hidden;
292
+ }
293
+
294
+ .meow_Button_IjcqKG {
295
+ color: var(--meowdown-text);
296
+ cursor: pointer;
297
+ border-radius: .5rem;
298
+ flex: none;
299
+ justify-content: center;
300
+ align-items: center;
301
+ padding: .375rem;
302
+ display: inline-flex;
303
+
304
+ &:hover {
305
+ background: var(--meowdown-link-popover-hover-bg, light-dark(#f4f4f5, #27272a));
306
+ }
307
+
308
+ & svg {
309
+ width: 1rem;
310
+ height: 1rem;
311
+ }
312
+ }
313
+
314
+ .meow_Form_IjcqKG {
315
+ flex-direction: column;
316
+ gap: .25rem;
317
+ padding: .25rem;
318
+ display: flex;
319
+ }
320
+
321
+ .meow_SrOnly_IjcqKG {
322
+ clip: rect(0, 0, 0, 0);
323
+ border: 0;
324
+ width: 1px;
325
+ height: 1px;
326
+ margin: -1px;
327
+ padding: 0;
328
+ position: absolute;
329
+ overflow: hidden;
330
+ }
331
+
332
+ .meow_Input_IjcqKG {
333
+ box-sizing: border-box;
334
+ width: 100%;
335
+ font: inherit;
336
+ color: var(--meowdown-text);
337
+ border: 1px solid var(--meowdown-border);
338
+ background: none;
339
+ border-radius: .5rem;
340
+ padding: .375rem .5rem;
341
+
342
+ &:focus {
343
+ outline: 2px solid var(--meowdown-accent);
344
+ outline-offset: -1px;
345
+ }
346
+ }
258
347
  .meow_Positioner_Dqll0G {
259
348
  z-index: 50;
260
349
  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.24.1",
4
+ "version": "0.26.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  "@prosekit/react": "^0.8.0-beta.11",
31
31
  "clsx": "^2.1.1",
32
32
  "lucide-react": "^1.21.0",
33
- "@meowdown/core": "0.24.1"
33
+ "@meowdown/core": "0.26.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": "^19.0.0",