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.
@@ -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="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"
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="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>
@@ -29,7 +29,8 @@ defineProps<{
29
29
  justify-content: center;
30
30
  font-size: 36px;
31
31
  color: currentcolor;
32
- background-color: var(--vp-c-bg, #fff);
32
+ background-color: inherit;
33
+ transition: background-color var(--t-color);
33
34
  }
34
35
 
35
36
  .md-power-loading.absolute {
@@ -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,4 @@
1
+ export declare const http: {
2
+ get: <T extends object = object, R = any>(url: string, query?: T) => Promise<R>;
3
+ post: <T_1 extends object = object, R_1 = any>(url: string, data?: T_1 | undefined) => Promise<R_1>;
4
+ };
@@ -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>;
@@ -0,0 +1,5 @@
1
+ export function sleep(ms) {
2
+ return new Promise((resolve) => {
3
+ setTimeout(resolve, ms);
4
+ });
5
+ }
@@ -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
- const periods = resolveVersions(versions);
54
- const accessible = 'false';
55
- const image = 'none';
56
- const url = 'https://caniuse.bitsofco.de/embed/index.html';
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 'future_1,current,past_1,past_2';
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
- const result = [];
70
- uniq.forEach((v) => {
71
- if (v < 0)
72
- result.push(`past_${Math.abs(v)}`);
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,2 @@
1
+ import type markdownIt from 'markdown-it';
2
+ export declare function langReplPlugin(md: markdownIt): void;
@@ -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
+ }
@@ -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
- const __dirname = getDirname(import.meta.url);
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,3 @@
1
+ import type { App } from 'vuepress/core';
2
+ import type { MarkdownPowerPluginOptions } from '../shared/index.js';
3
+ export declare function prepareConfigFile(app: App, options: MarkdownPowerPluginOptions): Promise<string>;
@@ -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
+ }
@@ -12,5 +12,6 @@ export interface MarkdownPowerPluginOptions {
12
12
  replit?: boolean;
13
13
  codeSandbox?: boolean;
14
14
  jsfiddle?: boolean;
15
+ repl?: boolean;
15
16
  caniuse?: boolean | CanIUseOptions;
16
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.53",
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.23"
48
+ "vue": "^3.4.25"
49
49
  },
50
50
  "devDependencies": {
51
- "@iconify/json": "^2.2.202",
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
- }
@@ -1,4 +0,0 @@
1
- import type { ClientConfig } from 'vuepress/client';
2
- import '@internal/md-power/icons.css';
3
- declare const _default: ClientConfig;
4
- export default _default;
@@ -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
- });