agkan 2.14.0 → 2.14.3
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/boardStyles.d.ts +1 -1
- package/dist/board/boardStyles.d.ts.map +1 -1
- package/dist/board/boardStyles.js +1 -1
- package/dist/board/client/addTaskModal.d.ts +2 -0
- package/dist/board/client/addTaskModal.d.ts.map +1 -0
- package/dist/board/client/addTaskModal.js +64 -0
- package/dist/board/client/addTaskModal.js.map +1 -0
- package/dist/board/client/autoScroll.d.ts +4 -0
- package/dist/board/client/autoScroll.d.ts.map +1 -0
- package/dist/board/client/autoScroll.js +59 -0
- package/dist/board/client/autoScroll.js.map +1 -0
- package/dist/board/client/board.js +231 -202
- package/dist/board/client/boardPolling.d.ts +15 -0
- package/dist/board/client/boardPolling.d.ts.map +1 -0
- package/dist/board/client/boardPolling.js +144 -0
- package/dist/board/client/boardPolling.js.map +1 -0
- package/dist/board/client/burgerMenu.d.ts +2 -0
- package/dist/board/client/burgerMenu.d.ts.map +1 -0
- package/dist/board/client/burgerMenu.js +80 -0
- package/dist/board/client/burgerMenu.js.map +1 -0
- package/dist/board/client/contextMenu.d.ts +2 -0
- package/dist/board/client/contextMenu.d.ts.map +1 -0
- package/dist/board/client/contextMenu.js +52 -0
- package/dist/board/client/contextMenu.js.map +1 -0
- package/dist/board/client/detailPanel.d.ts +8 -0
- package/dist/board/client/detailPanel.d.ts.map +1 -0
- package/dist/board/client/detailPanel.js +565 -0
- package/dist/board/client/detailPanel.js.map +1 -0
- package/dist/board/client/dragDrop.d.ts +6 -0
- package/dist/board/client/dragDrop.d.ts.map +1 -0
- package/dist/board/client/dragDrop.js +82 -0
- package/dist/board/client/dragDrop.js.map +1 -0
- package/dist/board/client/filters.d.ts +6 -0
- package/dist/board/client/filters.d.ts.map +1 -0
- package/dist/board/client/filters.js +167 -0
- package/dist/board/client/filters.js.map +1 -0
- package/dist/board/client/main.d.ts +2 -0
- package/dist/board/client/main.d.ts.map +1 -0
- package/dist/board/client/main.js +20 -0
- package/dist/board/client/main.js.map +1 -0
- package/dist/board/client/tags.d.ts +6 -0
- package/dist/board/client/tags.d.ts.map +1 -0
- package/dist/board/client/tags.js +198 -0
- package/dist/board/client/tags.js.map +1 -0
- package/dist/board/client/types.d.ts +48 -0
- package/dist/board/client/types.d.ts.map +1 -0
- package/dist/board/client/types.js +4 -0
- package/dist/board/client/types.js.map +1 -0
- package/dist/board/client/utils.d.ts +4 -0
- package/dist/board/client/utils.d.ts.map +1 -0
- package/dist/board/client/utils.js +44 -0
- package/dist/board/client/utils.js.map +1 -0
- package/package.json +1 -1
- package/dist/services/ProcessService.d.ts +0 -54
- package/dist/services/ProcessService.d.ts.map +0 -1
- package/dist/services/ProcessService.js +0 -147
- package/dist/services/ProcessService.js.map +0 -1
- package/dist/services/TmuxService.d.ts +0 -2
- package/dist/services/TmuxService.d.ts.map +0 -1
- package/dist/services/TmuxService.js +0 -7
- package/dist/services/TmuxService.js.map +0 -1
|
@@ -764,6 +764,221 @@
|
|
|
764
764
|
});
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
+
// src/board/client/detailPanelApi.ts
|
|
768
|
+
var PANEL_MIN_WIDTH = 280;
|
|
769
|
+
var PANEL_MAX_WIDTH = 800;
|
|
770
|
+
var PANEL_DEFAULT_WIDTH = 400;
|
|
771
|
+
async function fetchComments(taskId) {
|
|
772
|
+
const res = await fetch("/api/tasks/" + taskId + "/comments");
|
|
773
|
+
if (!res.ok) throw new Error("Server error");
|
|
774
|
+
const data = await res.json();
|
|
775
|
+
return data.comments || [];
|
|
776
|
+
}
|
|
777
|
+
async function patchComment(commentId, content) {
|
|
778
|
+
const res = await fetch("/api/comments/" + commentId, {
|
|
779
|
+
method: "PATCH",
|
|
780
|
+
headers: { "Content-Type": "application/json" },
|
|
781
|
+
body: JSON.stringify({ content })
|
|
782
|
+
});
|
|
783
|
+
if (!res.ok) throw new Error("Server error");
|
|
784
|
+
}
|
|
785
|
+
async function deleteCommentRequest(commentId) {
|
|
786
|
+
const res = await fetch("/api/comments/" + commentId, { method: "DELETE" });
|
|
787
|
+
if (!res.ok) throw new Error("Server error");
|
|
788
|
+
}
|
|
789
|
+
async function postComment(taskId, content) {
|
|
790
|
+
const res = await fetch("/api/tasks/" + taskId + "/comments", {
|
|
791
|
+
method: "POST",
|
|
792
|
+
headers: { "Content-Type": "application/json" },
|
|
793
|
+
body: JSON.stringify({ content })
|
|
794
|
+
});
|
|
795
|
+
if (!res.ok) throw new Error("Server error");
|
|
796
|
+
}
|
|
797
|
+
async function fetchTaskDetail(taskId) {
|
|
798
|
+
const res = await fetch("/api/tasks/" + taskId);
|
|
799
|
+
if (!res.ok) throw new Error("Server error");
|
|
800
|
+
return res.json();
|
|
801
|
+
}
|
|
802
|
+
async function patchTask(taskId, fields) {
|
|
803
|
+
const res = await fetch("/api/tasks/" + taskId, {
|
|
804
|
+
method: "PATCH",
|
|
805
|
+
headers: { "Content-Type": "application/json" },
|
|
806
|
+
body: JSON.stringify(fields)
|
|
807
|
+
});
|
|
808
|
+
if (!res.ok) throw new Error("Server error");
|
|
809
|
+
return fetchTaskDetail(taskId);
|
|
810
|
+
}
|
|
811
|
+
async function syncTimestampAfterSave() {
|
|
812
|
+
try {
|
|
813
|
+
const tsRes = await fetch("/api/board/updated-at");
|
|
814
|
+
if (tsRes.ok) {
|
|
815
|
+
const tsData = await tsRes.json();
|
|
816
|
+
setLastUpdatedAt(tsData.updatedAt);
|
|
817
|
+
}
|
|
818
|
+
} catch {
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
async function fetchPanelWidthFromConfig() {
|
|
822
|
+
let targetWidth = PANEL_DEFAULT_WIDTH;
|
|
823
|
+
try {
|
|
824
|
+
const res = await fetch("/api/config");
|
|
825
|
+
if (res.ok) {
|
|
826
|
+
const data = await res.json();
|
|
827
|
+
const savedWidth = data && data.board && data.board.detailPaneWidth;
|
|
828
|
+
if (typeof savedWidth === "number" && savedWidth >= PANEL_MIN_WIDTH && savedWidth <= PANEL_MAX_WIDTH) {
|
|
829
|
+
targetWidth = savedWidth;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
} catch {
|
|
833
|
+
}
|
|
834
|
+
return targetWidth;
|
|
835
|
+
}
|
|
836
|
+
function savePanelWidthToConfig(width) {
|
|
837
|
+
fetch("/api/config", {
|
|
838
|
+
method: "PUT",
|
|
839
|
+
headers: { "Content-Type": "application/json" },
|
|
840
|
+
body: JSON.stringify({ board: { detailPaneWidth: width } })
|
|
841
|
+
}).catch(function() {
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/board/client/detailPanelHtml.ts
|
|
846
|
+
function renderCommentItemHtml(comment, taskId) {
|
|
847
|
+
const authorText = comment.author ? escapeHtmlClient(comment.author) : "Anonymous";
|
|
848
|
+
const dateRel = relativeTime(comment.created_at);
|
|
849
|
+
const dateAbs = escapeHtmlClient(comment.created_at);
|
|
850
|
+
const contentText = escapeHtmlClient(comment.content);
|
|
851
|
+
let html = '<div class="comment-item" data-comment-id="' + comment.id + '">';
|
|
852
|
+
html += '<div class="comment-meta">';
|
|
853
|
+
html += '<span class="comment-author">' + authorText + "</span>";
|
|
854
|
+
html += '<span class="comment-date" title="' + dateAbs + '">' + dateRel + "</span>";
|
|
855
|
+
html += '<span class="comment-actions">';
|
|
856
|
+
html += '<button class="comment-action-btn" title="Edit" data-action="start-comment-edit" data-comment-id="' + comment.id + '">✎</button>';
|
|
857
|
+
html += '<button class="comment-action-btn danger" title="Delete" data-action="delete-comment" data-comment-id="' + comment.id + '" data-task-id="' + taskId + '">🗑</button>';
|
|
858
|
+
html += "</span></div>";
|
|
859
|
+
html += '<div class="comment-content" id="comment-content-' + comment.id + '">' + contentText + "</div>";
|
|
860
|
+
html += '<div id="comment-edit-' + comment.id + '" style="display:none;">';
|
|
861
|
+
html += '<textarea class="comment-edit-area" id="comment-edit-area-' + comment.id + '">' + contentText + "</textarea>";
|
|
862
|
+
html += '<div class="comment-edit-actions">';
|
|
863
|
+
html += '<button class="comment-btn" data-action="save-comment-edit" data-comment-id="' + comment.id + '" data-task-id="' + taskId + '">Save</button>';
|
|
864
|
+
html += '<button class="comment-btn" data-action="cancel-comment-edit" data-comment-id="' + comment.id + '">Cancel</button>';
|
|
865
|
+
html += "</div></div></div>";
|
|
866
|
+
return html;
|
|
867
|
+
}
|
|
868
|
+
function renderAddCommentFormHtml(taskId) {
|
|
869
|
+
let html = '<button class="add-comment-trigger" id="add-comment-trigger" data-action="open-add-comment">+ Add comment...</button>';
|
|
870
|
+
html += '<div class="add-comment-form" id="add-comment-form">';
|
|
871
|
+
html += '<textarea class="add-comment-textarea" id="add-comment-text" placeholder="Write a comment..."></textarea>';
|
|
872
|
+
html += "<div>";
|
|
873
|
+
html += '<button class="add-comment-submit" data-action="submit-comment" data-task-id="' + taskId + '">Add Comment</button>';
|
|
874
|
+
html += '<button class="add-comment-cancel" data-action="close-add-comment">Cancel</button>';
|
|
875
|
+
html += "</div></div>";
|
|
876
|
+
return html;
|
|
877
|
+
}
|
|
878
|
+
function renderStatusField(currentStatus, allStatuses, statusLabels) {
|
|
879
|
+
let html = '<div class="detail-field">';
|
|
880
|
+
html += '<div class="detail-field-label">Status</div>';
|
|
881
|
+
html += '<select id="detail-edit-status" class="detail-edit-select">';
|
|
882
|
+
allStatuses.forEach((s) => {
|
|
883
|
+
const selected = s === currentStatus ? " selected" : "";
|
|
884
|
+
html += '<option value="' + s + '"' + selected + ">" + statusLabels[s] + "</option>";
|
|
885
|
+
});
|
|
886
|
+
html += "</select></div>";
|
|
887
|
+
return html;
|
|
888
|
+
}
|
|
889
|
+
function renderPriorityField(currentPriority, allPriorities) {
|
|
890
|
+
let html = '<div class="detail-field">';
|
|
891
|
+
html += '<div class="detail-field-label">Priority</div>';
|
|
892
|
+
html += '<select id="detail-edit-priority" class="detail-edit-select">';
|
|
893
|
+
html += '<option value="">None</option>';
|
|
894
|
+
allPriorities.forEach((p) => {
|
|
895
|
+
const selected = currentPriority === p ? " selected" : "";
|
|
896
|
+
html += '<option value="' + p + '"' + selected + ">" + p.charAt(0).toUpperCase() + p.slice(1) + "</option>";
|
|
897
|
+
});
|
|
898
|
+
html += "</select></div>";
|
|
899
|
+
return html;
|
|
900
|
+
}
|
|
901
|
+
function renderRelationsHtml(parent, blockedBy, blocking) {
|
|
902
|
+
let html = '<div class="detail-relations">';
|
|
903
|
+
if (parent) {
|
|
904
|
+
html += '<div class="detail-relation-row">';
|
|
905
|
+
html += '<span class="detail-relation-label">Parent</span>';
|
|
906
|
+
html += '<div class="detail-relation-ids"><span class="detail-relation-id">#' + parent.id + " " + escapeHtmlClient(parent.title) + "</span></div>";
|
|
907
|
+
html += "</div>";
|
|
908
|
+
}
|
|
909
|
+
if (blockedBy.length > 0) {
|
|
910
|
+
html += '<div class="detail-relation-row"><span class="detail-relation-label">Blocked by</span>';
|
|
911
|
+
html += '<div class="detail-relation-ids">';
|
|
912
|
+
blockedBy.forEach((t) => {
|
|
913
|
+
html += '<span class="detail-relation-id">#' + t.id + "</span>";
|
|
914
|
+
});
|
|
915
|
+
html += "</div></div>";
|
|
916
|
+
}
|
|
917
|
+
if (blocking.length > 0) {
|
|
918
|
+
html += '<div class="detail-relation-row"><span class="detail-relation-label">Blocking</span>';
|
|
919
|
+
html += '<div class="detail-relation-ids">';
|
|
920
|
+
blocking.forEach((t) => {
|
|
921
|
+
html += '<span class="detail-relation-id">#' + t.id + "</span>";
|
|
922
|
+
});
|
|
923
|
+
html += "</div></div>";
|
|
924
|
+
}
|
|
925
|
+
html += "</div>";
|
|
926
|
+
return html;
|
|
927
|
+
}
|
|
928
|
+
function renderMetadataTable(metadata) {
|
|
929
|
+
const otherMeta = metadata.filter((m) => m.key !== "priority");
|
|
930
|
+
if (otherMeta.length === 0) return "";
|
|
931
|
+
let html = '<div class="detail-field"><div class="detail-field-label">Metadata</div>';
|
|
932
|
+
html += '<table class="detail-meta-table">';
|
|
933
|
+
otherMeta.forEach((m) => {
|
|
934
|
+
html += "<tr><td>" + escapeHtmlClient(m.key) + "</td><td>" + escapeHtmlClient(m.value) + "</td></tr>";
|
|
935
|
+
});
|
|
936
|
+
html += "</table></div>";
|
|
937
|
+
return html;
|
|
938
|
+
}
|
|
939
|
+
function renderEditableTextFields(task) {
|
|
940
|
+
let html = '<div class="detail-field"><div class="detail-field-label">Title</div>';
|
|
941
|
+
html += '<input id="detail-edit-title" class="detail-edit-input" type="text" value="' + escapeHtmlClient(task.title) + '">';
|
|
942
|
+
html += "</div>";
|
|
943
|
+
html += '<div class="detail-field description-field-wrapper"><div class="detail-field-label">Description</div>';
|
|
944
|
+
html += '<textarea id="detail-edit-body" class="detail-edit-textarea">' + escapeHtmlClient(task.body || "") + "</textarea>";
|
|
945
|
+
html += "</div>";
|
|
946
|
+
return html;
|
|
947
|
+
}
|
|
948
|
+
function renderDetailPanelHtml(data) {
|
|
949
|
+
const task = data.task;
|
|
950
|
+
const metadata = data.metadata || [];
|
|
951
|
+
const blockedBy = data.blockedBy || [];
|
|
952
|
+
const blocking = data.blocking || [];
|
|
953
|
+
const parent = data.parent || null;
|
|
954
|
+
const win = window;
|
|
955
|
+
const allStatuses = win.allStatuses;
|
|
956
|
+
const statusLabels = win.statusLabels;
|
|
957
|
+
const allPriorities = win.allPriorities;
|
|
958
|
+
let html = "";
|
|
959
|
+
html += renderStatusField(task.status, allStatuses, statusLabels);
|
|
960
|
+
html += renderPriorityField(task.priority, allPriorities);
|
|
961
|
+
html += '<div class="detail-field"><div class="detail-field-label">Tags</div>';
|
|
962
|
+
html += '<div id="detail-tags-container"></div></div>';
|
|
963
|
+
const hasRelations = parent || blockedBy.length > 0 || blocking.length > 0;
|
|
964
|
+
if (hasRelations) {
|
|
965
|
+
html += renderRelationsHtml(parent, blockedBy, blocking);
|
|
966
|
+
}
|
|
967
|
+
html += renderMetadataTable(metadata);
|
|
968
|
+
html += renderEditableTextFields(task);
|
|
969
|
+
return html;
|
|
970
|
+
}
|
|
971
|
+
function buildDetailPanelHtml() {
|
|
972
|
+
return '<div class="detail-panel" id="detail-panel"><div class="detail-panel-resize-handle" id="detail-panel-resize-handle"></div><div class="detail-panel-header"><h2 id="detail-panel-title">Task Detail</h2><button class="detail-panel-close" id="detail-panel-close" title="Close">×</button></div><div class="detail-tabs" id="detail-tabs"><button class="detail-tab active" data-tab="details">Details</button><button class="detail-tab" data-tab="comments" id="detail-tab-comments">Comments</button></div><div class="detail-panel-body" id="detail-panel-body"><div class="detail-tab-content active" id="detail-tab-content-details"></div><div class="detail-tab-content" id="detail-tab-content-comments"></div></div><div class="detail-panel-footer" id="detail-panel-footer"><button id="detail-save-btn">Save</button></div></div>';
|
|
973
|
+
}
|
|
974
|
+
function autoResizeTextarea(el) {
|
|
975
|
+
const scrollContainer = el.closest(".detail-tab-content");
|
|
976
|
+
const scrollTop = scrollContainer?.scrollTop ?? 0;
|
|
977
|
+
el.style.height = "auto";
|
|
978
|
+
el.style.height = el.scrollHeight + "px";
|
|
979
|
+
if (scrollContainer) scrollContainer.scrollTop = scrollTop;
|
|
980
|
+
}
|
|
981
|
+
|
|
767
982
|
// src/board/client/detailPanel.ts
|
|
768
983
|
var detailTaskId = null;
|
|
769
984
|
var lastTab = "details";
|
|
@@ -802,10 +1017,7 @@
|
|
|
802
1017
|
const pane = document.getElementById("detail-tab-content-comments");
|
|
803
1018
|
if (!pane) return;
|
|
804
1019
|
try {
|
|
805
|
-
const
|
|
806
|
-
if (!res.ok) throw new Error("Server error");
|
|
807
|
-
const data = await res.json();
|
|
808
|
-
const comments = data.comments || [];
|
|
1020
|
+
const comments = await fetchComments(taskId);
|
|
809
1021
|
if (tabBtn) tabBtn.textContent = "Comments (" + comments.length + ")";
|
|
810
1022
|
renderComments(taskId, comments);
|
|
811
1023
|
} catch (err) {
|
|
@@ -813,38 +1025,6 @@
|
|
|
813
1025
|
if (pane) pane.innerHTML = '<div style="padding:20px;font-size:12px;color:#94a3b8;">Failed to load comments</div>';
|
|
814
1026
|
}
|
|
815
1027
|
}
|
|
816
|
-
function renderCommentItemHtml(comment, taskId) {
|
|
817
|
-
const authorText = comment.author ? escapeHtmlClient(comment.author) : "Anonymous";
|
|
818
|
-
const dateRel = relativeTime(comment.created_at);
|
|
819
|
-
const dateAbs = escapeHtmlClient(comment.created_at);
|
|
820
|
-
const contentText = escapeHtmlClient(comment.content);
|
|
821
|
-
let html = '<div class="comment-item" data-comment-id="' + comment.id + '">';
|
|
822
|
-
html += '<div class="comment-meta">';
|
|
823
|
-
html += '<span class="comment-author">' + authorText + "</span>";
|
|
824
|
-
html += '<span class="comment-date" title="' + dateAbs + '">' + dateRel + "</span>";
|
|
825
|
-
html += '<span class="comment-actions">';
|
|
826
|
-
html += '<button class="comment-action-btn" title="Edit" data-action="start-comment-edit" data-comment-id="' + comment.id + '">✎</button>';
|
|
827
|
-
html += '<button class="comment-action-btn danger" title="Delete" data-action="delete-comment" data-comment-id="' + comment.id + '" data-task-id="' + taskId + '">🗑</button>';
|
|
828
|
-
html += "</span></div>";
|
|
829
|
-
html += '<div class="comment-content" id="comment-content-' + comment.id + '">' + contentText + "</div>";
|
|
830
|
-
html += '<div id="comment-edit-' + comment.id + '" style="display:none;">';
|
|
831
|
-
html += '<textarea class="comment-edit-area" id="comment-edit-area-' + comment.id + '">' + contentText + "</textarea>";
|
|
832
|
-
html += '<div class="comment-edit-actions">';
|
|
833
|
-
html += '<button class="comment-btn" data-action="save-comment-edit" data-comment-id="' + comment.id + '" data-task-id="' + taskId + '">Save</button>';
|
|
834
|
-
html += '<button class="comment-btn" data-action="cancel-comment-edit" data-comment-id="' + comment.id + '">Cancel</button>';
|
|
835
|
-
html += "</div></div></div>";
|
|
836
|
-
return html;
|
|
837
|
-
}
|
|
838
|
-
function renderAddCommentFormHtml(taskId) {
|
|
839
|
-
let html = '<button class="add-comment-trigger" id="add-comment-trigger" data-action="open-add-comment">+ Add comment...</button>';
|
|
840
|
-
html += '<div class="add-comment-form" id="add-comment-form">';
|
|
841
|
-
html += '<textarea class="add-comment-textarea" id="add-comment-text" placeholder="Write a comment..."></textarea>';
|
|
842
|
-
html += "<div>";
|
|
843
|
-
html += '<button class="add-comment-submit" data-action="submit-comment" data-task-id="' + taskId + '">Add Comment</button>';
|
|
844
|
-
html += '<button class="add-comment-cancel" data-action="close-add-comment">Cancel</button>';
|
|
845
|
-
html += "</div></div>";
|
|
846
|
-
return html;
|
|
847
|
-
}
|
|
848
1028
|
function renderComments(taskId, comments) {
|
|
849
1029
|
const pane = document.getElementById("detail-tab-content-comments");
|
|
850
1030
|
if (!pane) return;
|
|
@@ -903,12 +1083,7 @@
|
|
|
903
1083
|
return;
|
|
904
1084
|
}
|
|
905
1085
|
try {
|
|
906
|
-
|
|
907
|
-
method: "PATCH",
|
|
908
|
-
headers: { "Content-Type": "application/json" },
|
|
909
|
-
body: JSON.stringify({ content })
|
|
910
|
-
});
|
|
911
|
-
if (!res.ok) throw new Error("Server error");
|
|
1086
|
+
await patchComment(commentId, content);
|
|
912
1087
|
await loadComments(taskId);
|
|
913
1088
|
} catch {
|
|
914
1089
|
showToast("Failed to update comment");
|
|
@@ -917,8 +1092,7 @@
|
|
|
917
1092
|
async function deleteComment(commentId, taskId) {
|
|
918
1093
|
if (!confirm("Delete this comment?")) return;
|
|
919
1094
|
try {
|
|
920
|
-
|
|
921
|
-
if (!res.ok) throw new Error("Server error");
|
|
1095
|
+
await deleteCommentRequest(commentId);
|
|
922
1096
|
await loadComments(taskId);
|
|
923
1097
|
} catch {
|
|
924
1098
|
showToast("Failed to delete comment");
|
|
@@ -933,12 +1107,7 @@
|
|
|
933
1107
|
return;
|
|
934
1108
|
}
|
|
935
1109
|
try {
|
|
936
|
-
|
|
937
|
-
method: "POST",
|
|
938
|
-
headers: { "Content-Type": "application/json" },
|
|
939
|
-
body: JSON.stringify({ content })
|
|
940
|
-
});
|
|
941
|
-
if (!res.ok) throw new Error("Server error");
|
|
1110
|
+
await postComment(taskId, content);
|
|
942
1111
|
await loadComments(taskId);
|
|
943
1112
|
} catch {
|
|
944
1113
|
showToast("Failed to add comment");
|
|
@@ -977,103 +1146,6 @@
|
|
|
977
1146
|
const taskId = target.dataset.taskId ? Number(target.dataset.taskId) : NaN;
|
|
978
1147
|
dispatchCommentAction(action, commentId, taskId);
|
|
979
1148
|
}
|
|
980
|
-
function renderStatusField(currentStatus, allStatuses, statusLabels) {
|
|
981
|
-
let html = '<div class="detail-field">';
|
|
982
|
-
html += '<div class="detail-field-label">Status</div>';
|
|
983
|
-
html += '<select id="detail-edit-status" class="detail-edit-select">';
|
|
984
|
-
allStatuses.forEach((s) => {
|
|
985
|
-
const selected = s === currentStatus ? " selected" : "";
|
|
986
|
-
html += '<option value="' + s + '"' + selected + ">" + statusLabels[s] + "</option>";
|
|
987
|
-
});
|
|
988
|
-
html += "</select></div>";
|
|
989
|
-
return html;
|
|
990
|
-
}
|
|
991
|
-
function renderPriorityField(currentPriority, allPriorities) {
|
|
992
|
-
let html = '<div class="detail-field">';
|
|
993
|
-
html += '<div class="detail-field-label">Priority</div>';
|
|
994
|
-
html += '<select id="detail-edit-priority" class="detail-edit-select">';
|
|
995
|
-
html += '<option value="">None</option>';
|
|
996
|
-
allPriorities.forEach((p) => {
|
|
997
|
-
const selected = currentPriority === p ? " selected" : "";
|
|
998
|
-
html += '<option value="' + p + '"' + selected + ">" + p.charAt(0).toUpperCase() + p.slice(1) + "</option>";
|
|
999
|
-
});
|
|
1000
|
-
html += "</select></div>";
|
|
1001
|
-
return html;
|
|
1002
|
-
}
|
|
1003
|
-
function renderRelationsHtml(parent, blockedBy, blocking) {
|
|
1004
|
-
let html = '<div class="detail-relations">';
|
|
1005
|
-
if (parent) {
|
|
1006
|
-
html += '<div class="detail-relation-row">';
|
|
1007
|
-
html += '<span class="detail-relation-label">Parent</span>';
|
|
1008
|
-
html += '<div class="detail-relation-ids"><span class="detail-relation-id">#' + parent.id + " " + escapeHtmlClient(parent.title) + "</span></div>";
|
|
1009
|
-
html += "</div>";
|
|
1010
|
-
}
|
|
1011
|
-
if (blockedBy.length > 0) {
|
|
1012
|
-
html += '<div class="detail-relation-row"><span class="detail-relation-label">Blocked by</span>';
|
|
1013
|
-
html += '<div class="detail-relation-ids">';
|
|
1014
|
-
blockedBy.forEach((t) => {
|
|
1015
|
-
html += '<span class="detail-relation-id">#' + t.id + "</span>";
|
|
1016
|
-
});
|
|
1017
|
-
html += "</div></div>";
|
|
1018
|
-
}
|
|
1019
|
-
if (blocking.length > 0) {
|
|
1020
|
-
html += '<div class="detail-relation-row"><span class="detail-relation-label">Blocking</span>';
|
|
1021
|
-
html += '<div class="detail-relation-ids">';
|
|
1022
|
-
blocking.forEach((t) => {
|
|
1023
|
-
html += '<span class="detail-relation-id">#' + t.id + "</span>";
|
|
1024
|
-
});
|
|
1025
|
-
html += "</div></div>";
|
|
1026
|
-
}
|
|
1027
|
-
html += "</div>";
|
|
1028
|
-
return html;
|
|
1029
|
-
}
|
|
1030
|
-
function autoResizeTextarea(el) {
|
|
1031
|
-
el.style.height = "auto";
|
|
1032
|
-
el.style.height = el.scrollHeight + "px";
|
|
1033
|
-
}
|
|
1034
|
-
function renderMetadataTable(metadata) {
|
|
1035
|
-
const otherMeta = metadata.filter((m) => m.key !== "priority");
|
|
1036
|
-
if (otherMeta.length === 0) return "";
|
|
1037
|
-
let html = '<div class="detail-field"><div class="detail-field-label">Metadata</div>';
|
|
1038
|
-
html += '<table class="detail-meta-table">';
|
|
1039
|
-
otherMeta.forEach((m) => {
|
|
1040
|
-
html += "<tr><td>" + escapeHtmlClient(m.key) + "</td><td>" + escapeHtmlClient(m.value) + "</td></tr>";
|
|
1041
|
-
});
|
|
1042
|
-
html += "</table></div>";
|
|
1043
|
-
return html;
|
|
1044
|
-
}
|
|
1045
|
-
function renderEditableTextFields(task) {
|
|
1046
|
-
let html = '<div class="detail-field"><div class="detail-field-label">Title</div>';
|
|
1047
|
-
html += '<input id="detail-edit-title" class="detail-edit-input" type="text" value="' + escapeHtmlClient(task.title) + '">';
|
|
1048
|
-
html += "</div>";
|
|
1049
|
-
html += '<div class="detail-field description-field-wrapper"><div class="detail-field-label">Description</div>';
|
|
1050
|
-
html += '<textarea id="detail-edit-body" class="detail-edit-textarea">' + escapeHtmlClient(task.body || "") + "</textarea>";
|
|
1051
|
-
html += "</div>";
|
|
1052
|
-
return html;
|
|
1053
|
-
}
|
|
1054
|
-
function renderDetailPanelHtml(data) {
|
|
1055
|
-
const task = data.task;
|
|
1056
|
-
const metadata = data.metadata || [];
|
|
1057
|
-
const blockedBy = data.blockedBy || [];
|
|
1058
|
-
const blocking = data.blocking || [];
|
|
1059
|
-
const parent = data.parent || null;
|
|
1060
|
-
const win = window;
|
|
1061
|
-
const allStatuses = win.allStatuses;
|
|
1062
|
-
const statusLabels = win.statusLabels;
|
|
1063
|
-
const allPriorities = win.allPriorities;
|
|
1064
|
-
let html = "";
|
|
1065
|
-
html += renderStatusField(task.status, allStatuses, statusLabels);
|
|
1066
|
-
html += renderPriorityField(task.priority, allPriorities);
|
|
1067
|
-
html += '<div class="detail-field"><div class="detail-field-label">Tags</div>';
|
|
1068
|
-
html += '<div id="detail-tags-container"></div></div>';
|
|
1069
|
-
const hasRelations = parent || blockedBy.length > 0 || blocking.length > 0;
|
|
1070
|
-
if (hasRelations) {
|
|
1071
|
-
html += renderRelationsHtml(parent, blockedBy, blocking);
|
|
1072
|
-
}
|
|
1073
|
-
html += renderMetadataTable(metadata);
|
|
1074
|
-
html += renderEditableTextFields(task);
|
|
1075
|
-
return html;
|
|
1076
|
-
}
|
|
1077
1149
|
function renderDetailPanel(data) {
|
|
1078
1150
|
document.getElementById("detail-panel-update-warning")?.remove();
|
|
1079
1151
|
const detailPanelTitle = document.getElementById("detail-panel-title");
|
|
@@ -1093,12 +1165,15 @@
|
|
|
1093
1165
|
}
|
|
1094
1166
|
const textarea = document.getElementById("detail-edit-body");
|
|
1095
1167
|
if (textarea) {
|
|
1096
|
-
|
|
1168
|
+
requestAnimationFrame(() => {
|
|
1169
|
+
autoResizeTextarea(textarea);
|
|
1170
|
+
});
|
|
1097
1171
|
textarea.addEventListener("input", () => {
|
|
1098
1172
|
autoResizeTextarea(textarea);
|
|
1099
1173
|
});
|
|
1100
1174
|
}
|
|
1101
|
-
|
|
1175
|
+
renderTagsSection([...tags]);
|
|
1176
|
+
loadAllTags().catch((err) => {
|
|
1102
1177
|
console.error("[agkan] renderDetailPanel tags failed", err);
|
|
1103
1178
|
});
|
|
1104
1179
|
loadComments(task.id);
|
|
@@ -1108,9 +1183,7 @@
|
|
|
1108
1183
|
const detailPanel = document.getElementById("detail-panel");
|
|
1109
1184
|
const PANEL_DEFAULT_WIDTH2 = 400;
|
|
1110
1185
|
try {
|
|
1111
|
-
const
|
|
1112
|
-
if (!res.ok) throw new Error("Server error");
|
|
1113
|
-
const data = await res.json();
|
|
1186
|
+
const data = await fetchTaskDetail(taskId);
|
|
1114
1187
|
renderDetailPanel(data);
|
|
1115
1188
|
setActiveCard(Number(taskId));
|
|
1116
1189
|
if (!detailPanel.classList.contains("open")) {
|
|
@@ -1146,9 +1219,8 @@
|
|
|
1146
1219
|
reloadBtn.style.cssText = "background: none; border: none; cursor: pointer; font-size: 1.1em; color: red; padding: 0 2px; line-height: 1; flex-shrink: 0;";
|
|
1147
1220
|
reloadBtn.addEventListener("click", async () => {
|
|
1148
1221
|
try {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
const taskData = await taskRes.json();
|
|
1222
|
+
if (detailTaskId !== null) {
|
|
1223
|
+
const taskData = await fetchTaskDetail(detailTaskId);
|
|
1152
1224
|
renderDetailPanel(taskData);
|
|
1153
1225
|
}
|
|
1154
1226
|
} catch {
|
|
@@ -1173,24 +1245,13 @@
|
|
|
1173
1245
|
priority: priorityEl ? priorityEl.value || null : null
|
|
1174
1246
|
};
|
|
1175
1247
|
}
|
|
1176
|
-
async function patchAndReloadDetail(taskId, fields) {
|
|
1177
|
-
const res = await fetch("/api/tasks/" + taskId, {
|
|
1178
|
-
method: "PATCH",
|
|
1179
|
-
headers: { "Content-Type": "application/json" },
|
|
1180
|
-
body: JSON.stringify(fields)
|
|
1181
|
-
});
|
|
1182
|
-
if (!res.ok) throw new Error("Server error");
|
|
1183
|
-
const getRes = await fetch("/api/tasks/" + taskId);
|
|
1184
|
-
if (!getRes.ok) throw new Error("Failed to fetch updated task");
|
|
1185
|
-
const data = await getRes.json();
|
|
1186
|
-
renderDetailPanel(data);
|
|
1187
|
-
}
|
|
1188
1248
|
async function saveDetailTask() {
|
|
1189
1249
|
if (detailTaskId === null) return;
|
|
1190
1250
|
const fields = collectEditedTaskFields();
|
|
1191
1251
|
if (!fields) return;
|
|
1192
1252
|
try {
|
|
1193
|
-
await
|
|
1253
|
+
const data = await patchTask(detailTaskId, fields);
|
|
1254
|
+
renderDetailPanel(data);
|
|
1194
1255
|
showToast("Task saved successfully");
|
|
1195
1256
|
await syncTimestampAfterSave();
|
|
1196
1257
|
refreshBoardCards();
|
|
@@ -1198,32 +1259,8 @@
|
|
|
1198
1259
|
showToast("Failed to update task");
|
|
1199
1260
|
}
|
|
1200
1261
|
}
|
|
1201
|
-
async function syncTimestampAfterSave() {
|
|
1202
|
-
try {
|
|
1203
|
-
const tsRes = await fetch("/api/board/updated-at");
|
|
1204
|
-
if (tsRes.ok) {
|
|
1205
|
-
const tsData = await tsRes.json();
|
|
1206
|
-
setLastUpdatedAt(tsData.updatedAt);
|
|
1207
|
-
}
|
|
1208
|
-
} catch {
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
var PANEL_MIN_WIDTH = 280;
|
|
1212
|
-
var PANEL_MAX_WIDTH = 800;
|
|
1213
|
-
var PANEL_DEFAULT_WIDTH = 400;
|
|
1214
1262
|
async function initPanelWidthFromConfig(detailPanel) {
|
|
1215
|
-
|
|
1216
|
-
try {
|
|
1217
|
-
const res = await fetch("/api/config");
|
|
1218
|
-
if (res.ok) {
|
|
1219
|
-
const data = await res.json();
|
|
1220
|
-
const savedWidth = data && data.board && data.board.detailPaneWidth;
|
|
1221
|
-
if (typeof savedWidth === "number" && savedWidth >= PANEL_MIN_WIDTH && savedWidth <= PANEL_MAX_WIDTH) {
|
|
1222
|
-
targetWidth = savedWidth;
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
} catch {
|
|
1226
|
-
}
|
|
1263
|
+
const targetWidth = await fetchPanelWidthFromConfig();
|
|
1227
1264
|
detailPanel.dataset.preferredWidth = String(targetWidth);
|
|
1228
1265
|
}
|
|
1229
1266
|
function attachResizeMousedown(resizeHandle, detailPanel) {
|
|
@@ -1248,12 +1285,7 @@
|
|
|
1248
1285
|
detailPanel.style.transition = "";
|
|
1249
1286
|
const currentWidth = detailPanel.offsetWidth;
|
|
1250
1287
|
detailPanel.dataset.preferredWidth = String(currentWidth);
|
|
1251
|
-
|
|
1252
|
-
method: "PUT",
|
|
1253
|
-
headers: { "Content-Type": "application/json" },
|
|
1254
|
-
body: JSON.stringify({ board: { detailPaneWidth: currentWidth } })
|
|
1255
|
-
}).catch(function() {
|
|
1256
|
-
});
|
|
1288
|
+
savePanelWidthToConfig(currentWidth);
|
|
1257
1289
|
document.removeEventListener("mousemove", onMouseMove);
|
|
1258
1290
|
document.removeEventListener("mouseup", onMouseUp);
|
|
1259
1291
|
}
|
|
@@ -1266,9 +1298,6 @@
|
|
|
1266
1298
|
initPanelWidthFromConfig(detailPanel);
|
|
1267
1299
|
attachResizeMousedown(resizeHandle, detailPanel);
|
|
1268
1300
|
}
|
|
1269
|
-
function buildDetailPanelHtml() {
|
|
1270
|
-
return '<div class="detail-panel" id="detail-panel"><div class="detail-panel-resize-handle" id="detail-panel-resize-handle"></div><div class="detail-panel-header"><h2 id="detail-panel-title">Task Detail</h2><button class="detail-panel-close" id="detail-panel-close" title="Close">×</button></div><div class="detail-tabs" id="detail-tabs"><button class="detail-tab active" data-tab="details">Details</button><button class="detail-tab" data-tab="comments" id="detail-tab-comments">Comments</button></div><div class="detail-panel-body" id="detail-panel-body"><div class="detail-tab-content active" id="detail-tab-content-details"></div><div class="detail-tab-content" id="detail-tab-content-comments"></div></div><div class="detail-panel-footer" id="detail-panel-footer"><button id="detail-save-btn">Save</button></div></div>';
|
|
1271
|
-
}
|
|
1272
1301
|
function initDetailPanel() {
|
|
1273
1302
|
const boardContainer = document.querySelector(".board-container");
|
|
1274
1303
|
boardContainer.insertAdjacentHTML("beforeend", buildDetailPanelHtml());
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ActiveFilters } from './types';
|
|
2
|
+
export declare function getLastUpdatedAt(): string | null;
|
|
3
|
+
export declare function setLastUpdatedAt(val: string | null): void;
|
|
4
|
+
export declare const activeFilters: ActiveFilters;
|
|
5
|
+
export declare function buildFilterParams(): URLSearchParams;
|
|
6
|
+
export declare function registerDetailPanelCallbacks(callbacks: {
|
|
7
|
+
openTaskDetail: (taskId: string) => Promise<void>;
|
|
8
|
+
renderDetailPanel: (data: unknown) => void;
|
|
9
|
+
showUpdateWarning: () => void;
|
|
10
|
+
getDetailTaskId: () => number | null;
|
|
11
|
+
}): void;
|
|
12
|
+
export declare function refreshBoardCards(): Promise<void>;
|
|
13
|
+
export declare function pollBoardUpdates(): Promise<void>;
|
|
14
|
+
export declare function initBoardPolling(): void;
|
|
15
|
+
//# sourceMappingURL=boardPolling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardPolling.d.ts","sourceRoot":"","sources":["../../../src/board/client/boardPolling.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAIxC,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEzD;AAED,eAAO,MAAM,aAAa,EAAE,aAA4D,CAAC;AAEzF,wBAAgB,iBAAiB,IAAI,eAAe,CAYnD;AASD,wBAAgB,4BAA4B,CAAC,SAAS,EAAE;IACtD,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,iBAAiB,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,eAAe,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CACtC,GAAG,IAAI,CAKP;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA2DvD;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqBtD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}
|