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.
- package/dist/autumnnote.css +40 -0
- package/dist/autumnnote.es.js +382 -52
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +382 -52
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/core/markdown.js +361 -55
- package/src/js/index.js +1 -1
- package/src/js/module/Clipboard.js +69 -11
- package/src/styles/autumnnote.scss +23 -0
- package/types/index.d.ts +1 -1
package/dist/autumnnote.es.js
CHANGED
|
@@ -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":
|
|
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 ``;
|
|
5546
5566
|
}
|
|
5547
5567
|
case "ul": {
|
|
5548
|
-
const items =
|
|
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 =
|
|
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
|
|
5573
|
-
if (!
|
|
5574
|
-
const
|
|
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 += `| ${
|
|
5603
|
+
md += `| ${headerCells.join(" | ")} |\n`;
|
|
5583
5604
|
md += `| ${new Array(cols).fill("---").join(" | ")} |\n`;
|
|
5584
|
-
for (let r =
|
|
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
|
-
|
|
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() &&
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
5690
|
+
if (BQ_RE.test(line)) {
|
|
5651
5691
|
const bqLines = [];
|
|
5652
|
-
while (i < lines.length && lines[i]
|
|
5653
|
-
bqLines.push(lines[i]
|
|
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
|
|
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}
|
|
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.
|
|
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
|
-
*
|
|
5709
|
-
*
|
|
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
|
-
|
|
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() === "")
|
|
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].
|
|
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
|
|
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(({
|
|
5762
|
-
|
|
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
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
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(/<(https?:\/\/[^\s&]+?)>/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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
5782
6042
|
}
|
|
5783
6043
|
function _escAttr(v) {
|
|
5784
6044
|
return String(v).replaceAll("&", "&").replaceAll("\"", """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
5785
6045
|
}
|
|
6046
|
+
/** Escapes only quote characters — for attribute values already run through _esc(). */
|
|
6047
|
+
function _escAttrQuotes(v) {
|
|
6048
|
+
return String(v).replaceAll("\"", """).replaceAll("'", "'");
|
|
6049
|
+
}
|
|
6050
|
+
/** Reverses _esc()'s &/</> substitutions, for matching against un-escaped _linkDefs/_footnoteIds keys. */
|
|
6051
|
+
function _unescAmpLtGt(v) {
|
|
6052
|
+
return String(v).replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
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
|
-
|
|
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
|
|
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
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
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.
|
|
17914
|
+
version: "1.11.0"
|
|
17585
17915
|
};
|
|
17586
17916
|
/**
|
|
17587
17917
|
* @param {string|Element|NodeList|Element[]} selector
|