mnfst 0.5.233 → 0.5.235
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/lib/manifest.appwrite.data.js +1 -1
- package/lib/manifest.data.js +1 -1
- package/lib/manifest.edit.js +33 -10
- package/lib/manifest.integrity.json +5 -5
- package/lib/manifest.rich.text.js +224 -78
- package/lib/manifest.sw.js +1 -1
- package/package.json +2 -1
package/lib/manifest.data.js
CHANGED
package/lib/manifest.edit.js
CHANGED
|
@@ -186,6 +186,14 @@
|
|
|
186
186
|
}
|
|
187
187
|
const sanitizeFor = (el, html) => !window.ManifestRichText ? sanitizeHTML(html)
|
|
188
188
|
: authoredRich(el) ? window.ManifestRichText.sanitize(html, true, true) : leafClean(html, el);
|
|
189
|
+
// Text written into an element. A leaf shows exactly what is logged; a rich field owns its markup:
|
|
190
|
+
// it is written only when it differs as logged, then settles (keeping its editing affordances)
|
|
191
|
+
function writeText(el, html) {
|
|
192
|
+
const safe = sanitizeFor(el, html);
|
|
193
|
+
if (el.innerHTML === safe || (authoredRich(el) && sanitizeFor(el, el.innerHTML.trim()) === safe)) return;
|
|
194
|
+
el.innerHTML = safe;
|
|
195
|
+
if (el._rt && typeof el._rt.settle === 'function') el._rt.settle();
|
|
196
|
+
}
|
|
189
197
|
|
|
190
198
|
function sanitizeHTML(html) {
|
|
191
199
|
const t = document.createElement('template'); t.innerHTML = String(html == null ? '' : html);
|
|
@@ -529,7 +537,7 @@
|
|
|
529
537
|
const base = area._baseText || {}, baseC = area._baseClass || {}, baseS = area._baseStyle || {};
|
|
530
538
|
const pick = (prop, fallback) => iv[prop] !== undefined ? iv[prop] : (mv[prop] !== undefined ? mv[prop] : fallback);
|
|
531
539
|
const text = pick('text', base[p]), cls = pick('class', baseC[p]), sty = pick('style', baseS[p]);
|
|
532
|
-
if (text !== undefined)
|
|
540
|
+
if (text !== undefined) writeText(el, text);
|
|
533
541
|
if (cls !== undefined) writeClass(el, cls);
|
|
534
542
|
if (sty !== undefined && el.getAttribute('style') !== sty) { if (sty === '') el.removeAttribute('style'); else el.setAttribute('style', sty); }
|
|
535
543
|
});
|
|
@@ -540,7 +548,7 @@
|
|
|
540
548
|
if (path == null) return false;
|
|
541
549
|
if (prop === 'text') value = sanitizeFor(el, value);
|
|
542
550
|
if (before === undefined) before = prop === 'text' ? el._preEdit : prop === 'style' ? el._preStyle : el._preClass;
|
|
543
|
-
if (before === value) return false;
|
|
551
|
+
if (before === value || (prop === 'text' && before != null && sanitizeFor(el, before) === value)) return false;
|
|
544
552
|
const sig = nodeSig(el);
|
|
545
553
|
pushDelta(scope === 'main' ? { kind: 'cmp-main', component, path, prop, value, before, sig, region } : { kind: 'cmp-inst', component, region, path, prop, value, before, sig });
|
|
546
554
|
applyComponentState();
|
|
@@ -640,7 +648,7 @@
|
|
|
640
648
|
|
|
641
649
|
function applyStaticProp(el, prop, v) {
|
|
642
650
|
if (v === undefined) return;
|
|
643
|
-
if (prop === 'text')
|
|
651
|
+
if (prop === 'text') return writeText(el, v);
|
|
644
652
|
if (attrRefusal(prop, v, el.tagName)) return;
|
|
645
653
|
if (prop === 'class') return writeClass(el, v);
|
|
646
654
|
if (prop === 'style') return writeStyle(el, v);
|
|
@@ -862,8 +870,9 @@
|
|
|
862
870
|
const pre = prop === 'text' ? el._preEdit : prop === 'class' ? el._preClass : prop === 'style' ? el._preStyle : undefined;
|
|
863
871
|
before = pre !== undefined ? pre : fallback;
|
|
864
872
|
}
|
|
865
|
-
if (before === value) return false;
|
|
866
|
-
|
|
873
|
+
if (before === value || (prop === 'text' && before != null && sanitizeFor(el, before) === value)) return false;
|
|
874
|
+
// A rich field owns its markup: the logged value is never written back into it
|
|
875
|
+
if (prop === 'text' && document.activeElement !== el && !authoredRich(el)) writeText(el, value);
|
|
867
876
|
const last = log[cursor - 1];
|
|
868
877
|
if (opts.coalesce && last && cursor === log.length && last.kind === 'st-node' && last.region === region && last.path === path && last.prop === prop && Date.now() - (stamps.get(last) || 0) < COALESCE_MS) {
|
|
869
878
|
if (last.before === value) { log.pop(); cursor--; } else { last.value = value; stamps.set(last, Date.now()); }
|
|
@@ -901,7 +910,7 @@
|
|
|
901
910
|
value = String(value);
|
|
902
911
|
if (el && el._editRich) value = sanitizeFor(el, value);
|
|
903
912
|
const before = el ? el._preEdit : undefined;
|
|
904
|
-
if (before === value) return false;
|
|
913
|
+
if (before === value || (el && el._editRich && before != null && sanitizeFor(el, before) === value)) return false;
|
|
905
914
|
pushDelta({ kind: 'data-val', region: key(area), source, id, field, value, before });
|
|
906
915
|
applyDataValues();
|
|
907
916
|
return true;
|
|
@@ -2050,7 +2059,7 @@
|
|
|
2050
2059
|
area.querySelectorAll('[data-rich-text]:not([data-rich-text-bound])').forEach(el => {
|
|
2051
2060
|
if (el._editRt || !capOf(el, 'text') || !/^[@~]/.test(el.getAttribute('data-edit-key') || '')) return;
|
|
2052
2061
|
if (el._richBound) return; el._richBound = true;
|
|
2053
|
-
el.addEventListener('focusin', () => { el._preEdit = el
|
|
2062
|
+
el.addEventListener('focusin', () => { el._preEdit = leafText(el); });
|
|
2054
2063
|
el.addEventListener('blur', () => {
|
|
2055
2064
|
const a = regionOf(el) || area; // the region the element sits in NOW, not where it was armed
|
|
2056
2065
|
const commit = classify(a) === 'component' ? commitComponentNode : commitStaticNode;
|
|
@@ -2307,12 +2316,25 @@
|
|
|
2307
2316
|
el._cssvar.persist = !!(modifiers && modifiers.includes('persist'));
|
|
2308
2317
|
cssvarControls.add(el);
|
|
2309
2318
|
}
|
|
2319
|
+
// A colour input takes #rrggbb only: any CSS colour (a theme's oklch(), var() resolved) is read off a canvas pixel
|
|
2320
|
+
let colorCanvas = null;
|
|
2321
|
+
function hexColor(v) {
|
|
2322
|
+
if (/^#[0-9a-f]{6}$/i.test(v)) return v.toLowerCase();
|
|
2323
|
+
try {
|
|
2324
|
+
colorCanvas = colorCanvas || document.createElement('canvas');
|
|
2325
|
+
const g = colorCanvas.getContext && colorCanvas.getContext('2d', { willReadFrequently: true });
|
|
2326
|
+
if (!g) return null;
|
|
2327
|
+
const probe = document.createElement('i'); probe.style.color = v; if (!probe.style.color) return null;
|
|
2328
|
+
g.clearRect(0, 0, 1, 1); g.fillStyle = '#000'; g.fillStyle = v; g.fillRect(0, 0, 1, 1);
|
|
2329
|
+
return '#' + [...g.getImageData(0, 0, 1, 1).data.slice(0, 3)].map(x => x.toString(16).padStart(2, '0')).join('');
|
|
2330
|
+
} catch { return null; }
|
|
2331
|
+
}
|
|
2310
2332
|
function syncCssVarInput(el) {
|
|
2311
2333
|
const cv = el._cssvar; if (!cv || !('value' in el)) return;
|
|
2312
2334
|
const cur = getComputedStyle(themeTargetEl(cv.scope)).getPropertyValue(cv.var).trim();
|
|
2313
2335
|
if (!cur) return;
|
|
2314
|
-
const next = el.dataset.unit ? String(parseFloat(cur)) : cur;
|
|
2315
|
-
if (el.value !== next) el.value = next;
|
|
2336
|
+
const next = el.type === 'color' ? hexColor(cur) : el.dataset.unit ? String(parseFloat(cur)) : cur;
|
|
2337
|
+
if (next != null && el.value !== next) el.value = next;
|
|
2316
2338
|
}
|
|
2317
2339
|
function armCssVar(el) {
|
|
2318
2340
|
const cv = el._cssvar; if (!cv || !cv.var) return;
|
|
@@ -2922,7 +2944,8 @@
|
|
|
2922
2944
|
ancestor opts it out. Text that is still being typed keeps the browser's own
|
|
2923
2945
|
undo until it commits on blur; fields and x-rich-text keep theirs always. A leaf
|
|
2924
2946
|
on Rich Text uses Rich Text's history while it has steps (richHistoryKey). */
|
|
2925
|
-
|
|
2947
|
+
// Text as it would be logged: every baseline and comparison goes through the same sanitizer
|
|
2948
|
+
const leafText = (el) => el._editField && !el._editRich ? el.textContent : sanitizeFor(el, el.innerHTML.trim());
|
|
2926
2949
|
const hotkeysOff = (el) => !!el.closest('[data-edit-hotkeys="off"]');
|
|
2927
2950
|
// A focused leaf's baseline follows history rewrites under the caret.
|
|
2928
2951
|
function rebaseFocused() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-dsIWnH0qCAGSzAXMGrd8fvOrVl4tOIE6j5tC35JPTrQXZ56qLD36ARyvhR7vi3Id",
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-U1jsUuWiDJM4Gis+q87cyGQMA+iyShm2cxBTZNhdQLXDLjZoxO5KomiRzUTdBZDF",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
6
|
"manifest.charts.js": "sha384-gEAv/98+29RbhvNkiNWTNyBPbLTFr2Ld15wLu29FLCL57j1cwGChRrL9Pfk7dBtT",
|
|
@@ -11,24 +11,24 @@
|
|
|
11
11
|
"manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
|
|
12
12
|
"manifest.components.js": "sha384-JalISaBBWNFl3ggx92Hb3B52EpwwG3gzgra6Q+js0OgZ8vrFKMD48CuBox2N9A5T",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384-
|
|
14
|
+
"manifest.data.js": "sha384-HwDBkbNBxPVfBam7Y2jBi6DmDokw+Lw1OnHQZRM1LB6RKMxMKsUWAMzuEx86LaAJ",
|
|
15
15
|
"manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
|
|
16
16
|
"manifest.defer.js": "sha384-pMRGqQKiEoJhpUcD40QeM4jEHau7sz9Xn/YCpiUamT1T4xJBZDSiYX+xnVrSWoq+",
|
|
17
17
|
"manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
18
18
|
"manifest.dropdowns.js": "sha384-acufUps7R9WjqB4XXW5JLACqniuHJGM4CNTb2rF4B2Q/jv7ZRmsAwfr2McanOz3s",
|
|
19
|
-
"manifest.edit.js": "sha384-
|
|
19
|
+
"manifest.edit.js": "sha384-XUtJ+EXXYh+5afgYuVpXurjvylSP/o72Y4mQR5wxdkIFQ/NDFBVvRFAfbPXMInZc",
|
|
20
20
|
"manifest.export.js": "sha384-Rx6XvqBZc4apzP/FwcDr6yIln4/zNa08Q4ZqBZcEqUz1aNZQfl4h0+Ae7mqCQyuG",
|
|
21
21
|
"manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
|
|
22
22
|
"manifest.localization.js": "sha384-EImsIw8Z3l13V5eH9ZJF/yfsOfIvR1oPRNSArANkPji7V8O3IRf6zMwsNiAGj3Hk",
|
|
23
23
|
"manifest.markdown.js": "sha384-heyDR40LXHaAmAuA+fa381uFwShk1oAqpFFY6E2Qq4T3VN0TQfRqyc++MF40K5Hj",
|
|
24
24
|
"manifest.native.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
25
25
|
"manifest.payments.js": "sha384-8pofk2zhxtapPMpwnFyTelPMrYhShNhDCE722uQh/EYFvZFuPLmSdgunqsarVQjv",
|
|
26
|
-
"manifest.rich.text.js": "sha384-
|
|
26
|
+
"manifest.rich.text.js": "sha384-SeqwIXW01/FD39kMHLjCMaFKheVK/JxHOR/eDkePhvgaFH8Rl2y3mzZoqHuu8UH8",
|
|
27
27
|
"manifest.router.js": "sha384-QkKhRaCbdXEOxhj+ojT4jeLUCtQepBnqP+QWTIWdb6kUWD+mVzPN8hf6jhgschpW",
|
|
28
28
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
29
29
|
"manifest.status.js": "sha384-MBO345ab7H6Uq1X2dZLt4Ge1Gcra+Z2qYApvlAeIV8CJhDDJrz2FPXpp+Kcp8zcM",
|
|
30
30
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
31
|
-
"manifest.sw.js": "sha384-
|
|
31
|
+
"manifest.sw.js": "sha384-V+o5XeR3JaL7nGLhtZ14cymUmbx61S2nigZDlkLMr9rD6Ni2A0cVMDrmP+wNr26o",
|
|
32
32
|
"manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
|
|
33
33
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
34
34
|
"manifest.toasts.js": "sha384-3zkWxzw693pxg3QDFsoYCA3CqYaLKmBaMArULXODD66Utolpewl0OAeTB92aWUVv",
|
|
@@ -92,18 +92,14 @@
|
|
|
92
92
|
if (n.nodeType !== 1) return textEdge(last ? n.previousSibling : n.nextSibling, last);
|
|
93
93
|
if (n.tagName === 'BR') return '\n';
|
|
94
94
|
if (n.tagName === 'IMG') return last ? ')' : '!';
|
|
95
|
-
if (
|
|
95
|
+
if (n.tagName === 'A') return last ? ')' : '[';
|
|
96
|
+
if (MARKS[n.tagName]) return MARKS[n.tagName][0]; // its delimiter
|
|
96
97
|
const t = n.textContent; return t ? (last ? t[t.length - 1] : t[0]) : textEdge(last ? n.previousSibling : n.nextSibling, last);
|
|
97
98
|
};
|
|
98
|
-
// A * mark inside with text right against it: its delimiters could close an enclosing * mark
|
|
99
|
-
const riskyInside = (n) => [...n.querySelectorAll('strong, b, em, i')].some(d => {
|
|
100
|
-
const before = d.previousSibling ? textEdge(d.previousSibling, true) : null, after = d.nextSibling ? textEdge(d.nextSibling, false) : null;
|
|
101
|
-
return (before != null && !/\s/.test(before)) || (after != null && !/\s/.test(after));
|
|
102
|
-
});
|
|
103
99
|
function inlineRaw(node, ctx) {
|
|
104
100
|
let out = '';
|
|
105
101
|
node.childNodes.forEach(n => {
|
|
106
|
-
if (n.nodeType === 3) { out += esc(n.textContent); return; }
|
|
102
|
+
if (n.nodeType === 3) { out += ctx.link ? esc(n.textContent).replace(/[()]/g, '\\$&') : esc(n.textContent); return; } // in link text, no "(" can start a destination
|
|
107
103
|
if (n.nodeType !== 1) return;
|
|
108
104
|
const tag = n.tagName;
|
|
109
105
|
if (tag === 'BR') { out += ' \n'; return; }
|
|
@@ -112,8 +108,9 @@
|
|
|
112
108
|
if (tag === 'A' && !n.hasAttribute('title') && n.children.length === 0 && /^[a-z][a-z0-9+.-]{1,31}:[^\s<>]*$/i.test(n.getAttribute('href') || '') && n.textContent === n.getAttribute('href') && !unsafeUrl(n.textContent)) { out += '<' + n.textContent + '>'; return; }
|
|
113
109
|
if (tag === 'A') {
|
|
114
110
|
const h = n.getAttribute('href') || '', title = n.getAttribute('title');
|
|
115
|
-
const
|
|
116
|
-
|
|
111
|
+
const tt = title ? title.replace(/\n/g, ' ') : '';
|
|
112
|
+
const t = !tt ? '' : !tt.includes('"') ? ` "${tt.replace(/\\/g, '\\\\')}"` : !tt.includes("'") ? ` '${tt.replace(/\\/g, '\\\\')}'` : ` "${tt.replace(/[\\"]/g, '\\$&')}"`;
|
|
113
|
+
out += h && !unsafeUrl(h) ? `[${inlineRaw(n, { ...ctx, prev: '[', next: ']', link: true, trims: false })}](${mdUrl(h)}${t})` : inlineRaw(n, ctx);
|
|
117
114
|
return;
|
|
118
115
|
}
|
|
119
116
|
let m = MARKS[tag];
|
|
@@ -121,40 +118,46 @@
|
|
|
121
118
|
if (tag === 'CODE') {
|
|
122
119
|
// A fence longer than any backtick run inside; padded where the code starts or ends with one
|
|
123
120
|
const code = n.textContent; if (!code) return;
|
|
121
|
+
// Code holding a backtick needs a longer fence, which renderers do not all read alike
|
|
122
|
+
// around emphasis or in link text: written as HTML
|
|
123
|
+
if (code.includes('`')) { out += '<code>' + esc(code) + '</code>'; return; }
|
|
124
124
|
const fence = '`'.repeat(Math.max(0, ...(code.match(/`+/g) || []).map(r => r.length)) + 1);
|
|
125
125
|
const pad = /^`|`$/.test(code) || (/^ /.test(code) && / $/.test(code) && code.trim()) ? ' ' : '';
|
|
126
126
|
out += fence + pad + code + pad + fence;
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
if (!n.textContent.trim()) { out += inlineRaw(n, ctx); return; }
|
|
130
|
-
let body = inlineRaw(n, { ...ctx, prev: m[0], next: m[0],
|
|
130
|
+
let body = inlineRaw(n, { ...ctx, prev: m[0], next: m[0], trims: true });
|
|
131
131
|
// Delimiters hug the text: spaces go outside
|
|
132
132
|
let [, lead, core, trail] = body.match(/^(\s*)([\s\S]*?)(\s*)$/);
|
|
133
|
-
// _ where * would touch text (it could pair with an enclosing *), or encloses a mark that does
|
|
134
133
|
const after = textEdge(n.nextSibling, false);
|
|
135
|
-
|
|
134
|
+
// What will be against the delimiters: a space moved out of a markdown parent's edge is
|
|
135
|
+
// not there, the parent's delimiter is
|
|
136
|
+
const prev = lead ? (!out && ctx.trims ? ctx.prev : ' ') : (out ? out[out.length - 1] : ctx.prev);
|
|
137
|
+
const next = trail ? (after == null && ctx.trims ? ctx.next : ' ') : (after == null ? ctx.next : after);
|
|
136
138
|
const nonspace = /\S/.test(prev || '') || /\S/.test(next || '');
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// Delimiters every renderer reads the same way: whitespace or plain punctuation outside them
|
|
140
|
+
// (an enclosing mark's same delimiter too), text inside them that starts and ends plainly.
|
|
141
|
+
// Anything else (letters, another delimiter or an HTML tag against them) is written as HTML.
|
|
142
|
+
const isS = (c) => !c || /\s/.test(c), isD = (c) => c === '*' || c === '_' || c === '~';
|
|
143
|
+
// Punctuation outside only where text is inside: the delimiter can then only open (or close)
|
|
144
|
+
// Letters outside only between letters (intraword, as in foo**bar**), and only outside any other * mark
|
|
145
|
+
const wordy = (c) => !!c && !isS(c) && !MD_PUNCT.test(c);
|
|
146
|
+
const outside = (c, fromCtx, inner) => isS(c) || (fromCtx && c === m[0]) || (MD_PUNCT.test(c) && !isD(c) && c !== '<' && c !== '>' && c !== '`' && wordy(inner))
|
|
147
|
+
|| (wordy(c) && wordy(inner) && !ctx.star && m[0] === '*' && !n.querySelector('strong, b, em, i'));
|
|
148
|
+
const f = core[0], l = core[core.length - 1], bare = core.replace(new RegExp('^\\' + m[0] + '+|\\' + m[0] + '+$', 'g'), '');
|
|
149
|
+
const clean = outside(prev, !lead && !out, bare[0]) && outside(next, !trail && after == null, bare[bare.length - 1])
|
|
150
|
+
&& !isS(f) && !isS(l) && f !== '\\' && !/\\.$/.test(core) && f !== '<' && l !== '>'
|
|
151
|
+
&& !(isD(f) && f !== m[0]) && !(isD(l) && l !== m[0])
|
|
152
|
+
// runs merged at both ends (***a* b *c***) only around one inner mark (***all***)
|
|
153
|
+
&& !(f === m[0] && l === m[0] && [...n.childNodes].filter(c => c.nodeType === 1 || (c.nodeType === 3 && c.nodeValue.trim())).length > 1);
|
|
154
|
+
if ((m[0] === '*' || m[0] === '~') && (ctx.html === 'all' || (ctx.html === 'touch' && nonspace) || !clean)) {
|
|
155
|
+
const t = tag === 'STRONG' || tag === 'B' ? 'strong' : m[0] === '~' ? 's' : 'em';
|
|
156
|
+
// spaces stay inside a tag: what is against its children stays as computed
|
|
157
|
+
out += `<${t}>` + inlineRaw(n, { ...ctx, prev: '>', next: '<', trims: false }) + `</${t}>`;
|
|
141
158
|
return;
|
|
142
159
|
}
|
|
143
|
-
|
|
144
|
-
// * unless it would not read back: right after another * run, around a * mark with text
|
|
145
|
-
// against it, or with text against it inside a * mark
|
|
146
|
-
const touching = (!lead && /\S$/.test(out)) || (!trail && after != null && /\S/.test(after));
|
|
147
|
-
if (allowed && ((!lead && /(^|[^\\])\*$/.test(out)) || riskyInside(n) || (ctx.star && touching))) {
|
|
148
|
-
m = m.replace(/\*/g, '_');
|
|
149
|
-
[, lead, core, trail] = inlineRaw(n, { ...ctx, prev: '_', next: '_', under: true, star: ctx.star }).match(/^(\s*)([\s\S]*?)(\s*)$/);
|
|
150
|
-
} else if (m[0] === '*' && ctx.star && touching) {
|
|
151
|
-
// Inside a * mark with text against it, a * run could close the enclosing one: no
|
|
152
|
-
// markdown form says this, so it is written as HTML (which markdown carries)
|
|
153
|
-
const t = tag === 'STRONG' || tag === 'B' ? 'strong' : 'em';
|
|
154
|
-
[, lead, core, trail] = inlineRaw(n, { ...ctx, prev: '>', next: '<', under: ctx.under, star: false }).match(/^(\s*)([\s\S]*?)(\s*)$/);
|
|
155
|
-
out += lead + `<${t}>` + core + `</${t}>` + trail;
|
|
156
|
-
return;
|
|
157
|
-
} else if (m[0] === '*') [, lead, core, trail] = inlineRaw(n, { ...ctx, prev: '*', next: '*', under: ctx.under, star: true }).match(/^(\s*)([\s\S]*?)(\s*)$/);
|
|
160
|
+
if (m[0] === '*') [, lead, core, trail] = inlineRaw(n, { ...ctx, prev: '*', next: '*', star: true, trims: true }).match(/^(\s*)([\s\S]*?)(\s*)$/);
|
|
158
161
|
out += lead + m + core + m + trail;
|
|
159
162
|
});
|
|
160
163
|
return out;
|
|
@@ -163,13 +166,15 @@
|
|
|
163
166
|
// Inline markdown for a node, checked by reading it back: in the rare arrangement where a
|
|
164
167
|
// * or _ run would pair differently, em/strong touching text go as HTML, else all of them
|
|
165
168
|
function inline(node) {
|
|
166
|
-
const want = shapeOf(node), ctx = { prev: '', next: '',
|
|
169
|
+
const want = shapeOf(node), ctx = { prev: '', next: '', star: false, link: false, html: '' };
|
|
167
170
|
let first = null;
|
|
168
171
|
for (const html of ['', 'touch', 'all']) {
|
|
169
172
|
const md = inlineRaw(node, { ...ctx, html });
|
|
170
173
|
if (first == null) first = md;
|
|
174
|
+
// Read back as this reader does and as other markdown renderers (x-markdown) do
|
|
171
175
|
const t = document.createElement('template'); t.innerHTML = inlineHtml(md);
|
|
172
|
-
|
|
176
|
+
const u = document.createElement('template'); u.innerHTML = inlineHtml(md, { strict: true });
|
|
177
|
+
if (shapeOf(t.content) === want && shapeOf(u.content) === want) return md;
|
|
173
178
|
}
|
|
174
179
|
return first; // what no form can carry (an unsafe link, say) is not a reason to change notation
|
|
175
180
|
}
|
|
@@ -403,7 +408,7 @@
|
|
|
403
408
|
const url = m.url, body = s.slice(open, close);
|
|
404
409
|
flush();
|
|
405
410
|
if (img) parts.push(unsafeUrl(url, true) ? '' : `<img src="${escHtml(url)}" alt="${escHtml(body.replace(/\\([!-\/:-@\[-`{-~])/g, '$1'))}">`);
|
|
406
|
-
else parts.push(unsafeUrl(url) || !url ? inlineHtml(body, { raw: opts.raw }) : `<a href="${escHtml(url)}"${m.title != null ? ` title="${escHtml(m.title)}"` : ''}>${inlineHtml(body, { raw: opts.raw })}</a>`);
|
|
411
|
+
else parts.push(unsafeUrl(url) || !url ? inlineHtml(body, { raw: opts.raw, strict: opts.strict }) : `<a href="${escHtml(url)}"${m.title != null ? ` title="${escHtml(m.title)}"` : ''}>${inlineHtml(body, { raw: opts.raw, strict: opts.strict })}</a>`);
|
|
407
412
|
i = m.end; continue;
|
|
408
413
|
}
|
|
409
414
|
}
|
|
@@ -425,6 +430,10 @@
|
|
|
425
430
|
const left = !isSpace(after) && (!PUNCT.test(after) || isSpace(before) || PUNCT.test(before));
|
|
426
431
|
const right = !isSpace(before) && (!PUNCT.test(before) || isSpace(after) || PUNCT.test(after));
|
|
427
432
|
open = left && (!right || PUNCT.test(before)); close = right && (!left || PUNCT.test(after));
|
|
433
|
+
} else if (opts.strict) {
|
|
434
|
+
// CommonMark's flanking, punctuation weighed (what other markdown renderers do)
|
|
435
|
+
open = !isSpace(after) && (!PUNCT.test(after) || isSpace(before) || PUNCT.test(before));
|
|
436
|
+
close = !isSpace(before) && (!PUNCT.test(before) || isSpace(after) || PUNCT.test(after));
|
|
428
437
|
} else { open = !isSpace(after); close = !isSpace(before); }
|
|
429
438
|
if (c === '~' && run.length !== 2) { open = close = false; }
|
|
430
439
|
if (!open && !close) { text += run; i += run.length; continue; }
|
|
@@ -545,8 +554,19 @@
|
|
|
545
554
|
});
|
|
546
555
|
walk(t.content);
|
|
547
556
|
mergeAdjacent(t.content);
|
|
557
|
+
if (allowBlocks) dropTrailingEmpty(t.content);
|
|
548
558
|
return t.innerHTML;
|
|
549
559
|
}
|
|
560
|
+
// The empty paragraph after a closing list, table or code block is an editing affordance only:
|
|
561
|
+
// values never end in one (nor in several)
|
|
562
|
+
function dropTrailingEmpty(root) {
|
|
563
|
+
for (let n = root.lastChild; n;) {
|
|
564
|
+
const prev = n.previousSibling;
|
|
565
|
+
if ((n.nodeType === 3 && !n.nodeValue.trim()) || (n.nodeType === 1 && n.tagName === 'P' && !n.textContent.trim() && !n.querySelector('img, input'))) n.remove();
|
|
566
|
+
else break;
|
|
567
|
+
n = prev;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
550
570
|
|
|
551
571
|
const markdownHtml = (md) => sanitize(fromMarkdown(md), true);
|
|
552
572
|
|
|
@@ -967,6 +987,8 @@
|
|
|
967
987
|
const have = (grid[r] || []).filter(Boolean).length;
|
|
968
988
|
for (let i = have; i < width; i++) row.appendChild(emptyCell(rowCellTag(row)));
|
|
969
989
|
});
|
|
990
|
+
const group = table.querySelector(':scope > colgroup');
|
|
991
|
+
if (group && group.children.length !== width) rebalanceCols(table);
|
|
970
992
|
if (!table.querySelector('tbody, thead')) {
|
|
971
993
|
const body = document.createElement('tbody');
|
|
972
994
|
while (table.firstChild) body.appendChild(table.firstChild);
|
|
@@ -975,6 +997,43 @@
|
|
|
975
997
|
});
|
|
976
998
|
}
|
|
977
999
|
|
|
1000
|
+
// A table whose column widths were set by resizing keeps them adding up to 100% as its columns
|
|
1001
|
+
// change: an added column takes an equal share (the others scale down), a removed one's share
|
|
1002
|
+
// goes to all the others in proportion, and no column falls under the minimum. Tables without widths are left alone.
|
|
1003
|
+
function rebalanceCols(table, change) {
|
|
1004
|
+
const group = table.querySelector(':scope > colgroup'); if (!group) return;
|
|
1005
|
+
let w = [...group.children].map(c => /%$/.test(c.style.width) ? parseFloat(c.style.width) : NaN);
|
|
1006
|
+
if (!w.length || w.some(isNaN)) return;
|
|
1007
|
+
const n = columnCount(table);
|
|
1008
|
+
if (change && change.add != null) {
|
|
1009
|
+
const share = 100 / (w.length + 1);
|
|
1010
|
+
w = w.map(v => v * (100 - share) / 100);
|
|
1011
|
+
w.splice(Math.min(change.add, w.length), 0, share);
|
|
1012
|
+
} else if (change && change.remove != null && change.remove < w.length) {
|
|
1013
|
+
// its share goes to every remaining column, in proportion to its width
|
|
1014
|
+
const [gone] = w.splice(change.remove, 1), base = w.reduce((a, v) => a + v, 0);
|
|
1015
|
+
w = w.map(v => v + gone * (base ? v / base : 1 / w.length));
|
|
1016
|
+
}
|
|
1017
|
+
// Any other mismatch (merges, splits, padding): columns added at the end take equal shares, extras go
|
|
1018
|
+
while (w.length < n) { const share = 100 / (w.length + 1); w = w.map(v => v * (100 - share) / 100); w.push(share); }
|
|
1019
|
+
if (w.length > n) { const extra = w.splice(n).reduce((a, b) => a + b, 0); if (w.length) w[w.length - 1] += extra; }
|
|
1020
|
+
if (!w.length) return;
|
|
1021
|
+
// The minimum, taken from the widest columns
|
|
1022
|
+
const px = table.getBoundingClientRect().width, min = px ? MIN_COL / px * 100 : 0;
|
|
1023
|
+
if (min && min * w.length > 100) w = w.map(() => 100 / w.length); // too narrow for the minimum: equal shares
|
|
1024
|
+
else if (min) {
|
|
1025
|
+
for (let guard = 0; guard < 20 && w.some(v => v < min - 1e-6); guard++) {
|
|
1026
|
+
const need = w.reduce((a, v) => a + Math.max(0, min - v), 0), room = w.reduce((a, v) => a + Math.max(0, v - min), 0);
|
|
1027
|
+
w = w.map(v => v < min ? min : v - (v - min) / room * need);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const total = w.reduce((a, b) => a + b, 0), pct = (v) => Math.round(v * 100) / 100;
|
|
1031
|
+
while (group.children.length < w.length) group.appendChild(document.createElement('col'));
|
|
1032
|
+
while (group.children.length > w.length) group.lastElementChild.remove();
|
|
1033
|
+
let used = 0;
|
|
1034
|
+
[...group.children].forEach((col, i) => { const v = i === w.length - 1 ? pct(100 - used) : pct(w[i] / total * 100); used += v; col.style.width = v + '%'; });
|
|
1035
|
+
}
|
|
1036
|
+
|
|
978
1037
|
function addRow(where) {
|
|
979
1038
|
const cell = cellAt(); if (!cell) return;
|
|
980
1039
|
const row = cell.parentElement, table = row.closest('table');
|
|
@@ -996,6 +1055,7 @@
|
|
|
996
1055
|
fresh.appendChild(document.createElement('br'));
|
|
997
1056
|
if (where < 0) at.before(fresh); else at.after(fresh);
|
|
998
1057
|
});
|
|
1058
|
+
rebalanceCols(table, { add: where < 0 ? col : col + 1 });
|
|
999
1059
|
normalizeTables(table.parentElement || table);
|
|
1000
1060
|
caretIn(cellCovering(rowsOf(table)[gridPosition(cell).row], where < 0 ? col : col + 1), 0);
|
|
1001
1061
|
}
|
|
@@ -1015,6 +1075,7 @@
|
|
|
1015
1075
|
if (columnCount(table) <= 1) return removeTable();
|
|
1016
1076
|
const seen = new Set();
|
|
1017
1077
|
grid.forEach(row => { const at = row[col]; if (!at || seen.has(at)) return; seen.add(at); at.remove(); });
|
|
1078
|
+
rebalanceCols(table, { remove: col });
|
|
1018
1079
|
normalizeTables(table.parentElement || table);
|
|
1019
1080
|
caretIn(cellCovering(rowsOf(table)[0], Math.max(0, col - 1)), 0);
|
|
1020
1081
|
}
|
|
@@ -1152,21 +1213,34 @@
|
|
|
1152
1213
|
Widths on a <colgroup>, heights on the row. */
|
|
1153
1214
|
const EDGE = 5;
|
|
1154
1215
|
|
|
1155
|
-
|
|
1216
|
+
const MIN_COL = 28;
|
|
1217
|
+
// Each column's rendered width: a cell spanning it alone, else a spanning cell's share
|
|
1218
|
+
function columnWidths(table) {
|
|
1219
|
+
const grid = tableGrid(table), n = columnCount(table), out = [];
|
|
1220
|
+
for (let i = 0; i < n; i++) {
|
|
1221
|
+
let w = null;
|
|
1222
|
+
for (const row of grid) { const c = row[i]; if (c && (c.colSpan || 1) === 1) { w = c.getBoundingClientRect().width; break; } }
|
|
1223
|
+
if (w == null) { const c = grid.map(r => r[i]).find(Boolean); w = c ? c.getBoundingClientRect().width / (c.colSpan || 1) : 0; }
|
|
1224
|
+
out.push(w);
|
|
1225
|
+
}
|
|
1226
|
+
return out;
|
|
1227
|
+
}
|
|
1228
|
+
// Widths as percentages: of the table for its columns (they add up to 100%), and of the field
|
|
1229
|
+
// for the table, so what is stored is what renders, whatever width the field has
|
|
1230
|
+
function writeWidths(table, widths, tableWidth, fieldWidth) {
|
|
1156
1231
|
let group = table.querySelector(':scope > colgroup');
|
|
1157
1232
|
if (!group) { group = document.createElement('colgroup'); table.prepend(group); }
|
|
1158
|
-
|
|
1159
|
-
while (group.children.length
|
|
1160
|
-
|
|
1161
|
-
|
|
1233
|
+
while (group.children.length < widths.length) group.appendChild(document.createElement('col'));
|
|
1234
|
+
while (group.children.length > widths.length) group.lastElementChild.remove();
|
|
1235
|
+
const total = widths.reduce((a, b) => a + b, 0) || 1, pct = (v) => Math.round(v * 100) / 100;
|
|
1236
|
+
let used = 0;
|
|
1162
1237
|
[...group.children].forEach((col, i) => {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
if (at) col.style.width = Math.round(at.getBoundingClientRect().width / (at.colSpan || 1)) + 'px';
|
|
1238
|
+
const v = i === widths.length - 1 ? pct(100 - used) : pct(widths[i] / total * 100);
|
|
1239
|
+
used += v; col.style.width = v + '%';
|
|
1166
1240
|
});
|
|
1167
|
-
|
|
1241
|
+
table.style.width = pct(Math.min(100, tableWidth / fieldWidth * 100)) + '%';
|
|
1242
|
+
table.style.tableLayout = 'fixed'; // stored widths are the rendered ones, with or without the plugin's stylesheet
|
|
1168
1243
|
}
|
|
1169
|
-
|
|
1170
1244
|
function borderAt(x, y) {
|
|
1171
1245
|
const el = document.elementFromPoint(x, y);
|
|
1172
1246
|
let cell = el && el.closest && el.closest('td, th');
|
|
@@ -1193,39 +1267,56 @@
|
|
|
1193
1267
|
return null;
|
|
1194
1268
|
}
|
|
1195
1269
|
|
|
1270
|
+
// Sizes are written only once the pointer actually drags: a click on a border writes nothing
|
|
1196
1271
|
function startResize(e, hit) {
|
|
1197
1272
|
const cell = hit.cell, table = cell.closest('table');
|
|
1198
1273
|
const area = cell.closest('[data-rich-text]');
|
|
1199
1274
|
e.preventDefault();
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1275
|
+
const fromX = e.clientX, fromY = e.clientY;
|
|
1276
|
+
let apply = null;
|
|
1277
|
+
const begin = () => {
|
|
1278
|
+
area.setAttribute('data-rich-text-resizing', hit.axis);
|
|
1279
|
+
if (hit.axis === 'x') {
|
|
1280
|
+
// As a spreadsheet does: a border between columns moves width from one to the other (the
|
|
1281
|
+
// table keeps its width); the outer border sizes the last column and the table, within the field
|
|
1282
|
+
const { col } = gridPosition(cell);
|
|
1283
|
+
const widths = columnWidths(table), last = widths.length - 1;
|
|
1284
|
+
const index = Math.min(col + (cell.colSpan || 1) - 1, last);
|
|
1285
|
+
const tableStart = table.getBoundingClientRect().width;
|
|
1286
|
+
const cs = getComputedStyle(area), field = Math.max(1, area.clientWidth - (parseFloat(cs.paddingLeft) || 0) - (parseFloat(cs.paddingRight) || 0));
|
|
1287
|
+
const room = Math.max(field, tableStart) - (tableStart - widths.reduce((a, b) => a + b, 0)); // columns' room, borders aside
|
|
1288
|
+
return (ev) => {
|
|
1289
|
+
const delta = ev.clientX - fromX, w = widths.slice();
|
|
1290
|
+
if (index < last) {
|
|
1291
|
+
const pair = widths[index] + widths[index + 1];
|
|
1292
|
+
if (pair < 2 * MIN_COL) return;
|
|
1293
|
+
w[index] = Math.min(pair - MIN_COL, Math.max(MIN_COL, widths[index] + delta));
|
|
1294
|
+
w[index + 1] = pair - w[index];
|
|
1295
|
+
writeWidths(table, w, tableStart, field);
|
|
1296
|
+
} else {
|
|
1297
|
+
const others = widths.reduce((a, b) => a + b, 0) - widths[last];
|
|
1298
|
+
w[last] = Math.max(MIN_COL, Math.min(widths[last] + delta, room - others));
|
|
1299
|
+
writeWidths(table, w, tableStart - widths[last] + w[last], field);
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
const row = cell.parentElement, start = row.getBoundingClientRect().height;
|
|
1304
|
+
return (ev) => { row.style.height = Math.max(20, Math.round(start + ev.clientY - fromY)) + 'px'; };
|
|
1305
|
+
};
|
|
1224
1306
|
|
|
1225
|
-
const move = (ev) => {
|
|
1307
|
+
const move = (ev) => {
|
|
1308
|
+
ev.preventDefault();
|
|
1309
|
+
if (!apply) {
|
|
1310
|
+
if (Math.abs(ev.clientX - fromX) < 3 && Math.abs(ev.clientY - fromY) < 3) return;
|
|
1311
|
+
record(area);
|
|
1312
|
+
apply = begin();
|
|
1313
|
+
}
|
|
1314
|
+
apply(ev);
|
|
1315
|
+
};
|
|
1226
1316
|
const up = () => {
|
|
1227
1317
|
document.removeEventListener('pointermove', move);
|
|
1228
1318
|
document.removeEventListener('pointerup', up);
|
|
1319
|
+
if (!apply) return;
|
|
1229
1320
|
area.removeAttribute('data-rich-text-resizing');
|
|
1230
1321
|
area.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1231
1322
|
};
|
|
@@ -1668,7 +1759,13 @@
|
|
|
1668
1759
|
insertHTML(`<table data-rich-text-new><tbody>${row.repeat(rows)}</tbody></table><p><br></p>`);
|
|
1669
1760
|
// The caret goes into the first cell, so typing and table commands start there
|
|
1670
1761
|
const made = document.querySelector('table[data-rich-text-new]');
|
|
1671
|
-
if (made) {
|
|
1762
|
+
if (made) {
|
|
1763
|
+
made.removeAttribute('data-rich-text-new');
|
|
1764
|
+
// One empty line after it to land on, not one per insertion (from inside a table, it lands after it)
|
|
1765
|
+
const blank = (e) => e && e.tagName === 'P' && !e.textContent.trim() && !e.querySelector('img');
|
|
1766
|
+
for (let n = made.nextElementSibling; blank(n) && blank(n.nextElementSibling);) { const x = n.nextElementSibling; x.remove(); }
|
|
1767
|
+
const first = made.querySelector('td, th'); if (first) caretIn(first, 0);
|
|
1768
|
+
}
|
|
1672
1769
|
}
|
|
1673
1770
|
|
|
1674
1771
|
// A trailing table, rule or similar block gets a line after it to land on
|
|
@@ -1698,8 +1795,21 @@
|
|
|
1698
1795
|
}
|
|
1699
1796
|
|
|
1700
1797
|
// A value styles the selection; an empty one removes that style from it (spans left bare go)
|
|
1798
|
+
// A style span covering exactly what a style span around it covers is folded into it, so one span
|
|
1799
|
+
// carries the combined styles and a re-applied value replaces the old one instead of nesting
|
|
1701
1800
|
function styleSpan(prop, v) {
|
|
1702
|
-
if (v != null && v !== '')
|
|
1801
|
+
if (v != null && v !== '') {
|
|
1802
|
+
const made = wrapInline('span', { [prop]: v }).map(sp => {
|
|
1803
|
+
for (let p = sp.parentElement; p && p.tagName === 'SPAN' && !p.hasAttribute('data-rich-text') && [...p.attributes].every(a => a.name === 'style') && [...p.childNodes].filter(c => c.nodeType === 1 || c.nodeValue.length).length === 1; p = sp.parentElement) {
|
|
1804
|
+
for (let i = 0; i < sp.style.length; i++) { const name = sp.style.item(i); p.style.setProperty(name, sp.style.getPropertyValue(name)); }
|
|
1805
|
+
sp.replaceWith(...sp.childNodes);
|
|
1806
|
+
sp = p;
|
|
1807
|
+
}
|
|
1808
|
+
return sp;
|
|
1809
|
+
});
|
|
1810
|
+
if (made.length) { const r = document.createRange(), last = made[made.length - 1]; r.setStart(made[0], 0); r.setEnd(last, last.childNodes.length); const s = sel(); s.removeAllRanges(); s.addRange(r); }
|
|
1811
|
+
return made;
|
|
1812
|
+
}
|
|
1703
1813
|
const nodes = selectedTextNodes(), spans = new Set();
|
|
1704
1814
|
nodes.forEach(n => { for (let el = elementAt(n); el && el.closest && !el.hasAttribute('data-rich-text'); el = el.parentElement) if (el.tagName === 'SPAN' && el.style[prop]) spans.add(el); });
|
|
1705
1815
|
spans.forEach(s => { s.style[prop] = ''; if (!s.getAttribute('style')) s.removeAttribute('style'); if (!s.attributes.length) unwrapEl(s); });
|
|
@@ -2143,7 +2253,10 @@
|
|
|
2143
2253
|
}
|
|
2144
2254
|
|
|
2145
2255
|
// Native Enter inside code and for Shift+Enter; Chrome writes <br> pairs in a <pre>
|
|
2256
|
+
// A code block's lines: in its <code> (the shape markdown gives), else in the <pre> itself
|
|
2257
|
+
const codeBox = (pre) => { const c = pre.firstElementChild; return c && c.tagName === 'CODE' && pre.children.length === 1 && !pre.textContent.replace(c.textContent, '').trim() ? c : pre; };
|
|
2146
2258
|
const codeEndsBlank = (pre) => {
|
|
2259
|
+
pre = codeBox(pre);
|
|
2147
2260
|
const last = pre.lastChild;
|
|
2148
2261
|
if (last && last.nodeName === 'BR' && last.previousSibling && last.previousSibling.nodeName === 'BR') return true;
|
|
2149
2262
|
return /\n[^\S\n]*$/.test(pre.textContent);
|
|
@@ -2214,10 +2327,13 @@
|
|
|
2214
2327
|
// An empty last line leaves the code block (bounded to the area)
|
|
2215
2328
|
const pre = holder.closest('pre');
|
|
2216
2329
|
if (pre && pre !== area && area.contains(pre)) {
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2330
|
+
const box = codeBox(pre);
|
|
2331
|
+
while (box.lastChild && (box.lastChild.nodeName === 'BR' || !box.lastChild.textContent.trim())) box.lastChild.remove();
|
|
2332
|
+
if (/\n[^\S\n]*$/.test(box.textContent)) box.textContent = box.textContent.replace(/\n[^\S\n]*$/, '');
|
|
2333
|
+
// onto the empty line after it, if there is one
|
|
2334
|
+
const after = pre.nextElementSibling, blank = after && after.tagName === 'P' && !after.textContent.trim() && !after.querySelector('img');
|
|
2335
|
+
const p = blank ? after : document.createElement('p');
|
|
2336
|
+
if (!blank) pre.after(p);
|
|
2221
2337
|
if (!pre.textContent.length) pre.remove();
|
|
2222
2338
|
caretIn(p, 0);
|
|
2223
2339
|
return;
|
|
@@ -2256,7 +2372,14 @@
|
|
|
2256
2372
|
if (holder.tagName === 'HR') { caretIn(next, 0); return; }
|
|
2257
2373
|
|
|
2258
2374
|
// A finished ``` line opens the code block
|
|
2259
|
-
|
|
2375
|
+
// (its text in a <code>, the shape a code block read from markdown has)
|
|
2376
|
+
if (holder.tagName === 'PRE') {
|
|
2377
|
+
next.remove();
|
|
2378
|
+
let code = holder.querySelector(':scope > code');
|
|
2379
|
+
if (!code) { code = document.createElement('code'); code.append(...holder.childNodes); holder.appendChild(code); }
|
|
2380
|
+
caretIn(code, 0);
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2260
2383
|
|
|
2261
2384
|
if (holder.tagName === 'LI') {
|
|
2262
2385
|
const item = next.tagName === 'LI' ? next : retagBlock(next, 'li');
|
|
@@ -2497,6 +2620,28 @@
|
|
|
2497
2620
|
queueMicrotask(() => { beating = false; const now = surface(); if (now === lastSurface) return; lastSurface = now; pulse.n++; });
|
|
2498
2621
|
};
|
|
2499
2622
|
|
|
2623
|
+
// Any CSS colour (oklch(), a name, rgb()) as #rrggbb, read off a canvas pixel; null when it cannot be
|
|
2624
|
+
let colorCanvas = null;
|
|
2625
|
+
const hexCache = new Map();
|
|
2626
|
+
function hexColor(v) {
|
|
2627
|
+
if (v == null || v === '') return null;
|
|
2628
|
+
const s = String(v).trim();
|
|
2629
|
+
if (/^#[0-9a-f]{6}$/i.test(s)) return s.toLowerCase();
|
|
2630
|
+
if (hexCache.has(s)) return hexCache.get(s);
|
|
2631
|
+
let out = null;
|
|
2632
|
+
try {
|
|
2633
|
+
colorCanvas = colorCanvas || document.createElement('canvas');
|
|
2634
|
+
const g = colorCanvas.getContext && colorCanvas.getContext('2d', { willReadFrequently: true });
|
|
2635
|
+
if (g) {
|
|
2636
|
+
g.clearRect(0, 0, 1, 1); g.fillStyle = '#000'; g.fillStyle = s; g.fillRect(0, 0, 1, 1);
|
|
2637
|
+
const [r, gr, b] = g.getImageData(0, 0, 1, 1).data;
|
|
2638
|
+
out = '#' + [r, gr, b].map(x => x.toString(16).padStart(2, '0')).join('');
|
|
2639
|
+
}
|
|
2640
|
+
} catch { out = null; }
|
|
2641
|
+
hexCache.set(s, out);
|
|
2642
|
+
return out;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2500
2645
|
// Reflect caret state onto every control
|
|
2501
2646
|
function sync() {
|
|
2502
2647
|
beat();
|
|
@@ -2510,9 +2655,10 @@
|
|
|
2510
2655
|
c.setAttribute('aria-disabled', String(!usable));
|
|
2511
2656
|
if (usable && (all || spec.reflect) && 'value' in c && c !== document.activeElement) {
|
|
2512
2657
|
const v = all ? area.style.getPropertyValue(ALL_VARS[cfg.id]).trim() : judged(area, () => spec.reflect());
|
|
2513
|
-
// Never blank a <select> with a value it doesn't offer
|
|
2658
|
+
// Never blank a <select> with a value it doesn't offer; a colour input takes #rrggbb only
|
|
2514
2659
|
const offered = c.tagName !== 'SELECT' || [...c.options].some(o => o.value === v);
|
|
2515
|
-
|
|
2660
|
+
const val = c.type === 'color' ? hexColor(v) : v;
|
|
2661
|
+
if (val != null && offered && c.value !== val) c.value = val;
|
|
2516
2662
|
}
|
|
2517
2663
|
if (c.tagName === 'BUTTON') c.setAttribute('aria-pressed', String(c.hasAttribute('data-rich-text-active')));
|
|
2518
2664
|
});
|
package/lib/manifest.sw.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
// Build stamps the published version here; unstamped (dev) falls back to the
|
|
18
18
|
// registration URL's `v`, then 'dev'.
|
|
19
|
-
const BUILD_VERSION = '0.5.
|
|
19
|
+
const BUILD_VERSION = '0.5.235';
|
|
20
20
|
const CACHE_PREFIX = 'mnfst-sw:';
|
|
21
21
|
const META_CACHE = CACHE_PREFIX + 'meta';
|
|
22
22
|
// Sized to hold a full precache.json warm-up (PRECACHE_MAX_ENTRIES server-side)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mnfst",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.235",
|
|
4
4
|
"private": false,
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"templates/starter",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"entities": "4.5.0",
|
|
61
61
|
"glob": "^11.0.2",
|
|
62
62
|
"happy-dom": "^20.11.6",
|
|
63
|
+
"marked": "15.0.12",
|
|
63
64
|
"puppeteer": "^24.15.0",
|
|
64
65
|
"rimraf": "^5.0.0",
|
|
65
66
|
"rollup": "^4.24.2",
|