components_live_quotes_dialog.js

/**
 * LiveQuotesDialog — lists all embed quotes for a project.
 */
export class LiveQuotesDialog {
    /**
     * @param {string} projectId
     * @param {function} getToken - async function returning the current auth token, or null
     */
    constructor(projectId, getToken) {
        this._projectId = projectId;
        this._getToken  = getToken ?? (() => Promise.resolve(null));

        this._buildDOM();
        document.body.appendChild(this.scrim);
        document.body.appendChild(this.root);
        this._load();
    }

    _buildDOM() {
        this.scrim = document.createElement('div');
        this.scrim.className = 'lq-scrim';
        this.scrim.addEventListener('click', () => this.close());

        this.root = document.createElement('div');
        this.root.className = 'lq-dialog';
        this.root.innerHTML = `
            <div class="lq-header">
                <h2 class="lq-title">Live Quotes</h2>
                <button class="lq-close-btn" title="Close">&#x2715;</button>
            </div>
            <div class="lq-body">
                <div class="lq-list" id="lqList">
                    <div class="lq-loading">Loading…</div>
                </div>
            </div>
        `;

        this.root.querySelector('.lq-close-btn').addEventListener('click', () => this.close());
        this._list = this.root.querySelector('#lqList');
    }

    async _load() {
        try {
            const token = await this._getToken();
            const res = await fetch(`/api/projects/${this._projectId}/embeds`, {
                headers: token ? { 'X-Auth-Token': token } : {},
                credentials: 'include',
            });
            if (!res.ok) throw new Error();
            const embeds = await res.json();
            this._render(embeds);
        } catch {
            this._list.innerHTML = '<div class="lq-error">Failed to load quotes.</div>';
        }
    }

    _render(embeds) {
        if (!embeds.length) {
            this._list.innerHTML = '<div class="lq-empty">No live quotes yet. Select transcript text and use the context menu to create one.</div>';
            return;
        }

        this._list.innerHTML = '';
        embeds.forEach(embed => {
            const url = `${window.location.origin}/embeds/${embed.id}`;
            const date = embed.created_at
                ? new Date(embed.created_at).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
                : '—';

            const row = document.createElement('div');
            row.className = 'lq-row';
            row.dataset.embedId = embed.id;
            const nameText = embed.name || url;
            row.innerHTML = `
                <div class="lq-row-info">
                    <span class="lq-name">${nameText}</span>
                    <a class="lq-url" href="${url}" target="_blank" rel="noopener">${url}</a>
                    <div class="lq-meta">
                        <span class="lq-date">${date}</span>
                        <span class="lq-dot">·</span>
                        <span class="lq-views">${embed.invoke_count} ${embed.invoke_count === 1 ? 'view' : 'views'}</span>
                    </div>
                </div>
                <div class="lq-row-actions">
                    <button class="lq-copy-link-btn" title="Copy link">Copy Link</button>
                    <button class="lq-copy-btn" title="Copy embed code">Copy Code</button>
                    <button class="lq-delete-btn" title="Delete">Delete</button>
                </div>
            `;

            row.querySelector('.lq-copy-link-btn').addEventListener('click', (e) => {
                e.stopPropagation();
                this._copyText(url, row.querySelector('.lq-copy-link-btn'));
            });
            row.querySelector('.lq-copy-btn').addEventListener('click', (e) => {
                e.stopPropagation();
                this._copyEmbedCode(url, row.querySelector('.lq-copy-btn'));
            });
            row.querySelector('.lq-delete-btn').addEventListener('click', (e) => {
                e.stopPropagation();
                this._deleteEmbed(embed.id, row);
            });

            this._list.appendChild(row);
        });
    }

    _copyText(text, btn) {
        navigator.clipboard.writeText(text).then(() => {
            const orig = btn.textContent;
            btn.textContent = 'Copied!';
            setTimeout(() => { btn.textContent = orig; }, 2000);
        });
    }

    _copyEmbedCode(url, btn) {
        const code = `<iframe src="${url}" width="100%" height="200" frameborder="0" scrolling="no" style="border:none;overflow:hidden"></iframe>`;
        this._copyText(code, btn);
    }

    async _deleteEmbed(embedId, row) {
        const btn = row.querySelector('.lq-delete-btn');
        btn.disabled = true;
        try {
            const token = await this._getToken();
            const res = await fetch(`/api/embeds/${embedId}`, {
                method: 'DELETE',
                headers: token ? { 'X-Auth-Token': token } : {},
                credentials: 'include',
            });
            if (!res.ok) throw new Error();
            this._load();
        } catch {
            btn.disabled = false;
        }
    }

    close() {
        this.root.remove();
        this.scrim.remove();
    }
}