@ohhwells/bridge 0.1.25 → 0.1.27-next.18
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 -304
- package/dist/index.cjs +366 -217
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +379 -230
- package/dist/index.js.map +1 -1
- package/dist/styles.css +14 -12
- package/package.json +48 -48
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/OhhwellsBridge.tsx
|
|
4
|
-
import React5, { useCallback as useCallback2, useEffect as
|
|
4
|
+
import React5, { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState5 } from "react";
|
|
5
5
|
import { createRoot } from "react-dom/client";
|
|
6
6
|
import { flushSync } from "react-dom";
|
|
7
7
|
|
|
@@ -4175,6 +4175,252 @@ function isEditSessionActive() {
|
|
|
4175
4175
|
return new URLSearchParams(q).get("mode") === "edit";
|
|
4176
4176
|
}
|
|
4177
4177
|
|
|
4178
|
+
// src/lib/sections.ts
|
|
4179
|
+
function titleCaseSectionId(id) {
|
|
4180
|
+
return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4181
|
+
}
|
|
4182
|
+
function parseSectionsFromRoot(root) {
|
|
4183
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4184
|
+
const sections = [];
|
|
4185
|
+
for (const el of root.querySelectorAll("[data-ohw-section]")) {
|
|
4186
|
+
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
4187
|
+
if (!id || seen.has(id)) continue;
|
|
4188
|
+
seen.add(id);
|
|
4189
|
+
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
4190
|
+
sections.push({ id, label });
|
|
4191
|
+
}
|
|
4192
|
+
return sections;
|
|
4193
|
+
}
|
|
4194
|
+
function collectSectionsFromDom() {
|
|
4195
|
+
if (typeof document === "undefined") return [];
|
|
4196
|
+
return parseSectionsFromRoot(document);
|
|
4197
|
+
}
|
|
4198
|
+
function parseSectionsFromHtml(html) {
|
|
4199
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
4200
|
+
return parseSectionsFromRoot(doc);
|
|
4201
|
+
}
|
|
4202
|
+
|
|
4203
|
+
// src/ui/link-modal/linkModal.utils.ts
|
|
4204
|
+
function parseTarget(target) {
|
|
4205
|
+
if (!target) return { pageRoute: "", sectionId: null };
|
|
4206
|
+
const hashIndex = target.indexOf("#");
|
|
4207
|
+
if (hashIndex === -1) return { pageRoute: target, sectionId: null };
|
|
4208
|
+
return {
|
|
4209
|
+
pageRoute: target.slice(0, hashIndex),
|
|
4210
|
+
sectionId: target.slice(hashIndex + 1) || null
|
|
4211
|
+
};
|
|
4212
|
+
}
|
|
4213
|
+
function normalizePath(path) {
|
|
4214
|
+
const trimmed = path.trim();
|
|
4215
|
+
if (!trimmed) return "/";
|
|
4216
|
+
try {
|
|
4217
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
4218
|
+
return new URL(trimmed).pathname || "/";
|
|
4219
|
+
}
|
|
4220
|
+
} catch {
|
|
4221
|
+
}
|
|
4222
|
+
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
4223
|
+
if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
|
|
4224
|
+
return withSlash;
|
|
4225
|
+
}
|
|
4226
|
+
function findPageByPath(pages, path) {
|
|
4227
|
+
const normalized = normalizePath(path);
|
|
4228
|
+
return pages.find((p) => normalizePath(p.path) === normalized);
|
|
4229
|
+
}
|
|
4230
|
+
function inferPageTitleFromPath(path) {
|
|
4231
|
+
const normalized = normalizePath(path);
|
|
4232
|
+
if (normalized === "/") return "Home";
|
|
4233
|
+
const segment = normalized.split("/").filter(Boolean).pop() ?? "";
|
|
4234
|
+
if (!segment) return "Home";
|
|
4235
|
+
return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
4236
|
+
}
|
|
4237
|
+
function isInternalPath(route) {
|
|
4238
|
+
const trimmed = route.trim();
|
|
4239
|
+
if (!trimmed) return false;
|
|
4240
|
+
if (/^https?:\/\//i.test(trimmed)) return false;
|
|
4241
|
+
if (trimmed.startsWith("/")) return true;
|
|
4242
|
+
if (trimmed.includes(" ") || trimmed.includes(".")) return false;
|
|
4243
|
+
return true;
|
|
4244
|
+
}
|
|
4245
|
+
function resolvePage(route, pages) {
|
|
4246
|
+
const found = findPageByPath(pages, route);
|
|
4247
|
+
if (found) return { path: found.path, title: found.title };
|
|
4248
|
+
return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
|
|
4249
|
+
}
|
|
4250
|
+
function getSectionsForPath(sectionsByPath, pageRoute) {
|
|
4251
|
+
if (!sectionsByPath) return [];
|
|
4252
|
+
const normalized = normalizePath(pageRoute);
|
|
4253
|
+
return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? [];
|
|
4254
|
+
}
|
|
4255
|
+
function buildTarget(page, section) {
|
|
4256
|
+
if (section) return `${page.path}#${section.id}`;
|
|
4257
|
+
return page.path;
|
|
4258
|
+
}
|
|
4259
|
+
function isValidUrl(urlString) {
|
|
4260
|
+
try {
|
|
4261
|
+
const url = new URL(urlString);
|
|
4262
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
4263
|
+
const hostname = url.hostname;
|
|
4264
|
+
if (!hostname || hostname.length === 0) return false;
|
|
4265
|
+
if (hostname.includes(" ")) return false;
|
|
4266
|
+
return hostname.includes(".") || hostname === "localhost";
|
|
4267
|
+
} catch {
|
|
4268
|
+
return false;
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
function normalizeUrl(value) {
|
|
4272
|
+
const trimmed = value.trim();
|
|
4273
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
|
|
4274
|
+
return `https://${trimmed}`;
|
|
4275
|
+
}
|
|
4276
|
+
function validateUrlInput(value, pages, allPages) {
|
|
4277
|
+
if (!value.trim()) return "";
|
|
4278
|
+
const filtered = pages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase()));
|
|
4279
|
+
if (filtered.length > 0) return "";
|
|
4280
|
+
const matchingPage = allPages.find((p) => p.title.toLowerCase() === value.toLowerCase());
|
|
4281
|
+
if (matchingPage) return "";
|
|
4282
|
+
if (!isValidUrl(normalizeUrl(value))) {
|
|
4283
|
+
return "Please enter a valid URL (e.g., https://example.com)";
|
|
4284
|
+
}
|
|
4285
|
+
return "";
|
|
4286
|
+
}
|
|
4287
|
+
function getEditModeInitialState(target, pages, sections) {
|
|
4288
|
+
const empty = {
|
|
4289
|
+
searchValue: "",
|
|
4290
|
+
selectedPage: null,
|
|
4291
|
+
selectedSection: null,
|
|
4292
|
+
step: "input"
|
|
4293
|
+
};
|
|
4294
|
+
if (!target) return empty;
|
|
4295
|
+
if (isValidUrl(normalizeUrl(target))) {
|
|
4296
|
+
return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4297
|
+
}
|
|
4298
|
+
const { pageRoute, sectionId } = parseTarget(target);
|
|
4299
|
+
if (sectionId) {
|
|
4300
|
+
if (isInternalPath(pageRoute)) {
|
|
4301
|
+
const page = resolvePage(pageRoute, pages);
|
|
4302
|
+
const sec = sections.find((s) => s.id === sectionId);
|
|
4303
|
+
return {
|
|
4304
|
+
searchValue: page.title,
|
|
4305
|
+
selectedPage: page,
|
|
4306
|
+
selectedSection: sec ? { id: sec.id, label: sec.label } : null,
|
|
4307
|
+
step: "input"
|
|
4308
|
+
};
|
|
4309
|
+
}
|
|
4310
|
+
return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4311
|
+
}
|
|
4312
|
+
if (isInternalPath(pageRoute)) {
|
|
4313
|
+
const page = resolvePage(pageRoute, pages);
|
|
4314
|
+
return {
|
|
4315
|
+
searchValue: page.title,
|
|
4316
|
+
selectedPage: page,
|
|
4317
|
+
selectedSection: null,
|
|
4318
|
+
step: "input"
|
|
4319
|
+
};
|
|
4320
|
+
}
|
|
4321
|
+
return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4322
|
+
}
|
|
4323
|
+
function filterAvailablePages(pages, existingTargets) {
|
|
4324
|
+
const taken = new Set(existingTargets);
|
|
4325
|
+
return pages.filter((p) => !taken.has(p.path));
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
// src/lib/prefetch-sections.ts
|
|
4329
|
+
function buildPageFetchUrl(path) {
|
|
4330
|
+
const normalized = normalizePath(path);
|
|
4331
|
+
const search = readPreservedSearch();
|
|
4332
|
+
return `${window.location.origin}${normalized}${search}`;
|
|
4333
|
+
}
|
|
4334
|
+
async function loadAllSectionsManifest() {
|
|
4335
|
+
const manifest = await loadSectionsManifest();
|
|
4336
|
+
return manifest ?? {};
|
|
4337
|
+
}
|
|
4338
|
+
async function loadSectionsManifest() {
|
|
4339
|
+
try {
|
|
4340
|
+
const res = await fetch(`${window.location.origin}/ohw-sections.json`, {
|
|
4341
|
+
credentials: "same-origin",
|
|
4342
|
+
cache: "no-store"
|
|
4343
|
+
});
|
|
4344
|
+
if (!res.ok) return null;
|
|
4345
|
+
const data = await res.json();
|
|
4346
|
+
return data && typeof data === "object" ? data : null;
|
|
4347
|
+
} catch {
|
|
4348
|
+
return null;
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
async function prefetchSectionsFromHtml(paths) {
|
|
4352
|
+
const pairs = await Promise.all(
|
|
4353
|
+
paths.map(async (path) => {
|
|
4354
|
+
try {
|
|
4355
|
+
const res = await fetch(buildPageFetchUrl(path), {
|
|
4356
|
+
credentials: "same-origin",
|
|
4357
|
+
cache: "no-store",
|
|
4358
|
+
headers: { Accept: "text/html" }
|
|
4359
|
+
});
|
|
4360
|
+
if (!res.ok) return [path, []];
|
|
4361
|
+
const html = await res.text();
|
|
4362
|
+
return [path, parseSectionsFromHtml(html)];
|
|
4363
|
+
} catch {
|
|
4364
|
+
return [path, []];
|
|
4365
|
+
}
|
|
4366
|
+
})
|
|
4367
|
+
);
|
|
4368
|
+
const out = {};
|
|
4369
|
+
for (const [path, sections] of pairs) {
|
|
4370
|
+
if (sections.length > 0) out[path] = sections;
|
|
4371
|
+
}
|
|
4372
|
+
return out;
|
|
4373
|
+
}
|
|
4374
|
+
async function prefetchSectionsForPaths(paths) {
|
|
4375
|
+
const unique = [...new Set(paths.map((p) => normalizePath(p)))];
|
|
4376
|
+
const out = {};
|
|
4377
|
+
const manifest = await loadSectionsManifest();
|
|
4378
|
+
if (manifest) {
|
|
4379
|
+
const keys = unique.length > 0 ? unique : Object.keys(manifest);
|
|
4380
|
+
for (const path of keys) {
|
|
4381
|
+
const sections = manifest[path];
|
|
4382
|
+
if (sections?.length) out[path] = sections;
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
const missing = unique.filter((path) => !out[path]?.length);
|
|
4386
|
+
if (missing.length > 0) {
|
|
4387
|
+
Object.assign(out, await prefetchSectionsFromHtml(missing));
|
|
4388
|
+
}
|
|
4389
|
+
return out;
|
|
4390
|
+
}
|
|
4391
|
+
function mergePrefetchedSections(prev, fetched, livePath, liveSections) {
|
|
4392
|
+
const next = { ...prev, ...fetched };
|
|
4393
|
+
if (liveSections.length > 0) next[normalizePath(livePath)] = liveSections;
|
|
4394
|
+
return next;
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4397
|
+
// src/lib/scroll-to-section.ts
|
|
4398
|
+
var MAX_ATTEMPTS = 12;
|
|
4399
|
+
var RETRY_MS = 100;
|
|
4400
|
+
function getHashSectionId() {
|
|
4401
|
+
if (typeof window === "undefined") return null;
|
|
4402
|
+
const id = window.location.hash.replace(/^#/, "").trim();
|
|
4403
|
+
return id || null;
|
|
4404
|
+
}
|
|
4405
|
+
function scrollToOhwSection(sectionId, behavior = "smooth") {
|
|
4406
|
+
if (!sectionId || typeof document === "undefined") return false;
|
|
4407
|
+
const el = document.querySelector(`[data-ohw-section="${CSS.escape(sectionId)}"]`);
|
|
4408
|
+
if (!el) return false;
|
|
4409
|
+
el.scrollIntoView({ behavior, block: "start" });
|
|
4410
|
+
return true;
|
|
4411
|
+
}
|
|
4412
|
+
function scrollToHashSectionWhenReady(behavior = "smooth") {
|
|
4413
|
+
const id = getHashSectionId();
|
|
4414
|
+
if (!id) return;
|
|
4415
|
+
let attempt = 0;
|
|
4416
|
+
const tick = () => {
|
|
4417
|
+
if (scrollToOhwSection(id, behavior)) return;
|
|
4418
|
+
attempt += 1;
|
|
4419
|
+
if (attempt < MAX_ATTEMPTS) window.setTimeout(tick, RETRY_MS);
|
|
4420
|
+
};
|
|
4421
|
+
tick();
|
|
4422
|
+
}
|
|
4423
|
+
|
|
4178
4424
|
// src/ui/link-modal/calcPopoverPos.ts
|
|
4179
4425
|
function calcPopoverPos(rect, parentScroll, approxW = 483, approxH = 320) {
|
|
4180
4426
|
const GAP = 8;
|
|
@@ -4336,7 +4582,7 @@ function SectionPickerList({ sections, onSelect }) {
|
|
|
4336
4582
|
}
|
|
4337
4583
|
|
|
4338
4584
|
// src/ui/link-modal/UrlOrPageInput.tsx
|
|
4339
|
-
import {
|
|
4585
|
+
import { useId, useRef as useRef2, useState as useState3 } from "react";
|
|
4340
4586
|
|
|
4341
4587
|
// src/ui/input.tsx
|
|
4342
4588
|
import * as React4 from "react";
|
|
@@ -4433,12 +4679,7 @@ function UrlOrPageInput({
|
|
|
4433
4679
|
}) {
|
|
4434
4680
|
const inputId = useId();
|
|
4435
4681
|
const inputRef = useRef2(null);
|
|
4436
|
-
const [isEditing, setIsEditing] = useState3(false);
|
|
4437
4682
|
const [isFocused, setIsFocused] = useState3(false);
|
|
4438
|
-
const showFilledDisplay = Boolean(selectedPage) && !isEditing && !readOnly;
|
|
4439
|
-
useEffect2(() => {
|
|
4440
|
-
if (selectedPage) setIsEditing(false);
|
|
4441
|
-
}, [selectedPage]);
|
|
4442
4683
|
const openDropdown = () => {
|
|
4443
4684
|
if (readOnly || filteredPages.length === 0) return;
|
|
4444
4685
|
onDropdownOpenChange(true);
|
|
@@ -4449,9 +4690,10 @@ function UrlOrPageInput({
|
|
|
4449
4690
|
if (readOnly || filteredPages.length === 0) return;
|
|
4450
4691
|
onDropdownOpenChange(!dropdownOpen);
|
|
4451
4692
|
};
|
|
4452
|
-
const
|
|
4453
|
-
|
|
4454
|
-
|
|
4693
|
+
const clearSelection = (e) => {
|
|
4694
|
+
e.preventDefault();
|
|
4695
|
+
e.stopPropagation();
|
|
4696
|
+
onInputChange("");
|
|
4455
4697
|
requestAnimationFrame(() => inputRef.current?.focus());
|
|
4456
4698
|
};
|
|
4457
4699
|
const fieldClassName = cn(
|
|
@@ -4461,62 +4703,41 @@ function UrlOrPageInput({
|
|
|
4461
4703
|
return /* @__PURE__ */ jsxs6("div", { className: "flex w-full flex-col gap-2 p-0", children: [
|
|
4462
4704
|
/* @__PURE__ */ jsx12(Label, { htmlFor: inputId, className: cn(urlError && "text-destructive"), children: "Destination" }),
|
|
4463
4705
|
/* @__PURE__ */ jsxs6(Popover, { open: dropdownOpen && !readOnly, onOpenChange: onDropdownOpenChange, children: [
|
|
4464
|
-
/* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children:
|
|
4465
|
-
"
|
|
4466
|
-
{
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
}
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
id: inputId,
|
|
4500
|
-
value,
|
|
4501
|
-
onChange: (e) => onInputChange(e.target.value),
|
|
4502
|
-
onFocus: () => {
|
|
4503
|
-
setIsFocused(true);
|
|
4504
|
-
setIsEditing(true);
|
|
4505
|
-
openDropdown();
|
|
4506
|
-
},
|
|
4507
|
-
onBlur: () => setIsFocused(false),
|
|
4508
|
-
placeholder,
|
|
4509
|
-
className: cn(
|
|
4510
|
-
"min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0",
|
|
4511
|
-
value ? "text-foreground" : "text-muted-foreground placeholder:text-muted-foreground"
|
|
4512
|
-
)
|
|
4513
|
-
}
|
|
4514
|
-
),
|
|
4515
|
-
!readOnly ? /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown }) : null
|
|
4516
|
-
]
|
|
4517
|
-
}
|
|
4518
|
-
) }),
|
|
4519
|
-
/* @__PURE__ */ jsx12(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ jsxs6(
|
|
4706
|
+
/* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs6("div", { "data-ohw-link-field": true, className: fieldClassName, children: [
|
|
4707
|
+
selectedPage ? /* @__PURE__ */ jsx12("div", { className: "flex shrink-0 items-center pr-2", children: /* @__PURE__ */ jsx12(IconFile, { className: "text-foreground", "aria-hidden": true }) }) : null,
|
|
4708
|
+
readOnly ? /* @__PURE__ */ jsx12("span", { className: "min-w-0 flex-1 truncate text-sm leading-5 text-foreground", children: selectedPage?.title ?? value }) : /* @__PURE__ */ jsx12(
|
|
4709
|
+
Input,
|
|
4710
|
+
{
|
|
4711
|
+
ref: inputRef,
|
|
4712
|
+
id: inputId,
|
|
4713
|
+
value,
|
|
4714
|
+
onChange: (e) => onInputChange(e.target.value),
|
|
4715
|
+
onFocus: () => {
|
|
4716
|
+
setIsFocused(true);
|
|
4717
|
+
if (!selectedPage) openDropdown();
|
|
4718
|
+
},
|
|
4719
|
+
onBlur: () => setIsFocused(false),
|
|
4720
|
+
placeholder,
|
|
4721
|
+
className: cn(
|
|
4722
|
+
"min-w-0 flex-1 truncate border-0 bg-transparent p-0 text-sm font-normal leading-5 shadow-none outline-none ring-0 caret-foreground",
|
|
4723
|
+
value ? "text-foreground" : "text-muted-foreground placeholder:text-muted-foreground"
|
|
4724
|
+
)
|
|
4725
|
+
}
|
|
4726
|
+
),
|
|
4727
|
+
selectedPage && !readOnly ? /* @__PURE__ */ jsx12(
|
|
4728
|
+
"button",
|
|
4729
|
+
{
|
|
4730
|
+
type: "button",
|
|
4731
|
+
className: "flex shrink-0 items-center justify-center rounded-sm p-0.5 text-muted-foreground outline-none hover:bg-muted/50 hover:text-foreground",
|
|
4732
|
+
onMouseDown: clearSelection,
|
|
4733
|
+
"aria-label": "Clear selected page",
|
|
4734
|
+
tabIndex: -1,
|
|
4735
|
+
children: /* @__PURE__ */ jsx12(IconX, { "aria-hidden": true })
|
|
4736
|
+
}
|
|
4737
|
+
) : null,
|
|
4738
|
+
!readOnly ? /* @__PURE__ */ jsx12(FieldChevron, { onClick: toggleDropdown }) : null
|
|
4739
|
+
] }) }),
|
|
4740
|
+
filteredPages.length > 0 && /* @__PURE__ */ jsx12(PopoverContent, { "data-ohw-link-popover-root": "", onOpenAutoFocus: (e) => e.preventDefault(), children: filteredPages.map((page) => /* @__PURE__ */ jsxs6(
|
|
4520
4741
|
"button",
|
|
4521
4742
|
{
|
|
4522
4743
|
type: "button",
|
|
@@ -4535,139 +4756,12 @@ function UrlOrPageInput({
|
|
|
4535
4756
|
}
|
|
4536
4757
|
|
|
4537
4758
|
// src/ui/link-modal/useLinkModalState.ts
|
|
4538
|
-
import { useCallback, useEffect as
|
|
4539
|
-
|
|
4540
|
-
// src/ui/link-modal/linkModal.utils.ts
|
|
4541
|
-
function parseTarget(target) {
|
|
4542
|
-
if (!target) return { pageRoute: "", sectionId: null };
|
|
4543
|
-
const hashIndex = target.indexOf("#");
|
|
4544
|
-
if (hashIndex === -1) return { pageRoute: target, sectionId: null };
|
|
4545
|
-
return {
|
|
4546
|
-
pageRoute: target.slice(0, hashIndex),
|
|
4547
|
-
sectionId: target.slice(hashIndex + 1) || null
|
|
4548
|
-
};
|
|
4549
|
-
}
|
|
4550
|
-
function normalizePath(path) {
|
|
4551
|
-
const trimmed = path.trim();
|
|
4552
|
-
if (!trimmed) return "/";
|
|
4553
|
-
try {
|
|
4554
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
4555
|
-
return new URL(trimmed).pathname || "/";
|
|
4556
|
-
}
|
|
4557
|
-
} catch {
|
|
4558
|
-
}
|
|
4559
|
-
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
4560
|
-
if (withSlash.length > 1 && withSlash.endsWith("/")) return withSlash.slice(0, -1);
|
|
4561
|
-
return withSlash;
|
|
4562
|
-
}
|
|
4563
|
-
function findPageByPath(pages, path) {
|
|
4564
|
-
const normalized = normalizePath(path);
|
|
4565
|
-
return pages.find((p) => normalizePath(p.path) === normalized);
|
|
4566
|
-
}
|
|
4567
|
-
function inferPageTitleFromPath(path) {
|
|
4568
|
-
const normalized = normalizePath(path);
|
|
4569
|
-
if (normalized === "/") return "Home";
|
|
4570
|
-
const segment = normalized.split("/").filter(Boolean).pop() ?? "";
|
|
4571
|
-
if (!segment) return "Home";
|
|
4572
|
-
return segment.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
4573
|
-
}
|
|
4574
|
-
function isInternalPath(route) {
|
|
4575
|
-
const trimmed = route.trim();
|
|
4576
|
-
if (!trimmed) return false;
|
|
4577
|
-
if (/^https?:\/\//i.test(trimmed)) return false;
|
|
4578
|
-
if (trimmed.startsWith("/")) return true;
|
|
4579
|
-
if (trimmed.includes(" ") || trimmed.includes(".")) return false;
|
|
4580
|
-
return true;
|
|
4581
|
-
}
|
|
4582
|
-
function resolvePage(route, pages) {
|
|
4583
|
-
const found = findPageByPath(pages, route);
|
|
4584
|
-
if (found) return { path: found.path, title: found.title };
|
|
4585
|
-
return { path: normalizePath(route), title: inferPageTitleFromPath(route) };
|
|
4586
|
-
}
|
|
4587
|
-
function getSectionsForPath(sectionsByPath, pageRoute, fallback) {
|
|
4588
|
-
if (!sectionsByPath) return fallback;
|
|
4589
|
-
const normalized = normalizePath(pageRoute);
|
|
4590
|
-
return sectionsByPath[normalized] ?? sectionsByPath[pageRoute] ?? fallback;
|
|
4591
|
-
}
|
|
4592
|
-
function buildTarget(page, section) {
|
|
4593
|
-
if (section) return `${page.path}#${section.id}`;
|
|
4594
|
-
return page.path;
|
|
4595
|
-
}
|
|
4596
|
-
function isValidUrl(urlString) {
|
|
4597
|
-
try {
|
|
4598
|
-
const url = new URL(urlString);
|
|
4599
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
4600
|
-
const hostname = url.hostname;
|
|
4601
|
-
if (!hostname || hostname.length === 0) return false;
|
|
4602
|
-
if (hostname.includes(" ")) return false;
|
|
4603
|
-
return hostname.includes(".") || hostname === "localhost";
|
|
4604
|
-
} catch {
|
|
4605
|
-
return false;
|
|
4606
|
-
}
|
|
4607
|
-
}
|
|
4608
|
-
function normalizeUrl(value) {
|
|
4609
|
-
const trimmed = value.trim();
|
|
4610
|
-
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
|
|
4611
|
-
return `https://${trimmed}`;
|
|
4612
|
-
}
|
|
4613
|
-
function validateUrlInput(value, pages, allPages) {
|
|
4614
|
-
if (!value.trim()) return "";
|
|
4615
|
-
const filtered = pages.filter((p) => p.title.toLowerCase().startsWith(value.toLowerCase()));
|
|
4616
|
-
if (filtered.length > 0) return "";
|
|
4617
|
-
const matchingPage = allPages.find((p) => p.title.toLowerCase() === value.toLowerCase());
|
|
4618
|
-
if (matchingPage) return "";
|
|
4619
|
-
if (!isValidUrl(normalizeUrl(value))) {
|
|
4620
|
-
return "Please enter a valid URL (e.g., https://example.com)";
|
|
4621
|
-
}
|
|
4622
|
-
return "";
|
|
4623
|
-
}
|
|
4624
|
-
function getEditModeInitialState(target, pages, sections) {
|
|
4625
|
-
const empty = {
|
|
4626
|
-
searchValue: "",
|
|
4627
|
-
selectedPage: null,
|
|
4628
|
-
selectedSection: null,
|
|
4629
|
-
step: "input"
|
|
4630
|
-
};
|
|
4631
|
-
if (!target) return empty;
|
|
4632
|
-
if (isValidUrl(normalizeUrl(target))) {
|
|
4633
|
-
return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4634
|
-
}
|
|
4635
|
-
const { pageRoute, sectionId } = parseTarget(target);
|
|
4636
|
-
if (sectionId) {
|
|
4637
|
-
if (isInternalPath(pageRoute)) {
|
|
4638
|
-
const page = resolvePage(pageRoute, pages);
|
|
4639
|
-
const sec = sections.find((s) => s.id === sectionId);
|
|
4640
|
-
return {
|
|
4641
|
-
searchValue: page.title,
|
|
4642
|
-
selectedPage: page,
|
|
4643
|
-
selectedSection: sec ? { id: sec.id, label: sec.label } : null,
|
|
4644
|
-
step: "input"
|
|
4645
|
-
};
|
|
4646
|
-
}
|
|
4647
|
-
return { searchValue: pageRoute || target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4648
|
-
}
|
|
4649
|
-
if (isInternalPath(pageRoute)) {
|
|
4650
|
-
const page = resolvePage(pageRoute, pages);
|
|
4651
|
-
return {
|
|
4652
|
-
searchValue: page.title,
|
|
4653
|
-
selectedPage: page,
|
|
4654
|
-
selectedSection: null,
|
|
4655
|
-
step: "input"
|
|
4656
|
-
};
|
|
4657
|
-
}
|
|
4658
|
-
return { searchValue: target, selectedPage: null, selectedSection: null, step: "input" };
|
|
4659
|
-
}
|
|
4660
|
-
function filterAvailablePages(pages, existingTargets) {
|
|
4661
|
-
const taken = new Set(existingTargets);
|
|
4662
|
-
return pages.filter((p) => !taken.has(p.path));
|
|
4663
|
-
}
|
|
4664
|
-
|
|
4665
|
-
// src/ui/link-modal/useLinkModalState.ts
|
|
4759
|
+
import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useState as useState4 } from "react";
|
|
4666
4760
|
function useLinkModalState({
|
|
4667
4761
|
open,
|
|
4668
4762
|
mode,
|
|
4669
4763
|
pages,
|
|
4670
|
-
sections,
|
|
4764
|
+
sections: _sections,
|
|
4671
4765
|
sectionsByPath,
|
|
4672
4766
|
initialTarget,
|
|
4673
4767
|
existingTargets,
|
|
@@ -4692,11 +4786,11 @@ function useLinkModalState({
|
|
|
4692
4786
|
setDropdownOpen(false);
|
|
4693
4787
|
setUrlError("");
|
|
4694
4788
|
}, []);
|
|
4695
|
-
|
|
4789
|
+
useEffect2(() => {
|
|
4696
4790
|
if (!open) return;
|
|
4697
4791
|
if (mode === "edit" && initialTarget) {
|
|
4698
4792
|
const { pageRoute } = parseTarget(initialTarget);
|
|
4699
|
-
const targetSections = getSectionsForPath(sectionsByPath, pageRoute
|
|
4793
|
+
const targetSections = getSectionsForPath(sectionsByPath, pageRoute);
|
|
4700
4794
|
const init = getEditModeInitialState(initialTarget, pages, targetSections);
|
|
4701
4795
|
setSearchValue(init.searchValue);
|
|
4702
4796
|
setSelectedPage(init.selectedPage);
|
|
@@ -4707,21 +4801,19 @@ function useLinkModalState({
|
|
|
4707
4801
|
}
|
|
4708
4802
|
setDropdownOpen(false);
|
|
4709
4803
|
setUrlError("");
|
|
4710
|
-
}, [open, mode, initialTarget, pages,
|
|
4804
|
+
}, [open, mode, initialTarget, pages, sectionsByPath, reset]);
|
|
4711
4805
|
const filteredPages = useMemo2(() => {
|
|
4712
4806
|
if (!searchValue.trim()) return availablePages;
|
|
4713
4807
|
return availablePages.filter((p) => p.title.toLowerCase().startsWith(searchValue.toLowerCase()));
|
|
4714
4808
|
}, [availablePages, searchValue]);
|
|
4715
4809
|
const activeSections = useMemo2(() => {
|
|
4716
|
-
if (selectedPage
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
return sections;
|
|
4720
|
-
}, [selectedPage, sectionsByPath, sections]);
|
|
4810
|
+
if (!selectedPage) return [];
|
|
4811
|
+
return getSectionsForPath(sectionsByPath, selectedPage.path);
|
|
4812
|
+
}, [selectedPage, sectionsByPath]);
|
|
4721
4813
|
const showBreadcrumb = step === "confirmed" && selectedPage && selectedSection;
|
|
4722
4814
|
const showSectionPicker = step === "sectionPicker" && selectedPage;
|
|
4723
4815
|
const showSectionRow = mode === "edit" && selectedPage && selectedSection && step === "input";
|
|
4724
|
-
const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow;
|
|
4816
|
+
const showChooseSection = step === "input" && selectedPage && !selectedSection && !showSectionRow && activeSections.length > 0;
|
|
4725
4817
|
const handleInputChange = (value) => {
|
|
4726
4818
|
setSearchValue(value);
|
|
4727
4819
|
setSelectedPage(null);
|
|
@@ -4985,11 +5077,13 @@ var DEV_SECTIONS_BY_PATH = {
|
|
|
4985
5077
|
],
|
|
4986
5078
|
"/classes": [{ id: "class-library", label: "Class Library" }],
|
|
4987
5079
|
"/pricing": [
|
|
5080
|
+
{ id: "page-header", label: "Page Header" },
|
|
5081
|
+
{ id: "free-class-cta", label: "Free Class" },
|
|
4988
5082
|
{ id: "benefits-marquee", label: "Benefits" },
|
|
4989
5083
|
{ id: "monthly-memberships", label: "Memberships" },
|
|
4990
|
-
{ id: "package-matcher", label: "Packages" },
|
|
4991
5084
|
{ id: "specialty-programs", label: "Specialty Programs" },
|
|
4992
|
-
{ id: "
|
|
5085
|
+
{ id: "package-matcher", label: "Packages" },
|
|
5086
|
+
{ id: "wordmark", label: "Wordmark" }
|
|
4993
5087
|
],
|
|
4994
5088
|
"/studio": [
|
|
4995
5089
|
{ id: "studio-intro", label: "Studio Intro" },
|
|
@@ -5298,15 +5392,8 @@ function getHrefKeyFromElement(el) {
|
|
|
5298
5392
|
if (!key) return null;
|
|
5299
5393
|
return { anchor, key };
|
|
5300
5394
|
}
|
|
5301
|
-
function titleCaseSectionId(id) {
|
|
5302
|
-
return id.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
5303
|
-
}
|
|
5304
5395
|
function collectSections() {
|
|
5305
|
-
return
|
|
5306
|
-
const id = el.getAttribute("data-ohw-section") ?? "";
|
|
5307
|
-
const label = el.getAttribute("data-ohw-section-label") ?? titleCaseSectionId(id);
|
|
5308
|
-
return { id, label };
|
|
5309
|
-
});
|
|
5396
|
+
return collectSectionsFromDom();
|
|
5310
5397
|
}
|
|
5311
5398
|
var FORCE_PSEUDO_STATES = [
|
|
5312
5399
|
{ pseudo: ":hover", state: "hover" },
|
|
@@ -5669,7 +5756,7 @@ function OhhwellsBridge() {
|
|
|
5669
5756
|
const searchParams = useSearchParams();
|
|
5670
5757
|
const isEditMode = isEditSessionActive();
|
|
5671
5758
|
const [bridgeRoot, setBridgeRoot] = useState5(null);
|
|
5672
|
-
|
|
5759
|
+
useEffect3(() => {
|
|
5673
5760
|
const figtreeFontId = "ohw-figtree-font";
|
|
5674
5761
|
if (!document.getElementById(figtreeFontId)) {
|
|
5675
5762
|
const preconnect1 = Object.assign(document.createElement("link"), { rel: "preconnect", href: "https://fonts.googleapis.com" });
|
|
@@ -5728,6 +5815,7 @@ function OhhwellsBridge() {
|
|
|
5728
5815
|
const [linkPopover, setLinkPopover] = useState5(null);
|
|
5729
5816
|
const [sitePages, setSitePages] = useState5([]);
|
|
5730
5817
|
const [sectionsByPath, setSectionsByPath] = useState5({});
|
|
5818
|
+
const sectionsPrefetchGenRef = useRef3(0);
|
|
5731
5819
|
const setLinkPopoverRef = useRef3(setLinkPopover);
|
|
5732
5820
|
const linkPopoverPanelRef = useRef3(null);
|
|
5733
5821
|
const linkPopoverOpenRef = useRef3(false);
|
|
@@ -5736,7 +5824,36 @@ function OhhwellsBridge() {
|
|
|
5736
5824
|
const bumpLinkPopoverGrace = () => {
|
|
5737
5825
|
linkPopoverGraceUntilRef.current = Date.now() + 350;
|
|
5738
5826
|
};
|
|
5739
|
-
|
|
5827
|
+
const runSectionsPrefetch = useCallback2((pages) => {
|
|
5828
|
+
if (!isEditMode || shouldUseDevFixtures() || pages.length === 0) return;
|
|
5829
|
+
const gen = ++sectionsPrefetchGenRef.current;
|
|
5830
|
+
const paths = pages.map((p) => p.path);
|
|
5831
|
+
void prefetchSectionsForPaths(paths).then((fetched) => {
|
|
5832
|
+
if (gen !== sectionsPrefetchGenRef.current) return;
|
|
5833
|
+
if (process.env.NODE_ENV === "development") {
|
|
5834
|
+
console.info("[ohhwells-bridge] Sections prefetched for", Object.keys(fetched).join(", ") || "(none)");
|
|
5835
|
+
}
|
|
5836
|
+
setSectionsByPath(
|
|
5837
|
+
(prev) => mergePrefetchedSections(
|
|
5838
|
+
prev,
|
|
5839
|
+
fetched,
|
|
5840
|
+
typeof window !== "undefined" ? window.location.pathname : pathname,
|
|
5841
|
+
collectSections()
|
|
5842
|
+
)
|
|
5843
|
+
);
|
|
5844
|
+
});
|
|
5845
|
+
}, [isEditMode, pathname]);
|
|
5846
|
+
const runSectionsPrefetchRef = useRef3(runSectionsPrefetch);
|
|
5847
|
+
runSectionsPrefetchRef.current = runSectionsPrefetch;
|
|
5848
|
+
useEffect3(() => {
|
|
5849
|
+
if (!linkPopover) return;
|
|
5850
|
+
if (hoveredImageRef.current) {
|
|
5851
|
+
hoveredImageRef.current = null;
|
|
5852
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
5853
|
+
}
|
|
5854
|
+
postToParent({ type: "ow:image-unhover" });
|
|
5855
|
+
}, [linkPopover, postToParent]);
|
|
5856
|
+
useEffect3(() => {
|
|
5740
5857
|
if (!isEditMode) return;
|
|
5741
5858
|
const useFixtures = shouldUseDevFixtures();
|
|
5742
5859
|
if (useFixtures) {
|
|
@@ -5749,19 +5866,26 @@ function OhhwellsBridge() {
|
|
|
5749
5866
|
const onSitePages = (e) => {
|
|
5750
5867
|
if (e.data?.type !== "ow:site-pages" || !Array.isArray(e.data.pages)) return;
|
|
5751
5868
|
if (useFixtures) return;
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
);
|
|
5869
|
+
const mapped = e.data.pages.map((p) => ({
|
|
5870
|
+
path: p.path,
|
|
5871
|
+
title: p.title
|
|
5872
|
+
}));
|
|
5873
|
+
setSitePages(mapped);
|
|
5874
|
+
runSectionsPrefetchRef.current(mapped);
|
|
5758
5875
|
};
|
|
5759
5876
|
window.addEventListener("message", onSitePages);
|
|
5760
5877
|
if (!useFixtures) postToParent({ type: "ow:request-site-pages" });
|
|
5761
5878
|
return () => window.removeEventListener("message", onSitePages);
|
|
5762
5879
|
}, [isEditMode, postToParent]);
|
|
5763
5880
|
const [sectionGap, setSectionGap] = useState5(null);
|
|
5764
|
-
|
|
5881
|
+
useEffect3(() => {
|
|
5882
|
+
if (!isEditMode || shouldUseDevFixtures()) return;
|
|
5883
|
+
void loadAllSectionsManifest().then((manifest) => {
|
|
5884
|
+
if (Object.keys(manifest).length === 0) return;
|
|
5885
|
+
setSectionsByPath((prev) => ({ ...manifest, ...prev }));
|
|
5886
|
+
});
|
|
5887
|
+
}, [isEditMode]);
|
|
5888
|
+
useEffect3(() => {
|
|
5765
5889
|
const update = () => {
|
|
5766
5890
|
const el = activeElRef.current;
|
|
5767
5891
|
if (el) setToolbarRect(el.getBoundingClientRect());
|
|
@@ -5888,7 +6012,7 @@ function OhhwellsBridge() {
|
|
|
5888
6012
|
cancelled = true;
|
|
5889
6013
|
};
|
|
5890
6014
|
}, [subdomain, isEditMode, pathname]);
|
|
5891
|
-
|
|
6015
|
+
useEffect3(() => {
|
|
5892
6016
|
if (!subdomain || isEditMode) return;
|
|
5893
6017
|
let debounceTimer = null;
|
|
5894
6018
|
const applyFromCache = () => {
|
|
@@ -5931,10 +6055,10 @@ function OhhwellsBridge() {
|
|
|
5931
6055
|
const visible = Boolean(subdomain) && fetchState !== "done";
|
|
5932
6056
|
el.style.display = visible ? "flex" : "none";
|
|
5933
6057
|
}, [subdomain, fetchState]);
|
|
5934
|
-
|
|
6058
|
+
useEffect3(() => {
|
|
5935
6059
|
postToParent({ type: "ow:navigation", path: pathname });
|
|
5936
6060
|
}, [pathname, postToParent]);
|
|
5937
|
-
|
|
6061
|
+
useEffect3(() => {
|
|
5938
6062
|
if (!isEditMode) return;
|
|
5939
6063
|
const measure = () => {
|
|
5940
6064
|
const h = document.body.scrollHeight;
|
|
@@ -5958,7 +6082,7 @@ function OhhwellsBridge() {
|
|
|
5958
6082
|
window.removeEventListener("resize", handleResize);
|
|
5959
6083
|
};
|
|
5960
6084
|
}, [pathname, isEditMode, postToParent]);
|
|
5961
|
-
|
|
6085
|
+
useEffect3(() => {
|
|
5962
6086
|
if (!subdomainFromQuery || isEditMode) return;
|
|
5963
6087
|
const handleClick = (e) => {
|
|
5964
6088
|
const anchor = e.target.closest("a");
|
|
@@ -5974,7 +6098,7 @@ function OhhwellsBridge() {
|
|
|
5974
6098
|
document.addEventListener("click", handleClick, true);
|
|
5975
6099
|
return () => document.removeEventListener("click", handleClick, true);
|
|
5976
6100
|
}, [subdomainFromQuery, isEditMode, router]);
|
|
5977
|
-
|
|
6101
|
+
useEffect3(() => {
|
|
5978
6102
|
if (!isEditMode) {
|
|
5979
6103
|
editStylesRef.current?.base.remove();
|
|
5980
6104
|
editStylesRef.current?.forceHover.remove();
|
|
@@ -6188,6 +6312,15 @@ function OhhwellsBridge() {
|
|
|
6188
6312
|
return smallest;
|
|
6189
6313
|
};
|
|
6190
6314
|
const probeImageAt = (clientX, clientY, isDragOver = false, fromParentViewport = false) => {
|
|
6315
|
+
if (linkPopoverOpenRef.current) {
|
|
6316
|
+
if (hoveredImageRef.current) {
|
|
6317
|
+
hoveredImageRef.current = null;
|
|
6318
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
6319
|
+
resumeAnimTracks();
|
|
6320
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
6321
|
+
}
|
|
6322
|
+
return;
|
|
6323
|
+
}
|
|
6191
6324
|
const toggleEl = document.querySelector("[data-ohw-state-toggle]");
|
|
6192
6325
|
if (toggleEl) {
|
|
6193
6326
|
const { x, y } = toProbeCoords(clientX, clientY, fromParentViewport);
|
|
@@ -6213,6 +6346,15 @@ function OhhwellsBridge() {
|
|
|
6213
6346
|
}
|
|
6214
6347
|
return;
|
|
6215
6348
|
}
|
|
6349
|
+
if (topEl?.closest("[data-ohw-link-popover-root]") || topEl?.closest("[data-ohw-link-modal-root]")) {
|
|
6350
|
+
if (hoveredImageRef.current) {
|
|
6351
|
+
hoveredImageRef.current = null;
|
|
6352
|
+
hoveredImageHasTextOverlapRef.current = false;
|
|
6353
|
+
resumeAnimTracks();
|
|
6354
|
+
postToParentRef.current({ type: "ow:image-unhover" });
|
|
6355
|
+
}
|
|
6356
|
+
return;
|
|
6357
|
+
}
|
|
6216
6358
|
const isStateCardImage = !!imgEl.closest("[data-ohw-editable-state]");
|
|
6217
6359
|
const textEditable = Array.from(
|
|
6218
6360
|
document.querySelectorAll('[data-ohw-editable]:not([data-ohw-editable="image"]):not([data-ohw-editable="bg-image"])')
|
|
@@ -6814,7 +6956,7 @@ function OhhwellsBridge() {
|
|
|
6814
6956
|
if (imageShowTimerRef.current) clearTimeout(imageShowTimerRef.current);
|
|
6815
6957
|
};
|
|
6816
6958
|
}, [isEditMode, refreshStateRules]);
|
|
6817
|
-
|
|
6959
|
+
useEffect3(() => {
|
|
6818
6960
|
if (!isEditMode) return;
|
|
6819
6961
|
document.querySelectorAll("[data-ohw-active-state]").forEach((el) => {
|
|
6820
6962
|
el.removeAttribute("data-ohw-active-state");
|
|
@@ -6831,7 +6973,8 @@ function OhhwellsBridge() {
|
|
|
6831
6973
|
refreshStateRules();
|
|
6832
6974
|
const sections = collectSections();
|
|
6833
6975
|
setSectionsByPath((prev) => {
|
|
6834
|
-
const
|
|
6976
|
+
const pathKey = normalizePath(pathname);
|
|
6977
|
+
const next = { ...prev, [pathKey]: sections };
|
|
6835
6978
|
return shouldUseDevFixtures() ? { ...DEV_SECTIONS_BY_PATH, ...next } : next;
|
|
6836
6979
|
});
|
|
6837
6980
|
postToParent({ type: "ow:ready", version: "1", path: pathname, nodes: collectEditableNodes(), sections });
|
|
@@ -6842,6 +6985,12 @@ function OhhwellsBridge() {
|
|
|
6842
6985
|
clearTimeout(timer);
|
|
6843
6986
|
};
|
|
6844
6987
|
}, [pathname, isEditMode, refreshStateRules, postToParent]);
|
|
6988
|
+
useEffect3(() => {
|
|
6989
|
+
scrollToHashSectionWhenReady();
|
|
6990
|
+
const onHashChange = () => scrollToHashSectionWhenReady();
|
|
6991
|
+
window.addEventListener("hashchange", onHashChange);
|
|
6992
|
+
return () => window.removeEventListener("hashchange", onHashChange);
|
|
6993
|
+
}, [pathname]);
|
|
6845
6994
|
const handleCommand = useCallback2((cmd) => {
|
|
6846
6995
|
document.execCommand(cmd, false);
|
|
6847
6996
|
activeElRef.current?.focus();
|