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,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "code_block_patterns"
4
-
5
- module Docyard
6
- module Components
7
- module CodeBlockFeatureExtractor
8
- include CodeBlockPatterns
9
-
10
- CODE_FENCE_REGEX = /^```(\w+)(?:\s*\[([^\]]+)\])?(:\S+)?(?:\s*\{([^}\n]+)\})?[ \t]*\n(.*?)^```/m
11
-
12
- module_function
13
-
14
- def process_markdown(markdown)
15
- blocks = []
16
- cleaned = markdown.gsub(CODE_FENCE_REGEX) do
17
- block_data = extract_block_data(Regexp.last_match)
18
- blocks << block_data
19
- "```#{block_data[:lang]}\n#{block_data[:cleaned_content]}```"
20
- end
21
- { cleaned_markdown: cleaned, blocks: blocks }
22
- end
23
-
24
- def extract_block_data(match)
25
- code_content = match[5]
26
- diff_info = extract_diff_lines(code_content)
27
- focus_info = extract_focus_lines(diff_info[:cleaned_content])
28
- error_info = extract_error_lines(focus_info[:cleaned_content])
29
- warning_info = extract_warning_lines(error_info[:cleaned_content])
30
-
31
- build_block_result(match, diff_info, focus_info, error_info, warning_info)
32
- end
33
-
34
- def build_block_result(match, diff_info, focus_info, error_info, warning_info)
35
- {
36
- lang: match[1],
37
- title: match[2],
38
- option: match[3],
39
- highlights: parse_highlights(match[4]),
40
- diff_lines: diff_info[:lines],
41
- focus_lines: focus_info[:lines],
42
- error_lines: error_info[:lines],
43
- warning_lines: warning_info[:lines],
44
- cleaned_content: warning_info[:cleaned_content]
45
- }
46
- end
47
-
48
- def parse_highlights(highlights_str)
49
- return [] if highlights_str.nil? || highlights_str.strip.empty?
50
-
51
- highlights_str.split(",").flat_map { |part| parse_highlight_part(part.strip) }.uniq.sort
52
- end
53
-
54
- def parse_highlight_part(part)
55
- return (part.split("-")[0].to_i..part.split("-")[1].to_i).to_a if part.include?("-")
56
-
57
- [part.to_i]
58
- end
59
-
60
- def extract_diff_lines(code_content)
61
- lines = code_content.lines
62
- diff_lines = {}
63
- cleaned_lines = []
64
-
65
- lines.each_with_index do |line, index|
66
- line_num = index + 1
67
-
68
- if (match = line.match(DIFF_MARKER_PATTERN))
69
- diff_type = match.captures.compact.first
70
- diff_lines[line_num] = diff_type == "++" ? :addition : :deletion
71
- cleaned_line = line.gsub(DIFF_MARKER_PATTERN, "")
72
- cleaned_lines << cleaned_line
73
- else
74
- cleaned_lines << line
75
- end
76
- end
77
-
78
- { lines: diff_lines, cleaned_content: cleaned_lines.join }
79
- end
80
-
81
- def extract_focus_lines(code_content)
82
- extract_marker_lines(code_content, FOCUS_MARKER_PATTERN)
83
- end
84
-
85
- def extract_error_lines(code_content)
86
- extract_marker_lines(code_content, ERROR_MARKER_PATTERN)
87
- end
88
-
89
- def extract_warning_lines(code_content)
90
- extract_marker_lines(code_content, WARNING_MARKER_PATTERN)
91
- end
92
-
93
- def extract_marker_lines(code_content, pattern)
94
- lines = code_content.lines
95
- marker_lines = {}
96
- cleaned_lines = []
97
-
98
- lines.each_with_index do |line, index|
99
- line_num = index + 1
100
-
101
- if line.match?(pattern)
102
- marker_lines[line_num] = true
103
- cleaned_lines << line.gsub(pattern, "")
104
- else
105
- cleaned_lines << line
106
- end
107
- end
108
-
109
- { lines: marker_lines, cleaned_content: cleaned_lines.join }
110
- end
111
- end
112
- end
113
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_processor"
4
-
5
- module Docyard
6
- module Components
7
- class CodeBlockFocusPreprocessor < BaseProcessor
8
- self.priority = 7
9
-
10
- FOCUS_MARKER_PATTERN = %r{
11
- (?:
12
- //\s*\[!code\s+focus\] |
13
- \#\s*\[!code\s+focus\] |
14
- /\*\s*\[!code\s+focus\]\s*\*/ |
15
- --\s*\[!code\s+focus\] |
16
- <!--\s*\[!code\s+focus\]\s*--> |
17
- ;\s*\[!code\s+focus\]
18
- )[^\S\n]*
19
- }x
20
-
21
- CODE_BLOCK_REGEX = /^```(\w*).*?\n(.*?)^```/m
22
- TABS_BLOCK_REGEX = /^:::[ \t]*tabs[ \t]*\n.*?^:::[ \t]*$/m
23
-
24
- def preprocess(content)
25
- context[:code_block_focus_lines] ||= []
26
- @block_index = 0
27
- @tabs_ranges = find_tabs_ranges(content)
28
-
29
- content.gsub(CODE_BLOCK_REGEX) { |_| process_code_block(Regexp.last_match) }
30
- end
31
-
32
- private
33
-
34
- def process_code_block(match)
35
- return match[0] if inside_tabs?(match.begin(0))
36
-
37
- focus_info = extract_focus_lines(match[2])
38
- context[:code_block_focus_lines][@block_index] = focus_info[:lines]
39
- @block_index += 1
40
- match[0].sub(match[2], focus_info[:cleaned_content])
41
- end
42
-
43
- def extract_focus_lines(code_content)
44
- lines = code_content.lines
45
- focus_lines = {}
46
- cleaned_lines = []
47
-
48
- lines.each_with_index do |line, index|
49
- line_num = index + 1
50
-
51
- if line.match?(FOCUS_MARKER_PATTERN)
52
- focus_lines[line_num] = true
53
- cleaned_line = line.gsub(FOCUS_MARKER_PATTERN, "")
54
- cleaned_lines << cleaned_line
55
- else
56
- cleaned_lines << line
57
- end
58
- end
59
-
60
- { lines: focus_lines, cleaned_content: cleaned_lines.join }
61
- end
62
-
63
- def inside_tabs?(position)
64
- @tabs_ranges.any? { |range| range.cover?(position) }
65
- end
66
-
67
- def find_tabs_ranges(content)
68
- ranges = []
69
- content.scan(TABS_BLOCK_REGEX) do
70
- match = Regexp.last_match
71
- ranges << (match.begin(0)...match.end(0))
72
- end
73
- ranges
74
- end
75
- end
76
- end
77
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../language_mapping"
4
-
5
- module Docyard
6
- module Components
7
- module CodeBlockIconDetector
8
- MANUAL_ICON_PATTERN = /^:([a-z0-9-]+):\s*(.+)$/i
9
-
10
- module_function
11
-
12
- def detect(title, language)
13
- return { title: nil, icon: nil, icon_source: nil } if title.nil?
14
-
15
- if (match = title.match(MANUAL_ICON_PATTERN))
16
- return {
17
- title: match[2].strip,
18
- icon: match[1],
19
- icon_source: "phosphor"
20
- }
21
- end
22
-
23
- icon, icon_source = auto_detect_icon(language)
24
- { title: title, icon: icon, icon_source: icon_source }
25
- end
26
-
27
- def auto_detect_icon(language)
28
- return [nil, nil] if language.nil?
29
-
30
- if LanguageMapping.terminal_language?(language)
31
- %w[terminal-window phosphor]
32
- elsif (ext = LanguageMapping.extension_for(language))
33
- [ext, "file-extension"]
34
- else
35
- %w[file phosphor]
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "code_line_parser"
4
-
5
- module Docyard
6
- module Components
7
- module CodeBlockLineWrapper
8
- module_function
9
-
10
- def wrap_code_block(html, block_data)
11
- html.gsub(%r{<pre[^>]*><code[^>]*>(.*?)</code></pre>}m) do
12
- pre_match = Regexp.last_match(0)
13
- code_content = Regexp.last_match(1)
14
- lines = CodeLineParser.new(code_content).parse
15
- wrapped_lines = wrap_lines_with_classes(lines, block_data)
16
- pre_match.sub(code_content, wrapped_lines.join)
17
- end
18
- end
19
-
20
- def wrap_lines_with_classes(lines, block_data)
21
- lines.each_with_index.map do |line, index|
22
- source_line = index + 1
23
- display_line = block_data[:start_line] + index
24
- classes = build_line_classes(source_line, display_line, block_data)
25
- %(<span class="#{classes}">#{line}</span>)
26
- end
27
- end
28
-
29
- DIFF_CLASSES = { addition: "docyard-code-line--diff-add", deletion: "docyard-code-line--diff-remove" }.freeze
30
-
31
- def build_line_classes(source_line, display_line, block_data)
32
- (["docyard-code-line"] + feature_classes(source_line, display_line, block_data)).join(" ")
33
- end
34
-
35
- def feature_classes(source_line, display_line, block_data)
36
- [
37
- ("docyard-code-line--highlighted" if block_data[:highlights].include?(display_line)),
38
- DIFF_CLASSES[block_data[:diff_lines][source_line]],
39
- ("docyard-code-line--focus" if block_data[:focus_lines][source_line]),
40
- ("docyard-code-line--error" if block_data[:error_lines][source_line]),
41
- ("docyard-code-line--warning" if block_data[:warning_lines][source_line])
42
- ].compact
43
- end
44
- end
45
- end
46
- end
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_processor"
4
-
5
- module Docyard
6
- module Components
7
- class CodeBlockOptionsPreprocessor < BaseProcessor
8
- self.priority = 5
9
-
10
- CODE_FENCE_REGEX = /^```(\w+)(?:\s*\[([^\]]+)\])?(:\S+)?(?:\s*\{([^}\n]+)\})?/
11
- TABS_BLOCK_REGEX = /^:::[ \t]*tabs[ \t]*\n.*?^:::[ \t]*$/m
12
-
13
- def preprocess(content)
14
- context[:code_block_options] ||= []
15
- @tabs_ranges = find_tabs_ranges(content)
16
-
17
- process_code_fences(content)
18
- end
19
-
20
- private
21
-
22
- def process_code_fences(content)
23
- result = +""
24
- last_end = 0
25
-
26
- content.scan(CODE_FENCE_REGEX) do
27
- match = Regexp.last_match
28
- result << content[last_end...match.begin(0)]
29
- result << process_fence_match(match)
30
- last_end = match.end(0)
31
- end
32
-
33
- result << content[last_end..]
34
- end
35
-
36
- def process_fence_match(match)
37
- store_code_block_options(match) unless inside_tabs?(match.begin(0))
38
- "```#{match[1]}"
39
- end
40
-
41
- def store_code_block_options(match)
42
- context[:code_block_options] << {
43
- lang: match[1],
44
- title: match[2],
45
- option: match[3],
46
- highlights: parse_highlights(match[4])
47
- }
48
- end
49
-
50
- def inside_tabs?(position)
51
- @tabs_ranges.any? { |range| range.cover?(position) }
52
- end
53
-
54
- def find_tabs_ranges(content)
55
- ranges = []
56
- content.scan(TABS_BLOCK_REGEX) do
57
- match = Regexp.last_match
58
- ranges << (match.begin(0)...match.end(0))
59
- end
60
- ranges
61
- end
62
-
63
- def parse_highlights(highlights_str)
64
- return [] if highlights_str.nil? || highlights_str.strip.empty?
65
-
66
- highlights_str.split(",").flat_map { |part| parse_highlight_part(part.strip) }.uniq.sort
67
- end
68
-
69
- def parse_highlight_part(part)
70
- return (part.split("-")[0].to_i..part.split("-")[1].to_i).to_a if part.include?("-")
71
-
72
- [part.to_i]
73
- end
74
- end
75
- end
76
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Docyard
4
- module Components
5
- module CodeBlockPatterns
6
- DIFF_MARKER_PATTERN = %r{
7
- (?:
8
- //\s*\[!code\s*([+-]{2})\] |
9
- \#\s*\[!code\s*([+-]{2})\] |
10
- /\*\s*\[!code\s*([+-]{2})\]\s*\*/ |
11
- --\s*\[!code\s*([+-]{2})\] |
12
- <!--\s*\[!code\s*([+-]{2})\]\s*--> |
13
- ;\s*\[!code\s*([+-]{2})\]
14
- )[^\S\n]*
15
- }x
16
-
17
- FOCUS_MARKER_PATTERN = %r{
18
- (?:
19
- //\s*\[!code\s+focus\] |
20
- \#\s*\[!code\s+focus\] |
21
- /\*\s*\[!code\s+focus\]\s*\*/ |
22
- --\s*\[!code\s+focus\] |
23
- <!--\s*\[!code\s+focus\]\s*--> |
24
- ;\s*\[!code\s+focus\]
25
- )[^\S\n]*
26
- }x
27
-
28
- ERROR_MARKER_PATTERN = %r{
29
- (?:
30
- //\s*\[!code\s+error\] |
31
- \#\s*\[!code\s+error\] |
32
- /\*\s*\[!code\s+error\]\s*\*/ |
33
- --\s*\[!code\s+error\] |
34
- <!--\s*\[!code\s+error\]\s*--> |
35
- ;\s*\[!code\s+error\]
36
- )[^\S\n]*
37
- }x
38
-
39
- WARNING_MARKER_PATTERN = %r{
40
- (?:
41
- //\s*\[!code\s+warning\] |
42
- \#\s*\[!code\s+warning\] |
43
- /\*\s*\[!code\s+warning\]\s*\*/ |
44
- --\s*\[!code\s+warning\] |
45
- <!--\s*\[!code\s+warning\]\s*--> |
46
- ;\s*\[!code\s+warning\]
47
- )[^\S\n]*
48
- }x
49
- end
50
- end
51
- end
@@ -1,176 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../icons"
4
- require_relative "../language_mapping"
5
- require_relative "../renderer"
6
- require_relative "base_processor"
7
- require_relative "code_block_icon_detector"
8
- require_relative "code_block_line_wrapper"
9
- require_relative "tabs_range_finder"
10
-
11
- module Docyard
12
- module Components
13
- class CodeBlockProcessor < BaseProcessor
14
- include CodeBlockLineWrapper
15
-
16
- self.priority = 20
17
-
18
- def postprocess(html)
19
- return html unless html.include?('<div class="highlight">')
20
-
21
- initialize_postprocess_state(html)
22
- process_all_highlight_blocks(html)
23
- end
24
-
25
- private
26
-
27
- def initialize_postprocess_state(html)
28
- @block_index = 0
29
- @options = context[:code_block_options] || []
30
- @diff_lines = context[:code_block_diff_lines] || []
31
- @focus_lines = context[:code_block_focus_lines] || []
32
- @error_lines = context[:code_block_error_lines] || []
33
- @warning_lines = context[:code_block_warning_lines] || []
34
- @global_line_numbers = context.dig(:config, "markdown", "lineNumbers") || false
35
- @tabs_ranges = TabsRangeFinder.find_ranges(html)
36
- end
37
-
38
- def process_all_highlight_blocks(html)
39
- result = +""
40
- last_end = 0
41
-
42
- html.scan(%r{<div class="highlight">(.*?)</div>}m) do
43
- match = Regexp.last_match
44
- result << html[last_end...match.begin(0)]
45
- result << process_highlight_match(match)
46
- last_end = match.end(0)
47
- end
48
-
49
- result << html[last_end..]
50
- end
51
-
52
- def process_highlight_match(match)
53
- if inside_tabs?(match.begin(0))
54
- match[0]
55
- else
56
- processed = process_code_block(match[0], match[1])
57
- @block_index += 1
58
- processed
59
- end
60
- end
61
-
62
- def process_code_block(original_html, inner_html)
63
- block_data = extract_block_data(inner_html)
64
- processed_html = process_html_for_highlighting(original_html, block_data)
65
-
66
- render_code_block_with_copy(block_data.merge(html: processed_html))
67
- end
68
-
69
- def extract_block_data(inner_html)
70
- opts = current_block_options
71
- code_text = extract_code_text(inner_html)
72
- start_line = extract_start_line(opts[:option])
73
- show_line_numbers = determine_line_numbers(opts[:option])
74
- title_data = CodeBlockIconDetector.detect(opts[:title], opts[:lang])
75
-
76
- build_block_data(code_text, opts, show_line_numbers, start_line, title_data)
77
- end
78
-
79
- def current_block_options
80
- block_opts = @options[@block_index] || {}
81
- {
82
- option: block_opts[:option],
83
- title: block_opts[:title],
84
- lang: block_opts[:lang],
85
- highlights: block_opts[:highlights] || []
86
- }
87
- end
88
-
89
- def build_block_data(code_text, opts, show_line_numbers, start_line, title_data)
90
- {
91
- text: code_text,
92
- highlights: opts[:highlights],
93
- diff_lines: @diff_lines[@block_index] || {},
94
- focus_lines: @focus_lines[@block_index] || {},
95
- error_lines: @error_lines[@block_index] || {},
96
- warning_lines: @warning_lines[@block_index] || {},
97
- show_line_numbers: show_line_numbers,
98
- line_numbers: show_line_numbers ? generate_line_numbers(code_text, start_line) : [],
99
- start_line: start_line,
100
- title: title_data[:title],
101
- icon: title_data[:icon],
102
- icon_source: title_data[:icon_source]
103
- }
104
- end
105
-
106
- def process_html_for_highlighting(original_html, block_data)
107
- needs_wrapping = block_data[:highlights].any? || block_data[:diff_lines].any? ||
108
- block_data[:focus_lines].any? || block_data[:error_lines].any? ||
109
- block_data[:warning_lines].any?
110
- return original_html unless needs_wrapping
111
-
112
- wrap_code_block(original_html, block_data)
113
- end
114
-
115
- def determine_line_numbers(block_option)
116
- return false if block_option == ":no-line-numbers"
117
- return true if block_option&.start_with?(":line-numbers")
118
-
119
- @global_line_numbers
120
- end
121
-
122
- def extract_start_line(block_option)
123
- return 1 unless block_option&.include?("=")
124
-
125
- block_option.split("=").last.to_i
126
- end
127
-
128
- def generate_line_numbers(code_text, start_line)
129
- line_count = code_text.lines.count
130
- line_count = 1 if line_count.zero?
131
- (start_line...(start_line + line_count)).to_a
132
- end
133
-
134
- def inside_tabs?(position)
135
- @tabs_ranges.any? { |range| range.cover?(position) }
136
- end
137
-
138
- def extract_code_text(html)
139
- text = html.gsub(/<[^>]+>/, "")
140
- text = CGI.unescapeHTML(text)
141
- text.strip
142
- end
143
-
144
- def render_code_block_with_copy(block_data)
145
- Renderer.new.render_partial("_code_block", template_locals(block_data))
146
- end
147
-
148
- def template_locals(block_data)
149
- base_locals(block_data).merge(line_feature_locals(block_data)).merge(title_locals(block_data))
150
- end
151
-
152
- def base_locals(block_data)
153
- { code_block_html: block_data[:html], code_text: escape_html_attribute(block_data[:text]),
154
- copy_icon: Icons.render("copy", "regular") || "", show_line_numbers: block_data[:show_line_numbers],
155
- line_numbers: block_data[:line_numbers], start_line: block_data[:start_line] }
156
- end
157
-
158
- def line_feature_locals(block_data)
159
- { highlights: block_data[:highlights], diff_lines: block_data[:diff_lines],
160
- focus_lines: block_data[:focus_lines], error_lines: block_data[:error_lines],
161
- warning_lines: block_data[:warning_lines] }
162
- end
163
-
164
- def title_locals(block_data)
165
- { title: block_data[:title], icon: block_data[:icon], icon_source: block_data[:icon_source] }
166
- end
167
-
168
- def escape_html_attribute(text)
169
- text.gsub('"', "&quot;")
170
- .gsub("'", "&#39;")
171
- .gsub("<", "&lt;")
172
- .gsub(">", "&gt;")
173
- end
174
- end
175
- end
176
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../language_mapping"
4
-
5
- module Docyard
6
- module Components
7
- class CodeDetector
8
- def self.detect(content)
9
- new(content).detect
10
- end
11
-
12
- def initialize(content)
13
- @content = content
14
- end
15
-
16
- def detect
17
- return nil unless code_only?
18
-
19
- language = extract_language
20
- return nil unless language
21
-
22
- icon_for_language(language)
23
- end
24
-
25
- private
26
-
27
- attr_reader :content
28
-
29
- def code_only?
30
- stripped = content.strip
31
- return false unless stripped.start_with?("```") && stripped.end_with?("```")
32
-
33
- parts = stripped.split("```")
34
- parts.length == 2 && parts[0].empty?
35
- end
36
-
37
- def extract_language
38
- parts = content.strip.split("```")
39
- return nil unless parts[1]
40
-
41
- lines = parts[1].split("\n", 2)
42
- lang_line = lines[0].strip
43
- return nil if lang_line.empty? || lang_line.include?(" ")
44
-
45
- lang_line.downcase
46
- end
47
-
48
- def icon_for_language(language)
49
- if LanguageMapping.terminal_language?(language)
50
- { icon: "terminal-window", source: "phosphor" }
51
- elsif (extension = LanguageMapping.extension_for(language))
52
- { icon: extension, source: "file-extension" }
53
- else
54
- { icon: "file", source: "phosphor" }
55
- end
56
- end
57
- end
58
- end
59
- end