jekyll-vitepress-theme 1.10.0 → 1.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe14ff0d5fdef3e958e33fdb1d0af6325209f095c352b5c7b09e42cc4da38316
4
- data.tar.gz: cc2bc43f7102cde80e9dc6b54dbe8834febca38d906dde8aa47b1835275c2068
3
+ metadata.gz: 31b1b2c1994a774599c9b5612781ac138dc561291572b97a4a2d3a96530618a6
4
+ data.tar.gz: 85fc67f3109b3e0c8a0964a98604c219c7fb418b4482580802aae6bc056f5adb
5
5
  SHA512:
6
- metadata.gz: 62f45b83d078559a6065716a1d37170cf53043acbdc568760bef9c94e8c2dc90bc9bc37e070c1d50d4480d9abcc95f8c0ae7b286c7e4576a0ec312c35a2bce91
7
- data.tar.gz: 8040cd095339494a138568eeeb021056985f43656c48db23ad4a00489a2a249fd259d79d299178ad2f19923ed79c02ecdc118dfe24f4c217daaa5fc10bd0366e
6
+ metadata.gz: '099a79e03be5ea69bf4a6d60f5ec0d9c567439f54b75e777f00d2199fc8ca055b3a15bb9d751993f6544b6d2e363ef0d0615adb5d09b8965fa04b7a1cbcf0b9b'
7
+ data.tar.gz: 5b527894fd9d9a99c5e2521cc9c40aea76507136fa7125915ad10347c503b0bd9570e9d5ba4287d9b975541deac4c3e784141c59e555806fde0a15a43684a13d
@@ -985,7 +985,7 @@ body.vp-search-open {
985
985
  height: 100%;
986
986
  padding: 18px;
987
987
  object-fit: contain;
988
- filter: drop-shadow(-2px 4px 6px rgba(0, 0, 0, 0.2));
988
+ filter: drop-shadow(-2px 4px 6px rgb(0, 0, 0, 0.2));
989
989
  transform: translate(-50%, -50%);
990
990
  }
991
991
 
@@ -3,7 +3,7 @@ require 'rouge'
3
3
 
4
4
  module Jekyll
5
5
  module VitePressTheme
6
- # rubocop:disable Metrics/ModuleLength
6
+ # rubocop:disable-next Metrics/ModuleLength
7
7
  module Sidebar
8
8
  module_function
9
9
 
@@ -243,7 +243,6 @@ module Jekyll
243
243
  )
244
244
  end
245
245
  end
246
- # rubocop:enable Metrics/ModuleLength
247
246
 
248
247
  module SearchIndex
249
248
  module_function
@@ -319,24 +318,6 @@ module Jekyll
319
318
  end
320
319
  end
321
320
 
322
- module LastUpdated
323
- module_function
324
-
325
- def source_file_time(site, path)
326
- return nil unless path
327
-
328
- source_path = if Pathname.new(path).absolute?
329
- path
330
- else
331
- File.join(site.source, path)
332
- end
333
-
334
- return nil unless File.file?(source_path)
335
-
336
- File.mtime(source_path).utc
337
- end
338
- end
339
-
340
321
  module RougeStyles
341
322
  module_function
