docyard 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +29 -3
- data/README.md +37 -12
- data/lib/docyard/build/static_generator.rb +1 -1
- data/lib/docyard/components/base_processor.rb +6 -0
- data/lib/docyard/components/code_block_diff_preprocessor.rb +104 -0
- data/lib/docyard/components/code_block_feature_extractor.rb +113 -0
- data/lib/docyard/components/code_block_focus_preprocessor.rb +77 -0
- data/lib/docyard/components/code_block_icon_detector.rb +40 -0
- data/lib/docyard/components/code_block_line_wrapper.rb +46 -0
- data/lib/docyard/components/code_block_options_preprocessor.rb +76 -0
- data/lib/docyard/components/code_block_patterns.rb +51 -0
- data/lib/docyard/components/code_block_processor.rb +135 -14
- data/lib/docyard/components/code_line_parser.rb +80 -0
- data/lib/docyard/components/code_snippet_import_preprocessor.rb +125 -0
- data/lib/docyard/components/heading_anchor_processor.rb +34 -0
- data/lib/docyard/components/registry.rb +4 -4
- data/lib/docyard/components/table_of_contents_processor.rb +64 -0
- data/lib/docyard/components/tabs_parser.rb +135 -4
- data/lib/docyard/components/tabs_range_finder.rb +42 -0
- data/lib/docyard/config/validator.rb +8 -0
- data/lib/docyard/config.rb +18 -0
- data/lib/docyard/icons/file_types.rb +0 -13
- data/lib/docyard/icons/phosphor.rb +2 -1
- data/lib/docyard/markdown.rb +18 -4
- data/lib/docyard/prev_next_builder.rb +159 -0
- data/lib/docyard/rack_application.rb +25 -3
- data/lib/docyard/renderer.rb +20 -8
- data/lib/docyard/templates/assets/css/code.css +12 -4
- data/lib/docyard/templates/assets/css/components/code-block.css +427 -24
- data/lib/docyard/templates/assets/css/components/heading-anchor.css +77 -0
- data/lib/docyard/templates/assets/css/components/navigation.css +12 -9
- data/lib/docyard/templates/assets/css/components/prev-next.css +114 -0
- data/lib/docyard/templates/assets/css/components/table-of-contents.css +269 -0
- data/lib/docyard/templates/assets/css/components/tabs.css +50 -44
- data/lib/docyard/templates/assets/css/layout.css +58 -1
- data/lib/docyard/templates/assets/css/variables.css +45 -0
- data/lib/docyard/templates/assets/js/components/heading-anchor.js +90 -0
- data/lib/docyard/templates/assets/js/components/navigation.js +6 -2
- data/lib/docyard/templates/assets/js/components/table-of-contents.js +301 -0
- data/lib/docyard/templates/layouts/default.html.erb +9 -1
- data/lib/docyard/templates/partials/_code_block.html.erb +50 -2
- data/lib/docyard/templates/partials/_heading_anchor.html.erb +1 -0
- data/lib/docyard/templates/partials/_prev_next.html.erb +23 -0
- data/lib/docyard/templates/partials/_table_of_contents.html.erb +45 -0
- data/lib/docyard/templates/partials/_table_of_contents_toggle.html.erb +8 -0
- data/lib/docyard/version.rb +1 -1
- metadata +23 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HeadingAnchorManager handles anchor link interactions
|
|
3
|
+
* Provides copy-to-clipboard functionality with visual feedback
|
|
4
|
+
*/
|
|
5
|
+
class HeadingAnchorManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.anchors = document.querySelectorAll('.heading-anchor');
|
|
8
|
+
this.init();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
init() {
|
|
12
|
+
this.anchors.forEach(anchor => {
|
|
13
|
+
anchor.addEventListener('click', (e) => this.handleClick(e, anchor));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Handle anchor link click
|
|
19
|
+
* @param {Event} e - Click event
|
|
20
|
+
* @param {HTMLElement} anchor - Anchor element
|
|
21
|
+
*/
|
|
22
|
+
handleClick(e, anchor) {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
|
|
25
|
+
const headingId = anchor.dataset.headingId;
|
|
26
|
+
const url = `${window.location.origin}${window.location.pathname}#${headingId}`;
|
|
27
|
+
|
|
28
|
+
this.copyToClipboard(url, anchor);
|
|
29
|
+
|
|
30
|
+
history.pushState(null, null, `#${headingId}`);
|
|
31
|
+
|
|
32
|
+
const heading = document.getElementById(headingId);
|
|
33
|
+
if (heading) {
|
|
34
|
+
heading.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Copy text to clipboard with visual feedback
|
|
40
|
+
* @param {string} text - Text to copy
|
|
41
|
+
* @param {HTMLElement} anchor - Anchor element for feedback
|
|
42
|
+
*/
|
|
43
|
+
async copyToClipboard(text, anchor) {
|
|
44
|
+
try {
|
|
45
|
+
await navigator.clipboard.writeText(text);
|
|
46
|
+
this.showFeedback(anchor, true);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
this.fallbackCopyToClipboard(text);
|
|
49
|
+
this.showFeedback(anchor, true);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fallback copy method for older browsers
|
|
55
|
+
* @param {string} text - Text to copy
|
|
56
|
+
*/
|
|
57
|
+
fallbackCopyToClipboard(text) {
|
|
58
|
+
const textarea = document.createElement('textarea');
|
|
59
|
+
textarea.value = text;
|
|
60
|
+
textarea.style.position = 'fixed';
|
|
61
|
+
textarea.style.opacity = '0';
|
|
62
|
+
document.body.appendChild(textarea);
|
|
63
|
+
textarea.select();
|
|
64
|
+
document.execCommand('copy');
|
|
65
|
+
document.body.removeChild(textarea);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Show visual feedback on copy
|
|
70
|
+
* @param {HTMLElement} anchor - Anchor element
|
|
71
|
+
* @param {boolean} success - Whether copy succeeded
|
|
72
|
+
*/
|
|
73
|
+
showFeedback(anchor, success) {
|
|
74
|
+
const originalTitle = anchor.getAttribute('aria-label');
|
|
75
|
+
anchor.setAttribute('aria-label', success ? 'Link copied!' : 'Failed to copy');
|
|
76
|
+
|
|
77
|
+
anchor.style.color = success ? 'var(--color-success, #10b981)' : 'var(--color-danger, #ef4444)';
|
|
78
|
+
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
anchor.setAttribute('aria-label', originalTitle);
|
|
81
|
+
anchor.style.color = '';
|
|
82
|
+
}, 2000);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof window !== 'undefined') {
|
|
87
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
88
|
+
new HeadingAnchorManager();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
const sidebar = document.querySelector('.sidebar');
|
|
10
10
|
const overlay = document.querySelector('.mobile-overlay');
|
|
11
11
|
|
|
12
|
-
if (!toggle || !sidebar || !overlay)
|
|
12
|
+
if (!toggle || !sidebar || !overlay) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
13
15
|
|
|
14
16
|
function openMenu() {
|
|
15
17
|
sidebar.classList.add('is-open');
|
|
@@ -33,7 +35,9 @@
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
toggle.addEventListener('click',
|
|
38
|
+
toggle.addEventListener('click', function(e) {
|
|
39
|
+
toggleMenu();
|
|
40
|
+
});
|
|
37
41
|
overlay.addEventListener('click', closeMenu);
|
|
38
42
|
|
|
39
43
|
document.addEventListener('keydown', function(e) {
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TableOfContentsManager handles TOC interactions and scroll tracking
|
|
3
|
+
* Features: scroll spy, active highlighting, smooth scrolling, mobile toggle
|
|
4
|
+
*/
|
|
5
|
+
class TableOfContentsManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.toc = document.querySelector('.docyard-toc');
|
|
8
|
+
if (!this.toc) return;
|
|
9
|
+
|
|
10
|
+
this.links = Array.from(this.toc.querySelectorAll('.docyard-toc__link'));
|
|
11
|
+
this.headings = this.getHeadings();
|
|
12
|
+
this.nav = this.toc.querySelector('.docyard-toc__nav');
|
|
13
|
+
this.activeLink = null;
|
|
14
|
+
this.observer = null;
|
|
15
|
+
this.ticking = false;
|
|
16
|
+
|
|
17
|
+
this.init();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
init() {
|
|
21
|
+
if (this.headings.length === 0) return;
|
|
22
|
+
|
|
23
|
+
this.setupScrollSpy();
|
|
24
|
+
this.setupSmoothScrolling();
|
|
25
|
+
this.setupMobileToggle();
|
|
26
|
+
this.setupKeyboardNavigation();
|
|
27
|
+
this.handleInitialHash();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if viewport is mobile/tablet (TOC in secondary header)
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
isMobile() {
|
|
35
|
+
return window.innerWidth <= 1280;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if viewport is tablet (both headers visible)
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
isTablet() {
|
|
43
|
+
return window.innerWidth > 1024 && window.innerWidth <= 1280;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get scroll offset based on viewport and header state
|
|
48
|
+
* @returns {number}
|
|
49
|
+
*/
|
|
50
|
+
getScrollOffset() {
|
|
51
|
+
if (this.isTablet()) {
|
|
52
|
+
return 128;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (window.innerWidth <= 1024) {
|
|
56
|
+
const header = document.querySelector('.header');
|
|
57
|
+
const isHeaderHidden = header && header.classList.contains('hide-on-scroll');
|
|
58
|
+
return isHeaderHidden ? 128 : 64;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return 100;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get all headings referenced in TOC
|
|
66
|
+
* @returns {Array<HTMLElement>}
|
|
67
|
+
*/
|
|
68
|
+
getHeadings() {
|
|
69
|
+
return this.links
|
|
70
|
+
.map(link => {
|
|
71
|
+
const id = link.dataset.headingId;
|
|
72
|
+
return document.getElementById(id);
|
|
73
|
+
})
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Setup Intersection Observer for scroll spy
|
|
79
|
+
*/
|
|
80
|
+
setupScrollSpy() {
|
|
81
|
+
const options = {
|
|
82
|
+
root: null,
|
|
83
|
+
rootMargin: '-80px 0px -80% 0px',
|
|
84
|
+
threshold: 0
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
88
|
+
entries.forEach(entry => {
|
|
89
|
+
if (entry.isIntersecting) {
|
|
90
|
+
this.setActiveLink(entry.target.id);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}, options);
|
|
94
|
+
|
|
95
|
+
this.headings.forEach(heading => {
|
|
96
|
+
this.observer.observe(heading);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Set active link in TOC
|
|
102
|
+
* @param {string} id - Heading ID
|
|
103
|
+
*/
|
|
104
|
+
setActiveLink(id) {
|
|
105
|
+
const link = this.links.find(l => l.dataset.headingId === id);
|
|
106
|
+
if (!link || link === this.activeLink) return;
|
|
107
|
+
|
|
108
|
+
if (this.activeLink) {
|
|
109
|
+
this.activeLink.classList.remove('is-active');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
link.classList.add('is-active');
|
|
113
|
+
this.activeLink = link;
|
|
114
|
+
|
|
115
|
+
this.scrollLinkIntoView(link);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Scroll TOC to keep active link visible
|
|
120
|
+
* @param {HTMLElement} link - Active link element
|
|
121
|
+
*/
|
|
122
|
+
scrollLinkIntoView(link) {
|
|
123
|
+
if (this.isMobile()) return;
|
|
124
|
+
|
|
125
|
+
if (!this.ticking) {
|
|
126
|
+
requestAnimationFrame(() => {
|
|
127
|
+
const tocRect = this.toc.getBoundingClientRect();
|
|
128
|
+
const linkRect = link.getBoundingClientRect();
|
|
129
|
+
|
|
130
|
+
if (linkRect.top < tocRect.top || linkRect.bottom > tocRect.bottom) {
|
|
131
|
+
link.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.ticking = false;
|
|
135
|
+
});
|
|
136
|
+
this.ticking = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Setup smooth scrolling for TOC links
|
|
142
|
+
*/
|
|
143
|
+
setupSmoothScrolling() {
|
|
144
|
+
this.links.forEach(link => {
|
|
145
|
+
link.addEventListener('click', (e) => {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
|
|
148
|
+
const id = link.dataset.headingId;
|
|
149
|
+
const heading = document.getElementById(id);
|
|
150
|
+
|
|
151
|
+
if (heading) {
|
|
152
|
+
const offsetTop = heading.getBoundingClientRect().top + window.pageYOffset - this.getScrollOffset();
|
|
153
|
+
|
|
154
|
+
window.scrollTo({
|
|
155
|
+
top: offsetTop,
|
|
156
|
+
behavior: 'smooth'
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
history.pushState(null, null, `#${id}`);
|
|
160
|
+
|
|
161
|
+
heading.focus({ preventScroll: true });
|
|
162
|
+
|
|
163
|
+
// Close mobile menu after clicking a link
|
|
164
|
+
if (this.isMobile()) {
|
|
165
|
+
this.collapseMobile();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Setup mobile toggle functionality
|
|
174
|
+
*/
|
|
175
|
+
setupMobileToggle() {
|
|
176
|
+
const secondaryToggle = document.querySelector('.secondary-header-toc-toggle');
|
|
177
|
+
|
|
178
|
+
if (!secondaryToggle || !this.nav) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
secondaryToggle.addEventListener('click', () => {
|
|
183
|
+
const isExpanded = secondaryToggle.getAttribute('aria-expanded') === 'true';
|
|
184
|
+
secondaryToggle.setAttribute('aria-expanded', !isExpanded);
|
|
185
|
+
this.nav.classList.toggle('is-expanded', !isExpanded);
|
|
186
|
+
|
|
187
|
+
if (!isExpanded) {
|
|
188
|
+
document.body.style.overflow = 'hidden';
|
|
189
|
+
} else {
|
|
190
|
+
document.body.style.overflow = '';
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Close menu when clicking outside (mobile only)
|
|
195
|
+
document.addEventListener('click', (e) => {
|
|
196
|
+
if (!this.isMobile()) return;
|
|
197
|
+
|
|
198
|
+
const isExpanded = secondaryToggle.getAttribute('aria-expanded') === 'true';
|
|
199
|
+
if (!isExpanded) return;
|
|
200
|
+
|
|
201
|
+
const isClickInsideToc = this.nav.contains(e.target);
|
|
202
|
+
const isClickOnToggle = secondaryToggle.contains(e.target);
|
|
203
|
+
|
|
204
|
+
if (!isClickInsideToc && !isClickOnToggle) {
|
|
205
|
+
this.collapseMobile();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Collapse mobile TOC
|
|
212
|
+
*/
|
|
213
|
+
collapseMobile() {
|
|
214
|
+
const secondaryToggle = document.querySelector('.secondary-header-toc-toggle');
|
|
215
|
+
if (secondaryToggle) {
|
|
216
|
+
secondaryToggle.setAttribute('aria-expanded', 'false');
|
|
217
|
+
if (this.nav) {
|
|
218
|
+
this.nav.classList.remove('is-expanded');
|
|
219
|
+
}
|
|
220
|
+
document.body.style.overflow = '';
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Setup keyboard navigation
|
|
226
|
+
*/
|
|
227
|
+
setupKeyboardNavigation() {
|
|
228
|
+
this.links.forEach((link, index) => {
|
|
229
|
+
link.addEventListener('keydown', (e) => {
|
|
230
|
+
let targetIndex = -1;
|
|
231
|
+
|
|
232
|
+
switch (e.key) {
|
|
233
|
+
case 'ArrowDown':
|
|
234
|
+
e.preventDefault();
|
|
235
|
+
targetIndex = index + 1;
|
|
236
|
+
break;
|
|
237
|
+
case 'ArrowUp':
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
targetIndex = index - 1;
|
|
240
|
+
break;
|
|
241
|
+
case 'Home':
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
targetIndex = 0;
|
|
244
|
+
break;
|
|
245
|
+
case 'End':
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
targetIndex = this.links.length - 1;
|
|
248
|
+
break;
|
|
249
|
+
default:
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (targetIndex >= 0 && targetIndex < this.links.length) {
|
|
254
|
+
this.links[targetIndex].focus();
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Handle initial hash in URL on page load
|
|
262
|
+
*/
|
|
263
|
+
handleInitialHash() {
|
|
264
|
+
const hash = window.location.hash.slice(1);
|
|
265
|
+
if (!hash) return;
|
|
266
|
+
|
|
267
|
+
const heading = document.getElementById(hash);
|
|
268
|
+
if (!heading) return;
|
|
269
|
+
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
this.setActiveLink(hash);
|
|
272
|
+
|
|
273
|
+
const offsetTop = heading.getBoundingClientRect().top + window.pageYOffset - this.getScrollOffset();
|
|
274
|
+
window.scrollTo({
|
|
275
|
+
top: offsetTop,
|
|
276
|
+
behavior: 'auto'
|
|
277
|
+
});
|
|
278
|
+
}, 100);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Cleanup and destroy
|
|
283
|
+
*/
|
|
284
|
+
destroy() {
|
|
285
|
+
if (this.observer) {
|
|
286
|
+
this.observer.disconnect();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (typeof window !== 'undefined') {
|
|
292
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
293
|
+
window.tocManager = new TableOfContentsManager();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
window.addEventListener('beforeunload', () => {
|
|
297
|
+
if (window.tocManager) {
|
|
298
|
+
window.tocManager.destroy();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
@@ -51,10 +51,12 @@
|
|
|
51
51
|
<div class="secondary-header">
|
|
52
52
|
<button class="secondary-header-menu" aria-label="Toggle navigation menu" aria-expanded="false">
|
|
53
53
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="18" height="18">
|
|
54
|
-
<path d="
|
|
54
|
+
<path d="M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM40,72H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z"></path>
|
|
55
55
|
</svg>
|
|
56
56
|
<span>Menu</span>
|
|
57
57
|
</button>
|
|
58
|
+
|
|
59
|
+
<%= render_partial('_table_of_contents_toggle') %>
|
|
58
60
|
</div>
|
|
59
61
|
|
|
60
62
|
<!-- Mobile overlay -->
|
|
@@ -68,8 +70,14 @@
|
|
|
68
70
|
<div class="layout-main">
|
|
69
71
|
<main id="main-content" class="content">
|
|
70
72
|
<%= @content %>
|
|
73
|
+
|
|
74
|
+
<!-- Previous/Next Navigation -->
|
|
75
|
+
<%= @prev_next_html %>
|
|
71
76
|
</main>
|
|
72
77
|
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Table of contents -->
|
|
80
|
+
<%= render_partial('_table_of_contents') %>
|
|
73
81
|
</div>
|
|
74
82
|
|
|
75
83
|
<script src="/assets/js/theme.js"></script>
|
|
@@ -1,6 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<% has_diff = @diff_lines&.any? %><% has_focus = @focus_lines&.any? %><% has_error = @error_lines&.any? %><% has_warning = @warning_lines&.any? %><% has_title = !@title.nil? && !@title.empty? %><div class="docyard-code-block<%= ' docyard-code-block--line-numbers' if @show_line_numbers %><%= ' docyard-code-block--highlighted' if @highlights&.any? %><%= ' docyard-code-block--diff' if has_diff %><%= ' docyard-code-block--has-focus' if has_focus %><%= ' docyard-code-block--has-error' if has_error %><%= ' docyard-code-block--has-warning' if has_warning %><%= ' docyard-code-block--titled' if has_title %>">
|
|
2
|
+
<% if has_title %>
|
|
3
|
+
<div class="docyard-code-block__header">
|
|
4
|
+
<% if @icon %>
|
|
5
|
+
<span class="docyard-code-block__icon"><% if @icon_source == "file-extension" %><%= Docyard::Icons.render_file_extension(@icon) %><% elsif @icon_source == "phosphor" %><%= Docyard::Icons.render(@icon) %><% end %></span>
|
|
6
|
+
<% end %>
|
|
7
|
+
<span class="docyard-code-block__title" title="<%= @title %>"><%= @title %></span>
|
|
8
|
+
<button class="docyard-code-block__copy" aria-label="Copy code to clipboard" data-code="<%= @code_text %>">
|
|
9
|
+
<%= @copy_icon %>
|
|
10
|
+
</button>
|
|
11
|
+
</div>
|
|
12
|
+
<% end %>
|
|
13
|
+
<div class="docyard-code-block__body">
|
|
14
|
+
<% if @show_line_numbers %>
|
|
15
|
+
<div class="docyard-code-block__lines" aria-hidden="true">
|
|
16
|
+
<% @line_numbers.each_with_index do |num, index| %>
|
|
17
|
+
<%
|
|
18
|
+
source_line = index + 1
|
|
19
|
+
diff_type = @diff_lines&.dig(source_line)
|
|
20
|
+
is_error = @error_lines&.dig(source_line)
|
|
21
|
+
is_warning = @warning_lines&.dig(source_line)
|
|
22
|
+
line_class = case diff_type
|
|
23
|
+
when :addition then "docyard-code-block__line--diff-add"
|
|
24
|
+
when :deletion then "docyard-code-block__line--diff-remove"
|
|
25
|
+
else
|
|
26
|
+
if is_error
|
|
27
|
+
"docyard-code-block__line--error"
|
|
28
|
+
elsif is_warning
|
|
29
|
+
"docyard-code-block__line--warning"
|
|
30
|
+
elsif @highlights&.include?(num)
|
|
31
|
+
"docyard-code-block__line--highlighted"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
prefix = case diff_type
|
|
35
|
+
when :addition then "+"
|
|
36
|
+
when :deletion then "-"
|
|
37
|
+
else ""
|
|
38
|
+
end
|
|
39
|
+
%><% if line_class %><span class="<%= line_class %>"><%= prefix %><%= num %></span><% else %><span><%= num %></span><% end %>
|
|
40
|
+
<% end %>
|
|
41
|
+
</div>
|
|
42
|
+
<% elsif has_diff %>
|
|
43
|
+
<div class="docyard-code-block__diff-gutter" aria-hidden="true"><% line_count = @code_block_html.scan(/<span class="docyard-code-line/).count %><% line_count = 1 if line_count.zero? %><% (1..line_count).each do |num| %><% diff_type = @diff_lines&.dig(num) %><% gutter_class = diff_type == :addition ? "docyard-code-block__diff-indicator--add" : (diff_type == :deletion ? "docyard-code-block__diff-indicator--remove" : nil) %><% indicator = diff_type == :addition ? "+" : (diff_type == :deletion ? "-" : "") %><% if gutter_class %><span class="<%= gutter_class %>"><%= indicator %></span><% else %><span> </span><% end %><% end %></div>
|
|
44
|
+
<% end %>
|
|
45
|
+
<div class="docyard-code-block__content">
|
|
46
|
+
<%= @code_block_html %>
|
|
47
|
+
</div>
|
|
48
|
+
<% unless has_title %>
|
|
3
49
|
<button class="docyard-code-block__copy" aria-label="Copy code to clipboard" data-code="<%= @code_text %>">
|
|
4
50
|
<%= @copy_icon %>
|
|
5
51
|
</button>
|
|
52
|
+
<% end %>
|
|
53
|
+
</div>
|
|
6
54
|
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<a href="#<%= @id %>" class="heading-anchor" aria-label="Link to this section" data-heading-id="<%= @id %>">#</a>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<% if @prev || @next %>
|
|
2
|
+
<footer class="doc-footer">
|
|
3
|
+
<nav class="pager" aria-labelledby="doc-footer-aria-label">
|
|
4
|
+
<span id="doc-footer-aria-label" class="visually-hidden">Pager</span>
|
|
5
|
+
|
|
6
|
+
<% if @prev %>
|
|
7
|
+
<a href="<%= link_path(@prev[:path]) %>" class="pager-link prev" title="<%= @prev[:title] %>">
|
|
8
|
+
<span class="pager-label"><%= @prev_text %></span>
|
|
9
|
+
<span class="pager-title"><%= @prev[:title] %></span>
|
|
10
|
+
</a>
|
|
11
|
+
<% else %>
|
|
12
|
+
<span></span>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<% if @next %>
|
|
16
|
+
<a href="<%= link_path(@next[:path]) %>" class="pager-link next" title="<%= @next[:title] %>">
|
|
17
|
+
<span class="pager-label"><%= @next_text %></span>
|
|
18
|
+
<span class="pager-title"><%= @next[:title] %></span>
|
|
19
|
+
</a>
|
|
20
|
+
<% end %>
|
|
21
|
+
</nav>
|
|
22
|
+
</footer>
|
|
23
|
+
<% end %>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<% unless @toc.empty? %>
|
|
2
|
+
<aside class="docyard-toc" aria-label="Table of contents">
|
|
3
|
+
<div class="docyard-toc__header">
|
|
4
|
+
<h2 class="docyard-toc__title">
|
|
5
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 256 256" aria-hidden="true">
|
|
6
|
+
<path d="M88,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H96A8,8,0,0,1,88,64Zm128,56H96a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm0,64H96a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16ZM56,56H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Zm0,64H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Zm0,64H40a8,8,0,0,0,0,16H56a8,8,0,0,0,0-16Z"/>
|
|
7
|
+
</svg>
|
|
8
|
+
On this page
|
|
9
|
+
</h2>
|
|
10
|
+
</div>
|
|
11
|
+
<nav id="toc-content" class="docyard-toc__nav">
|
|
12
|
+
<ul class="docyard-toc__list">
|
|
13
|
+
<% @toc.each do |heading| %>
|
|
14
|
+
<li class="docyard-toc__item docyard-toc__item--level-<%= heading[:level] %>">
|
|
15
|
+
<a href="#<%= heading[:id] %>" class="docyard-toc__link" data-heading-id="<%= heading[:id] %>" title="<%= heading[:text] %>">
|
|
16
|
+
<%= heading[:text] %>
|
|
17
|
+
</a>
|
|
18
|
+
<% unless heading[:children].empty? %>
|
|
19
|
+
<ul class="docyard-toc__list">
|
|
20
|
+
<% heading[:children].each do |child| %>
|
|
21
|
+
<li class="docyard-toc__item docyard-toc__item--level-<%= child[:level] %>">
|
|
22
|
+
<a href="#<%= child[:id] %>" class="docyard-toc__link" data-heading-id="<%= child[:id] %>" title="<%= child[:text] %>">
|
|
23
|
+
<%= child[:text] %>
|
|
24
|
+
</a>
|
|
25
|
+
<% unless child[:children].empty? %>
|
|
26
|
+
<ul class="docyard-toc__list">
|
|
27
|
+
<% child[:children].each do |grandchild| %>
|
|
28
|
+
<li class="docyard-toc__item docyard-toc__item--level-<%= grandchild[:level] %>">
|
|
29
|
+
<a href="#<%= grandchild[:id] %>" class="docyard-toc__link" data-heading-id="<%= grandchild[:id] %>" title="<%= grandchild[:text] %>">
|
|
30
|
+
<%= grandchild[:text] %>
|
|
31
|
+
</a>
|
|
32
|
+
</li>
|
|
33
|
+
<% end %>
|
|
34
|
+
</ul>
|
|
35
|
+
<% end %>
|
|
36
|
+
</li>
|
|
37
|
+
<% end %>
|
|
38
|
+
</ul>
|
|
39
|
+
<% end %>
|
|
40
|
+
</li>
|
|
41
|
+
<% end %>
|
|
42
|
+
</ul>
|
|
43
|
+
</nav>
|
|
44
|
+
</aside>
|
|
45
|
+
<% end %>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% unless @toc.empty? %>
|
|
2
|
+
<button class="secondary-header-toc-toggle" aria-expanded="false" aria-controls="toc-content" aria-label="Toggle table of contents">
|
|
3
|
+
<span>On this page</span>
|
|
4
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true">
|
|
5
|
+
<path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z"/>
|
|
6
|
+
</svg>
|
|
7
|
+
</button>
|
|
8
|
+
<% end %>
|
data/lib/docyard/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: docyard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sanif Himani
|
|
@@ -192,14 +192,26 @@ files:
|
|
|
192
192
|
- lib/docyard/cli.rb
|
|
193
193
|
- lib/docyard/components/base_processor.rb
|
|
194
194
|
- lib/docyard/components/callout_processor.rb
|
|
195
|
+
- lib/docyard/components/code_block_diff_preprocessor.rb
|
|
196
|
+
- lib/docyard/components/code_block_feature_extractor.rb
|
|
197
|
+
- lib/docyard/components/code_block_focus_preprocessor.rb
|
|
198
|
+
- lib/docyard/components/code_block_icon_detector.rb
|
|
199
|
+
- lib/docyard/components/code_block_line_wrapper.rb
|
|
200
|
+
- lib/docyard/components/code_block_options_preprocessor.rb
|
|
201
|
+
- lib/docyard/components/code_block_patterns.rb
|
|
195
202
|
- lib/docyard/components/code_block_processor.rb
|
|
196
203
|
- lib/docyard/components/code_detector.rb
|
|
204
|
+
- lib/docyard/components/code_line_parser.rb
|
|
205
|
+
- lib/docyard/components/code_snippet_import_preprocessor.rb
|
|
206
|
+
- lib/docyard/components/heading_anchor_processor.rb
|
|
197
207
|
- lib/docyard/components/icon_detector.rb
|
|
198
208
|
- lib/docyard/components/icon_processor.rb
|
|
199
209
|
- lib/docyard/components/registry.rb
|
|
210
|
+
- lib/docyard/components/table_of_contents_processor.rb
|
|
200
211
|
- lib/docyard/components/table_wrapper_processor.rb
|
|
201
212
|
- lib/docyard/components/tabs_parser.rb
|
|
202
213
|
- lib/docyard/components/tabs_processor.rb
|
|
214
|
+
- lib/docyard/components/tabs_range_finder.rb
|
|
203
215
|
- lib/docyard/config.rb
|
|
204
216
|
- lib/docyard/config/validator.rb
|
|
205
217
|
- lib/docyard/constants.rb
|
|
@@ -213,6 +225,7 @@ files:
|
|
|
213
225
|
- lib/docyard/language_mapping.rb
|
|
214
226
|
- lib/docyard/logging.rb
|
|
215
227
|
- lib/docyard/markdown.rb
|
|
228
|
+
- lib/docyard/prev_next_builder.rb
|
|
216
229
|
- lib/docyard/preview_server.rb
|
|
217
230
|
- lib/docyard/rack_application.rb
|
|
218
231
|
- lib/docyard/renderer.rb
|
|
@@ -229,9 +242,12 @@ files:
|
|
|
229
242
|
- lib/docyard/templates/assets/css/code.css
|
|
230
243
|
- lib/docyard/templates/assets/css/components/callout.css
|
|
231
244
|
- lib/docyard/templates/assets/css/components/code-block.css
|
|
245
|
+
- lib/docyard/templates/assets/css/components/heading-anchor.css
|
|
232
246
|
- lib/docyard/templates/assets/css/components/icon.css
|
|
233
247
|
- lib/docyard/templates/assets/css/components/logo.css
|
|
234
248
|
- lib/docyard/templates/assets/css/components/navigation.css
|
|
249
|
+
- lib/docyard/templates/assets/css/components/prev-next.css
|
|
250
|
+
- lib/docyard/templates/assets/css/components/table-of-contents.css
|
|
235
251
|
- lib/docyard/templates/assets/css/components/tabs.css
|
|
236
252
|
- lib/docyard/templates/assets/css/components/theme-toggle.css
|
|
237
253
|
- lib/docyard/templates/assets/css/layout.css
|
|
@@ -242,7 +258,9 @@ files:
|
|
|
242
258
|
- lib/docyard/templates/assets/css/variables.css
|
|
243
259
|
- lib/docyard/templates/assets/favicon.svg
|
|
244
260
|
- lib/docyard/templates/assets/js/components/code-block.js
|
|
261
|
+
- lib/docyard/templates/assets/js/components/heading-anchor.js
|
|
245
262
|
- lib/docyard/templates/assets/js/components/navigation.js
|
|
263
|
+
- lib/docyard/templates/assets/js/components/table-of-contents.js
|
|
246
264
|
- lib/docyard/templates/assets/js/components/tabs.js
|
|
247
265
|
- lib/docyard/templates/assets/js/reload.js
|
|
248
266
|
- lib/docyard/templates/assets/js/theme.js
|
|
@@ -258,6 +276,7 @@ files:
|
|
|
258
276
|
- lib/docyard/templates/markdown/index.md.erb
|
|
259
277
|
- lib/docyard/templates/partials/_callout.html.erb
|
|
260
278
|
- lib/docyard/templates/partials/_code_block.html.erb
|
|
279
|
+
- lib/docyard/templates/partials/_heading_anchor.html.erb
|
|
261
280
|
- lib/docyard/templates/partials/_icon.html.erb
|
|
262
281
|
- lib/docyard/templates/partials/_icon_file_extension.html.erb
|
|
263
282
|
- lib/docyard/templates/partials/_nav_group.html.erb
|
|
@@ -265,8 +284,11 @@ files:
|
|
|
265
284
|
- lib/docyard/templates/partials/_nav_leaf.html.erb
|
|
266
285
|
- lib/docyard/templates/partials/_nav_list.html.erb
|
|
267
286
|
- lib/docyard/templates/partials/_nav_section.html.erb
|
|
287
|
+
- lib/docyard/templates/partials/_prev_next.html.erb
|
|
268
288
|
- lib/docyard/templates/partials/_sidebar.html.erb
|
|
269
289
|
- lib/docyard/templates/partials/_sidebar_footer.html.erb
|
|
290
|
+
- lib/docyard/templates/partials/_table_of_contents.html.erb
|
|
291
|
+
- lib/docyard/templates/partials/_table_of_contents_toggle.html.erb
|
|
270
292
|
- lib/docyard/templates/partials/_tabs.html.erb
|
|
271
293
|
- lib/docyard/templates/partials/_theme_toggle.html.erb
|
|
272
294
|
- lib/docyard/utils/path_resolver.rb
|