docyard 0.6.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -1
  3. data/lib/docyard/build/static_generator.rb +2 -43
  4. data/lib/docyard/builder.rb +14 -4
  5. data/lib/docyard/cli.rb +6 -3
  6. data/lib/docyard/components/aliases.rb +29 -0
  7. data/lib/docyard/components/processors/callout_processor.rb +124 -0
  8. data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +106 -0
  9. data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +79 -0
  10. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +78 -0
  11. data/lib/docyard/components/processors/code_block_processor.rb +175 -0
  12. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +127 -0
  13. data/lib/docyard/components/processors/heading_anchor_processor.rb +39 -0
  14. data/lib/docyard/components/processors/icon_processor.rb +53 -0
  15. data/lib/docyard/components/processors/table_of_contents_processor.rb +68 -0
  16. data/lib/docyard/components/processors/table_wrapper_processor.rb +22 -0
  17. data/lib/docyard/components/processors/tabs_processor.rb +48 -0
  18. data/lib/docyard/components/support/code_block/feature_extractor.rb +117 -0
  19. data/lib/docyard/components/support/code_block/icon_detector.rb +44 -0
  20. data/lib/docyard/components/support/code_block/line_parser.rb +84 -0
  21. data/lib/docyard/components/support/code_block/line_wrapper.rb +50 -0
  22. data/lib/docyard/components/support/code_block/patterns.rb +55 -0
  23. data/lib/docyard/components/support/code_detector.rb +61 -0
  24. data/lib/docyard/components/support/tabs/icon_detector.rb +62 -0
  25. data/lib/docyard/components/support/tabs/parser.rb +195 -0
  26. data/lib/docyard/components/support/tabs/range_finder.rb +46 -0
  27. data/lib/docyard/config/branding_resolver.rb +74 -0
  28. data/lib/docyard/{constants.rb → config/constants.rb} +1 -0
  29. data/lib/docyard/config.rb +10 -1
  30. data/lib/docyard/{prev_next_builder.rb → navigation/prev_next_builder.rb} +2 -2
  31. data/lib/docyard/{sidebar → navigation/sidebar}/renderer.rb +3 -14
  32. data/lib/docyard/{sidebar → navigation/sidebar}/tree_builder.rb +9 -2
  33. data/lib/docyard/{sidebar_builder.rb → navigation/sidebar_builder.rb} +3 -15
  34. data/lib/docyard/{icons → rendering/icons}/phosphor.rb +4 -1
  35. data/lib/docyard/{markdown.rb → rendering/markdown.rb} +14 -13
  36. data/lib/docyard/{renderer.rb → rendering/renderer.rb} +20 -17
  37. data/lib/docyard/search/build_indexer.rb +74 -0
  38. data/lib/docyard/search/dev_indexer.rb +110 -0
  39. data/lib/docyard/search/pagefind_support.rb +31 -0
  40. data/lib/docyard/{asset_handler.rb → server/asset_handler.rb} +1 -1
  41. data/lib/docyard/{server.rb → server/dev_server.rb} +32 -9
  42. data/lib/docyard/{preview_server.rb → server/preview_server.rb} +1 -1
  43. data/lib/docyard/{rack_application.rb → server/rack_application.rb} +52 -49
  44. data/lib/docyard/server/resolution_result.rb +29 -0
  45. data/lib/docyard/{router.rb → server/router.rb} +4 -4
  46. data/lib/docyard/templates/assets/css/components/search.css +549 -0
  47. data/lib/docyard/templates/assets/css/layout.css +15 -1
  48. data/lib/docyard/templates/assets/js/components/search.js +685 -0
  49. data/lib/docyard/templates/layouts/default.html.erb +14 -2
  50. data/lib/docyard/templates/partials/_code_block.html.erb +1 -1
  51. data/lib/docyard/templates/partials/_heading_anchor.html.erb +1 -1
  52. data/lib/docyard/templates/partials/_prev_next.html.erb +1 -1
  53. data/lib/docyard/templates/partials/_search_modal.html.erb +45 -0
  54. data/lib/docyard/templates/partials/_search_trigger.html.erb +22 -0
  55. data/lib/docyard/utils/html_helpers.rb +14 -0
  56. data/lib/docyard/utils/path_resolver.rb +2 -1
  57. data/lib/docyard/utils/url_helpers.rb +20 -0
  58. data/lib/docyard/version.rb +1 -1
  59. data/lib/docyard.rb +22 -15
  60. metadata +57 -46
  61. data/lib/docyard/components/callout_processor.rb +0 -121
  62. data/lib/docyard/components/code_block_diff_preprocessor.rb +0 -104
  63. data/lib/docyard/components/code_block_feature_extractor.rb +0 -113
  64. data/lib/docyard/components/code_block_focus_preprocessor.rb +0 -77
  65. data/lib/docyard/components/code_block_icon_detector.rb +0 -40
  66. data/lib/docyard/components/code_block_line_wrapper.rb +0 -46
  67. data/lib/docyard/components/code_block_options_preprocessor.rb +0 -76
  68. data/lib/docyard/components/code_block_patterns.rb +0 -51
  69. data/lib/docyard/components/code_block_processor.rb +0 -176
  70. data/lib/docyard/components/code_detector.rb +0 -59
  71. data/lib/docyard/components/code_line_parser.rb +0 -80
  72. data/lib/docyard/components/code_snippet_import_preprocessor.rb +0 -125
  73. data/lib/docyard/components/heading_anchor_processor.rb +0 -34
  74. data/lib/docyard/components/icon_detector.rb +0 -57
  75. data/lib/docyard/components/icon_processor.rb +0 -51
  76. data/lib/docyard/components/table_of_contents_processor.rb +0 -64
  77. data/lib/docyard/components/table_wrapper_processor.rb +0 -18
  78. data/lib/docyard/components/tabs_parser.rb +0 -191
  79. data/lib/docyard/components/tabs_processor.rb +0 -44
  80. data/lib/docyard/components/tabs_range_finder.rb +0 -42
  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 → rendering/icons}/file_types.rb +0 -0
  88. /data/lib/docyard/{icons.rb → rendering/icons.rb} +0 -0
  89. /data/lib/docyard/{language_mapping.rb → rendering/language_mapping.rb} +0 -0
  90. /data/lib/docyard/{file_watcher.rb → server/file_watcher.rb} +0 -0
  91. /data/lib/docyard/{errors.rb → utils/errors.rb} +0 -0
  92. /data/lib/docyard/{logging.rb → utils/logging.rb} +0 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Components
