feat(plugin-md-power): add @[codesandbox](user/id) syntax supported

This commit is contained in:
pengzhanbo 2024-04-03 02:06:26 +08:00
parent 9124f788a3
commit 3a6ebcf3d4
8 changed files with 187 additions and 0 deletions

View File

@ -74,6 +74,7 @@ export const theme: Theme = themePlume({
icons: true,
codepen: true,
replit: true,
codeSandbox: true,
},
comment: {
provider: 'Giscus',

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,7 @@ import PDFViewer from './components/PDFViewer.vue'
import Bilibili from './components/Bilibili.vue'
import Youtube from './components/Youtube.vue'
import Replit from './components/Replit.vue'
import CodeSandbox from './components/CodeSandbox.vue'
import '@internal/md-power/icons.css'
@ -25,6 +26,9 @@ export default defineClientConfig({
if (pluginOptions.replit)
app.component('ReplitViewer', Replit)
if (pluginOptions.codeSandbox)
app.component('CodeSandboxViewer', CodeSandbox)
if (__VUEPRESS_SSR__)
return

View File

@ -0,0 +1,97 @@
/**
* @[codesandbox](id)
* @[codesandbox share](user/id)
* @[codesanbox title="xxx" layout="Editor+Preview" height="500px" navbar=false console=false](id#filepath)
*/
import type { PluginWithOptions } from 'markdown-it'
import type { RuleBlock } from 'markdown-it/lib/parser_block.js'
import { resolveAttrs } from '../utils/resolveAttrs.js'
import { parseRect } from '../utils/parseRect.js'
import type { CodeSandboxTokenMeta } from '../../shared/codeSandbox.js'
// @[codesandbox]()
const MIN_LENGTH = 16
// char codes of `@[codesandbox`
const START_CODES = [64, 91, 99, 111, 100, 101, 115, 97, 110, 100, 98, 111, 120]
// regexp to match the import syntax
const SYNTAX_RE = /^@\[codesandbox(?:\s+(embed|share))?(?:\s+([^]*?))?\]\(([^)]*?)\)/
function createCodeSandboxRuleBlock(): RuleBlock {
return (state, startLine, endLine, silent) => {
const pos = state.bMarks[startLine] + state.tShift[startLine]
const max = state.eMarks[startLine]
// return false if the length is shorter than min length
if (pos + MIN_LENGTH > max)
return false
// check if it's matched the start
for (let i = 0; i < START_CODES.length; i += 1) {
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
return false
}
// check if it's matched the syntax
const match = state.src.slice(pos, max).match(SYNTAX_RE)
if (!match)
return false
// return true as we have matched the syntax
if (silent)
return true
const [, type, info = '', source] = match
const { attrs } = resolveAttrs(info)
const [profile, filepath = ''] = source.split('#')
const [user, id] = profile.includes('/') ? profile.split('/') : ['', profile]
const meta: CodeSandboxTokenMeta = {
width: attrs.width ? parseRect(attrs.width) : '100%',
height: attrs.height ? parseRect(attrs.height) : '500px',
user,
id,
title: attrs.title ?? '',
console: attrs.console ?? false,
navbar: attrs.navbar ?? true,
layout: attrs.layout ?? '',
type: (type || 'embed') as CodeSandboxTokenMeta['type'],
filepath,
}
const token = state.push('code_sandbox', '', 0)
token.meta = meta
token.map = [startLine, startLine + 1]
token.info = match[0]
state.line = startLine + 1
return true
}
}
function resolveCodeSandbox(meta: CodeSandboxTokenMeta) {
const { title, height, width, user, id, type, filepath, console, navbar, layout } = meta
return `<CodeSandboxViewer title="${title}" height="${height}" width="${width}" user="${user}" id="${id}" type="${type}" filepath="${filepath}" :console=${console} :navbar=${navbar} layout="${layout}" />`
}
export const codeSandboxPlugin: PluginWithOptions<never> = (md) => {
md.block.ruler.before(
'import_code',
'code_sandbox',
createCodeSandboxRuleBlock(),
)
md.renderer.rules.code_sandbox = (tokens, index) => {
const token = tokens[index]
const content = resolveCodeSandbox(token.meta)
token.content = content
return content
}
}

View File

@ -8,6 +8,7 @@ import { bilibiliPlugin } from './features/video/bilibili.js'
import { youtubePlugin } from './features/video/youtube.js'
import { codepenPlugin } from './features/codepen.js'
import { replitPlugin } from './features/replit.js'
import { codeSandboxPlugin } from './features/codeSandbox.js'
const __dirname = getDirname(import.meta.url)
@ -64,6 +65,11 @@ export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): P
// @[replit](user/repl-name)
md.use(replitPlugin)
}
if (options.codeSandbox) {
// @[codesandbox](id)
md.use(codeSandboxPlugin)
}
},
}
}

View File

@ -0,0 +1,12 @@
import type { SizeOptions } from './size.js'
export interface CodeSandboxTokenMeta extends SizeOptions {
user?: string
id?: string
layout?: string
type?: 'share' | 'embed'
title?: string
filepath?: string
navbar?: boolean
console?: boolean
}

View File

@ -3,4 +3,6 @@ export * from './pdf.js'
export * from './icons.js'
export * from './video.js'
export * from './codepen.js'
export * from './codeSandbox.js'
export * from './replit.js'
export * from './plugin.js'

View File

@ -15,6 +15,7 @@ export interface MarkdownPowerPluginOptions {
// code embed
codepen?: boolean
replit?: boolean
codeSandbox?: boolean
caniuse?: boolean | CanIUseOptions
}