added history-menu for text
authorPhiTux <redacted>
Tue, 17 Jun 2025 16:59:07 +0000 (18:59 +0200)
committerPhiTux <redacted>
Tue, 17 Jun 2025 16:59:07 +0000 (18:59 +0200)
backend/server/routers/logs.py
frontend/src/routes/+layout.svelte
frontend/src/routes/write/+page.svelte

index 1b522ce5730874405519b63d0f5c78c56106e071..8b7c0bc4aaf43a06f8c427f400326712d11f557d 100644 (file)
@@ -31,10 +31,12 @@ async def saveLog(log: Log, cookie = Depends(users.isLoggedIn)):
 
     content:dict = fileHandling.getMonth(cookie["user_id"], year, month)
     
+    history_available = False
     # move old log to history
     if "days" in content.keys():
         for dayLog in content["days"]:
             if dayLog["day"] == day and "text" in dayLog.keys():
+                history_available = True
                 historyVersion = 0
                 if "history" not in dayLog.keys():
                     dayLog["history"] = []
@@ -52,7 +54,7 @@ async def saveLog(log: Log, cookie = Depends(users.isLoggedIn)):
     ''' IMPORTANT: 
     Escaping html characters here is NOT possible, since it would break the appearance
     of html-code in the EDITOR. Code inside a markdown code-quote (`...`) will be auto-escaped.
-    Not a perfect solution, but actually any user can only load its own logs...
+    Not a perfect solution, but actually any user can only load its own logs (and so XSS himself)...
     '''
     encrypted_text = security.encrypt_text(log.text, enc_key)
     encrypted_date_written = security.encrypt_text(html.escape(log.date_written), enc_key)
@@ -75,7 +77,7 @@ async def saveLog(log: Log, cookie = Depends(users.isLoggedIn)):
         logger.error(f"Failed to save log for {cookie['user_id']} on {year}-{month:02d}-{day:02d}")
         return {"success": False}
 
-    return {"success": True}
+    return {"success": True, "history_available": history_available}
 
 
 @router.get("/getLog")
