infodocs 0.3.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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +26 -0
  3. data/CONTRIBUTING.md +31 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +341 -0
  6. data/Rakefile +10 -0
  7. data/app/controllers/infodocs/application_controller.rb +68 -0
  8. data/app/controllers/infodocs/assets_controller.rb +15 -0
  9. data/app/controllers/infodocs/editor_controller.rb +36 -0
  10. data/app/controllers/infodocs/indexes_controller.rb +22 -0
  11. data/app/controllers/infodocs/pages_controller.rb +48 -0
  12. data/app/controllers/infodocs/raws_controller.rb +13 -0
  13. data/app/controllers/infodocs/searches_controller.rb +18 -0
  14. data/app/controllers/infodocs/tags_controller.rb +11 -0
  15. data/app/controllers/infodocs/trees_controller.rb +40 -0
  16. data/app/helpers/infodocs/application_helper.rb +70 -0
  17. data/app/javascript/infodocs/application.js +74 -0
  18. data/app/javascript/infodocs/controllers/editor_controller.js +135 -0
  19. data/app/javascript/infodocs/controllers/highlight_controller.js +44 -0
  20. data/app/javascript/infodocs/controllers/mermaid_controller.js +25 -0
  21. data/app/javascript/infodocs/controllers/search_controller.js +125 -0
  22. data/app/javascript/infodocs/controllers/sidebar_controller.js +34 -0
  23. data/app/javascript/infodocs/controllers/toc_controller.js +18 -0
  24. data/app/javascript/infodocs/controllers/tree_editor_controller.js +65 -0
  25. data/app/javascript/infodocs/controllers/tree_state_controller.js +37 -0
  26. data/app/javascript/infodocs/vendor/sortable.esm.js +3378 -0
  27. data/app/views/infodocs/_delete_button.html.erb +6 -0
  28. data/app/views/infodocs/_handle.html.erb +5 -0
  29. data/app/views/infodocs/_tree.html.erb +35 -0
  30. data/app/views/infodocs/editor/edit.html.erb +61 -0
  31. data/app/views/infodocs/pages/empty_folder.html.erb +6 -0
  32. data/app/views/infodocs/pages/not_found.html.erb +11 -0
  33. data/app/views/infodocs/pages/show.html.erb +92 -0
  34. data/app/views/infodocs/searches/index.html.erb +20 -0
  35. data/app/views/infodocs/tags/index.html.erb +26 -0
  36. data/app/views/layouts/infodocs/application.html.erb +104 -0
  37. data/config/importmap.rb +6 -0
  38. data/config/routes.rb +26 -0
  39. data/lib/generators/infodocs/install/install_generator.rb +59 -0
  40. data/lib/generators/infodocs/install/templates/getting-started.md.tt +8 -0
  41. data/lib/generators/infodocs/install/templates/initializer.rb.tt +51 -0
  42. data/lib/generators/infodocs/views/views_generator.rb +13 -0
  43. data/lib/infodocs/code_theme.rb +130 -0
  44. data/lib/infodocs/configuration.rb +50 -0
  45. data/lib/infodocs/engine.rb +39 -0
  46. data/lib/infodocs/link_checker.rb +43 -0
  47. data/lib/infodocs/linter.rb +48 -0
  48. data/lib/infodocs/page.rb +124 -0
  49. data/lib/infodocs/rails_lexer.rb +39 -0
  50. data/lib/infodocs/renderer.rb +231 -0
  51. data/lib/infodocs/search.rb +131 -0
  52. data/lib/infodocs/source.rb +35 -0
  53. data/lib/infodocs/tree.rb +215 -0
  54. data/lib/infodocs/tree_editor.rb +179 -0
  55. data/lib/infodocs/url_helper.rb +23 -0
  56. data/lib/infodocs/version.rb +5 -0
  57. data/lib/infodocs.rb +74 -0
  58. data/lib/tasks/infodocs.rake +63 -0
  59. data/sig/infodocs.rbs +4 -0
  60. metadata +192 -0
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "open3"
5
+
6
+ module Infodocs
7
+ # Moves a page or folder to another folder / position by renaming files and
8
+ # rewriting the `order` lists of the affected `_tree.yml` manifests.
9
+ class TreeEditor
10
+ class Invalid < Error; end
11
+
12
+ def initialize(tree)
13
+ @tree = tree
14
+ end
15
+
16
+ # slug: node to move; parent: slug of the destination folder ("" = top);
17
+ # before: slug of the sibling it goes in front of (nil = last).
18
+ def move(slug:, parent:, before: nil)
19
+ node = @tree.node(slug)
20
+ raise Invalid, "unknown node #{slug.inspect}" if node.nil? || node.path.nil?
21
+
22
+ target = @tree.node(parent)
23
+ raise Invalid, "unknown destination #{parent.inspect}" if target.nil? || !target.folder?
24
+ raise Invalid, "cannot place items next to multiple source roots" if target.path.nil?
25
+ raise Invalid, "source folders cannot be moved" if @tree.root.children.include?(node) && @tree.multiple_roots?
26
+ raise Invalid, "cannot move a folder into itself" if node.folder? && (target.slug == node.slug || target.slug.start_with?("#{node.slug}/"))
27
+
28
+ source_dir = node.path.parent
29
+ name = node.name
30
+ destination = target.path.join(node.folder? ? name : "#{name}.md")
31
+
32
+ if source_dir != target.path
33
+ raise Invalid, "#{destination.basename} already exists in the destination" if destination.exist?
34
+
35
+ carried = node.leaf? ? (@tree.node(node.slug)&.page&.manifest_tags || []) : []
36
+ moved_pages = pages_under(node)
37
+ rename(node.path, destination)
38
+ leave_redirects(moved_pages, node, destination, target)
39
+ update_manifest(source_dir) { |manifest| remove_name(manifest, name) }
40
+ update_manifest(target.path) { |manifest| set_tags(manifest, name, carried) }
41
+ end
42
+
43
+ order = ordered_names(target, exclude: name)
44
+ sibling = before && @tree.node(before)&.name
45
+ index = sibling && order.index(sibling) || order.size
46
+ order.insert(index, name)
47
+ update_manifest(target.path) { |manifest| manifest["order"] = order }
48
+ end
49
+
50
+ # Saves the tags of a page in its folder's `_tree.yml` (`tags: { name: [..] }`).
51
+ # Tags that come from the page's own frontmatter are left alone.
52
+ def tag(slug:, tags:)
53
+ node = @tree.node(slug)
54
+ raise Invalid, "unknown page #{slug.inspect}" if node.nil? || node.path.nil? || !node.leaf?
55
+
56
+ list = Page.normalize_tags(tags) - Page.normalize_tags(node.page.frontmatter["tags"])
57
+ update_manifest(node.path.parent) { |manifest| set_tags(manifest, node.name, list) }
58
+ list
59
+ end
60
+
61
+ # Creates a page (`name.md`) or an (empty) folder with a `_tree.yml` titled
62
+ # after it, at the end of `parent`. Returns the slug of the new node.
63
+ def create(parent:, title:, kind: :page)
64
+ title = title.to_s.strip
65
+ raise Invalid, "title can't be blank" if title.empty?
66
+
67
+ target = @tree.node(parent)
68
+ raise Invalid, "unknown destination #{parent.inspect}" if target.nil? || !target.folder? || target.path.nil?
69
+
70
+ name = unique_name(target, title.parameterize.presence || "untitled")
71
+ if kind.to_s == "folder"
72
+ dir = target.path.join(name)
73
+ FileUtils.mkdir_p(dir)
74
+ dir.join(Tree::MANIFEST).write({ "title" => title }.to_yaml.sub(/\A---\s*\n/, ""))
75
+ else
76
+ target.path.join("#{name}.md").write("# #{title}\n\n")
77
+ end
78
+
79
+ order = ordered_names(target, exclude: name) << name
80
+ update_manifest(target.path) { |manifest| manifest["order"] = order }
81
+ [target.slug, name].reject(&:empty?).join("/")
82
+ end
83
+
84
+ # Deletes a page or a whole folder (with everything inside). Returns the
85
+ # slug of the folder that contained it. Tracked files go through `git rm`.
86
+ def delete(slug:)
87
+ node = @tree.node(slug)
88
+ raise Invalid, "unknown node #{slug.inspect}" if node.nil? || node.path.nil? || node.slug.empty?
89
+ raise Invalid, "source folders cannot be deleted" if @tree.root.children.include?(node) && @tree.multiple_roots?
90
+ raise Invalid, "path is outside the docs sources" unless inside_source?(node.path)
91
+
92
+ parent_dir = node.path.parent
93
+ remove(node.path)
94
+ update_manifest(parent_dir) { |manifest| remove_name(manifest, node.name) }
95
+ File.dirname(node.slug).sub(/\A\.\z/, "")
96
+ end
97
+
98
+ private
99
+
100
+ def inside_source?(path)
101
+ real = path.realpath
102
+ @tree.sources.any? { |source| real.to_s.start_with?("#{source.path.realpath}/") }
103
+ rescue Errno::ENOENT
104
+ false
105
+ end
106
+
107
+ def remove(path)
108
+ _out, _err, status = Open3.capture3("git", "rm", "-rq", "--", path.to_s, chdir: path.parent.to_s)
109
+ FileUtils.rm_rf(path) unless status.success?
110
+ end
111
+
112
+ def unique_name(folder, base)
113
+ taken = folder.children.map(&:name) + folder.path.children.map { |child| child.basename(".md").to_s }
114
+ name = base
115
+ counter = 1
116
+ name = "#{base}-#{counter += 1}" while taken.include?(name) || name.start_with?("_")
117
+ name
118
+ end
119
+
120
+ # Current display order of the folder's children (all, hidden included).
121
+ def ordered_names(folder, exclude:)
122
+ folder.children.map(&:name) - [exclude]
123
+ end
124
+
125
+ # [page, path relative to the moved node] for the page, or every page in the folder.
126
+ def pages_under(node)
127
+ return [[node.page, nil]] if node.leaf?
128
+
129
+ @tree.all_pages.select { |page| page.slug == node.slug || page.slug.start_with?("#{node.slug}/") }
130
+ .map { |page| [page, page.file.relative_path_from(node.path)] }
131
+ end
132
+
133
+ # Old URLs keep working: each moved page lists its previous slug in `redirect_from`.
134
+ def leave_redirects(moved_pages, node, destination, target)
135
+ new_prefix = [target.slug, node.name].reject(&:empty?).join("/")
136
+ moved_pages.each do |page, relative|
137
+ file = relative ? destination.join(relative) : destination
138
+ next unless file.file?
139
+
140
+ old_slug = page.slug
141
+ new_slug = node.leaf? ? new_prefix : old_slug.sub(/\A#{Regexp.escape(node.slug)}/, new_prefix)
142
+ moved = Page.new(file: file, slug: new_slug, root: target.path)
143
+ moved.add_redirect_from(old_slug)
144
+ end
145
+ end
146
+
147
+ def set_tags(manifest, name, tags)
148
+ map = manifest["tags"].is_a?(Hash) ? manifest["tags"].transform_keys(&:to_s) : {}
149
+ tags.empty? ? map.delete(name) : map[name] = tags
150
+ map.empty? ? manifest.delete("tags") : manifest["tags"] = map
151
+ end
152
+
153
+ def remove_name(manifest, name)
154
+ set_tags(manifest, name, []) if manifest["tags"].is_a?(Hash)
155
+ manifest["order"] = Array(manifest["order"]).map(&:to_s) - [name] if manifest.key?("order")
156
+ manifest["hidden"] = Array(manifest["hidden"]).map(&:to_s) - [name] if manifest.key?("hidden")
157
+ end
158
+
159
+ def update_manifest(dir)
160
+ file = dir.join(Tree::MANIFEST)
161
+ manifest = file.file? ? (YAML.safe_load(file.read) || {}) : {}
162
+ yield manifest
163
+ manifest.delete("order") if manifest["order"] == []
164
+ manifest.delete("hidden") if manifest["hidden"] == []
165
+ if manifest.empty?
166
+ file.delete if file.exist?
167
+ else
168
+ file.write(manifest.to_yaml.sub(/\A---\s*\n/, ""))
169
+ end
170
+ end
171
+
172
+ # `git mv` keeps history readable when the file is tracked; plain rename otherwise.
173
+ def rename(from, to)
174
+ Open3.capture3("git", "mv", from.to_s, to.to_s, chdir: from.parent.to_s).then do |_out, _err, status|
175
+ FileUtils.mv(from, to) unless status.success?
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Infodocs
4
+ # Link to the docs from the host app: `link_to "Setup", infodocs_page_path("guides/setup")`.
5
+ # Included into the host's views and controllers by the engine.
6
+ module UrlHelper
7
+ def infodocs_page_path(slug = nil, **options)
8
+ slug = clean_infodocs_slug(slug)
9
+ slug.empty? ? infodocs.root_path(**options) : infodocs.page_path(slug, **options)
10
+ end
11
+
12
+ def infodocs_page_url(slug = nil, **options)
13
+ slug = clean_infodocs_slug(slug)
14
+ slug.empty? ? infodocs.root_url(**options) : infodocs.page_url(slug, **options)
15
+ end
16
+
17
+ private
18
+
19
+ def clean_infodocs_slug(slug)
20
+ slug.to_s.delete_prefix("/").delete_suffix("/").delete_suffix(".md")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Infodocs
4
+ VERSION = "0.3.0"
5
+ end
data/lib/infodocs.rb ADDED
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "infodocs/version"
4
+
5
+ module Infodocs
6
+ class Error < StandardError; end
7
+ end
8
+
9
+ require_relative "infodocs/source"
10
+ require_relative "infodocs/code_theme"
11
+ require_relative "infodocs/configuration"
12
+ require_relative "infodocs/page"
13
+ require_relative "infodocs/tree"
14
+ require_relative "infodocs/tree_editor"
15
+ require_relative "infodocs/renderer"
16
+ require_relative "infodocs/url_helper"
17
+ require_relative "infodocs/link_checker"
18
+ require_relative "infodocs/linter"
19
+ require_relative "infodocs/search"
20
+ require_relative "infodocs/engine" if defined?(Rails)
21
+
22
+ module Infodocs
23
+ class << self
24
+ def config
25
+ @config ||= Configuration.new
26
+ end
27
+
28
+ def configure
29
+ yield config
30
+ end
31
+
32
+ def reset!
33
+ @config = nil
34
+ @tree = nil
35
+ @search = nil
36
+ @rendered = nil
37
+ end
38
+
39
+ def tree
40
+ @tree ||= Tree.build(config.paths, drafts: show_drafts?)
41
+ end
42
+
43
+ # `draft: true` pages are visible only while developing the docs.
44
+ def show_drafts?
45
+ defined?(Rails) && Rails.env.development?
46
+ end
47
+
48
+ # Editing is a development-only feature, on top of `config.editable`.
49
+ def editing_enabled?
50
+ config.editable && Rails.env.development?
51
+ end
52
+
53
+ RENDER_CACHE_LIMIT = 500
54
+
55
+ # Rendered page, cached until the file changes on disk.
56
+ def render(page, mount_path: "/docs")
57
+ stat = page.file.stat
58
+ key = [page.file.to_s, stat.mtime.to_r, stat.size, mount_path]
59
+ @rendered ||= {}
60
+ @rendered.delete(@rendered.keys.first) if @rendered.size >= RENDER_CACHE_LIMIT && !@rendered.key?(key)
61
+ @rendered[key] ||= Renderer.new(page, mount_path: mount_path).call
62
+ end
63
+
64
+ def reload!
65
+ @tree = nil
66
+ @search = nil
67
+ end
68
+
69
+ # Reindexed once per tree load (every reload in development).
70
+ def search
71
+ @search ||= Search.new(tree, db_path: config.index_path.to_s).tap(&:reindex!)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :infodocs do
4
+ desc "Report broken relative links, anchors and images in the docs"
5
+ task check_links: :environment do
6
+ problems = Infodocs::LinkChecker.new(Infodocs.tree).call
7
+ problems.each { |problem| puts "#{problem.page.file}: #{problem.target} (#{problem.reason})" }
8
+ puts "#{problems.size} broken link#{'s' unless problems.size == 1}"
9
+ exit(1) if problems.any?
10
+ end
11
+
12
+ desc "Check content: titles, frontmatter keys, hidden pages nobody links to, useless redirects"
13
+ task lint: :environment do
14
+ problems = Infodocs::Linter.new(Infodocs.tree).call
15
+ problems.each { |problem| puts "#{problem.page.file}: #{problem.message} [#{problem.rule}]" }
16
+ puts "#{problems.size} problem#{'s' unless problems.size == 1}"
17
+ exit(1) if problems.any?
18
+ end
19
+
20
+ desc "Export the docs as static HTML (default dir: tmp/infodocs-export). Run assets:precompile first"
21
+ task :export, [:dir] => :environment do |_task, args|
22
+ require "fileutils"
23
+ out = Pathname(args[:dir] || Rails.root.join("tmp/infodocs-export"))
24
+ mount = Infodocs::Engine.routes.find_script_name({}).presence || "/docs"
25
+ session = ActionDispatch::Integration::Session.new(Rails.application)
26
+ session.host! "localhost"
27
+
28
+ slugs = Infodocs.tree.all_pages.map(&:slug)
29
+ slugs.each do |slug|
30
+ path = [mount, slug].reject(&:empty?).join("/")
31
+ session.get(path)
32
+ abort "#{path} returned #{session.response.status}; static export needs auth = :none" unless session.response.successful?
33
+
34
+ target = out.join(*mount.split("/").reject(&:empty?), *slug.split("/"), "index.html")
35
+ FileUtils.mkdir_p(target.dirname)
36
+ target.write(session.response.body)
37
+ end
38
+
39
+ %w[llms.txt sitemap.xml].each do |name|
40
+ session.get("#{mount}/#{name}")
41
+ File.write(out.join(*mount.split("/").reject(&:empty?), name), session.response.body) if session.response.successful?
42
+ end
43
+
44
+ # Images and files next to the pages.
45
+ Infodocs.tree.sources.each do |source|
46
+ Dir.glob(source.path.join("**/*")).each do |file|
47
+ next unless Infodocs::Tree::ASSET_TYPES.key?(File.extname(file).downcase)
48
+
49
+ relative = Pathname(file).relative_path_from(source.path).to_s
50
+ next if relative.split("/").any? { |part| part.start_with?("_", ".") }
51
+
52
+ slug = Infodocs.tree.multiple_roots? ? File.join(source.key, relative) : relative
53
+ copy = out.join(*mount.split("/").reject(&:empty?), "_asset", slug)
54
+ FileUtils.mkdir_p(copy.dirname)
55
+ FileUtils.cp(file, copy)
56
+ end
57
+ end
58
+
59
+ public_assets = Rails.root.join("public/assets")
60
+ FileUtils.cp_r(public_assets, out.join("assets")) if public_assets.directory?
61
+ puts "Exported #{slugs.size} pages to #{out}"
62
+ end
63
+ end
data/sig/infodocs.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Infodocs
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: infodocs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Jakub Cieślar
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-10-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: commonmarker
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rouge
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: importmap-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: stimulus-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ description: Mountable Rails engine that serves .md files from your repo as a docs
98
+ site with a sidebar tree (_tree.yml ordering), SQLite FTS5 search, server-side syntax
99
+ highlighting and Mermaid diagrams. Tailwind + Stimulus.
100
+ email:
101
+ - cieslar.jakub@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - CHANGELOG.md
107
+ - CONTRIBUTING.md
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - app/controllers/infodocs/application_controller.rb
112
+ - app/controllers/infodocs/assets_controller.rb
113
+ - app/controllers/infodocs/editor_controller.rb
114
+ - app/controllers/infodocs/indexes_controller.rb
115
+ - app/controllers/infodocs/pages_controller.rb
116
+ - app/controllers/infodocs/raws_controller.rb
117
+ - app/controllers/infodocs/searches_controller.rb
118
+ - app/controllers/infodocs/tags_controller.rb
119
+ - app/controllers/infodocs/trees_controller.rb
120
+ - app/helpers/infodocs/application_helper.rb
121
+ - app/javascript/infodocs/application.js
122
+ - app/javascript/infodocs/controllers/editor_controller.js
123
+ - app/javascript/infodocs/controllers/highlight_controller.js
124
+ - app/javascript/infodocs/controllers/mermaid_controller.js
125
+ - app/javascript/infodocs/controllers/search_controller.js
126
+ - app/javascript/infodocs/controllers/sidebar_controller.js
127
+ - app/javascript/infodocs/controllers/toc_controller.js
128
+ - app/javascript/infodocs/controllers/tree_editor_controller.js
129
+ - app/javascript/infodocs/controllers/tree_state_controller.js
130
+ - app/javascript/infodocs/vendor/sortable.esm.js
131
+ - app/views/infodocs/_delete_button.html.erb
132
+ - app/views/infodocs/_handle.html.erb
133
+ - app/views/infodocs/_tree.html.erb
134
+ - app/views/infodocs/editor/edit.html.erb
135
+ - app/views/infodocs/pages/empty_folder.html.erb
136
+ - app/views/infodocs/pages/not_found.html.erb
137
+ - app/views/infodocs/pages/show.html.erb
138
+ - app/views/infodocs/searches/index.html.erb
139
+ - app/views/infodocs/tags/index.html.erb
140
+ - app/views/layouts/infodocs/application.html.erb
141
+ - config/importmap.rb
142
+ - config/routes.rb
143
+ - lib/generators/infodocs/install/install_generator.rb
144
+ - lib/generators/infodocs/install/templates/getting-started.md.tt
145
+ - lib/generators/infodocs/install/templates/initializer.rb.tt
146
+ - lib/generators/infodocs/views/views_generator.rb
147
+ - lib/infodocs.rb
148
+ - lib/infodocs/code_theme.rb
149
+ - lib/infodocs/configuration.rb
150
+ - lib/infodocs/engine.rb
151
+ - lib/infodocs/link_checker.rb
152
+ - lib/infodocs/linter.rb
153
+ - lib/infodocs/page.rb
154
+ - lib/infodocs/rails_lexer.rb
155
+ - lib/infodocs/renderer.rb
156
+ - lib/infodocs/search.rb
157
+ - lib/infodocs/source.rb
158
+ - lib/infodocs/tree.rb
159
+ - lib/infodocs/tree_editor.rb
160
+ - lib/infodocs/url_helper.rb
161
+ - lib/infodocs/version.rb
162
+ - lib/tasks/infodocs.rake
163
+ - sig/infodocs.rbs
164
+ homepage: https://github.com/jcieslar/infodocs
165
+ licenses:
166
+ - MIT
167
+ metadata:
168
+ homepage_uri: https://github.com/jcieslar/infodocs
169
+ source_code_uri: https://github.com/jcieslar/infodocs
170
+ rubygems_mfa_required: 'true'
171
+ changelog_uri: https://github.com/jcieslar/infodocs/blob/main/CHANGELOG.md
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: 3.2.0
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubygems_version: 3.5.16
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: 'Markdown documentation browser for Rails: tree sidebar, full-text search,
191
+ Mermaid.'
192
+ test_files: []