Skip to content

Instantly share code, notes, and snippets.

@markmarijnissen
Created January 19, 2026 11:18
Show Gist options
  • Select an option

  • Save markmarijnissen/e9a5dca544e285fdf6a2b7ce84e8e265 to your computer and use it in GitHub Desktop.

Select an option

Save markmarijnissen/e9a5dca544e285fdf6a2b7ce84e8e265 to your computer and use it in GitHub Desktop.
Trello: List Checklist items, grouped by User.
// ==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 &rarr; Card Assignee &rarr; 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;">&times;</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