autumnnote 1.8.2 → 1.9.1

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/README.md CHANGED
@@ -177,6 +177,9 @@ Official React and Vue 3 wrappers are available as separate packages in this mon
177
177
 
178
178
  ```bash
179
179
  npm install autumnnote autumnnote-react
180
+ ```
181
+
182
+ ```js
180
183
  import 'autumnnote/dist/autumnnote.css';
181
184
  ```
182
185
 
@@ -206,6 +209,9 @@ The `ref` is forwarded to the underlying `Context` instance via `useImperativeHa
206
209
 
207
210
  ```bash
208
211
  npm install autumnnote autumnnote-vue
212
+ ```
213
+
214
+ ```js
209
215
  import 'autumnnote/dist/autumnnote.css';
210
216
  ```
211
217
 
@@ -260,7 +266,6 @@ const editor = AutumnNote.create('#my-editor', {
260
266
  ```js
261
267
  const editor = AutumnNote.create('#my-editor', {
262
268
  useBootstrap: true,
263
- bootstrapVersion: 5,
264
269
  toolbarButtonClass: 'btn btn-sm btn-light',
265
270
  });
266
271
  ```
@@ -296,7 +301,7 @@ const editor = AutumnNote.create('#my-editor', {
296
301
  fd.append('file', files[0]);
297
302
  fetch('/api/upload', { method: 'POST', body: fd })
298
303
  .then(r => r.json())
299
- .then(({ url }) => editor.insertImage(url, files[0].name));
304
+ .then(({ url }) => editor.invoke('editor.insertImage', url, files[0].name));
300
305
  },
301
306
  });
302
307
  ```
