agkan 1.6.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +23 -17
- package/README.md +42 -17
- package/dist/board/server.d.ts +8 -0
- package/dist/board/server.d.ts.map +1 -0
- package/dist/board/server.js +657 -0
- package/dist/board/server.js.map +1 -0
- package/dist/cli/commands/board.d.ts +3 -0
- package/dist/cli/commands/board.d.ts.map +1 -0
- package/dist/cli/commands/board.js +26 -0
- package/dist/cli/commands/board.js.map +1 -0
- package/dist/cli/commands/task/add-helpers.d.ts.map +1 -1
- package/dist/cli/commands/task/add-helpers.js +1 -0
- package/dist/cli/commands/task/add-helpers.js.map +1 -1
- package/dist/cli/commands/task/add.d.ts.map +1 -1
- package/dist/cli/commands/task/add.js +11 -0
- package/dist/cli/commands/task/add.js.map +1 -1
- package/dist/cli/commands/task/get.d.ts.map +1 -1
- package/dist/cli/commands/task/get.js +9 -0
- package/dist/cli/commands/task/get.js.map +1 -1
- package/dist/cli/commands/task/purge.d.ts +6 -0
- package/dist/cli/commands/task/purge.d.ts.map +1 -0
- package/dist/cli/commands/task/purge.js +138 -0
- package/dist/cli/commands/task/purge.js.map +1 -0
- package/dist/cli/commands/task/update.d.ts.map +1 -1
- package/dist/cli/commands/task/update.js +26 -1
- package/dist/cli/commands/task/update.js.map +1 -1
- package/dist/cli/index.js +6 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +28 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/models/Priority.d.ts +9 -0
- package/dist/models/Priority.d.ts.map +1 -0
- package/dist/models/Priority.js +19 -0
- package/dist/models/Priority.js.map +1 -0
- package/dist/models/Task.d.ts +4 -0
- package/dist/models/Task.d.ts.map +1 -1
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +5 -0
- package/dist/models/index.js.map +1 -1
- package/dist/services/TaskService.d.ts +9 -0
- package/dist/services/TaskService.d.ts.map +1 -1
- package/dist/services/TaskService.js +35 -3
- package/dist/services/TaskService.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createBoardApp = createBoardApp;
|
|
4
|
+
exports.startBoardServer = startBoardServer;
|
|
5
|
+
const hono_1 = require("hono");
|
|
6
|
+
const node_server_1 = require("@hono/node-server");
|
|
7
|
+
const TaskService_1 = require("../services/TaskService");
|
|
8
|
+
const TaskTagService_1 = require("../services/TaskTagService");
|
|
9
|
+
const MetadataService_1 = require("../services/MetadataService");
|
|
10
|
+
const models_1 = require("../models");
|
|
11
|
+
const connection_1 = require("../db/connection");
|
|
12
|
+
const STATUSES = ['icebox', 'backlog', 'ready', 'in_progress', 'review', 'done', 'closed'];
|
|
13
|
+
const STATUS_LABELS = {
|
|
14
|
+
icebox: 'Icebox',
|
|
15
|
+
backlog: 'Backlog',
|
|
16
|
+
ready: 'Ready',
|
|
17
|
+
in_progress: 'In Progress',
|
|
18
|
+
review: 'Review',
|
|
19
|
+
done: 'Done',
|
|
20
|
+
closed: 'Closed',
|
|
21
|
+
};
|
|
22
|
+
const STATUS_COLORS = {
|
|
23
|
+
icebox: '#6b7280',
|
|
24
|
+
backlog: '#3b82f6',
|
|
25
|
+
ready: '#8b5cf6',
|
|
26
|
+
in_progress: '#f97316',
|
|
27
|
+
review: '#eab308',
|
|
28
|
+
done: '#22c55e',
|
|
29
|
+
closed: '#374151',
|
|
30
|
+
};
|
|
31
|
+
function escapeHtml(text) {
|
|
32
|
+
return text
|
|
33
|
+
.replace(/&/g, '&')
|
|
34
|
+
.replace(/</g, '<')
|
|
35
|
+
.replace(/>/g, '>')
|
|
36
|
+
.replace(/"/g, '"')
|
|
37
|
+
.replace(/'/g, ''');
|
|
38
|
+
}
|
|
39
|
+
function renderCard(task, tags, metadata) {
|
|
40
|
+
const priority = metadata.find((m) => m.key === 'priority')?.value;
|
|
41
|
+
const priorityBadge = priority
|
|
42
|
+
? `<span class="priority priority-${escapeHtml(priority)}">${escapeHtml(priority)}</span>`
|
|
43
|
+
: '';
|
|
44
|
+
const tagBadges = tags.map((t) => `<span class="tag">${escapeHtml(t.name)}</span>`).join('');
|
|
45
|
+
return `
|
|
46
|
+
<div class="card" draggable="true" data-id="${task.id}" data-status="${task.status}">
|
|
47
|
+
<div class="card-header">
|
|
48
|
+
<span class="card-id">#${task.id}</span>
|
|
49
|
+
${priorityBadge}
|
|
50
|
+
</div>
|
|
51
|
+
<div class="card-title">${escapeHtml(task.title)}</div>
|
|
52
|
+
${tagBadges ? `<div class="card-tags">${tagBadges}</div>` : ''}
|
|
53
|
+
</div>`;
|
|
54
|
+
}
|
|
55
|
+
function renderBoard(tasksByStatus, tagMap, metaMap) {
|
|
56
|
+
const columns = STATUSES.map((status) => {
|
|
57
|
+
const tasks = tasksByStatus.get(status) || [];
|
|
58
|
+
const color = STATUS_COLORS[status];
|
|
59
|
+
const label = STATUS_LABELS[status];
|
|
60
|
+
const cards = tasks.map((t) => renderCard(t, tagMap.get(t.id) || [], metaMap.get(t.id) || [])).join('');
|
|
61
|
+
return `
|
|
62
|
+
<div class="column" data-status="${status}">
|
|
63
|
+
<div class="column-header" style="border-top-color:${color}">
|
|
64
|
+
<span class="column-title" style="color:${color}">${label}</span>
|
|
65
|
+
<span class="column-header-right">
|
|
66
|
+
<span class="column-count">${tasks.length}</span>
|
|
67
|
+
<button class="add-btn" data-status="${status}" title="Add task">+</button>
|
|
68
|
+
</span>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="column-body" id="col-${status}">
|
|
71
|
+
${cards}
|
|
72
|
+
</div>
|
|
73
|
+
</div>`;
|
|
74
|
+
}).join('');
|
|
75
|
+
return `<!DOCTYPE html>
|
|
76
|
+
<html lang="en">
|
|
77
|
+
<head>
|
|
78
|
+
<meta charset="UTF-8">
|
|
79
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
80
|
+
<title>agkan board</title>
|
|
81
|
+
<style>
|
|
82
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
83
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f1f5f9; color: #1e293b; }
|
|
84
|
+
header { background: #1e293b; color: white; padding: 12px 20px; }
|
|
85
|
+
header h1 { font-size: 18px; font-weight: 700; }
|
|
86
|
+
.board { display: flex; gap: 12px; padding: 16px; overflow-x: auto; min-height: calc(100vh - 48px); align-items: flex-start; }
|
|
87
|
+
.column { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; width: 240px; flex-shrink: 0; display: flex; flex-direction: column; border-top: 3px solid transparent; }
|
|
88
|
+
.column-header { padding: 10px 12px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e2e8f0; }
|
|
89
|
+
.column-title { font-size: 13px; font-weight: 700; }
|
|
90
|
+
.column-header-right { display: flex; align-items: center; gap: 6px; }
|
|
91
|
+
.column-count { background: #e2e8f0; color: #64748b; border-radius: 10px; font-size: 11px; font-weight: 600; padding: 2px 7px; }
|
|
92
|
+
.add-btn { background: none; border: 1px solid #cbd5e1; color: #64748b; border-radius: 4px; width: 22px; height: 22px; font-size: 14px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; }
|
|
93
|
+
.add-btn:hover { background: #e2e8f0; color: #1e293b; }
|
|
94
|
+
.column-body { padding: 8px; min-height: 60px; }
|
|
95
|
+
.column.drag-over .column-body { background: #eff6ff; border-radius: 6px; }
|
|
96
|
+
.card { background: white; border: 1px solid #e2e8f0; border-radius: 6px; padding: 10px; margin-bottom: 6px; cursor: grab; transition: box-shadow 0.15s; }
|
|
97
|
+
.card:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
|
98
|
+
.card.dragging { opacity: 0.5; }
|
|
99
|
+
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 5px; }
|
|
100
|
+
.card-id { font-size: 11px; color: #94a3b8; font-weight: 600; }
|
|
101
|
+
.card-title { font-size: 13px; font-weight: 500; line-height: 1.4; }
|
|
102
|
+
.card-tags { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px; }
|
|
103
|
+
.tag { background: #e0f2fe; color: #0369a1; font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 10px; }
|
|
104
|
+
.priority { font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 10px; text-transform: uppercase; }
|
|
105
|
+
.priority-critical { background: #fee2e2; color: #dc2626; }
|
|
106
|
+
.priority-high { background: #fee2e2; color: #dc2626; }
|
|
107
|
+
.priority-medium { background: #fef9c3; color: #ca8a04; }
|
|
108
|
+
.priority-low { background: #dcfce7; color: #16a34a; }
|
|
109
|
+
.context-menu { position: fixed; background: white; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 4px 0; z-index: 1000; display: none; min-width: 140px; }
|
|
110
|
+
.context-menu-item { padding: 8px 14px; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 8px; }
|
|
111
|
+
.context-menu-item:hover { background: #f1f5f9; }
|
|
112
|
+
.context-menu-item.danger { color: #dc2626; }
|
|
113
|
+
.context-menu-item.danger:hover { background: #fef2f2; }
|
|
114
|
+
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 2000; display: none; align-items: center; justify-content: center; }
|
|
115
|
+
.modal-overlay.show { display: flex; }
|
|
116
|
+
.modal { background: white; border-radius: 8px; padding: 20px; width: 360px; box-shadow: 0 8px 24px rgba(0,0,0,0.2); }
|
|
117
|
+
.modal h2 { font-size: 16px; margin-bottom: 14px; }
|
|
118
|
+
.modal label { display: block; font-size: 12px; font-weight: 600; color: #64748b; margin-bottom: 4px; }
|
|
119
|
+
.modal input, .modal textarea, .modal select { width: 100%; border: 1px solid #e2e8f0; border-radius: 6px; padding: 8px 10px; font-size: 13px; font-family: inherit; margin-bottom: 12px; background: white; }
|
|
120
|
+
.modal textarea { resize: vertical; min-height: 60px; }
|
|
121
|
+
.modal input:focus, .modal textarea:focus, .modal select:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59,130,246,0.2); }
|
|
122
|
+
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
|
123
|
+
.modal-actions button { padding: 6px 14px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid #e2e8f0; background: white; color: #64748b; }
|
|
124
|
+
.modal-actions button:hover { background: #f1f5f9; }
|
|
125
|
+
.modal-actions button.primary { background: #3b82f6; color: white; border-color: #3b82f6; }
|
|
126
|
+
.modal-actions button.primary:hover { background: #2563eb; }
|
|
127
|
+
.toast { position: fixed; bottom: 20px; right: 20px; background: #ef4444; color: white; padding: 10px 16px; border-radius: 6px; font-size: 13px; opacity: 0; transition: opacity 0.3s; pointer-events: none; }
|
|
128
|
+
.toast.show { opacity: 1; }
|
|
129
|
+
.detail-panel { position: fixed; top: 0; right: 0; width: 400px; height: 100vh; background: white; box-shadow: -4px 0 16px rgba(0,0,0,0.1); z-index: 1500; transform: translateX(100%); transition: transform 0.25s ease; display: flex; flex-direction: column; }
|
|
130
|
+
.detail-panel.open { transform: translateX(0); }
|
|
131
|
+
.detail-panel-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid #e2e8f0; }
|
|
132
|
+
.detail-panel-header h2 { font-size: 16px; font-weight: 700; color: #1e293b; margin: 0; }
|
|
133
|
+
.detail-panel-close { background: none; border: none; font-size: 20px; color: #64748b; cursor: pointer; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 4px; }
|
|
134
|
+
.detail-panel-close:hover { background: #f1f5f9; color: #1e293b; }
|
|
135
|
+
.detail-panel-body { flex: 1; overflow-y: auto; padding: 20px; }
|
|
136
|
+
.detail-field { margin-bottom: 16px; }
|
|
137
|
+
.detail-field-label { font-size: 11px; font-weight: 700; text-transform: uppercase; color: #94a3b8; margin-bottom: 4px; letter-spacing: 0.05em; }
|
|
138
|
+
.detail-field-value { font-size: 13px; color: #1e293b; line-height: 1.5; }
|
|
139
|
+
.detail-field-value.empty { color: #94a3b8; font-style: italic; }
|
|
140
|
+
.detail-status-badge { display: inline-block; padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 600; color: white; }
|
|
141
|
+
.detail-tags { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
142
|
+
.detail-meta-table { width: 100%; border-collapse: collapse; }
|
|
143
|
+
.detail-meta-table td { padding: 4px 0; font-size: 12px; }
|
|
144
|
+
.detail-meta-table td:first-child { color: #64748b; width: 100px; }
|
|
145
|
+
.detail-meta-table td:last-child { color: #1e293b; }
|
|
146
|
+
.detail-panel-footer { padding: 12px 20px; border-top: 1px solid #e2e8f0; display: flex; justify-content: flex-end; }
|
|
147
|
+
.detail-panel-footer button { padding: 7px 18px; border-radius: 6px; font-size: 13px; font-weight: 600; cursor: pointer; border: 1px solid #3b82f6; background: #3b82f6; color: white; }
|
|
148
|
+
.detail-panel-footer button:hover { background: #2563eb; border-color: #2563eb; }
|
|
149
|
+
.detail-edit-input { width: 100%; border: 1px solid #e2e8f0; border-radius: 6px; padding: 7px 10px; font-size: 13px; font-family: inherit; background: white; color: #1e293b; }
|
|
150
|
+
.detail-edit-input:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59,130,246,0.2); }
|
|
151
|
+
.detail-edit-textarea { width: 100%; border: 1px solid #e2e8f0; border-radius: 6px; padding: 7px 10px; font-size: 13px; font-family: inherit; resize: vertical; min-height: 80px; background: white; color: #1e293b; }
|
|
152
|
+
.detail-edit-textarea:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59,130,246,0.2); }
|
|
153
|
+
.detail-edit-select { width: 100%; border: 1px solid #e2e8f0; border-radius: 6px; padding: 7px 10px; font-size: 13px; font-family: inherit; background: white; color: #1e293b; }
|
|
154
|
+
.detail-edit-select:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 2px rgba(59,130,246,0.2); }
|
|
155
|
+
</style>
|
|
156
|
+
</head>
|
|
157
|
+
<body>
|
|
158
|
+
<header><h1>agkan board</h1></header>
|
|
159
|
+
<div class="board">${columns}</div>
|
|
160
|
+
<div class="modal-overlay" id="add-modal">
|
|
161
|
+
<div class="modal">
|
|
162
|
+
<h2>Add Task</h2>
|
|
163
|
+
<label for="add-title">Title</label>
|
|
164
|
+
<input type="text" id="add-title" placeholder="Task title">
|
|
165
|
+
<label for="add-body">Description</label>
|
|
166
|
+
<textarea id="add-body" placeholder="Optional"></textarea>
|
|
167
|
+
<label for="add-priority">Priority</label>
|
|
168
|
+
<select id="add-priority">
|
|
169
|
+
<option value="">None</option>
|
|
170
|
+
${models_1.PRIORITIES.map((p) => `<option value="${p}">${p.charAt(0).toUpperCase() + p.slice(1)}</option>`).join('\n ')}
|
|
171
|
+
</select>
|
|
172
|
+
<input type="hidden" id="add-status">
|
|
173
|
+
<div class="modal-actions">
|
|
174
|
+
<button id="add-cancel">Cancel</button>
|
|
175
|
+
<button id="add-submit" class="primary">Add</button>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="context-menu" id="context-menu">
|
|
180
|
+
<div class="context-menu-item danger" id="ctx-delete">Delete task</div>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="detail-panel" id="detail-panel">
|
|
183
|
+
<div class="detail-panel-header">
|
|
184
|
+
<h2 id="detail-panel-title">Task Detail</h2>
|
|
185
|
+
<button class="detail-panel-close" id="detail-panel-close" title="Close">×</button>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="detail-panel-body" id="detail-panel-body"></div>
|
|
188
|
+
<div class="detail-panel-footer">
|
|
189
|
+
<button id="detail-save-btn">Save</button>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
<div class="toast" id="toast">Failed to update task</div>
|
|
193
|
+
<script>
|
|
194
|
+
let draggedCard = null;
|
|
195
|
+
let sourceBody = null;
|
|
196
|
+
|
|
197
|
+
document.querySelectorAll('.card').forEach(card => {
|
|
198
|
+
card.addEventListener('dragstart', e => {
|
|
199
|
+
draggedCard = card;
|
|
200
|
+
sourceBody = card.parentElement;
|
|
201
|
+
card.classList.add('dragging');
|
|
202
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
203
|
+
});
|
|
204
|
+
card.addEventListener('dragend', () => {
|
|
205
|
+
card.classList.remove('dragging');
|
|
206
|
+
draggedCard = null;
|
|
207
|
+
sourceBody = null;
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
document.querySelectorAll('.column').forEach(col => {
|
|
212
|
+
col.addEventListener('dragover', e => {
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
col.classList.add('drag-over');
|
|
215
|
+
});
|
|
216
|
+
col.addEventListener('dragleave', () => col.classList.remove('drag-over'));
|
|
217
|
+
col.addEventListener('drop', e => handleDrop(e, col.dataset.status, col));
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
async function handleDrop(e, newStatus, colEl) {
|
|
221
|
+
e.preventDefault();
|
|
222
|
+
colEl.classList.remove('drag-over');
|
|
223
|
+
if (!draggedCard) return;
|
|
224
|
+
const taskId = draggedCard.dataset.id;
|
|
225
|
+
const oldStatus = draggedCard.dataset.status;
|
|
226
|
+
if (oldStatus === newStatus) return;
|
|
227
|
+
|
|
228
|
+
const targetBody = document.getElementById('col-' + newStatus);
|
|
229
|
+
const prevBody = sourceBody;
|
|
230
|
+
targetBody.appendChild(draggedCard);
|
|
231
|
+
draggedCard.dataset.status = newStatus;
|
|
232
|
+
updateCount(oldStatus);
|
|
233
|
+
updateCount(newStatus);
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const res = await fetch('/api/tasks/' + taskId, {
|
|
237
|
+
method: 'PATCH',
|
|
238
|
+
headers: { 'Content-Type': 'application/json' },
|
|
239
|
+
body: JSON.stringify({ status: newStatus })
|
|
240
|
+
});
|
|
241
|
+
if (!res.ok) throw new Error('Server error');
|
|
242
|
+
} catch {
|
|
243
|
+
prevBody.appendChild(draggedCard);
|
|
244
|
+
draggedCard.dataset.status = oldStatus;
|
|
245
|
+
updateCount(oldStatus);
|
|
246
|
+
updateCount(newStatus);
|
|
247
|
+
showToast();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function updateCount(status) {
|
|
252
|
+
const col = document.querySelector('.column[data-status="' + status + '"]');
|
|
253
|
+
if (!col) return;
|
|
254
|
+
col.querySelector('.column-count').textContent = col.querySelector('.column-body').children.length;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function showToast(msg) {
|
|
258
|
+
const toast = document.getElementById('toast');
|
|
259
|
+
if (msg) toast.textContent = msg;
|
|
260
|
+
toast.classList.add('show');
|
|
261
|
+
setTimeout(() => toast.classList.remove('show'), 3000);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Add task modal
|
|
265
|
+
const addModal = document.getElementById('add-modal');
|
|
266
|
+
const addTitle = document.getElementById('add-title');
|
|
267
|
+
const addBody = document.getElementById('add-body');
|
|
268
|
+
const addPriority = document.getElementById('add-priority');
|
|
269
|
+
const addStatus = document.getElementById('add-status');
|
|
270
|
+
|
|
271
|
+
document.querySelectorAll('.add-btn').forEach(btn => {
|
|
272
|
+
btn.addEventListener('click', e => {
|
|
273
|
+
e.stopPropagation();
|
|
274
|
+
addStatus.value = btn.dataset.status;
|
|
275
|
+
addTitle.value = '';
|
|
276
|
+
addBody.value = '';
|
|
277
|
+
addPriority.value = '';
|
|
278
|
+
addModal.classList.add('show');
|
|
279
|
+
addTitle.focus();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
document.getElementById('add-cancel').addEventListener('click', () => {
|
|
284
|
+
addModal.classList.remove('show');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
addModal.addEventListener('click', e => {
|
|
288
|
+
if (e.target === addModal) addModal.classList.remove('show');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
addTitle.addEventListener('keydown', e => {
|
|
292
|
+
if (e.key === 'Enter' && !e.isComposing) { e.preventDefault(); document.getElementById('add-submit').click(); }
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
document.getElementById('add-submit').addEventListener('click', async () => {
|
|
296
|
+
const title = addTitle.value.trim();
|
|
297
|
+
if (!title) { addTitle.focus(); return; }
|
|
298
|
+
const status = addStatus.value;
|
|
299
|
+
addModal.classList.remove('show');
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const res = await fetch('/api/tasks', {
|
|
303
|
+
method: 'POST',
|
|
304
|
+
headers: { 'Content-Type': 'application/json' },
|
|
305
|
+
body: JSON.stringify({ title, body: addBody.value.trim() || null, status, priority: addPriority.value || null })
|
|
306
|
+
});
|
|
307
|
+
if (!res.ok) throw new Error('Server error');
|
|
308
|
+
location.reload();
|
|
309
|
+
} catch {
|
|
310
|
+
showToast('Failed to add task');
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Context menu
|
|
315
|
+
const ctxMenu = document.getElementById('context-menu');
|
|
316
|
+
let ctxTargetCard = null;
|
|
317
|
+
|
|
318
|
+
document.addEventListener('contextmenu', e => {
|
|
319
|
+
const card = e.target.closest('.card');
|
|
320
|
+
if (!card) { ctxMenu.style.display = 'none'; return; }
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
ctxTargetCard = card;
|
|
323
|
+
ctxMenu.style.left = e.clientX + 'px';
|
|
324
|
+
ctxMenu.style.top = e.clientY + 'px';
|
|
325
|
+
ctxMenu.style.display = 'block';
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
document.addEventListener('click', e => {
|
|
329
|
+
if (!e.target.closest('#context-menu')) {
|
|
330
|
+
ctxMenu.style.display = 'none';
|
|
331
|
+
ctxTargetCard = null;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
document.getElementById('ctx-delete').addEventListener('click', async e => {
|
|
336
|
+
e.stopPropagation();
|
|
337
|
+
ctxMenu.style.display = 'none';
|
|
338
|
+
if (!ctxTargetCard) return;
|
|
339
|
+
const card = ctxTargetCard;
|
|
340
|
+
ctxTargetCard = null;
|
|
341
|
+
const taskId = card.dataset.id;
|
|
342
|
+
const status = card.dataset.status;
|
|
343
|
+
if (!confirm('Delete task #' + taskId + '?')) return;
|
|
344
|
+
|
|
345
|
+
card.remove();
|
|
346
|
+
updateCount(status);
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const res = await fetch('/api/tasks/' + taskId, { method: 'DELETE' });
|
|
350
|
+
if (!res.ok) throw new Error('Server error');
|
|
351
|
+
} catch {
|
|
352
|
+
location.reload();
|
|
353
|
+
showToast('Failed to delete task');
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Detail panel
|
|
358
|
+
const detailPanel = document.getElementById('detail-panel');
|
|
359
|
+
const detailPanelTitle = document.getElementById('detail-panel-title');
|
|
360
|
+
const detailPanelBody = document.getElementById('detail-panel-body');
|
|
361
|
+
let detailTaskId = null;
|
|
362
|
+
|
|
363
|
+
function closeDetailPanel() {
|
|
364
|
+
detailPanel.classList.remove('open');
|
|
365
|
+
detailTaskId = null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
document.getElementById('detail-panel-close').addEventListener('click', closeDetailPanel);
|
|
369
|
+
|
|
370
|
+
const statusColors = ${JSON.stringify(STATUS_COLORS)};
|
|
371
|
+
const allStatuses = ${JSON.stringify(STATUSES)};
|
|
372
|
+
const statusLabels = ${JSON.stringify(STATUS_LABELS)};
|
|
373
|
+
const allPriorities = ${JSON.stringify(models_1.PRIORITIES)};
|
|
374
|
+
|
|
375
|
+
function renderDetailPanel(data) {
|
|
376
|
+
const task = data.task;
|
|
377
|
+
const tags = data.tags || [];
|
|
378
|
+
const metadata = data.metadata || [];
|
|
379
|
+
const priority = metadata.find(m => m.key === 'priority');
|
|
380
|
+
|
|
381
|
+
detailTaskId = task.id;
|
|
382
|
+
detailPanelTitle.textContent = '#' + task.id + ' ' + task.title;
|
|
383
|
+
|
|
384
|
+
let html = '';
|
|
385
|
+
|
|
386
|
+
// Title (editable)
|
|
387
|
+
html += '<div class="detail-field">';
|
|
388
|
+
html += '<div class="detail-field-label">Title</div>';
|
|
389
|
+
html += '<input id="detail-edit-title" class="detail-edit-input" type="text" value="' + escapeHtmlClient(task.title) + '">';
|
|
390
|
+
html += '</div>';
|
|
391
|
+
|
|
392
|
+
// Status (editable)
|
|
393
|
+
html += '<div class="detail-field">';
|
|
394
|
+
html += '<div class="detail-field-label">Status</div>';
|
|
395
|
+
html += '<select id="detail-edit-status" class="detail-edit-select">';
|
|
396
|
+
allStatuses.forEach(s => {
|
|
397
|
+
const selected = s === task.status ? ' selected' : '';
|
|
398
|
+
html += '<option value="' + s + '"' + selected + '>' + statusLabels[s] + '</option>';
|
|
399
|
+
});
|
|
400
|
+
html += '</select>';
|
|
401
|
+
html += '</div>';
|
|
402
|
+
|
|
403
|
+
// Priority (editable)
|
|
404
|
+
html += '<div class="detail-field">';
|
|
405
|
+
html += '<div class="detail-field-label">Priority</div>';
|
|
406
|
+
html += '<select id="detail-edit-priority" class="detail-edit-select">';
|
|
407
|
+
html += '<option value="">None</option>';
|
|
408
|
+
allPriorities.forEach(p => {
|
|
409
|
+
const selected = priority && priority.value === p ? ' selected' : '';
|
|
410
|
+
html += '<option value="' + p + '"' + selected + '>' + p.charAt(0).toUpperCase() + p.slice(1) + '</option>';
|
|
411
|
+
});
|
|
412
|
+
html += '</select>';
|
|
413
|
+
html += '</div>';
|
|
414
|
+
|
|
415
|
+
// Body (editable)
|
|
416
|
+
html += '<div class="detail-field">';
|
|
417
|
+
html += '<div class="detail-field-label">Description</div>';
|
|
418
|
+
html += '<textarea id="detail-edit-body" class="detail-edit-textarea">' + escapeHtmlClient(task.body || '') + '</textarea>';
|
|
419
|
+
html += '</div>';
|
|
420
|
+
|
|
421
|
+
// Tags (read-only)
|
|
422
|
+
if (tags.length > 0) {
|
|
423
|
+
html += '<div class="detail-field">';
|
|
424
|
+
html += '<div class="detail-field-label">Tags</div>';
|
|
425
|
+
html += '<div class="detail-field-value detail-tags">';
|
|
426
|
+
tags.forEach(t => { html += '<span class="tag">' + escapeHtmlClient(t.name) + '</span>'; });
|
|
427
|
+
html += '</div></div>';
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Metadata table (read-only, non-priority)
|
|
431
|
+
const otherMeta = metadata.filter(m => m.key !== 'priority');
|
|
432
|
+
if (otherMeta.length > 0) {
|
|
433
|
+
html += '<div class="detail-field">';
|
|
434
|
+
html += '<div class="detail-field-label">Metadata</div>';
|
|
435
|
+
html += '<table class="detail-meta-table">';
|
|
436
|
+
otherMeta.forEach(m => {
|
|
437
|
+
html += '<tr><td>' + escapeHtmlClient(m.key) + '</td><td>' + escapeHtmlClient(m.value) + '</td></tr>';
|
|
438
|
+
});
|
|
439
|
+
html += '</table></div>';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Timestamps (read-only)
|
|
443
|
+
html += '<div class="detail-field">';
|
|
444
|
+
html += '<div class="detail-field-label">Created</div>';
|
|
445
|
+
html += '<div class="detail-field-value">' + escapeHtmlClient(task.created_at) + '</div>';
|
|
446
|
+
html += '</div>';
|
|
447
|
+
html += '<div class="detail-field">';
|
|
448
|
+
html += '<div class="detail-field-label">Updated</div>';
|
|
449
|
+
html += '<div class="detail-field-value">' + escapeHtmlClient(task.updated_at) + '</div>';
|
|
450
|
+
html += '</div>';
|
|
451
|
+
|
|
452
|
+
detailPanelBody.innerHTML = html;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function escapeHtmlClient(str) {
|
|
456
|
+
if (!str) return '';
|
|
457
|
+
const div = document.createElement('div');
|
|
458
|
+
div.textContent = String(str);
|
|
459
|
+
return div.innerHTML;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
document.getElementById('detail-save-btn').addEventListener('click', async () => {
|
|
463
|
+
if (detailTaskId === null) return;
|
|
464
|
+
const titleInput = document.getElementById('detail-edit-title');
|
|
465
|
+
const title = titleInput ? titleInput.value.trim() : '';
|
|
466
|
+
if (!title) { if (titleInput) titleInput.focus(); return; }
|
|
467
|
+
const bodyEl = document.getElementById('detail-edit-body');
|
|
468
|
+
const statusEl = document.getElementById('detail-edit-status');
|
|
469
|
+
const priorityEl = document.getElementById('detail-edit-priority');
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
const res = await fetch('/api/tasks/' + detailTaskId, {
|
|
473
|
+
method: 'PATCH',
|
|
474
|
+
headers: { 'Content-Type': 'application/json' },
|
|
475
|
+
body: JSON.stringify({
|
|
476
|
+
title,
|
|
477
|
+
body: bodyEl ? (bodyEl.value.trim() || null) : null,
|
|
478
|
+
status: statusEl ? statusEl.value : undefined,
|
|
479
|
+
priority: priorityEl ? (priorityEl.value || null) : null
|
|
480
|
+
})
|
|
481
|
+
});
|
|
482
|
+
if (!res.ok) throw new Error('Server error');
|
|
483
|
+
location.reload();
|
|
484
|
+
} catch {
|
|
485
|
+
showToast('Failed to update task');
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
document.querySelectorAll('.card').forEach(card => {
|
|
490
|
+
card.addEventListener('click', async e => {
|
|
491
|
+
if (e.defaultPrevented) return;
|
|
492
|
+
const taskId = card.dataset.id;
|
|
493
|
+
try {
|
|
494
|
+
const res = await fetch('/api/tasks/' + taskId);
|
|
495
|
+
if (!res.ok) throw new Error('Server error');
|
|
496
|
+
const data = await res.json();
|
|
497
|
+
renderDetailPanel(data);
|
|
498
|
+
detailPanel.classList.add('open');
|
|
499
|
+
} catch {
|
|
500
|
+
showToast('Failed to load task details');
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// Board polling: reload when updated_at changes (skip during drag or detail panel open)
|
|
506
|
+
let lastUpdatedAt = null;
|
|
507
|
+
async function pollBoardUpdates() {
|
|
508
|
+
if (draggedCard !== null) return;
|
|
509
|
+
if (detailPanel.classList.contains('open')) return;
|
|
510
|
+
try {
|
|
511
|
+
const res = await fetch('/api/board/updated-at');
|
|
512
|
+
if (!res.ok) return;
|
|
513
|
+
const data = await res.json();
|
|
514
|
+
const ts = data.updatedAt;
|
|
515
|
+
if (lastUpdatedAt === null) {
|
|
516
|
+
lastUpdatedAt = ts;
|
|
517
|
+
} else if (ts !== lastUpdatedAt) {
|
|
518
|
+
location.reload();
|
|
519
|
+
}
|
|
520
|
+
} catch {
|
|
521
|
+
// Ignore network errors during polling
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
setInterval(pollBoardUpdates, 10000);
|
|
525
|
+
pollBoardUpdates();
|
|
526
|
+
</script>
|
|
527
|
+
</body>
|
|
528
|
+
</html>`;
|
|
529
|
+
}
|
|
530
|
+
function sortByPriority(tasks, metaMap) {
|
|
531
|
+
return [...tasks].sort((a, b) => {
|
|
532
|
+
const pa = metaMap.get(a.id)?.find((m) => m.key === 'priority')?.value;
|
|
533
|
+
const pb = metaMap.get(b.id)?.find((m) => m.key === 'priority')?.value;
|
|
534
|
+
const oa = pa && (0, models_1.isPriority)(pa) ? models_1.PRIORITY_ORDER[pa] : 4;
|
|
535
|
+
const ob = pb && (0, models_1.isPriority)(pb) ? models_1.PRIORITY_ORDER[pb] : 4;
|
|
536
|
+
return oa - ob;
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
function createBoardApp(taskService, taskTagService, metadataService, db) {
|
|
540
|
+
const app = new hono_1.Hono();
|
|
541
|
+
const ts = taskService ?? new TaskService_1.TaskService();
|
|
542
|
+
const tts = taskTagService ?? new TaskTagService_1.TaskTagService();
|
|
543
|
+
const ms = metadataService ?? new MetadataService_1.MetadataService();
|
|
544
|
+
const database = db ?? (0, connection_1.getDatabase)();
|
|
545
|
+
app.get('/', (c) => {
|
|
546
|
+
const tasks = ts.listTasks({}, 'id', 'asc');
|
|
547
|
+
const tagMap = tts.getAllTaskTags();
|
|
548
|
+
const metaMap = ms.getAllTasksMetadata();
|
|
549
|
+
const tasksByStatus = new Map();
|
|
550
|
+
for (const status of STATUSES) {
|
|
551
|
+
tasksByStatus.set(status, []);
|
|
552
|
+
}
|
|
553
|
+
for (const task of tasks) {
|
|
554
|
+
tasksByStatus.get(task.status)?.push(task);
|
|
555
|
+
}
|
|
556
|
+
for (const [status, statusTasks] of tasksByStatus) {
|
|
557
|
+
tasksByStatus.set(status, sortByPriority(statusTasks, metaMap));
|
|
558
|
+
}
|
|
559
|
+
return c.html(renderBoard(tasksByStatus, tagMap, metaMap));
|
|
560
|
+
});
|
|
561
|
+
app.get('/api/board/updated-at', (c) => {
|
|
562
|
+
const stmt = database.prepare(`
|
|
563
|
+
SELECT MAX(updated_at) as max_updated_at FROM (
|
|
564
|
+
SELECT updated_at FROM tasks
|
|
565
|
+
UNION ALL
|
|
566
|
+
SELECT updated_at FROM task_metadata
|
|
567
|
+
)
|
|
568
|
+
`);
|
|
569
|
+
const row = stmt.get();
|
|
570
|
+
return c.json({ updatedAt: row.max_updated_at });
|
|
571
|
+
});
|
|
572
|
+
app.get('/api/tasks', (c) => {
|
|
573
|
+
const tasks = ts.listTasks({}, 'id', 'asc');
|
|
574
|
+
return c.json({ tasks });
|
|
575
|
+
});
|
|
576
|
+
app.post('/api/tasks', async (c) => {
|
|
577
|
+
const body = await c.req.json();
|
|
578
|
+
if (!body.title || typeof body.title !== 'string' || !body.title.trim()) {
|
|
579
|
+
return c.json({ error: 'Title is required' }, 400);
|
|
580
|
+
}
|
|
581
|
+
const status = body.status && STATUSES.includes(body.status) ? body.status : 'backlog';
|
|
582
|
+
const task = ts.createTask({ title: body.title.trim(), body: body.body || undefined, status });
|
|
583
|
+
if (body.priority && (0, models_1.isPriority)(body.priority)) {
|
|
584
|
+
ms.setMetadata({ task_id: task.id, key: 'priority', value: body.priority });
|
|
585
|
+
}
|
|
586
|
+
return c.json(task, 201);
|
|
587
|
+
});
|
|
588
|
+
app.get('/api/tasks/:id', (c) => {
|
|
589
|
+
const id = Number(c.req.param('id'));
|
|
590
|
+
if (isNaN(id)) {
|
|
591
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
592
|
+
}
|
|
593
|
+
const task = ts.getTask(id);
|
|
594
|
+
if (!task) {
|
|
595
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
596
|
+
}
|
|
597
|
+
const tags = tts.getTagsForTask(id);
|
|
598
|
+
const metadata = ms.listMetadata(id);
|
|
599
|
+
return c.json({ task, tags, metadata });
|
|
600
|
+
});
|
|
601
|
+
app.patch('/api/tasks/:id', async (c) => {
|
|
602
|
+
const id = Number(c.req.param('id'));
|
|
603
|
+
if (isNaN(id)) {
|
|
604
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
605
|
+
}
|
|
606
|
+
const body = await c.req.json();
|
|
607
|
+
const updateInput = {};
|
|
608
|
+
if (body.status !== undefined) {
|
|
609
|
+
if (!STATUSES.includes(body.status)) {
|
|
610
|
+
return c.json({ error: 'Invalid status' }, 400);
|
|
611
|
+
}
|
|
612
|
+
updateInput.status = body.status;
|
|
613
|
+
}
|
|
614
|
+
if (body.title !== undefined) {
|
|
615
|
+
if (typeof body.title !== 'string' || !body.title.trim()) {
|
|
616
|
+
return c.json({ error: 'Title cannot be empty' }, 400);
|
|
617
|
+
}
|
|
618
|
+
updateInput.title = body.title.trim();
|
|
619
|
+
}
|
|
620
|
+
if (body.body !== undefined) {
|
|
621
|
+
updateInput.body = body.body ?? '';
|
|
622
|
+
}
|
|
623
|
+
const task = ts.updateTask(id, updateInput);
|
|
624
|
+
if (!task) {
|
|
625
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
626
|
+
}
|
|
627
|
+
if (body.priority !== undefined) {
|
|
628
|
+
if (body.priority && (0, models_1.isPriority)(body.priority)) {
|
|
629
|
+
ms.setMetadata({ task_id: id, key: 'priority', value: body.priority });
|
|
630
|
+
}
|
|
631
|
+
else {
|
|
632
|
+
ms.deleteMetadata(id, 'priority');
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return c.json(task);
|
|
636
|
+
});
|
|
637
|
+
app.delete('/api/tasks/:id', (c) => {
|
|
638
|
+
const id = Number(c.req.param('id'));
|
|
639
|
+
if (isNaN(id)) {
|
|
640
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
641
|
+
}
|
|
642
|
+
const task = ts.getTask(id);
|
|
643
|
+
if (!task) {
|
|
644
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
645
|
+
}
|
|
646
|
+
ts.deleteTask(id);
|
|
647
|
+
return c.json({ success: true });
|
|
648
|
+
});
|
|
649
|
+
return app;
|
|
650
|
+
}
|
|
651
|
+
function startBoardServer(port) {
|
|
652
|
+
const app = createBoardApp();
|
|
653
|
+
(0, node_server_1.serve)({ fetch: app.fetch, port }, () => {
|
|
654
|
+
console.log(`Board running at http://localhost:${port}`);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/board/server.ts"],"names":[],"mappings":"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"}
|