@astro-minimax/core 0.1.0

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 (103) hide show
  1. package/README.md +29 -0
  2. package/package.json +41 -0
  3. package/src/assets/icons/IconArchive.svg +1 -0
  4. package/src/assets/icons/IconArrowLeft.svg +1 -0
  5. package/src/assets/icons/IconArrowNarrowUp.svg +1 -0
  6. package/src/assets/icons/IconArrowRight.svg +1 -0
  7. package/src/assets/icons/IconArticle.svg +1 -0
  8. package/src/assets/icons/IconBrandX.svg +1 -0
  9. package/src/assets/icons/IconCalendar.svg +1 -0
  10. package/src/assets/icons/IconChevronLeft.svg +1 -0
  11. package/src/assets/icons/IconChevronRight.svg +1 -0
  12. package/src/assets/icons/IconEdit.svg +1 -0
  13. package/src/assets/icons/IconFacebook.svg +1 -0
  14. package/src/assets/icons/IconGitHub.svg +1 -0
  15. package/src/assets/icons/IconHash.svg +1 -0
  16. package/src/assets/icons/IconHome.svg +1 -0
  17. package/src/assets/icons/IconLinkedin.svg +1 -0
  18. package/src/assets/icons/IconMail.svg +1 -0
  19. package/src/assets/icons/IconMenuDeep.svg +1 -0
  20. package/src/assets/icons/IconMoon.svg +1 -0
  21. package/src/assets/icons/IconPinterest.svg +1 -0
  22. package/src/assets/icons/IconProject.svg +1 -0
  23. package/src/assets/icons/IconRss.svg +1 -0
  24. package/src/assets/icons/IconSearch.svg +1 -0
  25. package/src/assets/icons/IconSeries.svg +1 -0
  26. package/src/assets/icons/IconSunHigh.svg +1 -0
  27. package/src/assets/icons/IconTag.svg +1 -0
  28. package/src/assets/icons/IconTelegram.svg +1 -0
  29. package/src/assets/icons/IconUser.svg +1 -0
  30. package/src/assets/icons/IconWhatsapp.svg +1 -0
  31. package/src/assets/icons/IconX.svg +1 -0
  32. package/src/components/ai/AIChatWidget.astro +377 -0
  33. package/src/components/blog/Comments.astro +527 -0
  34. package/src/components/blog/Copyright.astro +152 -0
  35. package/src/components/blog/EditPost.astro +59 -0
  36. package/src/components/blog/FloatingTOC.astro +260 -0
  37. package/src/components/blog/InlineTOC.astro +223 -0
  38. package/src/components/blog/PostActions.astro +306 -0
  39. package/src/components/blog/RelatedPosts.astro +60 -0
  40. package/src/components/blog/SeriesNav.astro +176 -0
  41. package/src/components/blog/ShareLinks.astro +26 -0
  42. package/src/components/nav/BackButton.astro +37 -0
  43. package/src/components/nav/BackToTopButton.astro +223 -0
  44. package/src/components/nav/Breadcrumb.astro +57 -0
  45. package/src/components/nav/FloatingActions.astro +206 -0
  46. package/src/components/nav/Footer.astro +107 -0
  47. package/src/components/nav/Header.astro +252 -0
  48. package/src/components/nav/Pagination.astro +45 -0
  49. package/src/components/social/Socials.astro +19 -0
  50. package/src/components/social/Sponsors.astro +34 -0
  51. package/src/components/social/Sponsorship.astro +44 -0
  52. package/src/components/ui/Alert.astro +28 -0
  53. package/src/components/ui/Card.astro +206 -0
  54. package/src/components/ui/Collapse.astro +82 -0
  55. package/src/components/ui/ColorPreview.astro +29 -0
  56. package/src/components/ui/Datetime.astro +61 -0
  57. package/src/components/ui/GithubCard.astro +191 -0
  58. package/src/components/ui/LinkButton.astro +21 -0
  59. package/src/components/ui/Tag.astro +37 -0
  60. package/src/components/ui/TagCloud.astro +69 -0
  61. package/src/components/ui/Timeline.astro +39 -0
  62. package/src/layouts/AboutLayout.astro +24 -0
  63. package/src/layouts/Layout.astro +329 -0
  64. package/src/layouts/Main.astro +42 -0
  65. package/src/layouts/PostDetails.astro +445 -0
  66. package/src/plugins/rehype-autolink-headings.ts +46 -0
  67. package/src/plugins/rehype-external-links.ts +35 -0
  68. package/src/plugins/rehype-table-scroll.ts +35 -0
  69. package/src/plugins/remark-add-zoomable.ts +28 -0
  70. package/src/plugins/remark-reading-time.ts +18 -0
  71. package/src/plugins/shiki-transformers.ts +212 -0
  72. package/src/scripts/lightbox.ts +63 -0
  73. package/src/scripts/reading-position.ts +56 -0
  74. package/src/scripts/theme-utils.ts +19 -0
  75. package/src/scripts/theme.ts +179 -0
  76. package/src/scripts/web-vitals.ts +96 -0
  77. package/src/styles/code-blocks.css +194 -0
  78. package/src/styles/components.css +252 -0
  79. package/src/styles/global.css +403 -0
  80. package/src/styles/typography.css +149 -0
  81. package/src/types.ts +89 -0
  82. package/src/utils/generateOgImages.ts +38 -0
  83. package/src/utils/getCategoryPath.ts +23 -0
  84. package/src/utils/getPath.ts +52 -0
  85. package/src/utils/getPostsByCategory.ts +17 -0
  86. package/src/utils/getPostsByGroupCondition.ts +25 -0
  87. package/src/utils/getPostsByLang.ts +27 -0
  88. package/src/utils/getPostsByTag.ts +10 -0
  89. package/src/utils/getReadingTime.ts +33 -0
  90. package/src/utils/getRelatedPosts.ts +59 -0
  91. package/src/utils/getSeriesData.ts +57 -0
  92. package/src/utils/getSortedPosts.ts +18 -0
  93. package/src/utils/getTagsWithCount.ts +38 -0
  94. package/src/utils/getUniqueCategories.ts +81 -0
  95. package/src/utils/getUniqueTags.ts +23 -0
  96. package/src/utils/i18n.ts +249 -0
  97. package/src/utils/loadGoogleFont.ts +38 -0
  98. package/src/utils/og-templates/post.js +229 -0
  99. package/src/utils/og-templates/site.js +128 -0
  100. package/src/utils/pathUtils.ts +17 -0
  101. package/src/utils/postFilter.ts +11 -0
  102. package/src/utils/slugify.ts +23 -0
  103. package/src/utils/toc.ts +27 -0
