mnfst 0.5.233 → 0.5.234
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 +152 -68
- 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-pj/2ZCUMXz3zTWPZyICJHfCPnz5srho9URrw0Sl0nfWuvh5KqvoT/g3Kg4uyv7yl",
|
|
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-+wRqEd8FFPqaSX5gZhb66OgkAHTiSQDFK6W1agFMceKtd5nVL7OLh6p5gC96AJ3Z",
|
|
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-yJNb+EIumFIJx2KiKmLojwyHLCKwIn+48noxh3gUuZ10YZOvJJoanzAU7NLIBF1s",
|
|
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-alPjmWaugQV/aCb6l7hVITwZ6Z5k47SJ9jHXmNUN093iUu8ikbVggwLSGNb84Ib1",
|
|
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
|
|
|
@@ -1193,39 +1213,48 @@
|
|
|
1193
1213
|
return null;
|
|
1194
1214
|
}
|
|
1195
1215
|
|
|
1216
|
+
// Sizes are written only once the pointer actually drags: a click on a border writes nothing
|
|
1196
1217
|
function startResize(e, hit) {
|
|
1197
1218
|
const cell = hit.cell, table = cell.closest('table');
|
|
1198
1219
|
const area = cell.closest('[data-rich-text]');
|
|
1199
1220
|
e.preventDefault();
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
const row = cell.parentElement;
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
}
|
|
1221
|
+
const fromX = e.clientX, fromY = e.clientY;
|
|
1222
|
+
let apply = null;
|
|
1223
|
+
const begin = () => {
|
|
1224
|
+
area.setAttribute('data-rich-text-resizing', hit.axis);
|
|
1225
|
+
if (hit.axis === 'x') {
|
|
1226
|
+
const group = ensureCols(table);
|
|
1227
|
+
const { col } = gridPosition(cell);
|
|
1228
|
+
const index = Math.min(col + (cell.colSpan || 1) - 1, group.children.length - 1);
|
|
1229
|
+
const target = group.children[index];
|
|
1230
|
+
const start = parseFloat(target.style.width) || cell.getBoundingClientRect().width;
|
|
1231
|
+
// The outer border sizes the table too
|
|
1232
|
+
const outer = index === group.children.length - 1;
|
|
1233
|
+
const tableStart = table.getBoundingClientRect().width;
|
|
1234
|
+
if (outer) table.style.width = Math.round(tableStart) + 'px';
|
|
1235
|
+
return (ev) => {
|
|
1236
|
+
const delta = ev.clientX - fromX;
|
|
1237
|
+
target.style.width = Math.max(28, Math.round(start + delta)) + 'px';
|
|
1238
|
+
if (outer) table.style.width = Math.max(60, Math.round(tableStart + delta)) + 'px';
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const row = cell.parentElement, start = row.getBoundingClientRect().height;
|
|
1242
|
+
return (ev) => { row.style.height = Math.max(20, Math.round(start + ev.clientY - fromY)) + 'px'; };
|
|
1243
|
+
};
|
|
1224
1244
|
|
|
1225
|
-
const move = (ev) => {
|
|
1245
|
+
const move = (ev) => {
|
|
1246
|
+
ev.preventDefault();
|
|
1247
|
+
if (!apply) {
|
|
1248
|
+
if (Math.abs(ev.clientX - fromX) < 3 && Math.abs(ev.clientY - fromY) < 3) return;
|
|
1249
|
+
record(area);
|
|
1250
|
+
apply = begin();
|
|
1251
|
+
}
|
|
1252
|
+
apply(ev);
|
|
1253
|
+
};
|
|
1226
1254
|
const up = () => {
|
|
1227
1255
|
document.removeEventListener('pointermove', move);
|
|
1228
1256
|
document.removeEventListener('pointerup', up);
|
|
1257
|
+
if (!apply) return;
|
|
1229
1258
|
area.removeAttribute('data-rich-text-resizing');
|
|
1230
1259
|
area.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1231
1260
|
};
|
|
@@ -1668,7 +1697,13 @@
|
|
|
1668
1697
|
insertHTML(`<table data-rich-text-new><tbody>${row.repeat(rows)}</tbody></table><p><br></p>`);
|
|
1669
1698
|
// The caret goes into the first cell, so typing and table commands start there
|
|
1670
1699
|
const made = document.querySelector('table[data-rich-text-new]');
|
|
1671
|
-
if (made) {
|
|
1700
|
+
if (made) {
|
|
1701
|
+
made.removeAttribute('data-rich-text-new');
|
|
1702
|
+
// One empty line after it to land on, not one per insertion (from inside a table, it lands after it)
|
|
1703
|
+
const blank = (e) => e && e.tagName === 'P' && !e.textContent.trim() && !e.querySelector('img');
|
|
1704
|
+
for (let n = made.nextElementSibling; blank(n) && blank(n.nextElementSibling);) { const x = n.nextElementSibling; x.remove(); }
|
|
1705
|
+
const first = made.querySelector('td, th'); if (first) caretIn(first, 0);
|
|
1706
|
+
}
|
|
1672
1707
|
}
|
|
1673
1708
|
|
|
1674
1709
|
// A trailing table, rule or similar block gets a line after it to land on
|
|
@@ -1698,8 +1733,21 @@
|
|
|
1698
1733
|
}
|
|
1699
1734
|
|
|
1700
1735
|
// A value styles the selection; an empty one removes that style from it (spans left bare go)
|
|
1736
|
+
// A style span covering exactly what a style span around it covers is folded into it, so one span
|
|
1737
|
+
// carries the combined styles and a re-applied value replaces the old one instead of nesting
|
|
1701
1738
|
function styleSpan(prop, v) {
|
|
1702
|
-
if (v != null && v !== '')
|
|
1739
|
+
if (v != null && v !== '') {
|
|
1740
|
+
const made = wrapInline('span', { [prop]: v }).map(sp => {
|
|
1741
|
+
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) {
|
|
1742
|
+
for (let i = 0; i < sp.style.length; i++) { const name = sp.style.item(i); p.style.setProperty(name, sp.style.getPropertyValue(name)); }
|
|
1743
|
+
sp.replaceWith(...sp.childNodes);
|
|
1744
|
+
sp = p;
|
|
1745
|
+
}
|
|
1746
|
+
return sp;
|
|
1747
|
+
});
|
|
1748
|
+
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); }
|
|
1749
|
+
return made;
|
|
1750
|
+
}
|
|
1703
1751
|
const nodes = selectedTextNodes(), spans = new Set();
|
|
1704
1752
|
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
1753
|
spans.forEach(s => { s.style[prop] = ''; if (!s.getAttribute('style')) s.removeAttribute('style'); if (!s.attributes.length) unwrapEl(s); });
|
|
@@ -2143,7 +2191,10 @@
|
|
|
2143
2191
|
}
|
|
2144
2192
|
|
|
2145
2193
|
// Native Enter inside code and for Shift+Enter; Chrome writes <br> pairs in a <pre>
|
|
2194
|
+
// A code block's lines: in its <code> (the shape markdown gives), else in the <pre> itself
|
|
2195
|
+
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
2196
|
const codeEndsBlank = (pre) => {
|
|
2197
|
+
pre = codeBox(pre);
|
|
2147
2198
|
const last = pre.lastChild;
|
|
2148
2199
|
if (last && last.nodeName === 'BR' && last.previousSibling && last.previousSibling.nodeName === 'BR') return true;
|
|
2149
2200
|
return /\n[^\S\n]*$/.test(pre.textContent);
|
|
@@ -2214,10 +2265,13 @@
|
|
|
2214
2265
|
// An empty last line leaves the code block (bounded to the area)
|
|
2215
2266
|
const pre = holder.closest('pre');
|
|
2216
2267
|
if (pre && pre !== area && area.contains(pre)) {
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2268
|
+
const box = codeBox(pre);
|
|
2269
|
+
while (box.lastChild && (box.lastChild.nodeName === 'BR' || !box.lastChild.textContent.trim())) box.lastChild.remove();
|
|
2270
|
+
if (/\n[^\S\n]*$/.test(box.textContent)) box.textContent = box.textContent.replace(/\n[^\S\n]*$/, '');
|
|
2271
|
+
// onto the empty line after it, if there is one
|
|
2272
|
+
const after = pre.nextElementSibling, blank = after && after.tagName === 'P' && !after.textContent.trim() && !after.querySelector('img');
|
|
2273
|
+
const p = blank ? after : document.createElement('p');
|
|
2274
|
+
if (!blank) pre.after(p);
|
|
2221
2275
|
if (!pre.textContent.length) pre.remove();
|
|
2222
2276
|
caretIn(p, 0);
|
|
2223
2277
|
return;
|
|
@@ -2256,7 +2310,14 @@
|
|
|
2256
2310
|
if (holder.tagName === 'HR') { caretIn(next, 0); return; }
|
|
2257
2311
|
|
|
2258
2312
|
// A finished ``` line opens the code block
|
|
2259
|
-
|
|
2313
|
+
// (its text in a <code>, the shape a code block read from markdown has)
|
|
2314
|
+
if (holder.tagName === 'PRE') {
|
|
2315
|
+
next.remove();
|
|
2316
|
+
let code = holder.querySelector(':scope > code');
|
|
2317
|
+
if (!code) { code = document.createElement('code'); code.append(...holder.childNodes); holder.appendChild(code); }
|
|
2318
|
+
caretIn(code, 0);
|
|
2319
|
+
return;
|
|
2320
|
+
}
|
|
2260
2321
|
|
|
2261
2322
|
if (holder.tagName === 'LI') {
|
|
2262
2323
|
const item = next.tagName === 'LI' ? next : retagBlock(next, 'li');
|
|
@@ -2497,6 +2558,28 @@
|
|
|
2497
2558
|
queueMicrotask(() => { beating = false; const now = surface(); if (now === lastSurface) return; lastSurface = now; pulse.n++; });
|
|
2498
2559
|
};
|
|
2499
2560
|
|
|
2561
|
+
// Any CSS colour (oklch(), a name, rgb()) as #rrggbb, read off a canvas pixel; null when it cannot be
|
|
2562
|
+
let colorCanvas = null;
|
|
2563
|
+
const hexCache = new Map();
|
|
2564
|
+
function hexColor(v) {
|
|
2565
|
+
if (v == null || v === '') return null;
|
|
2566
|
+
const s = String(v).trim();
|
|
2567
|
+
if (/^#[0-9a-f]{6}$/i.test(s)) return s.toLowerCase();
|
|
2568
|
+
if (hexCache.has(s)) return hexCache.get(s);
|
|
2569
|
+
let out = null;
|
|
2570
|
+
try {
|
|
2571
|
+
colorCanvas = colorCanvas || document.createElement('canvas');
|
|
2572
|
+
const g = colorCanvas.getContext && colorCanvas.getContext('2d', { willReadFrequently: true });
|
|
2573
|
+
if (g) {
|
|
2574
|
+
g.clearRect(0, 0, 1, 1); g.fillStyle = '#000'; g.fillStyle = s; g.fillRect(0, 0, 1, 1);
|
|
2575
|
+
const [r, gr, b] = g.getImageData(0, 0, 1, 1).data;
|
|
2576
|
+
out = '#' + [r, gr, b].map(x => x.toString(16).padStart(2, '0')).join('');
|
|
2577
|
+
}
|
|
2578
|
+
} catch { out = null; }
|
|
2579
|
+
hexCache.set(s, out);
|
|
2580
|
+
return out;
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2500
2583
|
// Reflect caret state onto every control
|
|
2501
2584
|
function sync() {
|
|
2502
2585
|
beat();
|
|
@@ -2510,9 +2593,10 @@
|
|
|
2510
2593
|
c.setAttribute('aria-disabled', String(!usable));
|
|
2511
2594
|
if (usable && (all || spec.reflect) && 'value' in c && c !== document.activeElement) {
|
|
2512
2595
|
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
|
|
2596
|
+
// Never blank a <select> with a value it doesn't offer; a colour input takes #rrggbb only
|
|
2514
2597
|
const offered = c.tagName !== 'SELECT' || [...c.options].some(o => o.value === v);
|
|
2515
|
-
|
|
2598
|
+
const val = c.type === 'color' ? hexColor(v) : v;
|
|
2599
|
+
if (val != null && offered && c.value !== val) c.value = val;
|
|
2516
2600
|
}
|
|
2517
2601
|
if (c.tagName === 'BUTTON') c.setAttribute('aria-pressed', String(c.hasAttribute('data-rich-text-active')));
|
|
2518
2602
|
});
|
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.234';
|
|
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.234",
|
|
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",
|