fix(glossaire): harmonize portal sticky hero and follow behavior
This commit is contained in:
@@ -15,12 +15,14 @@ const {
|
||||
statusKey,
|
||||
level,
|
||||
version,
|
||||
stickyMode = "default",
|
||||
} = Astro.props;
|
||||
|
||||
const lvl = level ?? 1;
|
||||
|
||||
const isGlossaryEdition = String(editionKey ?? "") === "glossaire";
|
||||
const showLevelToggle = !isGlossaryEdition;
|
||||
const stickyModeValue = String(stickyMode || "default");
|
||||
|
||||
const canonical = Astro.site
|
||||
? new URL(Astro.url.pathname, Astro.site).href
|
||||
@@ -201,6 +203,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
data-doc-version={version}
|
||||
data-reading-level={String(lvl)}
|
||||
data-edition-key={String(editionKey ?? "")}
|
||||
data-sticky-mode={stickyModeValue}
|
||||
>
|
||||
<header>
|
||||
<SiteNav />
|
||||
@@ -262,6 +265,10 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
<ProposeModal />
|
||||
|
||||
<style>
|
||||
:global(:root){
|
||||
--glossary-local-sticky-h: 0px;
|
||||
}
|
||||
|
||||
.page{
|
||||
padding: var(--page-gap) 16px 48px;
|
||||
}
|
||||
@@ -479,6 +486,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
const PAGE_GAP = 12;
|
||||
const HYST = 4;
|
||||
|
||||
const body = document.body;
|
||||
const headerEl = document.querySelector("header");
|
||||
const followEl = document.getElementById("reading-follow");
|
||||
const reading = document.querySelector("article.reading");
|
||||
@@ -492,15 +500,94 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
const docTitle = document.body?.dataset?.docTitle || document.title || "Archicratie";
|
||||
const docVersion = document.body?.dataset?.docVersion || "";
|
||||
const docEditionKey = document.body?.dataset?.editionKey || "";
|
||||
const docStickyMode = String(document.body?.dataset?.stickyMode || "default");
|
||||
const isGlossaryEdition = docEditionKey === "glossaire";
|
||||
|
||||
const hasLocalGlossaryFollow =
|
||||
isGlossaryEdition && Boolean(document.getElementById("glossary-hero-follow"));
|
||||
|
||||
const isGlossaryHomeMode =
|
||||
docStickyMode === "glossary-home" || hasLocalGlossaryFollow;
|
||||
|
||||
const isGlossaryEntryMode =
|
||||
isGlossaryEdition && docStickyMode === "glossary-entry";
|
||||
|
||||
const isGlossaryPortalMode =
|
||||
isGlossaryEdition && docStickyMode === "glossary-portal";
|
||||
|
||||
function syncGlossaryFollowState(isOn) {
|
||||
if (!body || !isGlossaryEdition) return;
|
||||
|
||||
body.classList.toggle(
|
||||
"glossary-follow-on",
|
||||
Boolean(isOn && isGlossaryPortalMode)
|
||||
);
|
||||
}
|
||||
|
||||
function px(n){ return `${Math.max(0, Math.round(n))}px`; }
|
||||
function setRootVar(name, value) { document.documentElement.style.setProperty(name, value); }
|
||||
function headerH() { return headerEl ? headerEl.getBoundingClientRect().height : 0; }
|
||||
|
||||
function readPxVar(name) {
|
||||
const raw = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(name)
|
||||
.trim();
|
||||
const n = Number.parseFloat(raw);
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
function autoMeasureGlossaryLocalStickyHeight() {
|
||||
if (isGlossaryEntryMode) {
|
||||
const head = document.querySelector(".glossary-entry-head");
|
||||
return head ? Math.round(head.getBoundingClientRect().height) : 0;
|
||||
}
|
||||
|
||||
if (isGlossaryPortalMode) {
|
||||
const hero =
|
||||
document.querySelector(".scene-hero") ||
|
||||
document.querySelector(".glossary-portal-hero") ||
|
||||
document.querySelector(".glossary-page-hero");
|
||||
|
||||
return hero ? Math.round(hero.getBoundingClientRect().height) : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getLocalStickyHeight() {
|
||||
const explicit = readPxVar("--glossary-local-sticky-h");
|
||||
if (explicit > 0) return explicit;
|
||||
return autoMeasureGlossaryLocalStickyHeight();
|
||||
}
|
||||
|
||||
function syncFollowTop() {
|
||||
if (!followEl) return;
|
||||
|
||||
const localH =
|
||||
(isGlossaryEntryMode || isGlossaryPortalMode)
|
||||
? getLocalStickyHeight()
|
||||
: 0;
|
||||
|
||||
followEl.style.top = px(headerH() + PAGE_GAP + localH);
|
||||
}
|
||||
|
||||
window.__archiSetLocalStickyHeight = (n) => {
|
||||
const value = Math.max(0, Math.round(Number(n) || 0));
|
||||
setRootVar("--glossary-local-sticky-h", px(value));
|
||||
syncFollowTop();
|
||||
try { window.__archiUpdateFollow?.(); } catch {}
|
||||
};
|
||||
|
||||
window.__archiResetLocalStickyHeight = () => {
|
||||
setRootVar("--glossary-local-sticky-h", "0px");
|
||||
syncFollowTop();
|
||||
try { window.__archiUpdateFollow?.(); } catch {}
|
||||
};
|
||||
|
||||
function syncHeaderH() {
|
||||
setRootVar("--sticky-header-h", px(headerH()));
|
||||
setRootVar("--page-gap", px(PAGE_GAP));
|
||||
syncFollowTop();
|
||||
}
|
||||
|
||||
function syncReadingRect() {
|
||||
@@ -543,7 +630,15 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
.getPropertyValue("--sticky-offset-px")
|
||||
.trim();
|
||||
const n = Number.parseFloat(raw);
|
||||
return Number.isFinite(n) ? n : (headerH() + PAGE_GAP);
|
||||
|
||||
if (Number.isFinite(n)) return n;
|
||||
|
||||
const localH =
|
||||
(isGlossaryEntryMode || isGlossaryPortalMode)
|
||||
? getLocalStickyHeight()
|
||||
: 0;
|
||||
|
||||
return headerH() + PAGE_GAP + localH;
|
||||
}
|
||||
|
||||
function absTop(el) {
|
||||
@@ -1362,6 +1457,25 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
safe("reading-follow", () => {
|
||||
if (!reading || !followEl) return;
|
||||
|
||||
// Home du glossaire : le hero local gère déjà son propre follow.
|
||||
// Le follow global doit s'effacer totalement.
|
||||
if (isGlossaryHomeMode) {
|
||||
if (rfH1) rfH1.hidden = true;
|
||||
if (rfH2) rfH2.hidden = true;
|
||||
if (rfH3) rfH3.hidden = true;
|
||||
if (btnTopChapter) btnTopChapter.hidden = true;
|
||||
if (btnTopSection) btnTopSection.hidden = true;
|
||||
|
||||
followEl.classList.remove("is-on");
|
||||
followEl.setAttribute("aria-hidden", "true");
|
||||
followEl.style.display = "none";
|
||||
|
||||
setRootVar("--followbar-h", "0px");
|
||||
setRootVar("--sticky-offset-px", String(Math.round(headerH() + PAGE_GAP)));
|
||||
syncGlossaryFollowState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const h1 = reading.querySelector("h1");
|
||||
|
||||
const h2Anchors = Array.from(reading.querySelectorAll(".details-anchor[id]"))
|
||||
@@ -1386,11 +1500,17 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
.filter(Boolean);
|
||||
|
||||
const h2Plain = Array.from(reading.querySelectorAll("h2[id]"))
|
||||
.map((h2) => ({ id: h2.id, anchor: h2, marker: h2, title: (h2.textContent || "").trim() || "Section", h2 }));
|
||||
.map((h2) => ({
|
||||
id: h2.id,
|
||||
anchor: h2,
|
||||
marker: h2,
|
||||
title: (h2.textContent || "").trim() || "Section",
|
||||
h2,
|
||||
}));
|
||||
|
||||
const H2 = (h2Anchors.length ? h2Anchors : h2Plain)
|
||||
.slice()
|
||||
.sort((a,b) => absTop(a.marker) - absTop(b.marker));
|
||||
.sort((a, b) => absTop(a.marker) - absTop(b.marker));
|
||||
|
||||
const H3 = Array.from(reading.querySelectorAll("h3[id]"))
|
||||
.map((h3) => ({ id: h3.id, el: h3, title: (h3.textContent || "").trim() || "Sous-section" }));
|
||||
@@ -1401,24 +1521,37 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
let lastOpenedH2 = "";
|
||||
|
||||
function computeLineY(followH) {
|
||||
return headerH() + PAGE_GAP + (followH || 0) + HYST;
|
||||
const localH =
|
||||
(isGlossaryEntryMode || isGlossaryPortalMode)
|
||||
? getLocalStickyHeight()
|
||||
: 0;
|
||||
|
||||
return headerH() + PAGE_GAP + localH + (followH || 0) + HYST;
|
||||
}
|
||||
|
||||
function hasCrossed(el, lineY) {
|
||||
return Boolean(el && el.getBoundingClientRect().top <= lineY);
|
||||
}
|
||||
|
||||
function pickH2(lineY) {
|
||||
if (!H2.length) return null;
|
||||
|
||||
let cand = null;
|
||||
for (const t of H2) {
|
||||
const top = t.marker.getBoundingClientRect().top;
|
||||
if (top <= lineY) cand = t;
|
||||
if (hasCrossed(t.marker, lineY)) cand = t;
|
||||
else break;
|
||||
}
|
||||
if (!cand) cand = H2[0];
|
||||
|
||||
// Tant qu’aucun H2 n’a franchi la ligne de suivi,
|
||||
// on ne montre rien.
|
||||
if (!cand) return null;
|
||||
|
||||
const down = (window.scrollY || 0) >= lastY;
|
||||
if (!down && cand && curH2 && cand.id !== curH2.id) {
|
||||
const topNew = cand.marker.getBoundingClientRect().top;
|
||||
if (topNew > lineY - (HYST * 2)) cand = curH2;
|
||||
}
|
||||
|
||||
return cand;
|
||||
}
|
||||
|
||||
@@ -1426,6 +1559,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
if (!start || !el) return false;
|
||||
return Boolean(start.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING);
|
||||
}
|
||||
|
||||
function isBefore(end, el) {
|
||||
if (!end || !el) return true;
|
||||
return Boolean(end.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING);
|
||||
@@ -1452,85 +1586,112 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
|
||||
let cand = null;
|
||||
for (const t of scoped) {
|
||||
const top = t.el.getBoundingClientRect().top;
|
||||
if (top <= lineY) cand = t;
|
||||
if (hasCrossed(t.el, lineY)) cand = t;
|
||||
else break;
|
||||
}
|
||||
|
||||
return cand;
|
||||
}
|
||||
|
||||
function maybeOpenActiveSection(activeH2, lineY) {
|
||||
if (!activeH2 || !activeH2.id) return;
|
||||
|
||||
const pre = activeH2.marker.getBoundingClientRect().top <= (lineY + 140);
|
||||
if (!pre) return;
|
||||
|
||||
if (activeH2.id !== lastOpenedH2) {
|
||||
lastOpenedH2 = activeH2.id;
|
||||
openDetailsIfNeeded(activeH2.anchor || activeH2.h2 || activeH2.marker);
|
||||
}
|
||||
}
|
||||
|
||||
function updateFollow() {
|
||||
if (!followEl) return;
|
||||
function renderPass(lineY) {
|
||||
// Pour le glossaire, on évite le doublon du H1 dans la barre flottante.
|
||||
const showH1 =
|
||||
!isGlossaryEdition &&
|
||||
!!(h1 && h1.getBoundingClientRect().bottom <= (lineY - HYST));
|
||||
|
||||
const baseY = headerH() + PAGE_GAP;
|
||||
const showH1 = !!(h1 && (h1.getBoundingClientRect().bottom <= baseY + HYST));
|
||||
if (rfH1) {
|
||||
rfH1.hidden = !showH1;
|
||||
if (showH1) rfH1.textContent = (h1.textContent || "").trim();
|
||||
}
|
||||
|
||||
if (rfH1 && h1 && showH1) { rfH1.hidden = false; rfH1.textContent = (h1.textContent || "").trim(); }
|
||||
else if (rfH1) rfH1.hidden = true;
|
||||
const nextH2 = pickH2(lineY);
|
||||
maybeOpenActiveSection(nextH2, lineY);
|
||||
|
||||
const lineY0 = computeLineY(0);
|
||||
curH2 = pickH2(lineY0);
|
||||
maybeOpenActiveSection(curH2, lineY0);
|
||||
curH3 = pickH3(lineY0, curH2);
|
||||
const nextH3 = pickH3(lineY, nextH2);
|
||||
|
||||
try { syncTocLocal((curH3 && curH3.id) ? curH3.id : (curH2 && curH2.id) ? curH2.id : ""); } catch {}
|
||||
curH2 = nextH2;
|
||||
curH3 = nextH3;
|
||||
|
||||
if (rfH2 && curH2) { rfH2.hidden = false; rfH2.textContent = curH2.title; }
|
||||
else if (rfH2) rfH2.hidden = true;
|
||||
if (rfH2) {
|
||||
const show = Boolean(curH2 && hasCrossed(curH2.marker, lineY));
|
||||
rfH2.hidden = !show;
|
||||
if (show) rfH2.textContent = curH2.title;
|
||||
}
|
||||
|
||||
if (rfH3 && curH3) { rfH3.hidden = false; rfH3.textContent = curH3.title; }
|
||||
else if (rfH3) rfH3.hidden = true;
|
||||
if (rfH3) {
|
||||
const show = Boolean(curH3 && hasCrossed(curH3.el, lineY));
|
||||
rfH3.hidden = !show;
|
||||
if (show) rfH3.textContent = curH3.title;
|
||||
}
|
||||
|
||||
const any0 = (rfH1 && !rfH1.hidden) || (rfH2 && !rfH2.hidden) || (rfH3 && !rfH3.hidden);
|
||||
followEl.classList.toggle("is-on", Boolean(any0));
|
||||
followEl.setAttribute("aria-hidden", any0 ? "false" : "true");
|
||||
const any =
|
||||
(rfH1 && !rfH1.hidden) ||
|
||||
(rfH2 && !rfH2.hidden) ||
|
||||
(rfH3 && !rfH3.hidden);
|
||||
|
||||
const inner = followEl.querySelector(".reading-follow__inner");
|
||||
const followH = (any0 && inner) ? inner.getBoundingClientRect().height : 0;
|
||||
|
||||
const lineY = computeLineY(followH);
|
||||
curH2 = pickH2(lineY);
|
||||
maybeOpenActiveSection(curH2, lineY);
|
||||
curH3 = pickH3(lineY, curH2);
|
||||
|
||||
try { syncTocLocal((curH3 && curH3.id) ? curH3.id : (curH2 && curH2.id) ? curH2.id : ""); } catch {}
|
||||
|
||||
if (rfH2 && curH2) {
|
||||
const on = curH2.marker.getBoundingClientRect().top <= lineY;
|
||||
rfH2.hidden = !on;
|
||||
if (on) rfH2.textContent = curH2.title;
|
||||
} else if (rfH2) rfH2.hidden = true;
|
||||
|
||||
if (rfH3 && curH3) {
|
||||
const on = curH3.el.getBoundingClientRect().top <= lineY;
|
||||
rfH3.hidden = !on;
|
||||
if (on) rfH3.textContent = curH3.title;
|
||||
} else if (rfH3) rfH3.hidden = true;
|
||||
|
||||
const any = (rfH1 && !rfH1.hidden) || (rfH2 && !rfH2.hidden) || (rfH3 && !rfH3.hidden);
|
||||
followEl.classList.toggle("is-on", Boolean(any));
|
||||
followEl.setAttribute("aria-hidden", any ? "false" : "true");
|
||||
|
||||
try {
|
||||
syncTocLocal(
|
||||
(curH3 && rfH3 && !rfH3.hidden) ? curH3.id :
|
||||
(curH2 && rfH2 && !rfH2.hidden) ? curH2.id :
|
||||
""
|
||||
);
|
||||
} catch {}
|
||||
|
||||
return any;
|
||||
}
|
||||
|
||||
function updateFollow() {
|
||||
followEl.style.display = "";
|
||||
syncFollowTop();
|
||||
|
||||
const lineY0 = computeLineY(0);
|
||||
renderPass(lineY0);
|
||||
|
||||
const inner = followEl.querySelector(".reading-follow__inner");
|
||||
const followH0 =
|
||||
followEl.classList.contains("is-on") && inner
|
||||
? inner.getBoundingClientRect().height
|
||||
: 0;
|
||||
|
||||
const lineY1 = computeLineY(followH0);
|
||||
renderPass(lineY1);
|
||||
|
||||
const inner2 = followEl.querySelector(".reading-follow__inner");
|
||||
const followH2 = (any && inner2) ? inner2.getBoundingClientRect().height : 0;
|
||||
const followH =
|
||||
followEl.classList.contains("is-on") && inner2
|
||||
? inner2.getBoundingClientRect().height
|
||||
: 0;
|
||||
|
||||
setRootVar("--followbar-h", px(followH2));
|
||||
setRootVar("--sticky-offset-px", String(Math.round(headerH() + PAGE_GAP + followH2)));
|
||||
const localH =
|
||||
(isGlossaryEntryMode || isGlossaryPortalMode)
|
||||
? getLocalStickyHeight()
|
||||
: 0;
|
||||
|
||||
setRootVar("--followbar-h", px(followH));
|
||||
setRootVar("--sticky-offset-px", String(Math.round(headerH() + PAGE_GAP + localH + followH)));
|
||||
syncGlossaryFollowState(followEl.classList.contains("is-on") && followH > 0);
|
||||
|
||||
if (btnTopChapter) {
|
||||
btnTopChapter.hidden = !(rfH1 && !rfH1.hidden);
|
||||
}
|
||||
|
||||
if (btnTopChapter) btnTopChapter.hidden = !any;
|
||||
if (btnTopSection) {
|
||||
const ok = Boolean(curH2 && rfH2 && !rfH2.hidden);
|
||||
btnTopSection.hidden = !ok;
|
||||
btnTopSection.hidden = !(curH2 && rfH2 && !rfH2.hidden);
|
||||
}
|
||||
|
||||
lastY = window.scrollY || 0;
|
||||
@@ -1553,27 +1714,44 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
window.addEventListener("resize", onScroll);
|
||||
|
||||
if (rfH1) rfH1.addEventListener("click", () => { if (h1) scrollToElWithOffset(h1, 12, "smooth"); });
|
||||
if (rfH2) rfH2.addEventListener("click", () => {
|
||||
if (!curH2) return;
|
||||
openDetailsIfNeeded(curH2.anchor || curH2.h2 || curH2.marker);
|
||||
scrollToElWithOffset(curH2.marker, 12, "smooth");
|
||||
history.replaceState(null, "", `${window.location.pathname}#${curH2.id}`);
|
||||
});
|
||||
if (rfH3) rfH3.addEventListener("click", () => {
|
||||
if (!curH3) return;
|
||||
openDetailsIfNeeded(curH3.el);
|
||||
scrollToElWithOffset(curH3.el, 12, "smooth");
|
||||
history.replaceState(null, "", `${window.location.pathname}#${curH3.id}`);
|
||||
});
|
||||
if (rfH1) {
|
||||
rfH1.addEventListener("click", () => {
|
||||
if (h1) scrollToElWithOffset(h1, 12, "smooth");
|
||||
});
|
||||
}
|
||||
|
||||
if (btnTopChapter) btnTopChapter.addEventListener("click", () => { if (h1) scrollToElWithOffset(h1, 12, "smooth"); });
|
||||
if (btnTopSection) btnTopSection.addEventListener("click", () => {
|
||||
if (!curH2) return;
|
||||
openDetailsIfNeeded(curH2.anchor || curH2.h2 || curH2.marker);
|
||||
scrollToElWithOffset(curH2.marker, 12, "smooth");
|
||||
history.replaceState(null, "", `${window.location.pathname}#${curH2.id}`);
|
||||
});
|
||||
if (rfH2) {
|
||||
rfH2.addEventListener("click", () => {
|
||||
if (!curH2) return;
|
||||
openDetailsIfNeeded(curH2.anchor || curH2.h2 || curH2.marker);
|
||||
scrollToElWithOffset(curH2.marker, 12, "smooth");
|
||||
history.replaceState(null, "", `${window.location.pathname}#${curH2.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (rfH3) {
|
||||
rfH3.addEventListener("click", () => {
|
||||
if (!curH3) return;
|
||||
openDetailsIfNeeded(curH3.el);
|
||||
scrollToElWithOffset(curH3.el, 12, "smooth");
|
||||
history.replaceState(null, "", `${window.location.pathname}#${curH3.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (btnTopChapter) {
|
||||
btnTopChapter.addEventListener("click", () => {
|
||||
if (h1) scrollToElWithOffset(h1, 12, "smooth");
|
||||
});
|
||||
}
|
||||
|
||||
if (btnTopSection) {
|
||||
btnTopSection.addEventListener("click", () => {
|
||||
if (!curH2) return;
|
||||
openDetailsIfNeeded(curH2.anchor || curH2.h2 || curH2.marker);
|
||||
scrollToElWithOffset(curH2.marker, 12, "smooth");
|
||||
history.replaceState(null, "", `${window.location.pathname}#${curH2.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
updateFollow();
|
||||
|
||||
@@ -1600,6 +1778,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
window.addEventListener("resize", () => {
|
||||
afterReflow(() => {
|
||||
syncHeaderH(); syncReadingRect(); syncHeadingMetrics();
|
||||
syncFollowTop();
|
||||
try { window.__archiUpdateFollow?.(); } catch {}
|
||||
scheduleActive();
|
||||
});
|
||||
@@ -1608,6 +1787,7 @@ const WHOAMI_FORCE_LOCALHOST = (import.meta.env.PUBLIC_WHOAMI_FORCE_LOCALHOST ??
|
||||
window.addEventListener("archicratie:readingLevel", () => {
|
||||
afterReflow(() => {
|
||||
syncHeaderH(); syncReadingRect(); syncHeadingMetrics();
|
||||
syncFollowTop();
|
||||
try { window.__archiUpdateFollow?.(); } catch {}
|
||||
|
||||
const ctx = window.__archiLevelSwitchCtx || null;
|
||||
|
||||
Reference in New Issue
Block a user