docyard 0.5.0 → 0.7.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +34 -1
  4. data/lib/docyard/build/static_generator.rb +3 -44
  5. data/lib/docyard/builder.rb +14 -4
  6. data/lib/docyard/cli.rb +6 -3
  7. data/lib/docyard/components/aliases.rb +29 -0
  8. data/lib/docyard/components/base_processor.rb +6 -0
  9. data/lib/docyard/components/processors/callout_processor.rb +124 -0
  10. data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +106 -0
  11. data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +79 -0
  12. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +78 -0
  13. data/lib/docyard/components/processors/code_block_processor.rb +175 -0
  14. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +127 -0
  15. data/lib/docyard/components/processors/heading_anchor_processor.rb +39 -0
  16. data/lib/docyard/components/processors/icon_processor.rb +53 -0
  17. data/lib/docyard/components/processors/table_of_contents_processor.rb +68 -0
  18. data/lib/docyard/components/processors/table_wrapper_processor.rb +22 -0
  19. data/lib/docyard/components/processors/tabs_processor.rb +48 -0
  20. data/lib/docyard/components/registry.rb +4 -4
  21. data/lib/docyard/components/support/code_block/feature_extractor.rb +117 -0
  22. data/lib/docyard/components/support/code_block/icon_detector.rb +44 -0
  23. data/lib/docyard/components/support/code_block/line_parser.rb +84 -0
  24. data/lib/docyard/components/support/code_block/line_wrapper.rb +50 -0
  25. data/lib/docyard/components/support/code_block/patterns.rb +55 -0
  26. data/lib/docyard/components/support/code_detector.rb +61 -0
  27. data/lib/docyard/components/support/tabs/icon_detector.rb +62 -0
  28. data/lib/docyard/components/support/tabs/parser.rb +195 -0
  29. data/lib/docyard/components/support/tabs/range_finder.rb +46 -0
  30. data/lib/docyard/config/branding_resolver.rb +74 -0
  31. data/lib/docyard/{constants.rb → config/constants.rb} +1 -0
  32. data/lib/docyard/config/validator.rb +8 -0
  33. data/lib/docyard/config.rb +17 -1
  34. data/lib/docyard/{prev_next_builder.rb → navigation/prev_next_builder.rb} +2 -2
  35. data/lib/docyard/{sidebar → navigation/sidebar}/renderer.rb +3 -14
  36. data/lib/docyard/{sidebar → navigation/sidebar}/tree_builder.rb +9 -2
  37. data/lib/docyard/{sidebar_builder.rb → navigation/sidebar_builder.rb} +3 -15
  38. data/lib/docyard/{icons → rendering/icons}/file_types.rb +0 -13
  39. data/lib/docyard/{icons → rendering/icons}/phosphor.rb +4 -1
  40. data/lib/docyard/{markdown.rb → rendering/markdown.rb} +23 -14
  41. data/lib/docyard/{renderer.rb → rendering/renderer.rb} +24 -20
  42. data/lib/docyard/search/build_indexer.rb +74 -0
  43. data/lib/docyard/search/dev_indexer.rb +110 -0
  44. data/lib/docyard/search/pagefind_support.rb +31 -0
  45. data/lib/docyard/{asset_handler.rb → server/asset_handler.rb} +1 -1
  46. data/lib/docyard/{server.rb → server/dev_server.rb} +32 -9
  47. data/lib/docyard/{preview_server.rb → server/preview_server.rb} +1 -1
  48. data/lib/docyard/{rack_application.rb → server/rack_application.rb} +53 -50
  49. data/lib/docyard/server/resolution_result.rb +29 -0
  50. data/lib/docyard/{router.rb → server/router.rb} +4 -4
  51. data/lib/docyard/templates/assets/css/code.css +12 -4
  52. data/lib/docyard/templates/assets/css/components/code-block.css +427 -24
  53. data/lib/docyard/templates/assets/css/components/navigation.css +12 -9
  54. data/lib/docyard/templates/assets/css/components/search.css +549 -0
  55. data/lib/docyard/templates/assets/css/components/tabs.css +50 -44
  56. data/lib/docyard/templates/assets/css/layout.css +15 -1
  57. data/lib/docyard/templates/assets/css/variables.css +44 -0
  58. data/lib/docyard/templates/assets/js/components/search.js +685 -0
  59. data/lib/docyard/templates/layouts/default.html.erb +14 -2
  60. data/lib/docyard/templates/partials/_code_block.html.erb +50 -2
  61. data/lib/docyard/templates/partials/_heading_anchor.html.erb +1 -1
  62. data/lib/docyard/templates/partials/_prev_next.html.erb +1 -1
  63. data/lib/docyard/templates/partials/_search_modal.html.erb +45 -0
  64. data/lib/docyard/templates/partials/_search_trigger.html.erb +22 -0
  65. data/lib/docyard/utils/html_helpers.rb +14 -0
  66. data/lib/docyard/utils/path_resolver.rb +2 -1
  67. data/lib/docyard/utils/url_helpers.rb +20 -0
  68. data/lib/docyard/version.rb +1 -1
  69. data/lib/docyard.rb +22 -15
  70. metadata +57 -36
  71. data/lib/docyard/components/callout_processor.rb +0 -121
  72. data/lib/docyard/components/code_block_processor.rb +0 -55
  73. data/lib/docyard/components/code_detector.rb +0 -59
  74. data/lib/docyard/components/heading_anchor_processor.rb +0 -34
  75. data/lib/docyard/components/icon_detector.rb +0 -57
  76. data/lib/docyard/components/icon_processor.rb +0 -51
  77. data/lib/docyard/components/table_of_contents_processor.rb +0 -64
  78. data/lib/docyard/components/table_wrapper_processor.rb +0 -18
  79. data/lib/docyard/components/tabs_parser.rb +0 -60
  80. data/lib/docyard/components/tabs_processor.rb +0 -44
  81. data/lib/docyard/routing/resolution_result.rb +0 -31
  82. /data/lib/docyard/{sidebar → navigation/sidebar}/config_parser.rb +0 -0
  83. /data/lib/docyard/{sidebar → navigation/sidebar}/file_system_scanner.rb +0 -0
  84. /data/lib/docyard/{sidebar → navigation/sidebar}/item.rb +0 -0
  85. /data/lib/docyard/{sidebar → navigation/sidebar}/title_extractor.rb +0 -0
  86. /data/lib/docyard/{icons → rendering/icons}/LICENSE.phosphor +0 -0
  87. /data/lib/docyard/{icons.rb → rendering/icons.rb} +0 -0
  88. /data/lib/docyard/{language_mapping.rb → rendering/language_mapping.rb} +0 -0
  89. /data/lib/docyard/{file_watcher.rb → server/file_watcher.rb} +0 -0
  90. /data/lib/docyard/{errors.rb → utils/errors.rb} +0 -0
  91. /data/lib/docyard/{logging.rb → utils/logging.rb} +0 -0
@@ -4,30 +4,30 @@ require "webrick"
4
4
  require "stringio"
5
5
  require_relative "file_watcher"
6
6
  require_relative "rack_application"
7
- require_relative "config"
7
+ require_relative "../config"
8
8
 
9
9
  module Docyard
10
10
  class Server
11
11
  DEFAULT_PORT = 4200
12
12
  DEFAULT_HOST = "localhost"
13
13
 
14
- attr_reader :port, :host, :docs_path, :config
14
+ attr_reader :port, :host, :docs_path, :config, :search_enabled
15
15
 
16
- def initialize(port: DEFAULT_PORT, host: DEFAULT_HOST, docs_path: "docs")
16
+ def initialize(port: DEFAULT_PORT, host: DEFAULT_HOST, docs_path: "docs", search: false)
17
17
  @port = port
18
18
  @host = host
19
19
  @docs_path = docs_path
20
+ @search_enabled = search
20
21
  @config = Config.load
21
22
  @file_watcher = FileWatcher.new(File.expand_path(docs_path))
22
- @app = RackApplication.new(
23
- docs_path: File.expand_path(docs_path),
24
- file_watcher: @file_watcher,
25
- config: @config
26
- )
23
+ @search_indexer = nil
24
+ @app = nil
27
25
  end
28
26
 
29
27
  def start
30
28
  validate_docs_directory!
29
+ generate_search_index if @search_enabled
30
+ initialize_app
31
31
  print_server_info
32
32
  @file_watcher.start
33
33
 
@@ -35,11 +35,33 @@ module Docyard
35
35
  trap("INT") { shutdown_server }
36
36
 
37
37
  http_server.start
38
- @file_watcher.stop
38
+ cleanup
39
39
  end
40
40
 
41
41
  private
42
42
 
43
+ def generate_search_index
44
+ @search_indexer = Search::DevIndexer.new(
45
+ docs_path: File.expand_path(docs_path),
46
+ config: @config
47
+ )
48
+ @search_indexer.generate
49
+ end
50
+
51
+ def initialize_app
52
+ @app = RackApplication.new(
53
+ docs_path: File.expand_path(docs_path),
54
+ file_watcher: @file_watcher,
55
+ config: @config,
56
+ pagefind_path: @search_indexer&.pagefind_path
57
+ )
58
+ end
59
+
60
+ def cleanup
61
+ @file_watcher.stop
62
+ @search_indexer&.cleanup
63
+ end
64
+
43
65
  def validate_docs_directory!
44
66
  return if File.directory?(docs_path)
