aboutsummaryrefslogtreecommitdiff
path: root/modules/ext/kanban.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-07-29 22:26:50 +0200
committerAgando <roduccomm@gmail.com>2026-07-29 22:26:50 +0200
commit7f3df2012363a152d921c68a5f26ed6da0c2d428 (patch)
tree4843acf1894288786e09c7ff34caebdfa97c6800 /modules/ext/kanban.js
parent0003ad95fa091353827198d0a45cd63a3784479f (diff)
feat: add kanban module to newOS4 — todo/doing/done board with priorities
Diffstat (limited to 'modules/ext/kanban.js')
-rw-r--r--modules/ext/kanban.js190
1 files changed, 190 insertions, 0 deletions
diff --git a/modules/ext/kanban.js b/modules/ext/kanban.js
new file mode 100644
index 0000000..63d7f09
--- /dev/null
+++ b/modules/ext/kanban.js
@@ -0,0 +1,190 @@
+class KanbanModule {
+ constructor() {
+ this.commands = {};
+ this.terminal = null;
+ this.storageKey = 'webx11_kanban_tasks';
+ this.columns = ['todo', 'doing', 'done'];
+ this.priorities = ['low', 'medium', 'high'];
+ }
+
+ getHelpInfo() {
+ return {
+ 'kanban': 'kanban <command> [args] — Manage tasks on a kanban board (todo, doing, done columns)'
+ };
+ }
+
+ registerCommands() {
+ this.commands['kanban'] = (args) => this.handleKanban(args);
+ }
+
+ loadTasks() {
+ try {
+ const data = localStorage.getItem(this.storageKey);
+ return data ? JSON.parse(data) : [];
+ } catch (e) {
+ return [];
+ }
+ }
+
+ saveTasks(tasks) {
+ try {
+ localStorage.setItem(this.storageKey, JSON.stringify(tasks));
+ } catch (e) {
+ if (this.terminal) {
+ this.terminal.print('Failed to save kanban tasks', 'error');
+ }
+ }
+ }
+
+ getNextId(tasks) {
+ if (!tasks.length) return 1;
+ return Math.max(...tasks.map(t => t.id)) + 1;
+ }
+
+ printUsage() {
+ this.terminal.print('Usage:', 'info');
+ this.terminal.print(' kanban add <task> Add task to TODO column', 'info');
+ this.terminal.print(' kanban move <id> <column> Move task to column (todo/doing/done)', 'info');
+ this.terminal.print(' kanban list Show all columns with tasks', 'info');
+ this.terminal.print(' kanban rm <id> Remove task', 'info');
+ this.terminal.print(' kanban clear Clear all tasks', 'info');
+ this.terminal.print(' kanban priority <id> <level> Set priority (low/medium/high)', 'info');
+ }
+
+ handleKanban(args) {
+ if (!args[0]) {
+ return this.printUsage();
+ }
+
+ const sub = args[0].toLowerCase();
+
+ switch (sub) {
+ case 'add':
+ return this.addTask(args.slice(1));
+ case 'move':
+ return this.moveTask(args[1], args[2]);
+ case 'list':
+ return this.listTasks();
+ case 'rm':
+ return this.removeTask(args[1]);
+ case 'clear':
+ return this.clearTasks();
+ case 'priority':
+ return this.setPriority(args[1], args[2]);
+ default:
+ this.terminal.print(`Unknown kanban command: ${sub}`, 'error');
+ return this.printUsage();
+ }
+ }
+
+ addTask(titleArgs) {
+ const title = titleArgs.join(' ').trim();
+ if (!title) {
+ return this.terminal.print('Usage: kanban add <task>', 'warning');
+ }
+
+ const tasks = this.loadTasks();
+ const task = {
+ id: this.getNextId(tasks),
+ title: title,
+ column: 'todo',
+ priority: 'medium',
+ created: new Date().toISOString()
+ };
+
+ tasks.push(task);
+ this.saveTasks(tasks);
+ this.terminal.print(`Added task #${task.id}: ${title}`, 'success');
+ }
+
+ moveTask(idStr, column) {
+ const id = parseInt(idStr, 10);
+ if (isNaN(id) || !column) {
+ return this.terminal.print('Usage: kanban move <id> <column>', 'warning');
+ }
+
+ const col = column.toLowerCase();
+ if (!this.columns.includes(col)) {
+ return this.terminal.print(`Unknown column: ${column}. Use todo/doing/done.`, 'error');
+ }
+
+ const tasks = this.loadTasks();
+ const task = tasks.find(t => t.id === id);
+ if (!task) {
+ return this.terminal.print(`Task #${id} not found`, 'error');
+ }
+
+ task.column = col;
+ this.saveTasks(tasks);
+ this.terminal.print(`Moved task #${id} to ${col}`, 'success');
+ }
+
+ listTasks() {
+ const tasks = this.loadTasks();
+
+ if (!tasks.length) {
+ return this.terminal.print('No tasks on the kanban board.', 'info');
+ }
+
+ this.terminal.print('Kanban board:', 'info');
+
+ for (const column of this.columns) {
+ const columnTasks = tasks.filter(t => t.column === column);
+ this.terminal.print(`\n[${column.toUpperCase()}]`, 'info');
+
+ if (!columnTasks.length) {
+ this.terminal.print(' (empty)', 'info');
+ continue;
+ }
+
+ for (const task of columnTasks) {
+ this.terminal.print(` #${task.id} ${task.title} [${task.priority}]`, 'info');
+ }
+ }
+ }
+
+ removeTask(idStr) {
+ const id = parseInt(idStr, 10);
+ if (isNaN(id)) {
+ return this.terminal.print('Usage: kanban rm <id>', 'warning');
+ }
+
+ const tasks = this.loadTasks();
+ const index = tasks.findIndex(t => t.id === id);
+ if (index === -1) {
+ return this.terminal.print(`Task #${id} not found`, 'error');
+ }
+
+ const removed = tasks.splice(index, 1)[0];
+ this.saveTasks(tasks);
+ this.terminal.print(`Removed task #${id}: ${removed.title}`, 'success');
+ }
+
+ clearTasks() {
+ this.saveTasks([]);
+ this.terminal.print('All kanban tasks cleared', 'success');
+ }
+
+ setPriority(idStr, priority) {
+ const id = parseInt(idStr, 10);
+ const level = (priority || '').toLowerCase();
+
+ if (isNaN(id) || !this.priorities.includes(level)) {
+ return this.terminal.print('Usage: kanban priority <id> <low|medium|high>', 'warning');
+ }
+
+ const tasks = this.loadTasks();
+ const task = tasks.find(t => t.id === id);
+ if (!task) {
+ return this.terminal.print(`Task #${id} not found`, 'error');
+ }
+
+ task.priority = level;
+ this.saveTasks(tasks);
+ this.terminal.print(`Set task #${id} priority to ${level}`, 'success');
+ }
+}
+
+if (typeof window.webx11 !== 'undefined' && window.webx11) {
+ webx11.moduleManager.registerModule(new KanbanModule(), 'https://webx11.duckdns.org/spaces/newos4/modules/ext/kanban.js');
+}