Compare commits

..

8 Commits

Author SHA1 Message Date
798f2c1e44 Search works with the timezone in the db 2026-01-15 17:14:31 +13:00
22772ad436 refactor both entry endpoints to one 2026-01-15 17:05:08 +13:00
cb027a6e92 use compilerPath version 2026-01-15 16:52:28 +13:00
c1e39bbf86 fix type error 2026-01-15 16:38:25 +13:00
aa58dfe58b trying new styling but nowhere near happy with it lmfao 2026-01-15 16:00:14 +13:00
ce2eabf1f5 start homepage layout 2026-01-15 15:36:07 +13:00
e934d4893a Add initial calendar implementation 2026-01-15 15:25:09 +13:00
dad55dab39 refactor editor file 2026-01-15 15:09:01 +13:00
11 changed files with 239 additions and 89 deletions

View File

@@ -10,6 +10,12 @@
},
"dependencies": {
"@astrojs/node": "^9.5.1",
"@fullcalendar/core": "^6.1.20",
"@fullcalendar/daygrid": "^6.1.20",
"@fullcalendar/interaction": "^6.1.20",
"@fullcalendar/list": "^6.1.20",
"@fullcalendar/resource": "^6.1.20",
"@fullcalendar/scrollgrid": "^6.1.20",
"@tailwindcss/vite": "^4.1.18",
"astro": "^5.16.8",
"dotenv": "^17.2.3",

86
pnpm-lock.yaml generated
View File

@@ -11,6 +11,24 @@ importers:
'@astrojs/node':
specifier: ^9.5.1
version: 9.5.1(astro@5.16.8(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.55.1)(tsx@4.21.0)(typescript@5.9.3))
'@fullcalendar/core':
specifier: ^6.1.20
version: 6.1.20
'@fullcalendar/daygrid':
specifier: ^6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)
'@fullcalendar/interaction':
specifier: ^6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)
'@fullcalendar/list':
specifier: ^6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)
'@fullcalendar/resource':
specifier: ^6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)
'@fullcalendar/scrollgrid':
specifier: ^6.1.20
version: 6.1.20(@fullcalendar/core@6.1.20)
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18(vite@6.4.1(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
@@ -546,6 +564,39 @@ packages:
cpu: [x64]
os: [win32]
'@fullcalendar/core@6.1.20':
resolution: {integrity: sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==}
'@fullcalendar/daygrid@6.1.20':
resolution: {integrity: sha512-AO9vqhkLP77EesmJzuU+IGXgxNulsA8mgQHynclJ8U70vSwAVnbcLG9qftiTAFSlZjiY/NvhE7sflve6cJelyQ==}
peerDependencies:
'@fullcalendar/core': ~6.1.20
'@fullcalendar/interaction@6.1.20':
resolution: {integrity: sha512-p6txmc5txL0bMiPaJxe2ip6o0T384TyoD2KGdsU6UjZ5yoBlaY+dg7kxfnYKpYMzEJLG58n+URrHr2PgNL2fyA==}
peerDependencies:
'@fullcalendar/core': ~6.1.20
'@fullcalendar/list@6.1.20':
resolution: {integrity: sha512-7Hzkbb7uuSqrXwTyD0Ld/7SwWNxPD6SlU548vtkIpH55rZ4qquwtwYdMPgorHos5OynHA4OUrZNcH51CjrCf2g==}
peerDependencies:
'@fullcalendar/core': ~6.1.20
'@fullcalendar/premium-common@6.1.20':
resolution: {integrity: sha512-rT+AitNnRyZuFEtYvsB1OJ2g1Bq2jmTR6qdn/dEU6LwkIj/4L499goLtMOena/JyJ31VBztdHrccX//36QrY3w==}
peerDependencies:
'@fullcalendar/core': ~6.1.20
'@fullcalendar/resource@6.1.20':
resolution: {integrity: sha512-vpQs1eYJbc1zGOzF3obVVr+XsHTMTG7STKVQBEGy3AeFgfosRkUz+3DUawmy98vSjJUYOAQHO+pWW0ek0n5g0w==}
peerDependencies:
'@fullcalendar/core': ~6.1.20
'@fullcalendar/scrollgrid@6.1.20':
resolution: {integrity: sha512-M55m0hxpou4IPObto5f0nVcXvIj3rkSTba0ypclSFDwBz3JxuCPS6l8kaUznqlZCr2Ld/HFJr+jwyvY070AafQ==}
peerDependencies:
'@fullcalendar/core': ~6.1.20
'@img/colour@1.0.0':
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
engines: {node: '>=18'}
@@ -1957,6 +2008,9 @@ packages:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
preact@10.12.1:
resolution: {integrity: sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==}
prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
@@ -2729,6 +2783,36 @@ snapshots:
'@esbuild/win32-x64@0.27.2':
optional: true
'@fullcalendar/core@6.1.20':
dependencies:
preact: 10.12.1
'@fullcalendar/daygrid@6.1.20(@fullcalendar/core@6.1.20)':
dependencies:
'@fullcalendar/core': 6.1.20
'@fullcalendar/interaction@6.1.20(@fullcalendar/core@6.1.20)':
dependencies:
'@fullcalendar/core': 6.1.20
'@fullcalendar/list@6.1.20(@fullcalendar/core@6.1.20)':
dependencies:
'@fullcalendar/core': 6.1.20
'@fullcalendar/premium-common@6.1.20(@fullcalendar/core@6.1.20)':
dependencies:
'@fullcalendar/core': 6.1.20
'@fullcalendar/resource@6.1.20(@fullcalendar/core@6.1.20)':
dependencies:
'@fullcalendar/core': 6.1.20
'@fullcalendar/premium-common': 6.1.20(@fullcalendar/core@6.1.20)
'@fullcalendar/scrollgrid@6.1.20(@fullcalendar/core@6.1.20)':
dependencies:
'@fullcalendar/core': 6.1.20
'@fullcalendar/premium-common': 6.1.20(@fullcalendar/core@6.1.20)
'@img/colour@1.0.0':
optional: true
@@ -4281,6 +4365,8 @@ snapshots:
dependencies:
xtend: 4.0.2
preact@10.12.1: {}
prismjs@1.30.0: {}
promise-limit@2.7.0:

View File

@@ -0,0 +1,25 @@
<div id="calendar"/>
<script>
import { Calendar } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import listPlugin from '@fullcalendar/list';
import interactionPlugin from '@fullcalendar/interaction';
import scrollgridPlugin from '@fullcalendar/scrollgrid';
import resourcePlugin from '@fullcalendar/resource';
let calendarEl = document.getElementById('calendar')!;
let calendar = new Calendar(calendarEl, {
plugins: [ dayGridPlugin, listPlugin, interactionPlugin, scrollgridPlugin, resourcePlugin ],
initialView: 'dayGridMonth',
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
headerToolbar: {
left: 'prev,next',
center: 'title',
right: 'today'
},
});
calendar.render();
</script>

View File

@@ -1,3 +1,3 @@
<div class="flex w-full h-14 px-2 rounded-xl mb-4 bg-blue-400/40 place-items-center">
<p class="font-bold">Memento</p>
<div class="flex w-full h-14 px-2 text-2xl rounded-xl mb-4 bg-[#009FB7] place-items-center">
<p>Memento</p>
</div>

View File

@@ -0,0 +1,31 @@
<div id="editor" />
<script>
import Quill from "quill";
import { uploadEntry } from '../utils/quill'
import type { Entry } from "../utils/quill";
const quill = new Quill('#editor', {
modules: {
toolbar: [
['bold', 'italic', 'underline'],
['image']
],
},
placeholder: 'Compose an epic...',
theme: 'snow', // or 'bubble'
});
document.querySelector("#upload")?.addEventListener('click', async () => {
const contents = quill.getContents()
let entry: Entry = {
content: contents,
date: '2026-01-13T10:49:43Z',
location: null
}
await uploadEntry(entry)
})
</script>

View File

@@ -1,30 +0,0 @@
import type { APIContext } from 'astro';
import { eq } from 'drizzle-orm';
import { db } from '../../../utils/db';
import { entryTable } from '../../../db/schema';
import { httpResponse } from '../../../utils/response';
export async function GET({ params }: APIContext) {
try {
const { id } = params
if (!id) {
return httpResponse({'error': 'no id provided'}, 400)
}
return getEntry(id)
} catch (error) {
return httpResponse({ error: `Failed to retrieve entry: ${error}` }, 500);
}
}
async function getEntry(id) {
try {
const entry = await db.select().from(entryTable).where(eq(entryTable.id, id))
if (entry.length == 0) {
return httpResponse({'error': 'entry not found'}, 404)
}
return httpResponse(entry[0], 200)
} catch {
return httpResponse({'error': 'bad request'}, 400)
}
}

View File

@@ -0,0 +1,55 @@
import type { APIContext } from "astro";
import { httpResponse } from "@util/response";
import { getParams } from "@util/http";
import { eq, like, sql } from 'drizzle-orm';
import { db } from '@util/db';
import { entryTable } from '@db/schema';
export async function GET({ request }: APIContext) {
const { id, date } = getParams(request)
if (id && !isNaN(Number(id))) {
return getEntryByID(Number(id))
}
if (date) {
return getEntryByDate(date)
}
return httpResponse({ error: 'Failed to retrieve entry' }, 500);
}
async function getEntryByID(id: number) {
try {
const entry = await db.select().from(entryTable).where(eq(entryTable.id, id))
if (entry.length == 0) {
return httpResponse({'error': 'entry not found'}, 404)
}
return httpResponse(entry[0], 200)
} catch {
return httpResponse({'error': 'bad request'}, 400)
}
}
async function getEntryByDate(dateString: string) {
try {
// timezones suck
const startDate = new Date(dateString)
startDate.setHours(0, 0, 0, 0)
const endDate = new Date(dateString)
endDate.setHours(23, 59, 59, 999)
const entry = await db.select().from(entryTable).where(
sql`${entryTable.date} >= ${startDate.toISOString()}::timestamp AND ${entryTable.date} <= ${endDate.toISOString()}::timestamp`
)
if (entry.length == 0) {
return httpResponse({'error': 'entry not found'}, 404)
}
return httpResponse(entry, 200)
} catch(error) {
return httpResponse({'error': error}, 400)
}
}

View File

@@ -1,64 +1,21 @@
---
import "../styles/global.css"
import Layout from "../component/core/layout.astro"
import Calendar from "../component/calendar.astro"
import Editor from "../component/editor.astro"
---
<script>
import Quill from "quill";
import { uploadEntry } from '../utils/quill'
import type { Entry } from "../utils/quill";
const el = document.getElementById('entry-list')!;
const quill = new Quill('#editor', {
modules: {
toolbar: [
['bold', 'italic', 'underline'],
['image']
],
},
placeholder: 'Compose an epic...',
theme: 'snow', // or 'bubble'
});
document.querySelector("#upload")?.addEventListener('click', async () => {
const contents = quill.getContents()
let entry: Entry = {
content: contents,
date: '2026-01-13T10:49:43Z',
location: null
}
await uploadEntry(entry)
})
</script>
<script>
const el = document.getElementById('result')
document.querySelector('#export')?.addEventListener('click', async () => {
const res = await fetch('/api/entry/all')
const js = await res.json()
el!.innerText = JSON.stringify(js)
})
</script>
<Layout>
<div id="editor">
<p>Hello World!</p>
<p>Some initial <strong>bold</strong> text</p>
<p><br /></p>
<div class="flex space-x-4 w-full">
<div class="w-2/5">
<Calendar />
<div id="entry-list" />
</div>
<button id="upload" class="mt-2">
upload
</button>
<button id="export" class="mt-2">
export all
</button>
<div id="result">
</div>
</Layout>

View File

@@ -4,10 +4,15 @@
@import 'quill/dist/quill.snow.css';
html {
@apply p-3;
@apply bg-black/90 text-white
@apply py-3 px-4;
@apply bg-[#00171F] text-white
}
html body {
@apply font-[AmericanTypewriter];
/* @apply w-full md:w-[68%] mx-auto h-full; */
}
button {
@apply py-2 px-3 bg-green-400 rounded-xl;
}

6
src/utils/http.ts Normal file
View File

@@ -0,0 +1,6 @@
export function getParams(request: Request) {
const params = request.url.split("?")[1]
const searchParams = new URLSearchParams(params);
const paramsDict = Object.fromEntries(searchParams.entries());
return paramsDict
}

View File

@@ -1,5 +1,14 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
"exclude": ["dist"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@component/*": ["src/components/*.astro"],
"@layout/*": ["src/layout/*.astro"],
"@util/*": ["src/utils/*"],
"@db/*": ["src/db/*"]
}
}
}