ngx-xtroedge-cms 1.3.1 → 1.3.2

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.mjs CHANGED
@@ -753,7 +753,7 @@ var XtroedgeCMS = class _XtroedgeCMS {
753
753
  if (this.containerSelector) {
754
754
  return document.querySelector(this.containerSelector) || document.body;
755
755
  }
756
- return document.querySelector(".layout") || document.body;
756
+ return document.body;
757
757
  }
758
758
  autoDetectElements() {
759
759
  const container = this.resolveContainer();
@@ -761,7 +761,29 @@ var XtroedgeCMS = class _XtroedgeCMS {
761
761
  for (const el of this.autoDetectedElements) {
762
762
  if (!document.contains(el)) this.autoDetectedElements.delete(el);
763
763
  }
764
+ const getSectionSlug = (el) => {
765
+ let parent = el.parentElement;
766
+ while (parent && parent !== document.body) {
767
+ const tag = parent.tagName.toLowerCase();
768
+ if (tag === "header" || tag.endsWith("-header")) return "/header";
769
+ if (tag === "footer" || tag.endsWith("-footer")) return "/footer";
770
+ parent = parent.parentElement;
771
+ }
772
+ return this.currentSlug;
773
+ };
764
774
  const tagCounters = {};
775
+ const assignKey = (el, sectionSlug) => {
776
+ const sectionKey = sectionSlug === "/header" ? "header" : sectionSlug === "/footer" ? "footer" : "page";
777
+ if (!tagCounters[sectionKey]) tagCounters[sectionKey] = {};
778
+ const tag = el.tagName.toLowerCase();
779
+ if (!tagCounters[sectionKey][tag]) tagCounters[sectionKey][tag] = 0;
780
+ const prefix = sectionKey !== "page" ? `${sectionKey}_` : "";
781
+ const key = `${prefix}xcms_${tag}_${tagCounters[sectionKey][tag]}`;
782
+ tagCounters[sectionKey][tag]++;
783
+ el.setAttribute("data-cms", key);
784
+ el.setAttribute("data-cms-section", sectionSlug);
785
+ this.autoDetectedElements.add(el);
786
+ };
765
787
  const selector = this.editableTags.join(",");
766
788
  const elements = container.querySelectorAll(selector);
767
789
  elements.forEach((el) => {
@@ -770,12 +792,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
770
792
  if (el.children.length > 3) return;
771
793
  const text = this.getDirectTextContent(el).trim();
772
794
  if (text.length < 2) return;
773
- const tag = el.tagName.toLowerCase();
774
- if (!tagCounters[tag]) tagCounters[tag] = 0;
775
- const key = `xcms_${tag}_${tagCounters[tag]}`;
776
- tagCounters[tag]++;
777
- el.setAttribute("data-cms", key);
778
- this.autoDetectedElements.add(el);
795
+ assignKey(el, getSectionSlug(el));
796
+ });
797
+ const dataEditables = container.querySelectorAll("[data-editable]");
798
+ dataEditables.forEach((el) => {
799
+ if (el.hasAttribute("data-cms")) return;
800
+ if (el.closest("#xtroedge-cms-root, script, style, noscript")) return;
801
+ assignKey(el, getSectionSlug(el));
779
802
  });
780
803
  }
781
804
  scanDOM() {
@@ -846,8 +869,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
846
869
  const currentVal = this.getPageText(key);
847
870
  if (text !== currentVal) this.onTextChanged(key, text);
848
871
  };
849
- this.managedElements.set(el, { key, blurHandler, keydownHandler, inputHandler });
850
- if (this.editMode) this.enableElementEdit(el, key, blurHandler, keydownHandler, inputHandler);
872
+ const clickHandler = (e) => {
873
+ e.preventDefault();
874
+ e.stopPropagation();
875
+ };
876
+ const sectionSlug = el.getAttribute("data-cms-section") || this.currentSlug;
877
+ this.managedElements.set(el, { key, sectionSlug, blurHandler, keydownHandler, inputHandler, clickHandler });
878
+ if (this.editMode) this.enableElementEdit(el, key, blurHandler, keydownHandler, inputHandler, clickHandler);
851
879
  }
852
880
  detachElement(el) {
853
881
  const info = this.managedElements.get(el);
@@ -855,11 +883,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
855
883
  el.removeEventListener("blur", info.blurHandler);
856
884
  el.removeEventListener("keydown", info.keydownHandler);
857
885
  el.removeEventListener("input", info.inputHandler);
886
+ el.removeEventListener("click", info.clickHandler, true);
858
887
  el.removeAttribute("contenteditable");
859
888
  this.managedElements.delete(el);
860
889
  }
