@xiwebdesign/xicms-core 7.7.0 → 7.8.1
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/admin/AdminApp.js +3 -1
- package/dist/admin/AdminApp.js.map +1 -1
- package/dist/admin/lib/pagesClient.d.ts +13 -0
- package/dist/admin/lib/pagesClient.js +13 -0
- package/dist/admin/lib/pagesClient.js.map +1 -1
- package/dist/admin/shop/ProductPaneel.d.ts +15 -0
- package/dist/admin/shop/ProductPaneel.js +55 -0
- package/dist/admin/shop/ProductPaneel.js.map +1 -0
- package/dist/admin/shop/ProductsTab.d.ts +27 -0
- package/dist/admin/shop/ProductsTab.js +3 -2
- package/dist/admin/shop/ProductsTab.js.map +1 -1
- package/dist/admin/shop/productClient.d.ts +32 -0
- package/dist/admin/shop/productClient.js +31 -0
- package/dist/admin/shop/productClient.js.map +1 -0
- package/dist/admin/visual/VisualEditor.js +299 -44
- package/dist/admin/visual/VisualEditor.js.map +1 -1
- package/dist/admin/visual/wijzigingen.d.ts +46 -0
- package/dist/admin/visual/wijzigingen.js +78 -0
- package/dist/admin/visual/wijzigingen.js.map +1 -0
- package/dist/components/EditBar.d.ts +30 -0
- package/dist/components/EditBar.js +35 -7
- package/dist/components/EditBar.js.map +1 -1
- package/dist/components/barOffset.d.ts +23 -0
- package/dist/components/barOffset.js +31 -0
- package/dist/components/barOffset.js.map +1 -0
- package/dist/editor/EditorProvider.d.ts +5 -0
- package/dist/editor/EditorProvider.js +28 -11
- package/dist/editor/EditorProvider.js.map +1 -1
- package/dist/modules/shop/blocks/ProductDetail.js +3 -3
- package/dist/modules/shop/blocks/ProductDetail.js.map +1 -1
- package/dist/modules/shop/blocks/ProductGrid.js +5 -2
- package/dist/modules/shop/blocks/ProductGrid.js.map +1 -1
- package/dist/render/PublishedPage.js +13 -0
- package/dist/render/PublishedPage.js.map +1 -1
- package/dist/styles.css +166 -46
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/shop/productClient.ts"],"sourcesContent":["\"use client\";\n\nimport { doc, getDoc, onSnapshot, updateDoc } from \"firebase/firestore\";\nimport { getDb } from \"../../firebase/client\";\nimport type { Product } from \"../../schema/shop\";\n\nexport type StoredProduct = Product & { id: string };\n\n/**\n * Eén product, live — voor het paneel dat de bewerkmodus opent bij een productblok.\n *\n * Een abonnement en geen eenmalige lezing: hetzelfde product kan tegelijk openstaan in het\n * Webwinkel-paneel of in een tweede tabblad, en dan hoort dit paneel niet met een oude prijs te\n * blijven staan.\n */\nexport function subscribeProduct(\n siteId: string,\n productId: string,\n cb: (p: StoredProduct | null) => void,\n): () => void {\n const db = getDb();\n if (!db) {\n cb(null);\n return () => {};\n }\n return onSnapshot(doc(db, \"sites\", siteId, \"products\", productId), (snap) => {\n cb(snap.exists() ? ({ id: snap.id, ...(snap.data() as Product) }) : null);\n });\n}\n\n/**\n * Schrijf één veld van een product weg — de inline-tegenhanger van het volledige formulier.\n *\n * ── WAAROM DIT ANDERS IS DAN EEN TEKST OP EEN PAGINA ──────────────────────────────────────\n *\n * Een blokveld gaat naar de WERKVERSIE van die ene pagina en wordt pas zichtbaar als je\n * publiceert. Een product heeft geen werkversie: het staat in de productencollectie en wordt door\n * elke pagina gelezen die het toont. Wie hier de naam wijzigt, wijzigt hem dus METEEN en OVERAL —\n * op de cataloguspagina, in de zoekresultaten, op de bestelbevestiging.\n *\n * Dat verschil is niet weg te poetsen zonder een tweede publicatiestroom voor producten te bouwen,\n * en dat zou de webwinkel ingewikkelder maken dan hij is. Het staat daarom expliciet in de tekst\n * bij het potlood, zodat het een keuze is en geen verrassing.\n *\n * `updateDoc` en niet `setDoc`: een product heeft velden die dit pad niet kent (varianten,\n * voorraad, personalisatie). Een hele-document-schrijfactie zou die stilzwijgend wissen.\n */\nexport async function setProductField(\n siteId: string,\n productId: string,\n field: string,\n value: unknown,\n): Promise<void> {\n const db = getDb();\n if (!db) throw new Error(\"Firebase niet geconfigureerd.\");\n await updateDoc(doc(db, \"sites\", siteId, \"products\", productId), { [field]: value });\n}\n\n/** Eenmalige lezing — voor het moment waarop de editor moet weten wat er nú staat. */\nexport async function getProduct(siteId: string, productId: string): Promise<StoredProduct | null> {\n const db = getDb();\n if (!db) return null;\n const snap = await getDoc(doc(db, \"sites\", siteId, \"products\", productId));\n return snap.exists() ? ({ id: snap.id, ...(snap.data() as Product) }) : null;\n}\n"],"mappings":";AAEA,SAAS,KAAK,QAAQ,YAAY,iBAAiB;AACnD,SAAS,aAAa;AAYf,SAAS,iBACd,QACA,WACA,IACY;AACZ,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,IAAI;AACP,OAAG,IAAI;AACP,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,SAAO,WAAW,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,CAAC,SAAS;AAC3E,OAAG,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAAc,IAAK,IAAI;AAAA,EAC1E,CAAC;AACH;AAmBA,eAAsB,gBACpB,QACA,WACA,OACA,OACe;AACf,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,OAAM,IAAI,MAAM,+BAA+B;AACxD,QAAM,UAAU,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC;AACrF;AAGA,eAAsB,WAAW,QAAgB,WAAkD;AACjG,QAAM,KAAK,MAAM;AACjB,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,OAAO,MAAM,OAAO,IAAI,IAAI,SAAS,QAAQ,YAAY,SAAS,CAAC;AACzE,SAAO,KAAK,OAAO,IAAK,EAAE,IAAI,KAAK,IAAI,GAAI,KAAK,KAAK,EAAc,IAAK;AAC1E;","names":[]}
|
|
@@ -12,12 +12,17 @@ import { useNestedDialogOpen } from "../lib/useNestedDialog.js";
|
|
|
12
12
|
import { useThemeVars } from "../../XiCMSProvider.js";
|
|
13
13
|
import { buildBlockAddOptions } from "../../blocks/registry.js";
|
|
14
14
|
import DOMPurify from "dompurify";
|
|
15
|
-
import { createPage, getDraftByPath, saveDraft, publishPage, subscribeDraft, subscribeVersions, restoreVersion } from "../lib/pagesClient.js";
|
|
15
|
+
import { createPage, getDraftByPath, getPublishedPage, saveDraft, publishPage, subscribeDraft, subscribeVersions, restoreVersion } from "../lib/pagesClient.js";
|
|
16
16
|
import { isDemoActive } from "../../demo/demoStore.js";
|
|
17
|
+
import { setProductField } from "../shop/productClient.js";
|
|
18
|
+
import { ProductPaneel } from "../shop/ProductPaneel.js";
|
|
17
19
|
import { builtinBlockEditors, GenericEditor } from "../blockEditors/index.js";
|
|
18
20
|
import { StyleEditor } from "../blockEditors/StyleEditor.js";
|
|
19
21
|
import { siteLocales, parseLocaleSlugs, localizeBlocks } from "../../i18n/overlay.js";
|
|
20
22
|
import { useEditor } from "../../editor/EditorProvider.js";
|
|
23
|
+
import { AdminModes } from "../../components/EditBar.js";
|
|
24
|
+
import { useBalkOffset } from "../../components/barOffset.js";
|
|
25
|
+
import { aantalWijzigingen, bepaalWijzigingen, GEEN_WIJZIGINGEN } from "./wijzigingen.js";
|
|
21
26
|
import { TranslationPanel } from "./TranslationPanel.js";
|
|
22
27
|
import { PatternInserter } from "../patterns/PatternInserter.js";
|
|
23
28
|
import { Button, Field, Input, Textarea } from "../ui/primitives.js";
|
|
@@ -97,11 +102,14 @@ function VisualEditorInner({ onClose }) {
|
|
|
97
102
|
const [panelOpen, setPanelOpen] = useState(false);
|
|
98
103
|
const [status, setStatus] = useState("loading");
|
|
99
104
|
const [selectedKey, setSelectedKey] = useState(null);
|
|
105
|
+
const [selectedProduct, setSelectedProduct] = useState(null);
|
|
100
106
|
const [publishing, setPublishing] = useState(false);
|
|
101
107
|
const [imgPicker, setImgPicker] = useState(null);
|
|
102
108
|
const firstSave = useRef(true);
|
|
103
109
|
const canvasRef = useRef(null);
|
|
104
110
|
const [conflict, setConflict] = useState(false);
|
|
111
|
+
const [liveBlocks, setLiveBlocks] = useState(void 0);
|
|
112
|
+
useBalkOffset(true);
|
|
105
113
|
const { vars: themeVars, darkAuto } = useThemeVars();
|
|
106
114
|
const nestedDialogOpen = useNestedDialogOpen();
|
|
107
115
|
const { containerRef, focusFirst } = useFocusTrap({ isActive: !nestedDialogOpen });
|
|
@@ -215,6 +223,20 @@ function VisualEditorInner({ onClose }) {
|
|
|
215
223
|
active = false;
|
|
216
224
|
};
|
|
217
225
|
}, [siteId, path]);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
const id = draft?.id;
|
|
228
|
+
if (!id) {
|
|
229
|
+
setLiveBlocks(void 0);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
let active = true;
|
|
233
|
+
getPublishedPage(siteId, id).then((pagina) => {
|
|
234
|
+
if (active) setLiveBlocks(pagina?.blocks ?? null);
|
|
235
|
+
}).catch(() => active && setLiveBlocks(void 0));
|
|
236
|
+
return () => {
|
|
237
|
+
active = false;
|
|
238
|
+
};
|
|
239
|
+
}, [siteId, draft?.id]);
|
|
218
240
|
const handleCreatePage = useCallback(async () => {
|
|
219
241
|
setStatus("loading");
|
|
220
242
|
try {
|
|
@@ -254,6 +276,12 @@ function VisualEditorInner({ onClose }) {
|
|
|
254
276
|
() => isTranslating ? localizeBlocks(blocks, overlay) : blocks,
|
|
255
277
|
[blocks, isTranslating, overlay]
|
|
256
278
|
);
|
|
279
|
+
const nietLiveRef = useRef(false);
|
|
280
|
+
const wijzigingen = useMemo(
|
|
281
|
+
() => liveBlocks === void 0 ? GEEN_WIJZIGINGEN : bepaalWijzigingen(blocks, liveBlocks),
|
|
282
|
+
[blocks, liveBlocks]
|
|
283
|
+
);
|
|
284
|
+
nietLiveRef.current = wijzigingen.iets;
|
|
257
285
|
const { publishDraft } = useEditor();
|
|
258
286
|
const laatstGepubliceerd = useRef("");
|
|
259
287
|
useEffect(() => {
|
|
@@ -320,28 +348,41 @@ function VisualEditorInner({ onClose }) {
|
|
|
320
348
|
);
|
|
321
349
|
const registryRef = useRef(registry);
|
|
322
350
|
registryRef.current = registry;
|
|
323
|
-
|
|
351
|
+
function veiligeBovenkant() {
|
|
352
|
+
let onderkant = 0;
|
|
353
|
+
for (const el of Array.from(document.querySelectorAll("body *"))) {
|
|
354
|
+
const pos = getComputedStyle(el).position;
|
|
355
|
+
if (pos !== "fixed" && pos !== "sticky") continue;
|
|
356
|
+
const r = el.getBoundingClientRect();
|
|
357
|
+
if (r.height === 0 || r.top > 4) continue;
|
|
358
|
+
onderkant = Math.max(onderkant, r.bottom);
|
|
359
|
+
}
|
|
360
|
+
return onderkant;
|
|
361
|
+
}
|
|
362
|
+
const [blokknoppen, setBlokknoppen] = useState([]);
|
|
324
363
|
useEffect(() => {
|
|
325
364
|
if (!draft) {
|
|
326
|
-
|
|
365
|
+
setBlokknoppen((v) => v.length === 0 ? v : []);
|
|
327
366
|
return;
|
|
328
367
|
}
|
|
329
368
|
const meet = () => {
|
|
369
|
+
const veilig = veiligeBovenkant();
|
|
330
370
|
const gevonden = [];
|
|
331
371
|
for (const el of Array.from(document.querySelectorAll("[data-xicms-block]"))) {
|
|
332
372
|
const key = el.dataset.xicmsBlock;
|
|
333
373
|
if (!key) continue;
|
|
334
374
|
const r = el.getBoundingClientRect();
|
|
335
375
|
if (r.width === 0 && r.height === 0) continue;
|
|
376
|
+
if (r.bottom < veilig || r.top > window.innerHeight) continue;
|
|
336
377
|
const type = el.dataset.xicmsBlockType ?? "";
|
|
337
378
|
gevonden.push({
|
|
338
379
|
key,
|
|
339
|
-
top: Math.max(r.top + 8,
|
|
340
|
-
left: Math.max(r.right -
|
|
380
|
+
top: Math.max(r.top + 8, veilig + 8),
|
|
381
|
+
left: Math.max(r.right - 62, 8),
|
|
341
382
|
label: registryRef.current.get(type)?.label ?? type
|
|
342
383
|
});
|
|
343
384
|
}
|
|
344
|
-
|
|
385
|
+
setBlokknoppen((vorige) => {
|
|
345
386
|
if (vorige.length === gevonden.length && vorige.every((v, i) => v.key === gevonden[i].key && v.top === gevonden[i].top && v.left === gevonden[i].left)) {
|
|
346
387
|
return vorige;
|
|
347
388
|
}
|
|
@@ -356,9 +397,117 @@ function VisualEditorInner({ onClose }) {
|
|
|
356
397
|
window.removeEventListener("resize", meet);
|
|
357
398
|
};
|
|
358
399
|
}, [draft, displayBlocks, registryRef]);
|
|
400
|
+
const [productKnop, setProductKnop] = useState(null);
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (!draft) return;
|
|
403
|
+
const onOver = (e) => {
|
|
404
|
+
const doel = e.target?.closest?.("[data-xicms-product]");
|
|
405
|
+
if (!doel) {
|
|
406
|
+
const naarKnop = e.target?.closest?.(".xicms-ve-blokknoppen");
|
|
407
|
+
if (!naarKnop) setProductKnop(null);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const id = doel.dataset.xicmsProduct;
|
|
411
|
+
if (!id) return;
|
|
412
|
+
const r = doel.getBoundingClientRect();
|
|
413
|
+
setProductKnop(
|
|
414
|
+
(v) => v && v.id === id && v.top === Math.max(r.top + 8, 44) && v.left === Math.max(r.right - 62, 8) ? v : { id, top: Math.max(r.top + 8, 44), left: Math.max(r.right - 62, 8) }
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
document.addEventListener("mouseover", onOver, true);
|
|
418
|
+
return () => document.removeEventListener("mouseover", onOver, true);
|
|
419
|
+
}, [draft]);
|
|
420
|
+
const productBewerkRef = useRef(null);
|
|
421
|
+
const startProductInline = useCallback(
|
|
422
|
+
(productId) => {
|
|
423
|
+
const host = document.querySelector(`[data-xicms-product="${CSS.escape(productId)}"]`);
|
|
424
|
+
const el = host?.querySelector("[data-xicms-product-edit]");
|
|
425
|
+
if (!el) {
|
|
426
|
+
toast({
|
|
427
|
+
title: "Hier valt niets te typen",
|
|
428
|
+
description: "Gebruik het tandwiel voor prijs, foto's, varianten en voorraad."
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const veld = el.dataset.xicmsProductEdit;
|
|
433
|
+
if (!veld) return;
|
|
434
|
+
const origineel = el.textContent ?? "";
|
|
435
|
+
el.setAttribute("contenteditable", "true");
|
|
436
|
+
el.setAttribute("spellcheck", "true");
|
|
437
|
+
el.classList.add("is-editing");
|
|
438
|
+
productBewerkRef.current = el;
|
|
439
|
+
const stop = () => {
|
|
440
|
+
el.removeAttribute("contenteditable");
|
|
441
|
+
el.classList.remove("is-editing");
|
|
442
|
+
el.removeEventListener("blur", opBlur);
|
|
443
|
+
el.removeEventListener("keydown", opToets);
|
|
444
|
+
if (productBewerkRef.current === el) productBewerkRef.current = null;
|
|
445
|
+
};
|
|
446
|
+
const opBlur = () => {
|
|
447
|
+
const waarde = el.textContent ?? "";
|
|
448
|
+
stop();
|
|
449
|
+
if (waarde === origineel) return;
|
|
450
|
+
void setProductField(siteId, productId, veld, waarde).then(
|
|
451
|
+
() => toast({
|
|
452
|
+
title: "Product bijgewerkt",
|
|
453
|
+
description: "Producten hebben geen werkversie: dit staat meteen live, op elke pagina.",
|
|
454
|
+
tone: "success"
|
|
455
|
+
})
|
|
456
|
+
).catch((err) => {
|
|
457
|
+
el.textContent = origineel;
|
|
458
|
+
toast({ title: "Opslaan mislukt", description: err.message, tone: "error" });
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
const opToets = (ev) => {
|
|
462
|
+
if (ev.key === "Escape") {
|
|
463
|
+
ev.stopPropagation();
|
|
464
|
+
el.textContent = origineel;
|
|
465
|
+
el.blur();
|
|
466
|
+
} else if (ev.key === "Enter") {
|
|
467
|
+
ev.preventDefault();
|
|
468
|
+
el.blur();
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
el.addEventListener("blur", opBlur);
|
|
472
|
+
el.addEventListener("keydown", opToets);
|
|
473
|
+
el.focus();
|
|
474
|
+
const selection = window.getSelection();
|
|
475
|
+
const range = document.createRange();
|
|
476
|
+
range.selectNodeContents(el);
|
|
477
|
+
range.collapse(false);
|
|
478
|
+
selection?.removeAllRanges();
|
|
479
|
+
selection?.addRange(range);
|
|
480
|
+
},
|
|
481
|
+
[siteId, toast]
|
|
482
|
+
);
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (!draft) return;
|
|
485
|
+
const gemarkeerd = [];
|
|
486
|
+
const markeer = (el, klasse) => {
|
|
487
|
+
if (!el) return;
|
|
488
|
+
el.classList.add(klasse);
|
|
489
|
+
gemarkeerd.push(el);
|
|
490
|
+
};
|
|
491
|
+
for (const key of wijzigingen.blokken) {
|
|
492
|
+
markeer(document.querySelector(`[data-xicms-block="${CSS.escape(key)}"]`), "xicms-ve-nieuw");
|
|
493
|
+
}
|
|
494
|
+
for (const [key, velden] of wijzigingen.velden) {
|
|
495
|
+
const host = document.querySelector(`[data-xicms-block="${CSS.escape(key)}"]`);
|
|
496
|
+
if (!host) continue;
|
|
497
|
+
for (const veld of velden) {
|
|
498
|
+
host.querySelectorAll(`[data-xicms-edit="${CSS.escape(veld)}"]`).forEach((el) => markeer(el, "xicms-ve-gewijzigd"));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return () => {
|
|
502
|
+
for (const el of gemarkeerd) {
|
|
503
|
+
el.classList.remove("xicms-ve-nieuw", "xicms-ve-gewijzigd");
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
}, [draft, wijzigingen, displayBlocks]);
|
|
359
507
|
const [spots, setSpots] = useState([]);
|
|
360
508
|
const targetsRef = useRef([]);
|
|
361
509
|
const editingRef = useRef(null);
|
|
510
|
+
const wilInlineRef = useRef(false);
|
|
362
511
|
const stopInline = useCallback((el) => {
|
|
363
512
|
el.removeAttribute("contenteditable");
|
|
364
513
|
el.classList.remove("is-editing");
|
|
@@ -420,8 +569,10 @@ function VisualEditorInner({ onClose }) {
|
|
|
420
569
|
});
|
|
421
570
|
}
|
|
422
571
|
el.addEventListener("keydown", (e) => {
|
|
423
|
-
if (e.key === "Escape")
|
|
424
|
-
|
|
572
|
+
if (e.key === "Escape") {
|
|
573
|
+
e.stopPropagation();
|
|
574
|
+
el.blur();
|
|
575
|
+
} else if (!html && e.key === "Enter") {
|
|
425
576
|
e.preventDefault();
|
|
426
577
|
el.blur();
|
|
427
578
|
}
|
|
@@ -454,6 +605,16 @@ function VisualEditorInner({ onClose }) {
|
|
|
454
605
|
measure();
|
|
455
606
|
};
|
|
456
607
|
apply();
|
|
608
|
+
if (wilInlineRef.current) {
|
|
609
|
+
wilInlineRef.current = false;
|
|
610
|
+
if (targetsRef.current.length > 0) startInline(0);
|
|
611
|
+
else {
|
|
612
|
+
toast({
|
|
613
|
+
title: "Dit blok heeft geen eigen tekst",
|
|
614
|
+
description: "Wijs een product aan voor zijn naam, of gebruik het tandwiel voor de instellingen van dit blok."
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
457
618
|
const obs = new MutationObserver(() => {
|
|
458
619
|
if (!draggingRef.current) apply();
|
|
459
620
|
});
|
|
@@ -478,7 +639,7 @@ function VisualEditorInner({ onClose }) {
|
|
|
478
639
|
targetsRef.current = [];
|
|
479
640
|
setSpots([]);
|
|
480
641
|
};
|
|
481
|
-
}, [selectedKey, commitText, positionFmtBar, stopInline]);
|
|
642
|
+
}, [selectedKey, commitText, positionFmtBar, stopInline, startInline, toast]);
|
|
482
643
|
const dirtySinceRef = useRef(null);
|
|
483
644
|
useEffect(() => {
|
|
484
645
|
if (!draft) return;
|
|
@@ -507,7 +668,7 @@ function VisualEditorInner({ onClose }) {
|
|
|
507
668
|
}, [draft, siteId, conflict]);
|
|
508
669
|
useEffect(() => {
|
|
509
670
|
const beforeUnload = (e) => {
|
|
510
|
-
if (dirtyRef.current) {
|
|
671
|
+
if (dirtyRef.current || nietLiveRef.current) {
|
|
511
672
|
e.preventDefault();
|
|
512
673
|
e.returnValue = "";
|
|
513
674
|
}
|
|
@@ -647,13 +808,25 @@ function VisualEditorInner({ onClose }) {
|
|
|
647
808
|
}
|
|
648
809
|
setConflict(false);
|
|
649
810
|
}, [siteId, path, mutateDraft]);
|
|
650
|
-
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "xicms-ve__shell", role: "group", "aria-label": `Pagina bewerken: ${path}`, children: [
|
|
651
|
-
/* @__PURE__ */ jsxs("header", { className: "xicms-ve__bar", children: [
|
|
652
|
-
/* @__PURE__ */
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
811
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "xicms-ve__shell", "data-xicms-tooling": "", role: "group", "aria-label": `Pagina bewerken: ${path}`, children: [
|
|
812
|
+
/* @__PURE__ */ jsxs("header", { className: "xicms-ve__bar", role: "toolbar", "aria-label": "Beheer", children: [
|
|
813
|
+
/* @__PURE__ */ jsx("span", { className: "xicms-editbar__brand", children: isDemoActive() ? "XiCMS \xB7 demo" : "XiCMS" }),
|
|
814
|
+
/* @__PURE__ */ jsx(
|
|
815
|
+
AdminModes,
|
|
816
|
+
{
|
|
817
|
+
actief: "bewerken",
|
|
818
|
+
voorSwitch: (m) => {
|
|
819
|
+
if (m === "live" && nietLiveRef.current) {
|
|
820
|
+
toast({
|
|
821
|
+
title: "Nog niet gepubliceerd",
|
|
822
|
+
description: `${aantalWijzigingen(wijzigingen)} blijft bewaard als werkversie. Je ziet ze weer zodra je terug naar Bewerken gaat.`
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
),
|
|
828
|
+
/* @__PURE__ */ jsx("span", { className: "xicms-ve__sep", "aria-hidden": "true" }),
|
|
829
|
+
/* @__PURE__ */ jsx("span", { className: "xicms-ve__path", title: `Je bewerkt ${name} \u2014 ${path}`, children: path }),
|
|
657
830
|
localeCfg.locales.length > 1 && /* @__PURE__ */ jsxs("span", { className: "xicms-ve__locale", title: "Taal \u2014 in een andere taal dan de standaard bewerk je vertalingen", children: [
|
|
658
831
|
"\u{1F310}",
|
|
659
832
|
/* @__PURE__ */ jsx(
|
|
@@ -695,13 +868,9 @@ function VisualEditorInner({ onClose }) {
|
|
|
695
868
|
] }),
|
|
696
869
|
/* @__PURE__ */ jsxs("span", { className: "xicms-ve__status", "aria-live": "polite", children: [
|
|
697
870
|
status === "loading" ? "Werkversie laden\u2026" : null,
|
|
698
|
-
status === "saving" ? "Opslaan\u2026" : status === "saved" ? "\u2713
|
|
871
|
+
status === "saving" ? "Opslaan\u2026" : status === "saved" ? "\u2713 Bewaard als werkversie" : status === "error" ? "\u26A0 Niet opgeslagen" : ""
|
|
699
872
|
] }),
|
|
700
|
-
status === "error" && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: () => void flushSave().catch(() => setStatus("error")), children: "Opnieuw opslaan" })
|
|
701
|
-
/* @__PURE__ */ jsxs("div", { className: "xicms-ve__bar-actions", children: [
|
|
702
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", disabled: publishing || !draft, onClick: onPublish, children: publishing ? "Bezig\u2026" : "Publiceren" }),
|
|
703
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: handleClose, children: "Sluiten" })
|
|
704
|
-
] })
|
|
873
|
+
status === "error" && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "secondary", onClick: () => void flushSave().catch(() => setStatus("error")), children: "Opnieuw opslaan" })
|
|
705
874
|
] }),
|
|
706
875
|
conflict && /* @__PURE__ */ jsxs("div", { className: "xicms-ve__conflict", role: "alert", children: [
|
|
707
876
|
/* @__PURE__ */ jsx("span", { children: "\u26A0 Deze pagina is intussen ergens anders gewijzigd (andere tab of collega). Doorwerken kan die wijzigingen overschrijven." }),
|
|
@@ -718,7 +887,16 @@ function VisualEditorInner({ onClose }) {
|
|
|
718
887
|
/* @__PURE__ */ jsx("div", { className: "xicms-ve__inserter-select", children: /* @__PURE__ */ jsx(Select, { value: "", placeholder: "Kies een blok\u2026", onValueChange: (v) => v && addBlock(v), options: addOptions }) }),
|
|
719
888
|
/* @__PURE__ */ jsx(PatternInserter, { onInsert: insertAfterSelected })
|
|
720
889
|
] }),
|
|
721
|
-
|
|
890
|
+
selectedProduct ? /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
891
|
+
/* @__PURE__ */ jsxs("div", { className: "xicms-ve__panel-head", children: [
|
|
892
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-slate-500", children: "Webwinkel" }),
|
|
893
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
894
|
+
setSelectedProduct(null);
|
|
895
|
+
setPanelOpen(false);
|
|
896
|
+
}, children: "\u2715 Sluiten" })
|
|
897
|
+
] }),
|
|
898
|
+
/* @__PURE__ */ jsx(ProductPaneel, { siteId, productId: selectedProduct })
|
|
899
|
+
] }) : selected && isTranslating ? /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
|
|
722
900
|
/* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold", children: [
|
|
723
901
|
registry.get(selected._type)?.label ?? selected._type,
|
|
724
902
|
" \u2014 vertaling (",
|
|
@@ -792,26 +970,46 @@ function VisualEditorInner({ onClose }) {
|
|
|
792
970
|
}
|
|
793
971
|
}
|
|
794
972
|
),
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
/* @__PURE__ */
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
973
|
+
blokknoppen.map((b) => /* @__PURE__ */ jsxs("div", { className: "xicms-ve-blokknoppen", style: { top: b.top, left: b.left }, children: [
|
|
974
|
+
/* @__PURE__ */ jsx(
|
|
975
|
+
"button",
|
|
976
|
+
{
|
|
977
|
+
type: "button",
|
|
978
|
+
className: `xicms-ve-blokknop${b.key === selectedKey ? " is-on" : ""}`,
|
|
979
|
+
title: `Tekst van ${b.label} bewerken`,
|
|
980
|
+
"aria-label": `Tekst van ${b.label} bewerken`,
|
|
981
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
982
|
+
onClick: () => {
|
|
983
|
+
wilInlineRef.current = true;
|
|
984
|
+
setSelectedProduct(null);
|
|
985
|
+
setSelectedKey(b.key);
|
|
986
|
+
},
|
|
987
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
988
|
+
/* @__PURE__ */ jsx("path", { d: "M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z" }),
|
|
989
|
+
/* @__PURE__ */ jsx("path", { d: "M14.5 6.5l3 3" })
|
|
990
|
+
] })
|
|
991
|
+
}
|
|
992
|
+
),
|
|
993
|
+
/* @__PURE__ */ jsx(
|
|
994
|
+
"button",
|
|
995
|
+
{
|
|
996
|
+
type: "button",
|
|
997
|
+
className: `xicms-ve-blokknop${b.key === selectedKey && panelOpen ? " is-on" : ""}`,
|
|
998
|
+
title: `Instellingen van ${b.label}`,
|
|
999
|
+
"aria-label": `Instellingen van ${b.label}`,
|
|
1000
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1001
|
+
onClick: () => {
|
|
1002
|
+
setSelectedProduct(null);
|
|
1003
|
+
setSelectedKey(b.key);
|
|
1004
|
+
setPanelOpen(true);
|
|
1005
|
+
},
|
|
1006
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
1007
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
1008
|
+
/* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })
|
|
1009
|
+
] })
|
|
1010
|
+
}
|
|
1011
|
+
)
|
|
1012
|
+
] }, b.key)),
|
|
815
1013
|
spots.map((s, i) => /* @__PURE__ */ jsx(
|
|
816
1014
|
"button",
|
|
817
1015
|
{
|
|
@@ -831,6 +1029,63 @@ function VisualEditorInner({ onClose }) {
|
|
|
831
1029
|
},
|
|
832
1030
|
i
|
|
833
1031
|
)),
|
|
1032
|
+
productKnop && /* @__PURE__ */ jsxs(
|
|
1033
|
+
"div",
|
|
1034
|
+
{
|
|
1035
|
+
className: "xicms-ve-blokknoppen is-product",
|
|
1036
|
+
style: { top: productKnop.top, left: productKnop.left },
|
|
1037
|
+
children: [
|
|
1038
|
+
/* @__PURE__ */ jsx(
|
|
1039
|
+
"button",
|
|
1040
|
+
{
|
|
1041
|
+
type: "button",
|
|
1042
|
+
className: "xicms-ve-blokknop",
|
|
1043
|
+
title: "Productnaam bewerken (staat meteen live)",
|
|
1044
|
+
"aria-label": "Productnaam bewerken",
|
|
1045
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1046
|
+
onClick: () => startProductInline(productKnop.id),
|
|
1047
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
1048
|
+
/* @__PURE__ */ jsx("path", { d: "M4 20h4L19.5 8.5a2.1 2.1 0 0 0-3-3L5 17v3z" }),
|
|
1049
|
+
/* @__PURE__ */ jsx("path", { d: "M14.5 6.5l3 3" })
|
|
1050
|
+
] })
|
|
1051
|
+
}
|
|
1052
|
+
),
|
|
1053
|
+
/* @__PURE__ */ jsx(
|
|
1054
|
+
"button",
|
|
1055
|
+
{
|
|
1056
|
+
type: "button",
|
|
1057
|
+
className: `xicms-ve-blokknop${selectedProduct === productKnop.id ? " is-on" : ""}`,
|
|
1058
|
+
title: "Product bewerken: prijs, foto's, varianten, voorraad",
|
|
1059
|
+
"aria-label": "Product bewerken",
|
|
1060
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1061
|
+
onClick: () => {
|
|
1062
|
+
setSelectedKey(null);
|
|
1063
|
+
setSelectedProduct(productKnop.id);
|
|
1064
|
+
setPanelOpen(true);
|
|
1065
|
+
},
|
|
1066
|
+
children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
1067
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
1068
|
+
/* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.6a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })
|
|
1069
|
+
] })
|
|
1070
|
+
}
|
|
1071
|
+
)
|
|
1072
|
+
]
|
|
1073
|
+
}
|
|
1074
|
+
),
|
|
1075
|
+
draft && /* @__PURE__ */ jsxs(
|
|
1076
|
+
"button",
|
|
1077
|
+
{
|
|
1078
|
+
type: "button",
|
|
1079
|
+
className: `xicms-ve-publiceer${wijzigingen.iets ? " is-klaar" : ""}`,
|
|
1080
|
+
disabled: publishing || !wijzigingen.iets,
|
|
1081
|
+
onClick: () => void onPublish(),
|
|
1082
|
+
title: wijzigingen.iets ? "Zet deze wijzigingen op de live site" : "Er is niets veranderd sinds de laatste publicatie",
|
|
1083
|
+
children: [
|
|
1084
|
+
/* @__PURE__ */ jsx("span", { className: "xicms-ve-publiceer__label", children: publishing ? "Bezig\u2026" : "Publiceren" }),
|
|
1085
|
+
/* @__PURE__ */ jsx("span", { className: "xicms-ve-publiceer__sub", children: wijzigingen.iets ? aantalWijzigingen(wijzigingen) : "niets gewijzigd" })
|
|
1086
|
+
]
|
|
1087
|
+
}
|
|
1088
|
+
),
|
|
834
1089
|
fmtBar && /* @__PURE__ */ jsxs("div", { className: "xicms-ve-fmtbar", style: { top: fmtBar.top, left: fmtBar.left }, role: "toolbar", "aria-label": "Tekstopmaak", children: [
|
|
835
1090
|
/* @__PURE__ */ jsx(FmtBtn, { label: /* @__PURE__ */ jsx("b", { children: "B" }), title: "Vet (Ctrl/Cmd+B)", onRun: () => document.execCommand("bold") }),
|
|
836
1091
|
/* @__PURE__ */ jsx(FmtBtn, { label: /* @__PURE__ */ jsx("i", { children: "I" }), title: "Cursief (Ctrl/Cmd+I)", onRun: () => document.execCommand("italic") }),
|