mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
commit
3760ba2ecd
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -8,6 +8,8 @@
|
||||
"[markdown]": {
|
||||
"files.trimTrailingWhitespace": false
|
||||
},
|
||||
"css.validate": false,
|
||||
"scss.validate": false,
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"cSpell.words": [
|
||||
"bumpp",
|
||||
|
||||
@ -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 @@ _斜体文字_
|
||||
|
||||

|
||||
|
||||

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

|
||||
- `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}
|
||||
:::
|
||||
```
|
||||
效果:
|
||||

|
||||
|
||||
@ -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
|
||||
:::
|
||||
```
|
||||
效果:
|
||||

|
||||
@ -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')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -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 ''
|
||||
|
||||
@ -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(',')
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
174
theme/src/client/components/BackToTop.vue
Normal file
174
theme/src/client/components/BackToTop.vue
Normal 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>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -99,6 +99,12 @@ const showLocalNav = computed(() => {
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.local-nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
3
theme/src/client/components/icons/IconBackToTop.vue
Normal file
3
theme/src/client/components/icons/IconBackToTop.vue
Normal 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>
|
||||
@ -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(),
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -87,7 +87,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.details code {
|
||||
&.note code,
|
||||
&.info code,
|
||||
&.tip code,
|
||||
&.warning code,
|
||||
&.caution code,
|
||||
&.details, code {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
17
theme/src/client/utils/color.ts
Normal file
17
theme/src/client/utils/color.ts
Normal 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)]
|
||||
}
|
||||
@ -5,3 +5,4 @@ export * from './dom.js'
|
||||
export * from './resolveEditLink.js'
|
||||
export * from './resolveRepoType.js'
|
||||
export * from './base.js'
|
||||
export * from './color.js'
|
||||
|
||||
@ -26,6 +26,7 @@ export interface PlumeThemePageFrontmatter {
|
||||
contributors?: boolean
|
||||
prev?: string | NavItemWithLink
|
||||
next?: string | NavItemWithLink
|
||||
backToTop?: boolean
|
||||
}
|
||||
|
||||
export interface PlumeThemePostFrontmatter extends PlumeThemePageFrontmatter {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user