@meowdown/core 0.64.1 → 0.64.2

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 +33 -17
  2. package/dist/index.js +113 -111
  3. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -246,31 +246,47 @@ interface MdMathAttrs {
246
246
  formula: string;
247
247
  }
248
248
  /**
249
- * mdPack keys for units that store no extra data; their own marks carry it.
250
- */
251
- type MdPackSimpleKey = 'bold' | 'italic' | 'code' | 'strike' | 'highlight' | 'autolink' | 'math' | 'wikilink' | 'image' | 'file';
252
- /**
253
- * Content-derived identity of one inline syntax unit. Adjacent units of the
254
- * same kind are kept apart by it (so they do not merge into one mark run), and
255
- * it stays stable when unrelated text in the block is edited, so editing one
256
- * unit never re-marks the others. `data` carries the unit's parsed payload (a
257
- * link's `href`/`title`) so callers read it off the mark instead of re-parsing
258
- * the text. The parser sets `slot: 1` on a unit whose pack would otherwise
259
- * equal the pack of the unit ending exactly where it starts; equal packs would
260
- * merge the two units into one mark run and one mark view.
261
- */
262
- type MdPackAttrs = ({
249
+ * Content-derived identity of one inline syntax unit.
250
+ *
251
+ * - `key`: the unit's kind. It keeps adjacent same-kind units apart and stays
252
+ * stable when unrelated text in the block is edited, so editing one unit
253
+ * never re-marks the others.
254
+ * - `data`: the unit's parsed data, read off the mark instead of
255
+ * re-parsing the text.
256
+ * - `slot`: `1` when the pack would otherwise equal the pack of the unit
257
+ * ending exactly where this one starts; equal packs would merge the two
258
+ * units into one mark run and one mark view.
259
+ * - `revealInFocus`/`revealInHide`: the mark modes in which the unit's
260
+ * source reveals around the caret.
261
+ */
262
+ type MdPackAttrs = {
263
+ key: 'italic' | 'bold' | 'code' | 'del' | 'highlight' | 'autolink';
264
+ data?: null;
265
+ slot?: 1 | null;
266
+ revealInFocus: true;
267
+ revealInHide?: null;
268
+ } | {
263
269
  key: 'link';
264
270
  data: {
265
271
  href: string;
266
272
  title: string;
267
- reference?: true;
273
+ isReference: boolean;
268
274
  };
275
+ slot?: 1 | null;
276
+ revealInFocus: true;
277
+ revealInHide?: null;
278
+ } | {
279
+ key: 'math';
280
+ data?: null;
281
+ slot?: 1 | null;
282
+ revealInFocus: true;
283
+ revealInHide: true;
269
284
  } | {
270
- key: MdPackSimpleKey;
285
+ key: 'wikilink' | 'image' | 'file';
271
286
  data?: null;
272
- }) & {
273
287
  slot?: 1 | null;
288
+ revealInFocus?: null;
289
+ revealInHide?: null;
274
290
  };
275
291
  //#endregion
276
292
  //#region src/utils/range.d.ts
package/dist/index.js CHANGED
@@ -88,22 +88,25 @@ function hasPointerSelectionTransaction(transactions) {
88
88
  function isMarkOfType(mark, name) {
89
89
  return mark.type.name === name;
90
90
  }
91
- const SYNTAX_MARK_NAMES = /* @__PURE__ */ new Set([
91
+ function isMarkOfTypes(mark, names) {
92
+ return names.includes(mark.type.name);
93
+ }
94
+ const SYNTAX_MARK_NAMES = [
92
95
  "mdMark",
93
96
  "mdLinkUri",
94
97
  "mdLinkTitle"
95
- ]);
96
- const ATOM_MARK_NAMES = /* @__PURE__ */ new Set([
98
+ ];
99
+ const ATOM_MARK_NAMES = [
97
100
  "mdWikilink",
98
101
  "mdImage",
99
102
  "mdFile",
100
103
  "mdMath"
101
- ]);
102
- const ATOM_PACK_KEYS = /* @__PURE__ */ new Set([
103
- "wikilink",
104
- "image",
105
- "file"
106
- ]);
104
+ ];
105
+ const ATOM_SOURCE_MARK_NAMES = [
106
+ "mdImage",
107
+ "mdWikilink",
108
+ "mdFile"
109
+ ];
107
110
 
108
111
  //#endregion
109
112
  //#region src/extensions/mark-mode.ts
@@ -123,9 +126,8 @@ function createMarkModePlugin(initialMode) {
123
126
  return { "data-mark-mode": getCurrentMarkMode(state) ?? initialMode };
124
127
  },
125
128
  decorations: (state) => {
126
- const mode = getCurrentMarkMode(state);
127
- if (mode === "focus") return computeFocusDecorations(state);
128
- if (mode === "hide") return computeMathRevealDecorations(state);
129
+ const mode = getCurrentMarkMode(state) ?? initialMode;
130
+ return mode === "focus" || mode === "hide" ? computeRevealDecorations(state, mode) : void 0;
129
131
  }
130
132
  }
131
133
  });
