autumnnote 1.9.1 → 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.
@@ -194,6 +194,20 @@ export class Clipboard {
194
194
  return doc.body.innerHTML;
195
195
  }
196
196
 
197
+ /**
198
+ * Checks whether an HTML payload has no semantic markup beyond plain
199
+ * wrapper elements (e.g. a bare <div>/<p>). Used to decide whether a
200
+ * markdown-shaped plain-text paste should win over an accompanying HTML
201
+ * payload that isn't actually carrying any real rich-text formatting.
202
+ * @param {string} html
203
+ * @returns {boolean}
204
+ */
205
+ _isTriviallyPlainHtml(html) {
206
+ const doc = new DOMParser().parseFromString(`<body>${html}</body>`, 'text/html');
207
+ const SIGNIFICANT = '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';
208
+ return !doc.body.querySelector(SIGNIFICANT);
209
+ }
210
+
197
211
  /**
198
212
  * Forces the next paste operation to strip all HTML formatting.
199
213
  * Called by Editor when Ctrl+Shift+V is pressed.
@@ -219,6 +233,9 @@ export class Clipboard {
219
233
  const size = Math.max(text.length, html.length);
220
234
  if (size > maxBytes) {
221
235
  event.preventDefault();
236
+ const message = `Pasted content (${size} bytes) exceeds the ${this.options.maxPasteSize ?? 5} MB paste size limit.`;
237
+ this.context.triggerEvent('pasteError', { size, maxBytes, message });
238
+ console.warn(`[AutumnNote] ${message}`);
222
239
  return;
223
240
  }
224
241
  }
@@ -257,13 +274,20 @@ export class Clipboard {
257
274
  return;
258
275
  }
259
276
 
260
- // 3. Markdown paste — only when no HTML is on the clipboard (pure text source)
261
- if (this.options.markdownPaste !== false && !clipboardData.types.includes('text/html')) {
277
+ // 3. Markdown paste — when there's no HTML on the clipboard, or the
278
+ // accompanying HTML has no semantic markup (e.g. some terminal/clipboard
279
+ // tools put both a markdown-shaped text/plain and a trivial <div>-wrapped
280
+ // text/html on the clipboard). Real rich-text sources (Word, Docs, etc.)
281
+ // always have semantic tags after cleaning, so this is unaffected.
282
+ if (this.options.markdownPaste !== false) {
283
+ const hasHtml = clipboardData.types.includes('text/html');
284
+ const html = hasHtml ? clipboardData.getData('text/html') : '';
285
+ const htmlTriviallyPlain = !hasHtml || this._isTriviallyPlainHtml(html);
262
286
  const text = clipboardData.getData('text/plain');
263
- if (text && isMarkdown(text)) {
287
+ if (text && htmlTriviallyPlain && isMarkdown(text)) {
264
288
  event.preventDefault();
265
- const html = sanitiseHTML(markdownToHTML(text));
266
- execCommand('insertHTML', html);
289
+ const converted = sanitiseHTML(markdownToHTML(text));
290
+ execCommand('insertHTML', converted);
267
291
  this.context.invoke('editor.afterCommand');
268
292
  return;
269
293
  }
@@ -307,14 +331,48 @@ export class Clipboard {
307
331
  if (!dt?.files?.length) return;
308
332
 
309
333
  const imageFiles = Array.from(dt.files).filter((f) => f.type.startsWith('image/'));
310
- if (imageFiles.length === 0) return;
334
+ if (imageFiles.length > 0) {
335
+ event.preventDefault();
336
+ event.stopPropagation();
337
+ // Place the caret at the drop coordinates before inserting
338
+ this._placeCaretAtPoint(event.clientX, event.clientY);
339
+ this._insertImageFiles(imageFiles);
340
+ return;
341
+ }
311
342
 
312
- event.preventDefault();
313
- event.stopPropagation();
343
+ if (this.options.markdownPaste !== false) {
344
+ const mdFile = Array.from(dt.files).find((f) => /\.md$/i.test(f.name) || f.type === 'text/markdown');
345
+ if (mdFile) {
346
+ event.preventDefault();
347
+ event.stopPropagation();
348
+ this._placeCaretAtPoint(event.clientX, event.clientY);
349
+ this._insertMarkdownFile(mdFile);
350
+ }
351
+ }
352
+ }
314
353
 
315
- // Place the caret at the drop coordinates before inserting
316
- this._placeCaretAtPoint(event.clientX, event.clientY);
317
- this._insertImageFiles(imageFiles);
354
+ /**
355
+ * Reads a dropped `.md` File and inserts it converted to HTML at the
356
+ * current caret. Skips the isMarkdown() heuristic — an explicit `.md`
357
+ * extension/MIME type is an unambiguous signal, unlike pasted plain text.
358
+ * @param {File} file
359
+ */
360
+ _insertMarkdownFile(file) {
361
+ const maxBytes = (this.options.maxPasteSize ?? 5) * 1024 * 1024;
362
+ if (maxBytes > 0 && file.size > maxBytes) {
363
+ const message = `Dropped file "${file.name}" (${file.size} bytes) exceeds the ${this.options.maxPasteSize ?? 5} MB paste size limit.`;
364
+ this.context.triggerEvent('pasteError', { size: file.size, maxBytes, message });
365
+ console.warn(`[AutumnNote] ${message}`);
366
+ return;
367
+ }
368
+ const reader = new FileReader();
369
+ reader.onload = (e) => {
370
+ const html = sanitiseHTML(markdownToHTML(/** @type {string} */ (e.target.result) || ''));
371
+ execCommand('insertHTML', html);
372
+ this.context.invoke('editor.afterCommand');
373
+ };
374
+ reader.onerror = () => console.warn('[AutumnNote] Failed to read dropped markdown file', file.name);
375
+ reader.readAsText(file);
318
376
  }
