@ohhwells/bridge 0.1.24 → 0.1.25
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 +1289 -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 +1279 -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();
|
|
5639
|
+
},
|
|
5640
|
+
onMouseEnter: (e) => {
|
|
5641
|
+
e.currentTarget.style.background = "#F5F5F4";
|
|
5642
|
+
},
|
|
5643
|
+
onMouseLeave: (e) => {
|
|
5644
|
+
e.currentTarget.style.background = "transparent";
|
|
4741
5645
|
},
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
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,75 @@ 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 (!isEditMode) return;
|
|
5789
|
+
const useFixtures = shouldUseDevFixtures();
|
|
5790
|
+
if (useFixtures) {
|
|
5791
|
+
setSitePages(DEV_SITE_PAGES);
|
|
5792
|
+
setSectionsByPath(DEV_SECTIONS_BY_PATH);
|
|
5793
|
+
if (process.env.NODE_ENV === "development") {
|
|
5794
|
+
console.info("[ohhwells-bridge] Dev fixtures loaded (ohw-fixtures=1)");
|
|
5795
|
+
}
|
|
5796
|
+
}
|
|
5797
|
+
const onSitePages = (e) => {
|
|
5798
|
+
if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
|
|
5799
|
+
if (useFixtures) return;
|
|
5800
|
+
setSitePages(
|
|
5801
|
+
e.data.pages.map((p) => ({
|
|
5802
|
+
path: p.path,
|
|
5803
|
+
title: p.title
|
|
5804
|
+
}))
|
|
5805
|
+
);
|
|
5806
|
+
};
|
|
5807
|
+
window.addEventListener("message", onSitePages);
|
|
5808
|
+
if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
|
|
5809
|
+
return () => window.removeEventListener("message", onSitePages);
|
|
5810
|
+
}, [isEditMode, postToParent]);
|
|
5811
|
+
const [sectionGap, setSectionGap] = (0, import_react5.useState)(null);
|
|
5812
|
+
(0, import_react5.useEffect)(() => {
|
|
4860
5813
|
const update = () => {
|
|
4861
5814
|
const el = activeElRef.current;
|
|
4862
5815
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -4880,33 +5833,10 @@ function OhhwellsBridge() {
|
|
|
4880
5833
|
vvp.removeEventListener("resize", update);
|
|
4881
5834
|
};
|
|
4882
5835
|
}, []);
|
|
4883
|
-
const refreshStateRules = (0,
|
|
5836
|
+
const refreshStateRules = (0, import_react5.useCallback)(() => {
|
|
4884
5837
|
editStylesRef.current?.forceHover && (editStylesRef.current.forceHover.textContent = collectStateRules());
|
|
4885
5838
|
}, []);
|
|
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)(() => {
|
|
5839
|
+
const deactivate = (0, import_react5.useCallback)(() => {
|
|
4910
5840
|
const el = activeElRef.current;
|
|
4911
5841
|
if (!el) return;
|
|
4912
5842
|
const key = el.dataset.ohwKey;
|
|
@@ -4929,9 +5859,10 @@ function OhhwellsBridge() {
|
|
|
4929
5859
|
setToolbarRect(null);
|
|
4930
5860
|
setMaxBadge(null);
|
|
4931
5861
|
setActiveCommands(/* @__PURE__ */ new Set());
|
|
5862
|
+
setToolbarShowEditLink(false);
|
|
4932
5863
|
postToParent({ type: "ow:exit-edit" });
|
|
4933
5864
|
}, [postToParent]);
|
|
4934
|
-
const activate = (0,
|
|
5865
|
+
const activate = (0, import_react5.useCallback)((el) => {
|
|
4935
5866
|
if (activeElRef.current === el) return;
|
|
4936
5867
|
deactivate();
|
|
4937
5868
|
if (hoveredImageRef.current) {
|
|
@@ -4944,12 +5875,13 @@ function OhhwellsBridge() {
|
|
|
4944
5875
|
originalContentRef.current = el.innerHTML;
|
|
4945
5876
|
el.focus();
|
|
4946
5877
|
setToolbarRect(el.getBoundingClientRect());
|
|
5878
|
+
setToolbarShowEditLink(Boolean(getHrefKeyFromElement(el)));
|
|
4947
5879
|
postToParent({ type: "ow:enter-edit", key: el.dataset.ohwKey });
|
|
4948
5880
|
requestAnimationFrame(() => refreshActiveCommandsRef.current());
|
|
4949
5881
|
}, [deactivate, postToParent]);
|
|
4950
5882
|
activateRef.current = activate;
|
|
4951
5883
|
deactivateRef.current = deactivate;
|
|
4952
|
-
(0,
|
|
5884
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
4953
5885
|
if (!subdomain || isEditMode) {
|
|
4954
5886
|
setFetchState("done");
|
|
4955
5887
|
return;
|
|
@@ -4971,14 +5903,15 @@ function OhhwellsBridge() {
|
|
|
4971
5903
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
4972
5904
|
const next = `url('${val}')`;
|
|
4973
5905
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
5906
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
5907
|
+
applyLinkHref(el, val);
|
|
4974
5908
|
} else if (el.innerHTML !== val) {
|
|
4975
5909
|
el.innerHTML = val;
|
|
4976
5910
|
}
|
|
4977
5911
|
});
|
|
5912
|
+
applyLinkByKey(key, val);
|
|
4978
5913
|
}
|
|
4979
5914
|
initSectionsFromContent(content, true);
|
|
4980
|
-
sectionsLoadedRef.current = true;
|
|
4981
|
-
pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
|
|
4982
5915
|
return imageLoads.length > 0 ? Promise.all(imageLoads).then(() => {
|
|
4983
5916
|
}) : Promise.resolve();
|
|
4984
5917
|
};
|
|
@@ -5003,7 +5936,7 @@ function OhhwellsBridge() {
|
|
|
5003
5936
|
cancelled = true;
|
|
5004
5937
|
};
|
|
5005
5938
|
}, [subdomain, isEditMode, pathname]);
|
|
5006
|
-
(0,
|
|
5939
|
+
(0, import_react5.useEffect)(() => {
|
|
5007
5940
|
if (!subdomain || isEditMode) return;
|
|
5008
5941
|
let debounceTimer = null;
|
|
5009
5942
|
const applyFromCache = () => {
|
|
@@ -5019,10 +5952,13 @@ function OhhwellsBridge() {
|
|
|
5019
5952
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
5020
5953
|
const next = `url('${val}')`;
|
|
5021
5954
|
if (el.style.backgroundImage !== next) el.style.backgroundImage = next;
|
|
5955
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
5956
|
+
applyLinkHref(el, val);
|
|
5022
5957
|
} else if (el.innerHTML !== val) {
|
|
5023
5958
|
el.innerHTML = val;
|
|
5024
5959
|
}
|
|
5025
5960
|
});
|
|
5961
|
+
applyLinkByKey(key, val);
|
|
5026
5962
|
}
|
|
5027
5963
|
};
|
|
5028
5964
|
const scheduleApply = () => {
|
|
@@ -5037,16 +5973,16 @@ function OhhwellsBridge() {
|
|
|
5037
5973
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
5038
5974
|
};
|
|
5039
5975
|
}, [subdomain, isEditMode, pathname]);
|
|
5040
|
-
(0,
|
|
5976
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
5041
5977
|
const el = document.getElementById("ohw-loader");
|
|
5042
5978
|
if (!el) return;
|
|
5043
5979
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
5044
5980
|
el.style.display = visible ? "flex" : "none";
|
|
5045
5981
|
}, [subdomain, fetchState]);
|
|
5046
|
-
(0,
|
|
5982
|
+
(0, import_react5.useEffect)(() => {
|
|
5047
5983
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
5048
5984
|
}, [pathname, postToParent]);
|
|
5049
|
-
(0,
|
|
5985
|
+
(0, import_react5.useEffect)(() => {
|
|
5050
5986
|
if (!isEditMode) return;
|
|
5051
5987
|
const measure = () => {
|
|
5052
5988
|
const h = document.body.scrollHeight;
|
|
@@ -5070,7 +6006,7 @@ function OhhwellsBridge() {
|
|
|
5070
6006
|
window.removeEventListener("resize", handleResize);
|
|
5071
6007
|
};
|
|
5072
6008
|
}, [pathname, isEditMode, postToParent]);
|
|
5073
|
-
(0,
|
|
6009
|
+
(0, import_react5.useEffect)(() => {
|
|
5074
6010
|
if (!subdomainFromQuery || isEditMode) return;
|
|
5075
6011
|
const handleClick = (e) => {
|
|
5076
6012
|
const anchor = e.target.closest("a");
|
|
@@ -5086,7 +6022,7 @@ function OhhwellsBridge() {
|
|
|
5086
6022
|
document.addEventListener("click", handleClick, true);
|
|
5087
6023
|
return () => document.removeEventListener("click", handleClick, true);
|
|
5088
6024
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
5089
|
-
(0,
|
|
6025
|
+
(0, import_react5.useEffect)(() => {
|
|
5090
6026
|
if (!isEditMode) {
|
|
5091
6027
|
editStylesRef.current?.base.remove();
|
|
5092
6028
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -5112,6 +6048,7 @@ function OhhwellsBridge() {
|
|
|
5112
6048
|
[data-ohw-editable]:not([contenteditable]):not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"]) { cursor: text !important; }
|
|
5113
6049
|
[data-ohw-editable="image"], [data-ohw-editable="image"] *,
|
|
5114
6050
|
[data-ohw-editable="bg-image"], [data-ohw-editable="bg-image"] * { cursor: pointer !important; }
|
|
6051
|
+
[data-ohw-editable="link"], [data-ohw-editable="link"] * { cursor: pointer !important; }
|
|
5115
6052
|
[data-ohw-hovered]:not([contenteditable]) {
|
|
5116
6053
|
outline: 2px dashed ${PRIMARY} !important;
|
|
5117
6054
|
outline-offset: 4px;
|
|
@@ -5149,8 +6086,21 @@ function OhhwellsBridge() {
|
|
|
5149
6086
|
if (target.closest("[data-ohw-toolbar]")) return;
|
|
5150
6087
|
if (target.closest("[data-ohw-state-toggle]")) return;
|
|
5151
6088
|
if (target.closest("[data-ohw-max-badge]")) return;
|
|
6089
|
+
if (isInsideLinkEditor(target)) return;
|
|
5152
6090
|
const editable = target.closest("[data-ohw-editable]");
|
|
5153
6091
|
if (editable) {
|
|
6092
|
+
if (editable.dataset.ohwEditable === "link") {
|
|
6093
|
+
e.preventDefault();
|
|
6094
|
+
e.stopPropagation();
|
|
6095
|
+
deactivateRef.current();
|
|
6096
|
+
bumpLinkPopoverGrace();
|
|
6097
|
+
setLinkPopoverRef.current({
|
|
6098
|
+
key: editable.dataset.ohwKey ?? "",
|
|
6099
|
+
target: getLinkHref(editable),
|
|
6100
|
+
rect: editable.getBoundingClientRect()
|
|
6101
|
+
});
|
|
6102
|
+
return;
|
|
6103
|
+
}
|
|
5154
6104
|
if (editable.dataset.ohwEditable === "image" || editable.dataset.ohwEditable === "bg-image") {
|
|
5155
6105
|
e.preventDefault();
|
|
5156
6106
|
e.stopPropagation();
|
|
@@ -5171,6 +6121,15 @@ function OhhwellsBridge() {
|
|
|
5171
6121
|
}
|
|
5172
6122
|
const anchor = target.closest("a");
|
|
5173
6123
|
if (anchor) e.preventDefault();
|
|
6124
|
+
if (Date.now() < linkPopoverGraceUntilRef.current) return;
|
|
6125
|
+
if (linkPopoverOpenRef.current && activeElRef.current) {
|
|
6126
|
+
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6127
|
+
if (hrefCtx && (hrefCtx.anchor === target || hrefCtx.anchor.contains(target))) return;
|
|
6128
|
+
}
|
|
6129
|
+
if (linkPopoverOpenRef.current) {
|
|
6130
|
+
setLinkPopoverRef.current(null);
|
|
6131
|
+
return;
|
|
6132
|
+
}
|
|
5174
6133
|
deactivateRef.current();
|
|
5175
6134
|
};
|
|
5176
6135
|
const handleMouseOver = (e) => {
|
|
@@ -5621,25 +6580,35 @@ function OhhwellsBridge() {
|
|
|
5621
6580
|
if (img) img.src = val;
|
|
5622
6581
|
} else if (el.dataset.ohwEditable === "bg-image") {
|
|
5623
6582
|
el.style.backgroundImage = `url('${val}')`;
|
|
6583
|
+
} else if (el.dataset.ohwEditable === "link") {
|
|
6584
|
+
applyLinkHref(el, val);
|
|
5624
6585
|
} else {
|
|
5625
6586
|
el.innerHTML = val;
|
|
5626
6587
|
}
|
|
5627
6588
|
});
|
|
6589
|
+
applyLinkByKey(key, val);
|
|
5628
6590
|
}
|
|
5629
6591
|
if (sectionsJson) {
|
|
5630
6592
|
initSectionsFromContent({ __ohw_sections: sectionsJson }, true);
|
|
5631
|
-
sectionsLoadedRef.current = true;
|
|
5632
|
-
pendingScheduleConfigRequests.current.splice(0).forEach(processConfigRequest);
|
|
5633
6593
|
}
|
|
5634
6594
|
postToParentRef.current({ type: "ow:hydrate-done" });
|
|
5635
6595
|
};
|
|
5636
6596
|
window.addEventListener("message", handleHydrate);
|
|
5637
6597
|
const handleDeactivate = (e) => {
|
|
5638
6598
|
if (e.data?.type !== "ow:deactivate") return;
|
|
6599
|
+
if (Date.now() < linkPopoverGraceUntilRef.current) return;
|
|
6600
|
+
if (linkPopoverOpenRef.current) {
|
|
6601
|
+
setLinkPopoverRef.current(null);
|
|
6602
|
+
return;
|
|
6603
|
+
}
|
|
5639
6604
|
deactivateRef.current();
|
|
5640
6605
|
};
|
|
5641
6606
|
window.addEventListener("message", handleDeactivate);
|
|
5642
6607
|
const handleKeyDown = (e) => {
|
|
6608
|
+
if (e.key === "Escape" && linkPopoverOpenRef.current) {
|
|
6609
|
+
setLinkPopoverRef.current(null);
|
|
6610
|
+
return;
|
|
6611
|
+
}
|
|
5643
6612
|
if (e.key !== "Escape") return;
|
|
5644
6613
|
const el = activeElRef.current;
|
|
5645
6614
|
if (el && originalContentRef.current !== null) {
|
|
@@ -5893,23 +6862,7 @@ function OhhwellsBridge() {
|
|
|
5893
6862
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
5894
6863
|
};
|
|
5895
6864
|
}, [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)(() => {
|
|
6865
|
+
(0, import_react5.useEffect)(() => {
|
|
5913
6866
|
if (!isEditMode) return;
|
|
5914
6867
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
5915
6868
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -5924,20 +6877,26 @@ function OhhwellsBridge() {
|
|
|
5924
6877
|
const raf = requestAnimationFrame(() => refreshStateRules());
|
|
5925
6878
|
const timer = setTimeout(() => {
|
|
5926
6879
|
refreshStateRules();
|
|
5927
|
-
|
|
6880
|
+
const sections = collectSections();
|
|
6881
|
+
setSectionsByPath((prev) => {
|
|
6882
|
+
const next = { ...prev, [pathname]: sections };
|
|
6883
|
+
return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
|
|
6884
|
+
});
|
|
6885
|
+
postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
|
|
6886
|
+
postToParent({ type: "ow:request-site-pages" });
|
|
5928
6887
|
}, 150);
|
|
5929
6888
|
return () => {
|
|
5930
6889
|
cancelAnimationFrame(raf);
|
|
5931
6890
|
clearTimeout(timer);
|
|
5932
6891
|
};
|
|
5933
6892
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
5934
|
-
const handleCommand = (0,
|
|
6893
|
+
const handleCommand = (0, import_react5.useCallback)((cmd) => {
|
|
5935
6894
|
document.execCommand(cmd, false);
|
|
5936
6895
|
activeElRef.current?.focus();
|
|
5937
6896
|
if (activeElRef.current) setToolbarRect(activeElRef.current.getBoundingClientRect());
|
|
5938
6897
|
refreshActiveCommandsRef.current();
|
|
5939
6898
|
}, []);
|
|
5940
|
-
const handleStateChange = (0,
|
|
6899
|
+
const handleStateChange = (0, import_react5.useCallback)((state) => {
|
|
5941
6900
|
if (!activeStateElRef.current) return;
|
|
5942
6901
|
const el = activeStateElRef.current;
|
|
5943
6902
|
if (state === "Default") {
|
|
@@ -5950,13 +6909,48 @@ function OhhwellsBridge() {
|
|
|
5950
6909
|
}
|
|
5951
6910
|
setToggleState((prev) => prev ? { ...prev, activeState: state } : null);
|
|
5952
6911
|
}, [deactivate]);
|
|
6912
|
+
const closeLinkPopover = (0, import_react5.useCallback)(() => setLinkPopover(null), []);
|
|
6913
|
+
const openLinkPopoverForActive = (0, import_react5.useCallback)(() => {
|
|
6914
|
+
const hrefCtx = getHrefKeyFromElement(activeElRef.current);
|
|
6915
|
+
if (!hrefCtx) return;
|
|
6916
|
+
bumpLinkPopoverGrace();
|
|
6917
|
+
setLinkPopover({
|
|
6918
|
+
key: hrefCtx.key,
|
|
6919
|
+
target: getLinkHref(hrefCtx.anchor),
|
|
6920
|
+
rect: hrefCtx.anchor.getBoundingClientRect()
|
|
6921
|
+
});
|
|
6922
|
+
}, []);
|
|
6923
|
+
const handleLinkPopoverSubmit = (0, import_react5.useCallback)(
|
|
6924
|
+
(target) => {
|
|
6925
|
+
if (!linkPopover) return;
|
|
6926
|
+
const { key } = linkPopover;
|
|
6927
|
+
applyLinkByKey(key, target);
|
|
6928
|
+
postToParent({ type: "ow:change", nodes: [{ key, text: target }] });
|
|
6929
|
+
setLinkPopover(null);
|
|
6930
|
+
},
|
|
6931
|
+
[linkPopover, postToParent]
|
|
6932
|
+
);
|
|
6933
|
+
const showEditLink = toolbarShowEditLink;
|
|
6934
|
+
const currentSections = sectionsByPath[pathname] ?? [];
|
|
6935
|
+
linkPopoverOpenRef.current = linkPopover !== null;
|
|
5953
6936
|
return bridgeRoot ? (0, import_react_dom2.createPortal)(
|
|
5954
|
-
/* @__PURE__ */ (0,
|
|
5955
|
-
toolbarRect && /* @__PURE__ */ (0,
|
|
5956
|
-
/* @__PURE__ */ (0,
|
|
5957
|
-
/* @__PURE__ */ (0,
|
|
6937
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
6938
|
+
toolbarRect && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
|
|
6939
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(GlowFrame, { rect: toolbarRect, elRef: glowElRef }),
|
|
6940
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6941
|
+
FloatingToolbar,
|
|
6942
|
+
{
|
|
6943
|
+
rect: toolbarRect,
|
|
6944
|
+
parentScroll: parentScrollRef.current,
|
|
6945
|
+
elRef: toolbarElRef,
|
|
6946
|
+
onCommand: handleCommand,
|
|
6947
|
+
activeCommands,
|
|
6948
|
+
showEditLink,
|
|
6949
|
+
onEditLink: openLinkPopoverForActive
|
|
6950
|
+
}
|
|
6951
|
+
)
|
|
5958
6952
|
] }),
|
|
5959
|
-
maxBadge && /* @__PURE__ */ (0,
|
|
6953
|
+
maxBadge && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
5960
6954
|
"div",
|
|
5961
6955
|
{
|
|
5962
6956
|
"data-ohw-max-badge": "",
|
|
@@ -5982,7 +6976,7 @@ function OhhwellsBridge() {
|
|
|
5982
6976
|
]
|
|
5983
6977
|
}
|
|
5984
6978
|
),
|
|
5985
|
-
toggleState && /* @__PURE__ */ (0,
|
|
6979
|
+
toggleState && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
5986
6980
|
StateToggle,
|
|
5987
6981
|
{
|
|
5988
6982
|
rect: toggleState.rect,
|
|
@@ -5991,15 +6985,31 @@ function OhhwellsBridge() {
|
|
|
5991
6985
|
onStateChange: handleStateChange
|
|
5992
6986
|
}
|
|
5993
6987
|
),
|
|
5994
|
-
|
|
6988
|
+
linkPopover ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6989
|
+
LinkPopover,
|
|
6990
|
+
{
|
|
6991
|
+
rect: linkPopover.rect,
|
|
6992
|
+
parentScroll: parentScrollRef.current,
|
|
6993
|
+
panelRef: linkPopoverPanelRef,
|
|
6994
|
+
open: true,
|
|
6995
|
+
mode: "edit",
|
|
6996
|
+
pages: sitePages,
|
|
6997
|
+
sections: currentSections,
|
|
6998
|
+
sectionsByPath,
|
|
6999
|
+
initialTarget: linkPopover.target,
|
|
7000
|
+
onClose: closeLinkPopover,
|
|
7001
|
+
onSubmit: handleLinkPopoverSubmit
|
|
7002
|
+
}
|
|
7003
|
+
) : null,
|
|
7004
|
+
sectionGap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
|
|
5995
7005
|
"div",
|
|
5996
7006
|
{
|
|
5997
7007
|
"data-ohw-section-insert-line": "",
|
|
5998
7008
|
className: "fixed left-0 w-full z-2147483646 flex items-center pointer-events-none",
|
|
5999
7009
|
style: { top: sectionGap.y, transform: "translateY(-50%)" },
|
|
6000
7010
|
children: [
|
|
6001
|
-
/* @__PURE__ */ (0,
|
|
6002
|
-
/* @__PURE__ */ (0,
|
|
7011
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } }),
|
|
7012
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6003
7013
|
Badge,
|
|
6004
7014
|
{
|
|
6005
7015
|
className: "px-8 py-1 bg-primary hover:bg-primary text-primary-foreground text-xs font-medium shrink-0 rounded-full cursor-pointer pointer-events-auto",
|
|
@@ -6012,7 +7022,7 @@ function OhhwellsBridge() {
|
|
|
6012
7022
|
children: "Add Section"
|
|
6013
7023
|
}
|
|
6014
7024
|
),
|
|
6015
|
-
/* @__PURE__ */ (0,
|
|
7025
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex-1 bg-primary", style: { height: 3 } })
|
|
6016
7026
|
]
|
|
6017
7027
|
}
|
|
6018
7028
|
)
|
|
@@ -6022,11 +7032,19 @@ function OhhwellsBridge() {
|
|
|
6022
7032
|
}
|
|
6023
7033
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6024
7034
|
0 && (module.exports = {
|
|
7035
|
+
LinkEditorPanel,
|
|
7036
|
+
LinkPopover,
|
|
6025
7037
|
OhhwellsBridge,
|
|
6026
7038
|
SchedulingWidget,
|
|
6027
7039
|
Toggle,
|
|
6028
7040
|
ToggleGroup,
|
|
6029
7041
|
ToggleGroupItem,
|
|
6030
|
-
|
|
7042
|
+
buildTarget,
|
|
7043
|
+
filterAvailablePages,
|
|
7044
|
+
getEditModeInitialState,
|
|
7045
|
+
isValidUrl,
|
|
7046
|
+
parseTarget,
|
|
7047
|
+
toggleVariants,
|
|
7048
|
+
validateUrlInput
|
|
6031
7049
|
});
|
|
6032
7050
|
//# sourceMappingURL=index.cjs.map
|