@@ -462,7 +467,6 @@ See the [full Plugin API docs →](https://autumn.konexforge.com/docs.html#plugi
462
467
  | `toolbar` | `Array[]` | all buttons | Toolbar layout. See [Toolbar Customisation](#toolbar-customisation). |
463
468
  | `toolbarOverflow` | `string` | `'wrap'` | Toolbar overflow strategy: `'wrap'` or `'scroll'`. |
464
469
  | `useBootstrap` | `boolean` | `false` | Apply Bootstrap CSS classes to toolbar buttons. |
465
- | `bootstrapVersion` | `number` | `5` | Bootstrap major version (`4` or `5`). |
466
470
  | `toolbarButtonClass` | `string` | `'btn btn-sm btn-light'` | CSS classes for toolbar buttons when `useBootstrap` is `true`. |
467
471
  | `useFontAwesome` | `boolean` | `true` | Use FA icons when FontAwesome is detected on the page. |
468
472
  | `fontAwesomeClass` | `string` | `'fas'` | FA prefix: `'fas'` for FA 5, `'fa-solid'` for FA 6. |
@@ -479,7 +483,7 @@ See the [full Plugin API docs →](https://autumn.konexforge.com/docs.html#plugi
479
483
  | `fontFamilies` | `string[]` | 10 fonts | Font families available in the font-family dropdown. |
480
484
  | `stickyToolbar` | `boolean` | `false` | Pin the toolbar to the viewport top when the page is scrolled. |
481
485
  | `stickyToolbarOffset` | `number` | `0` | Top offset in pixels for the sticky toolbar (e.g. height of a fixed nav bar). |
482
- | `theme` | `string` | `'light'` | Colour theme: `'light'` or `'dark'`. |
486
+ | `theme` | `string` | `'light'` | Colour theme: `'light'`, `'dark'`, or `'auto'` (follows system preference). |
483
487
  | `readOnly` | `boolean` | `false` | Start the editor in non-editable (read-only) mode with toolbar hidden. |
484
488
  | `spellcheck` | `boolean` | `true` | Enable browser spellcheck in the editable area. |
485
489
  | `direction` | `string` | `'ltr'` | Text direction: `'ltr'` or `'rtl'`. |
@@ -495,10 +499,12 @@ See the [full Plugin API docs →](https://autumn.konexforge.com/docs.html#plugi
495
499
  | `lang` | `string \| object` | `'en'` | UI display language. Built-in codes: `'en'`, `'vi'`, `'ja'`, `'zh'`, `'fr'`, `'de'`, `'es'`, `'ko'`. Pass a partial locale object for custom overrides. |
496
500
  | `markdownShortcuts` | `boolean` | `true` | Convert Markdown-style syntax typed in the editor to HTML in real time (block and inline rules). |
497
501
  | `bubbleToolbar` | `boolean` | `false` | Show a mini floating toolbar above the text selection for quick formatting. |
498
- | `bubbleToolbarItems` | `string[]` | `['bold','italic','underline','link','foreColor','removeFormat']` | Buttons shown in the bubble toolbar. Available names: `'bold'`, `'italic'`, `'underline'`, `'strikethrough'`, `'link'`, `'foreColor'`, `'removeFormat'`, `'inlineCode'`. |
502
+ | `bubbleToolbarItems` | `string[]` | `['bold','italic','underline','link','foreColor','hiliteColor','removeFormat']` | Buttons shown in the bubble toolbar. Available names: `'bold'`, `'italic'`, `'underline'`, `'strikethrough'`, `'link'`, `'foreColor'`, `'hiliteColor'`, `'removeFormat'`, `'inlineCode'`. |
499
503
  | `autoSaveRestore` | `boolean` | `false` | When `autoSave` is also `true`, show a restore banner on load if a draft exists. |
500
504
  | `autoSaveRestoreTimeout` | `number` | `7` | Max draft age in days before it is auto-discarded. `0` = no expiry. |
501
505
  | `onAutoSaveRestore` | `Function` | `null` | `(html, context) => void` — called after the user restores a draft. |
506
+ | `maxPasteSize` | `number` | `5242880` | Maximum paste payload size in bytes (default 5 MB). Pastes larger than this are silently dropped. |
507
+ | `minImageSize` | `number` | `20` | Minimum image dimension in px during resize (width and height). |
502
508
  | `mention` | `object` | `null` | @mention configuration object. Set `mention.onSearch` to activate. See [Mentions](#mentions). |
503
509
  | `onChange` | `Function` | `null` | `(html: string) => void` — called on every content change. |
504
510
  | `onFocus` | `Function` | `null` | `(context) => void` — called when the editor gains focus. |
@@ -1724,6 +1724,8 @@ var defaultOptions = {
1724
1724
  autoSaveRestoreTimeout: 7,
1725
1725
  onAutoSaveRestore: null,
1726
1726
  markdownShortcuts: true,
1727
+ maxPasteSize: 5 * 1024 * 1024,
1728
+ minImageSize: 20,
1727
1729
  bubbleToolbar: false,
1728
1730
  bubbleToolbarItems: [
1729
1731
  "bold",
@@ -5594,7 +5596,7 @@ function _domToMd(node, depth = 0) {
5594
5596
  * @returns {boolean} `true` if any Markdown-like pattern is present, `false` otherwise.
5595
5597
  */
5596
5598
  function isMarkdown(text) {
5597
- 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);
5598
5600
  }
5599
5601
  /**
5600
5602
  * Converts a Markdown string to an HTML string.
@@ -5621,6 +5623,18 @@ function markdownToHTML(text) {
5621
5623
  i++;
5622
5624
  continue;
5623
5625
  }
5626
+ if (line.trim() && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line) && !/^#{1,6} /.test(line) && i + 1 < lines.length) {
5627
+ if (/^=+\s*$/.test(lines[i + 1])) {
5628
+ out.push(`<h1>${_inline(line.trim())}</h1>`);
5629
+ i += 2;
5630
+ continue;
5631
+ }
5632
+ if (/^-{2,}\s*$/.test(lines[i + 1])) {
5633
+ out.push(`<h2>${_inline(line.trim())}</h2>`);
5634
+ i += 2;
5635
+ continue;
5636
+ }
5637
+ }
5624
5638
  if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
5625
5639
  out.push("<hr>");
5626
5640
  i++;
@@ -5643,30 +5657,15 @@ function markdownToHTML(text) {
5643
5657
  continue;
5644
5658
  }
5645
5659
  if (/^[-*+] /.test(line)) {
5646
- const items = [];
5647
- const isChecklist = /^[-*+]\s+\[[ xX]\]\s+/.test(line);
5648
- const listTag = isChecklist ? "ul class=\"an-checklist\"" : "ul";
5649
- while (i < lines.length && /^[-*+] /.test(lines[i])) {
5650
- if (/^[-*+]\s+\[[ xX]\]\s+/.test(lines[i]) !== isChecklist) break;
5651
- const content = lines[i].slice(2);
5652
- if (isChecklist) {
5653
- const cbMatch = /^\[([ xX])\][ \t]+/.exec(content);
5654
- const cbHtml = `<input type="checkbox" contenteditable="false"${cbMatch?.[1]?.toLowerCase() === "x" ? " checked" : ""}>`;
5655
- const textContent = cbMatch ? content.slice(cbMatch[0].length) : content;
5656
- items.push(`<li>${cbHtml}${_inline(textContent)}</li>`);
5657
- } else items.push(`<li>${_inline(content)}</li>`);
5658
- i++;
5659
- }
5660
- out.push(`<${listTag}>${items.join("")}</${listTag.split(" ")[0]}>`);
5660
+ const { html: listHtml, endIdx } = _parseListBlock(lines, i);
5661
+ out.push(listHtml);
5662
+ i = endIdx;
5661
5663
  continue;
5662
5664
  }
5663
5665
  if (/^\d+\. /.test(line)) {
5664
- const items = [];
5665
- while (i < lines.length && /^\d+\. /.test(lines[i])) {
5666
- items.push(`<li>${_inline(lines[i].replace(/^\d+\. /, ""))}</li>`);
5667
- i++;
5668
- }
5669
- out.push(`<ol>${items.join("")}</ol>`);
5666
+ const { html: listHtml, endIdx } = _parseListBlock(lines, i);
5667
+ out.push(listHtml);
5668
+ i = endIdx;
5670
5669
  continue;
5671
5670
  }
5672
5671
  if (line.trim() === "") {
@@ -5675,20 +5674,29 @@ function markdownToHTML(text) {
5675
5674
  }
5676
5675
  if (/^\|.+\|/.test(line) && i + 1 < lines.length && /^\|[\s|:-]+\|/.test(lines[i + 1])) {
5677
5676
  const headerCells = _parseTableRow(line);
5677
+ const alignments = _parseTableRow(lines[i + 1]).map((c) => {
5678
+ if (c.startsWith(":") && c.endsWith(":")) return "center";
5679
+ if (c.endsWith(":")) return "right";
5680
+ if (c.startsWith(":")) return "left";
5681
+ return null;
5682
+ });
5678
5683
  i += 2;
5679
5684
  const bodyRows = [];
5680
5685
  while (i < lines.length && /^\|.+\|/.test(lines[i])) {
5681
5686
  bodyRows.push(_parseTableRow(lines[i]));
5682
5687
  i++;
5683
5688
  }
5684
- const thead = `<thead><tr>${headerCells.map((c) => `<th>${_inline(c)}</th>`).join("")}</tr></thead>`;
5685
- const renderRow = (row) => `<tr>${row.map((c) => `<td>${_inline(c)}</td>`).join("")}</tr>`;
5689
+ const _cell = (tag, content, align) => {
5690
+ return `<${tag}${align ? ` style="text-align:${align}"` : ""}>${_inline(content)}</${tag}>`;
5691
+ };
5692
+ const thead = `<thead><tr>${headerCells.map((c, idx) => _cell("th", c, alignments[idx])).join("")}</tr></thead>`;
5693
+ const renderRow = (row) => `<tr>${row.map((c, idx) => _cell("td", c, alignments[idx])).join("")}</tr>`;
5686
5694
  const tbody = bodyRows.length ? `<tbody>${bodyRows.map(renderRow).join("")}</tbody>` : "";
5687
5695
  out.push(`<table>${thead}${tbody}</table>`);
5688
5696
  continue;
5689
5697
  }
5690
5698
  const paraLines = [];
5691
- while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6} |> |[-*+] |\d+\. |```|---\s*$|\*{3}\s*$|_{3}\s*$)/.test(lines[i]) && !/^\|.+\|/.test(lines[i])) {
5699
+ while (i < lines.length && lines[i].trim() !== "" && !/^(#{1,6} |> |[-*+] |\d+\. |```|---\s*$|\*{3}\s*$|_{3}\s*$)/.test(lines[i]) && !/^\|.+\|/.test(lines[i]) && !(i + 1 < lines.length && /^=+\s*$/.test(lines[i + 1])) && !(i + 1 < lines.length && /^-{2,}\s*$/.test(lines[i + 1]))) {
5692
5700
  paraLines.push(lines[i]);
5693
5701
  i++;
5694
5702
  }
@@ -5705,6 +5713,57 @@ function markdownToHTML(text) {
5705
5713
  function _parseTableRow(row) {
5706
5714
  return row.replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
5707
5715
  }
5716
+ function _parseListBlock(lines, startIdx) {
5717
+ const baseIndent = lines[startIdx].match(/^(\s*)/)[1].length;
5718
+ const isOL = /^\s*\d+\. /.test(lines[startIdx]);
5719
+ const items = [];
5720
+ let firstIsCB = null;
5721
+ let i = startIdx;
5722
+ while (i < lines.length) {
5723
+ const line = lines[i];
5724
+ if (line.trim() === "") break;
5725
+ const indent = line.match(/^(\s*)/)[1].length;
5726
+ if (indent < baseIndent) break;
5727
+ if (indent === baseIndent) {
5728
+ if (!/^\s*(?:[-*+]|\d+\.) /.test(line)) break;
5729
+ if (/^\s*\d+\. /.test(line) !== isOL) break;
5730
+ const raw = isOL ? line.replace(/^\s*\d+\. /, "") : line.replace(/^\s*[-*+] /, "");
5731
+ const isCB = !isOL && /^\[[ xX]\]\s+/.test(raw);
5732
+ if (firstIsCB === null) firstIsCB = isCB;
5733
+ if (isCB !== firstIsCB) break;
5734
+ const checked = isCB && raw[1].toLowerCase() === "x";
5735
+ const text = isCB ? raw.replace(/^\[[ xX]\]\s+/, "") : raw;
5736
+ items.push({
5737
+ text,
5738
+ isCB,
5739
+ checked,
5740
+ sub: ""
5741
+ });
5742
+ i++;
5743
+ } else {
5744
+ if (!items.length) {
5745
+ i++;
5746
+ continue;
5747
+ }
5748
+ if (/^\s*(?:[-*+]|\d+\.) /.test(line)) {
5749
+ const nested = _parseListBlock(lines, i);
5750
+ items[items.length - 1].sub += nested.html;
5751
+ i = nested.endIdx;
5752
+ } else {
5753
+ items[items.length - 1].text += " " + line.trim();
5754
+ i++;
5755
+ }
5756
+ }
5757
+ }
5758
+ const open = isOL ? "<ol>" : !isOL && firstIsCB === true ? "<ul class=\"an-checklist\">" : "<ul>";
5759
+ const close = isOL ? "</ol>" : "</ul>";
5760
+ return {
5761
+ html: `${open}${items.map(({ text, isCB, checked, sub }) => {
5762
+ return `<li>${isCB ? `<input type="checkbox" contenteditable="false"${checked ? " checked" : ""}>` : ""}${_inline(text)}${sub}</li>`;
5763
+ }).join("")}${close}`,
5764
+ endIdx: i
5765
+ };
5766
+ }
5708
5767
  function _inline(text) {
5709
5768
  text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => `<img src="${_escAttr(src)}" alt="${_escAttr(alt)}" class="an-image">`);
5710
5769
  text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => `<a href="${_escAttr(href)}">${_esc(label)}</a>`);
@@ -7256,6 +7315,29 @@ var Clipboard = class {
7256
7315
  return doc.body.innerHTML;
7257
7316
  }
7258
7317
  /**
7318
+ * Normalizes task lists from external sources (GitHub, GitLab, etc.) so they
7319
+ * pass the sanitiser's `ul.an-checklist` guard. Runs before sanitiseHTML().
7320
+ * @param {string} html
7321
+ * @returns {string}
7322
+ */
7323
+ _normalizeExternalTaskLists(html) {
7324
+ const doc = new DOMParser().parseFromString(`<body>${html}</body>`, "text/html");
7325
+ for (const cb of doc.querySelectorAll("input[type=\"checkbox\"]")) {
7326
+ const li = cb.closest("li");
7327
+ const ul = li?.closest("ul");
7328
+ if (!li || !ul || ul.classList.contains("an-checklist")) continue;
7329
+ ul.classList.add("an-checklist");
7330
+ cb.removeAttribute("disabled");
7331
+ cb.setAttribute("contenteditable", "false");
7332
+ for (const attr of Array.from(cb.attributes)) if (![
7333
+ "type",
7334
+ "checked",
7335
+ "contenteditable"
7336
+ ].includes(attr.name)) cb.removeAttribute(attr.name);
7337
+ }
7338
+ return doc.body.innerHTML;
7339
+ }
7340
+ /**
7259
7341
  * Forces the next paste operation to strip all HTML formatting.
7260
7342
  * Called by Editor when Ctrl+Shift+V is pressed.
7261
7343
  * @param {boolean} val
@@ -7313,6 +7395,7 @@ var Clipboard = class {
7313
7395
  let html = raw;
7314
7396
  if (isWordContent) html = this._cleanWordHtml(html);
7315
7397
  else if (isSocialContent) html = this._cleanSocialHtml(html);
7398
+ html = this._normalizeExternalTaskLists(html);
7316
7399
  html = sanitiseHTML(html);
7317
7400
  if (this.options.pasteStripAttributes) html = this._stripAttributes(html);
7318
7401
  execCommand("insertHTML", html);
@@ -7427,7 +7510,7 @@ var Clipboard = class {
7427
7510
  _compressImage(file) {
7428
7511
  const MAX_DIM = 1920;
7429
7512
  const QUALITY = .85;
7430
- return new Promise((resolve) => {
7513
+ return new Promise((resolve, reject) => {
7431
7514
  const objectUrl = URL.createObjectURL(file);
7432
7515
  const img = new Image();
7433
7516
  img.onload = () => {
@@ -7447,6 +7530,7 @@ var Clipboard = class {
7447
7530
  if (!ctx) {
7448
7531
  const reader = new FileReader();
7449
7532
  reader.onload = (e) => resolve(e.target.result);
7533
+ reader.onerror = () => reject(/* @__PURE__ */ new Error("FileReader failed"));
7450
7534
  reader.readAsDataURL(file);
7451
7535
  return;
7452
7536
  }
@@ -7458,6 +7542,7 @@ var Clipboard = class {
7458
7542
  URL.revokeObjectURL(objectUrl);
7459
7543
  const reader = new FileReader();
7460
7544
  reader.onload = (e) => resolve(e.target.result);
7545
+ reader.onerror = () => reject(/* @__PURE__ */ new Error("FileReader failed"));
7461
7546
  reader.readAsDataURL(file);
7462
7547
  };
7463
7548
  img.src = objectUrl;
@@ -14179,7 +14264,7 @@ var defaultItems = [
14179
14264
  };
14180
14265
  if (typeof navigator.clipboard.read === "function") navigator.clipboard.read().then((items) => {
14181
14266
  for (const item of items) if (item.types.includes("text/html")) {
14182
- item.getType("text/html").then((blob) => blob.text()).then((html) => doInsert(html, null));
14267
+ item.getType("text/html").then((blob) => blob.text()).then((html) => doInsert(html, null)).catch(() => {});
14183
14268
  return;
14184
14269
  }
14185
14270
  navigator.clipboard.readText().then((text) => doInsert(null, text)).catch(() => {});
@@ -15304,6 +15389,10 @@ function drawCropToCanvas(img, naturalRect, renderW, renderH) {
15304
15389
  canvas.width = Math.round(renderW);
15305
15390
  canvas.height = Math.round(renderH);
15306
15391
  const ctx = canvas.getContext("2d");
15392
+ if (!ctx) {
15393
+ resolve(null);
15394
+ return;
15395
+ }
15307
15396
  try {
15308
15397
  ctx.drawImage(source, naturalRect.x, naturalRect.y, naturalRect.width, naturalRect.height, 0, 0, canvas.width, canvas.height);
15309
15398
  ctx.getImageData(0, 0, 1, 1);
@@ -17492,7 +17581,7 @@ var AutumnNote = {
17492
17581
  /** All pre-built button definitions — accessible in every module format including UMD/CJS. */
17493
17582
  buttons,
17494
17583
  /** Library version */
17495
- version: "1.8.2"
17584
+ version: "1.9.1"
17496
17585
  };
17497
17586
  /**
17498
17587
  * @param {string|Element|NodeList|Element[]} selector