@cancia/toolbar 0.5.1 → 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 +1643 -695
- 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,7 +199,12 @@ function applyOverlay() {
|
|
|
53
199
|
el.src = savedValue;
|
|
54
200
|
return;
|
|
55
201
|
}
|
|
56
|
-
if (el.dataset.cmsType === "
|
|
202
|
+
if (el.dataset.cmsType === "richtext") {
|
|
203
|
+
const blocks = parseRichValue(savedValue);
|
|
204
|
+
if (blocks) renderRichToDom(el, blocks);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
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);
|
|
59
210
|
const labelEls = el.querySelectorAll("[data-cms-label]");
|
|
@@ -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,23 +398,432 @@ 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
|
-
if (el.tagName === "A") return "link";
|
|
262
827
|
return "text";
|
|
263
828
|
}
|
|
264
829
|
function elementMode(el) {
|
|
@@ -269,9 +834,12 @@ function injectStyles() {
|
|
|
269
834
|
styleInjected = true;
|
|
270
835
|
const s = document.createElement("style");
|
|
271
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. */
|
|
272
840
|
@keyframes cancia-highlight-in {
|
|
273
|
-
from { opacity: 0;
|
|
274
|
-
to { opacity: 1;
|
|
841
|
+
from { opacity: 0; }
|
|
842
|
+
to { opacity: 1; }
|
|
275
843
|
}
|
|
276
844
|
@keyframes cancia-tooltip-in {
|
|
277
845
|
from { opacity: 0; transform: scale(0.95) translateY(3px); }
|
|
@@ -284,15 +852,19 @@ function getOrCreateOverlay() {
|
|
|
284
852
|
if (!overlayEl) {
|
|
285
853
|
overlayEl = document.createElement("div");
|
|
286
854
|
overlayEl.dataset.canciaOverlay = "1";
|
|
855
|
+
markUi(overlayEl);
|
|
287
856
|
overlayEl.style.cssText = `
|
|
288
857
|
position: fixed;
|
|
858
|
+
top: 0; left: 0;
|
|
289
859
|
pointer-events: none !important;
|
|
290
860
|
box-sizing: border-box;
|
|
291
|
-
|
|
861
|
+
background: transparent;
|
|
862
|
+
border: 0;
|
|
292
863
|
z-index: 2147483644;
|
|
293
|
-
will-change:
|
|
294
|
-
|
|
295
|
-
|
|
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")};
|
|
296
868
|
`;
|
|
297
869
|
document.body.appendChild(overlayEl);
|
|
298
870
|
}
|
|
@@ -302,37 +874,41 @@ function getOrCreateTooltip() {
|
|
|
302
874
|
if (!tooltipEl) {
|
|
303
875
|
tooltipEl = document.createElement("div");
|
|
304
876
|
tooltipEl.dataset.canciaTooltip = "1";
|
|
877
|
+
markUi(tooltipEl);
|
|
305
878
|
tooltipEl.style.cssText = `
|
|
306
879
|
position: fixed;
|
|
307
880
|
pointer-events: none !important;
|
|
308
|
-
z-index:
|
|
309
|
-
font-family:
|
|
310
|
-
font-size:
|
|
881
|
+
z-index: ${v("z-overlay")};
|
|
882
|
+
font-family: ${v("font")};
|
|
883
|
+
font-size: ${v("text-xs")};
|
|
311
884
|
font-weight: 500;
|
|
312
885
|
letter-spacing: 0.02em;
|
|
313
|
-
color:
|
|
314
|
-
background:
|
|
315
|
-
backdrop-filter: blur
|
|
316
|
-
-webkit-backdrop-filter: blur
|
|
317
|
-
border: 1px solid
|
|
318
|
-
padding:
|
|
319
|
-
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")};
|
|
320
893
|
white-space: nowrap;
|
|
321
894
|
max-width: 260px;
|
|
322
895
|
overflow: hidden;
|
|
323
896
|
text-overflow: ellipsis;
|
|
324
|
-
box-shadow:
|
|
897
|
+
box-shadow: ${v("shadow-sm")};
|
|
325
898
|
`;
|
|
326
899
|
document.body.appendChild(tooltipEl);
|
|
327
900
|
}
|
|
328
901
|
return tooltipEl;
|
|
329
902
|
}
|
|
330
903
|
var lastOverlayEl = null;
|
|
904
|
+
var LIST_FALLBACK = "#059669";
|
|
331
905
|
function listAccent(hex) {
|
|
332
|
-
if (!/^#[0-9a-f]{6}$/i.test(hex)) return
|
|
906
|
+
if (!/^#[0-9a-f]{6}$/i.test(hex)) return LIST_FALLBACK;
|
|
333
907
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
334
908
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
335
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;
|
|
336
912
|
const shifted = [g, b, r].map((c) => c.toString(16).padStart(2, "0")).join("");
|
|
337
913
|
return `#${shifted}`;
|
|
338
914
|
}
|
|
@@ -353,52 +929,54 @@ var LIST_ICON = `<svg width="10" height="10" viewBox="0 0 14 14" fill="none" str
|
|
|
353
929
|
<rect x="1" y="7.5" width="3" height="3" rx="0.5"/>
|
|
354
930
|
<path d="M6 3.5h7M6 9h7"/>
|
|
355
931
|
</svg>`;
|
|
932
|
+
var KIND_LABEL = {
|
|
933
|
+
text: "Text",
|
|
934
|
+
image: "Image",
|
|
935
|
+
link: "Link",
|
|
936
|
+
richtext: "Rich text"
|
|
937
|
+
};
|
|
356
938
|
function positionOverlay(el, animate = false) {
|
|
357
939
|
const rect = el.getBoundingClientRect();
|
|
358
940
|
const mode = elementMode(el);
|
|
359
|
-
const
|
|
941
|
+
const isList = mode === "list";
|
|
942
|
+
const a = isList ? listAccent(accent()) : accent();
|
|
360
943
|
const overlay = getOrCreateOverlay();
|
|
361
944
|
const tooltip = getOrCreateTooltip();
|
|
362
945
|
const padding = 3;
|
|
363
|
-
overlay.style.
|
|
364
|
-
overlay.style.left = `${rect.left - padding}px`;
|
|
946
|
+
overlay.style.transform = `translate(${rect.left - padding}px, ${rect.top - padding}px)`;
|
|
365
947
|
overlay.style.width = `${rect.width + padding * 2}px`;
|
|
366
948
|
overlay.style.height = `${rect.height + padding * 2}px`;
|
|
367
949
|
if (lastOverlayEl !== el) {
|
|
368
950
|
lastOverlayEl = el;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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;
|
|
373
960
|
let tooltipText;
|
|
374
|
-
if (
|
|
375
|
-
|
|
376
|
-
|
|
961
|
+
if (isList) {
|
|
962
|
+
tagIcon = LIST_ICON;
|
|
963
|
+
tagLabel = "List";
|
|
377
964
|
tooltipText = `list: ${el.dataset.cmsList}`;
|
|
378
965
|
} else {
|
|
379
966
|
const type = fieldType(el);
|
|
380
|
-
|
|
381
|
-
|
|
967
|
+
tagIcon = type === "image" ? IMAGE_ICON : type === "link" ? LINK_ICON : FIELD_ICON;
|
|
968
|
+
tagLabel = KIND_LABEL[type];
|
|
382
969
|
tooltipText = el.dataset.cms ?? "";
|
|
383
970
|
}
|
|
384
|
-
overlay.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
padding: 2px 6px; border-radius: 3px 0 4px 0;
|
|
391
|
-
display: flex; align-items: center; gap: 4px;
|
|
392
|
-
line-height: 1;
|
|
393
|
-
">
|
|
394
|
-
${badgeIcon}
|
|
395
|
-
${badgeLabel}
|
|
396
|
-
</div>
|
|
397
|
-
`;
|
|
398
|
-
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;
|
|
399
977
|
}
|
|
400
978
|
overlay.style.display = "block";
|
|
401
|
-
if (animate) overlay.style.animation =
|
|
979
|
+
if (animate) overlay.style.animation = `cancia-highlight-in ${v("duration-fast")} ${v("ease-out")} forwards`;
|
|
402
980
|
tooltip.style.display = "block";
|
|
403
981
|
if (animate) tooltip.style.animation = "cancia-tooltip-in 0.1s ease-out forwards";
|
|
404
982
|
const tooltipMargin = 8;
|
|
@@ -423,13 +1001,19 @@ function hideOverlay() {
|
|
|
423
1001
|
}
|
|
424
1002
|
}
|
|
425
1003
|
function handleScroll() {
|
|
426
|
-
if (
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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)) {
|
|
430
1012
|
positionOverlay(currentHighlighted);
|
|
1013
|
+
if (overlayEl) overlayEl.style.opacity = "1";
|
|
1014
|
+
if (tooltipEl) tooltipEl.style.opacity = "1";
|
|
431
1015
|
}
|
|
432
|
-
});
|
|
1016
|
+
}, 140);
|
|
433
1017
|
}
|
|
434
1018
|
function handleMouseOver(e) {
|
|
435
1019
|
const target = e.target.closest(CMS_SELECTOR);
|
|
@@ -525,13 +1109,18 @@ function onPendingChange(cb) {
|
|
|
525
1109
|
}
|
|
526
1110
|
|
|
527
1111
|
// src/popup.ts
|
|
1112
|
+
import {
|
|
1113
|
+
portableTextToRows,
|
|
1114
|
+
rowsToPortableText
|
|
1115
|
+
} from "@cancia/astro/richtext";
|
|
528
1116
|
var popupEl = null;
|
|
529
1117
|
var outsideListener = null;
|
|
530
1118
|
var keyListener = null;
|
|
531
1119
|
var dragover = false;
|
|
532
1120
|
var inputHandlers = /* @__PURE__ */ new WeakMap();
|
|
533
1121
|
function accent2() {
|
|
534
|
-
|
|
1122
|
+
const useAccent = state.config?.toolbarAccent === true;
|
|
1123
|
+
return (useAccent ? state.config?.accentColor : void 0) ?? "#18181b";
|
|
535
1124
|
}
|
|
536
1125
|
function getPopupPosition(anchor) {
|
|
537
1126
|
const rect = anchor.getBoundingClientRect();
|
|
@@ -566,15 +1155,15 @@ function buildHeader(key, onClose) {
|
|
|
566
1155
|
const keyParts = key.split(".");
|
|
567
1156
|
titleEl.textContent = keyParts[keyParts.length - 1].replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
568
1157
|
titleEl.style.cssText = `
|
|
569
|
-
font-size:
|
|
570
|
-
color:
|
|
1158
|
+
font-size: ${v("text-base")}; font-weight: 600;
|
|
1159
|
+
color: ${v("fg")};
|
|
571
1160
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
572
1161
|
`;
|
|
573
1162
|
const keyEl = document.createElement("span");
|
|
574
1163
|
keyEl.textContent = key;
|
|
575
1164
|
keyEl.style.cssText = `
|
|
576
1165
|
font-size: 10px; font-family: "SF Mono", "Fira Code", ui-monospace, monospace;
|
|
577
|
-
color:
|
|
1166
|
+
color: ${v("fg-faint")}; letter-spacing: 0.03em;
|
|
578
1167
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
579
1168
|
`;
|
|
580
1169
|
titleWrap.appendChild(titleEl);
|
|
@@ -592,7 +1181,7 @@ function buildTextPopup(key, anchorEl, onClose) {
|
|
|
592
1181
|
wrap.appendChild(buildHeader(key, onClose));
|
|
593
1182
|
if (langs.length > 1) {
|
|
594
1183
|
const tabs = document.createElement("div");
|
|
595
|
-
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")};`;
|
|
596
1185
|
const renderTabs2 = () => {
|
|
597
1186
|
tabs.innerHTML = "";
|
|
598
1187
|
langs.forEach((lang) => {
|
|
@@ -602,17 +1191,17 @@ function buildTextPopup(key, anchorEl, onClose) {
|
|
|
602
1191
|
tab.style.cssText = `
|
|
603
1192
|
padding: 5px 10px 6px; border: none; border-bottom: 2px solid;
|
|
604
1193
|
margin-bottom: -1px;
|
|
605
|
-
font-size:
|
|
1194
|
+
font-size: ${v("text-xs")}; font-weight: 600; cursor: pointer; letter-spacing: 0.06em;
|
|
606
1195
|
background: transparent;
|
|
607
|
-
border-bottom-color: ${isActive ?
|
|
608
|
-
color: ${isActive ? "
|
|
609
|
-
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")};
|
|
610
1199
|
`;
|
|
611
1200
|
tab.addEventListener("mouseenter", () => {
|
|
612
|
-
if (!isActive) tab.style.color = "
|
|
1201
|
+
if (!isActive) tab.style.color = v("fg");
|
|
613
1202
|
});
|
|
614
1203
|
tab.addEventListener("mouseleave", () => {
|
|
615
|
-
if (!isActive) tab.style.color = "
|
|
1204
|
+
if (!isActive) tab.style.color = v("fg-faint");
|
|
616
1205
|
});
|
|
617
1206
|
tab.addEventListener("click", () => {
|
|
618
1207
|
const current = wrap.querySelector("textarea");
|
|
@@ -650,8 +1239,8 @@ function buildTextPopup(key, anchorEl, onClose) {
|
|
|
650
1239
|
const renderTextarea = (isInit = false) => {
|
|
651
1240
|
if (!isInit && textarea) {
|
|
652
1241
|
textarea.value = getValue(key, activeLang) || anchorEl.textContent?.trim() || "";
|
|
653
|
-
textarea.style.borderColor =
|
|
654
|
-
textarea.style.background = "
|
|
1242
|
+
textarea.style.borderColor = v("accent-ring");
|
|
1243
|
+
textarea.style.background = v("surface-hover");
|
|
655
1244
|
attachInputHandler();
|
|
656
1245
|
return;
|
|
657
1246
|
}
|
|
@@ -661,23 +1250,13 @@ function buildTextPopup(key, anchorEl, onClose) {
|
|
|
661
1250
|
textarea.rows = 4;
|
|
662
1251
|
textarea.placeholder = "Enter text\u2026";
|
|
663
1252
|
textarea.style.cssText = `
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
font-size: 13px; font-family: inherit; resize: none; outline: none;
|
|
669
|
-
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;
|
|
670
1257
|
line-height: 1.55;
|
|
671
|
-
caret-color: ${accent2()};
|
|
672
1258
|
`;
|
|
673
|
-
textarea
|
|
674
|
-
textarea.style.borderColor = `${accent2()}66`;
|
|
675
|
-
textarea.style.background = "rgba(255,255,255,0.05)";
|
|
676
|
-
});
|
|
677
|
-
textarea.addEventListener("blur", () => {
|
|
678
|
-
textarea.style.borderColor = "rgba(255,255,255,0.07)";
|
|
679
|
-
textarea.style.background = "rgba(255,255,255,0.03)";
|
|
680
|
-
});
|
|
1259
|
+
attachInputFocus(textarea);
|
|
681
1260
|
attachInputHandler();
|
|
682
1261
|
if (footerEl) {
|
|
683
1262
|
wrap.insertBefore(textarea, footerEl);
|
|
@@ -753,31 +1332,21 @@ function buildLinkPopup(key, anchorEl, onClose) {
|
|
|
753
1332
|
};
|
|
754
1333
|
};
|
|
755
1334
|
const inputStyle = `
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
font-size: 13px; font-family: inherit; outline: none;
|
|
761
|
-
transition: border-color 0.18s, background 0.18s;
|
|
762
|
-
caret-color: ${accent2()};
|
|
1335
|
+
${input()}
|
|
1336
|
+
border-radius: ${v("radius")};
|
|
1337
|
+
padding: 9px ${v("space-3")};
|
|
1338
|
+
font-family: inherit;
|
|
763
1339
|
`;
|
|
764
|
-
const makeLabelled = (text,
|
|
1340
|
+
const makeLabelled = (text, input2) => {
|
|
765
1341
|
const field = document.createElement("div");
|
|
766
1342
|
field.style.cssText = `display: flex; flex-direction: column; gap: 5px; margin-bottom: 10px;`;
|
|
767
1343
|
const lab = document.createElement("div");
|
|
768
1344
|
lab.textContent = text;
|
|
769
|
-
lab.style.cssText =
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
input.style.borderColor = `${accent2()}66`;
|
|
773
|
-
input.style.background = "rgba(255,255,255,0.05)";
|
|
774
|
-
});
|
|
775
|
-
input.addEventListener("blur", () => {
|
|
776
|
-
input.style.borderColor = "rgba(255,255,255,0.07)";
|
|
777
|
-
input.style.background = "rgba(255,255,255,0.03)";
|
|
778
|
-
});
|
|
1345
|
+
lab.style.cssText = label();
|
|
1346
|
+
input2.style.cssText = inputStyle;
|
|
1347
|
+
attachInputFocus(input2);
|
|
779
1348
|
field.appendChild(lab);
|
|
780
|
-
field.appendChild(
|
|
1349
|
+
field.appendChild(input2);
|
|
781
1350
|
return field;
|
|
782
1351
|
};
|
|
783
1352
|
const labelInput = document.createElement("input");
|
|
@@ -792,7 +1361,7 @@ function buildLinkPopup(key, anchorEl, onClose) {
|
|
|
792
1361
|
hrefInput.value = current.href;
|
|
793
1362
|
if (langs.length > 1) {
|
|
794
1363
|
const tabs = document.createElement("div");
|
|
795
|
-
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")};`;
|
|
796
1365
|
const renderTabs2 = () => {
|
|
797
1366
|
tabs.innerHTML = "";
|
|
798
1367
|
langs.forEach((lang) => {
|
|
@@ -802,11 +1371,11 @@ function buildLinkPopup(key, anchorEl, onClose) {
|
|
|
802
1371
|
tab.style.cssText = `
|
|
803
1372
|
padding: 5px 10px 6px; border: none; border-bottom: 2px solid;
|
|
804
1373
|
margin-bottom: -1px;
|
|
805
|
-
font-size:
|
|
1374
|
+
font-size: ${v("text-xs")}; font-weight: 600; cursor: pointer; letter-spacing: 0.06em;
|
|
806
1375
|
background: transparent;
|
|
807
|
-
border-bottom-color: ${isActive ?
|
|
808
|
-
color: ${isActive ? "
|
|
809
|
-
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")};
|
|
810
1379
|
`;
|
|
811
1380
|
tab.addEventListener("click", () => {
|
|
812
1381
|
stage(activeLang);
|
|
@@ -824,12 +1393,12 @@ function buildLinkPopup(key, anchorEl, onClose) {
|
|
|
824
1393
|
}
|
|
825
1394
|
wrap.appendChild(makeLabelled("Label", labelInput));
|
|
826
1395
|
wrap.appendChild(makeLabelled("URL", hrefInput));
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
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);
|
|
831
1400
|
const warn = document.createElement("div");
|
|
832
|
-
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;`;
|
|
833
1402
|
wrap.appendChild(warn);
|
|
834
1403
|
const paint = () => {
|
|
835
1404
|
labelNodes.forEach((n) => n.textContent = labelInput.value);
|
|
@@ -886,8 +1455,8 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
886
1455
|
if (currentSrc && !currentSrc.startsWith("data:")) {
|
|
887
1456
|
const previewWrap = document.createElement("div");
|
|
888
1457
|
previewWrap.style.cssText = `
|
|
889
|
-
border-radius:
|
|
890
|
-
border: 1px solid
|
|
1458
|
+
border-radius: ${v("radius")}; overflow: hidden; margin-bottom: 10px;
|
|
1459
|
+
border: 1px solid ${v("border")};
|
|
891
1460
|
position: relative; height: 100px;
|
|
892
1461
|
`;
|
|
893
1462
|
const previewImg = document.createElement("img");
|
|
@@ -897,8 +1466,8 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
897
1466
|
previewLabel.textContent = "Current";
|
|
898
1467
|
previewLabel.style.cssText = `
|
|
899
1468
|
position: absolute; bottom: 0; left: 0; right: 0;
|
|
900
|
-
font-size: 10px; color: rgba(255,255,255,0.
|
|
901
|
-
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;
|
|
902
1471
|
background: linear-gradient(transparent, rgba(0,0,0,0.55));
|
|
903
1472
|
`;
|
|
904
1473
|
previewWrap.appendChild(previewImg);
|
|
@@ -908,23 +1477,23 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
908
1477
|
const dropZone = document.createElement("label");
|
|
909
1478
|
dropZone.style.cssText = `
|
|
910
1479
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
911
|
-
gap:
|
|
912
|
-
border: 1.5px dashed
|
|
913
|
-
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")};
|
|
914
1483
|
cursor: pointer;
|
|
915
|
-
transition: border-color
|
|
916
|
-
background:
|
|
1484
|
+
transition: border-color ${v("duration-fast")} ${v("ease")}, background ${v("duration-fast")} ${v("ease")};
|
|
1485
|
+
background: ${v("surface-raised")};
|
|
917
1486
|
`;
|
|
918
1487
|
const uploadIcon = document.createElement("div");
|
|
919
|
-
uploadIcon.style.cssText = `color:
|
|
1488
|
+
uploadIcon.style.cssText = `color: ${v("fg-muted")}; transition: color ${v("duration-fast")} ${v("ease")};`;
|
|
920
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">
|
|
921
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"/>
|
|
922
1491
|
</svg>`;
|
|
923
1492
|
const dropText = document.createElement("div");
|
|
924
1493
|
dropText.style.cssText = `text-align: center;`;
|
|
925
1494
|
dropText.innerHTML = `
|
|
926
|
-
<div style="font-size
|
|
927
|
-
<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>
|
|
928
1497
|
`;
|
|
929
1498
|
dropZone.appendChild(uploadIcon);
|
|
930
1499
|
dropZone.appendChild(dropText);
|
|
@@ -934,18 +1503,18 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
934
1503
|
fileInput.style.display = "none";
|
|
935
1504
|
dropZone.appendChild(fileInput);
|
|
936
1505
|
const statusWrap = document.createElement("div");
|
|
937
|
-
statusWrap.style.cssText = `margin-top:
|
|
1506
|
+
statusWrap.style.cssText = `margin-top: ${v("space-2")}; min-height: 18px;`;
|
|
938
1507
|
const statusMsg = document.createElement("p");
|
|
939
|
-
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")};`;
|
|
940
1509
|
const progressBar = document.createElement("div");
|
|
941
1510
|
progressBar.style.cssText = `
|
|
942
|
-
height: 2px; border-radius: 2px; background:
|
|
1511
|
+
height: 2px; border-radius: 2px; background: ${v("surface-raised")};
|
|
943
1512
|
overflow: hidden; margin-top: 6px; display: none;
|
|
944
1513
|
`;
|
|
945
1514
|
const progressFill = document.createElement("div");
|
|
946
1515
|
progressFill.style.cssText = `
|
|
947
|
-
height: 100%; border-radius: 2px; background: ${
|
|
948
|
-
width: 0%; transition: width
|
|
1516
|
+
height: 100%; border-radius: 2px; background: ${v("accent")};
|
|
1517
|
+
width: 0%; transition: width ${v("duration-slow")} ${v("ease-out")};
|
|
949
1518
|
`;
|
|
950
1519
|
progressBar.appendChild(progressFill);
|
|
951
1520
|
statusWrap.appendChild(statusMsg);
|
|
@@ -953,20 +1522,20 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
953
1522
|
const handleFile = async (file) => {
|
|
954
1523
|
if (!file.type.startsWith("image/")) {
|
|
955
1524
|
statusMsg.textContent = "Only image files are supported";
|
|
956
|
-
statusMsg.style.color = "
|
|
1525
|
+
statusMsg.style.color = v("danger");
|
|
957
1526
|
return;
|
|
958
1527
|
}
|
|
959
1528
|
const maxMb = 10;
|
|
960
1529
|
if (file.size > maxMb * 1024 * 1024) {
|
|
961
1530
|
statusMsg.textContent = `File too large (max ${maxMb}MB)`;
|
|
962
|
-
statusMsg.style.color = "
|
|
1531
|
+
statusMsg.style.color = v("danger");
|
|
963
1532
|
return;
|
|
964
1533
|
}
|
|
965
|
-
dropZone.style.borderColor =
|
|
966
|
-
dropZone.style.background =
|
|
967
|
-
uploadIcon.style.color =
|
|
1534
|
+
dropZone.style.borderColor = v("accent-ring");
|
|
1535
|
+
dropZone.style.background = v("accent-soft");
|
|
1536
|
+
uploadIcon.style.color = v("accent");
|
|
968
1537
|
statusMsg.textContent = "Uploading\u2026";
|
|
969
|
-
statusMsg.style.color = "
|
|
1538
|
+
statusMsg.style.color = v("fg-muted");
|
|
970
1539
|
progressBar.style.display = "block";
|
|
971
1540
|
progressFill.style.width = "0%";
|
|
972
1541
|
try {
|
|
@@ -990,16 +1559,16 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
990
1559
|
}
|
|
991
1560
|
setTimeout(() => {
|
|
992
1561
|
statusMsg.textContent = "Done";
|
|
993
|
-
statusMsg.style.color = "
|
|
1562
|
+
statusMsg.style.color = v("success");
|
|
994
1563
|
setTimeout(onClose, 600);
|
|
995
1564
|
}, 200);
|
|
996
1565
|
} catch {
|
|
997
1566
|
progressBar.style.display = "none";
|
|
998
1567
|
statusMsg.textContent = "Upload failed \u2014 try again";
|
|
999
|
-
statusMsg.style.color = "
|
|
1000
|
-
dropZone.style.borderColor = "
|
|
1001
|
-
dropZone.style.background = "
|
|
1002
|
-
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");
|
|
1003
1572
|
}
|
|
1004
1573
|
};
|
|
1005
1574
|
fileInput.addEventListener("change", () => {
|
|
@@ -1009,35 +1578,35 @@ function buildImagePopup(key, anchorEl, onClose) {
|
|
|
1009
1578
|
e.preventDefault();
|
|
1010
1579
|
if (!dragover) {
|
|
1011
1580
|
dragover = true;
|
|
1012
|
-
dropZone.style.borderColor =
|
|
1013
|
-
dropZone.style.background =
|
|
1014
|
-
uploadIcon.style.color =
|
|
1581
|
+
dropZone.style.borderColor = v("accent-ring");
|
|
1582
|
+
dropZone.style.background = v("accent-soft");
|
|
1583
|
+
uploadIcon.style.color = v("accent");
|
|
1015
1584
|
}
|
|
1016
1585
|
});
|
|
1017
1586
|
dropZone.addEventListener("dragleave", () => {
|
|
1018
1587
|
dragover = false;
|
|
1019
|
-
dropZone.style.borderColor = "
|
|
1020
|
-
dropZone.style.background = "
|
|
1021
|
-
uploadIcon.style.color = "
|
|
1588
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1589
|
+
dropZone.style.background = v("surface-raised");
|
|
1590
|
+
uploadIcon.style.color = v("fg-muted");
|
|
1022
1591
|
});
|
|
1023
1592
|
dropZone.addEventListener("drop", (e) => {
|
|
1024
1593
|
e.preventDefault();
|
|
1025
1594
|
dragover = false;
|
|
1026
|
-
dropZone.style.borderColor = "
|
|
1027
|
-
dropZone.style.background = "
|
|
1595
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1596
|
+
dropZone.style.background = v("surface-raised");
|
|
1028
1597
|
const file = e.dataTransfer?.files[0];
|
|
1029
1598
|
if (file) handleFile(file);
|
|
1030
1599
|
});
|
|
1031
1600
|
dropZone.addEventListener("mouseenter", () => {
|
|
1032
1601
|
if (!dragover) {
|
|
1033
|
-
dropZone.style.borderColor = "
|
|
1034
|
-
dropZone.style.background = "
|
|
1602
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1603
|
+
dropZone.style.background = v("surface-hover");
|
|
1035
1604
|
}
|
|
1036
1605
|
});
|
|
1037
1606
|
dropZone.addEventListener("mouseleave", () => {
|
|
1038
1607
|
if (!dragover) {
|
|
1039
|
-
dropZone.style.borderColor = "
|
|
1040
|
-
dropZone.style.background = "
|
|
1608
|
+
dropZone.style.borderColor = v("border-strong");
|
|
1609
|
+
dropZone.style.background = v("surface-raised");
|
|
1041
1610
|
}
|
|
1042
1611
|
});
|
|
1043
1612
|
wrap.appendChild(dropZone);
|
|
@@ -1048,67 +1617,246 @@ function makeCloseButton(onClose) {
|
|
|
1048
1617
|
const btn = document.createElement("button");
|
|
1049
1618
|
btn.style.cssText = `
|
|
1050
1619
|
display: flex; align-items: center; justify-content: center;
|
|
1051
|
-
width: 24px; height: 24px; border-radius:
|
|
1052
|
-
background:
|
|
1053
|
-
cursor: pointer; color:
|
|
1054
|
-
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")};
|
|
1055
1624
|
`;
|
|
1056
1625
|
btn.innerHTML = `<svg width="9" height="9" viewBox="0 0 10 10" fill="none">
|
|
1057
1626
|
<path d="M1 1l8 8M9 1L1 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
|
1058
1627
|
</svg>`;
|
|
1059
|
-
btn
|
|
1060
|
-
btn.style.background = "rgba(255,255,255,0.08)";
|
|
1061
|
-
btn.style.color = "rgba(255,255,255,0.75)";
|
|
1062
|
-
});
|
|
1063
|
-
btn.addEventListener("mouseleave", () => {
|
|
1064
|
-
btn.style.background = "rgba(255,255,255,0.04)";
|
|
1065
|
-
btn.style.color = "rgba(255,255,255,0.35)";
|
|
1066
|
-
});
|
|
1628
|
+
attachHover(btn, { color: v("fg") });
|
|
1067
1629
|
btn.addEventListener("click", onClose);
|
|
1068
1630
|
return btn;
|
|
1069
1631
|
}
|
|
1070
|
-
function makePrimaryButton(
|
|
1632
|
+
function makePrimaryButton(label2, color) {
|
|
1071
1633
|
const btn = document.createElement("button");
|
|
1072
|
-
btn
|
|
1634
|
+
markUi(btn);
|
|
1635
|
+
btn.textContent = label2;
|
|
1073
1636
|
btn.style.cssText = `
|
|
1074
|
-
padding: 7px
|
|
1075
|
-
background:
|
|
1076
|
-
font-size:
|
|
1077
|
-
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")};
|
|
1078
1641
|
`;
|
|
1079
1642
|
btn.addEventListener("mouseenter", () => btn.style.opacity = "0.88");
|
|
1080
1643
|
btn.addEventListener("mouseleave", () => btn.style.opacity = "1");
|
|
1644
|
+
attachPress(btn);
|
|
1081
1645
|
return btn;
|
|
1082
1646
|
}
|
|
1083
1647
|
function applyPopupStyles(el) {
|
|
1648
|
+
markUi(el);
|
|
1084
1649
|
el.style.cssText = `
|
|
1650
|
+
${surface(2)}
|
|
1085
1651
|
position: absolute;
|
|
1086
|
-
z-index:
|
|
1652
|
+
z-index: ${v("z-panel")};
|
|
1087
1653
|
width: 320px;
|
|
1088
|
-
|
|
1089
|
-
backdrop-filter: blur(24px) saturate(180%);
|
|
1090
|
-
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
|
1091
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
1092
|
-
border-radius: 14px;
|
|
1654
|
+
box-shadow: ${v("shadow-lg")};
|
|
1093
1655
|
padding: 14px;
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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")};
|
|
1097
1660
|
`;
|
|
1098
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
|
+
}
|
|
1099
1843
|
function openPopup(key, fieldType2, anchorEl, onClose) {
|
|
1100
1844
|
closePopup();
|
|
1101
1845
|
const done = () => {
|
|
1102
1846
|
onClose();
|
|
1103
1847
|
closePopup();
|
|
1104
1848
|
};
|
|
1105
|
-
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);
|
|
1106
1850
|
document.body.appendChild(popup);
|
|
1107
1851
|
popupEl = popup;
|
|
1108
1852
|
const { top, left, origin } = getPopupPosition(anchorEl);
|
|
1109
1853
|
popup.style.top = `${top}px`;
|
|
1110
1854
|
popup.style.left = `${left}px`;
|
|
1111
1855
|
popup.style.transformOrigin = origin;
|
|
1856
|
+
requestAnimationFrame(() => {
|
|
1857
|
+
popup.style.opacity = "1";
|
|
1858
|
+
popup.style.transform = "scale(1)";
|
|
1859
|
+
});
|
|
1112
1860
|
outsideListener = (e) => {
|
|
1113
1861
|
if (!popup.contains(e.target)) {
|
|
1114
1862
|
closePopup();
|
|
@@ -1143,17 +1891,17 @@ function closePopup() {
|
|
|
1143
1891
|
if (popupEl) {
|
|
1144
1892
|
const el = popupEl;
|
|
1145
1893
|
popupEl = null;
|
|
1146
|
-
el.style.
|
|
1147
|
-
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")}`;
|
|
1148
1895
|
el.style.opacity = "0";
|
|
1149
|
-
el.style.transform = "scale(0.
|
|
1896
|
+
el.style.transform = "scale(0.93)";
|
|
1150
1897
|
setTimeout(() => el.remove(), 200);
|
|
1151
1898
|
}
|
|
1152
1899
|
}
|
|
1153
1900
|
|
|
1154
1901
|
// src/list-panel.ts
|
|
1155
|
-
var PANEL_Z =
|
|
1156
|
-
var
|
|
1902
|
+
var PANEL_Z = v("z-panel");
|
|
1903
|
+
var TOOLBAR_RESERVE = "100px";
|
|
1904
|
+
var BACKDROP_Z = v("z-overlay");
|
|
1157
1905
|
var panelEl = null;
|
|
1158
1906
|
var backdropEl = null;
|
|
1159
1907
|
var styleInjected2 = false;
|
|
@@ -1166,6 +1914,7 @@ var currentOnTranslateEntry = null;
|
|
|
1166
1914
|
function injectStyles2() {
|
|
1167
1915
|
if (styleInjected2) return;
|
|
1168
1916
|
styleInjected2 = true;
|
|
1917
|
+
injectBaseStyles(state.config?.accentColor, state.config?.toolbarAccent === true);
|
|
1169
1918
|
const s = document.createElement("style");
|
|
1170
1919
|
s.textContent = `
|
|
1171
1920
|
@keyframes cancia-panel-in {
|
|
@@ -1183,9 +1932,6 @@ function injectStyles2() {
|
|
|
1183
1932
|
`;
|
|
1184
1933
|
document.head.appendChild(s);
|
|
1185
1934
|
}
|
|
1186
|
-
function accent3() {
|
|
1187
|
-
return state.config?.accentColor ?? "#6366f1";
|
|
1188
|
-
}
|
|
1189
1935
|
function escapeHtml(s) {
|
|
1190
1936
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1191
1937
|
}
|
|
@@ -1231,9 +1977,9 @@ function derivePreview(schema, data) {
|
|
|
1231
1977
|
let thumbnail = "";
|
|
1232
1978
|
for (const f of schema.fields) {
|
|
1233
1979
|
if (f.widget !== "image") continue;
|
|
1234
|
-
const
|
|
1235
|
-
if (typeof
|
|
1236
|
-
thumbnail =
|
|
1980
|
+
const v2 = data[f.name];
|
|
1981
|
+
if (typeof v2 === "string" && v2.trim()) {
|
|
1982
|
+
thumbnail = v2.trim();
|
|
1237
1983
|
break;
|
|
1238
1984
|
}
|
|
1239
1985
|
}
|
|
@@ -1245,52 +1991,73 @@ function locales() {
|
|
|
1245
1991
|
function buildShell(schema) {
|
|
1246
1992
|
const panel = document.createElement("div");
|
|
1247
1993
|
panel.dataset.canciaListPanel = "1";
|
|
1994
|
+
markUi(panel);
|
|
1248
1995
|
panel.style.cssText = `
|
|
1996
|
+
${surface(2)}
|
|
1249
1997
|
position: fixed;
|
|
1250
1998
|
top: 0; right: 0; bottom: 0;
|
|
1999
|
+
padding-bottom: ${TOOLBAR_RESERVE};
|
|
1251
2000
|
width: min(420px, 100vw);
|
|
1252
|
-
|
|
1253
|
-
color: #1a1a1d;
|
|
2001
|
+
color: ${v("fg")};
|
|
1254
2002
|
z-index: ${PANEL_Z};
|
|
1255
|
-
|
|
2003
|
+
border: 0;
|
|
2004
|
+
border-left: 1px solid ${v("border")};
|
|
2005
|
+
border-radius: 0;
|
|
2006
|
+
box-shadow: ${v("shadow-lg")};
|
|
1256
2007
|
display: flex;
|
|
1257
2008
|
flex-direction: column;
|
|
1258
|
-
font-family:
|
|
1259
|
-
|
|
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;
|
|
1260
2015
|
`;
|
|
1261
|
-
|
|
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 = `
|
|
1262
2024
|
<header style="
|
|
1263
2025
|
display: flex; align-items: center; justify-content: space-between;
|
|
1264
|
-
padding:
|
|
1265
|
-
border-bottom: 1px solid
|
|
2026
|
+
padding: ${v("space-4")} ${v("space-5")};
|
|
2027
|
+
border-bottom: 1px solid ${v("border")};
|
|
1266
2028
|
">
|
|
1267
2029
|
<div>
|
|
1268
|
-
<div style="font-size:
|
|
1269
|
-
<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>
|
|
1270
2032
|
</div>
|
|
1271
2033
|
<button data-cancia-close style="
|
|
2034
|
+
${iconButton()}
|
|
1272
2035
|
appearance: none; border: 0; background: transparent;
|
|
1273
|
-
cursor: pointer;
|
|
1274
|
-
color: #555; transition: background 0.12s, color 0.12s;
|
|
2036
|
+
cursor: pointer;
|
|
1275
2037
|
" aria-label="Close panel">
|
|
1276
2038
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
1277
2039
|
<path d="M4 4l10 10M14 4L4 14"/>
|
|
1278
2040
|
</svg>
|
|
1279
2041
|
</button>
|
|
1280
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. -->
|
|
1281
2049
|
<div data-cancia-tabs style="
|
|
1282
|
-
display: flex; gap:
|
|
1283
|
-
padding:
|
|
1284
|
-
border-bottom: 1px solid
|
|
1285
|
-
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")};
|
|
1286
2053
|
"></div>
|
|
1287
|
-
<div style="padding:
|
|
2054
|
+
<div style="padding: ${v("space-3")} ${v("space-5")}; border-bottom: 1px solid ${v("border")};">
|
|
1288
2055
|
<button data-cancia-add style="
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
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;
|
|
1292
2060
|
display: flex; align-items: center; justify-content: center; gap: 6px;
|
|
1293
|
-
transition: background 0.12s;
|
|
1294
2061
|
">
|
|
1295
2062
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
|
1296
2063
|
<path d="M7 2v10M2 7h10"/>
|
|
@@ -1300,13 +2067,18 @@ function buildShell(schema) {
|
|
|
1300
2067
|
</div>
|
|
1301
2068
|
<div data-cancia-entries style="
|
|
1302
2069
|
flex: 1; overflow-y: auto;
|
|
1303
|
-
padding:
|
|
2070
|
+
padding: ${v("space-2")} ${v("space-3")} ${v("space-4")};
|
|
1304
2071
|
">
|
|
1305
|
-
<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>
|
|
1306
2073
|
</div>
|
|
1307
2074
|
`;
|
|
1308
|
-
|
|
1309
|
-
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") });
|
|
1310
2082
|
return { panel, body, tabsRow };
|
|
1311
2083
|
}
|
|
1312
2084
|
function renderTabs(tabsRow, activeLocale, onSwitch) {
|
|
@@ -1322,22 +2094,22 @@ function renderTabs(tabsRow, activeLocale, onSwitch) {
|
|
|
1322
2094
|
const isActive = loc === activeLocale;
|
|
1323
2095
|
tab.style.cssText = `
|
|
1324
2096
|
appearance: none; border: 0; background: transparent;
|
|
1325
|
-
font-family: inherit; font-size:
|
|
2097
|
+
font-family: inherit; font-size: ${v("text-sm")}; font-weight: 600;
|
|
1326
2098
|
letter-spacing: 0.04em; text-transform: uppercase;
|
|
1327
|
-
padding:
|
|
2099
|
+
padding: ${v("space-2")} 10px 9px;
|
|
1328
2100
|
cursor: ${isActive ? "default" : "pointer"};
|
|
1329
|
-
color: ${isActive ?
|
|
1330
|
-
border-bottom: 2px solid ${isActive ?
|
|
2101
|
+
color: ${isActive ? v("accent") : v("fg-muted")};
|
|
2102
|
+
border-bottom: 2px solid ${isActive ? v("accent") : "transparent"};
|
|
1331
2103
|
margin-bottom: -1px;
|
|
1332
2104
|
transition: color 0.12s, border-color 0.12s;
|
|
1333
2105
|
`;
|
|
1334
2106
|
tab.textContent = loc;
|
|
1335
2107
|
if (!isActive) {
|
|
1336
2108
|
tab.addEventListener("mouseenter", () => {
|
|
1337
|
-
tab.style.color = "
|
|
2109
|
+
tab.style.color = v("fg-strong");
|
|
1338
2110
|
});
|
|
1339
2111
|
tab.addEventListener("mouseleave", () => {
|
|
1340
|
-
tab.style.color = "
|
|
2112
|
+
tab.style.color = v("fg-muted");
|
|
1341
2113
|
});
|
|
1342
2114
|
tab.addEventListener("click", () => onSwitch(loc));
|
|
1343
2115
|
}
|
|
@@ -1366,7 +2138,7 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1366
2138
|
});
|
|
1367
2139
|
if (rows.length === 0) {
|
|
1368
2140
|
body.innerHTML = `
|
|
1369
|
-
<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")};">
|
|
1370
2142
|
No entries yet. Click "Add ${escapeHtml(schema.labelSingular.toLowerCase())}" above to create one.
|
|
1371
2143
|
</div>
|
|
1372
2144
|
`;
|
|
@@ -1392,7 +2164,7 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1392
2164
|
}
|
|
1393
2165
|
const err = document.createElement("div");
|
|
1394
2166
|
err.textContent = "Couldn't save the new order. Reverted.";
|
|
1395
|
-
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")};`;
|
|
1396
2168
|
body.insertBefore(err, body.firstChild);
|
|
1397
2169
|
setTimeout(() => err.remove(), 3e3);
|
|
1398
2170
|
}
|
|
@@ -1400,7 +2172,7 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1400
2172
|
rows.forEach((row) => {
|
|
1401
2173
|
const isStub = row.entry === null;
|
|
1402
2174
|
const wrap = document.createElement("div");
|
|
1403
|
-
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;`;
|
|
1404
2176
|
const handle = document.createElement("div");
|
|
1405
2177
|
handle.textContent = "\u22EE\u22EE";
|
|
1406
2178
|
handle.title = "Drag to reorder";
|
|
@@ -1408,14 +2180,22 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1408
2180
|
handle.style.cssText = `
|
|
1409
2181
|
cursor: grab; user-select: none;
|
|
1410
2182
|
display: flex; align-items: center; justify-content: center;
|
|
1411
|
-
color:
|
|
2183
|
+
color: ${v("fg-faint")}; font-size: ${v("text-base")}; letter-spacing: -2px;
|
|
1412
2184
|
padding: 0 2px; flex-shrink: 0;
|
|
2185
|
+
transition: color 0.12s;
|
|
1413
2186
|
`;
|
|
2187
|
+
handle.addEventListener("mouseenter", () => {
|
|
2188
|
+
handle.style.color = v("accent");
|
|
2189
|
+
});
|
|
2190
|
+
handle.addEventListener("mouseleave", () => {
|
|
2191
|
+
handle.style.color = v("fg-faint");
|
|
2192
|
+
});
|
|
1414
2193
|
const item = document.createElement("button");
|
|
1415
2194
|
item.style.cssText = `
|
|
1416
|
-
appearance: none; border: 1px solid transparent;
|
|
2195
|
+
appearance: none; border: 1px solid transparent;
|
|
2196
|
+
background: ${isStub ? v("warning-soft") : v("surface-raised")};
|
|
1417
2197
|
text-align: left; flex: 1; min-width: 0;
|
|
1418
|
-
padding:
|
|
2198
|
+
padding: ${v("space-3")} 14px; border-radius: ${v("radius-sm")}; cursor: pointer;
|
|
1419
2199
|
transition: background 0.12s, border-color 0.12s, transform 0.12s;
|
|
1420
2200
|
display: block;
|
|
1421
2201
|
`;
|
|
@@ -1459,27 +2239,36 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1459
2239
|
const titleValue = row.entry.data[schema.titleField];
|
|
1460
2240
|
const title = typeof titleValue === "string" && titleValue.trim().length > 0 ? titleValue : `(untitled ${schema.labelSingular.toLowerCase()})`;
|
|
1461
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>` : "";
|
|
1462
2249
|
const textCol = `
|
|
1463
2250
|
<div style="min-width: 0; flex: 1;">
|
|
1464
|
-
<div style="
|
|
1465
|
-
|
|
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>` : ""}
|
|
1466
2255
|
</div>
|
|
1467
2256
|
`;
|
|
1468
2257
|
const thumb = thumbnail ? `<img src="${escapeHtml(thumbnail)}" alt="" loading="lazy" style="
|
|
1469
2258
|
width: 44px; height: 44px; flex-shrink: 0; object-fit: cover;
|
|
1470
|
-
border-radius:
|
|
2259
|
+
border-radius: ${v("radius-sm")}; background: ${v("surface-raised")}; border: 1px solid ${v("border")};
|
|
1471
2260
|
" onerror="this.style.display='none'" />` : "";
|
|
1472
2261
|
item.innerHTML = `
|
|
1473
|
-
<div style="display: flex; align-items: center; gap:
|
|
2262
|
+
<div style="display: flex; align-items: center; gap: ${v("space-3")};">
|
|
1474
2263
|
${thumb}${textCol}
|
|
1475
2264
|
</div>
|
|
1476
2265
|
`;
|
|
1477
2266
|
item.addEventListener("mouseenter", () => {
|
|
1478
|
-
item.style.background = "
|
|
1479
|
-
item.style.borderColor = "
|
|
2267
|
+
item.style.background = v("surface-hover");
|
|
2268
|
+
item.style.borderColor = v("border-strong");
|
|
1480
2269
|
});
|
|
1481
2270
|
item.addEventListener("mouseleave", () => {
|
|
1482
|
-
item.style.background = "
|
|
2271
|
+
item.style.background = v("surface-raised");
|
|
1483
2272
|
item.style.borderColor = "transparent";
|
|
1484
2273
|
});
|
|
1485
2274
|
item.addEventListener("click", () => currentOnEditEntry?.(row.entry, activeLocale));
|
|
@@ -1489,23 +2278,26 @@ function renderEntries(body, schema, activeLocale, entriesInLocale, translations
|
|
|
1489
2278
|
const sourceTitleValue = sourceEntry?.data[schema.titleField];
|
|
1490
2279
|
const sourceTitle = typeof sourceTitleValue === "string" && sourceTitleValue.trim().length > 0 ? sourceTitleValue : row.id;
|
|
1491
2280
|
item.innerHTML = `
|
|
1492
|
-
<div style="display: flex; align-items: center; gap:
|
|
2281
|
+
<div style="display: flex; align-items: center; gap: ${v("space-2")};">
|
|
1493
2282
|
<span style="
|
|
1494
2283
|
font-size: 9px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
|
|
1495
|
-
|
|
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")};
|
|
1496
2288
|
padding: 2px 6px; border-radius: 4px;
|
|
1497
2289
|
">Not translated</span>
|
|
1498
|
-
<span style="font-size:
|
|
2290
|
+
<span style="font-size: ${v("text-xs")}; color: ${v("fg-muted")};">from ${escapeHtml(sourceLocale)}</span>
|
|
1499
2291
|
</div>
|
|
1500
|
-
<div style="font-weight: 600; font-size: 14px; color:
|
|
1501
|
-
<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>
|
|
1502
2294
|
`;
|
|
1503
2295
|
item.addEventListener("mouseenter", () => {
|
|
1504
|
-
item.style.background = "
|
|
1505
|
-
item.style.borderColor = "
|
|
2296
|
+
item.style.background = "rgba(217, 119, 6, 0.18)";
|
|
2297
|
+
item.style.borderColor = v("warning");
|
|
1506
2298
|
});
|
|
1507
2299
|
item.addEventListener("mouseleave", () => {
|
|
1508
|
-
item.style.background = "
|
|
2300
|
+
item.style.background = v("warning-soft");
|
|
1509
2301
|
item.style.borderColor = "transparent";
|
|
1510
2302
|
});
|
|
1511
2303
|
item.addEventListener(
|
|
@@ -1524,11 +2316,12 @@ async function openListPanel(opts) {
|
|
|
1524
2316
|
injectStyles2();
|
|
1525
2317
|
const backdrop = document.createElement("div");
|
|
1526
2318
|
backdrop.dataset.canciaPanelBackdrop = "1";
|
|
2319
|
+
markUi(backdrop);
|
|
1527
2320
|
backdrop.style.cssText = `
|
|
1528
2321
|
position: fixed; inset: 0;
|
|
1529
|
-
background: rgba(10,10,12,0.
|
|
2322
|
+
background: rgba(10,10,12,0.20);
|
|
1530
2323
|
z-index: ${BACKDROP_Z};
|
|
1531
|
-
animation: cancia-backdrop-in
|
|
2324
|
+
animation: cancia-backdrop-in ${v("duration")} ${v("ease-out")} forwards;
|
|
1532
2325
|
`;
|
|
1533
2326
|
backdrop.addEventListener("click", () => closeListPanel());
|
|
1534
2327
|
document.body.appendChild(backdrop);
|
|
@@ -1578,7 +2371,7 @@ async function refreshListPanel() {
|
|
|
1578
2371
|
} catch (err) {
|
|
1579
2372
|
if (!panelEl) return;
|
|
1580
2373
|
body.innerHTML = `
|
|
1581
|
-
<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")};">
|
|
1582
2375
|
Failed to load entries: ${escapeHtml(err instanceof Error ? err.message : String(err))}
|
|
1583
2376
|
</div>
|
|
1584
2377
|
`;
|
|
@@ -1586,7 +2379,7 @@ async function refreshListPanel() {
|
|
|
1586
2379
|
}
|
|
1587
2380
|
function closeListPanel() {
|
|
1588
2381
|
if (panelEl) {
|
|
1589
|
-
panelEl.style.animation =
|
|
2382
|
+
panelEl.style.animation = `cancia-panel-out ${v("duration-exit")} ${v("ease-out")} forwards`;
|
|
1590
2383
|
const el = panelEl;
|
|
1591
2384
|
setTimeout(() => el.remove(), 180);
|
|
1592
2385
|
panelEl = null;
|
|
@@ -1594,7 +2387,7 @@ function closeListPanel() {
|
|
|
1594
2387
|
if (backdropEl) {
|
|
1595
2388
|
const el = backdropEl;
|
|
1596
2389
|
el.style.opacity = "0";
|
|
1597
|
-
el.style.transition =
|
|
2390
|
+
el.style.transition = `opacity ${v("duration-exit")} ${v("ease-out")}`;
|
|
1598
2391
|
setTimeout(() => el.remove(), 180);
|
|
1599
2392
|
backdropEl = null;
|
|
1600
2393
|
}
|
|
@@ -1608,65 +2401,146 @@ function closeListPanel() {
|
|
|
1608
2401
|
function isListPanelOpen() {
|
|
1609
2402
|
return panelEl !== null;
|
|
1610
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
|
+
}
|
|
1611
2449
|
|
|
1612
2450
|
// src/entry-modal.ts
|
|
1613
2451
|
import {
|
|
1614
|
-
rowsToPortableText,
|
|
1615
|
-
portableTextToRows,
|
|
1616
|
-
portableTextSubsetSchema,
|
|
1617
|
-
PT_STYLES
|
|
2452
|
+
rowsToPortableText as rowsToPortableText2,
|
|
2453
|
+
portableTextToRows as portableTextToRows2,
|
|
2454
|
+
portableTextSubsetSchema as portableTextSubsetSchema2,
|
|
2455
|
+
PT_STYLES as PT_STYLES2
|
|
1618
2456
|
} from "@cancia/astro/richtext";
|
|
1619
2457
|
import { slugify } from "@cancia/astro/schema";
|
|
1620
|
-
var MODAL_Z =
|
|
1621
|
-
var BACKDROP_Z2 =
|
|
2458
|
+
var MODAL_Z = v("z-bar");
|
|
2459
|
+
var BACKDROP_Z2 = v("z-panel");
|
|
1622
2460
|
var modalEl = null;
|
|
1623
2461
|
var backdropEl2 = null;
|
|
1624
2462
|
var escListener = null;
|
|
1625
2463
|
var styleInjected3 = false;
|
|
2464
|
+
var mountedInPanel = false;
|
|
1626
2465
|
function injectStyles3() {
|
|
1627
2466
|
if (styleInjected3) return;
|
|
1628
2467
|
styleInjected3 = true;
|
|
2468
|
+
injectBaseStyles(state.config?.accentColor, state.config?.toolbarAccent === true);
|
|
1629
2469
|
const s = document.createElement("style");
|
|
1630
2470
|
s.textContent = `
|
|
1631
|
-
@keyframes cancia-modal-in {
|
|
1632
|
-
from { opacity: 0; transform: translate(-50%, calc(-50% + 8px)) scale(0.985); }
|
|
1633
|
-
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
1634
|
-
}
|
|
1635
|
-
@keyframes cancia-modal-out {
|
|
1636
|
-
from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
1637
|
-
to { opacity: 0; transform: translate(-50%, calc(-50% + 8px)) scale(0.985); }
|
|
1638
|
-
}
|
|
1639
2471
|
.cancia-form-input:focus,
|
|
1640
2472
|
.cancia-form-textarea:focus,
|
|
1641
2473
|
.cancia-form-select:focus {
|
|
1642
|
-
border-color: var(--cancia-accent-border,
|
|
1643
|
-
background:
|
|
2474
|
+
border-color: var(--cancia-accent-border, ${v("accent-ring")});
|
|
2475
|
+
background: ${v("surface-hover")};
|
|
1644
2476
|
outline: none;
|
|
1645
2477
|
}
|
|
1646
2478
|
.cancia-form-input::placeholder,
|
|
1647
2479
|
.cancia-form-textarea::placeholder {
|
|
1648
|
-
color:
|
|
2480
|
+
color: ${v("fg-faint")};
|
|
1649
2481
|
}
|
|
1650
2482
|
.cancia-form-select option {
|
|
1651
|
-
|
|
1652
|
-
|
|
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")};
|
|
1653
2487
|
}
|
|
1654
2488
|
.cancia-field-error {
|
|
1655
|
-
color:
|
|
1656
|
-
font-size:
|
|
2489
|
+
color: ${v("danger")};
|
|
2490
|
+
font-size: ${v("text-xs")};
|
|
1657
2491
|
margin-top: 5px;
|
|
1658
2492
|
line-height: 1.35;
|
|
1659
2493
|
}
|
|
1660
2494
|
`;
|
|
1661
2495
|
document.head.appendChild(s);
|
|
1662
2496
|
}
|
|
1663
|
-
function
|
|
1664
|
-
|
|
2497
|
+
function accent3() {
|
|
2498
|
+
const useAccent = state.config?.toolbarAccent === true;
|
|
2499
|
+
return (useAccent ? state.config?.accentColor : void 0) ?? "#18181b";
|
|
1665
2500
|
}
|
|
1666
2501
|
function accentBorder() {
|
|
1667
|
-
const a =
|
|
2502
|
+
const a = accent3();
|
|
1668
2503
|
if (/^#[0-9a-f]{6}$/i.test(a)) return `${a}99`;
|
|
1669
|
-
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);
|
|
1670
2544
|
}
|
|
1671
2545
|
function isoToLocalInput(iso) {
|
|
1672
2546
|
if (!iso) return "";
|
|
@@ -1682,44 +2556,39 @@ function localInputToIso(local) {
|
|
|
1682
2556
|
return d.toISOString();
|
|
1683
2557
|
}
|
|
1684
2558
|
var INPUT_BASE = `
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
color: rgba(255,255,255,0.9);
|
|
1688
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
1689
|
-
border-radius: 8px;
|
|
1690
|
-
padding: 8px 11px;
|
|
1691
|
-
font-size: 13px;
|
|
2559
|
+
${input()}
|
|
2560
|
+
box-sizing: border-box;
|
|
1692
2561
|
font-family: inherit;
|
|
1693
2562
|
line-height: 1.5;
|
|
1694
|
-
outline: none;
|
|
1695
|
-
transition: border-color 0.18s, background 0.18s;
|
|
1696
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>")`;
|
|
1697
2565
|
var MAX_FIELD_DEPTH = 6;
|
|
1698
2566
|
function renderField(field, initial, depth = 0) {
|
|
1699
2567
|
const wrapper = document.createElement("div");
|
|
1700
|
-
wrapper.style.cssText = `margin-bottom:
|
|
2568
|
+
wrapper.style.cssText = `margin-bottom: ${v("space-4")};`;
|
|
1701
2569
|
const labelRow = document.createElement("label");
|
|
1702
2570
|
labelRow.style.cssText = `
|
|
2571
|
+
${label()}
|
|
1703
2572
|
display: flex; align-items: baseline; justify-content: space-between;
|
|
1704
|
-
gap:
|
|
1705
|
-
font-size: 11px; font-weight: 600;
|
|
1706
|
-
color: rgba(255,255,255,0.75);
|
|
1707
|
-
letter-spacing: 0.04em;
|
|
2573
|
+
gap: ${v("space-2")};
|
|
1708
2574
|
margin-bottom: 5px;
|
|
1709
2575
|
`;
|
|
1710
2576
|
const labelText = document.createElement("span");
|
|
1711
|
-
labelText.textContent = field.label;
|
|
1712
|
-
if (field.required) {
|
|
1713
|
-
const star = document.createElement("span");
|
|
1714
|
-
star.textContent = " *";
|
|
1715
|
-
star.style.color = "rgba(255,135,134,0.8)";
|
|
1716
|
-
labelText.appendChild(star);
|
|
1717
|
-
}
|
|
2577
|
+
labelText.textContent = humanise(field.label);
|
|
1718
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
|
+
}
|
|
1719
2588
|
if (field.label) wrapper.appendChild(labelRow);
|
|
1720
2589
|
if (field.description) {
|
|
1721
2590
|
const help = document.createElement("div");
|
|
1722
|
-
help.style.cssText =
|
|
2591
|
+
help.style.cssText = `${hint()} margin-bottom: 6px;`;
|
|
1723
2592
|
help.textContent = field.description;
|
|
1724
2593
|
wrapper.appendChild(help);
|
|
1725
2594
|
}
|
|
@@ -1770,7 +2639,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1770
2639
|
case "textarea": {
|
|
1771
2640
|
const ta = document.createElement("textarea");
|
|
1772
2641
|
ta.className = "cancia-form-textarea";
|
|
1773
|
-
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")};`;
|
|
1774
2643
|
ta.rows = 5;
|
|
1775
2644
|
if (field.placeholder) ta.placeholder = field.placeholder;
|
|
1776
2645
|
if (typeof initial === "string") ta.value = initial;
|
|
@@ -1783,10 +2652,10 @@ function renderField(field, initial, depth = 0) {
|
|
|
1783
2652
|
row.style.cssText = `display: flex; align-items: center; gap: 9px; cursor: pointer; user-select: none; padding: 6px 0;`;
|
|
1784
2653
|
const cb = document.createElement("input");
|
|
1785
2654
|
cb.type = "checkbox";
|
|
1786
|
-
cb.style.cssText = `width: 16px; height: 16px; accent-color: ${
|
|
2655
|
+
cb.style.cssText = `width: 16px; height: 16px; accent-color: ${v("accent")};`;
|
|
1787
2656
|
if (initial === true) cb.checked = true;
|
|
1788
2657
|
const txt = document.createElement("span");
|
|
1789
|
-
txt.style.cssText = `font-size:
|
|
2658
|
+
txt.style.cssText = `font-size: ${v("text-base")}; color: ${v("fg")};`;
|
|
1790
2659
|
txt.textContent = field.placeholder ?? `Enable ${field.label.toLowerCase()}`;
|
|
1791
2660
|
row.appendChild(cb);
|
|
1792
2661
|
row.appendChild(txt);
|
|
@@ -1797,7 +2666,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1797
2666
|
case "select": {
|
|
1798
2667
|
const sel = document.createElement("select");
|
|
1799
2668
|
sel.className = "cancia-form-select";
|
|
1800
|
-
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;`;
|
|
1801
2670
|
if (!field.required) {
|
|
1802
2671
|
const empty = document.createElement("option");
|
|
1803
2672
|
empty.value = "";
|
|
@@ -1821,19 +2690,19 @@ function renderField(field, initial, depth = 0) {
|
|
|
1821
2690
|
const container = document.createElement("div");
|
|
1822
2691
|
container.style.cssText = `
|
|
1823
2692
|
display: flex; gap: 10px; align-items: stretch;
|
|
1824
|
-
background:
|
|
1825
|
-
border: 1px dashed
|
|
1826
|
-
border-radius:
|
|
2693
|
+
background: ${v("surface-raised")};
|
|
2694
|
+
border: 1px dashed ${v("border-strong")};
|
|
2695
|
+
border-radius: ${v("radius")};
|
|
1827
2696
|
padding: 10px;
|
|
1828
2697
|
`;
|
|
1829
2698
|
const preview = document.createElement("div");
|
|
1830
2699
|
preview.style.cssText = `
|
|
1831
2700
|
width: 72px; height: 72px; flex-shrink: 0;
|
|
1832
|
-
background:
|
|
1833
|
-
border: 1px solid
|
|
1834
|
-
border-radius:
|
|
2701
|
+
background: ${v("surface-raised")} no-repeat center / cover;
|
|
2702
|
+
border: 1px solid ${v("border")};
|
|
2703
|
+
border-radius: ${v("radius-sm")};
|
|
1835
2704
|
display: flex; align-items: center; justify-content: center;
|
|
1836
|
-
color:
|
|
2705
|
+
color: ${v("fg-faint")};
|
|
1837
2706
|
`;
|
|
1838
2707
|
const updatePreview = (url) => {
|
|
1839
2708
|
if (url) {
|
|
@@ -1856,29 +2725,28 @@ function renderField(field, initial, depth = 0) {
|
|
|
1856
2725
|
const uploadBtn = document.createElement("button");
|
|
1857
2726
|
uploadBtn.type = "button";
|
|
1858
2727
|
uploadBtn.style.cssText = `
|
|
1859
|
-
|
|
1860
|
-
background:
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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;
|
|
1865
2736
|
`;
|
|
1866
2737
|
uploadBtn.textContent = "Upload\u2026";
|
|
1867
|
-
uploadBtn
|
|
1868
|
-
uploadBtn.style.background = "rgba(255,255,255,0.1)";
|
|
1869
|
-
});
|
|
1870
|
-
uploadBtn.addEventListener("mouseleave", () => {
|
|
1871
|
-
uploadBtn.style.background = "rgba(255,255,255,0.06)";
|
|
1872
|
-
});
|
|
2738
|
+
attachHover(uploadBtn, { bg: v("surface-active") });
|
|
1873
2739
|
uploadBtn.addEventListener("click", () => fileInput.click());
|
|
1874
2740
|
const clearBtn = document.createElement("button");
|
|
1875
2741
|
clearBtn.type = "button";
|
|
1876
2742
|
clearBtn.style.cssText = `
|
|
1877
|
-
|
|
1878
|
-
|
|
2743
|
+
${button("ghost")}
|
|
2744
|
+
color: ${v("fg-muted")};
|
|
1879
2745
|
border: 1px solid transparent;
|
|
1880
|
-
|
|
1881
|
-
|
|
2746
|
+
height: auto;
|
|
2747
|
+
font-size: ${v("text-xs")};
|
|
2748
|
+
padding: 5px 8px;
|
|
2749
|
+
cursor: pointer;
|
|
1882
2750
|
`;
|
|
1883
2751
|
clearBtn.textContent = "Clear";
|
|
1884
2752
|
clearBtn.addEventListener("click", () => {
|
|
@@ -1891,7 +2759,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1891
2759
|
const urlField = document.createElement("input");
|
|
1892
2760
|
urlField.type = "url";
|
|
1893
2761
|
urlField.className = "cancia-form-input";
|
|
1894
|
-
urlField.style.cssText = `${INPUT_BASE} font-size:
|
|
2762
|
+
urlField.style.cssText = `${INPUT_BASE} font-size: ${v("text-xs")}; padding: 6px 9px;`;
|
|
1895
2763
|
urlField.placeholder = "https://\u2026 or upload";
|
|
1896
2764
|
urlField.value = initialUrl;
|
|
1897
2765
|
urlField.addEventListener("input", () => {
|
|
@@ -1899,7 +2767,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1899
2767
|
updatePreview(currentUrl);
|
|
1900
2768
|
});
|
|
1901
2769
|
const progressEl = document.createElement("div");
|
|
1902
|
-
progressEl.style.cssText = `font-size: 10px; color:
|
|
2770
|
+
progressEl.style.cssText = `font-size: 10px; color: ${v("fg-muted")}; height: 12px;`;
|
|
1903
2771
|
right.appendChild(btnRow);
|
|
1904
2772
|
right.appendChild(urlField);
|
|
1905
2773
|
right.appendChild(progressEl);
|
|
@@ -1911,7 +2779,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1911
2779
|
const file = fileInput.files?.[0];
|
|
1912
2780
|
if (!file) return;
|
|
1913
2781
|
uploadBtn.disabled = true;
|
|
1914
|
-
progressEl.style.color = "
|
|
2782
|
+
progressEl.style.color = v("fg-muted");
|
|
1915
2783
|
try {
|
|
1916
2784
|
const url = await uploadImage(file, (pct) => {
|
|
1917
2785
|
progressEl.textContent = `Uploading\u2026 ${pct}%`;
|
|
@@ -1925,7 +2793,7 @@ function renderField(field, initial, depth = 0) {
|
|
|
1925
2793
|
}, 1500);
|
|
1926
2794
|
} catch (err) {
|
|
1927
2795
|
progressEl.textContent = `Upload failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
1928
|
-
progressEl.style.color = "
|
|
2796
|
+
progressEl.style.color = v("danger");
|
|
1929
2797
|
} finally {
|
|
1930
2798
|
uploadBtn.disabled = false;
|
|
1931
2799
|
fileInput.value = "";
|
|
@@ -1935,51 +2803,51 @@ function renderField(field, initial, depth = 0) {
|
|
|
1935
2803
|
break;
|
|
1936
2804
|
}
|
|
1937
2805
|
case "datetime": {
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
if (typeof initial === "string")
|
|
1943
|
-
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);
|
|
1944
2812
|
getValue2 = () => {
|
|
1945
|
-
const
|
|
1946
|
-
if (!
|
|
1947
|
-
return localInputToIso(
|
|
2813
|
+
const v2 = input2.value.trim();
|
|
2814
|
+
if (!v2) return void 0;
|
|
2815
|
+
return localInputToIso(v2);
|
|
1948
2816
|
};
|
|
1949
2817
|
break;
|
|
1950
2818
|
}
|
|
1951
2819
|
case "number": {
|
|
1952
|
-
const
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
if (field.min !== void 0)
|
|
1957
|
-
if (field.max !== void 0)
|
|
1958
|
-
if (typeof initial === "number")
|
|
1959
|
-
else if (typeof initial === "string" && initial !== "")
|
|
1960
|
-
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);
|
|
1961
2829
|
getValue2 = () => {
|
|
1962
|
-
const
|
|
1963
|
-
if (
|
|
1964
|
-
const n = Number(
|
|
2830
|
+
const v2 = input2.value.trim();
|
|
2831
|
+
if (v2 === "") return void 0;
|
|
2832
|
+
const n = Number(v2);
|
|
1965
2833
|
return Number.isNaN(n) ? void 0 : n;
|
|
1966
2834
|
};
|
|
1967
2835
|
break;
|
|
1968
2836
|
}
|
|
1969
2837
|
default: {
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
if (field.placeholder)
|
|
1975
|
-
if (field.minLength !== void 0)
|
|
1976
|
-
if (field.maxLength !== void 0)
|
|
1977
|
-
if (typeof initial === "string")
|
|
1978
|
-
wrapper.appendChild(
|
|
1979
|
-
getValue2 = () =>
|
|
1980
|
-
onInput = (cb) =>
|
|
1981
|
-
setValue = (
|
|
1982
|
-
|
|
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;
|
|
1983
2851
|
};
|
|
1984
2852
|
break;
|
|
1985
2853
|
}
|
|
@@ -2008,18 +2876,14 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2008
2876
|
const itemSchema = field.of;
|
|
2009
2877
|
const container = document.createElement("div");
|
|
2010
2878
|
container.style.cssText = `
|
|
2011
|
-
|
|
2012
|
-
background: rgba(255,255,255,0.02);
|
|
2013
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
2014
|
-
border-radius: 10px;
|
|
2015
|
-
padding: 10px;
|
|
2879
|
+
${group()}
|
|
2016
2880
|
`;
|
|
2017
2881
|
const rowsWrap = document.createElement("div");
|
|
2018
|
-
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap:
|
|
2882
|
+
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap: ${v("space-2")};`;
|
|
2019
2883
|
container.appendChild(rowsWrap);
|
|
2020
2884
|
if (!itemSchema || depth >= MAX_FIELD_DEPTH) {
|
|
2021
2885
|
const note = document.createElement("div");
|
|
2022
|
-
note.style.cssText = `font-size:
|
|
2886
|
+
note.style.cssText = `font-size: ${v("text-xs")}; color: ${v("fg-muted")};`;
|
|
2023
2887
|
note.textContent = itemSchema ? "Nesting too deep to edit here." : "This array has no item schema.";
|
|
2024
2888
|
container.appendChild(note);
|
|
2025
2889
|
return { control: container, getValue: () => [], validate: () => ({ value: [], ok: true }) };
|
|
@@ -2029,11 +2893,7 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2029
2893
|
function makeRow(itemValue) {
|
|
2030
2894
|
const row = document.createElement("div");
|
|
2031
2895
|
row.style.cssText = `
|
|
2032
|
-
|
|
2033
|
-
background: rgba(255,255,255,0.02);
|
|
2034
|
-
border: 1px solid rgba(255,255,255,0.06);
|
|
2035
|
-
border-radius: 8px;
|
|
2036
|
-
padding: 8px;
|
|
2896
|
+
${groupRow()}
|
|
2037
2897
|
`;
|
|
2038
2898
|
const handle = document.createElement("div");
|
|
2039
2899
|
handle.textContent = "\u22EE\u22EE";
|
|
@@ -2041,10 +2901,12 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2041
2901
|
handle.draggable = true;
|
|
2042
2902
|
handle.style.cssText = `
|
|
2043
2903
|
cursor: grab; user-select: none;
|
|
2044
|
-
color:
|
|
2045
|
-
font-size:
|
|
2046
|
-
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;
|
|
2047
2907
|
letter-spacing: -2px;
|
|
2908
|
+
opacity: 0;
|
|
2909
|
+
transition: opacity ${v("duration-fast")} ${v("ease")};
|
|
2048
2910
|
`;
|
|
2049
2911
|
const { wrapper, fieldState } = renderField(itemSchema, itemValue, depth + 1);
|
|
2050
2912
|
wrapper.style.marginBottom = "0";
|
|
@@ -2055,19 +2917,16 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2055
2917
|
removeBtn.textContent = "\xD7";
|
|
2056
2918
|
removeBtn.title = "Remove";
|
|
2057
2919
|
removeBtn.style.cssText = `
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2920
|
+
${button("danger")}
|
|
2921
|
+
flex-shrink: 0;
|
|
2922
|
+
border: 1px solid transparent;
|
|
2923
|
+
height: auto;
|
|
2061
2924
|
font-size: 16px; line-height: 1;
|
|
2062
|
-
padding: 2px 7px;
|
|
2063
|
-
|
|
2925
|
+
padding: 2px 7px;
|
|
2926
|
+
cursor: pointer;
|
|
2064
2927
|
`;
|
|
2065
|
-
removeBtn
|
|
2066
|
-
|
|
2067
|
-
});
|
|
2068
|
-
removeBtn.addEventListener("mouseleave", () => {
|
|
2069
|
-
removeBtn.style.background = "transparent";
|
|
2070
|
-
});
|
|
2928
|
+
attachHover(removeBtn, { bg: v("danger-soft") });
|
|
2929
|
+
attachRowReveal(row, [handle, removeBtn]);
|
|
2071
2930
|
row.appendChild(handle);
|
|
2072
2931
|
row.appendChild(wrapper);
|
|
2073
2932
|
row.appendChild(removeBtn);
|
|
@@ -2119,19 +2978,18 @@ function renderArrayField(field, initial, setOwnError, depth) {
|
|
|
2119
2978
|
const itemLabel = itemSchema.label || "item";
|
|
2120
2979
|
addBtn.textContent = `+ Add ${itemLabel.toLowerCase()}`;
|
|
2121
2980
|
addBtn.style.cssText = `
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
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;
|
|
2128
2991
|
`;
|
|
2129
|
-
addBtn
|
|
2130
|
-
addBtn.style.background = "rgba(255,255,255,0.1)";
|
|
2131
|
-
});
|
|
2132
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
2133
|
-
addBtn.style.background = "rgba(255,255,255,0.05)";
|
|
2134
|
-
});
|
|
2992
|
+
attachHover(addBtn, { bg: v("surface-active") });
|
|
2135
2993
|
addBtn.addEventListener("click", () => addRow(defaultForField(itemSchema)));
|
|
2136
2994
|
container.appendChild(addBtn);
|
|
2137
2995
|
const collect = () => rows.map((r) => r.state.getValue());
|
|
@@ -2161,15 +3019,12 @@ function renderObjectField(field, initial, setOwnError, depth) {
|
|
|
2161
3019
|
const subInitial = initial && typeof initial === "object" && !Array.isArray(initial) ? initial : {};
|
|
2162
3020
|
const fieldset = document.createElement("div");
|
|
2163
3021
|
fieldset.style.cssText = `
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
2167
|
-
border-radius: 10px;
|
|
2168
|
-
padding: 10px 10px 0;
|
|
3022
|
+
${group()}
|
|
3023
|
+
gap: 2px;
|
|
2169
3024
|
`;
|
|
2170
3025
|
if (depth >= MAX_FIELD_DEPTH) {
|
|
2171
3026
|
const note = document.createElement("div");
|
|
2172
|
-
note.style.cssText = `font-size:
|
|
3027
|
+
note.style.cssText = `font-size: ${v("text-xs")}; color: ${v("fg-muted")}; padding-bottom: 10px;`;
|
|
2173
3028
|
note.textContent = "Nesting too deep to edit here.";
|
|
2174
3029
|
fieldset.appendChild(note);
|
|
2175
3030
|
return { control: fieldset, getValue: () => ({}), validate: () => ({ value: {}, ok: true }) };
|
|
@@ -2183,8 +3038,8 @@ function renderObjectField(field, initial, setOwnError, depth) {
|
|
|
2183
3038
|
const collect = () => {
|
|
2184
3039
|
const out = {};
|
|
2185
3040
|
for (const c of childStates) {
|
|
2186
|
-
const
|
|
2187
|
-
if (
|
|
3041
|
+
const v2 = c.getValue();
|
|
3042
|
+
if (v2 !== void 0 && v2 !== "") out[c.field.name] = v2;
|
|
2188
3043
|
}
|
|
2189
3044
|
return out;
|
|
2190
3045
|
};
|
|
@@ -2207,7 +3062,7 @@ function renderReferenceField(field, initial) {
|
|
|
2207
3062
|
const targetList = field.referenceList;
|
|
2208
3063
|
const sel = document.createElement("select");
|
|
2209
3064
|
sel.className = "cancia-form-select";
|
|
2210
|
-
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;`;
|
|
2211
3066
|
const opt = (value, text, selected = false) => {
|
|
2212
3067
|
const o = document.createElement("option");
|
|
2213
3068
|
o.value = value;
|
|
@@ -2230,8 +3085,8 @@ function renderReferenceField(field, initial) {
|
|
|
2230
3085
|
const titleField = state.schemas[targetList]?.titleField;
|
|
2231
3086
|
const titleOf = (entry) => {
|
|
2232
3087
|
if (titleField) {
|
|
2233
|
-
const
|
|
2234
|
-
if (typeof
|
|
3088
|
+
const v2 = entry.data[titleField];
|
|
3089
|
+
if (typeof v2 === "string" && v2.trim()) return v2;
|
|
2235
3090
|
}
|
|
2236
3091
|
return `(untitled \xB7 ${entry.id})`;
|
|
2237
3092
|
};
|
|
@@ -2278,25 +3133,17 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2278
3133
|
];
|
|
2279
3134
|
const container = document.createElement("div");
|
|
2280
3135
|
container.style.cssText = `
|
|
2281
|
-
|
|
2282
|
-
background: rgba(255,255,255,0.02);
|
|
2283
|
-
border: 1px solid rgba(255,255,255,0.07);
|
|
2284
|
-
border-radius: 10px;
|
|
2285
|
-
padding: 10px;
|
|
3136
|
+
${group()}
|
|
2286
3137
|
`;
|
|
2287
3138
|
const rowsWrap = document.createElement("div");
|
|
2288
|
-
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap:
|
|
3139
|
+
rowsWrap.style.cssText = `display: flex; flex-direction: column; gap: ${v("space-2")};`;
|
|
2289
3140
|
container.appendChild(rowsWrap);
|
|
2290
3141
|
const rows = [];
|
|
2291
3142
|
let dragging = null;
|
|
2292
3143
|
function makeRow(initialRow) {
|
|
2293
3144
|
const row = document.createElement("div");
|
|
2294
3145
|
row.style.cssText = `
|
|
2295
|
-
|
|
2296
|
-
background: rgba(255,255,255,0.02);
|
|
2297
|
-
border: 1px solid rgba(255,255,255,0.06);
|
|
2298
|
-
border-radius: 8px;
|
|
2299
|
-
padding: 8px;
|
|
3146
|
+
${groupRow()}
|
|
2300
3147
|
`;
|
|
2301
3148
|
const handle = document.createElement("div");
|
|
2302
3149
|
handle.textContent = "\u22EE\u22EE";
|
|
@@ -2304,25 +3151,33 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2304
3151
|
handle.draggable = true;
|
|
2305
3152
|
handle.style.cssText = `
|
|
2306
3153
|
cursor: grab; user-select: none;
|
|
2307
|
-
color:
|
|
2308
|
-
font-size:
|
|
2309
|
-
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;
|
|
2310
3157
|
letter-spacing: -2px;
|
|
3158
|
+
opacity: 0;
|
|
3159
|
+
transition: opacity ${v("duration-fast")} ${v("ease")};
|
|
2311
3160
|
`;
|
|
2312
3161
|
const main = document.createElement("div");
|
|
2313
3162
|
main.style.cssText = `flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px;`;
|
|
2314
3163
|
const ta = document.createElement("textarea");
|
|
2315
3164
|
ta.className = "cancia-form-textarea";
|
|
2316
|
-
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")};`;
|
|
2317
3166
|
ta.rows = 2;
|
|
2318
3167
|
ta.placeholder = "Text \u2014 use **bold**, *italic*, [label](https://\u2026)";
|
|
2319
3168
|
ta.value = initialRow.text;
|
|
2320
3169
|
const controls = document.createElement("div");
|
|
2321
|
-
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";
|
|
2322
3177
|
const styleSel = document.createElement("select");
|
|
2323
3178
|
styleSel.className = "cancia-form-select";
|
|
2324
|
-
styleSel.style.cssText = `${INPUT_BASE} width: auto; flex: 1; appearance: none; padding: 5px 26px 5px 9px; font-size:
|
|
2325
|
-
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) {
|
|
2326
3181
|
const o = document.createElement("option");
|
|
2327
3182
|
o.value = s;
|
|
2328
3183
|
o.textContent = STYLE_LABELS[s];
|
|
@@ -2332,10 +3187,10 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2332
3187
|
const listSel = document.createElement("select");
|
|
2333
3188
|
listSel.className = "cancia-form-select";
|
|
2334
3189
|
listSel.style.cssText = styleSel.style.cssText;
|
|
2335
|
-
for (const { value, label } of LIST_LABELS) {
|
|
3190
|
+
for (const { value, label: label2 } of LIST_LABELS) {
|
|
2336
3191
|
const o = document.createElement("option");
|
|
2337
3192
|
o.value = value;
|
|
2338
|
-
o.textContent =
|
|
3193
|
+
o.textContent = label2;
|
|
2339
3194
|
if ((initialRow.listItem ?? "") === value) o.selected = true;
|
|
2340
3195
|
listSel.appendChild(o);
|
|
2341
3196
|
}
|
|
@@ -2348,19 +3203,16 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2348
3203
|
removeBtn.textContent = "\xD7";
|
|
2349
3204
|
removeBtn.title = "Remove block";
|
|
2350
3205
|
removeBtn.style.cssText = `
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
3206
|
+
${button("danger")}
|
|
3207
|
+
flex-shrink: 0;
|
|
3208
|
+
border: 1px solid transparent;
|
|
3209
|
+
height: auto;
|
|
2354
3210
|
font-size: 16px; line-height: 1;
|
|
2355
|
-
padding: 2px 7px;
|
|
2356
|
-
|
|
3211
|
+
padding: 2px 7px;
|
|
3212
|
+
cursor: pointer;
|
|
2357
3213
|
`;
|
|
2358
|
-
removeBtn
|
|
2359
|
-
|
|
2360
|
-
});
|
|
2361
|
-
removeBtn.addEventListener("mouseleave", () => {
|
|
2362
|
-
removeBtn.style.background = "transparent";
|
|
2363
|
-
});
|
|
3214
|
+
attachHover(removeBtn, { bg: v("danger-soft") });
|
|
3215
|
+
attachRowReveal(row, [handle, removeBtn, controls]);
|
|
2364
3216
|
row.appendChild(handle);
|
|
2365
3217
|
row.appendChild(main);
|
|
2366
3218
|
row.appendChild(removeBtn);
|
|
@@ -2415,8 +3267,8 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2415
3267
|
rowsWrap.appendChild(rec.el);
|
|
2416
3268
|
}
|
|
2417
3269
|
const initialRows = (() => {
|
|
2418
|
-
const parsed =
|
|
2419
|
-
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);
|
|
2420
3272
|
return [{ text: "", style: "normal" }];
|
|
2421
3273
|
})();
|
|
2422
3274
|
for (const r of initialRows) addRow(r);
|
|
@@ -2424,24 +3276,23 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2424
3276
|
addBtn.type = "button";
|
|
2425
3277
|
addBtn.textContent = "+ Add block";
|
|
2426
3278
|
addBtn.style.cssText = `
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
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;
|
|
2433
3289
|
`;
|
|
2434
|
-
addBtn
|
|
2435
|
-
addBtn.style.background = "rgba(255,255,255,0.1)";
|
|
2436
|
-
});
|
|
2437
|
-
addBtn.addEventListener("mouseleave", () => {
|
|
2438
|
-
addBtn.style.background = "rgba(255,255,255,0.05)";
|
|
2439
|
-
});
|
|
3290
|
+
attachHover(addBtn, { bg: v("surface-active") });
|
|
2440
3291
|
addBtn.addEventListener("click", () => addRow({ text: "", style: "normal" }));
|
|
2441
3292
|
container.appendChild(addBtn);
|
|
2442
3293
|
const serialise = () => {
|
|
2443
3294
|
const editorRows = rows.map((r) => r.read()).filter((r) => r.text.trim() !== "");
|
|
2444
|
-
return
|
|
3295
|
+
return rowsToPortableText2(editorRows);
|
|
2445
3296
|
};
|
|
2446
3297
|
return {
|
|
2447
3298
|
control: container,
|
|
@@ -2449,7 +3300,7 @@ function renderRichTextField(field, initial, setOwnError) {
|
|
|
2449
3300
|
validate: () => {
|
|
2450
3301
|
setOwnError(null);
|
|
2451
3302
|
const value = serialise();
|
|
2452
|
-
const parsed =
|
|
3303
|
+
const parsed = portableTextSubsetSchema2.safeParse(value);
|
|
2453
3304
|
if (!parsed.success) {
|
|
2454
3305
|
setOwnError("This rich-text content is not valid. Check links and formatting.");
|
|
2455
3306
|
return { value, ok: false };
|
|
@@ -2569,77 +3420,106 @@ function openEntryModal(opts) {
|
|
|
2569
3420
|
const isEdit = opts.entry !== null;
|
|
2570
3421
|
const isTranslate = !isEdit && (opts.translateFromEntry ?? null) !== null;
|
|
2571
3422
|
document.documentElement.style.setProperty("--cancia-accent-border", accentBorder());
|
|
2572
|
-
const backdrop = document.createElement("div");
|
|
2573
|
-
backdrop.dataset.canciaModalBackdrop = "1";
|
|
2574
|
-
backdrop.style.cssText = `
|
|
2575
|
-
position: fixed; inset: 0;
|
|
2576
|
-
background: rgba(8,8,10,0.55);
|
|
2577
|
-
backdrop-filter: blur(3px);
|
|
2578
|
-
-webkit-backdrop-filter: blur(3px);
|
|
2579
|
-
z-index: ${BACKDROP_Z2};
|
|
2580
|
-
opacity: 0;
|
|
2581
|
-
transition: opacity 0.2s ease-out;
|
|
2582
|
-
`;
|
|
2583
|
-
document.body.appendChild(backdrop);
|
|
2584
|
-
requestAnimationFrame(() => {
|
|
2585
|
-
backdrop.style.opacity = "1";
|
|
2586
|
-
});
|
|
2587
|
-
backdropEl2 = backdrop;
|
|
2588
3423
|
const modal = document.createElement("div");
|
|
2589
3424
|
modal.dataset.canciaModal = "1";
|
|
2590
|
-
modal
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
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
|
+
}
|
|
2610
3467
|
modalEl = modal;
|
|
2611
3468
|
const header = document.createElement("div");
|
|
2612
3469
|
header.style.cssText = `
|
|
2613
|
-
display: flex; align-items: center;
|
|
2614
|
-
padding: 14px
|
|
2615
|
-
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")};
|
|
2616
3473
|
flex-shrink: 0;
|
|
2617
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
|
+
}
|
|
2618
3493
|
const titleWrap = document.createElement("div");
|
|
2619
|
-
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;`;
|
|
2620
3495
|
const eyebrow = document.createElement("span");
|
|
2621
|
-
eyebrow.style.cssText =
|
|
2622
|
-
font-size: 10px; font-weight: 600;
|
|
2623
|
-
color: rgba(255,255,255,0.32);
|
|
2624
|
-
letter-spacing: 0.08em; text-transform: uppercase;
|
|
2625
|
-
`;
|
|
3496
|
+
eyebrow.style.cssText = `${label()} display: inline;`;
|
|
2626
3497
|
const action = isEdit ? "Edit" : isTranslate ? "Translate" : "New";
|
|
2627
3498
|
eyebrow.textContent = `${action} ${opts.schema.labelSingular.toLowerCase()} \xB7 ${opts.locale}`;
|
|
2628
3499
|
const titleEl = document.createElement("span");
|
|
2629
3500
|
titleEl.style.cssText = `
|
|
2630
3501
|
font-size: 14px; font-weight: 600;
|
|
2631
|
-
color:
|
|
3502
|
+
color: ${v("fg-strong")};
|
|
2632
3503
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
2633
3504
|
`;
|
|
2634
3505
|
titleEl.textContent = opts.schema.label;
|
|
2635
3506
|
titleWrap.appendChild(eyebrow);
|
|
2636
3507
|
titleWrap.appendChild(titleEl);
|
|
2637
3508
|
header.appendChild(titleWrap);
|
|
2638
|
-
header.appendChild(
|
|
3509
|
+
header.appendChild(
|
|
3510
|
+
makeCloseButton(() => {
|
|
3511
|
+
if (mountedInPanel) {
|
|
3512
|
+
closeEntryModal();
|
|
3513
|
+
closeListPanel();
|
|
3514
|
+
} else {
|
|
3515
|
+
closeEntryModal();
|
|
3516
|
+
}
|
|
3517
|
+
})
|
|
3518
|
+
);
|
|
2639
3519
|
modal.appendChild(header);
|
|
2640
3520
|
const body = document.createElement("div");
|
|
2641
3521
|
body.style.cssText = `
|
|
2642
|
-
padding: 14px
|
|
3522
|
+
padding: 14px ${v("space-4")} ${v("space-1")};
|
|
2643
3523
|
overflow-y: auto;
|
|
2644
3524
|
flex: 1 1 auto;
|
|
2645
3525
|
min-height: 0;
|
|
@@ -2648,14 +3528,14 @@ function openEntryModal(opts) {
|
|
|
2648
3528
|
const formError = document.createElement("div");
|
|
2649
3529
|
formError.style.cssText = `
|
|
2650
3530
|
display: none;
|
|
2651
|
-
background:
|
|
2652
|
-
color:
|
|
2653
|
-
border: 1px solid
|
|
2654
|
-
border-radius:
|
|
3531
|
+
background: ${v("danger-soft")};
|
|
3532
|
+
color: ${v("danger")};
|
|
3533
|
+
border: 1px solid ${v("danger-soft")};
|
|
3534
|
+
border-radius: ${v("radius-sm")};
|
|
2655
3535
|
padding: 9px 11px;
|
|
2656
|
-
font-size:
|
|
3536
|
+
font-size: ${v("text-sm")};
|
|
2657
3537
|
line-height: 1.4;
|
|
2658
|
-
margin-bottom:
|
|
3538
|
+
margin-bottom: ${v("space-3")};
|
|
2659
3539
|
`;
|
|
2660
3540
|
body.appendChild(formError);
|
|
2661
3541
|
function showFormError(msg) {
|
|
@@ -2669,14 +3549,14 @@ function openEntryModal(opts) {
|
|
|
2669
3549
|
if (isTranslate) {
|
|
2670
3550
|
const banner = document.createElement("div");
|
|
2671
3551
|
banner.style.cssText = `
|
|
2672
|
-
background:
|
|
2673
|
-
color:
|
|
2674
|
-
border: 1px solid
|
|
2675
|
-
border-radius:
|
|
3552
|
+
background: ${v("warning-soft")};
|
|
3553
|
+
color: ${v("warning")};
|
|
3554
|
+
border: 1px solid ${v("warning-soft")};
|
|
3555
|
+
border-radius: ${v("radius-sm")};
|
|
2676
3556
|
padding: 9px 11px;
|
|
2677
|
-
font-size:
|
|
3557
|
+
font-size: ${v("text-sm")};
|
|
2678
3558
|
line-height: 1.4;
|
|
2679
|
-
margin-bottom:
|
|
3559
|
+
margin-bottom: ${v("space-3")};
|
|
2680
3560
|
`;
|
|
2681
3561
|
const src = opts.translateFromEntry;
|
|
2682
3562
|
banner.textContent = `Translating from ${src.locale} into ${opts.locale}. Fields are pre-filled from the source.`;
|
|
@@ -2695,32 +3575,26 @@ function openEntryModal(opts) {
|
|
|
2695
3575
|
footer.style.cssText = `
|
|
2696
3576
|
display: flex; align-items: center; justify-content: space-between;
|
|
2697
3577
|
gap: 10px;
|
|
2698
|
-
padding:
|
|
2699
|
-
border-top: 1px solid
|
|
2700
|
-
background:
|
|
3578
|
+
padding: ${v("space-3")} ${v("space-4")};
|
|
3579
|
+
border-top: 1px solid ${v("border")};
|
|
3580
|
+
background: ${v("surface-raised")};
|
|
2701
3581
|
flex-shrink: 0;
|
|
2702
3582
|
`;
|
|
2703
3583
|
const leftActions = document.createElement("div");
|
|
2704
3584
|
const rightActions = document.createElement("div");
|
|
2705
|
-
rightActions.style.cssText = `display: flex; gap:
|
|
3585
|
+
rightActions.style.cssText = `display: flex; gap: ${v("space-2")};`;
|
|
2706
3586
|
if (isEdit) {
|
|
2707
3587
|
const deleteBtn = document.createElement("button");
|
|
2708
3588
|
deleteBtn.type = "button";
|
|
2709
3589
|
deleteBtn.style.cssText = `
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
transition: background 0.15s;
|
|
3590
|
+
${button("danger")}
|
|
3591
|
+
border: 1px solid transparent;
|
|
3592
|
+
height: auto;
|
|
3593
|
+
padding: 6px 10px;
|
|
3594
|
+
cursor: pointer;
|
|
2716
3595
|
`;
|
|
2717
3596
|
deleteBtn.textContent = "Delete";
|
|
2718
|
-
deleteBtn
|
|
2719
|
-
deleteBtn.style.background = "rgba(255,135,134,0.08)";
|
|
2720
|
-
});
|
|
2721
|
-
deleteBtn.addEventListener("mouseleave", () => {
|
|
2722
|
-
deleteBtn.style.background = "transparent";
|
|
2723
|
-
});
|
|
3597
|
+
attachHover(deleteBtn, { bg: v("danger-soft") });
|
|
2724
3598
|
deleteBtn.addEventListener("click", async () => {
|
|
2725
3599
|
if (!confirm(`Delete the ${opts.locale} version of this ${opts.schema.labelSingular.toLowerCase()}? This can't be undone.`)) return;
|
|
2726
3600
|
deleteBtn.disabled = true;
|
|
@@ -2738,25 +3612,17 @@ function openEntryModal(opts) {
|
|
|
2738
3612
|
const cancelBtn = document.createElement("button");
|
|
2739
3613
|
cancelBtn.type = "button";
|
|
2740
3614
|
cancelBtn.style.cssText = `
|
|
2741
|
-
|
|
2742
|
-
background:
|
|
2743
|
-
border: 1px solid
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
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;
|
|
2748
3621
|
`;
|
|
2749
3622
|
cancelBtn.textContent = "Cancel";
|
|
2750
|
-
cancelBtn
|
|
2751
|
-
cancelBtn.style.background = "rgba(255,255,255,0.08)";
|
|
2752
|
-
cancelBtn.style.color = "rgba(255,255,255,0.9)";
|
|
2753
|
-
});
|
|
2754
|
-
cancelBtn.addEventListener("mouseleave", () => {
|
|
2755
|
-
cancelBtn.style.background = "rgba(255,255,255,0.04)";
|
|
2756
|
-
cancelBtn.style.color = "rgba(255,255,255,0.75)";
|
|
2757
|
-
});
|
|
3623
|
+
attachHover(cancelBtn, { bg: v("surface-hover"), color: v("fg-strong") });
|
|
2758
3624
|
cancelBtn.addEventListener("click", () => closeEntryModal());
|
|
2759
|
-
const saveBtn = makePrimaryButton(isEdit ? "Save" : "Create",
|
|
3625
|
+
const saveBtn = makePrimaryButton(isEdit ? "Save" : "Create", accent3());
|
|
2760
3626
|
saveBtn.addEventListener("click", async () => {
|
|
2761
3627
|
clearFormError();
|
|
2762
3628
|
const { data, ok } = preValidate(fieldStates);
|
|
@@ -2799,7 +3665,6 @@ function openEntryModal(opts) {
|
|
|
2799
3665
|
footer.appendChild(leftActions);
|
|
2800
3666
|
footer.appendChild(rightActions);
|
|
2801
3667
|
modal.appendChild(footer);
|
|
2802
|
-
backdrop.addEventListener("click", () => closeEntryModal());
|
|
2803
3668
|
escListener = (e) => {
|
|
2804
3669
|
if (e.key === "Escape") {
|
|
2805
3670
|
e.stopPropagation();
|
|
@@ -2814,11 +3679,18 @@ function openEntryModal(opts) {
|
|
|
2814
3679
|
}
|
|
2815
3680
|
function closeEntryModal() {
|
|
2816
3681
|
if (modalEl) {
|
|
2817
|
-
modalEl.style.animation = "cancia-modal-out 0.16s cubic-bezier(0.7, 0, 0.84, 0) forwards";
|
|
2818
3682
|
const el = modalEl;
|
|
2819
|
-
setTimeout(() => el.remove(), 160);
|
|
2820
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
|
+
}
|
|
2821
3692
|
}
|
|
3693
|
+
mountedInPanel = false;
|
|
2822
3694
|
if (backdropEl2) {
|
|
2823
3695
|
const el = backdropEl2;
|
|
2824
3696
|
el.style.opacity = "0";
|
|
@@ -2839,13 +3711,12 @@ var toolbarEl = null;
|
|
|
2839
3711
|
var pendingPanelEl = null;
|
|
2840
3712
|
var isExpanded = false;
|
|
2841
3713
|
var expandedEscListener = null;
|
|
2842
|
-
|
|
2843
|
-
return state.config?.accentColor ?? "#6366f1";
|
|
2844
|
-
}
|
|
3714
|
+
var revealTimer = null;
|
|
2845
3715
|
var styleInjected4 = false;
|
|
2846
3716
|
function injectStyles4() {
|
|
2847
3717
|
if (styleInjected4) return;
|
|
2848
3718
|
styleInjected4 = true;
|
|
3719
|
+
injectBaseStyles(state.config?.accentColor, state.config?.toolbarAccent === true);
|
|
2849
3720
|
const s = document.createElement("style");
|
|
2850
3721
|
s.textContent = `
|
|
2851
3722
|
@keyframes cancia-enter {
|
|
@@ -2886,20 +3757,30 @@ function injectStyles4() {
|
|
|
2886
3757
|
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
|
2887
3758
|
}
|
|
2888
3759
|
[data-cancia-toolbar] * { box-sizing: border-box; }
|
|
2889
|
-
[data-cancia-toolbar] button:active:not(:disabled) { transform: scale(0.92) !important; }
|
|
2890
3760
|
[data-cancia-popup] * { box-sizing: border-box; }
|
|
2891
|
-
|
|
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); }
|
|
2892
3766
|
/* Protect stroke-based icons from host page "svg { fill: currentColor }" rules */
|
|
2893
3767
|
[data-cancia-toolbar] svg[fill="none"] { fill: none !important; }
|
|
2894
3768
|
[data-cancia-toolbar] svg[fill="none"] :not([fill]) { fill: none !important; }
|
|
2895
3769
|
[data-cancia-popup] svg[fill="none"] { fill: none !important; }
|
|
2896
3770
|
[data-cancia-popup] svg[fill="none"] :not([fill]) { fill: none !important; }
|
|
2897
|
-
/* 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. */
|
|
2898
3776
|
[data-cancia-toolbar] :where(button) {
|
|
2899
3777
|
background: unset; border: unset; border-radius: unset; padding: unset;
|
|
2900
|
-
margin: unset;
|
|
2901
|
-
|
|
2902
|
-
|
|
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;
|
|
2903
3784
|
}
|
|
2904
3785
|
`;
|
|
2905
3786
|
document.head.appendChild(s);
|
|
@@ -2911,27 +3792,28 @@ var tooltipVisible = false;
|
|
|
2911
3792
|
function getOrCreateBtnTooltip() {
|
|
2912
3793
|
if (!btnTooltipEl) {
|
|
2913
3794
|
btnTooltipEl = document.createElement("div");
|
|
3795
|
+
markUi(btnTooltipEl);
|
|
2914
3796
|
btnTooltipEl.style.cssText = `
|
|
2915
3797
|
position: fixed;
|
|
2916
3798
|
pointer-events: none;
|
|
2917
|
-
z-index:
|
|
2918
|
-
font-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
backdrop-filter: blur
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
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")};
|
|
2926
3808
|
white-space: nowrap;
|
|
2927
|
-
box-shadow:
|
|
3809
|
+
box-shadow: ${v("shadow-sm")};
|
|
2928
3810
|
display: none;
|
|
2929
3811
|
`;
|
|
2930
3812
|
document.body.appendChild(btnTooltipEl);
|
|
2931
3813
|
}
|
|
2932
3814
|
return btnTooltipEl;
|
|
2933
3815
|
}
|
|
2934
|
-
function showBtnTooltip(btn,
|
|
3816
|
+
function showBtnTooltip(btn, label2) {
|
|
2935
3817
|
if (tooltipHideTimer) {
|
|
2936
3818
|
clearTimeout(tooltipHideTimer);
|
|
2937
3819
|
tooltipHideTimer = null;
|
|
@@ -2943,9 +3825,9 @@ function showBtnTooltip(btn, label) {
|
|
|
2943
3825
|
const doShow = () => {
|
|
2944
3826
|
tooltipVisible = true;
|
|
2945
3827
|
const tooltip = getOrCreateBtnTooltip();
|
|
2946
|
-
tooltip.textContent =
|
|
3828
|
+
tooltip.textContent = label2;
|
|
2947
3829
|
tooltip.style.display = "block";
|
|
2948
|
-
tooltip.style.animation =
|
|
3830
|
+
tooltip.style.animation = `cancia-tooltip-in ${v("duration-fast")} ${v("ease-out")} both`;
|
|
2949
3831
|
const rect = btn.getBoundingClientRect();
|
|
2950
3832
|
const tooltipH = 26;
|
|
2951
3833
|
const gap = 8;
|
|
@@ -2973,37 +3855,39 @@ function buildToolbar() {
|
|
|
2973
3855
|
injectStyles4();
|
|
2974
3856
|
const bar = document.createElement("div");
|
|
2975
3857
|
bar.dataset.canciaToolbar = "1";
|
|
3858
|
+
markUi(bar);
|
|
2976
3859
|
bar.style.cssText = `
|
|
2977
3860
|
position: fixed;
|
|
2978
|
-
bottom:
|
|
2979
|
-
right:
|
|
2980
|
-
z-index:
|
|
2981
|
-
width:
|
|
2982
|
-
height:
|
|
2983
|
-
border-radius:
|
|
2984
|
-
background:
|
|
2985
|
-
backdrop-filter: blur
|
|
2986
|
-
-webkit-backdrop-filter: blur
|
|
2987
|
-
border: 1px solid
|
|
2988
|
-
box-shadow:
|
|
2989
|
-
font-
|
|
2990
|
-
|
|
2991
|
-
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")};
|
|
2992
3874
|
user-select: none;
|
|
2993
3875
|
cursor: pointer;
|
|
2994
3876
|
overflow: hidden;
|
|
2995
3877
|
display: flex;
|
|
2996
3878
|
align-items: center;
|
|
2997
3879
|
justify-content: center;
|
|
2998
|
-
transition: width
|
|
2999
|
-
|
|
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;
|
|
3000
3883
|
`;
|
|
3001
3884
|
const collapseIcon = document.createElement("div");
|
|
3002
3885
|
collapseIcon.style.cssText = `
|
|
3003
3886
|
position: absolute;
|
|
3004
3887
|
display: flex; align-items: center; justify-content: center;
|
|
3005
|
-
color:
|
|
3006
|
-
transition: opacity
|
|
3888
|
+
color: ${v("fg")};
|
|
3889
|
+
transition: opacity ${v("duration-fast")} ${v("ease")},
|
|
3890
|
+
transform ${v("duration-fast")} ${v("ease")};
|
|
3007
3891
|
pointer-events: none;
|
|
3008
3892
|
`;
|
|
3009
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">
|
|
@@ -3014,16 +3898,17 @@ function buildToolbar() {
|
|
|
3014
3898
|
controls.style.cssText = `
|
|
3015
3899
|
display: flex;
|
|
3016
3900
|
align-items: center;
|
|
3017
|
-
gap:
|
|
3901
|
+
gap: ${v("space-1")};
|
|
3018
3902
|
padding: 5px;
|
|
3019
3903
|
white-space: nowrap;
|
|
3020
3904
|
opacity: 0;
|
|
3905
|
+
transform: scale(0.6);
|
|
3021
3906
|
pointer-events: none;
|
|
3022
3907
|
transform-origin: right center;
|
|
3023
3908
|
`;
|
|
3024
3909
|
let editActive = false;
|
|
3025
|
-
const editBtn =
|
|
3026
|
-
`<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">
|
|
3027
3912
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
3028
3913
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
3029
3914
|
</svg>`,
|
|
@@ -3032,38 +3917,43 @@ function buildToolbar() {
|
|
|
3032
3917
|
);
|
|
3033
3918
|
controls.appendChild(editBtn);
|
|
3034
3919
|
const canPublish = state.config?.canPublish ?? true;
|
|
3035
|
-
const publishBtn =
|
|
3036
|
-
`<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">
|
|
3037
3922
|
<path d="M22 2L11 13"/>
|
|
3038
3923
|
<path d="M22 2L15 22l-4-9-9-4 20-7z"/>
|
|
3039
3924
|
</svg>`,
|
|
3040
|
-
|
|
3925
|
+
"Publish",
|
|
3041
3926
|
() => handlePublish(publishBtn)
|
|
3042
3927
|
);
|
|
3928
|
+
if (!canPublish) {
|
|
3929
|
+
publishBtn.title = "No publish method is configured for this site.";
|
|
3930
|
+
}
|
|
3043
3931
|
if (!canPublish) {
|
|
3044
3932
|
publishBtn.disabled = true;
|
|
3045
3933
|
publishBtn.style.opacity = "0.3";
|
|
3046
3934
|
publishBtn.style.cursor = "not-allowed";
|
|
3047
3935
|
}
|
|
3048
3936
|
controls.appendChild(publishBtn);
|
|
3049
|
-
const logoutBtn =
|
|
3050
|
-
`<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">
|
|
3051
3939
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
|
3052
3940
|
<path d="M16 17l5-5-5-5"/>
|
|
3053
3941
|
<path d="M21 12H9"/>
|
|
3054
3942
|
</svg>`,
|
|
3055
|
-
"
|
|
3943
|
+
"Sign out",
|
|
3056
3944
|
() => state.onLogout?.()
|
|
3057
3945
|
);
|
|
3058
3946
|
controls.appendChild(logoutBtn);
|
|
3059
3947
|
controls.appendChild(makeDivider());
|
|
3060
3948
|
const collapseBtn = makeIconButton(
|
|
3061
|
-
`<svg width="
|
|
3949
|
+
`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
|
3062
3950
|
<path d="M6 6l12 12M18 6L6 18"/>
|
|
3063
3951
|
</svg>`,
|
|
3064
3952
|
"Close",
|
|
3065
3953
|
() => collapse(bar, collapseIcon, controls)
|
|
3066
3954
|
);
|
|
3955
|
+
collapseBtn.style.width = "38px";
|
|
3956
|
+
collapseBtn.style.height = "38px";
|
|
3067
3957
|
controls.appendChild(collapseBtn);
|
|
3068
3958
|
bar.appendChild(collapseIcon);
|
|
3069
3959
|
bar.appendChild(controls);
|
|
@@ -3071,10 +3961,10 @@ function buildToolbar() {
|
|
|
3071
3961
|
if (!isExpanded) expand(bar, collapseIcon, controls);
|
|
3072
3962
|
});
|
|
3073
3963
|
bar.addEventListener("mouseenter", () => {
|
|
3074
|
-
if (!isExpanded) bar.style.background = "
|
|
3964
|
+
if (!isExpanded) bar.style.background = v("surface-2");
|
|
3075
3965
|
});
|
|
3076
3966
|
bar.addEventListener("mouseleave", () => {
|
|
3077
|
-
bar.style.background = "
|
|
3967
|
+
bar.style.background = v("surface-1");
|
|
3078
3968
|
});
|
|
3079
3969
|
onPendingChange(() => {
|
|
3080
3970
|
const count = state.pending.size;
|
|
@@ -3090,9 +3980,10 @@ function buildToolbar() {
|
|
|
3090
3980
|
state.editMode = editActive;
|
|
3091
3981
|
const svgEl = editBtn.querySelector("svg");
|
|
3092
3982
|
if (editActive) {
|
|
3093
|
-
editBtn.
|
|
3094
|
-
editBtn.style.
|
|
3095
|
-
|
|
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");
|
|
3096
3987
|
editBtn.dataset.canciaTooltip = "Stop editing (Esc)";
|
|
3097
3988
|
attachHighlight((selection) => {
|
|
3098
3989
|
if (selection.kind === "field") {
|
|
@@ -3156,8 +4047,9 @@ function buildToolbar() {
|
|
|
3156
4047
|
};
|
|
3157
4048
|
document.addEventListener("keydown", editModeEscListener, true);
|
|
3158
4049
|
} else {
|
|
4050
|
+
delete editBtn.dataset.canciaActive;
|
|
3159
4051
|
editBtn.style.background = "transparent";
|
|
3160
|
-
editBtn.style.color = "
|
|
4052
|
+
editBtn.style.color = v("fg-strong");
|
|
3161
4053
|
if (svgEl) svgEl.style.stroke = "";
|
|
3162
4054
|
editBtn.dataset.canciaTooltip = "Edit";
|
|
3163
4055
|
detachHighlight();
|
|
@@ -3184,40 +4076,49 @@ function expand(bar, icon, controls) {
|
|
|
3184
4076
|
controls.style.visibility = "hidden";
|
|
3185
4077
|
controls.style.opacity = "0";
|
|
3186
4078
|
controls.style.pointerEvents = "none";
|
|
3187
|
-
bar.style.width = "max-content";
|
|
3188
4079
|
bar.style.borderRadius = "100px";
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
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";
|
|
3192
4093
|
controls.style.visibility = "";
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
setTimeout(() => {
|
|
3200
|
-
controls.style.pointerEvents = "auto";
|
|
3201
|
-
controls.style.animation = "cancia-controls-in 0.4s cubic-bezier(0.19, 1, 0.22, 1) both";
|
|
3202
|
-
controls.style.opacity = "1";
|
|
3203
|
-
}, 80);
|
|
3204
|
-
});
|
|
3205
|
-
});
|
|
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);
|
|
3206
4100
|
}
|
|
3207
4101
|
function collapse(bar, icon, controls) {
|
|
3208
4102
|
if (!isExpanded) return;
|
|
3209
4103
|
isExpanded = false;
|
|
3210
4104
|
hideBtnTooltip();
|
|
4105
|
+
if (revealTimer !== null) {
|
|
4106
|
+
clearTimeout(revealTimer);
|
|
4107
|
+
revealTimer = null;
|
|
4108
|
+
}
|
|
3211
4109
|
if (expandedEscListener) {
|
|
3212
4110
|
document.removeEventListener("keydown", expandedEscListener, true);
|
|
3213
4111
|
expandedEscListener = null;
|
|
3214
4112
|
}
|
|
3215
4113
|
controls.style.pointerEvents = "none";
|
|
3216
|
-
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)";
|
|
3217
4118
|
setTimeout(() => {
|
|
3218
|
-
|
|
3219
|
-
bar.style.width = "
|
|
3220
|
-
bar.style.borderRadius = "
|
|
4119
|
+
if (isExpanded) return;
|
|
4120
|
+
bar.style.width = "52px";
|
|
4121
|
+
bar.style.borderRadius = "26px";
|
|
3221
4122
|
bar.style.cursor = "pointer";
|
|
3222
4123
|
icon.style.opacity = "1";
|
|
3223
4124
|
icon.style.transform = "scale(1) rotate(0deg)";
|
|
@@ -3261,42 +4162,87 @@ async function handlePublish(btn) {
|
|
|
3261
4162
|
}
|
|
3262
4163
|
function showToast(message, type) {
|
|
3263
4164
|
const toast = document.createElement("div");
|
|
3264
|
-
|
|
4165
|
+
markUi(toast);
|
|
4166
|
+
const color = type === "success" ? v("success") : v("danger");
|
|
3265
4167
|
toast.style.cssText = `
|
|
3266
|
-
position: fixed; bottom: 80px; right:
|
|
3267
|
-
display: flex; align-items: center; gap:
|
|
3268
|
-
background:
|
|
3269
|
-
backdrop-filter: blur
|
|
3270
|
-
|
|
3271
|
-
border
|
|
3272
|
-
|
|
3273
|
-
font-size:
|
|
3274
|
-
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")};
|
|
3275
4177
|
pointer-events: none;
|
|
3276
|
-
|
|
4178
|
+
opacity: 0; transform: translateY(4px);
|
|
4179
|
+
transition: opacity ${v("duration")} ${v("ease")}, transform ${v("duration")} ${v("ease")};
|
|
3277
4180
|
`;
|
|
3278
4181
|
const dot = document.createElement("span");
|
|
3279
4182
|
dot.style.cssText = `width: 7px; height: 7px; border-radius: 50%; background: ${color}; flex-shrink: 0;`;
|
|
3280
|
-
const
|
|
3281
|
-
|
|
4183
|
+
const label2 = document.createElement("span");
|
|
4184
|
+
label2.textContent = message;
|
|
3282
4185
|
toast.appendChild(dot);
|
|
3283
|
-
toast.appendChild(
|
|
4186
|
+
toast.appendChild(label2);
|
|
3284
4187
|
document.body.appendChild(toast);
|
|
4188
|
+
requestAnimationFrame(() => {
|
|
4189
|
+
toast.style.opacity = "1";
|
|
4190
|
+
toast.style.transform = "translateY(0)";
|
|
4191
|
+
});
|
|
3285
4192
|
setTimeout(() => {
|
|
3286
|
-
toast.style.transition =
|
|
4193
|
+
toast.style.transition = `opacity ${v("duration-fast")} ${v("ease-out")}, transform ${v("duration-fast")} ${v("ease-out")}`;
|
|
3287
4194
|
toast.style.opacity = "0";
|
|
3288
4195
|
toast.style.transform = "translateY(4px)";
|
|
3289
4196
|
setTimeout(() => toast.remove(), 300);
|
|
3290
4197
|
}, 2e3);
|
|
3291
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
|
+
}
|
|
3292
4236
|
function makeIconButton(svg, title, onClick) {
|
|
3293
4237
|
const btn = document.createElement("button");
|
|
3294
4238
|
btn.style.cssText = `
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
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")};
|
|
3300
4246
|
`;
|
|
3301
4247
|
btn.innerHTML = svg;
|
|
3302
4248
|
const svgEl = btn.querySelector("svg");
|
|
@@ -3307,15 +4253,12 @@ function makeIconButton(svg, title, onClick) {
|
|
|
3307
4253
|
btn.dataset.canciaTooltip = title;
|
|
3308
4254
|
btn.addEventListener("mouseenter", () => {
|
|
3309
4255
|
if (!btn.disabled) {
|
|
3310
|
-
btn.style.background = "
|
|
4256
|
+
btn.style.background = v("surface-active");
|
|
3311
4257
|
showBtnTooltip(btn, btn.dataset.canciaTooltip ?? title);
|
|
3312
4258
|
}
|
|
3313
4259
|
});
|
|
3314
4260
|
btn.addEventListener("mouseleave", () => {
|
|
3315
|
-
|
|
3316
|
-
if (!btn.style.background.includes(activeColor.slice(1, 7))) {
|
|
3317
|
-
btn.style.background = "transparent";
|
|
3318
|
-
}
|
|
4261
|
+
if (btn.dataset.canciaActive !== "1") btn.style.background = "transparent";
|
|
3319
4262
|
hideBtnTooltip();
|
|
3320
4263
|
});
|
|
3321
4264
|
btn.addEventListener("click", (e) => {
|
|
@@ -3327,45 +4270,45 @@ function makeIconButton(svg, title, onClick) {
|
|
|
3327
4270
|
}
|
|
3328
4271
|
function makeDivider() {
|
|
3329
4272
|
const d = document.createElement("span");
|
|
3330
|
-
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;`;
|
|
3331
4274
|
return d;
|
|
3332
4275
|
}
|
|
3333
4276
|
function showPendingPanel(count) {
|
|
3334
4277
|
if (!pendingPanelEl) {
|
|
3335
4278
|
pendingPanelEl = document.createElement("div");
|
|
4279
|
+
markUi(pendingPanelEl);
|
|
3336
4280
|
pendingPanelEl.style.cssText = `
|
|
3337
4281
|
position: fixed;
|
|
3338
4282
|
bottom: 80px;
|
|
3339
|
-
right:
|
|
3340
|
-
z-index:
|
|
4283
|
+
right: ${v("space-6")};
|
|
4284
|
+
z-index: ${v("z-panel")};
|
|
3341
4285
|
display: flex;
|
|
3342
4286
|
align-items: center;
|
|
3343
4287
|
justify-content: space-between;
|
|
3344
|
-
gap:
|
|
3345
|
-
background:
|
|
3346
|
-
backdrop-filter: blur
|
|
3347
|
-
-webkit-backdrop-filter: blur
|
|
3348
|
-
border: 1px solid
|
|
3349
|
-
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")};
|
|
3350
4294
|
padding: 0;
|
|
3351
|
-
box-shadow:
|
|
3352
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
3353
|
-
animation: cancia-fade-in 0.25s cubic-bezier(0.16,1,0.3,1) both;
|
|
4295
|
+
box-shadow: ${v("shadow")};
|
|
3354
4296
|
width: max-content;
|
|
3355
4297
|
overflow: hidden;
|
|
4298
|
+
opacity: 0; transform: translateY(4px);
|
|
4299
|
+
transition: opacity ${v("duration")} ${v("ease")}, transform ${v("duration")} ${v("ease")};
|
|
3356
4300
|
`;
|
|
3357
|
-
const
|
|
3358
|
-
|
|
3359
|
-
|
|
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;`;
|
|
3360
4304
|
const saveBtn = document.createElement("button");
|
|
3361
4305
|
saveBtn.dataset.canciaSaveBtn = "1";
|
|
3362
4306
|
saveBtn.title = "Save changes";
|
|
3363
4307
|
saveBtn.style.cssText = `
|
|
3364
|
-
padding: 5px 10px; border-radius:
|
|
3365
|
-
background:
|
|
3366
|
-
font-
|
|
3367
|
-
|
|
3368
|
-
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")};
|
|
3369
4312
|
flex-shrink: 0;
|
|
3370
4313
|
`;
|
|
3371
4314
|
saveBtn.textContent = "Save";
|
|
@@ -3375,6 +4318,7 @@ function showPendingPanel(count) {
|
|
|
3375
4318
|
saveBtn.addEventListener("mouseleave", () => {
|
|
3376
4319
|
saveBtn.style.opacity = "1";
|
|
3377
4320
|
});
|
|
4321
|
+
attachPress(saveBtn);
|
|
3378
4322
|
saveBtn.addEventListener("click", (e) => {
|
|
3379
4323
|
e.stopPropagation();
|
|
3380
4324
|
handleSave(saveBtn);
|
|
@@ -3382,22 +4326,22 @@ function showPendingPanel(count) {
|
|
|
3382
4326
|
const undoBtn = document.createElement("button");
|
|
3383
4327
|
undoBtn.title = "Discard changes";
|
|
3384
4328
|
undoBtn.style.cssText = `
|
|
3385
|
-
padding: 5px 10px; border-radius:
|
|
3386
|
-
background: transparent; color:
|
|
3387
|
-
font-
|
|
3388
|
-
|
|
3389
|
-
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")};
|
|
3390
4333
|
flex-shrink: 0;
|
|
3391
4334
|
`;
|
|
3392
4335
|
undoBtn.textContent = "Discard";
|
|
3393
4336
|
undoBtn.addEventListener("mouseenter", () => {
|
|
3394
|
-
undoBtn.style.color = "
|
|
3395
|
-
undoBtn.style.borderColor = "
|
|
4337
|
+
undoBtn.style.color = v("fg-strong");
|
|
4338
|
+
undoBtn.style.borderColor = v("border-strong");
|
|
3396
4339
|
});
|
|
3397
4340
|
undoBtn.addEventListener("mouseleave", () => {
|
|
3398
|
-
undoBtn.style.color = "
|
|
3399
|
-
undoBtn.style.borderColor = "
|
|
4341
|
+
undoBtn.style.color = v("fg-muted");
|
|
4342
|
+
undoBtn.style.borderColor = v("border");
|
|
3400
4343
|
});
|
|
4344
|
+
attachPress(undoBtn);
|
|
3401
4345
|
undoBtn.addEventListener("click", (e) => {
|
|
3402
4346
|
e.stopPropagation();
|
|
3403
4347
|
revertPending();
|
|
@@ -3412,32 +4356,36 @@ function showPendingPanel(count) {
|
|
|
3412
4356
|
row.style.cssText = `
|
|
3413
4357
|
grid-area: 1/1; display: flex; align-items: center; gap: 6px;
|
|
3414
4358
|
padding: 7px 7px 7px 12px;
|
|
3415
|
-
transition: transform
|
|
4359
|
+
transition: transform ${v("duration")} ${v("ease-out")};
|
|
3416
4360
|
`;
|
|
3417
|
-
row.appendChild(
|
|
4361
|
+
row.appendChild(label3);
|
|
3418
4362
|
row.appendChild(undoBtn);
|
|
3419
4363
|
row.appendChild(saveBtn);
|
|
3420
4364
|
const flash = document.createElement("div");
|
|
3421
4365
|
flash.dataset.canciaPanelFlash = "1";
|
|
3422
4366
|
flash.style.cssText = `
|
|
3423
4367
|
grid-area: 1/1; display: flex; align-items: center; gap: 7px;
|
|
3424
|
-
font-
|
|
3425
|
-
|
|
3426
|
-
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")};
|
|
3427
4370
|
transform: translateY(-150%);
|
|
3428
|
-
transition: transform
|
|
4371
|
+
transition: transform ${v("duration")} ${v("ease-out")};
|
|
3429
4372
|
`;
|
|
3430
4373
|
slot.appendChild(row);
|
|
3431
4374
|
slot.appendChild(flash);
|
|
3432
4375
|
pendingPanelEl.appendChild(slot);
|
|
3433
4376
|
document.body.appendChild(pendingPanelEl);
|
|
4377
|
+
const panel = pendingPanelEl;
|
|
4378
|
+
requestAnimationFrame(() => {
|
|
4379
|
+
panel.style.opacity = "1";
|
|
4380
|
+
panel.style.transform = "translateY(0)";
|
|
4381
|
+
});
|
|
3434
4382
|
}
|
|
3435
|
-
const
|
|
3436
|
-
if (
|
|
4383
|
+
const label2 = pendingPanelEl.querySelector("[data-cancia-pending-label]");
|
|
4384
|
+
if (label2) label2.textContent = `${count} unsaved change${count === 1 ? "" : "s"}`;
|
|
3437
4385
|
}
|
|
3438
4386
|
function flashPanelMessage(message, type) {
|
|
3439
4387
|
if (!pendingPanelEl) return;
|
|
3440
|
-
const color = type === "success" ? "
|
|
4388
|
+
const color = type === "success" ? v("success") : v("danger");
|
|
3441
4389
|
const row = pendingPanelEl.querySelector("[data-cancia-panel-row]");
|
|
3442
4390
|
const flash = pendingPanelEl.querySelector("[data-cancia-panel-flash]");
|
|
3443
4391
|
if (!row || !flash) return;
|
|
@@ -3450,7 +4398,7 @@ function hidePendingPanel() {
|
|
|
3450
4398
|
if (!pendingPanelEl) return;
|
|
3451
4399
|
const panel = pendingPanelEl;
|
|
3452
4400
|
pendingPanelEl = null;
|
|
3453
|
-
panel.style.transition =
|
|
4401
|
+
panel.style.transition = `opacity 0.2s ${v("ease-out")}, transform 0.2s ${v("ease-out")}`;
|
|
3454
4402
|
panel.style.opacity = "0";
|
|
3455
4403
|
panel.style.transform = "translateY(4px)";
|
|
3456
4404
|
setTimeout(() => panel.remove(), 220);
|
|
@@ -3470,7 +4418,7 @@ function unmountToolbar() {
|
|
|
3470
4418
|
pendingPanelEl?.remove();
|
|
3471
4419
|
pendingPanelEl = null;
|
|
3472
4420
|
if (toolbarEl) {
|
|
3473
|
-
toolbarEl.style.animation =
|
|
4421
|
+
toolbarEl.style.animation = `cancia-exit ${v("duration-exit")} ${v("ease-out")} both`;
|
|
3474
4422
|
setTimeout(() => {
|
|
3475
4423
|
toolbarEl?.remove();
|
|
3476
4424
|
toolbarEl = null;
|