Refactor zapthreads embed root handling

This commit is contained in:
thePR0M3TH3AN committed 2025-10-13 10:41:20 -04:00
1 parent 321af0ee19
commit 85c3e00d44
1 file changed
+74 -17
+74 -17
View File
@@ -1,3 +1,5 @@
import { escapeHTML } from '../utils/domUtils.js';
/** /**
* DOM helpers for constructing Zapthreads embed structures without relying on the * DOM helpers for constructing Zapthreads embed structures without relying on the
* compiled Svelte runtime helpers that appear in blog.html. * compiled Svelte runtime helpers that appear in blog.html.
@@ -365,9 +367,6 @@ export function createZapthreadsRoot() {
const root = document.createElement('div'); const root = document.createElement('div');
root.id = 'ztr-root'; root.id = 'ztr-root';
const style = document.createElement('style');
root.appendChild(style);
const watermark = document.createElement('div'); const watermark = document.createElement('div');
watermark.className = 'blog-ztr-watermark'; watermark.className = 'blog-ztr-watermark';
root.appendChild(watermark); root.appendChild(watermark);
@@ -654,12 +653,27 @@ export function renderWatermark(watermarkEl) {
watermarkEl.appendChild(label); watermarkEl.appendChild(label);
} }
export function renderAnchorVersion(labelEl, version) {
if (!labelEl) return;
labelEl.innerHTML = `Anchor version: ${escapeHTML(version || 'unknown')}`;
}
export const ZAPTHREADS_EVENT_MOUNTED = 'zapthreads:mounted';
export const ZAPTHREADS_EVENT_UNMOUNTED = 'zapthreads:unmounted';
export const ZAPTHREADS_EVENT_CLEAR_CACHE = 'zapthreads:clear-cache';
export function renderClearCache(buttonEl, clearCache) { export function renderClearCache(buttonEl, clearCache) {
if (!buttonEl) return () => {}; if (!buttonEl) return () => {};
const handler = () => { const handler = () => {
if (typeof clearCache === 'function') { if (typeof clearCache === 'function') {
clearCache(); clearCache();
} }
buttonEl.dispatchEvent(
new CustomEvent(ZAPTHREADS_EVENT_CLEAR_CACHE, {
bubbles: true,
detail: { source: 'button' },
})
);
}; };
buttonEl.addEventListener('click', handler); buttonEl.addEventListener('click', handler);
return () => { return () => {
@@ -667,9 +681,15 @@ export function renderClearCache(buttonEl, clearCache) {
}; };
} }
export function renderAnchorVersion(labelEl, version) { function clearElement(element) {
if (!labelEl) return; if (!element) return;
labelEl.textContent = `Anchor version: ${version || 'unknown'}`; while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
function getBlogAppRoot() {
return document.querySelector('.blog-app-root');
} }
export async function initZapthreadsEmbed({ export async function initZapthreadsEmbed({
@@ -684,12 +704,18 @@ export async function initZapthreadsEmbed({
const data = dataUtils || {}; const data = dataUtils || {};
const state = createState(options); const state = createState(options);
const rootEl = createZapthreadsRoot(); const blogRoot = getBlogAppRoot();
const styleEl = rootEl.querySelector('style'); if (!blogRoot) {
if (styleEl) { throw new Error('initZapthreadsEmbed requires .blog-app-root container');
styleEl.textContent = '#ztr-root { display: block; }';
} }
const existingRoot = blogRoot.querySelector('#ztr-root');
if (existingRoot) {
existingRoot.remove();
}
const rootEl = createZapthreadsRoot();
const watermarkEl = rootEl.querySelector('.blog-ztr-watermark'); const watermarkEl = rootEl.querySelector('.blog-ztr-watermark');
renderWatermark(watermarkEl); renderWatermark(watermarkEl);
@@ -714,25 +740,50 @@ export async function initZapthreadsEmbed({
disposeCallbacks.add(clearCacheCleanup); disposeCallbacks.add(clearCacheCleanup);
const renderError = (message) => { const renderError = (message) => {
contentEl.innerHTML = ''; clearElement(contentEl);
const heading = createZapthreadsErrorHeading(); const heading = createZapthreadsErrorHeading();
heading.textContent = 'Error!'; heading.textContent = 'Error!';
const details = createZapthreadsErrorDetails(); const details = createZapthreadsErrorDetails();
details.querySelector('pre').textContent = message; const pre = details.querySelector('pre');
if (pre) {
pre.innerHTML = escapeHTML(message || '');
}
contentEl.appendChild(heading); contentEl.appendChild(heading);
contentEl.appendChild(details); contentEl.appendChild(details);
}; };
if (state.anchor.type === 'error') { if (state.anchor.type === 'error') {
renderError(state.anchor.value); renderError(state.anchor.value);
root.appendChild(rootEl); blogRoot.appendChild(rootEl);
root.dispatchEvent(
new CustomEvent(ZAPTHREADS_EVENT_MOUNTED, {
bubbles: true,
detail: { anchor: state.anchor, root: rootEl },
})
);
return () => { return () => {
disposeCallbacks.forEach((callback) => callback()); disposeCallbacks.forEach((callback) => callback());
disposeCallbacks.clear(); disposeCallbacks.clear();
if (blogRoot.contains(rootEl)) {
blogRoot.removeChild(rootEl);
}
root.dispatchEvent(
new CustomEvent(ZAPTHREADS_EVENT_UNMOUNTED, {
bubbles: true,
detail: { anchor: state.anchor, root: rootEl },
})
);
}; };
} }
root.appendChild(rootEl); blogRoot.appendChild(rootEl);
root.dispatchEvent(
new CustomEvent(ZAPTHREADS_EVENT_MOUNTED, {
bubbles: true,
detail: { anchor: state.anchor, root: rootEl },
})
);
updateFilterForAnchor(state); updateFilterForAnchor(state);
@@ -764,7 +815,7 @@ export async function initZapthreadsEmbed({
if (typeof data.Na === 'function') { if (typeof data.Na === 'function') {
const parsed = data.Na(event); const parsed = data.Na(event);
if (parsed) { if (parsed) {
titleEl.textContent = parsed.tl || 'Thread'; titleEl.innerHTML = escapeHTML(parsed.tl || 'Thread');
} }
} }
}, },
@@ -785,8 +836,14 @@ export async function initZapthreadsEmbed({
} }
}); });
disposeCallbacks.clear(); disposeCallbacks.clear();
if (root.contains(rootEl)) { if (blogRoot.contains(rootEl)) {
root.removeChild(rootEl); blogRoot.removeChild(rootEl);
} }
root.dispatchEvent(
new CustomEvent(ZAPTHREADS_EVENT_UNMOUNTED, {
bubbles: true,
detail: { anchor: state.anchor, root: rootEl },
})
);
}; };
} }