mirror of
https://github.com/ZiuChen/ZiuChen.github.io.git
synced 2025-08-17 23:19:55 +08:00
feat: 定期检查文档更新并弹出提醒
- 扩展基础主题代码做抽取
This commit is contained in:
parent
f55842e2ac
commit
9c60e54509
74
docs/.vitepress/theme/Layout.vue
Normal file
74
docs/.vitepress/theme/Layout.vue
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import { nextTick, provide } from 'vue'
|
||||||
|
import { setupMediumZoom } from './setupMediumZoom'
|
||||||
|
|
||||||
|
const { isDark } = useData()
|
||||||
|
|
||||||
|
const enableTransitions = () =>
|
||||||
|
'startViewTransition' in document &&
|
||||||
|
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
|
||||||
|
|
||||||
|
setupMediumZoom()
|
||||||
|
|
||||||
|
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
|
||||||
|
if (!enableTransitions()) {
|
||||||
|
isDark.value = !isDark.value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const clipPath = [
|
||||||
|
`circle(0px at ${x}px ${y}px)`,
|
||||||
|
`circle(${Math.hypot(
|
||||||
|
Math.max(x, innerWidth - x),
|
||||||
|
Math.max(y, innerHeight - y)
|
||||||
|
)}px at ${x}px ${y}px)`
|
||||||
|
]
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
await document.startViewTransition(async () => {
|
||||||
|
isDark.value = !isDark.value
|
||||||
|
await nextTick()
|
||||||
|
}).ready
|
||||||
|
|
||||||
|
document.documentElement.animate(
|
||||||
|
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
|
||||||
|
{
|
||||||
|
duration: 300,
|
||||||
|
easing: 'ease-in',
|
||||||
|
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DefaultTheme.Layout />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
::view-transition-old(root),
|
||||||
|
::view-transition-new(root) {
|
||||||
|
animation: none;
|
||||||
|
mix-blend-mode: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-old(root),
|
||||||
|
.dark::view-transition-new(root) {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(root),
|
||||||
|
.dark::view-transition-old(root) {
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPSwitchAppearance {
|
||||||
|
width: 22px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPSwitchAppearance .check {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
</style>
|
14
docs/.vitepress/theme/customComponents.ts
Normal file
14
docs/.vitepress/theme/customComponents.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { App } from 'vue'
|
||||||
|
import Title from '../components/Title.vue'
|
||||||
|
import ImgSlider from '../components/ImgSlider.vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量完成自定义组件的注册
|
||||||
|
*/
|
||||||
|
export function customComponents(app: App) {
|
||||||
|
const cpns = [Title, ImgSlider]
|
||||||
|
|
||||||
|
for (const c of cpns) {
|
||||||
|
app.component(c.__name, c)
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,19 @@
|
|||||||
import type { EnhanceAppContext } from 'vitepress'
|
import { Theme } from 'vitepress'
|
||||||
import DefaultTheme from 'vitepress/theme'
|
import DefaultTheme from 'vitepress/theme'
|
||||||
import { nextTick, watch } from 'vue'
|
import { refresh } from './refresh'
|
||||||
import { inBrowser, useRoute } from 'vitepress'
|
import { customComponents } from './customComponents'
|
||||||
import mediumZoom from 'medium-zoom'
|
import Layout from './Layout.vue'
|
||||||
|
|
||||||
import Title from '../components/Title.vue'
|
|
||||||
import ImgSlider from '../components/ImgSlider.vue'
|
|
||||||
|
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|
||||||
export default {
|
const theme: Theme = {
|
||||||
...DefaultTheme,
|
...DefaultTheme,
|
||||||
enhanceApp(ctx: EnhanceAppContext) {
|
Layout,
|
||||||
// 使用默认主题的增强应用程序功能
|
enhanceApp(ctx) {
|
||||||
DefaultTheme.enhanceApp(ctx)
|
DefaultTheme.enhanceApp(ctx)
|
||||||
|
|
||||||
// 注册组件
|
|
||||||
const { app } = ctx
|
const { app } = ctx
|
||||||
app.component('Title', Title)
|
app.use(refresh).use(customComponents)
|
||||||
app.component('ImgSlider', ImgSlider)
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const route = useRoute()
|
|
||||||
watch(
|
|
||||||
() => route.path,
|
|
||||||
() => {
|
|
||||||
nextTick(() =>
|
|
||||||
inBrowser ? mediumZoom('.main img', { background: 'var(--vp-c-bg)' }) : null
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default theme
|
||||||
|
73
docs/.vitepress/theme/refresh.tsx
Normal file
73
docs/.vitepress/theme/refresh.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { Button, notification } from 'ant-design-vue'
|
||||||
|
|
||||||
|
let lastHashmap: any = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定期拉取 hashmap.json 文件
|
||||||
|
* 如果有更新则弹出提醒刷新页面
|
||||||
|
*/
|
||||||
|
export function refresh() {
|
||||||
|
// SSR or SSG 模式不需要检查更新
|
||||||
|
if (import.meta.env.SSR) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每隔 5 分钟检查一次
|
||||||
|
*/
|
||||||
|
async function loop() {
|
||||||
|
setTimeout(() => {
|
||||||
|
loop()
|
||||||
|
}, 1000 * 60 * 5)
|
||||||
|
|
||||||
|
const res = await fetch('/hashmap.json').then((res) => res.json())
|
||||||
|
|
||||||
|
if (!lastHashmap) {
|
||||||
|
lastHashmap = res
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff(lastHashmap, res)) {
|
||||||
|
lastHashmap = res
|
||||||
|
|
||||||
|
const lastCheckUpdate = localStorage.getItem('last-check-update')
|
||||||
|
|
||||||
|
// 一小时内不重复提醒
|
||||||
|
if (lastCheckUpdate && new Date().getTime() - Number(lastCheckUpdate) < 1000 * 60 * 60) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.info({
|
||||||
|
message: '文档有更新',
|
||||||
|
description: '请刷新页面以获取最新文档',
|
||||||
|
btn: (
|
||||||
|
<Button type="primary" onClick={() => window.location.reload()}>
|
||||||
|
刷新页面
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
localStorage.setItem('last-check-update', new Date().getTime().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个对象的差异
|
||||||
|
* 浅层比较
|
||||||
|
*/
|
||||||
|
function diff(o1: any, o2: any) {
|
||||||
|
const keys = Object.keys(o1)
|
||||||
|
const keys2 = Object.keys(o2)
|
||||||
|
if (keys.length !== keys2.length) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for (const key of keys) {
|
||||||
|
if (o1[key] !== o2[key]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
16
docs/.vitepress/theme/setupMediumZoom.ts
Normal file
16
docs/.vitepress/theme/setupMediumZoom.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { nextTick, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vitepress'
|
||||||
|
import mediumZoom from 'medium-zoom'
|
||||||
|
|
||||||
|
export function setupMediumZoom() {
|
||||||
|
if (import.meta.env.SSR) return
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
() => {
|
||||||
|
nextTick(() => mediumZoom('.main img', { background: 'var(--vp-c-bg)' }))
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
}
|
14
docs/vite.config.ts
Normal file
14
docs/vite.config.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
|
|
||||||
|
export default defineConfig(() => ({
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/hashmap.json': {
|
||||||
|
target: 'https://ziuchen.github.io',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [vueJsx()]
|
||||||
|
}))
|
@ -7,6 +7,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.18.5",
|
"@types/node": "^18.18.5",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||||
|
"ant-design-vue": "^4.0.7",
|
||||||
|
"vite": "^5.0.0",
|
||||||
"vue": "^3.3.4"
|
"vue": "^3.3.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
1079
pnpm-lock.yaml
generated
1079
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -10,11 +10,13 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"],
|
||||||
"types": ["node"],
|
"types": ["node", "vite/client"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
"jsxImportSource": "vue",
|
||||||
"baseUrl": "."
|
"baseUrl": "."
|
||||||
},
|
},
|
||||||
"include": ["docs/.vitepress/**/*.ts"],
|
|
||||||
|
"include": ["docs/.vitepress/**/*.ts", "docs/.vitepress/**/*.tsx", "docs/vite.config.ts"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user