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 +11 -5
- package/dist/autumnnote.es.js +117 -28
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +117 -28
- package/dist/autumnnote.umd.js.map +1 -1
- package/dist/robots.txt +4 -0
- package/dist/sitemap.xml +18 -0
- package/package.json +6 -2
- package/src/js/core/markdown.js +85 -35
- package/src/js/index.js +1 -1
- package/src/js/module/Clipboard.js +28 -1
- package/src/js/module/ContextMenu.js +1 -1
- package/src/js/module/ImageCropOverlay.js +1 -0
- package/src/js/settings.js +7 -1
- package/types/index.d.ts +4 -2
package/dist/robots.txt
ADDED
package/dist/sitemap.xml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
|
+
<url>
|
|
4
|
+
<loc>https://autumn.konexforge.com/</loc>
|
|
5
|
+
<changefreq>monthly</changefreq>
|
|
6
|
+
<priority>1.0</priority>
|
|
7
|
+
</url>
|
|
8
|
+
<url>
|
|
9
|
+
<loc>https://autumn.konexforge.com/docs.html</loc>
|
|
10
|
+
<changefreq>monthly</changefreq>
|
|
11
|
+
<priority>0.9</priority>
|
|
12
|
+
</url>
|
|
13
|
+
<url>
|
|
14
|
+
<loc>https://autumn.konexforge.com/playground.html</loc>
|
|
15
|
+
<changefreq>monthly</changefreq>
|
|
16
|
+
<priority>0.7</priority>
|
|
17
|
+
</url>
|
|
18
|
+
</urlset>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autumnnote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "WYSIWYG rich-text editor built with vanilla JavaScript — zero dependencies, no jQuery. Dark mode, @mention, markdown shortcuts, bubble toolbar. React and Vue 3 wrappers included.",
|
|
5
5
|
"main": "dist/autumnnote.umd.js",
|
|
6
6
|
"module": "dist/autumnnote.es.js",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
11
|
"import": "./dist/autumnnote.es.js",
|
|
12
|
-
"require": "./dist/autumnnote.umd.js"
|
|
12
|
+
"require": "./dist/autumnnote.umd.js",
|
|
13
|
+
"types": "./types/index.d.ts"
|
|
13
14
|
},
|
|
14
15
|
"./dist/autumnnote.css": "./dist/autumnnote.css"
|
|
15
16
|
},
|
|
@@ -100,6 +101,9 @@
|
|
|
100
101
|
"vite": "^8.0.3",
|
|
101
102
|
"vitest": "^4.1.2"
|
|
102
103
|
},
|
|
104
|
+
"engines": {
|
|
105
|
+
"node": ">=18.0.0"
|
|
106
|
+
},
|
|
103
107
|
"browserslist": [
|
|
104
108
|
"last 2 versions",
|
|
105
109
|
"not dead",
|
package/src/js/core/markdown.js
CHANGED
|
@@ -139,7 +139,9 @@ function _domToMd(node, depth = 0) {
|
|
|
139
139
|
* @returns {boolean} `true` if any Markdown-like pattern is present, `false` otherwise.
|
|
140
140
|
*/
|
|
141
141
|
export function isMarkdown(text) {
|
|
142
|
-
return /^#{1,6} [^\s]|^[ \t]*[-*+] [^\s]|^[ \t]*\d+\. [^\s]|^> [^\s]|^```|^\*{2}[^*\n]+\*{2}/m.test(text)
|
|
142
|
+
return /^#{1,6} [^\s]|^[ \t]*[-*+] [^\s]|^[ \t]*\d+\. [^\s]|^> [^\s]|^```|^\*{2}[^*\n]+\*{2}/m.test(text)
|
|
143
|
+
|| /^.+\n=+\s*$/m.test(text)
|
|
144
|
+
|| /^.+\n-{2,}\s*$/m.test(text);
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
/**
|
|
@@ -171,6 +173,20 @@ export function markdownToHTML(text) {
|
|
|
171
173
|
continue;
|
|
172
174
|
}
|
|
173
175
|
|
|
176
|
+
// ---- Setext headings (Title\n=== or Title\n---) -------------------------
|
|
177
|
+
if (line.trim() && !/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line) && !/^#{1,6} /.test(line) && i + 1 < lines.length) {
|
|
178
|
+
if (/^=+\s*$/.test(lines[i + 1])) {
|
|
179
|
+
out.push(`<h1>${_inline(line.trim())}</h1>`);
|
|
180
|
+
i += 2;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (/^-{2,}\s*$/.test(lines[i + 1])) {
|
|
184
|
+
out.push(`<h2>${_inline(line.trim())}</h2>`);
|
|
185
|
+
i += 2;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
174
190
|
// ---- Horizontal rule --- / *** / _________________________________________
|
|
175
191
|
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
|
|
176
192
|
out.push('<hr>');
|
|
@@ -200,41 +216,14 @@ export function markdownToHTML(text) {
|
|
|
200
216
|
|
|
201
217
|
// ---- Checklist or Unordered list - / * / + item ----------------------
|
|
202
218
|
if (/^[-*+] /.test(line)) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
const listTag = isChecklist ? 'ul class="an-checklist"' : 'ul';
|
|
206
|
-
while (i < lines.length && /^[-*+] /.test(lines[i])) {
|
|
207
|
-
const nextLineIsChecklist = /^[-*+]\s+\[[ xX]\]\s+/.test(lines[i]);
|
|
208
|
-
if (nextLineIsChecklist !== isChecklist) {
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
const itemLine = lines[i];
|
|
212
|
-
const content = itemLine.slice(2);
|
|
213
|
-
if (isChecklist) {
|
|
214
|
-
const cbMatch = /^\[([ xX])\][ \t]+/.exec(content);
|
|
215
|
-
const checked = cbMatch?.[1]?.toLowerCase() === 'x';
|
|
216
|
-
const checkedAttr = checked ? ' checked' : '';
|
|
217
|
-
const cbHtml = `<input type="checkbox" contenteditable="false"${checkedAttr}>`;
|
|
218
|
-
const textContent = cbMatch ? content.slice(cbMatch[0].length) : content;
|
|
219
|
-
items.push(`<li>${cbHtml}${_inline(textContent)}</li>`);
|
|
220
|
-
} else {
|
|
221
|
-
items.push(`<li>${_inline(content)}</li>`);
|
|
222
|
-
}
|
|
223
|
-
i++;
|
|
224
|
-
}
|
|
225
|
-
out.push(`<${listTag}>${items.join('')}</${listTag.split(' ')[0]}>`);
|
|
226
|
-
continue;
|
|
219
|
+
const { html: listHtml, endIdx } = _parseListBlock(lines, i);
|
|
220
|
+
out.push(listHtml); i = endIdx; continue;
|
|
227
221
|
}
|
|
228
222
|
|
|
229
223
|
// ---- Ordered list 1. item ----------------------------------------------
|
|
230
224
|
if (/^\d+\. /.test(line)) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
items.push(`<li>${_inline(lines[i].replace(/^\d+\. /, ''))}</li>`);
|
|
234
|
-
i++;
|
|
235
|
-
}
|
|
236
|
-
out.push(`<ol>${items.join('')}</ol>`);
|
|
237
|
-
continue;
|
|
225
|
+
const { html: listHtml, endIdx } = _parseListBlock(lines, i);
|
|
226
|
+
out.push(listHtml); i = endIdx; continue;
|
|
238
227
|
}
|
|
239
228
|
|
|
240
229
|
// ---- Blank line ----------------------------------------------------------
|
|
@@ -248,15 +237,25 @@ export function markdownToHTML(text) {
|
|
|
248
237
|
// a separator row (| --- | --- |). We detect and collect all rows.
|
|
249
238
|
if (/^\|.+\|/.test(line) && i + 1 < lines.length && /^\|[\s|:-]+\|/.test(lines[i + 1])) {
|
|
250
239
|
const headerCells = _parseTableRow(line);
|
|
240
|
+
const alignments = _parseTableRow(lines[i + 1]).map((c) => {
|
|
241
|
+
if (c.startsWith(':') && c.endsWith(':')) return 'center';
|
|
242
|
+
if (c.endsWith(':')) return 'right';
|
|
243
|
+
if (c.startsWith(':')) return 'left';
|
|
244
|
+
return null;
|
|
245
|
+
});
|
|
251
246
|
i += 2; // skip header + separator
|
|
252
247
|
const bodyRows = [];
|
|
253
248
|
while (i < lines.length && /^\|.+\|/.test(lines[i])) {
|
|
254
249
|
bodyRows.push(_parseTableRow(lines[i]));
|
|
255
250
|
i++;
|
|
256
251
|
}
|
|
257
|
-
const
|
|
252
|
+
const _cell = (tag, content, align) => {
|
|
253
|
+
const s = align ? ` style="text-align:${align}"` : '';
|
|
254
|
+
return `<${tag}${s}>${_inline(content)}</${tag}>`;
|
|
255
|
+
};
|
|
256
|
+
const thCells = headerCells.map((c, idx) => _cell('th', c, alignments[idx])).join('');
|
|
258
257
|
const thead = `<thead><tr>${thCells}</tr></thead>`;
|
|
259
|
-
const renderRow = (row) => `<tr>${row.map((c) =>
|
|
258
|
+
const renderRow = (row) => `<tr>${row.map((c, idx) => _cell('td', c, alignments[idx])).join('')}</tr>`;
|
|
260
259
|
const tbody = bodyRows.length ? `<tbody>${bodyRows.map(renderRow).join('')}</tbody>` : '';
|
|
261
260
|
out.push(`<table>${thead}${tbody}</table>`);
|
|
262
261
|
continue;
|
|
@@ -268,7 +267,9 @@ export function markdownToHTML(text) {
|
|
|
268
267
|
i < lines.length &&
|
|
269
268
|
lines[i].trim() !== '' &&
|
|
270
269
|
!/^(#{1,6} |> |[-*+] |\d+\. |```|---\s*$|\*{3}\s*$|_{3}\s*$)/.test(lines[i]) &&
|
|
271
|
-
!/^\|.+\|/.test(lines[i])
|
|
270
|
+
!/^\|.+\|/.test(lines[i]) &&
|
|
271
|
+
!(i + 1 < lines.length && /^=+\s*$/.test(lines[i + 1])) &&
|
|
272
|
+
!(i + 1 < lines.length && /^-{2,}\s*$/.test(lines[i + 1]))
|
|
272
273
|
) {
|
|
273
274
|
paraLines.push(lines[i]);
|
|
274
275
|
i++;
|
|
@@ -299,6 +300,55 @@ function _parseTableRow(row) {
|
|
|
299
300
|
.map((c) => c.trim());
|
|
300
301
|
}
|
|
301
302
|
|
|
303
|
+
function _parseListBlock(lines, startIdx) {
|
|
304
|
+
const baseIndent = (lines[startIdx].match(/^(\s*)/)[1]).length;
|
|
305
|
+
const isOL = /^\s*\d+\. /.test(lines[startIdx]);
|
|
306
|
+
const items = [];
|
|
307
|
+
let firstIsCB = null;
|
|
308
|
+
let i = startIdx;
|
|
309
|
+
|
|
310
|
+
while (i < lines.length) {
|
|
311
|
+
const line = lines[i];
|
|
312
|
+
if (line.trim() === '') break;
|
|
313
|
+
const indent = (line.match(/^(\s*)/)[1]).length;
|
|
314
|
+
if (indent < baseIndent) break;
|
|
315
|
+
|
|
316
|
+
if (indent === baseIndent) {
|
|
317
|
+
if (!/^\s*(?:[-*+]|\d+\.) /.test(line)) break;
|
|
318
|
+
if (/^\s*\d+\. /.test(line) !== isOL) break;
|
|
319
|
+
const raw = isOL ? line.replace(/^\s*\d+\. /, '') : line.replace(/^\s*[-*+] /, '');
|
|
320
|
+
const isCB = !isOL && /^\[[ xX]\]\s+/.test(raw);
|
|
321
|
+
if (firstIsCB === null) firstIsCB = isCB;
|
|
322
|
+
if (isCB !== firstIsCB) break;
|
|
323
|
+
const checked = isCB && raw[1].toLowerCase() === 'x';
|
|
324
|
+
const text = isCB ? raw.replace(/^\[[ xX]\]\s+/, '') : raw;
|
|
325
|
+
items.push({ text, isCB, checked, sub: '' });
|
|
326
|
+
i++;
|
|
327
|
+
} else {
|
|
328
|
+
if (!items.length) { i++; continue; }
|
|
329
|
+
if (/^\s*(?:[-*+]|\d+\.) /.test(line)) {
|
|
330
|
+
const nested = _parseListBlock(lines, i);
|
|
331
|
+
items[items.length - 1].sub += nested.html;
|
|
332
|
+
i = nested.endIdx;
|
|
333
|
+
} else {
|
|
334
|
+
items[items.length - 1].text += ' ' + line.trim();
|
|
335
|
+
i++;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const hasCB = !isOL && (firstIsCB === true);
|
|
341
|
+
const open = isOL ? '<ol>' : hasCB ? '<ul class="an-checklist">' : '<ul>';
|
|
342
|
+
const close = isOL ? '</ol>' : '</ul>';
|
|
343
|
+
const liHTML = items.map(({ text, isCB, checked, sub }) => {
|
|
344
|
+
const cbHTML = isCB
|
|
345
|
+
? `<input type="checkbox" contenteditable="false"${checked ? ' checked' : ''}>`
|
|
346
|
+
: '';
|
|
347
|
+
return `<li>${cbHTML}${_inline(text)}${sub}</li>`;
|
|
348
|
+
}).join('');
|
|
349
|
+
return { html: `${open}${liHTML}${close}`, endIdx: i };
|
|
350
|
+
}
|
|
351
|
+
|
|
302
352
|
function _inline(text) {
|
|
303
353
|
// Images before links (they share [] syntax)
|
|
304
354
|
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) =>
|
package/src/js/index.js
CHANGED
|
@@ -170,6 +170,30 @@ export class Clipboard {
|
|
|
170
170
|
return doc.body.innerHTML;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Normalizes task lists from external sources (GitHub, GitLab, etc.) so they
|
|
175
|
+
* pass the sanitiser's `ul.an-checklist` guard. Runs before sanitiseHTML().
|
|
176
|
+
* @param {string} html
|
|
177
|
+
* @returns {string}
|
|
178
|
+
*/
|
|
179
|
+
_normalizeExternalTaskLists(html) {
|
|
180
|
+
const doc = new DOMParser().parseFromString(`<body>${html}</body>`, 'text/html');
|
|
181
|
+
for (const cb of doc.querySelectorAll('input[type="checkbox"]')) {
|
|
182
|
+
const li = cb.closest('li');
|
|
183
|
+
const ul = li?.closest('ul');
|
|
184
|
+
if (!li || !ul || ul.classList.contains('an-checklist')) continue;
|
|
185
|
+
ul.classList.add('an-checklist');
|
|
186
|
+
cb.removeAttribute('disabled');
|
|
187
|
+
cb.setAttribute('contenteditable', 'false');
|
|
188
|
+
for (const attr of Array.from(cb.attributes)) {
|
|
189
|
+
if (!['type', 'checked', 'contenteditable'].includes(attr.name)) {
|
|
190
|
+
cb.removeAttribute(attr.name);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return doc.body.innerHTML;
|
|
195
|
+
}
|
|
196
|
+
|
|
173
197
|
/**
|
|
174
198
|
* Forces the next paste operation to strip all HTML formatting.
|
|
175
199
|
* Called by Editor when Ctrl+Shift+V is pressed.
|
|
@@ -255,6 +279,7 @@ export class Clipboard {
|
|
|
255
279
|
let html = raw;
|
|
256
280
|
if (isWordContent) html = this._cleanWordHtml(html);
|
|
257
281
|
else if (isSocialContent) html = this._cleanSocialHtml(html);
|
|
282
|
+
html = this._normalizeExternalTaskLists(html);
|
|
258
283
|
html = sanitiseHTML(html);
|
|
259
284
|
if (this.options.pasteStripAttributes) html = this._stripAttributes(html);
|
|
260
285
|
execCommand('insertHTML', html);
|
|
@@ -381,7 +406,7 @@ export class Clipboard {
|
|
|
381
406
|
const MAX_DIM = 1920;
|
|
382
407
|
const QUALITY = 0.85;
|
|
383
408
|
|
|
384
|
-
return new Promise((resolve) => {
|
|
409
|
+
return new Promise((resolve, reject) => {
|
|
385
410
|
const objectUrl = URL.createObjectURL(file);
|
|
386
411
|
const img = new Image();
|
|
387
412
|
|
|
@@ -408,6 +433,7 @@ export class Clipboard {
|
|
|
408
433
|
// embedding the original file without compression.
|
|
409
434
|
const reader = new FileReader();
|
|
410
435
|
reader.onload = (e) => resolve(/** @type {string} */ (e.target.result));
|
|
436
|
+
reader.onerror = () => reject(new Error('FileReader failed'));
|
|
411
437
|
reader.readAsDataURL(file);
|
|
412
438
|
return;
|
|
413
439
|
}
|
|
@@ -423,6 +449,7 @@ export class Clipboard {
|
|
|
423
449
|
// Fallback: embed original without compression
|
|
424
450
|
const reader = new FileReader();
|
|
425
451
|
reader.onload = (e) => resolve(/** @type {string} */ (e.target.result));
|
|
452
|
+
reader.onerror = () => reject(new Error('FileReader failed'));
|
|
426
453
|
reader.readAsDataURL(file);
|
|
427
454
|
};
|
|
428
455
|
|
|
@@ -77,7 +77,7 @@ const defaultItems = [
|
|
|
77
77
|
navigator.clipboard.read().then((items) => {
|
|
78
78
|
for (const item of items) {
|
|
79
79
|
if (item.types.includes('text/html')) {
|
|
80
|
-
item.getType('text/html').then((blob) => blob.text()).then((html) => doInsert(html, null));
|
|
80
|
+
item.getType('text/html').then((blob) => blob.text()).then((html) => doInsert(html, null)).catch(() => {});
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -48,6 +48,7 @@ function drawCropToCanvas(img, naturalRect, renderW, renderH) {
|
|
|
48
48
|
canvas.width = Math.round(renderW);
|
|
49
49
|
canvas.height = Math.round(renderH);
|
|
50
50
|
const ctx = canvas.getContext('2d');
|
|
51
|
+
if (!ctx) { resolve(null); return; } // GPU memory limit — let _confirm() handle null
|
|
51
52
|
try {
|
|
52
53
|
ctx.drawImage(
|
|
53
54
|
source,
|
package/src/js/settings.js
CHANGED
|
@@ -153,7 +153,7 @@ export const defaultOptions = {
|
|
|
153
153
|
// Accepts any valid CSS colour string, e.g. '#f97316', 'hsl(25,90%,55%)'.
|
|
154
154
|
focusColor: null,
|
|
155
155
|
// Display language for the editor UI.
|
|
156
|
-
// Built-in values: 'en' (default), 'vi', 'ja', 'zh', 'fr'.
|
|
156
|
+
// Built-in values: 'en' (default), 'vi', 'ja', 'zh', 'fr', 'de', 'es', 'ko'.
|
|
157
157
|
// Pass a partial or full locale object to override individual strings.
|
|
158
158
|
lang: 'en',
|
|
159
159
|
|
|
@@ -168,6 +168,12 @@ export const defaultOptions = {
|
|
|
168
168
|
// e.g. "## " at line start → <h2>, "**bold**" → <strong>
|
|
169
169
|
markdownShortcuts: true,
|
|
170
170
|
|
|
171
|
+
// Maximum paste size in bytes (default 5 MB). Pastes larger than this are silently dropped.
|
|
172
|
+
maxPasteSize: 5 * 1024 * 1024,
|
|
173
|
+
// Minimum image dimension in px during resize (width and height). Prevents images from being
|
|
174
|
+
// resized below this value.
|
|
175
|
+
minImageSize: 20,
|
|
176
|
+
|
|
171
177
|
// Bubble toolbar: show a mini floating toolbar above text selections.
|
|
172
178
|
bubbleToolbar: false,
|
|
173
179
|
bubbleToolbarItems: ['bold', 'italic', 'underline', 'link', 'foreColor', 'hiliteColor', 'removeFormat'],
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AutumnNote – TypeScript declarations
|
|
3
|
-
* @version 1.
|
|
3
|
+
* @version 1.9.1
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -104,6 +104,8 @@ export interface AsnOptions {
|
|
|
104
104
|
onPaste?: (data: { text: string; html: string | null }) => void;
|
|
105
105
|
/** Additional color swatches shown at the top of the color picker. */
|
|
106
106
|
colorSwatches?: string[];
|
|
107
|
+
/** Custom focus ring colour — overrides the default blue. Accepts any valid CSS colour string, e.g. '#f97316'. */
|
|
108
|
+
focusColor?: string | null;
|
|
107
109
|
/**
|
|
108
110
|
* Display language for the editor UI.
|
|
109
111
|
* Built-in: 'en' (default) | 'vi' | 'ja' | 'zh' | 'fr' | 'de' | 'es' | 'ko'.
|
|
@@ -134,7 +136,7 @@ export interface AsnOptions {
|
|
|
134
136
|
/** Show a mini floating toolbar above the text selection. Default: false. */
|
|
135
137
|
bubbleToolbar?: boolean;
|
|
136
138
|
/** Button names shown in the bubble toolbar. */
|
|
137
|
-
bubbleToolbarItems?: Array<'bold' | 'italic' | 'underline' | 'strikethrough' | 'link' | 'foreColor' | 'removeFormat' | 'inlineCode'>;
|
|
139
|
+
bubbleToolbarItems?: Array<'bold' | 'italic' | 'underline' | 'strikethrough' | 'link' | 'foreColor' | 'hiliteColor' | 'removeFormat' | 'inlineCode'>;
|
|
138
140
|
|
|
139
141
|
// ---- @mention (#5) --------------------------------------------------------
|
|
140
142
|
|