Browse Source

Refactor comments

imwald
Nuša Pukšič 3 months ago
parent
commit
b3392feb5b
  1. 96
      assets/controllers/comments_mercure_controller.js

96
assets/controllers/comments_mercure_controller.js

@ -7,57 +7,53 @@ export default class extends Controller {
connect() { connect() {
this._debounceId = null; this._debounceId = null;
this._liveRoot = this._findLiveRoot(); this._liveRoot = this._findLiveRoot();
this._liveReady = false;
this._queue = []; // buffer Mercure payloads until live connects
// If the live controller isn't ready yet, wait for it. // 1) Wait for Live to connect (or mark ready if already connected)
if (!this._getLiveController()) { const live = this._getLiveController();
if (live) {
this._liveReady = true;
} else if (this._liveRoot) {
this._onLiveConnect = () => { this._onLiveConnect = () => {
// Once live connects, do an initial render to paint cached HTML this._liveReady = true;
this._renderLiveComponent(); this._flushQueue();
}; };
this._liveRoot?.addEventListener('live:connect', this._onLiveConnect, { once: true }); this._liveRoot.addEventListener('live:connect', this._onLiveConnect, { once: true });
} else {
// Live controller already attached -> initial render now
this._renderLiveComponent();
} }
// Subscribe to Mercure updates // Optional: initial render to paint cached HTML
this._renderWhenReady();
// 2) Subscribe to Mercure
const hubUrl = window.MercureHubUrl || document.querySelector('meta[name="mercure-hub"]')?.content; const hubUrl = window.MercureHubUrl || document.querySelector('meta[name="mercure-hub"]')?.content;
if (!hubUrl) { if (!hubUrl) return;
console.warn("[comments-mercure] Missing Mercure hub URL meta");
this._hideLoading();
return;
}
const topic = `/comments/${this.coordinateValue}`; const topic = `/comments/${this.coordinateValue}`;
const url = new URL(hubUrl); const url = new URL(hubUrl); url.searchParams.append("topic", topic);
url.searchParams.append("topic", topic);
this.eventSource = new EventSource(url.toString()); this.eventSource = new EventSource(url.toString());
this.eventSource.onopen = () => this._debouncedRefresh(50);
this.eventSource.onerror = (e) => console.warn("[comments-mercure] EventSource error", e);
this.eventSource.onmessage = (event) => { this.eventSource.onmessage = (event) => {
console.log("[comments-mercure] Received update", event.data); // buffer if live not ready yet
const live = this._getLiveController(); if (!this._liveReady) {
if (!live) return; this._queue.push(event.data);
return;
// Send the Mercure payload to the component }
// LiveComponent will re-render after the action resolves this._ingest(event.data);
live.action('ingest', { payload: event.data });
}; };
} }
disconnect() { disconnect() {
if (this.eventSource) { try { this.eventSource.close(); } catch {} } if (this.eventSource) try { this.eventSource.close(); } catch {}
if (this._debounceId) { clearTimeout(this._debounceId); } if (this._debounceId) clearTimeout(this._debounceId);
if (this._liveRoot && this._onLiveConnect) { if (this._liveRoot && this._onLiveConnect) {
this._liveRoot.removeEventListener('live:connect', this._onLiveConnect); this._liveRoot.removeEventListener('live:connect', this._onLiveConnect);
} }
} }
// ---- private helpers ----------------------------------------------------- // ---- private -------------------------------------------------------------
_findLiveRoot() { _findLiveRoot() {
// Works for both modern ("live") and older namespaced identifiers
return this.element.closest( return this.element.closest(
'[data-controller~="live"]' '[data-controller~="live"]'
); );
@ -68,34 +64,32 @@ export default class extends Controller {
return this.application.getControllerForElementAndIdentifier(this._liveRoot, 'live'); return this.application.getControllerForElementAndIdentifier(this._liveRoot, 'live');
} }
_debouncedRefresh(delay = 150) { _renderWhenReady() {
if (this._debounceId) clearTimeout(this._debounceId); // If you also want an initial refresh from server/cache, you can do:
this._debounceId = setTimeout(() => this._renderLiveComponent(), delay); const tryRender = () => {
}
_renderLiveComponent() {
const live = this._getLiveController(); const live = this._getLiveController();
if (!live || typeof live.render !== 'function') { if (!live || typeof live.render !== 'function') return setTimeout(tryRender, 50);
// Live not ready yet—try again very soon (and don't spam logs) live.render();
setTimeout(() => this._renderLiveComponent(), 50); };
return; tryRender();
} }
this._showLoading(); _flushQueue() {
const p = live.render(); while (this._queue.length) {
if (p && typeof p.finally === 'function') { const data = this._queue.shift();
p.finally(() => this._hideLoading()); this._ingest(data);
} else {
setTimeout(() => this._hideLoading(), 0);
} }
} }
_showLoading() { _ingest(payload) {
if (this.hasLoadingTarget) this.loadingTarget.style.display = ""; const live = this._getLiveController();
if (this.hasListTarget) this.listTarget.style.opacity = "0.6"; if (!live || typeof live.action !== 'function') {
// if still not ready, re-buffer and retry soon
this._queue.unshift(payload);
return setTimeout(() => this._flushQueue(), 50);
} }
_hideLoading() { // Call your LiveAction; it will mutate props and re-render server-side
if (this.hasLoadingTarget) this.loadingTarget.style.display = "none"; // NOTE: payload must be a string; if you have an object, pass JSON.stringify(obj)
if (this.hasListTarget) this.listTarget.style.opacity = ""; live.action('ingest', { payload });
} }
} }

Loading…
Cancel
Save