@meowdown/react 0.73.0 → 0.74.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
@@ -122,7 +122,7 @@ interface EditorHandle {
122
122
  * Escape hatch: the underlying ProseKit editor, or `undefined` when the
123
123
  * handle does not wrap one.
124
124
  */
125
- readonly editor: TypedEditor | undefined;
125
+ getEditor: () => TypedEditor | undefined;
126
126
  }
127
127
  /**
128
128
  * One row of host items in the slash menu. The host ranks the rows; the menu does not re-sort.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { clsx } from "clsx/lite";
2
- import { Fragment, ViewTransition, cloneElement, createElement, memo, startTransition, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
3
- import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineCodeBlockPreviewPlugin, defineEditorExtension, defineLinkEditKeymap, defineLinkHoverHandler, definePendingReplacementHandler, defineSearchStatusHandler, defineVirtualCaret, defineWikilinkHoverHandler, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, normalizeHref, parsePostEmbedSnapshot, renderMathInto, updateEditorConfig } from "@meowdown/core";
2
+ import { Fragment, ViewTransition, cloneElement, createElement, memo, startTransition, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
3
+ import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineCodeBlockPreviewPlugin, defineEditorExtension, defineLinkEditKeymap, defineLinkHoverHandler, definePendingReplacementHandler, defineSearchStatusHandler, defineVirtualCaret, defineWikilinkHoverHandler, dismissLinkHover, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, normalizeHref, parsePostEmbedSnapshot, renderMathInto, updateEditorConfig } from "@meowdown/core";
4
4
  import { clamp } from "@ocavue/utils";
5
5
  import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, defineUpdateHandler, isTextSelection, union } from "@prosekit/core";
6
6
  import { Selection, TextSelection } from "@prosekit/pm/state";
@@ -422,6 +422,44 @@ function EditorExtensions({ config, searchQuery, onDocChange, onSearchChange })
422
422
  return null;
423
423
  }
424
424
 
425
+ //#endregion
426
+ //#region src/components/link-menu-state.ts
427
+ const LINK_MENU_IDLE = { kind: "idle" };
428
+ /**
429
+ * What the popup renders: the open view, or the closing one during the exit
430
+ * animation.
431
+ */
432
+ function getLinkMenuView(state) {
433
+ switch (state.kind) {
434
+ case "idle": return;
435
+ case "closing": return state.view;
436
+ default: return state;
437
+ }
438
+ }
439
+ function reduceLinkMenu(state, event) {
440
+ switch (event.type) {
441
+ case "hover":
442
+ if (state.kind === "edit") return state;
443
+ if (event.link) return {
444
+ kind: "preview",
445
+ link: event.link
446
+ };
447
+ return state.kind === "preview" ? {
448
+ kind: "closing",
449
+ view: state
450
+ } : state;
451
+ case "edit": return {
452
+ kind: "edit",
453
+ edit: event.edit
454
+ };
455
+ case "close": return state.kind === "preview" || state.kind === "edit" ? {
456
+ kind: "closing",
457
+ view: state
458
+ } : state;
459
+ case "closed": return state.kind === "closing" ? LINK_MENU_IDLE : state;
460
+ }
461
+ }
462
+
425
463
  //#endregion
426
464
  //#region src/components/link-menu.module.css
427
465
  var link_menu_module_default = {
@@ -735,41 +773,39 @@ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
735
773
  }
