Skip to content

Add public read-only note sharing with /n/<token> links - #26

Merged
utorque merged 1 commit into
mainfrom
claude/public-note-sharing-s65im8
Jul 24, 2026
Merged

Add public read-only note sharing with /n/<token> links#26
utorque merged 1 commit into
mainfrom
claude/public-note-sharing-s65im8

Conversation

@utorque

Copy link
Copy Markdown
Owner

Summary

Implements public read-only sharing for notes. Users can create a shareable link to any note that renders the latest markdown in a read-only EasyMDE preview. The share is revocable and tied to a single opaque token per note.

Key Changes

Database & Models

  • Added NoteShare model with one-to-one relationship to Note (unique token per note)
  • Token is the sole credential; no auth required to view shared notes
  • Cascade delete ensures revoking a share or deleting a note removes the link
  • Updated Note.to_dict() to include public_share_token field

API Routes (/api/notes/<id>/share)

  • POST creates or returns existing share token (idempotent)
  • DELETE revokes the share (idempotent, 204 whether or not one existed)
  • Both routes require authentication; token reuse is not supported on re-share
  • Changes logged via record_change() for audit trail

Public Page (/n/<token>)

  • New server-rendered route in pages.py (no auth required)
  • Renders note's latest markdown in read-only EasyMDE preview on every request
  • Returns 404 for invalid/revoked tokens
  • Includes "Copy raw markdown" toolbar button (shared implementation with private editor)
  • Branded with "Simpler" header and read-only badge

UI Updates

  • Share/Stop-sharing buttons in notes toolbar (right-aligned with Download)
  • Share button disabled until a note is open; flips to "Copy link" when already shared
  • Stop-sharing button appears only when note is shared (red/revoke styling)
  • Clipboard feedback via save indicator ("link copied", "link removed", etc.)
  • Client syncs cached note list with open note's share state

Styling

  • New .notes-toolbar-actions flex container for grouped toolbar buttons
  • Stop-sharing button styled as destructive action (red border/text)
  • Disabled state styling for buttons

Database Migration

  • note_shares table with indexes on note_id and token
  • Cascade delete on note_id FK

Implementation Details

  • Token generation uses secrets.token_urlsafe(16) (22-char URL-safe string)
  • Share state cached in client's state.currentNote to avoid refetch on button state changes
  • Public page always fetches fresh markdown from DB (no caching) to guarantee latest version
  • Copy-to-clipboard logic shared between private editor and public page (fallback for older browsers)
  • Audit trail records both share creation and revocation with token values

https://claude.ai/code/session_01BzQsU2rYQodWiDhyNFckz2

Share an open note via a revocable public link that always serves the
note's latest saved markdown, plus a copy-raw-markdown tool on both the
private editor and the public page.
- models: NoteShare (one-per-note, unique token, delete-orphan);
Note.to_dict exposes public_share_token
- routes/notes: POST/DELETE /api/notes/<id>/share (idempotent, audited)
- routes/pages: GET /n/<token> — public, no auth, server-rendered every
request so it reflects the latest version; unknown/revoked token 404s
- templates/public_note.html: read-only, preview-locked EasyMDE with a
copy-raw-markdown toolbar tool
- notes.js: Share/Copy-link/Stop-sharing buttons + copy-markdown tool
- migrate_db: additive note_shares table + indexes
- tests + docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzQsU2rYQodWiDhyNFckz2
@utorque
utorque merged commit 33dadeb into mainJul 24, 2026
2 checks passed
@utorque
utorque deleted the claude/public-note-sharing-s65im8 branch July 24, 2026 08:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@utorque@claude