Skip to content

Repository files navigation

MultiNotes

MultiNotes is a minimalist, full-screen block-based notes workspace for Cloudflare Workers. The frontend is a React single-page app built with Vite; each note pane is an Editor.js block editor with fluid Notion-style block dragging. Persistence is per-user via Hono, Drizzle ORM, and Cloudflare D1.

Features

  • Multiple block editors side-by-side in one full-desktop workspace
  • Editor.js blocks: headings, lists, checklists, quotes, images, and paragraphs
  • Per-block text alignment (left/center/right) from the block settings menu
  • Drag blocks within a note or across notes, including a multi-block selection
  • Drag-and-drop reordering of note panes (dnd-kit), persisted to D1
  • Slash menu (/) and inline toolbar for block creation and formatting
  • Inline images stored as base64 in the note content
  • Links hint to hold Cmd/Ctrl to open them in a new tab
  • Read-only public share links (/p/<token>) toggled from settings
  • Debounced auto-save to D1 as Editor.js JSON
  • Legacy HTML notes are converted to Editor.js blocks automatically on first open
  • Email/password auth with JWT cookies
  • Optional Cloudflare Access mode for private deployments
  • Light and dark themes

Stack

  • Cloudflare Workers + Hono (API)
  • React 18 + Vite + @cloudflare/vite-plugin (single-page app)
  • Editor.js (block editor) with editorjs-drag-drop
  • dnd-kit (note pane reordering)
  • Cloudflare D1 SQLite + Drizzle ORM and Drizzle Kit
  • Vitest with @cloudflare/vitest-pool-workers (worker) and happy-dom (client)

Requirements

  • Node.js 22 or newer
  • npm
  • A Cloudflare account for D1 and deployment
  • Wrangler authentication for deployment: npx wrangler login

Local Development

# Install dependencies
npm install

# Create local env vars
cp .dev.vars.example .dev.vars

Edit .dev.vars and set:

JWT_SECRET=replace-with-a-long-random-string
ENABLE_AUTH=true

Apply the D1 migration locally:

npx wrangler d1 migrations apply multinotes --local

Start the Vite dev server (serves the React app and runs the Worker via @cloudflare/vite-plugin):

npm run dev

Open the URL Vite prints (e.g. http://localhost:5173).

With ENABLE_AUTH=true, register an account on the login screen, then the workspace loads automatically. Note: for local development, set ENABLE_AUTH in wrangler.toml (the Vite plugin reads [vars] from it); Cloudflare Access mode requires the access header and is intended for deployed environments.

Auth Modes

Email/password auth

Set:

[vars]
ENABLE_AUTH = "true"

Users register and sign in with email/password. Passwords are hashed with PBKDF2 using the Web Crypto API. Sessions use an httpOnly JWT cookie signed by JWT_SECRET.

Cloudflare Access auth

Set:

[vars]
ENABLE_AUTH = "false"

In this mode, MultiNotes expects Cloudflare Access to provide the Cf-Access-Authenticated-User-Email header. Users are created automatically by email and no password is stored.

Production Deployment

Create a D1 database:

npx wrangler d1 create multinotes

Copy the returned database_id into wrangler.toml:

[[d1_databases]]
binding = "DB"
database_name = "multinotes"
database_id = "your-d1-database-id"
migrations_dir = "src/db/migrations"

Set a JWT secret:

npx wrangler secret put JWT_SECRET

Apply migrations remotely:

npx wrangler d1 migrations apply multinotes --remote

Deploy:

npm run deploy

The deploy script runs typecheck, tests, lint, the Vite build, remote D1 migrations, and wrangler deploy.

Scripts

npm run dev        # Start the Vite dev server (React app + Worker)
npm run build      # Build the SPA and Worker into dist/
npm test           # Run Vitest (worker + client projects)
npm run lint       # Run ESLint
npm run typecheck  # Type-check the Worker and the client
npm run deploy     # Validate, build, migrate remote D1, and deploy

Project Structure

index.html              Vite entry for the React single-page app
src/
  index.ts              Hono API entry (non-API routes served as SPA assets)
  middleware/auth.ts    JWT cookie or Cloudflare Access user resolution
  routes/auth.ts        Register, login, logout, and current-user routes
  routes/workspaces.ts  Workspace CRUD, share links, and ordered note loading
  routes/notes.ts       Note create, update, delete, and reorder routes
  db/schema.ts          Drizzle schema for users, workspaces, and notes
  db/index.ts           Drizzle D1 client factory
  lib/password.ts       PBKDF2 password hashing and verification
  lib/workspaces.ts     Main workspace creation helper
  client/               React single-page app
    main.tsx            React entry
    App.tsx             Auth bootstrap (login vs. workspace)
    api.ts              Typed fetch client for /api
    components/         Workspace, NotePane, PublicView/PublicNote, settings, login
    editor/             Editor.js tools, alignment tune, cross-note drag, parsing
    hooks/              useTheme, useDebouncedSave, useLinkTooltip
    styles/global.css   App and Editor.js theming
test/
  *.test.ts             Worker, API, auth, and routing tests
legacy/                 Previous vanilla-JS editor, kept for reference

API Overview

GET  /api/config            # { authEnabled } — SPA reads this to gate login

POST /api/auth/register
POST /api/auth/login
POST /api/auth/logout
GET  /api/auth/me

GET   /api/workspaces
POST  /api/workspaces
GET   /api/workspaces/:id
PATCH /api/workspaces/:id
GET    /api/workspaces/:id/share
PATCH  /api/workspaces/:id/share
DELETE /api/workspaces/:id/share

POST   /api/workspaces/:wid/notes
PATCH  /api/workspaces/:wid/notes/:nid
DELETE /api/workspaces/:wid/notes/:nid
PUT    /api/workspaces/:wid/notes/order

Notes

  • The app targets modern desktop browsers (Chrome, Edge, Firefox, Safari).
  • Note content is stored as Editor.js OutputData JSON in the notes.html column. Legacy notes stored as raw HTML are converted to blocks in the browser on first open and re-saved as JSON (one-time, per note).
  • Images are stored inline as base64 in the note content. This is convenient, but large images increase D1 row size.
  • Non-API routes are served as the React SPA via the assets binding (SPA fallback); auth gating happens client-side while the API stays protected server-side.
  • The previous vanilla-JS contenteditable editor is preserved under legacy/ purely for reference. Its text alignment, public share view, multi-select cross-note drag, and link tooltips have since been reimplemented natively in the React/Editor.js app; per-character font sizes were intentionally dropped.

About

Fast, minimal multi-pane notes app on Cloudflare Workers. Rich text editors side-by-side with checklists, drag-to-reorder, and workspaces. Share read-only links to any workspace. Light & dark theme. Built with Hono, Drizzle ORM, D1, and vanilla JS.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages