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.umd.js
CHANGED
|
@@ -5215,6 +5215,17 @@
|
|
|
5215
5215
|
* @param {number} [depth=0] - Current nesting depth used to indent nested list items.
|
|
5216
5216
|
* @returns {string} The Markdown representation of the node subtree.
|
|
5217
5217
|
*/
|
|
5218
|
+
/**
|
|
5219
|
+
* Direct child elements matching a tag name. Used instead of the CSS
|
|
5220
|
+
* `:scope > tag` combinator, which this project's jsdom version resolves
|
|
5221
|
+
* incorrectly (matches descendants at any depth, not just direct children).
|
|
5222
|
+
* @param {Element} el
|
|
5223
|
+
* @param {string} tagName
|
|
5224
|
+
* @returns {Element[]}
|
|
5225
|
+
*/
|
|
5226
|
+
function _directChildren(el, tagName) {
|
|
5227
|
+
return Array.from(el.children).filter((c) => c.tagName === tagName.toUpperCase());
|
|
5228
|
+
}
|
|
5218
5229
|
function _domToMd(node, depth = 0) {
|
|
5219
5230
|
if (node.nodeType === 3) return node.textContent.replace(/\s+/g, " ");
|
|
5220
5231
|
if (node.nodeType !== 1) return "";
|
|
@@ -5240,6 +5251,12 @@
|
|
|
5240
5251
|
case "strike": return `~~${inner()}~~`;
|
|
5241
5252
|
case "sup": return `^${inner()}^`;
|
|
5242
5253
|
case "sub": return `~${inner()}~`;
|
|
5254
|
+
case "u": return `<u>${inner()}</u>`;
|
|
5255
|
+
case "span": {
|
|
5256
|
+
const style = el.getAttribute("style") || "";
|
|
5257
|
+
if (/\b(color|background-color|font-size)\s*:/.test(style)) return `<span style="${_escAttr(style)}">${inner()}</span>`;
|
|
5258
|
+
return inner();
|
|
5259
|
+
}
|
|
5243
5260
|
case "code":
|
|
5244
5261
|
if (el.closest("pre")) return inner();
|
|
5245
5262
|
return `\`${inner()}\``;
|
|
@@ -5248,7 +5265,10 @@
|
|
|
5248
5265
|
const langMatch = /language-(\S+)/.exec(codeEl?.className || "");
|
|
5249
5266
|
return `\n\n\`\`\`${langMatch ? langMatch[1] : ""}\n${(codeEl || el).textContent || ""}\n\`\`\`\n\n`;
|
|
5250
5267
|
}
|
|
5251
|
-
case "blockquote":
|
|
5268
|
+
case "blockquote": {
|
|
5269
|
+
const rawLines = inner().trim().split("\n");
|
|
5270
|
+
return `\n\n${rawLines.filter((l, idx) => l.trim() !== "" || (rawLines[idx - 1] ?? "").trim() !== "").map((l) => l.trim() === "" ? ">" : `> ${l}`).join("\n")}\n\n`;
|
|
5271
|
+
}
|
|
5252
5272
|
case "a": {
|
|
5253
5273
|
const href = el.getAttribute("href") || "";
|
|
5254
5274
|
return `[${inner()}](${href})`;
|
|
@@ -5258,22 +5278,20 @@
|
|
|
5258
5278
|
return ``;
|
|
5259
5279
|
}
|
|
5260
5280
|
case "ul": {
|
|
5261
|
-
const items =
|
|
5281
|
+
const items = _directChildren(el, "li");
|
|
5262
5282
|
if (!items.length) return inner();
|
|
5263
5283
|
const indent = " ".repeat(depth);
|
|
5264
5284
|
const isChecklist = el.classList.contains("an-checklist");
|
|
5265
5285
|
const lines = items.map((li) => {
|
|
5286
|
+
const cb = _directChildren(li, "input").find((c) => c.getAttribute("type") === "checkbox");
|
|
5266
5287
|
let prefix = "- ";
|
|
5267
|
-
if (isChecklist)
|
|
5268
|
-
const cb = li.querySelector("input[type=\"checkbox\"]");
|
|
5269
|
-
prefix = (cb ? cb.checked : false) ? "- [x] " : "- [ ] ";
|
|
5270
|
-
}
|
|
5288
|
+
if (isChecklist || cb) prefix = (cb ? cb.checked : false) ? "- [x] " : "- [ ] ";
|
|
5271
5289
|
return `${indent}${prefix}${_domToMd(li, depth + 1).trim()}`;
|
|
5272
5290
|
}).join("\n");
|
|
5273
5291
|
return depth === 0 ? `\n\n${lines}\n\n` : `\n${lines}`;
|
|
5274
5292
|
}
|
|
5275
5293
|
case "ol": {
|
|
5276
|
-
const items =
|
|
5294
|
+
const items = _directChildren(el, "li");
|
|
5277
5295
|
if (!items.length) return inner();
|
|
5278
5296
|
const indent = " ".repeat(depth);
|
|
5279
5297
|
const lines = items.map((li, i) => `${indent}${i + 1}. ${_domToMd(li, depth + 1).trim()}`).join("\n");
|
|
@@ -5282,19 +5300,22 @@
|
|
|
5282
5300
|
case "li": return inner();
|
|
5283
5301
|
case "hr": return "\n\n---\n\n";
|
|
5284
5302
|
case "table": {
|
|
5285
|
-
const
|
|
5286
|
-
if (!
|
|
5287
|
-
const
|
|
5303
|
+
const allRows = Array.from(el.querySelectorAll("tr"));
|
|
5304
|
+
if (!allRows.length) return inner();
|
|
5305
|
+
const firstRowIsHeader = !!_directChildren(el, "thead")[0] || allRows[0].children.length > 0 && Array.from(allRows[0].children).every((c) => c.tagName === "TH");
|
|
5306
|
+
const cellTexts = allRows.map((tr) => Array.from(tr.querySelectorAll("th, td")).map((c) => c.textContent.trim().replaceAll("|", String.raw`\|`)));
|
|
5288
5307
|
const cols = Math.max(...cellTexts.map((r) => r.length));
|
|
5289
5308
|
const padRow = (row) => {
|
|
5290
5309
|
const r = [...row];
|
|
5291
5310
|
while (r.length < cols) r.push("");
|
|
5292
5311
|
return r;
|
|
5293
5312
|
};
|
|
5313
|
+
const bodyStart = firstRowIsHeader ? 1 : 0;
|
|
5314
|
+
const headerCells = firstRowIsHeader ? padRow(cellTexts[0]) : new Array(cols).fill("");
|
|
5294
5315
|
let md = "\n\n";
|
|
5295
|
-
md += `| ${
|
|
5316
|
+
md += `| ${headerCells.join(" | ")} |\n`;
|
|
5296
5317
|
md += `| ${new Array(cols).fill("---").join(" | ")} |\n`;
|
|
5297
|
-
for (let r =
|
|
5318
|
+
for (let r = bodyStart; r < cellTexts.length; r++) md += `| ${padRow(cellTexts[r]).join(" | ")} |\n`;
|
|
5298
5319
|
return md + "\n";
|
|
5299
5320
|
}
|
|
5300
5321
|
default: return inner();
|
|
@@ -5309,8 +5330,11 @@
|
|
|
5309
5330
|
* @returns {boolean} `true` if any Markdown-like pattern is present, `false` otherwise.
|
|
5310
5331
|
*/
|
|
5311
5332
|
function isMarkdown(text) {
|
|
5312
|
-
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);
|
|
5333
|
+
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);
|
|
5313
5334
|
}
|
|
5335
|
+
var BQ_RE = /^ {0,3}>( ?)(.*)$/;
|
|
5336
|
+
var HR_RE = /^ {0,3}([-*_])( *\1){2,}\s*$/;
|
|
5337
|
+
var HARD_BREAK = String.fromCharCode(1);
|
|
5314
5338
|
/**
|
|
5315
5339
|
* Converts a Markdown string to an HTML string.
|
|
5316
5340
|
* @param {string} text
|
|
@@ -5323,6 +5347,16 @@
|
|
|
5323
5347
|
lines = refs.clean;
|
|
5324
5348
|
_linkDefs = refs.linkDefs;
|
|
5325
5349
|
_footnoteIds = refs.footnoteIds;
|
|
5350
|
+
return _parseBlocks(lines);
|
|
5351
|
+
}
|
|
5352
|
+
/**
|
|
5353
|
+
* Parses a line array into block-level HTML. Called recursively for content
|
|
5354
|
+
* nested inside a blockquote so nested quotes and block content (lists,
|
|
5355
|
+
* headings, etc.) inside `>` are parsed the same as top-level content.
|
|
5356
|
+
* @param {string[]} lines
|
|
5357
|
+
* @returns {string}
|
|
5358
|
+
*/
|
|
5359
|
+
function _parseBlocks(lines) {
|
|
5326
5360
|
const out = [];
|
|
5327
5361
|
let i = 0;
|
|
5328
5362
|
while (i < lines.length) {
|
|
@@ -5341,7 +5375,7 @@
|
|
|
5341
5375
|
i++;
|
|
5342
5376
|
continue;
|
|
5343
5377
|
}
|
|
5344
|
-
if (line.trim() &&
|
|
5378
|
+
if (line.trim() && !HR_RE.test(line) && !/^#{1,6} /.test(line) && i + 1 < lines.length) {
|
|
5345
5379
|
if (/^=+\s*$/.test(lines[i + 1])) {
|
|
5346
5380
|
out.push(`<h1>${_inline(line.trim())}</h1>`);
|
|
5347
5381
|
i += 2;
|
|
@@ -5353,7 +5387,7 @@
|
|
|
5353
5387
|
continue;
|
|
5354
5388
|
}
|
|
5355
5389
|
}
|
|
5356
|
-
if (
|
|
5390
|
+
if (HR_RE.test(line)) {
|
|
5357
5391
|
out.push("<hr>");
|
|
5358
5392
|
i++;
|
|
5359
5393
|
continue;
|
|
@@ -5361,17 +5395,18 @@
|
|
|
5361
5395
|
const hMatch = /^(#{1,6})\s+(.+)$/.exec(line);
|
|
5362
5396
|
if (hMatch) {
|
|
5363
5397
|
const level = hMatch[1].length;
|
|
5364
|
-
|
|
5398
|
+
const content = hMatch[2].replace(/(?:^|\s)#+\s*$/, "");
|
|
5399
|
+
out.push(`<h${level}>${_inline(content)}</h${level}>`);
|
|
5365
5400
|
i++;
|
|
5366
5401
|
continue;
|
|
5367
5402
|
}
|
|
5368
|
-
if (
|
|
5403
|
+
if (BQ_RE.test(line)) {
|
|
5369
5404
|
const bqLines = [];
|
|
5370
|
-
while (i < lines.length && lines[i]
|
|
5371
|
-
bqLines.push(lines[i]
|
|
5405
|
+
while (i < lines.length && BQ_RE.test(lines[i])) {
|
|
5406
|
+
bqLines.push(BQ_RE.exec(lines[i])[2]);
|
|
5372
5407
|
i++;
|
|
5373
5408
|
}
|
|
5374
|
-
out.push(`<blockquote>${bqLines
|
|
5409
|
+
out.push(`<blockquote>${_parseBlocks(bqLines)}</blockquote>`);
|
|
5375
5410
|
continue;
|
|
5376
5411
|
}
|
|
5377
5412
|
if (/^[-*+] /.test(line)) {
|
|
@@ -5414,11 +5449,11 @@
|
|
|
5414
5449
|
continue;
|
|
5415
5450
|
}
|
|
5416
5451
|
const paraLines = [];
|
|
5417
|
-
while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6}
|
|
5452
|
+
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]))) {
|
|
5418
5453
|
paraLines.push(lines[i]);
|
|
5419
5454
|
i++;
|
|
5420
5455
|
}
|
|
5421
|
-
if (paraLines.length) out.push(`<p>${_inline(paraLines.
|
|
5456
|
+
if (paraLines.length) out.push(`<p>${_inline(_joinParagraphLines(paraLines)).replaceAll(HARD_BREAK, "<br>")}</p>`);
|
|
5422
5457
|
}
|
|
5423
5458
|
return out.join("");
|
|
5424
5459
|
}
|
|
@@ -5493,23 +5528,77 @@
|
|
|
5493
5528
|
};
|
|
5494
5529
|
}
|
|
5495
5530
|
/**
|
|
5496
|
-
*
|
|
5497
|
-
*
|
|
5531
|
+
* Joins a paragraph's source lines into one string, converting CommonMark
|
|
5532
|
+
* hard-break markers (a trailing backslash, or 2+ trailing spaces) on all
|
|
5533
|
+
* but the last line into a HARD_BREAK placeholder instead of a plain space.
|
|
5534
|
+
* @param {string[]} paraLines
|
|
5535
|
+
* @returns {string}
|
|
5536
|
+
*/
|
|
5537
|
+
function _joinParagraphLines(paraLines) {
|
|
5538
|
+
let joined = "";
|
|
5539
|
+
for (let idx = 0; idx < paraLines.length; idx++) {
|
|
5540
|
+
const isLast = idx === paraLines.length - 1;
|
|
5541
|
+
const ln = paraLines[idx];
|
|
5542
|
+
if (!isLast && /\\$/.test(ln)) {
|
|
5543
|
+
joined += ln.replace(/\\$/, "") + HARD_BREAK;
|
|
5544
|
+
continue;
|
|
5545
|
+
}
|
|
5546
|
+
if (!isLast && / {2,}$/.test(ln)) {
|
|
5547
|
+
joined += ln.replace(/ {2,}$/, "") + HARD_BREAK;
|
|
5548
|
+
continue;
|
|
5549
|
+
}
|
|
5550
|
+
joined += ln + (isLast ? "" : " ");
|
|
5551
|
+
}
|
|
5552
|
+
return joined;
|
|
5553
|
+
}
|
|
5554
|
+
/**
|
|
5555
|
+
* Splits a GFM table row string into trimmed cell strings, treating an
|
|
5556
|
+
* escaped pipe (`\|`) as a literal character rather than a cell separator.
|
|
5557
|
+
* '| a | b | c |' → ['a', 'b', 'c']; '| a\|b | c |' → ['a|b', 'c']
|
|
5498
5558
|
* @param {string} row
|
|
5499
5559
|
* @returns {string[]}
|
|
5500
5560
|
*/
|
|
5501
5561
|
function _parseTableRow(row) {
|
|
5502
|
-
|
|
5562
|
+
const trimmed = row.replace(/^\|/, "").replace(/\|$/, "");
|
|
5563
|
+
const cells = [];
|
|
5564
|
+
let cur = "";
|
|
5565
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
5566
|
+
if (trimmed[i] === "\\" && trimmed[i + 1] === "|") {
|
|
5567
|
+
cur += "|";
|
|
5568
|
+
i++;
|
|
5569
|
+
continue;
|
|
5570
|
+
}
|
|
5571
|
+
if (trimmed[i] === "|") {
|
|
5572
|
+
cells.push(cur);
|
|
5573
|
+
cur = "";
|
|
5574
|
+
continue;
|
|
5575
|
+
}
|
|
5576
|
+
cur += trimmed[i];
|
|
5577
|
+
}
|
|
5578
|
+
cells.push(cur);
|
|
5579
|
+
return cells.map((c) => c.trim());
|
|
5503
5580
|
}
|
|
5504
5581
|
function _parseListBlock(lines, startIdx) {
|
|
5505
5582
|
const baseIndent = lines[startIdx].match(/^(\s*)/)[1].length;
|
|
5506
5583
|
const isOL = /^\s*\d+\. /.test(lines[startIdx]);
|
|
5507
5584
|
const items = [];
|
|
5508
5585
|
let firstIsCB = null;
|
|
5586
|
+
let loose = false;
|
|
5587
|
+
let pendingBlank = false;
|
|
5509
5588
|
let i = startIdx;
|
|
5510
5589
|
while (i < lines.length) {
|
|
5511
5590
|
const line = lines[i];
|
|
5512
|
-
if (line.trim() === "")
|
|
5591
|
+
if (line.trim() === "") {
|
|
5592
|
+
const next = lines[i + 1];
|
|
5593
|
+
const nextIndent = next !== void 0 ? next.match(/^(\s*)/)[1].length : -1;
|
|
5594
|
+
const nextIsSameItem = next !== void 0 && /^\s*(?:[-*+]|\d+\.) /.test(next) && /^\s*\d+\. /.test(next) === isOL && nextIndent === baseIndent;
|
|
5595
|
+
const nextIsContinuation = next !== void 0 && next.trim() !== "" && nextIndent > baseIndent;
|
|
5596
|
+
if (!items.length || !nextIsSameItem && !nextIsContinuation) break;
|
|
5597
|
+
loose = true;
|
|
5598
|
+
pendingBlank = true;
|
|
5599
|
+
i++;
|
|
5600
|
+
continue;
|
|
5601
|
+
}
|
|
5513
5602
|
const indent = line.match(/^(\s*)/)[1].length;
|
|
5514
5603
|
if (indent < baseIndent) break;
|
|
5515
5604
|
if (indent === baseIndent) {
|
|
@@ -5522,11 +5611,12 @@
|
|
|
5522
5611
|
const checked = isCB && raw[1].toLowerCase() === "x";
|
|
5523
5612
|
const text = isCB ? raw.replace(/^\[[ xX]\]\s+/, "") : raw;
|
|
5524
5613
|
items.push({
|
|
5525
|
-
text,
|
|
5614
|
+
paras: [text],
|
|
5526
5615
|
isCB,
|
|
5527
5616
|
checked,
|
|
5528
5617
|
sub: ""
|
|
5529
5618
|
});
|
|
5619
|
+
pendingBlank = false;
|
|
5530
5620
|
i++;
|
|
5531
5621
|
} else {
|
|
5532
5622
|
if (!items.length) {
|
|
@@ -5537,53 +5627,143 @@
|
|
|
5537
5627
|
const nested = _parseListBlock(lines, i);
|
|
5538
5628
|
items[items.length - 1].sub += nested.html;
|
|
5539
5629
|
i = nested.endIdx;
|
|
5630
|
+
pendingBlank = false;
|
|
5631
|
+
} else if (pendingBlank) {
|
|
5632
|
+
items[items.length - 1].paras.push(line.trim());
|
|
5633
|
+
pendingBlank = false;
|
|
5634
|
+
i++;
|
|
5540
5635
|
} else {
|
|
5541
|
-
items[items.length - 1].
|
|
5636
|
+
const paras = items[items.length - 1].paras;
|
|
5637
|
+
paras[paras.length - 1] += " " + line.trim();
|
|
5542
5638
|
i++;
|
|
5543
5639
|
}
|
|
5544
5640
|
}
|
|
5545
5641
|
}
|
|
5546
|
-
const
|
|
5642
|
+
const hasCB = !isOL && firstIsCB === true;
|
|
5643
|
+
const startMatch = isOL ? /^\s*(\d+)\. /.exec(lines[startIdx]) : null;
|
|
5644
|
+
const startNum = startMatch ? Number.parseInt(startMatch[1], 10) : 1;
|
|
5645
|
+
const open = isOL ? startNum !== 1 ? `<ol start="${startNum}">` : "<ol>" : hasCB ? "<ul class=\"an-checklist\">" : "<ul>";
|
|
5547
5646
|
const close = isOL ? "</ol>" : "</ul>";
|
|
5548
5647
|
return {
|
|
5549
|
-
html: `${open}${items.map(({
|
|
5550
|
-
|
|
5648
|
+
html: `${open}${items.map(({ paras, isCB, checked, sub }) => {
|
|
5649
|
+
const cbHTML = isCB ? `<input type="checkbox" contenteditable="false"${checked ? " checked" : ""}>` : "";
|
|
5650
|
+
return `<li>${loose ? paras.map((p, idx) => `<p>${idx === 0 ? cbHTML : ""}${_inline(p)}</p>`).join("") : `${cbHTML}${_inline(paras[0])}`}${sub}</li>`;
|
|
5551
5651
|
}).join("")}${close}`,
|
|
5552
5652
|
endIdx: i
|
|
5553
5653
|
};
|
|
5554
5654
|
}
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5655
|
+
var ESCAPABLE_RE = /\\([*_`#[\]()>\\~|])/g;
|
|
5656
|
+
var MARK = String.fromCharCode(0);
|
|
5657
|
+
/**
|
|
5658
|
+
* Step 0 of _inline(): replaces backslash-escaped punctuation with inert
|
|
5659
|
+
* placeholders so later syntax regexes can't match them.
|
|
5660
|
+
* @param {string} text
|
|
5661
|
+
* @returns {{ text: string, literals: string[] }}
|
|
5662
|
+
*/
|
|
5663
|
+
function _extractBackslashEscapes(text) {
|
|
5664
|
+
const literals = [];
|
|
5665
|
+
return {
|
|
5666
|
+
text: text.replace(ESCAPABLE_RE, (_, ch) => {
|
|
5667
|
+
literals.push(ch);
|
|
5668
|
+
return `${MARK}${literals.length - 1}${MARK}`;
|
|
5669
|
+
}),
|
|
5670
|
+
literals
|
|
5671
|
+
};
|
|
5672
|
+
}
|
|
5673
|
+
/**
|
|
5674
|
+
* Restores placeholders from _extractBackslashEscapes(), HTML-escaping each
|
|
5675
|
+
* literal since it's inserted directly into the output.
|
|
5676
|
+
* @param {string} text
|
|
5677
|
+
* @param {string[]} literals
|
|
5678
|
+
* @returns {string}
|
|
5679
|
+
*/
|
|
5680
|
+
function _restoreBackslashEscapes(text, literals) {
|
|
5681
|
+
return text.replace(new RegExp(`${MARK}(\\d+)${MARK}`, "g"), (_, idx) => _esc(literals[Number(idx)]));
|
|
5682
|
+
}
|
|
5683
|
+
/**
|
|
5684
|
+
* Resolves images, inline links, GFM reference-style links (explicit,
|
|
5685
|
+
* shortcut, and bare/implicit forms), and footnote markers. Must run on text
|
|
5686
|
+
* already passed through _esc() — see _inline()'s Step 1 comment.
|
|
5687
|
+
* @param {string} text
|
|
5688
|
+
* @returns {string}
|
|
5689
|
+
*/
|
|
5690
|
+
function _resolveLinksAndFootnotes(text) {
|
|
5691
|
+
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => `<img src="${_escAttrQuotes(src)}" alt="${_escAttrQuotes(alt)}" class="an-image">`);
|
|
5692
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => `<a href="${_escAttrQuotes(href)}">${label}</a>`);
|
|
5558
5693
|
text = text.replace(/\[([^\]]+)\]\[([^\]]*)\]/g, (m, label, ref) => {
|
|
5559
|
-
const def = _linkDefs.get((ref || label).trim().toLowerCase());
|
|
5694
|
+
const def = _linkDefs.get(_unescAmpLtGt(ref || label).trim().toLowerCase());
|
|
5560
5695
|
if (!def) return m;
|
|
5561
5696
|
const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : "";
|
|
5562
|
-
return `<a href="${_escAttr(def.href)}"${titleAttr}>${
|
|
5697
|
+
return `<a href="${_escAttr(def.href)}"${titleAttr}>${label}</a>`;
|
|
5563
5698
|
});
|
|
5564
5699
|
text = text.replace(/\[([^\]]+)\]/g, (m, label) => {
|
|
5565
|
-
const def = _linkDefs.get(label.trim().toLowerCase());
|
|
5700
|
+
const def = _linkDefs.get(_unescAmpLtGt(label).trim().toLowerCase());
|
|
5566
5701
|
if (!def) return m;
|
|
5567
5702
|
const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : "";
|
|
5568
|
-
return `<a href="${_escAttr(def.href)}"${titleAttr}>${
|
|
5703
|
+
return `<a href="${_escAttr(def.href)}"${titleAttr}>${label}</a>`;
|
|
5704
|
+
});
|
|
5705
|
+
text = text.replace(/\[\^([^\]]+)\]/g, (m, id) => _footnoteIds.has(_unescAmpLtGt(id)) ? `<sup>[${id}]</sup>` : m);
|
|
5706
|
+
return text;
|
|
5707
|
+
}
|
|
5708
|
+
/**
|
|
5709
|
+
* Converts angle-bracket (`<https://...>`) and bare (`https://...`)
|
|
5710
|
+
* autolinks. Runs after _resolveLinksAndFootnotes() so an already-linked URL
|
|
5711
|
+
* isn't reprocessed, and on already-_esc()'d text (see _inline()).
|
|
5712
|
+
* @param {string} text
|
|
5713
|
+
* @returns {string}
|
|
5714
|
+
*/
|
|
5715
|
+
function _applyAutolinks(text) {
|
|
5716
|
+
text = text.replace(/<(https?:\/\/[^\s&]+?)>/g, (_, url) => `<a href="${_escAttrQuotes(url)}">${url}</a>`);
|
|
5717
|
+
text = text.replace(/(^|[\s(])(https?:\/\/[^\s()]+)/g, (m, pre, rawUrl) => {
|
|
5718
|
+
const trail = /[.,;:!?)]+$/.exec(rawUrl);
|
|
5719
|
+
const url = trail ? rawUrl.slice(0, -trail[0].length) : rawUrl;
|
|
5720
|
+
if (!url) return m;
|
|
5721
|
+
const suffix = trail ? trail[0] : "";
|
|
5722
|
+
return `${pre}<a href="${_escAttrQuotes(url)}">${url}</a>${suffix}`;
|
|
5569
5723
|
});
|
|
5570
|
-
text = text.replace(/\[\^([^\]]+)\]/g, (m, id) => _footnoteIds.has(id) ? `<sup>[${_esc(id)}]</sup>` : m);
|
|
5571
|
-
text = text.replace(/\*{3}([^*\n]+?)\*{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
|
|
5572
|
-
text = text.replace(/_{3}([^_\n]+?)_{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
|
|
5573
|
-
text = text.replace(/\*{2}([^*\n]+?)\*{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
|
|
5574
|
-
text = text.replace(/_{2}([^_\n]+?)_{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
|
|
5575
|
-
text = text.replace(/\*([^*\n]+?)\*/g, (_, c) => `<em>${_esc(c)}</em>`);
|
|
5576
|
-
text = text.replace(/_([^_\n]+?)_/g, (_, c) => `<em>${_esc(c)}</em>`);
|
|
5577
|
-
text = text.replace(/~~([^~\n]+?)~~/g, (_, c) => `<del>${_esc(c)}</del>`);
|
|
5578
|
-
text = text.replace(/`([^`]+)`/g, (_, c) => `<code>${_esc(c)}</code>`);
|
|
5579
5724
|
return text;
|
|
5580
5725
|
}
|
|
5726
|
+
/**
|
|
5727
|
+
* Applies bold/italic/bold-italic (asterisk and underscore forms — underscore
|
|
5728
|
+
* requires a non-word-character boundary per CommonMark), strikethrough, and
|
|
5729
|
+
* inline code.
|
|
5730
|
+
* @param {string} text
|
|
5731
|
+
* @returns {string}
|
|
5732
|
+
*/
|
|
5733
|
+
function _applyEmphasisAndCode(text) {
|
|
5734
|
+
text = text.replace(/\*{3}([^*\n]+?)\*{3}/g, (_, c) => `<strong><em>${c}</em></strong>`);
|
|
5735
|
+
text = text.replace(/(?<!\w)_{3}([^_\n]+?)_{3}(?!\w)/g, (_, c) => `<strong><em>${c}</em></strong>`);
|
|
5736
|
+
text = text.replace(/\*{2}([^*\n]+?)\*{2}/g, (_, c) => `<strong>${c}</strong>`);
|
|
5737
|
+
text = text.replace(/(?<!\w)_{2}([^_\n]+?)_{2}(?!\w)/g, (_, c) => `<strong>${c}</strong>`);
|
|
5738
|
+
text = text.replace(/\*([^*\n]+?)\*/g, (_, c) => `<em>${c}</em>`);
|
|
5739
|
+
text = text.replace(/(?<!\w)_([^_\n]+?)_(?!\w)/g, (_, c) => `<em>${c}</em>`);
|
|
5740
|
+
text = text.replace(/~~([^~\n]+?)~~/g, (_, c) => `<del>${c}</del>`);
|
|
5741
|
+
text = text.replace(/``([\s\S]*?)``/g, (_, c) => `<code>${c}</code>`);
|
|
5742
|
+
text = text.replace(/`([^`]+)`/g, (_, c) => `<code>${c}</code>`);
|
|
5743
|
+
return text;
|
|
5744
|
+
}
|
|
5745
|
+
function _inline(text) {
|
|
5746
|
+
const { text: withoutEscapes, literals } = _extractBackslashEscapes(text);
|
|
5747
|
+
let result = _esc(withoutEscapes);
|
|
5748
|
+
result = _resolveLinksAndFootnotes(result);
|
|
5749
|
+
result = _applyAutolinks(result);
|
|
5750
|
+
result = _applyEmphasisAndCode(result);
|
|
5751
|
+
return _restoreBackslashEscapes(result, literals);
|
|
5752
|
+
}
|
|
5581
5753
|
function _esc(v) {
|
|
5582
5754
|
return String(v).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
5583
5755
|
}
|
|
5584
5756
|
function _escAttr(v) {
|
|
5585
5757
|
return String(v).replaceAll("&", "&").replaceAll("\"", """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
5586
5758
|
}
|
|
5759
|
+
/** Escapes only quote characters — for attribute values already run through _esc(). */
|
|
5760
|
+
function _escAttrQuotes(v) {
|
|
5761
|
+
return String(v).replaceAll("\"", """).replaceAll("'", "'");
|
|
5762
|
+
}
|
|
5763
|
+
/** Reverses _esc()'s &/</> substitutions, for matching against un-escaped _linkDefs/_footnoteIds keys. */
|
|
5764
|
+
function _unescAmpLtGt(v) {
|
|
5765
|
+
return String(v).replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
5766
|
+
}
|
|
5587
5767
|
//#endregion
|
|
5588
5768
|
//#region src/js/core/detectLang.js
|
|
5589
5769
|
/**
|
|
@@ -7139,6 +7319,17 @@
|
|
|
7139
7319
|
return doc.body.innerHTML;
|
|
7140
7320
|
}
|
|
7141
7321
|
/**
|
|
7322
|
+
* Checks whether an HTML payload has no semantic markup beyond plain
|
|
7323
|
+
* wrapper elements (e.g. a bare <div>/<p>). Used to decide whether a
|
|
7324
|
+
* markdown-shaped plain-text paste should win over an accompanying HTML
|
|
7325
|
+
* payload that isn't actually carrying any real rich-text formatting.
|
|
7326
|
+
* @param {string} html
|
|
7327
|
+
* @returns {boolean}
|
|
7328
|
+
*/
|
|
7329
|
+
_isTriviallyPlainHtml(html) {
|
|
7330
|
+
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");
|
|
7331
|
+
}
|
|
7332
|
+
/**
|
|
7142
7333
|
* Forces the next paste operation to strip all HTML formatting.
|
|
7143
7334
|
* Called by Editor when Ctrl+Shift+V is pressed.
|
|
7144
7335
|
* @param {boolean} val
|
|
@@ -7155,8 +7346,16 @@
|
|
|
7155
7346
|
if (maxBytes > 0) {
|
|
7156
7347
|
const text = clipboardData.getData("text/plain") || "";
|
|
7157
7348
|
const html = clipboardData.getData("text/html") || "";
|
|
7158
|
-
|
|
7349
|
+
const size = Math.max(text.length, html.length);
|
|
7350
|
+
if (size > maxBytes) {
|
|
7159
7351
|
event.preventDefault();
|
|
7352
|
+
const message = `Pasted content (${size} bytes) exceeds the ${this.options.maxPasteSize ?? 5} MB paste size limit.`;
|
|
7353
|
+
this.context.triggerEvent("pasteError", {
|
|
7354
|
+
size,
|
|
7355
|
+
maxBytes,
|
|
7356
|
+
message
|
|
7357
|
+
});
|
|
7358
|
+
console.warn(`[AutumnNote] ${message}`);
|
|
7160
7359
|
return;
|
|
7161
7360
|
}
|
|
7162
7361
|
}
|
|
@@ -7179,9 +7378,12 @@
|
|
|
7179
7378
|
this.context.invoke("editor.afterCommand");
|
|
7180
7379
|
return;
|
|
7181
7380
|
}
|
|
7182
|
-
if (this.options.markdownPaste !== false
|
|
7381
|
+
if (this.options.markdownPaste !== false) {
|
|
7382
|
+
const hasHtml = clipboardData.types.includes("text/html");
|
|
7383
|
+
const html = hasHtml ? clipboardData.getData("text/html") : "";
|
|
7384
|
+
const htmlTriviallyPlain = !hasHtml || this._isTriviallyPlainHtml(html);
|
|
7183
7385
|
const text = clipboardData.getData("text/plain");
|
|
7184
|
-
if (text && isMarkdown(text)) {
|
|
7386
|
+
if (text && htmlTriviallyPlain && isMarkdown(text)) {
|
|
7185
7387
|
event.preventDefault();
|
|
7186
7388
|
execCommand("insertHTML", sanitiseHTML(markdownToHTML(text)));
|
|
7187
7389
|
this.context.invoke("editor.afterCommand");
|
|
@@ -7214,11 +7416,51 @@
|
|
|
7214
7416
|
const dt = event.dataTransfer;
|
|
7215
7417
|
if (!dt?.files?.length) return;
|
|
7216
7418
|
const imageFiles = Array.from(dt.files).filter((f) => f.type.startsWith("image/"));
|
|
7217
|
-
if (imageFiles.length
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7419
|
+
if (imageFiles.length > 0) {
|
|
7420
|
+
event.preventDefault();
|
|
7421
|
+
event.stopPropagation();
|
|
7422
|
+
this._placeCaretAtPoint(event.clientX, event.clientY);
|
|
7423
|
+
this._insertImageFiles(imageFiles);
|
|
7424
|
+
return;
|
|
7425
|
+
}
|
|
7426
|
+
if (this.options.markdownPaste !== false) {
|
|
7427
|
+
const mdFile = Array.from(dt.files).find((f) => /\.md$/i.test(f.name) || f.type === "text/markdown");
|
|
7428
|
+
if (mdFile) {
|
|
7429
|
+
event.preventDefault();
|
|
7430
|
+
event.stopPropagation();
|
|
7431
|
+
this._placeCaretAtPoint(event.clientX, event.clientY);
|
|
7432
|
+
this._insertMarkdownFile(mdFile);
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
7435
|
+
}
|
|
7436
|
+
/**
|
|
7437
|
+
* Reads a dropped `.md` File and inserts it converted to HTML at the
|
|
7438
|
+
* current caret. Skips the isMarkdown() heuristic — an explicit `.md`
|
|
7439
|
+
* extension/MIME type is an unambiguous signal, unlike pasted plain text.
|
|
7440
|
+
* @param {File} file
|
|
7441
|
+
*/
|
|
7442
|
+
_insertMarkdownFile(file) {
|
|
7443
|
+
const maxBytes = (this.options.maxPasteSize ?? 5) * 1024 * 1024;
|
|
7444
|
+
if (maxBytes > 0 && file.size > maxBytes) {
|
|
7445
|
+
const message = `Dropped file "${file.name}" (${file.size} bytes) exceeds the ${this.options.maxPasteSize ?? 5} MB paste size limit.`;
|
|
7446
|
+
this.context.triggerEvent("pasteError", {
|
|
7447
|
+
size: file.size,
|
|
7448
|
+
maxBytes,
|
|
7449
|
+
message
|
|
7450
|
+
});
|
|
7451
|
+
console.warn(`[AutumnNote] ${message}`);
|
|
7452
|
+
return;
|
|
7453
|
+
}
|
|
7454
|
+
const reader = new FileReader();
|
|
7455
|
+
reader.onload = (e) => {
|
|
7456
|
+
execCommand("insertHTML", sanitiseHTML(markdownToHTML(
|
|
7457
|
+
/** @type {string} */
|
|
7458
|
+
e.target.result || ""
|
|
7459
|
+
)));
|
|
7460
|
+
this.context.invoke("editor.afterCommand");
|
|
7461
|
+
};
|
|
7462
|
+
reader.onerror = () => console.warn("[AutumnNote] Failed to read dropped markdown file", file.name);
|
|
7463
|
+
reader.readAsText(file);
|
|
7222
7464
|
}
|
|
7223
7465
|
/**
|
|
7224
7466
|
* Inserts one or more image Files into the editor.
|
|
@@ -17256,7 +17498,7 @@
|
|
|
17256
17498
|
/** All pre-built button definitions — accessible in every module format including UMD/CJS. */
|
|
17257
17499
|
buttons,
|
|
17258
17500
|
/** Library version */
|
|
17259
|
-
version: "1.
|
|
17501
|
+
version: "1.11.0"
|
|
17260
17502
|
};
|
|
17261
17503
|
/**
|
|
17262
17504
|
* @param {string|Element|NodeList|Element[]} selector
|