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.js
CHANGED
|
@@ -423,6 +423,12 @@ body.lcms-editing [data-cms] {
|
|
|
423
423
|
-webkit-user-select: text !important;
|
|
424
424
|
cursor: text !important;
|
|
425
425
|
}
|
|
426
|
+
|
|
427
|
+
/* Ensure editable elements appear above other content */
|
|
428
|
+
body.lcms-editing [contenteditable="true"] {
|
|
429
|
+
position: relative !important;
|
|
430
|
+
z-index: 9999 !important;
|
|
431
|
+
}
|
|
426
432
|
`;
|
|
427
433
|
|
|
428
434
|
// src/xtroedge-cms.ts
|
|
@@ -509,6 +515,7 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
509
515
|
this.scanTimeout = null;
|
|
510
516
|
this.activeImageEl = null;
|
|
511
517
|
this.imageCtxMenu = null;
|
|
518
|
+
this.floatingEditDialog = null;
|
|
512
519
|
// ===== Rich Text Toolbar =====
|
|
513
520
|
this.richToolbarEl = null;
|
|
514
521
|
this.activeEditableEl = null;
|
|
@@ -546,6 +553,7 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
546
553
|
// ===== Edit-mode visibility tracking =====
|
|
547
554
|
this.editScrollHandler = null;
|
|
548
555
|
this.editScrollRAF = null;
|
|
556
|
+
this.editClickCaptureHandler = null;
|
|
549
557
|
// ===== FAB Drag =====
|
|
550
558
|
this.posX = 20;
|
|
551
559
|
this.posY = 20;
|
|
@@ -672,6 +680,26 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
672
680
|
this.observer.observe(document.body, { childList: true, subtree: true });
|
|
673
681
|
window.addEventListener("popstate", this.boundPopState);
|
|
674
682
|
window.addEventListener("hashchange", this.boundHashChange);
|
|
683
|
+
document.addEventListener("click", (e) => {
|
|
684
|
+
if (!document.body.classList.contains("lcms-editing")) return;
|
|
685
|
+
const target = e.target;
|
|
686
|
+
const anchor = target.closest("a");
|
|
687
|
+
if (anchor) {
|
|
688
|
+
let editableEl = null;
|
|
689
|
+
if (target.getAttribute("contenteditable") === "true") {
|
|
690
|
+
editableEl = target;
|
|
691
|
+
} else {
|
|
692
|
+
editableEl = target.closest('[contenteditable="true"]') || anchor.querySelector('[contenteditable="true"]');
|
|
693
|
+
}
|
|
694
|
+
if (editableEl) {
|
|
695
|
+
e.preventDefault();
|
|
696
|
+
e.stopPropagation();
|
|
697
|
+
setTimeout(() => {
|
|
698
|
+
editableEl.focus();
|
|
699
|
+
}, 0);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}, true);
|
|
675
703
|
this.handleNavigation();
|
|
676
704
|
}
|
|
677
705
|
destroy() {
|
|
@@ -692,6 +720,14 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
692
720
|
if (this.toastTimer) clearTimeout(this.toastTimer);
|
|
693
721
|
this.db?.close();
|
|
694
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* Manually trigger a rescan of the DOM to detect new editable elements.
|
|
725
|
+
* Useful when elements are dynamically shown/hidden or added to the page.
|
|
726
|
+
*/
|
|
727
|
+
rescan() {
|
|
728
|
+
if (this.scanTimeout) clearTimeout(this.scanTimeout);
|
|
729
|
+
this.autoDetectAndScan();
|
|
730
|
+
}
|
|
695
731
|
// 24 hours
|
|
696
732
|
async validateLicense() {
|
|
697
733
|
const licenseKey = this.config.licenseKey || _XtroedgeCMS.secureGet("builder_token") || "";
|
|
@@ -1191,13 +1227,20 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1191
1227
|
return this.currentSlug;
|
|
1192
1228
|
};
|
|
1193
1229
|
const tagCounters = {};
|
|
1230
|
+
const hasCmsKey = (candidate) => {
|
|
1231
|
+
return Array.from(document.querySelectorAll("[data-cms]")).some((node) => node.getAttribute("data-cms") === candidate);
|
|
1232
|
+
};
|
|
1194
1233
|
const assignKey = (el, sectionSlug) => {
|
|
1195
1234
|
const sectionKey = sectionSlug === "/header" ? "header" : sectionSlug === "/footer" ? "footer" : "page";
|
|
1196
1235
|
if (!tagCounters[sectionKey]) tagCounters[sectionKey] = {};
|
|
1197
1236
|
const tag = el.tagName.toLowerCase();
|
|
1198
1237
|
if (!tagCounters[sectionKey][tag]) tagCounters[sectionKey][tag] = 0;
|
|
1199
1238
|
const prefix = sectionKey !== "page" ? `${sectionKey}_` : "";
|
|
1200
|
-
|
|
1239
|
+
let key = `${prefix}xcms_${tag}_${tagCounters[sectionKey][tag]}`;
|
|
1240
|
+
while (hasCmsKey(key)) {
|
|
1241
|
+
tagCounters[sectionKey][tag]++;
|
|
1242
|
+
key = `${prefix}xcms_${tag}_${tagCounters[sectionKey][tag]}`;
|
|
1243
|
+
}
|
|
1201
1244
|
tagCounters[sectionKey][tag]++;
|
|
1202
1245
|
el.setAttribute("data-cms", key);
|
|
1203
1246
|
el.setAttribute("data-cms-section", sectionSlug);
|
|
@@ -1224,9 +1267,21 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1224
1267
|
}
|
|
1225
1268
|
scanDOM() {
|
|
1226
1269
|
const elements = document.querySelectorAll("[data-cms]");
|
|
1270
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1227
1271
|
elements.forEach((el) => {
|
|
1228
1272
|
if (el.closest("#xtroedge-cms-root, #lcms-login-modal")) return;
|
|
1229
|
-
|
|
1273
|
+
let key = el.getAttribute("data-cms");
|
|
1274
|
+
if (seenKeys.has(key)) {
|
|
1275
|
+
let idx = 1;
|
|
1276
|
+
let repaired = `${key}__${idx}`;
|
|
1277
|
+
while (document.querySelector(`[data-cms="${repaired}"]`)) {
|
|
1278
|
+
idx++;
|
|
1279
|
+
repaired = `${key}__${idx}`;
|
|
1280
|
+
}
|
|
1281
|
+
el.setAttribute("data-cms", repaired);
|
|
1282
|
+
key = repaired;
|
|
1283
|
+
}
|
|
1284
|
+
seenKeys.add(key);
|
|
1230
1285
|
this.registeredKeys.add(key);
|
|
1231
1286
|
if (!this.managedElements.has(el)) {
|
|
1232
1287
|
this.attachElement(el, key);
|
|
@@ -1274,21 +1329,20 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1274
1329
|
return !!el.querySelector("[data-cms]");
|
|
1275
1330
|
}
|
|
1276
1331
|
getElementContent(el) {
|
|
1277
|
-
if (this.
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
return this.richTextEnabled ? el.innerHTML?.trim() || "" : el.textContent?.trim() || "";
|
|
1332
|
+
if (this.richTextEnabled) return el.innerHTML?.trim() || "";
|
|
1333
|
+
if (this.hasEditableChildren(el)) return this.getDirectTextContent(el).trim();
|
|
1334
|
+
return el.textContent?.trim() || "";
|
|
1281
1335
|
}
|
|
1282
1336
|
setElementContent(el, val) {
|
|
1283
|
-
if (this.
|
|
1337
|
+
if (this.richTextEnabled) {
|
|
1338
|
+
el.innerHTML = this.sanitizeHTML(val);
|
|
1339
|
+
} else if (this.hasEditableChildren(el)) {
|
|
1284
1340
|
const textNodes = [];
|
|
1285
1341
|
for (let i = 0; i < el.childNodes.length; i++) {
|
|
1286
1342
|
if (el.childNodes[i].nodeType === Node.TEXT_NODE) textNodes.push(el.childNodes[i]);
|
|
1287
1343
|
}
|
|
1288
1344
|
if (textNodes.length > 0) textNodes[0].textContent = val;
|
|
1289
1345
|
else el.prepend(document.createTextNode(val));
|
|
1290
|
-
} else if (this.richTextEnabled) {
|
|
1291
|
-
el.innerHTML = this.sanitizeHTML(val);
|
|
1292
1346
|
} else {
|
|
1293
1347
|
el.textContent = val;
|
|
1294
1348
|
}
|
|
@@ -1322,12 +1376,21 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1322
1376
|
const currentVal = this.getPageText(key);
|
|
1323
1377
|
if (text !== currentVal) this.onTextChanged(key, text);
|
|
1324
1378
|
};
|
|
1379
|
+
const focusEditableEnd = () => {
|
|
1380
|
+
if (document.activeElement !== el) el.focus();
|
|
1381
|
+
const selection = window.getSelection();
|
|
1382
|
+
if (!selection) return;
|
|
1383
|
+
const range = document.createRange();
|
|
1384
|
+
range.selectNodeContents(el);
|
|
1385
|
+
range.collapse(false);
|
|
1386
|
+
selection.removeAllRanges();
|
|
1387
|
+
selection.addRange(range);
|
|
1388
|
+
};
|
|
1325
1389
|
const clickHandler = (e) => {
|
|
1326
|
-
|
|
1327
|
-
|
|
1390
|
+
e.stopPropagation();
|
|
1391
|
+
const inAnchor = el.tagName === "A" || !!e.target.closest("a");
|
|
1392
|
+
if (inAnchor) {
|
|
1328
1393
|
e.preventDefault();
|
|
1329
|
-
e.stopPropagation();
|
|
1330
|
-
el.focus();
|
|
1331
1394
|
}
|
|
1332
1395
|
};
|
|
1333
1396
|
const sectionSlug = el.getAttribute("data-cms-section") || this.currentSlug;
|
|
@@ -1349,6 +1412,16 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1349
1412
|
static stopProp(e) {
|
|
1350
1413
|
e.stopPropagation();
|
|
1351
1414
|
}
|
|
1415
|
+
focusEditableEnd(el) {
|
|
1416
|
+
if (document.activeElement !== el) el.focus();
|
|
1417
|
+
const selection = window.getSelection();
|
|
1418
|
+
if (!selection) return;
|
|
1419
|
+
const range = document.createRange();
|
|
1420
|
+
range.selectNodeContents(el);
|
|
1421
|
+
range.collapse(false);
|
|
1422
|
+
selection.removeAllRanges();
|
|
1423
|
+
selection.addRange(range);
|
|
1424
|
+
}
|
|
1352
1425
|
enableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
|
|
1353
1426
|
const val = this.getPageText(_key);
|
|
1354
1427
|
if (val) this.setElementContent(el, val);
|
|
@@ -1385,6 +1458,24 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1385
1458
|
applyEditMode(editMode) {
|
|
1386
1459
|
document.body.classList.toggle("lcms-editing", editMode);
|
|
1387
1460
|
if (editMode) {
|
|
1461
|
+
this.editClickCaptureHandler = (e) => {
|
|
1462
|
+
const target = e.target;
|
|
1463
|
+
if (!target) return;
|
|
1464
|
+
const editable = target.closest('[contenteditable="true"]');
|
|
1465
|
+
const imageTarget = target.closest("[data-cms-image-key], img[data-cms-key], [data-cms-key]");
|
|
1466
|
+
const isManagedEditable = !!editable && this.managedElements.has(editable);
|
|
1467
|
+
const isManagedImage = !!imageTarget && this.managedImages.has(imageTarget);
|
|
1468
|
+
const isManagedTarget = isManagedEditable || isManagedImage || !!editable || !!imageTarget;
|
|
1469
|
+
if (isManagedTarget) {
|
|
1470
|
+
e.stopPropagation();
|
|
1471
|
+
const anchor = target.closest("a");
|
|
1472
|
+
if (anchor) e.preventDefault();
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
document.addEventListener("click", this.editClickCaptureHandler, true);
|
|
1476
|
+
document.addEventListener("mousedown", this.editClickCaptureHandler, true);
|
|
1477
|
+
document.addEventListener("pointerdown", this.editClickCaptureHandler, true);
|
|
1478
|
+
document.addEventListener("touchstart", this.editClickCaptureHandler, true);
|
|
1388
1479
|
this.editScrollHandler = () => {
|
|
1389
1480
|
if (this.editScrollRAF) return;
|
|
1390
1481
|
this.editScrollRAF = requestAnimationFrame(() => {
|
|
@@ -1395,6 +1486,13 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1395
1486
|
window.addEventListener("scroll", this.editScrollHandler, { passive: true });
|
|
1396
1487
|
this.syncEditablePointerEvents();
|
|
1397
1488
|
} else {
|
|
1489
|
+
if (this.editClickCaptureHandler) {
|
|
1490
|
+
document.removeEventListener("click", this.editClickCaptureHandler, true);
|
|
1491
|
+
document.removeEventListener("mousedown", this.editClickCaptureHandler, true);
|
|
1492
|
+
document.removeEventListener("pointerdown", this.editClickCaptureHandler, true);
|
|
1493
|
+
document.removeEventListener("touchstart", this.editClickCaptureHandler, true);
|
|
1494
|
+
this.editClickCaptureHandler = null;
|
|
1495
|
+
}
|
|
1398
1496
|
if (this.editScrollHandler) {
|
|
1399
1497
|
window.removeEventListener("scroll", this.editScrollHandler);
|
|
1400
1498
|
this.editScrollHandler = null;
|
|
@@ -1557,6 +1655,11 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1557
1655
|
}
|
|
1558
1656
|
}
|
|
1559
1657
|
this.observer?.observe(document.body, { childList: true, subtree: true });
|
|
1658
|
+
setTimeout(() => {
|
|
1659
|
+
if (this.editMode) {
|
|
1660
|
+
this.applyEditMode(true);
|
|
1661
|
+
}
|
|
1662
|
+
}, 200);
|
|
1560
1663
|
}
|
|
1561
1664
|
cleanupManagedElements() {
|
|
1562
1665
|
for (const [el] of this.managedElements) this.detachElement(el);
|
|
@@ -1598,7 +1701,10 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1598
1701
|
if (editMode) this.enableImageEdit(img, info.ctxHandler);
|
|
1599
1702
|
else this.disableImageEdit(img, info.ctxHandler);
|
|
1600
1703
|
}
|
|
1601
|
-
if (!editMode)
|
|
1704
|
+
if (!editMode) {
|
|
1705
|
+
this.dismissImageCtxMenu();
|
|
1706
|
+
this.dismissFloatingEditDialog();
|
|
1707
|
+
}
|
|
1602
1708
|
}
|
|
1603
1709
|
enableImageEdit(img, ctxHandler) {
|
|
1604
1710
|
if (!img.dataset.origTitle) img.dataset.origTitle = img.title || "";
|
|
@@ -1675,6 +1781,123 @@ var _XtroedgeCMS = class _XtroedgeCMS {
|
|
|
1675
1781
|
this.imageCtxMenu = null;
|
|
1676
1782
|
}
|
|
1677
1783
|
}
|
|
1784
|
+
showFloatingEditDialog(el, key) {
|
|
1785
|
+
this.dismissFloatingEditDialog();
|
|
1786
|
+
const currentText = el.textContent || "";
|
|
1787
|
+
const rect = el.getBoundingClientRect();
|
|
1788
|
+
const dialog = document.createElement("div");
|
|
1789
|
+
Object.assign(dialog.style, {
|
|
1790
|
+
position: "fixed",
|
|
1791
|
+
zIndex: "99999",
|
|
1792
|
+
left: `${Math.max(20, rect.left)}px`,
|
|
1793
|
+
top: `${Math.max(20, rect.top - 120)}px`,
|
|
1794
|
+
background: "rgba(15, 10, 40, 0.95)",
|
|
1795
|
+
backdropFilter: "blur(20px)",
|
|
1796
|
+
webkitBackdropFilter: "blur(20px)",
|
|
1797
|
+
border: "2px solid rgba(139, 92, 246, 0.5)",
|
|
1798
|
+
borderRadius: "12px",
|
|
1799
|
+
padding: "16px",
|
|
1800
|
+
boxShadow: "0 12px 48px rgba(0, 0, 0, 0.6)",
|
|
1801
|
+
minWidth: "320px",
|
|
1802
|
+
maxWidth: "500px"
|
|
1803
|
+
});
|
|
1804
|
+
const title = document.createElement("div");
|
|
1805
|
+
title.textContent = "Edit Text";
|
|
1806
|
+
Object.assign(title.style, {
|
|
1807
|
+
color: "rgba(255, 255, 255, 0.9)",
|
|
1808
|
+
fontSize: "13px",
|
|
1809
|
+
fontWeight: "600",
|
|
1810
|
+
marginBottom: "12px",
|
|
1811
|
+
fontFamily: "system-ui, sans-serif"
|
|
1812
|
+
});
|
|
1813
|
+
const textarea = document.createElement("textarea");
|
|
1814
|
+
textarea.value = currentText;
|
|
1815
|
+
Object.assign(textarea.style, {
|
|
1816
|
+
width: "100%",
|
|
1817
|
+
minHeight: "80px",
|
|
1818
|
+
background: "rgba(255, 255, 255, 0.08)",
|
|
1819
|
+
border: "1px solid rgba(139, 92, 246, 0.3)",
|
|
1820
|
+
borderRadius: "8px",
|
|
1821
|
+
color: "white",
|
|
1822
|
+
padding: "10px",
|
|
1823
|
+
fontSize: "14px",
|
|
1824
|
+
fontFamily: "system-ui, sans-serif",
|
|
1825
|
+
resize: "vertical",
|
|
1826
|
+
outline: "none"
|
|
1827
|
+
});
|
|
1828
|
+
textarea.addEventListener("focus", () => {
|
|
1829
|
+
textarea.style.borderColor = this.highlightColor;
|
|
1830
|
+
});
|
|
1831
|
+
textarea.addEventListener("blur", () => {
|
|
1832
|
+
textarea.style.borderColor = "rgba(139, 92, 246, 0.3)";
|
|
1833
|
+
});
|
|
1834
|
+
const buttons = document.createElement("div");
|
|
1835
|
+
Object.assign(buttons.style, {
|
|
1836
|
+
display: "flex",
|
|
1837
|
+
gap: "8px",
|
|
1838
|
+
marginTop: "12px",
|
|
1839
|
+
justifyContent: "flex-end"
|
|
1840
|
+
});
|
|
1841
|
+
const cancelBtn = document.createElement("button");
|
|
1842
|
+
cancelBtn.textContent = "Cancel";
|
|
1843
|
+
Object.assign(cancelBtn.style, {
|
|
1844
|
+
padding: "8px 16px",
|
|
1845
|
+
background: "rgba(255, 255, 255, 0.1)",
|
|
1846
|
+
border: "none",
|
|
1847
|
+
borderRadius: "6px",
|
|
1848
|
+
color: "rgba(255, 255, 255, 0.7)",
|
|
1849
|
+
fontSize: "13px",
|
|
1850
|
+
fontWeight: "500",
|
|
1851
|
+
cursor: "pointer",
|
|
1852
|
+
fontFamily: "system-ui, sans-serif"
|
|
1853
|
+
});
|
|
1854
|
+
cancelBtn.addEventListener("click", () => this.dismissFloatingEditDialog());
|
|
1855
|
+
cancelBtn.addEventListener("mouseenter", () => cancelBtn.style.background = "rgba(255, 255, 255, 0.15)");
|
|
1856
|
+
cancelBtn.addEventListener("mouseleave", () => cancelBtn.style.background = "rgba(255, 255, 255, 0.1)");
|
|
1857
|
+
const saveBtn = document.createElement("button");
|
|
1858
|
+
saveBtn.textContent = "Save";
|
|
1859
|
+
Object.assign(saveBtn.style, {
|
|
1860
|
+
padding: "8px 16px",
|
|
1861
|
+
background: this.highlightColor,
|
|
1862
|
+
border: "none",
|
|
1863
|
+
borderRadius: "6px",
|
|
1864
|
+
color: "white",
|
|
1865
|
+
fontSize: "13px",
|
|
1866
|
+
fontWeight: "600",
|
|
1867
|
+
cursor: "pointer",
|
|
1868
|
+
fontFamily: "system-ui, sans-serif"
|
|
1869
|
+
});
|
|
1870
|
+
saveBtn.addEventListener("click", () => {
|
|
1871
|
+
const newText = textarea.value;
|
|
1872
|
+
this.setElementContent(el, newText);
|
|
1873
|
+
this.onTextChanged(key, newText);
|
|
1874
|
+
this.dismissFloatingEditDialog();
|
|
1875
|
+
});
|
|
1876
|
+
saveBtn.addEventListener("mouseenter", () => saveBtn.style.opacity = "0.9");
|
|
1877
|
+
saveBtn.addEventListener("mouseleave", () => saveBtn.style.opacity = "1");
|
|
1878
|
+
buttons.appendChild(cancelBtn);
|
|
1879
|
+
buttons.appendChild(saveBtn);
|
|
1880
|
+
dialog.appendChild(title);
|
|
1881
|
+
dialog.appendChild(textarea);
|
|
1882
|
+
dialog.appendChild(buttons);
|
|
1883
|
+
(this.rootEl || document.body).appendChild(dialog);
|
|
1884
|
+
this.floatingEditDialog = dialog;
|
|
1885
|
+
textarea.focus();
|
|
1886
|
+
textarea.select();
|
|
1887
|
+
const closeHandler = (e) => {
|
|
1888
|
+
if (!dialog.contains(e.target)) {
|
|
1889
|
+
this.dismissFloatingEditDialog();
|
|
1890
|
+
document.removeEventListener("mousedown", closeHandler);
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
setTimeout(() => document.addEventListener("mousedown", closeHandler), 0);
|
|
1894
|
+
}
|
|
1895
|
+
dismissFloatingEditDialog() {
|
|
1896
|
+
if (this.floatingEditDialog) {
|
|
1897
|
+
this.floatingEditDialog.remove();
|
|
1898
|
+
this.floatingEditDialog = null;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1678
1901
|
onImageFileSelected(event) {
|
|
1679
1902
|
const input = event.target;
|
|
1680
1903
|
const file = input.files?.[0];
|