Compare commits

...

10 Commits

Author SHA1 Message Date
1b0665222d Fix entry screen not updating 2026-02-11 15:55:37 +13:00
b8aad98b85 Hide delete button if new entry, reshow image styling 2026-02-11 15:42:28 +13:00
2fe5115e06 centred layout for desktop 2026-02-11 15:32:29 +13:00
171af1e435 simplify entry creation logic 2026-02-11 15:19:47 +13:00
9e521b81aa Fix editor brightness change 2026-02-04 16:48:03 +13:00
a2d13dc4c8 Fix off by one date error 2026-02-04 16:45:29 +13:00
4ccd12a9dc add endpoint to delete image 2026-02-04 15:19:38 +13:00
d14f3dfdb6 Add delete entry function 2026-02-04 14:29:21 +13:00
59e117003f Update styling a little 2026-02-04 14:25:18 +13:00
2e85fef7e2 updating entries works fully now !! 2026-02-03 20:48:40 +13:00
12 changed files with 180 additions and 61 deletions

View File

@@ -11,11 +11,18 @@
{:else}
<div class="flex flex-col items-center justify-center">
<button
class="bg-white/20 p-4 rounded-xl w-full hover:brightness-80 transition-all"
class="bg-white/10 p-4 rounded-xl w-full hover:brightness-80 transition-all"
onclick={() => edit = true}
>
<p class="rounded-full bg-white/30 p-2 h-10 w-10">+</p>
<p>Add Entry for {date}</p>
<div class="flex flex-col items-center gap-4">
<div class="rounded-full bg-white/20 p-3 h-14 w-14 flex items-center justify-center">
<span class="text-2xl font-light text-white">+</span>
</div>
<div class="text-center">
<p class="text-white/90 font-medium text-lg">Add Entry</p>
<p class="text-white/60 text-sm mt-1">{date}</p>
</div>
</div>
</button>
</div>
{/if}

View File

@@ -27,9 +27,13 @@
let currentYear = $state(parseInt(year));
let currentMonth = $state(parseInt(month));
function toISODate(year: number, month: number, date: number) {
return `${year}-${String(month + 1).padStart(2, '0')}-${String(date).padStart(2, '0')}`;
}
function getEntryForDay(year: number, month: number, date: number) {
if (!entries) return undefined;
const dateStr = new Date(year, month, date).toISOString().split("T")[0];
const dateStr = toISODate(year, month, date);
return entries.find((e) => e.date.split("T")[0] === dateStr);
}
@@ -44,7 +48,7 @@
date: date.getDate(),
month: date.getMonth(),
year: date.getFullYear(),
iso: date.toISOString().split('T')[0],
iso: toISODate(date.getFullYear(), date.getMonth(), date.getDate()),
dayName: headers[date.getDay() === 0 ? 6 : date.getDay() - 1],
isCurrentMonth,
isToday: date.toDateString() === today.toDateString(),

View File

@@ -39,7 +39,7 @@
<style>
@import 'tailwindcss';
/*#img-upload-button {
#img-upload-button {
@apply text-center bg-white/30 h-60 w-full rounded-2xl block hover:text-white/30 transition-all;
}*/
}
</style>

View File

