Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/.vuepress/configs/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ export const plugins = [
markmap: true,
mermaid: true,
plantuml: true,
DANGEROUS_ALLOW_SCRIPT_EXECUTION: true,
DANGEROUS_SCRIPT_EXECUTION_ALLOWLIST: [
'plugins/markdown/markdown-chart/echarts',
'zh/plugins/markdown/markdown-chart/echarts',
],
}),
markdownExtPlugin({
gfm: true,
Expand Down
4 changes: 2 additions & 2 deletions docs/.vuepress/echarts-snippets/bar.snippet.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ const run = () => {
for (let i = 0; i < data.length; i++)
data[i] += Math.round(Math.random() * Math.random() > 0.9 ? 2000 : 200)

echarts.setOption({
myChart.setOption({
series: [{ type: 'bar', data }],
})
}

const timeId = setInterval(() => {
if (echarts._disposed) {
if (myChart._disposed) {
clearInterval(timeId)

return
Expand Down
4 changes: 2 additions & 2 deletions docs/.vuepress/echarts-snippets/line.snippet.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const option = {
],
}
const timeId = setInterval(() => {
if (echarts._disposed) {
if (myChart._disposed) {
clearInterval(timeId)
return
}
Expand All @@ -86,7 +86,7 @@ const timeId = setInterval(() => {
data.shift()
data.push(randomData())
}
echarts.setOption({
myChart.setOption({
series: [{ data }],
})
}, 1000)
Expand Down
11 changes: 11 additions & 0 deletions docs/plugins/markdown/markdown-chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,14 @@ export default {

- Type: `boolean | MarkdownItPlantumlOptions[]`
- Details: Whether to enable PlantUML support. Can accept configuration options for advanced usage.

### DANGEROUS_ALLOW_SCRIPT_EXECUTION

- Type: `boolean`
- Details: Whether to allow script execution in charts.

### DANGEROUS_SCRIPT_EXECUTION_ALLOWLIST

- Type: `string[] | '*'`
- Default: `[]`
- Details: Only effective when `DANGEROUS_ALLOW_SCRIPT_EXECUTION` is enabled. A list of file paths allowed to execute chart scripts. Use `'*'` to allow all files.
8 changes: 7 additions & 1 deletion docs/plugins/markdown/markdown-chart/chartjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ export default {
:::
````

Both `js` and `javascript` code blocks are also supported. For these, you should assign your export object to `module.exports`.
You should use `json` code block to provide your Chart.js configuration whenever possible, however for dynamic data generation, you can also use script blocks. Both `js` and `javascript` code blocks are also supported. You should assign your export object to `module.exports`.

::: warning

For security reasons, you need to manually allow script blocks in certain files. Set `DANGEROUS_ALLOW_SCRIPT_EXECUTION: true` and `DANGEROUS_SCRIPT_EXECUTION_ALLOWLIST: ['your/file/path.md']` in plugin options.

:::

## Demo

Expand Down
10 changes: 8 additions & 2 deletions docs/plugins/markdown/markdown-chart/echarts.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,15 @@ If you can generate your chart data easily, you can just provide echarts config

### With Scripts

If you need to use script to get the data, you can use `js` or `javascript` code block.
You should use `json` code block to provide your ECharts configuration whenever possible, however for dynamic data generation, you can also use script blocks.

We will expose the echarts instance as `echarts` in the script, and you are expected to assign the echarts option object to `option` variable. Also, you can assign `width` and `height` variable to set the chart size.
Both `js` or `javascript` code block are supported. We will expose the echarts lib as `echarts` and the instance as `myChart` in the script, and you are expected to assign the echarts option object to `option` variable. Also, you can assign `width` and `height` variable to set the chart size.

::: warning

For security reasons, you need to manually allow script blocks in certain files. Set `DANGEROUS_ALLOW_SCRIPT_EXECUTION: true` and `DANGEROUS_SCRIPT_EXECUTION_ALLOWLIST: ['your/file/path.md']` in plugin options.

:::

````md
::: echarts Title
Expand Down
11 changes: 11 additions & 0 deletions docs/zh/plugins/markdown/markdown-chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,14 @@ export default {

- 类型:`boolean | MarkdownItPlantumlOptions[]`
- 详情:是否启用 PlantUML 支持。可以接受配置选项以供高级使用。

### DANGEROUS_ALLOW_SCRIPT_EXECUTION

- 类型:`boolean`
- 详情:是否允许在图表中执行脚本。这可能会带来安全风险,请谨慎使用。

### DANGEROUS_SCRIPT_EXECUTION_ALLOWLIST

- 类型:`string[] | '*'`
- 默认:`[]`
- 详情:当启用脚本执行时,允许执行图表脚本的文件路径列表。使用 `'*'` 允许所有文件。
8 changes: 7 additions & 1 deletion docs/zh/plugins/markdown/markdown-chart/chartjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ export default {
:::
````

同时支持 `js` 和 `javascript` 代码块。对于这些,你应该将导出对象赋值给 `module.exports`。
你应该尽可能使用 `json` 代码块来提供你的图表数据配置,但如果需要动态生成数据,你也可以使用脚本块。`js` 或 `javascript` 代码块均受支持。你应当将导出的对象赋值给 `module.exports`。

::: warning

出于安全考虑,你需要手动允许特定文件中的脚本块。请在插件选项中设置 `DANGEROUS_ALLOW_SCRIPT_EXECUTION: true` 和 `DANGEROUS_SCRIPT_EXECUTION_ALLOWLIST: ['your/file/path.md']`。

:::

## 案例

Expand Down
10 changes: 8 additions & 2 deletions docs/zh/plugins/markdown/markdown-chart/echarts.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,15 @@ export default {

### 使用脚本

如果你需要通过脚本来获取数据,你可以使用 `js` 和 `javascript` 的代码块
你应该尽可能使用 `json` 代码块来提供你的 ECharts 配置,但如果需要动态生成数据,你也可以使用脚本块

我们将通过 `echarts` 变量暴露 ECharts 实例,并且你应该将 Echart 配置赋值给 `option` 变量。同时,你也可以赋值 `width` 和 `height` 来设置图表大小。
`js` 或 `javascript` 代码块均受支持。在脚本中,我们会将 echarts 库作为 `echarts`,实例作为 `myChart` 暴露给你,你需要将 echarts 配置对象赋值给 `option` 变量。同时,你也可以通过设置 `width` 和 `height` 变量来设置图表大小。

::: warning

出于安全考虑,你需要手动允许特定文件中的脚本块。请在插件选项中设置 `DANGEROUS_ALLOW_SCRIPT_EXECUTION: true` 和 `DANGEROUS_SCRIPT_EXECUTION_ALLOWLIST: ['your/file/path.md']`。

:::

````md
::: echarts Title
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LoadingIcon, decodeData } from '@vuepress/helper/client'
import { useDebounceFn, useEventListener } from '@vueuse/core'
import type { EChartsOption, EChartsType } from 'echarts'
import type * as ECharts from 'echarts'
import type { PropType, VNode } from 'vue'
import {
defineComponent,
Expand Down Expand Up @@ -29,12 +30,14 @@ const AsyncFunction = (async (): Promise<void> => {}).constructor
const parseEChartsConfig = (
config: string,
type: 'js' | 'json',
echarts: typeof ECharts,
instance: EChartsType,
): Promise<EChartsConfig> => {
if (type === 'js') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const runner = AsyncFunction(
'echarts',
'myChart',
`\
let width,height,option,__echarts_config__;
{
Expand All @@ -46,7 +49,7 @@ return __echarts_config__;
)

// eslint-disable-next-line @typescript-eslint/no-unsafe-call
return runner(instance) as Promise<EChartsConfig>
return runner(echarts, instance) as Promise<EChartsConfig>
}

return Promise.resolve({ option: JSON.parse(config) as EChartsOption })
Expand Down Expand Up @@ -99,12 +102,12 @@ export default defineComponent({
}, 100),
)

const destroyEcharts = (): void => {
const destroyECharts = (): void => {
instance?.dispose()
instance = null
}

const renderEcharts = async (): Promise<void> => {
const renderECharts = async (): Promise<void> => {
if (__VUEPRESS_SSR__) return

const echarts = await import(/* webpackChunkName: "echarts" */ 'echarts')
Expand All @@ -116,6 +119,7 @@ export default defineComponent({
const { option, ...size } = await parseEChartsConfig(
decodeData(props.config),
props.type,
echarts,
instance,
)

Expand All @@ -125,7 +129,7 @@ export default defineComponent({

onContentUpdated(async (reason) => {
if (reason === 'mounted') {
await renderEcharts()
await renderECharts()
loaded.value = true
}
})
Expand All @@ -137,15 +141,15 @@ export default defineComponent({
watch(
() => props.config,
async () => {
destroyEcharts()
destroyECharts()
await nextTick()
await renderEcharts()
await renderECharts()
},
{ flush: 'post' },
)
})

onUnmounted(destroyEcharts)
onUnmounted(destroyECharts)

return (): (VNode | null)[] => [
props.title
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,92 @@
import { container } from '@mdit/plugin-container'
import { encodeData } from '@vuepress/helper'
import type { PluginSimple } from 'markdown-it'
import type { PluginWithOptions } from 'markdown-it'
import { colors } from 'vuepress/utils'

export interface ChartJSPluginOptions {
/**
* Allow executing custom scripts inside Chart.js blocks.
*
* 允许在 Chart.js 块内执行自定义脚本。
*
* @default false
*/
allowScripts?: boolean

/**
* Allow all scripts to be executed inside Chart.js blocks.
*
* 允许在 Chart.js 块内执行所有脚本。
*
* @default false
*/
allowAll?: boolean

/**
* List of files allowed to execute scripts inside Chart.js blocks.
*
* 允许在 Chart.js 块内执行脚本的文件列表。
*
* @default new Set()
*/
allowList?: Set<string>
}

/**
* Chart.js markdown-it plugin
*
* Chart.js markdown-it 插件
*/
export const chartjs: PluginSimple = (md) => {
export const chartjs: PluginWithOptions<ChartJSPluginOptions> = (
md,
options,
) => {
const { allowScripts, allowAll, allowList = new Set() } = options ?? {}

container(md, {
name: 'chartjs',
openRender: (tokens, index) => {
openRender: (tokens, index, _options, env) => {
const title = tokens[index].info
.trimStart()
// "chartjs" length
.slice(7)
.trim()

let config = '{}'
let configType = ''
let isJavaScript = false
let isInAllowList = false

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const filePathRelative: string = env?.filePathRelative ?? ''

for (let i = index; i < tokens.length; i++) {
const { type, content, info } = tokens[i]

if (type === 'container_chartjs_close') break
if (type === 'container_chartjs_close') {
if (isJavaScript && !isInAllowList) {
// eslint-disable-next-line no-console
console.warn(
`\
${colors.magenta('chartjs')}: JavaScript in Chart.js block is found in ${colors.cyan(filePathRelative)}, ${colors.red('it is ignored for security reasons')}.
To enable the chart, you must manually add it to allowlist, see https://vuepress.vuejs.org/plugin/markdown/markdown-charts/chartjs.html for details.
`,
)
tokens[i].hidden = true
}
break
}

if (!content) continue
if (type === 'fence')
if (info === 'json') {
config = encodeData(content)
configType = 'json'
} else if (info === 'js' || info === 'javascript') {
config = encodeData(content)
configType = 'js'
isJavaScript = true

if (allowScripts && (allowAll || allowList.has(filePathRelative))) {
isInAllowList = true
}
}

// Set to an unknown token type
Expand All @@ -41,10 +95,14 @@ export const chartjs: PluginSimple = (md) => {
tokens[i].hidden = true
}

return `<ChartJS config="${config}" ${
title ? `title="${encodeURIComponent(title)}" ` : ''
}type="${configType}">`
if (isJavaScript && !isInAllowList) {
return ''
}

return `<ChartJS config="${config}"${
title ? ` title="${encodeURIComponent(title)}"` : ''
}${isJavaScript ? ' type="js"' : ''}>`
},
closeRender: () => `</ChartJS>`,
closeRender: (tokens, index) => (tokens[index].hidden ? '' : '</ChartJS>'),
})
}
Loading
Loading