736
774
  function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = false }) {
737
775
  const editor = useEditor$1();
738
- const [infoOpen, setInfoOpen] = useState(false);
739
- const [editOpen, setEditOpen] = useState(false);
740
- const [link, setLink] = useState();
741
- const [edit, setEdit] = useState();
776
+ const [state, dispatch] = useReducer(reduceLinkMenu, LINK_MENU_IDLE);
742
777
  const isPointerOverPopupRef = useRef(false);
743
- const linkHoverExtension = useMemo(() => {
744
- return defineLinkHoverHandler((nextHit) => {
745
- setInfoOpen(nextHit != null);
746
- if (nextHit) setLink(nextHit.payload);
747
- }, { canLeave: () => !isPointerOverPopupRef.current });
748
- }, []);
749
- useExtension$1(linkHoverExtension);
750
- const linkEditExtension = useMemo(() => {
751
- return readOnly ? null : defineLinkEditKeymap((options) => {
752
- setEdit(options);
753
- setEditOpen(true);
754
- setInfoOpen(false);
755
- });
756
- }, [readOnly]);
757
- useExtension$1(linkEditExtension);
778
+ useEffect(() => {
779
+ const canLeave = () => !isPointerOverPopupRef.current;
780
+ const linkHoverExtension = defineLinkHoverHandler((hit) => dispatch({
781
+ type: "hover",
782
+ link: hit?.payload
783
+ }), { canLeave });
784
+ return editor.use(linkHoverExtension);
785
+ }, [editor]);
786
+ useEffect(() => {
787
+ if (readOnly) return;
788
+ const linkEditExtension = defineLinkEditKeymap((edit) => dispatch({
789
+ type: "edit",
790
+ edit
791
+ }));
792
+ return editor.use(linkEditExtension);
793
+ }, [editor, readOnly]);
758
794
  const handlePointerHover = useCallback((over) => {
759
795
  isPointerOverPopupRef.current = over;
760
796
  }, []);
761
- const closeInfo = useCallback(() => {
797
+ const close = useCallback(() => {
762
798
  isPointerOverPopupRef.current = false;
763
- setInfoOpen(false);
764
- }, []);
765
- const closeEdit = useCallback(() => {
766
- setEditOpen(false);
767
- closeInfo();
768
- }, [closeInfo]);
799
+ dismissLinkHover(editor.state);
800
+ dispatch({ type: "close" });
801
+ }, [editor]);
802
+ const view = getLinkMenuView(state);
803
+ const edit = view?.kind === "edit" ? view.edit : void 0;
804
+ const link = view?.kind === "preview" ? view.link : void 0;
769
805
  const handleEditRemove = useCallback(() => {
770
806
  editor.commands.removeLink();
771
- closeEdit();
772
- }, [editor, closeEdit]);
807
+ close();
808
+ }, [editor, close]);
773
809
  const handleEditSubmit = useCallback((text, href) => {
774
810
  if (!edit) return;
775
811
  if (edit.link) editor.commands.updateLink({
@@ -781,52 +817,52 @@ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = fals
781
817
  text,
782
818
  href
783
819
  });
784
- closeEdit();
820
+ close();
785
821
  }, [
786
822
  edit,
787
823
  editor,
788
- closeEdit
824
+ close
789
825
  ]);
790
826
  const mutable = link != null && !readOnly && link.form !== "reference";
791
827
  const linkText = useMemo(() => link ? getLinkText(link) : "", [link]);
792
828
  const editLink = useCallback(() => {
793
829
  if (!link) return;
794
830
  selectLinkUnit(editor, link);
795
- setEdit({
796
- from: link.unit.from,
797
- to: link.unit.to,
798
- link,
799
- text: linkText
831
+ dispatch({
832
+ type: "edit",
833
+ edit: {
834
+ from: link.unit.from,
835
+ to: link.unit.to,
836
+ link,
837
+ text: linkText
838
+ }
800
839
  });
801
- setEditOpen(true);
802
- closeInfo();
803
840
  }, [
804
841
  link,
805
842
  editor,
806
- linkText,
807
- closeInfo
843
+ linkText
808
844
  ]);
809
845
  const removeLink = useCallback(() => {
810
846
  if (!link) return;
811
847
  selectLinkUnit(editor, link);
812
848
  editor.commands.removeLink();
813
- closeInfo();
849
+ close();
814
850
  }, [
815
851
  link,
816
852
  editor,
817
- closeInfo
853
+ close
818
854
  ]);
819
855
  const handleUseTitle = useMemo(() => {
820
856
  if (!link || !mutable || !isLinkTextForHref(linkText, link.href)) return;
821
857
  return (title) => {
822
858
  selectLinkUnit(editor, link);
823
859
  editor.commands.updateLink({ text: title });
824
- closeInfo();
860
+ close();
825
861
  };
826
862
  }, [
827
863
  link,
828
864
  editor,
829
- closeInfo,
865
+ close,
830
866
  linkText,
831
867
  mutable
832
868
  ]);
@@ -836,24 +872,21 @@ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = fals
836
872
  if (!range) return;
837
873
  return getVirtualElementFromRange(editor.view, range);
838
874
  }, [range, editor]);
