mnfst 0.5.219 → 0.5.221
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.code.js +7 -1
- package/lib/manifest.css +65 -39
- package/lib/manifest.data.js +1 -1
- package/lib/manifest.edit.css +16 -27
- package/lib/manifest.edit.js +51 -13
- package/lib/manifest.integrity.json +7 -7
- package/lib/manifest.markdown.js +11 -5
- package/lib/manifest.min.css +1 -1
- package/lib/manifest.sw.js +1 -1
- package/lib/manifest.text.edit.css +49 -12
- package/lib/manifest.text.edit.js +53 -6
- package/package.json +1 -1
|
@@ -9,13 +9,9 @@
|
|
|
9
9
|
@layer components {
|
|
10
10
|
|
|
11
11
|
:where([data-text-edit]) {
|
|
12
|
-
|
|
13
|
-
max-height: var(--text-edit-max-height, none);
|
|
12
|
+
position: relative;
|
|
14
13
|
overflow-y: auto;
|
|
15
|
-
|
|
16
|
-
background: var(--text-edit-background, var(--color-surface-1));
|
|
17
|
-
border: 1px solid var(--color-line);
|
|
18
|
-
border-radius: var(--radius);
|
|
14
|
+
max-height: var(--text-edit-max-height, none);
|
|
19
15
|
outline: none;
|
|
20
16
|
|
|
21
17
|
/* Page-level styling — set by .page controls, read back by $text.page.
|
|
@@ -26,19 +22,45 @@
|
|
|
26
22
|
text-align: var(--text-edit-align, start);
|
|
27
23
|
color: var(--text-edit-color, inherit);
|
|
28
24
|
|
|
29
|
-
&:focus {
|
|
30
|
-
border-color: var(--color-content-subtle);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
25
|
/* Flagged from script: :empty never matches the <br> a contenteditable
|
|
34
|
-
keeps, and the tag holding it is not predictable after a wipe.
|
|
26
|
+
keeps, and the tag holding it is not predictable after a wipe. The
|
|
27
|
+
placeholder OVERLAYS the first line (it is not a line of its own), so
|
|
28
|
+
the caret sits on top of it and nothing jumps when typing starts. */
|
|
35
29
|
&[data-text-edit-empty]::before {
|
|
36
30
|
content: attr(placeholder);
|
|
31
|
+
position: absolute;
|
|
32
|
+
inset-block-start: var(--text-edit-padding, 0.75rem);
|
|
33
|
+
inset-inline: var(--text-edit-padding, 0.75rem);
|
|
37
34
|
color: var(--color-content-subtle);
|
|
38
35
|
pointer-events: none;
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
38
|
|
|
39
|
+
/* An unstyled area has no field padding, so its placeholder hugs the line. */
|
|
40
|
+
:where([data-text-edit].unstyle)[data-text-edit-empty]::before {
|
|
41
|
+
inset-block-start: 0;
|
|
42
|
+
inset-inline: 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* The FIELD look. `.unstyle` drops it so an area can sit seamlessly in a
|
|
46
|
+
page — a WYSIWYG document inherits its element's own type and colours;
|
|
47
|
+
everything above (caret custody, placeholder, commands) still works. */
|
|
48
|
+
:where([data-text-edit]:not(.unstyle)) {
|
|
49
|
+
min-height: var(--text-edit-min-height, 8rem);
|
|
50
|
+
padding: var(--text-edit-padding, 0.75rem);
|
|
51
|
+
background: var(--text-edit-background, var(--color-surface-1));
|
|
52
|
+
border: 1px solid var(--color-line);
|
|
53
|
+
border-radius: var(--radius);
|
|
54
|
+
|
|
55
|
+
/* Held while the area owns the editing session — a <select> or colour
|
|
56
|
+
control takes NATIVE focus by design, and the ring must not flicker
|
|
57
|
+
off while the writer is mid-command. */
|
|
58
|
+
&:focus,
|
|
59
|
+
&[data-text-edit-focused] {
|
|
60
|
+
border-color: var(--color-content-subtle);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
/* ---- Selection menu (x-text-edit-menu) ----
|
|
43
65
|
Positioned by the --text-edit-selection-* variables the plugin writes to
|
|
44
66
|
:root, so the default needs no script; restyle or reposition freely. */
|
|
@@ -49,7 +71,6 @@
|
|
|
49
71
|
left: var(--text-edit-selection-center);
|
|
50
72
|
top: var(--text-edit-selection-y);
|
|
51
73
|
transform: translate(-50%, calc(-100% - 0.5rem));
|
|
52
|
-
display: flex;
|
|
53
74
|
gap: 0.25rem;
|
|
54
75
|
align-items: center;
|
|
55
76
|
padding: 0.25rem;
|
|
@@ -59,6 +80,22 @@
|
|
|
59
80
|
box-shadow: 0 8px 30px rgb(0 0 0 / 0.2);
|
|
60
81
|
}
|
|
61
82
|
|
|
83
|
+
/* display only while OPEN: an author display on the base rule would beat
|
|
84
|
+
the UA's closed-popover display:none, leaving the menu floating fixed
|
|
85
|
+
over the page before any selection exists. */
|
|
86
|
+
:where([data-text-edit-menu]:popover-open) {
|
|
87
|
+
display: flex;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Without the Popover API the attribute is inert and the menu would render
|
|
91
|
+
permanently fixed on the page — hide it there; the bubble is progressive
|
|
92
|
+
enhancement. (In supporting browsers a closed popover is hidden natively.) */
|
|
93
|
+
@supports not selector(:popover-open) {
|
|
94
|
+
:where([data-text-edit-menu]) {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
62
99
|
/* ---- Controls ----
|
|
63
100
|
A control is marked wherever it lives, so a toolbar is optional markup and
|
|
64
101
|
never a requirement. The reset already dims [aria-disabled=true]. */
|
|
@@ -389,15 +389,34 @@
|
|
|
389
389
|
Swapping the element outright covers every block tag, where formatBlock only
|
|
390
390
|
accepts a short list and disagrees between engines. */
|
|
391
391
|
function setBlock(tag) {
|
|
392
|
-
|
|
392
|
+
let b = blockEl(); if (!b) return;
|
|
393
393
|
const want = (b.tagName === tag.toUpperCase() ? 'p' : tag).toLowerCase();
|
|
394
394
|
if (b.tagName === want.toUpperCase()) return;
|
|
395
|
+
// A heading or quote cannot live inside a list — markdown has no
|
|
396
|
+
// <ul><h2> — so lift the item out of its list first, then retag.
|
|
397
|
+
// Retagging in place serialized to nothing: the content vanished.
|
|
398
|
+
// A NESTED item outdents to the top level first, or the lift would
|
|
399
|
+
// land the new block inside the outer <li>.
|
|
400
|
+
if (b.tagName === 'LI' && want !== 'li') {
|
|
401
|
+
const area = b.closest('[data-text-edit]');
|
|
402
|
+
for (let guard = 0; guard < 8; guard++) {
|
|
403
|
+
const list = b && b.tagName === 'LI' && b.closest('ul, ol');
|
|
404
|
+
if (!list || !list.parentElement || !list.parentElement.closest('li')) break;
|
|
405
|
+
indent(-1, area);
|
|
406
|
+
b = blockEl(); if (!b) return;
|
|
407
|
+
}
|
|
408
|
+
if (b.tagName === 'LI') { unwrapItem(b); b = blockEl(); if (!b) return; }
|
|
409
|
+
if (b.tagName === want.toUpperCase()) return;
|
|
410
|
+
}
|
|
395
411
|
const n = document.createElement(want);
|
|
396
412
|
n.setAttribute('style', b.getAttribute('style') || '');
|
|
397
413
|
if (!n.getAttribute('style')) n.removeAttribute('style');
|
|
398
414
|
const off = offsetIn(b);
|
|
399
415
|
while (b.firstChild) n.appendChild(b.firstChild);
|
|
400
416
|
b.replaceWith(n);
|
|
417
|
+
// A sublist that rode along out of a list item cannot live inside a
|
|
418
|
+
// heading or quote — it steps out right after the new block.
|
|
419
|
+
[...n.querySelectorAll(':scope > ul, :scope > ol')].reverse().forEach(l => n.after(l));
|
|
401
420
|
caretIn(n, off);
|
|
402
421
|
}
|
|
403
422
|
|
|
@@ -1019,8 +1038,8 @@
|
|
|
1019
1038
|
// not applied; the field re-syncs to the real href, showing it didn't take.
|
|
1020
1039
|
function normalizeUrl(url) {
|
|
1021
1040
|
if (/\s/.test(url) || /^\s*javascript:/i.test(url)) return null;
|
|
1022
|
-
if (/^[a-z][a-z0-9+.-]
|
|
1023
|
-
if (/^[\w-]+(\.[\w-]+)+(\/|$|\?|#)/.test(url)) return 'https://' + url;
|
|
1041
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(url) || /^(mailto|tel|sms):/i.test(url) || /^[/#?.]/.test(url)) return url;
|
|
1042
|
+
if (/^[\w-]+(\.[\w-]+)+(:\d+)?(\/|$|\?|#)/.test(url)) return 'https://' + url;
|
|
1024
1043
|
return null;
|
|
1025
1044
|
}
|
|
1026
1045
|
function setLink(href) {
|
|
@@ -1329,7 +1348,10 @@
|
|
|
1329
1348
|
while ((m = re.exec(text))) {
|
|
1330
1349
|
if (m.index + m[0].length !== text.length) continue;
|
|
1331
1350
|
const skip = rule.pre ? m[rule.pre].length : 0;
|
|
1332
|
-
|
|
1351
|
+
const start = m.index + skip;
|
|
1352
|
+
// Earliest start wins, like firstInline —  must be
|
|
1353
|
+
// the image rule, not the link rule matching one char later.
|
|
1354
|
+
if (!best || start < best.start) best = { rule, m, start, len: m[0].length - skip };
|
|
1333
1355
|
break;
|
|
1334
1356
|
}
|
|
1335
1357
|
}
|
|
@@ -1638,6 +1660,7 @@
|
|
|
1638
1660
|
const was = area || lastFocused;
|
|
1639
1661
|
if (!area || area === lastFocused) lastFocused = null;
|
|
1640
1662
|
if (!was) return;
|
|
1663
|
+
was.removeAttribute('data-text-edit-focused');
|
|
1641
1664
|
was._selection = null;
|
|
1642
1665
|
was.removeAttribute('data-text-edit-selected');
|
|
1643
1666
|
document.documentElement.removeAttribute('data-text-edit-selected');
|
|
@@ -1694,6 +1717,13 @@
|
|
|
1694
1717
|
const spec = COMMANDS[id], cfg = area && area._te;
|
|
1695
1718
|
if (!spec || !cfg || cfg.mode === 'plain') return false;
|
|
1696
1719
|
const page = !!(ctrl && ctrl.page && spec.page);
|
|
1720
|
+
// The writer's caret is visibly in ANOTHER editable element (a plain
|
|
1721
|
+
// x-edit leaf, an input, someone else's contenteditable): acting now
|
|
1722
|
+
// would restore this area's old saved range and edit text the writer
|
|
1723
|
+
// can see is not selected. The control goes quiet instead. Page
|
|
1724
|
+
// commands write variables, never the selection — they stay live.
|
|
1725
|
+
const live = range();
|
|
1726
|
+
if (!page && live && !area.contains(live.startContainer) && editableElsewhere(live.startContainer)) return false;
|
|
1697
1727
|
if (cfg.minimal && spec.block && !page) return false;
|
|
1698
1728
|
if (cfg.mode !== 'html' && !spec.md && !page) return false;
|
|
1699
1729
|
if (page) return true; // writes to the area, needs no caret
|
|
@@ -1707,6 +1737,13 @@
|
|
|
1707
1737
|
const spec = COMMANDS[id]; if (!spec) return;
|
|
1708
1738
|
const page = !!(ctrl && ctrl.page && spec.page);
|
|
1709
1739
|
if (!page) {
|
|
1740
|
+
// Checked BEFORE restoring the saved range: if the writer's caret is
|
|
1741
|
+
// visibly in another editable element, restoring first would move the
|
|
1742
|
+
// selection back here and the command would edit text the writer can
|
|
1743
|
+
// see is not selected. (The reset blocks pointer clicks on disabled
|
|
1744
|
+
// controls; this covers keyboard activation.)
|
|
1745
|
+
const live = range();
|
|
1746
|
+
if (live && !area.contains(live.startContainer) && editableElsewhere(live.startContainer)) return;
|
|
1710
1747
|
restoreRange(area);
|
|
1711
1748
|
// If the caret could not be put in this area — it was never focused, or
|
|
1712
1749
|
// the saved range has gone stale — do nothing. Running anyway rewrites
|
|
@@ -1912,6 +1949,16 @@
|
|
|
1912
1949
|
// browser picker, Manifest's, or anything that fires `input`.
|
|
1913
1950
|
const prop = id === 'background' ? 'backgroundColor' : 'color';
|
|
1914
1951
|
let live = null;
|
|
1952
|
+
// A new selection elsewhere ends the pass: without this, a
|
|
1953
|
+
// picker that never fires `change` keeps recolouring the OLD
|
|
1954
|
+
// spans after the writer has selected different text.
|
|
1955
|
+
const dropLive = () => {
|
|
1956
|
+
if (!live) return;
|
|
1957
|
+
const r = range();
|
|
1958
|
+
if (!r || !live.some(sp => sp.isConnected && sp.contains(r.startContainer))) live = null;
|
|
1959
|
+
};
|
|
1960
|
+
document.addEventListener('text-edit:selection', dropLive);
|
|
1961
|
+
cleanup(() => document.removeEventListener('text-edit:selection', dropLive));
|
|
1915
1962
|
el.addEventListener('input', () => {
|
|
1916
1963
|
const a = resolve(el); if (!a) return;
|
|
1917
1964
|
if (el._te.page) { run(a, id, el.value, el._te); return; } // page vars are cheap to set per tick
|
|
@@ -2051,7 +2098,7 @@
|
|
|
2051
2098
|
if (a && el.contains(a)) e.preventDefault();
|
|
2052
2099
|
});
|
|
2053
2100
|
|
|
2054
|
-
el.addEventListener('focusin', () => { lastFocused = el; sync(); });
|
|
2101
|
+
el.addEventListener('focusin', () => { if (lastFocused && lastFocused !== el) release(lastFocused); lastFocused = el; el.setAttribute('data-text-edit-focused', ''); sync(); });
|
|
2055
2102
|
// Focus gone somewhere that is neither this area nor one of its controls.
|
|
2056
2103
|
el.addEventListener('focusout', (e) => {
|
|
2057
2104
|
if (keeps(el, e.relatedTarget || pointerTarget)) return;
|
|
@@ -2060,7 +2107,7 @@
|
|
|
2060
2107
|
});
|
|
2061
2108
|
el.addEventListener('input', (e) => {
|
|
2062
2109
|
if (!writing && e.data && applyPending(el, e.data)) { commitValue(); }
|
|
2063
|
-
if (!writing && e.data && autoformat && convertInlineAtCaret(el)) { commitValue(); }
|
|
2110
|
+
if (!writing && e.data && !e.isComposing && autoformat && convertInlineAtCaret(el)) { commitValue(); }
|
|
2064
2111
|
settle(); sync();
|
|
2065
2112
|
});
|
|
2066
2113
|
el.addEventListener('change', e => {
|