342
323
 
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'pathname'
5
+
6
+ module Jekyll
7
+ module VitePressTheme
8
+ # Dates each page by the last commit that touched its source file, read
9
+ # from one `git log` pass per build. Files Git cannot date reliably
10
+ # (untracked, locally modified, outside the repository, or older than a
11
+ # shallow clone's history) use their modification time instead.
12
+ module LastUpdated
13
+ module_function
14
+
15
+ FIELD_SEPARATOR = "\x1e"
16
+
17
+ # Git's view of the files under a site source: commit times keyed by
18
+ # real path, plus the paths whose working copy differs from HEAD.
19
+ class GitIndex
20
+ def self.empty
21
+ new({}, [])
22
+ end
23
+
24
+ def initialize(times, modified)
25
+ @times = times
26
+ @modified = modified.to_h { |path| [path, true] }
27
+ end
28
+
29
+ def time_for(path)
30
+ return nil if @modified.key?(path)
31
+
32
+ @times[path]
33
+ end
34
+ end
35
+
36
+ def source_file_time(site, path)
37
+ source_path = resolved_source_path(site, path)
38
+ return nil unless source_path
39
+
40
+ git_index(site).time_for(source_path) || File.mtime(source_path).utc
41
+ end
42
+
43
+ # The last commit date alone, or nil where Git cannot date the file.
44
+ # For uses such as the sitemap, where a build-time file timestamp
45
+ # would claim a change that never happened.
46
+ def commit_time(site, path)
47
+ source_path = resolved_source_path(site, path)
48
+ source_path && git_index(site).time_for(source_path)
49
+ end
50
+
51
+ def resolved_source_path(site, path)
52
+ return nil unless path
53
+
54
+ source_path = Pathname.new(path).absolute? ? path : File.join(site.source, path)
55
+ return nil unless File.file?(source_path)
56
+
57
+ File.realpath(source_path)
58
+ end
59
+
60
+ def git_index(site)
61
+ @git_indexes ||= {}
62
+ @git_indexes[site] ||= build_git_index(site.source)
63
+ end
64
+
65
+ def reset(site)
66
+ @git_indexes&.delete(site)
67
+ end
68
+
69
+ def build_git_index(source)
70
+ source = File.realpath(source)
71
+ root = repository_root(source)
72
+ return GitIndex.empty unless root
73
+
74
+ pathspec = relative_pathspec(source, root)
75
+ boundaries = shallow_boundaries(root)
76
+ warn_shallow if boundaries.any?
77
+
78
+ GitIndex.new(commit_times(root, pathspec, boundaries), modified_paths(root, pathspec))
79
+ rescue SystemCallError, ArgumentError => e
80
+ Jekyll.logger.debug('jekyll-vitepress-theme', "Git dates unavailable: #{e.message}")
81
+ GitIndex.empty
82
+ end
83
+
84
+ def repository_root(source)
85
+ root = git(source, 'rev-parse', '--show-toplevel').to_s.strip
86
+ root.empty? ? nil : File.realpath(root)
87
+ end
88
+
89
+ def relative_pathspec(source, root)
90
+ Pathname.new(source).relative_path_from(Pathname.new(root)).to_s
91
+ end
92
+
93
+ def commit_times(root, pathspec, boundaries)
94
+ log = git(root, 'log', '--no-renames', "--format=#{FIELD_SEPARATOR}%H %ct", '--name-only', '-z', '--', pathspec)
95
+ return {} unless log
96
+
97
+ log.split(FIELD_SEPARATOR).each_with_object({}) do |entry, times|
98
+ sha, time, files = parse_log_entry(entry)
99
+ next if files.empty? || boundaries.include?(sha)
100
+
101
+ files.each do |file|
102
+ path = File.join(root, file)
103
+ times[path] = time if times[path].nil? || time > times[path]
104
+ end
105
+ end
106
+ end
107
+
108
+ def parse_log_entry(entry)
109
+ header, files = entry.split("\0", 2)
110
+ sha, timestamp = header.to_s.split
111
+ return [sha, nil, []] if files.nil?
112
+
113
+ [sha, Time.at(Integer(timestamp)).utc, files.delete_prefix("\n").split("\0").reject(&:empty?)]
114
+ end
115
+
116
+ def modified_paths(root, pathspec)
117
+ status = git(root, 'status', '--porcelain=v1', '-z', '--untracked-files=no', '--', pathspec)
118
+ return [] unless status
119
+
120
+ entries = status.split("\0")
121
+ paths = []
122
+ until entries.empty?
123
+ entry = entries.shift
124
+ paths << File.join(root, entry[3..])
125
+ entries.shift if entry.start_with?('R', 'C')
126
+ end
127
+ paths
128
+ end
129
+
130
+ def shallow_boundaries(root)
131
+ return [] unless git(root, 'rev-parse', '--is-shallow-repository')&.strip == 'true'
132
+
133
+ shallow_file = git(root, 'rev-parse', '--git-path', 'shallow')&.strip
134
+ return [] unless shallow_file
135
+
136
+ shallow_file = File.expand_path(shallow_file, root)
137
+ return [] unless File.file?(shallow_file)
138
+
139
+ File.read(shallow_file).split
140
+ end
141
+
142
+ def warn_shallow
143
+ Jekyll.logger.warn(
144
+ 'jekyll-vitepress-theme',
145
+ 'Shallow Git clone: pages last changed before the clone\'s history use their file ' \
146
+ 'modification time for "Last updated". Fetch the full history (fetch-depth: 0) for accurate dates.'
147
+ )
148
+ end
149
+
150
+ def git(directory, *args)
151
+ output, status = Open3.capture2('git', '-C', directory, *args, err: File::NULL)
152
+ status.success? ? output : nil
153
+ rescue SystemCallError
154
+ nil
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ Jekyll::Hooks.register :site, :after_reset do |site|
161
+ Jekyll::VitePressTheme::LastUpdated.reset(site)
162
+ end
@@ -4,7 +4,7 @@ module Jekyll
4
4
  module VitePressTheme
5
5
  # Generates the proposed llms.txt index and its full-text companion from
6
6
  # the same canonical, indexable content used by the theme's SEO layer.
7
- # rubocop:disable Metrics/AbcSize, Metrics/ModuleLength
7
+ # rubocop:disable-next Metrics/AbcSize, Metrics/ModuleLength
8
8
  module LLMS
9
9
  module_function
10
10
 
@@ -231,6 +231,5 @@ module Jekyll
231
231
  value.is_a?(Hash) ? value : {}
232
232
  end
233
233
  end
234
- # rubocop:enable Metrics/AbcSize, Metrics/ModuleLength
235
234
  end
236
235
  end
@@ -11,7 +11,7 @@ module Jekyll
11
11
  # the exported .md files, and llms-full.txt all share it. Fenced code
12
12
  # blocks and code spans pass through untouched, and a page without HTML
13
13
  # comes back unchanged.
14
- # rubocop:disable Metrics/AbcSize, Metrics/ModuleLength
14
+ # rubocop:disable-next Metrics/AbcSize, Metrics/ModuleLength
15
15
  module MarkdownExport
16
16
  module_function
17
17
 
@@ -277,7 +277,7 @@ module Jekyll
277
277
  def dedent(text)
278
278
  lines = text.split("\n", -1)
279
279
  indents = lines.drop(1).reject { |line| line.strip.empty? || line.match?(/\A[ \t]*#{BLOCK_CODE_PATTERN.source}/) }
280
- .map { |line| line[/\A[ \t]*/].length }
280
+ .map { |line| line[/\A[ \t]*/].length }
281
281
  indent = indents.min.to_i
282
282
  return text if indent.zero?
283
283
 
@@ -632,6 +632,5 @@ module Jekyll
632
632
  nil
633
633
  end
634
634
  end
635
- # rubocop:enable Metrics/AbcSize, Metrics/ModuleLength
636
635
  end
637
636
  end
@@ -10,7 +10,7 @@ module Jekyll
10
10
  # Generates the metadata shared by the HTML head, sitemap, and robots.txt.
11
11
  # Keeping those outputs on one canonical URL resolver prevents contradictory
12
12
  # indexing signals across theme consumers.
13
- # rubocop:disable Metrics/AbcSize, Metrics/ModuleLength
13
+ # rubocop:disable-next Metrics/AbcSize, Metrics/ModuleLength
14
14
  module SEO
15
15
  module_function
16
16
 
@@ -388,7 +388,7 @@ module Jekyll
388
388
  metadata = item.data['_seo']
389
389
  next unless sitemap_candidate?(item, metadata)
390
390
 
391
- last_modified = metadata['date_modified']
391
+ last_modified = metadata['date_modified'] || source_modified(item)
392
392
  last_modified ||= metadata['date_published'] if posts_document?(item)
393
393
  [metadata['canonical_url'], last_modified]
394
394
  end
@@ -408,6 +408,13 @@ module Jekyll
408
408
  XML
409
409
  end
410
410
 
411
+ # Front matter first, then the last commit that touched the page's
412
+ # source file. Never a build-time file timestamp, which would mark every
413
+ # page as changed on each build.
414
+ def source_modified(item)
415
+ xml_time(item.data['last_updated_at'] || LastUpdated.commit_time(item.site, item.path))
416
+ end
417
+
411
418
  def robots_txt(site, config, site_url)
412
419
  index = config.fetch('index', true) != false
413
420
  lines = ['User-agent: *', index ? 'Allow: /' : 'Disallow: /']
@@ -588,6 +595,5 @@ module Jekyll
588
595
  CGI.escapeHTML(value.to_s)
589
596
  end
590
597
  end
591
- # rubocop:enable Metrics/AbcSize, Metrics/ModuleLength
592
598
  end
593
599
  end
@@ -1,5 +1,5 @@
1
1
  module Jekyll
2
2
  module VitePressTheme
3
- VERSION = "1.10.0".freeze
3
+ VERSION = "1.11.0".freeze
4
4
  end
5
5
  end
@@ -3,4 +3,5 @@ require_relative 'jekyll/vitepress_theme/version'
3
3
  require_relative 'jekyll/vitepress_theme/markdown_export'
4
4
  require_relative 'jekyll/vitepress_theme/seo'
5
5
  require_relative 'jekyll/vitepress_theme/llms'
6
+ require_relative 'jekyll/vitepress_theme/last_updated'
6
7
  require_relative 'jekyll/vitepress_theme/hooks'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-vitepress-theme
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-10-15 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: cgi
@@ -56,20 +55,20 @@ dependencies:
56
55
  requirements:
57
56
  - - ">="
58
57
  - !ruby/object:Gem::Version
59
- version: '3.0'
58
+ version: '4.1'
60
59
  - - "<"
61
60
  - !ruby/object:Gem::Version
62
- version: '5.0'
61
+ version: '6.0'
63
62
  type: :runtime
64
63
  prerelease: false
65
64
  version_requirements: !ruby/object:Gem::Requirement
66
65
  requirements:
67
66
  - - ">="
68
67
  - !ruby/object:Gem::Version
69
- version: '3.0'
68
+ version: '4.1'
70
69
  - - "<"
71
70
  - !ruby/object:Gem::Version
72
- version: '5.0'
71
+ version: '6.0'
73
72
  description: Jekyll VitePress Theme brings VitePress-style documentation UX, search,
74
73
  structured metadata, and Jekyll-native extensions to Ruby static sites.
75
74
  email:
@@ -177,6 +176,7 @@ files:
177
176
  - assets/vendor/turbo.js
178
177
  - lib/jekyll-vitepress-theme.rb
179
178
  - lib/jekyll/vitepress_theme/hooks.rb
179
+ - lib/jekyll/vitepress_theme/last_updated.rb
180
180
  - lib/jekyll/vitepress_theme/llms.rb
181
181
  - lib/jekyll/vitepress_theme/markdown_export.rb
182
182
  - lib/jekyll/vitepress_theme/seo.rb
@@ -192,7 +192,6 @@ metadata:
192
192
  documentation_uri: https://jekyll-vitepress.dev
193
193
  bug_tracker_uri: https://github.com/crmne/jekyll-vitepress-theme/issues
194
194
  funding_uri: https://github.com/sponsors/crmne
195
- post_install_message:
196
195
  rdoc_options: []
197
196
  require_paths:
198
197
  - lib
@@ -207,8 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
207
206
  - !ruby/object:Gem::Version
208
207
  version: '0'
209
208
  requirements: []
210
- rubygems_version: 3.5.22
211
- signing_key:
209
+ rubygems_version: 3.6.9
212
210
  specification_version: 4
213
211
  summary: A polished, SEO-ready VitePress-style documentation theme for Jekyll.
214
212
  test_files: []