autumnnote 1.10.0 → 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 +298 -56
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +298 -56
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/core/markdown.js +282 -61
- 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.css
CHANGED
|
@@ -377,6 +377,24 @@
|
|
|
377
377
|
margin: 0.5em 0;
|
|
378
378
|
line-height: 1.3;
|
|
379
379
|
}
|
|
380
|
+
.an-editable h1 {
|
|
381
|
+
font-size: 2em;
|
|
382
|
+
}
|
|
383
|
+
.an-editable h2 {
|
|
384
|
+
font-size: 1.5em;
|
|
385
|
+
}
|
|
386
|
+
.an-editable h3 {
|
|
387
|
+
font-size: 1.25em;
|
|
388
|
+
}
|
|
389
|
+
.an-editable h4 {
|
|
390
|
+
font-size: 1.1em;
|
|
391
|
+
}
|
|
392
|
+
.an-editable h5 {
|
|
393
|
+
font-size: 1em;
|
|
394
|
+
}
|
|
395
|
+
.an-editable h6 {
|
|
396
|
+
font-size: 0.875em;
|
|
397
|
+
}
|
|
380
398
|
.an-editable ul, .an-editable ol {
|
|
381
399
|
margin: 0 0 0.75em 0;
|
|
382
400
|
padding-left: 1.5em;
|
|
@@ -385,6 +403,18 @@
|
|
|
385
403
|
.an-editable li {
|
|
386
404
|
margin-bottom: 0.25em;
|
|
387
405
|
}
|
|
406
|
+
.an-editable li > p {
|
|
407
|
+
margin: 0 0 0.5em;
|
|
408
|
+
}
|
|
409
|
+
.an-editable li > p:first-child {
|
|
410
|
+
display: inline;
|
|
411
|
+
}
|
|
412
|
+
.an-editable li > p:last-child {
|
|
413
|
+
margin-bottom: 0;
|
|
414
|
+
}
|
|
415
|
+
.an-editable li:has(> p) {
|
|
416
|
+
margin-bottom: 0.6em;
|
|
417
|
+
}
|
|
388
418
|
.an-editable blockquote {
|
|
389
419
|
margin: 0.5em 0 0.5em 1em;
|
|
390
420
|
padding: 0.5em 1em;
|
|
@@ -404,6 +434,10 @@
|
|
|
404
434
|
.an-editable code {
|
|
405
435
|
padding: 0.1em 0.3em;
|
|
406
436
|
}
|
|
437
|
+
.an-editable pre > code {
|
|
438
|
+
background: transparent;
|
|
439
|
+
padding: 0;
|
|
440
|
+
}
|
|
407
441
|
.an-editable pre[class*=language-] {
|
|
408
442
|
background: #2d2d2d;
|
|
409
443
|
color: #ccc;
|
|
@@ -1807,6 +1841,9 @@
|
|
|
1807
1841
|
background: #313244;
|
|
1808
1842
|
color: #cdd6f4;
|
|
1809
1843
|
}
|
|
1844
|
+
.an-theme-dark .an-editable pre:not([class*=language-]) > code {
|
|
1845
|
+
background: transparent;
|
|
1846
|
+
}
|
|
1810
1847
|
.an-theme-dark .an-editable .an-code-line-numbers::before {
|
|
1811
1848
|
background: rgba(255, 255, 255, 0.05);
|
|
1812
1849
|
border-right-color: rgba(255, 255, 255, 0.1);
|
|
@@ -2229,6 +2266,9 @@
|
|
|
2229
2266
|
background: #313244;
|
|
2230
2267
|
color: #cdd6f4;
|
|
2231
2268
|
}
|
|
2269
|
+
.an-theme-auto .an-editable pre:not([class*=language-]) > code {
|
|
2270
|
+
background: transparent;
|
|
2271
|
+
}
|
|
2232
2272
|
.an-theme-auto .an-editable hr {
|
|
2233
2273
|
border-color: #3f3f5f;
|
|
2234
2274
|
}
|
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,8 +5617,11 @@ 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) || /^---\s*\n(?:[\s\S]*?\n)?(?:---|\.\.\.)\s*(?:\n|$)/.test(text) || /^\|.+\|[ \t]*\n\|[ \t:|-]+\|/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
|
|
@@ -5610,6 +5634,16 @@ function markdownToHTML(text) {
|
|
|
5610
5634
|
lines = refs.clean;
|
|
5611
5635
|
_linkDefs = refs.linkDefs;
|
|
5612
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) {
|
|
5613
5647
|
const out = [];
|
|
5614
5648
|
let i = 0;
|
|
5615
5649
|
while (i < lines.length) {
|
|
@@ -5628,7 +5662,7 @@ function markdownToHTML(text) {
|
|
|
5628
5662
|
i++;
|
|
5629
5663
|
continue;
|
|
5630
5664
|
}
|
|
5631
|
-
if (line.trim() &&
|
|
5665
|
+
if (line.trim() && !HR_RE.test(line) && !/^#{1,6} /.test(line) && i + 1 < lines.length) {
|
|
5632
5666
|
if (/^=+\s*$/.test(lines[i + 1])) {
|
|
5633
5667
|
out.push(`<h1>${_inline(line.trim())}</h1>`);
|
|
5634
5668
|
i += 2;
|
|
@@ -5640,7 +5674,7 @@ function markdownToHTML(text) {
|
|
|
5640
5674
|
continue;
|
|
5641
5675
|
}
|
|
5642
5676
|
}
|
|
5643
|
-
if (
|
|
5677
|
+
if (HR_RE.test(line)) {
|
|
5644
5678
|
out.push("<hr>");
|
|
5645
5679
|
i++;
|
|
5646
5680
|
continue;
|
|
@@ -5648,17 +5682,18 @@ function markdownToHTML(text) {
|
|
|
5648
5682
|
const hMatch = /^(#{1,6})\s+(.+)$/.exec(line);
|
|
5649
5683
|
if (hMatch) {
|
|
5650
5684
|
const level = hMatch[1].length;
|
|
5651
|
-
|
|
5685
|
+
const content = hMatch[2].replace(/(?:^|\s)#+\s*$/, "");
|
|
5686
|
+
out.push(`<h${level}>${_inline(content)}</h${level}>`);
|
|
5652
5687
|
i++;
|
|
5653
5688
|
continue;
|
|
5654
5689
|
}
|
|
5655
|
-
if (
|
|
5690
|
+
if (BQ_RE.test(line)) {
|
|
5656
5691
|
const bqLines = [];
|
|
5657
|
-
while (i < lines.length && lines[i]
|
|
5658
|
-
bqLines.push(lines[i]
|
|
5692
|
+
while (i < lines.length && BQ_RE.test(lines[i])) {
|
|
5693
|
+
bqLines.push(BQ_RE.exec(lines[i])[2]);
|
|
5659
5694
|
i++;
|
|
5660
5695
|
}
|
|
5661
|
-
out.push(`<blockquote>${bqLines
|
|
5696
|
+
out.push(`<blockquote>${_parseBlocks(bqLines)}</blockquote>`);
|
|
5662
5697
|
continue;
|
|
5663
5698
|
}
|
|
5664
5699
|
if (/^[-*+] /.test(line)) {
|
|
@@ -5701,11 +5736,11 @@ function markdownToHTML(text) {
|
|
|
5701
5736
|
continue;
|
|
5702
5737
|
}
|
|
5703
5738
|
const paraLines = [];
|
|
5704
|
-
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]))) {
|
|
5705
5740
|
paraLines.push(lines[i]);
|
|
5706
5741
|
i++;
|
|
5707
5742
|
}
|
|
5708
|
-
if (paraLines.length) out.push(`<p>${_inline(paraLines.
|
|
5743
|
+
if (paraLines.length) out.push(`<p>${_inline(_joinParagraphLines(paraLines)).replaceAll(HARD_BREAK, "<br>")}</p>`);
|
|
5709
5744
|
}
|
|
5710
5745
|
return out.join("");
|
|
5711
5746
|
}
|
|
@@ -5780,23 +5815,77 @@ function _extractReferenceDefinitions(lines) {
|
|
|
5780
5815
|
};
|
|
5781
5816
|
}
|
|
5782
5817
|
/**
|
|
5783
|
-
*
|
|
5784
|
-
*
|
|
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']
|
|
5785
5845
|
* @param {string} row
|
|
5786
5846
|
* @returns {string[]}
|
|
5787
5847
|
*/
|
|
5788
5848
|
function _parseTableRow(row) {
|
|
5789
|
-
|
|
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());
|
|
5790
5867
|
}
|
|
5791
5868
|
function _parseListBlock(lines, startIdx) {
|
|
5792
5869
|
const baseIndent = lines[startIdx].match(/^(\s*)/)[1].length;
|
|
5793
5870
|
const isOL = /^\s*\d+\. /.test(lines[startIdx]);
|
|
5794
5871
|
const items = [];
|
|
5795
5872
|
let firstIsCB = null;
|
|
5873
|
+
let loose = false;
|
|
5874
|
+
let pendingBlank = false;
|
|
5796
5875
|
let i = startIdx;
|
|
5797
5876
|
while (i < lines.length) {
|
|
5798
5877
|
const line = lines[i];
|
|
5799
|
-
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
|
+
}
|
|
5800
5889
|
const indent = line.match(/^(\s*)/)[1].length;
|
|
5801
5890
|
if (indent < baseIndent) break;
|
|
5802
5891
|
if (indent === baseIndent) {
|
|
@@ -5809,11 +5898,12 @@ function _parseListBlock(lines, startIdx) {
|
|
|
5809
5898
|
const checked = isCB && raw[1].toLowerCase() === "x";
|
|
5810
5899
|
const text = isCB ? raw.replace(/^\[[ xX]\]\s+/, "") : raw;
|
|
5811
5900
|
items.push({
|
|
5812
|
-
text,
|
|
5901
|
+
paras: [text],
|
|
5813
5902
|
isCB,
|
|
5814
5903
|
checked,
|
|
5815
5904
|
sub: ""
|
|
5816
5905
|
});
|
|
5906
|
+
pendingBlank = false;
|
|
5817
5907
|
i++;
|
|
5818
5908
|
} else {
|
|
5819
5909
|
if (!items.length) {
|
|
@@ -5824,53 +5914,143 @@ function _parseListBlock(lines, startIdx) {
|
|
|
5824
5914
|
const nested = _parseListBlock(lines, i);
|
|
5825
5915
|
items[items.length - 1].sub += nested.html;
|
|
5826
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++;
|
|
5827
5922
|
} else {
|
|
5828
|
-
items[items.length - 1].
|
|
5923
|
+
const paras = items[items.length - 1].paras;
|
|
5924
|
+
paras[paras.length - 1] += " " + line.trim();
|
|
5829
5925
|
i++;
|
|
5830
5926
|
}
|
|
5831
5927
|
}
|
|
5832
5928
|
}
|
|
5833
|
-
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>";
|
|
5834
5933
|
const close = isOL ? "</ol>" : "</ul>";
|
|
5835
5934
|
return {
|
|
5836
|
-
html: `${open}${items.map(({
|
|
5837
|
-
|
|
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>`;
|
|
5838
5938
|
}).join("")}${close}`,
|
|
5839
5939
|
endIdx: i
|
|
5840
5940
|
};
|
|
5841
5941
|
}
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
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>`);
|
|
5845
5980
|
text = text.replace(/\[([^\]]+)\]\[([^\]]*)\]/g, (m, label, ref) => {
|
|
5846
|
-
const def = _linkDefs.get((ref || label).trim().toLowerCase());
|
|
5981
|
+
const def = _linkDefs.get(_unescAmpLtGt(ref || label).trim().toLowerCase());
|
|
5847
5982
|
if (!def) return m;
|
|
5848
5983
|
const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : "";
|
|
5849
|
-
return `<a href="${_escAttr(def.href)}"${titleAttr}>${
|
|
5984
|
+
return `<a href="${_escAttr(def.href)}"${titleAttr}>${label}</a>`;
|
|
5850
5985
|
});
|
|
5851
5986
|
text = text.replace(/\[([^\]]+)\]/g, (m, label) => {
|
|
5852
|
-
const def = _linkDefs.get(label.trim().toLowerCase());
|
|
5987
|
+
const def = _linkDefs.get(_unescAmpLtGt(label).trim().toLowerCase());
|
|
5853
5988
|
if (!def) return m;
|
|
5854
5989
|
const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : "";
|
|
5855
|
-
return `<a href="${_escAttr(def.href)}"${titleAttr}>${
|
|
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);
|
|
5993
|
+
return text;
|
|
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}`;
|
|
5856
6010
|
});
|
|
5857
|
-
text = text.replace(/\[\^([^\]]+)\]/g, (m, id) => _footnoteIds.has(id) ? `<sup>[${_esc(id)}]</sup>` : m);
|
|
5858
|
-
text = text.replace(/\*{3}([^*\n]+?)\*{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
|
|
5859
|
-
text = text.replace(/_{3}([^_\n]+?)_{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
|
|
5860
|
-
text = text.replace(/\*{2}([^*\n]+?)\*{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
|
|
5861
|
-
text = text.replace(/_{2}([^_\n]+?)_{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
|
|
5862
|
-
text = text.replace(/\*([^*\n]+?)\*/g, (_, c) => `<em>${_esc(c)}</em>`);
|
|
5863
|
-
text = text.replace(/_([^_\n]+?)_/g, (_, c) => `<em>${_esc(c)}</em>`);
|
|
5864
|
-
text = text.replace(/~~([^~\n]+?)~~/g, (_, c) => `<del>${_esc(c)}</del>`);
|
|
5865
|
-
text = text.replace(/`([^`]+)`/g, (_, c) => `<code>${_esc(c)}</code>`);
|
|
5866
6011
|
return text;
|
|
5867
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
|
+
}
|
|
5868
6040
|
function _esc(v) {
|
|
5869
6041
|
return String(v).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
5870
6042
|
}
|
|
5871
6043
|
function _escAttr(v) {
|
|
5872
6044
|
return String(v).replaceAll("&", "&").replaceAll("\"", """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
5873
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
|
+
}
|
|
5874
6054
|
//#endregion
|
|
5875
6055
|
//#region src/js/core/detectLang.js
|
|
5876
6056
|
/**
|
|
@@ -7426,6 +7606,17 @@ var Clipboard = class {
|
|
|
7426
7606
|
return doc.body.innerHTML;
|
|
7427
7607
|
}
|
|
7428
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
|
+
/**
|
|
7429
7620
|
* Forces the next paste operation to strip all HTML formatting.
|
|
7430
7621
|
* Called by Editor when Ctrl+Shift+V is pressed.
|
|
7431
7622
|
* @param {boolean} val
|
|
@@ -7442,8 +7633,16 @@ var Clipboard = class {
|
|
|
7442
7633
|
if (maxBytes > 0) {
|
|
7443
7634
|
const text = clipboardData.getData("text/plain") || "";
|
|
7444
7635
|
const html = clipboardData.getData("text/html") || "";
|
|
7445
|
-
|
|
7636
|
+
const size = Math.max(text.length, html.length);
|
|
7637
|
+
if (size > maxBytes) {
|
|
7446
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}`);
|
|
7447
7646
|
return;
|
|
7448
7647
|
}
|
|
7449
7648
|
}
|
|
@@ -7466,9 +7665,12 @@ var Clipboard = class {
|
|
|
7466
7665
|
this.context.invoke("editor.afterCommand");
|
|
7467
7666
|
return;
|
|
7468
7667
|
}
|
|
7469
|
-
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);
|
|
7470
7672
|
const text = clipboardData.getData("text/plain");
|
|
7471
|
-
if (text && isMarkdown(text)) {
|
|
7673
|
+
if (text && htmlTriviallyPlain && isMarkdown(text)) {
|
|
7472
7674
|
event.preventDefault();
|
|
7473
7675
|
execCommand("insertHTML", sanitiseHTML(markdownToHTML(text)));
|
|
7474
7676
|
this.context.invoke("editor.afterCommand");
|
|
@@ -7501,11 +7703,51 @@ var Clipboard = class {
|
|
|
7501
7703
|
const dt = event.dataTransfer;
|
|
7502
7704
|
if (!dt?.files?.length) return;
|
|
7503
7705
|
const imageFiles = Array.from(dt.files).filter((f) => f.type.startsWith("image/"));
|
|
7504
|
-
if (imageFiles.length
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
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);
|
|
7509
7751
|
}
|
|
7510
7752
|
/**
|
|
7511
7753
|
* Inserts one or more image Files into the editor.
|
|
@@ -17669,7 +17911,7 @@ var AutumnNote = {
|
|
|
17669
17911
|
/** All pre-built button definitions — accessible in every module format including UMD/CJS. */
|
|
17670
17912
|
buttons,
|
|
17671
17913
|
/** Library version */
|
|
17672
|
-
version: "1.
|
|
17914
|
+
version: "1.11.0"
|
|
17673
17915
|
};
|
|
17674
17916
|
/**
|
|
17675
17917
|
* @param {string|Element|NodeList|Element[]} selector
|