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)
@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": []}
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
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([]);
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>
--- /dev/null
+<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>
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'
: ''}"
>
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: [],
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
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);
// 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
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';
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">