Project

markdownr

0.0
No release in over 3 years
Serve markdown files from any directory with a clean web interface, syntax highlighting, YAML frontmatter support, and wiki-link resolution.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 2.4
~> 7.0
~> 2.0
~> 4.0
~> 4.0
~> 3.0
 Project Readme

markdownr

CI Gem Version Gem Downloads License: MIT

A local web server for browsing and reading markdown files with a clean, book-inspired interface.

Point it at any directory and get a navigable website with rendered markdown, syntax-highlighted code, YAML frontmatter display, wiki-link resolution, and auto-generated tables of contents.

Screenshot showing TOC sidebar, wide table with expand icon, and link preview popup

Rendering docs/examples/overview.md

This was written primarily by Claude Code.

Install

gem install markdownr

Development

git clone https://github.com/brianmd/markdown-server.git
cd markdown-server
bundle install
ruby bin/markdownr [directory]

Usage

markdownr [options] [directory]

Serves the current directory if none is specified.

Options

Flag Description Default
-p, --port PORT Port to listen on 4567
-b, --bind ADDRESS Address to bind to 127.0.0.1
-T, --threads N Number of server threads 5
-t, --title TITLE Custom page title Directory name, titleized
-i, --index-file FILENAME Render this file instead of the directory listing when present (e.g. index.md, moc.md) Off
--behind-proxy Trust X-Forwarded-For for client IP (use when behind a reverse proxy such as Caddy or nginx) Off
--allow-robots Allow search engine crawling Disallowed
--no-link-tooltips Disable preview tooltips on local markdown links Enabled
--standard-newlines Treat single newlines as spaces (standard markdown); default is Obsidian-style where single newlines become line breaks Hard-wrap on
--verbose Print each request (time, IP, method, path) to stdout Off
--plugin NAME Enable a plugin by name (can be specified multiple times) None
--plugin-dir DIR Additional directory containing plugins (can be specified multiple times) None
-v, --version Show version and exit

Examples

# Serve the current directory
markdownr

# Serve a specific directory on port 3000
markdownr -p 3000 ~/notes

# Run multiple servers at once (uses Procfile.markdownr)
overmind start -f Procfile.markdownr        # start all
overmind restart lofts                      # restart one
overmind restart                            # restart all
overmind quit                               # stop all

# Serve with a custom title
markdownr -t "Field Notes" ~/research

# Enable the bible citations plugin
markdownr --plugin bible_citations ~/bible-notes

# Load plugins from an external directory
markdownr --plugin-dir ~/my-markdownr-plugins ~/notes

Example .markdownr.yml

A minimal config enabling the bible citations plugin:

plugins:
  bible_citations:
    enabled: true

A fuller config with all available options:

plugins:
  bible_citations:
    enabled: true
    version: "CEB"            # Bible translation (default: CEB)
    link_target: scrip        # "scrip" or "biblegateway"
    dictionary_url: https://example.com/dictionary.json

# Load additional plugins from external directories
plugin_dirs:
  - /path/to/custom/plugins
  - ~/my-markdownr-plugins

# Popup behavior for link previews
popups:
  local_md: true              # popups for internal .md links
  local_html: false           # popups for internal .html links
  external: true              # popups for external links
  external_domains: []        # limit external popups to specific domains

Features

Markdown rendering

  • GitHub Flavored Markdown -- tables, task lists, strikethrough, autolinks, and more (via Kramdown GFM)
  • Syntax highlighting for fenced code blocks and standalone source files (via Rouge)
  • YAML frontmatter parsed and displayed in a collapsible metadata table
  • Wiki links -- [[page-name]] resolves to matching .md files anywhere in the directory tree

Table of contents

  • Auto-generated sticky sidebar for documents with multiple headings (on wide screens)
  • Scroll spy highlights the current heading as you read
  • Swipe-to-reveal TOC drawer on touch devices -- swipe left to open, swipe right to dismiss
  • Floating TOC button on narrow screens for mouse users -- click to open the sliding drawer
  • Tapping a heading in the drawer navigates there and closes the panel

Search

  • Full-text search across file contents within any directory subtree
  • Searching from a markdown file shows only matches within that file
  • Multi-word queries require all words to match (AND logic, any order)
  • Each search term can be a regex (e.g., \d{4} or e.*him)
  • Results show matching lines with context, highlighted matches, and line numbers
  • Clickable results jump directly to the matching line in the document
  • Long lines are truncated with the match kept visible
  • Search box available on every page (directories search within; files search their parent directory)

Navigation

  • Directory browsing with file sizes, modification dates, and sortable columns (name, modified, created)
  • Sort persistence -- your chosen sort order is remembered across directories via localStorage
  • Breadcrumb navigation on every page, auto-hides on scroll and reappears on scroll-up or tap
  • Scroll position memory -- reopening a document returns you to the last heading you were reading

