@ohhwells/bridge 0.1.94 → 0.1.96
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.cjs +443 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +443 -274
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -70,6 +70,9 @@ function isRenderableTree(value) {
|
|
|
70
70
|
// src/lib/ai-sections-store.ts
|
|
71
71
|
var AI_SECTIONS_KEY = "__ohw_ai_sections";
|
|
72
72
|
var AI_SLOT_KEY_PREFIX = "ai.";
|
|
73
|
+
function aiSlotKeyPrefixFor(sectionId) {
|
|
74
|
+
return `${AI_SLOT_KEY_PREFIX}${sectionId}.`;
|
|
75
|
+
}
|
|
73
76
|
var EMPTY_AI_SECTIONS = { v: 1, sections: [] };
|
|
74
77
|
function parseAiSectionsState(raw) {
|
|
75
78
|
if (!raw) return EMPTY_AI_SECTIONS;
|
|
@@ -181,6 +184,33 @@ function deleteSectionFromState(state, sectionId) {
|
|
|
181
184
|
if (removed.includes(sectionId)) return state;
|
|
182
185
|
return { ...state, v: 1, sections: state.sections, removed: [...removed, sectionId] };
|
|
183
186
|
}
|
|
187
|
+
function reapRemovedAiSections(state, store, removedIds, excludeIds = /* @__PURE__ */ new Set()) {
|
|
188
|
+
const generated = new Set(state.sections.map((entry) => entry.id));
|
|
189
|
+
const reapedIds = [...new Set(removedIds)].filter((id) => generated.has(id) && !excludeIds.has(id));
|
|
190
|
+
if (reapedIds.length === 0) {
|
|
191
|
+
return { state, store, reapedIds: [], slotPrefixes: [], changed: false };
|
|
192
|
+
}
|
|
193
|
+
const reaped = new Set(reapedIds);
|
|
194
|
+
const slotPrefixes = reapedIds.map(aiSlotKeyPrefixFor);
|
|
195
|
+
const nextState = {
|
|
196
|
+
...state,
|
|
197
|
+
v: 1,
|
|
198
|
+
sections: state.sections.filter((entry) => !reaped.has(entry.id))
|
|
199
|
+
};
|
|
200
|
+
let nextStore = store;
|
|
201
|
+
if (store) {
|
|
202
|
+
const sections = {};
|
|
203
|
+
for (const [key, override] of Object.entries(store.sections)) {
|
|
204
|
+
if (!reaped.has(key)) sections[key] = override;
|
|
205
|
+
}
|
|
206
|
+
const nodes = {};
|
|
207
|
+
for (const [key, override] of Object.entries(store.nodes)) {
|
|
208
|
+
if (!slotPrefixes.some((prefix) => key.startsWith(prefix))) nodes[key] = override;
|
|
209
|
+
}
|
|
210
|
+
nextStore = { v: 1, sections, nodes };
|
|
211
|
+
}
|
|
212
|
+
return { state: nextState, store: nextStore, reapedIds, slotPrefixes, changed: true };
|
|
213
|
+
}
|
|
184
214
|
|
|
185
215
|
// src/lib/brand-chrome.ts
|
|
186
216
|
var BRAND_NAME_KEY = "__ohw_brand_name";
|
|
@@ -559,6 +589,254 @@ function applyStylesToDom(store) {
|
|
|
559
589
|
import { flushSync } from "react-dom";
|
|
560
590
|
import { createRoot } from "react-dom/client";
|
|
561
591
|
|
|
592
|
+
// src/lib/sections.ts
|
|
593
|
+
var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
|
|
594
|
+
function isChromeSection(el) {
|
|
595
|
+
return el.matches("header, nav, footer, aside");
|
|
596
|
+
}
|
|
597
|
+
function titleCaseSectionId(id) {
|
|
598
|
+
return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
599
|
+
}
|
|
600
|
+
function parseSectionsFromRoot(root) {
|
|
601
|
+
const seen = /* @__PURE__ */ new Set();
|
|
602
|
+
const sections = [];
|
|
603
|
+
for (const el of root.querySelectorAll("[data-ohw-section]")) {
|
|
604
|
+
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
605
|
+
if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
|
|
606
|
+
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
607
|
+
if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
|
|
608
|
+
continue;
|
|
609
|
+
seen.add(id);
|
|
610
|
+
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
611
|
+
sections.push({ id, label });
|
|
612
|
+
}
|
|
613
|
+
return sections;
|
|
614
|
+
}
|
|
615
|
+
function collectSectionsFromDom() {
|
|
616
|
+
if (typeof document === "undefined") return [];
|
|
617
|
+
return parseSectionsFromRoot(document);
|
|
618
|
+
}
|
|
619
|
+
function parseSectionsFromHtml(html) {
|
|
620
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
621
|
+
return parseSectionsFromRoot(doc);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/lib/section-instances.ts
|
|
625
|
+
var SECTION_ORDER_KEY = "__ohw_section_order";
|
|
626
|
+
var REMOVED_ATTR = "data-ohw-section-removed";
|
|
627
|
+
function isRemovedSection(el) {
|
|
628
|
+
return el.hasAttribute(REMOVED_ATTR);
|
|
629
|
+
}
|
|
630
|
+
function movableUnit(el) {
|
|
631
|
+
return el.closest("[data-ohw-section-container]") ?? el;
|
|
632
|
+
}
|
|
633
|
+
function sectionTypeOf(el) {
|
|
634
|
+
return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
|
|
635
|
+
}
|
|
636
|
+
function sectionElementOf(el) {
|
|
637
|
+
return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
|
|
638
|
+
}
|
|
639
|
+
function collectTopLevelUnits(predicate) {
|
|
640
|
+
const seen = /* @__PURE__ */ new Set();
|
|
641
|
+
const result = [];
|
|
642
|
+
document.querySelectorAll("[data-ohw-section]").forEach((el) => {
|
|
643
|
+
if (!predicate(el)) return;
|
|
644
|
+
const unit = movableUnit(el);
|
|
645
|
+
if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
|
|
646
|
+
if (seen.has(unit)) return;
|
|
647
|
+
seen.add(unit);
|
|
648
|
+
result.push(unit);
|
|
649
|
+
});
|
|
650
|
+
return result;
|
|
651
|
+
}
|
|
652
|
+
function topLevelSections() {
|
|
653
|
+
return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
|
|
654
|
+
}
|
|
655
|
+
function instanceIdOf(el) {
|
|
656
|
+
return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
|
|
657
|
+
}
|
|
658
|
+
function findByInstanceId(instanceId) {
|
|
659
|
+
const escapedId = CSS.escape(instanceId);
|
|
660
|
+
const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
|
|
661
|
+
if (direct) return movableUnit(direct);
|
|
662
|
+
const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
|
|
663
|
+
return bare ? movableUnit(bare) : null;
|
|
664
|
+
}
|
|
665
|
+
function planSectionMove(instanceId, targetIndex, currentPath) {
|
|
666
|
+
const sections = topLevelSections();
|
|
667
|
+
const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
|
|
668
|
+
if (index === -1) return null;
|
|
669
|
+
const dragged = sections[index];
|
|
670
|
+
const others = sections.filter((_, i) => i !== index);
|
|
671
|
+
const clamped = Math.max(0, Math.min(targetIndex, others.length));
|
|
672
|
+
const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
|
|
673
|
+
return reordered.map((el, order) => ({
|
|
674
|
+
instanceId: instanceIdOf(el),
|
|
675
|
+
type: sectionTypeOf(el),
|
|
676
|
+
order,
|
|
677
|
+
pagePath: currentPath
|
|
678
|
+
}));
|
|
679
|
+
}
|
|
680
|
+
function moveSectionInstance(instanceId, direction, currentPath) {
|
|
681
|
+
const sections = topLevelSections();
|
|
682
|
+
const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
|
|
683
|
+
if (index === -1) return null;
|
|
684
|
+
const siblingIndex = direction === "up" ? index - 1 : index + 1;
|
|
685
|
+
if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
|
|
686
|
+
const entries = planSectionMove(instanceId, siblingIndex, currentPath);
|
|
687
|
+
if (!entries) return null;
|
|
688
|
+
applyPersistedOrder(entries);
|
|
689
|
+
return entries;
|
|
690
|
+
}
|
|
691
|
+
function syncRemovedFlags(entries) {
|
|
692
|
+
const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
|
|
693
|
+
document.querySelectorAll(`[${REMOVED_ATTR}]`).forEach((el) => {
|
|
694
|
+
if (!removedIds.has(instanceIdOf(el))) {
|
|
695
|
+
el.style.removeProperty("display");
|
|
696
|
+
el.removeAttribute(REMOVED_ATTR);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
for (const id of removedIds) {
|
|
700
|
+
const el = findByInstanceId(id);
|
|
701
|
+
if (el) {
|
|
702
|
+
el.style.display = "none";
|
|
703
|
+
el.setAttribute(REMOVED_ATTR, "");
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function applyPersistedOrder(entries) {
|
|
708
|
+
syncRemovedFlags(entries);
|
|
709
|
+
if (entries.length === 0) return;
|
|
710
|
+
const sections = topLevelSections();
|
|
711
|
+
if (sections.length === 0) return;
|
|
712
|
+
const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
|
|
713
|
+
const ordered = [...sections].sort((a, b) => {
|
|
714
|
+
const aOrder = orderIndex.get(instanceIdOf(a));
|
|
715
|
+
const bOrder = orderIndex.get(instanceIdOf(b));
|
|
716
|
+
if (aOrder === void 0 && bOrder === void 0) return 0;
|
|
717
|
+
if (aOrder === void 0) return 1;
|
|
718
|
+
if (bOrder === void 0) return -1;
|
|
719
|
+
return aOrder - bOrder;
|
|
720
|
+
});
|
|
721
|
+
let prev = null;
|
|
722
|
+
for (const el of ordered) {
|
|
723
|
+
if (prev) prev.after(el);
|
|
724
|
+
prev = el;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
|
|
728
|
+
if (!findByInstanceId(instanceId)) return null;
|
|
729
|
+
const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
|
|
730
|
+
const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
|
|
731
|
+
allSections.forEach((el, order) => {
|
|
732
|
+
const id = instanceIdOf(el);
|
|
733
|
+
if (!byId.has(id)) {
|
|
734
|
+
byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
const target = byId.get(instanceId);
|
|
738
|
+
if (!target) return null;
|
|
739
|
+
byId.set(instanceId, { ...target, removed });
|
|
740
|
+
const entries = Array.from(byId.values());
|
|
741
|
+
applyPersistedOrder(entries);
|
|
742
|
+
return entries;
|
|
743
|
+
}
|
|
744
|
+
function deleteSectionInstance(instanceId, currentPath, existingEntries) {
|
|
745
|
+
return setSectionRemoved(instanceId, currentPath, existingEntries, true);
|
|
746
|
+
}
|
|
747
|
+
function restoreSectionInstance(instanceId, currentPath, existingEntries) {
|
|
748
|
+
return setSectionRemoved(instanceId, currentPath, existingEntries, false);
|
|
749
|
+
}
|
|
750
|
+
function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
|
|
751
|
+
const original = findByInstanceId(instanceId);
|
|
752
|
+
if (!original) return null;
|
|
753
|
+
const clone = original.cloneNode(true);
|
|
754
|
+
clone.setAttribute("data-ohw-instance", newId);
|
|
755
|
+
const keyRekeys = rekeySectionSubtree(clone, newId);
|
|
756
|
+
original.insertAdjacentElement("afterend", clone);
|
|
757
|
+
const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
|
|
758
|
+
const entries = topLevelSections().map((el, order) => {
|
|
759
|
+
const id = instanceIdOf(el);
|
|
760
|
+
return {
|
|
761
|
+
instanceId: id,
|
|
762
|
+
type: sectionTypeOf(el),
|
|
763
|
+
order,
|
|
764
|
+
pagePath: currentPath,
|
|
765
|
+
...byId.get(id)?.removed ? { removed: true } : {}
|
|
766
|
+
};
|
|
767
|
+
});
|
|
768
|
+
applyPersistedOrder(entries);
|
|
769
|
+
return { entries, keyRekeys };
|
|
770
|
+
}
|
|
771
|
+
function newInstanceId() {
|
|
772
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
773
|
+
}
|
|
774
|
+
function getPageSectionOrderEntries(raw, currentPath) {
|
|
775
|
+
if (!raw) return [];
|
|
776
|
+
try {
|
|
777
|
+
const entries = JSON.parse(raw);
|
|
778
|
+
return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
|
|
779
|
+
} catch {
|
|
780
|
+
return [];
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function mergePageSectionOrder(raw, currentPath, pageEntries) {
|
|
784
|
+
let all = [];
|
|
785
|
+
if (raw) {
|
|
786
|
+
try {
|
|
787
|
+
const parsed = JSON.parse(raw);
|
|
788
|
+
if (Array.isArray(parsed)) all = parsed;
|
|
789
|
+
} catch {
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const otherPages = all.filter((e) => e && e.pagePath && e.pagePath !== currentPath);
|
|
793
|
+
const pageIds = new Set(pageEntries.map((e) => e.instanceId));
|
|
794
|
+
const removedHere = all.filter(
|
|
795
|
+
(e) => e && (!e.pagePath || e.pagePath === currentPath) && e.removed && !pageIds.has(e.instanceId)
|
|
796
|
+
);
|
|
797
|
+
return [...otherPages, ...removedHere, ...pageEntries];
|
|
798
|
+
}
|
|
799
|
+
function rekeySectionSubtree(root, instanceId) {
|
|
800
|
+
const suffix = `::${instanceId}`;
|
|
801
|
+
const pairs = [];
|
|
802
|
+
const rekey = (el, attr) => {
|
|
803
|
+
const current = el.getAttribute(attr);
|
|
804
|
+
if (!current) return;
|
|
805
|
+
const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
|
|
806
|
+
const next = `${base}${suffix}`;
|
|
807
|
+
el.setAttribute(attr, next);
|
|
808
|
+
pairs.push({ from: current, to: next });
|
|
809
|
+
};
|
|
810
|
+
if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
|
|
811
|
+
if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
|
|
812
|
+
root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
|
|
813
|
+
root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
|
|
814
|
+
return pairs;
|
|
815
|
+
}
|
|
816
|
+
function initSectionInstancesFromContent(content, currentPath) {
|
|
817
|
+
document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
|
|
818
|
+
el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
|
|
819
|
+
});
|
|
820
|
+
document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
|
|
821
|
+
const type = sectionTypeOf(el);
|
|
822
|
+
if (type) el.setAttribute("data-ohw-instance", type);
|
|
823
|
+
});
|
|
824
|
+
const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
|
|
825
|
+
for (const entry of entries) {
|
|
826
|
+
if (entry.instanceId === entry.type) continue;
|
|
827
|
+
if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
|
|
828
|
+
const original = document.querySelector(
|
|
829
|
+
`[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
|
|
830
|
+
);
|
|
831
|
+
if (!original) continue;
|
|
832
|
+
const clone = original.cloneNode(true);
|
|
833
|
+
clone.setAttribute("data-ohw-instance", entry.instanceId);
|
|
834
|
+
rekeySectionSubtree(clone, entry.instanceId);
|
|
835
|
+
original.insertAdjacentElement("afterend", clone);
|
|
836
|
+
}
|
|
837
|
+
applyPersistedOrder(entries);
|
|
838
|
+
}
|
|
839
|
+
|
|
562
840
|
// src/ui/ai-tree/AiTreeRenderer.tsx
|
|
563
841
|
import React from "react";
|
|
564
842
|
import { ArrowLeft, ArrowRight, ChevronDown, icons as lucideIcons } from "lucide-react";
|
|
@@ -601,6 +879,25 @@ var FEATURE_LINE_CSS = [
|
|
|
601
879
|
`background-color:currentColor;-webkit-mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px;`,
|
|
602
880
|
`mask:url("${CHECK_MASK}") no-repeat 0 0/24px 24px}`
|
|
603
881
|
].join("");
|
|
882
|
+
function buttonShellStyle(ctx, fullWidth) {
|
|
883
|
+
const bs = ctx.buttonStyle;
|
|
884
|
+
if (bs) {
|
|
885
|
+
return {
|
|
886
|
+
borderRadius: bs.radius,
|
|
887
|
+
...bs.padding ? { padding: bs.padding } : {},
|
|
888
|
+
...bs.fontFamily ? { fontFamily: bs.fontFamily } : { fontFamily: ctx.brand.fonts.body },
|
|
889
|
+
...bs.fontSize ? { fontSize: bs.fontSize } : {},
|
|
890
|
+
...bs.fontWeight ? { fontWeight: bs.fontWeight } : {},
|
|
891
|
+
...bs.letterSpacing && bs.letterSpacing !== "normal" ? { letterSpacing: bs.letterSpacing } : {},
|
|
892
|
+
...bs.textTransform && bs.textTransform !== "none" ? { textTransform: bs.textTransform } : {}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
borderRadius: AI_TREE_TOKENS.radiusButton,
|
|
897
|
+
padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
|
|
898
|
+
...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
|
|
899
|
+
};
|
|
900
|
+
}
|
|
604
901
|
function hexLuminance(color) {
|
|
605
902
|
const m = /^#([0-9a-f]{6})$/i.exec(color.trim());
|
|
606
903
|
if (!m) return null;
|
|
@@ -617,6 +914,12 @@ function hexContrast(a, b) {
|
|
|
617
914
|
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
618
915
|
return (hi + 0.05) / (lo + 0.05);
|
|
619
916
|
}
|
|
917
|
+
function primaryButtonLabel(brand) {
|
|
918
|
+
const darkC = hexContrast(brand.palette.primary, brand.palette.dark);
|
|
919
|
+
const lightC = hexContrast(brand.palette.primary, AI_TREE_TOKENS.textPrimaryForeground);
|
|
920
|
+
if (darkC === null || lightC === null) return AI_TREE_TOKENS.textPrimaryForeground;
|
|
921
|
+
return darkC > lightC ? brand.palette.dark : AI_TREE_TOKENS.textPrimaryForeground;
|
|
922
|
+
}
|
|
620
923
|
function accentBandContext(brand) {
|
|
621
924
|
const p = brand.palette;
|
|
622
925
|
const lightWins = (hexContrast(p.light, p.primary) ?? 99) >= (hexContrast(p.dark, p.primary) ?? 0);
|
|
@@ -716,12 +1019,10 @@ function ButtonEl({
|
|
|
716
1019
|
width: fullWidth ? "100%" : void 0,
|
|
717
1020
|
alignItems: "center",
|
|
718
1021
|
justifyContent: "center",
|
|
719
|
-
padding: fullWidth ? `${AI_TREE_TOKENS.spacing2}px ${AI_TREE_TOKENS.spacing4}px` : `${AI_TREE_TOKENS.spacing3}px ${AI_TREE_TOKENS.spacing6}px`,
|
|
720
|
-
borderRadius: AI_TREE_TOKENS.radiusButton,
|
|
721
1022
|
textDecoration: "none",
|
|
722
1023
|
cursor: "pointer",
|
|
723
|
-
...
|
|
724
|
-
...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ??
|
|
1024
|
+
...buttonShellStyle(ctx, fullWidth),
|
|
1025
|
+
...secondary ? { border: `1px solid ${ctx.brand.palette.dark}`, color: ctx.brand.palette.dark } : { background: ctx.brand.palette.primary, color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand) }
|
|
725
1026
|
},
|
|
726
1027
|
children: /* @__PURE__ */ jsx("span", { ...path ? textAttrs(ctx, `${path}.label`) : {}, children: str(slots.label) })
|
|
727
1028
|
}
|
|
@@ -1761,15 +2062,12 @@ function renderNode(node, ctx, path) {
|
|
|
1761
2062
|
alignSelf: submitAlign,
|
|
1762
2063
|
border: "none",
|
|
1763
2064
|
cursor: "pointer",
|
|
1764
|
-
padding
|
|
1765
|
-
|
|
1766
|
-
// CTA); 8px only when the page has no template button to match.
|
|
1767
|
-
borderRadius: ctx.buttonRadius ?? 8,
|
|
2065
|
+
// Shape/padding/typography follow the host template's own buttons.
|
|
2066
|
+
...buttonShellStyle(ctx),
|
|
1768
2067
|
// Brand-styled: primary fill, brand-derived label colour (not a fixed token) so it
|
|
1769
2068
|
// reads correctly on custom palettes.
|
|
1770
2069
|
background: ctx.brand.palette.primary,
|
|
1771
|
-
color: ctx.buttonLabel ?? ctx.brand
|
|
1772
|
-
...typeStyle(AI_TREE_TOKENS.type.button, ctx.brand.fonts.body)
|
|
2070
|
+
color: ctx.buttonLabel ?? primaryButtonLabel(ctx.brand)
|
|
1773
2071
|
},
|
|
1774
2072
|
children: /* @__PURE__ */ jsx("span", { ...textAttrs(ctx, `${path}.c${i}.label`), children: str(cs.label) })
|
|
1775
2073
|
},
|
|
@@ -1804,7 +2102,7 @@ function renderNode(node, ctx, path) {
|
|
|
1804
2102
|
function AiTreeRenderer({
|
|
1805
2103
|
tree,
|
|
1806
2104
|
brand,
|
|
1807
|
-
|
|
2105
|
+
buttonStyle,
|
|
1808
2106
|
resolveMedia,
|
|
1809
2107
|
editKeyPrefix
|
|
1810
2108
|
}) {
|
|
@@ -1820,7 +2118,7 @@ function AiTreeRenderer({
|
|
|
1820
2118
|
cardSurface: blockBrand.palette.light.toUpperCase() === AI_DEFAULT_BRAND.palette.light.toUpperCase() ? "#ECECEB" : `color-mix(in srgb, ${blockBrand.palette.light} 90%, ${blockBrand.palette.dark})`,
|
|
1821
2119
|
keyFor: editKeyPrefix ? (path) => `${editKeyPrefix}.${path}` : null,
|
|
1822
2120
|
sectionAlignment: (tree.settings ?? {}).alignment === "center" ? "center" : "left",
|
|
1823
|
-
|
|
2121
|
+
buttonStyle,
|
|
1824
2122
|
...band ? { buttonLabel: band.buttonLabel } : {}
|
|
1825
2123
|
};
|
|
1826
2124
|
const settings = tree.settings ?? {};
|
|
@@ -1926,7 +2224,7 @@ function AiTreeRenderer({
|
|
|
1926
2224
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1927
2225
|
var CONTAINER_ATTR = "data-ohw-ai-generated";
|
|
1928
2226
|
var REPLACED_ATTR = "data-ohw-ai-replaced-by";
|
|
1929
|
-
var
|
|
2227
|
+
var REMOVED_ATTR2 = "data-ohw-ai-removed";
|
|
1930
2228
|
var TEMPLATE_HIDDEN_ATTR = "data-ohw-ai-template-hidden";
|
|
1931
2229
|
var CHROME_SECTION_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
|
|
1932
2230
|
function readRootVar(name) {
|
|
@@ -1950,13 +2248,13 @@ function deriveBrandOverride() {
|
|
|
1950
2248
|
};
|
|
1951
2249
|
}
|
|
1952
2250
|
function deriveTemplateBrand() {
|
|
1953
|
-
const
|
|
1954
|
-
const
|
|
1955
|
-
const light = readRootVar("--color-light");
|
|
2251
|
+
const primary = readRootVar("--brand-primary") || readRootVar("--color-primary");
|
|
2252
|
+
const dark = readRootVar("--brand-text") || readRootVar("--color-dark");
|
|
2253
|
+
const light = readRootVar("--brand-background") || readRootVar("--color-light");
|
|
1956
2254
|
if (!dark || !primary || !light) return null;
|
|
1957
|
-
const accent = readRootVar("--color-accent");
|
|
1958
|
-
const heading = readRootVar("--font-heading") || readRootVar("--font-display");
|
|
1959
|
-
const body = readRootVar("--font-body");
|
|
2255
|
+
const accent = readRootVar("--brand-accent") || readRootVar("--color-accent");
|
|
2256
|
+
const heading = readRootVar("--brand-font-heading") || readRootVar("--font-heading") || readRootVar("--font-display");
|
|
2257
|
+
const body = readRootVar("--brand-font-body") || readRootVar("--font-body");
|
|
1960
2258
|
return {
|
|
1961
2259
|
palette: { dark, primary, accent: accent || dark, light },
|
|
1962
2260
|
fonts: {
|
|
@@ -1965,12 +2263,32 @@ function deriveTemplateBrand() {
|
|
|
1965
2263
|
}
|
|
1966
2264
|
};
|
|
1967
2265
|
}
|
|
1968
|
-
function
|
|
2266
|
+
function deriveTemplateButtonStyle() {
|
|
1969
2267
|
if (typeof document === "undefined") return null;
|
|
1970
|
-
const btn = document.
|
|
2268
|
+
const btn = Array.from(document.querySelectorAll('[data-ohw-role="button"]')).find(
|
|
2269
|
+
(el) => !el.closest(`[${CONTAINER_ATTR}]`)
|
|
2270
|
+
);
|
|
1971
2271
|
if (!btn) return null;
|
|
1972
|
-
const
|
|
1973
|
-
|
|
2272
|
+
const cs = getComputedStyle(btn);
|
|
2273
|
+
const corners = [
|
|
2274
|
+
cs.borderTopLeftRadius,
|
|
2275
|
+
cs.borderTopRightRadius,
|
|
2276
|
+
cs.borderBottomRightRadius,
|
|
2277
|
+
cs.borderBottomLeftRadius
|
|
2278
|
+
].map((v) => v || "0px");
|
|
2279
|
+
const radius = corners.every((v) => v === corners[0]) ? corners[0] : corners.join(" ");
|
|
2280
|
+
const px = (v) => parseFloat(v) || 0;
|
|
2281
|
+
const padY = Math.max(px(cs.paddingTop), px(cs.paddingBottom));
|
|
2282
|
+
const padX = Math.max(px(cs.paddingLeft), px(cs.paddingRight));
|
|
2283
|
+
return {
|
|
2284
|
+
radius: radius || "10px",
|
|
2285
|
+
padding: `${padY}px ${padX}px`,
|
|
2286
|
+
fontFamily: cs.fontFamily || "",
|
|
2287
|
+
fontSize: cs.fontSize || "",
|
|
2288
|
+
fontWeight: cs.fontWeight || "",
|
|
2289
|
+
letterSpacing: cs.letterSpacing || "",
|
|
2290
|
+
textTransform: cs.textTransform || ""
|
|
2291
|
+
};
|
|
1974
2292
|
}
|
|
1975
2293
|
var mounted = /* @__PURE__ */ new Map();
|
|
1976
2294
|
function findTemplateSection(id) {
|
|
@@ -2021,18 +2339,18 @@ function placeContainer(container, entry) {
|
|
|
2021
2339
|
}
|
|
2022
2340
|
function syncRemovedSections(state) {
|
|
2023
2341
|
const removed = new Set(state.removed ?? []);
|
|
2024
|
-
for (const el of document.querySelectorAll(`[${
|
|
2342
|
+
for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
|
|
2025
2343
|
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
2026
2344
|
if (!removed.has(id)) {
|
|
2027
2345
|
el.style.removeProperty("display");
|
|
2028
|
-
el.removeAttribute(
|
|
2346
|
+
el.removeAttribute(REMOVED_ATTR2);
|
|
2029
2347
|
}
|
|
2030
2348
|
}
|
|
2031
2349
|
for (const id of removed) {
|
|
2032
2350
|
const section = findTemplateSection(id);
|
|
2033
2351
|
if (section && !section.hasAttribute(REPLACED_ATTR)) {
|
|
2034
2352
|
section.style.display = "none";
|
|
2035
|
-
section.setAttribute(
|
|
2353
|
+
section.setAttribute(REMOVED_ATTR2, "");
|
|
2036
2354
|
}
|
|
2037
2355
|
}
|
|
2038
2356
|
}
|
|
@@ -2049,7 +2367,7 @@ function syncTemplateHidden(state, pageHasSections) {
|
|
|
2049
2367
|
if (el.hasAttribute(CONTAINER_ATTR)) continue;
|
|
2050
2368
|
if (CHROME_SECTION_IDS.has(el.getAttribute("data-ohw-section") ?? "")) continue;
|
|
2051
2369
|
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
2052
|
-
if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(
|
|
2370
|
+
if (el.hasAttribute(REPLACED_ATTR) || el.hasAttribute(REMOVED_ATTR2)) continue;
|
|
2053
2371
|
el.style.display = "none";
|
|
2054
2372
|
el.setAttribute(TEMPLATE_HIDDEN_ATTR, "");
|
|
2055
2373
|
}
|
|
@@ -2073,18 +2391,23 @@ function syncReplacedOriginals(state) {
|
|
|
2073
2391
|
}
|
|
2074
2392
|
}
|
|
2075
2393
|
var sectionOrderIndex = /* @__PURE__ */ new Map();
|
|
2394
|
+
var removedSectionIds = /* @__PURE__ */ new Set();
|
|
2076
2395
|
function setAiSectionOrder(raw, currentPath) {
|
|
2077
2396
|
const next = /* @__PURE__ */ new Map();
|
|
2397
|
+
const removed = /* @__PURE__ */ new Set();
|
|
2078
2398
|
if (raw) {
|
|
2079
2399
|
try {
|
|
2080
2400
|
const entries = JSON.parse(raw);
|
|
2081
2401
|
for (const entry of entries) {
|
|
2082
|
-
if (
|
|
2402
|
+
if (entry.pagePath && entry.pagePath !== currentPath) continue;
|
|
2403
|
+
next.set(entry.instanceId, entry.order);
|
|
2404
|
+
if (entry.removed) removed.add(entry.instanceId);
|
|
2083
2405
|
}
|
|
2084
2406
|
} catch {
|
|
2085
2407
|
}
|
|
2086
2408
|
}
|
|
2087
2409
|
sectionOrderIndex = next;
|
|
2410
|
+
removedSectionIds = removed;
|
|
2088
2411
|
}
|
|
2089
2412
|
function applyExplicitOrder(entries) {
|
|
2090
2413
|
if (sectionOrderIndex.size === 0) return entries;
|
|
@@ -2120,11 +2443,23 @@ function orderByChain(sections) {
|
|
|
2120
2443
|
for (const root of roots) visit(root);
|
|
2121
2444
|
return out.length === sections.length ? out : sections;
|
|
2122
2445
|
}
|
|
2446
|
+
function syncSoftRemovedGenerated() {
|
|
2447
|
+
for (const [id, section] of mounted) {
|
|
2448
|
+
const el = section.container;
|
|
2449
|
+
if (removedSectionIds.has(id)) {
|
|
2450
|
+
el.style.display = "none";
|
|
2451
|
+
el.setAttribute(REMOVED_ATTR, "");
|
|
2452
|
+
} else if (el.hasAttribute(REMOVED_ATTR)) {
|
|
2453
|
+
el.style.removeProperty("display");
|
|
2454
|
+
el.removeAttribute(REMOVED_ATTR);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2123
2458
|
function applyAiSectionsToDom(state, options) {
|
|
2124
2459
|
if (typeof document === "undefined") return;
|
|
2125
2460
|
const brandOverride = deriveBrandOverride();
|
|
2126
2461
|
const templateBrand = deriveTemplateBrand();
|
|
2127
|
-
const
|
|
2462
|
+
const templateButtonStyle = deriveTemplateButtonStyle();
|
|
2128
2463
|
const brandKey = brandOverride ? JSON.stringify(brandOverride) : "";
|
|
2129
2464
|
const pagePath = window.location.pathname;
|
|
2130
2465
|
const pageSections = state.sections.filter((entry) => (entry.path ?? "/") === pagePath);
|
|
@@ -2164,7 +2499,7 @@ function applyAiSectionsToDom(state, options) {
|
|
|
2164
2499
|
{
|
|
2165
2500
|
tree: entry.tree,
|
|
2166
2501
|
brand: brandOverride ?? entry.brand ?? options?.brand ?? templateBrand ?? void 0,
|
|
2167
|
-
|
|
2502
|
+
buttonStyle: templateButtonStyle,
|
|
2168
2503
|
resolveMedia,
|
|
2169
2504
|
editKeyPrefix: `ai.${entry.id}`
|
|
2170
2505
|
}
|
|
@@ -2187,6 +2522,7 @@ function applyAiSectionsToDom(state, options) {
|
|
|
2187
2522
|
syncReplacedOriginals(state);
|
|
2188
2523
|
syncRemovedSections(state);
|
|
2189
2524
|
syncTemplateHidden(state, pageSections.length > 0);
|
|
2525
|
+
syncSoftRemovedGenerated();
|
|
2190
2526
|
}
|
|
2191
2527
|
function unmountAllAiSections() {
|
|
2192
2528
|
for (const [, section] of mounted) {
|
|
@@ -2199,9 +2535,9 @@ function unmountAllAiSections() {
|
|
|
2199
2535
|
el.style.removeProperty("display");
|
|
2200
2536
|
el.removeAttribute(REPLACED_ATTR);
|
|
2201
2537
|
}
|
|
2202
|
-
for (const el of document.querySelectorAll(`[${
|
|
2538
|
+
for (const el of document.querySelectorAll(`[${REMOVED_ATTR2}]`)) {
|
|
2203
2539
|
el.style.removeProperty("display");
|
|
2204
|
-
el.removeAttribute(
|
|
2540
|
+
el.removeAttribute(REMOVED_ATTR2);
|
|
2205
2541
|
}
|
|
2206
2542
|
}
|
|
2207
2543
|
|
|
@@ -8162,240 +8498,6 @@ function CarouselOverlay({
|
|
|
8162
8498
|
// src/ui/ai-section/AiSectionOverlay.tsx
|
|
8163
8499
|
import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
|
|
8164
8500
|
import { Check, X } from "lucide-react";
|
|
8165
|
-
|
|
8166
|
-
// src/lib/sections.ts
|
|
8167
|
-
var LINK_PICKER_EXCLUDED_IDS = /* @__PURE__ */ new Set(["navbar", "footer"]);
|
|
8168
|
-
function isChromeSection(el) {
|
|
8169
|
-
return el.matches("header, nav, footer, aside");
|
|
8170
|
-
}
|
|
8171
|
-
function titleCaseSectionId(id) {
|
|
8172
|
-
return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
8173
|
-
}
|
|
8174
|
-
function parseSectionsFromRoot(root) {
|
|
8175
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8176
|
-
const sections = [];
|
|
8177
|
-
for (const el of root.querySelectorAll("[data-ohw-section]")) {
|
|
8178
|
-
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
8179
|
-
if (!id || seen.has(id) || LINK_PICKER_EXCLUDED_IDS.has(id)) continue;
|
|
8180
|
-
if (el.parentElement?.closest("[data-ohw-section]")) continue;
|
|
8181
|
-
if (el.hasAttribute("data-ohw-ai-template-hidden") || el.hasAttribute("data-ohw-ai-removed") || el.hasAttribute("data-ohw-ai-replaced-by"))
|
|
8182
|
-
continue;
|
|
8183
|
-
seen.add(id);
|
|
8184
|
-
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
8185
|
-
sections.push({ id, label });
|
|
8186
|
-
}
|
|
8187
|
-
return sections;
|
|
8188
|
-
}
|
|
8189
|
-
function collectSectionsFromDom() {
|
|
8190
|
-
if (typeof document === "undefined") return [];
|
|
8191
|
-
return parseSectionsFromRoot(document);
|
|
8192
|
-
}
|
|
8193
|
-
function parseSectionsFromHtml(html) {
|
|
8194
|
-
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
8195
|
-
return parseSectionsFromRoot(doc);
|
|
8196
|
-
}
|
|
8197
|
-
|
|
8198
|
-
// src/lib/section-instances.ts
|
|
8199
|
-
var SECTION_ORDER_KEY = "__ohw_section_order";
|
|
8200
|
-
var REMOVED_ATTR2 = "data-ohw-section-removed";
|
|
8201
|
-
function isRemovedSection(el) {
|
|
8202
|
-
return el.hasAttribute(REMOVED_ATTR2);
|
|
8203
|
-
}
|
|
8204
|
-
function movableUnit(el) {
|
|
8205
|
-
return el.closest("[data-ohw-section-container]") ?? el;
|
|
8206
|
-
}
|
|
8207
|
-
function sectionTypeOf(el) {
|
|
8208
|
-
return el.getAttribute("data-ohw-section") ?? el.querySelector("[data-ohw-section]")?.getAttribute("data-ohw-section") ?? "";
|
|
8209
|
-
}
|
|
8210
|
-
function sectionElementOf(el) {
|
|
8211
|
-
return el.hasAttribute("data-ohw-section") ? el : el.querySelector("[data-ohw-section]") ?? el;
|
|
8212
|
-
}
|
|
8213
|
-
function collectTopLevelUnits(predicate) {
|
|
8214
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8215
|
-
const result = [];
|
|
8216
|
-
document.querySelectorAll("[data-ohw-section]").forEach((el) => {
|
|
8217
|
-
if (!predicate(el)) return;
|
|
8218
|
-
const unit = movableUnit(el);
|
|
8219
|
-
if (unit.parentElement?.closest("[data-ohw-section],[data-ohw-section-container]")) return;
|
|
8220
|
-
if (seen.has(unit)) return;
|
|
8221
|
-
seen.add(unit);
|
|
8222
|
-
result.push(unit);
|
|
8223
|
-
});
|
|
8224
|
-
return result;
|
|
8225
|
-
}
|
|
8226
|
-
function topLevelSections() {
|
|
8227
|
-
return collectTopLevelUnits((el) => !isChromeSection(el) && !isRemovedSection(movableUnit(el)));
|
|
8228
|
-
}
|
|
8229
|
-
function instanceIdOf(el) {
|
|
8230
|
-
return el.dataset.ohwInstance ?? el.dataset.ohwSection ?? "";
|
|
8231
|
-
}
|
|
8232
|
-
function findByInstanceId(instanceId) {
|
|
8233
|
-
const escapedId = CSS.escape(instanceId);
|
|
8234
|
-
const direct = document.querySelector(`[data-ohw-instance="${escapedId}"]`);
|
|
8235
|
-
if (direct) return movableUnit(direct);
|
|
8236
|
-
const bare = document.querySelector(`[data-ohw-section="${escapedId}"]:not([data-ohw-instance])`);
|
|
8237
|
-
return bare ? movableUnit(bare) : null;
|
|
8238
|
-
}
|
|
8239
|
-
function planSectionMove(instanceId, targetIndex, currentPath) {
|
|
8240
|
-
const sections = topLevelSections();
|
|
8241
|
-
const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
|
|
8242
|
-
if (index === -1) return null;
|
|
8243
|
-
const dragged = sections[index];
|
|
8244
|
-
const others = sections.filter((_, i) => i !== index);
|
|
8245
|
-
const clamped = Math.max(0, Math.min(targetIndex, others.length));
|
|
8246
|
-
const reordered = [...others.slice(0, clamped), dragged, ...others.slice(clamped)];
|
|
8247
|
-
return reordered.map((el, order) => ({
|
|
8248
|
-
instanceId: instanceIdOf(el),
|
|
8249
|
-
type: sectionTypeOf(el),
|
|
8250
|
-
order,
|
|
8251
|
-
pagePath: currentPath
|
|
8252
|
-
}));
|
|
8253
|
-
}
|
|
8254
|
-
function moveSectionInstance(instanceId, direction, currentPath) {
|
|
8255
|
-
const sections = topLevelSections();
|
|
8256
|
-
const index = sections.findIndex((el) => instanceIdOf(el) === instanceId);
|
|
8257
|
-
if (index === -1) return null;
|
|
8258
|
-
const siblingIndex = direction === "up" ? index - 1 : index + 1;
|
|
8259
|
-
if (siblingIndex < 0 || siblingIndex >= sections.length) return null;
|
|
8260
|
-
const entries = planSectionMove(instanceId, siblingIndex, currentPath);
|
|
8261
|
-
if (!entries) return null;
|
|
8262
|
-
applyPersistedOrder(entries);
|
|
8263
|
-
return entries;
|
|
8264
|
-
}
|
|
8265
|
-
function syncRemovedFlags(entries) {
|
|
8266
|
-
const removedIds = new Set(entries.filter((e) => e.removed).map((e) => e.instanceId));
|
|
8267
|
-
document.querySelectorAll(`[${REMOVED_ATTR2}]`).forEach((el) => {
|
|
8268
|
-
if (!removedIds.has(instanceIdOf(el))) {
|
|
8269
|
-
el.style.removeProperty("display");
|
|
8270
|
-
el.removeAttribute(REMOVED_ATTR2);
|
|
8271
|
-
}
|
|
8272
|
-
});
|
|
8273
|
-
for (const id of removedIds) {
|
|
8274
|
-
const el = findByInstanceId(id);
|
|
8275
|
-
if (el) {
|
|
8276
|
-
el.style.display = "none";
|
|
8277
|
-
el.setAttribute(REMOVED_ATTR2, "");
|
|
8278
|
-
}
|
|
8279
|
-
}
|
|
8280
|
-
}
|
|
8281
|
-
function applyPersistedOrder(entries) {
|
|
8282
|
-
syncRemovedFlags(entries);
|
|
8283
|
-
if (entries.length === 0) return;
|
|
8284
|
-
const sections = topLevelSections();
|
|
8285
|
-
if (sections.length === 0) return;
|
|
8286
|
-
const orderIndex = new Map(entries.map((e) => [e.instanceId, e.order]));
|
|
8287
|
-
const ordered = [...sections].sort((a, b) => {
|
|
8288
|
-
const aOrder = orderIndex.get(instanceIdOf(a));
|
|
8289
|
-
const bOrder = orderIndex.get(instanceIdOf(b));
|
|
8290
|
-
if (aOrder === void 0 && bOrder === void 0) return 0;
|
|
8291
|
-
if (aOrder === void 0) return 1;
|
|
8292
|
-
if (bOrder === void 0) return -1;
|
|
8293
|
-
return aOrder - bOrder;
|
|
8294
|
-
});
|
|
8295
|
-
let prev = null;
|
|
8296
|
-
for (const el of ordered) {
|
|
8297
|
-
if (prev) prev.after(el);
|
|
8298
|
-
prev = el;
|
|
8299
|
-
}
|
|
8300
|
-
}
|
|
8301
|
-
function setSectionRemoved(instanceId, currentPath, existingEntries, removed) {
|
|
8302
|
-
if (!findByInstanceId(instanceId)) return null;
|
|
8303
|
-
const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
|
|
8304
|
-
const allSections = collectTopLevelUnits((el) => !isChromeSection(el));
|
|
8305
|
-
allSections.forEach((el, order) => {
|
|
8306
|
-
const id = instanceIdOf(el);
|
|
8307
|
-
if (!byId.has(id)) {
|
|
8308
|
-
byId.set(id, { instanceId: id, type: sectionTypeOf(el), order, pagePath: currentPath });
|
|
8309
|
-
}
|
|
8310
|
-
});
|
|
8311
|
-
const target = byId.get(instanceId);
|
|
8312
|
-
if (!target) return null;
|
|
8313
|
-
byId.set(instanceId, { ...target, removed });
|
|
8314
|
-
const entries = Array.from(byId.values());
|
|
8315
|
-
applyPersistedOrder(entries);
|
|
8316
|
-
return entries;
|
|
8317
|
-
}
|
|
8318
|
-
function deleteSectionInstance(instanceId, currentPath, existingEntries) {
|
|
8319
|
-
return setSectionRemoved(instanceId, currentPath, existingEntries, true);
|
|
8320
|
-
}
|
|
8321
|
-
function restoreSectionInstance(instanceId, currentPath, existingEntries) {
|
|
8322
|
-
return setSectionRemoved(instanceId, currentPath, existingEntries, false);
|
|
8323
|
-
}
|
|
8324
|
-
function duplicateSectionInstance(instanceId, newId, currentPath, existingEntries) {
|
|
8325
|
-
const original = findByInstanceId(instanceId);
|
|
8326
|
-
if (!original) return null;
|
|
8327
|
-
const clone = original.cloneNode(true);
|
|
8328
|
-
clone.setAttribute("data-ohw-instance", newId);
|
|
8329
|
-
const keyRekeys = rekeySectionSubtree(clone, newId);
|
|
8330
|
-
original.insertAdjacentElement("afterend", clone);
|
|
8331
|
-
const byId = new Map(existingEntries.map((e) => [e.instanceId, e]));
|
|
8332
|
-
const entries = topLevelSections().map((el, order) => {
|
|
8333
|
-
const id = instanceIdOf(el);
|
|
8334
|
-
return {
|
|
8335
|
-
instanceId: id,
|
|
8336
|
-
type: sectionTypeOf(el),
|
|
8337
|
-
order,
|
|
8338
|
-
pagePath: currentPath,
|
|
8339
|
-
...byId.get(id)?.removed ? { removed: true } : {}
|
|
8340
|
-
};
|
|
8341
|
-
});
|
|
8342
|
-
applyPersistedOrder(entries);
|
|
8343
|
-
return { entries, keyRekeys };
|
|
8344
|
-
}
|
|
8345
|
-
function newInstanceId() {
|
|
8346
|
-
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `instance-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
8347
|
-
}
|
|
8348
|
-
function getPageSectionOrderEntries(raw, currentPath) {
|
|
8349
|
-
if (!raw) return [];
|
|
8350
|
-
try {
|
|
8351
|
-
const entries = JSON.parse(raw);
|
|
8352
|
-
return entries.filter((e) => !e.pagePath || e.pagePath === currentPath);
|
|
8353
|
-
} catch {
|
|
8354
|
-
return [];
|
|
8355
|
-
}
|
|
8356
|
-
}
|
|
8357
|
-
function rekeySectionSubtree(root, instanceId) {
|
|
8358
|
-
const suffix = `::${instanceId}`;
|
|
8359
|
-
const pairs = [];
|
|
8360
|
-
const rekey = (el, attr) => {
|
|
8361
|
-
const current = el.getAttribute(attr);
|
|
8362
|
-
if (!current) return;
|
|
8363
|
-
const base = current.includes("::") ? current.slice(0, current.indexOf("::")) : current;
|
|
8364
|
-
const next = `${base}${suffix}`;
|
|
8365
|
-
el.setAttribute(attr, next);
|
|
8366
|
-
pairs.push({ from: current, to: next });
|
|
8367
|
-
};
|
|
8368
|
-
if (root.hasAttribute("data-ohw-key")) rekey(root, "data-ohw-key");
|
|
8369
|
-
if (root.hasAttribute("data-ohw-href-key")) rekey(root, "data-ohw-href-key");
|
|
8370
|
-
root.querySelectorAll("[data-ohw-key]").forEach((el) => rekey(el, "data-ohw-key"));
|
|
8371
|
-
root.querySelectorAll("[data-ohw-href-key]").forEach((el) => rekey(el, "data-ohw-href-key"));
|
|
8372
|
-
return pairs;
|
|
8373
|
-
}
|
|
8374
|
-
function initSectionInstancesFromContent(content, currentPath) {
|
|
8375
|
-
document.querySelectorAll("[data-ohw-section]:not([data-ohw-instance])").forEach((el) => {
|
|
8376
|
-
el.setAttribute("data-ohw-instance", el.getAttribute("data-ohw-section") ?? "");
|
|
8377
|
-
});
|
|
8378
|
-
document.querySelectorAll("[data-ohw-section-container]:not([data-ohw-instance])").forEach((el) => {
|
|
8379
|
-
const type = sectionTypeOf(el);
|
|
8380
|
-
if (type) el.setAttribute("data-ohw-instance", type);
|
|
8381
|
-
});
|
|
8382
|
-
const entries = getPageSectionOrderEntries(content[SECTION_ORDER_KEY], currentPath);
|
|
8383
|
-
for (const entry of entries) {
|
|
8384
|
-
if (entry.instanceId === entry.type) continue;
|
|
8385
|
-
if (document.querySelector(`[data-ohw-instance="${CSS.escape(entry.instanceId)}"]`)) continue;
|
|
8386
|
-
const original = document.querySelector(
|
|
8387
|
-
`[data-ohw-section="${CSS.escape(entry.type)}"][data-ohw-instance="${CSS.escape(entry.type)}"]`
|
|
8388
|
-
);
|
|
8389
|
-
if (!original) continue;
|
|
8390
|
-
const clone = original.cloneNode(true);
|
|
8391
|
-
clone.setAttribute("data-ohw-instance", entry.instanceId);
|
|
8392
|
-
rekeySectionSubtree(clone, entry.instanceId);
|
|
8393
|
-
original.insertAdjacentElement("afterend", clone);
|
|
8394
|
-
}
|
|
8395
|
-
applyPersistedOrder(entries);
|
|
8396
|
-
}
|
|
8397
|
-
|
|
8398
|
-
// src/ui/ai-section/AiSectionOverlay.tsx
|
|
8399
8501
|
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
8400
8502
|
var findSectionElement = findByInstanceId;
|
|
8401
8503
|
function readRect(instanceId) {
|
|
@@ -14184,15 +14286,17 @@ function useSectionDrag({
|
|
|
14184
14286
|
clearSectionDragVisuals();
|
|
14185
14287
|
return;
|
|
14186
14288
|
}
|
|
14187
|
-
const orderJson = JSON.stringify(
|
|
14289
|
+
const orderJson = JSON.stringify(
|
|
14290
|
+
mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
|
|
14291
|
+
);
|
|
14188
14292
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
14189
14293
|
setAiSectionOrder(orderJson, window.location.pathname);
|
|
14190
14294
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
14191
|
-
applyPersistedOrder(
|
|
14295
|
+
applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
|
|
14192
14296
|
clearSectionDragVisuals();
|
|
14193
14297
|
requestAnimationFrame(() => {
|
|
14194
14298
|
if (editContentRef.current[SECTION_ORDER_KEY] === orderJson) {
|
|
14195
|
-
applyPersistedOrder(
|
|
14299
|
+
applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
|
|
14196
14300
|
}
|
|
14197
14301
|
requestAnimationFrame(() => {
|
|
14198
14302
|
window.dispatchEvent(new Event("resize"));
|
|
@@ -20217,6 +20321,44 @@ function OhhwellsBridge() {
|
|
|
20217
20321
|
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
20218
20322
|
}, 400));
|
|
20219
20323
|
};
|
|
20324
|
+
const reapCommittedAiSections = (excludeIds) => {
|
|
20325
|
+
const aiState = parseAiSectionsState(aiSectionsRef.current);
|
|
20326
|
+
if (aiState.sections.length === 0) return [];
|
|
20327
|
+
let orderEntries = [];
|
|
20328
|
+
try {
|
|
20329
|
+
const parsed = JSON.parse(editContentRef.current[SECTION_ORDER_KEY] ?? "[]");
|
|
20330
|
+
if (Array.isArray(parsed)) orderEntries = parsed;
|
|
20331
|
+
} catch {
|
|
20332
|
+
return [];
|
|
20333
|
+
}
|
|
20334
|
+
const removedIds = orderEntries.filter((entry) => entry && typeof entry.instanceId === "string" && entry.removed).map((entry) => entry.instanceId);
|
|
20335
|
+
if (removedIds.length === 0) return [];
|
|
20336
|
+
const result = reapRemovedAiSections(aiState, parseStyleStore(stylesRef.current), removedIds, excludeIds);
|
|
20337
|
+
if (!result.changed) return [];
|
|
20338
|
+
const nodes = [];
|
|
20339
|
+
aiSectionsRef.current = serializeAiSectionsState(result.state);
|
|
20340
|
+
nodes.push({ key: AI_SECTIONS_KEY, text: aiSectionsRef.current });
|
|
20341
|
+
const reaped = new Set(result.reapedIds);
|
|
20342
|
+
const nextOrderJson = JSON.stringify(orderEntries.filter((entry) => !reaped.has(entry.instanceId)));
|
|
20343
|
+
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: nextOrderJson };
|
|
20344
|
+
setAiSectionOrder(nextOrderJson, window.location.pathname);
|
|
20345
|
+
nodes.push({ key: SECTION_ORDER_KEY, text: nextOrderJson });
|
|
20346
|
+
if (result.store) {
|
|
20347
|
+
stylesRef.current = JSON.stringify(result.store);
|
|
20348
|
+
nodes.push({ key: STYLE_STORE_KEY, text: stylesRef.current });
|
|
20349
|
+
}
|
|
20350
|
+
const nextContent = { ...editContentRef.current };
|
|
20351
|
+
for (const key of Object.keys(nextContent)) {
|
|
20352
|
+
if (key.startsWith(AI_SLOT_KEY_PREFIX) && result.slotPrefixes.some((prefix) => key.startsWith(prefix)) && nextContent[key] !== "") {
|
|
20353
|
+
nextContent[key] = "";
|
|
20354
|
+
nodes.push({ key, text: "" });
|
|
20355
|
+
}
|
|
20356
|
+
}
|
|
20357
|
+
editContentRef.current = nextContent;
|
|
20358
|
+
applyAiSectionsToDom(result.state);
|
|
20359
|
+
applyStylesToDom(parseStyleStore(stylesRef.current));
|
|
20360
|
+
return nodes;
|
|
20361
|
+
};
|
|
20220
20362
|
const handleHydrate = (e) => {
|
|
20221
20363
|
if (e.data?.type !== "ow:hydrate") return;
|
|
20222
20364
|
const content = e.data.content;
|
|
@@ -20285,6 +20427,11 @@ function OhhwellsBridge() {
|
|
|
20285
20427
|
reconcileFooterOrderFromContent(editContentRef.current);
|
|
20286
20428
|
syncNavigationDragCursorAttrs();
|
|
20287
20429
|
enforceLinkHrefs();
|
|
20430
|
+
const hydrateReapExclude = /* @__PURE__ */ new Set();
|
|
20431
|
+
const hydratePendingUndo = pendingDeleteUndoRef.current;
|
|
20432
|
+
if (hydratePendingUndo?.sectionInstanceId) hydrateReapExclude.add(hydratePendingUndo.sectionInstanceId);
|
|
20433
|
+
const reapNodes = reapCommittedAiSections(hydrateReapExclude);
|
|
20434
|
+
if (reapNodes.length > 0) postToParentRef.current({ type: "ow:change", nodes: reapNodes });
|
|
20288
20435
|
const hydratedHeight = document.body.scrollHeight;
|
|
20289
20436
|
if (hydratedHeight > 50) postToParentRef.current({ type: "ow:height", height: hydratedHeight });
|
|
20290
20437
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
@@ -20469,8 +20616,11 @@ function OhhwellsBridge() {
|
|
|
20469
20616
|
if (!instanceId || !direction) return;
|
|
20470
20617
|
const entries = moveSectionInstance(instanceId, direction, window.location.pathname);
|
|
20471
20618
|
if (!entries) return;
|
|
20472
|
-
const orderJson = JSON.stringify(
|
|
20619
|
+
const orderJson = JSON.stringify(
|
|
20620
|
+
mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
|
|
20621
|
+
);
|
|
20473
20622
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
20623
|
+
applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
|
|
20474
20624
|
setAiSectionOrder(orderJson, window.location.pathname);
|
|
20475
20625
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
20476
20626
|
window.dispatchEvent(new Event("resize"));
|
|
@@ -20516,8 +20666,11 @@ function OhhwellsBridge() {
|
|
|
20516
20666
|
const currentEntries = getPageSectionOrderEntries(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname);
|
|
20517
20667
|
const entries = deleteSectionInstance(instanceId, window.location.pathname, currentEntries);
|
|
20518
20668
|
if (!entries) return;
|
|
20519
|
-
const orderJson = JSON.stringify(
|
|
20669
|
+
const orderJson = JSON.stringify(
|
|
20670
|
+
mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
|
|
20671
|
+
);
|
|
20520
20672
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: orderJson };
|
|
20673
|
+
setAiSectionOrder(orderJson, window.location.pathname);
|
|
20521
20674
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: orderJson }] });
|
|
20522
20675
|
aiSectionApiRef.current?.clear();
|
|
20523
20676
|
window.dispatchEvent(new Event("resize"));
|
|
@@ -20526,6 +20679,7 @@ function OhhwellsBridge() {
|
|
|
20526
20679
|
const actionId = newInstanceId();
|
|
20527
20680
|
pendingDeleteUndoRef.current = {
|
|
20528
20681
|
actionId,
|
|
20682
|
+
sectionInstanceId: instanceId,
|
|
20529
20683
|
restore: () => {
|
|
20530
20684
|
const restoredEntries = getPageSectionOrderEntries(
|
|
20531
20685
|
editContentRef.current[SECTION_ORDER_KEY],
|
|
@@ -20533,8 +20687,11 @@ function OhhwellsBridge() {
|
|
|
20533
20687
|
);
|
|
20534
20688
|
const restored = restoreSectionInstance(instanceId, window.location.pathname, restoredEntries);
|
|
20535
20689
|
if (!restored) return;
|
|
20536
|
-
const restoredJson = JSON.stringify(
|
|
20690
|
+
const restoredJson = JSON.stringify(
|
|
20691
|
+
mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, restored)
|
|
20692
|
+
);
|
|
20537
20693
|
editContentRef.current = { ...editContentRef.current, [SECTION_ORDER_KEY]: restoredJson };
|
|
20694
|
+
setAiSectionOrder(restoredJson, window.location.pathname);
|
|
20538
20695
|
postToParentRef.current({ type: "ow:change", nodes: [{ key: SECTION_ORDER_KEY, text: restoredJson }] });
|
|
20539
20696
|
window.dispatchEvent(new Event("resize"));
|
|
20540
20697
|
const restoreHeight = document.body.scrollHeight;
|
|
@@ -20560,7 +20717,9 @@ function OhhwellsBridge() {
|
|
|
20560
20717
|
const result = duplicateSectionInstance(instanceId, newId, window.location.pathname, currentEntries);
|
|
20561
20718
|
if (!result) return;
|
|
20562
20719
|
const { entries, keyRekeys } = result;
|
|
20563
|
-
const orderJson = JSON.stringify(
|
|
20720
|
+
const orderJson = JSON.stringify(
|
|
20721
|
+
mergePageSectionOrder(editContentRef.current[SECTION_ORDER_KEY], window.location.pathname, entries)
|
|
20722
|
+
);
|
|
20564
20723
|
const nodes = [{ key: SECTION_ORDER_KEY, text: orderJson }];
|
|
20565
20724
|
for (const { from, to } of keyRekeys) {
|
|
20566
20725
|
const inherited = editContentRef.current[from];
|
|
@@ -20570,6 +20729,7 @@ function OhhwellsBridge() {
|
|
|
20570
20729
|
...editContentRef.current,
|
|
20571
20730
|
...Object.fromEntries(nodes.map(({ key, text }) => [key, text]))
|
|
20572
20731
|
};
|
|
20732
|
+
applyPersistedOrder(getPageSectionOrderEntries(orderJson, window.location.pathname));
|
|
20573
20733
|
setAiSectionOrder(orderJson, window.location.pathname);
|
|
20574
20734
|
postToParentRef.current({ type: "ow:change", nodes });
|
|
20575
20735
|
window.dispatchEvent(new Event("resize"));
|
|
@@ -20833,6 +20993,10 @@ function OhhwellsBridge() {
|
|
|
20833
20993
|
};
|
|
20834
20994
|
const handleSave = (e) => {
|
|
20835
20995
|
if (e.data?.type !== "ow:save") return;
|
|
20996
|
+
const pendingUndo = pendingDeleteUndoRef.current;
|
|
20997
|
+
const reapExclude = /* @__PURE__ */ new Set();
|
|
20998
|
+
if (pendingUndo?.sectionInstanceId) reapExclude.add(pendingUndo.sectionInstanceId);
|
|
20999
|
+
const reapNodes = reapCommittedAiSections(reapExclude);
|
|
20836
21000
|
const nodes = collectEditableNodes(editContentRef.current);
|
|
20837
21001
|
const tracker = document.querySelector("[data-ohw-sections-tracker]");
|
|
20838
21002
|
if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
|
|
@@ -20852,6 +21016,11 @@ function OhhwellsBridge() {
|
|
|
20852
21016
|
const text = specs.length ? JSON.stringify(specs) : editContentRef.current[fieldsKey(formKey)];
|
|
20853
21017
|
if (text) nodes.push({ key: fieldsKey(formKey), type: "text", text });
|
|
20854
21018
|
});
|
|
21019
|
+
for (const reapNode of reapNodes) {
|
|
21020
|
+
if (reapNode.key.startsWith(AI_SLOT_KEY_PREFIX) && !nodes.some((n) => n.key === reapNode.key)) {
|
|
21021
|
+
nodes.push({ key: reapNode.key, type: "text", text: reapNode.text });
|
|
21022
|
+
}
|
|
21023
|
+
}
|
|
20855
21024
|
postToParentRef.current({ type: "ow:save-result", nodes });
|
|
20856
21025
|
};
|
|
20857
21026
|
const handleInsertSection = (e) => {
|
|
@@ -21473,7 +21642,7 @@ function OhhwellsBridge() {
|
|
|
21473
21642
|
postToParent2({
|
|
21474
21643
|
type: "ow:ready",
|
|
21475
21644
|
version: "1",
|
|
21476
|
-
bridgeVersion: "0.1.
|
|
21645
|
+
bridgeVersion: "0.1.95",
|
|
21477
21646
|
path: pathname,
|
|
21478
21647
|
nodes: collectEditableNodes(editContentRef.current),
|
|
21479
21648
|
sections
|