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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +29 -3
  4. data/README.md +37 -12
  5. data/lib/docyard/build/static_generator.rb +1 -1
  6. data/lib/docyard/components/base_processor.rb +6 -0
  7. data/lib/docyard/components/code_block_diff_preprocessor.rb +104 -0
  8. data/lib/docyard/components/code_block_feature_extractor.rb +113 -0
  9. data/lib/docyard/components/code_block_focus_preprocessor.rb +77 -0
  10. data/lib/docyard/components/code_block_icon_detector.rb +40 -0
  11. data/lib/docyard/components/code_block_line_wrapper.rb +46 -0
  12. data/lib/docyard/components/code_block_options_preprocessor.rb +76 -0
  13. data/lib/docyard/components/code_block_patterns.rb +51 -0
  14. data/lib/docyard/components/code_block_processor.rb +135 -14
  15. data/lib/docyard/components/code_line_parser.rb +80 -0
  16. data/lib/docyard/components/code_snippet_import_preprocessor.rb +125 -0
  17. data/lib/docyard/components/heading_anchor_processor.rb +34 -0
  18. data/lib/docyard/components/registry.rb +4 -4
  19. data/lib/docyard/components/table_of_contents_processor.rb +64 -0
  20. data/lib/docyard/components/tabs_parser.rb +135 -4
  21. data/lib/docyard/components/tabs_range_finder.rb +42 -0
  22. data/lib/docyard/config/validator.rb +8 -0
  23. data/lib/docyard/config.rb +18 -0
  24. data/lib/docyard/icons/file_types.rb +0 -13
  25. data/lib/docyard/icons/phosphor.rb +2 -1
  26. data/lib/docyard/markdown.rb +18 -4
  27. data/lib/docyard/prev_next_builder.rb +159 -0
  28. data/lib/docyard/rack_application.rb +25 -3
  29. data/lib/docyard/renderer.rb +20 -8
  30. data/lib/docyard/templates/assets/css/code.css +12 -4
  31. data/lib/docyard/templates/assets/css/components/code-block.css +427 -24
  32. data/lib/docyard/templates/assets/css/components/heading-anchor.css +77 -0
  33. data/lib/docyard/templates/assets/css/components/navigation.css +12 -9
  34. data/lib/docyard/templates/assets/css/components/prev-next.css +114 -0
  35. data/lib/docyard/templates/assets/css/components/table-of-contents.css +269 -0
  36. data/lib/docyard/templates/assets/css/components/tabs.css +50 -44
  37. data/lib/docyard/templates/assets/css/layout.css +58 -1
  38. data/lib/docyard/templates/assets/css/variables.css +45 -0
  39. data/lib/docyard/templates/assets/js/components/heading-anchor.js +90 -0
  40. data/lib/docyard/templates/assets/js/components/navigation.js +6 -2
  41. data/lib/docyard/templates/assets/js/components/table-of-contents.js +301 -0
  42. data/lib/docyard/templates/layouts/default.html.erb +9 -1
  43. data/lib/docyard/templates/partials/_code_block.html.erb +50 -2
  44. data/lib/docyard/templates/partials/_heading_anchor.html.erb +1 -0
  45. data/lib/docyard/templates/partials/_prev_next.html.erb +23 -0
  46. data/lib/docyard/templates/partials/_table_of_contents.html.erb +45 -0
  47. data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +8 -0
  48. data/lib/docyard/version.rb +1 -1
  49. 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)
@@ -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"/>'
@@ -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
- preprocessed_content = Components::Registry.run_preprocessors(content)
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: build_sidebar(current_path),
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 build_sidebar(current_path)
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
 
@@ -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
- sidebar_html: sidebar_html,
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, sidebar_html: "", branding: {})
39
+ def render(content:, page_title: Constants::DEFAULT_SITE_TITLE, navigation: {}, branding: {})
34
40
  template = File.read(layout_path)
35
41
 
36
- assign_content_variables(content, page_title, sidebar_html)
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,