Merge pull request #160 from pengzhanbo/perf-ui

fix(cli): incorrect exec context
This commit is contained in:
pengzhanbo 2024-08-31 00:07:17 +08:00 committed by GitHub
commit 2376379772
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 534 additions and 352 deletions

View File

@ -1,3 +1,5 @@
import process from 'node:process'
import path from 'node:path'
import { intro, outro, spinner } from '@clack/prompts'
import { execaCommand } from 'execa'
import colors from 'picocolors'
@ -19,16 +21,17 @@ export async function run(mode: Mode, root?: string) {
await generate(mode, data)
const cwd = path.join(process.cwd(), data.root)
if (data.git) {
progress.message(t('spinner.git'))
await execaCommand('git init')
await execaCommand('git init', { cwd })
}
const pm = data.packageManager
if (data.install) {
progress.message(t('spinner.install'))
await execaCommand(pm === 'yarn' ? 'yarn' : `${pm} install`)
await execaCommand(pm === 'yarn' ? 'yarn' : `${pm} install`, { cwd })
}
const cdCommand = mode === Mode.create ? colors.green(`cd ${data.root}`) : ''

View File

@ -1,165 +0,0 @@
import { defineNotesConfig } from 'vuepress-theme-plume'
export const zhNotes = defineNotesConfig({
dir: 'notes',
link: '/',
notes: [
{
dir: 'theme/guide',
link: '/guide/',
sidebar: [
{
text: '快速开始',
collapsed: false,
icon: 'carbon:idea',
items: ['介绍', '安装与使用', '博客', '知识笔记', '编写文章', '国际化', '部署'],
},
{
text: '写作',
icon: 'fluent-mdl2:edit-create',
collapsed: false,
items: [
{
text: 'markdown',
icon: 'material-symbols:markdown-outline',
prefix: 'markdown',
collapsed: true,
items: ['基础', '扩展', '进阶'],
},
{
text: '代码块',
prefix: '代码',
icon: 'ph:code-bold',
collapsed: true,
items: ['介绍', '特性支持', '代码组', '导入代码', 'twoslash'],
},
{
text: '代码演示',
prefix: '代码演示',
icon: 'carbon:demo',
collapsed: true,
items: ['前端', 'rust', 'golang', 'kotlin', 'codepen', 'jsFiddle', 'codeSandbox', 'replit'],
},
{
text: '图表',
icon: 'mdi:chart-line',
prefix: '图表',
collapsed: true,
items: ['chart', 'echarts', 'mermaid', 'flowchart'],
},
{
text: '资源嵌入',
icon: 'dashicons:embed-video',
prefix: '嵌入',
collapsed: true,
items: ['pdf', 'bilibili', 'youtube'],
},
],
},
{
text: '功能',
icon: 'lucide:box',
collapsed: false,
prefix: '功能',
items: ['图标', '代码复制', '内容搜索', '评论', '加密', '文章水印', '友情链接页', 'seo', 'sitemap'],
},
{
text: '组件',
prefix: '组件',
icon: 'uiw:component',
collapsed: false,
items: ['徽章', '图标', '隐秘文本', '卡片', '链接卡片', '图片卡片', '卡片容器', '首页布局容器', 'repoCard', 'npmBadge'],
},
{
text: '自定义',
icon: 'material-symbols:dashboard-customize-outline-rounded',
collapsed: false,
items: ['自定义首页', '自定义样式', '布局插槽', '组件覆写'],
},
{
text: 'API',
icon: 'mdi:api',
collapsed: false,
items: ['api-客户端', 'api-node'],
},
],
},
{
dir: 'theme/config',
link: '/config/',
sidebar: [
{
text: '配置',
collapsed: false,
items: [
'配置说明',
'多语言配置',
'主题配置',
'导航栏配置',
'notes配置',
'侧边栏配置',
],
},
{
text: 'frontmatter',
prefix: 'frontmatter',
collapsed: false,
items: ['basic', 'home', 'post', 'friend'],
},
{
text: '内置插件',
prefix: 'plugins',
collapsed: false,
items: ['', '代码高亮', '搜索', '阅读统计', 'markdown增强', 'markdownPower', '百度统计', '水印'],
},
],
},
{
dir: 'plugins',
link: '/plugins/',
sidebar: [
{
text: '插件',
link: '/plugins/',
items: [
// 'caniuse',
// 'iconify',
'shiki',
'md-power',
'content-updated',
{
text: 'plugin-netlify-functions',
dir: 'netlify-functions',
link: '/plugins/plugin-netlify-functions/',
collapsed: true,
items: [
'介绍',
'使用',
'功能',
'api',
'functions',
],
},
],
},
],
},
{
dir: 'tools',
link: '/tools/',
sidebar: [
{
text: '工具',
icon: 'tabler:tools',
items: ['custom-theme', 'home-hero-tint-plate', 'caniuse'],
},
],
},
],
})
export const enNotes = defineNotesConfig({
dir: 'en/notes',
link: '/',
notes: [],
})

View File

@ -0,0 +1,7 @@
import { defineNotesConfig } from 'vuepress-theme-plume'
export const enNotes = defineNotesConfig({
dir: 'en/notes',
link: '/',
notes: [],
})

View File

@ -0,0 +1,2 @@
export * from './en/index.js'
export * from './zh/index.js'

View File

@ -0,0 +1,16 @@
import { defineNotesConfig } from 'vuepress-theme-plume'
import { themeGuide } from './theme-guide'
import { themeConfig } from './theme-config'
import { plugins } from './plugins'
import { tools } from './tools'
export const zhNotes = defineNotesConfig({
dir: 'notes',
link: '/',
notes: [
themeGuide,
themeConfig,
plugins,
tools,
],
})

View File

@ -0,0 +1,32 @@
import { defineNoteConfig } from 'vuepress-theme-plume'
export const plugins = defineNoteConfig({
dir: 'plugins',
link: '/plugins/',
sidebar: [
{
text: '插件',
link: '/plugins/',
items: [
// 'caniuse',
// 'iconify',
'shiki',
'md-power',
'content-updated',
{
text: 'plugin-netlify-functions',
dir: 'netlify-functions',
link: '/plugins/plugin-netlify-functions/',
collapsed: true,
items: [
'介绍',
'使用',
'功能',
'api',
'functions',
],
},
],
},
],
})

View File

@ -0,0 +1,46 @@
import { defineNoteConfig } from 'vuepress-theme-plume'
export const themeConfig = defineNoteConfig({
dir: 'theme/config',
link: '/config/',
sidebar: [
{
text: '配置',
collapsed: false,
items: [
'配置说明',
'多语言配置',
'主题配置',
'导航栏配置',
'notes配置',
'侧边栏配置',
],
},
{
text: 'frontmatter',
prefix: 'frontmatter',
collapsed: false,
items: [
'basic',
'home',
'post',
'friend',
],
},
{
text: '内置插件',
prefix: 'plugins',
collapsed: false,
items: [
'',
'代码高亮',
'搜索',
'阅读统计',
'markdown增强',
'markdownPower',
'百度统计',
'水印',
],
},
],
})

View File

@ -0,0 +1,147 @@
import { defineNoteConfig } from 'vuepress-theme-plume'
export const themeGuide = defineNoteConfig({
dir: 'theme/guide',
link: '/guide/',
sidebar: [
{
text: '快速开始',
collapsed: false,
icon: 'carbon:idea',
items: [
'介绍',
'安装与使用',
'博客',
'知识笔记',
'编写文章',
'国际化',
'部署',
],
},
{
text: '写作',
icon: 'fluent-mdl2:edit-create',
collapsed: false,
items: [
{
text: 'markdown',
icon: 'material-symbols:markdown-outline',
prefix: 'markdown',
collapsed: true,
items: [
'基础',
'扩展',
'进阶',
],
},
{
text: '代码块',
prefix: '代码',
icon: 'ph:code-bold',
collapsed: true,
items: [
'介绍',
'特性支持',
'代码组',
'导入代码',
'twoslash',
],
},
{
text: '代码演示',
prefix: '代码演示',
icon: 'carbon:demo',
collapsed: true,
items: [
'前端',
'rust',
'golang',
'kotlin',
'codepen',
'jsFiddle',
'codeSandbox',
'replit',
],
},
{
text: '图表',
icon: 'mdi:chart-line',
prefix: '图表',
collapsed: true,
items: [
'chart',
'echarts',
'mermaid',
'flowchart',
],
},
{
text: '资源嵌入',
icon: 'dashicons:embed-video',
prefix: '嵌入',
collapsed: true,
items: [
'pdf',
'bilibili',
'youtube',
],
},
],
},
{
text: '功能',
icon: 'lucide:box',
collapsed: false,
prefix: '功能',
items: [
'图标',
'代码复制',
'内容搜索',
'评论',
'加密',
'文章水印',
'友情链接页',
'seo',
'sitemap',
],
},
{
text: '组件',
prefix: '组件',
icon: 'uiw:component',
collapsed: false,
items: [
'徽章',
'图标',
'隐秘文本',
'卡片',
'链接卡片',
'图片卡片',
'卡片容器',
'首页布局容器',
'repoCard',
'npmBadge',
],
},
{
text: '自定义',
icon: 'material-symbols:dashboard-customize-outline-rounded',
collapsed: false,
items: [
'自定义首页',
'自定义样式',
'布局插槽',
'组件覆写',
],
},
{
text: 'API',
icon: 'mdi:api',
collapsed: false,
items: [
'api-客户端',
'api-node',
],
},
],
})

View File

@ -0,0 +1,17 @@
import { defineNoteConfig } from 'vuepress-theme-plume'
export const tools = defineNoteConfig({
dir: 'tools',
link: '/tools/',
sidebar: [
{
text: '工具',
icon: 'tabler:tools',
items: [
'custom-theme',
'home-hero-tint-plate',
'caniuse',
],
},
],
})

View File

@ -1,5 +1,5 @@
import { defineThemeConfig } from 'vuepress-theme-plume'
import { enNotes, zhNotes } from './notes.js'
import { enNotes, zhNotes } from './notes/index.js'
import { enNavbar, zhNavbar } from './navbar.js'
export default defineThemeConfig({

View File

@ -6,7 +6,7 @@ export const theme: Theme = plumeTheme({
hostname: process.env.SITE_HOST || 'https://plume.pengzhanbo.cn',
plugins: {
shiki: { twoslash: true },
shiki: { twoslash: true, lineNumbers: 10 },
markdownEnhance: {
demo: true,

View File

@ -55,3 +55,8 @@ permalink: /guide/components/cark/
</template>
这里是卡片内容。
</Card>
:::info
在插槽内也可以使用 markdown 语法但需要注意的是markdown 语法需要与 标签之间间隔一行。
否则将被识别为普通文本。
:::

View File

@ -39,3 +39,48 @@ permalink: /guide/components/link-card/
<LinkCard title="卡片标题" href="/" description="这里是卡片内容" />
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
使用组件插槽,可以实现更丰富的表现。
**输入:**
```md :no-line-numbers
<LinkCard title="卡片标题" href="/">
- 这里是卡片内容
- 这里是卡片内容
</LinkCard>
<LinkCard href="/">
<template #title>
<span style="color: red" >卡片标题</span>
</template>
- 这里是卡片内容
- 这里是卡片内容
</LinkCard>
```
**输出:**
<LinkCard title="卡片标题" href="/">
- 这里是卡片内容
- 这里是卡片内容
</LinkCard>
<LinkCard href="/">
<template #title>
<span style="color: red" >卡片标题</span>
</template>
- 这里是卡片内容
- 这里是卡片内容
</LinkCard>
:::info
在插槽内也可以使用 markdown 语法但需要注意的是markdown 语法需要与 标签之间间隔一行。
否则将被识别为普通文本。
:::

View File

@ -45,7 +45,7 @@
"markdown-it-container": "^4.0.0",
"nanoid": "^5.0.7",
"shiki": "^1.14.1",
"tm-grammars": "^1.17.4",
"tm-grammars": "^1.17.8",
"tm-themes": "^1.8.1",
"vue": "^3.4.38"
},

16
pnpm-lock.yaml generated
View File

@ -166,8 +166,8 @@ importers:
specifier: ^1.14.1
version: 1.14.1
tm-grammars:
specifier: ^1.17.4
version: 1.17.4
specifier: ^1.17.8
version: 1.17.8
tm-themes:
specifier: ^1.8.1
version: 1.8.1
@ -403,9 +403,6 @@ packages:
peerDependencies:
'@algolia/client-search': '>= 4.9.1 < 6'
algoliasearch: '>= 4.9.1 < 6'
peerDependenciesMeta:
'@algolia/client-search':
optional: true
'@algolia/cache-browser-local-storage@4.20.0':
resolution: {integrity: sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ==}
@ -5369,8 +5366,8 @@ packages:
tinyexec@0.2.0:
resolution: {integrity: sha512-au8dwv4xKSDR+Fw52csDo3wcDztPdne2oM1o/7LFro4h6bdFmvyUAeAfX40pwDtzHgRFqz1XWaUqgKS2G83/ig==}
tm-grammars@1.17.4:
resolution: {integrity: sha512-FuZ+f/pwYcKy4Ci56r75d4mRLBsdLUJunIgF+1u5Oy0co9laQqZFH1rss+F6/vdx1vJljPubZj9clKxrwTKg0A==}
tm-grammars@1.17.8:
resolution: {integrity: sha512-Qw67JNutL9LCt8FFw5RfsogeQ40iSeqrTHDSp0ecnY/b+ZweK8izlw6y/ZMje2+I6DMtTiBOCgmXEf+2oH11jQ==}
tm-themes@1.8.1:
resolution: {integrity: sha512-jTUfDRn5TysYhkxxEWBQDo1C1n4yoHcnfNNqXkVxIMGQCgal/9poGuMBsfbnZCPEmFVcN2rtrUwaOJ8s2hVQXg==}
@ -5907,9 +5904,8 @@ snapshots:
'@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.20.0)(algoliasearch@4.20.0)':
dependencies:
algoliasearch: 4.20.0
optionalDependencies:
'@algolia/client-search': 4.20.0
algoliasearch: 4.20.0
'@algolia/cache-browser-local-storage@4.20.0':
dependencies:
@ -11462,7 +11458,7 @@ snapshots:
tinyexec@0.2.0: {}
tm-grammars@1.17.4: {}
tm-grammars@1.17.8: {}
tm-themes@1.8.1: {}

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import VPShortPostList from '@theme/Blog/VPShortPostList.vue'
import { useArchives, useBlogNavTitle } from '../../composables/index.js'
import { useArchives, useInternalLink } from '../../composables/index.js'
const title = useBlogNavTitle('archive')
const { archive: archiveLink } = useInternalLink()
const { archives } = useArchives()
</script>
@ -12,7 +12,7 @@ const { archives } = useArchives()
<h2 class="archives-title">
<span class="vpi-archive icon" />
<span>{{ title }}</span>
<span>{{ archiveLink.text }}</span>
</h2>
<div v-if="archives.length" class="archives">
<template v-for="archive in archives" :key="archive.label">

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import VPCategories from '@theme/Blog/VPCategories.vue'
import { useBlogCategory, useBlogNavTitle } from '../../composables/index.js'
import { useBlogCategory, useInternalLink } from '../../composables/index.js'
const title = useBlogNavTitle('category')
const { categories: categoriesLink } = useInternalLink()
const { categories } = useBlogCategory()
</script>
@ -12,7 +12,7 @@ const { categories } = useBlogCategory()
<h2 class="categories-title">
<span class="vpi-category icon" />
<span>{{ title }}</span>
<span>{{ categoriesLink.text }}</span>
</h2>
<slot name="blog-categories-content-before" />

View File

@ -1,9 +1,9 @@
<script lang="ts" setup>
import VPShortPostList from '@theme/Blog/VPShortPostList.vue'
import { useBlogNavTitle, useTags } from '../../composables/index.js'
import { useInternalLink, useTags } from '../../composables/index.js'
const { tags: tagsLink } = useInternalLink()
const { tags, currentTag, postList, handleTagClick } = useTags()
const title = useBlogNavTitle('tag')
</script>
<template>
@ -13,7 +13,7 @@ const title = useBlogNavTitle('tag')
<div class="tags-nav">
<h2 class="tags-title">
<span class="vpi-tag icon" />
<span>{{ title }}</span>
<span>{{ tagsLink.text }}</span>
</h2>
<slot name="blog-tags-title-after" />
<div class="tags">

View File

@ -2,13 +2,14 @@
import { computed } from 'vue'
import VPLink from '@theme/VPLink.vue'
import type { PlumeThemeBlogPostItem } from '../../../shared/index.js'
import { useTagColors } from '../../composables/index.js'
import { useInternalLink, useTagColors } from '../../composables/index.js'
const props = defineProps<{
post: PlumeThemeBlogPostItem
}>()
const colors = useTagColors()
const { categories: categoriesLink, tags: tagsLink } = useInternalLink()
const sticky = computed(() => {
if (typeof props.post.sticky === 'boolean') {
@ -53,19 +54,22 @@ const createTime = computed(() =>
<div v-if="categoryList.length" class="category-list">
<span class="icon vpi-folder" />
<template v-for="(cate, i) in categoryList" :key="i">
<span>{{ cate.name }}</span>
<VPLink :href="`${categoriesLink.link}?id=${cate.id}`">
{{ cate.name }}
</VPLink>
<span v-if="i !== categoryList.length - 1">/</span>
</template>
</div>
<div v-if="tags.length" class="tag-list">
<span class="icon vpi-tag" />
<template v-for="tag in tags" :key="tag.name">
<span
<VPLink
class="tag"
:class="tag.className"
:href="`${tagsLink.link}?tag=${tag.name}`"
>
{{ tag.name }}
</span>
</VPLink>
</template>
</div>
<div v-if="createTime" class="create-time">

View File

@ -1,12 +1,10 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRouteLocale } from 'vuepress/client'
import VPLink from '@theme/VPLink.vue'
import {
useBlogExtract,
useBlogNavTitle,
useBlogPageData,
useData,
useInternalLink,
useSidebarData,
} from '../composables/index.js'
import type { ResolvedSidebarItem } from '../../shared/index.js'
@ -18,9 +16,8 @@ interface Breadcrumb {
}
const { page } = useData<'post'>()
const routeLocale = useRouteLocale()
const { isBlogPost } = useBlogPageData()
const { categoriesLink, blogLink } = useBlogExtract()
const { home, blog, categories } = useInternalLink()
const sidebar = useSidebarData()
const hasBreadcrumb = computed(() => {
@ -28,21 +25,19 @@ const hasBreadcrumb = computed(() => {
return page.value.categoryList.length > 0
return sidebar.value.length > 0
})
const homeTitle = useBlogNavTitle('home')
const blogTile = useBlogNavTitle('blog')
const breadcrumbList = computed<Breadcrumb[]>(() => {
if (!hasBreadcrumb.value)
return []
const list: Breadcrumb[] = [{ text: homeTitle.value, link: routeLocale.value }]
const list: Breadcrumb[] = [{ text: home.value.text, link: home.value.link }]
if (isBlogPost.value) {
list.push({ text: blogTile.value, link: blogLink.value })
list.push({ text: blog.value.text, link: blog.value.link })
const categoryList = page.value.categoryList ?? []
for (const category of categoryList) {
list.push({
text: category.name,
link: `${categoriesLink.value}?id=${category.id}`,
link: `${categories.value.link}?id=${category.id}`,
})
}
}
@ -90,7 +85,7 @@ function resolveSidebar(
{{ text }}
</VPLink>
<span v-if="index !== breadcrumbList.length - 1" class="vpi-chevron-right" />
<meta property="position" :content="index + 1">
<meta property="position" :content="`${index + 1}`">
</li>
</ol>
</nav>
@ -98,19 +93,19 @@ function resolveSidebar(
<style scoped>
.vp-breadcrumb {
padding-left: 1rem;
padding-left: 8px;
margin-bottom: 2rem;
border-left: solid 4px var(--vp-c-brand-1);
border-left: solid 2px var(--vp-c-brand-1);
transition: border-left var(--t-color);
}
.vp-breadcrumb ol {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 4px;
align-items: center;
justify-content: flex-start;
font-size: 16px;
font-size: 14px;
font-weight: 400;
}
@ -120,6 +115,7 @@ function resolveSidebar(
}
.vp-breadcrumb .breadcrumb {
font-weight: bold;
color: var(--vp-c-brand-2);
transition: color var(--t-color);
}
@ -133,7 +129,8 @@ function resolveSidebar(
}
.vp-breadcrumb .vpi-chevron-right {
margin-left: 8px;
color: var(--vp-c-text-3);
margin-left: 4px;
color: var(--vp-c-border);
transition: color var(--t-color);
}
</style>

View File

@ -1,11 +1,13 @@
<script lang="ts" setup>
import { computed } from 'vue'
import VPLink from '@theme/VPLink.vue'
import { useReadingTimeLocale } from '@vuepress/plugin-reading-time/client'
import { useData, useTagColors } from '../composables/index.js'
import { useData, useInternalLink, useTagColors } from '../composables/index.js'
const { page, frontmatter: matter } = useData<'post'>()
const colors = useTagColors()
const readingTime = useReadingTimeLocale()
const { tags: tagsLink } = useInternalLink()
const createTime = computed(() => {
if (matter.value.createTime)
@ -40,14 +42,15 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
</p>
<p v-if="tags.length > 0">
<span class="vpi-tag icon" />
<span
<VPLink
v-for="tag in tags"
:key="tag.name"
class="tag"
:class="tag.className"
:href="`${tagsLink.link}?tag=${tag.name}`"
>
{{ tag.name }}
</span>
</VPLink>
</p>
<p v-if="createTime" class="create-time">
<span class="vpi-clock icon" /><span>{{ createTime }}</span>

View File

@ -52,11 +52,12 @@ defineProps<{
display: flex;
flex: 1;
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
.vp-link-card .body > :last-child {
margin-bottom: 0;
.vp-link-card .body > * {
margin: 0;
}
.vp-link-card .link {

View File

@ -1,64 +1,44 @@
import { useRouteLocale } from 'vuepress/client'
import { computed } from 'vue'
import type { PresetLocale } from '../../shared/index.js'
import { useLocalePostList } from './blog-data.js'
import { useTags } from './blog-tags.js'
import { type BlogCategory, useBlogCategory } from './blog-category.js'
import { useData } from './data.js'
import { useLocaleLink } from './locale.js'
declare const __PLUME_PRESET_LOCALE__: Record<string, PresetLocale>
const presetLocales = __PLUME_PRESET_LOCALE__
export function useBlogNavTitle(name: keyof PresetLocale) {
const locale = useRouteLocale()
return computed(() => presetLocales[locale.value]?.[name] || presetLocales['/'][name])
}
import { useInternalLink } from './internal-link.js'
export function useBlogExtract() {
const { theme } = useData()
const locale = useRouteLocale()
const postList = useLocalePostList()
const { tags: tagsList } = useTags()
const { categories: categoryList } = useBlogCategory()
const blog = computed(() => theme.value.blog || {})
const links = useInternalLink()
const hasBlogExtract = computed(() =>
blog.value.archives !== false
|| blog.value.tags !== false
|| blog.value.categories !== false,
)
const blogLink = useLocaleLink(blog.value.link || 'blog/')
const tagsLink = useLocaleLink(blog.value.tagsLink || 'blog/tags/')
const archiveLink = useLocaleLink(blog.value.archivesLink || 'blog/archives/')
const categoriesLink = useLocaleLink(blog.value.categoriesLink || 'blog/categories/')
const tags = computed(() => ({
link: tagsLink.value,
text: presetLocales[locale.value]?.tag || presetLocales['/'].tag,
link: links.tags.value.link,
text: links.tags.value.text,
total: tagsList.value.length,
}))
const archives = computed(() => ({
link: archiveLink.value,
text: presetLocales[locale.value]?.archive || presetLocales['/'].archive,
link: links.archive.value.link,
text: links.archive.value.text,
total: postList.value.length,
}))
const categories = computed(() => ({
link: categoriesLink.value,
text: presetLocales[locale.value]?.category || presetLocales['/'].category,
link: links.categories.value.link,
text: links.categories.value.text,
total: getCategoriesTotal(categoryList.value),
}))
return {
hasBlogExtract,
blogLink,
tagsLink,
archiveLink,
categoriesLink,
tags,
archives,
categories,

View File

@ -20,6 +20,7 @@ export * from './contributors.js'
export * from './home.js'
export * from './internal-link.js'
export * from './blog-data.js'
export * from './blog-post-list.js'
export * from './blog-extract.js'
@ -32,8 +33,8 @@ export * from './page.js'
export * from './encrypt-data.js'
export * from './encrypt.js'
export * from './preset-locales.js'
export * from './link.js'
export * from './locale.js'
export * from './route-query.js'
export * from './watermark.js'

View File

@ -0,0 +1,38 @@
import { computed } from 'vue'
import { useRouteLocale } from 'vuepress/client'
import type { PresetLocale } from '../../shared/index.js'
import { useData } from './data.js'
import { getPresetLocaleData } from './preset-locales.js'
export interface InternalLink {
text: string
link: string
}
export function useInternalLink() {
const { theme } = useData()
const routeLocale = useRouteLocale()
function resolveLink(name: keyof PresetLocale, link: string): InternalLink {
return {
link: (routeLocale.value + link).replace(/\/+/g, '/'),
text: getPresetLocaleData(routeLocale.value, name),
}
}
const blogData = computed(() => theme.value.blog || {})
const home = computed(() => resolveLink('home', '/'))
const blog = computed(() => resolveLink('blog', blogData.value.link || 'blog/'))
const tags = computed(() => resolveLink('tag', blogData.value.tagsLink || 'blog/tags/'))
const archive = computed(() => resolveLink('archive', blogData.value.archivesLink || 'blog/archives/'))
const categories = computed(() => resolveLink('category', blogData.value.categoriesLink || 'blog/categories/'))
return {
home,
blog,
tags,
archive,
categories,
}
}

View File

@ -1,10 +0,0 @@
import { useRouteLocale } from 'vuepress/client'
import { computed } from 'vue'
export function useLocaleLink(link: string) {
const prefix = useRouteLocale()
return computed(() => {
return (prefix.value + link).replace(/\/+/g, '/')
})
}

View File

@ -0,0 +1,9 @@
import type { PresetLocale } from '../../shared/index.js'
declare const __PLUME_PRESET_LOCALE__: Record<string, PresetLocale>
export const presetLocales = __PLUME_PRESET_LOCALE__
export function getPresetLocaleData(locale: string, name: keyof PresetLocale) {
return presetLocales[locale]?.[name] || presetLocales['/'][name]
}

View File

@ -95,6 +95,7 @@ export function getSidebar(routePath: string, routeLocal: string): ResolvedSideb
return resolveSidebarItems(_sidebar, routeLocal)
}
else if (isPlainObject(_sidebar)) {
routePath = decodeURIComponent(routePath)
const dir
= Object.keys(_sidebar)
.sort((a, b) => b.split('/').length - a.split('/').length)

View File

@ -311,6 +311,12 @@ html:not(.dark) .vp-code span {
transform: translateX(calc(-100% - 1px));
}
@media (max-width: 419px) {
.vp-doc div[class*="language-"] > button.copy {
display: none;
}
}
/*
Collapsed lines
--------------------------------------------------------------------------

View File

@ -13,6 +13,6 @@
}
/* ------------------ Plugin Comment ------------------ */
#vp-comment {
.vp-comment {
margin-top: 80px;
}

View File

@ -52,7 +52,7 @@
@media (max-width: 419px) {
.vp-doc .hint-container div[class*="language-"] {
margin: 0.85rem -0.75rem 0.85rem -1rem;
margin: 0.85rem -0.75rem;
}
.vp-doc .hint-container .vp-code-tabs-nav {
@ -247,9 +247,10 @@
}
.vp-doc .vp-code-demo .vp-code-demo-code-wrapper {
margin-bottom: -0.9rem;
margin-bottom: -13px;
}
.vp-doc .vp-code-demo .vp-code-demo-toggle-button {
margin: 0 12px 0 8px;
background-color: var(--vp-c-gray-2);
@ -275,7 +276,10 @@
}
.vp-doc .vp-code-demo .vp-code-demo-codes div[class*="language-"] {
margin-top: 0;
margin-bottom: 0;
border-bottom: 2px dashed var(--vp-c-divider);
border-radius: 0;
transition: border-bottom var(--t-color);
}

View File

@ -12,6 +12,7 @@ import type {
AutoFrontmatterObject,
PlumeThemeLocaleOptions,
} from '../../shared/index.js'
import { getThemeConfig } from '../loadConfig/index.js'
import { readMarkdown, readMarkdownList } from './readFile.js'
import { resolveOptions } from './resolveOptions.js'
@ -26,8 +27,6 @@ export interface Generate {
}
let generate: Generate | null = null
let generated = false
const whenGenerated: (() => void)[] = []
export function initAutoFrontmatter(
localeOptions: PlumeThemeLocaleOptions,
@ -64,19 +63,14 @@ export function initAutoFrontmatter(
export async function generateAutoFrontmatter(app: App) {
if (!generate)
return
generated = false
const markdownList = await readMarkdownList(app.dir.source(), generate.globFilter)
await promiseParallel(
markdownList.map(file => () => generator(file)),
64,
)
generated = true
whenGenerated.forEach(resolve => resolve())
whenGenerated.length = 0
}
export async function watchAutoFrontmatter(app: App, watchers: any[], enable?: () => boolean) {
export async function watchAutoFrontmatter(app: App, watchers: any[]) {
if (!generate)
return
@ -87,7 +81,7 @@ export async function watchAutoFrontmatter(app: App, watchers: any[], enable?: (
})
watcher.on('add', async (relativePath) => {
const enabled = enable ? enable() : true
const enabled = getThemeConfig().autoFrontmatter !== false
if (!generate!.globFilter(relativePath) || !enabled)
return
const file = await readMarkdown(app.dir.source(), relativePath)
@ -132,12 +126,3 @@ async function generator(file: AutoFrontmatterMarkdownFile): Promise<void> {
console.error(e)
}
}
export function waitForAutoFrontmatter() {
return new Promise<void>((resolve) => {
if (generate && !generated)
whenGenerated.push(resolve)
else
resolve()
})
}

View File

@ -65,13 +65,12 @@ export async function initConfigLoader(
loader.loaded = true
loader.dependencies = [...dependencies]
updateResolvedConfig(app, config)
runChangeEvents()
loader.whenLoaded.forEach(fn => fn(loader!.resolvedConfig))
loader.whenLoaded = []
}
export function watchConfigFile(app: App, watchers: any[]) {
export function watchConfigFile(app: App, watchers: any[], onChange: ChangeEvent) {
if (!loader || !loader.configFile)
return
@ -82,6 +81,8 @@ export function watchConfigFile(app: App, watchers: any[]) {
addDependencies(watcher)
onConfigChange(onChange)
watcher.on('change', async () => {
if (loader) {
loader.loaded = false
@ -121,7 +122,7 @@ export function waitForConfigLoaded() {
})
}
export function getResolvedThemeConfig() {
export function getThemeConfig() {
return loader!.resolvedConfig
}

View File

@ -22,6 +22,7 @@ import {
resolveSearchOptions,
} from '../config/index.js'
import { customContainerPlugins } from './containerPlugins.js'
import { markdownTitlePlugin } from './markdown-title.js'
export interface SetupPluginOptions {
app: App
@ -39,7 +40,7 @@ export function getPlugins({
const isProd = !app.env.isDev
const plugins: PluginConfig = [
markdownTitlePlugin(),
fontsPlugin(),
contentUpdatePlugin(),
activeHeaderLinksPlugin({

View File

@ -0,0 +1,46 @@
import type { Plugin } from 'vuepress/core'
import type { MarkdownEnv } from 'vuepress/markdown'
const REG_HEADING = /^#\s*?(\S.*)?\n/
export function markdownTitlePlugin(): Plugin {
return {
name: '@vuepress-plume/plugin-markdown-title',
extendsMarkdown(md) {
const render = md.render
md.render = (source, env: MarkdownEnv) => {
if (!env.filePathRelative)
return render(source, env)
let { matter, content } = parseSource(source.trim())
let title = ''
content = content.trim().replace(REG_HEADING, (_, match) => {
title = match.trim()
return ''
})
source = `${matter}\n${content}`
const result = render(source, env)
if (title)
env.title = title
return result
}
},
}
}
function parseSource(source: string) {
const char = '---'
if (!source.startsWith(char)) {
return { matter: '', content: source }
}
else {
const end = source.indexOf(`\n${char}`)
const len = char.length + 1
return {
matter: source.slice(0, end + len),
content: source.slice(end + len),
}
}
}

View File

@ -1,6 +1,6 @@
import type { App } from 'vuepress'
import { watch } from 'chokidar'
import { getResolvedThemeConfig } from '../loadConfig/index.js'
import { getThemeConfig } from '../loadConfig/index.js'
import { prepareArticleTagColors } from './prepareArticleTagColor.js'
import { preparedBlogData } from './prepareBlogData.js'
import { prepareEncrypt } from './prepareEncrypt.js'
@ -10,7 +10,7 @@ import { prepareIcons } from './prepareIcons.js'
export async function prepareData(
app: App,
): Promise<void> {
const { localeOptions, encrypt } = getResolvedThemeConfig()
const { localeOptions, encrypt } = getThemeConfig()
await Promise.all([
prepareArticleTagColors(app),
preparedBlogData(app, localeOptions, encrypt),

View File

@ -93,8 +93,8 @@ function getAutoDirSidebar(
while (nowIndex < maxIndex) {
pages = pages.sort((prev, next) => {
const pi = prev.splitPath?.[nowIndex]?.match(/(\d+)\.(?=[^/]+$)/)?.[1]
const ni = next.splitPath?.[nowIndex]?.match(/(\d+)\.(?=[^/]+$)/)?.[1]
const pi = prev.splitPath?.[nowIndex]?.match(/(?:(\d+)\.)?(?=[^/]+$)/)?.[1]
const ni = next.splitPath?.[nowIndex]?.match(/(?:(\d+)\.)?(?=[^/]+$)/)?.[1]
if (!pi || !ni)
return 0
return Number.parseFloat(pi) < Number.parseFloat(ni) ? -1 : 1

View File

@ -4,58 +4,24 @@ import type { PlumeThemeOptions, PlumeThemePageData } from '../shared/index.js'
import { getPlugins } from './plugins/index.js'
import { extendsPageData, setupPage } from './setupPages.js'
import { THEME_NAME, resolve, templates } from './utils/index.js'
import {
extendsBundlerOptions,
resolveAlias,
resolvePageHead,
resolveProvideData,
resolveThemeOptions,
templateBuildRenderer,
} from './config/index.js'
import {
getResolvedThemeConfig,
initConfigLoader,
onConfigChange,
waitForConfigLoaded,
watchConfigFile,
} from './loadConfig/index.js'
import {
generateAutoFrontmatter,
initAutoFrontmatter,
waitForAutoFrontmatter,
watchAutoFrontmatter,
} from './autoFrontmatter/index.js'
import { extendsBundlerOptions, resolveAlias, resolvePageHead, resolveProvideData, resolveThemeOptions, templateBuildRenderer } from './config/index.js'
import { getThemeConfig, initConfigLoader, waitForConfigLoaded, watchConfigFile } from './loadConfig/index.js'
import { generateAutoFrontmatter, initAutoFrontmatter, watchAutoFrontmatter } from './autoFrontmatter/index.js'
import { prepareData, watchPrepare } from './prepare/index.js'
import { prepareThemeData } from './prepare/prepareThemeData.js'
export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
const {
localeOptions,
pluginOptions,
hostname,
configFile,
cache,
} = resolveThemeOptions(options)
const { localeOptions, pluginOptions, hostname, configFile, cache } = resolveThemeOptions(options)
return (app) => {
initConfigLoader(app, localeOptions, {
configFile,
onChange: ({ localeOptions, autoFrontmatter }) => {
autoFrontmatter ??= pluginOptions.frontmatter
if (autoFrontmatter !== false) {
if (autoFrontmatter !== false)
initAutoFrontmatter(localeOptions, autoFrontmatter)
}
},
})
waitForConfigLoaded().then(async ({ autoFrontmatter }) => {
autoFrontmatter ??= pluginOptions.frontmatter
if (autoFrontmatter !== false) {
await sleep(100)
generateAutoFrontmatter(app)
}
})
return {
name: THEME_NAME,
@ -69,42 +35,46 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
plugins: getPlugins({ app, pluginOptions, hostname, cache }),
extendsBundlerOptions,
templateBuildRenderer,
extendsMarkdown: async (_, app) => {
const { autoFrontmatter, localeOptions } = await waitForConfigLoaded()
if (autoFrontmatter !== false) {
initAutoFrontmatter(localeOptions, autoFrontmatter)
await generateAutoFrontmatter(app)
// wait for autoFrontmatter generated
// i/o performance
await sleep(100)
}
},
extendsPage: async (page) => {
const { localeOptions } = getThemeConfig()
extendsPageData(page as Page<PlumeThemePageData>, localeOptions)
resolvePageHead(page, localeOptions)
},
onInitialized: async (app) => {
const { localeOptions } = await waitForConfigLoaded()
const { localeOptions } = getThemeConfig()
await setupPage(app, localeOptions)
},
onPrepared: async (app) => {
onConfigChange(async ({ localeOptions }) => {
await prepareThemeData(app, localeOptions)
await prepareData(app)
})
const { localeOptions } = await waitForConfigLoaded()
const { localeOptions } = getThemeConfig()
await prepareThemeData(app, localeOptions)
await prepareData(app)
},
onWatched: (app, watchers) => {
watchConfigFile(app, watchers)
watchPrepare(app, watchers)
watchAutoFrontmatter(app, watchers, () => {
const autoFrontmatter = getResolvedThemeConfig().autoFrontmatter ?? pluginOptions.frontmatter
return autoFrontmatter !== false
watchConfigFile(app, watchers, async ({ localeOptions }) => {
await prepareThemeData(app, localeOptions)
await prepareData(app)
})
watchAutoFrontmatter(app, watchers)
watchPrepare(app, watchers)
},
extendsPage: async (page) => {
const { localeOptions, autoFrontmatter } = await waitForConfigLoaded()
if ((autoFrontmatter ?? pluginOptions.frontmatter) !== false) {
await waitForAutoFrontmatter()
}
extendsPageData(page as Page<PlumeThemePageData>, localeOptions)
resolvePageHead(page, localeOptions)
},
extendsBundlerOptions,
templateBuildRenderer,
}
}
}

View File

@ -6,7 +6,6 @@ import type { MarkdownEnhancePluginOptions } from 'vuepress-plugin-md-enhance'
import type { ReadingTimePluginOptions } from '@vuepress/plugin-reading-time'
import type { MarkdownPowerPluginOptions } from 'vuepress-plugin-md-power'
import type { WatermarkPluginOptions } from '@vuepress/plugin-watermark'
import type { AutoFrontmatter } from '../auto-frontmatter.js'
export interface PlumeThemePluginOptions {
/**
@ -62,11 +61,6 @@ export interface PlumeThemePluginOptions {
*/
baiduTongji?: false | { key: string }
/**
* @deprecated 使 `autoFrontmatter`
*/
frontmatter?: Omit<AutoFrontmatter, 'frontmatter'>
/**
*
*/