@meowdown/core 0.68.2 → 0.69.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.
package/dist/index.d.ts CHANGED
@@ -513,6 +513,7 @@ interface ReferenceDefinitionIndex {
513
513
  definitions: ReferenceDefinitions;
514
514
  nodes: ReadonlySet<EditorNode>;
515
515
  }
516
+ export declare function isReferenceDefinitionNode(node: EditorNode, parent: EditorNode | null, index: number): boolean;
516
517
  export declare function collectReferenceDefinitions(doc: EditorNode): ReferenceDefinitionIndex;
517
518
  //#endregion
518
519
  //#region src/extensions/wiki-embed.d.ts
package/dist/index.js CHANGED
@@ -1318,7 +1318,8 @@ function definePlainTextPaste() {
1318
1318
  function docToMarkdown(node, options = {}) {
1319
1319
  const out = new MdOut();
1320
1320
  if (options.frontmatter) emitFrontmatter(node.attrs.frontmatter, out);
1321
- emit$1(node, out);
1321
+ const child = node.childCount === 1 ? node.child(0) : void 0;
1322
+ if (!(child && isNodeOfType(child, "paragraph") && child.childCount === 0)) emit$1(node, out);
1322
1323
  return out.finish();
1323
1324
  }
1324
1325
  /**
@@ -1373,6 +1374,7 @@ var MdOut = class {
1373
1374
  this.pendingFirst = null;
1374
1375
  this.atLineStart = true;
1375
1376
  this.deferredBlankPrefix = null;
1377
+ this.contentEnd = 0;
1376
1378
  }
1377
1379
  /**
1378
1380
  * Write `text`, opening each embedded line with the current line prefix.
@@ -1407,20 +1409,30 @@ var MdOut = class {
1407
1409
  }
1408
1410
  }
1409
1411
  /**
1410
- * End a block that owns no line of its own (an empty paragraph): flush the
1411
- * blank line owed by the previous block now and owe the next block a fresh
1412
- * one, so each empty block yields one extra blank line. A marker-bearing
1413
- * block (an empty list item's `- `) still owns its first line and falls
1414
- * through to `closeBlock`. At the very start of the output there is no
1415
- * blank line to flush or owe - leading empty blocks vanish, mirroring the
1416
- * parser, which materializes empty paragraphs only between sibling blocks.
1412
+ * End a block that owns no line of its own (an empty paragraph): it is one
1413
+ * blank line. After another block, the blank line that block owes is this
1414
+ * one, and the next block is owed a fresh one, so each empty block adds a
1415
+ * blank line to the separator. With nothing owed (the start of the output)
1416
+ * the line is written directly and nothing is owed after it: a leading empty
1417
+ * block is a blank line, not a separator. A marker-bearing block (an empty
1418
+ * list item's `- `, an empty quote line's `>`) writes its marker as the line.
1417
1419
  */
1418
1420
  closeEmptyBlock() {
1419
- if (!this.atLineStart || this.pendingFirst !== null) {
1421
+ if (!this.atLineStart) {
1420
1422
  this.closeBlock();
1421
1423
  return;
1422
1424
  }
1423
- if (this.parts.length === 0) return;
1425
+ if (this.pendingFirst !== null) {
1426
+ this.emitDeferredBlankLine();
1427
+ const marker = this.pendingFirst;
1428
+ this.parts.push(marker.endsWith("] ") ? marker : marker.trimEnd(), "\n");
1429
+ this.pendingFirst = null;
1430
+ return;
1431
+ }
1432
+ if (this.deferredBlankPrefix === null) {
1433
+ this.parts.push(this.linePrefix.trimEnd(), "\n");
1434
+ return;
1435
+ }
1424
1436
  this.emitDeferredBlankLine();
1425
1437
  this.deferredBlankPrefix = this.linePrefix;
1426
1438
  }
@@ -1435,7 +1447,10 @@ var MdOut = class {
1435
1447
  this.pendingFirst = null;
1436
1448
  this.atLineStart = false;
1437
1449
  }
1438
- if (!this.atLineStart) this.parts.push("\n");
1450
+ if (!this.atLineStart) {
1451
+ this.parts.push("\n");
1452
+ this.contentEnd = this.parts.length;
1453
+ }
1439
1454
  this.atLineStart = true;
1440
1455
  this.deferredBlankPrefix = this.linePrefix;
1441
1456
  }
@@ -1486,14 +1501,14 @@ var MdOut = class {
1486
1501
  this.pendingFirst = firstLine !== null ? null : savedFirst;
1487
1502
  }
1488
1503
  finish() {
1489
- const text = this.parts.join("");
1504
+ const text = this.parts.slice(0, this.contentEnd).join("");
1490
1505
  let cut = text.length;
1491
1506
  for (let i = text.length - 1; i >= 0; i--) {
1492
1507
  const code = text.charCodeAt(i);
1493
1508
  if (code === 10) cut = i;
1494
1509
  else if (code !== 32 && code !== 9) break;
1495
1510
  }
1496
- return text.slice(0, cut) + "\n";
1511
+ return (cut === 0 ? "" : text.slice(0, cut) + "\n") + this.parts.slice(this.contentEnd).join("") || "\n";
1497
1512
  }
1498
1513
  emitDeferredBlankLine() {
1499
1514
  const prefix = this.deferredBlankPrefix;
@@ -6157,7 +6172,7 @@ function markdownToDoc(markdown, options = {}) {
6157
6172
  frontmatterBody = body;
6158
6173
  if (matchLength) rest = markdown.slice(matchLength);
6159
6174
  }
6160
- const blocks = collectBlocks(nodes, gfmBlockOnlyParser.parse(rest).cursor(), rest, 0);
6175
+ const blocks = collectBlocks(nodes, gfmBlockOnlyParser.parse(rest).cursor(), rest, 0, rest !== markdown);
6161
6176
  return nodes.doc(frontmatterBody === void 0 ? {} : { frontmatter: frontmatterBody }, blocks);
6162
6177
  }
6163
6178
  /**
@@ -6175,13 +6190,19 @@ function matchFrontmatter(markdown) {
6175
6190
  return [(match[1] ?? "").replace(/\r?\n$/, ""), match[0].length];
6176
6191
  }
6177
6192
  /**
6178
- * Walk the current node's children, converting each block-level child
6179
- * and flattening any node converter that returns multiple siblings
6180
- * (lists are the main case).
6193
+ * Walk the document's children, converting each block-level child and
6194
+ * flattening any node converter that returns multiple siblings (lists are the
6195
+ * main case). Blank lines before the first block and after the last one are
6196
+ * content too, one empty paragraph each; when a frontmatter block was peeled
6197
+ * off ahead of `text`, the first leading blank line is that block's separator.
6181
6198
  */
6182
- function collectBlocks(nodes, cursor, text, column) {
6199
+ function collectBlocks(nodes, cursor, text, column, afterFrontmatter) {
6183
6200
  const out = [];
6184
- if (!cursor.firstChild()) return out;
6201
+ if (!cursor.firstChild()) {
6202
+ appendEmptyParagraphs(out, nodes, Math.max(1, countNewlines(text, 0, text.length)));
6203
+ return out;
6204
+ }
6205
+ appendEmptyParagraphs(out, nodes, countNewlines(text, 0, cursor.from) - (afterFrontmatter ? 1 : 0));
6185
6206
  let previousTo;
6186
6207
  do {
6187
6208
  if (previousTo != null) appendGapParagraphs(out, nodes, text, previousTo, cursor.from);
@@ -6189,6 +6210,8 @@ function collectBlocks(nodes, cursor, text, column) {
6189
6210
  appendBlocks(out, nodes, convertBlock(nodes, cursor, text, column));
6190
6211
  } while (cursor.nextSibling());
6191
6212
  cursor.parent();
6213
+ const end = text.endsWith("\n") ? text.length - 1 : text.length;
6214
+ appendEmptyParagraphs(out, nodes, countNewlines(text, previousTo, end));
6192
6215
  return out;
6193
6216
  }
6194
6217
  /**
@@ -6218,9 +6241,15 @@ function appendBlocks(out, nodes, blocks) {
6218
6241
  * of them (the previous block's own terminator plus one per blank line).
6219
6242
  */
6220
6243
  function appendGapParagraphs(out, nodes, text, gapFrom, gapTo) {
6221
- let newlineCount = 0;
6222
- for (let i = gapFrom; i < gapTo; i++) if (text.charCodeAt(i) === 10) newlineCount++;
6223
- for (let i = 2; i < newlineCount; i++) out.push(nodes.paragraph());
6244
+ appendEmptyParagraphs(out, nodes, countNewlines(text, gapFrom, gapTo) - 2);
6245
+ }
6246
+ function appendEmptyParagraphs(out, nodes, count) {
6247
+ for (let i = 0; i < count; i++) out.push(nodes.paragraph());
6248
+ }
6249
+ function countNewlines(text, from, to) {
6250
+ let count = 0;
6251
+ for (let i = from; i < to; i++) if (text.charCodeAt(i) === 10) count++;
6252
+ return count;
6224
6253
  }
6225
6254
  function convertBlock(nodes, cursor, text, column) {
6226
6255
  switch (cursor.type.id) {
@@ -6459,30 +6488,44 @@ function convertHTMLComment(nodes, cursor, text, column) {
6459
6488
  /**
6460
6489
  * A blockquote's children start at column 0: every column an enclosing container
6461
6490
  * wrote sits in front of the `> ` marker on the line, and comes off with it.
6491
+ *
6492
+ * Blank quote lines before the first block and after the last one are empty
6493
+ * paragraphs, like the document's own edges. The quote's range ends on its last
6494
+ * line rather than after it, so every newline in the trailing slice is a blank
6495
+ * line, and a quote made of markers only has one more line than newlines.
6462
6496
  */
6463
6497
  function convertBlockquote(nodes, cursor, text) {
6464
6498
  const content = [];
6499
+ const from = cursor.from;
6500
+ const to = cursor.to;
6501
+ let previousTo;
6465
6502
  if (cursor.firstChild()) {
6466
- let previousTo;
6467
6503
  do {
6468
6504
  if (cursor.type.id === LEZER_NODE_IDS.QuoteMark) continue;
6469
- if (previousTo != null) appendGapParagraphs(content, nodes, text, previousTo, cursor.from);
6505
+ if (previousTo == null) appendEmptyParagraphs(content, nodes, countNewlines(text, from, cursor.from));
6506
+ else appendGapParagraphs(content, nodes, text, previousTo, cursor.from);
6470
6507
  previousTo = cursor.to;
6471
6508
  appendBlocks(content, nodes, convertBlock(nodes, cursor, text, 0));
6472
6509
  } while (cursor.nextSibling());
6473
6510
  cursor.parent();
6474
6511
  }
6512
+ appendEmptyParagraphs(content, nodes, previousTo == null ? countNewlines(text, from, to) + 1 : countNewlines(text, previousTo, to));
6475
6513
  return nodes.blockquote(content);
6476
6514
  }
6477
6515
  function convertList(nodes, cursor, text, column, kind) {
6478
- const items = [];
6516
+ const out = [];
6479
6517
  if (cursor.firstChild()) {
6480
- do
6481
- if (cursor.type.id === LEZER_NODE_IDS.ListItem) items.push(convertListItem(nodes, cursor, text, column, kind));
6482
- while (cursor.nextSibling());
6518
+ let previousContentEnd;
6519
+ do {
6520
+ if (cursor.type.id !== LEZER_NODE_IDS.ListItem) continue;
6521
+ if (previousContentEnd != null) appendGapParagraphs(out, nodes, text, previousContentEnd, cursor.from);
6522
+ const [item, contentEnd] = convertListItem(nodes, cursor, text, column, kind);
6523
+ out.push(item);
6524
+ previousContentEnd = contentEnd;
6525
+ } while (cursor.nextSibling());
6483
6526
  cursor.parent();
6484
6527
  }
6485
- return items;
6528
+ return out;
6486
6529
  }
6487
6530
  /**
6488
6531
  * The marker style at `cursor`, plus the start number of an ordered item.
@@ -6536,6 +6579,19 @@ function convertTaskItem(nodes, cursor, text, column) {
6536
6579
  paragraph
6537
6580
  };
6538
6581
  }
6582
+ /**
6583
+ * The gap between a list marker ending at `markTo` and the item's first content
6584
+ * at `contentFrom`. Only content that opens on the marker's own line measures a
6585
+ * gap; an item whose content starts on the next line takes the canonical single
6586
+ * space, the column its own continuation lines are indented to. A gap of 5+ is
6587
+ * indented code (a different node, so the content's column would be the code
6588
+ * block's), and 1 is the canonical default; only a 2-4 space gap is a faithful,
6589
+ * content-preserving variation.
6590
+ */
6591
+ function measureMarkerGap(text, contentFrom, markTo, markEndColumn) {
6592
+ const gap = markTo != null && text.lastIndexOf("\n", contentFrom - 1) < markTo ? measureContentColumn(text, contentFrom) - markEndColumn : 1;
6593
+ return gap >= 2 && gap <= 4 ? gap : 1;
6594
+ }
6539
6595
  function convertListItem(nodes, cursor, text, column, kind) {
6540
6596
  const content = [];
6541
6597
  let taskChecked;
@@ -6547,7 +6603,7 @@ function convertListItem(nodes, cursor, text, column, kind) {
6547
6603
  let markEndColumn = 0;
6548
6604
  let markerGap = 1;
6549
6605
  let contentColumn = column + markWidth + markerGap;
6550
- let sawContent = false;
6606
+ let previousTo;
6551
6607
  if (cursor.firstChild()) {
6552
6608
  do {
6553
6609
  if (cursor.type.id === LEZER_NODE_IDS.QuoteMark) continue;
@@ -6560,12 +6616,11 @@ function convertListItem(nodes, cursor, text, column, kind) {
6560
6616
  markEndColumn = measureContentColumn(text, cursor.to);
6561
6617
  continue;
6562
6618
  }
6563
- if (!sawContent) {
6564
- sawContent = true;
6565
- const gap = markTo != null && text.lastIndexOf("\n", cursor.from - 1) < markTo ? measureContentColumn(text, cursor.from) - markEndColumn : 1;
6566
- markerGap = gap >= 2 && gap <= 4 ? gap : 1;
6619
+ if (previousTo == null) {
6620
+ markerGap = measureMarkerGap(text, cursor.from, markTo, markEndColumn);
6567
6621
  contentColumn = column + markWidth + markerGap;
6568
- }
6622
+ } else appendGapParagraphs(content, nodes, text, previousTo, cursor.from);
6623
+ previousTo = cursor.to;
6569
6624
  if (kind === "bullet" && cursor.type.id === LEZER_NODE_IDS.Task) {
6570
6625
  const task = convertTaskItem(nodes, cursor, text, contentColumn);
6571
6626
  taskChecked = task.checked;
@@ -6588,7 +6643,7 @@ function convertListItem(nodes, cursor, text, column, kind) {
6588
6643
  taskMarker,
6589
6644
  markerGap
6590
6645
  };
6591
- return nodes.list(attrs, content);
6646
+ return [nodes.list(attrs, content), previousTo ?? cursor.to];
6592
6647
  }
6593
6648
  function convertCodeBlock(nodes, cursor, text) {
6594
6649
  const indented = cursor.type.id === LEZER_NODE_IDS.CodeBlock;
@@ -8809,4 +8864,4 @@ function getVirtualElementFromRange(view, range) {
8809
8864
  }
8810
8865
 
8811
8866
  //#endregion
8812
- 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, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getFileKind, getIsComposing, getLinkText, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isMarkOfType, isModEvent, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, normalizeHref, parseWikiEmbed, removeLink, renderMathInto, updateLink, wikiEmbedBasename, withPriority };
8867
+ 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, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, formatSizedWikiEmbed, getCodeTokens, getFileKind, getIsComposing, getLinkText, getLinkUnitAt, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, inlineTextToMarkChunksWithContext, insertLink, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isMarkOfType, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, normalizeHref, parseWikiEmbed, removeLink, renderMathInto, updateLink, wikiEmbedBasename, withPriority };
package/dist/style.css CHANGED
@@ -194,6 +194,8 @@
194
194
  --meowdown-hover-card-radius: .75rem;
195
195
  --meowdown-hover-card-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
196
196
  --meowdown-danger: light-dark(oklch(63.7% .208 25), oklch(71.1% .166 22));
197
+ --meowdown-input-border: color-mix(in oklab, var(--meowdown-text) 15%, transparent);
198
+ --meowdown-focus-ring: var(--meowdown-accent);
197
199
  --meowdown-placeholder: var(--meowdown-muted);
198
200
  --meowdown-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
199
201
  --meowdown-node-outline: light-dark(oklch(64.2% .178 46), oklch(74.1% .165 52));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/core",
3
3
  "type": "module",
4
- "version": "0.68.2",
4
+ "version": "0.69.0",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "@codemirror/language-data": "^6.5.2",
26
26
  "@floating-ui/dom": "^1.8.0",
27
27
  "@lezer/highlight": "^1.2.3",
28
- "@ocavue/utils": "^1.7.0",
28
+ "@ocavue/utils": "^1.8.0",
29
29
  "@prosekit/core": "^0.13.2",
30
30
  "@prosekit/extensions": "^0.18.2",
31
31
  "@prosekit/pm": "^0.1.19",
@@ -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.68.2"
46
+ "@meowdown/markdown": "^0.69.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@ocavue/tsconfig": "^0.7.1",