Commit Graph

23 Commits

Author SHA1 Message Date
Erik Thiele
b85e0bc256 Shrink header logo to match LinkVault text height
24px was noticeably taller than the brand text; 18px lines up with
the font-size (1.2rem) much better.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 16:26:07 +02:00
Erik Thiele
d4d43ed5d9 Fix loading-spinner spacing and add logo to header brand
The "Hinzufügen" buttons reserved layout space for their loading
spinner even when idle, since .htmx-indicator only toggled opacity
(not display), leaving a visible gap before the centered text. Switch
to display:none/inline-flex so the spinner takes no space until an
htmx request is in flight, and drop the static plus icon next to
"Hinzufügen" so the idle button just shows centered text with the
spinner appearing in its place while loading.

Also adds the favicon as a small logo image in front of "LinkVault"
in the shared topbar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 16:19:42 +02:00
Erik Thiele
547374aa6d Fix facet_sort dropdown silently failing to navigate
The onchange handler used `new URL(location)`. Inline event handler
attributes execute inside an implicit `with(document){...}` scope, and
`document.URL` is a built-in string property — so the bare `URL`
identifier resolved to that string instead of the global URL
constructor, throwing "URL is not a constructor" on every change.
The error wasn't visible anywhere in the UI, so the dropdown appeared
to update (its own selected option changed) while the page silently
never navigated, and a reload reverted it to the default.

Fix: reference `window.URL` explicitly to bypass the shadowing.
Verified the dropdown now actually re-sorts the sidebar and the
change survives a reload on both the Links and Prompts pages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 13:02:08 +02:00
Erik Thiele
d13b8ce5bc Make sidebar category/manufacturer sorting selectable
Kategorien and Hersteller/Quellen lists were hardcoded to sort by
count descending. Add a small dropdown (Name A-Z / Anzahl) above the
lists on both the Links and Prompts pages, backed by a new facet_sort
query param plumbed through facets()/prompt_facets(). Defaults to
alphabetical, which was the actual ask; count sort stays available
for anyone who preferred the old behavior. All sidebar facet links
carry the current facet_sort so it doesn't reset when filtering.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 12:27:21 +02:00
Erik Thiele
af65171d87 Add a favicon
Simple SVG icon matching the app's look: a link/chain glyph in white
on the indigo accent color, same rounded-square shape as the card
avatars. Mounts a new /static route via StaticFiles and references it
from base.html's <head>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 12:14:37 +02:00
Erik Thiele
604c4ea9c2 Add invite-code registration and admin user management
Open registration let anyone with the URL create an account. Two
changes address that:

- REGISTRATION_CODE (.env, optional): when set, registration requires
  entering it correctly. Empty/unset keeps registration open, so
  existing installs are unaffected until configured.
- is_admin flag on User: the first account ever created on an install
  becomes admin automatically (existing installs get their oldest
  account promoted via the startup migration, so nobody is locked
  out of user management after upgrading).

Admins get a new "Benutzerverwaltung" panel in Einstellungen listing
every account (email, link/prompt counts, join date) with a delete
button per account — deleting cascades to that user's links and
prompts via the existing relationship cascade. Deleting your own
account through this page is blocked (redirects with an error) to
avoid accidental admin lockout. Non-admins get a 403 on the
/settings/users routes.

