agkan 2.13.0 → 2.14.1
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/dist/board/boardRenderer.d.ts.map +1 -1
- package/dist/board/boardRenderer.js +26 -2
- package/dist/board/boardRenderer.js.map +1 -1
- package/dist/board/boardRoutes.d.ts +1 -1
- package/dist/board/boardRoutes.d.ts.map +1 -1
- package/dist/board/boardRoutes.js +63 -4
- package/dist/board/boardRoutes.js.map +1 -1
- package/dist/board/boardStyles.d.ts +1 -1
- package/dist/board/boardStyles.d.ts.map +1 -1
- package/dist/board/boardStyles.js +19 -8
- package/dist/board/boardStyles.js.map +1 -1
- package/dist/board/client/board.js +474 -136
- package/dist/cli/commands/export.d.ts +7 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +30 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/import.d.ts +7 -0
- package/dist/cli/commands/import.d.ts.map +1 -0
- package/dist/cli/commands/import.js +44 -0
- package/dist/cli/commands/import.js.map +1 -0
- package/dist/cli/index.js +6 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/services/ExportImportService.d.ts +84 -0
- package/dist/services/ExportImportService.d.ts.map +1 -0
- package/dist/services/ExportImportService.js +222 -0
- package/dist/services/ExportImportService.js.map +1 -0
- package/dist/services/ProcessService.d.ts +54 -0
- package/dist/services/ProcessService.d.ts.map +1 -0
- package/dist/services/ProcessService.js +147 -0
- package/dist/services/ProcessService.js.map +1 -0
- package/dist/services/TmuxService.d.ts +2 -0
- package/dist/services/TmuxService.d.ts.map +1 -0
- package/dist/services/TmuxService.js +7 -0
- package/dist/services/TmuxService.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +3 -1
- package/dist/services/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -149,115 +149,6 @@
|
|
|
149
149
|
document.addEventListener("dragend", stopAutoScroll);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
// src/board/client/addTaskModal.ts
|
|
153
|
-
function openAddModal(elements, status) {
|
|
154
|
-
elements.addStatus.value = status;
|
|
155
|
-
elements.addTitle.value = "";
|
|
156
|
-
elements.addBody.value = "";
|
|
157
|
-
elements.addPriority.value = "";
|
|
158
|
-
elements.addModal.classList.add("show");
|
|
159
|
-
elements.addTitle.focus();
|
|
160
|
-
}
|
|
161
|
-
async function submitAddTask(elements) {
|
|
162
|
-
const title = elements.addTitle.value.trim();
|
|
163
|
-
if (!title) {
|
|
164
|
-
elements.addTitle.focus();
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const status = elements.addStatus.value;
|
|
168
|
-
elements.addModal.classList.remove("show");
|
|
169
|
-
try {
|
|
170
|
-
const res = await fetch("/api/tasks", {
|
|
171
|
-
method: "POST",
|
|
172
|
-
headers: { "Content-Type": "application/json" },
|
|
173
|
-
body: JSON.stringify({
|
|
174
|
-
title,
|
|
175
|
-
body: elements.addBody.value.trim() || null,
|
|
176
|
-
status,
|
|
177
|
-
priority: elements.addPriority.value || null
|
|
178
|
-
})
|
|
179
|
-
});
|
|
180
|
-
if (!res.ok) throw new Error("Server error");
|
|
181
|
-
location.reload();
|
|
182
|
-
} catch {
|
|
183
|
-
showToast("Failed to add task");
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function initAddTaskModal() {
|
|
187
|
-
const elements = {
|
|
188
|
-
addModal: document.getElementById("add-modal"),
|
|
189
|
-
addTitle: document.getElementById("add-title"),
|
|
190
|
-
addBody: document.getElementById("add-body"),
|
|
191
|
-
addPriority: document.getElementById("add-priority"),
|
|
192
|
-
addStatus: document.getElementById("add-status")
|
|
193
|
-
};
|
|
194
|
-
document.querySelectorAll(".add-btn").forEach((btn) => {
|
|
195
|
-
btn.addEventListener("click", (e) => {
|
|
196
|
-
e.stopPropagation();
|
|
197
|
-
openAddModal(elements, btn.dataset.status);
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
document.getElementById("add-cancel")?.addEventListener("click", () => {
|
|
201
|
-
elements.addModal.classList.remove("show");
|
|
202
|
-
});
|
|
203
|
-
elements.addModal.addEventListener("click", (e) => {
|
|
204
|
-
if (e.target === elements.addModal) elements.addModal.classList.remove("show");
|
|
205
|
-
});
|
|
206
|
-
elements.addTitle.addEventListener("keydown", (e) => {
|
|
207
|
-
if (e.key === "Enter" && !e.isComposing) {
|
|
208
|
-
e.preventDefault();
|
|
209
|
-
document.getElementById("add-submit").click();
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
document.getElementById("add-submit")?.addEventListener("click", () => submitAddTask(elements));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// src/board/client/contextMenu.ts
|
|
216
|
-
async function deleteCard(card) {
|
|
217
|
-
const taskId = card.dataset.id;
|
|
218
|
-
const status = card.dataset.status;
|
|
219
|
-
if (!confirm("Delete task #" + taskId + "?")) return;
|
|
220
|
-
card.remove();
|
|
221
|
-
updateCount(status);
|
|
222
|
-
try {
|
|
223
|
-
const res = await fetch("/api/tasks/" + taskId, { method: "DELETE" });
|
|
224
|
-
if (!res.ok) throw new Error("Server error");
|
|
225
|
-
} catch {
|
|
226
|
-
location.reload();
|
|
227
|
-
showToast("Failed to delete task");
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function initContextMenu() {
|
|
231
|
-
const ctxMenu = document.getElementById("context-menu");
|
|
232
|
-
let ctxTargetCard = null;
|
|
233
|
-
document.addEventListener("contextmenu", (e) => {
|
|
234
|
-
const card = e.target.closest(".card");
|
|
235
|
-
if (!card) {
|
|
236
|
-
ctxMenu.style.display = "none";
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
e.preventDefault();
|
|
240
|
-
ctxTargetCard = card;
|
|
241
|
-
ctxMenu.style.left = e.clientX + "px";
|
|
242
|
-
ctxMenu.style.top = e.clientY + "px";
|
|
243
|
-
ctxMenu.style.display = "block";
|
|
244
|
-
});
|
|
245
|
-
document.addEventListener("click", (e) => {
|
|
246
|
-
if (!e.target.closest("#context-menu")) {
|
|
247
|
-
ctxMenu.style.display = "none";
|
|
248
|
-
ctxTargetCard = null;
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
document.getElementById("ctx-delete")?.addEventListener("click", async (e) => {
|
|
252
|
-
e.stopPropagation();
|
|
253
|
-
ctxMenu.style.display = "none";
|
|
254
|
-
if (!ctxTargetCard) return;
|
|
255
|
-
const card = ctxTargetCard;
|
|
256
|
-
ctxTargetCard = null;
|
|
257
|
-
await deleteCard(card);
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
152
|
// src/board/client/tags.ts
|
|
262
153
|
var allAvailableTags = [];
|
|
263
154
|
var _getDetailTaskId = null;
|
|
@@ -455,11 +346,13 @@
|
|
|
455
346
|
var _renderDetailPanel = null;
|
|
456
347
|
var _showUpdateWarning = null;
|
|
457
348
|
var _getDetailTaskId2 = null;
|
|
349
|
+
var _setActiveCard = null;
|
|
458
350
|
function registerDetailPanelCallbacks(callbacks) {
|
|
459
351
|
_openTaskDetail = callbacks.openTaskDetail;
|
|
460
352
|
_renderDetailPanel = callbacks.renderDetailPanel;
|
|
461
353
|
_showUpdateWarning = callbacks.showUpdateWarning;
|
|
462
354
|
_getDetailTaskId2 = callbacks.getDetailTaskId;
|
|
355
|
+
_setActiveCard = callbacks.setActiveCard;
|
|
463
356
|
}
|
|
464
357
|
function attachCardListeners(body) {
|
|
465
358
|
body.querySelectorAll(".card").forEach((card) => {
|
|
@@ -470,10 +363,52 @@
|
|
|
470
363
|
});
|
|
471
364
|
});
|
|
472
365
|
}
|
|
366
|
+
function applyIncrementalCardUpdate(body, newHtml) {
|
|
367
|
+
const template = document.createElement("div");
|
|
368
|
+
template.innerHTML = newHtml;
|
|
369
|
+
const newCards = Array.from(template.querySelectorAll(".card"));
|
|
370
|
+
const existingCards = /* @__PURE__ */ new Map();
|
|
371
|
+
body.querySelectorAll(".card").forEach((card) => {
|
|
372
|
+
const id = card.dataset.id;
|
|
373
|
+
if (id) existingCards.set(id, card);
|
|
374
|
+
});
|
|
375
|
+
const newCardIds = /* @__PURE__ */ new Set();
|
|
376
|
+
newCards.forEach((card) => {
|
|
377
|
+
const id = card.dataset.id;
|
|
378
|
+
if (id) newCardIds.add(id);
|
|
379
|
+
});
|
|
380
|
+
existingCards.forEach((card, id) => {
|
|
381
|
+
if (!newCardIds.has(id)) {
|
|
382
|
+
card.remove();
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
newCards.forEach((newCard, index) => {
|
|
386
|
+
const id = newCard.dataset.id;
|
|
387
|
+
const newUpdatedAt = newCard.dataset.updatedAt;
|
|
388
|
+
const existing = id ? existingCards.get(id) : void 0;
|
|
389
|
+
if (existing) {
|
|
390
|
+
const existingUpdatedAt = existing.dataset.updatedAt;
|
|
391
|
+
let activeCard;
|
|
392
|
+
if (newUpdatedAt !== existingUpdatedAt) {
|
|
393
|
+
existing.replaceWith(newCard);
|
|
394
|
+
activeCard = newCard;
|
|
395
|
+
} else {
|
|
396
|
+
activeCard = existing;
|
|
397
|
+
}
|
|
398
|
+
const currentChild = body.children[index];
|
|
399
|
+
if (currentChild !== activeCard) {
|
|
400
|
+
body.insertBefore(activeCard, currentChild || null);
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
const currentChild = body.children[index];
|
|
404
|
+
body.insertBefore(newCard, currentChild || null);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
473
408
|
function updateColumnHtml(col) {
|
|
474
409
|
const body = document.getElementById("col-" + col.status);
|
|
475
410
|
if (!body) return;
|
|
476
|
-
body
|
|
411
|
+
applyIncrementalCardUpdate(body, col.html);
|
|
477
412
|
const colEl = body.closest(".column");
|
|
478
413
|
if (colEl) {
|
|
479
414
|
const countEl = colEl.querySelector(".column-count");
|
|
@@ -509,6 +444,9 @@
|
|
|
509
444
|
const data = await res.json();
|
|
510
445
|
data.columns.forEach(updateColumnHtml);
|
|
511
446
|
const detailTaskId2 = _getDetailTaskId2 ? _getDetailTaskId2() : null;
|
|
447
|
+
if (detailTaskId2 !== null && _setActiveCard) {
|
|
448
|
+
_setActiveCard(detailTaskId2);
|
|
449
|
+
}
|
|
512
450
|
if (detailTaskId2 !== null) {
|
|
513
451
|
await refreshOpenDetailPanel(detailTaskId2);
|
|
514
452
|
}
|
|
@@ -522,16 +460,11 @@
|
|
|
522
460
|
if (!res.ok) return;
|
|
523
461
|
const data = await res.json();
|
|
524
462
|
const ts = data.updatedAt;
|
|
525
|
-
const detailPanel = document.getElementById("detail-panel");
|
|
526
463
|
if (lastUpdatedAt === null) {
|
|
527
464
|
lastUpdatedAt = ts;
|
|
528
465
|
} else if (ts !== lastUpdatedAt) {
|
|
529
466
|
lastUpdatedAt = ts;
|
|
530
|
-
|
|
531
|
-
await refreshBoardCards();
|
|
532
|
-
} else {
|
|
533
|
-
location.reload();
|
|
534
|
-
}
|
|
467
|
+
await refreshBoardCards();
|
|
535
468
|
}
|
|
536
469
|
} catch {
|
|
537
470
|
}
|
|
@@ -541,16 +474,316 @@
|
|
|
541
474
|
pollBoardUpdates();
|
|
542
475
|
}
|
|
543
476
|
|
|
477
|
+
// src/board/client/addTaskModal.ts
|
|
478
|
+
var selectedTags = [];
|
|
479
|
+
var tagInputValue = "";
|
|
480
|
+
var tagFocusedIndex = -1;
|
|
481
|
+
function getFilteredAddTags() {
|
|
482
|
+
const selectedIds = new Set(selectedTags.map((t) => t.id));
|
|
483
|
+
const available = allAvailableTags.filter((t) => !selectedIds.has(t.id));
|
|
484
|
+
if (!tagInputValue.trim()) return available;
|
|
485
|
+
const q = tagInputValue.toLowerCase();
|
|
486
|
+
return available.filter((t) => t.name.toLowerCase().includes(q));
|
|
487
|
+
}
|
|
488
|
+
function renderAddTagPills(control, input) {
|
|
489
|
+
control.querySelectorAll(".tag-pill").forEach((p) => p.remove());
|
|
490
|
+
selectedTags.forEach((t) => {
|
|
491
|
+
const pill = document.createElement("span");
|
|
492
|
+
pill.className = "tag-pill";
|
|
493
|
+
pill.dataset.tagId = String(t.id);
|
|
494
|
+
const label = document.createTextNode(t.name);
|
|
495
|
+
const removeBtn = document.createElement("button");
|
|
496
|
+
removeBtn.className = "tag-pill-remove";
|
|
497
|
+
removeBtn.title = "Remove tag";
|
|
498
|
+
removeBtn.innerHTML = "×";
|
|
499
|
+
removeBtn.addEventListener("click", (e) => {
|
|
500
|
+
e.stopPropagation();
|
|
501
|
+
const idx = selectedTags.findIndex((x) => x.id === t.id);
|
|
502
|
+
if (idx !== -1) selectedTags.splice(idx, 1);
|
|
503
|
+
renderAddTagPills(control, input);
|
|
504
|
+
});
|
|
505
|
+
pill.appendChild(label);
|
|
506
|
+
pill.appendChild(removeBtn);
|
|
507
|
+
control.insertBefore(pill, input);
|
|
508
|
+
});
|
|
509
|
+
input.placeholder = selectedTags.length === 0 ? "Add tags..." : "";
|
|
510
|
+
}
|
|
511
|
+
function renderAddTagDropdown(dropdown) {
|
|
512
|
+
const filtered = getFilteredAddTags();
|
|
513
|
+
dropdown.innerHTML = "";
|
|
514
|
+
tagFocusedIndex = -1;
|
|
515
|
+
if (filtered.length === 0) {
|
|
516
|
+
const noOpt = document.createElement("div");
|
|
517
|
+
noOpt.className = "tag-select-no-options";
|
|
518
|
+
noOpt.textContent = tagInputValue ? "No matching tags" : "No tags available";
|
|
519
|
+
dropdown.appendChild(noOpt);
|
|
520
|
+
} else {
|
|
521
|
+
filtered.forEach((t, i) => {
|
|
522
|
+
const opt = document.createElement("div");
|
|
523
|
+
opt.className = "tag-select-option";
|
|
524
|
+
opt.dataset.tagId = String(t.id);
|
|
525
|
+
opt.textContent = t.name;
|
|
526
|
+
opt.addEventListener("mouseover", () => setAddTagFocused(dropdown, i));
|
|
527
|
+
opt.addEventListener("mousedown", (e) => {
|
|
528
|
+
e.preventDefault();
|
|
529
|
+
selectAddTag(t.id, dropdown, document.getElementById("add-tag-input"));
|
|
530
|
+
});
|
|
531
|
+
dropdown.appendChild(opt);
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function setAddTagFocused(dropdown, index) {
|
|
536
|
+
const opts = dropdown.querySelectorAll(".tag-select-option");
|
|
537
|
+
opts.forEach((o, i) => o.classList.toggle("focused", i === index));
|
|
538
|
+
tagFocusedIndex = index;
|
|
539
|
+
}
|
|
540
|
+
function selectAddTag(tagId, dropdown, input) {
|
|
541
|
+
const tag = allAvailableTags.find((t) => t.id === tagId);
|
|
542
|
+
if (!tag) return;
|
|
543
|
+
selectedTags.push(tag);
|
|
544
|
+
input.value = "";
|
|
545
|
+
tagInputValue = "";
|
|
546
|
+
const control = document.getElementById("add-tag-select-control");
|
|
547
|
+
renderAddTagPills(control, input);
|
|
548
|
+
renderAddTagDropdown(dropdown);
|
|
549
|
+
}
|
|
550
|
+
function initAddTagSelector() {
|
|
551
|
+
const control = document.getElementById("add-tag-select-control");
|
|
552
|
+
const dropdown = document.getElementById("add-tag-select-dropdown");
|
|
553
|
+
if (!control || !dropdown) return;
|
|
554
|
+
const input = document.createElement("input");
|
|
555
|
+
input.className = "tag-select-input";
|
|
556
|
+
input.id = "add-tag-input";
|
|
557
|
+
input.type = "text";
|
|
558
|
+
input.autocomplete = "off";
|
|
559
|
+
control.appendChild(input);
|
|
560
|
+
control.addEventListener("click", () => input.focus());
|
|
561
|
+
input.addEventListener("focus", () => {
|
|
562
|
+
renderAddTagDropdown(dropdown);
|
|
563
|
+
dropdown.classList.add("open");
|
|
564
|
+
});
|
|
565
|
+
input.addEventListener(
|
|
566
|
+
"blur",
|
|
567
|
+
() => setTimeout(() => {
|
|
568
|
+
dropdown.classList.remove("open");
|
|
569
|
+
tagFocusedIndex = -1;
|
|
570
|
+
}, 150)
|
|
571
|
+
);
|
|
572
|
+
input.addEventListener("input", () => {
|
|
573
|
+
tagInputValue = input.value;
|
|
574
|
+
renderAddTagDropdown(dropdown);
|
|
575
|
+
if (!dropdown.classList.contains("open")) dropdown.classList.add("open");
|
|
576
|
+
});
|
|
577
|
+
input.addEventListener("keydown", (e) => {
|
|
578
|
+
const filtered = getFilteredAddTags();
|
|
579
|
+
const opts = dropdown.querySelectorAll(".tag-select-option");
|
|
580
|
+
if (e.key === "ArrowDown") {
|
|
581
|
+
e.preventDefault();
|
|
582
|
+
setAddTagFocused(dropdown, Math.min(tagFocusedIndex + 1, opts.length - 1));
|
|
583
|
+
} else if (e.key === "ArrowUp") {
|
|
584
|
+
e.preventDefault();
|
|
585
|
+
setAddTagFocused(dropdown, Math.max(tagFocusedIndex - 1, 0));
|
|
586
|
+
} else if (e.key === "Enter") {
|
|
587
|
+
e.preventDefault();
|
|
588
|
+
if (tagFocusedIndex >= 0 && filtered[tagFocusedIndex]) {
|
|
589
|
+
selectAddTag(filtered[tagFocusedIndex].id, dropdown, input);
|
|
590
|
+
}
|
|
591
|
+
} else if (e.key === "Escape") {
|
|
592
|
+
dropdown.classList.remove("open");
|
|
593
|
+
input.blur();
|
|
594
|
+
} else if (e.key === "Backspace" && input.value === "" && selectedTags.length > 0) {
|
|
595
|
+
e.preventDefault();
|
|
596
|
+
selectedTags.splice(selectedTags.length - 1, 1);
|
|
597
|
+
renderAddTagPills(control, input);
|
|
598
|
+
renderAddTagDropdown(dropdown);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
function addMetadataRow(container) {
|
|
603
|
+
const row = document.createElement("div");
|
|
604
|
+
row.className = "metadata-row";
|
|
605
|
+
const keyInput = document.createElement("input");
|
|
606
|
+
keyInput.type = "text";
|
|
607
|
+
keyInput.className = "metadata-row-key";
|
|
608
|
+
keyInput.placeholder = "Key";
|
|
609
|
+
const valueInput = document.createElement("input");
|
|
610
|
+
valueInput.type = "text";
|
|
611
|
+
valueInput.className = "metadata-row-value";
|
|
612
|
+
valueInput.placeholder = "Value";
|
|
613
|
+
const removeBtn = document.createElement("button");
|
|
614
|
+
removeBtn.type = "button";
|
|
615
|
+
removeBtn.className = "metadata-row-remove";
|
|
616
|
+
removeBtn.title = "Remove";
|
|
617
|
+
removeBtn.innerHTML = "×";
|
|
618
|
+
removeBtn.addEventListener("click", () => {
|
|
619
|
+
row.remove();
|
|
620
|
+
});
|
|
621
|
+
row.appendChild(keyInput);
|
|
622
|
+
row.appendChild(valueInput);
|
|
623
|
+
row.appendChild(removeBtn);
|
|
624
|
+
container.appendChild(row);
|
|
625
|
+
}
|
|
626
|
+
function collectMetadata(container) {
|
|
627
|
+
const rows = container.querySelectorAll(".metadata-row");
|
|
628
|
+
const result = [];
|
|
629
|
+
rows.forEach((row) => {
|
|
630
|
+
const key = (row.querySelector(".metadata-row-key")?.value ?? "").trim();
|
|
631
|
+
const value = (row.querySelector(".metadata-row-value")?.value ?? "").trim();
|
|
632
|
+
if (key) result.push({ key, value });
|
|
633
|
+
});
|
|
634
|
+
return result;
|
|
635
|
+
}
|
|
636
|
+
function resetAddModal(elements) {
|
|
637
|
+
elements.addTitle.value = "";
|
|
638
|
+
elements.addBody.value = "";
|
|
639
|
+
elements.addPriority.value = "";
|
|
640
|
+
selectedTags = [];
|
|
641
|
+
tagInputValue = "";
|
|
642
|
+
tagFocusedIndex = -1;
|
|
643
|
+
const control = document.getElementById("add-tag-select-control");
|
|
644
|
+
const input = document.getElementById("add-tag-input");
|
|
645
|
+
if (control && input) {
|
|
646
|
+
renderAddTagPills(control, input);
|
|
647
|
+
}
|
|
648
|
+
elements.addMetadataRows.innerHTML = "";
|
|
649
|
+
}
|
|
650
|
+
function openAddModal(elements, status) {
|
|
651
|
+
elements.addStatus.value = status;
|
|
652
|
+
resetAddModal(elements);
|
|
653
|
+
elements.addModal.classList.add("show");
|
|
654
|
+
elements.addTitle.focus();
|
|
655
|
+
}
|
|
656
|
+
async function submitAddTask(elements) {
|
|
657
|
+
const title = elements.addTitle.value.trim();
|
|
658
|
+
if (!title) {
|
|
659
|
+
elements.addTitle.focus();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
const status = elements.addStatus.value;
|
|
663
|
+
elements.addModal.classList.remove("show");
|
|
664
|
+
const tags = selectedTags.map((t) => t.id);
|
|
665
|
+
const metadata = collectMetadata(elements.addMetadataRows);
|
|
666
|
+
try {
|
|
667
|
+
const res = await fetch("/api/tasks", {
|
|
668
|
+
method: "POST",
|
|
669
|
+
headers: { "Content-Type": "application/json" },
|
|
670
|
+
body: JSON.stringify({
|
|
671
|
+
title,
|
|
672
|
+
body: elements.addBody.value.trim() || null,
|
|
673
|
+
status,
|
|
674
|
+
priority: elements.addPriority.value || null,
|
|
675
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
676
|
+
metadata: metadata.length > 0 ? metadata : void 0
|
|
677
|
+
})
|
|
678
|
+
});
|
|
679
|
+
if (!res.ok) throw new Error("Server error");
|
|
680
|
+
await refreshBoardCards();
|
|
681
|
+
} catch {
|
|
682
|
+
showToast("Failed to add task");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function initAddTaskModal() {
|
|
686
|
+
const elements = {
|
|
687
|
+
addModal: document.getElementById("add-modal"),
|
|
688
|
+
addTitle: document.getElementById("add-title"),
|
|
689
|
+
addBody: document.getElementById("add-body"),
|
|
690
|
+
addPriority: document.getElementById("add-priority"),
|
|
691
|
+
addStatus: document.getElementById("add-status"),
|
|
692
|
+
addTagControl: document.getElementById("add-tag-select-control"),
|
|
693
|
+
addTagDropdown: document.getElementById("add-tag-select-dropdown"),
|
|
694
|
+
addMetadataRows: document.getElementById("add-metadata-rows")
|
|
695
|
+
};
|
|
696
|
+
initAddTagSelector();
|
|
697
|
+
document.querySelectorAll(".add-btn").forEach((btn) => {
|
|
698
|
+
btn.addEventListener("click", (e) => {
|
|
699
|
+
e.stopPropagation();
|
|
700
|
+
openAddModal(elements, btn.dataset.status);
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
document.getElementById("add-cancel")?.addEventListener("click", () => {
|
|
704
|
+
elements.addModal.classList.remove("show");
|
|
705
|
+
});
|
|
706
|
+
elements.addModal.addEventListener("click", (e) => {
|
|
707
|
+
if (e.target === elements.addModal) elements.addModal.classList.remove("show");
|
|
708
|
+
});
|
|
709
|
+
elements.addTitle.addEventListener("keydown", (e) => {
|
|
710
|
+
if (e.key === "Enter" && !e.isComposing) {
|
|
711
|
+
e.preventDefault();
|
|
712
|
+
document.getElementById("add-submit").click();
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
document.getElementById("add-metadata-add-row")?.addEventListener("click", () => {
|
|
716
|
+
addMetadataRow(elements.addMetadataRows);
|
|
717
|
+
});
|
|
718
|
+
document.getElementById("add-submit")?.addEventListener("click", () => submitAddTask(elements));
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// src/board/client/contextMenu.ts
|
|
722
|
+
async function deleteCard(card) {
|
|
723
|
+
const taskId = card.dataset.id;
|
|
724
|
+
const status = card.dataset.status;
|
|
725
|
+
if (!confirm("Delete task #" + taskId + "?")) return;
|
|
726
|
+
card.remove();
|
|
727
|
+
updateCount(status);
|
|
728
|
+
try {
|
|
729
|
+
const res = await fetch("/api/tasks/" + taskId, { method: "DELETE" });
|
|
730
|
+
if (!res.ok) throw new Error("Server error");
|
|
731
|
+
} catch {
|
|
732
|
+
location.reload();
|
|
733
|
+
showToast("Failed to delete task");
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function initContextMenu() {
|
|
737
|
+
const ctxMenu = document.getElementById("context-menu");
|
|
738
|
+
let ctxTargetCard = null;
|
|
739
|
+
document.addEventListener("contextmenu", (e) => {
|
|
740
|
+
const card = e.target.closest(".card");
|
|
741
|
+
if (!card) {
|
|
742
|
+
ctxMenu.style.display = "none";
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
e.preventDefault();
|
|
746
|
+
ctxTargetCard = card;
|
|
747
|
+
ctxMenu.style.left = e.clientX + "px";
|
|
748
|
+
ctxMenu.style.top = e.clientY + "px";
|
|
749
|
+
ctxMenu.style.display = "block";
|
|
750
|
+
});
|
|
751
|
+
document.addEventListener("click", (e) => {
|
|
752
|
+
if (!e.target.closest("#context-menu")) {
|
|
753
|
+
ctxMenu.style.display = "none";
|
|
754
|
+
ctxTargetCard = null;
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
document.getElementById("ctx-delete")?.addEventListener("click", async (e) => {
|
|
758
|
+
e.stopPropagation();
|
|
759
|
+
ctxMenu.style.display = "none";
|
|
760
|
+
if (!ctxTargetCard) return;
|
|
761
|
+
const card = ctxTargetCard;
|
|
762
|
+
ctxTargetCard = null;
|
|
763
|
+
await deleteCard(card);
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
544
767
|
// src/board/client/detailPanel.ts
|
|
545
768
|
var detailTaskId = null;
|
|
546
769
|
var lastTab = "details";
|
|
547
770
|
function getDetailTaskId() {
|
|
548
771
|
return detailTaskId;
|
|
549
772
|
}
|
|
773
|
+
function setActiveCard(taskId) {
|
|
774
|
+
document.querySelectorAll(".card.active").forEach((card) => {
|
|
775
|
+
card.classList.remove("active");
|
|
776
|
+
});
|
|
777
|
+
if (taskId !== null) {
|
|
778
|
+
const card = document.querySelector('.card[data-id="' + taskId + '"]');
|
|
779
|
+
if (card) card.classList.add("active");
|
|
780
|
+
}
|
|
781
|
+
}
|
|
550
782
|
function closeDetailPanel() {
|
|
551
783
|
const detailPanel = document.getElementById("detail-panel");
|
|
552
784
|
detailPanel.classList.remove("open");
|
|
553
785
|
detailPanel.style.width = "";
|
|
786
|
+
setActiveCard(null);
|
|
554
787
|
detailTaskId = null;
|
|
555
788
|
}
|
|
556
789
|
function switchTab(tabName) {
|
|
@@ -794,6 +1027,10 @@
|
|
|
794
1027
|
html += "</div>";
|
|
795
1028
|
return html;
|
|
796
1029
|
}
|
|
1030
|
+
function autoResizeTextarea(el) {
|
|
1031
|
+
el.style.height = "auto";
|
|
1032
|
+
el.style.height = el.scrollHeight + "px";
|
|
1033
|
+
}
|
|
797
1034
|
function renderMetadataTable(metadata) {
|
|
798
1035
|
const otherMeta = metadata.filter((m) => m.key !== "priority");
|
|
799
1036
|
if (otherMeta.length === 0) return "";
|
|
@@ -833,9 +1070,8 @@
|
|
|
833
1070
|
if (hasRelations) {
|
|
834
1071
|
html += renderRelationsHtml(parent, blockedBy, blocking);
|
|
835
1072
|
}
|
|
836
|
-
html += renderEditableTextFields(task);
|
|
837
1073
|
html += renderMetadataTable(metadata);
|
|
838
|
-
html +=
|
|
1074
|
+
html += renderEditableTextFields(task);
|
|
839
1075
|
return html;
|
|
840
1076
|
}
|
|
841
1077
|
function renderDetailPanel(data) {
|
|
@@ -850,6 +1086,20 @@
|
|
|
850
1086
|
detailsPane.innerHTML = renderDetailPanelHtml(data);
|
|
851
1087
|
detailsPane.style.padding = "20px";
|
|
852
1088
|
}
|
|
1089
|
+
const footer = document.getElementById("detail-panel-footer");
|
|
1090
|
+
if (footer) {
|
|
1091
|
+
footer.innerHTML = '<span class="detail-footer-timestamp">created ' + relativeTime(task.created_at) + " · updated " + relativeTime(task.updated_at) + '</span><button id="detail-save-btn">Save</button>';
|
|
1092
|
+
document.getElementById("detail-save-btn")?.addEventListener("click", saveDetailTask);
|
|
1093
|
+
}
|
|
1094
|
+
const textarea = document.getElementById("detail-edit-body");
|
|
1095
|
+
if (textarea) {
|
|
1096
|
+
requestAnimationFrame(() => {
|
|
1097
|
+
autoResizeTextarea(textarea);
|
|
1098
|
+
});
|
|
1099
|
+
textarea.addEventListener("input", () => {
|
|
1100
|
+
autoResizeTextarea(textarea);
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
853
1103
|
loadAllTags().then(() => renderTagsSection([...tags])).catch((err) => {
|
|
854
1104
|
console.error("[agkan] renderDetailPanel tags failed", err);
|
|
855
1105
|
});
|
|
@@ -864,6 +1114,7 @@
|
|
|
864
1114
|
if (!res.ok) throw new Error("Server error");
|
|
865
1115
|
const data = await res.json();
|
|
866
1116
|
renderDetailPanel(data);
|
|
1117
|
+
setActiveCard(Number(taskId));
|
|
867
1118
|
if (!detailPanel.classList.contains("open")) {
|
|
868
1119
|
const preferredWidth = detailPanel.dataset.preferredWidth || String(PANEL_DEFAULT_WIDTH2);
|
|
869
1120
|
detailPanel.style.width = preferredWidth + "px";
|
|
@@ -1036,7 +1287,6 @@
|
|
|
1036
1287
|
switchTab(btn.dataset.tab);
|
|
1037
1288
|
});
|
|
1038
1289
|
initPanelResize(detailPanel);
|
|
1039
|
-
document.getElementById("detail-save-btn")?.addEventListener("click", saveDetailTask);
|
|
1040
1290
|
document.querySelectorAll(".card").forEach((card) => {
|
|
1041
1291
|
card.addEventListener("click", async (e) => {
|
|
1042
1292
|
if (e.defaultPrevented) return;
|
|
@@ -1047,7 +1297,8 @@
|
|
|
1047
1297
|
openTaskDetail,
|
|
1048
1298
|
renderDetailPanel,
|
|
1049
1299
|
showUpdateWarning,
|
|
1050
|
-
getDetailTaskId
|
|
1300
|
+
getDetailTaskId,
|
|
1301
|
+
setActiveCard
|
|
1051
1302
|
});
|
|
1052
1303
|
registerGetDetailTaskId(getDetailTaskId);
|
|
1053
1304
|
}
|
|
@@ -1279,30 +1530,17 @@
|
|
|
1279
1530
|
}
|
|
1280
1531
|
});
|
|
1281
1532
|
}
|
|
1282
|
-
async function executePurge(
|
|
1283
|
-
purgeConfirmBtn.disabled = true;
|
|
1284
|
-
purgeConfirmBtn.textContent = "Purging...";
|
|
1533
|
+
async function executePurge() {
|
|
1285
1534
|
try {
|
|
1286
1535
|
const res = await fetch("/api/tasks/purge", {
|
|
1287
1536
|
method: "POST",
|
|
1288
1537
|
headers: { "Content-Type": "application/json" },
|
|
1289
1538
|
body: JSON.stringify({})
|
|
1290
1539
|
});
|
|
1291
|
-
const data = await res.json();
|
|
1292
1540
|
if (res.ok) {
|
|
1293
|
-
|
|
1294
|
-
setTimeout(() => {
|
|
1295
|
-
purgeModal.classList.remove("show");
|
|
1296
|
-
}, 1500);
|
|
1297
|
-
location.reload();
|
|
1298
|
-
} else {
|
|
1299
|
-
purgeResultEl.textContent = "Error: " + (data.error || "Unknown error");
|
|
1541
|
+
await refreshBoardCards();
|
|
1300
1542
|
}
|
|
1301
1543
|
} catch {
|
|
1302
|
-
purgeResultEl.textContent = "Failed to purge tasks.";
|
|
1303
|
-
} finally {
|
|
1304
|
-
purgeConfirmBtn.disabled = false;
|
|
1305
|
-
purgeConfirmBtn.textContent = "Purge";
|
|
1306
1544
|
}
|
|
1307
1545
|
}
|
|
1308
1546
|
function initPurgeModal(burgerDropdown) {
|
|
@@ -1318,7 +1556,10 @@
|
|
|
1318
1556
|
purgeCancelBtn.addEventListener("click", () => {
|
|
1319
1557
|
purgeModal.classList.remove("show");
|
|
1320
1558
|
});
|
|
1321
|
-
purgeConfirmBtn.addEventListener("click", () =>
|
|
1559
|
+
purgeConfirmBtn.addEventListener("click", () => {
|
|
1560
|
+
purgeModal.classList.remove("show");
|
|
1561
|
+
void executePurge();
|
|
1562
|
+
});
|
|
1322
1563
|
}
|
|
1323
1564
|
function initVersionModal(burgerDropdown) {
|
|
1324
1565
|
const versionModal = document.getElementById("version-info-modal");
|
|
@@ -1340,11 +1581,108 @@
|
|
|
1340
1581
|
versionModal.classList.remove("show");
|
|
1341
1582
|
});
|
|
1342
1583
|
}
|
|
1584
|
+
function initExportModal(burgerDropdown) {
|
|
1585
|
+
document.getElementById("burger-export-tasks")?.addEventListener("click", () => {
|
|
1586
|
+
burgerDropdown.classList.remove("open");
|
|
1587
|
+
const a = document.createElement("a");
|
|
1588
|
+
a.href = "/api/export";
|
|
1589
|
+
a.download = "";
|
|
1590
|
+
document.body.appendChild(a);
|
|
1591
|
+
a.click();
|
|
1592
|
+
document.body.removeChild(a);
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
function initImportModal(burgerDropdown) {
|
|
1596
|
+
const importModal = document.getElementById("import-modal");
|
|
1597
|
+
const importCancelBtn = document.getElementById("import-cancel-btn");
|
|
1598
|
+
const importConfirmBtn = document.getElementById("import-confirm-btn");
|
|
1599
|
+
const importResultEl = document.getElementById("import-result");
|
|
1600
|
+
const importDropZone = document.getElementById("import-drop-zone");
|
|
1601
|
+
const importFileInput = document.getElementById("import-file-input");
|
|
1602
|
+
if (!importModal || !importCancelBtn || !importConfirmBtn || !importResultEl || !importDropZone || !importFileInput) {
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
const safeImportModal = importModal;
|
|
1606
|
+
const safeImportCancelBtn = importCancelBtn;
|
|
1607
|
+
const safeImportConfirmBtn = importConfirmBtn;
|
|
1608
|
+
const safeImportResultEl = importResultEl;
|
|
1609
|
+
const safeImportDropZone = importDropZone;
|
|
1610
|
+
const safeImportFileInput = importFileInput;
|
|
1611
|
+
let selectedFile = null;
|
|
1612
|
+
function setFile(file) {
|
|
1613
|
+
selectedFile = file;
|
|
1614
|
+
safeImportResultEl.textContent = `Selected: ${file.name}`;
|
|
1615
|
+
safeImportResultEl.style.color = "#64748b";
|
|
1616
|
+
safeImportConfirmBtn.disabled = false;
|
|
1617
|
+
}
|
|
1618
|
+
document.getElementById("burger-import-tasks")?.addEventListener("click", () => {
|
|
1619
|
+
burgerDropdown.classList.remove("open");
|
|
1620
|
+
selectedFile = null;
|
|
1621
|
+
safeImportResultEl.textContent = "";
|
|
1622
|
+
safeImportConfirmBtn.disabled = true;
|
|
1623
|
+
safeImportFileInput.value = "";
|
|
1624
|
+
safeImportModal.classList.add("show");
|
|
1625
|
+
});
|
|
1626
|
+
safeImportCancelBtn.addEventListener("click", () => {
|
|
1627
|
+
safeImportModal.classList.remove("show");
|
|
1628
|
+
});
|
|
1629
|
+
safeImportFileInput.addEventListener("change", () => {
|
|
1630
|
+
const file = safeImportFileInput.files?.[0];
|
|
1631
|
+
if (file) setFile(file);
|
|
1632
|
+
});
|
|
1633
|
+
safeImportDropZone.addEventListener("dragover", (e) => {
|
|
1634
|
+
e.preventDefault();
|
|
1635
|
+
safeImportDropZone.style.borderColor = "#3b82f6";
|
|
1636
|
+
});
|
|
1637
|
+
safeImportDropZone.addEventListener("dragleave", () => {
|
|
1638
|
+
safeImportDropZone.style.borderColor = "#94a3b8";
|
|
1639
|
+
});
|
|
1640
|
+
safeImportDropZone.addEventListener("drop", (e) => {
|
|
1641
|
+
e.preventDefault();
|
|
1642
|
+
safeImportDropZone.style.borderColor = "#94a3b8";
|
|
1643
|
+
const file = e.dataTransfer?.files?.[0];
|
|
1644
|
+
if (file) setFile(file);
|
|
1645
|
+
});
|
|
1646
|
+
safeImportConfirmBtn.addEventListener("click", async () => {
|
|
1647
|
+
if (!selectedFile) return;
|
|
1648
|
+
safeImportConfirmBtn.disabled = true;
|
|
1649
|
+
safeImportConfirmBtn.textContent = "Importing...";
|
|
1650
|
+
try {
|
|
1651
|
+
const text = await selectedFile.text();
|
|
1652
|
+
const data = JSON.parse(text);
|
|
1653
|
+
const res = await fetch("/api/import", {
|
|
1654
|
+
method: "POST",
|
|
1655
|
+
headers: { "Content-Type": "application/json" },
|
|
1656
|
+
body: JSON.stringify(data)
|
|
1657
|
+
});
|
|
1658
|
+
const result = await res.json();
|
|
1659
|
+
if (res.ok) {
|
|
1660
|
+
safeImportResultEl.textContent = `Imported ${result.importedCount} task(s) successfully.`;
|
|
1661
|
+
safeImportResultEl.style.color = "#16a34a";
|
|
1662
|
+
setTimeout(() => {
|
|
1663
|
+
safeImportModal.classList.remove("show");
|
|
1664
|
+
location.reload();
|
|
1665
|
+
}, 1500);
|
|
1666
|
+
} else {
|
|
1667
|
+
safeImportResultEl.textContent = "Error: " + (result.error || "Unknown error");
|
|
1668
|
+
safeImportResultEl.style.color = "#dc2626";
|
|
1669
|
+
}
|
|
1670
|
+
} catch {
|
|
1671
|
+
safeImportResultEl.textContent = "Failed to import tasks. Invalid JSON file.";
|
|
1672
|
+
safeImportResultEl.style.color = "#dc2626";
|
|
1673
|
+
} finally {
|
|
1674
|
+
safeImportConfirmBtn.disabled = false;
|
|
1675
|
+
safeImportConfirmBtn.textContent = "Import";
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1343
1679
|
function initBurgerMenu() {
|
|
1344
1680
|
const burgerBtn = document.getElementById("burger-menu-btn");
|
|
1345
1681
|
const burgerDropdown = document.getElementById("burger-menu-dropdown");
|
|
1346
1682
|
initBurgerToggle(burgerBtn, burgerDropdown);
|
|
1347
1683
|
initPurgeModal(burgerDropdown);
|
|
1684
|
+
initExportModal(burgerDropdown);
|
|
1685
|
+
initImportModal(burgerDropdown);
|
|
1348
1686
|
initVersionModal(burgerDropdown);
|
|
1349
1687
|
initDarkMode();
|
|
1350
1688
|
}
|