jekyll-vitepress-theme 1.2.0 → 1.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9f21e5b46c57108b586ef2fd12ffeb0a63b67e2a8a9728814cd1b4a59a4c592
4
- data.tar.gz: 36ab852e71ca5c212f3d4c9b0f14c5f9ad117447d79ee14fa036180f7585b6db
3
+ metadata.gz: 87a44689ffa884d7e6d6e6a7ab80c6919ecd5e09d828307d16a94816176fed28
4
+ data.tar.gz: 0b1346f4a809c05492379ac6464b958b251d4977e4a86fd7bfee6b758916856a
5
5
  SHA512:
6
- metadata.gz: a0835b87b12521255a8c25114be2c9f05143f0317c0eb0387864c243b3e3215980f5d91cb24583036c4acaef2d7dbc777ecde37cc450273c4530fbdcf9cb3ac7
7
- data.tar.gz: 7238521b04688036a2d79258cecfab250b8c99ff31abe3352b2b9a6ddedaf61e45b5accfa106f54c8c5845c31d7d8802853b54a0357b077f27a1f33792ea7ba1
6
+ metadata.gz: 137bbe877456978ccfd6d4565a3c05f6efa686f6361b8970ea21eb0aa93d659db5f286e460dd2d398ccab8b39e09f0c33c1b77e50a01a966ee161f459db467f9
7
+ data.tar.gz: 9ce78c0cc99df03cb514841771d83b8de04a058418dcb7694c69b631dcb2e1a3b1b38f3b4bf86e159f2babc1ab50ae9700f59fe031910f3713fcf193e8e390a0
data/README.md CHANGED
@@ -7,6 +7,7 @@ A reusable Jekyll theme gem that reproduces the VitePress default docs experienc
7
7
  - VitePress-style layout structure (top nav, sidebar, outline, doc footer)
8
8
  - Appearance toggle with `auto -> dark -> light`
9
9
  - Local search modal (`/`, `Ctrl/Cmd+K`, `Cmd+K`)
10
+ - Automatic `/search.json` generation for the home page and sidebar collections
10
11
  - Optional GitHub star button with live count (`jekyll_vitepress.github_star`)
11
12
  - Code block copy button, language labels, file-title bars and icons
12
13
  - Page-level "Copy page" split button with raw Markdown copy + plain `.md` view (`jekyll_vitepress.copy_page`, enabled by default)
@@ -1700,15 +1700,26 @@ html.dark .only-light {
1700
1700
  }
1701
1701
 
1702
1702
  /* Copy as Markdown button group */
