M2 attachments: image upload + owner-scoped media serving
- Migration 0007: note_attachments (path/mime/size). Upload POST /api/notes/<id>/attachments (multipart, png/jpeg/gif/webp, 12MB cap via MAX_CONTENT_LENGTH) stored under Config.media_root() (first use of DATA_DIR); owner/ACL-scoped GET serves the file (nosniff); DELETE removes row + file. Note responses include attachments[]. - Frontend: notes store uploadAttachment (FormData)/deleteAttachment; editor image button + paste-to-upload + thumbnail grid with remove; card shows the first image as a cover. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -19,6 +19,14 @@ function cardClass(color: NoteColor): string {
|
||||
class="group relative mb-4 break-inside-avoid rounded-xl border p-3 shadow-sm transition hover:shadow-md"
|
||||
:class="cardClass(note.color)"
|
||||
>
|
||||
<img
|
||||
v-if="note.attachments.length"
|
||||
:src="note.attachments[0].url"
|
||||
alt=""
|
||||
class="mb-2 max-h-48 w-full cursor-pointer rounded-lg object-cover"
|
||||
@click="emit('open', note)"
|
||||
/>
|
||||
|
||||
<!-- Checklist notes can't nest interactive controls in a <button>, so use a
|
||||
focusable div; text notes keep a semantic button. -->
|
||||
<template v-if="note.kind === 'list'">
|
||||
@@ -48,7 +56,9 @@ function cardClass(color: NoteColor): string {
|
||||
<p v-if="note.body" class="whitespace-pre-wrap break-words text-sm text-neutral-700 dark:text-neutral-300">
|
||||
{{ note.body }}
|
||||
</p>
|
||||
<p v-if="!note.title && !note.body" class="text-sm italic text-neutral-400">Empty note</p>
|
||||
<p v-if="!note.title && !note.body && !note.attachments.length" class="text-sm italic text-neutral-400">
|
||||
Empty note
|
||||
</p>
|
||||
</button>
|
||||
|
||||
<div v-if="note.labels.length" class="mt-2 flex flex-wrap gap-1">
|
||||
|
||||
Reference in New Issue
Block a user