@meowdown/core 0.71.2 → 0.72.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +13 -16
  2. package/dist/index.js +162 -112
  3. package/package.json +9 -10
package/dist/index.d.ts CHANGED
@@ -6,7 +6,6 @@ import { SearchStatus, SearchStatusHandler, defineSearchStatusHandler, getSearch
6
6
  import { EditorView } from "@prosekit/pm/view";
7
7
  import { EditorNode, Mark, ProseMirrorNode } from "@prosekit/pm/model";
8
8
  import { ListAttrs } from "@prosekit/extensions/list";
9
- import { parseXPostId } from "@post-embed/schema";
10
9
  import { HorizontalRuleExtension } from "@prosekit/extensions/horizontal-rule";
11
10
  import { Resolver } from "@post-embed/elements/x";
12
11
  import { render } from "katex";
@@ -781,11 +780,6 @@ export declare function defineFollowLinkHandler(getHandlers?: (state: EditorStat
781
780
  //#region src/extensions/post-embed.d.ts
782
781
  type XPostResolver = Resolver<XPost>;
783
782
  type YouTubeVideoResolver = Resolver<YouTubeVideo>;
784
- type PostEmbedKind = 'x-post' | 'youtube-video';
785
- /**
786
- * The post-embed card an image `src` renders as, or `undefined` for a plain image.
787
- */
788
- export declare function matchPostEmbed(src: string): PostEmbedKind | undefined;
789
783
  export declare const defaultResolveXPost: XPostResolver;
790
784
  export declare const defaultResolveYouTubeVideo: YouTubeVideoResolver;
791
785
  /**
@@ -1168,7 +1162,7 @@ declare function defineEditorExtensionImpl(options: EditorExtensionOptions): imp
1168
1162
  fenceLength?: number | null;
1169
1163
  };
1170
1164
  };
1171
- }>, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension]>, MeowdownHorizontalRuleExtension, import("@prosekit/core").Extension<{
1165
+ }>, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension]>, MeowdownHorizontalRuleExtension, import("@prosekit/core").Extension<{
1172
1166
  Nodes: {
1173
1167
  htmlComment: MeowdownHTMLCommentAttrs;
1174
1168
  };
@@ -1232,7 +1226,7 @@ declare function defineEditorExtensionImpl(options: EditorExtensionOptions): imp
1232
1226
  Marks: {
1233
1227
  mdPack: MdPackAttrs;
1234
1228
  };
1235
- }>]>, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").Extension<import("@prosekit/core").ExtensionTyping<any, any, any>>, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").Union<readonly [import("@prosekit/core").Extension<{
1229
+ }>]>, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").Extension<import("@prosekit/core").ExtensionTyping<any, any, any>>, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").PlainExtension, import("@prosekit/core").Union<readonly [import("@prosekit/core").Extension<{
1236
1230
  Commands: {
1237
1231
  insertSoftBreak: [];
1238
1232
  };
@@ -1415,7 +1409,7 @@ export declare const EDITOR_KEY_BINDINGS: {
1415
1409
  readonly 'Mod-5': "Heading 5";
1416
1410
  readonly 'Mod-6': "Heading 6";
1417
1411
  readonly 'Mod-.': "Fold or unfold a bullet";
1418
- readonly 'Shift-Enter': "Insert a line break";
1412
+ readonly 'Shift-Enter': "Insert a line break, or leave a code block from its end";
1419
1413
  readonly 'Mod-Enter': "Follow the link under the caret, or cycle a checkbox task";
1420
1414
  readonly 'Mod-Shift-Enter': "Cycle a circle checkbox task";
1421
1415
  readonly 'Mod-Shift-7': "Ordered list";
@@ -1447,7 +1441,7 @@ interface LinkHoverOptions {
1447
1441
  }
1448
1442
  /**
1449
1443
  * Track the link under the user's attention: a mouse hover after a short
1450
- * dwell, or a touch tap immediately, since touch has no hover. Without the
1444
+ * delay, or a touch tap immediately, since touch has no hover. Without the
1451
1445
  * tap entry, the popup's preview and actions would stay unreachable on
1452
1446
  * phones, where a tap on a link only places the caret and raises the
1453
1447
  * software keyboard.
@@ -1564,13 +1558,16 @@ interface WikilinkHoverHit extends WikilinkHit {
1564
1558
  */
1565
1559
  type WikilinkHoverHandler = (hit: WikilinkHoverHit | undefined) => void;
1566
1560
  /**
1567
- * Track the wikilink under the pointer without attaching per-link listeners.
1561
+ * Track the wikilink the pointer rests on without attaching per-link
1562
+ * listeners.
1568
1563
  *
1569
- * The handler is revalidated after document transactions and receives leave
1570
- * when the hovered link is deleted, replaced, or changes target. Moving among
1571
- * descendants of one label is de-duplicated.
1564
+ * A cold pointer must rest on a link for `openDelay` ms before enter fires.
1565
+ * Moving to an adjacent link restarts the delay, or switches at once when a
1566
+ * link is already entered. Leave fires `closeDelay` ms after the pointer
1567
+ * leaves, and immediately when the hovered link is deleted, replaced, or
1568
+ * changes target. Moving among descendants of one label is de-duplicated.
1572
1569
  */
1573
- export declare function defineWikilinkHoverHandler(onHoverChange: WikilinkHoverHandler): PlainExtension;
1570
+ export declare function defineWikilinkHoverHandler(onHoverChange: WikilinkHoverHandler, openDelay?: number, closeDelay?: number): PlainExtension;
1574
1571
  //#endregion
1575
1572
  //#region src/extensions/wikilink-trigger.d.ts
1576
1573
  /**
@@ -1642,4 +1639,4 @@ export declare function getSelectedText(state: EditorState): string;
1642
1639
  */
1643
1640
  export declare function getVirtualElementFromRange(view: EditorView, range: PositionRange): VirtualElement;
1644
1641
  //#endregion
1645
- export { type AcceptPendingReplacementOptions, type CheckRoundTripOptions, type CodeBlockAttrs, type CodeBlockFenceStyle, type CodeToken, type DocToMarkdownOptions, type EditorConfig, type EditorExtension, type EditorExtensionOptions, type ExitBoundaryHandler, type ExitBoundaryOptions, type FileClickHandler, type FileClickPayload, type FileInfo, type FileInfoResolver, type FileLinkOptions, type FileLinkPayload, type FileLinkResolver, type FilePasteHandler, type FilePasteOptions, type FileSaveErrorHandler, type FileViewOptions, type FollowLinkHandlers, type ImageClickHandler, type ImageClickPayload, type ImageOptions, type InlineMarkContext, type InlineMarkOptions, type KaTeXRender, type LanguageItem, type LinkAttrs, type LinkClickHandler, type LinkClickPayload, type LinkCopyHandler, type LinkCopyPayload, type LinkEditHandler, type LinkEditOptions, type LinkHoverHandler, type LinkHoverOptions, type LinkPreview, type LinkPreviewResolver, type LinkUnit, type ListMarker, type MarkChunk, type MarkMode, type MarkName, type MarkdownToDocOptions, type MdFileAttrs, type MdImageAttrs, type MdLinkTextAttrs, type MdMathAttrs, type MdWikilinkAttrs, type MeowdownCodeBlockAttrs, type MeowdownHTMLCommentAttrs, type MeowdownListAttrs, type MeowdownTableCellAttrs, type NodeName, type ParsedWikiEmbed, type PendingReplacement, type PendingReplacementEvent, type PendingReplacementHandler, type PendingReplacementMode, type PendingReplacementOutcome, type PlaceholderOptions, type PositionRange, type PostEmbedKind, type PostEmbedSnapshot, Priority, type ReferenceDefinition, type ReferenceDefinitionIndex, type ReferenceDefinitions, type RoundTripFidelity, type SearchStatus, type SearchStatusHandler, type StartPendingReplacementOptions, type TableColumnAlign, type TagClickHandler, type TagClickPayload, type TypedEditor, type TypedMarkBuilders, type VirtualElement, type WikiEmbedOptions, type WikiEmbedResolution, type WikiEmbedResolver, type WikilinkClickHandler, type WikilinkClickPayload, type WikilinkHoverHandler, type WikilinkHoverHit, type WikilinkOptions, type WikilinkPayload, type WikilinkResolution, type WikilinkResolver, type XPostResolver, type YouTubeVideoResolver, defineCodeBlockPreviewPlugin, definePlaceholder, defineSearchStatusHandler, getSearchStatus, isCodeBlockPreviewHiddenDecoration, parseXPostId, withPriority };
1642
+ export { type AcceptPendingReplacementOptions, type CheckRoundTripOptions, type CodeBlockAttrs, type CodeBlockFenceStyle, type CodeToken, type DocToMarkdownOptions, type EditorConfig, type EditorExtension, type EditorExtensionOptions, type ExitBoundaryHandler, type ExitBoundaryOptions, type FileClickHandler, type FileClickPayload, type FileInfo, type FileInfoResolver, type FileLinkOptions, type FileLinkPayload, type FileLinkResolver, type FilePasteHandler, type FilePasteOptions, type FileSaveErrorHandler, type FileViewOptions, type FollowLinkHandlers, type ImageClickHandler, type ImageClickPayload, type ImageOptions, type InlineMarkContext, type InlineMarkOptions, type KaTeXRender, type LanguageItem, type LinkAttrs, type LinkClickHandler, type LinkClickPayload, type LinkCopyHandler, type LinkCopyPayload, type LinkEditHandler, type LinkEditOptions, type LinkHoverHandler, type LinkHoverOptions, type LinkPreview, type LinkPreviewResolver, type LinkUnit, type ListMarker, type MarkChunk, type MarkMode, type MarkName, type MarkdownToDocOptions, type MdFileAttrs, type MdImageAttrs, type MdLinkTextAttrs, type MdMathAttrs, type MdWikilinkAttrs, type MeowdownCodeBlockAttrs, type MeowdownHTMLCommentAttrs, type MeowdownListAttrs, type MeowdownTableCellAttrs, type NodeName, type ParsedWikiEmbed, type PendingReplacement, type PendingReplacementEvent, type PendingReplacementHandler, type PendingReplacementMode, type PendingReplacementOutcome, type PlaceholderOptions, type PositionRange, type PostEmbedSnapshot, Priority, type ReferenceDefinition, type ReferenceDefinitionIndex, type ReferenceDefinitions, type RoundTripFidelity, type SearchStatus, type SearchStatusHandler, type StartPendingReplacementOptions, type TableColumnAlign, type TagClickHandler, type TagClickPayload, type TypedEditor, type TypedMarkBuilders, type VirtualElement, type WikiEmbedOptions, type WikiEmbedResolution, type WikiEmbedResolver, type WikilinkClickHandler, type WikilinkClickPayload, type WikilinkHoverHandler, type WikilinkHoverHit, type WikilinkOptions, type WikilinkPayload, type WikilinkResolution, type WikilinkResolver, type XPostResolver, type YouTubeVideoResolver, defineCodeBlockPreviewPlugin, definePlaceholder, defineSearchStatusHandler, getSearchStatus, isCodeBlockPreviewHiddenDecoration, withPriority };
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { Priority, Priority as Priority$1, createMarkBuilders, createNodeBuilders, defineBaseCommands, defineBaseKeymap, defineClipboardSerializer, defineCommands, defineHistory, defineKeymap, defineMarkSpec, defineMarkView, defineNodeAttr, defineNodeSpec, definePlugin, findMarkRange, getMarkRange, getMarkType, getNodeType, isAllSelection, isApple, isAtBlockStart, isNodeSelection, isTextSelection, setBlockType, toggleNode, union, unsetBlockType, withPriority, withPriority as withPriority$1, withSkipCodeBlock } from "@prosekit/core";
1
+ import { Priority, Priority as Priority$1, createMarkBuilders, createNodeBuilders, defaultBlockAt, defineBaseCommands, defineBaseKeymap, defineClipboardSerializer, defineCommands, defineHistory, defineKeymap, defineMarkSpec, defineMarkView, defineNodeAttr, defineNodeSpec, definePlugin, findMarkRange, getMarkRange, getMarkType, getNodeType, isAllSelection, isApple, isAtBlockStart, isNodeSelection, isTextSelection, setBlockType, toggleNode, union, unsetBlockType, withPriority, withPriority as withPriority$1, withSkipCodeBlock } from "@prosekit/core";
2
2
  import { defineCodeBlock, defineCodeBlockHighlight, defineCodeBlockPreviewPlugin, isCodeBlockPreviewHiddenDecoration } from "@prosekit/extensions/code-block";
3
3
  import { definePlaceholder, definePlaceholder as definePlaceholder$1 } from "@prosekit/extensions/placeholder";
4
4
  import { NodeSelection, Plugin, PluginKey, Selection, TextSelection } from "@prosekit/pm/state";
5
5
  import { defineSearchCommands, defineSearchQuery, defineSearchStatusHandler, getSearchStatus, getSearchStatus as getSearchStatus$1 } from "@prosekit/extensions/search";
6
- import { LEZER_NODE_IDS, collectInlineElements, getAutolinkHref, gfmBlockOnlyParser, gfmParser, isSpaceChar, parseInline } from "@meowdown/markdown";
6
+ import { LEZER_NODE_IDS, collectInlineElements, getAutolinkHref, gfmBlockOnlyParser, gfmParser, isSpaceChar, matchEmbed, parseInline, parseXPostId } from "@meowdown/markdown";
7
7
  import { isElementLike, isHTMLElement, isObject, once } from "@ocavue/utils";
8
8
  import { defineBlockquote } from "@prosekit/extensions/blockquote";
9
9
  import { defineDoc } from "@prosekit/extensions/doc";
@@ -33,14 +33,14 @@ import { triggerAutocomplete } from "@prosekit/extensions/autocomplete";
33
33
  import { defineListCommands, defineListDropIndicator, defineListKeymap, defineListSpec, moveList, toggleList, unwrapList, wrapInList } from "@prosekit/extensions/list";
34
34
  import { chainCommands, lift, newlineInCode, splitBlock } from "@prosekit/pm/commands";
35
35
  import { closeHistory } from "@prosekit/pm/history";
36
- import { fromSyndication } from "@post-embed/exporter/x/syndication";
37
- import { XPostSchema, YouTubeVideoSchema, parseXPostId } from "@post-embed/schema";
38
- import { createLRU } from "lru.min";
39
- import * as v from "valibot";
40
36
  import { defineHorizontalRule } from "@prosekit/extensions/horizontal-rule";
41
37
  import { registerXPost } from "@post-embed/elements/x";
42
38
  import { registerYouTubeVideo } from "@post-embed/elements/youtube";
43
39
  import { registerResizableHandleElement, registerResizableRootElement } from "@prosekit/web/resizable";
40
+ import { fromSyndication } from "@post-embed/exporter/x/syndication";
41
+ import { XPostSchema, YouTubeVideoSchema } from "@post-embed/schema";
42
+ import { createLRU } from "lru.min";
43
+ import * as v from "valibot";
44
44
  import { AttrStep, ReplaceStep, Step, StepResult, Transform } from "@prosekit/pm/transform";
45
45
  import { decodeString } from "micromark-util-decode-string";
46
46
  import { normalizeIdentifier } from "micromark-util-normalize-identifier";
@@ -602,6 +602,41 @@ function defineBulletAfterHeading(enabled) {
602
602
  } }), Priority$1.high);
