frontend-auto-cms 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/dist/cli/apply.d.ts +7 -0
  4. package/dist/cli/apply.js +102 -0
  5. package/dist/cli/apply.js.map +1 -0
  6. package/dist/cli/fs-utils.d.ts +5 -0
  7. package/dist/cli/fs-utils.js +54 -0
  8. package/dist/cli/fs-utils.js.map +1 -0
  9. package/dist/cli/index.d.ts +2 -0
  10. package/dist/cli/index.js +32 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/cli/postinstall.d.ts +1 -0
  13. package/dist/cli/postinstall.js +10 -0
  14. package/dist/cli/postinstall.js.map +1 -0
  15. package/dist/cli/scanner.d.ts +2 -0
  16. package/dist/cli/scanner.js +97 -0
  17. package/dist/cli/scanner.js.map +1 -0
  18. package/dist/cli/setup.d.ts +6 -0
  19. package/dist/cli/setup.js +256 -0
  20. package/dist/cli/setup.js.map +1 -0
  21. package/dist/runtime/dashboard.js +1189 -0
  22. package/dist/runtime/index.js +7 -0
  23. package/dist/runtime/runtime/dashboard.d.ts +2 -0
  24. package/dist/runtime/runtime/dom-scan.d.ts +3 -0
  25. package/dist/runtime/runtime/i18n.d.ts +7 -0
  26. package/dist/runtime/runtime/index.d.ts +1 -0
  27. package/dist/runtime/runtime/publish.d.ts +11 -0
  28. package/dist/runtime/runtime/store.d.ts +7 -0
  29. package/dist/runtime/runtime/types.d.ts +8 -0
  30. package/dist/runtime/shared/constants.d.ts +10 -0
  31. package/dist/runtime/shared/hash.d.ts +4 -0
  32. package/dist/runtime/shared/types.d.ts +71 -0
  33. package/dist/shared/constants.d.ts +10 -0
  34. package/dist/shared/constants.js +11 -0
  35. package/dist/shared/constants.js.map +1 -0
  36. package/dist/shared/hash.d.ts +4 -0
  37. package/dist/shared/hash.js +19 -0
  38. package/dist/shared/hash.js.map +1 -0
  39. package/dist/shared/types.d.ts +71 -0
  40. package/dist/shared/types.js +2 -0
  41. package/dist/shared/types.js.map +1 -0
  42. package/package.json +68 -0
