agkan 2.14.1 → 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.
Files changed (61) hide show
  1. package/dist/board/boardStyles.d.ts +1 -1
  2. package/dist/board/boardStyles.d.ts.map +1 -1
  3. package/dist/board/boardStyles.js +1 -1
  4. package/dist/board/client/addTaskModal.d.ts +2 -0
  5. package/dist/board/client/addTaskModal.d.ts.map +1 -0
  6. package/dist/board/client/addTaskModal.js +64 -0
  7. package/dist/board/client/addTaskModal.js.map +1 -0
  8. package/dist/board/client/autoScroll.d.ts +4 -0
  9. package/dist/board/client/autoScroll.d.ts.map +1 -0
  10. package/dist/board/client/autoScroll.js +59 -0
  11. package/dist/board/client/autoScroll.js.map +1 -0
  12. package/dist/board/client/board.js +228 -201
  13. package/dist/board/client/boardPolling.d.ts +15 -0
  14. package/dist/board/client/boardPolling.d.ts.map +1 -0
  15. package/dist/board/client/boardPolling.js +144 -0
  16. package/dist/board/client/boardPolling.js.map +1 -0
  17. package/dist/board/client/burgerMenu.d.ts +2 -0
  18. package/dist/board/client/burgerMenu.d.ts.map +1 -0
  19. package/dist/board/client/burgerMenu.js +80 -0
  20. package/dist/board/client/burgerMenu.js.map +1 -0
  21. package/dist/board/client/contextMenu.d.ts +2 -0
  22. package/dist/board/client/contextMenu.d.ts.map +1 -0
  23. package/dist/board/client/contextMenu.js +52 -0
  24. package/dist/board/client/contextMenu.js.map +1 -0
  25. package/dist/board/client/detailPanel.d.ts +8 -0
  26. package/dist/board/client/detailPanel.d.ts.map +1 -0
  27. package/dist/board/client/detailPanel.js +565 -0
  28. package/dist/board/client/detailPanel.js.map +1 -0
  29. package/dist/board/client/dragDrop.d.ts +6 -0
  30. package/dist/board/client/dragDrop.d.ts.map +1 -0
  31. package/dist/board/client/dragDrop.js +82 -0
  32. package/dist/board/client/dragDrop.js.map +1 -0
  33. package/dist/board/client/filters.d.ts +6 -0
  34. package/dist/board/client/filters.d.ts.map +1 -0
  35. package/dist/board/client/filters.js +167 -0
  36. package/dist/board/client/filters.js.map +1 -0
  37. package/dist/board/client/main.d.ts +2 -0
  38. package/dist/board/client/main.d.ts.map +1 -0
  39. package/dist/board/client/main.js +20 -0
  40. package/dist/board/client/main.js.map +1 -0
  41. package/dist/board/client/tags.d.ts +6 -0
  42. package/dist/board/client/tags.d.ts.map +1 -0
  43. package/dist/board/client/tags.js +198 -0
  44. package/dist/board/client/tags.js.map +1 -0
  45. package/dist/board/client/types.d.ts +48 -0
  46. package/dist/board/client/types.d.ts.map +1 -0
  47. package/dist/board/client/types.js +4 -0
  48. package/dist/board/client/types.js.map +1 -0
  49. package/dist/board/client/utils.d.ts +4 -0
  50. package/dist/board/client/utils.d.ts.map +1 -0
  51. package/dist/board/client/utils.js +44 -0
  52. package/dist/board/client/utils.js.map +1 -0
  53. package/package.json +1 -1
  54. package/dist/services/ProcessService.d.ts +0 -54
  55. package/dist/services/ProcessService.d.ts.map +0 -1
  56. package/dist/services/ProcessService.js +0 -147
  57. package/dist/services/ProcessService.js.map +0 -1
  58. package/dist/services/TmuxService.d.ts +0 -2
  59. package/dist/services/TmuxService.d.ts.map +0 -1
  60. package/dist/services/TmuxService.js +0 -7
  61. 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 + '">&#9998;</button>';
