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.
Files changed (36) hide show
  1. package/lib/client/components/CanIUse.vue +93 -0
  2. package/lib/client/components/IconClose.vue +8 -0
  3. package/lib/client/components/IconConsole.vue +8 -0
  4. package/lib/client/components/IconRun.vue +8 -0
  5. package/lib/client/components/LanguageRepl.vue +204 -0
  6. package/lib/client/components/Loading.vue +2 -1
  7. package/lib/client/components/Plot.vue +97 -0
  8. package/lib/client/composables/codeRepl.d.ts +19 -0
  9. package/lib/client/composables/codeRepl.js +150 -0
  10. package/lib/client/composables/pdf.js +8 -10
  11. package/lib/client/composables/rustRepl.d.ts +9 -0
  12. package/lib/client/composables/rustRepl.js +102 -0
  13. package/lib/client/utils/http.d.ts +4 -0
  14. package/lib/client/utils/http.js +21 -0
  15. package/lib/client/utils/sleep.d.ts +1 -0
  16. package/lib/client/utils/sleep.js +5 -0
  17. package/lib/node/features/caniuse.js +12 -17
  18. package/lib/node/features/icons/plugin.js +5 -4
  19. package/lib/node/features/langRepl.d.ts +2 -0
  20. package/lib/node/features/langRepl.js +17 -0
  21. package/lib/node/features/pdf.js +1 -1
  22. package/lib/node/features/plot.d.ts +5 -0
  23. package/lib/node/features/plot.js +48 -0
  24. package/lib/node/plugin.js +12 -3
  25. package/lib/node/prepareConfigFile.d.ts +3 -0
  26. package/lib/node/prepareConfigFile.js +59 -0
  27. package/lib/shared/index.d.ts +1 -0
  28. package/lib/shared/index.js +1 -0
  29. package/lib/shared/plot.d.ts +27 -0
  30. package/lib/shared/plot.js +1 -0
  31. package/lib/shared/plugin.d.ts +3 -0
  32. package/package.json +3 -3
  33. package/lib/client/composables/setupCanIUse.d.ts +0 -1
  34. package/lib/client/composables/setupCanIUse.js +0 -18
  35. package/lib/client/config.d.ts +0 -4
  36. 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="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,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 = normalizeLink(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
- const pdfjsUrl = pdfOptions.pdfjsUrl
24
- ? `${ensureEndingSlash(withBase(pdfOptions.pdfjsUrl))}web/viewer.html`
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=${encodeURIComponent(url)}${queryString}`
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
- if (typeof pluginOptions.pdf === 'object' && pluginOptions.pdf.pdfjsUrl)
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,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
@@ -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,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
+ }
@@ -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 || 1,
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,5 @@
1
+ /**
2
+ * =|这里的文本将被黑幕隐藏,通过点击或者 hover 才可重现|=
3
+ */
4
+ import type { PluginWithOptions } from 'markdown-it';
5
+ export declare const plotPlugin: PluginWithOptions<never>;
@@ -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
+ };
@@ -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
- const __dirname = getDirname(import.meta.url);
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,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
+ }
@@ -6,4 +6,5 @@ export * from './codepen.js';
6
6
  export * from './codeSandbox.js';
7
7
  export * from './replit.js';
8
8
  export * from './jsfiddle.js';
9
+ export * from './plot.js';
9
10
  export * from './plugin.js';
@@ -6,4 +6,5 @@ export * from './codepen.js';
6
6
  export * from './codeSandbox.js';
7
7
  export * from './replit.js';
8
8
  export * from './jsfiddle.js';
9
+ export * from './plot.js';
9
10
  export * from './plugin.js';
@@ -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 {};
@@ -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.52",
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.22"
48
+ "vue": "^3.4.25"
49
49
  },
50
50
  "devDependencies": {
51
- "@iconify/json": "^2.2.201",
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,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
- });