45
67
 
@@ -51,6 +73,7 @@ module Docyard
51
73
  puts "Starting Docyard server..."
52
74
  puts "=> Serving docs from: #{docs_path}/"
53
75
  puts "=> Running at: http://#{host}:#{port}"
76
+ puts "=> Search: #{@search_enabled ? 'enabled' : 'disabled (use --search to enable)'}"
54
77
  puts "=> Press Ctrl+C to stop\n"
55
78
  end
56
79
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "webrick"
4
- require_relative "config"
4
+ require_relative "../config"
5
5
 
6
6
  module Docyard
7
7
  class PreviewServer
@@ -2,18 +2,26 @@
2
2
 
3
3
  require "json"
4
4
  require "rack"
5
- require_relative "sidebar_builder"
6
- require_relative "prev_next_builder"
7
- require_relative "constants"
5
+ require_relative "../navigation/sidebar_builder"
6
+ require_relative "../navigation/prev_next_builder"
7
+ require_relative "../config/branding_resolver"
8
+ require_relative "../config/constants"
8
9
 
9
10
  module Docyard
10
11
  class RackApplication
11
- def initialize(docs_path:, file_watcher:, config: nil)
12
+ PAGEFIND_CONTENT_TYPES = {
13
+ ".js" => "application/javascript; charset=utf-8",
14
+ ".css" => "text/css; charset=utf-8",
15
+ ".json" => "application/json; charset=utf-8"
16
+ }.freeze
17
+
18
+ def initialize(docs_path:, file_watcher:, config: nil, pagefind_path: nil)
12
19
  @docs_path = docs_path
13
20
  @file_watcher = file_watcher
14
21
  @config = config
22
+ @pagefind_path = pagefind_path
15
23
  @router = Router.new(docs_path: docs_path)
16
- @renderer = Renderer.new(base_url: config&.build&.base_url || "/")
24
+ @renderer = Renderer.new(base_url: config&.build&.base_url || "/", config: config)
17
25
  @asset_handler = AssetHandler.new
18
26
  end
19
27
 
@@ -23,13 +31,14 @@ module Docyard
23
31
 
24
32
  private
25
33
 
26
- attr_reader :docs_path, :file_watcher, :config, :router, :renderer, :asset_handler
34
+ attr_reader :docs_path, :file_watcher, :config, :pagefind_path, :router, :renderer, :asset_handler
27
35
 
28
36
  def handle_request(env)
29
37
  path = env["PATH_INFO"]
30
38
 
31
39
  return handle_reload_check(env) if path == Constants::RELOAD_ENDPOINT
32
40
  return asset_handler.serve(path) if path.start_with?(Constants::ASSETS_PREFIX)
41
+ return serve_pagefind(path) if path.start_with?(Constants::PAGEFIND_PREFIX)
33
42
 
34
43
  handle_documentation_request(path)
35
44
  rescue StandardError => e
@@ -91,50 +100,7 @@ module Docyard
91
100
  end
92
101
 
93
102
  def branding_options
94
- return default_branding unless config
95
-
96
- default_branding.merge(config_branding_options)
97
- end
98
-
99
- def default_branding
100
- {
101
- site_title: Constants::DEFAULT_SITE_TITLE,
102
- site_description: "",
103
- logo: Constants::DEFAULT_LOGO_PATH,
104
- logo_dark: Constants::DEFAULT_LOGO_DARK_PATH,
105
- favicon: nil,
106
- display_logo: true,
107
- display_title: true
108
- }
109
- end
110
-
111
- def config_branding_options
112
- site = config.site
113
- branding = config.branding
114
-
115
- {
116
- site_title: site.title || Constants::DEFAULT_SITE_TITLE,
117
- site_description: site.description || "",
118
- logo: resolve_logo(branding.logo, branding.logo_dark),
119
- logo_dark: resolve_logo_dark(branding.logo, branding.logo_dark),
120
- favicon: branding.favicon
121
- }.merge(appearance_options(branding.appearance))
122
- end
123
-
124
- def appearance_options(appearance)
125
- appearance ||= {}
126
- {
127
- display_logo: appearance["logo"] != false,
128
- display_title: appearance["title"] != false
129
- }
130
- end
131
-
132
- def resolve_logo(logo, logo_dark)
133
- logo || logo_dark || Constants::DEFAULT_LOGO_PATH
134
- end
135
-
136
- def resolve_logo_dark(logo, logo_dark)
137
- logo_dark || logo || Constants::DEFAULT_LOGO_DARK_PATH
103
+ BrandingResolver.new(config).resolve
138
104
  end
139
105
 
140
106
  def handle_reload_check(env)
