mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
chore: tweak
This commit is contained in:
parent
3018f6789b
commit
cb71585079
@ -35,7 +35,7 @@ const source = computed(() => {
|
||||
/>
|
||||
</ClientOnly>
|
||||
<p v-else>
|
||||
<a :href="source" target="_blank" rel="noopener noreferrer" :aria-label="title || 'CodeSandbox'">
|
||||
<a class="code-sandbox-link no-icon" :href="source" target="_blank" rel="noopener noreferrer" :aria-label="title || 'CodeSandbox'">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="165" height="32" viewBox="0 0 165 32" fill="none">
|
||||
<rect width="165" height="32" rx="4" fill="#E3FF73" />
|
||||
<rect x="0.5" y="0.5" width="164" height="31" rx="3.5" stroke="black" stroke-opacity="0.1" />
|
||||
@ -54,6 +54,10 @@ const source = computed(() => {
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.code-sandbox-link {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.code-sandbox-iframe {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
|
||||
@ -4,107 +4,14 @@
|
||||
*/
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
import type Token from 'markdown-it/lib/token.mjs'
|
||||
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import container from 'markdown-it-container'
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import type { CanIUseMode, CanIUseOptions, CanIUseTokenMeta } from '../../shared/index.js'
|
||||
import { createRuleBlock } from '../utils/createRuleBlock.js'
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5)
|
||||
|
||||
// @[caniuse]()
|
||||
const minLength = 12
|
||||
|
||||
// char codes of '@[caniuse'
|
||||
const START_CODES = [64, 91, 99, 97, 110, 105, 117, 115, 101]
|
||||
|
||||
// regexp to match the import syntax
|
||||
const SYNTAX_RE = /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/
|
||||
const UNDERLINE_RE = /_+/g
|
||||
|
||||
function createCanIUseRuleBlock(defaultMode: CanIUseMode): 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 + minLength > 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 [, mode, versions = '', feature] = match
|
||||
|
||||
const meta: CanIUseTokenMeta = {
|
||||
feature,
|
||||
mode: (mode as CanIUseMode) || defaultMode,
|
||||
versions,
|
||||
}
|
||||
|
||||
const token = state.push('caniuse', '', 0)
|
||||
|
||||
token.meta = meta
|
||||
token.map = [startLine, startLine + 1]
|
||||
token.info = mode || defaultMode
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function resolveCanIUse({ feature, mode, versions }: CanIUseTokenMeta): string {
|
||||
if (!feature)
|
||||
return ''
|
||||
|
||||
if (mode === 'image') {
|
||||
const link = 'https://caniuse.bitsofco.de/image/'
|
||||
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`
|
||||
return `<ClientOnly><p><picture>
|
||||
<source type="image/webp" srcset="${link}${feature}.webp">
|
||||
<source type="image/png" srcset="${link}${feature}.png">
|
||||
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
|
||||
</picture></p></ClientOnly>`
|
||||
}
|
||||
|
||||
feature = feature.replace(UNDERLINE_RE, '_')
|
||||
const { past, future } = resolveVersions(versions)
|
||||
const meta = nanoid()
|
||||
|
||||
return `<CanIUseViewer feature="${feature}" meta="${meta}" past="${past}" future="${future}" />`
|
||||
}
|
||||
|
||||
function resolveVersions(versions: string): { past: number, future: number } {
|
||||
if (!versions)
|
||||
return { past: 2, future: 1 }
|
||||
|
||||
const list = versions
|
||||
.split(',')
|
||||
.map(v => Number(v.trim()))
|
||||
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3)
|
||||
|
||||
list.push(0)
|
||||
|
||||
const uniq = [...new Set(list)].sort((a, b) => b - a)
|
||||
return {
|
||||
future: uniq[0],
|
||||
past: Math.abs(uniq[uniq.length - 1]),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```md
|
||||
@ -113,25 +20,18 @@ function resolveVersions(versions: string): { past: number, future: number } {
|
||||
*/
|
||||
export const caniusePlugin: PluginWithOptions<CanIUseOptions> = (
|
||||
md,
|
||||
{ mode = 'embed' }: CanIUseOptions = {},
|
||||
{ mode: defaultMode = 'embed' }: CanIUseOptions = {},
|
||||
): void => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'caniuse',
|
||||
createCanIUseRuleBlock(mode),
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||
},
|
||||
)
|
||||
|
||||
md.renderer.rules.caniuse = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
|
||||
const content = resolveCanIUse(token.meta)
|
||||
token.content = content
|
||||
|
||||
return content
|
||||
}
|
||||
createRuleBlock<CanIUseTokenMeta>(md, {
|
||||
type: 'caniuse',
|
||||
syntaxPattern: /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/,
|
||||
meta: ([, mode, versions = '', feature]) => ({
|
||||
feature,
|
||||
mode: (mode as CanIUseMode) || defaultMode,
|
||||
versions,
|
||||
}),
|
||||
content: meta => resolveCanIUse(meta),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,3 +74,42 @@ export function legacyCaniuse(
|
||||
|
||||
md.use(container, type, { validate, render })
|
||||
}
|
||||
|
||||
function resolveCanIUse({ feature, mode, versions }: CanIUseTokenMeta): string {
|
||||
if (!feature)
|
||||
return ''
|
||||
|
||||
if (mode === 'image') {
|
||||
const link = 'https://caniuse.bitsofco.de/image/'
|
||||
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`
|
||||
return `<ClientOnly><p><picture>
|
||||
<source type="image/webp" srcset="${link}${feature}.webp">
|
||||
<source type="image/png" srcset="${link}${feature}.png">
|
||||
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
|
||||
</picture></p></ClientOnly>`
|
||||
}
|
||||
|
||||
feature = feature.replace(UNDERLINE_RE, '_')
|
||||
const { past, future } = resolveVersions(versions)
|
||||
const meta = nanoid()
|
||||
|
||||
return `<CanIUseViewer feature="${feature}" meta="${meta}" past="${past}" future="${future}" />`
|
||||
}
|
||||
|
||||
function resolveVersions(versions: string): { past: number, future: number } {
|
||||
if (!versions)
|
||||
return { past: 2, future: 1 }
|
||||
|
||||
const list = versions
|
||||
.split(',')
|
||||
.map(v => Number(v.trim()))
|
||||
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3)
|
||||
|
||||
list.push(0)
|
||||
|
||||
const uniq = [...new Set(list)].sort((a, b) => b - a)
|
||||
return {
|
||||
future: uniq[0],
|
||||
past: Math.abs(uniq[uniq.length - 1]),
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,94 +4,35 @@
|
||||
* @[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.mjs'
|
||||
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|button))?(?:\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}" />`
|
||||
}
|
||||
import { createRuleBlock } from '../utils/createRuleBlock.js'
|
||||
|
||||
export const codeSandboxPlugin: PluginWithOptions<never> = (md) => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'code_sandbox',
|
||||
createCodeSandboxRuleBlock(),
|
||||
)
|
||||
createRuleBlock<CodeSandboxTokenMeta>(md, {
|
||||
type: 'codesandbox',
|
||||
syntaxPattern: /^@\[codesandbox(?:\s+(embed|button))?(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
||||
meta([, type, info = '', source = '']) {
|
||||
const { attrs } = resolveAttrs(info)
|
||||
const [profile, filepath = ''] = source.split('#')
|
||||
const [user, id] = profile.includes('/') ? profile.split('/') : ['', profile]
|
||||
|
||||
md.renderer.rules.code_sandbox = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
|
||||
const content = resolveCodeSandbox(token.meta)
|
||||
token.content = content
|
||||
|
||||
return content
|
||||
}
|
||||
return {
|
||||
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,
|
||||
}
|
||||
},
|
||||
content({ title, height, width, user, id, type, filepath, console, navbar, layout }) {
|
||||
return `<CodeSandboxViewer title="${title}" height="${height}" width="${width}" user="${user}" id="${id}" type="${type}" filepath="${filepath}" :console=${console} :navbar=${navbar} layout="${layout}" />`
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,106 +4,46 @@
|
||||
* @[codepen preview editable title="" height="400px" tab="css,result" theme="dark"](user/slash)
|
||||
*/
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
||||
import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { parseRect } from '../utils/parseRect.js'
|
||||
import type { CodepenTokenMeta } from '../../shared/codepen.js'
|
||||
import { createRuleBlock } from '../utils/createRuleBlock.js'
|
||||
|
||||
const CODEPEN_LINK = 'https://codepen.io/'
|
||||
|
||||
// @[codepen]()
|
||||
const MIN_LENGTH = 12
|
||||
|
||||
// char codes of `@[codepen`
|
||||
const START_CODES = [64, 91, 99, 111, 100, 101, 112, 101, 110]
|
||||
|
||||
// regexp to match the import syntax
|
||||
const SYNTAX_RE = /^@\[codepen(?:\s+([^]*?))?\]\(([^)]*?)\)/
|
||||
|
||||
function createCodepenRuleBlock(): 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 [, info = '', source] = match
|
||||
|
||||
const { attrs } = resolveAttrs(info)
|
||||
const [user, slash] = source.split('/')
|
||||
|
||||
const meta: CodepenTokenMeta = {
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '400px',
|
||||
user,
|
||||
slash,
|
||||
title: attrs.title,
|
||||
preview: attrs.preview,
|
||||
editable: attrs.editable,
|
||||
tab: attrs.tab ?? 'result',
|
||||
theme: attrs.theme,
|
||||
}
|
||||
|
||||
const token = state.push('codepen', '', 0)
|
||||
|
||||
token.meta = meta
|
||||
token.map = [startLine, startLine + 1]
|
||||
token.info = info
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function resolveCodepen(meta: CodepenTokenMeta): string {
|
||||
const { title = 'Codepen', height, width } = meta
|
||||
const params = new URLSearchParams()
|
||||
meta.editable && params.set('editable', 'true')
|
||||
meta.tab && params.set('default-tab', meta.tab)
|
||||
meta.theme && params.set('theme-id', meta.theme)
|
||||
|
||||
const middle = meta.preview ? '/embed/preview/' : '/embed/'
|
||||
|
||||
const link = `${CODEPEN_LINK}${meta.user}${middle}${meta.slash}?${params.toString()}`
|
||||
const style = `width:${width};height:${height};margin:16px auto;border-radius:5px;`
|
||||
|
||||
return `<iframe class="code-pen-iframe-wrapper" src="${link}" title="${title}" style="${style}" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">See the Pen <a href="${CODEPEN_LINK}${meta.user}/pen/${meta.slash}">${title}</a> by ${meta.user} (<a href="${CODEPEN_LINK}${meta.user}">@${meta.user}</a>) on <a href="${CODEPEN_LINK}">CodePen</a>.</iframe>`
|
||||
}
|
||||
|
||||
export const codepenPlugin: PluginWithOptions<never> = (md) => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'codepen',
|
||||
createCodepenRuleBlock(),
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||
createRuleBlock<CodepenTokenMeta>(md, {
|
||||
type: 'codepen',
|
||||
syntaxPattern: /^@\[codepen(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
||||
meta: ([, info = '', source = '']) => {
|
||||
const { attrs } = resolveAttrs(info)
|
||||
const [user, slash] = source.split('/')
|
||||
|
||||
return {
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '400px',
|
||||
user,
|
||||
slash,
|
||||
title: attrs.title,
|
||||
preview: attrs.preview,
|
||||
editable: attrs.editable,
|
||||
tab: attrs.tab ?? 'result',
|
||||
theme: attrs.theme,
|
||||
}
|
||||
},
|
||||
)
|
||||
content: (meta) => {
|
||||
const { title = 'Codepen', height, width } = meta
|
||||
const params = new URLSearchParams()
|
||||
meta.editable && params.set('editable', 'true')
|
||||
meta.tab && params.set('default-tab', meta.tab)
|
||||
meta.theme && params.set('theme-id', meta.theme)
|
||||
|
||||
md.renderer.rules.codepen = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
const middle = meta.preview ? '/embed/preview/' : '/embed/'
|
||||
|
||||
const content = resolveCodepen(token.meta)
|
||||
token.content = content
|
||||
const link = `${CODEPEN_LINK}${meta.user}${middle}${meta.slash}?${params.toString()}`
|
||||
const style = `width:${width};height:${height};margin:16px auto;border-radius:5px;`
|
||||
|
||||
return content
|
||||
}
|
||||
return `<iframe class="code-pen-iframe-wrapper" src="${link}" title="${title}" style="${style}" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">See the Pen <a href="${CODEPEN_LINK}${meta.user}/pen/${meta.slash}">${title}</a> by ${meta.user} (<a href="${CODEPEN_LINK}${meta.user}">@${meta.user}</a>) on <a href="${CODEPEN_LINK}">CodePen</a>.</iframe>`
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -3,99 +3,35 @@
|
||||
* @[jsfiddle theme="dark" tab="js,css,html,result"](user/id)
|
||||
*/
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
||||
import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { parseRect } from '../utils/parseRect.js'
|
||||
import type { JSFiddleTokenMeta } from '../../shared/jsfiddle.js'
|
||||
|
||||
const JS_FIDDLE_LINK = 'https://jsfiddle.net/'
|
||||
|
||||
// @[jsfiddle]()
|
||||
const MIN_LENGTH = 13
|
||||
|
||||
// char codes of `@[jsfiddle`
|
||||
const START_CODES = [64, 91, 106, 115, 102, 105, 100, 100, 108, 101]
|
||||
|
||||
// regexp to match the import syntax
|
||||
const SYNTAX_RE = /^@\[jsfiddle(?:\s+([^]*?))?\]\(([^)]*?)\)/
|
||||
|
||||
function createJsFiddleRuleBlock(): 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 [, info = '', source] = match
|
||||
|
||||
const { attrs } = resolveAttrs(info)
|
||||
const [user, id] = source.split('/')
|
||||
|
||||
const meta: JSFiddleTokenMeta = {
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '400px',
|
||||
user,
|
||||
id,
|
||||
title: attrs.title || 'JS Fiddle',
|
||||
tab: attrs.tab?.replace(/\s+/g, '') || 'js,css,html,result',
|
||||
theme: attrs.theme || 'dark',
|
||||
}
|
||||
|
||||
const token = state.push('js_fiddle', '', 0)
|
||||
|
||||
token.meta = meta
|
||||
token.map = [startLine, startLine + 1]
|
||||
token.info = info
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function resolveJsFiddle(meta: JSFiddleTokenMeta): string {
|
||||
const { title = 'JS Fiddle', height, width, user, id, tab } = meta
|
||||
const theme = meta.theme === 'dark' ? '/dark/' : ''
|
||||
|
||||
const link = `${JS_FIDDLE_LINK}${user}/${id}/embedded/${tab}${theme}`
|
||||
const style = `width:${width};height:${height};margin:16px auto;border:none;border-radius:5px;`
|
||||
|
||||
return `<iframe class="js-fiddle-iframe-wrapper" style="${style}" title="${title}" src="${link}" allowfullscreen="true" allowpaymentrequest="true"></iframe>`
|
||||
}
|
||||
import { createRuleBlock } from '../utils/createRuleBlock.js'
|
||||
|
||||
export const jsfiddlePlugin: PluginWithOptions<never> = (md) => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'js_fiddle',
|
||||
createJsFiddleRuleBlock(),
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||
createRuleBlock<JSFiddleTokenMeta>(md, {
|
||||
type: 'jsfiddle',
|
||||
syntaxPattern: /^@\[jsfiddle(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
||||
meta([, info = '', source]) {
|
||||
const { attrs } = resolveAttrs(info)
|
||||
const [user, id] = source.split('/')
|
||||
|
||||
return {
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '400px',
|
||||
user,
|
||||
id,
|
||||
title: attrs.title || 'JS Fiddle',
|
||||
tab: attrs.tab?.replace(/\s+/g, '') || 'js,css,html,result',
|
||||
theme: attrs.theme || 'dark',
|
||||
}
|
||||
},
|
||||
)
|
||||
content: ({ title = 'JS Fiddle', height, width, user, id, tab, theme }) => {
|
||||
theme = theme === 'dark' ? '/dark/' : ''
|
||||
|
||||
md.renderer.rules.js_fiddle = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
|
||||
const content = resolveJsFiddle(token.meta)
|
||||
token.content = content
|
||||
|
||||
return content
|
||||
}
|
||||
const link = `https://jsfiddle.net/${user}/${id}/embedded/${tab}${theme}`
|
||||
const style = `width:${width};height:${height};margin:16px auto;border:none;border-radius:5px;`
|
||||
return `<iframe class="js-fiddle-iframe-wrapper" style="${style}" title="${title}" src="${link}" allowfullscreen="true" allowpaymentrequest="true"></iframe>`
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,93 +5,30 @@
|
||||
*/
|
||||
import { path } from 'vuepress/utils'
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
||||
import type { PDFTokenMeta } from '../../shared/pdf.js'
|
||||
import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { parseRect } from '../utils/parseRect.js'
|
||||
|
||||
// @[pdf]()
|
||||
const MIN_LENGTH = 8
|
||||
|
||||
// char codes of `@[pdf`
|
||||
const START_CODES = [64, 91, 112, 100, 102]
|
||||
|
||||
// regexp to match the import syntax
|
||||
const SYNTAX_RE = /^@\[pdf(?:\s+(\d+))?(?:\s+([^]*?))?\]\(([^)]*?)\)/
|
||||
|
||||
function createPDFRuleBlock(): 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 [, page, info = '', src] = match
|
||||
|
||||
const { attrs } = resolveAttrs(info)
|
||||
|
||||
const meta: PDFTokenMeta = {
|
||||
src,
|
||||
page: +page || 1,
|
||||
noToolbar: Boolean(attrs.noToolbar ?? false),
|
||||
zoom: +attrs.zoom || 50,
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '',
|
||||
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
||||
title: path.basename(src || ''),
|
||||
}
|
||||
|
||||
const token = state.push('pdf', '', 0)
|
||||
|
||||
token.meta = meta
|
||||
token.map = [startLine, startLine + 1]
|
||||
token.info = info
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePDF(meta: PDFTokenMeta): string {
|
||||
const { title, src, page, noToolbar, width, height, ratio, zoom } = meta
|
||||
|
||||
return `<PDFViewer src="${src}" title="${title}" :page="${page}" :no-toolbar="${noToolbar}" width="${width}" height="${height}" ratio="${ratio}" :zoom="${zoom}" />`
|
||||
}
|
||||
import { createRuleBlock } from '../utils/createRuleBlock.js'
|
||||
|
||||
export const pdfPlugin: PluginWithOptions<never> = (md) => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'pdf',
|
||||
createPDFRuleBlock(),
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||
createRuleBlock<PDFTokenMeta>(md, {
|
||||
type: 'pdf',
|
||||
syntaxPattern: /^@\[pdf(?:\s+(\d+))?(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
||||
meta([, page, info = '', src = '']) {
|
||||
const { attrs } = resolveAttrs(info)
|
||||
return {
|
||||
src,
|
||||
page: +page || 1,
|
||||
noToolbar: Boolean(attrs.noToolbar ?? false),
|
||||
zoom: +attrs.zoom || 50,
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '',
|
||||
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
||||
title: path.basename(src || ''),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
md.renderer.rules.pdf = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
|
||||
const content = resolvePDF(token.meta)
|
||||
token.content = content
|
||||
|
||||
return content
|
||||
}
|
||||
content({ title, src, page, noToolbar, width, height, ratio, zoom }) {
|
||||
return `<PDFViewer src="${src}" title="${title}" :page="${page}" :no-toolbar="${noToolbar}" width="${width}" height="${height}" ratio="${ratio}" :zoom="${zoom}" />`
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,90 +4,27 @@
|
||||
* @[replit title="" height="400px" width="100%" theme="dark"](user/repl-name)
|
||||
*/
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
||||
import { resolveAttrs } from '../utils/resolveAttrs.js'
|
||||
import { parseRect } from '../utils/parseRect.js'
|
||||
import type { ReplitTokenMeta } from '../../shared/replit.js'
|
||||
|
||||
// @[replit]()
|
||||
const MIN_LENGTH = 11
|
||||
|
||||
// char codes of `@[replit`
|
||||
const START_CODES = [64, 91, 114, 101, 112, 108, 105, 116]
|
||||
|
||||
// regexp to match the import syntax
|
||||
const SYNTAX_RE = /^@\[replit(?:\s+([^]*?))?\]\(([^)]*?)\)/
|
||||
|
||||
function createReplitRuleBlock(): 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 [, info = '', source] = match
|
||||
|
||||
const { attrs } = resolveAttrs(info)
|
||||
|
||||
const meta: ReplitTokenMeta = {
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '450px',
|
||||
source: source.startsWith('@') ? source : `@${source}`,
|
||||
title: attrs.title,
|
||||
theme: attrs.theme || '',
|
||||
}
|
||||
|
||||
const token = state.push('replit', '', 0)
|
||||
|
||||
token.meta = meta
|
||||
token.map = [startLine, startLine + 1]
|
||||
token.info = info
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function resolveReplit(meta: ReplitTokenMeta): string {
|
||||
const { title, height, width, source, theme } = meta
|
||||
|
||||
return `<ReplitViewer title="${title || ''}" height="${height}" width="${width}" source="${source}" theme="${theme}" />`
|
||||
}
|
||||
import { createRuleBlock } from '../utils/createRuleBlock.js'
|
||||
|
||||
export const replitPlugin: PluginWithOptions<never> = (md) => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'replit',
|
||||
createReplitRuleBlock(),
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||
createRuleBlock<ReplitTokenMeta>(md, {
|
||||
type: 'replit',
|
||||
syntaxPattern: /^@\[replit(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
||||
meta: ([, info = '', source = '']) => {
|
||||
const { attrs } = resolveAttrs(info)
|
||||
return {
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '450px',
|
||||
source: source.startsWith('@') ? source : `@${source}`,
|
||||
title: attrs.title,
|
||||
theme: attrs.theme || '',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
md.renderer.rules.replit = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
|
||||
const content = resolveReplit(token.meta)
|
||||
token.content = content
|
||||
|
||||
return content
|
||||
}
|
||||
content({ title, height, width, source, theme }) {
|
||||
return `<ReplitViewer title="${title || ''}" height="${height}" width="${width}" source="${source}" theme="${theme}" />`
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -6,110 +6,51 @@
|
||||
*/
|
||||
import { URLSearchParams } from 'node:url'
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
||||
import type { BilibiliTokenMeta } from '../../../shared/video.js'
|
||||
import { resolveAttrs } from '../../utils/resolveAttrs.js'
|
||||
import { parseRect } from '../../utils/parseRect.js'
|
||||
import { timeToSeconds } from '../../utils/timeToSeconds.js'
|
||||
import { createRuleBlock } from '../../utils/createRuleBlock.js'
|
||||
|
||||
const BILIBILI_LINK = 'https://player.bilibili.com/player.html'
|
||||
|
||||
// @[bilibili]()
|
||||
const MIN_LENGTH = 13
|
||||
|
||||
// char codes of '@[bilibili'
|
||||
const START_CODES = [64, 91, 98, 105, 108, 105, 98, 105, 108, 105]
|
||||
|
||||
// regexp to match the import syntax
|
||||
const SYNTAX_RE = /^@\[bilibili(?:\s+p(\d+))?(?:\s+([^]*?))?\]\(([^)]*)\)/
|
||||
|
||||
function createBilibiliRuleBlock(): 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 [, page, info = '', source = ''] = match
|
||||
|
||||
const { attrs } = resolveAttrs(info)
|
||||
const ids = source.trim().split(/\s+/)
|
||||
const bvid = ids.find(id => id.startsWith('BV'))
|
||||
const [aid, cid] = ids.filter(id => !id.startsWith('BV'))
|
||||
|
||||
const meta: BilibiliTokenMeta = {
|
||||
page: +page || 1,
|
||||
bvid,
|
||||
aid,
|
||||
cid,
|
||||
autoplay: attrs.autoplay ?? false,
|
||||
time: timeToSeconds(attrs.time),
|
||||
title: attrs.title,
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '',
|
||||
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
||||
}
|
||||
|
||||
const token = state.push('video_bilibili', '', 0)
|
||||
|
||||
token.meta = meta
|
||||
token.map = [startLine, startLine + 1]
|
||||
token.info = info
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBilibili(meta: BilibiliTokenMeta): string {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
meta.bvid && params.set('bvid', meta.bvid)
|
||||
meta.aid && params.set('aid', meta.aid)
|
||||
meta.cid && params.set('cid', meta.cid)
|
||||
meta.page && params.set('p', meta.page.toString())
|
||||
meta.time && params.set('t', meta.time.toString())
|
||||
params.set('autoplay', meta.autoplay ? '1' : '0')
|
||||
|
||||
const source = `${BILIBILI_LINK}?${params.toString()}`
|
||||
|
||||
return `<VideoBilibili src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`
|
||||
}
|
||||
|
||||
export const bilibiliPlugin: PluginWithOptions<never> = (md) => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'video_bilibili',
|
||||
createBilibiliRuleBlock(),
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||
createRuleBlock<BilibiliTokenMeta>(md, {
|
||||
type: 'bilibili',
|
||||
name: 'video_bilibili',
|
||||
syntaxPattern: /^@\[bilibili(?:\s+p(\d+))?(?:\s+([^]*?))?\]\(([^)]*)\)/,
|
||||
meta([, page, info = '', source = '']) {
|
||||
const { attrs } = resolveAttrs(info)
|
||||
const ids = source.trim().split(/\s+/)
|
||||
const bvid = ids.find(id => id.startsWith('BV'))
|
||||
const [aid, cid] = ids.filter(id => !id.startsWith('BV'))
|
||||
|
||||
return {
|
||||
page: +page || 1,
|
||||
bvid,
|
||||
aid,
|
||||
cid,
|
||||
autoplay: attrs.autoplay ?? false,
|
||||
time: timeToSeconds(attrs.time),
|
||||
title: attrs.title,
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '',
|
||||
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
||||
}
|
||||
},
|
||||
)
|
||||
content(meta) {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
md.renderer.rules.video_bilibili = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
meta.bvid && params.set('bvid', meta.bvid)
|
||||
meta.aid && params.set('aid', meta.aid)
|
||||
meta.cid && params.set('cid', meta.cid)
|
||||
meta.page && params.set('p', meta.page.toString())
|
||||
meta.time && params.set('t', meta.time.toString())
|
||||
params.set('autoplay', meta.autoplay ? '1' : '0')
|
||||
|
||||
const content = resolveBilibili(token.meta)
|
||||
token.content = content
|
||||
const source = `${BILIBILI_LINK}?${params.toString()}`
|
||||
|
||||
return content
|
||||
}
|
||||
return `<VideoBilibili src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -3,104 +3,45 @@
|
||||
*/
|
||||
import { URLSearchParams } from 'node:url'
|
||||
import type { PluginWithOptions } from 'markdown-it'
|
||||
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
||||
import type { YoutubeTokenMeta } from '../../../shared/video.js'
|
||||
import { resolveAttrs } from '../../utils/resolveAttrs.js'
|
||||
import { parseRect } from '../../utils/parseRect.js'
|
||||
import { timeToSeconds } from '../../utils/timeToSeconds.js'
|
||||
import { createRuleBlock } from '../../utils/createRuleBlock.js'
|
||||
|
||||
const YOUTUBE_LINK = 'https://www.youtube.com/embed/'
|
||||
|
||||
// @[youtube]()
|
||||
const MIN_LENGTH = 13
|
||||
|
||||
// char codes of '@[youtube'
|
||||
const START_CODES = [64, 91, 121, 111, 117, 116, 117, 98, 101]
|
||||
|
||||
// regexp to match the import syntax
|
||||
const SYNTAX_RE = /^@\[youtube(?:\s+([^]*?))?\]\(([^)]*)\)/
|
||||
|
||||
function createYoutubeRuleBlock(): 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 [, info = '', id = ''] = match
|
||||
|
||||
const { attrs } = resolveAttrs(info)
|
||||
|
||||
const meta: YoutubeTokenMeta = {
|
||||
id,
|
||||
autoplay: attrs.autoplay ?? false,
|
||||
loop: attrs.loop ?? false,
|
||||
start: timeToSeconds(attrs.start),
|
||||
end: timeToSeconds(attrs.end),
|
||||
title: attrs.title,
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '',
|
||||
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
||||
}
|
||||
|
||||
const token = state.push('video_youtube', '', 0)
|
||||
|
||||
token.meta = meta
|
||||
token.map = [startLine, startLine + 1]
|
||||
token.info = info
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function resolveYoutube(meta: YoutubeTokenMeta): string {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
meta.autoplay && params.set('autoplay', '1')
|
||||
meta.loop && params.set('loop', '1')
|
||||
meta.start && params.set('start', meta.start.toString())
|
||||
meta.end && params.set('end', meta.end.toString())
|
||||
|
||||
const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`
|
||||
|
||||
return `<VideoYoutube src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`
|
||||
}
|
||||
|
||||
export const youtubePlugin: PluginWithOptions<never> = (md) => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'video_youtube',
|
||||
createYoutubeRuleBlock(),
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||
createRuleBlock<YoutubeTokenMeta>(md, {
|
||||
type: 'youtube',
|
||||
name: 'video_youtube',
|
||||
syntaxPattern: /^@\[youtube(?:\s+([^]*?))?\]\(([^)]*)\)/,
|
||||
meta([, info = '', id = '']) {
|
||||
const { attrs } = resolveAttrs(info)
|
||||
|
||||
return {
|
||||
id,
|
||||
autoplay: attrs.autoplay ?? false,
|
||||
loop: attrs.loop ?? false,
|
||||
start: timeToSeconds(attrs.start),
|
||||
end: timeToSeconds(attrs.end),
|
||||
title: attrs.title,
|
||||
width: attrs.width ? parseRect(attrs.width) : '100%',
|
||||
height: attrs.height ? parseRect(attrs.height) : '',
|
||||
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
||||
}
|
||||
},
|
||||
)
|
||||
content(meta) {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
md.renderer.rules.video_youtube = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
meta.autoplay && params.set('autoplay', '1')
|
||||
meta.loop && params.set('loop', '1')
|
||||
meta.start && params.set('start', meta.start.toString())
|
||||
meta.end && params.set('end', meta.end.toString())
|
||||
|
||||
const content = resolveYoutube(token.meta)
|
||||
token.content = content
|
||||
const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`
|
||||
|
||||
return content
|
||||
}
|
||||
return `<VideoYoutube src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
79
plugins/plugin-md-power/src/node/utils/createRuleBlock.ts
Normal file
79
plugins/plugin-md-power/src/node/utils/createRuleBlock.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import type { RuleOptions } from 'markdown-it/lib/ruler.mjs'
|
||||
import type { Markdown } from 'vuepress/markdown'
|
||||
|
||||
export interface RuleBlockOptions<Meta extends Record<string, any>> {
|
||||
/**
|
||||
* @[type]()
|
||||
*/
|
||||
type: string
|
||||
/**
|
||||
* token name
|
||||
*/
|
||||
name?: string
|
||||
beforeName?: string
|
||||
syntaxPattern: RegExp
|
||||
ruleOptions?: RuleOptions
|
||||
meta: (match: RegExpMatchArray) => Meta
|
||||
content: (meta: Meta) => string
|
||||
}
|
||||
|
||||
// @[name]()
|
||||
export function createRuleBlock<Meta extends Record<string, any> = Record<string, any>>(
|
||||
md: Markdown,
|
||||
{
|
||||
type,
|
||||
name = type,
|
||||
syntaxPattern,
|
||||
beforeName = 'import_code',
|
||||
ruleOptions = { alt: ['paragraph', 'reference', 'blockquote', 'list'] },
|
||||
meta,
|
||||
content,
|
||||
}: RuleBlockOptions<Meta>,
|
||||
): void {
|
||||
const MIN_LENGTH = type.length + 5
|
||||
const START_CODES = [64, 91, ...type.split('').map(c => c.charCodeAt(0))]
|
||||
|
||||
md.block.ruler.before(
|
||||
beforeName,
|
||||
name,
|
||||
(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(syntaxPattern)
|
||||
if (!match)
|
||||
return false
|
||||
|
||||
// return true as we have matched the syntax
|
||||
if (silent)
|
||||
return true
|
||||
|
||||
const token = state.push(name, '', 0)
|
||||
|
||||
token.meta = meta(match)
|
||||
token.map = [startLine, startLine + 1]
|
||||
|
||||
state.line = startLine + 1
|
||||
|
||||
return true
|
||||
},
|
||||
ruleOptions,
|
||||
)
|
||||
|
||||
md.renderer.rules[name] = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
token.content = content(token.meta)
|
||||
return token.content
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,11 @@ defineProps<{
|
||||
{{ group.desc }}
|
||||
</p>
|
||||
<section v-if="group.list?.length" class="friends-list">
|
||||
<FriendsItem v-for="(friend, index) in group.list" :key="friend.name + index" :friend="friend" />
|
||||
<FriendsItem
|
||||
v-for="(friend, index) in group.list"
|
||||
:key="friend.name + index"
|
||||
:friend="friend"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -14,4 +14,3 @@ const { resolve: onBeforeEnter, pending: onBeforeLeave } = useScrollPromise()
|
||||
<slot />
|
||||
</Transition>
|
||||
</template>
|
||||
../composables/scroll-promise.js
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
import { useRouteLocale, withBase } from 'vuepress/client'
|
||||
import LayoutContent from '../components/LayoutContent.vue'
|
||||
import Nav from '../components/Nav/index.vue'
|
||||
import { useThemeLocaleData } from '../composables'
|
||||
import { useData } from '../composables/data.js'
|
||||
|
||||
const root = useRouteLocale()
|
||||
const themeData = useThemeLocaleData()
|
||||
const { theme } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -14,19 +14,19 @@ const themeData = useThemeLocaleData()
|
||||
<LayoutContent is-not-found>
|
||||
<div class="not-found">
|
||||
<p class="code">
|
||||
{{ themeData.notFound?.code ?? '404' }}
|
||||
{{ theme.notFound?.code ?? '404' }}
|
||||
</p>
|
||||
<h1 class="title">
|
||||
{{ themeData.notFound?.title ?? 'PAGE NOT FOUND' }}
|
||||
{{ theme.notFound?.title ?? 'PAGE NOT FOUND' }}
|
||||
</h1>
|
||||
<div class="divider" />
|
||||
<blockquote class="quote">
|
||||
{{ themeData.notFound?.quote ?? `But if you don't change your direction, and if you keep looking, you may end up where you are heading.` }}
|
||||
{{ theme.notFound?.quote ?? `But if you don't change your direction, and if you keep looking, you may end up where you are heading.` }}
|
||||
</blockquote>
|
||||
|
||||
<div class="action">
|
||||
<a class="link" :href="withBase(root)" :aria-label="themeData.notFound?.linkLabel ?? 'go to home'">
|
||||
{{ themeData.notFound?.linkText ?? 'Take me home' }}
|
||||
<a class="link" :href="withBase(root)" :aria-label="theme.notFound?.linkLabel ?? 'go to home'">
|
||||
{{ theme.notFound?.linkText ?? 'Take me home' }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user