autumnnote 1.9.1 → 1.11.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.
@@ -5502,6 +5502,17 @@ function htmlToMarkdown(html) {
5502
5502
  * @param {number} [depth=0] - Current nesting depth used to indent nested list items.
5503
5503
  * @returns {string} The Markdown representation of the node subtree.
5504
5504
  */
5505
+ /**
5506
+ * Direct child elements matching a tag name. Used instead of the CSS
5507
+ * `:scope > tag` combinator, which this project's jsdom version resolves
5508
+ * incorrectly (matches descendants at any depth, not just direct children).
5509
+ * @param {Element} el
5510
+ * @param {string} tagName
5511
+ * @returns {Element[]}
5512
+ */
5513
+ function _directChildren(el, tagName) {
5514
+ return Array.from(el.children).filter((c) => c.tagName === tagName.toUpperCase());
5515
+ }
5505
5516
  function _domToMd(node, depth = 0) {
5506
5517
  if (node.nodeType === 3) return node.textContent.replace(/\s+/g, " ");
5507
5518
  if (node.nodeType !== 1) return "";
@@ -5527,6 +5538,12 @@ function _domToMd(node, depth = 0) {
5527
5538
  case "strike": return `~~${inner()}~~`;
5528
5539
  case "sup": return `^${inner()}^`;
5529
5540
  case "sub": return `~${inner()}~`;
5541
+ case "u": return `<u>${inner()}</u>`;
5542
+ case "span": {
5543
+ const style = el.getAttribute("style") || "";
5544
+ if (/\b(color|background-color|font-size)\s*:/.test(style)) return `<span style="${_escAttr(style)}">${inner()}</span>`;
5545
+ return inner();
5546
+ }
5530
5547
  case "code":
5531
5548
  if (el.closest("pre")) return inner();
5532
5549
  return `\`${inner()}\``;
@@ -5535,7 +5552,10 @@ function _domToMd(node, depth = 0) {
5535
5552
  const langMatch = /language-(\S+)/.exec(codeEl?.className || "");
5536
5553
  return `\n\n\`\`\`${langMatch ? langMatch[1] : ""}\n${(codeEl || el).textContent || ""}\n\`\`\`\n\n`;
5537
5554
  }
5538
- case "blockquote": return `\n\n${inner().trim().split("\n").map((l) => `> ${l}`).join("\n")}\n\n`;
5555
+ case "blockquote": {
5556
+ const rawLines = inner().trim().split("\n");
5557
+ return `\n\n${rawLines.filter((l, idx) => l.trim() !== "" || (rawLines[idx - 1] ?? "").trim() !== "").map((l) => l.trim() === "" ? ">" : `> ${l}`).join("\n")}\n\n`;
5558
+ }
5539
5559
  case "a": {
5540
5560
  const href = el.getAttribute("href") || "";
5541
5561
  return `[${inner()}](${href})`;
@@ -5545,22 +5565,20 @@ function _domToMd(node, depth = 0) {
5545
5565
  return `![${el.getAttribute("alt") || ""}](${src})`;
5546
5566
  }
5547
5567
  case "ul": {
5548
- const items = Array.from(el.querySelectorAll(":scope > li"));
5568
+ const items = _directChildren(el, "li");
5549
5569
  if (!items.length) return inner();
5550
5570
  const indent = " ".repeat(depth);
5551
5571
  const isChecklist = el.classList.contains("an-checklist");
5552
5572
  const lines = items.map((li) => {
5573
+ const cb = _directChildren(li, "input").find((c) => c.getAttribute("type") === "checkbox");
5553
5574
  let prefix = "- ";
5554
- if (isChecklist) {
5555
- const cb = li.querySelector("input[type=\"checkbox\"]");
5556
- prefix = (cb ? cb.checked : false) ? "- [x] " : "- [ ] ";
5557
- }
5575
+ if (isChecklist || cb) prefix = (cb ? cb.checked : false) ? "- [x] " : "- [ ] ";
5558
5576
  return `${indent}${prefix}${_domToMd(li, depth + 1).trim()}`;
5559
5577
  }).join("\n");
5560
5578
  return depth === 0 ? `\n\n${lines}\n\n` : `\n${lines}`;
5561
5579
  }
5562
5580
  case "ol": {
5563
- const items = Array.from(el.querySelectorAll(":scope > li"));
5581
+ const items = _directChildren(el, "li");
5564
5582
  if (!items.length) return inner();
5565
5583
  const indent = " ".repeat(depth);
5566
5584
  const lines = items.map((li, i) => `${indent}${i + 1}. ${_domToMd(li, depth + 1).trim()}`).join("\n");
@@ -5569,19 +5587,22 @@ function _domToMd(node, depth = 0) {
5569
5587
  case "li": return inner();
5570
5588
  case "hr": return "\n\n---\n\n";
5571
5589
  case "table": {
5572
- const rows = Array.from(el.querySelectorAll("tr"));
5573
- if (!rows.length) return inner();
5574
- const cellTexts = rows.map((tr) => Array.from(tr.querySelectorAll("th, td")).map((c) => c.textContent.trim().replaceAll("|", String.raw`\|`)));
5590
+ const allRows = Array.from(el.querySelectorAll("tr"));
5591
+ if (!allRows.length) return inner();
5592
+ const firstRowIsHeader = !!_directChildren(el, "thead")[0] || allRows[0].children.length > 0 && Array.from(allRows[0].children).every((c) => c.tagName === "TH");
5593
+ const cellTexts = allRows.map((tr) => Array.from(tr.querySelectorAll("th, td")).map((c) => c.textContent.trim().replaceAll("|", String.raw`\|`)));
5575
5594
  const cols = Math.max(...cellTexts.map((r) => r.length));
5576
5595
  const padRow = (row) => {
5577
5596
  const r = [...row];
5578
5597
  while (r.length < cols) r.push("");
5579
5598
  return r;
5580
5599
  };
5600
+ const bodyStart = firstRowIsHeader ? 1 : 0;
5601
+ const headerCells = firstRowIsHeader ? padRow(cellTexts[0]) : new Array(cols).fill("");
5581
5602
  let md = "\n\n";
5582
- md += `| ${padRow(cellTexts[0]).join(" | ")} |\n`;
5603
+ md += `| ${headerCells.join(" | ")} |\n`;
5583
5604
  md += `| ${new Array(cols).fill("---").join(" | ")} |\n`;
5584
- for (let r = 1; r < cellTexts.length; r++) md += `| ${padRow(cellTexts[r]).join(" | ")} |\n`;
5605
+ for (let r = bodyStart; r < cellTexts.length; r++) md += `| ${padRow(cellTexts[r]).join(" | ")} |\n`;
5585
5606
  return md + "\n";
5586
5607
  }
5587
5608
  default: return inner();
@@ -5596,15 +5617,33 @@ function _domToMd(node, depth = 0) {
5596
5617
  * @returns {boolean} `true` if any Markdown-like pattern is present, `false` otherwise.
5597
5618
  */
5598
5619
  function isMarkdown(text) {
5599
- return /^#{1,6} [^\s]|^[ \t]*[-*+] [^\s]|^[ \t]*\d+\. [^\s]|^> [^\s]|^```|^\*{2}[^*\n]+\*{2}/m.test(text) || /^.+\n=+\s*$/m.test(text) || /^.+\n-{2,}\s*$/m.test(text);
5620
+ return /^#{1,6} [^\s]|^[ \t]*[-*+] [^\s]|^[ \t]*\d+\. [^\s]|^> ?[^\s]|^```|^\*{2}[^*\n]+\*{2}/m.test(text) || /^.+\n=+\s*$/m.test(text) || /^.+\n-{2,}\s*$/m.test(text) || /^---\s*\n(?:[\s\S]*?\n)?(?:---|\.\.\.)\s*(?:\n|$)/.test(text) || /^\|.+\|[ \t]*\n\|[ \t:|-]+\|/m.test(text);
5600
5621
  }
5622
+ var BQ_RE = /^ {0,3}>( ?)(.*)$/;
5623
+ var HR_RE = /^ {0,3}([-*_])( *\1){2,}\s*$/;
5624
+ var HARD_BREAK = String.fromCharCode(1);
5601
5625
  /**
5602
5626
  * Converts a Markdown string to an HTML string.
5603
5627
  * @param {string} text
5604
5628
  * @returns {string}
5605
5629
  */
5606
5630
  function markdownToHTML(text) {
5607
- const lines = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
5631
+ let lines = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
5632
+ lines = _stripFrontmatter(lines);
5633
+ const refs = _extractReferenceDefinitions(lines);
5634
+ lines = refs.clean;
5635
+ _linkDefs = refs.linkDefs;
5636
+ _footnoteIds = refs.footnoteIds;
5637
+ return _parseBlocks(lines);
5638
+ }
5639
+ /**
5640
+ * Parses a line array into block-level HTML. Called recursively for content
5641
+ * nested inside a blockquote so nested quotes and block content (lists,
5642
+ * headings, etc.) inside `>` are parsed the same as top-level content.
5643
+ * @param {string[]} lines
5644
+ * @returns {string}
5645
+ */
5646
+ function _parseBlocks(lines) {
5608
5647
  const out = [];
5609
5648
  let i = 0;
5610
5649
  while (i < lines.length) {
@@ -5623,7 +5662,7 @@ function markdownToHTML(text) {
5623
5662
  i++;
5624
5663
  continue;
5625
5664
  }
5626
- if (line.trim() && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line) && !/^#{1,6} /.test(line) && i + 1 < lines.length) {
5665
+ if (line.trim() && !HR_RE.test(line) && !/^#{1,6} /.test(line) && i + 1 < lines.length) {
5627
5666
  if (/^=+\s*$/.test(lines[i + 1])) {
5628
5667
  out.push(`<h1>${_inline(line.trim())}</h1>`);
5629
5668
  i += 2;
@@ -5635,7 +5674,7 @@ function markdownToHTML(text) {
5635
5674
  continue;
5636
5675
  }
5637
5676
  }
5638
- if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
5677
+ if (HR_RE.test(line)) {
5639
5678
  out.push("<hr>");
5640
5679
  i++;
5641
5680
  continue;
@@ -5643,17 +5682,18 @@ function markdownToHTML(text) {
5643
5682
  const hMatch = /^(#{1,6})\s+(.+)$/.exec(line);
5644
5683
  if (hMatch) {
5645
5684
  const level = hMatch[1].length;
5646
- out.push(`<h${level}>${_inline(hMatch[2])}</h${level}>`);
5685
+ const content = hMatch[2].replace(/(?:^|\s)#+\s*$/, "");
5686
+ out.push(`<h${level}>${_inline(content)}</h${level}>`);
5647
5687
  i++;
5648
5688
  continue;
5649
5689
  }
5650
- if (line.startsWith("> ")) {
5690
+ if (BQ_RE.test(line)) {
5651
5691
  const bqLines = [];
5652
- while (i < lines.length && lines[i].startsWith("> ")) {
5653
- bqLines.push(lines[i].slice(2));
5692
+ while (i < lines.length && BQ_RE.test(lines[i])) {
5693
+ bqLines.push(BQ_RE.exec(lines[i])[2]);
5654
5694
  i++;
5655
5695
  }
5656
- out.push(`<blockquote>${bqLines.map(_inline).join("<br>")}</blockquote>`);
5696
+ out.push(`<blockquote>${_parseBlocks(bqLines)}</blockquote>`);
5657
5697
  continue;
5658
5698
  }
5659
5699
  if (/^[-*+] /.test(line)) {
@@ -5696,32 +5736,156 @@ function markdownToHTML(text) {
5696
5736
  continue;
5697
5737
  }
5698
5738
  const paraLines = [];
5699
- while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6} |> |[-*+] |\d+\. |```|---\s*$|\*{3}\s*$|_{3}\s*$)/.test(lines[i]) && !/^\|.+\|/.test(lines[i]) && !(i + 1 < lines.length && /^=+\s*$/.test(lines[i + 1])) && !(i + 1 < lines.length && /^-{2,}\s*$/.test(lines[i + 1]))) {
5739
+ while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6} |[-*+] |\d+\. |```)/.test(lines[i]) && !BQ_RE.test(lines[i]) && !HR_RE.test(lines[i]) && !/^\|.+\|/.test(lines[i]) && !(i + 1 < lines.length && /^=+\s*$/.test(lines[i + 1])) && !(i + 1 < lines.length && /^-{2,}\s*$/.test(lines[i + 1]))) {
5700
5740
  paraLines.push(lines[i]);
5701
5741
  i++;
5702
5742
  }
5703
- if (paraLines.length) out.push(`<p>${_inline(paraLines.join(" "))}</p>`);
5743
+ if (paraLines.length) out.push(`<p>${_inline(_joinParagraphLines(paraLines)).replaceAll(HARD_BREAK, "<br>")}</p>`);
5704
5744
  }
5705
5745
  return out.join("");
5706
5746
  }
5747
+ /** Reference-link and footnote definitions collected per markdownToHTML() call. */
5748
+ var _linkDefs = /* @__PURE__ */ new Map();
5749
+ var _footnoteIds = /* @__PURE__ */ new Set();
5750
+ /**
5751
+ * Strips a leading YAML frontmatter block (--- ... --- or --- ... ...) from
5752
+ * the line array, only when it is the very first line and the enclosed body
5753
+ * looks like YAML (key: value / list items / indented continuations) — this
5754
+ * disambiguates real frontmatter from a horizontal rule followed by prose.
5755
+ * @param {string[]} lines
5756
+ * @returns {string[]}
5757
+ */
5758
+ function _stripFrontmatter(lines) {
5759
+ if ((lines[0] || "").trim() !== "---") return lines;
5760
+ let closeIdx = -1;
5761
+ for (let j = 1; j < lines.length; j++) {
5762
+ const t = lines[j].trim();
5763
+ if (t === "---" || t === "...") {
5764
+ closeIdx = j;
5765
+ break;
5766
+ }
5767
+ }
5768
+ if (closeIdx === -1) return lines;
5769
+ if (!lines.slice(1, closeIdx).every((l) => l.trim() === "" || /^[ \t]*[\w$.-]+\s*:(\s|$)/.test(l) || /^[ \t]*-\s+\S/.test(l) || /^[ \t]+\S/.test(l))) return lines;
5770
+ let start = closeIdx + 1;
5771
+ if (lines[start] !== void 0 && lines[start].trim() === "") start++;
5772
+ return lines.slice(start);
5773
+ }
5707
5774
  /**
5708
- * Splits a GFM table row string into trimmed cell strings.
5709
- * '| a | b | c |' ['a', 'b', 'c']
5775
+ * Extracts GFM reference-link definitions (`[ref]: url "title"`) and footnote
5776
+ * definitions (`[^id]: text`) from the line array, skipping fenced code
5777
+ * regions. Returns the definition-free line array plus lookup maps.
5778
+ * @param {string[]} lines
5779
+ * @returns {{ clean: string[], linkDefs: Map<string, {href: string, title?: string}>, footnoteIds: Set<string> }}
5780
+ */
5781
+ function _extractReferenceDefinitions(lines) {
5782
+ const linkDefs = /* @__PURE__ */ new Map();
5783
+ const footnoteIds = /* @__PURE__ */ new Set();
5784
+ const clean = [];
5785
+ let inFence = false;
5786
+ const linkDefRe = /^\[([^\]]+)\]:\s*(\S+)(?:\s+"([^"]*)")?\s*$/;
5787
+ const footnoteDefRe = /^\[\^([^\]]+)\]:\s*(.+)$/;
5788
+ for (const line of lines) {
5789
+ if (/^```/.test(line)) {
5790
+ inFence = !inFence;
5791
+ clean.push(line);
5792
+ continue;
5793
+ }
5794
+ if (!inFence) {
5795
+ const fm = footnoteDefRe.exec(line);
5796
+ if (fm) {
5797
+ footnoteIds.add(fm[1]);
5798
+ continue;
5799
+ }
5800
+ const lm = linkDefRe.exec(line);
5801
+ if (lm) {
5802
+ linkDefs.set(lm[1].trim().toLowerCase(), {
5803
+ href: lm[2],
5804
+ title: lm[3]
5805
+ });
5806
+ continue;
5807
+ }
5808
+ }
5809
+ clean.push(line);
5810
+ }
5811
+ return {
5812
+ clean,
5813
+ linkDefs,
5814
+ footnoteIds
5815
+ };
5816
+ }
5817
+ /**
5818
+ * Joins a paragraph's source lines into one string, converting CommonMark
5819
+ * hard-break markers (a trailing backslash, or 2+ trailing spaces) on all
5820
+ * but the last line into a HARD_BREAK placeholder instead of a plain space.
5821
+ * @param {string[]} paraLines
5822
+ * @returns {string}
5823
+ */
5824
+ function _joinParagraphLines(paraLines) {
5825
+ let joined = "";
5826
+ for (let idx = 0; idx < paraLines.length; idx++) {
5827
+ const isLast = idx === paraLines.length - 1;
5828
+ const ln = paraLines[idx];
5829
+ if (!isLast && /\\$/.test(ln)) {
5830
+ joined += ln.replace(/\\$/, "") + HARD_BREAK;
5831
+ continue;
5832
+ }
5833
+ if (!isLast && / {2,}$/.test(ln)) {
5834
+ joined += ln.replace(/ {2,}$/, "") + HARD_BREAK;
5835
+ continue;
5836
+ }
5837
+ joined += ln + (isLast ? "" : " ");
5838
+ }
5839
+ return joined;
5840
+ }
5841
+ /**
5842
+ * Splits a GFM table row string into trimmed cell strings, treating an
5843
+ * escaped pipe (`\|`) as a literal character rather than a cell separator.
5844
+ * '| a | b | c |' → ['a', 'b', 'c']; '| a\|b | c |' → ['a|b', 'c']
5710
5845
  * @param {string} row
5711
5846
  * @returns {string[]}
5712
5847
  */
5713
5848
  function _parseTableRow(row) {
5714
- return row.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
5849
+ const trimmed = row.replace(/^\|/, "").replace(/\|$/, "");
5850
+ const cells = [];
5851
+ let cur = "";
5852
+ for (let i = 0; i < trimmed.length; i++) {
5853
+ if (trimmed[i] === "\\" && trimmed[i + 1] === "|") {
5854
+ cur += "|";
5855
+ i++;
5856
+ continue;
5857
+ }
5858
+ if (trimmed[i] === "|") {
5859
+ cells.push(cur);
5860
+ cur = "";
5861
+ continue;
5862
+ }
5863
+ cur += trimmed[i];
5864
+ }
5865
+ cells.push(cur);
5866
+ return cells.map((c) => c.trim());
5715
5867
  }
5716
5868
  function _parseListBlock(lines, startIdx) {
5717
5869
  const baseIndent = lines[startIdx].match(/^(\s*)/)[1].length;
5718
5870
  const isOL = /^\s*\d+\. /.test(lines[startIdx]);
5719
5871
  const items = [];
5720
5872
  let firstIsCB = null;
5873
+ let loose = false;
5874
+ let pendingBlank = false;
5721
5875
  let i = startIdx;
5722
5876
  while (i < lines.length) {
5723
5877
  const line = lines[i];
5724
- if (line.trim() === "") break;
5878
+ if (line.trim() === "") {
5879
+ const next = lines[i + 1];
5880
+ const nextIndent = next !== void 0 ? next.match(/^(\s*)/)[1].length : -1;
5881
+ const nextIsSameItem = next !== void 0 && /^\s*(?:[-*+]|\d+\.) /.test(next) && /^\s*\d+\. /.test(next) === isOL && nextIndent === baseIndent;
5882
+ const nextIsContinuation = next !== void 0 && next.trim() !== "" && nextIndent > baseIndent;
5883
+ if (!items.length || !nextIsSameItem && !nextIsContinuation) break;
5884
+ loose = true;
5885
+ pendingBlank = true;
5886
+ i++;
5887
+ continue;
5888
+ }
5725
5889
  const indent = line.match(/^(\s*)/)[1].length;
5726
5890
  if (indent < baseIndent) break;
5727
5891
  if (indent === baseIndent) {
@@ -5734,11 +5898,12 @@ function _parseListBlock(lines, startIdx) {
5734
5898
  const checked = isCB && raw[1].toLowerCase() === "x";
5735
5899
  const text = isCB ? raw.replace(/^\[[ xX]\]\s+/, "") : raw;
5736
5900
  items.push({
5737
- text,
5901
+ paras: [text],
5738
5902
  isCB,
5739
5903
  checked,
5740
5904
  sub: ""
5741
5905
  });
5906
+ pendingBlank = false;
5742
5907
  i++;
5743
5908
  } else {
5744
5909
  if (!items.length) {
@@ -5749,40 +5914,143 @@ function _parseListBlock(lines, startIdx) {
5749
5914
  const nested = _parseListBlock(lines, i);
5750
5915
  items[items.length - 1].sub += nested.html;
5751
5916
  i = nested.endIdx;
5917
+ pendingBlank = false;
5918
+ } else if (pendingBlank) {
5919
+ items[items.length - 1].paras.push(line.trim());
5920
+ pendingBlank = false;
5921
+ i++;
5752
5922
  } else {
5753
- items[items.length - 1].text += " " + line.trim();
5923
+ const paras = items[items.length - 1].paras;
5924
+ paras[paras.length - 1] += " " + line.trim();
5754
5925
  i++;
5755
5926
  }
5756
5927
  }
5757
5928
  }
5758
- const open = isOL ? "<ol>" : !isOL && firstIsCB === true ? "<ul class=\"an-checklist\">" : "<ul>";
5929
+ const hasCB = !isOL && firstIsCB === true;
5930
+ const startMatch = isOL ? /^\s*(\d+)\. /.exec(lines[startIdx]) : null;
5931
+ const startNum = startMatch ? Number.parseInt(startMatch[1], 10) : 1;
5932
+ const open = isOL ? startNum !== 1 ? `<ol start="${startNum}">` : "<ol>" : hasCB ? "<ul class=\"an-checklist\">" : "<ul>";
5759
5933
  const close = isOL ? "</ol>" : "</ul>";
5760
5934
  return {
5761
- html: `${open}${items.map(({ text, isCB, checked, sub }) => {
5762
- return `<li>${isCB ? `<input type="checkbox" contenteditable="false"${checked ? " checked" : ""}>` : ""}${_inline(text)}${sub}</li>`;
5935
+ html: `${open}${items.map(({ paras, isCB, checked, sub }) => {
5936
+ const cbHTML = isCB ? `<input type="checkbox" contenteditable="false"${checked ? " checked" : ""}>` : "";
5937
+ return `<li>${loose ? paras.map((p, idx) => `<p>${idx === 0 ? cbHTML : ""}${_inline(p)}</p>`).join("") : `${cbHTML}${_inline(paras[0])}`}${sub}</li>`;
5763
5938
  }).join("")}${close}`,
5764
5939
  endIdx: i
5765
5940
  };
5766
5941
  }
5767
- function _inline(text) {
5768
- text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => `<img src="${_escAttr(src)}" alt="${_escAttr(alt)}" class="an-image">`);
5769
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => `<a href="${_escAttr(href)}">${_esc(label)}</a>`);
5770
- text = text.replace(/\*{3}([^*\n]+?)\*{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
5771
- text = text.replace(/_{3}([^_\n]+?)_{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
5772
- text = text.replace(/\*{2}([^*\n]+?)\*{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
5773
- text = text.replace(/_{2}([^_\n]+?)_{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
5774
- text = text.replace(/\*([^*\n]+?)\*/g, (_, c) => `<em>${_esc(c)}</em>`);
5775
- text = text.replace(/_([^_\n]+?)_/g, (_, c) => `<em>${_esc(c)}</em>`);
5776
- text = text.replace(/~~([^~\n]+?)~~/g, (_, c) => `<del>${_esc(c)}</del>`);
5777
- text = text.replace(/`([^`]+)`/g, (_, c) => `<code>${_esc(c)}</code>`);
5942
+ var ESCAPABLE_RE = /\\([*_`#[\]()>\\~|])/g;
5943
+ var MARK = String.fromCharCode(0);
5944
+ /**
5945
+ * Step 0 of _inline(): replaces backslash-escaped punctuation with inert
5946
+ * placeholders so later syntax regexes can't match them.
5947
+ * @param {string} text
5948
+ * @returns {{ text: string, literals: string[] }}
5949
+ */
5950
+ function _extractBackslashEscapes(text) {
5951
+ const literals = [];
5952
+ return {
5953
+ text: text.replace(ESCAPABLE_RE, (_, ch) => {
5954
+ literals.push(ch);
5955
+ return `${MARK}${literals.length - 1}${MARK}`;
5956
+ }),
5957
+ literals
5958
+ };
5959
+ }
5960
+ /**
5961
+ * Restores placeholders from _extractBackslashEscapes(), HTML-escaping each
5962
+ * literal since it's inserted directly into the output.
5963
+ * @param {string} text
5964
+ * @param {string[]} literals
5965
+ * @returns {string}
5966
+ */
5967
+ function _restoreBackslashEscapes(text, literals) {
5968
+ return text.replace(new RegExp(`${MARK}(\\d+)${MARK}`, "g"), (_, idx) => _esc(literals[Number(idx)]));
5969
+ }
5970
+ /**
5971
+ * Resolves images, inline links, GFM reference-style links (explicit,
5972
+ * shortcut, and bare/implicit forms), and footnote markers. Must run on text
5973
+ * already passed through _esc() — see _inline()'s Step 1 comment.
5974
+ * @param {string} text
5975
+ * @returns {string}
5976
+ */
5977
+ function _resolveLinksAndFootnotes(text) {
5978
+ text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => `<img src="${_escAttrQuotes(src)}" alt="${_escAttrQuotes(alt)}" class="an-image">`);
5979
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => `<a href="${_escAttrQuotes(href)}">${label}</a>`);
5980
+ text = text.replace(/\[([^\]]+)\]\[([^\]]*)\]/g, (m, label, ref) => {
5981
+ const def = _linkDefs.get(_unescAmpLtGt(ref || label).trim().toLowerCase());
5982
+ if (!def) return m;
5983
+ const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : "";
5984
+ return `<a href="${_escAttr(def.href)}"${titleAttr}>${label}</a>`;
5985
+ });
5986
+ text = text.replace(/\[([^\]]+)\]/g, (m, label) => {
5987
+ const def = _linkDefs.get(_unescAmpLtGt(label).trim().toLowerCase());
5988
+ if (!def) return m;
5989
+ const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : "";
5990
+ return `<a href="${_escAttr(def.href)}"${titleAttr}>${label}</a>`;
5991
+ });
5992
+ text = text.replace(/\[\^([^\]]+)\]/g, (m, id) => _footnoteIds.has(_unescAmpLtGt(id)) ? `<sup>[${id}]</sup>` : m);
5778
5993
  return text;
5779
5994
  }
5995
+ /**
5996
+ * Converts angle-bracket (`<https://...>`) and bare (`https://...`)
5997
+ * autolinks. Runs after _resolveLinksAndFootnotes() so an already-linked URL
5998
+ * isn't reprocessed, and on already-_esc()'d text (see _inline()).
5999
+ * @param {string} text
6000
+ * @returns {string}
6001
+ */
6002
+ function _applyAutolinks(text) {
6003
+ text = text.replace(/&lt;(https?:\/\/[^\s&]+?)&gt;/g, (_, url) => `<a href="${_escAttrQuotes(url)}">${url}</a>`);
6004
+ text = text.replace(/(^|[\s(])(https?:\/\/[^\s()]+)/g, (m, pre, rawUrl) => {
6005
+ const trail = /[.,;:!?)]+$/.exec(rawUrl);
6006
+ const url = trail ? rawUrl.slice(0, -trail[0].length) : rawUrl;
6007
+ if (!url) return m;
6008
+ const suffix = trail ? trail[0] : "";
6009
+ return `${pre}<a href="${_escAttrQuotes(url)}">${url}</a>${suffix}`;
6010
+ });
6011
+ return text;
6012
+ }
6013
+ /**
6014
+ * Applies bold/italic/bold-italic (asterisk and underscore forms — underscore
6015
+ * requires a non-word-character boundary per CommonMark), strikethrough, and
6016
+ * inline code.
6017
+ * @param {string} text
6018
+ * @returns {string}
6019
+ */
6020
+ function _applyEmphasisAndCode(text) {
6021
+ text = text.replace(/\*{3}([^*\n]+?)\*{3}/g, (_, c) => `<strong><em>${c}</em></strong>`);
6022
+ text = text.replace(/(?<!\w)_{3}([^_\n]+?)_{3}(?!\w)/g, (_, c) => `<strong><em>${c}</em></strong>`);
6023
+ text = text.replace(/\*{2}([^*\n]+?)\*{2}/g, (_, c) => `<strong>${c}</strong>`);
6024
+ text = text.replace(/(?<!\w)_{2}([^_\n]+?)_{2}(?!\w)/g, (_, c) => `<strong>${c}</strong>`);
6025
+ text = text.replace(/\*([^*\n]+?)\*/g, (_, c) => `<em>${c}</em>`);
6026
+ text = text.replace(/(?<!\w)_([^_\n]+?)_(?!\w)/g, (_, c) => `<em>${c}</em>`);
6027
+ text = text.replace(/~~([^~\n]+?)~~/g, (_, c) => `<del>${c}</del>`);
6028
+ text = text.replace(/``([\s\S]*?)``/g, (_, c) => `<code>${c}</code>`);
6029
+ text = text.replace(/`([^`]+)`/g, (_, c) => `<code>${c}</code>`);
6030
+ return text;
6031
+ }
6032
+ function _inline(text) {
6033
+ const { text: withoutEscapes, literals } = _extractBackslashEscapes(text);
6034
+ let result = _esc(withoutEscapes);
6035
+ result = _resolveLinksAndFootnotes(result);
6036
+ result = _applyAutolinks(result);
6037
+ result = _applyEmphasisAndCode(result);
6038
+ return _restoreBackslashEscapes(result, literals);
6039
+ }
5780
6040
  function _esc(v) {
5781
6041
  return String(v).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5782
6042
  }
5783
6043
  function _escAttr(v) {
5784
6044
  return String(v).replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5785
6045
  }
6046
+ /** Escapes only quote characters — for attribute values already run through _esc(). */
6047
+ function _escAttrQuotes(v) {
6048
+ return String(v).replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
6049
+ }
6050
+ /** Reverses _esc()'s &amp;/&lt;/&gt; substitutions, for matching against un-escaped _linkDefs/_footnoteIds keys. */
6051
+ function _unescAmpLtGt(v) {
6052
+ return String(v).replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
6053
+ }
5786
6054
  //#endregion
5787
6055
  //#region src/js/core/detectLang.js
5788
6056
  /**
@@ -7338,6 +7606,17 @@ var Clipboard = class {
7338
7606
  return doc.body.innerHTML;
7339
7607
  }
7340
7608
  /**
7609
+ * Checks whether an HTML payload has no semantic markup beyond plain
7610
+ * wrapper elements (e.g. a bare <div>/<p>). Used to decide whether a
7611
+ * markdown-shaped plain-text paste should win over an accompanying HTML
7612
+ * payload that isn't actually carrying any real rich-text formatting.
7613
+ * @param {string} html
7614
+ * @returns {boolean}
7615
+ */
7616
+ _isTriviallyPlainHtml(html) {
7617
+ return !new DOMParser().parseFromString(`<body>${html}</body>`, "text/html").body.querySelector("a,img,table,ul,ol,li,blockquote,pre,code,h1,h2,h3,h4,h5,h6,strong,b,em,i,u,s,del,strike,hr,br");
7618
+ }
7619
+ /**
7341
7620
  * Forces the next paste operation to strip all HTML formatting.
7342
7621
  * Called by Editor when Ctrl+Shift+V is pressed.
7343
7622
  * @param {boolean} val
@@ -7354,8 +7633,16 @@ var Clipboard = class {
7354
7633
  if (maxBytes > 0) {
7355
7634
  const text = clipboardData.getData("text/plain") || "";
7356
7635
  const html = clipboardData.getData("text/html") || "";
7357
- if (Math.max(text.length, html.length) > maxBytes) {
7636
+ const size = Math.max(text.length, html.length);
7637
+ if (size > maxBytes) {
7358
7638
  event.preventDefault();
7639
+ const message = `Pasted content (${size} bytes) exceeds the ${this.options.maxPasteSize ?? 5} MB paste size limit.`;
7640
+ this.context.triggerEvent("pasteError", {
7641
+ size,
7642
+ maxBytes,
7643
+ message
7644
+ });
7645
+ console.warn(`[AutumnNote] ${message}`);
7359
7646
  return;
7360
7647
  }
7361
7648
  }
@@ -7378,9 +7665,12 @@ var Clipboard = class {
7378
7665
  this.context.invoke("editor.afterCommand");
7379
7666
  return;
7380
7667
  }
7381
- if (this.options.markdownPaste !== false && !clipboardData.types.includes("text/html")) {
7668
+ if (this.options.markdownPaste !== false) {
7669
+ const hasHtml = clipboardData.types.includes("text/html");
7670
+ const html = hasHtml ? clipboardData.getData("text/html") : "";
7671
+ const htmlTriviallyPlain = !hasHtml || this._isTriviallyPlainHtml(html);
7382
7672
  const text = clipboardData.getData("text/plain");
7383
- if (text && isMarkdown(text)) {
7673
+ if (text && htmlTriviallyPlain && isMarkdown(text)) {
7384
7674
  event.preventDefault();
7385
7675
  execCommand("insertHTML", sanitiseHTML(markdownToHTML(text)));
7386
7676
  this.context.invoke("editor.afterCommand");
@@ -7413,11 +7703,51 @@ var Clipboard = class {
7413
7703
  const dt = event.dataTransfer;
7414
7704
  if (!dt?.files?.length) return;
7415
7705
  const imageFiles = Array.from(dt.files).filter((f) => f.type.startsWith("image/"));
7416
- if (imageFiles.length === 0) return;
7417
- event.preventDefault();
7418
- event.stopPropagation();
7419
- this._placeCaretAtPoint(event.clientX, event.clientY);
7420
- this._insertImageFiles(imageFiles);
7706
+ if (imageFiles.length > 0) {
7707
+ event.preventDefault();
7708
+ event.stopPropagation();
7709
+ this._placeCaretAtPoint(event.clientX, event.clientY);
7710
+ this._insertImageFiles(imageFiles);
7711
+ return;
7712
+ }
7713
+ if (this.options.markdownPaste !== false) {
7714
+ const mdFile = Array.from(dt.files).find((f) => /\.md$/i.test(f.name) || f.type === "text/markdown");
7715
+ if (mdFile) {
7716
+ event.preventDefault();
7717
+ event.stopPropagation();
7718
+ this._placeCaretAtPoint(event.clientX, event.clientY);
7719
+ this._insertMarkdownFile(mdFile);
7720
+ }
7721
+ }
7722
+ }
7723
+ /**
7724
+ * Reads a dropped `.md` File and inserts it converted to HTML at the
7725
+ * current caret. Skips the isMarkdown() heuristic — an explicit `.md`
7726
+ * extension/MIME type is an unambiguous signal, unlike pasted plain text.
7727
+ * @param {File} file
7728
+ */
7729
+ _insertMarkdownFile(file) {
7730
+ const maxBytes = (this.options.maxPasteSize ?? 5) * 1024 * 1024;
7731
+ if (maxBytes > 0 && file.size > maxBytes) {
7732
+ const message = `Dropped file "${file.name}" (${file.size} bytes) exceeds the ${this.options.maxPasteSize ?? 5} MB paste size limit.`;
7733
+ this.context.triggerEvent("pasteError", {
7734
+ size: file.size,
7735
+ maxBytes,
7736
+ message
7737
+ });
7738
+ console.warn(`[AutumnNote] ${message}`);
7739
+ return;
7740
+ }
7741
+ const reader = new FileReader();
7742
+ reader.onload = (e) => {
7743
+ execCommand("insertHTML", sanitiseHTML(markdownToHTML(
7744
+ /** @type {string} */
7745
+ e.target.result || ""
7746
+ )));
7747
+ this.context.invoke("editor.afterCommand");
7748
+ };
7749
+ reader.onerror = () => console.warn("[AutumnNote] Failed to read dropped markdown file", file.name);
7750
+ reader.readAsText(file);
7421
7751
  }
7422
7752
  /**
7423
7753
  * Inserts one or more image Files into the editor.
@@ -17581,7 +17911,7 @@ var AutumnNote = {
17581
17911
  /** All pre-built button definitions — accessible in every module format including UMD/CJS. */
17582
17912
  buttons,
17583
17913
  /** Library version */
17584
- version: "1.9.1"
17914
+ version: "1.11.0"
17585
17915
  };
17586
17916
  /**
17587
17917
  * @param {string|Element|NodeList|Element[]} selector