Last active
July 22, 2026 07:50
-
-
Save aakashtyg/58f94b088296278d391fa5ba6dc8aa21 to your computer and use it in GitHub Desktop.
Add checkboxes as the first column in markdown tables rendered on Github
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
| // ==UserScript== | |
| // @name GitHub README table checkboxes | |
| // @namespace local | |
| // @match https://github.com/* | |
| // @grant none | |
| // ==/UserScript== | |
| (() => { | |
| // --- storage key: one bucket per repo + markdown file ----------------- | |
| function storageKey() { | |
| const p = location.pathname; | |
| const repo = (p.match(/^\/[^/]+\/[^/]+/) || ['/'])[0]; | |
| const file = (p.match(/\/(?:blob|tree)\/[^/]+\/(.*)$/) || [, ''])[1] | |
| .replace(/README\.md$/i, '') | |
| .replace(/\/$/, ''); | |
| return 'gh-table-checks:' + repo + (file ? '#' + file : ''); | |
| } | |
| // --- main ------------------------------------------------------------ | |
| function run() { | |
| const KEY = storageKey(); | |
| let state; | |
| try { | |
| state = JSON.parse(localStorage.getItem(KEY) || '{}'); | |
| } catch { | |
| state = {}; | |
| } | |
| const save = () => localStorage.setItem(KEY, JSON.stringify(state)); | |
| const tables = document.querySelectorAll('.markdown-body table, article table'); | |
| if (!tables.length) return; | |
| let added = 0; | |
| tables.forEach((table, ti) => { | |
| if (table.dataset.checkboxed) return; | |
| table.dataset.checkboxed = '1'; | |
| added++; | |
| const headRow = table.querySelector('thead tr'); | |
| if (headRow) { | |
| const th = document.createElement('th'); | |
| th.textContent = '\u2713'; | |
| th.style.width = '36px'; | |
| headRow.insertBefore(th, headRow.firstChild); | |
| } | |
| table.querySelectorAll('tbody tr').forEach((tr, ri) => { | |
| // stable key from the row's question text, position as fallback | |
| const label = (tr.cells[1]?.innerText || tr.innerText || '').trim().slice(0, 80); | |
| const rowKey = `${ti}|${label || 'row' + ri}`; | |
| const td = document.createElement('td'); | |
| td.style.textAlign = 'center'; | |
| const box = document.createElement('input'); | |
| box.type = 'checkbox'; | |
| box.style.cssText = 'width:16px;height:16px;cursor:pointer;'; | |
| box.checked = !!state[rowKey]; | |
| const paint = () => { | |
| tr.style.opacity = box.checked ? '0.45' : ''; | |
| tr.style.textDecoration = box.checked ? 'line-through' : ''; | |
| }; | |
| box.addEventListener('change', () => { | |
| if (box.checked) state[rowKey] = true; | |
| else delete state[rowKey]; | |
| save(); | |
| paint(); | |
| }); | |
| paint(); | |
| td.appendChild(box); | |
| tr.insertBefore(td, tr.firstChild); | |
| }); | |
| }); | |
| if (added) { | |
| const done = Object.keys(state).length; | |
| console.log(`[table-checkboxes] ${added} table(s) processed, ${done} row(s) checked. Key: ${KEY}`); | |
| } | |
| // helpers, rebound to whatever page you're currently on | |
| window.resetChecks = () => { | |
| localStorage.removeItem(KEY); | |
| location.reload(); | |
| }; | |
| window.exportChecks = () => console.log(JSON.stringify(state, null, 2)); | |
| window.importChecks = (json) => { | |
| localStorage.setItem(KEY, typeof json === 'string' ? json : JSON.stringify(json)); | |
| location.reload(); | |
| }; | |
| } | |
| // --- run now, and again after GitHub's client-side navigation --------- | |
| run(); | |
| if (!window.__ghTableCheckboxHooked) { | |
| window.__ghTableCheckboxHooked = true; | |
| document.addEventListener('turbo:load', run); | |
| document.addEventListener('turbo:render', run); | |
| document.addEventListener('pjax:end', run); | |
| // README bodies sometimes stream in after the page event fires | |
| let pending; | |
| new MutationObserver(() => { | |
| clearTimeout(pending); | |
| pending = setTimeout(run, 150); | |
| }).observe(document.body, { childList: true, subtree: true }); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment