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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/cli/apply.d.ts +7 -0
- package/dist/cli/apply.js +102 -0
- package/dist/cli/apply.js.map +1 -0
- package/dist/cli/fs-utils.d.ts +5 -0
- package/dist/cli/fs-utils.js +54 -0
- package/dist/cli/fs-utils.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +32 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/postinstall.d.ts +1 -0
- package/dist/cli/postinstall.js +10 -0
- package/dist/cli/postinstall.js.map +1 -0
- package/dist/cli/scanner.d.ts +2 -0
- package/dist/cli/scanner.js +97 -0
- package/dist/cli/scanner.js.map +1 -0
- package/dist/cli/setup.d.ts +6 -0
- package/dist/cli/setup.js +256 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/runtime/dashboard.js +1189 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/runtime/dashboard.d.ts +2 -0
- package/dist/runtime/runtime/dom-scan.d.ts +3 -0
- package/dist/runtime/runtime/i18n.d.ts +7 -0
- package/dist/runtime/runtime/index.d.ts +1 -0
- package/dist/runtime/runtime/publish.d.ts +11 -0
- package/dist/runtime/runtime/store.d.ts +7 -0
- package/dist/runtime/runtime/types.d.ts +8 -0
- package/dist/runtime/shared/constants.d.ts +10 -0
- package/dist/runtime/shared/hash.d.ts +4 -0
- package/dist/runtime/shared/types.d.ts +71 -0
- package/dist/shared/constants.d.ts +10 -0
- package/dist/shared/constants.js +11 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/hash.d.ts +4 -0
- package/dist/shared/hash.js +19 -0
- package/dist/shared/hash.js.map +1 -0
- package/dist/shared/types.d.ts +71 -0
- package/dist/shared/types.js +2 -0
- package/dist/shared/types.js.map +1 -0
- 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) => ({ "&": "&", "<": "<", ">": ">", "'": "'", '"': """ })[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
|
+
};
|