vuepress-plugin-md-power 1.0.0-rc.53 → 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/composables/codeRepl.d.ts +19 -0
- package/lib/client/composables/codeRepl.js +150 -0
- 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/langRepl.d.ts +2 -0
- package/lib/node/features/langRepl.js +17 -0
- package/lib/node/plugin.js +6 -3
- package/lib/node/prepareConfigFile.d.ts +3 -0
- package/lib/node/prepareConfigFile.js +59 -0
- package/lib/shared/plugin.d.ts +1 -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 -33
|
@@ -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,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
|
+
}
|
|
@@ -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
|
|
@@ -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/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';
|
|
@@ -9,13 +8,15 @@ 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';
|
|
12
|
-
|
|
11
|
+
import { langReplPlugin } from './features/langRepl.js';
|
|
12
|
+
import { prepareConfigFile } from './prepareConfigFile.js';
|
|
13
13
|
export function markdownPowerPlugin(options = {}) {
|
|
14
14
|
return (app) => {
|
|
15
15
|
const { initIcon, addIcon } = createIconCSSWriter(app, options.icons);
|
|
16
16
|
return {
|
|
17
17
|
name: '@vuepress-plume/plugin-md-power',
|
|
18
|
-
clientConfigFile: path.resolve(__dirname, '../client/config.js'),
|
|
18
|
+
// clientConfigFile: path.resolve(__dirname, '../client/config.js'),
|
|
19
|
+
clientConfigFile: app => prepareConfigFile(app, options),
|
|
19
20
|
define: {
|
|
20
21
|
__MD_POWER_INJECT_OPTIONS__: options,
|
|
21
22
|
},
|
|
@@ -65,6 +66,8 @@ export function markdownPowerPlugin(options = {}) {
|
|
|
65
66
|
// =|plot|=
|
|
66
67
|
md.use(plotPlugin);
|
|
67
68
|
}
|
|
69
|
+
if (options.repl)
|
|
70
|
+
langReplPlugin(md);
|
|
68
71
|
},
|
|
69
72
|
};
|
|
70
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/plugin.d.ts
CHANGED
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,33 +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 Plot from './components/Plot.vue';
|
|
10
|
-
import '@internal/md-power/icons.css';
|
|
11
|
-
export default defineClientConfig({
|
|
12
|
-
enhance({ router, app }) {
|
|
13
|
-
if (pluginOptions.pdf)
|
|
14
|
-
app.component('PDFViewer', PDFViewer);
|
|
15
|
-
if (pluginOptions.bilibili)
|
|
16
|
-
app.component('VideoBilibili', Bilibili);
|
|
17
|
-
if (pluginOptions.youtube)
|
|
18
|
-
app.component('VideoYoutube', Youtube);
|
|
19
|
-
if (pluginOptions.replit)
|
|
20
|
-
app.component('ReplitViewer', Replit);
|
|
21
|
-
if (pluginOptions.codeSandbox)
|
|
22
|
-
app.component('CodeSandboxViewer', CodeSandbox);
|
|
23
|
-
if (pluginOptions.plot)
|
|
24
|
-
app.component('Plot', Plot);
|
|
25
|
-
if (__VUEPRESS_SSR__)
|
|
26
|
-
return;
|
|
27
|
-
if (pluginOptions.caniuse) {
|
|
28
|
-
router.afterEach(() => {
|
|
29
|
-
setupCanIUse();
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
});
|