added 'a look back'-feature; Refactored date-handling
authorPhiTux <redacted>
Mon, 16 Jun 2025 16:44:44 +0000 (18:44 +0200)
committerPhiTux <redacted>
Mon, 16 Jun 2025 16:44:44 +0000 (18:44 +0200)
backend/server/routers/logs.py
frontend/src/lib/Datepicker.svelte
frontend/src/lib/OnThisDay.svelte [new file with mode: 0644]
frontend/src/lib/Sidenav.svelte
frontend/src/lib/calendarStore.js
frontend/src/lib/helpers.js
frontend/src/routes/+layout.svelte
frontend/src/routes/read/+page.svelte
frontend/src/routes/write/+page.svelte

index 24904d347db06728a4d4e7e9702fda45969781aa..1b522ce5730874405519b63d0f5c78c56106e071 100644 (file)
@@ -17,15 +17,17 @@ router = APIRouter()
 
 
 class Log(BaseModel):
-    date: str
+    day: int
+    month: int
+    year: int
     text: str
     date_written: str
 
 @router.post("/saveLog")
 async def saveLog(log: Log, cookie = Depends(users.isLoggedIn)):
-    year = datetime.datetime.fromisoformat(log.date).year
-    month = datetime.datetime.fromisoformat(log.date).month
-    day = datetime.datetime.fromisoformat(log.date).day
+    year = log.year
+    month = log.month
+    day = log.day
 
     content:dict = fileHandling.getMonth(cookie["user_id"], year, month)
     
@@ -77,12 +79,8 @@ async def saveLog(log: Log, cookie = Depends(users.isLoggedIn)):
 
 
 @router.get("/getLog")
-async def getLog(date: str, cookie = Depends(users.isLoggedIn)):
+async def getLog(day: int, month: int, year: int, cookie = Depends(users.isLoggedIn)):
     
-    year = datetime.datetime.fromisoformat(date).year
-    month = datetime.datetime.fromisoformat(date).month
-    day = datetime.datetime.fromisoformat(date).day
-
     content:dict = fileHandling.getMonth(cookie["user_id"], year, month)
     
     dummy = {"text": "", "date_written": "", "files": [], "tags": []}
@@ -555,4 +553,34 @@ async def saveTemplates(templates: Templates, cookie = Depends(users.isLoggedIn)
     if not fileHandling.writeTemplates(cookie["user_id"], content):
         raise HTTPException(status_code=500, detail="Failed to write templates - error writing templates")
     else:
-        return {"success": True}
\ No newline at end of file
+        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)):
+    enc_key = security.get_enc_key(cookie["user_id"], cookie["derived_key"])
+    
+    results = []
+
+    old_years = [year - int(y) for y in last_years.split(",") if y.isdigit()]
+
+    for old_year in old_years:
+        content:dict = fileHandling.getMonth(cookie["user_id"], old_year, month)
+
+        try:
+            for day_content in content['days']:
+                if day_content['day'] == day:
+                    text = security.decrypt_text(day_content['text'], enc_key) if 'text' in day_content else ''
+                    if text == '':
+                        continue
+                    results.append({'years_old': year - old_year, 'day': day, 'month': month, 'year': old_year, 'text': text})
+                    break
+        except:
+            continue
+        
+    return results
\ No newline at end of file
index 90d4a7d4c686bb886607c96f16a6e20df95e8f8d..952ce191c0e4c684a05bfb131d01e96a0dcf9613 100644 (file)
@@ -3,7 +3,7 @@
        import { onMount } from 'svelte';
        import { fly } from 'svelte/transition';
        import * as bootstrap from 'bootstrap';
-       import { offcanvasIsOpen } from '$lib/helpers.js';
+       import { offcanvasIsOpen, sameDate } from '$lib/helpers.js';
 
        let days = $state([]);
 
@@ -32,8 +32,8 @@
        const updateCalendar = () => {
                const month = $cal.currentMonth;
                const year = $cal.currentYear;
-               const firstDay = new Date(year, month, 1);
-               const lastDay = new Date(year, month + 1, 0);
+               const firstDay = new Date(Date.UTC(year, month, 1));
+               const lastDay = new Date(Date.UTC(year, month + 1, 0));
 
                let tempDays = [];
                // monday is first day
                }
 
                for (let i = 1; i <= lastDay.getDate(); i++) {
-                       const dayKey = `${year}-${(month + 1).toString().padStart(2, '0')}-${i
-                               .toString()
-                               .padStart(2, '0')}`;
-                       tempDays.push(new Date(Date.UTC(year, month, i)));
+                       tempDays.push({ year: year, month: month + 1, day: i });
                }
 
                return tempDays;
                                                        in:fly={{ y: 100, duration: 200 }}
                                                        out:fly={{ y: -100, duration: 200 }}
                                                        class="day
