mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
perf: 改版博客文章列表页UI,贴合设计趋势
This commit is contained in:
parent
8d56e989d4
commit
8a969abb27
@ -77,8 +77,8 @@ function handleClick() {
|
||||
.back-to-top-button {
|
||||
position: fixed;
|
||||
inset-inline-end: 1rem;
|
||||
right: 20px;
|
||||
bottom: 64px;
|
||||
right: 24px;
|
||||
bottom: calc(var(--vp-footer-height, 82px) - 18px);
|
||||
z-index: var(--vp-z-index-back-to-top);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@ -140,6 +140,7 @@ function handleClick() {
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.back-to-top-button {
|
||||
bottom: calc(var(--vp-footer-height, 88px) - 24px);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
@ -12,16 +12,22 @@ const page = usePageData<PlumeThemePageData>()
|
||||
|
||||
<template>
|
||||
<div class="blog-wrapper">
|
||||
<PostList v-if="page.type === 'blog'" />
|
||||
<Tags v-if="page.type === 'blog-tags'" />
|
||||
<Archives v-if="page.type === 'blog-archives'" />
|
||||
<BlogAside />
|
||||
<BlogExtract />
|
||||
<div class="blog-container">
|
||||
<PostList v-if="page.type === 'blog'" />
|
||||
<Tags v-if="page.type === 'blog-tags'" />
|
||||
<Archives v-if="page.type === 'blog-archives'" />
|
||||
<BlogAside />
|
||||
<BlogExtract />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.blog-wrapper {
|
||||
min-height: calc(100vh - var(--vp-footer-height, 0px));
|
||||
}
|
||||
|
||||
.blog-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
@ -30,15 +36,31 @@ const page = usePageData<PlumeThemePageData>()
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.blog-wrapper {
|
||||
min-height: calc(100vh + var(--vp-nav-height) - var(--vp-footer-height, 0px));
|
||||
}
|
||||
|
||||
.blog-wrapper {
|
||||
padding-top: var(--vp-nav-height);
|
||||
margin-top: calc(var(--vp-nav-height) * -1);
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.blog-wrapper {
|
||||
min-height: calc(100vh - var(--vp-footer-height, 0px));
|
||||
}
|
||||
|
||||
.blog-container {
|
||||
max-width: 784px;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.blog-wrapper {
|
||||
.blog-container {
|
||||
max-width: 1104px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useBlogExtract, useThemeLocaleData } from '../composables/index.js'
|
||||
import AutoLink from './AutoLink.vue'
|
||||
import IconArchive from './icons/IconArchive.vue'
|
||||
import IconTag from './icons/IconTag.vue'
|
||||
import IconChevronRight from './icons/IconChevronRight.vue'
|
||||
|
||||
const theme = useThemeLocaleData()
|
||||
const route = useRoute()
|
||||
|
||||
const avatar = computed(() => theme.value.avatar)
|
||||
const { hasBlogExtract, tags, archives } = useBlogExtract()
|
||||
@ -14,7 +17,7 @@ const { hasBlogExtract, tags, archives } = useBlogExtract()
|
||||
<template>
|
||||
<div v-if="avatar" class="blog-aside-wrapper">
|
||||
<div class="avatar-profile">
|
||||
<p v-if="avatar.url">
|
||||
<p v-if="avatar.url" :class="{ circle: avatar.circle }">
|
||||
<img :src="avatar.url" :alt="avatar.name">
|
||||
</p>
|
||||
<div>
|
||||
@ -23,13 +26,25 @@ const { hasBlogExtract, tags, archives } = useBlogExtract()
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hasBlogExtract" class="blog-nav">
|
||||
<AutoLink class="nav-link" :href="tags.link">
|
||||
<IconTag class="icon" />
|
||||
<span>{{ tags.text }}</span>
|
||||
<AutoLink
|
||||
class="nav-link"
|
||||
:class="{ active: route.path === tags.link }"
|
||||
:href="tags.link"
|
||||
>
|
||||
<IconTag class="icon icon-logo" />
|
||||
<span class="text">{{ tags.text }}</span>
|
||||
<span class="total">{{ tags.total }}</span>
|
||||
<IconChevronRight class="icon" />
|
||||
</AutoLink>
|
||||
<AutoLink class="nav-link" :href="archives.link">
|
||||
<IconArchive class="icon" />
|
||||
<span>{{ archives.text }}</span>
|
||||
<AutoLink
|
||||
class="nav-link"
|
||||
:class="{ active: route.path === archives.link }"
|
||||
:href="archives.link"
|
||||
>
|
||||
<IconArchive class="icon icon-logo" />
|
||||
<span class="text">{{ archives.text }}</span>
|
||||
<span class="total">{{ archives.total }}</span>
|
||||
<IconChevronRight class="icon" />
|
||||
</AutoLink>
|
||||
</div>
|
||||
</div>
|
||||
@ -41,17 +56,15 @@ const { hasBlogExtract, tags, archives } = useBlogExtract()
|
||||
top: calc(var(--vp-nav-height) + 2rem);
|
||||
display: none;
|
||||
width: 270px;
|
||||
padding: 1rem 0;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 12rem;
|
||||
margin-left: 2rem;
|
||||
margin: 2rem 1rem 0 2rem;
|
||||
text-align: center;
|
||||
border-left: solid 1px var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.blog-aside-wrapper img {
|
||||
width: 50%;
|
||||
width: 60%;
|
||||
margin: auto;
|
||||
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.blog-aside-wrapper h3 {
|
||||
@ -60,39 +73,74 @@ const { hasBlogExtract, tags, archives } = useBlogExtract()
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.blog-aside-wrapper {
|
||||
display: block;
|
||||
}
|
||||
.avatar-profile {
|
||||
padding: 24px 20px;
|
||||
margin-bottom: 24px;
|
||||
background-color: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
|
||||
.avatar-profile .circle img {
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.blog-nav {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
padding: 10px 12px 0;
|
||||
margin: 24px 24px 0;
|
||||
border-top: solid 1px var(--vp-c-divider);
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-brand-1);
|
||||
justify-content: flex-start;
|
||||
padding: 10px 14px 10px 20px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
transition: all var(--t-color);
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
transition: color var(--t-color);
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--vp-c-brand-2);
|
||||
.nav-link:hover,
|
||||
.nav-link.active {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.nav-link .text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
.nav-link .total {
|
||||
padding-right: 8px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.nav-link .icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-right: 4px;
|
||||
font-size: 1.2em;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.nav-link .icon-logo {
|
||||
margin-right: 10px;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.blog-aside-wrapper {
|
||||
margin: 2rem 1rem 2rem 1.25rem;
|
||||
}
|
||||
|
||||
.blog-aside-wrapper {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -129,7 +129,7 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--vp-nav-height);
|
||||
padding: 0 12px;
|
||||
padding: 0 10px;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
@ -45,11 +45,18 @@ const list = computed(() => matter.value.list || [])
|
||||
<style scoped>
|
||||
.friends-wrapper {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - var(--vp-footer-height, 0px));
|
||||
padding-top: var(--vp-nav-height);
|
||||
padding-bottom: 5rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.friends-wrapper {
|
||||
min-height: calc(100vh - var(--vp-nav-height) - var(--vp-footer-height, 0px));
|
||||
}
|
||||
}
|
||||
|
||||
.friends-wrapper .title {
|
||||
padding-top: 3rem;
|
||||
padding-left: 1rem;
|
||||
|
||||
@ -32,7 +32,7 @@ const page = usePageData()
|
||||
.navbar-menu-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: var(--vp-nav-height);
|
||||
|
||||
@ -5,7 +5,7 @@ import type {
|
||||
PlumeThemePageData,
|
||||
PlumeThemePostFrontmatter,
|
||||
} from '../../shared/index.js'
|
||||
import { useReadingTime } from '../composables/index.js'
|
||||
import { useExtraBlogData, useReadingTime } from '../composables/index.js'
|
||||
import IconBooks from './icons/IconBooks.vue'
|
||||
import IconClock from './icons/IconClock.vue'
|
||||
import IconTag from './icons/IconTag.vue'
|
||||
@ -13,6 +13,7 @@ import IconTag from './icons/IconTag.vue'
|
||||
const page = usePageData<PlumeThemePageData>()
|
||||
const matter = usePageFrontmatter<PlumeThemePostFrontmatter>()
|
||||
const readingTime = useReadingTime()
|
||||
const extraData = useExtraBlogData()
|
||||
|
||||
const createTime = computed(() => {
|
||||
if (matter.value.createTime)
|
||||
@ -26,8 +27,12 @@ const categoryList = computed(() => {
|
||||
})
|
||||
|
||||
const tags = computed(() => {
|
||||
if (matter.value.tags)
|
||||
return matter.value.tags.slice(0, 4)
|
||||
if (matter.value.tags) {
|
||||
return matter.value.tags.slice(0, 4).map(tag => ({
|
||||
name: tag,
|
||||
colors: extraData.value.tagsColorsPreset[extraData.value.tagsColors[tag]],
|
||||
}))
|
||||
}
|
||||
|
||||
return []
|
||||
})
|
||||
@ -59,9 +64,13 @@ const hasMeta = computed(() => readingTime.value.times || tags.value.length || c
|
||||
</p>
|
||||
<p v-if="tags.length > 0">
|
||||
<IconTag class="icon" />
|
||||
<span v-for="(tag, index) in tags" :key="tag" class="tag">
|
||||
{{ tag }}
|
||||
<template v-if="index < tags.length - 1">,</template>
|
||||
<span
|
||||
v-for="tag in tags"
|
||||
:key="tag.name"
|
||||
class="tag"
|
||||
:style="{ '--vp-tag-color': tag.colors[0], '--vp-tag-bg-color': tag.colors[2] }"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="createTime" class="create-time">
|
||||
@ -129,11 +138,12 @@ const hasMeta = computed(() => readingTime.value.times || tags.value.length || c
|
||||
|
||||
.page-meta-wrapper .tag {
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
padding: 3px 5px;
|
||||
margin-right: 6px;
|
||||
line-height: 1;
|
||||
color: var(--vp-c-text-2);
|
||||
background-color: var(--vp-c-mute);
|
||||
border-radius: 4px;
|
||||
color: var(--vp-tag-color);
|
||||
background-color: var(--vp-tag-bg-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.page-meta-wrapper .tag:last-of-type {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
|
||||
import { useExtraBlogData } from '../composables/index.js'
|
||||
import AutoLink from './AutoLink.vue'
|
||||
import IconClock from './icons/IconClock.vue'
|
||||
import IconFolder from './icons/IconFolder.vue'
|
||||
@ -10,12 +11,19 @@ const props = defineProps<{
|
||||
post: PlumeThemeBlogPostItem
|
||||
}>()
|
||||
|
||||
const extraData = useExtraBlogData()
|
||||
|
||||
const categoryList = computed(() =>
|
||||
props.post.categoryList ?? [],
|
||||
)
|
||||
|
||||
const tags = computed(() =>
|
||||
(props.post.tags ?? []).slice(0, 4),
|
||||
(props.post.tags ?? [])
|
||||
.slice(0, 4)
|
||||
.map(tag => ({
|
||||
name: tag,
|
||||
colors: extraData.value.tagsColorsPreset[extraData.value.tagsColors[tag]],
|
||||
})),
|
||||
)
|
||||
|
||||
const createTime = computed(() =>
|
||||
@ -46,9 +54,13 @@ const createTime = computed(() =>
|
||||
</div>
|
||||
<div v-if="tags.length" class="tag-list">
|
||||
<IconTag class="icon" />
|
||||
<template v-for="(tag, i) in tags" :key="tag">
|
||||
<span class="tag">{{ tag }}</span>
|
||||
<span v-if="i !== tags.length - 1">,</span>
|
||||
<template v-for="tag in tags" :key="tag.name">
|
||||
<span
|
||||
class="tag"
|
||||
:style="{ '--vp-tag-color': tag.colors[0], '--vp-tag-bg-color': tag.colors[2] }"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="createTime" class="create-time">
|
||||
@ -99,7 +111,21 @@ const createTime = computed(() =>
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.post-item {
|
||||
padding: 24px 20px;
|
||||
margin: 0 0 24px 20px;
|
||||
background-color: var(--vp-c-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.post-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.post-item h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
@ -133,7 +159,17 @@ const createTime = computed(() =>
|
||||
}
|
||||
|
||||
.post-meta .tag-list .tag {
|
||||
margin: 0 0.2rem;
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
margin-right: 6px;
|
||||
line-height: 1;
|
||||
color: var(--vp-tag-color);
|
||||
background-color: var(--vp-tag-bg-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.post-meta .tag-list .tag:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.post-meta .icon {
|
||||
|
||||
@ -65,6 +65,7 @@ const {
|
||||
padding: 0 4px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
background-color: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-brand-1);
|
||||
border-radius: 4px;
|
||||
transition: all var(--t-color);
|
||||
|
||||
@ -1,14 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useSidebar, useThemeLocaleData } from '../composables/index.js'
|
||||
import { inBrowser } from '../utils/index.js'
|
||||
|
||||
const theme = useThemeLocaleData()
|
||||
const { hasSidebar } = useSidebar()
|
||||
|
||||
const footerHeight = useCssVar('--vp-footer-height', inBrowser ? document.body : null)
|
||||
const footer = ref<HTMLElement | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
if (theme.value.footer && footer.value)
|
||||
footerHeight.value = `${footer.value.offsetHeight}px`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<footer
|
||||
v-if="theme.footer"
|
||||
ref="footer"
|
||||
class="plume-footer"
|
||||
:class="{ 'has-sidebar': hasSidebar }"
|
||||
>
|
||||
@ -37,10 +48,6 @@ const { hasSidebar } = useSidebar()
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.plume-footer.has-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.plume-footer :deep(a) {
|
||||
text-decoration-line: underline;
|
||||
text-underline-offset: 2px;
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
>
|
||||
<path
|
||||
d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { usePageLang } from 'vuepress/client'
|
||||
import { useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
|
||||
import { computed } from 'vue'
|
||||
import { useExtraBlogData as _useExtraBlogData, useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
|
||||
import { type Ref, computed } from 'vue'
|
||||
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
|
||||
import { useLocaleLink, useRouteQuery, useThemeLocaleData } from '../composables/index.js'
|
||||
import { getRandomColor, toArray } from '../utils/index.js'
|
||||
import { toArray } from '../utils/index.js'
|
||||
|
||||
export const useExtraBlogData = _useExtraBlogData as () => Ref<{
|
||||
tagsColorsPreset: (readonly [string, string, string])[]
|
||||
tagsColors: Record<string, number>
|
||||
}>
|
||||
|
||||
const DEFAULT_PER_PAGE = 10
|
||||
|
||||
export function useLocalePostList() {
|
||||
const locale = usePageLang()
|
||||
@ -49,7 +56,7 @@ export function usePostListControl() {
|
||||
const totalPage = computed(() => {
|
||||
if (blog.value.pagination === false)
|
||||
return 0
|
||||
const perPage = blog.value.pagination?.perPage || 20
|
||||
const perPage = blog.value.pagination?.perPage || DEFAULT_PER_PAGE
|
||||
return Math.ceil(postList.value.length / perPage)
|
||||
})
|
||||
const isLastPage = computed(() => page.value >= totalPage.value)
|
||||
@ -60,7 +67,7 @@ export function usePostListControl() {
|
||||
if (blog.value.pagination === false)
|
||||
return postList.value
|
||||
|
||||
const perPage = blog.value.pagination?.perPage || 20
|
||||
const perPage = blog.value.pagination?.perPage || DEFAULT_PER_PAGE
|
||||
if (postList.value.length <= perPage)
|
||||
return postList.value
|
||||
|
||||
@ -96,6 +103,8 @@ const extractLocales: Record<string, { tags: string, archives: string }> = {
|
||||
export function useBlogExtract() {
|
||||
const theme = useThemeLocaleData()
|
||||
const locale = usePageLang()
|
||||
const postList = useLocalePostList()
|
||||
const { tags: tagsList } = useTags()
|
||||
|
||||
const hasBlogExtract = computed(() => theme.value.blog?.archives !== false || theme.value.blog?.tags !== false)
|
||||
const tagsLink = useLocaleLink('blog/tags/')
|
||||
@ -104,11 +113,13 @@ export function useBlogExtract() {
|
||||
const tags = computed(() => ({
|
||||
link: tagsLink.value,
|
||||
text: extractLocales[locale.value]?.tags || extractLocales.en.tags,
|
||||
total: tagsList.value.length,
|
||||
}))
|
||||
|
||||
const archives = computed(() => ({
|
||||
link: archiveLink.value,
|
||||
text: extractLocales[locale.value]?.archives || extractLocales.en.archives,
|
||||
total: postList.value.length,
|
||||
}))
|
||||
|
||||
return {
|
||||
@ -122,6 +133,9 @@ export type ShortPostItem = Pick<PlumeThemeBlogPostItem, 'title' | 'path' | 'cre
|
||||
|
||||
export function useTags() {
|
||||
const list = useLocalePostList()
|
||||
|
||||
const extraData = useExtraBlogData()
|
||||
|
||||
const tags = computed(() => {
|
||||
const tagMap: Record<string, number> = {}
|
||||
list.value.forEach((item) => {
|
||||
@ -137,7 +151,7 @@ export function useTags() {
|
||||
return Object.keys(tagMap).map(tag => ({
|
||||
name: tag,
|
||||
count: tagMap[tag] > 99 ? '99+' : tagMap[tag],
|
||||
color: getRandomColor(),
|
||||
colors: extraData.value.tagsColorsPreset[extraData.value.tagsColors[tag]],
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
@ -17,13 +17,13 @@ export const readingTimeLocales = {
|
||||
'zh-CN': {
|
||||
word: '约$word字',
|
||||
less1Minute: '小于1分钟',
|
||||
time: '大约$time分钟',
|
||||
time: '约$time分钟',
|
||||
},
|
||||
|
||||
'zh-TW': {
|
||||
word: '約$word字',
|
||||
less1Minute: '小於1分鐘',
|
||||
time: '大约$time分鐘',
|
||||
time: '约$time分鐘',
|
||||
},
|
||||
|
||||
'de': {
|
||||
|
||||
@ -273,7 +273,6 @@
|
||||
"Liberation Mono",
|
||||
"Courier New",
|
||||
monospace;
|
||||
--vp-header-anchor-symbol: "#";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -5,4 +5,3 @@ export * from './dom.js'
|
||||
export * from './resolveEditLink.js'
|
||||
export * from './resolveRepoType.js'
|
||||
export * from './base.js'
|
||||
export * from './color.js'
|
||||
|
||||
40
theme/src/node/blogTags.ts
Normal file
40
theme/src/node/blogTags.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { hasOwn, random, toArray } from '@pengzhanbo/utils'
|
||||
export type BlogTagsColorsItem = readonly [
|
||||
string, // normal color
|
||||
string, // hover color
|
||||
string, // background color
|
||||
]
|
||||
|
||||
export const BLOG_TAGS_COLORS_PRESET: BlogTagsColorsItem[] = [
|
||||
['#6aa1b7', '#5086a1', 'rgba(131, 208, 218, 0.314)'],
|
||||
['#299764', '#18794e', 'rgba(16, 185, 129, 0.14)'],
|
||||
['#946300', '#915930', 'rgba(234, 179, 8, 0.14)'],
|
||||
['#d5393e', '#b8272c', 'rgba(244, 63, 94, 0.14)'],
|
||||
['#7e4cc9', '#6f42c1', 'rgba(159, 122, 234, 0.14)'],
|
||||
['#3a5ccc', '#3451b2', 'rgba(100, 108, 255, 0.14)'],
|
||||
['#f1c40f', '#f39c12', 'rgba(255, 213, 0, 0.14)'],
|
||||
['#cc6699', '#c75191', 'rgba(255, 153, 204, 0.14)'],
|
||||
]
|
||||
|
||||
const len = BLOG_TAGS_COLORS_PRESET.length
|
||||
let prevIndex: number[] = []
|
||||
|
||||
function getRandom() {
|
||||
let index: number
|
||||
do
|
||||
index = random(0, len - 1)
|
||||
while (prevIndex.includes(index))
|
||||
prevIndex.push(index)
|
||||
prevIndex = prevIndex.slice(-5)
|
||||
return index
|
||||
}
|
||||
|
||||
export function generateBlogTagsColors(map: Record<string, any>, tags?: string[]) {
|
||||
if (!tags || tags.length === 0)
|
||||
return
|
||||
|
||||
toArray(tags).forEach((tag) => {
|
||||
if (!hasOwn(map, tag))
|
||||
map[tag] = getRandom()
|
||||
})
|
||||
}
|
||||
@ -15,12 +15,12 @@ import { caniusePlugin } from '@vuepress-plume/plugin-caniuse'
|
||||
import { copyCodePlugin } from '@vuepress-plume/plugin-copy-code'
|
||||
import { iconifyPlugin } from '@vuepress-plume/plugin-iconify'
|
||||
import { notesDataPlugin } from '@vuepress-plume/plugin-notes-data'
|
||||
import { shikijiPlugin } from '@vuepress-plume/plugin-shikiji'
|
||||
import { shikiPlugin } from '@vuepress-plume/plugin-shikiji'
|
||||
import { commentPlugin } from 'vuepress-plugin-comment2'
|
||||
import { type MarkdownEnhanceOptions, mdEnhancePlugin } from 'vuepress-plugin-md-enhance'
|
||||
import { readingTimePlugin } from 'vuepress-plugin-reading-time2'
|
||||
import { seoPlugin } from 'vuepress-plugin-seo2'
|
||||
import { sitemapPlugin } from 'vuepress-plugin-sitemap2'
|
||||
import { seoPlugin } from '@vuepress/plugin-seo'
|
||||
import { sitemapPlugin } from '@vuepress/plugin-sitemap'
|
||||
import { contentUpdatePlugin } from '@vuepress-plume/plugin-content-update'
|
||||
import type {
|
||||
PlumeThemeLocaleOptions,
|
||||
@ -32,6 +32,7 @@ import { pathJoin } from './utils.js'
|
||||
import { resolveNotesList } from './resolveNotesList.js'
|
||||
import { resolvedDocsearchOption, resolvedSearchOptions } from './searchPluginOptions.js'
|
||||
import { customContainers } from './container.js'
|
||||
import { BLOG_TAGS_COLORS_PRESET, generateBlogTagsColors } from './blogTags.js'
|
||||
|
||||
export function setupPlugins(
|
||||
app: App,
|
||||
@ -68,13 +69,21 @@ export function setupPlugins(
|
||||
pageFilter: (page: any) => page.frontmatter.article !== undefined
|
||||
? !!page.frontmatter.article
|
||||
: true,
|
||||
extendBlogData: (page: any) => ({
|
||||
categoryList: page.data.categoryList,
|
||||
tags: page.frontmatter.tags,
|
||||
sticky: page.frontmatter.sticky,
|
||||
createTime: page.data.frontmatter.createTime,
|
||||
lang: page.lang,
|
||||
}),
|
||||
extraBlogData(extra) {
|
||||
extra.tagsColorsPreset = BLOG_TAGS_COLORS_PRESET
|
||||
extra.tagsColors = {}
|
||||
},
|
||||
extendBlogData: (page: any, extra) => {
|
||||
const tags = page.frontmatter.tags
|
||||
generateBlogTagsColors(extra.tagsColors, tags)
|
||||
return {
|
||||
categoryList: page.data.categoryList,
|
||||
tags,
|
||||
sticky: page.frontmatter.sticky,
|
||||
createTime: page.data.frontmatter.createTime,
|
||||
lang: page.lang,
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
notesDataPlugin(notesList),
|
||||
@ -144,10 +153,11 @@ export function setupPlugins(
|
||||
plugins.push(searchPlugin(resolvedSearchOptions(app, options.search)))
|
||||
}
|
||||
|
||||
if (options.shikiji !== false) {
|
||||
plugins.push(shikijiPlugin({
|
||||
const shikiOption = options.shiki || options.shikiji
|
||||
if (shikiOption !== false) {
|
||||
plugins.push(shikiPlugin({
|
||||
theme: { light: 'vitesse-light', dark: 'vitesse-dark' },
|
||||
...(options.shikiji ?? {}),
|
||||
...(shikiOption ?? {}),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,10 @@ export interface PlumeThemeAvatar {
|
||||
* 描述
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* 是否显示为圆形头像
|
||||
*/
|
||||
circle?: boolean
|
||||
}
|
||||
|
||||
export interface SocialLink {
|
||||
|
||||
@ -4,7 +4,7 @@ import type { AutoFrontmatterOptions } from '@vuepress-plume/plugin-auto-frontma
|
||||
import type { BaiduTongjiOptions } from '@vuepress-plume/plugin-baidu-tongji'
|
||||
import type { CanIUsePluginOptions } from '@vuepress-plume/plugin-caniuse'
|
||||
import type { CopyCodeOptions } from '@vuepress-plume/plugin-copy-code'
|
||||
import type { ShikijiPluginOptions } from '@vuepress-plume/plugin-shikiji'
|
||||
import type { ShikiPluginOptions } from '@vuepress-plume/plugin-shikiji'
|
||||
import type { CommentPluginOptions } from 'vuepress-plugin-comment2'
|
||||
import type { MarkdownEnhanceOptions } from 'vuepress-plugin-md-enhance'
|
||||
import type { ReadingTimeOptions } from 'vuepress-plugin-reading-time2'
|
||||
@ -31,9 +31,15 @@ export interface PlumeThemePluginOptions {
|
||||
docsearch?: false | DocsearchOptions
|
||||
|
||||
/**
|
||||
* @deprecated move to `shiki`
|
||||
* 代码高亮 配置
|
||||
*/
|
||||
shikiji?: false | ShikijiPluginOptions
|
||||
shikiji?: false | ShikiPluginOptions
|
||||
|
||||
/**
|
||||
* 代码高亮 配置
|
||||
*/
|
||||
shiki?: false | ShikiPluginOptions
|
||||
|
||||
/**
|
||||
* git 插件 配置
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user