@meowdown/react 0.74.0 → 0.75.1

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 { ComponentProps, ReactElement, ReactNode, Ref } from "react";
2
- import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileInfoResolver, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, LinkPreview, LinkPreviewResolver, LinkPreviewResolver as LinkPreviewResolver$1, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, SearchStatusHandler, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikiEmbedResolver, WikilinkClickHandler, WikilinkHoverHit, WikilinkResolver, XPostMediaClickHandler, XPostResolver, XPostResolver as XPostResolver$1, YouTubeVideoClickHandler, YouTubeVideoResolver, YouTubeVideoResolver as YouTubeVideoResolver$1 } from "@meowdown/core";
2
+ import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileInfoResolver, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, ImageUrlResolver, LinkClickHandler, LinkCopyHandler, LinkPreview, LinkPreviewResolver, LinkPreviewResolver as LinkPreviewResolver$1, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, SearchStatusHandler, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikiEmbedResolver, WikilinkClickHandler, WikilinkHoverHit, WikilinkResolver, XPostMediaClickHandler, XPostResolver, XPostResolver as XPostResolver$1, YouTubeVideoClickHandler, YouTubeVideoResolver, YouTubeVideoResolver as YouTubeVideoResolver$1 } from "@meowdown/core";
3
3
  import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
4
4
  import { ReactNodeViewComponent, ReactNodeViewComponent as ReactNodeViewComponent$1, useEditor, useExtension, useKeymap } from "@prosekit/react";
5
5
  //#region src/utils/date-format.d.ts
@@ -372,8 +372,10 @@ interface EditorProps {
372
372
  onExitBoundary?: ExitBoundaryHandler;
373
373
  /**
374
374
  * Maps an image `src` to a displayable URL, or `undefined` to skip that image.
375
- * Defaults to showing http(s) URLs as-is. Pass a stable function (e.g. from
376
- * `useCallback`).
375
+ * May return a `Promise` when the answer depends on data that has not loaded
376
+ * yet; the image appears once it resolves, in a box of its persisted size
377
+ * when one exists. Defaults to showing http(s) URLs as-is. Pass a stable
378
+ * function (e.g. from `useCallback`).
377
379
  */
378
380
  resolveImageUrl?: ImageOptions['resolveImageUrl'];
379
381
  /**
@@ -631,9 +633,11 @@ interface MarkdownViewProps {
631
633
  */
632
634
  expandCollapsed?: boolean;
633
635
  /**
634
- * Map an image `src` to a displayable URL, or `undefined` to skip it.
636
+ * Map an image `src` to a displayable URL, or `undefined` to skip it. May
637
+ * return a `Promise`; the image appears once it resolves, in a box of its
638
+ * persisted size when both dimensions are known.
635
639
  */
636
- resolveImageUrl?: (src: string) => string | undefined;
640
+ resolveImageUrl?: ImageUrlResolver;
637
641
  /**
638
642
  * Claim a `[label](url)` link as a file pill instead of a regular link.
639
643
  * Must be pure; return `false` for links that should render normally.
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { BlockHandleDraggable, BlockHandlePopup, BlockHandlePositioner, BlockHandleRoot } from "@prosekit/react/block-handle";
13
13
  import { DropIndicator } from "@prosekit/react/drop-indicator";
14
14
  import { Popover } from "@base-ui/react/popover";
15
+ import { queueExtension } from "@prosekit/web";
15
16
  import { AutocompleteEmpty, AutocompleteItem, AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from "@prosekit/react/autocomplete";
16
17
  import { MenuItem, MenuPopup, MenuPositioner } from "@prosekit/react/menu";
17
18
  import { TableHandleColumnMenuRoot, TableHandleColumnMenuTrigger, TableHandleColumnPopup, TableHandleColumnPositioner, TableHandleDragPreview, TableHandleDropIndicator, TableHandleRoot, TableHandleRowMenuRoot, TableHandleRowMenuTrigger, TableHandleRowPopup, TableHandleRowPositioner } from "@prosekit/react/table-handle";
@@ -781,7 +782,7 @@ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = fals
781
782
  type: "hover",
782
783
  link: hit?.payload
783
784
  }), { canLeave });
784
- return editor.use(linkHoverExtension);
785
+ return queueExtension(editor, linkHoverExtension);
785
786
  }, [editor]);
786
787
  useEffect(() => {
787
788
  if (readOnly) return;
@@ -789,7 +790,7 @@ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = fals
789
790
  type: "edit",
790
791
  edit
791
792
  }));
792
- return editor.use(linkEditExtension);
793
+ return queueExtension(editor, linkEditExtension);
793
794
  }, [editor, readOnly]);
794
795
  const handlePointerHover = useCallback((over) => {
795
796
  isPointerOverPopupRef.current = over;
@@ -2297,6 +2298,37 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
2297
2298
  });
2298
2299
  }
2299
2300
 
2301
+ //#endregion
2302
+ //#region src/hooks/use-maybe-promise.ts
2303
+ /**
2304
+ * The settled value of `input`: `input` itself when it is not a `Promise`,
2305
+ * otherwise `undefined` until it resolves. A rejection stays `undefined` and is
2306
+ * reported with `console.warn`, since the caller has no other way to see it. A
2307
+ * new `input` discards the previous `Promise`'s result at once, so a stale
2308
+ * value never shows for a newer input. Callers memoize `input`, or a fresh
2309
+ * `Promise` on every render never settles.
2310
+ */
2311
+ function useMaybePromise(input) {
2312
+ const [settled, setSettled] = useState(null);
2313
+ useEffect(() => {
2314
+ if (!(input instanceof Promise)) return;
2315
+ let active = true;
2316
+ input.then((value) => {
2317
+ if (active) setSettled({
2318
+ input,
2319
+ value
2320
+ });
2321
+ }, (error) => {
2322
+ if (active) console.warn("[meowdown] useMaybePromise input rejected:", error);
2323
+ });
2324
+ return () => {
2325
+ active = false;
2326
+ };
2327
+ }, [input]);
2328
+ if (!(input instanceof Promise)) return input;
2329
+ return settled?.input === input ? settled.value : void 0;
2330
+ }
2331
+
2300
2332
  //#endregion
2301
2333
  //#region src/components/attributes-to-props.ts
2302
2334
  const UNCONTROLLED_COMPONENT_ATTRIBUTES = ["checked", "value"];
@@ -2453,8 +2485,14 @@ function PostEmbed(props) {
2453
2485
  });
