vuepress-plugin-md-power 1.0.0-rc.48

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/lib/client/components/Bilibili.vue +45 -0
  4. package/lib/client/components/PDFViewer.vue +39 -0
  5. package/lib/client/components/Replit.vue +54 -0
  6. package/lib/client/components/Youtube.vue +41 -0
  7. package/lib/client/composables/pdf.d.ts +3 -0
  8. package/lib/client/composables/pdf.js +75 -0
  9. package/lib/client/composables/setupCanIUse.d.ts +1 -0
  10. package/lib/client/composables/setupCanIUse.js +18 -0
  11. package/lib/client/composables/size.d.ts +10 -0
  12. package/lib/client/composables/size.js +33 -0
  13. package/lib/client/config.d.ts +4 -0
  14. package/lib/client/config.js +27 -0
  15. package/lib/client/index.d.ts +1 -0
  16. package/lib/client/index.js +1 -0
  17. package/lib/client/options.d.ts +2 -0
  18. package/lib/client/options.js +1 -0
  19. package/lib/client/shim.d.ts +6 -0
  20. package/lib/client/utils/is.d.ts +3 -0
  21. package/lib/client/utils/is.js +13 -0
  22. package/lib/client/utils/link.d.ts +1 -0
  23. package/lib/client/utils/link.js +5 -0
  24. package/lib/node/features/caniuse.d.ts +25 -0
  25. package/lib/node/features/caniuse.js +129 -0
  26. package/lib/node/features/codepen.d.ts +7 -0
  27. package/lib/node/features/codepen.js +72 -0
  28. package/lib/node/features/icons/index.d.ts +2 -0
  29. package/lib/node/features/icons/index.js +2 -0
  30. package/lib/node/features/icons/plugin.d.ts +10 -0
  31. package/lib/node/features/icons/plugin.js +60 -0
  32. package/lib/node/features/icons/writer.d.ts +11 -0
  33. package/lib/node/features/icons/writer.js +100 -0
  34. package/lib/node/features/pdf.d.ts +2 -0
  35. package/lib/node/features/pdf.js +68 -0
  36. package/lib/node/features/replit.d.ts +7 -0
  37. package/lib/node/features/replit.js +59 -0
  38. package/lib/node/features/video/bilibili.d.ts +2 -0
  39. package/lib/node/features/video/bilibili.js +83 -0
  40. package/lib/node/features/video/youtube.d.ts +2 -0
  41. package/lib/node/features/video/youtube.js +74 -0
  42. package/lib/node/index.d.ts +2 -0
  43. package/lib/node/index.js +2 -0
  44. package/lib/node/markdown-it-container.d.ts +6 -0
  45. package/lib/node/plugin.d.ts +3 -0
  46. package/lib/node/plugin.js +55 -0
  47. package/lib/node/utils/package.d.ts +4 -0
  48. package/lib/node/utils/package.js +4 -0
  49. package/lib/node/utils/parseRect.d.ts +1 -0
  50. package/lib/node/utils/parseRect.js +5 -0
  51. package/lib/node/utils/resolveAttrs.d.ts +4 -0
  52. package/lib/node/utils/resolveAttrs.js +29 -0
  53. package/lib/node/utils/timeToSeconds.d.ts +1 -0
  54. package/lib/node/utils/timeToSeconds.js +8 -0
  55. package/lib/shared/caniuse.d.ts +18 -0
  56. package/lib/shared/caniuse.js +1 -0
  57. package/lib/shared/codepen.d.ts +10 -0
  58. package/lib/shared/codepen.js +1 -0
  59. package/lib/shared/icons.d.ts +17 -0
  60. package/lib/shared/icons.js +1 -0
  61. package/lib/shared/index.d.ts +6 -0
  62. package/lib/shared/index.js +6 -0
  63. package/lib/shared/pdf.d.ts +15 -0
  64. package/lib/shared/pdf.js +1 -0
  65. package/lib/shared/plugin.d.ts +12 -0
  66. package/lib/shared/plugin.js +1 -0
  67. package/lib/shared/replit.d.ts +6 -0
  68. package/lib/shared/replit.js +1 -0
  69. package/lib/shared/size.d.ts +5 -0
  70. package/lib/shared/size.js +1 -0
  71. package/lib/shared/video.d.ts +22 -0
  72. package/lib/shared/video.js +1 -0
  73. package/package.json +69 -0
