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