603
603
  }
604
604
 
605
+ //#endregion
606
+ //#region src/extensions/click-below.ts
607
+ const clickBelowKey = new PluginKey("meowdown-click-below");
608
+ function acceptsCaretAtEnd(node) {
609
+ return node.isTextblock && !node.type.spec.code;
610
+ }
611
+ function handleMouseDown(view, event) {
612
+ if (event.target !== view.dom) return false;
613
+ if (event.button !== 0 || !view.editable) return false;
614
+ const { doc } = view.state;
615
+ const lastBlock = doc.lastChild;
616
+ if (!lastBlock || acceptsCaretAtEnd(lastBlock)) return false;
617
+ const lastBlockDOM = view.nodeDOM(doc.content.size - lastBlock.nodeSize);
618
+ if (!(lastBlockDOM instanceof HTMLElement)) return false;
619
+ if (event.clientY <= lastBlockDOM.getBoundingClientRect().bottom) return false;
620
+ const block = defaultBlockAt(doc.contentMatchAt(doc.childCount))?.createAndFill();
621
+ if (!block) return false;
622
+ event.preventDefault();
623
+ const end = doc.content.size;
624
+ const tr = view.state.tr.insert(end, block);
625
+ view.dispatch(tr.setSelection(TextSelection.create(tr.doc, end + 1)).scrollIntoView());
626
+ view.focus();
627
+ return true;
628
+ }
629
+ /**
630
+ * A press below the last block appends an empty paragraph and puts the caret
631
+ * in it, when that block would otherwise keep the caret inside itself.
632
+ */
633
+ function defineClickBelow() {
634
+ return definePlugin(new Plugin({
635
+ key: clickBelowKey,
636
+ props: { handleDOMEvents: { mousedown: handleMouseDown } }
637
+ }));
638
+ }
639
+
605
640
  //#endregion
