perf(plugin-md-power): improve code embed (#497)

This commit is contained in:
pengzhanbo 2025-03-02 00:06:40 +08:00 committed by GitHub
parent 5ee3bc4d2f
commit 99ba7be92d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 145 additions and 81 deletions

View File

@ -1,9 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`codepenPlugin > should not work 1`] = `
"<iframe class="code-pen-iframe-wrapper" src="https://codepen.io//embed/undefined?default-tab=result" title="Codepen" style="width:100%;height:400px;margin:16px auto;border-radius:5px;" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">See the Pen <a href="https://codepen.io//pen/undefined">Codepen</a> by (<a href="https://codepen.io/">@</a>) on <a href="https://codepen.io/">CodePen</a>.</iframe><p>@[codepen]xxx</p>
"<CodePenViewer user="" slash="undefined" title="Code Pen" tab="result" width="100%" height="400px" /><p>@[codepen]xxx</p>
<p>@[codepen preview](</p>
"
`;
exports[`codepenPlugin > should work 1`] = `"<iframe class="code-pen-iframe-wrapper" src="https://codepen.io/user/embed/slash?default-tab=result" title="Codepen" style="width:100%;height:400px;margin:16px auto;border-radius:5px;" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">See the Pen <a href="https://codepen.io/user/pen/slash">Codepen</a> by user (<a href="https://codepen.io/user">@user</a>) on <a href="https://codepen.io/">CodePen</a>.</iframe><iframe class="code-pen-iframe-wrapper" src="https://codepen.io/user/embed/preview/slash?default-tab=result" title="Codepen" style="width:100%;height:400px;margin:16px auto;border-radius:5px;" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">See the Pen <a href="https://codepen.io/user/pen/slash">Codepen</a> by user (<a href="https://codepen.io/user">@user</a>) on <a href="https://codepen.io/">CodePen</a>.</iframe><iframe class="code-pen-iframe-wrapper" src="https://codepen.io/user/embed/preview/slash?editable=true&default-tab=css%2Cresult&theme-id=dark" title="codepen" style="width:100%;height:400px;margin:16px auto;border-radius:5px;" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">See the Pen <a href="https://codepen.io/user/pen/slash">codepen</a> by user (<a href="https://codepen.io/user">@user</a>) on <a href="https://codepen.io/">CodePen</a>.</iframe>"`;
exports[`codepenPlugin > should work 1`] = `"<CodePenViewer user="user" slash="slash" title="Code Pen" tab="result" width="100%" height="400px" /><CodePenViewer user="user" slash="slash" title="Code Pen" preview tab="result" width="100%" height="400px" /><CodePenViewer user="user" slash="slash" title="codepen" preview editable tab="css,result" theme="dark" width="100%" height="400px" />"`;

View File

@ -1,9 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`codeSandboxPlugin > should not work 1`] = `
"<iframe class="js-fiddle-iframe-wrapper" style="width:100%;height:400px;margin:16px auto;border:none;border-radius:5px;" title="JS Fiddle" src="https://jsfiddle.net//undefined/embedded/js,css,html,result/dark/" allowfullscreen="true" allowpaymentrequest="true"></iframe><p>@[jsfiddle]xxx</p>
"<JSFiddleViewer source="" title="JS Fiddle" tab="js,css,html,result" width="100%" height="400px" /><p>@[jsfiddle]xxx</p>
<p>@[jsfiddle](</p>
"
`;
exports[`codeSandboxPlugin > should work 1`] = `"<iframe class="js-fiddle-iframe-wrapper" style="width:100%;height:400px;margin:16px auto;border:none;border-radius:5px;" title="JS Fiddle" src="https://jsfiddle.net/user/id/embedded/js,css,html,result/dark/" allowfullscreen="true" allowpaymentrequest="true"></iframe><iframe class="js-fiddle-iframe-wrapper" style="width:100%;height:400px;margin:16px auto;border:none;border-radius:5px;" title="JS Fiddle" src="https://jsfiddle.net/user/id/embedded/js,css,html,result/dark/" allowfullscreen="true" allowpaymentrequest="true"></iframe><iframe class="js-fiddle-iframe-wrapper" style="width:100%;height:400px;margin:16px auto;border:none;border-radius:5px;" title="JS Fiddle" src="https://jsfiddle.net/user/id/embedded/js,css,html,result" allowfullscreen="true" allowpaymentrequest="true"></iframe><iframe class="js-fiddle-iframe-wrapper" style="width:100%;height:500px;margin:16px auto;border:none;border-radius:5px;" title="xxx" src="https://jsfiddle.net/user/id/embedded/js,css,html,result/dark/" allowfullscreen="true" allowpaymentrequest="true"></iframe>"`;
exports[`codeSandboxPlugin > should work 1`] = `"<JSFiddleViewer source="user/id" title="JS Fiddle" tab="js,css,html,result" width="100%" height="400px" /><JSFiddleViewer source="user/id" title="JS Fiddle" tab="js,css,html,result" width="100%" height="400px" /><JSFiddleViewer source="user/id" title="JS Fiddle" tab="js,css,html,result" width="100%" height="400px" theme="light" /><JSFiddleViewer source="user/id" title="xxx" tab="js,css,html,result" width="100%" height="500px" theme="dark" />"`;

