169 lines
4.3 KiB
Svelte
169 lines
4.3 KiB
Svelte
|
<script lang="ts">
|
||
|
import { run, preventDefault } from 'svelte/legacy';
|
||
|
import { notes } from '$lib';
|
||
|
import { page } from '$app/stores';
|
||
|
import { goto } from '$app/navigation';
|
||
|
import { renderMarkdown } from '$lib/markdown';
|
||
|
|
||
|
interface Note {
|
||
|
id: string;
|
||
|
title: string;
|
||
|
content: string;
|
||
|
createdAt: Date;
|
||
|
updatedAt: Date;
|
||
|
}
|
||
|
|
||
|
let note: Note | undefined = $state();
|
||
|
let isEditing = $state($page.url.searchParams.get('edit') === 'true');
|
||
|
let editedTitle = $state('');
|
||
|
let editedContent = $state('');
|
||
|
|
||
|
let id = $derived($page.params.id);
|
||
|
|
||
|
run(() => {
|
||
|
if ($notes && id) {
|
||
|
note = $notes.find((n) => n.id === id);
|
||
|
if (note) {
|
||
|
editedTitle = note.title;
|
||
|
editedContent = note.content;
|
||
|
} else if ($notes.length > 0) {
|
||
|
// Only redirect if we have loaded notes and didn't find this one
|
||
|
goto('/');
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
async function handleSave() {
|
||
|
if (!editedTitle || !editedContent) return;
|
||
|
|
||
|
try {
|
||
|
await notes.update(id, {
|
||
|
title: editedTitle,
|
||
|
content: editedContent
|
||
|
});
|
||
|
isEditing = false;
|
||
|
} catch (error) {
|
||
|
console.error('Failed to update note:', error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async function handleLinkClick(event: MouseEvent | KeyboardEvent) {
|
||
|
const target = event.target as HTMLElement;
|
||
|
if (target.tagName === 'A' && target.classList.contains('note-link')) {
|
||
|
event.preventDefault();
|
||
|
const title = target.getAttribute('data-title');
|
||
|
if (!title) return;
|
||
|
|
||
|
// Check if a note with this title exists
|
||
|
const existingNote = $notes.find((n) => n.title === title);
|
||
|
if (existingNote) {
|
||
|
// Navigate to the existing note
|
||
|
goto(`/notes/${existingNote.id}`);
|
||
|
} else {
|
||
|
// Navigate to the new note page with the title pre-filled
|
||
|
goto(`/notes/new?title=${encodeURIComponent(title)}`);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<div
|
||
|
class="container"
|
||
|
onclick={handleLinkClick}
|
||
|
onkeydown={(event) => {
|
||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||
|
handleLinkClick(event);
|
||
|
}
|
||
|
}}
|
||
|
role="button"
|
||
|
tabindex="0"
|
||
|
>
|
||
|
<section class="section">
|
||
|
{#if note}
|
||
|
{#if isEditing}
|
||
|
<form onsubmit={preventDefault(handleSave)}>
|
||
|
<div class="field">
|
||
|
<label class="label" for="title">Title</label>
|
||
|
<div class="control">
|
||
|
<input id="title" class="input" type="text" bind:value={editedTitle} required />
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="field">
|
||
|
<label class="label" for="content">Content</label>
|
||
|
<div class="control">
|
||
|
<textarea id="content" class="textarea" bind:value={editedContent} rows="10" required
|
||
|
></textarea>
|
||
|
</div>
|
||
|
<div class="mt-4 box">
|
||
|
<label class="label" for="preview">Preview</label>
|
||
|
<div id="preview" class="content" aria-live="polite">
|
||
|
{#await renderMarkdown(note.content)}
|
||
|
<p>Rendering markdown...</p>
|
||
|
{:then markdownContent}
|
||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||
|
{@html markdownContent}
|
||
|
{:catch err}
|
||
|
<p>Error rendering the markdown content!i {err.message}</p>
|
||
|
{/await}
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="field is-grouped">
|
||
|
<div class="control">
|
||
|
<button type="submit" class="button is-primary">Save</button>
|
||
|
</div>
|
||
|
<div class="control">
|
||
|
<button
|
||
|
type="button"
|
||
|
class="button is-light"
|
||
|
onclick={() => {
|
||
|
isEditing = false;
|
||
|
goto(`/notes/${id}`);
|
||
|
}}
|
||
|
>
|
||
|
Cancel
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</form>
|
||
|
{:else}
|
||
|
<div class="level">
|
||
|
<div class="level-left">
|
||
|
<div class="level-item">
|
||
|
<h1 class="title">{note.title}</h1>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="level-right">
|
||
|
<div class="level-item">
|
||
|
<button class="button is-primary" onclick={() => (isEditing = true)}> Edit </button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="content">
|
||
|
{#await renderMarkdown(note.content)}
|
||
|
<p>Rendering markdown...</p>
|
||
|
{:then markdownContent}
|
||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||
|
{@html markdownContent}
|
||
|
{:catch err}
|
||
|
<p>Error rendering the markdown content!i {err.message}</p>
|
||
|
{/await}
|
||
|
</div>
|
||
|
|
||
|
<p class="has-text-grey is-size-7">
|
||
|
Last updated: {note.updatedAt.toLocaleString()}
|
||
|
</p>
|
||
|
{/if}
|
||
|
{:else}
|
||
|
<div class="notification is-danger">Note not found</div>
|
||
|
{/if}
|
||
|
|
||
|
<div class="mt-4">
|
||
|
<a href="/" class="button is-light">Back to Notes</a>
|
||
|
</div>
|
||
|
</section>
|
||
|
</div>
|