vuepress-plugin-md-power 1.0.0-rc.63 → 1.0.0-rc.65
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/CodeSandbox.vue +5 -1
- package/lib/node/features/caniuse.js +42 -79
- package/lib/node/features/codeSandbox.js +25 -60
- package/lib/node/features/codepen.js +30 -66
- package/lib/node/features/jsfiddle.js +23 -61
- package/lib/node/features/pdf.js +20 -57
- package/lib/node/features/replit.js +17 -54
- package/lib/node/features/video/bilibili.js +34 -69
- package/lib/node/features/video/youtube.js +28 -63
- package/lib/node/utils/createRuleBlock.d.ts +18 -0
- package/lib/node/utils/createRuleBlock.js +34 -0
- package/package.json +6 -6
|
@@ -35,7 +35,7 @@ const source = computed(() => {
|
|
|
35
35
|
/>
|
|
36
36
|
</ClientOnly>
|
|
37
37
|
<p v-else>
|
|
38
|
-
<a :href="source" target="_blank" rel="noopener noreferrer" :aria-label="title || 'CodeSandbox'">
|
|
38
|
+
<a class="code-sandbox-link no-icon" :href="source" target="_blank" rel="noopener noreferrer" :aria-label="title || 'CodeSandbox'">
|
|
39
39
|
<svg xmlns="http://www.w3.org/2000/svg" width="165" height="32" viewBox="0 0 165 32" fill="none">
|
|
40
40
|
<rect width="165" height="32" rx="4" fill="#E3FF73" />
|
|
41
41
|
<rect x="0.5" y="0.5" width="164" height="31" rx="3.5" stroke="black" stroke-opacity="0.1" />
|
|
@@ -54,6 +54,10 @@ const source = computed(() => {
|
|
|
54
54
|
</template>
|
|
55
55
|
|
|
56
56
|
<style>
|
|
57
|
+
.code-sandbox-link {
|
|
58
|
+
display: inline-block;
|
|
59
|
+
}
|
|
60
|
+
|
|
57
61
|
.code-sandbox-iframe {
|
|
58
62
|
width: 100%;
|
|
59
63
|
height: 500px;
|
|
@@ -1,93 +1,25 @@
|
|
|
1
1
|
import container from 'markdown-it-container';
|
|
2
2
|
import { customAlphabet } from 'nanoid';
|
|
3
|
+
import { createRuleBlock } from '../utils/createRuleBlock.js';
|
|
3
4
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5);
|
|
4
|
-
// @[caniuse]()
|
|
5
|
-
const minLength = 12;
|
|
6
|
-
// char codes of '@[caniuse'
|
|
7
|
-
const START_CODES = [64, 91, 99, 97, 110, 105, 117, 115, 101];
|
|
8
|
-
// regexp to match the import syntax
|
|
9
|
-
const SYNTAX_RE = /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/;
|
|
10
5
|
const UNDERLINE_RE = /_+/g;
|
|
11
|
-
function createCanIUseRuleBlock(defaultMode) {
|
|
12
|
-
return (state, startLine, endLine, silent) => {
|
|
13
|
-
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
14
|
-
const max = state.eMarks[startLine];
|
|
15
|
-
// return false if the length is shorter than min length
|
|
16
|
-
if (pos + minLength > max)
|
|
17
|
-
return false;
|
|
18
|
-
// check if it's matched the start
|
|
19
|
-
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
20
|
-
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
// check if it's matched the syntax
|
|
24
|
-
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
25
|
-
if (!match)
|
|
26
|
-
return false;
|
|
27
|
-
// return true as we have matched the syntax
|
|
28
|
-
if (silent)
|
|
29
|
-
return true;
|
|
30
|
-
const [, mode, versions = '', feature] = match;
|
|
31
|
-
const meta = {
|
|
32
|
-
feature,
|
|
33
|
-
mode: mode || defaultMode,
|
|
34
|
-
versions,
|
|
35
|
-
};
|
|
36
|
-
const token = state.push('caniuse', '', 0);
|
|
37
|
-
token.meta = meta;
|
|
38
|
-
token.map = [startLine, startLine + 1];
|
|
39
|
-
token.info = mode || defaultMode;
|
|
40
|
-
state.line = startLine + 1;
|
|
41
|
-
return true;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function resolveCanIUse({ feature, mode, versions }) {
|
|
45
|
-
if (!feature)
|
|
46
|
-
return '';
|
|
47
|
-
if (mode === 'image') {
|
|
48
|
-
const link = 'https://caniuse.bitsofco.de/image/';
|
|
49
|
-
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`;
|
|
50
|
-
return `<ClientOnly><p><picture>
|
|
51
|
-
<source type="image/webp" srcset="${link}${feature}.webp">
|
|
52
|
-
<source type="image/png" srcset="${link}${feature}.png">
|
|
53
|
-
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
|
|
54
|
-
</picture></p></ClientOnly>`;
|
|
55
|
-
}
|
|
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}" />`;
|
|
60
|
-
}
|
|
61
|
-
function resolveVersions(versions) {
|
|
62
|
-
if (!versions)
|
|
63
|
-
return { past: 2, future: 1 };
|
|
64
|
-
const list = versions
|
|
65
|
-
.split(',')
|
|
66
|
-
.map(v => Number(v.trim()))
|
|
67
|
-
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3);
|
|
68
|
-
list.push(0);
|
|
69
|
-
const uniq = [...new Set(list)].sort((a, b) => b - a);
|
|
70
|
-
return {
|
|
71
|
-
future: uniq[0],
|
|
72
|
-
past: Math.abs(uniq[uniq.length - 1]),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
6
|
/**
|
|
76
7
|
* @example
|
|
77
8
|
* ```md
|
|
78
9
|
* @[caniuse](feature_name)
|
|
79
10
|
* ```
|
|
80
11
|
*/
|
|
81
|
-
export const caniusePlugin = (md, { mode = 'embed' } = {}) => {
|
|
82
|
-
md
|
|
83
|
-
|
|
12
|
+
export const caniusePlugin = (md, { mode: defaultMode = 'embed' } = {}) => {
|
|
13
|
+
createRuleBlock(md, {
|
|
14
|
+
type: 'caniuse',
|
|
15
|
+
syntaxPattern: /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/,
|
|
16
|
+
meta: ([, mode, versions = '', feature]) => ({
|
|
17
|
+
feature,
|
|
18
|
+
mode: mode || defaultMode,
|
|
19
|
+
versions,
|
|
20
|
+
}),
|
|
21
|
+
content: meta => resolveCanIUse(meta),
|
|
84
22
|
});
|
|
85
|
-
md.renderer.rules.caniuse = (tokens, index) => {
|
|
86
|
-
const token = tokens[index];
|
|
87
|
-
const content = resolveCanIUse(token.meta);
|
|
88
|
-
token.content = content;
|
|
89
|
-
return content;
|
|
90
|
-
};
|
|
91
23
|
};
|
|
92
24
|
/**
|
|
93
25
|
* @deprecated use caniuse plugin
|
|
@@ -122,3 +54,34 @@ export function legacyCaniuse(md, { mode = 'embed' } = {}) {
|
|
|
122
54
|
};
|
|
123
55
|
md.use(container, type, { validate, render });
|
|
124
56
|
}
|
|
57
|
+
function resolveCanIUse({ feature, mode, versions }) {
|
|
58
|
+
if (!feature)
|
|
59
|
+
return '';
|
|
60
|
+
if (mode === 'image') {
|
|
61
|
+
const link = 'https://caniuse.bitsofco.de/image/';
|
|
62
|
+
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`;
|
|
63
|
+
return `<ClientOnly><p><picture>
|
|
64
|
+
<source type="image/webp" srcset="${link}${feature}.webp">
|
|
65
|
+
<source type="image/png" srcset="${link}${feature}.png">
|
|
66
|
+
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
|
|
67
|
+
</picture></p></ClientOnly>`;
|
|
68
|
+
}
|
|
69
|
+
feature = feature.replace(UNDERLINE_RE, '_');
|
|
70
|
+
const { past, future } = resolveVersions(versions);
|
|
71
|
+
const meta = nanoid();
|
|
72
|
+
return `<CanIUseViewer feature="${feature}" meta="${meta}" past="${past}" future="${future}" />`;
|
|
73
|
+
}
|
|
74
|
+
function resolveVersions(versions) {
|
|
75
|
+
if (!versions)
|
|
76
|
+
return { past: 2, future: 1 };
|
|
77
|
+
const list = versions
|
|
78
|
+
.split(',')
|
|
79
|
+
.map(v => Number(v.trim()))
|
|
80
|
+
.filter(v => !Number.isNaN(v) && v >= -5 && v <= 3);
|
|
81
|
+
list.push(0);
|
|
82
|
+
const uniq = [...new Set(list)].sort((a, b) => b - a);
|
|
83
|
+
return {
|
|
84
|
+
future: uniq[0],
|
|
85
|
+
past: Math.abs(uniq[uniq.length - 1]),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -1,64 +1,29 @@
|
|
|
1
1
|
import { resolveAttrs } from '../utils/resolveAttrs.js';
|
|
2
2
|
import { parseRect } from '../utils/parseRect.js';
|
|
3
|
-
|
|
4
|
-
const MIN_LENGTH = 16;
|
|
5
|
-
// char codes of `@[codesandbox`
|
|
6
|
-
const START_CODES = [64, 91, 99, 111, 100, 101, 115, 97, 110, 100, 98, 111, 120];
|
|
7
|
-
// regexp to match the import syntax
|
|
8
|
-
const SYNTAX_RE = /^@\[codesandbox(?:\s+(embed|button))?(?:\s+([^]*?))?\]\(([^)]*?)\)/;
|
|
9
|
-
function createCodeSandboxRuleBlock() {
|
|
10
|
-
return (state, startLine, endLine, silent) => {
|
|
11
|
-
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
12
|
-
const max = state.eMarks[startLine];
|
|
13
|
-
// return false if the length is shorter than min length
|
|
14
|
-
if (pos + MIN_LENGTH > max)
|
|
15
|
-
return false;
|
|
16
|
-
// check if it's matched the start
|
|
17
|
-
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
18
|
-
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
// check if it's matched the syntax
|
|
22
|
-
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
23
|
-
if (!match)
|
|
24
|
-
return false;
|
|
25
|
-
// return true as we have matched the syntax
|
|
26
|
-
if (silent)
|
|
27
|
-
return true;
|
|
28
|
-
const [, type, info = '', source] = match;
|
|
29
|
-
const { attrs } = resolveAttrs(info);
|
|
30
|
-
const [profile, filepath = ''] = source.split('#');
|
|
31
|
-
const [user, id] = profile.includes('/') ? profile.split('/') : ['', profile];
|
|
32
|
-
const meta = {
|
|
33
|
-
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
34
|
-
height: attrs.height ? parseRect(attrs.height) : '500px',
|
|
35
|
-
user,
|
|
36
|
-
id,
|
|
37
|
-
title: attrs.title ?? '',
|
|
38
|
-
console: attrs.console ?? false,
|
|
39
|
-
navbar: attrs.navbar ?? true,
|
|
40
|
-
layout: attrs.layout ?? '',
|
|
41
|
-
type: (type || 'embed'),
|
|
42
|
-
filepath,
|
|
43
|
-
};
|
|
44
|
-
const token = state.push('code_sandbox', '', 0);
|
|
45
|
-
token.meta = meta;
|
|
46
|
-
token.map = [startLine, startLine + 1];
|
|
47
|
-
token.info = match[0];
|
|
48
|
-
state.line = startLine + 1;
|
|
49
|
-
return true;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function resolveCodeSandbox(meta) {
|
|
53
|
-
const { title, height, width, user, id, type, filepath, console, navbar, layout } = meta;
|
|
54
|
-
return `<CodeSandboxViewer title="${title}" height="${height}" width="${width}" user="${user}" id="${id}" type="${type}" filepath="${filepath}" :console=${console} :navbar=${navbar} layout="${layout}" />`;
|
|
55
|
-
}
|
|
3
|
+
import { createRuleBlock } from '../utils/createRuleBlock.js';
|
|
56
4
|
export const codeSandboxPlugin = (md) => {
|
|
57
|
-
md
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
5
|
+
createRuleBlock(md, {
|
|
6
|
+
type: 'codesandbox',
|
|
7
|
+
syntaxPattern: /^@\[codesandbox(?:\s+(embed|button))?(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
|
8
|
+
meta([, type, info = '', source = '']) {
|
|
9
|
+
const { attrs } = resolveAttrs(info);
|
|
10
|
+
const [profile, filepath = ''] = source.split('#');
|
|
11
|
+
const [user, id] = profile.includes('/') ? profile.split('/') : ['', profile];
|
|
12
|
+
return {
|
|
13
|
+
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
14
|
+
height: attrs.height ? parseRect(attrs.height) : '500px',
|
|
15
|
+
user,
|
|
16
|
+
id,
|
|
17
|
+
title: attrs.title ?? '',
|
|
18
|
+
console: attrs.console ?? false,
|
|
19
|
+
navbar: attrs.navbar ?? true,
|
|
20
|
+
layout: attrs.layout ?? '',
|
|
21
|
+
type: (type || 'embed'),
|
|
22
|
+
filepath,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
content({ title, height, width, user, id, type, filepath, console, navbar, layout }) {
|
|
26
|
+
return `<CodeSandboxViewer title="${title}" height="${height}" width="${width}" user="${user}" id="${id}" type="${type}" filepath="${filepath}" :console=${console} :navbar=${navbar} layout="${layout}" />`;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
64
29
|
};
|
|
@@ -1,72 +1,36 @@
|
|
|
1
1
|
import { resolveAttrs } from '../utils/resolveAttrs.js';
|
|
2
2
|
import { parseRect } from '../utils/parseRect.js';
|
|
3
|
+
import { createRuleBlock } from '../utils/createRuleBlock.js';
|
|
3
4
|
const CODEPEN_LINK = 'https://codepen.io/';
|
|
4
|
-
// @[codepen]()
|
|
5
|
-
const MIN_LENGTH = 12;
|
|
6
|
-
// char codes of `@[codepen`
|
|
7
|
-
const START_CODES = [64, 91, 99, 111, 100, 101, 112, 101, 110];
|
|
8
|
-
// regexp to match the import syntax
|
|
9
|
-
const SYNTAX_RE = /^@\[codepen(?:\s+([^]*?))?\]\(([^)]*?)\)/;
|
|
10
|
-
function createCodepenRuleBlock() {
|
|
11
|
-
return (state, startLine, endLine, silent) => {
|
|
12
|
-
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
13
|
-
const max = state.eMarks[startLine];
|
|
14
|
-
// return false if the length is shorter than min length
|
|
15
|
-
if (pos + MIN_LENGTH > max)
|
|
16
|
-
return false;
|
|
17
|
-
// check if it's matched the start
|
|
18
|
-
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
19
|
-
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
// check if it's matched the syntax
|
|
23
|
-
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
24
|
-
if (!match)
|
|
25
|
-
return false;
|
|
26
|
-
// return true as we have matched the syntax
|
|
27
|
-
if (silent)
|
|
28
|
-
return true;
|
|
29
|
-
const [, info = '', source] = match;
|
|
30
|
-
const { attrs } = resolveAttrs(info);
|
|
31
|
-
const [user, slash] = source.split('/');
|
|
32
|
-
const meta = {
|
|
33
|
-
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
34
|
-
height: attrs.height ? parseRect(attrs.height) : '400px',
|
|
35
|
-
user,
|
|
36
|
-
slash,
|
|
37
|
-
title: attrs.title,
|
|
38
|
-
preview: attrs.preview,
|
|
39
|
-
editable: attrs.editable,
|
|
40
|
-
tab: attrs.tab ?? 'result',
|
|
41
|
-
theme: attrs.theme,
|
|
42
|
-
};
|
|
43
|
-
const token = state.push('codepen', '', 0);
|
|
44
|
-
token.meta = meta;
|
|
45
|
-
token.map = [startLine, startLine + 1];
|
|
46
|
-
token.info = info;
|
|
47
|
-
state.line = startLine + 1;
|
|
48
|
-
return true;
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function resolveCodepen(meta) {
|
|
52
|
-
const { title = 'Codepen', height, width } = meta;
|
|
53
|
-
const params = new URLSearchParams();
|
|
54
|
-
meta.editable && params.set('editable', 'true');
|
|
55
|
-
meta.tab && params.set('default-tab', meta.tab);
|
|
56
|
-
meta.theme && params.set('theme-id', meta.theme);
|
|
57
|
-
const middle = meta.preview ? '/embed/preview/' : '/embed/';
|
|
58
|
-
const link = `${CODEPEN_LINK}${meta.user}${middle}${meta.slash}?${params.toString()}`;
|
|
59
|
-
const style = `width:${width};height:${height};margin:16px auto;border-radius:5px;`;
|
|
60
|
-
return `<iframe class="code-pen-iframe-wrapper" src="${link}" title="${title}" style="${style}" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">See the Pen <a href="${CODEPEN_LINK}${meta.user}/pen/${meta.slash}">${title}</a> by ${meta.user} (<a href="${CODEPEN_LINK}${meta.user}">@${meta.user}</a>) on <a href="${CODEPEN_LINK}">CodePen</a>.</iframe>`;
|
|
61
|
-
}
|
|
62
5
|
export const codepenPlugin = (md) => {
|
|
63
|
-
md
|
|
64
|
-
|
|
6
|
+
createRuleBlock(md, {
|
|
7
|
+
type: 'codepen',
|
|
8
|
+
syntaxPattern: /^@\[codepen(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
|
9
|
+
meta: ([, info = '', source = '']) => {
|
|
10
|
+
const { attrs } = resolveAttrs(info);
|
|
11
|
+
const [user, slash] = source.split('/');
|
|
12
|
+
return {
|
|
13
|
+
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
14
|
+
height: attrs.height ? parseRect(attrs.height) : '400px',
|
|
15
|
+
user,
|
|
16
|
+
slash,
|
|
17
|
+
title: attrs.title,
|
|
18
|
+
preview: attrs.preview,
|
|
19
|
+
editable: attrs.editable,
|
|
20
|
+
tab: attrs.tab ?? 'result',
|
|
21
|
+
theme: attrs.theme,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
content: (meta) => {
|
|
25
|
+
const { title = 'Codepen', height, width } = meta;
|
|
26
|
+
const params = new URLSearchParams();
|
|
27
|
+
meta.editable && params.set('editable', 'true');
|
|
28
|
+
meta.tab && params.set('default-tab', meta.tab);
|
|
29
|
+
meta.theme && params.set('theme-id', meta.theme);
|
|
30
|
+
const middle = meta.preview ? '/embed/preview/' : '/embed/';
|
|
31
|
+
const link = `${CODEPEN_LINK}${meta.user}${middle}${meta.slash}?${params.toString()}`;
|
|
32
|
+
const style = `width:${width};height:${height};margin:16px auto;border-radius:5px;`;
|
|
33
|
+
return `<iframe class="code-pen-iframe-wrapper" src="${link}" title="${title}" style="${style}" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">See the Pen <a href="${CODEPEN_LINK}${meta.user}/pen/${meta.slash}">${title}</a> by ${meta.user} (<a href="${CODEPEN_LINK}${meta.user}">@${meta.user}</a>) on <a href="${CODEPEN_LINK}">CodePen</a>.</iframe>`;
|
|
34
|
+
},
|
|
65
35
|
});
|
|
66
|
-
md.renderer.rules.codepen = (tokens, index) => {
|
|
67
|
-
const token = tokens[index];
|
|
68
|
-
const content = resolveCodepen(token.meta);
|
|
69
|
-
token.content = content;
|
|
70
|
-
return content;
|
|
71
|
-
};
|
|
72
36
|
};
|
|
@@ -1,66 +1,28 @@
|
|
|
1
1
|
import { resolveAttrs } from '../utils/resolveAttrs.js';
|
|
2
2
|
import { parseRect } from '../utils/parseRect.js';
|
|
3
|
-
|
|
4
|
-
// @[jsfiddle]()
|
|
5
|
-
const MIN_LENGTH = 13;
|
|
6
|
-
// char codes of `@[jsfiddle`
|
|
7
|
-
const START_CODES = [64, 91, 106, 115, 102, 105, 100, 100, 108, 101];
|
|
8
|
-
// regexp to match the import syntax
|
|
9
|
-
const SYNTAX_RE = /^@\[jsfiddle(?:\s+([^]*?))?\]\(([^)]*?)\)/;
|
|
10
|
-
function createJsFiddleRuleBlock() {
|
|
11
|
-
return (state, startLine, endLine, silent) => {
|
|
12
|
-
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
13
|
-
const max = state.eMarks[startLine];
|
|
14
|
-
// return false if the length is shorter than min length
|
|
15
|
-
if (pos + MIN_LENGTH > max)
|
|
16
|
-
return false;
|
|
17
|
-
// check if it's matched the start
|
|
18
|
-
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
19
|
-
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
// check if it's matched the syntax
|
|
23
|
-
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
24
|
-
if (!match)
|
|
25
|
-
return false;
|
|
26
|
-
// return true as we have matched the syntax
|
|
27
|
-
if (silent)
|
|
28
|
-
return true;
|
|
29
|
-
const [, info = '', source] = match;
|
|
30
|
-
const { attrs } = resolveAttrs(info);
|
|
31
|
-
const [user, id] = source.split('/');
|
|
32
|
-
const meta = {
|
|
33
|
-
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
34
|
-
height: attrs.height ? parseRect(attrs.height) : '400px',
|
|
35
|
-
user,
|
|
36
|
-
id,
|
|
37
|
-
title: attrs.title || 'JS Fiddle',
|
|
38
|
-
tab: attrs.tab?.replace(/\s+/g, '') || 'js,css,html,result',
|
|
39
|
-
theme: attrs.theme || 'dark',
|
|
40
|
-
};
|
|
41
|
-
const token = state.push('js_fiddle', '', 0);
|
|
42
|
-
token.meta = meta;
|
|
43
|
-
token.map = [startLine, startLine + 1];
|
|
44
|
-
token.info = info;
|
|
45
|
-
state.line = startLine + 1;
|
|
46
|
-
return true;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function resolveJsFiddle(meta) {
|
|
50
|
-
const { title = 'JS Fiddle', height, width, user, id, tab } = meta;
|
|
51
|
-
const theme = meta.theme === 'dark' ? '/dark/' : '';
|
|
52
|
-
const link = `${JS_FIDDLE_LINK}${user}/${id}/embedded/${tab}${theme}`;
|
|
53
|
-
const style = `width:${width};height:${height};margin:16px auto;border:none;border-radius:5px;`;
|
|
54
|
-
return `<iframe class="js-fiddle-iframe-wrapper" style="${style}" title="${title}" src="${link}" allowfullscreen="true" allowpaymentrequest="true"></iframe>`;
|
|
55
|
-
}
|
|
3
|
+
import { createRuleBlock } from '../utils/createRuleBlock.js';
|
|
56
4
|
export const jsfiddlePlugin = (md) => {
|
|
57
|
-
md
|
|
58
|
-
|
|
5
|
+
createRuleBlock(md, {
|
|
6
|
+
type: 'jsfiddle',
|
|
7
|
+
syntaxPattern: /^@\[jsfiddle(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
|
8
|
+
meta([, info = '', source]) {
|
|
9
|
+
const { attrs } = resolveAttrs(info);
|
|
10
|
+
const [user, id] = source.split('/');
|
|
11
|
+
return {
|
|
12
|
+
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
13
|
+
height: attrs.height ? parseRect(attrs.height) : '400px',
|
|
14
|
+
user,
|
|
15
|
+
id,
|
|
16
|
+
title: attrs.title || 'JS Fiddle',
|
|
17
|
+
tab: attrs.tab?.replace(/\s+/g, '') || 'js,css,html,result',
|
|
18
|
+
theme: attrs.theme || 'dark',
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
content: ({ title = 'JS Fiddle', height, width, user, id, tab, theme }) => {
|
|
22
|
+
theme = theme === 'dark' ? '/dark/' : '';
|
|
23
|
+
const link = `https://jsfiddle.net/${user}/${id}/embedded/${tab}${theme}`;
|
|
24
|
+
const style = `width:${width};height:${height};margin:16px auto;border:none;border-radius:5px;`;
|
|
25
|
+
return `<iframe class="js-fiddle-iframe-wrapper" style="${style}" title="${title}" src="${link}" allowfullscreen="true" allowpaymentrequest="true"></iframe>`;
|
|
26
|
+
},
|
|
59
27
|
});
|
|
60
|
-
md.renderer.rules.js_fiddle = (tokens, index) => {
|
|
61
|
-
const token = tokens[index];
|
|
62
|
-
const content = resolveJsFiddle(token.meta);
|
|
63
|
-
token.content = content;
|
|
64
|
-
return content;
|
|
65
|
-
};
|
|
66
28
|
};
|
package/lib/node/features/pdf.js
CHANGED
|
@@ -6,63 +6,26 @@
|
|
|
6
6
|
import { path } from 'vuepress/utils';
|
|
7
7
|
import { resolveAttrs } from '../utils/resolveAttrs.js';
|
|
8
8
|
import { parseRect } from '../utils/parseRect.js';
|
|
9
|
-
|
|
10
|
-
const MIN_LENGTH = 8;
|
|
11
|
-
// char codes of `@[pdf`
|
|
12
|
-
const START_CODES = [64, 91, 112, 100, 102];
|
|
13
|
-
// regexp to match the import syntax
|
|
14
|
-
const SYNTAX_RE = /^@\[pdf(?:\s+(\d+))?(?:\s+([^]*?))?\]\(([^)]*?)\)/;
|
|
15
|
-
function createPDFRuleBlock() {
|
|
16
|
-
return (state, startLine, endLine, silent) => {
|
|
17
|
-
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
18
|
-
const max = state.eMarks[startLine];
|
|
19
|
-
// return false if the length is shorter than min length
|
|
20
|
-
if (pos + MIN_LENGTH > max)
|
|
21
|
-
return false;
|
|
22
|
-
// check if it's matched the start
|
|
23
|
-
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
24
|
-
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
// check if it's matched the syntax
|
|
28
|
-
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
29
|
-
if (!match)
|
|
30
|
-
return false;
|
|
31
|
-
// return true as we have matched the syntax
|
|
32
|
-
if (silent)
|
|
33
|
-
return true;
|
|
34
|
-
const [, page, info = '', src] = match;
|
|
35
|
-
const { attrs } = resolveAttrs(info);
|
|
36
|
-
const meta = {
|
|
37
|
-
src,
|
|
38
|
-
page: +page || 1,
|
|
39
|
-
noToolbar: Boolean(attrs.noToolbar ?? false),
|
|
40
|
-
zoom: +attrs.zoom || 50,
|
|
41
|
-
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
42
|
-
height: attrs.height ? parseRect(attrs.height) : '',
|
|
43
|
-
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
|
44
|
-
title: path.basename(src || ''),
|
|
45
|
-
};
|
|
46
|
-
const token = state.push('pdf', '', 0);
|
|
47
|
-
token.meta = meta;
|
|
48
|
-
token.map = [startLine, startLine + 1];
|
|
49
|
-
token.info = info;
|
|
50
|
-
state.line = startLine + 1;
|
|
51
|
-
return true;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function resolvePDF(meta) {
|
|
55
|
-
const { title, src, page, noToolbar, width, height, ratio, zoom } = meta;
|
|
56
|
-
return `<PDFViewer src="${src}" title="${title}" :page="${page}" :no-toolbar="${noToolbar}" width="${width}" height="${height}" ratio="${ratio}" :zoom="${zoom}" />`;
|
|
57
|
-
}
|
|
9
|
+
import { createRuleBlock } from '../utils/createRuleBlock.js';
|
|
58
10
|
export const pdfPlugin = (md) => {
|
|
59
|
-
md
|
|
60
|
-
|
|
11
|
+
createRuleBlock(md, {
|
|
12
|
+
type: 'pdf',
|
|
13
|
+
syntaxPattern: /^@\[pdf(?:\s+(\d+))?(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
|
14
|
+
meta([, page, info = '', src = '']) {
|
|
15
|
+
const { attrs } = resolveAttrs(info);
|
|
16
|
+
return {
|
|
17
|
+
src,
|
|
18
|
+
page: +page || 1,
|
|
19
|
+
noToolbar: Boolean(attrs.noToolbar ?? false),
|
|
20
|
+
zoom: +attrs.zoom || 50,
|
|
21
|
+
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
22
|
+
height: attrs.height ? parseRect(attrs.height) : '',
|
|
23
|
+
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
|
24
|
+
title: path.basename(src || ''),
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
content({ title, src, page, noToolbar, width, height, ratio, zoom }) {
|
|
28
|
+
return `<PDFViewer src="${src}" title="${title}" :page="${page}" :no-toolbar="${noToolbar}" width="${width}" height="${height}" ratio="${ratio}" :zoom="${zoom}" />`;
|
|
29
|
+
},
|
|
61
30
|
});
|
|
62
|
-
md.renderer.rules.pdf = (tokens, index) => {
|
|
63
|
-
const token = tokens[index];
|
|
64
|
-
const content = resolvePDF(token.meta);
|
|
65
|
-
token.content = content;
|
|
66
|
-
return content;
|
|
67
|
-
};
|
|
68
31
|
};
|
|
@@ -1,59 +1,22 @@
|
|
|
1
1
|
import { resolveAttrs } from '../utils/resolveAttrs.js';
|
|
2
2
|
import { parseRect } from '../utils/parseRect.js';
|
|
3
|
-
|
|
4
|
-
const MIN_LENGTH = 11;
|
|
5
|
-
// char codes of `@[replit`
|
|
6
|
-
const START_CODES = [64, 91, 114, 101, 112, 108, 105, 116];
|
|
7
|
-
// regexp to match the import syntax
|
|
8
|
-
const SYNTAX_RE = /^@\[replit(?:\s+([^]*?))?\]\(([^)]*?)\)/;
|
|
9
|
-
function createReplitRuleBlock() {
|
|
10
|
-
return (state, startLine, endLine, silent) => {
|
|
11
|
-
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
12
|
-
const max = state.eMarks[startLine];
|
|
13
|
-
// return false if the length is shorter than min length
|
|
14
|
-
if (pos + MIN_LENGTH > max)
|
|
15
|
-
return false;
|
|
16
|
-
// check if it's matched the start
|
|
17
|
-
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
18
|
-
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
// check if it's matched the syntax
|
|
22
|
-
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
23
|
-
if (!match)
|
|
24
|
-
return false;
|
|
25
|
-
// return true as we have matched the syntax
|
|
26
|
-
if (silent)
|
|
27
|
-
return true;
|
|
28
|
-
const [, info = '', source] = match;
|
|
29
|
-
const { attrs } = resolveAttrs(info);
|
|
30
|
-
const meta = {
|
|
31
|
-
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
32
|
-
height: attrs.height ? parseRect(attrs.height) : '450px',
|
|
33
|
-
source: source.startsWith('@') ? source : `@${source}`,
|
|
34
|
-
title: attrs.title,
|
|
35
|
-
theme: attrs.theme || '',
|
|
36
|
-
};
|
|
37
|
-
const token = state.push('replit', '', 0);
|
|
38
|
-
token.meta = meta;
|
|
39
|
-
token.map = [startLine, startLine + 1];
|
|
40
|
-
token.info = info;
|
|
41
|
-
state.line = startLine + 1;
|
|
42
|
-
return true;
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function resolveReplit(meta) {
|
|
46
|
-
const { title, height, width, source, theme } = meta;
|
|
47
|
-
return `<ReplitViewer title="${title || ''}" height="${height}" width="${width}" source="${source}" theme="${theme}" />`;
|
|
48
|
-
}
|
|
3
|
+
import { createRuleBlock } from '../utils/createRuleBlock.js';
|
|
49
4
|
export const replitPlugin = (md) => {
|
|
50
|
-
md
|
|
51
|
-
|
|
5
|
+
createRuleBlock(md, {
|
|
6
|
+
type: 'replit',
|
|
7
|
+
syntaxPattern: /^@\[replit(?:\s+([^]*?))?\]\(([^)]*?)\)/,
|
|
8
|
+
meta: ([, info = '', source = '']) => {
|
|
9
|
+
const { attrs } = resolveAttrs(info);
|
|
10
|
+
return {
|
|
11
|
+
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
12
|
+
height: attrs.height ? parseRect(attrs.height) : '450px',
|
|
13
|
+
source: source.startsWith('@') ? source : `@${source}`,
|
|
14
|
+
title: attrs.title,
|
|
15
|
+
theme: attrs.theme || '',
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
content({ title, height, width, source, theme }) {
|
|
19
|
+
return `<ReplitViewer title="${title || ''}" height="${height}" width="${width}" source="${source}" theme="${theme}" />`;
|
|
20
|
+
},
|
|
52
21
|
});
|
|
53
|
-
md.renderer.rules.replit = (tokens, index) => {
|
|
54
|
-
const token = tokens[index];
|
|
55
|
-
const content = resolveReplit(token.meta);
|
|
56
|
-
token.content = content;
|
|
57
|
-
return content;
|
|
58
|
-
};
|
|
59
22
|
};
|
|
@@ -8,76 +8,41 @@ import { URLSearchParams } from 'node:url';
|
|
|
8
8
|
import { resolveAttrs } from '../../utils/resolveAttrs.js';
|
|
9
9
|
import { parseRect } from '../../utils/parseRect.js';
|
|
10
10
|
import { timeToSeconds } from '../../utils/timeToSeconds.js';
|
|
11
|
+
import { createRuleBlock } from '../../utils/createRuleBlock.js';
|
|
11
12
|
const BILIBILI_LINK = 'https://player.bilibili.com/player.html';
|
|
12
|
-
// @[bilibili]()
|
|
13
|
-
const MIN_LENGTH = 13;
|
|
14
|
-
// char codes of '@[bilibili'
|
|
15
|
-
const START_CODES = [64, 91, 98, 105, 108, 105, 98, 105, 108, 105];
|
|
16
|
-
// regexp to match the import syntax
|
|
17
|
-
const SYNTAX_RE = /^@\[bilibili(?:\s+p(\d+))?(?:\s+([^]*?))?\]\(([^)]*)\)/;
|
|
18
|
-
function createBilibiliRuleBlock() {
|
|
19
|
-
return (state, startLine, endLine, silent) => {
|
|
20
|
-
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
21
|
-
const max = state.eMarks[startLine];
|
|
22
|
-
// return false if the length is shorter than min length
|
|
23
|
-
if (pos + MIN_LENGTH > max)
|
|
24
|
-
return false;
|
|
25
|
-
// check if it's matched the start
|
|
26
|
-
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
27
|
-
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
// check if it's matched the syntax
|
|
31
|
-
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
32
|
-
if (!match)
|
|
33
|
-
return false;
|
|
34
|
-
// return true as we have matched the syntax
|
|
35
|
-
if (silent)
|
|
36
|
-
return true;
|
|
37
|
-
const [, page, info = '', source = ''] = match;
|
|
38
|
-
const { attrs } = resolveAttrs(info);
|
|
39
|
-
const ids = source.trim().split(/\s+/);
|
|
40
|
-
const bvid = ids.find(id => id.startsWith('BV'));
|
|
41
|
-
const [aid, cid] = ids.filter(id => !id.startsWith('BV'));
|
|
42
|
-
const meta = {
|
|
43
|
-
page: +page || 1,
|
|
44
|
-
bvid,
|
|
45
|
-
aid,
|
|
46
|
-
cid,
|
|
47
|
-
autoplay: attrs.autoplay ?? false,
|
|
48
|
-
time: timeToSeconds(attrs.time),
|
|
49
|
-
title: attrs.title,
|
|
50
|
-
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
51
|
-
height: attrs.height ? parseRect(attrs.height) : '',
|
|
52
|
-
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
|
53
|
-
};
|
|
54
|
-
const token = state.push('video_bilibili', '', 0);
|
|
55
|
-
token.meta = meta;
|
|
56
|
-
token.map = [startLine, startLine + 1];
|
|
57
|
-
token.info = info;
|
|
58
|
-
state.line = startLine + 1;
|
|
59
|
-
return true;
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function resolveBilibili(meta) {
|
|
63
|
-
const params = new URLSearchParams();
|
|
64
|
-
meta.bvid && params.set('bvid', meta.bvid);
|
|
65
|
-
meta.aid && params.set('aid', meta.aid);
|
|
66
|
-
meta.cid && params.set('cid', meta.cid);
|
|
67
|
-
meta.page && params.set('p', meta.page.toString());
|
|
68
|
-
meta.time && params.set('t', meta.time.toString());
|
|
69
|
-
params.set('autoplay', meta.autoplay ? '1' : '0');
|
|
70
|
-
const source = `${BILIBILI_LINK}?${params.toString()}`;
|
|
71
|
-
return `<VideoBilibili src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`;
|
|
72
|
-
}
|
|
73
13
|
export const bilibiliPlugin = (md) => {
|
|
74
|
-
md
|
|
75
|
-
|
|
14
|
+
createRuleBlock(md, {
|
|
15
|
+
type: 'bilibili',
|
|
16
|
+
name: 'video_bilibili',
|
|
17
|
+
syntaxPattern: /^@\[bilibili(?:\s+p(\d+))?(?:\s+([^]*?))?\]\(([^)]*)\)/,
|
|
18
|
+
meta([, page, info = '', source = '']) {
|
|
19
|
+
const { attrs } = resolveAttrs(info);
|
|
20
|
+
const ids = source.trim().split(/\s+/);
|
|
21
|
+
const bvid = ids.find(id => id.startsWith('BV'));
|
|
22
|
+
const [aid, cid] = ids.filter(id => !id.startsWith('BV'));
|
|
23
|
+
return {
|
|
24
|
+
page: +page || 1,
|
|
25
|
+
bvid,
|
|
26
|
+
aid,
|
|
27
|
+
cid,
|
|
28
|
+
autoplay: attrs.autoplay ?? false,
|
|
29
|
+
time: timeToSeconds(attrs.time),
|
|
30
|
+
title: attrs.title,
|
|
31
|
+
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
32
|
+
height: attrs.height ? parseRect(attrs.height) : '',
|
|
33
|
+
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
content(meta) {
|
|
37
|
+
const params = new URLSearchParams();
|
|
38
|
+
meta.bvid && params.set('bvid', meta.bvid);
|
|
39
|
+
meta.aid && params.set('aid', meta.aid);
|
|
40
|
+
meta.cid && params.set('cid', meta.cid);
|
|
41
|
+
meta.page && params.set('p', meta.page.toString());
|
|
42
|
+
meta.time && params.set('t', meta.time.toString());
|
|
43
|
+
params.set('autoplay', meta.autoplay ? '1' : '0');
|
|
44
|
+
const source = `${BILIBILI_LINK}?${params.toString()}`;
|
|
45
|
+
return `<VideoBilibili src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`;
|
|
46
|
+
},
|
|
76
47
|
});
|
|
77
|
-
md.renderer.rules.video_bilibili = (tokens, index) => {
|
|
78
|
-
const token = tokens[index];
|
|
79
|
-
const content = resolveBilibili(token.meta);
|
|
80
|
-
token.content = content;
|
|
81
|
-
return content;
|
|
82
|
-
};
|
|
83
48
|
};
|
|
@@ -5,70 +5,35 @@ import { URLSearchParams } from 'node:url';
|
|
|
5
5
|
import { resolveAttrs } from '../../utils/resolveAttrs.js';
|
|
6
6
|
import { parseRect } from '../../utils/parseRect.js';
|
|
7
7
|
import { timeToSeconds } from '../../utils/timeToSeconds.js';
|
|
8
|
+
import { createRuleBlock } from '../../utils/createRuleBlock.js';
|
|
8
9
|
const YOUTUBE_LINK = 'https://www.youtube.com/embed/';
|
|
9
|
-
// @[youtube]()
|
|
10
|
-
const MIN_LENGTH = 13;
|
|
11
|
-
// char codes of '@[youtube'
|
|
12
|
-
const START_CODES = [64, 91, 121, 111, 117, 116, 117, 98, 101];
|
|
13
|
-
// regexp to match the import syntax
|
|
14
|
-
const SYNTAX_RE = /^@\[youtube(?:\s+([^]*?))?\]\(([^)]*)\)/;
|
|
15
|
-
function createYoutubeRuleBlock() {
|
|
16
|
-
return (state, startLine, endLine, silent) => {
|
|
17
|
-
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
18
|
-
const max = state.eMarks[startLine];
|
|
19
|
-
// return false if the length is shorter than min length
|
|
20
|
-
if (pos + MIN_LENGTH > max)
|
|
21
|
-
return false;
|
|
22
|
-
// check if it's matched the start
|
|
23
|
-
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
24
|
-
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
// check if it's matched the syntax
|
|
28
|
-
const match = state.src.slice(pos, max).match(SYNTAX_RE);
|
|
29
|
-
if (!match)
|
|
30
|
-
return false;
|
|
31
|
-
// return true as we have matched the syntax
|
|
32
|
-
if (silent)
|
|
33
|
-
return true;
|
|
34
|
-
const [, info = '', id = ''] = match;
|
|
35
|
-
const { attrs } = resolveAttrs(info);
|
|
36
|
-
const meta = {
|
|
37
|
-
id,
|
|
38
|
-
autoplay: attrs.autoplay ?? false,
|
|
39
|
-
loop: attrs.loop ?? false,
|
|
40
|
-
start: timeToSeconds(attrs.start),
|
|
41
|
-
end: timeToSeconds(attrs.end),
|
|
42
|
-
title: attrs.title,
|
|
43
|
-
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
44
|
-
height: attrs.height ? parseRect(attrs.height) : '',
|
|
45
|
-
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
|
46
|
-
};
|
|
47
|
-
const token = state.push('video_youtube', '', 0);
|
|
48
|
-
token.meta = meta;
|
|
49
|
-
token.map = [startLine, startLine + 1];
|
|
50
|
-
token.info = info;
|
|
51
|
-
state.line = startLine + 1;
|
|
52
|
-
return true;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
function resolveYoutube(meta) {
|
|
56
|
-
const params = new URLSearchParams();
|
|
57
|
-
meta.autoplay && params.set('autoplay', '1');
|
|
58
|
-
meta.loop && params.set('loop', '1');
|
|
59
|
-
meta.start && params.set('start', meta.start.toString());
|
|
60
|
-
meta.end && params.set('end', meta.end.toString());
|
|
61
|
-
const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`;
|
|
62
|
-
return `<VideoYoutube src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`;
|
|
63
|
-
}
|
|
64
10
|
export const youtubePlugin = (md) => {
|
|
65
|
-
md
|
|
66
|
-
|
|
11
|
+
createRuleBlock(md, {
|
|
12
|
+
type: 'youtube',
|
|
13
|
+
name: 'video_youtube',
|
|
14
|
+
syntaxPattern: /^@\[youtube(?:\s+([^]*?))?\]\(([^)]*)\)/,
|
|
15
|
+
meta([, info = '', id = '']) {
|
|
16
|
+
const { attrs } = resolveAttrs(info);
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
autoplay: attrs.autoplay ?? false,
|
|
20
|
+
loop: attrs.loop ?? false,
|
|
21
|
+
start: timeToSeconds(attrs.start),
|
|
22
|
+
end: timeToSeconds(attrs.end),
|
|
23
|
+
title: attrs.title,
|
|
24
|
+
width: attrs.width ? parseRect(attrs.width) : '100%',
|
|
25
|
+
height: attrs.height ? parseRect(attrs.height) : '',
|
|
26
|
+
ratio: attrs.ratio ? parseRect(attrs.ratio) : '',
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
content(meta) {
|
|
30
|
+
const params = new URLSearchParams();
|
|
31
|
+
meta.autoplay && params.set('autoplay', '1');
|
|
32
|
+
meta.loop && params.set('loop', '1');
|
|
33
|
+
meta.start && params.set('start', meta.start.toString());
|
|
34
|
+
meta.end && params.set('end', meta.end.toString());
|
|
35
|
+
const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`;
|
|
36
|
+
return `<VideoYoutube src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`;
|
|
37
|
+
},
|
|
67
38
|
});
|
|
68
|
-
md.renderer.rules.video_youtube = (tokens, index) => {
|
|
69
|
-
const token = tokens[index];
|
|
70
|
-
const content = resolveYoutube(token.meta);
|
|
71
|
-
token.content = content;
|
|
72
|
-
return content;
|
|
73
|
-
};
|
|
74
39
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RuleOptions } from 'markdown-it/lib/ruler.mjs';
|
|
2
|
+
import type { Markdown } from 'vuepress/markdown';
|
|
3
|
+
export interface RuleBlockOptions<Meta extends Record<string, any>> {
|
|
4
|
+
/**
|
|
5
|
+
* @[type]()
|
|
6
|
+
*/
|
|
7
|
+
type: string;
|
|
8
|
+
/**
|
|
9
|
+
* token name
|
|
10
|
+
*/
|
|
11
|
+
name?: string;
|
|
12
|
+
beforeName?: string;
|
|
13
|
+
syntaxPattern: RegExp;
|
|
14
|
+
ruleOptions?: RuleOptions;
|
|
15
|
+
meta: (match: RegExpMatchArray) => Meta;
|
|
16
|
+
content: (meta: Meta) => string;
|
|
17
|
+
}
|
|
18
|
+
export declare function createRuleBlock<Meta extends Record<string, any> = Record<string, any>>(md: Markdown, { type, name, syntaxPattern, beforeName, ruleOptions, meta, content, }: RuleBlockOptions<Meta>): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @[name]()
|
|
2
|
+
export function createRuleBlock(md, { type, name = type, syntaxPattern, beforeName = 'import_code', ruleOptions = { alt: ['paragraph', 'reference', 'blockquote', 'list'] }, meta, content, }) {
|
|
3
|
+
const MIN_LENGTH = type.length + 5;
|
|
4
|
+
const START_CODES = [64, 91, ...type.split('').map(c => c.charCodeAt(0))];
|
|
5
|
+
md.block.ruler.before(beforeName, name, (state, startLine, endLine, silent) => {
|
|
6
|
+
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
7
|
+
const max = state.eMarks[startLine];
|
|
8
|
+
// return false if the length is shorter than min length
|
|
9
|
+
if (pos + MIN_LENGTH > max)
|
|
10
|
+
return false;
|
|
11
|
+
// check if it's matched the start
|
|
12
|
+
for (let i = 0; i < START_CODES.length; i += 1) {
|
|
13
|
+
if (state.src.charCodeAt(pos + i) !== START_CODES[i])
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
// check if it's matched the syntax
|
|
17
|
+
const match = state.src.slice(pos, max).match(syntaxPattern);
|
|
18
|
+
if (!match)
|
|
19
|
+
return false;
|
|
20
|
+
// return true as we have matched the syntax
|
|
21
|
+
if (silent)
|
|
22
|
+
return true;
|
|
23
|
+
const token = state.push(name, '', 0);
|
|
24
|
+
token.meta = meta(match);
|
|
25
|
+
token.map = [startLine, startLine + 1];
|
|
26
|
+
state.line = startLine + 1;
|
|
27
|
+
return true;
|
|
28
|
+
}, ruleOptions);
|
|
29
|
+
md.renderer.rules[name] = (tokens, index) => {
|
|
30
|
+
const token = tokens[index];
|
|
31
|
+
token.content = content(token.meta);
|
|
32
|
+
return token.content;
|
|
33
|
+
};
|
|
34
|
+
}
|
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.65",
|
|
5
5
|
"description": "The Plugin for VuePress 2 - markdown power",
|
|
6
6
|
"author": "pengzhanbo <volodymyr@foxmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -42,17 +42,17 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@iconify/utils": "^2.1.24",
|
|
44
44
|
"@vuepress/helper": "2.0.0-rc.34",
|
|
45
|
-
"@vueuse/core": "^10.
|
|
45
|
+
"@vueuse/core": "^10.11.0",
|
|
46
46
|
"local-pkg": "^0.5.0",
|
|
47
47
|
"markdown-it-container": "^4.0.0",
|
|
48
48
|
"nanoid": "^5.0.7",
|
|
49
|
-
"shiki": "^1.6.
|
|
50
|
-
"tm-grammars": "^1.12.
|
|
49
|
+
"shiki": "^1.6.4",
|
|
50
|
+
"tm-grammars": "^1.12.10",
|
|
51
51
|
"tm-themes": "^1.4.3",
|
|
52
|
-
"vue": "^3.4.
|
|
52
|
+
"vue": "^3.4.29"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@iconify/json": "^2.2.
|
|
55
|
+
"@iconify/json": "^2.2.219",
|
|
56
56
|
"@types/markdown-it": "^14.1.1"
|
|
57
57
|
},
|
|
58
58
|
"publishConfig": {
|