606
641
  //#region src/converters/html-to-md.ts
607
642
  /**
@@ -2222,6 +2257,38 @@ function getCodeTokens(code, language) {
2222
2257
  return loadLanguageSupport(trimmed).then((loaded) => loaded ? tokenize(code, loaded) : []);
2223
2258
  }
2224
2259
 
2260
+ //#endregion
2261
+ //#region src/extensions/code-block-exit.ts
2262
+ /**
2263
+ * With the caret at the end of a code block, move it into the block below when
2264
+ * that block is an empty paragraph, and into a new default block otherwise.
2265
+ */
2266
+ const exitCodeBlockAtEnd = (state, dispatch) => {
2267
+ const { $head, empty } = state.selection;
2268
+ if (!empty) return false;
2269
+ const codeBlock = $head.parent;
2270
+ if (!codeBlock.type.spec.code || $head.parentOffset !== codeBlock.content.size) return false;
2271
+ const container = $head.node(-1);
2272
+ const indexAfter = $head.indexAfter(-1);
2273
+ const after = $head.after();
2274
+ const next = container.maybeChild(indexAfter);
2275
+ if (next && isNodeOfType(next, "paragraph") && next.content.size === 0) {
2276
+ dispatch?.(state.tr.setSelection(TextSelection.create(state.doc, after + 1)).scrollIntoView());
2277
+ return true;
2278
+ }
2279
+ const type = defaultBlockAt(container.contentMatchAt(indexAfter));
2280
+ const block = type?.createAndFill();
2281
+ if (!type || !block || !container.canReplaceWith(indexAfter, indexAfter, type)) return false;
2282
+ if (dispatch) {
2283
+ const tr = state.tr.insert(after, block);
2284
+ dispatch(tr.setSelection(TextSelection.create(tr.doc, after + 1)).scrollIntoView());
2285
+ }
2286
+ return true;
2287
+ };
2288
+ function defineCodeBlockExitKeymap() {
2289
+ return defineKeymap({ "Mod-Enter": exitCodeBlockAtEnd });
2290
+ }
2291
+
2225
2292
  //#endregion
