mkdocs-document-dates 3.5.2__tar.gz → 3.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {mkdocs_document_dates-3.5.2/mkdocs_document_dates.egg-info → mkdocs_document_dates-3.6.0}/PKG-INFO +7 -1
  2. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/README.md +6 -0
  3. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/plugin.py +5 -18
  4. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/core/core.css +0 -1
  5. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/core/core.js +92 -15
  6. mkdocs_document_dates-3.6.0/mkdocs_document_dates/static/templates/recently_updated_detail.html +99 -0
  7. mkdocs_document_dates-3.6.0/mkdocs_document_dates/static/templates/recently_updated_grid.html +106 -0
  8. mkdocs_document_dates-3.6.0/mkdocs_document_dates/static/templates/recently_updated_group.html +186 -0
  9. mkdocs_document_dates-3.5.2/mkdocs_document_dates/static/templates/recently_updated.html → mkdocs_document_dates-3.6.0/mkdocs_document_dates/static/templates/recently_updated_list.html +23 -15
  10. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/utils.py +205 -3
  11. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0/mkdocs_document_dates.egg-info}/PKG-INFO +7 -1
  12. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates.egg-info/SOURCES.txt +4 -1
  13. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/setup.py +1 -1
  14. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/LICENSE +0 -0
  15. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/MANIFEST.in +0 -0
  16. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/__init__.py +0 -0
  17. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/cache_manager.py +0 -0
  18. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/hooks/pre-commit +0 -0
  19. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/hooks_installer.py +0 -0
  20. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/config/user.config.css +0 -0
  21. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/config/user.config.js +0 -0
  22. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/core/default.config.js +0 -0
  23. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/core/md5.min.js +0 -0
  24. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/core/timeago.full.min.js +0 -0
  25. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/core/timeago.min.js +0 -0
  26. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/core/utils.js +0 -0
  27. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/fonts/material-icons.css +0 -0
  28. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/fonts/materialicons.woff2 +0 -0
  29. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/tippy/backdrop.css +0 -0
  30. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/tippy/light.css +0 -0
  31. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/tippy/material.css +0 -0
  32. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/tippy/popper.min.js +0 -0
  33. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/tippy/scale.css +0 -0
  34. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/tippy/shift-away.css +0 -0
  35. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/tippy/tippy.css +0 -0
  36. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates/static/tippy/tippy.umd.min.js +0 -0
  37. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates.egg-info/dependency_links.txt +0 -0
  38. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates.egg-info/entry_points.txt +0 -0
  39. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates.egg-info/requires.txt +0 -0
  40. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/mkdocs_document_dates.egg-info/top_level.txt +0 -0
  41. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/pyproject.toml +0 -0
  42. {mkdocs_document_dates-3.5.2 → mkdocs_document_dates-3.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-document-dates
3
- Version: 3.5.2
3
+ Version: 3.6.0
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
@@ -97,6 +97,12 @@ In addition to the above basic configuration, the plug-in also provides a wealth
97
97
 
98
98
  See the documentation for details: https://jaywhj.netlify.app/document-dates-en
99
99
 
100
+ <br />
101
+
102
+ ## Other Projects
103
+
104
+ - [**MaterialX**](https://github.com/jaywhj/mkdocs-materialx), the next generation of mkdocs-material, is based on `mkdocs-material-9.7.0` and is named `X`. I'll be maintaining this branch continuously (since mkdocs-material will stop being maintained).
105
+ Updates have been released that refactor and add a lot of new features, see https://github.com/jaywhj/mkdocs-materialx/releases/
100
106
 
101
107
  <br />
102
108
 
@@ -71,6 +71,12 @@ In addition to the above basic configuration, the plug-in also provides a wealth
71
71
 
72
72
  See the documentation for details: https://jaywhj.netlify.app/document-dates-en
73
73
 
74
+ <br />
75
+
76
+ ## Other Projects
77
+
78
+ - [**MaterialX**](https://github.com/jaywhj/mkdocs-materialx), the next generation of mkdocs-material, is based on `mkdocs-material-9.7.0` and is named `X`. I'll be maintaining this branch continuously (since mkdocs-material will stop being maintained).
79
+ Updates have been released that refactor and add a lot of new features, see https://github.com/jaywhj/mkdocs-materialx/releases/
74
80
 
75
81
  <br />
76
82
 
@@ -46,7 +46,6 @@ class DocumentDatesPlugin(BasePlugin):
46
46
  self.dates_cache = {}
47
47
  self.last_updated_dates = {}
48
48
  self.authors_yml = {}
49
- self.github_username = None
50
49
  self.recent_docs_html = None
51
50
  self.recent_enable = False
52
51
 
@@ -186,7 +185,6 @@ class DocumentDatesPlugin(BasePlugin):
186
185
  # 获取配置
187
186
  exclude_list = recently_updated_config.get('exclude', [])
188
187
  limit = recently_updated_config.get('limit', 10)
189
- template_path = recently_updated_config.get('template')
190
188
 
191
189
  # 获取最近更新的文档数据
192
190
  recently_updated_docs = get_recently_updated_files(self.last_updated_dates, files, exclude_list, limit, self.recent_enable)
@@ -198,8 +196,7 @@ class DocumentDatesPlugin(BasePlugin):
198
196
 
199
197
  # 渲染HTML
200
198
  if self.recent_enable:
201
- docs_dir = Path(config['docs_dir'])
202
- self.recent_docs_html = self._render_recently_updated_html(docs_dir, template_path, recently_updated_docs)
199
+ self.recent_docs_html = self._render_recently_updated_html(recently_updated_docs)
203
200
 
204
201
  return env
205
202
 
@@ -233,20 +230,10 @@ class DocumentDatesPlugin(BasePlugin):
233
230
  logger.info(f"Error parsing .authors.yml: {e}")
234
231
 
235
232
 
236
- def _render_recently_updated_html(self, docs_dir, template_path, recently_updated_data):
237
- # 获取自定义模板路径
238
- if template_path:
239
- user_full_path = docs_dir / template_path
240
-
241
- # 选择模板路径
242
- if template_path and user_full_path.is_file():
243
- template_dir = user_full_path.parent
244
- template_file = user_full_path.name
245
- else:
246
- # 默认模板路径
247
- default_template_path = Path(__file__).parent / 'static' / 'templates' / 'recently_updated.html'
248
- template_dir = default_template_path.parent
249
- template_file = default_template_path.name
233
+ def _render_recently_updated_html(self, recently_updated_data):
234
+ default_template_path = Path(__file__).parent / 'static' / 'templates' / 'recently_updated_group.html'
235
+ template_dir = default_template_path.parent
236
+ template_file = default_template_path.name
250
237
 
251
238
  # 加载模板
252
239
  env = Environment(
@@ -68,7 +68,6 @@
68
68
  display: flex;
69
69
  align-items: center;
70
70
  flex-shrink: 0;
71
- margin-right: 1.4rem;
72
71
  }
73
72
  .dd-item {
74
73
  display: inline-flex;
@@ -144,25 +144,37 @@ const iconKeyMap = {
144
144
  doc_author: 'author',
145
145
  doc_authors: 'authors'
146
146
  };
147
+
148
+ function applyTimeagoToTimes(timeNodes, rawLocale) {
149
+ if (typeof timeago === 'undefined') {
150
+ return;
151
+ }
152
+ if (!timeNodes || !timeNodes.length) {
153
+ return;
154
+ }
155
+ const tLocale = ddUtils.resolveTimeagoLocale(rawLocale);
156
+ timeNodes.forEach(timeEl => {
157
+ const dt = timeEl.getAttribute('datetime');
158
+ if (dt) {
159
+ timeEl.textContent = timeago.format(dt, tLocale);
160
+ }
161
+ });
162
+ }
163
+
147
164
  // 处理数据加载
148
165
  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';
166
+ // 获取 locale,优先级:用户主动选择 > 服务端显式配置 > 用户浏览器语言 > 站点HTML语言 > 默认英语
167
+ const rawLocale =
168
+ ddUtils.getSavedLanguage() ||
169
+ // ddpEl.getAttribute('locale') ||
170
+ navigator.language ||
171
+ navigator.userLanguage ||
172
+ document.documentElement.lang ||
173
+ 'en';
158
174
 
175
+ document.querySelectorAll('.document-dates-plugin').forEach(ddpEl => {
159
176
  // 处理 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
- }
177
+ applyTimeagoToTimes(ddpEl.querySelectorAll('time'), rawLocale);
166
178
 
167
179
  // 动态处理 tooltip 内容
168
180
  const langData = TooltipLanguage.get(rawLocale);
@@ -178,6 +190,9 @@ function processDataLoading() {
178
190
  }
179
191
  });
180
192
  });
193
+
194
+ // 处理其他 timeago 时间
195
+ applyTimeagoToTimes(document.querySelectorAll('time.dd-timeago'), rawLocale);
181
196
  }
