feat: add group-icons for code title

This commit is contained in:
YunYouJun 2025-03-03 03:15:35 +08:00
parent 7f6b1934a6
commit ba2c7be3af
39 changed files with 260 additions and 190 deletions

View File

@ -1,11 +1,26 @@
<script setup lang="ts">
import { ref } from 'vue'
import { onMounted, ref } from 'vue'
const num = ref(100)
/**
* 如果你需要操作 DOM 元素可以在 onMounted 钩子中执行
*/
onMounted(() => {
setTimeout(() => {
document.title = 'Custom Vue Demo'
}, 3000)
})
</script>
<template>
<div class="text-bold text-red">
<div class="font-bold text-red custom-style">
{{ num }}
</div>
</template>
<style>
.custom-style {
font-size: 20px;
}
</style>

View File

@ -14,8 +14,7 @@ pnpm add [valaxy-addon-package1] [valaxy-addon-package2]
使用
```ts
// valaxy.config.ts
```ts [valaxy.config.ts]
import { defineValaxyConfig } from 'valaxy'
import { addonTest } from 'valaxy-addon-test'
@ -35,7 +34,7 @@ export default defineValaxyConfig({
譬如开启 Waline 评论:
```ts
```ts [valaxy.config.ts]
import { defineValaxyConfig } from 'valaxy'
import { addonWaline } from 'valaxy-addon-waline'

View File

@ -41,21 +41,4 @@ pnpm create valaxy
> 请参考 [生命周期钩子](/guide/custom/hooks) 了解更多。
```ts {11-14}
import consola from 'consola'
import { defineValaxyAddon } from 'valaxy'
import pkg from '../package.json'
export const addonTest = defineValaxyAddon(options => ({
name: pkg.name,
enable: true,
options,
setup(valaxy) {
valaxy.hook('build:before', () => {
// do something before build
consola.info('[valaxy-addon-test] build:before')
})
},
}))
```
<<< @/../packages/valaxy-addon-test/node/index.ts {11-14} [valaxy-addon-test/node/index.ts]

View File

@ -16,7 +16,7 @@ Valaxy 将中英文写一个 Markdown 文件中,因此您可以很容易地进
您可以以如下方式进行中英文内容书写,而文中公共的例子则只需保留一份。
```md
```md [pages/posts/xxx.md]
::: zh-CN
**效果如下**(点击按钮切换):
:::

View File

@ -21,8 +21,10 @@ You must use [pnpm](https://pnpm.io/). Because we use its workspace.
```bash
git clone https://github.com/YunYouJun/valaxy
cd valaxy
```
```bash [pnpm]
cd valaxy
pnpm i
# esbuild watch valaxy cli & valaxy-theme-yun
# and run demo

View File

@ -15,7 +15,7 @@ codeHeightLimit: 300
Set `codeHeightLimit: 300` in Front Matter.
:::
```md
```md [pages/code-height-limit.md]
---
codeHeightLimit: 300
---
@ -29,7 +29,7 @@ codeHeightLimit: 300
Rendering result
:::
```ts
```ts [valaxy.config.ts]
import type { ThemeConfig } from 'valaxy-theme-yun'
import { defineValaxyConfig } from 'valaxy'

View File

@ -46,7 +46,7 @@ If you prefer to build them as `/xxx/index.html`, you can modify the configurati
Set it in `vite.config.ts` under the user directory as follows:
:::
```ts
```ts [vite.config.ts]
import { defineConfig } from 'vite'
export default defineConfig({

View File

@ -18,9 +18,7 @@ top: 1
2. 插入第三方/大量动态内容时,优先将其封装为组件放置于 `components`,并通过组件标签名引入,以下是一个例子。
```vue
// blog/components/BszComponent.vue
```vue [blog/components/BszComponent.vue]
<script lang="ts" setup>
import { useScriptTag } from '@vueuse/core'
@ -35,30 +33,13 @@ useScriptTag('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
</template>
```
```md
// blog/pages/posts/test-custom-component.md
```md [blog/pages/posts/test-custom-component.md]
# hello World
<BszComponent/>
```
3. 如果文章中不想使用外链图片,建议按照以下格式创建文件夹及引用图片,便于管理和迁移。
```txt
posts
├─ your-post
│ ├─ a.png
| ├─ b.png
│ └─ index.md
```
```md
// posts/your-post/index.md
![](./a.png)
![](./b.png)
```
:::
::: en
@ -73,9 +54,7 @@ blog/pages/posts/your-post.md
2. When inserting third-party/large amounts of dynamic content, priority should be given to encapsulating it as a component and placing it in `blog/components`, and introducing it through component tag names.
```vue
// blog/components/BszComponent.vue
```vue [blog/components/BszComponent.vue]
<script lang="ts" setup>
import { useScriptTag } from '@vueuse/core'
@ -90,15 +69,15 @@ useScriptTag('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
</template>
```
```md
// blog/pages/posts/test-custom-component.md
```md [blog/pages/posts/test-custom-component.md]
# hello World
<BszComponent/>
```
3. If you do not want to use external links in the post, it is recommended to create folders and reference images in the following format for easy management and migration.
:::
```txt
posts
├─ your-post
@ -107,11 +86,7 @@ posts
│ └─ index.md
```
```md
// posts/your-post/index.md
```md [posts/your-post/index.md]
![](./a.png)
![](./b.png)
```
:::

View File

@ -71,8 +71,7 @@ export default defineValaxyConfig<ThemeConfig>({
Valaxy 默认集成了 [`@vitejs/plugin-vue`](https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue) 插件,你可以通过 `vue` 配置项进行配置。
```ts
// valaxy.config.ts
```ts [valaxy.config.ts]
import { defineValaxyConfig } from 'valaxy'
export default defineValaxyConfig({

View File

@ -42,7 +42,7 @@ See [UnoCSS | Markdown](/guide/markdown#unocss).
You can write UnoCSS configurations in the `uno.config.ts` or `unocss.config.ts` files within the theme directory. Alternatively, you can configure it in the `unocss` property of the `valaxy.config.ts` file. Below is an example of the `unocss` configuration in `valaxy.config.ts`:
:::
```ts
```ts [valaxy.config.ts]
import { presetIcons } from 'unocss'
export default defineValaxyConfig<ThemeConfig>({
@ -69,7 +69,7 @@ export default defineValaxyConfig<ThemeConfig>({
Directly configuring `presets` in the `unocss` option will override the default `presets` of the theme and Valaxy. To extend these presets, use [unocssPresets](#unocsspresets).
:::
```ts
```ts [valaxy.config.ts]
import { presetIcons } from 'unocss'
export default defineValaxyConfig<ThemeConfig>({
@ -116,7 +116,7 @@ To extend the presets, use [unocssPresets](#unocsspresets).
To extend the [UnoCSS presets](https://unocss.dev/guide/presets) in Valaxy, here is a basic example:
:::
```ts
```ts [valaxy.config.ts]
import { presetIcons } from 'unocss'
export default defineValaxyConfig<ThemeConfig>({
@ -147,7 +147,7 @@ The following method is incorrect. Note the difference between the `unocssPreset
</span>
```ts
```ts [valaxy.config.ts]
import { presetIcons } from 'unocss'
export default defineValaxyConfig<ThemeConfig>({

View File

@ -135,7 +135,7 @@ import YunFooter from 'valaxy-theme-yun/components/YunFooter.vue'
`components/` 文件夹下新建 `YunFooter.vue` 以自定义页脚并显示不蒜子统计。
你可以根据你的需要自由定制它的样式。
```vue
```vue [components/YunFooter.vue]
<script lang="ts" setup>
import { useScriptTag } from '@vueuse/core'
import YunFooter from 'valaxy-theme-yun/components/YunFooter.vue'
@ -154,7 +154,7 @@ useScriptTag('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js')
`components/` 文件夹下新建 `YunPostMeta.vue` 以自定义每篇文章的信息并显示不蒜子单篇文章统计。
```vue
```vue [components/YunPostMeta.vue]
<script lang="ts" setup>
import type { Post } from 'valaxy'
import { useLayout } from 'valaxy'

View File

@ -58,7 +58,7 @@ Create a `layouts` file, and write Vue components as layouts.
You can use it in your Markdown as follows.
:::
```md
```md [pages/album.md]
---
title: Photos
layout: album
@ -89,7 +89,7 @@ Create a new `index.html` file. You can globally insert anything in between `<he
For example:
:::
```html
```html [index.html]
<head>
<link
rel="stylesheet"
@ -105,7 +105,7 @@ For example:
::: zh-CN
新建 `setup/main.ts`
```ts
```ts [setup/main.ts]
import { defineAppSetup } from 'valaxy'
export default defineAppSetup((ctx) => {
@ -158,14 +158,12 @@ Create `locales` folder.
For example (make sure that the file is not empty):
:::
```yaml
# en.yml
```yaml [locales/en.yml]
button:
about: About
```
```yaml
# zh-CN.yml
```yaml [locales/zh-CN.yml]
button:
about: 关于
```
@ -178,7 +176,7 @@ button:
You can use it like this:
:::
```vue
```vue [components/CustomButton.vue]
<script setup>
import { useI18n } from 'vue-i18n'

View File

@ -27,8 +27,7 @@ Valaxy 提供了钩子系统,以便你可以对生命周期的各个阶段进
| `build:before` | | 在构建开始之前执行。|
| `build:done` | | 在构建完成之后执行。 |
```ts
// valaxy.config.ts
```ts [valaxy.config.ts]
import { defineValaxyConfig } from 'valaxy'
export default defineValaxyConfig({
@ -42,6 +41,8 @@ export default defineValaxyConfig({
### App Client
> TODO: 仅在客户端执行。
| Hook | Arguments | Description |
| ---- | --------- | ----------- |
<!-- | `app:created` | | 在应用程序实例创建之后执行。| -->

View File

@ -63,13 +63,11 @@ For example, you can override the default font in 'styles/index.ts'.
:::
```ts
// styles/index.ts
```ts [styles/index.ts]
import './vars.scss'
```
```scss
// styles/vars.scss
```scss [styles/vars.scss]
:root {
--va-font-serif: 'Noto Serif SC', STZhongsong, STKaiti, KaiTi, Roboto, serif;
--va-font-sans: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
@ -93,13 +91,13 @@ import './vars.scss'
新建 `styles/index.ts` 文件,引入 `vars.scss`
```ts
```ts [styles/index.ts]
import './vars.scss'
```
新建 `styles/vars.scss` 文件:
```scss
```scss [styles/vars.scss]
:root {
--cursor-default: url('https://cdn.yunyoujun.cn/css/md-cursors/pointer.cur');
--cursor-pointer: url('https://cdn.yunyoujun.cn/css/md-cursors/link.cur');

View File

@ -120,7 +120,7 @@ If you are a Windows user, I strongly recommend using a Unix-like shell (such as
::: zh-CN
> 由于 `npm init` 会缓存您此前下载的版本,我更推荐您使用 `pnpm` 来创建模版。
> 安装 [pnpm](https://pnpm.io/)`npm i -g pnpm`
> [安装 pnpm](https://pnpm.io/installation)
:::
::: en

View File

@ -23,9 +23,9 @@ end: false
例如在 `components` 目录下创建一个 Vue 组件 `CustomVueDemo.vue`
<<< @/components/CustomVueDemo.vue
<<< @/components/CustomVueDemo.vue [components/CustomVueDemo.vue]
```md
```md [pages/posts/xxx.md]
---
title: 在 Markdown 中使用 Vue
---
@ -665,6 +665,45 @@ Details Content
:::
```
## 添加代码块标题 {lang="zh-CN"}
## Add Code Block Title {lang="en"}
使用以下语法:
````md
```ts [valaxy.config.ts]
import { defineValaxyConfig } from 'valaxy'
export default defineValaxyConfig({})
```
````
我们将会得到带有 `valaxy.config.ts` 标题与 TypeScript 图标的代码块:
```ts [valaxy.config.ts]
import { defineValaxyConfig } from 'valaxy'
export default defineValaxyConfig({})
```
它基于 [vitepress-plugin-group-icons](https://github.com/yuyinws/vitepress-plugin-group-icons) 实现,你可以如下自定义更多图标。
```ts [valaxy.config.ts] {4-11}
import { defineValaxyConfig } from 'valaxy'
export default defineValaxyConfig({
groupIcons: {
customIcon: {
nodejs: 'vscode-icons:file-type-node',
playwright: 'vscode-icons:file-type-playwright',
typedoc: 'vscode-icons:file-type-typedoc',
eslint: 'vscode-icons:file-type-eslint',
},
}
})
```
## KaTeX {lang="en"}
## KaTeX 语法支持 {lang="zh-CN"}

View File

@ -73,8 +73,7 @@ Encryption relies on the browser's native [Web Crypto API | MDN](https://develop
</div>
:::
```ts
// site.config.ts
```ts [site.config.ts]
import { defineSiteConfig } from 'valaxy'
export default defineSiteConfig({

View File

@ -25,7 +25,7 @@ Valaxy 内置了基于 [fuse.js](https://fusejs.io/) 的离线搜索(须预先
如果你想要使用全文搜索,可参考 [Options | fuse.js](https://www.fusejs.io/api/options.html) 进行设置。
譬如:
```ts
```ts [site.config.ts]
export default defineSiteConfig({
search: {
enable: true,
@ -67,8 +67,7 @@ Valaxy has built-in local search based on [fuse.js](https://fusejs.io/). The loc
::: zh-CN
```ts
// site.config.ts
```ts [site.config.ts]
import { defineSiteConfig } from 'valaxy'
export default defineSiteConfig({
@ -85,8 +84,7 @@ export default defineSiteConfig({
::: en
```ts {7}
// site.config.ts
```ts [site.config.ts] {7}
import { defineSiteConfig } from 'valaxy'
export default defineSiteConfig({
@ -214,8 +212,7 @@ Here is a demo:
- 安装依赖:`pnpm add vue-gtag-next`
- 新建 `setup/main.ts`:
```ts
// setup/main.ts
```ts [setup/main.ts]
import { defineAppSetup } from 'valaxy'
import { install as installGtag } from './gtag'
@ -226,7 +223,7 @@ export default defineAppSetup((ctx) => {
- 新建 `setup/gtag.ts`:
```ts
```ts [setup/gtag.ts]
import type { UserModule } from 'valaxy'
import VueGtag, { trackRouter } from 'vue-gtag-next'

View File

@ -11,9 +11,7 @@ Valaxy 兼容 Vite/Vue 插件,你可以参考以下示例进行使用。
### 使用 vite-plugin-pwa
`vite.config.ts` 中添加以下配置:
```ts
```ts [valaxy.config.ts]
import type { ThemeConfig } from 'valaxy-theme-yun'
import { defineValaxyConfig } from 'valaxy'
import { VitePWA } from 'vite-plugin-pwa'
@ -28,8 +26,7 @@ export default defineValaxyConfig<ThemeConfig>({
})
```
```ts
// setup/main.ts
```ts [setup/main.ts]
import { defineAppSetup } from 'valaxy'
export default defineAppSetup(({ router, isClient }) => {

View File

@ -134,7 +134,7 @@ export default defineValaxyConfig<ThemeConfig>({
:::
### [Hexo](https://hexo.io/)/[Hugo](https://gohugo.io/)/Jekyll
### [Hexo](https://hexo.io/)/[Hugo](https://gohugo.io/)/[Jekyll](https://jekyllrb.com/)
::: zh-CN

View File

@ -115,8 +115,7 @@ pnpm create valaxy
> <https://github.com/posva/unplugin-vue-router/issues/43#issuecomment-1433140464>
```ts
// valaxy.config.ts
```ts [valaxy.config.ts]
import { defineTheme } from 'valaxy'
export default defineTheme({
@ -183,7 +182,7 @@ The following variables are stored in global state, which you can get through `u
- `toggleDark`: 切换暗黑模式
- `toggleDarkWithTransition`: 带有过渡效果的切换暗黑模式
```vue
```vue [components/YunToggleDark.vue]
<script lang="ts" setup>
import { useAppStore } from 'valaxy'
@ -233,8 +232,7 @@ const appStore = useAppStore()
> 你也可以使用你自己的全局状态管理。参见 [状态管理](#状态管理)。
```vue
<!-- valaxy-theme-yun/App.vue -->
```vue [valaxy-theme-yun/App.vue]
<script lang="ts" setup>
import { useHead } from '@unhead/vue'
import { useAppStore } from 'valaxy'
@ -264,7 +262,7 @@ onMounted(() => {
> 你可以从 `ValaxyMain``props` 中获取 `frontmatter``pageData`
```vue
```vue [valaxy-theme-yun/components/ValaxyMain.vue]
<script lang="ts" setup>
import type { PageData, Post } from 'valaxy'
@ -342,8 +340,7 @@ Markdown 样式是主题呈现文章样式的部分,需要由主题自定义
内置了基础的 [nprogress](https://github.com/rstacruz/nprogress) 样式,你可以通过覆盖 nprogress 的默认样式进行定制:
```scss
// your-theme/styles/index.scss
```scss [your-theme/styles/index.scss]
#nprogress {
pointer-events: none;
@ -376,7 +373,7 @@ Markdown 样式是主题呈现文章样式的部分,需要由主题自定义
:::
```ts
```ts [composables/config.ts]
import { useSiteConfig, useValaxyConfig } from 'valaxy'
import { useThemeConfig } from 'valaxy-theme-custom'
@ -391,10 +388,9 @@ const themeConfig = useThemeConfig()
你可以提供一个主题的 `useThemeConfig` 函数,以便自己/用户获得带有类型约束的配置。
```ts
```ts [composables/config.ts]
// custom your theme type
import type { YunTheme } from '../types'
// composables/config.ts
import { useValaxyConfig } from 'valaxy'
/**
* getThemeConfig
@ -405,11 +401,12 @@ export function useThemeConfig<ThemeConfig = YunTheme.Config>() {
}
```
```ts
// use
```vue [components/Example.vue]
<script lang="ts" setup>
import { useThemeConfig } from 'valaxy-theme-custom'
const themeConfig = useThemeConfig()
</script>
```
#### 获取文章列表
@ -480,8 +477,7 @@ const fm = useFrontmatter()
你可以借助 [Pinia](https://pinia.vuejs.org/) Valaxy 内置)建立自己的全局状态,并在随后使用它,
```ts
// stores/app.ts
```ts [stores/app.ts]
import { acceptHMRUpdate, defineStore } from 'pinia'
// custom your theme name
@ -621,8 +617,7 @@ If your theme depends on some large ESM packages, you can pre-build these depend
:::
```ts
// valaxy.config.ts
```ts [valaxy.config.ts]
import { defineTheme } from 'valaxy'
export default defineTheme({

View File

@ -128,7 +128,7 @@ export function getHeaders(options: GetHeadersOptions = {
// the first is the old one, the last is the new one
const markdownBodyElements = document.querySelectorAll(mdBodySelector) as NodeListOf<HTMLElement>
const markdownBody = markdownBodyElements[markdownBodyElements.length - 1]
const headers = Array.from(markdownBody.querySelectorAll(`${mdBodySelector} :where(h1,h2,h3,h4,h5,h6)`))
const headers = Array.from(markdownBody?.querySelectorAll(`${mdBodySelector} :where(h1,h2,h3,h4,h5,h6)`) || [])
.filter(el => options.filter ? options.filter(el) : true)
.map((el) => {
const level = Number(el.tagName[1])

View File

@ -9,6 +9,7 @@ export const addonTest = defineValaxyAddon(options => ({
setup(valaxy) {
valaxy.hook('build:before', () => {
// do something before build
consola.info('[valaxy-addon-test] build:before')
})
},

View File

@ -2,9 +2,8 @@
import { useI18n } from 'vue-i18n'
import { useEditLink } from '../composables'
const editLink = useEditLink()
const { t } = useI18n()
const editLink = useEditLink()
</script>
<template>

View File

@ -4,10 +4,10 @@ import { computed, onMounted, ref, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const data = useData()
const { page } = useData()
const date = computed(() => new Date(data.lastUpdated!))
const isoDatetime = computed(() => date.value.toISOString())
const date = computed(() => new Date(page.value.lastUpdated!))
const isoDatetime = computed(() => date.value?.toISOString())
const datetime = ref('')
// set time on mounted hook because the locale string might be different

View File

@ -1,2 +1,4 @@
export * from './config'
export * from './edit-link'
export * from './nav'
export * from './sidebar'
export * from './useEditLink'

View File

@ -4,11 +4,11 @@ import { useThemeConfig } from '.'
export function useEditLink() {
const themeConfig = useThemeConfig()
const { page } = useData()
return computed(() => {
const { text, pattern } = themeConfig.value.editLink || {}
const { relativePath } = useData()
const url = pattern.replace(/:path/g, relativePath)
const url = pattern.replace(/:path/g, page.value.relativePath)
return { url, text }
})

View File

@ -96,8 +96,7 @@ random: true
您可以新建样式文件并引入,以覆盖默认 CSS 变量。
```ts
// styles/index.ts
```ts [styles/index.ts]
import './vars.scss'
```

View File

@ -0,0 +1,19 @@
import type { Ref } from 'vue'
import type { Router } from 'vue-router'
import type { PageData } from '../../types'
import { computed } from 'vue'
export interface ValaxyData {
page: Ref<PageData>
}
/**
* init valaxy data
*/
export function initData(router: Router): ValaxyData {
return {
page: computed(() => (router.currentRoute.value as unknown as {
data: PageData
}).data),
}
}

View File

@ -1,9 +1,10 @@
import type { PageData, PostFrontMatter } from 'valaxy/types'
import type { PostFrontMatter } from 'valaxy/types'
import type { ValaxyData } from '../app/data'
import { isClient } from '@vueuse/core'
import { computed, inject } from 'vue'
import { computed, inject } from 'vue'
import { useRoute } from 'vue-router'
import { useSiteConfig } from '../config'
import { dataSymbol, useSiteConfig } from '../config'
/**
* Get `route.meta.frontmatter` from your markdown file
@ -71,9 +72,12 @@ export function useEncryptedPhotos() {
/**
* inject pageData
*/
export function useData(): PageData {
const value = inject<PageData>('pageData', {} as any)
return value
export function useData(): ValaxyData {
const data = inject(dataSymbol, {} as any)
if (!data) {
throw new Error('Valaxy data not properly injected in app')
}
return data
}
/**

View File

@ -5,10 +5,11 @@
import type { DefaultTheme, ValaxyConfig } from 'valaxy/types'
import type { ComputedRef, InjectionKey } from 'vue'
import type { ValaxyData } from './app/data'
import { computed, inject, readonly, shallowRef } from 'vue'
// @ts-expect-error virtual module @valaxyjs/config
import valaxyConfig from '/@valaxyjs/config'
// @ts-expect-error virtual module @valaxyjs/context
import valaxyContext from '/@valaxyjs/context'
@ -28,6 +29,8 @@ interface ValaxyContext {
export const valaxyConfigSymbol: InjectionKey<ComputedRef<ValaxyConfig>> = Symbol('valaxy:config')
export const valaxyConfigRef = shallowRef<ValaxyConfig>(parse<ValaxyConfig>(valaxyConfig))
export const dataSymbol: InjectionKey<ValaxyData> = Symbol('ValaxyData')
export const valaxyContextRef = shallowRef<ValaxyContext>(parse<ValaxyContext>(valaxyContext))
valaxyConfigRef.value = parse<ValaxyConfig>(valaxyConfig)

View File

@ -1,6 +1,6 @@
import type { ViteSSGContext } from 'vite-ssg'
import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders'
import { initValaxyConfig, valaxyConfigSymbol } from 'valaxy'
import { dataSymbol, initValaxyConfig, valaxyConfigSymbol } from 'valaxy'
import { setupLayouts } from 'virtual:generated-layouts'
import { ViteSSG } from 'vite-ssg'
@ -8,11 +8,12 @@ import { routes } from 'vue-router/auto-routes'
// import App from '/@valaxyjs/App.vue'
import App from './App.vue'
import { initData } from './app/data'
import AppLink from './components/AppLink.vue'
import setupMain from './setup/main'
import { setupValaxyDevTools } from './utils/dev'
// generate user styles
import '#valaxy/styles'
@ -71,6 +72,9 @@ export const createApp = ViteSSG(
// app-level provide
const { app, router } = ctx
const data = initData(router)
app.provide(dataSymbol, data)
// Register the plugin before the router
app.use(DataLoaderPlugin, { router })

View File

@ -11,7 +11,6 @@ import type { ResolvedValaxyOptions } from '../../options'
import type { ThemeOptions } from './types'
import {
headersPlugin,
} from '@mdit-vue/plugin-headers'
import { sfcPlugin } from '@mdit-vue/plugin-sfc'
@ -28,15 +27,16 @@ import footnotePlugin from 'markdown-it-footnote'
// https://www.npmjs.com/package/markdown-it-image-figures
import imageFigures from 'markdown-it-image-figures'
import TaskLists from 'markdown-it-task-lists'
import { linkPlugin } from './plugins/link'
import { groupIconMdPlugin } from 'vitepress-plugin-group-icons'
import { linkPlugin } from './plugins/link'
import { containerPlugin } from './plugins/markdown-it/container'
import { footnoteTooltipPlugin } from './plugins/markdown-it/footnoteTooltip'
import { highlightLinePlugin } from './plugins/markdown-it/highlightLines'
import Katex from './plugins/markdown-it/katex'
import { lineNumberPlugin } from './plugins/markdown-it/lineNumbers'
import { preWrapperPlugin } from './plugins/markdown-it/preWrapper'
import { preWrapperPlugin } from './plugins/markdown-it/preWrapper'
import { snippetPlugin } from './plugins/markdown-it/snippet'
export const defaultCodeTheme = { light: 'github-light', dark: 'github-dark' } as const as ThemeOptions
@ -155,5 +155,9 @@ export async function setupMarkdownPlugins(
if (mdOptions.config)
mdOptions.config(md)
md.use(groupIconMdPlugin, {
titleBar: { includeSnippet: true },
})
return md as MarkdownIt
}

View File

@ -18,15 +18,16 @@ const encryptedKeys = ['encryptedContent', 'partiallyEncryptedContents', 'encryp
export function injectPageDataCode(pageData: PageData) {
const vueContextImports = [
`import { provide } from 'vue'`,
`import { useRoute } from 'vue-router'`,
`import { useRoute, useRouter } from 'vue-router'`,
'const { data: pageData } = usePageData()',
'const router = useRouter()',
'const route = useRoute()',
// $frontmatter contain runtime added data, will be deleted (for example, $frontmatter.partiallyEncryptedContents)
`const $frontmatter = Object.assign(route.meta.frontmatter || {}, pageData.value?.frontmatter || {})
route.meta.frontmatter = $frontmatter
router.currentRoute.value.data = pageData.value
provide('pageData', pageData.value)
provide('valaxy:frontmatter', $frontmatter)
`,
]

View File

@ -12,6 +12,7 @@ import { resolve } from 'pathe'
import Components from 'unplugin-vue-components/vite'
import Layouts from 'vite-plugin-vue-layouts'
import { groupIconVitePlugin } from 'vitepress-plugin-group-icons'
import { customElements } from '../constants'
import { createConfigPlugin } from './extendConfig'
import { createMarkdownPlugin } from './markdown'
@ -19,6 +20,7 @@ import { createFixPlugins } from './patchTransform'
import { createClientSetupPlugin } from './setupClient'
import { createUnocssPlugin } from './unocss'
import { createValaxyLoader } from './valaxy'
import { createRouterPlugin } from './vueRouter'
export async function ViteValaxyPlugins(
@ -121,6 +123,15 @@ export async function ViteValaxyPlugins(
include: roots.map(root => `${root}/locales/**`),
}),
groupIconVitePlugin({
customIcon: {
nodejs: 'vscode-icons:file-type-node',
playwright: 'vscode-icons:file-type-playwright',
typedoc: 'vscode-icons:file-type-typedoc',
eslint: 'vscode-icons:file-type-eslint',
},
...valaxyConfig.groupIcons,
}),
createFixPlugins(options),
]

View File

@ -13,9 +13,10 @@ import type Router from 'unplugin-vue-router/vite'
import type { DefaultTheme, PartialDeep, ValaxyAddon, ValaxyConfig } from 'valaxy/types'
import type { UserConfig as ViteUserConfig } from 'vite'
import type Layouts from 'vite-plugin-vue-layouts'
import type { Options as GroupIconsOptions } from 'vitepress-plugin-group-icons'
import type { createValaxyNode } from './app'
import type { ResolvedValaxyOptions } from './options'
import type { ResolvedValaxyOptions } from './options'
import type { MarkdownOptions } from './plugins/markdown/types'
export type ValaxyNodeConfig<ThemeConfig = DefaultTheme.Config> = ValaxyConfig<ThemeConfig> & ValaxyExtendConfig
@ -159,6 +160,7 @@ export interface ValaxyExtendConfig {
* @see https://github.com/btd/rollup-plugin-visualizer
*/
visualizer?: PluginVisualizerOptions
groupIcons?: GroupIconsOptions
/**
* unocss presets
* @see https://unocss.dev/guide/presets

View File

@ -39,6 +39,7 @@ export const templateStyles: VirtualModuleTemplate = {
// minus the background color override for buttons to avoid conflicts with UI frameworks
imports.unshift(`import "${await resolveImportUrl('@unocss/reset/tailwind-compat.css')}"`)
imports.push('import "uno.css"')
imports.push('import "virtual:group-icons.css"')
return imports.join('\n')
},

View File

@ -132,6 +132,7 @@
"vite-plugin-vue-layouts": "^0.11.0",
"vite-ssg": "^25.2.0",
"vite-ssg-sitemap": "^0.8.1",
"vitepress-plugin-group-icons": "^1.3.6",
"vue": "^3.5.13",
"vue-i18n": "catalog:",
"vue-router": "^4.5.0",

View File

@ -604,6 +604,9 @@ importers:
vite-ssg-sitemap:
specifier: ^0.8.1
version: 0.8.1
vitepress-plugin-group-icons:
specifier: ^1.3.6
version: 1.3.6
vue:
specifier: ^3.5.13
version: 3.5.13(typescript@5.8.2)
@ -1785,9 +1788,6 @@ packages:
'@iconify-json/ri@1.2.5':
resolution: {integrity: sha512-kWGimOXMZrlYusjBKKXYOWcKhbOHusFsmrmRGmjS7rH0BpML5A9/fy8KHZqFOwZfC4M6amObQYbh8BqO5cMC3w==}
'@iconify-json/simple-icons@1.2.26':
resolution: {integrity: sha512-NvqRuE+5h/tp4boPlnvfs0alD0CvnRE7oeG9Li5NGmZRx2/rhwlNjW/vEVTyhZcR9zqvRPAVh2GXR+PTEpzV+A==}
'@iconify-json/simple-icons@1.2.27':
resolution: {integrity: sha512-FtZwp/H7ih5rY9FPfDR+k6toOo/cuwpHWY8faNhxLs5O5uW6Q8TeqdNWfjVfgFtrs5tUUzWysjqNGL234v8EMA==}
@ -3218,8 +3218,8 @@ packages:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'}
ansis@3.16.0:
resolution: {integrity: sha512-sU7d/tfZiYrsIAXbdL/CNZld5bCkruzwT5KmqmadCJYxuLxHAOBjidxD5+iLmN/6xEfjcQq1l7OpsiCBlc4LzA==}
ansis@3.17.0:
resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==}
engines: {node: '>=14'}
any-promise@1.3.0:
@ -3635,8 +3635,8 @@ packages:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
engines: {node: '>=12.13'}
core-js-compat@3.40.0:
resolution: {integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==}
core-js-compat@3.41.0:
resolution: {integrity: sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==}
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@ -4272,8 +4272,8 @@ packages:
peerDependencies:
eslint: '>=6.0.0'
eslint-plugin-n@17.15.1:
resolution: {integrity: sha512-KFw7x02hZZkBdbZEFQduRGH4VkIH4MW97ClsbAM4Y4E6KguBJWGfWG1P4HEIpZk2bkoWf0bojpnjNAhYQP8beA==}
eslint-plugin-n@17.16.1:
resolution: {integrity: sha512-/7FVAwjUrix9P5lycnsYRIQRwFo/DZROD+ZXWLpE+/EZWLyuLvyFaRdAPYJSz+nlAdZIZp+LAzlBerQSVYUNFg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: '>=8.23.0'
@ -5256,8 +5256,8 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
keyv@5.2.3:
resolution: {integrity: sha512-AGKecUfzrowabUv0bH1RIR5Vf7w+l4S3xtQAypKaUpTdIR1EbrAcTxHCrpo9Q+IWeUlFE2palRtgIQcgm+PQJw==}
keyv@5.3.0:
resolution: {integrity: sha512-XMBcWGBqH1j04AzjzdIulcsAKr5MaGlYC/N2PLyxdwTrEqVhQnuEIP5h1TPpa5UUcPOH1yVJ+xvhYJ2QAEv94w==}
khroma@2.1.0:
resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}
@ -5893,11 +5893,11 @@ packages:
ofetch@1.4.1:
resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
ohash@1.1.4:
resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==}
ohash@1.1.5:
resolution: {integrity: sha512-AtXrG/lMFjPBWj3uhWYFwYVZQqutPYRsv6nnPLTipnC+gJuMFc+WFzf/jx+94Ebray1vxfQfEFDtpIpppOe4xQ==}
ohash@2.0.7:
resolution: {integrity: sha512-YYFiEGQbFz5xMKWt7SIbbw7Oo2BcnVf+B4udQGAFT3eIxQjJUn18/lMTu6F5gpFL3bz19hWIXOInTOjfsbKyBA==}
ohash@2.0.9:
resolution: {integrity: sha512-ljz2sybhXrRpBW9LleuJPP9uxbMKW8qxFz9lLOHW2QEel78rJ1sUgaX2cBNDt49w+JleNSkhYkVOCx6RgkKn0Q==}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
@ -5964,8 +5964,8 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
package-manager-detector@0.2.10:
resolution: {integrity: sha512-1wlNZK7HW+UE3eGCcMv3hDaYokhspuIeH6enXSnCL1eEZSVDsy/dYwo/4CczhUsrKLA1SSXB+qce8Glw5DEVtw==}
package-manager-detector@0.2.11:
resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==}
packrup@0.1.2:
resolution: {integrity: sha512-ZcKU7zrr5GlonoS9cxxrb5HVswGnyj6jQvwFBa6p5VFw7G71VAHcUKL5wyZSU/ECtPM/9gacWxy2KFQKt1gMNA==}
@ -6455,8 +6455,8 @@ packages:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
quansync@0.2.6:
resolution: {integrity: sha512-u3TuxVTuJtkTxKGk5oZ7K2/o+l0/cC6J8SOyaaSnrnroqvcVy7xBxtvBUyd+Xa8cGoCr87XmQj4NR6W+zbqH8w==}
quansync@0.2.7:
resolution: {integrity: sha512-KZDFlN9/Si3CgKHZsIfLBsrjWKFjqu9KA0zDGJEQoQzPm5HWNDEFc2mkLeYUBBOwEJtxNBSMaNLE/GlvArIEfQ==}
querystring@0.2.0:
resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==}
@ -7222,6 +7222,11 @@ packages:
peerDependencies:
typescript: '>=4.8.4'
ts-declaration-location@1.0.5:
resolution: {integrity: sha512-WqmlO9IoeYwCqJ2E9kHMcY9GZhhfLYItC3VnHDlPOrg6nNdUWS4wn4hhDZUPt60m1EvtjPIZyprTjpI992Bgzw==}
peerDependencies:
typescript: '>=4.0.0'
ts-dedent@2.2.0:
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
engines: {node: '>=6.10'}
@ -7657,6 +7662,9 @@ packages:
yaml:
optional: true
vitepress-plugin-group-icons@1.3.6:
resolution: {integrity: sha512-MzUAuMZ43f51dfBKYowW7yv/A2DxIjtN50d8Dcj31nU9RB6GuYBJ48E/Ze88U0bEn4wlnrjMXFh2j2e0rYmGug==}
vitepress@1.1.4:
resolution: {integrity: sha512-bWIzFZXpPB6NIDBuWnS20aMADH+FcFKDfQNYFvbOWij03PR29eImTceQHIzCKordjXYBhM/TjE5VKFTUJ3EheA==}
hasBin: true
@ -8137,7 +8145,7 @@ snapshots:
'@typescript-eslint/eslint-plugin': 8.25.0(@typescript-eslint/parser@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)
'@typescript-eslint/parser': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)
'@vitest/eslint-plugin': 1.1.36(@typescript-eslint/utils@8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.8)(jiti@2.4.2)(jsdom@26.0.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))
ansis: 3.16.0
ansis: 3.17.0
cac: 6.7.14
eslint: 9.21.0(jiti@2.4.2)
eslint-config-flat-gitignore: 2.1.0(eslint@9.21.0(jiti@2.4.2))
@ -8148,7 +8156,7 @@ snapshots:
eslint-plugin-import-x: 4.6.1(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)
eslint-plugin-jsdoc: 50.6.3(eslint@9.21.0(jiti@2.4.2))
eslint-plugin-jsonc: 2.19.1(eslint@9.21.0(jiti@2.4.2))
eslint-plugin-n: 17.15.1(eslint@9.21.0(jiti@2.4.2))
eslint-plugin-n: 17.16.1(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)
eslint-plugin-no-only-tests: 3.3.0
eslint-plugin-perfectionist: 4.9.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)
eslint-plugin-regexp: 2.7.0(eslint@9.21.0(jiti@2.4.2))
@ -8175,7 +8183,7 @@ snapshots:
'@antfu/install-pkg@1.0.0':
dependencies:
package-manager-detector: 0.2.10
package-manager-detector: 0.2.11
tinyexec: 0.3.2
'@antfu/utils@0.7.10': {}
@ -8865,10 +8873,6 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/simple-icons@1.2.26':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/simple-icons@1.2.27':
dependencies:
'@iconify/types': 2.0.0
@ -9139,7 +9143,7 @@ snapshots:
klona: 2.0.6
knitwork: 1.2.0
mlly: 1.7.4
ohash: 1.1.4
ohash: 1.1.5
pathe: 2.0.3
pkg-types: 1.3.1
scule: 1.3.0
@ -9969,7 +9973,7 @@ snapshots:
'@unhead/schema': 1.11.20
'@unhead/shared': 1.11.20
defu: 6.1.4
ohash: 1.1.4
ohash: 1.1.5
ufo: 1.5.4
unhead: 1.11.20
optionalDependencies:
@ -10610,7 +10614,7 @@ snapshots:
ansi-styles@6.2.1: {}
ansis@3.16.0: {}
ansis@3.17.0: {}
any-promise@1.3.0: {}
@ -10801,7 +10805,7 @@ snapshots:
escalade: 3.2.0
js-yaml: 4.1.0
jsonc-parser: 3.3.1
package-manager-detector: 0.2.10
package-manager-detector: 0.2.11
prompts: 2.4.2
semver: 7.7.1
tinyexec: 0.3.2
@ -10829,7 +10833,7 @@ snapshots:
giget: 1.2.5
jiti: 2.4.2
mlly: 1.7.4
ohash: 2.0.7
ohash: 2.0.9
pathe: 2.0.3
perfect-debounce: 1.0.0
pkg-types: 1.3.1
@ -10840,7 +10844,7 @@ snapshots:
cacheable@1.8.8:
dependencies:
hookified: 1.7.1
keyv: 5.2.3
keyv: 5.3.0
call-bind-apply-helpers@1.0.2:
dependencies:
@ -11061,7 +11065,7 @@ snapshots:
dependencies:
is-what: 4.1.16
core-js-compat@3.40.0:
core-js-compat@3.41.0:
dependencies:
browserslist: 4.24.4
@ -11859,9 +11863,10 @@ snapshots:
transitivePeerDependencies:
- '@eslint/json'
eslint-plugin-n@17.15.1(eslint@9.21.0(jiti@2.4.2)):
eslint-plugin-n@17.16.1(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2):
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2))
'@typescript-eslint/utils': 8.25.0(eslint@9.21.0(jiti@2.4.2))(typescript@5.8.2)
enhanced-resolve: 5.18.1
eslint: 9.21.0(jiti@2.4.2)
eslint-plugin-es-x: 7.8.0(eslint@9.21.0(jiti@2.4.2))
@ -11870,6 +11875,10 @@ snapshots:
ignore: 5.3.2
minimatch: 9.0.5
semver: 7.7.1
ts-declaration-location: 1.0.5(typescript@5.8.2)
transitivePeerDependencies:
- supports-color
- typescript
eslint-plugin-no-only-tests@3.3.0: {}
@ -11910,7 +11919,7 @@ snapshots:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.21.0(jiti@2.4.2))
ci-info: 4.1.0
clean-regexp: 1.0.0
core-js-compat: 3.40.0
core-js-compat: 3.41.0
eslint: 9.21.0(jiti@2.4.2)
esquery: 1.6.0
globals: 15.15.0
@ -12994,7 +13003,7 @@ snapshots:
dependencies:
json-buffer: 3.0.1
keyv@5.2.3:
keyv@5.3.0:
dependencies:
'@keyv/serialize': 1.0.3
@ -13084,7 +13093,7 @@ snapshots:
dependencies:
mlly: 1.7.4
pkg-types: 1.3.1
quansync: 0.2.6
quansync: 0.2.7
locate-path@5.0.0:
dependencies:
@ -13801,9 +13810,9 @@ snapshots:
node-fetch-native: 1.6.6
ufo: 1.5.4
ohash@1.1.4: {}
ohash@1.1.5: {}
ohash@2.0.7: {}
ohash@2.0.9: {}
on-finished@2.4.1:
dependencies:
@ -13891,9 +13900,9 @@ snapshots:
package-json-from-dist@1.0.1: {}
package-manager-detector@0.2.10:
package-manager-detector@0.2.11:
dependencies:
quansync: 0.2.6
quansync: 0.2.7
packrup@0.1.2: {}
@ -14326,7 +14335,7 @@ snapshots:
dependencies:
side-channel: 1.1.0
quansync@0.2.6: {}
quansync@0.2.7: {}
querystring@0.2.0: {}
@ -15248,6 +15257,11 @@ snapshots:
dependencies:
typescript: 5.8.2
ts-declaration-location@1.0.5(typescript@5.8.2):
dependencies:
minimatch: 10.0.1
typescript: 5.8.2
ts-dedent@2.2.0: {}
ts-interface-checker@0.1.13: {}
@ -15886,7 +15900,7 @@ snapshots:
dependencies:
'@unhead/dom': 1.11.20
'@unhead/vue': 1.11.20(vue@3.5.13(typescript@5.8.2))
ansis: 3.16.0
ansis: 3.17.0
cac: 6.7.14
html-minifier-terser: 7.2.0
html5parser: 2.0.2
@ -15927,6 +15941,14 @@ snapshots:
tsx: 4.19.3
yaml: 2.7.0
vitepress-plugin-group-icons@1.3.6:
dependencies:
'@iconify-json/logos': 1.2.4
'@iconify-json/vscode-icons': 1.2.16
'@iconify/utils': 2.3.0
transitivePeerDependencies:
- supports-color
vitepress@1.1.4(@types/node@22.13.8)(axios@1.8.1)(nprogress@0.2.0)(postcss@8.5.3)(qrcode@1.5.4)(sass@1.85.1)(terser@5.39.0)(typescript@5.8.2):
dependencies:
'@docsearch/css': 3.9.0
@ -15978,7 +16000,7 @@ snapshots:
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.20.3)(search-insights@2.17.3)
'@iconify-json/simple-icons': 1.2.26
'@iconify-json/simple-icons': 1.2.27
'@shikijs/core': 2.5.0
'@shikijs/transformers': 2.5.0
'@shikijs/types': 2.5.0