feat: add hub
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
export default defineAppConfig({
|
||||
hub: {
|
||||
notes: { title: 'Notizen', icon: 'i-lucide-notebook-pen', to: '/notes', locked: true }
|
||||
}
|
||||
})
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
// "New", bottom right.
|
||||
// "Neu", bottom right.
|
||||
// The list opens with NotesDrawer (mobile) and NotesSidebarHandle (desktop).
|
||||
const { creating, createAndOpen } = useNewNote()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pointer-events-none fixed inset-x-0 bottom-0 z-40 flex justify-end px-4 pb-[max(1rem,env(safe-area-inset-bottom))] md:px-6 md:pb-6">
|
||||
<UTooltip text="New note" :kbds="['meta', 'alt', 'n']">
|
||||
<UTooltip text="Neue Notiz" :kbds="['meta', 'alt', 'n']">
|
||||
<UButton
|
||||
icon="i-lucide-square-pen"
|
||||
label="New"
|
||||
label="Neu"
|
||||
size="lg"
|
||||
class="pointer-events-auto rounded-full px-5 shadow-lg"
|
||||
:loading="creating"
|
||||
|
||||
@@ -24,7 +24,7 @@ function onTouchMove(event: TouchEvent) {
|
||||
<div class="md:hidden">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Show notes"
|
||||
aria-label="Notizen anzeigen"
|
||||
class="fixed inset-x-0 top-0 z-30 flex h-7 touch-none items-start justify-center pt-2"
|
||||
@click="drawerOpen = true"
|
||||
@touchstart.passive="onTouchStart"
|
||||
@@ -33,7 +33,11 @@ function onTouchMove(event: TouchEvent) {
|
||||
<span class="h-1.5 w-12 rounded-full bg-accented" />
|
||||
</button>
|
||||
|
||||
<UDrawer v-model:open="drawerOpen" direction="top" title="Notes" :ui="{ content: 'max-h-[85dvh]', body: 'overflow-y-auto p-0 sm:p-0' }">
|
||||
<UDrawer v-model:open="drawerOpen" direction="top" title="Notizen" :ui="{ content: 'max-h-[85dvh]', body: 'overflow-y-auto p-0 sm:p-0', header: 'px-2' }">
|
||||
<!-- In the title row, right aligned; the header is inset like the list's search bar (px-2), so they line up -->
|
||||
<template #actions>
|
||||
<AppHeaderActions />
|
||||
</template>
|
||||
<template #body>
|
||||
<NotesList />
|
||||
</template>
|
||||
|
||||
@@ -14,20 +14,21 @@ const route = useRoute()
|
||||
<UInput
|
||||
v-model="query"
|
||||
icon="i-lucide-search"
|
||||
placeholder="Search"
|
||||
placeholder="Suchen"
|
||||
:loading="searching"
|
||||
class="w-full"
|
||||
@keydown.esc="query = ''"
|
||||
>
|
||||
<template v-if="query" #trailing>
|
||||
<UButton icon="i-lucide-x" color="neutral" variant="link" size="sm" aria-label="Clear search" @click="query = ''" />
|
||||
<UButton icon="i-lucide-x" color="neutral" variant="link" size="sm" aria-label="Suche leeren" @click="query = ''" />
|
||||
</template>
|
||||
</UInput>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="px-4 text-sm text-error">Couldn't load the notes: {{ errorMessage(error) }}</p>
|
||||
<p v-if="error" class="px-2 text-sm text-error md:px-4">Notizen konnten nicht geladen werden: {{ errorMessage(error) }}</p>
|
||||
|
||||
<nav class="px-2 pb-4">
|
||||
<!-- On mobile the titles and trash icons line up with the search bar's edges (the highlight reaches past them) -->
|
||||
<nav class="pb-4 md:px-2">
|
||||
<!-- The delete button shows on hover on desktop (instead of the date), always on mobile -->
|
||||
<div v-for="doc in docs" :key="doc.id" class="group relative">
|
||||
<NuxtLink
|
||||
@@ -44,15 +45,15 @@ const route = useRoute()
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
:aria-label="`Delete ${notesDocName(doc)}`"
|
||||
title="Delete"
|
||||
class="absolute inset-y-0 end-1 my-auto h-fit text-dimmed hover:text-error md:opacity-0 md:group-hover:opacity-100 md:focus-visible:opacity-100"
|
||||
:aria-label="`${notesDocName(doc)} löschen`"
|
||||
title="Löschen"
|
||||
class="absolute inset-y-0 end-0.5 my-auto md:end-1 h-fit text-dimmed hover:text-error md:opacity-0 md:group-hover:opacity-100 md:focus-visible:opacity-100"
|
||||
@click="deleteDoc(doc)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="!docs.length && !error && !searching" class="px-2 py-4 text-sm text-muted">
|
||||
{{ query.trim() ? 'No notes match.' : 'No notes yet.' }}
|
||||
{{ query.trim() ? 'Keine Notiz passt.' : 'Noch keine Notizen.' }}
|
||||
</p>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,10 @@ const { open } = useNotesSidebar()
|
||||
|
||||
<template>
|
||||
<aside v-show="open" class="hidden w-72 shrink-0 flex-col border-e border-default md:flex">
|
||||
<h1 class="px-4 py-4 text-lg font-semibold text-highlighted">Notes</h1>
|
||||
<div class="flex items-center gap-1 px-4 py-4">
|
||||
<h1 class="me-auto text-lg font-semibold text-highlighted">Notizen</h1>
|
||||
<AppHeaderActions />
|
||||
</div>
|
||||
<NotesList class="flex-1 overflow-y-auto" />
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@@ -9,8 +9,8 @@ const { open, toggle } = useNotesSidebar()
|
||||
delete buttons), as wide as the note's side padding (md:px-10), so it never covers the text either -->
|
||||
<button
|
||||
type="button"
|
||||
:title="`${open ? 'Hide' : 'Show'} notes (⌘E)`"
|
||||
:aria-label="open ? 'Hide notes' : 'Show notes'"
|
||||
:title="`Notizen ${open ? 'ausblenden' : 'anzeigen'} (⌘E)`"
|
||||
:aria-label="open ? 'Notizen ausblenden' : 'Notizen anzeigen'"
|
||||
class="group fixed inset-y-0 z-20 hidden w-10 cursor-pointer items-center focus:outline-none md:flex"
|
||||
:class="open ? 'left-72' : 'left-0'"
|
||||
@click="toggle"
|
||||
|
||||
@@ -4,7 +4,7 @@ const dayFormat = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-d
|
||||
|
||||
/** A note is named by its title, or by when it was created if it has none */
|
||||
export function notesDocName(doc: Pick<NotesDocSummary, 'title' | 'created'>): string {
|
||||
return doc.title || (doc.created ? nameFormat.format(new Date(doc.created)) : 'Untitled')
|
||||
return doc.title || (doc.created ? nameFormat.format(new Date(doc.created)) : 'Ohne Titel')
|
||||
}
|
||||
|
||||
export function notesDocDay(iso: string | null): string {
|
||||
@@ -70,7 +70,7 @@ export function useNewNote() {
|
||||
closeOnMobile()
|
||||
await navigateTo(`/notes/${doc.id}`)
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Couldn\'t create a note', description: errorMessage(e), color: 'error', icon: 'i-lucide-triangle-alert' })
|
||||
toast.add({ title: 'Notiz konnte nicht erstellt werden', description: errorMessage(e), color: 'error', icon: 'i-lucide-triangle-alert' })
|
||||
} finally {
|
||||
creating.value = false
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export function useDeleteNote() {
|
||||
if (String(doc.id) === route.params.id) await navigateTo('/notes')
|
||||
await remove(doc.id)
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Couldn\'t delete the note', description: errorMessage(e), color: 'error', icon: 'i-lucide-triangle-alert' })
|
||||
toast.add({ title: 'Notiz konnte nicht gelöscht werden', description: errorMessage(e), color: 'error', icon: 'i-lucide-triangle-alert' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
useHead({ title: 'Notes' })
|
||||
useHead({ title: 'Notizen' })
|
||||
|
||||
const { toggleList } = useNotesSidebar()
|
||||
const { createAndOpen } = useNewNote()
|
||||
@@ -13,7 +13,7 @@ defineShortcuts({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PasswordGate title="Notes">
|
||||
<PasswordGate title="Notizen">
|
||||
<div class="flex h-dvh">
|
||||
<NotesSidebar />
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const editorProps = {
|
||||
|
||||
const { queue } = useNotesAutosave(id, summary => patchSummary(id, summary))
|
||||
|
||||
useHead({ title: () => doc.value ? notesDocName({ title: title.value, created: doc.value.created }) : 'Notes' })
|
||||
useHead({ title: () => doc.value ? notesDocName({ title: title.value, created: doc.value.created }) : 'Notizen' })
|
||||
|
||||
watch(title, (value) => {
|
||||
patchSummary(id, { title: value.trim() })
|
||||
@@ -47,20 +47,20 @@ onBeforeUnmount(() => mobileQuery?.removeEventListener('change', updateMobile))
|
||||
|
||||
const toolbarItems: EditorToolbarItem[][] = [
|
||||
[
|
||||
{ kind: 'heading', level: 1, icon: 'i-lucide-heading-1', 'aria-label': 'Heading 1' },
|
||||
{ kind: 'heading', level: 2, icon: 'i-lucide-heading-2', 'aria-label': 'Heading 2' }
|
||||
{ kind: 'heading', level: 1, icon: 'i-lucide-heading-1', 'aria-label': 'Überschrift 1' },
|
||||
{ kind: 'heading', level: 2, icon: 'i-lucide-heading-2', 'aria-label': 'Überschrift 2' }
|
||||
],
|
||||
[
|
||||
{ kind: 'mark', mark: 'bold', icon: 'i-lucide-bold', 'aria-label': 'Bold' },
|
||||
{ kind: 'mark', mark: 'italic', icon: 'i-lucide-italic', 'aria-label': 'Italic' },
|
||||
{ kind: 'mark', mark: 'strike', icon: 'i-lucide-strikethrough', 'aria-label': 'Strikethrough' },
|
||||
{ kind: 'mark', mark: 'bold', icon: 'i-lucide-bold', 'aria-label': 'Fett' },
|
||||
{ kind: 'mark', mark: 'italic', icon: 'i-lucide-italic', 'aria-label': 'Kursiv' },
|
||||
{ kind: 'mark', mark: 'strike', icon: 'i-lucide-strikethrough', 'aria-label': 'Durchgestrichen' },
|
||||
{ kind: 'mark', mark: 'code', icon: 'i-lucide-code', 'aria-label': 'Code' },
|
||||
{ kind: 'link', icon: 'i-lucide-link', 'aria-label': 'Link' }
|
||||
],
|
||||
[
|
||||
{ kind: 'bulletList', icon: 'i-lucide-list', 'aria-label': 'Bullet list' },
|
||||
{ kind: 'orderedList', icon: 'i-lucide-list-ordered', 'aria-label': 'Numbered list' },
|
||||
{ kind: 'blockquote', icon: 'i-lucide-text-quote', 'aria-label': 'Quote' }
|
||||
{ kind: 'bulletList', icon: 'i-lucide-list', 'aria-label': 'Aufzählung' },
|
||||
{ kind: 'orderedList', icon: 'i-lucide-list-ordered', 'aria-label': 'Nummerierte Liste' },
|
||||
{ kind: 'blockquote', icon: 'i-lucide-text-quote', 'aria-label': 'Zitat' }
|
||||
]
|
||||
]
|
||||
|
||||
@@ -73,7 +73,7 @@ const toolbarItems: EditorToolbarItem[][] = [
|
||||
color="error"
|
||||
variant="subtle"
|
||||
icon="i-lucide-triangle-alert"
|
||||
title="Couldn't load this note"
|
||||
title="Notiz konnte nicht geladen werden"
|
||||
:description="errorMessage(error)"
|
||||
/>
|
||||
|
||||
@@ -82,7 +82,7 @@ const toolbarItems: EditorToolbarItem[][] = [
|
||||
<input
|
||||
v-model="title"
|
||||
:placeholder="notesDocName({ title: '', created: doc.created })"
|
||||
aria-label="Title"
|
||||
aria-label="Titel"
|
||||
class="min-w-0 flex-1 bg-transparent text-3xl font-semibold text-highlighted outline-none placeholder:text-dimmed"
|
||||
>
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@ const toolbarItems: EditorToolbarItem[][] = [
|
||||
v-slot="{ editor }"
|
||||
v-model="content"
|
||||
content-type="markdown"
|
||||
placeholder="Write something…"
|
||||
placeholder="Schreib etwas …"
|
||||
:mention="false"
|
||||
:starter-kit="{ link: { openOnClick: true } }"
|
||||
:editor-props="editorProps"
|
||||
|
||||
@@ -8,7 +8,7 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="flex h-full items-center justify-center p-8 text-center text-sm text-muted">
|
||||
<span class="md:hidden">Pull down from the top for your notes, or start a new one.</span>
|
||||
<span class="hidden md:inline">Pick a note, or start a new one.</span>
|
||||
<span class="md:hidden">Von oben herunterziehen für deine Notizen, oder eine neue beginnen.</span>
|
||||
<span class="hidden md:inline">Notiz auswählen oder eine neue beginnen.</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,7 @@ export const NOTES_SUMMARY_FIELDS = 'title,created,updated'
|
||||
export function notesTable(event: H3Event): string {
|
||||
const { baserowNotesAppTableId } = useRuntimeConfig(event)
|
||||
if (!baserowNotesAppTableId) {
|
||||
throw createError({ statusCode: 500, statusMessage: 'NUXT_BASEROW_NOTES_APP_TABLE_ID is not set' })
|
||||
throw createError({ statusCode: 500, statusMessage: 'NUXT_BASEROW_NOTES_APP_TABLE_ID ist nicht gesetzt' })
|
||||
}
|
||||
return baserowNotesAppTableId
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export function notesTable(event: H3Event): string {
|
||||
export function notesDocId(event: H3Event): number {
|
||||
const id = Number(getRouterParam(event, 'id'))
|
||||
if (!Number.isInteger(id) || id < 1) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Invalid note' })
|
||||
throw createError({ statusCode: 400, statusMessage: 'Ungültige Notiz' })
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user