Skip to content

Commit

Permalink
feat: add plugin-markdown-ext
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed Oct 22, 2024
1 parent f9776da commit 1ebe65d
Show file tree
Hide file tree
Showing 15 changed files with 461 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@
"globby",
"gtag",
"jsonld",
"katex",
"lazyload",
"lightmode",
"katex",
"linkify",
"mathjax",
"mdit",
"nord",
Expand Down
60 changes: 60 additions & 0 deletions plugins/markdown/plugin-markdown-ext/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "@vuepress/plugin-markdown-ext",
"version": "2.0.0-rc.54",
"description": "VuePress plugin - markdown extension",
"keywords": [
"vuepress-plugin",
"vuepress",
"plugin",
"markdown",
"extension"
],
"homepage": "https://ecosystem.vuejs.press/plugins/markdown/markdown-ext.html",
"bugs": {
"url": "https://github.com/vuepress/ecosystem/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuepress/ecosystem.git",
"directory": "plugins/markdown/plugin-markdown-ext"
},
"license": "MIT",
"author": {
"name": "Mr.Hope",
"email": "mister-hope@outlook.com",
"url": "https://mister-hope.com"
},
"type": "module",
"exports": {
".": "./lib/node/index.js",
"./package.json": "./package.json"
},
"main": "./lib/node/index.js",
"types": "./lib/node/index.d.ts",
"files": [
"lib"
],
"scripts": {
"build": "tsc -b tsconfig.build.json",
"bundle": "rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf --glob ./lib ./*.tsbuildinfo"
},
"dependencies": {
"@mdit/plugin-container": "^0.13.1",
"@mdit/plugin-footnote": "^0.13.1",
"@mdit/plugin-tasklist": "^0.13.1",
"@types/markdown-it": "^14.1.2",
"@vuepress/helper": "workspace:*",
"js-yaml": "^4.1.0"
},
"peerDependencies": {
"vuepress": "2.0.0-rc.18"
},
"devDependencies": {
"@types/js-yaml": "4.0.9",
"markdown-it": "^14.1.0"
},
"publishConfig": {
"access": "public"
}
}
10 changes: 10 additions & 0 deletions plugins/markdown/plugin-markdown-ext/rollup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { rollupBundle } from '../../../scripts/rollup.js'

export default rollupBundle('node/index', {
external: [
'@mdit/plugin-container',
'@mdit/plugin-footnote',
'@mdit/plugin-tasklist',
'js-yaml',
],
})
1 change: 1 addition & 0 deletions plugins/markdown/plugin-markdown-ext/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './markdownExtPlugin.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { load } from 'js-yaml'
import type { Options, PluginSimple } from 'markdown-it'
import type { RenderRule } from 'markdown-it/lib/renderer.mjs'
import type Token from 'markdown-it/lib/token.mjs'
import type { MarkdownEnv } from 'vuepress/markdown'

import { logger } from '../utils.js'
import { stringifyProp } from './utils.js'

const getComponentRender =
(name: string): RenderRule =>
(
tokens: Token[],
index: number,
_options: Options,
{ filePathRelative }: MarkdownEnv,
): string => {
const token = tokens[index]
const { content } = token

let config: unknown = null

if (content.trim().startsWith('{'))
try {
config = JSON.parse(content) as unknown
} catch {
// Do nothing
}
else
try {
config = load(content)
} catch {
// Do nothing
}

if (config) return `<${name} v-bind='${stringifyProp(config)}' />`

logger.error(
`Invalid ${name} config${
filePathRelative ? ` found in ${filePathRelative}` : ''
}:
${content}
`,
)

return ''
}

