add/remove tags to/from logs
authorPhiTux <redacted>
Mon, 10 Mar 2025 18:43:56 +0000 (19:43 +0100)
committerPhiTux <redacted>
Mon, 10 Mar 2025 18:43:56 +0000 (19:43 +0100)
backend/server/routers/logs.py
frontend/src/lib/TagModal.svelte
frontend/src/routes/write/+page.svelte

index ba1b6a7634c2622c51c95b1f43854c83dc9fbe32..7f017b57000d079e6e92e77025d38039b5b4d478 100644 (file)
@@ -88,8 +88,10 @@ async def getLog(date: str, cookie = Depends(users.isLoggedIn)):
 
     content:dict = fileHandling.getDay(cookie["user_id"], year, month)
     
+    dummy = {"text": "", "date_written": "", "files": [], "tags": []}
+
     if "days" not in content.keys():
-        return {"text": "", "date_written": ""}
+        return dummy
     
     for dayLog in content["days"]:
         if dayLog["day"] == day:
@@ -103,9 +105,9 @@ async def getLog(date: str, cookie = Depends(users.isLoggedIn)):
                 for file in dayLog["files"]:
                     file["filename"] = security.decrypt_text(file["enc_filename"], enc_key)
                     file["type"] = security.decrypt_text(file["enc_type"], enc_key)
-            return {"text": text, "date_written": date_written, "files": dayLog.get("files", [])}
+            return {"text": text, "date_written": date_written, "files": dayLog.get("files", []), "tags": dayLog.get("tags", [])}
 
-    return {"text": "", "date_written": ""}
+    return dummy
 
 def get_start_index(text, index):
     # find a whitespace two places before the index
@@ -351,8 +353,8 @@ class NewTag(BaseModel):
     name: str
     color: str
 
-@router.post("/saveTag")
-async def saveTag(tag: NewTag, cookie = Depends(users.isLoggedIn)):
+@router.post("/saveNewTag")
+async def saveNewTag(tag: NewTag, cookie = Depends(users.isLoggedIn)):
     enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"])
     
     content:dict = fileHandling.getTags(cookie["user_id"])
@@ -372,4 +374,56 @@ async def saveTag(tag: NewTag, cookie = Depends(users.isLoggedIn)):
     if not fileHandling.writeTags(cookie["user_id"], content):
         return {"success": False}
     else:
-        return {"success": True}
\ No newline at end of file
+        return {"success": True}
+    
+
+class AddTagToLog(BaseModel):
+    day: int
+    month: int
+    year: int
+    tag_id: int
+
+@router.post("/addTagToLog")
+async def addTagToLog(data: AddTagToLog, cookie = Depends(users.isLoggedIn)):
+    content:dict = fileHandling.getDay(cookie["user_id"], data.year, data.month)
+    if "days" not in content.keys():
+        content["days"] = []
+    
+    dayFound = False
+    for dayLog in content["days"]:
+        if dayLog["day"] != data.day:
+            continue
+        dayFound = True
+        if not "tags" in dayLog.keys():
+            dayLog["tags"] = []
+        if data.tag_id in dayLog["tags"]:
+            # fail silently
+            return {"success": True}
+        dayLog["tags"].append(data.tag_id)
+        break
+    
+    if not dayFound:
+        content["days"].append({"day": data.day, "tags": [data.tag_id]})
+    
+    if not fileHandling.writeDay(cookie["user_id"], data.year, data.month, content):
+        raise HTTPException(status_code=500, detail="Failed to write tag - error writing log")
+    return {"success": True}
+
+@router.post("/removeTagFromLog")
+async def removeTagFromLog(data: AddTagToLog, cookie = Depends(users.isLoggedIn)):
+    content:dict = fileHandling.getDay(cookie["user_id"], data.year, data.month)
+    if "days" not in content.keys():
+        raise HTTPException(status_code=500, detail="Day not found - json error")
+    
+    for dayLog in content["days"]:
+        if dayLog["day"] != data.day:
+            continue
+        if not "tags" in dayLog.keys():
+            raise HTTPException(status_code=500, detail="Failed to remove tag - not found in log")
+        if not data.tag_id in dayLog["tags"]:
+            raise HTTPException(status_code=500, detail="Failed to remove tag - not found in log")
+        dayLog["tags"].remove(data.tag_id)
+        if not fileHandling.writeDay(cookie["user_id"], data.year, data.month, content):
+            raise HTTPException(status_code=500, detail="Failed to remove tag - error writing log")
+        return {"success": True}
+    
\ No newline at end of file
index f3a6bf4cde840779f0fa11f27dee44ab3e511e26..a6dfec8eeef57713f1326ba960d9df7fe684be3c 100644 (file)
                                        <div class="col-8">
                                                <button
                                                        class="btn btn-outline-secondary mb-2 {pickerShown ? 'active' : ''}"
-                                                       onclick={() => togglePicker()}>😀 Emoji auswählen</button
+                                                       onclick={() => togglePicker()}
                                                >