Also fixes several pre-existing German pluralization bugs found while
writing the new counts ("2 Linke" -> "2 Links", "Kontoen"/"Konton" ->
"Konten") — irregular plurals need a full word swap, not a suffix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 11:16:01 +02:00
Erik Thiele
cfcdf9a667 Add timestamp to CSV/SQL export filenames
linkvault-links.csv/.sql -> linkvault-links_2026-07-30_1542.csv/.sql,
so repeated exports don't just overwrite the same downloaded file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 15:43:46 +02:00
Erik Thiele
e90921ebb0 Fix brand color regression from making the logo a link
Turning <div class="brand"> into <a class="brand"> (for the topbar
refactor) let the global 'a { color: var(--accent-hover) }' rule
override the "Link" text, tinting it purple instead of the intended
near-white body text color. Set color explicitly on .brand so only
the "Vault" span keeps the accent color.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 15:28:05 +02:00
Erik Thiele
d436aca909 Bump version to 2.1.0
Markdown cheat sheet and full-width edit mode since 2.0.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 15:23:50 +02:00
Erik Thiele
84b6ca99ae Expand card to full grid width while editing
Both the link and prompt edit forms were squeezed into a single grid
column, making the summary/prompt textarea cramped. Add a
.card-edit-wide class (grid-column: 1 / -1) applied to the edit
partial's root element so it spans all columns instead, and lay out
category/tags side by side in the prompt edit form to use the extra
width. Also gives the prompt content textarea more rows (6 -> 12)
since there's now room for it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 15:22:48 +02:00
Erik Thiele
11eb770c8b Add a collapsible Markdown cheat sheet to the prompt input
A short, native <details>/<summary> reference (no JS) next to the
hint text, listing the basic syntax the new markdown rendering
supports: headings, bold/italic, lists, quotes, inline code, code
blocks, links.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 14:55:59 +02:00
Erik Thiele
78c95b522d Add a Prompts library alongside Links, bump to v2.0.0
New /prompts section extends the existing database (new Prompt model,
same SQLite file) and mirrors the Links experience: add a prompt, get
it auto-categorized and tagged by AI, filter by category, full-text
search, sort, edit, or have the AI re-categorize it. A header nav
switch ("Links" / "Prompts") toggles between the two collections; the
shared topbar/menu markup was factored into _topbar.html and
_topbar_actions.html so both pages (and settings) stay in sync.

Each prompt card has a dedicated copy-to-clipboard icon next to
edit/delete, so a stored prompt can be reused immediately. Copying
uses the raw markdown source (not the rendered HTML) so structure
survives when pasted into another AI tool. The copy handler tries the
async Clipboard API first and falls back to a hidden-textarea +
execCommand('copy') for plain-http/non-secure contexts, with clear
success/failure icon feedback either way.

Prompt content supports Markdown and is rendered server-side
(app/mdrender.py) for the card preview. Since this is user-supplied
HTML-adjacent content, rendering goes through two defenses: the raw
text is escaped (only '<' and '&', not '>', so blockquotes keep
working) before conversion so no raw tag can survive, and the
resulting HTML is passed through bleach with a tag/attribute/protocol
allowlist so Markdown-generated links can't carry a javascript: URL.
Verified against raw <script>, <img onerror>, and javascript: link
payloads. The edit form always shows the raw Markdown source, never
the rendered HTML.

