Skip to content

Repository files navigation

note

note

Browse and edit an Obsidian vault in your browser. No install, no backend —
a static site on GitHub Pages, plus a new-tab Chrome extension.

→ Live app

Screenshot

note opens a real Obsidian vault folder straight from the browser (File System Access API) and edits it in place — or holds quick Scratchpad notes in the browser when your vault isn't around.

Modes

Mode What it does Where
Browser Pick a vault folder; the browser reads & writes it directly. The real vault editor. Web — Chrome / Edge / Brave
Scratchpad Quick notes saved in this browser. Export as .md, copy into your vault, optionally sync across devices. Web + extension

The deployed site is Browser + Scratchpad only. Server and Dropbox modes from the original zero-build app still run locally with server.js — see Local server below.

Quick start

npm install
npm run dev        # → http://localhost:5174
Script Output
npm run dev dev server
npm run build production web build → dist/
npm run build:ext Chrome extension build → extension/
npm run build:all both
npm run preview serve the built dist/ locally
npm test security tests — Markdown renderer + sync logic

Requires Node.js 20+.

Deploy to GitHub Pages

Push to main and .github/workflows/deploy-pages.yml runs npm ci && npm run build, then deploys dist/. One-time setup: Settings → Pages → Source = "GitHub Actions".

The build uses base: "./" so asset paths are relative — the site works at a subpath (<account>.github.io/<repo>/) and at an apex domain. The footer version is baked in from package.json via Vite define (__APP_VERSION__).

Chrome extension (new-tab notes)

The same React app, built as a Manifest V3 extension that overrides the new tab — every new tab opens straight onto the Scratchpad.

npm run build:ext   # → extension/

Load it: chrome://extensions → enable Developer mode → Load unpacked → select the repo's extension/ folder.

  • New tab opens the Scratchpad; notes live in the extension's own storage.
  • Editing a local vault hands off to the web app — the File System Access picker is unreliable from chrome-extension:// pages.
  • Chromium only (Chrome / Edge / Brave). Same XSS-safe bundle as the web app; the only permission is storage.

Sync (cross-device Scratchpad)

Scratchpad notes stay in this browser by default. Optional sync keeps them on every device — no backend, just a store you control. Open Sync (the ⟳ icon in Scratchpad mode).

  • GitHub Gist (web + extension) — notes sync to a private Gist on your GitHub account. Both token types work, as long as the token has Gists access:
    • Fine-grained (github_pat_…) → Account permissions → Gists → Read and write
    • Classic (ghp_…) → the gist scope
  • Chrome account sync (extension only) — chrome.storage.sync, zero config, ~100 KB cap.

Reconciliation: last-write-wins per note by edit time; deletions propagate via tombstones (pruned after 30 days). It pulls on open/reconnect and pushes ~3 s after an edit, skipping the push when nothing changed. The token is yours, stored only in this browser's localStorage, and used solely to call api.github.com — revoke it any time (the app forgets it on Disconnect or Clear data).

Known LWW limitation: a delete on one device can be transiently undone by a peer that hasn't synced it yet; it self-heals once every device pulls the tombstone.

The editor

  • Split view by default — Markdown + preview side by side (toggle via topbar or ⌘K).
  • Keyboard-navigable tree — ↑/↓ move focus, →/← expand or collapse, Enter opens.
  • Drag to move files or folders; F2 / double-click to rename; a folder's ✕ removes it recursively. Moving a folder re-keys every note inside it.
  • Auto-save (~1 s idle) and Ctrl+S.
  • Image paste (Browser mode) writes to attachments/ and inserts the link.
  • Obsidian wikilinks ([[Note]], [[Note|Alias]]) and tables render in preview.

Present (slideshow)

Turn any note into a presentation with reveal.js — no export, no extra files.

  • Slides view (the 4th editor mode, or ⌘K → "View: slides"): edit on the left, a live slide preview on the right.
  • Fullscreen present (the ⤢ button, or Alt+P): a kiosk overlay; Esc exits.
  • Slide breaks: a line that is exactly --- starts a new slide; -- starts a vertical sub-slide. Breaks inside a fenced code block are literal. A leading Obsidian YAML front-matter block (--- … ---) is stripped.

The slides are rendered by the same XSS-safe Markdown engine as the preview, so nothing in a note can execute inside a deck. reveal.js loads on demand (a separate ~31 KB chunk) only when you enter Slides / Present, so it never slows the editor's first paint.

Security model

  • XSS-safe preview. The renderer escapes all HTML and sanitises links/images through a scheme allowlist (http, https, mailto, tel, #) that strips control chars first — so [x](java<TAB>script:alert(1)) and data: images are neutralised. Covered by npm test.
  • Strict CSP via a <meta> tag (script-src 'self', connect-src 'self'; the extension adds https://api.github.com). Even a future renderer regression can't execute injected scripts on a static host.
  • Sync tokens never leave the device except the one authenticated call to GitHub; the strict script-src means an injected script can't exfiltrate them. chrome.storage.sync carries no token.
  • Clear data (footer) wipes the Scratchpad, saved vault link, theme, tokens, and session data — use it on shared machines.
  • Hidden from the tree: .obsidian, .git, node_modules, .trash, .DS_Store.

Architecture

React 19 + Vite + Tailwind v4 + @alansynn/design ("Motus"), light theme by default (electric-violet #7c5cf6 primary). One src/ builds to both dist/ (web) and extension/.

  • src/lib/markdown.mjs — pure, DOM-free Markdown renderer + safeHref allowlist (the security boundary).
  • src/lib/stores.mjs — storage backends: Scratchpad (localStorage), Browser (File System Access), the IndexedDB handle store.
  • src/lib/sync-merge.mjs — pure merge logic (LWW + tombstones), unit-tested in isolation.
  • src/lib/sync.mjs — the two sync transports + the syncNow orchestrator.
  • src/lib/{paths,env}.mjs — path helpers; runtime flags (IS_EXTENSION, HOSTED_APP_URL).
  • src/vault.jsx — vault provider: tree, open file, autosave, hotkeys, sync wiring.
  • src/components/* — Sidebar, FileTree, EditorPane, Topbar, Statusbar, VaultDialog, CommandPalette.
  • index.html — Vite entry + the CSP <meta>.
  • vite.config.ext.mjs + app-static-ext/ — extension build (separate config + MV3 manifest).

The original zero-build app still lives in public/ as a legacy fallback; the Vite build never touches it (app-static/ is the React app's publicDir).

Local server (optional)

Server & Dropbox modes — local only, not deployed

The bundled server.js reads/writes a vault folder on disk:

node server.js --vault /path/to/your/vault     # or: OBSIDIAN_VAULT=/path/to/vault npm start

Open http://127.0.0.1:5173. Without --vault/OBSIDIAN_VAULT it serves the app in Browser mode (you pick the folder in the UI).

Dropbox mode needs server-side OAuth env vars — DROPBOX_APP_KEY, DROPBOX_APP_SECRET, DROPBOX_REDIRECT_URI — and proxies file ops through /api/dropbox/files/*. Neither mode runs on the static deploy (no backend; Dropbox is hidden and CSP-blocked).

Contributing

Branch from main (features/…), open a focused PR. Update this README when behavior changes, bump the version on every commit, and never commit personal vault content or secrets. Force-push only to your own branch (--force-with-lease, never main).

License

Not specified.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages