commit
44f51c3e65
@ -29,7 +29,13 @@ export const zhNotes = definePlumeNotesConfig({
|
|||||||
text: '代码块',
|
text: '代码块',
|
||||||
dir: '代码',
|
dir: '代码',
|
||||||
icon: 'ph:code-bold',
|
icon: 'ph:code-bold',
|
||||||
items: ['介绍', '特性支持', '代码组', '导入代码', 'codepen', 'jsFiddle', 'codeSandbox', 'replit', 'twoslash', '代码演示'],
|
items: ['介绍', '特性支持', '代码组', '导入代码', 'twoslash'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '代码演示',
|
||||||
|
dir: '代码演示',
|
||||||
|
icon: 'carbon:demo',
|
||||||
|
items: ['前端', 'rust', 'golang', 'kotlin', 'codepen', 'jsFiddle', 'codeSandbox', 'replit'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '图表',
|
text: '图表',
|
||||||
|
|||||||
@ -83,6 +83,7 @@ export const theme: Theme = themePlume({
|
|||||||
replit: true,
|
replit: true,
|
||||||
codeSandbox: true,
|
codeSandbox: true,
|
||||||
jsfiddle: true,
|
jsfiddle: true,
|
||||||
|
repl: true,
|
||||||
},
|
},
|
||||||
comment: {
|
comment: {
|
||||||
provider: 'Giscus',
|
provider: 'Giscus',
|
||||||
|
|||||||
@ -1,101 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { shallowRef } from 'vue'
|
||||||
import { onClickOutside, useLocalStorage, useThrottleFn } from '@vueuse/core'
|
import { useCaniuse, useCaniuseFeaturesSearch, useCaniuseVersionSelect } from '../composables/caniuse.js'
|
||||||
import { resolveCanIUse } from '../composables/caniuse.js'
|
|
||||||
import CodeViewer from './CodeViewer.vue'
|
import CodeViewer from './CodeViewer.vue'
|
||||||
|
|
||||||
interface Feature {
|
const listEl = shallowRef<HTMLUListElement | null>(null)
|
||||||
label: string
|
const inputEl = shallowRef<HTMLInputElement | null>(null)
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = 'https://api.pengzhanbo.cn/caniuse/features'
|
const { feature, featureList, onSelect, isFocus } = useCaniuseFeaturesSearch(inputEl, listEl)
|
||||||
|
const { past, pastList, future, futureList, embedType, embedTypeList } = useCaniuseVersionSelect()
|
||||||
const features = useLocalStorage('caniuse-features', [] as Feature[])
|
const { output, rendered } = useCaniuse({ feature, embedType, past, future })
|
||||||
onMounted(async () => {
|
|
||||||
const res = await fetch(api)
|
|
||||||
const data = await res.json()
|
|
||||||
features.value = data || features.value || []
|
|
||||||
})
|
|
||||||
|
|
||||||
const browserVersionList = ref([
|
|
||||||
{ label: '新版本(当前版本 + 3)', value: 3, checked: false },
|
|
||||||
{ label: '新版本(当前版本 + 2)', value: 2, checked: false },
|
|
||||||
{ label: '新版本(当前版本 + 1)', value: 1, checked: false },
|
|
||||||
{ label: '当前版本', value: 0, disabled: true, checked: true },
|
|
||||||
{ label: '旧版本(当前版本 - 1)', value: -1, checked: false },
|
|
||||||
{ label: '旧版本(当前版本 - 2)', value: -2, checked: false },
|
|
||||||
{ label: '旧版本(当前版本 - 3)', value: -3, checked: false },
|
|
||||||
{ label: '旧版本(当前版本 - 4)', value: -4, checked: false },
|
|
||||||
{ label: '旧版本(当前版本 - 5)', value: -5, checked: false },
|
|
||||||
])
|
|
||||||
|
|
||||||
const input = ref('')
|
|
||||||
const isFocus = ref(false)
|
|
||||||
const searched = ref<Feature[]>()
|
|
||||||
|
|
||||||
const selected = ref<Feature | null>(null)
|
|
||||||
const embedType = ref('')
|
|
||||||
const browserVersion = computed(() => {
|
|
||||||
const values = browserVersionList.value.filter(item => item.checked).map(item => item.value)
|
|
||||||
if (values.length === 1 && values[0] === 0)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
return values.join(',')
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => [features.value, isFocus.value], () => {
|
|
||||||
if (!isFocus.value)
|
|
||||||
searched.value = features.value
|
|
||||||
}, { immediate: true })
|
|
||||||
|
|
||||||
const listEl = ref<HTMLUListElement | null>(null)
|
|
||||||
const inputEl = ref<HTMLInputElement | null>(null)
|
|
||||||
onClickOutside(listEl, () => {
|
|
||||||
isFocus.value = false
|
|
||||||
}, { ignore: [inputEl] })
|
|
||||||
const onInput = useThrottleFn(() => {
|
|
||||||
selected.value = null
|
|
||||||
|
|
||||||
if (!input.value) {
|
|
||||||
searched.value = features.value
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
searched.value = features.value.filter(item => item.label.includes(input.value) || item.value.includes(input.value))
|
|
||||||
if (searched.value.length === 1)
|
|
||||||
selected.value = searched.value[0]
|
|
||||||
}
|
|
||||||
}, 300)
|
|
||||||
function onSelect(item: Feature) {
|
|
||||||
selected.value = item
|
|
||||||
input.value = item.label
|
|
||||||
isFocus.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = computed(() => {
|
|
||||||
let content = '@[caniuse'
|
|
||||||
if (embedType.value)
|
|
||||||
content += ` ${embedType.value}`
|
|
||||||
|
|
||||||
if (browserVersion.value && !embedType.value)
|
|
||||||
content += `{${browserVersion.value}}`
|
|
||||||
|
|
||||||
content += ']('
|
|
||||||
|
|
||||||
if (selected.value)
|
|
||||||
content += selected.value.value
|
|
||||||
|
|
||||||
return `${content})`
|
|
||||||
})
|
|
||||||
|
|
||||||
const rendered = ref('')
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
if (!selected.value)
|
|
||||||
return
|
|
||||||
|
|
||||||
rendered.value = resolveCanIUse(selected.value.value, embedType.value, browserVersion.value)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -105,12 +18,19 @@ function render() {
|
|||||||
<label for="feature">选择特性:</label>
|
<label for="feature">选择特性:</label>
|
||||||
<div class="feature-input">
|
<div class="feature-input">
|
||||||
<input
|
<input
|
||||||
ref="inputEl" v-model="input" class="feature-input__input" type="text" name="feature"
|
ref="inputEl"
|
||||||
placeholder="输入特性" @focus="isFocus = true" @input="onInput"
|
class="feature-input__input"
|
||||||
|
type="text"
|
||||||
|
name="feature"
|
||||||
|
placeholder="输入特性"
|
||||||
>
|
>
|
||||||
<span class="vpi-chevron-down" />
|
<span class="vpi-chevron-down" />
|
||||||
<ul v-show="isFocus" ref="listEl" class="feature-list">
|
<ul v-show="isFocus" ref="listEl" class="feature-list">
|
||||||
<li v-for="item in searched" :key="item.value" @click="onSelect(item)">
|
<li
|
||||||
|
v-for="item in featureList"
|
||||||
|
:key="item.value"
|
||||||
|
@click="onSelect(item)"
|
||||||
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -119,41 +39,35 @@ function render() {
|
|||||||
<div class="caniuse-form-item">
|
<div class="caniuse-form-item">
|
||||||
<label for="embedType">嵌入方式:</label>
|
<label for="embedType">嵌入方式:</label>
|
||||||
<div class="caniuse-embed-type">
|
<div class="caniuse-embed-type">
|
||||||
<label>
|
<label v-for="item in embedTypeList" :key="item.label">
|
||||||
<input type="radio" name="embedType" value="" :checked="embedType === ''" @click="embedType = ''">
|
<input v-model="embedType" type="radio" name="embedType" :value="item.value">
|
||||||
<span>iframe</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio" name="embedType" value="image" :checked="embedType === 'image'"
|
|
||||||
@click="embedType = 'image'"
|
|
||||||
> <span>image</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!embedType" class="caniuse-form-item">
|
|
||||||
<label for="browserVersion">浏览器版本:</label>
|
|
||||||
<div class="caniuse-browser-version">
|
|
||||||
<label v-for="item in browserVersionList" :key="item.value">
|
|
||||||
<input
|
|
||||||
v-model="item.checked" type="checkbox" name="browserVersion" :checked="item.checked"
|
|
||||||
:disabled="item.disabled"
|
|
||||||
>
|
|
||||||
<span>{{ item.label }}</span>
|
<span>{{ item.label }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="caniuse-render">
|
<div v-if="!embedType" class="caniuse-form-item">
|
||||||
<button class="caniuse-render-button" type="button" :disabled="!selected" @click="render">
|
<span>浏览器版本:</span>
|
||||||
生成预览
|
<div class="caniuse-browser-version">
|
||||||
</button>
|
<select v-model="past">
|
||||||
|
<option v-for="item in pastList" :key="item.value" :value="item.value">
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<span>-</span>
|
||||||
|
<select v-model="future">
|
||||||
|
<option v-for="item in futureList" :key="item.value" :value="item.value">
|
||||||
|
{{ item.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="caniuse-output">
|
<div class="caniuse-output">
|
||||||
<h4>输出:</h4>
|
<h4>输出:</h4>
|
||||||
<CodeViewer lang="md" :content="output" />
|
<CodeViewer lang="md" :content="output" />
|
||||||
</div>
|
</div>
|
||||||
<div v-html="rendered" />
|
<div v-if="embedType === 'image'" v-html="rendered" />
|
||||||
|
<CanIUseViewer v-else-if="feature" :feature="feature" :past="past" :future="future" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -183,6 +97,7 @@ function render() {
|
|||||||
|
|
||||||
.caniuse-form-item:nth-child(3) {
|
.caniuse-form-item:nth-child(3) {
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-input {
|
.feature-input {
|
||||||
@ -251,24 +166,40 @@ function render() {
|
|||||||
|
|
||||||
.caniuse-browser-version {
|
.caniuse-browser-version {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caniuse-browser-version label {
|
.caniuse-browser-version span {
|
||||||
display: block;
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caniuse-browser-version select {
|
||||||
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
padding: 3px 16px;
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
border: solid 1px var(--vp-c-divider);
|
||||||
|
transition: border var(--t-color), background-color var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.caniuse-browser-version select:first-of-type {
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.caniuse-browser-version {
|
.caniuse-browser-version {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px 0;
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caniuse-browser-version label {
|
.caniuse-browser-version span {
|
||||||
width: 50%;
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caniuse-browser-version select:first-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,46 +1,164 @@
|
|||||||
export function resolveCanIUse(feature: string, mode: string, versions: string): string {
|
import { type Ref, computed, onMounted, readonly, ref, watch } from 'vue'
|
||||||
if (!feature)
|
import { onClickOutside, useDebounceFn, useEventListener, useLocalStorage } from '@vueuse/core'
|
||||||
return ''
|
|
||||||
|
|
||||||
if (mode === 'image') {
|
interface Feature {
|
||||||
const link = 'https://caniuse.bitsofco.de/image/'
|
label: string
|
||||||
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`
|
value: string
|
||||||
return `<p><picture>
|
}
|
||||||
<source type="image/webp" srcset="${link}${feature}.webp">
|
|
||||||
<source type="image/png" srcset="${link}${feature}.png">
|
interface SelectItem {
|
||||||
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
|
label: string
|
||||||
</picture></p>`
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = 'https://caniuse.pengzhanbo.cn/features.json'
|
||||||
|
|
||||||
|
const pastVersions: SelectItem[] = [
|
||||||
|
{ label: '不显示旧版本', value: '0' },
|
||||||
|
...Array(5).fill(0).map((_, i) => ({
|
||||||
|
label: `旧版本(当前版本 - ${i + 1})`,
|
||||||
|
value: `${i + 1}`,
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
|
||||||
|
const futureVersions: SelectItem[] = [
|
||||||
|
{ label: '不显示未来版本', value: '0' },
|
||||||
|
...Array(3).fill(0).map((_, i) => ({
|
||||||
|
label: `未来版本(当前版本 + ${i + 1})`,
|
||||||
|
value: `${i + 1}`,
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
|
||||||
|
const embedTypes: SelectItem[] = [
|
||||||
|
{ label: 'iframe', value: '' },
|
||||||
|
{ label: 'image', value: 'image' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useCaniuseVersionSelect() {
|
||||||
|
const past = ref('2')
|
||||||
|
const future = ref('1')
|
||||||
|
const embedType = ref('')
|
||||||
|
|
||||||
|
const pastList = readonly(pastVersions)
|
||||||
|
const futureList = readonly(futureVersions)
|
||||||
|
const embedTypeList = readonly(embedTypes)
|
||||||
|
|
||||||
|
return {
|
||||||
|
past,
|
||||||
|
future,
|
||||||
|
pastList,
|
||||||
|
futureList,
|
||||||
|
embedType,
|
||||||
|
embedTypeList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCaniuseFeaturesSearch(
|
||||||
|
inputEl: Ref<HTMLInputElement | null>,
|
||||||
|
listEl: Ref<HTMLUListElement | null>,
|
||||||
|
) {
|
||||||
|
const features = useLocalStorage('caniuse-features', [] as Feature[])
|
||||||
|
const featuresUpdated = useLocalStorage('caniuse-features-updated', Date.now())
|
||||||
|
const maxAge = 1000 * 60 * 60 * 24 * 3 // 3 days
|
||||||
|
onMounted(async () => {
|
||||||
|
if (typeof document === 'undefined')
|
||||||
|
return
|
||||||
|
|
||||||
|
if (features.value.length && Date.now() - featuresUpdated.value < maxAge)
|
||||||
|
return
|
||||||
|
const data = await fetch(api).then(res => res.json())
|
||||||
|
features.value = data || features.value || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const input = ref('')
|
||||||
|
const isFocus = ref(false)
|
||||||
|
const searched = ref<Feature[]>()
|
||||||
|
|
||||||
|
const selected = ref<Feature | null>(null)
|
||||||
|
|
||||||
|
watch(() => [features.value, isFocus.value], () => {
|
||||||
|
if (!isFocus.value)
|
||||||
|
searched.value = features.value
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
onClickOutside(listEl, () => {
|
||||||
|
isFocus.value = false
|
||||||
|
}, { ignore: [inputEl] })
|
||||||
|
|
||||||
|
useEventListener(inputEl, 'input', useDebounceFn(() => {
|
||||||
|
selected.value = null
|
||||||
|
input.value = inputEl.value?.value || ''
|
||||||
|
|
||||||
|
if (!input.value) {
|
||||||
|
searched.value = features.value
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
searched.value = features.value.filter(item => item.label.includes(input.value) || item.value.includes(input.value))
|
||||||
|
if (searched.value.length === 1)
|
||||||
|
selected.value = searched.value[0]
|
||||||
|
}
|
||||||
|
}, 500))
|
||||||
|
|
||||||
|
useEventListener(inputEl, 'focus', () => {
|
||||||
|
isFocus.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSelect(item: Feature) {
|
||||||
|
selected.value = item
|
||||||
|
isFocus.value = false
|
||||||
|
if (inputEl.value)
|
||||||
|
inputEl.value.value = item.label
|
||||||
}
|
}
|
||||||
|
|
||||||
const periods = resolveVersions(versions)
|
return {
|
||||||
const accessible = 'false'
|
featureList: searched,
|
||||||
const image = 'none'
|
isFocus,
|
||||||
const url = 'https://caniuse.bitsofco.de/embed/index.html'
|
onSelect,
|
||||||
const src = `${url}?feat=${feature}&periods=${periods}&accessible-colours=${accessible}&image-base=${image}`
|
feature: computed(() => selected.value?.value || ''),
|
||||||
|
}
|
||||||
return `<div class="ciu_embed" style="margin:16px 0" data-feature="${feature}"><iframe src="${src}" frameborder="0" width="100%" height="400px" title="Can I use ${feature}"></iframe></div>`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveVersions(versions: string): string {
|
export function useCaniuse({ feature, embedType, past, future }: {
|
||||||
if (!versions)
|
feature: Ref<string>
|
||||||
return 'future_1,current,past_1,past_2'
|
embedType: Ref<string>
|
||||||
|
past: Ref<string>
|
||||||
|
future: Ref<string>
|
||||||
|
}) {
|
||||||
|
const output = computed(() => {
|
||||||
|
let content = '@[caniuse'
|
||||||
|
if (embedType.value)
|
||||||
|
content += ` ${embedType.value}`
|
||||||
|
|
||||||
const list = versions
|
if (past.value !== '-2' || future.value !== '1') {
|
||||||
.split(',')
|
if (past.value === '0' && future.value === '0')
|
||||||
.map(v => Number(v.trim()))
|
content += '{0}'
|
||||||
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3)
|
else
|
||||||
|
content += `{-${past.value},${future.value}}`
|
||||||
|
}
|
||||||
|
|
||||||
list.push(0)
|
content += ']('
|
||||||
|
|
||||||
const uniq = [...new Set(list)].sort((a, b) => b - a)
|
if (feature.value)
|
||||||
const result: string[] = []
|
content += feature.value
|
||||||
uniq.forEach((v) => {
|
|
||||||
if (v < 0)
|
return `${content})`
|
||||||
result.push(`past_${Math.abs(v)}`)
|
|
||||||
if (v === 0)
|
|
||||||
result.push('current')
|
|
||||||
if (v > 0)
|
|
||||||
result.push(`future_${v}`)
|
|
||||||
})
|
})
|
||||||
return result.join(',')
|
|
||||||
|
const rendered = computed(() => {
|
||||||
|
if (!feature.value || !embedType.value)
|
||||||
|
return ''
|
||||||
|
return resolveCanIUse(feature.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
return { output, rendered }
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCanIUse(feature: string): string {
|
||||||
|
const link = 'https://caniuse.bitsofco.de/image/'
|
||||||
|
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`
|
||||||
|
return `<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>`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,3 +4,133 @@ author: Plume Theme
|
|||||||
createTime: 2024/03/01 22:56:03
|
createTime: 2024/03/01 22:56:03
|
||||||
permalink: /article/z8zvx0ru/
|
permalink: /article/z8zvx0ru/
|
||||||
---
|
---
|
||||||
|
|
||||||
|
:::go-repl
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello World")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::go-repl
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
dur := time.Duration(rand.Intn(1000)) * time.Millisecond
|
||||||
|
fmt.Printf("Sleeping for %v\n", dur)
|
||||||
|
// Sleep for a random duration between 0-1000ms
|
||||||
|
time.Sleep(dur)
|
||||||
|
}
|
||||||
|
fmt.Println("Done!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: go-repl
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprint(w, "Hello, playground")
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("Starting server...")
|
||||||
|
l, err := net.Listen("tcp", "localhost:8080")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
log.Fatal(http.Serve(l, nil))
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("Sending request...")
|
||||||
|
res, err := http.Get("http://localhost:8080/hello")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Reading response...")
|
||||||
|
if _, err := io.Copy(os.Stdout, res.Body); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: kotlin-repl
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class Contact(val id: Int, var email: String)
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val contact = Contact(1, "mary@gmail.com")
|
||||||
|
println(contact.id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: kotlin-repl
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun mul(a: Int, b: Int): Int {
|
||||||
|
return a * b
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
print(mul(-2, 4))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: rust-repl
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: rust-repl
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
printlnl!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -5,6 +5,11 @@ createTime: 2024/03/11 17:22:52
|
|||||||
permalink: /plugins/plugin-caniuse/
|
permalink: /plugins/plugin-caniuse/
|
||||||
---
|
---
|
||||||
|
|
||||||
|
:::warning Deprecated
|
||||||
|
该插件功能已整合到 [vuepress-plugin-md-power](/plugins/plugin-md-power) 。
|
||||||
|
因此,此插件不再更新维护,并标记为 弃用。
|
||||||
|
:::
|
||||||
|
|
||||||
## 指南
|
## 指南
|
||||||
|
|
||||||
为你的 vuepress 站点,在编写文章时, 提供嵌入 [can-i-use](https://caniuse.com/) WEB feature 各平台支持说明 的功能。
|
为你的 vuepress 站点,在编写文章时, 提供嵌入 [can-i-use](https://caniuse.com/) WEB feature 各平台支持说明 的功能。
|
||||||
|
|||||||
@ -41,7 +41,8 @@ pnpm add vuepress-plugin-md-power
|
|||||||
```ts
|
```ts
|
||||||
// .vuepress/config.ts
|
// .vuepress/config.ts
|
||||||
import { markdownPowerPlugin } from 'vuepress-plugin-md-power'
|
import { markdownPowerPlugin } from 'vuepress-plugin-md-power'
|
||||||
module.exports = {
|
|
||||||
|
export default {
|
||||||
// ...
|
// ...
|
||||||
plugins: [
|
plugins: [
|
||||||
markdownPowerPlugin({
|
markdownPowerPlugin({
|
||||||
@ -72,6 +73,7 @@ interface MarkdownPowerPluginOptions {
|
|||||||
jsfiddle?: boolean
|
jsfiddle?: boolean
|
||||||
|
|
||||||
caniuse?: boolean | CanIUseOptions
|
caniuse?: boolean | CanIUseOptions
|
||||||
|
repl?: boolean
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -85,23 +87,23 @@ interface MarkdownPowerPluginOptions {
|
|||||||
|
|
||||||
```md
|
```md
|
||||||
@[caniuse](feature)
|
@[caniuse](feature)
|
||||||
@[caniuse image](feature)
|
@[caniuse image](feature) // 不再推荐使用
|
||||||
@[caniuse embed{versions}](feature)
|
@[caniuse embed{versionRange}](feature)
|
||||||
```
|
```
|
||||||
|
|
||||||
你可以从 [caniuse](https://caniuse.bitsofco.de/) 获取 feature 的值。
|
你可以从 [caniuse](https://caniuse.pengzhanbo.cn/) 获取 feature 的值。
|
||||||
|
|
||||||
默认情况下,插件通过 `iframe` 嵌入 `caniuse` 的支持情况查看器。
|
默认情况下,插件通过 `iframe` 嵌入 `caniuse` 的支持情况查看器。
|
||||||
你也可以使用 `@[caniuse image](feature)` 直接嵌入图片。
|
~~你也可以使用 `@[caniuse image](feature)` 直接嵌入图片。~~
|
||||||
|
|
||||||
caniuse 默认查看最近的5个浏览器版本。你可以通过 `{versions}` 手动设置查看的浏览器版本。
|
caniuse 默认查看最近的5个浏览器版本。你可以通过 `{versionRange}` 手动设置查看的浏览器版本。
|
||||||
格式为 `{number,number,...}`。取值范围为 `-5 ~ 3` 。
|
格式为 `{past,future}` 表示 `{过去版本,未来版本}`。取值范围为 `-5 ~ 3` 。
|
||||||
|
|
||||||
- 小于0 表示低于当前浏览器版本的支持情况
|
- 小于0 表示低于当前浏览器版本的支持情况
|
||||||
- 0 表示当前浏览器版本的支持情况
|
- 0 表示当前浏览器版本的支持情况
|
||||||
- 大于0 表示高于当前浏览器版本的支持情况
|
- 大于0 表示高于当前浏览器版本的支持情况
|
||||||
|
|
||||||
如 `{-2,-1,1,2}` 表示查看低于当前 2 个版本 到 高于当前 2 个版本的支持情况。
|
如 `{-2,2}` 表示查看低于当前 2 个版本 到 高于当前 2 个版本的支持情况。
|
||||||
|
|
||||||
### pdf
|
### pdf
|
||||||
|
|
||||||
@ -280,3 +282,32 @@ pnpm add @iconify/json
|
|||||||
- `tab`: 选项卡, 可选值:`"js" | "css" | "html" | "result"`, 多个用 `","` 分割,
|
- `tab`: 选项卡, 可选值:`"js" | "css" | "html" | "result"`, 多个用 `","` 分割,
|
||||||
顺序将决定选项卡的排序,默认为 `js,css,html,result`
|
顺序将决定选项卡的排序,默认为 `js,css,html,result`
|
||||||
- `height`: 高度
|
- `height`: 高度
|
||||||
|
|
||||||
|
### Repl
|
||||||
|
|
||||||
|
插件默认不启用该功能,你需要手动设置 `repl` 为 `true`
|
||||||
|
|
||||||
|
提供在 markdown 中为 `golang` 、`kotlin`、`rust` 语言的 在线代码演示 支持。
|
||||||
|
在线编译执行代码,并输出结果。
|
||||||
|
|
||||||
|
#### 语法
|
||||||
|
|
||||||
|
````md
|
||||||
|
::: go-repl
|
||||||
|
```go
|
||||||
|
// your go lang code
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: kotlin-repl
|
||||||
|
```kotlin
|
||||||
|
// your kotlin code
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::rust-repl
|
||||||
|
```rust
|
||||||
|
// your rust code
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export default defineUserConfig({
|
|||||||
// codeSandbox: true, // @[codesandbox](id) 嵌入 CodeSandbox
|
// codeSandbox: true, // @[codesandbox](id) 嵌入 CodeSandbox
|
||||||
// jsfiddle: true, // @[jsfiddle](id) 嵌入 jsfiddle
|
// jsfiddle: true, // @[jsfiddle](id) 嵌入 jsfiddle
|
||||||
// caniuse: true, // @[caniuse](feature) 嵌入 caniuse
|
// caniuse: true, // @[caniuse](feature) 嵌入 caniuse
|
||||||
|
// repl: true, // :::go-repl :::kotlin-repl :::rust-repl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -431,15 +431,15 @@ export default defineUserConfig({
|
|||||||
|
|
||||||
- `feature`
|
- `feature`
|
||||||
|
|
||||||
必填。 正确取值请参考 [https://caniuse.bitsofco.de/](https://caniuse.bitsofco.de/)
|
必填。 正确取值请参考 [caniuse-embed.vercel.app](https://caniuse-embed.vercel.app/zh-CN)
|
||||||
|
|
||||||
- `{browser_versions}`
|
- `{browser_versions}`
|
||||||
|
|
||||||
可选。当前特性在多个版本中的支持情况。
|
可选。当前特性在多个版本中的支持情况。
|
||||||
|
|
||||||
默认值为: `{-2,-1,1}`
|
默认值为: `{-2,1}`
|
||||||
|
|
||||||
格式: `{number,number,...}` 取值范围为 `-5 ~ 3`
|
格式: `{past,future}` 取值范围为 `-5 ~ 3`
|
||||||
|
|
||||||
- 小于`0` 表示低于当前浏览器版本的支持情况
|
- 小于`0` 表示低于当前浏览器版本的支持情况
|
||||||
- `0` 表示当前浏览器版本的支持情况
|
- `0` 表示当前浏览器版本的支持情况
|
||||||
@ -453,6 +453,11 @@ export default defineUserConfig({
|
|||||||
|
|
||||||
默认值为:`'embed'`
|
默认值为:`'embed'`
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
不再推荐使用 image 类型,建议使用 embed 类型,主题更换了 embed 实现技术方案,
|
||||||
|
现在的 embed 类型优势已远远超过 image 类型,加载速度更快,体积更小,交互体验更好。
|
||||||
|
:::
|
||||||
|
|
||||||
### 示例
|
### 示例
|
||||||
|
|
||||||
**获取 css 伪类选择器 `:dir()` 在各个浏览器的支持情况:**
|
**获取 css 伪类选择器 `:dir()` 在各个浏览器的支持情况:**
|
||||||
@ -478,12 +483,12 @@ export default defineUserConfig({
|
|||||||
**获取 css 伪类选择器 `:dir()` 特定范围浏览器的支持情况:**
|
**获取 css 伪类选择器 `:dir()` 特定范围浏览器的支持情况:**
|
||||||
|
|
||||||
```md
|
```md
|
||||||
@[caniuse{-2,-1,1,2,3}](css-matches-pseudo)
|
@[caniuse{-2,3}](css-matches-pseudo)
|
||||||
```
|
```
|
||||||
|
|
||||||
效果:
|
效果:
|
||||||
|
|
||||||
@[caniuse{-2,-1,1,2,3}](css-matches-pseudo)
|
@[caniuse{-2,3}](css-matches-pseudo)
|
||||||
|
|
||||||
## 导入文件
|
## 导入文件
|
||||||
|
|
||||||
|
|||||||
188
docs/notes/theme/guide/代码演示/golang.md
Normal file
188
docs/notes/theme/guide/代码演示/golang.md
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
---
|
||||||
|
title: Golang
|
||||||
|
author: pengzhanbo
|
||||||
|
icon: devicon-plain:go
|
||||||
|
createTime: 2024/04/22 09:44:30
|
||||||
|
permalink: /guide/repl/golang/
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
主题提供了 Golang 代码演示,支持 在线运行 Go 代码。
|
||||||
|
|
||||||
|
::: important
|
||||||
|
该功能通过将 代码提交到 服务器 进行 编译并执行,且一次只能提交单个代码文件。
|
||||||
|
|
||||||
|
因此,请不要使用此功能 执行 过于复杂的代码,也不要过于频繁的进行执行请求。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
该功能默认不启用,你可以通过配置来启用它。
|
||||||
|
|
||||||
|
::: code-tabs
|
||||||
|
@tab .vuepress/config.ts
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineUserConfig({
|
||||||
|
theme: plumeTheme({
|
||||||
|
plugins: {
|
||||||
|
markdownPower: {
|
||||||
|
repl: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
使用 `::: go-repl` 容器语法 将 Go 代码块包裹起来。主题会检查代码块并添加执行按钮。
|
||||||
|
|
||||||
|
````md
|
||||||
|
::: go-repl
|
||||||
|
```go
|
||||||
|
// your rust code
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### 打印内容
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
````md
|
||||||
|
:::go-repl
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello World")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
:::go-repl
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello World")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 循环随机延迟打印
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
````md
|
||||||
|
:::go-repl
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
dur := time.Duration(rand.Intn(1000)) * time.Millisecond
|
||||||
|
fmt.Printf("Sleeping for %v\n", dur)
|
||||||
|
// Sleep for a random duration between 0-1000ms
|
||||||
|
time.Sleep(dur)
|
||||||
|
}
|
||||||
|
fmt.Println("Done!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
:::go-repl
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
dur := time.Duration(rand.Intn(1000)) * time.Millisecond
|
||||||
|
fmt.Printf("Sleeping for %v\n", dur)
|
||||||
|
// Sleep for a random duration between 0-1000ms
|
||||||
|
time.Sleep(dur)
|
||||||
|
}
|
||||||
|
fmt.Println("Done!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 网络请求
|
||||||
|
|
||||||
|
::: go-repl
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprint(w, "Hello, playground")
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("Starting server...")
|
||||||
|
l, err := net.Listen("tcp", "localhost:8080")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
log.Fatal(http.Serve(l, nil))
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("Sending request...")
|
||||||
|
res, err := http.Get("http://localhost:8080/hello")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Reading response...")
|
||||||
|
if _, err := io.Copy(os.Stdout, res.Body); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
100
docs/notes/theme/guide/代码演示/kotlin.md
Normal file
100
docs/notes/theme/guide/代码演示/kotlin.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
title: Kotlin
|
||||||
|
author: pengzhanbo
|
||||||
|
icon: tabler:brand-kotlin
|
||||||
|
createTime: 2024/04/22 09:44:37
|
||||||
|
permalink: /guide/repl/kotlin/
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
主题提供了 Kotlin 代码演示,支持 在线运行 Kotlin 代码。
|
||||||
|
|
||||||
|
::: important
|
||||||
|
该功能通过将 代码提交到 服务器 进行 编译并执行,且一次只能提交单个代码文件。
|
||||||
|
|
||||||
|
因此,请不要使用此功能 执行 过于复杂的代码,也不要过于频繁的进行执行请求。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
该功能默认不启用,你可以通过配置来启用它。
|
||||||
|
|
||||||
|
::: code-tabs
|
||||||
|
@tab .vuepress/config.ts
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineUserConfig({
|
||||||
|
theme: plumeTheme({
|
||||||
|
plugins: {
|
||||||
|
markdownPower: {
|
||||||
|
repl: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
使用 `::: kotlin-repl` 容器语法 将 Rust 代码块包裹起来。主题会检查代码块并添加执行按钮。
|
||||||
|
|
||||||
|
````md
|
||||||
|
::: kotlin-repl
|
||||||
|
```kotlin
|
||||||
|
// your kotlin code
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### 打印内容
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
````md
|
||||||
|
::: kotlin-repl
|
||||||
|
```kotlin
|
||||||
|
class Contact(val id: Int, var email: String)
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val contact = Contact(1, "mary@gmail.com")
|
||||||
|
println(contact.id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
::: kotlin-repl
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class Contact(val id: Int, var email: String)
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val contact = Contact(1, "mary@gmail.com")
|
||||||
|
println(contact.id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 运算
|
||||||
|
|
||||||
|
::: kotlin-repl
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun mul(a: Int, b: Int): Int {
|
||||||
|
return a * b
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
print(mul(-2, 4))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
123
docs/notes/theme/guide/代码演示/rust.md
Normal file
123
docs/notes/theme/guide/代码演示/rust.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
---
|
||||||
|
title: Rust
|
||||||
|
author: pengzhanbo
|
||||||
|
icon: logos:rust
|
||||||
|
createTime: 2024/04/22 09:44:43
|
||||||
|
permalink: /guide/repl/rust/
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
主题提供了 Rust 代码演示,支持 在线运行 Rust 代码。
|
||||||
|
|
||||||
|
::: important
|
||||||
|
该功能通过将 代码提交到 服务器 进行 编译并执行,且一次只能提交单个代码文件。
|
||||||
|
|
||||||
|
因此,请不要使用此功能 执行 过于复杂的代码,也不要过于频繁的进行执行请求。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
该功能默认不启用,你可以通过配置来启用它。
|
||||||
|
|
||||||
|
::: code-tabs
|
||||||
|
@tab .vuepress/config.ts
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineUserConfig({
|
||||||
|
theme: plumeTheme({
|
||||||
|
plugins: {
|
||||||
|
markdownPower: {
|
||||||
|
repl: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
使用 `::: rust-repl` 容器语法 将 Rust 代码块包裹起来。主题会检查代码块并添加执行按钮。
|
||||||
|
|
||||||
|
````md
|
||||||
|
::: rust-repl
|
||||||
|
```rust
|
||||||
|
// your rust code
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|
||||||
|
## 示例
|
||||||
|
|
||||||
|
### 打印内容
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
````md
|
||||||
|
::: rust-repl
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
::: rust-repl
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
点击 执行 按钮,即可执行代码。
|
||||||
|
|
||||||
|
### 打印错误信息
|
||||||
|
|
||||||
|
**输入:**
|
||||||
|
|
||||||
|
````md
|
||||||
|
::: rust-repl
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
printlnl!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
````
|
||||||
|
|
||||||
|
**输出:**
|
||||||
|
|
||||||
|
::: rust-repl
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
printlnl!("Hello, world!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 等待子进程执行
|
||||||
|
|
||||||
|
::: rust-repl
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut child = Command::new("sleep").arg("5").spawn().unwrap();
|
||||||
|
let _result = child.wait().unwrap();
|
||||||
|
|
||||||
|
println!("reached end of main");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
@ -1,14 +1,16 @@
|
|||||||
---
|
---
|
||||||
title: 代码演示
|
title: 前端
|
||||||
author: pengzhanbo
|
author: pengzhanbo
|
||||||
icon: carbon:demo
|
icon: icon-park-outline:html-five
|
||||||
createTime: 2024/04/04 11:39:05
|
createTime: 2024/04/04 11:39:05
|
||||||
permalink: /guide/code/demo/
|
permalink: /guide/repl/frontend/
|
||||||
---
|
---
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
代码演示 默认不启用,你可以通过配置来启用它。
|
前端代码演示 由 [vuepress-plugin-md-enhance](https://plugin-md-enhance.vuejs.press/zh/) 提供支持。
|
||||||
|
|
||||||
|
前端 代码演示 默认不启用,你可以通过配置来启用它。
|
||||||
|
|
||||||
::: code-tabs
|
::: code-tabs
|
||||||
@tab .vuepress/config.ts
|
@tab .vuepress/config.ts
|
||||||
@ -12,14 +12,14 @@
|
|||||||
"vuepress": "2.0.0-rc.9"
|
"vuepress": "2.0.0-rc.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/json": "^2.2.202",
|
"@iconify/json": "^2.2.204",
|
||||||
"@vuepress/bundler-vite": "2.0.0-rc.9",
|
"@vuepress/bundler-vite": "2.0.0-rc.9",
|
||||||
"anywhere": "^1.6.0",
|
"anywhere": "^1.6.0",
|
||||||
"chart.js": "^4.4.2",
|
"chart.js": "^4.4.2",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^5.5.0",
|
||||||
"flowchart.ts": "^3.0.0",
|
"flowchart.ts": "^3.0.0",
|
||||||
"mermaid": "^10.9.0",
|
"mermaid": "^10.9.0",
|
||||||
"vue": "^3.4.23",
|
"vue": "^3.4.25",
|
||||||
"vuepress-theme-plume": "workspace:*"
|
"vuepress-theme-plume": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
14
package.json
14
package.json
@ -3,7 +3,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.0.0-rc.53",
|
"version": "1.0.0-rc.53",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.0.4",
|
"packageManager": "pnpm@9.0.6",
|
||||||
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
|
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@ -39,10 +39,10 @@
|
|||||||
"release:version": "bumpp package.json plugins/*/package.json theme/package.json --execute=\"pnpm release:changelog\" --commit \"build: publish v%s\" --all --tag --push"
|
"release:version": "bumpp package.json plugins/*/package.json theme/package.json --execute=\"pnpm release:changelog\" --commit \"build: publish v%s\" --all --tag --push"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^19.2.2",
|
"@commitlint/cli": "^19.3.0",
|
||||||
"@commitlint/config-conventional": "^19.2.2",
|
"@commitlint/config-conventional": "^19.2.2",
|
||||||
"@pengzhanbo/eslint-config-vue": "^1.9.0",
|
"@pengzhanbo/eslint-config-vue": "^1.9.1",
|
||||||
"@pengzhanbo/stylelint-config": "^1.9.0",
|
"@pengzhanbo/stylelint-config": "^1.9.1",
|
||||||
"@types/lodash.merge": "^4.6.9",
|
"@types/lodash.merge": "^4.6.9",
|
||||||
"@types/node": "20.9.1",
|
"@types/node": "20.9.1",
|
||||||
"@types/webpack-env": "^1.18.4",
|
"@types/webpack-env": "^1.18.4",
|
||||||
@ -52,14 +52,14 @@
|
|||||||
"conventional-changelog-cli": "^4.1.0",
|
"conventional-changelog-cli": "^4.1.0",
|
||||||
"cpx2": "^7.0.1",
|
"cpx2": "^7.0.1",
|
||||||
"cz-conventional-changelog": "^3.3.0",
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.1.1",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"stylelint": "^16.3.1",
|
"stylelint": "^16.4.0",
|
||||||
"tsconfig-vuepress": "^4.5.0",
|
"tsconfig-vuepress": "^4.5.0",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.2.9"
|
"vite": "^5.2.10"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
"@vue/devtools-api": "6.5.1",
|
"@vue/devtools-api": "6.5.1",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"create-filter": "^1.0.1",
|
"create-filter": "^1.0.1",
|
||||||
"vue": "^3.4.23"
|
"vue": "^3.4.25"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
"vuepress": "2.0.0-rc.9"
|
"vuepress": "2.0.0-rc.9"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.4.23"
|
"vue": "^3.4.25"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vuepress-plume/plugin-content-update": "workspace:*",
|
"@vuepress-plume/plugin-content-update": "workspace:*",
|
||||||
"vue": "^3.4.23"
|
"vue": "^3.4.25"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/vue": "^4.1.2",
|
"@iconify/vue": "^4.1.2",
|
||||||
"vue": "^3.4.23"
|
"vue": "^3.4.25"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@ -51,10 +51,10 @@
|
|||||||
"local-pkg": "^0.5.0",
|
"local-pkg": "^0.5.0",
|
||||||
"markdown-it-container": "^4.0.0",
|
"markdown-it-container": "^4.0.0",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
"vue": "^3.4.23"
|
"vue": "^3.4.25"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/json": "^2.2.202",
|
"@iconify/json": "^2.2.204",
|
||||||
"@types/markdown-it": "^14.0.1"
|
"@types/markdown-it": "^14.0.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
93
plugins/plugin-md-power/src/client/components/CanIUse.vue
Normal file
93
plugins/plugin-md-power/src/client/components/CanIUse.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, getCurrentInstance, ref } from 'vue'
|
||||||
|
import { useEventListener } from '@vueuse/core'
|
||||||
|
|
||||||
|
interface MessageData {
|
||||||
|
type: string
|
||||||
|
payload?: {
|
||||||
|
feature?: string
|
||||||
|
meta?: string
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
feature: string
|
||||||
|
past?: string
|
||||||
|
future?: string
|
||||||
|
meta?: string
|
||||||
|
}>(), {
|
||||||
|
past: '2',
|
||||||
|
future: '1',
|
||||||
|
meta: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const url = 'https://caniuse.pengzhanbo.cn/'
|
||||||
|
const current = getCurrentInstance()
|
||||||
|
|
||||||
|
const height = ref('330px')
|
||||||
|
|
||||||
|
const isDark = computed(() => current?.appContext.config.globalProperties.$isDark.value)
|
||||||
|
const source = computed(() => {
|
||||||
|
const source = `${url}${props.feature}#past=${props.past}&future=${props.future}&meta=${props.meta}&theme=${isDark.value ? 'dark' : 'light'}`
|
||||||
|
|
||||||
|
return source
|
||||||
|
})
|
||||||
|
|
||||||
|
useEventListener('message', (event) => {
|
||||||
|
const data = parseData(event.data)
|
||||||
|
const { type, payload } = data
|
||||||
|
if (
|
||||||
|
type === 'ciu_embed'
|
||||||
|
&& payload
|
||||||
|
&& payload.feature === props.feature
|
||||||
|
&& payload.meta === props.meta
|
||||||
|
)
|
||||||
|
height.value = `${Math.ceil(payload.height)}px`
|
||||||
|
})
|
||||||
|
|
||||||
|
function parseData(data: string | MessageData): MessageData {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return { type: '' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="ciu_embed"
|
||||||
|
:data-feature="feature"
|
||||||
|
:data-meta="meta"
|
||||||
|
:data-past="past"
|
||||||
|
:data-future="future"
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
:src="source"
|
||||||
|
:style="{ height }"
|
||||||
|
:title="`Can I use ${feature}`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ciu_embed {
|
||||||
|
margin: 16px -24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ciu_embed iframe {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.ciu_embed {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M20 19V7H4v12zm0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2zm-7 14v-2h5v2zm-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M21.409 9.353a2.998 2.998 0 0 1 0 5.294L8.597 21.614C6.534 22.737 4 21.277 4 18.968V5.033c0-2.31 2.534-3.769 4.597-2.648z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
204
plugins/plugin-md-power/src/client/components/LanguageRepl.vue
Normal file
204
plugins/plugin-md-power/src/client/components/LanguageRepl.vue
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { shallowRef } from 'vue'
|
||||||
|
import { useCodeRepl } from '../composables/codeRepl.js'
|
||||||
|
import IconRun from './IconRun.vue'
|
||||||
|
import Loading from './Loading.vue'
|
||||||
|
import IconConsole from './IconConsole.vue'
|
||||||
|
import IconClose from './IconClose.vue'
|
||||||
|
|
||||||
|
const replEl = shallowRef<HTMLDivElement | null>(null)
|
||||||
|
const outputEl = shallowRef<HTMLDivElement | null>(null)
|
||||||
|
const {
|
||||||
|
onRunCode,
|
||||||
|
onCleanRun,
|
||||||
|
firstRun,
|
||||||
|
stderr,
|
||||||
|
stdout,
|
||||||
|
error,
|
||||||
|
loaded,
|
||||||
|
finished,
|
||||||
|
lang,
|
||||||
|
backendVersion,
|
||||||
|
} = useCodeRepl(replEl)
|
||||||
|
|
||||||
|
function runCode() {
|
||||||
|
onRunCode()
|
||||||
|
|
||||||
|
if (outputEl.value)
|
||||||
|
outputEl.value.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="replEl" class="code-repl">
|
||||||
|
<span v-show="loaded && finished" class="icon-run" title="Run Code" @click="runCode">
|
||||||
|
<IconRun />
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
<div ref="outputEl" class="code-repl-pin" />
|
||||||
|
<div v-if="!firstRun" class="code-repl-output">
|
||||||
|
<div class="output-head">
|
||||||
|
<IconConsole class="icon-console" />
|
||||||
|
<span class="title">console</span>
|
||||||
|
<span v-if="lang && backendVersion" class="output-version">
|
||||||
|
Running on: {{ lang }} <i>{{ backendVersion }}</i>
|
||||||
|
</span>
|
||||||
|
<IconClose class="icon-close" @click="onCleanRun" />
|
||||||
|
</div>
|
||||||
|
<div v-if="!loaded" class="output-content">
|
||||||
|
<Loading />
|
||||||
|
</div>
|
||||||
|
<div v-else class="output-content" :class="lang">
|
||||||
|
<p v-if="error" class="error">
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
<div v-if="stderr.length" class="stderr">
|
||||||
|
<h4>Stderr:</h4>
|
||||||
|
<p
|
||||||
|
v-for="(item, index) in stderr" :key="index"
|
||||||
|
:class="{ error: lang === 'rust' && item.startsWith('error') }"
|
||||||
|
>
|
||||||
|
<pre>{{ item }}</pre>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="stdout.length" class="stdout">
|
||||||
|
<h4 v-if="stderr.length">
|
||||||
|
Stdout:
|
||||||
|
</h4>
|
||||||
|
<p v-for="(item, index) in stdout" :key="index">
|
||||||
|
<pre>{{ item }}</pre>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.code-repl {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-repl-output {
|
||||||
|
position: relative;
|
||||||
|
top: -20px;
|
||||||
|
padding-top: 6px;
|
||||||
|
margin: 0 -1.5rem;
|
||||||
|
background-color: var(--vp-code-block-bg);
|
||||||
|
transition: background-color, var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.code-repl-output {
|
||||||
|
margin: 0;
|
||||||
|
border-bottom-right-radius: 6px;
|
||||||
|
border-bottom-left-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-run {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--vp-c-bg);
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--vp-c-brand-1);
|
||||||
|
border-radius: 100%;
|
||||||
|
transition: var(--t-color);
|
||||||
|
transition-property: color, background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.icon-run {
|
||||||
|
top: 60px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-run:hover {
|
||||||
|
background-color: var(--vp-c-brand-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-repl-output .output-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 10px 4px 20px;
|
||||||
|
border-top: solid 2px var(--vp-c-border);
|
||||||
|
transition: border-color var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-head .title {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-head .output-version {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
transition: color var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-head .icon-close {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-head .icon-close:hover {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-content {
|
||||||
|
padding: 12px 20px 24px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-content h4 {
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-content p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-content p pre {
|
||||||
|
width: fit-content;
|
||||||
|
padding: 0 20px 0 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow-x: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-content .error,
|
||||||
|
.output-content .stderr p,
|
||||||
|
.output-content.rust .stderr p.error {
|
||||||
|
color: var(--vp-c-danger-1, #b8272c);
|
||||||
|
transition: color var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-content.rust .stderr p {
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-content .stderr + .stdout {
|
||||||
|
margin-top: 12px;
|
||||||
|
border-top: 1px solid var(--vp-c-divider);
|
||||||
|
transition: border-color var(--t-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -29,7 +29,8 @@ defineProps<{
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
color: currentcolor;
|
color: currentcolor;
|
||||||
background-color: var(--vp-c-bg, #fff);
|
background-color: inherit;
|
||||||
|
transition: background-color var(--t-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-power-loading.absolute {
|
.md-power-loading.absolute {
|
||||||
|
|||||||
213
plugins/plugin-md-power/src/client/composables/codeRepl.ts
Normal file
213
plugins/plugin-md-power/src/client/composables/codeRepl.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { type Ref, ref } from 'vue'
|
||||||
|
import { http } from '../utils/http.js'
|
||||||
|
import { sleep } from '../utils/sleep.js'
|
||||||
|
import { rustExecute } from './rustRepl.js'
|
||||||
|
|
||||||
|
const ignoredNodes = ['.diff.remove', '.vp-copy-ignore']
|
||||||
|
const RE_LANGUAGE = /language-([\w]+)/
|
||||||
|
const api = {
|
||||||
|
go: 'https://api.pengzhanbo.cn/repl/golang/run',
|
||||||
|
kotlin: 'https://api.pengzhanbo.cn/repl/kotlin/run',
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lang = 'kotlin' | 'go' | 'rust'
|
||||||
|
type ExecuteFn = (code: string) => Promise<any>
|
||||||
|
type ExecuteMap = Record<Lang, ExecuteFn>
|
||||||
|
|
||||||
|
const langAlias: Record<string, string> = {
|
||||||
|
kt: 'kotlin',
|
||||||
|
kotlin: 'kotlin',
|
||||||
|
go: 'go',
|
||||||
|
rust: 'rust',
|
||||||
|
rs: 'rust',
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportLang: Lang[] = ['kotlin', 'go', 'rust']
|
||||||
|
|
||||||
|
function resolveLang(lang?: string) {
|
||||||
|
return lang ? langAlias[lang] || lang : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveCodeInfo(el: HTMLDivElement) {
|
||||||
|
const wrapper = el.querySelector('[class*=language-]')
|
||||||
|
const lang = wrapper?.className.match(RE_LANGUAGE)?.[1]
|
||||||
|
const codeEl = wrapper?.querySelector('pre code')
|
||||||
|
let code = ''
|
||||||
|
|
||||||
|
if (codeEl) {
|
||||||
|
const clone = codeEl.cloneNode(true) as HTMLElement
|
||||||
|
clone
|
||||||
|
.querySelectorAll(ignoredNodes.join(','))
|
||||||
|
.forEach(node => node.remove())
|
||||||
|
|
||||||
|
code = clone.textContent || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return { lang: resolveLang(lang) as Lang, code }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCodeRepl(el: Ref<HTMLDivElement | null>) {
|
||||||
|
const lang = ref<Lang>()
|
||||||
|
const loaded = ref(true)
|
||||||
|
const firstRun = ref(true)
|
||||||
|
const finished = ref(true)
|
||||||
|
|
||||||
|
const stdout = ref<string[]>([]) // like print
|
||||||
|
const stderr = ref<string[]>([]) // like print error
|
||||||
|
const error = ref('') // execute error
|
||||||
|
const backendVersion = ref('')
|
||||||
|
|
||||||
|
const executeMap: ExecuteMap = {
|
||||||
|
kotlin: executeKotlin,
|
||||||
|
go: executeGolang,
|
||||||
|
rust: executeRust,
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCleanRun() {
|
||||||
|
loaded.value = false
|
||||||
|
finished.value = false
|
||||||
|
stdout.value = []
|
||||||
|
stderr.value = []
|
||||||
|
error.value = ''
|
||||||
|
firstRun.value = true
|
||||||
|
backendVersion.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRunCode() {
|
||||||
|
if (!el.value || !loaded.value)
|
||||||
|
return
|
||||||
|
const info = resolveCodeInfo(el.value)
|
||||||
|
lang.value = info.lang
|
||||||
|
|
||||||
|
if (!lang.value || !info.code || !supportLang.includes(lang.value))
|
||||||
|
return
|
||||||
|
|
||||||
|
if (firstRun.value)
|
||||||
|
firstRun.value = false
|
||||||
|
|
||||||
|
loaded.value = false
|
||||||
|
finished.value = false
|
||||||
|
stdout.value = []
|
||||||
|
stderr.value = []
|
||||||
|
error.value = ''
|
||||||
|
|
||||||
|
await executeMap[lang.value]?.(info.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeGolang(code: string) {
|
||||||
|
const res = await http.post<GolangRequest, GolangResponse>(api.go, { code })
|
||||||
|
backendVersion.value = `v${res.version}`
|
||||||
|
loaded.value = true
|
||||||
|
if (res.error) {
|
||||||
|
error.value = res.error
|
||||||
|
finished.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const events = res.events || []
|
||||||
|
for (const event of events) {
|
||||||
|
if (event.kind === 'stdout') {
|
||||||
|
if (event.delay)
|
||||||
|
await sleep(event.delay / 1000000)
|
||||||
|
|
||||||
|
stdout.value.push(event.message)
|
||||||
|
}
|
||||||
|
else if (event.kind === 'stderr') {
|
||||||
|
stderr.value.push(event.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finished.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeKotlin(code: string) {
|
||||||
|
const filename = 'File.kt'
|
||||||
|
const res = await http.post<KotlinRequest, KotlinResponse>(api.kotlin, {
|
||||||
|
args: '',
|
||||||
|
files: [{ name: filename, publicId: '', text: code }],
|
||||||
|
})
|
||||||
|
backendVersion.value = `v${res.version}`
|
||||||
|
loaded.value = true
|
||||||
|
if (res.errors) {
|
||||||
|
const errors = Array.isArray(res.errors[filename]) ? res.errors[filename] : [res.errors[filename]]
|
||||||
|
if (errors.length) {
|
||||||
|
errors.forEach(
|
||||||
|
({ message, severity }) => severity === 'ERROR' && stderr.value.push(message),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stdout.value.push(res.text)
|
||||||
|
finished.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeRust(code: string) {
|
||||||
|
await rustExecute(code, {
|
||||||
|
onBegin: () => {
|
||||||
|
loaded.value = true
|
||||||
|
finished.value = false
|
||||||
|
stdout.value = []
|
||||||
|
stderr.value = []
|
||||||
|
error.value = ''
|
||||||
|
backendVersion.value = 'release'
|
||||||
|
},
|
||||||
|
onError(message) {
|
||||||
|
error.value = message
|
||||||
|
},
|
||||||
|
onStdout(message) {
|
||||||
|
stdout.value.push(message)
|
||||||
|
},
|
||||||
|
onStderr(message) {
|
||||||
|
stderr.value.push(message)
|
||||||
|
},
|
||||||
|
onEnd: () => {
|
||||||
|
finished.value = true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
onRunCode,
|
||||||
|
onCleanRun,
|
||||||
|
lang,
|
||||||
|
backendVersion,
|
||||||
|
firstRun,
|
||||||
|
stderr,
|
||||||
|
stdout,
|
||||||
|
loaded,
|
||||||
|
finished,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GolangRequest {
|
||||||
|
code: string
|
||||||
|
version?: '' | 'goprev' | 'gotip'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GolangResponse {
|
||||||
|
events?: {
|
||||||
|
message: ''
|
||||||
|
kind: 'stdout' | 'stderr'
|
||||||
|
delay: number
|
||||||
|
}[]
|
||||||
|
error?: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KotlinRequest {
|
||||||
|
args?: string
|
||||||
|
files: {
|
||||||
|
name: string
|
||||||
|
publicId: string
|
||||||
|
text: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KotlinResponse {
|
||||||
|
text: string
|
||||||
|
version: string
|
||||||
|
errors: {
|
||||||
|
[filename: string]: {
|
||||||
|
message: string
|
||||||
|
severity: 'ERROR' | 'WARNING'
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
}
|
||||||
134
plugins/plugin-md-power/src/client/composables/rustRepl.ts
Normal file
134
plugins/plugin-md-power/src/client/composables/rustRepl.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* 相比于 golang 和 kotlin 可以比较简单的实现,
|
||||||
|
* rust 需要通过 websocket 建立连接在实现交互,因此,将其进行一些包装,
|
||||||
|
* 方便在 codeRepl 中使用
|
||||||
|
*/
|
||||||
|
import { tryOnScopeDispose } from '@vueuse/core'
|
||||||
|
|
||||||
|
const wsUrl = 'wss://play.rust-lang.org/websocket'
|
||||||
|
|
||||||
|
const payloadType = {
|
||||||
|
connected: 'websocket/connected',
|
||||||
|
request: 'output/execute/wsExecuteRequest',
|
||||||
|
execute: {
|
||||||
|
begin: 'output/execute/wsExecuteBegin',
|
||||||
|
// status: 'output/execute/wsExecuteStatus',
|
||||||
|
stderr: 'output/execute/wsExecuteStderr',
|
||||||
|
stdout: 'output/execute/wsExecuteStdout',
|
||||||
|
end: 'output/execute/wsExecuteEnd',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
let ws: WebSocket | null = null
|
||||||
|
let isOpen = false
|
||||||
|
let uuid = 0
|
||||||
|
|
||||||
|
function connect(): Promise<void> {
|
||||||
|
if (isOpen)
|
||||||
|
return Promise.resolve()
|
||||||
|
|
||||||
|
ws = new WebSocket(wsUrl)
|
||||||
|
uuid = 0
|
||||||
|
|
||||||
|
ws.addEventListener('open', () => {
|
||||||
|
isOpen = true
|
||||||
|
send(
|
||||||
|
payloadType.connected,
|
||||||
|
{ iAcceptThisIsAnUnsupportedApi: true },
|
||||||
|
{ websocket: true, sequenceNumber: uuid },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.addEventListener('close', () => {
|
||||||
|
isOpen = false
|
||||||
|
ws = null
|
||||||
|
})
|
||||||
|
|
||||||
|
tryOnScopeDispose(() => ws?.close())
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
function connected(e: WebSocketEventMap['message']) {
|
||||||
|
const data = JSON.parse(e.data)
|
||||||
|
if (data.type === payloadType.connected) {
|
||||||
|
ws?.removeEventListener('message', connected)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws?.addEventListener('message', connected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(type: string, payload: Record<string, any>, meta: Record<string, any>) {
|
||||||
|
const msg = { type, meta, payload }
|
||||||
|
ws?.send(JSON.stringify(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function rustExecute(
|
||||||
|
code: string,
|
||||||
|
{ onEnd, onError, onStderr, onStdout, onBegin }: RustExecuteOptions,
|
||||||
|
) {
|
||||||
|
await connect()
|
||||||
|
const meta = { sequenceNumber: uuid++ }
|
||||||
|
const payload = {
|
||||||
|
backtrace: false,
|
||||||
|
channel: 'stable',
|
||||||
|
crateType: 'bin',
|
||||||
|
edition: '2021',
|
||||||
|
mode: 'release',
|
||||||
|
tests: false,
|
||||||
|
code,
|
||||||
|
}
|
||||||
|
send(payloadType.request, payload, meta)
|
||||||
|
|
||||||
|
let stdout = ''
|
||||||
|
let stderr = ''
|
||||||
|
|
||||||
|
function onMessage(e: WebSocketEventMap['message']) {
|
||||||
|
const data = JSON.parse(e.data)
|
||||||
|
const { type, payload, meta: _meta = {} } = data
|
||||||
|
if (_meta.sequenceNumber !== meta.sequenceNumber)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (type === payloadType.execute.begin)
|
||||||
|
onBegin?.()
|
||||||
|
|
||||||
|
if (type === payloadType.execute.stdout) {
|
||||||
|
stdout += payload
|
||||||
|
if (stdout.endsWith('\n')) {
|
||||||
|
onStdout?.(stdout)
|
||||||
|
stdout = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === payloadType.execute.stderr) {
|
||||||
|
stderr += payload
|
||||||
|
if (stderr.endsWith('\n')) {
|
||||||
|
if (stderr.startsWith('error:')) {
|
||||||
|
const index = stderr.indexOf('\n')
|
||||||
|
onStderr?.(stderr.slice(0, index))
|
||||||
|
onStderr?.(stderr.slice(index + 1))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
onStderr?.(stderr)
|
||||||
|
}
|
||||||
|
stderr = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === payloadType.execute.end) {
|
||||||
|
if (payload.success === false)
|
||||||
|
onError?.(payload.exitDetail)
|
||||||
|
ws?.removeEventListener('message', onMessage)
|
||||||
|
onEnd?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ws?.addEventListener('message', onMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RustExecuteOptions {
|
||||||
|
onBegin?: () => void
|
||||||
|
onStdout?: (message: string) => void
|
||||||
|
onStderr?: (message: string) => void
|
||||||
|
onEnd?: () => void
|
||||||
|
onError?: (message: string) => void
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
let isBind = false
|
|
||||||
export function setupCanIUse(): void {
|
|
||||||
if (isBind)
|
|
||||||
return
|
|
||||||
isBind = true
|
|
||||||
|
|
||||||
window.addEventListener('message', (message) => {
|
|
||||||
const data = message.data
|
|
||||||
|
|
||||||
if (typeof data === 'string' && data.includes('ciu_embed')) {
|
|
||||||
const [, feature, height] = data.split(':')
|
|
||||||
const el = document.querySelector(`.ciu_embed[data-feature="${feature}"]:not([data-skip])`)
|
|
||||||
if (el) {
|
|
||||||
const h = Number.parseInt(height) + 30
|
|
||||||
;(el.childNodes[0] as any).height = `${h}px`
|
|
||||||
el.setAttribute('data-skip', 'true')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import { defineClientConfig } from 'vuepress/client'
|
|
||||||
import type { ClientConfig } from 'vuepress/client'
|
|
||||||
import { pluginOptions } from './options.js'
|
|
||||||
import { setupCanIUse } from './composables/setupCanIUse.js'
|
|
||||||
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 Plot from './components/Plot.vue'
|
|
||||||
|
|
||||||
import '@internal/md-power/icons.css'
|
|
||||||
|
|
||||||
declare const __VUEPRESS_SSR__: boolean
|
|
||||||
|
|
||||||
export default defineClientConfig({
|
|
||||||
enhance({ router, app }) {
|
|
||||||
if (pluginOptions.pdf)
|
|
||||||
app.component('PDFViewer', PDFViewer)
|
|
||||||
|
|
||||||
if (pluginOptions.bilibili)
|
|
||||||
app.component('VideoBilibili', Bilibili)
|
|
||||||
|
|
||||||
if (pluginOptions.youtube)
|
|
||||||
app.component('VideoYoutube', Youtube)
|
|
||||||
|
|
||||||
if (pluginOptions.replit)
|
|
||||||
app.component('ReplitViewer', Replit)
|
|
||||||
|
|
||||||
if (pluginOptions.codeSandbox)
|
|
||||||
app.component('CodeSandboxViewer', CodeSandbox)
|
|
||||||
|
|
||||||
if (pluginOptions.plot)
|
|
||||||
app.component('Plot', Plot)
|
|
||||||
|
|
||||||
if (__VUEPRESS_SSR__)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (pluginOptions.caniuse) {
|
|
||||||
router.afterEach(() => {
|
|
||||||
setupCanIUse()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}) as ClientConfig
|
|
||||||
28
plugins/plugin-md-power/src/client/utils/http.ts
Normal file
28
plugins/plugin-md-power/src/client/utils/http.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export const http = {
|
||||||
|
get: async <T extends object = object, R = any>(
|
||||||
|
url: string,
|
||||||
|
query?: T,
|
||||||
|
): Promise<R> => {
|
||||||
|
const _url = new URL(url)
|
||||||
|
if (query) {
|
||||||
|
for (const [key, value] of Object.entries(query))
|
||||||
|
_url.searchParams.append(key, value)
|
||||||
|
}
|
||||||
|
const res = await fetch(_url.toString())
|
||||||
|
return await res.json()
|
||||||
|
},
|
||||||
|
|
||||||
|
post: async <T extends object = object, R = any>(
|
||||||
|
url: string,
|
||||||
|
data?: T,
|
||||||
|
): Promise<R> => {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: data ? JSON.stringify(data) : undefined,
|
||||||
|
})
|
||||||
|
return await res.json()
|
||||||
|
},
|
||||||
|
}
|
||||||
5
plugins/plugin-md-power/src/client/utils/sleep.ts
Normal file
5
plugins/plugin-md-power/src/client/utils/sleep.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, ms)
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -7,8 +7,11 @@ import type Token from 'markdown-it/lib/token.mjs'
|
|||||||
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
import type { RuleBlock } from 'markdown-it/lib/parser_block.mjs'
|
||||||
import type { Markdown } from 'vuepress/markdown'
|
import type { Markdown } from 'vuepress/markdown'
|
||||||
import container from 'markdown-it-container'
|
import container from 'markdown-it-container'
|
||||||
|
import { customAlphabet } from 'nanoid'
|
||||||
import type { CanIUseMode, CanIUseOptions, CanIUseTokenMeta } from '../../shared/index.js'
|
import type { CanIUseMode, CanIUseOptions, CanIUseTokenMeta } from '../../shared/index.js'
|
||||||
|
|
||||||
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5)
|
||||||
|
|
||||||
// @[caniuse]()
|
// @[caniuse]()
|
||||||
const minLength = 12
|
const minLength = 12
|
||||||
|
|
||||||
@ -17,6 +20,7 @@ const START_CODES = [64, 91, 99, 97, 110, 105, 117, 115, 101]
|
|||||||
|
|
||||||
// regexp to match the import syntax
|
// regexp to match the import syntax
|
||||||
const SYNTAX_RE = /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/
|
const SYNTAX_RE = /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/
|
||||||
|
const UNDERLINE_RE = /_+/g
|
||||||
|
|
||||||
function createCanIUseRuleBlock(defaultMode: CanIUseMode): RuleBlock {
|
function createCanIUseRuleBlock(defaultMode: CanIUseMode): RuleBlock {
|
||||||
return (state, startLine, endLine, silent) => {
|
return (state, startLine, endLine, silent) => {
|
||||||
@ -76,18 +80,16 @@ function resolveCanIUse({ feature, mode, versions }: CanIUseTokenMeta): string {
|
|||||||
</picture></p></ClientOnly>`
|
</picture></p></ClientOnly>`
|
||||||
}
|
}
|
||||||
|
|
||||||
const periods = resolveVersions(versions)
|
feature = feature.replace(UNDERLINE_RE, '_')
|
||||||
const accessible = 'false'
|
const { past, future } = resolveVersions(versions)
|
||||||
const image = 'none'
|
const meta = nanoid()
|
||||||
const url = 'https://caniuse.bitsofco.de/embed/index.html'
|
|
||||||
const src = `${url}?feat=${feature}&periods=${periods}&accessible-colours=${accessible}&image-base=${image}`
|
|
||||||
|
|
||||||
return `<ClientOnly><div class="ciu_embed" style="margin:16px 0" data-feature="${feature}"><iframe src="${src}" frameborder="0" width="100%" height="400px" title="Can I use ${feature}"></iframe></div></ClientOnly>`
|
return `<CanIUseViewer feature="${feature}" meta="${meta}" past="${past}" future="${future}" />`
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveVersions(versions: string): string {
|
function resolveVersions(versions: string): { past: number, future: number } {
|
||||||
if (!versions)
|
if (!versions)
|
||||||
return 'future_1,current,past_1,past_2'
|
return { past: 2, future: 1 }
|
||||||
|
|
||||||
const list = versions
|
const list = versions
|
||||||
.split(',')
|
.split(',')
|
||||||
@ -97,16 +99,10 @@ function resolveVersions(versions: string): string {
|
|||||||
list.push(0)
|
list.push(0)
|
||||||
|
|
||||||
const uniq = [...new Set(list)].sort((a, b) => b - a)
|
const uniq = [...new Set(list)].sort((a, b) => b - a)
|
||||||
const result: string[] = []
|
return {
|
||||||
uniq.forEach((v) => {
|
future: uniq[0],
|
||||||
if (v < 0)
|
past: Math.abs(uniq[uniq.length - 1]),
|
||||||
result.push(`past_${Math.abs(v)}`)
|
}
|
||||||
if (v === 0)
|
|
||||||
result.push('current')
|
|
||||||
if (v > 0)
|
|
||||||
result.push(`future_${v}`)
|
|
||||||
})
|
|
||||||
return result.join(',')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
24
plugins/plugin-md-power/src/node/features/langRepl.ts
Normal file
24
plugins/plugin-md-power/src/node/features/langRepl.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type markdownIt from 'markdown-it'
|
||||||
|
import container from 'markdown-it-container'
|
||||||
|
import type Token from 'markdown-it/lib/token.mjs'
|
||||||
|
|
||||||
|
function createReplContainer(md: markdownIt, type: string) {
|
||||||
|
const validate = (info: string): boolean => info.trim().startsWith(type)
|
||||||
|
|
||||||
|
const render = (tokens: Token[], index: number): string => {
|
||||||
|
const token = tokens[index]
|
||||||
|
if (token.nesting === 1)
|
||||||
|
return '<LanguageRepl>'
|
||||||
|
|
||||||
|
else
|
||||||
|
return '</LanguageRepl>'
|
||||||
|
}
|
||||||
|
|
||||||
|
md.use(container, type, { validate, render })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function langReplPlugin(md: markdownIt) {
|
||||||
|
createReplContainer(md, 'kotlin-repl')
|
||||||
|
createReplContainer(md, 'go-repl')
|
||||||
|
createReplContainer(md, 'rust-repl')
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import type { Plugin } from 'vuepress/core'
|
import type { Plugin } from 'vuepress/core'
|
||||||
import { getDirname, path } from 'vuepress/utils'
|
|
||||||
import type { CanIUseOptions, MarkdownPowerPluginOptions } from '../shared/index.js'
|
import type { CanIUseOptions, MarkdownPowerPluginOptions } from '../shared/index.js'
|
||||||
import { caniusePlugin, legacyCaniuse } from './features/caniuse.js'
|
import { caniusePlugin, legacyCaniuse } from './features/caniuse.js'
|
||||||
import { pdfPlugin } from './features/pdf.js'
|
import { pdfPlugin } from './features/pdf.js'
|
||||||
@ -11,8 +10,8 @@ import { replitPlugin } from './features/replit.js'
|
|||||||
import { codeSandboxPlugin } from './features/codeSandbox.js'
|
import { codeSandboxPlugin } from './features/codeSandbox.js'
|
||||||
import { jsfiddlePlugin } from './features/jsfiddle.js'
|
import { jsfiddlePlugin } from './features/jsfiddle.js'
|
||||||
import { plotPlugin } from './features/plot.js'
|
import { plotPlugin } from './features/plot.js'
|
||||||
|
import { langReplPlugin } from './features/langRepl.js'
|
||||||
const __dirname = getDirname(import.meta.url)
|
import { prepareConfigFile } from './prepareConfigFile.js'
|
||||||
|
|
||||||
export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): Plugin {
|
export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): Plugin {
|
||||||
return (app) => {
|
return (app) => {
|
||||||
@ -21,7 +20,8 @@ export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): P
|
|||||||
return {
|
return {
|
||||||
name: '@vuepress-plume/plugin-md-power',
|
name: '@vuepress-plume/plugin-md-power',
|
||||||
|
|
||||||
clientConfigFile: path.resolve(__dirname, '../client/config.js'),
|
// clientConfigFile: path.resolve(__dirname, '../client/config.js'),
|
||||||
|
clientConfigFile: app => prepareConfigFile(app, options),
|
||||||
|
|
||||||
define: {
|
define: {
|
||||||
__MD_POWER_INJECT_OPTIONS__: options,
|
__MD_POWER_INJECT_OPTIONS__: options,
|
||||||
@ -85,6 +85,9 @@ export function markdownPowerPlugin(options: MarkdownPowerPluginOptions = {}): P
|
|||||||
// =|plot|=
|
// =|plot|=
|
||||||
md.use(plotPlugin)
|
md.use(plotPlugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.repl)
|
||||||
|
langReplPlugin(md)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
plugins/plugin-md-power/src/node/prepareConfigFile.ts
Normal file
81
plugins/plugin-md-power/src/node/prepareConfigFile.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { getDirname, path } from 'vuepress/utils'
|
||||||
|
import { ensureEndingSlash } from '@vuepress/helper'
|
||||||
|
import type { App } from 'vuepress/core'
|
||||||
|
import type { MarkdownPowerPluginOptions } from '../shared/index.js'
|
||||||
|
|
||||||
|
const { url: filepath } = import.meta
|
||||||
|
const __dirname = getDirname(filepath)
|
||||||
|
|
||||||
|
const CLIENT_FOLDER = ensureEndingSlash(
|
||||||
|
path.resolve(__dirname, '../client'),
|
||||||
|
)
|
||||||
|
|
||||||
|
export async function prepareConfigFile(app: App, options: MarkdownPowerPluginOptions) {
|
||||||
|
const imports = new Set<string>()
|
||||||
|
const enhances = new Set<string>()
|
||||||
|
|
||||||
|
imports.add(`import '@internal/md-power/icons.css'`)
|
||||||
|
|
||||||
|
if (options.pdf) {
|
||||||
|
imports.add(`import PDFViewer from '${CLIENT_FOLDER}components/PDFViewer.vue'`)
|
||||||
|
enhances.add(`app.component('PDFViewer', PDFViewer)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.bilibili) {
|
||||||
|
imports.add(`import Bilibili from '${CLIENT_FOLDER}components/Bilibili.vue'`)
|
||||||
|
enhances.add(`app.component('VideoBilibili', Bilibili)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.youtube) {
|
||||||
|
imports.add(`import Youtube from '${CLIENT_FOLDER}components/Youtube.vue'`)
|
||||||
|
enhances.add(`app.component('VideoYoutube', Youtube)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.replit) {
|
||||||
|
imports.add(`import Replit from '${CLIENT_FOLDER}components/Replit.vue'`)
|
||||||
|
enhances.add(`app.component('ReplitViewer', Replit)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.codeSandbox) {
|
||||||
|
imports.add(`import CodeSandbox from '${CLIENT_FOLDER}components/CodeSandbox.vue'`)
|
||||||
|
enhances.add(`app.component('CodeSandboxViewer', CodeSandbox)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.plot) {
|
||||||
|
imports.add(`import Plot from '${CLIENT_FOLDER}components/Plot.vue'`)
|
||||||
|
enhances.add(`app.component('Plot', Plot)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.repl) {
|
||||||
|
imports.add(`import LanguageRepl from '${CLIENT_FOLDER}components/LanguageRepl.vue'`)
|
||||||
|
enhances.add(`app.component('LanguageRepl', LanguageRepl)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// enhances.add(`if (__VUEPRESS_SSR__) return`)
|
||||||
|
|
||||||
|
if (options.caniuse) {
|
||||||
|
imports.add(`import CanIUse from '${CLIENT_FOLDER}components/CanIUse.vue'`)
|
||||||
|
enhances.add(`app.component('CanIUseViewer', CanIUse)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (options.caniuse) {
|
||||||
|
// imports.add(`import { setupCanIUse } from '${CLIENT_FOLDER}composables/setupCanIUse.js'`)
|
||||||
|
// enhances.add(`router.afterEach(() => setupCanIUse())`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
return app.writeTemp(
|
||||||
|
'md-power/config.js',
|
||||||
|
`\
|
||||||
|
import { defineClientConfig } from 'vuepress/client'
|
||||||
|
${Array.from(imports.values()).join('\n')}
|
||||||
|
|
||||||
|
export default defineClientConfig({
|
||||||
|
enhance({ router, app }) {
|
||||||
|
${Array.from(enhances.values())
|
||||||
|
.map(item => ` ${item}`)
|
||||||
|
.join('\n')}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -20,5 +20,8 @@ export interface MarkdownPowerPluginOptions {
|
|||||||
codeSandbox?: boolean
|
codeSandbox?: boolean
|
||||||
jsfiddle?: boolean
|
jsfiddle?: boolean
|
||||||
|
|
||||||
|
// container
|
||||||
|
repl?: boolean
|
||||||
|
|
||||||
caniuse?: boolean | CanIUseOptions
|
caniuse?: boolean | CanIUseOptions
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.20.2",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
"netlify-cli": "^17.22.1",
|
"netlify-cli": "^17.23.0",
|
||||||
"portfinder": "^1.0.32"
|
"portfinder": "^1.0.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -43,7 +43,7 @@
|
|||||||
"@vue/devtools-api": "6.5.1",
|
"@vue/devtools-api": "6.5.1",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"create-filter": "^1.0.1",
|
"create-filter": "^1.0.1",
|
||||||
"vue": "^3.4.23"
|
"vue": "^3.4.25"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@ -36,7 +36,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@netlify/functions": "^2.6.0",
|
"@netlify/functions": "^2.6.0",
|
||||||
"leancloud-storage": "^4.15.2",
|
"leancloud-storage": "^4.15.2",
|
||||||
"vue": "^3.4.23",
|
"vue": "^3.4.25",
|
||||||
"vue-router": "4.3.0",
|
"vue-router": "4.3.0",
|
||||||
"vuepress-plugin-netlify-functions": "workspace:*"
|
"vuepress-plugin-netlify-functions": "workspace:*"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
"mark.js": "^8.11.1",
|
"mark.js": "^8.11.1",
|
||||||
"minisearch": "^6.3.0",
|
"minisearch": "^6.3.0",
|
||||||
"p-map": "^7.0.2",
|
"p-map": "^7.0.2",
|
||||||
"vue": "^3.4.23"
|
"vue": "^3.4.25"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
2017
pnpm-lock.yaml
generated
2017
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,7 @@
|
|||||||
- 👀 支持 搜索、文章评论
|
- 👀 支持 搜索、文章评论
|
||||||
- 👨💻 支持 浅色/深色 主题 (包括代码高亮)
|
- 👨💻 支持 浅色/深色 主题 (包括代码高亮)
|
||||||
- 📠 markdown 增强,支持 代码块分组、提示容器、任务列表、数学公式、代码演示 等
|
- 📠 markdown 增强,支持 代码块分组、提示容器、任务列表、数学公式、代码演示 等
|
||||||
- 📚 代码演示,支持 CodePen, Replit
|
- 📚 代码演示,支持 CodePen, Replit, JSFiddle, CodeSandbox 等
|
||||||
- 📊 嵌入图标,支持 chart.js,Echarts,Mermaid,flowchart
|
- 📊 嵌入图标,支持 chart.js,Echarts,Mermaid,flowchart
|
||||||
- 🎛 资源嵌入,支持 PDF, bilibili视频,youtube视频等
|
- 🎛 资源嵌入,支持 PDF, bilibili视频,youtube视频等
|
||||||
|
|
||||||
|
|||||||
@ -97,9 +97,9 @@
|
|||||||
"katex": "^0.16.10",
|
"katex": "^0.16.10",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
"vue": "^3.4.23",
|
"vue": "^3.4.25",
|
||||||
"vue-router": "4.3.0",
|
"vue-router": "4.3.0",
|
||||||
"vuepress-plugin-md-enhance": "2.0.0-rc.36",
|
"vuepress-plugin-md-enhance": "2.0.0-rc.37",
|
||||||
"vuepress-plugin-md-power": "workspace:*"
|
"vuepress-plugin-md-power": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,5 +30,5 @@
|
|||||||
"docs/.vuepress/**/*",
|
"docs/.vuepress/**/*",
|
||||||
"scripts/**/*"
|
"scripts/**/*"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules", ".cache", ".temp", "lib", "dist"]
|
"exclude": ["**/node_modules/**", "**/.cache/**", "**/.temp/**", "**/lib/**", "**/dist/**"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user