autumnnote 1.8.3 → 1.10.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.
@@ -5596,7 +5596,7 @@ function _domToMd(node, depth = 0) {
5596
5596
  * @returns {boolean} `true` if any Markdown-like pattern is present, `false` otherwise.
5597
5597
  */
5598
5598
  function isMarkdown(text) {
5599
- return /^#{1,6} [^\s]|^[ \t]*[-*+] [^\s]|^[ \t]*\d+\. [^\s]|^> [^\s]|^```|^\*{2}[^*\n]+\*{2}/m.test(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) || /^---\s*\n(?:[\s\S]*?\n)?(?:---|\.\.\.)\s*(?:\n|$)/.test(text) || /^\|.+\|[ \t]*\n\|[ \t:|-]+\|/m.test(text);
5600
5600
  }
5601
5601
  /**
5602
5602
  * Converts a Markdown string to an HTML string.
@@ -5604,7 +5604,12 @@ function isMarkdown(text) {
5604
5604
  * @returns {string}
5605
5605
  */
5606
5606
  function markdownToHTML(text) {
5607
- const lines = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
5607
+ let lines = text.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
5608
+ lines = _stripFrontmatter(lines);
5609
+ const refs = _extractReferenceDefinitions(lines);
5610
+ lines = refs.clean;
5611
+ _linkDefs = refs.linkDefs;
5612
+ _footnoteIds = refs.footnoteIds;
5608
5613
  const out = [];
5609
5614
  let i = 0;
5610
5615
  while (i < lines.length) {
@@ -5623,6 +5628,18 @@ function markdownToHTML(text) {
5623
5628
  i++;
5624
5629
  continue;
5625
5630
  }
5631
+ if (line.trim() && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line) && !/^#{1,6} /.test(line) && i + 1 < lines.length) {
5632
+ if (/^=+\s*$/.test(lines[i + 1])) {
5633
+ out.push(`<h1>${_inline(line.trim())}</h1>`);
5634
+ i += 2;
5635
+ continue;
5636
+ }
5637
+ if (/^-{2,}\s*$/.test(lines[i + 1])) {
5638
+ out.push(`<h2>${_inline(line.trim())}</h2>`);
5639
+ i += 2;
5640
+ continue;
5641
+ }
5642
+ }
5626
5643
  if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
5627
5644
  out.push("<hr>");
5628
5645
  i++;
@@ -5645,30 +5662,15 @@ function markdownToHTML(text) {
5645
5662
  continue;
5646
5663
  }
5647
5664
  if (/^[-*+] /.test(line)) {
5648
- const items = [];
5649
- const isChecklist = /^[-*+]\s+\[[ xX]\]\s+/.test(line);
5650
- const listTag = isChecklist ? "ul class=\"an-checklist\"" : "ul";
5651
- while (i < lines.length && /^[-*+] /.test(lines[i])) {
5652
- if (/^[-*+]\s+\[[ xX]\]\s+/.test(lines[i]) !== isChecklist) break;
5653
- const content = lines[i].slice(2);
5654
- if (isChecklist) {
5655
- const cbMatch = /^\[([ xX])\][ \t]+/.exec(content);
5656
- const cbHtml = `<input type="checkbox" contenteditable="false"${cbMatch?.[1]?.toLowerCase() === "x" ? " checked" : ""}>`;
5657
- const textContent = cbMatch ? content.slice(cbMatch[0].length) : content;
5658
- items.push(`<li>${cbHtml}${_inline(textContent)}</li>`);
5659
- } else items.push(`<li>${_inline(content)}</li>`);
5660
- i++;
5661
- }
5662
- out.push(`<${listTag}>${items.join("")}</${listTag.split(" ")[0]}>`);
5665
+ const { html: listHtml, endIdx } = _parseListBlock(lines, i);
5666
+ out.push(listHtml);
5667
+ i = endIdx;
5663
5668
  continue;
5664
5669
  }
5665
5670
  if (/^\d+\. /.test(line)) {
5666
- const items = [];
5667
- while (i < lines.length && /^\d+\. /.test(lines[i])) {
5668
- items.push(`<li>${_inline(lines[i].replace(/^\d+\. /, ""))}</li>`);
5669
- i++;
5670
- }
5671
- out.push(`<ol>${items.join("")}</ol>`);
5671
+ const { html: listHtml, endIdx } = _parseListBlock(lines, i);
5672
+ out.push(listHtml);
5673
+ i = endIdx;
5672
5674
  continue;
5673
5675
  }
5674
5676
  if (line.trim() === "") {
@@ -5677,20 +5679,29 @@ function markdownToHTML(text) {
5677
5679
  }
5678
5680
  if (/^\|.+\|/.test(line) && i + 1 < lines.length && /^\|[\s|:-]+\|/.test(lines[i + 1])) {
5679
5681
  const headerCells = _parseTableRow(line);
5682
+ const alignments = _parseTableRow(lines[i + 1]).map((c) => {
5683
+ if (c.startsWith(":") && c.endsWith(":")) return "center";
5684
+ if (c.endsWith(":")) return "right";
5685
+ if (c.startsWith(":")) return "left";
5686
+ return null;
5687
+ });
5680
5688
  i += 2;
5681
5689
  const bodyRows = [];
5682
5690
  while (i < lines.length && /^\|.+\|/.test(lines[i])) {
5683
5691
  bodyRows.push(_parseTableRow(lines[i]));
5684
5692
  i++;
5685
5693
  }
5686
- const thead = `<thead><tr>${headerCells.map((c) => `<th>${_inline(c)}</th>`).join("")}</tr></thead>`;
5687
- const renderRow = (row) => `<tr>${row.map((c) => `<td>${_inline(c)}</td>`).join("")}</tr>`;
5694
+ const _cell = (tag, content, align) => {
5695
+ return `<${tag}${align ? ` style="text-align:${align}"` : ""}>${_inline(content)}</${tag}>`;
5696
+ };
5697
+ const thead = `<thead><tr>${headerCells.map((c, idx) => _cell("th", c, alignments[idx])).join("")}</tr></thead>`;
5698
+ const renderRow = (row) => `<tr>${row.map((c, idx) => _cell("td", c, alignments[idx])).join("")}</tr>`;
5688
5699
  const tbody = bodyRows.length ? `<tbody>${bodyRows.map(renderRow).join("")}</tbody>` : "";
5689
5700
  out.push(`<table>${thead}${tbody}</table>`);
5690
5701
  continue;
5691
5702
  }
5692
5703
  const paraLines = [];
5693
- while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6} |> |[-*+] |\d+\. |```|---\s*$|\*{3}\s*$|_{3}\s*$)/.test(lines[i]) && !/^\|.+\|/.test(lines[i])) {
5704
+ 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]))) {
5694
5705
  paraLines.push(lines[i]);
5695
5706
  i++;
5696
5707
  }
@@ -5698,6 +5709,76 @@ function markdownToHTML(text) {
5698
5709
  }
5699
5710
  return out.join("");
5700
5711
  }
5712
+ /** Reference-link and footnote definitions collected per markdownToHTML() call. */
5713
+ var _linkDefs = /* @__PURE__ */ new Map();
5714
+ var _footnoteIds = /* @__PURE__ */ new Set();
5715
+ /**
5716
+ * Strips a leading YAML frontmatter block (--- ... --- or --- ... ...) from
5717
+ * the line array, only when it is the very first line and the enclosed body
5718
+ * looks like YAML (key: value / list items / indented continuations) — this
5719
+ * disambiguates real frontmatter from a horizontal rule followed by prose.
5720
+ * @param {string[]} lines
5721
+ * @returns {string[]}
5722
+ */
5723
+ function _stripFrontmatter(lines) {
5724
+ if ((lines[0] || "").trim() !== "---") return lines;
5725
+ let closeIdx = -1;
5726
+ for (let j = 1; j < lines.length; j++) {
5727
+ const t = lines[j].trim();
5728
+ if (t === "---" || t === "...") {
5729
+ closeIdx = j;
5730
+ break;
5731
+ }
5732
+ }
5733
+ if (closeIdx === -1) return lines;
5734
+ 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;
5735
+ let start = closeIdx + 1;
5736
+ if (lines[start] !== void 0 && lines[start].trim() === "") start++;
5737
+ return lines.slice(start);
5738
+ }
5739
+ /**
5740
+ * Extracts GFM reference-link definitions (`[ref]: url "title"`) and footnote
5741
+ * definitions (`[^id]: text`) from the line array, skipping fenced code
5742
+ * regions. Returns the definition-free line array plus lookup maps.
5743
+ * @param {string[]} lines
5744
+ * @returns {{ clean: string[], linkDefs: Map<string, {href: string, title?: string}>, footnoteIds: Set<string> }}
5745
+ */
5746
+ function _extractReferenceDefinitions(lines) {
5747
+ const linkDefs = /* @__PURE__ */ new Map();
5748
+ const footnoteIds = /* @__PURE__ */ new Set();
5749
+ const clean = [];
5750
+ let inFence = false;
5751
+ const linkDefRe = /^\[([^\]]+)\]:\s*(\S+)(?:\s+"([^"]*)")?\s*$/;
5752
+ const footnoteDefRe = /^\[\^([^\]]+)\]:\s*(.+)$/;
5753
+ for (const line of lines) {
5754
+ if (/^```/.test(line)) {
5755
+ inFence = !inFence;
5756
+ clean.push(line);
5757
+ continue;
5758
+ }
5759
+ if (!inFence) {
5760
+ const fm = footnoteDefRe.exec(line);
5761
+ if (fm) {
5762
+ footnoteIds.add(fm[1]);
5763
+ continue;
5764
+ }
5765
+ const lm = linkDefRe.exec(line);
5766
+ if (lm) {
5767
+ linkDefs.set(lm[1].trim().toLowerCase(), {
5768
+ href: lm[2],
5769
+ title: lm[3]
5770
+ });
5771
+ continue;
5772
+ }
5773
+ }
5774
+ clean.push(line);
5775
+ }
5776
+ return {
5777
+ clean,
5778
+ linkDefs,
5779
+ footnoteIds
5780
+ };
5781
+ }
5701
5782
  /**
5702
5783
  * Splits a GFM table row string into trimmed cell strings.
5703
5784
  * '| a | b | c |' → ['a', 'b', 'c']
@@ -5707,9 +5788,73 @@ function markdownToHTML(text) {
5707
5788
  function _parseTableRow(row) {
5708
5789
  return row.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
5709
5790
  }
5791
+ function _parseListBlock(lines, startIdx) {
5792
+ const baseIndent = lines[startIdx].match(/^(\s*)/)[1].length;
5793
+ const isOL = /^\s*\d+\. /.test(lines[startIdx]);
5794
+ const items = [];
5795
+ let firstIsCB = null;
5796
+ let i = startIdx;
5797
+ while (i < lines.length) {
5798
+ const line = lines[i];
5799
+ if (line.trim() === "") break;
5800
+ const indent = line.match(/^(\s*)/)[1].length;
5801
+ if (indent < baseIndent) break;
5802
+ if (indent === baseIndent) {
5803
+ if (!/^\s*(?:[-*+]|\d+\.) /.test(line)) break;
5804
+ if (/^\s*\d+\. /.test(line) !== isOL) break;
5805
+ const raw = isOL ? line.replace(/^\s*\d+\. /, "") : line.replace(/^\s*[-*+] /, "");
5806
+ const isCB = !isOL && /^\[[ xX]\]\s+/.test(raw);
5807
+ if (firstIsCB === null) firstIsCB = isCB;
5808
+ if (isCB !== firstIsCB) break;
5809
+ const checked = isCB && raw[1].toLowerCase() === "x";
5810
+ const text = isCB ? raw.replace(/^\[[ xX]\]\s+/, "") : raw;
5811
+ items.push({
5812
+ text,
5813
+ isCB,
5814
+ checked,
5815
+ sub: ""
5816
+ });
5817
+ i++;
5818
+ } else {
5819
+ if (!items.length) {
5820
+ i++;
5821
+ continue;
5822
+ }
5823
+ if (/^\s*(?:[-*+]|\d+\.) /.test(line)) {
5824
+ const nested = _parseListBlock(lines, i);
5825
+ items[items.length - 1].sub += nested.html;
5826
+ i = nested.endIdx;
5827
+ } else {
5828
+ items[items.length - 1].text += " " + line.trim();
5829
+ i++;
5830
+ }
5831
+ }
5832
+ }
5833
+ const open = isOL ? "<ol>" : !isOL && firstIsCB === true ? "<ul class=\"an-checklist\">" : "<ul>";
5834
+ const close = isOL ? "</ol>" : "</ul>";
5835
+ return {
5836
+ html: `${open}${items.map(({ text, isCB, checked, sub }) => {
5837
+ return `<li>${isCB ? `<input type="checkbox" contenteditable="false"${checked ? " checked" : ""}>` : ""}${_inline(text)}${sub}</li>`;
5838
+ }).join("")}${close}`,
5839
+ endIdx: i
5840
+ };
5841
+ }
5710
5842
  function _inline(text) {
5711
5843
  text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => `<img src="${_escAttr(src)}" alt="${_escAttr(alt)}" class="an-image">`);
5712
5844
  text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => `<a href="${_escAttr(href)}">${_esc(label)}</a>`);
5845
+ text = text.replace(/\[([^\]]+)\]\[([^\]]*)\]/g, (m, label, ref) => {
5846
+ const def = _linkDefs.get((ref || label).trim().toLowerCase());
5847
+ if (!def) return m;
5848
+ const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : "";
5849
+ return `<a href="${_escAttr(def.href)}"${titleAttr}>${_esc(label)}</a>`;
5850
+ });
5851
+ text = text.replace(/\[([^\]]+)\]/g, (m, label) => {
5852
+ const def = _linkDefs.get(label.trim().toLowerCase());
5853
+ if (!def) return m;
5854
+ const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : "";
5855
+ return `<a href="${_escAttr(def.href)}"${titleAttr}>${_esc(label)}</a>`;
5856
+ });
5857
+ text = text.replace(/\[\^([^\]]+)\]/g, (m, id) => _footnoteIds.has(id) ? `<sup>[${_esc(id)}]</sup>` : m);
5713
5858
  text = text.replace(/\*{3}([^*\n]+?)\*{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
5714
5859
  text = text.replace(/_{3}([^_\n]+?)_{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
5715
5860
  text = text.replace(/\*{2}([^*\n]+?)\*{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
@@ -7258,6 +7403,29 @@ var Clipboard = class {
7258
7403
  return doc.body.innerHTML;
7259
7404
  }
7260
7405
  /**
7406
+ * Normalizes task lists from external sources (GitHub, GitLab, etc.) so they
7407
+ * pass the sanitiser's `ul.an-checklist` guard. Runs before sanitiseHTML().
7408
+ * @param {string} html
7409
+ * @returns {string}
7410
+ */
7411
+ _normalizeExternalTaskLists(html) {
7412
+ const doc = new DOMParser().parseFromString(`<body>${html}</body>`, "text/html");
7413
+ for (const cb of doc.querySelectorAll("input[type=\"checkbox\"]")) {
7414
+ const li = cb.closest("li");
7415
+ const ul = li?.closest("ul");
7416
+ if (!li || !ul || ul.classList.contains("an-checklist")) continue;
7417
+ ul.classList.add("an-checklist");
7418
+ cb.removeAttribute("disabled");
7419
+ cb.setAttribute("contenteditable", "false");
7420
+ for (const attr of Array.from(cb.attributes)) if (![
7421
+ "type",
7422
+ "checked",
7423
+ "contenteditable"
7424
+ ].includes(attr.name)) cb.removeAttribute(attr.name);
7425
+ }
7426
+ return doc.body.innerHTML;
7427
+ }
7428
+ /**
7261
7429
  * Forces the next paste operation to strip all HTML formatting.
7262
7430
  * Called by Editor when Ctrl+Shift+V is pressed.
7263
7431
  * @param {boolean} val
@@ -7315,6 +7483,7 @@ var Clipboard = class {
7315
7483
  let html = raw;
7316
7484
  if (isWordContent) html = this._cleanWordHtml(html);
7317
7485
  else if (isSocialContent) html = this._cleanSocialHtml(html);
7486
+ html = this._normalizeExternalTaskLists(html);
7318
7487
  html = sanitiseHTML(html);
7319
7488
  if (this.options.pasteStripAttributes) html = this._stripAttributes(html);
7320
7489
  execCommand("insertHTML", html);
@@ -17500,7 +17669,7 @@ var AutumnNote = {
17500
17669
  /** All pre-built button definitions — accessible in every module format including UMD/CJS. */
17501
17670
  buttons,
17502
17671
  /** Library version */
17503
- version: "1.8.3"
17672
+ version: "1.10.0"
17504
17673
  };
17505
17674
  /**
17506
17675
  * @param {string|Element|NodeList|Element[]} selector