mkdocs-document-dates 3.5.1__tar.gz → 3.5.2__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.5.1/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.5.2}/PKG-INFO +2 -5
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/README.md +1 -4
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/plugin.py +15 -9
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/core.css +22 -23
- mkdocs_document_dates-3.5.2/mkdocs_document_dates/static/core/core.js +393 -0
- mkdocs_document_dates-3.5.2/mkdocs_document_dates/static/core/md5.min.js +2 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/utils.py +3 -47
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2/mkdocs_document_dates.egg-info}/PKG-INFO +2 -5
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/SOURCES.txt +1 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/setup.py +1 -1
- mkdocs_document_dates-3.5.1/mkdocs_document_dates/static/core/core.js +0 -225
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/LICENSE +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/MANIFEST.in +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/__init__.py +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/cache_manager.py +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/hooks/pre-commit +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/hooks_installer.py +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/config/user.config.css +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/config/user.config.js +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/default.config.js +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/timeago.full.min.js +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/timeago.min.js +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/utils.js +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/fonts/material-icons.css +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/fonts/materialicons.woff2 +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/templates/recently_updated.html +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/backdrop.css +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/light.css +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/material.css +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/popper.min.js +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/scale.css +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/shift-away.css +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/tippy.css +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/tippy.umd.min.js +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/dependency_links.txt +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/entry_points.txt +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/requires.txt +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/top_level.txt +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/pyproject.toml +0 -0
- {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/setup.cfg +0 -0
{mkdocs_document_dates-3.5.1/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.5.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-document-dates
|
|
3
|
-
Version: 3.5.
|
|
3
|
+
Version: 3.5.2
|
|
4
4
|
Summary: A new generation MkDocs plugin for displaying exact creation date, last updated date, authors, email of documents
|
|
5
5
|
Home-page: https://github.com/jaywhj/mkdocs-document-dates
|
|
6
6
|
Author: Aaron Wang
|
|
@@ -79,9 +79,6 @@ plugins:
|
|
|
79
79
|
- blog/* # Example: exclude all files in blog folder, including subfolders
|
|
80
80
|
date_format: '%Y-%m-%d' # Date format strings (e.g., %Y-%m-%d, %b %d, %Y)
|
|
81
81
|
time_format: '%H:%M:%S' # Time format strings (valid only if type=datetime)
|
|
82
|
-
show_created: true # Show creation date: true false, default: true
|
|
83
|
-
show_updated: true # Show last updated date: true false, default: true
|
|
84
|
-
show_author: true # Show author: true(avatar) text(text) false(hidden), default: true
|
|
85
82
|
```
|
|
86
83
|
|
|
87
84
|
## Customization Settings
|
|
@@ -91,7 +88,7 @@ In addition to the above basic configuration, the plug-in also provides a wealth
|
|
|
91
88
|
- [Specify Datetime](https://jaywhj.netlify.app/document-dates-en#Specify-Datetime): Introduces the mechanism for obtaining document dates and methods for personalized customization, you can manually specify the creation date and last updated date for each document
|
|
92
89
|
- [Specify Author](https://jaywhj.netlify.app/document-dates-en#Specify-Author): Introduces the mechanism for obtaining document authors and methods for personalized customization, you can manually specify the author information for each document, such as name, link, avatar, email, etc.
|
|
93
90
|
- [Specify Avatar](https://jaywhj.netlify.app/document-dates-en#Specify-Avatar): You can manually specify the avatar for each author, support local file path and URL path
|
|
94
|
-
- [
|
|
91
|
+
- [Configuration 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
|
|
95
92
|
- [Use Template Variables](https://jaywhj.netlify.app/document-dates-en#Use-Template-Variables): Can be used to optimize `sitemap.xml` for site SEO; Can be used to re-customize plug-ins, etc.
|
|
96
93
|
- [Add Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Add-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
|
|
97
94
|
- [Add Localization Language](https://jaywhj.netlify.app/document-dates-en#Add-Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
@@ -53,9 +53,6 @@ plugins:
|
|
|
53
53
|
- blog/* # Example: exclude all files in blog folder, including subfolders
|
|
54
54
|
date_format: '%Y-%m-%d' # Date format strings (e.g., %Y-%m-%d, %b %d, %Y)
|
|
55
55
|
time_format: '%H:%M:%S' # Time format strings (valid only if type=datetime)
|
|
56
|
-
show_created: true # Show creation date: true false, default: true
|
|
57
|
-
show_updated: true # Show last updated date: true false, default: true
|
|
58
|
-
show_author: true # Show author: true(avatar) text(text) false(hidden), default: true
|
|
59
56
|
```
|
|
60
57
|
|
|
61
58
|
## Customization Settings
|
|
@@ -65,7 +62,7 @@ In addition to the above basic configuration, the plug-in also provides a wealth
|
|
|
65
62
|
- [Specify Datetime](https://jaywhj.netlify.app/document-dates-en#Specify-Datetime): Introduces the mechanism for obtaining document dates and methods for personalized customization, you can manually specify the creation date and last updated date for each document
|
|
66
63
|
- [Specify Author](https://jaywhj.netlify.app/document-dates-en#Specify-Author): Introduces the mechanism for obtaining document authors and methods for personalized customization, you can manually specify the author information for each document, such as name, link, avatar, email, etc.
|
|
67
64
|
- [Specify Avatar](https://jaywhj.netlify.app/document-dates-en#Specify-Avatar): You can manually specify the avatar for each author, support local file path and URL path
|
|
68
|
-
- [
|
|
65
|
+
- [Configuration 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
|
|
69
66
|
- [Use Template Variables](https://jaywhj.netlify.app/document-dates-en#Use-Template-Variables): Can be used to optimize `sitemap.xml` for site SEO; Can be used to re-customize plug-ins, etc.
|
|
70
67
|
- [Add Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Add-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
|
|
71
68
|
- [Add Localization Language](https://jaywhj.netlify.app/document-dates-en#Add-Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
@@ -132,6 +132,7 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
132
132
|
|
|
133
133
|
# 添加自定义 JS 文件
|
|
134
134
|
config['extra_javascript'].extend([
|
|
135
|
+
'assets/document_dates/core/md5.min.js',
|
|
135
136
|
'assets/document_dates/core/default.config.js',
|
|
136
137
|
'assets/document_dates/user.config.js',
|
|
137
138
|
'assets/document_dates/core/utils.js',
|
|
@@ -168,7 +169,7 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
168
169
|
return markdown
|
|
169
170
|
|
|
170
171
|
# 生成日期和作者信息 HTML
|
|
171
|
-
info_html = self._generate_html_info(created, updated, authors)
|
|
172
|
+
info_html = self._generate_html_info(page.meta, created, updated, authors)
|
|
172
173
|
|
|
173
174
|
# 将信息写入 markdown
|
|
174
175
|
return self._insert_date_info(markdown, info_html)
|
|
@@ -362,10 +363,14 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
362
363
|
return date.strftime(f"{self.config['date_format']} {self.config['time_format']}")
|
|
363
364
|
return date.strftime(self.config['date_format'])
|
|
364
365
|
|
|
365
|
-
def _generate_html_info(self, created: datetime, updated: datetime, authors=None):
|
|
366
|
+
def _generate_html_info(self, meta, created: datetime, updated: datetime, authors=None):
|
|
366
367
|
try:
|
|
367
|
-
|
|
368
|
-
|
|
368
|
+
show_created = self.config['show_created'] and meta.get('show_created') is not False
|
|
369
|
+
show_updated = self.config['show_updated'] and meta.get('show_updated') is not False
|
|
370
|
+
show_author = self.config['show_author'] and meta.get('show_author') is not False
|
|
371
|
+
|
|
372
|
+
show_dates = show_created or show_updated
|
|
373
|
+
show_plugin = show_dates or show_author
|
|
369
374
|
if not show_plugin:
|
|
370
375
|
return ""
|
|
371
376
|
|
|
@@ -387,15 +392,15 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
387
392
|
# 构建日期
|
|
388
393
|
if show_dates:
|
|
389
394
|
html_parts.append("<div class='dd-left'>")
|
|
390
|
-
if
|
|
395
|
+
if show_created:
|
|
391
396
|
html_parts.append(build_time_icon(created, 'doc_created'))
|
|
392
|
-
if
|
|
397
|
+
if show_updated:
|
|
393
398
|
html_parts.append(build_time_icon(updated, 'doc_updated'))
|
|
394
399
|
if show_dates:
|
|
395
400
|
html_parts.append("</div>")
|
|
396
401
|
|
|
397
402
|
# 构建作者
|
|
398
|
-
if
|
|
403
|
+
if show_author and authors:
|
|
399
404
|
def get_author_tooltip(author):
|
|
400
405
|
if author.url:
|
|
401
406
|
return f'<a href="{author.url}" target="_blank">{author.name}</a>'
|
|
@@ -410,7 +415,8 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
410
415
|
icon = 'doc_author' if len(authors) == 1 else 'doc_authors'
|
|
411
416
|
html_parts.append(f"<span class='material-icons' data-icon='{icon}'></span>")
|
|
412
417
|
html_parts.append("<div class='author-group'>")
|
|
413
|
-
|
|
418
|
+
show_text = self.config['show_author'] == 'text' or meta.get('show_author') == 'text'
|
|
419
|
+
if show_text:
|
|
414
420
|
# 显示文本模式
|
|
415
421
|
for index, author in enumerate(authors):
|
|
416
422
|
if index > 0:
|
|
@@ -424,7 +430,7 @@ class DocumentDatesPlugin(BasePlugin):
|
|
|
424
430
|
html_parts.append(
|
|
425
431
|
f"<div class='avatar-wrapper' data-name='{author.name}' data-tippy-content data-tippy-raw='{tooltip}'>"
|
|
426
432
|
f"<span class='avatar-text'></span>"
|
|
427
|
-
f"<img class='avatar' src='{author.avatar}'
|
|
433
|
+
f"<img class='avatar' data-src='{author.avatar}' data-email='{author.email}' />"
|
|
428
434
|
f"</div>"
|
|
429
435
|
)
|
|
430
436
|
html_parts.append("</div>")
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
color: #666;
|
|
24
24
|
font-size: 0.9rem;
|
|
25
25
|
display: flex;
|
|
26
|
-
/* flex-wrap: nowrap; */
|
|
27
26
|
align-items: center;
|
|
27
|
+
flex-wrap: nowrap;
|
|
28
28
|
justify-content: space-between;
|
|
29
|
-
padding: 0.
|
|
30
|
-
margin-bottom: 0.
|
|
29
|
+
padding: 0.1rem 0;
|
|
30
|
+
margin-bottom: 0.5rem;
|
|
31
31
|
}
|
|
32
32
|
.md-main .document-dates-plugin {
|
|
33
33
|
color: rgba(142, 142, 142, 0.7);
|
|
@@ -38,9 +38,6 @@
|
|
|
38
38
|
font-size: 1rem;
|
|
39
39
|
opacity: 0.85;
|
|
40
40
|
margin-right: 0.3rem;
|
|
41
|
-
display: inline-flex;
|
|
42
|
-
align-items: center;
|
|
43
|
-
line-height: 1;
|
|
44
41
|
}
|
|
45
42
|
.md-main .document-dates-plugin .material-icons {
|
|
46
43
|
font-size: 0.9rem;
|
|
@@ -70,17 +67,17 @@
|
|
|
70
67
|
.dd-left {
|
|
71
68
|
display: flex;
|
|
72
69
|
align-items: center;
|
|
73
|
-
|
|
70
|
+
flex-shrink: 0;
|
|
71
|
+
margin-right: 1.4rem;
|
|
74
72
|
}
|
|
75
73
|
.dd-item {
|
|
76
74
|
display: inline-flex;
|
|
77
75
|
align-items: center;
|
|
78
76
|
white-space: nowrap;
|
|
79
77
|
flex-shrink: 0;
|
|
80
|
-
line-height: 1;
|
|
81
78
|
}
|
|
82
79
|
.dd-left > .dd-item + .dd-item {
|
|
83
|
-
margin-left: 1.
|
|
80
|
+
margin-left: 1.4rem;
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
.dd-right {
|
|
@@ -88,13 +85,25 @@
|
|
|
88
85
|
align-items: center;
|
|
89
86
|
justify-content: flex-end;
|
|
90
87
|
flex: 1 1 auto;
|
|
91
|
-
min-width:
|
|
88
|
+
min-width: 0;
|
|
89
|
+
overflow: hidden;
|
|
92
90
|
}
|
|
93
|
-
|
|
91
|
+
/* Left-aligned when only dd-right */
|
|
94
92
|
.dd-right.dd-right-start {
|
|
95
93
|
justify-content: flex-start;
|
|
96
94
|
}
|
|
97
95
|
|
|
96
|
+
/* JS control toggle */
|
|
97
|
+
.document-dates-plugin.is-wrapped {
|
|
98
|
+
flex-wrap: wrap;
|
|
99
|
+
}
|
|
100
|
+
.document-dates-plugin.is-wrapped .dd-right {
|
|
101
|
+
flex: 0 0 100%;
|
|
102
|
+
justify-content: flex-start;
|
|
103
|
+
margin-top: 10px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
98
107
|
/* ==========================================================================
|
|
99
108
|
3. Author Avatar styles
|
|
100
109
|
========================================================================== */
|
|
@@ -111,9 +120,11 @@
|
|
|
111
120
|
|
|
112
121
|
margin-right: 10px;
|
|
113
122
|
|
|
123
|
+
/* Hide Firefox, IE scrollbar */
|
|
114
124
|
scrollbar-width: none;
|
|
115
125
|
-ms-overflow-style: none;
|
|
116
126
|
}
|
|
127
|
+
/* Hide Chrome/Safari scrollbar */
|
|
117
128
|
.author-group::-webkit-scrollbar {
|
|
118
129
|
display: none;
|
|
119
130
|
}
|
|
@@ -168,18 +179,6 @@
|
|
|
168
179
|
flex-shrink: 0;
|
|
169
180
|
}
|
|
170
181
|
|
|
171
|
-
/* Optimize the experience with line breaks on very narrow screens */
|
|
172
|
-
@media screen and (max-width: 359px) {
|
|
173
|
-
.document-dates-plugin {
|
|
174
|
-
flex-wrap: wrap;
|
|
175
|
-
}
|
|
176
|
-
.dd-right {
|
|
177
|
-
justify-content: flex-start;
|
|
178
|
-
width: 100%;
|
|
179
|
-
margin: 10px 0 0 0;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
182
|
|
|
184
183
|
/* ==========================================================================
|
|
185
184
|
4. Tooltip styles, including theme, arrow, font, color, background color, etc
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/*
|
|
2
|
+
1.加载头像
|
|
3
|
+
*/
|
|
4
|
+
function isLatin(name) {
|
|
5
|
+
return /^[A-Za-z\s]+$/.test(name.trim());
|
|
6
|
+
}
|
|
7
|
+
function extractInitials(name) {
|
|
8
|
+
name = name.trim();
|
|
9
|
+
if (!name) return '?';
|
|
10
|
+
if (isLatin(name)) {
|
|
11
|
+
const parts = name.toUpperCase().split(/\s+/).filter(Boolean);
|
|
12
|
+
return parts.length >= 2 ? parts[0][0] + parts[parts.length - 1][0] : parts[0][0];
|
|
13
|
+
} else {
|
|
14
|
+
return name[0];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function nameToHSL(name, s = 50, l = 55) {
|
|
18
|
+
let hash = 0;
|
|
19
|
+
for (let i = 0; i < name.length; i++) {
|
|
20
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
21
|
+
}
|
|
22
|
+
const hue = hash % 360;
|
|
23
|
+
return `hsl(${hue}, ${s}%, ${l}%)`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadAvatars() {
|
|
27
|
+
document.querySelectorAll('.avatar-wrapper').forEach(wrapper => {
|
|
28
|
+
const name = wrapper.dataset.name || '';
|
|
29
|
+
const initials = extractInitials(name);
|
|
30
|
+
const bgColor = nameToHSL(name);
|
|
31
|
+
|
|
32
|
+
const textEl = wrapper.querySelector('.avatar-text');
|
|
33
|
+
textEl.textContent = initials;
|
|
34
|
+
textEl.style.backgroundColor = bgColor;
|
|
35
|
+
|
|
36
|
+
const imgEl = wrapper.querySelector('img.avatar');
|
|
37
|
+
if (imgEl) {
|
|
38
|
+
const urls = [];
|
|
39
|
+
const dataSrc = (imgEl.dataset.src || '').trim();
|
|
40
|
+
const email = (imgEl.dataset.email || '').trim();
|
|
41
|
+
|
|
42
|
+
if (dataSrc) {
|
|
43
|
+
urls.push(dataSrc);
|
|
44
|
+
}
|
|
45
|
+
if (email && AvatarService.base) {
|
|
46
|
+
const hash = md5(email.trim().toLowerCase());
|
|
47
|
+
urls.push(AvatarService.build(hash));
|
|
48
|
+
}
|
|
49
|
+
if (urls.length === 0) {
|
|
50
|
+
imgEl.style.display = 'none';
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
bindAvatar(imgEl, urls);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function bindAvatar(imgEl, urls) {
|
|
59
|
+
let index = 0;
|
|
60
|
+
function next() {
|
|
61
|
+
if (index >= urls.length) {
|
|
62
|
+
imgEl.style.display = 'none';
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
imgEl.src = urls[index++];
|
|
66
|
+
}
|
|
67
|
+
imgEl.onerror = next;
|
|
68
|
+
// 加载成功后清掉 onerror
|
|
69
|
+
imgEl.onload = () => imgEl.onerror = null;
|
|
70
|
+
|
|
71
|
+
next();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const AVATAR_CDNS = {
|
|
75
|
+
// gravatar: 全球通用头像服务商,国内访问不稳定
|
|
76
|
+
// weavatar: gravatar 国内镜像,实测发现国内外访问都正常,数据跟 gravatar 一致
|
|
77
|
+
// cravatar: 国内头像服务商,有国际 CDN,很多开源项目在用,实测发现只包含部分 gravatar 数据
|
|
78
|
+
|
|
79
|
+
gravatar: 'https://www.gravatar.com/avatar',
|
|
80
|
+
weavatar: 'https://weavatar.com/avatar'
|
|
81
|
+
// cravatar: 'https://cravatar.cn/avatar'
|
|
82
|
+
};
|
|
83
|
+
const PROBE_TARGETS = [
|
|
84
|
+
{ name: 'gravatar', probe: 'https://www.gravatar.com/favicon.ico' },
|
|
85
|
+
{ name: 'weavatar', probe: 'https://weavatar.com/favicon.ico' }
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
let avatarServiceInited = false;
|
|
89
|
+
const AvatarService = {
|
|
90
|
+
base: null,
|
|
91
|
+
async init() {
|
|
92
|
+
if (avatarServiceInited) return;
|
|
93
|
+
avatarServiceInited = true;
|
|
94
|
+
|
|
95
|
+
const winner = await raceAvatarCDN(PROBE_TARGETS.map(t => t.probe));
|
|
96
|
+
if (!winner) return;
|
|
97
|
+
|
|
98
|
+
const hit = PROBE_TARGETS.find(t => t.probe === winner);
|
|
99
|
+
this.base = AVATAR_CDNS[hit.name];
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// style: '404' 'wavatar' 'retro' 'identicon' 'mp' 'monsterid' 'robohash' 'blank'
|
|
103
|
+
// size: avatar size in pixels (1~2048)
|
|
104
|
+
build(emailHash, style = '404', size = 128) {
|
|
105
|
+
if (!this.base) return null;
|
|
106
|
+
return `${this.base}/${emailHash}?d=${style}&s=${size}`;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
function raceAvatarCDN(urls, timeout = 500) {
|
|
110
|
+
return new Promise(resolve => {
|
|
111
|
+
let done = false;
|
|
112
|
+
urls.forEach(url => {
|
|
113
|
+
const img = new Image();
|
|
114
|
+
const timer = setTimeout(() => {
|
|
115
|
+
img.src = '';
|
|
116
|
+
}, timeout);
|
|
117
|
+
img.onload = () => {
|
|
118
|
+
if (done) return;
|
|
119
|
+
done = true;
|
|
120
|
+
clearTimeout(timer);
|
|
121
|
+
resolve(url);
|
|
122
|
+
};
|
|
123
|
+
img.onerror = () => {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
};
|
|
126
|
+
img.src = url;
|
|
127
|
+
});
|
|
128
|
+
// 兜底(全部失败 / 全部超时),不让 Promise 永远 pending
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
if (!done) resolve(null);
|
|
131
|
+
}, timeout);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
2.处理内容赋值
|
|
139
|
+
*/
|
|
140
|
+
// 图标键映射表
|
|
141
|
+
const iconKeyMap = {
|
|
142
|
+
doc_created: 'created_time',
|
|
143
|
+
doc_updated: 'updated_time',
|
|
144
|
+
doc_author: 'author',
|
|
145
|
+
doc_authors: 'authors'
|
|
146
|
+
};
|
|
147
|
+
// 处理数据加载
|
|
148
|
+
function processDataLoading() {
|
|
149
|
+
document.querySelectorAll('.document-dates-plugin').forEach(ddpEl => {
|
|
150
|
+
// 获取 locale,优先级:用户主动选择 > 服务端显式配置 > 用户浏览器语言 > 站点HTML语言 > 默认英语
|
|
151
|
+
const rawLocale =
|
|
152
|
+
ddUtils.getSavedLanguage() ||
|
|
153
|
+
ddpEl.getAttribute('locale') ||
|
|
154
|
+
navigator.language ||
|
|
155
|
+
navigator.userLanguage ||
|
|
156
|
+
document.documentElement.lang ||
|
|
157
|
+
'en';
|
|
158
|
+
|
|
159
|
+
// 处理 time 元素(使用 timeago 时)
|
|
160
|
+
if (typeof timeago !== 'undefined') {
|
|
161
|
+
const tLocale = ddUtils.resolveTimeagoLocale(rawLocale);
|
|
162
|
+
ddpEl.querySelectorAll('time').forEach(timeEl => {
|
|
163
|
+
timeEl.textContent = timeago.format(timeEl.getAttribute('datetime'), tLocale);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 动态处理 tooltip 内容
|
|
168
|
+
const langData = TooltipLanguage.get(rawLocale);
|
|
169
|
+
ddpEl.querySelectorAll('[data-tippy-content]').forEach(tippyEl => {
|
|
170
|
+
const iconEl = tippyEl.querySelector('[data-icon]');
|
|
171
|
+
const rawIconKey = iconEl ? iconEl.getAttribute('data-icon') : '';
|
|
172
|
+
const iconKey = iconKeyMap[rawIconKey] || 'author';
|
|
173
|
+
if (langData[iconKey]) {
|
|
174
|
+
const content = langData[iconKey] + ': ' + tippyEl.dataset.tippyRaw;
|
|
175
|
+
if (tippyEl._tippy) {
|
|
176
|
+
tippyEl._tippy.setContent(content);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 供外部使用:更新文档日期和 tippy 内容到指定语言(可持久化)
|
|
184
|
+
function updateDocumentDates(locale) {
|
|
185
|
+
ddUtils.saveLanguage(locale);
|
|
186
|
+
processDataLoading();
|
|
187
|
+
}
|
|
188
|
+
window.ddPlugin = {
|
|
189
|
+
updateLanguage: updateDocumentDates
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
/*
|
|
195
|
+
3.初始化 tippyManager,创建和管理 tippy 实例
|
|
196
|
+
*/
|
|
197
|
+
function getCurrentTheme() {
|
|
198
|
+
// 基于 Material's light/dark 配色方案返回对应的 tooltip 主题
|
|
199
|
+
const scheme = (document.body && document.body.getAttribute('data-md-color-scheme')) || 'default';
|
|
200
|
+
return scheme === 'slate' ? tooltip_config.theme.dark : tooltip_config.theme.light;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function initTippy() {
|
|
204
|
+
// 创建上下文对象,将其传递给钩子并从函数中返回
|
|
205
|
+
const context = { tooltip_config };
|
|
206
|
+
|
|
207
|
+
// 创建 tippy 实例
|
|
208
|
+
const tippyInstances = tippy('[data-tippy-content]', {
|
|
209
|
+
...tooltip_config,
|
|
210
|
+
theme: getCurrentTheme()
|
|
211
|
+
});
|
|
212
|
+
context.tippyInstances = tippyInstances;
|
|
213
|
+
|
|
214
|
+
// 添加观察者,监控 Material's 配色变化,自动切换 tooltip 主题
|
|
215
|
+
const observer = new MutationObserver((mutations) => {
|
|
216
|
+
mutations.forEach((mutation) => {
|
|
217
|
+
if (mutation.attributeName === 'data-md-color-scheme') {
|
|
218
|
+
const newTheme = getCurrentTheme();
|
|
219
|
+
tippyInstances.forEach(instance => {
|
|
220
|
+
instance.setProps({ theme: newTheme });
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
observer.observe(document.body, {
|
|
226
|
+
attributes: true,
|
|
227
|
+
attributeFilter: ['data-md-color-scheme']
|
|
228
|
+
});
|
|
229
|
+
context.observer = observer;
|
|
230
|
+
|
|
231
|
+
// 返回包含 tippyInstances 和 observer 的上下文,用于后续清理
|
|
232
|
+
return context;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 在滚动时隐藏 author-group 的 tooltip
|
|
236
|
+
function initAuthorGroupTippyGuard() {
|
|
237
|
+
document.querySelectorAll('.author-group').forEach(groupEl => {
|
|
238
|
+
// 先取消旧监听器,避免重复绑定
|
|
239
|
+
if (groupEl._ddTippyGuardAbortController) {
|
|
240
|
+
groupEl._ddTippyGuardAbortController.abort();
|
|
241
|
+
}
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
groupEl._ddTippyGuardAbortController = controller;
|
|
244
|
+
|
|
245
|
+
const tippyTargets = groupEl.querySelectorAll('[data-tippy-content]');
|
|
246
|
+
const hideAllTippies = () => {
|
|
247
|
+
tippyTargets.forEach(tippyEl => {
|
|
248
|
+
if (tippyEl._tippy) {
|
|
249
|
+
tippyEl._tippy.hide();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
// true: 浏览器立刻执行默认行为。这个事件监听器只是‘看看’,绝不会阻止浏览器的默认行为
|
|
254
|
+
const opts = { passive: true, signal: controller.signal };
|
|
255
|
+
groupEl.addEventListener('scroll', hideAllTippies, opts);
|
|
256
|
+
groupEl.addEventListener('touchmove', hideAllTippies, opts);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 通过 IIFE(立即执行的函数表达式)创建 tippyManager
|
|
261
|
+
const tippyManager = (() => {
|
|
262
|
+
let tippyInstances = [];
|
|
263
|
+
let observer = null;
|
|
264
|
+
function cleanup() {
|
|
265
|
+
// 销毁之前的 tippy 实例
|
|
266
|
+
if (tippyInstances.length > 0) {
|
|
267
|
+
tippyInstances.forEach(instance => instance.destroy());
|
|
268
|
+
tippyInstances = [];
|
|
269
|
+
}
|
|
270
|
+
// 断开之前的观察者连接
|
|
271
|
+
if (observer) {
|
|
272
|
+
observer.disconnect();
|
|
273
|
+
observer = null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
// 每一次调用都生成新的实例(兼容 navigation.instant)
|
|
278
|
+
initialize() {
|
|
279
|
+
// 先清理以前的实例
|
|
280
|
+
cleanup();
|
|
281
|
+
// 初始化新实例
|
|
282
|
+
const context = initTippy();
|
|
283
|
+
if (context && context.tippyInstances) {
|
|
284
|
+
tippyInstances = context.tippyInstances;
|
|
285
|
+
}
|
|
286
|
+
if (context && context.observer) {
|
|
287
|
+
observer = context.observer;
|
|
288
|
+
}
|
|
289
|
+
initAuthorGroupTippyGuard();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
})();
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
// 为 author-group 启用横向滚轮滚动
|
|
296
|
+
function enableHorizontalWheelScroll() {
|
|
297
|
+
// 移动端不接管滚轮
|
|
298
|
+
const isTouchDevice =
|
|
299
|
+
'ontouchstart' in window ||
|
|
300
|
+
navigator.maxTouchPoints > 0 ||
|
|
301
|
+
navigator.msMaxTouchPoints > 0;
|
|
302
|
+
if (isTouchDevice) return;
|
|
303
|
+
|
|
304
|
+
document.querySelectorAll('.author-group').forEach(groupEl => {
|
|
305
|
+
// 先取消旧监听器,避免重复绑定
|
|
306
|
+
if (groupEl._ddWheelAbortController) {
|
|
307
|
+
groupEl._ddWheelAbortController.abort();
|
|
308
|
+
}
|
|
309
|
+
const controller = new AbortController();
|
|
310
|
+
groupEl._ddWheelAbortController = controller;
|
|
311
|
+
|
|
312
|
+
groupEl.addEventListener('wheel', function (event) {
|
|
313
|
+
// 只处理纵向滚轮(触控板横向滑动不干预)
|
|
314
|
+
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return;
|
|
315
|
+
// 在 author-group 内,始终阻止页面纵向滚动
|
|
316
|
+
event.preventDefault();
|
|
317
|
+
|
|
318
|
+
const scrollWidth = groupEl.scrollWidth;
|
|
319
|
+
const clientWidth = groupEl.clientWidth;
|
|
320
|
+
// 元素不可横向滚动时返回
|
|
321
|
+
if (scrollWidth <= clientWidth) return;
|
|
322
|
+
|
|
323
|
+
const delta = event.deltaY;
|
|
324
|
+
const atLeft = groupEl.scrollLeft <= 0;
|
|
325
|
+
const atRight = groupEl.scrollLeft + clientWidth >= scrollWidth - 1;
|
|
326
|
+
|
|
327
|
+
if ((delta < 0 && !atLeft) || (delta > 0 && !atRight)) {
|
|
328
|
+
groupEl.scrollLeft += delta;
|
|
329
|
+
}
|
|
330
|
+
}, {
|
|
331
|
+
// false: 浏览器你先别急着执行默认行为,等 JS 跑完,再决定要不要动,因为可能会调用 event.preventDefault()
|
|
332
|
+
passive: false,
|
|
333
|
+
signal: controller.signal
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
// 为 author-group 添加自适应动态布局
|
|
340
|
+
function handleDocumentDatesAutoWrap() {
|
|
341
|
+
// 设定作者区域最小的显示宽度,大概2个作者宽度
|
|
342
|
+
const AUTHOR_THRESHOLD = 140;
|
|
343
|
+
document.querySelectorAll('.document-dates-plugin').forEach(ddpEl => {
|
|
344
|
+
const leftPart = ddpEl.querySelector('.dd-left');
|
|
345
|
+
const rightPart = ddpEl.querySelector('.dd-right');
|
|
346
|
+
if (!leftPart || !rightPart) return;
|
|
347
|
+
|
|
348
|
+
// 使用 getBoundingClientRect 更加精确(包含小数)
|
|
349
|
+
const containerWidth = ddpEl.getBoundingClientRect().width;
|
|
350
|
+
const leftWidth = leftPart.getBoundingClientRect().width;
|
|
351
|
+
if (containerWidth <= leftWidth) return;
|
|
352
|
+
|
|
353
|
+
// 如果: 容器总宽度 < 日期宽度 + 2个作者宽度,则换行
|
|
354
|
+
const shouldWrap = containerWidth < (leftWidth + AUTHOR_THRESHOLD);
|
|
355
|
+
// 只有在状态确实需要改变时才操作 DOM
|
|
356
|
+
if (ddpEl.classList.contains('is-wrapped') !== shouldWrap) {
|
|
357
|
+
ddpEl.classList.toggle('is-wrapped', shouldWrap);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
/*
|
|
364
|
+
入口
|
|
365
|
+
*/
|
|
366
|
+
let datesAutoWrapObserver = null;
|
|
367
|
+
function initPluginFeatures() {
|
|
368
|
+
tippyManager.initialize();
|
|
369
|
+
processDataLoading();
|
|
370
|
+
AvatarService.init().then(() => {
|
|
371
|
+
loadAvatars();
|
|
372
|
+
});
|
|
373
|
+
enableHorizontalWheelScroll();
|
|
374
|
+
|
|
375
|
+
// 观察插件尺寸变化,resize 时动态处理布局
|
|
376
|
+
if (datesAutoWrapObserver) datesAutoWrapObserver.disconnect();
|
|
377
|
+
datesAutoWrapObserver = new ResizeObserver(() => {
|
|
378
|
+
// 使用 RAF 确保在浏览器重绘前处理,减少视觉跳动
|
|
379
|
+
window.requestAnimationFrame(() => {
|
|
380
|
+
handleDocumentDatesAutoWrap();
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
document.querySelectorAll('.document-dates-plugin').forEach(ddpEl => datesAutoWrapObserver.observe(ddpEl));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 兼容 Material 主题的 'navigation.instant' 属性
|
|
387
|
+
if (window.document$ && !window.document$.isStopped) {
|
|
388
|
+
window.document$.subscribe(initPluginFeatures);
|
|
389
|
+
} else if (document.readyState === 'loading') {
|
|
390
|
+
document.addEventListener('DOMContentLoaded', initPluginFeatures);
|
|
391
|
+
} else {
|
|
392
|
+
initPluginFeatures();
|
|
393
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(n){"use strict";function d(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function f(n,t,r,e,o,u){return d((u=d(d(t,n),d(e,u)))<<o|u>>>32-o,r)}function l(n,t,r,e,o,u,c){return f(t&r|~t&e,n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t&e|r&~e,n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function c(n,t){var r,e,o,u;n[t>>5]|=128<<t%32,n[14+(t+64>>>9<<4)]=t;for(var c=1732584193,f=-271733879,i=-1732584194,a=271733878,h=0;h<n.length;h+=16)c=l(r=c,e=f,o=i,u=a,n[h],7,-680876936),a=l(a,c,f,i,n[h+1],12,-389564586),i=l(i,a,c,f,n[h+2],17,606105819),f=l(f,i,a,c,n[h+3],22,-1044525330),c=l(c,f,i,a,n[h+4],7,-176418897),a=l(a,c,f,i,n[h+5],12,1200080426),i=l(i,a,c,f,n[h+6],17,-1473231341),f=l(f,i,a,c,n[h+7],22,-45705983),c=l(c,f,i,a,n[h+8],7,1770035416),a=l(a,c,f,i,n[h+9],12,-1958414417),i=l(i,a,c,f,n[h+10],17,-42063),f=l(f,i,a,c,n[h+11],22,-1990404162),c=l(c,f,i,a,n[h+12],7,1804603682),a=l(a,c,f,i,n[h+13],12,-40341101),i=l(i,a,c,f,n[h+14],17,-1502002290),c=g(c,f=l(f,i,a,c,n[h+15],22,1236535329),i,a,n[h+1],5,-165796510),a=g(a,c,f,i,n[h+6],9,-1069501632),i=g(i,a,c,f,n[h+11],14,643717713),f=g(f,i,a,c,n[h],20,-373897302),c=g(c,f,i,a,n[h+5],5,-701558691),a=g(a,c,f,i,n[h+10],9,38016083),i=g(i,a,c,f,n[h+15],14,-660478335),f=g(f,i,a,c,n[h+4],20,-405537848),c=g(c,f,i,a,n[h+9],5,568446438),a=g(a,c,f,i,n[h+14],9,-1019803690),i=g(i,a,c,f,n[h+3],14,-187363961),f=g(f,i,a,c,n[h+8],20,1163531501),c=g(c,f,i,a,n[h+13],5,-1444681467),a=g(a,c,f,i,n[h+2],9,-51403784),i=g(i,a,c,f,n[h+7],14,1735328473),c=v(c,f=g(f,i,a,c,n[h+12],20,-1926607734),i,a,n[h+5],4,-378558),a=v(a,c,f,i,n[h+8],11,-2022574463),i=v(i,a,c,f,n[h+11],16,1839030562),f=v(f,i,a,c,n[h+14],23,-35309556),c=v(c,f,i,a,n[h+1],4,-1530992060),a=v(a,c,f,i,n[h+4],11,1272893353),i=v(i,a,c,f,n[h+7],16,-155497632),f=v(f,i,a,c,n[h+10],23,-1094730640),c=v(c,f,i,a,n[h+13],4,681279174),a=v(a,c,f,i,n[h],11,-358537222),i=v(i,a,c,f,n[h+3],16,-722521979),f=v(f,i,a,c,n[h+6],23,76029189),c=v(c,f,i,a,n[h+9],4,-640364487),a=v(a,c,f,i,n[h+12],11,-421815835),i=v(i,a,c,f,n[h+15],16,530742520),c=m(c,f=v(f,i,a,c,n[h+2],23,-995338651),i,a,n[h],6,-198630844),a=m(a,c,f,i,n[h+7],10,1126891415),i=m(i,a,c,f,n[h+14],15,-1416354905),f=m(f,i,a,c,n[h+5],21,-57434055),c=m(c,f,i,a,n[h+12],6,1700485571),a=m(a,c,f,i,n[h+3],10,-1894986606),i=m(i,a,c,f,n[h+10],15,-1051523),f=m(f,i,a,c,n[h+1],21,-2054922799),c=m(c,f,i,a,n[h+8],6,1873313359),a=m(a,c,f,i,n[h+15],10,-30611744),i=m(i,a,c,f,n[h+6],15,-1560198380),f=m(f,i,a,c,n[h+13],21,1309151649),c=m(c,f,i,a,n[h+4],6,-145523070),a=m(a,c,f,i,n[h+11],10,-1120210379),i=m(i,a,c,f,n[h+2],15,718787259),f=m(f,i,a,c,n[h+9],21,-343485551),c=d(c,r),f=d(f,e),i=d(i,o),a=d(a,u);return[c,f,i,a]}function i(n){for(var t="",r=32*n.length,e=0;e<r;e+=8)t+=String.fromCharCode(n[e>>5]>>>e%32&255);return t}function a(n){var t=[];for(t[(n.length>>2)-1]=void 0,e=0;e<t.length;e+=1)t[e]=0;for(var r=8*n.length,e=0;e<r;e+=8)t[e>>5]|=(255&n.charCodeAt(e/8))<<e%32;return t}function e(n){for(var t,r="0123456789abcdef",e="",o=0;o<n.length;o+=1)t=n.charCodeAt(o),e+=r.charAt(t>>>4&15)+r.charAt(15&t);return e}function r(n){return unescape(encodeURIComponent(n))}function o(n){return i(c(a(n=r(n)),8*n.length))}function u(n,t){return function(n,t){var r,e=a(n),o=[],u=[];for(o[15]=u[15]=void 0,16<e.length&&(e=c(e,8*n.length)),r=0;r<16;r+=1)o[r]=909522486^e[r],u[r]=1549556828^e[r];return t=c(o.concat(a(t)),512+8*t.length),i(c(u.concat(t),640))}(r(n),r(t))}function t(n,t,r){return t?r?u(t,n):e(u(t,n)):r?o(n):e(o(n))}"function"==typeof define&&define.amd?define(function(){return t}):"object"==typeof module&&module.exports?module.exports=t:n.md5=t}(this);
|
|
2
|
+
//# sourceMappingURL=md5.min.js.map
|
|
@@ -3,8 +3,6 @@ import platform
|
|
|
3
3
|
import json
|
|
4
4
|
import heapq
|
|
5
5
|
import logging
|
|
6
|
-
import hashlib
|
|
7
|
-
import requests
|
|
8
6
|
import subprocess
|
|
9
7
|
from pathlib import Path
|
|
10
8
|
from datetime import datetime
|
|
@@ -68,8 +66,6 @@ def load_git_metadata(docs_dir_path: Path):
|
|
|
68
66
|
cmd = ['git', 'log', '--reverse', '--no-merges', '--use-mailmap', '--name-only', '--format=%aN|%aE|%aI', f'--relative={rel_docs_path}', '--', '*.md']
|
|
69
67
|
process = subprocess.run(cmd, cwd=docs_dir_path, capture_output=True, encoding='utf-8')
|
|
70
68
|
if process.returncode == 0:
|
|
71
|
-
init_avatar_provider()
|
|
72
|
-
|
|
73
69
|
authors_dict = defaultdict(dict)
|
|
74
70
|
first_commit = {}
|
|
75
71
|
current_commit = None
|
|
@@ -85,14 +81,14 @@ def load_git_metadata(docs_dir_path: Path):
|
|
|
85
81
|
# 使用 defaultdict(dict)结构,处理有序与去重
|
|
86
82
|
# a.巧用 Python 字典的 setdefault 特性来去重(setdefault 为不存在的键提供初始值,不会覆盖已有值)
|
|
87
83
|
# b.巧用 Python 字典的插入顺序特性来保留内容插入顺序(Python 3.7+ 字典会保持插入顺序)
|
|
88
|
-
authors_dict[line].setdefault((name, email
|
|
84
|
+
authors_dict[line].setdefault((name, email), None)
|
|
89
85
|
first_commit.setdefault(line, created)
|
|
90
86
|
|
|
91
87
|
# 构建最终的缓存数据
|
|
92
88
|
for file_path in first_commit:
|
|
93
89
|
authors_list = [
|
|
94
|
-
{'name': name, 'email': email
|
|
95
|
-
for name, email
|
|
90
|
+
{'name': name, 'email': email}
|
|
91
|
+
for name, email in authors_dict[file_path].keys() # 这里的 keys() 是有序的
|
|
96
92
|
]
|
|
97
93
|
dates_cache[file_path] = {
|
|
98
94
|
'created': first_commit[file_path],
|
|
@@ -212,43 +208,3 @@ def write_jsonl_cache(jsonl_file: Path, dates_cache, tracked_files):
|
|
|
212
208
|
except Exception as e:
|
|
213
209
|
logger.warning(f"Failed to add JSONL cache file to git: {e}")
|
|
214
210
|
return False
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
_GRAVATAR = "https://www.gravatar.com/avatar" # Gravatar 头像,全球通用头像服务商,国内访问不稳定 ✓
|
|
218
|
-
_WEAVATAR = "https://weavatar.com/avatar" # Gravatar 国内镜像,实测发现国内外访问都正常,数据跟 Gravatar 一致 ✓
|
|
219
|
-
_CRAVATAR = "https://cravatar.cn/avatar" # 国内头像服务商,有国际 CDN,很多开源项目在用,实测发现只包含部分 Gravatar 数据
|
|
220
|
-
_BASE: str = None
|
|
221
|
-
|
|
222
|
-
def get_avatar_url(email: str, size: int = 64, default: str = "404") -> str:
|
|
223
|
-
"""
|
|
224
|
-
Generate a avatar URL from an email address.
|
|
225
|
-
|
|
226
|
-
:param email: Author email
|
|
227
|
-
:param size: Avatar size in pixels (1~2048)
|
|
228
|
-
:param default: Default avatar style when no avatar exists
|
|
229
|
-
options: '404' 'wavatar' 'retro' 'identicon' 'mp' 'monsterid' 'robohash' 'blank'
|
|
230
|
-
:return: Avatar URL
|
|
231
|
-
"""
|
|
232
|
-
|
|
233
|
-
if not _BASE or not email:
|
|
234
|
-
return ""
|
|
235
|
-
|
|
236
|
-
email_hash = hashlib.md5(email.strip().lower().encode("utf-8")).hexdigest()
|
|
237
|
-
return f"{_BASE}/{email_hash}?d={default}&s={size}"
|
|
238
|
-
|
|
239
|
-
def init_avatar_provider(timeout: float = 0.5) -> None:
|
|
240
|
-
global _BASE
|
|
241
|
-
|
|
242
|
-
if _BASE is not None:
|
|
243
|
-
return
|
|
244
|
-
|
|
245
|
-
for base in (_GRAVATAR, _WEAVATAR):
|
|
246
|
-
try:
|
|
247
|
-
r = requests.head(f"{base}/", timeout=timeout, allow_redirects=True)
|
|
248
|
-
if r.status_code < 500:
|
|
249
|
-
_BASE = base
|
|
250
|
-
return
|
|
251
|
-
except requests.RequestException:
|
|
252
|
-
continue
|
|
253
|
-
|
|
254
|
-
_BASE = ""
|
{mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2/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.5.
|
|
3
|
+
Version: 3.5.2
|
|
4
4
|
Summary: A new generation MkDocs plugin for displaying exact creation date, last updated date, authors, email of documents
|
|
5
5
|
Home-page: https://github.com/jaywhj/mkdocs-document-dates
|
|
6
6
|
Author: Aaron Wang
|
|
@@ -79,9 +79,6 @@ plugins:
|
|
|
79
79
|
- blog/* # Example: exclude all files in blog folder, including subfolders
|
|
80
80
|
date_format: '%Y-%m-%d' # Date format strings (e.g., %Y-%m-%d, %b %d, %Y)
|
|
81
81
|
time_format: '%H:%M:%S' # Time format strings (valid only if type=datetime)
|
|
82
|
-
show_created: true # Show creation date: true false, default: true
|
|
83
|
-
show_updated: true # Show last updated date: true false, default: true
|
|
84
|
-
show_author: true # Show author: true(avatar) text(text) false(hidden), default: true
|
|
85
82
|
```
|
|
86
83
|
|
|
87
84
|
## Customization Settings
|
|
@@ -91,7 +88,7 @@ In addition to the above basic configuration, the plug-in also provides a wealth
|
|
|
91
88
|
- [Specify Datetime](https://jaywhj.netlify.app/document-dates-en#Specify-Datetime): Introduces the mechanism for obtaining document dates and methods for personalized customization, you can manually specify the creation date and last updated date for each document
|
|
92
89
|
- [Specify Author](https://jaywhj.netlify.app/document-dates-en#Specify-Author): Introduces the mechanism for obtaining document authors and methods for personalized customization, you can manually specify the author information for each document, such as name, link, avatar, email, etc.
|
|
93
90
|
- [Specify Avatar](https://jaywhj.netlify.app/document-dates-en#Specify-Avatar): You can manually specify the avatar for each author, support local file path and URL path
|
|
94
|
-
- [
|
|
91
|
+
- [Configuration 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
|
|
95
92
|
- [Use Template Variables](https://jaywhj.netlify.app/document-dates-en#Use-Template-Variables): Can be used to optimize `sitemap.xml` for site SEO; Can be used to re-customize plug-ins, etc.
|
|
96
93
|
- [Add Recently Updated Module](https://jaywhj.netlify.app/document-dates-en#Add-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
|
|
97
94
|
- [Add Localization Language](https://jaywhj.netlify.app/document-dates-en#Add-Localization-Language): More localization languages for `timeago` and `tooltip`
|
|
@@ -20,6 +20,7 @@ mkdocs_document_dates/static/config/user.config.js
|
|
|
20
20
|
mkdocs_document_dates/static/core/core.css
|
|
21
21
|
mkdocs_document_dates/static/core/core.js
|
|
22
22
|
mkdocs_document_dates/static/core/default.config.js
|
|
23
|
+
mkdocs_document_dates/static/core/md5.min.js
|
|
23
24
|
mkdocs_document_dates/static/core/timeago.full.min.js
|
|
24
25
|
mkdocs_document_dates/static/core/timeago.min.js
|
|
25
26
|
mkdocs_document_dates/static/core/utils.js
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
1.生成头像
|
|
3
|
-
*/
|
|
4
|
-
function isLatin(name) {
|
|
5
|
-
return /^[A-Za-z\s]+$/.test(name.trim());
|
|
6
|
-
}
|
|
7
|
-
function extractInitials(name) {
|
|
8
|
-
name = name.trim();
|
|
9
|
-
if (!name) return '?';
|
|
10
|
-
if (isLatin(name)) {
|
|
11
|
-
const parts = name.toUpperCase().split(/\s+/).filter(Boolean);
|
|
12
|
-
return parts.length >= 2 ? parts[0][0] + parts[parts.length - 1][0] : parts[0][0];
|
|
13
|
-
} else {
|
|
14
|
-
return name[0];
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function nameToHSL(name, s = 50, l = 55) {
|
|
18
|
-
let hash = 0;
|
|
19
|
-
for (let i = 0; i < name.length; i++) {
|
|
20
|
-
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
21
|
-
}
|
|
22
|
-
const hue = hash % 360;
|
|
23
|
-
return `hsl(${hue}, ${s}%, ${l}%)`;
|
|
24
|
-
}
|
|
25
|
-
function generateAvatar() {
|
|
26
|
-
document.querySelectorAll('.avatar-wrapper').forEach(wrapper => {
|
|
27
|
-
const name = wrapper.dataset.name || '';
|
|
28
|
-
const initials = extractInitials(name);
|
|
29
|
-
const bgColor = nameToHSL(name);
|
|
30
|
-
|
|
31
|
-
const textEl = wrapper.querySelector('.avatar-text');
|
|
32
|
-
textEl.textContent = initials;
|
|
33
|
-
textEl.style.backgroundColor = bgColor;
|
|
34
|
-
|
|
35
|
-
const imgEl = wrapper.querySelector('img.avatar');
|
|
36
|
-
if (imgEl) {
|
|
37
|
-
const src = (imgEl.getAttribute('src') || '').trim();
|
|
38
|
-
if (!src) {
|
|
39
|
-
imgEl.style.display = 'none';
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
/*
|
|
48
|
-
2.处理内容赋值
|
|
49
|
-
*/
|
|
50
|
-
// 图标键映射表
|
|
51
|
-
const iconKeyMap = {
|
|
52
|
-
doc_created: 'created_time',
|
|
53
|
-
doc_updated: 'updated_time',
|
|
54
|
-
doc_author: 'author',
|
|
55
|
-
doc_authors: 'authors'
|
|
56
|
-
};
|
|
57
|
-
// 处理文档日期和提示内容
|
|
58
|
-
function processDocumentDates() {
|
|
59
|
-
document.querySelectorAll('.document-dates-plugin').forEach(ddpEl => {
|
|
60
|
-
// 获取 locale,优先级:用户主动选择 > 服务端显式配置 > 用户浏览器语言 > 站点HTML语言 > 默认英语
|
|
61
|
-
const rawLocale =
|
|
62
|
-
ddUtils.getSavedLanguage() ||
|
|
63
|
-
ddpEl.getAttribute('locale') ||
|
|
64
|
-
navigator.language ||
|
|
65
|
-
navigator.userLanguage ||
|
|
66
|
-
document.documentElement.lang ||
|
|
67
|
-
'en';
|
|
68
|
-
|
|
69
|
-
// 处理 time 元素(使用 timeago 时)
|
|
70
|
-
if (typeof timeago !== 'undefined') {
|
|
71
|
-
const tLocale = ddUtils.resolveTimeagoLocale(rawLocale);
|
|
72
|
-
ddpEl.querySelectorAll('time').forEach(timeEl => {
|
|
73
|
-
timeEl.textContent = timeago.format(timeEl.getAttribute('datetime'), tLocale);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 处理 tooltip 内容
|
|
78
|
-
const langData = TooltipLanguage.get(rawLocale);
|
|
79
|
-
ddpEl.querySelectorAll('[data-tippy-content]').forEach(tippyEl => {
|
|
80
|
-
const iconEl = tippyEl.querySelector('[data-icon]');
|
|
81
|
-
const rawIconKey = iconEl ? iconEl.getAttribute('data-icon') : '';
|
|
82
|
-
const iconKey = iconKeyMap[rawIconKey] || 'author';
|
|
83
|
-
if (langData[iconKey]) {
|
|
84
|
-
const content = langData[iconKey] + ': ' + tippyEl.dataset.tippyRaw;
|
|
85
|
-
// 更新 data-tippy-content 属性
|
|
86
|
-
tippyEl.dataset.tippyContent = content;
|
|
87
|
-
// 如果 tippy 实例已存在,直接更新内容
|
|
88
|
-
if (tippyEl._tippy) {
|
|
89
|
-
tippyEl._tippy.setContent(content);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// 供外部使用:更新文档日期和 tippy 内容到指定语言(可持久化)
|
|
97
|
-
function updateDocumentDates(locale) {
|
|
98
|
-
ddUtils.saveLanguage(locale);
|
|
99
|
-
processDocumentDates();
|
|
100
|
-
}
|
|
101
|
-
window.ddPlugin = {
|
|
102
|
-
updateLanguage: updateDocumentDates
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
/*
|
|
108
|
-
3.初始化 tippyManager,创建和管理 tippy 实例
|
|
109
|
-
*/
|
|
110
|
-
function getCurrentTheme() {
|
|
111
|
-
// 基于 Material's light/dark 配色方案返回对应的 tooltip 主题
|
|
112
|
-
const scheme = (document.body && document.body.getAttribute('data-md-color-scheme')) || 'default';
|
|
113
|
-
return scheme === 'slate' ? tooltip_config.theme.dark : tooltip_config.theme.light;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function initTippy() {
|
|
117
|
-
// 创建上下文对象,将其传递给钩子并从函数中返回
|
|
118
|
-
const context = { tooltip_config };
|
|
119
|
-
|
|
120
|
-
// 创建 tippy 实例
|
|
121
|
-
const tippyInstances = tippy('[data-tippy-content]', {
|
|
122
|
-
...tooltip_config,
|
|
123
|
-
theme: getCurrentTheme()
|
|
124
|
-
});
|
|
125
|
-
context.tippyInstances = tippyInstances;
|
|
126
|
-
|
|
127
|
-
// 添加观察者,监控 Material's 配色变化,自动切换 tooltip 主题
|
|
128
|
-
const observer = new MutationObserver((mutations) => {
|
|
129
|
-
mutations.forEach((mutation) => {
|
|
130
|
-
if (mutation.attributeName === 'data-md-color-scheme') {
|
|
131
|
-
const newTheme = getCurrentTheme();
|
|
132
|
-
tippyInstances.forEach(instance => {
|
|
133
|
-
instance.setProps({ theme: newTheme });
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
observer.observe(document.body, {
|
|
139
|
-
attributes: true,
|
|
140
|
-
attributeFilter: ['data-md-color-scheme']
|
|
141
|
-
});
|
|
142
|
-
context.observer = observer;
|
|
143
|
-
|
|
144
|
-
// 返回包含 tippyInstances 和 observer 的上下文,用于后续清理
|
|
145
|
-
return context;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 在滚动时隐藏 author-group 的 tooltip
|
|
149
|
-
function initAuthorGroupTippyGuard() {
|
|
150
|
-
document.querySelectorAll('.author-group').forEach(groupEl => {
|
|
151
|
-
if (groupEl._ddTippyGuardAbortController) {
|
|
152
|
-
groupEl._ddTippyGuardAbortController.abort();
|
|
153
|
-
}
|
|
154
|
-
const controller = new AbortController();
|
|
155
|
-
groupEl._ddTippyGuardAbortController = controller;
|
|
156
|
-
|
|
157
|
-
const getInstances = () => {
|
|
158
|
-
return Array.from(groupEl.querySelectorAll('[data-tippy-content]'))
|
|
159
|
-
.map(el => el._tippy)
|
|
160
|
-
.filter(Boolean);
|
|
161
|
-
};
|
|
162
|
-
const hideNow = () => {
|
|
163
|
-
const instances = getInstances();
|
|
164
|
-
instances.forEach(instance => {
|
|
165
|
-
instance.hide();
|
|
166
|
-
});
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const opts = { passive: true, signal: controller.signal };
|
|
170
|
-
groupEl.addEventListener('scroll', hideNow, opts);
|
|
171
|
-
groupEl.addEventListener('touchmove', hideNow, opts);
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// 通过 IIFE(立即执行的函数表达式)创建 tippyManager
|
|
176
|
-
const tippyManager = (() => {
|
|
177
|
-
let tippyInstances = [];
|
|
178
|
-
let observer = null;
|
|
179
|
-
function cleanup() {
|
|
180
|
-
// 销毁之前的 tippy 实例
|
|
181
|
-
if (tippyInstances.length > 0) {
|
|
182
|
-
tippyInstances.forEach(instance => instance.destroy());
|
|
183
|
-
tippyInstances = [];
|
|
184
|
-
}
|
|
185
|
-
// 断开之前的观察者连接
|
|
186
|
-
if (observer) {
|
|
187
|
-
observer.disconnect();
|
|
188
|
-
observer = null;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
// 每一次调用都生成新的实例(兼容 navigation.instant)
|
|
193
|
-
initialize() {
|
|
194
|
-
// 先清理以前的实例
|
|
195
|
-
cleanup();
|
|
196
|
-
// 初始化新实例
|
|
197
|
-
const context = initTippy();
|
|
198
|
-
if (context && context.tippyInstances) {
|
|
199
|
-
tippyInstances = context.tippyInstances;
|
|
200
|
-
}
|
|
201
|
-
if (context && context.observer) {
|
|
202
|
-
observer = context.observer;
|
|
203
|
-
}
|
|
204
|
-
initAuthorGroupTippyGuard();
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
})();
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
/*
|
|
212
|
-
入口: 兼容 Material 主题的 'navigation.instant' 属性
|
|
213
|
-
*/
|
|
214
|
-
if (typeof window.document$ !== 'undefined' && !window.document$.isStopped) {
|
|
215
|
-
window.document$.subscribe(() => {
|
|
216
|
-
processDocumentDates();
|
|
217
|
-
generateAvatar();
|
|
218
|
-
// 通过 tippyManager 创建 tippy 实例
|
|
219
|
-
tippyManager.initialize();
|
|
220
|
-
});
|
|
221
|
-
} else {
|
|
222
|
-
processDocumentDates();
|
|
223
|
-
generateAvatar();
|
|
224
|
-
document.addEventListener('DOMContentLoaded', tippyManager.initialize);
|
|
225
|
-
}
|
|
File without changes
|
|
File without changes
|
{mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/__init__.py
RENAMED
|
File without changes
|
{mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/cache_manager.py
RENAMED
|
File without changes
|
{mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/hooks/pre-commit
RENAMED
|
File without changes
|
{mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/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
|