autumnnote 1.8.3 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autumnnote",
3
- "version": "1.8.3",
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",
@@ -14,7 +14,6 @@
14
14
  },
15
15
  "./dist/autumnnote.css": "./dist/autumnnote.css"
16
16
  },
17
- "sideEffects": ["./dist/autumnnote.css", "**/*.css"],
18
17
  "files": [
19
18
  "dist",
20
19
  "src",
@@ -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 items = [];
204
- const isChecklist = /^[-*+]\s+\[[ xX]\]\s+/.test(line);
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 items = [];
232
- while (i < lines.length && /^\d+\. /.test(lines[i])) {
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 thCells = headerCells.map((c) => `<th>${_inline(c)}</th>`).join('');
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) => `<td>${_inline(c)}</td>`).join('')}</tr>`;
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
@@ -144,7 +144,7 @@ const AutumnNote = {
144
144
  buttons,
145
145
 
146
146
  /** Library version */
147
- version: '1.8.3',
147
+ version: '1.9.1',
148
148
  };
149
149
 
150
150
  // ---------------------------------------------------------------------------
@@ -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);
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AutumnNote – TypeScript declarations
3
- * @version 1.8.3
3
+ * @version 1.9.1
4
4
  */
5
5
 
6
6
  // ---------------------------------------------------------------------------