@meowdown/core 0.64.0 → 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.
- package/dist/index.d.ts +340 -117
- package/dist/index.js +577 -342
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -83,6 +83,31 @@ function hasPointerSelectionTransaction(transactions) {
|
|
|
83
83
|
return transactions.some(isPointerSelectionTransaction);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/extensions/mark-names.ts
|
|
88
|
+
function isMarkOfType(mark, name) {
|
|
89
|
+
return mark.type.name === name;
|
|
90
|
+
}
|
|
91
|
+
function isMarkOfTypes(mark, names) {
|
|
92
|
+
return names.includes(mark.type.name);
|
|
93
|
+
}
|
|
94
|
+
const SYNTAX_MARK_NAMES = [
|
|
95
|
+
"mdMark",
|
|
96
|
+
"mdLinkUri",
|
|
97
|
+
"mdLinkTitle"
|
|
98
|
+
];
|
|
99
|
+
const ATOM_MARK_NAMES = [
|
|
100
|
+
"mdWikilink",
|
|
101
|
+
"mdImage",
|
|
102
|
+
"mdFile",
|
|
103
|
+
"mdMath"
|
|
104
|
+
];
|
|
105
|
+
const ATOM_SOURCE_MARK_NAMES = [
|
|
106
|
+
"mdImage",
|
|
107
|
+
"mdWikilink",
|
|
108
|
+
"mdFile"
|
|
109
|
+
];
|
|
110
|
+
|
|
86
111
|
//#endregion
|
|
87
112
|
//#region src/extensions/mark-mode.ts
|
|
88
113
|
const markModeKey = new PluginKey("mark-mode");
|
|
@@ -101,9 +126,8 @@ function createMarkModePlugin(initialMode) {
|
|
|
101
126
|
return { "data-mark-mode": getCurrentMarkMode(state) ?? initialMode };
|
|
102
127
|
},
|
|
103
128
|
decorations: (state) => {
|
|
104
|
-
const mode = getCurrentMarkMode(state);
|
|
105
|
-
|
|
106
|
-
if (mode === "hide") return computeMathRevealDecorations(state);
|
|
129
|
+
const mode = getCurrentMarkMode(state) ?? initialMode;
|
|
130
|
+
return mode === "focus" || mode === "hide" ? computeRevealDecorations(state, mode) : void 0;
|
|
107
131
|
}
|
|
108
132
|
}
|
|
109
133
|
});
|
|
@@ -115,9 +139,6 @@ function setMarkMode(mode) {
|
|
|
115
139
|
return true;
|
|
116
140
|
};
|
|
117
141
|
}
|
|
118
|
-
function defineMarkMode(mode) {
|
|
119
|
-
return union(definePlugin(createMarkModePlugin(mode)), defineCommands({ setMarkMode }));
|
|
120
|
-
}
|
|
121
142
|
/**
|
|
122
143
|
* The active mark mode. `defineEditorExtension` always applies
|
|
123
144
|
* `defineMarkMode`, so this is `undefined` only for a state built without it.
|
|
@@ -125,38 +146,190 @@ function defineMarkMode(mode) {
|
|
|
125
146
|
function getMarkMode(state) {
|
|
126
147
|
return markModeKey.getState(state);
|
|
127
148
|
}
|
|
149
|
+
function findRevealablePack($pos, mode, direction) {
|
|
150
|
+
const { parent } = $pos;
|
|
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
|
+
}
|
|
157
|
+
}
|
|
128
158
|
/**
|
|
129
|
-
*
|
|
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.
|
|
130
162
|
*
|
|
131
|
-
* Every
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* `#tag`
|
|
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);
|
|
189
|
+
}
|
|
190
|
+
function defineMarkMode(mode) {
|
|
191
|
+
return union(definePlugin(createMarkModePlugin(mode)), defineCommands({ setMarkMode }));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/extensions/hidden-run.ts
|
|
196
|
+
function getCharMarks(state, pos) {
|
|
197
|
+
if (pos < 0 || pos + 1 > state.doc.content.size) return;
|
|
198
|
+
const $pos = state.doc.resolve(pos);
|
|
199
|
+
const child = $pos.parent.maybeChild($pos.index());
|
|
200
|
+
if (child == null || !child.isText) return;
|
|
201
|
+
return child.marks;
|
|
202
|
+
}
|
|
203
|
+
function isHiddenChar(state, pos) {
|
|
204
|
+
const marks = getCharMarks(state, pos);
|
|
205
|
+
if (marks == null) return false;
|
|
206
|
+
return marks.some((mark) => isMarkOfTypes(mark, SYNTAX_MARK_NAMES));
|
|
207
|
+
}
|
|
208
|
+
function isInsideNonCodeTextblock(state, pos) {
|
|
209
|
+
if (pos < 0 || pos > state.doc.content.size) return false;
|
|
210
|
+
const $pos = state.doc.resolve(pos);
|
|
211
|
+
return $pos.parent.isTextblock && !$pos.parent.type.spec.code;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The maximal contiguous hidden run ending exactly at `pos`, or undefined.
|
|
138
215
|
*/
|
|
139
|
-
function
|
|
140
|
-
|
|
216
|
+
function getHiddenRunBefore(state, pos) {
|
|
217
|
+
if (!isInsideNonCodeTextblock(state, pos)) return;
|
|
218
|
+
const blockStart = state.doc.resolve(pos).start();
|
|
219
|
+
let from = pos;
|
|
220
|
+
while (from > blockStart && isHiddenChar(state, from - 1)) from--;
|
|
221
|
+
return from < pos ? {
|
|
222
|
+
from,
|
|
223
|
+
to: pos
|
|
224
|
+
} : void 0;
|
|
141
225
|
}
|
|
142
226
|
/**
|
|
143
|
-
*
|
|
144
|
-
* its whole source (content included), so it is the one construct that must
|
|
145
|
-
* still reveal in hide mode to stay editable; everything else follows the
|
|
146
|
-
* hide-mode contract and never reveals.
|
|
227
|
+
* The maximal contiguous hidden run starting exactly at `pos`, or undefined.
|
|
147
228
|
*/
|
|
148
|
-
function
|
|
149
|
-
|
|
229
|
+
function getHiddenRunAfter(state, pos) {
|
|
230
|
+
if (!isInsideNonCodeTextblock(state, pos)) return;
|
|
231
|
+
const blockEnd = state.doc.resolve(pos).end();
|
|
232
|
+
let to = pos;
|
|
233
|
+
while (to < blockEnd && isHiddenChar(state, to)) to++;
|
|
234
|
+
return to > pos ? {
|
|
235
|
+
from: pos,
|
|
236
|
+
to
|
|
237
|
+
} : void 0;
|
|
150
238
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (!
|
|
159
|
-
|
|
239
|
+
function isHiddenRunInterior(state, pos) {
|
|
240
|
+
return isHiddenChar(state, pos - 1) && isHiddenChar(state, pos);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The full run around an interior position, or undefined for rest positions.
|
|
244
|
+
*/
|
|
245
|
+
function getHiddenRunAround(state, pos) {
|
|
246
|
+
if (!isHiddenRunInterior(state, pos)) return;
|
|
247
|
+
const before = getHiddenRunBefore(state, pos);
|
|
248
|
+
if (!before) return;
|
|
249
|
+
const after = getHiddenRunAfter(state, pos);
|
|
250
|
+
if (!after) return;
|
|
251
|
+
return {
|
|
252
|
+
from: before.from,
|
|
253
|
+
to: after.to
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function charHasMark(state, pos, mark) {
|
|
257
|
+
const marks = getCharMarks(state, pos);
|
|
258
|
+
return marks != null && mark.isInSet(marks);
|
|
259
|
+
}
|
|
260
|
+
function getInnermostPackRangeAt(state, charPos) {
|
|
261
|
+
const marks = getCharMarks(state, charPos);
|
|
262
|
+
if (marks == null) return;
|
|
263
|
+
const packType = getMarkType(state.schema, "mdPack");
|
|
264
|
+
const packs = marks.filter((mark) => mark.type === packType);
|
|
265
|
+
if (packs.length === 0) return;
|
|
266
|
+
const $pos = state.doc.resolve(charPos);
|
|
267
|
+
const blockStart = $pos.start();
|
|
268
|
+
const blockEnd = $pos.end();
|
|
269
|
+
let innermost;
|
|
270
|
+
for (const pack of packs) {
|
|
271
|
+
let from = charPos;
|
|
272
|
+
while (from > blockStart && charHasMark(state, from - 1, pack)) from--;
|
|
273
|
+
let to = charPos + 1;
|
|
274
|
+
while (to < blockEnd && charHasMark(state, to, pack)) to++;
|
|
275
|
+
if (innermost == null || to - from < innermost.to - innermost.from) innermost = {
|
|
276
|
+
from,
|
|
277
|
+
to
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return innermost;
|
|
281
|
+
}
|
|
282
|
+
function isPackOuterEdge(state, run, edge) {
|
|
283
|
+
const pack = getInnermostPackRangeAt(state, edge === "from" ? run.from : run.to - 1);
|
|
284
|
+
if (pack == null) return false;
|
|
285
|
+
return edge === "from" ? pack.from === run.from : pack.to === run.to;
|
|
286
|
+
}
|
|
287
|
+
function getPointerEdge(state, run, pos) {
|
|
288
|
+
const fromIsOuter = isPackOuterEdge(state, run, "from");
|
|
289
|
+
const toIsOuter = isPackOuterEdge(state, run, "to");
|
|
290
|
+
if (fromIsOuter && !toIsOuter) return run.from;
|
|
291
|
+
if (toIsOuter && !fromIsOuter) return run.to;
|
|
292
|
+
return pos - run.from <= run.to - pos ? run.from : run.to;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The rest position for a caret that landed at `newPos`. `oldPos` supplies the
|
|
296
|
+
* travel direction for keyboard motion; `isPointer` selects the click rules.
|
|
297
|
+
*/
|
|
298
|
+
function getRestPosition(state, oldPos, newPos, isPointer) {
|
|
299
|
+
if (!isInsideNonCodeTextblock(state, newPos)) return newPos;
|
|
300
|
+
const run = getHiddenRunAround(state, newPos);
|
|
301
|
+
if (run != null) {
|
|
302
|
+
if (!isPointer) return newPos >= oldPos ? run.to : run.from;
|
|
303
|
+
return getPointerEdge(state, run, newPos);
|
|
304
|
+
}
|
|
305
|
+
if (!isPointer) return newPos;
|
|
306
|
+
const runBefore = getHiddenRunBefore(state, newPos);
|
|
307
|
+
if (runBefore != null && isPackOuterEdge(state, runBefore, "from")) return runBefore.from;
|
|
308
|
+
const runAfter = getHiddenRunAfter(state, newPos);
|
|
309
|
+
if (runAfter != null && isPackOuterEdge(state, runAfter, "to")) return runAfter.to;
|
|
310
|
+
return newPos;
|
|
311
|
+
}
|
|
312
|
+
function getCaretTail(state, pos) {
|
|
313
|
+
if (!isInsideNonCodeTextblock(state, pos)) return;
|
|
314
|
+
const hiddenBefore = isHiddenChar(state, pos - 1);
|
|
315
|
+
const hiddenAfter = isHiddenChar(state, pos);
|
|
316
|
+
if (hiddenBefore === hiddenAfter) return;
|
|
317
|
+
return hiddenAfter ? "left" : "right";
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The leading and trailing hidden runs of the innermost unit whose marker
|
|
321
|
+
* character sits at `charPos`, trailing first so callers can delete them in
|
|
322
|
+
* order without remapping. A fully hidden unit yields one run.
|
|
323
|
+
*/
|
|
324
|
+
function getUnitMarkerRuns(state, charPos) {
|
|
325
|
+
const pack = getInnermostPackRangeAt(state, charPos);
|
|
326
|
+
if (pack == null) return [];
|
|
327
|
+
const leading = getHiddenRunAfter(state, pack.from);
|
|
328
|
+
const trailing = getHiddenRunBefore(state, pack.to);
|
|
329
|
+
const runs = [];
|
|
330
|
+
if (trailing != null) runs.push(trailing);
|
|
331
|
+
if (leading != null && (trailing == null || leading.from !== trailing.from)) runs.push(leading);
|
|
332
|
+
return runs;
|
|
160
333
|
}
|
|
161
334
|
|
|
162
335
|
//#endregion
|
|
@@ -184,11 +357,33 @@ function getMarkRangeAt(state, pos, markName, attrs) {
|
|
|
184
357
|
if (!$pos) return;
|
|
185
358
|
const markNames = Array.isArray(markName) ? markName : [markName];
|
|
186
359
|
for (const name of markNames) {
|
|
187
|
-
const range = getMarkRange($pos, name, attrs);
|
|
360
|
+
const range = ATOM_MARK_NAMES.includes(name) ? getAtomUnitRange(state, $pos, name) : getMarkRange($pos, name, attrs);
|
|
188
361
|
if (range) return range;
|
|
189
362
|
}
|
|
190
363
|
}
|
|
191
364
|
/**
|
|
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.
|
|
371
|
+
*/
|
|
372
|
+
function getAtomUnitRange(state, $pos, name) {
|
|
373
|
+
const parent = $pos.parent;
|
|
374
|
+
const after = parent.childAfter($pos.parentOffset);
|
|
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;
|
|
378
|
+
const pack = getInnermostPackRangeAt(state, $pos.start() + matched.offset);
|
|
379
|
+
if (!pack) return getMarkRange($pos, name);
|
|
380
|
+
return {
|
|
381
|
+
from: pack.from,
|
|
382
|
+
to: pack.to,
|
|
383
|
+
mark
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
192
387
|
* Returns the run ending exactly at `pos`, the one immediately to its left.
|
|
193
388
|
* Probes from inside the left neighbour (`pos - 1`): probing `pos` itself
|
|
194
389
|
* cannot see that run when another run starts exactly there, because
|
|
@@ -228,15 +423,6 @@ function getMarkRangeStrictlyAround(state, pos, markNames) {
|
|
|
228
423
|
|
|
229
424
|
//#endregion
|
|
230
425
|
//#region src/extensions/atom-mark-navigation.ts
|
|
231
|
-
/**
|
|
232
|
-
* The source marks whose mark views hide the raw text behind a rendered
|
|
233
|
-
* preview (`.md-atom-view-preview`) and act as one caret stop.
|
|
234
|
-
*/
|
|
235
|
-
const ATOM_SOURCE_MARK_NAMES = [
|
|
236
|
-
"mdImage",
|
|
237
|
-
"mdWikilink",
|
|
238
|
-
"mdFile"
|
|
239
|
-
];
|
|
240
426
|
function getActiveMarkNames(marks, state) {
|
|
241
427
|
const mode = getMarkMode(state);
|
|
242
428
|
if (!mode) return [];
|
|
@@ -256,7 +442,7 @@ function getSelectedRange(state, markNames) {
|
|
|
256
442
|
*/
|
|
257
443
|
function getSelectedAtomRange(state) {
|
|
258
444
|
if (!getMarkMode(state)) return;
|
|
259
|
-
return getSelectedRange(state,
|
|
445
|
+
return getSelectedRange(state, ATOM_SOURCE_MARK_NAMES);
|
|
260
446
|
}
|
|
261
447
|
function findSelectionAcrossBlockBoundary(state, pos, markNames, direction) {
|
|
262
448
|
const $pos = state.doc.resolve(pos);
|
|
@@ -470,7 +656,9 @@ const highlightToMarkdown = (node, _parent, state, info) => {
|
|
|
470
656
|
function isCheckboxInput(node) {
|
|
471
657
|
return node.tagName === "input" && node.properties.type === "checkbox";
|
|
472
658
|
}
|
|
473
|
-
/**
|
|
659
|
+
/**
|
|
660
|
+
* The first checkbox `<input>` before any nested list, if any.
|
|
661
|
+
*/
|
|
474
662
|
function findCheckbox(node) {
|
|
475
663
|
for (const child of node.children) {
|
|
476
664
|
if (child.type !== "element") continue;
|
|
@@ -518,7 +706,9 @@ function normalizeTaskItem(node) {
|
|
|
518
706
|
children: [input, ...content]
|
|
519
707
|
};
|
|
520
708
|
}
|
|
521
|
-
/**
|
|
709
|
+
/**
|
|
710
|
+
* `li` handler that recognizes ProseMirror-style task items, then delegates.
|
|
711
|
+
*/
|
|
522
712
|
const taskAwareListItem = (state, element) => {
|
|
523
713
|
return defaultHandlers.li(state, normalizeTaskItem(element) ?? element);
|
|
524
714
|
};
|
|
@@ -567,7 +757,9 @@ function createProcessor() {
|
|
|
567
757
|
}).freeze();
|
|
568
758
|
}
|
|
569
759
|
const getProcessor = once(createProcessor);
|
|
570
|
-
/**
|
|
760
|
+
/**
|
|
761
|
+
* Convert HTML into Markdown text.
|
|
762
|
+
*/
|
|
571
763
|
function htmlToMarkdown(html) {
|
|
572
764
|
return String(getProcessor().processSync(html));
|
|
573
765
|
}
|
|
@@ -584,43 +776,36 @@ function parsePositiveInteger(raw) {
|
|
|
584
776
|
return value != null && value > 0 ? value : void 0;
|
|
585
777
|
}
|
|
586
778
|
|
|
587
|
-
//#endregion
|
|
588
|
-
//#region src/extensions/mark-names.ts
|
|
589
|
-
function isMarkOfType(mark, name) {
|
|
590
|
-
return mark.type.name === name;
|
|
591
|
-
}
|
|
592
|
-
const SYNTAX_MARK_NAMES = /* @__PURE__ */ new Set([
|
|
593
|
-
"mdMark",
|
|
594
|
-
"mdLinkUri",
|
|
595
|
-
"mdLinkTitle"
|
|
596
|
-
]);
|
|
597
|
-
const ATOM_MARK_NAMES = /* @__PURE__ */ new Set([
|
|
598
|
-
"mdWikilink",
|
|
599
|
-
"mdImage",
|
|
600
|
-
"mdFile",
|
|
601
|
-
"mdMath"
|
|
602
|
-
]);
|
|
603
|
-
|
|
604
779
|
//#endregion
|
|
605
780
|
//#region src/extensions/inline-runs.ts
|
|
606
781
|
function findAtomMark(marks) {
|
|
607
|
-
return marks.find((mark) =>
|
|
782
|
+
return marks.find((mark) => isMarkOfTypes(mark, ATOM_MARK_NAMES));
|
|
783
|
+
}
|
|
784
|
+
function findOwnPackMark(marks) {
|
|
785
|
+
return marks.findLast((mark) => isMarkOfType(mark, "mdPack"));
|
|
608
786
|
}
|
|
609
787
|
function hasSyntaxMark(marks) {
|
|
610
|
-
return marks.some((mark) =>
|
|
788
|
+
return marks.some((mark) => isMarkOfTypes(mark, SYNTAX_MARK_NAMES));
|
|
611
789
|
}
|
|
612
790
|
/**
|
|
613
791
|
* Group a textblock's text nodes into atom units and plain runs. A unit's
|
|
614
|
-
* text nodes
|
|
615
|
-
*
|
|
792
|
+
* text nodes carry equal packs, while an identical neighbour's pack differs
|
|
793
|
+
* by `slot`, so pack equality splits adjacent same-attrs units.
|
|
616
794
|
*/
|
|
617
795
|
function groupInlineRuns(textblock) {
|
|
618
796
|
const runs = [];
|
|
797
|
+
let previousPack;
|
|
619
798
|
textblock.forEach((child) => {
|
|
620
|
-
if (!child.isText || !child.text)
|
|
799
|
+
if (!child.isText || !child.text) {
|
|
800
|
+
previousPack = void 0;
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
621
803
|
const atom = findAtomMark(child.marks);
|
|
804
|
+
const pack = findOwnPackMark(child.marks);
|
|
622
805
|
const last = runs.at(-1);
|
|
623
|
-
|
|
806
|
+
const continuesUnit = atom != null && last?.atom != null && pack != null && previousPack != null && pack.eq(previousPack);
|
|
807
|
+
previousPack = pack;
|
|
808
|
+
if (continuesUnit && last != null) {
|
|
624
809
|
last.text += child.text;
|
|
625
810
|
last.children.push(child);
|
|
626
811
|
return;
|
|
@@ -702,7 +887,9 @@ function syncOpenWrappers(open, next, out) {
|
|
|
702
887
|
});
|
|
703
888
|
}
|
|
704
889
|
}
|
|
705
|
-
/**
|
|
890
|
+
/**
|
|
891
|
+
* A soft break (a literal `\n` in the source) renders as `<br>`.
|
|
892
|
+
*/
|
|
706
893
|
function appendTextWithBreaks(parent, text) {
|
|
707
894
|
const lines = text.split("\n");
|
|
708
895
|
for (const [index, line] of lines.entries()) {
|
|
@@ -758,7 +945,9 @@ function defineHeadingWhitespace() {
|
|
|
758
945
|
whitespace: "pre"
|
|
759
946
|
});
|
|
760
947
|
}
|
|
761
|
-
/**
|
|
948
|
+
/**
|
|
949
|
+
* The clipboard DOM of a heading: semantic inline content plus `data-md`.
|
|
950
|
+
*/
|
|
762
951
|
function headingClipboardDOM(node) {
|
|
763
952
|
const attrs = node.attrs;
|
|
764
953
|
return semanticTextblockDOM(`h${attrs.level}`, node, {
|
|
@@ -766,7 +955,9 @@ function headingClipboardDOM(node) {
|
|
|
766
955
|
"data-closing-hashes": attrs.closingHashes != null ? String(attrs.closingHashes) : void 0
|
|
767
956
|
});
|
|
768
957
|
}
|
|
769
|
-
/**
|
|
958
|
+
/**
|
|
959
|
+
* The clipboard parse rules restoring a heading's source text from `data-md`.
|
|
960
|
+
*/
|
|
770
961
|
function headingFromDOM() {
|
|
771
962
|
return [
|
|
772
963
|
1,
|
|
@@ -839,11 +1030,15 @@ function defineMeowdownParagraphSpec() {
|
|
|
839
1030
|
}
|
|
840
1031
|
});
|
|
841
1032
|
}
|
|
842
|
-
/**
|
|
1033
|
+
/**
|
|
1034
|
+
* The clipboard DOM of a paragraph: semantic inline content plus `data-md`.
|
|
1035
|
+
*/
|
|
843
1036
|
function paragraphClipboardDOM(node) {
|
|
844
1037
|
return semanticTextblockDOM("p", node);
|
|
845
1038
|
}
|
|
846
|
-
/**
|
|
1039
|
+
/**
|
|
1040
|
+
* The clipboard parse rules restoring a paragraph's source text from `data-md`.
|
|
1041
|
+
*/
|
|
847
1042
|
function paragraphFromDOM() {
|
|
848
1043
|
return [createSourceTextRule("p", "paragraph")];
|
|
849
1044
|
}
|
|
@@ -968,7 +1163,9 @@ function appendText(output, text) {
|
|
|
968
1163
|
output.trailingNewlines = text.length - index;
|
|
969
1164
|
}
|
|
970
1165
|
}
|
|
971
|
-
/**
|
|
1166
|
+
/**
|
|
1167
|
+
* Recursive worker of {@link extractStyledPlainText}.
|
|
1168
|
+
*/
|
|
972
1169
|
function appendNodeText(node, output, insideLine) {
|
|
973
1170
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
974
1171
|
appendText(output, node.nodeValue ?? "");
|
|
@@ -1103,7 +1300,9 @@ function definePlainTextPaste() {
|
|
|
1103
1300
|
|
|
1104
1301
|
//#endregion
|
|
1105
1302
|
//#region src/utils/backticks.ts
|
|
1106
|
-
/**
|
|
1303
|
+
/**
|
|
1304
|
+
* Length of the longest run of `charCode` in `text`, at least `min`.
|
|
1305
|
+
*/
|
|
1107
1306
|
function longestCharRun(text, charCode, min = 0) {
|
|
1108
1307
|
let longest = min;
|
|
1109
1308
|
let run = 0;
|
|
@@ -1113,7 +1312,9 @@ function longestCharRun(text, charCode, min = 0) {
|
|
|
1113
1312
|
} else run = 0;
|
|
1114
1313
|
return longest;
|
|
1115
1314
|
}
|
|
1116
|
-
/**
|
|
1315
|
+
/**
|
|
1316
|
+
* Length of the longest run of backticks in `text`, at least `min`.
|
|
1317
|
+
*/
|
|
1117
1318
|
function longestBacktickRun(text, min = 0) {
|
|
1118
1319
|
return longestCharRun(text, 96, min);
|
|
1119
1320
|
}
|
|
@@ -1157,7 +1358,9 @@ function emitFrontmatter(body, out) {
|
|
|
1157
1358
|
out.write("---");
|
|
1158
1359
|
out.closeBlock();
|
|
1159
1360
|
}
|
|
1160
|
-
/**
|
|
1361
|
+
/**
|
|
1362
|
+
* Heading prefixes indexed by level (1..6). Index 0 is a sentinel.
|
|
1363
|
+
*/
|
|
1161
1364
|
const HEADING_PREFIX = [
|
|
1162
1365
|
"",
|
|
1163
1366
|
"# ",
|
|
@@ -1227,7 +1430,9 @@ var MdOut = class {
|
|
|
1227
1430
|
this.emitDeferredBlankLine();
|
|
1228
1431
|
this.deferredBlankPrefix = this.linePrefix;
|
|
1229
1432
|
}
|
|
1230
|
-
/**
|
|
1433
|
+
/**
|
|
1434
|
+
* End the current block; the next write gets a blank line before it.
|
|
1435
|
+
*/
|
|
1231
1436
|
closeBlock() {
|
|
1232
1437
|
if (this.atLineStart && this.pendingFirst !== null) {
|
|
1233
1438
|
this.emitDeferredBlankLine();
|
|
@@ -1449,7 +1654,9 @@ function toIndentedCode(code) {
|
|
|
1449
1654
|
for (let i = 0; i < lines.length; i++) if (lines[i] !== "") lines[i] = ` ${lines[i]}`;
|
|
1450
1655
|
return lines.join("\n");
|
|
1451
1656
|
}
|
|
1452
|
-
/**
|
|
1657
|
+
/**
|
|
1658
|
+
* Whether any content line would read as a closing `$$` fence.
|
|
1659
|
+
*/
|
|
1453
1660
|
function hasDollarFenceLine(code) {
|
|
1454
1661
|
return code.split("\n").some((line) => line.trim() === "$$");
|
|
1455
1662
|
}
|
|
@@ -1614,7 +1821,9 @@ function definePlainTextSerializer() {
|
|
|
1614
1821
|
} }
|
|
1615
1822
|
}));
|
|
1616
1823
|
}
|
|
1617
|
-
/**
|
|
1824
|
+
/**
|
|
1825
|
+
* Drop the inline text a hide-mode editor never shows.
|
|
1826
|
+
*/
|
|
1618
1827
|
function stripHiddenInline(slice) {
|
|
1619
1828
|
return new Slice(mapFragment(slice.content), slice.openStart, slice.openEnd);
|
|
1620
1829
|
}
|
|
@@ -1999,141 +2208,6 @@ function executeCommand(view, command) {
|
|
|
1999
2208
|
return command(view.state, view.dispatch, view);
|
|
2000
2209
|
}
|
|
2001
2210
|
|
|
2002
|
-
//#endregion
|
|
2003
|
-
//#region src/extensions/hidden-run.ts
|
|
2004
|
-
function getCharMarks(state, pos) {
|
|
2005
|
-
if (pos < 0 || pos + 1 > state.doc.content.size) return;
|
|
2006
|
-
const $pos = state.doc.resolve(pos);
|
|
2007
|
-
const child = $pos.parent.maybeChild($pos.index());
|
|
2008
|
-
if (child == null || !child.isText) return;
|
|
2009
|
-
return child.marks;
|
|
2010
|
-
}
|
|
2011
|
-
function isHiddenChar(state, pos) {
|
|
2012
|
-
const marks = getCharMarks(state, pos);
|
|
2013
|
-
if (marks == null) return false;
|
|
2014
|
-
return marks.some((mark) => SYNTAX_MARK_NAMES.has(mark.type.name));
|
|
2015
|
-
}
|
|
2016
|
-
function isInsideNonCodeTextblock(state, pos) {
|
|
2017
|
-
if (pos < 0 || pos > state.doc.content.size) return false;
|
|
2018
|
-
const $pos = state.doc.resolve(pos);
|
|
2019
|
-
return $pos.parent.isTextblock && !$pos.parent.type.spec.code;
|
|
2020
|
-
}
|
|
2021
|
-
/** The maximal contiguous hidden run ending exactly at `pos`, or undefined. */
|
|
2022
|
-
function getHiddenRunBefore(state, pos) {
|
|
2023
|
-
if (!isInsideNonCodeTextblock(state, pos)) return;
|
|
2024
|
-
const blockStart = state.doc.resolve(pos).start();
|
|
2025
|
-
let from = pos;
|
|
2026
|
-
while (from > blockStart && isHiddenChar(state, from - 1)) from--;
|
|
2027
|
-
return from < pos ? {
|
|
2028
|
-
from,
|
|
2029
|
-
to: pos
|
|
2030
|
-
} : void 0;
|
|
2031
|
-
}
|
|
2032
|
-
/** The maximal contiguous hidden run starting exactly at `pos`, or undefined. */
|
|
2033
|
-
function getHiddenRunAfter(state, pos) {
|
|
2034
|
-
if (!isInsideNonCodeTextblock(state, pos)) return;
|
|
2035
|
-
const blockEnd = state.doc.resolve(pos).end();
|
|
2036
|
-
let to = pos;
|
|
2037
|
-
while (to < blockEnd && isHiddenChar(state, to)) to++;
|
|
2038
|
-
return to > pos ? {
|
|
2039
|
-
from: pos,
|
|
2040
|
-
to
|
|
2041
|
-
} : void 0;
|
|
2042
|
-
}
|
|
2043
|
-
function isHiddenRunInterior(state, pos) {
|
|
2044
|
-
return isHiddenChar(state, pos - 1) && isHiddenChar(state, pos);
|
|
2045
|
-
}
|
|
2046
|
-
/** The full run around an interior position, or undefined for rest positions. */
|
|
2047
|
-
function getHiddenRunAround(state, pos) {
|
|
2048
|
-
if (!isHiddenRunInterior(state, pos)) return;
|
|
2049
|
-
const before = getHiddenRunBefore(state, pos);
|
|
2050
|
-
if (!before) return;
|
|
2051
|
-
const after = getHiddenRunAfter(state, pos);
|
|
2052
|
-
if (!after) return;
|
|
2053
|
-
return {
|
|
2054
|
-
from: before.from,
|
|
2055
|
-
to: after.to
|
|
2056
|
-
};
|
|
2057
|
-
}
|
|
2058
|
-
function charHasMark(state, pos, mark) {
|
|
2059
|
-
const marks = getCharMarks(state, pos);
|
|
2060
|
-
return marks != null && mark.isInSet(marks);
|
|
2061
|
-
}
|
|
2062
|
-
function getInnermostPackRangeAt(state, charPos) {
|
|
2063
|
-
const marks = getCharMarks(state, charPos);
|
|
2064
|
-
if (marks == null) return;
|
|
2065
|
-
const packType = getMarkType(state.schema, "mdPack");
|
|
2066
|
-
const packs = marks.filter((mark) => mark.type === packType);
|
|
2067
|
-
if (packs.length === 0) return;
|
|
2068
|
-
const $pos = state.doc.resolve(charPos);
|
|
2069
|
-
const blockStart = $pos.start();
|
|
2070
|
-
const blockEnd = $pos.end();
|
|
2071
|
-
let innermost;
|
|
2072
|
-
for (const pack of packs) {
|
|
2073
|
-
let from = charPos;
|
|
2074
|
-
while (from > blockStart && charHasMark(state, from - 1, pack)) from--;
|
|
2075
|
-
let to = charPos + 1;
|
|
2076
|
-
while (to < blockEnd && charHasMark(state, to, pack)) to++;
|
|
2077
|
-
if (innermost == null || to - from < innermost.to - innermost.from) innermost = {
|
|
2078
|
-
from,
|
|
2079
|
-
to
|
|
2080
|
-
};
|
|
2081
|
-
}
|
|
2082
|
-
return innermost;
|
|
2083
|
-
}
|
|
2084
|
-
function isPackOuterEdge(state, run, edge) {
|
|
2085
|
-
const pack = getInnermostPackRangeAt(state, edge === "from" ? run.from : run.to - 1);
|
|
2086
|
-
if (pack == null) return false;
|
|
2087
|
-
return edge === "from" ? pack.from === run.from : pack.to === run.to;
|
|
2088
|
-
}
|
|
2089
|
-
function getPointerEdge(state, run, pos) {
|
|
2090
|
-
const fromIsOuter = isPackOuterEdge(state, run, "from");
|
|
2091
|
-
const toIsOuter = isPackOuterEdge(state, run, "to");
|
|
2092
|
-
if (fromIsOuter && !toIsOuter) return run.from;
|
|
2093
|
-
if (toIsOuter && !fromIsOuter) return run.to;
|
|
2094
|
-
return pos - run.from <= run.to - pos ? run.from : run.to;
|
|
2095
|
-
}
|
|
2096
|
-
/**
|
|
2097
|
-
* The rest position for a caret that landed at `newPos`. `oldPos` supplies the
|
|
2098
|
-
* travel direction for keyboard motion; `isPointer` selects the click rules.
|
|
2099
|
-
*/
|
|
2100
|
-
function getRestPosition(state, oldPos, newPos, isPointer) {
|
|
2101
|
-
if (!isInsideNonCodeTextblock(state, newPos)) return newPos;
|
|
2102
|
-
const run = getHiddenRunAround(state, newPos);
|
|
2103
|
-
if (run != null) {
|
|
2104
|
-
if (!isPointer) return newPos >= oldPos ? run.to : run.from;
|
|
2105
|
-
return getPointerEdge(state, run, newPos);
|
|
2106
|
-
}
|
|
2107
|
-
if (!isPointer) return newPos;
|
|
2108
|
-
const runBefore = getHiddenRunBefore(state, newPos);
|
|
2109
|
-
if (runBefore != null && isPackOuterEdge(state, runBefore, "from")) return runBefore.from;
|
|
2110
|
-
const runAfter = getHiddenRunAfter(state, newPos);
|
|
2111
|
-
if (runAfter != null && isPackOuterEdge(state, runAfter, "to")) return runAfter.to;
|
|
2112
|
-
return newPos;
|
|
2113
|
-
}
|
|
2114
|
-
function getCaretTail(state, pos) {
|
|
2115
|
-
if (!isInsideNonCodeTextblock(state, pos)) return;
|
|
2116
|
-
const hiddenBefore = isHiddenChar(state, pos - 1);
|
|
2117
|
-
const hiddenAfter = isHiddenChar(state, pos);
|
|
2118
|
-
if (hiddenBefore === hiddenAfter) return;
|
|
2119
|
-
return hiddenAfter ? "left" : "right";
|
|
2120
|
-
}
|
|
2121
|
-
/**
|
|
2122
|
-
* The leading and trailing hidden runs of the innermost unit whose marker
|
|
2123
|
-
* character sits at `charPos`, trailing first so callers can delete them in
|
|
2124
|
-
* order without remapping. A fully hidden unit yields one run.
|
|
2125
|
-
*/
|
|
2126
|
-
function getUnitMarkerRuns(state, charPos) {
|
|
2127
|
-
const pack = getInnermostPackRangeAt(state, charPos);
|
|
2128
|
-
if (pack == null) return [];
|
|
2129
|
-
const leading = getHiddenRunAfter(state, pack.from);
|
|
2130
|
-
const trailing = getHiddenRunBefore(state, pack.to);
|
|
2131
|
-
const runs = [];
|
|
2132
|
-
if (trailing != null) runs.push(trailing);
|
|
2133
|
-
if (leading != null && (trailing == null || leading.from !== trailing.from)) runs.push(leading);
|
|
2134
|
-
return runs;
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
2211
|
//#endregion
|
|
2138
2212
|
//#region src/extensions/hidden-run-caret.ts
|
|
2139
2213
|
const snapKey = new PluginKey("meowdown-hidden-run-snap");
|
|
@@ -2494,11 +2568,15 @@ function parseMagicComment(comment) {
|
|
|
2494
2568
|
function toPositiveNumber(value) {
|
|
2495
2569
|
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.round(value);
|
|
2496
2570
|
}
|
|
2497
|
-
/**
|
|
2571
|
+
/**
|
|
2572
|
+
* The canonical comment meowdown writes for the metadata.
|
|
2573
|
+
*/
|
|
2498
2574
|
function formatMagicComment(magic) {
|
|
2499
2575
|
return `<!-- ${JSON.stringify(magic)} -->`;
|
|
2500
2576
|
}
|
|
2501
|
-
/**
|
|
2577
|
+
/**
|
|
2578
|
+
* Drop a trailing magic comment from the source text.
|
|
2579
|
+
*/
|
|
2502
2580
|
function stripMagicComment(source) {
|
|
2503
2581
|
return source.replace(TRAILING_MAGIC_COMMENT_RE, "");
|
|
2504
2582
|
}
|
|
@@ -2619,7 +2697,9 @@ function positiveInteger(value) {
|
|
|
2619
2697
|
const parsed = Number.parseInt(value, 10);
|
|
2620
2698
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
2621
2699
|
}
|
|
2622
|
-
/**
|
|
2700
|
+
/**
|
|
2701
|
+
* Parse `![[target]]`, `![[target|alias]]`, `![[target|width]]`, or `![[target|widthxheight]]`.
|
|
2702
|
+
*/
|
|
2623
2703
|
function parseWikiEmbed(source) {
|
|
2624
2704
|
const inner = source.replace(/^!\[\[/, "").replace(/\]\]$/, "");
|
|
2625
2705
|
const pipe = inner.lastIndexOf("|");
|
|
@@ -2653,11 +2733,15 @@ function parseWikiEmbed(source) {
|
|
|
2653
2733
|
height
|
|
2654
2734
|
};
|
|
2655
2735
|
}
|
|
2656
|
-
/**
|
|
2736
|
+
/**
|
|
2737
|
+
* Rewrite a wiki image embed with a persisted display size.
|
|
2738
|
+
*/
|
|
2657
2739
|
function formatSizedWikiEmbed(target, width, height) {
|
|
2658
2740
|
return `![[${target}|${Math.round(width)}x${Math.round(height)}]]`;
|
|
2659
2741
|
}
|
|
2660
|
-
/**
|
|
2742
|
+
/**
|
|
2743
|
+
* Last path component of a target, with a note heading/block fragment removed.
|
|
2744
|
+
*/
|
|
2661
2745
|
function wikiEmbedBasename(target) {
|
|
2662
2746
|
const path = target.split(/[?#]/, 1)[0];
|
|
2663
2747
|
const segment = path.split(/[/\\]/).findLast(Boolean) ?? path;
|
|
@@ -2670,7 +2754,9 @@ function wikiEmbedBasename(target) {
|
|
|
2670
2754
|
|
|
2671
2755
|
//#endregion
|
|
2672
2756
|
//#region src/extensions/wikilink.ts
|
|
2673
|
-
/**
|
|
2757
|
+
/**
|
|
2758
|
+
* Splits `[[target]]`/`[[target|alias]]` into its target and display label (the alias, or empty).
|
|
2759
|
+
*/
|
|
2674
2760
|
function parseWikilink(text) {
|
|
2675
2761
|
const inner = text.replace(/^\[\[/, "").replace(/\]\]$/, "");
|
|
2676
2762
|
const pipe = inner.indexOf("|");
|
|
@@ -2755,6 +2841,17 @@ const MARK_NAME_BY_TYPE_ID = /* @__PURE__ */ new Map([
|
|
|
2755
2841
|
[LEZER_NODE_IDS.Hashtag, "mdTag"],
|
|
2756
2842
|
[LEZER_NODE_IDS.WikilinkMark, "mdMark"]
|
|
2757
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
|
+
}
|
|
2758
2855
|
/**
|
|
2759
2856
|
* Walk a textblock's inline content and produce a list of mark chunks
|
|
2760
2857
|
* with positions relative to the start of `text` (i.e. zero-based).
|
|
@@ -2769,7 +2866,28 @@ function inlineTextToMarkChunksWithContext(marks, text, options, context) {
|
|
|
2769
2866
|
walk(elements, [], 0, text.length, text, marks, out, options, context);
|
|
2770
2867
|
return out;
|
|
2771
2868
|
}
|
|
2772
|
-
/**
|
|
2869
|
+
/**
|
|
2870
|
+
* The pack of one unit starting at `from`. When the chunk ending exactly there
|
|
2871
|
+
* closes a unit whose pack equals this one, create it with `slot: 1` instead:
|
|
2872
|
+
* equal packs would let ProseMirror merge the two units into one text node,
|
|
2873
|
+
* one mark run and one mark view. The neighbour's own pack sits at the same
|
|
2874
|
+
* depth, right after `parentMarks`; at any other depth that position holds a
|
|
2875
|
+
* different mark (or nothing) and never compares equal.
|
|
2876
|
+
*/
|
|
2877
|
+
function createUnitPack(marks, out, parentMarks, from, attrs) {
|
|
2878
|
+
const pack = marks.mdPack.create(attrs);
|
|
2879
|
+
const previous = out.at(-1);
|
|
2880
|
+
if (previous == null || previous[1] !== from) return pack;
|
|
2881
|
+
const neighbourPack = previous[2][parentMarks.length];
|
|
2882
|
+
if (neighbourPack == null || !neighbourPack.eq(pack)) return pack;
|
|
2883
|
+
return marks.mdPack.create({
|
|
2884
|
+
...attrs,
|
|
2885
|
+
slot: 1
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Drop the surrounding `"" '' ()` delimiters of a `LinkTitle` slice and unescape.
|
|
2890
|
+
*/
|
|
2773
2891
|
function unquoteTitle(raw) {
|
|
2774
2892
|
return raw.slice(1, -1).replaceAll(/\\(.)/g, "$1");
|
|
2775
2893
|
}
|
|
@@ -2805,16 +2923,13 @@ function walkNode(node, parentMarks, text, marks, out, options, context) {
|
|
|
2805
2923
|
* syntax marks, then recurses into its children.
|
|
2806
2924
|
*/
|
|
2807
2925
|
function walkGenericNode(node, parentMarks, text, marks, out, options, context) {
|
|
2808
|
-
const
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
else if (type === LEZER_NODE_IDS.Autolink) packKey = "autolink";
|
|
2816
|
-
const base = packKey ? [...parentMarks, marks.mdPack.create({ key: packKey })] : parentMarks;
|
|
2817
|
-
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);
|
|
2818
2933
|
const childMarks = maybeMarkName ? [...base, marks[maybeMarkName].create()] : base;
|
|
2819
2934
|
if (node.children.length === 0) emit(out, node.from, node.to, childMarks);
|
|
2820
2935
|
else walk(node.children, childMarks, node.from, node.to, text, marks, out, options, context);
|
|
@@ -2836,9 +2951,13 @@ function walkLink(node, parentMarks, text, marks, out, options, context) {
|
|
|
2836
2951
|
walkUnresolvedLink(node, parentMarks, text, marks, out, options, context);
|
|
2837
2952
|
return;
|
|
2838
2953
|
}
|
|
2839
|
-
const
|
|
2840
|
-
if (
|
|
2841
|
-
emit(out, node.from, node.to,
|
|
2954
|
+
const fileMark = claimFileLink(parts, resolution, text, marks, options);
|
|
2955
|
+
if (fileMark) {
|
|
2956
|
+
emit(out, node.from, node.to, [
|
|
2957
|
+
...parentMarks,
|
|
2958
|
+
createUnitPack(marks, out, parentMarks, node.from, { key: "file" }),
|
|
2959
|
+
fileMark
|
|
2960
|
+
]);
|
|
2842
2961
|
return;
|
|
2843
2962
|
}
|
|
2844
2963
|
walkResolvedLink(node, parts, resolution, parentMarks, text, marks, out, options, context);
|
|
@@ -2902,7 +3021,9 @@ function walkUnresolvedLink(node, parentMarks, text, marks, out, options, contex
|
|
|
2902
3021
|
return child.type !== LEZER_NODE_IDS.LinkMark && child.type !== LEZER_NODE_IDS.LinkLabel;
|
|
2903
3022
|
}), parentMarks, node.from, node.to, text, marks, out, options, context);
|
|
2904
3023
|
}
|
|
2905
|
-
/**
|
|
3024
|
+
/**
|
|
3025
|
+
* The last path segment of `href` (query/hash stripped), decoded when possible.
|
|
3026
|
+
*/
|
|
2906
3027
|
function hrefBasename(href) {
|
|
2907
3028
|
const path = href.split(/[?#]/, 1)[0];
|
|
2908
3029
|
const segment = path.split(/[/\\]/).findLast(Boolean) ?? path;
|
|
@@ -2913,12 +3034,12 @@ function hrefBasename(href) {
|
|
|
2913
3034
|
}
|
|
2914
3035
|
}
|
|
2915
3036
|
/**
|
|
2916
|
-
* The
|
|
2917
|
-
* claimed as a file, or `undefined` when the link
|
|
2918
|
-
* resolver is never consulted for a link without a
|
|
2919
|
-
* destination.
|
|
3037
|
+
* The `mdFile` mark for a whole inline or resolved reference link that the
|
|
3038
|
+
* host's `resolveFileLink` claimed as a file, or `undefined` when the link
|
|
3039
|
+
* stays a regular link. The resolver is never consulted for a link without a
|
|
3040
|
+
* closed label or a non-empty destination.
|
|
2920
3041
|
*/
|
|
2921
|
-
function claimFileLink(parts, resolution,
|
|
3042
|
+
function claimFileLink(parts, resolution, text, marks, options) {
|
|
2922
3043
|
const resolveFileLink = options?.resolveFileLink;
|
|
2923
3044
|
if (!resolveFileLink) return void 0;
|
|
2924
3045
|
const { labelFrom, labelTo } = parts;
|
|
@@ -2932,11 +3053,11 @@ function claimFileLink(parts, resolution, parentMarks, text, marks, options) {
|
|
|
2932
3053
|
title
|
|
2933
3054
|
})) return void 0;
|
|
2934
3055
|
const name = label || hrefBasename(href);
|
|
2935
|
-
return
|
|
3056
|
+
return marks.mdFile.create({
|
|
2936
3057
|
href,
|
|
2937
3058
|
name,
|
|
2938
3059
|
title
|
|
2939
|
-
})
|
|
3060
|
+
});
|
|
2940
3061
|
}
|
|
2941
3062
|
/**
|
|
2942
3063
|
* Special walker for `Link` nodes.
|
|
@@ -2960,17 +3081,14 @@ function walkResolvedLink(node, parts, resolution, parentMarks, text, marks, out
|
|
|
2960
3081
|
const { href, title, isReference } = resolution;
|
|
2961
3082
|
const linkTextMark = marks.mdLinkText.create({ href });
|
|
2962
3083
|
const inLabel = (pos) => labelEnd >= 0 && pos < labelEnd;
|
|
2963
|
-
const
|
|
2964
|
-
href,
|
|
2965
|
-
title,
|
|
2966
|
-
reference: true
|
|
2967
|
-
} : {
|
|
2968
|
-
href,
|
|
2969
|
-
title
|
|
2970
|
-
};
|
|
2971
|
-
const pack = marks.mdPack.create({
|
|
3084
|
+
const pack = createUnitPack(marks, out, parentMarks, node.from, {
|
|
2972
3085
|
key: "link",
|
|
2973
|
-
data
|
|
3086
|
+
data: {
|
|
3087
|
+
href,
|
|
3088
|
+
title,
|
|
3089
|
+
isReference
|
|
3090
|
+
},
|
|
3091
|
+
revealInFocus: true
|
|
2974
3092
|
});
|
|
2975
3093
|
const base = [...parentMarks, pack];
|
|
2976
3094
|
let pos = node.from;
|
|
@@ -3038,15 +3156,19 @@ function walkImage(node, parentMarks, text, marks, out, options, context, traili
|
|
|
3038
3156
|
const width = trailing?.magic.width ?? null;
|
|
3039
3157
|
const height = trailing?.magic.height ?? null;
|
|
3040
3158
|
const to = trailing?.to ?? node.to;
|
|
3041
|
-
emit(out, node.from, to, [
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3159
|
+
emit(out, node.from, to, [
|
|
3160
|
+
...parentMarks,
|
|
3161
|
+
createUnitPack(marks, out, parentMarks, node.from, { key: "image" }),
|
|
3162
|
+
marks.mdImage.create({
|
|
3163
|
+
src,
|
|
3164
|
+
alt,
|
|
3165
|
+
title,
|
|
3166
|
+
width,
|
|
3167
|
+
height,
|
|
3168
|
+
syntax: null,
|
|
3169
|
+
wikiTarget: null
|
|
3170
|
+
})
|
|
3171
|
+
]);
|
|
3050
3172
|
}
|
|
3051
3173
|
/**
|
|
3052
3174
|
* Special walker for inline math `$formula$`/`$$formula$$`.
|
|
@@ -3065,7 +3187,11 @@ function walkMath(node, parentMarks, text, marks, out) {
|
|
|
3065
3187
|
const formula = text.slice(markNodes[0].to, markNodes[1].from);
|
|
3066
3188
|
const base = [
|
|
3067
3189
|
...parentMarks,
|
|
3068
|
-
marks
|
|
3190
|
+
createUnitPack(marks, out, parentMarks, node.from, {
|
|
3191
|
+
key: "math",
|
|
3192
|
+
revealInFocus: true,
|
|
3193
|
+
revealInHide: true
|
|
3194
|
+
}),
|
|
3069
3195
|
marks.mdMath.create({ formula })
|
|
3070
3196
|
];
|
|
3071
3197
|
emit(out, node.from, markNodes[0].to, [...base, marks.mdMark.create()]);
|
|
@@ -3077,10 +3203,14 @@ function walkMath(node, parentMarks, text, marks, out) {
|
|
|
3077
3203
|
*/
|
|
3078
3204
|
function walkWikilink(node, parentMarks, text, marks, out) {
|
|
3079
3205
|
const { target, display } = parseWikilink(text.slice(node.from, node.to));
|
|
3080
|
-
emit(out, node.from, node.to, [
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3206
|
+
emit(out, node.from, node.to, [
|
|
3207
|
+
...parentMarks,
|
|
3208
|
+
createUnitPack(marks, out, parentMarks, node.from, { key: "wikilink" }),
|
|
3209
|
+
marks.mdWikilink.create({
|
|
3210
|
+
target,
|
|
3211
|
+
display
|
|
3212
|
+
})
|
|
3213
|
+
]);
|
|
3084
3214
|
}
|
|
3085
3215
|
/**
|
|
3086
3216
|
* Resolve `![[target]]` into one of Meowdown's existing source-backed atoms.
|
|
@@ -3097,33 +3227,45 @@ function walkWikiEmbed(node, parentMarks, text, marks, out, options) {
|
|
|
3097
3227
|
if (resolution.kind === "image") {
|
|
3098
3228
|
const src = resolution.src ?? embed.target;
|
|
3099
3229
|
const alt = (resolution.alt ?? embed.display) || wikiEmbedBasename(embed.target);
|
|
3100
|
-
emit(out, node.from, node.to, [
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3230
|
+
emit(out, node.from, node.to, [
|
|
3231
|
+
...parentMarks,
|
|
3232
|
+
createUnitPack(marks, out, parentMarks, node.from, { key: "image" }),
|
|
3233
|
+
marks.mdImage.create({
|
|
3234
|
+
src,
|
|
3235
|
+
alt,
|
|
3236
|
+
title: "",
|
|
3237
|
+
width: embed.width,
|
|
3238
|
+
height: embed.height,
|
|
3239
|
+
syntax: "wikiEmbed",
|
|
3240
|
+
wikiTarget: embed.target
|
|
3241
|
+
})
|
|
3242
|
+
]);
|
|
3109
3243
|
return;
|
|
3110
3244
|
}
|
|
3111
3245
|
if (resolution.kind === "file") {
|
|
3112
3246
|
const href = resolution.href ?? embed.target;
|
|
3113
3247
|
const name = (resolution.name ?? embed.display) || wikiEmbedBasename(embed.target);
|
|
3114
|
-
emit(out, node.from, node.to, [
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3248
|
+
emit(out, node.from, node.to, [
|
|
3249
|
+
...parentMarks,
|
|
3250
|
+
createUnitPack(marks, out, parentMarks, node.from, { key: "file" }),
|
|
3251
|
+
marks.mdFile.create({
|
|
3252
|
+
href,
|
|
3253
|
+
name,
|
|
3254
|
+
title: resolution.title ?? ""
|
|
3255
|
+
})
|
|
3256
|
+
]);
|
|
3119
3257
|
return;
|
|
3120
3258
|
}
|
|
3121
3259
|
const target = resolution.target ?? embed.target;
|
|
3122
3260
|
const display = resolution.display ?? embed.display;
|
|
3123
|
-
emit(out, node.from, node.to, [
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3261
|
+
emit(out, node.from, node.to, [
|
|
3262
|
+
...parentMarks,
|
|
3263
|
+
createUnitPack(marks, out, parentMarks, node.from, { key: "wikilink" }),
|
|
3264
|
+
marks.mdWikilink.create({
|
|
3265
|
+
target,
|
|
3266
|
+
display
|
|
3267
|
+
})
|
|
3268
|
+
]);
|
|
3127
3269
|
}
|
|
3128
3270
|
/**
|
|
3129
3271
|
* Push `[from, to, marks]` to `out`, coalescing with the previous chunk
|
|
@@ -3513,7 +3655,9 @@ function defineMdTag() {
|
|
|
3513
3655
|
parseDOM: [{ tag: "span.md-tag" }]
|
|
3514
3656
|
});
|
|
3515
3657
|
}
|
|
3516
|
-
/**
|
|
3658
|
+
/**
|
|
3659
|
+
* Covers the whole `[[target]]`/`[[target|alias]]` source.
|
|
3660
|
+
*/
|
|
3517
3661
|
function defineMdWikilink() {
|
|
3518
3662
|
return defineMarkSpec({
|
|
3519
3663
|
name: "mdWikilink",
|
|
@@ -3547,7 +3691,9 @@ function defineMdFile() {
|
|
|
3547
3691
|
parseDOM: [{ tag: "span.md-file" }]
|
|
3548
3692
|
});
|
|
3549
3693
|
}
|
|
3550
|
-
/**
|
|
3694
|
+
/**
|
|
3695
|
+
* Covers the whole `$formula$` source, dollars included.
|
|
3696
|
+
*/
|
|
3551
3697
|
function defineMdMath() {
|
|
3552
3698
|
return defineMarkSpec({
|
|
3553
3699
|
name: "mdMath",
|
|
@@ -3562,9 +3708,10 @@ function defineMdMath() {
|
|
|
3562
3708
|
});
|
|
3563
3709
|
}
|
|
3564
3710
|
/**
|
|
3565
|
-
* Wraps a whole
|
|
3566
|
-
* link, autolink,
|
|
3567
|
-
*
|
|
3711
|
+
* 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.
|
|
3568
3715
|
* `excludes: ''` lets nested units carry two of these marks at once.
|
|
3569
3716
|
*/
|
|
3570
3717
|
function defineMdPack() {
|
|
@@ -3574,7 +3721,10 @@ function defineMdPack() {
|
|
|
3574
3721
|
inclusive: false,
|
|
3575
3722
|
attrs: {
|
|
3576
3723
|
key: {},
|
|
3577
|
-
data: { default: null }
|
|
3724
|
+
data: { default: null },
|
|
3725
|
+
slot: { default: null },
|
|
3726
|
+
revealInFocus: { default: null },
|
|
3727
|
+
revealInHide: { default: null }
|
|
3578
3728
|
},
|
|
3579
3729
|
toDOM: (mark) => {
|
|
3580
3730
|
return [
|
|
@@ -3624,7 +3774,9 @@ const MARKER_IDS = /* @__PURE__ */ new Set([
|
|
|
3624
3774
|
LEZER_NODE_IDS.StrikethroughMark,
|
|
3625
3775
|
LEZER_NODE_IDS.HighlightMark
|
|
3626
3776
|
]);
|
|
3627
|
-
/**
|
|
3777
|
+
/**
|
|
3778
|
+
* The opening and closing delimiter tokens of a toggleable node.
|
|
3779
|
+
*/
|
|
3628
3780
|
function delimiters(node) {
|
|
3629
3781
|
return [node.children[0], node.children.at(-1)];
|
|
3630
3782
|
}
|
|
@@ -3672,7 +3824,9 @@ function engulf(nodes, from, to) {
|
|
|
3672
3824
|
}
|
|
3673
3825
|
return [from, to];
|
|
3674
3826
|
}
|
|
3675
|
-
/**
|
|
3827
|
+
/**
|
|
3828
|
+
* Shrink [from, to] so it starts and ends on non-whitespace.
|
|
3829
|
+
*/
|
|
3676
3830
|
function trimRange(text, from, to) {
|
|
3677
3831
|
while (from < to && isSpaceChar(text.charCodeAt(from))) from++;
|
|
3678
3832
|
while (to > from && isSpaceChar(text.charCodeAt(to - 1))) to--;
|
|
@@ -3832,7 +3986,9 @@ function caretPlan(text, pos, spec) {
|
|
|
3832
3986
|
pos
|
|
3833
3987
|
};
|
|
3834
3988
|
}
|
|
3835
|
-
/**
|
|
3989
|
+
/**
|
|
3990
|
+
* Whether `pos` sits where inserted syntax could not parse: inside an atom or inside another span's delimiters.
|
|
3991
|
+
*/
|
|
3836
3992
|
function insideAtom(nodes, pos) {
|
|
3837
3993
|
for (const node of nodes) if (node.from < pos && pos < node.to) {
|
|
3838
3994
|
const content = nestableContent(node);
|
|
@@ -3971,7 +4127,8 @@ function getLinkUnitAt(state, pos) {
|
|
|
3971
4127
|
title: ""
|
|
3972
4128
|
};
|
|
3973
4129
|
}
|
|
3974
|
-
|
|
4130
|
+
const linkData = packAttrs.data;
|
|
4131
|
+
if (linkData.isReference) {
|
|
3975
4132
|
const text = linkText == null ? {
|
|
3976
4133
|
from: unit.from,
|
|
3977
4134
|
to: unit.to
|
|
@@ -3985,8 +4142,8 @@ function getLinkUnitAt(state, pos) {
|
|
|
3985
4142
|
to: unit.to
|
|
3986
4143
|
},
|
|
3987
4144
|
text,
|
|
3988
|
-
href:
|
|
3989
|
-
title:
|
|
4145
|
+
href: linkData.href,
|
|
4146
|
+
title: linkData.title
|
|
3990
4147
|
};
|
|
3991
4148
|
}
|
|
3992
4149
|
const uri = lastMarkRunIn(state, unit, "mdLinkUri");
|
|
@@ -4007,19 +4164,23 @@ function getLinkUnitAt(state, pos) {
|
|
|
4007
4164
|
from: destFrom,
|
|
4008
4165
|
to: unit.to - 1
|
|
4009
4166
|
},
|
|
4010
|
-
href:
|
|
4011
|
-
title:
|
|
4167
|
+
href: linkData.href,
|
|
4168
|
+
title: linkData.title
|
|
4012
4169
|
};
|
|
4013
4170
|
}
|
|
4014
4171
|
|
|
4015
4172
|
//#endregion
|
|
4016
4173
|
//#region src/extensions/link-commands.ts
|
|
4017
|
-
/**
|
|
4174
|
+
/**
|
|
4175
|
+
* Normalize a typed URL with the existing autolink logic, else keep it verbatim.
|
|
4176
|
+
*/
|
|
4018
4177
|
function normalizeHref(raw) {
|
|
4019
4178
|
const value = raw.trim();
|
|
4020
4179
|
return value ? getAutolinkHref(value) ?? value : "";
|
|
4021
4180
|
}
|
|
4022
|
-
/**
|
|
4181
|
+
/**
|
|
4182
|
+
* The `( ... )` body for a link: the href plus an optional CommonMark title.
|
|
4183
|
+
*/
|
|
4023
4184
|
function destText(href, title) {
|
|
4024
4185
|
return href + (title ? ` "${title.replaceAll(/(["\\])/g, String.raw`\$1`)}"` : "");
|
|
4025
4186
|
}
|
|
@@ -4059,7 +4220,9 @@ function insertLink({ href, title, wrapText = true } = {}) {
|
|
|
4059
4220
|
return true;
|
|
4060
4221
|
};
|
|
4061
4222
|
}
|
|
4062
|
-
/**
|
|
4223
|
+
/**
|
|
4224
|
+
* Rewrite the `( ... )` of the link at the caret/selection.
|
|
4225
|
+
*/
|
|
4063
4226
|
function updateLink(attrs) {
|
|
4064
4227
|
return (state, dispatch) => {
|
|
4065
4228
|
const link = getLinkUnitAt(state, state.selection.from);
|
|
@@ -4069,7 +4232,9 @@ function updateLink(attrs) {
|
|
|
4069
4232
|
return true;
|
|
4070
4233
|
};
|
|
4071
4234
|
}
|
|
4072
|
-
/**
|
|
4235
|
+
/**
|
|
4236
|
+
* Unwrap the link at the caret: keep the label text, drop the syntax.
|
|
4237
|
+
*/
|
|
4073
4238
|
function removeLink() {
|
|
4074
4239
|
return (state, dispatch) => {
|
|
4075
4240
|
const link = getLinkUnitAt(state, state.selection.from);
|
|
@@ -4290,26 +4455,38 @@ const listInputRules = [
|
|
|
4290
4455
|
function defineMeowdownListInputRules() {
|
|
4291
4456
|
return union(listInputRules.map(defineInputRule));
|
|
4292
4457
|
}
|
|
4293
|
-
/**
|
|
4458
|
+
/**
|
|
4459
|
+
* Circle checkbox task: a `task` list item with a `+` marker.
|
|
4460
|
+
*/
|
|
4294
4461
|
function wrapInCircleTask() {
|
|
4295
4462
|
return wrapInList({
|
|
4296
4463
|
kind: "task",
|
|
4297
4464
|
marker: "+"
|
|
4298
4465
|
});
|
|
4299
4466
|
}
|
|
4300
|
-
/**
|
|
4467
|
+
/**
|
|
4468
|
+
* Square checkbox task: a `task` list item with the canonical `-` marker.
|
|
4469
|
+
*/
|
|
4301
4470
|
function wrapInSquareTask() {
|
|
4302
4471
|
return wrapInList({
|
|
4303
4472
|
kind: "task",
|
|
4304
4473
|
marker: null
|
|
4305
4474
|
});
|
|
4306
4475
|
}
|
|
4307
|
-
/**
|
|
4476
|
+
/**
|
|
4477
|
+
* The attributes of the list item whose own content holds the selection, if
|
|
4478
|
+
* any. A continuation block deeper inside an item (a paragraph after the
|
|
4479
|
+
* item's first block) is not the item's content, so it reports no list: the
|
|
4480
|
+
* cycle commands then wrap it into a new nested list instead of reading the
|
|
4481
|
+
* ancestor's kind.
|
|
4482
|
+
*/
|
|
4308
4483
|
function getListAttrsAtSelection(state) {
|
|
4309
|
-
const {
|
|
4484
|
+
const { selection } = state;
|
|
4485
|
+
if (isNodeSelection(selection) && isNodeOfType(selection.node, "list")) return selection.node.attrs;
|
|
4486
|
+
const { $from } = selection;
|
|
4310
4487
|
for (let depth = $from.depth; depth > 0; depth--) {
|
|
4311
4488
|
const node = $from.node(depth);
|
|
4312
|
-
if (isNodeOfType(node, "list")) return node.attrs;
|
|
4489
|
+
if (isNodeOfType(node, "list")) return $from.index(depth) === 0 ? node.attrs : null;
|
|
4313
4490
|
}
|
|
4314
4491
|
return null;
|
|
4315
4492
|
}
|
|
@@ -4340,18 +4517,18 @@ function cycleCheckableList() {
|
|
|
4340
4517
|
function cycleBulletOrderedList() {
|
|
4341
4518
|
return (state, dispatch, view) => {
|
|
4342
4519
|
const attrs = getListAttrsAtSelection(state);
|
|
4343
|
-
|
|
4520
|
+
if (attrs?.kind === "bullet" || attrs?.kind === "ordered") return toggleList({
|
|
4344
4521
|
kind: "ordered",
|
|
4345
4522
|
marker: null,
|
|
4346
4523
|
checked: false,
|
|
4347
4524
|
collapsed: false
|
|
4348
|
-
}
|
|
4525
|
+
})(state, dispatch, view);
|
|
4526
|
+
return wrapInList({
|
|
4349
4527
|
kind: "bullet",
|
|
4350
4528
|
marker: null,
|
|
4351
4529
|
checked: false,
|
|
4352
4530
|
collapsed: false
|
|
4353
|
-
};
|
|
4354
|
-
return toggleList(next)(state, dispatch, view);
|
|
4531
|
+
})(state, dispatch, view);
|
|
4355
4532
|
};
|
|
4356
4533
|
}
|
|
4357
4534
|
function toggleListCollapsed() {
|
|
@@ -4575,7 +4752,9 @@ var MathMarkView = class {
|
|
|
4575
4752
|
});
|
|
4576
4753
|
}
|
|
4577
4754
|
};
|
|
4578
|
-
/**
|
|
4755
|
+
/**
|
|
4756
|
+
* Inline math rendering: a KaTeX preview on the `mdMath` mark.
|
|
4757
|
+
*/
|
|
4579
4758
|
function defineMath() {
|
|
4580
4759
|
return defineMarkView({
|
|
4581
4760
|
name: "mdMath",
|
|
@@ -4884,7 +5063,9 @@ function defineMoveBlock() {
|
|
|
4884
5063
|
//#endregion
|
|
4885
5064
|
//#region src/extensions/pending-replacement.ts
|
|
4886
5065
|
const pendingReplacementKey = new PluginKey("meowdownPendingReplacement");
|
|
4887
|
-
/**
|
|
5066
|
+
/**
|
|
5067
|
+
* The active pending replacement, or null when there is none.
|
|
5068
|
+
*/
|
|
4888
5069
|
function getPendingReplacement(state) {
|
|
4889
5070
|
return pendingReplacementKey.getState(state)?.pending ?? null;
|
|
4890
5071
|
}
|
|
@@ -5026,7 +5207,9 @@ function definePendingReplacementCommands() {
|
|
|
5026
5207
|
discardPendingReplacement
|
|
5027
5208
|
});
|
|
5028
5209
|
}
|
|
5029
|
-
/**
|
|
5210
|
+
/**
|
|
5211
|
+
* Accept on Mod-Enter and discard on Escape, only while a replacement is pending.
|
|
5212
|
+
*/
|
|
5030
5213
|
function definePendingReplacementKeymap() {
|
|
5031
5214
|
return defineKeymap({
|
|
5032
5215
|
"Mod-Enter": acceptPendingReplacement(),
|
|
@@ -5343,22 +5526,30 @@ function defineEditorExtension(options = {}) {
|
|
|
5343
5526
|
|
|
5344
5527
|
//#endregion
|
|
5345
5528
|
//#region src/extensions/schema.ts
|
|
5346
|
-
/**
|
|
5529
|
+
/**
|
|
5530
|
+
* The schema shared by every parser and serializer, built once and cached.
|
|
5531
|
+
*/
|
|
5347
5532
|
const getSharedSchema = /* @__PURE__ */ once(() => {
|
|
5348
5533
|
const schema = defineEditorExtension().schema;
|
|
5349
5534
|
if (schema == null) throw new Error("Unexpected empty schema");
|
|
5350
5535
|
return schema;
|
|
5351
5536
|
});
|
|
5352
|
-
/**
|
|
5537
|
+
/**
|
|
5538
|
+
* Typed node builders bound to the shared schema.
|
|
5539
|
+
*/
|
|
5353
5540
|
const getNodeBuilders = /* @__PURE__ */ once(() => {
|
|
5354
5541
|
return createNodeBuilders(getSharedSchema());
|
|
5355
5542
|
});
|
|
5356
|
-
/**
|
|
5543
|
+
/**
|
|
5544
|
+
* Typed mark builders bound to the shared schema.
|
|
5545
|
+
*/
|
|
5357
5546
|
const getMarkBuilders = /* @__PURE__ */ once(() => {
|
|
5358
5547
|
return createMarkBuilders(getSharedSchema());
|
|
5359
5548
|
});
|
|
5360
5549
|
const MARK_BUILDERS_CACHE_KEY = "meowdown_mark_builders";
|
|
5361
|
-
/**
|
|
5550
|
+
/**
|
|
5551
|
+
* Typed mark builders bound to a specific schema, cached per schema.
|
|
5552
|
+
*/
|
|
5362
5553
|
function getMarkBuildersForSchema(schema) {
|
|
5363
5554
|
const cached = schema.cached[MARK_BUILDERS_CACHE_KEY];
|
|
5364
5555
|
if (cached) return cached;
|
|
@@ -5367,7 +5558,9 @@ function getMarkBuildersForSchema(schema) {
|
|
|
5367
5558
|
return builders;
|
|
5368
5559
|
}
|
|
5369
5560
|
const NODE_BUILDERS_CACHE_KEY = "meowdown_node_builders";
|
|
5370
|
-
/**
|
|
5561
|
+
/**
|
|
5562
|
+
* Typed node builders bound to a specific schema, cached per schema.
|
|
5563
|
+
*/
|
|
5371
5564
|
function getNodeBuildersForSchema(schema) {
|
|
5372
5565
|
const cached = schema.cached[NODE_BUILDERS_CACHE_KEY];
|
|
5373
5566
|
if (cached) return cached;
|
|
@@ -5512,7 +5705,9 @@ function convertHeading(nodes, cursor, text, level, isSetext) {
|
|
|
5512
5705
|
closingHashes
|
|
5513
5706
|
}, content);
|
|
5514
5707
|
}
|
|
5515
|
-
/**
|
|
5708
|
+
/**
|
|
5709
|
+
* Count the `=` / `-` characters in a setext underline run.
|
|
5710
|
+
*/
|
|
5516
5711
|
function countUnderlineChars(text, from, to) {
|
|
5517
5712
|
if (from < 0) return 0;
|
|
5518
5713
|
let count = 0;
|
|
@@ -5522,7 +5717,9 @@ function countUnderlineChars(text, from, to) {
|
|
|
5522
5717
|
}
|
|
5523
5718
|
return count;
|
|
5524
5719
|
}
|
|
5525
|
-
/**
|
|
5720
|
+
/**
|
|
5721
|
+
* Count the `#` characters between `from` and `to`.
|
|
5722
|
+
*/
|
|
5526
5723
|
function countHashChars(text, from, to) {
|
|
5527
5724
|
if (from < 0) return 0;
|
|
5528
5725
|
let count = 0;
|
|
@@ -5540,7 +5737,9 @@ function measureContentColumn(text, from) {
|
|
|
5540
5737
|
for (let index = lineStart; index < from; index++) col += text.charCodeAt(index) === 9 ? 4 - col % 4 : 1;
|
|
5541
5738
|
return col;
|
|
5542
5739
|
}
|
|
5543
|
-
/**
|
|
5740
|
+
/**
|
|
5741
|
+
* Drop a line's leading whitespace up to `column`, counting a tab as `4 - col % 4` columns.
|
|
5742
|
+
*/
|
|
5544
5743
|
function sliceColumn(line, column) {
|
|
5545
5744
|
let col = 0;
|
|
5546
5745
|
let index = 0;
|
|
@@ -5632,7 +5831,9 @@ function convertList(nodes, cursor, text, kind) {
|
|
|
5632
5831
|
}
|
|
5633
5832
|
return items;
|
|
5634
5833
|
}
|
|
5635
|
-
/**
|
|
5834
|
+
/**
|
|
5835
|
+
* The marker style at `cursor`, plus the start number of an ordered item.
|
|
5836
|
+
*/
|
|
5636
5837
|
function readListMark(cursor, text, kind) {
|
|
5637
5838
|
if (kind === "ordered") {
|
|
5638
5839
|
const delimiterCode = text.charCodeAt(cursor.to - 1);
|
|
@@ -5859,7 +6060,9 @@ function canonicalizeTableRow(line) {
|
|
|
5859
6060
|
function normalizeLine(line) {
|
|
5860
6061
|
return canonicalizeTableRow(line) ?? collapseWhitespace(line);
|
|
5861
6062
|
}
|
|
5862
|
-
/**
|
|
6063
|
+
/**
|
|
6064
|
+
* Classify how `markdown` survives the editor's parse-then-serialize round trip.
|
|
6065
|
+
*/
|
|
5863
6066
|
function checkRoundTrip(markdown, options = {}) {
|
|
5864
6067
|
const doc = markdownToDoc(markdown, { frontmatter: options.frontmatter });
|
|
5865
6068
|
const serialized = docToMarkdown(doc, { frontmatter: options.frontmatter });
|
|
@@ -6036,7 +6239,9 @@ function applyTweetHeight(iframe, height) {
|
|
|
6036
6239
|
const YOUTUBE_HOSTS = /^(?:www\.|m\.)?(?:youtube\.com|youtube-nocookie\.com)$/i;
|
|
6037
6240
|
const YOUTU_BE_HOST = /^(?:www\.)?youtu\.be$/i;
|
|
6038
6241
|
const VIDEO_ID = /^[\w-]{11}$/;
|
|
6039
|
-
/**
|
|
6242
|
+
/**
|
|
6243
|
+
* Extract `{ videoId, startSeconds? }` from any watch/shorts/embed/live/`youtu.be` URL.
|
|
6244
|
+
*/
|
|
6040
6245
|
function parseYouTube(src) {
|
|
6041
6246
|
let url;
|
|
6042
6247
|
try {
|
|
@@ -6059,7 +6264,9 @@ function parseYouTube(src) {
|
|
|
6059
6264
|
startSeconds
|
|
6060
6265
|
};
|
|
6061
6266
|
}
|
|
6062
|
-
/**
|
|
6267
|
+
/**
|
|
6268
|
+
* `90`, `90s`, `1m30s`, `1h2m3s` to seconds.
|
|
6269
|
+
*/
|
|
6063
6270
|
function parseStartSeconds(value) {
|
|
6064
6271
|
if (/^\d+$/.test(value)) return Number(value);
|
|
6065
6272
|
const matched = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/.exec(value);
|
|
@@ -6085,7 +6292,9 @@ const matchYouTube = (src) => {
|
|
|
6085
6292
|
//#endregion
|
|
6086
6293
|
//#region src/extensions/embed.ts
|
|
6087
6294
|
const EMBED_MATCHERS = [matchYouTube, matchTweet];
|
|
6088
|
-
/**
|
|
6295
|
+
/**
|
|
6296
|
+
* Detect a tweet/YouTube embed in an image `src`, or `undefined` for a plain image.
|
|
6297
|
+
*/
|
|
6089
6298
|
function matchEmbed(src) {
|
|
6090
6299
|
for (const match of EMBED_MATCHERS) {
|
|
6091
6300
|
const descriptor = match(src);
|
|
@@ -6170,7 +6379,9 @@ function createExitBoundaryPlugin(onExitBoundary) {
|
|
|
6170
6379
|
} }
|
|
6171
6380
|
});
|
|
6172
6381
|
}
|
|
6173
|
-
/**
|
|
6382
|
+
/**
|
|
6383
|
+
* Call `onExitBoundary` when an arrow key press would leave the document boundary.
|
|
6384
|
+
*/
|
|
6174
6385
|
function defineExitBoundaryHandler(onExitBoundary) {
|
|
6175
6386
|
return withPriority$1(definePlugin(createExitBoundaryPlugin(onExitBoundary)), Priority$1.low);
|
|
6176
6387
|
}
|
|
@@ -6253,7 +6464,9 @@ function takePastedFiles(data, options) {
|
|
|
6253
6464
|
const defaultOnFileSaveError = (error) => {
|
|
6254
6465
|
console.error("[meowdown] failed to save pasted file:", error);
|
|
6255
6466
|
};
|
|
6256
|
-
/**
|
|
6467
|
+
/**
|
|
6468
|
+
* Escape `\`, `[`, and `]` so a filename stays inside its `[text]` label.
|
|
6469
|
+
*/
|
|
6257
6470
|
function escapeLinkText(name) {
|
|
6258
6471
|
return name.replaceAll(/[\\[\]]/g, String.raw`\$&`);
|
|
6259
6472
|
}
|
|
@@ -6337,7 +6550,9 @@ function formatFileSize(bytes) {
|
|
|
6337
6550
|
|
|
6338
6551
|
//#endregion
|
|
6339
6552
|
//#region src/extensions/file-view.ts
|
|
6340
|
-
/**
|
|
6553
|
+
/**
|
|
6554
|
+
* `data-file-kind` values by file extension, for host CSS theming.
|
|
6555
|
+
*/
|
|
6341
6556
|
const FILE_KIND_BY_EXTENSION = /* @__PURE__ */ new Map([
|
|
6342
6557
|
["pdf", "pdf"],
|
|
6343
6558
|
["zip", "archive"],
|
|
@@ -6368,7 +6583,9 @@ const FILE_KIND_BY_EXTENSION = /* @__PURE__ */ new Map([
|
|
|
6368
6583
|
["txt", "text"],
|
|
6369
6584
|
["md", "text"]
|
|
6370
6585
|
]);
|
|
6371
|
-
/**
|
|
6586
|
+
/**
|
|
6587
|
+
* Classify a file destination for the pill's `data-file-kind` attribute.
|
|
6588
|
+
*/
|
|
6372
6589
|
function getFileKind(href) {
|
|
6373
6590
|
const path = href.split(/[?#]/, 1)[0];
|
|
6374
6591
|
const dot = path.lastIndexOf(".");
|
|
@@ -6377,7 +6594,9 @@ function getFileKind(href) {
|
|
|
6377
6594
|
return FILE_KIND_BY_EXTENSION.get(extension) ?? "generic";
|
|
6378
6595
|
}
|
|
6379
6596
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
6380
|
-
/**
|
|
6597
|
+
/**
|
|
6598
|
+
* A minimal document-outline icon, drawn in `currentColor`.
|
|
6599
|
+
*/
|
|
6381
6600
|
function buildFileIcon() {
|
|
6382
6601
|
const svg = document.createElementNS(SVG_NS, "svg");
|
|
6383
6602
|
svg.setAttribute("class", "md-file-view-icon");
|
|
@@ -6546,7 +6765,9 @@ function defineTagClickHandler(onClick) {
|
|
|
6546
6765
|
//#endregion
|
|
6547
6766
|
//#region src/extensions/wikilink-click.ts
|
|
6548
6767
|
const wikilinkClickKey = new PluginKey("meowdown-wikilink-click");
|
|
6549
|
-
/**
|
|
6768
|
+
/**
|
|
6769
|
+
* Exported for tests.
|
|
6770
|
+
*/
|
|
6550
6771
|
function findWikilinkAt(state, pos) {
|
|
6551
6772
|
const range = getMarkRangeAt(state, pos, "mdWikilink");
|
|
6552
6773
|
if (!range) return;
|
|
@@ -6676,7 +6897,9 @@ function findImageForPreview(view, preview) {
|
|
|
6676
6897
|
if (!content) return;
|
|
6677
6898
|
return findImageAt(view.state, view.posAtDOM(content, 0));
|
|
6678
6899
|
}
|
|
6679
|
-
/**
|
|
6900
|
+
/**
|
|
6901
|
+
* Fingers wander a little during a tap; past this it is a scroll or a drag.
|
|
6902
|
+
*/
|
|
6680
6903
|
const TAP_MOVE_TOLERANCE = 10;
|
|
6681
6904
|
function findTouch(touches, identifier) {
|
|
6682
6905
|
return Array.from(touches).find((touch) => touch.identifier === identifier);
|
|
@@ -6767,7 +6990,9 @@ function defineImageClickHandler(onClick) {
|
|
|
6767
6990
|
|
|
6768
6991
|
//#endregion
|
|
6769
6992
|
//#region src/extensions/image.ts
|
|
6770
|
-
/**
|
|
6993
|
+
/**
|
|
6994
|
+
* Show an `src` as-is when it is an http(s) URL, otherwise skip rendering it.
|
|
6995
|
+
*/
|
|
6771
6996
|
function defaultResolveImageUrl(src) {
|
|
6772
6997
|
return /^https?:\/\//i.test(src) ? src : void 0;
|
|
6773
6998
|
}
|
|
@@ -6843,7 +7068,9 @@ function rewriteMagicComment(view, range, patch, addToHistory) {
|
|
|
6843
7068
|
if (!addToHistory) transaction.setMeta("addToHistory", false);
|
|
6844
7069
|
view.dispatch(transaction);
|
|
6845
7070
|
}
|
|
6846
|
-
/**
|
|
7071
|
+
/**
|
|
7072
|
+
* Persist a resized width and height into the trailing magic comment.
|
|
7073
|
+
*/
|
|
6847
7074
|
function commitImageSize(view, content, rawWidth, rawHeight) {
|
|
6848
7075
|
const pos = view.posAtDOM(content, 0);
|
|
6849
7076
|
const range = getMarkRangeAt(view.state, pos, "mdImage");
|
|
@@ -6928,7 +7155,9 @@ var ImageMarkView = class {
|
|
|
6928
7155
|
ignoreMutation(mutation) {
|
|
6929
7156
|
return !this.#contentDOM.contains(mutation.target);
|
|
6930
7157
|
}
|
|
6931
|
-
/**
|
|
7158
|
+
/**
|
|
7159
|
+
* Build the inline preview for the image `src`: an embed iframe or a resizable `<img>`.
|
|
7160
|
+
*/
|
|
6932
7161
|
#renderPreview() {
|
|
6933
7162
|
const { src } = this.#attrs;
|
|
6934
7163
|
const embed = matchEmbed(src);
|
|
@@ -7038,7 +7267,9 @@ function defineImage(options = {}) {
|
|
|
7038
7267
|
|
|
7039
7268
|
//#endregion
|
|
7040
7269
|
//#region src/extensions/key-bindings.ts
|
|
7041
|
-
/**
|
|
7270
|
+
/**
|
|
7271
|
+
* Human-readable descriptions of the editor's formatting and heading shortcuts.
|
|
7272
|
+
*/
|
|
7042
7273
|
const EDITOR_KEY_BINDINGS = {
|
|
7043
7274
|
"Mod-b": "Bold",
|
|
7044
7275
|
"Mod-i": "Italic",
|
|
@@ -7390,7 +7621,9 @@ function defineSubstitutionEnterRules() {
|
|
|
7390
7621
|
});
|
|
7391
7622
|
}));
|
|
7392
7623
|
}
|
|
7393
|
-
/**
|
|
7624
|
+
/**
|
|
7625
|
+
* Apply the editor's automatic plain-text substitutions.
|
|
7626
|
+
*/
|
|
7394
7627
|
function defineSubstitution() {
|
|
7395
7628
|
return union(defineSubstitutionInputRules(), defineSubstitutionUndo(), defineSubstitutionEnterRules());
|
|
7396
7629
|
}
|
|
@@ -7454,7 +7687,9 @@ if (typeof window !== "undefined") {
|
|
|
7454
7687
|
function getIsTouchInput() {
|
|
7455
7688
|
return lastIsTouchInput;
|
|
7456
7689
|
}
|
|
7457
|
-
/**
|
|
7690
|
+
/**
|
|
7691
|
+
* Calls `listener` whenever {@link getIsTouchInput} may report a new value.
|
|
7692
|
+
*/
|
|
7458
7693
|
function onIsTouchInputChange(listener) {
|
|
7459
7694
|
listeners.add(listener);
|
|
7460
7695
|
return () => {
|
|
@@ -7667,7 +7902,7 @@ function cleanTextFromSlice(slice, options = {}) {
|
|
|
7667
7902
|
blockNode.descendants((textNode) => {
|
|
7668
7903
|
if (!textNode.isText || !textNode.text) return true;
|
|
7669
7904
|
const textNodeMarks = textNode.marks.map((mark) => mark.type.name);
|
|
7670
|
-
if (!(textNodeMarks.some((markName) => SYNTAX_MARK_NAMES.
|
|
7905
|
+
if (!(textNodeMarks.some((markName) => SYNTAX_MARK_NAMES.includes(markName)) && !(options.preserveMathSource && textNodeMarks.includes("mdMath")))) parts.push(textNode.text);
|
|
7671
7906
|
return false;
|
|
7672
7907
|
});
|
|
7673
7908
|
blocks.push(parts.join(""));
|