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.
- {mkdocs_document_dates-3.7.3/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.8.0}/PKG-INFO +4 -3
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/README.md +2 -2
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/cache_manager.py +3 -3
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/plugin.py +50 -63
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/.DS_Store +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_group.html +10 -2
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/utils.py +91 -35
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0/mkdocs_document_dates.egg-info}/PKG-INFO +4 -3
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/requires.txt +1 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/pyproject.toml +3 -3
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/setup.py +1 -1
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/LICENSE +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/MANIFEST.in +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/__init__.py +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks/pre-commit +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks_installer.py +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/config/user.config.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/config/user.config.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/core.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/core.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/default.config.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/md5.min.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/timeago.full.min.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/timeago.min.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/core/utils.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/fonts/material-icons.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/fonts/materialicons.woff2 +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_detail.html +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_grid.html +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/templates/recently_updated_list.html +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/backdrop.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/light.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/material.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/popper.min.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/scale.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/shift-away.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/tippy.css +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/tippy/tippy.umd.min.js +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/SOURCES.txt +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/dependency_links.txt +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/entry_points.txt +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates.egg-info/top_level.txt +0 -0
- {mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/setup.cfg +0 -0
{mkdocs_document_dates-3.7.3/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.8.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-document-dates
|
|
3
|
-
Version: 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
|
-
- [
|
|
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
|
-
- [
|
|
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
|
|
{mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/cache_manager.py
RENAMED
|
@@ -164,7 +164,7 @@ def update_cache():
|
|
|
164
164
|
global_updated = setup_gitattributes(docs_dir)
|
|
165
165
|
|
|
166
166
|
# 获取docs目录下已跟踪(tracked)的markdown文件
|
|
167
|
-
cmd = [
|
|
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)
|
|
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.
|
|
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
|
|
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
|
|
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'
|
|
158
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
{mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/static/.DS_Store
RENAMED
|
Binary file
|
|
@@ -260,11 +260,19 @@
|
|
|
260
260
|
--summary-lines-grid: {{ summary_lines.grid }};
|
|
261
261
|
--summary-lines-detail: {{ summary_lines.detail }};
|
|
262
262
|
">
|
|
263
|
-
{%- for
|
|
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="{{
|
|
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
|
|
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=%
|
|
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.
|
|
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
|
|
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|%
|
|
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
|
-
[
|
|
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 =
|
|
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'
|
|
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
|
-
|
|
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(
|
|
184
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
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
|
{mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0/mkdocs_document_dates.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-document-dates
|
|
3
|
-
Version: 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
|
-
- [
|
|
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
|
+
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
|
-
|
|
14
|
+
"mkdocs>=1.6,<=1.6.1",
|
|
15
|
+
"properdocs>=1.6.5"
|
|
16
16
|
]
|
|
17
17
|
classifiers = [
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
File without changes
|
|
File without changes
|
{mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/__init__.py
RENAMED
|
File without changes
|
{mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks/pre-commit
RENAMED
|
File without changes
|
{mkdocs_document_dates-3.7.3 → mkdocs_document_dates-3.8.0}/mkdocs_document_dates/hooks_installer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|