refactor(theme): 移动端支持&修复frontmatter监听问题
1.响应式布局,支持移动端; 2.修复监听markdown文件生成frontmatter时错误问题
This commit is contained in:
parent
f10248f267
commit
61d1e2e37f
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -22,6 +22,7 @@
|
||||
"commitlint",
|
||||
"composables",
|
||||
"Docsearch",
|
||||
"gsap",
|
||||
"nprogress",
|
||||
"pnpm",
|
||||
"tsbuildinfo",
|
||||
|
||||
@ -9,6 +9,7 @@ export default defineUserConfig({
|
||||
lang: 'zh',
|
||||
title: 'Plume Theme',
|
||||
description: '',
|
||||
source: path.resolve(__dirname, '../'),
|
||||
public: path.resolve(__dirname, 'public'),
|
||||
|
||||
bundler:
|
||||
|
||||
@ -20,3 +20,37 @@ permalink: /note/interview-question/
|
||||
|
||||
如果你发现本笔记中有哪些错误,欢迎指出,我将虚心受教!
|
||||
:::
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
|
||||
1
|
||||
|
||||
|
||||
11
|
||||
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
1
|
||||
|
||||
@ -30,6 +30,9 @@
|
||||
"@vuepress/utils": "2.0.0-beta.41",
|
||||
"markdown-it-container": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^12.2.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
|
||||
45
packages/theme/src/client/components/AsideNavbar.vue
Normal file
45
packages/theme/src/client/components/AsideNavbar.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<script lang="ts" setup>
|
||||
import Sidebar from '@theme-plume/Sidebar.vue'
|
||||
import { useAsideNavbar, useNavbarConfig } from '../composables'
|
||||
|
||||
const navbarConfig = useNavbarConfig()
|
||||
const { asideNavbarShow, triggerAsideNavbar } = useAsideNavbar()
|
||||
</script>
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-show="asideNavbarShow"
|
||||
class="aside-navbar-wrapper"
|
||||
@click.self="triggerAsideNavbar(false)"
|
||||
>
|
||||
<Sidebar :aside="navbarConfig" />
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
@import '../styles/variables';
|
||||
.aside-navbar-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
z-index: 30;
|
||||
|
||||
.plume-theme-sidebar-wrapper {
|
||||
display: block;
|
||||
position: relative;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 70%;
|
||||
height: 100%;
|
||||
padding: 1.25rem 0 1.25rem 1.25rem;
|
||||
}
|
||||
}
|
||||
@media (min-width: $MQMobile) {
|
||||
.aside-navbar-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -4,13 +4,13 @@ import NavbarBrand from '@theme-plume/NavbarBrand.vue'
|
||||
import NavbarItems from '@theme-plume/NavbarItems.vue'
|
||||
import ToggleSidebarButton from '@theme-plume/ToggleSidebarButton.vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useThemeLocaleData } from '../composables'
|
||||
import { useAsideNavbar, useThemeLocaleData } from '../composables'
|
||||
import { getCssValue } from '../utils'
|
||||
|
||||
defineEmits(['toggle-sidebar'])
|
||||
|
||||
const themeLocale = useThemeLocaleData()
|
||||
|
||||
const { triggerAsideNavbar } = useAsideNavbar()
|
||||
|
||||
const navbar = ref<HTMLElement | null>(null)
|
||||
const navbarBrand = ref<HTMLElement | null>(null)
|
||||
|
||||
@ -47,7 +47,7 @@ onMounted(() => {
|
||||
</script>
|
||||
<template>
|
||||
<header ref="navbar" class="navbar-wrapper">
|
||||
<ToggleSidebarButton @toggle="$emit('toggle-sidebar')" />
|
||||
<ToggleSidebarButton @toggle="triggerAsideNavbar(true)" />
|
||||
<span ref="navbarBrand">
|
||||
<NavbarBrand />
|
||||
</span>
|
||||
@ -69,11 +69,12 @@ onMounted(() => {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
width: 100v;
|
||||
height: var(--navbar-height);
|
||||
padding: var(--navbar-padding-v) var(--navbar-padding-h);
|
||||
background-color: var(--c-bg-navbar);
|
||||
|
||||
@ -3,11 +3,12 @@ import DropdownTransition from '@theme-plume/DropdownTransition.vue'
|
||||
import PostItem from '@theme-plume/PostItem.vue'
|
||||
import { usePageFrontmatter } from '@vuepress/client'
|
||||
import type { PropType } from 'vue'
|
||||
import { onMounted, toRefs, watch } from 'vue'
|
||||
import { toRefs, watch } from 'vue'
|
||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
|
||||
import type { PlumeThemeHomeFrontmatter } from '../../shared'
|
||||
import type { PostListData } from '../composables'
|
||||
import { usePostList } from '../composables'
|
||||
import { scrollTo } from '../utils'
|
||||
import Pagination from './Pagination.vue'
|
||||
|
||||
const props = defineProps({
|
||||
@ -30,6 +31,7 @@ watch(
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
onBeforeRouteUpdate((to) => {
|
||||
@ -42,7 +44,7 @@ onBeforeRouteUpdate((to) => {
|
||||
rect || document.querySelector('.navbar-wrapper')?.getBoundingClientRect()
|
||||
top = document.documentElement.clientHeight - (rect?.height || 0)
|
||||
}
|
||||
document.documentElement.scrollTop = top
|
||||
scrollTo(document, top)
|
||||
})
|
||||
|
||||
setPostListPage((route.query.p as unknown as number) || 1)
|
||||
|
||||
@ -1,21 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import SidebarItems from '@theme-plume/SidebarItems.vue'
|
||||
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
|
||||
import { useSidebarIndex } from '../composables'
|
||||
import type { PropType } from 'vue'
|
||||
import { watchEffect } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { SidebarOptions } from '../../shared'
|
||||
import { useAsideNavbar, useSidebarIndex } from '../composables'
|
||||
|
||||
defineProps({
|
||||
aside: {
|
||||
type: Array as PropType<SidebarOptions>,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const route = useRoute()
|
||||
const { sidebarList, initSidebarList } = useSidebarIndex()
|
||||
const { triggerAsideNavbar } = useAsideNavbar()
|
||||
initSidebarList(route.path)
|
||||
onBeforeRouteUpdate((to) => {
|
||||
initSidebarList(to.path)
|
||||
watchEffect(() => {
|
||||
initSidebarList(route.path)
|
||||
triggerAsideNavbar(false)
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<aside class="plume-theme-sidebar-wrapper">
|
||||
<SidebarItems class="aside-navbar" :sidebar-list="aside" />
|
||||
<SidebarItems :sidebar-list="sidebarList" />
|
||||
</aside>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
@import '../styles/variables';
|
||||
|
||||
.plume-theme-sidebar-wrapper {
|
||||
position: sticky;
|
||||
top: calc(var(--navbar-height) + 1.25rem);
|
||||
@ -40,5 +55,26 @@ onBeforeRouteUpdate((to) => {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: var(--c-brand);
|
||||
}
|
||||
|
||||
> .aside-navbar {
|
||||
position: relative;
|
||||
padding-bottom: 0.75rem;
|
||||
margin-bottom: 1.25rem;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -1.25rem;
|
||||
bottom: -4px;
|
||||
right: 0;
|
||||
border-bottom: solid 4px var(--c-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.plume-theme-sidebar-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -64,13 +64,13 @@ const sidebarClick = (sidebar: SidebarListComputed): void => {
|
||||
{{ sidebar.text }}
|
||||
</span>
|
||||
<ArrowRightIcon
|
||||
v-if="deep === 1 && sidebar.children.length"
|
||||
v-if="deep === 1 && sidebar.children && sidebar.children.length"
|
||||
:class="{ open: sidebar.open }"
|
||||
@click.self="sidebarClick(sidebar)"
|
||||
/>
|
||||
</p>
|
||||
<SidebarItems
|
||||
v-if="sidebar.children.length"
|
||||
v-if="sidebar.children && sidebar.children.length"
|
||||
v-show="sidebar.open"
|
||||
:sidebar-list="sidebar.children"
|
||||
:deep="deep + 1"
|
||||
|
||||
@ -78,6 +78,7 @@ const handleTag = (tag: string): void => {
|
||||
}
|
||||
|
||||
.tag-list {
|
||||
width: 100%;
|
||||
padding: 0 1.25rem 0.75rem;
|
||||
margin: 0 -0.25rem;
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import type { PropType, VNode } from 'vue'
|
||||
import { computed, defineComponent, h, toRefs } from 'vue'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { scrollTo } from '../utils'
|
||||
|
||||
export type TocPropsHeaders = PageHeader[]
|
||||
|
||||
@ -51,7 +52,7 @@ const renderLink = (
|
||||
if (!anchor) return
|
||||
const el = document.documentElement
|
||||
const top = anchor.getBoundingClientRect().top - 80 + el.scrollTop
|
||||
el.scrollTo ? el.scrollTo({ top }) : (el.scrollTop = top)
|
||||
scrollTo(document, top)
|
||||
}
|
||||
|
||||
return h(
|
||||
|
||||
23
packages/theme/src/client/composables/asideNavbar.ts
Normal file
23
packages/theme/src/client/composables/asideNavbar.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const asideNavbarShow = ref<boolean>(false)
|
||||
|
||||
const triggerAsideNavbar = (show?: boolean): void => {
|
||||
if (typeof show === 'boolean') {
|
||||
asideNavbarShow.value = show
|
||||
} else {
|
||||
asideNavbarShow.value = !asideNavbarShow.value
|
||||
}
|
||||
}
|
||||
|
||||
interface UseAsideNavbar {
|
||||
asideNavbarShow: Ref<boolean>
|
||||
triggerAsideNavbar: (show?: boolean) => void
|
||||
}
|
||||
export const useAsideNavbar = (): UseAsideNavbar => {
|
||||
return {
|
||||
asideNavbarShow,
|
||||
triggerAsideNavbar,
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ export * from './postIndex'
|
||||
export * from './sidebarIndex'
|
||||
export * from './postList'
|
||||
export * from './scrollPromise'
|
||||
export * from './asideNavbar'
|
||||
|
||||
export * from './tag'
|
||||
export * from './category'
|
||||
|
||||
@ -28,6 +28,8 @@ export const useSidebarIndex = (): UseSidebarIndex => {
|
||||
sidebarList.value = sidebarIndex.value[key]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
sidebarList.value = []
|
||||
}
|
||||
}
|
||||
return { sidebarList, initSidebarList }
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import Archive from '@theme-plume/Archive.vue'
|
||||
import AsideNavbar from '@theme-plume/AsideNavbar.vue'
|
||||
import Category from '@theme-plume/Category.vue'
|
||||
import Home from '@theme-plume/Home.vue'
|
||||
import Navbar from '@theme-plume/Navbar.vue'
|
||||
@ -44,6 +45,7 @@ const pageMap = {
|
||||
</template>
|
||||
</Navbar>
|
||||
</slot>
|
||||
<AsideNavbar />
|
||||
<slot name="page">
|
||||
<Home v-if="isHome" />
|
||||
<Component :is="pageMap[pageType]" v-else-if="pageType" />
|
||||
|
||||
2
packages/theme/src/client/shim.d.ts
vendored
2
packages/theme/src/client/shim.d.ts
vendored
@ -5,6 +5,8 @@ declare module '*.vue' {
|
||||
}
|
||||
|
||||
declare const __VUEPRESS_DEV__: boolean
|
||||
declare const __VUEPRESS_SSR__: boolean
|
||||
declare const __VUE_HMR_RUNTIME__: Record<string, unknown>
|
||||
|
||||
declare module '@internal/postIndex.js' {
|
||||
import type { PostIndex } from '../shared'
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
}
|
||||
|
||||
&-leave-active {
|
||||
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
}
|
||||
|
||||
&-enter-from,
|
||||
@ -13,3 +13,18 @@
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fade {
|
||||
&-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&-leave-active {
|
||||
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
}
|
||||
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
14
packages/theme/src/client/utils/animate.ts
Normal file
14
packages/theme/src/client/utils/animate.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @method 缓动算法
|
||||
* t: current time(当前时间);
|
||||
* b: beginning value(初始值);
|
||||
* c: change in value(变化量);
|
||||
* d: duration(持续时间)。
|
||||
*/
|
||||
export const tween = (t: number, b: number, c: number, d: number): number => {
|
||||
return c * (t /= d) * t * t + b
|
||||
}
|
||||
|
||||
export const linear = (t: number, b: number, c: number, d: number): number => {
|
||||
return (c * t) / d + b
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import { tween } from './animate'
|
||||
export function getCssValue(el: HTMLElement | null, property: string): number {
|
||||
const val = el?.ownerDocument?.defaultView?.getComputedStyle(el, null)?.[
|
||||
property
|
||||
@ -5,3 +6,50 @@ export function getCssValue(el: HTMLElement | null, property: string): number {
|
||||
const num = Number.parseInt(val, 10)
|
||||
return Number.isNaN(num) ? 0 : num
|
||||
}
|
||||
|
||||
export function getScrollTop(
|
||||
target: Document | HTMLElement = document
|
||||
): number {
|
||||
if (target === document || !target) {
|
||||
return document.documentElement.scrollTop || document.body.scrollTop
|
||||
} else {
|
||||
return (target as HTMLElement).scrollTop
|
||||
}
|
||||
}
|
||||
|
||||
export function setScrollTop(
|
||||
target: Document | HTMLElement = document,
|
||||
scrollTop = 0
|
||||
): void {
|
||||
if (typeof target === 'number') {
|
||||
scrollTop = target
|
||||
target = document
|
||||
document.documentElement.scrollTop = scrollTop
|
||||
document.body.scrollTop = scrollTop
|
||||
} else {
|
||||
if (target === document) {
|
||||
document.body.scrollTop = scrollTop || 0
|
||||
document.documentElement.scrollTop = scrollTop || 0
|
||||
} else {
|
||||
;(target as HTMLElement).scrollTop = scrollTop || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function scrollTo(
|
||||
target: Document | HTMLElement,
|
||||
top: number,
|
||||
time = 300
|
||||
): void {
|
||||
const currentTop = getScrollTop(target)
|
||||
const step = Math.ceil(time / 16)
|
||||
let currentStep = 0
|
||||
const change = top - currentTop
|
||||
const timer = setInterval(() => {
|
||||
currentStep++
|
||||
if (currentStep >= step) {
|
||||
timer && clearInterval(timer)
|
||||
}
|
||||
setScrollTop(target, tween(currentStep, currentTop, change, step))
|
||||
}, 1000 / 60)
|
||||
}
|
||||
|
||||
@ -124,7 +124,8 @@ export const generateFrontmatter = (
|
||||
}
|
||||
|
||||
const watchNewMarkDown = (app: App, watchers): void => {
|
||||
const watcher = chokidar.watch(['**/*.md', '!/{readme,README,index}.md'], {
|
||||
const watcher = chokidar.watch('**/*.md', {
|
||||
ignored: /node_modules/,
|
||||
cwd: app.options.source,
|
||||
ignoreInitial: true,
|
||||
})
|
||||
|
||||
@ -13,7 +13,7 @@ import { resolveMediumZoom } from './mediumZoom'
|
||||
import { resolveNprogress } from './nprogress'
|
||||
import { resolvePalette } from './palette'
|
||||
import { resolvePrismjs } from './prismjs'
|
||||
// import { resolveSearch } from './search'
|
||||
import { resolveSearch } from './search'
|
||||
// import { resolveSeo } from './seo'
|
||||
// import { resolveSitemap } from './sitemap'
|
||||
import { resolveThemeData } from './themeData'
|
||||
@ -29,7 +29,7 @@ export const getPlugins = (
|
||||
resolveMediumZoom(plugins),
|
||||
resolveCanIUse(plugins),
|
||||
resolveExternalLinkIconPlugin(plugins, localeOptions),
|
||||
// resolveSearch(plugins),
|
||||
resolveSearch(plugins),
|
||||
resolvePrismjs(plugins),
|
||||
// resolveCopyCode(plugins),
|
||||
// resolveMarkdownEnhance(plugins),
|
||||
|
||||
@ -19,12 +19,16 @@ export const readFileList = (
|
||||
readFileList(filepath, fileList)
|
||||
} else {
|
||||
const extname = path.extname(file)
|
||||
if (extname === '.md' || extname === '.MD') {
|
||||
const basename = path.basename(file)
|
||||
if (
|
||||
(extname === '.md' || extname === '.MD') &&
|
||||
basename !== 'CHANGELOG'
|
||||
) {
|
||||
fileList.push(readFile(filepath, stat))
|
||||
}
|
||||
}
|
||||
})
|
||||
return fileList
|
||||
return fileList.filter((file) => file.filepath.endsWith('.md'))
|
||||
}
|
||||
|
||||
export const readFile = (filepath: string, stat: fs.Stats): MarkdownFile => {
|
||||
|
||||
5425
pnpm-lock.yaml
generated
5425
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user