Show HN: Simple org-mode web adapter
Source: Hacker News
Org Web Adapter
A lightweight local web app for browsing and editing Org files.
The app is implemented as a single Python server (main.py) plus one HTML template (templates/index.html) and one stylesheet (static/style.css). It scans a notes directory for .org files and renders a three‑pane UI.
⚠️ There is no authentication or encryption; only run this service on trusted networks. ⚠️
Screenshots
Desktop
Mobile
Instructions
- Symlink your notes directory to
notes. - Edit the bind address and port in
config.yamlif desired. - Run the server:
python3 main.py
How it works
main.pystarts an HTTP server.- On each page request (
GET /), it rescans the notes directory for.orgfiles. - It resolves links/backlinks and builds HTML fragments.
- The fragments are injected into
templates/index.htmlplaceholders:{{NAV_ITEMS}}{{MAIN_CONTENT}}{{BACKLINKS}}
- Browser JavaScript in
templates/index.htmlhandles client‑side interactions (search, shuffle, sorting, jump‑to‑current, theme toggle).
Server‑side components (main.py)
File discovery and parsing
scan_org_files(...)recursively finds.orgfiles.- Extracts title from
#+TITLE:and ID from:ID:.
Org link/backlink handling
resolve_link_target(...)supportsfile:...andid:...links.find_backlinks(...)computes notes linking to the selected note.build_backlink_counts(...)computes backlink totals for sorting.
Rendering
render_org_to_html(...)converts headings (*,**, …) and paragraphs to simple HTML.render_line_with_links(...)converts Org links in text to clickable app links where resolvable.truncate_label(...)caps sidebar labels to 32 characters with….
Editing
POST /editupdates a selected.orgfile and redirects back with status flags.
Static files
serve_static(...)serves files understatic/with path‑traversal protection.
Frontend components
templates/index.html
- Base layout markup.
- Sidebar controls.
- Small JavaScript controller for filtering, sorting, and shuffling navigation links.
- MathJax initialization for inline
$...$rendering.
static/style.css
- Three‑column desktop grid and stacked mobile layout.
- Independent scroll regions for note list and backlinks.
- Mobile note‑list cap (about five notes visible before scrolling).
Configuration
Startup configuration is read from config.yaml by default.
bind_addr: host/IP to bind.bind_port: TCP port (1..65535).
If config.yaml is missing, defaults are 127.0.0.1:8000.
CLI flags --host and --port override config values, and --config /path/to/config.yaml lets you specify an alternate file.
Useful run commands
python3 main.py --dir notes
python3 main.py --host 127.0.0.1 --port 9000
python3 main.py --config ./config.yaml
python3 main.py --no-browser
Features
Note browsing
- Recursive
.orgfile discovery. - Sidebar note list with active‑note highlighting.
- Title/path search filter.
Sidebar ordering controls
- Shuffle notes.
- Sort by backlink count (descending).
- Sort by created date (ascending). Notes without timestamps are treated as older than notes with timestamps.
- Jump to current note button.
Backlinks
- Right sidebar lists notes linking to the current note.
- Supports both
file:andid:link resolution.
Editing
- Toggle Preview/Edit for the selected note.
- Save changes from the browser to disk.
- Inline status messages for save success or errors.
Math rendering
- Inline math rendering via MathJax using
$...$delimiters.
UI behavior
- Light/dark theme toggle (persisted in
localStorage). - Desktop: independent scrollable sidebars.
- Mobile: notes list capped with its own scroll area.
- Sidebar text truncation with ellipsis and tooltip for full text.
Project layout
main.py: server, parsing, rendering, routing.templates/index.html: page template + UI behavior JavaScript.static/style.css: styling and responsive layout.config.yaml: bind configuration.notes/: notes directory (can be a symlink).old_notes/: alternate local notes snapshot.
Limitations
- Not a full Org parser; rendering is intentionally simple.
- Notes are rescanned on each request (simple and fresh, but not optimized for huge note sets).
- Math rendering depends on loading MathJax from a CDN.

