Merge pull request #121 from pengzhanbo/RC-79

RC-79
This commit is contained in:
pengzhanbo 2024-07-17 00:51:20 +08:00 committed by GitHub
commit b9aacd68e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 1168 additions and 610 deletions

33
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Linter
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: Install deps
run: pnpm install --frozen-lockfile
- name: Linter
run: |
pnpm run lint
pnpm run lint:css

23
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Add Release Tag
on:
push:
tags:
- v*
jobs:
release:
if: github.repository == 'pengzhanbo/vuepress-theme-plume'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20.x
- run: npx changelogithub
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

View File

@ -20,6 +20,12 @@ __options__ : `PlumeThemeOptions`
查看 [主题配置](/config/basic/) 了解更多。
## `defineThemeConfig(options)`
主题配置帮助函数,用于在单独的 `plume.config.ts` 中使用。
查看 [主题配置文件](../config/配置说明.md#主题配置文件) 了解更多。
## `defineNavbarConfig(options)`
主题导航栏配置函数。

View File

@ -8,12 +8,12 @@ permalink: /guide/layout-slots/
## 概述
主题通过 `<Layout />` 提供了 丰富的 布局插槽,可以通过这些插槽,在 页面 的不同位置注入内容。
主题通过 `<Layout />` `<NotFound />` 提供了 丰富的 布局插槽,可以通过这些插槽,在 页面 的不同位置注入内容。
以便用户可以个性化的使用主题。
## 使用
首先,需要创建一个 客户端配置文件: `.vuepress/client.ts`:
`<Layout />` 为例,首先,需要创建一个 客户端配置文件: `.vuepress/client.ts`:
```ts
import { defineClientConfig } from 'vuepress/client'
@ -69,7 +69,7 @@ export default defineClientConfig({
## 插槽
主题支持以下插槽:
### `<Layout />` 插槽
- 当 `pageLayout: doc` 时:
@ -115,13 +115,24 @@ export default defineClientConfig({
- `blog-archives-before`
- `blog-archives-after`
- 总是启用
- 在 博客分类页 中
- `layout-top`
- `layout-bottom`
- `nav-bar-title-before`
- `nav-bar-title-after`
- `nav-bar-content-before`
- `nav-bar-content-after`
- `nav-screen-content-before`
- `nav-screen-content-after`
- `blog-categories-before`
- `blog-categories-after`
### `<NotFound />` 插槽
- `not-found`
### 通用插槽
以下插槽在 `<Layout />``<NotFound />` 中都支持:
- `layout-top`
- `layout-bottom`
- `nav-bar-title-before`
- `nav-bar-title-after`
- `nav-bar-content-before`
- `nav-bar-content-after`
- `nav-screen-content-before`
- `nav-screen-content-after`

View File

@ -12,7 +12,7 @@
"vuepress": "2.0.0-rc.14"
},
"dependencies": {
"@iconify/json": "^2.2.225",
"@iconify/json": "^2.2.228",
"@vuepress/bundler-vite": "2.0.0-rc.14",
"chart.js": "^4.4.3",
"echarts": "^5.5.1",

View File

@ -41,8 +41,8 @@
"devDependencies": {
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@pengzhanbo/eslint-config-vue": "^1.11.2",
"@pengzhanbo/stylelint-config": "^1.11.2",
"@pengzhanbo/eslint-config-vue": "^1.12.0",
"@pengzhanbo/stylelint-config": "^1.12.0",
"@types/lodash.merge": "^4.6.9",
"@types/node": "20.12.10",
"@types/webpack-env": "^1.18.5",
@ -52,11 +52,11 @@
"conventional-changelog-cli": "^5.0.0",
"cpx2": "^7.0.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.6.0",
"eslint": "^9.7.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.7",
"rimraf": "^5.0.8",
"stylelint": "^16.6.1",
"rimraf": "^6.0.1",
"stylelint": "^16.7.0",
"tsconfig-vuepress": "^4.5.0",
"typescript": "^5.5.3"
},

View File

@ -47,18 +47,18 @@
},
"dependencies": {
"@iconify/utils": "^2.1.25",
"@vuepress/helper": "2.0.0-rc.37",
"@vuepress/helper": "2.0.0-rc.38",
"@vueuse/core": "^10.11.0",
"local-pkg": "^0.5.0",
"markdown-it-container": "^4.0.0",
"nanoid": "^5.0.7",
"shiki": "^1.10.3",
"tm-grammars": "^1.13.7",
"tm-themes": "^1.5.1",
"tm-grammars": "^1.13.11",
"tm-themes": "^1.5.3",
"vue": "^3.4.31"
},
"devDependencies": {
"@iconify/json": "^2.2.225",
"@iconify/json": "^2.2.228",
"@types/markdown-it": "^14.1.1"
},
"publishConfig": {

View File

@ -48,7 +48,9 @@ function highlight() {
}
function updateScroll() {
container && (container.scrollLeft = textAreaEl.value?.scrollLeft || 0)
if (container) {
container.scrollLeft = textAreaEl.value?.scrollLeft || 0
}
}
watch([input], highlight, { flush: 'post' })

View File

@ -11,12 +11,19 @@ const SANDBOX = 'allow-forms allow-modals allow-popups allow-presentation allow-
const source = computed(() => {
const params = new URLSearchParams()
props.filepath && params.set(props.type === 'embed' ? 'module' : 'file', encodeURIComponent(props.filepath))
if (props.filepath) {
params.set(props.type === 'embed' ? 'module' : 'file', encodeURIComponent(props.filepath))
}
if (props.type === 'embed') {
params.set('view', props.layout ? props.layout.replace(/,/g, '+') : 'Editor+Preview')
props.console && params.set('expanddevtools', '1')
props.navbar === false && params.set('hidenavigation', '1')
if (props.console) {
params.set('expanddevtools', '1')
}
if (props.navbar === false) {
params.set('hidenavigation', '1')
}
}
else {
params.set('from-embed', '')

View File

@ -34,9 +34,16 @@ export const codepenPlugin: PluginWithOptions<never> = (md) => {
content: (meta) => {
const { title = 'Codepen', height, width } = meta
const params = new URLSearchParams()
meta.editable && params.set('editable', 'true')
meta.tab && params.set('default-tab', meta.tab)
meta.theme && params.set('theme-id', meta.theme)
if (meta.editable) {
params.set('editable', 'true')
}
if (meta.tab) {
params.set('default-tab', meta.tab)
}
if (meta.theme) {
params.set('theme-id', meta.theme)
}
const middle = meta.preview ? '/embed/preview/' : '/embed/'

View File

@ -130,7 +130,7 @@ async function genIconContent(iconName: string, cb: (content: string) => void) {
iconJson = JSON.parse(await fs.readFile(filename, 'utf-8'))
iconDataCache.set(collect, iconJson)
}
catch (e) {
catch {
logger.warn(`[plugin-md-power] Can not find icon, ${collect} is missing!`)
}
}

View File

@ -33,10 +33,15 @@ export async function langReplPlugin(app: App, md: markdownIt, {
kotlin = false,
rust = false,
}: ReplOptions) {
kotlin && createReplContainer(md, 'kotlin')
go && createReplContainer(md, 'go')
rust && createReplContainer(md, 'rust')
if (kotlin) {
createReplContainer(md, 'kotlin')
}
if (go) {
createReplContainer(md, 'go')
}
if (rust) {
createReplContainer(md, 'rust')
}
theme ??= { light: 'github-light', dark: 'github-dark' }
const data: ReplEditorData = { grammars: {} } as ReplEditorData

View File

@ -41,11 +41,26 @@ export const bilibiliPlugin: PluginWithOptions<never> = (md) => {
content(meta) {
const params = new URLSearchParams()
meta.bvid && params.set('bvid', meta.bvid)
meta.aid && params.set('aid', meta.aid)
meta.cid && params.set('cid', meta.cid)
meta.page && params.set('p', meta.page.toString())
meta.time && params.set('t', meta.time.toString())
if (meta.bvid) {
params.set('bvid', meta.bvid)
}
if (meta.aid) {
params.set('aid', meta.aid)
}
if (meta.cid) {
params.set('cid', meta.cid)
}
if (meta.page) {
params.set('p', meta.page.toString())
}
if (meta.time) {
params.set('t', meta.time.toString())
}
params.set('autoplay', meta.autoplay ? '1' : '0')
const source = `${BILIBILI_LINK}?${params.toString()}`

View File

@ -34,10 +34,21 @@ export const youtubePlugin: PluginWithOptions<never> = (md) => {
content(meta) {
const params = new URLSearchParams()
meta.autoplay && params.set('autoplay', '1')
meta.loop && params.set('loop', '1')
meta.start && params.set('start', meta.start.toString())
meta.end && params.set('end', meta.end.toString())
if (meta.autoplay) {
params.set('autoplay', '1')
}
if (meta.loop) {
params.set('loop', '1')
}
if (meta.start) {
params.set('start', meta.start.toString())
}
if (meta.end) {
params.set('end', meta.end.toString())
}
const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`

View File

@ -32,11 +32,13 @@ export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): P
onInitialized: async () => await initIcon(),
extendsBundlerOptions(bundlerOptions) {
options.repl && addViteOptimizeDepsInclude(
bundlerOptions,
app,
['shiki/core', 'shiki/wasm'],
)
if (options.repl) {
addViteOptimizeDepsInclude(
bundlerOptions,
app,
['shiki/core', 'shiki/wasm'],
)
}
},
extendsMarkdown: async (md: MarkdownIt, app) => {

View File

@ -40,7 +40,7 @@
"vuepress": "2.0.0-rc.14"
},
"dependencies": {
"@vuepress/helper": "2.0.0-rc.37",
"@vuepress/helper": "2.0.0-rc.38",
"@vueuse/core": "^10.11.0",
"@vueuse/integrations": "^10.11.0",
"chokidar": "^3.6.0",

View File

@ -163,7 +163,9 @@ const disableReset = computed(() => {
})
function focusSearchInput(select = true) {
searchInput.value?.focus()
select && searchInput.value?.select()
if (select) {
searchInput.value?.select()
}
}
onMounted(() => {

View File

@ -39,7 +39,7 @@
"@shikijs/transformers": "^1.10.3",
"@shikijs/twoslash": "^1.10.3",
"@types/hast": "^3.0.4",
"@vuepress/helper": "2.0.0-rc.37",
"@vuepress/helper": "2.0.0-rc.38",
"floating-vue": "^5.2.2",
"mdast-util-from-markdown": "^2.0.1",
"mdast-util-gfm": "^3.0.0",

1122
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -74,18 +74,18 @@
"@vuepress-plume/plugin-iconify": "workspace:*",
"@vuepress-plume/plugin-search": "workspace:*",
"@vuepress-plume/plugin-shikiji": "workspace:*",
"@vuepress/helper": "2.0.0-rc.37",
"@vuepress/plugin-active-header-links": "2.0.0-rc.37",
"@vuepress/plugin-comment": "2.0.0-rc.37",
"@vuepress/plugin-docsearch": "2.0.0-rc.37",
"@vuepress/plugin-git": "2.0.0-rc.37",
"@vuepress/helper": "2.0.0-rc.38",
"@vuepress/plugin-active-header-links": "2.0.0-rc.38",
"@vuepress/plugin-comment": "2.0.0-rc.38",
"@vuepress/plugin-docsearch": "2.0.0-rc.38",
"@vuepress/plugin-git": "2.0.0-rc.38",
"@vuepress/plugin-markdown-container": "2.0.0-rc.37",
"@vuepress/plugin-nprogress": "2.0.0-rc.37",
"@vuepress/plugin-photo-swipe": "2.0.0-rc.37",
"@vuepress/plugin-reading-time": "2.0.0-rc.37",
"@vuepress/plugin-seo": "2.0.0-rc.37",
"@vuepress/plugin-sitemap": "2.0.0-rc.37",
"@vuepress/plugin-watermark": "2.0.0-rc.37",
"@vuepress/plugin-nprogress": "2.0.0-rc.38",
"@vuepress/plugin-photo-swipe": "2.0.0-rc.38",
"@vuepress/plugin-reading-time": "2.0.0-rc.38",
"@vuepress/plugin-seo": "2.0.0-rc.38",
"@vuepress/plugin-sitemap": "2.0.0-rc.38",
"@vuepress/plugin-watermark": "2.0.0-rc.38",
"@vueuse/core": "^10.11.0",
"bcrypt-ts": "^5.0.2",
"chokidar": "^3.6.0",
@ -99,7 +99,7 @@
"nanoid": "^5.0.7",
"vue": "^3.4.31",
"vue-router": "^4.4.0",
"vuepress-plugin-md-enhance": "2.0.0-rc.50",
"vuepress-plugin-md-enhance": "2.0.0-rc.51",
"vuepress-plugin-md-power": "workspace:*"
}
}

View File

@ -41,7 +41,7 @@ const { theme, page } = useData()
<slot name="blog-categories-before" />
</template>
<template #blog-categories-after>
<slot name="blog-tags-after" />
<slot name="blog-categories-after" />
</template>
</VPBlogCategories>
<VPPostList v-else>

View File

@ -12,7 +12,7 @@ import VPNavBarTranslations from '@theme/Nav/VPNavBarTranslations.vue'
import { useData } from '../../composables/data.js'
import { useSidebar } from '../../composables/sidebar.js'
defineProps<{
const props = defineProps<{
isScreenOpen: boolean
}>()
defineEmits<(e: 'toggleScreen') => void>()
@ -28,6 +28,7 @@ watchPostEffect(() => {
'has-sidebar': hasSidebar.value,
'home': frontmatter.value.pageLayout === 'home',
'top': y.value === 0,
'screen-open': props.isScreenOpen,
}
})
</script>
@ -83,6 +84,12 @@ watchPostEffect(() => {
transition-property: background-color, color, border-bottom;
}
.vp-navbar.screen-open {
background-color: var(--vp-nav-bg-color);
border-bottom: 1px solid var(--vp-c-divider);
transition: none;
}
.vp-navbar:not(.home) {
background-color: var(--vp-nav-bg-color);
}
@ -240,6 +247,11 @@ watchPostEffect(() => {
margin-right: -8px;
}
.divider {
width: 100%;
height: 1px;
}
@media (min-width: 960px) {
.vp-navbar.has-sidebar .divider {
padding-left: var(--vp-sidebar-width);
@ -252,6 +264,10 @@ watchPostEffect(() => {
}
}
.vp-navbar.screen-open .divider {
display: none;
}
.divider-line {
width: 100%;
height: 1px;

View File

@ -48,8 +48,7 @@ const isLocked = useScrollLock(inBrowser ? document.body : null)
overflow-y: auto;
pointer-events: auto;
background-color: var(--vp-nav-screen-bg-color);
border-top: 1px solid var(--vp-c-divider);
transition: background-color var(--t-color), border-top var(--t-color);
transition: background-color var(--t-color);
}
.container {

View File

@ -40,7 +40,9 @@ const show = computed(() => {
let timer: NodeJS.Timeout | null = null
function resetScrolling() {
timer && clearTimeout(timer)
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
isScrolling.value = false
}, 1000)

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, nextTick, watch } from 'vue'
import { nextTick, watch } from 'vue'
import VPBlog from '@theme/Blog/VPBlog.vue'
import VPDoc from '@theme/VPDoc.vue'
import VPPage from '@theme/VPPage.vue'
@ -7,18 +7,15 @@ import VPHome from '@theme/Home/VPHome.vue'
import VPFriends from '@theme/VPFriends.vue'
import { useData, useSidebar } from '../composables/index.js'
import { inBrowser } from '../utils/index.js'
import { useBlogPageData } from '../composables/page.js'
const props = defineProps<{
isNotFound?: boolean
}>()
const { hasSidebar } = useSidebar()
const { frontmatter, page } = useData()
const isBlogLayout = computed(() => {
const { type } = page.value
return type === 'blog' || type === 'blog-archives' || type === 'blog-tags' || type === 'blog-categories'
})
const { frontmatter } = useData()
const { isBlogLayout } = useBlogPageData()
watch([isBlogLayout, () => frontmatter.value.pageLayout], () => nextTick(() =>
inBrowser && document.documentElement.classList.toggle(
@ -54,6 +51,12 @@ watch([isBlogLayout, () => frontmatter.value.pageLayout], () => nextTick(() =>
<template #blog-tags-after>
<slot name="blog-tags-after" />
</template>
<template #blog-categories-before>
<slot name="blog-categories-before" />
</template>
<template #blog-categories-after>
<slot name="blog-categories-after" />
</template>
<template #blog-post-list-before>
<slot name="blog-post-list-before" />
</template>

View File

@ -10,13 +10,13 @@ import { useEncrypt } from '../composables/encrypt.js'
import { useSidebar } from '../composables/sidebar.js'
import { useData } from '../composables/data.js'
import { useHeaders } from '../composables/outline.js'
import { useBlogPost } from '../composables/page.js'
import { useBlogPageData } from '../composables/page.js'
const { page, theme, frontmatter, isDark } = useData()
const route = useRoute()
const { hasSidebar, hasAside, leftAside } = useSidebar()
const { isBlogPost } = useBlogPost()
const { isBlogPost } = useBlogPageData()
const headers = useHeaders()
const { isPageDecrypted } = useEncrypt()

View File

@ -4,11 +4,11 @@ import { useReadingTimeLocale } from '@vuepress/plugin-reading-time/client'
import VPLink from '@theme/VPLink.vue'
import { useData } from '../composables/data.js'
import { useTagColors } from '../composables/tag-colors.js'
import { useBlogPost } from '../composables/page.js'
import { useBlogPageData } from '../composables/page.js'
import { useBlogExtract } from '../composables/blog-extract.js'
const { page, frontmatter: matter } = useData<'post'>()
const { isBlogPost } = useBlogPost()
const { isBlogPost } = useBlogPageData()
const colors = useTagColors()
const readingTime = useReadingTimeLocale()
const { categories } = useBlogExtract()
@ -53,7 +53,7 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
<span v-if="index !== categoryList.length - 1" class="dot">&rsaquo;</span>
</template>
</div>
<h1 class="vp-doc-title" :class="{ padding: !hasMeta }">
<h1 class="vp-doc-title page-title" :class="{ padding: !hasMeta }">
{{ page.title }}
</h1>
<div v-if="hasMeta" class="vp-doc-meta">

View File

@ -78,7 +78,7 @@ onMounted(() => {
margin-left: var(--vp-sidebar-width);
}
.vp-footer.vp-footer.has-sidebar .container {
.vp-footer.has-sidebar .container {
margin-left: calc(0px - var(--vp-sidebar-width));
}
}
@ -87,11 +87,9 @@ onMounted(() => {
.vp-footer {
padding: 24px;
}
.vp-footer.has-sidebar {
margin-left: calc(
(100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) -
32px
)
margin-left: calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}

View File

@ -5,7 +5,7 @@ import VPLocalNavOutlineDropdown from '@theme/VPLocalNavOutlineDropdown.vue'
import { useSidebar } from '../composables/sidebar.js'
import { useHeaders } from '../composables/outline.js'
import { useData } from '../composables/data.js'
import { useBlogPost } from '../composables/page.js'
import { useBlogPageData } from '../composables/page.js'
const props = defineProps<{
open: boolean
@ -15,7 +15,7 @@ const props = defineProps<{
defineEmits<(e: 'openMenu') => void>()
const { theme } = useData()
const { isBlogPost } = useBlogPost()
const { isBlogPost } = useBlogPageData()
const { hasSidebar } = useSidebar()
const { y } = useWindowScroll()

View File

@ -2,7 +2,7 @@
import { useScrollLock } from '@vueuse/core'
import { onMounted, ref, watch } from 'vue'
import { useRoutePath } from 'vuepress/client'
import VPSidebarItem from '@theme/VPSidebarItem.vue'
import VPSidebarGroup from '@theme/VPSidebarGroup.vue'
import VPTransitionFadeSlideY from '@theme/VPTransitionFadeSlideY.vue'
import { useSidebar } from '../composables/sidebar.js'
import { inBrowser } from '../utils/index.js'
@ -71,13 +71,7 @@ onMounted(() => {
<slot name="sidebar-nav-before" />
<div
v-for="item in sidebarGroups"
:key="item.text"
class="group"
>
<VPSidebarItem :item="item" :depth="0" />
</div>
<VPSidebarGroup :items="sidebarGroups" />
<slot name="sidebar-nav-after" />
</nav>
@ -170,17 +164,4 @@ onMounted(() => {
.nav {
outline: 0;
}
.group + .group {
padding-top: 10px;
border-top: 1px solid var(--vp-c-divider);
transition: border-top var(--t-color);
}
@media (min-width: 960px) {
.group {
width: calc(var(--vp-sidebar-width) - 64px);
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,55 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import type { ResolvedSidebarItem } from '../../shared/resolved/sidebar.js'
import VPSidebarItem from './VPSidebarItem.vue'
defineProps<{
items: ResolvedSidebarItem[]
}>()
const disableTransition = ref(true)
let timer: ReturnType<typeof setTimeout> | null = null
onMounted(() => {
timer = setTimeout(() => {
timer = null
disableTransition.value = false
}, 300)
})
onBeforeUnmount(() => {
if (timer != null) {
clearTimeout(timer)
timer = null
}
})
</script>
<template>
<div
v-for="item in items"
:key="item.text"
class="group"
:class="{ 'no-transition': disableTransition }"
>
<VPSidebarItem :item="item" :depth="0" />
</div>
</template>
<style scoped>
.no-transition :deep(.caret-icon) {
transition: none;
}
.group + .group {
padding-top: 10px;
border-top: 1px solid var(--vp-c-divider);
}
@media (min-width: 960px) {
.group {
width: calc(var(--vp-sidebar-width) - 64px);
padding-top: 10px;
}
}
</style>

View File

@ -47,16 +47,20 @@ function onItemInteraction(e: MouseEvent | Event) {
if ('key' in e && e.key !== 'Enter')
return
!props.item.link && toggle()
if (!props.item.link) {
toggle()
}
}
function onCaretClick() {
props.item.link && toggle()
if (props.item.link) {
toggle()
}
}
</script>
<template>
<Component :is="sectionTag" class="vp-sidebar-item" :class="classes">
<Component :is="sectionTag" class="vp-sidebar-item sidebar-item" :class="classes">
<div
v-if="item.text"
class="item"

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, inject, ref } from 'vue'
import { inject, ref, watchPostEffect } from 'vue'
import VPSwitch from '@theme/VPSwitch.vue'
import { useData } from '../composables/data.js'
@ -10,8 +10,9 @@ const toggleAppearance = inject('toggle-appearance', () => {
isDark.value = !isDark.value
})
const switchTitle = computed(() => {
return isDark.value
const switchTitle = ref('')
watchPostEffect(() => {
switchTitle.value = isDark.value
? theme.value.lightModeSwitchTitle || 'Switch to light theme'
: theme.value.darkModeSwitchTitle || 'Switch to dark theme'
})

View File

@ -17,7 +17,9 @@ export function useFlyout(options: UseFlyoutOptions) {
const focus = ref(false)
if (inBrowser) {
!active && activateFocusTracking()
if (!active) {
activateFocusTracking()
}
listeners++

View File

@ -91,13 +91,17 @@ export function useHomeHeroTintPlate(
onMounted(() => {
if (canvas.value && enable.value) {
ctx = canvas.value.getContext('2d')!
timer && window.cancelAnimationFrame(timer)
if (timer) {
window.cancelAnimationFrame(timer)
}
run()
}
})
onUnmounted(() => {
timer && window.cancelAnimationFrame(timer)
if (timer) {
window.cancelAnimationFrame(timer)
}
})
function run() {

View File

@ -4,7 +4,7 @@ import { normalizeLink } from '../utils/index.js'
import { useThemeData } from './theme-data.js'
import { useData } from './data.js'
import { getSidebarFirstLink, useSidebarData } from './sidebar.js'
import { useBlogPost } from './page.js'
import { useBlogPageData } from './page.js'
export function useLangs({
removeCurrent = true,
@ -13,7 +13,7 @@ export function useLangs({
const { page } = useData()
const routeLocale = useRouteLocale()
const sidebar = useSidebarData()
const { isBlogPost } = useBlogPost()
const { isBlogPost } = useBlogPageData()
const currentLang = computed(() => {
const link = routeLocale.value

View File

@ -60,14 +60,21 @@ export function useNav(): UseNavReturn {
}
function toggleScreen(): void {
isScreenOpen.value ? closeScreen() : openScreen()
if (isScreenOpen.value) {
closeScreen()
}
else {
openScreen()
}
}
/**
* Close screen when the user resizes the window wider than tablet size.
*/
function closeScreenOnTabletWindow(): void {
window.outerWidth >= 768 && closeScreen()
if (window.outerWidth >= 768) {
closeScreen()
}
}
const route = useRoute()

View File

@ -2,7 +2,7 @@ import { computed } from 'vue'
import { useData } from './data.js'
import { usePostList } from './blog-data.js'
export function useBlogPost() {
export function useBlogPageData() {
const { page } = useData()
const postList = usePostList()
@ -10,7 +10,13 @@ export function useBlogPost() {
return postList.value.some(item => item.path === page.value.path)
})
const isBlogLayout = computed(() => {
const type = page.value.type
return type === 'blog' || type === 'blog-archives' || type === 'blog-tags' || type === 'blog-categories'
})
return {
isBlogPost,
isBlogLayout,
}
}

View File

@ -7,7 +7,7 @@ import { resolveNavLink } from '../utils/index.js'
import { usePostList } from './blog-data.js'
import { useSidebar } from './sidebar.js'
import { useData } from './data.js'
import { useBlogPost } from './page.js'
import { useBlogPageData } from './page.js'
export function usePrevNext() {
const route = useRoute()
@ -15,7 +15,7 @@ export function usePrevNext() {
const { sidebar } = useSidebar()
const postList = usePostList() as unknown as Ref<PlumeThemeBlogPostItem[]>
const locale = usePageLang()
const { isBlogPost } = useBlogPost()
const { isBlogPost } = useBlogPageData()
const prevNavList = computed(() => {
const prevConfig = resolveFromFrontmatterConfig(frontmatter.value.prev)

View File

@ -295,7 +295,12 @@ export function useSidebar(): UseSidebarReturn {
}
const toggle = (): void => {
isOpen.value ? close() : open()
if (isOpen.value) {
close()
}
else {
open()
}
}
return {
@ -386,7 +391,9 @@ export function useSidebarControl(item: ComputedRef<ResolvedSidebarItem>): Sideb
})
watchPostEffect(() => {
;(isActiveLink.value || hasActiveLink.value) && (collapsed.value = false)
if (isActiveLink.value || hasActiveLink.value) {
collapsed.value = false
}
})
const toggle = (): void => {

View File

@ -1,7 +1,6 @@
import './styles/index.css'
import { defineClientConfig } from 'vuepress/client'
import type { ClientConfig } from 'vuepress/client'
import {
enhanceScrollBehavior,
setupDarkMode,
@ -29,4 +28,4 @@ export default defineClientConfig({
setupWatermark()
},
layouts: { Layout, NotFound },
}) as ClientConfig
})

View File

@ -133,6 +133,12 @@ useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
<template #blog-tags-after>
<slot name="blog-tags-after" />
</template>
<template #blog-categories-before>
<slot name="blog-categories-before" />
</template>
<template #blog-categories-after>
<slot name="blog-categories-after" />
</template>
<template #blog-post-list-before>
<slot name="blog-post-list-before" />
</template>

View File

@ -45,7 +45,13 @@
letter-spacing: -0.01em;
}
.vp-doc h4,
.vp-doc h4 {
margin: 24px 0 16px;
font-size: 18px;
line-height: 24px;
letter-spacing: -0.01em;
}
.vp-doc h5,
.vp-doc h6 {
margin: 24px 0 16px;
@ -127,15 +133,14 @@
.vp-doc blockquote {
padding-left: 16px;
margin: 16px 0;
color: var(--vp-c-text-2);
border-left: 2px solid var(--vp-c-divider);
transition: border-color var(--t-color);
transition: border-color var(--t-color), color var(--t-color);
}
.vp-doc blockquote > p {
margin: 0;
font-size: 16px;
color: var(--vp-c-text-2);
transition: color var(--t-color);
}
.vp-doc a {
@ -258,7 +263,8 @@
.vp-doc h1 > code,
.vp-doc h2 > code,
.vp-doc h3 > code {
.vp-doc h3 > code,
.vp-doc h4 > code {
font-size: 0.9em;
}

View File

@ -266,6 +266,26 @@
font-optical-sizing: auto;
}
:root:where(:lang(zh)),
:root:where(:lang(zh-CN)) {
--vp-font-family-base:
"Punctuation SC",
"Inter",
ui-sans-serif,
system-ui,
"PingFang SC",
"Noto Sans CJK SC",
"Noto Sans SC",
"Heiti SC",
"Microsoft YaHei",
"DengXian",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
}
/**
* Shadows
* -------------------------------------------------------------------------- */

View File

@ -57,8 +57,8 @@ export function scrollTo(
const change = top - currentTop
const timer = setInterval(() => {
currentStep++
if (currentStep >= step)
timer && clearInterval(timer)
if (currentStep >= step && timer)
clearInterval(timer)
setScrollTop(target, tween(currentStep, currentTop, change, step))
}, 1000 / 60)

View File

@ -177,10 +177,12 @@ export function resolveOptions(
if (note && sidebar && sidebar !== 'auto') {
const res = resolveLinkBySidebar(sidebar, pathJoin(notes?.dir || '', note.dir || ''))
const file = ensureLeadingSlash(relativePath)
if (res[file])
if (res[file]) {
args.push(res[file])
else
res[path.dirname(file)] && args.push(res[path.dirname(file)])
}
else if (res[path.dirname(file)]) {
args.push(res[path.dirname(file)])
}
}
return pathJoin(...args, nanoid(), '/')

View File

@ -1,4 +1,10 @@
import { addViteConfig, addViteOptimizeDepsExclude, addViteOptimizeDepsInclude, addViteSsrNoExternal } from '@vuepress/helper'
import {
addViteConfig,
addViteOptimizeDepsExclude,
addViteOptimizeDepsInclude,
addViteSsrNoExternal,
chainWebpack,
} from '@vuepress/helper'
import type { App } from 'vuepress'
export function extendsBundlerOptions(bundlerOptions: any, app: App): void {
@ -16,4 +22,18 @@ export function extendsBundlerOptions(bundlerOptions: any, app: App): void {
'@vuepress/plugin-reading-time',
'@vuepress/plugin-watermark',
])
chainWebpack(bundlerOptions, app, (config) => {
config.module
.rule('scss')
.use('sass-loader')
.tap((options: any) => ({
api: 'modern-compiler',
...options,
sassOptions: {
silenceDeprecations: ['mixed-decls'],
...options.sassOptions,
},
}))
})
}

View File

@ -51,14 +51,18 @@ export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): Pl
text: PRESET_LOCALES[localePath].blog,
link: withBase(blogLink, locale),
})
blog.tags !== false && navbar.push({
text: PRESET_LOCALES[localePath].tag,
link: withBase(blog.tagsLink || `${blogLink}/tags/`, locale),
})
blog.archives !== false && navbar.push({
text: PRESET_LOCALES[localePath].archive,
link: withBase(blog.archivesLink || `${blogLink}/archives/`, locale),
})
if (blog.tags !== false) {
navbar.push({
text: PRESET_LOCALES[localePath].tag,
link: withBase(blog.tagsLink || `${blogLink}/tags/`, locale),
})
}
if (blog.archives !== false) {
navbar.push({
text: PRESET_LOCALES[localePath].archive,
link: withBase(blog.archivesLink || `${blogLink}/archives/`, locale),
})
}
themeData.locales![locale].navbar = navbar
}

View File

@ -49,7 +49,9 @@ export async function extendsMarkdown(md: Markdown, app: App): Promise<void> {
const update = (filepath: string, data: CacheData): void => {
writeFile(`${basename}/${filepath}`, data)
timer && clearTimeout(timer)
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(async () => writeFile(metaFilepath, metadata), 200)
}
const rawRender = md.render

View File

@ -58,7 +58,9 @@ export async function initConfigLoader(
loader.configFile = await findConfigPath(app, configFile)
onChange && loader.changeEvents.push(onChange)
if (onChange) {
loader.changeEvents.push(onChange)
}
const { config, dependencies = [] } = await loader.load()
loader.loaded = true
@ -103,7 +105,9 @@ export function watchConfigFile(app: App, watchers: any[]) {
export async function onConfigChange(onChange: ChangeEvent) {
if (loader && !loader.changeEvents.includes(onChange)) {
loader.changeEvents.push(onChange)
loader.loaded && onChange(loader.resolvedConfig)
if (loader.loaded) {
onChange(loader.resolvedConfig)
}
}
}

View File

@ -46,7 +46,9 @@ export function genCode(app: App): { js: string, css: string } {
const { frontmatter: { tags } } = page
if (tags) {
toArray(tags).forEach((tag) => {
tag && tagList.add(tag as string)
if (tag) {
tagList.add(tag as string)
}
})
}
})

View File

@ -69,7 +69,9 @@ export async function preparedBlogData(
excerpt: '',
}
isEncryptPage(page, encrypt) && (data.encrypt = true)
if (isEncryptPage(page, encrypt)) {
data.encrypt = true
}
if (page.contentRendered.includes(EXCERPT_SPLIT)) {
const contents = page.contentRendered.split(EXCERPT_SPLIT)

View File

@ -38,18 +38,23 @@ function getSidebarData(
else if (isPlainObject(sidebar)) {
entries(sidebar).forEach(([dirname, config]) => {
const prefix = normalizeLink(localePath, dirname)
config === 'auto'
? autoDirList.push(prefix)
: isArray(config)
? autoDirList.push(...findAutoDirList(config, prefix))
: config.items === 'auto'
? autoDirList.push(normalizeLink(prefix, config.prefix))
: autoDirList.push(
...findAutoDirList(
config.items || [],
normalizeLink(prefix, config.prefix),
),
)
if (config === 'auto') {
autoDirList.push(prefix)
}
else if (isArray(config)) {
autoDirList.push(...findAutoDirList(config, prefix))
}
else if (config.items === 'auto') {
autoDirList.push(normalizeLink(prefix, config.prefix))
}
else {
autoDirList.push(
...findAutoDirList(
config.items || [],
normalizeLink(prefix, config.prefix),
),
)
}
})
}
else if (sidebar === 'auto') {
@ -121,8 +126,8 @@ function getAutoDirSidebar(
if (!isHome) {
items.push(current)
}
else {
!parent && items.unshift(current)
else if (!parent) {
items.unshift(current)
}
}
if (dir.endsWith('.md')) {
@ -159,9 +164,8 @@ function findAutoDirList(sidebar: (string | SidebarItem)[], prefix = ''): string
if (item.items === 'auto') {
list.push(nextPrefix)
}
else {
item.items?.length
&& list.push(...findAutoDirList(item.items, nextPrefix))
else if (item.items?.length) {
list.push(...findAutoDirList(item.items, nextPrefix))
}
}
})

View File

@ -42,22 +42,28 @@ export async function setupPage(
}))
// 添加 标签页
blog.tags !== false && pageList.push(createPage(app, {
path: withBase(blog.tagsLink || `${link}/tags/`, localePath),
frontmatter: { lang, _pageLayout: 'blog-tags', title: getTitle(locale, 'tag') },
}))
if (blog.tags !== false) {
pageList.push(createPage(app, {
path: withBase(blog.tagsLink || `${link}/tags/`, localePath),
frontmatter: { lang, _pageLayout: 'blog-tags', title: getTitle(locale, 'tag') },
}))
}
// 添加归档页
blog.archives !== false && pageList.push(createPage(app, {
path: withBase(blog.archivesLink || `${link}/archives/`, localePath),
frontmatter: { lang, _pageLayout: 'blog-archives', title: getTitle(locale, 'archive') },
}))
if (blog.archives !== false) {
pageList.push(createPage(app, {
path: withBase(blog.archivesLink || `${link}/archives/`, localePath),
frontmatter: { lang, _pageLayout: 'blog-archives', title: getTitle(locale, 'archive') },
}))
}
// 添加分类页
blog.categories !== false && pageList.push(createPage(app, {
path: withBase(blog.categoriesLink || `${link}/categories/`, localePath),
frontmatter: { lang, _pageLayout: 'blog-categories', title: getTitle(locale, 'category') },
}))
if (blog.categories !== false) {
pageList.push(createPage(app, {
path: withBase(blog.categoriesLink || `${link}/categories/`, localePath),
frontmatter: { lang, _pageLayout: 'blog-categories', title: getTitle(locale, 'category') },
}))
}
}
app.pages.push(...await Promise.all(pageList))
@ -137,7 +143,9 @@ export function autoCategory(
const categoryList: PageCategoryData[] = list
.map((category, index) => {
const match = category.match(RE_CATEGORY) || []
!cache[match[2]] && !match[1] && (cache[match[2]] = uuid++)
if (!cache[match[2]] && !match[1]) {
cache[match[2]] = uuid++
}
return {
id: hash(list.slice(0, index + 1).join('-')).slice(0, 6),
sort: Number(match[1] || cache[match[2]]),

View File

@ -40,7 +40,9 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
configFile,
onChange: ({ localeOptions, autoFrontmatter }) => {
autoFrontmatter ??= pluginOptions.frontmatter
autoFrontmatter !== false && initAutoFrontmatter(localeOptions, autoFrontmatter)
if (autoFrontmatter !== false) {
initAutoFrontmatter(localeOptions, autoFrontmatter)
}
},
})