Skip to content

Instantly share code, notes, and snippets.

@hiyukoim
Last active March 16, 2026 01:37
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.4
// @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() {
const WEBHOOK_STORAGE_KEY = 'WebhookUrl';
// --- Register Menu Command for the generic Webhook ---
GM_registerMenuCommand('βš™οΈ Set Webhook URL', () => {
const currentUrl = GM_getValue(WEBHOOK_STORAGE_KEY, '');
const newUrl = prompt('Enter your Webhook URL for Syncing:\n(e.g., n8n, Zapier, or custom endpoint)', currentUrl);
if (newUrl !== null) {
GM_setValue(WEBHOOK_STORAGE_KEY, newUrl.trim());
alert('βœ… Webhook URL saved successfully!');
}
});
function getTodayStr() {
const today = new Date();
return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
}
// --- Core: Scan Board via /_api/resource/{board}/ ---
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] Failed to scan board: ${boardName}`, e);
}
return boardStatus;
}
// --- Title Indicator (πŸ”₯ / ❗) ---
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 observer = new MutationObserver(() => {
if (injectTitleIcon(status)) observer.disconnect();
});
observer.observe(document.body, { childList: true, subtree: true });
}
// --- Main UI Initialization ---
async function init() {
const res = await fetch('/_api/resource/');
const boards = await res.json();
const currentBoard = boards.find(b => location.pathname.includes(b.name));
// --- Create Board Switcher (Native Structure) ---
const groupItem = document.createElement('div');
groupItem.className = 'app-header__group-item';
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}`;
const isCurrentBoard = currentBoard && board.name === currentBoard.name;
if (isCurrentBoard) 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 (isCurrentBoard) waitForTitleAndInject(status);
});
});
select.onchange = () => location.href = `/${select.value}`;
groupItem.appendChild(label);
groupItem.appendChild(select);
// --- Create Sync Button (Native Style) ---
const syncBtn = document.createElement('button');
syncBtn.type = 'button';
syncBtn.textContent = 'πŸ”„ Sync';
syncBtn.style.marginLeft = '8px';
syncBtn.onclick = async () => {
const url = GM_getValue(WEBHOOK_STORAGE_KEY, '');
if (!url) return alert('Please set your Webhook URL in the Tampermonkey Menu first!');
const originalText = syncBtn.textContent;
syncBtn.textContent = '⏳';
await fetch(url, { mode: 'no-cors' }).catch(() => {});
syncBtn.textContent = 'βœ…';
setTimeout(() => syncBtn.textContent = originalText, 2000);
};
const injectUI = () => {
const header = document.querySelector('.app-header');
if (header) {
// Places the new UI at the end of the header (after Language)
header.appendChild(groupItem);
header.appendChild(syncBtn);
return true;
}
return false;
};
if (!injectUI()) {
const headerObserver = new MutationObserver(() => {
if (injectUI()) headerObserver.disconnect();
});
headerObserver.observe(document.body, { childList: true, subtree: true });
}
}
// Start
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
@hiyukoim
Copy link
Author

hiyukoim commented Mar 16, 2026

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