ngx-xtroedge-cms 1.3.0 → 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
@@ -277,7 +277,9 @@ var DEFAULT_EDITABLE_TAGS = [
277
277
  "blockquote",
278
278
  "td",
279
279
  "th",
280
- "div"
280
+ "div",
281
+ "header",
282
+ "footer"
281
283
  ];
282
284
  var XtroedgeCMS = class _XtroedgeCMS {
283
285
  constructor(config) {
@@ -751,7 +753,7 @@ var XtroedgeCMS = class _XtroedgeCMS {
751
753
  if (this.containerSelector) {
752
754
  return document.querySelector(this.containerSelector) || document.body;
753
755
  }
754
- return document.querySelector(".layout") || document.body;
756
+ return document.body;
755
757
  }
756
758
  autoDetectElements() {
757
759
  const container = this.resolveContainer();
@@ -759,7 +761,29 @@ var XtroedgeCMS = class _XtroedgeCMS {
759
761
  for (const el of this.autoDetectedElements) {
760
762
  if (!document.contains(el)) this.autoDetectedElements.delete(el);
761
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
+ };
762
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
+ };
763
787
  const selector = this.editableTags.join(",");
764
788
  const elements = container.querySelectorAll(selector);
765
789
  elements.forEach((el) => {
@@ -768,12 +792,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
768
792
  if (el.children.length > 3) return;
769
793
  const text = this.getDirectTextContent(el).trim();
770
794
  if (text.length < 2) return;
771
- const tag = el.tagName.toLowerCase();
772
- if (!tagCounters[tag]) tagCounters[tag] = 0;
773
- const key = `xcms_${tag}_${tagCounters[tag]}`;
774
- tagCounters[tag]++;
775
- el.setAttribute("data-cms", key);
776
- 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));
777
802
  });
778
803
  }
779
804
  scanDOM() {
@@ -844,8 +869,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
844
869
  const currentVal = this.getPageText(key);
845
870
  if (text !== currentVal) this.onTextChanged(key, text);
846
871
  };
847
- this.managedElements.set(el, { key, blurHandler, keydownHandler, inputHandler });
848
- 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);
849
879
  }
850
880
  detachElement(el) {
851
881
  const info = this.managedElements.get(el);
@@ -853,11 +883,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
853
883
  el.removeEventListener("blur", info.blurHandler);
854
884
  el.removeEventListener("keydown", info.keydownHandler);
855
885
  el.removeEventListener("input", info.inputHandler);
886
+ el.removeEventListener("click", info.clickHandler, true);
856
887
  el.removeAttribute("contenteditable");
857
888
  this.managedElements.delete(el);
858
889
  }
859
890
  }
860
- enableElementEdit(el, _key, blurH, keyH, inputH) {
891
+ enableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
861
892
  const val = this.getPageText(_key);
862
893
  if (val) el.textContent = val;
863
894
  el.setAttribute("contenteditable", "true");
@@ -871,8 +902,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
871
902
  el.addEventListener("blur", blurH);
872
903
  el.addEventListener("keydown", keyH);
873
904
  el.addEventListener("input", inputH);
905
+ el.addEventListener("click", clickH, true);
874
906
  }
875
- disableElementEdit(el, _key, blurH, keyH, inputH) {
907
+ disableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
876
908
  el.removeAttribute("contenteditable");
877
909
  el.style.borderBottom = "";
878
910
  el.style.padding = "";
@@ -886,11 +918,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
886
918
  el.removeEventListener("blur", blurH);
887
919
  el.removeEventListener("keydown", keyH);
888
920
  el.removeEventListener("input", inputH);
921
+ el.removeEventListener("click", clickH, true);
889
922
  }
890
923
  applyEditMode(editMode) {
891
924
  for (const [el, info] of this.managedElements) {
892
- if (editMode) this.enableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler);
893
- 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);
894
927
  }
895
928
  this.applyImageEditMode(editMode);
896
929
  }
@@ -1212,23 +1245,43 @@ var XtroedgeCMS = class _XtroedgeCMS {
1212
1245
  getPageSection() {
1213
1246
  return this.currentSlug.replace(/^\//, "").replace(/-/g, "_").toUpperCase();
1214
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
+ }
1215
1259
  async loadPageContent(status) {
1216
1260
  this.setLoading(true);
1217
1261
  this.cleanOldHistory();
1218
1262
  try {
1219
- const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1220
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1221
- const page = await res.json();
1222
- const texts = page?.published_content?.texts || page?.content?.texts;
1223
- if (texts) {
1224
- 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
+ };
1225
1274
  } else {
1226
1275
  this.buildDefaultTexts();
1227
1276
  }
1228
- const images = page?.published_content?.images || page?.content?.images;
1229
- if (images) {
1230
- this.pageImages = JSON.parse(JSON.stringify(images));
1231
- 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
+ }
1232
1285
  }
1233
1286
  this.originalTexts = JSON.parse(JSON.stringify(this.pageTexts));
1234
1287
  this.originalImages = JSON.parse(JSON.stringify(this.pageImages));
@@ -1246,18 +1299,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
1246
1299
  async loadPublishedContent() {
1247
1300
  this.setLoading(true);
1248
1301
  try {
1249
- const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=published&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1250
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1251
- const page = await res.json();
1252
- const texts = page?.published_content?.texts;
1253
- if (texts && Object.keys(texts).length > 0) {
1254
- 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));
1255
1310
  this.updateElementTexts();
1256
1311
  }
1257
- const images = page?.published_content?.images;
1258
- if (images && Object.keys(images).length > 0) {
1259
- this.pageImages = JSON.parse(JSON.stringify(images));
1260
- 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
+ }
1261
1320
  }
1262
1321
  } catch {
1263
1322
  } finally {
@@ -1295,6 +1354,18 @@ var XtroedgeCMS = class _XtroedgeCMS {
1295
1354
  }
1296
1355
  }
1297
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
+ }
1298
1369
  async saveChanges() {
1299
1370
  if (!this.config.apiBase) {
1300
1371
  this.showToast("No API configured. Set apiBase to enable save.", "error");
@@ -1302,19 +1373,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
1302
1373
  }
1303
1374
  this.isSaving = true;
1304
1375
  this.updateUI();
1305
- const payload = {
1306
- slug: this.currentSlug,
1307
- title: this.currentTitle,
1308
- site_identifier: this.siteIdentifier,
1309
- content: { texts: this.pageTexts, images: this.pageImages }
1310
- };
1311
1376
  try {
1312
- const res = await fetch(`${this.config.apiBase}/web_page/save`, {
1313
- method: "POST",
1314
- headers: { "Content-Type": "application/json" },
1315
- body: JSON.stringify(payload)
1316
- });
1317
- 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");
1318
1392
  this.isSaving = false;
1319
1393
  this.resetAfterSave();
1320
1394
  this.updateUI();
@@ -1334,19 +1408,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
1334
1408
  }
1335
1409
  this.isPublishing = true;
1336
1410
  this.updateUI();
1337
- const payload = {
1338
- slug: this.currentSlug,
1339
- title: this.currentTitle,
1340
- site_identifier: this.siteIdentifier,
1341
- published_content: { texts: this.pageTexts, images: this.pageImages }
1342
- };
1343
1411
  try {
1344
- const res = await fetch(`${this.config.apiBase}/web_page/publish`, {
1345
- method: "POST",
1346
- headers: { "Content-Type": "application/json" },
1347
- body: JSON.stringify(payload)
1348
- });
1349
- 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");
1350
1427
  this.isPublishing = false;
1351
1428
  this.resetAfterSave();
1352
1429
  this.updateUI();