export const component: PluginSimple = (md) => {
// Handle ```component blocks
const { fence } = md.renderer.rules

md.renderer.rules.fence = (...args): string => {
const [tokens, index] = args
const { info } = tokens[index]

const [fenceName, componentName] = info.split(' ', 2)

if (fenceName === 'component') {
const render = getComponentRender(componentName)

return render(...args)
}

return fence!(...args)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './component.js'
export * from './vPre.js'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Single quote will break @vue/compiler-sfc
export const stringifyProp = (data: unknown): string =>
JSON.stringify(data).replace(/'/g, '&#39')
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { container } from '@mdit/plugin-container'
import type { PluginSimple } from 'markdown-it'

export const vPre: PluginSimple = (md) => {
container(md, {
name: 'v-pre',
openRender: () => `<div v-pre>\n`,
closeRender: () => '</div>\n',
})
}
38 changes: 38 additions & 0 deletions plugins/markdown/plugin-markdown-ext/src/node/markdownExtPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { footnote as footnotePlugin } from '@mdit/plugin-footnote'
import { tasklist as tasklistPlugin } from '@mdit/plugin-tasklist'
import type { Plugin } from 'vuepress/core'
import { isPlainObject } from 'vuepress/shared'

import {
component as componentPlugin,
vPre as vPrePlugin,
} from './markdown-it-plugins/index.js'
import type { MarkdownExtPluginOptions } from './options.js'
import { PLUGIN_NAME } from './utils.js'

export const markdownExtPlugin = ({
gfm,
breaks,
linkify,
footnote,
tasklist,

component,
vPre,
}: MarkdownExtPluginOptions): Plugin => {
return {
name: PLUGIN_NAME,

extendsMarkdown: (md) => {
// Behavior
if (breaks ?? gfm) md.options.breaks = true
if (linkify ?? gfm) md.options.linkify = true

if (footnote ?? gfm) md.use(footnotePlugin)
if (tasklist ?? gfm)
md.use(tasklistPlugin, [isPlainObject(tasklist) ? tasklist : {}])
if (component) md.use(componentPlugin)
if (vPre) md.use(vPrePlugin)
},
}
}
77 changes: 77 additions & 0 deletions plugins/markdown/plugin-markdown-ext/src/node/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { MarkdownItTaskListOptions } from '@mdit/plugin-tasklist'

/**
* Options of markdown-ext plugin
*/
export interface MarkdownExtPluginOptions {
/**
* Whether enable standard GFM support
*
* 是否启用标准的 GitHub Favor Markdown 支持
*
* @default false
*/
gfm?: boolean

/**
* Whether convert `\n` in paragraphs into `<br>`s
*
* 是否将段落中的 `\n` 转换为 `<br>`
*
* @description enabled in gfm mode
*
* @default false
*/
breaks?: boolean

/**
* Whether convert URL-like text into links
*
* 是否将文字中的链接格式文字转换为链接
*
* @description enabled in gfm mode
*
* @default false
*/
linkify?: boolean

/**
* Whether to enable footnote format support
*
* 是否启用脚注格式支持。
*
* @description enabled in gfm mode
*
* @default false
*/
footnote?: boolean

/**
* Whether to enable tasklist format support
*
* 是否启用任务列表支持
*
* @description enabled in gfm mode
*
* @default false
*/
tasklist?: MarkdownItTaskListOptions | boolean

/**
* Whether to enable component support
*
* 是否启用组件支持
*
* @default false
*/
component?: boolean

/**
* Whether to enable v-pre wrapper.
*
* 是否启用 v-pre 容器。
*
* @default false
*/
vPre?: boolean
}
5 changes: 5 additions & 0 deletions plugins/markdown/plugin-markdown-ext/src/node/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Logger } from '@vuepress/helper'

export const PLUGIN_NAME = '@vuepress/plugin-markdown-ext'

export const logger = new Logger(PLUGIN_NAME)
95 changes: 95 additions & 0 deletions plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import MarkdownIt from 'markdown-it'
import { describe, expect, it } from 'vitest'

import { component } from '../../src/node/markdown-it-plugins/component.js'

describe('component', () => {
const markdownIt = MarkdownIt({ linkify: true }).use(component)

it('Should resolve component fence', () => {
const result1 = markdownIt.render(
`
\`\`\`component VPCard
title: A card
desc: A card desc
logo: https://example.com/logo.png
link: https://example.com
color: "#000"
\`\`\`
`,
{},
)

const result2 = markdownIt.render(
`
\`\`\`component VPCard
{
"title": "A card",
"desc": "A card desc",
"logo": "https://example.com/logo.png",
"link": "https://example.com",
"color": "#000"
}
\`\`\`
`,
{},
)

expect(result1).toContain('VPCard')
expect(result1).toMatchSnapshot()

Check failure on line 39 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / coverage

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should resolve component fence

Error: Snapshot `component > Should resolve component fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:39:21

Check failure on line 39 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / build-check

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should resolve component fence

Error: Snapshot `component > Should resolve component fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:39:21

Check failure on line 39 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / bundle-check

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should resolve component fence

Error: Snapshot `component > Should resolve component fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:39:21

Check failure on line 39 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / coverage

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should resolve component fence

Error: Snapshot `component > Should resolve component fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:39:21

Check failure on line 39 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / build-check

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should resolve component fence

Error: Snapshot `component > Should resolve component fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:39:21

Check failure on line 39 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / bundle-check

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should resolve component fence

Error: Snapshot `component > Should resolve component fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:39:21
expect(result2).toContain('VPCard')
expect(result2).toMatchSnapshot()
})

it('Should not throw with invalid syntax', () => {
const result1 = markdownIt.render(
`
\`\`\`component VPCard
title: a
title: b
\`\`\`
`,
{},
)

const result2 = markdownIt.render(
`
\`\`\`component VPCard
title: a
title: b
\`\`\`
`,
{},
)

expect(result1).toEqual('')
expect(result2).toEqual('')
})

it('Should drop when receiving a invalid syntax', () => {
const result = markdownIt.render(
`
\`\`\`component VPCard
{a:1}
\`\`\`
`,
{},
)

expect(result).toMatch('')
})

it('Should not break markdown fence', () => {
const result = markdownIt.render(
`
\`\`\`js
const a = 1;
\`\`\`
`,
{},
)

expect(result).toMatch(/<pre.*>[\s\S]*<\/pre>/)
expect(result).toMatchSnapshot()

Check failure on line 93 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / coverage

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should not break markdown fence

Error: Snapshot `component > Should not break markdown fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:93:20

Check failure on line 93 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / build-check

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should not break markdown fence

Error: Snapshot `component > Should not break markdown fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:93:20

Check failure on line 93 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / bundle-check

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should not break markdown fence

Error: Snapshot `component > Should not break markdown fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:93:20

Check failure on line 93 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / coverage

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should not break markdown fence

Error: Snapshot `component > Should not break markdown fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:93:20

Check failure on line 93 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / build-check

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should not break markdown fence

Error: Snapshot `component > Should not break markdown fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:93:20

Check failure on line 93 in plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts

View workflow job for this annotation

GitHub Actions / bundle-check

plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts > component > Should not break markdown fence

Error: Snapshot `component > Should not break markdown fence 1` mismatched ❯ plugins/markdown/plugin-markdown-ext/tests/node/component.spec.ts:93:20
})
})
Loading

0 comments on commit 1ebe65d

Please sign in to comment.