-                                                               {$cal.daysWithLogs.includes(day.getDate()) ? 'mark-background' : ''} 
-                                                               {$cal.daysWithFiles.includes(day.getDate()) ? 'mark-dot' : ''} 
-                                                               {(!$readingDate && $selectedDate.toDateString() === day.toDateString()) ||
-                                                       $readingDate?.toDateString() === day.toDateString()
-                                                               ? 'selected'
-                                                               : ''}"
+                                                               {$cal.daysWithLogs.includes(day.day) ? 'mark-background' : ''} 
+                                                               {$cal.daysWithFiles.includes(day.day) ? 'mark-dot' : ''} 
+                                                               {(!$readingDate && sameDate($selectedDate, day)) || sameDate($readingDate, day) ? 'selected' : ''}"
                                                        onclick={() => onDateClick(day)}
                                                >
-                                                       {day.getDate()}
+                                                       {day.day}
                                                </div>
                                        {:else}
                                                <div class="day empty-slot"></div>
                        <button
                                class="btn btn-primary"
                                onclick={() => {
-                                       $selectedDate = new Date();
+                                       $selectedDate = {
+                                               day: new Date().getDate(),
+                                               month: new Date().getMonth() + 1,
+                                               year: new Date().getFullYear()
+                                       };
                                }}>Heute</button
                        >
                </div>
