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.
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 +231 -202
  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");
@@ -1093,12 +1165,15 @@
1093
1165
  }
1094
1166
  const textarea = document.getElementById("detail-edit-body");
1095
1167
  if (textarea) {
1096
- autoResizeTextarea(textarea);
1168
+ requestAnimationFrame(() => {
1169
+ autoResizeTextarea(textarea);
1170
+ });
1097
1171
  textarea.addEventListener("input", () => {
1098
1172
  autoResizeTextarea(textarea);
1099
1173
  });
1100
1174
  }
1101
- loadAllTags().then(() => renderTagsSection([...tags])).catch((err) => {
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 res = await fetch("/api/tasks/" + taskId);
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
- const taskRes = await fetch("/api/tasks/" + detailTaskId);
1150
- if (taskRes.ok) {
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 patchAndReloadDetail(detailTaskId, fields);
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
- let targetWidth = PANEL_DEFAULT_WIDTH;
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
- fetch("/api/config", {
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">&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>';
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"}