NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Sunsama Daily Shutdownn Today's Progress Generator
// @namespace https://www.patabugen.co.uk/
// @version 1.2
// @description Extract and display today's task progress in Sunsama. Roughly mimicks the pre-AI Daily Shutdown routine.
// @author Sami Walbury
// @match https://app.sunsama.com/group/*/shutdownDay/reflect*
// @grant none
// @license MIT
// @run-at document-idle
// @downloadURL https://openuserjs.org/install/Patabugen/Sunsama_Daily_Shutdownn_Todays_Progress_Generator.user.js
// @updateURL https://openuserjs.org/meta/Patabugen/Sunsama_Daily_Shutdownn_Todays_Progress_Generator.meta.js
// @copyright 2025, Patabugen (https://openuserjs.org/users/Patabugen)
// ==/UserScript==
/*jshint esversion: 6 */
/**
* Changelog:
* 1.0 Initial Release
* 1.1 Escape HTML in task titles and added tags for auto update and set jshint to esversion 6.
* 1.2 Don't set the background colour of the summary to fix display in dark mode
**/
(function () {
'use strict';
function waitForElement(selector, callback) {
const el = document.querySelector(selector);
if (el) {
callback(el);
}
else {
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
callback(el);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
}
function extractTasks() {
const firstDayContainer = document.querySelector('.kanban-day-tasks-items-container');
if (!firstDayContainer) return;
const taskContainers = firstDayContainer.querySelectorAll('.kanban-task-container.kanban-task-container-draggable');
const groupedTasks = {};
taskContainers.forEach(task => {
const titleElement = task.querySelector('.task-item-description');
const groupElement = task.querySelector('.kanban-task-stream-label');
if (!titleElement || !groupElement) return;
const taskTitle = titleElement.textContent.trim();
const groupTitle = groupElement.textContent.trim();
const subtaskElements = task.querySelectorAll('.subtask-item-description-container');
// Escape HTML in task names using an HTMLOptionElement: https://stackoverflow.com/a/22706073/1625144
const subtaskTexts = Array.from(subtaskElements).map(s => (new Option(s.textContent.trim())).innerHTML);
if (!groupedTasks[groupTitle]) {
groupedTasks[groupTitle] = [];
}
groupedTasks[groupTitle].push({
title: (new Option(taskTitle)).innerHTML,
subtasks: subtaskTexts
});
});
let output = '<h3>Today\'s Progress:</h3><br>';
for (const [group, tasks] of Object.entries(groupedTasks)) {
if (group == 'personal') {
continue;
}
output += `<h4><strong>${group}</strong></h4>`;
output += '<ul>';
tasks.forEach(task => {
output += `<li>${task.title}<br>`;
if (task.subtasks.length > 0) {
output += '<ul>';
task.subtasks.forEach(sub => {
output += `<li>${sub}</li>`;
});
output += '</ul>';
}
output += '</li>';
});
output += '</ul><br>';
}
displayRichText(output);
}
function getInjectedContainer() {
const groupBodyContainer = document.querySelector('#group-body-container');
if (!groupBodyContainer) return;
// Remove an existing container if it exists
let injectecdContainer = groupBodyContainer.querySelector('#injected-task-summary');
if (!injectecdContainer) {
injectecdContainer = document.createElement('div');
injectecdContainer.className = 'kanban-day-container'
injectecdContainer.id = 'injected-task-summary';
injectecdContainer.style.overflow = 'auto';
injectecdContainer.style.maxWidth = '100%';
injectecdContainer.style.boxSizing = 'border-box';
injectecdContainer.style.order = 10; // Put it at the far right of any columns
let title = document.createElement('div')
title.className = "kanban-day-label-container"
title.innerHTML = 'Status Update';
injectecdContainer.appendChild(title);
let spacer = document.createElement('div');
let body = document.createElement('div');
body.className = 'today-progress-body';
injectecdContainer.appendChild(body);
}
groupBodyContainer.appendChild(injectecdContainer);
return injectecdContainer.querySelector('.today-progress-body');
}
function displayRichText(htmlContent) {
const richTextEditor = document.createElement('div');
richTextEditor.contentEditable = 'true';
richTextEditor.style.width = '100%';
richTextEditor.style.minHeight = '200px';
richTextEditor.style.border = '1px solid #ccc';
richTextEditor.style.padding = '10px';
richTextEditor.style.boxSizing = 'border-box';
richTextEditor.innerHTML = htmlContent;
const injectedContainer = getInjectedContainer();
injectedContainer.replaceChildren(richTextEditor);
}
function injectButton() {
const button = document.createElement('div');
button.className = 'plan-day-btn';
button.textContent = "Generate Today's Progress";
button.style.cursor = 'pointer';
button.style.padding = '10px';
button.style.textAlign = 'center';
button.style.borderRadius = '4px';
button.style.maxHeight = '40px';
button.style.overflow = 'hidden';
button.style.whiteSpace = 'nowrap';
button.style.textOverflow = 'ellipsis';
button.addEventListener('click', extractTasks);
const injectedContainer = getInjectedContainer();
injectedContainer.replaceChildren(button);
}
waitForElement('.kanban-day-container', injectButton);
})();