@cancia/toolbar 0.5.2 → 0.12.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/cancia.d.ts +10 -0
- package/dist/cancia.iife.js +696 -512
- package/dist/cancia.js +1642 -693
- package/dist/cancia.js.map +1 -1
- package/package.json +7 -4
package/dist/cancia.js
CHANGED
|
@@ -1,3 +1,149 @@
|
|
|
1
|
+
// src/richtext.ts
|
|
2
|
+
import { PT_STYLES, portableTextSubsetSchema } from "@cancia/astro/richtext";
|
|
3
|
+
function parseRichValue(raw) {
|
|
4
|
+
if (raw === void 0) return null;
|
|
5
|
+
const trimmed = raw.trim();
|
|
6
|
+
if (trimmed === "") return [];
|
|
7
|
+
if (!trimmed.startsWith("[")) {
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
_type: "block",
|
|
11
|
+
_key: "legacy",
|
|
12
|
+
style: "normal",
|
|
13
|
+
markDefs: [],
|
|
14
|
+
children: [{ _type: "span", _key: "legacy0", text: raw, marks: [] }]
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const parsed = portableTextSubsetSchema.safeParse(JSON.parse(trimmed));
|
|
20
|
+
return parsed.success ? parsed.data : [];
|
|
21
|
+
} catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function serializeRichValue(blocks) {
|
|
26
|
+
return blocks.length === 0 ? "" : JSON.stringify(blocks);
|
|
27
|
+
}
|
|
28
|
+
var STYLE_TAG = {
|
|
29
|
+
normal: "p",
|
|
30
|
+
h2: "h2",
|
|
31
|
+
h3: "h3",
|
|
32
|
+
blockquote: "blockquote"
|
|
33
|
+
};
|
|
34
|
+
function renderRichToDom(target, blocks) {
|
|
35
|
+
target.replaceChildren();
|
|
36
|
+
let listWrap = null;
|
|
37
|
+
let listKind = null;
|
|
38
|
+
for (const block of blocks) {
|
|
39
|
+
if (block.listItem) {
|
|
40
|
+
const wantTag = block.listItem === "number" ? "ol" : "ul";
|
|
41
|
+
if (!listWrap || listKind !== block.listItem) {
|
|
42
|
+
listWrap = document.createElement(wantTag);
|
|
43
|
+
listKind = block.listItem;
|
|
44
|
+
target.appendChild(listWrap);
|
|
45
|
+
}
|
|
46
|
+
const li = document.createElement("li");
|
|
47
|
+
appendSpans(li, block);
|
|
48
|
+
listWrap.appendChild(li);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
listWrap = null;
|
|
52
|
+
listKind = null;
|
|
53
|
+
const el = document.createElement(STYLE_TAG[block.style] ?? "p");
|
|
54
|
+
appendSpans(el, block);
|
|
55
|
+
target.appendChild(el);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function appendSpans(parent, block) {
|
|
59
|
+
const markDefs = new Map(
|
|
60
|
+
(block.markDefs ?? []).map((d) => [d._key, d])
|
|
61
|
+
);
|
|
62
|
+
for (const span of block.children) {
|
|
63
|
+
let node = document.createTextNode(span.text);
|
|
64
|
+
for (const mark of span.marks ?? []) {
|
|
65
|
+
if (mark === "strong" || mark === "em") {
|
|
66
|
+
const wrap = document.createElement(mark === "strong" ? "strong" : "em");
|
|
67
|
+
wrap.appendChild(node);
|
|
68
|
+
node = wrap;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const def = markDefs.get(mark);
|
|
72
|
+
if (def) {
|
|
73
|
+
const a = document.createElement("a");
|
|
74
|
+
a.setAttribute("href", def.href);
|
|
75
|
+
a.appendChild(node);
|
|
76
|
+
node = a;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
parent.appendChild(node);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
var STYLE_OF_TAG = {
|
|
83
|
+
P: "normal",
|
|
84
|
+
H2: "h2",
|
|
85
|
+
H3: "h3",
|
|
86
|
+
BLOCKQUOTE: "blockquote"
|
|
87
|
+
};
|
|
88
|
+
function domToRows(region) {
|
|
89
|
+
const rows = [];
|
|
90
|
+
const pushBlock = (el, style, listItem) => {
|
|
91
|
+
const text = inlineToShorthand(el);
|
|
92
|
+
if (text.trim() === "") return;
|
|
93
|
+
rows.push(listItem ? { text, style, listItem } : { text, style });
|
|
94
|
+
};
|
|
95
|
+
for (const child of region.children) {
|
|
96
|
+
const tag = child.tagName;
|
|
97
|
+
if (tag === "UL" || tag === "OL") {
|
|
98
|
+
const kind = tag === "OL" ? "number" : "bullet";
|
|
99
|
+
for (const li of child.children) {
|
|
100
|
+
if (li.tagName === "LI") pushBlock(li, "normal", kind);
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (child.classList.contains("cancia-richtext")) {
|
|
105
|
+
rows.push(...domToRows(child));
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
pushBlock(child, STYLE_OF_TAG[tag] ?? "normal");
|
|
109
|
+
}
|
|
110
|
+
if (rows.length === 0) {
|
|
111
|
+
const text = inlineToShorthand(region);
|
|
112
|
+
if (text.trim() !== "") rows.push({ text, style: "normal" });
|
|
113
|
+
}
|
|
114
|
+
return rows;
|
|
115
|
+
}
|
|
116
|
+
function inlineToShorthand(el) {
|
|
117
|
+
let out = "";
|
|
118
|
+
for (const node of el.childNodes) {
|
|
119
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
120
|
+
out += (node.textContent ?? "").replace(/\s+/g, " ");
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
124
|
+
const child = node;
|
|
125
|
+
const inner = inlineToShorthand(child);
|
|
126
|
+
switch (child.tagName) {
|
|
127
|
+
case "STRONG":
|
|
128
|
+
case "B":
|
|
129
|
+
out += `**${inner}**`;
|
|
130
|
+
break;
|
|
131
|
+
case "EM":
|
|
132
|
+
case "I":
|
|
133
|
+
out += `*${inner}*`;
|
|
134
|
+
break;
|
|
135
|
+
case "A": {
|
|
136
|
+
const href = child.getAttribute("href") ?? "";
|
|
137
|
+
out += href ? `[${inner}](${href})` : inner;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
default:
|
|
141
|
+
out += inner;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
1
147
|
// src/state.ts
|
|
2
148
|
var state = {
|
|
3
149
|
config: null,
|
|
@@ -53,6 +199,11 @@ function applyOverlay() {
|
|
|
53
199
|
el.src = savedValue;
|
|
54
200
|
return;
|
|
55
201
|
}
|
|
202
|
+
if (el.dataset.cmsType === "richtext") {
|
|
203
|
+
const blocks = parseRichValue(savedValue);
|
|
204
|
+
if (blocks) renderRichToDom(el, blocks);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
56
207
|
if (el.dataset.cmsType === "link") {
|
|
57
208
|
const link = parseLinkOverlay(savedValue);
|
|
58
209
|
if (link.href && el.tagName === "A") el.setAttribute("href", link.href);
|
|
@@ -71,14 +222,19 @@ function applyOverlay() {
|
|
|
71
222
|
});
|
|
72
223
|
}
|
|
73
224
|
function revertPending() {
|
|
74
|
-
for (const [fullKey, { key
|
|
225
|
+
for (const [fullKey, { key }] of state.pending) {
|
|
75
226
|
const savedValue = state.cmsData[fullKey] ?? "";
|
|
76
227
|
document.querySelectorAll(`[data-cms="${key}"]`).forEach((el) => {
|
|
77
228
|
if (el.tagName === "IMG") {
|
|
78
229
|
el.src = savedValue;
|
|
79
|
-
|
|
80
|
-
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (el.dataset.cmsType === "richtext") {
|
|
233
|
+
const blocks = parseRichValue(savedValue);
|
|
234
|
+
if (blocks) renderRichToDom(el, blocks);
|
|
235
|
+
return;
|
|
81
236
|
}
|
|
237
|
+
el.textContent = savedValue;
|
|
82
238
|
});
|
|
83
239
|
}
|
|
84
240
|
state.pending.clear();
|
|
@@ -242,21 +398,431 @@ async function flushPending() {
|
|
|
242
398
|
if (failed > 0) throw new Error(`Cancia: ${failed} save(s) failed`);
|
|
243
399
|
}
|
|
244
400
|
|
|
401
|
+
// src/tokens.ts
|
|
402
|
+
var tokens = {
|
|
403
|
+
// ── Surfaces ──────────────────────────────────────────────────────────────
|
|
404
|
+
// All three chrome levels are plain white. On a light theme, depth comes from
|
|
405
|
+
// the BORDER and the shadow, not from a lightness ramp: three subtly
|
|
406
|
+
// different off-whites just look like a rendering bug. The scale is still
|
|
407
|
+
// three names so component code keeps its layering vocabulary.
|
|
408
|
+
"surface-1": "#ffffff",
|
|
409
|
+
// the floating bar itself
|
|
410
|
+
"surface-2": "#ffffff",
|
|
411
|
+
// popups, panels
|
|
412
|
+
"surface-3": "#ffffff",
|
|
413
|
+
// the drawer / form (the topmost layer)
|
|
414
|
+
"surface-raised": "#f4f4f5",
|
|
415
|
+
// an input or row ON a surface — recessed, not raised
|
|
416
|
+
"surface-hover": "#f4f4f5",
|
|
417
|
+
"surface-active": "#e4e4e7",
|
|
418
|
+
// ── Text ──────────────────────────────────────────────────────────────────
|
|
419
|
+
// Four steps only. More than four and nothing reads as deliberate.
|
|
420
|
+
// Opaque hex, not white-alpha: these sit on white, and an alpha black over a
|
|
421
|
+
// translucent surface picks up whatever the page beneath happens to be.
|
|
422
|
+
"fg-strong": "#18181b",
|
|
423
|
+
// headings, input text
|
|
424
|
+
"fg": "#3f3f46",
|
|
425
|
+
// body
|
|
426
|
+
"fg-muted": "#71717a",
|
|
427
|
+
// labels, help text
|
|
428
|
+
"fg-faint": "#a1a1aa",
|
|
429
|
+
// placeholders, disabled
|
|
430
|
+
// ── Borders ───────────────────────────────────────────────────────────────
|
|
431
|
+
// On a light surface a 1px border does more work than a large shadow — it is
|
|
432
|
+
// the primary way a panel separates itself from the page behind it.
|
|
433
|
+
"border": "#e4e4e7",
|
|
434
|
+
"border-strong": "#d4d4d8",
|
|
435
|
+
// ── Accent ────────────────────────────────────────────────────────────────
|
|
436
|
+
// Near-black, NOT a brand colour. See the note on tokenCss(): the site's
|
|
437
|
+
// configured accentColor is opt-in, because a client's brand colour is
|
|
438
|
+
// frequently pale or neon and produces an unreadable button on light chrome.
|
|
439
|
+
"accent": "#18181b",
|
|
440
|
+
"accent-fg": "#ffffff",
|
|
441
|
+
"accent-soft": "rgba(24, 24, 27, 0.06)",
|
|
442
|
+
// recomputed when an accent is supplied
|
|
443
|
+
"accent-ring": "rgba(24, 24, 27, 0.28)",
|
|
444
|
+
// recomputed when an accent is supplied
|
|
445
|
+
// ── Status ────────────────────────────────────────────────────────────────
|
|
446
|
+
// Retuned for light: the dark theme used pastel-bright status colours that
|
|
447
|
+
// glowed on near-black and wash out to illegible on white. These are the
|
|
448
|
+
// mid-weight variants that hold contrast against a white surface.
|
|
449
|
+
"danger": "#dc2626",
|
|
450
|
+
"danger-soft": "rgba(220, 38, 38, 0.08)",
|
|
451
|
+
"success": "#16a34a",
|
|
452
|
+
// Amber. Marks an incomplete-but-not-broken state — chiefly a list entry that
|
|
453
|
+
// exists in another locale but is NOT translated into the active one.
|
|
454
|
+
"warning": "#d97706",
|
|
455
|
+
"warning-soft": "rgba(217, 119, 6, 0.10)",
|
|
456
|
+
// ── Radii ─────────────────────────────────────────────────────────────────
|
|
457
|
+
"radius-sm": "6px",
|
|
458
|
+
"radius": "10px",
|
|
459
|
+
"radius-lg": "14px",
|
|
460
|
+
"radius-full": "999px",
|
|
461
|
+
// ── Spacing ───────────────────────────────────────────────────────────────
|
|
462
|
+
// A 4px scale. Every gap/padding in the UI is one of these.
|
|
463
|
+
"space-1": "4px",
|
|
464
|
+
"space-2": "8px",
|
|
465
|
+
"space-3": "12px",
|
|
466
|
+
"space-4": "16px",
|
|
467
|
+
"space-5": "20px",
|
|
468
|
+
"space-6": "24px",
|
|
469
|
+
// ── Typography ────────────────────────────────────────────────────────────
|
|
470
|
+
// The system stack, so the editor never waits on a webfont or inherits a
|
|
471
|
+
// display face from the page it is injected into.
|
|
472
|
+
"font": '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
473
|
+
"text-xs": "11px",
|
|
474
|
+
"text-sm": "12px",
|
|
475
|
+
"text-base": "13px",
|
|
476
|
+
"text-lg": "15px",
|
|
477
|
+
// ── Elevation ─────────────────────────────────────────────────────────────
|
|
478
|
+
// Retuned for light surfaces. The dark theme's shadows were 0.3–0.5 alpha
|
|
479
|
+
// black, which on white reads as a grey smear rather than elevation. Light UI
|
|
480
|
+
// wants soft, low-opacity shadows in two layers — a tight contact shadow plus
|
|
481
|
+
// a wide ambient one — with the 1px `border` doing most of the separating.
|
|
482
|
+
// The `inset` top highlight is gone: it simulated light catching the top edge
|
|
483
|
+
// of a dark glass panel and is invisible (or dirty) on white.
|
|
484
|
+
"shadow-sm": "0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.06)",
|
|
485
|
+
"shadow": "0 2px 4px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.08)",
|
|
486
|
+
"shadow-lg": "0 4px 8px rgba(0, 0, 0, 0.05), 0 16px 48px rgba(0, 0, 0, 0.12)",
|
|
487
|
+
// ── Motion ────────────────────────────────────────────────────────────────
|
|
488
|
+
// Two curves, and a rule for choosing between them.
|
|
489
|
+
//
|
|
490
|
+
// `ease` is critically damped — it settles without overshoot, which is what
|
|
491
|
+
// almost all UI wants. Overshoot on a menu that simply appeared reads as
|
|
492
|
+
// noise; overshoot is only earned when the user's own gesture carried
|
|
493
|
+
// momentum into it (a flick, a drag release).
|
|
494
|
+
//
|
|
495
|
+
// `ease-spring` is the momentum curve: a slight overshoot that makes a
|
|
496
|
+
// element feel thrown rather than placed. Reserve it for motion the user
|
|
497
|
+
// initiated with a gesture, and for the toolbar's own entrance (which should
|
|
498
|
+
// feel like it arrives, not like it blinks on).
|
|
499
|
+
// Three curves, chosen by what the element is DOING — not by taste.
|
|
500
|
+
//
|
|
501
|
+
// entering or exiting the screen -> ease-out
|
|
502
|
+
// already on screen, moving -> ease-in-out
|
|
503
|
+
// hover / colour change -> ease
|
|
504
|
+
//
|
|
505
|
+
// `ease-in` is deliberately absent. Its slow start delays visual feedback,
|
|
506
|
+
// which reads as a sluggish interface; this file previously carried a
|
|
507
|
+
// cubic-bezier(0.7, 0, 0.84, 0) exit that did exactly that.
|
|
508
|
+
//
|
|
509
|
+
// These are named after the standard easing set so the intent is legible at
|
|
510
|
+
// the call site: `ease-out-quart` is a strong ease-out, not a magic tuple.
|
|
511
|
+
"ease": "cubic-bezier(0.25, 0.1, 0.25, 1)",
|
|
512
|
+
// hover, colour — gentle, asymmetric
|
|
513
|
+
"ease-out": "cubic-bezier(0.165, 0.84, 0.44, 1)",
|
|
514
|
+
// quart: enter/exit, the default
|
|
515
|
+
"ease-out-soft": "cubic-bezier(0.25, 0.46, 0.45, 0.94)",
|
|
516
|
+
// quad: small/short moves
|
|
517
|
+
"ease-in-out": "cubic-bezier(0.645, 0.045, 0.355, 1)",
|
|
518
|
+
// cubic: on-screen movement
|
|
519
|
+
"ease-spring": "cubic-bezier(0.34, 1.35, 0.64, 1)",
|
|
520
|
+
// overshoot — momentum only
|
|
521
|
+
// Durations. UI animation stays under 300ms; past that an interface starts
|
|
522
|
+
// to feel like it is waiting on itself. Larger surfaces get the longer end,
|
|
523
|
+
// and an exit runs ~20% faster than the matching entrance because nobody
|
|
524
|
+
// wants to watch something leave.
|
|
525
|
+
"duration-fast": "0.12s",
|
|
526
|
+
// press feedback, hover — must feel instant
|
|
527
|
+
"duration": "0.2s",
|
|
528
|
+
// the default: popups, tooltips, panels
|
|
529
|
+
"duration-slow": "0.28s",
|
|
530
|
+
// the largest surfaces (drawer, list panel)
|
|
531
|
+
"duration-exit": "0.16s",
|
|
532
|
+
// exits: ~20% faster than the entrance
|
|
533
|
+
// ── Effects ───────────────────────────────────────────────────────────────
|
|
534
|
+
// Surfaces are opaque white now, so the blur is mostly inert — it is kept so
|
|
535
|
+
// a consumer who overrides a surface to a translucent value still gets the
|
|
536
|
+
// frosted treatment. The `saturate(180%)` boost is dropped: it existed to
|
|
537
|
+
// give dark glass richness, and over a light surface it pushes whatever page
|
|
538
|
+
// colour bleeds through toward the garish.
|
|
539
|
+
"blur": "blur(16px)",
|
|
540
|
+
// ── Layering ──────────────────────────────────────────────────────────────
|
|
541
|
+
// The toolbar must sit above the host page's own stacking contexts, so these
|
|
542
|
+
// live at the very top of the range.
|
|
543
|
+
"z-overlay": "2147483645",
|
|
544
|
+
"z-panel": "2147483646",
|
|
545
|
+
"z-bar": "2147483647"
|
|
546
|
+
};
|
|
547
|
+
function v(name) {
|
|
548
|
+
return `var(--cancia-${name})`;
|
|
549
|
+
}
|
|
550
|
+
function parseHex(hex) {
|
|
551
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
552
|
+
if (!m) return null;
|
|
553
|
+
let h = m[1];
|
|
554
|
+
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
555
|
+
return {
|
|
556
|
+
r: parseInt(h.slice(0, 2), 16),
|
|
557
|
+
g: parseInt(h.slice(2, 4), 16),
|
|
558
|
+
b: parseInt(h.slice(4, 6), 16)
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
function tokenCss(accent4, useAccent = false) {
|
|
562
|
+
const resolved = { ...tokens };
|
|
563
|
+
const rgb = accent4 ? parseHex(accent4) : null;
|
|
564
|
+
if (useAccent && accent4 && rgb) {
|
|
565
|
+
resolved["accent"] = accent4;
|
|
566
|
+
resolved["accent-soft"] = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.10)`;
|
|
567
|
+
resolved["accent-ring"] = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.35)`;
|
|
568
|
+
}
|
|
569
|
+
const decls = Object.entries(resolved).map(([k, val]) => ` --cancia-${k}: ${val};`).join("\n");
|
|
570
|
+
return `:root {
|
|
571
|
+
${decls}
|
|
572
|
+
}`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/styles.ts
|
|
576
|
+
var injected = false;
|
|
577
|
+
function injectBaseStyles(accent4, useAccent = false) {
|
|
578
|
+
if (injected) return;
|
|
579
|
+
injected = true;
|
|
580
|
+
const style = document.createElement("style");
|
|
581
|
+
style.dataset.cancia = "tokens";
|
|
582
|
+
style.textContent = `
|
|
583
|
+
${tokenCss(accent4, useAccent)}
|
|
584
|
+
|
|
585
|
+
@keyframes cancia-in {
|
|
586
|
+
from { opacity: 0; transform: translateY(4px) scale(0.98); }
|
|
587
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
588
|
+
}
|
|
589
|
+
@keyframes cancia-fade {
|
|
590
|
+
from { opacity: 0; }
|
|
591
|
+
to { opacity: 1; }
|
|
592
|
+
}
|
|
593
|
+
@keyframes cancia-spin {
|
|
594
|
+
to { transform: rotate(360deg); }
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/* Scoped reset. The toolbar is injected into somebody else's page, whose
|
|
598
|
+
global styles WILL otherwise reach our elements \u2014 a site-wide
|
|
599
|
+
\`button { text-transform: uppercase }\` would rewrite our labels. */
|
|
600
|
+
[data-cancia-ui], [data-cancia-ui] * {
|
|
601
|
+
box-sizing: border-box;
|
|
602
|
+
font-family: ${v("font")};
|
|
603
|
+
text-transform: none;
|
|
604
|
+
letter-spacing: normal;
|
|
605
|
+
line-height: 1.45;
|
|
606
|
+
margin: 0;
|
|
607
|
+
}
|
|
608
|
+
[data-cancia-ui] button {
|
|
609
|
+
font: inherit;
|
|
610
|
+
color: inherit;
|
|
611
|
+
background: none;
|
|
612
|
+
border: none;
|
|
613
|
+
cursor: pointer;
|
|
614
|
+
}
|
|
615
|
+
[data-cancia-ui] input,
|
|
616
|
+
[data-cancia-ui] textarea,
|
|
617
|
+
[data-cancia-ui] select {
|
|
618
|
+
font: inherit;
|
|
619
|
+
color: inherit;
|
|
620
|
+
}
|
|
621
|
+
[data-cancia-ui] ::placeholder { color: ${v("fg-faint")}; }
|
|
622
|
+
|
|
623
|
+
/* Focus is shown with our own ring so it matches the accent and is consistent
|
|
624
|
+
across browsers, rather than inheriting whatever the host page's UA default
|
|
625
|
+
or global \`:focus\` rule happens to be. */
|
|
626
|
+
[data-cancia-ui] :focus-visible {
|
|
627
|
+
outline: 2px solid ${v("accent-ring")};
|
|
628
|
+
outline-offset: 2px;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/* Reduced motion: neutralise every animation and transition on our subtree.
|
|
632
|
+
This is deliberately a blanket rule keyed on the [data-cancia-ui] marker \u2014
|
|
633
|
+
which is exactly why markUi() must be called on every top-level container we
|
|
634
|
+
create. Any new motion added anywhere in the toolbar is covered by this
|
|
635
|
+
automatically, as long as it lives inside a marked subtree and is expressed
|
|
636
|
+
as a CSS animation or transition (both of the mechanisms we use).
|
|
637
|
+
|
|
638
|
+
Note this zeroes DURATION, not the properties themselves: an element still
|
|
639
|
+
lands on its final state instantly, so nothing is left mid-transition. */
|
|
640
|
+
@media (prefers-reduced-motion: reduce) {
|
|
641
|
+
[data-cancia-ui], [data-cancia-ui] * {
|
|
642
|
+
animation-duration: 0.01ms !important;
|
|
643
|
+
transition-duration: 0.01ms !important;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
`;
|
|
647
|
+
document.head.appendChild(style);
|
|
648
|
+
}
|
|
649
|
+
function markUi(el) {
|
|
650
|
+
el.dataset.canciaUi = "";
|
|
651
|
+
return el;
|
|
652
|
+
}
|
|
653
|
+
var surface = (level = 2) => `
|
|
654
|
+
background: ${v(`surface-${level}`)};
|
|
655
|
+
backdrop-filter: ${v("blur")};
|
|
656
|
+
-webkit-backdrop-filter: ${v("blur")};
|
|
657
|
+
border: 1px solid ${v("border")};
|
|
658
|
+
border-radius: ${v("radius-lg")};
|
|
659
|
+
box-shadow: ${v("shadow")};
|
|
660
|
+
color: ${v("fg")};
|
|
661
|
+
`;
|
|
662
|
+
var button = (variant = "ghost") => {
|
|
663
|
+
const base = `
|
|
664
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
665
|
+
gap: ${v("space-2")};
|
|
666
|
+
height: 30px;
|
|
667
|
+
padding: 0 ${v("space-3")};
|
|
668
|
+
border-radius: ${v("radius-sm")};
|
|
669
|
+
font-size: ${v("text-sm")};
|
|
670
|
+
font-weight: 500;
|
|
671
|
+
white-space: nowrap;
|
|
672
|
+
transition: background ${v("duration-fast")} ${v("ease")},
|
|
673
|
+
color ${v("duration-fast")} ${v("ease")},
|
|
674
|
+
opacity ${v("duration-fast")} ${v("ease")};
|
|
675
|
+
`;
|
|
676
|
+
if (variant === "primary") {
|
|
677
|
+
return `${base}
|
|
678
|
+
background: ${v("accent")};
|
|
679
|
+
color: ${v("accent-fg")};
|
|
680
|
+
`;
|
|
681
|
+
}
|
|
682
|
+
if (variant === "danger") {
|
|
683
|
+
return `${base}
|
|
684
|
+
background: transparent;
|
|
685
|
+
color: ${v("danger")};
|
|
686
|
+
`;
|
|
687
|
+
}
|
|
688
|
+
return `${base}
|
|
689
|
+
background: transparent;
|
|
690
|
+
color: ${v("fg")};
|
|
691
|
+
`;
|
|
692
|
+
};
|
|
693
|
+
var iconButton = (size = 30) => `
|
|
694
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
695
|
+
width: ${size}px; height: ${size}px;
|
|
696
|
+
border-radius: ${v("radius-sm")};
|
|
697
|
+
color: ${v("fg-muted")};
|
|
698
|
+
transition: background ${v("duration-fast")} ${v("ease")},
|
|
699
|
+
color ${v("duration-fast")} ${v("ease")};
|
|
700
|
+
`;
|
|
701
|
+
var actionButton = () => `
|
|
702
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
703
|
+
gap: ${v("space-2")};
|
|
704
|
+
height: 38px;
|
|
705
|
+
padding: 0 ${v("space-4")};
|
|
706
|
+
/* radius-pill so the button's curve echoes the pill-shaped bar containing
|
|
707
|
+
it. A small square radius inside a fully-round container reads as two
|
|
708
|
+
unrelated shapes. */
|
|
709
|
+
border-radius: ${v("radius-full")};
|
|
710
|
+
font-size: ${v("text-base")};
|
|
711
|
+
font-weight: 500;
|
|
712
|
+
color: ${v("fg")};
|
|
713
|
+
background: transparent;
|
|
714
|
+
white-space: nowrap;
|
|
715
|
+
transition: background ${v("duration-fast")} ${v("ease")},
|
|
716
|
+
color ${v("duration-fast")} ${v("ease")},
|
|
717
|
+
transform ${v("duration-fast")} ${v("ease")};
|
|
718
|
+
`;
|
|
719
|
+
var input = () => `
|
|
720
|
+
width: 100%;
|
|
721
|
+
padding: ${v("space-2")} ${v("space-3")};
|
|
722
|
+
background: ${v("surface-raised")};
|
|
723
|
+
color: ${v("fg-strong")};
|
|
724
|
+
border: 1px solid ${v("border")};
|
|
725
|
+
border-radius: ${v("radius-sm")};
|
|
726
|
+
font-size: ${v("text-base")};
|
|
727
|
+
outline: none;
|
|
728
|
+
caret-color: ${v("accent")};
|
|
729
|
+
transition: border-color ${v("duration-fast")} ${v("ease")},
|
|
730
|
+
background ${v("duration-fast")} ${v("ease")};
|
|
731
|
+
`;
|
|
732
|
+
var label = () => `
|
|
733
|
+
display: block;
|
|
734
|
+
font-size: ${v("text-sm")};
|
|
735
|
+
font-weight: 500;
|
|
736
|
+
letter-spacing: normal;
|
|
737
|
+
text-transform: none;
|
|
738
|
+
color: ${v("fg-strong")};
|
|
739
|
+
`;
|
|
740
|
+
var hint = () => `
|
|
741
|
+
font-size: ${v("text-xs")};
|
|
742
|
+
color: ${v("fg-muted")};
|
|
743
|
+
line-height: 1.5;
|
|
744
|
+
`;
|
|
745
|
+
var group = () => `
|
|
746
|
+
display: flex;
|
|
747
|
+
flex-direction: column;
|
|
748
|
+
gap: ${v("space-2")};
|
|
749
|
+
padding-left: ${v("space-3")};
|
|
750
|
+
border-left: 1px solid ${v("border")};
|
|
751
|
+
margin-left: 1px;
|
|
752
|
+
`;
|
|
753
|
+
var groupRow = () => `
|
|
754
|
+
display: flex;
|
|
755
|
+
align-items: flex-start;
|
|
756
|
+
gap: ${v("space-2")};
|
|
757
|
+
`;
|
|
758
|
+
function attachHover(el, opts = {}) {
|
|
759
|
+
const bg = opts.bg ?? v("surface-hover");
|
|
760
|
+
const color = opts.color;
|
|
761
|
+
const priorBg = el.style.background;
|
|
762
|
+
const priorColor = el.style.color;
|
|
763
|
+
el.addEventListener("mouseenter", () => {
|
|
764
|
+
el.style.background = bg;
|
|
765
|
+
if (color) el.style.color = color;
|
|
766
|
+
});
|
|
767
|
+
el.addEventListener("mouseleave", () => {
|
|
768
|
+
el.style.background = priorBg;
|
|
769
|
+
if (color) el.style.color = priorColor;
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
function attachPress(el, scale = 0.96) {
|
|
773
|
+
const down = (e) => {
|
|
774
|
+
if (el.disabled) return;
|
|
775
|
+
el.setPointerCapture?.(e.pointerId);
|
|
776
|
+
el.style.transform = `scale(${scale})`;
|
|
777
|
+
};
|
|
778
|
+
const up = () => {
|
|
779
|
+
el.style.transform = "";
|
|
780
|
+
};
|
|
781
|
+
el.addEventListener("pointerdown", down);
|
|
782
|
+
el.addEventListener("pointerup", up);
|
|
783
|
+
el.addEventListener("pointercancel", up);
|
|
784
|
+
}
|
|
785
|
+
function attachInputFocus(el) {
|
|
786
|
+
el.addEventListener("focus", () => {
|
|
787
|
+
el.style.borderColor = v("accent-ring");
|
|
788
|
+
el.style.background = v("surface-hover");
|
|
789
|
+
});
|
|
790
|
+
el.addEventListener("blur", () => {
|
|
791
|
+
el.style.borderColor = v("border");
|
|
792
|
+
el.style.background = v("surface-raised");
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
245
796
|
// src/highlight.ts
|
|
246
797
|
var CMS_SELECTOR = "[data-cms], [data-cms-list]";
|
|
247
798
|
var onSelect = null;
|
|
248
799
|
var currentHighlighted = null;
|
|
249
800
|
var cleanupFns = [];
|
|
250
801
|
var scrollRAF = null;
|
|
802
|
+
var scrollEndTimer = null;
|
|
251
803
|
var overlayEl = null;
|
|
252
804
|
var tooltipEl = null;
|
|
253
805
|
var styleInjected = false;
|
|
254
806
|
function accent() {
|
|
255
|
-
|
|
807
|
+
const useAccent = state.config?.toolbarAccent === true;
|
|
808
|
+
return (useAccent ? state.config?.accentColor : void 0) ?? "#18181b";
|
|
809
|
+
}
|
|
810
|
+
function withAlpha(color, alpha) {
|
|
811
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim());
|
|
812
|
+
if (m) {
|
|
813
|
+
let h = m[1];
|
|
814
|
+
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
815
|
+
const r = parseInt(h.slice(0, 2), 16);
|
|
816
|
+
const g = parseInt(h.slice(2, 4), 16);
|
|
817
|
+
const b = parseInt(h.slice(4, 6), 16);
|
|
818
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
819
|
+
}
|
|
820
|
+
return `rgba(24, 24, 27, ${alpha})`;
|
|
256
821
|
}
|
|
257
822
|
function fieldType(el) {
|
|
258
823
|
if (el.dataset.cmsType === "image") return "image";
|
|
259
824
|
if (el.dataset.cmsType === "link") return "link";
|
|
825
|
+
if (el.dataset.cmsType === "richtext") return "richtext";
|
|
260
826
|
if (el.tagName === "IMG") return "image";
|
|
261
827
|
return "text";
|
|
262
828
|
}
|
|
@@ -268,9 +834,12 @@ function injectStyles() {
|
|
|
268
834
|
styleInjected = true;
|
|
269
835
|
const s = document.createElement("style");
|
|
270
836
|
s.textContent = `
|
|
837
|
+
/* A plain fade. The marker used to scale from 0.98, which was right for a
|
|
838
|
+
box growing into place but wrong for an underline \u2014 a scaling underline
|
|
839
|
+
reads as sliding sideways from its centre. Opacity only. */
|
|
271
840
|
@keyframes cancia-highlight-in {
|
|
272
|
-
from { opacity: 0;
|
|
273
|
-
to { opacity: 1;
|
|
841
|
+
from { opacity: 0; }
|
|
842
|
+
to { opacity: 1; }
|
|
274
843
|
}
|
|
275
844
|
@keyframes cancia-tooltip-in {
|
|
276
845
|
from { opacity: 0; transform: scale(0.95) translateY(3px); }
|
|
@@ -283,15 +852,19 @@ function getOrCreateOverlay() {
|
|
|
283
852
|
if (!overlayEl) {
|
|
284
853
|
overlayEl = document.createElement("div");
|
|
285
854
|
overlayEl.dataset.canciaOverlay = "1";
|
|
855
|
+
markUi(overlayEl);
|
|
286
856
|
overlayEl.style.cssText = `
|
|
287
857
|
position: fixed;
|
|
858
|
+
top: 0; left: 0;
|
|
288
859
|
pointer-events: none !important;
|
|
289
860
|
box-sizing: border-box;
|
|
290
|
-
|
|
861
|
+
background: transparent;
|
|
862
|
+
border: 0;
|
|
291
863
|
z-index: 2147483644;
|
|
292
|
-
will-change:
|
|
293
|
-
|
|
294
|
-
|
|
864
|
+
will-change: transform, opacity;
|
|
865
|
+
contain: layout style;
|
|
866
|
+
transition: transform ${v("duration-fast")} ${v("ease-out-soft")},
|
|
867
|
+
opacity ${v("duration-fast")} ${v("ease")};
|
|
295
868
|
`;
|
|
296
869
|
document.body.appendChild(overlayEl);
|
|
297
870
|
}
|
|
@@ -301,37 +874,41 @@ function getOrCreateTooltip() {
|
|
|
301
874
|
if (!tooltipEl) {
|
|
302
875
|
tooltipEl = document.createElement("div");
|
|
303
876
|
tooltipEl.dataset.canciaTooltip = "1";
|
|
877
|
+
markUi(tooltipEl);
|
|
304
878
|
tooltipEl.style.cssText = `
|
|
305
879
|
position: fixed;
|
|
306
880
|
pointer-events: none !important;
|
|
307
|
-
z-index:
|
|
308
|
-
font-family:
|
|
309
|
-
font-size:
|
|
881
|
+
z-index: ${v("z-overlay")};
|
|
882
|
+
font-family: ${v("font")};
|
|
883
|
+
font-size: ${v("text-xs")};
|
|
310
884
|
font-weight: 500;
|
|
311
885
|
letter-spacing: 0.02em;
|
|
312
|
-
color:
|
|
313
|
-
background:
|
|
314
|
-
backdrop-filter: blur
|
|
315
|
-
-webkit-backdrop-filter: blur
|
|
316
|
-
border: 1px solid
|
|
317
|
-
padding:
|
|
318
|
-
border-radius:
|
|
886
|
+
color: ${v("fg-strong")};
|
|
887
|
+
background: ${v("surface-1")};
|
|
888
|
+
backdrop-filter: ${v("blur")};
|
|
889
|
+
-webkit-backdrop-filter: ${v("blur")};
|
|
890
|
+
border: 1px solid ${v("border-strong")};
|
|
891
|
+
padding: ${v("space-1")} ${v("space-2")};
|
|
892
|
+
border-radius: ${v("radius-sm")};
|
|
319
893
|
white-space: nowrap;
|
|
320
894
|
max-width: 260px;
|
|
321
895
|
overflow: hidden;
|
|
322
896
|
text-overflow: ellipsis;
|
|
323
|
-
box-shadow:
|
|
897
|
+
box-shadow: ${v("shadow-sm")};
|
|
324
898
|
`;
|
|
325
899
|
document.body.appendChild(tooltipEl);
|
|
326
900
|
}
|
|
327
901
|
return tooltipEl;
|
|
328
902
|
}
|
|
329
903
|
var lastOverlayEl = null;
|
|
904
|
+
var LIST_FALLBACK = "#059669";
|
|
330
905
|
function listAccent(hex) {
|
|
331
|
-
if (!/^#[0-9a-f]{6}$/i.test(hex)) return
|
|
906
|
+
if (!/^#[0-9a-f]{6}$/i.test(hex)) return LIST_FALLBACK;
|
|
332
907
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
333
908
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
334
909
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
910
|
+
const spread = Math.max(r, g, b) - Math.min(r, g, b);
|
|
911
|
+
if (spread < 24) return LIST_FALLBACK;
|
|
335
912
|
const shifted = [g, b, r].map((c) => c.toString(16).padStart(2, "0")).join("");
|
|
336
913
|
return `#${shifted}`;
|
|
337
914
|
}
|
|
@@ -352,52 +929,54 @@ var LIST_ICON = `<svg width="10" height="10" viewBox="0 0 14 14" fill="none" str
|
|
|
352
929
|
<rect x="1" y="7.5" width="3" height="3" rx="0.5"/>
|
|
353
930
|
<path d="M6 3.5h7M6 9h7"/>
|
|
354
931
|
</svg>`;
|
|
932
|
+
var KIND_LABEL = {
|
|
933
|
+
text: "Text",
|
|
934
|
+
image: "Image",
|
|
935
|
+
link: "Link",
|
|
936
|
+
richtext: "Rich text"
|
|
937
|
+
};
|
|
355
938
|
function positionOverlay(el, animate = false) {
|
|
356
939
|
const rect = el.getBoundingClientRect();
|
|
357
940
|
const mode = elementMode(el);
|
|
358
|
-
const
|
|
941
|
+
const isList = mode === "list";
|
|
942
|
+
const a = isList ? listAccent(accent()) : accent();
|
|
359
943
|
const overlay = getOrCreateOverlay();
|
|
360
944
|
const tooltip = getOrCreateTooltip();
|
|
361
945
|
const padding = 3;
|
|
362
|
-
overlay.style.
|
|
363
|
-
overlay.style.left = `${rect.left - padding}px`;
|
|
946
|
+
overlay.style.transform = `translate(${rect.left - padding}px, ${rect.top - padding}px)`;
|
|
364
947
|
overlay.style.width = `${rect.width + padding * 2}px`;
|
|
365
948
|
overlay.style.height = `${rect.height + padding * 2}px`;
|
|
366
949
|
if (lastOverlayEl !== el) {
|
|
367
950
|
lastOverlayEl = el;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
951
|
+
if (isList) {
|
|
952
|
+
overlay.style.border = `1px dashed ${a}`;
|
|
953
|
+
overlay.style.borderRadius = "6px";
|
|
954
|
+
} else {
|
|
955
|
+
overlay.style.border = "0";
|
|
956
|
+
overlay.style.borderRadius = "0";
|
|
957
|
+
}
|
|
958
|
+
let tagIcon;
|
|
959
|
+
let tagLabel;
|
|
372
960
|
let tooltipText;
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
961
|
+
if (isList) {
|
|
962
|
+
tagIcon = LIST_ICON;
|
|
963
|
+
tagLabel = "List";
|
|
376
964
|
tooltipText = `list: ${el.dataset.cmsList}`;
|
|
377
965
|
} else {
|
|
378
966
|
const type = fieldType(el);
|
|
379
|
-
|
|
380
|
-
|
|
967
|
+
tagIcon = type === "image" ? IMAGE_ICON : type === "link" ? LINK_ICON : FIELD_ICON;
|
|
968
|
+
tagLabel = KIND_LABEL[type];
|
|
381
969
|
tooltipText = el.dataset.cms ?? "";
|
|
382
970
|
}
|
|
383
|
-
overlay.
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
padding: 2px 6px; border-radius: 3px 0 4px 0;
|
|
390
|
-
display: flex; align-items: center; gap: 4px;
|
|
391
|
-
line-height: 1;
|
|
392
|
-
">
|
|
393
|
-
${badgeIcon}
|
|
394
|
-
${badgeLabel}
|
|
395
|
-
</div>
|
|
396
|
-
`;
|
|
397
|
-
tooltip.textContent = tooltipText;
|
|
971
|
+
overlay.style.border = `2px solid ${withAlpha(a, isList ? 0.55 : 0.45)}`;
|
|
972
|
+
overlay.style.background = withAlpha(a, 0.05);
|
|
973
|
+
overlay.style.borderRadius = "4px";
|
|
974
|
+
overlay.style.borderStyle = isList ? "dashed" : "solid";
|
|
975
|
+
overlay.innerHTML = "";
|
|
976
|
+
tooltip.textContent = tagLabel ? `${tagLabel} ${tooltipText}` : tooltipText;
|
|
398
977
|
}
|
|
399
978
|
overlay.style.display = "block";
|
|
400
|
-
if (animate) overlay.style.animation =
|
|
979
|
+
if (animate) overlay.style.animation = `cancia-highlight-in ${v("duration-fast")} ${v("ease-out")} forwards`;
|
|
401
980
|
tooltip.style.display = "block";
|
|
402
981
|
if (animate) tooltip.style.animation = "cancia-tooltip-in 0.1s ease-out forwards";
|
|
403
982
|
const tooltipMargin = 8;
|
|
@@ -422,13 +1001,19 @@ function hideOverlay() {
|
|
|
422
1001
|
}
|
|
423
1002
|
}
|
|
424
1003
|
function handleScroll() {
|
|
425
|
-
if (
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
1004
|
+
if (overlayEl && overlayEl.style.display !== "none") {
|
|
1005
|
+
overlayEl.style.opacity = "0";
|
|
1006
|
+
if (tooltipEl) tooltipEl.style.opacity = "0";
|
|
1007
|
+
}
|
|
1008
|
+
if (scrollEndTimer) clearTimeout(scrollEndTimer);
|
|
1009
|
+
scrollEndTimer = setTimeout(() => {
|
|
1010
|
+
scrollEndTimer = null;
|
|
1011
|
+
if (currentHighlighted && document.contains(currentHighlighted)) {
|
|
429
1012
|
positionOverlay(currentHighlighted);
|
|
1013
|
+
if (overlayEl) overlayEl.style.opacity = "1";
|
|
1014
|
+
if (tooltipEl) tooltipEl.style.opacity = "1";
|
|
430
1015
|
}
|
|
431
|
-
});
|
|
1016
|
+
}, 140);
|
|
432
1017
|
}
|
|
433
1018
|
function handleMouseOver(e) {
|
|
434
1019
|
const target = e.target.closest(CMS_SELECTOR);
|
|
@@ -524,13 +1109,18 @@ function onPendingChange(cb) {
|
|
|
524
1109
|
}
|
|
525
1110
|
|
|
526
1111
|
// src/popup.ts
|
|
1112
|
+
import {
|
|
1113
|
+
portableTextToRows,
|
|
1114
|
+
rowsToPortableText
|
|
1115
|
+
} from "@cancia/astro/richtext";
|
|
527
1116
|
var popupEl = null;
|
|
528
1117
|
var outsideListener = null;
|
|
529
1118
|
var keyListener = null;
|
|
530
1119
|
var dragover = false;
|
|
531
1120
|
var inputHandlers = /* @__PURE__ */ new WeakMap();
|
|
532
1121
|
function accent2() {
|
|
533
|
-
|
|
1122
|
+
const useAccent = state.config?.toolbarAccent === true;
|
|
1123
|
+
return (useAccent ? state.config?.accentColor : void 0) ?? "#18181b";
|
|
534
1124
|
}
|
|
535
1125
|
function getPopupPosition(anchor) {
|
|
536
1126
|
const rect = anchor.getBoundingClientRect();
|
|
@@ -565,15 +1155,15 @@ function buildHeader(key, onClose) {
|
|
|
565
1155
|
const keyParts = key.split(".");
|
|
566
1156
|
titleEl.textContent = keyParts[keyParts.length - 1].replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
567
1157
|
titleEl.style.cssText = `
|
|
568
|
-
font-size:
|
|
569
|
-
color:
|
|
1158
|
+
font-size: ${v("text-base")}; font-weight: 600;
|
|
1159
|
+
color: ${v("fg")};
|
|
570
1160
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
571
1161
|
`;
|
|
572
1162
|
const keyEl = document.createElement("span");
|
|
573
1163
|
keyEl.textContent = key;
|
|
574
1164
|
keyEl.style.cssText = `
|
|
575
1165
|
font-size: 10px; font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
|
|
576
|
-
color:
|
|
1166
|
+
color: ${v("fg-faint")}; letter-spacing: 0.03em;
|
|
577
1167
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
578
1168
|
`;
|
|
579
1169
|
titleWrap.appendChild(titleEl);
|
|
@@ -591,7 +1181,7 @@ function buildTextPopup(key, anchorEl, onClose) {
|
|
|
591
1181
|
wrap.appendChild(buildHeader(key, onClose));
|
|
592
1182
|
if (langs.length > 1) {
|
|
593
1183
|
const tabs = document.createElement("div");
|
|
594
|
-
tabs.style.cssText = `display: flex; gap: 0; margin-bottom:
|
|
1184
|
+
tabs.style.cssText = `display: flex; gap: 0; margin-bottom: ${v("space-3")}; border-bottom: 1px solid ${v("border")};`;
|
|
595
1185
|
const renderTabs2 = () => {
|
|
596
1186
|
tabs.innerHTML = "";
|
|
597
1187
|
langs.forEach((lang) => {
|
|
@@ -601,17 +1191,17 @@ function buildTextPopup(key, anchorEl, onClose) {
|
|
|
601
1191
|
tab.style.cssText = `
|
|
602
1192
|
padding: 5px 10px 6px; border: none; border-bottom: 2px solid;
|
|
603
1193
|
margin-bottom: -1px;
|
|
604
|
-
font-size:
|
|
1194
|
+
font-size: ${v("text-xs")}; font-weight: 600; cursor: pointer; letter-spacing: 0.06em;
|
|
605
1195
|
background: transparent;
|
|
606
|
-
border-bottom-color: ${isActive ?
|
|
607
|
-
color: ${isActive ? "
|
|
608
|
-
transition: color
|
|
1196
|
+
border-bottom-color: ${isActive ? v("accent") : "transparent"};
|
|
1197
|
+
color: ${isActive ? v("fg-strong") : v("fg-faint")};
|
|
1198
|
+
transition: color ${v("duration-fast")} ${v("ease")}, border-color ${v("duration-fast")} ${v("ease")};
|
|
609
1199
|
`;
|
|
610
1200
|
tab.addEventListener("mouseenter", () => {
|
|
611
|
-
if (!isActive) tab.style.color = "
|
|
1201
|
+
if (!isActive) tab.style.color = v("fg");
|
|
612
1202
|
});
|
|
613
1203
|
tab.addEventListener("mouseleave", () => {
|
|
614
|
-
if (!isActive) tab.style.color = "
|
|
1204
|
+
if (!isActive) tab.style.color = v("fg-faint");
|
|
615
1205
|
});
|
|
616
1206
|
tab.addEventListener("click", () => {
|
|
617
1207
|
const current = wrap.querySelector("textarea");
|
|
@@ -649,8 +1239,8 @@ function buildTextPopup(key, anchorEl, onClose) {
|
|
|
649
1239
|
const renderTextarea = (isInit = false) => {
|
|
650
1240
|
if (!isInit && textarea) {
|
|
651
1241
|
textarea.value = getValue(key, activeLang) || anchorEl.textContent?.trim() || "";
|
|
652
|
-
textarea.style.borderColor =
|
|
653
|
-
textarea.style.background = "
|
|
1242
|
+
textarea.style.borderColor = v("accent-ring");
|
|
1243
|
+
textarea.style.background = v("surface-hover");
|
|
654
1244
|
attachInputHandler();
|
|
655
1245
|
return;
|
|
656
1246
|
}
|
|
@@ -660,23 +1250,13 @@ function buildTextPopup(key, anchorEl, onClose) {
|
|
|
660
1250
|
textarea.rows = 4;
|
|
661
1251
|
textarea.placeholder = "Enter text\u2026";
|
|
662
1252
|
textarea.style.cssText = `
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
font-size: 13px; font-family: inherit; resize: none; outline: none;
|
|
668
|
-
transition: border-color 0.18s, background 0.18s;
|
|
1253
|
+
${input()}
|
|
1254
|
+
border-radius: ${v("radius")};
|
|
1255
|
+
padding: 10px ${v("space-3")};
|
|
1256
|
+
font-family: inherit; resize: none;
|
|
669
1257
|
line-height: 1.55;
|
|
670
|
-
caret-color: ${accent2()};
|
|
671
1258
|
`;
|
|
672
|
-
textarea
|
|
673
|
-
textarea.style.borderColor = `${accent2()}66`;
|
|
674
|
-
textarea.style.background = "rgba(255,255,255,0.05)";
|
|
675
|
-
});
|
|
676
|
-
textarea.addEventListener("blur", () => {
|
|
677
|
-
textarea.style.borderColor = "rgba(255,255,255,0.07)";
|
|
678
|
-
textarea.style.background = "rgba(255,255,255,0.03)";
|
|
679
|
-
});
|
|
1259
|
+
attachInputFocus(textarea);
|
|
680
1260
|
attachInputHandler();
|
|
681
1261
|
if (footerEl) {
|
|
682
1262
|
wrap.insertBefore(textarea, footerEl);
|
|
@@ -752,31 +1332,21 @@ function buildLinkPopup(key, anchorEl, onClose) {
|
|
|
752
1332
|
};
|
|
753
1333
|
};
|
|
754
1334
|
const inputStyle = `
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
font-size: 13px; font-family: inherit; outline: none;
|
|
760
|
-
transition: border-color 0.18s, background 0.18s;
|
|
761
|
-
caret-color: ${accent2()};
|
|
1335
|
+
${input()}
|
|
1336
|
+
border-radius: ${v("radius")};
|
|
1337
|
+
padding: 9px ${v("space-3")};
|
|
1338
|
+
font-family: inherit;
|
|
762
1339
|
`;
|
|
763
|
-
const makeLabelled = (text,
|
|
1340
|
+
const makeLabelled = (text, input2) => {
|
|
764
1341
|
const field = document.createElement("div");
|
|
765
1342
|
field.style.cssText = `display: flex; flex-direction: column; gap: 5px; margin-bottom: 10px;`;
|
|
766
1343
|
const lab = document.createElement("div");
|
|
767
1344
|
lab.textContent = text;
|
|
768
|
-
lab.style.cssText =
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
input.style.borderColor = `${accent2()}66`;
|
|
772
|
-
input.style.background = "rgba(255,255,255,0.05)";
|
|
773
|
-
});
|
|
774
|
-
input.addEventListener("blur", () => {
|
|
775
|
-
input.style.borderColor = "rgba(255,255,255,0.07)";
|
|
776
|
-
input.style.background = "rgba(255,255,255,0.03)";
|
|
777
|
-
});
|
|
1345
|
+
lab.style.cssText = label();
|
|
1346
|
+
input2.style.cssText = inputStyle;
|
|
1347
|
+
attachInputFocus(input2);
|
|
778
1348
|
field.appendChild(lab);
|
|
779
|
-
field.appendChild(
|
|
1349
|
+
field.appendChild(input2);
|
|
780
1350
|
return field;
|
|
781
1351
|
};
|
|
782
1352
|
const labelInput = document.createElement("input");
|
|
@@ -791,7 +1361,7 @@ function buildLinkPopup(key, anchorEl, onClose) {
|
|
|
791
1361
|
hrefInput.value = current.href;
|
|
792
1362
|
if (langs.length > 1) {
|
|
793
1363
|
const tabs = document.createElement("div");
|
|
794
|
-
tabs.style.cssText = `display: flex; gap: 0; margin-bottom:
|
|
1364
|
+
tabs.style.cssText = `display: flex; gap: 0; margin-bottom: ${v("space-3")}; border-bottom: 1px solid ${v("border")};`;
|
|
795
1365
|
const renderTabs2 = () => {
|
|
796
1366
|
tabs.innerHTML = "";
|
|
797
1367
|
langs.forEach((lang) => {
|
|
@@ -801,11 +1371,11 @@ function buildLinkPopup(key, anchorEl, onClose) {
|
|
|
801
1371
|
tab.style.cssText = `
|
|
802
1372
|
padding: 5px 10px 6px; border: none; border-bottom: 2px solid;
|
|
803
1373
|
margin-bottom: -1px;
|
|
804
|
-
font-size:
|
|
1374
|
+
font-size: ${v("text-xs")}; font-weight: 600; cursor: pointer; letter-spacing: 0.06em;
|
|
805
1375
|
background: transparent;
|
|
806
|
-
border-bottom-color: ${isActive ?
|
|
807
|
-
color: ${isActive ? "
|
|
808
|
-
transition: color
|
|
1376
|
+
border-bottom-color: ${isActive ? v("accent") : "transparent"};
|
|
1377
|
+
color: ${isActive ? v("fg-strong") : v("fg-faint")};
|
|
1378
|
+
transition: color ${v("duration-fast")} ${v("ease")}, border-color ${v("duration-fast")} ${v("ease")};
|
|
809
1379
|
`;
|
|
810
1380
|
tab.addEventListener("click", () => {
|
|
811
1381
|
stage(activeLang);
|
|
@@ -823,12 +1393,12 @@ function buildLinkPopup(key, anchorEl, onClose) {
|
|
|
823
1393
|
}
|
|
824
1394
|
wrap.appendChild(makeLabelled("Label", labelInput));
|
|
825
1395
|
wrap.appendChild(makeLabelled("URL", hrefInput));
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
wrap.appendChild(
|
|
1396
|
+
const hint2 = document.createElement("div");
|
|
1397
|
+
hint2.style.cssText = `${hint()} margin:-${v("space-1")} 0 10px;`;
|
|
1398
|
+
hint2.textContent = langs.length > 1 ? "Relative (/start), #anchor, mailto: and tel: all work. The URL is shared across languages." : "Relative (/start), #anchor, mailto: and tel: all work.";
|
|
1399
|
+
wrap.appendChild(hint2);
|
|
830
1400
|
const warn = document.createElement("div");
|
|
831
|
-
warn.style.cssText = `font-size
|
|
1401
|
+
warn.style.cssText = `font-size:${v("text-xs")};color:${v("danger")}; margin:-${v("space-1")} 0 10px; display:none;`;
|
|
832
1402
|
wrap.appendChild(warn);
|
|
833
1403
|
const paint = () => {
|
|
834
1404
|
labelNodes.forEach((n) => n.textContent = labelInput.value);
|
|
@@ -885,8 +1455,8 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
885
1455
|
if (currentSrc && !currentSrc.startsWith("data:")) {
|
|
886
1456
|
const previewWrap = document.createElement("div");
|
|
887
1457
|
previewWrap.style.cssText = `
|
|
888
|
-
border-radius:
|
|
889
|
-
border: 1px solid
|
|
1458
|
+
border-radius: ${v("radius")}; overflow: hidden; margin-bottom: 10px;
|
|
1459
|
+
border: 1px solid ${v("border")};
|
|
890
1460
|
position: relative; height: 100px;
|
|
891
1461
|
`;
|
|
892
1462
|
const previewImg = document.createElement("img");
|
|
@@ -896,8 +1466,8 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
896
1466
|
previewLabel.textContent = "Current";
|
|
897
1467
|
previewLabel.style.cssText = `
|
|
898
1468
|
position: absolute; bottom: 0; left: 0; right: 0;
|
|
899
|
-
font-size: 10px; color: rgba(255,255,255,0.
|
|
900
|
-
padding:
|
|
1469
|
+
font-size: 10px; color: rgba(255,255,255,0.85); letter-spacing: 0.04em;
|
|
1470
|
+
padding: ${v("space-4")} ${v("space-2")} 6px;
|
|
901
1471
|
background: linear-gradient(transparent, rgba(0,0,0,0.55));
|
|
902
1472
|
`;
|
|
903
1473
|
previewWrap.appendChild(previewImg);
|
|
@@ -907,23 +1477,23 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
907
1477
|
const dropZone = document.createElement("label");
|
|
908
1478
|
dropZone.style.cssText = `
|
|
909
1479
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
910
|
-
gap:
|
|
911
|
-
border: 1.5px dashed
|
|
912
|
-
padding:
|
|
1480
|
+
gap: ${v("space-2")};
|
|
1481
|
+
border: 1.5px dashed ${v("border-strong")}; border-radius: ${v("radius")};
|
|
1482
|
+
padding: ${v("space-6")} ${v("space-5")};
|
|
913
1483
|
cursor: pointer;
|
|
914
|
-
transition: border-color
|
|
915
|
-
background:
|
|
1484
|
+
transition: border-color ${v("duration-fast")} ${v("ease")}, background ${v("duration-fast")} ${v("ease")};
|
|
1485
|
+
background: ${v("surface-raised")};
|
|
916
1486
|
`;
|
|
917
1487
|
const uploadIcon = document.createElement("div");
|
|
918
|
-
uploadIcon.style.cssText = `color:
|
|
1488
|
+
uploadIcon.style.cssText = `color: ${v("fg-muted")}; transition: color ${v("duration-fast")} ${v("ease")};`;
|
|
919
1489
|
uploadIcon.innerHTML = `<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
920
1490
|
<path d="M12 15V3m0 0L8 7m4-4l4 4M2 17l.621 2.485A2 2 0 004.561 21h14.878a2 2 0 001.94-1.515L22 17"/>
|
|
921
1491
|
</svg>`;
|
|
922
1492
|
const dropText = document.createElement("div");
|
|
923
1493
|
dropText.style.cssText = `text-align: center;`;
|
|
924
1494
|
dropText.innerHTML = `
|
|
925
|
-
<div style="font-size
|
|
926
|
-
<div style="font-size
|
|
1495
|
+
<div style="font-size:${v("text-sm")};font-weight:500;color:${v("fg-muted")};">Drop an image</div>
|
|
1496
|
+
<div style="font-size:${v("text-xs")};color:${v("fg-faint")};margin-top:2px;">or click to browse</div>
|
|
927
1497
|
`;
|
|
928
1498
|
dropZone.appendChild(uploadIcon);
|
|
929
1499
|
dropZone.appendChild(dropText);
|
|
@@ -933,18 +1503,18 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
933
1503
|
fileInput.style.display = "none";
|
|
934
1504
|
dropZone.appendChild(fileInput);
|
|
935
1505
|
const statusWrap = document.createElement("div");
|
|
936
|
-
statusWrap.style.cssText = `margin-top:
|
|
1506
|
+
statusWrap.style.cssText = `margin-top: ${v("space-2")}; min-height: 18px;`;
|
|
937
1507
|
const statusMsg = document.createElement("p");
|
|
938
|
-
statusMsg.style.cssText = `font-size:
|
|
1508
|
+
statusMsg.style.cssText = `font-size: ${v("text-xs")}; color: ${v("fg-muted")}; margin: 0; text-align: center; transition: color ${v("duration")} ${v("ease")};`;
|
|
939
1509
|
const progressBar = document.createElement("div");
|
|
940
1510
|
progressBar.style.cssText = `
|
|
941
|
-
height: 2px; border-radius: 2px; background:
|
|
1511
|
+
height: 2px; border-radius: 2px; background: ${v("surface-raised")};
|
|
942
1512
|
overflow: hidden; margin-top: 6px; display: none;
|
|
943
1513
|
`;
|
|
944
1514
|
const progressFill = document.createElement("div");
|
|
945
1515
|
progressFill.style.cssText = `
|
|
946
|
-
height: 100%; border-radius: 2px; background: ${
|
|
947
|
-
width: 0%; transition: width
|
|
1516
|
+
height: 100%; border-radius: 2px; background: ${v("accent")};
|
|
1517
|
+
width: 0%; transition: width ${v("duration-slow")} ${v("ease-out")};
|
|
948
1518
|
`;
|
|
949
1519
|
progressBar.appendChild(progressFill);
|
|
950
1520
|
statusWrap.appendChild(statusMsg);
|
|
@@ -952,20 +1522,20 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
952
1522
|
const handleFile = async (file) => {
|
|
953
1523
|
if (!file.type.startsWith("image/")) {
|
|
954
1524
|
statusMsg.textContent = "Only image files are supported";
|
|
955
|
-
statusMsg.style.color = "
|
|
1525
|
+
statusMsg.style.color = v("danger");
|
|
956
1526
|
return;
|
|
957
1527
|
}
|
|
958
1528
|
const maxMb = 10;
|
|
959
1529
|
if (file.size > maxMb * 1024 * 1024) {
|
|
960
1530
|
statusMsg.textContent = `File too large (max ${maxMb}MB)`;
|
|
961
|
-
statusMsg.style.color = "
|
|
1531
|
+
statusMsg.style.color = v("danger");
|
|
962
1532
|
return;
|
|
963
1533
|
}
|
|
964
|
-
dropZone.style.borderColor =
|
|
965
|
-
dropZone.style.background =
|
|
966
|
-
uploadIcon.style.color =
|
|
1534
|
+
dropZone.style.borderColor = v("accent-ring");
|
|
1535
|
+
dropZone.style.background = v("accent-soft");
|
|
1536
|
+
uploadIcon.style.color = v("accent");
|
|
967
1537
|
statusMsg.textContent = "Uploading\u2026";
|
|
968
|
-
statusMsg.style.color = "
|
|
1538
|
+
statusMsg.style.color = v("fg-muted");
|
|
969
1539
|
progressBar.style.display = "block";
|
|
970
1540
|
progressFill.style.width = "0%";
|
|
971
1541
|
try {
|
|
@@ -989,16 +1559,16 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
989
1559
|
}
|
|
990
1560
|
setTimeout(() => {
|
|
991
1561
|
statusMsg.textContent = "Done";
|
|
992
|
-
statusMsg.style.color = "
|
|
1562
|
+
statusMsg.style.color = v("success");
|
|
993
1563
|
setTimeout(onClose, 600);
|
|
994
1564
|
}, 200);
|
|
995
1565
|
} catch {
|
|
996
1566
|
progressBar.style.display = "none";
|
|
997
1567
|
statusMsg.textContent = "Upload failed \u2014 try again";
|
|
998
|
-
statusMsg.style.color = "
|
|
999
|
-
dropZone.style.borderColor = "
|
|
1000
|
-
dropZone.style.background = "
|
|
1001
|
-
uploadIcon.style.color = "
|
|
1568
|
+
statusMsg.style.color = v("danger");
|
|
1569
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1570
|
+
dropZone.style.background = v("surface-raised");
|
|
1571
|
+
uploadIcon.style.color = v("fg-muted");
|
|
1002
1572
|
}
|
|
1003
1573
|
};
|
|
1004
1574
|
fileInput.addEventListener("change", () => {
|
|
@@ -1008,35 +1578,35 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
1008
1578
|
e.preventDefault();
|
|
1009
1579
|
if (!dragover) {
|
|
1010
1580
|
dragover = true;
|
|
1011
|
-
dropZone.style.borderColor =
|
|
1012
|
-
dropZone.style.background =
|
|
1013
|
-
uploadIcon.style.color =
|
|
1581
|
+
dropZone.style.borderColor = v("accent-ring");
|
|
1582
|
+
dropZone.style.background = v("accent-soft");
|
|
1583
|
+
uploadIcon.style.color = v("accent");
|
|
1014
1584
|
}
|
|
1015
1585
|
});
|
|
1016
1586
|
dropZone.addEventListener("dragleave", () => {
|
|
1017
1587
|
dragover = false;
|
|
1018
|
-
dropZone.style.borderColor = "
|
|
1019
|
-
dropZone.style.background = "
|
|
1020
|
-
uploadIcon.style.color = "
|
|
1588
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1589
|
+
dropZone.style.background = v("surface-raised");
|
|
1590
|
+
uploadIcon.style.color = v("fg-muted");
|
|
1021
1591
|
});
|
|
1022
1592
|
dropZone.addEventListener("drop", (e) => {
|
|
1023
1593
|
e.preventDefault();
|
|
1024
1594
|
dragover = false;
|
|
1025
|
-
dropZone.style.borderColor = "
|
|
1026
|
-
dropZone.style.background = "
|
|
1595
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1596
|
+
dropZone.style.background = v("surface-raised");
|
|
1027
1597
|
const file = e.dataTransfer?.files[0];
|
|
1028
1598
|
if (file) handleFile(file);
|
|
1029
1599
|
});
|
|
1030
1600
|
dropZone.addEventListener("mouseenter", () => {
|
|
1031
1601
|
if (!dragover) {
|
|
1032
|
-
dropZone.style.borderColor = "
|
|
1033
|
-
dropZone.style.background = "
|
|
1602
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1603
|
+
dropZone.style.background = v("surface-hover");
|
|
1034
1604
|
}
|
|
1035
1605
|
});
|
|
1036
1606
|
dropZone.addEventListener("mouseleave", () => {
|
|
1037
1607
|
if (!dragover) {
|
|
1038
|
-
dropZone.style.borderColor = "
|
|
1039
|
-
dropZone.style.background = "
|
|
1608
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1609
|
+
dropZone.style.background = v("surface-raised");
|
|
1040
1610
|
}
|
|
1041
1611
|
});
|
|
1042
1612
|
wrap.appendChild(dropZone);
|
|
@@ -1047,67 +1617,246 @@ function makeCloseButton(onClose) {
|
|
|
1047
1617
|
const btn = document.createElement("button");
|
|
1048
1618
|
btn.style.cssText = `
|
|
1049
1619
|
display: flex; align-items: center; justify-content: center;
|
|
1050
|
-
width: 24px; height: 24px; border-radius:
|
|
1051
|
-
background:
|
|
1052
|
-
cursor: pointer; color:
|
|
1053
|
-
transition: background
|
|
1620
|
+
width: 24px; height: 24px; border-radius: ${v("radius-sm")}; flex-shrink: 0;
|
|
1621
|
+
background: ${v("surface-raised")}; border: 1px solid ${v("border")};
|
|
1622
|
+
cursor: pointer; color: ${v("fg-muted")}; padding: 0;
|
|
1623
|
+
transition: background ${v("duration-fast")} ${v("ease")}, color ${v("duration-fast")} ${v("ease")};
|
|
1054
1624
|
`;
|
|
1055
1625
|
btn.innerHTML = `<svg width="9" height="9" viewBox="0 0 10 10" fill="none">
|
|
1056
1626
|
<path d="M1 1l8 8M9 1L1 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
1057
1627
|
</svg>`;
|
|
1058
|
-
btn
|
|
1059
|
-
btn.style.background = "rgba(255,255,255,0.08)";
|
|
1060
|
-
btn.style.color = "rgba(255,255,255,0.75)";
|
|
1061
|
-
});
|
|
1062
|
-
btn.addEventListener("mouseleave", () => {
|
|
1063
|
-
btn.style.background = "rgba(255,255,255,0.04)";
|
|
1064
|
-
btn.style.color = "rgba(255,255,255,0.35)";
|
|
1065
|
-
});
|
|
1628
|
+
attachHover(btn, { color: v("fg") });
|
|
1066
1629
|
btn.addEventListener("click", onClose);
|
|
1067
1630
|
return btn;
|
|
1068
1631
|
}
|
|
1069
|
-
function makePrimaryButton(
|
|
1632
|
+
function makePrimaryButton(label2, color) {
|
|
1070
1633
|
const btn = document.createElement("button");
|
|
1071
|
-
btn
|
|
1634
|
+
markUi(btn);
|
|
1635
|
+
btn.textContent = label2;
|
|
1072
1636
|
btn.style.cssText = `
|
|
1073
|
-
padding: 7px
|
|
1074
|
-
background:
|
|
1075
|
-
font-size:
|
|
1076
|
-
transition: opacity
|
|
1637
|
+
padding: 7px ${v("space-4")}; border-radius: ${v("radius-sm")}; border: none; cursor: pointer;
|
|
1638
|
+
background: ${v("accent")}; color: ${v("accent-fg")};
|
|
1639
|
+
font-size: ${v("text-sm")}; font-weight: 600; letter-spacing: 0.01em;
|
|
1640
|
+
transition: opacity ${v("duration-fast")} ${v("ease")}, transform ${v("duration-fast")} ${v("ease")};
|
|
1077
1641
|
`;
|
|
1078
1642
|
btn.addEventListener("mouseenter", () => btn.style.opacity = "0.88");
|
|
1079
1643
|
btn.addEventListener("mouseleave", () => btn.style.opacity = "1");
|
|
1644
|
+
attachPress(btn);
|
|
1080
1645
|
return btn;
|
|
1081
1646
|
}
|
|
1082
1647
|
function applyPopupStyles(el) {
|
|
1648
|
+
markUi(el);
|
|
1083
1649
|
el.style.cssText = `
|
|
1650
|
+
${surface(2)}
|
|
1084
1651
|
position: absolute;
|
|
1085
|
-
z-index:
|
|
1652
|
+
z-index: ${v("z-panel")};
|
|
1086
1653
|
width: 320px;
|
|
1087
|
-
|
|
1088
|
-
backdrop-filter: blur(24px) saturate(180%);
|
|
1089
|
-
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
|
1090
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
1091
|
-
border-radius: 14px;
|
|
1654
|
+
box-shadow: ${v("shadow-lg")};
|
|
1092
1655
|
padding: 14px;
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1656
|
+
font-family: ${v("font")};
|
|
1657
|
+
opacity: 0;
|
|
1658
|
+
transform: scale(0.93);
|
|
1659
|
+
transition: opacity ${v("duration")} ${v("ease")}, transform ${v("duration")} ${v("ease")};
|
|
1096
1660
|
`;
|
|
1097
1661
|
}
|
|
1662
|
+
function buildRichPopup(key, anchorEl, onClose) {
|
|
1663
|
+
const langs = state.config?.languages ?? ["en"];
|
|
1664
|
+
let activeLang = state.activeLang || langs[0];
|
|
1665
|
+
const wrap = document.createElement("div");
|
|
1666
|
+
wrap.dataset.canciaPopup = "1";
|
|
1667
|
+
applyPopupStyles(wrap);
|
|
1668
|
+
wrap.style.width = "460px";
|
|
1669
|
+
wrap.appendChild(buildHeader(key, onClose));
|
|
1670
|
+
const rowsWrap = document.createElement("div");
|
|
1671
|
+
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap: ${v("space-2")}; max-height: 46vh; overflow-y: auto;`;
|
|
1672
|
+
wrap.appendChild(rowsWrap);
|
|
1673
|
+
let rows = [];
|
|
1674
|
+
function initialRows() {
|
|
1675
|
+
const stored = getValue(key, activeLang);
|
|
1676
|
+
if (stored) {
|
|
1677
|
+
const blocks = parseRichValue(stored);
|
|
1678
|
+
if (blocks && blocks.length) return portableTextToRows(blocks);
|
|
1679
|
+
if (blocks && blocks.length === 0) return [{ text: "", style: "normal" }];
|
|
1680
|
+
}
|
|
1681
|
+
const authored = domToRows(anchorEl);
|
|
1682
|
+
return authored.length ? authored : [{ text: "", style: "normal" }];
|
|
1683
|
+
}
|
|
1684
|
+
function makeRow(initial) {
|
|
1685
|
+
const row = document.createElement("div");
|
|
1686
|
+
row.style.cssText = `display: flex; gap: 6px; align-items: flex-start;`;
|
|
1687
|
+
const main = document.createElement("div");
|
|
1688
|
+
main.style.cssText = `flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px;`;
|
|
1689
|
+
const ta = document.createElement("textarea");
|
|
1690
|
+
ta.value = initial.text;
|
|
1691
|
+
ta.rows = 2;
|
|
1692
|
+
ta.placeholder = "Text \u2014 **bold**, *italic*, [label](https://\u2026)";
|
|
1693
|
+
ta.style.cssText = `
|
|
1694
|
+
${input()}
|
|
1695
|
+
border-radius: ${v("radius")};
|
|
1696
|
+
padding: 8px 10px;
|
|
1697
|
+
font-family: inherit; resize: vertical;
|
|
1698
|
+
min-height: 46px; line-height: 1.55;
|
|
1699
|
+
`;
|
|
1700
|
+
attachInputFocus(ta);
|
|
1701
|
+
ta.addEventListener("input", commit);
|
|
1702
|
+
const controls = document.createElement("div");
|
|
1703
|
+
controls.style.cssText = `display: flex; gap: 6px;`;
|
|
1704
|
+
const styleSel = document.createElement("select");
|
|
1705
|
+
styleSel.style.cssText = `${input()} width: auto; flex: 1; padding: 4px 8px; font-size: ${v("text-xs")}; cursor: pointer;`;
|
|
1706
|
+
for (const [value, labelText] of [
|
|
1707
|
+
["normal", "Normal"],
|
|
1708
|
+
["h2", "Heading 2"],
|
|
1709
|
+
["h3", "Heading 3"],
|
|
1710
|
+
["blockquote", "Quote"]
|
|
1711
|
+
]) {
|
|
1712
|
+
const o = document.createElement("option");
|
|
1713
|
+
o.value = value;
|
|
1714
|
+
o.textContent = labelText;
|
|
1715
|
+
if (initial.style === value) o.selected = true;
|
|
1716
|
+
styleSel.appendChild(o);
|
|
1717
|
+
}
|
|
1718
|
+
styleSel.addEventListener("change", commit);
|
|
1719
|
+
const listSel = document.createElement("select");
|
|
1720
|
+
listSel.style.cssText = styleSel.style.cssText;
|
|
1721
|
+
for (const [value, labelText] of [
|
|
1722
|
+
["", "No list"],
|
|
1723
|
+
["bullet", "Bulleted"],
|
|
1724
|
+
["number", "Numbered"]
|
|
1725
|
+
]) {
|
|
1726
|
+
const o = document.createElement("option");
|
|
1727
|
+
o.value = value;
|
|
1728
|
+
o.textContent = labelText;
|
|
1729
|
+
if ((initial.listItem ?? "") === value) o.selected = true;
|
|
1730
|
+
listSel.appendChild(o);
|
|
1731
|
+
}
|
|
1732
|
+
listSel.addEventListener("change", commit);
|
|
1733
|
+
controls.appendChild(styleSel);
|
|
1734
|
+
controls.appendChild(listSel);
|
|
1735
|
+
main.appendChild(ta);
|
|
1736
|
+
main.appendChild(controls);
|
|
1737
|
+
const removeBtn = document.createElement("button");
|
|
1738
|
+
removeBtn.type = "button";
|
|
1739
|
+
removeBtn.textContent = "\xD7";
|
|
1740
|
+
removeBtn.title = "Remove this block";
|
|
1741
|
+
removeBtn.style.cssText = `
|
|
1742
|
+
${button("ghost")}
|
|
1743
|
+
flex-shrink: 0; height: auto; padding: 6px 9px;
|
|
1744
|
+
font-size: 15px; line-height: 1; color: ${v("fg-faint")};
|
|
1745
|
+
`;
|
|
1746
|
+
attachHover(removeBtn);
|
|
1747
|
+
removeBtn.addEventListener("click", () => {
|
|
1748
|
+
if (rows.length === 1) {
|
|
1749
|
+
ta.value = "";
|
|
1750
|
+
commit();
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
rows = rows.filter((r) => r.el !== row);
|
|
1754
|
+
row.remove();
|
|
1755
|
+
commit();
|
|
1756
|
+
});
|
|
1757
|
+
row.appendChild(main);
|
|
1758
|
+
row.appendChild(removeBtn);
|
|
1759
|
+
return {
|
|
1760
|
+
el: row,
|
|
1761
|
+
read: () => {
|
|
1762
|
+
const listItem = listSel.value;
|
|
1763
|
+
return {
|
|
1764
|
+
text: ta.value,
|
|
1765
|
+
style: styleSel.value,
|
|
1766
|
+
...listItem ? { listItem } : {}
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
function commit() {
|
|
1772
|
+
const docRows = rows.map((r) => r.read());
|
|
1773
|
+
const meaningful = docRows.filter((r) => r.text.trim() !== "");
|
|
1774
|
+
const blocks = meaningful.length ? rowsToPortableText(meaningful) : [];
|
|
1775
|
+
setPending(key, activeLang, serializeRichValue(blocks));
|
|
1776
|
+
onPendingChange();
|
|
1777
|
+
renderRichToDom(anchorEl, blocks);
|
|
1778
|
+
}
|
|
1779
|
+
function renderRows() {
|
|
1780
|
+
rowsWrap.replaceChildren();
|
|
1781
|
+
rows = initialRows().map(makeRow);
|
|
1782
|
+
for (const r of rows) rowsWrap.appendChild(r.el);
|
|
1783
|
+
}
|
|
1784
|
+
if (langs.length > 1) {
|
|
1785
|
+
const tabs = document.createElement("div");
|
|
1786
|
+
tabs.style.cssText = `display: flex; gap: 0; margin-bottom: ${v("space-3")}; border-bottom: 1px solid ${v("border")};`;
|
|
1787
|
+
const renderTabs2 = () => {
|
|
1788
|
+
tabs.replaceChildren();
|
|
1789
|
+
for (const lang of langs) {
|
|
1790
|
+
const isActive = lang === activeLang;
|
|
1791
|
+
const tab = document.createElement("button");
|
|
1792
|
+
tab.textContent = lang.toUpperCase();
|
|
1793
|
+
tab.style.cssText = `
|
|
1794
|
+
padding: 5px 10px 6px; border: none; border-bottom: 2px solid;
|
|
1795
|
+
margin-bottom: -1px;
|
|
1796
|
+
font-size: ${v("text-xs")}; font-weight: 600; cursor: pointer; letter-spacing: 0.06em;
|
|
1797
|
+
background: transparent;
|
|
1798
|
+
border-bottom-color: ${isActive ? v("accent") : "transparent"};
|
|
1799
|
+
color: ${isActive ? v("fg-strong") : v("fg-faint")};
|
|
1800
|
+
`;
|
|
1801
|
+
tab.addEventListener("click", () => {
|
|
1802
|
+
commit();
|
|
1803
|
+
activeLang = lang;
|
|
1804
|
+
state.activeLang = lang;
|
|
1805
|
+
applyOverlay();
|
|
1806
|
+
renderTabs2();
|
|
1807
|
+
renderRows();
|
|
1808
|
+
});
|
|
1809
|
+
tabs.appendChild(tab);
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
renderTabs2();
|
|
1813
|
+
wrap.insertBefore(tabs, rowsWrap);
|
|
1814
|
+
}
|
|
1815
|
+
renderRows();
|
|
1816
|
+
const footer = document.createElement("div");
|
|
1817
|
+
footer.dataset.canciaFooter = "1";
|
|
1818
|
+
footer.style.cssText = `display: flex; justify-content: space-between; align-items: center; gap: ${v("space-2")}; margin-top: 10px;`;
|
|
1819
|
+
const addBtn = document.createElement("button");
|
|
1820
|
+
addBtn.type = "button";
|
|
1821
|
+
addBtn.textContent = "+ Add block";
|
|
1822
|
+
addBtn.style.cssText = `${button("ghost")} font-size: ${v("text-xs")};`;
|
|
1823
|
+
attachHover(addBtn);
|
|
1824
|
+
attachPress(addBtn);
|
|
1825
|
+
addBtn.addEventListener("click", () => {
|
|
1826
|
+
const r = makeRow({ text: "", style: "normal" });
|
|
1827
|
+
rows.push(r);
|
|
1828
|
+
rowsWrap.appendChild(r.el);
|
|
1829
|
+
r.el.querySelector("textarea")?.focus();
|
|
1830
|
+
});
|
|
1831
|
+
const saveBtn = makePrimaryButton("Save", accent2());
|
|
1832
|
+
saveBtn.dataset.canciaSave = "1";
|
|
1833
|
+
saveBtn.title = "Save (\u2318S)";
|
|
1834
|
+
saveBtn.addEventListener("click", () => {
|
|
1835
|
+
commit();
|
|
1836
|
+
onClose();
|
|
1837
|
+
});
|
|
1838
|
+
footer.appendChild(addBtn);
|
|
1839
|
+
footer.appendChild(saveBtn);
|
|
1840
|
+
wrap.appendChild(footer);
|
|
1841
|
+
return wrap;
|
|
1842
|
+
}
|
|
1098
1843
|
function openPopup(key, fieldType2, anchorEl, onClose) {
|
|
1099
1844
|
closePopup();
|
|
1100
1845
|
const done = () => {
|
|
1101
1846
|
onClose();
|
|
1102
1847
|
closePopup();
|
|
1103
1848
|
};
|
|
1104
|
-
const popup = fieldType2 === "image" ? buildImagePopup(key, anchorEl, done) : fieldType2 === "link" ? buildLinkPopup(key, anchorEl, done) : buildTextPopup(key, anchorEl, done);
|
|
1849
|
+
const popup = fieldType2 === "image" ? buildImagePopup(key, anchorEl, done) : fieldType2 === "link" ? buildLinkPopup(key, anchorEl, done) : fieldType2 === "richtext" ? buildRichPopup(key, anchorEl, done) : buildTextPopup(key, anchorEl, done);
|
|
1105
1850
|
document.body.appendChild(popup);
|
|
1106
1851
|
popupEl = popup;
|
|
1107
1852
|
const { top, left, origin } = getPopupPosition(anchorEl);
|
|
1108
1853
|
popup.style.top = `${top}px`;
|
|
1109
1854
|
popup.style.left = `${left}px`;
|
|
1110
1855
|
popup.style.transformOrigin = origin;
|
|
1856
|
+
requestAnimationFrame(() => {
|
|
1857
|
+
popup.style.opacity = "1";
|
|
1858
|
+
popup.style.transform = "scale(1)";
|
|
1859
|
+
});
|
|
1111
1860
|
outsideListener = (e) => {
|
|
1112
1861
|
if (!popup.contains(e.target)) {
|
|
1113
1862
|
closePopup();
|
|
@@ -1142,17 +1891,17 @@ function closePopup() {
|
|
|
1142
1891
|
if (popupEl) {
|
|
1143
1892
|
const el = popupEl;
|
|
1144
1893
|
popupEl = null;
|
|
1145
|
-
el.style.
|
|
1146
|
-
el.style.transition = "opacity 0.18s cubic-bezier(0.4, 0, 1, 1), transform 0.18s cubic-bezier(0.4, 0, 1, 1)";
|
|
1894
|
+
el.style.transition = `opacity ${v("duration-exit")} ${v("ease-out")}, transform ${v("duration-exit")} ${v("ease-out")}`;
|
|
1147
1895
|
el.style.opacity = "0";
|
|
1148
|
-
el.style.transform = "scale(0.
|
|
1896
|
+
el.style.transform = "scale(0.93)";
|
|
1149
1897
|
setTimeout(() => el.remove(), 200);
|
|
1150
1898
|
}
|
|
1151
1899
|
}
|
|
1152
1900
|
|
|
1153
1901
|
// src/list-panel.ts
|
|
1154
|
-
var PANEL_Z =
|
|
1155
|
-
var
|
|
1902
|
+
var PANEL_Z = v("z-panel");
|
|
1903
|
+
var TOOLBAR_RESERVE = "100px";
|
|
1904
|
+
var BACKDROP_Z = v("z-overlay");
|
|
1156
1905
|
var panelEl = null;
|
|
1157
1906
|
var backdropEl = null;
|
|
1158
1907
|
var styleInjected2 = false;
|
|
@@ -1165,6 +1914,7 @@ var currentOnTranslateEntry = null;
|
|
|
1165
1914
|
function injectStyles2() {
|
|
1166
1915
|
if (styleInjected2) return;
|
|
1167
1916
|
styleInjected2 = true;
|
|
1917
|
+
injectBaseStyles(state.config?.accentColor, state.config?.toolbarAccent === true);
|
|
1168
1918
|
const s = document.createElement("style");
|
|
1169
1919
|
s.textContent = `
|
|
1170
1920
|
@keyframes cancia-panel-in {
|
|
@@ -1182,9 +1932,6 @@ function injectStyles2() {
|
|
|
1182
1932
|
`;
|
|
1183
1933
|
document.head.appendChild(s);
|
|
1184
1934
|
}
|
|
1185
|
-
function accent3() {
|
|
1186
|
-
return state.config?.accentColor ?? "#6366f1";
|
|
1187
|
-
}
|
|
1188
1935
|
function escapeHtml(s) {
|
|
1189
1936
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1190
1937
|
}
|
|
@@ -1230,9 +1977,9 @@ function derivePreview(schema, data) {
|
|
|
1230
1977
|
let thumbnail = "";
|
|
1231
1978
|
for (const f of schema.fields) {
|
|
1232
1979
|
if (f.widget !== "image") continue;
|
|
1233
|
-
const
|
|
1234
|
-
if (typeof
|
|
1235
|
-
thumbnail =
|
|
1980
|
+
const v2 = data[f.name];
|
|
1981
|
+
if (typeof v2 === "string" && v2.trim()) {
|
|
1982
|
+
thumbnail = v2.trim();
|
|
1236
1983
|
break;
|
|
1237
1984
|
}
|
|
1238
1985
|
}
|
|
@@ -1244,52 +1991,73 @@ function locales() {
|
|
|
1244
1991
|
function buildShell(schema) {
|
|
1245
1992
|
const panel = document.createElement("div");
|
|
1246
1993
|
panel.dataset.canciaListPanel = "1";
|
|
1994
|
+
markUi(panel);
|
|
1247
1995
|
panel.style.cssText = `
|
|
1996
|
+
${surface(2)}
|
|
1248
1997
|
position: fixed;
|
|
1249
1998
|
top: 0; right: 0; bottom: 0;
|
|
1999
|
+
padding-bottom: ${TOOLBAR_RESERVE};
|
|
1250
2000
|
width: min(420px, 100vw);
|
|
1251
|
-
|
|
1252
|
-
color: #1a1a1d;
|
|
2001
|
+
color: ${v("fg")};
|
|
1253
2002
|
z-index: ${PANEL_Z};
|
|
1254
|
-
|
|
2003
|
+
border: 0;
|
|
2004
|
+
border-left: 1px solid ${v("border")};
|
|
2005
|
+
border-radius: 0;
|
|
2006
|
+
box-shadow: ${v("shadow-lg")};
|
|
1255
2007
|
display: flex;
|
|
1256
2008
|
flex-direction: column;
|
|
1257
|
-
font-family:
|
|
1258
|
-
|
|
2009
|
+
font-family: ${v("font")};
|
|
2010
|
+
/* The fixed positioning above also makes this the containing block for the
|
|
2011
|
+
absolutely-positioned list view and the form view that slides in over
|
|
2012
|
+
it; overflow:hidden clips both while they are off-stage. */
|
|
2013
|
+
overflow: hidden;
|
|
2014
|
+
animation: cancia-panel-in ${v("duration")} ${v("ease")} forwards;
|
|
1259
2015
|
`;
|
|
1260
|
-
|
|
2016
|
+
const listView = document.createElement("div");
|
|
2017
|
+
listView.dataset.canciaListView = "1";
|
|
2018
|
+
listView.style.cssText = `
|
|
2019
|
+
position: absolute; inset: 0;
|
|
2020
|
+
display: flex; flex-direction: column;
|
|
2021
|
+
transition: transform ${v("duration")} ${v("ease")}, opacity ${v("duration")} ${v("ease")};
|
|
2022
|
+
`;
|
|
2023
|
+
listView.innerHTML = `
|
|
1261
2024
|
<header style="
|
|
1262
2025
|
display: flex; align-items: center; justify-content: space-between;
|
|
1263
|
-
padding:
|
|
1264
|
-
border-bottom: 1px solid
|
|
2026
|
+
padding: ${v("space-4")} ${v("space-5")};
|
|
2027
|
+
border-bottom: 1px solid ${v("border")};
|
|
1265
2028
|
">
|
|
1266
2029
|
<div>
|
|
1267
|
-
<div style="font-size:
|
|
1268
|
-
<div style="font-size: 17px; font-weight: 600; margin-top: 2px;">${escapeHtml(schema.label)}</div>
|
|
2030
|
+
<div style="font-size: ${v("text-xs")}; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: ${v("fg-muted")};">List</div>
|
|
2031
|
+
<div style="font-size: 17px; font-weight: 600; margin-top: 2px; color: ${v("fg-strong")};">${escapeHtml(schema.label)}</div>
|
|
1269
2032
|
</div>
|
|
1270
2033
|
<button data-cancia-close style="
|
|
2034
|
+
${iconButton()}
|
|
1271
2035
|
appearance: none; border: 0; background: transparent;
|
|
1272
|
-
cursor: pointer;
|
|
1273
|
-
color: #555; transition: background 0.12s, color 0.12s;
|
|
2036
|
+
cursor: pointer;
|
|
1274
2037
|
" aria-label="Close panel">
|
|
1275
2038
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
1276
2039
|
<path d="M4 4l10 10M14 4L4 14"/>
|
|
1277
2040
|
</svg>
|
|
1278
2041
|
</button>
|
|
1279
2042
|
</header>
|
|
2043
|
+
<!-- No overflow-x here. This row holds one short tab per configured
|
|
2044
|
+
locale \u2014 two or three at most \u2014 so a scroll container was solving a
|
|
2045
|
+
problem that does not occur, while permanently costing a scrollbar
|
|
2046
|
+
gutter and (on Windows, where scrollbars are not overlaid) a visible
|
|
2047
|
+
bar under the tabs. If a site ever ships enough locales to overflow,
|
|
2048
|
+
wrapping is the right answer, not scrolling. -->
|
|
1280
2049
|
<div data-cancia-tabs style="
|
|
1281
|
-
display: flex; gap:
|
|
1282
|
-
padding:
|
|
1283
|
-
border-bottom: 1px solid
|
|
1284
|
-
overflow-x: auto;
|
|
2050
|
+
display: flex; flex-wrap: wrap; gap: ${v("space-1")};
|
|
2051
|
+
padding: ${v("space-2")} ${v("space-4")} 0;
|
|
2052
|
+
border-bottom: 1px solid ${v("border")};
|
|
1285
2053
|
"></div>
|
|
1286
|
-
<div style="padding:
|
|
2054
|
+
<div style="padding: ${v("space-3")} ${v("space-5")}; border-bottom: 1px solid ${v("border")};">
|
|
1287
2055
|
<button data-cancia-add style="
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
2056
|
+
${button("ghost")}
|
|
2057
|
+
appearance: none; border: 1px dashed ${v("border-strong")}; background: ${v("accent-soft")};
|
|
2058
|
+
color: ${v("accent")}; font-weight: 600; font-size: ${v("text-base")};
|
|
2059
|
+
padding: 10px 14px; height: auto; width: 100%; cursor: pointer;
|
|
1291
2060
|
display: flex; align-items: center; justify-content: center; gap: 6px;
|
|
1292
|
-
transition: background 0.12s;
|
|
1293
2061
|
">
|
|
1294
2062
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
1295
2063
|
<path d="M7 2v10M2 7h10"/>
|
|
@@ -1299,13 +2067,18 @@ function buildShell(schema) {
|
|
|
1299
2067
|
</div>
|
|
1300
2068
|
<div data-cancia-entries style="
|
|
1301
2069
|
flex: 1; overflow-y: auto;
|
|
1302
|
-
padding:
|
|
2070
|
+
padding: ${v("space-2")} ${v("space-3")} ${v("space-4")};
|
|
1303
2071
|
">
|
|
1304
|
-
<div data-cancia-loading style="text-align: center; padding: 32px
|
|
2072
|
+
<div data-cancia-loading style="text-align: center; padding: 32px ${v("space-3")}; color: ${v("fg-muted")}; font-size: ${v("text-base")};">Loading\u2026</div>
|
|
1305
2073
|
</div>
|
|
1306
2074
|
`;
|
|
1307
|
-
|
|
1308
|
-
const
|
|
2075
|
+
panel.appendChild(listView);
|
|
2076
|
+
const body = listView.querySelector("[data-cancia-entries]");
|
|
2077
|
+
const tabsRow = listView.querySelector("[data-cancia-tabs]");
|
|
2078
|
+
const closeBtn = listView.querySelector("[data-cancia-close]");
|
|
2079
|
+
if (closeBtn) attachHover(closeBtn, { bg: v("surface-hover"), color: v("fg-strong") });
|
|
2080
|
+
const addBtn = listView.querySelector("[data-cancia-add]");
|
|
2081
|
+
if (addBtn) attachHover(addBtn, { bg: v("accent-soft") });
|
|
1309
2082
|
return { panel, body, tabsRow };
|
|
1310
2083
|
}
|
|
1311
2084
|
function renderTabs(tabsRow, activeLocale, onSwitch) {
|
|
@@ -1321,22 +2094,22 @@ function renderTabs(tabsRow, activeLocale, onSwitch) {
|
|
|
1321
2094
|
const isActive = loc === activeLocale;
|
|
1322
2095
|
tab.style.cssText = `
|
|
1323
2096
|
appearance: none; border: 0; background: transparent;
|
|
1324
|
-
font-family: inherit; font-size:
|
|
2097
|
+
font-family: inherit; font-size: ${v("text-sm")}; font-weight: 600;
|
|
1325
2098
|
letter-spacing: 0.04em; text-transform: uppercase;
|
|
1326
|
-
padding:
|
|
2099
|
+
padding: ${v("space-2")} 10px 9px;
|
|
1327
2100
|
cursor: ${isActive ? "default" : "pointer"};
|
|
1328
|
-
color: ${isActive ?
|
|
1329
|
-
border-bottom: 2px solid ${isActive ?
|
|
2101
|
+
color: ${isActive ? v("accent") : v("fg-muted")};
|
|
2102
|
+
border-bottom: 2px solid ${isActive ? v("accent") : "transparent"};
|
|
1330
2103
|
margin-bottom: -1px;
|
|
1331
2104
|
transition: color 0.12s, border-color 0.12s;
|
|
1332
2105
|
`;
|
|
1333
2106
|
tab.textContent = loc;
|
|
1334
2107
|
if (!isActive) {
|
|
1335
2108
|
tab.addEventListener("mouseenter", () => {
|
|
1336
|
-
tab.style.color = "
|
|
2109
|
+
tab.style.color = v("fg-strong");
|
|
1337
2110
|
});
|
|
1338
2111
|
tab.addEventListener("mouseleave", () => {
|
|
1339
|
-
tab.style.color = "
|
|
2112
|
+
tab.style.color = v("fg-muted");
|
|
1340
2113
|
});
|
|
1341
2114
|
tab.addEventListener("click", () => onSwitch(loc));
|
|
1342
2115
|
}
|
|
@@ -1365,7 +2138,7 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1365
2138
|
});
|
|
1366
2139
|
if (rows.length === 0) {
|
|
1367
2140
|
body.innerHTML = `
|
|
1368
|
-
<div style="text-align: center; padding: 40px
|
|
2141
|
+
<div style="text-align: center; padding: 40px ${v("space-3")}; color: ${v("fg-muted")}; font-size: ${v("text-base")};">
|
|
1369
2142
|
No entries yet. Click "Add ${escapeHtml(schema.labelSingular.toLowerCase())}" above to create one.
|
|
1370
2143
|
</div>
|
|
1371
2144
|
`;
|
|
@@ -1391,7 +2164,7 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1391
2164
|
}
|
|
1392
2165
|
const err = document.createElement("div");
|
|
1393
2166
|
err.textContent = "Couldn't save the new order. Reverted.";
|
|
1394
|
-
err.style.cssText = `text-align:center; padding
|
|
2167
|
+
err.style.cssText = `text-align:center; padding:${v("space-2")}; color:${v("danger")}; font-size:${v("text-sm")};`;
|
|
1395
2168
|
body.insertBefore(err, body.firstChild);
|
|
1396
2169
|
setTimeout(() => err.remove(), 3e3);
|
|
1397
2170
|
}
|
|
@@ -1399,7 +2172,7 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1399
2172
|
rows.forEach((row) => {
|
|
1400
2173
|
const isStub = row.entry === null;
|
|
1401
2174
|
const wrap = document.createElement("div");
|
|
1402
|
-
wrap.style.cssText = `display: flex; align-items: stretch; gap:
|
|
2175
|
+
wrap.style.cssText = `display: flex; align-items: stretch; gap: ${v("space-1")}; margin-bottom: 6px;`;
|
|
1403
2176
|
const handle = document.createElement("div");
|
|
1404
2177
|
handle.textContent = "\u22EE\u22EE";
|
|
1405
2178
|
handle.title = "Drag to reorder";
|
|
@@ -1407,14 +2180,22 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1407
2180
|
handle.style.cssText = `
|
|
1408
2181
|
cursor: grab; user-select: none;
|
|
1409
2182
|
display: flex; align-items: center; justify-content: center;
|
|
1410
|
-
color:
|
|
2183
|
+
color: ${v("fg-faint")}; font-size: ${v("text-base")}; letter-spacing: -2px;
|
|
1411
2184
|
padding: 0 2px; flex-shrink: 0;
|
|
2185
|
+
transition: color 0.12s;
|
|
1412
2186
|
`;
|
|
2187
|
+
handle.addEventListener("mouseenter", () => {
|
|
2188
|
+
handle.style.color = v("accent");
|
|
2189
|
+
});
|
|
2190
|
+
handle.addEventListener("mouseleave", () => {
|
|
2191
|
+
handle.style.color = v("fg-faint");
|
|
2192
|
+
});
|
|
1413
2193
|
const item = document.createElement("button");
|
|
1414
2194
|
item.style.cssText = `
|
|
1415
|
-
appearance: none; border: 1px solid transparent;
|
|
2195
|
+
appearance: none; border: 1px solid transparent;
|
|
2196
|
+
background: ${isStub ? v("warning-soft") : v("surface-raised")};
|
|
1416
2197
|
text-align: left; flex: 1; min-width: 0;
|
|
1417
|
-
padding:
|
|
2198
|
+
padding: ${v("space-3")} 14px; border-radius: ${v("radius-sm")}; cursor: pointer;
|
|
1418
2199
|
transition: background 0.12s, border-color 0.12s, transform 0.12s;
|
|
1419
2200
|
display: block;
|
|
1420
2201
|
`;
|
|
@@ -1458,27 +2239,36 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1458
2239
|
const titleValue = row.entry.data[schema.titleField];
|
|
1459
2240
|
const title = typeof titleValue === "string" && titleValue.trim().length > 0 ? titleValue : `(untitled ${schema.labelSingular.toLowerCase()})`;
|
|
1460
2241
|
const { subtitle, thumbnail } = derivePreview(schema, row.entry.data);
|
|
2242
|
+
const isDraftRow = !!schema.draftField && row.entry.data[schema.draftField] === true;
|
|
2243
|
+
const draftBadge = isDraftRow ? `<span style="
|
|
2244
|
+
flex-shrink: 0; margin-left: ${v("space-2")}; padding: 1px 6px;
|
|
2245
|
+
font-size: 11px; font-weight: 600; line-height: 1.5;
|
|
2246
|
+
border-radius: ${v("radius-sm")}; color: ${v("fg-muted")};
|
|
2247
|
+
background: ${v("surface-raised")}; border: 1px solid ${v("border")};
|
|
2248
|
+
">Draft</span>` : "";
|
|
1461
2249
|
const textCol = `
|
|
1462
2250
|
<div style="min-width: 0; flex: 1;">
|
|
1463
|
-
<div style="
|
|
1464
|
-
|
|
2251
|
+
<div style="display: flex; align-items: center;">
|
|
2252
|
+
<span style="font-weight: 600; font-size: 14px; color: ${v("fg-strong")}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${escapeHtml(title)}</span>${draftBadge}
|
|
2253
|
+
</div>
|
|
2254
|
+
${subtitle ? `<div style="font-size: ${v("text-sm")}; color: ${v("fg-muted")}; margin-top: ${v("space-1")}; line-height: 1.4;">${escapeHtml(subtitle)}</div>` : ""}
|
|
1465
2255
|
</div>
|
|
1466
2256
|
`;
|
|
1467
2257
|
const thumb = thumbnail ? `<img src="${escapeHtml(thumbnail)}" alt="" loading="lazy" style="
|
|
1468
2258
|
width: 44px; height: 44px; flex-shrink: 0; object-fit: cover;
|
|
1469
|
-
border-radius:
|
|
2259
|
+
border-radius: ${v("radius-sm")}; background: ${v("surface-raised")}; border: 1px solid ${v("border")};
|
|
1470
2260
|
" onerror="this.style.display='none'" />` : "";
|
|
1471
2261
|
item.innerHTML = `
|
|
1472
|
-
<div style="display: flex; align-items: center; gap:
|
|
2262
|
+
<div style="display: flex; align-items: center; gap: ${v("space-3")};">
|
|
1473
2263
|
${thumb}${textCol}
|
|
1474
2264
|
</div>
|
|
1475
2265
|
`;
|
|
1476
2266
|
item.addEventListener("mouseenter", () => {
|
|
1477
|
-
item.style.background = "
|
|
1478
|
-
item.style.borderColor = "
|
|
2267
|
+
item.style.background = v("surface-hover");
|
|
2268
|
+
item.style.borderColor = v("border-strong");
|
|
1479
2269
|
});
|
|
1480
2270
|
item.addEventListener("mouseleave", () => {
|
|
1481
|
-
item.style.background = "
|
|
2271
|
+
item.style.background = v("surface-raised");
|
|
1482
2272
|
item.style.borderColor = "transparent";
|
|
1483
2273
|
});
|
|
1484
2274
|
item.addEventListener("click", () => currentOnEditEntry?.(row.entry, activeLocale));
|
|
@@ -1488,23 +2278,26 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1488
2278
|
const sourceTitleValue = sourceEntry?.data[schema.titleField];
|
|
1489
2279
|
const sourceTitle = typeof sourceTitleValue === "string" && sourceTitleValue.trim().length > 0 ? sourceTitleValue : row.id;
|
|
1490
2280
|
item.innerHTML = `
|
|
1491
|
-
<div style="display: flex; align-items: center; gap:
|
|
2281
|
+
<div style="display: flex; align-items: center; gap: ${v("space-2")};">
|
|
1492
2282
|
<span style="
|
|
1493
2283
|
font-size: 9px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
|
|
1494
|
-
|
|
2284
|
+
/* The light theme's warning token is a mid amber (#d97706), dark
|
|
2285
|
+
enough to carry WHITE text \u2014 so the chip now uses accent-fg and
|
|
2286
|
+
the near-black literal that used to live here is gone. */
|
|
2287
|
+
background: ${v("warning")}; color: ${v("accent-fg")};
|
|
1495
2288
|
padding: 2px 6px; border-radius: 4px;
|
|
1496
2289
|
">Not translated</span>
|
|
1497
|
-
<span style="font-size:
|
|
2290
|
+
<span style="font-size: ${v("text-xs")}; color: ${v("fg-muted")};">from ${escapeHtml(sourceLocale)}</span>
|
|
1498
2291
|
</div>
|
|
1499
|
-
<div style="font-weight: 600; font-size: 14px; color:
|
|
1500
|
-
<div style="font-size:
|
|
2292
|
+
<div style="font-weight: 600; font-size: 14px; color: ${v("fg-strong")}; margin-top: 6px;">${escapeHtml(sourceTitle)}</div>
|
|
2293
|
+
<div style="font-size: ${v("text-xs")}; color: ${v("warning")}; margin-top: ${v("space-1")};">Click to translate into ${escapeHtml(activeLocale)}</div>
|
|
1501
2294
|
`;
|
|
1502
2295
|
item.addEventListener("mouseenter", () => {
|
|
1503
|
-
item.style.background = "
|
|
1504
|
-
item.style.borderColor = "
|
|
2296
|
+
item.style.background = "rgba(217, 119, 6, 0.18)";
|
|
2297
|
+
item.style.borderColor = v("warning");
|
|
1505
2298
|
});
|
|
1506
2299
|
item.addEventListener("mouseleave", () => {
|
|
1507
|
-
item.style.background = "
|
|
2300
|
+
item.style.background = v("warning-soft");
|
|
1508
2301
|
item.style.borderColor = "transparent";
|
|
1509
2302
|
});
|
|
1510
2303
|
item.addEventListener(
|
|
@@ -1523,11 +2316,12 @@ async function openListPanel(opts) {
|
|
|
1523
2316
|
injectStyles2();
|
|
1524
2317
|
const backdrop = document.createElement("div");
|
|
1525
2318
|
backdrop.dataset.canciaPanelBackdrop = "1";
|
|
2319
|
+
markUi(backdrop);
|
|
1526
2320
|
backdrop.style.cssText = `
|
|
1527
2321
|
position: fixed; inset: 0;
|
|
1528
|
-
background: rgba(10,10,12,0.
|
|
2322
|
+
background: rgba(10,10,12,0.20);
|
|
1529
2323
|
z-index: ${BACKDROP_Z};
|
|
1530
|
-
animation: cancia-backdrop-in
|
|
2324
|
+
animation: cancia-backdrop-in ${v("duration")} ${v("ease-out")} forwards;
|
|
1531
2325
|
`;
|
|
1532
2326
|
backdrop.addEventListener("click", () => closeListPanel());
|
|
1533
2327
|
document.body.appendChild(backdrop);
|
|
@@ -1577,7 +2371,7 @@ async function refreshListPanel() {
|
|
|
1577
2371
|
} catch (err) {
|
|
1578
2372
|
if (!panelEl) return;
|
|
1579
2373
|
body.innerHTML = `
|
|
1580
|
-
<div style="text-align: center; padding: 32px
|
|
2374
|
+
<div style="text-align: center; padding: 32px ${v("space-3")}; color: ${v("danger")}; font-size: ${v("text-base")};">
|
|
1581
2375
|
Failed to load entries: ${escapeHtml(err instanceof Error ? err.message : String(err))}
|
|
1582
2376
|
</div>
|
|
1583
2377
|
`;
|
|
@@ -1585,7 +2379,7 @@ async function refreshListPanel() {
|
|
|
1585
2379
|
}
|
|
1586
2380
|
function closeListPanel() {
|
|
1587
2381
|
if (panelEl) {
|
|
1588
|
-
panelEl.style.animation =
|
|
2382
|
+
panelEl.style.animation = `cancia-panel-out ${v("duration-exit")} ${v("ease-out")} forwards`;
|
|
1589
2383
|
const el = panelEl;
|
|
1590
2384
|
setTimeout(() => el.remove(), 180);
|
|
1591
2385
|
panelEl = null;
|
|
@@ -1593,7 +2387,7 @@ function closeListPanel() {
|
|
|
1593
2387
|
if (backdropEl) {
|
|
1594
2388
|
const el = backdropEl;
|
|
1595
2389
|
el.style.opacity = "0";
|
|
1596
|
-
el.style.transition =
|
|
2390
|
+
el.style.transition = `opacity ${v("duration-exit")} ${v("ease-out")}`;
|
|
1597
2391
|
setTimeout(() => el.remove(), 180);
|
|
1598
2392
|
backdropEl = null;
|
|
1599
2393
|
}
|
|
@@ -1607,65 +2401,146 @@ function closeListPanel() {
|
|
|
1607
2401
|
function isListPanelOpen() {
|
|
1608
2402
|
return panelEl !== null;
|
|
1609
2403
|
}
|
|
2404
|
+
function listViewEl() {
|
|
2405
|
+
return panelEl?.querySelector("[data-cancia-list-view]") ?? null;
|
|
2406
|
+
}
|
|
2407
|
+
function pushPanelView(view) {
|
|
2408
|
+
const panel = panelEl;
|
|
2409
|
+
const list = listViewEl();
|
|
2410
|
+
if (!panel || !list) return false;
|
|
2411
|
+
view.style.position = "absolute";
|
|
2412
|
+
view.style.inset = "0";
|
|
2413
|
+
view.style.display = "flex";
|
|
2414
|
+
view.style.flexDirection = "column";
|
|
2415
|
+
view.style.background = v("surface-3");
|
|
2416
|
+
view.style.transform = "translateX(100%)";
|
|
2417
|
+
view.style.transition = `transform ${v("duration")} ${v("ease")}`;
|
|
2418
|
+
panel.appendChild(view);
|
|
2419
|
+
void view.offsetWidth;
|
|
2420
|
+
view.style.transform = "translateX(0)";
|
|
2421
|
+
list.style.transition = `transform ${v("duration")} ${v("ease")}, opacity ${v("duration")} ${v("ease")}`;
|
|
2422
|
+
void list.offsetWidth;
|
|
2423
|
+
list.style.transform = "translateX(-25%)";
|
|
2424
|
+
list.style.opacity = "0.4";
|
|
2425
|
+
list.setAttribute("aria-hidden", "true");
|
|
2426
|
+
list.style.pointerEvents = "none";
|
|
2427
|
+
return true;
|
|
2428
|
+
}
|
|
2429
|
+
function popPanelView(view) {
|
|
2430
|
+
const list = listViewEl();
|
|
2431
|
+
view.style.transition = `transform ${v("duration-exit")} ${v("ease-out")}`;
|
|
2432
|
+
view.style.transform = "translateX(100%)";
|
|
2433
|
+
if (list) {
|
|
2434
|
+
list.style.transition = `transform ${v("duration-exit")} ${v("ease-out")}, opacity ${v("duration-exit")} ${v("ease-out")}`;
|
|
2435
|
+
list.style.transform = "translateX(0)";
|
|
2436
|
+
list.style.opacity = "1";
|
|
2437
|
+
list.removeAttribute("aria-hidden");
|
|
2438
|
+
list.style.pointerEvents = "";
|
|
2439
|
+
}
|
|
2440
|
+
let removed = false;
|
|
2441
|
+
const done = () => {
|
|
2442
|
+
if (removed) return;
|
|
2443
|
+
removed = true;
|
|
2444
|
+
view.remove();
|
|
2445
|
+
};
|
|
2446
|
+
view.addEventListener("transitionend", done, { once: true });
|
|
2447
|
+
setTimeout(done, 400);
|
|
2448
|
+
}
|
|
1610
2449
|
|
|
1611
2450
|
// src/entry-modal.ts
|
|
1612
2451
|
import {
|
|
1613
|
-
rowsToPortableText,
|
|
1614
|
-
portableTextToRows,
|
|
1615
|
-
portableTextSubsetSchema,
|
|
1616
|
-
PT_STYLES
|
|
2452
|
+
rowsToPortableText as rowsToPortableText2,
|
|
2453
|
+
portableTextToRows as portableTextToRows2,
|
|
2454
|
+
portableTextSubsetSchema as portableTextSubsetSchema2,
|
|
2455
|
+
PT_STYLES as PT_STYLES2
|
|
1617
2456
|
} from "@cancia/astro/richtext";
|
|
1618
2457
|
import { slugify } from "@cancia/astro/schema";
|
|
1619
|
-
var MODAL_Z =
|
|
1620
|
-
var BACKDROP_Z2 =
|
|
2458
|
+
var MODAL_Z = v("z-bar");
|
|
2459
|
+
var BACKDROP_Z2 = v("z-panel");
|
|
1621
2460
|
var modalEl = null;
|
|
1622
2461
|
var backdropEl2 = null;
|
|
1623
2462
|
var escListener = null;
|
|
1624
2463
|
var styleInjected3 = false;
|
|
2464
|
+
var mountedInPanel = false;
|
|
1625
2465
|
function injectStyles3() {
|
|
1626
2466
|
if (styleInjected3) return;
|
|
1627
2467
|
styleInjected3 = true;
|
|
2468
|
+
injectBaseStyles(state.config?.accentColor, state.config?.toolbarAccent === true);
|
|
1628
2469
|
const s = document.createElement("style");
|
|
1629
2470
|
s.textContent = `
|
|
1630
|
-
@keyframes cancia-modal-in {
|
|
1631
|
-
from { opacity: 0; transform: translate(-50%, calc(-50% + 8px)) scale(0.985); }
|
|
1632
|
-
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
1633
|
-
}
|
|
1634
|
-
@keyframes cancia-modal-out {
|
|
1635
|
-
from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
1636
|
-
to { opacity: 0; transform: translate(-50%, calc(-50% + 8px)) scale(0.985); }
|
|
1637
|
-
}
|
|
1638
2471
|
.cancia-form-input:focus,
|
|
1639
2472
|
.cancia-form-textarea:focus,
|
|
1640
2473
|
.cancia-form-select:focus {
|
|
1641
|
-
border-color: var(--cancia-accent-border,
|
|
1642
|
-
background:
|
|
2474
|
+
border-color: var(--cancia-accent-border, ${v("accent-ring")});
|
|
2475
|
+
background: ${v("surface-hover")};
|
|
1643
2476
|
outline: none;
|
|
1644
2477
|
}
|
|
1645
2478
|
.cancia-form-input::placeholder,
|
|
1646
2479
|
.cancia-form-textarea::placeholder {
|
|
1647
|
-
color:
|
|
2480
|
+
color: ${v("fg-faint")};
|
|
1648
2481
|
}
|
|
1649
2482
|
.cancia-form-select option {
|
|
1650
|
-
|
|
1651
|
-
|
|
2483
|
+
/* An <option> is painted by the OS, so it cannot be translucent \u2014 this
|
|
2484
|
+
stays an opaque hex matching surface-3 (now white). */
|
|
2485
|
+
background: #ffffff;
|
|
2486
|
+
color: ${v("fg")};
|
|
1652
2487
|
}
|
|
1653
2488
|
.cancia-field-error {
|
|
1654
|
-
color:
|
|
1655
|
-
font-size:
|
|
2489
|
+
color: ${v("danger")};
|
|
2490
|
+
font-size: ${v("text-xs")};
|
|
1656
2491
|
margin-top: 5px;
|
|
1657
2492
|
line-height: 1.35;
|
|
1658
2493
|
}
|
|
1659
2494
|
`;
|
|
1660
2495
|
document.head.appendChild(s);
|
|
1661
2496
|
}
|
|
1662
|
-
function
|
|
1663
|
-
|
|
2497
|
+
function accent3() {
|
|
2498
|
+
const useAccent = state.config?.toolbarAccent === true;
|
|
2499
|
+
return (useAccent ? state.config?.accentColor : void 0) ?? "#18181b";
|
|
1664
2500
|
}
|
|
1665
2501
|
function accentBorder() {
|
|
1666
|
-
const a =
|
|
2502
|
+
const a = accent3();
|
|
1667
2503
|
if (/^#[0-9a-f]{6}$/i.test(a)) return `${a}99`;
|
|
1668
|
-
return "rgba(
|
|
2504
|
+
return "rgba(24,24,27,0.28)";
|
|
2505
|
+
}
|
|
2506
|
+
function attachRowReveal(row, targets) {
|
|
2507
|
+
const apply = () => {
|
|
2508
|
+
const on = row.dataset.canciaHover === "1" || row.contains(document.activeElement);
|
|
2509
|
+
for (const t of targets) {
|
|
2510
|
+
t.style.opacity = on ? "1" : "0";
|
|
2511
|
+
if (t.dataset.canciaCollapsible === "1") {
|
|
2512
|
+
t.style.maxHeight = on ? "40px" : "0";
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
};
|
|
2516
|
+
row.addEventListener("pointerenter", () => {
|
|
2517
|
+
row.dataset.canciaHover = "1";
|
|
2518
|
+
apply();
|
|
2519
|
+
});
|
|
2520
|
+
row.addEventListener("pointerleave", () => {
|
|
2521
|
+
row.dataset.canciaHover = "0";
|
|
2522
|
+
apply();
|
|
2523
|
+
});
|
|
2524
|
+
row.addEventListener("focusin", apply);
|
|
2525
|
+
row.addEventListener("focusout", () => requestAnimationFrame(apply));
|
|
2526
|
+
apply();
|
|
2527
|
+
}
|
|
2528
|
+
function humanise(label2) {
|
|
2529
|
+
if (!label2) return label2;
|
|
2530
|
+
if (/\s/.test(label2)) return label2;
|
|
2531
|
+
const PROPER = {
|
|
2532
|
+
linkedin: "LinkedIn",
|
|
2533
|
+
github: "GitHub",
|
|
2534
|
+
youtube: "YouTube",
|
|
2535
|
+
tiktok: "TikTok",
|
|
2536
|
+
whatsapp: "WhatsApp",
|
|
2537
|
+
facebook: "Facebook",
|
|
2538
|
+
instagram: "Instagram"
|
|
2539
|
+
};
|
|
2540
|
+
const exact = PROPER[label2.toLowerCase()];
|
|
2541
|
+
if (exact) return exact;
|
|
2542
|
+
const spaced = label2.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
|
|
2543
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
1669
2544
|
}
|
|
1670
2545
|
function isoToLocalInput(iso) {
|
|
1671
2546
|
if (!iso) return "";
|
|
@@ -1681,44 +2556,39 @@ function localInputToIso(local) {
|
|
|
1681
2556
|
return d.toISOString();
|
|
1682
2557
|
}
|
|
1683
2558
|
var INPUT_BASE = `
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
color: rgba(255,255,255,0.9);
|
|
1687
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
1688
|
-
border-radius: 8px;
|
|
1689
|
-
padding: 8px 11px;
|
|
1690
|
-
font-size: 13px;
|
|
2559
|
+
${input()}
|
|
2560
|
+
box-sizing: border-box;
|
|
1691
2561
|
font-family: inherit;
|
|
1692
2562
|
line-height: 1.5;
|
|
1693
|
-
outline: none;
|
|
1694
|
-
transition: border-color 0.18s, background 0.18s;
|
|
1695
2563
|
`;
|
|
2564
|
+
var SELECT_CHEVRON = `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2.5 3.75l2.5 2.5 2.5-2.5' stroke='%2371717a' stroke-width='1.3' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>")`;
|
|
1696
2565
|
var MAX_FIELD_DEPTH = 6;
|
|
1697
2566
|
function renderField(field, initial, depth = 0) {
|
|
1698
2567
|
const wrapper = document.createElement("div");
|
|
1699
|
-
wrapper.style.cssText = `margin-bottom:
|
|
2568
|
+
wrapper.style.cssText = `margin-bottom: ${v("space-4")};`;
|
|
1700
2569
|
const labelRow = document.createElement("label");
|
|
1701
2570
|
labelRow.style.cssText = `
|
|
2571
|
+
${label()}
|
|
1702
2572
|
display: flex; align-items: baseline; justify-content: space-between;
|
|
1703
|
-
gap:
|
|
1704
|
-
font-size: 11px; font-weight: 600;
|
|
1705
|
-
color: rgba(255,255,255,0.75);
|
|
1706
|
-
letter-spacing: 0.04em;
|
|
2573
|
+
gap: ${v("space-2")};
|
|
1707
2574
|
margin-bottom: 5px;
|
|
1708
2575
|
`;
|
|
1709
2576
|
const labelText = document.createElement("span");
|
|
1710
|
-
labelText.textContent = field.label;
|
|
1711
|
-
if (field.required) {
|
|
1712
|
-
const star = document.createElement("span");
|
|
1713
|
-
star.textContent = " *";
|
|
1714
|
-
star.style.color = "rgba(255,135,134,0.8)";
|
|
1715
|
-
labelText.appendChild(star);
|
|
1716
|
-
}
|
|
2577
|
+
labelText.textContent = humanise(field.label);
|
|
1717
2578
|
labelRow.appendChild(labelText);
|
|
2579
|
+
if (!field.required) {
|
|
2580
|
+
const opt = document.createElement("span");
|
|
2581
|
+
opt.textContent = "Optional";
|
|
2582
|
+
opt.style.cssText = `
|
|
2583
|
+
font-size: ${v("text-xs")}; font-weight: 400;
|
|
2584
|
+
color: ${v("fg-faint")}; text-transform: none; letter-spacing: normal;
|
|
2585
|
+
`;
|
|
2586
|
+
labelRow.appendChild(opt);
|
|
2587
|
+
}
|
|
1718
2588
|
if (field.label) wrapper.appendChild(labelRow);
|
|
1719
2589
|
if (field.description) {
|
|
1720
2590
|
const help = document.createElement("div");
|
|
1721
|
-
help.style.cssText =
|
|
2591
|
+
help.style.cssText = `${hint()} margin-bottom: 6px;`;
|
|
1722
2592
|
help.textContent = field.description;
|
|
1723
2593
|
wrapper.appendChild(help);
|
|
1724
2594
|
}
|
|
@@ -1769,7 +2639,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1769
2639
|
case "textarea": {
|
|
1770
2640
|
const ta = document.createElement("textarea");
|
|
1771
2641
|
ta.className = "cancia-form-textarea";
|
|
1772
|
-
ta.style.cssText = `${INPUT_BASE} resize: vertical; min-height: 110px; max-height: 320px; caret-color: ${
|
|
2642
|
+
ta.style.cssText = `${INPUT_BASE} resize: vertical; min-height: 110px; max-height: 320px; caret-color: ${v("accent")};`;
|
|
1773
2643
|
ta.rows = 5;
|
|
1774
2644
|
if (field.placeholder) ta.placeholder = field.placeholder;
|
|
1775
2645
|
if (typeof initial === "string") ta.value = initial;
|
|
@@ -1782,10 +2652,10 @@ function renderField(field, initial, depth = 0) {
|
|
|
1782
2652
|
row.style.cssText = `display: flex; align-items: center; gap: 9px; cursor: pointer; user-select: none; padding: 6px 0;`;
|
|
1783
2653
|
const cb = document.createElement("input");
|
|
1784
2654
|
cb.type = "checkbox";
|
|
1785
|
-
cb.style.cssText = `width: 16px; height: 16px; accent-color: ${
|
|
2655
|
+
cb.style.cssText = `width: 16px; height: 16px; accent-color: ${v("accent")};`;
|
|
1786
2656
|
if (initial === true) cb.checked = true;
|
|
1787
2657
|
const txt = document.createElement("span");
|
|
1788
|
-
txt.style.cssText = `font-size:
|
|
2658
|
+
txt.style.cssText = `font-size: ${v("text-base")}; color: ${v("fg")};`;
|
|
1789
2659
|
txt.textContent = field.placeholder ?? `Enable ${field.label.toLowerCase()}`;
|
|
1790
2660
|
row.appendChild(cb);
|
|
1791
2661
|
row.appendChild(txt);
|
|
@@ -1796,7 +2666,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1796
2666
|
case "select": {
|
|
1797
2667
|
const sel = document.createElement("select");
|
|
1798
2668
|
sel.className = "cancia-form-select";
|
|
1799
|
-
sel.style.cssText = `${INPUT_BASE} appearance: none; padding-right: 30px; background-image:
|
|
2669
|
+
sel.style.cssText = `${INPUT_BASE} appearance: none; padding-right: 30px; background-image: ${SELECT_CHEVRON}; background-repeat: no-repeat; background-position: right 10px center; cursor: pointer;`;
|
|
1800
2670
|
if (!field.required) {
|
|
1801
2671
|
const empty = document.createElement("option");
|
|
1802
2672
|
empty.value = "";
|
|
@@ -1820,19 +2690,19 @@ function renderField(field, initial, depth = 0) {
|
|
|
1820
2690
|
const container = document.createElement("div");
|
|
1821
2691
|
container.style.cssText = `
|
|
1822
2692
|
display: flex; gap: 10px; align-items: stretch;
|
|
1823
|
-
background:
|
|
1824
|
-
border: 1px dashed
|
|
1825
|
-
border-radius:
|
|
2693
|
+
background: ${v("surface-raised")};
|
|
2694
|
+
border: 1px dashed ${v("border-strong")};
|
|
2695
|
+
border-radius: ${v("radius")};
|
|
1826
2696
|
padding: 10px;
|
|
1827
2697
|
`;
|
|
1828
2698
|
const preview = document.createElement("div");
|
|
1829
2699
|
preview.style.cssText = `
|
|
1830
2700
|
width: 72px; height: 72px; flex-shrink: 0;
|
|
1831
|
-
background:
|
|
1832
|
-
border: 1px solid
|
|
1833
|
-
border-radius:
|
|
2701
|
+
background: ${v("surface-raised")} no-repeat center / cover;
|
|
2702
|
+
border: 1px solid ${v("border")};
|
|
2703
|
+
border-radius: ${v("radius-sm")};
|
|
1834
2704
|
display: flex; align-items: center; justify-content: center;
|
|
1835
|
-
color:
|
|
2705
|
+
color: ${v("fg-faint")};
|
|
1836
2706
|
`;
|
|
1837
2707
|
const updatePreview = (url) => {
|
|
1838
2708
|
if (url) {
|
|
@@ -1855,29 +2725,28 @@ function renderField(field, initial, depth = 0) {
|
|
|
1855
2725
|
const uploadBtn = document.createElement("button");
|
|
1856
2726
|
uploadBtn.type = "button";
|
|
1857
2727
|
uploadBtn.style.cssText = `
|
|
1858
|
-
|
|
1859
|
-
background:
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2728
|
+
${button("ghost")}
|
|
2729
|
+
background: ${v("surface-hover")};
|
|
2730
|
+
color: ${v("fg")};
|
|
2731
|
+
border: 1px solid ${v("border")};
|
|
2732
|
+
height: auto;
|
|
2733
|
+
font-size: ${v("text-xs")}; letter-spacing: 0.02em;
|
|
2734
|
+
padding: 5px 10px;
|
|
2735
|
+
cursor: pointer;
|
|
1864
2736
|
`;
|
|
1865
2737
|
uploadBtn.textContent = "Upload\u2026";
|
|
1866
|
-
uploadBtn
|
|
1867
|
-
uploadBtn.style.background = "rgba(255,255,255,0.1)";
|
|
1868
|
-
});
|
|
1869
|
-
uploadBtn.addEventListener("mouseleave", () => {
|
|
1870
|
-
uploadBtn.style.background = "rgba(255,255,255,0.06)";
|
|
1871
|
-
});
|
|
2738
|
+
attachHover(uploadBtn, { bg: v("surface-active") });
|
|
1872
2739
|
uploadBtn.addEventListener("click", () => fileInput.click());
|
|
1873
2740
|
const clearBtn = document.createElement("button");
|
|
1874
2741
|
clearBtn.type = "button";
|
|
1875
2742
|
clearBtn.style.cssText = `
|
|
1876
|
-
|
|
1877
|
-
|
|
2743
|
+
${button("ghost")}
|
|
2744
|
+
color: ${v("fg-muted")};
|
|
1878
2745
|
border: 1px solid transparent;
|
|
1879
|
-
|
|
1880
|
-
|
|
2746
|
+
height: auto;
|
|
2747
|
+
font-size: ${v("text-xs")};
|
|
2748
|
+
padding: 5px 8px;
|
|
2749
|
+
cursor: pointer;
|
|
1881
2750
|
`;
|
|
1882
2751
|
clearBtn.textContent = "Clear";
|
|
1883
2752
|
clearBtn.addEventListener("click", () => {
|
|
@@ -1890,7 +2759,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1890
2759
|
const urlField = document.createElement("input");
|
|
1891
2760
|
urlField.type = "url";
|
|
1892
2761
|
urlField.className = "cancia-form-input";
|
|
1893
|
-
urlField.style.cssText = `${INPUT_BASE} font-size:
|
|
2762
|
+
urlField.style.cssText = `${INPUT_BASE} font-size: ${v("text-xs")}; padding: 6px 9px;`;
|
|
1894
2763
|
urlField.placeholder = "https://\u2026 or upload";
|
|
1895
2764
|
urlField.value = initialUrl;
|
|
1896
2765
|
urlField.addEventListener("input", () => {
|
|
@@ -1898,7 +2767,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1898
2767
|
updatePreview(currentUrl);
|
|
1899
2768
|
});
|
|
1900
2769
|
const progressEl = document.createElement("div");
|
|
1901
|
-
progressEl.style.cssText = `font-size: 10px; color:
|
|
2770
|
+
progressEl.style.cssText = `font-size: 10px; color: ${v("fg-muted")}; height: 12px;`;
|
|
1902
2771
|
right.appendChild(btnRow);
|
|
1903
2772
|
right.appendChild(urlField);
|
|
1904
2773
|
right.appendChild(progressEl);
|
|
@@ -1910,7 +2779,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1910
2779
|
const file = fileInput.files?.[0];
|
|
1911
2780
|
if (!file) return;
|
|
1912
2781
|
uploadBtn.disabled = true;
|
|
1913
|
-
progressEl.style.color = "
|
|
2782
|
+
progressEl.style.color = v("fg-muted");
|
|
1914
2783
|
try {
|
|
1915
2784
|
const url = await uploadImage(file, (pct) => {
|
|
1916
2785
|
progressEl.textContent = `Uploading\u2026 ${pct}%`;
|
|
@@ -1924,7 +2793,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1924
2793
|
}, 1500);
|
|
1925
2794
|
} catch (err) {
|
|
1926
2795
|
progressEl.textContent = `Upload failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
1927
|
-
progressEl.style.color = "
|
|
2796
|
+
progressEl.style.color = v("danger");
|
|
1928
2797
|
} finally {
|
|
1929
2798
|
uploadBtn.disabled = false;
|
|
1930
2799
|
fileInput.value = "";
|
|
@@ -1934,51 +2803,51 @@ function renderField(field, initial, depth = 0) {
|
|
|
1934
2803
|
break;
|
|
1935
2804
|
}
|
|
1936
2805
|
case "datetime": {
|
|
1937
|
-
const
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
if (typeof initial === "string")
|
|
1942
|
-
wrapper.appendChild(
|
|
2806
|
+
const input2 = document.createElement("input");
|
|
2807
|
+
input2.type = "datetime-local";
|
|
2808
|
+
input2.className = "cancia-form-input";
|
|
2809
|
+
input2.style.cssText = `${INPUT_BASE} color-scheme: light; caret-color: ${v("accent")};`;
|
|
2810
|
+
if (typeof initial === "string") input2.value = isoToLocalInput(initial);
|
|
2811
|
+
wrapper.appendChild(input2);
|
|
1943
2812
|
getValue2 = () => {
|
|
1944
|
-
const
|
|
1945
|
-
if (!
|
|
1946
|
-
return localInputToIso(
|
|
2813
|
+
const v2 = input2.value.trim();
|
|
2814
|
+
if (!v2) return void 0;
|
|
2815
|
+
return localInputToIso(v2);
|
|
1947
2816
|
};
|
|
1948
2817
|
break;
|
|
1949
2818
|
}
|
|
1950
2819
|
case "number": {
|
|
1951
|
-
const
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
if (field.min !== void 0)
|
|
1956
|
-
if (field.max !== void 0)
|
|
1957
|
-
if (typeof initial === "number")
|
|
1958
|
-
else if (typeof initial === "string" && initial !== "")
|
|
1959
|
-
wrapper.appendChild(
|
|
2820
|
+
const input2 = document.createElement("input");
|
|
2821
|
+
input2.type = "number";
|
|
2822
|
+
input2.className = "cancia-form-input";
|
|
2823
|
+
input2.style.cssText = `${INPUT_BASE} caret-color: ${v("accent")};`;
|
|
2824
|
+
if (field.min !== void 0) input2.min = String(field.min);
|
|
2825
|
+
if (field.max !== void 0) input2.max = String(field.max);
|
|
2826
|
+
if (typeof initial === "number") input2.value = String(initial);
|
|
2827
|
+
else if (typeof initial === "string" && initial !== "") input2.value = initial;
|
|
2828
|
+
wrapper.appendChild(input2);
|
|
1960
2829
|
getValue2 = () => {
|
|
1961
|
-
const
|
|
1962
|
-
if (
|
|
1963
|
-
const n = Number(
|
|
2830
|
+
const v2 = input2.value.trim();
|
|
2831
|
+
if (v2 === "") return void 0;
|
|
2832
|
+
const n = Number(v2);
|
|
1964
2833
|
return Number.isNaN(n) ? void 0 : n;
|
|
1965
2834
|
};
|
|
1966
2835
|
break;
|
|
1967
2836
|
}
|
|
1968
2837
|
default: {
|
|
1969
|
-
const
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
if (field.placeholder)
|
|
1974
|
-
if (field.minLength !== void 0)
|
|
1975
|
-
if (field.maxLength !== void 0)
|
|
1976
|
-
if (typeof initial === "string")
|
|
1977
|
-
wrapper.appendChild(
|
|
1978
|
-
getValue2 = () =>
|
|
1979
|
-
onInput = (cb) =>
|
|
1980
|
-
setValue = (
|
|
1981
|
-
|
|
2838
|
+
const input2 = document.createElement("input");
|
|
2839
|
+
input2.type = field.widget === "url" ? "url" : field.widget === "email" ? "email" : "text";
|
|
2840
|
+
input2.className = "cancia-form-input";
|
|
2841
|
+
input2.style.cssText = `${INPUT_BASE} caret-color: ${v("accent")};`;
|
|
2842
|
+
if (field.placeholder) input2.placeholder = field.placeholder;
|
|
2843
|
+
if (field.minLength !== void 0) input2.minLength = field.minLength;
|
|
2844
|
+
if (field.maxLength !== void 0) input2.maxLength = field.maxLength;
|
|
2845
|
+
if (typeof initial === "string") input2.value = initial;
|
|
2846
|
+
wrapper.appendChild(input2);
|
|
2847
|
+
getValue2 = () => input2.value;
|
|
2848
|
+
onInput = (cb) => input2.addEventListener("input", cb);
|
|
2849
|
+
setValue = (v2) => {
|
|
2850
|
+
input2.value = v2;
|
|
1982
2851
|
};
|
|
1983
2852
|
break;
|
|
1984
2853
|
}
|
|
@@ -2007,18 +2876,14 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2007
2876
|
const itemSchema = field.of;
|
|
2008
2877
|
const container = document.createElement("div");
|
|
2009
2878
|
container.style.cssText = `
|
|
2010
|
-
|
|
2011
|
-
background: rgba(255,255,255,0.02);
|
|
2012
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
2013
|
-
border-radius: 10px;
|
|
2014
|
-
padding: 10px;
|
|
2879
|
+
${group()}
|
|
2015
2880
|
`;
|
|
2016
2881
|
const rowsWrap = document.createElement("div");
|
|
2017
|
-
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap:
|
|
2882
|
+
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap: ${v("space-2")};`;
|
|
2018
2883
|
container.appendChild(rowsWrap);
|
|
2019
2884
|
if (!itemSchema || depth >= MAX_FIELD_DEPTH) {
|
|
2020
2885
|
const note = document.createElement("div");
|
|
2021
|
-
note.style.cssText = `font-size:
|
|
2886
|
+
note.style.cssText = `font-size: ${v("text-xs")}; color: ${v("fg-muted")};`;
|
|
2022
2887
|
note.textContent = itemSchema ? "Nesting too deep to edit here." : "This array has no item schema.";
|
|
2023
2888
|
container.appendChild(note);
|
|
2024
2889
|
return { control: container, getValue: () => [], validate: () => ({ value: [], ok: true }) };
|
|
@@ -2028,11 +2893,7 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2028
2893
|
function makeRow(itemValue) {
|
|
2029
2894
|
const row = document.createElement("div");
|
|
2030
2895
|
row.style.cssText = `
|
|
2031
|
-
|
|
2032
|
-
background: rgba(255,255,255,0.02);
|
|
2033
|
-
border: 1px solid rgba(255,255,255,0.06);
|
|
2034
|
-
border-radius: 8px;
|
|
2035
|
-
padding: 8px;
|
|
2896
|
+
${groupRow()}
|
|
2036
2897
|
`;
|
|
2037
2898
|
const handle = document.createElement("div");
|
|
2038
2899
|
handle.textContent = "\u22EE\u22EE";
|
|
@@ -2040,10 +2901,12 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2040
2901
|
handle.draggable = true;
|
|
2041
2902
|
handle.style.cssText = `
|
|
2042
2903
|
cursor: grab; user-select: none;
|
|
2043
|
-
color:
|
|
2044
|
-
font-size:
|
|
2045
|
-
padding:
|
|
2904
|
+
color: ${v("fg-faint")};
|
|
2905
|
+
font-size: ${v("text-base")}; line-height: 1.2;
|
|
2906
|
+
padding: ${v("space-1")} 2px; flex-shrink: 0;
|
|
2046
2907
|
letter-spacing: -2px;
|
|
2908
|
+
opacity: 0;
|
|
2909
|
+
transition: opacity ${v("duration-fast")} ${v("ease")};
|
|
2047
2910
|
`;
|
|
2048
2911
|
const { wrapper, fieldState } = renderField(itemSchema, itemValue, depth + 1);
|
|
2049
2912
|
wrapper.style.marginBottom = "0";
|
|
@@ -2054,19 +2917,16 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2054
2917
|
removeBtn.textContent = "\xD7";
|
|
2055
2918
|
removeBtn.title = "Remove";
|
|
2056
2919
|
removeBtn.style.cssText = `
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2920
|
+
${button("danger")}
|
|
2921
|
+
flex-shrink: 0;
|
|
2922
|
+
border: 1px solid transparent;
|
|
2923
|
+
height: auto;
|
|
2060
2924
|
font-size: 16px; line-height: 1;
|
|
2061
|
-
padding: 2px 7px;
|
|
2062
|
-
|
|
2925
|
+
padding: 2px 7px;
|
|
2926
|
+
cursor: pointer;
|
|
2063
2927
|
`;
|
|
2064
|
-
removeBtn
|
|
2065
|
-
|
|
2066
|
-
});
|
|
2067
|
-
removeBtn.addEventListener("mouseleave", () => {
|
|
2068
|
-
removeBtn.style.background = "transparent";
|
|
2069
|
-
});
|
|
2928
|
+
attachHover(removeBtn, { bg: v("danger-soft") });
|
|
2929
|
+
attachRowReveal(row, [handle, removeBtn]);
|
|
2070
2930
|
row.appendChild(handle);
|
|
2071
2931
|
row.appendChild(wrapper);
|
|
2072
2932
|
row.appendChild(removeBtn);
|
|
@@ -2118,19 +2978,18 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2118
2978
|
const itemLabel = itemSchema.label || "item";
|
|
2119
2979
|
addBtn.textContent = `+ Add ${itemLabel.toLowerCase()}`;
|
|
2120
2980
|
addBtn.style.cssText = `
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2981
|
+
${button("ghost")}
|
|
2982
|
+
align-self: flex-start;
|
|
2983
|
+
background: transparent;
|
|
2984
|
+
color: ${v("fg-muted")};
|
|
2985
|
+
border: 0;
|
|
2986
|
+
height: auto;
|
|
2987
|
+
font-size: ${v("text-sm")};
|
|
2988
|
+
font-weight: 500;
|
|
2989
|
+
padding: 4px 0;
|
|
2990
|
+
cursor: pointer;
|
|
2127
2991
|
`;
|
|
2128
|
-
addBtn
|
|
2129
|
-
addBtn.style.background = "rgba(255,255,255,0.1)";
|
|
2130
|
-
});
|
|
2131
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
2132
|
-
addBtn.style.background = "rgba(255,255,255,0.05)";
|
|
2133
|
-
});
|
|
2992
|
+
attachHover(addBtn, { bg: v("surface-active") });
|
|
2134
2993
|
addBtn.addEventListener("click", () => addRow(defaultForField(itemSchema)));
|
|
2135
2994
|
container.appendChild(addBtn);
|
|
2136
2995
|
const collect = () => rows.map((r) => r.state.getValue());
|
|
@@ -2160,15 +3019,12 @@ function renderObjectField(field, initial, setOwnError, depth) {
|
|
|
2160
3019
|
const subInitial = initial && typeof initial === "object" && !Array.isArray(initial) ? initial : {};
|
|
2161
3020
|
const fieldset = document.createElement("div");
|
|
2162
3021
|
fieldset.style.cssText = `
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
2166
|
-
border-radius: 10px;
|
|
2167
|
-
padding: 10px 10px 0;
|
|
3022
|
+
${group()}
|
|
3023
|
+
gap: 2px;
|
|
2168
3024
|
`;
|
|
2169
3025
|
if (depth >= MAX_FIELD_DEPTH) {
|
|
2170
3026
|
const note = document.createElement("div");
|
|
2171
|
-
note.style.cssText = `font-size:
|
|
3027
|
+
note.style.cssText = `font-size: ${v("text-xs")}; color: ${v("fg-muted")}; padding-bottom: 10px;`;
|
|
2172
3028
|
note.textContent = "Nesting too deep to edit here.";
|
|
2173
3029
|
fieldset.appendChild(note);
|
|
2174
3030
|
return { control: fieldset, getValue: () => ({}), validate: () => ({ value: {}, ok: true }) };
|
|
@@ -2182,8 +3038,8 @@ function renderObjectField(field, initial, setOwnError, depth) {
|
|
|
2182
3038
|
const collect = () => {
|
|
2183
3039
|
const out = {};
|
|
2184
3040
|
for (const c of childStates) {
|
|
2185
|
-
const
|
|
2186
|
-
if (
|
|
3041
|
+
const v2 = c.getValue();
|
|
3042
|
+
if (v2 !== void 0 && v2 !== "") out[c.field.name] = v2;
|
|
2187
3043
|
}
|
|
2188
3044
|
return out;
|
|
2189
3045
|
};
|
|
@@ -2206,7 +3062,7 @@ function renderReferenceField(field, initial) {
|
|
|
2206
3062
|
const targetList = field.referenceList;
|
|
2207
3063
|
const sel = document.createElement("select");
|
|
2208
3064
|
sel.className = "cancia-form-select";
|
|
2209
|
-
sel.style.cssText = `${INPUT_BASE} appearance: none; padding-right: 30px; background-image:
|
|
3065
|
+
sel.style.cssText = `${INPUT_BASE} appearance: none; padding-right: 30px; background-image: ${SELECT_CHEVRON}; background-repeat: no-repeat; background-position: right 10px center; cursor: pointer;`;
|
|
2210
3066
|
const opt = (value, text, selected = false) => {
|
|
2211
3067
|
const o = document.createElement("option");
|
|
2212
3068
|
o.value = value;
|
|
@@ -2229,8 +3085,8 @@ function renderReferenceField(field, initial) {
|
|
|
2229
3085
|
const titleField = state.schemas[targetList]?.titleField;
|
|
2230
3086
|
const titleOf = (entry) => {
|
|
2231
3087
|
if (titleField) {
|
|
2232
|
-
const
|
|
2233
|
-
if (typeof
|
|
3088
|
+
const v2 = entry.data[titleField];
|
|
3089
|
+
if (typeof v2 === "string" && v2.trim()) return v2;
|
|
2234
3090
|
}
|
|
2235
3091
|
return `(untitled \xB7 ${entry.id})`;
|
|
2236
3092
|
};
|
|
@@ -2277,25 +3133,17 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2277
3133
|
];
|
|
2278
3134
|
const container = document.createElement("div");
|
|
2279
3135
|
container.style.cssText = `
|
|
2280
|
-
|
|
2281
|
-
background: rgba(255,255,255,0.02);
|
|
2282
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
2283
|
-
border-radius: 10px;
|
|
2284
|
-
padding: 10px;
|
|
3136
|
+
${group()}
|
|
2285
3137
|
`;
|
|
2286
3138
|
const rowsWrap = document.createElement("div");
|
|
2287
|
-
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap:
|
|
3139
|
+
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap: ${v("space-2")};`;
|
|
2288
3140
|
container.appendChild(rowsWrap);
|
|
2289
3141
|
const rows = [];
|
|
2290
3142
|
let dragging = null;
|
|
2291
3143
|
function makeRow(initialRow) {
|
|
2292
3144
|
const row = document.createElement("div");
|
|
2293
3145
|
row.style.cssText = `
|
|
2294
|
-
|
|
2295
|
-
background: rgba(255,255,255,0.02);
|
|
2296
|
-
border: 1px solid rgba(255,255,255,0.06);
|
|
2297
|
-
border-radius: 8px;
|
|
2298
|
-
padding: 8px;
|
|
3146
|
+
${groupRow()}
|
|
2299
3147
|
`;
|
|
2300
3148
|
const handle = document.createElement("div");
|
|
2301
3149
|
handle.textContent = "\u22EE\u22EE";
|
|
@@ -2303,25 +3151,33 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2303
3151
|
handle.draggable = true;
|
|
2304
3152
|
handle.style.cssText = `
|
|
2305
3153
|
cursor: grab; user-select: none;
|
|
2306
|
-
color:
|
|
2307
|
-
font-size:
|
|
2308
|
-
padding:
|
|
3154
|
+
color: ${v("fg-faint")};
|
|
3155
|
+
font-size: ${v("text-base")}; line-height: 1.2;
|
|
3156
|
+
padding: ${v("space-1")} 2px; flex-shrink: 0;
|
|
2309
3157
|
letter-spacing: -2px;
|
|
3158
|
+
opacity: 0;
|
|
3159
|
+
transition: opacity ${v("duration-fast")} ${v("ease")};
|
|
2310
3160
|
`;
|
|
2311
3161
|
const main = document.createElement("div");
|
|
2312
3162
|
main.style.cssText = `flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px;`;
|
|
2313
3163
|
const ta = document.createElement("textarea");
|
|
2314
3164
|
ta.className = "cancia-form-textarea";
|
|
2315
|
-
ta.style.cssText = `${INPUT_BASE} resize: vertical; min-height: 54px; max-height: 240px; caret-color: ${
|
|
3165
|
+
ta.style.cssText = `${INPUT_BASE} resize: vertical; min-height: 54px; max-height: 240px; caret-color: ${v("accent")};`;
|
|
2316
3166
|
ta.rows = 2;
|
|
2317
3167
|
ta.placeholder = "Text \u2014 use **bold**, *italic*, [label](https://\u2026)";
|
|
2318
3168
|
ta.value = initialRow.text;
|
|
2319
3169
|
const controls = document.createElement("div");
|
|
2320
|
-
controls.style.cssText = `
|
|
3170
|
+
controls.style.cssText = `
|
|
3171
|
+
display: flex; gap: 6px;
|
|
3172
|
+
max-height: 0; opacity: 0; overflow: hidden;
|
|
3173
|
+
transition: max-height ${v("duration-fast")} ${v("ease-out")},
|
|
3174
|
+
opacity ${v("duration-fast")} ${v("ease")};
|
|
3175
|
+
`;
|
|
3176
|
+
controls.dataset.canciaCollapsible = "1";
|
|
2321
3177
|
const styleSel = document.createElement("select");
|
|
2322
3178
|
styleSel.className = "cancia-form-select";
|
|
2323
|
-
styleSel.style.cssText = `${INPUT_BASE} width: auto; flex: 1; appearance: none; padding: 5px 26px 5px 9px; font-size:
|
|
2324
|
-
for (const s of
|
|
3179
|
+
styleSel.style.cssText = `${INPUT_BASE} width: auto; flex: 1; appearance: none; padding: 5px 26px 5px 9px; font-size: ${v("text-xs")}; background-image: ${SELECT_CHEVRON}; background-repeat: no-repeat; background-position: right 9px center; cursor: pointer;`;
|
|
3180
|
+
for (const s of PT_STYLES2) {
|
|
2325
3181
|
const o = document.createElement("option");
|
|
2326
3182
|
o.value = s;
|
|
2327
3183
|
o.textContent = STYLE_LABELS[s];
|
|
@@ -2331,10 +3187,10 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2331
3187
|
const listSel = document.createElement("select");
|
|
2332
3188
|
listSel.className = "cancia-form-select";
|
|
2333
3189
|
listSel.style.cssText = styleSel.style.cssText;
|
|
2334
|
-
for (const { value, label } of LIST_LABELS) {
|
|
3190
|
+
for (const { value, label: label2 } of LIST_LABELS) {
|
|
2335
3191
|
const o = document.createElement("option");
|
|
2336
3192
|
o.value = value;
|
|
2337
|
-
o.textContent =
|
|
3193
|
+
o.textContent = label2;
|
|
2338
3194
|
if ((initialRow.listItem ?? "") === value) o.selected = true;
|
|
2339
3195
|
listSel.appendChild(o);
|
|
2340
3196
|
}
|
|
@@ -2347,19 +3203,16 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2347
3203
|
removeBtn.textContent = "\xD7";
|
|
2348
3204
|
removeBtn.title = "Remove block";
|
|
2349
3205
|
removeBtn.style.cssText = `
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
3206
|
+
${button("danger")}
|
|
3207
|
+
flex-shrink: 0;
|
|
3208
|
+
border: 1px solid transparent;
|
|
3209
|
+
height: auto;
|
|
2353
3210
|
font-size: 16px; line-height: 1;
|
|
2354
|
-
padding: 2px 7px;
|
|
2355
|
-
|
|
3211
|
+
padding: 2px 7px;
|
|
3212
|
+
cursor: pointer;
|
|
2356
3213
|
`;
|
|
2357
|
-
removeBtn
|
|
2358
|
-
|
|
2359
|
-
});
|
|
2360
|
-
removeBtn.addEventListener("mouseleave", () => {
|
|
2361
|
-
removeBtn.style.background = "transparent";
|
|
2362
|
-
});
|
|
3214
|
+
attachHover(removeBtn, { bg: v("danger-soft") });
|
|
3215
|
+
attachRowReveal(row, [handle, removeBtn, controls]);
|
|
2363
3216
|
row.appendChild(handle);
|
|
2364
3217
|
row.appendChild(main);
|
|
2365
3218
|
row.appendChild(removeBtn);
|
|
@@ -2414,8 +3267,8 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2414
3267
|
rowsWrap.appendChild(rec.el);
|
|
2415
3268
|
}
|
|
2416
3269
|
const initialRows = (() => {
|
|
2417
|
-
const parsed =
|
|
2418
|
-
if (parsed.success && parsed.data.length > 0) return
|
|
3270
|
+
const parsed = portableTextSubsetSchema2.safeParse(initial);
|
|
3271
|
+
if (parsed.success && parsed.data.length > 0) return portableTextToRows2(parsed.data);
|
|
2419
3272
|
return [{ text: "", style: "normal" }];
|
|
2420
3273
|
})();
|
|
2421
3274
|
for (const r of initialRows) addRow(r);
|
|
@@ -2423,24 +3276,23 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2423
3276
|
addBtn.type = "button";
|
|
2424
3277
|
addBtn.textContent = "+ Add block";
|
|
2425
3278
|
addBtn.style.cssText = `
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
3279
|
+
${button("ghost")}
|
|
3280
|
+
align-self: flex-start;
|
|
3281
|
+
background: transparent;
|
|
3282
|
+
color: ${v("fg-muted")};
|
|
3283
|
+
border: 0;
|
|
3284
|
+
height: auto;
|
|
3285
|
+
font-size: ${v("text-sm")};
|
|
3286
|
+
font-weight: 500;
|
|
3287
|
+
padding: 4px 0;
|
|
3288
|
+
cursor: pointer;
|
|
2432
3289
|
`;
|
|
2433
|
-
addBtn
|
|
2434
|
-
addBtn.style.background = "rgba(255,255,255,0.1)";
|
|
2435
|
-
});
|
|
2436
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
2437
|
-
addBtn.style.background = "rgba(255,255,255,0.05)";
|
|
2438
|
-
});
|
|
3290
|
+
attachHover(addBtn, { bg: v("surface-active") });
|
|
2439
3291
|
addBtn.addEventListener("click", () => addRow({ text: "", style: "normal" }));
|
|
2440
3292
|
container.appendChild(addBtn);
|
|
2441
3293
|
const serialise = () => {
|
|
2442
3294
|
const editorRows = rows.map((r) => r.read()).filter((r) => r.text.trim() !== "");
|
|
2443
|
-
return
|
|
3295
|
+
return rowsToPortableText2(editorRows);
|
|
2444
3296
|
};
|
|
2445
3297
|
return {
|
|
2446
3298
|
control: container,
|
|
@@ -2448,7 +3300,7 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2448
3300
|
validate: () => {
|
|
2449
3301
|
setOwnError(null);
|
|
2450
3302
|
const value = serialise();
|
|
2451
|
-
const parsed =
|
|
3303
|
+
const parsed = portableTextSubsetSchema2.safeParse(value);
|
|
2452
3304
|
if (!parsed.success) {
|
|
2453
3305
|
setOwnError("This rich-text content is not valid. Check links and formatting.");
|
|
2454
3306
|
return { value, ok: false };
|
|
@@ -2568,77 +3420,106 @@ function openEntryModal(opts) {
|
|
|
2568
3420
|
const isEdit = opts.entry !== null;
|
|
2569
3421
|
const isTranslate = !isEdit && (opts.translateFromEntry ?? null) !== null;
|
|
2570
3422
|
document.documentElement.style.setProperty("--cancia-accent-border", accentBorder());
|
|
2571
|
-
const backdrop = document.createElement("div");
|
|
2572
|
-
backdrop.dataset.canciaModalBackdrop = "1";
|
|
2573
|
-
backdrop.style.cssText = `
|
|
2574
|
-
position: fixed; inset: 0;
|
|
2575
|
-
background: rgba(8,8,10,0.55);
|
|
2576
|
-
backdrop-filter: blur(3px);
|
|
2577
|
-
-webkit-backdrop-filter: blur(3px);
|
|
2578
|
-
z-index: ${BACKDROP_Z2};
|
|
2579
|
-
opacity: 0;
|
|
2580
|
-
transition: opacity 0.2s ease-out;
|
|
2581
|
-
`;
|
|
2582
|
-
document.body.appendChild(backdrop);
|
|
2583
|
-
requestAnimationFrame(() => {
|
|
2584
|
-
backdrop.style.opacity = "1";
|
|
2585
|
-
});
|
|
2586
|
-
backdropEl2 = backdrop;
|
|
2587
3423
|
const modal = document.createElement("div");
|
|
2588
3424
|
modal.dataset.canciaModal = "1";
|
|
2589
|
-
modal
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
3425
|
+
markUi(modal);
|
|
3426
|
+
document.documentElement.style.setProperty("--cancia-accent-border", accentBorder());
|
|
3427
|
+
mountedInPanel = pushPanelView(modal);
|
|
3428
|
+
if (!mountedInPanel) {
|
|
3429
|
+
const backdrop = document.createElement("div");
|
|
3430
|
+
backdrop.dataset.canciaModalBackdrop = "1";
|
|
3431
|
+
markUi(backdrop);
|
|
3432
|
+
backdrop.style.cssText = `
|
|
3433
|
+
position: fixed; inset: 0;
|
|
3434
|
+
background: rgba(8,8,10,0.45);
|
|
3435
|
+
backdrop-filter: blur(3px);
|
|
3436
|
+
-webkit-backdrop-filter: blur(3px);
|
|
3437
|
+
z-index: ${BACKDROP_Z2};
|
|
3438
|
+
opacity: 0;
|
|
3439
|
+
transition: opacity ${v("duration")} ${v("ease-out")};
|
|
3440
|
+
`;
|
|
3441
|
+
document.body.appendChild(backdrop);
|
|
3442
|
+
requestAnimationFrame(() => {
|
|
3443
|
+
backdrop.style.opacity = "1";
|
|
3444
|
+
});
|
|
3445
|
+
backdropEl2 = backdrop;
|
|
3446
|
+
backdrop.addEventListener("click", () => closeEntryModal());
|
|
3447
|
+
modal.style.cssText = `
|
|
3448
|
+
${surface(3)}
|
|
3449
|
+
position: fixed;
|
|
3450
|
+
top: 50%; left: 50%;
|
|
3451
|
+
width: min(480px, calc(100vw - 32px));
|
|
3452
|
+
max-height: min(680px, calc(100vh - 48px));
|
|
3453
|
+
display: flex; flex-direction: column;
|
|
3454
|
+
box-shadow: ${v("shadow-lg")};
|
|
3455
|
+
z-index: ${MODAL_Z};
|
|
3456
|
+
overflow: hidden;
|
|
3457
|
+
opacity: 0;
|
|
3458
|
+
transform: translate(-50%, calc(-50% + 8px)) scale(0.985);
|
|
3459
|
+
transition: opacity ${v("duration")} ${v("ease")}, transform ${v("duration")} ${v("ease")};
|
|
3460
|
+
`;
|
|
3461
|
+
document.body.appendChild(modal);
|
|
3462
|
+
requestAnimationFrame(() => {
|
|
3463
|
+
modal.style.opacity = "1";
|
|
3464
|
+
modal.style.transform = "translate(-50%, -50%) scale(1)";
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
2609
3467
|
modalEl = modal;
|
|
2610
3468
|
const header = document.createElement("div");
|
|
2611
3469
|
header.style.cssText = `
|
|
2612
|
-
display: flex; align-items: center;
|
|
2613
|
-
padding: 14px
|
|
2614
|
-
border-bottom: 1px solid
|
|
3470
|
+
display: flex; align-items: center; gap: ${v("space-2")};
|
|
3471
|
+
padding: 14px ${v("space-4")} ${v("space-3")};
|
|
3472
|
+
border-bottom: 1px solid ${v("border")};
|
|
2615
3473
|
flex-shrink: 0;
|
|
2616
3474
|
`;
|
|
3475
|
+
if (mountedInPanel) {
|
|
3476
|
+
const backBtn = document.createElement("button");
|
|
3477
|
+
backBtn.type = "button";
|
|
3478
|
+
backBtn.setAttribute("aria-label", "Back to list");
|
|
3479
|
+
backBtn.title = "Back to list";
|
|
3480
|
+
backBtn.style.cssText = `
|
|
3481
|
+
${iconButton(28)}
|
|
3482
|
+
flex-shrink: 0; padding: 0; cursor: pointer;
|
|
3483
|
+
background: transparent; border: 0;
|
|
3484
|
+
`;
|
|
3485
|
+
backBtn.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
|
3486
|
+
<path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/>
|
|
3487
|
+
</svg>`;
|
|
3488
|
+
attachHover(backBtn, { bg: v("surface-hover"), color: v("fg-strong") });
|
|
3489
|
+
attachPress(backBtn);
|
|
3490
|
+
backBtn.addEventListener("click", () => closeEntryModal());
|
|
3491
|
+
header.appendChild(backBtn);
|
|
3492
|
+
}
|
|
2617
3493
|
const titleWrap = document.createElement("div");
|
|
2618
|
-
titleWrap.style.cssText = `display: flex; flex-direction: column; gap: 2px; min-width: 0;`;
|
|
3494
|
+
titleWrap.style.cssText = `display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1;`;
|
|
2619
3495
|
const eyebrow = document.createElement("span");
|
|
2620
|
-
eyebrow.style.cssText =
|
|
2621
|
-
font-size: 10px; font-weight: 600;
|
|
2622
|
-
color: rgba(255,255,255,0.32);
|
|
2623
|
-
letter-spacing: 0.08em; text-transform: uppercase;
|
|
2624
|
-
`;
|
|
3496
|
+
eyebrow.style.cssText = `${label()} display: inline;`;
|
|
2625
3497
|
const action = isEdit ? "Edit" : isTranslate ? "Translate" : "New";
|
|
2626
3498
|
eyebrow.textContent = `${action} ${opts.schema.labelSingular.toLowerCase()} \xB7 ${opts.locale}`;
|
|
2627
3499
|
const titleEl = document.createElement("span");
|
|
2628
3500
|
titleEl.style.cssText = `
|
|
2629
3501
|
font-size: 14px; font-weight: 600;
|
|
2630
|
-
color:
|
|
3502
|
+
color: ${v("fg-strong")};
|
|
2631
3503
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
2632
3504
|
`;
|
|
2633
3505
|
titleEl.textContent = opts.schema.label;
|
|
2634
3506
|
titleWrap.appendChild(eyebrow);
|
|
2635
3507
|
titleWrap.appendChild(titleEl);
|
|
2636
3508
|
header.appendChild(titleWrap);
|
|
2637
|
-
header.appendChild(
|
|
3509
|
+
header.appendChild(
|
|
3510
|
+
makeCloseButton(() => {
|
|
3511
|
+
if (mountedInPanel) {
|
|
3512
|
+
closeEntryModal();
|
|
3513
|
+
closeListPanel();
|
|
3514
|
+
} else {
|
|
3515
|
+
closeEntryModal();
|
|
3516
|
+
}
|
|
3517
|
+
})
|
|
3518
|
+
);
|
|
2638
3519
|
modal.appendChild(header);
|
|
2639
3520
|
const body = document.createElement("div");
|
|
2640
3521
|
body.style.cssText = `
|
|
2641
|
-
padding: 14px
|
|
3522
|
+
padding: 14px ${v("space-4")} ${v("space-1")};
|
|
2642
3523
|
overflow-y: auto;
|
|
2643
3524
|
flex: 1 1 auto;
|
|
2644
3525
|
min-height: 0;
|
|
@@ -2647,14 +3528,14 @@ function openEntryModal(opts) {
|
|
|
2647
3528
|
const formError = document.createElement("div");
|
|
2648
3529
|
formError.style.cssText = `
|
|
2649
3530
|
display: none;
|
|
2650
|
-
background:
|
|
2651
|
-
color:
|
|
2652
|
-
border: 1px solid
|
|
2653
|
-
border-radius:
|
|
3531
|
+
background: ${v("danger-soft")};
|
|
3532
|
+
color: ${v("danger")};
|
|
3533
|
+
border: 1px solid ${v("danger-soft")};
|
|
3534
|
+
border-radius: ${v("radius-sm")};
|
|
2654
3535
|
padding: 9px 11px;
|
|
2655
|
-
font-size:
|
|
3536
|
+
font-size: ${v("text-sm")};
|
|
2656
3537
|
line-height: 1.4;
|
|
2657
|
-
margin-bottom:
|
|
3538
|
+
margin-bottom: ${v("space-3")};
|
|
2658
3539
|
`;
|
|
2659
3540
|
body.appendChild(formError);
|
|
2660
3541
|
function showFormError(msg) {
|
|
@@ -2668,14 +3549,14 @@ function openEntryModal(opts) {
|
|
|
2668
3549
|
if (isTranslate) {
|
|
2669
3550
|
const banner = document.createElement("div");
|
|
2670
3551
|
banner.style.cssText = `
|
|
2671
|
-
background:
|
|
2672
|
-
color:
|
|
2673
|
-
border: 1px solid
|
|
2674
|
-
border-radius:
|
|
3552
|
+
background: ${v("warning-soft")};
|
|
3553
|
+
color: ${v("warning")};
|
|
3554
|
+
border: 1px solid ${v("warning-soft")};
|
|
3555
|
+
border-radius: ${v("radius-sm")};
|
|
2675
3556
|
padding: 9px 11px;
|
|
2676
|
-
font-size:
|
|
3557
|
+
font-size: ${v("text-sm")};
|
|
2677
3558
|
line-height: 1.4;
|
|
2678
|
-
margin-bottom:
|
|
3559
|
+
margin-bottom: ${v("space-3")};
|
|
2679
3560
|
`;
|
|
2680
3561
|
const src = opts.translateFromEntry;
|
|
2681
3562
|
banner.textContent = `Translating from ${src.locale} into ${opts.locale}. Fields are pre-filled from the source.`;
|
|
@@ -2694,32 +3575,26 @@ function openEntryModal(opts) {
|
|
|
2694
3575
|
footer.style.cssText = `
|
|
2695
3576
|
display: flex; align-items: center; justify-content: space-between;
|
|
2696
3577
|
gap: 10px;
|
|
2697
|
-
padding:
|
|
2698
|
-
border-top: 1px solid
|
|
2699
|
-
background:
|
|
3578
|
+
padding: ${v("space-3")} ${v("space-4")};
|
|
3579
|
+
border-top: 1px solid ${v("border")};
|
|
3580
|
+
background: ${v("surface-raised")};
|
|
2700
3581
|
flex-shrink: 0;
|
|
2701
3582
|
`;
|
|
2702
3583
|
const leftActions = document.createElement("div");
|
|
2703
3584
|
const rightActions = document.createElement("div");
|
|
2704
|
-
rightActions.style.cssText = `display: flex; gap:
|
|
3585
|
+
rightActions.style.cssText = `display: flex; gap: ${v("space-2")};`;
|
|
2705
3586
|
if (isEdit) {
|
|
2706
3587
|
const deleteBtn = document.createElement("button");
|
|
2707
3588
|
deleteBtn.type = "button";
|
|
2708
3589
|
deleteBtn.style.cssText = `
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
transition: background 0.15s;
|
|
3590
|
+
${button("danger")}
|
|
3591
|
+
border: 1px solid transparent;
|
|
3592
|
+
height: auto;
|
|
3593
|
+
padding: 6px 10px;
|
|
3594
|
+
cursor: pointer;
|
|
2715
3595
|
`;
|
|
2716
3596
|
deleteBtn.textContent = "Delete";
|
|
2717
|
-
deleteBtn
|
|
2718
|
-
deleteBtn.style.background = "rgba(255,135,134,0.08)";
|
|
2719
|
-
});
|
|
2720
|
-
deleteBtn.addEventListener("mouseleave", () => {
|
|
2721
|
-
deleteBtn.style.background = "transparent";
|
|
2722
|
-
});
|
|
3597
|
+
attachHover(deleteBtn, { bg: v("danger-soft") });
|
|
2723
3598
|
deleteBtn.addEventListener("click", async () => {
|
|
2724
3599
|
if (!confirm(`Delete the ${opts.locale} version of this ${opts.schema.labelSingular.toLowerCase()}? This can't be undone.`)) return;
|
|
2725
3600
|
deleteBtn.disabled = true;
|
|
@@ -2737,25 +3612,17 @@ function openEntryModal(opts) {
|
|
|
2737
3612
|
const cancelBtn = document.createElement("button");
|
|
2738
3613
|
cancelBtn.type = "button";
|
|
2739
3614
|
cancelBtn.style.cssText = `
|
|
2740
|
-
|
|
2741
|
-
background:
|
|
2742
|
-
border: 1px solid
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
transition: background 0.15s, color 0.15s;
|
|
3615
|
+
${button("ghost")}
|
|
3616
|
+
background: ${v("surface-raised")};
|
|
3617
|
+
border: 1px solid ${v("border")};
|
|
3618
|
+
height: auto;
|
|
3619
|
+
padding: 7px 14px;
|
|
3620
|
+
cursor: pointer;
|
|
2747
3621
|
`;
|
|
2748
3622
|
cancelBtn.textContent = "Cancel";
|
|
2749
|
-
cancelBtn
|
|
2750
|
-
cancelBtn.style.background = "rgba(255,255,255,0.08)";
|
|
2751
|
-
cancelBtn.style.color = "rgba(255,255,255,0.9)";
|
|
2752
|
-
});
|
|
2753
|
-
cancelBtn.addEventListener("mouseleave", () => {
|
|
2754
|
-
cancelBtn.style.background = "rgba(255,255,255,0.04)";
|
|
2755
|
-
cancelBtn.style.color = "rgba(255,255,255,0.75)";
|
|
2756
|
-
});
|
|
3623
|
+
attachHover(cancelBtn, { bg: v("surface-hover"), color: v("fg-strong") });
|
|
2757
3624
|
cancelBtn.addEventListener("click", () => closeEntryModal());
|
|
2758
|
-
const saveBtn = makePrimaryButton(isEdit ? "Save" : "Create",
|
|
3625
|
+
const saveBtn = makePrimaryButton(isEdit ? "Save" : "Create", accent3());
|
|
2759
3626
|
saveBtn.addEventListener("click", async () => {
|
|
2760
3627
|
clearFormError();
|
|
2761
3628
|
const { data, ok } = preValidate(fieldStates);
|
|
@@ -2798,7 +3665,6 @@ function openEntryModal(opts) {
|
|
|
2798
3665
|
footer.appendChild(leftActions);
|
|
2799
3666
|
footer.appendChild(rightActions);
|
|
2800
3667
|
modal.appendChild(footer);
|
|
2801
|
-
backdrop.addEventListener("click", () => closeEntryModal());
|
|
2802
3668
|
escListener = (e) => {
|
|
2803
3669
|
if (e.key === "Escape") {
|
|
2804
3670
|
e.stopPropagation();
|
|
@@ -2813,11 +3679,18 @@ function openEntryModal(opts) {
|
|
|
2813
3679
|
}
|
|
2814
3680
|
function closeEntryModal() {
|
|
2815
3681
|
if (modalEl) {
|
|
2816
|
-
modalEl.style.animation = "cancia-modal-out 0.16s cubic-bezier(0.7, 0, 0.84, 0) forwards";
|
|
2817
3682
|
const el = modalEl;
|
|
2818
|
-
setTimeout(() => el.remove(), 160);
|
|
2819
3683
|
modalEl = null;
|
|
3684
|
+
if (mountedInPanel) {
|
|
3685
|
+
popPanelView(el);
|
|
3686
|
+
} else {
|
|
3687
|
+
el.style.transition = `opacity 0.16s ${v("ease-out")}, transform 0.16s ${v("ease-out")}`;
|
|
3688
|
+
el.style.opacity = "0";
|
|
3689
|
+
el.style.transform = "translate(-50%, calc(-50% + 8px)) scale(0.985)";
|
|
3690
|
+
setTimeout(() => el.remove(), 180);
|
|
3691
|
+
}
|
|
2820
3692
|
}
|
|
3693
|
+
mountedInPanel = false;
|
|
2821
3694
|
if (backdropEl2) {
|
|
2822
3695
|
const el = backdropEl2;
|
|
2823
3696
|
el.style.opacity = "0";
|
|
@@ -2838,13 +3711,12 @@ var toolbarEl = null;
|
|
|
2838
3711
|
var pendingPanelEl = null;
|
|
2839
3712
|
var isExpanded = false;
|
|
2840
3713
|
var expandedEscListener = null;
|
|
2841
|
-
|
|
2842
|
-
return state.config?.accentColor ?? "#6366f1";
|
|
2843
|
-
}
|
|
3714
|
+
var revealTimer = null;
|
|
2844
3715
|
var styleInjected4 = false;
|
|
2845
3716
|
function injectStyles4() {
|
|
2846
3717
|
if (styleInjected4) return;
|
|
2847
3718
|
styleInjected4 = true;
|
|
3719
|
+
injectBaseStyles(state.config?.accentColor, state.config?.toolbarAccent === true);
|
|
2848
3720
|
const s = document.createElement("style");
|
|
2849
3721
|
s.textContent = `
|
|
2850
3722
|
@keyframes cancia-enter {
|
|
@@ -2885,20 +3757,30 @@ function injectStyles4() {
|
|
|
2885
3757
|
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
2886
3758
|
}
|
|
2887
3759
|
[data-cancia-toolbar] * { box-sizing: border-box; }
|
|
2888
|
-
[data-cancia-toolbar] button:active:not(:disabled) { transform: scale(0.92) !important; }
|
|
2889
3760
|
[data-cancia-popup] * { box-sizing: border-box; }
|
|
2890
|
-
|
|
3761
|
+
/* Press feedback for popup buttons. The TOOLBAR's buttons deliberately do
|
|
3762
|
+
NOT use :active \u2014 they use attachPress() on pointerdown instead, because
|
|
3763
|
+
:active only lands after the browser's own hit-test and reads as lag.
|
|
3764
|
+
An !important here would also override that inline transform. */
|
|
3765
|
+
[data-cancia-popup] button:active:not(:disabled) { transform: scale(0.96); }
|
|
2891
3766
|
/* Protect stroke-based icons from host page "svg { fill: currentColor }" rules */
|
|
2892
3767
|
[data-cancia-toolbar] svg[fill="none"] { fill: none !important; }
|
|
2893
3768
|
[data-cancia-toolbar] svg[fill="none"] :not([fill]) { fill: none !important; }
|
|
2894
3769
|
[data-cancia-popup] svg[fill="none"] { fill: none !important; }
|
|
2895
3770
|
[data-cancia-popup] svg[fill="none"] :not([fill]) { fill: none !important; }
|
|
2896
|
-
/* Reset cosmetic host CSS leaking into toolbar buttons
|
|
3771
|
+
/* Reset cosmetic host CSS leaking into toolbar buttons.
|
|
3772
|
+
NOTE: font-* and color are deliberately NOT unset here \u2014 the buttons now
|
|
3773
|
+
carry visible text labels, and unsetting those would strip the label's
|
|
3774
|
+
typography back to the UA default. The scoped reset in styles.ts already
|
|
3775
|
+
neutralises host typography for [data-cancia-ui] subtrees. */
|
|
2897
3776
|
[data-cancia-toolbar] :where(button) {
|
|
2898
3777
|
background: unset; border: unset; border-radius: unset; padding: unset;
|
|
2899
|
-
margin: unset;
|
|
2900
|
-
|
|
2901
|
-
|
|
3778
|
+
margin: unset; box-shadow: unset; outline: unset;
|
|
3779
|
+
}
|
|
3780
|
+
/* Labels must never be transformed by a host \`button { text-transform }\`. */
|
|
3781
|
+
[data-cancia-toolbar] [data-cancia-label] {
|
|
3782
|
+
text-transform: none;
|
|
3783
|
+
letter-spacing: normal;
|
|
2902
3784
|
}
|
|
2903
3785
|
`;
|
|
2904
3786
|
document.head.appendChild(s);
|
|
@@ -2910,27 +3792,28 @@ var tooltipVisible = false;
|
|
|
2910
3792
|
function getOrCreateBtnTooltip() {
|
|
2911
3793
|
if (!btnTooltipEl) {
|
|
2912
3794
|
btnTooltipEl = document.createElement("div");
|
|
3795
|
+
markUi(btnTooltipEl);
|
|
2913
3796
|
btnTooltipEl.style.cssText = `
|
|
2914
3797
|
position: fixed;
|
|
2915
3798
|
pointer-events: none;
|
|
2916
|
-
z-index:
|
|
2917
|
-
font-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
backdrop-filter: blur
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
3799
|
+
z-index: ${v("z-panel")};
|
|
3800
|
+
font-size: ${v("text-xs")}; font-weight: 500; letter-spacing: 0.02em;
|
|
3801
|
+
color: ${v("fg-strong")};
|
|
3802
|
+
background: ${v("surface-1")};
|
|
3803
|
+
backdrop-filter: ${v("blur")};
|
|
3804
|
+
-webkit-backdrop-filter: ${v("blur")};
|
|
3805
|
+
border: 1px solid ${v("border-strong")};
|
|
3806
|
+
padding: ${v("space-1")} ${v("space-2")};
|
|
3807
|
+
border-radius: ${v("radius-sm")};
|
|
2925
3808
|
white-space: nowrap;
|
|
2926
|
-
box-shadow:
|
|
3809
|
+
box-shadow: ${v("shadow-sm")};
|
|
2927
3810
|
display: none;
|
|
2928
3811
|
`;
|
|
2929
3812
|
document.body.appendChild(btnTooltipEl);
|
|
2930
3813
|
}
|
|
2931
3814
|
return btnTooltipEl;
|
|
2932
3815
|
}
|
|
2933
|
-
function showBtnTooltip(btn,
|
|
3816
|
+
function showBtnTooltip(btn, label2) {
|
|
2934
3817
|
if (tooltipHideTimer) {
|
|
2935
3818
|
clearTimeout(tooltipHideTimer);
|
|
2936
3819
|
tooltipHideTimer = null;
|
|
@@ -2942,9 +3825,9 @@ function showBtnTooltip(btn, label) {
|
|
|
2942
3825
|
const doShow = () => {
|
|
2943
3826
|
tooltipVisible = true;
|
|
2944
3827
|
const tooltip = getOrCreateBtnTooltip();
|
|
2945
|
-
tooltip.textContent =
|
|
3828
|
+
tooltip.textContent = label2;
|
|
2946
3829
|
tooltip.style.display = "block";
|
|
2947
|
-
tooltip.style.animation =
|
|
3830
|
+
tooltip.style.animation = `cancia-tooltip-in ${v("duration-fast")} ${v("ease-out")} both`;
|
|
2948
3831
|
const rect = btn.getBoundingClientRect();
|
|
2949
3832
|
const tooltipH = 26;
|
|
2950
3833
|
const gap = 8;
|
|
@@ -2972,37 +3855,39 @@ function buildToolbar() {
|
|
|
2972
3855
|
injectStyles4();
|
|
2973
3856
|
const bar = document.createElement("div");
|
|
2974
3857
|
bar.dataset.canciaToolbar = "1";
|
|
3858
|
+
markUi(bar);
|
|
2975
3859
|
bar.style.cssText = `
|
|
2976
3860
|
position: fixed;
|
|
2977
|
-
bottom:
|
|
2978
|
-
right:
|
|
2979
|
-
z-index:
|
|
2980
|
-
width:
|
|
2981
|
-
height:
|
|
2982
|
-
border-radius:
|
|
2983
|
-
background:
|
|
2984
|
-
backdrop-filter: blur
|
|
2985
|
-
-webkit-backdrop-filter: blur
|
|
2986
|
-
border: 1px solid
|
|
2987
|
-
box-shadow:
|
|
2988
|
-
font-
|
|
2989
|
-
|
|
2990
|
-
color: #f0f0f0;
|
|
3861
|
+
bottom: ${v("space-6")};
|
|
3862
|
+
right: ${v("space-6")};
|
|
3863
|
+
z-index: ${v("z-bar")};
|
|
3864
|
+
width: 52px;
|
|
3865
|
+
height: 52px;
|
|
3866
|
+
border-radius: ${v("radius-full")};
|
|
3867
|
+
background: ${v("surface-1")};
|
|
3868
|
+
backdrop-filter: ${v("blur")};
|
|
3869
|
+
-webkit-backdrop-filter: ${v("blur")};
|
|
3870
|
+
border: 1px solid ${v("border")};
|
|
3871
|
+
box-shadow: ${v("shadow")};
|
|
3872
|
+
font-size: ${v("text-base")};
|
|
3873
|
+
color: ${v("fg")};
|
|
2991
3874
|
user-select: none;
|
|
2992
3875
|
cursor: pointer;
|
|
2993
3876
|
overflow: hidden;
|
|
2994
3877
|
display: flex;
|
|
2995
3878
|
align-items: center;
|
|
2996
3879
|
justify-content: center;
|
|
2997
|
-
transition: width
|
|
2998
|
-
|
|
3880
|
+
transition: width ${v("duration-slow")} ${v("ease")},
|
|
3881
|
+
border-radius ${v("duration-slow")} ${v("ease")};
|
|
3882
|
+
animation: cancia-enter ${v("duration-slow")} ${v("ease-spring")} both;
|
|
2999
3883
|
`;
|
|
3000
3884
|
const collapseIcon = document.createElement("div");
|
|
3001
3885
|
collapseIcon.style.cssText = `
|
|
3002
3886
|
position: absolute;
|
|
3003
3887
|
display: flex; align-items: center; justify-content: center;
|
|
3004
|
-
color:
|
|
3005
|
-
transition: opacity
|
|
3888
|
+
color: ${v("fg")};
|
|
3889
|
+
transition: opacity ${v("duration-fast")} ${v("ease")},
|
|
3890
|
+
transform ${v("duration-fast")} ${v("ease")};
|
|
3006
3891
|
pointer-events: none;
|
|
3007
3892
|
`;
|
|
3008
3893
|
collapseIcon.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -3013,16 +3898,17 @@ function buildToolbar() {
|
|
|
3013
3898
|
controls.style.cssText = `
|
|
3014
3899
|
display: flex;
|
|
3015
3900
|
align-items: center;
|
|
3016
|
-
gap:
|
|
3901
|
+
gap: ${v("space-1")};
|
|
3017
3902
|
padding: 5px;
|
|
3018
3903
|
white-space: nowrap;
|
|
3019
3904
|
opacity: 0;
|
|
3905
|
+
transform: scale(0.6);
|
|
3020
3906
|
pointer-events: none;
|
|
3021
3907
|
transform-origin: right center;
|
|
3022
3908
|
`;
|
|
3023
3909
|
let editActive = false;
|
|
3024
|
-
const editBtn =
|
|
3025
|
-
`<svg width="
|
|
3910
|
+
const editBtn = makeActionButton(
|
|
3911
|
+
`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
3026
3912
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
3027
3913
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
3028
3914
|
</svg>`,
|
|
@@ -3031,38 +3917,43 @@ function buildToolbar() {
|
|
|
3031
3917
|
);
|
|
3032
3918
|
controls.appendChild(editBtn);
|
|
3033
3919
|
const canPublish = state.config?.canPublish ?? true;
|
|
3034
|
-
const publishBtn =
|
|
3035
|
-
`<svg width="
|
|
3920
|
+
const publishBtn = makeActionButton(
|
|
3921
|
+
`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
3036
3922
|
<path d="M22 2L11 13"/>
|
|
3037
3923
|
<path d="M22 2L15 22l-4-9-9-4 20-7z"/>
|
|
3038
3924
|
</svg>`,
|
|
3039
|
-
|
|
3925
|
+
"Publish",
|
|
3040
3926
|
() => handlePublish(publishBtn)
|
|
3041
3927
|
);
|
|
3928
|
+
if (!canPublish) {
|
|
3929
|
+
publishBtn.title = "No publish method is configured for this site.";
|
|
3930
|
+
}
|
|
3042
3931
|
if (!canPublish) {
|
|
3043
3932
|
publishBtn.disabled = true;
|
|
3044
3933
|
publishBtn.style.opacity = "0.3";
|
|
3045
3934
|
publishBtn.style.cursor = "not-allowed";
|
|
3046
3935
|
}
|
|
3047
3936
|
controls.appendChild(publishBtn);
|
|
3048
|
-
const logoutBtn =
|
|
3049
|
-
`<svg width="
|
|
3937
|
+
const logoutBtn = makeActionButton(
|
|
3938
|
+
`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
|
3050
3939
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
|
3051
3940
|
<path d="M16 17l5-5-5-5"/>
|
|
3052
3941
|
<path d="M21 12H9"/>
|
|
3053
3942
|
</svg>`,
|
|
3054
|
-
"
|
|
3943
|
+
"Sign out",
|
|
3055
3944
|
() => state.onLogout?.()
|
|
3056
3945
|
);
|
|
3057
3946
|
controls.appendChild(logoutBtn);
|
|
3058
3947
|
controls.appendChild(makeDivider());
|
|
3059
3948
|
const collapseBtn = makeIconButton(
|
|
3060
|
-
`<svg width="
|
|
3949
|
+
`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
|
3061
3950
|
<path d="M6 6l12 12M18 6L6 18"/>
|
|
3062
3951
|
</svg>`,
|
|
3063
3952
|
"Close",
|
|
3064
3953
|
() => collapse(bar, collapseIcon, controls)
|
|
3065
3954
|
);
|
|
3955
|
+
collapseBtn.style.width = "38px";
|
|
3956
|
+
collapseBtn.style.height = "38px";
|
|
3066
3957
|
controls.appendChild(collapseBtn);
|
|
3067
3958
|
bar.appendChild(collapseIcon);
|
|
3068
3959
|
bar.appendChild(controls);
|
|
@@ -3070,10 +3961,10 @@ function buildToolbar() {
|
|
|
3070
3961
|
if (!isExpanded) expand(bar, collapseIcon, controls);
|
|
3071
3962
|
});
|
|
3072
3963
|
bar.addEventListener("mouseenter", () => {
|
|
3073
|
-
if (!isExpanded) bar.style.background = "
|
|
3964
|
+
if (!isExpanded) bar.style.background = v("surface-2");
|
|
3074
3965
|
});
|
|
3075
3966
|
bar.addEventListener("mouseleave", () => {
|
|
3076
|
-
bar.style.background = "
|
|
3967
|
+
bar.style.background = v("surface-1");
|
|
3077
3968
|
});
|
|
3078
3969
|
onPendingChange(() => {
|
|
3079
3970
|
const count = state.pending.size;
|
|
@@ -3089,9 +3980,10 @@ function buildToolbar() {
|
|
|
3089
3980
|
state.editMode = editActive;
|
|
3090
3981
|
const svgEl = editBtn.querySelector("svg");
|
|
3091
3982
|
if (editActive) {
|
|
3092
|
-
editBtn.
|
|
3093
|
-
editBtn.style.
|
|
3094
|
-
|
|
3983
|
+
editBtn.dataset.canciaActive = "1";
|
|
3984
|
+
editBtn.style.background = v("accent-soft");
|
|
3985
|
+
editBtn.style.color = v("accent");
|
|
3986
|
+
if (svgEl) svgEl.style.stroke = v("accent");
|
|
3095
3987
|
editBtn.dataset.canciaTooltip = "Stop editing (Esc)";
|
|
3096
3988
|
attachHighlight((selection) => {
|
|
3097
3989
|
if (selection.kind === "field") {
|
|
@@ -3155,8 +4047,9 @@ function buildToolbar() {
|
|
|
3155
4047
|
};
|
|
3156
4048
|
document.addEventListener("keydown", editModeEscListener, true);
|
|
3157
4049
|
} else {
|
|
4050
|
+
delete editBtn.dataset.canciaActive;
|
|
3158
4051
|
editBtn.style.background = "transparent";
|
|
3159
|
-
editBtn.style.color = "
|
|
4052
|
+
editBtn.style.color = v("fg-strong");
|
|
3160
4053
|
if (svgEl) svgEl.style.stroke = "";
|
|
3161
4054
|
editBtn.dataset.canciaTooltip = "Edit";
|
|
3162
4055
|
detachHighlight();
|
|
@@ -3183,40 +4076,49 @@ function expand(bar, icon, controls) {
|
|
|
3183
4076
|
controls.style.visibility = "hidden";
|
|
3184
4077
|
controls.style.opacity = "0";
|
|
3185
4078
|
controls.style.pointerEvents = "none";
|
|
3186
|
-
bar.style.width = "max-content";
|
|
3187
4079
|
bar.style.borderRadius = "100px";
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
4080
|
+
bar.style.width = "max-content";
|
|
4081
|
+
const naturalW = bar.scrollWidth;
|
|
4082
|
+
bar.style.width = "52px";
|
|
4083
|
+
controls.style.visibility = "";
|
|
4084
|
+
void bar.offsetWidth;
|
|
4085
|
+
bar.style.width = `${naturalW}px`;
|
|
4086
|
+
bar.style.cursor = "default";
|
|
4087
|
+
icon.style.opacity = "0";
|
|
4088
|
+
icon.style.transform = "scale(0.5) rotate(-90deg)";
|
|
4089
|
+
revealTimer = setTimeout(() => {
|
|
4090
|
+
revealTimer = null;
|
|
4091
|
+
if (!isExpanded) return;
|
|
4092
|
+
controls.style.pointerEvents = "auto";
|
|
3191
4093
|
controls.style.visibility = "";
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
setTimeout(() => {
|
|
3199
|
-
controls.style.pointerEvents = "auto";
|
|
3200
|
-
controls.style.animation = "cancia-controls-in 0.4s cubic-bezier(0.19, 1, 0.22, 1) both";
|
|
3201
|
-
controls.style.opacity = "1";
|
|
3202
|
-
}, 80);
|
|
3203
|
-
});
|
|
3204
|
-
});
|
|
4094
|
+
controls.style.animation = "none";
|
|
4095
|
+
controls.style.transition = `opacity ${v("duration")} ${v("ease")}, transform ${v("duration")} ${v("ease")}`;
|
|
4096
|
+
void controls.offsetWidth;
|
|
4097
|
+
controls.style.opacity = "1";
|
|
4098
|
+
controls.style.transform = "scale(1)";
|
|
4099
|
+
}, 80);
|
|
3205
4100
|
}
|
|
3206
4101
|
function collapse(bar, icon, controls) {
|
|
3207
4102
|
if (!isExpanded) return;
|
|
3208
4103
|
isExpanded = false;
|
|
3209
4104
|
hideBtnTooltip();
|
|
4105
|
+
if (revealTimer !== null) {
|
|
4106
|
+
clearTimeout(revealTimer);
|
|
4107
|
+
revealTimer = null;
|
|
4108
|
+
}
|
|
3210
4109
|
if (expandedEscListener) {
|
|
3211
4110
|
document.removeEventListener("keydown", expandedEscListener, true);
|
|
3212
4111
|
expandedEscListener = null;
|
|
3213
4112
|
}
|
|
3214
4113
|
controls.style.pointerEvents = "none";
|
|
3215
|
-
controls.style.animation = "
|
|
4114
|
+
controls.style.animation = "none";
|
|
4115
|
+
controls.style.transition = `opacity ${v("duration-fast")} ${v("ease-out")}, transform ${v("duration-fast")} ${v("ease-out")}`;
|
|
4116
|
+
controls.style.opacity = "0";
|
|
4117
|
+
controls.style.transform = "scale(0.6)";
|
|
3216
4118
|
setTimeout(() => {
|
|
3217
|
-
|
|
3218
|
-
bar.style.width = "
|
|
3219
|
-
bar.style.borderRadius = "
|
|
4119
|
+
if (isExpanded) return;
|
|
4120
|
+
bar.style.width = "52px";
|
|
4121
|
+
bar.style.borderRadius = "26px";
|
|
3220
4122
|
bar.style.cursor = "pointer";
|
|
3221
4123
|
icon.style.opacity = "1";
|
|
3222
4124
|
icon.style.transform = "scale(1) rotate(0deg)";
|
|
@@ -3260,42 +4162,87 @@ async function handlePublish(btn) {
|
|
|
3260
4162
|
}
|
|
3261
4163
|
function showToast(message, type) {
|
|
3262
4164
|
const toast = document.createElement("div");
|
|
3263
|
-
|
|
4165
|
+
markUi(toast);
|
|
4166
|
+
const color = type === "success" ? v("success") : v("danger");
|
|
3264
4167
|
toast.style.cssText = `
|
|
3265
|
-
position: fixed; bottom: 80px; right:
|
|
3266
|
-
display: flex; align-items: center; gap:
|
|
3267
|
-
background:
|
|
3268
|
-
backdrop-filter: blur
|
|
3269
|
-
|
|
3270
|
-
border
|
|
3271
|
-
|
|
3272
|
-
font-size:
|
|
3273
|
-
box-shadow:
|
|
4168
|
+
position: fixed; bottom: 80px; right: ${v("space-6")}; z-index: ${v("z-bar")};
|
|
4169
|
+
display: flex; align-items: center; gap: ${v("space-2")};
|
|
4170
|
+
background: ${v("surface-1")};
|
|
4171
|
+
backdrop-filter: ${v("blur")};
|
|
4172
|
+
-webkit-backdrop-filter: ${v("blur")};
|
|
4173
|
+
border: 1px solid ${v("border")};
|
|
4174
|
+
border-radius: ${v("radius")}; padding: 10px 14px;
|
|
4175
|
+
font-size: ${v("text-base")}; font-weight: 500; color: ${v("fg-strong")};
|
|
4176
|
+
box-shadow: ${v("shadow")};
|
|
3274
4177
|
pointer-events: none;
|
|
3275
|
-
|
|
4178
|
+
opacity: 0; transform: translateY(4px);
|
|
4179
|
+
transition: opacity ${v("duration")} ${v("ease")}, transform ${v("duration")} ${v("ease")};
|
|
3276
4180
|
`;
|
|
3277
4181
|
const dot = document.createElement("span");
|
|
3278
4182
|
dot.style.cssText = `width: 7px; height: 7px; border-radius: 50%; background: ${color}; flex-shrink: 0;`;
|
|
3279
|
-
const
|
|
3280
|
-
|
|
4183
|
+
const label2 = document.createElement("span");
|
|
4184
|
+
label2.textContent = message;
|
|
3281
4185
|
toast.appendChild(dot);
|
|
3282
|
-
toast.appendChild(
|
|
4186
|
+
toast.appendChild(label2);
|
|
3283
4187
|
document.body.appendChild(toast);
|
|
4188
|
+
requestAnimationFrame(() => {
|
|
4189
|
+
toast.style.opacity = "1";
|
|
4190
|
+
toast.style.transform = "translateY(0)";
|
|
4191
|
+
});
|
|
3284
4192
|
setTimeout(() => {
|
|
3285
|
-
toast.style.transition =
|
|
4193
|
+
toast.style.transition = `opacity ${v("duration-fast")} ${v("ease-out")}, transform ${v("duration-fast")} ${v("ease-out")}`;
|
|
3286
4194
|
toast.style.opacity = "0";
|
|
3287
4195
|
toast.style.transform = "translateY(4px)";
|
|
3288
4196
|
setTimeout(() => toast.remove(), 300);
|
|
3289
4197
|
}, 2e3);
|
|
3290
4198
|
}
|
|
4199
|
+
function makeActionButton(svg, labelText, onClick) {
|
|
4200
|
+
const btn = document.createElement("button");
|
|
4201
|
+
btn.type = "button";
|
|
4202
|
+
btn.style.cssText = actionButton();
|
|
4203
|
+
btn.setAttribute("aria-label", labelText);
|
|
4204
|
+
const icon = document.createElement("span");
|
|
4205
|
+
icon.style.cssText = `display: flex; flex-shrink: 0;`;
|
|
4206
|
+
icon.innerHTML = svg;
|
|
4207
|
+
const svgEl = icon.querySelector("svg");
|
|
4208
|
+
if (svgEl) {
|
|
4209
|
+
svgEl.style.cssText = "display:block;flex-shrink:0;overflow:visible;";
|
|
4210
|
+
svgEl.setAttribute("stroke-width", "1.6");
|
|
4211
|
+
}
|
|
4212
|
+
const label2 = document.createElement("span");
|
|
4213
|
+
label2.textContent = labelText;
|
|
4214
|
+
label2.dataset.canciaLabel = "1";
|
|
4215
|
+
btn.appendChild(icon);
|
|
4216
|
+
btn.appendChild(label2);
|
|
4217
|
+
btn.addEventListener("mouseenter", () => {
|
|
4218
|
+
if (!btn.disabled && btn.dataset.canciaActive !== "1") {
|
|
4219
|
+
btn.style.background = v("surface-hover");
|
|
4220
|
+
btn.style.color = v("fg-strong");
|
|
4221
|
+
}
|
|
4222
|
+
});
|
|
4223
|
+
btn.addEventListener("mouseleave", () => {
|
|
4224
|
+
if (btn.dataset.canciaActive !== "1") {
|
|
4225
|
+
btn.style.background = "transparent";
|
|
4226
|
+
btn.style.color = v("fg");
|
|
4227
|
+
}
|
|
4228
|
+
});
|
|
4229
|
+
attachPress(btn);
|
|
4230
|
+
btn.addEventListener("click", (e) => {
|
|
4231
|
+
e.stopPropagation();
|
|
4232
|
+
onClick();
|
|
4233
|
+
});
|
|
4234
|
+
return btn;
|
|
4235
|
+
}
|
|
3291
4236
|
function makeIconButton(svg, title, onClick) {
|
|
3292
4237
|
const btn = document.createElement("button");
|
|
3293
4238
|
btn.style.cssText = `
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
transition: color
|
|
4239
|
+
${iconButton(34)}
|
|
4240
|
+
border-radius: ${v("radius-full")};
|
|
4241
|
+
color: ${v("fg-strong")};
|
|
4242
|
+
flex-shrink: 0; padding: 0;
|
|
4243
|
+
transition: color ${v("duration-fast")} ${v("ease")},
|
|
4244
|
+
background ${v("duration-fast")} ${v("ease")},
|
|
4245
|
+
transform 0.1s ${v("ease")};
|
|
3299
4246
|
`;
|
|
3300
4247
|
btn.innerHTML = svg;
|
|
3301
4248
|
const svgEl = btn.querySelector("svg");
|
|
@@ -3306,15 +4253,12 @@ function makeIconButton(svg, title, onClick) {
|
|
|
3306
4253
|
btn.dataset.canciaTooltip = title;
|
|
3307
4254
|
btn.addEventListener("mouseenter", () => {
|
|
3308
4255
|
if (!btn.disabled) {
|
|
3309
|
-
btn.style.background = "
|
|
4256
|
+
btn.style.background = v("surface-active");
|
|
3310
4257
|
showBtnTooltip(btn, btn.dataset.canciaTooltip ?? title);
|
|
3311
4258
|
}
|
|
3312
4259
|
});
|
|
3313
4260
|
btn.addEventListener("mouseleave", () => {
|
|
3314
|
-
|
|
3315
|
-
if (!btn.style.background.includes(activeColor.slice(1, 7))) {
|
|
3316
|
-
btn.style.background = "transparent";
|
|
3317
|
-
}
|
|
4261
|
+
if (btn.dataset.canciaActive !== "1") btn.style.background = "transparent";
|
|
3318
4262
|
hideBtnTooltip();
|
|
3319
4263
|
});
|
|
3320
4264
|
btn.addEventListener("click", (e) => {
|
|
@@ -3326,45 +4270,45 @@ function makeIconButton(svg, title, onClick) {
|
|
|
3326
4270
|
}
|
|
3327
4271
|
function makeDivider() {
|
|
3328
4272
|
const d = document.createElement("span");
|
|
3329
|
-
d.style.cssText = `width: 1px; height: 14px; background:
|
|
4273
|
+
d.style.cssText = `width: 1px; height: 14px; background: ${v("border")}; flex-shrink: 0; margin: 0 1px;`;
|
|
3330
4274
|
return d;
|
|
3331
4275
|
}
|
|
3332
4276
|
function showPendingPanel(count) {
|
|
3333
4277
|
if (!pendingPanelEl) {
|
|
3334
4278
|
pendingPanelEl = document.createElement("div");
|
|
4279
|
+
markUi(pendingPanelEl);
|
|
3335
4280
|
pendingPanelEl.style.cssText = `
|
|
3336
4281
|
position: fixed;
|
|
3337
4282
|
bottom: 80px;
|
|
3338
|
-
right:
|
|
3339
|
-
z-index:
|
|
4283
|
+
right: ${v("space-6")};
|
|
4284
|
+
z-index: ${v("z-panel")};
|
|
3340
4285
|
display: flex;
|
|
3341
4286
|
align-items: center;
|
|
3342
4287
|
justify-content: space-between;
|
|
3343
|
-
gap:
|
|
3344
|
-
background:
|
|
3345
|
-
backdrop-filter: blur
|
|
3346
|
-
-webkit-backdrop-filter: blur
|
|
3347
|
-
border: 1px solid
|
|
3348
|
-
border-radius:
|
|
4288
|
+
gap: ${v("space-3")};
|
|
4289
|
+
background: ${v("surface-1")};
|
|
4290
|
+
backdrop-filter: ${v("blur")};
|
|
4291
|
+
-webkit-backdrop-filter: ${v("blur")};
|
|
4292
|
+
border: 1px solid ${v("border")};
|
|
4293
|
+
border-radius: ${v("radius")};
|
|
3349
4294
|
padding: 0;
|
|
3350
|
-
box-shadow:
|
|
3351
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
3352
|
-
animation: cancia-fade-in 0.25s cubic-bezier(0.16,1,0.3,1) both;
|
|
4295
|
+
box-shadow: ${v("shadow")};
|
|
3353
4296
|
width: max-content;
|
|
3354
4297
|
overflow: hidden;
|
|
4298
|
+
opacity: 0; transform: translateY(4px);
|
|
4299
|
+
transition: opacity ${v("duration")} ${v("ease")}, transform ${v("duration")} ${v("ease")};
|
|
3355
4300
|
`;
|
|
3356
|
-
const
|
|
3357
|
-
|
|
3358
|
-
|
|
4301
|
+
const label3 = document.createElement("span");
|
|
4302
|
+
label3.dataset.canciaPendingLabel = "1";
|
|
4303
|
+
label3.style.cssText = `font-size: ${v("text-sm")}; font-weight: 500; color: ${v("fg-muted")}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;`;
|
|
3359
4304
|
const saveBtn = document.createElement("button");
|
|
3360
4305
|
saveBtn.dataset.canciaSaveBtn = "1";
|
|
3361
4306
|
saveBtn.title = "Save changes";
|
|
3362
4307
|
saveBtn.style.cssText = `
|
|
3363
|
-
padding: 5px 10px; border-radius:
|
|
3364
|
-
background:
|
|
3365
|
-
font-
|
|
3366
|
-
|
|
3367
|
-
transition: opacity 0.15s, transform 0.1s cubic-bezier(0.2,0,0,1);
|
|
4308
|
+
padding: 5px 10px; border-radius: ${v("radius-sm")}; border: none; cursor: pointer;
|
|
4309
|
+
background: ${v("accent")}; color: ${v("accent-fg")};
|
|
4310
|
+
font-size: ${v("text-sm")}; font-weight: 600; letter-spacing: 0.01em;
|
|
4311
|
+
transition: opacity ${v("duration-fast")} ${v("ease")}, transform ${v("duration-fast")} ${v("ease")};
|
|
3368
4312
|
flex-shrink: 0;
|
|
3369
4313
|
`;
|
|
3370
4314
|
saveBtn.textContent = "Save";
|
|
@@ -3374,6 +4318,7 @@ function showPendingPanel(count) {
|
|
|
3374
4318
|
saveBtn.addEventListener("mouseleave", () => {
|
|
3375
4319
|
saveBtn.style.opacity = "1";
|
|
3376
4320
|
});
|
|
4321
|
+
attachPress(saveBtn);
|
|
3377
4322
|
saveBtn.addEventListener("click", (e) => {
|
|
3378
4323
|
e.stopPropagation();
|
|
3379
4324
|
handleSave(saveBtn);
|
|
@@ -3381,22 +4326,22 @@ function showPendingPanel(count) {
|
|
|
3381
4326
|
const undoBtn = document.createElement("button");
|
|
3382
4327
|
undoBtn.title = "Discard changes";
|
|
3383
4328
|
undoBtn.style.cssText = `
|
|
3384
|
-
padding: 5px 10px; border-radius:
|
|
3385
|
-
background: transparent; color:
|
|
3386
|
-
font-
|
|
3387
|
-
|
|
3388
|
-
transition: color 0.15s, border-color 0.15s;
|
|
4329
|
+
padding: 5px 10px; border-radius: ${v("radius-sm")}; border: 1px solid ${v("border")}; cursor: pointer;
|
|
4330
|
+
background: transparent; color: ${v("fg-muted")};
|
|
4331
|
+
font-size: ${v("text-sm")}; font-weight: 500; letter-spacing: 0.01em;
|
|
4332
|
+
transition: color ${v("duration-fast")} ${v("ease")}, border-color ${v("duration-fast")} ${v("ease")};
|
|
3389
4333
|
flex-shrink: 0;
|
|
3390
4334
|
`;
|
|
3391
4335
|
undoBtn.textContent = "Discard";
|
|
3392
4336
|
undoBtn.addEventListener("mouseenter", () => {
|
|
3393
|
-
undoBtn.style.color = "
|
|
3394
|
-
undoBtn.style.borderColor = "
|
|
4337
|
+
undoBtn.style.color = v("fg-strong");
|
|
4338
|
+
undoBtn.style.borderColor = v("border-strong");
|
|
3395
4339
|
});
|
|
3396
4340
|
undoBtn.addEventListener("mouseleave", () => {
|
|
3397
|
-
undoBtn.style.color = "
|
|
3398
|
-
undoBtn.style.borderColor = "
|
|
4341
|
+
undoBtn.style.color = v("fg-muted");
|
|
4342
|
+
undoBtn.style.borderColor = v("border");
|
|
3399
4343
|
});
|
|
4344
|
+
attachPress(undoBtn);
|
|
3400
4345
|
undoBtn.addEventListener("click", (e) => {
|
|
3401
4346
|
e.stopPropagation();
|
|
3402
4347
|
revertPending();
|
|
@@ -3411,32 +4356,36 @@ function showPendingPanel(count) {
|
|
|
3411
4356
|
row.style.cssText = `
|
|
3412
4357
|
grid-area: 1/1; display: flex; align-items: center; gap: 6px;
|
|
3413
4358
|
padding: 7px 7px 7px 12px;
|
|
3414
|
-
transition: transform
|
|
4359
|
+
transition: transform ${v("duration")} ${v("ease-out")};
|
|
3415
4360
|
`;
|
|
3416
|
-
row.appendChild(
|
|
4361
|
+
row.appendChild(label3);
|
|
3417
4362
|
row.appendChild(undoBtn);
|
|
3418
4363
|
row.appendChild(saveBtn);
|
|
3419
4364
|
const flash = document.createElement("div");
|
|
3420
4365
|
flash.dataset.canciaPanelFlash = "1";
|
|
3421
4366
|
flash.style.cssText = `
|
|
3422
4367
|
grid-area: 1/1; display: flex; align-items: center; gap: 7px;
|
|
3423
|
-
font-
|
|
3424
|
-
|
|
3425
|
-
padding: 7px 12px;
|
|
4368
|
+
font-size: ${v("text-sm")}; font-weight: 500; color: ${v("fg-strong")}; white-space: nowrap;
|
|
4369
|
+
padding: 7px ${v("space-3")};
|
|
3426
4370
|
transform: translateY(-150%);
|
|
3427
|
-
transition: transform
|
|
4371
|
+
transition: transform ${v("duration")} ${v("ease-out")};
|
|
3428
4372
|
`;
|
|
3429
4373
|
slot.appendChild(row);
|
|
3430
4374
|
slot.appendChild(flash);
|
|
3431
4375
|
pendingPanelEl.appendChild(slot);
|
|
3432
4376
|
document.body.appendChild(pendingPanelEl);
|
|
4377
|
+
const panel = pendingPanelEl;
|
|
4378
|
+
requestAnimationFrame(() => {
|
|
4379
|
+
panel.style.opacity = "1";
|
|
4380
|
+
panel.style.transform = "translateY(0)";
|
|
4381
|
+
});
|
|
3433
4382
|
}
|
|
3434
|
-
const
|
|
3435
|
-
if (
|
|
4383
|
+
const label2 = pendingPanelEl.querySelector("[data-cancia-pending-label]");
|
|
4384
|
+
if (label2) label2.textContent = `${count} unsaved change${count === 1 ? "" : "s"}`;
|
|
3436
4385
|
}
|
|
3437
4386
|
function flashPanelMessage(message, type) {
|
|
3438
4387
|
if (!pendingPanelEl) return;
|
|
3439
|
-
const color = type === "success" ? "
|
|
4388
|
+
const color = type === "success" ? v("success") : v("danger");
|
|
3440
4389
|
const row = pendingPanelEl.querySelector("[data-cancia-panel-row]");
|
|
3441
4390
|
const flash = pendingPanelEl.querySelector("[data-cancia-panel-flash]");
|
|
3442
4391
|
if (!row || !flash) return;
|
|
@@ -3449,7 +4398,7 @@ function hidePendingPanel() {
|
|
|
3449
4398
|
if (!pendingPanelEl) return;
|
|
3450
4399
|
const panel = pendingPanelEl;
|
|
3451
4400
|
pendingPanelEl = null;
|
|
3452
|
-
panel.style.transition =
|
|
4401
|
+
panel.style.transition = `opacity 0.2s ${v("ease-out")}, transform 0.2s ${v("ease-out")}`;
|
|
3453
4402
|
panel.style.opacity = "0";
|
|
3454
4403
|
panel.style.transform = "translateY(4px)";
|
|
3455
4404
|
setTimeout(() => panel.remove(), 220);
|
|
@@ -3469,7 +4418,7 @@ function unmountToolbar() {
|
|
|
3469
4418
|
pendingPanelEl?.remove();
|
|
3470
4419
|
pendingPanelEl = null;
|
|
3471
4420
|
if (toolbarEl) {
|
|
3472
|
-
toolbarEl.style.animation =
|
|
4421
|
+
toolbarEl.style.animation = `cancia-exit ${v("duration-exit")} ${v("ease-out")} both`;
|
|
3473
4422
|
setTimeout(() => {
|
|
3474
4423
|
toolbarEl?.remove();
|
|
3475
4424
|
toolbarEl = null;
|