M6 1900: any-file attachments + audio memos (broaden beyond images)
A note can now carry any file, not just images — PDFs, documents, audio memos, etc. "Dump anything" capture. Backend: - note_attachments.filename (migration 0019) records the original name for download + display. - Upload drops the image-only mime gate: accepts any type, derives the storage extension from the filename, and enforces a DB-backed per-file cap — new setting max_attachment_mb (default 25, rule 25). App body ceiling raised 12→64 MB (also lifts the import-zip / sync-push limits); the per-file cap is the effective attachment limit. - Serve sets Content-Disposition: images inline, everything else downloads with its original (header-sanitized) filename. - Import (native + Keep Takeout) now brings in ANY attachment, not just images — completing the Keep audio-memo gap; preserves filename + sha256. - Attachment metadata (delta feed + REST) carries filename. Frontend: - Editor renders attachments by kind: images inline (thumbnail), audio via an inline <audio> player, any other file as a download chip (paperclip + filename + size). File picker accepts any type; "Attach a file". - Card previews the first image; non-image files show as compact chips. Tests (DB-free): _safe_filename, _attachment_ext, _header_filename. 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:
@@ -11,8 +11,8 @@ from . import Base
|
||||
|
||||
|
||||
class NoteAttachment(Base):
|
||||
"""An image uploaded onto a note. Bytes live under Config.media_root(); this
|
||||
row records the relative path + metadata."""
|
||||
"""A file uploaded onto a note (any type — images render inline, others download).
|
||||
Bytes live under Config.media_root(); this row records the relative path + metadata."""
|
||||
|
||||
__tablename__ = "note_attachments"
|
||||
|
||||
@@ -21,6 +21,8 @@ class NoteAttachment(Base):
|
||||
UUID(as_uuid=True), ForeignKey("notes.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
path: Mapped[str] = mapped_column(Text(), nullable=False) # relative to media_root
|
||||
# Original upload filename, for the download name + display (non-image files).
|
||||
filename: Mapped[str | None] = mapped_column(Text(), nullable=True)
|
||||
mime: Mapped[str] = mapped_column(Text(), nullable=False)
|
||||
size: Mapped[int] = mapped_column(BigInteger(), nullable=False)
|
||||
# Content hash (sha256 hex) for client-side dedupe + integrity over sync. Nullable
|
||||
|
||||
Reference in New Issue
Block a user