@ohhwells/bridge 0.1.24 → 0.1.26
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 +304 -327
- package/dist/index.cjs +1315 -271
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -5
- package/dist/index.d.ts +65 -5
- package/dist/index.js +1305 -269
- package/dist/index.js.map +1 -1
- package/dist/styles.css +322 -2
- package/package.json +48 -48
package/dist/index.cjs
CHANGED
|
@@ -31,17 +31,25 @@ 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,
|
|
35
37
|
SchedulingWidget: () => SchedulingWidget,
|
|
36
38
|
Toggle: () => Toggle,
|
|
37
39
|
ToggleGroup: () => ToggleGroup,
|
|
38
40
|
ToggleGroupItem: () => ToggleGroupItem,
|
|
39
|
-
|
|
41
|
+
buildTarget: () => buildTarget,
|
|
42
|
+
filterAvailablePages: () => filterAvailablePages,
|
|
43
|
+
getEditModeInitialState: () => getEditModeInitialState,
|
|
44
|
+
isValidUrl: () => isValidUrl,
|
|
45
|
+
parseTarget: () => parseTarget,
|
|
46
|
+
toggleVariants: () => toggleVariants,
|
|
47
|
+
validateUrlInput: () => validateUrlInput
|
|
40
48
|
});
|
|
41
49
|
module.exports = __toCommonJS(index_exports);
|
|
42
50
|
|
|
43
51
|
// src/OhhwellsBridge.tsx
|
|
44
|
-
var
|
|
52
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
45
53
|
var import_client = require("react-dom/client");
|
|
46
54
|
var import_react_dom = require("react-dom");
|
|
47
55
|
|
|
@@ -204,6 +212,13 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
|
204
212
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
205
213
|
var API_URL = process.env.NEXT_PUBLIC_FLOWOPS_API_URL ?? "https://flowops-backend-staging-2yfdh7pwpq-as.a.run.app";
|
|
206
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
|
+
}
|
|
207
222
|
function classRunsOnDate(cls, date, type) {
|
|
208
223
|
if (type === "FIXED") {
|
|
209
224
|
const d = String(date.getDate()).padStart(2, "0");
|
|
@@ -249,10 +264,10 @@ function buildTimezoneLabel(tz) {
|
|
|
249
264
|
return tz;
|
|
250
265
|
}
|
|
251
266
|
}
|
|
252
|
-
function EmptyState({ inEditor
|
|
267
|
+
function EmptyState({ inEditor }) {
|
|
253
268
|
const handleAddSchedule = () => {
|
|
254
269
|
if (inEditor) {
|
|
255
|
-
window.parent.postMessage({ type: "ow:scheduling-not-connected"
|
|
270
|
+
window.parent.postMessage({ type: "ow:scheduling-not-connected" }, "*");
|
|
256
271
|
}
|
|
257
272
|
};
|
|
258
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: [
|
|
@@ -477,21 +492,18 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
477
492
|
const booked = getBookingsOnDate(cls, selectedDate);
|
|
478
493
|
const available = cls.maxParticipants - booked;
|
|
479
494
|
const isFull = available <= 0;
|
|
480
|
-
const isPrivate = cls.maxParticipants === 1;
|
|
481
495
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
|
|
482
496
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-4 py-4 sm:items-center sm:gap-[60px] box-border", children: [
|
|
483
497
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-2 shrink-0 sm:contents", children: [
|
|
484
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
)
|
|
494
|
-
] }),
|
|
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
|
+
),
|
|
495
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: [
|
|
496
508
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
497
509
|
available,
|
|
@@ -564,9 +576,7 @@ function CalendarFoldIcon() {
|
|
|
564
576
|
}
|
|
565
577
|
);
|
|
566
578
|
}
|
|
567
|
-
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter
|
|
568
|
-
const autoId = (0, import_react2.useId)();
|
|
569
|
-
const insertAfter = insertAfterProp ?? autoId;
|
|
579
|
+
function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAfter }) {
|
|
570
580
|
const [schedule, setSchedule] = (0, import_react2.useState)(null);
|
|
571
581
|
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
572
582
|
const [inEditor, setInEditor] = (0, import_react2.useState)(false);
|
|
@@ -596,99 +606,88 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
596
606
|
if (typeof window === "undefined") return;
|
|
597
607
|
const isInEditor = window.self !== window.top;
|
|
598
608
|
setInEditor(isInEditor);
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (!initialized) {
|
|
606
|
-
initialized = true;
|
|
607
|
-
setLoading(false);
|
|
608
|
-
}
|
|
609
|
-
}, 5e3) : null;
|
|
610
|
-
const handler = (e) => {
|
|
611
|
-
if (e.data?.type === "ow:clear-scheduling-widget") {
|
|
612
|
-
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
613
|
-
setSchedule(null);
|
|
614
|
-
setLoading(false);
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
if (e.data?.type === "ow:switch-schedule") {
|
|
618
|
-
if (!e.data.insertAfter || e.data.insertAfter !== insertAfter) return;
|
|
619
|
-
switchScheduleIdRef.current = e.data.scheduleId ?? null;
|
|
620
|
-
initialized = false;
|
|
621
|
-
setLoading(true);
|
|
622
|
-
window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
if (e.data?.type !== "ow:user-schedules-response") return;
|
|
626
|
-
if (e.data.insertAfter && e.data.insertAfter !== insertAfter) return;
|
|
627
|
-
if (initialized && switchScheduleIdRef.current === null) return;
|
|
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) {
|
|
628
615
|
initialized = true;
|
|
629
|
-
if (timer) clearTimeout(timer);
|
|
630
|
-
const schedules = e.data.schedules ?? [];
|
|
631
|
-
const isSwitching = switchScheduleIdRef.current !== null;
|
|
632
|
-
const target = isSwitching ? schedules.find((s) => s.id === switchScheduleIdRef.current) ?? schedules[0] ?? null : effectiveId ? schedules.find((s) => s.id === effectiveId) ?? schedules[0] ?? null : schedules[0] ?? null;
|
|
633
|
-
switchScheduleIdRef.current = null;
|
|
634
|
-
setSchedule(target);
|
|
635
616
|
setLoading(false);
|
|
636
|
-
if (notifyOnConnect && target && !isSwitching) {
|
|
637
|
-
window.parent.postMessage({
|
|
638
|
-
type: "ow:schedule-connected",
|
|
639
|
-
schedule: { id: target.id, name: target.name },
|
|
640
|
-
insertAfter
|
|
641
|
-
}, "*");
|
|
642
|
-
window.postMessage({ type: "ow:schedule-linked", scheduleId: target.id, insertAfter }, "*");
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
window.addEventListener("message", handler);
|
|
646
|
-
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
647
|
-
return () => {
|
|
648
|
-
window.removeEventListener("message", handler);
|
|
649
|
-
if (timer) clearTimeout(timer);
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
if (!effectiveId) {
|
|
653
|
-
setLoading(false);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
;
|
|
657
|
-
(async () => {
|
|
658
|
-
try {
|
|
659
|
-
const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
|
|
660
|
-
const data = await res.json();
|
|
661
|
-
setSchedule(data?.id ? data : null);
|
|
662
|
-
} catch {
|
|
663
617
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
let innerCleanup;
|
|
670
|
-
const timer = setTimeout(() => {
|
|
671
|
-
if (!done) {
|
|
672
|
-
done = true;
|
|
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);
|
|
673
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 }, "*");
|
|
674
652
|
}
|
|
675
|
-
}, 2e3);
|
|
676
|
-
const configHandler = (e) => {
|
|
677
|
-
if (e.data?.type !== "ow:schedule-config" || e.data.insertAfter !== insertAfter || done) return;
|
|
678
|
-
done = true;
|
|
679
|
-
clearTimeout(timer);
|
|
680
|
-
window.removeEventListener("message", configHandler);
|
|
681
|
-
innerCleanup = loadWithId(e.data.scheduleId ?? null);
|
|
682
653
|
};
|
|
683
|
-
window.addEventListener("message",
|
|
684
|
-
window.postMessage({ type: "ow:request-
|
|
654
|
+
window.addEventListener("message", handler);
|
|
655
|
+
if (!startEmpty) window.parent.postMessage({ type: "ow:request-user-schedules", insertAfter }, "*");
|
|
685
656
|
return () => {
|
|
686
|
-
window.removeEventListener("message",
|
|
687
|
-
clearTimeout(timer);
|
|
688
|
-
innerCleanup?.();
|
|
657
|
+
window.removeEventListener("message", handler);
|
|
658
|
+
if (timer) clearTimeout(timer);
|
|
689
659
|
};
|
|
690
660
|
}
|
|
691
|
-
|
|
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
|
+
})();
|
|
692
691
|
}, []);
|
|
693
692
|
const timezoneLabel = schedule?.timezone ? (() => {
|
|
694
693
|
try {
|
|
@@ -723,7 +722,6 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
723
722
|
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
724
723
|
}
|
|
725
724
|
};
|
|
726
|
-
if (!inEditor && !loading && !schedule) return null;
|
|
727
725
|
const sectionId = `scheduling-${insertAfter}`;
|
|
728
726
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
729
727
|
"section",
|
|
@@ -769,7 +767,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
769
767
|
] }),
|
|
770
768
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
771
769
|
timezoneLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm font-normal text-(--color-accent,#A89B83)", children: timezoneLabel }),
|
|
772
|
-
/* @__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
|
|
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)(
|
|
773
771
|
ScheduleView,
|
|
774
772
|
{
|
|
775
773
|
schedule,
|
|
@@ -4225,8 +4223,844 @@ function isEditSessionActive() {
|
|
|
4225
4223
|
return new URLSearchParams(q).get("mode") === "edit";
|
|
4226
4224
|
}
|
|
4227
4225
|
|
|
4228
|
-
// src/ui/
|
|
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");
|
|
4229
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");
|
|
4230
5064
|
var badgeVariants = cva(
|
|
4231
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",
|
|
4232
5066
|
{
|
|
@@ -4244,11 +5078,11 @@ var badgeVariants = cva(
|
|
|
4244
5078
|
}
|
|
4245
5079
|
);
|
|
4246
5080
|
function Badge({ className, variant, ...props }) {
|
|
4247
|
-
return /* @__PURE__ */ (0,
|
|
5081
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn(badgeVariants({ variant }), className), ...props });
|
|
4248
5082
|
}
|
|
4249
5083
|
|
|
4250
5084
|
// src/OhhwellsBridge.tsx
|
|
4251
|
-
var
|
|
5085
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
4252
5086
|
var PRIMARY = "#0885FE";
|
|
4253
5087
|
var IMAGE_FADE_MS = 300;
|
|
4254
5088
|
function runOpacityFade(el, onDone) {
|
|
@@ -4388,14 +5222,9 @@ function initSectionsFromContent(content, removeExisting = false) {
|
|
|
4388
5222
|
const inEditor = typeof window !== "undefined" && window.self !== window.top;
|
|
4389
5223
|
if (inEditor) getSectionsTracker().textContent = raw;
|
|
4390
5224
|
const pageEntries = getPageSchedulingEntries(raw);
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry));
|
|
4395
|
-
setTimeout(() => retryMissingSchedulingMounts(pageEntries, notifyForEntry), 250);
|
|
4396
|
-
for (const entry of preExisting) {
|
|
4397
|
-
window.postMessage({ type: "ow:schedule-config", insertAfter: entry.insertAfter, scheduleId: entry.scheduleId ?? null }, "*");
|
|
4398
|
-
}
|
|
5225
|
+
mountSchedulingEntries(pageEntries, false);
|
|
5226
|
+
requestAnimationFrame(() => retryMissingSchedulingMounts(pageEntries, false));
|
|
5227
|
+
setTimeout(() => retryMissingSchedulingMounts(pageEntries, false), 250);
|
|
4399
5228
|
} catch {
|
|
4400
5229
|
}
|
|
4401
5230
|
}
|
|
@@ -4422,7 +5251,7 @@ function mountSchedulingWidget(insertAfter, notifyOnConnect = false, scheduleId,
|
|
|
4422
5251
|
const root = (0, import_client.createRoot)(container);
|
|
4423
5252
|
(0, import_react_dom.flushSync)(() => {
|
|
4424
5253
|
root.render(
|
|
4425
|
-
/* @__PURE__ */ (0,
|
|
5254
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4426
5255
|
SchedulingWidget,
|
|
4427
5256
|
{
|
|
4428
5257
|
notifyOnConnect,
|
|
@@ -4454,16 +5283,23 @@ function mountSchedulingEntries(entries, notifyOnConnect = false) {
|
|
|
4454
5283
|
const pending = entries.filter((e) => e.type === "scheduling").sort((a, b) => schedulingMountDepth(a.insertAfter) - schedulingMountDepth(b.insertAfter));
|
|
4455
5284
|
for (let attempt = 0; attempt < pending.length + 1 && pending.length > 0; attempt++) {
|
|
4456
5285
|
for (let i = pending.length - 1; i >= 0; i--) {
|
|
4457
|
-
const
|
|
4458
|
-
|
|
4459
|
-
if (mountSchedulingWidget(entry.insertAfter, shouldNotify, entry.scheduleId)) {
|
|
5286
|
+
const { insertAfter, scheduleId } = pending[i];
|
|
5287
|
+
if (mountSchedulingWidget(insertAfter, notifyOnConnect, scheduleId)) {
|
|
4460
5288
|
pending.splice(i, 1);
|
|
4461
5289
|
}
|
|
4462
5290
|
}
|
|
4463
5291
|
}
|
|
4464
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
|
+
}
|
|
4465
5301
|
function collectEditableNodes() {
|
|
4466
|
-
|
|
5302
|
+
const nodes = Array.from(document.querySelectorAll("[data-ohw-editable]")).map((el) => {
|
|
4467
5303
|
if (el.dataset.ohwEditable === "image") {
|
|
4468
5304
|
const img = el instanceof HTMLImageElement ? el : el.querySelector("img");
|
|
4469
5305
|
return { key: el.dataset.ohwKey ?? "", type: "image", text: img?.src ?? "" };
|
|
@@ -4473,12 +5309,52 @@ function collectEditableNodes() {
|
|
|
4473
5309
|
const url = raw.replace(/^url\(['"]?/, "").replace(/['"]?\)$/, "");
|
|
4474
5310
|
return { key: el.dataset.ohwKey ?? "", type: "bg-image", text: url };
|
|
4475
5311
|
}
|
|
5312
|
+
if (el.dataset.ohwEditable === "link") {
|
|
5313
|
+
return { key: el.dataset.ohwKey ?? "", type: "link", text: getLinkHref(el) };
|
|
5314
|
+
}
|
|
4476
5315
|
return {
|
|
4477
5316
|
key: el.dataset.ohwKey ?? "",
|
|
4478
5317
|
type: el.dataset.ohwEditable ?? "text",
|
|
4479
5318
|
text: el.dataset.ohwEditable === "plain" ? el.innerText ?? "" : el.innerHTML
|
|
4480
5319
|
};
|
|
4481
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
|
+
});
|
|
4482
5358
|
}
|
|
4483
5359
|
var FORCE_PSEUDO_STATES = [
|
|
4484
5360
|
{ pseudo: ":hover", state: "hover" },
|
|
@@ -4619,7 +5495,7 @@ var TOOLBAR_GROUPS = [
|
|
|
4619
5495
|
];
|
|
4620
5496
|
function GlowFrame({ rect, elRef }) {
|
|
4621
5497
|
const GAP = 6;
|
|
4622
|
-
return /* @__PURE__ */ (0,
|
|
5498
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4623
5499
|
"div",
|
|
4624
5500
|
{
|
|
4625
5501
|
ref: elRef,
|
|
@@ -4667,10 +5543,12 @@ function FloatingToolbar({
|
|
|
4667
5543
|
parentScroll,
|
|
4668
5544
|
elRef,
|
|
4669
5545
|
onCommand,
|
|
4670
|
-
activeCommands
|
|
5546
|
+
activeCommands,
|
|
5547
|
+
showEditLink,
|
|
5548
|
+
onEditLink
|
|
4671
5549
|
}) {
|
|
4672
5550
|
const { top, left, transform } = calcToolbarPos(rect, parentScroll);
|
|
4673
|
-
return /* @__PURE__ */ (0,
|
|
5551
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
4674
5552
|
"div",
|
|
4675
5553
|
{
|
|
4676
5554
|
ref: elRef,
|
|
@@ -4693,56 +5571,97 @@ function FloatingToolbar({
|
|
|
4693
5571
|
pointerEvents: "auto",
|
|
4694
5572
|
whiteSpace: "nowrap"
|
|
4695
5573
|
},
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
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
|
+
)
|
|
4725
5621
|
},
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
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();
|
|
4741
5639
|
},
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
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
|
+
]
|
|
4746
5665
|
}
|
|
4747
5666
|
);
|
|
4748
5667
|
}
|
|
@@ -4756,7 +5675,7 @@ function StateToggle({
|
|
|
4756
5675
|
states,
|
|
4757
5676
|
onStateChange
|
|
4758
5677
|
}) {
|
|
4759
|
-
return /* @__PURE__ */ (0,
|
|
5678
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
4760
5679
|
ToggleGroup,
|
|
4761
5680
|
{
|
|
4762
5681
|
"data-ohw-state-toggle": "",
|
|
@@ -4770,7 +5689,7 @@ function StateToggle({
|
|
|
4770
5689
|
left: rect.right - 8,
|
|
4771
5690
|
transform: "translateX(-100%)"
|
|
4772
5691
|
},
|
|
4773
|
-
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))
|
|
4774
5693
|
}
|
|
4775
5694
|
);
|
|
4776
5695
|
}
|
|
@@ -4797,8 +5716,8 @@ function OhhwellsBridge() {
|
|
|
4797
5716
|
const router = (0, import_navigation.useRouter)();
|
|
4798
5717
|
const searchParams = (0, import_navigation.useSearchParams)();
|
|
4799
5718
|
const isEditMode = isEditSessionActive();
|
|
4800
|
-
const [bridgeRoot, setBridgeRoot] = (0,
|
|
4801
|
-
(0,
|
|
5719
|
+
const [bridgeRoot, setBridgeRoot] = (0, import_react5.useState)(null);
|
|
5720
|
+
(0, import_react5.useEffect)(() => {
|
|
4802
5721
|
const figtreeFontId = "ohw-figtree-font";
|
|
4803
5722
|
if (!document.getElementById(figtreeFontId)) {
|
|
4804
5723
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -4822,41 +5741,83 @@ function OhhwellsBridge() {
|
|
|
4822
5741
|
}, []);
|
|
4823
5742
|
const subdomainFromQuery = searchParams.get("subdomain");
|
|
4824
5743
|
const subdomain = resolveSubdomain(subdomainFromQuery);
|
|
4825
|
-
const postToParent = (0,
|
|
5744
|
+
const postToParent = (0, import_react5.useCallback)((data) => {
|
|
4826
5745
|
if (typeof window !== "undefined" && window.parent !== window) {
|
|
4827
5746
|
window.parent.postMessage(data, "*");
|
|
4828
5747
|
}
|
|
4829
5748
|
}, []);
|
|
4830
|
-
const [fetchState, setFetchState] = (0,
|
|
4831
|
-
const autoSaveTimers = (0,
|
|
4832
|
-
const activeElRef = (0,
|
|
4833
|
-
const originalContentRef = (0,
|
|
4834
|
-
const activeStateElRef = (0,
|
|
4835
|
-
const parentScrollRef = (0,
|
|
4836
|
-
const toolbarElRef = (0,
|
|
4837
|
-
const glowElRef = (0,
|
|
4838
|
-
const hoveredImageRef = (0,
|
|
4839
|
-
const hoveredImageHasTextOverlapRef = (0,
|
|
4840
|
-
const hoveredGapRef = (0,
|
|
4841
|
-
const imageUnhoverTimerRef = (0,
|
|
4842
|
-
const imageShowTimerRef = (0,
|
|
4843
|
-
const editStylesRef = (0,
|
|
4844
|
-
const activateRef = (0,
|
|
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)(() => {
|
|
4845
5764
|
});
|
|
4846
|
-
const deactivateRef = (0,
|
|
5765
|
+
const deactivateRef = (0, import_react5.useRef)(() => {
|
|
4847
5766
|
});
|
|
4848
|
-
const refreshActiveCommandsRef = (0,
|
|
5767
|
+
const refreshActiveCommandsRef = (0, import_react5.useRef)(() => {
|
|
4849
5768
|
});
|
|
4850
|
-
const postToParentRef = (0,
|
|
5769
|
+
const postToParentRef = (0, import_react5.useRef)(postToParent);
|
|
4851
5770
|
postToParentRef.current = postToParent;
|
|
4852
|
-
const
|
|
4853
|
-
const
|
|
4854
|
-
const [
|
|
4855
|
-
const [
|
|
4856
|
-
const [
|
|
4857
|
-
const [
|
|
4858
|
-
const [
|
|
4859
|
-
(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 (!linkPopover) return;
|
|
5789
|
+
if (hoveredImageRef.current) {
|
|
5790
|
+
hoveredImageRef.current = null;
|
|
5791
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
5792
|
+
}
|
|
5793
|
+
postToParent({ type: "ow:image-unhover" });
|
|
5794
|
+
}, [linkPopover, postToParent]);
|
|
5795
|
+
(0, import_react5.useEffect)(() => {
|
|
5796
|
+
if (!isEditMode) return;
|
|
5797
|
+
const useFixtures = shouldUseDevFixtures();
|
|
5798
|
+
if (useFixtures) {
|
|
5799
|
+
setSitePages(DEV_SITE_PAGES);
|
|
5800
|
+
setSectionsByPath(DEV_SECTIONS_BY_PATH);
|
|
5801
|
+
if (process.env.NODE_ENV === "development") {
|
|
5802
|
+
console.info("[ohhwells-bridge] Dev fixtures loaded (ohw-fixtures=1)");
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
const onSitePages = (e) => {
|
|
5806
|
+
if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
|
|
5807
|
+
if (useFixtures) return;
|
|
5808
|
+
setSitePages(
|
|
5809
|
+
e.data.pages.map((p) => ({
|
|
5810
|
+
path: p.path,
|
|
5811
|
+
title: p.title
|
|
5812
|
+
}))
|
|
5813
|
+
);
|
|
5814
|
+
};
|
|
5815
|
+
window.addEventListener("message", onSitePages);
|
|
5816
|
+
if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
|
|
5817
|
+
return () => window.removeEventListener("message", onSitePages);
|
|
5818
|
+
}, [isEditMode, postToParent]);
|
|
5819
|
+
const [sectionGap, setSectionGap] = (0, import_react5.useState)(null);
|
|
5820
|
+
(0, import_react5.useEffect)(() => {
|
|
4860
5821
|
const update = () => {
|
|
4861
5822
|
const el = activeElRef.current;
|
|
4862
5823
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -4880,33 +5841,10 @@ function OhhwellsBridge() {
|
|
|
4880
5841
|
vvp.removeEventListener("resize", update);
|
|
4881
5842
|
};
|
|
4882
5843
|
}, []);
|
|
4883
|
-
const refreshStateRules = (0,
|
|
5844
|
+
const refreshStateRules = (0, import_react5.useCallback)(() => {
|
|
4884
5845
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
4885
5846
|
}, []);
|
|
4886
|
-
const
|
|
4887
|
-
const tracker = getSectionsTracker();
|
|
4888
|
-
let entries = [];
|
|
4889
|
-
try {
|
|
4890
|
-
entries = JSON.parse(tracker.textContent || "[]");
|
|
4891
|
-
} catch {
|
|
4892
|
-
}
|
|
4893
|
-
const path = window.location.pathname;
|
|
4894
|
-
const entry = entries.find(
|
|
4895
|
-
(e) => e.type === "scheduling" && e.insertAfter === insertAfterVal && (!e.pagePath || e.pagePath === path)
|
|
4896
|
-
);
|
|
4897
|
-
if (entry) {
|
|
4898
|
-
window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: entry.scheduleId ?? null }, "*");
|
|
4899
|
-
return;
|
|
4900
|
-
}
|
|
4901
|
-
const newEntry = { type: "scheduling", insertAfter: insertAfterVal, pagePath: path };
|
|
4902
|
-
entries.push(newEntry);
|
|
4903
|
-
tracker.textContent = JSON.stringify(entries);
|
|
4904
|
-
if (isEditMode) {
|
|
4905
|
-
postToParentRef.current({ type: "ow:change", nodes: [{ key: "__ohw_sections", text: tracker.textContent }] });
|
|
4906
|
-
}
|
|
4907
|
-
window.postMessage({ type: "ow:schedule-config", insertAfter: insertAfterVal, scheduleId: null }, "*");
|
|
4908
|
-
}, [isEditMode]);
|
|
4909
|
-
const deactivate = (0, import_react3.useCallback)(() => {
|
|
5847
|
+
const deactivate = (0, import_react5.useCallback)(() => {
|
|
4910
5848
|
const el = activeElRef.current;
|
|
4911
5849
|
if (!el) return;
|
|
4912
5850
|
const key = el.dataset.ohwKey;
|
|
@@ -4929,9 +5867,10 @@ function OhhwellsBridge() {
|
|
|
4929
5867
|
setToolbarRect(null);
|
|
4930
5868
|
setMaxBadge(null);
|
|
4931
5869
|
setActiveCommands(/* @__PURE__ */ new Set());
|
|
5870
|
+
setToolbarShowEditLink(false);
|
|
4932
5871
|
postToParent({ type: "ow:exit-edit" });
|
|
4933
5872
|
}, [postToParent]);
|
|
4934
|
-
const activate = (0,
|
|
5873
|
+
const activate = (0, import_react5.useCallback)((el) => {
|
|
4935
5874
|
if (activeElRef.current === el) return;
|
|
4936
5875
|
deactivate();
|
|
4937
5876
|
if (hoveredImageRef.current) {
|
|
@@ -4944,12 +5883,13 @@ function OhhwellsBridge() {
|
|
|
4944
5883
|
originalContentRef.current = el.innerHTML;
|
|
4945
5884
|
el.focus();
|
|
4946
5885
|
setToolbarRect(el.getBoundingClientRect());
|
|
5886
|
+
setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
|
|
4947
5887
|
postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
|
|
4948
5888
|
requestAnimationFrame(() => refreshActiveCommandsRef.current());
|
|
4949
5889
|
}, [deactivate, postToParent]);
|
|
4950
5890
|
activateRef.current = activate;
|
|
4951
5891
|
deactivateRef.current = deactivate;
|
|
4952
|
-
(0,
|
|
5892
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
4953
5893
|
if (!subdomain || isEditMode) {
|
|
4954
5894
|
setFetchState("done");
|
|
4955
5895
|
return;
|
|
@@ -4971,14 +5911,15 @@ function OhhwellsBridge() {
|
|
|
4971
5911
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
4972
5912
|
const next = `url('${val}')`;
|
|
4973
5913
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
5914
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
5915
|
+
applyLinkHref(el, val);
|
|
4974
5916
|
} else if (el.innerHTML !== val) {
|
|
4975
5917
|
el.innerHTML = val;
|
|
4976
5918
|
}
|
|
4977
5919
|
});
|
|
5920
|
+
applyLinkByKey(key, val);
|
|
4978
5921
|
}
|
|
4979
5922
|
initSectionsFromContent(content, true);
|
|
4980
|
-
sectionsLoadedRef.current = true;
|
|
4981
|
-
pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
|
|
4982
5923
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
4983
5924
|
}) : Promise.resolve();
|
|
4984
5925
|
};
|
|
@@ -5003,7 +5944,7 @@ function OhhwellsBridge() {
|
|
|
5003
5944
|
cancelled = true;
|
|
5004
5945
|
};
|
|
5005
5946
|
}, [subdomain, isEditMode, pathname]);
|
|
5006
|
-
(0,
|
|
5947
|
+
(0, import_react5.useEffect)(() => {
|
|
5007
5948
|
if (!subdomain || isEditMode) return;
|
|
5008
5949
|
let debounceTimer = null;
|
|
5009
5950
|
const applyFromCache = () => {
|
|
@@ -5019,10 +5960,13 @@ function OhhwellsBridge() {
|
|
|
5019
5960
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
5020
5961
|
const next = `url('${val}')`;
|
|
5021
5962
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
5963
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
5964
|
+
applyLinkHref(el, val);
|
|
5022
5965
|
} else if (el.innerHTML !== val) {
|
|
5023
5966
|
el.innerHTML = val;
|
|
5024
5967
|
}
|
|
5025
5968
|
});
|
|
5969
|
+
applyLinkByKey(key, val);
|
|
5026
5970
|
}
|
|
5027
5971
|
};
|
|
5028
5972
|
const scheduleApply = () => {
|
|
@@ -5037,16 +5981,16 @@ function OhhwellsBridge() {
|
|
|
5037
5981
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
5038
5982
|
};
|
|
5039
5983
|
}, [subdomain, isEditMode, pathname]);
|
|
5040
|
-
(0,
|
|
5984
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
5041
5985
|
const el = document.getElementById("ohw-loader");
|
|
5042
5986
|
if (!el) return;
|
|
5043
5987
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
5044
5988
|
el.style.display = visible ? "flex" : "none";
|
|
5045
5989
|
}, [subdomain, fetchState]);
|
|
5046
|
-
(0,
|
|
5990
|
+
(0, import_react5.useEffect)(() => {
|
|
5047
5991
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
5048
5992
|
}, [pathname, postToParent]);
|
|
5049
|
-
(0,
|
|
5993
|
+
(0, import_react5.useEffect)(() => {
|
|
5050
5994
|
if (!isEditMode) return;
|
|
5051
5995
|
const measure = () => {
|
|
5052
5996
|
const h = document.body.scrollHeight;
|
|
@@ -5070,7 +6014,7 @@ function OhhwellsBridge() {
|
|
|
5070
6014
|
window.removeEventListener("resize", handleResize);
|
|
5071
6015
|
};
|
|
5072
6016
|
}, [pathname, isEditMode, postToParent]);
|
|
5073
|
-
(0,
|
|
6017
|
+
(0, import_react5.useEffect)(() => {
|
|
5074
6018
|
if (!subdomainFromQuery || isEditMode) return;
|
|
5075
6019
|
const handleClick = (e) => {
|
|
5076
6020
|
const anchor = e.target.closest("a");
|
|
@@ -5086,7 +6030,7 @@ function OhhwellsBridge() {
|
|
|
5086
6030
|
document.addEventListener("click", handleClick, true);
|
|
5087
6031
|
return () => document.removeEventListener("click", handleClick, true);
|
|
5088
6032
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
5089
|
-
(0,
|
|
6033
|
+
(0, import_react5.useEffect)(() => {
|
|
5090
6034
|
if (!isEditMode) {
|
|
5091
6035
|
editStylesRef.current?.base.remove();
|
|
5092
6036
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -5112,6 +6056,7 @@ function OhhwellsBridge() {
|
|
|
5112
6056
|
[data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
|
|
5113
6057
|
[data-ohw-editable="image"], [data-ohw-editable="image"] *,
|
|
5114
6058
|
[data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
|
|
6059
|
+
[data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
|
|
5115
6060
|
[data-ohw-hovered]:not([contenteditable]) {
|
|
5116
6061
|
outline: 2px dashed ${PRIMARY} !important;
|
|
5117
6062
|
outline-offset: 4px;
|
|
@@ -5149,8 +6094,21 @@ function OhhwellsBridge() {
|
|
|
5149
6094
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
5150
6095
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
5151
6096
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
6097
|
+
if (isInsideLinkEditor(target)) return;
|
|
5152
6098
|
const editable = target.closest("[data-ohw-editable]");
|
|
5153
6099
|
if (editable) {
|
|
6100
|
+
if (editable.dataset.ohwEditable === "link") {
|
|
6101
|
+
e.preventDefault();
|
|
6102
|
+
e.stopPropagation();
|
|
6103
|
+
deactivateRef.current();
|
|
6104
|
+
bumpLinkPopoverGrace();
|
|
6105
|
+
setLinkPopoverRef.current({
|
|
6106
|
+
key: editable.dataset.ohwKey ?? "",
|
|
6107
|
+
target: getLinkHref(editable),
|
|
6108
|
+
rect: editable.getBoundingClientRect()
|
|
6109
|
+
});
|
|
6110
|
+
return;
|
|
6111
|
+
}
|
|
5154
6112
|
if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
|
|
5155
6113
|
e.preventDefault();
|
|
5156
6114
|
e.stopPropagation();
|
|
@@ -5171,6 +6129,15 @@ function OhhwellsBridge() {
|
|
|
5171
6129
|
}
|
|
5172
6130
|
const anchor = target.closest("a");
|
|
5173
6131
|
if (anchor) e.preventDefault();
|
|
6132
|
+
if (Date.now() < linkPopoverGraceUntilRef.current) return;
|
|
6133
|
+
if (linkPopoverOpenRef.current && activeElRef.current) {
|
|
6134
|
+
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6135
|
+
if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
|
|
6136
|
+
}
|
|
6137
|
+
if (linkPopoverOpenRef.current) {
|
|
6138
|
+
setLinkPopoverRef.current(null);
|
|
6139
|
+
return;
|
|
6140
|
+
}
|
|
5174
6141
|
deactivateRef.current();
|
|
5175
6142
|
};
|
|
5176
6143
|
const handleMouseOver = (e) => {
|
|
@@ -5277,6 +6244,15 @@ function OhhwellsBridge() {
|
|
|
5277
6244
|
return smallest;
|
|
5278
6245
|
};
|
|
5279
6246
|
const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
|
|
6247
|
+
if (linkPopoverOpenRef.current) {
|
|
6248
|
+
if (hoveredImageRef.current) {
|
|
6249
|
+
hoveredImageRef.current = null;
|
|
6250
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
6251
|
+
resumeAnimTracks();
|
|
6252
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
6253
|
+
}
|
|
6254
|
+
return;
|
|
6255
|
+
}
|
|
5280
6256
|
const toggleEl = document.querySelector("[data-ohw-state-toggle]");
|
|
5281
6257
|
if (toggleEl) {
|
|
5282
6258
|
const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
@@ -5302,6 +6278,15 @@ function OhhwellsBridge() {
|
|
|
5302
6278
|
}
|
|
5303
6279
|
return;
|
|
5304
6280
|
}
|
|
6281
|
+
if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]")) {
|
|
6282
|
+
if (hoveredImageRef.current) {
|
|
6283
|
+
hoveredImageRef.current = null;
|
|
6284
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
6285
|
+
resumeAnimTracks();
|
|
6286
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
6287
|
+
}
|
|
6288
|
+
return;
|
|
6289
|
+
}
|
|
5305
6290
|
const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
|
|
5306
6291
|
const textEditable = Array.from(
|
|
5307
6292
|
document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
|
|
@@ -5621,25 +6606,35 @@ function OhhwellsBridge() {
|
|
|
5621
6606
|
if (img) img.src = val;
|
|
5622
6607
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
5623
6608
|
el.style.backgroundImage = `url('${val}')`;
|
|
6609
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
6610
|
+
applyLinkHref(el, val);
|
|
5624
6611
|
} else {
|
|
5625
6612
|
el.innerHTML = val;
|
|
5626
6613
|
}
|
|
5627
6614
|
});
|
|
6615
|
+
applyLinkByKey(key, val);
|
|
5628
6616
|
}
|
|
5629
6617
|
if (sectionsJson) {
|
|
5630
6618
|
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
5631
|
-
sectionsLoadedRef.current = true;
|
|
5632
|
-
pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
|
|
5633
6619
|
}
|
|
5634
6620
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
5635
6621
|
};
|
|
5636
6622
|
window.addEventListener("message", handleHydrate);
|
|
5637
6623
|
const handleDeactivate = (e) => {
|
|
5638
6624
|
if (e.data?.type !== "ow:deactivate") return;
|
|
6625
|
+
if (Date.now() < linkPopoverGraceUntilRef.current) return;
|
|
6626
|
+
if (linkPopoverOpenRef.current) {
|
|
6627
|
+
setLinkPopoverRef.current(null);
|
|
6628
|
+
return;
|
|
6629
|
+
}
|
|
5639
6630
|
deactivateRef.current();
|
|
5640
6631
|
};
|
|
5641
6632
|
window.addEventListener("message", handleDeactivate);
|
|
5642
6633
|
const handleKeyDown = (e) => {
|
|
6634
|
+
if (e.key === "Escape" && linkPopoverOpenRef.current) {
|
|
6635
|
+
setLinkPopoverRef.current(null);
|
|
6636
|
+
return;
|
|
6637
|
+
}
|
|
5643
6638
|
if (e.key !== "Escape") return;
|
|
5644
6639
|
const el = activeElRef.current;
|
|
5645
6640
|
if (el && originalContentRef.current !== null) {
|
|
@@ -5893,23 +6888,7 @@ function OhhwellsBridge() {
|
|
|
5893
6888
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
5894
6889
|
};
|
|
5895
6890
|
}, [isEditMode, refreshStateRules]);
|
|
5896
|
-
(0,
|
|
5897
|
-
const handler = (e) => {
|
|
5898
|
-
if (e.data?.type !== "ow:request-schedule-config") return;
|
|
5899
|
-
const insertAfterVal = e.data.insertAfter;
|
|
5900
|
-
if (!insertAfterVal) return;
|
|
5901
|
-
if (!sectionsLoadedRef.current) {
|
|
5902
|
-
if (!pendingScheduleConfigRequests.current.includes(insertAfterVal)) {
|
|
5903
|
-
pendingScheduleConfigRequests.current.push(insertAfterVal);
|
|
5904
|
-
}
|
|
5905
|
-
return;
|
|
5906
|
-
}
|
|
5907
|
-
processConfigRequest(insertAfterVal);
|
|
5908
|
-
};
|
|
5909
|
-
window.addEventListener("message", handler);
|
|
5910
|
-
return () => window.removeEventListener("message", handler);
|
|
5911
|
-
}, [processConfigRequest]);
|
|
5912
|
-
(0, import_react3.useEffect)(() => {
|
|
6891
|
+
(0, import_react5.useEffect)(() => {
|
|
5913
6892
|
if (!isEditMode) return;
|
|
5914
6893
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
5915
6894
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -5924,20 +6903,26 @@ function OhhwellsBridge() {
|
|
|
5924
6903
|
const raf = requestAnimationFrame(() => refreshStateRules());
|
|
5925
6904
|
const timer = setTimeout(() => {
|
|
5926
6905
|
refreshStateRules();
|
|
5927
|
-
|
|
6906
|
+
const sections = collectSections();
|
|
6907
|
+
setSectionsByPath((prev) => {
|
|
6908
|
+
const next = { ...prev, [pathname]: sections };
|
|
6909
|
+
return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
|
|
6910
|
+
});
|
|
6911
|
+
postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
|
|
6912
|
+
postToParent({ type: "ow:request-site-pages" });
|
|
5928
6913
|
}, 150);
|
|
5929
6914
|
return () => {
|
|
5930
6915
|
cancelAnimationFrame(raf);
|
|
5931
6916
|
clearTimeout(timer);
|
|
5932
6917
|
};
|
|
5933
6918
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
5934
|
-
const handleCommand = (0,
|
|
6919
|
+
const handleCommand = (0, import_react5.useCallback)((cmd) => {
|
|
5935
6920
|
document.execCommand(cmd, false);
|
|
5936
6921
|
activeElRef.current?.focus();
|
|
5937
6922
|
if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
|
|
5938
6923
|
refreshActiveCommandsRef.current();
|
|
5939
6924
|
}, []);
|
|
5940
|
-
const handleStateChange = (0,
|
|
6925
|
+
const handleStateChange = (0, import_react5.useCallback)((state) => {
|
|
5941
6926
|
if (!activeStateElRef.current) return;
|
|
5942
6927
|
const el = activeStateElRef.current;
|
|
5943
6928
|
if (state === "Default") {
|
|
@@ -5950,13 +6935,48 @@ function OhhwellsBridge() {
|
|
|
5950
6935
|
}
|
|
5951
6936
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
5952
6937
|
}, [deactivate]);
|
|
6938
|
+
const closeLinkPopover = (0, import_react5.useCallback)(() => setLinkPopover(null), []);
|
|
6939
|
+
const openLinkPopoverForActive = (0, import_react5.useCallback)(() => {
|
|
6940
|
+
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6941
|
+
if (!hrefCtx) return;
|
|
6942
|
+
bumpLinkPopoverGrace();
|
|
6943
|
+
setLinkPopover({
|
|
6944
|
+
key: hrefCtx.key,
|
|
6945
|
+
target: getLinkHref(hrefCtx.anchor),
|
|
6946
|
+
rect: hrefCtx.anchor.getBoundingClientRect()
|
|
6947
|
+
});
|
|
6948
|
+
}, []);
|
|
6949
|
+
const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
|
|
6950
|
+
(target) => {
|
|
6951
|
+
if (!linkPopover) return;
|
|
6952
|
+
const { key } = linkPopover;
|
|
6953
|
+
applyLinkByKey(key, target);
|
|
6954
|
+
postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
|
|
6955
|
+
setLinkPopover(null);
|
|
6956
|
+
},
|
|
6957
|
+
[linkPopover, postToParent]
|
|
6958
|
+
);
|
|
6959
|
+
const showEditLink = toolbarShowEditLink;
|
|
6960
|
+
const currentSections = sectionsByPath[pathname] ?? [];
|
|
6961
|
+
linkPopoverOpenRef.current = linkPopover !== null;
|
|
5953
6962
|
return bridgeRoot ? (0, import_react_dom2.createPortal)(
|
|
5954
|
-
/* @__PURE__ */ (0,
|
|
5955
|
-
toolbarRect && /* @__PURE__ */ (0,
|
|
5956
|
-
/* @__PURE__ */ (0,
|
|
5957
|
-
/* @__PURE__ */ (0,
|
|
6963
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
6964
|
+
toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
6965
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
6966
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6967
|
+
FloatingToolbar,
|
|
6968
|
+
{
|
|
6969
|
+
rect: toolbarRect,
|
|
6970
|
+
parentScroll: parentScrollRef.current,
|
|
6971
|
+
elRef: toolbarElRef,
|
|
6972
|
+
onCommand: handleCommand,
|
|
6973
|
+
activeCommands,
|
|
6974
|
+
showEditLink,
|
|
6975
|
+
onEditLink: openLinkPopoverForActive
|
|
6976
|
+
}
|
|
6977
|
+
)
|
|
5958
6978
|
] }),
|
|
5959
|
-
maxBadge && /* @__PURE__ */ (0,
|
|
6979
|
+
maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
5960
6980
|
"div",
|
|
5961
6981
|
{
|
|
5962
6982
|
"data-ohw-max-badge": "",
|
|
@@ -5982,7 +7002,7 @@ function OhhwellsBridge() {
|
|
|
5982
7002
|
]
|
|
5983
7003
|
}
|
|
5984
7004
|
),
|
|
5985
|
-
toggleState && /* @__PURE__ */ (0,
|
|
7005
|
+
toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5986
7006
|
StateToggle,
|
|
5987
7007
|
{
|
|
5988
7008
|
rect: toggleState.rect,
|
|
@@ -5991,15 +7011,31 @@ function OhhwellsBridge() {
|
|
|
5991
7011
|
onStateChange: handleStateChange
|
|
5992
7012
|
}
|
|
5993
7013
|
),
|
|
5994
|
-
|
|
7014
|
+
linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
7015
|
+
LinkPopover,
|
|
7016
|
+
{
|
|
7017
|
+
rect: linkPopover.rect,
|
|
7018
|
+
parentScroll: parentScrollRef.current,
|
|
7019
|
+
panelRef: linkPopoverPanelRef,
|
|
7020
|
+
open: true,
|
|
7021
|
+
mode: "edit",
|
|
7022
|
+
pages: sitePages,
|
|
7023
|
+
sections: currentSections,
|
|
7024
|
+
sectionsByPath,
|
|
7025
|
+
initialTarget: linkPopover.target,
|
|
7026
|
+
onClose: closeLinkPopover,
|
|
7027
|
+
onSubmit: handleLinkPopoverSubmit
|
|
7028
|
+
}
|
|
7029
|
+
) : null,
|
|
7030
|
+
sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
5995
7031
|
"div",
|
|
5996
7032
|
{
|
|
5997
7033
|
"data-ohw-section-insert-line": "",
|
|
5998
7034
|
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
5999
7035
|
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
6000
7036
|
children: [
|
|
6001
|
-
/* @__PURE__ */ (0,
|
|
6002
|
-
/* @__PURE__ */ (0,
|
|
7037
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
7038
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6003
7039
|
Badge,
|
|
6004
7040
|
{
|
|
6005
7041
|
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",
|
|
@@ -6012,7 +7048,7 @@ function OhhwellsBridge() {
|
|
|
6012
7048
|
children: "Add Section"
|
|
6013
7049
|
}
|
|
6014
7050
|
),
|
|
6015
|
-
/* @__PURE__ */ (0,
|
|
7051
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
6016
7052
|
]
|
|
6017
7053
|
}
|
|
6018
7054
|
)
|
|
@@ -6022,11 +7058,19 @@ function OhhwellsBridge() {
|
|
|
6022
7058
|
}
|
|
6023
7059
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6024
7060
|
0 && (module.exports = {
|
|
7061
|
+
LinkEditorPanel,
|
|
7062
|
+
LinkPopover,
|
|
6025
7063
|
OhhwellsBridge,
|
|
6026
7064
|
SchedulingWidget,
|
|
6027
7065
|
Toggle,
|
|
6028
7066
|
ToggleGroup,
|
|
6029
7067
|
ToggleGroupItem,
|
|
6030
|
-
|
|
7068
|
+
buildTarget,
|
|
7069
|
+
filterAvailablePages,
|
|
7070
|
+
getEditModeInitialState,
|
|
7071
|
+
isValidUrl,
|
|
7072
|
+
parseTarget,
|
|
7073
|
+
toggleVariants,
|
|
7074
|
+
validateUrlInput
|
|
6031
7075
|
});
|
|
6032
7076
|
//# sourceMappingURL=index.cjs.map
|