feat: add notes app
This commit is contained in:
@@ -2,14 +2,23 @@
|
||||
|
||||
One Nuxt 4 + Nuxt UI deployment made of self-contained apps. Each app is a [Nuxt layer](https://nuxt.com/docs/guide/going-further/layers)
|
||||
in `layers/<name>/` with its own pages, components, composables, server routes and config; layers in that folder are
|
||||
picked up automatically. The root only holds what apps share: the shell (`app/app.vue`, global CSS, theme), an empty `/`, and the Baserow
|
||||
client `server/utils/baserow.ts` (`listRows`, `createRow`, `updateRow`, `listFields`, each taking a table ID).
|
||||
`NUXT_BASEROW_URL` and `NUXT_BASEROW_TOKEN` are shared; each app reads its own table IDs.
|
||||
picked up automatically. The root only holds what apps share:
|
||||
|
||||
- the shell (`app/app.vue`, global CSS, theme) and an empty `/`
|
||||
- the Baserow client `server/utils/baserow.ts` (`listRows`, `getRow`, `createRow`, `updateRow`, `deleteRow`,
|
||||
`listFields`, each taking a table ID). `NUXT_BASEROW_URL` and `NUXT_BASEROW_TOKEN` are shared; each app reads its
|
||||
own table IDs.
|
||||
- the login. One password (`NUXT_DIARY_PASSWORD`) unlocks every protected app: `server/middleware/auth.ts` returns
|
||||
401 for the API prefixes in `PROTECTED_PREFIXES` until `/api/auth/login` has set the session cookie
|
||||
(signed with `NUXT_SESSION_SECRET`, http-only, 30 days), and a page wraps itself in `<PasswordGate>` to show the
|
||||
password prompt. To protect a new app, add its API prefix and use the gate.
|
||||
- `readValidBody(event, zodSchema)` and the client util `errorMessage(error)`
|
||||
|
||||
| Path | App |
|
||||
|---|---|
|
||||
| `/diary` | [Diary](#diary) — `layers/diary/` (password protected) |
|
||||
| `/wishlist` | [Wishlist](#wishlist) — `layers/wishlist/` |
|
||||
| `/notes` | [Notes](#notes) — `layers/notes/` (password protected) |
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
@@ -26,6 +35,42 @@ pnpm dev
|
||||
names that won't clash with another app's (e.g. `DiaryEntry`, not `Entry`).
|
||||
3. Don't use `~/` inside a layer — it points to the root `app/`. Use relative imports.
|
||||
|
||||
# Notes
|
||||
|
||||
Minimal notes: a collapsible list of notes on the left, a WYSIWYG Markdown editor (Nuxt UI `UEditor`) on the right.
|
||||
No folders. A note is named by its title, or by when it was created if the title is empty. Changes save themselves
|
||||
0.8 s after you stop typing, and when you leave the note or close the tab. Select text for the formatting toolbar;
|
||||
Markdown shortcuts (`# `, `- `, `**bold**`, …) also work.
|
||||
|
||||
Search at the top of the list matches title and text (`/api/notes/docs?q=`, case-insensitive, parts of words too).
|
||||
It uses Baserow's search; Baserow only searches the fields a request includes, so the search includes `content`.
|
||||
|
||||
Shortcuts (Ctrl instead of ⌘ outside macOS): **⌘ ⌥ N** new note, **⌘ E** show/hide the note list (so the editor's
|
||||
⌘ E for inline code is turned off; use backticks or the toolbar). Plain ⌘ N can't be used, the browser keeps it.
|
||||
|
||||
**Table** (`NUXT_BASEROW_NOTES_APP_TABLE_ID`): `title` (single line text), `content` (long text without rich text; holds the
|
||||
Markdown), `created` and `updated` (dates with time, set by the
|
||||
server when a note is created or saved). The list is sorted by `created`, newest first. Rows added in Baserow directly have no dates and show as
|
||||
"Untitled" at the bottom. The token needs read, create, update and delete.
|
||||
|
||||
```
|
||||
layers/notes/
|
||||
app/
|
||||
pages/notes.vue password gate, sidebar + the open note
|
||||
pages/notes/index.vue nothing selected
|
||||
pages/notes/[id].vue title, editor, delete
|
||||
components/NotesList.vue the note list (open, delete)
|
||||
components/NotesSidebar.vue desktop: the list on the left (open/closed is remembered in a cookie)
|
||||
components/NotesDrawer.vue mobile: the list, pulled down from the top
|
||||
components/NotesActionBar.vue "New", bottom right
|
||||
components/NotesSidebarHandle.vue desktop: chevron on the sidebar edge, on hover, shows/hides the list
|
||||
composables/
|
||||
useNotes.ts the list, create/delete, note names
|
||||
useNotesAutosave.ts debounced saving
|
||||
server/api/notes/docs/ GET list, POST create, GET/PATCH/DELETE [id]
|
||||
shared/ NotesDoc type, update schema
|
||||
```
|
||||
|
||||
# Wishlist
|
||||
|
||||
A public shelf of the books in the Baserow wishlist table (`NUXT_BASEROW_WISHLIST_TABLE_ID`): cover, German title
|
||||
@@ -77,7 +122,8 @@ self-hosted Baserow tables.
|
||||
| `NUXT_BASEROW_REVIEWS_TABLE_ID` | Reviews table |
|
||||
| `NUXT_BASEROW_NOTES_TABLE_ID` | Notes table |
|
||||
| `NUXT_BASEROW_WISHLIST_TABLE_ID` | Wishlist table (see [Wishlist](#wishlist)) |
|
||||
| `NUXT_DIARY_PASSWORD` | Password that unlocks the diary |
|
||||
| `NUXT_BASEROW_NOTES_APP_TABLE_ID` | Table of the notes app (see [Notes](#notes)), not the diary's notes |
|
||||
| `NUXT_DIARY_PASSWORD` | Password that unlocks the diary and notes |
|
||||
| `NUXT_SESSION_SECRET` | Signs the login cookie, at least 32 characters (`openssl rand -hex 32`) |
|
||||
|
||||
In `layers/diary/app/app.config.ts`, `diary.showCovers: true` shows a generated title tile next to each review.
|
||||
@@ -90,9 +136,8 @@ In `layers/diary/app/app.config.ts`, `diary.showCovers: true` shows a generated
|
||||
the date (`aug 2025`, `2025-08`), the title, the author and the text. Matches in the text are highlighted.
|
||||
- "Log" opens a form with a Note / Review switch. A review is written for a book picked from the books table;
|
||||
its "Read on" date is saved as that book's `read_date`.
|
||||
- Until the password is entered, only a password prompt is shown and every `/api/diary/*` route except
|
||||
`/api/diary/session` and `/api/diary/login` returns 401. A correct password sets a signed, http-only cookie valid for 30 days.
|
||||
Changing `NUXT_SESSION_SECRET` logs out every browser.
|
||||
- Until the password is entered, only a password prompt is shown and every `/api/diary/*` route returns 401
|
||||
(the shared login, see the top of this file). Changing `NUXT_SESSION_SECRET` logs out every browser.
|
||||
|
||||
## Code layout
|
||||
|
||||
@@ -107,22 +152,18 @@ app/
|
||||
DiaryEntry.vue one timeline entry (date, title, text, read more)
|
||||
LogModal.vue Note / Review switch around the two forms
|
||||
NoteForm.vue, ReviewForm.vue
|
||||
PasswordModal.vue
|
||||
CoverTile.vue generated cover, used when showCovers is on
|
||||
composables/
|
||||
useDiary.ts loads reviews + notes, searches, sorts and groups them by year
|
||||
useSaveEntry.ts shared submit logic of the forms
|
||||
useLog.ts open/close state of the Log modal
|
||||
utils/ dates, icons, search highlighting, error messages
|
||||
utils/ dates, icons, search highlighting
|
||||
types/entry.ts a timeline entry (review or note)
|
||||
server/
|
||||
middleware/diary-auth.ts locks /api/diary/* until unlocked
|
||||
api/diary/ session, login, reviews and notes (GET list, POST create), books (for the review form)
|
||||
api/diary/ reviews and notes (GET list, POST create), books (for the review form)
|
||||
utils/
|
||||
diaryTable.ts the diary's table IDs (the Baserow client itself is in the root server/utils)
|
||||
review.ts, note.ts, book.ts Baserow row -> app type
|
||||
auth.ts session cookie and password check
|
||||
validation.ts readValidBody(event, zodSchema)
|
||||
shared/
|
||||
types/ Review, Note
|
||||
utils/medium.ts the review media: label and creator label
|
||||
@@ -137,7 +178,7 @@ shared/
|
||||
|
||||
**Books:** `title`, `title_german`, `authors` (link) and `read_date` (date).
|
||||
|
||||
**Notes:** `Datum` (date) and `Notes` (long text).
|
||||
**Notes** (the diary's, now named `diary` in Baserow): `Datum` (date) and `Notes` (long text).
|
||||
|
||||
Rows without a date are skipped. Text is plain text: line breaks are kept and it is never rendered as HTML.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user