autumnnote 1.9.1 → 1.10.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.es.js +91 -3
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +91 -3
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/core/markdown.js +87 -2
- package/src/js/index.js +1 -1
- package/types/index.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autumnnote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
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",
|
package/src/js/core/markdown.js
CHANGED
|
@@ -141,7 +141,9 @@ function _domToMd(node, depth = 0) {
|
|
|
141
141
|
export function isMarkdown(text) {
|
|
142
142
|
return /^#{1,6} [^\s]|^[ \t]*[-*+] [^\s]|^[ \t]*\d+\. [^\s]|^> [^\s]|^```|^\*{2}[^*\n]+\*{2}/m.test(text)
|
|
143
143
|
|| /^.+\n=+\s*$/m.test(text)
|
|
144
|
-
|| /^.+\n-{2,}\s*$/m.test(text)
|
|
144
|
+
|| /^.+\n-{2,}\s*$/m.test(text)
|
|
145
|
+
|| /^---\s*\n(?:[\s\S]*?\n)?(?:---|\.\.\.)\s*(?:\n|$)/.test(text)
|
|
146
|
+
|| /^\|.+\|[ \t]*\n\|[ \t:|-]+\|/m.test(text);
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
/**
|
|
@@ -150,7 +152,12 @@ export function isMarkdown(text) {
|
|
|
150
152
|
* @returns {string}
|
|
151
153
|
*/
|
|
152
154
|
export function markdownToHTML(text) {
|
|
153
|
-
|
|
155
|
+
let lines = text.replaceAll('\r\n', '\n').replaceAll('\r', '\n').split('\n');
|
|
156
|
+
lines = _stripFrontmatter(lines);
|
|
157
|
+
const refs = _extractReferenceDefinitions(lines);
|
|
158
|
+
lines = refs.clean;
|
|
159
|
+
_linkDefs = refs.linkDefs;
|
|
160
|
+
_footnoteIds = refs.footnoteIds;
|
|
154
161
|
const out = [];
|
|
155
162
|
let i = 0;
|
|
156
163
|
|
|
@@ -286,6 +293,68 @@ export function markdownToHTML(text) {
|
|
|
286
293
|
// Inline formatting
|
|
287
294
|
// ---------------------------------------------------------------------------
|
|
288
295
|
|
|
296
|
+
/** Reference-link and footnote definitions collected per markdownToHTML() call. */
|
|
297
|
+
let _linkDefs = new Map();
|
|
298
|
+
let _footnoteIds = new Set();
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Strips a leading YAML frontmatter block (--- ... --- or --- ... ...) from
|
|
302
|
+
* the line array, only when it is the very first line and the enclosed body
|
|
303
|
+
* looks like YAML (key: value / list items / indented continuations) — this
|
|
304
|
+
* disambiguates real frontmatter from a horizontal rule followed by prose.
|
|
305
|
+
* @param {string[]} lines
|
|
306
|
+
* @returns {string[]}
|
|
307
|
+
*/
|
|
308
|
+
function _stripFrontmatter(lines) {
|
|
309
|
+
if ((lines[0] || '').trim() !== '---') return lines;
|
|
310
|
+
let closeIdx = -1;
|
|
311
|
+
for (let j = 1; j < lines.length; j++) {
|
|
312
|
+
const t = lines[j].trim();
|
|
313
|
+
if (t === '---' || t === '...') { closeIdx = j; break; }
|
|
314
|
+
}
|
|
315
|
+
if (closeIdx === -1) return lines;
|
|
316
|
+
|
|
317
|
+
const body = lines.slice(1, closeIdx);
|
|
318
|
+
const looksLikeYAML = body.every((l) =>
|
|
319
|
+
l.trim() === '' ||
|
|
320
|
+
/^[ \t]*[\w$.-]+\s*:(\s|$)/.test(l) ||
|
|
321
|
+
/^[ \t]*-\s+\S/.test(l) ||
|
|
322
|
+
/^[ \t]+\S/.test(l));
|
|
323
|
+
if (!looksLikeYAML) return lines;
|
|
324
|
+
|
|
325
|
+
let start = closeIdx + 1;
|
|
326
|
+
if (lines[start] !== undefined && lines[start].trim() === '') start++;
|
|
327
|
+
return lines.slice(start);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Extracts GFM reference-link definitions (`[ref]: url "title"`) and footnote
|
|
332
|
+
* definitions (`[^id]: text`) from the line array, skipping fenced code
|
|
333
|
+
* regions. Returns the definition-free line array plus lookup maps.
|
|
334
|
+
* @param {string[]} lines
|
|
335
|
+
* @returns {{ clean: string[], linkDefs: Map<string, {href: string, title?: string}>, footnoteIds: Set<string> }}
|
|
336
|
+
*/
|
|
337
|
+
function _extractReferenceDefinitions(lines) {
|
|
338
|
+
const linkDefs = new Map();
|
|
339
|
+
const footnoteIds = new Set();
|
|
340
|
+
const clean = [];
|
|
341
|
+
let inFence = false;
|
|
342
|
+
const linkDefRe = /^\[([^\]]+)\]:\s*(\S+)(?:\s+"([^"]*)")?\s*$/;
|
|
343
|
+
const footnoteDefRe = /^\[\^([^\]]+)\]:\s*(.+)$/;
|
|
344
|
+
|
|
345
|
+
for (const line of lines) {
|
|
346
|
+
if (/^```/.test(line)) { inFence = !inFence; clean.push(line); continue; }
|
|
347
|
+
if (!inFence) {
|
|
348
|
+
const fm = footnoteDefRe.exec(line);
|
|
349
|
+
if (fm) { footnoteIds.add(fm[1]); continue; }
|
|
350
|
+
const lm = linkDefRe.exec(line);
|
|
351
|
+
if (lm) { linkDefs.set(lm[1].trim().toLowerCase(), { href: lm[2], title: lm[3] }); continue; }
|
|
352
|
+
}
|
|
353
|
+
clean.push(line);
|
|
354
|
+
}
|
|
355
|
+
return { clean, linkDefs, footnoteIds };
|
|
356
|
+
}
|
|
357
|
+
|
|
289
358
|
/**
|
|
290
359
|
* Splits a GFM table row string into trimmed cell strings.
|
|
291
360
|
* '| a | b | c |' → ['a', 'b', 'c']
|
|
@@ -356,6 +425,22 @@ function _inline(text) {
|
|
|
356
425
|
// Links
|
|
357
426
|
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) =>
|
|
358
427
|
`<a href="${_escAttr(href)}">${_esc(label)}</a>`);
|
|
428
|
+
// Reference-style links [text][ref] and shortcut [text][]
|
|
429
|
+
text = text.replace(/\[([^\]]+)\]\[([^\]]*)\]/g, (m, label, ref) => {
|
|
430
|
+
const def = _linkDefs.get((ref || label).trim().toLowerCase());
|
|
431
|
+
if (!def) return m;
|
|
432
|
+
const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : '';
|
|
433
|
+
return `<a href="${_escAttr(def.href)}"${titleAttr}>${_esc(label)}</a>`;
|
|
434
|
+
});
|
|
435
|
+
// Bare/implicit reference link [text] — only when a definition exists
|
|
436
|
+
text = text.replace(/\[([^\]]+)\]/g, (m, label) => {
|
|
437
|
+
const def = _linkDefs.get(label.trim().toLowerCase());
|
|
438
|
+
if (!def) return m;
|
|
439
|
+
const titleAttr = def.title ? ` title="${_escAttr(def.title)}"` : '';
|
|
440
|
+
return `<a href="${_escAttr(def.href)}"${titleAttr}>${_esc(label)}</a>`;
|
|
441
|
+
});
|
|
442
|
+
// Footnote reference marker [^id] — run last
|
|
443
|
+
text = text.replace(/\[\^([^\]]+)\]/g, (m, id) => (_footnoteIds.has(id) ? `<sup>[${_esc(id)}]</sup>` : m));
|
|
359
444
|
// Bold + italic ***text***
|
|
360
445
|
text = text.replace(/\*{3}([^*\n]+?)\*{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
|
|
361
446
|
text = text.replace(/_{3}([^_\n]+?)_{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
|
package/src/js/index.js
CHANGED