/** * — Comment button with unresolved-count badge. * * Shows a chat-bubble icon in the header bar. Badge displays the count * of unresolved comment pins on the current canvas. Clicking dispatches * a `comment-pin-activate` event on `window` so canvas.html can enter * pin-placement mode. * * Data source: `window.__communitySync?.doc?.commentPins` * Listens for `comment-pins-changed` on `window` (re-dispatched by canvas). * Polls every 5s as fallback (sync may appear after component mounts). */ const POLL_INTERVAL = 5_000; export class RStackCommentBell extends HTMLElement { #shadow: ShadowRoot; #count = 0; #pollTimer: ReturnType | null = null; constructor() { super(); this.#shadow = this.attachShadow({ mode: "open" }); } connectedCallback() { this.#render(); this.#refreshCount(); this.#pollTimer = setInterval(() => this.#refreshCount(), POLL_INTERVAL); window.addEventListener("comment-pins-changed", this.#onPinsChanged); } disconnectedCallback() { if (this.#pollTimer) clearInterval(this.#pollTimer); window.removeEventListener("comment-pins-changed", this.#onPinsChanged); } #onPinsChanged = () => { this.#refreshCount(); }; #refreshCount() { const sync = (window as any).__communitySync; const pins = sync?.doc?.commentPins; if (!pins) { if (this.#count !== 0) { this.#count = 0; this.#render(); } return; } const newCount = Object.values(pins).filter( (p: any) => !p.resolved ).length; if (newCount !== this.#count) { this.#count = newCount; this.#render(); } } #render() { const badge = this.#count > 0 ? `${this.#count > 99 ? "99+" : this.#count}` : ""; this.#shadow.innerHTML = ` `; this.#shadow .querySelector(".comment-btn") ?.addEventListener("click", (e) => { e.stopPropagation(); window.dispatchEvent(new CustomEvent("comment-pin-activate")); }); } static define(tag = "rstack-comment-bell") { if (!customElements.get(tag)) customElements.define(tag, RStackCommentBell); } } const STYLES = ` :host { display: inline-flex; align-items: center; } .comment-btn { position: relative; background: none; border: none; color: var(--rs-text-muted, #94a3b8); cursor: pointer; padding: 6px; border-radius: 8px; display: flex; align-items: center; justify-content: center; transition: color 0.15s, background 0.15s; } .comment-btn:hover { color: var(--rs-text-primary, #e2e8f0); background: var(--rs-bg-hover, rgba(255,255,255,0.05)); } .badge { position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; border-radius: 8px; background: #ef4444; color: white; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; padding: 0 4px; line-height: 1; pointer-events: none; } @media (max-width: 640px) { .comment-btn { display: none; } } `;