2226
2293
  //#region src/extensions/code-block.ts
2227
2294
  function defineFenceStyleAttr() {
@@ -2279,7 +2346,7 @@ function defineDollarFenceEnterRule() {
2279
2346
  });
2280
2347
  }
2281
2348
  function defineCodeBlock$1() {
2282
- return union(defineCodeBlock(), defineFenceStyleAttr(), defineFenceLengthAttr(), defineTildeFenceInputRule(), defineTildeFenceEnterRule(), defineDollarFenceEnterRule());
2349
+ return union(defineCodeBlock(), defineFenceStyleAttr(), defineFenceLengthAttr(), defineTildeFenceInputRule(), defineTildeFenceEnterRule(), defineDollarFenceEnterRule(), defineCodeBlockExitKeymap());
2283
2350
  }
2284
2351
 
2285
2352
  //#endregion
@@ -2478,99 +2545,13 @@ function getPastedText(event, slice) {
2478
2545
  return slice.content.textBetween(0, slice.content.size, "\n");
2479
2546
  }
2480
2547
 
2481
- //#endregion
2482
- //#region src/extensions/post-embed.ts
2483
- const YOUTUBE_HOSTS = /^(?:www\.|m\.)?(?:youtube\.com|youtube-nocookie\.com)$/i;
2484
- const YOUTU_BE_HOST = /^(?:www\.)?youtu\.be$/i;
2485
- const VIDEO_ID = /^[\w-]{11}$/;
2486
- function parseURL(src) {
2487
- try {
2488
- return new URL(src);
2489
- } catch {
2490
- return;
2491
- }
2492
- }
2493
- function isYouTubeVideo(src) {
2494
- const url = parseURL(src);
2495
- if (!url) return false;
2496
- let videoId = null;
2497
- if (YOUTU_BE_HOST.test(url.hostname)) videoId = url.pathname.slice(1);
2498
- else if (YOUTUBE_HOSTS.test(url.hostname)) {
2499
- const [, firstSegment, secondSegment] = url.pathname.split("/");
2500
- if (url.pathname === "/watch") videoId = url.searchParams.get("v");
2501
- else if (firstSegment === "shorts" || firstSegment === "embed" || firstSegment === "live") videoId = secondSegment ?? null;
2502
- }
2503
- return videoId !== null && VIDEO_ID.test(videoId);
2504
- }
2505
- /**
2506
- * The post-embed card an image `src` renders as, or `undefined` for a plain image.
2507
- */
2508
- function matchPostEmbed(src) {
2509
- if (parseXPostId(src) !== void 0) return "x-post";
2510
- if (isYouTubeVideo(src)) return "youtube-video";
2511
- }
2512
- /**
2513
- * Wrap a loader in a 64-entry LRU keyed by URL. A settled URL answers
2514
- * synchronously, so a revisited document renders its cards in the first
2515
- * frame; the first visit returns the in-flight load.
2516
- */
2517
- function cached(load) {
2518
- const cache = createLRU({ max: 64 });
2519
- return (url) => {
2520
- if (cache.has(url)) return cache.get(url);
2521
- const pending = load(url).then((value) => {
2522
- cache.set(url, value);
2523
- return value;
2524
- }, (error) => {
2525
- cache.delete(url);
2526
- throw error;
2527
- });
2528
- cache.set(url, pending);
2529
- return pending;
2530
- };
2531
- }
2532
- const X_POST_API = "https://react-tweet.vercel.app/api/tweet/";
2533
- const defaultResolveXPost = cached(async (url) => {
2534
- const id = parseXPostId(url);
2535
- if (id === void 0) return;
2536
- const response = await fetch(X_POST_API + id);
2537
- if (!response.ok) return;
2538
- const json = await response.json();
2539
- return fromSyndication(json.data);
2540
- });
2541
- const YOUTUBE_OEMBED_API = "https://www.youtube.com/oembed?format=json&url=";
2542
- const defaultResolveYouTubeVideo = cached(async (url) => {
2543
- const response = await fetch(YOUTUBE_OEMBED_API + encodeURIComponent(url));
2544
- if (!response.ok) return;
2545
- return {
2546
- url,
2547
- ...await response.json()
2548
- };
2549
- });
2550
- const PostEmbedSnapshotSchema = v.variant("kind", [v.object({
2551
- kind: v.literal("x-post"),
2552
- data: XPostSchema
2553
- }), v.object({
2554
- kind: v.literal("youtube-video"),
2555
- data: YouTubeVideoSchema
2556
- })]);
2557
- /**
2558
- * The snapshot a card renders from a saved JSON object, or `undefined` when
2559
- * the object does not validate. Unknown fields are dropped, so a persisted
2560
- * snapshot is exactly what the card needs.
2561
- */
2562
- function parsePostEmbedSnapshot(value) {
2563
- const result = v.safeParse(PostEmbedSnapshotSchema, value);
2564
- return result.success ? result.output : void 0;
2565
- }
2566
-
2567
2548
  //#endregion
