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.
Files changed (40) hide show
  1. {mkdocs_document_dates-3.5.1/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.5.2}/PKG-INFO +2 -5
  2. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/README.md +1 -4
  3. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/plugin.py +15 -9
  4. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/core.css +22 -23
  5. mkdocs_document_dates-3.5.2/mkdocs_document_dates/static/core/core.js +393 -0
  6. mkdocs_document_dates-3.5.2/mkdocs_document_dates/static/core/md5.min.js +2 -0
  7. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/utils.py +3 -47
  8. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2/mkdocs_document_dates.egg-info}/PKG-INFO +2 -5
  9. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/SOURCES.txt +1 -0
  10. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/setup.py +1 -1
  11. mkdocs_document_dates-3.5.1/mkdocs_document_dates/static/core/core.js +0 -225
  12. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/LICENSE +0 -0
  13. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/MANIFEST.in +0 -0
  14. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/__init__.py +0 -0
  15. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/cache_manager.py +0 -0
  16. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/hooks/pre-commit +0 -0
  17. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/hooks_installer.py +0 -0
  18. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/config/user.config.css +0 -0
  19. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/config/user.config.js +0 -0
  20. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/default.config.js +0 -0
  21. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/timeago.full.min.js +0 -0
  22. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/timeago.min.js +0 -0
  23. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/core/utils.js +0 -0
  24. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/fonts/material-icons.css +0 -0
  25. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/fonts/materialicons.woff2 +0 -0
  26. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/templates/recently_updated.html +0 -0
  27. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/backdrop.css +0 -0
  28. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/light.css +0 -0
  29. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/material.css +0 -0
  30. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/popper.min.js +0 -0
  31. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/scale.css +0 -0
  32. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/shift-away.css +0 -0
  33. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/tippy.css +0 -0
  34. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates/static/tippy/tippy.umd.min.js +0 -0
  35. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/dependency_links.txt +0 -0
  36. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/entry_points.txt +0 -0
  37. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/requires.txt +0 -0
  38. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/mkdocs_document_dates.egg-info/top_level.txt +0 -0
  39. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/pyproject.toml +0 -0
  40. {mkdocs_document_dates-3.5.1 → mkdocs_document_dates-3.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-document-dates
3
- Version: 3.5.1
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
- - [Set Plugin Style](https://jaywhj.netlify.app/document-dates-en#Set-Plugin-Style): You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
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
- - [Set Plugin Style](https://jaywhj.netlify.app/document-dates-en#Set-Plugin-Style): You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
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
- show_dates = self.config['show_created'] or self.config['show_updated']
368
- show_plugin = show_dates or self.config['show_author']
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 self.config['show_created']:
395
+ if show_created:
391
396
  html_parts.append(build_time_icon(created, 'doc_created'))
392
- if self.config['show_updated']:
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 self.config['show_author'] and authors:
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
- if self.config['show_author'] == 'text':
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}' onerror=\"this.style.display='none'\" />"
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.2rem 0;
30
- margin-bottom: 0.6rem;
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
- margin-right: 1.5rem;
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.5rem;
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: 110px;
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, get_avatar_url(email)), None)
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, 'avatar': avatar}
95
- for name, email, avatar in authors_dict[file_path].keys() # 这里的 keys() 是有序的
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 = ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-document-dates
3
- Version: 3.5.1
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
- - [Set Plugin Style](https://jaywhj.netlify.app/document-dates-en#Set-Plugin-Style): You can quickly set the plugin styles through preset entrances, such as icons, themes, colors, fonts, animations, dividing line and so on
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
@@ -23,7 +23,7 @@ class CustomInstallCommand(install):
23
23
  install.run(self)
24
24
 
25
25
 
26
- VERSION = '3.5.1'
26
+ VERSION = '3.5.2'
27
27
 
28
28
  setup(
29
29
  name="mkdocs-document-dates",
@@ -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
- }