diff --git a/frontend/src/lib/OnThisDay.svelte b/frontend/src/lib/OnThisDay.svelte
new file mode 100644 (file)
index 0000000..73b9e73
--- /dev/null
@@ -0,0 +1,253 @@
+<script>
+       import { marked } from 'marked';
+       import { selectedDate } from './calendarStore';
+
+       marked.use({
+               breaks: true,
+               gfm: true
+       });
+
+       let { log } = $props();
+
+       let preview;
+       let content;
+       let modal;
+       let isModalOpen = $state(false);
+
+       function openModal() {
+               if (!preview || !modal || !content) return;
+
+               const previewRect = preview.getBoundingClientRect();
+               const targetWidth = Math.min(window.innerWidth * 0.8, 600); // Target width
+
+               // Initial state for the animation
+               // Position and scale to match the button
+               content.style.left = `${previewRect.left}px`;
+               content.style.top = `${previewRect.top}px`;
+               content.style.width = `${previewRect.width}px`;
+               content.style.height = `${previewRect.height}px`;
+               content.style.transform = 'scale(1)'; // Start at button's scale
+               content.style.opacity = '0';
+
+               modal.style.display = 'flex';
+
+               void content.offsetWidth;
+
+               // Target state for the animation
+               // Calculate scale factor to reach targetWidth from previewRect.width
+               const scaleX = targetWidth / previewRect.width;
+
+               const targetLeft = (window.innerWidth - targetWidth) / 2;
+               const targetTop = window.innerHeight * 0.2;
+
+               content.style.left = `${targetLeft}px`; // Position for final state
+               content.style.top = `${targetTop}px`; // Position for final state
+               content.style.width = `${targetWidth}px`;
+               // Height will be 'auto' or controlled by max-height in CSS
+               content.style.height = 'auto'; // Let CSS max-height handle this
+               content.style.transform = 'scale(1)'; // End at normal scale, but at new position/size
+               content.style.opacity = '1';
+
+               isModalOpen = true;
+               document.body.style.overflow = 'hidden';
+       }
+
+       function closeModal() {
+               if (!preview || !modal || !content) return;
+
+               const previewRect = preview.getBoundingClientRect();
+
+               content.style.left = `${previewRect.left}px`;
+               content.style.top = `${previewRect.top}px`;
+               content.style.width = `${previewRect.width}px`;
+               content.style.height = `${previewRect.height}px`;
+               content.style.transform = 'scale(1)';
+               content.style.opacity = '0';
+
+               setTimeout(() => {
+                       if (!isModalOpen) {
+                               modal.style.display = 'none';
+                               document.body.style.overflow = '';
+                       }
+               }, 400);
+
+               isModalOpen = false;
+       }
+
+       function handleKeydown(event) {
+               if (event.key === 'Escape' && isModalOpen) {
+                       closeModal();
+               }
+       }
+</script>
+
+<!-- svelte-ignore a11y_click_events_have_key_events -->
+<svelte:window on:keydown={handleKeydown} />
+
+<!-- svelte-ignore a11y_consider_explicit_label -->
+<button
+       bind:this={preview}
+       onclick={openModal}
+       id="zoomButton"
+       class="btn"
+       style="width: 200px; height: 100px;"
+>
+       <div class="d-flex flex-row h-100">
+               <div class="left d-flex flex-column justify-content-evenly px-1">
+                       <div><b>{log?.year}</b></div>
+                       <div><em><b>{log?.years_old}</b> J</em></div>
+               </div>
+               <div class="html-preview p-1">
+                       {@html marked.parse(log?.text)}
+               </div>
+       </div>
+</button>
+
+<!-- svelte-ignore a11y_no_static_element_interactions -->
+<!-- svelte-ignore a11y_click_events_have_key_events -->
+<div
+       bind:this={modal}
+       id="zoomModal"
+       class="zoom-modal"
+       onclick={(event) => {
+               if (event.target === modal) {
+                       closeModal();
+               }
+       }}
+>
+       <div bind:this={content} class="zoom-content">
+               <div class="zoom-content-header">
+                       <span
+                               >Vor {log?.years_old} Jahren | {new Date(
+                                       log?.year,
+                                       log?.month - 1,
+                                       log?.day
+                               ).toLocaleDateString('locale', {
+                                       day: '2-digit',
+                                       month: '2-digit',
+                                       year: 'numeric'
+                               })}</span
+                       >
+                       <button
+                               type="button"
+                               class="btn-close btn-close-white"
+                               aria-label="Close"
+                               onclick={closeModal}
+                       ></button>
+               </div>
+               <div class="modal-text">{@html marked.parse(log?.text)}</div>
+               <button
+                       onclick={() => {
+                               $selectedDate = { year: log.year, month: log.month, day: log.day };
+                       }}
+                       class="btn btn-primary"
+                       id="closeZoom">Öffnen</button
+               >
+       </div>
+</div>
+
+<style>
+       .left {
+               background-color: rgba(180, 180, 180, 0.45);
+               border-top-left-radius: 0.375rem;
+               border-bottom-left-radius: 0.375rem;
+       }
+
+       .modal-text {
+               margin-left: 20px;
+               margin-right: 20px;
+               margin-top: 20px;
+       }
+
+       #closeZoom {
+               margin-left: 20px;
+               margin-bottom: 20px;
+       }
+
+       .zoom-content-header {
+               display: flex;
+               justify-content: space-between;
+               align-items: center;
+               padding: 8px 15px;
+               background-color: #343a40;
+               color: white;
+               border-bottom: 1px solid #495057;
+               flex-shrink: 0;
+               border-top-left-radius: 8px;
+               border-top-right-radius: 8px;
+       }
+
+       .html-preview :global(h1),
+       .html-preview :global(h2),
+       .html-preview :global(h3),
+       .html-preview :global(h4),
+       .html-preview :global(h5),
+       .html-preview :global(h6) {
+               font-size: 1.1em !important;
+       }
+
+       .html-preview {
+               overflow: hidden;
+               flex-grow: 1;
+               max-height: 100%;
+               line-height: 1.25;
+               backdrop-filter: blur(8px) saturate(150%);
+       }
+
+       .html-preview::after {
+               content: '';
+               position: absolute;
+               bottom: 0;
+               left: 0;
+               right: 0;
+               height: 40px;
+               background: linear-gradient(to bottom, transparent, rgba(219, 219, 219, 0.45) 80%);
+               pointer-events: none;
+       }
+
+       #zoomButton {
+               background-color: rgba(219, 219, 219, 0.45);
+               transition: 0.3s ease;
+               padding: 0 !important;
+       }
+
+       #zoomButton:hover {
+               background-color: rgba(219, 219, 219, 0.65);
+       }
+
+       .zoom-modal {
+               position: fixed;
+               top: 0;
+               left: 0;
+               width: 100vw;
+               height: 100vh;
+               display: none;
+               justify-content: center;
+               align-items: center;
+               z-index: 1050;
+               background-color: rgba(0, 0, 0, 0.5);
+               overflow: hidden;
+       }
+
+       .zoom-content {
+               position: absolute;
+               background: white;
+               color: black;
+               border-radius: 8px;
+               box-shadow: 0 0 20px rgba(0, 0, 0, 0.4);
+               /* transform-origin: top left; */ /* Let's try center for smoother scaling if we use scale for size */
+               transition:
+                       left 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
+                       top 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
+                       width 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
+                       height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
+                       /* Animating height can be jittery */ transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
+                       opacity 0.4s ease-out;
+               /* padding: 20px; */
+               overflow-y: auto;
+               max-width: 600px; /* Set max-width here */
+               max-height: 80vh; /* Set max-height here */
+               /* Consider adding will-change for properties you animate, but use sparingly */
+               /* will-change: transform, opacity, left, top, width, height; */
+       }
+</style>
index 122438574a09a36545a09155eb2bb198755d8669..18de68587b4c0e35506a12a60b4109448e5f920b 100644 (file)
@@ -8,7 +8,7 @@
        import { onMount } from 'svelte';
        import * as bootstrap from 'bootstrap';
        import Tag from './Tag.svelte';
