1/* Poll Maker Dashboard "Live polls" (D06, 2026-09-30; DUX 2026-10-01), loaded by dash-hook.js (it adds the rail item and keeps the 2 first hash in window.pmLiveH0). sys.pages.live is a console frame onto library.min.html, below the header, beside the rail. 3 Routes: #pg=live&lr=<encoded library route>, &p=<poll> (owner email), a cold #pg=live or #/p/<id> is replayed. 4 DUX: the Present editor opens INSIDE the Dashboard, in a second frame (editor.min.html: the same card and present.min.js as 5 /Present) beside the library frame: lr=/new, /edit/<poll>, /dup/<poll>, /present/<poll> (also #pg=live/new and 6 #pg=live/edit/<poll>). Opening the editor adds one history entry, so Back returns to the list. The library seeds the draft 7 (PMLibrary.prepare); the stage covers the whole window while it shows. PM console only. */ 8(function (w, d) { 9 'use strict'; 10 var LIB = '/3012/live/library.min.html', ED = '/3012/live/editor.min.html', POLL_RE = /^[A-Za-z0-9]{10}$/, ROUTE_RE = /^\/[A-Za-z0-9\/?=&_.,:-]{0,200}$/; 11 var ED_RE = /^\/(new|edit|dup|present)(?:\/([A-Za-z0-9]{10}))?$/, tries = 0, cur = null, ed = null, edKey = '', edWhat = '', pushed = false; 12 13 function sys() { return w.sys; } 14 // DV3: no tracker here: one AN1 "ui" beacon (area shell), none under DNT/GPC 15 function beacon(k) { 16 var n = w.navigator, t = Date.now(), S; if (n.globalPrivacyControl || n.doNotTrack == '1' || !n.sendBeacon) return; 17 try { S = JSON.parse(w.sessionStorage.getItem('pmt-s')) || { id: 's' + t.toString(36) + Math.random().toString(36).slice(2, 9) }; n.sendBeacon('/api/live/t', JSON.stringify({ v: 1, sid: S.id, s: 'dashboard', now: t, events: [{ n: 'ui', t: t, p: w.location.pathname, props: { el: k, area: 'shell' } }] })); } catch (e) { /* never breaks the Dashboard */ } 18 } 19 function ready() { var s = sys(); return !!(s && s.pages && s.page && s.getHash && s.canonH && s.lmenu && s.dashdrop && s.tool); } 20 // the raw library route of a console hash ('' = the list) 21 function lrOf(h) { 22 var s = sys(), r = s.getHash('lr', h), p = s.getHash('p', h); 23 if (!r && POLL_RE.test(p)) return '/p/' + p; // D05 live_owner_mail.js: /Dashboard#pg=live&p=<poll> 24 if (!r) return ''; 25 try { r = decodeURIComponent(r); } catch (e) { return ''; } 26 return ROUTE_RE.test(r) ? r : ''; 27 } 28 function edRoute(r) { var m = ED_RE.exec(r || ''); return m && (m[1] === 'new' || m[2]) ? { what: m[1], poll: m[2] || '' } : null; } 29 function libHash(h) { var r = lrOf(h); return r && !edRoute(r) ? '#' + r : ''; } 30 // the console hash with lr set or removed (p= goes too: the frame's route supersedes it) 31 function withLr(h, route) { 32 h = (h || '').replace(/([#&])(lr|p)=[^&#]*/g, '$1').replace(/&&+/g, '&').replace(/[#&]+$/, '').replace(/^#&/, '#'); 33 if (route && route !== '/') h = (h && h !== '#' ? h + '&' : '#') + 'lr=' + encodeURIComponent(route); 34 return h; 35 } 36 function replaceHash(h) { 37 var s = sys(); s.expectedHash = s.canonH(h); 38 try { w.history.replaceState(w.history.state, '', w.location.pathname + w.location.search + h); } catch (e) { w.location.replace(h || '#'); } 39 } 40 // the console appends its own parameters to a hash it does not know (#/p/<id>&pg=dashboard) 41 function consoleless(r) { return String(r || '').replace(/&(pg|tab|qp|src)=[^&]*/g, ''); } 42 function forwardFrom(h, route) { 43 var m = /^#(\/[^#]*)$/.exec(h || ''), r = m ? consoleless(m[1]) : ''; 44 var e = /(?:^#|&)pg=live\/((?:new|edit|dup|present)(?:\/[A-Za-z0-9]{10})?)(?:&|$)/.exec(h || ''); // #pg=live/new, #pg=live/edit/<poll> 45 if (e) r = '/' + e[1]; else if (!m || !ROUTE_RE.test(r)) return false; 46 replaceHash(withLr('#pg=live', r)); if (route !== false) sys().page(); return true; 47 } 48 function frameUrl(f) { try { return f.contentWindow.location.href; } catch (e) { return ''; } } 49 function go(f, url) { try { f.contentWindow.location.replace(url); } catch (e) { f.src = url; } } 50 // below the header and breadcrumb (.dashnav), right of the rail (#lmenu, 216 or 55 px; 0 on phones); an open phone menu overlays 51 function fit(f) { 52 f = f || (cur && cur.frame); if (!f || d.body.getAttribute('menu') === '1') return; 53 var n = d.querySelector('.dashnav') || d.querySelector('.bread'), rl = d.getElementById('lmenu'), top = 0, left = 0, r; 54 if (n) { r = n.getBoundingClientRect(); top = Math.max(0, Math.round(r.bottom)); } // DUX: on this page the bar is folded to 0 px (its title said the page twice) 55 if (rl) { r = rl.getBoundingClientRect(); if (r.width && r.left <= 0 && r.right < w.innerWidth / 2) left = Math.round(r.right); }
56 [f, ed].forEach(function (x) { if (!x || (x === ed && x.hasAttribute('data-full'))) return; x.style.top = top + 'px'; x.style.left = left + 'px'; x.style.width = 'calc(100% - ' + left + 'px)'; x.style.setProperty('height', 'calc(100% - ' + top + 'px)', 'important'); }); // the console says height:100% !important 57 } 58 // the console's title bar: "Live polls" with no drop down (test/www console.js: setTitle(v, sub, nodrop); this tree: setTitle(v, nodrop)) 59 function title(sub) { var s = sys(), t = s.dashdrop.setTitle; if (t.length >= 3) t(cur ? cur.title : 'Live polls', sub || '', 1); else t((cur ? cur.title : 'Live polls') + (sub ? ': ' + sub : ''), 1); } 60 // mirror the frame's route into the console address, so a reload or a copied address reopens the same view 61 function hook(o) { 62 var cw; try { cw = o.frame.contentWindow; } catch (e) { return; } 63 if (!cw || cw.__pmLiveHooked) return; cw.__pmLiveHooked = 1; 64 cw.addEventListener('hashchange', function () { 65 var s = sys(); if (!s.pages.pg || s.pages.pg.c !== 'live' || edKey) return; 66 var fh = cw.location.hash || '', route = fh.charAt(1) === '/' ? fh.slice(1) : '', h = withLr(w.location.hash, ROUTE_RE.test(route) ? route : ''); 67 if (h !== w.location.hash) replaceHash(h); 68 }); 69 } 70 71 // ---------------------------------------------------------------- DUX: the editor frame 72 function lib() { try { var x = cur.frame.contentWindow; return x && x.PMLibrary && x.PMLibrary.prepare ? x.PMLibrary : null; } catch (e) { return null; } } 73 function whenLib(fn, n) { var L = lib(); if (L) return fn(L); if ((n || 0) < 150) setTimeout(function () { whenLib(fn, (n || 0) + 1); }, 100); else fn(null); } 74 function edFrame() { 75 if (ed) return ed; 76 ed = d.createElement('iframe'); ed.id = 'pmlive-ed'; ed.title = 'Poll editor'; ed.setAttribute('allow', 'fullscreen; clipboard-read; clipboard-write'); ed.hidden = true; 77 cur.frame.parentNode.appendChild(ed); fit(); return ed; 78 } 79 function full(on) { 80 if (!ed) return; 81 if (on) { ed.setAttribute('data-full', '1'); ed.style.cssText = 'position:fixed;top:0;left:0;width:100%;z-index:2147483000;background:#fff'; ed.style.setProperty('height', '100%', 'important'); } 82 else if (ed.hasAttribute('data-full')) { ed.removeAttribute('data-full'); ed.style.cssText = ''; fit(); } 83 } 84 function showEd(on) { if (ed) ed.hidden = !on; if (cur) cur.frame.style.visibility = on ? 'hidden' : ''; if (!on) full(false); } 85 function openEd(er) { 86 var key = er.what + '/' + er.poll; if (edKey === key) return; edKey = key; edWhat = er.what; 87 edFrame(); showEd(true); title(er.what === 'edit' ? 'Edit poll' : er.what === 'present' ? 'Present' : 'New poll'); 88 var load = function (h, fresh) { if (!fresh && frameUrl(ed).indexOf(ED) >= 0) { if ((ed.contentWindow.location.hash || '') !== h) ed.contentWindow.location.hash = h; return; } go(ed, ED + h); }; 89 if (er.what === 'new') return load('', false); 90 whenLib(function (L) { 91 var no = function () { if (edKey === key) leave('/'); }; 92 if (!L) return no(); 93 L.prepare(er.what, er.poll).then(function (x) { if (edKey === key) load(x && x.hash || '', true); }, no); 94 }); 95 } 96 function closeEd() { if (!edKey) return; edKey = ''; edWhat = ''; pushed = false; showEd(false); title(); } 97 // leave the editor for a library route. The address is replaced, never Back: the editor frame's own hash changes (the stage, 98 // a template) are entries of the same history, so Back could reopen the stage instead of leaving 99 function leave(route) { pushed = false; replaceHash(withLr('#pg=live', route || '')); sys().page(); } 100 function edEvent(type, x) { 101 if (type === 'back') return leave(''); 102 if (type === 'full') { if (!x) full(false); return; } // FIX5: never full window (the stage opens in a new tab) 103 if (type === 'report' && POLL_RE.test(x || '')) { full(false); leave('/p/' + x); } 104 } 105 106 var page = { 107 template: 'console-frame', title: 'Live polls', 108 firstLoad: function () { cur = this; this.frame.setAttribute('title', 'Live polls'); this.reLoad(); }, 109 // the console calls unLoad then reLoad when the live page opens again while it shows: blanking waits a tick, so that 110 // case keeps the loaded library and only changes its route 111 reLoad: function () { 112 var f = this.frame, er = edRoute(lrOf()), url, at = frameUrl(f).split('#')[0]; 113 if (this.unT) { clearTimeout(this.unT); this.unT = 0; } 114 // FIX5: no stage inside the Dashboard (the rail and bar covered it): lr=/present/<poll> = the list + a new-tab note 115 if (er && er.what === 'present') { var pp = er.poll; er = null; replaceHash(withLr('#pg=live', '')); whenLib(function (L) { if (L && L.presenting) L.presenting(pp); }); } 116 url = LIB + libHash(); 117 d.body.setAttribute('data-pmlive', '1'); fit(f); 118 if (er) openEd(er); else closeEd(); 119 if (at.slice(-LIB.length) === LIB && !f.hasAttribute('loading')) { 120 var want = libHash() || '#/'; if (!er && (f.contentWindow.location.hash || '#/') !== want) f.contentWindow.location.hash = want; 121 return; 122 } 123 w.setWait(2); f.setAttribute('loading', 1); go(f, url); 124 }, 125 unLoad: function () { 126 var self = this; clearTimeout(self.unT); 127 self.unT = setTimeout(function () { self.unT = 0; d.body.removeAttribute('data-pmlive'); self.frame.removeAttribute('loading'); go(self.frame, 'about:blank'); if (ed) { closeEd(); go(ed, 'about:blank'); } }, 0); 128 }, 129 frameLoad: function (o) { 130 if (frameUrl(o.frame).indexOf(LIB) < 0) return; // about:blank after unLoad 131 o.frame.removeAttribute('loading'); w.setWait(0); 132 var s = sys(); s.lmenu.hideM(s.lmenu.hide, o.frame); title(edKey ? 'Editor' : ''); 133 fit(o.frame); hook(o); if (s.pmLive) s.pmLive.how = 'menu'; // DV3 134 if (o.frameCB) { o.frameCB(o); delete o.frameCB; } 135 } 136 }; 137 138 function install() { 139 var s = sys(); if (!s.tool || s.tool.h !== 'PM' || s.pages.live) return; 140 s.pages.live = page; 141 var css = d.createElement('style'); css.textContent = 'body[data-pmlive] .dashnav{height:0!important;min-height:0!important;padding:0!important;margin:0!important;border:0!important;overflow:hidden!important;visibility:hidden}'; d.head.appendChild(css); 142 // for the library, the editor and the Dashboard: open a library route; hand a classic poll to the console's Results page 143 s.pmLive = { 144 open: function (route) { replaceHash(withLr('#pg=live', ROUTE_RE.test(route || '') ? route : '')); s.page(); }, 145 // DUX: a route that opens the editor gets its own history entry (Back returns to the list) 146 go: function (route) { if (!edRoute(route)) return this.open(route); pushed = true; w.location.hash = withLr('#pg=live', route); }, 147 edEvent: edEvent, 148 results: function (qp) { if (/^\d+x[A-Za-z0-9]*-\d+$/.test(qp || '')) s.page('results', 'select', 'pm' + qp); } 149 }; 150 // a #/p/<id> (or #pg=live/new) pasted later: the console turns it into the Dashboard, so read the event's own URL 151 w.addEventListener('hashchange', function (e) { var u = String((e && e.newURL) || ''), i = u.indexOf('#'); if (i >= 0) forwardFrom(u.slice(i)); }); 152 // the frame follows the rail (collapse, expand) and the window 153 var fitT = 0, later = function () { clearTimeout(fitT); fitT = setTimeout(function () { fitT = 0; if (s.pages.pg && s.pages.pg.c === 'live') fit(); }, 350); }; 154 w.addEventListener('resize', later); 155 if (w.MutationObserver) new MutationObserver(later).observe(d.body, { attributes: true, attributeFilter: ['lmenu', 'menu'] }); 156 // replay the first hash (not routed yet: fixing the address is enough; the console's first route then finds the page) 157 var h0 = String(w.pmLiveH0 || ''), routed = s.pages.pg && s.pages.pg.n; w.pmLiveH0 = ''; 158 // DV3: opened by a deep link or the rail (the library reads it) 159 s.pmLive.how = /^#\/|pg=live(\/|&|$)|[#&]lr=|[#&]p=/.test(h0) ? 'deep' : 'menu'; beacon('menu.shown'); 160 if (/^#\/|pg=live\//.test(h0)) { if (!routed || s.pages.pg.c === 'dashboard' || s.pages.pg.c !== 'live') forwardFrom(h0, !!routed); } 161 else if (/(^#|&)pg=live(&|$)/.test(h0) && routed && s.pages.pg.c === 'dashboard') { replaceHash(h0); s.page(); } 162 } 163 (function wait() { 164 if (ready()) return install(); 165 if (++tries < 100) setTimeout(wait, 100); // the console finishes its set-up in the same task; a safety net 166 })();
167})(window, document);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.