182
197
 
183
198
  // 供外部使用:更新文档日期和 tippy 内容到指定语言(可持久化)
@@ -359,6 +374,66 @@ function handleDocumentDatesAutoWrap() {
359
374
  });
360
375
  }
361
376
 
377
+ // 最近更新 - 布局切换器 (Layout Switcher)
378
+ function initLayoutSwitcher() {
379
+ const grids = document.querySelectorAll('.article-grid');
380
+ if (!grids.length) return;
381
+
382
+ const savedLayout = localStorage.getItem('dd_recent_docs_layout') || 'grid';
383
+
384
+ grids.forEach(grid => {
385
+ // 应用初始布局
386
+ grid.classList.toggle('is-list', savedLayout === 'list');
387
+ grid.classList.toggle('is-detail', savedLayout === 'detail');
388
+
389
+ // 查找或创建切换器容器
390
+ let switcher = grid.previousElementSibling;
391
+ if (!switcher || !switcher.classList.contains('article-layout-switcher')) {
392
+ // 如果模板中没写,可以动态注入,但建议写在模板里以保证 UI 一致性
393
+ return;
394
+ }
395
+
396
+ const listBtn = switcher.querySelector('.layout-list-btn');
397
+ const detailBtn = switcher.querySelector('.layout-detail-btn');
398
+ const gridBtn = switcher.querySelector('.layout-grid-btn');
399
+
400
+ const updateActiveBtn = (layout) => {
401
+ if (listBtn) listBtn.classList.toggle('is-active', layout === 'list');
402
+ if (detailBtn) detailBtn.classList.toggle('is-active', layout === 'detail');
403
+ if (gridBtn) gridBtn.classList.toggle('is-active', layout === 'grid');
404
+ };
405
+
406
+ updateActiveBtn(savedLayout);
407
+
408
+ const setLayout = (layout) => {
409
+ grid.classList.remove('is-list', 'is-detail');
410
+ if (layout !== 'grid') {
411
+ grid.classList.add(`is-${layout}`);
412
+ }
413
+ localStorage.setItem('dd_recent_docs_layout', layout);
414
+ updateActiveBtn(layout);
415
+ };
416
+
417
+ if (listBtn) {
418
+ listBtn.onclick = () => {
419
+ setLayout('list');
420
+ listBtn.blur();
421
+ };
422
+ }
423
+ if (detailBtn) {
424
+ detailBtn.onclick = () => {
425
+ setLayout('detail');
426
+ detailBtn.blur();
427
+ };
428
+ }
429
+ if (gridBtn) {
430
+ gridBtn.onclick = () => {
431
+ setLayout('grid');
432
+ gridBtn.blur();
433
+ };
434
+ }
435
+ });
436
+ }
362
437
 