-       import { offcanvasIsOpen } from '$lib/helpers.js';
+       import { offcanvasIsOpen, sameDate } from '$lib/helpers.js';
        import { API_URL } from '$lib/APIurl.js';
        import axios from 'axios';
 
                                        <button
                                                type="button"
                                                onclick={() => {
-                                                       /* $selectedDate = new Date(Date.UTC(result.year, result.month - 1, result.day)); */
-                                                       selectDate(new Date(Date.UTC(result.year, result.month - 1, result.day)));
+                                                       selectDate({
+                                                               year: parseInt(result.year),
+                                                               month: parseInt(result.month),
+                                                               day: result.day
+                                                       });
                                                }}
-                                               class="list-group-item list-group-item-action {$selectedDate.toDateString() ===
-                                               new Date(Date.UTC(result.year, result.month - 1, result.day)).toDateString()
+                                               class="list-group-item list-group-item-action {sameDate($selectedDate, {
+                                                       year: parseInt(result.year),
+                                                       month: parseInt(result.month),
+                                                       day: result.day
+                                               })
                                                        ? 'active'
                                                        : ''}"
                                        >
index f21a4dbbf9d37116b5ac8bb7f5c2114b97bd4181..1a4a94a8a46d4c803ecfe0dac495309a4871ff45 100644 (file)
@@ -2,7 +2,11 @@ import {writable} from 'svelte/store';
 
 let date = new Date();
 
