docyard 0.4.0 → 0.6.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/.rubocop.yml +1 -1
- data/CHANGELOG.md +29 -3
- data/README.md +37 -12
- data/lib/docyard/build/static_generator.rb +1 -1
- data/lib/docyard/components/base_processor.rb +6 -0
- data/lib/docyard/components/code_block_diff_preprocessor.rb +104 -0
- data/lib/docyard/components/code_block_feature_extractor.rb +113 -0
- data/lib/docyard/components/code_block_focus_preprocessor.rb +77 -0
- data/lib/docyard/components/code_block_icon_detector.rb +40 -0
- data/lib/docyard/components/code_block_line_wrapper.rb +46 -0
- data/lib/docyard/components/code_block_options_preprocessor.rb +76 -0
- data/lib/docyard/components/code_block_patterns.rb +51 -0
- data/lib/docyard/components/code_block_processor.rb +135 -14
- data/lib/docyard/components/code_line_parser.rb +80 -0
- data/lib/docyard/components/code_snippet_import_preprocessor.rb +125 -0
- data/lib/docyard/components/heading_anchor_processor.rb +34 -0
- data/lib/docyard/components/registry.rb +4 -4
- data/lib/docyard/components/table_of_contents_processor.rb +64 -0
- data/lib/docyard/components/tabs_parser.rb +135 -4
- data/lib/docyard/components/tabs_range_finder.rb +42 -0
- data/lib/docyard/config/validator.rb +8 -0
- data/lib/docyard/config.rb +18 -0
- data/lib/docyard/icons/file_types.rb +0 -13
- data/lib/docyard/icons/phosphor.rb +2 -1
- data/lib/docyard/markdown.rb +18 -4
- data/lib/docyard/prev_next_builder.rb +159 -0
- data/lib/docyard/rack_application.rb +25 -3
- data/lib/docyard/renderer.rb +20 -8
- data/lib/docyard/templates/assets/css/code.css +12 -4
- data/lib/docyard/templates/assets/css/components/code-block.css +427 -24
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +77 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +12 -9
- data/lib/docyard/templates/assets/css/components/prev-next.css +114 -0
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +269 -0
- data/lib/docyard/templates/assets/css/components/tabs.css +50 -44
- data/lib/docyard/templates/assets/css/layout.css +58 -1
- data/lib/docyard/templates/assets/css/variables.css +45 -0
- data/lib/docyard/templates/assets/js/components/heading-anchor.js +90 -0
- data/lib/docyard/templates/assets/js/components/navigation.js +6 -2
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +301 -0
- data/lib/docyard/templates/layouts/default.html.erb +9 -1
- data/lib/docyard/templates/partials/_code_block.html.erb +50 -2
- data/lib/docyard/templates/partials/_heading_anchor.html.erb +1 -0
- data/lib/docyard/templates/partials/_prev_next.html.erb +23 -0
- data/lib/docyard/templates/partials/_table_of_contents.html.erb +45 -0
- data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +8 -0
- data/lib/docyard/version.rb +1 -1
- metadata +23 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Components
|
|
5
|
+
module TabsRangeFinder
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def find_ranges(html)
|
|
9
|
+
ranges = []
|
|
10
|
+
start_pattern = '<div class="docyard-tabs"'
|
|
11
|
+
|
|
12
|
+
pos = 0
|
|
13
|
+
while (start_pos = html.index(start_pattern, pos))
|
|
14
|
+
end_pos = find_matching_close_div(html, start_pos)
|
|
15
|
+
ranges << (start_pos...end_pos) if end_pos
|
|
16
|
+
pos = end_pos || (start_pos + 1)
|
|
17
|
+
end
|
|
18
|
+
ranges
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def find_matching_close_div(html, start_pos)
|
|
22
|
+
depth = 0
|
|
23
|
+
pos = start_pos
|
|
24
|
+
|
|
25
|
+
while pos < html.length
|
|
26
|
+
if html[pos, 4] == "<div"
|
|
27
|
+
depth += 1
|
|
28
|
+
pos += 4
|
|
29
|
+
elsif html[pos, 6] == "</div>"
|
|
30
|
+
depth -= 1
|
|
31
|
+
return pos + 6 if depth.zero?
|
|
32
|
+
|
|
33
|
+
pos += 6
|
|
34
|
+
else
|
|
35
|
+
pos += 1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -12,6 +12,7 @@ module Docyard
|
|
|
12
12
|
validate_site_section
|
|
13
13
|
validate_branding_section
|
|
14
14
|
validate_build_section
|
|
15
|
+
validate_markdown_section
|
|
15
16
|
|
|
16
17
|
raise ConfigError, format_errors if @errors.any?
|
|
17
18
|
end
|
|
@@ -48,6 +49,13 @@ module Docyard
|
|
|
48
49
|
validate_boolean(build["clean"], "build.clean")
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
def validate_markdown_section
|
|
53
|
+
markdown = @config["markdown"]
|
|
54
|
+
return unless markdown
|
|
55
|
+
|
|
56
|
+
validate_boolean(markdown["lineNumbers"], "markdown.lineNumbers") if markdown.key?("lineNumbers")
|
|
57
|
+
end
|
|
58
|
+
|
|
51
59
|
def validate_string(value, field_name)
|
|
52
60
|
return if value.nil?
|
|
53
61
|
return if value.is_a?(String)
|
data/lib/docyard/config.rb
CHANGED
|
@@ -27,6 +27,16 @@ module Docyard
|
|
|
27
27
|
},
|
|
28
28
|
"sidebar" => {
|
|
29
29
|
"items" => []
|
|
30
|
+
},
|
|
31
|
+
"navigation" => {
|
|
32
|
+
"footer" => {
|
|
33
|
+
"enabled" => true,
|
|
34
|
+
"prev_text" => "Previous",
|
|
35
|
+
"next_text" => "Next"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"markdown" => {
|
|
39
|
+
"lineNumbers" => false
|
|
30
40
|
}
|
|
31
41
|
}.freeze
|
|
32
42
|
|
|
@@ -63,6 +73,14 @@ module Docyard
|
|
|
63
73
|
@sidebar ||= ConfigSection.new(data["sidebar"])
|
|
64
74
|
end
|
|
65
75
|
|
|
76
|
+
def navigation
|
|
77
|
+
@navigation ||= ConfigSection.new(data["navigation"])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def markdown
|
|
81
|
+
@markdown ||= ConfigSection.new(data["markdown"])
|
|
82
|
+
end
|
|
83
|
+
|
|
66
84
|
private
|
|
67
85
|
|
|
68
86
|
def load_config_data
|
|
@@ -8,7 +8,6 @@ module Docyard
|
|
|
8
8
|
# License: CC BY-SA 4.0 (see LICENSE.vscode-icons)
|
|
9
9
|
# Copyright (c) 2016 Roberto Huertas
|
|
10
10
|
module FileTypes
|
|
11
|
-
# SVG content for each file type icon
|
|
12
11
|
# rubocop:disable Layout/LineLength
|
|
13
12
|
ICONS = {
|
|
14
13
|
"css" => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-labelledby="css-logo-title css-logo-description"><title>CSS Logo</title><g style="display:inline"><path fill="#639" d="M1.995 1.994h23.52a4.48 4.48 0 0 1 4.48 4.48v19.04a4.48 4.48 0 0 1-4.48 4.48H6.475a4.48 4.48 0 0 1-4.48-4.48Z"/><path fill="#fff" d="M9.079 24.87v-4.704c0-1.876 1.204-2.884 3.024-2.884 1.792-.028 2.912 1.148 2.856 3.136h-2.072c.056-.756-.28-1.316-.84-1.288-.7 0-.896.476-.896 1.372v4.088c0 .868.28 1.288.896 1.316.644 0 .896-.644.84-1.372h2.072c.112 2.044-1.176 3.248-2.996 3.22-1.764 0-2.884-.98-2.884-2.884zm6.636-.336h1.932c.028.896.308 1.456.924 1.456.616 0 .84-.364.84-1.204 0-.7-.308-1.092-1.064-1.456l-.728-.336c-1.288-.616-1.82-1.372-1.82-2.884 0-1.68 1.064-2.856 2.8-2.856 1.736 0 2.66 1.204 2.688 3.164h-1.876c0-.812-.168-1.372-.784-1.372-.56 0-.84.28-.84.98s.252.98.924 1.26l.672.308c1.428.672 2.044 1.54 2.044 3.164 0 1.932-1.092 2.996-2.884 2.996-1.792 0-2.8-1.232-2.828-3.22zm6.328 0h1.96c0 .896.308 1.456.896 1.456.588 0 .84-.364.84-1.204 0-.7-.28-1.092-1.064-1.456l-.728-.336c-1.288-.616-1.792-1.372-1.792-2.884 0-1.68 1.036-2.856 2.8-2.856 1.764 0 2.632 1.204 2.688 3.164h-1.876c-.028-.812-.196-1.372-.812-1.372-.56 0-.812.28-.812.98s.224.98.896 1.26l.7.308c1.4.672 2.016 1.54 2.016 3.164 0 1.932-1.092 2.996-2.884 2.996-1.792 0-2.8-1.232-2.828-3.22z"/></g></svg>',
|
|
@@ -35,7 +34,6 @@ module Docyard
|
|
|
35
34
|
}.freeze
|
|
36
35
|
# rubocop:enable Layout/LineLength
|
|
37
36
|
|
|
38
|
-
# Map of file extensions/aliases to icon names
|
|
39
37
|
EXTENSIONS = {
|
|
40
38
|
"css" => "css",
|
|
41
39
|
"go" => "go",
|
|
@@ -62,10 +60,6 @@ module Docyard
|
|
|
62
60
|
"postgresql" => "pgsql"
|
|
63
61
|
}.freeze
|
|
64
62
|
|
|
65
|
-
# Get the SVG content for a file extension
|
|
66
|
-
#
|
|
67
|
-
# @param extension [String] The file extension (e.g., "js", "ts")
|
|
68
|
-
# @return [String, nil] The SVG content, or nil if not found
|
|
69
63
|
def self.svg(extension)
|
|
70
64
|
icon_name = EXTENSIONS[extension.to_s.downcase]
|
|
71
65
|
return nil unless icon_name
|
|
@@ -73,17 +67,10 @@ module Docyard
|
|
|
73
67
|
ICONS[icon_name]
|
|
74
68
|
end
|
|
75
69
|
|
|
76
|
-
# Check if an icon exists for a file extension
|
|
77
|
-
#
|
|
78
|
-
# @param extension [String] The file extension to check
|
|
79
|
-
# @return [Boolean] true if icon exists
|
|
80
70
|
def self.exists?(extension)
|
|
81
71
|
EXTENSIONS.key?(extension.to_s.downcase)
|
|
82
72
|
end
|
|
83
73
|
|
|
84
|
-
# Get all available file extensions
|
|
85
|
-
#
|
|
86
|
-
# @return [Array<String>] Array of supported extensions
|
|
87
74
|
def self.available
|
|
88
75
|
EXTENSIONS.keys.sort
|
|
89
76
|
end
|
|
@@ -35,7 +35,8 @@ module Docyard
|
|
|
35
35
|
"warning-octagon" => '<path d="M120,136V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0ZM232,91.55v72.9a15.86,15.86,0,0,1-4.69,11.31l-51.55,51.55A15.86,15.86,0,0,1,164.45,232H91.55a15.86,15.86,0,0,1-11.31-4.69L28.69,175.76A15.86,15.86,0,0,1,24,164.45V91.55a15.86,15.86,0,0,1,4.69-11.31L80.24,28.69A15.86,15.86,0,0,1,91.55,24h72.9a15.86,15.86,0,0,1,11.31,4.69l51.55,51.55A15.86,15.86,0,0,1,232,91.55Zm-16,0L164.45,40H91.55L40,91.55v72.9L91.55,216h72.9L216,164.45ZM128,160a12,12,0,1,0,12,12A12,12,0,0,0,128,160Z"/>',
|
|
36
36
|
"siren" => '<path d="M120,16V8a8,8,0,0,1,16,0v8a8,8,0,0,1-16,0Zm80,32a8,8,0,0,0,5.66-2.34l8-8a8,8,0,0,0-11.32-11.32l-8,8A8,8,0,0,0,200,48ZM50.34,45.66A8,8,0,0,0,61.66,34.34l-8-8A8,8,0,0,0,42.34,37.66Zm87,26.45a8,8,0,1,0-2.64,15.78C153.67,91.08,168,108.32,168,128a8,8,0,0,0,16,0C184,100.6,163.93,76.57,137.32,72.11ZM232,176v24a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V176a16,16,0,0,1,16-16V128a88,88,0,0,1,88.67-88c48.15.36,87.33,40.29,87.33,89v31A16,16,0,0,1,232,176ZM56,160H200V129c0-40-32.05-72.71-71.45-73H128a72,72,0,0,0-72,72Zm160,40V176H40v24H216Z"/>',
|
|
37
37
|
"file" => '<path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z"/>',
|
|
38
|
-
"terminal-window" => '<path d="M128,128a8,8,0,0,1-3,6.25l-40,32a8,8,0,1,1-10-12.5L107.19,128,75,102.25a8,8,0,1,1,10-12.5l40,32A8,8,0,0,1,128,128Zm48,24H136a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Zm56-96V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56ZM216,200V56H40V200H216Z"/>'
|
|
38
|
+
"terminal-window" => '<path d="M128,128a8,8,0,0,1-3,6.25l-40,32a8,8,0,1,1-10-12.5L107.19,128,75,102.25a8,8,0,1,1,10-12.5l40,32A8,8,0,0,1,128,128Zm48,24H136a8,8,0,0,0,0,16h40a8,8,0,0,0,0-16Zm56-96V200a16,16,0,0,1-16,16H40a16,16,0,0,1-16-16V56A16,16,0,0,1,40,40H216A16,16,0,0,1,232,56ZM216,200V56H40V200H216Z"/>',
|
|
39
|
+
"list-dashes" => '<path d="M88,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H96A8,8,0,0,1,88,64Zm128,56H96a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,64H96a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM56,56H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Zm0,64H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Zm0,64H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Z"/>'
|
|
39
40
|
},
|
|
40
41
|
"bold" => {
|
|
41
42
|
"heart" => '<path d="M178,36c-20.09,0-37.92,7.93-50,21.56C115.92,43.93,98.09,36,78,36a66.08,66.08,0,0,0-66,66c0,72.34,105.81,130.14,110.31,132.57a12,12,0,0,0,11.38,0C138.19,232.14,244,174.34,244,102A66.08,66.08,0,0,0,178,36Zm-5.49,142.36A328.69,328.69,0,0,1,128,210.16a328.69,328.69,0,0,1-44.51-31.8C61.82,159.77,36,131.42,36,102A42,42,0,0,1,78,60c17.8,0,32.7,9.4,38.89,24.54a12,12,0,0,0,22.22,0C145.3,69.4,160.2,60,178,60a42,42,0,0,1,42,42C220,131.42,194.18,159.77,172.51,178.36Z"/>'
|
data/lib/docyard/markdown.rb
CHANGED
|
@@ -9,16 +9,24 @@ require_relative "components/callout_processor"
|
|
|
9
9
|
require_relative "components/tabs_processor"
|
|
10
10
|
require_relative "components/icon_processor"
|
|
11
11
|
require_relative "components/code_block_processor"
|
|
12
|
+
require_relative "components/code_snippet_import_preprocessor"
|
|
13
|
+
require_relative "components/code_block_options_preprocessor"
|
|
14
|
+
require_relative "components/code_block_diff_preprocessor"
|
|
15
|
+
require_relative "components/code_block_focus_preprocessor"
|
|
12
16
|
require_relative "components/table_wrapper_processor"
|
|
17
|
+
require_relative "components/heading_anchor_processor"
|
|
18
|
+
require_relative "components/table_of_contents_processor"
|
|
13
19
|
|
|
14
20
|
module Docyard
|
|
15
21
|
class Markdown
|
|
16
22
|
FRONTMATTER_REGEX = /\A---\s*\n(.*?\n)---\s*\n/m
|
|
17
23
|
|
|
18
|
-
attr_reader :raw
|
|
24
|
+
attr_reader :raw, :config
|
|
19
25
|
|
|
20
|
-
def initialize(raw)
|
|
26
|
+
def initialize(raw, config: nil)
|
|
21
27
|
@raw = raw.freeze
|
|
28
|
+
@config = config
|
|
29
|
+
@context = {}
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
def frontmatter
|
|
@@ -53,6 +61,10 @@ module Docyard
|
|
|
53
61
|
frontmatter.dig("sidebar", "collapsed")
|
|
54
62
|
end
|
|
55
63
|
|
|
64
|
+
def toc
|
|
65
|
+
@context[:toc] || []
|
|
66
|
+
end
|
|
67
|
+
|
|
56
68
|
private
|
|
57
69
|
|
|
58
70
|
def parse_frontmatter
|
|
@@ -69,7 +81,9 @@ module Docyard
|
|
|
69
81
|
end
|
|
70
82
|
|
|
71
83
|
def render_html
|
|
72
|
-
|
|
84
|
+
@context[:config] = config&.data
|
|
85
|
+
|
|
86
|
+
preprocessed_content = Components::Registry.run_preprocessors(content, @context)
|
|
73
87
|
|
|
74
88
|
raw_html = Kramdown::Document.new(
|
|
75
89
|
preprocessed_content,
|
|
@@ -79,7 +93,7 @@ module Docyard
|
|
|
79
93
|
parse_block_html: true
|
|
80
94
|
).to_html
|
|
81
95
|
|
|
82
|
-
Components::Registry.run_postprocessors(raw_html)
|
|
96
|
+
Components::Registry.run_postprocessors(raw_html, @context)
|
|
83
97
|
end
|
|
84
98
|
end
|
|
85
99
|
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "renderer"
|
|
4
|
+
require_relative "utils/path_resolver"
|
|
5
|
+
|
|
6
|
+
module Docyard
|
|
7
|
+
class PrevNextBuilder
|
|
8
|
+
attr_reader :sidebar_tree, :current_path, :frontmatter, :config
|
|
9
|
+
|
|
10
|
+
def initialize(sidebar_tree:, current_path:, frontmatter: {}, config: {})
|
|
11
|
+
@sidebar_tree = sidebar_tree
|
|
12
|
+
@current_path = Utils::PathResolver.normalize(current_path)
|
|
13
|
+
@frontmatter = frontmatter
|
|
14
|
+
@config = config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def prev_next_links
|
|
18
|
+
return nil unless enabled?
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
prev: build_prev_link,
|
|
22
|
+
next: build_next_link
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_html
|
|
27
|
+
links = prev_next_links
|
|
28
|
+
return "" if links.nil? || (links[:prev].nil? && links[:next].nil?)
|
|
29
|
+
|
|
30
|
+
Renderer.new.render_partial(
|
|
31
|
+
"_prev_next", {
|
|
32
|
+
prev: links[:prev],
|
|
33
|
+
next: links[:next],
|
|
34
|
+
prev_text: config_prev_text,
|
|
35
|
+
next_text: config_next_text
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def enabled?
|
|
43
|
+
return false if config_disabled?
|
|
44
|
+
return false if frontmatter_disabled?
|
|
45
|
+
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def config_disabled?
|
|
50
|
+
return false if config.nil? || config.empty?
|
|
51
|
+
|
|
52
|
+
config == false || config["enabled"] == false || config[:enabled] == false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def frontmatter_disabled?
|
|
56
|
+
frontmatter["prev"] == false && frontmatter["next"] == false
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_prev_link
|
|
60
|
+
return nil if frontmatter["prev"] == false
|
|
61
|
+
|
|
62
|
+
return build_frontmatter_link(frontmatter["prev"]) if frontmatter["prev"]
|
|
63
|
+
|
|
64
|
+
auto_prev_link
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_next_link
|
|
68
|
+
return nil if frontmatter["next"] == false
|
|
69
|
+
|
|
70
|
+
return build_frontmatter_link(frontmatter["next"]) if frontmatter["next"]
|
|
71
|
+
|
|
72
|
+
auto_next_link
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build_frontmatter_link(value)
|
|
76
|
+
case value
|
|
77
|
+
when String
|
|
78
|
+
find_link_by_text(value)
|
|
79
|
+
when Hash
|
|
80
|
+
{
|
|
81
|
+
title: value["text"] || value[:text],
|
|
82
|
+
path: value["link"] || value[:link]
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def find_link_by_text(text)
|
|
88
|
+
flat_links.find { |link| link[:title].downcase == text.downcase }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def auto_prev_link
|
|
92
|
+
index = current_page_index
|
|
93
|
+
return nil unless index&.positive?
|
|
94
|
+
|
|
95
|
+
flat_links[index - 1]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def auto_next_link
|
|
99
|
+
index = current_page_index
|
|
100
|
+
return nil unless index && index < flat_links.length - 1
|
|
101
|
+
|
|
102
|
+
flat_links[index + 1]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def current_page_index
|
|
106
|
+
@current_page_index ||= flat_links.find_index do |link|
|
|
107
|
+
normalized_path(link[:path]) == normalized_path(current_path)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def flat_links
|
|
112
|
+
@flat_links ||= begin
|
|
113
|
+
links = []
|
|
114
|
+
flatten_tree(sidebar_tree, links)
|
|
115
|
+
links.uniq { |link| normalized_path(link[:path]) }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def flatten_tree(items, links)
|
|
120
|
+
items.each do |item|
|
|
121
|
+
links << build_link(item) if valid_navigation_item?(item)
|
|
122
|
+
flatten_tree(item[:children], links) if item[:children]&.any?
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def valid_navigation_item?(item)
|
|
127
|
+
item[:type] == :file && item[:path] && !external_link?(item[:path])
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def build_link(item)
|
|
131
|
+
{
|
|
132
|
+
title: item[:footer_text] || item[:title],
|
|
133
|
+
path: item[:path]
|
|
134
|
+
}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def external_link?(path)
|
|
138
|
+
path.start_with?("http://", "https://")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def normalized_path(path)
|
|
142
|
+
return "" if path.nil?
|
|
143
|
+
|
|
144
|
+
path.gsub(/[?#].*$/, "")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def config_prev_text
|
|
148
|
+
return "Previous" if config.nil? || config.empty?
|
|
149
|
+
|
|
150
|
+
config["prev_text"] || config[:prev_text] || "Previous"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def config_next_text
|
|
154
|
+
return "Next" if config.nil? || config.empty?
|
|
155
|
+
|
|
156
|
+
config["next_text"] || config[:next_text] || "Next"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "rack"
|
|
5
5
|
require_relative "sidebar_builder"
|
|
6
|
+
require_relative "prev_next_builder"
|
|
6
7
|
require_relative "constants"
|
|
7
8
|
|
|
8
9
|
module Docyard
|
|
@@ -12,7 +13,7 @@ module Docyard
|
|
|
12
13
|
@file_watcher = file_watcher
|
|
13
14
|
@config = config
|
|
14
15
|
@router = Router.new(docs_path: docs_path)
|
|
15
|
-
@renderer = Renderer.new(base_url: config&.build&.base_url || "/")
|
|
16
|
+
@renderer = Renderer.new(base_url: config&.build&.base_url || "/", config: config)
|
|
16
17
|
@asset_handler = AssetHandler.new
|
|
17
18
|
end
|
|
18
19
|
|
|
@@ -46,9 +47,12 @@ module Docyard
|
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def render_documentation_page(file_path, current_path)
|
|
50
|
+
sidebar_builder = build_sidebar_instance(current_path)
|
|
51
|
+
|
|
49
52
|
html = renderer.render_file(
|
|
50
53
|
file_path,
|
|
51
|
-
sidebar_html:
|
|
54
|
+
sidebar_html: sidebar_builder.to_html,
|
|
55
|
+
prev_next_html: build_prev_next(sidebar_builder, current_path, file_path),
|
|
52
56
|
branding: branding_options
|
|
53
57
|
)
|
|
54
58
|
|
|
@@ -60,14 +64,32 @@ module Docyard
|
|
|
60
64
|
[Constants::STATUS_NOT_FOUND, { "Content-Type" => Constants::CONTENT_TYPE_HTML }, [html]]
|
|
61
65
|
end
|
|
62
66
|
|
|
63
|
-
def
|
|
67
|
+
def build_sidebar_instance(current_path)
|
|
64
68
|
SidebarBuilder.new(
|
|
65
69
|
docs_path: docs_path,
|
|
66
70
|
current_path: current_path,
|
|
67
71
|
config: config
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build_prev_next(sidebar_builder, current_path, file_path)
|
|
76
|
+
markdown_content = File.read(file_path)
|
|
77
|
+
markdown = Markdown.new(markdown_content)
|
|
78
|
+
|
|
79
|
+
PrevNextBuilder.new(
|
|
80
|
+
sidebar_tree: sidebar_builder.tree,
|
|
81
|
+
current_path: current_path,
|
|
82
|
+
frontmatter: markdown.frontmatter,
|
|
83
|
+
config: navigation_config
|
|
68
84
|
).to_html
|
|
69
85
|
end
|
|
70
86
|
|
|
87
|
+
def navigation_config
|
|
88
|
+
return {} unless config
|
|
89
|
+
|
|
90
|
+
config.navigation&.footer || {}
|
|
91
|
+
end
|
|
92
|
+
|
|
71
93
|
def branding_options
|
|
72
94
|
return default_branding unless config
|
|
73
95
|
|
data/lib/docyard/renderer.rb
CHANGED
|
@@ -9,31 +9,41 @@ module Docyard
|
|
|
9
9
|
ERRORS_PATH = File.join(__dir__, "templates", "errors")
|
|
10
10
|
PARTIALS_PATH = File.join(__dir__, "templates", "partials")
|
|
11
11
|
|
|
12
|
-
attr_reader :layout_path, :base_url
|
|
12
|
+
attr_reader :layout_path, :base_url, :config
|
|
13
13
|
|
|
14
|
-
def initialize(layout: "default", base_url: "/")
|
|
14
|
+
def initialize(layout: "default", base_url: "/", config: nil)
|
|
15
15
|
@layout_path = File.join(LAYOUTS_PATH, "#{layout}.html.erb")
|
|
16
16
|
@base_url = normalize_base_url(base_url)
|
|
17
|
+
@config = config
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
def render_file(file_path, sidebar_html: "", branding: {})
|
|
20
|
+
def render_file(file_path, sidebar_html: "", prev_next_html: "", branding: {})
|
|
20
21
|
markdown_content = File.read(file_path)
|
|
21
|
-
markdown = Markdown.new(markdown_content)
|
|
22
|
+
markdown = Markdown.new(markdown_content, config: config)
|
|
22
23
|
|
|
23
24
|
html_content = strip_md_from_links(markdown.html)
|
|
25
|
+
toc = markdown.toc
|
|
24
26
|
|
|
25
27
|
render(
|
|
26
28
|
content: html_content,
|
|
27
29
|
page_title: markdown.title || Constants::DEFAULT_SITE_TITLE,
|
|
28
|
-
|
|
30
|
+
navigation: {
|
|
31
|
+
sidebar_html: sidebar_html,
|
|
32
|
+
prev_next_html: prev_next_html,
|
|
33
|
+
toc: toc
|
|
34
|
+
},
|
|
29
35
|
branding: branding
|
|
30
36
|
)
|
|
31
37
|
end
|
|
32
38
|
|
|
33
|
-
def render(content:, page_title: Constants::DEFAULT_SITE_TITLE,
|
|
39
|
+
def render(content:, page_title: Constants::DEFAULT_SITE_TITLE, navigation: {}, branding: {})
|
|
34
40
|
template = File.read(layout_path)
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
sidebar_html = navigation[:sidebar_html] || ""
|
|
43
|
+
prev_next_html = navigation[:prev_next_html] || ""
|
|
44
|
+
toc = navigation[:toc] || []
|
|
45
|
+
|
|
46
|
+
assign_content_variables(content, page_title, sidebar_html, prev_next_html, toc)
|
|
37
47
|
assign_branding_variables(branding)
|
|
38
48
|
|
|
39
49
|
ERB.new(template).result(binding)
|
|
@@ -85,10 +95,12 @@ module Docyard
|
|
|
85
95
|
url.end_with?("/") ? url : "#{url}/"
|
|
86
96
|
end
|
|
87
97
|
|
|
88
|
-
def assign_content_variables(content, page_title, sidebar_html)
|
|
98
|
+
def assign_content_variables(content, page_title, sidebar_html, prev_next_html, toc)
|
|
89
99
|
@content = content
|
|
90
100
|
@page_title = page_title
|
|
91
101
|
@sidebar_html = sidebar_html
|
|
102
|
+
@prev_next_html = prev_next_html
|
|
103
|
+
@toc = toc
|
|
92
104
|
end
|
|
93
105
|
|
|
94
106
|
def assign_branding_variables(branding)
|
|
@@ -42,12 +42,16 @@
|
|
|
42
42
|
margin: 0;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
.highlight
|
|
46
|
-
.highlight .w {
|
|
45
|
+
.highlight {
|
|
47
46
|
color: #24292f;
|
|
48
47
|
background-color: #f6f8fa;
|
|
49
48
|
}
|
|
50
49
|
|
|
50
|
+
.highlight .w {
|
|
51
|
+
color: #24292f;
|
|
52
|
+
background-color: transparent;
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
/* Keywords */
|
|
52
56
|
.highlight .k,
|
|
53
57
|
.highlight .kd,
|
|
@@ -214,12 +218,16 @@
|
|
|
214
218
|
}
|
|
215
219
|
|
|
216
220
|
/* Dark Mode Syntax Highlighting - GitHub Dark */
|
|
217
|
-
.dark .highlight
|
|
218
|
-
.dark .highlight .w {
|
|
221
|
+
.dark .highlight {
|
|
219
222
|
color: #e6edf3;
|
|
220
223
|
background-color: #161b22;
|
|
221
224
|
}
|
|
222
225
|
|
|
226
|
+
.dark .highlight .w {
|
|
227
|
+
color: #e6edf3;
|
|
228
|
+
background-color: transparent;
|
|
229
|
+
}
|
|
230
|
+
|
|
223
231
|
/* Keywords */
|
|
224
232
|
.dark .highlight .k,
|
|
225
233
|
.dark .highlight .kd,
|