319
377
 
320
378
  // ---------------------------------------------------------------------------
@@ -459,8 +459,24 @@
459
459
  // Content styles
460
460
  p { margin: 0 0 0.75em; }
461
461
  h1, h2, h3, h4, h5, h6 { margin: 0.5em 0; line-height: 1.3; }
462
+ h1 { font-size: 2em; }
463
+ h2 { font-size: 1.5em; }
464
+ h3 { font-size: 1.25em; }
465
+ h4 { font-size: 1.1em; }
466
+ h5 { font-size: 1em; }
467
+ h6 { font-size: 0.875em; }
462
468
  ul, ol { margin: 0 0 0.75em 0; padding-left: 1.5em; list-style-position: inside; }
463
469
  li { margin-bottom: 0.25em; }
470
+
471
+ // Loose lists (markdown paste wraps item text in <p>): keep the list marker
472
+ // on the same line as the first paragraph (list-style-position: inside puts
473
+ // it in the inline flow), and use tighter margins than standalone <p>.
474
+ li > p {
475
+ margin: 0 0 0.5em;
476
+ &:first-child { display: inline; }
477
+ &:last-child { margin-bottom: 0; }
478
+ }
479
+ li:has(> p) { margin-bottom: 0.6em; }
464
480
  blockquote {
465
481
  margin: 0.5em 0 0.5em 1em;
466
482
  padding: 0.5em 1em;
@@ -475,6 +491,9 @@
475
491
  }
476
492
  pre { padding: 0.75em 1em; overflow-x: auto; }
477
493
  code { padding: 0.1em 0.3em; }
494
+ // Inside <pre> the wrapper already paints the background — reset the inner
495
+ // <code> so a plain (non-Prism) code fence isn't double-tinted.
496
+ pre > code { background: transparent; padding: 0; }
478
497
 
479
498
  // Prism-highlighted blocks — <pre> gets class="language-*" mirrored from <code>
480
499
  // Set dark background here as fallback for before Prism CSS loads from CDN.
@@ -1981,6 +2000,8 @@ $an-video-accent: #8b5cf6; // violet-500
1981
2000
  color: #cdd6f4;
1982
2001
  }
1983
2002
 
2003
+ pre:not([class*='language-']) > code { background: transparent; }
2004
+
1984
2005
  .an-code-line-numbers::before {
1985
2006
  background: rgba(255, 255, 255, 0.05);
1986
2007
  border-right-color: rgba(255, 255, 255, 0.1);
@@ -2447,6 +2468,8 @@ $an-video-accent: #8b5cf6; // violet-500
2447
2468
  color: #cdd6f4;
2448
2469
  }
2449
2470
 
2471
+ pre:not([class*='language-']) > code { background: transparent; }
2472
+
2450
2473
  hr { border-color: #3f3f5f; }
2451
2474
 
2452
2475
  table, .an-table {
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AutumnNote – TypeScript declarations
3
- * @version 1.9.1
3
+ * @version 1.11.0
4
4
  */
5
5
 
6
6
  // ---------------------------------------------------------------------------