@jet-w/astro-blog 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.
- package/dist/chunk-FXPGR372.js +0 -0
- package/dist/chunk-GYLSY3OJ.js +173 -0
- package/dist/config/index.d.ts +166 -0
- package/dist/config/index.js +38 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +59 -0
- package/dist/types/index.d.ts +75 -0
- package/dist/types/index.js +1 -0
- package/package.json +84 -0
- package/src/components/EChartsCard.vue +118 -0
- package/src/components/Mermaid.vue +73 -0
- package/src/components/about/ContentCard.astro +27 -0
- package/src/components/about/IconCard.astro +77 -0
- package/src/components/about/SocialLinks.astro +54 -0
- package/src/components/about/TagCard.astro +65 -0
- package/src/components/about/TagGroup.astro +33 -0
- package/src/components/about/TimelineCard.astro +52 -0
- package/src/components/blog/FloatingToc.vue +198 -0
- package/src/components/blog/Hero.astro +147 -0
- package/src/components/blog/NavigationTabs.vue +245 -0
- package/src/components/blog/PostCard.astro +161 -0
- package/src/components/blog/PostNavigation.astro +106 -0
- package/src/components/blog/RelatedPosts.astro +175 -0
- package/src/components/blog/TableOfContents.astro +153 -0
- package/src/components/blog/TagCloud.astro +91 -0
- package/src/components/home/FeaturedPostsSection.astro +54 -0
- package/src/components/home/QuickNavSection.astro +81 -0
- package/src/components/home/RecentPostsSection.astro +52 -0
- package/src/components/home/StatsSection.astro +44 -0
- package/src/components/layout/Footer.astro +103 -0
- package/src/components/layout/Header.astro +68 -0
- package/src/components/layout/Sidebar.astro +594 -0
- package/src/components/media/Bilibili.astro +114 -0
- package/src/components/media/Slides.astro +313 -0
- package/src/components/media/Video.astro +111 -0
- package/src/components/media/VideoPlayer.astro +89 -0
- package/src/components/media/YouTube.astro +92 -0
- package/src/components/pte/StudyCalendar.vue +1348 -0
- package/src/components/ui/Icon.astro +187 -0
- package/src/components/ui/MobileMenu.vue +201 -0
- package/src/components/ui/Pagination.astro +143 -0
- package/src/components/ui/SearchBox.vue +179 -0
- package/src/components/ui/SearchInterface.vue +409 -0
- package/src/components/ui/SidebarToggle.vue +57 -0
- package/src/components/ui/ThemeToggle.vue +90 -0
- package/src/layouts/AboutLayout.astro +18 -0
- package/src/layouts/BaseLayout.astro +362 -0
- package/src/layouts/PageLayout.astro +217 -0
- package/src/layouts/SlidesLayout.astro +320 -0
- package/src/plugins/rehype-clean-containers.mjs +24 -0
- package/src/plugins/rehype-relative-links.mjs +43 -0
- package/src/plugins/rehype-tabs.mjs +116 -0
- package/src/plugins/remark-containers.mjs +407 -0
- package/src/plugins/remark-mermaid.mjs +46 -0
- package/src/styles/global.css +870 -0
- package/src/styles/slides.css +220 -0
- package/src/utils/sidebar.ts +492 -0
- package/templates/default/astro.config.mjs +51 -0
- package/templates/default/content/pages/about.mdx +93 -0
- package/templates/default/content/pages/index.mdx +20 -0
- package/templates/default/content/posts/blog_docs/01-quick-start.md +162 -0
- package/templates/default/content/posts/blog_docs/02-frontmatter.md +277 -0
- package/templates/default/content/posts/blog_docs/03-markdown-basic.md +350 -0
- package/templates/default/content/posts/blog_docs/04-containers.md +331 -0
- package/templates/default/content/posts/blog_docs/05-code-blocks.md +388 -0
- package/templates/default/content/posts/blog_docs/06-mermaid.md +431 -0
- package/templates/default/content/posts/blog_docs/07-video.md +243 -0
- package/templates/default/content/posts/blog_docs/08-latex.md +382 -0
- package/templates/default/content/posts/blog_docs/09-icons.md +326 -0
- package/templates/default/content/posts/blog_docs/10-sidebar.md +445 -0
- package/templates/default/content/posts/blog_docs/11-config.md +334 -0
- package/templates/default/content/posts/blog_docs/12-slides.mdx +552 -0
- package/templates/default/content/posts/blog_docs/README.md +151 -0
- package/templates/default/content/slides/demo.md +146 -0
- package/templates/default/content/slides/docs/basic-demo.md +35 -0
- package/templates/default/content/slides/docs/code-demo.md +62 -0
- package/templates/default/content/slides/docs/echarts-demo.md +139 -0
- package/templates/default/content/slides/docs/fragment-demo.md +35 -0
- package/templates/default/content/slides/docs/math-demo.md +48 -0
- package/templates/default/content/slides/docs/mermaid-demo.md +105 -0
- package/templates/default/content/slides/docs/theme-demo.md +38 -0
- package/templates/default/content/slides/docs/vertical-demo.md +50 -0
- package/templates/default/package.json +31 -0
- package/templates/default/public/favicon-bak.svg +4 -0
- package/templates/default/public/images/avatar.jpg +0 -0
- package/templates/default/public/images/avatar.svg +142 -0
- package/templates/default/public/js/mermaid-container.js +402 -0
- package/templates/default/public/js/mermaid-init.js +131 -0
- package/templates/default/public/js/mermaid-render.js +98 -0
- package/templates/default/public/js/mermaid-simple.js +95 -0
- package/templates/default/public/js/tabs-init.js +86 -0
- package/templates/default/public/media/individual_portfolio/INDIVIDUAL PORTFOLIO.png +0 -0
- package/templates/default/public/slides/plugin/highlight/highlight.js +5 -0
- package/templates/default/public/slides/plugin/highlight/monokai.css +71 -0
- package/templates/default/public/slides/plugin/markdown/markdown.js +7 -0
- package/templates/default/public/slides/plugin/math/math.js +1 -0
- package/templates/default/public/slides/plugin/notes/notes.js +1 -0
- package/templates/default/public/slides/reveal.css +9 -0
- package/templates/default/public/slides/reveal.js +9 -0
- package/templates/default/public/slides/theme/beige.css +366 -0
- package/templates/default/public/slides/theme/black-contrast.css +362 -0
- package/templates/default/public/slides/theme/black.css +359 -0
- package/templates/default/public/slides/theme/blood.css +392 -0
- package/templates/default/public/slides/theme/dracula.css +385 -0
- package/templates/default/public/slides/theme/league.css +368 -0
- package/templates/default/public/slides/theme/moon.css +362 -0
- package/templates/default/public/slides/theme/night.css +360 -0
- package/templates/default/public/slides/theme/serif.css +363 -0
- package/templates/default/public/slides/theme/simple.css +362 -0
- package/templates/default/public/slides/theme/sky.css +370 -0
- package/templates/default/public/slides/theme/solarized.css +363 -0
- package/templates/default/public/slides/theme/white-contrast.css +362 -0
- package/templates/default/public/slides/theme/white.css +359 -0
- package/templates/default/public/slides/theme/white_contrast_compact_verbatim_headers.css +360 -0
- package/templates/default/public/test-complete.html +43 -0
- package/templates/default/public/test-mermaid.html +124 -0
- package/templates/default/src/config/index.ts +114 -0
- package/templates/default/src/content.config.ts +96 -0
- package/templates/default/src/pages/[...slug].astro +27 -0
- package/templates/default/src/pages/archives/[year]/[month]/page/[page].astro +176 -0
- package/templates/default/src/pages/archives/[year]/[month].astro +158 -0
- package/templates/default/src/pages/archives/index.astro +210 -0
- package/templates/default/src/pages/categories/[category]/page/[page].astro +218 -0
- package/templates/default/src/pages/categories/[category].astro +198 -0
- package/templates/default/src/pages/categories/index.astro +190 -0
- package/templates/default/src/pages/container-test.astro +79 -0
- package/templates/default/src/pages/mermaid-direct.html +78 -0
- package/templates/default/src/pages/posts/[...slug].astro +335 -0
- package/templates/default/src/pages/posts/index.astro +541 -0
- package/templates/default/src/pages/posts/page/[page].astro +146 -0
- package/templates/default/src/pages/rss.xml.ts +28 -0
- package/templates/default/src/pages/search-index.json.ts +21 -0
- package/templates/default/src/pages/search.astro +50 -0
- package/templates/default/src/pages/slides/[...slug].astro +54 -0
- package/templates/default/src/pages/slides/index.astro +135 -0
- package/templates/default/src/pages/tags/[tag]/page/[page].astro +211 -0
- package/templates/default/src/pages/tags/[tag].astro +191 -0
- package/templates/default/src/pages/tags/index.astro +167 -0
- package/templates/default/tailwind.config.mjs +78 -0
- package/templates/default/tsconfig.json +9 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Bilibili 视频嵌入组件
|
|
4
|
+
*
|
|
5
|
+
* 使用方法:
|
|
6
|
+
* <Bilibili bvid="BV1xx411c7mD" />
|
|
7
|
+
* <Bilibili aid="170001" />
|
|
8
|
+
* <Bilibili url="https://www.bilibili.com/video/BV1xx411c7mD" />
|
|
9
|
+
* <Bilibili bvid="BV1xx411c7mD" page={2} />
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
/** BV 号 */
|
|
14
|
+
bvid?: string;
|
|
15
|
+
/** AV 号 */
|
|
16
|
+
aid?: string | number;
|
|
17
|
+
/** Bilibili 视频完整 URL */
|
|
18
|
+
url?: string;
|
|
19
|
+
/** 视频标题 (用于无障碍) */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** 分P (从1开始) */
|
|
22
|
+
page?: number;
|
|
23
|
+
/** 开始时间 (秒) */
|
|
24
|
+
start?: number;
|
|
25
|
+
/** 是否高清 */
|
|
26
|
+
highQuality?: boolean;
|
|
27
|
+
/** 是否自动播放 */
|
|
28
|
+
autoplay?: boolean;
|
|
29
|
+
/** 是否静音 */
|
|
30
|
+
muted?: boolean;
|
|
31
|
+
/** 自定义宽高比 (默认 16/9) */
|
|
32
|
+
aspectRatio?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
bvid,
|
|
37
|
+
aid,
|
|
38
|
+
url,
|
|
39
|
+
title = 'Bilibili 视频',
|
|
40
|
+
page = 1,
|
|
41
|
+
start = 0,
|
|
42
|
+
highQuality = true,
|
|
43
|
+
autoplay = false,
|
|
44
|
+
muted = false,
|
|
45
|
+
aspectRatio = '16/9'
|
|
46
|
+
} = Astro.props;
|
|
47
|
+
|
|
48
|
+
// 从 URL 中提取视频信息
|
|
49
|
+
function extractVideoInfo(videoUrl: string): { bvid?: string; aid?: string } {
|
|
50
|
+
// BV 号匹配
|
|
51
|
+
const bvMatch = videoUrl.match(/BV[a-zA-Z0-9]+/);
|
|
52
|
+
if (bvMatch) return { bvid: bvMatch[0] };
|
|
53
|
+
|
|
54
|
+
// AV 号匹配
|
|
55
|
+
const avMatch = videoUrl.match(/av(\d+)/i);
|
|
56
|
+
if (avMatch) return { aid: avMatch[1] };
|
|
57
|
+
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let videoBvid = bvid;
|
|
62
|
+
let videoAid = aid?.toString();
|
|
63
|
+
|
|
64
|
+
if (url) {
|
|
65
|
+
const info = extractVideoInfo(url);
|
|
66
|
+
videoBvid = videoBvid || info.bvid;
|
|
67
|
+
videoAid = videoAid || info.aid;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 构建嵌入 URL
|
|
71
|
+
let embedUrl = 'https://player.bilibili.com/player.html?';
|
|
72
|
+
const params = new URLSearchParams();
|
|
73
|
+
|
|
74
|
+
if (videoBvid) {
|
|
75
|
+
params.set('bvid', videoBvid);
|
|
76
|
+
} else if (videoAid) {
|
|
77
|
+
params.set('aid', videoAid);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
params.set('page', page.toString());
|
|
81
|
+
if (start > 0) params.set('t', start.toString());
|
|
82
|
+
if (highQuality) params.set('high_quality', '1');
|
|
83
|
+
if (autoplay) params.set('autoplay', '1');
|
|
84
|
+
if (muted) params.set('muted', '1');
|
|
85
|
+
params.set('danmaku', '0'); // 默认关闭弹幕
|
|
86
|
+
|
|
87
|
+
embedUrl += params.toString();
|
|
88
|
+
|
|
89
|
+
const hasValidId = videoBvid || videoAid;
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
{hasValidId ? (
|
|
93
|
+
<div class="video-container bilibili-video" style={`aspect-ratio: ${aspectRatio};`}>
|
|
94
|
+
<iframe
|
|
95
|
+
src={embedUrl}
|
|
96
|
+
title={title}
|
|
97
|
+
frameborder="0"
|
|
98
|
+
allowfullscreen
|
|
99
|
+
loading="lazy"
|
|
100
|
+
scrolling="no"
|
|
101
|
+
sandbox="allow-scripts allow-same-origin allow-popups"
|
|
102
|
+
></iframe>
|
|
103
|
+
<div class="video-platform-badge bilibili">
|
|
104
|
+
<svg viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4">
|
|
105
|
+
<path d="M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373z"/>
|
|
106
|
+
</svg>
|
|
107
|
+
<span>Bilibili</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
) : (
|
|
111
|
+
<div class="video-error">
|
|
112
|
+
<p>无效的 Bilibili 视频链接</p>
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* 内嵌幻灯片组件
|
|
4
|
+
*
|
|
5
|
+
* 使用方法:
|
|
6
|
+
*
|
|
7
|
+
* 1. 通过 iframe 嵌入已有幻灯片:
|
|
8
|
+
* <Slides src="/slides/demo" />
|
|
9
|
+
* <Slides src="/slides/demo" height="500px" />
|
|
10
|
+
*
|
|
11
|
+
* 2. 直接内嵌 Markdown 内容:
|
|
12
|
+
* <Slides>
|
|
13
|
+
* # 第一页
|
|
14
|
+
*
|
|
15
|
+
* 内容...
|
|
16
|
+
*
|
|
17
|
+
* ---
|
|
18
|
+
*
|
|
19
|
+
* # 第二页
|
|
20
|
+
*
|
|
21
|
+
* 更多内容...
|
|
22
|
+
* </Slides>
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
/** 幻灯片 URL(使用 iframe 嵌入模式) */
|
|
27
|
+
src?: string;
|
|
28
|
+
/** 容器高度 */
|
|
29
|
+
height?: string;
|
|
30
|
+
/** 宽高比(当不指定 height 时使用) */
|
|
31
|
+
aspectRatio?: string;
|
|
32
|
+
/** Reveal.js 主题 */
|
|
33
|
+
theme?: string;
|
|
34
|
+
/** 过渡效果 */
|
|
35
|
+
transition?: string;
|
|
36
|
+
/** 显示控制箭头 */
|
|
37
|
+
controls?: boolean;
|
|
38
|
+
/** 显示进度条 */
|
|
39
|
+
progress?: boolean;
|
|
40
|
+
/** 内容居中 */
|
|
41
|
+
center?: boolean;
|
|
42
|
+
/** 显示页码 */
|
|
43
|
+
slideNumber?: boolean;
|
|
44
|
+
/** 是否嵌入模式(隐藏部分 UI) */
|
|
45
|
+
embedded?: boolean;
|
|
46
|
+
/** 标题(用于无障碍) */
|
|
47
|
+
title?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
src,
|
|
52
|
+
height,
|
|
53
|
+
aspectRatio = '16/9',
|
|
54
|
+
theme = 'black',
|
|
55
|
+
transition = 'slide',
|
|
56
|
+
controls = true,
|
|
57
|
+
progress = true,
|
|
58
|
+
center = true,
|
|
59
|
+
slideNumber = false,
|
|
60
|
+
embedded = true,
|
|
61
|
+
title = '幻灯片演示'
|
|
62
|
+
} = Astro.props;
|
|
63
|
+
|
|
64
|
+
// 生成唯一 ID
|
|
65
|
+
const slidesId = `slides-${Math.random().toString(36).substr(2, 9)}`;
|
|
66
|
+
|
|
67
|
+
// Reveal.js 配置
|
|
68
|
+
const revealConfig = JSON.stringify({
|
|
69
|
+
hash: false,
|
|
70
|
+
controls,
|
|
71
|
+
progress,
|
|
72
|
+
center,
|
|
73
|
+
transition,
|
|
74
|
+
slideNumber,
|
|
75
|
+
embedded,
|
|
76
|
+
keyboard: true,
|
|
77
|
+
overview: true,
|
|
78
|
+
touch: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// 获取 slot 内容
|
|
82
|
+
const slotContent = await Astro.slots.render('default');
|
|
83
|
+
const hasInlineContent = slotContent && slotContent.trim().length > 0;
|
|
84
|
+
|
|
85
|
+
// 解析 Markdown 为幻灯片结构
|
|
86
|
+
function parseSlides(markdown: string): string[][] {
|
|
87
|
+
// 移除 HTML 注释和空白
|
|
88
|
+
const cleanMarkdown = markdown
|
|
89
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
90
|
+
.trim();
|
|
91
|
+
|
|
92
|
+
// 按水平分隔符分割
|
|
93
|
+
const horizontalSlides = cleanMarkdown.split(/\n---\n/);
|
|
94
|
+
|
|
95
|
+
return horizontalSlides.map((hSlide) => {
|
|
96
|
+
// 按垂直分隔符分割
|
|
97
|
+
return hSlide.split(/\n----\n/).map(s => s.trim());
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const slidesData = hasInlineContent ? parseSlides(slotContent) : [];
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
{src ? (
|
|
105
|
+
<!-- iframe 嵌入模式 -->
|
|
106
|
+
<div
|
|
107
|
+
class="slides-embed-container"
|
|
108
|
+
style={height ? `height: ${height};` : `aspect-ratio: ${aspectRatio};`}
|
|
109
|
+
>
|
|
110
|
+
<iframe
|
|
111
|
+
src={`${src}${src.includes('?') ? '&' : '?'}embed=true`}
|
|
112
|
+
title={title}
|
|
113
|
+
class="slides-iframe"
|
|
114
|
+
allowfullscreen
|
|
115
|
+
loading="lazy"
|
|
116
|
+
></iframe>
|
|
117
|
+
<a
|
|
118
|
+
href={src}
|
|
119
|
+
target="_blank"
|
|
120
|
+
class="slides-fullscreen-btn"
|
|
121
|
+
title="在新窗口打开"
|
|
122
|
+
>
|
|
123
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
124
|
+
<polyline points="15 3 21 3 21 9"></polyline>
|
|
125
|
+
<polyline points="9 21 3 21 3 15"></polyline>
|
|
126
|
+
<line x1="21" y1="3" x2="14" y2="10"></line>
|
|
127
|
+
<line x1="3" y1="21" x2="10" y2="14"></line>
|
|
128
|
+
</svg>
|
|
129
|
+
</a>
|
|
130
|
+
</div>
|
|
131
|
+
) : hasInlineContent ? (
|
|
132
|
+
<!-- 内联内容模式 -->
|
|
133
|
+
<div
|
|
134
|
+
class="slides-inline-container"
|
|
135
|
+
style={height ? `height: ${height};` : `aspect-ratio: ${aspectRatio};`}
|
|
136
|
+
>
|
|
137
|
+
<div class="reveal" id={slidesId}>
|
|
138
|
+
<div class="slides">
|
|
139
|
+
{slidesData.map((verticalSlides) => (
|
|
140
|
+
<section>
|
|
141
|
+
{verticalSlides.map((slideContent) => (
|
|
142
|
+
<section data-markdown>
|
|
143
|
+
<textarea data-template set:text={slideContent}></textarea>
|
|
144
|
+
</section>
|
|
145
|
+
))}
|
|
146
|
+
</section>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<!-- 全屏按钮 -->
|
|
152
|
+
<button
|
|
153
|
+
class="slides-fullscreen-btn"
|
|
154
|
+
title="全屏"
|
|
155
|
+
onclick={`document.getElementById('${slidesId}').requestFullscreen()`}
|
|
156
|
+
>
|
|
157
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
158
|
+
<polyline points="15 3 21 3 21 9"></polyline>
|
|
159
|
+
<polyline points="9 21 3 21 3 15"></polyline>
|
|
160
|
+
<line x1="21" y1="3" x2="14" y2="10"></line>
|
|
161
|
+
<line x1="3" y1="21" x2="10" y2="14"></line>
|
|
162
|
+
</svg>
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<!-- Reveal.js 资源 -->
|
|
167
|
+
<link rel="stylesheet" href="/slides/reveal.css" />
|
|
168
|
+
<link rel="stylesheet" href={`/slides/theme/${theme}.css`} />
|
|
169
|
+
<link rel="stylesheet" href="/slides/plugin/highlight/monokai.css" />
|
|
170
|
+
|
|
171
|
+
<script is:inline src="/slides/reveal.js"></script>
|
|
172
|
+
<script is:inline src="/slides/plugin/markdown/markdown.js"></script>
|
|
173
|
+
<script is:inline src="/slides/plugin/highlight/highlight.js"></script>
|
|
174
|
+
|
|
175
|
+
<script is:inline define:vars={{ slidesId, revealConfig }}>
|
|
176
|
+
(function() {
|
|
177
|
+
function initSlides() {
|
|
178
|
+
const element = document.getElementById(slidesId);
|
|
179
|
+
if (!element || element.dataset.initialized) return;
|
|
180
|
+
|
|
181
|
+
const config = JSON.parse(revealConfig);
|
|
182
|
+
|
|
183
|
+
// 使用 Reveal 构造函数创建独立实例
|
|
184
|
+
const deck = new Reveal(element, {
|
|
185
|
+
hash: config.hash,
|
|
186
|
+
controls: config.controls,
|
|
187
|
+
progress: config.progress,
|
|
188
|
+
center: config.center,
|
|
189
|
+
transition: config.transition,
|
|
190
|
+
slideNumber: config.slideNumber,
|
|
191
|
+
embedded: config.embedded,
|
|
192
|
+
keyboard: config.keyboard,
|
|
193
|
+
overview: config.overview,
|
|
194
|
+
touch: config.touch,
|
|
195
|
+
plugins: [RevealMarkdown, RevealHighlight]
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
deck.initialize();
|
|
199
|
+
element.dataset.initialized = 'true';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (document.readyState === 'loading') {
|
|
203
|
+
document.addEventListener('DOMContentLoaded', initSlides);
|
|
204
|
+
} else {
|
|
205
|
+
// 延迟初始化确保 Reveal.js 已加载
|
|
206
|
+
setTimeout(initSlides, 100);
|
|
207
|
+
}
|
|
208
|
+
})();
|
|
209
|
+
</script>
|
|
210
|
+
) : (
|
|
211
|
+
<!-- 无内容提示 -->
|
|
212
|
+
<div class="slides-empty">
|
|
213
|
+
<p>请提供 <code>src</code> 属性或内联 Markdown 内容</p>
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
<style>
|
|
218
|
+
/* 嵌入容器 */
|
|
219
|
+
.slides-embed-container,
|
|
220
|
+
.slides-inline-container {
|
|
221
|
+
position: relative;
|
|
222
|
+
width: 100%;
|
|
223
|
+
border-radius: 0.5rem;
|
|
224
|
+
overflow: hidden;
|
|
225
|
+
background: #000;
|
|
226
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* iframe 样式 */
|
|
230
|
+
.slides-iframe {
|
|
231
|
+
width: 100%;
|
|
232
|
+
height: 100%;
|
|
233
|
+
border: none;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/* 内联 Reveal 容器 */
|
|
237
|
+
.slides-inline-container .reveal {
|
|
238
|
+
width: 100%;
|
|
239
|
+
height: 100%;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* 全屏按钮 */
|
|
243
|
+
.slides-fullscreen-btn {
|
|
244
|
+
position: absolute;
|
|
245
|
+
top: 10px;
|
|
246
|
+
right: 10px;
|
|
247
|
+
z-index: 30;
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
justify-content: center;
|
|
251
|
+
width: 32px;
|
|
252
|
+
height: 32px;
|
|
253
|
+
background: rgba(0, 0, 0, 0.5);
|
|
254
|
+
border: none;
|
|
255
|
+
border-radius: 4px;
|
|
256
|
+
color: white;
|
|
257
|
+
cursor: pointer;
|
|
258
|
+
opacity: 0;
|
|
259
|
+
transition: opacity 0.3s;
|
|
260
|
+
text-decoration: none;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.slides-embed-container:hover .slides-fullscreen-btn,
|
|
264
|
+
.slides-inline-container:hover .slides-fullscreen-btn {
|
|
265
|
+
opacity: 0.8;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.slides-fullscreen-btn:hover {
|
|
269
|
+
opacity: 1 !important;
|
|
270
|
+
background: rgba(0, 0, 0, 0.7);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* 空内容提示 */
|
|
274
|
+
.slides-empty {
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
padding: 2rem;
|
|
279
|
+
background: #f3f4f6;
|
|
280
|
+
border-radius: 0.5rem;
|
|
281
|
+
color: #6b7280;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.slides-empty code {
|
|
285
|
+
padding: 0.25rem 0.5rem;
|
|
286
|
+
background: #e5e7eb;
|
|
287
|
+
border-radius: 0.25rem;
|
|
288
|
+
font-size: 0.875rem;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
:global(.dark) .slides-empty {
|
|
292
|
+
background: #1f2937;
|
|
293
|
+
color: #9ca3af;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
:global(.dark) .slides-empty code {
|
|
297
|
+
background: #374151;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* 内联模式下的 Reveal.js 样式覆盖 */
|
|
301
|
+
.slides-inline-container .reveal .slides {
|
|
302
|
+
text-align: left;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.slides-inline-container .reveal .slides section {
|
|
306
|
+
padding: 20px;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* 全屏模式样式 */
|
|
310
|
+
.slides-inline-container .reveal:fullscreen {
|
|
311
|
+
background: #000;
|
|
312
|
+
}
|
|
313
|
+
</style>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* 自托管视频组件
|
|
4
|
+
*
|
|
5
|
+
* 使用方法:
|
|
6
|
+
* <Video src="/videos/demo.mp4" />
|
|
7
|
+
* <Video src="/videos/demo.mp4" poster="/images/poster.jpg" />
|
|
8
|
+
* <Video src="/videos/demo.webm" type="video/webm" />
|
|
9
|
+
* <Video src="/videos/demo.mp4" autoplay muted loop />
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
/** 视频源 URL */
|
|
14
|
+
src: string;
|
|
15
|
+
/** 视频类型 (如 video/mp4, video/webm) */
|
|
16
|
+
type?: string;
|
|
17
|
+
/** 封面图片 */
|
|
18
|
+
poster?: string;
|
|
19
|
+
/** 视频标题 */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** 是否自动播放 */
|
|
22
|
+
autoplay?: boolean;
|
|
23
|
+
/** 是否静音 */
|
|
24
|
+
muted?: boolean;
|
|
25
|
+
/** 是否循环播放 */
|
|
26
|
+
loop?: boolean;
|
|
27
|
+
/** 是否显示控制栏 */
|
|
28
|
+
controls?: boolean;
|
|
29
|
+
/** 预加载策略: none, metadata, auto */
|
|
30
|
+
preload?: 'none' | 'metadata' | 'auto';
|
|
31
|
+
/** 自定义宽高比 (默认 16/9) */
|
|
32
|
+
aspectRatio?: string;
|
|
33
|
+
/** 备用视频源列表 */
|
|
34
|
+
sources?: Array<{ src: string; type: string }>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
src,
|
|
39
|
+
type,
|
|
40
|
+
poster,
|
|
41
|
+
title = '视频',
|
|
42
|
+
autoplay = false,
|
|
43
|
+
muted = false,
|
|
44
|
+
loop = false,
|
|
45
|
+
controls = true,
|
|
46
|
+
preload = 'metadata',
|
|
47
|
+
aspectRatio = '16/9',
|
|
48
|
+
sources = []
|
|
49
|
+
} = Astro.props;
|
|
50
|
+
|
|
51
|
+
// 自动检测视频类型
|
|
52
|
+
function getVideoType(videoSrc: string): string {
|
|
53
|
+
const ext = videoSrc.split('.').pop()?.toLowerCase();
|
|
54
|
+
const typeMap: Record<string, string> = {
|
|
55
|
+
mp4: 'video/mp4',
|
|
56
|
+
webm: 'video/webm',
|
|
57
|
+
ogg: 'video/ogg',
|
|
58
|
+
ogv: 'video/ogg',
|
|
59
|
+
mov: 'video/quicktime',
|
|
60
|
+
avi: 'video/x-msvideo',
|
|
61
|
+
mkv: 'video/x-matroska'
|
|
62
|
+
};
|
|
63
|
+
return typeMap[ext || ''] || 'video/mp4';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const videoType = type || getVideoType(src);
|
|
67
|
+
|
|
68
|
+
// 合并所有视频源
|
|
69
|
+
const allSources = [
|
|
70
|
+
{ src, type: videoType },
|
|
71
|
+
...sources
|
|
72
|
+
];
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
<div class="video-container self-hosted-video" style={`aspect-ratio: ${aspectRatio};`}>
|
|
76
|
+
<video
|
|
77
|
+
{title}
|
|
78
|
+
{poster}
|
|
79
|
+
{autoplay}
|
|
80
|
+
{muted}
|
|
81
|
+
{loop}
|
|
82
|
+
{controls}
|
|
83
|
+
{preload}
|
|
84
|
+
playsinline
|
|
85
|
+
class="w-full h-full object-contain bg-black"
|
|
86
|
+
>
|
|
87
|
+
{allSources.map(source => (
|
|
88
|
+
<source src={source.src} type={source.type} />
|
|
89
|
+
))}
|
|
90
|
+
<p class="video-fallback">
|
|
91
|
+
您的浏览器不支持 HTML5 视频。
|
|
92
|
+
<a href={src} download>点击下载视频</a>
|
|
93
|
+
</p>
|
|
94
|
+
</video>
|
|
95
|
+
<div class="video-platform-badge self-hosted">
|
|
96
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
|
97
|
+
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
|
98
|
+
</svg>
|
|
99
|
+
<span>Video</span>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<style>
|
|
104
|
+
.self-hosted-video video {
|
|
105
|
+
border-radius: 0.5rem;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.self-hosted-video video::-webkit-media-controls-panel {
|
|
109
|
+
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* 统一视频播放器组件 - 自动检测视频平台
|
|
4
|
+
*
|
|
5
|
+
* 使用方法:
|
|
6
|
+
* <VideoPlayer src="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />
|
|
7
|
+
* <VideoPlayer src="https://www.bilibili.com/video/BV1xx411c7mD" />
|
|
8
|
+
* <VideoPlayer src="/videos/demo.mp4" />
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import YouTube from './YouTube.astro';
|
|
12
|
+
import Bilibili from './Bilibili.astro';
|
|
13
|
+
import Video from './Video.astro';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
/** 视频源 URL */
|
|
17
|
+
src: string;
|
|
18
|
+
/** 视频标题 */
|
|
19
|
+
title?: string;
|
|
20
|
+
/** 自定义宽高比 */
|
|
21
|
+
aspectRatio?: string;
|
|
22
|
+
/** 是否自动播放 */
|
|
23
|
+
autoplay?: boolean;
|
|
24
|
+
/** 是否静音 */
|
|
25
|
+
muted?: boolean;
|
|
26
|
+
/** 是否循环播放 */
|
|
27
|
+
loop?: boolean;
|
|
28
|
+
/** 封面图片 (仅自托管视频) */
|
|
29
|
+
poster?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
src,
|
|
34
|
+
title,
|
|
35
|
+
aspectRatio = '16/9',
|
|
36
|
+
autoplay = false,
|
|
37
|
+
muted = false,
|
|
38
|
+
loop = false,
|
|
39
|
+
poster
|
|
40
|
+
} = Astro.props;
|
|
41
|
+
|
|
42
|
+
// 检测视频平台
|
|
43
|
+
type Platform = 'youtube' | 'bilibili' | 'self-hosted';
|
|
44
|
+
|
|
45
|
+
function detectPlatform(url: string): Platform {
|
|
46
|
+
if (/youtube\.com|youtu\.be/.test(url)) {
|
|
47
|
+
return 'youtube';
|
|
48
|
+
}
|
|
49
|
+
if (/bilibili\.com|b23\.tv/.test(url)) {
|
|
50
|
+
return 'bilibili';
|
|
51
|
+
}
|
|
52
|
+
return 'self-hosted';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const platform = detectPlatform(src);
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
{platform === 'youtube' && (
|
|
59
|
+
<YouTube
|
|
60
|
+
url={src}
|
|
61
|
+
title={title}
|
|
62
|
+
aspectRatio={aspectRatio}
|
|
63
|
+
autoplay={autoplay}
|
|
64
|
+
muted={muted}
|
|
65
|
+
loop={loop}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{platform === 'bilibili' && (
|
|
70
|
+
<Bilibili
|
|
71
|
+
url={src}
|
|
72
|
+
title={title}
|
|
73
|
+
aspectRatio={aspectRatio}
|
|
74
|
+
autoplay={autoplay}
|
|
75
|
+
muted={muted}
|
|
76
|
+
/>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
{platform === 'self-hosted' && (
|
|
80
|
+
<Video
|
|
81
|
+
src={src}
|
|
82
|
+
title={title}
|
|
83
|
+
aspectRatio={aspectRatio}
|
|
84
|
+
autoplay={autoplay}
|
|
85
|
+
muted={muted}
|
|
86
|
+
loop={loop}
|
|
87
|
+
poster={poster}
|
|
88
|
+
/>
|
|
89
|
+
)}
|