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
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Components
5
- class CodeLineParser
6
- def initialize(code_content)
7
- @code_content = code_content
8
- @lines = []
9
- @current_line = ""
10
- @open_tags = []
11
- @in_tag = false
12
- @tag_buffer = ""
13
- end
14
-
15
- def parse
16
- @code_content.each_char { |char| process_char(char) }
17
- finalize
18
- end
19
-
20
- private
21
-
22
- def process_char(char)
23
- case char
24
- when "<" then start_tag(char)
25
- when ">" then end_tag_if_applicable(char)
26
- when "\n" then handle_newline
27
- else handle_regular_char(char)
28
- end
29
- end
30
-
31
- def start_tag(char)
32
- @in_tag = true
33
- @tag_buffer = char
34
- end
35
-
36
- def end_tag_if_applicable(char)
37
- if @in_tag
38
- @in_tag = false
39
- @tag_buffer += char
40
-
41
- if @tag_buffer.start_with?("</")
42
- @open_tags.pop
43
- elsif !@tag_buffer.end_with?("/>")
44
- @open_tags << @tag_buffer
45
- end
46
-
47
- @current_line += @tag_buffer
48
- @tag_buffer = ""
49
- else
50
- @current_line += char
51
- end
52
- end
53
-
54
- def handle_newline
55
- closing_tags = @open_tags.reverse.map { |tag| closing_tag_for(tag) }.join
56
- @lines << "#{@current_line}#{closing_tags}\n"
57
- @current_line = @open_tags.join
58
- end
59
-
60
- def handle_regular_char(char)
61
- if @in_tag
62
- @tag_buffer += char
63
- else
64
- @current_line += char
65
- end
66
- end
67
-
68
- def finalize
69
- @lines << @current_line unless @current_line.empty?
70
- @lines << "" if @lines.empty?
71
- @lines
72
- end
73
-
74
- def closing_tag_for(open_tag)
75
- tag_name = open_tag.match(/<(\w+)/)[1]
76
- "</#{tag_name}>"
77
- end
78
- end
79
- end
80
- end
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_processor"
4
-
5
- module Docyard
6
- module Components
7
- class CodeSnippetImportPreprocessor < BaseProcessor
8
- EXTENSION_MAP = {
9
- "rb" => "ruby",
10
- "js" => "javascript",
11
- "ts" => "typescript",
12
- "py" => "python",
13
- "yml" => "yaml",
14
- "md" => "markdown",
15
- "sh" => "bash",
16
- "zsh" => "bash",
17
- "jsx" => "jsx",
18
- "tsx" => "tsx"
19
- }.freeze
20
-
21
- IMPORT_PATTERN = %r{^<<<\s+@/([^\s{#]+)(?:#([\w-]+))?(?:\{([^}]+)\})?\s*$}
22
-
23
- self.priority = 1
24
-
25
- def preprocess(content)
26
- @docs_root = context[:docs_root] || "docs"
27
- content.gsub(IMPORT_PATTERN) { |_| process_import(Regexp.last_match) }
28
- end
29
-
30
- private
31
-
32
- def process_import(match)
33
- filepath = match[1]
34
- region = match[2]
35
- options = match[3]
36
-
37
- file_content = read_file(filepath)
38
- return import_error(filepath, "File not found") unless file_content
39
-
40
- file_content = extract_region(file_content, region) if region
41
- return import_error(filepath, "Region '#{region}' not found") unless file_content
42
-
43
- build_code_block(file_content, filepath, options)
44
- end
45
-
46
- def read_file(filepath)
47
- full_path = File.join(@docs_root, filepath)
48
- return nil unless File.exist?(full_path)
49
-
50
- File.read(full_path)
51
- end
52
-
53
- def extract_region(content, region_name)
54
- region_start = %r{^[ \t]*(?://|#|/\*)\s*#region\s+#{Regexp.escape(region_name)}\b.*$}
55
- region_end = %r{^[ \t]*(?://|#|/\*|\*/)\s*#endregion\s*#{Regexp.escape(region_name)}?\b.*$}
56
-
57
- lines = content.lines
58
- start_index = lines.find_index { |line| line.match?(region_start) }
59
- return nil unless start_index
60
-
61
- end_index = lines[(start_index + 1)..].find_index { |line| line.match?(region_end) }
62
- return nil unless end_index
63
-
64
- end_index += start_index + 1
65
- lines[(start_index + 1)...end_index].join
66
- end
67
-
68
- def build_code_block(content, filepath, options)
69
- lang = detect_language(filepath)
70
- highlights = nil
71
-
72
- if options
73
- parsed = parse_options(options)
74
- lang = parsed[:lang] if parsed[:lang]
75
- highlights = parsed[:highlights] if parsed[:highlights]
76
- end
77
-
78
- content = extract_line_range(content, highlights) if highlights&.include?("-") && !highlights.include?(",")
79
-
80
- meta = build_meta_string(highlights, filepath)
81
-
82
- "```#{lang}#{meta}\n#{content.chomp}\n```"
83
- end
84
-
85
- def parse_options(options)
86
- parts = options.strip.split(/\s+/)
87
- result = { highlights: nil, lang: nil }
88
-
89
- parts.each do |part|
90
- if part.match?(/^[\d,-]+$/)
91
- result[:highlights] = part
92
- else
93
- result[:lang] = part
94
- end
95
- end
96
-
97
- result
98
- end
99
-
100
- def extract_line_range(content, range_str)
101
- return content unless range_str&.match?(/^\d+-\d+$/)
102
-
103
- start_line, end_line = range_str.split("-").map(&:to_i)
104
- lines = content.lines
105
- lines[(start_line - 1)..(end_line - 1)]&.join || content
106
- end
107
-
108
- def build_meta_string(highlights, filepath)
109
- parts = []
110
- parts << " [#{File.basename(filepath)}]" if filepath
111
- parts << " {#{highlights}}" if highlights && !highlights.match?(/^\d+-\d+$/)
112
- parts.join
113
- end
114
-
115
- def detect_language(filepath)
116
- ext = File.extname(filepath).delete_prefix(".")
117
- EXTENSION_MAP[ext] || ext
118
- end
119
-
120
- def import_error(filepath, message)
121
- "```\nError importing #{filepath}: #{message}\n```"
122
- end
123
- end
124
- end
125
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Components
5
- class HeadingAnchorProcessor < BaseProcessor
6
- self.priority = 30
7
-
8
- def postprocess(html)
9
- add_anchor_links(html)
10
- end
11
-
12
- private
13
-
14
- def add_anchor_links(html)
15
- html.gsub(%r{<(h[2-6])\s+id="([^"]+)">(.*?)</\1>}m) do |_match|
16
- tag = Regexp.last_match(1)
17
- id = Regexp.last_match(2)
18
- content = Regexp.last_match(3)
19
-
20
- anchor_html = render_anchor_link(id)
21
-
22
- "<#{tag} id=\"#{id}\">#{content}#{anchor_html}</#{tag}>"
23
- end
24
- end
25
-
26
- def render_anchor_link(id)
27
- renderer = Renderer.new
28
- renderer.render_partial("_heading_anchor", {
29
- id: id
30
- })
31
- end
32
- end
33
- end
34
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "code_detector"
4
-
5
- module Docyard
6
- module Components
7
- class IconDetector
8
- MANUAL_ICON_PATTERN = /^:([a-z0-9-]+):\s*(.+)$/i
9
-
10
- def self.detect(tab_name, tab_content)
11
- new(tab_name, tab_content).detect
12
- end
13
-
14
- def initialize(tab_name, tab_content)
15
- @tab_name = tab_name
16
- @tab_content = tab_content
17
- end
18
-
19
- def detect
20
- manual_icon || auto_detected_icon || no_icon
21
- end
22
-
23
- private
24
-
25
- attr_reader :tab_name, :tab_content
26
-
27
- def manual_icon
28
- return nil unless tab_name.match(MANUAL_ICON_PATTERN)
29
-
30
- {
31
- name: Regexp.last_match(2).strip,
32
- icon: Regexp.last_match(1),
33
- icon_source: "phosphor"
34
- }
35
- end
36
-
37
- def auto_detected_icon
38
- detected = CodeDetector.detect(tab_content)
39
- return nil unless detected
40
-
41
- {
42
- name: tab_name,
43
- icon: detected[:icon],
44
- icon_source: detected[:source]
45
- }
46
- end
47
-
48
- def no_icon
49
- {
50
- name: tab_name,
51
- icon: nil,
52
- icon_source: nil
53
- }
54
- end
55
- end
56
- end
57
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../icons"
4
- require_relative "base_processor"
5
-
6
- module Docyard
7
- module Components
8
- class IconProcessor < BaseProcessor
9
- self.priority = 20
10
-
11
- ICON_PATTERN = /:([a-z][a-z0-9-]*):(?:([a-z]+):)?/i
12
-
13
- def postprocess(html)
14
- segments = split_preserving_code_blocks(html)
15
-
16
- segments.map do |segment|
17
- segment[:type] == :code ? segment[:content] : process_segment(segment[:content])
18
- end.join
19
- end
20
-
21
- private
22
-
23
- def split_preserving_code_blocks(html)
24
- segments = []
25
- current_pos = 0
26
-
27
- html.scan(%r{<(code|pre)[^>]*>.*?</\1>}m) do
28
- match_start = Regexp.last_match.begin(0)
29
- match_end = Regexp.last_match.end(0)
30
-
31
- segments << { type: :text, content: html[current_pos...match_start] } if match_start > current_pos
32
- segments << { type: :code, content: html[match_start...match_end] }
33
-
34
- current_pos = match_end
35
- end
36
-
37
- segments << { type: :text, content: html[current_pos..] } if current_pos < html.length
38
-
39
- segments.empty? ? [{ type: :text, content: html }] : segments
40
- end
41
-
42
- def process_segment(content)
43
- content.gsub(ICON_PATTERN) do
44
- icon_name = Regexp.last_match(1)
45
- weight = Regexp.last_match(2) || "regular"
46
- Icons.render(icon_name, weight) || Regexp.last_match(0)
47
- end
48
- end
49
- end
50
- end
51
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Components
5
- class TableOfContentsProcessor < BaseProcessor
6
- self.priority = 35
7
-
8
- def postprocess(html)
9
- headings = extract_headings(html)
10
- context[:toc] = headings
11
- html
12
- end
13
-
14
- private
15
-
16
- def extract_headings(html)
17
- headings = []
18
-
19
- html.scan(%r{<(h[2-4])\s+id="([^"]+)">(.*?)</\1>}m) do
20
- level = Regexp.last_match(1)[1].to_i
21
- id = Regexp.last_match(2)
22
- text = strip_html(Regexp.last_match(3))
23
-
24
- headings << {
25
- level: level,
26
- id: id,
27
- text: text
28
- }
29
- end
30
-
31
- build_hierarchy(headings)
32
- end
33
-
34
- def build_hierarchy(headings)
35
- return [] if headings.empty?
36
-
37
- root = []
38
- stack = []
39
-
40
- headings.each do |heading|
41
- heading[:children] = []
42
-
43
- stack.pop while stack.any? && stack.last[:level] >= heading[:level]
44
-
45
- if stack.empty?
46
- root << heading
47
- else
48
- stack.last[:children] << heading
49
- end
50
-
51
- stack << heading
52
- end
53
-
54
- root
55
- end
56
-
57
- def strip_html(text)
58
- text.gsub(%r{<a[^>]*class="heading-anchor"[^>]*>.*?</a>}, "")
59
- .gsub(/<[^>]+>/, "")
60
- .strip
61
- end
62
- end
63
- end
64
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Components
5
- class TableWrapperProcessor < BaseProcessor
6
- self.priority = 100
7
-
8
- def postprocess(html)
9
- wrapped = html.gsub(/<table([^>]*)>/) do
10
- attributes = Regexp.last_match(1)
11
- "<div class=\"table-wrapper\"><table#{attributes}>"
12
- end
13
-
14
- wrapped.gsub("</table>", "</table></div>")
15
- end
16
- end
17
- end
18
- end
@@ -1,191 +0,0 @@
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 "../icons"
8
- require_relative "../renderer"
9
- require "kramdown"
10
- require "kramdown-parser-gfm"
11
- require "cgi"
12
-
13
- module Docyard
14
- module Components
15
- class TabsParser
16
- def self.parse(content)
17
- new(content).parse
18
- end
19
-
20
- def initialize(content)
21
- @content = content
22
- end
23
-
24
- def parse
25
- sections.filter_map { |section| parse_section(section) }
26
- end
27
-
28
- private
29
-
30
- attr_reader :content
31
-
32
- def sections
33
- content.split(/^==[ \t]+/)
34
- end
35
-
36
- def parse_section(section)
37
- return nil if section.strip.empty?
38
-
39
- tab_name, tab_content = extract_tab_parts(section)
40
- return nil if tab_name.nil? || tab_name.empty?
41
-
42
- build_tab_data(tab_name, tab_content)
43
- end
44
-
45
- def extract_tab_parts(section)
46
- parts = section.split("\n", 2)
47
- [parts[0]&.strip, parts[1]&.strip || ""]
48
- end
49
-
50
- def build_tab_data(tab_name, tab_content)
51
- icon_data = IconDetector.detect(tab_name, tab_content)
52
-
53
- extracted = CodeBlockFeatureExtractor.process_markdown(tab_content)
54
- rendered_content = render_markdown(extracted[:cleaned_markdown])
55
- enhanced_content = enhance_code_blocks(rendered_content, extracted[:blocks])
56
-
57
- {
58
- name: icon_data[:name],
59
- content: enhanced_content,
60
- icon: icon_data[:icon],
61
- icon_source: icon_data[:icon_source]
62
- }
63
- end
64
-
65
- def render_markdown(markdown_content)
66
- return "" if markdown_content.empty?
67
-
68
- Kramdown::Document.new(
69
- markdown_content,
70
- input: "GFM",
71
- hard_wrap: false,
72
- syntax_highlighter: "rouge"
73
- ).to_html
74
- end
75
-
76
- def enhance_code_blocks(html, blocks)
77
- return html unless html.include?('<div class="highlight">')
78
-
79
- block_index = 0
80
- html.gsub(%r{<div class="highlight">(.*?)</div>}m) do
81
- block_data = blocks[block_index] || {}
82
- block_index += 1
83
- render_enhanced_code_block(Regexp.last_match, block_data)
84
- end
85
- end
86
-
87
- def render_enhanced_code_block(match, block_data)
88
- original_html = match[0]
89
- inner_html = match[1]
90
- code_text = extract_code_text(inner_html)
91
-
92
- processed_html = if needs_line_wrapping?(block_data)
93
- wrap_code_block_lines(original_html, block_data)
94
- else
95
- original_html
96
- end
97
-
98
- Renderer.new.render_partial("_code_block", build_full_locals(processed_html, code_text, block_data))
99
- end
100
-
101
- def needs_line_wrapping?(block_data)
102
- %i[highlights diff_lines focus_lines error_lines warning_lines].any? do |key|
103
- block_data[key]&.any?
104
- end
105
- end
106
-
107
- def wrap_code_block_lines(html, block_data)
108
- wrapper_data = {
109
- highlights: block_data[:highlights] || [],
110
- diff_lines: block_data[:diff_lines] || {},
111
- focus_lines: block_data[:focus_lines] || {},
112
- error_lines: block_data[:error_lines] || {},
113
- warning_lines: block_data[:warning_lines] || {},
114
- start_line: extract_start_line(block_data[:option])
115
- }
116
- CodeBlockLineWrapper.wrap_code_block(html, wrapper_data)
117
- end
118
-
119
- def build_full_locals(processed_html, code_text, block_data)
120
- title_data = CodeBlockIconDetector.detect(block_data[:title], block_data[:lang])
121
- show_line_numbers = line_numbers_enabled?(block_data[:option])
122
- start_line = extract_start_line(block_data[:option])
123
-
124
- base_locals(processed_html, code_text, show_line_numbers, start_line)
125
- .merge(feature_locals(block_data))
126
- .merge(title_locals(title_data))
127
- end
128
-
129
- def base_locals(processed_html, code_text, show_line_numbers, start_line)
130
- {
131
- code_block_html: processed_html,
132
- code_text: escape_html_attribute(code_text),
133
- copy_icon: Icons.render("copy", "regular") || "",
134
- show_line_numbers: show_line_numbers,
135
- line_numbers: show_line_numbers ? generate_line_numbers(code_text, start_line) : [],
136
- start_line: start_line
137
- }
138
- end
139
-
140
- def feature_locals(block_data)
141
- {
142
- highlights: block_data[:highlights] || [],
143
- diff_lines: block_data[:diff_lines] || {},
144
- focus_lines: block_data[:focus_lines] || {},
145
- error_lines: block_data[:error_lines] || {},
146
- warning_lines: block_data[:warning_lines] || {}
147
- }
148
- end
149
-
150
- def title_locals(title_data)
151
- {
152
- title: title_data[:title],
153
- icon: title_data[:icon],
154
- icon_source: title_data[:icon_source]
155
- }
156
- end
157
-
158
- def line_numbers_enabled?(block_option)
159
- return false if block_option == ":no-line-numbers"
160
- return true if block_option&.start_with?(":line-numbers")
161
-
162
- false
163
- end
164
-
165
- def extract_start_line(block_option)
166
- return 1 unless block_option&.include?("=")
167
-
168
- block_option.split("=").last.to_i
169
- end
170
-
171
- def generate_line_numbers(code_text, start_line)
172
- line_count = code_text.lines.count
173
- line_count = 1 if line_count.zero?
174
- (start_line...(start_line + line_count)).to_a
175
- end
176
-
177
- def extract_code_text(html)
178
- text = html.gsub(/<[^>]+>/, "")
179
- text = CGI.unescapeHTML(text)
180
- text.strip
181
- end
182
-
183
- def escape_html_attribute(text)
184
- text.gsub('"', "&quot;")
185
- .gsub("'", "&#39;")
186
- .gsub("<", "&lt;")
187
- .gsub(">", "&gt;")
188
- end
189
- end
190
- end
191
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../renderer"
4
- require_relative "base_processor"
5
- require_relative "tabs_parser"
6
- require "securerandom"
7
-
8
- module Docyard
9
- module Components
10
- class TabsProcessor < BaseProcessor
11
- self.priority = 15
12
-
13
- def preprocess(content)
14
- return content unless content.include?(":::tabs")
15
-
16
- content.gsub(/^:::[ \t]*tabs[ \t]*\n(.*?)^:::[ \t]*$/m) do
17
- process_tabs_block(Regexp.last_match(1))
18
- end
19
- end
20
-
21
- private
22
-
23
- def process_tabs_block(tabs_content)
24
- tabs = TabsParser.parse(tabs_content)
25
- return "" if tabs.empty?
26
-
27
- wrap_in_nomarkdown(render_tabs(tabs))
28
- end
29
-
30
- def render_tabs(tabs)
31
- Renderer.new.render_partial(
32
- "_tabs", {
33
- tabs: tabs,
34
- group_id: SecureRandom.hex(4)
35
- }
36
- )
37
- end
38
-
39
- def wrap_in_nomarkdown(html)
40
- "{::nomarkdown}\n#{html}\n{:/nomarkdown}"
41
- end
42
- end
43
- end
44
- end