@ohhwells/bridge 0.1.29-next.20 → 0.1.29-next.21
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/README.md +58 -0
- package/dist/index.cjs +159 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +162 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -184,6 +184,64 @@ Add `data-ohw-editable` and `data-ohw-key` to any element the studio owner shoul
|
|
|
184
184
|
|
|
185
185
|
---
|
|
186
186
|
|
|
187
|
+
## SchedulingWidget (vibe-coder placement)
|
|
188
|
+
|
|
189
|
+
`SchedulingWidget` lets a template builder embed a scheduling/booking section directly in their template JSX, without going through the "Add Section" flow. The canvas editor treats it exactly like a dynamically-inserted scheduling widget — the studio owner can connect a schedule, switch it, and clear it.
|
|
190
|
+
|
|
191
|
+
### Basic usage
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
import { SchedulingWidget } from '@ohhwells/bridge'
|
|
195
|
+
|
|
196
|
+
export default function ClassesPage() {
|
|
197
|
+
return (
|
|
198
|
+
<>
|
|
199
|
+
<PageHeader ... />
|
|
200
|
+
<ClassLibrary ... />
|
|
201
|
+
<SchedulingWidget />
|
|
202
|
+
<WordmarkBand ... />
|
|
203
|
+
</>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
No props are required. The widget auto-generates a stable identity via React's `useId()` so the bridge can track which schedule is connected to it across saves.
|
|
209
|
+
|
|
210
|
+
### Props
|
|
211
|
+
|
|
212
|
+
| Prop | Type | Default | Description |
|
|
213
|
+
|---|---|---|---|
|
|
214
|
+
| `insertAfter` | `string` | auto | Stable identifier used as the tracker key. Only set this manually if you need two `SchedulingWidget`s on the same page. |
|
|
215
|
+
| `initialScheduleId` | `string \| null` | `undefined` | Set by the bridge internally after hydration. Do not pass this yourself. |
|
|
216
|
+
| `notifyOnConnect` | `boolean` | `false` | Set by the bridge internally. Do not pass this yourself. |
|
|
217
|
+
|
|
218
|
+
### How it works
|
|
219
|
+
|
|
220
|
+
1. The widget renders immediately with a loading skeleton.
|
|
221
|
+
2. It sends `ow:request-schedule-config` to the bridge (running in the same window).
|
|
222
|
+
3. The bridge looks up the tracker for a saved `scheduleId` for this widget:
|
|
223
|
+
- **Found** → responds with `ow:schedule-config { scheduleId }` → widget loads that schedule.
|
|
224
|
+
- **Not found (first time)** → bridge adopts the widget (adds it to the tracker, notifies the canvas editor), responds with `scheduleId: null` → widget shows empty state with an "Add Schedule" button.
|
|
225
|
+
4. In the canvas editor, the studio owner clicks "Add Schedule" → selects a schedule → the widget updates and the connection is saved.
|
|
226
|
+
5. On the live site, the widget fetches the saved schedule by ID and renders it.
|
|
227
|
+
|
|
228
|
+
### Multiple widgets on one page
|
|
229
|
+
|
|
230
|
+
Each `SchedulingWidget` must have a unique `insertAfter` to be tracked independently:
|
|
231
|
+
|
|
232
|
+
```tsx
|
|
233
|
+
<SchedulingWidget insertAfter="classes-morning" />
|
|
234
|
+
<SchedulingWidget insertAfter="classes-evening" />
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
If you omit `insertAfter` on both, `useId()` auto-generates different stable IDs for each, so they are tracked separately anyway.
|
|
238
|
+
|
|
239
|
+
### Empty state on live site
|
|
240
|
+
|
|
241
|
+
If the studio owner has never connected a schedule to this widget, it renders nothing on the live site (no empty placeholder is shown to visitors).
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
187
245
|
## Editable states (advanced)
|
|
188
246
|
|
|
189
247
|
For elements with multiple display states (e.g. a contact form with default/success/error views), wrap each state in a `data-ohw-state-view` and mark the container with `data-ohw-editable-state`:
|
package/dist/index.cjs
CHANGED
|
@@ -212,13 +212,6 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
|
212
212
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
213
213
|
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
214
214
|
var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
215
|
-
function getApiDomain() {
|
|
216
|
-
const h = window.location.hostname;
|
|
217
|
-
for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
|
|
218
|
-
if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
|
|
219
|
-
}
|
|
220
|
-
return h;
|
|
221
|
-
}
|
|
222
215
|
function classRunsOnDate(cls, date, type) {
|
|
223
216
|
if (type === "FIXED") {
|
|
224
217
|
const d = String(date.getDate()).padStart(2, "0");
|
|
@@ -264,10 +257,10 @@ function buildTimezoneLabel(tz) {
|
|
|
264
257
|
return tz;
|
|
265
258
|
}
|
|
266
259
|
}
|
|
267
|
-
function EmptyState({ inEditor }) {
|
|
260
|
+
function EmptyState({ inEditor, insertAfter }) {
|
|
268
261
|
const handleAddSchedule = () => {
|
|
269
262
|
if (inEditor) {
|
|
270
|
-
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
263
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
271
264
|
}
|
|
272
265
|
};
|
|
273
266
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
|
|
@@ -492,18 +485,21 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
492
485
|
const booked = getBookingsOnDate(cls, selectedDate);
|
|
493
486
|
const available = cls.maxParticipants - booked;
|
|
494
487
|
const isFull = available <= 0;
|
|
488
|
+
const isPrivate = cls.maxParticipants === 1;
|
|
495
489
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
496
490
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
|
|
497
491
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
|
|
498
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 sm:w-[120px] sm:shrink-0", children: [
|
|
493
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }),
|
|
494
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
495
|
+
"span",
|
|
496
|
+
{
|
|
497
|
+
className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium w-fit",
|
|
498
|
+
style: isPrivate ? { background: "#DDFBE3", color: "#199130", border: "1px solid #199130", fontWeight: 500 } : { background: "#EEEDFF", color: "#5953FF", border: "1px solid #5953FF", fontWeight: 500 },
|
|
499
|
+
children: isPrivate ? "PRIVATE" : "GROUP"
|
|
500
|
+
}
|
|
501
|
+
)
|
|
502
|
+
] }),
|
|
507
503
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
508
504
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
509
505
|
available,
|
|
@@ -576,7 +572,9 @@ function CalendarFoldIcon() {
|
|
|
576
572
|
}
|
|
577
573
|
);
|
|
578
574
|
}
|
|
579
|
-
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
|
|
575
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter: insertAfterProp }) {
|
|
576
|
+
const autoId = (0, import_react2.useId)();
|
|
577
|
+
const insertAfter = insertAfterProp ?? autoId;
|
|
580
578
|
const [schedule, setSchedule] = (0, import_react2.useState)(null);
|
|
581
579
|
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
582
580
|
const [inEditor, setInEditor] = (0, import_react2.useState)(false);
|
|
@@ -606,88 +604,99 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
606
604
|
if (typeof window === "undefined") return;
|
|
607
605
|
const isInEditor = window.self !== window.top;
|
|
608
606
|
setInEditor(isInEditor);
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
607
|
+
function loadWithId(effectiveId) {
|
|
608
|
+
if (isInEditor) {
|
|
609
|
+
const startEmpty = effectiveId === null;
|
|
610
|
+
if (startEmpty) setLoading(false);
|
|
611
|
+
let initialized = startEmpty;
|
|
612
|
+
const timer = !startEmpty ? setTimeout(() => {
|
|
613
|
+
if (!initialized) {
|
|
614
|
+
initialized = true;
|
|
615
|
+
setLoading(false);
|
|
616
|
+
}
|
|
617
|
+
}, 5e3) : null;
|
|
618
|
+
const handler = (e) => {
|
|
619
|
+
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
620
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
621
|
+
setSchedule(null);
|
|
622
|
+
setLoading(false);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (e.data?.type === "ow:switch-schedule") {
|
|
626
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
627
|
+
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
628
|
+
initialized = false;
|
|
629
|
+
setLoading(true);
|
|
630
|
+
window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
634
|
+
if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
|
|
635
|
+
if (initialized && switchScheduleIdRef.current === null) return;
|
|
615
636
|
initialized = true;
|
|
637
|
+
if (timer) clearTimeout(timer);
|
|
638
|
+
const schedules = e.data.schedules ?? [];
|
|
639
|
+
const isSwitching = switchScheduleIdRef.current !== null;
|
|
640
|
+
const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : effectiveId ? schedules.find((s) => s.id === effectiveId) ?? schedules[0] ?? null : schedules[0] ?? null;
|
|
641
|
+
switchScheduleIdRef.current = null;
|
|
642
|
+
setSchedule(target);
|
|
616
643
|
setLoading(false);
|
|
644
|
+
if (notifyOnConnect && target && !isSwitching) {
|
|
645
|
+
window.parent.postMessage({
|
|
646
|
+
type: "ow:schedule-connected",
|
|
647
|
+
schedule: { id: target.id, name: target.name },
|
|
648
|
+
insertAfter
|
|
649
|
+
}, "*");
|
|
650
|
+
window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
window.addEventListener("message", handler);
|
|
654
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
655
|
+
return () => {
|
|
656
|
+
window.removeEventListener("message", handler);
|
|
657
|
+
if (timer) clearTimeout(timer);
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (!effectiveId) {
|
|
661
|
+
setLoading(false);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
;
|
|
665
|
+
(async () => {
|
|
666
|
+
try {
|
|
667
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
|
|
668
|
+
const data = await res.json();
|
|
669
|
+
setSchedule(data?.id ? data : null);
|
|
670
|
+
} catch {
|
|
617
671
|
}
|
|
618
|
-
}, 5e3) : null;
|
|
619
|
-
const handler = (e) => {
|
|
620
|
-
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
621
|
-
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
622
|
-
setSchedule(null);
|
|
623
|
-
setLoading(false);
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
if (e.data?.type === "ow:switch-schedule") {
|
|
627
|
-
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
628
|
-
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
629
|
-
initialized = false;
|
|
630
|
-
setLoading(true);
|
|
631
|
-
window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
635
|
-
if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
|
|
636
|
-
if (initialized && switchScheduleIdRef.current === null) return;
|
|
637
|
-
initialized = true;
|
|
638
|
-
if (timer) clearTimeout(timer);
|
|
639
|
-
const schedules = e.data.schedules ?? [];
|
|
640
|
-
const isSwitching = switchScheduleIdRef.current !== null;
|
|
641
|
-
const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : initialScheduleId ? schedules.find((s) => s.id === initialScheduleId) ?? schedules[0] ?? null : schedules[0] ?? null;
|
|
642
|
-
switchScheduleIdRef.current = null;
|
|
643
|
-
setSchedule(target);
|
|
644
672
|
setLoading(false);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
673
|
+
})();
|
|
674
|
+
}
|
|
675
|
+
if (initialScheduleId === void 0) {
|
|
676
|
+
let done = false;
|
|
677
|
+
let innerCleanup;
|
|
678
|
+
const timer = setTimeout(() => {
|
|
679
|
+
if (!done) {
|
|
680
|
+
done = true;
|
|
681
|
+
setLoading(false);
|
|
652
682
|
}
|
|
683
|
+
}, 2e3);
|
|
684
|
+
const configHandler = (e) => {
|
|
685
|
+
if (e.data?.type !== "ow:schedule-config" || e.data.insertAfter !== insertAfter || done) return;
|
|
686
|
+
done = true;
|
|
687
|
+
clearTimeout(timer);
|
|
688
|
+
window.removeEventListener("message", configHandler);
|
|
689
|
+
innerCleanup = loadWithId(e.data.scheduleId ?? null);
|
|
653
690
|
};
|
|
654
|
-
window.addEventListener("message",
|
|
655
|
-
|
|
691
|
+
window.addEventListener("message", configHandler);
|
|
692
|
+
window.postMessage({ type: "ow:request-schedule-config", insertAfter }, "*");
|
|
656
693
|
return () => {
|
|
657
|
-
window.removeEventListener("message",
|
|
658
|
-
|
|
694
|
+
window.removeEventListener("message", configHandler);
|
|
695
|
+
clearTimeout(timer);
|
|
696
|
+
innerCleanup?.();
|
|
659
697
|
};
|
|
660
698
|
}
|
|
661
|
-
|
|
662
|
-
setLoading(false);
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
;
|
|
666
|
-
(async () => {
|
|
667
|
-
try {
|
|
668
|
-
if (initialScheduleId) {
|
|
669
|
-
const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
|
|
670
|
-
const data = await res.json();
|
|
671
|
-
setSchedule(data?.id ? data : null);
|
|
672
|
-
} else {
|
|
673
|
-
const domain = getApiDomain();
|
|
674
|
-
const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
|
|
675
|
-
const slugs = await sitemapRes.json();
|
|
676
|
-
if (!Array.isArray(slugs) || !slugs.length) {
|
|
677
|
-
setLoading(false);
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const { slug } = [...slugs].sort(
|
|
681
|
-
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
682
|
-
)[0];
|
|
683
|
-
const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
|
|
684
|
-
const data = await res.json();
|
|
685
|
-
setSchedule(data?.id ? data : null);
|
|
686
|
-
}
|
|
687
|
-
} catch {
|
|
688
|
-
}
|
|
689
|
-
setLoading(false);
|
|
690
|
-
})();
|
|
699
|
+
return loadWithId(initialScheduleId) ?? void 0;
|
|
691
700
|
}, []);
|
|
692
701
|
const timezoneLabel = schedule?.timezone ? (() => {
|
|
693
702
|
try {
|
|
@@ -722,6 +731,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
722
731
|
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
723
732
|
}
|
|
724
733
|
};
|
|
734
|
+
if (!inEditor && !loading && !schedule) return null;
|
|
725
735
|
const sectionId = `scheduling-${insertAfter}`;
|
|
726
736
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
727
737
|
"section",
|
|
@@ -767,7 +777,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
767
777
|
] }),
|
|
768
778
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
769
779
|
timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
770
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor, insertAfter }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
771
781
|
ScheduleView,
|
|
772
782
|
{
|
|
773
783
|
schedule,
|
|
@@ -5325,9 +5335,14 @@ function initSectionsFromContent(content, removeExisting = false) {
|
|
|
5325
5335
|
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
5326
5336
|
if (inEditor) getSectionsTracker().textContent = raw;
|
|
5327
5337
|
const pageEntries = getPageSchedulingEntries(raw);
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5338
|
+
const preExisting = pageEntries.filter((e) => !isSchedulingWidgetMissing(e));
|
|
5339
|
+
const notifyForEntry = inEditor ? (e) => !e.scheduleId : false;
|
|
5340
|
+
mountSchedulingEntries(pageEntries, notifyForEntry);
|
|
5341
|
+
requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
|
|
5342
|
+
setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
|
|
5343
|
+
for (const entry of preExisting) {
|
|
5344
|
+
window.postMessage({ type: "ow:schedule-config", insertAfter: entry.insertAfter, scheduleId: entry.scheduleId ?? null }, "*");
|
|
5345
|
+
}
|
|
5331
5346
|
} catch {
|
|
5332
5347
|
}
|
|
5333
5348
|
}
|
|
@@ -5386,8 +5401,9 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
|
|
|
5386
5401
|
const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
|
|
5387
5402
|
for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
|
|
5388
5403
|
for (let i = pending.length - 1; i >= 0; i--) {
|
|
5389
|
-
const
|
|
5390
|
-
|
|
5404
|
+
const entry = pending[i];
|
|
5405
|
+
const shouldNotify = typeof notifyOnConnect === "function" ? notifyOnConnect(entry) : notifyOnConnect;
|
|
5406
|
+
if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
|
|
5391
5407
|
pending.splice(i, 1);
|
|
5392
5408
|
}
|
|
5393
5409
|
}
|
|
@@ -5865,6 +5881,8 @@ function OhhwellsBridge() {
|
|
|
5865
5881
|
});
|
|
5866
5882
|
const postToParentRef = (0, import_react5.useRef)(postToParent);
|
|
5867
5883
|
postToParentRef.current = postToParent;
|
|
5884
|
+
const sectionsLoadedRef = (0, import_react5.useRef)(false);
|
|
5885
|
+
const pendingScheduleConfigRequests = (0, import_react5.useRef)([]);
|
|
5868
5886
|
const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
|
|
5869
5887
|
const [toggleState, setToggleState] = (0, import_react5.useState)(null);
|
|
5870
5888
|
const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
|
|
@@ -5970,6 +5988,29 @@ function OhhwellsBridge() {
|
|
|
5970
5988
|
const refreshStateRules = (0, import_react5.useCallback)(() => {
|
|
5971
5989
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
5972
5990
|
}, []);
|
|
5991
|
+
const processConfigRequest = (0, import_react5.useCallback)((insertAfterVal) => {
|
|
5992
|
+
const tracker = getSectionsTracker();
|
|
5993
|
+
let entries = [];
|
|
5994
|
+
try {
|
|
5995
|
+
entries = JSON.parse(tracker.textContent || "[]");
|
|
5996
|
+
} catch {
|
|
5997
|
+
}
|
|
5998
|
+
const path = window.location.pathname;
|
|
5999
|
+
const entry = entries.find(
|
|
6000
|
+
(e) => e.type === "scheduling" && e.insertAfter === insertAfterVal && (!e.pagePath || e.pagePath === path)
|
|
6001
|
+
);
|
|
6002
|
+
if (entry) {
|
|
6003
|
+
window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: entry.scheduleId ?? null }, "*");
|
|
6004
|
+
return;
|
|
6005
|
+
}
|
|
6006
|
+
const newEntry = { type: "scheduling", insertAfter: insertAfterVal, pagePath: path };
|
|
6007
|
+
entries.push(newEntry);
|
|
6008
|
+
tracker.textContent = JSON.stringify(entries);
|
|
6009
|
+
if (isEditMode) {
|
|
6010
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
6011
|
+
}
|
|
6012
|
+
window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
|
|
6013
|
+
}, [isEditMode]);
|
|
5973
6014
|
const deactivate = (0, import_react5.useCallback)(() => {
|
|
5974
6015
|
const el = activeElRef.current;
|
|
5975
6016
|
if (!el) return;
|
|
@@ -6046,6 +6087,8 @@ function OhhwellsBridge() {
|
|
|
6046
6087
|
applyLinkByKey(key, val);
|
|
6047
6088
|
}
|
|
6048
6089
|
initSectionsFromContent(content, true);
|
|
6090
|
+
sectionsLoadedRef.current = true;
|
|
6091
|
+
pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
|
|
6049
6092
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
6050
6093
|
}) : Promise.resolve();
|
|
6051
6094
|
};
|
|
@@ -6742,6 +6785,8 @@ function OhhwellsBridge() {
|
|
|
6742
6785
|
}
|
|
6743
6786
|
if (sectionsJson) {
|
|
6744
6787
|
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
6788
|
+
sectionsLoadedRef.current = true;
|
|
6789
|
+
pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
|
|
6745
6790
|
}
|
|
6746
6791
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
6747
6792
|
};
|
|
@@ -7014,6 +7059,22 @@ function OhhwellsBridge() {
|
|
|
7014
7059
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
7015
7060
|
};
|
|
7016
7061
|
}, [isEditMode, refreshStateRules]);
|
|
7062
|
+
(0, import_react5.useEffect)(() => {
|
|
7063
|
+
const handler = (e) => {
|
|
7064
|
+
if (e.data?.type !== "ow:request-schedule-config") return;
|
|
7065
|
+
const insertAfterVal = e.data.insertAfter;
|
|
7066
|
+
if (!insertAfterVal) return;
|
|
7067
|
+
if (!sectionsLoadedRef.current) {
|
|
7068
|
+
if (!pendingScheduleConfigRequests.current.includes(insertAfterVal)) {
|
|
7069
|
+
pendingScheduleConfigRequests.current.push(insertAfterVal);
|
|
7070
|
+
}
|
|
7071
|
+
return;
|
|
7072
|
+
}
|
|
7073
|
+
processConfigRequest(insertAfterVal);
|
|
7074
|
+
};
|
|
7075
|
+
window.addEventListener("message", handler);
|
|
7076
|
+
return () => window.removeEventListener("message", handler);
|
|
7077
|
+
}, [processConfigRequest]);
|
|
7017
7078
|
(0, import_react5.useEffect)(() => {
|
|
7018
7079
|
if (!isEditMode) return;
|
|
7019
7080
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|