File handling

  • JSON files rendered as syntax-highlighted YAML for readability
  • PDF served in an inline viewer
  • EPUB served as a download
  • Source files (.py, .rb, .js, .sh, .yaml, .html, etc.) displayed with syntax highlighting
  • Other text files shown as plain text; binary files served as downloads

Link previews

  • Hover preview -- hovering a local markdown link shows a popup with the linked document's content
  • Click popup -- clicking a local markdown link shows the same popup; click again or follow to navigate
  • Popup navigation -- links inside a popup load that document into the same popup, with a back button (←) to return to the previous document; browse several documents without leaving the page
  • Popups auto-close on mouse leave; disabled with --no-link-tooltips

Directory index files

  • Set --index-file index.md (or any filename) to render that file instead of the directory listing when it is present in a directory
  • The directory listing is still accessible via the breadcrumb or direct URL
  • Embed the directory listing inline inside any markdown file using these tokens, each on its own line:
    • {{directory}} -- always renders the listing at that position
    • {{admin-directory}} -- renders the listing only for admins; renders nothing for other visitors

Admin access

Place a .setup.yml file in the root of the served directory to enable admin features:

admin:
  user: myusername   # login form username
  pw: mypassword     # login form password
  ip: 192.168.1.10   # optional: automatically grant admin if request comes from this IP

The ip field accepts a single address, a list of addresses, or CIDR notation:

admin:
  ip:
    - 192.168.86.0/24   # entire subnet
    - 10.0.0.5           # specific address

Admin access is required to save or delete CSV rows. Non-admin users can browse and view data but will be prompted to log in when attempting edits.

Admin routes:

  • GET /admin/login -- login form (username + password)
  • GET /admin/logout -- clears the session
  • GET /setup-info -- shows your IP, served path (admins only), and any non-admin config keys from .setup.yml

Any non-admin keys in .setup.yml are public and displayed on /setup-info. The admin block is never exposed.

Sessions are signed with a random secret (re-generated on each server start). For sessions that survive restarts, set the MARKDOWNR_SESSION_SECRET environment variable to a long random string before starting the server.

When running behind a reverse proxy, pass --behind-proxy so the real client IP is read from X-Forwarded-For instead of the direct connection address.

Configuration

Place a .markdownr.yml file in the root of your served directory (see Example .markdownr.yml above for the full format).

Plugin settings can also be controlled via environment variables:

MARKDOWNR_PLUGIN_BIBLE_CITATIONS_ENABLED=true markdownr ~/notes

Priority order (highest wins): CLI flags > environment variables > .markdownr.yml > defaults.

CSV browser

  • Schema-driven database browser -- define tables with JSON Schema properties in a YAML config and browse CSV data in a popup-based UI
  • Foreign key navigation -- columns can reference other tables; clicking a foreign key cell opens the referenced table filtered to that record
  • Related tables -- tables automatically show links to other tables that reference them (e.g., a Students table shows an Enrollments button on each row)
  • Per-column filtering -- global regex search plus per-column filter inputs that combine with AND logic
  • View/edit modes -- view mode for navigating between tables; edit mode for inline cell editing with server-side JSON Schema validation
  • Table colors -- each table can have a color used for title bars, FK cell tints, and related-table buttons

See CSV Browser documentation for setup instructions, configuration reference, and a walkthrough of all features. A working example is in the school fixture.

Quick start -- add to .markdownr.yml:

csv_databases:
  - data/school.yaml     # path relative to served directory

Bible citations (plugin)

  • Bare verse citations are auto-linked -- e.g. John 3:16, Rom. 12:1, 1 Cor 5:7
  • Links are styled bold purple to distinguish them from regular links
  • Citations inside code spans, fenced code blocks, or existing markdown links are left untouched
  • Supports a wide range of book abbreviations (full names, standard short forms, dotted forms) for all OT, NT, and deuterocanonical books present in the CEB
  • Opt-in -- enable with --plugin bible_citations or via .markdownr.yml (see Configuration)
  • Configurable link target (scrip or biblegateway), Bible version (default: CEB), and Strong's dictionary URL

Tables

  • Wide-mode expand -- each table has a floating expand button (⤢) that widens the page to full viewport width, left-aligning the content for maximum reading area
  • Tables scroll horizontally when they overflow on small screens
  • Swiping on a table does not trigger the TOC drawer, so horizontal table scrolling works without interference

Responsive design

  • Clean, book-inspired interface that adapts from desktop to mobile
  • TOC transitions from a fixed sidebar to a swipe drawer on narrow screens
  • Metadata tables reflow to stacked layout on mobile

Supported File Types

Extension Rendering
.md Rendered markdown with TOC
.json Syntax-highlighted YAML
.pdf Inline PDF viewer
.epub Download
.py, .rb, .sh, .js, .yaml, .html Syntax-highlighted source
Other text Plain text display
Binary Served as download