@@ -0,0 +1,527 @@
1
+ ---
2
+ import { SITE } from "@/config";
3
+
4
+ // Only render if Waline is enabled in config
5
+ const enabled = SITE.waline?.enabled;
6
+ ---
7
+
8
+ {
9
+ enabled && (
10
+ <div class="comments-section panel-card mt-8 px-5 py-5 sm:px-6">
11
+ {/* Comment Section Header */}
12
+ <div class="mb-6 flex flex-col gap-3 border-b border-border/70 pb-4 sm:flex-row sm:items-center sm:justify-between">
13
+ <h3 class="flex items-center gap-2 font-display text-2xl leading-tight tracking-[-0.02em] text-foreground">
14
+ <svg class="size-5 text-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
15
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
16
+ </svg>
17
+ <span>评论区</span>
18
+ </h3>
19
+ <span class="text-sm text-foreground-soft">文明评论,共建和谐社区</span>
20
+ </div>
21
+
22
+ {/* Waline Container */}
23
+ <div id="waline" class="not-prose"></div>
24
+
25
+ {/* Loading indicator */}
26
+ <div id="waline-loading" class="flex items-center justify-center py-8">
27
+ <div class="animate-spin size-8 border-2 border-accent border-t-transparent rounded-full"></div>
28
+ </div>
29
+
30
+ {/* Fallback for disabled JS */}
31
+ <noscript>
32
+ <div class="rounded-lg bg-muted/50 p-4 text-center text-muted-foreground">
33
+ 请启用 JavaScript 以查看评论功能。
34
+ </div>
35
+ </noscript>
36
+
37
+ <link rel="stylesheet" href="https://unpkg.com/@waline/client@v3/dist/waline.css" />
38
+
39
+ <script is:inline type="module" define:vars={{
40
+ serverURL: SITE.waline.serverURL,
41
+ emoji: SITE.waline.emoji,
42
+ lang: SITE.waline.lang,
43
+ reaction: SITE.waline.reaction,
44
+ pageview: SITE.waline.pageview,
45
+ login: SITE.waline.login,
46
+ wordLimit: SITE.waline.wordLimit,
47
+ imageUploader: SITE.waline.imageUploader,
48
+ requiredMeta: SITE.waline.requiredMeta,
49
+ copyright: SITE.waline.copyright
50
+ }}>
51
+ import { init } from 'https://unpkg.com/@waline/client@v3/dist/waline.js';
52
+
53
+ let walineInstance = null;
54
+
55
+ function loadWaline() {
56
+ const container = document.getElementById('waline');
57
+ const loading = document.getElementById('waline-loading');
58
+
59
+ if (container && serverURL) {
60
+ try {
61
+ // Clean up previous instance
62
+ if (walineInstance) {
63
+ // Handle both sync errors and Promise rejections from destroy()
64
+ // Waline's internal Vue components may reject with AbortError during cleanup
65
+ const result = walineInstance.destroy();
66
+ if (result && typeof result.catch === 'function') {
67
+ result.catch(() => {
68
+ // Suppress internal Vue AbortError during cleanup
69
+ });
70
+ }
71
+ walineInstance = null;
72
+ }
73
+
74
+ walineInstance = init({
75
+ el: '#waline',
76
+ serverURL: serverURL,
77
+ emoji: emoji,
78
+ lang: lang,
79
+ reaction: reaction,
80
+ pageview: pageview,
81
+ login: login,
82
+ wordLimit: wordLimit,
83
+ imageUploader: imageUploader,
84
+ requiredMeta: requiredMeta,
85
+ copyright: copyright,
86
+ dark: 'html[data-theme="dark"]',
87
+ path: window.location.pathname.replace(/\/$/, ''), // Ensure path consistency
88
+ locale: {
89
+ placeholder: '欢迎评论,支持 Markdown 语法。\n请遵守社区规范,友善发言。',
90
+ sofa: '快来抢沙发吧~',
91
+ submit: '发布评论',
92
+ reply: '回复',
93
+ cancelReply: '取消回复',
94
+ comment: '评论',
95
+ refresh: '刷新',
96
+ more: '加载更多...',
97
+ preview: '预览',
98
+ emoji: '表情',
99
+ uploadImage: '上传图片',
100
+ seconds: '秒前',
101
+ minutes: '分钟前',
102
+ hours: '小时前',
103
+ days: '天前',
104
+ now: '刚刚',
105
+ uploading: '上传中',
106
+ login: '登录',
107
+ logout: '退出',
108
+ admin: '博主',
109
+ sticky: '置顶',
110
+ word: '字',
111
+ wordHint: '评论字数应在 $0 到 $1 字之间!\n当前字数:$2',
112
+ anonymous: '匿名',
113
+ level0: '潜水',
114
+ level1: '冒泡',
115
+ level2: '吐槽',
116
+ level3: '活跃',
117
+ level4: '话痨',
118
+ level5: '传说',
119
+ },
120
+ });
121
+
122
+ // Hide loading indicator after successful load
123
+ if (loading) {
124
+ loading.style.display = 'none';
125
+ }
126
+ } catch (e) {
127
+ // eslint-disable-next-line no-console
128
+ console.error('Failed to load Waline:', e);
129
+ if (loading) {
130
+ loading.innerHTML = '<div class="text-center text-muted-foreground">评论加载失败,请刷新重试</div>';
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ // Handle initial load
137
+ loadWaline();
138
+
139
+ // Handle View Transitions (if using ClientRouter)
140
+ document.addEventListener('astro:page-load', loadWaline);
141
+
142
+ // Cleanup before page swap to prevent AbortError
143
+ document.addEventListener('astro:before-swap', () => {
144
+ if (walineInstance) {
145
+ // Handle both sync errors and Promise rejections from destroy()
146
+ const result = walineInstance.destroy();
147
+ if (result && typeof result.catch === 'function') {
148
+ result.catch(() => {
149
+ // Ignore errors during cleanup - component may already be unmounted
150
+ });
151
+ }
152
+ walineInstance = null;
153
+ }
154
+ });
155
+ </script>
156
+
157
+ <style is:global>
158
+ /* ===========================================
159
+ * WALINE COMMENT SYSTEM STYLES
160
+ * Comprehensive theming for light/dark modes
161
+ * =========================================== */
162
+
163
+ .comments-section {
164
+ animation: fadeIn 0.5s ease-out;
165
+ }
166
+
167
+ @keyframes fadeIn {
168
+ from {
169
+ opacity: 0;
170
+ transform: translateY(10px);
171
+ }
172
+ to {
173
+ opacity: 1;
174
+ transform: translateY(0);
175
+ }
176
+ }
177
+
178
+ /* ===========================================
179
+ * ROOT VARIABLES (Common)
180
+ * =========================================== */
181
+ :root {
182
+ /* Theme Colors */
183
+ --waline-theme-color: var(--accent) !important;
184
+ --waline-active-color: var(--accent) !important;
185
+
186
+ /* Typography */
187
+ --waline-font-size: 0.9375rem !important;
188
+ --waline-border-radius: 0.5rem !important;
189
+
190
+ /* Avatar */
191
+ --waline-avatar-size: 2.75rem !important;
192
+ --waline-m-avatar-size: 2.25rem !important;
193
+
194
+ /* Box Shadow */
195
+ --waline-box-shadow: none !important;
196
+ }
197
+
198
+ /* ===========================================
199
+ * LIGHT MODE THEME
200
+ * =========================================== */
201
+ html:not([data-theme="dark"]) {
202
+ /* Primary Backgrounds - use --card for consistency */
203
+ --waline-bg-color: var(--card) !important;
204
+ --waline-bg-color-light: var(--muted) !important;
205
+ --waline-bg-color-hover: rgba(0, 0, 0, 0.05) !important;
206
+
207
+ /* Text Colors */
208
+ --waline-color: var(--foreground) !important;
209
+ --waline-font-color: var(--foreground) !important;
210
+
211
+ /* Border & Disabled */
212
+ --waline-border-color: var(--border) !important;
213
+ --waline-disable-bg-color: var(--muted) !important;
214
+ --waline-disable-color: var(--foreground-soft) !important;
215
+
216
+ /* Special Colors */
217
+ --waline-bq-color: var(--muted) !important;
218
+ --waline-info-bg-color: var(--muted) !important;
219
+ --waline-info-color: var(--foreground-soft) !important;
220
+ --waline-code-bg-color: #1e1e1e !important;
221
+
222
+ /* White/Grey overrides */
223
+ --waline-white: var(--card) !important;
224
+ --waline-light-grey: var(--foreground-soft) !important;
225
+ --waline-dark-grey: var(--foreground) !important;
226
+ }
227
+
228
+ /* ===========================================
229
+ * DARK MODE THEME
230
+ * =========================================== */
231
+ html[data-theme="dark"] {
232
+ /* Primary Backgrounds - use --card for consistency */
233
+ --waline-bg-color: var(--card) !important;
234
+ --waline-bg-color-light: var(--muted) !important;
235
+ --waline-bg-color-hover: rgba(255, 255, 255, 0.05) !important;
236
+
237
+ /* Text Colors */
238
+ --waline-color: var(--foreground) !important;
239
+ --waline-font-color: var(--foreground) !important;
240
+
241
+ /* Border & Disabled */
242
+ --waline-border-color: var(--border) !important;
243
+ --waline-disable-bg-color: rgba(128, 128, 128, 0.2) !important;
244
+ --waline-disable-color: var(--foreground-soft) !important;
245
+
246
+ /* Special Colors */
247
+ --waline-bq-color: var(--muted) !important;
248
+ --waline-info-bg-color: var(--muted) !important;
249
+ --waline-info-color: var(--foreground-soft) !important;
250
+ --waline-code-bg-color: #0d1117 !important;
251
+
252
+ /* White/Grey overrides */
253
+ --waline-white: var(--card) !important;
254
+ --waline-light-grey: var(--foreground-soft) !important;
255
+ --waline-dark-grey: var(--foreground) !important;
256
+ }
257
+
258
+ /* ===========================================
259
+ * CONTAINER & PANEL STYLES
260
+ * =========================================== */
261
+ #waline {
262
+ padding-top: 0.5rem;
263
+ }
264
+
265
+ /* Main panel - ensure consistent background */
266
+ #waline .wl-panel {
267
+ background-color: var(--card) !important;
268
+ border-color: var(--border) !important;
269
+ }
270
+
271
+ /* Header area */
272
+ #waline .wl-header {
273
+ background-color: transparent !important;
274
+ }
275
+
276
+ #waline .wl-header-item {
277
+ background-color: var(--muted) !important;
278
+ border-color: var(--border) !important;
279
+ }
280
+
281
+ /* Input wrapper */
282
+ #waline .wl-input-wrapper {
283
+ background-color: var(--card) !important;
284
+ border-color: var(--border) !important;
285
+ }
286
+
287
+ /* ===========================================
288
+ * INPUT & EDITOR STYLES
289
+ * =========================================== */
290
+ #waline .wl-editor,
291
+ #waline .wl-input {
292
+ min-height: 120px !important;
293
+ background-color: var(--card) !important;
294
+ border-color: var(--border) !important;
295
+ border-radius: 0.5rem !important;
296
+ transition: border-color 0.2s ease, box-shadow 0.2s ease !important;
297
+ }
298
+
299
+ #waline .wl-input {
300
+ min-height: auto !important;
301
+ }
302
+
303
+ #waline .wl-editor:focus,
304
+ #waline .wl-input:focus {
305
+ border-color: var(--accent) !important;
306
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent) !important;
307
+ }
308
+
309
+ /* Textarea */
310
+ #waline textarea {
311
+ background-color: var(--card) !important;
312
+ color: var(--foreground) !important;
313
+ }
314
+
315
+ /* ===========================================
316
+ * BUTTON STYLES
317
+ * =========================================== */
318
+ #waline .wl-btn {
319
+ transition: all 0.2s ease !important;
320
+ border-radius: 0.375rem !important;
321
+ background-color: var(--muted) !important;
322
+ color: var(--foreground) !important;
323
+ }
324
+
325
+ #waline .wl-btn:hover {
326
+ transform: translateY(-1px);
327
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
328
+ background-color: var(--bg-color-hover) !important;
329
+ }
330
+
331
+ #waline .wl-primary-btn {
332
+ font-weight: 500 !important;
333
+ background-color: var(--accent) !important;
334
+ color: var(--background) !important;
335
+ }
336
+
337
+ #waline .wl-primary-btn:hover {
338
+ background-color: var(--accent) !important;
339
+ opacity: 0.9;
340
+ }
341
+
342
+ /* ===========================================
343
+ * COMMENT CARD STYLES
344
+ * =========================================== */
345
+ #waline .wl-card {
346
+ border-radius: 0.5rem !important;
347
+ background-color: var(--card) !important;
348
+ border-color: var(--border) !important;
349
+ transition: box-shadow 0.2s ease !important;
350
+ }
351
+
352
+ #waline .wl-card:hover {
353
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
354
+ }
355
+
356
+ /* Comment content area */
357
+ #waline .wl-content {
358
+ background-color: transparent !important;
359
+ }
360
+
361
+ /* Comment meta info */
362
+ #waline .wl-meta {
363
+ color: var(--foreground-soft) !important;
364
+ }
365
+
366
+ /* ===========================================
367
+ * AVATAR STYLES
368
+ * =========================================== */
369
+ #waline .wl-avatar {
370
+ border-radius: 50% !important;
371
+ }
372
+
373
+ /* ===========================================
374
+ * REACTION STYLES
375
+ * =========================================== */
376
+ #waline .wl-reaction {
377
+ background-color: transparent !important;
378
+ }
379
+
380
+ #waline .wl-reaction-img {
381
+ transition: transform 0.2s ease !important;
382
+ background-color: var(--muted) !important;
383
+ border-radius: 0.5rem !important;
384
+ }
385
+
386
+ #waline .wl-reaction-img:hover {
387
+ transform: scale(1.1);
388
+ }
389
+
390
+ #waline .wl-reaction-title {
391
+ color: var(--foreground-soft) !important;
392
+ }
393
+
394
+ /* ===========================================
395
+ * QUOTE & CODE STYLES
396
+ * =========================================== */
397
+ #waline .wl-quote {
398
+ border-left-color: var(--accent) !important;
399
+ background-color: var(--muted) !important;
400
+ color: var(--foreground) !important;
401
+ }
402
+
403
+ #waline .wl-content pre,
404
+ #waline .wl-content code {
405
+ background-color: var(--waline-code-bg-color) !important;
406
+ border-radius: 0.25rem !important;
407
+ }
408
+
409
+ /* ===========================================
410
+ * EMOJI PICKER
411
+ * =========================================== */
412
+ #waline .wl-emoji-popup {
413
+ border-radius: 0.5rem !important;
414
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
415
+ background-color: var(--card) !important;
416
+ border-color: var(--border) !important;
417
+ }
418
+
419
+ #waline .wl-tab-wrapper button {
420
+ color: var(--foreground) !important;
421
+ }
422
+
423
+ #waline .wl-tab-wrapper button.active {
424
+ color: var(--accent) !important;
425
+ border-bottom-color: var(--accent) !important;
426
+ }
427
+
428
+ /* ===========================================
429
+ * REPLY FORM
430
+ * =========================================== */
431
+ #waline .wl-reply-wrapper {
432
+ border-radius: 0.5rem !important;
433
+ background-color: var(--muted) !important;
434
+ }
435
+
436
+ #waline .wl-reply {
437
+ background-color: var(--card) !important;
438
+ }
439
+
440
+ /* ===========================================
441
+ * PAGINATION
442
+ * =========================================== */
443
+ #waline .wl-page-button {
444
+ border-radius: 0.25rem !important;
445
+ transition: background-color 0.2s ease !important;
446
+ background-color: var(--muted) !important;
447
+ color: var(--foreground) !important;
448
+ }
449
+
450
+ #waline .wl-page-button:hover {
451
+ background-color: var(--accent) !important;
452
+ color: var(--background) !important;
453
+ }
454
+
455
+ #waline .wl-page-button.active {
456
+ background-color: var(--accent) !important;
457
+ color: var(--background) !important;
458
+ }
459
+
460
+ /* ===========================================
461
+ * ADMIN & BADGE STYLES
462
+ * =========================================== */
463
+ #waline .wl-badge {
464
+ background-color: var(--accent) !important;
465
+ color: var(--background) !important;
466
+ }
467
+
468
+ #waline .wl-admin {
469
+ color: var(--accent) !important;
470
+ }
471
+
472
+ /* ===========================================
473
+ * LOGIN FORM
474
+ * =========================================== */
475
+ #waline .wl-login-form {
476
+ background-color: var(--card) !important;
477
+ }
478
+
479
+ /* ===========================================
480
+ * LOADING ANIMATION
481
+ * =========================================== */
482
+ #waline-loading {
483
+ transition: opacity 0.3s ease;
484
+ }
485
+
486
+ /* ===========================================
487
+ * GUEST STYLES
488
+ * =========================================== */
489
+ #waline .wl-guest {
490
+ background-color: transparent !important;
491
+ }
492
+
493
+ #waline .wl-guest .wl-nick,
494
+ #waline .wl-guest .wl-link {
495
+ background-color: var(--card) !important;
496
+ border-color: var(--border) !important;
497
+ color: var(--foreground) !important;
498
+ }
499
+
500
+ /* ===========================================
501
+ * COMMENT OPERATIONS
502
+ * =========================================== */
503
+ #waline .wl-comment-actions button {
504
+ color: var(--foreground-soft) !important;
505
+ }
506
+
507
+ #waline .wl-comment-actions button:hover {
508
+ color: var(--accent) !important;
509
+ }
510
+
511
+ /* ===========================================
512
+ * IMAGE PREVIEW
513
+ * =========================================== */
514
+ #waline .wl-image-preview {
515
+ background-color: rgba(0, 0, 0, 0.85) !important;
516
+ }
517
+
518
+ /* ===========================================
519
+ * VERIFICATION BADGE
520
+ * =========================================== */
521
+ #waline .wl-verified-icon {
522
+ color: var(--accent) !important;
523
+ }
524
+ </style>
525
+ </div>
526
+ )
527
+ }
@@ -0,0 +1,152 @@
1
+ ---
2
+ import { SITE } from "@/config";
3
+
4
+ interface Props {
5
+ title: string;
6
+ pubDatetime: Date;
7
+ url?: string;
8
+ lang?: string;
9
+ }
10
+
11
+ const { title, pubDatetime, url, lang = "zh" } = Astro.props;
12
+ const pageUrl = url || Astro.url.href;
13
+ const copyrightConfig = SITE.copyright;
14
+ const hasSponsor = SITE.sponsor?.enabled;
15
+ ---
16
+
17
+ <div class="copyright-box mt-8 rounded-xl border">
18
+ <div class="flex flex-col gap-2 px-3 py-3 sm:px-4">
19
+ <div class="flex items-center gap-2 text-sm font-medium text-foreground">
20
+ <svg
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ width="18"
23
+ height="18"
24
+ viewBox="0 0 24 24"
25
+ fill="none"
26
+ stroke="currentColor"
27
+ stroke-width="2"
28
+ stroke-linecap="round"
29
+ stroke-linejoin="round"
30
+ >
31
+ <circle cx="12" cy="12" r="10"></circle>
32
+ <path d="M14.83 14.83a4 4 0 1 1 0-5.66"></path>
33
+ </svg>
34
+ <span class="line-clamp-1">{title}</span>
35
+ </div>
36
+ <div class="text-xs text-foreground-soft break-all">{pageUrl}</div>
37
+ <div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-foreground-soft">
38
+ <span>
39
+ {lang === "zh" ? "作者" : "Author"}:
40
+ <span class="text-foreground">{SITE.author}</span>
41
+ </span>
42
+ <span>
43
+ {lang === "zh" ? "发布于" : "Published"}:
44
+ <time class="text-foreground">
45
+ {pubDatetime.toLocaleDateString(lang === "zh" ? "zh-CN" : "en-US", {
46
+ year: "numeric",
47
+ month: "short",
48
+ day: "numeric",
49
+ })}
50
+ </time>
51
+ </span>
52
+ <span>
53
+ {lang === "zh" ? "许可" : "License"}:
54
+ <a
55
+ href={copyrightConfig?.licenseUrl || "https://creativecommons.org/licenses/by-nc-sa/4.0/"}
56
+ target="_blank"
57
+ rel="noopener noreferrer"
58
+ class="text-accent hover:underline"
59
+ >
60
+ {copyrightConfig?.license || "CC BY-NC-SA 4.0"}
61
+ </a>
62
+ </span>
63
+ </div>
64
+ <div class="mt-1 flex items-center gap-2">
65
+ <button
66
+ id="copy-link-btn"
67
+ class="flex items-center gap-1 rounded-md border px-2 py-1 text-xs text-foreground-soft transition-colors hover:bg-muted hover:text-foreground"
68
+ aria-label={lang === "zh" ? "复制链接" : "Copy link"}
69
+ >
70
+ <svg
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ width="14"
73
+ height="14"
74
+ viewBox="0 0 24 24"
75
+ fill="none"
76
+ stroke="currentColor"
77
+ stroke-width="2"
78
+ stroke-linecap="round"
79
+ stroke-linejoin="round"
80
+ >
81
+ <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
82
+ <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
83
+ </svg>
84
+ {lang === "zh" ? "复制链接" : "Copy link"}
85
+ </button>
86
+ <a
87
+ href={`https://x.com/intent/tweet?url=${encodeURIComponent(pageUrl)}&text=${encodeURIComponent(title)}`}
88
+ target="_blank"
89
+ rel="noopener noreferrer"
90
+ class="flex items-center gap-1 rounded-md border px-2 py-1 text-xs text-foreground-soft transition-colors hover:bg-muted hover:text-foreground"
91
+ aria-label="Share on X"
92
+ >
93
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
94
+ <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
95
+ </svg>
96
+ </a>
97
+ <a
98
+ href={`https://service.weibo.com/share/share.php?url=${encodeURIComponent(pageUrl)}&title=${encodeURIComponent(title)}`}
99
+ target="_blank"
100
+ rel="noopener noreferrer"
101
+ class="flex items-center gap-1 rounded-md border px-2 py-1 text-xs text-foreground-soft transition-colors hover:bg-muted hover:text-foreground"
102
+ aria-label="Share on Weibo"
103
+ >
104
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
105
+ <path d="M9.82 13.87c-2.27 0-4.11.96-4.11 2.14s1.84 2.14 4.11 2.14 4.11-.96 4.11-2.14-1.84-2.14-4.11-2.14zm1.28 3.16c-.63.37-1.52.43-2 .13-.47-.29-.4-.87.18-1.28.58-.41 1.48-.47 1.97-.13.49.34.42.91-.15 1.28zM20.5 7.5c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5.67 1.5 1.5 1.5 1.5-.67 1.5-1.5zM22 4c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm-2.18 3.35c-.94-3.47-4.58-5.16-8.13-3.78-3.43 1.34-5.54 5.1-4.6 8.31.93 3.18 4.58 4.86 8.12 3.85 3.66-1.04 5.55-5.06 4.61-8.38z" />
106
+ </svg>
107
+ </a>
108
+ </div>
109
+ </div>
110
+ {
111
+ hasSponsor && (
112
+ <div class="rounded-b-xl border-t px-3 pb-1.5 pt-1">
113
+ <a
114
+ href={`/${lang}/about#sponsorship`}
115
+ class="flex items-center justify-between text-sm text-foreground-soft transition-colors hover:text-accent"
116
+ >
117
+ <span>{lang === "zh" ? "请我喝杯咖啡 ☕" : "Buy me a cup of coffee ☕"}</span>
118
+ <svg
119
+ xmlns="http://www.w3.org/2000/svg"
120
+ width="16"
121
+ height="16"
122
+ viewBox="0 0 24 24"
123
+ fill="none"
124
+ stroke="currentColor"
125
+ stroke-width="2"
126
+ stroke-linecap="round"
127
+ stroke-linejoin="round"
128
+ >
129
+ <polyline points="9 18 15 12 9 6"></polyline>
130
+ </svg>
131
+ </a>
132
+ </div>
133
+ )
134
+ }
135
+ </div>
136
+
137
+ <script>
138
+ function initCopyLink() {
139
+ document.querySelectorAll("#copy-link-btn").forEach(btn => {
140
+ btn.addEventListener("click", () => {
141
+ navigator.clipboard.writeText(window.location.href);
142
+ const original = btn.textContent;
143
+ btn.textContent = "✓ Copied!";
144
+ setTimeout(() => {
145
+ if (original) btn.textContent = original;
146
+ }, 2000);
147
+ });
148
+ });
149
+ }
150
+ initCopyLink();
151
+ document.addEventListener("astro:page-load", initCopyLink);
152
+ </script>