@meowdown/core 0.64.2 → 0.65.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +116 -50
  2. package/dist/index.js +340 -246
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -260,7 +260,7 @@ interface MdMathAttrs {
260
260
  * source reveals around the caret.
261
261
  */
262
262
  type MdPackAttrs = {
263
- key: 'italic' | 'bold' | 'code' | 'del' | 'highlight' | 'autolink';
263
+ key: 'italic' | 'bold' | 'code' | 'del' | 'highlight';
264
264
  data?: null;
265
265
  slot?: 1 | null;
266
266
  revealInFocus: true;
@@ -268,13 +268,25 @@ type MdPackAttrs = {
268
268
  } | {
269
269
  key: 'link';
270
270
  data: {
271
+ form: 'inline' | 'reference';
271
272
  href: string;
272
273
  title: string;
273
- isReference: boolean;
274
+ } | {
275
+ form: 'angle';
276
+ href: string;
274
277
  };
275
278
  slot?: 1 | null;
276
279
  revealInFocus: true;
277
280
  revealInHide?: null;
281
+ } | {
282
+ key: 'link';
283
+ data: {
284
+ form: 'bare';
285
+ href: string;
286
+ };
287
+ slot?: 1 | null;
288
+ revealInFocus?: null;
289
+ revealInHide?: null;
278
290
  } | {
279
291
  key: 'math';
280
292
  data?: null;
@@ -296,7 +308,7 @@ interface PositionRange {
296
308
  }
297
309
  //#endregion
298
310
  //#region src/extensions/get-link-unit-at.d.ts
299
- interface LinkUnit {
311
+ interface LinkUnitBase {
300
312
  /**
301
313
  * Whole inline link, reference link, or autolink range.
302
314
  */
@@ -308,14 +320,6 @@ interface LinkUnit {
308
320
  * whose collapsed glyphs measure at bogus coordinates.
309
321
  */
310
322
  text: PositionRange;
311
- /**
312
- * Interior of `[ ]`. Absent for an autolink.
313
- */
314
- label?: PositionRange;
315
- /**
316
- * Interior of `( )`. What `updateLink` rewrites. Absent for an autolink.
317
- */
318
- dest?: PositionRange;
319
323
  /**
320
324
  * The link URL. Could be an empty string.
321
325
  */
@@ -325,6 +329,25 @@ interface LinkUnit {
325
329
  */
326
330
  title: string;
327
331
  }
332
+ type LinkUnit = (LinkUnitBase & {
333
+ form: 'inline';
334
+ /**
335
+ * Interior of `[ ]`.
336
+ */
337
+ label: PositionRange;
338
+ /**
339
+ * Interior of `( )`. What `updateLink` rewrites.
340
+ */
341
+ dest: PositionRange;
342
+ }) | (LinkUnitBase & {
343
+ /**
344
+ * A reference link or autolink resolves an href but has no editable
345
+ * label/dest.
346
+ */
347
+ form: 'reference' | 'angle' | 'bare';
348
+ label?: undefined;
349
+ dest?: undefined;
350
+ });
328
351
  /**
329
352
  * The link covering `pos`, with its sub-ranges (`label`, `dest`) and parsed
330
353
  * `href`/`title`. The single query the commands and the hover/click handlers
@@ -1032,6 +1055,11 @@ interface FileClickPayload {
1032
1055
  * pill. Read modifier keys or position a popover from it.
1033
1056
  */
1034
1057
  event: MouseEvent | KeyboardEvent;
1058
+ /**
1059
+ * Whether the platform's mod key (`Command` on Apple, `Ctrl` elsewhere) was held
1060
+ * beyond the gesture that triggered the activation.
1061
+ */
1062
+ mod: boolean;
1035
1063
  }
1036
1064
  type FileClickHandler = (payload: FileClickPayload) => void;
1037
1065
  /**
@@ -1117,6 +1145,45 @@ declare function getFileKind(href: string): string;
1117
1145
  */
1118
1146
  declare function defineFileView(options?: FileViewOptions): PlainExtension;
1119
1147
  //#endregion
1148
+ //#region src/extensions/image-click.d.ts
1149
+ /**
1150
+ * Payload for {@link ImageClickHandler}.
1151
+ */
1152
+ interface ImageClickPayload {
1153
+ /**
1154
+ * The resolved source from `![alt](src)` or a claimed `![[target]]`.
1155
+ */
1156
+ src: string;
1157
+ /**
1158
+ * The image alt text.
1159
+ */
1160
+ alt: string;
1161
+ /**
1162
+ * The originating click or touch tap, or the `Enter`/`Mod-Enter` key press
1163
+ * that followed the selected image. Read the target or position a popover
1164
+ * from it; a touch surface delivers the `touchend` instead of a click.
1165
+ */
1166
+ event: MouseEvent | TouchEvent | KeyboardEvent;
1167
+ /**
1168
+ * Whether the platform's mod key (`Command` on Apple, `Ctrl` elsewhere) was held
1169
+ * beyond the gesture that triggered the activation.
1170
+ */
1171
+ mod: boolean;
1172
+ }
1173
+ type ImageClickHandler = (payload: ImageClickPayload) => void;
1174
+ /**
1175
+ * Call `onClick` when the user clicks or taps a rendered image preview, with
1176
+ * the image's markdown `src`, `alt`, and the originating event.
1177
+ *
1178
+ * Touch taps are handled from `touchend` rather than the synthetic click:
1179
+ * previews live inside the editor contenteditable, and iOS WebKit's
1180
+ * tap-to-focus is a native gesture default action that only cancelling the
1181
+ * `touchend` can suppress — otherwise a tap briefly focuses the editor and
1182
+ * raises the software keyboard before the handler opens its own surface
1183
+ * (such as a lightbox).
1184
+ */
1185
+ declare function defineImageClickHandler(onClick: ImageClickHandler): PlainExtension;
1186
+ //#endregion
1120
1187
  //#region src/extensions/link-click.d.ts
1121
1188
  interface LinkClickPayload {
1122
1189
  href: string;
@@ -1124,6 +1191,11 @@ interface LinkClickPayload {
1124
1191
  * The originating click, or the `Enter`/`Mod-Enter` key press that followed the link.
1125
1192
  */
1126
1193
  event: MouseEvent | KeyboardEvent;
1194
+ /**
1195
+ * Whether the platform's mod key (`Command` on Apple, `Ctrl` elsewhere) was held
1196
+ * beyond the gesture that triggered the activation.
1197
+ */
1198
+ mod: boolean;
1127
1199
  }
1128
1200
  type LinkClickHandler = (payload: LinkClickPayload) => void;
1129
1201
  interface LinkCopyPayload {
@@ -1148,6 +1220,11 @@ interface TagClickPayload {
1148
1220
  * Read modifier keys or position a popover from it.
1149
1221
  */
1150
1222
  event: MouseEvent | KeyboardEvent;
1223
+ /**
1224
+ * Whether the platform's mod key (`Command` on Apple, `Ctrl` elsewhere) was held
1225
+ * beyond the gesture that triggered the activation.
1226
+ */
1227
+ mod: boolean;
1151
1228
  }
1152
1229
  type TagClickHandler = (payload: TagClickPayload) => void;
1153
1230
  /**
@@ -1169,6 +1246,11 @@ interface WikilinkClickPayload {
1169
1246
  * The originating click, or the `Enter`/`Mod-Enter` key press that followed the link.
1170
1247
  */
1171
1248
  event: MouseEvent | KeyboardEvent;
1249
+ /**
1250
+ * Whether the platform's mod key (`Command` on Apple, `Ctrl` elsewhere) was held
1251
+ * beyond the gesture that triggered the activation.
1252
+ */
1253
+ mod: boolean;
1172
1254
  }
1173
1255
  type WikilinkClickHandler = (payload: WikilinkClickPayload) => void;
1174
1256
  /**
@@ -1184,49 +1266,24 @@ interface FollowLinkHandlers {
1184
1266
  onTagClick?: TagClickHandler;
1185
1267
  onFileClick?: FileClickHandler;
1186
1268
  onLinkClick?: LinkClickHandler;
1269
+ onImageClick?: ImageClickHandler;
1187
1270
  }
1188
1271
  /**
1189
1272
  * Binds `Mod-Enter` to follow the wikilink, tag, file pill, or Markdown link
1190
- * under the caret, and plain `Enter` to follow a selected atom unit, firing
1191
- * the same handlers a click does. Off a link, `Mod-Enter` falls through so
1192
- * the list keymap keeps cycling checkbox tasks; off a selected unit, `Enter`
1193
- * falls through to the regular split. High priority puts this ahead of every
1194
- * keymap binding.
1195
- */
1196
- declare function defineFollowLinkHandler(handlers: FollowLinkHandlers): PlainExtension;
1197
- //#endregion
1198
- //#region src/extensions/image-click.d.ts
1199
- /**
1200
- * Payload for {@link ImageClickHandler}.
1201
- */
1202
- interface ImageClickPayload {
1203
- /**
1204
- * The resolved source from `![alt](src)` or a claimed `![[target]]`.
1205
- */
1206
- src: string;
1207
- /**
1208
- * The image alt text.
1209
- */
1210
- alt: string;
1211
- /**
1212
- * The originating click or touch tap. Read the target or position a popover
1213
- * from it; a touch surface delivers the `touchend` instead of a click.
1214
- */
1215
- event: MouseEvent | TouchEvent;
1216
- }
1217
- type ImageClickHandler = (payload: ImageClickPayload) => void;
1218
- /**
1219
- * Call `onClick` when the user clicks or taps a rendered image preview, with
1220
- * the image's markdown `src`, `alt`, and the originating event.
1273
+ * under the caret, and plain `Enter` to follow a selected atom unit (a
1274
+ * wikilink, file pill, or image), firing the same handlers a click does.
1275
+ * "Under the caret" means strictly inside the unit: a caret merely touching
1276
+ * a unit's edge is next to it, not on it.
1277
+ * Off a link, `Mod-Enter` falls through so the list keymap keeps cycling
1278
+ * checkbox tasks; off a selected unit, `Enter` falls through to the regular
1279
+ * split. High priority puts this ahead of every keymap binding.
1221
1280
  *
1222
- * Touch taps are handled from `touchend` rather than the synthetic click:
1223
- * previews live inside the editor contenteditable, and iOS WebKit's
1224
- * tap-to-focus is a native gesture default action that only cancelling the
1225
- * `touchend` can suppress — otherwise a tap briefly focuses the editor and
1226
- * raises the software keyboard before the handler opens its own surface
1227
- * (such as a lightbox).
1281
+ * A selected-unit follow reports `mod: true` when the platform's mod key
1282
+ * (`⌘` on Apple, `Ctrl` elsewhere) was held beyond its plain-`Enter` trigger;
1283
+ * a caret follow always reports `mod: false`, its mod key being the trigger
1284
+ * itself.
1228
1285
  */
1229
- declare function defineImageClickHandler(onClick: ImageClickHandler): PlainExtension;
1286
+ declare function defineFollowLinkHandler(handlers: FollowLinkHandlers): PlainExtension;
1230
1287
  //#endregion
1231
1288
  //#region src/extensions/image.d.ts
1232
1289
  type ImageUrlResolver = (src: string) => string | undefined;
@@ -1432,6 +1489,15 @@ declare function getTextblockDisplayText(textblock: ProseMirrorNode): string;
1432
1489
  */
1433
1490
  declare function formatFileSize(bytes: number): string;
1434
1491
  //#endregion
1492
+ //#region src/utils/is-mod-event.d.ts
1493
+ /**
1494
+ * Whether the platform's mod key is held on `event`: `Command` on Apple, `Ctrl` elsewhere.
1495
+ */
1496
+ declare function isModEvent(event: MouseEvent | KeyboardEvent | TouchEvent | {
1497
+ metaKey: boolean;
1498
+ ctrlKey: boolean;
1499
+ }): boolean;
1500
+ //#endregion
1435
1501
  //#region src/utils/katex-chunk.d.ts
1436
1502
  type KaTeXRender = typeof render;
1437
1503
  //#endregion
@@ -1469,4 +1535,4 @@ declare function getSelectedText(state: EditorState): string;
1469
1535
  */
1470
1536
  declare function getVirtualElementFromRange(view: EditorView, range: PositionRange): VirtualElement;
1471
1537
  //#endregion
1472
- export { type AcceptPendingReplacementOptions, type CheckRoundTripOptions, type CodeBlockAttrs, type CodeBlockFenceStyle, type CodeToken, type DocToMarkdownOptions, EDITOR_KEY_BINDINGS, type EditorExtension, type EditorExtensionOptions, type EmbedDescriptor, 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 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, 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, buildFileMarkdown, checkRoundTrip, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineCodeBlockSyntaxHighlight, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLComment, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkCommands, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMath, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSpellCheckPlugin, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getFileKind, getIsComposing, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isMarkOfType, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, parseWikiEmbed, removeLink, renderMathInto, updateLink, wikiEmbedBasename, withPriority };
1538
+ export { type AcceptPendingReplacementOptions, type CheckRoundTripOptions, type CodeBlockAttrs, type CodeBlockFenceStyle, type CodeToken, type DocToMarkdownOptions, EDITOR_KEY_BINDINGS, type EditorExtension, type EditorExtensionOptions, type EmbedDescriptor, 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 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, 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, buildFileMarkdown, checkRoundTrip, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineCodeBlockSyntaxHighlight, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLComment, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkCommands, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMath, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSpellCheckPlugin, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getFileKind, getIsComposing, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isMarkOfType, isModEvent, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, parseWikiEmbed, removeLink, renderMathInto, updateLink, wikiEmbedBasename, withPriority };
package/dist/index.js CHANGED
@@ -347,19 +347,14 @@ function resolvePosition(state, pos) {
347
347
  return $pos;
348
348
  }
349
349
  /**
350
- * Returns the run of the first listed mark covering `pos`, or `undefined` when
351
- * none covers it. `attrs` narrows the match to marks whose attributes contain
352
- * it, which is how a caller picks one level out of a nested stack of the same
353
- * mark.
350
+ * Returns the run of the mark covering `pos`, or `undefined` when none covers
351
+ * it. `attrs` narrows the match to marks whose attributes contain it, which
352
+ * is how a caller picks one level out of a nested stack of the same mark.
354
353
  */
355
354
  function getMarkRangeAt(state, pos, markName, attrs) {
356
355
  const $pos = resolvePosition(state, pos);
357
356
  if (!$pos) return;
358
- const markNames = Array.isArray(markName) ? markName : [markName];
359
- for (const name of markNames) {
360
- const range = ATOM_MARK_NAMES.includes(name) ? getAtomUnitRange(state, $pos, name) : getMarkRange($pos, name, attrs);
361
- if (range) return range;
362
- }
357
+ return ATOM_MARK_NAMES.includes(markName) ? getAtomUnitRange(state, $pos, markName) : getMarkRange($pos, markName, attrs);
363
358
  }
364
359
  /**
365
360
  * The range of the atom unit touching `$pos`, preferring the child to the
@@ -2848,7 +2843,6 @@ function getGenericPackKey(type) {
2848
2843
  case LEZER_NODE_IDS.InlineCode: return "code";
2849
2844
  case LEZER_NODE_IDS.Strikethrough: return "del";
2850
2845
  case LEZER_NODE_IDS.Highlight: return "highlight";
2851
- case LEZER_NODE_IDS.Autolink: return "autolink";
2852
2846
  default: return;
2853
2847
  }
2854
2848
  }
@@ -2914,6 +2908,7 @@ function walkNode(node, parentMarks, text, marks, out, options, context) {
2914
2908
  case LEZER_NODE_IDS.Wikilink: return walkWikilink(node, parentMarks, text, marks, out);
2915
2909
  case LEZER_NODE_IDS.WikiEmbed: return walkWikiEmbed(node, parentMarks, text, marks, out, options);
2916
2910
  case LEZER_NODE_IDS.InlineMath: return walkMath(node, parentMarks, text, marks, out);
2911
+ case LEZER_NODE_IDS.Autolink: return walkAutolink(node, parentMarks, text, marks, out);
2917
2912
  case LEZER_NODE_IDS.URL: return walkURL(node, parentMarks, text, marks, out);
2918
2913
  default: return walkGenericNode(node, parentMarks, text, marks, out, options, context);
2919
2914
  }
@@ -2935,14 +2930,55 @@ function walkGenericNode(node, parentMarks, text, marks, out, options, context)
2935
2930
  else walk(node.children, childMarks, node.from, node.to, text, marks, out, options, context);
2936
2931
  }
2937
2932
  /**
2938
- * A standalone `URL` node is a GFM autolink (the address part of a real
2939
- * `[text](url)` is handled inside `walkResolvedLink`, not here). Linkify the
2940
- * shapes we recognize; anything else keeps the muted `mdLinkUri`.
2933
+ * Special walker for an angle autolink `<url>`. The unit's `href` lives on
2934
+ * its pack, computed from the `URL` child (`''` when the address is not
2935
+ * linkable, in which case the URL keeps the muted `mdLinkUri`). The children
2936
+ * are only the `<`/`>` marks and the `URL`, consumed right here: the `URL`
2937
+ * is a component of this unit, never a unit of its own.
2938
+ */
2939
+ function walkAutolink(node, parentMarks, text, marks, out) {
2940
+ const urlNode = node.children.find((child) => child.type === LEZER_NODE_IDS.URL);
2941
+ const href = urlNode ? getAutolinkHref(text.slice(urlNode.from, urlNode.to)) ?? "" : "";
2942
+ const base = [...parentMarks, createUnitPack(marks, out, parentMarks, node.from, {
2943
+ key: "link",
2944
+ data: {
2945
+ form: "angle",
2946
+ href
2947
+ },
2948
+ revealInFocus: true
2949
+ })];
2950
+ let pos = node.from;
2951
+ for (const child of node.children) {
2952
+ if (child.from > pos) emit(out, pos, child.from, base);
2953
+ const childMark = child.type === LEZER_NODE_IDS.URL ? href ? marks.mdLinkText.create({ href }) : marks.mdLinkUri.create() : marks.mdMark.create();
2954
+ emit(out, child.from, child.to, [...base, childMark]);
2955
+ pos = child.to;
2956
+ }
2957
+ if (pos < node.to) emit(out, pos, node.to, base);
2958
+ }
2959
+ /**
2960
+ * A `URL` node reaching this walker is a standalone GFM autolink: the address
2961
+ * part of a `[text](url)` link is consumed by `walkResolvedLink`, and an
2962
+ * angle autolink's URL by `walkAutolink`. Linkify the shapes we recognize,
2963
+ * packing each as its own unit; anything else keeps the muted `mdLinkUri`.
2941
2964
  */
2942
2965
  function walkURL(node, parentMarks, text, marks, out) {
2943
2966
  const href = getAutolinkHref(text.slice(node.from, node.to));
2944
- const mark = href ? marks.mdLinkText.create({ href }) : marks.mdLinkUri.create();
2945
- emit(out, node.from, node.to, [...parentMarks, mark]);
2967
+ if (!href) {
2968
+ emit(out, node.from, node.to, [...parentMarks, marks.mdLinkUri.create()]);
2969
+ return;
2970
+ }
2971
+ emit(out, node.from, node.to, [
2972
+ ...parentMarks,
2973
+ createUnitPack(marks, out, parentMarks, node.from, {
2974
+ key: "link",
2975
+ data: {
2976
+ form: "bare",
2977
+ href
2978
+ }
2979
+ }),
2980
+ marks.mdLinkText.create({ href })
2981
+ ]);
2946
2982
  }
2947
2983
  function walkLink(node, parentMarks, text, marks, out, options, context) {
2948
2984
  const parts = scanLinkParts(node);
@@ -2966,6 +3002,9 @@ function walkLink(node, parentMarks, text, marks, out, options, context) {
2966
3002
  * Locate the pieces of a `Link` node in Lezer's flat child list:
2967
3003
  * LinkMark `[`, [label children], LinkMark `]`, LinkMark `(`, URL,
2968
3004
  * optional LinkTitle, LinkMark `)`.
3005
+ *
3006
+ * An autolink inside the label also emits a `URL` child, so only a `URL`
3007
+ * after the second `LinkMark` (the `]` closing the label) is the destination.
2969
3008
  */
2970
3009
  function scanLinkParts(node) {
2971
3010
  let labelFrom = -1;
@@ -2982,7 +3021,7 @@ function scanLinkParts(node) {
2982
3021
  bracketCount++;
2983
3022
  if (bracketCount === 1) labelFrom = child.to;
2984
3023
  if (bracketCount === 2) labelTo = child.from;
2985
- } else if (urlNode == null && childType === LEZER_NODE_IDS.URL) urlNode = child;
3024
+ } else if (urlNode == null && bracketCount >= 2 && childType === LEZER_NODE_IDS.URL) urlNode = child;
2986
3025
  else if (titleNode == null && childType === LEZER_NODE_IDS.LinkTitle) titleNode = child;
2987
3026
  else if (referenceLabelNode == null && childType === LEZER_NODE_IDS.LinkLabel) referenceLabelNode = child;
2988
3027
  }
@@ -3084,9 +3123,9 @@ function walkResolvedLink(node, parts, resolution, parentMarks, text, marks, out
3084
3123
  const pack = createUnitPack(marks, out, parentMarks, node.from, {
3085
3124
  key: "link",
3086
3125
  data: {
3126
+ form: isReference ? "reference" : "inline",
3087
3127
  href,
3088
- title,
3089
- isReference
3128
+ title
3090
3129
  },
3091
3130
  revealInFocus: true
3092
3131
  });
@@ -3118,6 +3157,11 @@ function walkResolvedLink(node, parts, resolution, parentMarks, text, marks, out
3118
3157
  pos = child.to;
3119
3158
  continue;
3120
3159
  }
3160
+ if (child.type === LEZER_NODE_IDS.URL && inLabel(child.from)) {
3161
+ emit(out, child.from, child.to, baseForChild);
3162
+ pos = child.to;
3163
+ continue;
3164
+ }
3121
3165
  const maybeMarkName = MARK_NAME_BY_TYPE_ID.get(child.type);
3122
3166
  const childMarks = maybeMarkName ? [...baseForChild, marks[maybeMarkName].create()] : baseForChild;
3123
3167
  if (child.children.length === 0) emit(out, child.from, child.to, childMarks);
@@ -3709,10 +3753,11 @@ function defineMdMath() {
3709
3753
  }
3710
3754
  /**
3711
3755
  * Wraps a whole inline unit. For a revealable unit (emphasis, strong, code,
3712
- * strikethrough, link, autolink, math) focus mode reveals the unit with one
3713
- * range lookup instead of stitching its punctuation back together; an atom
3714
- * unit (wikilink, image, file) carries it purely as unit identity.
3715
- * `excludes: ''` lets nested units carry two of these marks at once.
3756
+ * strikethrough, inline and angle link, math) focus mode reveals the unit
3757
+ * with one range lookup instead of stitching its punctuation back together;
3758
+ * a unit that never reveals (wikilink, image, file, bare autolink) carries
3759
+ * it as unit identity. `excludes: ''` lets nested units carry two of these
3760
+ * marks at once.
3716
3761
  */
3717
3762
  function defineMdPack() {
3718
3763
  return defineMarkSpec({
@@ -4106,67 +4151,66 @@ function lastMarkRunIn(state, range, markName) {
4106
4151
  * the `mdLinkUri` run locates the `( )` body.
4107
4152
  */
4108
4153
  function getLinkUnitAt(state, pos) {
4109
- const linkText = getMarkRangeAt(state, pos, "mdLinkText");
4110
- const pack = getMarkRangeAt(state, pos, "mdPack", { key: "link" }) ?? getMarkRangeAt(state, pos, "mdPack", { key: "autolink" });
4111
- const unit = pack ?? linkText;
4154
+ const unit = getMarkRangeAt(state, pos, "mdPack", { key: "link" });
4112
4155
  if (!unit) return;
4113
- const packAttrs = pack?.mark.attrs;
4114
- const href = (linkText?.mark.attrs)?.href ?? "";
4115
- if (!pack || packAttrs?.key !== "link") {
4116
- const unitRange = {
4117
- from: unit.from,
4118
- to: unit.to
4156
+ const { data } = unit.mark.attrs;
4157
+ const unitRange = {
4158
+ from: unit.from,
4159
+ to: unit.to
4160
+ };
4161
+ switch (data.form) {
4162
+ case "bare": return {
4163
+ form: "bare",
4164
+ unit: unitRange,
4165
+ text: unitRange,
4166
+ href: data.href,
4167
+ title: ""
4119
4168
  };
4120
- return {
4169
+ case "angle": return {
4170
+ form: "angle",
4121
4171
  unit: unitRange,
4122
- text: packAttrs?.key === "autolink" ? lastMarkRunIn(state, unitRange, "mdLinkText") ?? {
4172
+ text: {
4123
4173
  from: unit.from + 1,
4124
4174
  to: unit.to - 1
4125
- } : unitRange,
4126
- href,
4127
- title: ""
4128
- };
4129
- }
4130
- const linkData = packAttrs.data;
4131
- if (linkData.isReference) {
4132
- const text = linkText == null ? {
4133
- from: unit.from,
4134
- to: unit.to
4135
- } : {
4136
- from: linkText.from + 1,
4137
- to: linkText.to
4138
- };
4139
- return {
4140
- unit: {
4141
- from: unit.from,
4142
- to: unit.to
4143
4175
  },
4144
- text,
4145
- href: linkData.href,
4146
- title: linkData.title
4176
+ href: data.href,
4177
+ title: ""
4147
4178
  };
4179
+ case "reference": {
4180
+ const linkText = getMarkRangeAt(state, pos, "mdLinkText");
4181
+ return {
4182
+ form: "reference",
4183
+ unit: unitRange,
4184
+ text: linkText == null ? unitRange : {
4185
+ from: linkText.from + 1,
4186
+ to: linkText.to
4187
+ },
4188
+ href: data.href,
4189
+ title: data.title
4190
+ };
4191
+ }
4192
+ case "inline": {
4193
+ const uri = lastMarkRunIn(state, unitRange, "mdLinkUri");
4194
+ const closeBracket = uri ? uri.from - 2 : unit.to - 3;
4195
+ const destFrom = uri ? uri.from : unit.to - 1;
4196
+ const label = {
4197
+ from: unit.from + 1,
4198
+ to: closeBracket
4199
+ };
4200
+ return {
4201
+ form: "inline",
4202
+ unit: unitRange,
4203
+ text: label,
4204
+ label,
4205
+ dest: {
4206
+ from: destFrom,
4207
+ to: unit.to - 1
4208
+ },
4209
+ href: data.href,
4210
+ title: data.title
4211
+ };
4212
+ }
4148
4213
  }
4149
- const uri = lastMarkRunIn(state, unit, "mdLinkUri");
4150
- const closeBracket = uri ? uri.from - 2 : unit.to - 3;
4151
- const destFrom = uri ? uri.from : unit.to - 1;
4152
- const label = {
4153
- from: unit.from + 1,
4154
- to: closeBracket
4155
- };
4156
- return {
4157
- unit: {
4158
- from: unit.from,
4159
- to: unit.to
4160
- },
4161
- text: label,
4162
- label,
4163
- dest: {
4164
- from: destFrom,
4165
- to: unit.to - 1
4166
- },
4167
- href: linkData.href,
4168
- title: linkData.title
4169
- };
4170
4214
  }
4171
4215
 
4172
4216
  //#endregion
@@ -4226,7 +4270,7 @@ function insertLink({ href, title, wrapText = true } = {}) {
4226
4270
  function updateLink(attrs) {
4227
4271
  return (state, dispatch) => {
4228
4272
  const link = getLinkUnitAt(state, state.selection.from);
4229
- if (!link?.dest) return false;
4273
+ if (link?.form !== "inline") return false;
4230
4274
  const dest = destText(normalizeHref(attrs.href ?? link.href), attrs.title ?? link.title);
4231
4275
  if (dispatch) dispatch(state.tr.insertText(dest, link.dest.from, link.dest.to).scrollIntoView());
4232
4276
  return true;
@@ -4238,7 +4282,7 @@ function updateLink(attrs) {
4238
4282
  function removeLink() {
4239
4283
  return (state, dispatch) => {
4240
4284
  const link = getLinkUnitAt(state, state.selection.from);
4241
- if (!link?.label) return false;
4285
+ if (link?.form !== "inline") return false;
4242
4286
  if (dispatch) dispatch(state.tr.delete(link.label.to, link.unit.to).delete(link.unit.from, link.label.from).scrollIntoView());
4243
4287
  return true;
4244
4288
  };
@@ -4254,7 +4298,7 @@ function openLinkEdit(onLinkEdit) {
4254
4298
  return (state, dispatch, view) => {
4255
4299
  const link = getLinkUnitAt(state, state.selection.from);
4256
4300
  if (link) {
4257
- if (link.label == null || link.dest == null) return false;
4301
+ if (link.form !== "inline") return false;
4258
4302
  if (dispatch && view) {
4259
4303
  const { unit: { from, to } } = link;
4260
4304
  dispatch(state.tr.setSelection(TextSelection.create(state.doc, from, to)).scrollIntoView());
@@ -4384,11 +4428,13 @@ function defineMeowdownListSerializer() {
4384
4428
  return (...args) => {
4385
4429
  return {
4386
4430
  ...nodesFromSchema(...args),
4387
- list: (node) => listToDOM({
4388
- node,
4389
- nativeList: true,
4390
- getAttributes: getListClipboardAttributes
4391
- })
4431
+ list: (node) => {
4432
+ return listToDOM({
4433
+ node,
4434
+ nativeList: true,
4435
+ getAttributes: getListClipboardAttributes
4436
+ });
4437
+ }
4392
4438
  };
4393
4439
  };
4394
4440
  }
@@ -6386,6 +6432,15 @@ function defineExitBoundaryHandler(onExitBoundary) {
6386
6432
  return withPriority$1(definePlugin(createExitBoundaryPlugin(onExitBoundary)), Priority$1.low);
6387
6433
  }
6388
6434
 
6435
+ //#endregion
6436
+ //#region src/utils/is-mod-event.ts
6437
+ /**
6438
+ * Whether the platform's mod key is held on `event`: `Command` on Apple, `Ctrl` elsewhere.
6439
+ */
6440
+ function isModEvent(event) {
6441
+ return isApple ? event.metaKey : event.ctrlKey;
6442
+ }
6443
+
6389
6444
  //#endregion
6390
6445
  //#region src/extensions/file-click.ts
6391
6446
  const fileClickKey = new PluginKey("meowdown-file-click");
@@ -6394,6 +6449,8 @@ function findFileAt(state, pos) {
6394
6449
  if (!range) return;
6395
6450
  const { href, name } = range.mark.attrs;
6396
6451
  return {
6452
+ from: range.from,
6453
+ to: range.to,
6397
6454
  href,
6398
6455
  name
6399
6456
  };
@@ -6416,7 +6473,8 @@ function defineFileClickHandler(onClick) {
6416
6473
  onClick({
6417
6474
  href: hit.href,
6418
6475
  name: hit.name,
6419
- event
6476
+ event,
6477
+ mod: isModEvent(event)
6420
6478
  });
6421
6479
  return true;
6422
6480
  } }
@@ -6704,6 +6762,126 @@ function defineFileView(options = {}) {
6704
6762
  });
6705
6763
  }
6706
6764
 
6765
+ //#endregion
6766
+ //#region src/extensions/image-click.ts
6767
+ const imageClickKey = new PluginKey("meowdown-image-click");
6768
+ function getClosestImagePreview(target) {
6769
+ return target instanceof HTMLElement && target.closest(".md-image-view-preview");
6770
+ }
6771
+ function findImageAt(state, pos) {
6772
+ const range = getMarkRangeAt(state, pos, "mdImage");
6773
+ if (!range) return;
6774
+ const { src, alt } = range.mark.attrs;
6775
+ return {
6776
+ from: range.from,
6777
+ to: range.to,
6778
+ src,
6779
+ alt
6780
+ };
6781
+ }
6782
+ /**
6783
+ * Resolve the image hit for a preview element via its content holder, not the
6784
+ * event's document position: an event on the non-editable preview lands on the
6785
+ * run boundary, where `getMarkRange` would pick the next adjacent image.
6786
+ */
6787
+ function findImageForPreview(view, preview) {
6788
+ const content = preview.closest(".md-image-view")?.querySelector(".md-image-view-content");
6789
+ if (!content) return;
6790
+ return findImageAt(view.state, view.posAtDOM(content, 0));
6791
+ }
6792
+ /**
6793
+ * Fingers wander a little during a tap; past this it is a scroll or a drag.
6794
+ */
6795
+ const TAP_MOVE_TOLERANCE = 10;
6796
+ function findTouch(touches, identifier) {
6797
+ return Array.from(touches).find((touch) => touch.identifier === identifier);
6798
+ }
6799
+ function isWithinTapTolerance(pending, touch) {
6800
+ return Math.abs(touch.clientX - pending.clientX) <= TAP_MOVE_TOLERANCE && Math.abs(touch.clientY - pending.clientY) <= TAP_MOVE_TOLERANCE;
6801
+ }
6802
+ /**
6803
+ * Call `onClick` when the user clicks or taps a rendered image preview, with
6804
+ * the image's markdown `src`, `alt`, and the originating event.
6805
+ *
6806
+ * Touch taps are handled from `touchend` rather than the synthetic click:
6807
+ * previews live inside the editor contenteditable, and iOS WebKit's
6808
+ * tap-to-focus is a native gesture default action that only cancelling the
6809
+ * `touchend` can suppress — otherwise a tap briefly focuses the editor and
6810
+ * raises the software keyboard before the handler opens its own surface
6811
+ * (such as a lightbox).
6812
+ */
6813
+ function defineImageClickHandler(onClick) {
6814
+ const pendingTaps = /* @__PURE__ */ new WeakMap();
6815
+ const handleTouchEnd = (view, event) => {
6816
+ const pending = pendingTaps.get(view);
6817
+ pendingTaps.delete(view);
6818
+ if (!pending || event.touches.length > 0) return false;
6819
+ const touch = findTouch(event.changedTouches, pending.identifier);
6820
+ if (!touch || !isWithinTapTolerance(pending, touch)) return false;
6821
+ const preview = getClosestImagePreview(event.target);
6822
+ if (!preview) return false;
6823
+ event.preventDefault();
6824
+ const hit = findImageForPreview(view, preview);
6825
+ if (hit) onClick({
6826
+ src: hit.src,
6827
+ alt: hit.alt,
6828
+ event,
6829
+ mod: isModEvent(event)
6830
+ });
6831
+ return true;
6832
+ };
6833
+ return definePlugin(new Plugin({
6834
+ key: imageClickKey,
6835
+ props: {
6836
+ handleDOMEvents: {
6837
+ pointerdown: (view, event) => {
6838
+ if (getClosestImagePreview(event.target) && event.pointerType !== "mouse") event.preventDefault();
6839
+ return false;
6840
+ },
6841
+ touchstart: (view, event) => {
6842
+ pendingTaps.delete(view);
6843
+ if (event.touches.length !== 1) return false;
6844
+ if (!getClosestImagePreview(event.target)) return false;
6845
+ if (event.target instanceof HTMLElement && event.target.closest(".md-image-resize-handle")) return false;
6846
+ const touch = event.changedTouches[0];
6847
+ if (!touch) return false;
6848
+ pendingTaps.set(view, {
6849
+ identifier: touch.identifier,
6850
+ clientX: touch.clientX,
6851
+ clientY: touch.clientY
6852
+ });
6853
+ return false;
6854
+ },
6855
+ touchmove: (view, event) => {
6856
+ const pending = pendingTaps.get(view);
6857
+ if (!pending) return false;
6858
+ const touch = findTouch(event.changedTouches, pending.identifier);
6859
+ if (touch && !isWithinTapTolerance(pending, touch)) pendingTaps.delete(view);
6860
+ return false;
6861
+ },
6862
+ touchcancel: (view) => {
6863
+ pendingTaps.delete(view);
6864
+ return false;
6865
+ },
6866
+ touchend: handleTouchEnd
6867
+ },
6868
+ handleClick: (view, _pos, event) => {
6869
+ const preview = getClosestImagePreview(event.target);
6870
+ if (!preview) return false;
6871
+ const hit = findImageForPreview(view, preview);
6872
+ if (!hit) return false;
6873
+ onClick({
6874
+ src: hit.src,
6875
+ alt: hit.alt,
6876
+ event,
6877
+ mod: isModEvent(event)
6878
+ });
6879
+ return true;
6880
+ }
6881
+ }
6882
+ }));
6883
+ }
6884
+
6707
6885
  //#endregion
6708
6886
  //#region src/extensions/mark-click.ts
6709
6887
  /**
@@ -6757,7 +6935,8 @@ function defineTagClickHandler(onClick) {
6757
6935
  findPayloadAt: (state, pos) => findTagAt(state, pos)?.tag,
6758
6936
  onClick: (tag, event) => onClick({
6759
6937
  tag,
6760
- event
6938
+ event,
6939
+ mod: isModEvent(event)
6761
6940
  })
6762
6941
  });
6763
6942
  }
@@ -6804,7 +6983,8 @@ function defineWikilinkClickHandler(onClick) {
6804
6983
  findPayloadForElement: (view, element) => findWikilinkForElement(view, element)?.target,
6805
6984
  onClick: (target, event) => onClick({
6806
6985
  target,
6807
- event
6986
+ event,
6987
+ mod: isModEvent(event)
6808
6988
  })
6809
6989
  });
6810
6990
  }
@@ -6819,173 +6999,86 @@ function createFollowLinkPlugin(handlers) {
6819
6999
  if (getIsComposing() || event.key !== "Enter" || event.shiftKey) return false;
6820
7000
  const { state } = view;
6821
7001
  const selectedAtom = getSelectedAtomRange(state);
6822
- if (!(isApple ? event.metaKey : event.ctrlKey) && !selectedAtom) return false;
6823
- const pos = selectedAtom ? selectedAtom.from + 1 : state.selection.head;
6824
- const wikilink = handlers.onWikilinkClick && findWikilinkAt(state, pos);
6825
- if (wikilink) {
6826
- handlers.onWikilinkClick?.({
6827
- target: wikilink.target,
6828
- event
6829
- });
6830
- return true;
6831
- }
6832
- const tag = handlers.onTagClick && findTagAt(state, pos);
6833
- if (tag) {
6834
- handlers.onTagClick?.({
6835
- tag: tag.tag,
6836
- event
6837
- });
6838
- return true;
6839
- }
6840
- const file = handlers.onFileClick && findFileAt(state, pos);
6841
- if (file) {
6842
- handlers.onFileClick?.({
6843
- href: file.href,
6844
- name: file.name,
6845
- event
6846
- });
6847
- return true;
6848
- }
6849
- const link = handlers.onLinkClick && getLinkUnitAt(state, pos);
6850
- if (link) {
6851
- handlers.onLinkClick?.({
6852
- href: link.href,
6853
- event
6854
- });
6855
- return true;
6856
- }
7002
+ const mod = isModEvent(event);
7003
+ if (!selectedAtom && !mod) return;
7004
+ if (selectedAtom && handlerAtomMarkTrigger(state, event, handlers, mod, selectedAtom)) return true;
7005
+ if (handlerTextMarkTrigger(state, event, handlers, mod)) return true;
6857
7006
  return false;
6858
7007
  } }
6859
7008
  });
6860
7009
  }
6861
- /**
6862
- * Binds `Mod-Enter` to follow the wikilink, tag, file pill, or Markdown link
6863
- * under the caret, and plain `Enter` to follow a selected atom unit, firing
6864
- * the same handlers a click does. Off a link, `Mod-Enter` falls through so
6865
- * the list keymap keeps cycling checkbox tasks; off a selected unit, `Enter`
6866
- * falls through to the regular split. High priority puts this ahead of every
6867
- * keymap binding.
6868
- */
6869
- function defineFollowLinkHandler(handlers) {
6870
- return withPriority$1(definePlugin(createFollowLinkPlugin(handlers)), Priority$1.high);
6871
- }
6872
-
6873
- //#endregion
6874
- //#region src/extensions/image-click.ts
6875
- const imageClickKey = new PluginKey("meowdown-image-click");
6876
- function getClosestImagePreview(target) {
6877
- return target instanceof HTMLElement && target.closest(".md-image-view-preview");
6878
- }
6879
- function findImageAt(state, pos) {
6880
- const range = getMarkRangeAt(state, pos, "mdImage");
6881
- if (!range) return;
6882
- const { src, alt } = range.mark.attrs;
6883
- return {
6884
- from: range.from,
6885
- to: range.to,
6886
- src,
6887
- alt
6888
- };
6889
- }
6890
- /**
6891
- * Resolve the image hit for a preview element via its content holder, not the
6892
- * event's document position: an event on the non-editable preview lands on the
6893
- * run boundary, where `getMarkRange` would pick the next adjacent image.
6894
- */
6895
- function findImageForPreview(view, preview) {
6896
- const content = preview.closest(".md-image-view")?.querySelector(".md-image-view-content");
6897
- if (!content) return;
6898
- return findImageAt(view.state, view.posAtDOM(content, 0));
6899
- }
6900
- /**
6901
- * Fingers wander a little during a tap; past this it is a scroll or a drag.
6902
- */
6903
- const TAP_MOVE_TOLERANCE = 10;
6904
- function findTouch(touches, identifier) {
6905
- return Array.from(touches).find((touch) => touch.identifier === identifier);
7010
+ function handlerAtomMarkTrigger(state, event, handlers, mod, selectedAtom) {
7011
+ const pos = selectedAtom.from + 1;
7012
+ const { onWikilinkClick, onFileClick, onImageClick } = handlers;
7013
+ const wikilink = onWikilinkClick && findWikilinkAt(state, pos);
7014
+ if (wikilink) {
7015
+ onWikilinkClick({
7016
+ target: wikilink.target,
7017
+ event,
7018
+ mod
7019
+ });
7020
+ return true;
7021
+ }
7022
+ const file = onFileClick && findFileAt(state, pos);
7023
+ if (file) {
7024
+ onFileClick({
7025
+ href: file.href,
7026
+ name: file.name,
7027
+ event,
7028
+ mod
7029
+ });
7030
+ return true;
7031
+ }
7032
+ const image = onImageClick && findImageAt(state, pos);
7033
+ if (image) {
7034
+ onImageClick({
7035
+ src: image.src,
7036
+ alt: image.alt,
7037
+ event,
7038
+ mod
7039
+ });
7040
+ return true;
7041
+ }
6906
7042
  }
6907
- function isWithinTapTolerance(pending, touch) {
6908
- return Math.abs(touch.clientX - pending.clientX) <= TAP_MOVE_TOLERANCE && Math.abs(touch.clientY - pending.clientY) <= TAP_MOVE_TOLERANCE;
7043
+ function handlerTextMarkTrigger(state, event, handlers, mod) {
7044
+ const pos = state.selection.head;
7045
+ const { onTagClick, onLinkClick } = handlers;
7046
+ const tag = onTagClick && findTagAt(state, pos);
7047
+ if (tag && tag.from < pos && pos < tag.to) {
7048
+ onTagClick({
7049
+ tag: tag.tag,
7050
+ event,
7051
+ mod
7052
+ });
7053
+ return true;
7054
+ }
7055
+ const link = onLinkClick && getLinkUnitAt(state, pos);
7056
+ if (link && link.unit.from < pos && pos < link.unit.to) {
7057
+ onLinkClick({
7058
+ href: link.href,
7059
+ event,
7060
+ mod
7061
+ });
7062
+ return true;
7063
+ }
6909
7064
  }
6910
7065
  /**
6911
- * Call `onClick` when the user clicks or taps a rendered image preview, with
6912
- * the image's markdown `src`, `alt`, and the originating event.
7066
+ * Binds `Mod-Enter` to follow the wikilink, tag, file pill, or Markdown link
7067
+ * under the caret, and plain `Enter` to follow a selected atom unit (a
7068
+ * wikilink, file pill, or image), firing the same handlers a click does.
7069
+ * "Under the caret" means strictly inside the unit: a caret merely touching
7070
+ * a unit's edge is next to it, not on it.
7071
+ * Off a link, `Mod-Enter` falls through so the list keymap keeps cycling
7072
+ * checkbox tasks; off a selected unit, `Enter` falls through to the regular
7073
+ * split. High priority puts this ahead of every keymap binding.
6913
7074
  *
6914
- * Touch taps are handled from `touchend` rather than the synthetic click:
6915
- * previews live inside the editor contenteditable, and iOS WebKit's
6916
- * tap-to-focus is a native gesture default action that only cancelling the
6917
- * `touchend` can suppress — otherwise a tap briefly focuses the editor and
6918
- * raises the software keyboard before the handler opens its own surface
6919
- * (such as a lightbox).
7075
+ * A selected-unit follow reports `mod: true` when the platform's mod key
7076
+ * (`⌘` on Apple, `Ctrl` elsewhere) was held beyond its plain-`Enter` trigger;
7077
+ * a caret follow always reports `mod: false`, its mod key being the trigger
7078
+ * itself.
6920
7079
  */
6921
- function defineImageClickHandler(onClick) {
6922
- const pendingTaps = /* @__PURE__ */ new WeakMap();
6923
- const handleTouchEnd = (view, event) => {
6924
- const pending = pendingTaps.get(view);
6925
- pendingTaps.delete(view);
6926
- if (!pending || event.touches.length > 0) return false;
6927
- const touch = findTouch(event.changedTouches, pending.identifier);
6928
- if (!touch || !isWithinTapTolerance(pending, touch)) return false;
6929
- const preview = getClosestImagePreview(event.target);
6930
- if (!preview) return false;
6931
- event.preventDefault();
6932
- const hit = findImageForPreview(view, preview);
6933
- if (hit) onClick({
6934
- src: hit.src,
6935
- alt: hit.alt,
6936
- event
6937
- });
6938
- return true;
6939
- };
6940
- return definePlugin(new Plugin({
6941
- key: imageClickKey,
6942
- props: {
6943
- handleDOMEvents: {
6944
- pointerdown: (view, event) => {
6945
- if (getClosestImagePreview(event.target) && event.pointerType !== "mouse") event.preventDefault();
6946
- return false;
6947
- },
6948
- touchstart: (view, event) => {
6949
- pendingTaps.delete(view);
6950
- if (event.touches.length !== 1) return false;
6951
- if (!getClosestImagePreview(event.target)) return false;
6952
- if (event.target instanceof HTMLElement && event.target.closest(".md-image-resize-handle")) return false;
6953
- const touch = event.changedTouches[0];
6954
- if (!touch) return false;
6955
- pendingTaps.set(view, {
6956
- identifier: touch.identifier,
6957
- clientX: touch.clientX,
6958
- clientY: touch.clientY
6959
- });
6960
- return false;
6961
- },
6962
- touchmove: (view, event) => {
6963
- const pending = pendingTaps.get(view);
6964
- if (!pending) return false;
6965
- const touch = findTouch(event.changedTouches, pending.identifier);
6966
- if (touch && !isWithinTapTolerance(pending, touch)) pendingTaps.delete(view);
6967
- return false;
6968
- },
6969
- touchcancel: (view) => {
6970
- pendingTaps.delete(view);
6971
- return false;
6972
- },
6973
- touchend: handleTouchEnd
6974
- },
6975
- handleClick: (view, _pos, event) => {
6976
- const preview = getClosestImagePreview(event.target);
6977
- if (!preview) return false;
6978
- const hit = findImageForPreview(view, preview);
6979
- if (!hit) return false;
6980
- onClick({
6981
- src: hit.src,
6982
- alt: hit.alt,
6983
- event
6984
- });
6985
- return true;
6986
- }
6987
- }
6988
- }));
7080
+ function defineFollowLinkHandler(handlers) {
7081
+ return withPriority$1(definePlugin(createFollowLinkPlugin(handlers)), Priority$1.high);
6989
7082
  }
6990
7083
 
6991
7084
  //#endregion
@@ -7315,7 +7408,8 @@ function defineLinkClickHandler(onClick) {
7315
7408
  findPayloadAt: (state, pos) => getLinkUnitAt(state, pos)?.href,
7316
7409
  onClick: (href, event) => onClick({
7317
7410
  href,
7318
- event
7411
+ event,
7412
+ mod: isModEvent(event)
7319
7413
  })
7320
7414
  });
7321
7415
  }
@@ -8043,4 +8137,4 @@ function getVirtualElementFromRange(view, range) {
8043
8137
  }
8044
8138
 
8045
8139
  //#endregion
8046
- export { EDITOR_KEY_BINDINGS, Priority, buildFileMarkdown, checkRoundTrip, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineCodeBlockSyntaxHighlight, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLComment, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkCommands, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMath, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSpellCheckPlugin, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getFileKind, getIsComposing, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isMarkOfType, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, parseWikiEmbed, removeLink, renderMathInto, updateLink, wikiEmbedBasename, withPriority };
8140
+ export { EDITOR_KEY_BINDINGS, Priority, buildFileMarkdown, checkRoundTrip, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineCodeBlockSyntaxHighlight, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLComment, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkCommands, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMath, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSpellCheckPlugin, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getFileKind, getIsComposing, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isMarkOfType, isModEvent, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, parseWikiEmbed, removeLink, renderMathInto, 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.64.2",
4
+ "version": "0.65.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -43,7 +43,7 @@
43
43
  "remark-stringify": "^11.0.0",
44
44
  "unicode-by-name": "^0.2.0",
45
45
  "unified": "^11.0.5",
46
- "@meowdown/markdown": "^0.64.2"
46
+ "@meowdown/markdown": "^0.65.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@ocavue/tsconfig": "^0.7.1",