Architecture

Request routing

flowchart TD
    req[HTTP Request] --> route{Route}

    route -->|GET /| redir[redirect → /browse/]
    route -->|GET /robots.txt| robots[robots.txt response]
    route -->|GET /version| ver[version + active plugins → JSON]
    route -->|GET /download/*| dl[send_file as attachment]
    route -->|GET /fetch| fetch[plugin dispatch → JSON]
    route -->|GET /preview/*| prev[render_markdown → JSON]
    route -->|GET /search/*| srch[compile_regexes → search.erb]
    route -->|GET /browse/*| sp{safe_path\ncheck}

    sp -->|traversal / excluded| err[403 / 404]
    sp -->|directory| rd[render_directory\n→ directory.erb]
    sp -->|file| ext{Extension?}

    ext -->|.md| md[parse_frontmatter\nrender_markdown\nextract_toc\n→ markdown.erb]
    ext -->|.json| json[JSON → YAML\nsyntax_highlight\n→ raw.erb]
    ext -->|.pdf| pdf[send inline]
    ext -->|.epub| epub[redirect /download/]
    ext -->|source files| src[syntax_highlight\n→ raw.erb]
    ext -->|binary| bin[send as download]
Loading

Markdown render pipeline

The steps inside render_markdown must run in this order:

flowchart TD
    A[Raw markdown text] --> P

    P["0 · Plugin transforms\nEach enabled plugin's transform_markdown\nruns in sequence (e.g. Bible citation auto-linking)"]
    P --> B

    B["1 · Resolve wiki links\n\[\[page\]\] and \[\[page|label\]\] → inline HTML\nMust run before Kramdown so the pipe character\nis not consumed as a GFM table delimiter"]
    B --> C

    C["2 · Kramdown GFM\nConverts markdown → HTML\nGenerates heading IDs and numbers all\nfootnote references sequentially (1, 2, 3…)"]
    C --> D

    D["3 · Restore footnote labels\nKramdown replaces \[^name\] with a number;\ntwo post-processing regexes restore the\noriginal label in both the inline superscript\nand the footnote list at the bottom"]
    D --> E[Final HTML]
Loading

Testing

# Run unit and integration tests (no browser required)
bundle exec rspec

# Run all tests including browser tests (requires Chrome)
BROWSER_TESTS=1 bundle exec rspec

# Lint (style cops disabled, Lint/Security enabled)
bundle exec rubocop
File Tests Coverage
spec/helpers_spec.rb 38 parse_frontmatter, format_size/date, breadcrumbs, compile_regexes, extract_toc, render_markdown (wiki links, footnote label restoration, tables)
spec/routes_spec.rb 42 All HTTP routes — redirects, directory/file rendering, frontmatter, TOC, search, downloads, 404/403, path traversal
spec/layout_spec.rb 27 HTML/CSS/JS structure for the table wide-mode feature: right-sidebar div, expand button CSS, body.wide-mode rules, :has(:empty) logic, all JS identifiers
spec/rendering_helpers_spec.rb 20 Markdown rendering helpers, wiki link resolution, frontmatter rendering
spec/admin_spec.rb 25 Admin auth (IP, basic auth, session login), setup-info route, behind-proxy IP detection
spec/plugin_spec.rb 9 Plugin module interface, PluginRegistry (registration, enable/disable, CLI overrides, config resolution)
spec/plugins/bible_citations_plugin_spec.rb 11 BibleCitations plugin adapter (transform_markdown, transform_html, custom version)
spec/bible_citations_spec.rb 46 Bible citation regex matching, abbreviation disambiguation, verse ranges, chapter ranges
spec/bible_citations_html_spec.rb 14 HTML-aware citation linking, skip tags/scripts/anchors
spec/biblegateway_url_spec.rb 6 BibleGateway URL construction, custom version keyword
spec/html_injection_spec.rb 11 HTML file serving with plugin injection, popup assets
spec/file_types_spec.rb 12 File type rendering (JSON→YAML, source highlighting, binary downloads)
spec/csv_browser_spec.rb 37 CSV browser API: table data, views, references, reverse references, row editing, validation, render API
spec/csv_browser_browser_spec.rb 20 Real Chrome: CSV popup view/edit modes, per-column filters, FK navigation, reverse reference links, display-value filtering
spec/browser_spec.rb 26 Real Chrome: table DOM wrapping, expand button visibility, wide-mode toggle, two-table interactions, right-sidebar :empty CSS
spec/scrip_popup_spec.rb 9 Scripture page popup rendering, CSS inlining, content detection (requires local scrip server)

Browser tests use headless Chrome via Capybara and selenium-webdriver. On macOS, the spec automatically locates the version-matched chromedriver from the selenium cache, clears any Gatekeeper quarantine, and ad-hoc signs it before running.

Requirements

  • Ruby >= 3.0

License

MIT