mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
feat(plugin-md-power): add @[replit](user/repl-name) syntax supported
This commit is contained in:
parent
9a441c0941
commit
07b911a5a3
54
plugins/plugin-md-power/src/client/components/Replit.vue
Normal file
54
plugins/plugin-md-power/src/client/components/Replit.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, getCurrentInstance, ref } from 'vue'
|
||||
import type { ReplitTokenMeta } from '../../shared/replit.js'
|
||||
|
||||
const props = defineProps<ReplitTokenMeta>()
|
||||
|
||||
const current = getCurrentInstance()
|
||||
// magic height
|
||||
const height = ref('47px')
|
||||
|
||||
const REPLIT_LINK = 'https://replit.com/'
|
||||
|
||||
const isDark = computed(() => current?.appContext.config.globalProperties.$isDark.value)
|
||||
|
||||
const link = computed(() => {
|
||||
const url = new URL(`/${props.source}`, REPLIT_LINK)
|
||||
url.searchParams.set('embed', 'true')
|
||||
|
||||
const theme = props.theme || (isDark.value ? 'dark' : 'light')
|
||||
url.searchParams.set('theme', theme)
|
||||
|
||||
return url.toString()
|
||||
})
|
||||
|
||||
function onload() {
|
||||
height.value = props.height || '450px'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<iframe
|
||||
class="replit-iframe-wrapper"
|
||||
:src="link"
|
||||
:title="title || 'Replit'"
|
||||
:style="{ width, height }"
|
||||
allowtransparency="true"
|
||||
allowfullscree="true"
|
||||
@load="onload"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.replit-iframe-wrapper {
|
||||
width: 100%;
|
||||
margin: 16px auto;
|
||||
border: none;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
transition: border 0.25s;
|
||||
}
|
||||
</style>
|
||||
93
plugins/plugin-md-power/src/node/features/replit.ts
Normal file
93
plugins/plugin-md-power/src/node/features/replit.ts
Normal file
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @[replit](user/repl-name)
|
||||
* @[replit](user/repl-name#filepath)
|
||||
* @[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.js'
|
||||
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}" />`
|
||||
}
|
||||
|
||||
export const replitPlugin: PluginWithOptions<never> = (md) => {
|
||||
md.block.ruler.before(
|
||||
'import_code',
|
||||
'replit',
|
||||
createReplitRuleBlock(),
|
||||
{
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list'],
|
||||
},
|
||||
)
|
||||
|
||||
md.renderer.rules.replit = (tokens, index) => {
|
||||
const token = tokens[index]
|
||||
|
||||
const content = resolveReplit(token.meta)
|
||||
token.content = content
|
||||
|
||||
return content
|
||||
}
|
||||
}
|
||||
7
plugins/plugin-md-power/src/shared/replit.ts
Normal file
7
plugins/plugin-md-power/src/shared/replit.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { SizeOptions } from './size'
|
||||
|
||||
export interface ReplitTokenMeta extends SizeOptions {
|
||||
title?: string
|
||||
source?: string
|
||||
theme?: string
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user