Merge pull request #131 from pengzhanbo/RC-83

RC 83
This commit is contained in:
pengzhanbo 2024-07-28 11:48:39 +08:00 committed by GitHub
commit 4bad4b90d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 804 additions and 64 deletions

View File

@ -22,31 +22,36 @@ export const zhNotes = definePlumeNotesConfig({
{
text: 'markdown',
icon: 'material-symbols:markdown-outline',
dir: 'markdown',
prefix: 'markdown',
collapsed: true,
items: ['基础', '扩展', '进阶'],
},
{
text: '代码块',
dir: '代码',
prefix: '代码',
icon: 'ph:code-bold',
collapsed: true,
items: ['介绍', '特性支持', '代码组', '导入代码', 'twoslash'],
},
{
text: '代码演示',
dir: '代码演示',
prefix: '代码演示',
icon: 'carbon:demo',
collapsed: true,
items: ['前端', 'rust', 'golang', 'kotlin', 'codepen', 'jsFiddle', 'codeSandbox', 'replit'],
},
{
text: '图表',
icon: 'mdi:chart-line',
dir: '图表',
prefix: '图表',
collapsed: true,
items: ['chart', 'echarts', 'mermaid', 'flowchart'],
},
{
text: '资源嵌入',
icon: 'dashicons:embed-video',
dir: '嵌入',
prefix: '嵌入',
collapsed: true,
items: ['pdf', 'bilibili', 'youtube'],
},
],
@ -55,8 +60,13 @@ export const zhNotes = definePlumeNotesConfig({
text: '功能',
icon: 'lucide:box',
collapsed: false,
dir: '功能',
items: ['图标', '代码复制', '内容搜索', '评论', '加密', '组件', '文章水印', '友情链接页', 'seo', 'sitemap'],
prefix: '功能',
items: ['图标', '代码复制', '内容搜索', '评论', '加密', '组件', '文章水印', '友情链接页', 'seo', 'sitemap', {
text: '非内置功能',
icon: 'system-uicons:box-add',
collapsed: true,
items: ['repoCard', 'npmBadge'],
}],
},
{
text: '自定义',
@ -89,13 +99,13 @@ export const zhNotes = definePlumeNotesConfig({
},
{
text: 'frontmatter',
dir: 'frontmatter',
prefix: 'frontmatter',
collapsed: false,
items: ['basic', 'home', 'post', 'friend'],
},
{
text: '内置插件',
dir: 'plugins',
prefix: 'plugins',
collapsed: false,
items: ['', '代码高亮', '搜索', '阅读统计', 'markdown增强', 'markdownPower', '百度统计', '水印'],
},
@ -109,7 +119,7 @@ export const zhNotes = definePlumeNotesConfig({
text: '插件',
items: [
// 'caniuse',
'iconify',
// 'iconify',
'shiki',
'md-power',
'content-updated',

View File

@ -179,6 +179,8 @@ interface BlogOptions {
::: warning
该字段不支持在 [主题配置文件 `plume.config.js`](./配置说明.md#主题配置文件) 中进行配置。
为了使缓存能够生效,您应该 **删除** `package.json``vuepress dev` 开发服务启动脚本中的 `--clean-cache` 参数。
:::
### locales

View File

@ -0,0 +1,151 @@
---
title: Npm 徽章
author: pengzhanbo
icon: akar-icons:npm-fill
createTime: 2024/07/26 22:07:23
permalink: /guide/npm-badge/
---
<script setup>
import NpmBadge from 'vuepress-theme-plume/features/NpmBadge.vue'
import NpmBadgeGroup from 'vuepress-theme-plume/features/NpmBadgeGroup.vue'
</script>
## 概述
Npm 徽章组件 用于显示 npm 包信息,并提供相关的链接。
徽章由 <https://shields.io> 提供支持。
## 使用
使用该组件需要你手动导入 `NpmBadge``NpmBadgeGroup` 组件:
```md :no-line-numbers
<!-- 在 markdown 中导入 -->
<script setup>
import NpmBadge from 'vuepress-theme-plume/features/NpmBadge.vue'
import NpmBadgeGroup from 'vuepress-theme-plume/features/NpmBadgeGroup.vue'
</script>
<!-- 导入后,即可在 markdown 中使用 -->
<NpmBadge name="vuepress-theme-plume" type="dm" />
<!-- 并排显示多个 npm badge -->
<NpmBadgeGroup name="vuepress-theme-plume" items="version,dm" />
```
<NpmBadge name="vuepress-theme-plume" type="dm" />
<NpmBadgeGroup name="vuepress-theme-plume" items="version,dm" />
## `<NpmBadge />`
单个 npm badge
### Props
| 名称 | 类型 | 必填 | 默认值 | 说明 |
| ---------- | --------------- | --------------- | ----------- | --------------------------------------- |
| name | `string` | 否 | `''` | npm 包名,为空则从 `repo` 中获取 |
| repo | `string` | name 为空时必填 | `''` | 包 github 仓库地址, 格式为 `owner/repo` |
| type | `NpmBadgeType` | 是 | - | 徽章类型 |
| theme | `NpmBadgeTheme` | 否 | `'flat'` | 徽章主题 |
| label | `string` | 否 | `''` | 徽章标签 |
| color | `string` | 否 | `'#32A9C3'` | 徽章颜色 |
| labelColor | `string` | 否 | `'#1B3C4A'` | 徽章标签颜色 |
| branch | `string` | 否 | `'main'` | 仓库分支 |
| dir | `string` | 否 | `''` | 包所在仓库目录,适用于 monorepo 项目 |
### Types
```ts
type NpmBadgeType =
// github
| 'source' // github source
| 'stars' // github stars
| 'forks' // github forks
| 'license' // github license
// npm
| 'version' // npm version
| 'dt' // alias d18m
| 'd18m' // npm downloads last 18 months
| 'dw' // npm downloads weekly
| 'dm' // npm downloads monthly
| 'dy' // npm downloads yearly
type NpmBadgeTheme = 'flat' | 'flat-square' | 'plastic' | 'for-the-badge' | 'social'
```
### 示例
- `<NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="source" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="source" />
- `<NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="stars" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="stars" />
- `<NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="forks" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="forks" />
- `<NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="license" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="license" />
- `<NpmBadge name="vuepress-theme-plume" type="version" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="version" />
- `<NpmBadge name="vuepress-theme-plume" type="dt" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="dt" />
- `<NpmBadge name="vuepress-theme-plume" type="d18m" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="d18m" />
- `<NpmBadge name="vuepress-theme-plume" type="dy" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="dy" />
- `<NpmBadge name="vuepress-theme-plume" type="dm" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="dm" />
- `<NpmBadge name="vuepress-theme-plume" type="dw" />` - <NpmBadge repo="pengzhanbo/vuepress-theme-plume" type="dw" />
## `<NpmBadgeGroup />`
组合多个 npm badge
### Props
| 名称 | 类型 | 必填 | 默认值 | 说明 |
| ---------- | --------------- | --------------- | ------ | --------------------------------------- |
| name | `string` | 否 | `''` | npm 包名,为空则从 `repo` 中获取 |
| repo | `string` | name 为空时必填 | `''` | 包 github 仓库地址, 格式为 `owner/repo` |
| items | `string \| NpmBadgeType[]` | 否 | - | 徽章类型列表, 传入 `string` 时用 `','`分隔,会自动转换为 `NpmBadgeType[]` |
| theme | `NpmBadgeTheme` | 否 | `''` | 徽章主题 |
| color | `string` | 否 | `''` | 徽章颜色 |
| labelColor | `string` | 否 | `''` | 徽章标签颜色 |
| branch | `string` | 否 | `''` | 仓库分支 |
| dir | `string` | 否 | `''` | 包所在仓库目录,适用于 monorepo 项目 |
### Slots
`<NpmBadgeGroup />` 支持传入 多个 `<NpmBadge />` 组件。
`<NpmBadgeGroup />` 声明的 `Props` 将被注入到 `<NpmBadge />` 组件中。通过这种方式来实现和简化徽章组合。
### 示例
**输入:**
```md :no-line-numbers
<NpmBadgeGroup
repo="pengzhanbo/vuepress-theme-plume"
items="stars,version,dm,source"
/>
```
**输出:**
<NpmBadgeGroup repo="pengzhanbo/vuepress-theme-plume" items="stars,version,dm,source" />
使用 `<slot />` 灵活定义徽章组合:
**输入:**
```md :no-line-numbers
<NpmBadgeGroup repo="pengzhanbo/vuepress-theme-plume">
<NpmBadge type="stars" />
<NpmBadge type="version" label="npm" />
<NpmBadge type="dm" />
<NpmBadge type="source" />
</NpmBadgeGroup>
```
**输出:**
<NpmBadgeGroup repo="pengzhanbo/vuepress-theme-plume">
<NpmBadge type="stars" />
<NpmBadge type="version" label="npm" />
<NpmBadge type="dm" />
<NpmBadge type="source" />
</NpmBadgeGroup>

View File

@ -0,0 +1,91 @@
---
title: Repo 卡片
author: pengzhanbo
icon: octicon:repo-16
createTime: 2024/07/26 21:11:56
permalink: /guide/github-repo-card/
---
<script setup>
import RepoCard from 'vuepress-theme-plume/features/RepoCard.vue'
</script>
## 概述
Repo 卡片组件 用于显示 GitHub 仓库信息。
## 使用
使用该组件需要你手动导入 `RepoCard` 组件:
```md :no-line-numbers
<!-- 在 markdown 中导入 -->
<script setup>
import RepoCard from 'vuepress-theme-plume/features/RepoCard.vue'
</script>
<!-- 导入后,即可在 markdown 中使用 -->
<RepoCard repo="pengzhanbo/vuepress-theme-plume" />
```
注册为全局组件:
::: code-tabs
@tab .vuepress/client.ts
```ts
import { defineClientConfig } from 'vuepress/client'
import RepoCard from 'vuepress-theme-plume/features/RepoCard.vue'
export default defineClientConfig({
enhance({ app }) {
app.component('RepoCard', RepoCard)
},
})
```
:::
全局组件可在 其他任意 markdown 文件中使用
```md
<RepoCard repo="pengzhanbo/vuepress-theme-plume" />
```
### Props
`RepoCard` 组件的 接收一个 `repo` 参数,传入的是仓库的地址,格式为 `owner/repo`
## 示例
### 单卡片
**输入:**
```md
<RepoCard repo="pengzhanbo/vuepress-theme-plume" />
```
**输出:**
<RepoCard repo="pengzhanbo/vuepress-theme-plume" />
### 多卡片
如果希望以紧凑的方式并排展示多个卡片,可以使用 `CardGrid` 组件。
**输入:**
```md
<CardGrid>
<RepoCard repo="vuepress/core" />
<RepoCard repo="vuepress/ecosystem" />
</CardGrid>
```
**输出:**
<CardGrid>
<RepoCard repo="vuepress/core" />
<RepoCard repo="vuepress/ecosystem" />
</CardGrid>

View File

@ -37,6 +37,9 @@
"types": "./lib/client/composables/index.d.ts",
"import": "./lib/client/composables/index.js"
},
"./features/*": {
"import": "./lib/client/features/components/*"
},
"./shared": {
"types": "./lib/shared/index.d.ts",
"import": "./lib/shared/index.js"

View File

@ -174,7 +174,7 @@ watch(
@media (min-width: 1440px) {
.vp-doc-container:not(.has-sidebar) .content {
max-width: 784px;
max-width: 884px;
}
.vp-doc-container.is-blog:not(.has-sidebar.has-aside) .content {
@ -275,6 +275,6 @@ watch(
}
.vp-doc-container.has-aside .content-container {
max-width: 688px;
max-width: 788px;
}
</style>

View File

@ -61,7 +61,7 @@ const bind = computed<any>(() => ({
<span v-if="!loaded" class="vp-icon" :style="{ color, width: size, height: size }" />
<OfflineIcon
v-else-if="icon"
class="vp-iconify"
class="vp-icon"
v-bind="bind"
/>
</ClientOnly>

View File

@ -29,8 +29,9 @@ export function useLangs({
const { notFound, path } = resolveRoute(targetPath)
if (!notFound)
return path
const blog = theme.value.blog
if (isBlogPost.value)
if (isBlogPost.value && blog !== false)
return withBase(blog?.link || normalizeLink(locale, 'blog/'))
const sidebarList = sidebar.value

View File

@ -16,7 +16,6 @@ import {
ref,
watch,
watchEffect,
watchPostEffect,
} from 'vue'
import { sidebar as sidebarRaw } from '@internal/sidebar'
import { isActive, normalizeLink, normalizePrefix, resolveNavLink } from '../utils/index.js'
@ -368,7 +367,7 @@ export function useSidebarControl(item: ComputedRef<ResolvedSidebarItem>): Sideb
)
}
watch([page, item, () => route.hash], updateIsActiveLink)
watch([() => page.value.path, item, () => route.hash], updateIsActiveLink)
onMounted(updateIsActiveLink)
const hasActiveLink = computed(() => {
@ -389,11 +388,11 @@ export function useSidebarControl(item: ComputedRef<ResolvedSidebarItem>): Sideb
collapsed.value = !!(collapsible.value && item.value.collapsed)
})
watchPostEffect(() => {
watch(() => [page.value.path, isActiveLink.value, hasActiveLink.value], () => {
if (isActiveLink.value || hasActiveLink.value) {
collapsed.value = false
}
})
}, { immediate: true, flush: 'post' })
const toggle = (): void => {
if (collapsible.value) {

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import { toRef } from 'vue'
import type { NpmBadgeOptions } from '../composables/npm-badge.js'
import { useNpmBadge } from '../composables/npm-badge.js'
const props = defineProps<NpmBadgeOptions>()
const info = useNpmBadge(toRef(() => props))
</script>
<template>
<span class="vp-npm-badge">
<img v-if="!info.link" :src="info.badgeUrl" :alt="info.alt">
<a v-else :href="info.link" target="_blank" rel="noreferrer">
<img :src="info.badgeUrl" :alt="info.alt">
</a>
</span>
</template>
<style>
.vp-npm-badge {
display: inline-flex;
vertical-align: middle;
}
:where(div,p,h2,h3,h4,h5,h6,li):not(.vp-npm-badge-group) > .vp-npm-badge + .vp-npm-badge {
margin-inline-start: 8px;
}
.vp-npm-badge a {
display: inline-flex;
text-decoration: none;
text-underline-offset: 0;
}
</style>

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import { computed, toRef } from 'vue'
import type { NpmBadgeGroupOptions, NpmBadgeType } from '../composables/npm-badge.js'
import { useNpmBadgeGroup } from '../composables/npm-badge.js'
import NpmBadge from './NpmBadge.vue'
const props = defineProps<NpmBadgeGroupOptions>()
useNpmBadgeGroup(toRef(() => props))
const list = computed(() => {
if (!props.items)
return []
if (Array.isArray(props.items))
return props.items
return props.items.split(',').map(type => type.trim()) as NpmBadgeType[]
})
</script>
<template>
<p class="vp-npm-badge-group">
<slot>
<NpmBadge v-for="(type, index) in list" :key="type + index" :type="type" />
</slot>
</p>
</template>
<style scoped>
.vp-npm-badge-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
</style>

View File

@ -0,0 +1,155 @@
<script setup lang="ts">
import { toRef } from 'vue'
import { useGithubRepo } from '../composables/github-repo.js'
const props = defineProps<{
repo: string
}>()
const { loaded, data } = useGithubRepo(toRef(props, 'repo'))
</script>
<template>
<div v-if="loaded && data" class="vp-repo-card">
<p class="repo-name">
<span class="vpi-github-repo" />
<span class="repo-link">
<a :href="data.url" target="_blank" rel="noopener noreferrer">
{{ data.ownerType === 'Organization' ? data.fullName : data.name }}
</a>
</span>
<span class="repo-visibility">{{ data.visibility + (data.template ? ' Template' : '') }}</span>
</p>
<p class="repo-desc">
{{ data.description }}
</p>
<div class="repo-info">
<p v-if="data.language">
<span
class="repo-language" :style="{ 'background-color': data.languageColor }"
/><span>{{ data.language }}</span>
</p>
<p><span class="vpi-github-star" /><span>{{ data.stars }}</span></p>
<p><span class="vpi-github-fork" /><span>{{ data.forks }}</span></p>
<p v-if="data.license">
<span class="vpi-github-license" /><span>{{ data.license.name }}</span>
</p>
</div>
</div>
</template>
<style scoped>
.vp-repo-card {
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px 20px;
margin: 16px 0;
border: solid 1px var(--vp-c-divider);
border-radius: 8px;
transition: border-color var(--t-color);
}
.vp-repo-card:hover {
border-color: var(--vp-c-brand-2);
}
.vp-repo-card p {
margin: 0;
}
.vp-repo-card .repo-name {
display: flex;
gap: 0 8px;
align-items: center;
max-width: 100%;
font-size: 16px;
}
.vp-repo-card .repo-link {
flex: 1;
width: 1px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.vp-repo-card .repo-name a {
max-width: 100%;
font-weight: 600;
color: var(--vp-c-brand-1);
text-decoration: none;
transition: color var(--t-color);
}
.vp-repo-card .repo-name a:hover {
color: var(--vp-c-brand-2);
}
.vp-repo-card .repo-visibility {
display: inline-block;
padding: 0 8px;
font-size: 14px;
line-height: 20px;
color: var(--vp-c-text-2);
border: solid 1px var(--vp-c-divider);
border-radius: 22px;
transition: color var(--t-color), border var(--t-color);
}
.vp-repo-card .repo-desc {
flex: 1;
font-size: 14px;
line-height: 22px;
color: var(--vp-c-text-2);
transition: color var(--t-color);
}
.vp-repo-card .repo-info {
display: flex;
gap: 16px;
align-items: center;
justify-content: flex-start;
font-size: 14px;
line-height: 22px;
}
.vp-repo-card .repo-info p {
display: flex;
gap: 0 4px;
align-items: center;
color: var(--vp-c-text-2);
transition: color var(--t-color);
}
.vp-repo-card .repo-info p [class^="vpi-github-"] {
color: var(--vp-c-text-1);
transition: color var(--t-color);
}
.vp-repo-card .repo-language {
display: inline-block;
width: 0.8em;
height: 0.8em;
border-radius: 100%;
}
.vpi-github-repo {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 16 16'%3E%3Cpath fill='%23000' d='M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7a.75.75 0 1 1-1.072 1.05A2.5 2.5 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.5 2.5 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.25.25 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z'/%3E%3C/svg%3E");
color: var(--vp-c-text-2);
transition: color var(--t-color);
transform: translateY(2px);
}
.vpi-github-star {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='M243 96a20.33 20.33 0 0 0-17.74-14l-56.59-4.57l-21.84-52.81a20.36 20.36 0 0 0-37.66 0L87.35 77.44L30.76 82a20.45 20.45 0 0 0-11.66 35.88l43.18 37.24l-13.2 55.7A20.37 20.37 0 0 0 79.57 233L128 203.19L176.43 233a20.39 20.39 0 0 0 30.49-22.15l-13.2-55.7l43.18-37.24A20.43 20.43 0 0 0 243 96m-70.47 45.7a12 12 0 0 0-3.84 11.86L181.58 208l-47.29-29.08a12 12 0 0 0-12.58 0L74.42 208l12.89-54.4a12 12 0 0 0-3.84-11.86l-42.27-36.5l55.4-4.47a12 12 0 0 0 10.13-7.38L128 41.89l21.27 51.5a12 12 0 0 0 10.13 7.38l55.4 4.47Z'/%3E%3C/svg%3E");
}
.vpi-github-fork {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 256 256'%3E%3Cpath fill='%23000' d='M228 64a36 36 0 1 0-48 33.94V112a4 4 0 0 1-4 4H80a4 4 0 0 1-4-4V97.94a36 36 0 1 0-24 0V112a28 28 0 0 0 28 28h36v18.06a36 36 0 1 0 24 0V140h36a28 28 0 0 0 28-28V97.94A36.07 36.07 0 0 0 228 64M64 52a12 12 0 1 1-12 12a12 12 0 0 1 12-12m64 152a12 12 0 1 1 12-12a12 12 0 0 1-12 12m64-128a12 12 0 1 1 12-12a12 12 0 0 1-12 12'/%3E%3C/svg%3E");
}
.vpi-github-license {
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' d='M4.5 13.5h7M8.01 1v12.06M1.5 3.5h3l1.5-1h4l1.5 1h3M.5 10L3 4.48L5.5 10C4 11 2 11 .5 10m10 0L13 4.48L15.5 10c-1.5 1-3.5 1-5 0'/%3E%3C/svg%3E");
}
</style>

View File

@ -0,0 +1,48 @@
import { computed, ref, toValue, watch } from 'vue'
import type { MaybeRef } from 'vue'
export interface GithubRepoInfo {
name: string
fullName: string
description: string
url: string
stars: number
forks: number
watchers: number
language: string
languageColor: string
visibility: 'Private' | 'Public' // private, public
template: boolean
ownerType: 'User' | 'Organization'
license: {
name: string
url: string
} | null
}
export function useGithubRepo(repo: MaybeRef<string>) {
const repoRef = computed(() => {
const info = toValue(repo)
const [owner = '', name = ''] = info.split('/')
return { owner, name }
})
const data = ref<GithubRepoInfo | null>(null)
const loaded = ref(false)
async function fetchData() {
const { owner, name } = repoRef.value
if (__VUEPRESS_SSR__ || !owner || !name)
return
loaded.value = false
const res = await fetch(`https://api.pengzhanbo.cn/github/repo/${owner}/${name}`)
.then(res => res.json()) as GithubRepoInfo
data.value = res
loaded.value = true
}
watch(repoRef, fetchData, { immediate: true })
return { data, loaded }
}

View File

@ -0,0 +1,175 @@
import { computed, inject, provide, ref, toValue } from 'vue'
import type { ComputedRef, InjectionKey, Ref } from 'vue'
const DEFAULT_COLOR = '#32A9C3'
const DEFAULT_LABEL_COLOR = '#1B3C4A'
const BADGE_URL = 'https://img.shields.io'
const GITHUB_URL = 'https://github.com'
const NPM_URL = 'https://www.npmjs.com/package'
export type NpmBadgeType =
// github
| 'source' // github source
| 'stars' // github stars
| 'forks' // github forks
| 'license' // github license
// npm
| 'version' // npm version
| 'dt' // alias d18m
| 'd18m' // npm downloads last 18 months
| 'dw' // npm downloads weekly
| 'dm' // npm downloads monthly
| 'dy' // npm downloads yearly
export type NpmBadgeTheme = 'flat' | 'flat-square' | 'plastic' | 'for-the-badge' | 'social'
export interface NpmBadgeBaseOptions {
// npm package name
name?: string
// github repo
repo?: string
// github branch
branch?: string
// github directory
dir?: string
// text color
color?: string
// label color
labelColor?: string
// badge style
theme?: NpmBadgeTheme
}
export interface NpmBadgeOptions extends NpmBadgeBaseOptions {
type: NpmBadgeType
// label text
label?: string
}
export interface NpmBadgeGroupOptions extends Omit<NpmBadgeBaseOptions, 'label'> {
items?: string | NpmBadgeType | NpmBadgeType[]
}
export interface NpmBadgeInfo {
badgeUrl: string
link?: string
alt?: string
}
type NpmBadgeBaseOptionsRef = Ref<NpmBadgeBaseOptions>
const NpmBadgeSymbol: InjectionKey<NpmBadgeBaseOptionsRef> = Symbol(__VUEPRESS_DEV__ ? 'NpmBadge' : '')
export function useNpmBadge(opt: Ref<NpmBadgeOptions>): ComputedRef<NpmBadgeInfo> {
const parentOpt = inject(NpmBadgeSymbol, ref({}) as NpmBadgeBaseOptionsRef)
return computed(() => {
const po = toValue(parentOpt)
const o = toValue(opt)
const res: NpmBadgeOptions = {
name: o.name || po.name,
repo: o.repo || po.repo,
branch: o.branch || po.branch,
dir: o.dir || po.dir,
type: o.type,
color: o.color || po.color,
label: o.label,
labelColor: o.labelColor || po.labelColor,
theme: o.theme || po.theme,
}
return resolveNpmBadgeOptions(res)
})
}
export function useNpmBadgeGroup(opt: Ref<NpmBadgeGroupOptions>) {
const baseOptions = computed<NpmBadgeBaseOptions>(() => {
const o = toValue(opt)
return {
name: o.name,
repo: o.repo,
branch: o.branch,
dir: o.dir,
color: o.color,
labelColor: o.labelColor,
theme: o.theme,
}
})
provide(NpmBadgeSymbol, baseOptions)
}
function resolveNpmBadgeOptions(options: NpmBadgeOptions): NpmBadgeInfo {
let { name = '', repo = '', branch = 'main', dir = '', type, color, label, labelColor, theme = '' } = options
name = name || repo.split('/')?.[1] || ''
const normalizeName = encodeURIComponent(name)
const githubLink = repo ? `${GITHUB_URL}/${repo}${dir ? `/tree/${branch}/${dir}` : ''}` : ''
const npmLink = `${NPM_URL}/${name}`
const params = new URLSearchParams()
if (type !== 'source' && type !== 'stars' && type !== 'forks') {
params.append('style', theme || 'flat')
params.append('color', color || DEFAULT_COLOR)
params.append('labelColor', labelColor || DEFAULT_LABEL_COLOR)
}
switch (type) {
case 'source': {
params.append('logo', 'github')
params.append('color', labelColor || DEFAULT_LABEL_COLOR)
return {
badgeUrl: `${BADGE_URL}/badge/source-a?${params.toString()}`,
link: githubLink,
alt: 'github source',
}
}
case 'stars':
case 'forks': {
params.append('style', theme || 'social')
return {
badgeUrl: `${BADGE_URL}/github/${type}/${repo}?${params.toString()}`,
link: githubLink,
alt: `github ${type}`,
}
}
case 'license':
return {
badgeUrl: `${BADGE_URL}/github/license/${repo}?${params.toString()}`,
link: githubLink,
alt: 'license',
}
case 'version': {
params.append('label', label || name || 'npm')
return {
badgeUrl: `${BADGE_URL}/npm/v/${normalizeName}?${params.toString()}`,
link: npmLink,
alt: 'npm version',
}
}
case 'dt':
case 'd18m': {
params.append('label', label || 'downloads')
return {
badgeUrl: `${BADGE_URL}/npm/d18m/${normalizeName}?${params.toString()}`,
link: npmLink,
alt: 'npm downloads',
}
}
case 'dm':
case 'dy':
case 'dw': {
params.append('label', label || 'downloads')
return {
badgeUrl: `${BADGE_URL}/npm/${type}/${normalizeName}?${params.toString()}`,
link: npmLink,
alt: 'npm downloads',
}
}
default:
return {
badgeUrl: `${BADGE_URL}/badge/unknown?${params.toString()}`,
alt: 'unknown',
}
}
}

View File

@ -194,34 +194,36 @@ export function resolveOptions(
include: '**/{readme,README,index}.md',
frontmatter: {},
},
{
include: localeOptions.blog?.include ?? ['**/*.md'],
frontmatter: {
...options.title !== false
? {
title(title: string, { relativePath }) {
if (title)
return title
const basename = path.basename(relativePath || '', '.md')
return basename
},
} as AutoFrontmatterObject
: undefined,
...baseFrontmatter,
...options.permalink !== false
? {
permalink(permalink: string, { relativePath }) {
if (permalink)
return permalink
const locale = resolveLocale(relativePath)
const prefix = withBase(articlePrefix, locale)
localeOptions.blog !== false
? {
include: localeOptions.blog?.include ?? ['**/*.md'],
frontmatter: {
...options.title !== false
? {
title(title: string, { relativePath }) {
if (title)
return title
const basename = path.basename(relativePath || '', '.md')
return basename
},
} as AutoFrontmatterObject
: undefined,
...baseFrontmatter,
...options.permalink !== false
? {
permalink(permalink: string, { relativePath }) {
if (permalink)
return permalink
const locale = resolveLocale(relativePath)
const prefix = withBase(articlePrefix, locale)
return normalizePath(`${prefix}/${nanoid()}/`)
},
} as AutoFrontmatterObject
: undefined,
},
},
return normalizePath(`${prefix}/${nanoid()}/`)
},
} as AutoFrontmatterObject
: undefined,
},
}
: '',
{
include: '*',

View File

@ -35,8 +35,6 @@ export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): Pl
}
})
const blog = options.blog || {}
const blogLink = blog.link || '/blog/'
entries(options.locales || {}).forEach(([locale, opt]) => {
// 注入预设 导航栏
// home | blog | tags | archives
@ -47,21 +45,25 @@ export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): Pl
text: PRESET_LOCALES[localePath].home,
link: locale,
}]
navbar.push({
text: PRESET_LOCALES[localePath].blog,
link: withBase(blogLink, locale),
})
if (blog.tags !== false) {
if (options.blog !== false) {
const blog = options.blog || {}
const blogLink = blog.link || '/blog/'
navbar.push({
text: PRESET_LOCALES[localePath].tag,
link: withBase(blog.tagsLink || `${blogLink}/tags/`, locale),
})
}
if (blog.archives !== false) {
navbar.push({
text: PRESET_LOCALES[localePath].archive,
link: withBase(blog.archivesLink || `${blogLink}/archives/`, locale),
text: PRESET_LOCALES[localePath].blog,
link: withBase(blogLink, locale),
})
if (blog.tags !== false) {
navbar.push({
text: PRESET_LOCALES[localePath].tag,
link: withBase(blog.tagsLink || `${blogLink}/tags/`, locale),
})
}
if (blog.archives !== false) {
navbar.push({
text: PRESET_LOCALES[localePath].archive,
link: withBase(blog.archivesLink || `${blogLink}/archives/`, locale),
})
}
}
themeData.locales![locale].navbar = navbar

View File

@ -25,6 +25,12 @@ export async function preparedBlogData(
localeOptions: PlumeThemeLocaleOptions,
encrypt?: PlumeThemeEncrypt,
): Promise<void> {
if (localeOptions.blog === false) {
const content = resolveContent(app, { name: 'blogPostData', content: [] })
await writeTemp(app, 'internal/blogData.js', content)
return
}
const start = performance.now()
const blog = localeOptions.blog || {}

View File

@ -16,8 +16,8 @@ interface IconData {
type CollectMap = Record<string, string[]>
type IconDataMap = Record<string, IconData>
const ICON_REGEXP = /<(?:VP)?Icon(?:ify)?([^>]*)>/g
const ICON_NAME_REGEXP = /name="([^"]+)"/
const ICON_REGEXP = /<(?:VP)?(Icon|Card|LinkCard)([^>]*)>/g
const ICON_NAME_REGEXP = /(?:name|icon)="([^"]+)"/
const URL_CONTENT_REGEXP = /(url\([\s\S]+\))/
const JS_FILENAME = 'internal/iconify.js'
const CSS_FILENAME = 'internal/iconify.css'

View File

@ -18,6 +18,9 @@ export async function setupPage(
app: App,
localeOption: PlumeThemeLocaleOptions,
) {
if (localeOption.blog === false)
return
const pageList: Promise<Page>[] = []
const locales = localeOption.locales || {}
const rootPath = getRootLangPath(app)

View File

@ -64,7 +64,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
/**
*
*/
blog?: PlumeThemeBlog
blog?: false | PlumeThemeBlog
/**
*
@ -274,7 +274,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
encryptPlaceholder?: string
}
/** =========================== Avatar ================================ */
/** =========================== Profile ================================ */
/**
*

View File

@ -1,3 +1,6 @@
import process from 'node:process'
import fs from 'node:fs'
import path from 'node:path'
import { type Options, defineConfig } from 'tsup'
const sharedExternal: (string | RegExp)[] = [
@ -12,6 +15,11 @@ const clientExternal: (string | RegExp)[] = [
/.*\.css$/,
]
const featuresComposables = fs.readdirSync(
path.join(process.cwd(), 'src/client/features/composables'),
{ recursive: true, encoding: 'utf-8' },
)
export default defineConfig((cli) => {
const DEFAULT_OPTIONS: Options = {
dts: !cli.watch,
@ -35,6 +43,7 @@ export default defineConfig((cli) => {
outDir: './lib/node',
external: sharedExternal,
target: 'node18',
watch: false,
},
// client/utils/index.js
{
@ -77,5 +86,16 @@ export default defineConfig((cli) => {
'./config.js',
],
},
...featuresComposables.map(file => ({
...DEFAULT_OPTIONS,
entry: [`./src/client/features/composables/${file}`],
outDir: `./lib/client/features/composables/`,
external: [
...clientExternal,
'../../composables/index.js',
'../../utils/index.js',
...featuresComposables.map(file => `./${file.replace('.ts', '.js')}`),
],
})),
]
})