861
890
  }
862
- enableElementEdit(el, _key, blurH, keyH, inputH) {
891
+ enableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
863
892
  const val = this.getPageText(_key);
864
893
  if (val) el.textContent = val;
865
894
  el.setAttribute("contenteditable", "true");
@@ -873,8 +902,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
873
902
  el.addEventListener("blur", blurH);
874
903
  el.addEventListener("keydown", keyH);
875
904
  el.addEventListener("input", inputH);
905
+ el.addEventListener("click", clickH, true);
876
906
  }
877
- disableElementEdit(el, _key, blurH, keyH, inputH) {
907
+ disableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
878
908
  el.removeAttribute("contenteditable");
879
909
  el.style.borderBottom = "";
880
910
  el.style.padding = "";
@@ -888,11 +918,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
888
918
  el.removeEventListener("blur", blurH);
889
919
  el.removeEventListener("keydown", keyH);
890
920
  el.removeEventListener("input", inputH);
921
+ el.removeEventListener("click", clickH, true);
891
922
  }
892
923
  applyEditMode(editMode) {
893
924
  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);
925
+ if (editMode) this.enableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler, info.clickHandler);
926
+ else this.disableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler, info.clickHandler);
896
927
  }
897
928
  this.applyImageEditMode(editMode);
898
929
  }
