docyard 0.8.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 +67 -1
- 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/aliases.rb +12 -0
- data/lib/docyard/components/processors/abbreviation_processor.rb +72 -0
- data/lib/docyard/components/processors/accordion_processor.rb +81 -0
- data/lib/docyard/components/processors/badge_processor.rb +72 -0
- data/lib/docyard/components/processors/callout_processor.rb +9 -3
- data/lib/docyard/components/processors/cards_processor.rb +100 -0
- 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 +34 -3
- data/lib/docyard/components/processors/code_block_processor.rb +11 -24
- data/lib/docyard/components/processors/code_group_processor.rb +182 -0
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +7 -1
- data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +150 -0
- data/lib/docyard/components/processors/icon_processor.rb +8 -2
- data/lib/docyard/components/processors/image_caption_processor.rb +96 -0
- data/lib/docyard/components/processors/include_processor.rb +86 -0
- data/lib/docyard/components/processors/steps_processor.rb +89 -0
- data/lib/docyard/components/processors/tabs_processor.rb +9 -1
- data/lib/docyard/components/processors/tooltip_processor.rb +57 -0
- data/lib/docyard/components/processors/video_embed_processor.rb +207 -0
- 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 +118 -0
- data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
- 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 +84 -58
- data/lib/docyard/config/key_validator.rb +30 -0
- data/lib/docyard/config/logo_detector.rb +39 -0
- 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 +45 -96
- 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/item.rb +6 -1
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +69 -3
- data/lib/docyard/navigation/sidebar/renderer.rb +18 -3
- 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 +20 -15
- data/lib/docyard/rendering/og_helpers.rb +36 -0
- data/lib/docyard/rendering/renderer.rb +87 -58
- 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 +40 -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/abbreviation.css +86 -0
- data/lib/docyard/templates/assets/css/components/accordion.css +138 -0
- data/lib/docyard/templates/assets/css/components/badges.css +47 -0
- data/lib/docyard/templates/assets/css/components/banner.css +233 -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/cards.css +100 -0
- data/lib/docyard/templates/assets/css/components/code-block.css +14 -2
- data/lib/docyard/templates/assets/css/components/code-group.css +294 -0
- data/lib/docyard/templates/assets/css/components/feedback.css +126 -0
- data/lib/docyard/templates/assets/css/components/figure.css +22 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +125 -0
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +21 -13
- data/lib/docyard/templates/assets/css/components/icon.css +5 -0
- data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
- data/lib/docyard/templates/assets/css/components/nav-menu.css +20 -4
- data/lib/docyard/templates/assets/css/components/navigation.css +32 -3
- data/lib/docyard/templates/assets/css/components/page-actions.css +131 -0
- data/lib/docyard/templates/assets/css/components/prev-next.css +20 -22
- data/lib/docyard/templates/assets/css/components/search.css +6 -10
- data/lib/docyard/templates/assets/css/components/steps.css +122 -0
- 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 +13 -5
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +3 -1
- data/lib/docyard/templates/assets/css/components/tooltip.css +113 -0
- data/lib/docyard/templates/assets/css/components/video.css +41 -0
- 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 +25 -3
- data/lib/docyard/templates/assets/css/variables.css +13 -1
- data/lib/docyard/templates/assets/js/components/abbreviation.js +85 -0
- data/lib/docyard/templates/assets/js/components/banner.js +81 -0
- data/lib/docyard/templates/assets/js/components/code-group.js +286 -0
- 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 +39 -0
- data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
- 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 +118 -0
- 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 +11 -0
- data/lib/docyard/templates/layouts/splash.html.erb +15 -1
- data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
- data/lib/docyard/templates/partials/_analytics.html.erb +24 -0
- data/lib/docyard/templates/partials/_banner.html.erb +27 -0
- data/lib/docyard/templates/partials/_card.html.erb +23 -0
- 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/_nav_group.html.erb +6 -0
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +3 -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/_step.html.erb +14 -0
- 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 +114 -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 -78
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +0 -78
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +0 -69
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +0 -47
- 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 -139
- 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 -90
- 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,7 +2,7 @@
|
|
|
2
2
|
<% if has_title %>
|
|
3
3
|
<div class="docyard-code-block__header">
|
|
4
4
|
<% if @icon %>
|
|
5
|
-
<span class="docyard-code-block__icon"><% if @icon_source == "file-extension" %><%= icon_file_extension(@icon) %><% elsif @icon_source == "phosphor" %><%= icon(@icon) %><% end %></span>
|
|
5
|
+
<span class="docyard-code-block__icon"><% if @icon_source == "language" %><%= icon_for_language(@icon) %><% elsif @icon_source == "file-extension" %><%= icon_file_extension(@icon) %><% elsif @icon_source == "phosphor" %><%= icon(@icon) %><% end %></span>
|
|
6
6
|
<% end %>
|
|
7
7
|
<span class="docyard-code-block__title" title="<%= @title %>"><%= @title %></span>
|
|
8
8
|
<button class="docyard-code-block__copy" aria-label="Copy code to clipboard" data-code="<%= @code_text %>">
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<% if @feedback_enabled %>
|
|
2
|
+
<div class="feedback" data-pagefind-ignore>
|
|
3
|
+
<p class="feedback__question"><%= @feedback_question %></p>
|
|
4
|
+
<div class="feedback__buttons">
|
|
5
|
+
<button type="button" class="feedback__btn" data-feedback="yes" aria-label="Yes, this page was helpful">
|
|
6
|
+
<%= icon(:thumbs_up) %>
|
|
7
|
+
</button>
|
|
8
|
+
<button type="button" class="feedback__btn" data-feedback="no" aria-label="No, this page was not helpful">
|
|
9
|
+
<%= icon(:thumbs_down) %>
|
|
10
|
+
</button>
|
|
11
|
+
</div>
|
|
12
|
+
<p class="feedback__thanks" aria-live="polite" hidden>Thanks for your feedback!</p>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
%>
|
|
18
18
|
<div class="<%= footer_class %>">
|
|
19
19
|
<% if @credits %>
|
|
20
|
-
<a href="https://docyard.
|
|
20
|
+
<a href="https://docyard.dev" target="_blank" rel="noopener noreferrer" class="footer-attribution">Built with Docyard</a>
|
|
21
21
|
<% end %>
|
|
22
22
|
|
|
23
23
|
<% if has_footer_links %>
|
|
@@ -5,18 +5,93 @@
|
|
|
5
5
|
<title><%= @page_title %> | <%= @site_title %></title>
|
|
6
6
|
<link rel="icon" href="<%= asset_path(@favicon) %>" type="image/svg+xml">
|
|
7
7
|
|
|
8
|
+
<% if @og_enabled %>
|
|
9
|
+
<link rel="canonical" href="<%= @og_url %>">
|
|
10
|
+
|
|
11
|
+
<meta property="og:type" content="website">
|
|
12
|
+
<meta property="og:site_name" content="<%= @site_title %>">
|
|
13
|
+
<meta property="og:title" content="<%= @page_title %>">
|
|
14
|
+
<meta property="og:url" content="<%= @og_url %>">
|
|
15
|
+
<% if @og_description && !@og_description.empty? %>
|
|
16
|
+
<meta property="og:description" content="<%= @og_description %>">
|
|
17
|
+
<% end %>
|
|
18
|
+
<% if @og_image %>
|
|
19
|
+
<meta property="og:image" content="<%= @og_image %>">
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<meta name="twitter:card" content="<%= @og_image ? 'summary_large_image' : 'summary' %>">
|
|
23
|
+
<meta name="twitter:title" content="<%= @page_title %>">
|
|
24
|
+
<% if @og_description && !@og_description.empty? %>
|
|
25
|
+
<meta name="twitter:description" content="<%= @og_description %>">
|
|
26
|
+
<% end %>
|
|
27
|
+
<% if @og_image %>
|
|
28
|
+
<meta name="twitter:image" content="<%= @og_image %>">
|
|
29
|
+
<% end %>
|
|
30
|
+
<% if @og_twitter %>
|
|
31
|
+
<meta name="twitter:site" content="@<%= @og_twitter.delete_prefix('@') %>">
|
|
32
|
+
<% end %>
|
|
33
|
+
<% end %>
|
|
34
|
+
|
|
8
35
|
<link rel="preload" href="/_docyard/fonts/Inter-Variable.ttf" as="font" type="font/ttf" crossorigin>
|
|
9
36
|
|
|
37
|
+
<%= render_partial('_icon_library') %>
|
|
38
|
+
|
|
10
39
|
<script>
|
|
11
40
|
(function() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
41
|
+
var html = document.documentElement;
|
|
42
|
+
|
|
43
|
+
html.classList.add('no-transition');
|
|
44
|
+
|
|
45
|
+
var theme = localStorage.getItem('theme') ||
|
|
46
|
+
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
47
|
+
html.classList.toggle('dark', theme === 'dark');
|
|
15
48
|
|
|
16
49
|
if (localStorage.getItem('docyard_sidebar_collapsed') === 'true') {
|
|
17
|
-
|
|
50
|
+
html.classList.add('sidebar-collapsed');
|
|
18
51
|
}
|
|
52
|
+
|
|
53
|
+
var BANNER_KEY = 'docyard-announcement-dismissed';
|
|
54
|
+
try {
|
|
55
|
+
var dismissed = localStorage.getItem(BANNER_KEY);
|
|
56
|
+
if (dismissed) {
|
|
57
|
+
var dismissedAt = parseInt(dismissed, 10);
|
|
58
|
+
var sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
|
|
59
|
+
if (dismissedAt > sevenDaysAgo) {
|
|
60
|
+
html.classList.add('banner-dismissed');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {}
|
|
64
|
+
|
|
65
|
+
requestAnimationFrame(function() {
|
|
66
|
+
requestAnimationFrame(function() {
|
|
67
|
+
html.classList.remove('no-transition');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
19
70
|
})();
|
|
20
71
|
</script>
|
|
21
72
|
|
|
22
73
|
<link rel="stylesheet" href="/_docyard/css/main.css">
|
|
74
|
+
|
|
75
|
+
<% if @primary_color && (@primary_color[:light] || @primary_color[:dark]) %>
|
|
76
|
+
<style>
|
|
77
|
+
<% light_color = @primary_color[:light] %>
|
|
78
|
+
<% dark_color = @primary_color[:dark] || light_color %>
|
|
79
|
+
<% if light_color %>
|
|
80
|
+
:root {
|
|
81
|
+
--primary: <%= light_color %>;
|
|
82
|
+
--sidebar-primary: <%= light_color %>;
|
|
83
|
+
}
|
|
84
|
+
.dark {
|
|
85
|
+
--primary: <%= dark_color %>;
|
|
86
|
+
--sidebar-primary: <%= dark_color %>;
|
|
87
|
+
}
|
|
88
|
+
<% elsif dark_color %>
|
|
89
|
+
.dark {
|
|
90
|
+
--primary: <%= dark_color %>;
|
|
91
|
+
--sidebar-primary: <%= dark_color %>;
|
|
92
|
+
}
|
|
93
|
+
<% end %>
|
|
94
|
+
</style>
|
|
95
|
+
<% end %>
|
|
96
|
+
|
|
97
|
+
<%= render_partial('_analytics') %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/regular/style.css">
|
|
2
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/bold/style.css">
|
|
3
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/fill/style.css">
|
|
4
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/light/style.css">
|
|
5
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/thin/style.css">
|
|
6
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/duotone/style.css">
|
|
7
|
+
|
|
8
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/devicons/devicon@latest/devicon.min.css">
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
<span class="nav-item-icon"><%= icon(@icon) %></span>
|
|
8
8
|
<% end %>
|
|
9
9
|
<span class="nav-item-text"><%= @title %></span>
|
|
10
|
+
<% if @badge %>
|
|
11
|
+
<span class="docyard-badge docyard-badge--<%= @badge_type || 'default' %>"><%= @badge %></span>
|
|
12
|
+
<% end %>
|
|
10
13
|
</span>
|
|
11
14
|
<span class="nav-group-icon"><%= icon(:caret_right) %></span>
|
|
12
15
|
</a>
|
|
@@ -17,6 +20,9 @@
|
|
|
17
20
|
<span class="nav-item-icon"><%= icon(@icon) %></span>
|
|
18
21
|
<% end %>
|
|
19
22
|
<span class="nav-item-text"><%= @title %></span>
|
|
23
|
+
<% if @badge %>
|
|
24
|
+
<span class="docyard-badge docyard-badge--<%= @badge_type || 'default' %>"><%= @badge %></span>
|
|
25
|
+
<% end %>
|
|
20
26
|
</span>
|
|
21
27
|
<span class="nav-group-icon"><%= icon(:caret_right) %></span>
|
|
22
28
|
</button>
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
<span class="nav-item-icon"><%= icon(@icon) %></span>
|
|
4
4
|
<% end %>
|
|
5
5
|
<span class="nav-item-text"><%= @title %></span>
|
|
6
|
+
<% if @badge %>
|
|
7
|
+
<span class="docyard-badge docyard-badge--<%= @badge_type || 'default' %>"><%= @badge %></span>
|
|
8
|
+
<% end %>
|
|
6
9
|
<% if @target == "_blank" %>
|
|
7
10
|
<span class="nav-item-external"><%= icon(:link_external) %></span>
|
|
8
11
|
<% end %>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<% if @raw_markdown || @show_edit_link || @show_last_updated %>
|
|
2
|
+
<div class="page-actions">
|
|
3
|
+
<% if @raw_markdown %>
|
|
4
|
+
<button type="button" class="page-actions__copy-btn" data-copy-page>
|
|
5
|
+
<%= icon(:copy) %>
|
|
6
|
+
<span class="page-actions__copy-text">Copy page</span>
|
|
7
|
+
</button>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if @show_edit_link && @edit_url %>
|
|
10
|
+
<a href="<%= @edit_url %>" class="page-actions__edit-link" target="_blank" rel="noopener noreferrer">
|
|
11
|
+
<%= icon(:pencil_simple_line) %>
|
|
12
|
+
Edit this page
|
|
13
|
+
</a>
|
|
14
|
+
<% end %>
|
|
15
|
+
<% if @show_last_updated && @last_updated %>
|
|
16
|
+
<div class="page-actions__last-updated">
|
|
17
|
+
Last updated <time datetime="<%= @last_updated[:iso] %>" title="<%= @last_updated[:formatted] %>"><%= @last_updated[:relative] %></time>
|
|
18
|
+
</div>
|
|
19
|
+
<% end %>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<%= render_partial('_search_modal') %>
|
|
3
3
|
<% end %>
|
|
4
4
|
|
|
5
|
-
<script src="/_docyard/js/theme.js"></script>
|
|
6
|
-
<script src="/_docyard/js/components.js"></script>
|
|
7
|
-
|
|
5
|
+
<script src="/_docyard/js/theme.js" defer></script>
|
|
6
|
+
<script src="/_docyard/js/components.js" defer></script>
|
|
7
|
+
<% if @dev_mode %>
|
|
8
|
+
<script>window.__DOCYARD_SSE_PORT__ = <%= @sse_port %>;</script>
|
|
9
|
+
<script src="/_docyard/js/hot-reload.js" defer></script>
|
|
10
|
+
<% end %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="docyard-step<%= ' docyard-step--last' if @is_last %>">
|
|
2
|
+
<div class="docyard-step__indicator">
|
|
3
|
+
<span class="docyard-step__number"><%= @number %></span>
|
|
4
|
+
<% unless @is_last %><div class="docyard-step__connector"></div><% end %>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="docyard-step__content">
|
|
7
|
+
<h3 class="docyard-step__title"><%= @title %></h3>
|
|
8
|
+
<% unless @content_html.empty? %>
|
|
9
|
+
<div class="docyard-step__body">
|
|
10
|
+
<%= @content_html %>
|
|
11
|
+
</div>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
id="tab-<%= @group_id %>-<%= index %>"
|
|
10
10
|
class="docyard-tabs__tab"
|
|
11
11
|
tabindex="<%= index == 0 ? '0' : '-1' %>"
|
|
12
|
+
data-tab-name="<%= tab[:name].downcase %>"
|
|
12
13
|
>
|
|
13
14
|
<% if tab[:icon] %>
|
|
14
15
|
<span class="docyard-tabs__icon">
|
|
15
|
-
<% if tab[:icon_source] == "
|
|
16
|
+
<% if tab[:icon_source] == "language" %>
|
|
17
|
+
<%= icon_for_language(tab[:icon]) %>
|
|
18
|
+
<% elsif tab[:icon_source] == "file-extension" %>
|
|
16
19
|
<%= icon_file_extension(tab[:icon]) %>
|
|
17
20
|
<% elsif tab[:icon_source] == "phosphor" %>
|
|
18
21
|
<%= icon(tab[:icon]) %>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Utils
|
|
7
|
+
class GitInfo
|
|
8
|
+
TIME_UNITS = [
|
|
9
|
+
[60, "minute"],
|
|
10
|
+
[3600, "hour"],
|
|
11
|
+
[86_400, "day"],
|
|
12
|
+
[604_800, "week"],
|
|
13
|
+
[2_592_000, "month"],
|
|
14
|
+
[31_536_000, "year"]
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
attr_accessor :timestamp_cache
|
|
19
|
+
|
|
20
|
+
def prefetch_timestamps(docs_path = "docs")
|
|
21
|
+
return unless git_repository?
|
|
22
|
+
|
|
23
|
+
@timestamp_cache = fetch_all_timestamps(docs_path)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear_cache
|
|
27
|
+
@timestamp_cache = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def cached_timestamp(file_path)
|
|
31
|
+
return nil unless @timestamp_cache
|
|
32
|
+
|
|
33
|
+
@timestamp_cache[file_path]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def git_repository?
|
|
37
|
+
File.directory?(".git") || system("git", "rev-parse", "--git-dir", out: File::NULL, err: File::NULL)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def fetch_all_timestamps(docs_path)
|
|
43
|
+
output, _, status = Open3.capture3("git", "log", "--pretty=format:%cI", "--name-only", "--", "#{docs_path}/")
|
|
44
|
+
return {} unless status.success?
|
|
45
|
+
|
|
46
|
+
parse_git_log_output(output)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def parse_git_log_output(output)
|
|
50
|
+
timestamps = {}
|
|
51
|
+
current_timestamp = nil
|
|
52
|
+
|
|
53
|
+
output.each_line do |line|
|
|
54
|
+
line = line.strip
|
|
55
|
+
next if line.empty?
|
|
56
|
+
|
|
57
|
+
if line.match?(/^\d{4}-\d{2}-\d{2}T/)
|
|
58
|
+
current_timestamp = Time.parse(line)
|
|
59
|
+
elsif current_timestamp && !timestamps.key?(line)
|
|
60
|
+
timestamps[line] = current_timestamp
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
timestamps
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
attr_reader :repo_url, :branch, :edit_path
|
|
69
|
+
|
|
70
|
+
def initialize(repo_url:, branch: "main", edit_path: "docs")
|
|
71
|
+
@repo_url = repo_url
|
|
72
|
+
@branch = branch
|
|
73
|
+
@edit_path = edit_path
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def edit_url(file_path)
|
|
77
|
+
return nil unless repo_url
|
|
78
|
+
|
|
79
|
+
relative_path = extract_relative_path(file_path)
|
|
80
|
+
return nil unless relative_path
|
|
81
|
+
|
|
82
|
+
normalized_url = repo_url.chomp("/")
|
|
83
|
+
"#{normalized_url}/edit/#{branch}/#{edit_path}/#{relative_path}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def last_updated(file_path)
|
|
87
|
+
return nil unless file_path && File.exist?(file_path)
|
|
88
|
+
return nil unless self.class.git_repository?
|
|
89
|
+
|
|
90
|
+
timestamp = git_last_commit_time(file_path)
|
|
91
|
+
return nil unless timestamp
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
time: timestamp,
|
|
95
|
+
iso: timestamp.iso8601,
|
|
96
|
+
formatted: format_datetime(timestamp),
|
|
97
|
+
formatted_short: format_date_short(timestamp),
|
|
98
|
+
relative: relative_time(timestamp)
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def extract_relative_path(file_path)
|
|
105
|
+
return nil unless file_path
|
|
106
|
+
|
|
107
|
+
match = file_path.match(%r{#{Regexp.escape(edit_path)}/(.+)$})
|
|
108
|
+
match ? match[1] : nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def git_last_commit_time(file_path)
|
|
112
|
+
cached = self.class.cached_timestamp(file_path)
|
|
113
|
+
return cached if cached
|
|
114
|
+
|
|
115
|
+
fetch_single_timestamp(file_path)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def fetch_single_timestamp(file_path)
|
|
119
|
+
output, _, status = Open3.capture3("git", "log", "-1", "--format=%cI", "--", file_path)
|
|
120
|
+
return nil unless status.success?
|
|
121
|
+
return nil if output.strip.empty?
|
|
122
|
+
|
|
123
|
+
Time.parse(output.strip)
|
|
124
|
+
rescue ArgumentError
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def format_datetime(time)
|
|
129
|
+
time.strftime("%B %-d, %Y at %-I:%M %p")
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def format_date_short(time)
|
|
133
|
+
time.strftime("%b %-d, %Y")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def relative_time(time)
|
|
137
|
+
seconds = Time.now - time
|
|
138
|
+
return "just now" if seconds < TIME_UNITS.first.first
|
|
139
|
+
|
|
140
|
+
divisor, unit = find_time_unit(seconds)
|
|
141
|
+
pluralize((seconds / divisor).to_i, unit)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def find_time_unit(seconds)
|
|
145
|
+
TIME_UNITS.reverse_each do |threshold, unit|
|
|
146
|
+
return [threshold, unit] if seconds >= threshold
|
|
147
|
+
end
|
|
148
|
+
TIME_UNITS.first
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def pluralize(count, word)
|
|
152
|
+
suffix = count == 1 ? "" : "s"
|
|
153
|
+
"#{count} #{word}#{suffix} ago"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Utils
|
|
5
|
+
module HashUtils
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def deep_merge(hash1, hash2)
|
|
9
|
+
hash1.merge(hash2) do |_key, v1, v2|
|
|
10
|
+
if v2.nil?
|
|
11
|
+
v1
|
|
12
|
+
elsif v1.is_a?(Hash) && v2.is_a?(Hash)
|
|
13
|
+
deep_merge(v1, v2)
|
|
14
|
+
else
|
|
15
|
+
v2
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def deep_dup(hash)
|
|
21
|
+
hash.transform_values do |value|
|
|
22
|
+
case value
|
|
23
|
+
when Hash then deep_dup(value)
|
|
24
|
+
when Array then value.map { |v| v.is_a?(Hash) ? deep_dup(v) : v }
|
|
25
|
+
else value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
module Docyard
|
|
4
4
|
module Utils
|
|
5
5
|
module HtmlHelpers
|
|
6
|
+
def escape_html(text)
|
|
7
|
+
text.to_s
|
|
8
|
+
.gsub("&", "&")
|
|
9
|
+
.gsub("<", "<")
|
|
10
|
+
.gsub(">", ">")
|
|
11
|
+
.gsub('"', """)
|
|
12
|
+
end
|
|
13
|
+
|
|
6
14
|
def escape_html_attribute(text)
|
|
7
15
|
text.gsub('"', """)
|
|
8
16
|
.gsub("'", "'")
|
|
@@ -15,6 +15,32 @@ module Docyard
|
|
|
15
15
|
logger.level = Logger.const_get(level.to_s.upcase)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def start_buffering
|
|
19
|
+
@buffered_warnings = []
|
|
20
|
+
@buffering = true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def stop_buffering
|
|
24
|
+
@buffering = false
|
|
25
|
+
warnings = @buffered_warnings || []
|
|
26
|
+
@buffered_warnings = []
|
|
27
|
+
warnings
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def buffering?
|
|
31
|
+
@buffering == true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def buffer_warning(message)
|
|
35
|
+
@buffered_warnings ||= []
|
|
36
|
+
@buffered_warnings << message
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def flush_warnings
|
|
40
|
+
warnings = stop_buffering
|
|
41
|
+
warnings.each { |msg| logger.warn(msg) }
|
|
42
|
+
end
|
|
43
|
+
|
|
18
44
|
private
|
|
19
45
|
|
|
20
46
|
def default_logger
|
|
@@ -25,9 +51,24 @@ module Docyard
|
|
|
25
51
|
end
|
|
26
52
|
|
|
27
53
|
def log_formatter
|
|
28
|
-
proc do |severity,
|
|
29
|
-
|
|
30
|
-
|
|
54
|
+
proc do |severity, _datetime, _progname, msg|
|
|
55
|
+
if severity == "WARN" && buffering?
|
|
56
|
+
buffer_warning(msg)
|
|
57
|
+
nil
|
|
58
|
+
else
|
|
59
|
+
format_message(severity, msg)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def format_message(severity, msg)
|
|
65
|
+
case severity
|
|
66
|
+
when "DEBUG"
|
|
67
|
+
"[DEBUG] #{msg}\n"
|
|
68
|
+
when "INFO"
|
|
69
|
+
"#{msg}\n"
|
|
70
|
+
else
|
|
71
|
+
"[#{severity}] #{msg}\n"
|
|
31
72
|
end
|
|
32
73
|
end
|
|
33
74
|
end
|
|
@@ -16,16 +16,6 @@ module Docyard
|
|
|
16
16
|
normalized = "/#{normalized}" unless normalized.start_with?("/")
|
|
17
17
|
normalized
|
|
18
18
|
end
|
|
19
|
-
|
|
20
|
-
def self.to_url(relative_path)
|
|
21
|
-
normalize(relative_path)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def self.ancestor?(parent_path, child_path)
|
|
25
|
-
return false if parent_path.nil?
|
|
26
|
-
|
|
27
|
-
child_path.start_with?(parent_path) && child_path != parent_path
|
|
28
|
-
end
|
|
29
19
|
end
|
|
30
20
|
end
|
|
31
21
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Docyard
|
|
6
|
+
module Utils
|
|
7
|
+
module PathUtils
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def sanitize_url_path(request_path)
|
|
11
|
+
decoded = decode_path(request_path)
|
|
12
|
+
clean = decoded.delete_prefix("/").delete_suffix("/")
|
|
13
|
+
clean = "index" if clean.empty?
|
|
14
|
+
clean.delete_suffix(".md")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def safe_path?(requested_path, base_dir)
|
|
18
|
+
return false if requested_path.nil? || base_dir.nil?
|
|
19
|
+
|
|
20
|
+
expanded_base = File.expand_path(base_dir)
|
|
21
|
+
expanded_path = File.expand_path(requested_path, base_dir)
|
|
22
|
+
expanded_path.start_with?("#{expanded_base}/") || expanded_path == expanded_base
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def resolve_safe_path(relative_path, base_dir)
|
|
26
|
+
return nil if relative_path.nil? || base_dir.nil?
|
|
27
|
+
|
|
28
|
+
decoded = decode_path(relative_path)
|
|
29
|
+
full_path = File.join(base_dir, decoded)
|
|
30
|
+
expanded = File.expand_path(full_path)
|
|
31
|
+
expanded_base = File.expand_path(base_dir)
|
|
32
|
+
|
|
33
|
+
return nil unless expanded.start_with?("#{expanded_base}/")
|
|
34
|
+
|
|
35
|
+
expanded
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def decode_path(path)
|
|
39
|
+
decoded = URI.decode_www_form_component(path.to_s)
|
|
40
|
+
decoded.gsub(/\\+/, "/")
|
|
41
|
+
rescue ArgumentError
|
|
42
|
+
path.to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def markdown_file_to_url(file_path, docs_path)
|
|
46
|
+
relative_path = file_path.delete_prefix("#{docs_path}/")
|
|
47
|
+
relative_path_to_url(relative_path)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def relative_path_to_url(relative_path)
|
|
51
|
+
base_name = File.basename(relative_path, ".md")
|
|
52
|
+
dir_name = File.dirname(relative_path)
|
|
53
|
+
|
|
54
|
+
if base_name == "index"
|
|
55
|
+
dir_name == "." ? "/" : "/#{dir_name}"
|
|
56
|
+
else
|
|
57
|
+
dir_name == "." ? "/#{base_name}" : "/#{dir_name}/#{base_name}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def markdown_to_html_output(relative_path, output_dir)
|
|
62
|
+
base_name = File.basename(relative_path, ".md")
|
|
63
|
+
dir_name = File.dirname(relative_path)
|
|
64
|
+
|
|
65
|
+
if base_name == "index"
|
|
66
|
+
File.join(output_dir, dir_name, "index.html")
|
|
67
|
+
else
|
|
68
|
+
File.join(output_dir, dir_name, base_name, "index.html")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/docyard/version.rb
CHANGED
data/lib/docyard.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "docyard/version"
|
|
4
|
-
require_relative "docyard/
|
|
5
|
-
require_relative "docyard/
|
|
4
|
+
require_relative "docyard/constants"
|
|
5
|
+
require_relative "docyard/errors"
|
|
6
6
|
require_relative "docyard/utils/logging"
|
|
7
7
|
|
|
8
8
|
require_relative "docyard/utils/text_formatter"
|