ngx-xtroedge-cms 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.global.js +76 -3
- package/dist/index.js +284 -59
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +284 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -237,6 +237,58 @@ var CMS_STYLES = `
|
|
|
237
237
|
}
|
|
238
238
|
.lcms-spinner-lg { width: 36px; height: 36px; border: 3px solid rgba(255,255,255,0.2); border-top-color: var(--lcms-primary, #00C853); border-radius: 50%; animation: lcmsSpin 0.6s linear infinite; }
|
|
239
239
|
|
|
240
|
+
/* LOGIN MODAL */
|
|
241
|
+
.lcms-login-overlay {
|
|
242
|
+
position: fixed; inset: 0; z-index: 10010;
|
|
243
|
+
display: flex; align-items: center; justify-content: center;
|
|
244
|
+
background: rgba(8, 8, 15, 0.75);
|
|
245
|
+
backdrop-filter: blur(16px) saturate(1.4);
|
|
246
|
+
-webkit-backdrop-filter: blur(16px) saturate(1.4);
|
|
247
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
248
|
+
}
|
|
249
|
+
.lcms-login-box {
|
|
250
|
+
background: #13151a; border: 1px solid rgba(255,255,255,0.1);
|
|
251
|
+
border-radius: 16px; padding: 36px 32px; width: 340px;
|
|
252
|
+
box-shadow: 0 24px 60px rgba(0,0,0,0.6);
|
|
253
|
+
}
|
|
254
|
+
.lcms-login-logo {
|
|
255
|
+
display: flex; align-items: center; justify-content: center;
|
|
256
|
+
gap: 8px; margin-bottom: 24px;
|
|
257
|
+
}
|
|
258
|
+
.lcms-login-logo-icon {
|
|
259
|
+
width: 32px; height: 32px; border-radius: 8px;
|
|
260
|
+
background: linear-gradient(135deg, var(--lcms-primary, #00C853), var(--lcms-primary-dark, #2E7D32));
|
|
261
|
+
display: flex; align-items: center; justify-content: center;
|
|
262
|
+
}
|
|
263
|
+
.lcms-login-logo-text { color: white; font-size: 15px; font-weight: 700; letter-spacing: 0.5px; }
|
|
264
|
+
.lcms-login-title { color: white; font-size: 18px; font-weight: 700; text-align: center; margin-bottom: 6px; }
|
|
265
|
+
.lcms-login-sub { color: rgba(255,255,255,0.4); font-size: 12px; text-align: center; margin-bottom: 24px; }
|
|
266
|
+
.lcms-login-field { margin-bottom: 14px; }
|
|
267
|
+
.lcms-login-label { display: block; color: rgba(255,255,255,0.6); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.8px; margin-bottom: 6px; }
|
|
268
|
+
.lcms-login-input {
|
|
269
|
+
width: 100%; box-sizing: border-box;
|
|
270
|
+
background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12);
|
|
271
|
+
border-radius: 8px; padding: 10px 12px;
|
|
272
|
+
color: white; font-size: 13px; font-family: inherit; outline: none;
|
|
273
|
+
transition: border-color 0.2s;
|
|
274
|
+
}
|
|
275
|
+
.lcms-login-input:focus { border-color: var(--lcms-primary, #00C853); }
|
|
276
|
+
.lcms-login-input::placeholder { color: rgba(255,255,255,0.25); }
|
|
277
|
+
.lcms-login-btn {
|
|
278
|
+
width: 100%; padding: 11px; margin-top: 6px; border: none; border-radius: 8px; cursor: pointer;
|
|
279
|
+
background: linear-gradient(135deg, var(--lcms-primary, #00C853), var(--lcms-primary-dark, #2E7D32));
|
|
280
|
+
color: white; font-size: 13px; font-weight: 700; font-family: inherit;
|
|
281
|
+
letter-spacing: 0.3px; transition: filter 0.2s;
|
|
282
|
+
}
|
|
283
|
+
.lcms-login-btn:hover { filter: brightness(1.1); }
|
|
284
|
+
.lcms-login-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
285
|
+
.lcms-login-error {
|
|
286
|
+
background: rgba(239,68,68,0.15); border: 1px solid rgba(239,68,68,0.3);
|
|
287
|
+
border-radius: 8px; padding: 9px 12px; color: #f87171;
|
|
288
|
+
font-size: 12px; text-align: center; margin-top: 12px; display: none;
|
|
289
|
+
}
|
|
290
|
+
.lcms-login-error.visible { display: block; }
|
|
291
|
+
|
|
240
292
|
/* HIDDEN ELEMENTS */
|
|
241
293
|
.lcms-hidden { display: none !important; }
|
|
242
294
|
`;
|
|
@@ -286,6 +338,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
286
338
|
this.brandingEl = null;
|
|
287
339
|
this.siteIdentifier = "";
|
|
288
340
|
this.siteIdEl = null;
|
|
341
|
+
this.loginModalEl = null;
|
|
342
|
+
this.pendingEditMode = false;
|
|
343
|
+
// edit=true was requested but token missing
|
|
289
344
|
// ===== State =====
|
|
290
345
|
this.editMode = false;
|
|
291
346
|
this.currentLang = "en";
|
|
@@ -475,12 +530,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
475
530
|
this.resetAll();
|
|
476
531
|
}
|
|
477
532
|
const editFromSession = sessionStorage.getItem("builder_edit_mode") === "true";
|
|
478
|
-
|
|
533
|
+
const wantsEdit = editViaParam || editFromSession;
|
|
534
|
+
if (wantsEdit) {
|
|
535
|
+
const token = localStorage.getItem("builder_token");
|
|
536
|
+
if (!token) {
|
|
537
|
+
this.pendingEditMode = true;
|
|
538
|
+
this.currentLang = this.detectCurrentLanguage();
|
|
539
|
+
this.setLoading(true);
|
|
540
|
+
setTimeout(() => {
|
|
541
|
+
this.loadTranslationsAndInit(false);
|
|
542
|
+
this.showLoginModal();
|
|
543
|
+
}, 300);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
479
546
|
this.editMode = true;
|
|
480
547
|
}
|
|
481
548
|
this.currentLang = this.detectCurrentLanguage();
|
|
482
549
|
this.setLoading(true);
|
|
483
|
-
const loadDraft =
|
|
550
|
+
const loadDraft = wantsEdit;
|
|
484
551
|
setTimeout(() => {
|
|
485
552
|
this.loadTranslationsAndInit(loadDraft);
|
|
486
553
|
}, 300);
|
|
@@ -753,7 +820,7 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
753
820
|
if (this.containerSelector) {
|
|
754
821
|
return document.querySelector(this.containerSelector) || document.body;
|
|
755
822
|
}
|
|
756
|
-
return document.
|
|
823
|
+
return document.body;
|
|
757
824
|
}
|
|
758
825
|
autoDetectElements() {
|
|
759
826
|
const container = this.resolveContainer();
|
|
@@ -761,7 +828,29 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
761
828
|
for (const el of this.autoDetectedElements) {
|
|
762
829
|
if (!document.contains(el)) this.autoDetectedElements.delete(el);
|
|
763
830
|
}
|
|
831
|
+
const getSectionSlug = (el) => {
|
|
832
|
+
let parent = el.parentElement;
|
|
833
|
+
while (parent && parent !== document.body) {
|
|
834
|
+
const tag = parent.tagName.toLowerCase();
|
|
835
|
+
if (tag === "header" || tag.endsWith("-header")) return "/header";
|
|
836
|
+
if (tag === "footer" || tag.endsWith("-footer")) return "/footer";
|
|
837
|
+
parent = parent.parentElement;
|
|
838
|
+
}
|
|
839
|
+
return this.currentSlug;
|
|
840
|
+
};
|
|
764
841
|
const tagCounters = {};
|
|
842
|
+
const assignKey = (el, sectionSlug) => {
|
|
843
|
+
const sectionKey = sectionSlug === "/header" ? "header" : sectionSlug === "/footer" ? "footer" : "page";
|
|
844
|
+
if (!tagCounters[sectionKey]) tagCounters[sectionKey] = {};
|
|
845
|
+
const tag = el.tagName.toLowerCase();
|
|
846
|
+
if (!tagCounters[sectionKey][tag]) tagCounters[sectionKey][tag] = 0;
|
|
847
|
+
const prefix = sectionKey !== "page" ? `${sectionKey}_` : "";
|
|
848
|
+
const key = `${prefix}xcms_${tag}_${tagCounters[sectionKey][tag]}`;
|
|
849
|
+
tagCounters[sectionKey][tag]++;
|
|
850
|
+
el.setAttribute("data-cms", key);
|
|
851
|
+
el.setAttribute("data-cms-section", sectionSlug);
|
|
852
|
+
this.autoDetectedElements.add(el);
|
|
853
|
+
};
|
|
765
854
|
const selector = this.editableTags.join(",");
|
|
766
855
|
const elements = container.querySelectorAll(selector);
|
|
767
856
|
elements.forEach((el) => {
|
|
@@ -770,12 +859,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
770
859
|
if (el.children.length > 3) return;
|
|
771
860
|
const text = this.getDirectTextContent(el).trim();
|
|
772
861
|
if (text.length < 2) return;
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
el.
|
|
778
|
-
|
|
862
|
+
assignKey(el, getSectionSlug(el));
|
|
863
|
+
});
|
|
864
|
+
const dataEditables = container.querySelectorAll("[data-editable]");
|
|
865
|
+
dataEditables.forEach((el) => {
|
|
866
|
+
if (el.hasAttribute("data-cms")) return;
|
|
867
|
+
if (el.closest("#xtroedge-cms-root, script, style, noscript")) return;
|
|
868
|
+
assignKey(el, getSectionSlug(el));
|
|
779
869
|
});
|
|
780
870
|
}
|
|
781
871
|
scanDOM() {
|
|
@@ -846,8 +936,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
846
936
|
const currentVal = this.getPageText(key);
|
|
847
937
|
if (text !== currentVal) this.onTextChanged(key, text);
|
|
848
938
|
};
|
|
849
|
-
|
|
850
|
-
|
|
939
|
+
const clickHandler = (e) => {
|
|
940
|
+
e.preventDefault();
|
|
941
|
+
e.stopPropagation();
|
|
942
|
+
};
|
|
943
|
+
const sectionSlug = el.getAttribute("data-cms-section") || this.currentSlug;
|
|
944
|
+
this.managedElements.set(el, { key, sectionSlug, blurHandler, keydownHandler, inputHandler, clickHandler });
|
|
945
|
+
if (this.editMode) this.enableElementEdit(el, key, blurHandler, keydownHandler, inputHandler, clickHandler);
|
|
851
946
|
}
|
|
852
947
|
detachElement(el) {
|
|
853
948
|
const info = this.managedElements.get(el);
|
|
@@ -855,11 +950,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
855
950
|
el.removeEventListener("blur", info.blurHandler);
|
|
856
951
|
el.removeEventListener("keydown", info.keydownHandler);
|
|
857
952
|
el.removeEventListener("input", info.inputHandler);
|
|
953
|
+
el.removeEventListener("click", info.clickHandler, true);
|
|
858
954
|
el.removeAttribute("contenteditable");
|
|
859
955
|
this.managedElements.delete(el);
|
|
860
956
|
}
|
|
861
957
|
}
|
|
862
|
-
enableElementEdit(el, _key, blurH, keyH, inputH) {
|
|
958
|
+
enableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
|
|
863
959
|
const val = this.getPageText(_key);
|
|
864
960
|
if (val) el.textContent = val;
|
|
865
961
|
el.setAttribute("contenteditable", "true");
|
|
@@ -873,8 +969,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
873
969
|
el.addEventListener("blur", blurH);
|
|
874
970
|
el.addEventListener("keydown", keyH);
|
|
875
971
|
el.addEventListener("input", inputH);
|
|
972
|
+
el.addEventListener("click", clickH, true);
|
|
876
973
|
}
|
|
877
|
-
disableElementEdit(el, _key, blurH, keyH, inputH) {
|
|
974
|
+
disableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
|
|
878
975
|
el.removeAttribute("contenteditable");
|
|
879
976
|
el.style.borderBottom = "";
|
|
880
977
|
el.style.padding = "";
|
|
@@ -888,11 +985,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
888
985
|
el.removeEventListener("blur", blurH);
|
|
889
986
|
el.removeEventListener("keydown", keyH);
|
|
890
987
|
el.removeEventListener("input", inputH);
|
|
988
|
+
el.removeEventListener("click", clickH, true);
|
|
891
989
|
}
|
|
892
990
|
applyEditMode(editMode) {
|
|
893
991
|
for (const [el, info] of this.managedElements) {
|
|
894
|
-
if (editMode) this.enableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler);
|
|
895
|
-
else this.disableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler);
|
|
992
|
+
if (editMode) this.enableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler, info.clickHandler);
|
|
993
|
+
else this.disableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler, info.clickHandler);
|
|
896
994
|
}
|
|
897
995
|
this.applyImageEditMode(editMode);
|
|
898
996
|
}
|
|
@@ -1214,23 +1312,43 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1214
1312
|
getPageSection() {
|
|
1215
1313
|
return this.currentSlug.replace(/^\//, "").replace(/-/g, "_").toUpperCase();
|
|
1216
1314
|
}
|
|
1315
|
+
// Fetch texts for a single slug and merge into pageTexts
|
|
1316
|
+
async fetchSectionTexts(slug, status) {
|
|
1317
|
+
try {
|
|
1318
|
+
const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(slug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
|
|
1319
|
+
if (!res.ok) return null;
|
|
1320
|
+
const page = await res.json();
|
|
1321
|
+
return page?.published_content?.texts || page?.content?.texts || null;
|
|
1322
|
+
} catch {
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1217
1326
|
async loadPageContent(status) {
|
|
1218
1327
|
this.setLoading(true);
|
|
1219
1328
|
this.cleanOldHistory();
|
|
1220
1329
|
try {
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1330
|
+
const [pageTexts, headerTexts, footerTexts] = await Promise.all([
|
|
1331
|
+
this.fetchSectionTexts(this.currentSlug, status),
|
|
1332
|
+
this.fetchSectionTexts("/header", status),
|
|
1333
|
+
this.fetchSectionTexts("/footer", status)
|
|
1334
|
+
]);
|
|
1335
|
+
if (pageTexts || headerTexts || footerTexts) {
|
|
1336
|
+
this.pageTexts = {
|
|
1337
|
+
...pageTexts || {},
|
|
1338
|
+
...headerTexts || {},
|
|
1339
|
+
...footerTexts || {}
|
|
1340
|
+
};
|
|
1227
1341
|
} else {
|
|
1228
1342
|
this.buildDefaultTexts();
|
|
1229
1343
|
}
|
|
1230
|
-
const
|
|
1231
|
-
if (
|
|
1232
|
-
|
|
1233
|
-
|
|
1344
|
+
const resImg = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`).catch(() => null);
|
|
1345
|
+
if (resImg?.ok) {
|
|
1346
|
+
const page = await resImg.json();
|
|
1347
|
+
const images = page?.published_content?.images || page?.content?.images;
|
|
1348
|
+
if (images) {
|
|
1349
|
+
this.pageImages = JSON.parse(JSON.stringify(images));
|
|
1350
|
+
this.applyPageImages();
|
|
1351
|
+
}
|
|
1234
1352
|
}
|
|
1235
1353
|
this.originalTexts = JSON.parse(JSON.stringify(this.pageTexts));
|
|
1236
1354
|
this.originalImages = JSON.parse(JSON.stringify(this.pageImages));
|
|
@@ -1248,18 +1366,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1248
1366
|
async loadPublishedContent() {
|
|
1249
1367
|
this.setLoading(true);
|
|
1250
1368
|
try {
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1369
|
+
const [pageTexts, headerTexts, footerTexts] = await Promise.all([
|
|
1370
|
+
this.fetchSectionTexts(this.currentSlug, "published"),
|
|
1371
|
+
this.fetchSectionTexts("/header", "published"),
|
|
1372
|
+
this.fetchSectionTexts("/footer", "published")
|
|
1373
|
+
]);
|
|
1374
|
+
const merged = { ...pageTexts || {}, ...headerTexts || {}, ...footerTexts || {} };
|
|
1375
|
+
if (Object.keys(merged).length > 0) {
|
|
1376
|
+
this.pageTexts = JSON.parse(JSON.stringify(merged));
|
|
1257
1377
|
this.updateElementTexts();
|
|
1258
1378
|
}
|
|
1259
|
-
const
|
|
1260
|
-
if (
|
|
1261
|
-
|
|
1262
|
-
|
|
1379
|
+
const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=published&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
|
|
1380
|
+
if (res.ok) {
|
|
1381
|
+
const page = await res.json();
|
|
1382
|
+
const images = page?.published_content?.images;
|
|
1383
|
+
if (images && Object.keys(images).length > 0) {
|
|
1384
|
+
this.pageImages = JSON.parse(JSON.stringify(images));
|
|
1385
|
+
this.applyPageImages();
|
|
1386
|
+
}
|
|
1263
1387
|
}
|
|
1264
1388
|
} catch {
|
|
1265
1389
|
} finally {
|
|
@@ -1297,6 +1421,18 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1297
1421
|
}
|
|
1298
1422
|
}
|
|
1299
1423
|
}
|
|
1424
|
+
// Group pageTexts by section slug based on managedElements info
|
|
1425
|
+
groupTextsBySection() {
|
|
1426
|
+
const sections = {};
|
|
1427
|
+
for (const [, info] of this.managedElements) {
|
|
1428
|
+
const slug = info.sectionSlug;
|
|
1429
|
+
if (!sections[slug]) sections[slug] = {};
|
|
1430
|
+
if (this.pageTexts[info.key] !== void 0) {
|
|
1431
|
+
sections[slug][info.key] = this.pageTexts[info.key];
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return sections;
|
|
1435
|
+
}
|
|
1300
1436
|
async saveChanges() {
|
|
1301
1437
|
if (!this.config.apiBase) {
|
|
1302
1438
|
this.showToast("No API configured. Set apiBase to enable save.", "error");
|
|
@@ -1304,19 +1440,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1304
1440
|
}
|
|
1305
1441
|
this.isSaving = true;
|
|
1306
1442
|
this.updateUI();
|
|
1307
|
-
const payload = {
|
|
1308
|
-
slug: this.currentSlug,
|
|
1309
|
-
title: this.currentTitle,
|
|
1310
|
-
site_identifier: this.siteIdentifier,
|
|
1311
|
-
content: { texts: this.pageTexts, images: this.pageImages }
|
|
1312
|
-
};
|
|
1313
1443
|
try {
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1444
|
+
const sections = this.groupTextsBySection();
|
|
1445
|
+
const requests = Object.entries(sections).map(
|
|
1446
|
+
([slug, texts]) => fetch(`${this.config.apiBase}/web_page/save`, {
|
|
1447
|
+
method: "POST",
|
|
1448
|
+
headers: { "Content-Type": "application/json" },
|
|
1449
|
+
body: JSON.stringify({
|
|
1450
|
+
slug,
|
|
1451
|
+
title: slug === this.currentSlug ? this.currentTitle : slug.replace("/", ""),
|
|
1452
|
+
site_identifier: this.siteIdentifier,
|
|
1453
|
+
content: { texts, ...slug === this.currentSlug ? { images: this.pageImages } : {} }
|
|
1454
|
+
})
|
|
1455
|
+
})
|
|
1456
|
+
);
|
|
1457
|
+
const results = await Promise.all(requests);
|
|
1458
|
+
if (results.some((r) => !r.ok)) throw new Error("One or more sections failed to save");
|
|
1320
1459
|
this.isSaving = false;
|
|
1321
1460
|
this.resetAfterSave();
|
|
1322
1461
|
this.updateUI();
|
|
@@ -1336,19 +1475,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1336
1475
|
}
|
|
1337
1476
|
this.isPublishing = true;
|
|
1338
1477
|
this.updateUI();
|
|
1339
|
-
const payload = {
|
|
1340
|
-
slug: this.currentSlug,
|
|
1341
|
-
title: this.currentTitle,
|
|
1342
|
-
site_identifier: this.siteIdentifier,
|
|
1343
|
-
published_content: { texts: this.pageTexts, images: this.pageImages }
|
|
1344
|
-
};
|
|
1345
1478
|
try {
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1479
|
+
const sections = this.groupTextsBySection();
|
|
1480
|
+
const requests = Object.entries(sections).map(
|
|
1481
|
+
([slug, texts]) => fetch(`${this.config.apiBase}/web_page/publish`, {
|
|
1482
|
+
method: "POST",
|
|
1483
|
+
headers: { "Content-Type": "application/json" },
|
|
1484
|
+
body: JSON.stringify({
|
|
1485
|
+
slug,
|
|
1486
|
+
title: slug === this.currentSlug ? this.currentTitle : slug.replace("/", ""),
|
|
1487
|
+
site_identifier: this.siteIdentifier,
|
|
1488
|
+
published_content: { texts, ...slug === this.currentSlug ? { images: this.pageImages } : {} }
|
|
1489
|
+
})
|
|
1490
|
+
})
|
|
1491
|
+
);
|
|
1492
|
+
const results = await Promise.all(requests);
|
|
1493
|
+
if (results.some((r) => !r.ok)) throw new Error("One or more sections failed to publish");
|
|
1352
1494
|
this.isPublishing = false;
|
|
1353
1495
|
this.resetAfterSave();
|
|
1354
1496
|
this.updateUI();
|
|
@@ -1616,6 +1758,89 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1616
1758
|
return `${months[d.getMonth()]} ${d.getDate()}, ${h12}:${m} ${ampm}`;
|
|
1617
1759
|
}
|
|
1618
1760
|
// ===============================================
|
|
1761
|
+
// LOGIN MODAL
|
|
1762
|
+
// ===============================================
|
|
1763
|
+
showLoginModal() {
|
|
1764
|
+
if (this.loginModalEl) return;
|
|
1765
|
+
const overlay = document.createElement("div");
|
|
1766
|
+
overlay.className = "lcms-login-overlay";
|
|
1767
|
+
overlay.id = "lcms-login-modal";
|
|
1768
|
+
overlay.innerHTML = `
|
|
1769
|
+
<div class="lcms-login-box">
|
|
1770
|
+
<div class="lcms-login-logo">
|
|
1771
|
+
<div class="lcms-login-logo-icon">
|
|
1772
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
|
1773
|
+
</div>
|
|
1774
|
+
<span class="lcms-login-logo-text">XtroEdge CMS</span>
|
|
1775
|
+
</div>
|
|
1776
|
+
<div class="lcms-login-title">Builder Login</div>
|
|
1777
|
+
<div class="lcms-login-sub">Sign in to access edit mode</div>
|
|
1778
|
+
<div class="lcms-login-field">
|
|
1779
|
+
<label class="lcms-login-label">Email or Username</label>
|
|
1780
|
+
<input class="lcms-login-input" id="lcms-login-email" type="text" placeholder="Enter your email or username" autocomplete="username" />
|
|
1781
|
+
</div>
|
|
1782
|
+
<div class="lcms-login-field">
|
|
1783
|
+
<label class="lcms-login-label">Password</label>
|
|
1784
|
+
<input class="lcms-login-input" id="lcms-login-password" type="password" placeholder="Enter your password" autocomplete="current-password" />
|
|
1785
|
+
</div>
|
|
1786
|
+
<button class="lcms-login-btn" id="lcms-login-btn">Sign In</button>
|
|
1787
|
+
<div class="lcms-login-error" id="lcms-login-error"></div>
|
|
1788
|
+
</div>
|
|
1789
|
+
`;
|
|
1790
|
+
document.body.appendChild(overlay);
|
|
1791
|
+
this.loginModalEl = overlay;
|
|
1792
|
+
const btn = overlay.querySelector("#lcms-login-btn");
|
|
1793
|
+
const emailInput = overlay.querySelector("#lcms-login-email");
|
|
1794
|
+
const passInput = overlay.querySelector("#lcms-login-password");
|
|
1795
|
+
const doLogin = () => this.attemptLogin(emailInput.value.trim(), passInput.value);
|
|
1796
|
+
btn.addEventListener("click", doLogin);
|
|
1797
|
+
passInput.addEventListener("keydown", (e) => {
|
|
1798
|
+
if (e.key === "Enter") doLogin();
|
|
1799
|
+
});
|
|
1800
|
+
setTimeout(() => emailInput.focus(), 50);
|
|
1801
|
+
}
|
|
1802
|
+
hideLoginModal() {
|
|
1803
|
+
this.loginModalEl?.remove();
|
|
1804
|
+
this.loginModalEl = null;
|
|
1805
|
+
}
|
|
1806
|
+
async attemptLogin(email, password) {
|
|
1807
|
+
const btn = document.getElementById("lcms-login-btn");
|
|
1808
|
+
const errorEl = document.getElementById("lcms-login-error");
|
|
1809
|
+
if (!email || !password) {
|
|
1810
|
+
errorEl.textContent = "Please enter your email/username and password.";
|
|
1811
|
+
errorEl.classList.add("visible");
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
btn.disabled = true;
|
|
1815
|
+
btn.textContent = "Signing in...";
|
|
1816
|
+
errorEl.classList.remove("visible");
|
|
1817
|
+
try {
|
|
1818
|
+
const loginUrl = this.config.loginUrl || `${this.config.apiBase}/auth/login`;
|
|
1819
|
+
const res = await fetch(loginUrl, {
|
|
1820
|
+
method: "POST",
|
|
1821
|
+
headers: { "Content-Type": "application/json" },
|
|
1822
|
+
body: JSON.stringify({ email, password })
|
|
1823
|
+
});
|
|
1824
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1825
|
+
const data = await res.json();
|
|
1826
|
+
const token = data?.token || data?.authToken || data?.auth_token || data?.data?.token;
|
|
1827
|
+
if (!token) throw new Error("No token in response");
|
|
1828
|
+
localStorage.setItem("builder_token", token);
|
|
1829
|
+
this.hideLoginModal();
|
|
1830
|
+
this.editMode = true;
|
|
1831
|
+
this.pendingEditMode = false;
|
|
1832
|
+
sessionStorage.setItem("builder_edit_mode", "true");
|
|
1833
|
+
this.applyEditMode(true);
|
|
1834
|
+
this.updateUI();
|
|
1835
|
+
this.loadPageContent("draft");
|
|
1836
|
+
} catch {
|
|
1837
|
+
btn.disabled = false;
|
|
1838
|
+
btn.textContent = "Sign In";
|
|
1839
|
+
errorEl.textContent = "Invalid credentials. Please try again.";
|
|
1840
|
+
errorEl.classList.add("visible");
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
// ===============================================
|
|
1619
1844
|
// SITE IDENTIFIER
|
|
1620
1845
|
// ===============================================
|
|
1621
1846
|
resolveSiteIdentifier() {
|