From 5cf6b59377f823de6ce9e6fd690e75384079bd48 Mon Sep 17 00:00:00 2001 From: Sandro Date: Tue, 27 Oct 2026 21:09:25 +0100 Subject: [PATCH] feat(tasks): refine enhanced task linking, outliner badge positioning, completion sync and unlink behavior --- frontend/src/features/triage/TasksView.tsx | 18 ++- .../src/features/workspace/OutlinerBlock.tsx | 103 +++++++++++------- frontend/src/lib/api.ts | 6 + internal/api/server.go | 12 +- internal/storage/sqlite.go | 60 +++++++--- 5 files changed, 139 insertions(+), 60 deletions(-) diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx index a26d6b1..b785334 100644 --- a/frontend/src/features/triage/TasksView.tsx +++ b/frontend/src/features/triage/TasksView.tsx @@ -2196,8 +2196,24 @@ export const TasksView: React.FC = ({ )} - {/* Badges: Priority, Status, Tags */} + {/* Badges: In Notiz, Priority, Status, Tags */}
e.stopPropagation()}> + {(task.convertedType === 'document_block' || task.docId) && ( + + )} {task.priority === 'urgent' && ( - ) : canEdit ? ( + + )} + + {canEdit && ( + {!block.taskId ? ( + + ) : ( + + )}
)} - ) : null} + )} )} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index d264ce3..8746a1e 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -664,6 +664,12 @@ export const api = { }); }, + async unlinkEnhancedTask(taskId: string): Promise<{ unlinked: boolean }> { + return request<{ unlinked: boolean }>(`/api/v1/tasks/${encodeURIComponent(taskId)}/unlink`, { + method: 'POST', + }); + }, + // --- DOCUMENT SHARING --- async getDocShares(docId: string): Promise { const res = await request(`/api/v1/documents/${docId}/shares`); diff --git a/internal/api/server.go b/internal/api/server.go index 637948e..253cd93 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -97,6 +97,7 @@ func (s *Server) Router() http.Handler { r.Get("/api/v1/triage/deck", s.handleGetTriageDeck) r.Patch("/api/v1/triage/{id}", s.handleTriageAction) r.Get("/api/v1/tasks", s.handleGetTasks) + r.Post("/api/v1/tasks/{id}/unlink", s.handleUnlinkEnhancedTask) r.Post("/api/v1/tasks/reorder", s.handleReorderTasks) r.Post("/api/v1/task-lists/{id}/tasks/reorder", s.handleReorderTasks) @@ -842,13 +843,18 @@ func (s *Server) handleUpdateThought(w http.ResponseWriter, r *http.Request) { writeError(w, http.StatusBadRequest, err.Error()) return } - if req.IsCompleted != nil { - s.store.SyncDocBlockAndTask(userID, thoughtID, *req.IsCompleted) - } + s.store.SyncDocBlockAndTask(userID, thoughtID, updated.IsCompleted) s.wsHub.BroadcastToAll("TASK_UPDATED", updated) writeJSON(w, http.StatusOK, updated) } +func (s *Server) handleUnlinkEnhancedTask(w http.ResponseWriter, r *http.Request) { + userID := auth.GetUserID(r.Context()) + thoughtID := chi.URLParam(r, "id") + s.store.UnlinkTaskFromDocBlock(userID, thoughtID) + writeJSON(w, http.StatusOK, map[string]bool{"unlinked": true}) +} + func (s *Server) handleDeleteThought(w http.ResponseWriter, r *http.Request) { userID := auth.GetUserID(r.Context()) thoughtID := chi.URLParam(r, "id") diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index 1b42af1..c538261 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -2404,6 +2404,29 @@ func (s *Store) UpdateDocument(userID, docID string, title, icon, folderPath *st s.SyncTaskFromBlockCheck(doc.UserID, b.TaskID, b.Checked) } } + + // When a block is deleted from the note, unlink the corresponding task in thoughts without deleting the task + remainingBlockIDs := make(map[string]bool) + for _, b := range doc.Blocks { + remainingBlockIDs[b.ID] = true + } + rows, err := s.db.Query(`SELECT id, converted_ref_id, block_id FROM thoughts WHERE (doc_id = ? OR converted_ref_id LIKE ?) AND converted_type = 'document_block'`, docID, docID+":%") + if err == nil { + for rows.Next() { + var tid, refID, bID string + if err := rows.Scan(&tid, &refID, &bID); err == nil { + bid := bID + if bid == "" && strings.Contains(refID, ":") { + parts := strings.SplitN(refID, ":", 2) + bid = parts[1] + } + if !remainingBlockIDs[bid] { + _, _ = s.db.Exec(`UPDATE thoughts SET converted_type = '', converted_ref_id = '', doc_id = '', block_id = '' WHERE id = ?`, tid) + } + } + } + _ = rows.Close() + } } return doc, nil } @@ -4239,27 +4262,32 @@ func (s *Store) SyncDocBlockAndTask(userID, taskID string, isCompleted bool) { func (s *Store) UnlinkTaskFromDocBlock(userID, taskID string) { t, err := s.GetThoughtByID(userID, taskID) - if err != nil || t == nil || t.ConvertedType != "document_block" || !strings.Contains(t.ConvertedRefID, ":") { + if err != nil || t == nil { return } - parts := strings.SplitN(t.ConvertedRefID, ":", 2) - docID, blockID := parts[0], parts[1] - doc, err := s.GetDocumentByID(userID, docID) - if err != nil || doc == nil { - return + docID := t.DocID + blockID := t.BlockID + if (docID == "" || blockID == "") && strings.Contains(t.ConvertedRefID, ":") { + parts := strings.SplitN(t.ConvertedRefID, ":", 2) + docID, blockID = parts[0], parts[1] } - var updatedBlocks []domain.DocumentBlock - found := false - for _, b := range doc.Blocks { - if b.ID == blockID { - b.TaskID = "" - found = true + if docID != "" { + if doc, err := s.GetDocumentByID(userID, docID); err == nil && doc != nil { + var updatedBlocks []domain.DocumentBlock + found := false + for _, b := range doc.Blocks { + if b.ID == blockID || b.TaskID == taskID { + b.TaskID = "" + found = true + } + updatedBlocks = append(updatedBlocks, b) + } + if found { + _, _ = s.UpdateDocument(userID, docID, nil, nil, nil, &updatedBlocks, nil, nil) + } } - updatedBlocks = append(updatedBlocks, b) - } - if found { - _, _ = s.UpdateDocument(userID, docID, nil, nil, nil, &updatedBlocks, nil, nil) } + _, _ = s.db.Exec(`UPDATE thoughts SET converted_type = '', converted_ref_id = '', doc_id = '', block_id = '' WHERE id = ?`, taskID) } func (s *Store) SyncTaskFromBlockCheck(userID, taskID string, checked bool) {