Created
January 19, 2026 11:18
-
-
Save markmarijnissen/e9a5dca544e285fdf6a2b7ce84e8e265 to your computer and use it in GitHub Desktop.
Trello: List Checklist items, grouped by User.
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 Trello Checklist Summary (Team View) | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.1 | |
| // @description Adds a button to Trello Board Header to view incomplete checklist items by @mention or assignee. | |
| // @author You | |
| // @match https://trello.com/b/* | |
| // @grant none | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| /* --- CONFIGURATION --- */ | |
| const BUTTON_ID = 'trello-checklist-summary-btn'; | |
| const CHECK_INTERVAL = 2000; // Check every 2 seconds if button needs re-injecting (SPA behavior) | |
| /* --- MAIN LOGIC (Reused from Bookmarklet) --- */ | |
| function runReport() { | |
| const url = window.location.href.split('?')[0].replace(/\/$/, "") + '.json'; | |
| // Loader | |
| const loader = document.createElement('div'); | |
| loader.innerHTML = '<div style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);color:white;display:flex;justify-content:center;align-items:center;z-index:9999;font-family:sans-serif;font-size:24px;">Analyzing Assignments...</div>'; | |
| document.body.appendChild(loader); | |
| fetch(url) | |
| .then(r => r.json()) | |
| .then(data => { | |
| const members = {}; | |
| if(data.members) data.members.forEach(m => members[m.id] = m.fullName); | |
| const cards = {}; | |
| if(data.cards) data.cards.forEach(c => cards[c.id] = c); | |
| const report = {}; | |
| const addToReport = (groupName, card, item) => { | |
| if (!report[groupName]) report[groupName] = []; | |
| report[groupName].push({ | |
| cardName: card.name, | |
| itemName: item.name, | |
| link: card.shortUrl | |
| }); | |
| }; | |
| if(data.checklists) { | |
| data.checklists.forEach(cl => { | |
| const card = cards[cl.idCard]; | |
| if(!card || card.closed) return; | |
| cl.checkItems.forEach(item => { | |
| if (item.state === 'incomplete') { | |
| const mentionRegex = /@([a-zA-Z0-9_]+)/g; | |
| const matches = item.name.match(mentionRegex); | |
| if (matches) { | |
| matches.forEach(handle => addToReport(handle, card, item)); | |
| } else { | |
| if (card.idMembers && card.idMembers.length > 0) { | |
| const firstMemberId = card.idMembers[0]; | |
| const memberName = members[firstMemberId] || 'Unknown Member'; | |
| addToReport(memberName + " (Card Owner)", card, item); | |
| } else { | |
| addToReport('Unassigned', card, item); | |
| } | |
| } | |
| } | |
| }); | |
| }); | |
| } | |
| // Build UI | |
| let htmlContent = '<div style="padding:20px; max-height:80vh; overflow-y:auto;">'; | |
| htmlContent += '<h2 style="margin-top:0; color:#172b4d;">Checklist Summary</h2>'; | |
| // htmlContent += '<p style="font-size:0.9em; color:#5e6c84; margin-bottom:20px; border-bottom:1px solid #ddd; padding-bottom:10px;">Logic: @mentions → Card Assignee → Unassigned</p>'; | |
| const sortedUsers = Object.keys(report).sort(); | |
| if(sortedUsers.length === 0) htmlContent += '<p>No incomplete checklist items found.</p>'; | |
| sortedUsers.forEach(user => { | |
| let headerColor = '#172b4d'; | |
| if(user === 'Unassigned') headerColor = '#eb5a46'; | |
| else if(user.includes('(Card Owner)')) headerColor = '#0079bf'; | |
| htmlContent += `<h3 style="border-bottom:2px solid #dfe1e6; padding-bottom:5px; margin-top:20px; color:${headerColor};">${user} <span style="font-size:0.8em; color:#5e6c84;">(${report[user].length})</span></h3>`; | |
| htmlContent += '<ul style="list-style:none; padding-left:0;">'; | |
| report[user].forEach(r => { | |
| htmlContent += `<li style="margin-bottom:8px; padding:10px; background:#f4f5f7; border-radius:3px; border-left: 3px solid ${headerColor};"> | |
| <input type="checkbox" disabled> <span style="color:#172b4d;">${r.itemName}</span><br> | |
| <span style="font-size:0.85em; color:#5e6c84; margin-left: 20px;">on card: <a href="${r.link}" target="_blank" style="color:#172b4d; text-decoration:underline;">${r.cardName}</a></span> | |
| </li>`; | |
| }); | |
| htmlContent += '</ul>'; | |
| }); | |
| htmlContent += '</div>'; | |
| htmlContent += '<button id="close-trello-report" style="position:absolute; top:15px; right:15px; background:transparent; color:#42526e; border:none; padding:5px 10px; cursor:pointer; font-size:20px;">×</button>'; | |
| loader.remove(); | |
| // Remove existing overlay if any | |
| const existing = document.getElementById('trello-report-overlay'); | |
| if(existing) existing.remove(); | |
| const overlay = document.createElement('div'); | |
| overlay.id = 'trello-report-overlay'; | |
| overlay.innerHTML = htmlContent; | |
| overlay.style.cssText = 'position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); width:650px; max-width:90%; background:white; box-shadow:0 0 50px rgba(0,0,0,0.5); z-index:10000; border-radius:3px; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif; color:#172b4d;'; | |
| document.body.appendChild(overlay); | |
| document.getElementById('close-trello-report').onclick = function(){ overlay.remove(); }; | |
| }).catch(e => { | |
| loader.remove(); | |
| alert('Error: Could not fetch board data. Please refresh and try again.'); | |
| console.error(e); | |
| }); | |
| } | |
| /* --- DOM INJECTION --- */ | |
| function injectButton() { | |
| // Trello classes change often. We target the board header container. | |
| // We look for the "Board Header" right side buttons container. | |
| const header = document.querySelector('[data-testid="board-header"] > span:last-child'); | |
| // If header exists AND button doesn't exist yet | |
| if (header && !document.getElementById(BUTTON_ID)) { | |
| const btn = document.createElement('a'); | |
| btn.id = BUTTON_ID; | |
| btn.className = 'board-header-btn'; // Trello native class for styling | |
| btn.href = '#'; | |
| btn.style.cssText = 'background-color: rgba(255,255,255,0.3); color: white; padding: 0 12px; margin-right: 8px; display: inline-flex; align-items: center; text-decoration: none; font-weight: bold; height: 32px; border-radius: 3px; line-height: 32px;'; | |
| btn.innerHTML = '<span class="icon-sm icon-checklist" style="margin-right:6px;"></span> Checklist'; | |
| // Hover effect manually since we are inline | |
| btn.onmouseover = function() { this.style.backgroundColor = 'rgba(255,255,255,0.4)'; }; | |
| btn.onmouseout = function() { this.style.backgroundColor = 'rgba(255,255,255,0.3)'; }; | |
| btn.onclick = function(e) { | |
| e.preventDefault(); | |
| runReport(); | |
| }; | |
| // Insert as the first item in the button list | |
| header.insertBefore(btn, header.firstChild); | |
| } | |
| } | |
| /* --- INITIALIZATION --- */ | |
| // Trello is an SPA (Single Page App). | |
| // The header might disappear/reappear when navigating. | |
| // We use a simple interval to ensure the button stays there. | |
| setInterval(injectButton, CHECK_INTERVAL); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment