tasks.lmth
All 54 lines. No libraries, nothing hidden. Read it before you run it.
<!doctype html>
<meta charset="utf-8">
<meta name="resworb-write" content="./*.md">
<title>Tasks</title>
<style>
body { margin: 0; padding: 24px; color: #111; font: 14px/1.4 'Helvetica Neue', sans-serif; }
main { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
section { min-height: 70vh; padding: 12px; border-top: 1px solid #111; background: #f7f7f8; }
h2 { margin: 0 0 12px; color: #002fa7; font-size: 13px; font-weight: 500; text-transform: uppercase; }
article { margin-bottom: 8px; padding: 10px 12px; border: 1px solid #e3e3e8; background: #fff; cursor: grab; }
small { display: block; color: #6b6b72; }
</style>
<main id="board">
<section data-status="todo"><h2>To do</h2></section>
<section data-status="doing"><h2>Doing</h2></section>
<section data-status="done"><h2>Done</h2></section>
</main>
<script type="module">
// Each .md file in this folder is a card. Its column is the status line at the top, which a drop rewrites:
// ---
// status: doing
// ---
const columns = [...board.children];
const statusLine = /^---\nstatus: (\w+)\n---\n/;
for (const column of columns) {
column.ondragover = event => event.preventDefault();
column.ondrop = async event => {
const name = event.dataTransfer.getData('text/plain');
const text = await resworb.readFile(name);
await resworb.writeFile(name, `---\nstatus: ${column.dataset.status}\n---\n${text.replace(statusLine, '')}`);
};
}
// Draws every card at once, so a watch event that lands mid-draw can't leave a card twice.
async function draw() {
const cards = { todo: [], doing: [], done: [] };
for (const name of await resworb.readdir('.')) {
if (!name.endsWith('.md')) continue;
const text = await resworb.readFile(name);
const card = document.createElement('article');
card.draggable = true;
card.ondragstart = event => event.dataTransfer.setData('text/plain', name);
card.textContent = text.match(/^# (.+)$/m)?.[1] ?? name;
card.append(Object.assign(document.createElement('small'), { textContent: name }));
(cards[text.match(statusLine)?.[1]] ?? cards.todo).push(card);
}
for (const column of columns) column.replaceChildren(column.firstElementChild, ...cards[column.dataset.status]);
}
await draw();
// A drop above, or a save in another editor, redraws the board.
await resworb.watch('.', draw);
</script>