View File

@ -82,6 +82,12 @@ useEventListener('scroll', updatePosition, { passive: true })
transform: rotate(0deg);
}
@media print {
.vpi-annotation {
display: none;
}
}
.vp-annotation.active {
z-index: 10;
}

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { useDarkMode } from '@vuepress/helper/client'
import { useEventListener } from '@vueuse/core'
import { computed, getCurrentInstance, ref } from 'vue'
import { computed, ref } from 'vue'
interface MessageData {
type: string
@ -23,11 +24,10 @@ const props = withDefaults(defineProps<{
})
const url = 'https://caniuse.pengzhanbo.cn/'
const current = getCurrentInstance()
const height = ref('330px')
const isDark = computed(() => current?.appContext.config.globalProperties.$isDark.value)
const isDark = useDarkMode()
const source = computed(() => {
const source = `${url}${props.feature}#past=${props.past}&future=${props.future}&meta=${props.meta}&theme=${isDark.value ? 'dark' : 'light'}`

View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import { useDarkMode } from '@vuepress/helper/client'
import { computed } from 'vue'
const props = defineProps<{
user: string
slash: string
title?: string
preview?: boolean
editable?: boolean
tab?: string
theme?: 'light' | 'dark'
width?: string
height?: string
}>()
const CODEPEN_LINK = 'https://codepen.io/'
const isDark = useDarkMode()
const link = computed(() => {
const middle = props.preview ? '/embed/preview/' : '/embed/'
const params = new URLSearchParams()
props.editable && params.set('editable', 'true')
props.tab && params.set('default-tab', props.tab)
const theme = props.theme ?? (isDark.value ? 'dark' : 'light')
theme && params.set('theme-id', theme)
return `${CODEPEN_LINK}${props.user}${middle}${props.slash}?${params.toString()}`
})
</script>
<template>
<iframe
:src="link"
class="code-pen-iframe"
:title="title"
:style="{ width, height }"
frameborder="0"
loading="lazy"
allowtransparency="true"
allowfullscreen="true"
/>
</template>
<style>
.code-pen-iframe {
margin: 16px auto;
border: none;
box-shadow: var(--vp-shadow-2);
}
</style>

View File

@ -0,0 +1,40 @@
<script setup lang="ts">
import { useDarkMode } from '@vuepress/helper/client'
import { computed } from 'vue'
const props = defineProps<{
source: string
title?: string
tab: string
theme?: 'light' | 'dark'
width?: string
height?: string
}>()
const isDark = useDarkMode()
const link = computed(() => {
const theme = props.theme === 'dark' ? '/dark/' : isDark.value ? '/dark/' : ''
return `https://jsfiddle.net/${props.source}/embedded/${props.tab}${theme}`
})
</script>
<template>
<iframe
class="js-fiddle-iframe"
:src="link"
:title="title"
:style="{ width, height }"
frameborder="0"
allowfullscreen="true"
allowpaymentrequest="true"
/>
</template>
<style>
.js-fiddle-iframe {
margin: 16px auto;
border: none;
box-shadow: var(--vp-shadow-2);
}
</style>

View File

@ -1,18 +1,18 @@
<script setup lang="ts">
import type { ReplitTokenMeta } from '../../shared/index.js'
import { computed, getCurrentInstance, ref } from 'vue'
import { useDarkMode } from '@vuepress/helper/client'
import { computed, ref } from 'vue'
import Loading from './icons/Loading.vue'
const props = defineProps<ReplitTokenMeta>()
const current = getCurrentInstance()
// magic height
const height = ref('47px')
const loaded = ref(false)
const REPLIT_LINK = 'https://replit.com/'
const isDark = computed(() => current?.appContext.config.globalProperties.$isDark.value)
const isDark = useDarkMode()
const link = computed(() => {
const url = new URL(`/${props.source}`, REPLIT_LINK)
@ -50,7 +50,7 @@ function onload() {
width: 100%;
margin: 16px auto;
border: none;
border-top: 1px solid var(--vp-c-divider, #e2e2e3);
border-top: 1px solid var(--vp-c-divider);
border-bottom-right-radius: 8px;
border-bottom-left-radius: 8px;
transition: border 0.25s;

View File

@ -12,10 +12,10 @@
*/
import type { PDFEmbedType, PDFTokenMeta } from '../../shared/index.js'
import { checkIsiPad, checkIsMobile, checkIsSafari } from '@vuepress/helper/client'
import { withBase } from 'vuepress/client'
import { ensureEndingSlash, isLinkHttp } from 'vuepress/shared'
import { pluginOptions } from '../options.js'
import { checkIsiPad, checkIsMobile, checkIsSafari } from '../utils/is.js'
function queryStringify(options: PDFTokenMeta): string {
const { page, noToolbar, zoom } = options

View File

@ -1,15 +0,0 @@
export function checkIsMobile(ua: string): boolean {
return /\b(?:Android|iPhone)/i.test(ua)
}
export function checkIsSafari(ua: string): boolean {
return /version\/[\w.]+ .*(?:mobile ?safari|safari)/i.test(ua)
}
export function checkIsiPad(ua: string): boolean {
return [
/\((ipad);[-\w),; ]+apple/i,
/applecoremedia\/[\w.]+ \((ipad)/i,
/\b(ipad)\d\d?,\d\d?[;\]].+ios/i,
].some(item => item.test(ua))
}

View File

@ -30,9 +30,9 @@ export const codeTabs: PluginWithOptions<CodeTabsOptions> = (md, options: CodeTa
tab(md, {
name: 'code-tabs',
tabsOpenRenderer: ({ active, data }, tokens, index) => {
tabsOpenRenderer: ({ active, data }, tokens, index, _, env) => {
const { meta } = tokens[index]
const titles = data.map(({ title }) => md.renderInline(title))
const titles = data.map(({ title }) => md.renderInline(title, env))
const tabsData = data.map((item, dataIndex) => {
const { id = titles[dataIndex] } = item

View File

@ -6,9 +6,9 @@ export const tabs: PluginSimple = (md) => {
tab(md, {
name: 'tabs',
tabsOpenRenderer: ({ active, data }, tokens, index) => {
tabsOpenRenderer: ({ active, data }, tokens, index, _, env) => {
const { meta } = tokens[index]
const titles = data.map(({ title }) => md.renderInline(title))
const titles = data.map(({ title }) => md.renderInline(title, env))
const tabsData = data.map((item, dataIndex) => {
const { id = titles[dataIndex] } = item

View File

@ -9,48 +9,25 @@ import { parseRect } from '../../utils/parseRect.js'
import { resolveAttrs } from '../../utils/resolveAttrs.js'
import { createEmbedRuleBlock } from '../createEmbedRuleBlock.js'
const CODEPEN_LINK = 'https://codepen.io/'
export const codepenPlugin: PluginWithOptions<never> = (md) => {
createEmbedRuleBlock<CodepenTokenMeta>(md, {
type: 'codepen',
syntaxPattern: /^@\[codepen([^\]]*)\]\(([^)]*)\)/,
meta: ([, info, source]) => {
const { attrs } = resolveAttrs(info)
const { width, height, title, tab, ...rest } = resolveAttrs<CodepenTokenMeta>(info).attrs
const [user, slash] = source.split('/')
return {
width: attrs.width ? parseRect(attrs.width) : '100%',
height: attrs.height ? parseRect(attrs.height) : '400px',
width: width ? parseRect(width) : '100%',
height: height ? parseRect(height) : '400px',
user,
slash,
title: attrs.title,
preview: attrs.preview,
editable: attrs.editable,
tab: attrs.tab || 'result',
theme: attrs.theme,
title: title || 'Code Pen',
tab: tab || 'result',
...rest,
}
},
content: (meta) => {
const { title = 'Codepen', height, width } = meta
const params = new URLSearchParams()
if (meta.editable) {
params.set('editable', 'true')
}
params.set('default-tab', meta.tab!)
if (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>`
},
content: ({ title, height, width, user, slash, preview, editable, tab, theme }) =>
`<CodePenViewer user="${user}" slash="${slash}" title="${title}"${preview ? ' preview' : ''}${editable ? ' editable' : ''} tab="${tab}"${theme ? ` theme="${theme}"` : ''} width="${width}" height="${height}" />`,
})
}

View File

@ -13,25 +13,18 @@ export const jsfiddlePlugin: PluginWithOptions<never> = (md) => {
type: 'jsfiddle',
syntaxPattern: /^@\[jsfiddle([^\]]*)\]\(([^)]*)\)/,
meta([, info, source]) {
const { attrs } = resolveAttrs(info)
const [user, id] = source.split('/')
const { width, height, title, tab, theme } = resolveAttrs<JSFiddleTokenMeta>(info).attrs
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',
width: width ? parseRect(width) : '100%',
height: height ? parseRect(height) : '400px',
source,
title: title || 'JS Fiddle',
tab: tab?.replace(/\s+/g, '') || 'js,css,html,result',
theme,
}
},
content: ({ title, height, width, user, id, tab, theme }) => {
theme = theme === 'dark' ? '/dark/' : ''
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>`
},
content: ({ title, height, width, source, tab, theme }) =>
`<JSFiddleViewer source="${source}" title="${title}" tab="${tab}" width="${width}" height="${height}"${theme ? ` theme="${theme}"` : ''} />`,
})
}

View File

@ -35,6 +35,16 @@ export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOp
enhances.add(`app.component('VideoYoutube', Youtube)`)
}
if (options.codepen) {
imports.add(`import CodePen from '${CLIENT_FOLDER}components/CodePen.vue'`)
enhances.add(`app.component('CodePenViewer', CodePen)`)
}
if (options.jsfiddle) {
imports.add(`import JSFiddle from '${CLIENT_FOLDER}components/JSFiddle.vue'`)
enhances.add(`app.component('JSFiddleViewer', JSFiddle)`)
}
if (options.replit) {
imports.add(`import Replit from '${CLIENT_FOLDER}components/Replit.vue'`)
enhances.add(`app.component('ReplitViewer', Replit)`)

View File

@ -1,8 +1,7 @@
import type { SizeOptions } from './size'
export interface JSFiddleTokenMeta extends SizeOptions {
user?: string
id?: string
source: string
title?: string
theme?: string
tab?: string

View File

@ -4,7 +4,7 @@ import { argv } from '../../scripts/tsup-args.js'
const config = [
{ dir: 'composables', files: ['codeRepl.ts', 'pdf.ts', 'rustRepl.ts', 'size.ts', 'audio.ts', 'demo.ts'] },
{ dir: 'utils', files: ['http.ts', 'is.ts', 'link.ts', 'sleep.ts'] },
{ dir: 'utils', files: ['http.ts', 'link.ts', 'sleep.ts'] },
{ dir: '', files: ['index.ts', 'options.ts'] },
]