@@ -1214,23 +1245,43 @@ var XtroedgeCMS = class _XtroedgeCMS {
1214
1245
  getPageSection() {
1215
1246
  return this.currentSlug.replace(/^\//, "").replace(/-/g, "_").toUpperCase();
1216
1247
  }
1248
+ // Fetch texts for a single slug and merge into pageTexts
1249
+ async fetchSectionTexts(slug, status) {
1250
+ try {
1251
+ const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(slug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1252
+ if (!res.ok) return null;
1253
+ const page = await res.json();
1254
+ return page?.published_content?.texts || page?.content?.texts || null;
1255
+ } catch {
1256
+ return null;
1257
+ }
1258
+ }
1217
1259
  async loadPageContent(status) {
1218
1260
  this.setLoading(true);
1219
1261
  this.cleanOldHistory();
1220
1262
  try {
1221
- const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1222
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1223
- const page = await res.json();
1224
- const texts = page?.published_content?.texts || page?.content?.texts;
1225
- if (texts) {
1226
- this.pageTexts = JSON.parse(JSON.stringify(texts));
1263
+ const [pageTexts, headerTexts, footerTexts] = await Promise.all([
1264
+ this.fetchSectionTexts(this.currentSlug, status),
1265
+ this.fetchSectionTexts("/header", status),
1266
+ this.fetchSectionTexts("/footer", status)
1267
+ ]);
1268
+ if (pageTexts || headerTexts || footerTexts) {
1269
+ this.pageTexts = {
1270
+ ...pageTexts || {},
1271
+ ...headerTexts || {},
1272
+ ...footerTexts || {}
1273
+ };
1227
1274
  } else {
1228
1275
  this.buildDefaultTexts();
1229
1276
  }
1230
- const images = page?.published_content?.images || page?.content?.images;
1231
- if (images) {
1232
- this.pageImages = JSON.parse(JSON.stringify(images));
1233
- this.applyPageImages();
1277
+ const resImg = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`).catch(() => null);
1278
+ if (resImg?.ok) {
1279
+ const page = await resImg.json();
1280
+ const images = page?.published_content?.images || page?.content?.images;
1281
+ if (images) {
1282
+ this.pageImages = JSON.parse(JSON.stringify(images));
1283
+ this.applyPageImages();
1284
+ }
1234
1285
  }
1235
1286
  this.originalTexts = JSON.parse(JSON.stringify(this.pageTexts));
1236
1287
  this.originalImages = JSON.parse(JSON.stringify(this.pageImages));
@@ -1248,18 +1299,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
1248
1299
  async loadPublishedContent() {
1249
1300
  this.setLoading(true);
1250
1301
  try {
1251
- const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=published&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1252
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1253
- const page = await res.json();
1254
- const texts = page?.published_content?.texts;
1255
- if (texts && Object.keys(texts).length > 0) {
1256
- this.pageTexts = JSON.parse(JSON.stringify(texts));
1302
+ const [pageTexts, headerTexts, footerTexts] = await Promise.all([
1303
+ this.fetchSectionTexts(this.currentSlug, "published"),
1304
+ this.fetchSectionTexts("/header", "published"),
1305
+ this.fetchSectionTexts("/footer", "published")
1306
+ ]);
1307
+ const merged = { ...pageTexts || {}, ...headerTexts || {}, ...footerTexts || {} };
1308
+ if (Object.keys(merged).length > 0) {
1309
+ this.pageTexts = JSON.parse(JSON.stringify(merged));
1257
1310
  this.updateElementTexts();
1258
1311
  }
1259
- const images = page?.published_content?.images;
1260
- if (images && Object.keys(images).length > 0) {
1261
- this.pageImages = JSON.parse(JSON.stringify(images));
1262
- this.applyPageImages();
1312
+ const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=published&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1313
+ if (res.ok) {
1314
+ const page = await res.json();
1315
+ const images = page?.published_content?.images;
1316
+ if (images && Object.keys(images).length > 0) {
1317
+ this.pageImages = JSON.parse(JSON.stringify(images));
1318
+ this.applyPageImages();
1319
+ }
1263
1320
  }
1264
1321
  } catch {
1265
1322
  } finally {
@@ -1297,6 +1354,18 @@ var XtroedgeCMS = class _XtroedgeCMS {
1297
1354
  }
1298
1355
  }
1299
1356
  }
1357
+ // Group pageTexts by section slug based on managedElements info
1358
+ groupTextsBySection() {
1359
+ const sections = {};
1360
+ for (const [, info] of this.managedElements) {
1361
+ const slug = info.sectionSlug;
1362
+ if (!sections[slug]) sections[slug] = {};
1363
+ if (this.pageTexts[info.key] !== void 0) {
1364
+ sections[slug][info.key] = this.pageTexts[info.key];
1365
+ }
1366
+ }
1367
+ return sections;
1368
+ }
1300
1369
  async saveChanges() {
1301
1370
  if (!this.config.apiBase) {
1302
1371
  this.showToast("No API configured. Set apiBase to enable save.", "error");
@@ -1304,19 +1373,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
1304
1373
  }
1305
1374
  this.isSaving = true;
1306
1375
  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
1376
  try {
1314
- const res = await fetch(`${this.config.apiBase}/web_page/save`, {
1315
- method: "POST",
1316
- headers: { "Content-Type": "application/json" },
1317
- body: JSON.stringify(payload)
1318
- });
1319
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1377
+ const sections = this.groupTextsBySection();
1378
+ const requests = Object.entries(sections).map(
1379
+ ([slug, texts]) => fetch(`${this.config.apiBase}/web_page/save`, {
1380
+ method: "POST",
1381
+ headers: { "Content-Type": "application/json" },
1382
+ body: JSON.stringify({
1383
+ slug,
1384
+ title: slug === this.currentSlug ? this.currentTitle : slug.replace("/", ""),
1385
+ site_identifier: this.siteIdentifier,
1386
+ content: { texts, ...slug === this.currentSlug ? { images: this.pageImages } : {} }
1387
+ })
1388
+ })
1389
+ );
1390
+ const results = await Promise.all(requests);
1391
+ if (results.some((r) => !r.ok)) throw new Error("One or more sections failed to save");
1320
1392
  this.isSaving = false;
1321
1393
  this.resetAfterSave();
1322
1394
  this.updateUI();
@@ -1336,19 +1408,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
1336
1408
  }
1337
1409
  this.isPublishing = true;
1338
1410
  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
1411
  try {
1346
- const res = await fetch(`${this.config.apiBase}/web_page/publish`, {
1347
- method: "POST",
1348
- headers: { "Content-Type": "application/json" },
1349
- body: JSON.stringify(payload)
1350
- });
1351
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1412
+ const sections = this.groupTextsBySection();
1413
+ const requests = Object.entries(sections).map(
1414
+ ([slug, texts]) => fetch(`${this.config.apiBase}/web_page/publish`, {
1415
+ method: "POST",
1416
+ headers: { "Content-Type": "application/json" },
1417
+ body: JSON.stringify({
1418
+ slug,
1419
+ title: slug === this.currentSlug ? this.currentTitle : slug.replace("/", ""),
1420
+ site_identifier: this.siteIdentifier,
1421
+ published_content: { texts, ...slug === this.currentSlug ? { images: this.pageImages } : {} }
1422
+ })
1423
+ })
1424
+ );
1425
+ const results = await Promise.all(requests);
1426
+ if (results.some((r) => !r.ok)) throw new Error("One or more sections failed to publish");
1352
1427
  this.isPublishing = false;
1353
1428
  this.resetAfterSave();
1354
1429
  this.updateUI();