vuepress-plugin-md-power 1.0.0-rc.52 → 1.0.0-rc.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client/components/CanIUse.vue +93 -0
- package/lib/client/components/IconClose.vue +8 -0
- package/lib/client/components/IconConsole.vue +8 -0
- package/lib/client/components/IconRun.vue +8 -0
- package/lib/client/components/LanguageRepl.vue +204 -0
- package/lib/client/components/Loading.vue +2 -1
- package/lib/client/components/Plot.vue +97 -0
- package/lib/client/composables/codeRepl.d.ts +19 -0
- package/lib/client/composables/codeRepl.js +150 -0
- package/lib/client/composables/pdf.js +8 -10
- package/lib/client/composables/rustRepl.d.ts +9 -0
- package/lib/client/composables/rustRepl.js +102 -0
- package/lib/client/utils/http.d.ts +4 -0
- package/lib/client/utils/http.js +21 -0
- package/lib/client/utils/sleep.d.ts +1 -0
- package/lib/client/utils/sleep.js +5 -0
- package/lib/node/features/caniuse.js +12 -17
- package/lib/node/features/icons/plugin.js +5 -4
- package/lib/node/features/langRepl.d.ts +2 -0
- package/lib/node/features/langRepl.js +17 -0
- package/lib/node/features/pdf.js +1 -1
- package/lib/node/features/plot.d.ts +5 -0
- package/lib/node/features/plot.js +48 -0
- package/lib/node/plugin.js +12 -3
- package/lib/node/prepareConfigFile.d.ts +3 -0
- package/lib/node/prepareConfigFile.js +59 -0
- package/lib/shared/index.d.ts +1 -0
- package/lib/shared/index.js +1 -0
- package/lib/shared/plot.d.ts +27 -0
- package/lib/shared/plot.js +1 -0
- package/lib/shared/plugin.d.ts +3 -0
- package/package.json +3 -3
- package/lib/client/composables/setupCanIUse.d.ts +0 -1
- package/lib/client/composables/setupCanIUse.js +0 -18
- package/lib/client/config.d.ts +0 -4
- package/lib/client/config.js +0 -30
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, getCurrentInstance, ref } from 'vue'
|
|
3
|
+
import { useEventListener } from '@vueuse/core'
|
|
4
|
+
|
|
5
|
+
interface MessageData {
|
|
6
|
+
type: string
|
|
7
|
+
payload?: {
|
|
8
|
+
feature?: string
|
|
9
|
+
meta?: string
|
|
10
|
+
height: number
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<{
|
|
15
|
+
feature: string
|
|
16
|
+
past?: string
|
|
17
|
+
future?: string
|
|
18
|
+
meta?: string
|
|
19
|
+
}>(), {
|
|
20
|
+
past: '2',
|
|
21
|
+
future: '1',
|
|
22
|
+
meta: '',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const url = 'https://caniuse.pengzhanbo.cn/'
|
|
26
|
+
const current = getCurrentInstance()
|
|
27
|
+
|
|
28
|
+
const height = ref('330px')
|
|
29
|
+
|
|
30
|
+
const isDark = computed(() => current?.appContext.config.globalProperties.$isDark.value)
|
|
31
|
+
const source = computed(() => {
|
|
32
|
+
const source = `${url}${props.feature}#past=${props.past}&future=${props.future}&meta=${props.meta}&theme=${isDark.value ? 'dark' : 'light'}`
|
|
33
|
+
|
|
34
|
+
return source
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
useEventListener('message', (event) => {
|
|
38
|
+
const data = parseData(event.data)
|
|
39
|
+
const { type, payload } = data
|
|
40
|
+
if (
|
|
41
|
+
type === 'ciu_embed'
|
|
42
|
+
&& payload
|
|
43
|
+
&& payload.feature === props.feature
|
|
44
|
+
&& payload.meta === props.meta
|
|
45
|
+
)
|
|
46
|
+
height.value = `${Math.ceil(payload.height)}px`
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
function parseData(data: string | MessageData): MessageData {
|
|
50
|
+
if (typeof data === 'string') {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(data)
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { type: '' }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return data
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
<div
|
|
64
|
+
class="ciu_embed"
|
|
65
|
+
:data-feature="feature"
|
|
66
|
+
:data-meta="meta"
|
|
67
|
+
:data-past="past"
|
|
68
|
+
:data-future="future"
|
|
69
|
+
>
|
|
70
|
+
<iframe
|
|
71
|
+
:src="source"
|
|
72
|
+
:style="{ height }"
|
|
73
|
+
:title="`Can I use ${feature}`"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<style scoped>
|
|
79
|
+
.ciu_embed {
|
|
80
|
+
margin: 16px -24px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ciu_embed iframe {
|
|
84
|
+
width: 100%;
|
|
85
|
+
border: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media (min-width: 768px) {
|
|
89
|
+
.ciu_embed {
|
|
90
|
+
margin: 16px 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
|
3
|
+
<path
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
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"
|
|
6
|
+
/>
|
|
7
|
+
</svg>
|
|
8
|
+
</template>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
|
3
|
+
<path
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
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"
|
|
6
|
+
/>
|
|
7
|
+
</svg>
|
|
8
|
+
</template>
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { shallowRef } from 'vue'
|
|
3
|
+
import { useCodeRepl } from '../composables/codeRepl.js'
|
|
4
|
+
import IconRun from './IconRun.vue'
|
|
5
|
+
import Loading from './Loading.vue'
|
|
6
|
+
import IconConsole from './IconConsole.vue'
|
|
7
|
+
import IconClose from './IconClose.vue'
|
|
8
|
+
|
|
9
|
+
const replEl = shallowRef<HTMLDivElement | null>(null)
|
|
10
|
+
const outputEl = shallowRef<HTMLDivElement | null>(null)
|
|
11
|
+
const {
|
|
12
|
+
onRunCode,
|
|
13
|
+
onCleanRun,
|
|
14
|
+
firstRun,
|
|
15
|
+
stderr,
|
|
16
|
+
stdout,
|
|
17
|
+
error,
|
|
18
|
+
loaded,
|
|
19
|
+
finished,
|
|
20
|
+
lang,
|
|
21
|
+
backendVersion,
|
|
22
|
+
} = useCodeRepl(replEl)
|
|
23
|
+
|
|
24
|
+
function runCode() {
|
|
25
|
+
onRunCode()
|
|
26
|
+
|
|
27
|
+
if (outputEl.value)
|
|
28
|
+
outputEl.value.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div ref="replEl" class="code-repl">
|
|
34
|
+
<span v-show="loaded && finished" class="icon-run" title="Run Code" @click="runCode">
|
|
35
|
+
<IconRun />
|
|
36
|
+
</span>
|
|
37
|
+
<slot />
|
|
38
|
+
<div ref="outputEl" class="code-repl-pin" />
|
|
39
|
+
<div v-if="!firstRun" class="code-repl-output">
|
|
40
|
+
<div class="output-head">
|
|
41
|
+
<IconConsole class="icon-console" />
|
|
42
|
+
<span class="title">console</span>
|
|
43
|
+
<span v-if="lang && backendVersion" class="output-version">
|
|
44
|
+
Running on: {{ lang }} <i>{{ backendVersion }}</i>
|
|
45
|
+
</span>
|
|
46
|
+
<IconClose class="icon-close" @click="onCleanRun" />
|
|
47
|
+
</div>
|
|
48
|
+
<div v-if="!loaded" class="output-content">
|
|
49
|
+
<Loading />
|
|
50
|
+
</div>
|
|
51
|
+
<div v-else class="output-content" :class="lang">
|
|
52
|
+
<p v-if="error" class="error">
|
|
53
|
+
{{ error }}
|
|
54
|
+
</p>
|
|
55
|
+
<div v-if="stderr.length" class="stderr">
|
|
56
|
+
<h4>Stderr:</h4>
|
|
57
|
+
<p
|
|
58
|
+
v-for="(item, index) in stderr" :key="index"
|
|
59
|
+
:class="{ error: lang === 'rust' && item.startsWith('error') }"
|
|
60
|
+
>
|
|
61
|
+
<pre>{{ item }}</pre>
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
<div v-if="stdout.length" class="stdout">
|
|
65
|
+
<h4 v-if="stderr.length">
|
|
66
|
+
Stdout:
|
|
67
|
+
</h4>
|
|
68
|
+
<p v-for="(item, index) in stdout" :key="index">
|
|
69
|
+
<pre>{{ item }}</pre>
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<style scoped>
|
|
78
|
+
.code-repl {
|
|
79
|
+
position: relative;
|
|
80
|
+
margin-bottom: 16px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.code-repl-output {
|
|
84
|
+
position: relative;
|
|
85
|
+
top: -20px;
|
|
86
|
+
padding-top: 6px;
|
|
87
|
+
margin: 0 -1.5rem;
|
|
88
|
+
background-color: var(--vp-code-block-bg);
|
|
89
|
+
transition: background-color, var(--t-color);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@media (min-width: 768px) {
|
|
93
|
+
.code-repl-output {
|
|
94
|
+
margin: 0;
|
|
95
|
+
border-bottom-right-radius: 6px;
|
|
96
|
+
border-bottom-left-radius: 6px;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.icon-run {
|
|
101
|
+
position: absolute;
|
|
102
|
+
top: -10px;
|
|
103
|
+
right: 10px;
|
|
104
|
+
z-index: 2;
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
width: 30px;
|
|
109
|
+
height: 30px;
|
|
110
|
+
font-size: 16px;
|
|
111
|
+
color: var(--vp-c-bg);
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
background-color: var(--vp-c-brand-1);
|
|
114
|
+
border-radius: 100%;
|
|
115
|
+
transition: var(--t-color);
|
|
116
|
+
transition-property: color, background-color;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@media (min-width: 768px) {
|
|
120
|
+
.icon-run {
|
|
121
|
+
top: 60px;
|
|
122
|
+
right: 16px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.icon-run:hover {
|
|
127
|
+
background-color: var(--vp-c-brand-2);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.code-repl-output .output-head {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: center;
|
|
133
|
+
justify-content: space-between;
|
|
134
|
+
padding: 4px 10px 4px 20px;
|
|
135
|
+
border-top: solid 2px var(--vp-c-border);
|
|
136
|
+
transition: border-color var(--t-color);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.output-head .title {
|
|
140
|
+
flex: 1;
|
|
141
|
+
margin-left: 10px;
|
|
142
|
+
font-size: 14px;
|
|
143
|
+
font-weight: 500;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.output-head .output-version {
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
color: var(--vp-c-text-3);
|
|
149
|
+
transition: color var(--t-color);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.output-head .icon-close {
|
|
153
|
+
width: 20px;
|
|
154
|
+
height: 20px;
|
|
155
|
+
margin-left: 20px;
|
|
156
|
+
color: var(--vp-c-text-3);
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
transition: color var(--t-color);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.output-head .icon-close:hover {
|
|
162
|
+
color: var(--vp-c-text-2);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.output-content {
|
|
166
|
+
padding: 12px 20px 24px;
|
|
167
|
+
overflow-x: auto;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.output-content h4 {
|
|
171
|
+
margin: 8px 0;
|
|
172
|
+
font-size: 16px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.output-content p {
|
|
176
|
+
margin: 0;
|
|
177
|
+
font-size: 14px;
|
|
178
|
+
line-height: 20px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.output-content p pre {
|
|
182
|
+
width: fit-content;
|
|
183
|
+
padding: 0 20px 0 0;
|
|
184
|
+
margin: 0;
|
|
185
|
+
overflow-x: initial;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.output-content .error,
|
|
189
|
+
.output-content .stderr p,
|
|
190
|
+
.output-content.rust .stderr p.error {
|
|
191
|
+
color: var(--vp-c-danger-1, #b8272c);
|
|
192
|
+
transition: color var(--t-color);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.output-content.rust .stderr p {
|
|
196
|
+
color: var(--vp-c-text-1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.output-content .stderr + .stdout {
|
|
200
|
+
margin-top: 12px;
|
|
201
|
+
border-top: 1px solid var(--vp-c-divider);
|
|
202
|
+
transition: border-color var(--t-color);
|
|
203
|
+
}
|
|
204
|
+
</style>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, shallowRef } from 'vue'
|
|
3
|
+
import { onClickOutside, useMediaQuery } from '@vueuse/core'
|
|
4
|
+
import { usePageFrontmatter } from 'vuepress/client'
|
|
5
|
+
import type { PlotOptions } from '../../shared/plot.js'
|
|
6
|
+
import { pluginOptions } from '../options.js'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<Omit<PlotOptions, 'tag'>>()
|
|
9
|
+
|
|
10
|
+
const matter = usePageFrontmatter()
|
|
11
|
+
|
|
12
|
+
const options = computed(() => {
|
|
13
|
+
const plot = typeof pluginOptions.plot === 'object' ? pluginOptions.plot : {}
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
trigger: props.trigger || matter.value.plotTrigger || plot.trigger || 'hover',
|
|
17
|
+
color: props.color || plot.color,
|
|
18
|
+
mask: props.mask || plot.mask,
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const styles = computed(() => {
|
|
23
|
+
const plot = options.value
|
|
24
|
+
if (!plot.color && !plot.mask)
|
|
25
|
+
return {}
|
|
26
|
+
const style: Record<string, string> = {}
|
|
27
|
+
if (plot.color) {
|
|
28
|
+
if (typeof plot.color === 'string') {
|
|
29
|
+
style['--vp-c-plot-light'] = plot.color
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
style['--vp-c-plot-light'] = plot.color.light
|
|
33
|
+
style['--vp-c-plot-dark'] = plot.color.dark
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (plot.mask) {
|
|
37
|
+
if (typeof plot.mask === 'string') {
|
|
38
|
+
style['--vp-c-bg-plot-light'] = plot.mask
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
style['--vp-c-bg-plot-light'] = plot.mask.light
|
|
42
|
+
style['--vp-c-bg-plot-dark'] = plot.mask.dark
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return style
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const isMobile = useMediaQuery('(max-width: 768px)')
|
|
49
|
+
const active = ref(false)
|
|
50
|
+
const el = shallowRef<HTMLElement>()
|
|
51
|
+
|
|
52
|
+
onClickOutside(el, () => {
|
|
53
|
+
if (options.value.trigger === 'click' || isMobile.value)
|
|
54
|
+
active.value = false
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
function onClick() {
|
|
58
|
+
if (props.trigger === 'click' || isMobile.value)
|
|
59
|
+
active.value = !active.value
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<span
|
|
65
|
+
ref="el"
|
|
66
|
+
class="vp-plot"
|
|
67
|
+
:class="{ hover: options.trigger !== 'click', active }"
|
|
68
|
+
:style="styles"
|
|
69
|
+
@click="onClick"
|
|
70
|
+
>
|
|
71
|
+
<slot />
|
|
72
|
+
</span>
|
|
73
|
+
</template>
|
|
74
|
+
|
|
75
|
+
<style>
|
|
76
|
+
.vp-plot {
|
|
77
|
+
padding-right: 2px;
|
|
78
|
+
padding-left: 2px;
|
|
79
|
+
color: transparent;
|
|
80
|
+
background-color: var(--vp-c-bg-plot-light, #000);
|
|
81
|
+
transition: color ease 0.25s, background-color ease 0.25s;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.dark .vp-plot {
|
|
85
|
+
background-color: var(--vp-c-bg-plot-dark, #fff);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.vp-plot.hover:hover,
|
|
89
|
+
.vp-plot.active {
|
|
90
|
+
color: var(--vp-c-plot-light, #fff);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.dark .vp-plot.hover:hover,
|
|
94
|
+
.dark .vp-plot.active {
|
|
95
|
+
color: var(--vp-c-plot-dark, #000);
|
|
96
|
+
}
|
|
97
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
type Lang = 'kotlin' | 'go' | 'rust';
|
|
3
|
+
export declare function resolveCodeInfo(el: HTMLDivElement): {
|
|
4
|
+
lang: Lang;
|
|
5
|
+
code: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function useCodeRepl(el: Ref<HTMLDivElement | null>): {
|
|
8
|
+
onRunCode: () => Promise<void>;
|
|
9
|
+
onCleanRun: () => void;
|
|
10
|
+
lang: Ref<Lang | undefined>;
|
|
11
|
+
backendVersion: Ref<string>;
|
|
12
|
+
firstRun: Ref<boolean>;
|
|
13
|
+
stderr: Ref<string[]>;
|
|
14
|
+
stdout: Ref<string[]>;
|
|
15
|
+
loaded: Ref<boolean>;
|
|
16
|
+
finished: Ref<boolean>;
|
|
17
|
+
error: Ref<string>;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
import { http } from '../utils/http.js';
|
|
3
|
+
import { sleep } from '../utils/sleep.js';
|
|
4
|
+
import { rustExecute } from './rustRepl.js';
|
|
5
|
+
const ignoredNodes = ['.diff.remove', '.vp-copy-ignore'];
|
|
6
|
+
const RE_LANGUAGE = /language-([\w]+)/;
|
|
7
|
+
const api = {
|
|
8
|
+
go: 'https://api.pengzhanbo.cn/repl/golang/run',
|
|
9
|
+
kotlin: 'https://api.pengzhanbo.cn/repl/kotlin/run',
|
|
10
|
+
};
|
|
11
|
+
const langAlias = {
|
|
12
|
+
kt: 'kotlin',
|
|
13
|
+
kotlin: 'kotlin',
|
|
14
|
+
go: 'go',
|
|
15
|
+
rust: 'rust',
|
|
16
|
+
rs: 'rust',
|
|
17
|
+
};
|
|
18
|
+
const supportLang = ['kotlin', 'go', 'rust'];
|
|
19
|
+
function resolveLang(lang) {
|
|
20
|
+
return lang ? langAlias[lang] || lang : '';
|
|
21
|
+
}
|
|
22
|
+
export function resolveCodeInfo(el) {
|
|
23
|
+
const wrapper = el.querySelector('[class*=language-]');
|
|
24
|
+
const lang = wrapper?.className.match(RE_LANGUAGE)?.[1];
|
|
25
|
+
const codeEl = wrapper?.querySelector('pre code');
|
|
26
|
+
let code = '';
|
|
27
|
+
if (codeEl) {
|
|
28
|
+
const clone = codeEl.cloneNode(true);
|
|
29
|
+
clone
|
|
30
|
+
.querySelectorAll(ignoredNodes.join(','))
|
|
31
|
+
.forEach(node => node.remove());
|
|
32
|
+
code = clone.textContent || '';
|
|
33
|
+
}
|
|
34
|
+
return { lang: resolveLang(lang), code };
|
|
35
|
+
}
|
|
36
|
+
export function useCodeRepl(el) {
|
|
37
|
+
const lang = ref();
|
|
38
|
+
const loaded = ref(true);
|
|
39
|
+
const firstRun = ref(true);
|
|
40
|
+
const finished = ref(true);
|
|
41
|
+
const stdout = ref([]); // like print
|
|
42
|
+
const stderr = ref([]); // like print error
|
|
43
|
+
const error = ref(''); // execute error
|
|
44
|
+
const backendVersion = ref('');
|
|
45
|
+
const executeMap = {
|
|
46
|
+
kotlin: executeKotlin,
|
|
47
|
+
go: executeGolang,
|
|
48
|
+
rust: executeRust,
|
|
49
|
+
};
|
|
50
|
+
function onCleanRun() {
|
|
51
|
+
loaded.value = false;
|
|
52
|
+
finished.value = false;
|
|
53
|
+
stdout.value = [];
|
|
54
|
+
stderr.value = [];
|
|
55
|
+
error.value = '';
|
|
56
|
+
firstRun.value = true;
|
|
57
|
+
backendVersion.value = '';
|
|
58
|
+
}
|
|
59
|
+
async function onRunCode() {
|
|
60
|
+
if (!el.value || !loaded.value)
|
|
61
|
+
return;
|
|
62
|
+
const info = resolveCodeInfo(el.value);
|
|
63
|
+
lang.value = info.lang;
|
|
64
|
+
if (!lang.value || !info.code || !supportLang.includes(lang.value))
|
|
65
|
+
return;
|
|
66
|
+
if (firstRun.value)
|
|
67
|
+
firstRun.value = false;
|
|
68
|
+
loaded.value = false;
|
|
69
|
+
finished.value = false;
|
|
70
|
+
stdout.value = [];
|
|
71
|
+
stderr.value = [];
|
|
72
|
+
error.value = '';
|
|
73
|
+
await executeMap[lang.value]?.(info.code);
|
|
74
|
+
}
|
|
75
|
+
async function executeGolang(code) {
|
|
76
|
+
const res = await http.post(api.go, { code });
|
|
77
|
+
backendVersion.value = `v${res.version}`;
|
|
78
|
+
loaded.value = true;
|
|
79
|
+
if (res.error) {
|
|
80
|
+
error.value = res.error;
|
|
81
|
+
finished.value = true;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const events = res.events || [];
|
|
85
|
+
for (const event of events) {
|
|
86
|
+
if (event.kind === 'stdout') {
|
|
87
|
+
if (event.delay)
|
|
88
|
+
await sleep(event.delay / 1000000);
|
|
89
|
+
stdout.value.push(event.message);
|
|
90
|
+
}
|
|
91
|
+
else if (event.kind === 'stderr') {
|
|
92
|
+
stderr.value.push(event.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
finished.value = true;
|
|
96
|
+
}
|
|
97
|
+
async function executeKotlin(code) {
|
|
98
|
+
const filename = 'File.kt';
|
|
99
|
+
const res = await http.post(api.kotlin, {
|
|
100
|
+
args: '',
|
|
101
|
+
files: [{ name: filename, publicId: '', text: code }],
|
|
102
|
+
});
|
|
103
|
+
backendVersion.value = `v${res.version}`;
|
|
104
|
+
loaded.value = true;
|
|
105
|
+
if (res.errors) {
|
|
106
|
+
const errors = Array.isArray(res.errors[filename]) ? res.errors[filename] : [res.errors[filename]];
|
|
107
|
+
if (errors.length) {
|
|
108
|
+
errors.forEach(({ message, severity }) => severity === 'ERROR' && stderr.value.push(message));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
stdout.value.push(res.text);
|
|
112
|
+
finished.value = true;
|
|
113
|
+
}
|
|
114
|
+
async function executeRust(code) {
|
|
115
|
+
await rustExecute(code, {
|
|
116
|
+
onBegin: () => {
|
|
117
|
+
loaded.value = true;
|
|
118
|
+
finished.value = false;
|
|
119
|
+
stdout.value = [];
|
|
120
|
+
stderr.value = [];
|
|
121
|
+
error.value = '';
|
|
122
|
+
backendVersion.value = 'release';
|
|
123
|
+
},
|
|
124
|
+
onError(message) {
|
|
125
|
+
error.value = message;
|
|
126
|
+
},
|
|
127
|
+
onStdout(message) {
|
|
128
|
+
stdout.value.push(message);
|
|
129
|
+
},
|
|
130
|
+
onStderr(message) {
|
|
131
|
+
stderr.value.push(message);
|
|
132
|
+
},
|
|
133
|
+
onEnd: () => {
|
|
134
|
+
finished.value = true;
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
onRunCode,
|
|
140
|
+
onCleanRun,
|
|
141
|
+
lang,
|
|
142
|
+
backendVersion,
|
|
143
|
+
firstRun,
|
|
144
|
+
stderr,
|
|
145
|
+
stdout,
|
|
146
|
+
loaded,
|
|
147
|
+
finished,
|
|
148
|
+
error,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ensureEndingSlash } from 'vuepress/shared';
|
|
1
|
+
import { ensureEndingSlash, isLinkHttp } from 'vuepress/shared';
|
|
2
2
|
import { withBase } from 'vuepress/client';
|
|
3
|
-
import { normalizeLink } from '../utils/link.js';
|
|
4
3
|
import { pluginOptions } from '../options.js';
|
|
5
4
|
import { checkIsMobile, checkIsSafari, checkIsiPad } from '../utils/is.js';
|
|
6
5
|
function queryStringify(options) {
|
|
@@ -18,14 +17,15 @@ function queryStringify(options) {
|
|
|
18
17
|
export function renderPDF(el, url, embedType, options) {
|
|
19
18
|
if (!pluginOptions.pdf)
|
|
20
19
|
return;
|
|
21
|
-
url =
|
|
20
|
+
url = isLinkHttp(url)
|
|
21
|
+
? url
|
|
22
|
+
: new URL(withBase(url), typeof location !== 'undefined' ? location.href : '').toString();
|
|
22
23
|
const pdfOptions = pluginOptions.pdf === true ? {} : pluginOptions.pdf;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
: '';
|
|
24
|
+
pdfOptions.pdfjsUrl ??= 'https://static.pengzhanbo.cn/pdfjs/';
|
|
25
|
+
const pdfjsUrl = `${ensureEndingSlash(withBase(pdfOptions.pdfjsUrl))}web/viewer.html`;
|
|
26
26
|
const queryString = queryStringify(options);
|
|
27
27
|
const source = embedType === 'pdfjs'
|
|
28
|
-
? `${pdfjsUrl}?file=${
|
|
28
|
+
? `${pdfjsUrl}?file=${url}${queryString}`
|
|
29
29
|
: `${url}${queryString}`;
|
|
30
30
|
const tagName = embedType === 'pdfjs' || embedType === 'iframe'
|
|
31
31
|
? 'iframe'
|
|
@@ -69,7 +69,5 @@ export function usePDF(el, url, options) {
|
|
|
69
69
|
const embedType = isSafariDesktop ? 'iframe' : 'embed';
|
|
70
70
|
return renderPDF(el, url, embedType, options);
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
return renderPDF(el, url, 'pdfjs', options);
|
|
74
|
-
el.innerHTML = `<p>This browser does not support embedding PDFs. Please download the PDF to view it: <a href='${url}' target='_blank'>Download PDF</a></p>`;
|
|
72
|
+
return renderPDF(el, url, 'pdfjs', options);
|
|
75
73
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function rustExecute(code: string, { onEnd, onError, onStderr, onStdout, onBegin }: RustExecuteOptions): Promise<void>;
|
|
2
|
+
interface RustExecuteOptions {
|
|
3
|
+
onBegin?: () => void;
|
|
4
|
+
onStdout?: (message: string) => void;
|
|
5
|
+
onStderr?: (message: string) => void;
|
|
6
|
+
onEnd?: () => void;
|
|
7
|
+
onError?: (message: string) => void;
|
|
8
|
+
}
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 相比于 golang 和 kotlin 可以比较简单的实现,
|
|
3
|
+
* rust 需要通过 websocket 建立连接在实现交互,因此,将其进行一些包装,
|
|
4
|
+
* 方便在 codeRepl 中使用
|
|
5
|
+
*/
|
|
6
|
+
import { tryOnScopeDispose } from '@vueuse/core';
|
|
7
|
+
const wsUrl = 'wss://play.rust-lang.org/websocket';
|
|
8
|
+
const payloadType = {
|
|
9
|
+
connected: 'websocket/connected',
|
|
10
|
+
request: 'output/execute/wsExecuteRequest',
|
|
11
|
+
execute: {
|
|
12
|
+
begin: 'output/execute/wsExecuteBegin',
|
|
13
|
+
// status: 'output/execute/wsExecuteStatus',
|
|
14
|
+
stderr: 'output/execute/wsExecuteStderr',
|
|
15
|
+
stdout: 'output/execute/wsExecuteStdout',
|
|
16
|
+
end: 'output/execute/wsExecuteEnd',
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
let ws = null;
|
|
20
|
+
let isOpen = false;
|
|
21
|
+
let uuid = 0;
|
|
22
|
+
function connect() {
|
|
23
|
+
if (isOpen)
|
|
24
|
+
return Promise.resolve();
|
|
25
|
+
ws = new WebSocket(wsUrl);
|
|
26
|
+
uuid = 0;
|
|
27
|
+
ws.addEventListener('open', () => {
|
|
28
|
+
isOpen = true;
|
|
29
|
+
send(payloadType.connected, { iAcceptThisIsAnUnsupportedApi: true }, { websocket: true, sequenceNumber: uuid });
|
|
30
|
+
});
|
|
31
|
+
ws.addEventListener('close', () => {
|
|
32
|
+
isOpen = false;
|
|
33
|
+
ws = null;
|
|
34
|
+
});
|
|
35
|
+
tryOnScopeDispose(() => ws?.close());
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
function connected(e) {
|
|
38
|
+
const data = JSON.parse(e.data);
|
|
39
|
+
if (data.type === payloadType.connected) {
|
|
40
|
+
ws?.removeEventListener('message', connected);
|
|
41
|
+
resolve();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
ws?.addEventListener('message', connected);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function send(type, payload, meta) {
|
|
48
|
+
const msg = { type, meta, payload };
|
|
49
|
+
ws?.send(JSON.stringify(msg));
|
|
50
|
+
}
|
|
51
|
+
export async function rustExecute(code, { onEnd, onError, onStderr, onStdout, onBegin }) {
|
|
52
|
+
await connect();
|
|
53
|
+
const meta = { sequenceNumber: uuid++ };
|
|
54
|
+
const payload = {
|
|
55
|
+
backtrace: false,
|
|
56
|
+
channel: 'stable',
|
|
57
|
+
crateType: 'bin',
|
|
58
|
+
edition: '2021',
|
|
59
|
+
mode: 'release',
|
|
60
|
+
tests: false,
|
|
61
|
+
code,
|
|
62
|
+
};
|
|
63
|
+
send(payloadType.request, payload, meta);
|
|
64
|
+
let stdout = '';
|
|
65
|
+
let stderr = '';
|
|
66
|
+
function onMessage(e) {
|
|
67
|
+
const data = JSON.parse(e.data);
|
|
68
|
+
const { type, payload, meta: _meta = {} } = data;
|
|
69
|
+
if (_meta.sequenceNumber !== meta.sequenceNumber)
|
|
70
|
+
return;
|
|
71
|
+
if (type === payloadType.execute.begin)
|
|
72
|
+
onBegin?.();
|
|
73
|
+
if (type === payloadType.execute.stdout) {
|
|
74
|
+
stdout += payload;
|
|
75
|
+
if (stdout.endsWith('\n')) {
|
|
76
|
+
onStdout?.(stdout);
|
|
77
|
+
stdout = '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (type === payloadType.execute.stderr) {
|
|
81
|
+
stderr += payload;
|
|
82
|
+
if (stderr.endsWith('\n')) {
|
|
83
|
+
if (stderr.startsWith('error:')) {
|
|
84
|
+
const index = stderr.indexOf('\n');
|
|
85
|
+
onStderr?.(stderr.slice(0, index));
|
|
86
|
+
onStderr?.(stderr.slice(index + 1));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
onStderr?.(stderr);
|
|
90
|
+
}
|
|
91
|
+
stderr = '';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (type === payloadType.execute.end) {
|
|
95
|
+
if (payload.success === false)
|
|
96
|
+
onError?.(payload.exitDetail);
|
|
97
|
+
ws?.removeEventListener('message', onMessage);
|
|
98
|
+
onEnd?.();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
ws?.addEventListener('message', onMessage);
|
|
102
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const http = {
|
|
2
|
+
get: async (url, query) => {
|
|
3
|
+
const _url = new URL(url);
|
|
4
|
+
if (query) {
|
|
5
|
+
for (const [key, value] of Object.entries(query))
|
|
6
|
+
_url.searchParams.append(key, value);
|
|
7
|
+
}
|
|
8
|
+
const res = await fetch(_url.toString());
|
|
9
|
+
return await res.json();
|
|
10
|
+
},
|
|
11
|
+
post: async (url, data) => {
|
|
12
|
+
const res = await fetch(url, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
},
|
|
17
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
18
|
+
});
|
|
19
|
+
return await res.json();
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import container from 'markdown-it-container';
|
|
2
|
+
import { customAlphabet } from 'nanoid';
|
|
3
|
+
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5);
|
|
2
4
|
// @[caniuse]()
|
|
3
5
|
const minLength = 12;
|
|
4
6
|
// char codes of '@[caniuse'
|
|
5
7
|
const START_CODES = [64, 91, 99, 97, 110, 105, 117, 115, 101];
|
|
6
8
|
// regexp to match the import syntax
|
|
7
9
|
const SYNTAX_RE = /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/;
|
|
10
|
+
const UNDERLINE_RE = /_+/g;
|
|
8
11
|
function createCanIUseRuleBlock(defaultMode) {
|
|
9
12
|
return (state, startLine, endLine, silent) => {
|
|
10
13
|
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
@@ -50,32 +53,24 @@ function resolveCanIUse({ feature, mode, versions }) {
|
|
|
50
53
|
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
|
|
51
54
|
</picture></p></ClientOnly>`;
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const src = `${url}?feat=${feature}&periods=${periods}&accessible-colours=${accessible}&image-base=${image}`;
|
|
58
|
-
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>`;
|
|
56
|
+
feature = feature.replace(UNDERLINE_RE, '_');
|
|
57
|
+
const { past, future } = resolveVersions(versions);
|
|
58
|
+
const meta = nanoid();
|
|
59
|
+
return `<CanIUseViewer feature="${feature}" meta="${meta}" past="${past}" future="${future}" />`;
|
|
59
60
|
}
|
|
60
61
|
function resolveVersions(versions) {
|
|
61
62
|
if (!versions)
|
|
62
|
-
return
|
|
63
|
+
return { past: 2, future: 1 };
|
|
63
64
|
const list = versions
|
|
64
65
|
.split(',')
|
|
65
66
|
.map(v => Number(v.trim()))
|
|
66
67
|
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3);
|
|
67
68
|
list.push(0);
|
|
68
69
|
const uniq = [...new Set(list)].sort((a, b) => b - a);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (v === 0)
|
|
74
|
-
result.push('current');
|
|
75
|
-
if (v > 0)
|
|
76
|
-
result.push(`future_${v}`);
|
|
77
|
-
});
|
|
78
|
-
return result.join(',');
|
|
70
|
+
return {
|
|
71
|
+
future: uniq[0],
|
|
72
|
+
past: Math.abs(uniq[uniq.length - 1]),
|
|
73
|
+
};
|
|
79
74
|
}
|
|
80
75
|
/**
|
|
81
76
|
* @example
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { parseRect } from '../../utils/parseRect.js';
|
|
2
|
+
const [openTag, endTag] = [':[', ']:'];
|
|
2
3
|
function createTokenizer(addIcon) {
|
|
3
4
|
return (state, silent) => {
|
|
4
5
|
let found = false;
|
|
5
6
|
const max = state.posMax;
|
|
6
7
|
const start = state.pos;
|
|
7
|
-
if (state.src.slice(start, start + 2) !==
|
|
8
|
+
if (state.src.slice(start, start + 2) !== openTag)
|
|
8
9
|
return false;
|
|
9
10
|
if (silent)
|
|
10
11
|
return false;
|
|
@@ -13,7 +14,7 @@ function createTokenizer(addIcon) {
|
|
|
13
14
|
return false;
|
|
14
15
|
state.pos = start + 2;
|
|
15
16
|
while (state.pos < max) {
|
|
16
|
-
if (state.src.slice(state.pos, state.pos + 2) ===
|
|
17
|
+
if (state.src.slice(state.pos, state.pos + 2) === endTag) {
|
|
17
18
|
found = true;
|
|
18
19
|
break;
|
|
19
20
|
}
|
|
@@ -35,7 +36,7 @@ function createTokenizer(addIcon) {
|
|
|
35
36
|
const [iconName, options = ''] = content.split(/\s+/);
|
|
36
37
|
const [size, color] = options.split('/');
|
|
37
38
|
const open = state.push('iconify_open', 'span', 1);
|
|
38
|
-
open.markup =
|
|
39
|
+
open.markup = openTag;
|
|
39
40
|
const className = addIcon(iconName);
|
|
40
41
|
if (className)
|
|
41
42
|
open.attrSet('class', className);
|
|
@@ -49,7 +50,7 @@ function createTokenizer(addIcon) {
|
|
|
49
50
|
const text = state.push('text', '', 0);
|
|
50
51
|
text.content = className ? '' : iconName;
|
|
51
52
|
const close = state.push('iconify_close', 'span', -1);
|
|
52
|
-
close.markup =
|
|
53
|
+
close.markup = endTag;
|
|
53
54
|
state.pos = state.posMax + 2;
|
|
54
55
|
state.posMax = max;
|
|
55
56
|
return true;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import container from 'markdown-it-container';
|
|
2
|
+
function createReplContainer(md, type) {
|
|
3
|
+
const validate = (info) => info.trim().startsWith(type);
|
|
4
|
+
const render = (tokens, index) => {
|
|
5
|
+
const token = tokens[index];
|
|
6
|
+
if (token.nesting === 1)
|
|
7
|
+
return '<LanguageRepl>';
|
|
8
|
+
else
|
|
9
|
+
return '</LanguageRepl>';
|
|
10
|
+
};
|
|
11
|
+
md.use(container, type, { validate, render });
|
|
12
|
+
}
|
|
13
|
+
export function langReplPlugin(md) {
|
|
14
|
+
createReplContainer(md, 'kotlin-repl');
|
|
15
|
+
createReplContainer(md, 'go-repl');
|
|
16
|
+
createReplContainer(md, 'rust-repl');
|
|
17
|
+
}
|
package/lib/node/features/pdf.js
CHANGED
|
@@ -37,7 +37,7 @@ function createPDFRuleBlock() {
|
|
|
37
37
|
src,
|
|
38
38
|
page: +page || 1,
|
|
39
39
|
noToolbar: Boolean(attrs.noToolbar ?? false),
|
|
40
|
-
zoom: +attrs.zoom ||
|
|
40
|
+
zoom: +attrs.zoom || 50,
|
|
41
41
|
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
42
42
|
height: attrs.height ? parseRect(attrs.height) : '',
|
|
43
43
|
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const [openTag, endTag] = ['=|', '|='];
|
|
2
|
+
function createTokenizer() {
|
|
3
|
+
return (state, silent) => {
|
|
4
|
+
let found = false;
|
|
5
|
+
const max = state.posMax;
|
|
6
|
+
const start = state.pos;
|
|
7
|
+
if (state.src.slice(start, start + 2) !== openTag)
|
|
8
|
+
return false;
|
|
9
|
+
if (silent)
|
|
10
|
+
return false;
|
|
11
|
+
// =||=
|
|
12
|
+
if (max - start < 5)
|
|
13
|
+
return false;
|
|
14
|
+
state.pos = start + 2;
|
|
15
|
+
while (state.pos < max) {
|
|
16
|
+
if (state.src.slice(state.pos - 1, state.pos + 1) === endTag) {
|
|
17
|
+
found = true;
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
state.md.inline.skipToken(state);
|
|
21
|
+
}
|
|
22
|
+
if (!found || start + 2 === state.pos) {
|
|
23
|
+
state.pos = start;
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const content = state.src.slice(start + 2, state.pos - 1);
|
|
27
|
+
// 不允许前后带有空格
|
|
28
|
+
if (/^\s|\s$/.test(content)) {
|
|
29
|
+
state.pos = start;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// found!
|
|
33
|
+
state.posMax = state.pos - 1;
|
|
34
|
+
state.pos = start + 2;
|
|
35
|
+
const open = state.push('plot_open', 'Plot', 1);
|
|
36
|
+
open.markup = openTag;
|
|
37
|
+
const text = state.push('text', '', 0);
|
|
38
|
+
text.content = content;
|
|
39
|
+
const close = state.push('plot_close', 'Plot', -1);
|
|
40
|
+
close.markup = endTag;
|
|
41
|
+
state.pos = state.posMax + 2;
|
|
42
|
+
state.posMax = max;
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export const plotPlugin = (md) => {
|
|
47
|
+
md.inline.ruler.before('emphasis', 'plot', createTokenizer());
|
|
48
|
+
};
|
package/lib/node/plugin.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getDirname, path } from 'vuepress/utils';
|
|
2
1
|
import { caniusePlugin, legacyCaniuse } from './features/caniuse.js';
|
|
3
2
|
import { pdfPlugin } from './features/pdf.js';
|
|
4
3
|
import { createIconCSSWriter, iconsPlugin } from './features/icons/index.js';
|
|
@@ -8,13 +7,16 @@ import { codepenPlugin } from './features/codepen.js';
|
|
|
8
7
|
import { replitPlugin } from './features/replit.js';
|
|
9
8
|
import { codeSandboxPlugin } from './features/codeSandbox.js';
|
|
10
9
|
import { jsfiddlePlugin } from './features/jsfiddle.js';
|
|
11
|
-
|
|
10
|
+
import { plotPlugin } from './features/plot.js';
|
|
11
|
+
import { langReplPlugin } from './features/langRepl.js';
|
|
12
|
+
import { prepareConfigFile } from './prepareConfigFile.js';
|
|
12
13
|
export function markdownPowerPlugin(options = {}) {
|
|
13
14
|
return (app) => {
|
|
14
15
|
const { initIcon, addIcon } = createIconCSSWriter(app, options.icons);
|
|
15
16
|
return {
|
|
16
17
|
name: '@vuepress-plume/plugin-md-power',
|
|
17
|
-
clientConfigFile: path.resolve(__dirname, '../client/config.js'),
|
|
18
|
+
// clientConfigFile: path.resolve(__dirname, '../client/config.js'),
|
|
19
|
+
clientConfigFile: app => prepareConfigFile(app, options),
|
|
18
20
|
define: {
|
|
19
21
|
__MD_POWER_INJECT_OPTIONS__: options,
|
|
20
22
|
},
|
|
@@ -59,6 +61,13 @@ export function markdownPowerPlugin(options = {}) {
|
|
|
59
61
|
// @[jsfiddle](user/id)
|
|
60
62
|
md.use(jsfiddlePlugin);
|
|
61
63
|
}
|
|
64
|
+
if (options.plot === true
|
|
65
|
+
|| (typeof options.plot === 'object' && options.plot.tag !== false)) {
|
|
66
|
+
// =|plot|=
|
|
67
|
+
md.use(plotPlugin);
|
|
68
|
+
}
|
|
69
|
+
if (options.repl)
|
|
70
|
+
langReplPlugin(md);
|
|
62
71
|
},
|
|
63
72
|
};
|
|
64
73
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { getDirname, path } from 'vuepress/utils';
|
|
2
|
+
import { ensureEndingSlash } from '@vuepress/helper';
|
|
3
|
+
const { url: filepath } = import.meta;
|
|
4
|
+
const __dirname = getDirname(filepath);
|
|
5
|
+
const CLIENT_FOLDER = ensureEndingSlash(path.resolve(__dirname, '../client'));
|
|
6
|
+
export async function prepareConfigFile(app, options) {
|
|
7
|
+
const imports = new Set();
|
|
8
|
+
const enhances = new Set();
|
|
9
|
+
imports.add(`import '@internal/md-power/icons.css'`);
|
|
10
|
+
if (options.pdf) {
|
|
11
|
+
imports.add(`import PDFViewer from '${CLIENT_FOLDER}components/PDFViewer.vue'`);
|
|
12
|
+
enhances.add(`app.component('PDFViewer', PDFViewer)`);
|
|
13
|
+
}
|
|
14
|
+
if (options.bilibili) {
|
|
15
|
+
imports.add(`import Bilibili from '${CLIENT_FOLDER}components/Bilibili.vue'`);
|
|
16
|
+
enhances.add(`app.component('VideoBilibili', Bilibili)`);
|
|
17
|
+
}
|
|
18
|
+
if (options.youtube) {
|
|
19
|
+
imports.add(`import Youtube from '${CLIENT_FOLDER}components/Youtube.vue'`);
|
|
20
|
+
enhances.add(`app.component('VideoYoutube', Youtube)`);
|
|
21
|
+
}
|
|
22
|
+
if (options.replit) {
|
|
23
|
+
imports.add(`import Replit from '${CLIENT_FOLDER}components/Replit.vue'`);
|
|
24
|
+
enhances.add(`app.component('ReplitViewer', Replit)`);
|
|
25
|
+
}
|
|
26
|
+
if (options.codeSandbox) {
|
|
27
|
+
imports.add(`import CodeSandbox from '${CLIENT_FOLDER}components/CodeSandbox.vue'`);
|
|
28
|
+
enhances.add(`app.component('CodeSandboxViewer', CodeSandbox)`);
|
|
29
|
+
}
|
|
30
|
+
if (options.plot) {
|
|
31
|
+
imports.add(`import Plot from '${CLIENT_FOLDER}components/Plot.vue'`);
|
|
32
|
+
enhances.add(`app.component('Plot', Plot)`);
|
|
33
|
+
}
|
|
34
|
+
if (options.repl) {
|
|
35
|
+
imports.add(`import LanguageRepl from '${CLIENT_FOLDER}components/LanguageRepl.vue'`);
|
|
36
|
+
enhances.add(`app.component('LanguageRepl', LanguageRepl)`);
|
|
37
|
+
}
|
|
38
|
+
// enhances.add(`if (__VUEPRESS_SSR__) return`)
|
|
39
|
+
if (options.caniuse) {
|
|
40
|
+
imports.add(`import CanIUse from '${CLIENT_FOLDER}components/CanIUse.vue'`);
|
|
41
|
+
enhances.add(`app.component('CanIUseViewer', CanIUse)`);
|
|
42
|
+
}
|
|
43
|
+
// if (options.caniuse) {
|
|
44
|
+
// imports.add(`import { setupCanIUse } from '${CLIENT_FOLDER}composables/setupCanIUse.js'`)
|
|
45
|
+
// enhances.add(`router.afterEach(() => setupCanIUse())`)
|
|
46
|
+
// }
|
|
47
|
+
return app.writeTemp('md-power/config.js', `\
|
|
48
|
+
import { defineClientConfig } from 'vuepress/client'
|
|
49
|
+
${Array.from(imports.values()).join('\n')}
|
|
50
|
+
|
|
51
|
+
export default defineClientConfig({
|
|
52
|
+
enhance({ router, app }) {
|
|
53
|
+
${Array.from(enhances.values())
|
|
54
|
+
.map(item => ` ${item}`)
|
|
55
|
+
.join('\n')}
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
`);
|
|
59
|
+
}
|
package/lib/shared/index.d.ts
CHANGED
package/lib/shared/index.js
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface PlotOptions {
|
|
2
|
+
/**
|
|
3
|
+
* 是否启用 `=| |=` markdown (该标记为非标准标记,脱离插件将不生效)
|
|
4
|
+
* @default true
|
|
5
|
+
*/
|
|
6
|
+
tag?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* 遮罩层颜色
|
|
9
|
+
*/
|
|
10
|
+
mask?: string | {
|
|
11
|
+
light: string;
|
|
12
|
+
dark: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* 文本颜色
|
|
16
|
+
*/
|
|
17
|
+
color?: string | {
|
|
18
|
+
light: string;
|
|
19
|
+
dark: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* 触发方式
|
|
23
|
+
*
|
|
24
|
+
* @default 'hover'
|
|
25
|
+
*/
|
|
26
|
+
trigger?: 'hover' | 'click';
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/shared/plugin.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { CanIUseOptions } from './caniuse.js';
|
|
2
2
|
import type { PDFOptions } from './pdf.js';
|
|
3
3
|
import type { IconsOptions } from './icons.js';
|
|
4
|
+
import type { PlotOptions } from './plot.js';
|
|
4
5
|
export interface MarkdownPowerPluginOptions {
|
|
5
6
|
pdf?: boolean | PDFOptions;
|
|
6
7
|
icons?: boolean | IconsOptions;
|
|
8
|
+
plot?: boolean | PlotOptions;
|
|
7
9
|
bilibili?: boolean;
|
|
8
10
|
youtube?: boolean;
|
|
9
11
|
codepen?: boolean;
|
|
10
12
|
replit?: boolean;
|
|
11
13
|
codeSandbox?: boolean;
|
|
12
14
|
jsfiddle?: boolean;
|
|
15
|
+
repl?: boolean;
|
|
13
16
|
caniuse?: boolean | CanIUseOptions;
|
|
14
17
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vuepress-plugin-md-power",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.54",
|
|
5
5
|
"description": "The Plugin for VuePres 2 - markdown power",
|
|
6
6
|
"author": "pengzhanbo <volodymyr@foxmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"local-pkg": "^0.5.0",
|
|
46
46
|
"markdown-it-container": "^4.0.0",
|
|
47
47
|
"nanoid": "^5.0.7",
|
|
48
|
-
"vue": "^3.4.
|
|
48
|
+
"vue": "^3.4.25"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@iconify/json": "^2.2.
|
|
51
|
+
"@iconify/json": "^2.2.204",
|
|
52
52
|
"@types/markdown-it": "^14.0.1"
|
|
53
53
|
},
|
|
54
54
|
"publishConfig": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function setupCanIUse(): void;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
let isBind = false;
|
|
2
|
-
export function setupCanIUse() {
|
|
3
|
-
if (isBind)
|
|
4
|
-
return;
|
|
5
|
-
isBind = true;
|
|
6
|
-
window.addEventListener('message', (message) => {
|
|
7
|
-
const data = message.data;
|
|
8
|
-
if (typeof data === 'string' && data.includes('ciu_embed')) {
|
|
9
|
-
const [, feature, height] = data.split(':');
|
|
10
|
-
const el = document.querySelector(`.ciu_embed[data-feature="${feature}"]:not([data-skip])`);
|
|
11
|
-
if (el) {
|
|
12
|
-
const h = Number.parseInt(height) + 30;
|
|
13
|
-
el.childNodes[0].height = `${h}px`;
|
|
14
|
-
el.setAttribute('data-skip', 'true');
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
}
|
package/lib/client/config.d.ts
DELETED
package/lib/client/config.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { defineClientConfig } from 'vuepress/client';
|
|
2
|
-
import { pluginOptions } from './options.js';
|
|
3
|
-
import { setupCanIUse } from './composables/setupCanIUse.js';
|
|
4
|
-
import PDFViewer from './components/PDFViewer.vue';
|
|
5
|
-
import Bilibili from './components/Bilibili.vue';
|
|
6
|
-
import Youtube from './components/Youtube.vue';
|
|
7
|
-
import Replit from './components/Replit.vue';
|
|
8
|
-
import CodeSandbox from './components/CodeSandbox.vue';
|
|
9
|
-
import '@internal/md-power/icons.css';
|
|
10
|
-
export default defineClientConfig({
|
|
11
|
-
enhance({ router, app }) {
|
|
12
|
-
if (pluginOptions.pdf)
|
|
13
|
-
app.component('PDFViewer', PDFViewer);
|
|
14
|
-
if (pluginOptions.bilibili)
|
|
15
|
-
app.component('VideoBilibili', Bilibili);
|
|
16
|
-
if (pluginOptions.youtube)
|
|
17
|
-
app.component('VideoYoutube', Youtube);
|
|
18
|
-
if (pluginOptions.replit)
|
|
19
|
-
app.component('ReplitViewer', Replit);
|
|
20
|
-
if (pluginOptions.codeSandbox)
|
|
21
|
-
app.component('CodeSandboxViewer', CodeSandbox);
|
|
22
|
-
if (__VUEPRESS_SSR__)
|
|
23
|
-
return;
|
|
24
|
-
if (pluginOptions.caniuse) {
|
|
25
|
-
router.afterEach(() => {
|
|
26
|
-
setupCanIUse();
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
});
|