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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -1
- data/lib/docyard/build/static_generator.rb +2 -43
- data/lib/docyard/builder.rb +14 -4
- data/lib/docyard/cli.rb +6 -3
- data/lib/docyard/components/aliases.rb +29 -0
- data/lib/docyard/components/processors/callout_processor.rb +124 -0
- data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +106 -0
- data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +79 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +78 -0
- data/lib/docyard/components/processors/code_block_processor.rb +175 -0
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +127 -0
- data/lib/docyard/components/processors/heading_anchor_processor.rb +39 -0
- data/lib/docyard/components/processors/icon_processor.rb +53 -0
- data/lib/docyard/components/processors/table_of_contents_processor.rb +68 -0
- data/lib/docyard/components/processors/table_wrapper_processor.rb +22 -0
- data/lib/docyard/components/processors/tabs_processor.rb +48 -0
- data/lib/docyard/components/support/code_block/feature_extractor.rb +117 -0
- data/lib/docyard/components/support/code_block/icon_detector.rb +44 -0
- data/lib/docyard/components/support/code_block/line_parser.rb +84 -0
- data/lib/docyard/components/support/code_block/line_wrapper.rb +50 -0
- data/lib/docyard/components/support/code_block/patterns.rb +55 -0
- data/lib/docyard/components/support/code_detector.rb +61 -0
- data/lib/docyard/components/support/tabs/icon_detector.rb +62 -0
- data/lib/docyard/components/support/tabs/parser.rb +195 -0
- data/lib/docyard/components/support/tabs/range_finder.rb +46 -0
- data/lib/docyard/config/branding_resolver.rb +74 -0
- data/lib/docyard/{constants.rb → config/constants.rb} +1 -0
- data/lib/docyard/config.rb +10 -1
- data/lib/docyard/{prev_next_builder.rb → navigation/prev_next_builder.rb} +2 -2
- data/lib/docyard/{sidebar → navigation/sidebar}/renderer.rb +3 -14
- data/lib/docyard/{sidebar → navigation/sidebar}/tree_builder.rb +9 -2
- data/lib/docyard/{sidebar_builder.rb → navigation/sidebar_builder.rb} +3 -15
- data/lib/docyard/{icons → rendering/icons}/phosphor.rb +4 -1
- data/lib/docyard/{markdown.rb → rendering/markdown.rb} +14 -13
- data/lib/docyard/{renderer.rb → rendering/renderer.rb} +20 -17
- data/lib/docyard/search/build_indexer.rb +74 -0
- data/lib/docyard/search/dev_indexer.rb +110 -0
- data/lib/docyard/search/pagefind_support.rb +31 -0
- data/lib/docyard/{asset_handler.rb → server/asset_handler.rb} +1 -1
- data/lib/docyard/{server.rb → server/dev_server.rb} +32 -9
- data/lib/docyard/{preview_server.rb → server/preview_server.rb} +1 -1
- data/lib/docyard/{rack_application.rb → server/rack_application.rb} +52 -49
- data/lib/docyard/server/resolution_result.rb +29 -0
- data/lib/docyard/{router.rb → server/router.rb} +4 -4
- data/lib/docyard/templates/assets/css/components/search.css +549 -0
- data/lib/docyard/templates/assets/css/layout.css +15 -1
- data/lib/docyard/templates/assets/js/components/search.js +685 -0
- data/lib/docyard/templates/layouts/default.html.erb +14 -2
- data/lib/docyard/templates/partials/_code_block.html.erb +1 -1
- data/lib/docyard/templates/partials/_heading_anchor.html.erb +1 -1
- data/lib/docyard/templates/partials/_prev_next.html.erb +1 -1
- data/lib/docyard/templates/partials/_search_modal.html.erb +45 -0
- data/lib/docyard/templates/partials/_search_trigger.html.erb +22 -0
- data/lib/docyard/utils/html_helpers.rb +14 -0
- data/lib/docyard/utils/path_resolver.rb +2 -1
- data/lib/docyard/utils/url_helpers.rb +20 -0
- data/lib/docyard/version.rb +1 -1
- data/lib/docyard.rb +22 -15
- metadata +57 -46
- data/lib/docyard/components/callout_processor.rb +0 -121
- data/lib/docyard/components/code_block_diff_preprocessor.rb +0 -104
- data/lib/docyard/components/code_block_feature_extractor.rb +0 -113
- data/lib/docyard/components/code_block_focus_preprocessor.rb +0 -77
- data/lib/docyard/components/code_block_icon_detector.rb +0 -40
- data/lib/docyard/components/code_block_line_wrapper.rb +0 -46
- data/lib/docyard/components/code_block_options_preprocessor.rb +0 -76
- data/lib/docyard/components/code_block_patterns.rb +0 -51
- data/lib/docyard/components/code_block_processor.rb +0 -176
- data/lib/docyard/components/code_detector.rb +0 -59
- data/lib/docyard/components/code_line_parser.rb +0 -80
- data/lib/docyard/components/code_snippet_import_preprocessor.rb +0 -125
- data/lib/docyard/components/heading_anchor_processor.rb +0 -34
- data/lib/docyard/components/icon_detector.rb +0 -57
- data/lib/docyard/components/icon_processor.rb +0 -51
- data/lib/docyard/components/table_of_contents_processor.rb +0 -64
- data/lib/docyard/components/table_wrapper_processor.rb +0 -18
- data/lib/docyard/components/tabs_parser.rb +0 -191
- data/lib/docyard/components/tabs_processor.rb +0 -44
- data/lib/docyard/components/tabs_range_finder.rb +0 -42
- data/lib/docyard/routing/resolution_result.rb +0 -31
- /data/lib/docyard/{sidebar → navigation/sidebar}/config_parser.rb +0 -0
- /data/lib/docyard/{sidebar → navigation/sidebar}/file_system_scanner.rb +0 -0
- /data/lib/docyard/{sidebar → navigation/sidebar}/item.rb +0 -0
- /data/lib/docyard/{sidebar → navigation/sidebar}/title_extractor.rb +0 -0
- /data/lib/docyard/{icons → rendering/icons}/LICENSE.phosphor +0 -0
- /data/lib/docyard/{icons → rendering/icons}/file_types.rb +0 -0
- /data/lib/docyard/{icons.rb → rendering/icons.rb} +0 -0
- /data/lib/docyard/{language_mapping.rb → rendering/language_mapping.rb} +0 -0
- /data/lib/docyard/{file_watcher.rb → server/file_watcher.rb} +0 -0
- /data/lib/docyard/{errors.rb → utils/errors.rb} +0 -0
- /data/lib/docyard/{logging.rb → utils/logging.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e127990af44c6db555b00cfd2c7a89be408e6e4bfddf2bed9a753387f708ee87
|
|
4
|
+
data.tar.gz: ed54f92691fe29fc8dd83b8b797504162283bba12840c49c120a4341ac22a66c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ae09bfea3ce92e5b5b8cee3e7317d6d57b253e17314adcfbf9f468cc0635a54d3cbd55a90f28807514875359e4d93b4e1c68fee99b7ec65a9ab12d34a21c6c2
|
|
7
|
+
data.tar.gz: 3c2148376ab27c804a148666795420afe24fe23cb890fa76fa2602af5434d9e60d589fcbb460454210d2dfe1cc38f1f8584db41b1209db14b8b21ec3d7abeb68
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-01-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Full-text Search** - Pagefind-powered search with Cmd/Ctrl+K modal, keyboard navigation, and highlighting (#41)
|
|
14
|
+
- **Search Configuration** - Customize placeholder text, enable/disable search, and exclude paths via `docyard.yml` (#41)
|
|
15
|
+
- **Dev Server Search** - Opt-in search indexing during development with `--search` flag (#41)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Major codebase reorganization for improved maintainability (#42)
|
|
19
|
+
- Components reorganized into `processors/` and `support/` subdirectories (#42)
|
|
20
|
+
- Consolidated `server/`, `rendering/`, `navigation/`, `config/`, and `search/` modules (#42)
|
|
21
|
+
- Extracted shared utilities into `utils/` module (#42)
|
|
22
|
+
|
|
10
23
|
## [0.6.0] - 2025-12-25
|
|
11
24
|
|
|
12
25
|
### Added
|
|
@@ -109,7 +122,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
109
122
|
- Initial gem structure
|
|
110
123
|
- Project scaffolding
|
|
111
124
|
|
|
112
|
-
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.
|
|
125
|
+
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v0.7.0...HEAD
|
|
126
|
+
[0.7.0]: https://github.com/sanifhimani/docyard/compare/v0.6.0...v0.7.0
|
|
113
127
|
[0.6.0]: https://github.com/sanifhimani/docyard/compare/v0.5.0...v0.6.0
|
|
114
128
|
[0.5.0]: https://github.com/sanifhimani/docyard/compare/v0.4.0...v0.5.0
|
|
115
129
|
[0.4.0]: https://github.com/sanifhimani/docyard/compare/v0.3.0...v0.4.0
|
|
@@ -18,7 +18,7 @@ module Docyard
|
|
|
18
18
|
puts "\n[✓] Found #{markdown_files.size} markdown files"
|
|
19
19
|
|
|
20
20
|
progress = TTY::ProgressBar.new(
|
|
21
|
-
"Generating pages [:bar] :current/:total (:percent
|
|
21
|
+
"Generating pages [:bar] :current/:total (:percent)",
|
|
22
22
|
total: markdown_files.size,
|
|
23
23
|
width: 50
|
|
24
24
|
)
|
|
@@ -89,48 +89,7 @@ module Docyard
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def branding_options
|
|
92
|
-
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def default_branding
|
|
96
|
-
{
|
|
97
|
-
site_title: Constants::DEFAULT_SITE_TITLE,
|
|
98
|
-
site_description: "",
|
|
99
|
-
logo: Constants::DEFAULT_LOGO_PATH,
|
|
100
|
-
logo_dark: Constants::DEFAULT_LOGO_DARK_PATH,
|
|
101
|
-
favicon: nil,
|
|
102
|
-
display_logo: true,
|
|
103
|
-
display_title: true
|
|
104
|
-
}
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def config_branding_options
|
|
108
|
-
site = config.site
|
|
109
|
-
branding = config.branding
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
site_title: site.title || Constants::DEFAULT_SITE_TITLE,
|
|
113
|
-
site_description: site.description || "",
|
|
114
|
-
logo: resolve_logo(branding.logo, branding.logo_dark),
|
|
115
|
-
logo_dark: resolve_logo_dark(branding.logo, branding.logo_dark),
|
|
116
|
-
favicon: branding.favicon
|
|
117
|
-
}.merge(appearance_options(branding.appearance))
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def appearance_options(appearance)
|
|
121
|
-
appearance ||= {}
|
|
122
|
-
{
|
|
123
|
-
display_logo: appearance["logo"] != false,
|
|
124
|
-
display_title: appearance["title"] != false
|
|
125
|
-
}
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def resolve_logo(logo, logo_dark)
|
|
129
|
-
logo || logo_dark || Constants::DEFAULT_LOGO_PATH
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def resolve_logo_dark(logo, logo_dark)
|
|
133
|
-
logo_dark || logo || Constants::DEFAULT_LOGO_DARK_PATH
|
|
92
|
+
BrandingResolver.new(config).resolve
|
|
134
93
|
end
|
|
135
94
|
|
|
136
95
|
def log(message)
|
data/lib/docyard/builder.rb
CHANGED
|
@@ -22,8 +22,9 @@ module Docyard
|
|
|
22
22
|
bundles_created = bundle_assets
|
|
23
23
|
assets_copied = copy_static_files
|
|
24
24
|
generate_seo_files
|
|
25
|
+
pages_indexed = generate_search_index
|
|
25
26
|
|
|
26
|
-
display_summary(pages_built, bundles_created, assets_copied)
|
|
27
|
+
display_summary(pages_built, bundles_created, assets_copied, pages_indexed)
|
|
27
28
|
true
|
|
28
29
|
rescue StandardError => e
|
|
29
30
|
error "Build failed: #{e.message}"
|
|
@@ -68,7 +69,12 @@ module Docyard
|
|
|
68
69
|
sitemap_gen.generate
|
|
69
70
|
|
|
70
71
|
File.write(File.join(config.build.output_dir, "robots.txt"), robots_txt_content)
|
|
71
|
-
log "[
|
|
72
|
+
log "[+] Generated robots.txt"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def generate_search_index
|
|
76
|
+
indexer = Search::BuildIndexer.new(config, verbose: verbose)
|
|
77
|
+
indexer.index
|
|
72
78
|
end
|
|
73
79
|
|
|
74
80
|
def robots_txt_content
|
|
@@ -83,13 +89,17 @@ module Docyard
|
|
|
83
89
|
ROBOTS
|
|
84
90
|
end
|
|
85
91
|
|
|
86
|
-
def display_summary(pages, bundles, assets)
|
|
92
|
+
def display_summary(pages, bundles, assets, indexed = 0)
|
|
87
93
|
elapsed = Time.now - start_time
|
|
88
94
|
|
|
89
95
|
puts "\n#{'=' * 50}"
|
|
90
96
|
puts "Build complete in #{format('%.2f', elapsed)}s"
|
|
91
97
|
puts "Output: #{config.build.output_dir}/"
|
|
92
|
-
|
|
98
|
+
|
|
99
|
+
summary = "#{pages} pages, #{bundles} bundles, #{assets} static files"
|
|
100
|
+
summary += ", #{indexed} pages indexed" if indexed.positive?
|
|
101
|
+
puts summary
|
|
102
|
+
|
|
93
103
|
puts "=" * 50
|
|
94
104
|
end
|
|
95
105
|
|
data/lib/docyard/cli.rb
CHANGED
|
@@ -34,18 +34,21 @@ module Docyard
|
|
|
34
34
|
desc "preview", "Preview the built site locally"
|
|
35
35
|
method_option :port, type: :numeric, default: 4000, aliases: "-p", desc: "Port to run preview server on"
|
|
36
36
|
def preview
|
|
37
|
-
require_relative "preview_server"
|
|
37
|
+
require_relative "server/preview_server"
|
|
38
38
|
Docyard::PreviewServer.new(port: options[:port]).start
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
desc "serve", "Start the development server"
|
|
42
42
|
method_option :port, type: :numeric, default: 4200, aliases: "-p", desc: "Port to run the server on"
|
|
43
43
|
method_option :host, type: :string, default: "localhost", aliases: "-h", desc: "Host to bind the server to"
|
|
44
|
+
method_option :search, type: :boolean, default: false, aliases: "-s",
|
|
45
|
+
desc: "Enable search indexing (slower startup)"
|
|
44
46
|
def serve
|
|
45
|
-
require_relative "server"
|
|
47
|
+
require_relative "server/dev_server"
|
|
46
48
|
server = Docyard::Server.new(
|
|
47
49
|
port: options[:port],
|
|
48
|
-
host: options[:host]
|
|
50
|
+
host: options[:host],
|
|
51
|
+
search: options[:search]
|
|
49
52
|
)
|
|
50
53
|
server.start
|
|
51
54
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Components
|
|
5
|
+
CalloutProcessor = Processors::CalloutProcessor
|
|
6
|
+
CodeBlockProcessor = Processors::CodeBlockProcessor
|
|
7
|
+
CodeBlockDiffPreprocessor = Processors::CodeBlockDiffPreprocessor
|
|
8
|
+
CodeBlockFocusPreprocessor = Processors::CodeBlockFocusPreprocessor
|
|
9
|
+
CodeBlockOptionsPreprocessor = Processors::CodeBlockOptionsPreprocessor
|
|
10
|
+
CodeSnippetImportPreprocessor = Processors::CodeSnippetImportPreprocessor
|
|
11
|
+
HeadingAnchorProcessor = Processors::HeadingAnchorProcessor
|
|
12
|
+
IconProcessor = Processors::IconProcessor
|
|
13
|
+
TableOfContentsProcessor = Processors::TableOfContentsProcessor
|
|
14
|
+
TableWrapperProcessor = Processors::TableWrapperProcessor
|
|
15
|
+
TabsProcessor = Processors::TabsProcessor
|
|
16
|
+
|
|
17
|
+
CodeDetector = Support::CodeDetector
|
|
18
|
+
IconDetector = Support::Tabs::IconDetector
|
|
19
|
+
|
|
20
|
+
CodeBlockFeatureExtractor = Support::CodeBlock::FeatureExtractor
|
|
21
|
+
CodeBlockIconDetector = Support::CodeBlock::IconDetector
|
|
22
|
+
CodeBlockLineWrapper = Support::CodeBlock::LineWrapper
|
|
23
|
+
CodeBlockPatterns = Support::CodeBlock::Patterns
|
|
24
|
+
CodeLineParser = Support::CodeBlock::LineParser
|
|
25
|
+
|
|
26
|
+
TabsParser = Support::Tabs::Parser
|
|
27
|
+
TabsRangeFinder = Support::Tabs::RangeFinder
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../rendering/icons"
|
|
4
|
+
require_relative "../../rendering/renderer"
|
|
5
|
+
require_relative "../base_processor"
|
|
6
|
+
require "kramdown"
|
|
7
|
+
require "kramdown-parser-gfm"
|
|
8
|
+
|
|
9
|
+
module Docyard
|
|
10
|
+
module Components
|
|
11
|
+
module Processors
|
|
12
|
+
class CalloutProcessor < BaseProcessor
|
|
13
|
+
self.priority = 10
|
|
14
|
+
|
|
15
|
+
CALLOUT_TYPES = {
|
|
16
|
+
"note" => { title: "Note", icon: "info", color: "note" },
|
|
17
|
+
"tip" => { title: "Tip", icon: "lightbulb", color: "tip" },
|
|
18
|
+
"important" => { title: "Important", icon: "warning-circle", color: "important" },
|
|
19
|
+
"warning" => { title: "Warning", icon: "warning", color: "warning" },
|
|
20
|
+
"danger" => { title: "Danger", icon: "siren", color: "danger" }
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
GITHUB_ALERT_TYPES = {
|
|
24
|
+
"NOTE" => "note",
|
|
25
|
+
"TIP" => "tip",
|
|
26
|
+
"IMPORTANT" => "important",
|
|
27
|
+
"WARNING" => "warning",
|
|
28
|
+
"CAUTION" => "danger"
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
def preprocess(markdown)
|
|
32
|
+
process_container_syntax(markdown)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def postprocess(html)
|
|
36
|
+
process_github_alerts(html)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def process_container_syntax(markdown)
|
|
42
|
+
markdown.gsub(/^:::[ \t]*(\w+)(?:[ \t]+([^\n]+?))?[ \t]*\n(.*?)^:::[ \t]*$/m) do
|
|
43
|
+
process_callout_match(Regexp.last_match(0), Regexp.last_match(1), Regexp.last_match(2),
|
|
44
|
+
Regexp.last_match(3))
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def process_callout_match(original_match, type_raw, custom_title, content_markdown)
|
|
49
|
+
type = type_raw.downcase
|
|
50
|
+
return original_match unless CALLOUT_TYPES.key?(type)
|
|
51
|
+
|
|
52
|
+
config = CALLOUT_TYPES[type]
|
|
53
|
+
title = determine_title(custom_title, config[:title])
|
|
54
|
+
content_html = render_markdown_content(content_markdown.strip)
|
|
55
|
+
|
|
56
|
+
wrap_in_nomarkdown(render_callout_html(type, title, content_html, config[:icon]))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def determine_title(custom_title, default_title)
|
|
60
|
+
title = custom_title&.strip
|
|
61
|
+
title.nil? || title.empty? ? default_title : title
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render_markdown_content(content_markdown)
|
|
65
|
+
return "" if content_markdown.empty?
|
|
66
|
+
|
|
67
|
+
Kramdown::Document.new(
|
|
68
|
+
content_markdown,
|
|
69
|
+
input: "GFM",
|
|
70
|
+
hard_wrap: false,
|
|
71
|
+
syntax_highlighter: "rouge"
|
|
72
|
+
).to_html
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def wrap_in_nomarkdown(html)
|
|
76
|
+
"{::nomarkdown}\n#{html}\n{:/nomarkdown}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def process_github_alerts(html)
|
|
80
|
+
github_alert_regex = %r{
|
|
81
|
+
<blockquote>\s*
|
|
82
|
+
<p>\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*
|
|
83
|
+
(?:<br\s*/>)?\s*
|
|
84
|
+
(.*?)</p>
|
|
85
|
+
(.*?)
|
|
86
|
+
</blockquote>
|
|
87
|
+
}mx
|
|
88
|
+
|
|
89
|
+
html.gsub(github_alert_regex) do
|
|
90
|
+
process_github_alert_match(Regexp.last_match(1), Regexp.last_match(2), Regexp.last_match(3))
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def process_github_alert_match(alert_type, first_para, rest_content)
|
|
95
|
+
type = GITHUB_ALERT_TYPES[alert_type]
|
|
96
|
+
config = CALLOUT_TYPES[type]
|
|
97
|
+
content_html = combine_alert_content(first_para.strip, rest_content.strip)
|
|
98
|
+
|
|
99
|
+
render_callout_html(type, config[:title], content_html, config[:icon])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def combine_alert_content(first_para, rest_content)
|
|
103
|
+
return "<p>#{first_para}</p>" if rest_content.empty?
|
|
104
|
+
|
|
105
|
+
"<p>#{first_para}</p>#{rest_content}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def render_callout_html(type, title, content_html, icon_name)
|
|
109
|
+
icon_svg = Icons.render(icon_name, "duotone") || ""
|
|
110
|
+
renderer = Renderer.new
|
|
111
|
+
|
|
112
|
+
renderer.render_partial(
|
|
113
|
+
"_callout", {
|
|
114
|
+
type: type,
|
|
115
|
+
title: title,
|
|
116
|
+
content_html: content_html,
|
|
117
|
+
icon_svg: icon_svg
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_processor"
|
|
4
|
+
require_relative "../support/code_block/patterns"
|
|
5
|
+
|
|
6
|
+
module Docyard
|
|
7
|
+
module Components
|
|
8
|
+
module Processors
|
|
9
|
+
class CodeBlockDiffPreprocessor < BaseProcessor
|
|
10
|
+
include Support::CodeBlock::Patterns
|
|
11
|
+
|
|
12
|
+
self.priority = 6
|
|
13
|
+
|
|
14
|
+
CODE_BLOCK_REGEX = /^```(\w*).*?\n(.*?)^```/m
|
|
15
|
+
TABS_BLOCK_REGEX = /^:::[ \t]*tabs[ \t]*\n.*?^:::[ \t]*$/m
|
|
16
|
+
|
|
17
|
+
def preprocess(content)
|
|
18
|
+
context[:code_block_diff_lines] ||= []
|
|
19
|
+
context[:code_block_error_lines] ||= []
|
|
20
|
+
context[:code_block_warning_lines] ||= []
|
|
21
|
+
@block_index = 0
|
|
22
|
+
@tabs_ranges = find_tabs_ranges(content)
|
|
23
|
+
|
|
24
|
+
content.gsub(CODE_BLOCK_REGEX) { |_| process_code_block(Regexp.last_match) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def process_code_block(match)
|
|
30
|
+
return match[0] if inside_tabs?(match.begin(0))
|
|
31
|
+
|
|
32
|
+
result = extract_all_markers(match[2])
|
|
33
|
+
store_extracted_markers(result)
|
|
34
|
+
@block_index += 1
|
|
35
|
+
match[0].sub(match[2], result[:cleaned_content])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def store_extracted_markers(result)
|
|
39
|
+
context[:code_block_diff_lines][@block_index] = result[:diff_lines]
|
|
40
|
+
context[:code_block_error_lines][@block_index] = result[:error_lines]
|
|
41
|
+
context[:code_block_warning_lines][@block_index] = result[:warning_lines]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def extract_all_markers(code_content)
|
|
45
|
+
diff_info = extract_diff_lines(code_content)
|
|
46
|
+
error_info = extract_error_lines(diff_info[:cleaned_content])
|
|
47
|
+
warning_info = extract_warning_lines(error_info[:cleaned_content])
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
diff_lines: diff_info[:lines],
|
|
51
|
+
error_lines: error_info[:lines],
|
|
52
|
+
warning_lines: warning_info[:lines],
|
|
53
|
+
cleaned_content: warning_info[:cleaned_content]
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def extract_diff_lines(code_content)
|
|
58
|
+
extract_marker_lines(code_content, DIFF_MARKER_PATTERN) do |match|
|
|
59
|
+
diff_type = match.captures.compact.first
|
|
60
|
+
diff_type == "++" ? :addition : :deletion
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def extract_error_lines(code_content)
|
|
65
|
+
extract_marker_lines(code_content, ERROR_MARKER_PATTERN) { true }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def extract_warning_lines(code_content)
|
|
69
|
+
extract_marker_lines(code_content, WARNING_MARKER_PATTERN) { true }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def extract_marker_lines(code_content, pattern)
|
|
73
|
+
lines = code_content.lines
|
|
74
|
+
marker_lines = {}
|
|
75
|
+
cleaned_lines = []
|
|
76
|
+
|
|
77
|
+
lines.each_with_index do |line, index|
|
|
78
|
+
line_num = index + 1
|
|
79
|
+
|
|
80
|
+
if (match = line.match(pattern))
|
|
81
|
+
marker_lines[line_num] = yield(match)
|
|
82
|
+
cleaned_lines << line.gsub(pattern, "")
|
|
83
|
+
else
|
|
84
|
+
cleaned_lines << line
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
{ lines: marker_lines, cleaned_content: cleaned_lines.join }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def inside_tabs?(position)
|
|
92
|
+
@tabs_ranges.any? { |range| range.cover?(position) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def find_tabs_ranges(content)
|
|
96
|
+
ranges = []
|
|
97
|
+
content.scan(TABS_BLOCK_REGEX) do
|
|
98
|
+
match = Regexp.last_match
|
|
99
|
+
ranges << (match.begin(0)...match.end(0))
|
|
100
|
+
end
|
|
101
|
+
ranges
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_processor"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Components
|
|
7
|
+
module Processors
|
|
8
|
+
class CodeBlockFocusPreprocessor < BaseProcessor
|
|
9
|
+
self.priority = 7
|
|
10
|
+
|
|
11
|
+
FOCUS_MARKER_PATTERN = %r{
|
|
12
|
+
(?:
|
|
13
|
+
//\s*\[!code\s+focus\] |
|
|
14
|
+
\#\s*\[!code\s+focus\] |
|
|
15
|
+
/\*\s*\[!code\s+focus\]\s*\*/ |
|
|
16
|
+
--\s*\[!code\s+focus\] |
|
|
17
|
+
<!--\s*\[!code\s+focus\]\s*--> |
|
|
18
|
+
;\s*\[!code\s+focus\]
|
|
19
|
+
)[^\S\n]*
|
|
20
|
+
}x
|
|
21
|
+
|
|
22
|
+
CODE_BLOCK_REGEX = /^```(\w*).*?\n(.*?)^```/m
|
|
23
|
+
TABS_BLOCK_REGEX = /^:::[ \t]*tabs[ \t]*\n.*?^:::[ \t]*$/m
|
|
24
|
+
|
|
25
|
+
def preprocess(content)
|
|
26
|
+
context[:code_block_focus_lines] ||= []
|
|
27
|
+
@block_index = 0
|
|
28
|
+
@tabs_ranges = find_tabs_ranges(content)
|
|
29
|
+
|
|
30
|
+
content.gsub(CODE_BLOCK_REGEX) { |_| process_code_block(Regexp.last_match) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def process_code_block(match)
|
|
36
|
+
return match[0] if inside_tabs?(match.begin(0))
|
|
37
|
+
|
|
38
|
+
focus_info = extract_focus_lines(match[2])
|
|
39
|
+
context[:code_block_focus_lines][@block_index] = focus_info[:lines]
|
|
40
|
+
@block_index += 1
|
|
41
|
+
match[0].sub(match[2], focus_info[:cleaned_content])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def extract_focus_lines(code_content)
|
|
45
|
+
lines = code_content.lines
|
|
46
|
+
focus_lines = {}
|
|
47
|
+
cleaned_lines = []
|
|
48
|
+
|
|
49
|
+
lines.each_with_index do |line, index|
|
|
50
|
+
line_num = index + 1
|
|
51
|
+
|
|
52
|
+
if line.match?(FOCUS_MARKER_PATTERN)
|
|
53
|
+
focus_lines[line_num] = true
|
|
54
|
+
cleaned_line = line.gsub(FOCUS_MARKER_PATTERN, "")
|
|
55
|
+
cleaned_lines << cleaned_line
|
|
56
|
+
else
|
|
57
|
+
cleaned_lines << line
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
{ lines: focus_lines, cleaned_content: cleaned_lines.join }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def inside_tabs?(position)
|
|
65
|
+
@tabs_ranges.any? { |range| range.cover?(position) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def find_tabs_ranges(content)
|
|
69
|
+
ranges = []
|
|
70
|
+
content.scan(TABS_BLOCK_REGEX) do
|
|
71
|
+
match = Regexp.last_match
|
|
72
|
+
ranges << (match.begin(0)...match.end(0))
|
|
73
|
+
end
|
|
74
|
+
ranges
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_processor"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Components
|
|
7
|
+
module Processors
|
|
8
|
+
class CodeBlockOptionsPreprocessor < BaseProcessor
|
|
9
|
+
self.priority = 5
|
|
10
|
+
|
|
11
|
+
CODE_FENCE_REGEX = /^```(\w+)(?:\s*\[([^\]]+)\])?(:\S+)?(?:\s*\{([^}\n]+)\})?/
|
|
12
|
+
TABS_BLOCK_REGEX = /^:::[ \t]*tabs[ \t]*\n.*?^:::[ \t]*$/m
|
|
13
|
+
|
|
14
|
+
def preprocess(content)
|
|
15
|
+
context[:code_block_options] ||= []
|
|
16
|
+
@tabs_ranges = find_tabs_ranges(content)
|
|
17
|
+
|
|
18
|
+
process_code_fences(content)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def process_code_fences(content)
|
|
24
|
+
result = +""
|
|
25
|
+
last_end = 0
|
|
26
|
+
|
|
27
|
+
content.scan(CODE_FENCE_REGEX) do
|
|
28
|
+
match = Regexp.last_match
|
|
29
|
+
result << content[last_end...match.begin(0)]
|
|
30
|
+
result << process_fence_match(match)
|
|
31
|
+
last_end = match.end(0)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
result << content[last_end..]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def process_fence_match(match)
|
|
38
|
+
store_code_block_options(match) unless inside_tabs?(match.begin(0))
|
|
39
|
+
"```#{match[1]}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def store_code_block_options(match)
|
|
43
|
+
context[:code_block_options] << {
|
|
44
|
+
lang: match[1],
|
|
45
|
+
title: match[2],
|
|
46
|
+
option: match[3],
|
|
47
|
+
highlights: parse_highlights(match[4])
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def inside_tabs?(position)
|
|
52
|
+
@tabs_ranges.any? { |range| range.cover?(position) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def find_tabs_ranges(content)
|
|
56
|
+
ranges = []
|
|
57
|
+
content.scan(TABS_BLOCK_REGEX) do
|
|
58
|
+
match = Regexp.last_match
|
|
59
|
+
ranges << (match.begin(0)...match.end(0))
|
|
60
|
+
end
|
|
61
|
+
ranges
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def parse_highlights(highlights_str)
|
|
65
|
+
return [] if highlights_str.nil? || highlights_str.strip.empty?
|
|
66
|
+
|
|
67
|
+
highlights_str.split(",").flat_map { |part| parse_highlight_part(part.strip) }.uniq.sort
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def parse_highlight_part(part)
|
|
71
|
+
return (part.split("-")[0].to_i..part.split("-")[1].to_i).to_a if part.include?("-")
|
|
72
|
+
|
|
73
|
+
[part.to_i]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|