feat: Add mobile task index with 11ty build-time data

Phase 2 of task management consolidation:

- Added _data/tasks.js - fetches tasks from Gitea API at build time
- Added tasks-index.njk - mobile-friendly task list page
- Added node-fetch dependency for API calls
- Added .gitignore for node_modules and _site

Features:
- Shows only open/blocked tasks (filters out complete)
- Priority filtering (P1/P2/P3/P4)
- Color-coded priority badges (Fire/Gold/Frost/Arcane)
- Links to Gitea for full task details
- Mobile-optimized touch targets

Access at: firefrostgaming.com/tasks-index.html

Chronicler #69
This commit is contained in:
Claude
2026-04-08 14:25:00 +00:00
parent cb079ae52d
commit 9bb4101ffe
5 changed files with 448 additions and 0 deletions

128
_data/tasks.js Normal file
View File

@@ -0,0 +1,128 @@
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
module.exports = async function() {
const GITEA_API = 'https://git.firefrostgaming.com/api/v1';
const REPO_OWNER = 'firefrost-gaming';
const REPO_NAME = 'firefrost-operations-manual';
const TOKEN = process.env.GITEA_TOKEN || 'e0e330cba1749b01ab505093a160e4423ebbbe36';
const tasks = [];
try {
// Get list of task directories
const response = await fetch(
`${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/docs/tasks?ref=master`,
{
headers: {
'Authorization': `token ${TOKEN}`,
'Accept': 'application/json'
},
timeout: 30000
}
);
if (!response.ok) {
console.error('Failed to fetch task list:', response.status);
return [];
}
const dirs = await response.json();
const taskDirs = dirs.filter(d => d.type === 'dir' && d.name !== '_archive');
// Fetch each task's README
for (const dir of taskDirs) {
try {
// Try README.md first
let readmePath = `docs/tasks/${dir.name}/README.md`;
let readmeResponse = await fetch(
`${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/${encodeURIComponent(readmePath)}?ref=master`,
{
headers: {
'Authorization': `token ${TOKEN}`,
'Accept': 'application/json'
}
}
);
if (!readmeResponse.ok) {
// Try to find any .md file
const filesResponse = await fetch(
`${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/docs/tasks/${encodeURIComponent(dir.name)}?ref=master`,
{
headers: {
'Authorization': `token ${TOKEN}`,
'Accept': 'application/json'
}
}
);
if (filesResponse.ok) {
const files = await filesResponse.json();
const mdFile = files.find(f => f.name.endsWith('.md'));
if (mdFile) {
readmePath = `docs/tasks/${dir.name}/${mdFile.name}`;
readmeResponse = await fetch(
`${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/${encodeURIComponent(readmePath)}?ref=master`,
{
headers: {
'Authorization': `token ${TOKEN}`,
'Accept': 'application/json'
}
}
);
}
}
}
if (readmeResponse && readmeResponse.ok) {
const fileData = await readmeResponse.json();
const content = Buffer.from(fileData.content, 'base64').toString('utf-8');
// Parse YAML frontmatter
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
if (frontmatterMatch) {
const frontmatter = {};
frontmatterMatch[1].split('\n').forEach(line => {
const match = line.match(/^(\w+):\s*(.+)$/);
if (match) {
frontmatter[match[1]] = match[2].trim();
}
});
// Extract title from first H1
const titleMatch = content.match(/^#\s+(.+)$/m);
const title = titleMatch ? titleMatch[1] : dir.name;
tasks.push({
slug: dir.name,
title: title,
status: frontmatter.status || 'open',
priority: frontmatter.priority || 'P3',
owner: frontmatter.owner || 'Michael',
created: frontmatter.created || '2026-01-01',
giteaUrl: `https://git.firefrostgaming.com/${REPO_OWNER}/${REPO_NAME}/src/branch/master/docs/tasks/${encodeURIComponent(dir.name)}`
});
}
}
} catch (err) {
console.error(`Error processing ${dir.name}:`, err.message);
}
}
// Sort by priority (P1 first) then by title
tasks.sort((a, b) => {
if (a.priority !== b.priority) {
return a.priority.localeCompare(b.priority);
}
return a.title.localeCompare(b.title);
});
console.log(`[11ty] Loaded ${tasks.length} tasks from Gitea`);
return tasks;
} catch (err) {
console.error('[11ty] Error fetching tasks:', err.message);
// Return empty array - page will still build, just with no tasks
return [];
}
};