docyard 0.3.0 → 0.5.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 +32 -2
- data/README.md +80 -33
- data/lib/docyard/build/asset_bundler.rb +139 -0
- data/lib/docyard/build/file_copier.rb +105 -0
- data/lib/docyard/build/sitemap_generator.rb +57 -0
- data/lib/docyard/build/static_generator.rb +141 -0
- data/lib/docyard/builder.rb +104 -0
- data/lib/docyard/cli.rb +19 -0
- data/lib/docyard/components/heading_anchor_processor.rb +34 -0
- data/lib/docyard/components/table_of_contents_processor.rb +64 -0
- data/lib/docyard/components/table_wrapper_processor.rb +18 -0
- data/lib/docyard/config.rb +15 -2
- data/lib/docyard/icons/phosphor.rb +3 -1
- data/lib/docyard/initializer.rb +80 -14
- data/lib/docyard/markdown.rb +19 -0
- data/lib/docyard/prev_next_builder.rb +159 -0
- data/lib/docyard/preview_server.rb +72 -0
- data/lib/docyard/rack_application.rb +25 -3
- data/lib/docyard/renderer.rb +33 -8
- data/lib/docyard/sidebar/config_parser.rb +180 -0
- data/lib/docyard/sidebar/item.rb +58 -0
- data/lib/docyard/sidebar/renderer.rb +33 -6
- data/lib/docyard/sidebar_builder.rb +45 -1
- data/lib/docyard/templates/assets/css/components/callout.css +1 -1
- data/lib/docyard/templates/assets/css/components/code-block.css +2 -2
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +77 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +65 -7
- 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 +3 -2
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +8 -0
- data/lib/docyard/templates/assets/css/layout.css +58 -1
- data/lib/docyard/templates/assets/css/markdown.css +20 -11
- data/lib/docyard/templates/assets/css/variables.css +1 -0
- data/lib/docyard/templates/assets/js/components/heading-anchor.js +90 -0
- data/lib/docyard/templates/assets/js/components/navigation.js +225 -0
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +301 -0
- data/lib/docyard/templates/assets/js/theme.js +2 -185
- data/lib/docyard/templates/config/docyard.yml.erb +32 -10
- data/lib/docyard/templates/layouts/default.html.erb +10 -2
- data/lib/docyard/templates/markdown/getting-started/installation.md.erb +46 -12
- data/lib/docyard/templates/markdown/guides/configuration.md.erb +202 -0
- data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +247 -0
- data/lib/docyard/templates/markdown/index.md.erb +55 -59
- data/lib/docyard/templates/partials/_heading_anchor.html.erb +1 -0
- data/lib/docyard/templates/partials/_nav_group.html.erb +10 -4
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +9 -1
- 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
- data/lib/docyard.rb +8 -0
- metadata +67 -10
- data/lib/docyard/templates/markdown/components/callouts.md.erb +0 -204
- data/lib/docyard/templates/markdown/components/icons.md.erb +0 -125
- data/lib/docyard/templates/markdown/components/tabs.md.erb +0 -686
- data/lib/docyard/templates/markdown/configuration.md.erb +0 -202
- data/lib/docyard/templates/markdown/core-concepts/file-structure.md.erb +0 -61
- data/lib/docyard/templates/markdown/core-concepts/markdown.md.erb +0 -90
- data/lib/docyard/templates/markdown/getting-started/introduction.md.erb +0 -30
- data/lib/docyard/templates/markdown/getting-started/quick-start.md.erb +0 -56
- data/lib/docyard/templates/partials/_icons.html.erb +0 -11
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "tty-progressbar"
|
|
5
|
+
|
|
6
|
+
module Docyard
|
|
7
|
+
class Builder
|
|
8
|
+
attr_reader :config, :clean, :verbose, :start_time
|
|
9
|
+
|
|
10
|
+
def initialize(clean: true, verbose: false)
|
|
11
|
+
@config = Config.new
|
|
12
|
+
@clean = clean
|
|
13
|
+
@verbose = verbose
|
|
14
|
+
@start_time = Time.now
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build
|
|
18
|
+
prepare_output_directory
|
|
19
|
+
log "Building static site..."
|
|
20
|
+
|
|
21
|
+
pages_built = generate_static_pages
|
|
22
|
+
bundles_created = bundle_assets
|
|
23
|
+
assets_copied = copy_static_files
|
|
24
|
+
generate_seo_files
|
|
25
|
+
|
|
26
|
+
display_summary(pages_built, bundles_created, assets_copied)
|
|
27
|
+
true
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
error "Build failed: #{e.message}"
|
|
30
|
+
error e.backtrace.first if verbose
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def prepare_output_directory
|
|
37
|
+
output_dir = config.build.output_dir
|
|
38
|
+
|
|
39
|
+
if clean && Dir.exist?(output_dir)
|
|
40
|
+
log "[✓] Cleaning #{output_dir}/ directory"
|
|
41
|
+
FileUtils.rm_rf(output_dir)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
FileUtils.mkdir_p(output_dir)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def generate_static_pages
|
|
48
|
+
require_relative "build/static_generator"
|
|
49
|
+
generator = Build::StaticGenerator.new(config, verbose: verbose)
|
|
50
|
+
generator.generate
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def bundle_assets
|
|
54
|
+
require_relative "build/asset_bundler"
|
|
55
|
+
bundler = Build::AssetBundler.new(config, verbose: verbose)
|
|
56
|
+
bundler.bundle
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def copy_static_files
|
|
60
|
+
require_relative "build/file_copier"
|
|
61
|
+
copier = Build::FileCopier.new(config, verbose: verbose)
|
|
62
|
+
copier.copy
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def generate_seo_files
|
|
66
|
+
require_relative "build/sitemap_generator"
|
|
67
|
+
sitemap_gen = Build::SitemapGenerator.new(config)
|
|
68
|
+
sitemap_gen.generate
|
|
69
|
+
|
|
70
|
+
File.write(File.join(config.build.output_dir, "robots.txt"), robots_txt_content)
|
|
71
|
+
log "[✓] Generated robots.txt"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def robots_txt_content
|
|
75
|
+
base_url = config.build.base_url
|
|
76
|
+
base_url = "#{base_url}/" unless base_url.end_with?("/")
|
|
77
|
+
|
|
78
|
+
<<~ROBOTS
|
|
79
|
+
User-agent: *
|
|
80
|
+
Allow: /
|
|
81
|
+
|
|
82
|
+
Sitemap: #{base_url}sitemap.xml
|
|
83
|
+
ROBOTS
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def display_summary(pages, bundles, assets)
|
|
87
|
+
elapsed = Time.now - start_time
|
|
88
|
+
|
|
89
|
+
puts "\n#{'=' * 50}"
|
|
90
|
+
puts "Build complete in #{format('%.2f', elapsed)}s"
|
|
91
|
+
puts "Output: #{config.build.output_dir}/"
|
|
92
|
+
puts "#{pages} pages, #{bundles} bundles, #{assets} static files"
|
|
93
|
+
puts "=" * 50
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def log(message)
|
|
97
|
+
puts message
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def error(message)
|
|
101
|
+
warn "[ERROR] #{message}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/docyard/cli.rb
CHANGED
|
@@ -19,6 +19,25 @@ module Docyard
|
|
|
19
19
|
exit(1) unless initializer.run
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
desc "build", "Build static site for production"
|
|
23
|
+
method_option :clean, type: :boolean, default: true, desc: "Clean output directory before building"
|
|
24
|
+
method_option :verbose, type: :boolean, default: false, aliases: "-v", desc: "Show verbose output"
|
|
25
|
+
def build
|
|
26
|
+
require_relative "builder"
|
|
27
|
+
builder = Docyard::Builder.new(
|
|
28
|
+
clean: options[:clean],
|
|
29
|
+
verbose: options[:verbose]
|
|
30
|
+
)
|
|
31
|
+
exit(1) unless builder.build
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "preview", "Preview the built site locally"
|
|
35
|
+
method_option :port, type: :numeric, default: 4000, aliases: "-p", desc: "Port to run preview server on"
|
|
36
|
+
def preview
|
|
37
|
+
require_relative "preview_server"
|
|
38
|
+
Docyard::PreviewServer.new(port: options[:port]).start
|
|
39
|
+
end
|
|
40
|
+
|
|
22
41
|
desc "serve", "Start the development server"
|
|
23
42
|
method_option :port, type: :numeric, default: 4200, aliases: "-p", desc: "Port to run the server on"
|
|
24
43
|
method_option :host, type: :string, default: "localhost", aliases: "-h", desc: "Host to bind the server to"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Components
|
|
5
|
+
class HeadingAnchorProcessor < BaseProcessor
|
|
6
|
+
self.priority = 30
|
|
7
|
+
|
|
8
|
+
def postprocess(html)
|
|
9
|
+
add_anchor_links(html)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def add_anchor_links(html)
|
|
15
|
+
html.gsub(%r{<(h[2-6])\s+id="([^"]+)">(.*?)</\1>}m) do |_match|
|
|
16
|
+
tag = Regexp.last_match(1)
|
|
17
|
+
id = Regexp.last_match(2)
|
|
18
|
+
content = Regexp.last_match(3)
|
|
19
|
+
|
|
20
|
+
anchor_html = render_anchor_link(id)
|
|
21
|
+
|
|
22
|
+
"<#{tag} id=\"#{id}\">#{content}#{anchor_html}</#{tag}>"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render_anchor_link(id)
|
|
27
|
+
renderer = Renderer.new
|
|
28
|
+
renderer.render_partial("_heading_anchor", {
|
|
29
|
+
id: id
|
|
30
|
+
})
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Components
|
|
5
|
+
class TableOfContentsProcessor < BaseProcessor
|
|
6
|
+
self.priority = 35
|
|
7
|
+
|
|
8
|
+
def postprocess(html)
|
|
9
|
+
headings = extract_headings(html)
|
|
10
|
+
Thread.current[:docyard_toc] = headings
|
|
11
|
+
html
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def extract_headings(html)
|
|
17
|
+
headings = []
|
|
18
|
+
|
|
19
|
+
html.scan(%r{<(h[2-4])\s+id="([^"]+)">(.*?)</\1>}m) do
|
|
20
|
+
level = Regexp.last_match(1)[1].to_i
|
|
21
|
+
id = Regexp.last_match(2)
|
|
22
|
+
text = strip_html(Regexp.last_match(3))
|
|
23
|
+
|
|
24
|
+
headings << {
|
|
25
|
+
level: level,
|
|
26
|
+
id: id,
|
|
27
|
+
text: text
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
build_hierarchy(headings)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build_hierarchy(headings)
|
|
35
|
+
return [] if headings.empty?
|
|
36
|
+
|
|
37
|
+
root = []
|
|
38
|
+
stack = []
|
|
39
|
+
|
|
40
|
+
headings.each do |heading|
|
|
41
|
+
heading[:children] = []
|
|
42
|
+
|
|
43
|
+
stack.pop while stack.any? && stack.last[:level] >= heading[:level]
|
|
44
|
+
|
|
45
|
+
if stack.empty?
|
|
46
|
+
root << heading
|
|
47
|
+
else
|
|
48
|
+
stack.last[:children] << heading
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
stack << heading
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
root
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def strip_html(text)
|
|
58
|
+
text.gsub(%r{<a[^>]*class="heading-anchor"[^>]*>.*?</a>}, "")
|
|
59
|
+
.gsub(/<[^>]+>/, "")
|
|
60
|
+
.strip
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Components
|
|
5
|
+
class TableWrapperProcessor < BaseProcessor
|
|
6
|
+
self.priority = 100
|
|
7
|
+
|
|
8
|
+
def postprocess(html)
|
|
9
|
+
wrapped = html.gsub(/<table([^>]*)>/) do
|
|
10
|
+
attributes = Regexp.last_match(1)
|
|
11
|
+
"<div class=\"table-wrapper\"><table#{attributes}>"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
wrapped.gsub("</table>", "</table></div>")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/docyard/config.rb
CHANGED
|
@@ -25,7 +25,16 @@ module Docyard
|
|
|
25
25
|
"base_url" => "/",
|
|
26
26
|
"clean" => true
|
|
27
27
|
},
|
|
28
|
-
"sidebar" =>
|
|
28
|
+
"sidebar" => {
|
|
29
|
+
"items" => []
|
|
30
|
+
},
|
|
31
|
+
"navigation" => {
|
|
32
|
+
"footer" => {
|
|
33
|
+
"enabled" => true,
|
|
34
|
+
"prev_text" => "Previous",
|
|
35
|
+
"next_text" => "Next"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
29
38
|
}.freeze
|
|
30
39
|
|
|
31
40
|
attr_reader :data, :file_path
|
|
@@ -58,7 +67,11 @@ module Docyard
|
|
|
58
67
|
end
|
|
59
68
|
|
|
60
69
|
def sidebar
|
|
61
|
-
data["sidebar"]
|
|
70
|
+
@sidebar ||= ConfigSection.new(data["sidebar"])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def navigation
|
|
74
|
+
@navigation ||= ConfigSection.new(data["navigation"])
|
|
62
75
|
end
|
|
63
76
|
|
|
64
77
|
private
|
|
@@ -27,6 +27,7 @@ module Docyard
|
|
|
27
27
|
"sun" => '<path d="M120,40V16a8,8,0,0,1,16,0V40a8,8,0,0,1-16,0Zm72,88a64,64,0,1,1-64-64A64.07,64.07,0,0,1,192,128Zm-16,0a48,48,0,1,0-48,48A48.05,48.05,0,0,0,176,128ZM58.34,69.66A8,8,0,0,0,69.66,58.34l-16-16A8,8,0,0,0,42.34,53.66Zm0,116.68-16,16a8,8,0,0,0,11.32,11.32l16-16a8,8,0,0,0-11.32-11.32ZM192,72a8,8,0,0,0,5.66-2.34l16-16a8,8,0,0,0-11.32-11.32l-16,16A8,8,0,0,0,192,72Zm5.66,114.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32-11.32ZM48,128a8,8,0,0,0-8-8H16a8,8,0,0,0,0,16H40A8,8,0,0,0,48,128Zm80,80a8,8,0,0,0-8,8v24a8,8,0,0,0,16,0V216A8,8,0,0,0,128,208Zm112-88H216a8,8,0,0,0,0,16h24a8,8,0,0,0,0-16Z"/>',
|
|
28
28
|
"link-external" => '<path d="M224,104a8,8,0,0,1-16,0V59.32l-66.33,66.34a8,8,0,0,1-11.32-11.32L196.68,48H152a8,8,0,0,1,0-16h64a8,8,0,0,1,8,8Zm-40,24a8,8,0,0,0-8,8v72H48V80h72a8,8,0,0,0,0-16H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V136A8,8,0,0,0,184,128Z"/>',
|
|
29
29
|
"copy" => '<path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z"/>',
|
|
30
|
+
"caret-right" => '<path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z"/>',
|
|
30
31
|
"github" => '<path d="M208.31,75.68A59.78,59.78,0,0,0,202.93,28,8,8,0,0,0,196,24a59.75,59.75,0,0,0-48,24H124A59.75,59.75,0,0,0,76,24a8,8,0,0,0-6.93,4,59.78,59.78,0,0,0-5.38,47.68A58.14,58.14,0,0,0,56,104v8a56.06,56.06,0,0,0,48.44,55.47A39.8,39.8,0,0,0,96,192v8H72a24,24,0,0,1-24-24A40,40,0,0,0,8,136a8,8,0,0,0,0,16,24,24,0,0,1,24,24,40,40,0,0,0,40,40H96v16a8,8,0,0,0,16,0V192a24,24,0,0,1,48,0v40a8,8,0,0,0,16,0V192a39.8,39.8,0,0,0-8.44-24.53A56.06,56.06,0,0,0,216,112v-8A58.14,58.14,0,0,0,208.31,75.68ZM200,112a40,40,0,0,1-40,40H112a40,40,0,0,1-40-40v-8a41.74,41.74,0,0,1,6.9-22.48,8,8,0,0,0,1.1-7.69,43.81,43.81,0,0,1,.79-33.58,43.88,43.88,0,0,1,32.32,20.06,8,8,0,0,0,6.71,3.69h32.35a8,8,0,0,0,6.74-3.69,43.87,43.87,0,0,1,32.32-20.06,43.81,43.81,0,0,1,.77,33.58,8.09,8.09,0,0,0,1,7.65,41.76,41.76,0,0,1,7,22.52Z"/>',
|
|
31
32
|
"question" => '<path d="M140,180a12,12,0,1,1-12-12A12,12,0,0,1,140,180ZM128,72c-22.06,0-40,16.15-40,36v4a8,8,0,0,0,16,0v-4c0-11,10.77-20,24-20s24,9,24,20-10.77,20-24,20a8,8,0,0,0-8,8v8a8,8,0,0,0,16,0v-.72c18.24-3.35,32-17.9,32-35.28C168,88.15,150.06,72,128,72Zm104,56A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128Z"/>',
|
|
32
33
|
"lightbulb" => '<path d="M176,232a8,8,0,0,1-8,8H88a8,8,0,0,1,0-16h80A8,8,0,0,1,176,232Zm40-128a87.55,87.55,0,0,1-33.64,69.21A16.24,16.24,0,0,0,176,186v6a16,16,0,0,1-16,16H96a16,16,0,0,1-16-16v-6a16,16,0,0,0-6.23-12.66A87.59,87.59,0,0,1,40,104.49C39.74,56.83,78.26,17.14,125.88,16A88,88,0,0,1,216,104Zm-16,0a72,72,0,0,0-73.74-72c-39,.92-70.47,33.39-70.26,72.39a71.65,71.65,0,0,0,27.64,56.3A32,32,0,0,1,96,186v6h64v-6a32.15,32.15,0,0,1,12.47-25.35A71.65,71.65,0,0,0,200,104Zm-16.11-9.34a57.6,57.6,0,0,0-46.56-46.55,8,8,0,0,0-2.66,15.78c16.57,2.79,30.63,16.85,33.44,33.45A8,8,0,0,0,176,104a9,9,0,0,0,1.35-.11A8,8,0,0,0,183.89,94.66Z"/>',
|
|
@@ -34,7 +35,8 @@ module Docyard
|
|
|
34
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"/>',
|
|
35
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"/>',
|
|
36
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"/>',
|
|
37
|
-
"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"/>'
|
|
38
40
|
},
|
|
39
41
|
"bold" => {
|
|
40
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/initializer.rb
CHANGED
|
@@ -10,14 +10,9 @@ module Docyard
|
|
|
10
10
|
|
|
11
11
|
TEMPLATES = {
|
|
12
12
|
"index.md" => "index.md.erb",
|
|
13
|
-
"getting-started/introduction.md" => "getting-started/introduction.md.erb",
|
|
14
13
|
"getting-started/installation.md" => "getting-started/installation.md.erb",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"core-concepts/markdown.md" => "core-concepts/markdown.md.erb",
|
|
18
|
-
"components/callouts.md" => "components/callouts.md.erb",
|
|
19
|
-
"components/icons.md" => "components/icons.md.erb",
|
|
20
|
-
"components/tabs.md" => "components/tabs.md.erb"
|
|
14
|
+
"guides/markdown-features.md" => "guides/markdown-features.md.erb",
|
|
15
|
+
"guides/configuration.md" => "guides/configuration.md.erb"
|
|
21
16
|
}.freeze
|
|
22
17
|
|
|
23
18
|
def initialize(path = ".")
|
|
@@ -79,16 +74,87 @@ module Docyard
|
|
|
79
74
|
end
|
|
80
75
|
|
|
81
76
|
def print_success
|
|
82
|
-
|
|
77
|
+
print_banner
|
|
78
|
+
print_created_files
|
|
79
|
+
print_next_steps
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def print_banner
|
|
83
|
+
puts ""
|
|
84
|
+
puts "┌─────────────────────────────────────────────────────────────┐"
|
|
85
|
+
puts "│ ✓ Docyard initialized successfully │"
|
|
86
|
+
puts "└─────────────────────────────────────────────────────────────┘"
|
|
87
|
+
puts ""
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def print_created_files
|
|
91
|
+
puts "Created files:"
|
|
83
92
|
puts ""
|
|
84
|
-
|
|
85
|
-
TEMPLATES.each_key { |file| puts " #{DOCS_DIR}/#{file}" }
|
|
86
|
-
puts " docyard.yml (configuration - optional)"
|
|
93
|
+
print_file_tree
|
|
87
94
|
puts ""
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def print_next_steps
|
|
88
98
|
puts "Next steps:"
|
|
89
|
-
puts "
|
|
90
|
-
puts "
|
|
91
|
-
puts "
|
|
99
|
+
puts ""
|
|
100
|
+
puts " Start development server:"
|
|
101
|
+
puts " docyard serve"
|
|
102
|
+
puts " → http://localhost:4200"
|
|
103
|
+
puts ""
|
|
104
|
+
puts " Build for production:"
|
|
105
|
+
puts " docyard build"
|
|
106
|
+
puts ""
|
|
107
|
+
puts " Preview production build:"
|
|
108
|
+
puts " docyard preview"
|
|
109
|
+
puts ""
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def print_file_tree
|
|
113
|
+
puts " ├── docs/"
|
|
114
|
+
|
|
115
|
+
grouped_files = TEMPLATES.keys.group_by { |file| File.dirname(file) }
|
|
116
|
+
sorted_dirs = grouped_files.keys.sort
|
|
117
|
+
|
|
118
|
+
sorted_dirs.each_with_index do |dir, dir_idx|
|
|
119
|
+
print_directory_group(dir, grouped_files[dir], dir_idx == sorted_dirs.length - 1)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
puts " └── docyard.yml"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def print_directory_group(dir, files, is_last_dir)
|
|
126
|
+
sorted_files = files.sort
|
|
127
|
+
|
|
128
|
+
if dir == "."
|
|
129
|
+
print_root_files(sorted_files, is_last_dir)
|
|
130
|
+
else
|
|
131
|
+
print_subdirectory(dir, sorted_files, is_last_dir)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def print_root_files(files, is_last_dir)
|
|
136
|
+
files.each_with_index do |file, idx|
|
|
137
|
+
is_last = idx == files.length - 1 && is_last_dir
|
|
138
|
+
prefix = is_last ? " │ └──" : " │ ├──"
|
|
139
|
+
puts "#{prefix} #{file}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def print_subdirectory(dir, files, is_last_dir)
|
|
144
|
+
dir_prefix = is_last_dir ? " │ └──" : " │ ├──"
|
|
145
|
+
puts "#{dir_prefix} #{dir}/"
|
|
146
|
+
|
|
147
|
+
files.each_with_index do |file, idx|
|
|
148
|
+
print_subdirectory_file(file, idx, files.length, is_last_dir)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def print_subdirectory_file(file, idx, total, is_last_dir)
|
|
153
|
+
is_last_file = idx == total - 1
|
|
154
|
+
file_prefix = is_last_dir ? " │ " : " │ │ "
|
|
155
|
+
file_prefix += is_last_file ? "└──" : "├──"
|
|
156
|
+
basename = File.basename(file)
|
|
157
|
+
puts "#{file_prefix} #{basename}"
|
|
92
158
|
end
|
|
93
159
|
end
|
|
94
160
|
end
|
data/lib/docyard/markdown.rb
CHANGED
|
@@ -9,6 +9,9 @@ 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/table_wrapper_processor"
|
|
13
|
+
require_relative "components/heading_anchor_processor"
|
|
14
|
+
require_relative "components/table_of_contents_processor"
|
|
12
15
|
|
|
13
16
|
module Docyard
|
|
14
17
|
class Markdown
|
|
@@ -40,6 +43,22 @@ module Docyard
|
|
|
40
43
|
frontmatter["description"]
|
|
41
44
|
end
|
|
42
45
|
|
|
46
|
+
def sidebar_icon
|
|
47
|
+
frontmatter.dig("sidebar", "icon")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def sidebar_text
|
|
51
|
+
frontmatter.dig("sidebar", "text")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def sidebar_collapsed
|
|
55
|
+
frontmatter.dig("sidebar", "collapsed")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def toc
|
|
59
|
+
@toc ||= Thread.current[:docyard_toc] || []
|
|
60
|
+
end
|
|
61
|
+
|
|
43
62
|
private
|
|
44
63
|
|
|
45
64
|
def parse_frontmatter
|
|
@@ -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
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webrick"
|
|
4
|
+
require_relative "config"
|
|
5
|
+
|
|
6
|
+
module Docyard
|
|
7
|
+
class PreviewServer
|
|
8
|
+
DEFAULT_PORT = 4000
|
|
9
|
+
|
|
10
|
+
attr_reader :port, :output_dir
|
|
11
|
+
|
|
12
|
+
def initialize(port: DEFAULT_PORT)
|
|
13
|
+
@port = port
|
|
14
|
+
@config = Config.load
|
|
15
|
+
@output_dir = File.expand_path(@config.build.output_dir)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start
|
|
19
|
+
validate_output_directory!
|
|
20
|
+
print_server_info
|
|
21
|
+
|
|
22
|
+
server = create_server
|
|
23
|
+
trap("INT") { shutdown_server(server) }
|
|
24
|
+
|
|
25
|
+
server.start
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def validate_output_directory!
|
|
31
|
+
return if File.directory?(output_dir)
|
|
32
|
+
|
|
33
|
+
abort "Error: #{output_dir}/ directory not found.\n" \
|
|
34
|
+
"Run `docyard build` first to build the site."
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def print_server_info
|
|
38
|
+
puts "Preview server starting..."
|
|
39
|
+
puts "=> Serving from: #{output_dir}/"
|
|
40
|
+
puts "=> Running at: http://localhost:#{port}"
|
|
41
|
+
puts "=> Press Ctrl+C to stop\n"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def create_server
|
|
45
|
+
WEBrick::HTTPServer.new(
|
|
46
|
+
Port: port,
|
|
47
|
+
DocumentRoot: output_dir,
|
|
48
|
+
AccessLog: [],
|
|
49
|
+
Logger: WEBrick::Log.new(File::NULL),
|
|
50
|
+
MimeTypes: mime_types
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def mime_types
|
|
55
|
+
WEBrick::HTTPUtils::DefaultMimeTypes.merge(
|
|
56
|
+
{
|
|
57
|
+
"css" => "text/css",
|
|
58
|
+
"js" => "application/javascript",
|
|
59
|
+
"json" => "application/json",
|
|
60
|
+
"svg" => "image/svg+xml",
|
|
61
|
+
"woff" => "font/woff",
|
|
62
|
+
"woff2" => "font/woff2"
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def shutdown_server(server)
|
|
68
|
+
puts "\nShutting down preview server..."
|
|
69
|
+
server.shutdown
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|