@@ -2,27 +2,68 @@
import ImageTarget from "./imageTarget.svelte";
import TextEditor from "./textEditor.svelte";
import { formatDate } from "$lib/date.ts";
import { createEntry } from "$lib/upload.ts";
import { createEntry, updateEntry } from "$lib/upload.ts";
let { newEntryDate, entry = $bindable(), edit = $bindable(false) } = $props();
let newEntry = $state({ date: newEntryDate, image: "", content: "" });
let isNewEntry = !entry && newEntryDate
let newEntry = $state({
date: isNewEntry ? newEntryDate : entry.date,
image: isNewEntry ? "" : entry.image,
content: isNewEntry ? "" : entry.content
});
async function saveEntry() {
if (isNewEntry) {
await createEntry(newEntry);
} else {
await updateEntry(newEntry);
}
edit = false;
}
async function deleteEntry() {
const res = await fetch(`/api/entry/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: entry.id })
});
if (res.ok) {
entry = null;
} else {
alert('Failed to delete entry');
}
}
</script>
<div class="flex flex-row mb-2">
<p class="text-3xl font-bold">{entry ? formatDate(entry.date) : formatDate(newEntryDate)}</p>
<p class="text-3xl font-bold mr-auto">{entry ? formatDate(entry.date) : formatDate(newEntryDate)}</p>
{#if edit}
{#if !isNewEntry}
<button
class="bg-red-500 px-3 rounded-lg"
onclick={deleteEntry}
>
delete
</button>
{/if}
<button
class="bg-white/10 px-3 rounded-lg ml-auto"
class="bg-white/10 px-3 rounded-lg ml-1"
onclick={() => edit = false}
>
cancel
</button>
<button
class="bg-white/20 px-3 rounded-lg ml-1"
onclick={() => { createEntry(newEntry); edit = false }}
onclick={saveEntry}
>
done
</button>
@@ -37,10 +78,5 @@
</div>
{#if entry}
<ImageTarget bind:image={entry.image} bind:edit />
<TextEditor bind:content={entry.content} bind:edit />
{:else}
<ImageTarget bind:image={newEntry.image} bind:edit />
<TextEditor bind:content={newEntry.content} bind:edit />
{/if}
<ImageTarget bind:image={newEntry.image} bind:edit />
<TextEditor bind:content={newEntry.content} bind:edit />

View File

@@ -7,7 +7,7 @@
import { marked } from "marked";
</script>
<div class="bg-white/20 min-h-30 rounded-2xl p-2 mt-4">
<div class="min-h-30 bg-white/10 p-2 rounded-2xl w-full">
{#if edit}
<textarea
class="w-full min-h-30 border-none bg-white/10 rounded-xl p-2"

View File

@@ -9,15 +9,16 @@
const formattedDate = formatDate(entry.date);
</script>
<li class="bg-white/15 rounded-2xl mb-4 p-3" onclick={clickCB}>
<button class="bg-white/10 p-4 rounded-xl w-full hover:brightness-80 transition-all mb-3" onclick={clickCB}>
<div class="flex flex-row">
<div>
<div class="text-start">
<p class="text-sm text-white/60">{formattedDate}</p>
{@html marked(entry.content)}
</div>
<img
class="max-h-30 min-h-15 max-w-1/2 rounded-xl object-cover ml-auto"
src={entry.image}
alt=""
/>
</div>
</li>
</button>

View File

@@ -39,4 +39,19 @@ export async function createEntry(newEntry) {
},
body: JSON.stringify(newEntry),
});
}
}
export async function updateEntry(entry) {
if (entry.image) {
const url = await uploadImage(entry.image)
entry.image = url
}
await fetch("/api/entry/update", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(entry),
});
}

View File

@@ -8,6 +8,7 @@
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
<Header />
{@render children()}
<div class="md:max-w-2/3 mx-auto">
<Header />
{@render children()}
</div>

View File

@@ -1,52 +1,54 @@
<script lang="ts">
const {
data
} = $props()
import Calendar from '$lib/components/calendar.svelte'
import Editor from '$lib/components/editor/index.svelte';
import EntrySummaryView from '$lib/components/entrySummaryView.svelte'
import AddEntryCover from '$lib/components/addEntryCover.svelte'
import { today } from '$lib/date';
let edit = $state(false)
let selectedEntry = $state('')
let selectedDate = $state(today())
const { data } = $props();
import Calendar from "$lib/components/calendar.svelte";
import Editor from "$lib/components/editor/index.svelte";
import EntrySummaryView from "$lib/components/entrySummaryView.svelte";
import AddEntryCover from "$lib/components/addEntryCover.svelte";
import { today } from "$lib/date";
let edit = $state(false);
let selectedEntry = $state("");
let selectedDate = $state(today());
let selectEntry = async (hasEntry: boolean, data: string | null) => {
// data should be the entryID if hasEntry == true, or the date if there is no entry
if (hasEntry) {
const res = await fetch(`/api/entry?id=${data}`)
selectedEntry = await res.json()
selectedDate = selectedEntry.date
} else {
selectedEntry = ''
selectedDate = data as string
}
}
// data should be the entryID if hasEntry == true, or the date if there is no entry
if (hasEntry) {
const res = await fetch(`/api/entry?id=${data}`);
selectedEntry = await res.json();
selectedDate = selectedEntry.date;
} else {
selectedEntry = "";
selectedDate = data as string;
}
};
// $effect(() => {selectedEntry = ''})
</script>
<Calendar entries={data.all} dayClickCallback={selectEntry} />
<div class="flex flex-col md:flex-row space-y-4 w-full mt-6">
<div class="md:w-1/2 md:order-2">
{#if selectedEntry}
<Editor bind:entry={selectedEntry} />
{#key selectedEntry.id}
<Editor bind:entry={selectedEntry} />
{/key}
{:else}
<AddEntryCover bind:date={selectedDate} />
{#key selectedDate}
<AddEntryCover bind:date={selectedDate} />
{/key}
{/if}
</div>
<div class="md:w-1/2 md:mr-4">
<ul>
{#each data.all as entry}
<EntrySummaryView clickCB={async () => await selectEntry(true, entry.id)} {entry} />
<EntrySummaryView
clickCB={async () => await selectEntry(true, entry.id)}
{entry}
/>
{/each}
</ul>
</div>
</div>
</div>

View File

@@ -0,0 +1,16 @@
import { db } from "$lib/server/db";
import { entryTable } from "$lib/server/db/schema";
import { httpResponse } from "$lib/server/http";
import { eq } from "drizzle-orm";
export async function POST({ request }) {
const { id } = await request.json();
try {
await db.delete(entryTable).where(eq(entryTable.id, id)).execute();
return httpResponse({ message: "Entry deleted successfully" }, 200);
} catch (error) {
return httpResponse({ message: "Failed to delete entry", details: error }, 500);
}
}

View File

@@ -0,0 +1,16 @@
import { db } from "$lib/server/db";
import { entryTable } from "$lib/server/db/schema";
import { httpResponse } from "$lib/server/http";
import { eq } from "drizzle-orm";
export async function POST({ request }) {
const { id, content, image } = await request.json();
try {
await db.update(entryTable).set({ content, image }).where(eq(entryTable.id, id)).execute();
return httpResponse({ message: "Entry updated successfully" }, 200);
} catch (error) {
return httpResponse({ message: "Failed to update entry", details: error }, 500);
}
}

View File

@@ -32,4 +32,25 @@ async function readImage(id: string) {
'Content-Type': contentType,
}
});
}
export async function DELETE({ params }) {
try {
const { id } = params
await deleteImage(id!)
return httpResponse({ message: 'Image deleted successfully' }, 200);
} catch (error) {
return httpResponse({ error: `Failed to delete image: ${error}` }, 500);
}
}
async function deleteImage(id: string) {
const filepath = join(UPLOAD_DIR, id);
try {
await fs.unlink(filepath);
} catch {
throw new Error('Failed to delete image');
}
}