1703
- .vp-doc-header {
1703
+ .vp-doc-header,
1704
+ .vp-doc-title-row {
1704
1705
  display: flex;
1705
1706
  align-items: center;
1706
1707
  justify-content: space-between;
1707
1708
  gap: 12px;
1709
+ flex-wrap: wrap;
1708
1710
  }
1709
1711
 
1710
- .vp-doc-header h1 {
1712
+ .vp-doc-header h1,
1713
+ .vp-doc-title-row h1 {
1711
1714
  margin-top: 0;
1715
+ margin-bottom: 0;
1716
+ flex: 1 1 320px;
1717
+ min-width: 0;
1718
+ }
1719
+
1720
+ .vp-doc-header .copy-md-group,
1721
+ .vp-doc-title-row .copy-md-group {
1722
+ margin-left: auto;
1712
1723
  }
1713
1724
 
1714
1725
  /* Copy page icon defs */
@@ -1164,16 +1164,72 @@
1164
1164
  addCopyButtons();
1165
1165
 
1166
1166
  // Copy page as Markdown - split button with dropdown
1167
+ function markdownStartsWithH1(markdown) {
1168
+ var value = String(markdown || '').replace(/^\s+/, '');
1169
+ return /^#\s+\S/.test(value) || /^<h1(?:\s|>)/i.test(value) || /^[^\n]+\n=+\s*(?:\n|$)/.test(value);
1170
+ }
1171
+
1172
+ function resolveCopyPageTitleHeading() {
1173
+ var doc = document.querySelector('.vp-doc');
1174
+ if (!doc) {
1175
+ return null;
1176
+ }
1177
+
1178
+ var headerHeading = doc.querySelector('.vp-doc-header h1');
1179
+ if (headerHeading) {
1180
+ return headerHeading;
1181
+ }
1182
+
1183
+ var headings = Array.from(doc.querySelectorAll('h1'));
1184
+ for (var i = 0; i < headings.length; i += 1) {
1185
+ if (!headings[i].closest('.vp-doc-header')) {
1186
+ return headings[i];
1187
+ }
1188
+ }
1189
+
1190
+ return null;
1191
+ }
1192
+
1193
+ function alignCopyPageButtonWithHeading() {
1194
+ var header = document.querySelector('.vp-doc-header');
1195
+ var group = header && header.querySelector('.copy-md-group');
1196
+ if (!header || !group || header.querySelector('h1')) {
1197
+ return;
1198
+ }
1199
+
1200
+ var titleHeading = resolveCopyPageTitleHeading();
1201
+ if (!titleHeading) {
1202
+ return;
1203
+ }
1204
+
1205
+ var titleRow = titleHeading.closest('.vp-doc-title-row');
1206
+ if (!titleRow) {
1207
+ titleRow = document.createElement('div');
1208
+ titleRow.className = 'vp-doc-title-row';
1209
+ titleHeading.parentNode.insertBefore(titleRow, titleHeading);
1210
+ titleRow.appendChild(titleHeading);
1211
+ }
1212
+
1213
+ titleRow.appendChild(group);
1214
+
1215
+ if (!header.querySelector('*')) {
1216
+ header.hidden = true;
1217
+ }
1218
+ }
1219
+
1220
+ alignCopyPageButtonWithHeading();
1221
+
1167
1222
  function copyPageMarkdown(btn) {
1223
+ if (!btn) return;
1224
+
1168
1225
  var textarea = document.querySelector('.vp-raw-markdown');
1169
1226
  if (!textarea) return;
1170
1227
 
1171
1228
  var md = textarea.value;
1172
- if (!/^#\s/.test(md.trim())) {
1173
- var header = document.querySelector('.vp-doc-header');
1174
- var autoH1 = header && header.querySelector('h1');
1175
- if (autoH1) {
1176
- var titleText = autoH1.textContent.trim();
1229
+ if (!markdownStartsWithH1(md)) {
1230
+ var titleHeading = resolveCopyPageTitleHeading();
1231
+ if (titleHeading) {
1232
+ var titleText = titleHeading.textContent.trim();
1177
1233
  if (titleText) {
1178
1234
  md = '# ' + titleText + '\n\n' + md;
1179
1235
  }
@@ -3,6 +3,75 @@ require 'rouge'
3
3
 
4
4
  module Jekyll
5
5
  module VitePressTheme
6
+ module SearchIndex
7
+ module_function
8
+
9
+ TEMPLATE = <<~LIQUID.freeze
10
+ [
11
+ {% assign first = true %}
12
+ {% assign home_page = site.pages | where: 'url', '/' | first %}
13
+ {% if home_page %}
14
+ {% assign home_excerpt = home_page.content | markdownify | strip_html | strip_newlines | replace: ' ', ' ' | strip | truncate: 2400, '' %}
15
+ {
16
+ "title": {{ home_page.title | default: site.title | strip_html | strip | jsonify }},
17
+ "url": {{ home_page.url | relative_url | jsonify }},
18
+ "content": {{ home_excerpt | jsonify }}
19
+ }
20
+ {% assign first = false %}
21
+ {% endif %}
22
+ {% assign sidebar_groups = site.data.sidebar %}
23
+ {% for group in sidebar_groups %}
24
+ {% assign docs = site[group.collection] | sort: 'nav_order' %}
25
+ {% for doc in docs %}
26
+ {% if doc.title and doc.url %}
27
+ {% unless first %},{% endunless %}
28
+ {% assign excerpt = doc.content | markdownify | strip_html | strip_newlines | replace: ' ', ' ' | strip | truncate: 2400, '' %}
29
+ {
30
+ "title": {{ doc.title | strip_html | strip | jsonify }},
31
+ "url": {{ doc.url | relative_url | jsonify }},
32
+ "content": {{ excerpt | jsonify }}
33
+ }
34
+ {% assign first = false %}
35
+ {% endif %}
36
+ {% endfor %}
37
+ {% endfor %}
38
+ ]
39
+ LIQUID
40
+
41
+ class GeneratedPage < Jekyll::PageWithoutAFile
42
+ def initialize(site)
43
+ super(site, site.source, '', 'search.json')
44
+
45
+ self.content = TEMPLATE
46
+ data['layout'] = nil
47
+ data['permalink'] = '/search.json'
48
+ end
49
+ end
50
+
51
+ def apply(site)
52
+ return if custom_page?(site)
53
+
54
+ site.pages << GeneratedPage.new(site)
55
+ end
56
+
57
+ def custom_page?(site)
58
+ site.pages.any? { |page| search_index_path?(page.path) || search_index_url?(page.url) } ||
59
+ site.static_files.any? { |file| search_index_path?(file.path) || search_index_url?(file.relative_path) }
60
+ end
61
+
62
+ def search_index_path?(value)
63
+ return false unless value
64
+
65
+ Pathname.new(value.to_s).basename.to_s == 'search.json'
66
+ rescue ArgumentError
67
+ false
68
+ end
69
+
70
+ def search_index_url?(value)
71
+ value.to_s.strip == '/search.json'
72
+ end
73
+ end
74
+
6
75
  module LastUpdated
7
76
  module_function
8
77
 
@@ -104,46 +173,81 @@ module Jekyll
104
173
  value.to_s.strip.casecmp(AUTO_VALUE).zero?
105
174
  end
106
175
  end
176
+
177
+ module CopyPage
178
+ module_function
179
+
180
+ def enabled?(item)
181
+ theme_config = item.site.config['jekyll_vitepress']
182
+ copy_page_disabled = theme_config.is_a?(Hash) &&
183
+ theme_config['copy_page'].is_a?(Hash) &&
184
+ theme_config['copy_page']['enabled'] == false
185
+
186
+ unless copy_page_disabled
187
+ page_theme = item.data['jekyll_vitepress']
188
+ copy_page_disabled = page_theme == false || (page_theme.is_a?(Hash) && page_theme['copy_page'] == false)
189
+ end
190
+
191
+ !copy_page_disabled
192
+ end
193
+
194
+ def resolved_markdown(item, payload)
195
+ raw = item.content.to_s
196
+ return raw unless item.respond_to?(:render_with_liquid?) && item.render_with_liquid?
197
+
198
+ item.renderer.render_liquid(raw, payload, liquid_render_info(item, payload), item.path)
199
+ rescue StandardError => e
200
+ relative_path = item.respond_to?(:relative_path) ? item.relative_path : item.path
201
+ Jekyll.logger.warn('jekyll-vitepress-theme', "Copy page markdown capture failed for #{relative_path}: #{e.message}")
202
+ raw
203
+ end
204
+
205
+ def liquid_render_info(item, payload)
206
+ liquid_options = item.site.config['liquid'] || {}
207
+
208
+ {
209
+ registers: { site: item.site, page: payload['page'] },
210
+ strict_filters: liquid_options['strict_filters'],
211
+ strict_variables: liquid_options['strict_variables']
212
+ }
213
+ end
214
+
215
+ def leading_h1?(markdown)
216
+ return false if markdown.nil?
217
+
218
+ stripped = markdown.lstrip
219
+ return false if stripped.empty?
220
+
221
+ stripped.match?(/\A#\s+\S/) ||
222
+ stripped.match?(/\A<h1(?:\s|>)/i) ||
223
+ stripped.match?(/\A[^\n]+\n=+\s*(?:\n|$)/)
224
+ end
225
+ end
107
226
  end
108
227
  end
109
228
 
110
229
  Jekyll::Hooks.register :site, :post_read do |site|
111
230
  Jekyll::VitePressTheme::VersionLabel.apply(site)
112
231
  Jekyll::VitePressTheme::RougeStyles.apply(site)
232
+ Jekyll::VitePressTheme::SearchIndex.apply(site)
113
233
  end
114
234
 
115
- Jekyll::Hooks.register :documents, :pre_render do |document|
116
- theme_config = document.site.config['jekyll_vitepress']
117
- copy_page_disabled = theme_config.is_a?(Hash) &&
118
- theme_config['copy_page'].is_a?(Hash) &&
119
- theme_config['copy_page']['enabled'] == false
120
-
121
- unless copy_page_disabled
122
- page_theme = document.data['jekyll_vitepress']
123
- copy_page_disabled = page_theme == false || (page_theme.is_a?(Hash) && page_theme['copy_page'] == false)
235
+ Jekyll::Hooks.register :documents, :pre_render do |document, payload|
236
+ if Jekyll::VitePressTheme::CopyPage.enabled?(document)
237
+ document.data['_raw_markdown'] = Jekyll::VitePressTheme::CopyPage.resolved_markdown(document, payload)
124
238
  end
125
239
 
126
- document.data['_raw_markdown'] = document.content unless copy_page_disabled
127
-
128
240
  next if document.data.key?('last_updated_at')
129
241
 
130
242
  updated_at = Jekyll::VitePressTheme::LastUpdated.source_file_time(document.site, document.path)
131
243
  document.data['last_updated_at'] = updated_at if updated_at
132
244
  end
133
245
 
134
- Jekyll::Hooks.register :pages, :pre_render do |page|
135
- theme_config = page.site.config['jekyll_vitepress']
136
- copy_page_disabled = theme_config.is_a?(Hash) &&
137
- theme_config['copy_page'].is_a?(Hash) &&
138
- theme_config['copy_page']['enabled'] == false
139
-
140
- unless copy_page_disabled
141
- page_theme = page.data['jekyll_vitepress']
142
- copy_page_disabled = page_theme == false || (page_theme.is_a?(Hash) && page_theme['copy_page'] == false)
246
+ Jekyll::Hooks.register :pages, :pre_render do |page, payload|
247
+ if Jekyll::VitePressTheme::CopyPage.enabled?(page)
248
+ page.data['_raw_markdown'] = Jekyll::VitePressTheme::CopyPage.resolved_markdown(page, payload)
143
249
  end
144
250
 
145
- page.data['_raw_markdown'] = page.content unless copy_page_disabled
146
-
147
251
  next if page.data.key?('last_updated_at')
148
252
 
149
253
  updated_at = Jekyll::VitePressTheme::LastUpdated.source_file_time(page.site, page.path)
@@ -171,7 +275,7 @@ Jekyll::Hooks.register :site, :post_write do |site|
171
275
  md_path = '/index.md' if item.url == '/'
172
276
 
173
277
  title = item.data['title']
174
- body = if title && !title.empty? && !raw.strip.start_with?('# ')
278
+ body = if title && !title.empty? && !Jekyll::VitePressTheme::CopyPage.leading_h1?(raw)
175
279
  "# #{title}\n\n#{raw}"
176
280
  else
177
281
  raw
@@ -1,5 +1,5 @@
1
1
  module Jekyll
2
2
  module VitePressTheme
3
- VERSION = "1.2.0".freeze
3
+ VERSION = "1.2.2".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-vitepress-theme
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino