Skip to content

Instantly share code, notes, and snippets.

@hiyukoim
Last active March 17, 2026 02:18
Show Gist options
  • Select an option

  • Save hiyukoim/121273fbded5f1d4ad6d8fca312a1835 to your computer and use it in GitHub Desktop.

Select an option

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)
// ==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();
})();
@hiyukoim

hiyukoim commented Mar 16, 2026

Copy link
Copy Markdown
Author

πŸš€ 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:

  1. Open the Tampermonkey Dashboard.
  2. Click on Tasks.md Plus to edit.
  3. Go to the Settings tab.
  4. Find User matches and add your instance URL: https://task.your-domain.com/*.
  5. (Optional but recommended) Uncheck Original matches to disable the script on the placeholder domain.

3. Set Up Your Webhook

The Sync button allows you to push your local board state to external services like n8n:

  1. Click the Tampermonkey icon in your browser toolbar.
  2. Select βš™οΈ Webhook URL.
  3. Paste your webhook URL (e.g., from n8n, Zapier, or a custom script).
  4. Click the πŸ”„ Sync button in the header whenever you want to trigger a sync!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment