docyard 0.8.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -1
- data/lib/docyard/components/aliases.rb +12 -0
- data/lib/docyard/components/processors/abbreviation_processor.rb +72 -0
- data/lib/docyard/components/processors/accordion_processor.rb +81 -0
- data/lib/docyard/components/processors/badge_processor.rb +72 -0
- data/lib/docyard/components/processors/callout_processor.rb +8 -2
- data/lib/docyard/components/processors/cards_processor.rb +100 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +23 -2
- data/lib/docyard/components/processors/code_block_processor.rb +6 -0
- data/lib/docyard/components/processors/code_group_processor.rb +198 -0
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +6 -1
- data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +151 -0
- data/lib/docyard/components/processors/image_caption_processor.rb +96 -0
- data/lib/docyard/components/processors/include_processor.rb +86 -0
- data/lib/docyard/components/processors/steps_processor.rb +89 -0
- data/lib/docyard/components/processors/tabs_processor.rb +9 -1
- data/lib/docyard/components/processors/tooltip_processor.rb +57 -0
- data/lib/docyard/components/processors/video_embed_processor.rb +196 -0
- data/lib/docyard/components/support/code_group/html_builder.rb +122 -0
- data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
- data/lib/docyard/config/branding_resolver.rb +30 -35
- data/lib/docyard/config/logo_detector.rb +39 -0
- data/lib/docyard/config.rb +6 -1
- data/lib/docyard/navigation/sidebar/file_resolver.rb +16 -4
- data/lib/docyard/navigation/sidebar/item.rb +6 -1
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +4 -2
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +8 -4
- data/lib/docyard/navigation/sidebar/renderer.rb +6 -2
- data/lib/docyard/navigation/sidebar/tree_builder.rb +2 -1
- data/lib/docyard/rendering/icons/phosphor.rb +3 -0
- data/lib/docyard/rendering/markdown.rb +24 -1
- data/lib/docyard/rendering/renderer.rb +2 -1
- data/lib/docyard/server/asset_handler.rb +1 -0
- data/lib/docyard/templates/assets/css/components/abbreviation.css +86 -0
- data/lib/docyard/templates/assets/css/components/accordion.css +138 -0
- data/lib/docyard/templates/assets/css/components/badges.css +47 -0
- data/lib/docyard/templates/assets/css/components/banner.css +202 -0
- data/lib/docyard/templates/assets/css/components/cards.css +100 -0
- data/lib/docyard/templates/assets/css/components/code-block.css +10 -0
- data/lib/docyard/templates/assets/css/components/code-group.css +281 -0
- data/lib/docyard/templates/assets/css/components/figure.css +22 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +124 -0
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +21 -13
- data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +7 -0
- data/lib/docyard/templates/assets/css/components/prev-next.css +9 -18
- data/lib/docyard/templates/assets/css/components/steps.css +122 -0
- data/lib/docyard/templates/assets/css/components/tabs.css +1 -1
- data/lib/docyard/templates/assets/css/components/tooltip.css +113 -0
- data/lib/docyard/templates/assets/css/components/video.css +41 -0
- data/lib/docyard/templates/assets/css/markdown.css +5 -3
- data/lib/docyard/templates/assets/js/components/abbreviation.js +85 -0
- data/lib/docyard/templates/assets/js/components/banner.js +81 -0
- data/lib/docyard/templates/assets/js/components/code-group.js +283 -0
- data/lib/docyard/templates/assets/js/components/file-tree.js +39 -0
- data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
- data/lib/docyard/templates/assets/js/components/tooltip.js +118 -0
- data/lib/docyard/templates/layouts/default.html.erb +1 -0
- data/lib/docyard/templates/layouts/splash.html.erb +1 -0
- data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
- data/lib/docyard/templates/partials/_banner.html.erb +27 -0
- data/lib/docyard/templates/partials/_card.html.erb +23 -0
- data/lib/docyard/templates/partials/_nav_group.html.erb +6 -0
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +3 -0
- data/lib/docyard/templates/partials/_step.html.erb +14 -0
- data/lib/docyard/version.rb +1 -1
- metadata +38 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 51b2adfdb7f77c4366592cc3eea5a160fc42f85f17e44593e9bf192c3b470b48
|
|
4
|
+
data.tar.gz: 439d82b762c7bc6f485ab627365522b7e202833fc841c6b8655fb28e1e9b3e55
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a66355efb4615ae4552aa8656e63078989cdec734f41670dc172936060954a065fefb1a2733e51494747ef0ab9a1dbee399b3c8f37daf2718e92ba03c315df9f
|
|
7
|
+
data.tar.gz: be3cea82f91b60cc7e7c35be142b6c2ccd9be4323cbf1c5190987b6ba6894d4c8da38f55c141abda43211a5ac2b70d38283e1b340d62071ee5c22c87c9c35a51
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.0] - 2026-01-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Accordions** - Collapsible content sections with `:::details{title="..."}` syntax (#62)
|
|
14
|
+
- **Steps** - Numbered step-by-step instructions with `:::steps` syntax and vertical connector lines (#63)
|
|
15
|
+
- **Cards** - Grid of linked content blocks with `:::cards` and `::card{title="" icon="" href=""}` syntax (#64)
|
|
16
|
+
- **Badges** - Inline status indicators with `:badge[text]{type="success|warning|danger"}` syntax (#65)
|
|
17
|
+
- **Sidebar Badges** - Navigation labels via frontmatter `sidebar.badge` and `sidebar.badge_type` (#66)
|
|
18
|
+
- **Announcement Banner** - Dismissible top banner with optional action button via config (#56)
|
|
19
|
+
- **Markdown Inclusion** - Include content from other files with `<!--@include: ./file.md-->` syntax (#57)
|
|
20
|
+
- **Custom Anchor IDs** - Override auto-generated heading IDs with `## Heading {#custom-id}` syntax (#58)
|
|
21
|
+
- **Image Captions** - Figure elements with captions using `{caption="..."}` syntax (#59)
|
|
22
|
+
- **Video Embeds** - YouTube and Vimeo embedding with `::youtube[ID]` and `::vimeo[ID]` syntax (#60)
|
|
23
|
+
- **File Tree** - Display directory structures with icons using `filetree` code blocks (#67)
|
|
24
|
+
- **Tooltips** - Inline hover definitions with `:tooltip[term]{description="..."}` syntax (#68)
|
|
25
|
+
- **Abbreviations** - Auto-expanding terms with `*[TERM]: Definition` syntax (#68)
|
|
26
|
+
- **Code Groups** - Tabbed code blocks with `:::code-group` syntax, syncs selection across page (#70)
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- **Copy Button Overlap** - Repositioned copy button to prevent overlapping code content in non-titled blocks (#71)
|
|
30
|
+
- **Code Fence Protection** - Preprocessors now skip content inside fenced code blocks, allowing documentation to show raw syntax examples (#72)
|
|
31
|
+
|
|
10
32
|
## [0.8.0] - 2026-01-13
|
|
11
33
|
|
|
12
34
|
### Added
|
|
@@ -140,7 +162,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
140
162
|
- Initial gem structure
|
|
141
163
|
- Project scaffolding
|
|
142
164
|
|
|
143
|
-
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.
|
|
165
|
+
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.9.0...HEAD
|
|
166
|
+
[0.9.0]: https://github.com/sanifhimani/docyard/compare/v0.8.0...v0.9.0
|
|
144
167
|
[0.8.0]: https://github.com/sanifhimani/docyard/compare/v0.7.0...v0.8.0
|
|
145
168
|
[0.7.0]: https://github.com/sanifhimani/docyard/compare/v0.6.0...v0.7.0
|
|
146
169
|
[0.6.0]: https://github.com/sanifhimani/docyard/compare/v0.5.0...v0.6.0
|
|
@@ -2,17 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module Docyard
|
|
4
4
|
module Components
|
|
5
|
+
AbbreviationProcessor = Processors::AbbreviationProcessor
|
|
6
|
+
AccordionProcessor = Processors::AccordionProcessor
|
|
7
|
+
BadgeProcessor = Processors::BadgeProcessor
|
|
8
|
+
StepsProcessor = Processors::StepsProcessor
|
|
9
|
+
CardsProcessor = Processors::CardsProcessor
|
|
5
10
|
CalloutProcessor = Processors::CalloutProcessor
|
|
6
11
|
CodeBlockProcessor = Processors::CodeBlockProcessor
|
|
12
|
+
CodeGroupProcessor = Processors::CodeGroupProcessor
|
|
7
13
|
CodeBlockDiffPreprocessor = Processors::CodeBlockDiffPreprocessor
|
|
8
14
|
CodeBlockFocusPreprocessor = Processors::CodeBlockFocusPreprocessor
|
|
9
15
|
CodeBlockOptionsPreprocessor = Processors::CodeBlockOptionsPreprocessor
|
|
10
16
|
CodeSnippetImportPreprocessor = Processors::CodeSnippetImportPreprocessor
|
|
17
|
+
CustomAnchorProcessor = Processors::CustomAnchorProcessor
|
|
18
|
+
ImageCaptionProcessor = Processors::ImageCaptionProcessor
|
|
19
|
+
IncludeProcessor = Processors::IncludeProcessor
|
|
20
|
+
VideoEmbedProcessor = Processors::VideoEmbedProcessor
|
|
21
|
+
FileTreeProcessor = Processors::FileTreeProcessor
|
|
11
22
|
HeadingAnchorProcessor = Processors::HeadingAnchorProcessor
|
|
12
23
|
IconProcessor = Processors::IconProcessor
|
|
13
24
|
TableOfContentsProcessor = Processors::TableOfContentsProcessor
|
|
14
25
|
TableWrapperProcessor = Processors::TableWrapperProcessor
|
|
15
26
|
TabsProcessor = Processors::TabsProcessor
|
|
27
|
+
TooltipProcessor = Processors::TooltipProcessor
|
|
16
28
|
|
|
17
29
|
CodeDetector = Support::CodeDetector
|
|
18
30
|
IconDetector = Support::Tabs::IconDetector
|
|
@@ -0,0 +1,72 @@
|
|
|
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 AbbreviationProcessor < BaseProcessor
|
|
10
|
+
include Support::MarkdownCodeBlockHelper
|
|
11
|
+
|
|
12
|
+
DEFINITION_PATTERN = /^\*\[([^\]]+)\]:\s*(.+)$/
|
|
13
|
+
self.priority = 5
|
|
14
|
+
|
|
15
|
+
def preprocess(content)
|
|
16
|
+
abbreviations = extract_abbreviations_outside_code_blocks(content)
|
|
17
|
+
return content if abbreviations.empty?
|
|
18
|
+
|
|
19
|
+
process_outside_code_blocks(content) do |segment|
|
|
20
|
+
segment = remove_definitions(segment)
|
|
21
|
+
apply_abbreviations(segment, abbreviations)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def extract_abbreviations_outside_code_blocks(content)
|
|
28
|
+
abbreviations = {}
|
|
29
|
+
process_outside_code_blocks(content) do |segment|
|
|
30
|
+
segment.scan(DEFINITION_PATTERN) do |term, definition|
|
|
31
|
+
abbreviations[term] = definition.strip
|
|
32
|
+
end
|
|
33
|
+
segment
|
|
34
|
+
end
|
|
35
|
+
abbreviations
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def remove_definitions(content)
|
|
39
|
+
content.gsub(/^[ \t]*\*\[([^\]]+)\]:\s*.+$\n?/, "")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def apply_abbreviations(content, abbreviations)
|
|
43
|
+
abbreviations.each do |term, definition|
|
|
44
|
+
pattern = build_term_pattern(term)
|
|
45
|
+
content = content.gsub(pattern) do |match|
|
|
46
|
+
build_abbr_tag(match, definition)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
content
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_term_pattern(term)
|
|
53
|
+
escaped = Regexp.escape(term)
|
|
54
|
+
/(?<![<\w])#{escaped}(?![>\w])/
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def build_abbr_tag(term, definition)
|
|
58
|
+
escaped_definition = escape_html(definition)
|
|
59
|
+
%(<abbr class="docyard-abbr" data-definition="#{escaped_definition}">#{term}</abbr>)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def escape_html(text)
|
|
63
|
+
text.to_s
|
|
64
|
+
.gsub("&", "&")
|
|
65
|
+
.gsub("<", "<")
|
|
66
|
+
.gsub(">", ">")
|
|
67
|
+
.gsub('"', """)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../rendering/icons"
|
|
4
|
+
require_relative "../../rendering/renderer"
|
|
5
|
+
require_relative "../base_processor"
|
|
6
|
+
require_relative "../support/markdown_code_block_helper"
|
|
7
|
+
require "kramdown"
|
|
8
|
+
require "kramdown-parser-gfm"
|
|
9
|
+
|
|
10
|
+
module Docyard
|
|
11
|
+
module Components
|
|
12
|
+
module Processors
|
|
13
|
+
class AccordionProcessor < BaseProcessor
|
|
14
|
+
include Support::MarkdownCodeBlockHelper
|
|
15
|
+
|
|
16
|
+
self.priority = 10
|
|
17
|
+
|
|
18
|
+
DETAILS_PATTERN = /^:::details(?:\{([^}]*)\})?\s*\n(.*?)^:::\s*$/m
|
|
19
|
+
|
|
20
|
+
def preprocess(markdown)
|
|
21
|
+
@code_block_ranges = find_code_block_ranges(markdown)
|
|
22
|
+
|
|
23
|
+
markdown.gsub(DETAILS_PATTERN) do
|
|
24
|
+
match = Regexp.last_match
|
|
25
|
+
next match[0] if inside_code_block?(match.begin(0), @code_block_ranges)
|
|
26
|
+
|
|
27
|
+
attributes = parse_attributes(match[1])
|
|
28
|
+
content_markdown = match[2]
|
|
29
|
+
|
|
30
|
+
title = attributes["title"] || "Details"
|
|
31
|
+
open = attributes.key?("open")
|
|
32
|
+
content_html = render_markdown_content(content_markdown.strip)
|
|
33
|
+
|
|
34
|
+
wrap_in_nomarkdown(render_accordion_html(title, content_html, open))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def parse_attributes(attr_string)
|
|
41
|
+
return {} if attr_string.nil? || attr_string.empty?
|
|
42
|
+
|
|
43
|
+
attrs = {}
|
|
44
|
+
attr_string.scan(/(\w+)(?:="([^"]*)")?/) do |key, value|
|
|
45
|
+
attrs[key] = value || true
|
|
46
|
+
end
|
|
47
|
+
attrs
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def render_markdown_content(content_markdown)
|
|
51
|
+
return "" if content_markdown.empty?
|
|
52
|
+
|
|
53
|
+
Kramdown::Document.new(
|
|
54
|
+
content_markdown,
|
|
55
|
+
input: "GFM",
|
|
56
|
+
hard_wrap: false,
|
|
57
|
+
syntax_highlighter: "rouge"
|
|
58
|
+
).to_html
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def wrap_in_nomarkdown(html)
|
|
62
|
+
"{::nomarkdown}\n#{html}\n{:/nomarkdown}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def render_accordion_html(title, content_html, open)
|
|
66
|
+
icon_svg = Icons.render("caret-right") || ""
|
|
67
|
+
renderer = Renderer.new
|
|
68
|
+
|
|
69
|
+
renderer.render_partial(
|
|
70
|
+
"_accordion", {
|
|
71
|
+
title: title,
|
|
72
|
+
content_html: content_html,
|
|
73
|
+
icon_svg: icon_svg,
|
|
74
|
+
open: open
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_processor"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Components
|
|
7
|
+
module Processors
|
|
8
|
+
class BadgeProcessor < BaseProcessor
|
|
9
|
+
self.priority = 15
|
|
10
|
+
|
|
11
|
+
BADGE_PATTERN = /:badge\[([^\]]*)\](?:\{([^}]*)\})?/
|
|
12
|
+
|
|
13
|
+
VALID_TYPES = %w[default success warning danger].freeze
|
|
14
|
+
|
|
15
|
+
def postprocess(html)
|
|
16
|
+
segments = split_preserving_code_blocks(html)
|
|
17
|
+
|
|
18
|
+
segments.map do |segment|
|
|
19
|
+
segment[:type] == :code ? segment[:content] : process_segment(segment[:content])
|
|
20
|
+
end.join
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def split_preserving_code_blocks(html)
|
|
26
|
+
segments = []
|
|
27
|
+
current_pos = 0
|
|
28
|
+
|
|
29
|
+
html.scan(%r{<(code|pre)[^>]*>.*?</\1>}m) do
|
|
30
|
+
match_start = Regexp.last_match.begin(0)
|
|
31
|
+
match_end = Regexp.last_match.end(0)
|
|
32
|
+
|
|
33
|
+
segments << { type: :text, content: html[current_pos...match_start] } if match_start > current_pos
|
|
34
|
+
segments << { type: :code, content: html[match_start...match_end] }
|
|
35
|
+
|
|
36
|
+
current_pos = match_end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
segments << { type: :text, content: html[current_pos..] } if current_pos < html.length
|
|
40
|
+
|
|
41
|
+
segments.empty? ? [{ type: :text, content: html }] : segments
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def process_segment(content)
|
|
45
|
+
content.gsub(BADGE_PATTERN) do
|
|
46
|
+
text = Regexp.last_match(1)
|
|
47
|
+
attrs = Regexp.last_match(2)
|
|
48
|
+
|
|
49
|
+
render_badge(text, parse_attributes(attrs))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parse_attributes(attrs_string)
|
|
54
|
+
return {} if attrs_string.nil? || attrs_string.empty?
|
|
55
|
+
|
|
56
|
+
attrs = {}
|
|
57
|
+
attrs_string.scan(/(\w+)=["'\u201C\u201D]([^"'\u201C\u201D]*)["'\u201C\u201D]/) do |key, value|
|
|
58
|
+
attrs[key] = value
|
|
59
|
+
end
|
|
60
|
+
attrs
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def render_badge(text, attrs)
|
|
64
|
+
type = attrs["type"] || "default"
|
|
65
|
+
type = "default" unless VALID_TYPES.include?(type)
|
|
66
|
+
|
|
67
|
+
%(<span class="docyard-badge docyard-badge--#{type}">#{text}</span>)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "../../rendering/icons"
|
|
4
4
|
require_relative "../../rendering/renderer"
|
|
5
5
|
require_relative "../base_processor"
|
|
6
|
+
require_relative "../support/markdown_code_block_helper"
|
|
6
7
|
require "kramdown"
|
|
7
8
|
require "kramdown-parser-gfm"
|
|
8
9
|
|
|
@@ -10,6 +11,8 @@ module Docyard
|
|
|
10
11
|
module Components
|
|
11
12
|
module Processors
|
|
12
13
|
class CalloutProcessor < BaseProcessor
|
|
14
|
+
include Support::MarkdownCodeBlockHelper
|
|
15
|
+
|
|
13
16
|
self.priority = 10
|
|
14
17
|
|
|
15
18
|
CALLOUT_TYPES = {
|
|
@@ -29,6 +32,7 @@ module Docyard
|
|
|
29
32
|
}.freeze
|
|
30
33
|
|
|
31
34
|
def preprocess(markdown)
|
|
35
|
+
@code_block_ranges = find_code_block_ranges(markdown)
|
|
32
36
|
process_container_syntax(markdown)
|
|
33
37
|
end
|
|
34
38
|
|
|
@@ -40,8 +44,10 @@ module Docyard
|
|
|
40
44
|
|
|
41
45
|
def process_container_syntax(markdown)
|
|
42
46
|
markdown.gsub(/^:::[ \t]*(\w+)(?:[ \t]+([^\n]+?))?[ \t]*\n(.*?)^:::[ \t]*$/m) do
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
match = Regexp.last_match
|
|
48
|
+
next match[0] if inside_code_block?(match.begin(0), @code_block_ranges)
|
|
49
|
+
|
|
50
|
+
process_callout_match(match[0], match[1], match[2], match[3])
|
|
45
51
|
end
|
|
46
52
|
end
|
|
47
53
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../rendering/icons"
|
|
4
|
+
require_relative "../../rendering/renderer"
|
|
5
|
+
require_relative "../base_processor"
|
|
6
|
+
require_relative "../support/markdown_code_block_helper"
|
|
7
|
+
require "kramdown"
|
|
8
|
+
require "kramdown-parser-gfm"
|
|
9
|
+
|
|
10
|
+
module Docyard
|
|
11
|
+
module Components
|
|
12
|
+
module Processors
|
|
13
|
+
class CardsProcessor < BaseProcessor
|
|
14
|
+
include Support::MarkdownCodeBlockHelper
|
|
15
|
+
|
|
16
|
+
self.priority = 10
|
|
17
|
+
|
|
18
|
+
CARDS_PATTERN = /^:::cards\s*\n(.*?)^:::\s*$/m
|
|
19
|
+
CARD_PATTERN = /^::card\{([^}]*)\}\s*\n(.*?)^::\s*$/m
|
|
20
|
+
|
|
21
|
+
def preprocess(markdown)
|
|
22
|
+
@code_block_ranges = find_code_block_ranges(markdown)
|
|
23
|
+
|
|
24
|
+
markdown.gsub(CARDS_PATTERN) do
|
|
25
|
+
match = Regexp.last_match
|
|
26
|
+
next match[0] if inside_code_block?(match.begin(0), @code_block_ranges)
|
|
27
|
+
|
|
28
|
+
content = match[1]
|
|
29
|
+
cards = parse_cards(content)
|
|
30
|
+
|
|
31
|
+
wrap_in_nomarkdown(render_cards_html(cards))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def parse_cards(content)
|
|
38
|
+
cards = []
|
|
39
|
+
|
|
40
|
+
content.scan(CARD_PATTERN) do |attrs_string, card_content|
|
|
41
|
+
attrs = parse_attributes(attrs_string)
|
|
42
|
+
cards << {
|
|
43
|
+
title: attrs["title"] || "Card",
|
|
44
|
+
icon: attrs["icon"],
|
|
45
|
+
href: attrs["href"],
|
|
46
|
+
content: card_content.strip
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
cards
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parse_attributes(attr_string)
|
|
54
|
+
return {} if attr_string.nil? || attr_string.empty?
|
|
55
|
+
|
|
56
|
+
attrs = {}
|
|
57
|
+
attr_string.scan(/(\w+)="([^"]*)"/) do |key, value|
|
|
58
|
+
attrs[key] = value
|
|
59
|
+
end
|
|
60
|
+
attrs
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def render_cards_html(cards)
|
|
64
|
+
renderer = Renderer.new
|
|
65
|
+
|
|
66
|
+
cards_html = cards.map do |card|
|
|
67
|
+
icon_svg = card[:icon] ? Icons.render(card[:icon]) : nil
|
|
68
|
+
content_html = render_markdown_content(card[:content])
|
|
69
|
+
|
|
70
|
+
renderer.render_partial(
|
|
71
|
+
"_card", {
|
|
72
|
+
title: card[:title],
|
|
73
|
+
icon_svg: icon_svg,
|
|
74
|
+
href: card[:href],
|
|
75
|
+
content_html: content_html
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
end.join("\n")
|
|
79
|
+
|
|
80
|
+
"<div class=\"docyard-cards\">\n#{cards_html}\n</div>"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def render_markdown_content(content_markdown)
|
|
84
|
+
return "" if content_markdown.empty?
|
|
85
|
+
|
|
86
|
+
Kramdown::Document.new(
|
|
87
|
+
content_markdown,
|
|
88
|
+
input: "GFM",
|
|
89
|
+
hard_wrap: false,
|
|
90
|
+
syntax_highlighter: "rouge"
|
|
91
|
+
).to_html
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def wrap_in_nomarkdown(html)
|
|
95
|
+
"{::nomarkdown}\n#{html}\n{:/nomarkdown}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -10,10 +10,12 @@ module Docyard
|
|
|
10
10
|
|
|
11
11
|
CODE_FENCE_REGEX = /^```(\w+)(?:\s*\[([^\]]+)\])?(:\S+)?(?:\s*\{([^}\n]+)\})?/
|
|
12
12
|
TABS_BLOCK_REGEX = /^:::[ \t]*tabs[ \t]*\n.*?^:::[ \t]*$/m
|
|
13
|
+
CODE_GROUP_BLOCK_REGEX = /^:::[ \t]*code-group[ \t]*\n.*?^:::[ \t]*$/m
|
|
13
14
|
|
|
14
15
|
def preprocess(content)
|
|
15
16
|
context[:code_block_options] ||= []
|
|
16
17
|
@tabs_ranges = find_tabs_ranges(content)
|
|
18
|
+
@code_group_ranges = find_code_group_ranges(content)
|
|
17
19
|
|
|
18
20
|
process_code_fences(content)
|
|
19
21
|
end
|
|
@@ -35,10 +37,21 @@ module Docyard
|
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def process_fence_match(match)
|
|
38
|
-
|
|
40
|
+
position = match.begin(0)
|
|
41
|
+
return match[0] if inside_special_block?(position)
|
|
42
|
+
|
|
43
|
+
store_code_block_options(match)
|
|
39
44
|
"```#{match[1]}"
|
|
40
45
|
end
|
|
41
46
|
|
|
47
|
+
def inside_special_block?(position)
|
|
48
|
+
inside_tabs?(position) || inside_code_group?(position)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def inside_code_group?(position)
|
|
52
|
+
@code_group_ranges.any? { |range| range.cover?(position) }
|
|
53
|
+
end
|
|
54
|
+
|
|
42
55
|
def store_code_block_options(match)
|
|
43
56
|
context[:code_block_options] << {
|
|
44
57
|
lang: match[1],
|
|
@@ -53,8 +66,16 @@ module Docyard
|
|
|
53
66
|
end
|
|
54
67
|
|
|
55
68
|
def find_tabs_ranges(content)
|
|
69
|
+
find_block_ranges(content, TABS_BLOCK_REGEX)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def find_code_group_ranges(content)
|
|
73
|
+
find_block_ranges(content, CODE_GROUP_BLOCK_REGEX)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def find_block_ranges(content, regex)
|
|
56
77
|
ranges = []
|
|
57
|
-
content.scan(
|
|
78
|
+
content.scan(regex) do
|
|
58
79
|
match = Regexp.last_match
|
|
59
80
|
ranges << (match.begin(0)...match.end(0))
|
|
60
81
|
end
|
|
@@ -67,10 +67,16 @@ module Docyard
|
|
|
67
67
|
def process_code_block(original_html, inner_html)
|
|
68
68
|
block_data = extract_block_data(inner_html)
|
|
69
69
|
processed_html = process_html_for_highlighting(original_html, block_data)
|
|
70
|
+
processed_html = inject_scroll_spacer(processed_html) unless block_data[:title]
|
|
70
71
|
|
|
71
72
|
render_code_block_with_copy(block_data.merge(html: processed_html))
|
|
72
73
|
end
|
|
73
74
|
|
|
75
|
+
def inject_scroll_spacer(html)
|
|
76
|
+
spacer = '<span class="docyard-code-block__scroll-spacer" aria-hidden="true"></span>'
|
|
77
|
+
html.sub("\n", "#{spacer}\n")
|
|
78
|
+
end
|
|
79
|
+
|
|
74
80
|
def extract_block_data(inner_html)
|
|
75
81
|
opts = current_block_options
|
|
76
82
|
code_text = extract_code_text(inner_html)
|