Also bumps the default APP_VERSION (shown in the footer) from 1.0.0
to 2.0.0 to mark this feature addition.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 14:45:30 +02:00
Erik Thiele
7262aee63d Add settings page with CSV export, SQL dump export/import
New /settings page (linked from the header's burger menu) offers:
- CSV export of the user's links, for spreadsheet apps.
- SQL dump export as INSERT INTO links (...) statements, scoped to
  the current user only — never a raw full-database dump, since that
  would leak other accounts' password hashes and data. Embeddings are
  excluded (regenerated via "KI neu beschreiben lassen" if needed).
- SQL import that re-adds a previously exported dump to the current
  account (additive, doesn't touch existing links).

Import safety (app/backup.py): uploaded SQL is never executed against
the real database. Each non-comment line is required to start with
"insert into links" and is run one statement at a time against an
isolated in-memory SQLite database with only a whitelisted `links`
schema (no id/user_id columns) — sqlite3.execute() also rejects
multiple statements per call. Only after that succeeds are rows
copied into the real DB via the ORM, with user_id forced to the
logged-in user. Verified this rejects DROP TABLE, ATTACH DATABASE,
stacked statements, cross-table subqueries, and user_id injection.
Upload is capped at 2 MB.

Also adds download/upload icons and a proper file-input styling
pattern (visually-hidden input + <label> trigger + filename readout),
since the browser's ::file-selector-button pseudo-element didn't
render reliably in testing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 23:25:27 +02:00
Erik Thiele
4f99d51f9e Redesign header: theme toggle, user avatar, burger menu
The app was dark-only with the email shown as plain text and a bare
"Abmelden" button. Header now has:

- A light/dark theme toggle (sun/moon icon). Choice persists via
  localStorage and is applied before first paint (inline script in
  <head>) to avoid a flash of the wrong theme; falls back to the
  system preference on first visit. Added a light CSS variable set
  alongside the existing dark palette.
- A circular user icon (head-and-shoulders) showing the email as a
  tooltip, replacing the plain-text address.
- A burger-menu dropdown with the email, disabled placeholder items
  ("Tags verwalten", "Einstellungen" — marked "bald") to make room
  for future features, and the actual Abmelden action.

Dropdown opens/closes via a small vanilla-JS handler in base.html
(click outside or Escape closes it), consistent with the project's
no-build-step, mostly-HTMX frontend.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 23:01:22 +02:00
Erik Thiele
dca6033c40 Replace emoji icons with a monochrome SVG icon set
Emoji (📚 🔖 🏷️ ✎ ✕ 🕓  🤖 ⚠️ 🔍) rendered in each platform's own
colorful style and looked inconsistent. Add app/templates/_icons.html
with small inline SVG macros (Lucide-style, MIT-licensed path data,
stroke=currentColor) and use them throughout instead, so icons pick
up the muted/accent text colors like the rest of the UI.

The needs-review bookmark toggle now uses an outline vs. filled icon
for its two states instead of swapping emoji.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 22:50:52 +02:00
Erik Thiele
b6a236527e Add a "needs review" flag with a sidebar filter
Links can be bookmarked for a later look via a toggle button on the
card (🏷️/🔖), shown with a yellow border while flagged. A new
sidebar section "Merkliste" links to /?review=1, filtering to just
the flagged links; the count updates live via the existing facets
query.

Since the project has no migration framework, add a minimal
startup check that ALTER TABLEs in the needs_review column for
existing SQLite databases (create_all only creates missing tables).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 22:41:40 +02:00
Erik Thiele
8e9b49e57b Suggest existing categories/manufacturers when editing
The category and manufacturer fields in the edit form now offer the
already-used values via a datalist dropdown, while still allowing a
new value to be typed. Both edit entry points (open edit form and AI
re-describe) pass the current lists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 22:13:08 +02:00
Erik Thiele
fdfddf8ccf Restyle link cards as a uniform tile grid
Cards previously had ragged heights because summaries vary in length.
Follow the community-scripts.org card pattern instead: fixed-height
title and summary blocks (clamped to 2 and 3 lines with an ellipsis),
a colored letter avatar derived from the domain, category and
manufacturer as badges in the header, tags in a single clipped row,
and domain plus added-date in a footer.

The avatar color is derived locally from the domain, so no external
favicon requests are made.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 15:34:27 +02:00
Erik Thiele
4d96dfb9ac Use full page width with a responsive link grid
The layout was capped at 1200px and listed links in a single column.
Drop the cap and lay the cards out in a grid: three per row on wide
screens, two from 1000px, one below that.

To make the narrower cards work, shorten the edit action to an icon
button so the title and URL keep their room, and let the edit form's
field and button rows wrap.

Also fixes pre-existing horizontal overflow on phone widths by
stacking the sidebar above the content and letting the add-link form
wrap. Grid tracks use minmax(0, 1fr) so cards can shrink below their
content's intrinsic width instead of pushing the page wider.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 14:56:42 +02:00
Erik Thiele
148074e24c Add sorting by date or name
Adds a sort dropdown offering newest/oldest first and name A-Z/Z-A.
Sorting applies to plain listings, text search, and semantic search
results, and the sidebar facet links preserve the current choice.
Invalid sort values fall back to the default.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 22:05:22 +02:00
Erik Thiele
6a35769943 Show the date a link was added
The created_at timestamp was already stored but never displayed. Add
a "datum" Jinja filter that converts the stored UTC value to local
time and render it on both the link card and the edit form.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 22:02:09 +02:00
Erik Thiele
a0db3e5147 Add link editing, AI re-description, and footer
Links can now be edited manually (title, summary, category,
manufacturer, tags) via an inline edit form, with a "KI neu
beschreiben lassen" action that re-fetches the URL and lets the AI
regenerate all fields. Editing recomputes the search embedding.

Also adds an app footer showing author, version, and hostname.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 21:54:01 +02:00
Erik Thiele
47685f0d34 Initial commit: LinkVault – KI-gestützte Link-Sammlung
FastAPI + HTMX Web-App zum Speichern von Links mit automatischer
KI-Kategorisierung, Zusammenfassung und semantischer Suche (OpenAI).
Mehrbenutzer mit Login, SQLite-Persistenz, Docker/Docker-Compose-Setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 22:07:05 +02:00