docyard 0.9.0 → 1.0.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 +43 -0
- data/README.md +8 -253
- data/exe/docyard +6 -0
- data/lib/docyard/build/asset_bundler.rb +2 -2
- data/lib/docyard/build/file_copier.rb +12 -5
- data/lib/docyard/build/llms_txt_generator.rb +103 -0
- data/lib/docyard/build/sitemap_generator.rb +1 -1
- data/lib/docyard/build/static_generator.rb +115 -79
- data/lib/docyard/builder.rb +6 -2
- data/lib/docyard/cli.rb +14 -4
- data/lib/docyard/components/processors/callout_processor.rb +1 -1
- data/lib/docyard/components/processors/code_block_extended_fence_postprocessor.rb +24 -0
- data/lib/docyard/components/processors/code_block_extended_fence_preprocessor.rb +44 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +11 -1
- data/lib/docyard/components/processors/code_block_processor.rb +5 -24
- data/lib/docyard/components/processors/code_group_processor.rb +6 -22
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +1 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +1 -2
- data/lib/docyard/components/processors/icon_processor.rb +8 -2
- data/lib/docyard/components/processors/include_processor.rb +10 -10
- data/lib/docyard/components/processors/video_embed_processor.rb +14 -3
- data/lib/docyard/components/support/code_block/feature_extractor.rb +3 -1
- data/lib/docyard/components/support/code_block/icon_detector.rb +5 -12
- data/lib/docyard/components/support/code_block/line_number_resolver.rb +30 -0
- data/lib/docyard/components/support/code_detector.rb +2 -12
- data/lib/docyard/components/support/code_group/html_builder.rb +2 -6
- data/lib/docyard/components/support/tabs/icon_detector.rb +6 -2
- data/lib/docyard/components/support/tabs/parser.rb +6 -23
- data/lib/docyard/config/analytics_resolver.rb +24 -0
- data/lib/docyard/config/branding_resolver.rb +58 -27
- data/lib/docyard/config/key_validator.rb +30 -0
- data/lib/docyard/config/logo_detector.rb +8 -8
- data/lib/docyard/config/schema.rb +39 -0
- data/lib/docyard/config/section.rb +21 -0
- data/lib/docyard/config/validation_helpers.rb +83 -0
- data/lib/docyard/config/validator.rb +45 -144
- data/lib/docyard/config/validators/navigation.rb +43 -0
- data/lib/docyard/config/validators/section.rb +114 -0
- data/lib/docyard/config.rb +46 -102
- data/lib/docyard/constants.rb +59 -0
- data/lib/docyard/{utils/errors.rb → errors.rb} +6 -0
- data/lib/docyard/initializer.rb +100 -49
- data/lib/docyard/navigation/page_navigation_builder.rb +65 -0
- data/lib/docyard/navigation/sidebar/auto_builder.rb +107 -0
- data/lib/docyard/navigation/sidebar/cache.rb +96 -0
- data/lib/docyard/navigation/sidebar/config_builder.rb +179 -0
- data/lib/docyard/navigation/sidebar/distributed_builder.rb +145 -0
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +69 -3
- data/lib/docyard/navigation/sidebar/renderer.rb +12 -1
- data/lib/docyard/navigation/sidebar_builder.rb +43 -81
- data/lib/docyard/rendering/branding_variables.rb +65 -0
- data/lib/docyard/rendering/icon_helpers.rb +14 -1
- data/lib/docyard/rendering/icons/devicons.rb +63 -0
- data/lib/docyard/rendering/icons.rb +26 -27
- data/lib/docyard/rendering/markdown.rb +5 -23
- data/lib/docyard/rendering/og_helpers.rb +36 -0
- data/lib/docyard/rendering/renderer.rb +87 -59
- data/lib/docyard/rendering/template_resolver.rb +14 -0
- data/lib/docyard/routing/fallback_resolver.rb +3 -3
- data/lib/docyard/search/build_indexer.rb +2 -2
- data/lib/docyard/search/dev_indexer.rb +36 -28
- data/lib/docyard/search/pagefind_support.rb +1 -1
- data/lib/docyard/server/asset_handler.rb +39 -15
- data/lib/docyard/server/dev_server.rb +90 -55
- data/lib/docyard/server/file_watcher.rb +68 -18
- data/lib/docyard/server/pagefind_handler.rb +1 -1
- data/lib/docyard/server/preview_server.rb +29 -33
- data/lib/docyard/server/rack_application.rb +38 -70
- data/lib/docyard/server/router.rb +11 -7
- data/lib/docyard/server/sse_server.rb +157 -0
- data/lib/docyard/server/static_file_app.rb +42 -0
- data/lib/docyard/templates/assets/css/components/banner.css +31 -0
- data/lib/docyard/templates/assets/css/components/breadcrumbs.css +2 -1
- data/lib/docyard/templates/assets/css/components/callout.css +26 -6
- data/lib/docyard/templates/assets/css/components/code-block.css +4 -2
- data/lib/docyard/templates/assets/css/components/code-group.css +20 -7
- data/lib/docyard/templates/assets/css/components/feedback.css +126 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +5 -4
- data/lib/docyard/templates/assets/css/components/icon.css +5 -0
- data/lib/docyard/templates/assets/css/components/nav-menu.css +20 -4
- data/lib/docyard/templates/assets/css/components/navigation.css +25 -3
- data/lib/docyard/templates/assets/css/components/page-actions.css +131 -0
- data/lib/docyard/templates/assets/css/components/prev-next.css +14 -7
- data/lib/docyard/templates/assets/css/components/search.css +6 -10
- data/lib/docyard/templates/assets/css/components/tab-bar.css +7 -4
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +57 -11
- data/lib/docyard/templates/assets/css/components/tabs.css +12 -4
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +3 -1
- data/lib/docyard/templates/assets/css/landing.css +82 -13
- data/lib/docyard/templates/assets/css/layout.css +17 -0
- data/lib/docyard/templates/assets/css/markdown.css +22 -2
- data/lib/docyard/templates/assets/css/variables.css +13 -1
- data/lib/docyard/templates/assets/js/components/code-group.js +4 -1
- data/lib/docyard/templates/assets/js/components/copy-page.js +115 -0
- data/lib/docyard/templates/assets/js/components/feedback.js +66 -0
- data/lib/docyard/templates/assets/js/components/file-tree.js +5 -5
- data/lib/docyard/templates/assets/js/components/navigation.js +3 -3
- data/lib/docyard/templates/assets/js/components/search.js +3 -3
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +12 -6
- data/lib/docyard/templates/assets/js/components/tabs.js +45 -22
- data/lib/docyard/templates/assets/js/components/tooltip.js +4 -4
- data/lib/docyard/templates/assets/js/hot-reload.js +44 -0
- data/lib/docyard/templates/errors/404.html.erb +114 -5
- data/lib/docyard/templates/errors/500.html.erb +173 -10
- data/lib/docyard/templates/init/_sidebar.yml +36 -0
- data/lib/docyard/templates/init/docyard.yml +36 -0
- data/lib/docyard/templates/init/pages/components.md +146 -0
- data/lib/docyard/templates/init/pages/getting-started.md +94 -0
- data/lib/docyard/templates/init/pages/index.md +22 -0
- data/lib/docyard/templates/layouts/default.html.erb +10 -0
- data/lib/docyard/templates/layouts/splash.html.erb +14 -1
- data/lib/docyard/templates/partials/_analytics.html.erb +24 -0
- data/lib/docyard/templates/partials/_banner.html.erb +1 -1
- data/lib/docyard/templates/partials/_code_block.html.erb +1 -1
- data/lib/docyard/templates/partials/_feedback.html.erb +14 -0
- data/lib/docyard/templates/partials/_footer.html.erb +1 -1
- data/lib/docyard/templates/partials/_head.html.erb +79 -4
- data/lib/docyard/templates/partials/_icon_library.html.erb +8 -0
- data/lib/docyard/templates/partials/_page_actions.html.erb +21 -0
- data/lib/docyard/templates/partials/_scripts.html.erb +6 -3
- data/lib/docyard/templates/partials/_tabs.html.erb +4 -1
- data/lib/docyard/utils/git_info.rb +157 -0
- data/lib/docyard/utils/hash_utils.rb +31 -0
- data/lib/docyard/utils/html_helpers.rb +8 -0
- data/lib/docyard/utils/logging.rb +44 -3
- data/lib/docyard/utils/path_resolver.rb +0 -10
- data/lib/docyard/utils/path_utils.rb +73 -0
- data/lib/docyard/version.rb +1 -1
- data/lib/docyard.rb +2 -2
- metadata +77 -47
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- data/.github/pull_request_template.md +0 -14
- data/.github/workflows/ci.yml +0 -49
- data/.rubocop.yml +0 -42
- data/CODE_OF_CONDUCT.md +0 -132
- data/CONTRIBUTING.md +0 -55
- data/LICENSE.vscode-icons +0 -42
- data/Rakefile +0 -8
- data/lib/docyard/config/constants.rb +0 -31
- data/lib/docyard/navigation/sidebar/children_discoverer.rb +0 -51
- data/lib/docyard/navigation/sidebar/config_parser.rb +0 -208
- data/lib/docyard/navigation/sidebar/file_resolver.rb +0 -90
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +0 -78
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +0 -71
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +0 -51
- data/lib/docyard/navigation/sidebar/path_prefixer.rb +0 -34
- data/lib/docyard/navigation/sidebar/sorter.rb +0 -21
- data/lib/docyard/navigation/sidebar/title_extractor.rb +0 -25
- data/lib/docyard/navigation/sidebar/tree_builder.rb +0 -140
- data/lib/docyard/rendering/icons/LICENSE.phosphor +0 -21
- data/lib/docyard/rendering/icons/file_types.rb +0 -79
- data/lib/docyard/rendering/icons/phosphor.rb +0 -93
- data/lib/docyard/rendering/language_mapping.rb +0 -52
- data/lib/docyard/templates/assets/js/reload.js +0 -98
- data/lib/docyard/templates/partials/_icon.html.erb +0 -1
- data/lib/docyard/templates/partials/_icon_file_extension.html.erb +0 -1
- data/sig/docyard.rbs +0 -4
|
@@ -2,12 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module Docyard
|
|
4
4
|
module IconHelpers
|
|
5
|
+
VALID_WEIGHTS = %w[regular bold fill light thin duotone].freeze
|
|
6
|
+
|
|
5
7
|
def icon(name, weight = "regular")
|
|
6
|
-
|
|
8
|
+
name = name.to_s
|
|
9
|
+
return name if name.strip.start_with?("<svg")
|
|
10
|
+
|
|
11
|
+
name = name.tr("_", "-")
|
|
12
|
+
weight = weight.to_s
|
|
13
|
+
weight = "regular" unless VALID_WEIGHTS.include?(weight)
|
|
14
|
+
weight_class = weight == "regular" ? "ph" : "ph-#{weight}"
|
|
15
|
+
%(<i class="#{weight_class} ph-#{name}" aria-hidden="true"></i>)
|
|
7
16
|
end
|
|
8
17
|
|
|
9
18
|
def icon_file_extension(extension)
|
|
10
19
|
Icons.render_file_extension(extension) || ""
|
|
11
20
|
end
|
|
21
|
+
|
|
22
|
+
def icon_for_language(language)
|
|
23
|
+
Icons.render_for_language(language) || ""
|
|
24
|
+
end
|
|
12
25
|
end
|
|
13
26
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Icons
|
|
5
|
+
module Devicons
|
|
6
|
+
MAP = {
|
|
7
|
+
"bash" => "devicon-bash-plain", "bun" => "devicon-bun-plain colored",
|
|
8
|
+
"c" => "devicon-c-plain colored", "cargo" => "devicon-rust-original colored",
|
|
9
|
+
"clj" => "devicon-clojure-plain colored", "clojure" => "devicon-clojure-plain colored",
|
|
10
|
+
"coffee" => "devicon-coffeescript-original colored", "coffeescript" => "devicon-coffeescript-original colored",
|
|
11
|
+
"composer" => "devicon-composer-plain colored", "cpp" => "devicon-cplusplus-plain colored",
|
|
12
|
+
"cs" => "devicon-csharp-plain colored", "csharp" => "devicon-csharp-plain colored",
|
|
13
|
+
"css" => "devicon-css3-plain colored", "dart" => "devicon-dart-plain colored",
|
|
14
|
+
"docker" => "devicon-docker-plain colored", "dockerfile" => "devicon-docker-plain colored",
|
|
15
|
+
"elixir" => "devicon-elixir-plain colored", "elm" => "devicon-elm-plain colored",
|
|
16
|
+
"erb" => "devicon-ruby-plain colored", "erl" => "devicon-erlang-plain colored",
|
|
17
|
+
"erlang" => "devicon-erlang-plain colored", "ex" => "devicon-elixir-plain colored",
|
|
18
|
+
"gem" => "devicon-ruby-plain colored", "go" => "devicon-go-plain colored",
|
|
19
|
+
"golang" => "devicon-go-plain colored", "gql" => "devicon-graphql-plain colored",
|
|
20
|
+
"gradle" => "devicon-gradle-original colored", "graphql" => "devicon-graphql-plain colored",
|
|
21
|
+
"groovy" => "devicon-groovy-plain colored", "h" => "devicon-c-plain colored",
|
|
22
|
+
"haml" => "devicon-ruby-plain colored", "haskell" => "devicon-haskell-plain colored",
|
|
23
|
+
"homebrew" => "devicon-homebrew-plain colored", "hs" => "devicon-haskell-plain colored",
|
|
24
|
+
"htm" => "devicon-html5-plain colored", "html" => "devicon-html5-plain colored",
|
|
25
|
+
"html5" => "devicon-html5-plain colored", "java" => "devicon-java-plain colored",
|
|
26
|
+
"javascript" => "devicon-javascript-plain colored", "js" => "devicon-javascript-plain colored",
|
|
27
|
+
"json" => "devicon-json-plain colored", "jsx" => "devicon-react-original colored",
|
|
28
|
+
"kotlin" => "devicon-kotlin-plain colored", "kt" => "devicon-kotlin-plain colored",
|
|
29
|
+
"less" => "devicon-less-plain-wordmark colored", "lua" => "devicon-lua-plain colored",
|
|
30
|
+
"markdown" => "devicon-markdown-original", "maven" => "devicon-maven-plain colored",
|
|
31
|
+
"md" => "devicon-markdown-original", "mysql" => "devicon-mysql-plain colored",
|
|
32
|
+
"nginx" => "devicon-nginx-original colored", "nim" => "devicon-nim-plain colored",
|
|
33
|
+
"nix" => "devicon-nixos-plain colored", "npm" => "devicon-npm-original-wordmark colored",
|
|
34
|
+
"nuget" => "devicon-nuget-original colored", "objc" => "devicon-objectivec-plain colored",
|
|
35
|
+
"objectivec" => "devicon-objectivec-plain colored", "ocaml" => "devicon-ocaml-plain colored",
|
|
36
|
+
"pacman" => "devicon-archlinux-plain colored", "perl" => "devicon-perl-plain colored",
|
|
37
|
+
"pgsql" => "devicon-postgresql-plain colored", "php" => "devicon-php-plain colored",
|
|
38
|
+
"pip" => "devicon-python-plain colored", "pl" => "devicon-perl-plain colored",
|
|
39
|
+
"pnpm" => "devicon-pnpm-plain colored", "postgres" => "devicon-postgresql-plain colored",
|
|
40
|
+
"postgresql" => "devicon-postgresql-plain colored", "py" => "devicon-python-plain colored",
|
|
41
|
+
"python" => "devicon-python-plain colored", "r" => "devicon-r-plain colored",
|
|
42
|
+
"rb" => "devicon-ruby-plain colored", "rs" => "devicon-rust-original colored",
|
|
43
|
+
"ruby" => "devicon-ruby-plain colored", "rust" => "devicon-rust-original colored",
|
|
44
|
+
"sass" => "devicon-sass-original colored", "scala" => "devicon-scala-plain colored",
|
|
45
|
+
"scss" => "devicon-sass-original colored", "sql" => "devicon-azuresqldatabase-plain colored",
|
|
46
|
+
"svelte" => "devicon-svelte-plain colored", "swift" => "devicon-swift-plain colored",
|
|
47
|
+
"terraform" => "devicon-terraform-plain colored", "tf" => "devicon-terraform-plain colored",
|
|
48
|
+
"ts" => "devicon-typescript-plain colored", "tsx" => "devicon-react-original colored",
|
|
49
|
+
"typescript" => "devicon-typescript-plain colored", "vim" => "devicon-vim-plain colored",
|
|
50
|
+
"vue" => "devicon-vuejs-plain colored", "xml" => "devicon-xml-plain colored",
|
|
51
|
+
"yaml" => "devicon-yaml-plain colored", "yml" => "devicon-yaml-plain colored",
|
|
52
|
+
"yarn" => "devicon-yarn-plain colored", "zig" => "devicon-zig-plain colored"
|
|
53
|
+
}.freeze
|
|
54
|
+
|
|
55
|
+
HIGHLIGHT_ALIASES = {
|
|
56
|
+
"apt" => "bash", "bun" => "bash", "cargo" => "bash", "composer" => "bash",
|
|
57
|
+
"gem" => "bash", "gradle" => "bash", "homebrew" => "bash", "maven" => "bash",
|
|
58
|
+
"npm" => "bash", "nuget" => "bash", "pacman" => "bash", "pip" => "bash",
|
|
59
|
+
"pnpm" => "bash", "yarn" => "bash"
|
|
60
|
+
}.freeze
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "icons/
|
|
4
|
-
require_relative "icons/file_types"
|
|
5
|
-
require_relative "renderer"
|
|
3
|
+
require_relative "icons/devicons"
|
|
6
4
|
|
|
7
5
|
module Docyard
|
|
8
6
|
module Icons
|
|
9
|
-
|
|
10
|
-
phosphor: PHOSPHOR
|
|
11
|
-
}.freeze
|
|
7
|
+
VALID_WEIGHTS = %w[regular bold fill light thin duotone].freeze
|
|
12
8
|
|
|
13
9
|
def self.render(name, weight = "regular")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
name = name.to_s.tr("_", "-")
|
|
11
|
+
weight = weight.to_s
|
|
12
|
+
weight = "regular" unless VALID_WEIGHTS.include?(weight)
|
|
13
|
+
weight_class = weight == "regular" ? "ph" : "ph-#{weight}"
|
|
14
|
+
%(<i class="#{weight_class} ph-#{name}" aria-hidden="true"></i>)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.render_for_language(language)
|
|
18
|
+
devicon_class = Devicons::MAP[language.to_s.downcase]
|
|
19
|
+
return %(<i class="#{devicon_class}" aria-hidden="true"></i>) if devicon_class
|
|
20
|
+
|
|
21
|
+
""
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def self.render_file_extension(extension)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
devicon_class = Devicons::MAP[extension.to_s.downcase]
|
|
26
|
+
return %(<i class="#{devicon_class}" aria-hidden="true"></i>) if devicon_class
|
|
27
|
+
|
|
28
|
+
""
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.highlight_language(language)
|
|
32
|
+
Devicons::HIGHLIGHT_ALIASES[language.to_s.downcase] || language
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.devicon?(language)
|
|
36
|
+
Devicons::MAP.key?(language.to_s.downcase)
|
|
38
37
|
end
|
|
39
38
|
end
|
|
40
39
|
end
|
|
@@ -19,6 +19,8 @@ require_relative "../components/processors/include_processor"
|
|
|
19
19
|
require_relative "../components/processors/code_block_options_preprocessor"
|
|
20
20
|
require_relative "../components/processors/code_block_diff_preprocessor"
|
|
21
21
|
require_relative "../components/processors/code_block_focus_preprocessor"
|
|
22
|
+
require_relative "../components/processors/code_block_extended_fence_preprocessor"
|
|
23
|
+
require_relative "../components/processors/code_block_extended_fence_postprocessor"
|
|
22
24
|
require_relative "../components/processors/table_wrapper_processor"
|
|
23
25
|
require_relative "../components/processors/heading_anchor_processor"
|
|
24
26
|
require_relative "../components/processors/custom_anchor_processor"
|
|
@@ -63,28 +65,8 @@ module Docyard
|
|
|
63
65
|
frontmatter["description"]
|
|
64
66
|
end
|
|
65
67
|
|
|
66
|
-
def
|
|
67
|
-
frontmatter
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def sidebar_text
|
|
71
|
-
frontmatter.dig("sidebar", "text")
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def sidebar_collapsed
|
|
75
|
-
frontmatter.dig("sidebar", "collapsed")
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def sidebar_order
|
|
79
|
-
frontmatter.dig("sidebar", "order")
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def sidebar_badge
|
|
83
|
-
frontmatter.dig("sidebar", "badge")
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def sidebar_badge_type
|
|
87
|
-
frontmatter.dig("sidebar", "badge_type")
|
|
68
|
+
def og_image
|
|
69
|
+
frontmatter["og_image"]
|
|
88
70
|
end
|
|
89
71
|
|
|
90
72
|
def toc
|
|
@@ -109,7 +91,7 @@ module Docyard
|
|
|
109
91
|
def render_html
|
|
110
92
|
@context[:config] = config&.data
|
|
111
93
|
@context[:current_file] = @file_path
|
|
112
|
-
@context[:docs_root] = "docs"
|
|
94
|
+
@context[:docs_root] = config&.source || "docs"
|
|
113
95
|
|
|
114
96
|
preprocessed_content = Components::Registry.run_preprocessors(content, @context)
|
|
115
97
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module OgHelpers
|
|
5
|
+
def assign_og_variables(branding, page_description, page_og_image, current_path)
|
|
6
|
+
site_url = branding[:site_url]
|
|
7
|
+
@og_enabled = !site_url.nil? && !site_url.empty?
|
|
8
|
+
return unless @og_enabled
|
|
9
|
+
|
|
10
|
+
@og_url = build_canonical_url(site_url, current_path)
|
|
11
|
+
@og_description = page_description || @site_description
|
|
12
|
+
@og_image = build_og_image_url(site_url, page_og_image || branding[:og_image])
|
|
13
|
+
@og_twitter = branding[:twitter]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_canonical_url(site_url, current_path)
|
|
19
|
+
base = site_url.chomp("/")
|
|
20
|
+
path = current_path.start_with?("/") ? current_path : "/#{current_path}"
|
|
21
|
+
"#{base}#{path}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def build_og_image_url(site_url, og_image)
|
|
25
|
+
return nil if og_image.nil?
|
|
26
|
+
|
|
27
|
+
if og_image.start_with?("http://", "https://")
|
|
28
|
+
og_image
|
|
29
|
+
else
|
|
30
|
+
base = site_url.chomp("/")
|
|
31
|
+
path = og_image.start_with?("/") ? og_image : "/#{og_image}"
|
|
32
|
+
"#{base}#{path}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -1,53 +1,84 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "erb"
|
|
4
|
-
require_relative "../
|
|
4
|
+
require_relative "../constants"
|
|
5
|
+
require_relative "../utils/git_info"
|
|
5
6
|
require_relative "icon_helpers"
|
|
7
|
+
require_relative "og_helpers"
|
|
8
|
+
require_relative "branding_variables"
|
|
6
9
|
|
|
7
10
|
module Docyard
|
|
8
11
|
class Renderer
|
|
9
12
|
include Utils::UrlHelpers
|
|
13
|
+
include Utils::HtmlHelpers
|
|
10
14
|
include IconHelpers
|
|
15
|
+
include OgHelpers
|
|
16
|
+
include BrandingVariables
|
|
11
17
|
|
|
12
18
|
LAYOUTS_PATH = File.join(__dir__, "../templates", "layouts")
|
|
13
19
|
ERRORS_PATH = File.join(__dir__, "../templates", "errors")
|
|
14
20
|
PARTIALS_PATH = File.join(__dir__, "../templates", "partials")
|
|
15
21
|
DEFAULT_LAYOUT = "default"
|
|
16
22
|
|
|
17
|
-
attr_reader :base_url, :config
|
|
23
|
+
attr_reader :base_url, :config, :dev_mode, :sse_port
|
|
18
24
|
|
|
19
|
-
def initialize(base_url: "/", config: nil)
|
|
25
|
+
def initialize(base_url: "/", config: nil, dev_mode: false, sse_port: nil)
|
|
20
26
|
@base_url = normalize_base_url(base_url)
|
|
21
27
|
@config = config
|
|
28
|
+
@dev_mode = dev_mode
|
|
29
|
+
@sse_port = sse_port
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
def render_file(file_path, sidebar_html: "", prev_next_html: "", breadcrumbs: nil, branding: {},
|
|
25
33
|
template_options: {}, current_path: "/")
|
|
26
|
-
|
|
34
|
+
raw_content = File.read(file_path)
|
|
35
|
+
markdown = Markdown.new(raw_content, config: config, file_path: file_path)
|
|
27
36
|
|
|
28
37
|
render(
|
|
29
38
|
content: strip_md_from_links(markdown.html),
|
|
30
39
|
page_title: markdown.title || Constants::DEFAULT_SITE_TITLE,
|
|
40
|
+
page_description: markdown.description,
|
|
41
|
+
page_og_image: markdown.og_image,
|
|
31
42
|
navigation: build_navigation(sidebar_html, prev_next_html, markdown.toc, breadcrumbs),
|
|
32
43
|
branding: branding,
|
|
33
44
|
template_options: template_options,
|
|
34
|
-
current_path: current_path
|
|
45
|
+
current_path: current_path,
|
|
46
|
+
file_path: file_path,
|
|
47
|
+
raw_markdown: raw_content
|
|
35
48
|
)
|
|
36
49
|
end
|
|
37
50
|
|
|
51
|
+
def render_for_search(file_path)
|
|
52
|
+
markdown = Markdown.new(File.read(file_path), config: config, file_path: file_path)
|
|
53
|
+
title = markdown.title || Constants::DEFAULT_SITE_TITLE
|
|
54
|
+
content = strip_md_from_links(markdown.html)
|
|
55
|
+
|
|
56
|
+
<<~HTML
|
|
57
|
+
<!DOCTYPE html>
|
|
58
|
+
<html>
|
|
59
|
+
<head><title>#{escape_html(title)}</title></head>
|
|
60
|
+
<body><main data-pagefind-body>#{content}</main></body>
|
|
61
|
+
</html>
|
|
62
|
+
HTML
|
|
63
|
+
end
|
|
64
|
+
|
|
38
65
|
def build_navigation(sidebar_html, prev_next_html, toc, breadcrumbs)
|
|
39
66
|
{ sidebar_html: sidebar_html, prev_next_html: prev_next_html, toc: toc, breadcrumbs: breadcrumbs }
|
|
40
67
|
end
|
|
41
68
|
|
|
42
|
-
def render(content:, page_title: Constants::DEFAULT_SITE_TITLE,
|
|
43
|
-
template_options: {}, current_path: "/"
|
|
69
|
+
def render(content:, page_title: Constants::DEFAULT_SITE_TITLE, page_description: nil, page_og_image: nil,
|
|
70
|
+
navigation: {}, branding: {}, template_options: {}, current_path: "/", file_path: nil,
|
|
71
|
+
raw_markdown: nil)
|
|
44
72
|
layout = template_options[:template] || DEFAULT_LAYOUT
|
|
45
73
|
layout_path = File.join(LAYOUTS_PATH, "#{layout}.html.erb")
|
|
46
74
|
template = File.read(layout_path)
|
|
47
75
|
|
|
48
|
-
assign_content_variables(content, page_title, navigation)
|
|
76
|
+
assign_content_variables(content, page_title, navigation, raw_markdown)
|
|
49
77
|
assign_branding_variables(branding, current_path)
|
|
78
|
+
assign_og_variables(branding, page_description, page_og_image, current_path)
|
|
50
79
|
assign_template_variables(template_options)
|
|
80
|
+
assign_git_info(branding, file_path)
|
|
81
|
+
assign_feedback_variables
|
|
51
82
|
|
|
52
83
|
ERB.new(template).result(binding)
|
|
53
84
|
end
|
|
@@ -58,7 +89,7 @@ module Docyard
|
|
|
58
89
|
|
|
59
90
|
def render_server_error(error)
|
|
60
91
|
@error_message = error.message
|
|
61
|
-
@backtrace = error.backtrace
|
|
92
|
+
@backtrace = error.backtrace&.join("\n") || "No backtrace available"
|
|
62
93
|
render_error_template(500)
|
|
63
94
|
end
|
|
64
95
|
|
|
@@ -68,15 +99,37 @@ module Docyard
|
|
|
68
99
|
ERB.new(template).result(binding)
|
|
69
100
|
end
|
|
70
101
|
|
|
102
|
+
VALID_IVAR_PATTERN = /\A[a-z_][a-z0-9_]*\z/i
|
|
103
|
+
|
|
71
104
|
def render_partial(name, locals = {})
|
|
72
105
|
partial_path = File.join(PARTIALS_PATH, "#{name}.html.erb")
|
|
73
106
|
template = File.read(partial_path)
|
|
74
107
|
|
|
75
|
-
locals.each
|
|
108
|
+
locals.each do |key, value|
|
|
109
|
+
validate_variable_name!(key)
|
|
110
|
+
instance_variable_set("@#{key}", value)
|
|
111
|
+
end
|
|
76
112
|
|
|
77
113
|
ERB.new(template).result(binding)
|
|
78
114
|
end
|
|
79
115
|
|
|
116
|
+
def render_custom_visual(file_path)
|
|
117
|
+
return "" if file_path.nil? || file_path.empty?
|
|
118
|
+
|
|
119
|
+
source_dir = config&.source || "docs"
|
|
120
|
+
full_path = File.join(source_dir, file_path)
|
|
121
|
+
|
|
122
|
+
return "" unless File.exist?(full_path)
|
|
123
|
+
|
|
124
|
+
File.read(full_path)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def validate_variable_name!(name)
|
|
128
|
+
return if name.to_s.match?(VALID_IVAR_PATTERN)
|
|
129
|
+
|
|
130
|
+
raise ArgumentError, "Invalid variable name: #{name}"
|
|
131
|
+
end
|
|
132
|
+
|
|
80
133
|
def asset_path(path)
|
|
81
134
|
return path if path.nil? || path.start_with?("http://", "https://")
|
|
82
135
|
|
|
@@ -85,61 +138,14 @@ module Docyard
|
|
|
85
138
|
|
|
86
139
|
private
|
|
87
140
|
|
|
88
|
-
def assign_content_variables(content, page_title, navigation)
|
|
141
|
+
def assign_content_variables(content, page_title, navigation, raw_markdown)
|
|
89
142
|
@content = content
|
|
90
143
|
@page_title = page_title
|
|
91
144
|
@sidebar_html = navigation[:sidebar_html] || ""
|
|
92
145
|
@prev_next_html = navigation[:prev_next_html] || ""
|
|
93
146
|
@toc = navigation[:toc] || []
|
|
94
147
|
@breadcrumbs = navigation[:breadcrumbs]
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def assign_branding_variables(branding, current_path = "/")
|
|
98
|
-
assign_site_branding(branding)
|
|
99
|
-
assign_search_options(branding)
|
|
100
|
-
assign_credits_and_social(branding)
|
|
101
|
-
assign_tabs(branding, current_path)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def assign_site_branding(branding)
|
|
105
|
-
@site_title = branding[:site_title] || Constants::DEFAULT_SITE_TITLE
|
|
106
|
-
@site_description = branding[:site_description] || ""
|
|
107
|
-
@logo = branding[:logo] || Constants::DEFAULT_LOGO_PATH
|
|
108
|
-
@logo_dark = branding[:logo_dark]
|
|
109
|
-
@favicon = branding[:favicon] || Constants::DEFAULT_FAVICON_PATH
|
|
110
|
-
@has_custom_logo = branding[:has_custom_logo] || false
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def assign_search_options(branding)
|
|
114
|
-
@search_enabled = branding[:search_enabled].nil? || branding[:search_enabled]
|
|
115
|
-
@search_placeholder = branding[:search_placeholder] || "Search documentation..."
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def assign_credits_and_social(branding)
|
|
119
|
-
@credits = branding[:credits] != false
|
|
120
|
-
@copyright = branding[:copyright]
|
|
121
|
-
@social = branding[:social] || []
|
|
122
|
-
@header_ctas = branding[:header_ctas] || []
|
|
123
|
-
@announcement = branding[:announcement]
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def assign_tabs(branding, current_path)
|
|
127
|
-
tabs = branding[:tabs] || []
|
|
128
|
-
@tabs = tabs.map { |tab| tab.merge(active: tab_active?(tab[:href], current_path)) }
|
|
129
|
-
@has_tabs = branding[:has_tabs] || false
|
|
130
|
-
@current_path = current_path
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def tab_active?(tab_href, current_path)
|
|
134
|
-
return false if tab_href.nil? || current_path.nil?
|
|
135
|
-
return false if tab_href.start_with?("http://", "https://")
|
|
136
|
-
|
|
137
|
-
normalized_tab = tab_href.chomp("/")
|
|
138
|
-
normalized_current = current_path.chomp("/")
|
|
139
|
-
|
|
140
|
-
return true if normalized_tab == normalized_current
|
|
141
|
-
|
|
142
|
-
current_path.start_with?("#{normalized_tab}/")
|
|
148
|
+
@raw_markdown = raw_markdown
|
|
143
149
|
end
|
|
144
150
|
|
|
145
151
|
def assign_template_variables(template_options)
|
|
@@ -160,5 +166,27 @@ module Docyard
|
|
|
160
166
|
def strip_md_from_links(html)
|
|
161
167
|
html.gsub(/href="([^"]+)\.md"/, 'href="\1"')
|
|
162
168
|
end
|
|
169
|
+
|
|
170
|
+
def assign_git_info(branding, file_path)
|
|
171
|
+
@show_edit_link = branding[:show_edit_link] && file_path
|
|
172
|
+
@show_last_updated = branding[:show_last_updated] && file_path
|
|
173
|
+
return unless @show_edit_link || @show_last_updated
|
|
174
|
+
|
|
175
|
+
git_info = Utils::GitInfo.new(
|
|
176
|
+
repo_url: branding[:repo_url],
|
|
177
|
+
branch: branding[:repo_branch],
|
|
178
|
+
edit_path: branding[:repo_edit_path]
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
@edit_url = git_info.edit_url(file_path) if @show_edit_link
|
|
182
|
+
@last_updated = git_info.last_updated(file_path) if @show_last_updated
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def assign_feedback_variables
|
|
186
|
+
return unless config
|
|
187
|
+
|
|
188
|
+
@feedback_enabled = config.feedback.enabled
|
|
189
|
+
@feedback_question = config.feedback.question
|
|
190
|
+
end
|
|
163
191
|
end
|
|
164
192
|
end
|
|
@@ -117,6 +117,7 @@ module Docyard
|
|
|
117
117
|
tagline: hero["tagline"],
|
|
118
118
|
gradient: hero.fetch("gradient", true),
|
|
119
119
|
image: symbolize_image(hero["image"]),
|
|
120
|
+
custom_visual: symbolize_custom_visual(hero["custom_visual"]),
|
|
120
121
|
actions: symbolize_actions(hero["actions"])
|
|
121
122
|
}.compact
|
|
122
123
|
end
|
|
@@ -138,6 +139,19 @@ module Docyard
|
|
|
138
139
|
end
|
|
139
140
|
end
|
|
140
141
|
|
|
142
|
+
def symbolize_custom_visual(custom_visual)
|
|
143
|
+
return nil if custom_visual.nil?
|
|
144
|
+
|
|
145
|
+
if custom_visual.is_a?(String)
|
|
146
|
+
{ html: custom_visual, placement: "side" }
|
|
147
|
+
elsif custom_visual.is_a?(Hash)
|
|
148
|
+
{
|
|
149
|
+
html: custom_visual["html"],
|
|
150
|
+
placement: custom_visual["placement"] || "side"
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
141
155
|
def symbolize_actions(actions)
|
|
142
156
|
return nil unless actions.is_a?(Array)
|
|
143
157
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../utils/path_utils"
|
|
4
|
+
|
|
3
5
|
module Docyard
|
|
4
6
|
module Routing
|
|
5
7
|
class FallbackResolver
|
|
@@ -29,9 +31,7 @@ module Docyard
|
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def sanitize_path(request_path)
|
|
32
|
-
|
|
33
|
-
clean = "index" if clean.empty?
|
|
34
|
-
clean.delete_suffix(".md")
|
|
34
|
+
Utils::PathUtils.sanitize_url_path(request_path)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def find_first_item_in_section(request_path)
|