feat: add hub
This commit is contained in:
@@ -2,9 +2,14 @@
|
||||
|
||||
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:
|
||||
picked up automatically. The UI is German (`lang="de"` and Nuxt UI's German locale in `app/app.vue`; only the CV
|
||||
has an English version too), and so are the error messages of the API. Every app has a house button back to the hub
|
||||
and a light / dark switch in its header, both from `<AppHeaderActions>` (one size and style everywhere) (Notizen: in the title row); the
|
||||
choice of light or dark applies to the whole site. The root only holds what apps share:
|
||||
|
||||
- the shell (`app/app.vue`, global CSS, theme) and an empty `/`
|
||||
- the shell (`app/app.vue`, global CSS, theme) and the hub on `/`: a tile per app (icon, title, a lock
|
||||
for the protected ones), sorted by title. Each app registers its tile in its own `app/app.config.ts` under
|
||||
`hub.<name>` (`HubApp` in `shared/types/hub.ts`).
|
||||
- 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.
|
||||
@@ -13,7 +18,7 @@ picked up automatically. The root only holds what apps share:
|
||||
(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)`, the client util `errorMessage(error)` and `usePdfExport(fileName)` ("Save as PDF"
|
||||
through the browser's print dialog: names the PDF and prints light also in dark mode; used by CV and Packer, with the `<SavePdfButton>` under the content)
|
||||
through the browser's print dialog: names the PDF and prints light also in dark mode; used by CV and Packliste, with the `<SavePdfButton>` under the content)
|
||||
|
||||
| Path | App |
|
||||
|---|---|
|
||||
@@ -21,7 +26,7 @@ picked up automatically. The root only holds what apps share:
|
||||
| `/wishlist` | [Wishlist](#wishlist) — `layers/wishlist/` |
|
||||
| `/notes` | [Notes](#notes) — `layers/notes/` (password protected) |
|
||||
| `/cv` | [CV](#cv) — `layers/cv/` |
|
||||
| `/packer` | [Packer](#packer) — `layers/packer/` |
|
||||
| `/packer` | [Packliste](#packliste) — `layers/packer/` |
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
@@ -36,9 +41,13 @@ pnpm dev
|
||||
2. Keep it contained: serve its API under `/api/<name>/`, and prefix `useState` / `useFetch` keys and app config
|
||||
with the app name. All layers share one auto-import namespace, so give components and exported utils
|
||||
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.
|
||||
3. Add its tile to the hub: `hub: { <name>: { title, icon, to: '/<name>' } }` in its
|
||||
`app/app.config.ts` (`locked: true` if it is behind the password).
|
||||
4. Don't use `~/` inside a layer — it points to the root `app/`. Use relative imports.
|
||||
|
||||
# Packer
|
||||
# Packliste
|
||||
|
||||
The app is named Packliste everywhere; only its URL, folder and code keep `packer`.
|
||||
|
||||
A public packing list built from the Baserow packing table (`NUXT_BASEROW_PACKER_TABLE_ID`). At the top you set the
|
||||
trip: number of days and people, where you sleep, weather, season and activities. The list below is grouped by category in table order, items sorted alphabetically.
|
||||
@@ -77,7 +86,7 @@ The settings are kept in the URL, so a list can be bookmarked or sent.
|
||||
tab's `sessionStorage`, survives reloads and is gone when the tab is closed.
|
||||
- **Als PDF speichern** (under the list, as on the CV) prints the list as a checklist (with the browser's "Save as PDF"): the trip in one line under the title
|
||||
(days, people, season, stay, activities, total weight), the categories in two columns, ticked items ticked. The
|
||||
settings and the buttons are left out; the file is named `packer_<days>_tage`.
|
||||
settings and the buttons are left out; the file is named `packliste_<days>_tage`.
|
||||
- **Zurücksetzen** (shown once anything differs from a fresh list) clears all of that and puts the settings back to
|
||||
their defaults (weights changed on the page go back to the table's too): 1 day, 1 person, today's season, Übernachtung and the automatic activities.
|
||||
|
||||
@@ -113,13 +122,17 @@ The header (name, description per language, photo, email, LinkedIn link) is set
|
||||
under `cv`. LinkedIn blocks scraping, so the photo and description are not fetched from there; put the photo in
|
||||
`layers/cv/public/`.
|
||||
|
||||
Next to it a light / dark switch (Nuxt UI's color mode, so it applies to the whole site; the PDF is always light).
|
||||
The accent color `cv.accent` is used in light mode and in the PDF; dark mode uses the site's amber like the other apps.
|
||||
|
||||
A DE / EN switch (remembered in the `cv-lang` cookie, German by default) translates the page's own text: headings,
|
||||
dates, durations, types, the description. `Area` comes from `area_german` on the German page; the rest of the table's text (`Institution`) is shown as stored.
|
||||
|
||||
**Table:** `From`, `To` (dates), `Type` (single select), `Institution`, `Area` (text, English), `area_german` (text, used on the German page, falls back to `Area`), `Place` (single select),
|
||||
`Grade` (number), `relevant` (boolean), `file` (file; each file is listed under its entry as a download, e.g. a
|
||||
thesis), `urls` (long text: one link per line, a blank line between groups such as projects; GitHub links show
|
||||
`owner/repo` with a GitHub icon, others their address; write `Label https://…` for custom text). Rows without `From`
|
||||
`owner/repo` with a GitHub icon, others their address; write `Label https://…` for custom text). On phones the
|
||||
links (also email, LinkedIn and files) show only their icon, with the text as tooltip. Rows without `From`
|
||||
are skipped. Baserow returns file URLs as `http://`, so they are rewritten to
|
||||
`NUXT_BASEROW_URL`.
|
||||
|
||||
@@ -127,8 +140,8 @@ are skipped. Baserow returns file URLs as `http://`, so they are rewritten to
|
||||
layers/cv/
|
||||
nuxt.config.ts baserowCvTableId
|
||||
app/
|
||||
app.config.ts cv: name, description, photo, email, linkedin
|
||||
pages/cv.vue language switch, header, Experience and Education
|
||||
app.config.ts cv: name, description, photo, email, linkedin, accent
|
||||
pages/cv.vue light / dark and language switch, header, Experience and Education
|
||||
composables/useCvLang.ts current language and the translated texts
|
||||
components/CvSection.vue one section: period, duration, area, institution, type, grade
|
||||
utils/cvDate.ts period and duration, open-ended until today
|
||||
@@ -154,7 +167,7 @@ Shortcuts (Ctrl instead of ⌘ outside macOS): **⌘ ⌥ N** new note, **⌘ E**
|
||||
**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.
|
||||
"Ohne Titel" at the bottom. The token needs read, create, update and delete.
|
||||
|
||||
```
|
||||
layers/notes/
|
||||
@@ -165,7 +178,7 @@ app/
|
||||
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/NotesActionBar.vue "Neu", 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
|
||||
@@ -226,7 +239,7 @@ self-hosted Baserow tables.
|
||||
| `NUXT_BASEROW_NOTES_TABLE_ID` | Notes table |
|
||||
| `NUXT_BASEROW_WISHLIST_TABLE_ID` | Wishlist table (see [Wishlist](#wishlist)) |
|
||||
| `NUXT_BASEROW_CV_TABLE_ID` | CV table (see [CV](#cv)) |
|
||||
| `NUXT_BASEROW_PACKER_TABLE_ID` | Packing table (see [Packer](#packer)) |
|
||||
| `NUXT_BASEROW_PACKER_TABLE_ID` | Packing table (see [Packliste](#packliste)) |
|
||||
| `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`) |
|
||||
@@ -236,10 +249,10 @@ In `layers/diary/app/app.config.ts`, `diary.showCovers: true` shows a generated
|
||||
## What it does
|
||||
|
||||
- `/diary`: reviews and notes on one timeline, newest first, grouped by year.
|
||||
- Only entries with text are shown. Long text is collapsed behind "Read more".
|
||||
- Search matches every word against the kind (`review`, `note`), the medium (`book`, `movie`, `album`),
|
||||
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;
|
||||
- Only entries with text are shown. Long text is collapsed behind "Weiterlesen".
|
||||
- Search matches every word against the kind (`rezension`, `notiz`), the medium (`buch`, `film`, `album`),
|
||||
the date (`okt 2025`, `2025-08`), the title, the author and the text. Matches in the text are highlighted.
|
||||
- "Eintragen" opens a form with a Notiz / Rezension 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 returns 401
|
||||
(the shared login, see the top of this file). Changing `NUXT_SESSION_SECRET` logs out every browser.
|
||||
@@ -253,9 +266,9 @@ app/
|
||||
app.config.ts diary.showCovers
|
||||
pages/diary.vue password gate around the timeline
|
||||
components/
|
||||
DiaryTimeline.vue the diary: search, Log button, entries grouped by year
|
||||
DiaryTimeline.vue the diary: search, Eintragen button, entries grouped by year
|
||||
DiaryEntry.vue one timeline entry (date, title, text, read more)
|
||||
LogModal.vue Note / Review switch around the two forms
|
||||
LogModal.vue Notiz / Rezension switch around the two forms
|
||||
NoteForm.vue, ReviewForm.vue
|
||||
CoverTile.vue generated cover, used when showCovers is on
|
||||
composables/
|
||||
|
||||
Reference in New Issue
Block a user