/** * Straw Poll Widget Hydration for MkDocs * * Supports two modes: * .straw-poll-inline — Full voting UI embedded in docs page * .straw-poll-card — Preview card linking to the full poll lander * * Uses the lightweight /api/straw-polls/widget/:slug endpoint (cached). * Follows the scheduling-poll.js hydration pattern. */ (function () { 'use strict'; function getApiUrl() { if (window.PAYMENT_API_URL) return window.PAYMENT_API_URL; if (window.API_URL) return window.API_URL; var host = window.location.hostname; if (host !== 'localhost' && host.indexOf('.') !== -1) { var parts = host.split('.'); var base = parts.slice(-2).join('.'); return window.location.protocol + '//api.' + base; } return 'http://localhost:4000'; } function getAppUrl() { if (window.APP_URL) return window.APP_URL; var host = window.location.hostname; if (host !== 'localhost' && host.indexOf('.') !== -1) { var parts = host.split('.'); var base = parts.slice(-2).join('.'); return window.location.protocol + '//app.' + base; } return 'http://localhost:3000'; } var COLORS = ['#1890ff', '#52c41a', '#faad14', '#ff4d4f', '#722ed1', '#13c2c2', '#eb2f96']; var YNA_COLORS = { Yes: '#52c41a', No: '#ff4d4f', Abstain: '#8c8c8c' }; function tokenKey(slug) { return 'straw_poll_voter_token_' + slug; } // ===== Card Mode ===== function renderCard(block, poll, appUrl) { var html = '
'; html += '
'; html += (poll.type === 'YES_NO_ABSTAIN' ? 'Yes / No / Abstain' : 'Single Choice') + ' Poll
'; html += '

' + poll.title + '

'; html += '
' + poll.totalVotes + ' vote' + (poll.totalVotes !== 1 ? 's' : '') + '
'; if (poll.status === 'ACTIVE') { html += ' 0; var html = '
'; // Title html += '

' + poll.title + '

'; html += '
'; html += (poll.type === 'YES_NO_ABSTAIN' ? 'Yes / No / Abstain' : 'Single Choice'); html += ' · ' + poll.totalVotes + ' vote' + (poll.totalVotes !== 1 ? 's' : '') + '
'; if (poll.status === 'ACTIVE' && !hasVoted) { // Vote form html += '
'; poll.options.forEach(function (opt, i) { var isYNA = poll.type === 'YES_NO_ABSTAIN'; var color = isYNA ? (YNA_COLORS[opt.label] || COLORS[i]) : COLORS[i % COLORS.length]; html += ''; }); html += ''; html += ''; html += '
'; } // Results if (showResults && (hasVoted || poll.status !== 'ACTIVE')) { html += renderResultsHtml(poll); } if (hasVoted && poll.status === 'ACTIVE') { html += '
✓ You\'ve voted
'; } html += '
'; block.innerHTML = html; // Wire up vote form if (poll.status === 'ACTIVE' && !hasVoted) { wireVoteForm(block, poll, apiUrl, slug); } } function renderResultsHtml(poll) { var html = '
'; poll.options.forEach(function (opt, i) { var pct = poll.totalVotes > 0 ? Math.round((opt.voteCount / poll.totalVotes) * 100) : 0; var color = poll.type === 'YES_NO_ABSTAIN' ? (YNA_COLORS[opt.label] || COLORS[i]) : COLORS[i % COLORS.length]; html += '
'; html += '
'; html += '' + opt.label + '' + opt.voteCount + ' (' + pct + '%)
'; html += '
'; html += '
'; html += '
'; }); html += '
'; return html; } function wireVoteForm(block, poll, apiUrl, slug) { var selectedId = null; var buttons = block.querySelectorAll('.sp-opt-btn'); var submitBtn = block.querySelector('#sp-submit-' + slug); buttons.forEach(function (btn) { btn.addEventListener('click', function () { selectedId = btn.getAttribute('data-option-id'); buttons.forEach(function (b) { b.style.borderWidth = '2px'; b.style.fontWeight = 'normal'; }); btn.style.borderWidth = '3px'; btn.style.fontWeight = '600'; submitBtn.disabled = false; submitBtn.style.opacity = '1'; }); }); submitBtn.addEventListener('click', function () { if (!selectedId) return; submitBtn.disabled = true; submitBtn.textContent = 'Submitting...'; var voterName = (block.querySelector('#sp-voter-name-' + slug) || {}).value || ''; var body = { optionId: selectedId }; if (voterName) body.voterName = voterName; var storedToken = localStorage.getItem(tokenKey(slug)); if (storedToken) body.voterToken = storedToken; fetch(apiUrl + '/api/straw-polls/public/' + encodeURIComponent(slug) + '/vote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) .then(function (res) { return res.json(); }) .then(function (data) { if (data.voterToken) localStorage.setItem(tokenKey(slug), data.voterToken); // Re-fetch and re-render with results return fetch(apiUrl + '/api/straw-polls/widget/' + encodeURIComponent(slug)).then(function (r) { return r.json(); }); }) .then(function (updated) { renderInline(block, updated, apiUrl); }) .catch(function () { submitBtn.textContent = 'Error — try again'; submitBtn.disabled = false; submitBtn.style.opacity = '1'; }); }); } // ===== Hydration ===== function hydrateBlocks() { var apiUrl = getApiUrl(); var appUrl = getAppUrl(); // Inline embeds document.querySelectorAll('.straw-poll-inline').forEach(function (block) { if (block.getAttribute('data-hydrated') === 'true') return; var slug = block.getAttribute('data-poll-slug'); if (!slug) return; block.setAttribute('data-hydrated', 'true'); block.innerHTML = '
Loading poll...
'; fetch(apiUrl + '/api/straw-polls/widget/' + encodeURIComponent(slug)) .then(function (res) { if (!res.ok) throw new Error(); return res.json(); }) .then(function (poll) { renderInline(block, poll, apiUrl); }) .catch(function () { block.innerHTML = '
Poll unavailable
'; }); }); // Card links document.querySelectorAll('.straw-poll-card').forEach(function (block) { if (block.getAttribute('data-hydrated') === 'true') return; var slug = block.getAttribute('data-poll-slug'); if (!slug) return; block.setAttribute('data-hydrated', 'true'); block.innerHTML = '
Loading...
'; fetch(apiUrl + '/api/straw-polls/widget/' + encodeURIComponent(slug)) .then(function (res) { if (!res.ok) throw new Error(); return res.json(); }) .then(function (poll) { renderCard(block, poll, appUrl); }) .catch(function () { block.innerHTML = '
Poll unavailable
'; }); }); } // Initial hydration if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', hydrateBlocks); } else { hydrateBlocks(); } // Re-hydrate on MkDocs SPA navigation if (typeof document$ !== 'undefined') { document$.subscribe(function () { setTimeout(hydrateBlocks, 100); }); } })();