857
+ html += '<button class="comment-action-btn danger" title="Delete" data-action="delete-comment" data-comment-id="' + comment.id + '" data-task-id="' + taskId + '">&#128465;</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">&times;</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 res = await fetch("/api/tasks/" + taskId + "/comments");
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 + '">&#9998;</button>';
827
- html += '<button class="comment-action-btn danger" title="Delete" data-action="delete-comment" data-comment-id="' + comment.id + '" data-task-id="' + taskId + '">&#128465;</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
- const res = await fetch("/api/comments/" + commentId, {
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
- const res = await fetch("/api/comments/" + commentId, { method: "DELETE" });
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
- const res = await fetch("/api/tasks/" + taskId + "/comments", {
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");
@@ -1100,7 +1172,8 @@
1100
1172
  autoResizeTextarea(textarea);
1101
1173
  });
1102
1174
  }
1103
- loadAllTags().then(() => renderTagsSection([...tags])).catch((err) => {
1175
+ renderTagsSection([...tags]);
1176
+ loadAllTags().catch((err) => {
1104
1177
  console.error("[agkan] renderDetailPanel tags failed", err);
1105
1178
  });
1106
1179
  loadComments(task.id);
@@ -1110,9 +1183,7 @@
1110
1183
  const detailPanel = document.getElementById("detail-panel");
1111
1184
  const PANEL_DEFAULT_WIDTH2 = 400;
1112
1185
  try {
1113
- const res = await fetch("/api/tasks/" + taskId);
1114
- if (!res.ok) throw new Error("Server error");
1115
- const data = await res.json();
1186
+ const data = await fetchTaskDetail(taskId);
1116
1187
  renderDetailPanel(data);
1117
1188
  setActiveCard(Number(taskId));
1118
1189
  if (!detailPanel.classList.contains("open")) {
@@ -1148,9 +1219,8 @@
1148
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;";
1149
1220
  reloadBtn.addEventListener("click", async () => {
1150
1221
  try {
1151
- const taskRes = await fetch("/api/tasks/" + detailTaskId);
1152
- if (taskRes.ok) {
1153
- const taskData = await taskRes.json();
1222
+ if (detailTaskId !== null) {
1223
+ const taskData = await fetchTaskDetail(detailTaskId);
1154
1224
  renderDetailPanel(taskData);
1155
1225
  }
1156
1226
  } catch {
@@ -1175,24 +1245,13 @@
1175
1245
  priority: priorityEl ? priorityEl.value || null : null
1176
1246
  };
1177
1247
  }
1178
- async function patchAndReloadDetail(taskId, fields) {
1179
- const res = await fetch("/api/tasks/" + taskId, {
1180
- method: "PATCH",
1181
- headers: { "Content-Type": "application/json" },
1182
- body: JSON.stringify(fields)
1183
- });
1184
- if (!res.ok) throw new Error("Server error");
1185
- const getRes = await fetch("/api/tasks/" + taskId);
1186
- if (!getRes.ok) throw new Error("Failed to fetch updated task");
1187
- const data = await getRes.json();
1188
- renderDetailPanel(data);
1189
- }
1190
1248
  async function saveDetailTask() {
1191
1249
  if (detailTaskId === null) return;
1192
1250
  const fields = collectEditedTaskFields();
1193
1251
  if (!fields) return;
1194
1252
  try {
1195
- await patchAndReloadDetail(detailTaskId, fields);
1253
+ const data = await patchTask(detailTaskId, fields);
1254
+ renderDetailPanel(data);
1196
1255
  showToast("Task saved successfully");
1197
1256
  await syncTimestampAfterSave();
1198
1257
  refreshBoardCards();
@@ -1200,32 +1259,8 @@
1200
1259
  showToast("Failed to update task");
1201
1260
  }
1202
1261
  }
1203
- async function syncTimestampAfterSave() {
1204
- try {
1205
- const tsRes = await fetch("/api/board/updated-at");
1206
- if (tsRes.ok) {
1207
- const tsData = await tsRes.json();
1208
- setLastUpdatedAt(tsData.updatedAt);
1209
- }
1210
- } catch {
1211
- }
1212
- }
1213
- var PANEL_MIN_WIDTH = 280;
1214
- var PANEL_MAX_WIDTH = 800;
1215
- var PANEL_DEFAULT_WIDTH = 400;
1216
1262
  async function initPanelWidthFromConfig(detailPanel) {
1217
- let targetWidth = PANEL_DEFAULT_WIDTH;
1218
- try {
1219
- const res = await fetch("/api/config");
1220
- if (res.ok) {
1221
- const data = await res.json();
1222
- const savedWidth = data && data.board && data.board.detailPaneWidth;
1223
- if (typeof savedWidth === "number" && savedWidth >= PANEL_MIN_WIDTH && savedWidth <= PANEL_MAX_WIDTH) {
1224
- targetWidth = savedWidth;
1225
- }
1226
- }
1227
- } catch {
1228
- }
1263
+ const targetWidth = await fetchPanelWidthFromConfig();
1229
1264
  detailPanel.dataset.preferredWidth = String(targetWidth);
1230
1265
  }
1231
1266
  function attachResizeMousedown(resizeHandle, detailPanel) {
@@ -1250,12 +1285,7 @@
1250
1285
  detailPanel.style.transition = "";
1251
1286
  const currentWidth = detailPanel.offsetWidth;
1252
1287
  detailPanel.dataset.preferredWidth = String(currentWidth);
1253
- fetch("/api/config", {
1254
- method: "PUT",
1255
- headers: { "Content-Type": "application/json" },
1256
- body: JSON.stringify({ board: { detailPaneWidth: currentWidth } })
1257
- }).catch(function() {
1258
- });
1288
+ savePanelWidthToConfig(currentWidth);
1259
1289
  document.removeEventListener("mousemove", onMouseMove);
1260
1290
  document.removeEventListener("mouseup", onMouseUp);
1261
1291
  }
@@ -1268,9 +1298,6 @@
1268
1298
  initPanelWidthFromConfig(detailPanel);
1269
1299
  attachResizeMousedown(resizeHandle, detailPanel);
1270
1300
  }
1271
- function buildDetailPanelHtml() {
1272
- 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">&times;</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>';
1273
- }
1274
1301
  function initDetailPanel() {
1275
1302
  const boardContainer = document.querySelector(".board-container");
1276
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"}