Last active
March 17, 2026 02:18
-
-
Save hiyukoim/121273fbded5f1d4ad6d8fca312a1835 to your computer and use it in GitHub Desktop.
Userscript for Tasks.md: Adds a board switcher, n8n sync button, and automatic today/overdue alerts. (https://github.com/BaldissaraMatheus/Tasks.md)
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 Tasks.md Plus | |
| // @description Adds a board switcher with background due date alerts and a customizable webhook sync. | |
| // @match https://YOUR-TASKS-DOMAIN-HERE.com/* | |
| // @version 1.5 | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // @grant GM_registerMenuCommand | |
| // @updateURL https://gist.githubusercontent.com/hiyukoim/121273fbded5f1d4ad6d8fca312a1835/raw/Tasks.md%2520Plus | |
| // @downloadURL https://gist.githubusercontent.com/hiyukoim/121273fbded5f1d4ad6d8fca312a1835/raw/Tasks.md%2520Plus | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| const WEBHOOK_STORAGE_KEY = 'WebhookUrl'; | |
| GM_registerMenuCommand('βοΈ Set Webhook URL', () => { | |
| const currentUrl = GM_getValue(WEBHOOK_STORAGE_KEY, ''); | |
| const newUrl = prompt('Enter your Webhook URL for Syncing:', currentUrl); | |
| if (newUrl !== null) { | |
| GM_setValue(WEBHOOK_STORAGE_KEY, newUrl.trim()); | |
| alert('β Saved!'); | |
| } | |
| }); | |
| function getTodayStr() { | |
| const today = new Date(); | |
| return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; | |
| } | |
| async function getBoardStatus(boardName) { | |
| const activeLanes = new Set(['_To-Do', 'Doing']); | |
| const todayStr = getTodayStr(); | |
| let boardStatus = 'none'; | |
| try { | |
| const res = await fetch(`/_api/resource/${encodeURIComponent(boardName)}/`); | |
| if (!res.ok) return 'none'; | |
| const lanes = await res.json(); | |
| for (const lane of lanes) { | |
| if (!activeLanes.has(lane.name)) continue; | |
| for (const file of lane.files) { | |
| const matches = [...(file.content || '').matchAll(/\[due:(\d{4}-\d{2}-\d{2})\]/g)]; | |
| for (const match of matches) { | |
| const dueDate = match[1]; | |
| if (dueDate < todayStr) return 'overdue'; | |
| else if (dueDate === todayStr) boardStatus = 'today'; | |
| } | |
| } | |
| } | |
| } catch (e) { | |
| console.error(`[Tasks.md Plus] Status fetch failed: ${boardName}`, e); | |
| } | |
| return boardStatus; | |
| } | |
| function injectTitleIcon(status) { | |
| const titleEl = document.querySelector('h1.app-title'); | |
| if (!titleEl) return false; | |
| const existing = titleEl.querySelector('.tm-plus-status'); | |
| if (existing) existing.remove(); | |
| if (status === 'none') return true; | |
| const icon = document.createElement('span'); | |
| icon.className = 'tm-plus-status'; | |
| icon.textContent = status === 'overdue' ? 'π₯ ' : 'β '; | |
| titleEl.prepend(icon); | |
| return true; | |
| } | |
| function waitForTitleAndInject(status) { | |
| if (injectTitleIcon(status)) return; | |
| const obs = new MutationObserver(() => { | |
| if (injectTitleIcon(status)) obs.disconnect(); | |
| }); | |
| obs.observe(document.body, { childList: true, subtree: true }); | |
| } | |
| let syncBtn, groupItem; | |
| function createUI(boards) { | |
| const currentBoardName = location.pathname.split('/').filter(Boolean).pop(); | |
| groupItem = document.createElement('div'); | |
| groupItem.className = 'app-header__group-item tm-plus-ui'; | |
| groupItem.style.marginLeft = '8px'; | |
| const label = document.createElement('div'); | |
| label.className = 'app-header__group-item-label'; | |
| label.textContent = 'Board:'; | |
| const select = document.createElement('select'); | |
| boards.forEach(board => { | |
| const opt = document.createElement('option'); | |
| opt.value = board.name; | |
| const cleanName = board.name.replaceAll('_', ' '); | |
| opt.textContent = `β³ ${cleanName}`; | |
| if (board.name === currentBoardName) opt.selected = true; | |
| select.appendChild(opt); | |
| getBoardStatus(board.name).then(status => { | |
| if (status === 'overdue') opt.textContent = `π₯ ${cleanName}`; | |
| else if (status === 'today') opt.textContent = `β ${cleanName}`; | |
| else opt.textContent = cleanName; | |
| if (board.name === currentBoardName) waitForTitleAndInject(status); | |
| }); | |
| }); | |
| select.onchange = () => location.href = `/${select.value}`; | |
| groupItem.appendChild(label); | |
| groupItem.appendChild(select); | |
| syncBtn = document.createElement('button'); | |
| syncBtn.type = 'button'; | |
| syncBtn.className = 'tm-plus-ui'; | |
| syncBtn.textContent = 'π Sync'; | |
| syncBtn.style.marginLeft = '8px'; | |
| syncBtn.onclick = async () => { | |
| const url = GM_getValue(WEBHOOK_STORAGE_KEY, ''); | |
| if (!url) return alert('Set Webhook URL in Tampermonkey menu!'); | |
| syncBtn.textContent = 'β³'; | |
| await fetch(url, { mode: 'no-cors' }).catch(() => {}); | |
| syncBtn.textContent = 'β '; | |
| setTimeout(() => syncBtn.textContent = 'π Sync', 2000); | |
| }; | |
| } | |
| function tryInject() { | |
| const header = document.querySelector('.app-header'); | |
| if (header && !header.querySelector('.tm-plus-ui')) { | |
| header.appendChild(groupItem); | |
| header.appendChild(syncBtn); | |
| } | |
| } | |
| async function init() { | |
| try { | |
| const res = await fetch('/_api/resource/'); | |
| const boards = await res.json(); | |
| createUI(boards); | |
| tryInject(); | |
| const observer = new MutationObserver(tryInject); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| } catch (e) { | |
| console.error('[Tasks.md Plus] Init failed', e); | |
| } | |
| } | |
| if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); | |
| else init(); | |
| })(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
π Tasks.md Plus
Enhance your [Tasks.md](https://github.com/BaldissaraMatheus/Tasks.md) experience with a native-feeling header dashboard. This userscript adds a global board switcher with real-time due date alerts and a customizable webhook sync button.
β¨ Features
Global Board Switcher: Navigate between all your boards directly from the header.
Smart Indicators: Scans background folders automatically.
π₯ Overdue: Tasks that passed their due date.
β Today: Tasks due today.
Webhook Integration: A "Sync" button to trigger n8n, Zapier, or any custom webhook to keep your files in sync.
Theme Native: Automatically inherits your Tasks.md CSS theme for a seamless look.
π οΈ How to Use
1. Install a Userscript Manager
Install [Tampermonkey](https://www.tampermonkey.net/) (recommended). While it may work with other managers like Violentmonkey or GreaseMonkey, it has been primarily tested on Tampermonkey.
2. Configure Your Instance
To protect your privacy, the script uses a placeholder for the URL. You must tell Tampermonkey where to run:
https://task.your-domain.com/*.3. Set Up Your Webhook
The Sync button allows you to push your local board state to external services like n8n: