@ohhwells/bridge 0.1.16 → 0.1.17-next.15
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 +237 -74
- package/dist/index.cjs +2292 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -2
- package/dist/index.d.ts +68 -2
- package/dist/index.js +2276 -119
- package/dist/index.js.map +1 -1
- package/dist/styles.css +829 -55
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,752 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/OhhwellsBridge.tsx
|
|
4
|
-
import
|
|
4
|
+
import React5, { useCallback as useCallback2, useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState5 } from "react";
|
|
5
|
+
import { createRoot } from "react-dom/client";
|
|
6
|
+
import { flushSync } from "react-dom";
|
|
7
|
+
|
|
8
|
+
// src/ui/SchedulingWidget.tsx
|
|
9
|
+
import { useEffect, useMemo, useRef, useState as useState2 } from "react";
|
|
10
|
+
|
|
11
|
+
// src/ui/EmailCaptureModal.tsx
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { Dialog } from "radix-ui";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
function Spinner() {
|
|
16
|
+
return /* @__PURE__ */ jsxs(
|
|
17
|
+
"svg",
|
|
18
|
+
{
|
|
19
|
+
width: "32",
|
|
20
|
+
height: "32",
|
|
21
|
+
viewBox: "0 0 32 32",
|
|
22
|
+
fill: "none",
|
|
23
|
+
style: { animation: "ohw-spin 0.75s linear infinite" },
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ jsx("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
|
|
26
|
+
/* @__PURE__ */ jsx("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
|
|
27
|
+
/* @__PURE__ */ jsx(
|
|
28
|
+
"path",
|
|
29
|
+
{
|
|
30
|
+
d: "M16 4a12 12 0 0 1 12 12",
|
|
31
|
+
stroke: "#3b82f6",
|
|
32
|
+
strokeWidth: "3",
|
|
33
|
+
strokeLinecap: "round"
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
41
|
+
const [email, setEmail] = useState("");
|
|
42
|
+
const [submittedEmail, setSubmittedEmail] = useState("");
|
|
43
|
+
const [loading, setLoading] = useState(false);
|
|
44
|
+
const [success, setSuccess] = useState(false);
|
|
45
|
+
const [error, setError] = useState(null);
|
|
46
|
+
const isBook = title === "Confirm your spot";
|
|
47
|
+
const handleSubmit = async () => {
|
|
48
|
+
if (!email.trim()) return;
|
|
49
|
+
setLoading(true);
|
|
50
|
+
setError(null);
|
|
51
|
+
try {
|
|
52
|
+
await onSubmit(email.trim());
|
|
53
|
+
setSubmittedEmail(email.trim());
|
|
54
|
+
setSuccess(true);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
57
|
+
} finally {
|
|
58
|
+
setLoading(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const handleKeyDown = (e) => {
|
|
62
|
+
if (e.key === "Enter" && !loading) handleSubmit();
|
|
63
|
+
};
|
|
64
|
+
return /* @__PURE__ */ jsx(Dialog.Root, { open: true, onOpenChange: (open) => {
|
|
65
|
+
if (!open) onClose();
|
|
66
|
+
}, children: /* @__PURE__ */ jsxs(Dialog.Portal, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
Dialog.Overlay,
|
|
69
|
+
{
|
|
70
|
+
className: "fixed inset-0 z-50",
|
|
71
|
+
style: { background: "rgba(0,0,0,0.45)" }
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsxs(
|
|
75
|
+
Dialog.Content,
|
|
76
|
+
{
|
|
77
|
+
className: "fixed left-1/2 top-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 bg-white rounded-xl shadow-xl outline-none font-body box-border overflow-hidden",
|
|
78
|
+
style: { maxWidth: 400 },
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 p-6", children: [
|
|
81
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
82
|
+
/* @__PURE__ */ jsx(
|
|
83
|
+
Dialog.Title,
|
|
84
|
+
{
|
|
85
|
+
className: "font-body text-xl font-bold m-0 leading-tight",
|
|
86
|
+
style: { color: "var(--color-dark,#200C02)" },
|
|
87
|
+
children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
|
|
88
|
+
}
|
|
89
|
+
),
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
Dialog.Description,
|
|
92
|
+
{
|
|
93
|
+
className: "font-body text-sm m-0",
|
|
94
|
+
style: { color: "#6B7280" },
|
|
95
|
+
children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
] }),
|
|
99
|
+
/* @__PURE__ */ jsx(
|
|
100
|
+
Dialog.Close,
|
|
101
|
+
{
|
|
102
|
+
className: "shrink-0 w-7 h-7 flex items-center justify-center rounded-full bg-transparent border-none cursor-pointer mt-0.5 absolute top-2.5 right-1.5",
|
|
103
|
+
style: { color: "#6B7280" },
|
|
104
|
+
"aria-label": "Close",
|
|
105
|
+
children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
106
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
107
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
108
|
+
] })
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
] }),
|
|
112
|
+
!success && (loading ? (
|
|
113
|
+
/* Loading state — full body replaced */
|
|
114
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
|
|
115
|
+
/* @__PURE__ */ jsx(Spinner, {}),
|
|
116
|
+
/* @__PURE__ */ jsx("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
|
|
117
|
+
] })
|
|
118
|
+
) : (
|
|
119
|
+
/* Email form */
|
|
120
|
+
/* @__PURE__ */ jsxs("div", { className: "px-7 pt-2 pb-6", children: [
|
|
121
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5 mb-8", children: [
|
|
122
|
+
/* @__PURE__ */ jsx("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
|
|
123
|
+
/* @__PURE__ */ jsx(
|
|
124
|
+
"input",
|
|
125
|
+
{
|
|
126
|
+
type: "email",
|
|
127
|
+
value: email,
|
|
128
|
+
onChange: (e) => setEmail(e.target.value),
|
|
129
|
+
onKeyDown: handleKeyDown,
|
|
130
|
+
placeholder: "hello@gmail.com",
|
|
131
|
+
autoFocus: true,
|
|
132
|
+
className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
|
|
133
|
+
style: {
|
|
134
|
+
borderColor: "#E7E5E4",
|
|
135
|
+
background: "#fff",
|
|
136
|
+
color: "var(--color-dark,#200C02)"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
error && /* @__PURE__ */ jsx("p", { className: "font-body text-xs text-red-500 m-0", children: error })
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
143
|
+
"button",
|
|
144
|
+
{
|
|
145
|
+
onClick: handleSubmit,
|
|
146
|
+
disabled: !email.trim(),
|
|
147
|
+
className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
|
|
148
|
+
style: {
|
|
149
|
+
background: "var(--color-primary,#3D312B)",
|
|
150
|
+
color: "var(--color-light,#FEFFF0)"
|
|
151
|
+
},
|
|
152
|
+
children: "Send"
|
|
153
|
+
}
|
|
154
|
+
) })
|
|
155
|
+
] })
|
|
156
|
+
))
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
] }) });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/ui/SchedulingWidget.tsx
|
|
164
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
165
|
+
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
166
|
+
var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
167
|
+
function getApiDomain() {
|
|
168
|
+
const h = window.location.hostname;
|
|
169
|
+
for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
|
|
170
|
+
if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
|
|
171
|
+
}
|
|
172
|
+
return h;
|
|
173
|
+
}
|
|
174
|
+
function classRunsOnDate(cls, date, type) {
|
|
175
|
+
if (type === "FIXED") {
|
|
176
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
177
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
178
|
+
return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
|
|
179
|
+
}
|
|
180
|
+
return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
|
|
181
|
+
}
|
|
182
|
+
function formatClassTime(cls) {
|
|
183
|
+
let h = parseInt(cls.startTime.hour, 10);
|
|
184
|
+
const m = parseInt(cls.startTime.minutes, 10);
|
|
185
|
+
if (cls.startTime.time === "PM" && h !== 12) h += 12;
|
|
186
|
+
if (cls.startTime.time === "AM" && h === 12) h = 0;
|
|
187
|
+
const endTotal = h * 60 + m + cls.duration;
|
|
188
|
+
const eh = Math.floor(endTotal / 60) % 24;
|
|
189
|
+
const em = endTotal % 60;
|
|
190
|
+
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
191
|
+
}
|
|
192
|
+
function getBookingsOnDate(cls, date) {
|
|
193
|
+
if (!cls.bookings?.length) return 0;
|
|
194
|
+
const ds = date.toISOString().split("T")[0];
|
|
195
|
+
return cls.bookings.filter((b) => {
|
|
196
|
+
try {
|
|
197
|
+
return new Date(b.classDate).toISOString().split("T")[0] === ds;
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}).length;
|
|
202
|
+
}
|
|
203
|
+
function buildTimezoneLabel(tz) {
|
|
204
|
+
try {
|
|
205
|
+
const now = /* @__PURE__ */ new Date();
|
|
206
|
+
const timeStr = new Intl.DateTimeFormat("en-US", {
|
|
207
|
+
hour: "2-digit",
|
|
208
|
+
minute: "2-digit",
|
|
209
|
+
hour12: false,
|
|
210
|
+
timeZone: tz
|
|
211
|
+
}).format(now);
|
|
212
|
+
const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
213
|
+
const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
|
|
214
|
+
return `${timeStr} (${offsetPart}) ${city} time`;
|
|
215
|
+
} catch {
|
|
216
|
+
return tz;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function EmptyState({ inEditor }) {
|
|
220
|
+
const handleAddSchedule = () => {
|
|
221
|
+
if (inEditor) {
|
|
222
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
return /* @__PURE__ */ jsxs2("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: [
|
|
226
|
+
/* @__PURE__ */ jsx2("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ jsxs2(
|
|
227
|
+
"svg",
|
|
228
|
+
{
|
|
229
|
+
width: "20",
|
|
230
|
+
height: "20",
|
|
231
|
+
viewBox: "0 0 24 24",
|
|
232
|
+
fill: "none",
|
|
233
|
+
stroke: "var(--color-accent, #A89B83)",
|
|
234
|
+
strokeWidth: "1.5",
|
|
235
|
+
strokeLinecap: "round",
|
|
236
|
+
strokeLinejoin: "round",
|
|
237
|
+
children: [
|
|
238
|
+
/* @__PURE__ */ jsx2("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
|
|
239
|
+
/* @__PURE__ */ jsx2("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
|
|
240
|
+
/* @__PURE__ */ jsx2("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
|
|
241
|
+
/* @__PURE__ */ jsx2("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
|
|
242
|
+
/* @__PURE__ */ jsx2("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
|
|
243
|
+
/* @__PURE__ */ jsx2("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
) }),
|
|
247
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-2", children: [
|
|
248
|
+
/* @__PURE__ */ jsx2("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
|
|
249
|
+
/* @__PURE__ */ jsx2("p", { className: "font-body text-base font-normal text-center text-(--color-accent,#A89B83) m-0", children: "Add a scheduling widget to get instant bookings." })
|
|
250
|
+
] }),
|
|
251
|
+
/* @__PURE__ */ jsx2(
|
|
252
|
+
"button",
|
|
253
|
+
{
|
|
254
|
+
onClick: handleAddSchedule,
|
|
255
|
+
className: "h-9 px-2 py-1.5 flex items-center justify-center bg-white border border-[#E7E5E4] rounded-md font-body text-sm font-medium text-(--color-dark,#200C02) cursor-pointer",
|
|
256
|
+
children: "Add Schedule"
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
] });
|
|
260
|
+
}
|
|
261
|
+
function LoadingSkeleton() {
|
|
262
|
+
const shimmer = {
|
|
263
|
+
background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
|
|
264
|
+
backgroundSize: "200% 100%",
|
|
265
|
+
animation: "ohw-shimmer 1.5s infinite"
|
|
266
|
+
};
|
|
267
|
+
return /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col gap-10", children: [
|
|
268
|
+
/* @__PURE__ */ jsx2("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
|
|
269
|
+
/* @__PURE__ */ jsx2("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
|
|
270
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
|
|
271
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
|
|
272
|
+
] }, i)) }),
|
|
273
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ jsxs2("div", { children: [
|
|
274
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:hidden", children: [
|
|
275
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0", children: [
|
|
276
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
|
|
277
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
|
|
278
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
|
|
279
|
+
] }),
|
|
280
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2", children: [
|
|
281
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
|
|
282
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
|
|
283
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
|
|
284
|
+
] })
|
|
285
|
+
] }),
|
|
286
|
+
/* @__PURE__ */ jsxs2("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
|
|
287
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
|
|
288
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2", children: [
|
|
289
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
|
|
290
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
|
|
293
|
+
/* @__PURE__ */ jsx2("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
|
|
294
|
+
] }),
|
|
295
|
+
i < 2 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
|
|
296
|
+
] }, i))
|
|
297
|
+
] });
|
|
298
|
+
}
|
|
299
|
+
function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
|
|
300
|
+
const todayMs = useMemo(() => {
|
|
301
|
+
const d = /* @__PURE__ */ new Date();
|
|
302
|
+
d.setHours(0, 0, 0, 0);
|
|
303
|
+
return d.getTime();
|
|
304
|
+
}, []);
|
|
305
|
+
const scrollRef = useRef(null);
|
|
306
|
+
const [canScrollLeft, setCanScrollLeft] = useState2(false);
|
|
307
|
+
const [canScrollRight, setCanScrollRight] = useState2(false);
|
|
308
|
+
const updateScrollState = () => {
|
|
309
|
+
const el = scrollRef.current;
|
|
310
|
+
if (!el) return;
|
|
311
|
+
setCanScrollLeft(el.scrollLeft > 0);
|
|
312
|
+
setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
313
|
+
};
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
const el = scrollRef.current;
|
|
316
|
+
if (!el) return;
|
|
317
|
+
updateScrollState();
|
|
318
|
+
el.addEventListener("scroll", updateScrollState);
|
|
319
|
+
const ro = new ResizeObserver(updateScrollState);
|
|
320
|
+
ro.observe(el);
|
|
321
|
+
return () => {
|
|
322
|
+
el.removeEventListener("scroll", updateScrollState);
|
|
323
|
+
ro.disconnect();
|
|
324
|
+
};
|
|
325
|
+
}, [dates]);
|
|
326
|
+
const scroll = (dir) => {
|
|
327
|
+
scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
|
|
328
|
+
};
|
|
329
|
+
const isDragging = useRef(false);
|
|
330
|
+
const dragStartX = useRef(0);
|
|
331
|
+
const dragStartScrollLeft = useRef(0);
|
|
332
|
+
const onDragStart = (e) => {
|
|
333
|
+
if (!scrollRef.current) return;
|
|
334
|
+
isDragging.current = true;
|
|
335
|
+
dragStartX.current = e.clientX;
|
|
336
|
+
dragStartScrollLeft.current = scrollRef.current.scrollLeft;
|
|
337
|
+
scrollRef.current.style.cursor = "grabbing";
|
|
338
|
+
scrollRef.current.style.userSelect = "none";
|
|
339
|
+
};
|
|
340
|
+
const onDragMove = (e) => {
|
|
341
|
+
if (!isDragging.current || !scrollRef.current) return;
|
|
342
|
+
const delta = e.clientX - dragStartX.current;
|
|
343
|
+
scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
|
|
344
|
+
};
|
|
345
|
+
const onDragEnd = () => {
|
|
346
|
+
if (!scrollRef.current) return;
|
|
347
|
+
isDragging.current = false;
|
|
348
|
+
scrollRef.current.style.cursor = "grab";
|
|
349
|
+
scrollRef.current.style.userSelect = "";
|
|
350
|
+
};
|
|
351
|
+
const datesWithClasses = useMemo(
|
|
352
|
+
() => new Set(dates.map(
|
|
353
|
+
(d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
|
|
354
|
+
).filter((i) => i >= 0)),
|
|
355
|
+
[dates, schedule]
|
|
356
|
+
);
|
|
357
|
+
const selectedDate = dates[selectedIdx];
|
|
358
|
+
const selectedClasses = useMemo(
|
|
359
|
+
() => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
|
|
360
|
+
[schedule, selectedDate]
|
|
361
|
+
);
|
|
362
|
+
return /* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col gap-10", children: [
|
|
363
|
+
/* @__PURE__ */ jsxs2("div", { className: "relative w-full", children: [
|
|
364
|
+
canScrollLeft && /* @__PURE__ */ jsx2(
|
|
365
|
+
"button",
|
|
366
|
+
{
|
|
367
|
+
onClick: () => scroll("left"),
|
|
368
|
+
className: "hidden sm:flex absolute left-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
|
|
369
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
370
|
+
"aria-label": "Scroll left",
|
|
371
|
+
children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx2("polyline", { points: "15 18 9 12 15 6" }) })
|
|
372
|
+
}
|
|
373
|
+
),
|
|
374
|
+
canScrollRight && /* @__PURE__ */ jsx2(
|
|
375
|
+
"button",
|
|
376
|
+
{
|
|
377
|
+
onClick: () => scroll("right"),
|
|
378
|
+
className: "hidden sm:flex absolute right-0 top-1/2 -translate-y-1/2 z-10 w-8 h-8 items-center justify-center rounded-full border-none cursor-pointer",
|
|
379
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
380
|
+
"aria-label": "Scroll right",
|
|
381
|
+
children: /* @__PURE__ */ jsx2("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "var(--color-dark,#200C02)", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx2("polyline", { points: "9 18 15 12 9 6" }) })
|
|
382
|
+
}
|
|
383
|
+
),
|
|
384
|
+
canScrollLeft && /* @__PURE__ */ jsx2(
|
|
385
|
+
"div",
|
|
386
|
+
{
|
|
387
|
+
className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
388
|
+
style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
|
|
389
|
+
}
|
|
390
|
+
),
|
|
391
|
+
canScrollRight && /* @__PURE__ */ jsx2(
|
|
392
|
+
"div",
|
|
393
|
+
{
|
|
394
|
+
className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
395
|
+
style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
|
|
396
|
+
}
|
|
397
|
+
),
|
|
398
|
+
/* @__PURE__ */ jsx2(
|
|
399
|
+
"div",
|
|
400
|
+
{
|
|
401
|
+
ref: scrollRef,
|
|
402
|
+
className: "overflow-x-auto w-full",
|
|
403
|
+
style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
|
|
404
|
+
onMouseDown: onDragStart,
|
|
405
|
+
onMouseMove: onDragMove,
|
|
406
|
+
onMouseUp: onDragEnd,
|
|
407
|
+
onMouseLeave: onDragEnd,
|
|
408
|
+
children: /* @__PURE__ */ jsx2("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
|
|
409
|
+
const isToday = date.getTime() === todayMs;
|
|
410
|
+
const isSelected = i === selectedIdx;
|
|
411
|
+
const clickable = datesWithClasses.has(i);
|
|
412
|
+
const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
|
|
413
|
+
return /* @__PURE__ */ jsxs2(
|
|
414
|
+
"div",
|
|
415
|
+
{
|
|
416
|
+
onClick: () => clickable && onSelectDate(i),
|
|
417
|
+
className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
|
|
418
|
+
children: [
|
|
419
|
+
/* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
|
|
420
|
+
/* @__PURE__ */ jsx2(
|
|
421
|
+
"div",
|
|
422
|
+
{
|
|
423
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center",
|
|
424
|
+
style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
|
|
425
|
+
children: /* @__PURE__ */ jsx2(
|
|
426
|
+
"span",
|
|
427
|
+
{
|
|
428
|
+
className: "font-body text-xl font-bold text-center tracking-display-tight",
|
|
429
|
+
style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
|
|
430
|
+
children: date.getDate()
|
|
431
|
+
}
|
|
432
|
+
)
|
|
433
|
+
}
|
|
434
|
+
)
|
|
435
|
+
]
|
|
436
|
+
},
|
|
437
|
+
i
|
|
438
|
+
);
|
|
439
|
+
}) })
|
|
440
|
+
}
|
|
441
|
+
)
|
|
442
|
+
] }),
|
|
443
|
+
selectedClasses.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ jsx2("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
|
|
444
|
+
const booked = getBookingsOnDate(cls, selectedDate);
|
|
445
|
+
const available = cls.maxParticipants - booked;
|
|
446
|
+
const isFull = available <= 0;
|
|
447
|
+
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
448
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
|
|
449
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
|
|
450
|
+
/* @__PURE__ */ jsx2("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
|
|
451
|
+
/* @__PURE__ */ jsx2(
|
|
452
|
+
"span",
|
|
453
|
+
{
|
|
454
|
+
className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
|
|
455
|
+
style: { borderColor: "#0885FE", color: "#0885FE" },
|
|
456
|
+
children: "GROUP"
|
|
457
|
+
}
|
|
458
|
+
),
|
|
459
|
+
/* @__PURE__ */ jsx2("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
460
|
+
/* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
461
|
+
available,
|
|
462
|
+
"/",
|
|
463
|
+
cls.maxParticipants
|
|
464
|
+
] }),
|
|
465
|
+
/* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
466
|
+
] }) })
|
|
467
|
+
] }),
|
|
468
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
|
|
469
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
|
|
470
|
+
/* @__PURE__ */ jsx2("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
471
|
+
cls.hostName && /* @__PURE__ */ jsx2("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
|
|
472
|
+
] }),
|
|
473
|
+
/* @__PURE__ */ jsx2("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
474
|
+
/* @__PURE__ */ jsxs2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
475
|
+
available,
|
|
476
|
+
"/",
|
|
477
|
+
cls.maxParticipants
|
|
478
|
+
] }),
|
|
479
|
+
/* @__PURE__ */ jsx2("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
480
|
+
] }) }),
|
|
481
|
+
/* @__PURE__ */ jsx2(
|
|
482
|
+
"button",
|
|
483
|
+
{
|
|
484
|
+
className: "w-full sm:w-[200px] sm:shrink-0 px-5 py-[10px] flex items-center justify-center rounded-lg font-body text-base font-bold cursor-pointer box-border",
|
|
485
|
+
style: isFull ? {
|
|
486
|
+
background: "transparent",
|
|
487
|
+
border: "1px solid var(--color-dark, #200C02)",
|
|
488
|
+
color: "var(--color-dark, #200C02)"
|
|
489
|
+
} : {
|
|
490
|
+
background: "var(--color-primary, #3D312B)",
|
|
491
|
+
border: "none",
|
|
492
|
+
color: "var(--color-light, #FEFFF0)"
|
|
493
|
+
},
|
|
494
|
+
onClick: () => {
|
|
495
|
+
if (!cls.id) return;
|
|
496
|
+
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
497
|
+
},
|
|
498
|
+
children: isFull ? "Join Waitlist" : "Book Now"
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
] })
|
|
502
|
+
] }),
|
|
503
|
+
i < selectedClasses.length - 1 && /* @__PURE__ */ jsx2("div", { className: "h-px bg-black/20" })
|
|
504
|
+
] }, cls.id ?? i);
|
|
505
|
+
}) })
|
|
506
|
+
] });
|
|
507
|
+
}
|
|
508
|
+
function CalendarFoldIcon() {
|
|
509
|
+
return /* @__PURE__ */ jsxs2(
|
|
510
|
+
"svg",
|
|
511
|
+
{
|
|
512
|
+
width: "16",
|
|
513
|
+
height: "16",
|
|
514
|
+
viewBox: "0 0 24 24",
|
|
515
|
+
fill: "none",
|
|
516
|
+
stroke: "currentColor",
|
|
517
|
+
strokeWidth: "2",
|
|
518
|
+
strokeLinecap: "round",
|
|
519
|
+
strokeLinejoin: "round",
|
|
520
|
+
style: { flexShrink: 0 },
|
|
521
|
+
children: [
|
|
522
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 2v4" }),
|
|
523
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 2v4" }),
|
|
524
|
+
/* @__PURE__ */ jsx2("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
|
|
525
|
+
/* @__PURE__ */ jsx2("path", { d: "M3 10h18" }),
|
|
526
|
+
/* @__PURE__ */ jsx2("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
|
|
527
|
+
]
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
|
|
532
|
+
const [schedule, setSchedule] = useState2(null);
|
|
533
|
+
const [loading, setLoading] = useState2(true);
|
|
534
|
+
const [inEditor, setInEditor] = useState2(false);
|
|
535
|
+
const [isHovered, setIsHovered] = useState2(false);
|
|
536
|
+
const [modalState, setModalState] = useState2(null);
|
|
537
|
+
const switchScheduleIdRef = useRef(null);
|
|
538
|
+
const dates = useMemo(() => {
|
|
539
|
+
const today = /* @__PURE__ */ new Date();
|
|
540
|
+
today.setHours(0, 0, 0, 0);
|
|
541
|
+
return Array.from({ length: 14 }, (_, i) => {
|
|
542
|
+
const d = new Date(today);
|
|
543
|
+
d.setDate(today.getDate() + i);
|
|
544
|
+
return d;
|
|
545
|
+
});
|
|
546
|
+
}, []);
|
|
547
|
+
const [selectedIdx, setSelectedIdx] = useState2(0);
|
|
548
|
+
useEffect(() => {
|
|
549
|
+
if (!schedule?.classes) return;
|
|
550
|
+
for (let i = 0; i < dates.length; i++) {
|
|
551
|
+
if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
|
|
552
|
+
setSelectedIdx(i);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}, [schedule, dates]);
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
if (typeof window === "undefined") return;
|
|
559
|
+
const isInEditor = window.self !== window.top;
|
|
560
|
+
setInEditor(isInEditor);
|
|
561
|
+
if (isInEditor) {
|
|
562
|
+
const startEmpty = initialScheduleId === null;
|
|
563
|
+
if (startEmpty) setLoading(false);
|
|
564
|
+
let initialized = startEmpty;
|
|
565
|
+
const timer = !startEmpty ? setTimeout(() => {
|
|
566
|
+
if (!initialized) {
|
|
567
|
+
initialized = true;
|
|
568
|
+
setLoading(false);
|
|
569
|
+
}
|
|
570
|
+
}, 5e3) : null;
|
|
571
|
+
const handler = (e) => {
|
|
572
|
+
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
573
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
574
|
+
setSchedule(null);
|
|
575
|
+
setLoading(false);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (e.data?.type === "ow:switch-schedule") {
|
|
579
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
580
|
+
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
581
|
+
initialized = false;
|
|
582
|
+
setLoading(true);
|
|
583
|
+
window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
587
|
+
if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
|
|
588
|
+
if (initialized && switchScheduleIdRef.current === null) return;
|
|
589
|
+
initialized = true;
|
|
590
|
+
if (timer) clearTimeout(timer);
|
|
591
|
+
const schedules = e.data.schedules ?? [];
|
|
592
|
+
const isSwitching = switchScheduleIdRef.current !== null;
|
|
593
|
+
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;
|
|
594
|
+
switchScheduleIdRef.current = null;
|
|
595
|
+
setSchedule(target);
|
|
596
|
+
setLoading(false);
|
|
597
|
+
if (notifyOnConnect && target && !isSwitching) {
|
|
598
|
+
window.parent.postMessage({
|
|
599
|
+
type: "ow:schedule-connected",
|
|
600
|
+
schedule: { id: target.id, name: target.name },
|
|
601
|
+
insertAfter
|
|
602
|
+
}, "*");
|
|
603
|
+
window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
window.addEventListener("message", handler);
|
|
607
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
608
|
+
return () => {
|
|
609
|
+
window.removeEventListener("message", handler);
|
|
610
|
+
if (timer) clearTimeout(timer);
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (initialScheduleId === null) {
|
|
614
|
+
setLoading(false);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
;
|
|
618
|
+
(async () => {
|
|
619
|
+
try {
|
|
620
|
+
if (initialScheduleId) {
|
|
621
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
|
|
622
|
+
const data = await res.json();
|
|
623
|
+
setSchedule(data?.id ? data : null);
|
|
624
|
+
} else {
|
|
625
|
+
const domain = getApiDomain();
|
|
626
|
+
const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
|
|
627
|
+
const slugs = await sitemapRes.json();
|
|
628
|
+
if (!Array.isArray(slugs) || !slugs.length) {
|
|
629
|
+
setLoading(false);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const { slug } = [...slugs].sort(
|
|
633
|
+
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
634
|
+
)[0];
|
|
635
|
+
const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
|
|
636
|
+
const data = await res.json();
|
|
637
|
+
setSchedule(data?.id ? data : null);
|
|
638
|
+
}
|
|
639
|
+
} catch {
|
|
640
|
+
}
|
|
641
|
+
setLoading(false);
|
|
642
|
+
})();
|
|
643
|
+
}, []);
|
|
644
|
+
const timezoneLabel = schedule?.timezone ? (() => {
|
|
645
|
+
try {
|
|
646
|
+
return buildTimezoneLabel(schedule.timezone);
|
|
647
|
+
} catch {
|
|
648
|
+
return void 0;
|
|
649
|
+
}
|
|
650
|
+
})() : void 0;
|
|
651
|
+
const handleModalSubmit = async (email) => {
|
|
652
|
+
if (!modalState) return;
|
|
653
|
+
const { mode, classId, classDate } = modalState;
|
|
654
|
+
const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
|
|
655
|
+
const res = await fetch(endpoint, {
|
|
656
|
+
method: "POST",
|
|
657
|
+
headers: { "Content-Type": "application/json" },
|
|
658
|
+
body: JSON.stringify({ classDate: classDate.toISOString(), email })
|
|
659
|
+
});
|
|
660
|
+
if (!res.ok) {
|
|
661
|
+
const data = await res.json().catch(() => ({}));
|
|
662
|
+
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
const handleReplaceSchedule = () => {
|
|
666
|
+
setIsHovered(false);
|
|
667
|
+
if (schedule) {
|
|
668
|
+
window.parent.postMessage({
|
|
669
|
+
type: "ow:schedule-connected",
|
|
670
|
+
schedule: { id: schedule.id, name: schedule.name },
|
|
671
|
+
insertAfter
|
|
672
|
+
}, "*");
|
|
673
|
+
} else {
|
|
674
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
675
|
+
}
|
|
676
|
+
};
|
|
677
|
+
const sectionId = `scheduling-${insertAfter}`;
|
|
678
|
+
return /* @__PURE__ */ jsxs2(
|
|
679
|
+
"section",
|
|
680
|
+
{
|
|
681
|
+
"data-ohw-section": sectionId,
|
|
682
|
+
"data-ohw-scheduling-anchor": insertAfter,
|
|
683
|
+
className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
|
|
684
|
+
onMouseEnter: () => inEditor && setIsHovered(true),
|
|
685
|
+
onMouseLeave: () => setIsHovered(false),
|
|
686
|
+
children: [
|
|
687
|
+
inEditor && isHovered && !!schedule && /* @__PURE__ */ jsxs2(
|
|
688
|
+
"div",
|
|
689
|
+
{
|
|
690
|
+
className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
|
|
691
|
+
onClick: handleReplaceSchedule,
|
|
692
|
+
children: [
|
|
693
|
+
/* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
|
|
694
|
+
/* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
|
|
695
|
+
/* @__PURE__ */ jsx2("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs2(
|
|
696
|
+
"div",
|
|
697
|
+
{
|
|
698
|
+
className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
|
|
699
|
+
style: {
|
|
700
|
+
fontSize: 14,
|
|
701
|
+
lineHeight: "24px",
|
|
702
|
+
fontFamily: "'Figtree', system-ui, sans-serif",
|
|
703
|
+
fontWeight: 500,
|
|
704
|
+
color: "#0C0A09"
|
|
705
|
+
},
|
|
706
|
+
children: [
|
|
707
|
+
/* @__PURE__ */ jsx2(CalendarFoldIcon, {}),
|
|
708
|
+
"Replace schedule"
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
) })
|
|
712
|
+
]
|
|
713
|
+
}
|
|
714
|
+
),
|
|
715
|
+
/* @__PURE__ */ jsxs2("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
716
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4", children: [
|
|
717
|
+
/* @__PURE__ */ jsx2("h2", { className: "font-display text-4xl sm:text-5xl font-bold text-center m-0 text-(--color-dark,#200C02)", children: schedule?.name ?? "Book an appointment" }),
|
|
718
|
+
schedule?.description && /* @__PURE__ */ jsx2("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
|
|
719
|
+
] }),
|
|
720
|
+
/* @__PURE__ */ jsxs2("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
721
|
+
timezoneLabel && /* @__PURE__ */ jsx2("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
722
|
+
/* @__PURE__ */ jsx2("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ jsx2(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ jsx2(EmptyState, { inEditor }) : /* @__PURE__ */ jsx2(
|
|
723
|
+
ScheduleView,
|
|
724
|
+
{
|
|
725
|
+
schedule,
|
|
726
|
+
dates,
|
|
727
|
+
selectedIdx,
|
|
728
|
+
onSelectDate: setSelectedIdx,
|
|
729
|
+
onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
|
|
730
|
+
}
|
|
731
|
+
) })
|
|
732
|
+
] })
|
|
733
|
+
] }),
|
|
734
|
+
modalState && /* @__PURE__ */ jsx2(
|
|
735
|
+
EmailCaptureModal,
|
|
736
|
+
{
|
|
737
|
+
title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
|
|
738
|
+
subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
|
|
739
|
+
onSubmit: handleModalSubmit,
|
|
740
|
+
onClose: () => setModalState(null)
|
|
741
|
+
}
|
|
742
|
+
)
|
|
743
|
+
]
|
|
744
|
+
}
|
|
745
|
+
);
|
|
746
|
+
}
|
|
5
747
|
|
|
6
748
|
// src/ui/toggle-group.tsx
|
|
7
|
-
import * as
|
|
749
|
+
import * as React2 from "react";
|
|
8
750
|
|
|
9
751
|
// node_modules/clsx/dist/clsx.mjs
|
|
10
752
|
function r(e) {
|
|
@@ -3324,7 +4066,7 @@ var cva = (base, config) => (props) => {
|
|
|
3324
4066
|
|
|
3325
4067
|
// src/ui/toggle.tsx
|
|
3326
4068
|
import { Toggle as TogglePrimitive } from "radix-ui";
|
|
3327
|
-
import { jsx } from "react/jsx-runtime";
|
|
4069
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
3328
4070
|
var toggleVariants = cva(
|
|
3329
4071
|
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
3330
4072
|
{
|
|
@@ -3351,7 +4093,7 @@ function Toggle({
|
|
|
3351
4093
|
size,
|
|
3352
4094
|
...props
|
|
3353
4095
|
}) {
|
|
3354
|
-
return /* @__PURE__ */
|
|
4096
|
+
return /* @__PURE__ */ jsx3(
|
|
3355
4097
|
TogglePrimitive.Root,
|
|
3356
4098
|
{
|
|
3357
4099
|
"data-slot": "toggle",
|
|
@@ -3362,8 +4104,8 @@ function Toggle({
|
|
|
3362
4104
|
}
|
|
3363
4105
|
|
|
3364
4106
|
// src/ui/toggle-group.tsx
|
|
3365
|
-
import { jsx as
|
|
3366
|
-
var ToggleGroupContext =
|
|
4107
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
4108
|
+
var ToggleGroupContext = React2.createContext({
|
|
3367
4109
|
value: "",
|
|
3368
4110
|
onValueChange: () => {
|
|
3369
4111
|
}
|
|
@@ -3375,13 +4117,13 @@ function ToggleGroup({
|
|
|
3375
4117
|
children,
|
|
3376
4118
|
...props
|
|
3377
4119
|
}) {
|
|
3378
|
-
return /* @__PURE__ */
|
|
4120
|
+
return /* @__PURE__ */ jsx4(
|
|
3379
4121
|
"div",
|
|
3380
4122
|
{
|
|
3381
4123
|
role: "group",
|
|
3382
4124
|
className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
|
|
3383
4125
|
...props,
|
|
3384
|
-
children: /* @__PURE__ */
|
|
4126
|
+
children: /* @__PURE__ */ jsx4(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
|
|
3385
4127
|
}
|
|
3386
4128
|
);
|
|
3387
4129
|
}
|
|
@@ -3391,8 +4133,8 @@ function ToggleGroupItem({
|
|
|
3391
4133
|
children,
|
|
3392
4134
|
...props
|
|
3393
4135
|
}) {
|
|
3394
|
-
const { value: groupValue, onValueChange } =
|
|
3395
|
-
return /* @__PURE__ */
|
|
4136
|
+
const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
|
|
4137
|
+
return /* @__PURE__ */ jsx4(
|
|
3396
4138
|
Toggle,
|
|
3397
4139
|
{
|
|
3398
4140
|
pressed: groupValue === value,
|
|
@@ -3433,8 +4175,866 @@ function isEditSessionActive() {
|
|
|
3433
4175
|
return new URLSearchParams(q).get("mode") === "edit";
|
|
3434
4176
|
}
|
|
3435
4177
|
|
|
4178
|
+
// src/ui/link-modal/calcPopoverPos.ts
|
|
4179
|
+
function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
|
|
4180
|
+
const GAP = 8;
|
|
4181
|
+
const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
|
|
4182
|
+
const visibleTop = rect.top - canvasTopInIframe;
|
|
4183
|
+
let top = rect.top - GAP;
|
|
4184
|
+
let transform = "translateX(-50%) translateY(-100%)";
|
|
4185
|
+
if (visibleTop < approxH + GAP) {
|
|
4186
|
+
top = rect.bottom + GAP;
|
|
4187
|
+
transform = "translateX(-50%)";
|
|
4188
|
+
}
|
|
4189
|
+
const rawLeft = rect.left + rect.width / 2;
|
|
4190
|
+
const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
|
|
4191
|
+
return { top, left, transform };
|
|
4192
|
+
}
|
|
4193
|
+
|
|
4194
|
+
// src/ui/button.tsx
|
|
4195
|
+
import * as React3 from "react";
|
|
4196
|
+
import { Slot } from "radix-ui";
|
|
4197
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
4198
|
+
var buttonVariants = cva(
|
|
4199
|
+
"inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-md text-sm font-medium transition-colors outline-none disabled:pointer-events-none disabled:opacity-50 min-w-[80px] px-3 py-2",
|
|
4200
|
+
{
|
|
4201
|
+
variants: {
|
|
4202
|
+
variant: {
|
|
4203
|
+
default: "bg-primary text-primary-foreground hover:opacity-90",
|
|
4204
|
+
outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
|
|
4205
|
+
ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
|
|
4206
|
+
},
|
|
4207
|
+
size: {
|
|
4208
|
+
default: "h-9",
|
|
4209
|
+
sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
|
|
4210
|
+
}
|
|
4211
|
+
},
|
|
4212
|
+
defaultVariants: {
|
|
4213
|
+
variant: "default",
|
|
4214
|
+
size: "default"
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
);
|
|
4218
|
+
var Button = React3.forwardRef(
|
|
4219
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
4220
|
+
const Comp = asChild ? Slot.Root : "button";
|
|
4221
|
+
return /* @__PURE__ */ jsx5(
|
|
4222
|
+
Comp,
|
|
4223
|
+
{
|
|
4224
|
+
ref,
|
|
4225
|
+
"data-slot": "button",
|
|
4226
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
4227
|
+
...props
|
|
4228
|
+
}
|
|
4229
|
+
);
|
|
4230
|
+
}
|
|
4231
|
+
);
|
|
4232
|
+
Button.displayName = "Button";
|
|
4233
|
+
|
|
4234
|
+
// src/ui/icons.tsx
|
|
4235
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
4236
|
+
function IconX({ className, ...props }) {
|
|
4237
|
+
return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4238
|
+
/* @__PURE__ */ jsx6("path", { d: "M18 6 6 18" }),
|
|
4239
|
+
/* @__PURE__ */ jsx6("path", { d: "m6 6 12 12" })
|
|
4240
|
+
] });
|
|
4241
|
+
}
|
|
4242
|
+
function IconChevronDown({ className, ...props }) {
|
|
4243
|
+
return /* @__PURE__ */ jsx6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ jsx6("path", { d: "m6 9 6 6 6-6" }) });
|
|
4244
|
+
}
|
|
4245
|
+
function IconFile({ className, ...props }) {
|
|
4246
|
+
return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4247
|
+
/* @__PURE__ */ jsx6("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
|
|
4248
|
+
/* @__PURE__ */ jsx6("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
|
|
4249
|
+
] });
|
|
4250
|
+
}
|
|
4251
|
+
function IconInfo({ className, ...props }) {
|
|
4252
|
+
return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4253
|
+
/* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "10" }),
|
|
4254
|
+
/* @__PURE__ */ jsx6("path", { d: "M12 16v-4" }),
|
|
4255
|
+
/* @__PURE__ */ jsx6("path", { d: "M12 8h.01" })
|
|
4256
|
+
] });
|
|
4257
|
+
}
|
|
4258
|
+
function IconArrowRight({ className, ...props }) {
|
|
4259
|
+
return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4260
|
+
/* @__PURE__ */ jsx6("path", { d: "M5 12h14" }),
|
|
4261
|
+
/* @__PURE__ */ jsx6("path", { d: "m12 5 7 7-7 7" })
|
|
4262
|
+
] });
|
|
4263
|
+
}
|
|
4264
|
+
function IconSection({ className, ...props }) {
|
|
4265
|
+
return /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4266
|
+
/* @__PURE__ */ jsx6("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
|
|
4267
|
+
/* @__PURE__ */ jsx6("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
|
|
4268
|
+
/* @__PURE__ */ jsx6("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
|
|
4269
|
+
] });
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
// src/ui/link-modal/DestinationBreadcrumb.tsx
|
|
4273
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
4274
|
+
function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
|
|
4275
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex w-full flex-col gap-2", children: [
|
|
4276
|
+
/* @__PURE__ */ jsx7("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
|
|
4277
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
|
|
4278
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
|
|
4279
|
+
/* @__PURE__ */ jsx7(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4280
|
+
/* @__PURE__ */ jsx7("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
|
|
4281
|
+
] }),
|
|
4282
|
+
/* @__PURE__ */ jsx7(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
4283
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
|
|
4284
|
+
/* @__PURE__ */ jsx7(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4285
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
|
|
4286
|
+
] })
|
|
4287
|
+
] })
|
|
4288
|
+
] });
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
// src/ui/link-modal/SectionTreeItem.tsx
|
|
4292
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
4293
|
+
function SectionTreeItem({ section, onSelect, selected }) {
|
|
4294
|
+
const interactive = Boolean(onSelect);
|
|
4295
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex h-9 w-full items-end pl-3", children: [
|
|
4296
|
+
/* @__PURE__ */ jsx8(
|
|
4297
|
+
"div",
|
|
4298
|
+
{
|
|
4299
|
+
className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
|
|
4300
|
+
"aria-hidden": true
|
|
4301
|
+
}
|
|
4302
|
+
),
|
|
4303
|
+
/* @__PURE__ */ jsxs5(
|
|
4304
|
+
"div",
|
|
4305
|
+
{
|
|
4306
|
+
role: interactive ? "button" : void 0,
|
|
4307
|
+
tabIndex: interactive ? 0 : void 0,
|
|
4308
|
+
onClick: interactive ? () => onSelect?.(section) : void 0,
|
|
4309
|
+
onKeyDown: interactive ? (e) => {
|
|
4310
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
4311
|
+
e.preventDefault();
|
|
4312
|
+
onSelect?.(section);
|
|
4313
|
+
}
|
|
4314
|
+
} : void 0,
|
|
4315
|
+
className: cn(
|
|
4316
|
+
"flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
|
|
4317
|
+
interactive && "cursor-pointer hover:bg-muted/30",
|
|
4318
|
+
interactive && selected && "border-primary"
|
|
4319
|
+
),
|
|
4320
|
+
children: [
|
|
4321
|
+
/* @__PURE__ */ jsx8(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4322
|
+
/* @__PURE__ */ jsx8("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
|
|
4323
|
+
]
|
|
4324
|
+
}
|
|
4325
|
+
)
|
|
4326
|
+
] });
|
|
4327
|
+
}
|
|
4328
|
+
function SectionPickerList({ sections, onSelect }) {
|
|
4329
|
+
if (sections.length === 0) {
|
|
4330
|
+
return /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
|
|
4331
|
+
}
|
|
4332
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", children: [
|
|
4333
|
+
/* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
|
|
4334
|
+
sections.map((section) => /* @__PURE__ */ jsx8(SectionTreeItem, { section, onSelect }, section.id))
|
|
4335
|
+
] });
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
// src/ui/link-modal/UrlOrPageInput.tsx
|
|
4339
|
+
import { useEffect as useEffect2, useId, useRef as useRef2, useState as useState3 } from "react";
|
|
4340
|
+
|
|
4341
|
+
// src/ui/input.tsx
|
|
4342
|
+
import * as React4 from "react";
|
|
4343
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
4344
|
+
var Input = React4.forwardRef(
|
|
4345
|
+
({ className, type, ...props }, ref) => {
|
|
4346
|
+
return /* @__PURE__ */ jsx9(
|
|
4347
|
+
"input",
|
|
4348
|
+
{
|
|
4349
|
+
type,
|
|
4350
|
+
ref,
|
|
4351
|
+
"data-slot": "input",
|
|
4352
|
+
className: cn(
|
|
4353
|
+
"flex h-full w-full min-w-0 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground",
|
|
4354
|
+
className
|
|
4355
|
+
),
|
|
4356
|
+
...props
|
|
4357
|
+
}
|
|
4358
|
+
);
|
|
4359
|
+
}
|
|
4360
|
+
);
|
|
4361
|
+
Input.displayName = "Input";
|
|
4362
|
+
|
|
4363
|
+
// src/ui/label.tsx
|
|
4364
|
+
import { Label as LabelPrimitive } from "radix-ui";
|
|
4365
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
4366
|
+
function Label({ className, ...props }) {
|
|
4367
|
+
return /* @__PURE__ */ jsx10(
|
|
4368
|
+
LabelPrimitive.Root,
|
|
4369
|
+
{
|
|
4370
|
+
"data-slot": "label",
|
|
4371
|
+
className: cn("text-sm font-medium leading-5 text-foreground", className),
|
|
4372
|
+
...props
|
|
4373
|
+
}
|
|
4374
|
+
);
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4377
|
+
// src/ui/popover.tsx
|
|
4378
|
+
import { Popover as PopoverPrimitive } from "radix-ui";
|
|
4379
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
4380
|
+
function Popover({ ...props }) {
|
|
4381
|
+
return /* @__PURE__ */ jsx11(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
|
|
4382
|
+
}
|
|
4383
|
+
function PopoverTrigger({ ...props }) {
|
|
4384
|
+
return /* @__PURE__ */ jsx11(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
4385
|
+
}
|
|
4386
|
+
function PopoverContent({
|
|
4387
|
+
className,
|
|
4388
|
+
align = "start",
|
|
4389
|
+
sideOffset = 6,
|
|
4390
|
+
...props
|
|
4391
|
+
}) {
|
|
4392
|
+
return /* @__PURE__ */ jsx11(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx11(
|
|
4393
|
+
PopoverPrimitive.Content,
|
|
4394
|
+
{
|
|
4395
|
+
"data-slot": "popover-content",
|
|
4396
|
+
align,
|
|
4397
|
+
sideOffset,
|
|
4398
|
+
className: cn(
|
|
4399
|
+
"z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
|
|
4400
|
+
className
|
|
4401
|
+
),
|
|
4402
|
+
...props
|
|
4403
|
+
}
|
|
4404
|
+
) });
|
|
4405
|
+
}
|
|
4406
|
+
|
|
4407
|
+
// src/ui/link-modal/UrlOrPageInput.tsx
|
|
4408
|
+
import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4409
|
+
function FieldChevron({ onClick }) {
|
|
4410
|
+
return /* @__PURE__ */ jsx12(
|
|
4411
|
+
"button",
|
|
4412
|
+
{
|
|
4413
|
+
type: "button",
|
|
4414
|
+
className: "flex shrink-0 items-center justify-end border-0 bg-transparent p-0 pl-4 text-muted-foreground outline-none",
|
|
4415
|
+
onClick,
|
|
4416
|
+
"aria-label": "Open page list",
|
|
4417
|
+
tabIndex: -1,
|
|
4418
|
+
children: /* @__PURE__ */ jsx12(IconChevronDown, {})
|
|
4419
|
+
}
|
|
4420
|
+
);
|
|
4421
|
+
}
|
|
4422
|
+
function UrlOrPageInput({
|
|
4423
|
+
value,
|
|
4424
|
+
selectedPage,
|
|
4425
|
+
placeholder = "Paste a URL or choose a page",
|
|
4426
|
+
dropdownOpen,
|
|
4427
|
+
onDropdownOpenChange,
|
|
4428
|
+
filteredPages,
|
|
4429
|
+
onInputChange,
|
|
4430
|
+
onPageSelect,
|
|
4431
|
+
readOnly = false,
|
|
4432
|
+
urlError
|
|
4433
|
+
}) {
|
|
4434
|
+
const inputId = useId();
|
|
4435
|
+
const inputRef = useRef2(null);
|
|
4436
|
+
const [isEditing, setIsEditing] = useState3(false);
|
|
4437
|
+
const [isFocused, setIsFocused] = useState3(false);
|
|
4438
|
+
const showFilledDisplay = Boolean(selectedPage) && !isEditing && !readOnly;
|
|
4439
|
+
useEffect2(() => {
|
|
4440
|
+
if (selectedPage) setIsEditing(false);
|
|
4441
|
+
}, [selectedPage]);
|
|
4442
|
+
const openDropdown = () => {
|
|
4443
|
+
if (readOnly || filteredPages.length === 0) return;
|
|
4444
|
+
onDropdownOpenChange(true);
|
|
4445
|
+
};
|
|
4446
|
+
const toggleDropdown = (e) => {
|
|
4447
|
+
e.stopPropagation();
|
|
4448
|
+
e.preventDefault();
|
|
4449
|
+
if (readOnly || filteredPages.length === 0) return;
|
|
4450
|
+
onDropdownOpenChange(!dropdownOpen);
|
|
4451
|
+
};
|
|
4452
|
+
const enterEditMode = () => {
|
|
4453
|
+
if (readOnly) return;
|
|
4454
|
+
setIsEditing(true);
|
|
4455
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
4456
|
+
};
|
|
4457
|
+
const fieldClassName = cn(
|
|
4458
|
+
"data-ohw-link-field flex h-10 w-full items-center overflow-hidden rounded-md border bg-background pl-3 pr-3 py-2 outline-none transition-[border-color,box-shadow]",
|
|
4459
|
+
urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
|
|
4460
|
+
);
|
|
4461
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex w-full flex-col gap-2 p-0", children: [
|
|
4462
|
+
/* @__PURE__ */ jsx12(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
|
|
4463
|
+
/* @__PURE__ */ jsxs6(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
|
|
4464
|
+
/* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: showFilledDisplay ? /* @__PURE__ */ jsxs6(
|
|
4465
|
+
"button",
|
|
4466
|
+
{
|
|
4467
|
+
type: "button",
|
|
4468
|
+
"data-ohw-link-field": true,
|
|
4469
|
+
className: cn(fieldClassName, "m-0 cursor-pointer border-solid text-left"),
|
|
4470
|
+
onClick: openDropdown,
|
|
4471
|
+
onFocus: () => setIsFocused(true),
|
|
4472
|
+
onBlur: () => setIsFocused(false),
|
|
4473
|
+
onKeyDown: (e) => {
|
|
4474
|
+
if (e.key === "Backspace" || e.key === "Delete" || e.key.length === 1) {
|
|
4475
|
+
enterEditMode();
|
|
4476
|
+
}
|
|
4477
|
+
},
|
|
4478
|
+
children: [
|
|
4479
|
+
/* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }),
|
|
4480
|
+
/* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm font-normal text-foreground", children: selectedPage.title }),
|
|
4481
|
+
/* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown })
|
|
4482
|
+
]
|
|
4483
|
+
}
|
|
4484
|
+
) : /* @__PURE__ */ jsxs6(
|
|
4485
|
+
"div",
|
|
4486
|
+
{
|
|
4487
|
+
"data-ohw-link-field": true,
|
|
4488
|
+
className: fieldClassName,
|
|
4489
|
+
onMouseDown: (e) => {
|
|
4490
|
+
if (readOnly) return;
|
|
4491
|
+
if (e.target === e.currentTarget) enterEditMode();
|
|
4492
|
+
},
|
|
4493
|
+
children: [
|
|
4494
|
+
selectedPage ? /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
|
|
4495
|
+
readOnly ? /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx12(
|
|
4496
|
+
Input,
|
|
4497
|
+
{
|
|
4498
|
+
ref: inputRef,
|
|
4499
|
+
id: inputId,
|
|
4500
|
+
value,
|
|
4501
|
+
onChange: (e) => onInputChange(e.target.value),
|
|
4502
|
+
onFocus: () => {
|
|
4503
|
+
setIsFocused(true);
|
|
4504
|
+
setIsEditing(true);
|
|
4505
|
+
openDropdown();
|
|
4506
|
+
},
|
|
4507
|
+
onBlur: () => setIsFocused(false),
|
|
4508
|
+
placeholder,
|
|
4509
|
+
className: cn(
|
|
4510
|
+
"min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0",
|
|
4511
|
+
value ? "text-foreground" : "text-muted-foreground placeholder:text-muted-foreground"
|
|
4512
|
+
)
|
|
4513
|
+
}
|
|
4514
|
+
),
|
|
4515
|
+
!readOnly ? /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown }) : null
|
|
4516
|
+
]
|
|
4517
|
+
}
|
|
4518
|
+
) }),
|
|
4519
|
+
/* @__PURE__ */ jsx12(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ jsxs6(
|
|
4520
|
+
"button",
|
|
4521
|
+
{
|
|
4522
|
+
type: "button",
|
|
4523
|
+
className: "flex h-9 w-full items-center gap-2 border-0 bg-transparent px-3 text-left text-sm leading-5 text-foreground outline-none hover:bg-muted",
|
|
4524
|
+
onClick: () => onPageSelect(page),
|
|
4525
|
+
children: [
|
|
4526
|
+
/* @__PURE__ */ jsx12(IconFile, { className: "shrink-0", "aria-hidden": true }),
|
|
4527
|
+
/* @__PURE__ */ jsx12("span", { className: "truncate", children: page.title })
|
|
4528
|
+
]
|
|
4529
|
+
},
|
|
4530
|
+
page.path
|
|
4531
|
+
)) })
|
|
4532
|
+
] }),
|
|
4533
|
+
urlError ? /* @__PURE__ */ jsx12("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
|
|
4534
|
+
] });
|
|
4535
|
+
}
|
|
4536
|
+
|
|
4537
|
+
// src/ui/link-modal/useLinkModalState.ts
|
|
4538
|
+
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useState as useState4 } from "react";
|
|
4539
|
+
|
|
4540
|
+
// src/ui/link-modal/linkModal.utils.ts
|
|
4541
|
+
function parseTarget(target) {
|
|
4542
|
+
if (!target) return { pageRoute: "", sectionId: null };
|
|
4543
|
+
const hashIndex = target.indexOf("#");
|
|
4544
|
+
if (hashIndex === -1) return { pageRoute: target, sectionId: null };
|
|
4545
|
+
return {
|
|
4546
|
+
pageRoute: target.slice(0, hashIndex),
|
|
4547
|
+
sectionId: target.slice(hashIndex + 1) || null
|
|
4548
|
+
};
|
|
4549
|
+
}
|
|
4550
|
+
function normalizePath(path) {
|
|
4551
|
+
const trimmed = path.trim();
|
|
4552
|
+
if (!trimmed) return "/";
|
|
4553
|
+
try {
|
|
4554
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
4555
|
+
return new URL(trimmed).pathname || "/";
|
|
4556
|
+
}
|
|
4557
|
+
} catch {
|
|
4558
|
+
}
|
|
4559
|
+
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
4560
|
+
if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
|
|
4561
|
+
return withSlash;
|
|
4562
|
+
}
|
|
4563
|
+
function findPageByPath(pages, path) {
|
|
4564
|
+
const normalized = normalizePath(path);
|
|
4565
|
+
return pages.find((p) => normalizePath(p.path) === normalized);
|
|
4566
|
+
}
|
|
4567
|
+
function inferPageTitleFromPath(path) {
|
|
4568
|
+
const normalized = normalizePath(path);
|
|
4569
|
+
if (normalized === "/") return "Home";
|
|
4570
|
+
const segment = normalized.split("/").filter(Boolean).pop() ?? "";
|
|
4571
|
+
if (!segment) return "Home";
|
|
4572
|
+
return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
4573
|
+
}
|
|
4574
|
+
function isInternalPath(route) {
|
|
4575
|
+
const trimmed = route.trim();
|
|
4576
|
+
if (!trimmed) return false;
|
|
4577
|
+
if (/^https?:\/\//i.test(trimmed)) return false;
|
|
4578
|
+
if (trimmed.startsWith("/")) return true;
|
|
4579
|
+
if (trimmed.includes(" ") || trimmed.includes(".")) return false;
|
|
4580
|
+
return true;
|
|
4581
|
+
}
|
|
4582
|
+
function resolvePage(route, pages) {
|
|
4583
|
+
const found = findPageByPath(pages, route);
|
|
4584
|
+
if (found) return { path: found.path, title: found.title };
|
|
4585
|
+
return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
|
|
4586
|
+
}
|
|
4587
|
+
function getSectionsForPath(sectionsByPath, pageRoute, fallback) {
|
|
4588
|
+
if (!sectionsByPath) return fallback;
|
|
4589
|
+
const normalized = normalizePath(pageRoute);
|
|
4590
|
+
return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? fallback;
|
|
4591
|
+
}
|
|
4592
|
+
function buildTarget(page, section) {
|
|
4593
|
+
if (section) return `${page.path}#${section.id}`;
|
|
4594
|
+
return page.path;
|
|
4595
|
+
}
|
|
4596
|
+
function isValidUrl(urlString) {
|
|
4597
|
+
try {
|
|
4598
|
+
const url = new URL(urlString);
|
|
4599
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
4600
|
+
const hostname = url.hostname;
|
|
4601
|
+
if (!hostname || hostname.length === 0) return false;
|
|
4602
|
+
if (hostname.includes(" ")) return false;
|
|
4603
|
+
return hostname.includes(".") || hostname === "localhost";
|
|
4604
|
+
} catch {
|
|
4605
|
+
return false;
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
function normalizeUrl(value) {
|
|
4609
|
+
const trimmed = value.trim();
|
|
4610
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
|
|
4611
|
+
return `https://${trimmed}`;
|
|
4612
|
+
}
|
|
4613
|
+
function validateUrlInput(value, pages, allPages) {
|
|
4614
|
+
if (!value.trim()) return "";
|
|
4615
|
+
const filtered = pages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase()));
|
|
4616
|
+
if (filtered.length > 0) return "";
|
|
4617
|
+
const matchingPage = allPages.find((p) => p.title.toLowerCase() === value.toLowerCase());
|
|
4618
|
+
if (matchingPage) return "";
|
|
4619
|
+
if (!isValidUrl(normalizeUrl(value))) {
|
|
4620
|
+
return "Please enter a valid URL (e.g., https://example.com)";
|
|
4621
|
+
}
|
|
4622
|
+
return "";
|
|
4623
|
+
}
|
|
4624
|
+
function getEditModeInitialState(target, pages, sections) {
|
|
4625
|
+
const empty = {
|
|
4626
|
+
searchValue: "",
|
|
4627
|
+
selectedPage: null,
|
|
4628
|
+
selectedSection: null,
|
|
4629
|
+
step: "input"
|
|
4630
|
+
};
|
|
4631
|
+
if (!target) return empty;
|
|
4632
|
+
if (isValidUrl(normalizeUrl(target))) {
|
|
4633
|
+
return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4634
|
+
}
|
|
4635
|
+
const { pageRoute, sectionId } = parseTarget(target);
|
|
4636
|
+
if (sectionId) {
|
|
4637
|
+
if (isInternalPath(pageRoute)) {
|
|
4638
|
+
const page = resolvePage(pageRoute, pages);
|
|
4639
|
+
const sec = sections.find((s) => s.id === sectionId);
|
|
4640
|
+
return {
|
|
4641
|
+
searchValue: page.title,
|
|
4642
|
+
selectedPage: page,
|
|
4643
|
+
selectedSection: sec ? { id: sec.id, label: sec.label } : null,
|
|
4644
|
+
step: "input"
|
|
4645
|
+
};
|
|
4646
|
+
}
|
|
4647
|
+
return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4648
|
+
}
|
|
4649
|
+
if (isInternalPath(pageRoute)) {
|
|
4650
|
+
const page = resolvePage(pageRoute, pages);
|
|
4651
|
+
return {
|
|
4652
|
+
searchValue: page.title,
|
|
4653
|
+
selectedPage: page,
|
|
4654
|
+
selectedSection: null,
|
|
4655
|
+
step: "input"
|
|
4656
|
+
};
|
|
4657
|
+
}
|
|
4658
|
+
return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4659
|
+
}
|
|
4660
|
+
function filterAvailablePages(pages, existingTargets) {
|
|
4661
|
+
const taken = new Set(existingTargets);
|
|
4662
|
+
return pages.filter((p) => !taken.has(p.path));
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4665
|
+
// src/ui/link-modal/useLinkModalState.ts
|
|
4666
|
+
function useLinkModalState({
|
|
4667
|
+
open,
|
|
4668
|
+
mode,
|
|
4669
|
+
pages,
|
|
4670
|
+
sections,
|
|
4671
|
+
sectionsByPath,
|
|
4672
|
+
initialTarget,
|
|
4673
|
+
existingTargets,
|
|
4674
|
+
onClose,
|
|
4675
|
+
onSubmit
|
|
4676
|
+
}) {
|
|
4677
|
+
const availablePages = useMemo2(
|
|
4678
|
+
() => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
|
|
4679
|
+
[mode, pages, existingTargets]
|
|
4680
|
+
);
|
|
4681
|
+
const [searchValue, setSearchValue] = useState4("");
|
|
4682
|
+
const [selectedPage, setSelectedPage] = useState4(null);
|
|
4683
|
+
const [selectedSection, setSelectedSection] = useState4(null);
|
|
4684
|
+
const [step, setStep] = useState4("input");
|
|
4685
|
+
const [dropdownOpen, setDropdownOpen] = useState4(false);
|
|
4686
|
+
const [urlError, setUrlError] = useState4("");
|
|
4687
|
+
const reset = useCallback(() => {
|
|
4688
|
+
setSearchValue("");
|
|
4689
|
+
setSelectedPage(null);
|
|
4690
|
+
setSelectedSection(null);
|
|
4691
|
+
setStep("input");
|
|
4692
|
+
setDropdownOpen(false);
|
|
4693
|
+
setUrlError("");
|
|
4694
|
+
}, []);
|
|
4695
|
+
useEffect3(() => {
|
|
4696
|
+
if (!open) return;
|
|
4697
|
+
if (mode === "edit" && initialTarget) {
|
|
4698
|
+
const { pageRoute } = parseTarget(initialTarget);
|
|
4699
|
+
const targetSections = getSectionsForPath(sectionsByPath, pageRoute, sections);
|
|
4700
|
+
const init = getEditModeInitialState(initialTarget, pages, targetSections);
|
|
4701
|
+
setSearchValue(init.searchValue);
|
|
4702
|
+
setSelectedPage(init.selectedPage);
|
|
4703
|
+
setSelectedSection(init.selectedSection);
|
|
4704
|
+
setStep(init.step);
|
|
4705
|
+
} else {
|
|
4706
|
+
reset();
|
|
4707
|
+
}
|
|
4708
|
+
setDropdownOpen(false);
|
|
4709
|
+
setUrlError("");
|
|
4710
|
+
}, [open, mode, initialTarget, pages, sections, sectionsByPath, reset]);
|
|
4711
|
+
const filteredPages = useMemo2(() => {
|
|
4712
|
+
if (!searchValue.trim()) return availablePages;
|
|
4713
|
+
return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
|
|
4714
|
+
}, [availablePages, searchValue]);
|
|
4715
|
+
const activeSections = useMemo2(() => {
|
|
4716
|
+
if (selectedPage && sectionsByPath) {
|
|
4717
|
+
return getSectionsForPath(sectionsByPath, selectedPage.path, sections);
|
|
4718
|
+
}
|
|
4719
|
+
return sections;
|
|
4720
|
+
}, [selectedPage, sectionsByPath, sections]);
|
|
4721
|
+
const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
|
|
4722
|
+
const showSectionPicker = step === "sectionPicker" && selectedPage;
|
|
4723
|
+
const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
|
|
4724
|
+
const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow;
|
|
4725
|
+
const handleInputChange = (value) => {
|
|
4726
|
+
setSearchValue(value);
|
|
4727
|
+
setSelectedPage(null);
|
|
4728
|
+
setSelectedSection(null);
|
|
4729
|
+
setStep("input");
|
|
4730
|
+
setUrlError("");
|
|
4731
|
+
const filtered = value.trim() ? availablePages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase())) : availablePages;
|
|
4732
|
+
setDropdownOpen(filtered.length > 0);
|
|
4733
|
+
if (value.trim() && filtered.length === 0) {
|
|
4734
|
+
const error = validateUrlInput(value, availablePages, pages);
|
|
4735
|
+
if (error) setUrlError(error);
|
|
4736
|
+
}
|
|
4737
|
+
};
|
|
4738
|
+
const handlePageSelect = (page) => {
|
|
4739
|
+
setSelectedPage({ path: page.path, title: page.title });
|
|
4740
|
+
setSelectedSection(null);
|
|
4741
|
+
setSearchValue(page.title);
|
|
4742
|
+
setStep("input");
|
|
4743
|
+
setDropdownOpen(false);
|
|
4744
|
+
setUrlError("");
|
|
4745
|
+
};
|
|
4746
|
+
const handleChooseSection = () => {
|
|
4747
|
+
setStep("sectionPicker");
|
|
4748
|
+
};
|
|
4749
|
+
const handleSectionSelect = (section) => {
|
|
4750
|
+
setSelectedSection({ id: section.id, label: section.label });
|
|
4751
|
+
if (mode === "create") {
|
|
4752
|
+
setStep("confirmed");
|
|
4753
|
+
} else {
|
|
4754
|
+
setStep("input");
|
|
4755
|
+
}
|
|
4756
|
+
};
|
|
4757
|
+
const handleBackToSections = () => {
|
|
4758
|
+
setStep("sectionPicker");
|
|
4759
|
+
};
|
|
4760
|
+
const handleClose = () => {
|
|
4761
|
+
reset();
|
|
4762
|
+
onClose();
|
|
4763
|
+
};
|
|
4764
|
+
const isValid = useMemo2(() => {
|
|
4765
|
+
if (urlError) return false;
|
|
4766
|
+
if (showBreadcrumb) return true;
|
|
4767
|
+
if (selectedPage) return true;
|
|
4768
|
+
if (!searchValue.trim()) return false;
|
|
4769
|
+
return validateUrlInput(searchValue, availablePages, pages) === "";
|
|
4770
|
+
}, [urlError, showBreadcrumb, selectedPage, searchValue, availablePages, pages]);
|
|
4771
|
+
const handleSubmit = () => {
|
|
4772
|
+
if (selectedPage && selectedSection) {
|
|
4773
|
+
onSubmit(buildTarget(selectedPage, selectedSection));
|
|
4774
|
+
handleClose();
|
|
4775
|
+
return;
|
|
4776
|
+
}
|
|
4777
|
+
if (selectedPage) {
|
|
4778
|
+
onSubmit(buildTarget(selectedPage, null));
|
|
4779
|
+
handleClose();
|
|
4780
|
+
return;
|
|
4781
|
+
}
|
|
4782
|
+
if (!searchValue.trim()) return;
|
|
4783
|
+
const error = validateUrlInput(searchValue, availablePages, pages);
|
|
4784
|
+
if (error) {
|
|
4785
|
+
setUrlError(error);
|
|
4786
|
+
return;
|
|
4787
|
+
}
|
|
4788
|
+
const matchingPage = pages.find((p) => p.title.toLowerCase() === searchValue.toLowerCase());
|
|
4789
|
+
if (matchingPage) {
|
|
4790
|
+
onSubmit(matchingPage.path);
|
|
4791
|
+
handleClose();
|
|
4792
|
+
return;
|
|
4793
|
+
}
|
|
4794
|
+
onSubmit(normalizeUrl(searchValue));
|
|
4795
|
+
handleClose();
|
|
4796
|
+
};
|
|
4797
|
+
const submitLabel = mode === "edit" ? "Save" : "Create";
|
|
4798
|
+
const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
|
|
4799
|
+
const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
|
|
4800
|
+
const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
|
|
4801
|
+
return {
|
|
4802
|
+
title,
|
|
4803
|
+
submitLabel,
|
|
4804
|
+
secondaryLabel,
|
|
4805
|
+
searchValue,
|
|
4806
|
+
selectedPage,
|
|
4807
|
+
selectedSection,
|
|
4808
|
+
step,
|
|
4809
|
+
dropdownOpen,
|
|
4810
|
+
setDropdownOpen,
|
|
4811
|
+
urlError,
|
|
4812
|
+
filteredPages,
|
|
4813
|
+
showBreadcrumb,
|
|
4814
|
+
showSectionPicker,
|
|
4815
|
+
showChooseSection,
|
|
4816
|
+
showSectionRow,
|
|
4817
|
+
isValid,
|
|
4818
|
+
activeSections,
|
|
4819
|
+
handleInputChange,
|
|
4820
|
+
handlePageSelect,
|
|
4821
|
+
handleChooseSection,
|
|
4822
|
+
handleSectionSelect,
|
|
4823
|
+
handleBackToSections,
|
|
4824
|
+
handleClose,
|
|
4825
|
+
handleSecondary,
|
|
4826
|
+
handleSubmit
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
|
|
4830
|
+
// src/ui/link-modal/LinkEditorPanel.tsx
|
|
4831
|
+
import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
4832
|
+
function LinkEditorPanel({
|
|
4833
|
+
open = true,
|
|
4834
|
+
mode = "create",
|
|
4835
|
+
pages,
|
|
4836
|
+
sections = [],
|
|
4837
|
+
sectionsByPath,
|
|
4838
|
+
initialTarget,
|
|
4839
|
+
existingTargets = [],
|
|
4840
|
+
onClose,
|
|
4841
|
+
onSubmit
|
|
4842
|
+
}) {
|
|
4843
|
+
const state = useLinkModalState({
|
|
4844
|
+
open,
|
|
4845
|
+
mode,
|
|
4846
|
+
pages,
|
|
4847
|
+
sections,
|
|
4848
|
+
sectionsByPath,
|
|
4849
|
+
initialTarget,
|
|
4850
|
+
existingTargets,
|
|
4851
|
+
onClose,
|
|
4852
|
+
onSubmit
|
|
4853
|
+
});
|
|
4854
|
+
const isCancel = state.secondaryLabel === "Cancel";
|
|
4855
|
+
return /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
4856
|
+
/* @__PURE__ */ jsx13(
|
|
4857
|
+
"button",
|
|
4858
|
+
{
|
|
4859
|
+
type: "button",
|
|
4860
|
+
className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
|
|
4861
|
+
onClick: state.handleClose,
|
|
4862
|
+
"aria-label": "Close",
|
|
4863
|
+
children: /* @__PURE__ */ jsx13(IconX, { "aria-hidden": true })
|
|
4864
|
+
}
|
|
4865
|
+
),
|
|
4866
|
+
/* @__PURE__ */ jsx13("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ jsx13("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
|
|
4867
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
|
|
4868
|
+
state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ jsx13(
|
|
4869
|
+
DestinationBreadcrumb,
|
|
4870
|
+
{
|
|
4871
|
+
pageTitle: state.selectedPage.title,
|
|
4872
|
+
sectionLabel: state.selectedSection.label
|
|
4873
|
+
}
|
|
4874
|
+
) : /* @__PURE__ */ jsx13(
|
|
4875
|
+
UrlOrPageInput,
|
|
4876
|
+
{
|
|
4877
|
+
value: state.searchValue,
|
|
4878
|
+
selectedPage: state.selectedPage,
|
|
4879
|
+
dropdownOpen: state.dropdownOpen,
|
|
4880
|
+
onDropdownOpenChange: state.setDropdownOpen,
|
|
4881
|
+
filteredPages: state.filteredPages,
|
|
4882
|
+
onInputChange: state.handleInputChange,
|
|
4883
|
+
onPageSelect: state.handlePageSelect,
|
|
4884
|
+
urlError: state.urlError
|
|
4885
|
+
}
|
|
4886
|
+
),
|
|
4887
|
+
state.showChooseSection ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col justify-center gap-2", children: [
|
|
4888
|
+
/* @__PURE__ */ jsx13(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
|
|
4889
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
|
|
4890
|
+
/* @__PURE__ */ jsx13(IconInfo, { className: "shrink-0", "aria-hidden": true }),
|
|
4891
|
+
/* @__PURE__ */ jsx13("span", { children: "Pick a section this link should scroll to." })
|
|
4892
|
+
] })
|
|
4893
|
+
] }) : null,
|
|
4894
|
+
state.showSectionPicker ? /* @__PURE__ */ jsx13(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
|
|
4895
|
+
state.showSectionRow && state.selectedSection ? /* @__PURE__ */ jsx13(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
|
|
4896
|
+
] }),
|
|
4897
|
+
/* @__PURE__ */ jsxs7("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
|
|
4898
|
+
/* @__PURE__ */ jsx13(
|
|
4899
|
+
Button,
|
|
4900
|
+
{
|
|
4901
|
+
type: "button",
|
|
4902
|
+
variant: isCancel ? "outline" : "ghost",
|
|
4903
|
+
className: "leading-6 shadow-none cursor-pointer",
|
|
4904
|
+
style: isCancel ? {
|
|
4905
|
+
backgroundColor: "var(--ohw-background)",
|
|
4906
|
+
borderColor: "var(--ohw-border)",
|
|
4907
|
+
color: "var(--ohw-foreground)"
|
|
4908
|
+
} : void 0,
|
|
4909
|
+
onClick: state.handleSecondary,
|
|
4910
|
+
children: state.secondaryLabel
|
|
4911
|
+
}
|
|
4912
|
+
),
|
|
4913
|
+
/* @__PURE__ */ jsx13(
|
|
4914
|
+
Button,
|
|
4915
|
+
{
|
|
4916
|
+
type: "button",
|
|
4917
|
+
className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
|
|
4918
|
+
style: {
|
|
4919
|
+
backgroundColor: "var(--ohw-primary)",
|
|
4920
|
+
color: "var(--ohw-primary-foreground)"
|
|
4921
|
+
},
|
|
4922
|
+
disabled: !state.isValid,
|
|
4923
|
+
onClick: state.handleSubmit,
|
|
4924
|
+
children: state.submitLabel
|
|
4925
|
+
}
|
|
4926
|
+
)
|
|
4927
|
+
] })
|
|
4928
|
+
] });
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
// src/ui/link-modal/LinkPopover.tsx
|
|
4932
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
4933
|
+
function LinkPopover({
|
|
4934
|
+
rect,
|
|
4935
|
+
parentScroll,
|
|
4936
|
+
panelRef,
|
|
4937
|
+
...editorProps
|
|
4938
|
+
}) {
|
|
4939
|
+
const { top, left, transform } = calcPopoverPos(rect, parentScroll);
|
|
4940
|
+
return /* @__PURE__ */ jsx14(
|
|
4941
|
+
"div",
|
|
4942
|
+
{
|
|
4943
|
+
ref: panelRef,
|
|
4944
|
+
"data-ohw-link-popover-root": "",
|
|
4945
|
+
"data-ohw-link-modal-root": "",
|
|
4946
|
+
"data-ohw-bridge": "",
|
|
4947
|
+
className: cn(
|
|
4948
|
+
"pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
|
|
4949
|
+
"rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
|
|
4950
|
+
),
|
|
4951
|
+
style: { top, left, transform },
|
|
4952
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
4953
|
+
children: /* @__PURE__ */ jsx14(LinkEditorPanel, { ...editorProps, open: true })
|
|
4954
|
+
}
|
|
4955
|
+
);
|
|
4956
|
+
}
|
|
4957
|
+
|
|
4958
|
+
// src/ui/link-modal/devFixtures.ts
|
|
4959
|
+
var DEV_SITE_PAGES = [
|
|
4960
|
+
{ path: "/", title: "Home" },
|
|
4961
|
+
{ path: "/about", title: "About" },
|
|
4962
|
+
{ path: "/classes", title: "Classes" },
|
|
4963
|
+
{ path: "/pricing", title: "Pricing" },
|
|
4964
|
+
{ path: "/studio", title: "Studio" },
|
|
4965
|
+
{ path: "/instructors", title: "Instructors" },
|
|
4966
|
+
{ path: "/contact", title: "Contact" }
|
|
4967
|
+
];
|
|
4968
|
+
var DEV_SECTIONS_BY_PATH = {
|
|
4969
|
+
"/": [
|
|
4970
|
+
{ id: "hero", label: "Hero" },
|
|
4971
|
+
{ id: "lagree-intro", label: "Lagree Intro" },
|
|
4972
|
+
{ id: "classes-strip", label: "Classes" },
|
|
4973
|
+
{ id: "feel-split", label: "Feel Split" },
|
|
4974
|
+
{ id: "founder-teaser", label: "Founder" },
|
|
4975
|
+
{ id: "plan-form", label: "Plan Form" },
|
|
4976
|
+
{ id: "testimonials", label: "Testimonials" },
|
|
4977
|
+
{ id: "wordmark", label: "Wordmark" }
|
|
4978
|
+
],
|
|
4979
|
+
"/about": [
|
|
4980
|
+
{ id: "manifesto", label: "Manifesto" },
|
|
4981
|
+
{ id: "story-letter", label: "Our Story" },
|
|
4982
|
+
{ id: "lagree-explainer", label: "Lagree Explainer" },
|
|
4983
|
+
{ id: "waitlist-cta", label: "Waitlist" },
|
|
4984
|
+
{ id: "personal-training", label: "Personal training" }
|
|
4985
|
+
],
|
|
4986
|
+
"/classes": [{ id: "class-library", label: "Class Library" }],
|
|
4987
|
+
"/pricing": [
|
|
4988
|
+
{ id: "benefits-marquee", label: "Benefits" },
|
|
4989
|
+
{ id: "monthly-memberships", label: "Memberships" },
|
|
4990
|
+
{ id: "package-matcher", label: "Packages" },
|
|
4991
|
+
{ id: "specialty-programs", label: "Specialty Programs" },
|
|
4992
|
+
{ id: "free-class-cta", label: "Free Class" }
|
|
4993
|
+
],
|
|
4994
|
+
"/studio": [
|
|
4995
|
+
{ id: "studio-intro", label: "Studio Intro" },
|
|
4996
|
+
{ id: "studio-features", label: "Studio Features" },
|
|
4997
|
+
{ id: "studio-gallery", label: "Studio Gallery" },
|
|
4998
|
+
{ id: "studio-visit", label: "Visit Studio" }
|
|
4999
|
+
],
|
|
5000
|
+
"/instructors": [{ id: "instructor-grid", label: "Instructors" }],
|
|
5001
|
+
"/contact": [
|
|
5002
|
+
{ id: "hello-hero", label: "Hello" },
|
|
5003
|
+
{ id: "contact-form", label: "Contact Form" },
|
|
5004
|
+
{ id: "faq", label: "FAQ" }
|
|
5005
|
+
]
|
|
5006
|
+
};
|
|
5007
|
+
function shouldUseDevFixtures() {
|
|
5008
|
+
if (typeof window === "undefined") return false;
|
|
5009
|
+
const raw = readPreservedSearch();
|
|
5010
|
+
const q = raw.startsWith("?") ? raw.slice(1) : raw;
|
|
5011
|
+
return new URLSearchParams(q).get("ohw-fixtures") === "1";
|
|
5012
|
+
}
|
|
5013
|
+
|
|
5014
|
+
// src/ui/badge.tsx
|
|
5015
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
5016
|
+
var badgeVariants = cva(
|
|
5017
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
5018
|
+
{
|
|
5019
|
+
variants: {
|
|
5020
|
+
variant: {
|
|
5021
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
5022
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
5023
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
5024
|
+
outline: "text-foreground"
|
|
5025
|
+
}
|
|
5026
|
+
},
|
|
5027
|
+
defaultVariants: {
|
|
5028
|
+
variant: "default"
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
);
|
|
5032
|
+
function Badge({ className, variant, ...props }) {
|
|
5033
|
+
return /* @__PURE__ */ jsx15("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
5034
|
+
}
|
|
5035
|
+
|
|
3436
5036
|
// src/OhhwellsBridge.tsx
|
|
3437
|
-
import { Fragment, jsx as
|
|
5037
|
+
import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3438
5038
|
var PRIMARY = "#0885FE";
|
|
3439
5039
|
var IMAGE_FADE_MS = 300;
|
|
3440
5040
|
function runOpacityFade(el, onDone) {
|
|
@@ -3485,8 +5085,173 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
3485
5085
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
3486
5086
|
});
|
|
3487
5087
|
}
|
|
5088
|
+
function getSectionsTracker() {
|
|
5089
|
+
let el = document.querySelector("[data-ohw-sections-tracker]");
|
|
5090
|
+
if (!el) {
|
|
5091
|
+
el = document.createElement("div");
|
|
5092
|
+
el.setAttribute("data-ohw-sections-tracker", "");
|
|
5093
|
+
el.style.display = "none";
|
|
5094
|
+
document.body.appendChild(el);
|
|
5095
|
+
}
|
|
5096
|
+
return el;
|
|
5097
|
+
}
|
|
5098
|
+
function updateSectionScheduleId(insertAfter, scheduleId) {
|
|
5099
|
+
const tracker = getSectionsTracker();
|
|
5100
|
+
let sections = [];
|
|
5101
|
+
try {
|
|
5102
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5103
|
+
} catch {
|
|
5104
|
+
}
|
|
5105
|
+
const currentPath = window.location.pathname;
|
|
5106
|
+
const updated = sections.map((s) => {
|
|
5107
|
+
if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
|
|
5108
|
+
if (s.pagePath && s.pagePath !== currentPath) return s;
|
|
5109
|
+
return { ...s, scheduleId };
|
|
5110
|
+
});
|
|
5111
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5112
|
+
return tracker.textContent ?? "[]";
|
|
5113
|
+
}
|
|
5114
|
+
function schedulingSectionId(insertAfter) {
|
|
5115
|
+
return `scheduling-${insertAfter}`;
|
|
5116
|
+
}
|
|
5117
|
+
var INSERT_BEFORE_MARKER = ":before:";
|
|
5118
|
+
function parseSchedulingInsertAfter(insertAfter) {
|
|
5119
|
+
const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
|
|
5120
|
+
if (idx < 0) return { anchor: insertAfter, insertBefore: null };
|
|
5121
|
+
return {
|
|
5122
|
+
anchor: insertAfter.slice(0, idx),
|
|
5123
|
+
insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
|
|
5124
|
+
};
|
|
5125
|
+
}
|
|
5126
|
+
function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
|
|
5127
|
+
const parsed = parseSchedulingInsertAfter(insertAfter);
|
|
5128
|
+
const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
|
|
5129
|
+
const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
|
|
5130
|
+
return { effectiveInsertAfter, insertBefore };
|
|
5131
|
+
}
|
|
5132
|
+
function getSchedulingMountPoint(insertAfter) {
|
|
5133
|
+
const { anchor } = parseSchedulingInsertAfter(insertAfter);
|
|
5134
|
+
let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
|
|
5135
|
+
if (!anchorEl && anchor === "scheduling") {
|
|
5136
|
+
const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
|
|
5137
|
+
anchorEl = widgets.at(-1) ?? null;
|
|
5138
|
+
}
|
|
5139
|
+
if (!anchorEl) return null;
|
|
5140
|
+
return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
|
|
5141
|
+
}
|
|
5142
|
+
function schedulingMountDepth(insertAfter) {
|
|
5143
|
+
if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
|
|
5144
|
+
return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
|
|
5145
|
+
}
|
|
5146
|
+
function getPageSchedulingEntries(raw) {
|
|
5147
|
+
if (!raw) return [];
|
|
5148
|
+
try {
|
|
5149
|
+
const entries = JSON.parse(raw);
|
|
5150
|
+
const currentPath = window.location.pathname;
|
|
5151
|
+
return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
|
|
5152
|
+
} catch {
|
|
5153
|
+
return [];
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
function isSchedulingWidgetMissing(entry) {
|
|
5157
|
+
const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
|
|
5158
|
+
return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
|
|
5159
|
+
}
|
|
5160
|
+
function hasMissingSchedulingWidgets(entries) {
|
|
5161
|
+
return entries.some(isSchedulingWidgetMissing);
|
|
5162
|
+
}
|
|
5163
|
+
function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
|
|
5164
|
+
if (!hasMissingSchedulingWidgets(entries)) return;
|
|
5165
|
+
mountSchedulingEntries(entries, notifyOnConnect);
|
|
5166
|
+
}
|
|
5167
|
+
function initSectionsFromContent(content, removeExisting = false) {
|
|
5168
|
+
const raw = content["__ohw_sections"];
|
|
5169
|
+
if (!raw) return;
|
|
5170
|
+
try {
|
|
5171
|
+
if (removeExisting) {
|
|
5172
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
|
|
5173
|
+
}
|
|
5174
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
5175
|
+
if (inEditor) getSectionsTracker().textContent = raw;
|
|
5176
|
+
const pageEntries = getPageSchedulingEntries(raw);
|
|
5177
|
+
mountSchedulingEntries(pageEntries, false);
|
|
5178
|
+
requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
|
|
5179
|
+
setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
|
|
5180
|
+
} catch {
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
|
|
5184
|
+
const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
|
|
5185
|
+
const sectionId = schedulingSectionId(effectiveInsertAfter);
|
|
5186
|
+
if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
|
|
5187
|
+
const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
|
|
5188
|
+
if (!mountPoint) return false;
|
|
5189
|
+
const container = document.createElement("div");
|
|
5190
|
+
container.dataset.ohwSectionContainer = "scheduling";
|
|
5191
|
+
if (insertBefore) {
|
|
5192
|
+
const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
|
|
5193
|
+
const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
|
|
5194
|
+
if (!beforePoint) return false;
|
|
5195
|
+
beforePoint.insertAdjacentElement("beforebegin", container);
|
|
5196
|
+
} else {
|
|
5197
|
+
let tail = mountPoint;
|
|
5198
|
+
while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
|
|
5199
|
+
tail = tail.nextElementSibling;
|
|
5200
|
+
}
|
|
5201
|
+
tail.insertAdjacentElement("afterend", container);
|
|
5202
|
+
}
|
|
5203
|
+
const root = createRoot(container);
|
|
5204
|
+
flushSync(() => {
|
|
5205
|
+
root.render(
|
|
5206
|
+
/* @__PURE__ */ jsx16(
|
|
5207
|
+
SchedulingWidget,
|
|
5208
|
+
{
|
|
5209
|
+
notifyOnConnect,
|
|
5210
|
+
initialScheduleId: scheduleId,
|
|
5211
|
+
insertAfter: effectiveInsertAfter
|
|
5212
|
+
}
|
|
5213
|
+
)
|
|
5214
|
+
);
|
|
5215
|
+
});
|
|
5216
|
+
const tracker = getSectionsTracker();
|
|
5217
|
+
let sections = [];
|
|
5218
|
+
try {
|
|
5219
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5220
|
+
} catch {
|
|
5221
|
+
}
|
|
5222
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
5223
|
+
if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
|
|
5224
|
+
sections.push({
|
|
5225
|
+
type: "scheduling",
|
|
5226
|
+
insertAfter: effectiveInsertAfter,
|
|
5227
|
+
pagePath: window.location.pathname,
|
|
5228
|
+
...scheduleId ? { scheduleId } : {}
|
|
5229
|
+
});
|
|
5230
|
+
tracker.textContent = JSON.stringify(sections);
|
|
5231
|
+
}
|
|
5232
|
+
return true;
|
|
5233
|
+
}
|
|
5234
|
+
function mountSchedulingEntries(entries, notifyOnConnect = false) {
|
|
5235
|
+
const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
|
|
5236
|
+
for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
|
|
5237
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
5238
|
+
const { insertAfter, scheduleId } = pending[i];
|
|
5239
|
+
if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
|
|
5240
|
+
pending.splice(i, 1);
|
|
5241
|
+
}
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
function getLinkHref(el) {
|
|
5246
|
+
const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
|
|
5247
|
+
return anchor?.getAttribute("href") ?? "";
|
|
5248
|
+
}
|
|
5249
|
+
function applyLinkHref(el, val) {
|
|
5250
|
+
const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
|
|
5251
|
+
if (anchor) anchor.setAttribute("href", val);
|
|
5252
|
+
}
|
|
3488
5253
|
function collectEditableNodes() {
|
|
3489
|
-
|
|
5254
|
+
const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
3490
5255
|
if (el.dataset.ohwEditable === "image") {
|
|
3491
5256
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
3492
5257
|
return { key: el.dataset.ohwKey ?? "", type: "image", text: img?.src ?? "" };
|
|
@@ -3496,12 +5261,52 @@ function collectEditableNodes() {
|
|
|
3496
5261
|
const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
|
|
3497
5262
|
return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
|
|
3498
5263
|
}
|
|
5264
|
+
if (el.dataset.ohwEditable === "link") {
|
|
5265
|
+
return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
|
|
5266
|
+
}
|
|
3499
5267
|
return {
|
|
3500
5268
|
key: el.dataset.ohwKey ?? "",
|
|
3501
5269
|
type: el.dataset.ohwEditable ?? "text",
|
|
3502
5270
|
text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
|
|
3503
5271
|
};
|
|
3504
5272
|
});
|
|
5273
|
+
document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
|
|
5274
|
+
const key = el.getAttribute("data-ohw-href-key") ?? "";
|
|
5275
|
+
if (!key) return;
|
|
5276
|
+
nodes.push({ key, type: "link", text: getLinkHref(el) });
|
|
5277
|
+
});
|
|
5278
|
+
return nodes;
|
|
5279
|
+
}
|
|
5280
|
+
function applyLinkByKey(key, val) {
|
|
5281
|
+
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
5282
|
+
if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
|
|
5283
|
+
});
|
|
5284
|
+
document.querySelectorAll(`[data-ohw-href-key="${key}"]`).forEach((el) => {
|
|
5285
|
+
applyLinkHref(el, val);
|
|
5286
|
+
});
|
|
5287
|
+
}
|
|
5288
|
+
function isInsideLinkEditor(target) {
|
|
5289
|
+
return Boolean(
|
|
5290
|
+
target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
|
|
5291
|
+
);
|
|
5292
|
+
}
|
|
5293
|
+
function getHrefKeyFromElement(el) {
|
|
5294
|
+
if (!el) return null;
|
|
5295
|
+
const anchor = el.closest("[data-ohw-href-key]");
|
|
5296
|
+
if (!anchor) return null;
|
|
5297
|
+
const key = anchor.getAttribute("data-ohw-href-key");
|
|
5298
|
+
if (!key) return null;
|
|
5299
|
+
return { anchor, key };
|
|
5300
|
+
}
|
|
5301
|
+
function titleCaseSectionId(id) {
|
|
5302
|
+
return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
5303
|
+
}
|
|
5304
|
+
function collectSections() {
|
|
5305
|
+
return Array.from(document.querySelectorAll("[data-ohw-section]")).map((el) => {
|
|
5306
|
+
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
5307
|
+
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
5308
|
+
return { id, label };
|
|
5309
|
+
});
|
|
3505
5310
|
}
|
|
3506
5311
|
var FORCE_PSEUDO_STATES = [
|
|
3507
5312
|
{ pseudo: ":hover", state: "hover" },
|
|
@@ -3642,7 +5447,7 @@ var TOOLBAR_GROUPS = [
|
|
|
3642
5447
|
];
|
|
3643
5448
|
function GlowFrame({ rect, elRef }) {
|
|
3644
5449
|
const GAP = 6;
|
|
3645
|
-
return /* @__PURE__ */
|
|
5450
|
+
return /* @__PURE__ */ jsx16(
|
|
3646
5451
|
"div",
|
|
3647
5452
|
{
|
|
3648
5453
|
ref: elRef,
|
|
@@ -3690,10 +5495,12 @@ function FloatingToolbar({
|
|
|
3690
5495
|
parentScroll,
|
|
3691
5496
|
elRef,
|
|
3692
5497
|
onCommand,
|
|
3693
|
-
activeCommands
|
|
5498
|
+
activeCommands,
|
|
5499
|
+
showEditLink,
|
|
5500
|
+
onEditLink
|
|
3694
5501
|
}) {
|
|
3695
5502
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
3696
|
-
return /* @__PURE__ */
|
|
5503
|
+
return /* @__PURE__ */ jsxs8(
|
|
3697
5504
|
"div",
|
|
3698
5505
|
{
|
|
3699
5506
|
ref: elRef,
|
|
@@ -3716,56 +5523,97 @@ function FloatingToolbar({
|
|
|
3716
5523
|
pointerEvents: "auto",
|
|
3717
5524
|
whiteSpace: "nowrap"
|
|
3718
5525
|
},
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
5526
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
5527
|
+
children: [
|
|
5528
|
+
TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ jsxs8(React5.Fragment, { children: [
|
|
5529
|
+
gi > 0 && /* @__PURE__ */ jsx16("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
|
|
5530
|
+
btns.map((btn) => {
|
|
5531
|
+
const isActive = activeCommands.has(btn.cmd);
|
|
5532
|
+
return /* @__PURE__ */ jsx16(
|
|
5533
|
+
"button",
|
|
5534
|
+
{
|
|
5535
|
+
title: btn.title,
|
|
5536
|
+
onMouseDown: (e) => {
|
|
5537
|
+
e.preventDefault();
|
|
5538
|
+
onCommand(btn.cmd);
|
|
5539
|
+
},
|
|
5540
|
+
onMouseEnter: (e) => {
|
|
5541
|
+
if (!isActive) e.currentTarget.style.background = "#F5F5F4";
|
|
5542
|
+
},
|
|
5543
|
+
onMouseLeave: (e) => {
|
|
5544
|
+
if (!isActive) e.currentTarget.style.background = "transparent";
|
|
5545
|
+
},
|
|
5546
|
+
style: {
|
|
5547
|
+
display: "flex",
|
|
5548
|
+
alignItems: "center",
|
|
5549
|
+
justifyContent: "center",
|
|
5550
|
+
border: "none",
|
|
5551
|
+
background: isActive ? PRIMARY : "transparent",
|
|
5552
|
+
borderRadius: 4,
|
|
5553
|
+
cursor: "pointer",
|
|
5554
|
+
color: isActive ? "#FFFFFF" : "#1C1917",
|
|
5555
|
+
flexShrink: 0,
|
|
5556
|
+
padding: 6
|
|
5557
|
+
},
|
|
5558
|
+
children: /* @__PURE__ */ jsx16(
|
|
5559
|
+
"svg",
|
|
5560
|
+
{
|
|
5561
|
+
width: "16",
|
|
5562
|
+
height: "16",
|
|
5563
|
+
viewBox: "0 0 24 24",
|
|
5564
|
+
fill: "none",
|
|
5565
|
+
stroke: isActive ? "#FFFFFF" : "#1C1917",
|
|
5566
|
+
strokeWidth: "2.5",
|
|
5567
|
+
strokeLinecap: "round",
|
|
5568
|
+
strokeLinejoin: "round",
|
|
5569
|
+
style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
|
|
5570
|
+
dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
|
|
5571
|
+
}
|
|
5572
|
+
)
|
|
3730
5573
|
},
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
},
|
|
3749
|
-
children: /* @__PURE__ */ jsx3(
|
|
3750
|
-
"svg",
|
|
3751
|
-
{
|
|
3752
|
-
width: "16",
|
|
3753
|
-
height: "16",
|
|
3754
|
-
viewBox: "0 0 24 24",
|
|
3755
|
-
fill: "none",
|
|
3756
|
-
stroke: isActive ? "#FFFFFF" : "#1C1917",
|
|
3757
|
-
strokeWidth: "2.5",
|
|
3758
|
-
strokeLinecap: "round",
|
|
3759
|
-
strokeLinejoin: "round",
|
|
3760
|
-
style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
|
|
3761
|
-
dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
|
|
3762
|
-
}
|
|
3763
|
-
)
|
|
5574
|
+
btn.cmd
|
|
5575
|
+
);
|
|
5576
|
+
})
|
|
5577
|
+
] }, gi)),
|
|
5578
|
+
showEditLink ? /* @__PURE__ */ jsx16(
|
|
5579
|
+
"button",
|
|
5580
|
+
{
|
|
5581
|
+
type: "button",
|
|
5582
|
+
title: "Edit link",
|
|
5583
|
+
onMouseDown: (e) => {
|
|
5584
|
+
e.preventDefault();
|
|
5585
|
+
e.stopPropagation();
|
|
5586
|
+
onEditLink?.();
|
|
5587
|
+
},
|
|
5588
|
+
onClick: (e) => {
|
|
5589
|
+
e.preventDefault();
|
|
5590
|
+
e.stopPropagation();
|
|
3764
5591
|
},
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
5592
|
+
onMouseEnter: (e) => {
|
|
5593
|
+
e.currentTarget.style.background = "#F5F5F4";
|
|
5594
|
+
},
|
|
5595
|
+
onMouseLeave: (e) => {
|
|
5596
|
+
e.currentTarget.style.background = "transparent";
|
|
5597
|
+
},
|
|
5598
|
+
style: {
|
|
5599
|
+
display: "flex",
|
|
5600
|
+
alignItems: "center",
|
|
5601
|
+
justifyContent: "center",
|
|
5602
|
+
border: "none",
|
|
5603
|
+
background: "transparent",
|
|
5604
|
+
borderRadius: 4,
|
|
5605
|
+
cursor: "pointer",
|
|
5606
|
+
color: "#1C1917",
|
|
5607
|
+
flexShrink: 0,
|
|
5608
|
+
padding: 6
|
|
5609
|
+
},
|
|
5610
|
+
children: /* @__PURE__ */ jsxs8("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
5611
|
+
/* @__PURE__ */ jsx16("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
|
|
5612
|
+
/* @__PURE__ */ jsx16("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
|
|
5613
|
+
] })
|
|
5614
|
+
}
|
|
5615
|
+
) : null
|
|
5616
|
+
]
|
|
3769
5617
|
}
|
|
3770
5618
|
);
|
|
3771
5619
|
}
|
|
@@ -3779,7 +5627,7 @@ function StateToggle({
|
|
|
3779
5627
|
states,
|
|
3780
5628
|
onStateChange
|
|
3781
5629
|
}) {
|
|
3782
|
-
return /* @__PURE__ */
|
|
5630
|
+
return /* @__PURE__ */ jsx16(
|
|
3783
5631
|
ToggleGroup,
|
|
3784
5632
|
{
|
|
3785
5633
|
"data-ohw-state-toggle": "",
|
|
@@ -3793,18 +5641,35 @@ function StateToggle({
|
|
|
3793
5641
|
left: rect.right - 8,
|
|
3794
5642
|
transform: "translateX(-100%)"
|
|
3795
5643
|
},
|
|
3796
|
-
children: states.map((state) => /* @__PURE__ */
|
|
5644
|
+
children: states.map((state) => /* @__PURE__ */ jsx16(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
|
|
3797
5645
|
}
|
|
3798
5646
|
);
|
|
3799
5647
|
}
|
|
3800
5648
|
var contentCache = /* @__PURE__ */ new Map();
|
|
5649
|
+
function resolveSubdomain(subdomainFromQuery) {
|
|
5650
|
+
if (subdomainFromQuery) return subdomainFromQuery;
|
|
5651
|
+
if (typeof window !== "undefined") {
|
|
5652
|
+
const parts = window.location.hostname.split(".");
|
|
5653
|
+
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
5654
|
+
}
|
|
5655
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
|
|
5656
|
+
if (siteUrl) {
|
|
5657
|
+
try {
|
|
5658
|
+
const host = new URL(siteUrl).hostname;
|
|
5659
|
+
const siteParts = host.split(".");
|
|
5660
|
+
if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
|
|
5661
|
+
} catch {
|
|
5662
|
+
}
|
|
5663
|
+
}
|
|
5664
|
+
return "";
|
|
5665
|
+
}
|
|
3801
5666
|
function OhhwellsBridge() {
|
|
3802
5667
|
const pathname = usePathname();
|
|
3803
5668
|
const router = useRouter();
|
|
3804
5669
|
const searchParams = useSearchParams();
|
|
3805
5670
|
const isEditMode = isEditSessionActive();
|
|
3806
|
-
const [bridgeRoot, setBridgeRoot] =
|
|
3807
|
-
|
|
5671
|
+
const [bridgeRoot, setBridgeRoot] = useState5(null);
|
|
5672
|
+
useEffect4(() => {
|
|
3808
5673
|
const figtreeFontId = "ohw-figtree-font";
|
|
3809
5674
|
if (!document.getElementById(figtreeFontId)) {
|
|
3810
5675
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -3827,42 +5692,76 @@ function OhhwellsBridge() {
|
|
|
3827
5692
|
};
|
|
3828
5693
|
}, []);
|
|
3829
5694
|
const subdomainFromQuery = searchParams.get("subdomain");
|
|
3830
|
-
const subdomain = subdomainFromQuery
|
|
3831
|
-
|
|
3832
|
-
const parts = window.location.hostname.split(".");
|
|
3833
|
-
return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
|
|
3834
|
-
})();
|
|
3835
|
-
const postToParent = useCallback((data) => {
|
|
5695
|
+
const subdomain = resolveSubdomain(subdomainFromQuery);
|
|
5696
|
+
const postToParent = useCallback2((data) => {
|
|
3836
5697
|
if (typeof window !== "undefined" && window.parent !== window) {
|
|
3837
5698
|
window.parent.postMessage(data, "*");
|
|
3838
5699
|
}
|
|
3839
5700
|
}, []);
|
|
3840
|
-
const [fetchState, setFetchState] =
|
|
3841
|
-
const autoSaveTimers =
|
|
3842
|
-
const activeElRef =
|
|
3843
|
-
const originalContentRef =
|
|
3844
|
-
const activeStateElRef =
|
|
3845
|
-
const parentScrollRef =
|
|
3846
|
-
const toolbarElRef =
|
|
3847
|
-
const glowElRef =
|
|
3848
|
-
const hoveredImageRef =
|
|
3849
|
-
const hoveredImageHasTextOverlapRef =
|
|
3850
|
-
const
|
|
3851
|
-
const
|
|
3852
|
-
const
|
|
3853
|
-
const
|
|
5701
|
+
const [fetchState, setFetchState] = useState5("idle");
|
|
5702
|
+
const autoSaveTimers = useRef3(/* @__PURE__ */ new Map());
|
|
5703
|
+
const activeElRef = useRef3(null);
|
|
5704
|
+
const originalContentRef = useRef3(null);
|
|
5705
|
+
const activeStateElRef = useRef3(null);
|
|
5706
|
+
const parentScrollRef = useRef3(null);
|
|
5707
|
+
const toolbarElRef = useRef3(null);
|
|
5708
|
+
const glowElRef = useRef3(null);
|
|
5709
|
+
const hoveredImageRef = useRef3(null);
|
|
5710
|
+
const hoveredImageHasTextOverlapRef = useRef3(false);
|
|
5711
|
+
const hoveredGapRef = useRef3(null);
|
|
5712
|
+
const imageUnhoverTimerRef = useRef3(null);
|
|
5713
|
+
const imageShowTimerRef = useRef3(null);
|
|
5714
|
+
const editStylesRef = useRef3(null);
|
|
5715
|
+
const activateRef = useRef3(() => {
|
|
3854
5716
|
});
|
|
3855
|
-
const deactivateRef =
|
|
5717
|
+
const deactivateRef = useRef3(() => {
|
|
3856
5718
|
});
|
|
3857
|
-
const refreshActiveCommandsRef =
|
|
5719
|
+
const refreshActiveCommandsRef = useRef3(() => {
|
|
3858
5720
|
});
|
|
3859
|
-
const postToParentRef =
|
|
5721
|
+
const postToParentRef = useRef3(postToParent);
|
|
3860
5722
|
postToParentRef.current = postToParent;
|
|
3861
|
-
const [toolbarRect, setToolbarRect] =
|
|
3862
|
-
const [toggleState, setToggleState] =
|
|
3863
|
-
const [maxBadge, setMaxBadge] =
|
|
3864
|
-
const [activeCommands, setActiveCommands] =
|
|
3865
|
-
|
|
5723
|
+
const [toolbarRect, setToolbarRect] = useState5(null);
|
|
5724
|
+
const [toggleState, setToggleState] = useState5(null);
|
|
5725
|
+
const [maxBadge, setMaxBadge] = useState5(null);
|
|
5726
|
+
const [activeCommands, setActiveCommands] = useState5(/* @__PURE__ */ new Set());
|
|
5727
|
+
const [toolbarShowEditLink, setToolbarShowEditLink] = useState5(false);
|
|
5728
|
+
const [linkPopover, setLinkPopover] = useState5(null);
|
|
5729
|
+
const [sitePages, setSitePages] = useState5([]);
|
|
5730
|
+
const [sectionsByPath, setSectionsByPath] = useState5({});
|
|
5731
|
+
const setLinkPopoverRef = useRef3(setLinkPopover);
|
|
5732
|
+
const linkPopoverPanelRef = useRef3(null);
|
|
5733
|
+
const linkPopoverOpenRef = useRef3(false);
|
|
5734
|
+
const linkPopoverGraceUntilRef = useRef3(0);
|
|
5735
|
+
setLinkPopoverRef.current = setLinkPopover;
|
|
5736
|
+
const bumpLinkPopoverGrace = () => {
|
|
5737
|
+
linkPopoverGraceUntilRef.current = Date.now() + 350;
|
|
5738
|
+
};
|
|
5739
|
+
useEffect4(() => {
|
|
5740
|
+
if (!isEditMode) return;
|
|
5741
|
+
const useFixtures = shouldUseDevFixtures();
|
|
5742
|
+
if (useFixtures) {
|
|
5743
|
+
setSitePages(DEV_SITE_PAGES);
|
|
5744
|
+
setSectionsByPath(DEV_SECTIONS_BY_PATH);
|
|
5745
|
+
if (process.env.NODE_ENV === "development") {
|
|
5746
|
+
console.info("[ohhwells-bridge] Dev fixtures loaded (ohw-fixtures=1)");
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
const onSitePages = (e) => {
|
|
5750
|
+
if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
|
|
5751
|
+
if (useFixtures) return;
|
|
5752
|
+
setSitePages(
|
|
5753
|
+
e.data.pages.map((p) => ({
|
|
5754
|
+
path: p.path,
|
|
5755
|
+
title: p.title
|
|
5756
|
+
}))
|
|
5757
|
+
);
|
|
5758
|
+
};
|
|
5759
|
+
window.addEventListener("message", onSitePages);
|
|
5760
|
+
if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
|
|
5761
|
+
return () => window.removeEventListener("message", onSitePages);
|
|
5762
|
+
}, [isEditMode, postToParent]);
|
|
5763
|
+
const [sectionGap, setSectionGap] = useState5(null);
|
|
5764
|
+
useEffect4(() => {
|
|
3866
5765
|
const update = () => {
|
|
3867
5766
|
const el = activeElRef.current;
|
|
3868
5767
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -3870,6 +5769,12 @@ function OhhwellsBridge() {
|
|
|
3870
5769
|
if (!prev || !activeStateElRef.current) return prev;
|
|
3871
5770
|
return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
|
|
3872
5771
|
});
|
|
5772
|
+
setSectionGap((prev) => {
|
|
5773
|
+
if (!prev) return prev;
|
|
5774
|
+
const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
|
|
5775
|
+
if (!anchor) return prev;
|
|
5776
|
+
return { ...prev, y: anchor.getBoundingClientRect().bottom };
|
|
5777
|
+
});
|
|
3873
5778
|
};
|
|
3874
5779
|
const vvp = window.visualViewport;
|
|
3875
5780
|
if (!vvp) return;
|
|
@@ -3880,10 +5785,10 @@ function OhhwellsBridge() {
|
|
|
3880
5785
|
vvp.removeEventListener("resize", update);
|
|
3881
5786
|
};
|
|
3882
5787
|
}, []);
|
|
3883
|
-
const refreshStateRules =
|
|
5788
|
+
const refreshStateRules = useCallback2(() => {
|
|
3884
5789
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
3885
5790
|
}, []);
|
|
3886
|
-
const deactivate =
|
|
5791
|
+
const deactivate = useCallback2(() => {
|
|
3887
5792
|
const el = activeElRef.current;
|
|
3888
5793
|
if (!el) return;
|
|
3889
5794
|
const key = el.dataset.ohwKey;
|
|
@@ -3906,9 +5811,10 @@ function OhhwellsBridge() {
|
|
|
3906
5811
|
setToolbarRect(null);
|
|
3907
5812
|
setMaxBadge(null);
|
|
3908
5813
|
setActiveCommands(/* @__PURE__ */ new Set());
|
|
5814
|
+
setToolbarShowEditLink(false);
|
|
3909
5815
|
postToParent({ type: "ow:exit-edit" });
|
|
3910
5816
|
}, [postToParent]);
|
|
3911
|
-
const activate =
|
|
5817
|
+
const activate = useCallback2((el) => {
|
|
3912
5818
|
if (activeElRef.current === el) return;
|
|
3913
5819
|
deactivate();
|
|
3914
5820
|
if (hoveredImageRef.current) {
|
|
@@ -3921,6 +5827,7 @@ function OhhwellsBridge() {
|
|
|
3921
5827
|
originalContentRef.current = el.innerHTML;
|
|
3922
5828
|
el.focus();
|
|
3923
5829
|
setToolbarRect(el.getBoundingClientRect());
|
|
5830
|
+
setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
|
|
3924
5831
|
postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
|
|
3925
5832
|
requestAnimationFrame(() => refreshActiveCommandsRef.current());
|
|
3926
5833
|
}, [deactivate, postToParent]);
|
|
@@ -3934,6 +5841,7 @@ function OhhwellsBridge() {
|
|
|
3934
5841
|
const applyContent = (content) => {
|
|
3935
5842
|
const imageLoads = [];
|
|
3936
5843
|
for (const [key, val] of Object.entries(content)) {
|
|
5844
|
+
if (key === "__ohw_sections") continue;
|
|
3937
5845
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3938
5846
|
if (el.dataset.ohwEditable === "image") {
|
|
3939
5847
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -3947,11 +5855,15 @@ function OhhwellsBridge() {
|
|
|
3947
5855
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
3948
5856
|
const next = `url('${val}')`;
|
|
3949
5857
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
5858
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
5859
|
+
applyLinkHref(el, val);
|
|
3950
5860
|
} else if (el.innerHTML !== val) {
|
|
3951
5861
|
el.innerHTML = val;
|
|
3952
5862
|
}
|
|
3953
5863
|
});
|
|
5864
|
+
applyLinkByKey(key, val);
|
|
3954
5865
|
}
|
|
5866
|
+
initSectionsFromContent(content, true);
|
|
3955
5867
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
3956
5868
|
}) : Promise.resolve();
|
|
3957
5869
|
};
|
|
@@ -3976,12 +5888,15 @@ function OhhwellsBridge() {
|
|
|
3976
5888
|
cancelled = true;
|
|
3977
5889
|
};
|
|
3978
5890
|
}, [subdomain, isEditMode, pathname]);
|
|
3979
|
-
|
|
5891
|
+
useEffect4(() => {
|
|
3980
5892
|
if (!subdomain || isEditMode) return;
|
|
5893
|
+
let debounceTimer = null;
|
|
3981
5894
|
const applyFromCache = () => {
|
|
3982
5895
|
const content = contentCache.get(subdomain);
|
|
3983
5896
|
if (!content) return;
|
|
5897
|
+
retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
|
|
3984
5898
|
for (const [key, val] of Object.entries(content)) {
|
|
5899
|
+
if (key === "__ohw_sections") continue;
|
|
3985
5900
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3986
5901
|
if (el.dataset.ohwEditable === "image") {
|
|
3987
5902
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -3989,27 +5904,37 @@ function OhhwellsBridge() {
|
|
|
3989
5904
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
3990
5905
|
const next = `url('${val}')`;
|
|
3991
5906
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
5907
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
5908
|
+
applyLinkHref(el, val);
|
|
3992
5909
|
} else if (el.innerHTML !== val) {
|
|
3993
5910
|
el.innerHTML = val;
|
|
3994
5911
|
}
|
|
3995
5912
|
});
|
|
5913
|
+
applyLinkByKey(key, val);
|
|
3996
5914
|
}
|
|
3997
5915
|
};
|
|
3998
|
-
|
|
3999
|
-
|
|
5916
|
+
const scheduleApply = () => {
|
|
5917
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
5918
|
+
debounceTimer = setTimeout(applyFromCache, 150);
|
|
5919
|
+
};
|
|
5920
|
+
scheduleApply();
|
|
5921
|
+
const observer = new MutationObserver(scheduleApply);
|
|
4000
5922
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
4001
|
-
return () =>
|
|
4002
|
-
|
|
5923
|
+
return () => {
|
|
5924
|
+
observer.disconnect();
|
|
5925
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
5926
|
+
};
|
|
5927
|
+
}, [subdomain, isEditMode, pathname]);
|
|
4003
5928
|
useLayoutEffect(() => {
|
|
4004
5929
|
const el = document.getElementById("ohw-loader");
|
|
4005
5930
|
if (!el) return;
|
|
4006
5931
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
4007
5932
|
el.style.display = visible ? "flex" : "none";
|
|
4008
5933
|
}, [subdomain, fetchState]);
|
|
4009
|
-
|
|
5934
|
+
useEffect4(() => {
|
|
4010
5935
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
4011
5936
|
}, [pathname, postToParent]);
|
|
4012
|
-
|
|
5937
|
+
useEffect4(() => {
|
|
4013
5938
|
if (!isEditMode) return;
|
|
4014
5939
|
const measure = () => {
|
|
4015
5940
|
const h = document.body.scrollHeight;
|
|
@@ -4033,7 +5958,7 @@ function OhhwellsBridge() {
|
|
|
4033
5958
|
window.removeEventListener("resize", handleResize);
|
|
4034
5959
|
};
|
|
4035
5960
|
}, [pathname, isEditMode, postToParent]);
|
|
4036
|
-
|
|
5961
|
+
useEffect4(() => {
|
|
4037
5962
|
if (!subdomainFromQuery || isEditMode) return;
|
|
4038
5963
|
const handleClick = (e) => {
|
|
4039
5964
|
const anchor = e.target.closest("a");
|
|
@@ -4049,7 +5974,7 @@ function OhhwellsBridge() {
|
|
|
4049
5974
|
document.addEventListener("click", handleClick, true);
|
|
4050
5975
|
return () => document.removeEventListener("click", handleClick, true);
|
|
4051
5976
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
4052
|
-
|
|
5977
|
+
useEffect4(() => {
|
|
4053
5978
|
if (!isEditMode) {
|
|
4054
5979
|
editStylesRef.current?.base.remove();
|
|
4055
5980
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -4075,6 +6000,7 @@ function OhhwellsBridge() {
|
|
|
4075
6000
|
[data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
|
|
4076
6001
|
[data-ohw-editable="image"], [data-ohw-editable="image"] *,
|
|
4077
6002
|
[data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
|
|
6003
|
+
[data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
|
|
4078
6004
|
[data-ohw-hovered]:not([contenteditable]) {
|
|
4079
6005
|
outline: 2px dashed ${PRIMARY} !important;
|
|
4080
6006
|
outline-offset: 4px;
|
|
@@ -4112,8 +6038,21 @@ function OhhwellsBridge() {
|
|
|
4112
6038
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
4113
6039
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
4114
6040
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
6041
|
+
if (isInsideLinkEditor(target)) return;
|
|
4115
6042
|
const editable = target.closest("[data-ohw-editable]");
|
|
4116
6043
|
if (editable) {
|
|
6044
|
+
if (editable.dataset.ohwEditable === "link") {
|
|
6045
|
+
e.preventDefault();
|
|
6046
|
+
e.stopPropagation();
|
|
6047
|
+
deactivateRef.current();
|
|
6048
|
+
bumpLinkPopoverGrace();
|
|
6049
|
+
setLinkPopoverRef.current({
|
|
6050
|
+
key: editable.dataset.ohwKey ?? "",
|
|
6051
|
+
target: getLinkHref(editable),
|
|
6052
|
+
rect: editable.getBoundingClientRect()
|
|
6053
|
+
});
|
|
6054
|
+
return;
|
|
6055
|
+
}
|
|
4117
6056
|
if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
|
|
4118
6057
|
e.preventDefault();
|
|
4119
6058
|
e.stopPropagation();
|
|
@@ -4134,6 +6073,15 @@ function OhhwellsBridge() {
|
|
|
4134
6073
|
}
|
|
4135
6074
|
const anchor = target.closest("a");
|
|
4136
6075
|
if (anchor) e.preventDefault();
|
|
6076
|
+
if (Date.now() < linkPopoverGraceUntilRef.current) return;
|
|
6077
|
+
if (linkPopoverOpenRef.current && activeElRef.current) {
|
|
6078
|
+
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6079
|
+
if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
|
|
6080
|
+
}
|
|
6081
|
+
if (linkPopoverOpenRef.current) {
|
|
6082
|
+
setLinkPopoverRef.current(null);
|
|
6083
|
+
return;
|
|
6084
|
+
}
|
|
4137
6085
|
deactivateRef.current();
|
|
4138
6086
|
};
|
|
4139
6087
|
const handleMouseOver = (e) => {
|
|
@@ -4309,6 +6257,15 @@ function OhhwellsBridge() {
|
|
|
4309
6257
|
textEditable.setAttribute("data-ohw-hovered", "");
|
|
4310
6258
|
return;
|
|
4311
6259
|
}
|
|
6260
|
+
if (hoveredGapRef.current) {
|
|
6261
|
+
if (hoveredImageRef.current) {
|
|
6262
|
+
hoveredImageRef.current = null;
|
|
6263
|
+
resumeAnimTracks();
|
|
6264
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
6265
|
+
}
|
|
6266
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
6267
|
+
return;
|
|
6268
|
+
}
|
|
4312
6269
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4313
6270
|
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
4314
6271
|
hoveredImageRef.current = imgEl;
|
|
@@ -4374,8 +6331,30 @@ function OhhwellsBridge() {
|
|
|
4374
6331
|
}
|
|
4375
6332
|
}
|
|
4376
6333
|
};
|
|
6334
|
+
const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
|
|
6335
|
+
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
6336
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
6337
|
+
const ZONE = 20;
|
|
6338
|
+
for (let i = 0; i < sections.length; i++) {
|
|
6339
|
+
const a = sections[i];
|
|
6340
|
+
const b = sections[i + 1] ?? null;
|
|
6341
|
+
const boundaryY = a.getBoundingClientRect().bottom;
|
|
6342
|
+
if (Math.abs(y - boundaryY) <= ZONE) {
|
|
6343
|
+
const insertAfter = a.dataset.ohwSection ?? "";
|
|
6344
|
+
const insertBefore = b?.dataset.ohwSection ?? null;
|
|
6345
|
+
hoveredGapRef.current = { insertAfter, insertBefore };
|
|
6346
|
+
setSectionGap({ insertAfter, insertBefore, y: boundaryY });
|
|
6347
|
+
return;
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
if (hoveredGapRef.current) {
|
|
6351
|
+
hoveredGapRef.current = null;
|
|
6352
|
+
setSectionGap(null);
|
|
6353
|
+
}
|
|
6354
|
+
};
|
|
4377
6355
|
const handleMouseMove = (e) => {
|
|
4378
6356
|
const { clientX, clientY } = e;
|
|
6357
|
+
probeSectionGapAt(clientX, clientY);
|
|
4379
6358
|
probeImageAt(clientX, clientY);
|
|
4380
6359
|
probeHoverCardsAt(clientX, clientY);
|
|
4381
6360
|
};
|
|
@@ -4383,6 +6362,7 @@ function OhhwellsBridge() {
|
|
|
4383
6362
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
4384
6363
|
const { clientX, clientY } = e.data;
|
|
4385
6364
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
6365
|
+
probeSectionGapAt(clientX, clientY);
|
|
4386
6366
|
probeImageAt(clientX, clientY);
|
|
4387
6367
|
probeHoverCardsAt(clientX, clientY);
|
|
4388
6368
|
};
|
|
@@ -4540,27 +6520,47 @@ function OhhwellsBridge() {
|
|
|
4540
6520
|
if (e.data?.type !== "ow:hydrate") return;
|
|
4541
6521
|
const content = e.data.content;
|
|
4542
6522
|
if (!content) return;
|
|
6523
|
+
let sectionsJson = null;
|
|
4543
6524
|
for (const [key, val] of Object.entries(content)) {
|
|
6525
|
+
if (key === "__ohw_sections") {
|
|
6526
|
+
sectionsJson = val;
|
|
6527
|
+
continue;
|
|
6528
|
+
}
|
|
4544
6529
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4545
6530
|
if (el.dataset.ohwEditable === "image") {
|
|
4546
6531
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
4547
6532
|
if (img) img.src = val;
|
|
4548
6533
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
4549
6534
|
el.style.backgroundImage = `url('${val}')`;
|
|
6535
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
6536
|
+
applyLinkHref(el, val);
|
|
4550
6537
|
} else {
|
|
4551
6538
|
el.innerHTML = val;
|
|
4552
6539
|
}
|
|
4553
6540
|
});
|
|
6541
|
+
applyLinkByKey(key, val);
|
|
6542
|
+
}
|
|
6543
|
+
if (sectionsJson) {
|
|
6544
|
+
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
4554
6545
|
}
|
|
4555
6546
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
4556
6547
|
};
|
|
4557
6548
|
window.addEventListener("message", handleHydrate);
|
|
4558
6549
|
const handleDeactivate = (e) => {
|
|
4559
6550
|
if (e.data?.type !== "ow:deactivate") return;
|
|
6551
|
+
if (Date.now() < linkPopoverGraceUntilRef.current) return;
|
|
6552
|
+
if (linkPopoverOpenRef.current) {
|
|
6553
|
+
setLinkPopoverRef.current(null);
|
|
6554
|
+
return;
|
|
6555
|
+
}
|
|
4560
6556
|
deactivateRef.current();
|
|
4561
6557
|
};
|
|
4562
6558
|
window.addEventListener("message", handleDeactivate);
|
|
4563
6559
|
const handleKeyDown = (e) => {
|
|
6560
|
+
if (e.key === "Escape" && linkPopoverOpenRef.current) {
|
|
6561
|
+
setLinkPopoverRef.current(null);
|
|
6562
|
+
return;
|
|
6563
|
+
}
|
|
4564
6564
|
if (e.key !== "Escape") return;
|
|
4565
6565
|
const el = activeElRef.current;
|
|
4566
6566
|
if (el && originalContentRef.current !== null) {
|
|
@@ -4595,7 +6595,63 @@ function OhhwellsBridge() {
|
|
|
4595
6595
|
};
|
|
4596
6596
|
const handleSave = (e) => {
|
|
4597
6597
|
if (e.data?.type !== "ow:save") return;
|
|
4598
|
-
|
|
6598
|
+
const nodes = collectEditableNodes();
|
|
6599
|
+
const tracker = document.querySelector("[data-ohw-sections-tracker]");
|
|
6600
|
+
if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
|
|
6601
|
+
postToParentRef.current({ type: "ow:save-result", nodes });
|
|
6602
|
+
};
|
|
6603
|
+
const handleInsertSection = (e) => {
|
|
6604
|
+
if (e.data?.type !== "ow:insert-section") return;
|
|
6605
|
+
const { widgetType, insertAfter, insertBefore } = e.data;
|
|
6606
|
+
if (widgetType !== "scheduling") return;
|
|
6607
|
+
const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
|
|
6608
|
+
if (inserted) {
|
|
6609
|
+
const tracker = getSectionsTracker();
|
|
6610
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
|
|
6611
|
+
const h = document.documentElement.scrollHeight;
|
|
6612
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
6613
|
+
}
|
|
6614
|
+
};
|
|
6615
|
+
const handleSwitchSchedule = (e) => {
|
|
6616
|
+
if (e.data?.type !== "ow:switch-schedule") return;
|
|
6617
|
+
const scheduleId = e.data.scheduleId;
|
|
6618
|
+
const insertAfter = e.data.insertAfter;
|
|
6619
|
+
if (!scheduleId || !insertAfter) return;
|
|
6620
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
6621
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6622
|
+
};
|
|
6623
|
+
const handleScheduleLinked = (e) => {
|
|
6624
|
+
if (e.data?.type !== "ow:schedule-linked") return;
|
|
6625
|
+
const scheduleId = e.data.scheduleId;
|
|
6626
|
+
const insertAfter = e.data.insertAfter;
|
|
6627
|
+
if (!scheduleId || !insertAfter) return;
|
|
6628
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
6629
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6630
|
+
};
|
|
6631
|
+
const handleClearSchedulingWidget = (e) => {
|
|
6632
|
+
if (e.data?.type !== "ow:clear-scheduling-widget") return;
|
|
6633
|
+
const insertAfter = e.data.insertAfter;
|
|
6634
|
+
if (!insertAfter) return;
|
|
6635
|
+
const text = updateSectionScheduleId(insertAfter, null);
|
|
6636
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6637
|
+
};
|
|
6638
|
+
const handleRemoveSchedulingSection = (e) => {
|
|
6639
|
+
if (e.data?.type !== "ow:remove-scheduling-section") return;
|
|
6640
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
|
|
6641
|
+
el.remove();
|
|
6642
|
+
});
|
|
6643
|
+
const tracker = getSectionsTracker();
|
|
6644
|
+
let sections = [];
|
|
6645
|
+
try {
|
|
6646
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
6647
|
+
} catch {
|
|
6648
|
+
}
|
|
6649
|
+
const currentPath = window.location.pathname;
|
|
6650
|
+
const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
|
|
6651
|
+
tracker.textContent = JSON.stringify(updated);
|
|
6652
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
6653
|
+
const h = document.documentElement.scrollHeight;
|
|
6654
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
4599
6655
|
};
|
|
4600
6656
|
const handleSelectionChange = () => {
|
|
4601
6657
|
if (!activeElRef.current) return;
|
|
@@ -4700,6 +6756,11 @@ function OhhwellsBridge() {
|
|
|
4700
6756
|
deactivateRef.current();
|
|
4701
6757
|
};
|
|
4702
6758
|
window.addEventListener("message", handleSave);
|
|
6759
|
+
window.addEventListener("message", handleInsertSection);
|
|
6760
|
+
window.addEventListener("message", handleSwitchSchedule);
|
|
6761
|
+
window.addEventListener("message", handleScheduleLinked);
|
|
6762
|
+
window.addEventListener("message", handleClearSchedulingWidget);
|
|
6763
|
+
window.addEventListener("message", handleRemoveSchedulingSection);
|
|
4703
6764
|
window.addEventListener("message", handleImageUrl);
|
|
4704
6765
|
window.addEventListener("message", handleAnimLock);
|
|
4705
6766
|
window.addEventListener("message", handleCanvasHeight);
|
|
@@ -4734,6 +6795,11 @@ function OhhwellsBridge() {
|
|
|
4734
6795
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
4735
6796
|
window.removeEventListener("scroll", handleScroll, true);
|
|
4736
6797
|
window.removeEventListener("message", handleSave);
|
|
6798
|
+
window.removeEventListener("message", handleInsertSection);
|
|
6799
|
+
window.removeEventListener("message", handleSwitchSchedule);
|
|
6800
|
+
window.removeEventListener("message", handleScheduleLinked);
|
|
6801
|
+
window.removeEventListener("message", handleClearSchedulingWidget);
|
|
6802
|
+
window.removeEventListener("message", handleRemoveSchedulingSection);
|
|
4737
6803
|
window.removeEventListener("message", handleImageUrl);
|
|
4738
6804
|
window.removeEventListener("message", handleAnimLock);
|
|
4739
6805
|
window.removeEventListener("message", handleCanvasHeight);
|
|
@@ -4748,7 +6814,7 @@ function OhhwellsBridge() {
|
|
|
4748
6814
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
4749
6815
|
};
|
|
4750
6816
|
}, [isEditMode, refreshStateRules]);
|
|
4751
|
-
|
|
6817
|
+
useEffect4(() => {
|
|
4752
6818
|
if (!isEditMode) return;
|
|
4753
6819
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
4754
6820
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -4763,20 +6829,26 @@ function OhhwellsBridge() {
|
|
|
4763
6829
|
const raf = requestAnimationFrame(() => refreshStateRules());
|
|
4764
6830
|
const timer = setTimeout(() => {
|
|
4765
6831
|
refreshStateRules();
|
|
4766
|
-
|
|
6832
|
+
const sections = collectSections();
|
|
6833
|
+
setSectionsByPath((prev) => {
|
|
6834
|
+
const next = { ...prev, [pathname]: sections };
|
|
6835
|
+
return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
|
|
6836
|
+
});
|
|
6837
|
+
postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
|
|
6838
|
+
postToParent({ type: "ow:request-site-pages" });
|
|
4767
6839
|
}, 150);
|
|
4768
6840
|
return () => {
|
|
4769
6841
|
cancelAnimationFrame(raf);
|
|
4770
6842
|
clearTimeout(timer);
|
|
4771
6843
|
};
|
|
4772
6844
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
4773
|
-
const handleCommand =
|
|
6845
|
+
const handleCommand = useCallback2((cmd) => {
|
|
4774
6846
|
document.execCommand(cmd, false);
|
|
4775
6847
|
activeElRef.current?.focus();
|
|
4776
6848
|
if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
|
|
4777
6849
|
refreshActiveCommandsRef.current();
|
|
4778
6850
|
}, []);
|
|
4779
|
-
const handleStateChange =
|
|
6851
|
+
const handleStateChange = useCallback2((state) => {
|
|
4780
6852
|
if (!activeStateElRef.current) return;
|
|
4781
6853
|
const el = activeStateElRef.current;
|
|
4782
6854
|
if (state === "Default") {
|
|
@@ -4789,13 +6861,48 @@ function OhhwellsBridge() {
|
|
|
4789
6861
|
}
|
|
4790
6862
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
4791
6863
|
}, [deactivate]);
|
|
6864
|
+
const closeLinkPopover = useCallback2(() => setLinkPopover(null), []);
|
|
6865
|
+
const openLinkPopoverForActive = useCallback2(() => {
|
|
6866
|
+
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6867
|
+
if (!hrefCtx) return;
|
|
6868
|
+
bumpLinkPopoverGrace();
|
|
6869
|
+
setLinkPopover({
|
|
6870
|
+
key: hrefCtx.key,
|
|
6871
|
+
target: getLinkHref(hrefCtx.anchor),
|
|
6872
|
+
rect: hrefCtx.anchor.getBoundingClientRect()
|
|
6873
|
+
});
|
|
6874
|
+
}, []);
|
|
6875
|
+
const handleLinkPopoverSubmit = useCallback2(
|
|
6876
|
+
(target) => {
|
|
6877
|
+
if (!linkPopover) return;
|
|
6878
|
+
const { key } = linkPopover;
|
|
6879
|
+
applyLinkByKey(key, target);
|
|
6880
|
+
postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
|
|
6881
|
+
setLinkPopover(null);
|
|
6882
|
+
},
|
|
6883
|
+
[linkPopover, postToParent]
|
|
6884
|
+
);
|
|
6885
|
+
const showEditLink = toolbarShowEditLink;
|
|
6886
|
+
const currentSections = sectionsByPath[pathname] ?? [];
|
|
6887
|
+
linkPopoverOpenRef.current = linkPopover !== null;
|
|
4792
6888
|
return bridgeRoot ? createPortal(
|
|
4793
|
-
/* @__PURE__ */
|
|
4794
|
-
toolbarRect && /* @__PURE__ */
|
|
4795
|
-
/* @__PURE__ */
|
|
4796
|
-
/* @__PURE__ */
|
|
6889
|
+
/* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
6890
|
+
toolbarRect && /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
6891
|
+
/* @__PURE__ */ jsx16(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
6892
|
+
/* @__PURE__ */ jsx16(
|
|
6893
|
+
FloatingToolbar,
|
|
6894
|
+
{
|
|
6895
|
+
rect: toolbarRect,
|
|
6896
|
+
parentScroll: parentScrollRef.current,
|
|
6897
|
+
elRef: toolbarElRef,
|
|
6898
|
+
onCommand: handleCommand,
|
|
6899
|
+
activeCommands,
|
|
6900
|
+
showEditLink,
|
|
6901
|
+
onEditLink: openLinkPopoverForActive
|
|
6902
|
+
}
|
|
6903
|
+
)
|
|
4797
6904
|
] }),
|
|
4798
|
-
maxBadge && /* @__PURE__ */
|
|
6905
|
+
maxBadge && /* @__PURE__ */ jsxs8(
|
|
4799
6906
|
"div",
|
|
4800
6907
|
{
|
|
4801
6908
|
"data-ohw-max-badge": "",
|
|
@@ -4821,7 +6928,7 @@ function OhhwellsBridge() {
|
|
|
4821
6928
|
]
|
|
4822
6929
|
}
|
|
4823
6930
|
),
|
|
4824
|
-
toggleState && /* @__PURE__ */
|
|
6931
|
+
toggleState && /* @__PURE__ */ jsx16(
|
|
4825
6932
|
StateToggle,
|
|
4826
6933
|
{
|
|
4827
6934
|
rect: toggleState.rect,
|
|
@@ -4829,16 +6936,66 @@ function OhhwellsBridge() {
|
|
|
4829
6936
|
states: toggleState.states,
|
|
4830
6937
|
onStateChange: handleStateChange
|
|
4831
6938
|
}
|
|
6939
|
+
),
|
|
6940
|
+
linkPopover ? /* @__PURE__ */ jsx16(
|
|
6941
|
+
LinkPopover,
|
|
6942
|
+
{
|
|
6943
|
+
rect: linkPopover.rect,
|
|
6944
|
+
parentScroll: parentScrollRef.current,
|
|
6945
|
+
panelRef: linkPopoverPanelRef,
|
|
6946
|
+
open: true,
|
|
6947
|
+
mode: "edit",
|
|
6948
|
+
pages: sitePages,
|
|
6949
|
+
sections: currentSections,
|
|
6950
|
+
sectionsByPath,
|
|
6951
|
+
initialTarget: linkPopover.target,
|
|
6952
|
+
onClose: closeLinkPopover,
|
|
6953
|
+
onSubmit: handleLinkPopoverSubmit
|
|
6954
|
+
}
|
|
6955
|
+
) : null,
|
|
6956
|
+
sectionGap && /* @__PURE__ */ jsxs8(
|
|
6957
|
+
"div",
|
|
6958
|
+
{
|
|
6959
|
+
"data-ohw-section-insert-line": "",
|
|
6960
|
+
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
6961
|
+
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
6962
|
+
children: [
|
|
6963
|
+
/* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
6964
|
+
/* @__PURE__ */ jsx16(
|
|
6965
|
+
Badge,
|
|
6966
|
+
{
|
|
6967
|
+
className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
|
|
6968
|
+
onClick: () => {
|
|
6969
|
+
window.parent.postMessage(
|
|
6970
|
+
{ type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
|
|
6971
|
+
"*"
|
|
6972
|
+
);
|
|
6973
|
+
},
|
|
6974
|
+
children: "Add Section"
|
|
6975
|
+
}
|
|
6976
|
+
),
|
|
6977
|
+
/* @__PURE__ */ jsx16("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
6978
|
+
]
|
|
6979
|
+
}
|
|
4832
6980
|
)
|
|
4833
6981
|
] }),
|
|
4834
6982
|
bridgeRoot
|
|
4835
6983
|
) : null;
|
|
4836
6984
|
}
|
|
4837
6985
|
export {
|
|
6986
|
+
LinkEditorPanel,
|
|
6987
|
+
LinkPopover,
|
|
4838
6988
|
OhhwellsBridge,
|
|
6989
|
+
SchedulingWidget,
|
|
4839
6990
|
Toggle,
|
|
4840
6991
|
ToggleGroup,
|
|
4841
6992
|
ToggleGroupItem,
|
|
4842
|
-
|
|
6993
|
+
buildTarget,
|
|
6994
|
+
filterAvailablePages,
|
|
6995
|
+
getEditModeInitialState,
|
|
6996
|
+
isValidUrl,
|
|
6997
|
+
parseTarget,
|
|
6998
|
+
toggleVariants,
|
|
6999
|
+
validateUrlInput
|
|
4843
7000
|
};
|
|
4844
7001
|
//# sourceMappingURL=index.js.map
|