-export let selectedDate = writable(date);
+export let selectedDate = writable({
+  day: date.getDate(),
+  month: date.getMonth() + 1,
+  year: date.getFullYear(),
+});
 
 export let cal = writable({
   daysWithLogs: [],
@@ -11,4 +15,8 @@ export let cal = writable({
   currentYear: date.getFullYear(),
 });
 
-export let readingDate = writable(date)
\ No newline at end of file
+export let readingDate = writable({
+  day: date.getDate(),
+  month: date.getMonth() + 1,
+  year: date.getFullYear(),
+});
\ No newline at end of file
index 82fd3e890f06784e8bd978597fa4e0f9fcae76c8..f2eb91e5038aa68e74d65b740506c687c5fa3863 100644 (file)
@@ -11,7 +11,16 @@ function formatBytes(bytes) {
        return `${parseFloat((bytes / Math.pow(k, i)).toFixed(0))} ${sizes[i]}`;
 }
 
-export { formatBytes };
+function sameDate(date1, date2) {
+       if (!date1 || !date2) return false;
+       return (
+               date1.day === date2.day &&
+               date1.month === date2.month &&
+               date1.year === date2.year
+       );
+}
+
+export { formatBytes, sameDate };
 
 export let alwaysShowSidenav = writable(true);
 
index cd856f282804e198004ff4e7bb318af4b9b2bbe0..7eb8a6c10fffac5f12cf02f3c0fab3174911f3ed 100644 (file)
        // check if onThisDayYears is valid
        $effect(() => {
                onThisDayYearsInvalid = false;
+               if ($tempSettings.useOnThisDay === false) {
+                       return;
+               }
 
                //regex: years may only contain numbers and commas
                if (onThisDayYears.match(/[^0-9,]/)) {
                        .trim()
                        .split(',')
                        .forEach((year) => {
-                               //if (isNaN(year.trim()) || year.trim() === '' || year.conta) {
                                if (!Number.isInteger(parseInt(year.trim()))) {
                                        onThisDayYearsInvalid = true;
                                }
                                                                                        <div class="unsaved-changes" transition:slide></div>
                                                                                {/if}
 
-                                                                               <h5>An diesem Tag</h5>
+                                                                               <h5>Ein Blick zurück</h5>
                                                                                <ul>
                                                                                        <li>
                                                                                                Lege fest, aus welchen vergangenen Jahren Tagebucheinträge desselben
index 1cff3048df224ff645df5b394b10d29b2e1b7c2f..ee972800711f59014318852b670c0d039cece9d6 100644 (file)
@@ -12,7 +12,6 @@
        import { autoLoadImagesThisDevice, settings } from '$lib/settingsStore';
        import { faCloudArrowDown } from '@fortawesome/free-solid-svg-icons';
        import { Fa } from 'svelte-fa';
-       import { fade, slide } from 'svelte/transition';
        import ImageViewer from '$lib/ImageViewer.svelte';
        import { alwaysShowSidenav } from '$lib/helpers.js';
 
index 8466b2acf8d839d511a6c566a2b3a4c7a3b5aeff..67478bc153ad15422d579b5aebf587be4a895da5 100644 (file)
        import Tag from '$lib/Tag.svelte';
        import TagModal from '$lib/TagModal.svelte';
        import FileList from '$lib/FileList.svelte';
-       import { formatBytes, alwaysShowSidenav } from '$lib/helpers.js';
+       import { formatBytes, alwaysShowSidenav, sameDate } from '$lib/helpers.js';
        import ImageViewer from '$lib/ImageViewer.svelte';
        import TemplateDropdown from '$lib/TemplateDropdown.svelte';
        import { templates, insertTemplate } from '$lib/templateStore';
+       import OnThisDay from '$lib/OnThisDay.svelte';
 
        axios.interceptors.request.use((config) => {
                config.withCredentials = true;
 
        let loading = false;
        $effect(() => {
-               if ($selectedDate !== lastSelectedDate) {
+               if (!sameDate($selectedDate, lastSelectedDate)) {
                        cancelDownload.abort();
                        cancelDownload = new AbortController();
 
                        const result = getLog();
                        if (result) {
                                lastSelectedDate = $selectedDate;
-                               $cal.currentYear = $selectedDate.getFullYear();
-                               $cal.currentMonth = $selectedDate.getMonth();
+                               $cal.currentYear = $selectedDate.year;
+                               $cal.currentMonth = $selectedDate.month - 1;
                        } else {
                                $selectedDate = lastSelectedDate;
                        }
        }
 
        function changeDay(increment) {
-               const newDate = new Date($selectedDate);
-               newDate.setDate(newDate.getDate() + increment);
-               $selectedDate = newDate;
+               $selectedDate = {
+                       day: $selectedDate.day + increment,
+                       month: $selectedDate.month,
+                       year: $selectedDate.year
+               };
        }
 
        let currentLog = $state('');
                try {
                        const response = await axios.get(API_URL + '/logs/getLog', {
                                params: {
-                                       date: $selectedDate.toISOString()
+                                       day: $selectedDate.day,
+                                       month: $selectedDate.month,
+                                       year: $selectedDate.year
                                }
                        });
 
 
                        logDateWritten = response.data.date_written;
 
+                       getOnThisDay();
+
                        return true;
                } catch (error) {
                        console.error(error.response);
                }
        }
 
+       let onThisDay = $state([]);
+
+       function getOnThisDay() {
+               if (!$settings.useOnThisDay) {
+                       onThisDay = [];
+                       return;
+               }
+
+               axios
+                       .get(API_URL + '/logs/getOnThisDay', {
+                               params: {
+                                       day: $selectedDate.day,
+                                       month: $selectedDate.month,
+                                       year: $selectedDate.year,
+                                       last_years: $settings.onThisDayYears.join(',')
+                               }
+                       })
+                       .then((response) => {
+                               onThisDay = response.data;
+                       })
+                       .catch((error) => {
+                               console.error(error);
+                       });
+       }
+
        const imageExtensions = ['jpeg', 'jpg', 'gif', 'png', 'webp'];
        //TODO: support svg? -> minsize is necessary...
 
                let dateOfSave = lastSelectedDate;
                try {
                        const response = await axios.post(API_URL + '/logs/saveLog', {
-                               date: lastSelectedDate.toISOString(),
+                               day: lastSelectedDate.day,
+                               month: lastSelectedDate.month,
+                               year: lastSelectedDate.year,
                                text: currentLog,
                                date_written: date_written
                        });
                                logDateWritten = date_written;
 
                                // add to $cal.daysWithLogs
-                               if (!$cal.daysWithLogs.includes(lastSelectedDate.getDate())) {
-                                       $cal.daysWithLogs = [...$cal.daysWithLogs, dateOfSave.getDate()];
+                               if (!$cal.daysWithLogs.includes(lastSelectedDate.day)) {
+                                       $cal.daysWithLogs = [...$cal.daysWithLogs, dateOfSave.day];
                                }
 
                                return true;
                };
 
                const formData = new FormData();
-               formData.append('day', $selectedDate.getDate());
-               formData.append('month', $selectedDate.getMonth() + 1);
-               formData.append('year', $selectedDate.getFullYear());
+               formData.append('day', $selectedDate.day);
+               formData.append('month', $selectedDate.month);
+               formData.append('year', $selectedDate.year);
                formData.append('file', f);
                formData.append('uuid', uuid);
 
                        .get(API_URL + '/logs/deleteFile', {
                                params: {
                                        uuid: uuid,
-                                       year: $selectedDate.getFullYear(),
-                                       month: $selectedDate.getMonth() + 1,
-                                       day: $selectedDate.getDate()
+                                       year: $selectedDate.year,
+                                       month: $selectedDate.month,
+                                       day: $selectedDate.day
                                }
                        })
                        .then((response) => {
 
                axios
                        .post(API_URL + '/logs/addTagToLog', {
-                               day: $selectedDate.getDate(),
-                               month: $selectedDate.getMonth() + 1,
-                               year: $selectedDate.getFullYear(),
+                               day: $selectedDate.day,
+                               month: $selectedDate.month,
+                               year: $selectedDate.year,
                                tag_id: id
                        })
                        .then((response) => {
 
                axios
                        .post(API_URL + '/logs/removeTagFromLog', {
-                               day: $selectedDate.getDate(),
-                               month: $selectedDate.getMonth() + 1,
-                               year: $selectedDate.getFullYear(),
+                               day: $selectedDate.day,
+                               month: $selectedDate.month,
+                               year: $selectedDate.year,
                                tag_id: id
                        })
                        .then((response) => {
                        <!-- Input-Area -->
                        <div class="d-flex flex-row textAreaHeader">
                                <div class="flex-fill textAreaDate">
-                                       {$selectedDate.toLocaleDateString('locale', { weekday: 'long' })}<br />
-                                       {$selectedDate.toLocaleDateString('locale', {
+                                       {new Date(
+                                               Date.UTC($selectedDate.year, $selectedDate.month - 1, $selectedDate.day)
+                                       ).toLocaleDateString('locale', { weekday: 'long', timeZone: 'UTC' })}<br />
+                                       {new Date(
+                                               Date.UTC($selectedDate.year, $selectedDate.month - 1, $selectedDate.day)
+                                       ).toLocaleDateString('locale', {
                                                day: '2-digit',
                                                month: '2-digit',
-                                               year: 'numeric'
+                                               year: 'numeric',
+                                               timeZone: 'UTC'
                                        })}
                                </div>
                                <div class="flex-fill textAreaWrittenAt">
                                        <ImageViewer {images} />
                                {/if}
                        {/if}
+
+                       {#if $settings.useOnThisDay && onThisDay.length > 0}
+                               <div class="mt-3 d-flex gap-2">
+                                       {#each onThisDay as log}
+                                               <OnThisDay {log} />
+                                       {/each}
+                               </div>
+                       {/if}
                </div>
 
                <div id="right" class="d-flex flex-column">
git clone https://git.99rst.org/PROJECT