+                                                       {#if editTag.icon === ''}
+                                                               ðŸ˜€
+                                                       {:else}
+                                                               {editTag.icon}
+                                                       {/if} Emoji auswählen
+                                               </button>
                                                <!-- <em>(freiwillig)</em> -->
                                                <div class="tooltip" role="tooltip">
                                                        <emoji-picker class="emojiPicker" onemoji-click={(ev) => emojiSelected(ev)}
index 4f02b455f59e09d1af76711b0047e230de1388e2..b552e5712725e72b195f2a4d5a43267e586a3c83 100644 (file)
 
                        currentLog = response.data.text;
                        filesOfDay = response.data.files;
+                       selectedTags = response.data.tags;
+
                        savedLog = currentLog;
 
                        tinyMDE.setContent(currentLog);
 
        // show the correct tags in the dropdown
        $effect(() => {
+               if (tags.length === 0) {
+                       filteredTags = [];
+                       return;
+               }
+
                // exclude already selected tags
                let tagsWithoutSelected = tags.filter(
-                       (tag) => !selectedTags.find((selectedTag) => selectedTag.id === tag.id)
+                       (tag) => !selectedTags.find((selectedTag) => selectedTag === tag.id)
                );
 
                if (searchTab === '') {
                }, 0);
        }
 
+       let showTagLoading = $state(false);
+
        function selectTag(id) {
-               selectedTags = [...selectedTags, tags.find((tag) => tag.id === id)];
+               showTagLoading = true;
+
+               axios
+                       .post(API_URL + '/logs/addTagToLog', {
+                               day: $selectedDate.getDate(),
+                               month: $selectedDate.getMonth() + 1,
+                               year: $selectedDate.getFullYear(),
+                               tag_id: id
+                       })
+                       .then((response) => {
+                               if (response.data.success) {
+                                       selectedTags = [...selectedTags, id];
+                               } else {
+                                       // toast
+                                       const toast = new bootstrap.Toast(document.getElementById('toastErrorAddingTagToDay'));
+                                       toast.show();
+                               }
+                       })
+                       .catch((error) => {
+                               console.error(error);
+                               // toast
+                               const toast = new bootstrap.Toast(document.getElementById('toastErrorAddingTagToDay'));
+                               toast.show();
+                       })
+                       .finally(() => {
+                               showTagLoading = false;
+                       });
+
                searchTab = '';
        }
 
        function removeTag(id) {
-               selectedTags = selectedTags.filter((tag) => tag.id !== id);
+               showTagLoading = true;
+
+               axios
+                       .post(API_URL + '/logs/removeTagFromLog', {
+                               day: $selectedDate.getDate(),
+                               month: $selectedDate.getMonth() + 1,
+                               year: $selectedDate.getFullYear(),
+                               tag_id: id
+                       })
+                       .then((response) => {
+                               if (response.data.success) {
+                                       selectedTags = selectedTags.filter((tag) => tag !== id);
+                               } else {
+                                       // toast
+                                       const toast = new bootstrap.Toast(
+                                               document.getElementById('toastErrorRemovingTagFromDay')
+                                       );
+                                       toast.show();
+                               }
+                       })
+                       .catch((error) => {
+                               console.error(error);
+                               // toast
+                               const toast = new bootstrap.Toast(document.getElementById('toastErrorRemovingTagFromDay'));
+                               toast.show();
+                       })
+                       .finally(() => {
+                               showTagLoading = false;
+                       });
        }
 
        let editTag = $state({});
        function saveNewTag() {
                isSavingNewTag = true;
                axios
-                       .post(API_URL + '/logs/saveTag', {
+                       .post(API_URL + '/logs/saveNewTag', {
                                icon: editTag.icon,
                                name: editTag.name,
                                color: editTag.color
                                        tagModal.close();
                                } else {
                                        // toast
-                                       const toast = new bootstrap.Toast(document.getElementById('toastErrorSavingTag'));
+                                       const toast = new bootstrap.Toast(document.getElementById('toastErrorSavingNewTag'));
                                        toast.show();
                                }
                        })
        <div id="right" class="d-flex flex-column">
                <div class="tags">
                        <div class="d-flex flex-row justify-content-between">
-                               <h3>Tags</h3>
+                               <div class="d-flex flex-row">
+                                       <h3>Tags</h3>
+                                       {#if showTagLoading}
+                                               <div class="spinner-border ms-3" role="status">
+                                                       <span class="visually-hidden">Loading...</span>
+                                               </div>
+                                       {/if}
+                               </div>
                                <!-- svelte-ignore a11y_missing_attribute -->
                                <a
                                        tabindex="-1"
                                </div>
                        {/if}
                        <div class="selectedTags d-flex flex-row flex-wrap">
-                               {#each selectedTags as tag (tag.id)}
-                                       <Tag {tag} {removeTag} isRemovable="true" />
-                               {/each}
+                               {#if tags.length !== 0}
+                                       {#each selectedTags as tag_id (tag_id)}
+                                               <div transition:slide={{ axis: 'x' }}>
+                                                       <Tag tag={tags.find((tag) => tag.id === tag_id)} {removeTag} isRemovable="true" />
+                                               </div>
+                                       {/each}
+                               {/if}
                        </div>
                </div>
 
 
        <div class="toast-container position-fixed bottom-0 end-0 p-3">
                <div
-                       id="toastErrorSavingTag"
+                       id="toastErrorRemovingTagFromDay"
+                       class="toast align-items-center text-bg-danger"
+                       role="alert"
+                       aria-live="assertive"
+                       aria-atomic="true"
+               >
+                       <div class="d-flex">
+                               <div class="toast-body">Fehler beim Enfternen des Tags!</div>
+                       </div>
+               </div>
+
+               <div
+                       id="toastErrorAddingTagToDay"
+                       class="toast align-items-center text-bg-danger"
+                       role="alert"
+                       aria-live="assertive"
+                       aria-atomic="true"
+               >
+                       <div class="d-flex">
+                               <div class="toast-body">Fehler beim Hinzufügen des Tags zum ausgewählten Datum!</div>
+                       </div>
+               </div>
+
+               <div
+                       id="toastErrorSavingNewTag"
                        class="toast align-items-center text-bg-danger"
                        role="alert"
                        aria-live="assertive"
git clone https://git.99rst.org/PROJECT