363
438
  /*
364
439
  入口
@@ -367,6 +442,7 @@ let datesAutoWrapObserver = null;
367
442
  function initPluginFeatures() {
368
443
  tippyManager.initialize();
369
444
  processDataLoading();
445
+ initLayoutSwitcher();
370
446
  AvatarService.init().then(() => {
371
447
  loadAvatars();
372
448
  });
@@ -381,6 +457,7 @@ function initPluginFeatures() {
381
457
  });
382
458
  });
383
459
  document.querySelectorAll('.document-dates-plugin').forEach(ddpEl => datesAutoWrapObserver.observe(ddpEl));
460
+ setTimeout(handleDocumentDatesAutoWrap, 100);
384
461
  }
385
462
 
386
463
  // 兼容 Material 主题的 'navigation.instant' 属性
@@ -0,0 +1,99 @@
1
+ <style>
2
+ .article-card {
3
+ border: 1px solid rgba(142, 142, 142, 0.2);
4
+ border-radius: 8px;
5
+ padding: 16px;
6
+ margin: 20px auto;
7
+ font-family: sans-serif;
8
+ box-sizing: border-box;
9
+ display: flex;
10
+ flex-direction: column;
11
+ }
12
+
13
+ /* --- 第一行:标题与时间 --- */
14
+ .card-header {
15
+ display: flex;
16
+ justify-content: space-between;
17
+ align-items: center;
18
+ text-decoration: none;
19
+ }
20
+ .card-title {
21
+ font-weight: 700;
22
+ line-height: 1.4;
23
+ color: var(--md-typeset-color, #1c1c1c);
24
+ margin-right: 16px;
25
+ }
26
+ .card-date {
27
+ font-size: 0.84em;
28
+ color: rgba(100, 100, 100, 0.4);
29
+ white-space: nowrap;
30
+ margin-right: 10px;
31
+ }
32
+ .card-header:hover .card-title {
33
+ color: var(--md-accent-fg-color, blue);
34
+ text-decoration: underline;
35
+ }
36
+
37
+ /* --- 第二行:摘要与封面图 --- */
38
+ .card-body {
39
+ display: flex;
40
+ justify-content: space-between;
41
+ align-items: center;
42
+ margin-top: 12px;
43
+ }
44
+ .card-summary {
45
+ flex: 1;
46
+ color: rgba(142, 142, 142, 1);
47
+ font-size: 0.7rem;
48
+ line-height: 1.6;
49
+ letter-spacing: 0.02rem;
50
+ word-break: break-all;
51
+
52
+ /* 3 行文字截断,超出显示省略号 */
53
+ display: -webkit-box;
54
+ -webkit-box-orient: vertical;
55
+ -webkit-line-clamp: 3;
56
+ overflow: hidden;
57
+ }
58
+ .card-cover {
59
+ width: 120px;
60
+ height: 80px;
61
+ border-radius: 8px;
62
+ flex-shrink: 0;
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ overflow: hidden;
67
+ margin-left: 10px;
68
+ }
69
+ .card-cover a {
70
+ display: block;
71
+ width: 100%;
72
+ height: 100%;
73
+ }
74
+ .card-cover img {
75
+ width: 100%;
76
+ height: 100%;
77
+ object-fit: cover;
78
+ margin: 0 !important;
79
+ }
80
+ </style>
81
+
82
+ <div>
83
+ {%- for mtime, rel_path, title, url, cover, summary in recent_docs %}
84
+ <div class="article-card">
85
+ <a class="card-header" href="{{ url }}" target="_blank">
86
+ <div class="card-title">{{ title }}</div>
87
+ <time class="dd-timeago card-date" datetime="{{ mtime }}">{{ mtime[:10] }}</time>
88
+ </a>
89
+ <div class="card-body">
90
+ <div class="card-summary">{{ summary }}</div>
91
+ {%- if cover %}
92
+ <div class="card-cover">
93
+ <img src="{{ cover }}" alt="{{ title }}">
94
+ </div>
95
+ {%- endif %}
96
+ </div>
97
+ </div>
98
+ {%- endfor %}
99
+ </div>
@@ -0,0 +1,106 @@
1
+ <style>
2
+ /* --- 网格布局容器 --- */
3
+ .article-grid {
4
+ display: grid;
5
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
6
+ gap: 20px;
7
+ margin: 20px 0;
8
+ }
9
+
10
+ .article-card {
11
+ border: 1px solid rgba(142, 142, 142, 0.2);
12
+ border-radius: 8px;
13
+ padding: 16px;
14
+ font-family: sans-serif;
15
+ box-sizing: border-box;
16
+ display: flex;
17
+ flex-direction: column;
18
+ }
19
+
20
+ /* --- 第一行:标题与时间 --- */
21
+ .card-header {
22
+ display: flex;
23
+ justify-content: space-between;
24
+ align-items: center;
25
+ text-decoration: none;
26
+ }
27
+ .card-title {
28
+ font-weight: 700;
29
+ line-height: 1.4;
30
+ color: var(--md-typeset-color, #1c1c1c);
31
+ margin-right: 16px;
32
+ }
33
+ .card-date {
34
+ font-size: 0.84em;
35
+ color: rgba(100, 100, 100, 0.4);
36
+ white-space: nowrap;
37
+ margin-right: 10px;
38
+ }
39
+ .card-header:hover .card-title {
40
+ color: var(--md-accent-fg-color, blue);
41
+ text-decoration: underline;
42
+ }
43
+
44
+ /* --- 第二行:摘要与封面图 --- */
45
+ .card-body {
46
+ display: flex;
47
+ justify-content: space-between;
48
+ align-items: center;
49
+ margin-top: 12px;
50
+ }
51
+ .card-summary {
52
+ flex: 1;
53
+ color: rgba(142, 142, 142, 1);
54
+ font-size: 0.7rem;
55
+ line-height: 1.6;
56
+ letter-spacing: 0.02rem;
57
+ word-break: break-all;
58
+
59
+ /* 3 行文字截断,超出显示省略号 */
60
+ display: -webkit-box;
61
+ -webkit-box-orient: vertical;
62
+ -webkit-line-clamp: 3;
63
+ overflow: hidden;
64
+ }
65
+ .card-cover {
66
+ width: 120px;
67
+ height: 80px;
68
+ border-radius: 8px;
69
+ flex-shrink: 0;
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ overflow: hidden;
74
+ margin-left: 10px;
75
+ }
76
+ .card-cover a {
77
+ display: block;
78
+ width: 100%;
79
+ height: 100%;
80
+ }
81
+ .card-cover img {
82
+ width: 100%;
83
+ height: 100%;
84
+ object-fit: cover;
85
+ margin: 0 !important;
86
+ }
87
+ </style>
88
+
89
+ <div class="article-grid">
90
+ {%- for mtime, rel_path, title, url, cover, summary in recent_docs %}
91
+ <div class="article-card">
92
+ <a class="card-header" href="{{ url }}" target="_blank">
93
+ <div class="card-title">{{ title }}</div>
94
+ <time class="dd-timeago card-date" datetime="{{ mtime }}">{{ mtime[:10] }}</time>
95
+ </a>
96
+ <div class="card-body">
97
+ <div class="card-summary">{{ summary }}</div>
98
+ {%- if cover %}
99
+ <div class="card-cover">
100
+ <img src="{{ cover }}" alt="{{ title }}">
101
+ </div>
102
+ {%- endif %}
103
+ </div>
104
+ </div>
105
+ {%- endfor %}
106
+ </div>
@@ -0,0 +1,186 @@
1
+ <style>
2
+ .article-layout-switcher {
3
+ display: flex;
4
+ justify-content: flex-end;
5
+ margin-bottom: 12px;
6
+ }
7
+ .article-layout-switcher button + button {
8
+ margin-left: 8px;
9
+ }
10
+ .article-layout-switcher button {
11
+ background: transparent;
12
+ border: none;
13
+ cursor: pointer;
14
+ padding: 4px;
15
+ border-radius: 4px;
16
+ display: flex;
17
+ align-items: center;
18
+ color: var(--md-typeset-color, #1c1c1c);
19
+ opacity: 0.4;
20
+ transition: all 0.2s;
21
+ }
22
+ .article-layout-switcher button:hover {
23
+ background: rgba(142, 142, 142, 0.1);
24
+ opacity: 0.8;
25
+ }
26
+ .article-layout-switcher button.is-active {
27
+ opacity: 1;
28
+ color: var(--md-accent-fg-color, blue);
29
+ background: rgba(142, 142, 142, 0.1);
30
+ }
31
+ .article-layout-switcher .material-icons {
32
+ font-size: 20px;
33
+ }
34
+
35
+ /* --- 网格模式 (默认) --- */
36
+ .article-grid {
37
+ display: grid;
38
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
39
+ gap: 20px;
40
+ margin: 20px 0;
41
+ /* transition: all 0.2s ease; */
42
+ }
43
+
44
+ /* --- 详情模式 (is-detail) --- */
45
+ .article-grid.is-detail {
46
+ grid-template-columns: 1fr;
47
+ margin-left: auto;
48
+ margin-right: auto;
49
+ }
50
+
51
+ /* --- 列表模式 (is-list) --- */
52
+ .article-grid.is-list {
53
+ margin-left: auto;
54
+ margin-right: auto;
55
+
56
+ border: 1px solid rgba(142, 142, 142, 0.2);
57
+ border-radius: 8px;
58
+ row-gap: 0;
59
+ padding: 11px;
60
+ }
61
+ .article-grid.is-list .card-body {
62
+ display: none;
63
+ }
64
+ .article-grid.is-list .article-card {
65
+ border: none;
66
+ border-radius: 0;
67
+ padding: 8px;
68
+ }
69
+ .article-grid.is-list .card-title {
70
+ font-weight: 400;
71
+ color: #0077cc;
72
+ /* color: var(--md-typeset-color, #1c1c1c); */
73
+
74
+ white-space: nowrap;
75
+ overflow: hidden;
76
+ text-overflow: ellipsis;
77
+ min-width: 0;
78
+ }
79
+ .article-grid.is-list .card-date {
80
+ margin-right: 0;
81
+ }
82
+
83
+
84
+ .article-card {
85
+ border: 1px solid rgba(142, 142, 142, 0.2);
86
+ border-radius: 8px;
87
+ padding: 16px;
88
+ font-family: sans-serif;
89
+ box-sizing: border-box;
90
+ display: flex;
91
+ flex-direction: column;
92
+ }
93
+
94
+ /* --- 第一行:标题与时间 --- */
95
+ .card-header {
96
+ display: flex;
97
+ justify-content: space-between;
98
+ align-items: center;
99
+ text-decoration: none;
100
+ }
101
+ .card-title {
102
+ font-weight: 700;
103
+ line-height: 1.4;
104
+ color: var(--md-typeset-color, #1c1c1c);
105
+ margin-right: 12px;
106
+ }
107
+ .card-date {
108
+ font-size: 0.84em;
109
+ color: rgba(100, 100, 100, 0.4);
110
+ white-space: nowrap;
111
+ margin-right: 10px;
112
+ }
113
+ .card-header:hover .card-title {
114
+ color: var(--md-accent-fg-color, blue);
115
+ text-decoration: underline;
116
+ }
117
+
118
+ /* --- 第二行:摘要与封面图 --- */
119
+ .card-body {
120
+ display: flex;
121
+ justify-content: space-between;
122
+ align-items: center;
123
+ margin-top: 12px;
124
+ }
125
+ .card-summary {
126
+ flex: 1;
127
+ color: rgba(142, 142, 142, 1);
128
+ font-size: 0.7rem;
129
+ line-height: 1.6;
130
+ letter-spacing: 0.02rem;
131
+ word-break: break-all;
132
+
133
+ /* 3 行文字截断,超出显示省略号 */
134
+ display: -webkit-box;
135
+ -webkit-box-orient: vertical;
136
+ -webkit-line-clamp: 3;
137
+ overflow: hidden;
138
+ }
139
+ .card-cover {
140
+ width: 120px;
141
+ height: 80px;
142
+ border-radius: 8px;
143
+ flex-shrink: 0;
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ overflow: hidden;
148
+ margin-left: 10px;
149
+ }
150
+ .card-cover a {
151
+ display: block;
152
+ width: 100%;
153
+ height: 100%;
154
+ }
155
+ .card-cover img {
156
+ width: 100%;
157
+ height: 100%;
158
+ object-fit: cover;
159
+ margin: 0 !important;
160
+ }
161
+ </style>
162
+
163
+ <div class="article-layout-switcher">
164
+ <button class="layout-list-btn" title="List View"><span class="material-icons">view_list</span></button>
165
+ <button class="layout-detail-btn" title="Detail View"><span class="material-icons">view_day</span></button>
166
+ <button class="layout-grid-btn" title="Grid View"><span class="material-icons">view_module</span></button>
167
+ </div>
168
+
169
+ <div class="article-grid">
170
+ {%- for mtime, rel_path, title, url, cover, summary in recent_docs %}
171
+ <div class="article-card">
172
+ <a class="card-header" href="{{ url }}" target="_blank">
173
+ <div class="card-title">{{ title }}</div>
174
+ <time class="dd-timeago card-date" datetime="{{ mtime }}">{{ mtime[:10] }}</time>
175
+ </a>
176
+ <div class="card-body">
177
+ <div class="card-summary">{{ summary }}</div>
178
+ {%- if cover %}
179
+ <div class="card-cover">
180
+ <img src="{{ cover }}" alt="{{ title }}">
181
+ </div>
182
+ {%- endif %}
183
+ </div>
184
+ </div>
185
+ {%- endfor %}
186
+ </div>
@@ -1,24 +1,24 @@
1
1
  <style>
2
2
  .recently-updated {
3
3
  display: grid;
4
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
5
- gap: 10px;
6
- margin: 0;
7
- padding: 16px;
8
- border: 1px solid rgba(142, 142, 142, 0.15);
4
+ grid-template-columns: repeat(2, minmax(0, 1fr));
5
+ padding: 11px;
6
+ border: 1px solid rgba(142, 142, 142, 0.2);
9
7
  border-radius: 8px;
10
8
  font-family: system-ui, sans-serif;
11
9
  }
10
+
11
+ /* 屏幕宽度 675px 及以下时切换为 1 列 */
12
+ @media screen and (max-width: 675px) {
13
+ .recently-updated {
14
+ grid-template-columns: 1fr;
15
+ }
16
+ }
12
17
  .recently-updated-item {
13
18
  display: flex;
14
19
  align-items: center;
15
- }
16
- .recently-updated-item span {
17
- font-size: 0.84em;
18
- color: rgba(142, 142, 142, 0.5);
19
- margin-right: 4px;
20
- flex-shrink: 0;
21
- width: 90px;
20
+ justify-content: space-between;
21
+ margin: 5px;
22
22
  }
23
23
  .recently-updated-item a {
24
24
  overflow: hidden;
@@ -26,17 +26,25 @@
26
26
  white-space: nowrap;
27
27
  color: #0077cc;
28
28
  text-decoration: none;
29
- transition: color 0.2s ease;
29
+ margin-left: 6px;
30
30
  }
31
31
  .recently-updated-item a:hover {
32
32
  text-decoration: underline;
33
33
  }
34
+ .recently-updated-item time {
35
+ font-size: 0.84em;
36
+ color: rgba(142, 142, 142, 0.4);
37
+ margin: 0 6px;
38
+ flex-shrink: 0;
39
+ text-align: right;
40
+ }
34
41
  </style>
42
+
35
43
  <div class="recently-updated">
36
- {%- for mtime, rel_path, title, url in recent_docs %}
44
+ {%- for mtime, rel_path, title, url, cover, summary in recent_docs %}
37
45
  <div class="recently-updated-item">
38
- <span>{{ mtime[:10] }}</span>
39
46
  <a href="{{ url }}" target="_blank">{{ title }}</a>
47
+ <time class="dd-timeago" datetime="{{ mtime }}">{{ mtime[:10] }}</time>
40
48
  </div>
41
49
  {%- endfor %}
42
50
  </div>
@@ -4,6 +4,7 @@ import json
4
4
  import heapq
5
5
  import logging
6
6
  import subprocess
7
+ import re
7
8
  from pathlib import Path
8
9
  from datetime import datetime
9
10
  from collections import defaultdict
@@ -132,7 +133,7 @@ def load_git_last_updated_date(docs_dir_path: Path):
132
133
 
133
134
  return doc_mtime_map
134
135
 
135
- def get_recently_updated_files(existing_map: dict, files: Files, exclude_list: list, limit: int = 10, recent_enable: bool = False):
136
+ def get_recently_updated_files(existing_dates: dict, files: Files, exclude_list: list, limit: int = 10, recent_enable: bool = False):
136
137
  recently_updated_results = []
137
138
  if recent_enable:
138
139
  files_meta = []
@@ -148,14 +149,23 @@ def get_recently_updated_files(existing_map: dict, files: Files, exclude_list: l
148
149
  continue
149
150
 
150
151
  # 优先从现有数据获取 mtime,如果不存在则 fallback 到文件系统 mtime
151
- mtime = existing_map.get(rel_path, os.path.getmtime(file.abs_src_path))
152
+ mtime = existing_dates.get(rel_path, os.path.getmtime(file.abs_src_path))
152
153
 
153
154
  # 获取文档标题和 URL
154
155
  title = file.page.title if file.page and file.page.title else file.name
155
156
  url = file.page.url if file.page and file.page.url else file.url
156
157
 
158
+ cover = ''
159
+ summary = ''
160
+ # authors = []
161
+ if file.page:
162
+ cover = file.page.meta.get('cover', '')
163
+ # authors = file.page.meta.get('document_dates_authors', [])
164
+ if file.page.file:
165
+ summary = extract_summary(file.page.file.content_string)
166
+
157
167
  # 存储信息
158
- files_meta.append((mtime, rel_path, title, url))
168
+ files_meta.append((mtime, rel_path, title, url, cover, summary))
159
169
  # existing_map[rel_path] = mtime
160
170
 
161
171
  # 构建最近更新列表
@@ -208,3 +218,195 @@ def write_jsonl_cache(jsonl_file: Path, dates_cache, tracked_files):
208
218
  except Exception as e:
209
219
  logger.warning(f"Failed to add JSONL cache file to git: {e}")
210
220
  return False
221
+
222
+
223
+ # ===== Extract Summary =====
224
+ #
225
+ # -------- block skip --------
226
+ # Fence
227
+ FENCE_RE = re.compile(r"^\s*([`~]{3,})")
228
+
229
+ # HTML comment
230
+ HTML_COMMENT_START = re.compile(r'<!--', re.I)
231
+ HTML_COMMENT_END = re.compile(r'-->', re.I)
232
+
233
+ # HTML
234
+ HTML_TAG_OPEN = re.compile(r'<\s*([a-zA-Z][\w\-]*)\b', re.I)
235
+ HTML_TAG_CLOSE_TEMPLATE = r"</\s*{}\s*>"
236
+ HTML_VOID_TAGS = {
237
+ "area", "base", "br", "col", "embed", "hr",
238
+ "img", "input", "link", "meta", "param",
239
+ "source", "track", "wbr"
240
+ }
241
+ HTML_VOID_CLOSE_RE = re.compile(r">", re.I)
242
+
243
+ # -------- inline skip --------
244
+ H1_TITLE = re.compile(r'^\s*# .+$', re.MULTILINE)
245
+ SINGLE_LINE_HTML_NOISE = re.compile(r'^</?[a-z][\w-]*[^>]*>$', re.I)
246
+ TABLE_ROW_RE = re.compile(r"^\s*\|.*\|\s*$")
247
+ INLINE_SKIP_RE = re.compile(
248
+ r"""
249
+ ^\s*> | # quote
250
+ ^\s*(?:!!!|\?\?\?) | # admonition
251
+ ^\s*=== | # tab
252
+ ^\s*\[.+?\]: # reference link, including footnote
253
+ """,
254
+ re.X,
255
+ )
256
+
257
+ # -------- inline replace --------
258
+ IMAGE_RE = re.compile(r'!\[[^\]]*\]\([^)]+\)')
259
+ LINK_RE = re.compile(r'\[([^\]]+)\]\([^)]+\)')
260
+ BRACE_RE = re.compile(r"\{[^}]*\}")
261
+ MD_SYNTAX_RE = re.compile(r'[`*_>#]+')
262
+
263
+ def clean_markdown(md: str) -> list:
264
+
265
+ lines = md.splitlines()
266
+ result = []
267
+
268
+ state = "NORMAL"
269
+ fence = ""
270
+ html_close_re = None
271
+ frontmatter_parsed = False
272
+ h1_parsed = False
273
+ math_delim = ""
274
+
275
+ for line in lines:
276
+ stripped = line.strip()
277
+ if not stripped:
278
+ continue
279
+
280
+ # ==================================================
281
+ # 1. Frontmatter
282
+ # ==================================================
283
+ if not frontmatter_parsed:
284
+ if state == "FRONTMATTER":
285
+ if stripped in ("---", "+++"):
286
+ state = "NORMAL"
287
+ frontmatter_parsed = True
288
+ continue
289
+
290
+ if state == "NORMAL" and stripped in ("---", "+++"):
291
+ state = "FRONTMATTER"
292
+ continue
293
+
294
+ # ==================================================
295
+ # 2. Fence Block
296
+ # ==================================================
297
+ if state == "FENCE":
298
+ if stripped.startswith(fence):
299
+ state = "NORMAL"
300
+ continue
301
+
302
+ if state == "NORMAL":
303
+ m = FENCE_RE.match(stripped)
304
+ if m:
305
+ fence = m.group(1)
306
+ state = "FENCE"
307
+ continue
308
+
309
+ # ==================================================
310
+ # 3. HTML Comment
311
+ # ==================================================
312
+ if state == "COMMENT":
313
+ if HTML_COMMENT_END.search(stripped):
314
+ state = "NORMAL"
315
+ continue
316
+
317
+ if state == "NORMAL" and HTML_COMMENT_START.search(stripped):
318
+ state = "COMMENT"
319
+ if HTML_COMMENT_END.search(stripped):
320
+ state = "NORMAL"
321
+ continue
322
+
323
+ # ==================================================
324
+ # 4. HTML Block
325
+ # ==================================================
326
+ if state == "HTML_BLOCK":
327
+ if html_close_re and html_close_re.search(stripped):
328
+ state = "NORMAL"
329
+ html_close_re = None
330
+ continue
331
+
332
+ if state == "NORMAL":
333
+ m = HTML_TAG_OPEN.match(stripped)
334
+ if m:
335
+ tag = m.group(1).lower()
336
+ is_void = tag in HTML_VOID_TAGS
337
+
338
+ # void tag:单行且以 > 结尾,视为直接结束,忽略该行
339
+ if stripped.endswith('>') and is_void:
340
+ continue
341
+
342
+ # 非 void tag:进入 HTML_BLOCK
343
+ state = "HTML_BLOCK"
344
+ if is_void:
345
+ html_close_re = HTML_VOID_CLOSE_RE
346
+ else:
347
+ html_close_re = re.compile(HTML_TAG_CLOSE_TEMPLATE.format(re.escape(tag)), re.I)
348
+
349
+ # same-line close: <div>...</div>
350
+ if html_close_re.search(stripped):
351
+ state = "NORMAL"
352
+ html_close_re = None
353
+
354
+ continue
355
+
356
+ # ==================================================
357
+ # 5. Math Block
358
+ # ==================================================
359
+ if state == "MATH":
360
+ if stripped == math_delim:
361
+ state = "NORMAL"
362
+ continue
363
+
364
+ if state == "NORMAL" and stripped in ("$$", "\\["):
365
+ math_delim = "$$" if stripped == "$$" else "\\]"
366
+ state = "MATH"
367
+ continue
368
+
369
+ # ==================================================
370
+ # 6. Inline Skip
371
+ # ==================================================
372
+ if state == "NORMAL":
373
+ if TABLE_ROW_RE.match(stripped):
374
+ continue
375
+ if INLINE_SKIP_RE.match(stripped):
376
+ continue
377
+ # 单行 HTML 噪音兜底
378
+ if SINGLE_LINE_HTML_NOISE.match(stripped):
379
+ continue
380
+ if not h1_parsed:
381
+ if H1_TITLE.match(stripped):
382
+ h1_parsed = True
383
+ continue
384
+ if stripped.startswith(('---', '***')):
385
+ continue
386
+
387
+ # ==================================================
388
+ # 7. Inline Replace
389
+ # ==================================================
390
+ text = stripped
391
+ text = IMAGE_RE.sub("", text)
392
+ text = LINK_RE.sub(r"\1", text)
393
+ text = BRACE_RE.sub("", text)
394
+
395
+ text = text.strip()
396
+ if text:
397
+ result.append(text)
398
+
399
+ # 提前熔断
400
+ if len(result) >= 10:
401
+ break
402
+
403
+ # 锁定 Frontmatter 状态,防止后续 --- 干扰
404
+ frontmatter_parsed = True
405
+
406
+ return result
407
+ # return "\n".join(result)
408
+
409
+ def extract_summary(markdown_text: str) -> str:
410
+ md_list = clean_markdown(markdown_text)
411
+ text = " ".join(md_list)
412
+ return MD_SYNTAX_RE.sub('', text).strip()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocs-document-dates
3
- Version: 3.5.2
3
+ Version: 3.6.0
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
@@ -97,6 +97,12 @@ In addition to the above basic configuration, the plug-in also provides a wealth
97
97
 
98
98
  See the documentation for details: https://jaywhj.netlify.app/document-dates-en
99
99
 
100
+ <br />
101
+
102
+ ## Other Projects
103
+
104
+ - [**MaterialX**](https://github.com/jaywhj/mkdocs-materialx), the next generation of mkdocs-material, is based on `mkdocs-material-9.7.0` and is named `X`. I'll be maintaining this branch continuously (since mkdocs-material will stop being maintained).
105
+ Updates have been released that refactor and add a lot of new features, see https://github.com/jaywhj/mkdocs-materialx/releases/
100
106
 
101
107
  <br />
102
108
 
@@ -26,7 +26,10 @@ mkdocs_document_dates/static/core/timeago.min.js
26
26
  mkdocs_document_dates/static/core/utils.js
27
27
  mkdocs_document_dates/static/fonts/material-icons.css
28
28
  mkdocs_document_dates/static/fonts/materialicons.woff2
29
- mkdocs_document_dates/static/templates/recently_updated.html
29
+ mkdocs_document_dates/static/templates/recently_updated_detail.html
30
+ mkdocs_document_dates/static/templates/recently_updated_grid.html
31
+ mkdocs_document_dates/static/templates/recently_updated_group.html
32
+ mkdocs_document_dates/static/templates/recently_updated_list.html
30
33
  mkdocs_document_dates/static/tippy/backdrop.css
31
34
  mkdocs_document_dates/static/tippy/light.css
32
35
  mkdocs_document_dates/static/tippy/material.css
@@ -23,7 +23,7 @@ class CustomInstallCommand(install):
23
23
  install.run(self)
24
24
 
25
25
 
26
- VERSION = '3.5.2'
26
+ VERSION = '3.6.0'
27
27
 
28
28
  setup(
29
29
  name="mkdocs-document-dates",