feat: 定期检查文档更新并弹出提醒

- 扩展基础主题代码做抽取
This commit is contained in:
ZiuChen 2023-11-17 00:33:46 +08:00
parent f55842e2ac
commit 9c60e54509
9 changed files with 1280 additions and 35 deletions

View 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>

View 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)
}
}

View File

@ -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

View 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
}

View 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
View 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()]
}))

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"]
} }