Merge pull request #171 from pengzhanbo/perf-docs

feat(plugin-md-power): add support remote image in `imageSize`
This commit is contained in:
pengzhanbo 2024-09-13 01:43:42 +08:00 committed by GitHub
commit 24920486cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
243 changed files with 2724 additions and 1471 deletions

View File

@ -1,12 +1,12 @@
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import fs from 'node:fs'
import { execaCommand } from 'execa'
import { DeployType, Mode } from './constants.js'
import { createPackageJson } from './packageJson.js'
import { createRender } from './render.js'
import { getTemplate, readFiles, readJsonFile, writeFiles } from './utils/index.js'
import type { File, ResolvedData } from './types.js'
import { DeployType, Mode } from './constants.js'
export async function generate(mode: Mode, data: ResolvedData): Promise<void> {
const cwd = process.cwd()

View File

@ -1,6 +1,6 @@
import cac from 'cac'
import { run } from './run.js'
import { Mode } from './constants.js'
import { run } from './run.js'
declare const __CLI_VERSION__: string

View File

@ -1,6 +1,6 @@
import type { Langs, Locale } from '../types.js'
import { en } from './en.js'
import { zh } from './zh.js'
import type { Langs, Locale } from '../types.js'
export const locales: Record<Langs, Locale> = {
'zh-CN': zh,

View File

@ -1,13 +1,14 @@
import { execaCommand } from 'execa'
import { kebabCase } from '@pengzhanbo/utils'
import { execaCommand } from 'execa'
import { Mode } from './constants.js'
import { getDependenciesVersion, readJsonFile, resolve } from './utils/index.js'
import type { File, ResolvedData } from './types.js'
import { Mode } from './constants.js'
export async function createPackageJson(
mode: Mode,
pkg: Record<string, any>,
{
packageManager,
docsDir,
siteName,
siteDescription,
@ -20,11 +21,20 @@ export async function createPackageJson(
pkg.type = 'module'
pkg.version = '1.0.0'
pkg.description = siteDescription
if (packageManager !== 'npm') {
const version = await getPackageManagerVersion(packageManager)
if (version) {
pkg.packageManager = `${packageManager}@${version}`
}
}
const userInfo = await getUserInfo()
if (userInfo) {
pkg.author = userInfo.username + (userInfo.email ? ` <${userInfo.email}>` : '')
}
pkg.license = 'MIT'
pkg.engines = { node: '^18.20.0 || >=20.0.0' }
}
if (injectNpmScripts) {
@ -77,3 +87,13 @@ async function getUserInfo() {
return null
}
}
async function getPackageManagerVersion(pkg: string) {
try {
const { stdout } = await execaCommand(`${pkg} -v`)
return stdout
}
catch {
return null
}
}

View File

@ -1,9 +1,9 @@
import process from 'node:process'
import { createRequire } from 'node:module'
import process from 'node:process'
import { cancel, confirm, group, select, text } from '@clack/prompts'
import { bundlerOptions, deployOptions, DeployType, languageOptions, Mode } from './constants.js'
import { setLang, t } from './translate.js'
import type { Bundler, Langs, Options, PromptResult } from './types.js'
import { DeployType, Mode, bundlerOptions, deployOptions, languageOptions } from './constants.js'
const require = createRequire(process.cwd())

View File

@ -1,5 +1,5 @@
import handlebars from 'handlebars'
import { kebabCase } from '@pengzhanbo/utils'
import handlebars from 'handlebars'
import type { ResolvedData } from './types.js'
export interface RenderData extends ResolvedData {

View File

@ -1,14 +1,14 @@
import process from 'node:process'
import path from 'node:path'
import process from 'node:process'
import { intro, outro, spinner } from '@clack/prompts'
import { execaCommand } from 'execa'
import colors from 'picocolors'
import { prompt } from './prompt.js'
import { generate } from './generate.js'
import { t } from './translate.js'
import { Mode } from './constants.js'
import type { PromptResult, ResolvedData } from './types.js'
import { generate } from './generate.js'
import { prompt } from './prompt.js'
import { t } from './translate.js'
import { getPackageManager } from './utils/index.js'
import type { PromptResult, ResolvedData } from './types.js'
export async function run(mode: Mode, root?: string) {
intro(colors.cyan('Welcome to VuePress and vuepress-theme-plume !'))

View File

@ -1,5 +1,5 @@
import type { Langs, Locale } from './types.js'
import { locales } from './locales/index.js'
import type { Langs, Locale } from './types.js'
function createTranslate(lang?: Langs) {
let current: Langs = lang || 'en-US'

View File

@ -1,5 +1,5 @@
import path from 'node:path'
import fs from 'node:fs/promises'
import path from 'node:path'
import type { File } from '../types.js'
export async function readFiles(root: string): Promise<File[]> {

View File

@ -1,5 +1,5 @@
import { fileURLToPath } from 'node:url'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
export const __dirname = path.dirname(fileURLToPath(import.meta.url))
@ -7,6 +7,6 @@ export const resolve = (...args: string[]) => path.resolve(__dirname, '../', ...
export const getTemplate = (dir: string) => resolve('templates', dir)
export * from './fs.js'
export * from './depsVersion.js'
export * from './fs.js'
export * from './getPackageManager.js'

View File

@ -1,9 +1,9 @@
import { type ClientConfig, defineClientConfig } from 'vuepress/client'
import HeroTintPlateConfig from './themes/components/HeroTintPlateConfig.vue'
import CanIUseConfig from './themes/components/CanIUseConfig.vue'
import Demos from './themes/components/Demos.vue'
import ThemeColors from './themes/components/ThemeColors.vue'
import Contributors from './themes/components/Contributors.vue'
import Demos from './themes/components/Demos.vue'
import HeroTintPlateConfig from './themes/components/HeroTintPlateConfig.vue'
import ThemeColors from './themes/components/ThemeColors.vue'
import { setupThemeColors } from './themes/composables/theme-colors.js'
export default defineClientConfig({

View File

@ -1,7 +1,7 @@
import * as path from 'node:path'
import { type UserConfig, defineUserConfig } from 'vuepress'
import { viteBundler } from '@vuepress/bundler-vite'
import { addViteOptimizeDepsInclude, addViteSsrExternal } from '@vuepress/helper'
import { defineUserConfig, type UserConfig } from 'vuepress'
import { peerDependencies } from '../package.json'
import { theme } from './theme.js'

View File

@ -16,13 +16,13 @@ export const zhNavbar = [
link: '/notes/theme/config/配置说明.md',
activeMatch: '^/config/',
},
{
text: '插件',
icon: 'clarity:plugin-line',
// link: '/plugins/',
link: '/notes/plugins/README.md',
activeMatch: '^/plugins/',
},
// {
// text: '插件',
// icon: 'clarity:plugin-line',
// // link: '/plugins/',
// link: '/notes/plugins/README.md',
// activeMatch: '^/plugins/',
// },
{
text: '博客',
link: '/blog/',

View File

@ -1,7 +1,7 @@
import { defineNotesConfig } from 'vuepress-theme-plume'
import { themeGuide } from './theme-guide'
import { themeConfig } from './theme-config'
import { plugins } from './plugins'
import { themeConfig } from './theme-config'
import { themeGuide } from './theme-guide'
import { tools } from './tools'
export const zhNotes = defineNotesConfig({

View File

@ -38,7 +38,6 @@ export const themeConfig = defineNoteConfig({
'阅读统计',
'markdown增强',
'markdownPower',
'百度统计',
'水印',
],
},

View File

@ -16,6 +16,7 @@ export const themeGuide = defineNoteConfig({
'编写文章',
'国际化',
'部署',
'构建优化',
],
},
{
@ -121,6 +122,7 @@ export const themeGuide = defineNoteConfig({
'首页布局容器',
'repoCard',
'npmBadge',
'轮播图',
],
},
{

View File

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

View File

@ -17,6 +17,7 @@ export const theme: Theme = plumeTheme({
flowchart: true,
},
markdownPower: {
imageSize: 'all',
pdf: true,
caniuse: true,
plot: true,

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import '@simonwep/pickr/dist/themes/nano.min.css'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import '@simonwep/pickr/dist/themes/nano.min.css'
const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()

View File

@ -1,14 +1,21 @@
<script setup lang="ts">
defineProps<{
contributors: string[]
import { computed } from 'vue'
const props = defineProps<{
contributors: ({ github: string, name: string } | string)[]
}>()
const list = computed(() =>
props.contributors.map(contributor =>
typeof contributor === 'string' ? { github: contributor, name: contributor } : contributor),
)
</script>
<template>
<div class="contributors">
<div v-for="contributor in contributors" :key="contributor" class="contributor">
<img :src="`https://github.com/${contributor}.png`" :alt="contributor">
<a :href="`https://github.com/${contributor}`" target="_blank" rel="noopener noreferrer">{{ contributor }}</a>
<div v-for="contributor in list" :key="contributor.github" class="contributor">
<img :src="`https://avatars.githubusercontent.com/${contributor.github}?v=4`" :alt="contributor.name">
<a :href="`https://github.com/${contributor.github}`" target="_blank" rel="noopener noreferrer">{{ contributor.name }}</a>
</div>
</div>
</template>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import type { PlumeThemeHomeHeroTintPlate } from 'vuepress-theme-plume/client'
import { computed, watch } from 'vue'
import type { PlumeThemeHomeHeroTintPlate } from 'vuepress-theme-plume/client'
import InputRange from './InputRange.vue'
const min = 20

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import VPHomeHero from 'vuepress-theme-plume/components/Home/VPHomeHero.vue'
import { useDarkMode } from 'vuepress-theme-plume/composables'
import type { PlumeThemeHomeHeroTintPlate } from 'vuepress-theme-plume/client'
import { computed, ref, watch } from 'vue'
import CodeViewer from './CodeViewer.vue'
import CustomTintPlate from './CustomTintPlate.vue'
import DemoWrapper from './DemoWrapper.vue'
import SingleTintPlate from './SingleTintPlate.vue'
import TripletTintPlate from './TripletTintPlate.vue'
import CustomTintPlate from './CustomTintPlate.vue'
import CodeViewer from './CodeViewer.vue'
type Mode = 'single' | 'triplet' | 'custom'

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import VPButton from '@theme/VPButton.vue'
import { useThemeColors } from '../composables/theme-colors.js'
import ColorPick from './ColorPick.vue'
import CodeViewer from './CodeViewer.vue'
import ColorPick from './ColorPick.vue'
const { lightColors, darkColors, css, reset } = useThemeColors()
</script>

View File

@ -1,5 +1,5 @@
import { type Ref, computed, onMounted, readonly, ref, watch } from 'vue'
import { onClickOutside, useDebounceFn, useEventListener, useLocalStorage } from '@vueuse/core'
import { computed, onMounted, readonly, type Ref, ref, watch } from 'vue'
interface Feature {
label: string

View File

@ -1,5 +1,5 @@
import { type InjectionKey, type Ref, inject, provide, watch } from 'vue'
import { useSessionStorage, useStyleTag } from '@vueuse/core'
import { inject, type InjectionKey, provide, type Ref, watch } from 'vue'
export interface ThemeColor {
name: string

View File

@ -169,6 +169,16 @@ export default defineUserConfig({
### 贡献者
<Contributors :contributors="['pengzhanbo', 'huankong233', 'northword', 'KrLite', 'shylock-wu', 'hrradev']" />
<Contributors
:contributors="[
'pengzhanbo',
{ github: 'huankong233', name: 'huan_kong' },
{ github: 'northword', name: 'Northword' },
'KrLite',
'shylock-wu',
'hrradev',
{ github: 'TheCoderAlex', name: 'Tang Zifeng' },
]"
/>
</div>

View File

@ -49,8 +49,8 @@ pnpm add @vuepress-plume/plugin-caniuse
@tab .vuepress/config.ts
``` ts
import { defineUserConfig } from 'vuepress'
import { caniusePlugin } from '@vuepress-plume/plugin-caniuse'
import { defineUserConfig } from 'vuepress'
export default defineUserConfig({
plugins: [

View File

@ -56,8 +56,8 @@ yarn add @vuepress-plume/plugin-content-update
@tab .vuepress/config.ts
``` ts
import { defineUserConfig } from 'vuepress'
import { contentUpdatePlugin } from '@vuepress-plume/plugin-content-update'
import { defineUserConfig } from 'vuepress'
export default defineUserConfig({
plugins: [
@ -86,8 +86,8 @@ onContentUpdated(() => {
```vue
<script lang="ts" setup>
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
import { useMediumZoom } from '@vuepress/plugin-medium-zoom/client'
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
const mediumZoom = useMediumZoom()

View File

@ -76,7 +76,7 @@ permalink: /config/frontmatter/basic/
主题会在文件创建时:
- 博客类型的文章,自动填充 `/article/` + `nanoid 生成的 6 位随机字符串` 作为 文章永久链接
- 博客类型的文章,自动填充 `/article/` + `nanoid 生成的 8 位随机字符串` 作为 文章永久链接
- notes 目录下的文章,会根据 notes 的配置,自动填充 文章永久链接
### externalLinkIcon

View File

@ -5,15 +5,15 @@ createTime: 2024/03/06 8:26:44
permalink: /config/plugins/
---
主题内置的使用的插件,扩展了主题的众多功能,你可以在这个 字段中, 实现对内部使用的各个插件的自定义配置。
主题内置的使用的插件,扩展了主题的众多功能,你可以在 `plugins` 配置中, 实现对内部使用的各个插件的自定义配置。
## 配置
所有主题内部使用的插件, 均在 `plugins` 字段中进行配置。
``` js
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
@ -23,3 +23,7 @@ export default defineUserConfig({
}),
})
```
:::tip
您无需重复安装这些内置插件,也无需在 [vuepress配置 > plugins](https://v2.vuepress.vuejs.org/zh/reference/config.html#plugins) 中添加它们。主题已在内部完成了这些工作。
:::

View File

@ -9,13 +9,15 @@ permalink: /config/plugin/markdown-power/
提供 Markdown 增强功能。
关联插件: [@vuepress-plume/plugin-md-power](/)
关联插件: [@vuepress-plume/plugin-md-power](https://github.com/pengzhanbo/vuepress-theme-plume/tree/main/plugins/plugin-md-power)
## 配置
默认配置:
```ts
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
@ -32,12 +34,216 @@ export default defineUserConfig({
// jsfiddle: true, // @[jsfiddle](id) 嵌入 jsfiddle
// caniuse: true, // @[caniuse](feature) 嵌入 caniuse
// repl: true, // :::go-repl :::kotlin-repl :::rust-repl
// plot: true, // !!plot!! 隐秘文本
// fileTree: true, // :::file-tree 文件树容器
// imageSize: true, // 在构建阶段为 图片添加 width/height 属性
}
}
}),
})
```
## 配置
## 功能
查看 [文档](../../../plugins/md-power.md)
### 嵌入 PDF
插件默认不启用该功能,你需要手动设置 `pdf``true`
__语法:__
```md
@[pdf](url)
```
请查看 [完整使用文档](../../guide/嵌入/pdf.md)
### iconify 图标
插件默认不启用该功能,你需要手动设置 `icons``true`
得益于 [iconify](https://iconify.design/), 你可以在 Markdown 中使用 iconify __200k+__ 的图标
__语法:__
```md
:[collect:name]:
```
请查看 [完整使用文档](../../guide/markdown/进阶.md#iconify-图标)
### bilibili 视频
插件默认不启用该功能,你需要手动设置 `bilibili``true`
__语法:__
```md
@[bilibili](bvid)
```
请查看 [完整使用文档](../../guide/嵌入/bilibili.md)
### youtube 视频
插件默认不启用该功能,你需要手动设置 `youtube``true`
__语法:__
```md
@[youtube](id)
```
请查看 [完整使用文档](../../guide/嵌入/youtube.md)
### codePen 代码演示
插件默认不启用该功能,你需要手动设置 `codepen``true`
__语法:__
```md
@[codepen](user/slash)
```
请查看 [完整使用文档](../../guide/代码演示/codepen.md)
### codeSandbox 代码演示
插件默认不启用该功能,你需要手动设置 `codeSandbox``true`
__语法:__
```md
@[codesandbox](id)
```
请查看 [完整使用文档](../../guide/代码演示/codeSandbox.md)
### jsfiddle 代码演示
插件默认不启用该功能,你需要手动设置 `jsfiddle``true`
__语法:__
```md
@[jsfiddle](id)
```
请查看 [完整使用文档](../../guide/代码演示/jsFiddle.md)
### caniuse 浏览器支持
插件默认不启用该功能,你需要手动设置 `caniuse``true`
__语法:__
```md
@[caniuse](feature)
```
请查看 [完整使用文档](../../guide/markdown/进阶.md#can-i-use)
### Repl 代码演示容器
插件默认不启用该功能,你需要手动设置 `repl``true`
支持在线运行 Rust、Golang、Kotlin 代码,还支持在线编辑。
或者开启部分功能,如下所示
``` ts
export default defineUserConfig({
theme: plumeTheme({
plugins: {
markdownPower: {
repl: {
rust: true,
go: true,
kotlin: true,
},
},
}
})
})
```
__语法:__
````md
::: rust-repl
```rust
// rust code
```
:::
::: go-repl
```go
// go code
```
:::
::: kotlin-repl
```kotlin
// kotlin code
```
:::
````
请查看完整使用文档:
- [代码演示 > Rust](../../guide/代码演示/rust.md)
- [代码演示 > Golang](../../guide/代码演示/golang.md)
- [代码演示 > Kotlin](../../guide/代码演示/kotlin.md)
### Plot 隐秘文本
插件默认不启用该功能,你需要手动设置 `plot``true`
__语法:__
```md
!!content!!
```
请查看 [完整使用文档](../../guide/markdown/进阶.md#隐秘文本)
### 文件树
插件默认不启用该功能,你需要手动设置 `fileTree``true`
__语法:__
```md
::: file-tree
- folder1
- file1.md
- file2.ts
- folder2
- file3.md
- folder3
:::
```
请查看 [完整使用文档](../../guide/markdown/进阶.md#文件树)
### 图片尺寸
该功能会为 markdown 文件中的 图片引用 添加当前图片的 `width``height` 属性。
通过读取 图片的原始尺寸大小,为 图片设置默认的 图片尺寸 和 比例。
从而解决页面在图片加载未完成到完成时,布局闪烁的问题。
插件默认不启用该功能,你需要手动设置 `imageSize`
- 如果 `imageSize``true`,则插件仅处理本地图片,等同于 `local` 选项;
- 如果 `imageSize``'local'`,则插件仅处理本地图片;
- 如果 `imageSize``'all'`,则插件同时处理本地图片和远程图片。
::: important
__此功能仅在构建生产包时生效。__
请谨慎 使用 `'all'` 选项,由于该选项会在 构建生产包时,请求远程图片资源,这会使得构建时间变长。
虽然主题做了优化仅会加载图片 __几 KB__ 的数据包 用于分析尺寸,但还是会实际影响构建时间。
:::

View File

@ -14,8 +14,8 @@ permalink: /config/plugins/markdown-enhance/
默认配置:
```ts
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({

View File

@ -16,9 +16,16 @@ Shiki 支持多种编程语言。
关联插件: [@vuepress-plume/plugin-shikiji](https://github.com/pengzhanbo/vuepress-theme-plume/tree/main/plugins/plugin-shikiji)
相比于 官方的 [@vuepress/plugin-prismjs](https://ecosystem.vuejs.press/zh/plugins/prismjs.html) 和
[@vuepress/plugin-shiki](https://ecosystem.vuejs.press/zh/plugins/shiki.html) 两个代码高亮插件,
提供了更为丰富的功能支持,包括:
::: details 为什么不用 官方的 @vuepress/plugin-shiki
你可以认为本插件是 官方 [@vuepress/plugin-shiki](https://ecosystem.vuejs.press/zh/plugins/shiki.html) 的
一个分支版本,但本插件更为激进,支持更多新的特性。
同时,我也是 [@vuepress/plugin-shiki](https://ecosystem.vuejs.press/zh/plugins/shiki.html) 的主要维护者之一
,在 `@vuepress-plume/plugin-shikiji` 插件中新增的试验性的新特性,会在未来合适的时候合并到 官方插件中。
:::
## 特性
- [代码行高亮](../../guide/代码/特性支持.md#在代码块中实现行高亮)
- [代码聚焦](../../guide/代码/特性支持.md#代码块中聚焦)
@ -26,13 +33,15 @@ Shiki 支持多种编程语言。
- [代码高亮“错误”和“警告”](../../guide/代码/特性支持.md#高亮-错误-和-警告)
- [代码词高亮](../../guide/代码/特性支持.md#代码块中-词高亮)
- [代码块折叠](../../guide/代码/特性支持.md#折叠代码块)
- [twoslash](../../guide/代码/twoslash.md#twoslash) ,在代码块内提供内联类型提示。
- [twoslash](../../guide/代码/twoslash.md#twoslash) :在代码块内提供内联类型提示。
## 配置
默认配置:
```ts
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
@ -45,8 +54,6 @@ export default defineUserConfig({
})
```
## 配置
### theme
- 类型: `string | { light: string, dark: string }`
@ -123,14 +130,7 @@ interface CopyCodeOptions {
}
```
### codeTransformers
- 类型: `ShikiTransformer[]`
- 默认值: `[]`
代码转换器, 查看 [shiki transformers](https://shiki.style/guide/transformers) 了解更多信息。
### twoslash <Badge type="tip" text="实验性" />
### twoslash
- 类型: `boolean`
- 默认值: `false`
@ -156,3 +156,10 @@ interface CopyCodeOptions {
- 默认值: `false`
将代码块折叠到指定行数。
### codeTransformers
- 类型: `ShikiTransformer[]`
- 默认值: `[]`
代码转换器, 查看 [shiki transformers](https://shiki.style/guide/transformers) 了解更多信息。

View File

@ -14,8 +14,8 @@ permalink: /config/plugins/reading-time/
默认配置:
```ts
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({

View File

@ -30,8 +30,8 @@ VuePress 站点的基本配置文件是 `.vuepress/config.js` ,但也同样支
```ts
import { viteBundler } from '@vuepress/bundler-vite'
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
bundler: viteBundler(),
@ -61,8 +61,8 @@ export default defineUserConfig({
一般我们使用 `.vuepress/config.js` 或者 `.vuepress/config.ts` 来配置主题。
```ts
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({
@ -122,8 +122,8 @@ export default defineThemeConfig({
```ts
import path from 'node:path'
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({

View File

@ -33,8 +33,8 @@ import { Layout, NotFound } from 'vuepress-theme-plume/client'
更多其他组件请查看 [源代码](https://github.com/pengzhanbo/vuepress-theme-plume/tree/main/theme/src/client/components)
```ts
import VPLink from 'vuepress-theme-plume/components/VPLink.vue'
import VPButton from 'vuepress-theme-plume/components/VPButton.vue'
import VPLink from 'vuepress-theme-plume/components/VPLink.vue'
```
## 组合式 API

View File

@ -189,8 +189,8 @@ config:
(还可以在重新修改 分类页/标签页/归档页的链接地址)
```ts
import { plumeTheme } from 'vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({
theme: plumeTheme({

View File

@ -54,8 +54,8 @@ export default defineUserConfig({
`locales` 支持 所有主题配置项。
```js
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from '@vuepress-plume/vuepress-theme-plume'
import { defineUserConfig } from 'vuepress'
export default defineUserConfig({
lang: 'en-US',

View File

@ -196,8 +196,8 @@ cd open-source # 进入 D: 分区下的 open-source 目录
@tab docs/.vuepress/config.js
``` ts :no-line-numbers
import { defineUserConfig } from 'vuepress'
import { viteBundler } from '@vuepress/bundler-vite'
import { defineUserConfig } from 'vuepress'
import { plumeTheme } from 'vuepress-theme-plume'
export default defineUserConfig({

View File

@ -53,8 +53,8 @@ import { Layout } from 'vuepress-theme-plume/client'
也可以使用 渲染函数 实现注入内容,在 `.vuepress/client.ts` 中:
```ts
import { defineClientConfig } from 'vuepress/client'
import { h } from 'vue'
import { defineClientConfig } from 'vuepress/client'
import { Layout } from 'vuepress-theme-plume/client'
import CustomContent from './components/CustomContent.vue'

View File

@ -0,0 +1,75 @@
---
title: 构建优化
icon: clarity:bundle-solid
createTime: 2024/09/10 01:50:20
permalink: /guide/optimize-build/
---
## 图片优化 <Badge type="warning" text="试验性" />
当我们在 markdown 中使用 `[alt](url)` 或者 `<img src="url">` 嵌入图片后,虽然页面按照预期的显示了
图片。
由于图片的体积不同,当图片体积较小,网络情况良好的时候,我们不会感受到页面的布局有产生明显的抖动。
然而,当图片体积较大,或者网络情况较差时,由于图片为完成加载,原本页面上应该显示图片的位置被后面的
内容挤压,等到图片加载完成后,页面布局会发生变化,图片重新占据应该显示的位置,其它的内容被排开。
事实上这个体验相当不友好。特别是对于页面内的图片数量较多时,页面会频繁发生布局变化,这一过程还可能
感知到卡顿,较为明显的是布局的抖动。
因此,让页面布局稳定下来,图片是一个必须解决的问题。
从 [MDN > img](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/img#height) 我们可以知道:
::: info
`<img>` 同时包括 `height``width` 使浏览器在加载图像之前计算图像的长宽比。
此长宽比用于保留显示图像所需的空间,减少甚至防止在下载图像并将其绘制到屏幕上时布局的偏移。
减少布局偏移是良好用户体验和 Web 性能的主要组成部分。
:::
因此,主题围绕这个问题,提供了 一个解决方案:
为 markdown 文件中的 `[alt](url)` 或者 `<img src="url">` 自动添加 `width``height` 属性。
你可以通过配置 `markdownPower` 来启用它:
```ts
export default defineUserConfig({
theme: plumeTheme({
plugins: {
markdownPower: {
imageSize: true, // 'local' | 'all'
},
}
})
})
```
启用此功能后,主题会通过图片资源地址,获取图片的原始尺寸,然后为图片添加 `width``height` 属性。
- 如果设置为 `'local'` 则仅为 本地图片添加 `width``height` 属性。
- 如果设置为 `'all'` 则包括 __本地图片____网络图片__ 都 添加 `width``height` 属性。
- 如果设置为 `true` 则等同于 `'local'`
::: important
请注意,出于性能考虑,即使您启用了此功能,该功能也仅在 构建生产包时生效。
:::
::: important
请谨慎使用 `'all'` 选项,该选项会在构建生产包时,请求所有 markdown 中包含的 远程图片资源,
这对于包含较多图片资源的站点而言,会使得构建时间变长。
主题也针对此类情况做了优化,请求远程图片仅在获取 __几 KB__ 的数据包足够分析尺寸后不再等待请求完成,
同时并发请求其他图片资源。这在一定程度上能够改善构建时间。
:::
::: details 还有其他方案吗?
事实上有的,当前的方案其实是一个折中的方案。
我考虑过使用 [thumbhash](https://github.com/evanw/thumbhash) 为图片生成缩略图,通过 占位图 和 懒加载
等技术方案实现更为平滑优雅的图片加载体验。
然而这是有代价的,这需要使用的 [sharp](https://github.com/lovell/sharp) 或 [canvaskit](https://github.com/google/skia/tree/main/modules/canvaskit) 等图片处理库,对图片进行处理分析,
再通过 [thumbhash](https://github.com/evanw/thumbhash) 生成大概 `20byte` 大小的缩略图。这需要花费更多的时间,
这可能对于用户而言是不可接受的。
:::

View File

@ -171,6 +171,12 @@ interface SidebarItem {
}
```
::: info
以下代码块中 `sidebar` 表示传入到 `notes` 中的 `sidebar` 参数。
这里为了方便演示说明,将其单独使用 `const sidebar: Sidebar = [...]` 进行说明。
:::
当 传入类型为 `string` 时,表示 markdown 文件的路径:
```ts

View File

@ -0,0 +1,308 @@
---
title: 轮播图
icon: dashicons:images-alt2
createTime: 2024/09/12 22:00:22
permalink: /guide/components/swiper/
---
## 概述
使用 `<Swiper>` 组件在 页面中显示轮播图片。
## 使用
使用该组件,首先需要手动安装 `swiper` 库:
::: code-tabs
@tab pnpm
```sh
pnpm add swiper
```
@tab yarn
```sh
yarn add swiper
```
@tab npm
```sh
npm install swiper
```
:::
然后,手动导入 `Swiper` 组件:
```md :no-line-numbers
<!-- 在 markdown 中导入 -->
<script setup>
import Swiper from 'vuepress-theme-plume/features/Swiper.vue'
</script>
<!-- 导入后,即可在 markdown 中使用 -->
<Swiper :items="['img_link1', 'img_link2']" />
```
注册为全局组件:
::: code-tabs
@tab .vuepress/client.ts
```ts
import { defineClientConfig } from 'vuepress/client'
import Swiper from 'vuepress-theme-plume/features/Swiper.vue'
export default defineClientConfig({
enhance({ app }) {
app.component('Swiper', Swiper)
},
})
```
:::
全局组件可在 其他任意 markdown 文件中使用
```md
<Swiper :items="['img_link1', 'img_link2']" />
```
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import Swiper from 'vuepress-theme-plume/features/Swiper.vue'
const images = ref([])
async function fetchImage() {
const res = await fetch('https://api.pengzhanbo.cn/wallpaper/bing/list/zh/').then((res) => res.json())
images.value = res.map(item => ({
name: item.title,
link: item.url,
}))
}
if (!__VUEPRESS_SSR__) {
fetchImage()
}
</script>
**示例:**
<Swiper v-if="images.length" :items="images" />
## Props
| 名称 | 类型 | 默认值 | 说明 |
| ----------------- | ---------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------- |
| items | `string \| { link: string; href?: string; alt?: string}[]` | `[]` | 图片链接数组,传入对象时,`link`表示图片链接,`href`表示跳转链接,`alt`表示图片描述 |
| width | `number \| string` | `100%` | 轮播区域宽度 |
| height | `number \| string` | `100%` | 轮播区域高度 |
| mode | `'banner' \| 'carousel' \| 'broadcast'` | `'banner'` | 轮播模式, `banner`: 轮播图; `carousel`: 走马灯; `broadcast`: 信息展播 |
| navigation | `boolean` | `true` | 是否显示导航按钮 |
| effect | `'slide' \| 'fade' \| 'cube' \| 'coverflow' \| 'flip' \| 'cards' \| 'creative'` | `'slide'` | 轮播效果 |
| delay | `number` | `3000` | 轮播间隔时间, 仅当 `mode: 'banner'` 时生效,单位 `ms` |
| speed | `number` | `300` | 动画持续时间,单位 `ms` |
| loop | `boolean` | `true` | 是否循环 |
| pauseOnMouseEnter | `boolean` | `false` | 是否鼠标悬停时暂停轮播 |
| swipe | `boolean` | `true` | 是否开启手势滑动 |
更多 props 请参考 [Swiper 文档](https://swiperjs.com/swiper-api#parameters)
## 参考示例
### 预设动画效果
**cube:**
<Swiper v-if="images.length" :items="images" effect="cube" />
:::details 查看代码
```md
<Swiper :items="images" effect="cube" />
```
:::
**fade:**
<Swiper v-if="images.length" :items="images" effect="fade" />
:::details 查看代码
```md
<Swiper :items="images" effect="fade" />
```
:::
**coverflow:**
<Swiper v-if="images.length" :items="images" effect="coverflow" />
:::details 查看代码
```md
<Swiper :items="images" effect="coverflow" />
```
:::
**flip:**
<Swiper v-if="images.length" :items="images" effect="flip" />
:::details 查看代码
```md
<Swiper :items="images" effect="flip" />
```
:::
**cards:**
<Swiper v-if="images.length" :items="images" effect="cards" />
:::details 查看代码
```md
<Swiper :items="images" effect="cards" />
```
:::
### 自定义动画效果
**示例 1**
<Swiper v-if="images.length" :items="images" effect="creative" :creativeEffect="{
prev: { shadow: true, translate: [0, 0, -400] },
next: { translate: ['100%', 0, 0] },
}" />
::: details 查看代码
```md
<Swiper :items="images" effect="creative" :creativeEffect="{
prev: { shadow: true, translate: [0, 0, -400] },
next: { translate: ['100%', 0, 0] },
}"
/>
```
:::
**示例 2**
<Swiper v-if="images.length" :items="images" effect="creative" :creativeEffect="{
prev: { shadow: true, translate: [0, 0, -800], rotate: [180, 0, 0] },
next: { shadow: true, translate: [0, 0, -800], rotate: [-180, 0, 0] },
}" />
:::details 查看代码
```md
<Swiper :items="images" effect="creative" :creativeEffect="{
prev: { shadow: true, translate: [0, 0, -800], rotate: [180, 0, 0] },
next: { shadow: true, translate: [0, 0, -800], rotate: [-180, 0, 0] },
}"
/>
```
:::
**示例 3**
<Swiper v-if="images.length" :items="images" effect="creative" :creativeEffect="{
prev: { shadow: true, translate: ['-125%', 0, -800], rotate: [0, 0, -90] },
next: { shadow: true, translate: ['125%', 0, -800], rotate: [0, 0, 90] },
}" />
:::details 查看代码
```md
<Swiper :items="images" effect="creative" :creativeEffect="{
prev: { shadow: true, translate: ['-125%', 0, -800], rotate: [0, 0, -90] },
next: { shadow: true, translate: ['125%', 0, -800], rotate: [0, 0, 90] },
}"
/>
```
:::
**示例 4**
<Swiper v-if="images.length" :items="images" effect="creative" :creativeEffect="{
prev: { shadow: true, origin: 'left center', translate: ['-5%', 0, -200], rotate: [0, 100, 0] },
next: { origin: 'right center', translate: ['5%', 0, -200], rotate: [0, -100, 0] },
}" />
:::details 查看代码
```md
<Swiper :items="images" effect="creative" :creativeEffect="{
prev: { shadow: true, origin: 'left center', translate: ['-5%', 0, -200], rotate: [0, 100, 0] },
next: { origin: 'right center', translate: ['5%', 0, -200], rotate: [0, -100, 0] },
}"
/>
```
:::
### 走马灯
<Swiper
v-if="images.length"
:items="images"
mode="carousel"
:height="200"
:slides-per-view="3"
:space-between="20"
:speed="5500"
/>
:::details 查看代码
```md
<Swiper
:items="images"
mode="carousel"
:height="200"
:slides-per-view="3"
:space-between="20"
:speed="5500"
/>
```
:::
### 信息展播
<Swiper
v-if="images.length"
:items="images"
mode="broadcast"
:height="200"
:slides-per-view="3"
:space-between="20"
mousewheel
/>
:::details 查看代码
```md
<Swiper
:items="images"
mode="broadcast"
:height="200"
:slides-per-view="3"
:space-between="20"
mousewheel
/>
```
:::

View File

@ -24,9 +24,9 @@ permalink: /guide/component-overrides/
如果你想要覆写 `VPFooter.vue` 组件,只需要在配置文件 `.vuepress/config.ts` 中覆盖这个别名即可:
```ts
import { plumeTheme } from 'vuepress-theme-plume'
import { getDirname, path } from 'vuepress/utils'
import { defineUserConfig } from 'vuepress'
import { getDirname, path } from 'vuepress/utils'
import { plumeTheme } from 'vuepress-theme-plume'
const __dirname = getDirname(import.meta.url)

View File

@ -21,10 +21,10 @@ permalink: /guide/custom-style/
@tab .vuepress/client.ts
```ts {1}
import './styles/index.css'
import { defineClientConfig } from 'vuepress/client'
import './styles/index.css'
export default defineClientConfig({
// ...
})

View File

@ -49,8 +49,8 @@ cupiditate sequi.
@tab TS
```ts
import MarkdownIt from 'markdown-it'
import { include } from '@mdit/plugin-include'
import MarkdownIt from 'markdown-it'
// #region snippet
const mdIt = MarkdownIt().use(include, {
@ -66,8 +66,8 @@ mdIt.render('<!-- @include: ./path/to/include/file.md -->', {
@tab JS
```js
const MarkdownIt = require('markdown-it')
const { include } = require('@mdit/plugin-include')
const MarkdownIt = require('markdown-it')
// #region snippet
const mdIt = MarkdownIt().use(include, {

View File

@ -12,15 +12,16 @@
"vuepress": "2.0.0-rc.15"
},
"dependencies": {
"@iconify/json": "^2.2.245",
"@iconify/json": "^2.2.247",
"@simonwep/pickr": "^1.9.1",
"@vuepress/bundler-vite": "2.0.0-rc.15",
"chart.js": "^4.4.4",
"echarts": "^5.5.1",
"flowchart.ts": "^3.0.1",
"http-server": "^14.1.1",
"mermaid": "^11.1.1",
"vue": "^3.5.3",
"mermaid": "^11.2.0",
"swiper": "^11.1.12",
"vue": "^3.5.4",
"vuepress-theme-plume": "workspace:*"
},
"devDependencies": {

View File

@ -31,5 +31,3 @@ readingTime: false
| **锋 | 2024-04-18 | 6.88 | 支持下作者,加油! |
| *杰 | 2024-05-25 | 6.00 | 请你喝杯茶,加油 |
| **党 | 2024-08-22 | 8.80 | 感谢开源,加油 |
1

View File

@ -3,7 +3,7 @@
"type": "module",
"version": "1.0.0-rc.97",
"private": true,
"packageManager": "pnpm@9.9.0",
"packageManager": "pnpm@9.10.0",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"license": "MIT",
"keywords": [
@ -38,10 +38,10 @@
"release:version": "bumpp package.json plugins/*/package.json theme/package.json cli/package.json --execute=\"pnpm release:changelog\" --commit \"build: publish v%s\" --all --tag --push"
},
"devDependencies": {
"@commitlint/cli": "^19.4.1",
"@commitlint/config-conventional": "^19.4.1",
"@pengzhanbo/eslint-config-vue": "^1.15.0",
"@pengzhanbo/stylelint-config": "^1.15.0",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@pengzhanbo/eslint-config-vue": "^1.16.0",
"@pengzhanbo/stylelint-config": "^1.16.0",
"@types/lodash.merge": "^4.6.9",
"@types/node": "20.12.10",
"@types/webpack-env": "^1.18.5",
@ -51,14 +51,14 @@
"cpx2": "^7.0.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.10.0",
"husky": "^9.1.5",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"rimraf": "^6.0.1",
"stylelint": "^16.9.0",
"tsconfig-vuepress": "^5.0.0",
"tsconfig-vuepress": "^5.2.0",
"tsup": "^8.2.4",
"typescript": "^5.5.4",
"wait-on": "^8.0.0"
"typescript": "^5.6.2",
"wait-on": "^8.0.1"
},
"lint-staged": {
"*": "eslint --fix",

View File

@ -40,7 +40,7 @@
"vuepress": "2.0.0-rc.15"
},
"dependencies": {
"vue": "^3.5.3"
"vue": "^3.5.4"
},
"publishConfig": {
"access": "public"

View File

@ -1,5 +1,5 @@
import type { Plugin } from 'vuepress/core'
import { getDirname, path } from 'vuepress/utils'
import type { Plugin } from 'vuepress/core'
const __dirname = getDirname(import.meta.url)

View File

@ -1,4 +1,4 @@
import { type Options, defineConfig } from 'tsup'
import { defineConfig, type Options } from 'tsup'
const clientExternal: (string | RegExp)[] = [
/.*\.vue$/,

View File

@ -1,5 +1,5 @@
import type { Plugin } from 'vuepress/core'
import { getDirname, path } from 'vuepress/utils'
import type { Plugin } from 'vuepress/core'
export function fontsPlugin(): Plugin {
return {

View File

@ -1,4 +1,4 @@
import { type Options, defineConfig } from 'tsup'
import { defineConfig, type Options } from 'tsup'
const clientExternal: (string | RegExp)[] = [
/.*\.vue$/,

View File

@ -45,10 +45,10 @@
"image-size": "^1.1.1",
"markdown-it-container": "^4.0.0",
"nanoid": "^5.0.7",
"shiki": "^1.16.2",
"shiki": "^1.17.0",
"tm-grammars": "^1.17.18",
"tm-themes": "^1.8.1",
"vue": "^3.5.3"
"vue": "^3.5.4"
},
"devDependencies": {
"@types/markdown-it": "^14.1.2"

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, getCurrentInstance, ref } from 'vue'
import { useEventListener } from '@vueuse/core'
import { computed, getCurrentInstance, ref } from 'vue'
interface MessageData {
type: string

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import { getHighlighterCore } from 'shiki/core'
import type { HighlighterCore } from 'shiki/core'
import editorData from '@internal/md-power/replEditorData'
import { getHighlighterCore } from 'shiki/core'
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'
import type { HighlighterCore } from 'shiki/core'
import { resolveCodeInfo } from '../composables/codeRepl.js'
let highlighter: HighlighterCore | null = null

View File

@ -1,10 +1,10 @@
<script setup lang="ts">
import { defineAsyncComponent, shallowRef } from 'vue'
import { useCodeRepl } from '../composables/codeRepl.js'
import IconClose from './IconClose.vue'
import IconConsole from './IconConsole.vue'
import IconRun from './IconRun.vue'
import Loading from './Loading.vue'
import IconConsole from './IconConsole.vue'
import IconClose from './IconClose.vue'
defineProps<{
editable?: boolean

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
const props = defineProps<{
type: 'file' | 'folder'
@ -10,13 +10,22 @@ const props = defineProps<{
const active = ref(!!props.expanded)
const el = ref<HTMLElement>()
function toggle() {
active.value = !active.value
}
onMounted(() => {
if (!el.value || props.type !== 'folder')
return
el.value.querySelector('.tree-node.folder')?.addEventListener('click', () => {
active.value = !active.value
})
el.value.querySelector('.tree-node.folder')?.addEventListener('click', toggle)
})
onUnmounted(() => {
if (!el.value || props.type !== 'folder')
return
el.value.querySelector('.tree-node.folder')?.removeEventListener('click', toggle)
})
</script>
@ -45,6 +54,15 @@ onMounted(() => {
transition: border var(--t-color), background-color var(--t-color);
}
.vp-file-tree .vp-file-tree-title {
padding-left: 16px;
margin: -16px -16px 0;
font-weight: bold;
color: var(--vp-c-text-1);
border-bottom: solid 1px var(--vp-c-divider);
transition: color var(--t-color), border-color var(--t-color);
}
.vp-file-tree ul {
padding: 0 !important;
margin: 0 !important;

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
import { onMounted, toRefs } from 'vue'
import type { PDFTokenMeta } from '../../shared/index.js'
import { useSize } from '../composables/size.js'
import { usePDF } from '../composables/pdf.js'
import { useSize } from '../composables/size.js'
import type { PDFTokenMeta } from '../../shared/index.js'
const props = defineProps<PDFTokenMeta>()

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed, ref, shallowRef } from 'vue'
import { onClickOutside, useMediaQuery } from '@vueuse/core'
import { computed, ref, shallowRef } from 'vue'
import { usePageFrontmatter } from 'vuepress/client'
import type { PlotOptions } from '../../shared/index.js'
import { pluginOptions } from '../options.js'
import type { PlotOptions } from '../../shared/index.js'
const props = defineProps<Omit<PlotOptions, 'tag'>>()

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, getCurrentInstance, ref } from 'vue'
import type { ReplitTokenMeta } from '../../shared/index.js'
import Loading from './Loading.vue'
import type { ReplitTokenMeta } from '../../shared/index.js'
const props = defineProps<ReplitTokenMeta>()

View File

@ -1,4 +1,4 @@
import { type Ref, onMounted, ref } from 'vue'
import { onMounted, type Ref, ref } from 'vue'
import { http } from '../utils/http.js'
import { sleep } from '../utils/sleep.js'
import { rustExecute } from './rustRepl.js'

View File

@ -11,11 +11,11 @@
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { ensureEndingSlash, isLinkHttp } from 'vuepress/shared'
import { withBase } from 'vuepress/client'
import type { PDFEmbedType, PDFTokenMeta } from '../../shared/index.js'
import { ensureEndingSlash, isLinkHttp } from 'vuepress/shared'
import { pluginOptions } from '../options.js'
import { checkIsMobile, checkIsSafari, checkIsiPad } from '../utils/is.js'
import { checkIsiPad, checkIsMobile, checkIsSafari } from '../utils/is.js'
import type { PDFEmbedType, PDFTokenMeta } from '../../shared/index.js'
function queryStringify(options: PDFTokenMeta): string {
const { page, noToolbar, zoom } = options
@ -105,7 +105,7 @@ export function usePDF(
// We're moving into the age of MIME-less browsers. They mostly all support PDF rendering without plugins.
&& (isModernBrowser
// Modern versions of Firefox come bundled with PDFJS
|| isFirefoxWithPDFJS)
|| isFirefoxWithPDFJS)
if (!url)
return

View File

@ -11,10 +11,10 @@
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import type { MaybeRef } from '@vueuse/core'
import { useEventListener } from '@vueuse/core'
import type { Ref, ShallowRef, ToRefs } from 'vue'
import { computed, isRef, onMounted, ref, shallowRef, toValue, watch } from 'vue'
import type { MaybeRef } from '@vueuse/core'
import type { Ref, ShallowRef, ToRefs } from 'vue'
import type { SizeOptions } from '../../shared/index.js'
export interface SizeInfo<T extends HTMLElement> {

View File

@ -1,5 +1,5 @@
import { isLinkHttp } from 'vuepress/shared'
import { withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
export function normalizeLink(url: string): string {
return isLinkHttp(url) ? url : withBase(url)

View File

@ -2,13 +2,13 @@
* @[caniuse embed{1,2,3,4}](feature_name)
* @[caniuse image](feature_name)
*/
import type { PluginWithOptions } from 'markdown-it'
import type Token from 'markdown-it/lib/token.mjs'
import type MarkdownIt from 'markdown-it'
import container from 'markdown-it-container'
import { customAlphabet } from 'nanoid'
import type { CanIUseMode, CanIUseOptions, CanIUseTokenMeta } from '../../shared/index.js'
import type { PluginWithOptions } from 'markdown-it'
import type MarkdownIt from 'markdown-it'
import type Token from 'markdown-it/lib/token.mjs'
import { createRuleBlock } from '../utils/createRuleBlock.js'
import type { CanIUseMode, CanIUseOptions, CanIUseTokenMeta } from '../../shared/index.js'
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5)
const UNDERLINE_RE = /_+/g

View File

@ -4,10 +4,10 @@
* @[codesanbox title="xxx" layout="Editor+Preview" height="500px" navbar="false" console="false"](id#filepath)
*/
import type { PluginWithOptions } from 'markdown-it'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { parseRect } from '../utils/parseRect.js'
import type { CodeSandboxTokenMeta } from '../../shared/index.js'
import { createRuleBlock } from '../utils/createRuleBlock.js'
import { parseRect } from '../utils/parseRect.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import type { CodeSandboxTokenMeta } from '../../shared/index.js'
export const codeSandboxPlugin: PluginWithOptions<never> = (md) => {
createRuleBlock<CodeSandboxTokenMeta>(md, {

View File

@ -4,10 +4,10 @@
* @[codepen preview editable title="" height="400px" tab="css,result" theme="dark"](user/slash)
*/
import type { PluginWithOptions } from 'markdown-it'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { parseRect } from '../utils/parseRect.js'
import type { CodepenTokenMeta } from '../../shared/index.js'
import { createRuleBlock } from '../utils/createRuleBlock.js'
import { parseRect } from '../utils/parseRect.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import type { CodepenTokenMeta } from '../../shared/index.js'
const CODEPEN_LINK = 'https://codepen.io/'

View File

@ -1,4 +1,4 @@
import { FileIcons, definitions } from './icons.js'
import { definitions, FileIcons } from './icons.js'
export interface FileIcon {
name: string

View File

@ -1,10 +1,10 @@
import fs from 'node:fs'
import container from 'markdown-it-container'
import type { Markdown } from 'vuepress/markdown'
import type Token from 'markdown-it/lib/token.mjs'
import type { App } from 'vuepress/core'
import { resolveTreeNodeInfo, updateInlineToken } from './resolveTreeNodeInfo.js'
import type { Markdown } from 'vuepress/markdown'
import { type FileIcon, folderIcon, getFileIcon } from './findIcon.js'
import { resolveTreeNodeInfo, updateInlineToken } from './resolveTreeNodeInfo.js'
const type = 'file-tree'
const closeType = `container_${type}_close`
@ -21,7 +21,7 @@ export async function fileTreePlugin(app: App, md: Markdown) {
for (
let i = idx + 1;
!(tokens[i].nesting === -1
&& tokens[i].type === closeType);
&& tokens[i].type === closeType);
++i
) {
const token = tokens[i]
@ -46,7 +46,10 @@ export async function fileTreePlugin(app: App, md: Markdown) {
token.tag = componentName
}
}
return '<div class="vp-file-tree">'
const info = tokens[idx].info.trim()
const title = info.slice(type.length).trim()
return `<div class="vp-file-tree">${title ? `<p class="vp-file-tree-title">${title}</p>` : ''}`
}
else {
return '</div>'

View File

@ -1,5 +1,5 @@
import { removeLeadingSlash } from 'vuepress/shared'
import Token from 'markdown-it/lib/token.mjs'
import { removeLeadingSlash } from 'vuepress/shared'
interface FileTreeNode {
filename: string

View File

@ -1,14 +1,46 @@
import { Buffer } from 'node:buffer'
import http from 'node:https'
import { URL } from 'node:url'
import { isLinkExternal, isLinkHttp } from '@vuepress/helper'
import imageSize from 'image-size'
import { fs, path } from 'vuepress/utils'
import type { RenderRule } from 'markdown-it/lib/renderer.mjs'
import type { App } from 'vuepress'
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
import { isLinkExternal } from '@vuepress/helper'
import { fs, path } from '@vuepress/utils'
import imageSize from 'image-size'
import { resolveAttrs } from '../utils/resolveAttrs.js'
export function imageSizePlugin(app: App, md: Markdown): void {
if (!app.env.isBuild)
interface ImgSize {
width: number
height: number
}
const REG_IMG = /!\[.*?\]\(.*?\)/g
const REG_IMG_TAG = /<img(.*?)>/g
const REG_IMG_TAG_SRC = /src(?:set)?=(['"])(.+?)\1/g
const BADGE_LIST = [
'https://img.shields.io',
'https://badge.fury.io',
'https://badgen.net',
'https://forthebadge.com',
'https://vercel.com/button',
]
const cache = new Map<string, ImgSize>()
export async function imageSizePlugin(
app: App,
md: Markdown,
type: boolean | 'local' | 'all' = false,
) {
if (!app.env.isBuild || !type)
return
const cache = new Map<string, { width: number, height: number }>()
if (type === 'all') {
try {
await scanRemoteImageSize(app)
}
catch {}
}
const imageRule = md.renderer.rules.image!
md.renderer.rules.image = (tokens, idx, options, env: MarkdownEnv, self) => {
@ -17,25 +49,77 @@ export function imageSizePlugin(app: App, md: Markdown): void {
const token = tokens[idx]
const src = token.attrGet('src')
if (!src || src.startsWith('data:') || isLinkExternal(src))
return imageRule(tokens, idx, options, env, self)
const width = token.attrGet('width')
const height = token.attrGet('height')
const size = resolveSize(src, width, height, env)
if (size) {
token.attrSet('width', `${size.width}`)
token.attrSet('height', `${size.height}`)
}
return imageRule(tokens, idx, options, env, self)
}
const rawHtmlBlockRule = md.renderer.rules.html_block!
const rawHtmlInlineRule = md.renderer.rules.html_inline!
md.renderer.rules.html_block = createHtmlRule(rawHtmlBlockRule)
md.renderer.rules.html_inline = createHtmlRule(rawHtmlInlineRule)
function createHtmlRule(rawHtmlRule: RenderRule): RenderRule {
return (tokens, idx, options, env, self) => {
const token = tokens[idx]
token.content = token.content.replace(REG_IMG_TAG, (raw, info) => {
const { attrs } = resolveAttrs(info)
const src = attrs.src || attrs.srcset
const size = resolveSize(src, attrs.width, attrs.height, env)
if (!size)
return raw
attrs.width = size.width
attrs.height = size.height
const imgAttrs = Object.entries(attrs)
.map(([key, value]) => typeof value === 'boolean' ? key : `${key}="${value}"`)
.join(' ')
return `<img ${imgAttrs}>`
})
return rawHtmlRule(tokens, idx, options, env, self)
}
}
function resolveSize(
src: string | null | undefined,
width: string | null | undefined,
height: string | null | undefined,
env: MarkdownEnv,
): false | ImgSize {
if (!src || src.startsWith('data:'))
return false
if (width && height)
return imageRule(tokens, idx, options, env, self)
return false
const filepath = resolveImageUrl(src, env)
const isExternal = isLinkExternal(src, env.base)
const filepath = isExternal ? src : resolveImageUrl(src, env, app)
if (!cache.has(filepath)) {
if (!filepath || !fs.existsSync(filepath))
return imageRule(tokens, idx, options, env, self)
if (isExternal) {
if (!cache.has(filepath))
return false
}
else {
if (!cache.has(filepath)) {
if (!fs.existsSync(filepath))
return false
const { width: w, height: h } = imageSize(filepath)
if (!w || !h)
return imageRule(tokens, idx, options, env, self)
cache.set(filepath, { width: w, height: h })
const { width: w, height: h } = imageSize(filepath)
if (!w || !h)
return false
cache.set(filepath, { width: w, height: h })
}
}
const { width: originalWidth, height: originalHeight } = cache.get(filepath)!
@ -44,32 +128,117 @@ export function imageSizePlugin(app: App, md: Markdown): void {
if (width && !height) {
const w = Number.parseInt(width, 10)
token.attrSet('width', `${w}`)
token.attrSet('height', `${Math.round(w / ratio)}`)
return { width: w, height: Math.round(w / ratio) }
}
else if (height && !width) {
const h = Number.parseInt(height, 10)
token.attrSet('width', `${Math.round(h * ratio)}`)
token.attrSet('height', `${h}`)
return { width: Math.round(h * ratio), height: h }
}
else {
token.attrSet('width', `${originalWidth}`)
token.attrSet('height', `${originalHeight}`)
return { width: originalWidth, height: originalHeight }
}
return imageRule(tokens, idx, options, env, self)
}
function resolveImageUrl(src: string, env: MarkdownEnv): string {
if (src[0] === '/')
return app.dir.public(src.slice(1))
if (env.filePathRelative)
return app.dir.source(path.join(path.dirname(env.filePathRelative), src))
if (env.filePath)
return path.resolve(env.filePath, src)
return ''
}
}
function resolveImageUrl(src: string, env: MarkdownEnv, app: App): string {
if (src[0] === '/')
return app.dir.public(src.slice(1))
if (env.filePathRelative && src[0] === '.')
return app.dir.source(path.join(path.dirname(env.filePathRelative), src))
// fallback
if (env.filePath && (src[0] === '.' || src[0] === '/'))
return path.resolve(env.filePath, src)
return path.resolve(src)
}
export async function scanRemoteImageSize(app: App) {
if (!app.env.isBuild)
return
const cwd = app.dir.source()
const files = await fs.readdir(cwd, { recursive: true })
const imgList: string[] = []
for (const file of files) {
const filepath = path.join(cwd, file)
if (
(await (fs.stat(filepath))).isFile()
&& !filepath.includes('.vuepress')
&& !filepath.includes('node_modules')
&& filepath.endsWith('.md')
) {
const content = await fs.readFile(filepath, 'utf-8')
// [xx](xxx)
const syntaxMatched = content.match(REG_IMG) ?? []
for (const img of syntaxMatched) {
const src = img.slice(img.indexOf('](') + 2, -1)
addList(src.split(/\s+/)[0])
}
// <img src=""> or <img srcset="xxx">
const tagMatched = content.match(REG_IMG_TAG) ?? []
for (const img of tagMatched) {
const src = img.match(REG_IMG_TAG_SRC)?.[2] ?? ''
addList(src)
}
}
}
function addList(src: string) {
if (src && isLinkHttp(src)
&& !imgList.includes(src)
&& !BADGE_LIST.some(badge => src.startsWith(badge))
) {
imgList.push(src)
}
}
await Promise.all(imgList.map(async (src) => {
if (!cache.has(src)) {
const { width, height } = await fetchImageSize(src)
if (width && height)
cache.set(src, { width, height })
}
}))
}
function fetchImageSize(src: string): Promise<ImgSize> {
const link = new URL(src)
return new Promise((resolve) => {
http.get(link, async (stream) => {
const chunks: any[] = []
for await (const chunk of stream) {
chunks.push(chunk)
try {
const { width, height } = imageSize(Buffer.concat(chunks))
if (width && height) {
return resolve({ width, height })
}
}
catch {}
}
const { width, height } = imageSize(Buffer.concat(chunks))
resolve({ width: width!, height: height! })
}).on('error', () => resolve({ width: 0, height: 0 }))
})
}
export async function resolveImageSize(app: App, url: string, remote = false): Promise<ImgSize> {
if (cache.has(url))
return cache.get(url)!
if (isLinkHttp(url) && remote) {
return await fetchImageSize(url)
}
if (url[0] === '/') {
const filepath = app.dir.public(url.slice(1))
if (fs.existsSync(filepath)) {
const { width, height } = imageSize(filepath)
return { width: width!, height: height! }
}
}
return { width: 0, height: 0 }
}

View File

@ -3,10 +3,10 @@
* @[jsfiddle theme="dark" tab="js,css,html,result"](user/id)
*/
import type { PluginWithOptions } from 'markdown-it'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { parseRect } from '../utils/parseRect.js'
import type { JSFiddleTokenMeta } from '../../shared/index.js'
import { createRuleBlock } from '../utils/createRuleBlock.js'
import { parseRect } from '../utils/parseRect.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import type { JSFiddleTokenMeta } from '../../shared/index.js'
export const jsfiddlePlugin: PluginWithOptions<never> = (md) => {
createRuleBlock<JSFiddleTokenMeta>(md, {

View File

@ -1,8 +1,8 @@
import type markdownIt from 'markdown-it'
import container from 'markdown-it-container'
import { fs, getDirname, path } from 'vuepress/utils'
import type markdownIt from 'markdown-it'
import type Token from 'markdown-it/lib/token.mjs'
import type { App } from 'vuepress/core'
import { fs, getDirname, path } from 'vuepress/utils'
import type { ReplEditorData, ReplOptions } from '../../shared/index.js'
const RE_INFO = /^(#editable)?(.*)$/

View File

@ -5,10 +5,10 @@
*/
import { path } from 'vuepress/utils'
import type { PluginWithOptions } from 'markdown-it'
import type { PDFTokenMeta } from '../../shared/index.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { parseRect } from '../utils/parseRect.js'
import { createRuleBlock } from '../utils/createRuleBlock.js'
import { parseRect } from '../utils/parseRect.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import type { PDFTokenMeta } from '../../shared/index.js'
export const pdfPlugin: PluginWithOptions<never> = (md) => {
createRuleBlock<PDFTokenMeta>(md, {

View File

@ -4,10 +4,10 @@
* @[replit title="" height="400px" width="100%" theme="dark"](user/repl-name)
*/
import type { PluginWithOptions } from 'markdown-it'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { parseRect } from '../utils/parseRect.js'
import type { ReplitTokenMeta } from '../../shared/index.js'
import { createRuleBlock } from '../utils/createRuleBlock.js'
import { parseRect } from '../utils/parseRect.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import type { ReplitTokenMeta } from '../../shared/index.js'
export const replitPlugin: PluginWithOptions<never> = (md) => {
createRuleBlock<ReplitTokenMeta>(md, {

View File

@ -6,11 +6,11 @@
*/
import { URLSearchParams } from 'node:url'
import type { PluginWithOptions } from 'markdown-it'
import type { BilibiliTokenMeta } from '../../../shared/index.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { parseRect } from '../../utils/parseRect.js'
import { timeToSeconds } from '../../utils/timeToSeconds.js'
import { createRuleBlock } from '../../utils/createRuleBlock.js'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { timeToSeconds } from '../../utils/timeToSeconds.js'
import type { BilibiliTokenMeta } from '../../../shared/index.js'
const BILIBILI_LINK = 'https://player.bilibili.com/player.html'

View File

@ -3,11 +3,11 @@
*/
import { URLSearchParams } from 'node:url'
import type { PluginWithOptions } from 'markdown-it'
import type { YoutubeTokenMeta } from '../../../shared/index.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { parseRect } from '../../utils/parseRect.js'
import { timeToSeconds } from '../../utils/timeToSeconds.js'
import { createRuleBlock } from '../../utils/createRuleBlock.js'
import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { timeToSeconds } from '../../utils/timeToSeconds.js'
import type { YoutubeTokenMeta } from '../../../shared/index.js'
const YOUTUBE_LINK = 'https://www.youtube.com/embed/'

View File

@ -1,2 +1,3 @@
export * from './plugin.js'
export * from '../shared/index.js'
export { resolveImageSize } from './features/imageSize.js'
export * from './plugin.js'

View File

@ -1,21 +1,21 @@
import type { Plugin } from 'vuepress/core'
import type MarkdownIt from 'markdown-it'
import { addViteOptimizeDepsInclude } from '@vuepress/helper'
import type { CanIUseOptions, MarkdownPowerPluginOptions } from '../shared/index.js'
import type MarkdownIt from 'markdown-it'
import type { Plugin } from 'vuepress/core'
import { caniusePlugin, legacyCaniuse } from './features/caniuse.js'
import { pdfPlugin } from './features/pdf.js'
import { codepenPlugin } from './features/codepen.js'
import { codeSandboxPlugin } from './features/codeSandbox.js'
import { fileTreePlugin } from './features/fileTree/index.js'
import { iconsPlugin } from './features/icons.js'
import { imageSizePlugin } from './features/imageSize.js'
import { jsfiddlePlugin } from './features/jsfiddle.js'
import { langReplPlugin } from './features/langRepl.js'
import { pdfPlugin } from './features/pdf.js'
import { plotPlugin } from './features/plot.js'
import { replitPlugin } from './features/replit.js'
import { bilibiliPlugin } from './features/video/bilibili.js'
import { youtubePlugin } from './features/video/youtube.js'
import { codepenPlugin } from './features/codepen.js'
import { replitPlugin } from './features/replit.js'
import { codeSandboxPlugin } from './features/codeSandbox.js'
import { jsfiddlePlugin } from './features/jsfiddle.js'
import { plotPlugin } from './features/plot.js'
import { langReplPlugin } from './features/langRepl.js'
import { prepareConfigFile } from './prepareConfigFile.js'
import { fileTreePlugin } from './features/fileTree/index.js'
import { imageSizePlugin } from './features/imageSize.js'
import type { CanIUseOptions, MarkdownPowerPluginOptions } from '../shared/index.js'
export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): Plugin {
return (app) => {
@ -39,7 +39,7 @@ export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): P
},
extendsMarkdown: async (md: MarkdownIt, app) => {
imageSizePlugin(app, md)
await imageSizePlugin(app, md, options.imageSize)
if (options.caniuse) {
const caniuse = options.caniuse === true ? {} : options.caniuse

View File

@ -1,5 +1,5 @@
import { getDirname, path } from 'vuepress/utils'
import { ensureEndingSlash } from '@vuepress/helper'
import { getDirname, path } from 'vuepress/utils'
import type { App } from 'vuepress/core'
import type { MarkdownPowerPluginOptions } from '../shared/index.js'

View File

@ -1,14 +1,14 @@
export * from './caniuse.js'
export * from './pdf.js'
export * from './icons.js'
export * from './video.js'
export * from './codepen.js'
export * from './codeSandbox.js'
export * from './icons.js'
export * from './jsfiddle.js'
export * from './pdf.js'
export * from './plot.js'
export * from './plugin.js'
export * from './repl.js'
export * from './replit.js'
export * from './jsfiddle.js'
export * from './plot.js'
export * from './plugin.js'
export * from './size.js'
export * from './video.js'

View File

@ -1,32 +1,125 @@
import type { CanIUseOptions } from './caniuse.js'
import type { PDFOptions } from './pdf.js'
import type { IconsOptions } from './icons.js'
import type { PDFOptions } from './pdf.js'
import type { PlotOptions } from './plot.js'
import type { ReplOptions } from './repl.js'
export interface MarkdownPowerPluginOptions {
/**
* PDF
*
* `@[pdf](pdf_url)`
*
* @default false
*/
pdf?: boolean | PDFOptions
// new syntax
/**
* iconify
*
* `:[collect:icon_name]:`
*
* @default false
*/
icons?: boolean | IconsOptions
/**
*
*
* `!!plot_content!!`
*
* @default false
*/
plot?: boolean | PlotOptions
// video embed
/**
* bilibili
*
* `@[bilibili](bid)`
*
* @default false
*/
bilibili?: boolean
/**
* youtube
*
* `@[youtube](video_id)`
*
* @default false
*/
youtube?: boolean
// code embed
/**
* codepen
*
* `@[codepen](pen_id)`
*
* @default false
*/
codepen?: boolean
/**
* @deprecated
*/
replit?: boolean
/**
* codeSandbox
*
* `@[codesandbox](codesandbox_id)`
*
* @default false
*/
codeSandbox?: boolean
/**
* jsfiddle
*
* `@[jsfiddle](jsfiddle_id)`
*
* @default false
*/
jsfiddle?: boolean
// container
/**
* REPL
*
* @default false
*/
repl?: false | ReplOptions
/**
*
*
* @default false
*/
fileTree?: boolean
/**
* caniuse
*
* `@[caniuse](feature_name)`
*
* @default false
*/
caniuse?: boolean | CanIUseOptions
// enhance
/**
*
*
* __请注意__
*
* - `true` `'local'`
* - `local` width height
* - `all`( ) width height
*
*
* `width` `height`
*
* 使 `all`
* KB
*
* @default false
*/
imageSize?: boolean | 'local' | 'all'
}

View File

@ -1,4 +1,4 @@
import { type Options, defineConfig } from 'tsup'
import { defineConfig, type Options } from 'tsup'
const config = [
{ dir: 'composables', files: ['codeRepl.ts', 'pdf.ts', 'rustRepl.ts', 'size.ts'] },

View File

@ -44,11 +44,11 @@
"@vueuse/core": "^11.0.3",
"@vueuse/integrations": "^11.0.3",
"chokidar": "^3.6.0",
"focus-trap": "^7.5.4",
"focus-trap": "^7.6.0",
"mark.js": "^8.11.1",
"minisearch": "^7.1.0",
"p-map": "^7.0.2",
"vue": "^3.5.3"
"vue": "^3.5.4"
},
"publishConfig": {
"access": "public"

View File

@ -4,8 +4,8 @@ import {
defineAsyncComponent,
ref,
} from 'vue'
import type { SearchBoxLocales, SearchOptions } from '../../shared/index.js'
import SearchButton from './SearchButton.vue'
import type { SearchBoxLocales, SearchOptions } from '../../shared/index.js'
defineProps<{
locales: SearchBoxLocales

View File

@ -1,17 +1,4 @@
<script setup lang="ts">
import {
type Ref,
computed,
markRaw,
nextTick,
onBeforeUnmount,
onMounted,
ref,
shallowRef,
toRef,
watch,
} from 'vue'
import { useRouteLocale, useRouter } from 'vuepress/client'
import {
computedAsync,
debouncedWatch,
@ -20,15 +7,28 @@ import {
useScrollLock,
useSessionStorage,
} from '@vueuse/core'
import Mark from 'mark.js/src/vanilla.js'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import Mark from 'mark.js/src/vanilla.js'
import MiniSearch, { type SearchResult } from 'minisearch'
import {
computed,
markRaw,
nextTick,
onBeforeUnmount,
onMounted,
type Ref,
ref,
shallowRef,
toRef,
watch,
} from 'vue'
import { useRouteLocale, useRouter } from 'vuepress/client'
import { useLocale, useSearchIndex } from '../composables/index.js'
import type { SearchBoxLocales, SearchOptions } from '../../shared/index.js'
import { LRUCache } from '../utils/index.js'
import SearchIcon from './icons/SearchIcon.vue'
import ClearIcon from './icons/ClearIcon.vue'
import BackIcon from './icons/BackIcon.vue'
import ClearIcon from './icons/ClearIcon.vue'
import SearchIcon from './icons/SearchIcon.vue'
import type { SearchBoxLocales, SearchOptions } from '../../shared/index.js'
const props = defineProps<{
locales: SearchBoxLocales

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { toRef } from 'vue'
import type { SearchBoxLocales } from '../../shared/index.js'
import { useLocale } from '../composables/index.js'
import type { SearchBoxLocales } from '../../shared/index.js'
const props = defineProps<{
locales: SearchBoxLocales

View File

@ -1,2 +1,2 @@
export * from './searchIndex.js'
export * from './locale.js'
export * from './searchIndex.js'

View File

@ -1,6 +1,6 @@
import type { MaybeRef } from 'vue'
import { useRouteLocale } from 'vuepress/client'
import { computed, toRef } from 'vue'
import { useRouteLocale } from 'vuepress/client'
import type { MaybeRef } from 'vue'
import type { SearchBoxLocales } from '../../shared/index.js'
const defaultLocales: SearchBoxLocales = {

View File

@ -1,8 +1,8 @@
import { h } from 'vue'
import { defineClientConfig } from 'vuepress/client'
import type { ClientConfig } from 'vuepress/client'
import { h } from 'vue'
import type { SearchBoxLocales, SearchOptions } from '../shared/index.js'
import Search from './components/Search.vue'
import type { SearchBoxLocales, SearchOptions } from '../shared/index.js'
declare const __SEARCH_LOCALES__: SearchBoxLocales
declare const __SEARCH_OPTIONS__: SearchOptions

View File

@ -1,7 +1,7 @@
import { searchPlugin } from './searchPlugin.js'
export { prepareSearchIndex } from './prepareSearchIndex.js'
export * from '../shared/index.js'
export { prepareSearchIndex } from './prepareSearchIndex.js'
export {
searchPlugin,

View File

@ -1,7 +1,7 @@
import type { App, Page } from 'vuepress/core'
import MiniSearch from 'minisearch'
import pMap from 'p-map'
import { colors, logger } from 'vuepress/utils'
import type { App, Page } from 'vuepress/core'
import type { SearchOptions, SearchPluginOptions } from '../shared/index.js'
export interface SearchIndexOptions {

View File

@ -1,9 +1,9 @@
import chokidar from 'chokidar'
import type { Plugin } from 'vuepress/core'
import { getDirname, path } from 'vuepress/utils'
import { addViteOptimizeDepsInclude } from '@vuepress/helper'
import type { SearchPluginOptions } from '../shared/index.js'
import chokidar from 'chokidar'
import { getDirname, path } from 'vuepress/utils'
import type { Plugin } from 'vuepress/core'
import { onSearchIndexRemoved, onSearchIndexUpdated, prepareSearchIndex } from './prepareSearchIndex.js'
import type { SearchPluginOptions } from '../shared/index.js'
const __dirname = getDirname(import.meta.url)

View File

@ -1,5 +1,5 @@
import type { LocaleConfig, Page } from 'vuepress/core'
import type { Options as MiniSearchOptions } from 'minisearch'
import type { LocaleConfig, Page } from 'vuepress/core'
export interface SearchLocaleOptions {
placeholder: string

View File

@ -1,4 +1,4 @@
import { type Options, defineConfig } from 'tsup'
import { defineConfig, type Options } from 'tsup'
const sharedExternal: (string | RegExp)[] = [
/.*\/shared\/index\.js$/,

View File

@ -36,8 +36,8 @@
"vuepress": "2.0.0-rc.15"
},
"dependencies": {
"@shikijs/transformers": "^1.16.2",
"@shikijs/twoslash": "^1.16.2",
"@shikijs/transformers": "^1.17.0",
"@shikijs/twoslash": "^1.17.0",
"@types/hast": "^3.0.4",
"@vuepress/helper": "2.0.0-rc.42",
"@vueuse/core": "^11.0.3",
@ -46,7 +46,7 @@
"mdast-util-gfm": "^3.0.0",
"mdast-util-to-hast": "^13.2.0",
"nanoid": "^5.0.7",
"shiki": "^1.16.2",
"shiki": "^1.17.0",
"twoslash": "^0.2.11",
"twoslash-vue": "^0.2.11"
},

View File

@ -1,5 +1,5 @@
import type { App } from 'vue'
import FloatingVue, { recomputeAllPoppers } from 'floating-vue'
import type { App } from 'vue'
import 'floating-vue/dist/style.css'
const isMobile = typeof navigator !== 'undefined' && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)

View File

@ -1,7 +1,7 @@
import type { App } from 'vuepress'
import type { Markdown, MarkdownEnv } from 'vuepress/markdown'
import type { CopyCodeOptions } from '../types.js'
import { createCopyCodeButtonRender } from './createCopyCodeButtonRender.js'
import type { CopyCodeOptions } from '../types.js'
/**
* This plugin should work after `preWrapperPlugin`,

Some files were not shown because too many files have changed in this diff Show More