@@ -93,13 +95,16 @@ async def getLog(day: int, month: int, year: int, cookie = Depends(users.isLogge
             enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"])
             text = ""
             date_written = ""
+            history_available = False
             if "text" in dayLog.keys():
                 text = security.decrypt_text(dayLog["text"], enc_key)
                 date_written = security.decrypt_text(dayLog["date_written"], enc_key)
             if "files" in dayLog.keys():
                 for file in dayLog["files"]:
                     file["filename"] = security.decrypt_text(file["enc_filename"], enc_key)
-            return {"text": text, "date_written": date_written, "files": dayLog.get("files", []), "tags": dayLog.get("tags", [])}
+            if "history" in dayLog.keys() and len(dayLog["history"]) > 0:
+                history_available = True
+            return {"text": text, "date_written": date_written, "files": dayLog.get("files", []), "tags": dayLog.get("tags", []), "history_available": history_available}
 
     return dummy
 
@@ -555,11 +560,6 @@ async def saveTemplates(templates: Templates, cookie = Depends(users.isLoggedIn)
     else:
         return {"success": True}
 
-""" class OnThisDay(BaseModel):
-    day: int
-    month: int
-    year: int
-    years: list[int] """
 
 @router.get("/getOnThisDay")
 async def getOnThisDay(day: int, month: int, year: int, last_years: str, cookie = Depends(users.isLoggedIn)):
@@ -583,4 +583,26 @@ async def getOnThisDay(day: int, month: int, year: int, last_years: str, cookie
         except:
             continue
         
-    return results
\ No newline at end of file
+    return results
+
+
+@router.get("/getHistory")
+async def getHistory(day: int, month: int, year: int, cookie = Depends(users.isLoggedIn)):
+    enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"])
+    
+    content:dict = fileHandling.getMonth(cookie["user_id"], year, month)
+    
+    if "days" not in content.keys():
+        return []
+
+    for dayLog in content["days"]:
+        if dayLog["day"] == day and "history" in dayLog.keys():
+            history = []
+            for historyLog in dayLog["history"]:
+                history.append({
+                    "text": security.decrypt_text(historyLog["text"], enc_key),
+                    "date_written": security.decrypt_text(historyLog["date_written"], enc_key)
+                })
+            return history
+    
+    return []
\ No newline at end of file
index 7eb8a6c10fffac5f12cf02f3c0fab3174911f3ed..95126897feb035e24a6decfd6b6cc969bf4d8f04 100644 (file)
        let settingsModal;
        function openSettingsModal() {
                $tempSettings = JSON.parse(JSON.stringify($settings));
+               onThisDayYears = $settings.onThisDayYears.toString();
 
                settingsModal = new bootstrap.Modal(document.getElementById('settingsModal'));
                settingsModal.show();
index 67478bc153ad15422d579b5aebf587be4a895da5..5ec77d25b6efc4d5076563444ad0121231283d11 100644 (file)
                faCloudArrowUp,
                faCloudArrowDown,
                faSquarePlus,
-               faQuestionCircle
+               faQuestionCircle,
+               faClockRotateLeft,
+               faArrowLeft,
+               faArrowRight
        } from '@fortawesome/free-solid-svg-icons';
        import Fa from 'svelte-fa';
        import { v4 as uuidv4 } from 'uuid';
@@ -30,6 +33,7 @@
        import TemplateDropdown from '$lib/TemplateDropdown.svelte';
        import { templates, insertTemplate } from '$lib/templateStore';
        import OnThisDay from '$lib/OnThisDay.svelte';
+       import { marked } from 'marked';
 
        axios.interceptors.request.use((config) => {
                config.withCredentials = true;
                });
        }
 
+       let historyAvailable = $state(false);
        async function getLog() {
                if (savedLog !== currentLog) {
                        const success = await saveLog();
                        currentLog = response.data.text;
                        filesOfDay = response.data.files;
                        selectedTags = response.data.tags;
+                       historyAvailable = response.data.history_available;
 
                        savedLog = currentLog;
 
                        if (response.data.success) {
                                savedLog = currentLog;
                                logDateWritten = date_written;
+                               historyAvailable = response.data.history_available;
 
                                // add to $cal.daysWithLogs
                                if (!$cal.daysWithLogs.includes(lastSelectedDate.day)) {
                                isSavingNewTag = false;
                        });
        }
+
+       let history = $state([]);
+       let historySelected = $state(0);
+       function getHistory() {
+               axios
+                       .get(API_URL + '/logs/getHistory', {
+                               params: {
+                                       day: $selectedDate.day,
+                                       month: $selectedDate.month,
+                                       year: $selectedDate.year
+                               }
+                       })
+                       .then((response) => {
+                               if (response.data.length === 0) {
+                                       // no history
+                                       return;
+                               }
+
+                               history = response.data.map((log) => {
+                                       return {
+                                               text: log.text,
+                                               date_written: log.date_written
+                                       };
+                               });
+                               historySelected = history.length - 1;
+
+                               // show history in a modal or something
+                               const modal = new bootstrap.Modal(document.getElementById('modalHistory'));
+                               modal.show();
+                       })
+                       .catch((error) => {
+                               console.error(error);
+                               const toast = new bootstrap.Toast(document.getElementById('toastErrorLoadingLog'));
+                               toast.show();
+                       });
+       }
+
+       function selectHistory() {
+               if (historySelected < 0 || historySelected >= history.length) return;
+
+               currentLog = history[historySelected].text;
+               //logDateWritten = history[historySelected].date_written;
+
+               tinyMDE.setContent(currentLog);
+               tinyMDE.setSelection({ row: 0, col: 0 });
+       }
 </script>
 
 <DatepickerLogic />
                                        <div class={logDateWritten ? '' : 'opacity-50'}>Geschrieben am:</div>
                                        {logDateWritten}
                                </div>
-                               <div class="textAreaHistory">history</div>
+                               {#if historyAvailable}
+                                       <div class="textAreaHistory d-flex flex-column justify-content-center">
+                                               <button class="btn px-0 btn-hover" onclick={() => getHistory()}>
+                                                       <Fa icon={faClockRotateLeft} class="" size="1.5x" fw />
+                                               </button>
+                                       </div>
+                               {/if}
                                <div class="textAreaDelete">delete</div>
                        </div>
                        <div id="log" class="focus-ring">
                </div>
        </div>
 
+       <div class="modal fade" id="modalHistory" tabindex="-1">
+               <div class="modal-dialog modal-lg modal-fullscreen-lg-down modal-dialog-centered">
+                       <div class="modal-content">
+                               <div class="modal-header">
+                                       <h5 class="modal-title">Verlauf</h5>
+                                       <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"
+                                       ></button>
+                               </div>
+                               <div class="modal-body">
+                                       <div class="d-flex flex-row justify-content-center">
+                                               <button
+                                                       disabled={historySelected <= 0}
+                                                       class="btn btn-outline-secondary history-btn"
+                                                       onclick={() => {
+                                                               if (historySelected > 0) historySelected--;
+                                                       }}
+                                               >
+                                                       <Fa icon={faArrowLeft} class="me-2" fw />
+                                                       Älter
+                                               </button>
+                                               <select
+                                                       bind:value={historySelected}
+                                                       class="form-select mx-2"
+                                                       aria-label="Default select example"
+                                               >
+                                                       {#each history as entry, index (index)}
+                                                               <option value={index}>{entry.date_written}</option>
+                                                       {/each}
+                                               </select>
+                                               <button
+                                                       disabled={historySelected >= history.length - 1}
+                                                       class="btn btn-outline-secondary history-btn"
+                                                       onclick={() => {
+                                                               if (historySelected < history.length - 1) historySelected++;
+                                                       }}
+                                               >
+                                                       <Fa icon={faArrowRight} class="me-2" fw />
+                                                       Neuer
+                                               </button>
+                                       </div>
+                                       <div class="text mt-2">
+                                               {@html marked.parse(history[historySelected]?.text || 'Error!')}
+                                       </div>
+                               </div>
+                               <div class="modal-footer">
+                                       <div class="d-flex flex-column">
+                                               <div class="form-text">
+                                                       Mit <b>Speichern</b> machst du den angezeigten älteren Text wieder zum aktuellen Haupttext.
+                                               </div>
+                                               <div class="d-flex flex-row justify-content-end mt-2">
+                                                       <button type="button" class="btn btn-secondary me-2" data-bs-dismiss="modal"
+                                                               >Schließen</button
+                                                       >
+                                                       <button
+                                                               onclick={() => selectHistory()}
+                                                               type="button"
+                                                               class="btn btn-primary"
+                                                               data-bs-dismiss="modal">Speichern</button
+                                                       >
+                                               </div>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+
        <TagModal
                bind:this={tagModal}
                bind:editTag={newTag}
 </div>
 
 <style>
+       #modalHistory > div > div {
+               height: 80vh;
+       }
+
+       @media (max-width: 991px) {
+               #modalHistory > div > div {
+                       height: 100vh;
+               }
+       }
+
+       #modalHistory > div > div > .modal-body {
+               height: 50%;
+               display: flex;
+               flex-direction: column;
+       }
+
+       #modalHistory > div > div > .modal-body > .text {
+               flex: 1 1 auto;
+               overflow-y: auto;
+       }
+
+       .text {
+               border: 1px solid #ccc;
+               border-radius: 15px;
+               padding: 1rem;
+               word-wrap: anywhere;
+               white-space: break-spaces;
+       }
+
+       .history-btn {
+               white-space: nowrap;
+       }
+
+       .btn-hover:hover {
+               backdrop-filter: blur(8px) saturate(150%);
+               background-color: rgba(219, 219, 219, 0.45);
+               border: 1px solid #adadad77;
+       }
+
        @media (max-width: 1150px) {
                .middle-right {
                        flex-direction: column !important;
git clone https://git.99rst.org/PROJECT