@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.cjs
CHANGED
|
@@ -31,19 +31,770 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/index.ts
|
|
32
32
|
var index_exports = {};
|
|
33
33
|
__export(index_exports, {
|
|
34
|
+
LinkEditorPanel: () => LinkEditorPanel,
|
|
35
|
+
LinkPopover: () => LinkPopover,
|
|
34
36
|
OhhwellsBridge: () => OhhwellsBridge,
|
|
37
|
+
SchedulingWidget: () => SchedulingWidget,
|
|
35
38
|
Toggle: () => Toggle,
|
|
36
39
|
ToggleGroup: () => ToggleGroup,
|
|
37
40
|
ToggleGroupItem: () => ToggleGroupItem,
|
|
38
|
-
|
|
41
|
+
buildTarget: () => buildTarget,
|
|
42
|
+
filterAvailablePages: () => filterAvailablePages,
|
|
43
|
+
getEditModeInitialState: () => getEditModeInitialState,
|
|
44
|
+
isValidUrl: () => isValidUrl,
|
|
45
|
+
parseTarget: () => parseTarget,
|
|
46
|
+
toggleVariants: () => toggleVariants,
|
|
47
|
+
validateUrlInput: () => validateUrlInput
|
|
39
48
|
});
|
|
40
49
|
module.exports = __toCommonJS(index_exports);
|
|
41
50
|
|
|
42
51
|
// src/OhhwellsBridge.tsx
|
|
43
|
-
var
|
|
52
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
53
|
+
var import_client = require("react-dom/client");
|
|
54
|
+
var import_react_dom = require("react-dom");
|
|
55
|
+
|
|
56
|
+
// src/ui/SchedulingWidget.tsx
|
|
57
|
+
var import_react2 = require("react");
|
|
58
|
+
|
|
59
|
+
// src/ui/EmailCaptureModal.tsx
|
|
60
|
+
var import_react = require("react");
|
|
61
|
+
var import_radix_ui = require("radix-ui");
|
|
62
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
63
|
+
function Spinner() {
|
|
64
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
65
|
+
"svg",
|
|
66
|
+
{
|
|
67
|
+
width: "32",
|
|
68
|
+
height: "32",
|
|
69
|
+
viewBox: "0 0 32 32",
|
|
70
|
+
fill: "none",
|
|
71
|
+
style: { animation: "ohw-spin 0.75s linear infinite" },
|
|
72
|
+
children: [
|
|
73
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `@keyframes ohw-spin { to { transform: rotate(360deg); } }` }),
|
|
74
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "16", cy: "16", r: "12", stroke: "#e5e7eb", strokeWidth: "3" }),
|
|
75
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
76
|
+
"path",
|
|
77
|
+
{
|
|
78
|
+
d: "M16 4a12 12 0 0 1 12 12",
|
|
79
|
+
stroke: "#3b82f6",
|
|
80
|
+
strokeWidth: "3",
|
|
81
|
+
strokeLinecap: "round"
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
89
|
+
const [email, setEmail] = (0, import_react.useState)("");
|
|
90
|
+
const [submittedEmail, setSubmittedEmail] = (0, import_react.useState)("");
|
|
91
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
92
|
+
const [success, setSuccess] = (0, import_react.useState)(false);
|
|
93
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
94
|
+
const isBook = title === "Confirm your spot";
|
|
95
|
+
const handleSubmit = async () => {
|
|
96
|
+
if (!email.trim()) return;
|
|
97
|
+
setLoading(true);
|
|
98
|
+
setError(null);
|
|
99
|
+
try {
|
|
100
|
+
await onSubmit(email.trim());
|
|
101
|
+
setSubmittedEmail(email.trim());
|
|
102
|
+
setSuccess(true);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
105
|
+
} finally {
|
|
106
|
+
setLoading(false);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const handleKeyDown = (e) => {
|
|
110
|
+
if (e.key === "Enter" && !loading) handleSubmit();
|
|
111
|
+
};
|
|
112
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_radix_ui.Dialog.Root, { open: true, onOpenChange: (open) => {
|
|
113
|
+
if (!open) onClose();
|
|
114
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_radix_ui.Dialog.Portal, { children: [
|
|
115
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
116
|
+
import_radix_ui.Dialog.Overlay,
|
|
117
|
+
{
|
|
118
|
+
className: "fixed inset-0 z-50",
|
|
119
|
+
style: { background: "rgba(0,0,0,0.45)" }
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
123
|
+
import_radix_ui.Dialog.Content,
|
|
124
|
+
{
|
|
125
|
+
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",
|
|
126
|
+
style: { maxWidth: 400 },
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3 p-6", children: [
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
130
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
131
|
+
import_radix_ui.Dialog.Title,
|
|
132
|
+
{
|
|
133
|
+
className: "font-body text-xl font-bold m-0 leading-tight",
|
|
134
|
+
style: { color: "var(--color-dark,#200C02)" },
|
|
135
|
+
children: success ? isBook ? "Reservation confirmed!" : "You're on the waitlist!" : title
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
139
|
+
import_radix_ui.Dialog.Description,
|
|
140
|
+
{
|
|
141
|
+
className: "font-body text-sm m-0",
|
|
142
|
+
style: { color: "#6B7280" },
|
|
143
|
+
children: success ? isBook ? `A confirmation email has been sent to ${submittedEmail}.` : "We'll let you know via email if someone cancels." : subtitle
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
] }),
|
|
147
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
148
|
+
import_radix_ui.Dialog.Close,
|
|
149
|
+
{
|
|
150
|
+
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",
|
|
151
|
+
style: { color: "#6B7280" },
|
|
152
|
+
"aria-label": "Close",
|
|
153
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
156
|
+
] })
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
] }),
|
|
160
|
+
!success && (loading ? (
|
|
161
|
+
/* Loading state — full body replaced */
|
|
162
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col items-center justify-center gap-3 px-7 py-12", children: [
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
164
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-sm m-0", style: { color: "#6B7280" }, children: "Please wait" })
|
|
165
|
+
] })
|
|
166
|
+
) : (
|
|
167
|
+
/* Email form */
|
|
168
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "px-7 pt-2 pb-6", children: [
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1.5 mb-8", children: [
|
|
170
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "font-body text-sm font-medium", style: { color: "var(--color-dark,#200C02)" }, children: "Email" }),
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
172
|
+
"input",
|
|
173
|
+
{
|
|
174
|
+
type: "email",
|
|
175
|
+
value: email,
|
|
176
|
+
onChange: (e) => setEmail(e.target.value),
|
|
177
|
+
onKeyDown: handleKeyDown,
|
|
178
|
+
placeholder: "hello@gmail.com",
|
|
179
|
+
autoFocus: true,
|
|
180
|
+
className: "w-full h-10 px-3 rounded-lg border font-body text-sm outline-none box-border",
|
|
181
|
+
style: {
|
|
182
|
+
borderColor: "#E7E5E4",
|
|
183
|
+
background: "#fff",
|
|
184
|
+
color: "var(--color-dark,#200C02)"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
),
|
|
188
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "font-body text-xs text-red-500 m-0", children: error })
|
|
189
|
+
] }),
|
|
190
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
191
|
+
"button",
|
|
192
|
+
{
|
|
193
|
+
onClick: handleSubmit,
|
|
194
|
+
disabled: !email.trim(),
|
|
195
|
+
className: "h-9 px-5 rounded-lg font-body text-sm font-semibold cursor-pointer border-none disabled:opacity-50 disabled:cursor-not-allowed",
|
|
196
|
+
style: {
|
|
197
|
+
background: "var(--color-primary,#3D312B)",
|
|
198
|
+
color: "var(--color-light,#FEFFF0)"
|
|
199
|
+
},
|
|
200
|
+
children: "Send"
|
|
201
|
+
}
|
|
202
|
+
) })
|
|
203
|
+
] })
|
|
204
|
+
))
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
] }) });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/ui/SchedulingWidget.tsx
|
|
212
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
213
|
+
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
214
|
+
var DAY_ABBR = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
215
|
+
function getApiDomain() {
|
|
216
|
+
const h = window.location.hostname;
|
|
217
|
+
for (const base of ["ohhwells.site", "theflowops-staging.com", "theflowops.com"]) {
|
|
218
|
+
if (h.endsWith(`.${base}`)) return h.slice(0, -(base.length + 1));
|
|
219
|
+
}
|
|
220
|
+
return h;
|
|
221
|
+
}
|
|
222
|
+
function classRunsOnDate(cls, date, type) {
|
|
223
|
+
if (type === "FIXED") {
|
|
224
|
+
const d = String(date.getDate()).padStart(2, "0");
|
|
225
|
+
const m = String(date.getMonth() + 1).padStart(2, "0");
|
|
226
|
+
return cls.fixedDates?.some((fd) => fd.date === `${d}-${m}-${date.getFullYear()}`) ?? false;
|
|
227
|
+
}
|
|
228
|
+
return cls.weekdays?.some((w) => w.weekday === date.getDay()) ?? false;
|
|
229
|
+
}
|
|
230
|
+
function formatClassTime(cls) {
|
|
231
|
+
let h = parseInt(cls.startTime.hour, 10);
|
|
232
|
+
const m = parseInt(cls.startTime.minutes, 10);
|
|
233
|
+
if (cls.startTime.time === "PM" && h !== 12) h += 12;
|
|
234
|
+
if (cls.startTime.time === "AM" && h === 12) h = 0;
|
|
235
|
+
const endTotal = h * 60 + m + cls.duration;
|
|
236
|
+
const eh = Math.floor(endTotal / 60) % 24;
|
|
237
|
+
const em = endTotal % 60;
|
|
238
|
+
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
239
|
+
}
|
|
240
|
+
function getBookingsOnDate(cls, date) {
|
|
241
|
+
if (!cls.bookings?.length) return 0;
|
|
242
|
+
const ds = date.toISOString().split("T")[0];
|
|
243
|
+
return cls.bookings.filter((b) => {
|
|
244
|
+
try {
|
|
245
|
+
return new Date(b.classDate).toISOString().split("T")[0] === ds;
|
|
246
|
+
} catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}).length;
|
|
250
|
+
}
|
|
251
|
+
function buildTimezoneLabel(tz) {
|
|
252
|
+
try {
|
|
253
|
+
const now = /* @__PURE__ */ new Date();
|
|
254
|
+
const timeStr = new Intl.DateTimeFormat("en-US", {
|
|
255
|
+
hour: "2-digit",
|
|
256
|
+
minute: "2-digit",
|
|
257
|
+
hour12: false,
|
|
258
|
+
timeZone: tz
|
|
259
|
+
}).format(now);
|
|
260
|
+
const offsetPart = new Intl.DateTimeFormat("en-US", { timeZoneName: "short", timeZone: tz }).formatToParts(now).find((p) => p.type === "timeZoneName")?.value ?? "";
|
|
261
|
+
const city = tz.split("/").pop()?.replace(/_/g, " ") ?? tz;
|
|
262
|
+
return `${timeStr} (${offsetPart}) ${city} time`;
|
|
263
|
+
} catch {
|
|
264
|
+
return tz;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function EmptyState({ inEditor }) {
|
|
268
|
+
const handleAddSchedule = () => {
|
|
269
|
+
if (inEditor) {
|
|
270
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center justify-center gap-6 p-12 bg-[#FAFAF9] border-2 border-dashed border-[#E7E5E4] rounded-[10px] h-[264px] w-full box-border", children: [
|
|
274
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-10 h-10 bg-[#F5F5F4] rounded-[10px] flex items-center justify-center shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
275
|
+
"svg",
|
|
276
|
+
{
|
|
277
|
+
width: "20",
|
|
278
|
+
height: "20",
|
|
279
|
+
viewBox: "0 0 24 24",
|
|
280
|
+
fill: "none",
|
|
281
|
+
stroke: "var(--color-accent, #A89B83)",
|
|
282
|
+
strokeWidth: "1.5",
|
|
283
|
+
strokeLinecap: "round",
|
|
284
|
+
strokeLinejoin: "round",
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "3", y: "4", width: "18", height: "18", rx: "2" }),
|
|
287
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "16", y1: "2", x2: "16", y2: "6" }),
|
|
288
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "8", y1: "2", x2: "8", y2: "6" }),
|
|
289
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "3", y1: "10", x2: "21", y2: "10" }),
|
|
290
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", y1: "14", x2: "12", y2: "18" }),
|
|
291
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "16", x2: "14", y2: "16" })
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
) }),
|
|
295
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
|
|
296
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-lg font-medium text-center text-[#0A0A0A] m-0", children: "No schedule yet" }),
|
|
297
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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." })
|
|
298
|
+
] }),
|
|
299
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
300
|
+
"button",
|
|
301
|
+
{
|
|
302
|
+
onClick: handleAddSchedule,
|
|
303
|
+
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",
|
|
304
|
+
children: "Add Schedule"
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
] });
|
|
308
|
+
}
|
|
309
|
+
function LoadingSkeleton() {
|
|
310
|
+
const shimmer = {
|
|
311
|
+
background: "linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%)",
|
|
312
|
+
backgroundSize: "200% 100%",
|
|
313
|
+
animation: "ohw-shimmer 1.5s infinite"
|
|
314
|
+
};
|
|
315
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes ohw-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}` }),
|
|
317
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex h-[70px] items-stretch", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col justify-between items-center px-1", children: [
|
|
318
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "32px", height: "14px", borderRadius: "4px" } }),
|
|
319
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "40px", height: "40px", borderRadius: "50%" } })
|
|
320
|
+
] }, i)) }),
|
|
321
|
+
[0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
322
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:hidden", children: [
|
|
323
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0", children: [
|
|
324
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100px", height: "16px", borderRadius: "4px" } }),
|
|
325
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "52px", height: "20px", borderRadius: "999px" } }),
|
|
326
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "60px", height: "14px", borderRadius: "4px" } })
|
|
327
|
+
] }),
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2", children: [
|
|
329
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "75%", height: "16px", borderRadius: "4px" } }),
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "50%", height: "14px", borderRadius: "4px" } }),
|
|
331
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "100%", height: "44px", borderRadius: "8px" } })
|
|
332
|
+
] })
|
|
333
|
+
] }),
|
|
334
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hidden sm:flex items-center gap-[60px] py-4", children: [
|
|
335
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "120px", height: "16px", borderRadius: "4px", flexShrink: 0 } }),
|
|
336
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2", children: [
|
|
337
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "55%", height: "16px", borderRadius: "4px" } }),
|
|
338
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "38%", height: "14px", borderRadius: "4px" } })
|
|
339
|
+
] }),
|
|
340
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "56px", height: "32px", borderRadius: "4px", flexShrink: 0 } }),
|
|
341
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { ...shimmer, width: "200px", height: "44px", borderRadius: "8px", flexShrink: 0 } })
|
|
342
|
+
] }),
|
|
343
|
+
i < 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
|
|
344
|
+
] }, i))
|
|
345
|
+
] });
|
|
346
|
+
}
|
|
347
|
+
function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal }) {
|
|
348
|
+
const todayMs = (0, import_react2.useMemo)(() => {
|
|
349
|
+
const d = /* @__PURE__ */ new Date();
|
|
350
|
+
d.setHours(0, 0, 0, 0);
|
|
351
|
+
return d.getTime();
|
|
352
|
+
}, []);
|
|
353
|
+
const scrollRef = (0, import_react2.useRef)(null);
|
|
354
|
+
const [canScrollLeft, setCanScrollLeft] = (0, import_react2.useState)(false);
|
|
355
|
+
const [canScrollRight, setCanScrollRight] = (0, import_react2.useState)(false);
|
|
356
|
+
const updateScrollState = () => {
|
|
357
|
+
const el = scrollRef.current;
|
|
358
|
+
if (!el) return;
|
|
359
|
+
setCanScrollLeft(el.scrollLeft > 0);
|
|
360
|
+
setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
|
|
361
|
+
};
|
|
362
|
+
(0, import_react2.useEffect)(() => {
|
|
363
|
+
const el = scrollRef.current;
|
|
364
|
+
if (!el) return;
|
|
365
|
+
updateScrollState();
|
|
366
|
+
el.addEventListener("scroll", updateScrollState);
|
|
367
|
+
const ro = new ResizeObserver(updateScrollState);
|
|
368
|
+
ro.observe(el);
|
|
369
|
+
return () => {
|
|
370
|
+
el.removeEventListener("scroll", updateScrollState);
|
|
371
|
+
ro.disconnect();
|
|
372
|
+
};
|
|
373
|
+
}, [dates]);
|
|
374
|
+
const scroll = (dir) => {
|
|
375
|
+
scrollRef.current?.scrollBy({ left: dir === "left" ? -216 : 216, behavior: "smooth" });
|
|
376
|
+
};
|
|
377
|
+
const isDragging = (0, import_react2.useRef)(false);
|
|
378
|
+
const dragStartX = (0, import_react2.useRef)(0);
|
|
379
|
+
const dragStartScrollLeft = (0, import_react2.useRef)(0);
|
|
380
|
+
const onDragStart = (e) => {
|
|
381
|
+
if (!scrollRef.current) return;
|
|
382
|
+
isDragging.current = true;
|
|
383
|
+
dragStartX.current = e.clientX;
|
|
384
|
+
dragStartScrollLeft.current = scrollRef.current.scrollLeft;
|
|
385
|
+
scrollRef.current.style.cursor = "grabbing";
|
|
386
|
+
scrollRef.current.style.userSelect = "none";
|
|
387
|
+
};
|
|
388
|
+
const onDragMove = (e) => {
|
|
389
|
+
if (!isDragging.current || !scrollRef.current) return;
|
|
390
|
+
const delta = e.clientX - dragStartX.current;
|
|
391
|
+
scrollRef.current.scrollLeft = dragStartScrollLeft.current - delta;
|
|
392
|
+
};
|
|
393
|
+
const onDragEnd = () => {
|
|
394
|
+
if (!scrollRef.current) return;
|
|
395
|
+
isDragging.current = false;
|
|
396
|
+
scrollRef.current.style.cursor = "grab";
|
|
397
|
+
scrollRef.current.style.userSelect = "";
|
|
398
|
+
};
|
|
399
|
+
const datesWithClasses = (0, import_react2.useMemo)(
|
|
400
|
+
() => new Set(dates.map(
|
|
401
|
+
(d, i) => schedule.classes?.some((c) => classRunsOnDate(c, d, schedule.type)) ? i : -1
|
|
402
|
+
).filter((i) => i >= 0)),
|
|
403
|
+
[dates, schedule]
|
|
404
|
+
);
|
|
405
|
+
const selectedDate = dates[selectedIdx];
|
|
406
|
+
const selectedClasses = (0, import_react2.useMemo)(
|
|
407
|
+
() => (schedule.classes ?? []).filter((c) => selectedDate && classRunsOnDate(c, selectedDate, schedule.type)),
|
|
408
|
+
[schedule, selectedDate]
|
|
409
|
+
);
|
|
410
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col gap-10", children: [
|
|
411
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative w-full", children: [
|
|
412
|
+
canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
413
|
+
"button",
|
|
414
|
+
{
|
|
415
|
+
onClick: () => scroll("left"),
|
|
416
|
+
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",
|
|
417
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
418
|
+
"aria-label": "Scroll left",
|
|
419
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "15 18 9 12 15 6" }) })
|
|
420
|
+
}
|
|
421
|
+
),
|
|
422
|
+
canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
423
|
+
"button",
|
|
424
|
+
{
|
|
425
|
+
onClick: () => scroll("right"),
|
|
426
|
+
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",
|
|
427
|
+
style: { background: "var(--color-light,#FEFFF0)", boxShadow: "0 1px 4px rgba(0,0,0,0.15)" },
|
|
428
|
+
"aria-label": "Scroll right",
|
|
429
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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__ */ (0, import_jsx_runtime2.jsx)("polyline", { points: "9 18 15 12 9 6" }) })
|
|
430
|
+
}
|
|
431
|
+
),
|
|
432
|
+
canScrollLeft && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
433
|
+
"div",
|
|
434
|
+
{
|
|
435
|
+
className: "sm:hidden absolute left-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
436
|
+
style: { background: "linear-gradient(to right, var(--color-light,#FEFFF0), transparent)" }
|
|
437
|
+
}
|
|
438
|
+
),
|
|
439
|
+
canScrollRight && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
440
|
+
"div",
|
|
441
|
+
{
|
|
442
|
+
className: "sm:hidden absolute right-0 top-0 bottom-0 w-10 z-10 pointer-events-none",
|
|
443
|
+
style: { background: "linear-gradient(to left, var(--color-light,#FEFFF0), transparent)" }
|
|
444
|
+
}
|
|
445
|
+
),
|
|
446
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
447
|
+
"div",
|
|
448
|
+
{
|
|
449
|
+
ref: scrollRef,
|
|
450
|
+
className: "overflow-x-auto w-full",
|
|
451
|
+
style: { scrollbarWidth: "none", msOverflowStyle: "none", touchAction: "pan-x", cursor: "grab" },
|
|
452
|
+
onMouseDown: onDragStart,
|
|
453
|
+
onMouseMove: onDragMove,
|
|
454
|
+
onMouseUp: onDragEnd,
|
|
455
|
+
onMouseLeave: onDragEnd,
|
|
456
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex", style: { width: `max(100%, ${dates.length * 60}px)` }, children: dates.map((date, i) => {
|
|
457
|
+
const isToday = date.getTime() === todayMs;
|
|
458
|
+
const isSelected = i === selectedIdx;
|
|
459
|
+
const clickable = datesWithClasses.has(i);
|
|
460
|
+
const label = isToday ? "TODAY" : DAY_ABBR[date.getDay()];
|
|
461
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
462
|
+
"div",
|
|
463
|
+
{
|
|
464
|
+
onClick: () => clickable && onSelectDate(i),
|
|
465
|
+
className: `flex-1 h-[70px] flex flex-col justify-between items-center ${clickable ? "cursor-pointer opacity-100" : "cursor-default opacity-50"}`,
|
|
466
|
+
children: [
|
|
467
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-center text-(--color-dark,#200C02)", children: label }),
|
|
468
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
469
|
+
"div",
|
|
470
|
+
{
|
|
471
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center",
|
|
472
|
+
style: { background: isSelected ? "var(--color-primary, #3D312B)" : "transparent" },
|
|
473
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
474
|
+
"span",
|
|
475
|
+
{
|
|
476
|
+
className: "font-body text-xl font-bold text-center tracking-display-tight",
|
|
477
|
+
style: { color: isSelected ? "var(--color-light, #FEFFF0)" : "var(--color-dark, #200C02)" },
|
|
478
|
+
children: date.getDate()
|
|
479
|
+
}
|
|
480
|
+
)
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
]
|
|
484
|
+
},
|
|
485
|
+
i
|
|
486
|
+
);
|
|
487
|
+
}) })
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
] }),
|
|
491
|
+
selectedClasses.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-base text-(--color-accent,#A89B83) text-center py-8 m-0", children: "No classes scheduled for this day." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-col", children: selectedClasses.map((cls, i) => {
|
|
492
|
+
const booked = getBookingsOnDate(cls, selectedDate);
|
|
493
|
+
const available = cls.maxParticipants - booked;
|
|
494
|
+
const isFull = available <= 0;
|
|
495
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
496
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:w-[120px] sm:shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block", children: formatClassTime(cls) }) }),
|
|
499
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
500
|
+
"span",
|
|
501
|
+
{
|
|
502
|
+
className: "inline-flex items-center px-2 py-0.5 rounded-full border text-xs font-medium sm:hidden",
|
|
503
|
+
style: { borderColor: "#0885FE", color: "#0885FE" },
|
|
504
|
+
children: "GROUP"
|
|
505
|
+
}
|
|
506
|
+
),
|
|
507
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "sm:hidden flex flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
509
|
+
available,
|
|
510
|
+
"/",
|
|
511
|
+
cls.maxParticipants
|
|
512
|
+
] }),
|
|
513
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
514
|
+
] }) })
|
|
515
|
+
] }),
|
|
516
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
|
|
517
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
|
|
518
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
519
|
+
cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
|
|
520
|
+
] }),
|
|
521
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
522
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
523
|
+
available,
|
|
524
|
+
"/",
|
|
525
|
+
cls.maxParticipants
|
|
526
|
+
] }),
|
|
527
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "available" })
|
|
528
|
+
] }) }),
|
|
529
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
530
|
+
"button",
|
|
531
|
+
{
|
|
532
|
+
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",
|
|
533
|
+
style: isFull ? {
|
|
534
|
+
background: "transparent",
|
|
535
|
+
border: "1px solid var(--color-dark, #200C02)",
|
|
536
|
+
color: "var(--color-dark, #200C02)"
|
|
537
|
+
} : {
|
|
538
|
+
background: "var(--color-primary, #3D312B)",
|
|
539
|
+
border: "none",
|
|
540
|
+
color: "var(--color-light, #FEFFF0)"
|
|
541
|
+
},
|
|
542
|
+
onClick: () => {
|
|
543
|
+
if (!cls.id) return;
|
|
544
|
+
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
545
|
+
},
|
|
546
|
+
children: isFull ? "Join Waitlist" : "Book Now"
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
] })
|
|
550
|
+
] }),
|
|
551
|
+
i < selectedClasses.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "h-px bg-black/20" })
|
|
552
|
+
] }, cls.id ?? i);
|
|
553
|
+
}) })
|
|
554
|
+
] });
|
|
555
|
+
}
|
|
556
|
+
function CalendarFoldIcon() {
|
|
557
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
558
|
+
"svg",
|
|
559
|
+
{
|
|
560
|
+
width: "16",
|
|
561
|
+
height: "16",
|
|
562
|
+
viewBox: "0 0 24 24",
|
|
563
|
+
fill: "none",
|
|
564
|
+
stroke: "currentColor",
|
|
565
|
+
strokeWidth: "2",
|
|
566
|
+
strokeLinecap: "round",
|
|
567
|
+
strokeLinejoin: "round",
|
|
568
|
+
style: { flexShrink: 0 },
|
|
569
|
+
children: [
|
|
570
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 2v4" }),
|
|
571
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 2v4" }),
|
|
572
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M21 17V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11Z" }),
|
|
573
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M3 10h18" }),
|
|
574
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15 22v-4a2 2 0 0 1 2-2h4" })
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
|
|
580
|
+
const [schedule, setSchedule] = (0, import_react2.useState)(null);
|
|
581
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
582
|
+
const [inEditor, setInEditor] = (0, import_react2.useState)(false);
|
|
583
|
+
const [isHovered, setIsHovered] = (0, import_react2.useState)(false);
|
|
584
|
+
const [modalState, setModalState] = (0, import_react2.useState)(null);
|
|
585
|
+
const switchScheduleIdRef = (0, import_react2.useRef)(null);
|
|
586
|
+
const dates = (0, import_react2.useMemo)(() => {
|
|
587
|
+
const today = /* @__PURE__ */ new Date();
|
|
588
|
+
today.setHours(0, 0, 0, 0);
|
|
589
|
+
return Array.from({ length: 14 }, (_, i) => {
|
|
590
|
+
const d = new Date(today);
|
|
591
|
+
d.setDate(today.getDate() + i);
|
|
592
|
+
return d;
|
|
593
|
+
});
|
|
594
|
+
}, []);
|
|
595
|
+
const [selectedIdx, setSelectedIdx] = (0, import_react2.useState)(0);
|
|
596
|
+
(0, import_react2.useEffect)(() => {
|
|
597
|
+
if (!schedule?.classes) return;
|
|
598
|
+
for (let i = 0; i < dates.length; i++) {
|
|
599
|
+
if (schedule.classes.some((c) => classRunsOnDate(c, dates[i], schedule.type))) {
|
|
600
|
+
setSelectedIdx(i);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}, [schedule, dates]);
|
|
605
|
+
(0, import_react2.useEffect)(() => {
|
|
606
|
+
if (typeof window === "undefined") return;
|
|
607
|
+
const isInEditor = window.self !== window.top;
|
|
608
|
+
setInEditor(isInEditor);
|
|
609
|
+
if (isInEditor) {
|
|
610
|
+
const startEmpty = initialScheduleId === null;
|
|
611
|
+
if (startEmpty) setLoading(false);
|
|
612
|
+
let initialized = startEmpty;
|
|
613
|
+
const timer = !startEmpty ? setTimeout(() => {
|
|
614
|
+
if (!initialized) {
|
|
615
|
+
initialized = true;
|
|
616
|
+
setLoading(false);
|
|
617
|
+
}
|
|
618
|
+
}, 5e3) : null;
|
|
619
|
+
const handler = (e) => {
|
|
620
|
+
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
621
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
622
|
+
setSchedule(null);
|
|
623
|
+
setLoading(false);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (e.data?.type === "ow:switch-schedule") {
|
|
627
|
+
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
628
|
+
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
629
|
+
initialized = false;
|
|
630
|
+
setLoading(true);
|
|
631
|
+
window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
635
|
+
if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
|
|
636
|
+
if (initialized && switchScheduleIdRef.current === null) return;
|
|
637
|
+
initialized = true;
|
|
638
|
+
if (timer) clearTimeout(timer);
|
|
639
|
+
const schedules = e.data.schedules ?? [];
|
|
640
|
+
const isSwitching = switchScheduleIdRef.current !== null;
|
|
641
|
+
const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : initialScheduleId ? schedules.find((s) => s.id === initialScheduleId) ?? schedules[0] ?? null : schedules[0] ?? null;
|
|
642
|
+
switchScheduleIdRef.current = null;
|
|
643
|
+
setSchedule(target);
|
|
644
|
+
setLoading(false);
|
|
645
|
+
if (notifyOnConnect && target && !isSwitching) {
|
|
646
|
+
window.parent.postMessage({
|
|
647
|
+
type: "ow:schedule-connected",
|
|
648
|
+
schedule: { id: target.id, name: target.name },
|
|
649
|
+
insertAfter
|
|
650
|
+
}, "*");
|
|
651
|
+
window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
window.addEventListener("message", handler);
|
|
655
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
656
|
+
return () => {
|
|
657
|
+
window.removeEventListener("message", handler);
|
|
658
|
+
if (timer) clearTimeout(timer);
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
if (initialScheduleId === null) {
|
|
662
|
+
setLoading(false);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
;
|
|
666
|
+
(async () => {
|
|
667
|
+
try {
|
|
668
|
+
if (initialScheduleId) {
|
|
669
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${initialScheduleId}`);
|
|
670
|
+
const data = await res.json();
|
|
671
|
+
setSchedule(data?.id ? data : null);
|
|
672
|
+
} else {
|
|
673
|
+
const domain = getApiDomain();
|
|
674
|
+
const sitemapRes = await fetch(`${API_URL}/api/schedule/sitemap/${domain}`);
|
|
675
|
+
const slugs = await sitemapRes.json();
|
|
676
|
+
if (!Array.isArray(slugs) || !slugs.length) {
|
|
677
|
+
setLoading(false);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const { slug } = [...slugs].sort(
|
|
681
|
+
(a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
682
|
+
)[0];
|
|
683
|
+
const res = await fetch(`${API_URL}/api/schedule/${domain}/${slug}`);
|
|
684
|
+
const data = await res.json();
|
|
685
|
+
setSchedule(data?.id ? data : null);
|
|
686
|
+
}
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
setLoading(false);
|
|
690
|
+
})();
|
|
691
|
+
}, []);
|
|
692
|
+
const timezoneLabel = schedule?.timezone ? (() => {
|
|
693
|
+
try {
|
|
694
|
+
return buildTimezoneLabel(schedule.timezone);
|
|
695
|
+
} catch {
|
|
696
|
+
return void 0;
|
|
697
|
+
}
|
|
698
|
+
})() : void 0;
|
|
699
|
+
const handleModalSubmit = async (email) => {
|
|
700
|
+
if (!modalState) return;
|
|
701
|
+
const { mode, classId, classDate } = modalState;
|
|
702
|
+
const endpoint = mode === "book" ? `${API_URL}/api/schedule/class/${classId}/booking` : `${API_URL}/api/schedule/class/${classId}/waitlist`;
|
|
703
|
+
const res = await fetch(endpoint, {
|
|
704
|
+
method: "POST",
|
|
705
|
+
headers: { "Content-Type": "application/json" },
|
|
706
|
+
body: JSON.stringify({ classDate: classDate.toISOString(), email })
|
|
707
|
+
});
|
|
708
|
+
if (!res.ok) {
|
|
709
|
+
const data = await res.json().catch(() => ({}));
|
|
710
|
+
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
const handleReplaceSchedule = () => {
|
|
714
|
+
setIsHovered(false);
|
|
715
|
+
if (schedule) {
|
|
716
|
+
window.parent.postMessage({
|
|
717
|
+
type: "ow:schedule-connected",
|
|
718
|
+
schedule: { id: schedule.id, name: schedule.name },
|
|
719
|
+
insertAfter
|
|
720
|
+
}, "*");
|
|
721
|
+
} else {
|
|
722
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
const sectionId = `scheduling-${insertAfter}`;
|
|
726
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
727
|
+
"section",
|
|
728
|
+
{
|
|
729
|
+
"data-ohw-section": sectionId,
|
|
730
|
+
"data-ohw-scheduling-anchor": insertAfter,
|
|
731
|
+
className: "w-full box-border py-10 px-5 sm:py-20 sm:px-16 font-body bg-(--color-light,#FEFFF0) relative",
|
|
732
|
+
onMouseEnter: () => inEditor && setIsHovered(true),
|
|
733
|
+
onMouseLeave: () => setIsHovered(false),
|
|
734
|
+
children: [
|
|
735
|
+
inEditor && isHovered && !!schedule && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
736
|
+
"div",
|
|
737
|
+
{
|
|
738
|
+
className: "absolute inset-0 z-10 pointer-events-auto cursor-pointer",
|
|
739
|
+
onClick: handleReplaceSchedule,
|
|
740
|
+
children: [
|
|
741
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { background: "#0885FE", opacity: 0.18 } }),
|
|
742
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0", style: { boxShadow: "inset 0 0 0 1.5px #0885FE" } }),
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
744
|
+
"div",
|
|
745
|
+
{
|
|
746
|
+
className: "bg-white border border-[#E7E5E4] rounded-md px-3 py-1.5 flex items-center gap-1.5",
|
|
747
|
+
style: {
|
|
748
|
+
fontSize: 14,
|
|
749
|
+
lineHeight: "24px",
|
|
750
|
+
fontFamily: "'Figtree', system-ui, sans-serif",
|
|
751
|
+
fontWeight: 500,
|
|
752
|
+
color: "#0C0A09"
|
|
753
|
+
},
|
|
754
|
+
children: [
|
|
755
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CalendarFoldIcon, {}),
|
|
756
|
+
"Replace schedule"
|
|
757
|
+
]
|
|
758
|
+
}
|
|
759
|
+
) })
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
),
|
|
763
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
764
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
|
|
765
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }),
|
|
766
|
+
schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
|
|
767
|
+
] }),
|
|
768
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
769
|
+
timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full p-0 sm:p-10 box-border", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LoadingSkeleton, {}) : !schedule ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyState, { inEditor }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
771
|
+
ScheduleView,
|
|
772
|
+
{
|
|
773
|
+
schedule,
|
|
774
|
+
dates,
|
|
775
|
+
selectedIdx,
|
|
776
|
+
onSelectDate: setSelectedIdx,
|
|
777
|
+
onOpenModal: inEditor ? void 0 : (mode, classId, classDate) => setModalState({ mode, classId, classDate })
|
|
778
|
+
}
|
|
779
|
+
) })
|
|
780
|
+
] })
|
|
781
|
+
] }),
|
|
782
|
+
modalState && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
783
|
+
EmailCaptureModal,
|
|
784
|
+
{
|
|
785
|
+
title: modalState.mode === "book" ? "Confirm your spot" : "Leave your email",
|
|
786
|
+
subtitle: modalState.mode === "book" ? "We'll send your booking confirmation here." : "We'll let you know when spots open up!",
|
|
787
|
+
onSubmit: handleModalSubmit,
|
|
788
|
+
onClose: () => setModalState(null)
|
|
789
|
+
}
|
|
790
|
+
)
|
|
791
|
+
]
|
|
792
|
+
}
|
|
793
|
+
);
|
|
794
|
+
}
|
|
44
795
|
|
|
45
796
|
// src/ui/toggle-group.tsx
|
|
46
|
-
var
|
|
797
|
+
var React2 = __toESM(require("react"), 1);
|
|
47
798
|
|
|
48
799
|
// node_modules/clsx/dist/clsx.mjs
|
|
49
800
|
function r(e) {
|
|
@@ -3362,8 +4113,8 @@ var cva = (base, config) => (props) => {
|
|
|
3362
4113
|
};
|
|
3363
4114
|
|
|
3364
4115
|
// src/ui/toggle.tsx
|
|
3365
|
-
var
|
|
3366
|
-
var
|
|
4116
|
+
var import_radix_ui2 = require("radix-ui");
|
|
4117
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
3367
4118
|
var toggleVariants = cva(
|
|
3368
4119
|
"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",
|
|
3369
4120
|
{
|
|
@@ -3390,8 +4141,8 @@ function Toggle({
|
|
|
3390
4141
|
size,
|
|
3391
4142
|
...props
|
|
3392
4143
|
}) {
|
|
3393
|
-
return /* @__PURE__ */ (0,
|
|
3394
|
-
|
|
4144
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
4145
|
+
import_radix_ui2.Toggle.Root,
|
|
3395
4146
|
{
|
|
3396
4147
|
"data-slot": "toggle",
|
|
3397
4148
|
className: cn(toggleVariants({ variant, size, className })),
|
|
@@ -3401,8 +4152,8 @@ function Toggle({
|
|
|
3401
4152
|
}
|
|
3402
4153
|
|
|
3403
4154
|
// src/ui/toggle-group.tsx
|
|
3404
|
-
var
|
|
3405
|
-
var ToggleGroupContext =
|
|
4155
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
4156
|
+
var ToggleGroupContext = React2.createContext({
|
|
3406
4157
|
value: "",
|
|
3407
4158
|
onValueChange: () => {
|
|
3408
4159
|
}
|
|
@@ -3414,13 +4165,13 @@ function ToggleGroup({
|
|
|
3414
4165
|
children,
|
|
3415
4166
|
...props
|
|
3416
4167
|
}) {
|
|
3417
|
-
return /* @__PURE__ */ (0,
|
|
4168
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3418
4169
|
"div",
|
|
3419
4170
|
{
|
|
3420
4171
|
role: "group",
|
|
3421
4172
|
className: cn("flex items-center gap-1 p-1 bg-muted rounded-md", className),
|
|
3422
4173
|
...props,
|
|
3423
|
-
children: /* @__PURE__ */ (0,
|
|
4174
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToggleGroupContext.Provider, { value: { value, onValueChange }, children })
|
|
3424
4175
|
}
|
|
3425
4176
|
);
|
|
3426
4177
|
}
|
|
@@ -3430,8 +4181,8 @@ function ToggleGroupItem({
|
|
|
3430
4181
|
children,
|
|
3431
4182
|
...props
|
|
3432
4183
|
}) {
|
|
3433
|
-
const { value: groupValue, onValueChange } =
|
|
3434
|
-
return /* @__PURE__ */ (0,
|
|
4184
|
+
const { value: groupValue, onValueChange } = React2.useContext(ToggleGroupContext);
|
|
4185
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
3435
4186
|
Toggle,
|
|
3436
4187
|
{
|
|
3437
4188
|
pressed: groupValue === value,
|
|
@@ -3451,7 +4202,7 @@ function ToggleGroupItem({
|
|
|
3451
4202
|
}
|
|
3452
4203
|
|
|
3453
4204
|
// src/OhhwellsBridge.tsx
|
|
3454
|
-
var
|
|
4205
|
+
var import_react_dom2 = require("react-dom");
|
|
3455
4206
|
var import_navigation = require("next/navigation");
|
|
3456
4207
|
|
|
3457
4208
|
// src/lib/session-search.ts
|
|
@@ -3472,8 +4223,866 @@ function isEditSessionActive() {
|
|
|
3472
4223
|
return new URLSearchParams(q).get("mode") === "edit";
|
|
3473
4224
|
}
|
|
3474
4225
|
|
|
4226
|
+
// src/ui/link-modal/calcPopoverPos.ts
|
|
4227
|
+
function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
|
|
4228
|
+
const GAP = 8;
|
|
4229
|
+
const canvasTopInIframe = parentScroll != null ? parentScroll.headerH - parentScroll.iframeOffsetTop : 0;
|
|
4230
|
+
const visibleTop = rect.top - canvasTopInIframe;
|
|
4231
|
+
let top = rect.top - GAP;
|
|
4232
|
+
let transform = "translateX(-50%) translateY(-100%)";
|
|
4233
|
+
if (visibleTop < approxH + GAP) {
|
|
4234
|
+
top = rect.bottom + GAP;
|
|
4235
|
+
transform = "translateX(-50%)";
|
|
4236
|
+
}
|
|
4237
|
+
const rawLeft = rect.left + rect.width / 2;
|
|
4238
|
+
const left = Math.max(GAP + approxW / 2, Math.min(rawLeft, window.innerWidth - GAP - approxW / 2));
|
|
4239
|
+
return { top, left, transform };
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
// src/ui/button.tsx
|
|
4243
|
+
var React3 = __toESM(require("react"), 1);
|
|
4244
|
+
var import_radix_ui3 = require("radix-ui");
|
|
4245
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
4246
|
+
var buttonVariants = cva(
|
|
4247
|
+
"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",
|
|
4248
|
+
{
|
|
4249
|
+
variants: {
|
|
4250
|
+
variant: {
|
|
4251
|
+
default: "bg-primary text-primary-foreground hover:opacity-90",
|
|
4252
|
+
outline: "border border-border bg-background text-foreground shadow-sm hover:bg-muted/80",
|
|
4253
|
+
ghost: "min-w-0 px-3 py-2 text-foreground hover:bg-muted/50"
|
|
4254
|
+
},
|
|
4255
|
+
size: {
|
|
4256
|
+
default: "h-9",
|
|
4257
|
+
sm: "h-8 min-w-[64px] px-2 py-1.5 text-sm"
|
|
4258
|
+
}
|
|
4259
|
+
},
|
|
4260
|
+
defaultVariants: {
|
|
4261
|
+
variant: "default",
|
|
4262
|
+
size: "default"
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
);
|
|
4266
|
+
var Button = React3.forwardRef(
|
|
4267
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
4268
|
+
const Comp = asChild ? import_radix_ui3.Slot.Root : "button";
|
|
4269
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
4270
|
+
Comp,
|
|
4271
|
+
{
|
|
4272
|
+
ref,
|
|
4273
|
+
"data-slot": "button",
|
|
4274
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
4275
|
+
...props
|
|
4276
|
+
}
|
|
4277
|
+
);
|
|
4278
|
+
}
|
|
4279
|
+
);
|
|
4280
|
+
Button.displayName = "Button";
|
|
4281
|
+
|
|
4282
|
+
// src/ui/icons.tsx
|
|
4283
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
4284
|
+
function IconX({ className, ...props }) {
|
|
4285
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4286
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M18 6 6 18" }),
|
|
4287
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 6 12 12" })
|
|
4288
|
+
] });
|
|
4289
|
+
}
|
|
4290
|
+
function IconChevronDown({ className, ...props }) {
|
|
4291
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m6 9 6 6 6-6" }) });
|
|
4292
|
+
}
|
|
4293
|
+
function IconFile({ className, ...props }) {
|
|
4294
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4295
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
|
|
4296
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
|
|
4297
|
+
] });
|
|
4298
|
+
}
|
|
4299
|
+
function IconInfo({ className, ...props }) {
|
|
4300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4301
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
4302
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 16v-4" }),
|
|
4303
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 8h.01" })
|
|
4304
|
+
] });
|
|
4305
|
+
}
|
|
4306
|
+
function IconArrowRight({ className, ...props }) {
|
|
4307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4308
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5 12h14" }),
|
|
4309
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m12 5 7 7-7 7" })
|
|
4310
|
+
] });
|
|
4311
|
+
}
|
|
4312
|
+
function IconSection({ className, ...props }) {
|
|
4313
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className, ...props, children: [
|
|
4314
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "18", height: "7", x: "3", y: "3", rx: "1" }),
|
|
4315
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "9", height: "7", x: "3", y: "14", rx: "1" }),
|
|
4316
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { width: "5", height: "7", x: "16", y: "14", rx: "1" })
|
|
4317
|
+
] });
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
// src/ui/link-modal/DestinationBreadcrumb.tsx
|
|
4321
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
4322
|
+
function DestinationBreadcrumb({ pageTitle, sectionLabel }) {
|
|
4323
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex w-full flex-col gap-2", children: [
|
|
4324
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-sm font-medium text-foreground", children: "Destination" }),
|
|
4325
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
4326
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
4327
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconFile, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4328
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-sm font-medium leading-none text-foreground", children: pageTitle })
|
|
4329
|
+
] }),
|
|
4330
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconArrowRight, { className: "shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
4331
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
|
|
4332
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4333
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "truncate text-sm font-medium leading-none text-foreground", children: sectionLabel })
|
|
4334
|
+
] })
|
|
4335
|
+
] })
|
|
4336
|
+
] });
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
// src/ui/link-modal/SectionTreeItem.tsx
|
|
4340
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
4341
|
+
function SectionTreeItem({ section, onSelect, selected }) {
|
|
4342
|
+
const interactive = Boolean(onSelect);
|
|
4343
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex h-9 w-full items-end pl-3", children: [
|
|
4344
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
4345
|
+
"div",
|
|
4346
|
+
{
|
|
4347
|
+
className: "mr-[-1px] h-9 w-2 shrink-0 rounded-bl-sm border-b border-l border-border mb-4",
|
|
4348
|
+
"aria-hidden": true
|
|
4349
|
+
}
|
|
4350
|
+
),
|
|
4351
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
4352
|
+
"div",
|
|
4353
|
+
{
|
|
4354
|
+
role: interactive ? "button" : void 0,
|
|
4355
|
+
tabIndex: interactive ? 0 : void 0,
|
|
4356
|
+
onClick: interactive ? () => onSelect?.(section) : void 0,
|
|
4357
|
+
onKeyDown: interactive ? (e) => {
|
|
4358
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
4359
|
+
e.preventDefault();
|
|
4360
|
+
onSelect?.(section);
|
|
4361
|
+
}
|
|
4362
|
+
} : void 0,
|
|
4363
|
+
className: cn(
|
|
4364
|
+
"flex h-9 min-w-0 flex-1 items-center gap-2 rounded-md border border-border bg-background p-3",
|
|
4365
|
+
interactive && "cursor-pointer hover:bg-muted/30",
|
|
4366
|
+
interactive && selected && "border-primary"
|
|
4367
|
+
),
|
|
4368
|
+
children: [
|
|
4369
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(IconSection, { className: "shrink-0 text-foreground", "aria-hidden": true }),
|
|
4370
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate text-sm font-normal leading-5 text-foreground", children: section.label })
|
|
4371
|
+
]
|
|
4372
|
+
}
|
|
4373
|
+
)
|
|
4374
|
+
] });
|
|
4375
|
+
}
|
|
4376
|
+
function SectionPickerList({ sections, onSelect }) {
|
|
4377
|
+
if (sections.length === 0) {
|
|
4378
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "No sections found on this page." });
|
|
4379
|
+
}
|
|
4380
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
4381
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-muted-foreground", children: "Pick a section this link should scroll to." }),
|
|
4382
|
+
sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SectionTreeItem, { section, onSelect }, section.id))
|
|
4383
|
+
] });
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
// src/ui/link-modal/UrlOrPageInput.tsx
|
|
4387
|
+
var import_react3 = require("react");
|
|
4388
|
+
|
|
4389
|
+
// src/ui/input.tsx
|
|
4390
|
+
var React4 = __toESM(require("react"), 1);
|
|
4391
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
4392
|
+
var Input = React4.forwardRef(
|
|
4393
|
+
({ className, type, ...props }, ref) => {
|
|
4394
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
4395
|
+
"input",
|
|
4396
|
+
{
|
|
4397
|
+
type,
|
|
4398
|
+
ref,
|
|
4399
|
+
"data-slot": "input",
|
|
4400
|
+
className: cn(
|
|
4401
|
+
"flex h-full w-full min-w-0 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground",
|
|
4402
|
+
className
|
|
4403
|
+
),
|
|
4404
|
+
...props
|
|
4405
|
+
}
|
|
4406
|
+
);
|
|
4407
|
+
}
|
|
4408
|
+
);
|
|
4409
|
+
Input.displayName = "Input";
|
|
4410
|
+
|
|
4411
|
+
// src/ui/label.tsx
|
|
4412
|
+
var import_radix_ui4 = require("radix-ui");
|
|
4413
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
4414
|
+
function Label({ className, ...props }) {
|
|
4415
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
4416
|
+
import_radix_ui4.Label.Root,
|
|
4417
|
+
{
|
|
4418
|
+
"data-slot": "label",
|
|
4419
|
+
className: cn("text-sm font-medium leading-5 text-foreground", className),
|
|
4420
|
+
...props
|
|
4421
|
+
}
|
|
4422
|
+
);
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
// src/ui/popover.tsx
|
|
4426
|
+
var import_radix_ui5 = require("radix-ui");
|
|
4427
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
4428
|
+
function Popover({ ...props }) {
|
|
4429
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Root, { "data-slot": "popover", ...props });
|
|
4430
|
+
}
|
|
4431
|
+
function PopoverTrigger({ ...props }) {
|
|
4432
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
4433
|
+
}
|
|
4434
|
+
function PopoverContent({
|
|
4435
|
+
className,
|
|
4436
|
+
align = "start",
|
|
4437
|
+
sideOffset = 6,
|
|
4438
|
+
...props
|
|
4439
|
+
}) {
|
|
4440
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_radix_ui5.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
4441
|
+
import_radix_ui5.Popover.Content,
|
|
4442
|
+
{
|
|
4443
|
+
"data-slot": "popover-content",
|
|
4444
|
+
align,
|
|
4445
|
+
sideOffset,
|
|
4446
|
+
className: cn(
|
|
4447
|
+
"z-[2147483647] w-[var(--radix-popover-trigger-width)] overflow-auto rounded-lg border border-border bg-popover py-1 shadow-lg outline-none",
|
|
4448
|
+
className
|
|
4449
|
+
),
|
|
4450
|
+
...props
|
|
4451
|
+
}
|
|
4452
|
+
) });
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4455
|
+
// src/ui/link-modal/UrlOrPageInput.tsx
|
|
4456
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
4457
|
+
function FieldChevron({ onClick }) {
|
|
4458
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
4459
|
+
"button",
|
|
4460
|
+
{
|
|
4461
|
+
type: "button",
|
|
4462
|
+
className: "flex shrink-0 items-center justify-end border-0 bg-transparent p-0 pl-4 text-muted-foreground outline-none",
|
|
4463
|
+
onClick,
|
|
4464
|
+
"aria-label": "Open page list",
|
|
4465
|
+
tabIndex: -1,
|
|
4466
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconChevronDown, {})
|
|
4467
|
+
}
|
|
4468
|
+
);
|
|
4469
|
+
}
|
|
4470
|
+
function UrlOrPageInput({
|
|
4471
|
+
value,
|
|
4472
|
+
selectedPage,
|
|
4473
|
+
placeholder = "Paste a URL or choose a page",
|
|
4474
|
+
dropdownOpen,
|
|
4475
|
+
onDropdownOpenChange,
|
|
4476
|
+
filteredPages,
|
|
4477
|
+
onInputChange,
|
|
4478
|
+
onPageSelect,
|
|
4479
|
+
readOnly = false,
|
|
4480
|
+
urlError
|
|
4481
|
+
}) {
|
|
4482
|
+
const inputId = (0, import_react3.useId)();
|
|
4483
|
+
const inputRef = (0, import_react3.useRef)(null);
|
|
4484
|
+
const [isEditing, setIsEditing] = (0, import_react3.useState)(false);
|
|
4485
|
+
const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
|
|
4486
|
+
const showFilledDisplay = Boolean(selectedPage) && !isEditing && !readOnly;
|
|
4487
|
+
(0, import_react3.useEffect)(() => {
|
|
4488
|
+
if (selectedPage) setIsEditing(false);
|
|
4489
|
+
}, [selectedPage]);
|
|
4490
|
+
const openDropdown = () => {
|
|
4491
|
+
if (readOnly || filteredPages.length === 0) return;
|
|
4492
|
+
onDropdownOpenChange(true);
|
|
4493
|
+
};
|
|
4494
|
+
const toggleDropdown = (e) => {
|
|
4495
|
+
e.stopPropagation();
|
|
4496
|
+
e.preventDefault();
|
|
4497
|
+
if (readOnly || filteredPages.length === 0) return;
|
|
4498
|
+
onDropdownOpenChange(!dropdownOpen);
|
|
4499
|
+
};
|
|
4500
|
+
const enterEditMode = () => {
|
|
4501
|
+
if (readOnly) return;
|
|
4502
|
+
setIsEditing(true);
|
|
4503
|
+
requestAnimationFrame(() => inputRef.current?.focus());
|
|
4504
|
+
};
|
|
4505
|
+
const fieldClassName = cn(
|
|
4506
|
+
"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]",
|
|
4507
|
+
urlError ? "border-destructive shadow-[0_0_0_1px_var(--ohw-destructive)]" : isFocused ? "border-primary shadow-[0_0_0_1px_var(--ohw-primary)]" : "border-input"
|
|
4508
|
+
);
|
|
4509
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex w-full flex-col gap-2 p-0", children: [
|
|
4510
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
|
|
4511
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
|
|
4512
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverTrigger, { asChild: true, children: showFilledDisplay ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
4513
|
+
"button",
|
|
4514
|
+
{
|
|
4515
|
+
type: "button",
|
|
4516
|
+
"data-ohw-link-field": true,
|
|
4517
|
+
className: cn(fieldClassName, "m-0 cursor-pointer border-solid text-left"),
|
|
4518
|
+
onClick: openDropdown,
|
|
4519
|
+
onFocus: () => setIsFocused(true),
|
|
4520
|
+
onBlur: () => setIsFocused(false),
|
|
4521
|
+
onKeyDown: (e) => {
|
|
4522
|
+
if (e.key === "Backspace" || e.key === "Delete" || e.key.length === 1) {
|
|
4523
|
+
enterEditMode();
|
|
4524
|
+
}
|
|
4525
|
+
},
|
|
4526
|
+
children: [
|
|
4527
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }),
|
|
4528
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm font-normal text-foreground", children: selectedPage.title }),
|
|
4529
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldChevron, { onClick: toggleDropdown })
|
|
4530
|
+
]
|
|
4531
|
+
}
|
|
4532
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
4533
|
+
"div",
|
|
4534
|
+
{
|
|
4535
|
+
"data-ohw-link-field": true,
|
|
4536
|
+
className: fieldClassName,
|
|
4537
|
+
onMouseDown: (e) => {
|
|
4538
|
+
if (readOnly) return;
|
|
4539
|
+
if (e.target === e.currentTarget) enterEditMode();
|
|
4540
|
+
},
|
|
4541
|
+
children: [
|
|
4542
|
+
selectedPage ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
|
|
4543
|
+
readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
4544
|
+
Input,
|
|
4545
|
+
{
|
|
4546
|
+
ref: inputRef,
|
|
4547
|
+
id: inputId,
|
|
4548
|
+
value,
|
|
4549
|
+
onChange: (e) => onInputChange(e.target.value),
|
|
4550
|
+
onFocus: () => {
|
|
4551
|
+
setIsFocused(true);
|
|
4552
|
+
setIsEditing(true);
|
|
4553
|
+
openDropdown();
|
|
4554
|
+
},
|
|
4555
|
+
onBlur: () => setIsFocused(false),
|
|
4556
|
+
placeholder,
|
|
4557
|
+
className: cn(
|
|
4558
|
+
"min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0",
|
|
4559
|
+
value ? "text-foreground" : "text-muted-foreground placeholder:text-muted-foreground"
|
|
4560
|
+
)
|
|
4561
|
+
}
|
|
4562
|
+
),
|
|
4563
|
+
!readOnly ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldChevron, { onClick: toggleDropdown }) : null
|
|
4564
|
+
]
|
|
4565
|
+
}
|
|
4566
|
+
) }),
|
|
4567
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
4568
|
+
"button",
|
|
4569
|
+
{
|
|
4570
|
+
type: "button",
|
|
4571
|
+
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",
|
|
4572
|
+
onClick: () => onPageSelect(page),
|
|
4573
|
+
children: [
|
|
4574
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(IconFile, { className: "shrink-0", "aria-hidden": true }),
|
|
4575
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: page.title })
|
|
4576
|
+
]
|
|
4577
|
+
},
|
|
4578
|
+
page.path
|
|
4579
|
+
)) })
|
|
4580
|
+
] }),
|
|
4581
|
+
urlError ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm font-medium text-destructive", children: urlError }) : null
|
|
4582
|
+
] });
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
// src/ui/link-modal/useLinkModalState.ts
|
|
4586
|
+
var import_react4 = require("react");
|
|
4587
|
+
|
|
4588
|
+
// src/ui/link-modal/linkModal.utils.ts
|
|
4589
|
+
function parseTarget(target) {
|
|
4590
|
+
if (!target) return { pageRoute: "", sectionId: null };
|
|
4591
|
+
const hashIndex = target.indexOf("#");
|
|
4592
|
+
if (hashIndex === -1) return { pageRoute: target, sectionId: null };
|
|
4593
|
+
return {
|
|
4594
|
+
pageRoute: target.slice(0, hashIndex),
|
|
4595
|
+
sectionId: target.slice(hashIndex + 1) || null
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
function normalizePath(path) {
|
|
4599
|
+
const trimmed = path.trim();
|
|
4600
|
+
if (!trimmed) return "/";
|
|
4601
|
+
try {
|
|
4602
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
4603
|
+
return new URL(trimmed).pathname || "/";
|
|
4604
|
+
}
|
|
4605
|
+
} catch {
|
|
4606
|
+
}
|
|
4607
|
+
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
4608
|
+
if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
|
|
4609
|
+
return withSlash;
|
|
4610
|
+
}
|
|
4611
|
+
function findPageByPath(pages, path) {
|
|
4612
|
+
const normalized = normalizePath(path);
|
|
4613
|
+
return pages.find((p) => normalizePath(p.path) === normalized);
|
|
4614
|
+
}
|
|
4615
|
+
function inferPageTitleFromPath(path) {
|
|
4616
|
+
const normalized = normalizePath(path);
|
|
4617
|
+
if (normalized === "/") return "Home";
|
|
4618
|
+
const segment = normalized.split("/").filter(Boolean).pop() ?? "";
|
|
4619
|
+
if (!segment) return "Home";
|
|
4620
|
+
return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
4621
|
+
}
|
|
4622
|
+
function isInternalPath(route) {
|
|
4623
|
+
const trimmed = route.trim();
|
|
4624
|
+
if (!trimmed) return false;
|
|
4625
|
+
if (/^https?:\/\//i.test(trimmed)) return false;
|
|
4626
|
+
if (trimmed.startsWith("/")) return true;
|
|
4627
|
+
if (trimmed.includes(" ") || trimmed.includes(".")) return false;
|
|
4628
|
+
return true;
|
|
4629
|
+
}
|
|
4630
|
+
function resolvePage(route, pages) {
|
|
4631
|
+
const found = findPageByPath(pages, route);
|
|
4632
|
+
if (found) return { path: found.path, title: found.title };
|
|
4633
|
+
return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
|
|
4634
|
+
}
|
|
4635
|
+
function getSectionsForPath(sectionsByPath, pageRoute, fallback) {
|
|
4636
|
+
if (!sectionsByPath) return fallback;
|
|
4637
|
+
const normalized = normalizePath(pageRoute);
|
|
4638
|
+
return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? fallback;
|
|
4639
|
+
}
|
|
4640
|
+
function buildTarget(page, section) {
|
|
4641
|
+
if (section) return `${page.path}#${section.id}`;
|
|
4642
|
+
return page.path;
|
|
4643
|
+
}
|
|
4644
|
+
function isValidUrl(urlString) {
|
|
4645
|
+
try {
|
|
4646
|
+
const url = new URL(urlString);
|
|
4647
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
4648
|
+
const hostname = url.hostname;
|
|
4649
|
+
if (!hostname || hostname.length === 0) return false;
|
|
4650
|
+
if (hostname.includes(" ")) return false;
|
|
4651
|
+
return hostname.includes(".") || hostname === "localhost";
|
|
4652
|
+
} catch {
|
|
4653
|
+
return false;
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
function normalizeUrl(value) {
|
|
4657
|
+
const trimmed = value.trim();
|
|
4658
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
|
|
4659
|
+
return `https://${trimmed}`;
|
|
4660
|
+
}
|
|
4661
|
+
function validateUrlInput(value, pages, allPages) {
|
|
4662
|
+
if (!value.trim()) return "";
|
|
4663
|
+
const filtered = pages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase()));
|
|
4664
|
+
if (filtered.length > 0) return "";
|
|
4665
|
+
const matchingPage = allPages.find((p) => p.title.toLowerCase() === value.toLowerCase());
|
|
4666
|
+
if (matchingPage) return "";
|
|
4667
|
+
if (!isValidUrl(normalizeUrl(value))) {
|
|
4668
|
+
return "Please enter a valid URL (e.g., https://example.com)";
|
|
4669
|
+
}
|
|
4670
|
+
return "";
|
|
4671
|
+
}
|
|
4672
|
+
function getEditModeInitialState(target, pages, sections) {
|
|
4673
|
+
const empty = {
|
|
4674
|
+
searchValue: "",
|
|
4675
|
+
selectedPage: null,
|
|
4676
|
+
selectedSection: null,
|
|
4677
|
+
step: "input"
|
|
4678
|
+
};
|
|
4679
|
+
if (!target) return empty;
|
|
4680
|
+
if (isValidUrl(normalizeUrl(target))) {
|
|
4681
|
+
return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4682
|
+
}
|
|
4683
|
+
const { pageRoute, sectionId } = parseTarget(target);
|
|
4684
|
+
if (sectionId) {
|
|
4685
|
+
if (isInternalPath(pageRoute)) {
|
|
4686
|
+
const page = resolvePage(pageRoute, pages);
|
|
4687
|
+
const sec = sections.find((s) => s.id === sectionId);
|
|
4688
|
+
return {
|
|
4689
|
+
searchValue: page.title,
|
|
4690
|
+
selectedPage: page,
|
|
4691
|
+
selectedSection: sec ? { id: sec.id, label: sec.label } : null,
|
|
4692
|
+
step: "input"
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4695
|
+
return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4696
|
+
}
|
|
4697
|
+
if (isInternalPath(pageRoute)) {
|
|
4698
|
+
const page = resolvePage(pageRoute, pages);
|
|
4699
|
+
return {
|
|
4700
|
+
searchValue: page.title,
|
|
4701
|
+
selectedPage: page,
|
|
4702
|
+
selectedSection: null,
|
|
4703
|
+
step: "input"
|
|
4704
|
+
};
|
|
4705
|
+
}
|
|
4706
|
+
return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4707
|
+
}
|
|
4708
|
+
function filterAvailablePages(pages, existingTargets) {
|
|
4709
|
+
const taken = new Set(existingTargets);
|
|
4710
|
+
return pages.filter((p) => !taken.has(p.path));
|
|
4711
|
+
}
|
|
4712
|
+
|
|
4713
|
+
// src/ui/link-modal/useLinkModalState.ts
|
|
4714
|
+
function useLinkModalState({
|
|
4715
|
+
open,
|
|
4716
|
+
mode,
|
|
4717
|
+
pages,
|
|
4718
|
+
sections,
|
|
4719
|
+
sectionsByPath,
|
|
4720
|
+
initialTarget,
|
|
4721
|
+
existingTargets,
|
|
4722
|
+
onClose,
|
|
4723
|
+
onSubmit
|
|
4724
|
+
}) {
|
|
4725
|
+
const availablePages = (0, import_react4.useMemo)(
|
|
4726
|
+
() => mode === "create" ? filterAvailablePages(pages, existingTargets) : pages,
|
|
4727
|
+
[mode, pages, existingTargets]
|
|
4728
|
+
);
|
|
4729
|
+
const [searchValue, setSearchValue] = (0, import_react4.useState)("");
|
|
4730
|
+
const [selectedPage, setSelectedPage] = (0, import_react4.useState)(null);
|
|
4731
|
+
const [selectedSection, setSelectedSection] = (0, import_react4.useState)(null);
|
|
4732
|
+
const [step, setStep] = (0, import_react4.useState)("input");
|
|
4733
|
+
const [dropdownOpen, setDropdownOpen] = (0, import_react4.useState)(false);
|
|
4734
|
+
const [urlError, setUrlError] = (0, import_react4.useState)("");
|
|
4735
|
+
const reset = (0, import_react4.useCallback)(() => {
|
|
4736
|
+
setSearchValue("");
|
|
4737
|
+
setSelectedPage(null);
|
|
4738
|
+
setSelectedSection(null);
|
|
4739
|
+
setStep("input");
|
|
4740
|
+
setDropdownOpen(false);
|
|
4741
|
+
setUrlError("");
|
|
4742
|
+
}, []);
|
|
4743
|
+
(0, import_react4.useEffect)(() => {
|
|
4744
|
+
if (!open) return;
|
|
4745
|
+
if (mode === "edit" && initialTarget) {
|
|
4746
|
+
const { pageRoute } = parseTarget(initialTarget);
|
|
4747
|
+
const targetSections = getSectionsForPath(sectionsByPath, pageRoute, sections);
|
|
4748
|
+
const init = getEditModeInitialState(initialTarget, pages, targetSections);
|
|
4749
|
+
setSearchValue(init.searchValue);
|
|
4750
|
+
setSelectedPage(init.selectedPage);
|
|
4751
|
+
setSelectedSection(init.selectedSection);
|
|
4752
|
+
setStep(init.step);
|
|
4753
|
+
} else {
|
|
4754
|
+
reset();
|
|
4755
|
+
}
|
|
4756
|
+
setDropdownOpen(false);
|
|
4757
|
+
setUrlError("");
|
|
4758
|
+
}, [open, mode, initialTarget, pages, sections, sectionsByPath, reset]);
|
|
4759
|
+
const filteredPages = (0, import_react4.useMemo)(() => {
|
|
4760
|
+
if (!searchValue.trim()) return availablePages;
|
|
4761
|
+
return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
|
|
4762
|
+
}, [availablePages, searchValue]);
|
|
4763
|
+
const activeSections = (0, import_react4.useMemo)(() => {
|
|
4764
|
+
if (selectedPage && sectionsByPath) {
|
|
4765
|
+
return getSectionsForPath(sectionsByPath, selectedPage.path, sections);
|
|
4766
|
+
}
|
|
4767
|
+
return sections;
|
|
4768
|
+
}, [selectedPage, sectionsByPath, sections]);
|
|
4769
|
+
const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
|
|
4770
|
+
const showSectionPicker = step === "sectionPicker" && selectedPage;
|
|
4771
|
+
const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
|
|
4772
|
+
const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow;
|
|
4773
|
+
const handleInputChange = (value) => {
|
|
4774
|
+
setSearchValue(value);
|
|
4775
|
+
setSelectedPage(null);
|
|
4776
|
+
setSelectedSection(null);
|
|
4777
|
+
setStep("input");
|
|
4778
|
+
setUrlError("");
|
|
4779
|
+
const filtered = value.trim() ? availablePages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase())) : availablePages;
|
|
4780
|
+
setDropdownOpen(filtered.length > 0);
|
|
4781
|
+
if (value.trim() && filtered.length === 0) {
|
|
4782
|
+
const error = validateUrlInput(value, availablePages, pages);
|
|
4783
|
+
if (error) setUrlError(error);
|
|
4784
|
+
}
|
|
4785
|
+
};
|
|
4786
|
+
const handlePageSelect = (page) => {
|
|
4787
|
+
setSelectedPage({ path: page.path, title: page.title });
|
|
4788
|
+
setSelectedSection(null);
|
|
4789
|
+
setSearchValue(page.title);
|
|
4790
|
+
setStep("input");
|
|
4791
|
+
setDropdownOpen(false);
|
|
4792
|
+
setUrlError("");
|
|
4793
|
+
};
|
|
4794
|
+
const handleChooseSection = () => {
|
|
4795
|
+
setStep("sectionPicker");
|
|
4796
|
+
};
|
|
4797
|
+
const handleSectionSelect = (section) => {
|
|
4798
|
+
setSelectedSection({ id: section.id, label: section.label });
|
|
4799
|
+
if (mode === "create") {
|
|
4800
|
+
setStep("confirmed");
|
|
4801
|
+
} else {
|
|
4802
|
+
setStep("input");
|
|
4803
|
+
}
|
|
4804
|
+
};
|
|
4805
|
+
const handleBackToSections = () => {
|
|
4806
|
+
setStep("sectionPicker");
|
|
4807
|
+
};
|
|
4808
|
+
const handleClose = () => {
|
|
4809
|
+
reset();
|
|
4810
|
+
onClose();
|
|
4811
|
+
};
|
|
4812
|
+
const isValid = (0, import_react4.useMemo)(() => {
|
|
4813
|
+
if (urlError) return false;
|
|
4814
|
+
if (showBreadcrumb) return true;
|
|
4815
|
+
if (selectedPage) return true;
|
|
4816
|
+
if (!searchValue.trim()) return false;
|
|
4817
|
+
return validateUrlInput(searchValue, availablePages, pages) === "";
|
|
4818
|
+
}, [urlError, showBreadcrumb, selectedPage, searchValue, availablePages, pages]);
|
|
4819
|
+
const handleSubmit = () => {
|
|
4820
|
+
if (selectedPage && selectedSection) {
|
|
4821
|
+
onSubmit(buildTarget(selectedPage, selectedSection));
|
|
4822
|
+
handleClose();
|
|
4823
|
+
return;
|
|
4824
|
+
}
|
|
4825
|
+
if (selectedPage) {
|
|
4826
|
+
onSubmit(buildTarget(selectedPage, null));
|
|
4827
|
+
handleClose();
|
|
4828
|
+
return;
|
|
4829
|
+
}
|
|
4830
|
+
if (!searchValue.trim()) return;
|
|
4831
|
+
const error = validateUrlInput(searchValue, availablePages, pages);
|
|
4832
|
+
if (error) {
|
|
4833
|
+
setUrlError(error);
|
|
4834
|
+
return;
|
|
4835
|
+
}
|
|
4836
|
+
const matchingPage = pages.find((p) => p.title.toLowerCase() === searchValue.toLowerCase());
|
|
4837
|
+
if (matchingPage) {
|
|
4838
|
+
onSubmit(matchingPage.path);
|
|
4839
|
+
handleClose();
|
|
4840
|
+
return;
|
|
4841
|
+
}
|
|
4842
|
+
onSubmit(normalizeUrl(searchValue));
|
|
4843
|
+
handleClose();
|
|
4844
|
+
};
|
|
4845
|
+
const submitLabel = mode === "edit" ? "Save" : "Create";
|
|
4846
|
+
const title = mode === "edit" ? "Edit navigation item" : "Create navigation item";
|
|
4847
|
+
const secondaryLabel = showBreadcrumb ? "Back to sections" : "Cancel";
|
|
4848
|
+
const handleSecondary = showBreadcrumb ? handleBackToSections : handleClose;
|
|
4849
|
+
return {
|
|
4850
|
+
title,
|
|
4851
|
+
submitLabel,
|
|
4852
|
+
secondaryLabel,
|
|
4853
|
+
searchValue,
|
|
4854
|
+
selectedPage,
|
|
4855
|
+
selectedSection,
|
|
4856
|
+
step,
|
|
4857
|
+
dropdownOpen,
|
|
4858
|
+
setDropdownOpen,
|
|
4859
|
+
urlError,
|
|
4860
|
+
filteredPages,
|
|
4861
|
+
showBreadcrumb,
|
|
4862
|
+
showSectionPicker,
|
|
4863
|
+
showChooseSection,
|
|
4864
|
+
showSectionRow,
|
|
4865
|
+
isValid,
|
|
4866
|
+
activeSections,
|
|
4867
|
+
handleInputChange,
|
|
4868
|
+
handlePageSelect,
|
|
4869
|
+
handleChooseSection,
|
|
4870
|
+
handleSectionSelect,
|
|
4871
|
+
handleBackToSections,
|
|
4872
|
+
handleClose,
|
|
4873
|
+
handleSecondary,
|
|
4874
|
+
handleSubmit
|
|
4875
|
+
};
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
// src/ui/link-modal/LinkEditorPanel.tsx
|
|
4879
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
4880
|
+
function LinkEditorPanel({
|
|
4881
|
+
open = true,
|
|
4882
|
+
mode = "create",
|
|
4883
|
+
pages,
|
|
4884
|
+
sections = [],
|
|
4885
|
+
sectionsByPath,
|
|
4886
|
+
initialTarget,
|
|
4887
|
+
existingTargets = [],
|
|
4888
|
+
onClose,
|
|
4889
|
+
onSubmit
|
|
4890
|
+
}) {
|
|
4891
|
+
const state = useLinkModalState({
|
|
4892
|
+
open,
|
|
4893
|
+
mode,
|
|
4894
|
+
pages,
|
|
4895
|
+
sections,
|
|
4896
|
+
sectionsByPath,
|
|
4897
|
+
initialTarget,
|
|
4898
|
+
existingTargets,
|
|
4899
|
+
onClose,
|
|
4900
|
+
onSubmit
|
|
4901
|
+
});
|
|
4902
|
+
const isCancel = state.secondaryLabel === "Cancel";
|
|
4903
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
4904
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4905
|
+
"button",
|
|
4906
|
+
{
|
|
4907
|
+
type: "button",
|
|
4908
|
+
className: "absolute right-[9px] top-[9px] rounded-sm p-1.5 text-foreground hover:bg-muted/50",
|
|
4909
|
+
onClick: state.handleClose,
|
|
4910
|
+
"aria-label": "Close",
|
|
4911
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconX, { "aria-hidden": true })
|
|
4912
|
+
}
|
|
4913
|
+
),
|
|
4914
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("header", { className: "flex w-full flex-col gap-1.5 p-6 pr-12", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { className: "m-0 w-full break-words text-lg font-semibold leading-none tracking-tight text-card-foreground", children: state.title }) }),
|
|
4915
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex w-full flex-col gap-3 px-6 pb-8 pt-1", children: [
|
|
4916
|
+
state.showBreadcrumb && state.selectedPage && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4917
|
+
DestinationBreadcrumb,
|
|
4918
|
+
{
|
|
4919
|
+
pageTitle: state.selectedPage.title,
|
|
4920
|
+
sectionLabel: state.selectedSection.label
|
|
4921
|
+
}
|
|
4922
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4923
|
+
UrlOrPageInput,
|
|
4924
|
+
{
|
|
4925
|
+
value: state.searchValue,
|
|
4926
|
+
selectedPage: state.selectedPage,
|
|
4927
|
+
dropdownOpen: state.dropdownOpen,
|
|
4928
|
+
onDropdownOpenChange: state.setDropdownOpen,
|
|
4929
|
+
filteredPages: state.filteredPages,
|
|
4930
|
+
onInputChange: state.handleInputChange,
|
|
4931
|
+
onPageSelect: state.handlePageSelect,
|
|
4932
|
+
urlError: state.urlError
|
|
4933
|
+
}
|
|
4934
|
+
),
|
|
4935
|
+
state.showChooseSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-col justify-center gap-2", children: [
|
|
4936
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Button, { type: "button", variant: "outline", className: "w-fit cursor-pointer", size: "sm", onClick: state.handleChooseSection, children: "Choose a section" }),
|
|
4937
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1 text-sm text-muted-foreground", children: [
|
|
4938
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(IconInfo, { className: "shrink-0", "aria-hidden": true }),
|
|
4939
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Pick a section this link should scroll to." })
|
|
4940
|
+
] })
|
|
4941
|
+
] }) : null,
|
|
4942
|
+
state.showSectionPicker ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionPickerList, { sections: state.activeSections, onSelect: state.handleSectionSelect }) : null,
|
|
4943
|
+
state.showSectionRow && state.selectedSection ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SectionTreeItem, { section: state.selectedSection, selected: true }) : null
|
|
4944
|
+
] }),
|
|
4945
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("footer", { className: "flex w-full items-center justify-end gap-2 px-6 pb-6", children: [
|
|
4946
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4947
|
+
Button,
|
|
4948
|
+
{
|
|
4949
|
+
type: "button",
|
|
4950
|
+
variant: isCancel ? "outline" : "ghost",
|
|
4951
|
+
className: "leading-6 shadow-none cursor-pointer",
|
|
4952
|
+
style: isCancel ? {
|
|
4953
|
+
backgroundColor: "var(--ohw-background)",
|
|
4954
|
+
borderColor: "var(--ohw-border)",
|
|
4955
|
+
color: "var(--ohw-foreground)"
|
|
4956
|
+
} : void 0,
|
|
4957
|
+
onClick: state.handleSecondary,
|
|
4958
|
+
children: state.secondaryLabel
|
|
4959
|
+
}
|
|
4960
|
+
),
|
|
4961
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
4962
|
+
Button,
|
|
4963
|
+
{
|
|
4964
|
+
type: "button",
|
|
4965
|
+
className: "border-0 leading-6 shadow-none hover:opacity-90 disabled:opacity-50 cursor-pointer",
|
|
4966
|
+
style: {
|
|
4967
|
+
backgroundColor: "var(--ohw-primary)",
|
|
4968
|
+
color: "var(--ohw-primary-foreground)"
|
|
4969
|
+
},
|
|
4970
|
+
disabled: !state.isValid,
|
|
4971
|
+
onClick: state.handleSubmit,
|
|
4972
|
+
children: state.submitLabel
|
|
4973
|
+
}
|
|
4974
|
+
)
|
|
4975
|
+
] })
|
|
4976
|
+
] });
|
|
4977
|
+
}
|
|
4978
|
+
|
|
4979
|
+
// src/ui/link-modal/LinkPopover.tsx
|
|
4980
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
4981
|
+
function LinkPopover({
|
|
4982
|
+
rect,
|
|
4983
|
+
parentScroll,
|
|
4984
|
+
panelRef,
|
|
4985
|
+
...editorProps
|
|
4986
|
+
}) {
|
|
4987
|
+
const { top, left, transform } = calcPopoverPos(rect, parentScroll);
|
|
4988
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
4989
|
+
"div",
|
|
4990
|
+
{
|
|
4991
|
+
ref: panelRef,
|
|
4992
|
+
"data-ohw-link-popover-root": "",
|
|
4993
|
+
"data-ohw-link-modal-root": "",
|
|
4994
|
+
"data-ohw-bridge": "",
|
|
4995
|
+
className: cn(
|
|
4996
|
+
"pointer-events-auto fixed z-[2147483647] flex w-[483px] max-w-[calc(100vw-16px)] flex-col overflow-hidden",
|
|
4997
|
+
"rounded-xl border border-border bg-background font-sans shadow-lg outline-none"
|
|
4998
|
+
),
|
|
4999
|
+
style: { top, left, transform },
|
|
5000
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
5001
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(LinkEditorPanel, { ...editorProps, open: true })
|
|
5002
|
+
}
|
|
5003
|
+
);
|
|
5004
|
+
}
|
|
5005
|
+
|
|
5006
|
+
// src/ui/link-modal/devFixtures.ts
|
|
5007
|
+
var DEV_SITE_PAGES = [
|
|
5008
|
+
{ path: "/", title: "Home" },
|
|
5009
|
+
{ path: "/about", title: "About" },
|
|
5010
|
+
{ path: "/classes", title: "Classes" },
|
|
5011
|
+
{ path: "/pricing", title: "Pricing" },
|
|
5012
|
+
{ path: "/studio", title: "Studio" },
|
|
5013
|
+
{ path: "/instructors", title: "Instructors" },
|
|
5014
|
+
{ path: "/contact", title: "Contact" }
|
|
5015
|
+
];
|
|
5016
|
+
var DEV_SECTIONS_BY_PATH = {
|
|
5017
|
+
"/": [
|
|
5018
|
+
{ id: "hero", label: "Hero" },
|
|
5019
|
+
{ id: "lagree-intro", label: "Lagree Intro" },
|
|
5020
|
+
{ id: "classes-strip", label: "Classes" },
|
|
5021
|
+
{ id: "feel-split", label: "Feel Split" },
|
|
5022
|
+
{ id: "founder-teaser", label: "Founder" },
|
|
5023
|
+
{ id: "plan-form", label: "Plan Form" },
|
|
5024
|
+
{ id: "testimonials", label: "Testimonials" },
|
|
5025
|
+
{ id: "wordmark", label: "Wordmark" }
|
|
5026
|
+
],
|
|
5027
|
+
"/about": [
|
|
5028
|
+
{ id: "manifesto", label: "Manifesto" },
|
|
5029
|
+
{ id: "story-letter", label: "Our Story" },
|
|
5030
|
+
{ id: "lagree-explainer", label: "Lagree Explainer" },
|
|
5031
|
+
{ id: "waitlist-cta", label: "Waitlist" },
|
|
5032
|
+
{ id: "personal-training", label: "Personal training" }
|
|
5033
|
+
],
|
|
5034
|
+
"/classes": [{ id: "class-library", label: "Class Library" }],
|
|
5035
|
+
"/pricing": [
|
|
5036
|
+
{ id: "benefits-marquee", label: "Benefits" },
|
|
5037
|
+
{ id: "monthly-memberships", label: "Memberships" },
|
|
5038
|
+
{ id: "package-matcher", label: "Packages" },
|
|
5039
|
+
{ id: "specialty-programs", label: "Specialty Programs" },
|
|
5040
|
+
{ id: "free-class-cta", label: "Free Class" }
|
|
5041
|
+
],
|
|
5042
|
+
"/studio": [
|
|
5043
|
+
{ id: "studio-intro", label: "Studio Intro" },
|
|
5044
|
+
{ id: "studio-features", label: "Studio Features" },
|
|
5045
|
+
{ id: "studio-gallery", label: "Studio Gallery" },
|
|
5046
|
+
{ id: "studio-visit", label: "Visit Studio" }
|
|
5047
|
+
],
|
|
5048
|
+
"/instructors": [{ id: "instructor-grid", label: "Instructors" }],
|
|
5049
|
+
"/contact": [
|
|
5050
|
+
{ id: "hello-hero", label: "Hello" },
|
|
5051
|
+
{ id: "contact-form", label: "Contact Form" },
|
|
5052
|
+
{ id: "faq", label: "FAQ" }
|
|
5053
|
+
]
|
|
5054
|
+
};
|
|
5055
|
+
function shouldUseDevFixtures() {
|
|
5056
|
+
if (typeof window === "undefined") return false;
|
|
5057
|
+
const raw = readPreservedSearch();
|
|
5058
|
+
const q = raw.startsWith("?") ? raw.slice(1) : raw;
|
|
5059
|
+
return new URLSearchParams(q).get("ohw-fixtures") === "1";
|
|
5060
|
+
}
|
|
5061
|
+
|
|
5062
|
+
// src/ui/badge.tsx
|
|
5063
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
5064
|
+
var badgeVariants = cva(
|
|
5065
|
+
"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",
|
|
5066
|
+
{
|
|
5067
|
+
variants: {
|
|
5068
|
+
variant: {
|
|
5069
|
+
default: "border-transparent bg-primary text-primary-foreground",
|
|
5070
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
5071
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
5072
|
+
outline: "text-foreground"
|
|
5073
|
+
}
|
|
5074
|
+
},
|
|
5075
|
+
defaultVariants: {
|
|
5076
|
+
variant: "default"
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
);
|
|
5080
|
+
function Badge({ className, variant, ...props }) {
|
|
5081
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
5082
|
+
}
|
|
5083
|
+
|
|
3475
5084
|
// src/OhhwellsBridge.tsx
|
|
3476
|
-
var
|
|
5085
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
3477
5086
|
var PRIMARY = "#0885FE";
|
|
3478
5087
|
var IMAGE_FADE_MS = 300;
|
|
3479
5088
|
function runOpacityFade(el, onDone) {
|
|
@@ -3524,8 +5133,173 @@ function fadeInBgImage(el, url, onReady) {
|
|
|
3524
5133
|
if (!prevPos || prevPos === "static") el.style.position = prevPos;
|
|
3525
5134
|
});
|
|
3526
5135
|
}
|
|
5136
|
+
function getSectionsTracker() {
|
|
5137
|
+
let el = document.querySelector("[data-ohw-sections-tracker]");
|
|
5138
|
+
if (!el) {
|
|
5139
|
+
el = document.createElement("div");
|
|
5140
|
+
el.setAttribute("data-ohw-sections-tracker", "");
|
|
5141
|
+
el.style.display = "none";
|
|
5142
|
+
document.body.appendChild(el);
|
|
5143
|
+
}
|
|
5144
|
+
return el;
|
|
5145
|
+
}
|
|
5146
|
+
function updateSectionScheduleId(insertAfter, scheduleId) {
|
|
5147
|
+
const tracker = getSectionsTracker();
|
|
5148
|
+
let sections = [];
|
|
5149
|
+
try {
|
|
5150
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5151
|
+
} catch {
|
|
5152
|
+
}
|
|
5153
|
+
const currentPath = window.location.pathname;
|
|
5154
|
+
const updated = sections.map((s) => {
|
|
5155
|
+
if (s.type !== "scheduling" || s.insertAfter !== insertAfter) return s;
|
|
5156
|
+
if (s.pagePath && s.pagePath !== currentPath) return s;
|
|
5157
|
+
return { ...s, scheduleId };
|
|
5158
|
+
});
|
|
5159
|
+
tracker.textContent = JSON.stringify(updated);
|
|
5160
|
+
return tracker.textContent ?? "[]";
|
|
5161
|
+
}
|
|
5162
|
+
function schedulingSectionId(insertAfter) {
|
|
5163
|
+
return `scheduling-${insertAfter}`;
|
|
5164
|
+
}
|
|
5165
|
+
var INSERT_BEFORE_MARKER = ":before:";
|
|
5166
|
+
function parseSchedulingInsertAfter(insertAfter) {
|
|
5167
|
+
const idx = insertAfter.indexOf(INSERT_BEFORE_MARKER);
|
|
5168
|
+
if (idx < 0) return { anchor: insertAfter, insertBefore: null };
|
|
5169
|
+
return {
|
|
5170
|
+
anchor: insertAfter.slice(0, idx),
|
|
5171
|
+
insertBefore: insertAfter.slice(idx + INSERT_BEFORE_MARKER.length)
|
|
5172
|
+
};
|
|
5173
|
+
}
|
|
5174
|
+
function resolveSchedulingInsert(insertAfter, explicitInsertBefore) {
|
|
5175
|
+
const parsed = parseSchedulingInsertAfter(insertAfter);
|
|
5176
|
+
const insertBefore = explicitInsertBefore ?? parsed.insertBefore;
|
|
5177
|
+
const effectiveInsertAfter = insertBefore && parsed.insertBefore === null ? `${insertAfter}${INSERT_BEFORE_MARKER}${insertBefore}` : insertAfter;
|
|
5178
|
+
return { effectiveInsertAfter, insertBefore };
|
|
5179
|
+
}
|
|
5180
|
+
function getSchedulingMountPoint(insertAfter) {
|
|
5181
|
+
const { anchor } = parseSchedulingInsertAfter(insertAfter);
|
|
5182
|
+
let anchorEl = document.querySelector(`[data-ohw-section="${anchor}"]`);
|
|
5183
|
+
if (!anchorEl && anchor === "scheduling") {
|
|
5184
|
+
const widgets = Array.from(document.querySelectorAll('[data-ohw-section^="scheduling-"]'));
|
|
5185
|
+
anchorEl = widgets.at(-1) ?? null;
|
|
5186
|
+
}
|
|
5187
|
+
if (!anchorEl) return null;
|
|
5188
|
+
return anchorEl.closest('[data-ohw-section-container="scheduling"]') ?? anchorEl;
|
|
5189
|
+
}
|
|
5190
|
+
function schedulingMountDepth(insertAfter) {
|
|
5191
|
+
if (!insertAfter.includes(INSERT_BEFORE_MARKER)) return 0;
|
|
5192
|
+
return insertAfter.split(INSERT_BEFORE_MARKER).length - 1;
|
|
5193
|
+
}
|
|
5194
|
+
function getPageSchedulingEntries(raw) {
|
|
5195
|
+
if (!raw) return [];
|
|
5196
|
+
try {
|
|
5197
|
+
const entries = JSON.parse(raw);
|
|
5198
|
+
const currentPath = window.location.pathname;
|
|
5199
|
+
return entries.filter((e) => e.type === "scheduling" && (!e.pagePath || e.pagePath === currentPath));
|
|
5200
|
+
} catch {
|
|
5201
|
+
return [];
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
function isSchedulingWidgetMissing(entry) {
|
|
5205
|
+
const { effectiveInsertAfter } = resolveSchedulingInsert(entry.insertAfter);
|
|
5206
|
+
return !document.querySelector(`[data-ohw-section="${schedulingSectionId(effectiveInsertAfter)}"]`);
|
|
5207
|
+
}
|
|
5208
|
+
function hasMissingSchedulingWidgets(entries) {
|
|
5209
|
+
return entries.some(isSchedulingWidgetMissing);
|
|
5210
|
+
}
|
|
5211
|
+
function retryMissingSchedulingMounts(entries, notifyOnConnect = false) {
|
|
5212
|
+
if (!hasMissingSchedulingWidgets(entries)) return;
|
|
5213
|
+
mountSchedulingEntries(entries, notifyOnConnect);
|
|
5214
|
+
}
|
|
5215
|
+
function initSectionsFromContent(content, removeExisting = false) {
|
|
5216
|
+
const raw = content["__ohw_sections"];
|
|
5217
|
+
if (!raw) return;
|
|
5218
|
+
try {
|
|
5219
|
+
if (removeExisting) {
|
|
5220
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => el.remove());
|
|
5221
|
+
}
|
|
5222
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
5223
|
+
if (inEditor) getSectionsTracker().textContent = raw;
|
|
5224
|
+
const pageEntries = getPageSchedulingEntries(raw);
|
|
5225
|
+
mountSchedulingEntries(pageEntries, false);
|
|
5226
|
+
requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
|
|
5227
|
+
setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
|
|
5228
|
+
} catch {
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId, explicitInsertBefore) {
|
|
5232
|
+
const { effectiveInsertAfter, insertBefore } = resolveSchedulingInsert(insertAfter, explicitInsertBefore);
|
|
5233
|
+
const sectionId = schedulingSectionId(effectiveInsertAfter);
|
|
5234
|
+
if (document.querySelector(`[data-ohw-section="${sectionId}"]`)) return false;
|
|
5235
|
+
const mountPoint = getSchedulingMountPoint(effectiveInsertAfter);
|
|
5236
|
+
if (!mountPoint) return false;
|
|
5237
|
+
const container = document.createElement("div");
|
|
5238
|
+
container.dataset.ohwSectionContainer = "scheduling";
|
|
5239
|
+
if (insertBefore) {
|
|
5240
|
+
const beforeAnchor = document.querySelector(`[data-ohw-section="${insertBefore}"]`);
|
|
5241
|
+
const beforePoint = beforeAnchor?.closest('[data-ohw-section-container="scheduling"]') ?? beforeAnchor;
|
|
5242
|
+
if (!beforePoint) return false;
|
|
5243
|
+
beforePoint.insertAdjacentElement("beforebegin", container);
|
|
5244
|
+
} else {
|
|
5245
|
+
let tail = mountPoint;
|
|
5246
|
+
while (tail.nextElementSibling?.dataset.ohwSectionContainer === "scheduling") {
|
|
5247
|
+
tail = tail.nextElementSibling;
|
|
5248
|
+
}
|
|
5249
|
+
tail.insertAdjacentElement("afterend", container);
|
|
5250
|
+
}
|
|
5251
|
+
const root = (0, import_client.createRoot)(container);
|
|
5252
|
+
(0, import_react_dom.flushSync)(() => {
|
|
5253
|
+
root.render(
|
|
5254
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5255
|
+
SchedulingWidget,
|
|
5256
|
+
{
|
|
5257
|
+
notifyOnConnect,
|
|
5258
|
+
initialScheduleId: scheduleId,
|
|
5259
|
+
insertAfter: effectiveInsertAfter
|
|
5260
|
+
}
|
|
5261
|
+
)
|
|
5262
|
+
);
|
|
5263
|
+
});
|
|
5264
|
+
const tracker = getSectionsTracker();
|
|
5265
|
+
let sections = [];
|
|
5266
|
+
try {
|
|
5267
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
5268
|
+
} catch {
|
|
5269
|
+
}
|
|
5270
|
+
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
5271
|
+
if (inEditor && !sections.find((s) => s.type === "scheduling" && s.insertAfter === effectiveInsertAfter)) {
|
|
5272
|
+
sections.push({
|
|
5273
|
+
type: "scheduling",
|
|
5274
|
+
insertAfter: effectiveInsertAfter,
|
|
5275
|
+
pagePath: window.location.pathname,
|
|
5276
|
+
...scheduleId ? { scheduleId } : {}
|
|
5277
|
+
});
|
|
5278
|
+
tracker.textContent = JSON.stringify(sections);
|
|
5279
|
+
}
|
|
5280
|
+
return true;
|
|
5281
|
+
}
|
|
5282
|
+
function mountSchedulingEntries(entries, notifyOnConnect = false) {
|
|
5283
|
+
const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
|
|
5284
|
+
for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
|
|
5285
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
5286
|
+
const { insertAfter, scheduleId } = pending[i];
|
|
5287
|
+
if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
|
|
5288
|
+
pending.splice(i, 1);
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
function getLinkHref(el) {
|
|
5294
|
+
const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
|
|
5295
|
+
return anchor?.getAttribute("href") ?? "";
|
|
5296
|
+
}
|
|
5297
|
+
function applyLinkHref(el, val) {
|
|
5298
|
+
const anchor = el instanceof HTMLAnchorElement ? el : el.querySelector("a");
|
|
5299
|
+
if (anchor) anchor.setAttribute("href", val);
|
|
5300
|
+
}
|
|
3527
5301
|
function collectEditableNodes() {
|
|
3528
|
-
|
|
5302
|
+
const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
3529
5303
|
if (el.dataset.ohwEditable === "image") {
|
|
3530
5304
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
3531
5305
|
return { key: el.dataset.ohwKey ?? "", type: "image", text: img?.src ?? "" };
|
|
@@ -3535,12 +5309,52 @@ function collectEditableNodes() {
|
|
|
3535
5309
|
const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
|
|
3536
5310
|
return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
|
|
3537
5311
|
}
|
|
5312
|
+
if (el.dataset.ohwEditable === "link") {
|
|
5313
|
+
return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
|
|
5314
|
+
}
|
|
3538
5315
|
return {
|
|
3539
5316
|
key: el.dataset.ohwKey ?? "",
|
|
3540
5317
|
type: el.dataset.ohwEditable ?? "text",
|
|
3541
5318
|
text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
|
|
3542
5319
|
};
|
|
3543
5320
|
});
|
|
5321
|
+
document.querySelectorAll("[data-ohw-href-key]").forEach((el) => {
|
|
5322
|
+
const key = el.getAttribute("data-ohw-href-key") ?? "";
|
|
5323
|
+
if (!key) return;
|
|
5324
|
+
nodes.push({ key, type: "link", text: getLinkHref(el) });
|
|
5325
|
+
});
|
|
5326
|
+
return nodes;
|
|
5327
|
+
}
|
|
5328
|
+
function applyLinkByKey(key, val) {
|
|
5329
|
+
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
5330
|
+
if (el.dataset.ohwEditable === "link") applyLinkHref(el, val);
|
|
5331
|
+
});
|
|
5332
|
+
document.querySelectorAll(`[data-ohw-href-key="${key}"]`).forEach((el) => {
|
|
5333
|
+
applyLinkHref(el, val);
|
|
5334
|
+
});
|
|
5335
|
+
}
|
|
5336
|
+
function isInsideLinkEditor(target) {
|
|
5337
|
+
return Boolean(
|
|
5338
|
+
target.closest("[data-ohw-link-popover-root]") || target.closest("[data-ohw-link-modal-root]") || target.closest('[data-slot="popover-content"]')
|
|
5339
|
+
);
|
|
5340
|
+
}
|
|
5341
|
+
function getHrefKeyFromElement(el) {
|
|
5342
|
+
if (!el) return null;
|
|
5343
|
+
const anchor = el.closest("[data-ohw-href-key]");
|
|
5344
|
+
if (!anchor) return null;
|
|
5345
|
+
const key = anchor.getAttribute("data-ohw-href-key");
|
|
5346
|
+
if (!key) return null;
|
|
5347
|
+
return { anchor, key };
|
|
5348
|
+
}
|
|
5349
|
+
function titleCaseSectionId(id) {
|
|
5350
|
+
return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
5351
|
+
}
|
|
5352
|
+
function collectSections() {
|
|
5353
|
+
return Array.from(document.querySelectorAll("[data-ohw-section]")).map((el) => {
|
|
5354
|
+
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
5355
|
+
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
5356
|
+
return { id, label };
|
|
5357
|
+
});
|
|
3544
5358
|
}
|
|
3545
5359
|
var FORCE_PSEUDO_STATES = [
|
|
3546
5360
|
{ pseudo: ":hover", state: "hover" },
|
|
@@ -3681,7 +5495,7 @@ var TOOLBAR_GROUPS = [
|
|
|
3681
5495
|
];
|
|
3682
5496
|
function GlowFrame({ rect, elRef }) {
|
|
3683
5497
|
const GAP = 6;
|
|
3684
|
-
return /* @__PURE__ */ (0,
|
|
5498
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3685
5499
|
"div",
|
|
3686
5500
|
{
|
|
3687
5501
|
ref: elRef,
|
|
@@ -3729,10 +5543,12 @@ function FloatingToolbar({
|
|
|
3729
5543
|
parentScroll,
|
|
3730
5544
|
elRef,
|
|
3731
5545
|
onCommand,
|
|
3732
|
-
activeCommands
|
|
5546
|
+
activeCommands,
|
|
5547
|
+
showEditLink,
|
|
5548
|
+
onEditLink
|
|
3733
5549
|
}) {
|
|
3734
5550
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
3735
|
-
return /* @__PURE__ */ (0,
|
|
5551
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
3736
5552
|
"div",
|
|
3737
5553
|
{
|
|
3738
5554
|
ref: elRef,
|
|
@@ -3755,56 +5571,97 @@ function FloatingToolbar({
|
|
|
3755
5571
|
pointerEvents: "auto",
|
|
3756
5572
|
whiteSpace: "nowrap"
|
|
3757
5573
|
},
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
5574
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
5575
|
+
children: [
|
|
5576
|
+
TOOLBAR_GROUPS.map((btns, gi) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_react5.default.Fragment, { children: [
|
|
5577
|
+
gi > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { display: "block", width: 1, height: 24, background: "#E7E5E4", flexShrink: 0 } }),
|
|
5578
|
+
btns.map((btn) => {
|
|
5579
|
+
const isActive = activeCommands.has(btn.cmd);
|
|
5580
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5581
|
+
"button",
|
|
5582
|
+
{
|
|
5583
|
+
title: btn.title,
|
|
5584
|
+
onMouseDown: (e) => {
|
|
5585
|
+
e.preventDefault();
|
|
5586
|
+
onCommand(btn.cmd);
|
|
5587
|
+
},
|
|
5588
|
+
onMouseEnter: (e) => {
|
|
5589
|
+
if (!isActive) e.currentTarget.style.background = "#F5F5F4";
|
|
5590
|
+
},
|
|
5591
|
+
onMouseLeave: (e) => {
|
|
5592
|
+
if (!isActive) e.currentTarget.style.background = "transparent";
|
|
5593
|
+
},
|
|
5594
|
+
style: {
|
|
5595
|
+
display: "flex",
|
|
5596
|
+
alignItems: "center",
|
|
5597
|
+
justifyContent: "center",
|
|
5598
|
+
border: "none",
|
|
5599
|
+
background: isActive ? PRIMARY : "transparent",
|
|
5600
|
+
borderRadius: 4,
|
|
5601
|
+
cursor: "pointer",
|
|
5602
|
+
color: isActive ? "#FFFFFF" : "#1C1917",
|
|
5603
|
+
flexShrink: 0,
|
|
5604
|
+
padding: 6
|
|
5605
|
+
},
|
|
5606
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5607
|
+
"svg",
|
|
5608
|
+
{
|
|
5609
|
+
width: "16",
|
|
5610
|
+
height: "16",
|
|
5611
|
+
viewBox: "0 0 24 24",
|
|
5612
|
+
fill: "none",
|
|
5613
|
+
stroke: isActive ? "#FFFFFF" : "#1C1917",
|
|
5614
|
+
strokeWidth: "2.5",
|
|
5615
|
+
strokeLinecap: "round",
|
|
5616
|
+
strokeLinejoin: "round",
|
|
5617
|
+
style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
|
|
5618
|
+
dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
|
|
5619
|
+
}
|
|
5620
|
+
)
|
|
3769
5621
|
},
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
},
|
|
3788
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
3789
|
-
"svg",
|
|
3790
|
-
{
|
|
3791
|
-
width: "16",
|
|
3792
|
-
height: "16",
|
|
3793
|
-
viewBox: "0 0 24 24",
|
|
3794
|
-
fill: "none",
|
|
3795
|
-
stroke: isActive ? "#FFFFFF" : "#1C1917",
|
|
3796
|
-
strokeWidth: "2.5",
|
|
3797
|
-
strokeLinecap: "round",
|
|
3798
|
-
strokeLinejoin: "round",
|
|
3799
|
-
style: isActive && gi > 0 ? { filter: "drop-shadow(0 0 0.5px #fff)" } : void 0,
|
|
3800
|
-
dangerouslySetInnerHTML: { __html: ICONS[btn.cmd] }
|
|
3801
|
-
}
|
|
3802
|
-
)
|
|
5622
|
+
btn.cmd
|
|
5623
|
+
);
|
|
5624
|
+
})
|
|
5625
|
+
] }, gi)),
|
|
5626
|
+
showEditLink ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5627
|
+
"button",
|
|
5628
|
+
{
|
|
5629
|
+
type: "button",
|
|
5630
|
+
title: "Edit link",
|
|
5631
|
+
onMouseDown: (e) => {
|
|
5632
|
+
e.preventDefault();
|
|
5633
|
+
e.stopPropagation();
|
|
5634
|
+
onEditLink?.();
|
|
5635
|
+
},
|
|
5636
|
+
onClick: (e) => {
|
|
5637
|
+
e.preventDefault();
|
|
5638
|
+
e.stopPropagation();
|
|
3803
5639
|
},
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
5640
|
+
onMouseEnter: (e) => {
|
|
5641
|
+
e.currentTarget.style.background = "#F5F5F4";
|
|
5642
|
+
},
|
|
5643
|
+
onMouseLeave: (e) => {
|
|
5644
|
+
e.currentTarget.style.background = "transparent";
|
|
5645
|
+
},
|
|
5646
|
+
style: {
|
|
5647
|
+
display: "flex",
|
|
5648
|
+
alignItems: "center",
|
|
5649
|
+
justifyContent: "center",
|
|
5650
|
+
border: "none",
|
|
5651
|
+
background: "transparent",
|
|
5652
|
+
borderRadius: 4,
|
|
5653
|
+
cursor: "pointer",
|
|
5654
|
+
color: "#1C1917",
|
|
5655
|
+
flexShrink: 0,
|
|
5656
|
+
padding: 6
|
|
5657
|
+
},
|
|
5658
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("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: [
|
|
5659
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
|
|
5660
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
|
|
5661
|
+
] })
|
|
5662
|
+
}
|
|
5663
|
+
) : null
|
|
5664
|
+
]
|
|
3808
5665
|
}
|
|
3809
5666
|
);
|
|
3810
5667
|
}
|
|
@@ -3818,7 +5675,7 @@ function StateToggle({
|
|
|
3818
5675
|
states,
|
|
3819
5676
|
onStateChange
|
|
3820
5677
|
}) {
|
|
3821
|
-
return /* @__PURE__ */ (0,
|
|
5678
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
3822
5679
|
ToggleGroup,
|
|
3823
5680
|
{
|
|
3824
5681
|
"data-ohw-state-toggle": "",
|
|
@@ -3832,18 +5689,35 @@ function StateToggle({
|
|
|
3832
5689
|
left: rect.right - 8,
|
|
3833
5690
|
transform: "translateX(-100%)"
|
|
3834
5691
|
},
|
|
3835
|
-
children: states.map((state) => /* @__PURE__ */ (0,
|
|
5692
|
+
children: states.map((state) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ToggleGroupItem, { value: state, size: "sm", children: state }, state))
|
|
3836
5693
|
}
|
|
3837
5694
|
);
|
|
3838
5695
|
}
|
|
3839
5696
|
var contentCache = /* @__PURE__ */ new Map();
|
|
5697
|
+
function resolveSubdomain(subdomainFromQuery) {
|
|
5698
|
+
if (subdomainFromQuery) return subdomainFromQuery;
|
|
5699
|
+
if (typeof window !== "undefined") {
|
|
5700
|
+
const parts = window.location.hostname.split(".");
|
|
5701
|
+
if (parts.length >= 3 && parts[0] !== "www") return parts[0];
|
|
5702
|
+
}
|
|
5703
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL;
|
|
5704
|
+
if (siteUrl) {
|
|
5705
|
+
try {
|
|
5706
|
+
const host = new URL(siteUrl).hostname;
|
|
5707
|
+
const siteParts = host.split(".");
|
|
5708
|
+
if (siteParts.length >= 3 && siteParts[0] !== "www") return siteParts[0];
|
|
5709
|
+
} catch {
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
return "";
|
|
5713
|
+
}
|
|
3840
5714
|
function OhhwellsBridge() {
|
|
3841
5715
|
const pathname = (0, import_navigation.usePathname)();
|
|
3842
5716
|
const router = (0, import_navigation.useRouter)();
|
|
3843
5717
|
const searchParams = (0, import_navigation.useSearchParams)();
|
|
3844
5718
|
const isEditMode = isEditSessionActive();
|
|
3845
|
-
const [bridgeRoot, setBridgeRoot] = (0,
|
|
3846
|
-
(0,
|
|
5719
|
+
const [bridgeRoot, setBridgeRoot] = (0, import_react5.useState)(null);
|
|
5720
|
+
(0, import_react5.useEffect)(() => {
|
|
3847
5721
|
const figtreeFontId = "ohw-figtree-font";
|
|
3848
5722
|
if (!document.getElementById(figtreeFontId)) {
|
|
3849
5723
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -3866,42 +5740,76 @@ function OhhwellsBridge() {
|
|
|
3866
5740
|
};
|
|
3867
5741
|
}, []);
|
|
3868
5742
|
const subdomainFromQuery = searchParams.get("subdomain");
|
|
3869
|
-
const subdomain = subdomainFromQuery
|
|
3870
|
-
|
|
3871
|
-
const parts = window.location.hostname.split(".");
|
|
3872
|
-
return parts.length >= 3 && parts[0] !== "www" ? parts[0] : "";
|
|
3873
|
-
})();
|
|
3874
|
-
const postToParent = (0, import_react.useCallback)((data) => {
|
|
5743
|
+
const subdomain = resolveSubdomain(subdomainFromQuery);
|
|
5744
|
+
const postToParent = (0, import_react5.useCallback)((data) => {
|
|
3875
5745
|
if (typeof window !== "undefined" && window.parent !== window) {
|
|
3876
5746
|
window.parent.postMessage(data, "*");
|
|
3877
5747
|
}
|
|
3878
5748
|
}, []);
|
|
3879
|
-
const [fetchState, setFetchState] = (0,
|
|
3880
|
-
const autoSaveTimers = (0,
|
|
3881
|
-
const activeElRef = (0,
|
|
3882
|
-
const originalContentRef = (0,
|
|
3883
|
-
const activeStateElRef = (0,
|
|
3884
|
-
const parentScrollRef = (0,
|
|
3885
|
-
const toolbarElRef = (0,
|
|
3886
|
-
const glowElRef = (0,
|
|
3887
|
-
const hoveredImageRef = (0,
|
|
3888
|
-
const hoveredImageHasTextOverlapRef = (0,
|
|
3889
|
-
const
|
|
3890
|
-
const
|
|
3891
|
-
const
|
|
3892
|
-
const
|
|
5749
|
+
const [fetchState, setFetchState] = (0, import_react5.useState)("idle");
|
|
5750
|
+
const autoSaveTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
|
|
5751
|
+
const activeElRef = (0, import_react5.useRef)(null);
|
|
5752
|
+
const originalContentRef = (0, import_react5.useRef)(null);
|
|
5753
|
+
const activeStateElRef = (0, import_react5.useRef)(null);
|
|
5754
|
+
const parentScrollRef = (0, import_react5.useRef)(null);
|
|
5755
|
+
const toolbarElRef = (0, import_react5.useRef)(null);
|
|
5756
|
+
const glowElRef = (0, import_react5.useRef)(null);
|
|
5757
|
+
const hoveredImageRef = (0, import_react5.useRef)(null);
|
|
5758
|
+
const hoveredImageHasTextOverlapRef = (0, import_react5.useRef)(false);
|
|
5759
|
+
const hoveredGapRef = (0, import_react5.useRef)(null);
|
|
5760
|
+
const imageUnhoverTimerRef = (0, import_react5.useRef)(null);
|
|
5761
|
+
const imageShowTimerRef = (0, import_react5.useRef)(null);
|
|
5762
|
+
const editStylesRef = (0, import_react5.useRef)(null);
|
|
5763
|
+
const activateRef = (0, import_react5.useRef)(() => {
|
|
3893
5764
|
});
|
|
3894
|
-
const deactivateRef = (0,
|
|
5765
|
+
const deactivateRef = (0, import_react5.useRef)(() => {
|
|
3895
5766
|
});
|
|
3896
|
-
const refreshActiveCommandsRef = (0,
|
|
5767
|
+
const refreshActiveCommandsRef = (0, import_react5.useRef)(() => {
|
|
3897
5768
|
});
|
|
3898
|
-
const postToParentRef = (0,
|
|
5769
|
+
const postToParentRef = (0, import_react5.useRef)(postToParent);
|
|
3899
5770
|
postToParentRef.current = postToParent;
|
|
3900
|
-
const [toolbarRect, setToolbarRect] = (0,
|
|
3901
|
-
const [toggleState, setToggleState] = (0,
|
|
3902
|
-
const [maxBadge, setMaxBadge] = (0,
|
|
3903
|
-
const [activeCommands, setActiveCommands] = (0,
|
|
3904
|
-
(0,
|
|
5771
|
+
const [toolbarRect, setToolbarRect] = (0, import_react5.useState)(null);
|
|
5772
|
+
const [toggleState, setToggleState] = (0, import_react5.useState)(null);
|
|
5773
|
+
const [maxBadge, setMaxBadge] = (0, import_react5.useState)(null);
|
|
5774
|
+
const [activeCommands, setActiveCommands] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
|
|
5775
|
+
const [toolbarShowEditLink, setToolbarShowEditLink] = (0, import_react5.useState)(false);
|
|
5776
|
+
const [linkPopover, setLinkPopover] = (0, import_react5.useState)(null);
|
|
5777
|
+
const [sitePages, setSitePages] = (0, import_react5.useState)([]);
|
|
5778
|
+
const [sectionsByPath, setSectionsByPath] = (0, import_react5.useState)({});
|
|
5779
|
+
const setLinkPopoverRef = (0, import_react5.useRef)(setLinkPopover);
|
|
5780
|
+
const linkPopoverPanelRef = (0, import_react5.useRef)(null);
|
|
5781
|
+
const linkPopoverOpenRef = (0, import_react5.useRef)(false);
|
|
5782
|
+
const linkPopoverGraceUntilRef = (0, import_react5.useRef)(0);
|
|
5783
|
+
setLinkPopoverRef.current = setLinkPopover;
|
|
5784
|
+
const bumpLinkPopoverGrace = () => {
|
|
5785
|
+
linkPopoverGraceUntilRef.current = Date.now() + 350;
|
|
5786
|
+
};
|
|
5787
|
+
(0, import_react5.useEffect)(() => {
|
|
5788
|
+
if (!isEditMode) return;
|
|
5789
|
+
const useFixtures = shouldUseDevFixtures();
|
|
5790
|
+
if (useFixtures) {
|
|
5791
|
+
setSitePages(DEV_SITE_PAGES);
|
|
5792
|
+
setSectionsByPath(DEV_SECTIONS_BY_PATH);
|
|
5793
|
+
if (process.env.NODE_ENV === "development") {
|
|
5794
|
+
console.info("[ohhwells-bridge] Dev fixtures loaded (ohw-fixtures=1)");
|
|
5795
|
+
}
|
|
5796
|
+
}
|
|
5797
|
+
const onSitePages = (e) => {
|
|
5798
|
+
if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
|
|
5799
|
+
if (useFixtures) return;
|
|
5800
|
+
setSitePages(
|
|
5801
|
+
e.data.pages.map((p) => ({
|
|
5802
|
+
path: p.path,
|
|
5803
|
+
title: p.title
|
|
5804
|
+
}))
|
|
5805
|
+
);
|
|
5806
|
+
};
|
|
5807
|
+
window.addEventListener("message", onSitePages);
|
|
5808
|
+
if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
|
|
5809
|
+
return () => window.removeEventListener("message", onSitePages);
|
|
5810
|
+
}, [isEditMode, postToParent]);
|
|
5811
|
+
const [sectionGap, setSectionGap] = (0, import_react5.useState)(null);
|
|
5812
|
+
(0, import_react5.useEffect)(() => {
|
|
3905
5813
|
const update = () => {
|
|
3906
5814
|
const el = activeElRef.current;
|
|
3907
5815
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -3909,6 +5817,12 @@ function OhhwellsBridge() {
|
|
|
3909
5817
|
if (!prev || !activeStateElRef.current) return prev;
|
|
3910
5818
|
return { ...prev, rect: activeStateElRef.current.getBoundingClientRect() };
|
|
3911
5819
|
});
|
|
5820
|
+
setSectionGap((prev) => {
|
|
5821
|
+
if (!prev) return prev;
|
|
5822
|
+
const anchor = document.querySelector(`[data-ohw-section="${prev.insertAfter}"]`);
|
|
5823
|
+
if (!anchor) return prev;
|
|
5824
|
+
return { ...prev, y: anchor.getBoundingClientRect().bottom };
|
|
5825
|
+
});
|
|
3912
5826
|
};
|
|
3913
5827
|
const vvp = window.visualViewport;
|
|
3914
5828
|
if (!vvp) return;
|
|
@@ -3919,10 +5833,10 @@ function OhhwellsBridge() {
|
|
|
3919
5833
|
vvp.removeEventListener("resize", update);
|
|
3920
5834
|
};
|
|
3921
5835
|
}, []);
|
|
3922
|
-
const refreshStateRules = (0,
|
|
5836
|
+
const refreshStateRules = (0, import_react5.useCallback)(() => {
|
|
3923
5837
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
3924
5838
|
}, []);
|
|
3925
|
-
const deactivate = (0,
|
|
5839
|
+
const deactivate = (0, import_react5.useCallback)(() => {
|
|
3926
5840
|
const el = activeElRef.current;
|
|
3927
5841
|
if (!el) return;
|
|
3928
5842
|
const key = el.dataset.ohwKey;
|
|
@@ -3945,9 +5859,10 @@ function OhhwellsBridge() {
|
|
|
3945
5859
|
setToolbarRect(null);
|
|
3946
5860
|
setMaxBadge(null);
|
|
3947
5861
|
setActiveCommands(/* @__PURE__ */ new Set());
|
|
5862
|
+
setToolbarShowEditLink(false);
|
|
3948
5863
|
postToParent({ type: "ow:exit-edit" });
|
|
3949
5864
|
}, [postToParent]);
|
|
3950
|
-
const activate = (0,
|
|
5865
|
+
const activate = (0, import_react5.useCallback)((el) => {
|
|
3951
5866
|
if (activeElRef.current === el) return;
|
|
3952
5867
|
deactivate();
|
|
3953
5868
|
if (hoveredImageRef.current) {
|
|
@@ -3960,12 +5875,13 @@ function OhhwellsBridge() {
|
|
|
3960
5875
|
originalContentRef.current = el.innerHTML;
|
|
3961
5876
|
el.focus();
|
|
3962
5877
|
setToolbarRect(el.getBoundingClientRect());
|
|
5878
|
+
setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
|
|
3963
5879
|
postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
|
|
3964
5880
|
requestAnimationFrame(() => refreshActiveCommandsRef.current());
|
|
3965
5881
|
}, [deactivate, postToParent]);
|
|
3966
5882
|
activateRef.current = activate;
|
|
3967
5883
|
deactivateRef.current = deactivate;
|
|
3968
|
-
(0,
|
|
5884
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
3969
5885
|
if (!subdomain || isEditMode) {
|
|
3970
5886
|
setFetchState("done");
|
|
3971
5887
|
return;
|
|
@@ -3973,6 +5889,7 @@ function OhhwellsBridge() {
|
|
|
3973
5889
|
const applyContent = (content) => {
|
|
3974
5890
|
const imageLoads = [];
|
|
3975
5891
|
for (const [key, val] of Object.entries(content)) {
|
|
5892
|
+
if (key === "__ohw_sections") continue;
|
|
3976
5893
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
3977
5894
|
if (el.dataset.ohwEditable === "image") {
|
|
3978
5895
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -3986,11 +5903,15 @@ function OhhwellsBridge() {
|
|
|
3986
5903
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
3987
5904
|
const next = `url('${val}')`;
|
|
3988
5905
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
5906
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
5907
|
+
applyLinkHref(el, val);
|
|
3989
5908
|
} else if (el.innerHTML !== val) {
|
|
3990
5909
|
el.innerHTML = val;
|
|
3991
5910
|
}
|
|
3992
5911
|
});
|
|
5912
|
+
applyLinkByKey(key, val);
|
|
3993
5913
|
}
|
|
5914
|
+
initSectionsFromContent(content, true);
|
|
3994
5915
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
3995
5916
|
}) : Promise.resolve();
|
|
3996
5917
|
};
|
|
@@ -4015,12 +5936,15 @@ function OhhwellsBridge() {
|
|
|
4015
5936
|
cancelled = true;
|
|
4016
5937
|
};
|
|
4017
5938
|
}, [subdomain, isEditMode, pathname]);
|
|
4018
|
-
(0,
|
|
5939
|
+
(0, import_react5.useEffect)(() => {
|
|
4019
5940
|
if (!subdomain || isEditMode) return;
|
|
5941
|
+
let debounceTimer = null;
|
|
4020
5942
|
const applyFromCache = () => {
|
|
4021
5943
|
const content = contentCache.get(subdomain);
|
|
4022
5944
|
if (!content) return;
|
|
5945
|
+
retryMissingSchedulingMounts(getPageSchedulingEntries(content["__ohw_sections"]), false);
|
|
4023
5946
|
for (const [key, val] of Object.entries(content)) {
|
|
5947
|
+
if (key === "__ohw_sections") continue;
|
|
4024
5948
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4025
5949
|
if (el.dataset.ohwEditable === "image") {
|
|
4026
5950
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
@@ -4028,27 +5952,37 @@ function OhhwellsBridge() {
|
|
|
4028
5952
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
4029
5953
|
const next = `url('${val}')`;
|
|
4030
5954
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
5955
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
5956
|
+
applyLinkHref(el, val);
|
|
4031
5957
|
} else if (el.innerHTML !== val) {
|
|
4032
5958
|
el.innerHTML = val;
|
|
4033
5959
|
}
|
|
4034
5960
|
});
|
|
5961
|
+
applyLinkByKey(key, val);
|
|
4035
5962
|
}
|
|
4036
5963
|
};
|
|
4037
|
-
|
|
4038
|
-
|
|
5964
|
+
const scheduleApply = () => {
|
|
5965
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
5966
|
+
debounceTimer = setTimeout(applyFromCache, 150);
|
|
5967
|
+
};
|
|
5968
|
+
scheduleApply();
|
|
5969
|
+
const observer = new MutationObserver(scheduleApply);
|
|
4039
5970
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
4040
|
-
return () =>
|
|
4041
|
-
|
|
4042
|
-
|
|
5971
|
+
return () => {
|
|
5972
|
+
observer.disconnect();
|
|
5973
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
5974
|
+
};
|
|
5975
|
+
}, [subdomain, isEditMode, pathname]);
|
|
5976
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
4043
5977
|
const el = document.getElementById("ohw-loader");
|
|
4044
5978
|
if (!el) return;
|
|
4045
5979
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
4046
5980
|
el.style.display = visible ? "flex" : "none";
|
|
4047
5981
|
}, [subdomain, fetchState]);
|
|
4048
|
-
(0,
|
|
5982
|
+
(0, import_react5.useEffect)(() => {
|
|
4049
5983
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
4050
5984
|
}, [pathname, postToParent]);
|
|
4051
|
-
(0,
|
|
5985
|
+
(0, import_react5.useEffect)(() => {
|
|
4052
5986
|
if (!isEditMode) return;
|
|
4053
5987
|
const measure = () => {
|
|
4054
5988
|
const h = document.body.scrollHeight;
|
|
@@ -4072,7 +6006,7 @@ function OhhwellsBridge() {
|
|
|
4072
6006
|
window.removeEventListener("resize", handleResize);
|
|
4073
6007
|
};
|
|
4074
6008
|
}, [pathname, isEditMode, postToParent]);
|
|
4075
|
-
(0,
|
|
6009
|
+
(0, import_react5.useEffect)(() => {
|
|
4076
6010
|
if (!subdomainFromQuery || isEditMode) return;
|
|
4077
6011
|
const handleClick = (e) => {
|
|
4078
6012
|
const anchor = e.target.closest("a");
|
|
@@ -4088,7 +6022,7 @@ function OhhwellsBridge() {
|
|
|
4088
6022
|
document.addEventListener("click", handleClick, true);
|
|
4089
6023
|
return () => document.removeEventListener("click", handleClick, true);
|
|
4090
6024
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
4091
|
-
(0,
|
|
6025
|
+
(0, import_react5.useEffect)(() => {
|
|
4092
6026
|
if (!isEditMode) {
|
|
4093
6027
|
editStylesRef.current?.base.remove();
|
|
4094
6028
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -4114,6 +6048,7 @@ function OhhwellsBridge() {
|
|
|
4114
6048
|
[data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
|
|
4115
6049
|
[data-ohw-editable="image"], [data-ohw-editable="image"] *,
|
|
4116
6050
|
[data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
|
|
6051
|
+
[data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
|
|
4117
6052
|
[data-ohw-hovered]:not([contenteditable]) {
|
|
4118
6053
|
outline: 2px dashed ${PRIMARY} !important;
|
|
4119
6054
|
outline-offset: 4px;
|
|
@@ -4151,8 +6086,21 @@ function OhhwellsBridge() {
|
|
|
4151
6086
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
4152
6087
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
4153
6088
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
6089
|
+
if (isInsideLinkEditor(target)) return;
|
|
4154
6090
|
const editable = target.closest("[data-ohw-editable]");
|
|
4155
6091
|
if (editable) {
|
|
6092
|
+
if (editable.dataset.ohwEditable === "link") {
|
|
6093
|
+
e.preventDefault();
|
|
6094
|
+
e.stopPropagation();
|
|
6095
|
+
deactivateRef.current();
|
|
6096
|
+
bumpLinkPopoverGrace();
|
|
6097
|
+
setLinkPopoverRef.current({
|
|
6098
|
+
key: editable.dataset.ohwKey ?? "",
|
|
6099
|
+
target: getLinkHref(editable),
|
|
6100
|
+
rect: editable.getBoundingClientRect()
|
|
6101
|
+
});
|
|
6102
|
+
return;
|
|
6103
|
+
}
|
|
4156
6104
|
if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
|
|
4157
6105
|
e.preventDefault();
|
|
4158
6106
|
e.stopPropagation();
|
|
@@ -4173,6 +6121,15 @@ function OhhwellsBridge() {
|
|
|
4173
6121
|
}
|
|
4174
6122
|
const anchor = target.closest("a");
|
|
4175
6123
|
if (anchor) e.preventDefault();
|
|
6124
|
+
if (Date.now() < linkPopoverGraceUntilRef.current) return;
|
|
6125
|
+
if (linkPopoverOpenRef.current && activeElRef.current) {
|
|
6126
|
+
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6127
|
+
if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
|
|
6128
|
+
}
|
|
6129
|
+
if (linkPopoverOpenRef.current) {
|
|
6130
|
+
setLinkPopoverRef.current(null);
|
|
6131
|
+
return;
|
|
6132
|
+
}
|
|
4176
6133
|
deactivateRef.current();
|
|
4177
6134
|
};
|
|
4178
6135
|
const handleMouseOver = (e) => {
|
|
@@ -4348,6 +6305,15 @@ function OhhwellsBridge() {
|
|
|
4348
6305
|
textEditable.setAttribute("data-ohw-hovered", "");
|
|
4349
6306
|
return;
|
|
4350
6307
|
}
|
|
6308
|
+
if (hoveredGapRef.current) {
|
|
6309
|
+
if (hoveredImageRef.current) {
|
|
6310
|
+
hoveredImageRef.current = null;
|
|
6311
|
+
resumeAnimTracks();
|
|
6312
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
6313
|
+
}
|
|
6314
|
+
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
6315
|
+
return;
|
|
6316
|
+
}
|
|
4351
6317
|
document.querySelectorAll("[data-ohw-hovered]").forEach((el) => el.removeAttribute("data-ohw-hovered"));
|
|
4352
6318
|
if (imgEl !== hoveredImageRef.current || hoveredImageHasTextOverlapRef.current) {
|
|
4353
6319
|
hoveredImageRef.current = imgEl;
|
|
@@ -4413,8 +6379,30 @@ function OhhwellsBridge() {
|
|
|
4413
6379
|
}
|
|
4414
6380
|
}
|
|
4415
6381
|
};
|
|
6382
|
+
const probeSectionGapAt = (clientX, clientY, fromParentViewport = false) => {
|
|
6383
|
+
const { y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
6384
|
+
const sections = Array.from(document.querySelectorAll("[data-ohw-section]")).sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
|
|
6385
|
+
const ZONE = 20;
|
|
6386
|
+
for (let i = 0; i < sections.length; i++) {
|
|
6387
|
+
const a = sections[i];
|
|
6388
|
+
const b = sections[i + 1] ?? null;
|
|
6389
|
+
const boundaryY = a.getBoundingClientRect().bottom;
|
|
6390
|
+
if (Math.abs(y - boundaryY) <= ZONE) {
|
|
6391
|
+
const insertAfter = a.dataset.ohwSection ?? "";
|
|
6392
|
+
const insertBefore = b?.dataset.ohwSection ?? null;
|
|
6393
|
+
hoveredGapRef.current = { insertAfter, insertBefore };
|
|
6394
|
+
setSectionGap({ insertAfter, insertBefore, y: boundaryY });
|
|
6395
|
+
return;
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
if (hoveredGapRef.current) {
|
|
6399
|
+
hoveredGapRef.current = null;
|
|
6400
|
+
setSectionGap(null);
|
|
6401
|
+
}
|
|
6402
|
+
};
|
|
4416
6403
|
const handleMouseMove = (e) => {
|
|
4417
6404
|
const { clientX, clientY } = e;
|
|
6405
|
+
probeSectionGapAt(clientX, clientY);
|
|
4418
6406
|
probeImageAt(clientX, clientY);
|
|
4419
6407
|
probeHoverCardsAt(clientX, clientY);
|
|
4420
6408
|
};
|
|
@@ -4422,6 +6410,7 @@ function OhhwellsBridge() {
|
|
|
4422
6410
|
if (e.data?.type !== "ow:pointer-sync") return;
|
|
4423
6411
|
const { clientX, clientY } = e.data;
|
|
4424
6412
|
if (typeof clientX !== "number" || typeof clientY !== "number") return;
|
|
6413
|
+
probeSectionGapAt(clientX, clientY);
|
|
4425
6414
|
probeImageAt(clientX, clientY);
|
|
4426
6415
|
probeHoverCardsAt(clientX, clientY);
|
|
4427
6416
|
};
|
|
@@ -4579,27 +6568,47 @@ function OhhwellsBridge() {
|
|
|
4579
6568
|
if (e.data?.type !== "ow:hydrate") return;
|
|
4580
6569
|
const content = e.data.content;
|
|
4581
6570
|
if (!content) return;
|
|
6571
|
+
let sectionsJson = null;
|
|
4582
6572
|
for (const [key, val] of Object.entries(content)) {
|
|
6573
|
+
if (key === "__ohw_sections") {
|
|
6574
|
+
sectionsJson = val;
|
|
6575
|
+
continue;
|
|
6576
|
+
}
|
|
4583
6577
|
document.querySelectorAll(`[data-ohw-key="${key}"]`).forEach((el) => {
|
|
4584
6578
|
if (el.dataset.ohwEditable === "image") {
|
|
4585
6579
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
4586
6580
|
if (img) img.src = val;
|
|
4587
6581
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
4588
6582
|
el.style.backgroundImage = `url('${val}')`;
|
|
6583
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
6584
|
+
applyLinkHref(el, val);
|
|
4589
6585
|
} else {
|
|
4590
6586
|
el.innerHTML = val;
|
|
4591
6587
|
}
|
|
4592
6588
|
});
|
|
6589
|
+
applyLinkByKey(key, val);
|
|
6590
|
+
}
|
|
6591
|
+
if (sectionsJson) {
|
|
6592
|
+
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
4593
6593
|
}
|
|
4594
6594
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
4595
6595
|
};
|
|
4596
6596
|
window.addEventListener("message", handleHydrate);
|
|
4597
6597
|
const handleDeactivate = (e) => {
|
|
4598
6598
|
if (e.data?.type !== "ow:deactivate") return;
|
|
6599
|
+
if (Date.now() < linkPopoverGraceUntilRef.current) return;
|
|
6600
|
+
if (linkPopoverOpenRef.current) {
|
|
6601
|
+
setLinkPopoverRef.current(null);
|
|
6602
|
+
return;
|
|
6603
|
+
}
|
|
4599
6604
|
deactivateRef.current();
|
|
4600
6605
|
};
|
|
4601
6606
|
window.addEventListener("message", handleDeactivate);
|
|
4602
6607
|
const handleKeyDown = (e) => {
|
|
6608
|
+
if (e.key === "Escape" && linkPopoverOpenRef.current) {
|
|
6609
|
+
setLinkPopoverRef.current(null);
|
|
6610
|
+
return;
|
|
6611
|
+
}
|
|
4603
6612
|
if (e.key !== "Escape") return;
|
|
4604
6613
|
const el = activeElRef.current;
|
|
4605
6614
|
if (el && originalContentRef.current !== null) {
|
|
@@ -4634,7 +6643,63 @@ function OhhwellsBridge() {
|
|
|
4634
6643
|
};
|
|
4635
6644
|
const handleSave = (e) => {
|
|
4636
6645
|
if (e.data?.type !== "ow:save") return;
|
|
4637
|
-
|
|
6646
|
+
const nodes = collectEditableNodes();
|
|
6647
|
+
const tracker = document.querySelector("[data-ohw-sections-tracker]");
|
|
6648
|
+
if (tracker?.textContent) nodes.push({ key: "__ohw_sections", type: "sections", text: tracker.textContent });
|
|
6649
|
+
postToParentRef.current({ type: "ow:save-result", nodes });
|
|
6650
|
+
};
|
|
6651
|
+
const handleInsertSection = (e) => {
|
|
6652
|
+
if (e.data?.type !== "ow:insert-section") return;
|
|
6653
|
+
const { widgetType, insertAfter, insertBefore } = e.data;
|
|
6654
|
+
if (widgetType !== "scheduling") return;
|
|
6655
|
+
const inserted = mountSchedulingWidget(insertAfter, true, void 0, insertBefore ?? null);
|
|
6656
|
+
if (inserted) {
|
|
6657
|
+
const tracker = getSectionsTracker();
|
|
6658
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent ?? "[]" }] });
|
|
6659
|
+
const h = document.documentElement.scrollHeight;
|
|
6660
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
6661
|
+
}
|
|
6662
|
+
};
|
|
6663
|
+
const handleSwitchSchedule = (e) => {
|
|
6664
|
+
if (e.data?.type !== "ow:switch-schedule") return;
|
|
6665
|
+
const scheduleId = e.data.scheduleId;
|
|
6666
|
+
const insertAfter = e.data.insertAfter;
|
|
6667
|
+
if (!scheduleId || !insertAfter) return;
|
|
6668
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
6669
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6670
|
+
};
|
|
6671
|
+
const handleScheduleLinked = (e) => {
|
|
6672
|
+
if (e.data?.type !== "ow:schedule-linked") return;
|
|
6673
|
+
const scheduleId = e.data.scheduleId;
|
|
6674
|
+
const insertAfter = e.data.insertAfter;
|
|
6675
|
+
if (!scheduleId || !insertAfter) return;
|
|
6676
|
+
const text = updateSectionScheduleId(insertAfter, scheduleId);
|
|
6677
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6678
|
+
};
|
|
6679
|
+
const handleClearSchedulingWidget = (e) => {
|
|
6680
|
+
if (e.data?.type !== "ow:clear-scheduling-widget") return;
|
|
6681
|
+
const insertAfter = e.data.insertAfter;
|
|
6682
|
+
if (!insertAfter) return;
|
|
6683
|
+
const text = updateSectionScheduleId(insertAfter, null);
|
|
6684
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text }] });
|
|
6685
|
+
};
|
|
6686
|
+
const handleRemoveSchedulingSection = (e) => {
|
|
6687
|
+
if (e.data?.type !== "ow:remove-scheduling-section") return;
|
|
6688
|
+
document.querySelectorAll('[data-ohw-section-container="scheduling"]').forEach((el) => {
|
|
6689
|
+
el.remove();
|
|
6690
|
+
});
|
|
6691
|
+
const tracker = getSectionsTracker();
|
|
6692
|
+
let sections = [];
|
|
6693
|
+
try {
|
|
6694
|
+
sections = JSON.parse(tracker.textContent || "[]");
|
|
6695
|
+
} catch {
|
|
6696
|
+
}
|
|
6697
|
+
const currentPath = window.location.pathname;
|
|
6698
|
+
const updated = sections.filter((s) => !(s.type === "scheduling" && s.pagePath === currentPath));
|
|
6699
|
+
tracker.textContent = JSON.stringify(updated);
|
|
6700
|
+
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
6701
|
+
const h = document.documentElement.scrollHeight;
|
|
6702
|
+
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
4638
6703
|
};
|
|
4639
6704
|
const handleSelectionChange = () => {
|
|
4640
6705
|
if (!activeElRef.current) return;
|
|
@@ -4739,6 +6804,11 @@ function OhhwellsBridge() {
|
|
|
4739
6804
|
deactivateRef.current();
|
|
4740
6805
|
};
|
|
4741
6806
|
window.addEventListener("message", handleSave);
|
|
6807
|
+
window.addEventListener("message", handleInsertSection);
|
|
6808
|
+
window.addEventListener("message", handleSwitchSchedule);
|
|
6809
|
+
window.addEventListener("message", handleScheduleLinked);
|
|
6810
|
+
window.addEventListener("message", handleClearSchedulingWidget);
|
|
6811
|
+
window.addEventListener("message", handleRemoveSchedulingSection);
|
|
4742
6812
|
window.addEventListener("message", handleImageUrl);
|
|
4743
6813
|
window.addEventListener("message", handleAnimLock);
|
|
4744
6814
|
window.addEventListener("message", handleCanvasHeight);
|
|
@@ -4773,6 +6843,11 @@ function OhhwellsBridge() {
|
|
|
4773
6843
|
document.removeEventListener("selectionchange", handleSelectionChange);
|
|
4774
6844
|
window.removeEventListener("scroll", handleScroll, true);
|
|
4775
6845
|
window.removeEventListener("message", handleSave);
|
|
6846
|
+
window.removeEventListener("message", handleInsertSection);
|
|
6847
|
+
window.removeEventListener("message", handleSwitchSchedule);
|
|
6848
|
+
window.removeEventListener("message", handleScheduleLinked);
|
|
6849
|
+
window.removeEventListener("message", handleClearSchedulingWidget);
|
|
6850
|
+
window.removeEventListener("message", handleRemoveSchedulingSection);
|
|
4776
6851
|
window.removeEventListener("message", handleImageUrl);
|
|
4777
6852
|
window.removeEventListener("message", handleAnimLock);
|
|
4778
6853
|
window.removeEventListener("message", handleCanvasHeight);
|
|
@@ -4787,7 +6862,7 @@ function OhhwellsBridge() {
|
|
|
4787
6862
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
4788
6863
|
};
|
|
4789
6864
|
}, [isEditMode, refreshStateRules]);
|
|
4790
|
-
(0,
|
|
6865
|
+
(0, import_react5.useEffect)(() => {
|
|
4791
6866
|
if (!isEditMode) return;
|
|
4792
6867
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
4793
6868
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -4802,20 +6877,26 @@ function OhhwellsBridge() {
|
|
|
4802
6877
|
const raf = requestAnimationFrame(() => refreshStateRules());
|
|
4803
6878
|
const timer = setTimeout(() => {
|
|
4804
6879
|
refreshStateRules();
|
|
4805
|
-
|
|
6880
|
+
const sections = collectSections();
|
|
6881
|
+
setSectionsByPath((prev) => {
|
|
6882
|
+
const next = { ...prev, [pathname]: sections };
|
|
6883
|
+
return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
|
|
6884
|
+
});
|
|
6885
|
+
postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
|
|
6886
|
+
postToParent({ type: "ow:request-site-pages" });
|
|
4806
6887
|
}, 150);
|
|
4807
6888
|
return () => {
|
|
4808
6889
|
cancelAnimationFrame(raf);
|
|
4809
6890
|
clearTimeout(timer);
|
|
4810
6891
|
};
|
|
4811
6892
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
4812
|
-
const handleCommand = (0,
|
|
6893
|
+
const handleCommand = (0, import_react5.useCallback)((cmd) => {
|
|
4813
6894
|
document.execCommand(cmd, false);
|
|
4814
6895
|
activeElRef.current?.focus();
|
|
4815
6896
|
if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
|
|
4816
6897
|
refreshActiveCommandsRef.current();
|
|
4817
6898
|
}, []);
|
|
4818
|
-
const handleStateChange = (0,
|
|
6899
|
+
const handleStateChange = (0, import_react5.useCallback)((state) => {
|
|
4819
6900
|
if (!activeStateElRef.current) return;
|
|
4820
6901
|
const el = activeStateElRef.current;
|
|
4821
6902
|
if (state === "Default") {
|
|
@@ -4828,13 +6909,48 @@ function OhhwellsBridge() {
|
|
|
4828
6909
|
}
|
|
4829
6910
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
4830
6911
|
}, [deactivate]);
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
6912
|
+
const closeLinkPopover = (0, import_react5.useCallback)(() => setLinkPopover(null), []);
|
|
6913
|
+
const openLinkPopoverForActive = (0, import_react5.useCallback)(() => {
|
|
6914
|
+
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6915
|
+
if (!hrefCtx) return;
|
|
6916
|
+
bumpLinkPopoverGrace();
|
|
6917
|
+
setLinkPopover({
|
|
6918
|
+
key: hrefCtx.key,
|
|
6919
|
+
target: getLinkHref(hrefCtx.anchor),
|
|
6920
|
+
rect: hrefCtx.anchor.getBoundingClientRect()
|
|
6921
|
+
});
|
|
6922
|
+
}, []);
|
|
6923
|
+
const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
|
|
6924
|
+
(target) => {
|
|
6925
|
+
if (!linkPopover) return;
|
|
6926
|
+
const { key } = linkPopover;
|
|
6927
|
+
applyLinkByKey(key, target);
|
|
6928
|
+
postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
|
|
6929
|
+
setLinkPopover(null);
|
|
6930
|
+
},
|
|
6931
|
+
[linkPopover, postToParent]
|
|
6932
|
+
);
|
|
6933
|
+
const showEditLink = toolbarShowEditLink;
|
|
6934
|
+
const currentSections = sectionsByPath[pathname] ?? [];
|
|
6935
|
+
linkPopoverOpenRef.current = linkPopover !== null;
|
|
6936
|
+
return bridgeRoot ? (0, import_react_dom2.createPortal)(
|
|
6937
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
6938
|
+
toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
6939
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
6940
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6941
|
+
FloatingToolbar,
|
|
6942
|
+
{
|
|
6943
|
+
rect: toolbarRect,
|
|
6944
|
+
parentScroll: parentScrollRef.current,
|
|
6945
|
+
elRef: toolbarElRef,
|
|
6946
|
+
onCommand: handleCommand,
|
|
6947
|
+
activeCommands,
|
|
6948
|
+
showEditLink,
|
|
6949
|
+
onEditLink: openLinkPopoverForActive
|
|
6950
|
+
}
|
|
6951
|
+
)
|
|
4836
6952
|
] }),
|
|
4837
|
-
maxBadge && /* @__PURE__ */ (0,
|
|
6953
|
+
maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4838
6954
|
"div",
|
|
4839
6955
|
{
|
|
4840
6956
|
"data-ohw-max-badge": "",
|
|
@@ -4860,7 +6976,7 @@ function OhhwellsBridge() {
|
|
|
4860
6976
|
]
|
|
4861
6977
|
}
|
|
4862
6978
|
),
|
|
4863
|
-
toggleState && /* @__PURE__ */ (0,
|
|
6979
|
+
toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4864
6980
|
StateToggle,
|
|
4865
6981
|
{
|
|
4866
6982
|
rect: toggleState.rect,
|
|
@@ -4868,6 +6984,47 @@ function OhhwellsBridge() {
|
|
|
4868
6984
|
states: toggleState.states,
|
|
4869
6985
|
onStateChange: handleStateChange
|
|
4870
6986
|
}
|
|
6987
|
+
),
|
|
6988
|
+
linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6989
|
+
LinkPopover,
|
|
6990
|
+
{
|
|
6991
|
+
rect: linkPopover.rect,
|
|
6992
|
+
parentScroll: parentScrollRef.current,
|
|
6993
|
+
panelRef: linkPopoverPanelRef,
|
|
6994
|
+
open: true,
|
|
6995
|
+
mode: "edit",
|
|
6996
|
+
pages: sitePages,
|
|
6997
|
+
sections: currentSections,
|
|
6998
|
+
sectionsByPath,
|
|
6999
|
+
initialTarget: linkPopover.target,
|
|
7000
|
+
onClose: closeLinkPopover,
|
|
7001
|
+
onSubmit: handleLinkPopoverSubmit
|
|
7002
|
+
}
|
|
7003
|
+
) : null,
|
|
7004
|
+
sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
7005
|
+
"div",
|
|
7006
|
+
{
|
|
7007
|
+
"data-ohw-section-insert-line": "",
|
|
7008
|
+
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
7009
|
+
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
7010
|
+
children: [
|
|
7011
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
7012
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
7013
|
+
Badge,
|
|
7014
|
+
{
|
|
7015
|
+
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",
|
|
7016
|
+
onClick: () => {
|
|
7017
|
+
window.parent.postMessage(
|
|
7018
|
+
{ type: "ow:add-section", insertAfter: sectionGap.insertAfter, insertBefore: sectionGap.insertBefore },
|
|
7019
|
+
"*"
|
|
7020
|
+
);
|
|
7021
|
+
},
|
|
7022
|
+
children: "Add Section"
|
|
7023
|
+
}
|
|
7024
|
+
),
|
|
7025
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
7026
|
+
]
|
|
7027
|
+
}
|
|
4871
7028
|
)
|
|
4872
7029
|
] }),
|
|
4873
7030
|
bridgeRoot
|
|
@@ -4875,10 +7032,19 @@ function OhhwellsBridge() {
|
|
|
4875
7032
|
}
|
|
4876
7033
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4877
7034
|
0 && (module.exports = {
|
|
7035
|
+
LinkEditorPanel,
|
|
7036
|
+
LinkPopover,
|
|
4878
7037
|
OhhwellsBridge,
|
|
7038
|
+
SchedulingWidget,
|
|
4879
7039
|
Toggle,
|
|
4880
7040
|
ToggleGroup,
|
|
4881
7041
|
ToggleGroupItem,
|
|
4882
|
-
|
|
7042
|
+
buildTarget,
|
|
7043
|
+
filterAvailablePages,
|
|
7044
|
+
getEditModeInitialState,
|
|
7045
|
+
isValidUrl,
|
|
7046
|
+
parseTarget,
|
|
7047
|
+
toggleVariants,
|
|
7048
|
+
validateUrlInput
|
|
4883
7049
|
});
|
|
4884
7050
|
//# sourceMappingURL=index.cjs.map
|