2454
2486
  }
2455
2487
  function ImagePreview(props) {
2456
- const { src, alt, width, snapshot, resolveImageUrl, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onImageClick, interactive } = props;
2488
+ const { src, alt, width, height, snapshot, resolveImageUrl, resolveXPost, mediaUrlProtocols, resolveYouTubeVideo, onImageClick, interactive } = props;
2457
2489
  const kind = matchEmbed(src);
2490
+ const resolved = useMemo(() => kind ? void 0 : (resolveImageUrl ?? defaultResolveImageUrl)(src), [
2491
+ kind,
2492
+ resolveImageUrl,
2493
+ src
2494
+ ]);
2495
+ const url = useMaybePromise(resolved);
2458
2496
  if (kind) {
2459
2497
  if (!interactive) return null;
2460
2498
  return /* @__PURE__ */ jsx(PostEmbed, {
@@ -2467,8 +2505,23 @@ function ImagePreview(props) {
2467
2505
  resolveYouTubeVideo: resolveYouTubeVideo ?? defaultResolveYouTubeVideo
2468
2506
  }, src);
2469
2507
  }
2470
- const url = (resolveImageUrl ?? defaultResolveImageUrl)(src);
2471
- if (!url) return null;
2508
+ if (!url) {
2509
+ if (resolved instanceof Promise && width != null && height != null) return /* @__PURE__ */ jsx("span", {
2510
+ className: "md-image-view-preview md-atom-view-preview",
2511
+ "data-testid": "image-preview",
2512
+ contentEditable: false,
2513
+ children: /* @__PURE__ */ jsx("span", {
2514
+ className: "md-image-resizable",
2515
+ "data-testid": "image-resizable",
2516
+ "data-loading": "",
2517
+ style: {
2518
+ width: `${width}px`,
2519
+ height: `${height}px`
2520
+ }
2521
+ })
2522
+ });
2523
+ return null;
2524
+ }
2472
2525
  return /* @__PURE__ */ jsx("span", {
2473
2526
  className: "md-image-view-preview md-atom-view-preview",
2474
2527
  "data-testid": "image-preview",
@@ -2491,13 +2544,14 @@ function ImagePreview(props) {
2491
2544
  });
2492
2545
  }
2493
2546
  function ImageView(props) {
2494
- const { src, alt, width, snapshot, context, children } = props;
2547
+ const { src, alt, width, height, snapshot, context, children } = props;
2495
2548
  return /* @__PURE__ */ jsxs("span", {
2496
2549
  className: "md-image-view md-atom-view",
2497
2550
  children: [/* @__PURE__ */ jsx(ImagePreview, {
2498
2551
  src,
2499
2552
  alt,
2500
2553
  width,
2554
+ height,
2501
2555
  snapshot,
2502
2556
  resolveImageUrl: context.resolveImageUrl,
2503
2557
  resolveXPost: context.resolveXPost,
@@ -2599,27 +2653,8 @@ function renderTokens(code, tokens) {
2599
2653
  return out;
2600
2654
  }
2601
2655
  function CodeBlock({ code, language }) {
2602
- const syncTokens = useMemo(() => {
2603
- const result = getCodeTokens(code, language);
2604
- return Array.isArray(result) ? result : null;
2605
- }, [code, language]);
2606
- const [asyncTokens, setAsyncTokens] = useState(null);
2607
- useEffect(() => {
2608
- if (syncTokens) return;
2609
- let active = true;
2610
- const result = getCodeTokens(code, language);
2611
- if (!Array.isArray(result)) result.then((loaded) => {
2612
- if (active) setAsyncTokens(loaded);
2613
- });
2614
- return () => {
2615
- active = false;
2616
- };
2617
- }, [
2618
- code,
2619
- language,
2620
- syncTokens
2621
- ]);
2622
- const tokens = syncTokens ?? asyncTokens ?? [];
2656
+ const result = useMemo(() => getCodeTokens(code, language), [code, language]);
2657
+ const tokens = useMaybePromise(result) ?? [];
2623
2658
  return /* @__PURE__ */ jsx("pre", {
2624
2659
  "data-language": language || void 0,
2625
2660
  children: /* @__PURE__ */ jsx("code", { children: tokens.length > 0 ? renderTokens(code, tokens) : code })
@@ -2698,6 +2733,7 @@ function wrapMark(mark, children, context) {
2698
2733
  src: attrs.src,
2699
2734
  alt: attrs.alt,
2700
2735
  width: attrs.width,
2736
+ height: attrs.height,
2701
2737
  snapshot: attrs.snapshot,
2702
2738
  context,
2703
2739
  children
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/react",
3
3
  "type": "module",
4
- "version": "0.74.0",
4
+ "version": "0.75.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -22,14 +22,15 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "@base-ui/react": "^1.8.0",
25
- "@meowdown/core": "0.74.2",
26
- "@meowdown/embed": "0.2.0",
25
+ "@meowdown/core": "0.75.1",
26
+ "@meowdown/embed": "0.3.0",
27
27
  "@meowdown/markdown": "0.72.0",
28
28
  "@ocavue/utils": "^1.8.0",
29
29
  "@post-embed/types": "^0.5.0",
30
30
  "@prosekit/core": "^0.13.3",
31
31
  "@prosekit/pm": "^0.1.20",
32
32
  "@prosekit/react": "^0.8.3",
33
+ "@prosekit/web": "^0.9.3",
33
34
  "beautiful-mermaid": "^1.1.3",
34
35
  "clsx": "^2.1.1",
35
36
  "github-slugger": "^2.0.0",