feat(media): hidden 标签过滤与指南 public/media 约定(T5 补)
- 首页/归档/RSS(zh+en)集合查询统一 draft+hidden 双过滤——样张
永久保留为可访问不列出的活样例(回归测试资产)
- 写作指南视音频段更正:raw HTML 相对路径不走资产管线(实测),
媒体放 public/media/{slug}/ 用绝对 /media/ 引用,与仓外 store
同命名空间
验证: pnpm astro build exit 0(hidden 过滤后样张退出列表与 RSS)
Spec: 2026-10-25_add-media-and-polish
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
dcc20e9fb3
commit
71a799bd97
9 files changed
+82
-18
No files matched your search
+14
-11
@@ -28,24 +28,27 @@ content/posts/
|
|||||||
- 图注可选;写法固定为 `*…*` 单独成段,紧跟图片
|
- 图注可选;写法固定为 `*…*` 单独成段,紧跟图片
|
||||||
- 压缩纪律:入仓前长边 ≤2400px(可跑 `python3 scripts/photos_ingest.py`,对 posts 资产同样有效的思路,图片请自行用同规格压缩)
|
- 压缩纪律:入仓前长边 ≤2400px(可跑 `python3 scripts/photos_ingest.py`,对 posts 资产同样有效的思路,图片请自行用同规格压缩)
|
||||||
|
|
||||||
## 视频
|
## 视频 / 音频
|
||||||
|
|
||||||
markdown 原生不认视频,直接写 HTML 标签(Astro 透传):
|
markdown 原生不认视音频,写 HTML 标签;**raw HTML 的相对路径不走 Astro
|
||||||
|
资产管线**(只有 `![]()` 图片语法走),所以媒体文件放 **`public/media/{slug}/`**
|
||||||
|
(构建原样拷入 dist),引用用绝对路径 `/media/...`:
|
||||||
|
|
||||||
|
```
|
||||||
|
public/media/my-post/clip.mp4 ← 文件放这里
|
||||||
|
```
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<video controls preload="metadata" src="./assets/my-post/clip.mp4"></video>
|
<video controls preload="metadata" src="/media/my-post/clip.mp4"></video>
|
||||||
*片头三秒试拍*
|
*片头三秒试拍*
|
||||||
|
|
||||||
|
<audio controls preload="metadata" src="/media/my-post/voice.mp3"></audio>
|
||||||
|
*朗读:面纱选段*
|
||||||
```
|
```
|
||||||
|
|
||||||
- `preload="metadata"` 必写(不预拖流量,只取时长/首帧)
|
- `preload="metadata"` 必写(不预拖流量,只取时长/首帧)
|
||||||
- 样式已全局处理(全宽圆角黑底)
|
- 样式已全局处理(视频全宽圆角黑底、音频胶囊条)
|
||||||
|
- /media/ 命名空间同时覆盖"仓内小文件"与"仓外 store 大文件"(见体积纪律表)
|
||||||
## 音频
|
|
||||||
|
|
||||||
```html
|
|
||||||
<audio controls preload="metadata" src="./assets/my-post/voice.mp3"></audio>
|
|
||||||
*朗读:面纱选段*
|
|
||||||
```
|
|
||||||
|
|
||||||
## 体积纪律(git 仓红线)
|
## 体积纪律(git 仓红线)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,65 @@ const { pageKey, pageTitle } = Astro.props;
|
|||||||
|
|
||||||
<div id="artalk-container" data-page-key={pageKey} data-page-title={pageTitle}></div>
|
<div id="artalk-container" data-page-key={pageKey} data-page-title={pageTitle}></div>
|
||||||
|
|
||||||
|
<!-- Artalk 主题接管:--at-color-* 全套映射到站点 tokens(值均为 var(),明暗随
|
||||||
|
html[data-theme] 翻转 tokens 自动生效,一套规则双态成立)。
|
||||||
|
依据:Artalk.css(v2.10) 全部表面由这 18 个变量驱动;init 锁 darkMode:false
|
||||||
|
防 Artalk 自带 atk-dark-mode 类与接管层打架。弹层挂 body 下需单独同款映射 -->
|
||||||
|
<style is:global>
|
||||||
|
#artalk-container,
|
||||||
|
.atk-layer-wrap {
|
||||||
|
--at-color-bg: var(--bg);
|
||||||
|
--at-color-bg-transl: color-mix(in oklab, var(--bg) 95%, transparent);
|
||||||
|
--at-color-bg-grey: var(--bg-soft);
|
||||||
|
--at-color-bg-grey-transl: color-mix(in oklab, var(--bg-soft) 95%, transparent);
|
||||||
|
--at-color-bg-light: var(--accent-soft);
|
||||||
|
--at-color-border: var(--line);
|
||||||
|
--at-color-font: var(--ink-2);
|
||||||
|
--at-color-deep: var(--ink-1);
|
||||||
|
--at-color-sub: var(--ink-3);
|
||||||
|
--at-color-grey: var(--ink-3);
|
||||||
|
--at-color-meta: var(--ink-4);
|
||||||
|
--at-color-light: var(--ink-4);
|
||||||
|
--at-color-main: var(--accent);
|
||||||
|
/* 高度限高渐隐遮罩必须与 bg 同源,否则暗色下露白腿 */
|
||||||
|
--at-color-gradient: linear-gradient(180deg, transparent, var(--bg));
|
||||||
|
/* red/pink/yellow/green 语义功能色保留官方默认:纪律只管气质,不损语义 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 气质对齐(不动排版):字体、头像圆角、时间戳 mono、链接纪律 */
|
||||||
|
#artalk-container {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
#artalk-container .atk-avatar img {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
#artalk-container .atk-meta,
|
||||||
|
#artalk-container .atk-ua {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
#artalk-container .atk-copyright {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--ink-4);
|
||||||
|
}
|
||||||
|
#artalk-container .atk-textarea,
|
||||||
|
#artalk-container .atk-input,
|
||||||
|
#artalk-container .atk-send-btn,
|
||||||
|
#artalk-container .atk-btn {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
#artalk-container .atk-content a {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-color: color-mix(in oklab, var(--accent) 40%, transparent);
|
||||||
|
text-underline-offset: 0.2em;
|
||||||
|
}
|
||||||
|
#artalk-container .atk-content a:hover {
|
||||||
|
text-decoration-color: var(--accent);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const el = document.getElementById('artalk-container');
|
const el = document.getElementById('artalk-container');
|
||||||
if (el) {
|
if (el) {
|
||||||
@@ -30,6 +89,8 @@ const { pageKey, pageTitle } = Astro.props;
|
|||||||
pageKey: el.dataset.pageKey,
|
pageKey: el.dataset.pageKey,
|
||||||
pageTitle: el.dataset.pageTitle,
|
pageTitle: el.dataset.pageTitle,
|
||||||
locale: 'zh-CN',
|
locale: 'zh-CN',
|
||||||
|
// 锁亮皮:明暗由页面 tokens 接管(见上方 --at-* 映射),避免双暗色机制叠加
|
||||||
|
darkMode: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 进入视口 ~200px 前挂载;无 IO 环境直接挂
|
// 进入视口 ~200px 前挂载;无 IO 环境直接挂
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { trackOf } from '../../content.config';
|
|||||||
|
|
||||||
// URL = /archives/{slug},slug 即文件名,与 Halo 旧结构 1:1(不可改)
|
// URL = /archives/{slug},slug 即文件名,与 Halo 旧结构 1:1(不可改)
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = (await getCollection('posts', ({ data }) => !data.draft)).sort(
|
const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
|
||||||
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
||||||
);
|
);
|
||||||
return posts.map((entry, i) => ({
|
return posts.map((entry, i) => ({
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getCollection } from 'astro:content';
|
|||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
import PostList from '../../components/PostList.astro';
|
import PostList from '../../components/PostList.astro';
|
||||||
|
|
||||||
const posts = (await getCollection('posts', ({ data }) => !data.draft)).sort(
|
const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
|
||||||
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
||||||
);
|
);
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import AiTranslatedBadge from '../../../components/AiTranslatedBadge.astro';
|
|||||||
import { t } from '../../../i18n/ui';
|
import { t } from '../../../i18n/ui';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const zh = (await getCollection('posts', ({ data }) => !data.draft)).sort(
|
const zh = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
|
||||||
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
||||||
);
|
);
|
||||||
const en = new Map((await getCollection('postsEn')).map((e) => [e.id, e]));
|
const en = new Map((await getCollection('postsEn')).map((e) => [e.id, e]));
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ProjectWall from '../../components/geek/ProjectWall.astro';
|
|||||||
import Heatmap from '../../components/geek/Heatmap.astro';
|
import Heatmap from '../../components/geek/Heatmap.astro';
|
||||||
import { t } from '../../i18n/ui';
|
import { t } from '../../i18n/ui';
|
||||||
|
|
||||||
const posts = (await getCollection('posts', ({ data }) => !data.draft)).sort(
|
const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
|
||||||
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
||||||
);
|
);
|
||||||
// 列表标题吃 postsEn 译题(缺译回退 zh,与 /en/archives 同策略)
|
// 列表标题吃 postsEn 译题(缺译回退 zh,与 /en/archives 同策略)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { APIContext } from 'astro';
|
|||||||
// 英文 RSS:postsEn 全量;标题缺译时回退 zh 标题(与 /en/ 页同策略)
|
// 英文 RSS:postsEn 全量;标题缺译时回退 zh 标题(与 /en/ 页同策略)
|
||||||
export async function GET(context: APIContext) {
|
export async function GET(context: APIContext) {
|
||||||
const en = new Map((await getCollection('postsEn')).map((e) => [e.id, e]));
|
const en = new Map((await getCollection('postsEn')).map((e) => [e.id, e]));
|
||||||
const posts = (await getCollection('posts', ({ data }) => !data.draft)).sort(
|
const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
|
||||||
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
|
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
|
||||||
);
|
);
|
||||||
return rss({
|
return rss({
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import ProjectWall from '../components/geek/ProjectWall.astro';
|
|||||||
import Heatmap from '../components/geek/Heatmap.astro';
|
import Heatmap from '../components/geek/Heatmap.astro';
|
||||||
import { t } from '../i18n/ui';
|
import { t } from '../i18n/ui';
|
||||||
|
|
||||||
const posts = (await getCollection('posts', ({ data }) => !data.draft)).sort(
|
const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden'))).sort(
|
||||||
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
(a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime(),
|
||||||
);
|
);
|
||||||
// 最新影像取集合前 4 张;照片时间序由照片管线(exif 车道)治理,此处只裁切
|
// 最新影像取集合前 4 张;照片时间序由照片管线(exif 车道)治理,此处只裁切
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { APIContext } from 'astro';
|
|||||||
|
|
||||||
// RSS:posts 集合全量(draft 排除);Phase 3 起双语各出一路(/rss.xml 与 /en/rss.xml)
|
// RSS:posts 集合全量(draft 排除);Phase 3 起双语各出一路(/rss.xml 与 /en/rss.xml)
|
||||||
export async function GET(context: APIContext) {
|
export async function GET(context: APIContext) {
|
||||||
const posts = (await getCollection('posts', ({ data }) => !data.draft))
|
const posts = (await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden')))
|
||||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||||
return rss({
|
return rss({
|
||||||
title: 'wait4x',
|
title: 'wait4x',
|
||||||
|
|||||||
Reference in new issue
Block a user