@@ -168,5 +134,42 @@ module Docyard
168
134
  [Constants::STATUS_INTERNAL_ERROR, { "Content-Type" => Constants::CONTENT_TYPE_HTML },
169
135
  [renderer.render_server_error(error)]]
170
136
  end
137
+
138
+ def serve_pagefind(path)
139
+ relative_path = path.delete_prefix(Constants::PAGEFIND_PREFIX)
140
+ return pagefind_not_found if relative_path.include?("..")
141
+
142
+ file_path = resolve_pagefind_file(relative_path)
143
+ return pagefind_not_found unless file_path && File.file?(file_path)
144
+
145
+ content = File.binread(file_path)
146
+ content_type = pagefind_content_type(file_path)
147
+
148
+ headers = {
149
+ "Content-Type" => content_type,
150
+ "Cache-Control" => "no-cache, no-store, must-revalidate",
151
+ "Pragma" => "no-cache",
152
+ "Expires" => "0"
153
+ }
154
+
155
+ [Constants::STATUS_OK, headers, [content]]
156
+ end
157
+
158
+ def resolve_pagefind_file(relative_path)
159
+ return File.join(pagefind_path, relative_path) if pagefind_path && Dir.exist?(pagefind_path)
160
+
161
+ output_dir = config&.build&.output_dir || "dist"
162
+ File.join(output_dir, "pagefind", relative_path)
163
+ end
164
+
165
+ def pagefind_content_type(file_path)
166
+ extension = File.extname(file_path)
167
+ PAGEFIND_CONTENT_TYPES.fetch(extension, "application/octet-stream")
168
+ end
169
+
170
+ def pagefind_not_found
171
+ message = "Pagefind not found. Run 'docyard build' first."
172
+ [Constants::STATUS_NOT_FOUND, { "Content-Type" => "text/plain" }, [message]]
173
+ end
171
174
  end
172
175
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ class ResolutionResult
5
+ attr_reader :file_path, :status
6
+
7
+ def self.found(file_path)
8
+ new(file_path: file_path, status: :found)
9
+ end
10
+
11
+ def self.not_found
12
+ new(file_path: nil, status: :not_found)
13
+ end
14
+
15
+ def initialize(file_path:, status:)
16
+ @file_path = file_path
17
+ @status = status
18
+ freeze
19
+ end
20
+
21
+ def found?
22
+ status == :found
23
+ end
24
+
25
+ def not_found?
26
+ status == :not_found
27
+ end
28
+ end
29
+ end
@@ -12,18 +12,18 @@ module Docyard
12
12
  clean_path = sanitize_path(request_path)
13
13
 
14
14
  file_path = File.join(docs_path, "#{clean_path}#{Constants::MARKDOWN_EXTENSION}")
15
- return Routing::ResolutionResult.found(file_path) if File.file?(file_path)
15
+ return ResolutionResult.found(file_path) if File.file?(file_path)
16
16
 
17
17
  index_path = File.join(docs_path, clean_path, "#{Constants::INDEX_FILE}#{Constants::MARKDOWN_EXTENSION}")
18
- return Routing::ResolutionResult.found(index_path) if File.file?(index_path)
18
+ return ResolutionResult.found(index_path) if File.file?(index_path)
19
19
 
20
- Routing::ResolutionResult.not_found
20
+ ResolutionResult.not_found
21
21
  end
22
22
 
23
23
  private
24
24
 
25
25
  def sanitize_path(request_path)
26
- clean = request_path.delete_prefix("/")
26
+ clean = request_path.delete_prefix("/").delete_suffix("/")
27
27
  clean = Constants::INDEX_FILE if clean.empty?
28
28
  clean.delete_suffix(Constants::MARKDOWN_EXTENSION)
29
29
  end
@@ -42,12 +42,16 @@
42
42
  margin: 0;
43
43
  }
44
44
 
45
- .highlight,
46
- .highlight .w {
45
+ .highlight {
47
46
  color: #24292f;
48
47
  background-color: #f6f8fa;
49
48
  }
50
49
 
50
+ .highlight .w {
51
+ color: #24292f;
52
+ background-color: transparent;
53
+ }
54
+
51
55
  /* Keywords */
52
56
  .highlight .k,
53
57
  .highlight .kd,
@@ -214,12 +218,16 @@
214
218
  }
215
219
 
216
220
  /* Dark Mode Syntax Highlighting - GitHub Dark */
217
- .dark .highlight,
218
- .dark .highlight .w {
221
+ .dark .highlight {
219
222
  color: #e6edf3;
220
223
  background-color: #161b22;
221
224
  }
222
225
 
226
+ .dark .highlight .w {
227
+ color: #e6edf3;
228
+ background-color: transparent;
229
+ }
230
+
223
231
  /* Keywords */
224
232
  .dark .highlight .k,
225
233
  .dark .highlight .kd,