docyard 0.7.0 → 0.9.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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -1
  3. data/CHANGELOG.md +43 -1
  4. data/lib/docyard/build/asset_bundler.rb +22 -7
  5. data/lib/docyard/build/file_copier.rb +49 -27
  6. data/lib/docyard/build/sitemap_generator.rb +6 -6
  7. data/lib/docyard/build/static_generator.rb +85 -12
  8. data/lib/docyard/builder.rb +6 -6
  9. data/lib/docyard/components/aliases.rb +12 -0
  10. data/lib/docyard/components/processors/abbreviation_processor.rb +72 -0
  11. data/lib/docyard/components/processors/accordion_processor.rb +81 -0
  12. data/lib/docyard/components/processors/badge_processor.rb +72 -0
  13. data/lib/docyard/components/processors/callout_processor.rb +8 -2
  14. data/lib/docyard/components/processors/cards_processor.rb +100 -0
  15. data/lib/docyard/components/processors/code_block_options_preprocessor.rb +23 -2
  16. data/lib/docyard/components/processors/code_block_processor.rb +6 -0
  17. data/lib/docyard/components/processors/code_group_processor.rb +198 -0
  18. data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +6 -1
  19. data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
  20. data/lib/docyard/components/processors/file_tree_processor.rb +151 -0
  21. data/lib/docyard/components/processors/image_caption_processor.rb +96 -0
  22. data/lib/docyard/components/processors/include_processor.rb +86 -0
  23. data/lib/docyard/components/processors/steps_processor.rb +89 -0
  24. data/lib/docyard/components/processors/tabs_processor.rb +9 -1
  25. data/lib/docyard/components/processors/tooltip_processor.rb +57 -0
  26. data/lib/docyard/components/processors/video_embed_processor.rb +196 -0
  27. data/lib/docyard/components/support/code_group/html_builder.rb +122 -0
  28. data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
  29. data/lib/docyard/config/branding_resolver.rb +121 -17
  30. data/lib/docyard/config/constants.rb +6 -4
  31. data/lib/docyard/config/logo_detector.rb +39 -0
  32. data/lib/docyard/config/validator.rb +122 -99
  33. data/lib/docyard/config.rb +40 -42
  34. data/lib/docyard/initializer.rb +15 -76
  35. data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
  36. data/lib/docyard/navigation/prev_next_builder.rb +4 -1
  37. data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
  38. data/lib/docyard/navigation/sidebar/config_parser.rb +136 -108
  39. data/lib/docyard/navigation/sidebar/file_resolver.rb +90 -0
  40. data/lib/docyard/navigation/sidebar/file_system_scanner.rb +2 -1
  41. data/lib/docyard/navigation/sidebar/item.rb +50 -7
  42. data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
  43. data/lib/docyard/navigation/sidebar/metadata_extractor.rb +71 -0
  44. data/lib/docyard/navigation/sidebar/metadata_reader.rb +51 -0
  45. data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
  46. data/lib/docyard/navigation/sidebar/renderer.rb +60 -38
  47. data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
  48. data/lib/docyard/navigation/sidebar/tree_builder.rb +100 -26
  49. data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
  50. data/lib/docyard/navigation/sidebar_builder.rb +105 -36
  51. data/lib/docyard/rendering/icon_helpers.rb +13 -0
  52. data/lib/docyard/rendering/icons/phosphor.rb +26 -1
  53. data/lib/docyard/rendering/markdown.rb +29 -1
  54. data/lib/docyard/rendering/renderer.rb +75 -34
  55. data/lib/docyard/rendering/template_resolver.rb +172 -0
  56. data/lib/docyard/routing/fallback_resolver.rb +92 -0
  57. data/lib/docyard/search/build_indexer.rb +1 -1
  58. data/lib/docyard/search/dev_indexer.rb +51 -6
  59. data/lib/docyard/search/pagefind_support.rb +2 -0
  60. data/lib/docyard/server/asset_handler.rb +25 -19
  61. data/lib/docyard/server/pagefind_handler.rb +63 -0
  62. data/lib/docyard/server/preview_server.rb +1 -1
  63. data/lib/docyard/server/rack_application.rb +81 -64
  64. data/lib/docyard/templates/assets/css/code.css +18 -51
  65. data/lib/docyard/templates/assets/css/components/abbreviation.css +86 -0
  66. data/lib/docyard/templates/assets/css/components/accordion.css +138 -0
  67. data/lib/docyard/templates/assets/css/components/badges.css +47 -0
  68. data/lib/docyard/templates/assets/css/components/banner.css +202 -0
  69. data/lib/docyard/templates/assets/css/components/breadcrumbs.css +143 -0
  70. data/lib/docyard/templates/assets/css/components/callout.css +67 -67
  71. data/lib/docyard/templates/assets/css/components/cards.css +100 -0
  72. data/lib/docyard/templates/assets/css/components/code-block.css +190 -282
  73. data/lib/docyard/templates/assets/css/components/code-group.css +281 -0
  74. data/lib/docyard/templates/assets/css/components/figure.css +22 -0
  75. data/lib/docyard/templates/assets/css/components/file-tree.css +124 -0
  76. data/lib/docyard/templates/assets/css/components/heading-anchor.css +36 -15
  77. data/lib/docyard/templates/assets/css/components/icon.css +0 -1
  78. data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
  79. data/lib/docyard/templates/assets/css/components/logo.css +0 -2
  80. data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
  81. data/lib/docyard/templates/assets/css/components/navigation.css +193 -167
  82. data/lib/docyard/templates/assets/css/components/prev-next.css +68 -48
  83. data/lib/docyard/templates/assets/css/components/search.css +186 -174
  84. data/lib/docyard/templates/assets/css/components/steps.css +122 -0
  85. data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
  86. data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
  87. data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
  88. data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
  89. data/lib/docyard/templates/assets/css/components/tooltip.css +113 -0
  90. data/lib/docyard/templates/assets/css/components/video.css +41 -0
  91. data/lib/docyard/templates/assets/css/landing.css +815 -0
  92. data/lib/docyard/templates/assets/css/layout.css +489 -87
  93. data/lib/docyard/templates/assets/css/main.css +1 -3
  94. data/lib/docyard/templates/assets/css/markdown.css +113 -93
  95. data/lib/docyard/templates/assets/css/reset.css +0 -3
  96. data/lib/docyard/templates/assets/css/typography.css +43 -41
  97. data/lib/docyard/templates/assets/css/variables.css +268 -208
  98. data/lib/docyard/templates/assets/favicon.svg +7 -8
  99. data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
  100. data/lib/docyard/templates/assets/js/components/abbreviation.js +85 -0
  101. data/lib/docyard/templates/assets/js/components/banner.js +81 -0
  102. data/lib/docyard/templates/assets/js/components/code-block.js +24 -42
  103. data/lib/docyard/templates/assets/js/components/code-group.js +283 -0
  104. data/lib/docyard/templates/assets/js/components/file-tree.js +39 -0
  105. data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
  106. data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
  107. data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
  108. data/lib/docyard/templates/assets/js/components/search.js +0 -75
  109. data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
  110. data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
  111. data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
  112. data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
  113. data/lib/docyard/templates/assets/js/components/tooltip.js +118 -0
  114. data/lib/docyard/templates/assets/js/theme.js +0 -3
  115. data/lib/docyard/templates/assets/logo-dark.svg +8 -2
  116. data/lib/docyard/templates/assets/logo.svg +7 -4
  117. data/lib/docyard/templates/config/docyard.yml.erb +37 -34
  118. data/lib/docyard/templates/errors/404.html.erb +1 -1
  119. data/lib/docyard/templates/errors/500.html.erb +1 -1
  120. data/lib/docyard/templates/layouts/default.html.erb +19 -67
  121. data/lib/docyard/templates/layouts/splash.html.erb +177 -0
  122. data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
  123. data/lib/docyard/templates/partials/_banner.html.erb +27 -0
  124. data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
  125. data/lib/docyard/templates/partials/_card.html.erb +23 -0
  126. data/lib/docyard/templates/partials/_code_block.html.erb +5 -3
  127. data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
  128. data/lib/docyard/templates/partials/_features.html.erb +15 -0
  129. data/lib/docyard/templates/partials/_footer.html.erb +42 -0
  130. data/lib/docyard/templates/partials/_head.html.erb +22 -0
  131. data/lib/docyard/templates/partials/_header.html.erb +49 -0
  132. data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
  133. data/lib/docyard/templates/partials/_hero.html.erb +27 -0
  134. data/lib/docyard/templates/partials/_nav_group.html.erb +31 -11
  135. data/lib/docyard/templates/partials/_nav_leaf.html.erb +4 -1
  136. data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
  137. data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
  138. data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
  139. data/lib/docyard/templates/partials/_prev_next.html.erb +8 -2
  140. data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
  141. data/lib/docyard/templates/partials/_search_modal.html.erb +2 -6
  142. data/lib/docyard/templates/partials/_search_trigger.html.erb +2 -6
  143. data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
  144. data/lib/docyard/templates/partials/_step.html.erb +14 -0
  145. data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
  146. data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
  147. data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
  148. data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
  149. data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
  150. data/lib/docyard/version.rb +1 -1
  151. metadata +70 -5
  152. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
  153. data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
  154. data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
  155. data/lib/docyard/templates/markdown/index.md.erb +0 -82
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_processor"
4
+ require_relative "../support/markdown_code_block_helper"
5
+ require_relative "../../rendering/icons"
6
+
7
+ module Docyard
8
+ module Components
9
+ module Processors
10
+ class FileTreeProcessor < BaseProcessor
11
+ include Support::MarkdownCodeBlockHelper
12
+
13
+ FILETREE_PATTERN = /```filetree\n(.*?)```/m
14
+
15
+ self.priority = 8
16
+
17
+ def preprocess(content)
18
+ @code_block_ranges = find_code_block_ranges(content, exclude_language: "filetree")
19
+
20
+ content.gsub(FILETREE_PATTERN) do
21
+ match = Regexp.last_match
22
+ next match[0] if inside_code_block?(match.begin(0), @code_block_ranges)
23
+
24
+ build_file_tree(match[1])
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def build_file_tree(content)
31
+ lines = content.lines.map(&:chomp).reject(&:empty?)
32
+
33
+ items = parse_tree_structure(lines)
34
+ html = render_tree(items)
35
+
36
+ "\n\n<div class=\"docyard-filetree\" markdown=\"0\">\n#{html}</div>\n\n"
37
+ end
38
+
39
+ def parse_tree_structure(lines)
40
+ root_items = []
41
+ stack = [{ indent: -1, children: root_items }]
42
+
43
+ lines.each { |line| process_line(line, stack) }
44
+
45
+ root_items
46
+ end
47
+
48
+ def process_line(line, stack)
49
+ indent = line[/\A */].length
50
+ name = line.strip
51
+ return if name.empty?
52
+
53
+ item = build_item(name, indent)
54
+ unwind_stack(stack, indent)
55
+ stack.last[:children] << item
56
+
57
+ add_folder_to_stack(item, indent, stack) if item[:type] == :folder
58
+ end
59
+
60
+ def build_item(name, indent)
61
+ item = parse_item(name)
62
+ item[:indent] = indent
63
+ item
64
+ end
65
+
66
+ def unwind_stack(stack, indent)
67
+ stack.pop while stack.length > 1 && stack.last[:indent] >= indent
68
+ end
69
+
70
+ def add_folder_to_stack(item, indent, stack)
71
+ item[:children] = []
72
+ stack.push({ indent: indent, children: item[:children] })
73
+ end
74
+
75
+ def parse_item(name)
76
+ highlighted = name.end_with?(" *")
77
+ name = name.chomp(" *") if highlighted
78
+
79
+ name, comment = extract_comment(name)
80
+ type = name.end_with?("/") ? :folder : :file
81
+ name = name.chomp("/") if type == :folder
82
+
83
+ { name: name, type: type, highlighted: highlighted, comment: comment }
84
+ end
85
+
86
+ def extract_comment(name)
87
+ return [name, nil] unless name.include?(" # ")
88
+
89
+ name.split(" # ", 2)
90
+ end
91
+
92
+ def render_tree(items, depth = 0)
93
+ return "" if items.empty?
94
+
95
+ html = "<ul class=\"docyard-filetree__list\">\n"
96
+ items.each { |item| html += render_item(item, depth) }
97
+ html += "</ul>\n"
98
+ html
99
+ end
100
+
101
+ def render_item(item, depth)
102
+ classes = item_classes(item)
103
+
104
+ html = "<li class=\"#{classes}\">\n"
105
+ html += render_entry(item)
106
+ html += render_tree(item[:children], depth + 1) if render_children?(item)
107
+ html += "</li>\n"
108
+ html
109
+ end
110
+
111
+ def item_classes(item)
112
+ classes = ["docyard-filetree__item", "docyard-filetree__item--#{item[:type]}"]
113
+ classes << "docyard-filetree__item--highlighted" if item[:highlighted]
114
+ classes.join(" ")
115
+ end
116
+
117
+ def render_entry(item)
118
+ html = "<span class=\"docyard-filetree__entry\">"
119
+ html += icon_for(item[:type])
120
+ html += "<span class=\"docyard-filetree__name\">#{escape_html(item[:name])}</span>"
121
+ html += render_comment(item[:comment])
122
+ html += "</span>"
123
+ html
124
+ end
125
+
126
+ def render_comment(comment)
127
+ return "" unless comment
128
+
129
+ "<span class=\"docyard-filetree__comment\">#{escape_html(comment)}</span>"
130
+ end
131
+
132
+ def render_children?(item)
133
+ item[:type] == :folder && item[:children] && !item[:children].empty?
134
+ end
135
+
136
+ def icon_for(type)
137
+ icon_name = type == :folder ? "folder-open" : "file-text"
138
+ Icons.render(icon_name)
139
+ end
140
+
141
+ def escape_html(text)
142
+ text.to_s
143
+ .gsub("&", "&amp;")
144
+ .gsub("<", "&lt;")
145
+ .gsub(">", "&gt;")
146
+ .gsub('"', "&quot;")
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_processor"
4
+ require_relative "../support/markdown_code_block_helper"
5
+
6
+ module Docyard
7
+ module Components
8
+ module Processors
9
+ class ImageCaptionProcessor < BaseProcessor
10
+ include Support::MarkdownCodeBlockHelper
11
+
12
+ IMAGE_ATTRS_PATTERN = /!\[([^\]]*)\]\(([^)]+)\)\{([^}]+)\}/
13
+
14
+ self.priority = 5
15
+
16
+ def preprocess(content)
17
+ process_outside_code_blocks(content) do |segment|
18
+ process_images_with_attrs(segment)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def process_images_with_attrs(content)
25
+ content.gsub(IMAGE_ATTRS_PATTERN) do
26
+ alt = Regexp.last_match(1)
27
+ src = Regexp.last_match(2)
28
+ attrs_string = Regexp.last_match(3)
29
+
30
+ attrs = parse_attributes(attrs_string)
31
+ build_image_html(alt, src, attrs)
32
+ end
33
+ end
34
+
35
+ def parse_attributes(attrs_string)
36
+ attrs = {}
37
+
38
+ attrs_string.scan(/(\w+)="([^"]*)"/) do |key, value|
39
+ attrs[key] = value
40
+ end
41
+
42
+ attrs[:nozoom] = true if attrs_string.include?("nozoom")
43
+
44
+ attrs
45
+ end
46
+
47
+ def build_image_html(alt, src, attrs)
48
+ if attrs["caption"]
49
+ build_figure(alt, src, attrs)
50
+ else
51
+ build_img(alt, src, attrs)
52
+ end
53
+ end
54
+
55
+ def build_figure(alt, src, attrs)
56
+ "\n\n" \
57
+ "<figure class=\"docyard-figure\" markdown=\"0\">\n" \
58
+ "#{build_img_tag(alt, src, attrs)}\n" \
59
+ "<figcaption>#{escape_html(attrs['caption'])}</figcaption>\n" \
60
+ "</figure>" \
61
+ "\n\n"
62
+ end
63
+
64
+ def build_img(alt, src, attrs)
65
+ "\n\n#{build_img_tag(alt, src, attrs)}\n\n"
66
+ end
67
+
68
+ def build_img_tag(alt, src, attrs)
69
+ parts = base_img_attrs(alt, src)
70
+ parts.concat(dimension_attrs(attrs))
71
+ parts << "data-no-zoom" if attrs[:nozoom]
72
+ "<img #{parts.join(' ')}>"
73
+ end
74
+
75
+ def base_img_attrs(alt, src)
76
+ ["src=\"#{escape_html(src)}\"", "alt=\"#{escape_html(alt)}\""]
77
+ end
78
+
79
+ def dimension_attrs(attrs)
80
+ result = []
81
+ result << "width=\"#{escape_html(attrs['width'])}\"" if attrs["width"]
82
+ result << "height=\"#{escape_html(attrs['height'])}\"" if attrs["height"]
83
+ result
84
+ end
85
+
86
+ def escape_html(text)
87
+ text.to_s
88
+ .gsub("&", "&amp;")
89
+ .gsub("<", "&lt;")
90
+ .gsub(">", "&gt;")
91
+ .gsub('"', "&quot;")
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_processor"
4
+ require_relative "../support/markdown_code_block_helper"
5
+
6
+ module Docyard
7
+ module Components
8
+ module Processors
9
+ class IncludeProcessor < BaseProcessor
10
+ include Support::MarkdownCodeBlockHelper
11
+
12
+ INCLUDE_PATTERN = /<!--\s*@include:\s*([^\s]+)\s*-->/
13
+
14
+ self.priority = 0
15
+
16
+ def preprocess(content)
17
+ @current_file = context[:current_file]
18
+ @docs_root = context[:docs_root] || "docs"
19
+ @included_files = Set.new
20
+
21
+ process_outside_code_blocks(content) do |segment|
22
+ process_includes(segment)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def process_includes(content)
29
+ content.gsub(INCLUDE_PATTERN) { |_| process_include(Regexp.last_match) }
30
+ end
31
+
32
+ def process_include(match)
33
+ filepath = match[1]
34
+ full_path = resolve_path(filepath)
35
+
36
+ error = validate_include(filepath, full_path)
37
+ return error if error
38
+
39
+ @included_files.add(full_path)
40
+ file_content = File.read(full_path)
41
+
42
+ process_includes(file_content.strip)
43
+ end
44
+
45
+ def validate_include(filepath, full_path)
46
+ return include_error(filepath, "File not found") unless full_path && File.exist?(full_path)
47
+ return include_error(filepath, "Circular include detected") if @included_files.include?(full_path)
48
+ return include_error(filepath, "Use code snippets for non-markdown files") unless markdown_file?(filepath)
49
+
50
+ nil
51
+ end
52
+
53
+ def resolve_path(filepath)
54
+ if filepath.start_with?("./", "../")
55
+ resolve_relative_path(filepath)
56
+ else
57
+ resolve_docs_path(filepath)
58
+ end
59
+ end
60
+
61
+ def resolve_relative_path(filepath)
62
+ return nil unless @current_file
63
+
64
+ base_dir = File.dirname(@current_file)
65
+ full_path = File.expand_path(filepath, base_dir)
66
+
67
+ full_path if File.exist?(full_path)
68
+ end
69
+
70
+ def resolve_docs_path(filepath)
71
+ full_path = File.join(@docs_root, filepath)
72
+ full_path if File.exist?(full_path)
73
+ end
74
+
75
+ def markdown_file?(filepath)
76
+ ext = File.extname(filepath).downcase
77
+ %w[.md .markdown .mdx].include?(ext)
78
+ end
79
+
80
+ def include_error(filepath, message)
81
+ "> [!WARNING]\n> Include error: #{filepath} - #{message}\n"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../rendering/renderer"
4
+ require_relative "../base_processor"
5
+ require_relative "../support/markdown_code_block_helper"
6
+ require "kramdown"
7
+ require "kramdown-parser-gfm"
8
+
9
+ module Docyard
10
+ module Components
11
+ module Processors
12
+ class StepsProcessor < BaseProcessor
13
+ include Support::MarkdownCodeBlockHelper
14
+
15
+ self.priority = 10
16
+
17
+ STEPS_PATTERN = /^:::steps\s*\n(.*?)^:::\s*$/m
18
+ STEP_HEADING_PATTERN = /^###\s+(.+)$/
19
+
20
+ def preprocess(markdown)
21
+ @code_block_ranges = find_code_block_ranges(markdown)
22
+
23
+ markdown.gsub(STEPS_PATTERN) do
24
+ match = Regexp.last_match
25
+ next match[0] if inside_code_block?(match.begin(0), @code_block_ranges)
26
+
27
+ content = match[1]
28
+ steps = parse_steps(content)
29
+
30
+ wrap_in_nomarkdown(render_steps_html(steps))
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def parse_steps(content)
37
+ steps = []
38
+ current_step = nil
39
+
40
+ content.lines.each do |line|
41
+ if line.match(STEP_HEADING_PATTERN)
42
+ steps << current_step if current_step
43
+ current_step = { title: Regexp.last_match(1).strip, content: "" }
44
+ elsif current_step
45
+ current_step[:content] += line
46
+ end
47
+ end
48
+
49
+ steps << current_step if current_step
50
+ steps
51
+ end
52
+
53
+ def render_steps_html(steps)
54
+ renderer = Renderer.new
55
+
56
+ steps_html = steps.map.with_index(1) do |step, index|
57
+ content_html = render_markdown_content(step[:content].strip)
58
+
59
+ renderer.render_partial(
60
+ "_step", {
61
+ number: index,
62
+ title: step[:title],
63
+ content_html: content_html,
64
+ is_last: index == steps.length
65
+ }
66
+ )
67
+ end.join("\n")
68
+
69
+ "<div class=\"docyard-steps\">\n#{steps_html}\n</div>"
70
+ end
71
+
72
+ def render_markdown_content(content_markdown)
73
+ return "" if content_markdown.empty?
74
+
75
+ Kramdown::Document.new(
76
+ content_markdown,
77
+ input: "GFM",
78
+ hard_wrap: false,
79
+ syntax_highlighter: "rouge"
80
+ ).to_html
81
+ end
82
+
83
+ def wrap_in_nomarkdown(html)
84
+ "{::nomarkdown}\n#{html}\n{:/nomarkdown}"
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../../rendering/renderer"
4
4
  require_relative "../base_processor"