2568
2549
  //#region src/extensions/embed-paste.ts
2569
2550
  const embedPasteKey = new PluginKey("meowdown-embed-paste");
2570
2551
  function detectEmbedUrl(text) {
2571
2552
  const trimmed = text.trim();
2572
2553
  if (!trimmed || /\s/.test(trimmed)) return void 0;
2573
- return matchPostEmbed(trimmed) ? trimmed : void 0;
2554
+ return matchEmbed(trimmed) ? trimmed : void 0;
2574
2555
  }
2575
2556
  function insertEmbedFromPaste(view, url) {
2576
2557
  const { from, to } = view.state.selection;
@@ -3663,6 +3644,63 @@ function stripMagicComment(source) {
3663
3644
  return source.replace(TRAILING_MAGIC_COMMENT_RE, "");
3664
3645
  }
3665
3646
 
3647
+ //#endregion
3648
+ //#region src/extensions/post-embed.ts
3649
+ /**
3650
+ * Wrap a loader in a 64-entry LRU keyed by URL. A settled URL answers
3651
+ * synchronously, so a revisited document renders its cards in the first
3652
+ * frame; the first visit returns the in-flight load.
3653
+ */
3654
+ function cached(load) {
3655
+ const cache = createLRU({ max: 64 });
3656
+ return (url) => {
3657
+ if (cache.has(url)) return cache.get(url);
3658
+ const pending = load(url).then((value) => {
3659
+ cache.set(url, value);
3660
+ return value;
3661
+ }, (error) => {
3662
+ cache.delete(url);
3663
+ throw error;
3664
+ });
3665
+ cache.set(url, pending);
3666
+ return pending;
3667
+ };
3668
+ }
3669
+ const X_POST_API = "https://react-tweet.vercel.app/api/tweet/";
3670
+ const defaultResolveXPost = cached(async (url) => {
3671
+ const id = parseXPostId(url);
3672
+ if (id === void 0) return;
3673
+ const response = await fetch(X_POST_API + id);
3674
+ if (!response.ok) return;
3675
+ const json = await response.json();
3676
+ return fromSyndication(json.data);
3677
+ });
3678
+ const YOUTUBE_OEMBED_API = "https://www.youtube.com/oembed?format=json&url=";
3679
+ const defaultResolveYouTubeVideo = cached(async (url) => {
3680
+ const response = await fetch(YOUTUBE_OEMBED_API + encodeURIComponent(url));
3681
+ if (!response.ok) return;
3682
+ return {
3683
+ url,
3684
+ ...await response.json()
3685
+ };
3686
+ });
3687
+ const PostEmbedSnapshotSchema = v.variant("kind", [v.object({
3688
+ kind: v.literal("x-post"),
3689
+ data: XPostSchema
3690
+ }), v.object({
3691
+ kind: v.literal("youtube-video"),
3692
+ data: YouTubeVideoSchema
3693
+ })]);
3694
+ /**
3695
+ * The snapshot a card renders from a saved JSON object, or `undefined` when
3696
+ * the object does not validate. Unknown fields are dropped, so a persisted
3697
+ * snapshot is exactly what the card needs.
3698
+ */
3699
+ function parsePostEmbedSnapshot(value) {
3700
+ const result = v.safeParse(PostEmbedSnapshotSchema, value);
3701
+ return result.success ? result.output : void 0;
3702
+ }
3703
+
3666
3704
  //#endregion
3667
3705
  //#region src/extensions/wiki-embed.ts
3668
3706
  const WIKI_EMBED_SIZE = /^(\d+)(?:x(\d+))?$/i;
@@ -3906,7 +3944,7 @@ var ImageMarkView = class {
3906
3944
  const { src } = this.#attrs;
3907
3945
  const wrapper = document.createElement("span");
3908
3946
  wrapper.className = "md-image-view-preview md-atom-view-preview";
3909
- const kind = matchPostEmbed(src);
3947
+ const kind = matchEmbed(src);
3910
3948
  if (kind) {
3911
3949
  wrapper.dataset.testid = `${kind}-embed`;
3912
3950
  wrapper.dataset.postEmbed = kind;
@@ -7317,7 +7355,7 @@ function insertSoftBreak() {
7317
7355
  return softBreakCommand;
7318
7356
  }
7319
7357
  function defineSoftBreak() {
7320
- return union(defineCommands({ insertSoftBreak }), defineKeymap({ "Shift-Enter": softBreakCommand }));
7358
+ return union(defineCommands({ insertSoftBreak }), defineKeymap({ "Shift-Enter": chainCommands(exitCodeBlockAtEnd, softBreakCommand) }));
7321
7359
  }
7322
7360
 
7323
7361
  //#endregion
@@ -7576,7 +7614,7 @@ function defineEditorExtensionImpl(options) {
7576
7614
  if (editorClassName) attributes.class += ` ${editorClassName}`;
7577
7615
  if (spellCheck != null) attributes.spellcheck = String(spellCheck);
7578
7616
  return attributes;
7579
- }), defineCodeBlockSyntaxHighlight(), defineCrossEditorDrag(), defineEscapeCollapse(), defineSoftBreak(), defineMoveBlock(), defineSelectDocBoundary(), defineInlineMarkPlugin(getEditorConfig), defineInlineToggle(), defineLinkCommands(), defineWikilink(), defineMath(), defineMarkMode(getMarkMode), defineClipboard(), defineScrollToSelection(), defineHiddenRunCaret(), defineSystemSubstitutionGuard(), defineAtomMarkNavigation({ marks: ATOM_SOURCE_MARK_NAMES.map((name) => ({
7617
+ }), defineCodeBlockSyntaxHighlight(), defineCrossEditorDrag(), defineClickBelow(), defineEscapeCollapse(), defineSoftBreak(), defineMoveBlock(), defineSelectDocBoundary(), defineInlineMarkPlugin(getEditorConfig), defineInlineToggle(), defineLinkCommands(), defineWikilink(), defineMath(), defineMarkMode(getMarkMode), defineClipboard(), defineScrollToSelection(), defineHiddenRunCaret(), defineSystemSubstitutionGuard(), defineAtomMarkNavigation({ marks: ATOM_SOURCE_MARK_NAMES.map((name) => ({
7580
7618
  name,
7581
7619
  modes: [
7582
7620
  "hide",
@@ -8369,7 +8407,7 @@ const EDITOR_KEY_BINDINGS = {
8369
8407
  "Mod-5": "Heading 5",
8370
8408
  "Mod-6": "Heading 6",
8371
8409
  "Mod-.": "Fold or unfold a bullet",
8372
- "Shift-Enter": "Insert a line break",
8410
+ "Shift-Enter": "Insert a line break, or leave a code block from its end",
8373
8411
  "Mod-Enter": "Follow the link under the caret, or cycle a checkbox task",
8374
8412
  "Mod-Shift-Enter": "Cycle a circle checkbox task",
8375
8413
  "Mod-Shift-7": "Ordered list",
@@ -8550,17 +8588,17 @@ function defineMarkHoverHandler(config) {
8550
8588
  //#region src/extensions/link-hover.ts
8551
8589
  const linkHoverKey = new PluginKey("meowdown-link-hover");
8552
8590
  /**
8553
- * Dwell before a cold hover enters, in ms.
8591
+ * Delay before a cold hover enters, in ms.
8554
8592
  */
8555
- const OPEN_DELAY = 300;
8593
+ const OPEN_DELAY$1 = 300;
8556
8594
  /**
8557
8595
  * Grace before a leave fires, in ms. The window lets a pointer travel from
8558
8596
  * the hovered link onto the popup it anchors.
8559
8597
  */
8560
- const CLOSE_DELAY = 100;
8598
+ const CLOSE_DELAY$1 = 100;
8561
8599
  /**
8562
8600
  * Track the link under the user's attention: a mouse hover after a short
8563
- * dwell, or a touch tap immediately, since touch has no hover. Without the
8601
+ * delay, or a touch tap immediately, since touch has no hover. Without the
8564
8602
  * tap entry, the popup's preview and actions would stay unreachable on
8565
8603
  * phones, where a tap on a link only places the caret and raises the
8566
8604
  * software keyboard.
@@ -8569,8 +8607,8 @@ function defineLinkHoverHandler(onHoverChange, { canLeave } = {}) {
8569
8607
  return defineMarkHoverHandler({
8570
8608
  key: linkHoverKey,
8571
8609
  selector: ".md-link",
8572
- openDelay: OPEN_DELAY,
8573
- closeDelay: CLOSE_DELAY,
8610
+ openDelay: OPEN_DELAY$1,
8611
+ closeDelay: CLOSE_DELAY$1,
8574
8612
  tap: true,
8575
8613
  canLeave,
8576
8614
  findPayloadAt: (state, pos) => {
@@ -8821,18 +8859,30 @@ function defineVirtualCaret(layer) {
8821
8859
  //#region src/extensions/wikilink-hover.ts
8822
8860
  const wikilinkHoverKey = new PluginKey("meowdown-wikilink-hover");
8823
8861
  /**
8824
- * Track the wikilink under the pointer without attaching per-link listeners.
8862
+ * Delay before a cold hover enters, in ms.
8863
+ */
8864
+ const OPEN_DELAY = 300;
8865
+ /**
8866
+ * Grace before a leave fires, in ms. Returning within it re-enters without
8867
+ * a new delay.
8868
+ */
8869
+ const CLOSE_DELAY = 100;
8870
+ /**
8871
+ * Track the wikilink the pointer rests on without attaching per-link
8872
+ * listeners.
8825
8873
  *
8826
- * The handler is revalidated after document transactions and receives leave
8827
- * when the hovered link is deleted, replaced, or changes target. Moving among
8828
- * descendants of one label is de-duplicated.
8874
+ * A cold pointer must rest on a link for `openDelay` ms before enter fires.
8875
+ * Moving to an adjacent link restarts the delay, or switches at once when a
8876
+ * link is already entered. Leave fires `closeDelay` ms after the pointer
8877
+ * leaves, and immediately when the hovered link is deleted, replaced, or
8878
+ * changes target. Moving among descendants of one label is de-duplicated.
8829
8879
  */
8830
- function defineWikilinkHoverHandler(onHoverChange) {
8880
+ function defineWikilinkHoverHandler(onHoverChange, openDelay = OPEN_DELAY, closeDelay = CLOSE_DELAY) {
8831
8881
  return defineMarkHoverHandler({
8832
8882
  key: wikilinkHoverKey,
8833
8883
  selector: ".md-wikilink-view-preview",
8834
- openDelay: 0,
8835
- closeDelay: 0,
8884
+ openDelay,
8885
+ closeDelay,
8836
8886
  tap: false,
8837
8887
  findPayloadAt: findWikilinkAt,
8838
8888
  findPayloadForElement: findWikilinkForElement,
@@ -8937,4 +8987,4 @@ function getVirtualElementFromRange(view, range) {
8937
8987
  }
8938
8988
 
8939
8989
  //#endregion
8940
- export { EDITOR_KEY_BINDINGS, Priority, buildFileMarkdown, checkRoundTrip, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineCodeBlockSyntaxHighlight, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLComment, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkCommands, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMath, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getEditorConfig, getFileKind, getIsComposing, getLinkText, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isMarkOfType, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, matchPostEmbed, normalizeHref, parsePostEmbedSnapshot, parseWikiEmbed, parseXPostId, removeLink, renderMathInto, updateEditorConfig, updateLink, wikiEmbedBasename, withPriority };
8990
+ export { EDITOR_KEY_BINDINGS, Priority, buildFileMarkdown, checkRoundTrip, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineCodeBlockSyntaxHighlight, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLComment, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkCommands, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMath, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getEditorConfig, getFileKind, getIsComposing, getLinkText, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isMarkOfType, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, normalizeHref, parsePostEmbedSnapshot, parseWikiEmbed, removeLink, renderMathInto, updateEditorConfig, updateLink, wikiEmbedBasename, withPriority };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/core",
3
3
  "type": "module",
4
- "version": "0.71.2",
4
+ "version": "0.72.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -25,6 +25,7 @@
25
25
  "@codemirror/language-data": "^6.5.2",
26
26
  "@floating-ui/dom": "^1.8.0",
27
27
  "@lezer/highlight": "^1.2.3",
28
+ "@meowdown/markdown": "^0.72.0",
28
29
  "@ocavue/utils": "^1.8.0",
29
30
  "@post-embed/elements": "^0.5.0",
30
31
  "@post-embed/exporter": "^0.4.1",
@@ -35,7 +36,7 @@
35
36
  "@prosekit/pm": "^0.1.19",
36
37
  "@prosekit/web": "^0.9.2",
37
38
  "hast-util-to-mdast": "^10.1.2",
38
- "katex": "^0.18.5",
39
+ "katex": "^0.18.7",
39
40
  "lru.min": "^1.1.5",
40
41
  "mdast-util-to-markdown": "^2.1.2",
41
42
  "micromark-util-decode-string": "^2.0.1",
@@ -48,22 +49,20 @@
48
49
  "remark-stringify": "^11.0.0",
49
50
  "unicode-by-name": "^0.2.0",
50
51
  "unified": "^11.0.5",
51
- "valibot": "^1.5.0",
52
- "@meowdown/markdown": "^0.71.2"
52
+ "valibot": "^1.5.0"
53
53
  },
54
54
  "devDependencies": {
55
+ "@meowdown/vitest": "0.0.0",
55
56
  "@ocavue/tsconfig": "^0.7.1",
56
- "@tsdown/css": "^0.23.0-rc.1",
57
+ "@tsdown/css": "^0.23.0",
57
58
  "@types/hast": "^3.0.5",
58
59
  "@types/mdast": "^4.0.4",
59
- "@vitest/browser-playwright": "^5.0.0",
60
+ "@vitest/browser-playwright": "^5.0.1",
60
61
  "commonmark.json": "^0.31.0",
61
62
  "dedent": "^1.7.2",
62
63
  "diffable-html-snapshot": "^0.3.0",
63
- "tsdown": "^0.23.0-rc.1",
64
- "vitest": "^5.0.0",
65
- "vitest-browser-commands": "^0.4.1",
66
- "@meowdown/vitest": "0.0.0"
64
+ "tsdown": "^0.23.0",
65
+ "vitest": "^5.0.1"
67
66
  },
68
67
  "publishConfig": {
69
68
  "access": "public"