Files
ONE/frontend/src/customCss.js
T
Sakurasan fbda293072 余白 UI:按设计稿模块化还原 + 站主自定义 CSS + 详情 neighbors + 静态缓存头
前端(新增 ui/yohaku/,各视图按 ui_id=vivid 分支渲染;classic 与后台不受影响)
- 令牌层:设计稿数值原样落地,28 个 --v-* 为站主接口(自定义 CSS 可覆盖,
  放 @layer 让出优先级);另加防线,中和 styles.css 裸选择器
  (h1 衬线 / body 行高 / 后台 .stat/.toolbar/.chip 卡片)漏进余白元素
- 顶栏(滚动收缩态)/ 阅读进度(顶部 2px + READ n%)/ 页脚
- 首页:hero(统计条)/ 筛选(类型分段 + 标签 chips + 搜索)/
  长文卡片与短文纸条 / 分页 / 作品展示区(3 件 + 查看所有,参照 diygod.cc);
  面板栅格改 1.6fr 1fr 1fr 且高度自适应内容(标签云明显大一圈)
- 文章页:三栏「纸」—— 左时间线(前后各两篇)/ 中纸张 / 右目录与阅读进度;
  短文详情不显示标题(h1 视觉隐藏保留无障碍)
- 全部规则收敛在 html[data-ui='vivid'] 前缀下;无 !important

后端
- GET /api/posts/:slug 增 neighbors(前后各两篇,详情页侧栏时间线用)
- SPA 缓存头:index.html no-cache,assets/ 带 hash 长期 immutable
  (此前无缓存头,浏览器会一直拿旧构建)

其他
- 站主自定义 CSS 管线(customCss.js + ui/sections.js,按分区注入,仅 vivid)
- 删除旧 vivid 覆盖层实现(VividNav/Hero/Footer/Timeline/Toc、ui/vivid.css)
- 设计稿原型附于仓库根 index.html;作品表清掉外链 diygod.cc 的示例封面,
  改用本地占位图(public/covers/)
2026-09-23 15:37:42 +08:00

81 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { watch } from 'vue'
import { site } from './site'
// 站主自定义 CSS 的注入管线。
//
// 两个 <style>:global 常驻,section 跟着路由换,这样切页面不用重新解析全局那段。
// 只在 vivid 下注入,并且 /admin 下一律清空 —— 站主写坏样式也影响不到后台。
//
// 安全:只写 textContent,绝不用 innerHTML。textContent 不会被 HTML 解析,
// 所以站主的 CSS 里即使写了 </style> 也逃不出去,因此不需要引入 CSS 清洗库。
// (能写这两段的只有管理员,本身也不是权限边界。)
const GLOBAL_ID = 'one-css-global'
const SECTION_ID = 'one-css-section'
// 路由名 → 分区 id。tag 归到 tags;not-found / 其他一律只吃 global。
const ROUTE_SECTION = {
home: 'home',
post: 'post',
archive: 'archive',
tags: 'tags',
tag: 'tags',
projects: 'projects',
about: 'about'
}
function ensureStyle(id) {
if (typeof document === 'undefined') return null
// 先按 id 找再建:dev 热更新会重复执行模块,不查会堆出一串重复 <style>
let el = document.getElementById(id)
if (!el) {
el = document.createElement('style')
el.id = id
document.head.appendChild(el)
}
return el
}
export function sectionForRoute(route) {
if (!route || !route.name) return ''
return ROUTE_SECTION[route.name] || ''
}
function cssFor(section) {
if (!section) return ''
const map = site.custom_css
if (!map || typeof map !== 'object') return ''
const v = map[section]
return typeof v === 'string' ? v : ''
}
export function refreshCustomCss(route) {
const globalEl = ensureStyle(GLOBAL_ID)
const sectionEl = ensureStyle(SECTION_ID)
if (!globalEl || !sectionEl) return
const isAdmin = !!(route && route.path && route.path.startsWith('/admin'))
if (site.ui_id !== 'vivid' || isAdmin) {
// 清空而不是跳过:避免从公开页跳到后台时残留上一页的分区样式
if (globalEl.textContent) globalEl.textContent = ''
if (sectionEl.textContent) sectionEl.textContent = ''
return
}
const globalCss = cssFor('global')
const sectionCss = cssFor(sectionForRoute(route))
if (globalEl.textContent !== globalCss) globalEl.textContent = globalCss
if (sectionEl.textContent !== sectionCss) sectionEl.textContent = sectionCss
}
export function installCustomCss(router) {
refreshCustomCss(router.currentRoute.value)
router.afterEach((to) => refreshCustomCss(to))
// 站主切 UI 时立刻重新注入/清空。保存 CSS 后需要重新加载页面才会生效
// (site 只在启动时取一次),这是当前有意的简化。
watch(
() => site.ui_id,
() => refreshCustomCss(router.currentRoute.value)
)
}