mirror of https://github.com/YunYouJun/valaxy
feat: add import code & hightlint line & code group
This commit is contained in:
parent
56ad1dc824
commit
0862c12d8c
|
@ -1,50 +0,0 @@
|
|||
---
|
||||
title_zh-CN: 容器
|
||||
title: Container
|
||||
categories:
|
||||
- guide
|
||||
end: false
|
||||
---
|
||||
|
||||
::: zh-CN
|
||||
|
||||
通过对 `markdownIt` 进行配置,你可以自由设置自定义块区域的文字以及图标及图标的颜色。
|
||||
|
||||
:::
|
||||
|
||||
::: en
|
||||
|
||||
By configuring `markdownIt`, you can set the text and icon (and its color) for
|
||||
custom block.
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
tip
|
||||
|
||||
:::
|
||||
|
||||
::: warning
|
||||
|
||||
warning
|
||||
|
||||
:::
|
||||
|
||||
::: danger
|
||||
|
||||
danger
|
||||
|
||||
:::
|
||||
|
||||
::: info
|
||||
|
||||
info
|
||||
|
||||
:::
|
||||
|
||||
::: details Click to expand
|
||||
|
||||
details
|
||||
|
||||
:::
|
|
@ -0,0 +1,419 @@
|
|||
---
|
||||
title_zh-CN: Markdown 扩展
|
||||
title: Markdown Extensions
|
||||
categories:
|
||||
- guide
|
||||
end: false
|
||||
---
|
||||
|
||||
::: info
|
||||
与 `Hexo` 不同,`Valaxy` 在框架层面实现了一些 Markdown 扩展(如 Container、数学公式)等,而无需主题开发者再次实现。
|
||||
|
||||
这与 `VitePress` 许多功能类似,`Valaxy` 从 `VitePress` 中借鉴了许多,并复用了 [mdit-vue](https://github.com/mdit-vue/mdit-vue) 的插件。
|
||||
但也存在一些不同之处,此前当 `Valaxy` 实现数学公式时 `VitePress` 尚未支持,目前 `Valaxy` 默认的数学公式基于 KaTeX,而 `VitePress` 基于 MathJax。
|
||||
|
||||
> KaTeX 相对于 MathJax 有更快的渲染速度,MathJax 则拥有更多的功能。
|
||||
|
||||
当然,你仍然可以在 Valaxy 中通过添加 MarkdownIt 插件来实现更多功能。
|
||||
:::
|
||||
|
||||
## Emoji :tada:
|
||||
|
||||
**Input**
|
||||
|
||||
```
|
||||
:tada: :100:
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
:tada: :100:
|
||||
|
||||
A [list of all emojis](https://github.com/markdown-it/markdown-it-emoji/blob/master/lib/data/full.json) is available.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
**Input**
|
||||
|
||||
```
|
||||
[[toc]]
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
[[toc]]
|
||||
|
||||
Rendering of the TOC can be configured using the `markdown.toc` option.
|
||||
|
||||
## 代码行高亮
|
||||
|
||||
**Input**
|
||||
|
||||
````
|
||||
```js{4}
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
msg: 'Highlighted!'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
**Output**
|
||||
|
||||
```js{4}
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
msg: 'Highlighted!'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Input**
|
||||
|
||||
````md
|
||||
```ts {1}
|
||||
// line-numbers is disabled by default
|
||||
const line2 = 'This is line 2'
|
||||
const line3 = 'This is line 3'
|
||||
```
|
||||
|
||||
```ts:line-numbers {1}
|
||||
// line-numbers is enabled
|
||||
const line2 = 'This is line 2'
|
||||
const line3 = 'This is line 3'
|
||||
```
|
||||
|
||||
```ts:line-numbers=2 {1}
|
||||
// line-numbers is enabled and start from 2
|
||||
const line3 = 'This is line 3'
|
||||
const line4 = 'This is line 4'
|
||||
```
|
||||
````
|
||||
|
||||
**Output**
|
||||
|
||||
```ts {1}
|
||||
// line-numbers is disabled by default
|
||||
const line2 = 'This is line 2'
|
||||
const line3 = 'This is line 3'
|
||||
```
|
||||
|
||||
```ts:line-numbers {1}
|
||||
// line-numbers is enabled
|
||||
const line2 = 'This is line 2'
|
||||
const line3 = 'This is line 3'
|
||||
```
|
||||
|
||||
```ts:line-numbers=2 {1}
|
||||
// line-numbers is enabled and start from 2
|
||||
const line3 = 'This is line 3'
|
||||
const line4 = 'This is line 4'
|
||||
````
|
||||
|
||||
## Colored Diffs in Code Blocks
|
||||
|
||||
Adding the `// [!code --]` or `// [!code ++]` comments on a line will create a diff of that line, while keeping the colors of the codeblock.
|
||||
|
||||
**Input**
|
||||
|
||||
Note that only one space is required after `!code`, here are two to prevent processing.
|
||||
|
||||
````
|
||||
```js
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
msg: 'Removed' // [!code --]
|
||||
msg: 'Added' // [!code ++]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
**Output**
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
msg: 'Removed', // [!code --]
|
||||
msg: 'Added', // [!code ++]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors and Warnings in Code Blocks
|
||||
|
||||
Adding the `// [!code warning]` or `// [!code error]` comments on a line will color it accordingly.
|
||||
|
||||
**Input**
|
||||
|
||||
Note that only one space is required after `!code`, here are two to prevent processing.
|
||||
|
||||
````
|
||||
```js
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
msg: 'Error', // [!code error]
|
||||
msg: 'Warning' // [!code warning]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
**Output**
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
msg: 'Error', // [!code error]
|
||||
msg: 'Warning' // [!code warning]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Import Code Snippets
|
||||
|
||||
You can import code snippets from existing files via following syntax:
|
||||
|
||||
```md
|
||||
<<< @/filepath
|
||||
```
|
||||
|
||||
It also supports [line highlighting](#line-highlighting-in-code-blocks):
|
||||
|
||||
```md
|
||||
<<< @/filepath{highlightLines}
|
||||
```
|
||||
|
||||
**Input**
|
||||
|
||||
```md
|
||||
<<< @/snippets/snippet.js{2}
|
||||
```
|
||||
|
||||
**Code file**
|
||||
|
||||
<<< @/snippets/snippet.js
|
||||
|
||||
**Output**
|
||||
|
||||
<<< @/snippets/snippet.js
|
||||
|
||||
::: tip
|
||||
The value of `@` corresponds to the source root. By default it's the blog root, unless `srcDir` is configured. Alternatively, you can also import from relative paths:
|
||||
|
||||
```md
|
||||
<<< ../snippets/snippet.js
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
You can also use a [VS Code region](https://code.visualstudio.com/docs/editor/codebasics#_folding) to only include the corresponding part of the code file. You can provide a custom region name after a `#` following the filepath:
|
||||
|
||||
**Input**
|
||||
|
||||
```md
|
||||
<<< @/snippets/snippet-with-region.js#snippet{1}
|
||||
```
|
||||
|
||||
**Code file**
|
||||
|
||||
<<< @/snippets/snippet-with-region.js
|
||||
|
||||
**Output**
|
||||
|
||||
<<< @/snippets/snippet-with-region.js#snippet{1}
|
||||
|
||||
You can also specify the language inside the braces (`{}`) like this:
|
||||
|
||||
```md
|
||||
<<< @/snippets/snippet.cs{c#}
|
||||
|
||||
<!-- with line highlighting: -->
|
||||
|
||||
<<< @/snippets/snippet.cs{1,2,4-6 c#}
|
||||
|
||||
<!-- with line numbers: -->
|
||||
|
||||
<<< @/snippets/snippet.cs{1,2,4-6 c#:line-numbers}
|
||||
```
|
||||
|
||||
This is helpful if source language cannot be inferred from your file extension.
|
||||
|
||||
|
||||
## Code Groups
|
||||
|
||||
You can group multiple code blocks like this:
|
||||
|
||||
**Input**
|
||||
|
||||
````md
|
||||
::: code-group
|
||||
|
||||
```js [config.js]
|
||||
/**
|
||||
* @type {import('vitepress').UserConfig}
|
||||
*/
|
||||
const config = {
|
||||
// ...
|
||||
}
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
```ts [config.ts]
|
||||
import type { UserConfig } from 'vitepress'
|
||||
|
||||
const config: UserConfig = {
|
||||
// ...
|
||||
}
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
:::
|
||||
````
|
||||
|
||||
**Output**
|
||||
|
||||
::: code-group
|
||||
|
||||
```js [config.js]
|
||||
/**
|
||||
* @type {import('vitepress').UserConfig}
|
||||
*/
|
||||
const config = {
|
||||
// ...
|
||||
}
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
```ts [config.ts]
|
||||
import type { UserConfig } from 'vitepress'
|
||||
|
||||
const config: UserConfig = {
|
||||
// ...
|
||||
}
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
You can also [import snippets](#import-code-snippets) in code groups:
|
||||
|
||||
**Input**
|
||||
|
||||
```md
|
||||
::: code-group
|
||||
|
||||
<!-- filename is used as title by default -->
|
||||
|
||||
<<< @/snippets/snippet.js
|
||||
|
||||
<!-- you can provide a custom one too -->
|
||||
|
||||
<<< @/snippets/snippet-with-region.js#snippet{1,2 ts:line-numbers} [snippet with region]
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
::: code-group
|
||||
|
||||
<<< @/snippets/snippet.js
|
||||
|
||||
<<< @/snippets/snippet-with-region.js#snippet{1,2 ts:line-numbers} [snippet with region]
|
||||
|
||||
:::
|
||||
|
||||
|
||||
## Container
|
||||
|
||||
::: zh-CN
|
||||
|
||||
通过对 `markdownIt` 进行配置,你可以自由设置自定义块区域的文字以及图标及图标的颜色。
|
||||
|
||||
:::
|
||||
|
||||
::: en
|
||||
|
||||
By configuring `markdownIt`, you can set the text and icon (and its color) for
|
||||
custom block.
|
||||
|
||||
:::
|
||||
|
||||
::: tip
|
||||
|
||||
tip
|
||||
|
||||
:::
|
||||
|
||||
::: warning
|
||||
|
||||
warning
|
||||
|
||||
:::
|
||||
|
||||
::: danger
|
||||
|
||||
danger
|
||||
|
||||
:::
|
||||
|
||||
::: info
|
||||
|
||||
info
|
||||
|
||||
:::
|
||||
|
||||
::: details Click to expand
|
||||
|
||||
details
|
||||
|
||||
:::
|
||||
|
||||
## KaTeX
|
||||
|
||||
**Input**
|
||||
|
||||
```md
|
||||
When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are
|
||||
$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$
|
||||
|
||||
**Maxwell's equations:**
|
||||
|
||||
| equation | description |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero |
|
||||
| $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ |
|
||||
| $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ |
|
||||
```
|
||||
|
||||
**Output**
|
||||
|
||||
When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are
|
||||
$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$
|
||||
|
||||
**Maxwell's equations:**
|
||||
|
||||
| equation | description |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero |
|
||||
| $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ |
|
||||
| $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | _wha?_ |
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
[0;90m┌[0m [0;36;1mWelcome to VitePress![0m[0m
|
||||
[0;90m│[0m[0m
|
||||
[0;32m◇[0m Where should VitePress initialize the config?[0m
|
||||
[0;90m│[0m [0;2m./docs[0m[0m
|
||||
[0;90m│[0m[0m
|
||||
[0;32m◇[0m Site title:[0m
|
||||
[0;90m│[0m [0;2mMy Awesome Project[0m[0m
|
||||
[0;90m│[0m[0m
|
||||
[0;32m◇[0m Site description:[0m
|
||||
[0;90m│[0m [0;2mA VitePress Site[0m[0m
|
||||
[0;90m│[0m[0m
|
||||
[0;36m◆[0m Theme:[0m
|
||||
[0;36m│[0m [0;32m●[0m Default Theme [0;2m(Out of the box, good-looking docs)[0m[0m
|
||||
[0;36m│[0m [0;2m○[0m [0;2mDefault Theme + Customization[0m[0m
|
||||
[0;36m│[0m [0;2m○[0m [0;2mCustom Theme[0m[0m
|
||||
[0;36m└[0m
|
|
@ -0,0 +1,7 @@
|
|||
// #region snippet
|
||||
function foo() {
|
||||
// ..
|
||||
}
|
||||
// #endregion snippet
|
||||
|
||||
export default foo
|
|
@ -0,0 +1,3 @@
|
|||
export default function () {
|
||||
// ..
|
||||
}
|
|
@ -6,7 +6,7 @@ import 'vitepress/dist/client/theme-default/styles/vars.css'
|
|||
// import 'vitepress/dist/client/theme-default/styles/base.css'
|
||||
// import 'vitepress/dist/client/theme-default/styles/utils.css'
|
||||
// import 'vitepress/dist/client/theme-default/styles/components/vp-code.css'
|
||||
// import 'vitepress/dist/client/theme-default/styles/components/vp-code-group.css'
|
||||
import 'vitepress/dist/client/theme-default/styles/components/vp-code-group.css'
|
||||
import 'vitepress/dist/client/theme-default/styles/components/vp-doc.css'
|
||||
import 'vitepress/dist/client/theme-default/styles/components/custom-block.css'
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import { useBodyScrollLock, useSiteConfig } from 'valaxy'
|
|||
import { useRouter } from 'vue-router'
|
||||
import type { FuseListItem } from 'valaxy/types'
|
||||
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
}>()
|
||||
|
@ -69,6 +71,10 @@ function jumpToLink(link: string) {
|
|||
router.push(link)
|
||||
emit('close')
|
||||
}
|
||||
|
||||
onClickOutside(searchInputRef, () => {
|
||||
// emit('close')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -79,7 +85,9 @@ function jumpToLink(link: string) {
|
|||
>
|
||||
<div
|
||||
v-if="open" ref="searchContainer"
|
||||
class="yun-popup yun-search-popup yun-fuse-search flex-center" flex="col"
|
||||
class="yun-popup yun-search-popup yun-fuse-search flex-center pointer-events-auto" flex="col"
|
||||
justify="start"
|
||||
pt-12
|
||||
>
|
||||
<div class="yun-search-input-container flex-center" w="full">
|
||||
<input ref="searchInputRef" v-model="input" class="yun-search-input" :placeholder="t('search.placeholder')">
|
||||
|
@ -87,27 +95,25 @@ function jumpToLink(link: string) {
|
|||
<div v-if="input" class="flex-center" w="full" py="4">
|
||||
{{ t('search.hits', results.length || 0) }}
|
||||
</div>
|
||||
<div overflow="auto" flex="~ 1" w="full">
|
||||
<div v-if="results.length > 0" overflow="auto" flex="~" w="full">
|
||||
<div class="yun-fuse-result-container" flex="~ col" w="full">
|
||||
<template v-if="results.length > 0">
|
||||
<div
|
||||
v-for="result in results" :key="result.item.title"
|
||||
:to="result.item.link"
|
||||
class="yun-fuse-result-item text-$va-c-text hover:(text-$va-c-bg bg-$va-c-text-dark bg-opacity-100)"
|
||||
flex="~ col" pb-2
|
||||
@click="jumpToLink(result.item.link)"
|
||||
>
|
||||
<h3 font="serif black">
|
||||
{{ result.item.title }}
|
||||
</h3>
|
||||
<span text="sm" opacity="80">
|
||||
{{ result.item.excerpt }}
|
||||
</span>
|
||||
<span text-xs opacity-50 mt="1">
|
||||
Score Index: {{ result.refIndex }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-for="result in results" :key="result.item.title"
|
||||
:to="result.item.link"
|
||||
class="yun-fuse-result-item text-$va-c-text hover:(text-$va-c-bg bg-$va-c-text-dark bg-opacity-100)"
|
||||
flex="~ col" pb-2
|
||||
@click="jumpToLink(result.item.link)"
|
||||
>
|
||||
<h3 font="serif black">
|
||||
{{ result.item.title }}
|
||||
</h3>
|
||||
<span text="sm" opacity="80">
|
||||
{{ result.item.excerpt }}
|
||||
</span>
|
||||
<span text-xs opacity-50 mt="1">
|
||||
Score Index: {{ result.refIndex }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -126,7 +132,6 @@ function jumpToLink(link: string) {
|
|||
-webkit-backdrop-filter: blur(30px);
|
||||
|
||||
text-align: center;
|
||||
padding-top: 3.5rem;
|
||||
margin: 0;
|
||||
z-index: var(--yun-z-search-popup);
|
||||
transition: 0.6s;
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
open?: boolean
|
||||
}>(), {
|
||||
open: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'open'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
function onClick() {
|
||||
if (props.open)
|
||||
emit('close')
|
||||
else
|
||||
emit('open')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="yun-search-btn popup-trigger yun-icon-btn" :title="t('menu.search')">
|
||||
<button class="yun-search-btn popup-trigger yun-icon-btn" :title="t('menu.search')" @click="onClick">
|
||||
<div v-if="!open" i-ri-search-line />
|
||||
<div v-else text="!2xl" i-ri-close-line />
|
||||
</button>
|
||||
|
|
|
@ -21,14 +21,22 @@ watch(Meta_K, (val) => {
|
|||
togglePopup()
|
||||
})
|
||||
|
||||
function openSearch() {
|
||||
open.value = true
|
||||
}
|
||||
|
||||
function closeSearch() {
|
||||
open.value = false
|
||||
}
|
||||
|
||||
const YunAlgoliaSearch = isAlgolia.value
|
||||
? defineAsyncComponent(() => import('./third/YunAlgoliaSearch.vue'))
|
||||
: () => null
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YunSearchBtn :open="open && !isAlgolia" @click="togglePopup" />
|
||||
<YunSearchBtn :open="open && !isAlgolia" @open="openSearch" @close="closeSearch" />
|
||||
|
||||
<YunAlgoliaSearch v-if="isAlgolia" :open="open" @close="open = false" />
|
||||
<YunFuseSearch v-else-if="isFuse" :open="open" @close="open = false" />
|
||||
<YunAlgoliaSearch v-if="isAlgolia" :open="open" @close="closeSearch" />
|
||||
<YunFuseSearch v-else-if="isFuse" :open="open" @close="closeSearch" />
|
||||
</template>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'
|
|||
import { onContentUpdated, runContentUpdated, useAplayer, useCodePen, useCopyCode, useMediumZoom, wrapTable } from 'valaxy'
|
||||
import type { Post } from 'valaxy'
|
||||
import { useVanillaLazyLoad } from '../composables/features/vanilla-lazyload'
|
||||
import { useCodeGroups } from '../composables/codeGroups'
|
||||
|
||||
const props = defineProps<{
|
||||
frontmatter: Post
|
||||
|
@ -29,6 +30,7 @@ if (props.frontmatter.codepen)
|
|||
useCodePen()
|
||||
|
||||
useCopyCode()
|
||||
useCodeGroups()
|
||||
|
||||
if (typeof props.frontmatter.medium_zoom === 'undefined' || props.frontmatter.medium_zoom)
|
||||
useMediumZoom()
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import { onContentUpdated } from 'valaxy'
|
||||
import { isClient } from '@vueuse/core'
|
||||
|
||||
export function useCodeGroups() {
|
||||
if (import.meta.env.DEV) {
|
||||
onContentUpdated(() => {
|
||||
document.querySelectorAll('.vp-code-group > .blocks').forEach((el) => {
|
||||
Array.from(el.children).forEach((child) => {
|
||||
child.classList.remove('active')
|
||||
})
|
||||
el.children[0].classList.add('active')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (isClient) {
|
||||
window.addEventListener('click', (e) => {
|
||||
const el = e.target as HTMLInputElement
|
||||
|
||||
if (el.matches('.vp-code-group input')) {
|
||||
// input <- .tabs <- .vp-code-group
|
||||
const group = el.parentElement?.parentElement
|
||||
if (!group)
|
||||
return
|
||||
|
||||
const i = Array.from(group.querySelectorAll('input')).indexOf(el)
|
||||
if (i < 0)
|
||||
return
|
||||
|
||||
const blocks = group.querySelector('.blocks')
|
||||
if (!blocks)
|
||||
return
|
||||
|
||||
const current = Array.from(blocks.children).find(child =>
|
||||
child.classList.contains('active'),
|
||||
)
|
||||
if (!current)
|
||||
return
|
||||
|
||||
const next = blocks.children[i]
|
||||
if (!next || current === next)
|
||||
return
|
||||
|
||||
current.classList.remove('active')
|
||||
next.classList.add('active')
|
||||
|
||||
const label = group?.querySelector(`label[for="${el.id}"]`)
|
||||
label?.scrollIntoView({ block: 'nearest' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -68,7 +68,7 @@ html:not(.dark) .vp-code-dark {
|
|||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
padding: 1rem 0;
|
||||
padding: 20px 0;
|
||||
background: transparent;
|
||||
overflow-x: auto;
|
||||
|
||||
|
@ -87,7 +87,7 @@ html:not(.dark) .vp-code-dark {
|
|||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 16px 0;
|
||||
padding: 20px 0;
|
||||
width: 100%;
|
||||
line-height: var(--va-code-line-height);
|
||||
font-family: var(--va-font-mono);
|
||||
|
|
|
@ -37,4 +37,5 @@ export interface MarkdownEnv {
|
|||
relativePath: string
|
||||
cleanUrls: CleanUrlsMode
|
||||
links?: string[]
|
||||
realPath?: string
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ import { highlightLinePlugin } from './plugins/markdown-it/highlightLines'
|
|||
|
||||
import { linkPlugin } from './plugins/link'
|
||||
import { preWrapperPlugin } from './plugins/markdown-it/preWrapper'
|
||||
|
||||
// import { lineNumberPlugin } from "./plugins/lineNumbers";
|
||||
import { lineNumberPlugin } from './plugins/markdown-it/lineNumbers'
|
||||
import { snippetPlugin } from './plugins/markdown-it/snippet'
|
||||
|
||||
export * from './env'
|
||||
|
||||
|
@ -57,7 +57,11 @@ export async function setupMarkdownPlugins(
|
|||
// custom plugins
|
||||
md.use(highlightLinePlugin)
|
||||
.use(preWrapperPlugin, { theme })
|
||||
.use(containerPlugin, mdOptions.blocks)
|
||||
.use(snippetPlugin, options?.userRoot)
|
||||
.use(containerPlugin, {
|
||||
...mdOptions.blocks,
|
||||
theme,
|
||||
})
|
||||
.use(cssI18nContainer, {
|
||||
languages: ['zh-CN', 'en'],
|
||||
})
|
||||
|
@ -76,8 +80,6 @@ export async function setupMarkdownPlugins(
|
|||
if (!mdOptions.attrs?.disable)
|
||||
md.use(attrsPlugin, mdOptions.attrs)
|
||||
|
||||
// .use(lineNumberPlugin)
|
||||
|
||||
md.use(Katex, mdOptions.katex)
|
||||
md.use(emojiPlugin)
|
||||
|
||||
|
@ -142,15 +144,14 @@ export async function setupMarkdownPlugins(
|
|||
slugify,
|
||||
...mdOptions.toc,
|
||||
} as TocPluginOptions)
|
||||
// ref vitepress
|
||||
md.use(lineNumberPlugin, mdOptions.lineNumbers)
|
||||
|
||||
md.use(TaskLists)
|
||||
|
||||
if (mdOptions.config)
|
||||
mdOptions.config(md)
|
||||
|
||||
// if (options.lineNumbers)
|
||||
// md.use(lineNumberPlugin)
|
||||
|
||||
return md as MarkdownRenderer
|
||||
}
|
||||
|
||||
|
|
|
@ -114,10 +114,13 @@ export async function createMarkdownToVueRenderFn(
|
|||
file: string,
|
||||
publicDir: string,
|
||||
): Promise<MarkdownCompileResult> => {
|
||||
const relativePath = slash(path.relative(srcDir, file))
|
||||
const fileOrig = file
|
||||
const dir = path.dirname(file)
|
||||
const relativePath = slash(path.relative(srcDir, file))
|
||||
|
||||
const cached = cache.get(src)
|
||||
const cacheKey = JSON.stringify({ src, file: fileOrig })
|
||||
|
||||
const cached = cache.get(cacheKey)
|
||||
if (cached) {
|
||||
debug(`[cache hit] ${relativePath}`)
|
||||
return cached
|
||||
|
@ -144,6 +147,7 @@ export async function createMarkdownToVueRenderFn(
|
|||
path: file,
|
||||
relativePath,
|
||||
cleanUrls,
|
||||
realPath: fileOrig,
|
||||
}
|
||||
|
||||
const html = md.render(src, env)
|
||||
|
|
|
@ -89,17 +89,22 @@ export async function highlight(
|
|||
const styleRE = /<pre[^>]*(style=".*?")/
|
||||
const preRE = /^<pre(.*?)>/
|
||||
const vueRE = /-vue$/
|
||||
const lineNoRE = /:(no-)?line-numbers$/
|
||||
const lineNoStartRE = /=(\d*)/
|
||||
const lineNoRE = /:(no-)?line-numbers(=\d*)?$/
|
||||
const mustacheRE = /\{\{.*?\}\}/g
|
||||
|
||||
return (str: string, lang: string, attrs: string) => {
|
||||
const vPre = vueRE.test(lang) ? '' : 'v-pre'
|
||||
lang
|
||||
= lang.replace(lineNoRE, '').replace(vueRE, '').toLowerCase() || defaultLang
|
||||
= lang
|
||||
.replace(lineNoStartRE, '')
|
||||
.replace(lineNoRE, '')
|
||||
.replace(vueRE, '')
|
||||
.toLowerCase() || defaultLang
|
||||
|
||||
if (lang) {
|
||||
const langLoaded = highlighter.getLoadedLanguages().includes(lang as any)
|
||||
if (!langLoaded && lang !== 'ansi' && lang !== 'txt') {
|
||||
if (!langLoaded && !['ansi', 'plaintext', 'txt', 'text'].includes(lang)) {
|
||||
logger.warn(
|
||||
c.yellow(
|
||||
`\nThe language '${lang}' is not loaded, falling back to '${
|
||||
|
@ -150,7 +155,7 @@ export async function highlight(
|
|||
)
|
||||
}
|
||||
|
||||
str = removeMustache(str).trim()
|
||||
str = removeMustache(str).trimEnd()
|
||||
|
||||
const codeToHtml = (theme: IThemeRegistration) => {
|
||||
const res
|
||||
|
|
|
@ -5,6 +5,15 @@ import type MarkdownIt from 'markdown-it'
|
|||
import type Token from 'markdown-it/lib/token'
|
||||
import container from 'markdown-it-container'
|
||||
|
||||
import { nanoid } from 'nanoid'
|
||||
import type {
|
||||
Options,
|
||||
} from './preWrapper'
|
||||
import {
|
||||
extractTitle,
|
||||
getAdaptiveThemeMarker,
|
||||
} from './preWrapper'
|
||||
|
||||
type ContainerArgs = [
|
||||
typeof container,
|
||||
string,
|
||||
|
@ -67,7 +76,9 @@ export interface Blocks {
|
|||
details?: BlockItem
|
||||
}
|
||||
|
||||
const defaultBlocksOptions: Blocks = {
|
||||
export type ContainerOptions = Blocks & Partial<Options>
|
||||
|
||||
const defaultBlocksOptions: ContainerOptions = {
|
||||
tip: {
|
||||
text: 'TIP',
|
||||
langs: {
|
||||
|
@ -100,7 +111,7 @@ const defaultBlocksOptions: Blocks = {
|
|||
},
|
||||
}
|
||||
|
||||
export function containerPlugin(md: MarkdownIt, options: Blocks = {}) {
|
||||
export function containerPlugin(md: MarkdownIt, options: ContainerOptions = {}) {
|
||||
Object.keys(defaultBlocksOptions).forEach((optionKey) => {
|
||||
const option: BlockItem = {
|
||||
...defaultBlocksOptions[optionKey as keyof Blocks],
|
||||
|
@ -109,6 +120,7 @@ export function containerPlugin(md: MarkdownIt, options: Blocks = {}) {
|
|||
|
||||
md.use(...createContainer(optionKey, option))
|
||||
})
|
||||
md.use(...createCodeGroup(options))
|
||||
|
||||
// explicitly escape Vue syntax
|
||||
md.use(container, 'v-pre', {
|
||||
|
@ -122,3 +134,56 @@ export function containerPlugin(md: MarkdownIt, options: Blocks = {}) {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
function createCodeGroup(options: ContainerOptions): ContainerArgs {
|
||||
return [
|
||||
container,
|
||||
'code-group',
|
||||
{
|
||||
render(tokens, idx) {
|
||||
if (tokens[idx].nesting === 1) {
|
||||
const name = nanoid(5)
|
||||
let tabs = ''
|
||||
let checked = 'checked="checked"'
|
||||
|
||||
for (
|
||||
let i = idx + 1;
|
||||
!(
|
||||
tokens[i].nesting === -1
|
||||
&& tokens[i].type === 'container_code-group_close'
|
||||
);
|
||||
++i
|
||||
) {
|
||||
const isHtml = tokens[i].type === 'html_block'
|
||||
|
||||
if (
|
||||
(tokens[i].type === 'fence' && tokens[i].tag === 'code')
|
||||
|| isHtml
|
||||
) {
|
||||
const title = extractTitle(
|
||||
isHtml ? tokens[i].content : tokens[i].info,
|
||||
isHtml,
|
||||
)
|
||||
|
||||
if (title) {
|
||||
const id = nanoid(7)
|
||||
tabs += `<input type="radio" name="group-${name}" id="tab-${id}" ${checked}><label for="tab-${id}">${title}</label>`
|
||||
|
||||
if (checked && !isHtml)
|
||||
tokens[i].info += ' active'
|
||||
checked = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return `<div class="vp-code-group${getAdaptiveThemeMarker(
|
||||
{
|
||||
theme: options.theme!,
|
||||
},
|
||||
)}"><div class="tabs">${tabs}</div><div class="blocks">\n`
|
||||
}
|
||||
return `</div></div>\n`
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,78 +1,44 @@
|
|||
// Modified from https://github.com/egoist/markdown-it-highlight-lines
|
||||
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
|
||||
const wrapperRE = /^<pre .*?><code>/
|
||||
const RE = /{([\d,-]+)}/
|
||||
|
||||
export function highlightLinePlugin(md: MarkdownIt) {
|
||||
const fence = md.renderer.rules.fence!
|
||||
md.renderer.rules.fence = (...args) => {
|
||||
const [tokens, idx, options] = args
|
||||
const [tokens, idx] = args
|
||||
const token = tokens[idx]
|
||||
|
||||
// due to use of markdown-it-attrs, the {0} syntax would have been converted
|
||||
// to attrs on the token
|
||||
// due to use of markdown-it-attrs, the {0} syntax would have been
|
||||
// converted to attrs on the token
|
||||
const attr = token.attrs && token.attrs[0]
|
||||
if (!attr)
|
||||
return fence(...args)
|
||||
|
||||
const lines = attr[0]
|
||||
if (!lines || !/[\d,-]+/.test(lines))
|
||||
return fence(...args)
|
||||
let lines = null
|
||||
|
||||
const lineNumbers = lines
|
||||
.split(',')
|
||||
.map(v => v.split('-').map(v => Number.parseInt(v, 10)))
|
||||
if (!attr) {
|
||||
// markdown-it-attrs maybe disabled
|
||||
const rawInfo = token.info
|
||||
|
||||
const code = options.highlight
|
||||
? options.highlight(token.content, token.info, '')
|
||||
: token.content
|
||||
if (!rawInfo || !RE.test(rawInfo))
|
||||
return fence(...args)
|
||||
|
||||
const rawCode = code.replace(wrapperRE, '')
|
||||
const highlightLinesCode = rawCode
|
||||
.split('\n')
|
||||
.map((split, index) => {
|
||||
const lineNumber = index + 1
|
||||
const inRange = lineNumbers.some(([start, end]) => {
|
||||
if (start && end)
|
||||
return lineNumber >= start && lineNumber <= end
|
||||
const langName = rawInfo.replace(RE, '').trim()
|
||||
|
||||
return lineNumber === start
|
||||
})
|
||||
if (inRange)
|
||||
return '<div class="highlighted"> </div>'
|
||||
// ensure the next plugin get the correct lang
|
||||
token.info = langName
|
||||
|
||||
return '<br>'
|
||||
})
|
||||
.join('')
|
||||
lines = RE.exec(rawInfo)![1]
|
||||
}
|
||||
|
||||
const highlightLinesWrapperCode = `<div class="highlight-lines">${highlightLinesCode}</div>`
|
||||
if (!lines) {
|
||||
lines = attr![0]
|
||||
|
||||
return highlightLinesWrapperCode + code
|
||||
}
|
||||
}
|
||||
|
||||
// markdown-it plugin for generating line numbers.
|
||||
// It depends on preWrapper plugin.
|
||||
export function lineNumberPlugin(md: MarkdownIt) {
|
||||
const fence = md.renderer.rules.fence!
|
||||
md.renderer.rules.fence = (...args) => {
|
||||
const rawCode = fence(...args)
|
||||
const code = rawCode.slice(
|
||||
rawCode.indexOf('<code>'),
|
||||
rawCode.indexOf('</code>'),
|
||||
)
|
||||
|
||||
const lines = code.split('\n')
|
||||
const lineNumbersCode = [...Array(lines.length - 1)]
|
||||
.map((line, index) => `<span class="line-number">${index + 1}</span><br>`)
|
||||
.join('')
|
||||
|
||||
const lineNumbersWrapperCode = `<div class="line-numbers-wrapper">${lineNumbersCode}</div>`
|
||||
|
||||
const finalCode = rawCode
|
||||
.replace(/<\/div>$/, `${lineNumbersWrapperCode}</div>`)
|
||||
.replace(/"(language-\w+)"/, '"$1 line-numbers-mode"')
|
||||
|
||||
return finalCode
|
||||
if (!lines || !/[\d,-]+/.test(lines))
|
||||
return fence(...args)
|
||||
}
|
||||
|
||||
token.info += ` ${lines}`
|
||||
return fence(...args)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
// markdown-it plugin for generating line numbers.
|
||||
// It depends on preWrapper plugin.
|
||||
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
|
||||
export function lineNumberPlugin(md: MarkdownIt, enable = false) {
|
||||
const fence = md.renderer.rules.fence!
|
||||
md.renderer.rules.fence = (...args) => {
|
||||
const rawCode = fence(...args)
|
||||
|
||||
const [tokens, idx] = args
|
||||
const info = tokens[idx].info
|
||||
|
||||
if (
|
||||
(!enable && !/:line-numbers($| |=)/.test(info))
|
||||
|| (enable && /:no-line-numbers($| )/.test(info))
|
||||
)
|
||||
return rawCode
|
||||
|
||||
let startLineNumber = 1
|
||||
const matchStartLineNumber = info.match(/=(\d*)/)
|
||||
if (matchStartLineNumber && matchStartLineNumber[1])
|
||||
startLineNumber = Number.parseInt(matchStartLineNumber[1])
|
||||
|
||||
const code = rawCode.slice(
|
||||
rawCode.indexOf('<code>'),
|
||||
rawCode.indexOf('</code>'),
|
||||
)
|
||||
|
||||
const lines = code.split('\n')
|
||||
|
||||
const lineNumbersCode = [...Array(lines.length)]
|
||||
.map(
|
||||
(_, index) => `<span class="line-number">${index + startLineNumber}</span><br>`,
|
||||
)
|
||||
.join('')
|
||||
|
||||
const lineNumbersWrapperCode = `<div class="line-numbers-wrapper" aria-hidden="true">${lineNumbersCode}</div>`
|
||||
|
||||
const finalCode = rawCode
|
||||
.replace(/<\/div>$/, `${lineNumbersWrapperCode}</div>`)
|
||||
.replace(/"(language-[^"]*?)"/, '"$1 line-numbers-mode"')
|
||||
|
||||
return finalCode
|
||||
}
|
||||
}
|
|
@ -9,9 +9,11 @@ export interface Options {
|
|||
export function extractLang(info: string) {
|
||||
return info
|
||||
.trim()
|
||||
.replace(/:(no-)?line-numbers({| |$).*/, '')
|
||||
.replace(/=(\d*)/, '')
|
||||
.replace(/:(no-)?line-numbers({| |$|=\d*).*/, '')
|
||||
.replace(/(-vue|{| ).*$/, '')
|
||||
.replace(/^vue-html$/, 'template')
|
||||
.replace(/^ansi$/, '')
|
||||
}
|
||||
|
||||
export function getAdaptiveThemeMarker(options: Options) {
|
||||
|
@ -27,6 +29,15 @@ export function getAdaptiveThemeMarker(options: Options) {
|
|||
return marker
|
||||
}
|
||||
|
||||
export function extractTitle(info: string, html = false) {
|
||||
if (html) {
|
||||
return (
|
||||
info.replace(/<!--[^]*?-->/g, '').match(/data-title="(.*?)"/)?.[1] || ''
|
||||
)
|
||||
}
|
||||
return info.match(/\[(.*)\]/)?.[1] || extractLang(info) || 'txt'
|
||||
}
|
||||
|
||||
// markdown-it plugin for wrapping <pre> ... </pre>.
|
||||
//
|
||||
// If your plugin was chained before preWrapper, you can add additional element directly.
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
import path from 'node:path'
|
||||
import fs from 'fs-extra'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import type { RuleBlock } from 'markdown-it/lib/parser_block'
|
||||
import type { MarkdownEnv } from '../..'
|
||||
|
||||
/**
|
||||
* raw path format: "/path/to/file.extension#region {meta} [title]"
|
||||
* where #region, {meta} and [title] are optional
|
||||
* meta can be like '1,2,4-6 lang', 'lang' or '1,2,4-6'
|
||||
* lang can contain special characters like C++, C#, F#, etc.
|
||||
* path can be relative to the current file or absolute
|
||||
* file extension is optional
|
||||
* path can contain spaces and dots
|
||||
*
|
||||
* captures: ['/path/to/file.extension', 'extension', '#region', '{meta}', '[title]']
|
||||
*/
|
||||
export const rawPathRegexp
|
||||
= /^(.+?(?:(?:\.([a-z0-9]+))?))(?:(#[\w-]+))?(?: ?(?:{(\d+(?:[,-]\d+)*)? ?(\S+)?}))? ?(?:\[(.+)\])?$/
|
||||
|
||||
export function rawPathToToken(rawPath: string) {
|
||||
const [
|
||||
filepath = '',
|
||||
extension = '',
|
||||
region = '',
|
||||
lines = '',
|
||||
lang = '',
|
||||
rawTitle = '',
|
||||
] = (rawPathRegexp.exec(rawPath) || []).slice(1)
|
||||
|
||||
const title = rawTitle || filepath.split('/').pop() || ''
|
||||
|
||||
return { filepath, extension, region, lines, lang, title }
|
||||
}
|
||||
|
||||
export function dedent(text: string): string {
|
||||
const lines = text.split('\n')
|
||||
|
||||
const minIndentLength = lines.reduce((acc, line) => {
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
if (line[i] !== ' ' && line[i] !== '\t')
|
||||
return Math.min(i, acc)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, Number.POSITIVE_INFINITY)
|
||||
|
||||
if (minIndentLength < Number.POSITIVE_INFINITY)
|
||||
return lines.map(x => x.slice(minIndentLength)).join('\n')
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
function testLine(
|
||||
line: string,
|
||||
regexp: RegExp,
|
||||
regionName: string,
|
||||
end: boolean = false,
|
||||
) {
|
||||
const [full, tag, name] = regexp.exec(line.trim()) || []
|
||||
|
||||
return (
|
||||
full
|
||||
&& tag
|
||||
&& name === regionName
|
||||
&& tag.match(end ? /^[Ee]nd ?[rR]egion$/ : /^[rR]egion$/)
|
||||
)
|
||||
}
|
||||
|
||||
function findRegion(lines: Array<string>, regionName: string) {
|
||||
const regionRegexps = [
|
||||
/^\/\/ ?#?((?:end)?region) ([\w*-]+)$/, // javascript, typescript, java
|
||||
/^\/\* ?#((?:end)?region) ([\w*-]+) ?\*\/$/, // css, less, scss
|
||||
/^#pragma ((?:end)?region) ([\w*-]+)$/, // C, C++
|
||||
/^<!-- #?((?:end)?region) ([\w*-]+) -->$/, // HTML, markdown
|
||||
/^#((?:End )Region) ([\w*-]+)$/, // Visual Basic
|
||||
/^::#((?:end)region) ([\w*-]+)$/, // Bat
|
||||
/^# ?((?:end)?region) ([\w*-]+)$/, // C#, PHP, Powershell, Python, perl & misc
|
||||
]
|
||||
|
||||
let regexp = null
|
||||
let start = -1
|
||||
|
||||
for (const [lineId, line] of lines.entries()) {
|
||||
if (regexp === null) {
|
||||
for (const reg of regionRegexps) {
|
||||
if (testLine(line, reg, regionName)) {
|
||||
start = lineId + 1
|
||||
regexp = reg
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (testLine(line, regexp, regionName, true)) {
|
||||
return { start, end: lineId, regexp }
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function snippetPlugin(md: MarkdownIt, srcDir: string) {
|
||||
const parser: RuleBlock = (state, startLine, endLine, silent) => {
|
||||
const CH = '<'.charCodeAt(0)
|
||||
const pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||
const max = state.eMarks[startLine]
|
||||
|
||||
// if it's indented more than 3 spaces, it should be a code block
|
||||
if (state.sCount[startLine] - state.blkIndent >= 4)
|
||||
return false
|
||||
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
const ch = state.src.charCodeAt(pos + i)
|
||||
if (ch !== CH || pos + i >= max)
|
||||
return false
|
||||
}
|
||||
|
||||
if (silent)
|
||||
return true
|
||||
|
||||
const start = pos + 3
|
||||
const end = state.skipSpacesBack(max, pos)
|
||||
|
||||
const rawPath = state.src
|
||||
.slice(start, end)
|
||||
.trim()
|
||||
.replace(/^@/, srcDir)
|
||||
.trim()
|
||||
|
||||
const { filepath, extension, region, lines, lang, title }
|
||||
= rawPathToToken(rawPath)
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
const token = state.push('fence', 'code', 0)
|
||||
token.info = `${lang || extension}${lines ? `{${lines}}` : ''}${
|
||||
title ? `[${title}]` : ''
|
||||
}`
|
||||
|
||||
const { realPath, path: _path } = state.env as MarkdownEnv
|
||||
const resolvedPath = path.resolve(path.dirname(realPath ?? _path), filepath)
|
||||
|
||||
// @ts-expect-error - hack
|
||||
token.src = [resolvedPath, region.slice(1)]
|
||||
token.markup = '```'
|
||||
token.map = [startLine, startLine + 1]
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const fence = md.renderer.rules.fence!
|
||||
|
||||
md.renderer.rules.fence = (...args) => {
|
||||
const [tokens, idx, , { includes }] = args
|
||||
const token = tokens[idx]
|
||||
const [src, regionName] = token.src ?? []
|
||||
|
||||
if (!src)
|
||||
return fence(...args)
|
||||
|
||||
if (includes)
|
||||
includes.push(src)
|
||||
|
||||
const isAFile = fs.statSync(src).isFile()
|
||||
if (!fs.existsSync(src) || !isAFile) {
|
||||
token.content = isAFile
|
||||
? `Code snippet path not found: ${src}`
|
||||
: `Invalid code snippet option`
|
||||
token.info = ''
|
||||
return fence(...args)
|
||||
}
|
||||
|
||||
let content = fs.readFileSync(src, 'utf8')
|
||||
|
||||
if (regionName) {
|
||||
const lines = content.split(/\r?\n/)
|
||||
const region = findRegion(lines, regionName)
|
||||
|
||||
if (region) {
|
||||
content = dedent(
|
||||
lines
|
||||
.slice(region.start, region.end)
|
||||
.filter(line => !region.regexp.test(line.trim()))
|
||||
.join('\n'),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
token.content = content
|
||||
return fence(...args)
|
||||
}
|
||||
|
||||
md.block.ruler.before('fence', 'snippet', parser)
|
||||
}
|
|
@ -54,6 +54,8 @@ export interface MarkdownOptions {
|
|||
classes: string
|
||||
}
|
||||
|
||||
lineNumbers?: boolean
|
||||
|
||||
katex?: KatexOptions
|
||||
/**
|
||||
* shiki
|
||||
|
|
Loading…
Reference in New Issue