feat(theme): 优化文章标签颜色生成方式,所有文章支持标签
This commit is contained in:
parent
ddb6834a7c
commit
a0eac82f4e
@ -2,7 +2,6 @@ import type { BlogPostData } from '../shared/index.js'
|
||||
|
||||
declare module '@internal/blogData' {
|
||||
const blogPostData: BlogPostData
|
||||
const extraBlogData: Record<string, any>
|
||||
|
||||
export { blogPostData, extraBlogData }
|
||||
export { blogPostData }
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import {
|
||||
blogPostData as blogPostDataRaw,
|
||||
extraBlogData as extraBlogDataRaw,
|
||||
} from '@internal/blogData'
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
@ -18,19 +17,8 @@ export function useBlogPostData<
|
||||
return blogPostData as BlogDataRef<T>
|
||||
}
|
||||
|
||||
export type ExtraBlogDataRef = Ref<Record<string, any>>
|
||||
|
||||
export const extraBlogData: ExtraBlogDataRef = ref(extraBlogDataRaw)
|
||||
|
||||
export function useExtraBlogData(): ExtraBlogDataRef {
|
||||
return extraBlogData as ExtraBlogDataRef
|
||||
}
|
||||
|
||||
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
||||
__VUE_HMR_RUNTIME__.updateBlogData = (data: BlogPostData) => {
|
||||
blogPostData.value = data
|
||||
}
|
||||
__VUE_HMR_RUNTIME__.updateExtraBlogData = (data: Record<string, any>) => {
|
||||
extraBlogData.value = data
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,15 +9,11 @@ if (import.meta.webpackHot) {
|
||||
if (__VUE_HMR_RUNTIME__.updateBlogData) {
|
||||
__VUE_HMR_RUNTIME__.updateBlogData(blogPostData)
|
||||
}
|
||||
if (__VUE_HMR_RUNTIME__.updateExtraBlogData) {
|
||||
__VUE_HMR_RUNTIME__.updateExtraBlogData(extraBlogData)
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(({ blogPostData, extraBlogData }) => {
|
||||
import.meta.hot.accept(({ blogPostData }) => {
|
||||
__VUE_HMR_RUNTIME__.updateBlogData(blogPostData)
|
||||
__VUE_HMR_RUNTIME__.updateExtraBlogData(extraBlogData)
|
||||
})
|
||||
}
|
||||
`
|
||||
@ -56,15 +52,10 @@ export async function preparedBlogData(app: App, pageFilter: (id: string) => boo
|
||||
})
|
||||
}
|
||||
|
||||
const extraBlogData: Record<string, any> = {}
|
||||
|
||||
if (typeof options.extraBlogData === 'function')
|
||||
options.extraBlogData(extraBlogData)
|
||||
|
||||
const blogData: BlogPostData = pages.map((page: Page) => {
|
||||
let extended: Partial<BlogPostDataItem> = {}
|
||||
if (typeof options.extendBlogData === 'function')
|
||||
extended = options.extendBlogData(page, extraBlogData)
|
||||
extended = options.extendBlogData(page)
|
||||
|
||||
const data = {
|
||||
path: page.path,
|
||||
@ -87,9 +78,6 @@ export async function preparedBlogData(app: App, pageFilter: (id: string) => boo
|
||||
export const blogPostData = JSON.parse(${JSON.stringify(
|
||||
JSON.stringify(blogData),
|
||||
)});
|
||||
export const extraBlogData = JSON.parse(${JSON.stringify(
|
||||
JSON.stringify(extraBlogData),
|
||||
)});
|
||||
`
|
||||
|
||||
// inject HMR code
|
||||
|
||||
@ -5,10 +5,8 @@ export interface BlogDataPluginOptions {
|
||||
exclude?: string | string[]
|
||||
sortBy?: 'createTime' | false | (<T>(prev: T, next: T) => boolean)
|
||||
excerpt?: boolean
|
||||
extendBlogData?: <T = any>(page: T, extra: Record<string, any>) => Record<string, any>
|
||||
extendBlogData?: <T = any>(page: T) => Record<string, any>
|
||||
pageFilter?: (page: Page) => boolean
|
||||
|
||||
extraBlogData?: (extra: Record<string, any>) => void
|
||||
}
|
||||
|
||||
export type BlogPostData<T extends object = object> = BlogPostDataItem<T>[]
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import type { PlumeThemeBlogPostItem } from '../../../shared/index.js'
|
||||
import { useExtraBlogData } from '../../composables/index.js'
|
||||
import { useTagColors } from '../../composables/index.js'
|
||||
import AutoLink from '../AutoLink.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
post: PlumeThemeBlogPostItem
|
||||
}>()
|
||||
|
||||
const extraData = useExtraBlogData()
|
||||
const colors = useTagColors()
|
||||
|
||||
const categoryList = computed(() =>
|
||||
props.post.categoryList ?? [],
|
||||
@ -19,7 +19,7 @@ const tags = computed(() =>
|
||||
.slice(0, 4)
|
||||
.map(tag => ({
|
||||
name: tag,
|
||||
colors: extraData.value.tagsColorsPreset[extraData.value.tagsColors[tag]],
|
||||
className: `vp-tag-${colors.value[tag]}`,
|
||||
})),
|
||||
)
|
||||
|
||||
@ -55,7 +55,7 @@ const createTime = computed(() =>
|
||||
<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] }"
|
||||
:class="tag.className"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
@ -188,7 +188,7 @@ const createTime = computed(() =>
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
color: var(--vp-tag-color);
|
||||
background-color: var(--vp-tag-bg-color);
|
||||
background-color: var(--vp-tag-bg);
|
||||
border-radius: 3px;
|
||||
transition: color var(--t-color), background-color var(--t-color);
|
||||
}
|
||||
|
||||
@ -18,8 +18,7 @@ const { tags: tagsLink } = useBlogExtract()
|
||||
v-for="tag in tags"
|
||||
:key="tag.name"
|
||||
class="tag"
|
||||
:class="{ active: tag.name === currentTag }"
|
||||
:style="{ '--vp-tag-color': tag.colors[0], '--vp-tag-hover-color': tag.colors[1] }"
|
||||
:class="{ active: tag.name === currentTag, [tag.className]: true }"
|
||||
@click="handleTagClick(tag.name)"
|
||||
>
|
||||
<span class="tag-name">{{ tag.name }}</span>
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
import { usePageLang } from 'vuepress/client'
|
||||
import { useExtraBlogData as _useExtraBlogData, useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
|
||||
import { type Ref, computed } from 'vue'
|
||||
import { useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
|
||||
import { computed } from 'vue'
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
|
||||
import { useData, useLocaleLink, useRouteQuery } from '../composables/index.js'
|
||||
import { toArray } from '../utils/index.js'
|
||||
|
||||
export const useExtraBlogData = _useExtraBlogData as () => Ref<{
|
||||
tagsColorsPreset: (readonly [string, string, string])[]
|
||||
tagsColors: Record<string, number>
|
||||
}>
|
||||
import { useTagColors } from './tag-colors.js'
|
||||
|
||||
const DEFAULT_PER_PAGE = 10
|
||||
|
||||
@ -179,7 +175,7 @@ export type ShortPostItem = Pick<PlumeThemeBlogPostItem, 'title' | 'path' | 'cre
|
||||
export function useTags() {
|
||||
const list = useLocalePostList()
|
||||
|
||||
const extraData = useExtraBlogData()
|
||||
const colors = useTagColors()
|
||||
|
||||
const tags = computed(() => {
|
||||
const tagMap: Record<string, number> = {}
|
||||
@ -196,7 +192,7 @@ export function useTags() {
|
||||
return Object.keys(tagMap).map(tag => ({
|
||||
name: tag,
|
||||
count: tagMap[tag] > 99 ? '99+' : tagMap[tag],
|
||||
colors: extraData.value.tagsColorsPreset[extraData.value.tagsColors[tag]] ?? [],
|
||||
className: `vp-tag-${colors.value[tag]}`,
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ export * from './sidebar.js'
|
||||
export * from './aside.js'
|
||||
export * from './page.js'
|
||||
export * from './blog.js'
|
||||
export * from './tag-colors.js'
|
||||
export * from './locale.js'
|
||||
export * from './useRouteQuery.js'
|
||||
export * from './watermark.js'
|
||||
|
||||
16
theme/src/client/composables/tag-colors.ts
Normal file
16
theme/src/client/composables/tag-colors.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { articleTagColors } from '@internal/articleTagColors'
|
||||
import { type Ref, ref } from 'vue'
|
||||
|
||||
export type TagColors = Record<string, string>
|
||||
|
||||
export type TagColorsRef = Ref<TagColors>
|
||||
|
||||
const tagColorsRef: TagColorsRef = ref(articleTagColors)
|
||||
|
||||
export const useTagColors = (): TagColorsRef => tagColorsRef
|
||||
|
||||
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
|
||||
__VUE_HMR_RUNTIME__.updateArticleTagColor = (data: TagColors) => {
|
||||
tagColorsRef.value = data
|
||||
}
|
||||
}
|
||||
7
theme/src/client/shim.d.ts
vendored
7
theme/src/client/shim.d.ts
vendored
@ -6,3 +6,10 @@ declare module '*.vue' {
|
||||
}
|
||||
|
||||
declare const __VUEPRESS_DEV__: string
|
||||
|
||||
declare module '@internal/articleTagColors' {
|
||||
const articleTagColors: Record<string, string>
|
||||
export {
|
||||
articleTagColors,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
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)'],
|
||||
['#fab10f', '#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()
|
||||
})
|
||||
}
|
||||
@ -6,10 +6,6 @@ import {
|
||||
} from '../config/index.js'
|
||||
import { normalizePath } from '../utils.js'
|
||||
import type { PlumeThemeEncrypt, PlumeThemeLocaleOptions } from '../..//shared/index.js'
|
||||
import {
|
||||
BLOG_TAGS_COLORS_PRESET,
|
||||
generateBlogTagsColors,
|
||||
} from './blogTags.js'
|
||||
|
||||
export function resolveBlogDataOptions(
|
||||
localeOptions: PlumeThemeLocaleOptions,
|
||||
@ -35,13 +31,8 @@ export function resolveBlogDataOptions(
|
||||
pageFilter: (page: any) => page.frontmatter.article !== undefined
|
||||
? !!page.frontmatter.article
|
||||
: true,
|
||||
extraBlogData(extra) {
|
||||
extra.tagsColorsPreset = BLOG_TAGS_COLORS_PRESET
|
||||
extra.tagsColors = {}
|
||||
},
|
||||
extendBlogData: (page: any, extra) => {
|
||||
extendBlogData: (page: any) => {
|
||||
const tags = page.frontmatter.tags
|
||||
generateBlogTagsColors(extra.tagsColors, tags)
|
||||
const data: Record<string, any> = {
|
||||
categoryList: page.data.categoryList,
|
||||
tags,
|
||||
|
||||
20
theme/src/node/prepare/index.ts
Normal file
20
theme/src/node/prepare/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { App } from 'vuepress'
|
||||
import { watch } from 'chokidar'
|
||||
import { prepareArticleTagColors } from './prepareArticleTagColor.js'
|
||||
|
||||
export async function setupPrepare(app: App): Promise<void> {
|
||||
await prepareArticleTagColors(app)
|
||||
}
|
||||
|
||||
export function watchPrepare(app: App, watchers: any[]): void {
|
||||
const watcher = watch('pages/**', {
|
||||
cwd: app.dir.temp(),
|
||||
ignoreInitial: true,
|
||||
})
|
||||
|
||||
watcher.on('change', () => prepareArticleTagColors(app))
|
||||
watcher.on('add', () => prepareArticleTagColors(app))
|
||||
watcher.on('unlink', () => prepareArticleTagColors(app))
|
||||
|
||||
watchers.push(watcher)
|
||||
}
|
||||
108
theme/src/node/prepare/prepareArticleTagColor.ts
Normal file
108
theme/src/node/prepare/prepareArticleTagColor.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { toArray } from '@pengzhanbo/utils'
|
||||
import type { App } from 'vuepress'
|
||||
import { nanoid } from '../utils.js'
|
||||
|
||||
export type TagsColorsItem = readonly [
|
||||
string, // normal color
|
||||
string, // hover color
|
||||
string, // background color
|
||||
]
|
||||
|
||||
export const PRESET: TagsColorsItem[] = [
|
||||
['#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)'],
|
||||
['#fab10f', '#f39c12', 'rgba(255, 213, 0, 0.14)'],
|
||||
['#cc6699', '#c75191', 'rgba(255, 153, 204, 0.14)'],
|
||||
['#55AAEE', '#0088CC', 'rgba(0, 136, 204, 0.14)'],
|
||||
['#AA66CC', '#9933CC', 'rgba(153, 121, 204, 0.14)'],
|
||||
['#9933CC', '#993399', 'rgba(153, 151, 204, 0.14)'],
|
||||
['#CC9999', '#CC8888', 'rgba(204, 153, 153, 0.14)'],
|
||||
['#9999CC', '#9999FF', 'rgba(153, 153, 204, 0.14)'],
|
||||
['#66CCCC', '#66CCAA', 'rgba(102, 204, 204, 0.14)'],
|
||||
['#CCBC99', '#CCAA99', 'rgba(204, 204, 153, 0.14)'],
|
||||
]
|
||||
|
||||
const HMR_CODE = `
|
||||
if (import.meta.webpackHot) {
|
||||
import.meta.webpackHot.accept()
|
||||
if (__VUE_HMR_RUNTIME__.updateArticleTagColors) {
|
||||
__VUE_HMR_RUNTIME__.updateArticleTagColor(articleTagColors)
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept(({ articleTagColors }) => {
|
||||
__VUE_HMR_RUNTIME__.updateArticleTagColor(articleTagColors)
|
||||
})
|
||||
}
|
||||
`
|
||||
|
||||
// { index: className }
|
||||
const cache: Record<number, string> = {}
|
||||
|
||||
export async function prepareArticleTagColors(app: App): Promise<void> {
|
||||
const articleTagColors: Record<string, string> = {}
|
||||
const tagList = new Set<string>()
|
||||
|
||||
app.pages.forEach((page) => {
|
||||
const { frontmatter: { tags } } = page
|
||||
if (tags) {
|
||||
toArray(tags).forEach((tag) => {
|
||||
tag && tagList.add(tag as string)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
tagList.forEach((tag) => {
|
||||
const code = getTagCode(tag)
|
||||
if (!cache[code]) {
|
||||
cache[code] = nanoid(4)
|
||||
}
|
||||
if (!articleTagColors[tag]) {
|
||||
articleTagColors[tag] = cache[code]
|
||||
}
|
||||
})
|
||||
|
||||
let code = `\
|
||||
import './articleTagColors.css'
|
||||
export const articleTagColors = ${JSON.stringify(articleTagColors)}
|
||||
`
|
||||
if (app.env.isDev) {
|
||||
code += HMR_CODE
|
||||
}
|
||||
|
||||
await app.writeTemp('internal/articleTagColors.css', genTagColorsStyle())
|
||||
await app.writeTemp('internal/articleTagColors.ts', code)
|
||||
}
|
||||
|
||||
function getTagCode(tag: string): number {
|
||||
tag = tag.toLowerCase()
|
||||
let code = 0
|
||||
for (let i = 0; i < tag.length; i++) {
|
||||
code += tag.charCodeAt(i)
|
||||
}
|
||||
return code % PRESET.length
|
||||
}
|
||||
|
||||
function genTagColorsStyle(): string {
|
||||
let css = ''
|
||||
|
||||
for (const [code, className] of Object.entries(cache)) {
|
||||
const index = Number(code)
|
||||
const [color, hoverColor, backgroundColor] = PRESET[index]
|
||||
|
||||
css += `\
|
||||
.vp-tag-${className} {
|
||||
--vp-tag-color: ${color};
|
||||
--vp-tag-hover-color: ${hoverColor};
|
||||
--vp-tag-bg: ${backgroundColor};
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
return css
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"rootDir": "./src",
|
||||
"types": ["vuepress/client-types", "vite/client"],
|
||||
"types": ["vuepress/client-types", "vite/client", "webpack-env"],
|
||||
"outDir": "./lib"
|
||||
},
|
||||
"files": [],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user