- Migrering 0004: notes-tabell (nodes i kunnskapsgrafen) - REST API: GET/PATCH notat - PG-adapter med 500ms debounce og 10s polling - NotesBlock: tittel + fritekst med auto-lagring og status - Seed: notater for begge workspaces, kalenderside med 2-1 layout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
164 lines
3 KiB
Svelte
164 lines
3 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import { createNote } from '$lib/notes/create.svelte';
|
|
import type { NoteConnection } from '$lib/notes/types';
|
|
|
|
let { props = {} }: { props?: Record<string, unknown> } = $props();
|
|
|
|
const noteId = props.noteId as string | undefined;
|
|
|
|
let conn = $state<NoteConnection | null>(null);
|
|
let title = $state('');
|
|
let content = $state('');
|
|
let initialized = $state(false);
|
|
|
|
// Synk fra server ved første lasting
|
|
$effect(() => {
|
|
if (conn?.note && !initialized) {
|
|
title = conn.note.title;
|
|
content = conn.note.content;
|
|
initialized = true;
|
|
}
|
|
});
|
|
|
|
function handleTitleInput() {
|
|
conn?.save({ title });
|
|
}
|
|
|
|
function handleContentInput() {
|
|
conn?.save({ content });
|
|
}
|
|
|
|
let updatedAt = $derived.by(() => {
|
|
if (!conn?.note?.updated_at) return '';
|
|
const d = new Date(conn.note.updated_at);
|
|
return d.toLocaleString('nb-NO', {
|
|
day: 'numeric',
|
|
month: 'short',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
});
|
|
|
|
onMount(() => {
|
|
if (noteId) {
|
|
conn = createNote(noteId);
|
|
}
|
|
return () => conn?.destroy();
|
|
});
|
|
</script>
|
|
|
|
{#if !noteId}
|
|
<div class="no-note"><p>Ingen notat konfigurert for denne blokken.</p></div>
|
|
{:else if conn?.loading}
|
|
<div class="no-note"><p>Laster...</p></div>
|
|
{:else}
|
|
<div class="notes-wrapper">
|
|
<input
|
|
type="text"
|
|
class="note-title"
|
|
placeholder="Tittel..."
|
|
bind:value={title}
|
|
oninput={handleTitleInput}
|
|
/>
|
|
<textarea
|
|
class="note-content"
|
|
placeholder="Skriv her..."
|
|
bind:value={content}
|
|
oninput={handleContentInput}
|
|
></textarea>
|
|
<div class="note-footer">
|
|
{#if conn?.saving}
|
|
<span class="status saving">Lagrer...</span>
|
|
{:else if updatedAt}
|
|
<span class="status saved">Lagret {updatedAt}</span>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
{#if conn?.error}
|
|
<div class="error">{conn.error}</div>
|
|
{/if}
|
|
{/if}
|
|
|
|
<style>
|
|
.notes-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
min-height: 0;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.note-title {
|
|
background: transparent;
|
|
border: none;
|
|
border-bottom: 1px solid #2d3148;
|
|
color: #e1e4e8;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
font-family: inherit;
|
|
padding: 0.25rem 0;
|
|
}
|
|
|
|
.note-title:focus {
|
|
outline: none;
|
|
border-bottom-color: #3b82f6;
|
|
}
|
|
|
|
.note-title::placeholder {
|
|
color: #8b92a5;
|
|
}
|
|
|
|
.note-content {
|
|
flex: 1;
|
|
background: transparent;
|
|
border: none;
|
|
color: #e1e4e8;
|
|
font-size: 0.85rem;
|
|
font-family: inherit;
|
|
line-height: 1.6;
|
|
resize: none;
|
|
padding: 0;
|
|
min-height: 100px;
|
|
}
|
|
|
|
.note-content:focus {
|
|
outline: none;
|
|
}
|
|
|
|
.note-content::placeholder {
|
|
color: #8b92a5;
|
|
}
|
|
|
|
.note-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding-top: 0.25rem;
|
|
border-top: 1px solid #2d3148;
|
|
}
|
|
|
|
.status {
|
|
font-size: 0.7rem;
|
|
color: #8b92a5;
|
|
}
|
|
|
|
.status.saving {
|
|
color: #f59e0b;
|
|
}
|
|
|
|
.no-note {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
color: #8b92a5;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.error {
|
|
font-size: 0.75rem;
|
|
color: #f87171;
|
|
padding: 0.25rem 0;
|
|
}
|
|
</style>
|