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.js
CHANGED
|
@@ -263,6 +263,58 @@ var CMS_STYLES = `
|
|
|
263
263
|
}
|
|
264
264
|
.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; }
|
|
265
265
|
|
|
266
|
+
/* LOGIN MODAL */
|
|
267
|
+
.lcms-login-overlay {
|
|
268
|
+
position: fixed; inset: 0; z-index: 10010;
|
|
269
|
+
display: flex; align-items: center; justify-content: center;
|
|
270
|
+
background: rgba(8, 8, 15, 0.75);
|
|
271
|
+
backdrop-filter: blur(16px) saturate(1.4);
|
|
272
|
+
-webkit-backdrop-filter: blur(16px) saturate(1.4);
|
|
273
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
274
|
+
}
|
|
275
|
+
.lcms-login-box {
|
|
276
|
+
background: #13151a; border: 1px solid rgba(255,255,255,0.1);
|
|
277
|
+
border-radius: 16px; padding: 36px 32px; width: 340px;
|
|
278
|
+
box-shadow: 0 24px 60px rgba(0,0,0,0.6);
|
|
279
|
+
}
|
|
280
|
+
.lcms-login-logo {
|
|
281
|
+
display: flex; align-items: center; justify-content: center;
|
|
282
|
+
gap: 8px; margin-bottom: 24px;
|
|
283
|
+
}
|
|
284
|
+
.lcms-login-logo-icon {
|
|
285
|
+
width: 32px; height: 32px; border-radius: 8px;
|
|
286
|
+
background: linear-gradient(135deg, var(--lcms-primary, #00C853), var(--lcms-primary-dark, #2E7D32));
|
|
287
|
+
display: flex; align-items: center; justify-content: center;
|
|
288
|
+
}
|
|
289
|
+
.lcms-login-logo-text { color: white; font-size: 15px; font-weight: 700; letter-spacing: 0.5px; }
|
|
290
|
+
.lcms-login-title { color: white; font-size: 18px; font-weight: 700; text-align: center; margin-bottom: 6px; }
|
|
291
|
+
.lcms-login-sub { color: rgba(255,255,255,0.4); font-size: 12px; text-align: center; margin-bottom: 24px; }
|
|
292
|
+
.lcms-login-field { margin-bottom: 14px; }
|
|
293
|
+
.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; }
|
|
294
|
+
.lcms-login-input {
|
|
295
|
+
width: 100%; box-sizing: border-box;
|
|
296
|
+
background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12);
|
|
297
|
+
border-radius: 8px; padding: 10px 12px;
|
|
298
|
+
color: white; font-size: 13px; font-family: inherit; outline: none;
|
|
299
|
+
transition: border-color 0.2s;
|
|
300
|
+
}
|
|
301
|
+
.lcms-login-input:focus { border-color: var(--lcms-primary, #00C853); }
|
|
302
|
+
.lcms-login-input::placeholder { color: rgba(255,255,255,0.25); }
|
|
303
|
+
.lcms-login-btn {
|
|
304
|
+
width: 100%; padding: 11px; margin-top: 6px; border: none; border-radius: 8px; cursor: pointer;
|
|
305
|
+
background: linear-gradient(135deg, var(--lcms-primary, #00C853), var(--lcms-primary-dark, #2E7D32));
|
|
306
|
+
color: white; font-size: 13px; font-weight: 700; font-family: inherit;
|
|
307
|
+
letter-spacing: 0.3px; transition: filter 0.2s;
|
|
308
|
+
}
|
|
309
|
+
.lcms-login-btn:hover { filter: brightness(1.1); }
|
|
310
|
+
.lcms-login-btn:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
311
|
+
.lcms-login-error {
|
|
312
|
+
background: rgba(239,68,68,0.15); border: 1px solid rgba(239,68,68,0.3);
|
|
313
|
+
border-radius: 8px; padding: 9px 12px; color: #f87171;
|
|
314
|
+
font-size: 12px; text-align: center; margin-top: 12px; display: none;
|
|
315
|
+
}
|
|
316
|
+
.lcms-login-error.visible { display: block; }
|
|
317
|
+
|
|
266
318
|
/* HIDDEN ELEMENTS */
|
|
267
319
|
.lcms-hidden { display: none !important; }
|
|
268
320
|
`;
|
|
@@ -312,6 +364,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
312
364
|
this.brandingEl = null;
|
|
313
365
|
this.siteIdentifier = "";
|
|
314
366
|
this.siteIdEl = null;
|
|
367
|
+
this.loginModalEl = null;
|
|
368
|
+
this.pendingEditMode = false;
|
|
369
|
+
// edit=true was requested but token missing
|
|
315
370
|
// ===== State =====
|
|
316
371
|
this.editMode = false;
|
|
317
372
|
this.currentLang = "en";
|
|
@@ -501,12 +556,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
501
556
|
this.resetAll();
|
|
502
557
|
}
|
|
503
558
|
const editFromSession = sessionStorage.getItem("builder_edit_mode") === "true";
|
|
504
|
-
|
|
559
|
+
const wantsEdit = editViaParam || editFromSession;
|
|
560
|
+
if (wantsEdit) {
|
|
561
|
+
const token = localStorage.getItem("builder_token");
|
|
562
|
+
if (!token) {
|
|
563
|
+
this.pendingEditMode = true;
|
|
564
|
+
this.currentLang = this.detectCurrentLanguage();
|
|
565
|
+
this.setLoading(true);
|
|
566
|
+
setTimeout(() => {
|
|
567
|
+
this.loadTranslationsAndInit(false);
|
|
568
|
+
this.showLoginModal();
|
|
569
|
+
}, 300);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
505
572
|
this.editMode = true;
|
|
506
573
|
}
|
|
507
574
|
this.currentLang = this.detectCurrentLanguage();
|
|
508
575
|
this.setLoading(true);
|
|
509
|
-
const loadDraft =
|
|
576
|
+
const loadDraft = wantsEdit;
|
|
510
577
|
setTimeout(() => {
|
|
511
578
|
this.loadTranslationsAndInit(loadDraft);
|
|
512
579
|
}, 300);
|
|
@@ -779,7 +846,7 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
779
846
|
if (this.containerSelector) {
|
|
780
847
|
return document.querySelector(this.containerSelector) || document.body;
|
|
781
848
|
}
|
|
782
|
-
return document.
|
|
849
|
+
return document.body;
|
|
783
850
|
}
|
|
784
851
|
autoDetectElements() {
|
|
785
852
|
const container = this.resolveContainer();
|
|
@@ -787,7 +854,29 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
787
854
|
for (const el of this.autoDetectedElements) {
|
|
788
855
|
if (!document.contains(el)) this.autoDetectedElements.delete(el);
|
|
789
856
|
}
|
|
857
|
+
const getSectionSlug = (el) => {
|
|
858
|
+
let parent = el.parentElement;
|
|
859
|
+
while (parent && parent !== document.body) {
|
|
860
|
+
const tag = parent.tagName.toLowerCase();
|
|
861
|
+
if (tag === "header" || tag.endsWith("-header")) return "/header";
|
|
862
|
+
if (tag === "footer" || tag.endsWith("-footer")) return "/footer";
|
|
863
|
+
parent = parent.parentElement;
|
|
864
|
+
}
|
|
865
|
+
return this.currentSlug;
|
|
866
|
+
};
|
|
790
867
|
const tagCounters = {};
|
|
868
|
+
const assignKey = (el, sectionSlug) => {
|
|
869
|
+
const sectionKey = sectionSlug === "/header" ? "header" : sectionSlug === "/footer" ? "footer" : "page";
|
|
870
|
+
if (!tagCounters[sectionKey]) tagCounters[sectionKey] = {};
|
|
871
|
+
const tag = el.tagName.toLowerCase();
|
|
872
|
+
if (!tagCounters[sectionKey][tag]) tagCounters[sectionKey][tag] = 0;
|
|
873
|
+
const prefix = sectionKey !== "page" ? `${sectionKey}_` : "";
|
|
874
|
+
const key = `${prefix}xcms_${tag}_${tagCounters[sectionKey][tag]}`;
|
|
875
|
+
tagCounters[sectionKey][tag]++;
|
|
876
|
+
el.setAttribute("data-cms", key);
|
|
877
|
+
el.setAttribute("data-cms-section", sectionSlug);
|
|
878
|
+
this.autoDetectedElements.add(el);
|
|
879
|
+
};
|
|
791
880
|
const selector = this.editableTags.join(",");
|
|
792
881
|
const elements = container.querySelectorAll(selector);
|
|
793
882
|
elements.forEach((el) => {
|
|
@@ -796,12 +885,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
796
885
|
if (el.children.length > 3) return;
|
|
797
886
|
const text = this.getDirectTextContent(el).trim();
|
|
798
887
|
if (text.length < 2) return;
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
el.
|
|
804
|
-
|
|
888
|
+
assignKey(el, getSectionSlug(el));
|
|
889
|
+
});
|
|
890
|
+
const dataEditables = container.querySelectorAll("[data-editable]");
|
|
891
|
+
dataEditables.forEach((el) => {
|
|
892
|
+
if (el.hasAttribute("data-cms")) return;
|
|
893
|
+
if (el.closest("#xtroedge-cms-root, script, style, noscript")) return;
|
|
894
|
+
assignKey(el, getSectionSlug(el));
|
|
805
895
|
});
|
|
806
896
|
}
|
|
807
897
|
scanDOM() {
|
|
@@ -872,8 +962,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
872
962
|
const currentVal = this.getPageText(key);
|
|
873
963
|
if (text !== currentVal) this.onTextChanged(key, text);
|
|
874
964
|
};
|
|
875
|
-
|
|
876
|
-
|
|
965
|
+
const clickHandler = (e) => {
|
|
966
|
+
e.preventDefault();
|
|
967
|
+
e.stopPropagation();
|
|
968
|
+
};
|
|
969
|
+
const sectionSlug = el.getAttribute("data-cms-section") || this.currentSlug;
|
|
970
|
+
this.managedElements.set(el, { key, sectionSlug, blurHandler, keydownHandler, inputHandler, clickHandler });
|
|
971
|
+
if (this.editMode) this.enableElementEdit(el, key, blurHandler, keydownHandler, inputHandler, clickHandler);
|
|
877
972
|
}
|
|
878
973
|
detachElement(el) {
|
|
879
974
|
const info = this.managedElements.get(el);
|
|
@@ -881,11 +976,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
881
976
|
el.removeEventListener("blur", info.blurHandler);
|
|
882
977
|
el.removeEventListener("keydown", info.keydownHandler);
|
|
883
978
|
el.removeEventListener("input", info.inputHandler);
|
|
979
|
+
el.removeEventListener("click", info.clickHandler, true);
|
|
884
980
|
el.removeAttribute("contenteditable");
|
|
885
981
|
this.managedElements.delete(el);
|
|
886
982
|
}
|
|
887
983
|
}
|
|
888
|
-
enableElementEdit(el, _key, blurH, keyH, inputH) {
|
|
984
|
+
enableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
|
|
889
985
|
const val = this.getPageText(_key);
|
|
890
986
|
if (val) el.textContent = val;
|
|
891
987
|
el.setAttribute("contenteditable", "true");
|
|
@@ -899,8 +995,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
899
995
|
el.addEventListener("blur", blurH);
|
|
900
996
|
el.addEventListener("keydown", keyH);
|
|
901
997
|
el.addEventListener("input", inputH);
|
|
998
|
+
el.addEventListener("click", clickH, true);
|
|
902
999
|
}
|
|
903
|
-
disableElementEdit(el, _key, blurH, keyH, inputH) {
|
|
1000
|
+
disableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
|
|
904
1001
|
el.removeAttribute("contenteditable");
|
|
905
1002
|
el.style.borderBottom = "";
|
|
906
1003
|
el.style.padding = "";
|
|
@@ -914,11 +1011,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
914
1011
|
el.removeEventListener("blur", blurH);
|
|
915
1012
|
el.removeEventListener("keydown", keyH);
|
|
916
1013
|
el.removeEventListener("input", inputH);
|
|
1014
|
+
el.removeEventListener("click", clickH, true);
|
|
917
1015
|
}
|
|
918
1016
|
applyEditMode(editMode) {
|
|
919
1017
|
for (const [el, info] of this.managedElements) {
|
|
920
|
-
if (editMode) this.enableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler);
|
|
921
|
-
else this.disableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler);
|
|
1018
|
+
if (editMode) this.enableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler, info.clickHandler);
|
|
1019
|
+
else this.disableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler, info.clickHandler);
|
|
922
1020
|
}
|
|
923
1021
|
this.applyImageEditMode(editMode);
|
|
924
1022
|
}
|
|
@@ -1240,23 +1338,43 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1240
1338
|
getPageSection() {
|
|
1241
1339
|
return this.currentSlug.replace(/^\//, "").replace(/-/g, "_").toUpperCase();
|
|
1242
1340
|
}
|
|
1341
|
+
// Fetch texts for a single slug and merge into pageTexts
|
|
1342
|
+
async fetchSectionTexts(slug, status) {
|
|
1343
|
+
try {
|
|
1344
|
+
const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(slug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
|
|
1345
|
+
if (!res.ok) return null;
|
|
1346
|
+
const page = await res.json();
|
|
1347
|
+
return page?.published_content?.texts || page?.content?.texts || null;
|
|
1348
|
+
} catch {
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1243
1352
|
async loadPageContent(status) {
|
|
1244
1353
|
this.setLoading(true);
|
|
1245
1354
|
this.cleanOldHistory();
|
|
1246
1355
|
try {
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1356
|
+
const [pageTexts, headerTexts, footerTexts] = await Promise.all([
|
|
1357
|
+
this.fetchSectionTexts(this.currentSlug, status),
|
|
1358
|
+
this.fetchSectionTexts("/header", status),
|
|
1359
|
+
this.fetchSectionTexts("/footer", status)
|
|
1360
|
+
]);
|
|
1361
|
+
if (pageTexts || headerTexts || footerTexts) {
|
|
1362
|
+
this.pageTexts = {
|
|
1363
|
+
...pageTexts || {},
|
|
1364
|
+
...headerTexts || {},
|
|
1365
|
+
...footerTexts || {}
|
|
1366
|
+
};
|
|
1253
1367
|
} else {
|
|
1254
1368
|
this.buildDefaultTexts();
|
|
1255
1369
|
}
|
|
1256
|
-
const
|
|
1257
|
-
if (
|
|
1258
|
-
|
|
1259
|
-
|
|
1370
|
+
const resImg = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`).catch(() => null);
|
|
1371
|
+
if (resImg?.ok) {
|
|
1372
|
+
const page = await resImg.json();
|
|
1373
|
+
const images = page?.published_content?.images || page?.content?.images;
|
|
1374
|
+
if (images) {
|
|
1375
|
+
this.pageImages = JSON.parse(JSON.stringify(images));
|
|
1376
|
+
this.applyPageImages();
|
|
1377
|
+
}
|
|
1260
1378
|
}
|
|
1261
1379
|
this.originalTexts = JSON.parse(JSON.stringify(this.pageTexts));
|
|
1262
1380
|
this.originalImages = JSON.parse(JSON.stringify(this.pageImages));
|
|
@@ -1274,18 +1392,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1274
1392
|
async loadPublishedContent() {
|
|
1275
1393
|
this.setLoading(true);
|
|
1276
1394
|
try {
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1395
|
+
const [pageTexts, headerTexts, footerTexts] = await Promise.all([
|
|
1396
|
+
this.fetchSectionTexts(this.currentSlug, "published"),
|
|
1397
|
+
this.fetchSectionTexts("/header", "published"),
|
|
1398
|
+
this.fetchSectionTexts("/footer", "published")
|
|
1399
|
+
]);
|
|
1400
|
+
const merged = { ...pageTexts || {}, ...headerTexts || {}, ...footerTexts || {} };
|
|
1401
|
+
if (Object.keys(merged).length > 0) {
|
|
1402
|
+
this.pageTexts = JSON.parse(JSON.stringify(merged));
|
|
1283
1403
|
this.updateElementTexts();
|
|
1284
1404
|
}
|
|
1285
|
-
const
|
|
1286
|
-
if (
|
|
1287
|
-
|
|
1288
|
-
|
|
1405
|
+
const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=published&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
|
|
1406
|
+
if (res.ok) {
|
|
1407
|
+
const page = await res.json();
|
|
1408
|
+
const images = page?.published_content?.images;
|
|
1409
|
+
if (images && Object.keys(images).length > 0) {
|
|
1410
|
+
this.pageImages = JSON.parse(JSON.stringify(images));
|
|
1411
|
+
this.applyPageImages();
|
|
1412
|
+
}
|
|
1289
1413
|
}
|
|
1290
1414
|
} catch {
|
|
1291
1415
|
} finally {
|
|
@@ -1323,6 +1447,18 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1323
1447
|
}
|
|
1324
1448
|
}
|
|
1325
1449
|
}
|
|
1450
|
+
// Group pageTexts by section slug based on managedElements info
|
|
1451
|
+
groupTextsBySection() {
|
|
1452
|
+
const sections = {};
|
|
1453
|
+
for (const [, info] of this.managedElements) {
|
|
1454
|
+
const slug = info.sectionSlug;
|
|
1455
|
+
if (!sections[slug]) sections[slug] = {};
|
|
1456
|
+
if (this.pageTexts[info.key] !== void 0) {
|
|
1457
|
+
sections[slug][info.key] = this.pageTexts[info.key];
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
return sections;
|
|
1461
|
+
}
|
|
1326
1462
|
async saveChanges() {
|
|
1327
1463
|
if (!this.config.apiBase) {
|
|
1328
1464
|
this.showToast("No API configured. Set apiBase to enable save.", "error");
|
|
@@ -1330,19 +1466,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1330
1466
|
}
|
|
1331
1467
|
this.isSaving = true;
|
|
1332
1468
|
this.updateUI();
|
|
1333
|
-
const payload = {
|
|
1334
|
-
slug: this.currentSlug,
|
|
1335
|
-
title: this.currentTitle,
|
|
1336
|
-
site_identifier: this.siteIdentifier,
|
|
1337
|
-
content: { texts: this.pageTexts, images: this.pageImages }
|
|
1338
|
-
};
|
|
1339
1469
|
try {
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1470
|
+
const sections = this.groupTextsBySection();
|
|
1471
|
+
const requests = Object.entries(sections).map(
|
|
1472
|
+
([slug, texts]) => fetch(`${this.config.apiBase}/web_page/save`, {
|
|
1473
|
+
method: "POST",
|
|
1474
|
+
headers: { "Content-Type": "application/json" },
|
|
1475
|
+
body: JSON.stringify({
|
|
1476
|
+
slug,
|
|
1477
|
+
title: slug === this.currentSlug ? this.currentTitle : slug.replace("/", ""),
|
|
1478
|
+
site_identifier: this.siteIdentifier,
|
|
1479
|
+
content: { texts, ...slug === this.currentSlug ? { images: this.pageImages } : {} }
|
|
1480
|
+
})
|
|
1481
|
+
})
|
|
1482
|
+
);
|
|
1483
|
+
const results = await Promise.all(requests);
|
|
1484
|
+
if (results.some((r) => !r.ok)) throw new Error("One or more sections failed to save");
|
|
1346
1485
|
this.isSaving = false;
|
|
1347
1486
|
this.resetAfterSave();
|
|
1348
1487
|
this.updateUI();
|
|
@@ -1362,19 +1501,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1362
1501
|
}
|
|
1363
1502
|
this.isPublishing = true;
|
|
1364
1503
|
this.updateUI();
|
|
1365
|
-
const payload = {
|
|
1366
|
-
slug: this.currentSlug,
|
|
1367
|
-
title: this.currentTitle,
|
|
1368
|
-
site_identifier: this.siteIdentifier,
|
|
1369
|
-
published_content: { texts: this.pageTexts, images: this.pageImages }
|
|
1370
|
-
};
|
|
1371
1504
|
try {
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1505
|
+
const sections = this.groupTextsBySection();
|
|
1506
|
+
const requests = Object.entries(sections).map(
|
|
1507
|
+
([slug, texts]) => fetch(`${this.config.apiBase}/web_page/publish`, {
|
|
1508
|
+
method: "POST",
|
|
1509
|
+
headers: { "Content-Type": "application/json" },
|
|
1510
|
+
body: JSON.stringify({
|
|
1511
|
+
slug,
|
|
1512
|
+
title: slug === this.currentSlug ? this.currentTitle : slug.replace("/", ""),
|
|
1513
|
+
site_identifier: this.siteIdentifier,
|
|
1514
|
+
published_content: { texts, ...slug === this.currentSlug ? { images: this.pageImages } : {} }
|
|
1515
|
+
})
|
|
1516
|
+
})
|
|
1517
|
+
);
|
|
1518
|
+
const results = await Promise.all(requests);
|
|
1519
|
+
if (results.some((r) => !r.ok)) throw new Error("One or more sections failed to publish");
|
|
1378
1520
|
this.isPublishing = false;
|
|
1379
1521
|
this.resetAfterSave();
|
|
1380
1522
|
this.updateUI();
|
|
@@ -1642,6 +1784,89 @@ var XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1642
1784
|
return `${months[d.getMonth()]} ${d.getDate()}, ${h12}:${m} ${ampm}`;
|
|
1643
1785
|
}
|
|
1644
1786
|
// ===============================================
|
|
1787
|
+
// LOGIN MODAL
|
|
1788
|
+
// ===============================================
|
|
1789
|
+
showLoginModal() {
|
|
1790
|
+
if (this.loginModalEl) return;
|
|
1791
|
+
const overlay = document.createElement("div");
|
|
1792
|
+
overlay.className = "lcms-login-overlay";
|
|
1793
|
+
overlay.id = "lcms-login-modal";
|
|
1794
|
+
overlay.innerHTML = `
|
|
1795
|
+
<div class="lcms-login-box">
|
|
1796
|
+
<div class="lcms-login-logo">
|
|
1797
|
+
<div class="lcms-login-logo-icon">
|
|
1798
|
+
<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>
|
|
1799
|
+
</div>
|
|
1800
|
+
<span class="lcms-login-logo-text">XtroEdge CMS</span>
|
|
1801
|
+
</div>
|
|
1802
|
+
<div class="lcms-login-title">Builder Login</div>
|
|
1803
|
+
<div class="lcms-login-sub">Sign in to access edit mode</div>
|
|
1804
|
+
<div class="lcms-login-field">
|
|
1805
|
+
<label class="lcms-login-label">Email or Username</label>
|
|
1806
|
+
<input class="lcms-login-input" id="lcms-login-email" type="text" placeholder="Enter your email or username" autocomplete="username" />
|
|
1807
|
+
</div>
|
|
1808
|
+
<div class="lcms-login-field">
|
|
1809
|
+
<label class="lcms-login-label">Password</label>
|
|
1810
|
+
<input class="lcms-login-input" id="lcms-login-password" type="password" placeholder="Enter your password" autocomplete="current-password" />
|
|
1811
|
+
</div>
|
|
1812
|
+
<button class="lcms-login-btn" id="lcms-login-btn">Sign In</button>
|
|
1813
|
+
<div class="lcms-login-error" id="lcms-login-error"></div>
|
|
1814
|
+
</div>
|
|
1815
|
+
`;
|
|
1816
|
+
document.body.appendChild(overlay);
|
|
1817
|
+
this.loginModalEl = overlay;
|
|
1818
|
+
const btn = overlay.querySelector("#lcms-login-btn");
|
|
1819
|
+
const emailInput = overlay.querySelector("#lcms-login-email");
|
|
1820
|
+
const passInput = overlay.querySelector("#lcms-login-password");
|
|
1821
|
+
const doLogin = () => this.attemptLogin(emailInput.value.trim(), passInput.value);
|
|
1822
|
+
btn.addEventListener("click", doLogin);
|
|
1823
|
+
passInput.addEventListener("keydown", (e) => {
|
|
1824
|
+
if (e.key === "Enter") doLogin();
|
|
1825
|
+
});
|
|
1826
|
+
setTimeout(() => emailInput.focus(), 50);
|
|
1827
|
+
}
|
|
1828
|
+
hideLoginModal() {
|
|
1829
|
+
this.loginModalEl?.remove();
|
|
1830
|
+
this.loginModalEl = null;
|
|
1831
|
+
}
|
|
1832
|
+
async attemptLogin(email, password) {
|
|
1833
|
+
const btn = document.getElementById("lcms-login-btn");
|
|
1834
|
+
const errorEl = document.getElementById("lcms-login-error");
|
|
1835
|
+
if (!email || !password) {
|
|
1836
|
+
errorEl.textContent = "Please enter your email/username and password.";
|
|
1837
|
+
errorEl.classList.add("visible");
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
btn.disabled = true;
|
|
1841
|
+
btn.textContent = "Signing in...";
|
|
1842
|
+
errorEl.classList.remove("visible");
|
|
1843
|
+
try {
|
|
1844
|
+
const loginUrl = this.config.loginUrl || `${this.config.apiBase}/auth/login`;
|
|
1845
|
+
const res = await fetch(loginUrl, {
|
|
1846
|
+
method: "POST",
|
|
1847
|
+
headers: { "Content-Type": "application/json" },
|
|
1848
|
+
body: JSON.stringify({ email, password })
|
|
1849
|
+
});
|
|
1850
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1851
|
+
const data = await res.json();
|
|
1852
|
+
const token = data?.token || data?.authToken || data?.auth_token || data?.data?.token;
|
|
1853
|
+
if (!token) throw new Error("No token in response");
|
|
1854
|
+
localStorage.setItem("builder_token", token);
|
|
1855
|
+
this.hideLoginModal();
|
|
1856
|
+
this.editMode = true;
|
|
1857
|
+
this.pendingEditMode = false;
|
|
1858
|
+
sessionStorage.setItem("builder_edit_mode", "true");
|
|
1859
|
+
this.applyEditMode(true);
|
|
1860
|
+
this.updateUI();
|
|
1861
|
+
this.loadPageContent("draft");
|
|
1862
|
+
} catch {
|
|
1863
|
+
btn.disabled = false;
|
|
1864
|
+
btn.textContent = "Sign In";
|
|
1865
|
+
errorEl.textContent = "Invalid credentials. Please try again.";
|
|
1866
|
+
errorEl.classList.add("visible");
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
// ===============================================
|
|
1645
1870
|
// SITE IDENTIFIER
|
|
1646
1871
|
// ===============================================
|
|
1647
1872
|
resolveSiteIdentifier() {
|