refactor(theme): 移动端支持&修复frontmatter监听问题

1.响应式布局,支持移动端;
2.修复监听markdown文件生成frontmatter时错误问题
This commit is contained in:
pengzhanbo 2022-04-30 23:09:28 +08:00
parent f10248f267
commit 61d1e2e37f
23 changed files with 332 additions and 5370 deletions

View File

@ -22,6 +22,7 @@
"commitlint",
"composables",
"Docsearch",
"gsap",
"nprogress",
"pnpm",
"tsbuildinfo",

View File

@ -9,6 +9,7 @@ export default defineUserConfig({
lang: 'zh',
title: 'Plume Theme',
description: '',
source: path.resolve(__dirname, '../'),
public: path.resolve(__dirname, 'public'),
bundler:

View File

@ -20,3 +20,37 @@ permalink: /note/interview-question/
如果你发现本笔记中有哪些错误,欢迎指出,我将虚心受教!
:::
1
1
1
1
11
1
1
1
1
1
1
1
1
1
1

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -78,6 +78,7 @@ const handleTag = (tag: string): void => {
}
.tag-list {
width: 100%;
padding: 0 1.25rem 0.75rem;
margin: 0 -0.25rem;

View File

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

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

View File

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

View File

@ -28,6 +28,8 @@ export const useSidebarIndex = (): UseSidebarIndex => {
sidebarList.value = sidebarIndex.value[key]
}
})
} else {
sidebarList.value = []
}
}
return { sidebarList, initSidebarList }

View File

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

View File

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

View File

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

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

View File

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

View File

@ -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,
})

View File

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

View File

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

File diff suppressed because it is too large Load Diff