@@ -0,0 +1,1189 @@
1
+ const fe = "cms-export.patch.json", pe = "frontend-auto-cms::content", Z = "frontend-auto-cms::locales";
2
+ function Q(e) {
3
+ localStorage.setItem(pe, JSON.stringify(e));
4
+ }
5
+ function ee() {
6
+ try {
7
+ const e = localStorage.getItem(Z);
8
+ return e ? JSON.parse(e) : {};
9
+ } catch {
10
+ return {};
11
+ }
12
+ }
13
+ function A(e) {
14
+ localStorage.setItem(Z, JSON.stringify(e));
15
+ }
16
+ async function ge() {
17
+ try {
18
+ const e = await fetch("/cms-locales.json", { cache: "no-store" });
19
+ if (!e.ok)
20
+ return {};
21
+ const a = (await e.json())?.locales;
22
+ return a && typeof a == "object" ? a : {};
23
+ } catch {
24
+ return {};
25
+ }
26
+ }
27
+ function be(e) {
28
+ const t = new Blob([JSON.stringify(e, null, 2)], { type: "application/json" }), a = URL.createObjectURL(t), r = document.createElement("a");
29
+ r.href = a, r.download = fe, r.click(), URL.revokeObjectURL(a);
30
+ }
31
+ function E(e) {
32
+ return e.replace(/[^\w]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
33
+ }
34
+ function k(e) {
35
+ const t = e.getAttribute("id");
36
+ if (t)
37
+ return `#${t}`;
38
+ const a = (e.getAttribute("class") ?? "").split(/\s+/).filter(Boolean)[0];
39
+ return a ? `${e.tagName.toLowerCase()}.${a}` : e.tagName.toLowerCase();
40
+ }
41
+ function te(e) {
42
+ return ["SCRIPT", "STYLE", "NOSCRIPT", "IFRAME"].includes(e.tagName);
43
+ }
44
+ function z(e) {
45
+ return (e ?? "").replace(/\s+/g, " ").trim();
46
+ }
47
+ function me(e, t, a) {
48
+ const r = t.parentElement;
49
+ if (!r || te(r))
50
+ return null;
51
+ if (Array.from(r.childNodes).filter(
52
+ (s) => s.nodeType === Node.TEXT_NODE && z(s.nodeValue).length > 0
53
+ ).length === 1 && r.children.length === 0)
54
+ return r.setAttribute("data-cms-key", a), r;
55
+ const o = e.createElement("span");
56
+ return o.textContent = t.nodeValue ?? "", o.setAttribute("data-cms-key", a), t.parentNode?.replaceChild(o, t), o;
57
+ }
58
+ function N(e = document, t) {
59
+ const a = [];
60
+ let r = 0, n = 0, o = 0, s = 0;
61
+ const l = t ?? e.location?.pathname ?? location.pathname, c = e.body;
62
+ if (!c)
63
+ return {
64
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
65
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
66
+ nodes: a
67
+ };
68
+ const f = e.createTreeWalker(c, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
69
+ for (; f.nextNode(); ) {
70
+ const u = f.currentNode;
71
+ if (u.nodeType === Node.TEXT_NODE) {
72
+ const b = u, m = z(b.nodeValue);
73
+ if (m.length < 2)
74
+ continue;
75
+ r += 1;
76
+ const U = `${E(l || "index")}.text.${r}`, C = me(e, b, U);
77
+ if (!C)
78
+ continue;
79
+ a.push({
80
+ id: `dom_txt_${r}`,
81
+ key: U,
82
+ type: "text",
83
+ label: k(C),
84
+ value: m,
85
+ selector: k(C),
86
+ sourceRefs: [{ file: l, original: m }]
87
+ });
88
+ continue;
89
+ }
90
+ const i = u;
91
+ if (te(i))
92
+ continue;
93
+ const p = i.tagName.toLowerCase();
94
+ if (p === "img" || p === "video") {
95
+ const b = i.getAttribute("src") ?? "";
96
+ if (b) {
97
+ n += 1;
98
+ const m = `${E(l || "index")}.${p}.${n}`;
99
+ i.setAttribute("data-cms-key", m), a.push({
100
+ id: `dom_media_${n}`,
101
+ key: m,
102
+ type: p === "img" ? "image" : "video",
103
+ label: k(i),
104
+ value: b,
105
+ selector: k(i),
106
+ attrs: { src: b, alt: i.getAttribute("alt") ?? "" },
107
+ sourceRefs: [{ file: l, original: b }]
108
+ });
109
+ }
110
+ }
111
+ if (["section", "ul", "ol"].includes(p) || i.hasAttribute("data-repeatable")) {
112
+ const b = Array.from(i.children).map((m) => z(m.textContent)).filter(Boolean);
113
+ if (b.length >= 2) {
114
+ o += 1;
115
+ const m = `${E(l || "index")}.section.${o}`;
116
+ i.setAttribute("data-cms-key", m), a.push({
117
+ id: `dom_sec_${o}`,
118
+ key: m,
119
+ type: "section",
120
+ label: k(i),
121
+ value: b.join(" | "),
122
+ selector: k(i),
123
+ sectionItems: b,
124
+ sourceRefs: [{ file: l, original: i.innerHTML }]
125
+ });
126
+ }
127
+ }
128
+ const g = {};
129
+ if (["alt", "href", "src", "aria-label", "title"].forEach((b) => {
130
+ const m = i.getAttribute(b);
131
+ m != null && m.trim() && (g[b] = m);
132
+ }), Object.keys(g).length > 0 && !(p === "img" || p === "video")) {
133
+ s += 1;
134
+ const b = `${E(l || "index")}.property.${s}`;
135
+ a.push({
136
+ id: `dom_prop_${s}`,
137
+ key: b,
138
+ type: "property",
139
+ label: k(i),
140
+ value: JSON.stringify(g),
141
+ selector: k(i),
142
+ attrs: g,
143
+ sourceRefs: [{ file: l, original: JSON.stringify(g) }]
144
+ });
145
+ }
146
+ }
147
+ return {
148
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
149
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
150
+ nodes: a
151
+ };
152
+ }
153
+ function ae(e, t = document) {
154
+ if (!e.key)
155
+ return;
156
+ const a = t.querySelector(`[data-cms-key="${CSS.escape(e.key)}"]`);
157
+ if (a) {
158
+ if (e.type === "text")
159
+ a.textContent = e.value;
160
+ else if (e.type === "image" || e.type === "video")
161
+ a.src = e.value, e.attrs?.alt != null && a.setAttribute("alt", e.attrs.alt);
162
+ else if (e.type === "property" && e.attrs)
163
+ Object.entries(e.attrs).forEach(([r, n]) => a.setAttribute(r, n));
164
+ else if (e.type === "section" && e.sectionItems) {
165
+ const r = Array.from(a.children);
166
+ e.sectionItems.forEach((n, o) => {
167
+ r[o] && (r[o].textContent = n);
168
+ });
169
+ }
170
+ }
171
+ }
172
+ const B = [
173
+ { code: "af", label: "Afrikaans", nllb: "afr_Latn" },
174
+ { code: "ar", label: "Arabic", nllb: "arb_Arab" },
175
+ { code: "az", label: "Azerbaijani", nllb: "azj_Latn" },
176
+ { code: "be", label: "Belarusian", nllb: "bel_Cyrl" },
177
+ { code: "bg", label: "Bulgarian", nllb: "bul_Cyrl" },
178
+ { code: "bn", label: "Bengali", nllb: "ben_Beng" },
179
+ { code: "bs", label: "Bosnian", nllb: "bos_Latn" },
180
+ { code: "ca", label: "Catalan", nllb: "cat_Latn" },
181
+ { code: "cs", label: "Czech", nllb: "ces_Latn" },
182
+ { code: "cy", label: "Welsh", nllb: "cym_Latn" },
183
+ { code: "da", label: "Danish", nllb: "dan_Latn" },
184
+ { code: "de", label: "German", nllb: "deu_Latn" },
185
+ { code: "el", label: "Greek", nllb: "ell_Grek" },
186
+ { code: "es", label: "Spanish", nllb: "spa_Latn" },
187
+ { code: "et", label: "Estonian", nllb: "est_Latn" },
188
+ { code: "fa", label: "Persian", nllb: "pes_Arab" },
189
+ { code: "fi", label: "Finnish", nllb: "fin_Latn" },
190
+ { code: "fr", label: "French", nllb: "fra_Latn" },
191
+ { code: "ga", label: "Irish", nllb: "gle_Latn" },
192
+ { code: "gl", label: "Galician", nllb: "glg_Latn" },
193
+ { code: "gu", label: "Gujarati", nllb: "guj_Gujr" },
194
+ { code: "he", label: "Hebrew", nllb: "heb_Hebr" },
195
+ { code: "hi", label: "Hindi", nllb: "hin_Deva" },
196
+ { code: "hr", label: "Croatian", nllb: "hrv_Latn" },
197
+ { code: "hu", label: "Hungarian", nllb: "hun_Latn" },
198
+ { code: "hy", label: "Armenian", nllb: "hye_Armn" },
199
+ { code: "id", label: "Indonesian", nllb: "ind_Latn" },
200
+ { code: "is", label: "Icelandic", nllb: "isl_Latn" },
201
+ { code: "it", label: "Italian", nllb: "ita_Latn" },
202
+ { code: "ja", label: "Japanese", nllb: "jpn_Jpan" },
203
+ { code: "ka", label: "Georgian", nllb: "kat_Geor" },
204
+ { code: "kk", label: "Kazakh", nllb: "kaz_Cyrl" },
205
+ { code: "km", label: "Khmer", nllb: "khm_Khmr" },
206
+ { code: "ko", label: "Korean", nllb: "kor_Hang" },
207
+ { code: "lt", label: "Lithuanian", nllb: "lit_Latn" },
208
+ { code: "lv", label: "Latvian", nllb: "lvs_Latn" },
209
+ { code: "mk", label: "Macedonian", nllb: "mkd_Cyrl" },
210
+ { code: "ml", label: "Malayalam", nllb: "mal_Mlym" },
211
+ { code: "mr", label: "Marathi", nllb: "mar_Deva" },
212
+ { code: "ms", label: "Malay", nllb: "zsm_Latn" },
213
+ { code: "mt", label: "Maltese", nllb: "mlt_Latn" },
214
+ { code: "ne", label: "Nepali", nllb: "npi_Deva" },
215
+ { code: "nl", label: "Dutch", nllb: "nld_Latn" },
216
+ { code: "no", label: "Norwegian", nllb: "nob_Latn" },
217
+ { code: "pa", label: "Punjabi", nllb: "pan_Guru" },
218
+ { code: "pl", label: "Polish", nllb: "pol_Latn" },
219
+ { code: "pt", label: "Portuguese", nllb: "por_Latn" },
220
+ { code: "ro", label: "Romanian", nllb: "ron_Latn" },
221
+ { code: "ru", label: "Russian", nllb: "rus_Cyrl" },
222
+ { code: "sk", label: "Slovak", nllb: "slk_Latn" },
223
+ { code: "sl", label: "Slovenian", nllb: "slv_Latn" },
224
+ { code: "sq", label: "Albanian", nllb: "als_Latn" },
225
+ { code: "sr", label: "Serbian", nllb: "srp_Cyrl" },
226
+ { code: "sv", label: "Swedish", nllb: "swe_Latn" },
227
+ { code: "sw", label: "Swahili", nllb: "swh_Latn" },
228
+ { code: "ta", label: "Tamil", nllb: "tam_Taml" },
229
+ { code: "te", label: "Telugu", nllb: "tel_Telu" },
230
+ { code: "th", label: "Thai", nllb: "tha_Thai" },
231
+ { code: "tr", label: "Turkish", nllb: "tur_Latn" },
232
+ { code: "uk", label: "Ukrainian", nllb: "ukr_Cyrl" },
233
+ { code: "ur", label: "Urdu", nllb: "urd_Arab" },
234
+ { code: "uz", label: "Uzbek", nllb: "uzn_Latn" },
235
+ { code: "vi", label: "Vietnamese", nllb: "vie_Latn" },
236
+ { code: "zh", label: "Chinese (Simplified)", nllb: "zho_Hans" }
237
+ ], G = Object.fromEntries(
238
+ B.map((e) => [e.nllb.slice(0, 3).toLowerCase(), e.code])
239
+ ), xe = "https://api.mymemory.translated.net/get";
240
+ function K(e) {
241
+ const t = e.trim().toLowerCase().split(/[-_]/)[0];
242
+ if (/^[a-z]{2}$/i.test(t))
243
+ return t;
244
+ if (/^[a-z]{3}$/i.test(t) && G[t])
245
+ return G[t];
246
+ throw new Error(`Unsupported language code "${e}" for MyMemory translation.`);
247
+ }
248
+ async function ye(e, t, a) {
249
+ if (!e.trim())
250
+ return e;
251
+ const r = new URLSearchParams({
252
+ q: e,
253
+ langpair: `${t}|${a}`
254
+ }), n = await fetch(`${xe}?${r.toString()}`, { method: "GET" });
255
+ if (!n.ok)
256
+ throw new Error(`MyMemory request failed (${n.status})`);
257
+ const o = await n.json();
258
+ if (o.responseStatus && o.responseStatus !== 200)
259
+ throw new Error(`MyMemory translation failed with status ${o.responseStatus}`);
260
+ const s = o.responseData?.translatedText;
261
+ return typeof s == "string" && s.trim() ? s : e;
262
+ }
263
+ async function he(e, t, a = "en", r) {
264
+ const n = K(a), o = K(t), s = /* @__PURE__ */ new Map(), l = {}, c = Object.entries(e), f = c.length;
265
+ let u = 0;
266
+ for (const [i, p] of c) {
267
+ if (s.has(p)) {
268
+ l[i] = s.get(p), u += 1, r?.(u, f);
269
+ continue;
270
+ }
271
+ try {
272
+ const g = await ye(p, n, o);
273
+ s.set(p, g), l[i] = g;
274
+ } catch {
275
+ l[i] = p;
276
+ }
277
+ u += 1, r?.(u, f);
278
+ }
279
+ return l;
280
+ }
281
+ async function ke(e, t, a) {
282
+ return (await fetch(
283
+ `https://gitlab.com/api/v4/projects/${encodeURIComponent(e.repository)}/repository/files/${encodeURIComponent(a)}?ref=${encodeURIComponent(e.branch)}`,
284
+ {
285
+ method: "GET",
286
+ headers: { "PRIVATE-TOKEN": t }
287
+ }
288
+ )).ok;
289
+ }
290
+ async function ve() {
291
+ try {
292
+ const e = await fetch("/cms-hosting.json", { cache: "no-store" });
293
+ if (!e.ok)
294
+ return null;
295
+ const t = await e.json();
296
+ return !t || !t.provider || t.provider === "none" || t.provider !== "github" && t.provider !== "gitlab" || typeof t.repository != "string" || !t.repository.trim() || typeof t.branch != "string" || !t.branch.trim() ? null : t;
297
+ } catch {
298
+ return null;
299
+ }
300
+ }
301
+ async function O(e, t, a, r, n) {
302
+ const o = `https://api.github.com/repos/${e.repository}/contents/${encodeURIComponent(a).replace(/%2F/g, "/")}`;
303
+ let s;
304
+ const l = await fetch(`${o}?ref=${encodeURIComponent(e.branch)}`, {
305
+ headers: { Authorization: `Bearer ${t}` }
306
+ });
307
+ l.ok && (s = (await l.json()).sha);
308
+ const c = {
309
+ message: n,
310
+ content: btoa(unescape(encodeURIComponent(r))),
311
+ branch: e.branch,
312
+ sha: s
313
+ };
314
+ if (!(await fetch(o, {
315
+ method: "PUT",
316
+ headers: {
317
+ Authorization: `Bearer ${t}`,
318
+ "Content-Type": "application/json"
319
+ },
320
+ body: JSON.stringify(c)
321
+ })).ok)
322
+ throw new Error(`GitHub publish failed for ${a}`);
323
+ }
324
+ async function we(e, t, a) {
325
+ const r = `chore(cms): publish content updates ${(/* @__PURE__ */ new Date()).toISOString()}`;
326
+ if (await O(e, t, "cms-content.json", JSON.stringify(a.content, null, 2), r), a.locales)
327
+ for (const [n, o] of Object.entries(a.locales))
328
+ await O(e, t, `locales/${n}.json`, JSON.stringify(o, null, 2), r);
329
+ await O(e, t, "cms-export.patch.json", JSON.stringify(a, null, 2), r);
330
+ }
331
+ async function Le(e, t, a) {
332
+ const r = [
333
+ { filePath: "cms-content.json", content: JSON.stringify(a.content, null, 2) },
334
+ { filePath: "cms-export.patch.json", content: JSON.stringify(a, null, 2) }
335
+ ];
336
+ if (a.locales)
337
+ for (const [s, l] of Object.entries(a.locales))
338
+ r.push({ filePath: `locales/${s}.json`, content: JSON.stringify(l, null, 2) });
339
+ const n = [];
340
+ for (const s of r) {
341
+ const l = await ke(e, t, s.filePath);
342
+ n.push({
343
+ action: l ? "update" : "create",
344
+ file_path: s.filePath,
345
+ content: s.content
346
+ });
347
+ }
348
+ if (!(await fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(e.repository)}/repository/commits`, {
349
+ method: "POST",
350
+ headers: {
351
+ "PRIVATE-TOKEN": t,
352
+ "Content-Type": "application/json"
353
+ },
354
+ body: JSON.stringify({
355
+ branch: e.branch,
356
+ commit_message: `chore(cms): publish content updates ${(/* @__PURE__ */ new Date()).toISOString()}`,
357
+ actions: n
358
+ })
359
+ })).ok)
360
+ throw new Error("GitLab publish failed.");
361
+ }
362
+ async function $e(e, t, a) {
363
+ return !a || !t.trim() ? !1 : a.provider === "github" ? (await we(a, t.trim(), e), !0) : a.provider === "gitlab" ? (await Le(a, t.trim(), e), !0) : !1;
364
+ }
365
+ const J = "facms-auth-modal", M = "facms-auth-missing", F = "facms-token-modal", Y = "facms-language-modal", Se = "frontend-auto-cms::theme", ne = "frontend-auto-cms::main-language", oe = "frontend-auto-cms::language-labels";
366
+ let P = !1, v = null, V = !1, h = null, d = "dark";
367
+ function re() {
368
+ if (document.getElementById("facms-dashboard-css"))
369
+ return;
370
+ const e = document.createElement("style");
371
+ e.id = "facms-dashboard-css", e.textContent = `
372
+ #facms-app, #facms-app * { box-sizing: border-box; font-family: Inter, system-ui, sans-serif; }
373
+ #facms-app {
374
+ height: 100dvh;
375
+ width: 100vw;
376
+ display: grid;
377
+ grid-template-columns: 1fr;
378
+ background: #020617;
379
+ color: #e2e8f0;
380
+ overflow: hidden;
381
+ }
382
+ @media (min-width: 1024px) { #facms-app { grid-template-columns: 760px 1fr; } }
383
+ #facms-app > div { min-height: 0; }
384
+
385
+ #facms-app > div:first-child {
386
+ border-right: 1px solid #1e293b;
387
+ background: #0f172a;
388
+ overflow: hidden;
389
+ display: grid;
390
+ grid-template-rows: auto 1fr;
391
+ min-height: 0;
392
+ }
393
+ #facms-app > div:last-child { background: #020617; padding: 12px; }
394
+ @media (min-width: 1024px) { #facms-app > div:last-child { padding: 20px; } }
395
+
396
+ #facms-app h2 { margin: 0; font-size: 1.25rem; font-weight: 700; color: #f8fafc; }
397
+ #facms-app p { margin: 0; color: #94a3b8; }
398
+ #facms-app > div:first-child > div:last-child {
399
+ display: grid;
400
+ grid-template-columns: 220px 1fr;
401
+ min-height: 0;
402
+ }
403
+ #facms-app aside {
404
+ min-height: 0;
405
+ overflow: auto;
406
+ }
407
+ #facms-route-editor {
408
+ min-height: 0;
409
+ overflow: auto;
410
+ overscroll-behavior: contain;
411
+ }
412
+ #facms-route-editor > div { padding: 16px; min-height: 100%; }
413
+ #facms-route-editor #facms-i18n-status { margin-top: 8px; font-size: 12px; color: #94a3b8; }
414
+ #facms-i18n-feedback {
415
+ display: flex;
416
+ align-items: center;
417
+ justify-content: flex-start;
418
+ gap: 10px;
419
+ min-height: 20px;
420
+ margin-top: 8px;
421
+ }
422
+ #facms-translate-loader {
423
+ display: none;
424
+ align-items: center;
425
+ gap: 7px;
426
+ font-size: 12px;
427
+ color: #a5b4fc;
428
+ white-space: nowrap;
429
+ }
430
+ #facms-translate-loader::before {
431
+ content: "";
432
+ width: 12px;
433
+ height: 12px;
434
+ border-radius: 999px;
435
+ border: 2px solid #818cf8;
436
+ border-top-color: transparent;
437
+ animation: facms-spin 0.8s linear infinite;
438
+ }
439
+ @keyframes facms-spin {
440
+ from { transform: rotate(0deg); }
441
+ to { transform: rotate(360deg); }
442
+ }
443
+ #facms-global-loader {
444
+ position: fixed;
445
+ inset: 0;
446
+ display: none;
447
+ align-items: center;
448
+ justify-content: center;
449
+ background: rgba(2, 6, 23, 0.72);
450
+ z-index: 2147483646;
451
+ backdrop-filter: blur(2px);
452
+ }
453
+ #facms-global-loader-inner {
454
+ display: flex;
455
+ align-items: center;
456
+ gap: 10px;
457
+ background: #0f172a;
458
+ border: 1px solid #334155;
459
+ color: #e2e8f0;
460
+ border-radius: 12px;
461
+ padding: 10px 14px;
462
+ box-shadow: 0 20px 45px rgba(2, 6, 23, 0.45);
463
+ font-size: 13px;
464
+ font-weight: 500;
465
+ }
466
+ #facms-global-loader-spinner {
467
+ width: 15px;
468
+ height: 15px;
469
+ border-radius: 999px;
470
+ border: 2px solid #818cf8;
471
+ border-top-color: transparent;
472
+ animation: facms-spin 0.8s linear infinite;
473
+ }
474
+
475
+ #facms-route-preview {
476
+ width: 100%;
477
+ height: 100%;
478
+ border: 1px solid #1e293b;
479
+ border-radius: 12px;
480
+ background: #ffffff;
481
+ }
482
+
483
+ #facms-page-tabs { padding: 0 8px 12px; }
484
+ #facms-page-tabs button {
485
+ width: 100%;
486
+ text-align: left;
487
+ margin-bottom: 6px;
488
+ padding: 8px 12px;
489
+ border-radius: 10px;
490
+ border: 1px solid #334155;
491
+ background: #1e293b;
492
+ color: #e2e8f0;
493
+ cursor: pointer;
494
+ }
495
+
496
+ #facms-route-editor > div > div:nth-child(2) {
497
+ display: flex;
498
+ align-items: center;
499
+ flex-wrap: wrap;
500
+ gap: 10px;
501
+ margin-bottom: 4px;
502
+ }
503
+ #facms-language-tabs { display: flex; flex-wrap: wrap; gap: 10px; }
504
+ #facms-language-tabs > div {
505
+ display: inline-flex;
506
+ align-items: center;
507
+ border: 1px solid #334155;
508
+ border-radius: 10px;
509
+ overflow: hidden;
510
+ background: #1e293b;
511
+ }
512
+ #facms-language-tabs button, #facms-language-tabs span {
513
+ border: 0;
514
+ border-right: 1px solid #334155;
515
+ padding: 7px 11px;
516
+ font-size: 12px;
517
+ background: #1e293b;
518
+ color: #e2e8f0;
519
+ line-height: 1.1;
520
+ }
521
+ #facms-language-tabs [data-remove-lang] { border-right: 0; }
522
+
523
+ #facms-add-language, #facms-save {
524
+ border-radius: 10px;
525
+ padding: 8px 12px;
526
+ font-size: 13px;
527
+ cursor: pointer;
528
+ }
529
+ #facms-add-language {
530
+ border: 1px solid #6366f1;
531
+ background: #1e1b4b;
532
+ color: #c7d2fe;
533
+ }
534
+ #facms-add-language:disabled {
535
+ opacity: 0.6;
536
+ cursor: wait;
537
+ }
538
+ #facms-save {
539
+ border: 1px solid #059669;
540
+ background: #059669;
541
+ color: #ffffff;
542
+ font-weight: 600;
543
+ }
544
+
545
+ #facms-route-editor details {
546
+ border: 1px solid #334155;
547
+ border-radius: 12px;
548
+ background: #0f172a;
549
+ box-shadow: 0 6px 20px rgba(2, 6, 23, 0.35);
550
+ margin-bottom: 10px;
551
+ }
552
+ #facms-route-editor summary {
553
+ list-style: none;
554
+ cursor: pointer;
555
+ padding: 12px;
556
+ border-radius: 10px;
557
+ background: #1e293b;
558
+ border: 1px solid #334155;
559
+ margin: 8px;
560
+ color: #cbd5e1;
561
+ }
562
+ #facms-route-editor details > div { padding: 4px 10px 10px; }
563
+
564
+ #facms-route-editor input,
565
+ #facms-route-editor textarea,
566
+ #facms-route-editor select {
567
+ width: 100%;
568
+ border: 1px solid #334155;
569
+ background: #0b1220;
570
+ color: #e2e8f0;
571
+ border-radius: 10px;
572
+ padding: 8px 10px;
573
+ font-size: 13px;
574
+ outline: none;
575
+ }
576
+ #facms-route-editor textarea { min-height: 96px; resize: vertical; }
577
+ #facms-route-editor input:focus,
578
+ #facms-route-editor textarea:focus,
579
+ #facms-route-editor select:focus {
580
+ border-color: #6366f1;
581
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.25);
582
+ }
583
+
584
+ #facms-route-editor .text-slate-500,
585
+ #facms-route-editor .text-slate-400,
586
+ #facms-route-editor .text-slate-700,
587
+ #facms-route-editor .text-slate-300 { color: #94a3b8; }
588
+ #facms-route-editor .text-indigo-600,
589
+ #facms-route-editor .text-indigo-400 { color: #818cf8; }
590
+ #facms-route-editor .truncate {
591
+ white-space: nowrap;
592
+ overflow: hidden;
593
+ text-overflow: ellipsis;
594
+ }
595
+
596
+ #facms-app .bg-indigo-600, #facms-app .bg-indigo-700, #facms-app .bg-indigo-800 {
597
+ background: #4f46e5 !important;
598
+ color: #ffffff !important;
599
+ }
600
+ #facms-app .bg-emerald-600, #facms-app .hover\\:bg-emerald-500:hover {
601
+ background: #059669 !important;
602
+ color: #ffffff !important;
603
+ }
604
+ `, document.head.appendChild(e);
605
+ }
606
+ function se(e) {
607
+ d = e;
608
+ const t = e === "dark";
609
+ document.documentElement.classList.toggle("dark", t), document.body.classList.toggle("dark", t), document.documentElement.style.background = t ? "#020617" : "#f1f5f9", document.body.style.background = t ? "#020617" : "#f1f5f9", document.documentElement.setAttribute("data-facms-theme", e), document.getElementById("facms-app")?.classList.toggle("dark", t), localStorage.setItem(Se, e);
610
+ const r = document.getElementById("facms-theme-toggle");
611
+ r && (r.textContent = e === "dark" ? "Switch to light" : "Switch to dark");
612
+ }
613
+ function le() {
614
+ d = "dark", se("dark");
615
+ }
616
+ function Ee(e) {
617
+ return Array.from(new Uint8Array(e)).map((t) => t.toString(16).padStart(2, "0")).join("");
618
+ }
619
+ async function _e(e) {
620
+ const t = new TextEncoder().encode(e), a = await crypto.subtle.digest("SHA-256", t);
621
+ return Ee(a);
622
+ }
623
+ async function ie() {
624
+ if (h)
625
+ return h;
626
+ try {
627
+ const e = await fetch("/cms-settings.json", { cache: "no-store" });
628
+ if (e.ok) {
629
+ const t = await e.json();
630
+ return h = {
631
+ dashboardPath: t.dashboardPath || "/dashboard",
632
+ pages: (t.pages?.length ? t.pages : ["/"]).map((a) => a.startsWith("/") ? a : `/${a}`),
633
+ showFloatingButton: t.showFloatingButton ?? !1,
634
+ autoTranslateEnabled: t.autoTranslateEnabled ?? !0
635
+ }, h;
636
+ }
637
+ } catch {
638
+ }
639
+ return h = { dashboardPath: "/dashboard", pages: ["/"], showFloatingButton: !1, autoTranslateEnabled: !0 }, h;
640
+ }
641
+ async function R() {
642
+ if (!V) {
643
+ V = !0;
644
+ try {
645
+ const e = await fetch("/cms-runtime-auth.json", { cache: "no-store" });
646
+ if (!e.ok) return;
647
+ const t = await e.json();
648
+ t.algorithm === "sha256" && typeof t.salt == "string" && typeof t.passcodeHash == "string" && (v = { algorithm: "sha256", salt: t.salt, passcodeHash: t.passcodeHash });
649
+ } catch {
650
+ v = null;
651
+ }
652
+ }
653
+ }
654
+ async function Ae(e) {
655
+ const t = e.trim();
656
+ return !t || (await R(), !v) ? !1 : await _e(`${v.salt}:${t}`) === v.passcodeHash;
657
+ }
658
+ function Te() {
659
+ let e = document.getElementById(M);
660
+ e || (e = document.createElement("div"), e.id = M, e.style.position = "fixed", e.style.inset = "0", e.style.zIndex = "2147483647", e.style.background = "rgba(2, 6, 23, 0.88)", e.style.display = "grid", e.style.placeItems = "center", e.innerHTML = `
661
+ <div style="width:min(560px,92vw);background:#0f172a;color:#e2e8f0;border:1px solid #334155;border-radius:16px;padding:16px;box-shadow:0 20px 55px rgba(2,6,23,.45);font-family:Inter,system-ui,sans-serif;">
662
+ <h3 style="margin:0 0 8px 0;font-size:20px;line-height:1.2;font-weight:700;">CMS locked: runtime auth missing</h3>
663
+ <p style="margin:0 0 10px 0;font-size:13px;color:#94a3b8;">The dashboard is blocked until runtime auth is generated.</p>
664
+ <p style="margin:0;font-size:12px;color:#cbd5e1;">Run <code style="color:#e2e8f0;">frontend-auto-cms setup</code> in this project to create <code style="color:#e2e8f0;">public/cms-runtime-auth.json</code>, then refresh.</p>
665
+ </div>
666
+ `, document.body.appendChild(e));
667
+ }
668
+ function Ie() {
669
+ document.getElementById(M)?.remove();
670
+ }
671
+ async function ce() {
672
+ return await R(), v ? (Ie(), !0) : (Te(), !1);
673
+ }
674
+ function de(e) {
675
+ const t = document.getElementById(J);
676
+ if (t) {
677
+ t.style.display = "grid";
678
+ return;
679
+ }
680
+ const a = document.createElement("div");
681
+ a.id = J, a.style.position = "fixed", a.style.inset = "0", a.style.zIndex = "2147483647", a.style.background = "rgba(2, 6, 23, 0.45)", a.style.display = "grid", a.style.placeItems = "center", a.innerHTML = `
682
+ <div style="width: min(420px, 92vw); background: ${d === "dark" ? "#0f172a" : "#ffffff"}; color: ${d === "dark" ? "#e2e8f0" : "#0f172a"}; border: 1px solid ${d === "dark" ? "#334155" : "#e2e8f0"}; border-radius: 16px; padding: 16px; box-shadow: 0 20px 55px rgba(2,6,23,0.45); font-family: Inter, system-ui, sans-serif;">
683
+ <h3 style="margin: 0 0 6px 0; font-size: 20px; line-height: 1.2; font-weight: 700;">Unlock CMS</h3>
684
+ <p style="margin: 0 0 12px 0; font-size: 12px; color: ${d === "dark" ? "#94a3b8" : "#475569"};">Enter your passcode to edit content.</p>
685
+ <input id="facms-passcode-input" type="password" style="width: 100%; border: 1px solid ${d === "dark" ? "#475569" : "#cbd5e1"}; background: ${d === "dark" ? "#1e293b" : "#ffffff"}; color: ${d === "dark" ? "#f1f5f9" : "#0f172a"}; border-radius: 10px; padding: 10px 12px; font-size: 14px; box-sizing: border-box;" placeholder="Enter passcode" />
686
+ <p id="facms-passcode-error" style="display:none; margin: 8px 0 0 0; font-size: 12px; color: #ef4444;">Incorrect passcode.</p>
687
+ <div style="margin-top: 14px; display: flex; gap: 8px; justify-content: flex-end;">
688
+ <button id="facms-passcode-cancel" style="border: 1px solid ${d === "dark" ? "#475569" : "#cbd5e1"}; background: ${d === "dark" ? "#1e293b" : "#f1f5f9"}; color: ${d === "dark" ? "#e2e8f0" : "#334155"}; border-radius: 10px; padding: 8px 12px; font-size: 13px; cursor: pointer;">Cancel</button>
689
+ <button id="facms-passcode-submit" style="border: 1px solid #4f46e5; background: #4f46e5; color: white; border-radius: 10px; padding: 8px 12px; font-size: 13px; cursor: pointer;">Open CMS</button>
690
+ </div>
691
+ </div>
692
+ `;
693
+ const r = a.querySelector("#facms-passcode-input"), n = a.querySelector("#facms-passcode-error"), o = () => a.style.display = "none", s = async () => {
694
+ if (await R(), !v) {
695
+ n && (n.textContent = "CMS auth is not configured. Run setup to generate runtime auth files.", n.style.display = "block");
696
+ return;
697
+ }
698
+ if (!await Ae(r?.value ?? "")) {
699
+ n && (n.textContent = "Incorrect passcode.", n.style.display = "block");
700
+ return;
701
+ }
702
+ n && (n.style.display = "none"), P = !0, o(), e();
703
+ };
704
+ a.querySelector("#facms-passcode-cancel")?.addEventListener("click", o), a.querySelector("#facms-passcode-submit")?.addEventListener("click", s), r?.addEventListener("keydown", (l) => l.key === "Enter" && void s()), document.body.appendChild(a), r?.focus();
705
+ }
706
+ function Ce(e, t) {
707
+ const a = document.getElementById(F);
708
+ a && a.remove();
709
+ const r = document.createElement("div");
710
+ r.id = F, r.style.position = "fixed", r.style.inset = "0", r.style.zIndex = "2147483647", r.style.background = "rgba(2, 6, 23, 0.45)", r.style.display = "grid", r.style.placeItems = "center", r.innerHTML = `
711
+ <div style="width: min(520px, 92vw); background: ${d === "dark" ? "#0f172a" : "#ffffff"}; color: ${d === "dark" ? "#e2e8f0" : "#0f172a"}; border: 1px solid ${d === "dark" ? "#334155" : "#e2e8f0"}; border-radius: 16px; padding: 16px; box-shadow: 0 20px 55px rgba(2,6,23,0.45); font-family: Inter, system-ui, sans-serif;">
712
+ <h3 style="margin: 0 0 6px 0; font-size: 20px; line-height: 1.2; font-weight: 700;">Publish token required</h3>
713
+ <p style="margin: 0 0 12px 0; font-size: 12px; color: ${d === "dark" ? "#94a3b8" : "#475569"};">Enter your ${e} token for this publish only. It is not stored.</p>
714
+ <input id="facms-token-input" type="password" style="width: 100%; border: 1px solid ${d === "dark" ? "#475569" : "#cbd5e1"}; background: ${d === "dark" ? "#1e293b" : "#ffffff"}; color: ${d === "dark" ? "#f1f5f9" : "#0f172a"}; border-radius: 10px; padding: 10px 12px; font-size: 14px; box-sizing: border-box;" placeholder="Token" />
715
+ <p id="facms-token-error" style="display:none; margin: 8px 0 0 0; font-size: 12px; color: #ef4444;">Token is required.</p>
716
+ <div style="margin-top: 14px; display: flex; gap: 8px; justify-content: flex-end;">
717
+ <button id="facms-token-cancel" style="border: 1px solid ${d === "dark" ? "#475569" : "#cbd5e1"}; background: ${d === "dark" ? "#1e293b" : "#f1f5f9"}; color: ${d === "dark" ? "#e2e8f0" : "#334155"}; border-radius: 10px; padding: 8px 12px; font-size: 13px; cursor: pointer;">Cancel</button>
718
+ <button id="facms-token-submit" style="border: 1px solid #4f46e5; background: #4f46e5; color: white; border-radius: 10px; padding: 8px 12px; font-size: 13px; cursor: pointer;">Publish</button>
719
+ </div>
720
+ </div>
721
+ `;
722
+ const n = r.querySelector("#facms-token-input"), o = r.querySelector("#facms-token-error"), s = () => r.style.display = "none", l = () => {
723
+ const c = (n?.value ?? "").trim();
724
+ if (!c) {
725
+ o && (o.style.display = "block");
726
+ return;
727
+ }
728
+ o && (o.style.display = "none"), s(), t(c);
729
+ };
730
+ r.querySelector("#facms-token-cancel")?.addEventListener("click", s), r.querySelector("#facms-token-submit")?.addEventListener("click", l), n?.addEventListener("keydown", (c) => c.key === "Enter" && l()), document.body.appendChild(r), n?.focus();
731
+ }
732
+ function Oe(e, t, a) {
733
+ const r = document.getElementById(Y);
734
+ r && r.remove();
735
+ const n = document.createElement("div");
736
+ n.id = Y, n.style.position = "fixed", n.style.inset = "0", n.style.zIndex = "2147483647", n.style.background = "rgba(2, 6, 23, 0.45)", n.style.display = "grid", n.style.placeItems = "center", n.innerHTML = `
737
+ <div style="width: min(520px, 92vw); background: ${d === "dark" ? "#0f172a" : "#ffffff"}; color: ${d === "dark" ? "#e2e8f0" : "#0f172a"}; border: 1px solid ${d === "dark" ? "#334155" : "#e2e8f0"}; border-radius: 16px; padding: 16px; box-shadow: 0 20px 55px rgba(2,6,23,0.45); font-family: Inter, system-ui, sans-serif;">
738
+ <h3 style="margin: 0 0 6px 0; font-size: 20px; line-height: 1.2; font-weight: 700;">Add translation</h3>
739
+ <p style="margin: 0 0 8px 0; font-size: 12px; color: ${d === "dark" ? "#94a3b8" : "#475569"};">Pick a supported language or define a custom one for manual translation.</p>
740
+ <select id="facms-language-select" style="width: 100%; border: 1px solid ${d === "dark" ? "#475569" : "#cbd5e1"}; background: ${d === "dark" ? "#1e293b" : "#ffffff"}; color: ${d === "dark" ? "#f1f5f9" : "#0f172a"}; border-radius: 10px; padding: 10px 12px; font-size: 14px; box-sizing: border-box; margin-bottom: 8px;">
741
+ <option value="">Select supported language</option>
742
+ ${B.filter((u) => !e.includes(u.code)).map((u) => `<option value="${u.code}">${u.label} (${u.code})</option>`).join("")}
743
+ <option value="__custom__">Custom language (manual)</option>
744
+ </select>
745
+ <input id="facms-language-name" type="text" placeholder="Custom language name (e.g. Klingon)" style="width: 100%; border: 1px solid ${d === "dark" ? "#475569" : "#cbd5e1"}; background: ${d === "dark" ? "#1e293b" : "#ffffff"}; color: ${d === "dark" ? "#f1f5f9" : "#0f172a"}; border-radius: 10px; padding: 10px 12px; font-size: 14px; box-sizing: border-box; margin-bottom: 8px; display:none;" />
746
+ <input id="facms-language-input" type="text" placeholder="Custom language code (e.g. tlh)" style="width: 100%; border: 1px solid ${d === "dark" ? "#475569" : "#cbd5e1"}; background: ${d === "dark" ? "#1e293b" : "#ffffff"}; color: ${d === "dark" ? "#f1f5f9" : "#0f172a"}; border-radius: 10px; padding: 10px 12px; font-size: 14px; box-sizing: border-box; display:none;" />
747
+ <p id="facms-language-error" style="display:none; margin: 8px 0 0 0; font-size: 12px; color: #ef4444;">Enter a valid language code.</p>
748
+ <div style="margin-top: 14px; display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap;">
749
+ <button id="facms-language-cancel" style="border: 1px solid ${d === "dark" ? "#475569" : "#cbd5e1"}; background: ${d === "dark" ? "#1e293b" : "#f1f5f9"}; color: ${d === "dark" ? "#e2e8f0" : "#334155"}; border-radius: 10px; padding: 8px 12px; font-size: 13px; cursor: pointer;">Cancel</button>
750
+ <button id="facms-language-manual" style="border: 1px solid #334155; background: #334155; color: white; border-radius: 10px; padding: 8px 12px; font-size: 13px; cursor: pointer;">Add manual</button>
751
+ ${t ? '<button id="facms-language-autofill" style="border: 1px solid #4f46e5; background: #4f46e5; color: white; border-radius: 10px; padding: 8px 12px; font-size: 13px; cursor: pointer;">Add & Auto-fill</button>' : ""}
752
+ </div>
753
+ </div>
754
+ `;
755
+ const o = () => n.style.display = "none";
756
+ n.querySelector("#facms-language-cancel")?.addEventListener("click", o);
757
+ const s = n.querySelector("#facms-language-select"), l = n.querySelector("#facms-language-input"), c = n.querySelector("#facms-language-name");
758
+ s?.addEventListener("change", () => {
759
+ const u = s.value === "__custom__";
760
+ l && (l.style.display = u ? "block" : "none"), c && (c.style.display = u ? "block" : "none");
761
+ });
762
+ const f = (u) => {
763
+ const i = n.querySelector("#facms-language-error");
764
+ let p = "";
765
+ const g = s?.value ?? "";
766
+ let y = "";
767
+ if (g && g !== "__custom__" ? (p = T(g), y = $(p)) : (p = T(l?.value ?? ""), y = (c?.value ?? "").trim()), !p || !/^[a-z]{2,3}([_-][a-z0-9]{2,8})?$|^[a-z]{3}_[A-Za-z]+$/i.test(p)) {
768
+ i && (i.textContent = "Enter a valid language code.", i.style.display = "block");
769
+ return;
770
+ }
771
+ if (y || (y = $(p)), e.includes(p)) {
772
+ i && (i.textContent = `Language "${p}" already exists.`, i.style.display = "block");
773
+ return;
774
+ }
775
+ i && (i.style.display = "none"), o(), a(`${p}::${y}`, u);
776
+ };
777
+ n.querySelector("#facms-language-manual")?.addEventListener("click", () => f("manual")), t && n.querySelector("#facms-language-autofill")?.addEventListener("click", () => f("auto")), document.body.appendChild(n);
778
+ }
779
+ function j(e, t) {
780
+ if (t === void 0) {
781
+ console.info(`[frontend-auto-cms:dashboard] ${e}`);
782
+ return;
783
+ }
784
+ console.info(`[frontend-auto-cms:dashboard] ${e}`, t);
785
+ }
786
+ function T(e) {
787
+ return e.trim().toLowerCase();
788
+ }
789
+ function $(e) {
790
+ const t = T(e), a = B.find((r) => r.code === t);
791
+ return a ? a.label : t.toUpperCase();
792
+ }
793
+ function je() {
794
+ try {
795
+ const e = localStorage.getItem(oe);
796
+ if (!e)
797
+ return {};
798
+ const t = JSON.parse(e);
799
+ return t && typeof t == "object" ? t : {};
800
+ } catch {
801
+ return {};
802
+ }
803
+ }
804
+ function D(e) {
805
+ localStorage.setItem(oe, JSON.stringify(e));
806
+ }
807
+ function I(e) {
808
+ const t = localStorage.getItem(ne);
809
+ return t && e[t] ? t : e.en ? "en" : Object.keys(e)[0] ?? "en";
810
+ }
811
+ function W(e) {
812
+ localStorage.setItem(ne, e);
813
+ }
814
+ function x(e) {
815
+ return e.replace(/[&<>'"]/g, (t) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;" })[t]);
816
+ }
817
+ function _(e, t = 72) {
818
+ const a = e.replace(/\s+/g, " ").trim();
819
+ return a.length <= t ? a : `${a.slice(0, t)}...`;
820
+ }
821
+ function ze(e) {
822
+ return e.type === "section" ? _((e.sectionItems ?? []).join(" | ")) : e.type === "property" ? _(JSON.stringify(e.attrs ?? {})) : _(e.value);
823
+ }
824
+ function Ne(e) {
825
+ const t = [];
826
+ return e.forEach((a, r) => {
827
+ const n = (a.selector ?? "").trim(), o = t[t.length - 1];
828
+ if (!!(o && n && o.selector === n) && o) {
829
+ o.items.push({ node: a, index: r });
830
+ return;
831
+ }
832
+ t.push({
833
+ key: n || `group_${r}`,
834
+ selector: n,
835
+ items: [{ node: a, index: r }]
836
+ });
837
+ }), t;
838
+ }
839
+ function Me(e) {
840
+ const t = (a) => {
841
+ if (Array.isArray(a))
842
+ return a.map(t);
843
+ if (a && typeof a == "object") {
844
+ const r = Object.entries(a).sort(([n], [o]) => n.localeCompare(o));
845
+ return Object.fromEntries(r.map(([n, o]) => [n, t(o)]));
846
+ }
847
+ return a;
848
+ };
849
+ return JSON.stringify(t(e));
850
+ }
851
+ async function Be(e) {
852
+ const t = new TextEncoder().encode(e), a = await crypto.subtle.digest("SHA-256", t);
853
+ return Array.from(new Uint8Array(a)).map((r) => r.toString(16).padStart(2, "0")).join("");
854
+ }
855
+ async function Pe(e, t, a) {
856
+ const r = e.nodes.flatMap(
857
+ (s) => s.sourceRefs.map((l) => ({
858
+ file: l.file,
859
+ find: l.original,
860
+ replace: s.type === "property" ? JSON.stringify(s.attrs ?? {}) : s.type === "section" ? (s.sectionItems ?? []).join(" ") : s.type === "text" ? a[s.key] ?? s.value : s.value,
861
+ occurrence: l.occurrence ?? 1
862
+ }))
863
+ ), n = {
864
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
865
+ content: { ...e, updatedAt: (/* @__PURE__ */ new Date()).toISOString() },
866
+ operations: r,
867
+ locales: t
868
+ }, o = await Be(Me(n));
869
+ return {
870
+ ...n,
871
+ integrity: {
872
+ algorithm: "sha256",
873
+ value: o
874
+ }
875
+ };
876
+ }
877
+ function Re(e, t) {
878
+ const a = `${e.type.toUpperCase()} • ${x(e.label || e.selector || "")}`, r = `<div class="text-xs text-slate-500 dark:text-slate-400 mb-2">#${t + 1} ${a}</div>`;
879
+ if (e.type === "text")
880
+ return `<div class="rounded-lg p-2 bg-slate-50/60 dark:bg-slate-900/40">${r}<textarea data-cms-id="${e.id}" data-cms-field="value" class="w-full border border-slate-300 dark:border-slate-800 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 rounded-lg p-2 text-sm h-24 focus:outline-none focus:ring-2 focus:ring-indigo-500/40">${x(e.value)}</textarea></div>`;
881
+ if (e.type === "image" || e.type === "video")
882
+ return `<div class="rounded-lg p-2 bg-slate-50/60 dark:bg-slate-900/40">${r}<label class="text-xs text-slate-500 dark:text-slate-400">Source URL</label><input data-cms-id="${e.id}" data-cms-field="value" value="${x(e.value)}" class="w-full border border-slate-300 dark:border-slate-800 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 rounded-lg p-2 text-sm mb-2 focus:outline-none focus:ring-2 focus:ring-indigo-500/40" /><label class="text-xs text-slate-500 dark:text-slate-400">Alt text</label><input data-cms-id="${e.id}" data-cms-field="alt" value="${x(e.attrs?.alt ?? "")}" class="w-full border border-slate-300 dark:border-slate-800 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 rounded-lg p-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/40" /></div>`;
883
+ if (e.type === "section") {
884
+ const n = (e.sectionItems ?? []).join(`
885
+ `);
886
+ return `<div class="rounded-lg p-2 bg-slate-50/60 dark:bg-slate-900/40">${r}<textarea data-cms-id="${e.id}" data-cms-field="sectionItems" class="w-full border border-slate-300 dark:border-slate-800 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 rounded-lg p-2 text-sm h-28 focus:outline-none focus:ring-2 focus:ring-indigo-500/40">${x(n)}</textarea></div>`;
887
+ }
888
+ return `<div class="rounded-lg p-2 bg-slate-50/60 dark:bg-slate-900/40">${r}<textarea data-cms-id="${e.id}" data-cms-field="attrs" class="w-full border border-slate-300 dark:border-slate-800 bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 rounded-lg p-2 text-xs h-28 focus:outline-none focus:ring-2 focus:ring-indigo-500/40">${x(JSON.stringify(e.attrs ?? {}, null, 2))}</textarea></div>`;
889
+ }
890
+ function S(e, t, a, r) {
891
+ e.nodes.forEach((n) => {
892
+ n.type === "text" && (n.value = t[n.key] ?? r[n.key] ?? n.value, ae(n, a));
893
+ });
894
+ }
895
+ function De(e, t) {
896
+ e.activeLanguage = t;
897
+ const a = e.locales[t] ?? e.locales[e.mainLanguage] ?? e.baseTextByKey;
898
+ S(e.content, a, e.workingDocument, e.baseTextByKey);
899
+ }
900
+ function qe(e, t) {
901
+ const a = e.querySelector("#facms-language-tabs");
902
+ if (!a)
903
+ return;
904
+ const r = Object.keys(t.locales);
905
+ a.innerHTML = r.map((n) => {
906
+ const o = n === t.activeLanguage, s = n === t.mainLanguage, l = t.languageLabels[n] ?? $(n);
907
+ return `<div class="inline-flex items-center rounded-lg overflow-hidden border ${o ? "border-indigo-500" : "border-slate-200 dark:border-slate-700"}">
908
+ <button data-lang="${n}" class="px-2.5 py-1.5 text-xs ${o ? "bg-indigo-600 text-white" : "bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-200"}">${x(l)}</button>
909
+ ${s ? `<span class="px-2 py-1.5 text-[10px] font-semibold ${o ? "bg-indigo-700 text-indigo-100" : "bg-emerald-600 text-white"}">MAIN</span>` : `<button data-main-lang="${n}" title="Set as main language" class="px-2 py-1.5 text-xs ${o ? "bg-indigo-700 text-indigo-100 hover:bg-indigo-800" : "bg-emerald-600 text-white hover:bg-emerald-500"}">★</button>`}
910
+ <button data-remove-lang="${n}" title="Remove language" class="px-2 py-1.5 text-xs ${o ? "bg-indigo-700 text-indigo-100 hover:bg-indigo-800" : "bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-200 hover:bg-slate-300 dark:hover:bg-slate-600"}">×</button>
911
+ </div>`;
912
+ }).join(""), a.querySelectorAll("[data-lang]").forEach((n) => {
913
+ n.addEventListener("click", () => {
914
+ const o = n.dataset.lang;
915
+ o && (De(t, o), L(e, t));
916
+ });
917
+ }), a.querySelectorAll("[data-main-lang]").forEach((n) => {
918
+ n.addEventListener("click", (o) => {
919
+ o.stopPropagation();
920
+ const s = n.dataset.mainLang;
921
+ !s || !t.locales[s] || (t.mainLanguage = s, W(s), t.baseTextByKey = { ...t.locales[s] }, L(e, t));
922
+ });
923
+ }), a.querySelectorAll("[data-remove-lang]").forEach((n) => {
924
+ n.addEventListener("click", (o) => {
925
+ o.stopPropagation();
926
+ const s = n.dataset.removeLang;
927
+ if (s) {
928
+ if (Object.keys(t.locales).length <= 1) {
929
+ alert("You must keep at least one language.");
930
+ return;
931
+ }
932
+ delete t.locales[s], delete t.languageLabels[s], D(t.languageLabels), A(t.locales), t.mainLanguage === s && (t.mainLanguage = I(t.locales), W(t.mainLanguage), t.baseTextByKey = { ...t.locales[t.mainLanguage] ?? {} }), t.activeLanguage === s && (t.activeLanguage = q(t.locales, t.mainLanguage)), ue(t), L(e, t);
933
+ }
934
+ });
935
+ });
936
+ }
937
+ function He(e, t) {
938
+ e.querySelectorAll("[data-cms-id]").forEach((a) => {
939
+ const r = a.dataset.cmsId, n = a.dataset.cmsField;
940
+ !r || !n || a.addEventListener("input", () => {
941
+ const o = t.content.nodes.find((s) => s.id === r);
942
+ if (o) {
943
+ if (n === "value")
944
+ o.value = a.value, o.type === "text" && (t.activeLanguage === t.mainLanguage ? (t.baseTextByKey[o.key] = a.value, t.locales[t.mainLanguage] = { ...t.locales[t.mainLanguage] ?? {}, [o.key]: a.value }) : t.locales[t.activeLanguage] = {
945
+ ...t.locales[t.activeLanguage] ?? {},
946
+ [o.key]: a.value
947
+ }, A(t.locales));
948
+ else if (n === "alt") o.attrs = { ...o.attrs ?? {}, alt: a.value };
949
+ else if (n === "sectionItems") o.sectionItems = a.value.split(`
950
+ `).map((s) => s.trim()).filter(Boolean);
951
+ else if (n === "attrs")
952
+ try {
953
+ o.attrs = JSON.parse(a.value);
954
+ } catch {
955
+ return;
956
+ }
957
+ ae(o, t.workingDocument), Q(t.content);
958
+ }
959
+ });
960
+ });
961
+ }
962
+ function w(e, t, a) {
963
+ const r = e.querySelector("#facms-i18n-status"), n = e.querySelector("#facms-translate-loader"), o = e.querySelector("#facms-add-language"), s = document.getElementById("facms-global-loader"), l = document.getElementById("facms-global-loader-label");
964
+ r && (r.textContent = t), n && (n.style.display = a ? "inline-flex" : "none"), o && (o.disabled = a), s && (s.style.display = a ? "flex" : "none"), l && (l.textContent = a ? t : "Translating");
965
+ }
966
+ function L(e, t) {
967
+ const r = Ne(t.content.nodes).map((n) => {
968
+ const o = n.items[0], s = n.items[n.items.length - 1], l = o.index === s.index ? `#${o.index + 1}` : `#${o.index + 1} - #${s.index + 1}`, c = n.items.map((i) => ze(i.node)).filter(Boolean).join(" • "), f = n.items.length > 1 ? `${l} ${x(n.selector || "Grouped fields")} (${n.items.length} fields)` : `${l} ${x(o.node.type.toUpperCase())} • ${x(o.node.label || o.node.selector || "")}`, u = n.items.map((i) => Re(i.node, i.index)).join("");
969
+ return `
970
+ <details class="rounded-xl p-2 border border-slate-200 dark:border-transparent bg-white dark:bg-slate-900/60 shadow-sm">
971
+ <summary class="list-none cursor-pointer flex items-center justify-between gap-2 p-3 rounded-lg bg-slate-50 dark:bg-slate-800/70 border border-slate-200 dark:border-slate-800">
972
+ <div class="min-w-0">
973
+ <div class="text-xs text-slate-500 dark:text-slate-400">${f}</div>
974
+ <div class="text-sm text-slate-700 dark:text-slate-300 truncate">${x(_(c || "No preview"))}</div>
975
+ </div>
976
+ <span class="text-xs text-indigo-600 dark:text-indigo-400">Expand</span>
977
+ </summary>
978
+ <div class="pt-3 px-2 pb-2 space-y-2">${u}</div>
979
+ </details>
980
+ `;
981
+ }).join("");
982
+ e.innerHTML = `
983
+ <div class="p-4 space-y-3">
984
+ <p class="text-xs text-slate-500 dark:text-slate-400 mb-2">All editable elements are shown in page order.</p>
985
+ <div class="flex items-center flex-wrap gap-2">
986
+ <div id="facms-language-tabs" class="flex flex-wrap gap-2"></div>
987
+ <button id="facms-add-language" class="px-2.5 py-1.5 rounded-lg text-xs border border-indigo-500 text-indigo-600 dark:text-indigo-300 dark:border-indigo-400 hover:bg-indigo-50 dark:hover:bg-indigo-900/30">+ Add translation</button>
988
+ </div>
989
+ <div class="space-y-3">${r}</div>
990
+ <div class="mt-4 flex flex-wrap gap-2">
991
+ <button id="facms-save" class="px-3 py-2 bg-emerald-600 text-white rounded-lg text-sm hover:bg-emerald-500 shadow">Save + Publish</button>
992
+ </div>
993
+ <div id="facms-i18n-feedback">
994
+ <div id="facms-translate-loader">Translating</div>
995
+ <p id="facms-i18n-status" class="text-xs text-slate-500 dark:text-slate-400"></p>
996
+ </div>
997
+ </div>
998
+ `, qe(e, t), e.querySelector("#facms-save")?.addEventListener("click", async () => {
999
+ const n = await Pe(t.content, t.locales, t.baseTextByKey), o = await ve();
1000
+ if (!o || o.provider === "none") {
1001
+ be(n), alert("Patch downloaded. Configure hosting in setup for one-click publish.");
1002
+ return;
1003
+ }
1004
+ const s = o.provider;
1005
+ Ce(s, async (l) => {
1006
+ let c = !1;
1007
+ try {
1008
+ c = await $e(n, l, o);
1009
+ } catch {
1010
+ c = !1;
1011
+ }
1012
+ if (!c) {
1013
+ alert("Publishing failed. Please verify token permissions and repository settings.");
1014
+ return;
1015
+ }
1016
+ alert("Changes published successfully.");
1017
+ });
1018
+ }), e.querySelector("#facms-add-language")?.addEventListener("click", () => {
1019
+ Oe(Object.keys(t.locales), h?.autoTranslateEnabled ?? !0, async (n, o) => {
1020
+ const [s, l] = n.split("::"), c = T(s ?? ""), f = (l ?? "").trim() || $(c);
1021
+ if (!c)
1022
+ return;
1023
+ const u = performance.now(), i = t.locales[t.mainLanguage] ?? t.baseTextByKey, p = { ...i };
1024
+ if (j("Add language requested", {
1025
+ lang: c,
1026
+ mode: o,
1027
+ sourceKeys: Object.keys(i).length,
1028
+ browserLanguage: navigator.language,
1029
+ browserLanguages: navigator.languages
1030
+ }), t.locales[c] = p, t.languageLabels[c] = f, D(t.languageLabels), t.activeLanguage = c, A(t.locales), S(t.content, t.locales[c], t.workingDocument, t.baseTextByKey), L(e, t), j("Manual language tab created", { lang: c }), w(e, `Added ${f} tab prefilled with source text for manual translation.`, !1), o !== "auto" || !(h?.autoTranslateEnabled ?? !0)) {
1031
+ o === "auto" && !(h?.autoTranslateEnabled ?? !0) && w(e, `Privacy mode is on. Added ${f} tab for manual translation only.`, !1);
1032
+ return;
1033
+ }
1034
+ try {
1035
+ const g = Object.keys(i).length;
1036
+ w(e, `Auto-filling ${f} translation... (0/${g})`, !0);
1037
+ const y = await he(i, c, t.mainLanguage, (H, b) => {
1038
+ w(e, `Auto-filling ${f} translation... (${H}/${b})`, !0);
1039
+ });
1040
+ t.locales[c] = y, A(t.locales), t.activeLanguage = c, S(t.content, y, t.workingDocument, t.baseTextByKey), L(e, t), j("Automatic translation succeeded", {
1041
+ lang: c,
1042
+ translatedKeys: Object.keys(y).length,
1043
+ ms: Math.round(performance.now() - u)
1044
+ }), w(e, `Added ${f} with auto-filled translation. Review and edit as needed.`, !1);
1045
+ } catch (g) {
1046
+ console.error("[frontend-auto-cms:dashboard] Automatic translation failed", {
1047
+ lang: c,
1048
+ message: g instanceof Error ? g.message : String(g),
1049
+ stack: g instanceof Error ? g.stack : void 0,
1050
+ ms: Math.round(performance.now() - u)
1051
+ }), w(e, `Auto-fill failed. ${f} stays prefilled with source text for manual editing.`, !1);
1052
+ }
1053
+ });
1054
+ }), He(e, t);
1055
+ }
1056
+ function q(e, t) {
1057
+ const a = Object.keys(e), r = t && e[t] ? t : I(e), n = [...navigator.languages ?? [], navigator.language].map((o) => o.toLowerCase().split("-")[0]);
1058
+ for (const o of n)
1059
+ if (a.includes(o))
1060
+ return o;
1061
+ return a.includes(r) ? r : a[0] ?? "en";
1062
+ }
1063
+ function ue(e) {
1064
+ const t = e.locales[e.activeLanguage] ?? e.locales.en ?? e.baseTextByKey;
1065
+ S(e.content, t, e.workingDocument, e.baseTextByKey);
1066
+ }
1067
+ function Ue(e, t, a) {
1068
+ const r = ee(), n = {
1069
+ ...a,
1070
+ ...r
1071
+ }, o = je(), s = Object.fromEntries(e.nodes.filter((i) => i.type === "text").map((i) => [i.key, i.value])), l = I(n), c = {
1072
+ ...n,
1073
+ [l]: { ...n[l] ?? {}, ...s }
1074
+ };
1075
+ !c.en && l === "en" && (c.en = Object.assign({}, c.en ?? {}, s));
1076
+ const f = q(c, l), u = { ...o };
1077
+ return Object.keys(c).forEach((i) => {
1078
+ u[i] || (u[i] = $(i));
1079
+ }), D(u), {
1080
+ content: e,
1081
+ locales: c,
1082
+ workingDocument: t,
1083
+ baseTextByKey: c[l] ?? s,
1084
+ activeLanguage: f,
1085
+ mainLanguage: l,
1086
+ languageLabels: u
1087
+ };
1088
+ }
1089
+ function Ge() {
1090
+ const e = ee();
1091
+ if (!Object.keys(e).length)
1092
+ return;
1093
+ const t = q(e, I(e));
1094
+ if (!t || t === "en")
1095
+ return;
1096
+ const a = N(document, location.pathname), r = e[t];
1097
+ if (!r)
1098
+ return;
1099
+ const n = Object.fromEntries(a.nodes.filter((o) => o.type === "text").map((o) => [o.key, o.value]));
1100
+ S(a, r, document, n);
1101
+ }
1102
+ function Ke(e) {
1103
+ const t = e.pages.length ? e.pages : ["/"];
1104
+ document.documentElement.style.margin = "0", document.documentElement.style.padding = "0", document.documentElement.style.background = d === "dark" ? "#020617" : "#f1f5f9", document.body.style.margin = "0", document.body.style.padding = "0", document.body.style.background = d === "dark" ? "#020617" : "#f1f5f9", document.body.innerHTML = `
1105
+ <div id="facms-app" class="h-screen w-screen grid grid-cols-1 lg:grid-cols-[760px_1fr] bg-slate-100 dark:bg-slate-950 text-slate-900 dark:text-slate-100">
1106
+ <div class="border-r border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 overflow-hidden grid grid-rows-[auto_1fr]">
1107
+ <div class="p-4 border-b border-slate-200 dark:border-slate-800 flex items-start justify-between gap-3">
1108
+ <div>
1109
+ <h2 class="font-semibold text-xl">CMS Dashboard</h2>
1110
+ <p class="text-xs text-slate-500 dark:text-slate-400">Edit pages from one place.</p>
1111
+ </div>
1112
+ </div>
1113
+ <div class="grid grid-cols-[220px_1fr] min-h-0">
1114
+ <aside class="border-r border-slate-200 dark:border-slate-800 overflow-auto">
1115
+ <div class="px-3 pt-3 pb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">Routes</div>
1116
+ <div id="facms-page-tabs" class="px-2 pb-3 space-y-1"></div>
1117
+ </aside>
1118
+ <div id="facms-route-editor" class="overflow-auto"></div>
1119
+ </div>
1120
+ </div>
1121
+ <div class="bg-slate-200 dark:bg-slate-950 p-3 lg:p-5">
1122
+ <iframe id="facms-route-preview" class="w-full h-full border border-slate-200 dark:border-slate-800 rounded-xl bg-white dark:bg-slate-950" src="/"></iframe>
1123
+ </div>
1124
+ </div>
1125
+ <div id="facms-global-loader" aria-live="polite" aria-busy="true">
1126
+ <div id="facms-global-loader-inner">
1127
+ <div id="facms-global-loader-spinner"></div>
1128
+ <div id="facms-global-loader-label">Translating</div>
1129
+ </div>
1130
+ </div>
1131
+ `;
1132
+ const a = document.getElementById("facms-route-preview"), r = document.getElementById("facms-route-editor"), n = document.getElementById("facms-page-tabs"), o = /* @__PURE__ */ new Map();
1133
+ let s = t[0], l = {};
1134
+ const c = () => {
1135
+ n.innerHTML = t.map(
1136
+ (f) => `<button data-page="${f}" class="w-full text-left px-3 py-2 rounded-lg text-sm border ${f === s ? "bg-indigo-600 border-indigo-500 text-white" : "bg-slate-100 border-slate-200 dark:bg-slate-800 dark:border-slate-700 text-slate-700 dark:text-slate-200 hover:bg-slate-200 dark:hover:bg-slate-700"}">${x(f)}</button>`
1137
+ ).join(""), n.querySelectorAll("[data-page]").forEach((f) => {
1138
+ f.addEventListener("click", () => {
1139
+ s = f.dataset.page || "/", c(), a.src = s;
1140
+ });
1141
+ });
1142
+ };
1143
+ se(d), a.addEventListener("load", () => {
1144
+ const f = a.contentDocument;
1145
+ if (!f) return;
1146
+ let u = o.get(s);
1147
+ if (u) {
1148
+ u.workingDocument = f;
1149
+ const i = N(f, s);
1150
+ i.nodes.length && !u.content.nodes.length && (u.content = i);
1151
+ } else {
1152
+ const i = N(f, s);
1153
+ u = Ue(i, f, l), o.set(s, u);
1154
+ }
1155
+ ue(u), Q(u.content), L(r, u);
1156
+ }), c(), ge().then((f) => {
1157
+ l = f, a.src = s;
1158
+ });
1159
+ }
1160
+ async function X() {
1161
+ if (re(), le(), !await ce())
1162
+ return;
1163
+ const e = await ie(), t = () => Ke(e);
1164
+ if (!P) {
1165
+ de(t);
1166
+ return;
1167
+ }
1168
+ t();
1169
+ }
1170
+ async function Je() {
1171
+ const e = await ie();
1172
+ if (location.pathname !== e.dashboardPath) {
1173
+ Ge();
1174
+ return;
1175
+ }
1176
+ if (re(), le(), !!await ce()) {
1177
+ if (!P) {
1178
+ de(() => {
1179
+ X();
1180
+ });
1181
+ return;
1182
+ }
1183
+ X();
1184
+ }
1185
+ }
1186
+ export {
1187
+ X as launchDashboard,
1188
+ Je as registerCmsLauncher
1189
+ };