refactor: introduce RevealManager and Article DOM context
Decouple the reveal logic from the `Article` component by introducing a `RevealManager` component and an `ArticleDomCtx`. This allows the reveal logic to reactively manage DOM injections and classes without polluting the `Article` component's props or lifecycle. Additionally, implements a robust `cleanupInjections` mechanism to ensure that injected buttons and styling artifacts are properly removed during navigation, role changes, or disconnections.
This commit is contained in:
@@ -66,6 +66,13 @@ export function setLinkPrefill(text: string | null) {
|
||||
setActionPrefill(text ? { command: "/link", text } : null);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DOM markers for injected artifacts (used by cleanup) --------------__________
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const DATA_BUTTON = "data-reveal-button";
|
||||
const DATA_INJECTED = "data-reveal-injected";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// addRevealedClasses — GM injects buttons; non-GM applies revealed/concealed
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -95,10 +102,19 @@ export function addRevealedClasses(
|
||||
completions: CompletionsForInject = { sparkTables: [] },
|
||||
) {
|
||||
const state = journalStreamState;
|
||||
if (!state.connected) return;
|
||||
if (!state.connected) {
|
||||
// Disconnected — scrub all artifacts so the page looks clean
|
||||
cleanupInjections(root);
|
||||
return;
|
||||
}
|
||||
|
||||
const normalized = normalizePath(path);
|
||||
|
||||
// Always clean up previous injections before applying new ones.
|
||||
// This handles role changes, reconnects, and navigation without
|
||||
// leaving stale buttons or classes behind.
|
||||
cleanupInjections(root);
|
||||
|
||||
// ---- GM mode: inject action buttons on headings and spark tables ----
|
||||
if (state.myRole === "gm") {
|
||||
injectActionButtons(root, normalized, completions);
|
||||
@@ -114,6 +130,28 @@ export function addRevealedClasses(
|
||||
applyClasses(root, revealedSet);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cleanup — strip all previously injected artifacts
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function cleanupInjections(root: Element): void {
|
||||
// Remove injected buttons
|
||||
root.querySelectorAll(`[${DATA_BUTTON}]`).forEach((el) => el.remove());
|
||||
|
||||
// Clean up injected classes and inline styles on headings / wrappers
|
||||
root.querySelectorAll(`[${DATA_INJECTED}]`).forEach((el) => {
|
||||
const htmlEl = el as HTMLElement;
|
||||
htmlEl.classList.remove("group", "flex", "items-center");
|
||||
htmlEl.style.position = "";
|
||||
htmlEl.removeAttribute(DATA_INJECTED);
|
||||
});
|
||||
|
||||
// Remove revealed / concealed classes from all elements
|
||||
root
|
||||
.querySelectorAll(".revealed, .concealed")
|
||||
.forEach((el) => el.classList.remove("revealed", "concealed"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Completions payload type (mirrors the completions module shape)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -144,8 +182,11 @@ function injectActionButtons(
|
||||
const headingText = el.id || el.textContent?.trim() || "";
|
||||
if (headingText) {
|
||||
const btn = createLinkButton(normalizedPath, headingText);
|
||||
btn.setAttribute(DATA_BUTTON, "");
|
||||
el.insertBefore(btn, el.firstChild);
|
||||
(el as HTMLElement).classList.add("group", "flex", "items-center");
|
||||
const htmlEl = el as HTMLElement;
|
||||
htmlEl.setAttribute(DATA_INJECTED, "");
|
||||
htmlEl.classList.add("group", "flex", "items-center");
|
||||
}
|
||||
}
|
||||
for (const child of el.children) walk(child);
|
||||
@@ -211,8 +252,10 @@ function injectSparkButtons(
|
||||
|
||||
// Inject the spark button into the <md-table> wrapper
|
||||
const btn = createSparkButton(match.slug);
|
||||
btn.setAttribute(DATA_BUTTON, "");
|
||||
const wrapper = el.parentElement;
|
||||
if (wrapper) {
|
||||
wrapper.setAttribute(DATA_INJECTED, "");
|
||||
wrapper.style.position = "relative";
|
||||
wrapper.classList.add("group");
|
||||
wrapper.insertBefore(btn, wrapper.firstChild);
|
||||
|
||||
Reference in New Issue
Block a user