5
+ module Support
6
+ module CodeBlock
7
+ class LineParser
8
+ def initialize(code_content)
9
+ @code_content = code_content
10
+ @lines = []
11
+ @current_line = ""
12
+ @open_tags = []
13
+ @in_tag = false
14
+ @tag_buffer = ""
15
+ end
16
+
17
+ def parse
18
+ @code_content.each_char { |char| process_char(char) }
19
+ finalize
20
+ end
21
+
22
+ private
23
+
24
+ def process_char(char)
25
+ case char
26
+ when "<" then start_tag(char)
27
+ when ">" then end_tag_if_applicable(char)
28
+ when "\n" then handle_newline
29
+ else handle_regular_char(char)
30
+ end
31
+ end
32
+
33
+ def start_tag(char)
34
+ @in_tag = true
35
+ @tag_buffer = char
36
+ end
37
+
38
+ def end_tag_if_applicable(char)
39
+ if @in_tag
40
+ @in_tag = false
41
+ @tag_buffer += char
42
+
43
+ if @tag_buffer.start_with?("</")
44
+ @open_tags.pop
45
+ elsif !@tag_buffer.end_with?("/>")
46
+ @open_tags << @tag_buffer
47
+ end
48
+
49
+ @current_line += @tag_buffer
50
+ @tag_buffer = ""
51
+ else
52
+ @current_line += char
53
+ end
54
+ end
55
+
56
+ def handle_newline
57
+ closing_tags = @open_tags.reverse.map { |tag| closing_tag_for(tag) }.join
58
+ @lines << "#{@current_line}#{closing_tags}\n"
59
+ @current_line = @open_tags.join
60
+ end
61
+
62
+ def handle_regular_char(char)
63
+ if @in_tag
64
+ @tag_buffer += char
65
+ else
66
+ @current_line += char
67
+ end
68
+ end
69
+
70
+ def finalize
71
+ @lines << @current_line unless @current_line.empty?
72
+ @lines << "" if @lines.empty?
73
+ @lines
74
+ end
75
+
76
+ def closing_tag_for(open_tag)
77
+ tag_name = open_tag.match(/<(\w+)/)[1]
78
+ "</#{tag_name}>"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "line_parser"
4
+
5
+ module Docyard
6
+ module Components
7
+ module Support
8
+ module CodeBlock
9
+ module LineWrapper
10
+ module_function
11
+
12
+ def wrap_code_block(html, block_data)
13
+ html.gsub(%r{<pre[^>]*><code[^>]*>(.*?)</code></pre>}m) do
14
+ pre_match = Regexp.last_match(0)
15
+ code_content = Regexp.last_match(1)
16
+ lines = LineParser.new(code_content).parse
17
+ wrapped_lines = wrap_lines_with_classes(lines, block_data)
18
+ pre_match.sub(code_content, wrapped_lines.join)
19
+ end
20
+ end
21
+
22
+ def wrap_lines_with_classes(lines, block_data)
23
+ lines.each_with_index.map do |line, index|
24
+ source_line = index + 1
25
+ display_line = block_data[:start_line] + index
26
+ classes = build_line_classes(source_line, display_line, block_data)
27
+ %(<span class="#{classes}">#{line}</span>)
28
+ end
29
+ end
30
+
31
+ DIFF_CLASSES = { addition: "docyard-code-line--diff-add", deletion: "docyard-code-line--diff-remove" }.freeze
32
+
33
+ def build_line_classes(source_line, display_line, block_data)
34
+ (["docyard-code-line"] + feature_classes(source_line, display_line, block_data)).join(" ")
35
+ end
36
+
37
+ def feature_classes(source_line, display_line, block_data)
38
+ [
39
+ ("docyard-code-line--highlighted" if block_data[:highlights].include?(display_line)),
40
+ DIFF_CLASSES[block_data[:diff_lines][source_line]],
41
+ ("docyard-code-line--focus" if block_data[:focus_lines][source_line]),
42
+ ("docyard-code-line--error" if block_data[:error_lines][source_line]),
43
+ ("docyard-code-line--warning" if block_data[:warning_lines][source_line])
44
+ ].compact
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Components
5
+ module Support
6
+ module CodeBlock
7
+ module Patterns
8
+ DIFF_MARKER_PATTERN = %r{
9
+ (?:
10
+ //\s*\[!code\s*([+-]{2})\] |
11
+ \#\s*\[!code\s*([+-]{2})\] |
12
+ /\*\s*\[!code\s*([+-]{2})\]\s*\*/ |
13
+ --\s*\[!code\s*([+-]{2})\] |
14
+ <!--\s*\[!code\s*([+-]{2})\]\s*--> |
15
+ ;\s*\[!code\s*([+-]{2})\]
16
+ )[^\S\n]*
17
+ }x
18
+
19
+ FOCUS_MARKER_PATTERN = %r{
20
+ (?:
21
+ //\s*\[!code\s+focus\] |
22
+ \#\s*\[!code\s+focus\] |
23
+ /\*\s*\[!code\s+focus\]\s*\*/ |
24
+ --\s*\[!code\s+focus\] |
25
+ <!--\s*\[!code\s+focus\]\s*--> |
26
+ ;\s*\[!code\s+focus\]
27
+ )[^\S\n]*
28
+ }x
29
+
30
+ ERROR_MARKER_PATTERN = %r{
31
+ (?:
32
+ //\s*\[!code\s+error\] |
33
+ \#\s*\[!code\s+error\] |
34
+ /\*\s*\[!code\s+error\]\s*\*/ |
35
+ --\s*\[!code\s+error\] |
36
+ <!--\s*\[!code\s+error\]\s*--> |
37
+ ;\s*\[!code\s+error\]
38
+ )[^\S\n]*
39
+ }x
40
+
41
+ WARNING_MARKER_PATTERN = %r{
42
+ (?:
43
+ //\s*\[!code\s+warning\] |
44
+ \#\s*\[!code\s+warning\] |
45
+ /\*\s*\[!code\s+warning\]\s*\*/ |
46
+ --\s*\[!code\s+warning\] |
47
+ <!--\s*\[!code\s+warning\]\s*--> |
48
+ ;\s*\[!code\s+warning\]
49
+ )[^\S\n]*
50
+ }x
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../rendering/language_mapping"
4
+
5
+ module Docyard
6
+ module Components
7
+ module Support
8
+ class CodeDetector
9
+ def self.detect(content)
10
+ new(content).detect
11
+ end
12
+
13
+ def initialize(content)
14
+ @content = content
15
+ end
16
+
17
+ def detect
18
+ return nil unless code_only?
19
+
20
+ language = extract_language
21
+ return nil unless language
22
+
23
+ icon_for_language(language)
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :content
29
+
30
+ def code_only?
31
+ stripped = content.strip
32
+ return false unless stripped.start_with?("```") && stripped.end_with?("```")
33
+
34
+ parts = stripped.split("```")
35
+ parts.length == 2 && parts[0].empty?
36
+ end
37
+
38
+ def extract_language
39
+ parts = content.strip.split("```")
40
+ return nil unless parts[1]
41
+
42
+ lines = parts[1].split("\n", 2)
43
+ lang_line = lines[0].strip
44
+ return nil if lang_line.empty? || lang_line.include?(" ")
45
+
46
+ lang_line.downcase
47
+ end
48
+
49
+ def icon_for_language(language)
50
+ if LanguageMapping.terminal_language?(language)
51
+ { icon: "terminal-window", source: "phosphor" }
52
+ elsif (extension = LanguageMapping.extension_for(language))
53
+ { icon: extension, source: "file-extension" }
54
+ else
55
+ { icon: "file", source: "phosphor" }
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../code_detector"
4
+
5
+ module Docyard
6
+ module Components
7
+ module Support
8
+ module Tabs
9
+ class IconDetector
10
+ MANUAL_ICON_PATTERN = /^:([a-z0-9-]+):\s*(.+)$/i
11
+ CodeDetector = Support::CodeDetector
12
+
13
+ def self.detect(tab_name, tab_content)
14
+ new(tab_name, tab_content).detect
15
+ end
16
+
17
+ def initialize(tab_name, tab_content)
18
+ @tab_name = tab_name
19
+ @tab_content = tab_content
20
+ end
21
+
22
+ def detect
23
+ manual_icon || auto_detected_icon || no_icon
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :tab_name, :tab_content
29
+
30
+ def manual_icon
31
+ return nil unless tab_name.match(MANUAL_ICON_PATTERN)
32
+
33
+ {
34
+ name: Regexp.last_match(2).strip,
35
+ icon: Regexp.last_match(1),
36
+ icon_source: "phosphor"
37
+ }
38
+ end
39
+
40
+ def auto_detected_icon
41
+ detected = CodeDetector.detect(tab_content)
42
+ return nil unless detected
43
+
44
+ {
45
+ name: tab_name,
46
+ icon: detected[:icon],
47
+ icon_source: detected[:source]
48
+ }
49
+ end
50
+
51
+ def no_icon
52
+ {
53
+ name: tab_name,
54
+ icon: nil,
55
+ icon_source: nil
56
+ }
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../code_block/feature_extractor"
4
+ require_relative "../code_block/icon_detector"
5
+ require_relative "../code_block/line_wrapper"
6
+ require_relative "icon_detector"
7
+ require_relative "../../../rendering/icons"
8
+ require_relative "../../../rendering/renderer"
9
+ require "kramdown"
10
+ require "kramdown-parser-gfm"
11
+ require "cgi"
12
+
13
+ module Docyard
14
+ module Components
15
+ module Support
16
+ module Tabs
17
+ class Parser
18
+ include Utils::HtmlHelpers
19
+
20
+ IconDetector = Tabs::IconDetector
21
+ CodeBlockFeatureExtractor = CodeBlock::FeatureExtractor
22
+ CodeBlockIconDetector = CodeBlock::IconDetector
23
+ CodeBlockLineWrapper = CodeBlock::LineWrapper
24
+
25
+ def self.parse(content)
26
+ new(content).parse
27
+ end
28
+
29
+ def initialize(content)
30
+ @content = content
31
+ end
32
+
33
+ def parse
34
+ sections.filter_map { |section| parse_section(section) }
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :content
40
+
41
+ def sections
42
+ content.split(/^==[ \t]+/)
43
+ end
44
+
45
+ def parse_section(section)
46
+ return nil if section.strip.empty?
47
+
48
+ tab_name, tab_content = extract_tab_parts(section)
49
+ return nil if tab_name.nil? || tab_name.empty?
50
+
51
+ build_tab_data(tab_name, tab_content)
52
+ end
53
+
54
+ def extract_tab_parts(section)
55
+ parts = section.split("\n", 2)
56
+ [parts[0]&.strip, parts[1]&.strip || ""]
57
+ end
58
+
59
+ def build_tab_data(tab_name, tab_content)
60
+ icon_data = IconDetector.detect(tab_name, tab_content)
61
+
62
+ extracted = CodeBlockFeatureExtractor.process_markdown(tab_content)
63
+ rendered_content = render_markdown(extracted[:cleaned_markdown])
64
+ enhanced_content = enhance_code_blocks(rendered_content, extracted[:blocks])
65
+
66
+ {
67
+ name: icon_data[:name],
68
+ content: enhanced_content,
69
+ icon: icon_data[:icon],
70
+ icon_source: icon_data[:icon_source]
71
+ }
72
+ end
73
+
74
+ def render_markdown(markdown_content)
75
+ return "" if markdown_content.empty?
76
+
77
+ Kramdown::Document.new(
78
+ markdown_content,
79
+ input: "GFM",
80
+ hard_wrap: false,
81
+ syntax_highlighter: "rouge"
82
+ ).to_html
83
+ end
84
+
85
+ def enhance_code_blocks(html, blocks)
86
+ return html unless html.include?('<div class="highlight">')
87
+
88
+ block_index = 0
89
+ html.gsub(%r{<div class="highlight">(.*?)</div>}m) do
90
+ block_data = blocks[block_index] || {}
91
+ block_index += 1
92
+ render_enhanced_code_block(Regexp.last_match, block_data)
93
+ end
94
+ end
95
+
96
+ def render_enhanced_code_block(match, block_data)
97
+ original_html = match[0]
98
+ inner_html = match[1]
99
+ code_text = extract_code_text(inner_html)
100
+
101
+ processed_html = if needs_line_wrapping?(block_data)
102
+ wrap_code_block_lines(original_html, block_data)
103
+ else
104
+ original_html
105
+ end
106
+
107
+ Renderer.new.render_partial("_code_block", build_full_locals(processed_html, code_text, block_data))
108
+ end
109
+
110
+ def needs_line_wrapping?(block_data)
111
+ %i[highlights diff_lines focus_lines error_lines warning_lines].any? do |key|
112
+ block_data[key]&.any?
113
+ end
114
+ end
115
+
116
+ def wrap_code_block_lines(html, block_data)
117
+ wrapper_data = {
118
+ highlights: block_data[:highlights] || [],
119
+ diff_lines: block_data[:diff_lines] || {},
120
+ focus_lines: block_data[:focus_lines] || {},
121
+ error_lines: block_data[:error_lines] || {},
122
+ warning_lines: block_data[:warning_lines] || {},
123
+ start_line: extract_start_line(block_data[:option])
124
+ }
125
+ CodeBlockLineWrapper.wrap_code_block(html, wrapper_data)
126
+ end
127
+
128
+ def build_full_locals(processed_html, code_text, block_data)
129
+ title_data = CodeBlockIconDetector.detect(block_data[:title], block_data[:lang])
130
+ show_line_numbers = line_numbers_enabled?(block_data[:option])
131
+ start_line = extract_start_line(block_data[:option])
132
+
133
+ base_locals(processed_html, code_text, show_line_numbers, start_line)
134
+ .merge(feature_locals(block_data))
135
+ .merge(title_locals(title_data))
136
+ end
137
+
138
+ def base_locals(processed_html, code_text, show_line_numbers, start_line)
139
+ {
140
+ code_block_html: processed_html,
141
+ code_text: escape_html_attribute(code_text),
142
+ copy_icon: Icons.render("copy", "regular") || "",
143
+ show_line_numbers: show_line_numbers,
144
+ line_numbers: show_line_numbers ? generate_line_numbers(code_text, start_line) : [],
145
+ start_line: start_line
146
+ }
147
+ end
148
+
149
+ def feature_locals(block_data)
150
+ {
151
+ highlights: block_data[:highlights] || [],
152
+ diff_lines: block_data[:diff_lines] || {},
153
+ focus_lines: block_data[:focus_lines] || {},
154
+ error_lines: block_data[:error_lines] || {},
155
+ warning_lines: block_data[:warning_lines] || {}
156
+ }
157
+ end
158
+
159
+ def title_locals(title_data)
160
+ {
161
+ title: title_data[:title],
162
+ icon: title_data[:icon],
163
+ icon_source: title_data[:icon_source]
164
+ }
165
+ end
166
+
167
+ def line_numbers_enabled?(block_option)
168
+ return false if block_option == ":no-line-numbers"
169
+ return true if block_option&.start_with?(":line-numbers")
170
+
171
+ false
172
+ end
173
+
174
+ def extract_start_line(block_option)
175
+ return 1 unless block_option&.include?("=")
176
+
177
+ block_option.split("=").last.to_i
178
+ end
179
+
180
+ def generate_line_numbers(code_text, start_line)
181
+ line_count = code_text.lines.count
182
+ line_count = 1 if line_count.zero?
183
+ (start_line...(start_line + line_count)).to_a
184
+ end
185
+
186
+ def extract_code_text(html)
187
+ text = html.gsub(/<[^>]+>/, "")
188
+ text = CGI.unescapeHTML(text)
189
+ text.strip
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ module Components
5
+ module Support
6
+ module Tabs
7
+ module RangeFinder
8
+ module_function
9
+
10
+ def find_ranges(html)
11
+ ranges = []
12
+ start_pattern = '<div class="docyard-tabs"'
13
+
14
+ pos = 0
15
+ while (start_pos = html.index(start_pattern, pos))
16
+ end_pos = find_matching_close_div(html, start_pos)
17
+ ranges << (start_pos...end_pos) if end_pos
18
+ pos = end_pos || (start_pos + 1)
19
+ end
20
+ ranges
21
+ end
22
+
23
+ def find_matching_close_div(html, start_pos)
24
+ depth = 0
25
+ pos = start_pos
26
+
27
+ while pos < html.length
28
+ if html[pos, 4] == "<div"
29
+ depth += 1
30
+ pos += 4
31
+ elsif html[pos, 6] == "</div>"
32
+ depth -= 1
33
+ return pos + 6 if depth.zero?
34
+
35
+ pos += 6
36
+ else
37
+ pos += 1
38
+ end
39
+ end
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docyard
4
+ class BrandingResolver
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ def resolve
10
+ return default_branding unless config
11
+
12
+ default_branding.merge(config_branding_options)
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :config
18
+
19
+ def default_branding
20
+ {
21
+ site_title: Constants::DEFAULT_SITE_TITLE,
22
+ site_description: "",
23
+ logo: Constants::DEFAULT_LOGO_PATH,
24
+ logo_dark: Constants::DEFAULT_LOGO_DARK_PATH,
25
+ favicon: nil,
26
+ display_logo: true,
27
+ display_title: true
28
+ }
29
+ end
30
+
31
+ def config_branding_options
32
+ site_options.merge(logo_options).merge(search_options).merge(appearance_options)
33
+ end
34
+
35
+ def site_options
36
+ {
37
+ site_title: config.site.title || Constants::DEFAULT_SITE_TITLE,
38
+ site_description: config.site.description || "",
39
+ favicon: config.branding.favicon
40
+ }
41
+ end
42
+
43
+ def logo_options
44
+ branding = config.branding
45
+ {
46
+ logo: resolve_logo(branding.logo, branding.logo_dark),
47
+ logo_dark: resolve_logo_dark(branding.logo, branding.logo_dark)
48
+ }
49
+ end
50
+
51
+ def search_options
52
+ {
53
+ search_enabled: config.search.enabled != false,
54
+ search_placeholder: config.search.placeholder || "Search documentation..."
55
+ }
56
+ end
57
+
58
+ def appearance_options
59
+ appearance = config.branding.appearance || {}
60
+ {
61
+ display_logo: appearance["logo"] != false,
62
+ display_title: appearance["title"] != false
63
+ }
64
+ end
65
+
66
+ def resolve_logo(logo, logo_dark)
67
+ logo || logo_dark || Constants::DEFAULT_LOGO_PATH
68
+ end
69
+
70
+ def resolve_logo_dark(logo, logo_dark)
71
+ logo_dark || logo || Constants::DEFAULT_LOGO_DARK_PATH
72
+ end
73
+ end
74
+ end
@@ -9,6 +9,7 @@ module Docyard
9
9
 
10
10
  RELOAD_ENDPOINT = "/_docyard/reload"
11
11
  ASSETS_PREFIX = "/assets/"
12
+ PAGEFIND_PREFIX = "/pagefind/"
12
13
 
13
14
  INDEX_FILE = "index"
14
15
  INDEX_TITLE = "Home"
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "yaml"
4
4
  require_relative "config/validator"
5
- require_relative "constants"
5
+ require_relative "config/constants"
6
6
 
7
7
  module Docyard
8
8
  class Config
@@ -37,6 +37,11 @@ module Docyard
37
37
  },
38
38
  "markdown" => {
39
39
  "lineNumbers" => false
40
+ },
41
+ "search" => {
42
+ "enabled" => true,
43
+ "placeholder" => "Search documentation...",
44
+ "exclude" => []
40
45
  }
41
46
  }.freeze
42
47
 
@@ -81,6 +86,10 @@ module Docyard
81
86
  @markdown ||= ConfigSection.new(data["markdown"])
82
87
  end
83
88
 
89
+ def search
90
+ @search ||= ConfigSection.new(data["search"])
91
+ end
92
+
84
93
  private
85
94
 
86
95
  def load_config_data
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "renderer"
4
- require_relative "utils/path_resolver"
3
+ require_relative "../rendering/renderer"
4
+ require_relative "../utils/path_resolver"
5
5
 
6
6
  module Docyard
7
7
  class PrevNextBuilder