ngx-xtroedge-cms 1.4.0 → 1.4.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 +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.global.js +1 -1
- package/dist/index.js +237 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +237 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -397,6 +397,12 @@ body.lcms-editing [data-cms] {
|
|
|
397
397
|
-webkit-user-select: text !important;
|
|
398
398
|
cursor: text !important;
|
|
399
399
|
}
|
|
400
|
+
|
|
401
|
+
/* Ensure editable elements appear above other content */
|
|
402
|
+
body.lcms-editing [contenteditable="true"] {
|
|
403
|
+
position: relative !important;
|
|
404
|
+
z-index: 9999 !important;
|
|
405
|
+
}
|
|
400
406
|
`;
|
|
401
407
|
|
|
402
408
|
// src/xtroedge-cms.ts
|
|
@@ -483,6 +489,7 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
483
489
|
this.scanTimeout = null;
|
|
484
490
|
this.activeImageEl = null;
|
|
485
491
|
this.imageCtxMenu = null;
|
|
492
|
+
this.floatingEditDialog = null;
|
|
486
493
|
// ===== Rich Text Toolbar =====
|
|
487
494
|
this.richToolbarEl = null;
|
|
488
495
|
this.activeEditableEl = null;
|
|
@@ -520,6 +527,7 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
520
527
|
// ===== Edit-mode visibility tracking =====
|
|
521
528
|
this.editScrollHandler = null;
|
|
522
529
|
this.editScrollRAF = null;
|
|
530
|
+
this.editClickCaptureHandler = null;
|
|
523
531
|
// ===== FAB Drag =====
|
|
524
532
|
this.posX = 20;
|
|
525
533
|
this.posY = 20;
|
|
@@ -646,6 +654,26 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
646
654
|
this.observer.observe(document.body, { childList: true, subtree: true });
|
|
647
655
|
window.addEventListener("popstate", this.boundPopState);
|
|
648
656
|
window.addEventListener("hashchange", this.boundHashChange);
|
|
657
|
+
document.addEventListener("click", (e) => {
|
|
658
|
+
if (!document.body.classList.contains("lcms-editing")) return;
|
|
659
|
+
const target = e.target;
|
|
660
|
+
const anchor = target.closest("a");
|
|
661
|
+
if (anchor) {
|
|
662
|
+
let editableEl = null;
|
|
663
|
+
if (target.getAttribute("contenteditable") === "true") {
|
|
664
|
+
editableEl = target;
|
|
665
|
+
} else {
|
|
666
|
+
editableEl = target.closest('[contenteditable="true"]') || anchor.querySelector('[contenteditable="true"]');
|
|
667
|
+
}
|
|
668
|
+
if (editableEl) {
|
|
669
|
+
e.preventDefault();
|
|
670
|
+
e.stopPropagation();
|
|
671
|
+
setTimeout(() => {
|
|
672
|
+
editableEl.focus();
|
|
673
|
+
}, 0);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}, true);
|
|
649
677
|
this.handleNavigation();
|
|
650
678
|
}
|
|
651
679
|
destroy() {
|
|
@@ -666,6 +694,14 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
666
694
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
667
695
|
this.db?.close();
|
|
668
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Manually trigger a rescan of the DOM to detect new editable elements.
|
|
699
|
+
* Useful when elements are dynamically shown/hidden or added to the page.
|
|
700
|
+
*/
|
|
701
|
+
rescan() {
|
|
702
|
+
if (this.scanTimeout) clearTimeout(this.scanTimeout);
|
|
703
|
+
this.autoDetectAndScan();
|
|
704
|
+
}
|
|
669
705
|
// 24 hours
|
|
670
706
|
async validateLicense() {
|
|
671
707
|
const licenseKey = this.config.licenseKey || _XtroedgeCMS.secureGet("builder_token") || "";
|
|
@@ -1165,13 +1201,20 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1165
1201
|
return this.currentSlug;
|
|
1166
1202
|
};
|
|
1167
1203
|
const tagCounters = {};
|
|
1204
|
+
const hasCmsKey = (candidate) => {
|
|
1205
|
+
return Array.from(document.querySelectorAll("[data-cms]")).some((node) => node.getAttribute("data-cms") === candidate);
|
|
1206
|
+
};
|
|
1168
1207
|
const assignKey = (el, sectionSlug) => {
|
|
1169
1208
|
const sectionKey = sectionSlug === "/header" ? "header" : sectionSlug === "/footer" ? "footer" : "page";
|
|
1170
1209
|
if (!tagCounters[sectionKey]) tagCounters[sectionKey] = {};
|
|
1171
1210
|
const tag = el.tagName.toLowerCase();
|
|
1172
1211
|
if (!tagCounters[sectionKey][tag]) tagCounters[sectionKey][tag] = 0;
|
|
1173
1212
|
const prefix = sectionKey !== "page" ? `${sectionKey}_` : "";
|
|
1174
|
-
|
|
1213
|
+
let key = `${prefix}xcms_${tag}_${tagCounters[sectionKey][tag]}`;
|
|
1214
|
+
while (hasCmsKey(key)) {
|
|
1215
|
+
tagCounters[sectionKey][tag]++;
|
|
1216
|
+
key = `${prefix}xcms_${tag}_${tagCounters[sectionKey][tag]}`;
|
|
1217
|
+
}
|
|
1175
1218
|
tagCounters[sectionKey][tag]++;
|
|
1176
1219
|
el.setAttribute("data-cms", key);
|
|
1177
1220
|
el.setAttribute("data-cms-section", sectionSlug);
|
|
@@ -1198,9 +1241,21 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1198
1241
|
}
|
|
1199
1242
|
scanDOM() {
|
|
1200
1243
|
const elements = document.querySelectorAll("[data-cms]");
|
|
1244
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1201
1245
|
elements.forEach((el) => {
|
|
1202
1246
|
if (el.closest("#xtroedge-cms-root, #lcms-login-modal")) return;
|
|
1203
|
-
|
|
1247
|
+
let key = el.getAttribute("data-cms");
|
|
1248
|
+
if (seenKeys.has(key)) {
|
|
1249
|
+
let idx = 1;
|
|
1250
|
+
let repaired = `${key}__${idx}`;
|
|
1251
|
+
while (document.querySelector(`[data-cms="${repaired}"]`)) {
|
|
1252
|
+
idx++;
|
|
1253
|
+
repaired = `${key}__${idx}`;
|
|
1254
|
+
}
|
|
1255
|
+
el.setAttribute("data-cms", repaired);
|
|
1256
|
+
key = repaired;
|
|
1257
|
+
}
|
|
1258
|
+
seenKeys.add(key);
|
|
1204
1259
|
this.registeredKeys.add(key);
|
|
1205
1260
|
if (!this.managedElements.has(el)) {
|
|
1206
1261
|
this.attachElement(el, key);
|
|
@@ -1248,21 +1303,20 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1248
1303
|
return !!el.querySelector("[data-cms]");
|
|
1249
1304
|
}
|
|
1250
1305
|
getElementContent(el) {
|
|
1251
|
-
if (this.
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
return this.richTextEnabled ? el.innerHTML?.trim() || "" : el.textContent?.trim() || "";
|
|
1306
|
+
if (this.richTextEnabled) return el.innerHTML?.trim() || "";
|
|
1307
|
+
if (this.hasEditableChildren(el)) return this.getDirectTextContent(el).trim();
|
|
1308
|
+
return el.textContent?.trim() || "";
|
|
1255
1309
|
}
|
|
1256
1310
|
setElementContent(el, val) {
|
|
1257
|
-
if (this.
|
|
1311
|
+
if (this.richTextEnabled) {
|
|
1312
|
+
el.innerHTML = this.sanitizeHTML(val);
|
|
1313
|
+
} else if (this.hasEditableChildren(el)) {
|
|
1258
1314
|
const textNodes = [];
|
|
1259
1315
|
for (let i = 0; i < el.childNodes.length; i++) {
|
|
1260
1316
|
if (el.childNodes[i].nodeType === Node.TEXT_NODE) textNodes.push(el.childNodes[i]);
|
|
1261
1317
|
}
|
|
1262
1318
|
if (textNodes.length > 0) textNodes[0].textContent = val;
|
|
1263
1319
|
else el.prepend(document.createTextNode(val));
|
|
1264
|
-
} else if (this.richTextEnabled) {
|
|
1265
|
-
el.innerHTML = this.sanitizeHTML(val);
|
|
1266
1320
|
} else {
|
|
1267
1321
|
el.textContent = val;
|
|
1268
1322
|
}
|
|
@@ -1296,12 +1350,21 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1296
1350
|
const currentVal = this.getPageText(key);
|
|
1297
1351
|
if (text !== currentVal) this.onTextChanged(key, text);
|
|
1298
1352
|
};
|
|
1353
|
+
const focusEditableEnd = () => {
|
|
1354
|
+
if (document.activeElement !== el) el.focus();
|
|
1355
|
+
const selection = window.getSelection();
|
|
1356
|
+
if (!selection) return;
|
|
1357
|
+
const range = document.createRange();
|
|
1358
|
+
range.selectNodeContents(el);
|
|
1359
|
+
range.collapse(false);
|
|
1360
|
+
selection.removeAllRanges();
|
|
1361
|
+
selection.addRange(range);
|
|
1362
|
+
};
|
|
1299
1363
|
const clickHandler = (e) => {
|
|
1300
|
-
|
|
1301
|
-
|
|
1364
|
+
e.stopPropagation();
|
|
1365
|
+
const inAnchor = el.tagName === "A" || !!e.target.closest("a");
|
|
1366
|
+
if (inAnchor) {
|
|
1302
1367
|
e.preventDefault();
|
|
1303
|
-
e.stopPropagation();
|
|
1304
|
-
el.focus();
|
|
1305
1368
|
}
|
|
1306
1369
|
};
|
|
1307
1370
|
const sectionSlug = el.getAttribute("data-cms-section") || this.currentSlug;
|
|
@@ -1323,6 +1386,16 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1323
1386
|
static stopProp(e) {
|
|
1324
1387
|
e.stopPropagation();
|
|
1325
1388
|
}
|
|
1389
|
+
focusEditableEnd(el) {
|
|
1390
|
+
if (document.activeElement !== el) el.focus();
|
|
1391
|
+
const selection = window.getSelection();
|
|
1392
|
+
if (!selection) return;
|
|
1393
|
+
const range = document.createRange();
|
|
1394
|
+
range.selectNodeContents(el);
|
|
1395
|
+
range.collapse(false);
|
|
1396
|
+
selection.removeAllRanges();
|
|
1397
|
+
selection.addRange(range);
|
|
1398
|
+
}
|
|
1326
1399
|
enableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
|
|
1327
1400
|
const val = this.getPageText(_key);
|
|
1328
1401
|
if (val) this.setElementContent(el, val);
|
|
@@ -1359,6 +1432,24 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1359
1432
|
applyEditMode(editMode) {
|
|
1360
1433
|
document.body.classList.toggle("lcms-editing", editMode);
|
|
1361
1434
|
if (editMode) {
|
|
1435
|
+
this.editClickCaptureHandler = (e) => {
|
|
1436
|
+
const target = e.target;
|
|
1437
|
+
if (!target) return;
|
|
1438
|
+
const editable = target.closest('[contenteditable="true"]');
|
|
1439
|
+
const imageTarget = target.closest("[data-cms-image-key], img[data-cms-key], [data-cms-key]");
|
|
1440
|
+
const isManagedEditable = !!editable && this.managedElements.has(editable);
|
|
1441
|
+
const isManagedImage = !!imageTarget && this.managedImages.has(imageTarget);
|
|
1442
|
+
const isManagedTarget = isManagedEditable || isManagedImage || !!editable || !!imageTarget;
|
|
1443
|
+
if (isManagedTarget) {
|
|
1444
|
+
e.stopPropagation();
|
|
1445
|
+
const anchor = target.closest("a");
|
|
1446
|
+
if (anchor) e.preventDefault();
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
document.addEventListener("click", this.editClickCaptureHandler, true);
|
|
1450
|
+
document.addEventListener("mousedown", this.editClickCaptureHandler, true);
|
|
1451
|
+
document.addEventListener("pointerdown", this.editClickCaptureHandler, true);
|
|
1452
|
+
document.addEventListener("touchstart", this.editClickCaptureHandler, true);
|
|
1362
1453
|
this.editScrollHandler = () => {
|
|
1363
1454
|
if (this.editScrollRAF) return;
|
|
1364
1455
|
this.editScrollRAF = requestAnimationFrame(() => {
|
|
@@ -1369,6 +1460,13 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1369
1460
|
window.addEventListener("scroll", this.editScrollHandler, { passive: true });
|
|
1370
1461
|
this.syncEditablePointerEvents();
|
|
1371
1462
|
} else {
|
|
1463
|
+
if (this.editClickCaptureHandler) {
|
|
1464
|
+
document.removeEventListener("click", this.editClickCaptureHandler, true);
|
|
1465
|
+
document.removeEventListener("mousedown", this.editClickCaptureHandler, true);
|
|
1466
|
+
document.removeEventListener("pointerdown", this.editClickCaptureHandler, true);
|
|
1467
|
+
document.removeEventListener("touchstart", this.editClickCaptureHandler, true);
|
|
1468
|
+
this.editClickCaptureHandler = null;
|
|
1469
|
+
}
|
|
1372
1470
|
if (this.editScrollHandler) {
|
|
1373
1471
|
window.removeEventListener("scroll", this.editScrollHandler);
|
|
1374
1472
|
this.editScrollHandler = null;
|
|
@@ -1531,6 +1629,11 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1531
1629
|
}
|
|
1532
1630
|
}
|
|
1533
1631
|
this.observer?.observe(document.body, { childList: true, subtree: true });
|
|
1632
|
+
setTimeout(() => {
|
|
1633
|
+
if (this.editMode) {
|
|
1634
|
+
this.applyEditMode(true);
|
|
1635
|
+
}
|
|
1636
|
+
}, 200);
|
|
1534
1637
|
}
|
|
1535
1638
|
cleanupManagedElements() {
|
|
1536
1639
|
for (const [el] of this.managedElements) this.detachElement(el);
|
|
@@ -1572,7 +1675,10 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1572
1675
|
if (editMode) this.enableImageEdit(img, info.ctxHandler);
|
|
1573
1676
|
else this.disableImageEdit(img, info.ctxHandler);
|
|
1574
1677
|
}
|
|
1575
|
-
if (!editMode)
|
|
1678
|
+
if (!editMode) {
|
|
1679
|
+
this.dismissImageCtxMenu();
|
|
1680
|
+
this.dismissFloatingEditDialog();
|
|
1681
|
+
}
|
|
1576
1682
|
}
|
|
1577
1683
|
enableImageEdit(img, ctxHandler) {
|
|
1578
1684
|
if (!img.dataset.origTitle) img.dataset.origTitle = img.title || "";
|
|
@@ -1649,6 +1755,123 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1649
1755
|
this.imageCtxMenu = null;
|
|
1650
1756
|
}
|
|
1651
1757
|
}
|
|
1758
|
+
showFloatingEditDialog(el, key) {
|
|
1759
|
+
this.dismissFloatingEditDialog();
|
|
1760
|
+
const currentText = el.textContent || "";
|
|
1761
|
+
const rect = el.getBoundingClientRect();
|
|
1762
|
+
const dialog = document.createElement("div");
|
|
1763
|
+
Object.assign(dialog.style, {
|
|
1764
|
+
position: "fixed",
|
|
1765
|
+
zIndex: "99999",
|
|
1766
|
+
left: `${Math.max(20, rect.left)}px`,
|
|
1767
|
+
top: `${Math.max(20, rect.top - 120)}px`,
|
|
1768
|
+
background: "rgba(15, 10, 40, 0.95)",
|
|
1769
|
+
backdropFilter: "blur(20px)",
|
|
1770
|
+
webkitBackdropFilter: "blur(20px)",
|
|
1771
|
+
border: "2px solid rgba(139, 92, 246, 0.5)",
|
|
1772
|
+
borderRadius: "12px",
|
|
1773
|
+
padding: "16px",
|
|
1774
|
+
boxShadow: "0 12px 48px rgba(0, 0, 0, 0.6)",
|
|
1775
|
+
minWidth: "320px",
|
|
1776
|
+
maxWidth: "500px"
|
|
1777
|
+
});
|
|
1778
|
+
const title = document.createElement("div");
|
|
1779
|
+
title.textContent = "Edit Text";
|
|
1780
|
+
Object.assign(title.style, {
|
|
1781
|
+
color: "rgba(255, 255, 255, 0.9)",
|
|
1782
|
+
fontSize: "13px",
|
|
1783
|
+
fontWeight: "600",
|
|
1784
|
+
marginBottom: "12px",
|
|
1785
|
+
fontFamily: "system-ui, sans-serif"
|
|
1786
|
+
});
|
|
1787
|
+
const textarea = document.createElement("textarea");
|
|
1788
|
+
textarea.value = currentText;
|
|
1789
|
+
Object.assign(textarea.style, {
|
|
1790
|
+
width: "100%",
|
|
1791
|
+
minHeight: "80px",
|
|
1792
|
+
background: "rgba(255, 255, 255, 0.08)",
|
|
1793
|
+
border: "1px solid rgba(139, 92, 246, 0.3)",
|
|
1794
|
+
borderRadius: "8px",
|
|
1795
|
+
color: "white",
|
|
1796
|
+
padding: "10px",
|
|
1797
|
+
fontSize: "14px",
|
|
1798
|
+
fontFamily: "system-ui, sans-serif",
|
|
1799
|
+
resize: "vertical",
|
|
1800
|
+
outline: "none"
|
|
1801
|
+
});
|
|
1802
|
+
textarea.addEventListener("focus", () => {
|
|
1803
|
+
textarea.style.borderColor = this.highlightColor;
|
|
1804
|
+
});
|
|
1805
|
+
textarea.addEventListener("blur", () => {
|
|
1806
|
+
textarea.style.borderColor = "rgba(139, 92, 246, 0.3)";
|
|
1807
|
+
});
|
|
1808
|
+
const buttons = document.createElement("div");
|
|
1809
|
+
Object.assign(buttons.style, {
|
|
1810
|
+
display: "flex",
|
|
1811
|
+
gap: "8px",
|
|
1812
|
+
marginTop: "12px",
|
|
1813
|
+
justifyContent: "flex-end"
|
|
1814
|
+
});
|
|
1815
|
+
const cancelBtn = document.createElement("button");
|
|
1816
|
+
cancelBtn.textContent = "Cancel";
|
|
1817
|
+
Object.assign(cancelBtn.style, {
|
|
1818
|
+
padding: "8px 16px",
|
|
1819
|
+
background: "rgba(255, 255, 255, 0.1)",
|
|
1820
|
+
border: "none",
|
|
1821
|
+
borderRadius: "6px",
|
|
1822
|
+
color: "rgba(255, 255, 255, 0.7)",
|
|
1823
|
+
fontSize: "13px",
|
|
1824
|
+
fontWeight: "500",
|
|
1825
|
+
cursor: "pointer",
|
|
1826
|
+
fontFamily: "system-ui, sans-serif"
|
|
1827
|
+
});
|
|
1828
|
+
cancelBtn.addEventListener("click", () => this.dismissFloatingEditDialog());
|
|
1829
|
+
cancelBtn.addEventListener("mouseenter", () => cancelBtn.style.background = "rgba(255, 255, 255, 0.15)");
|
|
1830
|
+
cancelBtn.addEventListener("mouseleave", () => cancelBtn.style.background = "rgba(255, 255, 255, 0.1)");
|
|
1831
|
+
const saveBtn = document.createElement("button");
|
|
1832
|
+
saveBtn.textContent = "Save";
|
|
1833
|
+
Object.assign(saveBtn.style, {
|
|
1834
|
+
padding: "8px 16px",
|
|
1835
|
+
background: this.highlightColor,
|
|
1836
|
+
border: "none",
|
|
1837
|
+
borderRadius: "6px",
|
|
1838
|
+
color: "white",
|
|
1839
|
+
fontSize: "13px",
|
|
1840
|
+
fontWeight: "600",
|
|
1841
|
+
cursor: "pointer",
|
|
1842
|
+
fontFamily: "system-ui, sans-serif"
|
|
1843
|
+
});
|
|
1844
|
+
saveBtn.addEventListener("click", () => {
|
|
1845
|
+
const newText = textarea.value;
|
|
1846
|
+
this.setElementContent(el, newText);
|
|
1847
|
+
this.onTextChanged(key, newText);
|
|
1848
|
+
this.dismissFloatingEditDialog();
|
|
1849
|
+
});
|
|
1850
|
+
saveBtn.addEventListener("mouseenter", () => saveBtn.style.opacity = "0.9");
|
|
1851
|
+
saveBtn.addEventListener("mouseleave", () => saveBtn.style.opacity = "1");
|
|
1852
|
+
buttons.appendChild(cancelBtn);
|
|
1853
|
+
buttons.appendChild(saveBtn);
|
|
1854
|
+
dialog.appendChild(title);
|
|
1855
|
+
dialog.appendChild(textarea);
|
|
1856
|
+
dialog.appendChild(buttons);
|
|
1857
|
+
(this.rootEl || document.body).appendChild(dialog);
|
|
1858
|
+
this.floatingEditDialog = dialog;
|
|
1859
|
+
textarea.focus();
|
|
1860
|
+
textarea.select();
|
|
1861
|
+
const closeHandler = (e) => {
|
|
1862
|
+
if (!dialog.contains(e.target)) {
|
|
1863
|
+
this.dismissFloatingEditDialog();
|
|
1864
|
+
document.removeEventListener("mousedown", closeHandler);
|
|
1865
|
+
}
|
|
1866
|
+
};
|
|
1867
|
+
setTimeout(() => document.addEventListener("mousedown", closeHandler), 0);
|
|
1868
|
+
}
|
|
1869
|
+
dismissFloatingEditDialog() {
|
|
1870
|
+
if (this.floatingEditDialog) {
|
|
1871
|
+
this.floatingEditDialog.remove();
|
|
1872
|
+
this.floatingEditDialog = null;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1652
1875
|
onImageFileSelected(event) {
|
|
1653
1876
|
const input = event.target;
|
|
1654
1877
|
const file = input.files?.[0];
|