@@ -0,0 +1,129 @@
1
+ import container from 'markdown-it-container';
2
+ // @[caniuse]()
3
+ const minLength = 12;
4
+ // char codes of '@[caniuse'
5
+ const START_CODES = [64, 91, 99, 97, 110, 105, 117, 115, 101];
6
+ // regexp to match the import syntax
7
+ const SYNTAX_RE = /^@\[caniuse(?:\s*?(embed|image)?(?:{([0-9,\-]*?)})?)\]\(([^)]*)\)/;
8
+ function createCanIUseRuleBlock(defaultMode) {
9
+ return (state, startLine, endLine, silent) => {
10
+ const pos = state.bMarks[startLine] + state.tShift[startLine];
11
+ const max = state.eMarks[startLine];
12
+ // return false if the length is shorter than min length
13
+ if (pos + minLength > max)
14
+ return false;
15
+ // check if it's matched the start
16
+ for (let i = 0; i < START_CODES.length; i += 1) {
17
+ if (state.src.charCodeAt(pos + i) !== START_CODES[i])
18
+ return false;
19
+ }
20
+ // check if it's matched the syntax
21
+ const match = state.src.slice(pos, max).match(SYNTAX_RE);
22
+ if (!match)
23
+ return false;
24
+ // return true as we have matched the syntax
25
+ if (silent)
26
+ return true;
27
+ const [, mode, versions = '', feature] = match;
28
+ const meta = {
29
+ feature,
30
+ mode: mode || defaultMode,
31
+ versions,
32
+ };
33
+ const token = state.push('caniuse', '', 0);
34
+ token.meta = meta;
35
+ token.map = [startLine, startLine + 1];
36
+ token.info = mode || defaultMode;
37
+ state.line = startLine + 1;
38
+ return true;
39
+ };
40
+ }
41
+ function resolveCanIUse({ feature, mode, versions }) {
42
+ if (!feature)
43
+ return '';
44
+ if (mode === 'image') {
45
+ const link = 'https://caniuse.bitsofco.de/image/';
46
+ const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`;
47
+ return `<ClientOnly><p><picture>
48
+ <source type="image/webp" srcset="${link}${feature}.webp">
49
+ <source type="image/png" srcset="${link}${feature}.png">
50
+ <img src="${link}${feature}.jpg" alt="${alt}" width="100%">
51
+ </picture></p></ClientOnly>`;
52
+ }
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>`;
59
+ }
60
+ function resolveVersions(versions) {
61
+ if (!versions)
62
+ return 'future_1,current,past_1,past_2';
63
+ const list = versions
64
+ .split(',')
65
+ .map(v => Number(v.trim()))
66
+ .filter(v => !Number.isNaN(v) && v >= -5 && v <= 3);
67
+ list.push(0);
68
+ 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(',');
79
+ }
80
+ /**
81
+ * @example
82
+ * ```md
83
+ * @[caniuse](feature_name)
84
+ * ```
85
+ */
86
+ export const caniusePlugin = (md, { mode = 'embed' } = {}) => {
87
+ md.block.ruler.before('import_code', 'caniuse', createCanIUseRuleBlock(mode), {
88
+ alt: ['paragraph', 'reference', 'blockquote', 'list'],
89
+ });
90
+ md.renderer.rules.caniuse = (tokens, index) => {
91
+ const token = tokens[index];
92
+ const content = resolveCanIUse(token.meta);
93
+ token.content = content;
94
+ return content;
95
+ };
96
+ };
97
+ /**
98
+ * @deprecated use caniuse plugin
99
+ *
100
+ * 兼容旧语法
101
+ * @example
102
+ * ```md
103
+ * :::caniuse <feature_name>
104
+ * :::
105
+ * ```
106
+ */
107
+ export function legacyCaniuse(md, { mode = 'embed' } = {}) {
108
+ const modeMap = ['image', 'embed'];
109
+ const isMode = (mode) => modeMap.includes(mode);
110
+ mode = isMode(mode) ? mode : modeMap[0];
111
+ const type = 'caniuse';
112
+ const validateReg = new RegExp(`^${type}\\s+(.*)$`);
113
+ const validate = (info) => {
114
+ return validateReg.test(info.trim());
115
+ };
116
+ const render = (tokens, index) => {
117
+ const token = tokens[index];
118
+ if (token.nesting === 1) {
119
+ const info = token.info.trim().slice(type.length).trim() || '';
120
+ const feature = info.split(/\s+/)[0];
121
+ const versions = info.match(/\{(.*)\}/)?.[1] || '';
122
+ return feature ? resolveCanIUse({ feature, mode, versions }) : '';
123
+ }
124
+ else {
125
+ return '';
126
+ }
127
+ };
128
+ md.use(container, type, { validate, render });
129
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @[codepen](user/slash)
3
+ * @[codepen preview](user/slash)
4
+ * @[codepen preview editable title="" height="400px" tab="css,result" theme="dark"](user/slash)
5
+ */
6
+ import type { PluginWithOptions } from 'markdown-it';
7
+ export declare const codepenPlugin: PluginWithOptions<never>;
@@ -0,0 +1,72 @@
1
+ import { resolveAttrs } from '../utils/resolveAttrs.js';
2
+ import { parseRect } from '../utils/parseRect.js';
3
+ 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
+ export const codepenPlugin = (md) => {
63
+ md.block.ruler.before('import_code', 'codepen', createCodepenRuleBlock(), {
64
+ alt: ['paragraph', 'reference', 'blockquote', 'list'],
65
+ });
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
+ };
@@ -0,0 +1,2 @@
1
+ export * from './writer.js';
2
+ export * from './plugin.js';
@@ -0,0 +1,2 @@
1
+ export * from './writer.js';
2
+ export * from './plugin.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * :[mdi:11]:
3
+ * :[mdi:11 24px]:
4
+ * :[mid:11 /#ccc]:
5
+ * :[fluent-mdl2:toggle-filled 128px/#fff]:
6
+ */
7
+ import type { PluginWithOptions } from 'markdown-it';
8
+ type AddIcon = (iconName: string) => string | undefined;
9
+ export declare const iconsPlugin: PluginWithOptions<AddIcon>;
10
+ export {};
@@ -0,0 +1,60 @@
1
+ import { parseRect } from '../../utils/parseRect.js';
2
+ function createTokenizer(addIcon) {
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) !== ':[')
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, state.pos + 2) === ']:') {
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);
27
+ // 不允许前后带有空格
28
+ if (/^\s|\s$/.test(content)) {
29
+ state.pos = start;
30
+ return false;
31
+ }
32
+ // found!
33
+ state.posMax = state.pos;
34
+ state.pos = start + 2;
35
+ const [iconName, options = ''] = content.split(/\s+/);
36
+ const [size, color] = options.split('/');
37
+ const open = state.push('iconify_open', 'span', 1);
38
+ open.markup = ':[';
39
+ const className = addIcon(iconName);
40
+ if (className)
41
+ open.attrSet('class', className);
42
+ let style = '';
43
+ if (size)
44
+ style += `width:${parseRect(size)};height:${parseRect(size)};`;
45
+ if (color)
46
+ style += `color:${color};`;
47
+ if (style)
48
+ open.attrSet('style', style);
49
+ const text = state.push('text', '', 0);
50
+ text.content = className ? '' : iconName;
51
+ const close = state.push('iconify_close', 'span', -1);
52
+ close.markup = ']:';
53
+ state.pos = state.posMax + 2;
54
+ state.posMax = max;
55
+ return true;
56
+ };
57
+ }
58
+ export const iconsPlugin = (md, addIcon = () => '') => {
59
+ md.inline.ruler.before('emphasis', 'iconify', createTokenizer(addIcon));
60
+ };
@@ -0,0 +1,11 @@
1
+ import type { App } from 'vuepress/core';
2
+ import type { IconsOptions } from '../../../shared/icons.js';
3
+ export interface IconCacheItem {
4
+ className: string;
5
+ content: string;
6
+ }
7
+ export declare function createIconCSSWriter(app: App, opt?: boolean | IconsOptions): {
8
+ addIcon: (iconName: string) => string | undefined;
9
+ writeCss: () => Promise<void>;
10
+ initIcon: () => Promise<string | void>;
11
+ };
@@ -0,0 +1,100 @@
1
+ import { getIconContentCSS, getIconData } from '@iconify/utils';
2
+ import { fs, logger } from 'vuepress/utils';
3
+ import { isPackageExists } from 'local-pkg';
4
+ import { customAlphabet } from 'nanoid';
5
+ import { interopDefault } from '../../utils/package.js';
6
+ import { parseRect } from '../../utils/parseRect.js';
7
+ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 8);
8
+ const iconDataCache = new Map();
9
+ const URL_CONTENT_RE = /(url\([^]+?\))/;
10
+ function resolveOption(opt) {
11
+ const options = typeof opt === 'object' ? opt : {};
12
+ options.prefix ??= 'vp-mdi';
13
+ options.color = options.color === 'currentColor' || !options.color ? 'currentcolor' : options.color;
14
+ options.size = options.size ? parseRect(`${options.size}`) : '1em';
15
+ return options;
16
+ }
17
+ export function createIconCSSWriter(app, opt) {
18
+ const cache = new Map();
19
+ const isInstalled = isPackageExists('@iconify/json');
20
+ const write = (content) => app.writeTemp('internal/md-power/icons.css', content);
21
+ const options = resolveOption(opt);
22
+ const prefix = options.prefix;
23
+ const defaultContent = getDefaultContent(options);
24
+ async function writeCss() {
25
+ let css = defaultContent;
26
+ for (const [, { content, className }] of cache)
27
+ css += `.${className} {\n --svg: ${content};\n}\n`;
28
+ await write(css);
29
+ }
30
+ function addIcon(iconName) {
31
+ if (!isInstalled)
32
+ return;
33
+ if (cache.has(iconName))
34
+ return cache.get(iconName).className;
35
+ const item = {
36
+ className: `${prefix}-${nanoid()}`,
37
+ content: '',
38
+ };
39
+ cache.set(iconName, item);
40
+ genIconContent(iconName, (content) => {
41
+ item.content = content;
42
+ writeCss();
43
+ });
44
+ return item.className;
45
+ }
46
+ async function initIcon() {
47
+ if (!opt)
48
+ return await write('');
49
+ if (!isInstalled) {
50
+ logger.error('[plugin-md-power]: `@iconify/json` not found! Please install `@iconify/json` first.');
51
+ return;
52
+ }
53
+ return await writeCss();
54
+ }
55
+ return { addIcon, writeCss, initIcon };
56
+ }
57
+ function getDefaultContent(options) {
58
+ const { prefix, size, color } = options;
59
+ return `[class^="${prefix}-"],
60
+ [class*=" ${prefix}-"] {
61
+ display: inline-block;
62
+ width: ${size};
63
+ height: ${size};
64
+ vertical-align: middle;
65
+ color: inherit;
66
+ background-color: ${color};
67
+ -webkit-mask: var(--svg) no-repeat;
68
+ mask: var(--svg) no-repeat;
69
+ -webkit-mask-size: 100% 100%;
70
+ mask-size: 100% 100%;
71
+ }
72
+ `;
73
+ }
74
+ let locate;
75
+ async function genIconContent(iconName, cb) {
76
+ if (!locate) {
77
+ const mod = await interopDefault(import('@iconify/json'));
78
+ locate = mod.locate;
79
+ }
80
+ const [collect, name] = iconName.split(':');
81
+ let iconJson = iconDataCache.get(collect);
82
+ if (!iconJson) {
83
+ const filename = locate(collect);
84
+ try {
85
+ iconJson = JSON.parse(await fs.readFile(filename, 'utf-8'));
86
+ iconDataCache.set(collect, iconJson);
87
+ }
88
+ catch (e) {
89
+ logger.warn(`[plugin-md-power] Can not find icon, ${collect} is missing!`);
90
+ }
91
+ }
92
+ const data = getIconData(iconJson, name);
93
+ if (!data)
94
+ return logger.error(`[plugin-md-power] Can not read icon in ${collect}, ${name} is missing!`);
95
+ const content = getIconContentCSS(data, {
96
+ height: data.height || 24,
97
+ });
98
+ const match = content.match(URL_CONTENT_RE);
99
+ return cb(match ? match[1] : '');
100
+ }
@@ -0,0 +1,2 @@
1
+ import type { PluginWithOptions } from 'markdown-it';
2
+ export declare const pdfPlugin: PluginWithOptions<never>;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @[pdf](/xxx)
3
+ * @[pdf 1](/xxx)
4
+ * @[pdf 1 no-toolbar width="100%" height="600px" zoom="1" ratio="1:1"](/xxx)
5
+ */
6
+ import { path } from 'vuepress/utils';
7
+ import { resolveAttrs } from '../utils/resolveAttrs.js';
8
+ import { parseRect } from '../utils/parseRect.js';
9
+ // @[pdf]()
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 || 1,
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
+ }
58
+ export const pdfPlugin = (md) => {
59
+ md.block.ruler.before('import_code', 'pdf', createPDFRuleBlock(), {
60
+ alt: ['paragraph', 'reference', 'blockquote', 'list'],
61
+ });
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
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @[replit](user/repl-name)
3
+ * @[replit](user/repl-name#filepath)
4
+ * @[replit title="" height="400px" width="100%" theme="dark"](user/repl-name)
5
+ */
6
+ import type { PluginWithOptions } from 'markdown-it';
7
+ export declare const replitPlugin: PluginWithOptions<never>;
@@ -0,0 +1,59 @@
1
+ import { resolveAttrs } from '../utils/resolveAttrs.js';
2
+ import { parseRect } from '../utils/parseRect.js';
3
+ // @[replit]()
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
+ }
49
+ export const replitPlugin = (md) => {
50
+ md.block.ruler.before('import_code', 'replit', createReplitRuleBlock(), {
51
+ alt: ['paragraph', 'reference', 'blockquote', 'list'],
52
+ });
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
+ };
@@ -0,0 +1,2 @@
1
+ import type { PluginWithOptions } from 'markdown-it';
2
+ export declare const bilibiliPlugin: PluginWithOptions<never>;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @[bilibili](bid)
3
+ * @[bilibili](aid cid)
4
+ * @[bilibili](bid aid cid)
5
+ * @[bilibili p1 autoplay time=1](aid cid)
6
+ */
7
+ import { URLSearchParams } from 'node:url';
8
+ import { resolveAttrs } from '../../utils/resolveAttrs.js';
9
+ import { parseRect } from '../../utils/parseRect.js';
10
+ import { timeToSeconds } from '../../utils/timeToSeconds.js';
11
+ 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
+ export const bilibiliPlugin = (md) => {
74
+ md.block.ruler.before('import_code', 'video_bilibili', createBilibiliRuleBlock(), {
75
+ alt: ['paragraph', 'reference', 'blockquote', 'list'],
76
+ });
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
+ };
@@ -0,0 +1,2 @@
1
+ import type { PluginWithOptions } from 'markdown-it';
2
+ export declare const youtubePlugin: PluginWithOptions<never>;