@ohhwells/bridge 0.1.42-next.93 → 0.1.42-next.95
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 +12 -0
- package/dist/index.cjs +238 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +289 -103
- package/dist/index.js.map +1 -1
- package/dist/styles.css +12 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -318,6 +318,18 @@ npm run build
|
|
|
318
318
|
npm publish --access public
|
|
319
319
|
```
|
|
320
320
|
|
|
321
|
+
### Staging → rebound-template auto-bump
|
|
322
|
+
|
|
323
|
+
When `staging` publishes successfully, the bridge workflow dispatches a `bridge-published` event to `TheFlowOps-Eng/rebound-template`. That repo’s `bump-bridge.yml` workflow checks out `staging`, runs `npm install @ohhwells/bridge@<version> --save-exact`, and pushes the lockfile bump.
|
|
324
|
+
|
|
325
|
+
**One-time setup** (in `ohhwells-bridge` GitHub repo → Settings → Secrets):
|
|
326
|
+
|
|
327
|
+
| Secret | Value |
|
|
328
|
+
|--------|--------|
|
|
329
|
+
| `REBOUND_TEMPLATE_DISPATCH_TOKEN` | Fine-grained or classic PAT with `repo` access to `rebound-template` (needs permission to trigger `repository_dispatch`) |
|
|
330
|
+
|
|
331
|
+
The published staging version looks like `0.1.31-next.42` and is tagged `next` on npm.
|
|
332
|
+
|
|
321
333
|
---
|
|
322
334
|
|
|
323
335
|
## Link dialog (Canvas Editor)
|
package/dist/index.cjs
CHANGED
|
@@ -115,6 +115,7 @@ var import_react3 = require("react");
|
|
|
115
115
|
var import_react2 = require("react");
|
|
116
116
|
var import_radix_ui = require("radix-ui");
|
|
117
117
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
118
|
+
var isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
118
119
|
function Spinner() {
|
|
119
120
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
120
121
|
"svg",
|
|
@@ -148,12 +149,17 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
|
148
149
|
const [error, setError] = (0, import_react2.useState)(null);
|
|
149
150
|
const isBook = title === "Confirm your spot";
|
|
150
151
|
const handleSubmit = async () => {
|
|
151
|
-
|
|
152
|
+
const trimmed = email.trim();
|
|
153
|
+
if (!trimmed) return;
|
|
154
|
+
if (!isValidEmail(trimmed)) {
|
|
155
|
+
setError("Please enter a valid email address.");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
152
158
|
setLoading(true);
|
|
153
159
|
setError(null);
|
|
154
160
|
try {
|
|
155
|
-
await onSubmit(
|
|
156
|
-
setSubmittedEmail(
|
|
161
|
+
await onSubmit(trimmed);
|
|
162
|
+
setSubmittedEmail(trimmed);
|
|
157
163
|
setSuccess(true);
|
|
158
164
|
} catch (e) {
|
|
159
165
|
setError(e instanceof Error ? e.message : "Something went wrong. Please try again.");
|
|
@@ -228,7 +234,10 @@ function EmailCaptureModal({ title, subtitle, onSubmit, onClose }) {
|
|
|
228
234
|
{
|
|
229
235
|
type: "email",
|
|
230
236
|
value: email,
|
|
231
|
-
onChange: (e) =>
|
|
237
|
+
onChange: (e) => {
|
|
238
|
+
setEmail(e.target.value);
|
|
239
|
+
if (error) setError(null);
|
|
240
|
+
},
|
|
232
241
|
onKeyDown: handleKeyDown,
|
|
233
242
|
placeholder: "hello@gmail.com",
|
|
234
243
|
autoFocus: true,
|
|
@@ -285,12 +294,20 @@ function formatClassTime(cls) {
|
|
|
285
294
|
const em = endTotal % 60;
|
|
286
295
|
return `${h}:${cls.startTime.minutes}-${eh}:${String(em).padStart(2, "0")}`;
|
|
287
296
|
}
|
|
297
|
+
function formatBookingLabel(cls) {
|
|
298
|
+
const price = cls.price;
|
|
299
|
+
const isFree = price === null || price === void 0 || `${price}` === "" || `${price}` === "0";
|
|
300
|
+
return isFree ? "Book for free" : `Book for ${cls.currency ?? ""} ${price}`.trim();
|
|
301
|
+
}
|
|
288
302
|
function getBookingsOnDate(cls, date) {
|
|
289
303
|
if (!cls.bookings?.length) return 0;
|
|
290
|
-
const
|
|
304
|
+
const target = new Date(date);
|
|
305
|
+
target.setHours(0, 0, 0, 0);
|
|
291
306
|
return cls.bookings.filter((b) => {
|
|
292
307
|
try {
|
|
293
|
-
|
|
308
|
+
const bookingDate = new Date(b.classDate);
|
|
309
|
+
bookingDate.setHours(0, 0, 0, 0);
|
|
310
|
+
return bookingDate.getTime() === target.getTime();
|
|
294
311
|
} catch {
|
|
295
312
|
return false;
|
|
296
313
|
}
|
|
@@ -555,7 +572,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
555
572
|
}
|
|
556
573
|
)
|
|
557
574
|
] }),
|
|
558
|
-
/* @__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: [
|
|
575
|
+
cls.showAvailability !== false && /* @__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: [
|
|
559
576
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
560
577
|
available,
|
|
561
578
|
"/",
|
|
@@ -567,9 +584,17 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
567
584
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-1 flex-col gap-2 min-w-0 sm:contents", children: [
|
|
568
585
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-1 flex flex-col gap-2 min-w-0", children: [
|
|
569
586
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-bold text-(--color-dark,#200C02) block truncate", children: cls.name }),
|
|
570
|
-
cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName })
|
|
587
|
+
cls.hostName && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block truncate", children: cls.hostName }),
|
|
588
|
+
cls.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
589
|
+
"span",
|
|
590
|
+
{
|
|
591
|
+
className: "font-body text-base font-normal text-(--color-dark,#200C02) opacity-80 block",
|
|
592
|
+
style: { wordBreak: "break-word" },
|
|
593
|
+
children: cls.description
|
|
594
|
+
}
|
|
595
|
+
)
|
|
571
596
|
] }),
|
|
572
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
597
|
+
cls.showAvailability !== false && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hidden sm:flex w-14 shrink-0 flex-col gap-px", children: isFull ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: "Full" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
573
598
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "font-body text-sm text-(--color-dark,#200C02) opacity-80", children: [
|
|
574
599
|
available,
|
|
575
600
|
"/",
|
|
@@ -594,7 +619,7 @@ function ScheduleView({ schedule, dates, selectedIdx, onSelectDate, onOpenModal
|
|
|
594
619
|
if (!cls.id) return;
|
|
595
620
|
onOpenModal?.(isFull ? "waitlist" : "book", cls.id, selectedDate);
|
|
596
621
|
},
|
|
597
|
-
children: isFull ? "Join Waitlist" :
|
|
622
|
+
children: isFull ? "Join Waitlist" : formatBookingLabel(cls)
|
|
598
623
|
}
|
|
599
624
|
)
|
|
600
625
|
] })
|
|
@@ -636,6 +661,17 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
636
661
|
const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
|
|
637
662
|
const [modalState, setModalState] = (0, import_react3.useState)(null);
|
|
638
663
|
const switchScheduleIdRef = (0, import_react3.useRef)(null);
|
|
664
|
+
const liveScheduleIdRef = (0, import_react3.useRef)(null);
|
|
665
|
+
const refetchLiveSchedule = (0, import_react3.useCallback)(async () => {
|
|
666
|
+
const id = liveScheduleIdRef.current;
|
|
667
|
+
if (!id) return;
|
|
668
|
+
try {
|
|
669
|
+
const res = await fetch(`${API_URL}/api/schedule/id/${id}`);
|
|
670
|
+
const data = await res.json();
|
|
671
|
+
if (data?.id) setSchedule(data);
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
}, []);
|
|
639
675
|
const dates = (0, import_react3.useMemo)(() => {
|
|
640
676
|
const today = /* @__PURE__ */ new Date();
|
|
641
677
|
today.setHours(0, 0, 0, 0);
|
|
@@ -655,6 +691,18 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
655
691
|
}
|
|
656
692
|
}
|
|
657
693
|
}, [schedule, dates]);
|
|
694
|
+
(0, import_react3.useEffect)(() => {
|
|
695
|
+
if (typeof window === "undefined") return;
|
|
696
|
+
const onVisible = () => {
|
|
697
|
+
if (!document.hidden) void refetchLiveSchedule();
|
|
698
|
+
};
|
|
699
|
+
window.addEventListener("focus", refetchLiveSchedule);
|
|
700
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
701
|
+
return () => {
|
|
702
|
+
window.removeEventListener("focus", refetchLiveSchedule);
|
|
703
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
704
|
+
};
|
|
705
|
+
}, [refetchLiveSchedule]);
|
|
658
706
|
(0, import_react3.useEffect)(() => {
|
|
659
707
|
if (typeof window === "undefined") return;
|
|
660
708
|
const isInEditor = window.self !== window.top;
|
|
@@ -716,7 +764,7 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
716
764
|
setLoading(false);
|
|
717
765
|
return;
|
|
718
766
|
}
|
|
719
|
-
;
|
|
767
|
+
liveScheduleIdRef.current = effectiveId;
|
|
720
768
|
(async () => {
|
|
721
769
|
try {
|
|
722
770
|
const res = await fetch(`${API_URL}/api/schedule/id/${effectiveId}`);
|
|
@@ -773,18 +821,14 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
773
821
|
const data = await res.json().catch(() => ({}));
|
|
774
822
|
throw new Error(data?.message ?? "Something went wrong. Please try again.");
|
|
775
823
|
}
|
|
824
|
+
await refetchLiveSchedule();
|
|
776
825
|
};
|
|
777
826
|
const handleReplaceSchedule = () => {
|
|
778
827
|
setIsHovered(false);
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
insertAfter
|
|
784
|
-
}, "*");
|
|
785
|
-
} else {
|
|
786
|
-
window.parent.postMessage({ type: "ow:scheduling-not-connected", insertAfter }, "*");
|
|
787
|
-
}
|
|
828
|
+
window.parent.postMessage(
|
|
829
|
+
{ type: "ow:scheduling-not-connected", insertAfter, currentScheduleId: schedule?.id },
|
|
830
|
+
"*"
|
|
831
|
+
);
|
|
788
832
|
};
|
|
789
833
|
if (!inEditor && !loading && !schedule) return null;
|
|
790
834
|
const sectionId = `scheduling-${insertAfter}`;
|
|
@@ -827,7 +871,19 @@ function SchedulingWidget({ notifyOnConnect = false, initialScheduleId, insertAf
|
|
|
827
871
|
),
|
|
828
872
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "max-w-[1280px] mx-auto flex flex-col items-center gap-16", children: [
|
|
829
873
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
|
|
830
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
874
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
875
|
+
"h2",
|
|
876
|
+
{
|
|
877
|
+
className: "font-display text-center m-0 text-(--color-dark,#200C02)",
|
|
878
|
+
style: {
|
|
879
|
+
fontSize: "var(--fs-section-h2, clamp(40px, 4.4vw, 64px))",
|
|
880
|
+
fontWeight: "var(--font-weight-heading, 400)",
|
|
881
|
+
lineHeight: 1.05,
|
|
882
|
+
letterSpacing: "-0.02em"
|
|
883
|
+
},
|
|
884
|
+
children: schedule?.name ?? "Book an appointment"
|
|
885
|
+
}
|
|
886
|
+
),
|
|
831
887
|
schedule?.description && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "font-body text-body text-center m-0 text-(--color-accent,#A89B83)", children: schedule.description })
|
|
832
888
|
] }),
|
|
833
889
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "w-full flex flex-col items-end gap-3", children: [
|
|
@@ -6661,6 +6717,8 @@ function applyNavOrderToContainer(container, order) {
|
|
|
6661
6717
|
if (current.length === expected.length && current.every((key, i) => key === expected[i])) {
|
|
6662
6718
|
return;
|
|
6663
6719
|
}
|
|
6720
|
+
const lastCurrentLinkEl = current.length > 0 ? findCounterpartByHrefKey(current[current.length - 1], container) : null;
|
|
6721
|
+
const anchor = lastCurrentLinkEl?.parentElement === container ? lastCurrentLinkEl.nextElementSibling : null;
|
|
6664
6722
|
const seen = /* @__PURE__ */ new Set();
|
|
6665
6723
|
for (const hrefKey of desired) {
|
|
6666
6724
|
if (seen.has(hrefKey)) continue;
|
|
@@ -6675,7 +6733,9 @@ function applyNavOrderToContainer(container, order) {
|
|
|
6675
6733
|
orderedEls.push(el);
|
|
6676
6734
|
}
|
|
6677
6735
|
for (const el of orderedEls) {
|
|
6678
|
-
if (
|
|
6736
|
+
if (anchor) {
|
|
6737
|
+
if (el.nextSibling !== anchor) container.insertBefore(el, anchor);
|
|
6738
|
+
} else if (container.lastElementChild !== el) {
|
|
6679
6739
|
container.appendChild(el);
|
|
6680
6740
|
}
|
|
6681
6741
|
}
|
|
@@ -7017,6 +7077,11 @@ function reconcileFooterOrderFromContent(content) {
|
|
|
7017
7077
|
}
|
|
7018
7078
|
applyFooterOrder(order);
|
|
7019
7079
|
}
|
|
7080
|
+
function isRowLayoutColumn(links) {
|
|
7081
|
+
if (links.length < 2) return false;
|
|
7082
|
+
const firstTop = links[0].getBoundingClientRect().top;
|
|
7083
|
+
return links.every((link) => Math.abs(link.getBoundingClientRect().top - firstTop) < 4);
|
|
7084
|
+
}
|
|
7020
7085
|
function buildLinkDropSlots(column, columnIndex) {
|
|
7021
7086
|
const links = listFooterLinksInColumn(column);
|
|
7022
7087
|
const colRect = column.getBoundingClientRect();
|
|
@@ -7034,6 +7099,32 @@ function buildLinkDropSlots(column, columnIndex) {
|
|
|
7034
7099
|
});
|
|
7035
7100
|
return slots;
|
|
7036
7101
|
}
|
|
7102
|
+
if (isRowLayoutColumn(links)) {
|
|
7103
|
+
for (let i = 0; i <= links.length; i++) {
|
|
7104
|
+
let left;
|
|
7105
|
+
if (i === 0) {
|
|
7106
|
+
left = links[0].getBoundingClientRect().left - barThickness / 2;
|
|
7107
|
+
} else if (i === links.length) {
|
|
7108
|
+
const last = links[links.length - 1].getBoundingClientRect();
|
|
7109
|
+
left = last.right - barThickness / 2;
|
|
7110
|
+
} else {
|
|
7111
|
+
const prev = links[i - 1].getBoundingClientRect();
|
|
7112
|
+
const next = links[i].getBoundingClientRect();
|
|
7113
|
+
left = (prev.right + next.left) / 2 - barThickness / 2;
|
|
7114
|
+
}
|
|
7115
|
+
const heightRef = i === 0 ? links[0].getBoundingClientRect() : i === links.length ? links[links.length - 1].getBoundingClientRect() : links[i].getBoundingClientRect();
|
|
7116
|
+
slots.push({
|
|
7117
|
+
insertIndex: i,
|
|
7118
|
+
columnIndex,
|
|
7119
|
+
left,
|
|
7120
|
+
top: heightRef.top,
|
|
7121
|
+
width: barThickness,
|
|
7122
|
+
height: Math.max(heightRef.height, colRect.height * 0.8),
|
|
7123
|
+
direction: "vertical"
|
|
7124
|
+
});
|
|
7125
|
+
}
|
|
7126
|
+
return slots;
|
|
7127
|
+
}
|
|
7037
7128
|
for (let i = 0; i <= links.length; i++) {
|
|
7038
7129
|
let top;
|
|
7039
7130
|
if (i === 0) {
|
|
@@ -7117,8 +7208,7 @@ function hitTestLinkDropSlot(clientX, clientY, draggedHrefKey) {
|
|
|
7117
7208
|
return true;
|
|
7118
7209
|
});
|
|
7119
7210
|
for (const slot of slots) {
|
|
7120
|
-
const
|
|
7121
|
-
const dist = Math.abs(clientY - cy) + (inColX ? 0 : 1e3);
|
|
7211
|
+
const dist = slot.direction === "vertical" ? Math.abs(clientX - (slot.left + slot.width / 2)) : Math.abs(clientY - (slot.top + slot.height / 2));
|
|
7122
7212
|
if (!best || dist < best.dist) best = { slot, dist };
|
|
7123
7213
|
}
|
|
7124
7214
|
}
|
|
@@ -7528,7 +7618,6 @@ function useNavItemDrag({
|
|
|
7528
7618
|
const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
|
|
7529
7619
|
if (!anchor || !isNavbarHrefKey(hrefKey)) return;
|
|
7530
7620
|
if (isNavbarButton3(anchor) || isDragHandleDisabled2(anchor)) return;
|
|
7531
|
-
armItemPressDrag();
|
|
7532
7621
|
navPointerDragRef.current = {
|
|
7533
7622
|
el: anchor,
|
|
7534
7623
|
startX: e.clientX,
|
|
@@ -7555,6 +7644,7 @@ function useNavItemDrag({
|
|
|
7555
7644
|
if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
|
|
7556
7645
|
e.preventDefault();
|
|
7557
7646
|
pending.started = true;
|
|
7647
|
+
armItemPressDrag();
|
|
7558
7648
|
clearTextSelection();
|
|
7559
7649
|
try {
|
|
7560
7650
|
document.body.setPointerCapture(pending.pointerId);
|
|
@@ -7582,7 +7672,8 @@ function useNavItemDrag({
|
|
|
7582
7672
|
}
|
|
7583
7673
|
} catch {
|
|
7584
7674
|
}
|
|
7585
|
-
if (!pending
|
|
7675
|
+
if (!pending) return;
|
|
7676
|
+
if (!pending.started) {
|
|
7586
7677
|
unlockItemDragInteraction();
|
|
7587
7678
|
return;
|
|
7588
7679
|
}
|
|
@@ -7634,7 +7725,6 @@ function useNavItemDrag({
|
|
|
7634
7725
|
const hrefKey = selected.getAttribute("data-ohw-href-key");
|
|
7635
7726
|
if (!hrefKey || !isNavbarHrefKey(hrefKey)) return false;
|
|
7636
7727
|
if (isNavbarButton3(selected) || isDragHandleDisabled2(selected)) return false;
|
|
7637
|
-
armItemPressDrag();
|
|
7638
7728
|
navPointerDragRef.current = {
|
|
7639
7729
|
el: selected,
|
|
7640
7730
|
startX: clientX,
|
|
@@ -8861,6 +8951,7 @@ function OhhwellsBridge() {
|
|
|
8861
8951
|
const [fetchState, setFetchState] = (0, import_react9.useState)("idle");
|
|
8862
8952
|
const autoSaveTimers = (0, import_react9.useRef)(/* @__PURE__ */ new Map());
|
|
8863
8953
|
const activeElRef = (0, import_react9.useRef)(null);
|
|
8954
|
+
const pointerHeldRef = (0, import_react9.useRef)(false);
|
|
8864
8955
|
const selectedElRef = (0, import_react9.useRef)(null);
|
|
8865
8956
|
const selectedHrefKeyRef = (0, import_react9.useRef)(null);
|
|
8866
8957
|
const selectedFooterColAttrRef = (0, import_react9.useRef)(null);
|
|
@@ -9558,7 +9649,6 @@ function OhhwellsBridge() {
|
|
|
9558
9649
|
if (e.button !== 0) return;
|
|
9559
9650
|
const selected = selectedElRef.current;
|
|
9560
9651
|
if (!selected) return;
|
|
9561
|
-
armFooterPressDrag();
|
|
9562
9652
|
const hrefKey = selected.getAttribute("data-ohw-href-key");
|
|
9563
9653
|
if (hrefKey && isFooterHrefKey(hrefKey)) {
|
|
9564
9654
|
footerPointerDragRef.current = {
|
|
@@ -9669,6 +9759,8 @@ function OhhwellsBridge() {
|
|
|
9669
9759
|
siblingHintElRef.current = null;
|
|
9670
9760
|
setSiblingHintRect(null);
|
|
9671
9761
|
setIsItemDragging(false);
|
|
9762
|
+
const preActivationSelection = window.getSelection();
|
|
9763
|
+
const preservedRange = preActivationSelection && preActivationSelection.rangeCount > 0 && !preActivationSelection.isCollapsed && el.contains(preActivationSelection.getRangeAt(0).commonAncestorContainer) ? preActivationSelection.getRangeAt(0).cloneRange() : null;
|
|
9672
9764
|
el.setAttribute("contenteditable", "true");
|
|
9673
9765
|
el.setAttribute("data-ohw-editing", "");
|
|
9674
9766
|
el.removeAttribute("data-ohw-hovered");
|
|
@@ -9676,7 +9768,11 @@ function OhhwellsBridge() {
|
|
|
9676
9768
|
activeElRef.current = el;
|
|
9677
9769
|
originalContentRef.current = el.innerHTML;
|
|
9678
9770
|
el.focus({ preventScroll: true });
|
|
9679
|
-
if (
|
|
9771
|
+
if (preservedRange) {
|
|
9772
|
+
const selection = window.getSelection();
|
|
9773
|
+
selection?.removeAllRanges();
|
|
9774
|
+
selection?.addRange(preservedRange);
|
|
9775
|
+
} else if (options?.caretX !== void 0 && options?.caretY !== void 0) {
|
|
9680
9776
|
const { caretX, caretY } = options;
|
|
9681
9777
|
placeCaretAtPoint(el, caretX, caretY);
|
|
9682
9778
|
requestAnimationFrame(() => {
|
|
@@ -10132,7 +10228,18 @@ function OhhwellsBridge() {
|
|
|
10132
10228
|
if (inActive || inActiveNav) {
|
|
10133
10229
|
e.preventDefault();
|
|
10134
10230
|
e.stopPropagation();
|
|
10135
|
-
|
|
10231
|
+
const selection = window.getSelection();
|
|
10232
|
+
const hasRangeSelection = Boolean(selection && !selection.isCollapsed);
|
|
10233
|
+
console.log(
|
|
10234
|
+
"[OHW DEBUG handleClick] already-active branch",
|
|
10235
|
+
"detail=",
|
|
10236
|
+
e.detail,
|
|
10237
|
+
"hasRangeSelection=",
|
|
10238
|
+
hasRangeSelection,
|
|
10239
|
+
"willPlaceCaret=",
|
|
10240
|
+
e.detail < 2 && !hasRangeSelection
|
|
10241
|
+
);
|
|
10242
|
+
if (e.detail < 2 && !hasRangeSelection) {
|
|
10136
10243
|
placeCaretAtPoint(active, e.clientX, e.clientY);
|
|
10137
10244
|
refreshActiveCommandsRef.current();
|
|
10138
10245
|
}
|
|
@@ -10175,6 +10282,15 @@ function OhhwellsBridge() {
|
|
|
10175
10282
|
}
|
|
10176
10283
|
e.preventDefault();
|
|
10177
10284
|
e.stopPropagation();
|
|
10285
|
+
console.log(
|
|
10286
|
+
"[OHW DEBUG handleClick] first-activation branch",
|
|
10287
|
+
"detail=",
|
|
10288
|
+
e.detail,
|
|
10289
|
+
"selectionAtClick=",
|
|
10290
|
+
JSON.stringify(window.getSelection()?.toString() ?? ""),
|
|
10291
|
+
"collapsedAtClick=",
|
|
10292
|
+
window.getSelection()?.isCollapsed
|
|
10293
|
+
);
|
|
10178
10294
|
activateRef.current(editable);
|
|
10179
10295
|
return;
|
|
10180
10296
|
}
|
|
@@ -11285,30 +11401,83 @@ function OhhwellsBridge() {
|
|
|
11285
11401
|
const h = document.documentElement.scrollHeight;
|
|
11286
11402
|
if (h > 50) postToParentRef.current({ type: "ow:height", height: h });
|
|
11287
11403
|
};
|
|
11288
|
-
|
|
11289
|
-
|
|
11404
|
+
let selectionChangeRaf = null;
|
|
11405
|
+
const runSelectionChange = () => {
|
|
11406
|
+
selectionChangeRaf = null;
|
|
11407
|
+
const activeRoot = activeElRef.current;
|
|
11408
|
+
if (!activeRoot) return;
|
|
11290
11409
|
const next = /* @__PURE__ */ new Set();
|
|
11291
|
-
for (const cmd of ["bold", "italic", "underline", "strikeThrough", "insertUnorderedList", "insertOrderedList"]) {
|
|
11292
|
-
try {
|
|
11293
|
-
if (document.queryCommandState(cmd)) next.add(cmd);
|
|
11294
|
-
} catch {
|
|
11295
|
-
}
|
|
11296
|
-
}
|
|
11297
11410
|
const sel = window.getSelection();
|
|
11298
|
-
|
|
11299
|
-
if (
|
|
11300
|
-
const
|
|
11301
|
-
|
|
11302
|
-
if (
|
|
11303
|
-
const
|
|
11304
|
-
|
|
11305
|
-
else if (align === "right" || align === "end") next.add("justifyRight");
|
|
11306
|
-
else next.add("justifyLeft");
|
|
11411
|
+
let startNode = null;
|
|
11412
|
+
if (sel && sel.rangeCount > 0) {
|
|
11413
|
+
const range = sel.getRangeAt(0);
|
|
11414
|
+
startNode = range.startContainer;
|
|
11415
|
+
if (startNode.nodeType === Node.ELEMENT_NODE) {
|
|
11416
|
+
const el = startNode;
|
|
11417
|
+
startNode = el.childNodes[range.startOffset] ?? el.childNodes[range.startOffset - 1] ?? el;
|
|
11307
11418
|
}
|
|
11308
11419
|
}
|
|
11309
|
-
|
|
11420
|
+
const startEl = startNode ? startNode.nodeType === Node.TEXT_NODE ? startNode.parentElement : startNode : null;
|
|
11421
|
+
if (startEl) {
|
|
11422
|
+
const cs = getComputedStyle(startEl);
|
|
11423
|
+
const weight = parseInt(cs.fontWeight, 10);
|
|
11424
|
+
let hasBold = cs.fontWeight === "bold" || !Number.isNaN(weight) && weight >= 600;
|
|
11425
|
+
let hasItalic = cs.fontStyle === "italic" || cs.fontStyle === "oblique";
|
|
11426
|
+
let hasUnderline = false;
|
|
11427
|
+
let hasStrike = false;
|
|
11428
|
+
let hasUl = false;
|
|
11429
|
+
let hasOl = false;
|
|
11430
|
+
for (let el = startEl; el; el = el.parentElement) {
|
|
11431
|
+
if (el.tagName === "B" || el.tagName === "STRONG") hasBold = true;
|
|
11432
|
+
if (el.tagName === "I" || el.tagName === "EM") hasItalic = true;
|
|
11433
|
+
const decoration = getComputedStyle(el).textDecorationLine;
|
|
11434
|
+
if (decoration.includes("underline") || el.tagName === "U" || el.tagName === "INS") hasUnderline = true;
|
|
11435
|
+
if (decoration.includes("line-through") || el.tagName === "S" || el.tagName === "STRIKE" || el.tagName === "DEL") hasStrike = true;
|
|
11436
|
+
if (el.tagName === "UL") hasUl = true;
|
|
11437
|
+
if (el.tagName === "OL") hasOl = true;
|
|
11438
|
+
if (el === activeRoot) break;
|
|
11439
|
+
}
|
|
11440
|
+
if (hasBold) next.add("bold");
|
|
11441
|
+
if (hasItalic) next.add("italic");
|
|
11442
|
+
if (hasUnderline) next.add("underline");
|
|
11443
|
+
if (hasStrike) next.add("strikeThrough");
|
|
11444
|
+
if (hasUl) next.add("insertUnorderedList");
|
|
11445
|
+
if (hasOl) next.add("insertOrderedList");
|
|
11446
|
+
const block = startEl.closest("div, p, h1, h2, h3, h4, h5, h6, li, td, th") ?? startEl;
|
|
11447
|
+
const align = getComputedStyle(block).textAlign;
|
|
11448
|
+
if (align === "center") next.add("justifyCenter");
|
|
11449
|
+
else if (align === "right" || align === "end") next.add("justifyRight");
|
|
11450
|
+
else next.add("justifyLeft");
|
|
11451
|
+
}
|
|
11452
|
+
setActiveCommands((prev) => {
|
|
11453
|
+
if (prev.size === next.size) {
|
|
11454
|
+
let same = true;
|
|
11455
|
+
for (const cmd of prev) {
|
|
11456
|
+
if (!next.has(cmd)) {
|
|
11457
|
+
same = false;
|
|
11458
|
+
break;
|
|
11459
|
+
}
|
|
11460
|
+
}
|
|
11461
|
+
if (same) return prev;
|
|
11462
|
+
}
|
|
11463
|
+
return next;
|
|
11464
|
+
});
|
|
11465
|
+
};
|
|
11466
|
+
const handleSelectionChange = () => {
|
|
11467
|
+
if (pointerHeldRef.current) return;
|
|
11468
|
+
if (selectionChangeRaf !== null) return;
|
|
11469
|
+
selectionChangeRaf = requestAnimationFrame(runSelectionChange);
|
|
11470
|
+
};
|
|
11471
|
+
const markPointerHeld = (e) => {
|
|
11472
|
+
if (e.button !== 0) return;
|
|
11473
|
+
pointerHeldRef.current = true;
|
|
11474
|
+
};
|
|
11475
|
+
const markPointerReleased = () => {
|
|
11476
|
+
if (!pointerHeldRef.current) return;
|
|
11477
|
+
pointerHeldRef.current = false;
|
|
11478
|
+
handleSelectionChange();
|
|
11310
11479
|
};
|
|
11311
|
-
refreshActiveCommandsRef.current =
|
|
11480
|
+
refreshActiveCommandsRef.current = runSelectionChange;
|
|
11312
11481
|
const handleDocMouseLeave = () => {
|
|
11313
11482
|
hoveredImageRef.current = null;
|
|
11314
11483
|
setMediaHover(null);
|
|
@@ -11548,7 +11717,6 @@ function OhhwellsBridge() {
|
|
|
11548
11717
|
const anchor = getNavigationItemAnchor(target);
|
|
11549
11718
|
const hrefKey = anchor?.getAttribute("data-ohw-href-key") ?? null;
|
|
11550
11719
|
if (anchor && isFooterHrefKey(hrefKey)) {
|
|
11551
|
-
armFooterPressDrag();
|
|
11552
11720
|
footerPointerDragRef.current = {
|
|
11553
11721
|
el: anchor,
|
|
11554
11722
|
kind: "link",
|
|
@@ -11562,7 +11730,6 @@ function OhhwellsBridge() {
|
|
|
11562
11730
|
}
|
|
11563
11731
|
const col = target.closest("[data-ohw-footer-col]");
|
|
11564
11732
|
if (col && !getNavigationItemAnchor(target)) {
|
|
11565
|
-
armFooterPressDrag();
|
|
11566
11733
|
footerPointerDragRef.current = {
|
|
11567
11734
|
el: col,
|
|
11568
11735
|
kind: "column",
|
|
@@ -11591,6 +11758,7 @@ function OhhwellsBridge() {
|
|
|
11591
11758
|
if (dx * dx + dy * dy < THRESHOLD * THRESHOLD) return;
|
|
11592
11759
|
e.preventDefault();
|
|
11593
11760
|
pending.started = true;
|
|
11761
|
+
armFooterPressDrag();
|
|
11594
11762
|
clearTextSelection();
|
|
11595
11763
|
try {
|
|
11596
11764
|
document.body.setPointerCapture(pending.pointerId);
|
|
@@ -11644,7 +11812,8 @@ function OhhwellsBridge() {
|
|
|
11644
11812
|
}
|
|
11645
11813
|
} catch {
|
|
11646
11814
|
}
|
|
11647
|
-
if (!pending
|
|
11815
|
+
if (!pending) return;
|
|
11816
|
+
if (!pending.started) {
|
|
11648
11817
|
unlockFooterDragInteraction();
|
|
11649
11818
|
return;
|
|
11650
11819
|
}
|
|
@@ -11679,6 +11848,22 @@ function OhhwellsBridge() {
|
|
|
11679
11848
|
unlockFooterDragInteraction();
|
|
11680
11849
|
};
|
|
11681
11850
|
}, [isEditMode]);
|
|
11851
|
+
(0, import_react9.useEffect)(() => {
|
|
11852
|
+
if (!isEditMode) return;
|
|
11853
|
+
const debugSelectionChange = () => {
|
|
11854
|
+
const sel = window.getSelection();
|
|
11855
|
+
console.log(
|
|
11856
|
+
"[OHW DEBUG selectionchange]",
|
|
11857
|
+
JSON.stringify(sel?.toString() ?? ""),
|
|
11858
|
+
"collapsed=",
|
|
11859
|
+
sel?.isCollapsed,
|
|
11860
|
+
"anchorNode=",
|
|
11861
|
+
sel?.anchorNode,
|
|
11862
|
+
"activeEl=",
|
|
11863
|
+
activeElRef.current
|
|
11864
|
+
);
|
|
11865
|
+
};
|
|
11866
|
+
}, [isEditMode]);
|
|
11682
11867
|
(0, import_react9.useEffect)(() => {
|
|
11683
11868
|
const handler = (e) => {
|
|
11684
11869
|
if (e.data?.type !== "ow:request-schedule-config") return;
|
|
@@ -11737,6 +11922,7 @@ function OhhwellsBridge() {
|
|
|
11737
11922
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
11738
11923
|
}, [pathname]);
|
|
11739
11924
|
const handleCommand = (0, import_react9.useCallback)((cmd) => {
|
|
11925
|
+
document.execCommand("styleWithCSS", false, true);
|
|
11740
11926
|
document.execCommand(cmd, false);
|
|
11741
11927
|
activeElRef.current?.focus();
|
|
11742
11928
|
if (activeElRef.current) setToolbarRect(getEditMeasureEl(activeElRef.current).getBoundingClientRect());
|