browsedown 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 776115bdbc3e5a8f0af0c65073b690412749089de1bce897a8f3bfad043a2005
4
+ data.tar.gz: 54bb1383cdad441d684562f5d76a0f9ec90a27565fe62255dfc38519a3acd799
5
+ SHA512:
6
+ metadata.gz: 555891a9fe6bcd37de511c0d8f36f875a656711810dc71ca254ce6578e1b1ae93d342d11a9f71aaaeb0af5ba9641b2f075cde30e49ff16fda43d3f9978e7f19f
7
+ data.tar.gz: 0a135f69a55dba11701d964bf9e26d6edc59af98164aa4c4f881d9495ba94fff9226185b70570d8bc3056a8de949a87d949ce394e9c491b2ac8bb477e6b66c43
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Browsedown
2
+
3
+ A Rails engine that serves a directory of markdown files as a browsable documentation site.
4
+
5
+ Point it at a folder, mount it in your routes, and your `.md` files become readable in the browser with a sidebar, dark/light mode, and prose-optimized typography.
6
+
7
+ ## Installation
8
+
9
+ Add to your Gemfile:
10
+
11
+ ```ruby
12
+ gem "browsedown"
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Mount it in `config/routes.rb`:
18
+
19
+ ```ruby
20
+ mount Browsedown["docs"] => "/documentation"
21
+ ```
22
+
23
+ This serves all `.md` files under `Rails.root/docs` at `/documentation`.
24
+
25
+ The path argument is relative to `Rails.root`:
26
+
27
+ ```ruby
28
+ Browsedown["content/guides"] # => Rails.root/content/guides
29
+ ```
30
+
31
+ ## How it works
32
+
33
+ - Scans the directory tree for `*.md` files
34
+ - Renders markdown to HTML using Redcarpet (fenced code blocks, tables, autolinks, strikethrough)
35
+ - Displays a sidebar with links to every page
36
+ - Shows `README.md` as the landing page if one exists
37
+ - Blocks path traversal — requests outside the configured root return 404
38
+
39
+ ## Requirements
40
+
41
+ - Ruby >= 3.0
42
+ - Rails >= 6.0
43
+
44
+ ## License
45
+
46
+ MIT
@@ -0,0 +1,34 @@
1
+ module Browsedown
2
+ class PagesController < ActionController::Base
3
+ layout false
4
+
5
+ before_action :load_pages
6
+
7
+ def index
8
+ @readme = @pages.find { |p| p.relative_path.downcase == "readme.md" }
9
+ end
10
+
11
+ def show
12
+ path = params[:path]
13
+ path = "#{path}.md" unless path.end_with?(".md")
14
+
15
+ resolved = Pathname.new(File.join(docs_root, path)).cleanpath
16
+ raise ActionController::RoutingError, "Not Found" unless resolved.to_s.start_with?(docs_root)
17
+
18
+ @page = @pages.find { |p| p.relative_path == path }
19
+ raise ActionController::RoutingError, "Not Found" unless @page
20
+ rescue ActionController::RoutingError
21
+ head :not_found
22
+ end
23
+
24
+ private
25
+
26
+ def load_pages
27
+ @pages = FindPages.call(root: docs_root)
28
+ end
29
+
30
+ def docs_root
31
+ @docs_root ||= Rails.root.join(Browsedown::Engine.root_path).to_s
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ <%= render "browsedown/pages/style" %>
2
+ <div class="browsedown">
3
+ <nav class="browsedown-sidebar">
4
+ <h2><a href="<%= browsedown.root_path %>">Contents</a></h2>
5
+ <% pages.group_by(&:section).each do |section, group| %>
6
+ <% if section %>
7
+ <h3><%= section %></h3>
8
+ <% end %>
9
+ <ul>
10
+ <% group.each do |pg| %>
11
+ <li>
12
+ <a href="<%= browsedown.page_path(path: pg.relative_path.delete_suffix('.md')) %>"
13
+ class="<%= 'active' if defined?(current_page) && current_page&.relative_path == pg.relative_path %>">
14
+ <%= pg.title %>
15
+ </a>
16
+ </li>
17
+ <% end %>
18
+ </ul>
19
+ <% end %>
20
+ </nav>
21
+ <main class="browsedown-content">
22
+ <%= yield %>
23
+ </main>
24
+ </div>
@@ -0,0 +1,416 @@
1
+ <link rel="preconnect" href="https://fonts.googleapis.com">
2
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
3
+ <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,600;0,9..144,700;1,9..144,400&family=Inter:wght@300;400;500&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
4
+ <style>
5
+ :root {
6
+ --bd-bg: #faf6f1;
7
+ --bd-text: #2c2520;
8
+ --bd-text-muted: #8a7e74;
9
+ --bd-sidebar-bg: #f3ede6;
10
+ --bd-sidebar-border: #e2d9cf;
11
+ --bd-accent: #9e4a3a;
12
+ --bd-accent-hover: #7a3529;
13
+ --bd-link: #7a3529;
14
+ --bd-link-underline: #d4a99a;
15
+ --bd-code-bg: #eee8e0;
16
+ --bd-code-text: #5c4f43;
17
+ --bd-pre-bg: #2c2520;
18
+ --bd-pre-text: #e8ddd3;
19
+ --bd-rule: #d8cec3;
20
+ --bd-blockquote-border: #c4a882;
21
+ --bd-blockquote-bg: rgba(196, 168, 130, 0.08);
22
+ --bd-table-stripe: rgba(44, 37, 32, 0.03);
23
+ --bd-shadow: rgba(44, 37, 32, 0.06);
24
+ }
25
+
26
+ @media (prefers-color-scheme: dark) {
27
+ :root {
28
+ --bd-bg: #1c1917;
29
+ --bd-text: #e7ddd2;
30
+ --bd-text-muted: #8a7e74;
31
+ --bd-sidebar-bg: #161311;
32
+ --bd-sidebar-border: #2e2924;
33
+ --bd-accent: #d4a08a;
34
+ --bd-accent-hover: #e8c0ad;
35
+ --bd-link: #d4a08a;
36
+ --bd-link-underline: rgba(212, 160, 138, 0.3);
37
+ --bd-code-bg: #262120;
38
+ --bd-code-text: #d4a08a;
39
+ --bd-pre-bg: #111010;
40
+ --bd-pre-text: #c8bdb2;
41
+ --bd-rule: #2e2924;
42
+ --bd-blockquote-border: #6b5a48;
43
+ --bd-blockquote-bg: rgba(107, 90, 72, 0.08);
44
+ --bd-table-stripe: rgba(231, 221, 210, 0.03);
45
+ --bd-shadow: rgba(0, 0, 0, 0.2);
46
+ }
47
+ }
48
+
49
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
50
+
51
+ html {
52
+ font-size: 18px;
53
+ scroll-behavior: smooth;
54
+ -webkit-font-smoothing: antialiased;
55
+ -moz-osx-font-smoothing: grayscale;
56
+ }
57
+
58
+ .browsedown {
59
+ display: flex;
60
+ min-height: 100vh;
61
+ background: var(--bd-bg);
62
+ color: var(--bd-text);
63
+ }
64
+
65
+ /* ---- Sidebar ---- */
66
+
67
+ .browsedown-sidebar {
68
+ position: sticky;
69
+ top: 0;
70
+ height: 100vh;
71
+ width: 280px;
72
+ min-width: 280px;
73
+ background: var(--bd-sidebar-bg);
74
+ border-right: 1px solid var(--bd-sidebar-border);
75
+ padding: 2.5rem 1.5rem 2rem;
76
+ overflow-y: auto;
77
+ display: flex;
78
+ flex-direction: column;
79
+ }
80
+
81
+ .browsedown-sidebar::-webkit-scrollbar { width: 4px; }
82
+ .browsedown-sidebar::-webkit-scrollbar-thumb { background: var(--bd-sidebar-border); border-radius: 2px; }
83
+
84
+ .browsedown-sidebar h2 {
85
+ font-family: 'Fraunces', serif;
86
+ font-weight: 300;
87
+ font-size: 0.85rem;
88
+ letter-spacing: 0.15em;
89
+ text-transform: uppercase;
90
+ color: var(--bd-text-muted);
91
+ margin-bottom: 1.75rem;
92
+ padding-bottom: 1rem;
93
+ border-bottom: 1px solid var(--bd-sidebar-border);
94
+ }
95
+
96
+ .browsedown-sidebar h2 a {
97
+ color: inherit;
98
+ text-decoration: none;
99
+ transition: color 0.2s;
100
+ }
101
+
102
+ .browsedown-sidebar h2 a:hover {
103
+ color: var(--bd-accent);
104
+ }
105
+
106
+ .browsedown-sidebar ul {
107
+ list-style: none;
108
+ }
109
+
110
+ .browsedown-sidebar li {
111
+ margin: 0.15rem 0;
112
+ }
113
+
114
+ .browsedown-sidebar a {
115
+ font-family: 'Inter', sans-serif;
116
+ font-size: 0.88rem;
117
+ font-weight: 400;
118
+ color: var(--bd-text-muted);
119
+ text-decoration: none;
120
+ display: block;
121
+ padding: 0.4rem 0.75rem;
122
+ border-radius: 4px;
123
+ border-left: 2px solid transparent;
124
+ transition: all 0.2s ease;
125
+ line-height: 1.4;
126
+ }
127
+
128
+ .browsedown-sidebar a:hover {
129
+ color: var(--bd-text);
130
+ border-left-color: var(--bd-sidebar-border);
131
+ background: rgba(0,0,0,0.02);
132
+ }
133
+
134
+ @media (prefers-color-scheme: dark) {
135
+ .browsedown-sidebar a:hover {
136
+ background: rgba(255,255,255,0.03);
137
+ }
138
+ }
139
+
140
+ .browsedown-sidebar h3 {
141
+ font-family: 'Fraunces', serif;
142
+ font-weight: 400;
143
+ font-size: 0.75rem;
144
+ letter-spacing: 0.1em;
145
+ text-transform: uppercase;
146
+ color: var(--bd-text-muted);
147
+ margin: 1.25rem 0 0.35rem 0.75rem;
148
+ }
149
+
150
+ .browsedown-sidebar a.active {
151
+ color: var(--bd-accent);
152
+ border-left-color: var(--bd-accent);
153
+ font-weight: 500;
154
+ }
155
+
156
+ /* ---- Main content ---- */
157
+
158
+ .browsedown-content {
159
+ flex: 1;
160
+ padding: 4rem 4rem 6rem;
161
+ font-family: 'Inter', sans-serif;
162
+ font-weight: 400;
163
+ font-size: 1rem;
164
+ line-height: 1.78;
165
+ }
166
+
167
+ /* ---- Typography ---- */
168
+
169
+ .browsedown-content h1 {
170
+ font-family: 'Fraunces', serif;
171
+ font-weight: 700;
172
+ font-size: 2.4rem;
173
+ line-height: 1.15;
174
+ margin: 0 0 0.5rem;
175
+ letter-spacing: -0.02em;
176
+ color: var(--bd-text);
177
+ }
178
+
179
+ .browsedown-content h1 + p,
180
+ .browsedown-content h1 + ul,
181
+ .browsedown-content h1 + ol {
182
+ margin-top: 1.5rem;
183
+ }
184
+
185
+ .browsedown-content h1::after {
186
+ content: "";
187
+ display: block;
188
+ width: 3rem;
189
+ height: 2px;
190
+ background: var(--bd-accent);
191
+ margin-top: 1.2rem;
192
+ margin-bottom: 1rem;
193
+ border-radius: 1px;
194
+ }
195
+
196
+ .browsedown-content h2 {
197
+ font-family: 'Fraunces', serif;
198
+ font-weight: 600;
199
+ font-size: 1.5rem;
200
+ line-height: 1.25;
201
+ margin: 3rem 0 0.75rem;
202
+ letter-spacing: -0.01em;
203
+ color: var(--bd-text);
204
+ }
205
+
206
+ .browsedown-content h3 {
207
+ font-family: 'Fraunces', serif;
208
+ font-weight: 600;
209
+ font-size: 1.15rem;
210
+ line-height: 1.35;
211
+ margin: 2.25rem 0 0.6rem;
212
+ color: var(--bd-text);
213
+ }
214
+
215
+ .browsedown-content h4 {
216
+ font-family: 'Fraunces', serif;
217
+ font-weight: 600;
218
+ font-size: 1rem;
219
+ margin: 1.75rem 0 0.5rem;
220
+ color: var(--bd-text-muted);
221
+ text-transform: uppercase;
222
+ letter-spacing: 0.06em;
223
+ font-size: 0.85rem;
224
+ }
225
+
226
+ .browsedown-content p {
227
+ margin: 0 0 1.25rem;
228
+ }
229
+
230
+ /* ---- Links ---- */
231
+
232
+ .browsedown-content a {
233
+ color: var(--bd-link);
234
+ text-decoration: underline;
235
+ text-underline-offset: 3px;
236
+ text-decoration-color: var(--bd-link-underline);
237
+ text-decoration-thickness: 1.5px;
238
+ transition: text-decoration-color 0.2s;
239
+ }
240
+
241
+ .browsedown-content a:hover {
242
+ text-decoration-color: var(--bd-accent);
243
+ }
244
+
245
+ /* ---- Lists ---- */
246
+
247
+ .browsedown-content ul,
248
+ .browsedown-content ol {
249
+ margin: 0 0 1.25rem;
250
+ padding-left: 1.5rem;
251
+ }
252
+
253
+ .browsedown-content li {
254
+ margin-bottom: 0.35rem;
255
+ }
256
+
257
+ .browsedown-content li::marker {
258
+ color: var(--bd-text-muted);
259
+ }
260
+
261
+ /* ---- Blockquotes ---- */
262
+
263
+ .browsedown-content blockquote {
264
+ margin: 1.5rem 0;
265
+ padding: 1rem 1.5rem;
266
+ border-left: 3px solid var(--bd-blockquote-border);
267
+ background: var(--bd-blockquote-bg);
268
+ border-radius: 0 6px 6px 0;
269
+ font-style: italic;
270
+ }
271
+
272
+ .browsedown-content blockquote p:last-child {
273
+ margin-bottom: 0;
274
+ }
275
+
276
+ /* ---- Code ---- */
277
+
278
+ .browsedown-content code {
279
+ font-family: 'JetBrains Mono', monospace;
280
+ font-size: 0.82em;
281
+ background: var(--bd-code-bg);
282
+ color: var(--bd-code-text);
283
+ padding: 0.15em 0.4em;
284
+ border-radius: 4px;
285
+ }
286
+
287
+ .browsedown-content pre {
288
+ margin: 1.5rem 0;
289
+ padding: 1.25rem 1.5rem;
290
+ background: var(--bd-pre-bg);
291
+ color: var(--bd-pre-text);
292
+ border-radius: 8px;
293
+ overflow-x: auto;
294
+ line-height: 1.55;
295
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.15);
296
+ }
297
+
298
+ .browsedown-content pre code {
299
+ background: none;
300
+ color: inherit;
301
+ padding: 0;
302
+ font-size: 0.82rem;
303
+ }
304
+
305
+ /* ---- Tables ---- */
306
+
307
+ .browsedown-content table {
308
+ width: 100%;
309
+ border-collapse: collapse;
310
+ margin: 1.5rem 0;
311
+ font-size: 0.92rem;
312
+ }
313
+
314
+ .browsedown-content thead th {
315
+ font-family: 'Fraunces', serif;
316
+ font-weight: 600;
317
+ font-size: 0.78rem;
318
+ letter-spacing: 0.06em;
319
+ text-transform: uppercase;
320
+ color: var(--bd-text-muted);
321
+ text-align: left;
322
+ padding: 0.6rem 0.85rem;
323
+ border-bottom: 2px solid var(--bd-rule);
324
+ }
325
+
326
+ .browsedown-content td {
327
+ padding: 0.6rem 0.85rem;
328
+ border-bottom: 1px solid var(--bd-rule);
329
+ }
330
+
331
+ .browsedown-content tbody tr:nth-child(even) {
332
+ background: var(--bd-table-stripe);
333
+ }
334
+
335
+ /* ---- Horizontal rules ---- */
336
+
337
+ .browsedown-content hr {
338
+ border: none;
339
+ height: 1px;
340
+ background: var(--bd-rule);
341
+ margin: 2.5rem auto;
342
+ width: 40%;
343
+ }
344
+
345
+ /* ---- Images ---- */
346
+
347
+ .browsedown-content img {
348
+ max-width: 100%;
349
+ border-radius: 6px;
350
+ margin: 1rem 0;
351
+ box-shadow: 0 2px 12px var(--bd-shadow);
352
+ }
353
+
354
+ /* ---- Strong / Em ---- */
355
+
356
+ .browsedown-content strong {
357
+ font-weight: 500;
358
+ color: var(--bd-text);
359
+ }
360
+
361
+ .browsedown-content em {
362
+ font-style: italic;
363
+ }
364
+
365
+ /* ---- Empty state ---- */
366
+
367
+ .browsedown-empty {
368
+ color: var(--bd-text-muted);
369
+ font-style: italic;
370
+ font-family: 'Inter', sans-serif;
371
+ }
372
+
373
+ /* ---- Responsive ---- */
374
+
375
+ @media (max-width: 800px) {
376
+ .browsedown {
377
+ flex-direction: column;
378
+ }
379
+
380
+ .browsedown-sidebar {
381
+ position: relative;
382
+ height: auto;
383
+ width: 100%;
384
+ min-width: 100%;
385
+ border-right: none;
386
+ border-bottom: 1px solid var(--bd-sidebar-border);
387
+ padding: 1.5rem;
388
+ }
389
+
390
+ .browsedown-sidebar ul {
391
+ display: flex;
392
+ flex-wrap: wrap;
393
+ gap: 0.25rem;
394
+ }
395
+
396
+ .browsedown-sidebar a {
397
+ border-left: none;
398
+ border-bottom: 2px solid transparent;
399
+ padding: 0.3rem 0.6rem;
400
+ }
401
+
402
+ .browsedown-sidebar h3 {
403
+ width: 100%;
404
+ margin: 0.75rem 0 0.25rem 0;
405
+ }
406
+
407
+ .browsedown-sidebar a.active {
408
+ border-left: none;
409
+ border-bottom-color: var(--bd-accent);
410
+ }
411
+
412
+ .browsedown-content {
413
+ padding: 2.5rem 1.25rem 4rem;
414
+ }
415
+ }
416
+ </style>
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Documentation</title></head>
4
+ <body style="margin:0">
5
+ <%= render "browsedown/pages/layout", pages: @pages, current_page: nil do %>
6
+ <% if @readme %>
7
+ <%= @readme.html.html_safe %>
8
+ <% else %>
9
+ <h1>Documentation</h1>
10
+ <% if @pages.empty? %>
11
+ <p class="browsedown-empty">No markdown files found.</p>
12
+ <% else %>
13
+ <p>Select a page from the sidebar.</p>
14
+ <% end %>
15
+ <% end %>
16
+ <% end %>
17
+ </body>
18
+ </html>
@@ -0,0 +1,9 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title><%= @page.title %></title></head>
4
+ <body style="margin:0">
5
+ <%= render "browsedown/pages/layout", pages: @pages, current_page: @page do %>
6
+ <%= @page.html.html_safe %>
7
+ <% end %>
8
+ </body>
9
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ Browsedown::Engine.routes.draw do
2
+ root to: "pages#index"
3
+ get "/*path", to: "pages#show", as: :page
4
+ end
@@ -0,0 +1,16 @@
1
+ require "redcarpet"
2
+
3
+ module Browsedown
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Browsedown
6
+
7
+ def self.[](path)
8
+ @root_path = path
9
+ self
10
+ end
11
+
12
+ def self.root_path
13
+ @root_path || "docs"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Browsedown
2
+ class FindPages
3
+ def self.call(root:)
4
+ return [] unless File.directory?(root)
5
+
6
+ Dir.glob("**/*.md", base: root).sort.map do |relative_path|
7
+ Page.new(root: root, relative_path: relative_path)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,46 @@
1
+ module Browsedown
2
+ class Page
3
+ attr_reader :relative_path, :title, :section
4
+
5
+ def initialize(root:, relative_path:)
6
+ @relative_path = relative_path
7
+ @root = root
8
+ @title = extract_title
9
+ @section = extract_section
10
+ end
11
+
12
+ def html
13
+ @html ||= RenderMarkdown.call(raw)
14
+ end
15
+
16
+ def raw
17
+ @raw ||= File.read(full_path)
18
+ end
19
+
20
+ private
21
+
22
+ def full_path
23
+ File.join(@root, @relative_path)
24
+ end
25
+
26
+ def extract_section
27
+ dir = File.dirname(relative_path)
28
+ return nil if dir == "."
29
+
30
+ humanize(dir)
31
+ end
32
+
33
+ def humanize(name)
34
+ name.tr("-_", " ").capitalize
35
+ end
36
+
37
+ def extract_title
38
+ first_line = File.foreach(full_path).first
39
+ if first_line && (match = first_line.match(/\A\s*#\s+(.+)/))
40
+ match[1].strip
41
+ else
42
+ humanize(File.basename(relative_path, ".md"))
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,16 @@
1
+ module Browsedown
2
+ class RenderMarkdown
3
+ RENDERER = Redcarpet::Markdown.new(
4
+ Redcarpet::Render::HTML.new(hard_wrap: true, link_attributes: {target: "_blank"}),
5
+ fenced_code_blocks: true,
6
+ tables: true,
7
+ autolink: true,
8
+ strikethrough: true,
9
+ no_intra_emphasis: true
10
+ )
11
+
12
+ def self.call(text)
13
+ RENDERER.render(text)
14
+ end
15
+ end
16
+ end
data/lib/browsedown.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "browsedown/engine"
2
+ require "browsedown/page"
3
+ require "browsedown/find_pages"
4
+ require "browsedown/render_markdown"
5
+
6
+ module Browsedown
7
+ def self.[](path)
8
+ Engine[path]
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: browsedown
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jared
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-03-10 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: redcarpet
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.5'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.5'
40
+ description: A Rails engine that serves and renders markdown files from a directory
41
+ tree.
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - README.md
47
+ - app/controllers/browsedown/pages_controller.rb
48
+ - app/views/browsedown/pages/_layout.html.erb
49
+ - app/views/browsedown/pages/_style.html.erb
50
+ - app/views/browsedown/pages/index.html.erb
51
+ - app/views/browsedown/pages/show.html.erb
52
+ - config/routes.rb
53
+ - lib/browsedown.rb
54
+ - lib/browsedown/engine.rb
55
+ - lib/browsedown/find_pages.rb
56
+ - lib/browsedown/page.rb
57
+ - lib/browsedown/render_markdown.rb
58
+ homepage: https://github.com/modsognir/browsedown
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.6.2
77
+ specification_version: 4
78
+ summary: Browse markdown files in your browser
79
+ test_files: []