docyard 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +43 -1
- data/lib/docyard/build/asset_bundler.rb +22 -7
- data/lib/docyard/build/file_copier.rb +49 -27
- data/lib/docyard/build/sitemap_generator.rb +6 -6
- data/lib/docyard/build/static_generator.rb +85 -12
- data/lib/docyard/builder.rb +6 -6
- 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 +8 -2
- data/lib/docyard/components/processors/cards_processor.rb +100 -0
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +23 -2
- data/lib/docyard/components/processors/code_block_processor.rb +6 -0
- data/lib/docyard/components/processors/code_group_processor.rb +198 -0
- data/lib/docyard/components/processors/code_snippet_import_preprocessor.rb +6 -1
- data/lib/docyard/components/processors/custom_anchor_processor.rb +42 -0
- data/lib/docyard/components/processors/file_tree_processor.rb +151 -0
- 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 +196 -0
- data/lib/docyard/components/support/code_group/html_builder.rb +122 -0
- data/lib/docyard/components/support/markdown_code_block_helper.rb +56 -0
- data/lib/docyard/config/branding_resolver.rb +121 -17
- data/lib/docyard/config/constants.rb +6 -4
- data/lib/docyard/config/logo_detector.rb +39 -0
- data/lib/docyard/config/validator.rb +122 -99
- data/lib/docyard/config.rb +40 -42
- data/lib/docyard/initializer.rb +15 -76
- data/lib/docyard/navigation/breadcrumb_builder.rb +133 -0
- data/lib/docyard/navigation/prev_next_builder.rb +4 -1
- data/lib/docyard/navigation/sidebar/children_discoverer.rb +51 -0
- data/lib/docyard/navigation/sidebar/config_parser.rb +136 -108
- data/lib/docyard/navigation/sidebar/file_resolver.rb +90 -0
- data/lib/docyard/navigation/sidebar/file_system_scanner.rb +2 -1
- data/lib/docyard/navigation/sidebar/item.rb +50 -7
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +51 -0
- data/lib/docyard/navigation/sidebar/metadata_extractor.rb +71 -0
- data/lib/docyard/navigation/sidebar/metadata_reader.rb +51 -0
- data/lib/docyard/navigation/sidebar/path_prefixer.rb +34 -0
- data/lib/docyard/navigation/sidebar/renderer.rb +60 -38
- data/lib/docyard/navigation/sidebar/sorter.rb +21 -0
- data/lib/docyard/navigation/sidebar/tree_builder.rb +100 -26
- data/lib/docyard/navigation/sidebar/tree_filter.rb +55 -0
- data/lib/docyard/navigation/sidebar_builder.rb +105 -36
- data/lib/docyard/rendering/icon_helpers.rb +13 -0
- data/lib/docyard/rendering/icons/phosphor.rb +26 -1
- data/lib/docyard/rendering/markdown.rb +29 -1
- data/lib/docyard/rendering/renderer.rb +75 -34
- data/lib/docyard/rendering/template_resolver.rb +172 -0
- data/lib/docyard/routing/fallback_resolver.rb +92 -0
- data/lib/docyard/search/build_indexer.rb +1 -1
- data/lib/docyard/search/dev_indexer.rb +51 -6
- data/lib/docyard/search/pagefind_support.rb +2 -0
- data/lib/docyard/server/asset_handler.rb +25 -19
- data/lib/docyard/server/pagefind_handler.rb +63 -0
- data/lib/docyard/server/preview_server.rb +1 -1
- data/lib/docyard/server/rack_application.rb +81 -64
- data/lib/docyard/templates/assets/css/code.css +18 -51
- 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 +202 -0
- data/lib/docyard/templates/assets/css/components/breadcrumbs.css +143 -0
- data/lib/docyard/templates/assets/css/components/callout.css +67 -67
- data/lib/docyard/templates/assets/css/components/cards.css +100 -0
- data/lib/docyard/templates/assets/css/components/code-block.css +190 -282
- data/lib/docyard/templates/assets/css/components/code-group.css +281 -0
- data/lib/docyard/templates/assets/css/components/figure.css +22 -0
- data/lib/docyard/templates/assets/css/components/file-tree.css +124 -0
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +36 -15
- data/lib/docyard/templates/assets/css/components/icon.css +0 -1
- data/lib/docyard/templates/assets/css/components/lightbox.css +65 -0
- data/lib/docyard/templates/assets/css/components/logo.css +0 -2
- data/lib/docyard/templates/assets/css/components/nav-menu.css +237 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +193 -167
- data/lib/docyard/templates/assets/css/components/prev-next.css +68 -48
- data/lib/docyard/templates/assets/css/components/search.css +186 -174
- data/lib/docyard/templates/assets/css/components/steps.css +122 -0
- data/lib/docyard/templates/assets/css/components/tab-bar.css +163 -0
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +127 -114
- data/lib/docyard/templates/assets/css/components/tabs.css +119 -160
- data/lib/docyard/templates/assets/css/components/theme-toggle.css +48 -44
- 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 +815 -0
- data/lib/docyard/templates/assets/css/layout.css +489 -87
- data/lib/docyard/templates/assets/css/main.css +1 -3
- data/lib/docyard/templates/assets/css/markdown.css +113 -93
- data/lib/docyard/templates/assets/css/reset.css +0 -3
- data/lib/docyard/templates/assets/css/typography.css +43 -41
- data/lib/docyard/templates/assets/css/variables.css +268 -208
- data/lib/docyard/templates/assets/favicon.svg +7 -8
- data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
- 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-block.js +24 -42
- data/lib/docyard/templates/assets/js/components/code-group.js +283 -0
- data/lib/docyard/templates/assets/js/components/file-tree.js +39 -0
- data/lib/docyard/templates/assets/js/components/heading-anchor.js +26 -24
- data/lib/docyard/templates/assets/js/components/lightbox.js +72 -0
- data/lib/docyard/templates/assets/js/components/navigation.js +181 -70
- data/lib/docyard/templates/assets/js/components/search.js +0 -75
- data/lib/docyard/templates/assets/js/components/sidebar-toggle.js +29 -0
- data/lib/docyard/templates/assets/js/components/tab-navigation.js +145 -0
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +153 -66
- data/lib/docyard/templates/assets/js/components/tabs.js +31 -69
- data/lib/docyard/templates/assets/js/components/tooltip.js +118 -0
- data/lib/docyard/templates/assets/js/theme.js +0 -3
- data/lib/docyard/templates/assets/logo-dark.svg +8 -2
- data/lib/docyard/templates/assets/logo.svg +7 -4
- data/lib/docyard/templates/config/docyard.yml.erb +37 -34
- data/lib/docyard/templates/errors/404.html.erb +1 -1
- data/lib/docyard/templates/errors/500.html.erb +1 -1
- data/lib/docyard/templates/layouts/default.html.erb +19 -67
- data/lib/docyard/templates/layouts/splash.html.erb +177 -0
- data/lib/docyard/templates/partials/_accordion.html.erb +9 -0
- data/lib/docyard/templates/partials/_banner.html.erb +27 -0
- data/lib/docyard/templates/partials/_breadcrumbs.html.erb +24 -0
- data/lib/docyard/templates/partials/_card.html.erb +23 -0
- data/lib/docyard/templates/partials/_code_block.html.erb +5 -3
- data/lib/docyard/templates/partials/_doc_footer.html.erb +25 -0
- data/lib/docyard/templates/partials/_features.html.erb +15 -0
- data/lib/docyard/templates/partials/_footer.html.erb +42 -0
- data/lib/docyard/templates/partials/_head.html.erb +22 -0
- data/lib/docyard/templates/partials/_header.html.erb +49 -0
- data/lib/docyard/templates/partials/_heading_anchor.html.erb +3 -1
- data/lib/docyard/templates/partials/_hero.html.erb +27 -0
- data/lib/docyard/templates/partials/_nav_group.html.erb +31 -11
- data/lib/docyard/templates/partials/_nav_leaf.html.erb +4 -1
- data/lib/docyard/templates/partials/_nav_menu.html.erb +42 -0
- data/lib/docyard/templates/partials/_nav_nested_section.html.erb +11 -0
- data/lib/docyard/templates/partials/_nav_section.html.erb +1 -1
- data/lib/docyard/templates/partials/_prev_next.html.erb +8 -2
- data/lib/docyard/templates/partials/_scripts.html.erb +7 -0
- data/lib/docyard/templates/partials/_search_modal.html.erb +2 -6
- data/lib/docyard/templates/partials/_search_trigger.html.erb +2 -6
- data/lib/docyard/templates/partials/_sidebar.html.erb +21 -4
- data/lib/docyard/templates/partials/_step.html.erb +14 -0
- data/lib/docyard/templates/partials/_tab_bar.html.erb +25 -0
- data/lib/docyard/templates/partials/_table_of_contents.html.erb +12 -12
- data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +1 -3
- data/lib/docyard/templates/partials/_tabs.html.erb +2 -2
- data/lib/docyard/templates/partials/_theme_toggle.html.erb +2 -11
- data/lib/docyard/version.rb +1 -1
- metadata +70 -5
- data/lib/docyard/templates/markdown/getting-started/installation.md.erb +0 -77
- data/lib/docyard/templates/markdown/guides/configuration.md.erb +0 -202
- data/lib/docyard/templates/markdown/guides/markdown-features.md.erb +0 -247
- data/lib/docyard/templates/markdown/index.md.erb +0 -82
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Docyard Navigation JavaScript
|
|
2
|
-
// Handles sidebar navigation, mobile menu, accordion groups, and scroll behavior
|
|
3
|
-
|
|
4
1
|
(function() {
|
|
5
2
|
'use strict';
|
|
6
3
|
|
|
@@ -13,18 +10,36 @@
|
|
|
13
10
|
return;
|
|
14
11
|
}
|
|
15
12
|
|
|
13
|
+
var scrollPosition = 0;
|
|
14
|
+
|
|
15
|
+
function lockBodyScroll() {
|
|
16
|
+
scrollPosition = window.pageYOffset;
|
|
17
|
+
document.body.style.overflow = 'hidden';
|
|
18
|
+
document.body.style.position = 'fixed';
|
|
19
|
+
document.body.style.top = -scrollPosition + 'px';
|
|
20
|
+
document.body.style.width = '100%';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function unlockBodyScroll() {
|
|
24
|
+
document.body.style.removeProperty('overflow');
|
|
25
|
+
document.body.style.removeProperty('position');
|
|
26
|
+
document.body.style.removeProperty('top');
|
|
27
|
+
document.body.style.removeProperty('width');
|
|
28
|
+
window.scrollTo(0, scrollPosition);
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
function openMenu() {
|
|
32
|
+
lockBodyScroll();
|
|
17
33
|
sidebar.classList.add('is-open');
|
|
18
34
|
overlay.classList.add('is-visible');
|
|
19
35
|
toggle.setAttribute('aria-expanded', 'true');
|
|
20
|
-
document.body.style.overflow = 'hidden';
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
function closeMenu() {
|
|
24
39
|
sidebar.classList.remove('is-open');
|
|
25
40
|
overlay.classList.remove('is-visible');
|
|
26
41
|
toggle.setAttribute('aria-expanded', 'false');
|
|
27
|
-
|
|
42
|
+
unlockBodyScroll();
|
|
28
43
|
}
|
|
29
44
|
|
|
30
45
|
function toggleMenu() {
|
|
@@ -52,24 +67,86 @@
|
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
function initAccordion() {
|
|
55
|
-
|
|
70
|
+
var TOGGLE_STATE_KEY = 'docyard_toggle_states';
|
|
71
|
+
var toggles = document.querySelectorAll('[data-nav-toggle]');
|
|
72
|
+
|
|
73
|
+
function getDefaultCollapsed(navGroup) {
|
|
74
|
+
return navGroup.getAttribute('data-default-collapsed') === 'true';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function collapseGroup(navGroup, animate) {
|
|
78
|
+
var header = navGroup.querySelector('[data-nav-toggle]');
|
|
79
|
+
var children = navGroup.querySelector('.nav-group-children');
|
|
80
|
+
if (!header || !children) return;
|
|
81
|
+
|
|
82
|
+
if (animate) {
|
|
83
|
+
children.style.transition = 'max-height 0.2s cubic-bezier(0.4, 0, 1, 1)';
|
|
84
|
+
} else {
|
|
85
|
+
children.style.transition = 'none';
|
|
86
|
+
}
|
|
87
|
+
header.setAttribute('aria-expanded', 'false');
|
|
88
|
+
children.classList.add('collapsed');
|
|
89
|
+
children.style.maxHeight = '0';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function expandGroup(navGroup, animate) {
|
|
93
|
+
var header = navGroup.querySelector('[data-nav-toggle]');
|
|
94
|
+
var children = navGroup.querySelector('.nav-group-children');
|
|
95
|
+
if (!header || !children) return;
|
|
96
|
+
|
|
97
|
+
var fullHeight = children.scrollHeight;
|
|
98
|
+
if (animate) {
|
|
99
|
+
children.style.transition = 'max-height 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)';
|
|
100
|
+
} else {
|
|
101
|
+
children.style.transition = 'none';
|
|
102
|
+
}
|
|
103
|
+
header.setAttribute('aria-expanded', 'true');
|
|
104
|
+
children.classList.remove('collapsed');
|
|
105
|
+
children.style.maxHeight = fullHeight + 'px';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function revertOthersToDefault(currentNavGroup) {
|
|
109
|
+
document.querySelectorAll('.nav-group').forEach(function(navGroup) {
|
|
110
|
+
if (navGroup === currentNavGroup) return;
|
|
111
|
+
|
|
112
|
+
var defaultCollapsed = getDefaultCollapsed(navGroup);
|
|
113
|
+
var children = navGroup.querySelector('.nav-group-children');
|
|
114
|
+
if (!children) return;
|
|
115
|
+
|
|
116
|
+
var isCurrentlyCollapsed = children.classList.contains('collapsed');
|
|
117
|
+
|
|
118
|
+
if (defaultCollapsed && !isCurrentlyCollapsed) {
|
|
119
|
+
collapseGroup(navGroup, true);
|
|
120
|
+
} else if (!defaultCollapsed && isCurrentlyCollapsed) {
|
|
121
|
+
expandGroup(navGroup, true);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
56
125
|
|
|
57
126
|
toggles.forEach(function(toggle) {
|
|
58
|
-
toggle.addEventListener('click', function() {
|
|
59
|
-
|
|
60
|
-
|
|
127
|
+
toggle.addEventListener('click', function(e) {
|
|
128
|
+
var expanded = toggle.getAttribute('aria-expanded') === 'true';
|
|
129
|
+
var navGroup = toggle.closest('.nav-group');
|
|
130
|
+
var children = navGroup ? navGroup.querySelector('.nav-group-children') : null;
|
|
61
131
|
|
|
62
|
-
if (!children
|
|
132
|
+
if (!children) {
|
|
63
133
|
return;
|
|
64
134
|
}
|
|
65
135
|
|
|
66
|
-
|
|
67
|
-
|
|
136
|
+
revertOthersToDefault(navGroup);
|
|
137
|
+
|
|
138
|
+
if (toggle.tagName === 'A') {
|
|
139
|
+
var href = toggle.getAttribute('href');
|
|
140
|
+
var states = {};
|
|
141
|
+
states[href] = true;
|
|
142
|
+
sessionStorage.setItem(TOGGLE_STATE_KEY, JSON.stringify(states));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
68
145
|
|
|
69
146
|
if (expanded) {
|
|
70
|
-
|
|
147
|
+
collapseGroup(navGroup, true);
|
|
71
148
|
} else {
|
|
72
|
-
|
|
149
|
+
expandGroup(navGroup, true);
|
|
73
150
|
}
|
|
74
151
|
});
|
|
75
152
|
});
|
|
@@ -80,32 +157,80 @@
|
|
|
80
157
|
}
|
|
81
158
|
|
|
82
159
|
function expandActiveGroups() {
|
|
83
|
-
|
|
160
|
+
var TOGGLE_STATE_KEY = 'docyard_toggle_states';
|
|
161
|
+
var currentUrl = window.location.pathname;
|
|
162
|
+
var lastUrl = sessionStorage.getItem('docyard_last_url') || '';
|
|
163
|
+
var toggleStates = JSON.parse(sessionStorage.getItem(TOGGLE_STATE_KEY) || '{}');
|
|
84
164
|
|
|
85
|
-
|
|
86
|
-
|
|
165
|
+
sessionStorage.setItem('docyard_last_url', currentUrl);
|
|
166
|
+
sessionStorage.removeItem(TOGGLE_STATE_KEY);
|
|
87
167
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
parent.classList.remove('collapsed');
|
|
168
|
+
document.querySelectorAll('[data-nav-toggle]').forEach(function(toggle) {
|
|
169
|
+
if (toggle.tagName !== 'A') return;
|
|
91
170
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
toggle.setAttribute('aria-expanded', 'true');
|
|
171
|
+
var href = toggle.getAttribute('href');
|
|
172
|
+
var shouldOpen = toggleStates[href] === true;
|
|
95
173
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
174
|
+
if (!shouldOpen) return;
|
|
175
|
+
|
|
176
|
+
var navGroup = toggle.closest('.nav-group');
|
|
177
|
+
var children = navGroup ? navGroup.querySelector('.nav-group-children') : null;
|
|
178
|
+
|
|
179
|
+
if (!children || !children.classList.contains('collapsed')) return;
|
|
101
180
|
|
|
102
|
-
|
|
181
|
+
var fullHeight = children.scrollHeight;
|
|
182
|
+
|
|
183
|
+
children.style.transition = 'none';
|
|
184
|
+
children.style.maxHeight = '0';
|
|
185
|
+
children.offsetHeight;
|
|
186
|
+
|
|
187
|
+
children.style.transition = 'max-height 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)';
|
|
188
|
+
requestAnimationFrame(function() {
|
|
189
|
+
toggle.setAttribute('aria-expanded', 'true');
|
|
190
|
+
children.classList.remove('collapsed');
|
|
191
|
+
children.style.maxHeight = fullHeight + 'px';
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
var expandedGroups = document.querySelectorAll('.nav-group-children:not(.collapsed)');
|
|
196
|
+
|
|
197
|
+
expandedGroups.forEach(function(group) {
|
|
198
|
+
if (group.style.maxHeight) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
var navGroup = group.closest('.nav-group');
|
|
203
|
+
var header = navGroup ? navGroup.querySelector('.nav-group-header') : null;
|
|
204
|
+
var headerHref = header && header.tagName === 'A' ? header.getAttribute('href') : null;
|
|
205
|
+
|
|
206
|
+
if (headerHref && toggleStates[headerHref] === true) {
|
|
207
|
+
group.style.maxHeight = group.scrollHeight + 'px';
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
var wasInGroup = headerHref && (lastUrl === headerHref || lastUrl.startsWith(headerHref + '/'));
|
|
212
|
+
var shouldAnimate = header && header.classList.contains('active') && !wasInGroup;
|
|
213
|
+
var fullHeight = group.scrollHeight;
|
|
214
|
+
|
|
215
|
+
if (shouldAnimate) {
|
|
216
|
+
group.style.transition = 'none';
|
|
217
|
+
group.style.maxHeight = '0';
|
|
218
|
+
group.classList.add('collapsed');
|
|
219
|
+
group.offsetHeight;
|
|
220
|
+
|
|
221
|
+
group.style.transition = 'max-height 0.35s cubic-bezier(0.34, 1.56, 0.64, 1)';
|
|
222
|
+
requestAnimationFrame(function() {
|
|
223
|
+
group.classList.remove('collapsed');
|
|
224
|
+
group.style.maxHeight = fullHeight + 'px';
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
group.style.maxHeight = fullHeight + 'px';
|
|
103
228
|
}
|
|
104
229
|
});
|
|
105
230
|
}
|
|
106
231
|
|
|
107
232
|
function initSidebarScroll() {
|
|
108
|
-
const scrollContainer = document.querySelector('.sidebar
|
|
233
|
+
const scrollContainer = document.querySelector('.sidebar-scroll');
|
|
109
234
|
if (!scrollContainer) return;
|
|
110
235
|
|
|
111
236
|
const STORAGE_KEY = 'docyard_sidebar_scroll';
|
|
@@ -153,54 +278,38 @@
|
|
|
153
278
|
}
|
|
154
279
|
}
|
|
155
280
|
|
|
156
|
-
function
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
if (!header || !secondaryHeader) return;
|
|
161
|
-
|
|
162
|
-
let lastScrollTop = 0;
|
|
163
|
-
let ticking = false;
|
|
281
|
+
function initScrollFadeIndicators() {
|
|
282
|
+
const sidebar = document.querySelector('.sidebar');
|
|
283
|
+
const scrollContainer = document.querySelector('.sidebar-scroll');
|
|
284
|
+
if (!sidebar || !scrollContainer) return;
|
|
164
285
|
|
|
165
|
-
function
|
|
166
|
-
|
|
167
|
-
|
|
286
|
+
function updateFadeIndicators() {
|
|
287
|
+
const scrollTop = scrollContainer.scrollTop;
|
|
288
|
+
const scrollHeight = scrollContainer.scrollHeight;
|
|
289
|
+
const clientHeight = scrollContainer.clientHeight;
|
|
290
|
+
const threshold = 10;
|
|
168
291
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
ticking = false;
|
|
174
|
-
return;
|
|
292
|
+
if (scrollTop > threshold) {
|
|
293
|
+
sidebar.classList.add('can-scroll-top');
|
|
294
|
+
} else {
|
|
295
|
+
sidebar.classList.remove('can-scroll-top');
|
|
175
296
|
}
|
|
176
297
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
secondaryHeader.classList.add('shift-up');
|
|
182
|
-
} else if (scrollTop < lastScrollTop) {
|
|
183
|
-
header.classList.remove('hide-on-scroll');
|
|
184
|
-
secondaryHeader.classList.remove('shift-up');
|
|
298
|
+
if (scrollTop + clientHeight < scrollHeight - threshold) {
|
|
299
|
+
sidebar.classList.add('can-scroll-bottom');
|
|
300
|
+
} else {
|
|
301
|
+
sidebar.classList.remove('can-scroll-bottom');
|
|
185
302
|
}
|
|
186
|
-
|
|
187
|
-
lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
|
|
188
|
-
ticking = false;
|
|
189
303
|
}
|
|
190
304
|
|
|
191
|
-
|
|
192
|
-
if (!ticking) {
|
|
193
|
-
window.requestAnimationFrame(updateHeaders);
|
|
194
|
-
ticking = true;
|
|
195
|
-
}
|
|
196
|
-
});
|
|
305
|
+
updateFadeIndicators();
|
|
197
306
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
307
|
+
scrollContainer.addEventListener('scroll', updateFadeIndicators);
|
|
308
|
+
|
|
309
|
+
window.addEventListener('resize', updateFadeIndicators);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function initScrollBehavior() {
|
|
204
313
|
}
|
|
205
314
|
|
|
206
315
|
if ('scrollRestoration' in history) {
|
|
@@ -213,6 +322,7 @@
|
|
|
213
322
|
initAccordion();
|
|
214
323
|
expandActiveGroups();
|
|
215
324
|
initSidebarScroll();
|
|
325
|
+
initScrollFadeIndicators();
|
|
216
326
|
initScrollBehavior();
|
|
217
327
|
});
|
|
218
328
|
} else {
|
|
@@ -220,6 +330,7 @@
|
|
|
220
330
|
initAccordion();
|
|
221
331
|
expandActiveGroups();
|
|
222
332
|
initSidebarScroll();
|
|
333
|
+
initScrollFadeIndicators();
|
|
223
334
|
initScrollBehavior();
|
|
224
335
|
}
|
|
225
336
|
})();
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SearchManager - Handles search functionality with Pagefind integration
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - Cmd+K / Ctrl+K keyboard shortcut
|
|
6
|
-
* - Lazy loading of Pagefind
|
|
7
|
-
* - Keyboard navigation in results
|
|
8
|
-
* - Debounced search input
|
|
9
|
-
*
|
|
10
|
-
* @class SearchManager
|
|
11
|
-
*/
|
|
12
1
|
class SearchManager {
|
|
13
2
|
constructor() {
|
|
14
3
|
this.modal = document.querySelector('[data-search-modal]');
|
|
@@ -32,7 +21,6 @@ class SearchManager {
|
|
|
32
21
|
this.DEBOUNCE_DELAY = 150;
|
|
33
22
|
this.RESULTS_PER_PAGE = 6;
|
|
34
23
|
|
|
35
|
-
// State for "load more" functionality
|
|
36
24
|
this.allSearchResults = [];
|
|
37
25
|
this.displayedCount = 0;
|
|
38
26
|
this.currentQuery = '';
|
|
@@ -52,36 +40,29 @@ class SearchManager {
|
|
|
52
40
|
}
|
|
53
41
|
|
|
54
42
|
attachEventListeners() {
|
|
55
|
-
// Global keyboard shortcut
|
|
56
43
|
document.addEventListener('keydown', this.handleKeyDown);
|
|
57
44
|
|
|
58
|
-
// Trigger button
|
|
59
45
|
if (this.trigger) {
|
|
60
46
|
this.trigger.addEventListener('click', () => this.open());
|
|
61
47
|
}
|
|
62
48
|
|
|
63
|
-
// Backdrop click to close
|
|
64
49
|
if (this.backdrop) {
|
|
65
50
|
this.backdrop.addEventListener('click', () => this.close());
|
|
66
51
|
}
|
|
67
52
|
|
|
68
|
-
// Close button
|
|
69
53
|
if (this.closeButton) {
|
|
70
54
|
this.closeButton.addEventListener('click', () => this.close());
|
|
71
55
|
}
|
|
72
56
|
|
|
73
|
-
// Clear button
|
|
74
57
|
if (this.clearButton) {
|
|
75
58
|
this.clearButton.addEventListener('click', () => this.clearSearch());
|
|
76
59
|
}
|
|
77
60
|
|
|
78
|
-
// Search input
|
|
79
61
|
if (this.input) {
|
|
80
62
|
this.input.addEventListener('input', this.handleInput);
|
|
81
63
|
this.input.addEventListener('keydown', (e) => this.handleInputKeyDown(e));
|
|
82
64
|
}
|
|
83
65
|
|
|
84
|
-
// Results click delegation
|
|
85
66
|
if (this.resultsContainer) {
|
|
86
67
|
this.resultsContainer.addEventListener('click', this.handleResultClick);
|
|
87
68
|
}
|
|
@@ -99,21 +80,18 @@ class SearchManager {
|
|
|
99
80
|
}
|
|
100
81
|
|
|
101
82
|
handleKeyDown(event) {
|
|
102
|
-
// Cmd+K or Ctrl+K to open
|
|
103
83
|
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
|
|
104
84
|
event.preventDefault();
|
|
105
85
|
this.toggle();
|
|
106
86
|
return;
|
|
107
87
|
}
|
|
108
88
|
|
|
109
|
-
// Escape to close
|
|
110
89
|
if (event.key === 'Escape' && this.isOpen) {
|
|
111
90
|
event.preventDefault();
|
|
112
91
|
this.close();
|
|
113
92
|
return;
|
|
114
93
|
}
|
|
115
94
|
|
|
116
|
-
// Forward slash to focus search (when not in an input)
|
|
117
95
|
if (event.key === '/' && !this.isOpen && !this.isInputFocused()) {
|
|
118
96
|
event.preventDefault();
|
|
119
97
|
this.open();
|
|
@@ -140,12 +118,10 @@ class SearchManager {
|
|
|
140
118
|
handleInput(event) {
|
|
141
119
|
const query = event.target.value.trim();
|
|
142
120
|
|
|
143
|
-
// Update clear button visibility
|
|
144
121
|
if (this.clearButton) {
|
|
145
122
|
this.clearButton.hidden = query.length === 0;
|
|
146
123
|
}
|
|
147
124
|
|
|
148
|
-
// Debounce search
|
|
149
125
|
if (this.searchTimeout) {
|
|
150
126
|
clearTimeout(this.searchTimeout);
|
|
151
127
|
}
|
|
@@ -195,10 +171,8 @@ class SearchManager {
|
|
|
195
171
|
this.modal.hidden = false;
|
|
196
172
|
document.body.style.overflow = 'hidden';
|
|
197
173
|
|
|
198
|
-
// Trigger animation on next frame
|
|
199
174
|
requestAnimationFrame(() => {
|
|
200
175
|
this.modal.classList.add('is-open');
|
|
201
|
-
// Double rAF ensures focus works after CSS transition starts
|
|
202
176
|
requestAnimationFrame(() => {
|
|
203
177
|
if (this.input) {
|
|
204
178
|
this.input.focus();
|
|
@@ -206,7 +180,6 @@ class SearchManager {
|
|
|
206
180
|
});
|
|
207
181
|
});
|
|
208
182
|
|
|
209
|
-
// Initialize Pagefind if not already done
|
|
210
183
|
if (!this.pagefind) {
|
|
211
184
|
await this.initPagefind();
|
|
212
185
|
}
|
|
@@ -220,18 +193,15 @@ class SearchManager {
|
|
|
220
193
|
document.body.style.overflow = '';
|
|
221
194
|
this.selectedIndex = -1;
|
|
222
195
|
|
|
223
|
-
// Hide modal after animation completes
|
|
224
196
|
setTimeout(() => {
|
|
225
197
|
if (!this.isOpen) {
|
|
226
198
|
this.modal.hidden = true;
|
|
227
|
-
// Reset body visibility for next open
|
|
228
199
|
if (this.body) {
|
|
229
200
|
this.body.hidden = true;
|
|
230
201
|
}
|
|
231
202
|
}
|
|
232
203
|
}, 200);
|
|
233
204
|
|
|
234
|
-
// Return focus to trigger
|
|
235
205
|
if (this.trigger) {
|
|
236
206
|
this.trigger.focus();
|
|
237
207
|
}
|
|
@@ -274,13 +244,11 @@ class SearchManager {
|
|
|
274
244
|
return;
|
|
275
245
|
}
|
|
276
246
|
|
|
277
|
-
// Store state for "load more"
|
|
278
247
|
this.allSearchResults = searchResults.results;
|
|
279
248
|
this.currentQuery = query;
|
|
280
249
|
this.displayedCount = 0;
|
|
281
250
|
this.groupedResults = [];
|
|
282
251
|
|
|
283
|
-
// Load initial batch
|
|
284
252
|
await this.loadMoreResults();
|
|
285
253
|
} catch (error) {
|
|
286
254
|
console.error('Search error:', error);
|
|
@@ -294,17 +262,14 @@ class SearchManager {
|
|
|
294
262
|
|
|
295
263
|
if (startIndex >= this.allSearchResults.length) return;
|
|
296
264
|
|
|
297
|
-
// Load the next batch of results
|
|
298
265
|
const resultsData = await Promise.all(
|
|
299
266
|
this.allSearchResults.slice(startIndex, endIndex).map(r => r.data())
|
|
300
267
|
);
|
|
301
268
|
|
|
302
|
-
// Group results by page with sections nested
|
|
303
269
|
const newGrouped = this.groupResults(resultsData);
|
|
304
270
|
this.groupedResults = [...this.groupedResults, ...newGrouped];
|
|
305
271
|
this.displayedCount = endIndex;
|
|
306
272
|
|
|
307
|
-
// Flatten for keyboard navigation
|
|
308
273
|
this.results = this.flattenForNavigation(this.groupedResults);
|
|
309
274
|
this.renderGroupedResults(this.groupedResults, this.currentQuery, this.allSearchResults.length);
|
|
310
275
|
}
|
|
@@ -320,14 +285,11 @@ class SearchManager {
|
|
|
320
285
|
for (const result of resultsData) {
|
|
321
286
|
const pageTitle = result.meta?.title || this.extractTitleFromUrl(result.url);
|
|
322
287
|
|
|
323
|
-
// Get sub-results (sections) for this page
|
|
324
|
-
// Filter out sections with same title as page (H1 heading duplicates)
|
|
325
288
|
const subResults = (result.sub_results || [])
|
|
326
289
|
.filter(sub => {
|
|
327
290
|
if (sub.url === result.url) return false;
|
|
328
291
|
if (!sub.title) return false;
|
|
329
292
|
const cleanedTitle = this.cleanSectionTitle(sub.title);
|
|
330
|
-
// Skip if section title matches page title
|
|
331
293
|
if (cleanedTitle.toLowerCase() === pageTitle.toLowerCase()) return false;
|
|
332
294
|
return true;
|
|
333
295
|
})
|
|
@@ -352,7 +314,6 @@ class SearchManager {
|
|
|
352
314
|
}
|
|
353
315
|
|
|
354
316
|
cleanSectionTitle(title) {
|
|
355
|
-
// Remove trailing # that Pagefind sometimes includes
|
|
356
317
|
return title.replace(/#$/, '').trim();
|
|
357
318
|
}
|
|
358
319
|
|
|
@@ -389,7 +350,6 @@ class SearchManager {
|
|
|
389
350
|
return this.renderPageResult(page, pageIndex, isPageSelected, sectionsHtml, query);
|
|
390
351
|
}).join('');
|
|
391
352
|
|
|
392
|
-
// Add "View more results" link if there are more results
|
|
393
353
|
const hasMore = this.displayedCount < totalResults;
|
|
394
354
|
const loadMoreHtml = hasMore ? `
|
|
395
355
|
<li class="search-load-more">
|
|
@@ -401,7 +361,6 @@ class SearchManager {
|
|
|
401
361
|
|
|
402
362
|
this.resultsContainer.innerHTML = resultsHtml + loadMoreHtml;
|
|
403
363
|
|
|
404
|
-
// Attach event listener to "View more" button
|
|
405
364
|
const loadMoreBtn = this.resultsContainer.querySelector('[data-search-load-more]');
|
|
406
365
|
if (loadMoreBtn) {
|
|
407
366
|
loadMoreBtn.addEventListener('click', this.handleLoadMore);
|
|
@@ -409,7 +368,6 @@ class SearchManager {
|
|
|
409
368
|
}
|
|
410
369
|
|
|
411
370
|
renderPageResult(page, index, isSelected, sectionsHtml, query) {
|
|
412
|
-
// Article icon (Phosphor)
|
|
413
371
|
const pageIcon = `<svg class="search-result-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor">
|
|
414
372
|
<path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,160H40V56H216V200ZM184,96a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,96Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,128Zm0,32a8,8,0,0,1-8,8H80a8,8,0,0,1,0-16h96A8,8,0,0,1,184,160Z"></path>
|
|
415
373
|
</svg>`;
|
|
@@ -473,7 +431,6 @@ class SearchManager {
|
|
|
473
431
|
const terms = query.trim().split(/\s+/).filter(t => t.length > 1);
|
|
474
432
|
if (terms.length === 0) return escaped;
|
|
475
433
|
|
|
476
|
-
// Match exact search term only (like Stripe does)
|
|
477
434
|
const regex = new RegExp(`(${terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
478
435
|
.join('|')})`, 'gi');
|
|
479
436
|
return escaped.replace(regex, '<mark class="search-title-highlight">$1</mark>');
|
|
@@ -482,30 +439,22 @@ class SearchManager {
|
|
|
482
439
|
highlightQuery(text, query, title = '') {
|
|
483
440
|
if (!query || !text) return this.escapeHtml(text);
|
|
484
441
|
|
|
485
|
-
// Decode HTML entities first (Pagefind may return encoded HTML)
|
|
486
442
|
let cleanText = this.decodeHtmlEntities(text);
|
|
487
443
|
|
|
488
|
-
// Strip all HTML tags
|
|
489
444
|
cleanText = cleanText.replace(/<[^>]*>/g, '');
|
|
490
445
|
|
|
491
|
-
// Remove the title if it appears at the start of the excerpt (Pagefind often includes it)
|
|
492
446
|
if (title) {
|
|
493
|
-
// Remove "Title#" or "Title:" patterns at the start
|
|
494
447
|
const titlePattern = new RegExp(`^${title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[#:]?\\s*`, 'i');
|
|
495
448
|
cleanText = cleanText.replace(titlePattern, '');
|
|
496
449
|
}
|
|
497
450
|
|
|
498
|
-
// Clean markdown and special characters
|
|
499
451
|
cleanText = this.cleanMarkdown(cleanText);
|
|
500
452
|
|
|
501
|
-
// Escape for safe HTML
|
|
502
453
|
const escaped = this.escapeHtml(cleanText);
|
|
503
454
|
|
|
504
|
-
// Highlight exact search term only (like Stripe does)
|
|
505
455
|
const terms = query.trim().split(/\s+/).filter(t => t.length > 1);
|
|
506
456
|
if (terms.length === 0) return escaped;
|
|
507
457
|
|
|
508
|
-
// Match exact search term only
|
|
509
458
|
const regex = new RegExp(`(${terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
510
459
|
.join('|')})`, 'gi');
|
|
511
460
|
return escaped.replace(regex, '<mark>$1</mark>');
|
|
@@ -519,51 +468,33 @@ class SearchManager {
|
|
|
519
468
|
|
|
520
469
|
cleanMarkdown(text) {
|
|
521
470
|
return text
|
|
522
|
-
// Remove code block markers with optional language/title (```ruby, ```js title="foo")
|
|
523
471
|
.replace(/```\w*(?:\s+[^`\n]*)?\n?/g, '')
|
|
524
472
|
.replace(/```/g, '')
|
|
525
|
-
// Remove Kramdown/Jekyll directives like {:/nomarkdown}, {::nomarkdown}, {:.class}
|
|
526
473
|
.replace(/\{:?:?[^}]*\}/g, '')
|
|
527
|
-
// Remove heading anchors like "Dark Mode#" or "Styling#" (word followed by #)
|
|
528
474
|
.replace(/(\w)#(?=\s|$|[A-Z])/g, '$1')
|
|
529
|
-
// Remove markdown bold/italic
|
|
530
475
|
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
531
476
|
.replace(/\*([^*]+)\*/g, '$1')
|
|
532
477
|
.replace(/__([^_]+)__/g, '$1')
|
|
533
478
|
.replace(/_([^_]+)_/g, '$1')
|
|
534
|
-
// Remove markdown links [text](url)
|
|
535
479
|
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
536
|
-
// Remove inline code backticks
|
|
537
480
|
.replace(/`([^`]+)`/g, '$1')
|
|
538
|
-
// Remove standalone backticks
|
|
539
481
|
.replace(/`/g, '')
|
|
540
|
-
// Remove heading markers
|
|
541
482
|
.replace(/^#+\s*/gm, '')
|
|
542
|
-
// Remove title="..." and similar attributes
|
|
543
483
|
.replace(/\s*title=["'][^"']*["']/gi, '')
|
|
544
|
-
// Remove URLs (http, https, ftp)
|
|
545
484
|
.replace(/https?:\/\/[^\s<>"{}|\\^`[\]]+/gi, '')
|
|
546
485
|
.replace(/ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi, '')
|
|
547
|
-
// Remove YAML-like patterns (key: value)
|
|
548
486
|
.replace(/\b\w+:\s*["']?[^"'\s,]+["']?(?=\s|,|$)/g, '')
|
|
549
|
-
// Remove common code syntax patterns
|
|
550
487
|
.replace(/\b(const|let|var|function|interface|class|import|export|return|if|else)\b/g, '')
|
|
551
488
|
.replace(/[=;{}()<>[\]]/g, ' ')
|
|
552
|
-
// Remove common unicode symbols
|
|
553
489
|
.replace(/[✓✔✗✘→←↑↓•·►▸▹▶]/g, '')
|
|
554
|
-
// Remove YAML-like frontmatter patterns
|
|
555
490
|
.replace(/^---[\s\S]*?---/m, '')
|
|
556
|
-
// Clean up navigation/menu text patterns
|
|
557
491
|
.replace(/Skip to main content/gi, '')
|
|
558
492
|
.replace(/On this page/gi, '')
|
|
559
493
|
.replace(/Menu/gi, '')
|
|
560
494
|
.replace(/Search\.\.\./gi, '')
|
|
561
|
-
// Remove list markers
|
|
562
495
|
.replace(/^[\s]*[-*+]\s+/gm, '')
|
|
563
496
|
.replace(/^[\s]*\d+\.\s+/gm, '')
|
|
564
|
-
// Clean up excessive whitespace
|
|
565
497
|
.replace(/\s+/g, ' ')
|
|
566
|
-
// Remove leading/trailing punctuation
|
|
567
498
|
.replace(/^[.\s,;:]+/, '')
|
|
568
499
|
.replace(/[.\s,;:]+$/, '')
|
|
569
500
|
.trim();
|
|
@@ -586,12 +517,10 @@ class SearchManager {
|
|
|
586
517
|
updateSelection(newIndex) {
|
|
587
518
|
const resultElements = this.resultsContainer.querySelectorAll('.search-result');
|
|
588
519
|
|
|
589
|
-
// Remove previous selection
|
|
590
520
|
if (this.selectedIndex >= 0 && resultElements[this.selectedIndex]) {
|
|
591
521
|
resultElements[this.selectedIndex].setAttribute('aria-selected', 'false');
|
|
592
522
|
}
|
|
593
523
|
|
|
594
|
-
// Add new selection
|
|
595
524
|
this.selectedIndex = newIndex;
|
|
596
525
|
if (resultElements[newIndex]) {
|
|
597
526
|
resultElements[newIndex].setAttribute('aria-selected', 'true');
|
|
@@ -666,14 +595,10 @@ class SearchManager {
|
|
|
666
595
|
}
|
|
667
596
|
}
|
|
668
597
|
|
|
669
|
-
/**
|
|
670
|
-
* Initialize search on page load
|
|
671
|
-
*/
|
|
672
598
|
function initializeSearch() {
|
|
673
599
|
new SearchManager();
|
|
674
600
|
}
|
|
675
601
|
|
|
676
|
-
// Initialize on DOM ready
|
|
677
602
|
if (document.readyState === 'loading') {
|
|
678
603
|
document.addEventListener('DOMContentLoaded', initializeSearch);
|
|
679
604
|
} else {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var STORAGE_KEY = 'docyard_sidebar_collapsed';
|
|
5
|
+
|
|
6
|
+
function initSidebarToggle() {
|
|
7
|
+
var toggle = document.querySelector('.breadcrumb-toggle');
|
|
8
|
+
var sidebar = document.querySelector('.sidebar');
|
|
9
|
+
|
|
10
|
+
if (!toggle || !sidebar) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
var isCollapsed = document.documentElement.classList.contains('sidebar-collapsed');
|
|
15
|
+
toggle.setAttribute('aria-expanded', !isCollapsed);
|
|
16
|
+
|
|
17
|
+
toggle.addEventListener('click', function() {
|
|
18
|
+
var collapsed = document.documentElement.classList.toggle('sidebar-collapsed');
|
|
19
|
+
localStorage.setItem(STORAGE_KEY, collapsed);
|
|
20
|
+
toggle.setAttribute('aria-expanded', !collapsed);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (document.readyState === 'loading') {
|
|
25
|
+
document.addEventListener('DOMContentLoaded', initSidebarToggle);
|
|
26
|
+
} else {
|
|
27
|
+
initSidebarToggle();
|
|
28
|
+
}
|
|
29
|
+
})();
|