Last active
June 2, 2026 13:40
-
-
Save wjhrdy/ec9acbc0528389d983e8e200f7f6bd91 to your computer and use it in GitHub Desktop.
Jira stand-up random-order roller bookmarklet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| javascript:!function()%7B%22use%20strict%22%3Bvar%20e%3D%22__standup_roller__%22%2Ct%3D%5B%22unassigned%22%2C%22assigned%20to%20me%22%2C%22only%20my%20issues%22%2C%22recently%20updated%22%2C%22deployed%20to%20production%22%5D%3Bfunction%20n()%7Bvar%20e%3Ddocument.querySelector('fieldset%5Bdata-testid%3D%22software-filters.ui.filter-selection-bar.filter-selection-bar%22%5D')%3Bif(e)return%20e%3Bvar%20t%3Ddocument.querySelector('input%5Bid%5E%3D%22checkbox-id-%22%5D')%3Breturn%20t%3Ft.closest(%22fieldset%22)%3Anull%7Dfunction%20o()%7Bvar%20e%3Dn()%3Bif(!e)return%5B%5D%3Bvar%20o%3D%5B%5D%2Cr%3D%7B%7D%3Breturn%20e.querySelectorAll('input%5Btype%3D%22checkbox%22%5D').forEach(function(n)%7Bif(n.id%26%26!r%5Bn.id%5D)%7Bvar%20i%3Dfunction(e%2Ct)%7Bvar%20n%3D'label%5Bfor%3D%22'%2B(window.CSS%26%26CSS.escape%3FCSS.escape(t)%3At)%2B'%22%5D'%2Co%3De.querySelector(n)%3Breturn%20o%3Fo.textContent.trim()%3A%22%22%7D(e%2Cn.id)%3Bi%26%26-1%3D%3D%3Dt.indexOf(i.toLowerCase())%26%26(r%5Bn.id%5D%3D1%2Co.push(%7Bid%3An.id%2Cname%3Ai%7D))%7D%7D)%2Co%7Dfunction%20r(e%2Ct)%7Be.forEach(function(e)%7Bvar%20o%2Cr%3Bo%3Dfunction(e)%7Bvar%20t%3Ddocument.getElementById(e.id)%3Bif(t%26%26%22checkbox%22%3D%3D%3Dt.type)return%20t%3Bvar%20o%3Dn()%3Bif(!o)return%20null%3Bfor(var%20r%3Do.querySelectorAll(%22label%22)%2Ci%3D0%3Bi%3Cr.length%3Bi%2B%2B)if(r%5Bi%5D.textContent.trim()%3D%3D%3De.name)%7Bvar%20l%3Dr%5Bi%5D.getAttribute(%22for%22)%2Ca%3Dl%26%26document.getElementById(l)%3Bif(a)return%20a%7Dreturn%20null%7D(e)%2Cr%3De%3D%3D%3Dt%2Co%26%26o.checked!%3D%3Dr%26%26o.click()%7D)%7Dvar%20i%3D%5B%5D%2Cl%3D%5B%5D%2Ca%3D-1%2Cc%3Ddocument.getElementById(e)%3Bc%26%26c.remove()%3Bvar%20d%3Ddocument.createElement(%22div%22)%3Bd.id%3De%2Cd.style.cssText%3D%5B%22position%3Afixed%22%2C%22left%3A20px%22%2C%22bottom%3A20px%22%2C%22z-index%3A2147483647%22%2C'font-family%3A-apple-system%2CBlinkMacSystemFont%2C%22Segoe%20UI%22%2CRoboto%2Csans-serif'%2C%22min-width%3A220px%22%2C%22max-width%3A320px%22%2C%22background%3A%231d2125%22%2C%22color%3A%23fff%22%2C%22border%3A1px%20solid%20rgba(255%2C255%2C255%2C.12)%22%2C%22border-radius%3A14px%22%2C%22box-shadow%3A0%2012px%2032px%20rgba(0%2C0%2C0%2C.45)%22%2C%22overflow%3Ahidden%22%2C%22user-select%3Anone%22%5D.join(%22%3B%22)%3Bvar%20s%3Ddocument.createElement(%22div%22)%3Bs.style.cssText%3D%22display%3Aflex%3Balign-items%3Acenter%3Bjustify-content%3Aspace-between%3Bpadding%3A8px%2012px%3Bbackground%3Argba(255%2C255%2C255%2C.06)%3Bfont-size%3A12px%3Bfont-weight%3A600%3Bletter-spacing%3A.04em%3Btext-transform%3Auppercase%3Bcolor%3A%239fadbc%22%3Bvar%20u%3Ddocument.createElement(%22span%22)%3Bu.textContent%3D%22Stand-up%20%F0%9F%8E%B2%22%3Bvar%20p%3Ddocument.createElement(%22div%22)%3Bp.style.cssText%3D%22display%3Aflex%3Bgap%3A6px%22%3Bvar%20f%3Ddocument.createElement(%22button%22)%3Bf.textContent%3D%22%E2%86%BA%22%2Cf.title%3D%22Reset%22%3Bvar%20x%3Ddocument.createElement(%22button%22)%3Bx.textContent%3D%22%E2%9C%95%22%2Cx.title%3D%22Close%22%2C%5Bf%2Cx%5D.forEach(function(e)%7Be.style.cssText%3D%22all%3Aunset%3Bcursor%3Apointer%3Bcolor%3A%239fadbc%3Bfont-size%3A13px%3Bline-height%3A1%3Bpadding%3A2px%205px%3Bborder-radius%3A6px%22%2Ce.onmouseenter%3Dfunction()%7Be.style.color%3D%22%23fff%22%2Ce.style.background%3D%22rgba(255%2C255%2C255%2C.12)%22%7D%2Ce.onmouseleave%3Dfunction()%7Be.style.color%3D%22%239fadbc%22%2Ce.style.background%3D%22transparent%22%7D%7D)%2Cp.appendChild(f)%2Cp.appendChild(x)%2Cs.appendChild(u)%2Cs.appendChild(p)%3Bvar%20m%3Ddocument.createElement(%22button%22)%3Bm.style.cssText%3D%22all%3Aunset%3Bdisplay%3Ablock%3Bcursor%3Apointer%3Bwidth%3A100%25%3Bbox-sizing%3Aborder-box%3Bpadding%3A16px%2016px%2018px%22%3Bvar%20g%3Ddocument.createElement(%22div%22)%3Bg.style.cssText%3D%22font-size%3A24px%3Bfont-weight%3A700%3Bline-height%3A1.15%3Bword-break%3Abreak-word%22%3Bvar%20b%3Ddocument.createElement(%22div%22)%3Bfunction%20h()%7Bvar%20e%3Dl.length%3Bif(-1%3D%3D%3Da)g.textContent%3D%22%F0%9F%8E%B2%20Start%20stand-up%22%2Cg.style.color%3D%22%23fff%22%2Cb.textContent%3Di.length%2B(1%3D%3D%3Di.length%3F%22%20person%22%3A%22%20people%22)%2B%22%20%C2%B7%20click%20to%20begin%22%3Belse%20if(a%3E%3De)g.textContent%3D%22%E2%9C%85%20All%20done!%22%2Cg.style.color%3D%22%234bce97%22%2Cb.textContent%3D%22click%20to%20run%20again%22%3Belse%7Bvar%20t%3Dl%5Ba%5D%3Bg.textContent%3Dt.name%2Cg.style.color%3D%22%23669df1%22%3Bvar%20n%3Da%3D%3D%3De-1%3Bb.textContent%3Da%2B1%2B%22%20%2F%20%22%2Be%2B%22%20%C2%B7%20%22%2B(n%3F%22last%20%E2%80%94%20click%20to%20finish%22%3A%22click%20for%20next%22)%7D%7Db.style.cssText%3D%22margin-top%3A6px%3Bfont-size%3A12px%3Bcolor%3A%239fadbc%22%2Cm.appendChild(g)%2Cm.appendChild(b)%2Cm.onmouseenter%3Dfunction()%7Bm.style.background%3D%22rgba(255%2C255%2C255%2C.05)%22%7D%2Cm.onmouseleave%3Dfunction()%7Bm.style.background%3D%22transparent%22%7D%2Cd.appendChild(s)%2Cd.appendChild(m)%2Cdocument.body.appendChild(d)%2Cm.addEventListener(%22click%22%2Cfunction()%7Bif(-1%3D%3D%3Da%7C%7Ca%3E%3Dl.length)%7Bif(!(i%3Do()).length)return%20g.textContent%3D%22No%20people%20found%22%2Cg.style.color%3D%22%23f87168%22%2Cvoid(b.textContent%3D%22open%20the%20assignee%20filter%20bar%20first%22)%3Bl%3Dfunction(e)%7Bfor(var%20t%3De.length-1%3Bt%3E0%3Bt--)%7Bvar%20n%3DMath.floor(Math.random()*(t%2B1))%2Co%3De%5Bt%5D%3Be%5Bt%5D%3De%5Bn%5D%2Ce%5Bn%5D%3Do%7Dreturn%20e%7D(i.slice())%2Ca%3D0%2Cr(i%2Cl%5B0%5D)%7Delse%20a%3Cl.length-1%3F(a%2B%2B%2Cr(i%2Cl%5Ba%5D))%3A(a%3Dl.length%2Cr(i%2Cnull))%3Bh()%7D)%2Cf.addEventListener(%22click%22%2Cfunction(e)%7Be.stopPropagation()%2Ci%3Do()%2Cl%3D%5B%5D%2Ca%3D-1%2Cr(i%2Cnull)%2Ch()%7D)%2Cx.addEventListener(%22click%22%2Cfunction(e)%7Be.stopPropagation()%2Cd.remove()%7D)%2Ci%3Do()%2Ch()%7D()%3B |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>Stand-up Roller — install</title> | |
| <style> | |
| :root{color-scheme:dark} | |
| body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center; | |
| background:#0b0f14;color:#c7d1db; | |
| font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;line-height:1.55} | |
| .card{max-width:560px;margin:32px;padding:32px;background:#1d2125;border:1px solid rgba(255,255,255,.12); | |
| border-radius:16px;box-shadow:0 12px 32px rgba(0,0,0,.45)} | |
| h1{margin:0 0 4px;font-size:22px;color:#fff} | |
| p{margin:12px 0} | |
| .drag{display:inline-block;margin:18px 0;padding:14px 22px;border-radius:12px; | |
| background:#669df1;color:#0b0f14;font-weight:700;font-size:16px;text-decoration:none; | |
| cursor:grab;box-shadow:0 6px 18px rgba(102,157,241,.4)} | |
| .drag:active{cursor:grabbing} | |
| ol{padding-left:20px} | |
| code{background:rgba(255,255,255,.08);padding:2px 6px;border-radius:6px;font-size:13px} | |
| .muted{color:#9fadbc;font-size:13px} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card"> | |
| <h1>Stand-up Roller 🎲</h1> | |
| <p>Picks a random stand-up order from a Jira board's assignee filter bar and toggles each person as you go.</p> | |
| <p><strong>Drag this button up to your bookmarks bar:</strong></p> | |
| <p><a class="drag" href="javascript:!function()%7B%22use%20strict%22%3Bvar%20e%3D%22__standup_roller__%22%2Ct%3D%5B%22unassigned%22%2C%22assigned%20to%20me%22%2C%22only%20my%20issues%22%2C%22recently%20updated%22%2C%22deployed%20to%20production%22%5D%3Bfunction%20n()%7Bvar%20e%3Ddocument.querySelector('fieldset%5Bdata-testid%3D%22software-filters.ui.filter-selection-bar.filter-selection-bar%22%5D')%3Bif(e)return%20e%3Bvar%20t%3Ddocument.querySelector('input%5Bid%5E%3D%22checkbox-id-%22%5D')%3Breturn%20t%3Ft.closest(%22fieldset%22)%3Anull%7Dfunction%20o()%7Bvar%20e%3Dn()%3Bif(!e)return%5B%5D%3Bvar%20o%3D%5B%5D%2Cr%3D%7B%7D%3Breturn%20e.querySelectorAll('input%5Btype%3D%22checkbox%22%5D').forEach(function(n)%7Bif(n.id%26%26!r%5Bn.id%5D)%7Bvar%20i%3Dfunction(e%2Ct)%7Bvar%20n%3D'label%5Bfor%3D%22'%2B(window.CSS%26%26CSS.escape%3FCSS.escape(t)%3At)%2B'%22%5D'%2Co%3De.querySelector(n)%3Breturn%20o%3Fo.textContent.trim()%3A%22%22%7D(e%2Cn.id)%3Bi%26%26-1%3D%3D%3Dt.indexOf(i.toLowerCase())%26%26(r%5Bn.id%5D%3D1%2Co.push(%7Bid%3An.id%2Cname%3Ai%7D))%7D%7D)%2Co%7Dfunction%20r(e%2Ct)%7Be.forEach(function(e)%7Bvar%20o%2Cr%3Bo%3Dfunction(e)%7Bvar%20t%3Ddocument.getElementById(e.id)%3Bif(t%26%26%22checkbox%22%3D%3D%3Dt.type)return%20t%3Bvar%20o%3Dn()%3Bif(!o)return%20null%3Bfor(var%20r%3Do.querySelectorAll(%22label%22)%2Ci%3D0%3Bi%3Cr.length%3Bi%2B%2B)if(r%5Bi%5D.textContent.trim()%3D%3D%3De.name)%7Bvar%20l%3Dr%5Bi%5D.getAttribute(%22for%22)%2Ca%3Dl%26%26document.getElementById(l)%3Bif(a)return%20a%7Dreturn%20null%7D(e)%2Cr%3De%3D%3D%3Dt%2Co%26%26o.checked!%3D%3Dr%26%26o.click()%7D)%7Dvar%20i%3D%5B%5D%2Cl%3D%5B%5D%2Ca%3D-1%2Cc%3Ddocument.getElementById(e)%3Bc%26%26c.remove()%3Bvar%20d%3Ddocument.createElement(%22div%22)%3Bd.id%3De%2Cd.style.cssText%3D%5B%22position%3Afixed%22%2C%22left%3A20px%22%2C%22bottom%3A20px%22%2C%22z-index%3A2147483647%22%2C'font-family%3A-apple-system%2CBlinkMacSystemFont%2C%22Segoe%20UI%22%2CRoboto%2Csans-serif'%2C%22min-width%3A220px%22%2C%22max-width%3A320px%22%2C%22background%3A%231d2125%22%2C%22color%3A%23fff%22%2C%22border%3A1px%20solid%20rgba(255%2C255%2C255%2C.12)%22%2C%22border-radius%3A14px%22%2C%22box-shadow%3A0%2012px%2032px%20rgba(0%2C0%2C0%2C.45)%22%2C%22overflow%3Ahidden%22%2C%22user-select%3Anone%22%5D.join(%22%3B%22)%3Bvar%20s%3Ddocument.createElement(%22div%22)%3Bs.style.cssText%3D%22display%3Aflex%3Balign-items%3Acenter%3Bjustify-content%3Aspace-between%3Bpadding%3A8px%2012px%3Bbackground%3Argba(255%2C255%2C255%2C.06)%3Bfont-size%3A12px%3Bfont-weight%3A600%3Bletter-spacing%3A.04em%3Btext-transform%3Auppercase%3Bcolor%3A%239fadbc%22%3Bvar%20u%3Ddocument.createElement(%22span%22)%3Bu.textContent%3D%22Stand-up%20%F0%9F%8E%B2%22%3Bvar%20p%3Ddocument.createElement(%22div%22)%3Bp.style.cssText%3D%22display%3Aflex%3Bgap%3A6px%22%3Bvar%20f%3Ddocument.createElement(%22button%22)%3Bf.textContent%3D%22%E2%86%BA%22%2Cf.title%3D%22Reset%22%3Bvar%20x%3Ddocument.createElement(%22button%22)%3Bx.textContent%3D%22%E2%9C%95%22%2Cx.title%3D%22Close%22%2C%5Bf%2Cx%5D.forEach(function(e)%7Be.style.cssText%3D%22all%3Aunset%3Bcursor%3Apointer%3Bcolor%3A%239fadbc%3Bfont-size%3A13px%3Bline-height%3A1%3Bpadding%3A2px%205px%3Bborder-radius%3A6px%22%2Ce.onmouseenter%3Dfunction()%7Be.style.color%3D%22%23fff%22%2Ce.style.background%3D%22rgba(255%2C255%2C255%2C.12)%22%7D%2Ce.onmouseleave%3Dfunction()%7Be.style.color%3D%22%239fadbc%22%2Ce.style.background%3D%22transparent%22%7D%7D)%2Cp.appendChild(f)%2Cp.appendChild(x)%2Cs.appendChild(u)%2Cs.appendChild(p)%3Bvar%20m%3Ddocument.createElement(%22button%22)%3Bm.style.cssText%3D%22all%3Aunset%3Bdisplay%3Ablock%3Bcursor%3Apointer%3Bwidth%3A100%25%3Bbox-sizing%3Aborder-box%3Bpadding%3A16px%2016px%2018px%22%3Bvar%20g%3Ddocument.createElement(%22div%22)%3Bg.style.cssText%3D%22font-size%3A24px%3Bfont-weight%3A700%3Bline-height%3A1.15%3Bword-break%3Abreak-word%22%3Bvar%20b%3Ddocument.createElement(%22div%22)%3Bfunction%20h()%7Bvar%20e%3Dl.length%3Bif(-1%3D%3D%3Da)g.textContent%3D%22%F0%9F%8E%B2%20Start%20stand-up%22%2Cg.style.color%3D%22%23fff%22%2Cb.textContent%3Di.length%2B(1%3D%3D%3Di.length%3F%22%20person%22%3A%22%20people%22)%2B%22%20%C2%B7%20click%20to%20begin%22%3Belse%20if(a%3E%3De)g.textContent%3D%22%E2%9C%85%20All%20done!%22%2Cg.style.color%3D%22%234bce97%22%2Cb.textContent%3D%22click%20to%20run%20again%22%3Belse%7Bvar%20t%3Dl%5Ba%5D%3Bg.textContent%3Dt.name%2Cg.style.color%3D%22%23669df1%22%3Bvar%20n%3Da%3D%3D%3De-1%3Bb.textContent%3Da%2B1%2B%22%20%2F%20%22%2Be%2B%22%20%C2%B7%20%22%2B(n%3F%22last%20%E2%80%94%20click%20to%20finish%22%3A%22click%20for%20next%22)%7D%7Db.style.cssText%3D%22margin-top%3A6px%3Bfont-size%3A12px%3Bcolor%3A%239fadbc%22%2Cm.appendChild(g)%2Cm.appendChild(b)%2Cm.onmouseenter%3Dfunction()%7Bm.style.background%3D%22rgba(255%2C255%2C255%2C.05)%22%7D%2Cm.onmouseleave%3Dfunction()%7Bm.style.background%3D%22transparent%22%7D%2Cd.appendChild(s)%2Cd.appendChild(m)%2Cdocument.body.appendChild(d)%2Cm.addEventListener(%22click%22%2Cfunction()%7Bif(-1%3D%3D%3Da%7C%7Ca%3E%3Dl.length)%7Bif(!(i%3Do()).length)return%20g.textContent%3D%22No%20people%20found%22%2Cg.style.color%3D%22%23f87168%22%2Cvoid(b.textContent%3D%22open%20the%20assignee%20filter%20bar%20first%22)%3Bl%3Dfunction(e)%7Bfor(var%20t%3De.length-1%3Bt%3E0%3Bt--)%7Bvar%20n%3DMath.floor(Math.random()*(t%2B1))%2Co%3De%5Bt%5D%3Be%5Bt%5D%3De%5Bn%5D%2Ce%5Bn%5D%3Do%7Dreturn%20e%7D(i.slice())%2Ca%3D0%2Cr(i%2Cl%5B0%5D)%7Delse%20a%3Cl.length-1%3F(a%2B%2B%2Cr(i%2Cl%5Ba%5D))%3A(a%3Dl.length%2Cr(i%2Cnull))%3Bh()%7D)%2Cf.addEventListener(%22click%22%2Cfunction(e)%7Be.stopPropagation()%2Ci%3Do()%2Cl%3D%5B%5D%2Ca%3D-1%2Cr(i%2Cnull)%2Ch()%7D)%2Cx.addEventListener(%22click%22%2Cfunction(e)%7Be.stopPropagation()%2Cd.remove()%7D)%2Ci%3Do()%2Ch()%7D()%3B">🎲 Stand-up Roller</a></p> | |
| <ol> | |
| <li>Make sure your bookmarks bar is visible (<code>⌘⇧B</code> / <code>Ctrl⇧B</code>).</li> | |
| <li>Drag the blue button onto it. (Browsers don't allow one-click install — dragging is the only way.)</li> | |
| <li>Open your Jira board, expand the assignee filter bar, then click the bookmark.</li> | |
| </ol> | |
| <p class="muted">Can't drag? Right-click the button → <em>Copy link</em>, then create a new bookmark and paste it into the URL/address field.</p> | |
| </div> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Stand-up random-order roller — Jira assignee filter bar. | |
| * | |
| * Discovers the people in the Jira "filter selection bar" (the assignee | |
| * avatar/name checkboxes), shuffles them into a random order, and gives you a | |
| * floating button in the bottom-left. Each click rolls to the next person | |
| * (without replacement): it unchecks the previous person and checks the next, | |
| * so the board filters down to whoever is currently up. After the last person | |
| * it shows a "done" state; click again to reshuffle and run another round. | |
| * | |
| * This is the readable source. The drag-to-bookmarks one-liner is in | |
| * bookmarklet.txt (regenerate with: node build.js). | |
| */ | |
| (function () { | |
| 'use strict'; | |
| var WIDGET_ID = '__standup_roller__'; | |
| var FS_SELECTOR = | |
| 'fieldset[data-testid="software-filters.ui.filter-selection-bar.filter-selection-bar"]'; | |
| // Non-person quick-filters that live in the same bar. Compared case-insensitively | |
| // against the trimmed label text, so they never end up in the roster. | |
| var EXCLUDE = [ | |
| 'unassigned', | |
| 'assigned to me', | |
| 'only my issues', | |
| 'recently updated', | |
| 'deployed to production' | |
| ]; | |
| function getFieldset() { | |
| var fs = document.querySelector(FS_SELECTOR); | |
| if (fs) return fs; | |
| // Fallback: any fieldset that holds Jira filter checkboxes. | |
| var cb = document.querySelector('input[id^="checkbox-id-"]'); | |
| return cb ? cb.closest('fieldset') : null; | |
| } | |
| function labelFor(fs, id) { | |
| var sel = 'label[for="' + (window.CSS && CSS.escape ? CSS.escape(id) : id) + '"]'; | |
| var lb = fs.querySelector(sel); | |
| return lb ? lb.textContent.trim() : ''; | |
| } | |
| // Build the roster of people from the live DOM. | |
| function discover() { | |
| var fs = getFieldset(); | |
| if (!fs) return []; | |
| var out = []; | |
| var seen = {}; | |
| fs.querySelectorAll('input[type="checkbox"]').forEach(function (inp) { | |
| if (!inp.id || seen[inp.id]) return; | |
| var name = labelFor(fs, inp.id); | |
| if (!name) return; | |
| if (EXCLUDE.indexOf(name.toLowerCase()) !== -1) return; | |
| seen[inp.id] = 1; | |
| out.push({ id: inp.id, name: name }); | |
| }); | |
| return out; | |
| } | |
| // Re-find a person's live checkbox each time — Jira re-renders nodes, so a | |
| // stored reference can go stale. Match by id first, then fall back to label text. | |
| function resolve(entry) { | |
| var el = document.getElementById(entry.id); | |
| if (el && el.type === 'checkbox') return el; | |
| var fs = getFieldset(); | |
| if (!fs) return null; | |
| var labels = fs.querySelectorAll('label'); | |
| for (var i = 0; i < labels.length; i++) { | |
| if (labels[i].textContent.trim() === entry.name) { | |
| var fid = labels[i].getAttribute('for'); | |
| var inp = fid && document.getElementById(fid); | |
| if (inp) return inp; | |
| } | |
| } | |
| return null; | |
| } | |
| // Real click → toggles the box AND fires the event React listens for. Only | |
| // click when the current state differs from what we want, so it's idempotent. | |
| function setChecked(input, desired) { | |
| if (input && input.checked !== desired) input.click(); | |
| } | |
| // Ensure exactly `current` is checked among the roster (null = uncheck all). | |
| function selectOnly(roster, current) { | |
| roster.forEach(function (e) { | |
| setChecked(resolve(e), e === current); | |
| }); | |
| } | |
| function shuffle(a) { | |
| for (var i = a.length - 1; i > 0; i--) { | |
| var j = Math.floor(Math.random() * (i + 1)); | |
| var t = a[i]; | |
| a[i] = a[j]; | |
| a[j] = t; | |
| } | |
| return a; | |
| } | |
| // ---- state ---- | |
| var roster = []; | |
| var order = []; | |
| var idx = -1; // -1 = idle, 0..N-1 = running, >=N = done | |
| // ---- UI ---- | |
| var old = document.getElementById(WIDGET_ID); | |
| if (old) old.remove(); | |
| var box = document.createElement('div'); | |
| box.id = WIDGET_ID; | |
| box.style.cssText = [ | |
| 'position:fixed', 'left:20px', 'bottom:20px', 'z-index:2147483647', | |
| 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif', | |
| 'min-width:220px', 'max-width:320px', 'background:#1d2125', 'color:#fff', | |
| 'border:1px solid rgba(255,255,255,.12)', 'border-radius:14px', | |
| 'box-shadow:0 12px 32px rgba(0,0,0,.45)', 'overflow:hidden', 'user-select:none' | |
| ].join(';'); | |
| var header = document.createElement('div'); | |
| header.style.cssText = | |
| 'display:flex;align-items:center;justify-content:space-between;padding:8px 12px;' + | |
| 'background:rgba(255,255,255,.06);font-size:12px;font-weight:600;letter-spacing:.04em;' + | |
| 'text-transform:uppercase;color:#9fadbc'; | |
| var htitle = document.createElement('span'); | |
| htitle.textContent = 'Stand-up \uD83C\uDFB2'; | |
| var hbtns = document.createElement('div'); | |
| hbtns.style.cssText = 'display:flex;gap:6px'; | |
| var resetBtn = document.createElement('button'); | |
| resetBtn.textContent = '\u21BA'; | |
| resetBtn.title = 'Reset'; | |
| var closeBtn = document.createElement('button'); | |
| closeBtn.textContent = '\u2715'; | |
| closeBtn.title = 'Close'; | |
| [resetBtn, closeBtn].forEach(function (b) { | |
| b.style.cssText = | |
| 'all:unset;cursor:pointer;color:#9fadbc;font-size:13px;line-height:1;padding:2px 5px;border-radius:6px'; | |
| b.onmouseenter = function () { b.style.color = '#fff'; b.style.background = 'rgba(255,255,255,.12)'; }; | |
| b.onmouseleave = function () { b.style.color = '#9fadbc'; b.style.background = 'transparent'; }; | |
| }); | |
| hbtns.appendChild(resetBtn); | |
| hbtns.appendChild(closeBtn); | |
| header.appendChild(htitle); | |
| header.appendChild(hbtns); | |
| var bodyBtn = document.createElement('button'); | |
| bodyBtn.style.cssText = | |
| 'all:unset;display:block;cursor:pointer;width:100%;box-sizing:border-box;padding:16px 16px 18px'; | |
| var nameEl = document.createElement('div'); | |
| nameEl.style.cssText = 'font-size:24px;font-weight:700;line-height:1.15;word-break:break-word'; | |
| var subEl = document.createElement('div'); | |
| subEl.style.cssText = 'margin-top:6px;font-size:12px;color:#9fadbc'; | |
| bodyBtn.appendChild(nameEl); | |
| bodyBtn.appendChild(subEl); | |
| bodyBtn.onmouseenter = function () { bodyBtn.style.background = 'rgba(255,255,255,.05)'; }; | |
| bodyBtn.onmouseleave = function () { bodyBtn.style.background = 'transparent'; }; | |
| box.appendChild(header); | |
| box.appendChild(bodyBtn); | |
| document.body.appendChild(box); | |
| function render() { | |
| var n = order.length; | |
| if (idx === -1) { | |
| nameEl.textContent = '\uD83C\uDFB2 Start stand-up'; | |
| nameEl.style.color = '#fff'; | |
| subEl.textContent = | |
| roster.length + (roster.length === 1 ? ' person' : ' people') + ' \u00B7 click to begin'; | |
| } else if (idx >= n) { | |
| nameEl.textContent = '\u2705 All done!'; | |
| nameEl.style.color = '#4bce97'; | |
| subEl.textContent = 'click to run again'; | |
| } else { | |
| var cur = order[idx]; | |
| nameEl.textContent = cur.name; | |
| nameEl.style.color = '#669df1'; | |
| var last = idx === n - 1; | |
| subEl.textContent = | |
| (idx + 1) + ' / ' + n + ' \u00B7 ' + (last ? 'last \u2014 click to finish' : 'click for next'); | |
| } | |
| } | |
| function advance() { | |
| if (idx === -1 || idx >= order.length) { | |
| // (re)start a round | |
| roster = discover(); | |
| if (!roster.length) { | |
| nameEl.textContent = 'No people found'; | |
| nameEl.style.color = '#f87168'; | |
| subEl.textContent = 'open the assignee filter bar first'; | |
| return; | |
| } | |
| order = shuffle(roster.slice()); | |
| idx = 0; | |
| selectOnly(roster, order[0]); | |
| } else if (idx < order.length - 1) { | |
| idx++; | |
| selectOnly(roster, order[idx]); | |
| } else { | |
| idx = order.length; // last -> done | |
| selectOnly(roster, null); | |
| } | |
| render(); | |
| } | |
| bodyBtn.addEventListener('click', advance); | |
| resetBtn.addEventListener('click', function (ev) { | |
| ev.stopPropagation(); | |
| roster = discover(); | |
| order = []; | |
| idx = -1; | |
| selectOnly(roster, null); | |
| render(); | |
| }); | |
| closeBtn.addEventListener('click', function (ev) { | |
| ev.stopPropagation(); | |
| box.remove(); | |
| }); | |
| roster = discover(); | |
| render(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment