mkdocs-document-dates 3.7.3__tar.gz → 3.8.0__tar.gz

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.
Files changed (43) hide show
  1. {mkdocs_document_dates-3.7.3/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.8.0}/PKG-INFO +4 -3
  2. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/README.md +2 -2
  3. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/cache_manager.py +3 -3
  4. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/plugin.py +50 -63
  5. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/.DS_Store +0 -0
  6. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_group.html +10 -2
  7. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/utils.py +91 -35
  8. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0/mkdocs_document_dates.egg-info}/PKG-INFO +4 -3
  9. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/requires.txt +1 -0
  10. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/pyproject.toml +3 -3
  11. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/setup.py +1 -1
  12. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/LICENSE +0 -0
  13. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/MANIFEST.in +0 -0
  14. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/__init__.py +0 -0
  15. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks/pre-commit +0 -0
  16. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks_installer.py +0 -0
  17. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/config/user.config.css +0 -0
  18. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/config/user.config.js +0 -0
  19. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/core.css +0 -0
  20. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/core.js +0 -0
  21. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/default.config.js +0 -0
  22. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/md5.min.js +0 -0
  23. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/timeago.full.min.js +0 -0
  24. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/timeago.min.js +0 -0
  25. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/utils.js +0 -0
  26. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/fonts/material-icons.css +0 -0
  27. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/fonts/materialicons.woff2 +0 -0
  28. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_detail.html +0 -0
  29. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_grid.html +0 -0
  30. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_list.html +0 -0
  31. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/backdrop.css +0 -0
  32. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/light.css +0 -0
  33. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/material.css +0 -0
  34. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/popper.min.js +0 -0
  35. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/scale.css +0 -0
  36. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/shift-away.css +0 -0
  37. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/tippy.css +0 -0
  38. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/tippy.umd.min.js +0 -0
  39. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/SOURCES.txt +0 -0
  40. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/dependency_links.txt +0 -0
  41. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/entry_points.txt +0 -0
  42. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/top_level.txt +0 -0
  43. {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-document-dates
3
- Version: 3.7.3
3
+ Version: 3.8.0
4
4
  Summary: A new generation MkDocs plugin for displaying exact creation date, last updated date, authors, email of documents
5
5
  Author-email: Aaron Wang <aaronwqt@gmail.com>
6
6
  License-Expression: MIT
@@ -13,6 +13,7 @@ Requires-Python: >=3.7
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: mkdocs<=1.6.1,>=1.6
16
+ Requires-Dist: properdocs>=1.6.5
16
17
  Dynamic: license-file
17
18
 
18
19
  # mkdocs-document-dates
@@ -81,8 +82,8 @@ In addition to the above basic configuration, the plug-in also provides a wealth
81
82
  - [Structure and Style](https://jaywhj.netlify.app/document-dates-en#Structure-and-Style): You can freely configure the plugin's display structure in mkdocs.yml or Front Matter. You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
82
83
  - [Template Variables](https://jaywhj.netlify.app/document-dates-en#Template-Variables): Can be used to optimize `sitemap.xml` for site SEO
83
84
  - [Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Recently-Updated-Module): Enable list of recently updated documents (in descending order of update date), this is ideal for sites with a large number of documents, so that readers can quickly see what's new
84
- - [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
85
- - [Development Stories](https://jaywhj.netlify.app/document-dates-en#Development-Stories): Describes the origin of the plug-in, the difficulties and solutions encountered in development, and the principles and directions of product design
85
+ - [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
86
+ - [Developer API](https://jaywhj.netlify.app/document-dates-en#Developer-API): Provides a date data API for developers, making it easy to retrieve exact dates in other plugins or hooks
86
87
 
87
88
  See the documentation for details: https://jaywhj.netlify.app/document-dates-en
88
89
 
@@ -64,8 +64,8 @@ In addition to the above basic configuration, the plug-in also provides a wealth
64
64
  - [Structure and Style](https://jaywhj.netlify.app/document-dates-en#Structure-and-Style): You can freely configure the plugin's display structure in mkdocs.yml or Front Matter. You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
65
65
  - [Template Variables](https://jaywhj.netlify.app/document-dates-en#Template-Variables): Can be used to optimize `sitemap.xml` for site SEO
66
66
  - [Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Recently-Updated-Module): Enable list of recently updated documents (in descending order of update date), this is ideal for sites with a large number of documents, so that readers can quickly see what's new
67
- - [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
68
- - [Development Stories](https://jaywhj.netlify.app/document-dates-en#Development-Stories): Describes the origin of the plug-in, the difficulties and solutions encountered in development, and the principles and directions of product design
67
+ - [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
68
+ - [Developer API](https://jaywhj.netlify.app/document-dates-en#Developer-API): Provides a date data API for developers, making it easy to retrieve exact dates in other plugins or hooks
69
69
 
70
70
  See the documentation for details: https://jaywhj.netlify.app/document-dates-en
71
71
 
@@ -164,7 +164,7 @@ def update_cache():
164
164
  global_updated = setup_gitattributes(docs_dir)
165
165
 
166
166
  # 获取docs目录下已跟踪(tracked)的markdown文件
167
- cmd = ["git", "ls-files", "*.md"]
167
+ cmd = ['git', '-c', 'core.quotepath=false', 'ls-files', '*.md']
168
168
  result = subprocess.run(cmd, cwd=docs_dir, env=_clean_git_env(), capture_output=True, encoding='utf-8')
169
169
  tracked_files = result.stdout.splitlines() if result.stdout else []
170
170
 
@@ -185,13 +185,13 @@ def update_cache():
185
185
 
186
186
  full_path = docs_dir / rel_path
187
187
  if full_path.exists():
188
- created_time = load_file_creation_date(full_path).astimezone()
188
+ created_time = load_file_creation_date(full_path)
189
189
  if not jsonl_cache_file.exists():
190
190
  git_time = load_git_first_commit_date(full_path)
191
191
  if git_time:
192
192
  created_time = min(created_time, git_time)
193
193
  jsonl_dates_cache[rel_path] = {
194
- "created": created_time.isoformat()
194
+ "created": int(created_time.timestamp())
195
195
  }
196
196
  project_updated = True
197
197
  except Exception as e:
@@ -1,16 +1,15 @@
1
- import os
2
1
  import yaml
3
2
  import shutil
4
3
  import logging
5
4
  from jinja2 import ChoiceLoader, FileSystemLoader
6
- from datetime import datetime
5
+ from datetime import datetime, timezone
7
6
  from pathlib import Path
8
7
  from mkdocs.plugins import BasePlugin, event_priority
9
8
  from mkdocs.config import config_options
10
9
  from mkdocs.structure.pages import Page
11
10
  from mkdocs.utils import get_relative_url
12
11
  from urllib.parse import urlparse
13
- from .utils import load_file_creation_date, load_git_metadata, load_git_last_updated_dates, read_jsonl_cache, compile_exclude_patterns, is_excluded, get_recently_updated_files
12
+ from .utils import compile_exclude_patterns, is_excluded, get_recently_updated_files, load_dates_and_authors
14
13
 
15
14
  logger = logging.getLogger("mkdocs.plugins.document_dates")
16
15
  logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, CRITICAL
@@ -45,14 +44,13 @@ class DocumentDatesPlugin(BasePlugin):
45
44
  super().__init__()
46
45
 
47
46
  self.data_cached = {}
48
- self.last_updated_dates = {}
49
47
  self.authors_yml = {}
50
48
  self.recent_docs_html = None
51
49
  self.recent_enable = False
52
50
  self._exclude_patterns = []
53
51
 
54
52
  def on_config(self, config):
55
- docs_dir_path = Path(config['docs_dir'])
53
+ docs_dir_path = Path(config.docs_dir)
56
54
 
57
55
  # 加载 author 配置
58
56
  authors_file = docs_dir_path / 'authors.yml'
@@ -65,20 +63,6 @@ class DocumentDatesPlugin(BasePlugin):
65
63
  pass
66
64
  self._load_authors_from_yaml(authors_file)
67
65
 
68
- # 加载文档 git 元数据(日期 & 作者)
69
- self.data_cached = load_git_metadata(docs_dir_path)
70
- # 加载 jsonl 缓存数据
71
- jsonl_cache_file = docs_dir_path / '.dates_cache.jsonl'
72
- if jsonl_cache_file.exists():
73
- jsonl_cache = read_jsonl_cache(jsonl_cache_file)
74
- for filename, new_info in jsonl_cache.items():
75
- if filename in self.data_cached:
76
- self.data_cached[filename].update(new_info)
77
-
78
- # 加载文档最近更新时间(日期)
79
- self.last_updated_dates = load_git_last_updated_dates(docs_dir_path)
80
-
81
-
82
66
  # 复制配置文件到用户目录(如果不存在)
83
67
  dest_dir = docs_dir_path / 'assets' / 'document_dates'
84
68
  dest_dir.mkdir(parents=True, exist_ok=True)
@@ -151,32 +135,41 @@ class DocumentDatesPlugin(BasePlugin):
151
135
 
152
136
  return config
153
137
 
138
+ @event_priority(50)
139
+ def on_files(self, files, config):
140
+ self.data_cached = load_dates_and_authors(Path(config.docs_dir), files)
141
+ return files
142
+
154
143
  @event_priority(50)
155
144
  def on_page_markdown(self, markdown, page: Page, config, files):
156
145
  # 获取相对路径,src_uri 总是以"/"分隔
157
- rel_path = getattr(page.file, 'src_uri', page.file.src_path)
158
- if os.sep != '/':
159
- rel_path = rel_path.replace(os.sep, '/')
160
- file_path = page.file.abs_src_path
161
-
146
+ rel_path = getattr(page.file, 'src_uri')
147
+
162
148
  # 优先获取 page.meta 中的数据
163
149
  created = self._load_meta_date(page.meta, self.config['created_field_names'])
164
150
  updated = self._load_meta_date(page.meta, self.config['updated_field_names'])
165
151
  authors = self._load_meta_author(page.meta, page.url)
166
152
 
167
- # 再获取缓存的数据
168
- if not created:
169
- created = self._load_created_cached(file_path, rel_path)
170
- if not updated:
171
- updated = self._load_updated_cached(file_path, rel_path)
153
+ # 如果 meta 数据存在,则存储
154
+ cache = self.data_cached.setdefault(rel_path, {})
155
+ if created:
156
+ cache['created'] = created
157
+ else:
158
+ created = cache.get('created')
159
+
160
+ if updated:
161
+ cache['updated'] = updated
162
+ else:
163
+ updated = cache.get('updated')
164
+
172
165
  if not authors:
173
166
  authors = self._load_author_cached(rel_path, page, config)
174
167
 
175
- # 注入数据
168
+ # 注入数据到模板 (utc datetime -> local datetime)
176
169
  page.meta["document_dates"] = {
177
170
  "dates": {
178
- "created": created.isoformat(),
179
- "updated": updated.isoformat(),
171
+ "created": created.astimezone().isoformat() if created else None,
172
+ "updated": updated.astimezone().isoformat() if updated else None,
180
173
  },
181
174
  "authors": authors
182
175
  }
@@ -205,9 +198,14 @@ class DocumentDatesPlugin(BasePlugin):
205
198
  exclude_list = recently_updated_config.get('exclude', [])
206
199
  limit = recently_updated_config.get('limit', 10)
207
200
 
201
+ # 获取站点 URL 路径前缀
202
+ site_url = config.get("site_url", "")
203
+ base_path = urlparse(site_url).path.rstrip("/")
204
+ prefix = f"{base_path}/" if base_path else "/"
205
+
208
206
  # 获取最近更新的文档数据
209
207
  recent_exclude_patterns = compile_exclude_patterns(exclude_list)
210
- recently_updated_docs = get_recently_updated_files(self.last_updated_dates, files, recent_exclude_patterns, limit, self.recent_enable)
208
+ recently_updated_docs = get_recently_updated_files(self.data_cached, files, recent_exclude_patterns, limit, self.recent_enable, prefix)
211
209
 
212
210
  # 将数据注入到 config['extra'] 中供全局访问
213
211
  if not config.get('extra', {}).get("recently_updated_docs", {}):
@@ -285,42 +283,31 @@ class DocumentDatesPlugin(BasePlugin):
285
283
  for field in field_names:
286
284
  if field in meta:
287
285
  try:
288
- # 移除首尾可能存在的单双引号和时区信息
286
+ # 移除首尾可能存在的单双引号
289
287
  date_str = str(meta[field]).strip("'\"")
290
- return datetime.fromisoformat(date_str).replace(tzinfo=None)
288
+ dt = datetime.fromisoformat(date_str)
289
+ # 如果没时区,则当成本地时间,再转 UTC
290
+ if dt.tzinfo is None:
291
+ local_tz = datetime.now().astimezone().tzinfo
292
+ dt = dt.replace(tzinfo=local_tz)
293
+ return dt.astimezone(timezone.utc)
294
+ # return datetime.fromisoformat(date_str).astimezone()
291
295
  except Exception:
292
296
  continue
293
297
  return None
294
298
 
295
- def _load_created_cached(self, file_path, rel_path):
296
- # 优先从缓存中读取
297
- if rel_path in self.data_cached:
298
- return datetime.fromisoformat(self.data_cached[rel_path]['created'])
299
- # 从文件系统获取
300
- return load_file_creation_date(file_path).astimezone()
301
-
302
- def _load_updated_cached(self, file_path, rel_path):
303
- # 优先从缓存中读取
304
- if rel_path in self.last_updated_dates:
305
- return datetime.fromtimestamp(self.last_updated_dates[rel_path]).astimezone()
306
- # 从文件系统获取最后修改时间
307
- stat = os.stat(file_path)
308
- return datetime.fromtimestamp(stat.st_mtime).astimezone()
309
-
310
-
311
299
  def _load_author_cached(self, rel_path, page, config):
312
300
  # 1. git author
313
- if rel_path in self.data_cached:
314
- authors_list = self.data_cached[rel_path].get('authors')
315
- if authors_list:
316
- authors = []
317
- for data in authors_list:
318
- full_author = self.authors_yml.get(data['name'])
319
- if full_author:
320
- authors.append(self._repair_author(full_author, page.url))
321
- else:
322
- authors.append(Author(**data))
323
- return authors
301
+ authors_list = self.data_cached.get(rel_path, {}).get('authors', None)
302
+ if authors_list:
303
+ authors = []
304
+ for data in authors_list:
305
+ full_author = self.authors_yml.get(data['name'])
306
+ if full_author:
307
+ authors.append(self._repair_author(full_author, page.url))
308
+ else:
309
+ authors.append(Author(**data))
310
+ return authors
324
311
 
325
312
  # 2. site_author 或 PC username
326
313
  name = config.get('site_author') or Path.home().name
@@ -402,7 +389,7 @@ class DocumentDatesPlugin(BasePlugin):
402
389
  return (
403
390
  f"<span class='dd-item' data-tippy-content data-tippy-raw='{formatted}'>"
404
391
  f"<span class='material-icons' data-icon='{icon}'></span>"
405
- f"<time datetime='{time_obj.isoformat()}'>{self._formatting_date(time_obj)}</time>"
392
+ f"<time datetime='{time_obj.astimezone().isoformat()}'>{self._formatting_date(time_obj)}</time>"
406
393
  f"</span>"
407
394
  )
408
395
 
@@ -260,11 +260,19 @@
260
260
  --summary-lines-grid: {{ summary_lines.grid }};
261
261
  --summary-lines-detail: {{ summary_lines.detail }};
262
262
  ">
263
- {%- for mtime, rel_path, title, url, cover, summary, readtime, tags in recent_docs %}
263
+ {%- for doc in recent_docs %}
264
+ {%- set title = doc.title | default('') %}
265
+ {%- set url = doc.url | default('') %}
266
+ {%- set updated_dt = doc.updated_dt | default('') %}
267
+ {%- set updated = doc.updated | default(updated_dt[:10]) %}
268
+ {%- set cover = doc.cover | default('') %}
269
+ {%- set summary = doc.summary | default('') %}
270
+ {%- set readtime = doc.readtime | default(0) %}
271
+ {%- set tags = doc.tags | default([]) %}
264
272
  <div class="article-card">
265
273
  <a class="card-header" href="{{ url }}" target="_blank">
266
274
  <div class="card-title">{{ title }}</div>
267
- <time class="dd-timeago card-date" datetime="{{ mtime }}">{{ mtime[:10] }}</time>
275
+ <time class="dd-timeago card-date" datetime="{{ updated_dt }}">{{ updated }}</time>
268
276
  </a>
269
277
  <div class="card-body">
270
278
  {%- if cover %}
@@ -8,7 +8,8 @@ import fnmatch
8
8
  import re
9
9
  import math
10
10
  from pathlib import Path
11
- from datetime import datetime
11
+ from operator import itemgetter
12
+ from datetime import datetime, timezone
12
13
  from collections import defaultdict
13
14
  from mkdocs.structure.files import Files
14
15
 
@@ -16,6 +17,44 @@ logger = logging.getLogger("mkdocs.plugins.document_dates")
16
17
  logger.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, CRITICAL
17
18
 
18
19
 
20
+ def load_dates_and_authors(docs_dir_path: Path, files: Files):
21
+
22
+ # git 创建日期
23
+ created_data = load_git_metadata(docs_dir_path)
24
+
25
+ # 覆盖 jsonl 创建日期
26
+ jsonl_cache_file = docs_dir_path / '.dates_cache.jsonl'
27
+ if jsonl_cache_file.exists():
28
+ jsonl_cache = read_jsonl_cache(jsonl_cache_file)
29
+ for filename, new_info in jsonl_cache.items():
30
+ if filename in created_data:
31
+ created_data[filename].update(new_info)
32
+
33
+ # git 更新日期
34
+ updated_data = load_git_last_updated_dates(docs_dir_path)
35
+
36
+ for file in files:
37
+ if file.inclusion.is_excluded():
38
+ continue
39
+ if not file.src_path.endswith('.md'):
40
+ continue
41
+ rel_path = getattr(file, 'src_uri')
42
+
43
+ # created: timestamp -> datetime
44
+ cache = created_data.setdefault(rel_path, {})
45
+ created_ts = cache.get('created')
46
+ cache['created'] = (
47
+ datetime.fromtimestamp(created_ts, tz=timezone.utc)
48
+ if created_ts is not None
49
+ else load_file_creation_date(file.abs_src_path)
50
+ )
51
+
52
+ # updated: timestamp -> datetime
53
+ mtime = updated_data.get(rel_path, os.path.getmtime(file.abs_src_path))
54
+ created_data[rel_path]['updated'] = datetime.fromtimestamp(mtime, tz=timezone.utc)
55
+
56
+ return created_data
57
+
19
58
  def compile_exclude_patterns(exclude_list):
20
59
  if not exclude_list:
21
60
  return []
@@ -35,34 +74,34 @@ def is_excluded(path, patterns):
35
74
  return True
36
75
  return False
37
76
 
38
- def load_file_creation_date(file_path):
77
+ def load_file_creation_date(file_path) -> datetime:
39
78
  try:
40
79
  stat = os.stat(file_path)
41
80
  system = platform.system().lower()
42
81
  if system.startswith('win'): # Windows
43
- return datetime.fromtimestamp(stat.st_ctime)
82
+ return datetime.fromtimestamp(stat.st_ctime, tz=timezone.utc)
44
83
  elif system == 'darwin': # macOS
45
84
  try:
46
- return datetime.fromtimestamp(stat.st_birthtime)
85
+ return datetime.fromtimestamp(stat.st_birthtime, tz=timezone.utc)
47
86
  except AttributeError:
48
- return datetime.fromtimestamp(stat.st_ctime)
87
+ return datetime.fromtimestamp(stat.st_ctime, tz=timezone.utc)
49
88
  else: # Linux, 没有创建时间,使用修改时间
50
- return datetime.fromtimestamp(stat.st_mtime)
89
+ return datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
51
90
  except (OSError, ValueError) as e:
52
91
  logger.error(f"Failed to load file creation date for {file_path}: {e}")
53
- return datetime.now()
92
+ return datetime.now(timezone.utc)
54
93
 
55
- def load_git_first_commit_date(file_path):
94
+ def load_git_first_commit_date(file_path) -> datetime:
56
95
  try:
57
96
  # git log --reverse --format="%aI" -- {file_path} | head -n 1
58
- cmd_list = ['git', 'log', '--reverse', '--format=%aI', '--', file_path]
97
+ cmd_list = ['git', 'log', '--reverse', '--format=%at', '--', file_path]
59
98
  process = subprocess.run(cmd_list, capture_output=True, encoding='utf-8')
60
99
  if process.returncode == 0 and process.stdout.strip():
61
- first_line = process.stdout.partition('\n')[0].strip()
62
- return datetime.fromisoformat(first_line)
100
+ first_line = int(process.stdout.partition('\n')[0].strip())
101
+ return datetime.fromtimestamp(first_line, tz=timezone.utc)
63
102
  except Exception as e:
64
103
  logger.info(f"Error load git first commit date for {file_path}: {e}")
65
- return None
104
+ return datetime.now(timezone.utc)
66
105
 
67
106
  def load_git_metadata(docs_dir_path: Path):
68
107
  dates_cache = {}
@@ -73,7 +112,7 @@ def load_git_metadata(docs_dir_path: Path):
73
112
  ).strip())
74
113
  rel_docs_path = docs_dir_path.relative_to(git_root).as_posix()
75
114
 
76
- cmd = ['git', 'log', '--reverse', '--no-merges', '--use-mailmap', '--name-only', '--format=%aN|%aE|%aI', f'--relative={rel_docs_path}', '--', '*.md']
115
+ cmd = ['git', '-c', 'core.quotepath=false', 'log', '--reverse', '--no-merges', '--use-mailmap', '--name-only', '--format=%aN|%aE|%at', f'--relative={rel_docs_path}', '--', '*.md']
77
116
  process = subprocess.run(cmd, cwd=docs_dir_path, capture_output=True, encoding='utf-8')
78
117
  if process.returncode == 0:
79
118
  authors_dict = defaultdict(dict)
@@ -92,7 +131,7 @@ def load_git_metadata(docs_dir_path: Path):
92
131
  # a.巧用 Python 字典的 setdefault 特性来去重(setdefault 为不存在的键提供初始值,不会覆盖已有值)
93
132
  # b.巧用 Python 字典的插入顺序特性来保留内容插入顺序(Python 3.7+ 字典会保持插入顺序)
94
133
  authors_dict[line].setdefault((name, email), None)
95
- first_commit.setdefault(line, created)
134
+ first_commit.setdefault(line, int(created))
96
135
 
97
136
  # 构建最终的缓存数据
98
137
  for file_path in first_commit:
@@ -117,11 +156,11 @@ def load_git_last_updated_dates(docs_dir_path: Path):
117
156
  ).strip())
118
157
  rel_docs_path = docs_dir_path.relative_to(git_root).as_posix()
119
158
 
120
- cmd = ['git', 'log', '--no-merges', '--use-mailmap', '--format=%aN|%aE|%at', '--name-only', f'--relative={rel_docs_path}', '--', '*.md']
159
+ cmd = ['git', '-c', 'core.quotepath=false', 'log', '--no-merges', '--use-mailmap', '--format=%aN|%aE|%at', '--name-only', f'--relative={rel_docs_path}', '--', '*.md']
121
160
  process = subprocess.run(cmd, cwd=docs_dir_path, capture_output=True, encoding='utf-8')
122
161
  if process.returncode == 0:
123
162
  result = subprocess.run(
124
- ["git", "ls-files", "*.md"],
163
+ ['git', '-c', 'core.quotepath=false', 'ls-files', '*.md'],
125
164
  cwd=docs_dir_path, capture_output=True, encoding='utf-8'
126
165
  )
127
166
  # 只记录已跟踪的文件(还有已删除、重命名、不再跟踪)
@@ -133,7 +172,7 @@ def load_git_last_updated_dates(docs_dir_path: Path):
133
172
  if not line:
134
173
  continue
135
174
  if '|' in line:
136
- ts = float(line.split('|')[2])
175
+ ts = int(line.split('|')[2])
137
176
  elif line.endswith('.md') and line in tracked_files and ts:
138
177
  # 只记录第一次出现的文件,即最近一次提交(setdefault 机制不会覆盖已有值)
139
178
  doc_mtime_map.setdefault(line, ts)
@@ -143,7 +182,7 @@ def load_git_last_updated_dates(docs_dir_path: Path):
143
182
  return doc_mtime_map
144
183
 
145
184
  # 建议在 on_page_markdown 之后的全局事件中调用,因为需要读取 page.meta 中的信息
146
- def get_recently_updated_files(existing_dates: dict, files: Files, exclude_list: list, limit: int = 10, recent_enable: bool = False):
185
+ def get_recently_updated_files(existing_dates: dict, files: Files, exclude_list: list, limit: int = 10, recent_enable: bool = False, prefix: str = ""):
147
186
  recently_updated_results = []
148
187
  if recent_enable:
149
188
  files_meta = []
@@ -152,19 +191,18 @@ def get_recently_updated_files(existing_dates: dict, files: Files, exclude_list:
152
191
  continue
153
192
  if not file.src_path.endswith('.md'):
154
193
  continue
155
- rel_path = getattr(file, 'src_uri', file.src_path)
156
- if os.sep != '/':
157
- rel_path = rel_path.replace(os.sep, '/')
194
+ rel_path = getattr(file, 'src_uri')
158
195
  if is_excluded(rel_path, exclude_list):
159
196
  continue
160
197
 
161
198
  # 优先从现有数据获取 mtime,如果不存在则 fallback 到文件系统 mtime
162
- mtime = existing_dates.get(rel_path, os.path.getmtime(file.abs_src_path))
199
+ exist_updated: datetime = existing_dates.get(rel_path, {}).get('updated')
200
+ mtime = exist_updated.timestamp() if exist_updated else os.path.getmtime(file.abs_src_path)
163
201
 
164
202
  # 获取文档其它信息
165
203
  title = file.page.title if file.page and file.page.title else file.name
166
- url = file.page.url if file.page and file.page.url else file.url
167
- tags = file.page.meta.get("tags") or []
204
+ url = prefix + (file.page.url if file.page and file.page.url else file.url)
205
+ tags = (file.page.meta.get("tags") or []) if file.page else []
168
206
 
169
207
  cover = ''
170
208
  summary = ''
@@ -172,25 +210,37 @@ def get_recently_updated_files(existing_dates: dict, files: Files, exclude_list:
172
210
  # authors = []
173
211
  if file.page:
174
212
  cover = file.page.meta.get('cover', '')
213
+ if cover and not cover.startswith(('http', 'ftp')):
214
+ cover = prefix + cover.lstrip('/')
175
215
  # authors = file.page.meta.document_dates.authors
176
216
  if file.page.file:
177
217
  summary, readtime = analyze_markdown(file.page.file.content_string)
178
218
 
179
- meta_readtime = int(file.page.meta.get('readtime') or 0)
219
+ meta_readtime = int((file.page.meta.get('readtime') or 0) if file.page else 0)
180
220
  readtime = meta_readtime if meta_readtime > 0 else readtime
181
221
 
182
222
  # 存储信息(更新时间、路径、标题、URL、封面、摘要、阅读时间、标签)
183
- files_meta.append((mtime, rel_path, title, url, cover, summary, readtime, tags))
184
- # existing_map[rel_path] = mtime
223
+ files_meta.append({
224
+ "updated_ts": mtime,
225
+ "rel_path": rel_path,
226
+ "title": title,
227
+ "url": url,
228
+ "cover": cover,
229
+ "summary": summary,
230
+ "readtime": readtime,
231
+ "tags": tags,
232
+ })
185
233
 
186
234
  # 构建最近更新列表
187
235
  if files_meta:
188
236
  # heapq 取 top limit
189
- top_results = heapq.nlargest(limit, files_meta, key=lambda x: x[0])
190
- recently_updated_results = [
191
- (datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S"), *rest)
192
- for mtime, *rest in top_results
193
- ]
237
+ recently_updated_results = heapq.nlargest(limit, files_meta, key=itemgetter("updated_ts"))
238
+
239
+ for doc in recently_updated_results:
240
+ # timestamp -> utc datetime -> local datetime
241
+ dt = datetime.fromtimestamp(doc["updated_ts"], tz=timezone.utc).astimezone()
242
+ doc["updated_dt"] = dt.isoformat()
243
+ doc["updated"] = dt.date().isoformat()
194
244
 
195
245
  return recently_updated_results
196
246
 
@@ -204,8 +254,11 @@ def read_jsonl_cache(jsonl_file: Path):
204
254
  entry = json.loads(line.strip())
205
255
  if entry and isinstance(entry, dict) and len(entry) == 1:
206
256
  file_path, file_info = next(iter(entry.items()))
257
+ if isinstance(file_info, dict):
258
+ if file_info.get('created'):
259
+ file_info['created'] = int(datetime.fromisoformat(file_info['created']).timestamp())
207
260
  dates_cache[file_path] = file_info
208
- except (json.JSONDecodeError, StopIteration) as e:
261
+ except (json.JSONDecodeError, StopIteration, ValueError) as e:
209
262
  logger.warning(f"Skipping invalid JSONL line: {e}")
210
263
  except IOError as e:
211
264
  logger.warning(f"Error reading from '.dates_cache.jsonl': {str(e)}")
@@ -218,6 +271,9 @@ def write_jsonl_cache(jsonl_file: Path, dates_cache, tracked_files):
218
271
  with open(temp_file, 'w', encoding='utf-8') as f:
219
272
  for file_path in tracked_files:
220
273
  if file_path in dates_cache:
274
+ file_info = dates_cache[file_path].copy()
275
+ if file_info.get('created') is not None:
276
+ file_info['created'] = datetime.fromtimestamp(file_info['created'], tz=timezone.utc).isoformat()
221
277
  entry = {file_path: dates_cache[file_path]}
222
278
  f.write(json.dumps(entry, ensure_ascii=False) + '\n')
223
279
 
@@ -228,7 +284,7 @@ def write_jsonl_cache(jsonl_file: Path, dates_cache, tracked_files):
228
284
  subprocess.run(["git", "add", str(jsonl_file)], check=True)
229
285
  logger.info(f"Successfully updated JSONL cache file: {jsonl_file}")
230
286
  return True
231
- except (IOError, json.JSONDecodeError) as e:
287
+ except IOError as e:
232
288
  logger.warning(f"Failed to write JSONL cache file {jsonl_file}: {e}")
233
289
  except Exception as e:
234
290
  logger.warning(f"Failed to add JSONL cache file to git: {e}")
@@ -503,4 +559,4 @@ def analyze_markdown(md: str) -> list:
503
559
  summary = MD_SYNTAX_RE.sub("", " ".join(summary_lines)).strip()
504
560
  minutes = max(1, math.ceil(seconds / 60))
505
561
 
506
- return summary, minutes
562
+ return summary, minutes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-document-dates
3
- Version: 3.7.3
3
+ Version: 3.8.0
4
4
  Summary: A new generation MkDocs plugin for displaying exact creation date, last updated date, authors, email of documents
5
5
  Author-email: Aaron Wang <aaronwqt@gmail.com>
6
6
  License-Expression: MIT
@@ -13,6 +13,7 @@ Requires-Python: >=3.7
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: mkdocs<=1.6.1,>=1.6
16
+ Requires-Dist: properdocs>=1.6.5
16
17
  Dynamic: license-file
17
18
 
18
19
  # mkdocs-document-dates
@@ -81,8 +82,8 @@ In addition to the above basic configuration, the plug-in also provides a wealth
81
82
  - [Structure and Style](https://jaywhj.netlify.app/document-dates-en#Structure-and-Style): You can freely configure the plugin's display structure in mkdocs.yml or Front Matter. You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
82
83
  - [Template Variables](https://jaywhj.netlify.app/document-dates-en#Template-Variables): Can be used to optimize `sitemap.xml` for site SEO
83
84
  - [Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Recently-Updated-Module): Enable list of recently updated documents (in descending order of update date), this is ideal for sites with a large number of documents, so that readers can quickly see what's new
84
- - [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
85
- - [Development Stories](https://jaywhj.netlify.app/document-dates-en#Development-Stories): Describes the origin of the plug-in, the difficulties and solutions encountered in development, and the principles and directions of product design
85
+ - [Localization Language](https://jaywhj.netlify.app/document-dates-en#Localization-Language): More localization languages for `timeago` and `tooltip`
86
+ - [Developer API](https://jaywhj.netlify.app/document-dates-en#Developer-API): Provides a date data API for developers, making it easy to retrieve exact dates in other plugins or hooks
86
87
 
87
88
  See the documentation for details: https://jaywhj.netlify.app/document-dates-en
88
89
 
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mkdocs-document-dates"
7
- version = "3.7.3"
7
+ version = "3.8.0"
8
8
  description = "A new generation MkDocs plugin for displaying exact creation date, last updated date, authors, email of documents"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.7"
11
11
  license = "MIT"
12
12
  authors = [{ name = "Aaron Wang", email = "aaronwqt@gmail.com" }]
13
13
  dependencies = [
14
- "mkdocs>=1.6,<=1.6.1"
15
- # "properdocs>=1.6.5"
14
+ "mkdocs>=1.6,<=1.6.1",
15
+ "properdocs>=1.6.5"
16
16
  ]
17
17
  classifiers = [
18
18
  "Programming Language :: Python :: 3",
@@ -4,7 +4,7 @@ from setuptools import find_packages, setup
4
4
  def legacy_setup():
5
5
  setup(
6
6
  name="mkdocs-document-dates",
7
- version="3.7.3",
7
+ version="3.8.0",
8
8
  author="Aaron Wang",
9
9
  author_email="aaronwqt@gmail.com",
10
10
  license="MIT",