Skip to content

brianmd/markdown-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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, --title TITLE Custom page title Directory name, titleized
--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
-v, --version Show version and exit

Examples

# Serve the current directory
markdownr

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

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

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

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

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 /download/*| dl[send_file as attachment]
    route -->|GET /fetch| fetch[fetch external URL → 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] --> 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 (101 tests, no browser required)
bundle exec rspec

# Run all tests including browser tests (127 tests, requires Chrome)
BROWSER_TESTS=1 bundle exec rspec
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 37 All HTTP routes — redirects, directory/file rendering, frontmatter, TOC, search, downloads, 404/403, path traversal
spec/layout_spec.rb 26 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/browser_spec.rb 26 Real Chrome: table DOM wrapping, expand button visibility, wide-mode toggle, two-table interactions, right-sidebar :empty CSS

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

About

Http server for serving markdown files

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors