jekyll-vitepress-theme 1.7.0 → 1.8.1
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/README.md +8 -0
- data/_includes/doc_footer.html +8 -1
- data/_includes/nav.html +13 -1
- data/_includes/sidebar.html +42 -24
- data/_includes/sidebar_items.html +41 -0
- data/assets/css/vitepress-overrides.css +10 -0
- data/assets/js/vitepress-theme.js +29 -6
- data/lib/jekyll/vitepress_theme/hooks.rb +287 -4
- data/lib/jekyll/vitepress_theme/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e8e0506d69bd7172baf0392133cc0782815c07784a747437be2b7a59503ca37
|
|
4
|
+
data.tar.gz: 7037f54855dc45578def24ab7144a563f9211f7a441b110bfa56172cebdff91d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08cad3bf3e61558a9ca8a9832f1ad95943289691c6832961405551f62d43399294a3f709f1e120a5a265a664c39ee46b3591a2e39dd20e3dc3ad92b3c603ea46'
|
|
7
|
+
data.tar.gz: 2cedeabbc9f8be7ded0ed39164d7808bc63178e84a7ef06aa2da6bddcfd6262047490eff29eb36da1dcd80393c0aba1bc33cc4340a5358af02bc4373c02f568e
|
data/README.md
CHANGED
|
@@ -78,6 +78,14 @@ bundle install
|
|
|
78
78
|
bundle exec jekyll serve --livereload
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
### Metadata and jekyll-seo-tag
|
|
82
|
+
|
|
83
|
+
The theme writes the page description, canonical link, Open Graph, and Twitter
|
|
84
|
+
card tags itself. Calling `{% seo %}` as well emits a second copy of each, so
|
|
85
|
+
drop it when you switch to this theme, or drop the theme's tags if you prefer
|
|
86
|
+
jekyll-seo-tag's. Its JSON-LD has no equivalent here, so keep that plugin if
|
|
87
|
+
you want structured data.
|
|
88
|
+
|
|
81
89
|
## Screenshots
|
|
82
90
|
|
|
83
91
|
| Home | Docs |
|
data/_includes/doc_footer.html
CHANGED
|
@@ -56,7 +56,14 @@
|
|
|
56
56
|
{% assign prev_doc = nil %}
|
|
57
57
|
{% assign next_doc = nil %}
|
|
58
58
|
{% if page.collection %}
|
|
59
|
-
{% assign
|
|
59
|
+
{% assign generated_sidebar = site.data.jekyll_vitepress_sidebar %}
|
|
60
|
+
{% assign sidebar_collections = generated_sidebar.collections %}
|
|
61
|
+
{% assign sidebar_collection = sidebar_collections[page.collection] %}
|
|
62
|
+
{% if sidebar_collection and sidebar_collection.docs %}
|
|
63
|
+
{% assign docs = sidebar_collection.docs %}
|
|
64
|
+
{% else %}
|
|
65
|
+
{% assign docs = site[page.collection] | sort: 'nav_order' %}
|
|
66
|
+
{% endif %}
|
|
60
67
|
{% assign found_current = false %}
|
|
61
68
|
{% for doc in docs %}
|
|
62
69
|
{% if found_current and next_doc == nil %}
|
data/_includes/nav.html
CHANGED
|
@@ -103,8 +103,20 @@
|
|
|
103
103
|
{% if versions and versions.items and versions.items.size > 0 %}
|
|
104
104
|
<div class="VPVersionSelector" id="vp-version-selector">
|
|
105
105
|
<button type="button" class="button" id="vp-version-button" aria-haspopup="true" aria-expanded="false" aria-controls="vp-version-menu">
|
|
106
|
+
{%- comment -%}
|
|
107
|
+
`current` names an item, by id or title, the way
|
|
108
|
+
version_link.html matches it. Show that item's title so a
|
|
109
|
+
manifest holding ids does not label the button "next".
|
|
110
|
+
{%- endcomment -%}
|
|
111
|
+
{%- assign version_current_label = versions.current -%}
|
|
112
|
+
{%- for version_item in versions.items -%}
|
|
113
|
+
{%- assign version_item_key = version_item.id | default: version_item.title -%}
|
|
114
|
+
{%- if version_item_key == versions.current or version_item.title == versions.current -%}
|
|
115
|
+
{%- assign version_current_label = version_item.title -%}
|
|
116
|
+
{%- endif -%}
|
|
117
|
+
{%- endfor -%}
|
|
106
118
|
<span class="text">
|
|
107
|
-
<span>{{
|
|
119
|
+
<span>{{ version_current_label | default: 'Version' }}</span>
|
|
108
120
|
<span class="vpi-chevron-down text-icon"></span>
|
|
109
121
|
</span>
|
|
110
122
|
</button>
|
data/_includes/sidebar.html
CHANGED
|
@@ -3,42 +3,60 @@
|
|
|
3
3
|
<nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1">
|
|
4
4
|
<span class="visually-hidden" id="sidebar-aria-label">Sidebar Navigation</span>
|
|
5
5
|
|
|
6
|
-
{% assign
|
|
6
|
+
{% assign generated_sidebar = site.data.jekyll_vitepress_sidebar %}
|
|
7
|
+
{% assign sidebar_groups = generated_sidebar.groups | default: site.data.sidebar %}
|
|
7
8
|
{% for group in sidebar_groups %}
|
|
8
|
-
{%
|
|
9
|
-
|
|
9
|
+
{% if group.items %}
|
|
10
|
+
{% assign sidebar_items = group.items %}
|
|
11
|
+
{% else %}
|
|
12
|
+
{% assign docs = site[group.collection] | sort: 'nav_order' %}
|
|
13
|
+
{% assign sidebar_items = docs %}
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% if sidebar_items.size > 0 %}
|
|
10
16
|
{% assign has_active = false %}
|
|
11
|
-
{%
|
|
12
|
-
{%
|
|
13
|
-
|
|
14
|
-
{%
|
|
15
|
-
|
|
17
|
+
{% if group.active_urls contains page.url %}
|
|
18
|
+
{% assign has_active = true %}
|
|
19
|
+
{% else %}
|
|
20
|
+
{% for doc in docs %}
|
|
21
|
+
{% if doc.url == page.url %}
|
|
22
|
+
{% assign has_active = true %}
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% endfor %}
|
|
25
|
+
{% endif %}
|
|
26
|
+
{% assign collapsed = false %}
|
|
27
|
+
{% if group.collapsed and has_active == false %}
|
|
28
|
+
{% assign collapsed = true %}
|
|
29
|
+
{% endif %}
|
|
16
30
|
|
|
17
31
|
<div class="group no-transition">
|
|
18
|
-
<section class="VPSidebarItem level-0 collapsible{% if has_active %} has-active{% endif %}" data-vp-sidebar-group data-collection="{{ group.collection }}">
|
|
19
|
-
<div class="item" role="button" tabindex="0">
|
|
32
|
+
<section class="VPSidebarItem level-0 collapsible{% if has_active %} has-active{% endif %}{% if collapsed %} collapsed{% endif %}" data-vp-sidebar-group data-collection="{{ group.collection }}">
|
|
33
|
+
<div class="item" role="button" tabindex="0" aria-expanded="{% if collapsed %}false{% else %}true{% endif %}">
|
|
20
34
|
<div class="indicator"></div>
|
|
21
35
|
<h2 class="text">{{ group.title }}</h2>
|
|
22
|
-
<div class="caret" role="button" aria-label="toggle section" tabindex="0">
|
|
36
|
+
<div class="caret" role="button" aria-label="toggle section" tabindex="0" aria-expanded="{% if collapsed %}false{% else %}true{% endif %}">
|
|
23
37
|
<span class="vpi-chevron-right caret-icon"></span>
|
|
24
38
|
</div>
|
|
25
39
|
</div>
|
|
26
40
|
|
|
27
41
|
<div class="items">
|
|
28
|
-
{%
|
|
29
|
-
{%
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
{% if group.items %}
|
|
43
|
+
{% include sidebar_items.html nodes=group.items level=1 %}
|
|
44
|
+
{% else %}
|
|
45
|
+
{% for doc in docs %}
|
|
46
|
+
{% assign active = false %}
|
|
47
|
+
{% if doc.url == page.url %}
|
|
48
|
+
{% assign active = true %}
|
|
49
|
+
{% endif %}
|
|
50
|
+
<div class="VPSidebarItem level-1 is-link{% if active %} is-active{% endif %}">
|
|
51
|
+
<div class="item">
|
|
52
|
+
<div class="indicator"></div>
|
|
53
|
+
<a class="VPLink link link" href="{{ doc.url | relative_url }}" data-vp-sidebar-link data-turbo="true" data-turbo-frame="vp-content-frame" data-turbo-action="advance">
|
|
54
|
+
<p class="text">{{ doc.title }}</p>
|
|
55
|
+
</a>
|
|
56
|
+
</div>
|
|
39
57
|
</div>
|
|
40
|
-
|
|
41
|
-
{%
|
|
58
|
+
{% endfor %}
|
|
59
|
+
{% endif %}
|
|
42
60
|
</div>
|
|
43
61
|
</section>
|
|
44
62
|
</div>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{% for node in include.nodes %}
|
|
2
|
+
{% assign item_level = include.level | default: 1 %}
|
|
3
|
+
{% assign doc = node.doc %}
|
|
4
|
+
{% assign has_children = false %}
|
|
5
|
+
{% if node.children and node.children.size > 0 %}
|
|
6
|
+
{% assign has_children = true %}
|
|
7
|
+
{% endif %}
|
|
8
|
+
{% assign active = false %}
|
|
9
|
+
{% if node.url == page.url %}
|
|
10
|
+
{% assign active = true %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% assign has_active = false %}
|
|
13
|
+
{% if node.active_urls contains page.url %}
|
|
14
|
+
{% assign has_active = true %}
|
|
15
|
+
{% endif %}
|
|
16
|
+
{% assign collapsed = false %}
|
|
17
|
+
{% if node.collapsed and has_active == false %}
|
|
18
|
+
{% assign collapsed = true %}
|
|
19
|
+
{% endif %}
|
|
20
|
+
|
|
21
|
+
<div class="VPSidebarItem level-{{ item_level }} is-link{% if has_children %} collapsible{% endif %}{% if active %} is-active{% endif %}{% if has_active %} has-active{% endif %}{% if collapsed %} collapsed{% endif %}">
|
|
22
|
+
<div class="item"{% if has_children %} aria-expanded="{% if collapsed %}false{% else %}true{% endif %}"{% endif %}>
|
|
23
|
+
<div class="indicator"></div>
|
|
24
|
+
<a class="VPLink link link" href="{{ node.url | relative_url }}" data-vp-sidebar-link data-turbo="true" data-turbo-frame="vp-content-frame" data-turbo-action="advance">
|
|
25
|
+
<p class="text">{{ node.title | default: doc.title }}</p>
|
|
26
|
+
</a>
|
|
27
|
+
{% if has_children %}
|
|
28
|
+
<div class="caret" role="button" aria-label="toggle section" tabindex="0" aria-expanded="{% if collapsed %}false{% else %}true{% endif %}">
|
|
29
|
+
<span class="vpi-chevron-right caret-icon"></span>
|
|
30
|
+
</div>
|
|
31
|
+
{% endif %}
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
{% if has_children %}
|
|
35
|
+
<div class="items">
|
|
36
|
+
{% assign child_level = item_level | plus: 1 %}
|
|
37
|
+
{% include sidebar_items.html nodes=node.children level=child_level %}
|
|
38
|
+
</div>
|
|
39
|
+
{% endif %}
|
|
40
|
+
</div>
|
|
41
|
+
{% endfor %}
|
|
@@ -329,6 +329,11 @@ body.vp-nav-screen-open .VPNav {
|
|
|
329
329
|
width: 100%;
|
|
330
330
|
height: 100%;
|
|
331
331
|
fill: currentcolor;
|
|
332
|
+
|
|
333
|
+
/* These icons draw a 16-unit viewBox into a 13-14px box. Their paths touch
|
|
334
|
+
the viewBox edge, so at a fractional scale the SVG viewport clips the
|
|
335
|
+
outermost column and the shape looks flat on one side. */
|
|
336
|
+
overflow: visible;
|
|
332
337
|
}
|
|
333
338
|
|
|
334
339
|
.VPNavBarStarText,
|
|
@@ -1120,6 +1125,11 @@ body.vp-search-open {
|
|
|
1120
1125
|
width: 100%;
|
|
1121
1126
|
height: 100%;
|
|
1122
1127
|
fill: currentcolor;
|
|
1128
|
+
|
|
1129
|
+
/* These icons draw a 16-unit viewBox into a 13-14px box. Their paths touch
|
|
1130
|
+
the viewBox edge, so at a fractional scale the SVG viewport clips the
|
|
1131
|
+
outermost column and the shape looks flat on one side. */
|
|
1132
|
+
overflow: visible;
|
|
1123
1133
|
}
|
|
1124
1134
|
|
|
1125
1135
|
.VPMetricButtonCount {
|
|
@@ -684,12 +684,28 @@
|
|
|
684
684
|
syncNavTop();
|
|
685
685
|
window.addEventListener('scroll', syncNavTop, { passive: true });
|
|
686
686
|
|
|
687
|
-
|
|
687
|
+
function setSidebarSectionCollapsed(section, collapsed) {
|
|
688
|
+
section.classList.toggle('collapsed', collapsed);
|
|
689
|
+
|
|
690
|
+
var item = section.querySelector(':scope > .item');
|
|
691
|
+
var caret = section.querySelector(':scope > .item .caret');
|
|
692
|
+
var expanded = String(!collapsed);
|
|
693
|
+
|
|
694
|
+
if (item) {
|
|
695
|
+
item.setAttribute('aria-expanded', expanded);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (caret) {
|
|
699
|
+
caret.setAttribute('aria-expanded', expanded);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
document.querySelectorAll('.VPSidebarItem.collapsible').forEach(function (section) {
|
|
688
704
|
var item = section.querySelector(':scope > .item');
|
|
689
705
|
var caret = section.querySelector(':scope > .item .caret');
|
|
690
706
|
|
|
691
707
|
function toggleSection() {
|
|
692
|
-
section.classList.
|
|
708
|
+
setSidebarSectionCollapsed(section, !section.classList.contains('collapsed'));
|
|
693
709
|
}
|
|
694
710
|
|
|
695
711
|
if (item) {
|
|
@@ -701,6 +717,10 @@
|
|
|
701
717
|
});
|
|
702
718
|
|
|
703
719
|
item.addEventListener('keydown', function (event) {
|
|
720
|
+
if (event.target.closest('a')) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
704
724
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
705
725
|
event.preventDefault();
|
|
706
726
|
toggleSection();
|
|
@@ -2174,7 +2194,7 @@
|
|
|
2174
2194
|
return null;
|
|
2175
2195
|
}
|
|
2176
2196
|
|
|
2177
|
-
var activeItems = Array.from(sidebar.querySelectorAll('.VPSidebarItem.
|
|
2197
|
+
var activeItems = Array.from(sidebar.querySelectorAll('.VPSidebarItem.is-link.is-active'));
|
|
2178
2198
|
|
|
2179
2199
|
if (!activeItems.length) {
|
|
2180
2200
|
return null;
|
|
@@ -2212,16 +2232,19 @@
|
|
|
2212
2232
|
}
|
|
2213
2233
|
|
|
2214
2234
|
document.querySelectorAll('[data-vp-sidebar-link]').forEach(function (link) {
|
|
2215
|
-
var item = link.closest('.VPSidebarItem.
|
|
2235
|
+
var item = link.closest('.VPSidebarItem.is-link');
|
|
2216
2236
|
var active = normalizePathname(link.getAttribute('href')) === currentPath;
|
|
2217
2237
|
if (item) {
|
|
2218
2238
|
item.classList.toggle('is-active', active);
|
|
2219
2239
|
}
|
|
2220
2240
|
});
|
|
2221
2241
|
|
|
2222
|
-
document.querySelectorAll('
|
|
2223
|
-
var hasActiveLink = !!group.querySelector('.VPSidebarItem.
|
|
2242
|
+
document.querySelectorAll('.VPSidebarItem.collapsible').forEach(function (group) {
|
|
2243
|
+
var hasActiveLink = group.classList.contains('is-active') || !!group.querySelector('.VPSidebarItem.is-link.is-active');
|
|
2224
2244
|
group.classList.toggle('has-active', hasActiveLink);
|
|
2245
|
+
if (hasActiveLink) {
|
|
2246
|
+
setSidebarSectionCollapsed(group, false);
|
|
2247
|
+
}
|
|
2225
2248
|
});
|
|
2226
2249
|
|
|
2227
2250
|
document.querySelectorAll('[data-vp-nav-link]').forEach(function (link) {
|
|
@@ -3,6 +3,248 @@ require 'rouge'
|
|
|
3
3
|
|
|
4
4
|
module Jekyll
|
|
5
5
|
module VitePressTheme
|
|
6
|
+
# rubocop:disable Metrics/ModuleLength
|
|
7
|
+
module Sidebar
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
DATA_KEY = 'jekyll_vitepress_sidebar'.freeze
|
|
11
|
+
MAX_ITEM_LEVEL = 5
|
|
12
|
+
|
|
13
|
+
def apply(site)
|
|
14
|
+
generated_sidebar = generate(site)
|
|
15
|
+
site.data[DATA_KEY] = generated_sidebar if generated_sidebar
|
|
16
|
+
rescue StandardError => e
|
|
17
|
+
Jekyll.logger.warn('jekyll-vitepress-theme', "Sidebar hierarchy generation failed: #{e.message}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# A site with collection documents and no _data/sidebar.yml builds a page
|
|
21
|
+
# with an empty sidebar and no other complaint, which is a slow thing to
|
|
22
|
+
# notice. Overriding `data_dir` without carrying the file over is the
|
|
23
|
+
# usual cause.
|
|
24
|
+
def warn_missing_sidebar_data(site)
|
|
25
|
+
return if site.collections.values.all? { |collection| collection.docs.empty? }
|
|
26
|
+
|
|
27
|
+
Jekyll.logger.warn(
|
|
28
|
+
'jekyll-vitepress-theme',
|
|
29
|
+
'No _data/sidebar.yml found, so the sidebar will be empty. ' \
|
|
30
|
+
'If you set a custom data_dir, copy the theme data files into it.'
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate(site)
|
|
35
|
+
sidebar_groups = site.data['sidebar']
|
|
36
|
+
unless sidebar_groups.respond_to?(:each)
|
|
37
|
+
warn_missing_sidebar_data(site)
|
|
38
|
+
return nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
groups = sidebar_groups.filter_map { |group| generated_group(site, group) }
|
|
42
|
+
|
|
43
|
+
{
|
|
44
|
+
'groups' => groups,
|
|
45
|
+
'collections' => groups.to_h { |group| [group['collection'], collection_data(group)] }
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def generated_group(site, group)
|
|
50
|
+
collection_name = group_value(group, 'collection')
|
|
51
|
+
docs = collection_docs(site, collection_name)
|
|
52
|
+
return nil if docs.empty?
|
|
53
|
+
|
|
54
|
+
collection_data = build_collection(collection_name, docs)
|
|
55
|
+
return nil if collection_data['docs'].empty?
|
|
56
|
+
|
|
57
|
+
group_hash(group).merge(collection_data)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def collection_data(group)
|
|
61
|
+
group.slice('collection', 'items', 'docs', 'active_urls')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def build_collection(collection_name, docs)
|
|
65
|
+
ordered_docs = sort_docs(docs)
|
|
66
|
+
nodes = ordered_docs.to_h { |doc| [doc, node_for(doc)] }
|
|
67
|
+
roots = attach_nodes(collection_name, ordered_docs, nodes)
|
|
68
|
+
|
|
69
|
+
finalize_nodes(roots, 1)
|
|
70
|
+
flat_docs = flatten_docs(roots)
|
|
71
|
+
{
|
|
72
|
+
'collection' => collection_name.to_s,
|
|
73
|
+
'items' => roots,
|
|
74
|
+
'docs' => flat_docs,
|
|
75
|
+
'active_urls' => flat_docs.filter_map { |doc| doc_url(doc) }
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def attach_nodes(collection_name, docs, nodes)
|
|
80
|
+
docs.each_with_object([]) do |doc, roots|
|
|
81
|
+
parent_doc = parent_doc_for(doc, docs)
|
|
82
|
+
if valid_parent?(doc, parent_doc, docs)
|
|
83
|
+
nodes[parent_doc]['children'] << nodes[doc]
|
|
84
|
+
else
|
|
85
|
+
warn_missing_parent(collection_name, doc) if parent_title(doc)
|
|
86
|
+
roots << nodes[doc]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def valid_parent?(doc, parent_doc, docs)
|
|
92
|
+
parent_doc && !attaching_creates_cycle?(doc, parent_doc, docs)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def collection_docs(site, collection_name)
|
|
96
|
+
return [] unless collection_name
|
|
97
|
+
|
|
98
|
+
collection = site.collections[collection_name.to_s]
|
|
99
|
+
return [] unless collection
|
|
100
|
+
|
|
101
|
+
collection.docs
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def group_hash(group)
|
|
105
|
+
return group if group.is_a?(Hash)
|
|
106
|
+
|
|
107
|
+
group.respond_to?(:to_h) ? group.to_h : {}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def group_value(group, key)
|
|
111
|
+
group_hash(group)[key] || group_hash(group)[key.to_sym]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def sort_docs(docs)
|
|
115
|
+
docs.sort_by { |doc| sort_key(doc) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def sort_key(doc)
|
|
119
|
+
nav_order = data_value(doc, 'nav_order')
|
|
120
|
+
order_bucket = nav_order.nil? ? 1 : 0
|
|
121
|
+
numeric_order = numeric?(nav_order)
|
|
122
|
+
order_type = numeric_order ? 0 : 1
|
|
123
|
+
order_value = numeric_order ? nav_order.to_f : nav_order.to_s
|
|
124
|
+
|
|
125
|
+
[order_bucket, order_type, order_value, title(doc).downcase, doc_url(doc).to_s]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def numeric?(value)
|
|
129
|
+
value.is_a?(Numeric) || value.to_s.match?(/\A-?\d+(?:\.\d+)?\z/)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def node_for(doc)
|
|
133
|
+
{
|
|
134
|
+
'doc' => doc,
|
|
135
|
+
'title' => title(doc),
|
|
136
|
+
'url' => doc_url(doc),
|
|
137
|
+
'collapsed' => truthy?(data_value(doc, 'collapsed')),
|
|
138
|
+
'children' => [],
|
|
139
|
+
'active_urls' => []
|
|
140
|
+
}
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def parent_doc_for(doc, docs)
|
|
144
|
+
direct_parent = parent_title(doc)
|
|
145
|
+
return nil unless direct_parent
|
|
146
|
+
|
|
147
|
+
candidates = docs.select { |candidate| candidate != doc && title(candidate) == direct_parent }
|
|
148
|
+
grand_parent = normalized_string(data_value(doc, 'grand_parent'))
|
|
149
|
+
return candidates.first unless grand_parent
|
|
150
|
+
|
|
151
|
+
candidates.find { |candidate| ancestor_titles(candidate, docs).include?(grand_parent) }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def attaching_creates_cycle?(doc, parent_doc, docs)
|
|
155
|
+
current = parent_doc
|
|
156
|
+
seen = []
|
|
157
|
+
|
|
158
|
+
while current
|
|
159
|
+
return true if current == doc || seen.include?(current)
|
|
160
|
+
|
|
161
|
+
seen << current
|
|
162
|
+
current = parent_doc_for(current, docs)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
false
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def ancestor_titles(doc, docs)
|
|
169
|
+
titles = []
|
|
170
|
+
current = parent_doc_for(doc, docs)
|
|
171
|
+
seen = []
|
|
172
|
+
|
|
173
|
+
while current && !seen.include?(current)
|
|
174
|
+
seen << current
|
|
175
|
+
titles << title(current)
|
|
176
|
+
current = parent_doc_for(current, docs)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
titles
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def finalize_nodes(nodes, level)
|
|
183
|
+
nodes.sort_by! { |node| sort_key(node['doc']) }
|
|
184
|
+
nodes.each do |node|
|
|
185
|
+
finalize_children(node, level)
|
|
186
|
+
|
|
187
|
+
node['active_urls'] = [node['url'], *node['children'].flat_map { |child| child['active_urls'] }].compact
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def finalize_children(node, level)
|
|
192
|
+
if level >= MAX_ITEM_LEVEL
|
|
193
|
+
warn_depth_limit(node) unless node['children'].empty?
|
|
194
|
+
node['children'] = []
|
|
195
|
+
else
|
|
196
|
+
finalize_nodes(node['children'], level + 1)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def flatten_docs(nodes)
|
|
201
|
+
nodes.flat_map do |node|
|
|
202
|
+
[node['doc'], *flatten_docs(node['children'])]
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def data_value(doc, key)
|
|
207
|
+
data = doc.respond_to?(:data) ? doc.data : {}
|
|
208
|
+
data[key] || data[key.to_sym]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def title(doc)
|
|
212
|
+
normalized_string(data_value(doc, 'title')) || ''
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def parent_title(doc)
|
|
216
|
+
normalized_string(data_value(doc, 'parent'))
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def doc_url(doc)
|
|
220
|
+
doc.url if doc.respond_to?(:url)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def normalized_string(value)
|
|
224
|
+
string = value.to_s.strip
|
|
225
|
+
string.empty? ? nil : string
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def truthy?(value)
|
|
229
|
+
value == true || value.to_s.casecmp('true').zero?
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def warn_missing_parent(collection_name, doc)
|
|
233
|
+
Jekyll.logger.warn(
|
|
234
|
+
'jekyll-vitepress-theme',
|
|
235
|
+
"Missing sidebar parent '#{parent_title(doc)}' for '#{title(doc)}' in #{collection_name}; rendering it at the collection root."
|
|
236
|
+
)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def warn_depth_limit(node)
|
|
240
|
+
Jekyll.logger.warn(
|
|
241
|
+
'jekyll-vitepress-theme',
|
|
242
|
+
"Sidebar item '#{node['title']}' is deeper than #{MAX_ITEM_LEVEL} item levels; nested children were not rendered."
|
|
243
|
+
)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
# rubocop:enable Metrics/ModuleLength
|
|
247
|
+
|
|
6
248
|
module SearchIndex
|
|
7
249
|
module_function
|
|
8
250
|
|
|
@@ -19,9 +261,14 @@ module Jekyll
|
|
|
19
261
|
}
|
|
20
262
|
{% assign first = false %}
|
|
21
263
|
{% endif %}
|
|
22
|
-
{% assign
|
|
264
|
+
{% assign generated_sidebar = site.data.jekyll_vitepress_sidebar %}
|
|
265
|
+
{% assign sidebar_groups = generated_sidebar.groups | default: site.data.sidebar %}
|
|
23
266
|
{% for group in sidebar_groups %}
|
|
24
|
-
{%
|
|
267
|
+
{% if group.docs %}
|
|
268
|
+
{% assign docs = group.docs %}
|
|
269
|
+
{% else %}
|
|
270
|
+
{% assign docs = site[group.collection] | sort: 'nav_order' %}
|
|
271
|
+
{% endif %}
|
|
25
272
|
{% for doc in docs %}
|
|
26
273
|
{% if doc.title and doc.url %}
|
|
27
274
|
{% unless first %},{% endunless %}
|
|
@@ -154,7 +401,18 @@ module Jekyll
|
|
|
154
401
|
module_function
|
|
155
402
|
|
|
156
403
|
AUTO_VALUE = 'auto'.freeze
|
|
157
|
-
|
|
404
|
+
VERSION_PATTERN = /VERSION\s*=\s*['"]([^'"]+)['"]/
|
|
405
|
+
|
|
406
|
+
# Resolves `current: auto` to the version of the project being documented.
|
|
407
|
+
# Configure where to read it from:
|
|
408
|
+
#
|
|
409
|
+
# jekyll_vitepress:
|
|
410
|
+
# version:
|
|
411
|
+
# value: "2.0.0" # literal, wins over file
|
|
412
|
+
# file: lib/my_gem/version.rb # or a file holding VERSION = '...'
|
|
413
|
+
#
|
|
414
|
+
# With neither, it falls back to this theme's own version, which is only
|
|
415
|
+
# ever right for the theme's own documentation.
|
|
158
416
|
def apply(site)
|
|
159
417
|
versions = site.data['versions']
|
|
160
418
|
return unless versions.is_a?(Hash)
|
|
@@ -162,11 +420,35 @@ module Jekyll
|
|
|
162
420
|
current_value = versions['current'] || versions[:current]
|
|
163
421
|
return unless auto_value?(current_value)
|
|
164
422
|
|
|
165
|
-
versions['current'] = "v#{Jekyll::VitePressTheme::VERSION}"
|
|
423
|
+
versions['current'] = "v#{site_version(site) || Jekyll::VitePressTheme::VERSION}"
|
|
166
424
|
rescue StandardError => e
|
|
167
425
|
Jekyll.logger.warn('jekyll-vitepress-theme', "Version label resolution failed: #{e.message}")
|
|
168
426
|
end
|
|
169
427
|
|
|
428
|
+
def site_version(site)
|
|
429
|
+
config = site.config.dig('jekyll_vitepress', 'version')
|
|
430
|
+
return nil unless config.is_a?(Hash)
|
|
431
|
+
|
|
432
|
+
literal = config['value'].to_s.strip
|
|
433
|
+
return normalize(literal) unless literal.empty?
|
|
434
|
+
|
|
435
|
+
version_from_file(site, config['file'])
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def version_from_file(site, path)
|
|
439
|
+
return nil if path.to_s.strip.empty?
|
|
440
|
+
|
|
441
|
+
full_path = File.expand_path(path.to_s, site.source)
|
|
442
|
+
return nil unless File.file?(full_path)
|
|
443
|
+
|
|
444
|
+
match = File.read(full_path)[VERSION_PATTERN]
|
|
445
|
+
match && normalize(Regexp.last_match(1))
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def normalize(value)
|
|
449
|
+
value.to_s.strip.sub(/\Av/, '')
|
|
450
|
+
end
|
|
451
|
+
|
|
170
452
|
def auto_value?(value)
|
|
171
453
|
value.to_s.strip.casecmp(AUTO_VALUE).zero?
|
|
172
454
|
end
|
|
@@ -236,6 +518,7 @@ end
|
|
|
236
518
|
Jekyll::Hooks.register :site, :post_read do |site|
|
|
237
519
|
Jekyll::VitePressTheme::VersionLabel.apply(site)
|
|
238
520
|
Jekyll::VitePressTheme::RougeStyles.apply(site)
|
|
521
|
+
Jekyll::VitePressTheme::Sidebar.apply(site)
|
|
239
522
|
Jekyll::VitePressTheme::SearchIndex.apply(site)
|
|
240
523
|
end
|
|
241
524
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-vitepress-theme
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carmine Paolino
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -79,6 +79,7 @@ files:
|
|
|
79
79
|
- _includes/rubygems_downloads_button.html
|
|
80
80
|
- _includes/search.html
|
|
81
81
|
- _includes/sidebar.html
|
|
82
|
+
- _includes/sidebar_items.html
|
|
82
83
|
- _includes/version_link.html
|
|
83
84
|
- _includes/vp_image.html
|
|
84
85
|
- _layouts/default.html
|