Merge pull request #34 from pengzhanbo/RC-9

RC-9
This commit is contained in:
pengzhanbo 2023-12-31 00:13:30 +08:00 committed by GitHub
commit 3760ba2ecd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 423 additions and 112 deletions

View File

@ -8,6 +8,8 @@
"[markdown]": {
"files.trimTrailingWhitespace": false
},
"css.validate": false,
"scss.validate": false,
"typescript.tsdk": "node_modules/typescript/lib",
"cSpell.words": [
"bumpp",

View File

@ -44,6 +44,13 @@ _斜体文字_
数学表达式: $-(2^{n-1})$ ~ $2^{n-1} -1$
$\frac {\partial^r} {\partial \omega^r} \left(\frac {y^{\omega}} {\omega}\right)
= \left(\frac {y^{\omega}} {\omega}\right) \left\{(\log y)^r + \sum_{i=1}^r \frac {(-1)^ Ir \cdots (r-i+1) (\log y)^{ri}} {\omega^i} \right\}$
19^th^
H~2~O
::: center
内容居中
:::
@ -81,6 +88,8 @@ _斜体文字_
![image](/images/1px-lines.png)
![image](/images/1px-lines.png)
**Badge**
- <Badge type="info" text="info badge" />
@ -91,7 +100,7 @@ _斜体文字_
**图标**
- home - <Iconify name="material-symbols:home" color="currentColor" size="1em" />
- home - <Icon name="material-symbols:home" color="currentColor" size="1em" />
- vscode - <Iconify name="skill-icons:vscode-dark" size="2em" />
- twitter - <Iconify name="skill-icons:twitter" size="2em" />
@ -167,18 +176,38 @@ function foo() {
::: tip 提示
提示内容
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: warning 警告
警告内容
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: caution 错误
错误内容
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: important 重要
重要内容
```js
const a = 1
const b = 2
const c = a + b
```
:::
::: info

View File

@ -86,6 +86,7 @@ __示例 获取 css 伪类选择器 `:dir()` 在各个浏览器的支持情
```
效果:
::: caniuse css-matches-pseudo
:::

View File

@ -1,34 +1,59 @@
# `@vuepress-plume/plugin-caniuse`
# vuepress-plugin-caniuse
VuePress 2 Plugin
VuePress 2 插件
在Markdown中添加 [can-i-use](https://caniuse.com/) 支持这对于你在写前端技术博客时说明某个feature的兼容性时特别有用。
为 markdown 添加 can-i-use 容器支持
## Install
```
``` sh
yarn add @vuepress-plume/plugin-caniuse
```
## Usage
### 在VuePress 配置文件中添加插件
``` js
// .vuepress/config.js
const { caniusePlugin } = require('@vuepress-plume/plugin-caniuse')
module.exports = {
export default {
// ...
plugins: [
caniusePlugin({ mode: 'embed' })
['@vuepress-plume/plugin-caniuse', { mode: 'image' }]
]
// ...
}
```
### options
- mode: 渲染模式,默认值 `embed`
- embed: 交互式嵌入,通过 iframe 嵌入可交互的 can-i-use
- image: 仅添加 图片
### 在markdown中编写
``` md
::: caniuse css-matches-pseudo
::: caniuse <feature> {{browser_versions}}
:::
```
## 效果
### Options
![css-dir-pseudo](https://caniuse.bitsofco.de/image/css-dir-pseudo.webp)
- `options.mode`: can-i-use插入文档的模式 支持 `embed``image`, 默认值是 `image`
- `image`: 插入图片
- `embed`: 使用iframe嵌入 can-i-use
### \<feature>
正确取值请参考 [https://caniuse.bitsofco.de/](https://caniuse.bitsofco.de/)
### \{browser_versions\}`
可选。当前特性在多个版本中的支持情况。
格式: `{number,number,...}` 取值范围为 `-5 ~ 3`
- 小于`0` 表示低于当前浏览器版本的支持情况
- `0` 表示当前浏览器版本的支持情况
- 大于`0` 表示高于当前浏览器版本的支持情况
## Example
``` md
::: caniuse css-matches-pseudo {-2,-1,1}
:::
```
效果:
![can-i-use css-matches-pseudo](https://caniuse.bitsofco.de/image/css-dir-pseudo.webp)

View File

@ -1,49 +0,0 @@
# vuepress-plugin-caniuse
VuePress 2 Plugin
VuePress 2 插件
在Markdown中添加 [can-i-use](https://caniuse.com/) 支持这对于你在写前端技术博客时说明某个feature的兼容性时特别有用。
## Install
``` sh
yarn add @vuepress-plume/plugin-caniuse
```
## Usage
### 在VuePress 配置文件中添加插件
``` js
// .vuepress/config.js
export default {
// ...
plugins: [
['@vuepress-plume/plugin-caniuse', { mode: 'image' }]
]
// ...
}
```
### 在markdown中编写
``` md
::: caniuse <feature>
:::
```
### Options
- `options.mode`: can-i-use插入文档的模式 支持 `embed``image`, 默认值是 `image`
- `image`: 插入图片
- `embed`: 使用iframe嵌入 can-i-use
### \<feature>
正确取值请参考 [https://caniuse.bitsofco.de/](https://caniuse.bitsofco.de/)
## Example
``` md
::: caniuse css-matches-pseudo
:::
```
效果:
![can-i-use css-matches-pseudo](https://caniuse.bitsofco.de/image/css-dir-pseudo.webp)

View File

@ -9,10 +9,11 @@ export function resolveCanIUse(): void {
if (typeof data === 'string' && data.includes('ciu_embed')) {
const [, feature, height] = data.split(':')
const el = document.querySelector(`.ciu_embed[data-feature="${feature}"]`)
const el = document.querySelector(`.ciu_embed[data-feature="${feature}"]:not([data-skip])`)
if (el) {
const h = Number.parseInt(height) + 30
;(el.childNodes[0] as any).height = `${h}px`
el.setAttribute('data-skip', 'true')
}
}
})

View File

@ -30,8 +30,10 @@ export function caniusePlugin({
const render = (tokens: Token[], index: number): string => {
const token = tokens[index]
if (token.nesting === 1) {
const feature = token.info.trim().slice(type.length).trim() || ''
return feature ? resolveCanIUse(feature, mode) : ''
const info = token.info.trim().slice(type.length).trim() || ''
const feature = info.split(/\s+/)[0]
const versions = info.match(/\{(.*)\}/)?.[1] || ''
return feature ? resolveCanIUse(feature, mode, versions) : ''
}
else {
return ''

View File

@ -1,6 +1,6 @@
import type { CanIUseMode } from '../shared/index.js'
export function resolveCanIUse(feature: string, mode: CanIUseMode): string {
export function resolveCanIUse(feature: string, mode: CanIUseMode, versions: string): string {
if (!feature)
return ''
@ -12,7 +12,7 @@ export function resolveCanIUse(feature: string, mode: CanIUseMode): string {
</picture>`
}
const periods = 'future_2,future_1,current,past_1,past_2'
const periods = resolveVersions(versions)
const accessible = 'false'
const image = 'none'
const url = 'https://caniuse.bitsofco.de/embed/index.html'
@ -20,3 +20,27 @@ export function resolveCanIUse(feature: string, mode: CanIUseMode): string {
return `<div class="ciu_embed" style="margin:16px 0" data-feature="${feature}"><iframe src="${src}" frameborder="0" width="100%" height="400px"></iframe></div>`
}
function resolveVersions(versions: string): string {
if (!versions)
return 'future_1,current,past_1,past_2'
const list = versions
.split(',')
.map(v => Number(v.trim()))
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3)
list.push(0)
const uniq = [...new Set(list)].sort((a, b) => b - a)
const result: string[] = []
uniq.forEach((v) => {
if (v < 0)
result.push(`past_${Math.abs(v)}`)
if (v === 0)
result.push('current')
if (v > 0)
result.push(`future_${v}`)
})
return result.join(',')
}

View File

@ -28,9 +28,9 @@ export const Content = defineComponent({
return () =>
pageComponent.value
? h(pageComponent.value, {
onVnodeMounted: runCallbacks,
onVnodeUpdated: runCallbacks,
onVnodeBeforeUnmount: runCallbacks,
onVnodeMounted: () => runCallbacks({ mounted: true }),
onVnodeUpdated: () => runCallbacks({ updated: true }),
onVnodeBeforeUnmount: () => runCallbacks({ beforeUnmount: true }),
})
: h(
'div',

View File

@ -1,7 +1,12 @@
import { onUnmounted } from 'vue'
// eslint-disable-next-line import/no-mutable-exports
export let contentUpdatedCallbacks: (() => any)[] = []
export interface ContentUpdated {
mounted?: boolean
updated?: boolean
beforeUnmount?: boolean
}
let contentUpdatedCallbacks: ((lifeCircleType: ContentUpdated) => any)[] = []
/**
* Register callback that is called every time the markdown content is updated
@ -14,6 +19,6 @@ export function onContentUpdated(fn: () => any) {
})
}
export function runCallbacks() {
contentUpdatedCallbacks.forEach(fn => fn())
export function runCallbacks(lifeCircleType: ContentUpdated) {
contentUpdatedCallbacks.forEach(fn => fn(lifeCircleType))
}

View File

@ -1,15 +1,23 @@
<script lang="ts" setup>
import { Icon as OfflineIcon } from '@iconify/vue/offline'
import { ClientOnly } from '@vuepress/client'
import type { CSSProperties } from 'vue'
import type { IconifyRenderMode } from '@iconify/vue'
import type { StyleValue } from 'vue'
import { computed, toRefs } from 'vue'
import { useIconify } from '../composables/iconify.js'
const props = withDefaults(
defineProps<{
name?: string
size?: string
size?: string | number
color?: string
mode?: IconifyRenderMode
style?: StyleValue
flip?: string
vFlip?: boolean
hFlip?: boolean
inline?: boolean
rotate?: number
}>(),
{
name: '',
@ -29,14 +37,21 @@ const size = computed(() => {
return size
})
const iconStyle = computed(() => {
const style: CSSProperties = {
color: props.color || __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_COLOR__,
width: size.value,
height: size.value,
}
return style
})
const color = computed(() => props.color || __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_COLOR__)
const bind = computed<any>(() => ({
icon: icon.value,
mode: props.mode,
inline: props.inline,
rotate: props.rotate,
flip: props.flip,
vFlip: props.vFlip,
hFlip: props.hFlip,
color: props.color,
width: size.value,
height: size.value,
style: props.style,
}))
</script>
<script lang="ts">
@ -46,12 +61,11 @@ declare const __VUEPRESS_PLUGIN_ICONIFY_DEFAULT_COLOR__: string
<template>
<ClientOnly>
<span v-if="!loaded" class="vp-iconify" :style="iconStyle" />
<span v-if="!loaded" class="vp-iconify" :style="{ color, width: size, height: size }" />
<OfflineIcon
v-else-if="icon"
:icon="icon"
class="vp-iconify"
:style="iconStyle"
v-bind="bind"
/>
</ClientOnly>
</template>

View File

@ -0,0 +1,174 @@
<script lang="ts" setup>
import { useElementSize, useWindowScroll, useWindowSize } from '@vueuse/core'
import { computed, onMounted, ref, shallowRef, watch } from 'vue'
import { usePageData } from '@vuepress/client'
import type { PlumeThemePageData } from '../../shared/index.js'
import IconBackToTop from './icons/IconBackToTop.vue'
const body = shallowRef<HTMLElement | null>()
const { height: bodyHeight } = useElementSize(body)
const { height: windowHeight } = useWindowSize()
onMounted(() => {
body.value = document.body
})
const page = usePageData<PlumeThemePageData>()
const { y } = useWindowScroll()
const isScrolling = ref(false)
const progress = computed(
() => (y.value / (bodyHeight.value - windowHeight.value)) * 100,
)
const percent = computed(() => `${Math.round(progress.value)}%`)
const stroke = computed(() =>
`calc(${Math.PI * progress.value}% - ${4 * Math.PI}px) calc(${Math.PI * 100}% - ${4 * Math.PI}px)`,
)
const mustHidden = computed(() => {
return page.value.frontmatter.backToTop === false || page.value.frontmatter.home
})
const show = computed(() => {
if (bodyHeight.value < windowHeight.value)
return false
else
return y.value > windowHeight.value / 2
})
// eslint-disable-next-line no-undef
let timer: NodeJS.Timeout | null = null
function resetScrolling() {
timer && clearTimeout(timer)
timer = setTimeout(() => {
isScrolling.value = false
}, 1000)
}
watch(y, () => {
isScrolling.value = true
resetScrolling()
})
function handleClick() {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
</script>
<template>
<Transition name="fade">
<button
v-show="!mustHidden && (show || isScrolling)"
type="button"
class="back-to-top-button"
aria-label="back to top"
@click="handleClick"
>
<span class="percent" :class="{ show: isScrolling }">{{ percent }}</span>
<IconBackToTop class="icon" :class="{ show: !isScrolling }" />
<svg aria-hidden="true">
<circle cx="50%" cy="50%" :style="{ 'stroke-dasharray': stroke }" />
</svg>
</button>
</Transition>
</template>
<style scoped>
.back-to-top-button {
position: fixed;
right: 20px;
bottom: 64px;
z-index: var(--vp-z-index-back-to-top);
width: 36px;
height: 36px;
border-radius: 100%;
box-shadow: var(--vp-shadow-2);
background-color: var(--vp-c-bg);
inset-inline-end: 1rem;
transition: background 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
}
.back-to-top-button .percent,
.back-to-top-button .icon {
position: absolute;
top: 0;
left: 0;
transition: opacity 0.5s ease;
opacity: 0;
}
.back-to-top-button .percent.show,
.back-to-top-button .icon.show {
opacity: 1;
}
.back-to-top-button .percent {
width: 100%;
height: 100%;
user-select: none;
line-height: 36px;
text-align: center;
font-size: 10px;
}
.back-to-top-button .icon {
width: 18px;
height: 18px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--vp-c-text-3);
}
.back-to-top-button svg {
width: 100%;
height: 100%;
}
.back-to-top-button svg circle {
fill: none;
stroke: var(--vp-c-brand-2);
transform: rotate(-90deg);
transform-origin: 50% 50%;
r: 16;
stroke-dasharray: 0% 314.1593%;
stroke-width: 4px;
}
@media (min-width: 768px) {
.back-to-top-button {
width: 48px;
height: 48px;
}
.back-to-top-button .percent {
line-height: 48px;
font-size: 14px;
}
.back-to-top-button .icon {
width: 24px;
height: 24px;
}
.back-to-top-button svg circle {
r: 22;
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
@media print {
.back-to-top-button {
display: none;
}
}
</style>

View File

@ -111,12 +111,18 @@ const showBlogExtract = computed(() => {
}
}
@media print {
.blog-extract {
display: none;
}
}
.blog-modal {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: var(--vp-z-index-sidebar);
z-index: var(--vp-z-index-overlay);
width: 100%;
opacity: 1;
background-color: rgba(0, 0, 0, 0.3);
@ -140,11 +146,15 @@ const showBlogExtract = computed(() => {
background-color: var(--vp-c-bg);
border-top-left-radius: 12px;
border-top-right-radius: 12px;
box-shadow: 0 -3px 12px rgba(0, 0, 0, 0.1), 0 -1px 4px rgba(0, 0, 0, 0.1);;
box-shadow: 0 -3px 12px rgba(0, 0, 0, 0.1), 0 -1px 4px rgba(0, 0, 0, 0.1);
transform: translateY(100%);
transition: transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}
.dark .blog-modal-container {
box-shadow: 0 -3px 12px rgba(0, 0, 0, 0.3), 0 -1px 4px rgba(0, 0, 0, 0.27);
}
.blog-modal-container.open {
transform: translateY(0);
}

View File

@ -99,6 +99,12 @@ const showLocalNav = computed(() => {
}
}
@media print {
.local-nav {
display: none;
}
}
.menu {
display: flex;
align-items: center;

View File

@ -23,6 +23,7 @@
}
}
/* plugin-docsearch */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand-1);
--docsearch-highlight-color: var(--docsearch-primary-color);
@ -211,4 +212,13 @@
.dark .DocSearch-Form {
background-color: var(--vp-c-bg-soft);
}
/* plugin-docsearch */
/* plugin-search */
.navbar-search .search-box input {
padding: 0 0.3rem 0 1.575rem;
background-position: 0.5rem 0.4rem;
}
/* plugin-search */
</style>

View File

@ -1,6 +1,8 @@
<script lang="ts" setup>
import { usePageData } from '@vuepress/client'
import { computed } from 'vue'
import { useMediumZoom } from '@vuepress/plugin-medium-zoom/client'
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
import type { PlumeThemePageData } from '../../shared/index.js'
import { useDarkMode, useSidebar } from '../composables/index.js'
import PageAside from './PageAside.vue'
@ -10,9 +12,14 @@ import PageMeta from './PageMeta.vue'
const { hasSidebar, hasAside } = useSidebar()
const isDark = useDarkMode()
const page = usePageData<PlumeThemePageData>()
const hasComments = computed(() => {
return page.value.frontmatter.comments !== false
})
const zoom = useMediumZoom()
onContentUpdated(() => zoom?.refresh())
</script>
<template>

View File

@ -1,6 +1,12 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { useContributors, useEditNavLink, useLastUpdated, usePageNav, useThemeLocaleData } from '../composables/index.js'
import {
useContributors,
useEditNavLink,
useLastUpdated,
usePageNav,
useThemeLocaleData,
} from '../composables/index.js'
import AutoLink from './AutoLink.vue'
import IconEdit from './icons/IconEdit.vue'
@ -117,9 +123,17 @@ const showFooter = computed(() => {
}
.contributors {
margin-top: -10px;
padding-bottom: 6px;
line-height: 32px;
font-size: 14px;
text-align: left;
}
@media (min-width: 768px) {
.contributors {
text-align: right;
}
}
.contributors-label {
@ -131,7 +145,7 @@ const showFooter = computed(() => {
.contributors-info {
color: var(--vp-c-text-2);
.contributor {
color: var(--vp-c-brand-2);
color: var(--vp-c-text-3);
}
}

View File

@ -19,6 +19,7 @@ const { tags: tagsLink } = useBlogExtract()
:key="tag.name"
class="tag"
:class="{ active: tag.name === currentTag }"
:style="{ '--vp-tag-color': tag.color }"
@click="handleTagClick(tag.name)"
>
<span class="tag-name">{{ tag.name }}</span>
@ -68,8 +69,10 @@ const { tags: tagsLink } = useBlogExtract()
word-wrap: break-word;
margin: 8px;
padding: 6px 6px 6px 10px;
background-color: var(--vp-c-default-soft);
color: var(--vp-c-text-2);
/* background-color: var(--vp-c-default-soft); */
/* color: var(--vp-c-text-2); */
color: var(--vp-tag-color);
border: solid 1px var(--vp-tag-color);
line-height: 1;
border-radius: 4px;
cursor: pointer;
@ -84,27 +87,24 @@ const { tags: tagsLink } = useBlogExtract()
margin: 20px 12px -10px 12px;
}
.tag-name {
font-weight: 600;
}
.tag-count {
display: inline-block;
border-left: 1px solid var(--vp-c-divider);
border-left: 1px solid var(--vp-tag-color);
padding-left: 6px;
margin-left: 4px;
color: var(--vp-c-text-3);
color: var(--vp-tag-color);
transition: all var(--t-color);
}
.tags .tag:hover,
.tags .tag.active {
background-color: var(--vp-c-brand-1);
background-color: var(--vp-tag-color);
color: var(--vp-c-bg);
}
.tags .tag:hover .tag-count,
.tags .tag.active .tag-count {
color: var(--vp-bg);
border-left-color: var(--vp-c-divider);
}
</style>

View File

@ -0,0 +1,3 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M24.008 14.1V42M12 26l12-12l12 12M12 6h24" /></svg>
</template>

View File

@ -4,7 +4,7 @@ import { computed, ref } from 'vue'
import type { Ref } from 'vue'
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
import { useLocaleLink, useThemeLocaleData } from '../composables/index.js'
import { toArray } from '../utils/index.js'
import { getRandomColor, toArray } from '../utils/index.js'
export function usePostListControl() {
const locale = usePageLang()
@ -129,7 +129,8 @@ export function useTags() {
})
return Object.keys(tagMap).map(tag => ({
name: tag,
count: tagMap[tag],
count: tagMap[tag] > 99 ? '99+' : tagMap[tag],
color: getRandomColor(),
}))
})

View File

@ -14,6 +14,7 @@ import Page from '../components/Page.vue'
import Sidebar from '../components/Sidebar.vue'
import SkipLink from '../components/SkipLink.vue'
import VFooter from '../components/VFooter.vue'
import BackToTop from '../components/BackToTop.vue'
import {
useCloseSidebarOnEscape,
useSidebar,
@ -56,6 +57,7 @@ provide('is-sidebar-open', isSidebarOpen)
<Friends v-else-if="page.frontmatter.friends" />
<Blog v-else-if="isBlogLayout" />
<Page v-else />
<BackToTop />
<VFooter />
</LayoutContent>
</div>

View File

@ -1,5 +1 @@
@import '@vuepress/plugin-palette/palette';
$codeLang: 'c' 'cpp' 'cs' 'dart' 'docker' 'fs' 'go' 'java' 'kt' 'makefile' 'css'
'less' 'sass' 'scss' 'styl' 'html' 'js' 'json' 'ts' 'vue' 'jsx' 'md' 'php'
'py' 'rb' 'rs' 'sh' 'toml' 'yml' !default;

View File

@ -87,7 +87,12 @@
}
}
&.details code {
&.note code,
&.info code,
&.tip code,
&.warning code,
&.caution code,
&.details, code {
background-color: transparent;
}

View File

@ -259,6 +259,14 @@
--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, 0.16), 0 4px 12px rgba(0, 0, 0, 0.16);
}
.dark {
--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.27), 0 1px 2px rgba(0, 0, 0, 0.22);
--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.27);
--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.3);
--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, 0.39), 0 3px 9px rgba(0, 0, 0, 0.35);
--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, 0.42), 0 4px 12px rgba(0, 0, 0, 0.38);
}
/**
* Z-indexes
* -------------------------------------------------------------------------- */
@ -270,6 +278,8 @@
--vp-z-index-layout-top: 40;
--vp-z-index-backdrop: 50;
--vp-z-index-sidebar: 60;
--vp-z-index-back-to-top: 70;
--vp-z-index-overlay: 80;
}
/**
@ -543,7 +553,7 @@ html[lang='zh-CN'] {
/** Component: Search **/
:root {
--search-bg-color: var(--vp-c-bg-alt);
--search-bg-color: var(--vp-c-default-soft);
--search-text-color: var(--vp-c-text-2);
--search-item-text-color: var(--vp-c-text-1);
--search-item-focus-bg-color: var(--vp-c-bg-alt);

View File

@ -0,0 +1,17 @@
const colorList = [
'var(--vp-c-brand-1)',
'var(--vp-c-brand-2)',
'var(--vp-c-green-1)',
'var(--vp-c-green-2)',
'var(--vp-c-green-3)',
'var(--vp-c-yellow-1)',
'var(--vp-c-yellow-2)',
'var(--vp-c-yellow-3)',
'var(--vp-c-red-1)',
'var(--vp-c-red-2)',
'var(--vp-c-red-3)',
]
export function getRandomColor() {
return colorList[Math.floor(Math.random() * colorList.length)]
}

View File

@ -5,3 +5,4 @@ export * from './dom.js'
export * from './resolveEditLink.js'
export * from './resolveRepoType.js'
export * from './base.js'
export * from './color.js'

View File

@ -26,6 +26,7 @@ export interface PlumeThemePageFrontmatter {
contributors?: boolean
prev?: string | NavItemWithLink
next?: string | NavItemWithLink
backToTop?: boolean
}
export interface PlumeThemePostFrontmatter extends PlumeThemePageFrontmatter {