5
+ require_relative "../support/markdown_code_block_helper"
5
6
  require_relative "../support/tabs/parser"
6
7
  require "securerandom"
7
8
 
@@ -9,6 +10,8 @@ module Docyard
9
10
  module Components
10
11
  module Processors
11
12
  class TabsProcessor < BaseProcessor
13
+ include Support::MarkdownCodeBlockHelper
14
+
12
15
  self.priority = 15
13
16
 
14
17
  TabsParser = Support::Tabs::Parser
@@ -16,8 +19,13 @@ module Docyard
16
19
  def preprocess(content)
17
20
  return content unless content.include?(":::tabs")
18
21
 
22
+ @code_block_ranges = find_code_block_ranges(content)
23
+
19
24
  content.gsub(/^:::[ \t]*tabs[ \t]*\n(.*?)^:::[ \t]*$/m) do
20
- process_tabs_block(Regexp.last_match(1))
25
+ match = Regexp.last_match
26
+ next match[0] if inside_code_block?(match.begin(0), @code_block_ranges)
27
+
28
+ process_tabs_block(match[1])
21
29
  end
22
30
  end
23
31
 
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_processor"
4
+ require_relative "../support/markdown_code_block_helper"
5
+
6
+ module Docyard
7
+ module Components
8
+ module Processors
9
+ class TooltipProcessor < BaseProcessor
10
+ include Support::MarkdownCodeBlockHelper
11
+
12
+ TOOLTIP_PATTERN = /:tooltip\[([^\]]+)\]\{([^}]+)\}/
13
+ self.priority = 6
14
+
15
+ def preprocess(content)
16
+ process_outside_code_blocks(content) do |segment|
17
+ segment.gsub(TOOLTIP_PATTERN) do |_match|
18
+ term = ::Regexp.last_match(1)
19
+ attributes = parse_attributes(::Regexp.last_match(2))
20
+ build_tooltip_tag(term, attributes)
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def parse_attributes(attr_string)
28
+ attributes = {}
29
+ attr_string.scan(/(\w+)="([^"]*)"/) do |key, value|
30
+ attributes[key.to_sym] = value
31
+ end
32
+ attributes
33
+ end
34
+
35
+ def build_tooltip_tag(term, attributes)
36
+ description = escape_html(attributes[:description] || "")
37
+ link = attributes[:link]
38
+ link_text = attributes[:link_text] || "Learn more"
39
+
40
+ data_attrs = %(data-description="#{description}")
41
+ data_attrs += %( data-link="#{escape_html(link)}") if link
42
+ data_attrs += %( data-link-text="#{escape_html(link_text)}") if link
43
+
44
+ %(<span class="docyard-tooltip" #{data_attrs}>#{term}</span>)
45
+ end
46
+
47
+ def escape_html(text)
48
+ text.to_s
49
+ .gsub("&", "&amp;")
50
+ .gsub("<", "&lt;")
51
+ .gsub(">", "&gt;")
52
+ .gsub('"', "&quot;")
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end