@@ -137,9 +139,6 @@ function setMarkMode(mode) {
137
139
  return true;
138
140
  };
139
141
  }
140
- function defineMarkMode(mode) {
141
- return union(definePlugin(createMarkModePlugin(mode)), defineCommands({ setMarkMode }));
142
- }
143
142
  /**
144
143
  * The active mark mode. `defineEditorExtension` always applies
145
144
  * `defineMarkMode`, so this is `undefined` only for a state built without it.
@@ -147,51 +146,49 @@ function defineMarkMode(mode) {
147
146
  function getMarkMode(state) {
148
147
  return markModeKey.getState(state);
149
148
  }
150
- /**
151
- * In focus mode, reveal the markdown syntax of the inline unit under the caret.
152
- *
153
- * Every revealable unit (emphasis, strong, code, strikethrough, link, autolink,
154
- * image) carries one `mdPack` mark spanning it, so a single boundary-inclusive
155
- * `getMarkRange` finds the unit, returning the outermost when units nest. One
156
- * decoration over its range flips the hidden punctuation/url/source visible via
157
- * the `.show` CSS rule. Because the range covers the whole unit, a caret at
158
- * either edge (e.g. right after a link's `)`) still reveals it. Atom packs
159
- * (`ATOM_PACK_KEYS`) and `#tag` never reveal.
160
- */
161
- function computeFocusDecorations(state) {
162
- return computeRevealDecorations(state, void 0);
163
- }
164
- /**
165
- * In hide mode, reveal only the math unit under the caret. A math unit hides
166
- * its whole source (content included), so it is the one construct that must
167
- * still reveal in hide mode to stay editable; everything else follows the
168
- * hide-mode contract and never reveals.
169
- */
170
- function computeMathRevealDecorations(state) {
171
- return computeRevealDecorations(state, { key: "math" });
172
- }
173
- function computeRevealDecorations(state, packAttrs) {
174
- const { selection } = state;
175
- if (!selection.empty) return DecorationSet.empty;
176
- const $pos = selection.$head;
149
+ function findRevealablePack($pos, mode, direction) {
177
150
  const { parent } = $pos;
178
- if (!parent.isTextblock || parent.type.spec.code) return DecorationSet.empty;
179
- const range = getRevealablePackRange(state, $pos, packAttrs);
180
- if (!range) return DecorationSet.empty;
181
- return DecorationSet.create(state.doc, [Decoration.inline(range.from, range.to, { class: "show" })]);
151
+ if (!parent.isTextblock || parent.type.spec.code) return;
152
+ const node = direction === -1 ? $pos.nodeBefore : $pos.nodeAfter;
153
+ for (const mark of node?.marks ?? []) if (isMarkOfType(mark, "mdPack")) {
154
+ const attrs = mark.attrs;
155
+ if (mode === "focus" && attrs.revealInFocus || mode === "hide" && attrs.revealInHide) return mark;
156
+ }
182
157
  }
183
- function isAtomPack(mark) {
184
- return ATOM_PACK_KEYS.has(mark.attrs.key);
158
+ /**
159
+ * Reveal the markdown syntax of the inline units the selection touches: the
160
+ * unit before its start and the unit after its end, which for a caret are
161
+ * the two units meeting at it.
162
+ *
163
+ * Every unit carries one `mdPack` mark spanning it, so one `getMarkRange`
164
+ * per probed pack expands it to its whole unit, and one decoration over
165
+ * each range flips the hidden punctuation/url/source visible via the `.show`
166
+ * CSS rule. The two probes land on the same unit when the selection sits
167
+ * inside one (equal ranges dedupe to one decoration), and on two units at a
168
+ * shared boundary, so the characters an edit would touch are never hidden.
169
+ * `#tag` carries no pack and never reveals.
170
+ */
171
+ function computeRevealDecorations(state, mode) {
172
+ const { $from, $to } = state.selection;
173
+ const packBefore = findRevealablePack($from, mode, -1);
174
+ const packAfter = findRevealablePack($to, mode, 1);
175
+ const ranges = [];
176
+ for (const [$pos, pack] of [[$from, packBefore], [$to, packAfter]]) {
177
+ if (!pack) continue;
178
+ const range = getMarkRange($pos, "mdPack", pack.attrs);
179
+ if (!range) continue;
180
+ if (ranges.some((r) => r.from === range.from && r.to === range.to)) continue;
181
+ ranges.push({
182
+ from: range.from,
183
+ to: range.to
184
+ });
185
+ }
186
+ if (ranges.length === 0) return;
187
+ const decorations = ranges.map((range) => Decoration.inline(range.from, range.to, { class: "show" }));
188
+ return DecorationSet.create(state.doc, decorations);
185
189
  }
186
- function getRevealablePackRange(state, $pos, packAttrs) {
187
- const packType = getMarkType(state.schema, "mdPack");
188
- const range = getMarkRange($pos, packType, packAttrs);
189
- if (!range) return;
190
- if (!isAtomPack(range.mark)) return range;
191
- if ($pos.parentOffset === 0) return;
192
- const before = getMarkRange(state.doc.resolve($pos.pos - 1), packType, packAttrs);
193
- if (!before || before.to !== $pos.pos || isAtomPack(before.mark)) return;
194
- return before;
190
+ function defineMarkMode(mode) {
191
+ return union(definePlugin(createMarkModePlugin(mode)), defineCommands({ setMarkMode }));
195
192
  }
196
193
 
197
194
  //#endregion
@@ -206,7 +203,7 @@ function getCharMarks(state, pos) {
206
203
  function isHiddenChar(state, pos) {
207
204
  const marks = getCharMarks(state, pos);
208
205
  if (marks == null) return false;
209
- return marks.some((mark) => SYNTAX_MARK_NAMES.has(mark.type.name));
206
+ return marks.some((mark) => isMarkOfTypes(mark, SYNTAX_MARK_NAMES));
210
207
  }
211
208
  function isInsideNonCodeTextblock(state, pos) {
212
209
  if (pos < 0 || pos > state.doc.content.size) return false;
@@ -360,28 +357,30 @@ function getMarkRangeAt(state, pos, markName, attrs) {
360
357
  if (!$pos) return;
361
358
  const markNames = Array.isArray(markName) ? markName : [markName];
362
359
  for (const name of markNames) {
363
- const range = getMarkRange($pos, name, attrs);
364
- if (range) return ATOM_MARK_NAMES.has(name) ? clampToUnitRange(state, $pos, range) : range;
360
+ const range = ATOM_MARK_NAMES.includes(name) ? getAtomUnitRange(state, $pos, name) : getMarkRange($pos, name, attrs);
361
+ if (range) return range;
365
362
  }
366
363
  }
367
364
  /**
368
- * Two identical adjacent units carry equal atom marks (only their packs differ,
369
- * by `slot`), so `getMarkRange`'s eq-based expansion runs across both. Clamp
370
- * the range to the matched child's own unit: the innermost pack covering its
371
- * first character. A child without a pack (a document not produced by the
372
- * inline parser) keeps the unclamped range.
365
+ * The range of the atom unit touching `$pos`, preferring the child to the
366
+ * right like `getMarkRange`. Two identical adjacent units carry equal atom
367
+ * marks, so eq-based expansion would run across both; the unit boundary comes
368
+ * from the innermost pack instead, which `slot` keeps distinct. A text node
369
+ * without a pack (a document not produced by the inline parser) falls back to
370
+ * the eq-based range. Does not support `attrs` filtering.
373
371
  */
374
- function clampToUnitRange(state, $pos, range) {
372
+ function getAtomUnitRange(state, $pos, name) {
375
373
  const parent = $pos.parent;
376
374
  const after = parent.childAfter($pos.parentOffset);
377
- const matched = after.node && range.mark.isInSet(after.node.marks) ? after : parent.childBefore($pos.parentOffset);
378
- if (!matched.node) return range;
375
+ const matched = after.node && after.node.marks.some((mark) => isMarkOfType(mark, name)) ? after : parent.childBefore($pos.parentOffset);
376
+ const mark = matched.node?.marks.find((candidate) => isMarkOfType(candidate, name));
377
+ if (!matched.node || mark == null) return;
379
378
  const pack = getInnermostPackRangeAt(state, $pos.start() + matched.offset);
380
- if (!pack) return range;
379
+ if (!pack) return getMarkRange($pos, name);
381
380
  return {
382
- from: Math.max(range.from, pack.from),
383
- to: Math.min(range.to, pack.to),
384
- mark: range.mark
381
+ from: pack.from,
382
+ to: pack.to,
383
+ mark
385
384
  };
386
385
  }
387
386
  /**
@@ -424,15 +423,6 @@ function getMarkRangeStrictlyAround(state, pos, markNames) {
424
423
 
425
424
  //#endregion
426
425
  //#region src/extensions/atom-mark-navigation.ts
427
- /**
428
- * The source marks whose mark views hide the raw text behind a rendered
429
- * preview (`.md-atom-view-preview`) and act as one caret stop.
430
- */
431
- const ATOM_SOURCE_MARK_NAMES = [
432
- "mdImage",
433
- "mdWikilink",
434
- "mdFile"
435
- ];
436
426
  function getActiveMarkNames(marks, state) {
437
427
  const mode = getMarkMode(state);
438
428
  if (!mode) return [];
@@ -452,7 +442,7 @@ function getSelectedRange(state, markNames) {
452
442
  */
453
443
  function getSelectedAtomRange(state) {
454
444
  if (!getMarkMode(state)) return;
455
- return getSelectedRange(state, [...ATOM_SOURCE_MARK_NAMES]);
445
+ return getSelectedRange(state, ATOM_SOURCE_MARK_NAMES);
456
446
  }
457
447
  function findSelectionAcrossBlockBoundary(state, pos, markNames, direction) {
458
448
  const $pos = state.doc.resolve(pos);
@@ -789,13 +779,13 @@ function parsePositiveInteger(raw) {
789
779
  //#endregion
790
780
  //#region src/extensions/inline-runs.ts
791
781
  function findAtomMark(marks) {
792
- return marks.find((mark) => ATOM_MARK_NAMES.has(mark.type.name));
782
+ return marks.find((mark) => isMarkOfTypes(mark, ATOM_MARK_NAMES));
793
783
  }
794
784
  function findOwnPackMark(marks) {
795
785
  return marks.findLast((mark) => isMarkOfType(mark, "mdPack"));
796
786
  }
797
787
  function hasSyntaxMark(marks) {
798
- return marks.some((mark) => SYNTAX_MARK_NAMES.has(mark.type.name));
788
+ return marks.some((mark) => isMarkOfTypes(mark, SYNTAX_MARK_NAMES));
799
789
  }
800
790
  /**
801
791
  * Group a textblock's text nodes into atom units and plain runs. A unit's
@@ -2851,6 +2841,17 @@ const MARK_NAME_BY_TYPE_ID = /* @__PURE__ */ new Map([
2851
2841
  [LEZER_NODE_IDS.Hashtag, "mdTag"],
2852
2842
  [LEZER_NODE_IDS.WikilinkMark, "mdMark"]
2853
2843
  ]);
2844
+ function getGenericPackKey(type) {
2845
+ switch (type) {
2846
+ case LEZER_NODE_IDS.Emphasis: return "italic";
2847
+ case LEZER_NODE_IDS.StrongEmphasis: return "bold";
2848
+ case LEZER_NODE_IDS.InlineCode: return "code";
2849
+ case LEZER_NODE_IDS.Strikethrough: return "del";
2850
+ case LEZER_NODE_IDS.Highlight: return "highlight";
2851
+ case LEZER_NODE_IDS.Autolink: return "autolink";
2852
+ default: return;
2853
+ }
2854
+ }
2854
2855
  /**
2855
2856
  * Walk a textblock's inline content and produce a list of mark chunks
2856
2857
  * with positions relative to the start of `text` (i.e. zero-based).
@@ -2922,16 +2923,13 @@ function walkNode(node, parentMarks, text, marks, out, options, context) {
2922
2923
  * syntax marks, then recurses into its children.
2923
2924
  */
2924
2925
  function walkGenericNode(node, parentMarks, text, marks, out, options, context) {
2925
- const type = node.type;
2926
- let packKey;
2927
- if (type === LEZER_NODE_IDS.Emphasis) packKey = "italic";
2928
- else if (type === LEZER_NODE_IDS.StrongEmphasis) packKey = "bold";
2929
- else if (type === LEZER_NODE_IDS.InlineCode) packKey = "code";
2930
- else if (type === LEZER_NODE_IDS.Strikethrough) packKey = "strike";
2931
- else if (type === LEZER_NODE_IDS.Highlight) packKey = "highlight";
2932
- else if (type === LEZER_NODE_IDS.Autolink) packKey = "autolink";
2933
- const base = packKey ? [...parentMarks, createUnitPack(marks, out, parentMarks, node.from, { key: packKey })] : parentMarks;
2934
- const maybeMarkName = MARK_NAME_BY_TYPE_ID.get(type);
2926
+ const packKey = getGenericPackKey(node.type);
2927
+ const packMark = packKey && createUnitPack(marks, out, parentMarks, node.from, {
2928
+ key: packKey,
2929
+ revealInFocus: true
2930
+ });
2931
+ const base = packMark ? [...parentMarks, packMark] : parentMarks;
2932
+ const maybeMarkName = MARK_NAME_BY_TYPE_ID.get(node.type);
2935
2933
  const childMarks = maybeMarkName ? [...base, marks[maybeMarkName].create()] : base;
2936
2934
  if (node.children.length === 0) emit(out, node.from, node.to, childMarks);
2937
2935
  else walk(node.children, childMarks, node.from, node.to, text, marks, out, options, context);
@@ -3083,17 +3081,14 @@ function walkResolvedLink(node, parts, resolution, parentMarks, text, marks, out
3083
3081
  const { href, title, isReference } = resolution;
3084
3082
  const linkTextMark = marks.mdLinkText.create({ href });
3085
3083
  const inLabel = (pos) => labelEnd >= 0 && pos < labelEnd;
3086
- const data = isReference ? {
3087
- href,
3088
- title,
3089
- reference: true
3090
- } : {
3091
- href,
3092
- title
3093
- };
3094
3084
  const pack = createUnitPack(marks, out, parentMarks, node.from, {
3095
3085
  key: "link",
3096
- data
3086
+ data: {
3087
+ href,
3088
+ title,
3089
+ isReference
3090
+ },
3091
+ revealInFocus: true
3097
3092
  });
3098
3093
  const base = [...parentMarks, pack];
3099
3094
  let pos = node.from;
@@ -3192,7 +3187,11 @@ function walkMath(node, parentMarks, text, marks, out) {
3192
3187
  const formula = text.slice(markNodes[0].to, markNodes[1].from);
3193
3188
  const base = [
3194
3189
  ...parentMarks,
3195
- createUnitPack(marks, out, parentMarks, node.from, { key: "math" }),
3190
+ createUnitPack(marks, out, parentMarks, node.from, {
3191
+ key: "math",
3192
+ revealInFocus: true,
3193
+ revealInHide: true
3194
+ }),
3196
3195
  marks.mdMath.create({ formula })
3197
3196
  ];
3198
3197
  emit(out, node.from, markNodes[0].to, [...base, marks.mdMark.create()]);
@@ -3723,7 +3722,9 @@ function defineMdPack() {
3723
3722
  attrs: {
3724
3723
  key: {},
3725
3724
  data: { default: null },
3726
- slot: { default: null }
3725
+ slot: { default: null },
3726
+ revealInFocus: { default: null },
3727
+ revealInHide: { default: null }
3727
3728
  },
3728
3729
  toDOM: (mark) => {
3729
3730
  return [
@@ -4126,7 +4127,8 @@ function getLinkUnitAt(state, pos) {
4126
4127
  title: ""
4127
4128
  };
4128
4129
  }
4129
- if (packAttrs.data.reference === true) {
4130
+ const linkData = packAttrs.data;
4131
+ if (linkData.isReference) {
4130
4132
  const text = linkText == null ? {
4131
4133
  from: unit.from,
4132
4134
  to: unit.to
@@ -4140,8 +4142,8 @@ function getLinkUnitAt(state, pos) {
4140
4142
  to: unit.to
4141
4143
  },
4142
4144
  text,
4143
- href: packAttrs.data.href,
4144
- title: packAttrs.data.title
4145
+ href: linkData.href,
4146
+ title: linkData.title
4145
4147
  };
4146
4148
  }
4147
4149
  const uri = lastMarkRunIn(state, unit, "mdLinkUri");
@@ -4162,8 +4164,8 @@ function getLinkUnitAt(state, pos) {
4162
4164
  from: destFrom,
4163
4165
  to: unit.to - 1
4164
4166
  },
4165
- href: packAttrs.data.href,
4166
- title: packAttrs.data.title
4167
+ href: linkData.href,
4168
+ title: linkData.title
4167
4169
  };
4168
4170
  }
4169
4171
 
@@ -7900,7 +7902,7 @@ function cleanTextFromSlice(slice, options = {}) {
7900
7902
  blockNode.descendants((textNode) => {
7901
7903
  if (!textNode.isText || !textNode.text) return true;
7902
7904
  const textNodeMarks = textNode.marks.map((mark) => mark.type.name);
7903
- if (!(textNodeMarks.some((markName) => SYNTAX_MARK_NAMES.has(markName)) && !(options.preserveMathSource && textNodeMarks.includes("mdMath")))) parts.push(textNode.text);
7905
+ if (!(textNodeMarks.some((markName) => SYNTAX_MARK_NAMES.includes(markName)) && !(options.preserveMathSource && textNodeMarks.includes("mdMath")))) parts.push(textNode.text);
7904
7906
  return false;
7905
7907
  });
7906
7908
  blocks.push(parts.join(""));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/core",
3
3
  "type": "module",
4
- "version": "0.64.1",
4
+ "version": "0.64.2",
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.1"
46
+ "@meowdown/markdown": "^0.64.2"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@ocavue/tsconfig": "^0.7.1",