839
- const editing = edit != null && !readOnly;
840
875
  return /* @__PURE__ */ jsx(LinkPopover, {
841
876
  anchor,
842
- open: editing ? editOpen : infoOpen,
843
- onClose: editing ? closeEdit : closeInfo,
877
+ open: state.kind === "preview" || state.kind === "edit",
878
+ onClose: close,
844
879
  onCloseComplete: () => {
845
- if (editing) {
846
- setEdit(void 0);
847
- editor.focus();
848
- } else if (!infoOpen) setLink(void 0);
880
+ if (edit) editor.focus();
881
+ dispatch({ type: "closed" });
849
882
  },
850
883
  onPopupHover: handlePointerHover,
851
- children: editing ? /* @__PURE__ */ jsx(LinkEditContent, {
884
+ children: edit ? /* @__PURE__ */ jsx(LinkEditContent, {
852
885
  edit,
853
886
  resolveLinkPreview,
854
887
  onRemove: edit.link ? handleEditRemove : void 0,
855
888
  onSubmit: handleEditSubmit
856
- }) : link ? /* @__PURE__ */ jsx(LinkInfoContent, {
889
+ }, `${edit.from}:${edit.to}`) : link ? /* @__PURE__ */ jsx(LinkInfoContent, {
857
890
  href: link.href,
858
891
  previewState,
859
892
  onLinkClick,
@@ -1706,6 +1739,7 @@ function TagMenu({ onTagSearch }) {
1706
1739
  className: autocomplete_menu_module_default.Popup,
1707
1740
  "data-testid": "tag-menu",
1708
1741
  children: [items.map((item) => /* @__PURE__ */ jsxs(AutocompleteItem, {
1742
+ value: item.tag,
1709
1743
  className: autocomplete_menu_module_default.Item,
1710
1744
  onSelect: () => {
1711
1745
  editor.commands.insertText({ text: `#${item.tag} ` });
@@ -1782,6 +1816,7 @@ function WikilinkMenu({ onWikilinkSearch }) {
1782
1816
  className: autocomplete_menu_module_default.Popup,
1783
1817
  "data-testid": "wikilink-menu",
1784
1818
  children: [items.map((item) => /* @__PURE__ */ jsxs(AutocompleteItem, {
1819
+ value: item.target,
1785
1820
  className: autocomplete_menu_module_default.Item,
1786
1821
  onSelect: () => {
1787
1822
  editor.commands.insertText({ text: `[[${item.target}]]` });
@@ -1970,8 +2005,10 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1970
2005
  suppressDocChangeRef.current = true;
1971
2006
  try {
1972
2007
  editor.view.dispatch(transaction);
1973
- } finally {
1974
2008
  suppressDocChangeRef.current = false;
2009
+ } catch (error) {
2010
+ suppressDocChangeRef.current = false;
2011
+ throw error;
1975
2012
  }
1976
2013
  }
1977
2014
  function setState(markdown, selection) {
@@ -2048,7 +2085,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
2048
2085
  discardPendingReplacement,
2049
2086
  findNext,
2050
2087
  findPrevious,
2051
- editor
2088
+ getEditor: () => editor
2052
2089
  };
2053
2090
  }, [
2054
2091
  editor,
@@ -2056,11 +2093,14 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
2056
2093
  hasSelectionMenu,
2057
2094
  openSelectionMenu
2058
2095
  ]);
2096
+ const mount = useCallback((element) => {
2097
+ return editor.mount(element);
2098
+ }, [editor]);
2059
2099
  return /* @__PURE__ */ jsxs(ProseKit, {
2060
2100
  editor,
2061
2101
  children: [
2062
2102
  /* @__PURE__ */ jsx(VirtualCaret, {}),
2063
- /* @__PURE__ */ jsx("div", { ref: editor.mount }),
2103
+ /* @__PURE__ */ jsx("div", { ref: mount }),
2064
2104
  /* @__PURE__ */ jsx(EditorExtensions, {
2065
2105
  config,
2066
2106
  searchQuery,
@@ -2081,7 +2121,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
2081
2121
  onLinkCopy,
2082
2122
  resolveLinkPreview,
2083
2123
  readOnly
2084
- }),
2124
+ }, String(readOnly)),
2085
2125
  onTagSearch && /* @__PURE__ */ jsx(TagMenu, { onTagSearch }),
2086
2126
  onWikilinkSearch && /* @__PURE__ */ jsx(WikilinkMenu, { onWikilinkSearch }),
2087
2127
  onSelectionMenuSearch && !readOnly && /* @__PURE__ */ jsx(SelectionMenu, {
@@ -2177,6 +2217,9 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2177
2217
  function findPrevious() {
2178
2218
  childRef.current?.findPrevious();
2179
2219
  }
2220
+ function getEditor() {
2221
+ return childRef.current?.getEditor();
2222
+ }
2180
2223
  return {
2181
2224
  getMarkdown,
2182
2225
  setMarkdown,
@@ -2197,9 +2240,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2197
2240
  discardPendingReplacement,
2198
2241
  findNext,
2199
2242
  findPrevious,
2200
- get editor() {
2201
- return childRef.current?.editor;
2202
- }
2243
+ getEditor
2203
2244
  };
2204
2245
  }, []);
2205
2246
  return /* @__PURE__ */ jsx("div", {
package/dist/style.css CHANGED
@@ -319,7 +319,9 @@
319
319
  }
320
320
 
321
321
  @media (prefers-reduced-motion: reduce) {
322
- transition: none;
322
+ &, &[data-closed] {
323
+ transition: none;
324
+ }
323
325
  }
324
326
  }
325
327
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/react",
3
3
  "type": "module",
4
- "version": "0.73.0",
4
+ "version": "0.74.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@base-ui/react": "^1.8.0",
25
- "@meowdown/core": "0.74.0",
25
+ "@meowdown/core": "0.74.2",
26
26
  "@meowdown/embed": "0.2.0",
27
27
  "@meowdown/markdown": "0.72.0",
28
28
  "@ocavue/utils": "^1.8.0",