vuepress-plugin-md-power 1.0.0-rc.135 → 1.0.0-rc.137

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/node/index.js CHANGED
@@ -1,275 +1,3 @@
1
- // src/node/enhance/imageSize.ts
2
- import { Buffer } from "node:buffer";
3
- import http from "node:https";
4
- import { URL } from "node:url";
5
- import { isLinkExternal, isLinkHttp } from "@vuepress/helper";
6
- import imageSize from "image-size";
7
- import { fs, logger, path } from "vuepress/utils";
8
-
9
- // src/node/utils/resolveAttrs.ts
10
- var RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=\s*(?<quote>['"])(?<value>.+?)\k<quote>)?(?:\s+|$)/;
11
- function resolveAttrs(info) {
12
- info = info.trim();
13
- if (!info)
14
- return { rawAttrs: "", attrs: {} };
15
- const attrs2 = {};
16
- const rawAttrs = info;
17
- let matched;
18
- while (matched = info.match(RE_ATTR_VALUE)) {
19
- const { attr, value } = matched.groups;
20
- attrs2[attr] = value ?? true;
21
- info = info.slice(matched[0].length);
22
- }
23
- Object.keys(attrs2).forEach((key) => {
24
- let value = attrs2[key];
25
- value = typeof value === "string" ? value.trim() : value;
26
- if (value === "true")
27
- value = true;
28
- else if (value === "false")
29
- value = false;
30
- attrs2[key] = value;
31
- if (key.includes("-")) {
32
- const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase());
33
- attrs2[_key] = value;
34
- }
35
- });
36
- return { attrs: attrs2, rawAttrs };
37
- }
38
-
39
- // src/node/enhance/imageSize.ts
40
- var REG_IMG = /!\[.*?\]\(.*?\)/g;
41
- var REG_IMG_TAG = /<img(.*?)>/g;
42
- var REG_IMG_TAG_SRC = /src(?:set)?=(['"])(.+?)\1/g;
43
- var BADGE_LIST = [
44
- "https://img.shields.io",
45
- "https://badge.fury.io",
46
- "https://badgen.net",
47
- "https://forthebadge.com",
48
- "https://vercel.com/button"
49
- ];
50
- var cache = /* @__PURE__ */ new Map();
51
- async function imageSizePlugin(app, md, type2 = false) {
52
- if (!app.env.isBuild || !type2)
53
- return;
54
- if (type2 === "all") {
55
- const start = performance.now();
56
- try {
57
- await scanRemoteImageSize(app);
58
- } catch {
59
- }
60
- if (app.env.isDebug) {
61
- logger.info(`[vuepress-plugin-md-power] imageSizePlugin: scan all images time spent: ${performance.now() - start}ms`);
62
- }
63
- }
64
- const imageRule = md.renderer.rules.image;
65
- md.renderer.rules.image = (tokens, idx, options, env, self) => {
66
- if (!env.filePathRelative || !env.filePath)
67
- return imageRule(tokens, idx, options, env, self);
68
- const token = tokens[idx];
69
- const src = token.attrGet("src");
70
- const width = token.attrGet("width");
71
- const height = token.attrGet("height");
72
- const size = resolveSize(src, width, height, env);
73
- if (size) {
74
- token.attrSet("width", `${size.width}`);
75
- token.attrSet("height", `${size.height}`);
76
- }
77
- return imageRule(tokens, idx, options, env, self);
78
- };
79
- const rawHtmlBlockRule = md.renderer.rules.html_block;
80
- const rawHtmlInlineRule = md.renderer.rules.html_inline;
81
- md.renderer.rules.html_block = createHtmlRule(rawHtmlBlockRule);
82
- md.renderer.rules.html_inline = createHtmlRule(rawHtmlInlineRule);
83
- function createHtmlRule(rawHtmlRule) {
84
- return (tokens, idx, options, env, self) => {
85
- const token = tokens[idx];
86
- token.content = token.content.replace(REG_IMG_TAG, (raw, info) => {
87
- const { attrs: attrs2 } = resolveAttrs(info);
88
- const src = attrs2.src || attrs2.srcset;
89
- const size = resolveSize(src, attrs2.width, attrs2.height, env);
90
- if (!size)
91
- return raw;
92
- attrs2.width = size.width;
93
- attrs2.height = size.height;
94
- const imgAttrs = Object.entries(attrs2).map(([key, value]) => typeof value === "boolean" ? key : `${key}="${value}"`).join(" ");
95
- return `<img ${imgAttrs}>`;
96
- });
97
- return rawHtmlRule(tokens, idx, options, env, self);
98
- };
99
- }
100
- function resolveSize(src, width, height, env) {
101
- if (!src || src.startsWith("data:"))
102
- return false;
103
- if (width && height)
104
- return false;
105
- const isExternal = isLinkExternal(src, env.base);
106
- const filepath2 = isExternal ? src : resolveImageUrl(src, env, app);
107
- if (isExternal) {
108
- if (!cache.has(filepath2))
109
- return false;
110
- } else {
111
- if (!cache.has(filepath2)) {
112
- if (!fs.existsSync(filepath2))
113
- return false;
114
- const { width: w, height: h } = imageSize(fs.readFileSync(filepath2));
115
- if (!w || !h)
116
- return false;
117
- cache.set(filepath2, { width: w, height: h });
118
- }
119
- }
120
- const { width: originalWidth, height: originalHeight } = cache.get(filepath2);
121
- const ratio = originalWidth / originalHeight;
122
- if (width && !height) {
123
- const w = Number.parseInt(width, 10);
124
- return { width: w, height: Math.round(w / ratio) };
125
- } else if (height && !width) {
126
- const h = Number.parseInt(height, 10);
127
- return { width: Math.round(h * ratio), height: h };
128
- } else {
129
- return { width: originalWidth, height: originalHeight };
130
- }
131
- }
132
- }
133
- function resolveImageUrl(src, env, app) {
134
- if (src[0] === "/")
135
- return app.dir.public(src.slice(1));
136
- if (env.filePathRelative && src[0] === ".")
137
- return app.dir.source(path.join(path.dirname(env.filePathRelative), src));
138
- if (env.filePath && (src[0] === "." || src[0] === "/"))
139
- return path.resolve(env.filePath, src);
140
- return path.resolve(src);
141
- }
142
- async function scanRemoteImageSize(app) {
143
- if (!app.env.isBuild)
144
- return;
145
- const cwd = app.dir.source();
146
- const files = await fs.readdir(cwd, { recursive: true });
147
- const imgList = [];
148
- for (const file of files) {
149
- const filepath2 = path.join(cwd, file);
150
- if ((await fs.stat(filepath2)).isFile() && !filepath2.includes(".vuepress") && !filepath2.includes("node_modules") && filepath2.endsWith(".md")) {
151
- const content = await fs.readFile(filepath2, "utf-8");
152
- const syntaxMatched = content.match(REG_IMG) ?? [];
153
- for (const img of syntaxMatched) {
154
- const src = img.slice(img.indexOf("](") + 2, -1);
155
- addList(src.split(/\s+/)[0]);
156
- }
157
- const tagMatched = content.match(REG_IMG_TAG) ?? [];
158
- for (const img of tagMatched) {
159
- const src = img.match(REG_IMG_TAG_SRC)?.[2] ?? "";
160
- addList(src);
161
- }
162
- }
163
- }
164
- function addList(src) {
165
- if (src && isLinkHttp(src) && !imgList.includes(src) && !BADGE_LIST.some((badge) => src.startsWith(badge))) {
166
- imgList.push(src);
167
- }
168
- }
169
- await Promise.all(imgList.map(async (src) => {
170
- if (!cache.has(src)) {
171
- const { width, height } = await fetchImageSize(src);
172
- if (width && height)
173
- cache.set(src, { width, height });
174
- }
175
- }));
176
- }
177
- function fetchImageSize(src) {
178
- const link = new URL(src);
179
- return new Promise((resolve) => {
180
- http.get(link, async (stream) => {
181
- const chunks = [];
182
- for await (const chunk of stream) {
183
- chunks.push(chunk);
184
- try {
185
- const { width: width2, height: height2 } = imageSize(Buffer.concat(chunks));
186
- if (width2 && height2) {
187
- return resolve({ width: width2, height: height2 });
188
- }
189
- } catch {
190
- }
191
- }
192
- const { width, height } = imageSize(Buffer.concat(chunks));
193
- resolve({ width, height });
194
- }).on("error", () => resolve({ width: 0, height: 0 }));
195
- });
196
- }
197
- async function resolveImageSize(app, url, remote = false) {
198
- if (cache.has(url))
199
- return cache.get(url);
200
- if (isLinkHttp(url) && remote) {
201
- return await fetchImageSize(url);
202
- }
203
- if (url[0] === "/") {
204
- const filepath2 = app.dir.public(url.slice(1));
205
- if (fs.existsSync(filepath2)) {
206
- const { width, height } = imageSize(fs.readFileSync(filepath2));
207
- return { width, height };
208
- }
209
- }
210
- return { width: 0, height: 0 };
211
- }
212
-
213
- // src/node/plugin.ts
214
- import { addViteOptimizeDepsInclude } from "@vuepress/helper";
215
- import { isPackageExists as isPackageExists3 } from "local-pkg";
216
-
217
- // src/node/container/index.ts
218
- import { isPlainObject as isPlainObject2 } from "@vuepress/helper";
219
-
220
- // src/node/container/createContainer.ts
221
- import container from "markdown-it-container";
222
- function createContainerPlugin(md, type2, options = {}) {
223
- const render = (tokens, index) => {
224
- const token = tokens[index];
225
- const info = token.info.trim().slice(type2.length).trim() || "";
226
- if (token.nesting === 1) {
227
- return options.before?.(info, tokens, index) || `<div class="custom-container ${type2}">`;
228
- } else {
229
- return options.after?.(info, tokens, index) || "</div>";
230
- }
231
- };
232
- md.use(container, type2, { render });
233
- }
234
-
235
- // src/node/container/align.ts
236
- var alignList = ["left", "center", "right", "justify"];
237
- function alignPlugin(md) {
238
- for (const name of alignList) {
239
- createContainerPlugin(md, name, {
240
- before: () => `<div style="text-align:${name}">`
241
- });
242
- }
243
- }
244
-
245
- // src/node/container/card.ts
246
- function cardPlugin(md) {
247
- createContainerPlugin(md, "card", {
248
- before(info) {
249
- const { attrs: attrs2 } = resolveAttrs(info);
250
- const { title, icon } = attrs2;
251
- return `<VPCard${title ? ` title="${title}"` : ""}${icon ? ` icon="${icon}"` : ""}>`;
252
- },
253
- after: () => "</VPCard>"
254
- });
255
- createContainerPlugin(md, "card-grid", {
256
- before: () => "<VPCardGrid>",
257
- after: () => "</VPCardGrid>"
258
- });
259
- createContainerPlugin(md, "card-masonry", {
260
- before: (info) => {
261
- const { attrs: attrs2 } = resolveAttrs(info);
262
- let cols;
263
- if (attrs2.cols) {
264
- cols = attrs2.cols[0] === "{" ? attrs2.cols : Number.parseInt(`${attrs2.cols}`);
265
- }
266
- const gap = Number.parseInt(`${attrs2.gap}`);
267
- return `<VPCardMasonry${cols ? ` :cols="${cols}"` : ""}${gap >= 0 ? ` :gap="${gap}"` : ""}>`;
268
- },
269
- after: () => "</VPCardMasonry>"
270
- });
271
- }
272
-
273
1
  // src/node/container/codeTabs.ts
274
2
  import { tab } from "@mdit/plugin-tab";
275
3
  import { isPlainObject } from "@vuepress/helper";
@@ -1094,104 +822,477 @@ function getFileIcon(fileName, type2) {
1094
822
  return type2 !== "folder" ? defaultFile : defaultFolder;
1095
823
  return name;
1096
824
  }
1097
- function getFileIconName(fileName, type2 = "file") {
1098
- if (type2 === "folder") {
1099
- const icon2 = definitions.folders[fileName];
1100
- if (icon2)
1101
- return icon2;
1102
- if (fileName.includes("/"))
1103
- return definitions.folders[fileName.slice(fileName.lastIndexOf("/") + 1)];
825
+ function getFileIconName(fileName, type2 = "file") {
826
+ if (type2 === "folder") {
827
+ const icon2 = definitions.folders[fileName];
828
+ if (icon2)
829
+ return icon2;
830
+ if (fileName.includes("/"))
831
+ return definitions.folders[fileName.slice(fileName.lastIndexOf("/") + 1)];
832
+ return;
833
+ }
834
+ let icon = definitions.named[fileName] || definitions.files[fileName];
835
+ if (icon)
836
+ return icon;
837
+ icon = getFileIconTypeFromExtension(fileName) || void 0;
838
+ if (icon)
839
+ return icon;
840
+ for (const [partial, partialIcon] of Object.entries(definitions.partials)) {
841
+ if (fileName.includes(partial))
842
+ return partialIcon;
843
+ }
844
+ return icon;
845
+ }
846
+ function getFileIconTypeFromExtension(fileName) {
847
+ const firstDotIndex = fileName.indexOf(".");
848
+ if (firstDotIndex === -1)
849
+ return;
850
+ let extension = fileName.slice(firstDotIndex);
851
+ while (extension !== "") {
852
+ const icon = definitions.extensions[extension];
853
+ if (icon)
854
+ return icon;
855
+ const nextDotIndex = extension.indexOf(".", 1);
856
+ if (nextDotIndex === -1)
857
+ return;
858
+ extension = extension.slice(nextDotIndex);
859
+ }
860
+ }
861
+
862
+ // src/node/utils/cleanMarkdownEnv.ts
863
+ function cleanMarkdownEnv(env) {
864
+ return {
865
+ base: env.base,
866
+ filePath: env.filePath,
867
+ filePathRelative: env.filePathRelative,
868
+ references: env.references,
869
+ abbreviations: env.abbreviations,
870
+ annotations: env.annotations
871
+ };
872
+ }
873
+
874
+ // src/node/utils/stringifyProp.ts
875
+ function stringifyProp(data) {
876
+ return JSON.stringify(data).replace(/'/g, "&#39");
877
+ }
878
+
879
+ // src/node/container/codeTabs.ts
880
+ function createCodeTabIconGetter(options = {}) {
881
+ const noop = () => void 0;
882
+ if (options.icon === false)
883
+ return noop;
884
+ const { named, extensions } = isPlainObject(options.icon) ? options.icon : {};
885
+ return function getIcon(filename) {
886
+ if (named === false && definitions.named[filename])
887
+ return void 0;
888
+ if (extensions === false && getFileIconTypeFromExtension(filename)) {
889
+ return void 0;
890
+ }
891
+ const hasNamed = named && named.length;
892
+ const hasExt = extensions && extensions.length;
893
+ if (hasNamed || hasExt) {
894
+ if (hasNamed && named.includes(filename))
895
+ return definitions.named[filename];
896
+ if (hasExt && extensions.some((ext) => filename.endsWith(ext)))
897
+ return getFileIconTypeFromExtension(filename);
898
+ return void 0;
899
+ }
900
+ return getFileIconName(filename);
901
+ };
902
+ }
903
+ var codeTabs = (md, options = {}) => {
904
+ const getIcon = createCodeTabIconGetter(options);
905
+ tab(md, {
906
+ name: "code-tabs",
907
+ tabsOpenRenderer: ({ active, data }, tokens, index, _, env) => {
908
+ const { meta } = tokens[index];
909
+ const titles = data.map(({ title }) => md.renderInline(title, cleanMarkdownEnv(env)));
910
+ const tabsData = data.map((item, dataIndex) => {
911
+ const { id = titles[dataIndex] } = item;
912
+ return { id };
913
+ });
914
+ const titlesContent = titles.map((title, index2) => {
915
+ const icon = getIcon(title);
916
+ return `<template #title${index2}="{ value, isActive }">${icon ? `<VPIcon name="${icon}"/>` : ""}<span>${title}</span></template>`;
917
+ }).join("");
918
+ return `<CodeTabs id="${index}" :data='${stringifyProp(tabsData)}'${active === -1 ? "" : ` :active="${active}"`}${meta.id ? ` tab-id="${meta.id}"` : ""}>${titlesContent}`;
919
+ },
920
+ tabsCloseRenderer: () => `</CodeTabs>`,
921
+ tabOpenRenderer: ({ index }, tokens, tokenIndex) => {
922
+ let foundFence = false;
923
+ for (let i = tokenIndex; i < tokens.length; i++) {
924
+ const { type: type2 } = tokens[i];
925
+ if (type2 === "code-tabs_tab_close")
926
+ break;
927
+ if ((type2 === "fence" || type2 === "import_code") && !foundFence) {
928
+ foundFence = true;
929
+ continue;
930
+ }
931
+ tokens[i].type = "code_tab_empty";
932
+ tokens[i].hidden = true;
933
+ }
934
+ return `<template #tab${index}="{ value, isActive }">`;
935
+ },
936
+ tabCloseRenderer: () => `</template>`
937
+ });
938
+ };
939
+
940
+ // src/node/enhance/imageSize.ts
941
+ import { Buffer } from "node:buffer";
942
+ import http from "node:https";
943
+ import { URL } from "node:url";
944
+ import { isLinkExternal, isLinkHttp } from "@vuepress/helper";
945
+ import imageSize from "image-size";
946
+ import { fs, logger, path } from "vuepress/utils";
947
+
948
+ // src/node/utils/resolveAttrs.ts
949
+ var RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=\s*(?<quote>['"])(?<value>.+?)\k<quote>)?(?:\s+|$)/;
950
+ function resolveAttrs(info) {
951
+ info = info.trim();
952
+ if (!info)
953
+ return { rawAttrs: "", attrs: {} };
954
+ const attrs2 = {};
955
+ const rawAttrs = info;
956
+ let matched;
957
+ while (matched = info.match(RE_ATTR_VALUE)) {
958
+ const { attr, value } = matched.groups;
959
+ attrs2[attr] = value ?? true;
960
+ info = info.slice(matched[0].length);
961
+ }
962
+ Object.keys(attrs2).forEach((key) => {
963
+ let value = attrs2[key];
964
+ value = typeof value === "string" ? value.trim() : value;
965
+ if (value === "true")
966
+ value = true;
967
+ else if (value === "false")
968
+ value = false;
969
+ attrs2[key] = value;
970
+ if (key.includes("-")) {
971
+ const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase());
972
+ attrs2[_key] = value;
973
+ }
974
+ });
975
+ return { attrs: attrs2, rawAttrs };
976
+ }
977
+
978
+ // src/node/enhance/imageSize.ts
979
+ var REG_IMG = /!\[.*?\]\(.*?\)/g;
980
+ var REG_IMG_TAG = /<img(.*?)>/g;
981
+ var REG_IMG_TAG_SRC = /src(?:set)?=(['"])(.+?)\1/g;
982
+ var BADGE_LIST = [
983
+ "https://img.shields.io",
984
+ "https://badge.fury.io",
985
+ "https://badgen.net",
986
+ "https://forthebadge.com",
987
+ "https://vercel.com/button"
988
+ ];
989
+ var cache = /* @__PURE__ */ new Map();
990
+ async function imageSizePlugin(app, md, type2 = false) {
991
+ if (!app.env.isBuild || !type2)
992
+ return;
993
+ if (type2 === "all") {
994
+ const start = performance.now();
995
+ try {
996
+ await scanRemoteImageSize(app);
997
+ } catch {
998
+ }
999
+ if (app.env.isDebug) {
1000
+ logger.info(`[vuepress-plugin-md-power] imageSizePlugin: scan all images time spent: ${performance.now() - start}ms`);
1001
+ }
1002
+ }
1003
+ const imageRule = md.renderer.rules.image;
1004
+ md.renderer.rules.image = (tokens, idx, options, env, self) => {
1005
+ if (!env.filePathRelative || !env.filePath)
1006
+ return imageRule(tokens, idx, options, env, self);
1007
+ const token = tokens[idx];
1008
+ const src = token.attrGet("src");
1009
+ const width = token.attrGet("width");
1010
+ const height = token.attrGet("height");
1011
+ const size = resolveSize(src, width, height, env);
1012
+ if (size) {
1013
+ token.attrSet("width", `${size.width}`);
1014
+ token.attrSet("height", `${size.height}`);
1015
+ }
1016
+ return imageRule(tokens, idx, options, env, self);
1017
+ };
1018
+ const rawHtmlBlockRule = md.renderer.rules.html_block;
1019
+ const rawHtmlInlineRule = md.renderer.rules.html_inline;
1020
+ md.renderer.rules.html_block = createHtmlRule(rawHtmlBlockRule);
1021
+ md.renderer.rules.html_inline = createHtmlRule(rawHtmlInlineRule);
1022
+ function createHtmlRule(rawHtmlRule) {
1023
+ return (tokens, idx, options, env, self) => {
1024
+ const token = tokens[idx];
1025
+ token.content = token.content.replace(REG_IMG_TAG, (raw, info) => {
1026
+ const { attrs: attrs2 } = resolveAttrs(info);
1027
+ const src = attrs2.src || attrs2.srcset;
1028
+ const size = resolveSize(src, attrs2.width, attrs2.height, env);
1029
+ if (!size)
1030
+ return raw;
1031
+ attrs2.width = size.width;
1032
+ attrs2.height = size.height;
1033
+ const imgAttrs = Object.entries(attrs2).map(([key, value]) => typeof value === "boolean" ? key : `${key}="${value}"`).join(" ");
1034
+ return `<img ${imgAttrs}>`;
1035
+ });
1036
+ return rawHtmlRule(tokens, idx, options, env, self);
1037
+ };
1038
+ }
1039
+ function resolveSize(src, width, height, env) {
1040
+ if (!src || src.startsWith("data:"))
1041
+ return false;
1042
+ if (width && height)
1043
+ return false;
1044
+ const isExternal = isLinkExternal(src, env.base);
1045
+ const filepath2 = isExternal ? src : resolveImageUrl(src, env, app);
1046
+ if (isExternal) {
1047
+ if (!cache.has(filepath2))
1048
+ return false;
1049
+ } else {
1050
+ if (!cache.has(filepath2)) {
1051
+ if (!fs.existsSync(filepath2))
1052
+ return false;
1053
+ const { width: w, height: h } = imageSize(fs.readFileSync(filepath2));
1054
+ if (!w || !h)
1055
+ return false;
1056
+ cache.set(filepath2, { width: w, height: h });
1057
+ }
1058
+ }
1059
+ const { width: originalWidth, height: originalHeight } = cache.get(filepath2);
1060
+ const ratio = originalWidth / originalHeight;
1061
+ if (width && !height) {
1062
+ const w = Number.parseInt(width, 10);
1063
+ return { width: w, height: Math.round(w / ratio) };
1064
+ } else if (height && !width) {
1065
+ const h = Number.parseInt(height, 10);
1066
+ return { width: Math.round(h * ratio), height: h };
1067
+ } else {
1068
+ return { width: originalWidth, height: originalHeight };
1069
+ }
1070
+ }
1071
+ }
1072
+ function resolveImageUrl(src, env, app) {
1073
+ if (src[0] === "/")
1074
+ return app.dir.public(src.slice(1));
1075
+ if (env.filePathRelative && src[0] === ".")
1076
+ return app.dir.source(path.join(path.dirname(env.filePathRelative), src));
1077
+ if (env.filePath && (src[0] === "." || src[0] === "/"))
1078
+ return path.resolve(env.filePath, src);
1079
+ return path.resolve(src);
1080
+ }
1081
+ async function scanRemoteImageSize(app) {
1082
+ if (!app.env.isBuild)
1104
1083
  return;
1084
+ const cwd = app.dir.source();
1085
+ const files = await fs.readdir(cwd, { recursive: true });
1086
+ const imgList = [];
1087
+ for (const file of files) {
1088
+ const filepath2 = path.join(cwd, file);
1089
+ if ((await fs.stat(filepath2)).isFile() && !filepath2.includes(".vuepress") && !filepath2.includes("node_modules") && filepath2.endsWith(".md")) {
1090
+ const content = await fs.readFile(filepath2, "utf-8");
1091
+ const syntaxMatched = content.match(REG_IMG) ?? [];
1092
+ for (const img of syntaxMatched) {
1093
+ const src = img.slice(img.indexOf("](") + 2, -1);
1094
+ addList(src.split(/\s+/)[0]);
1095
+ }
1096
+ const tagMatched = content.match(REG_IMG_TAG) ?? [];
1097
+ for (const img of tagMatched) {
1098
+ const src = img.match(REG_IMG_TAG_SRC)?.[2] ?? "";
1099
+ addList(src);
1100
+ }
1101
+ }
1105
1102
  }
1106
- let icon = definitions.named[fileName] || definitions.files[fileName];
1107
- if (icon)
1108
- return icon;
1109
- icon = getFileIconTypeFromExtension(fileName) || void 0;
1110
- if (icon)
1111
- return icon;
1112
- for (const [partial, partialIcon] of Object.entries(definitions.partials)) {
1113
- if (fileName.includes(partial))
1114
- return partialIcon;
1103
+ function addList(src) {
1104
+ if (src && isLinkHttp(src) && !imgList.includes(src) && !BADGE_LIST.some((badge) => src.startsWith(badge))) {
1105
+ imgList.push(src);
1106
+ }
1115
1107
  }
1116
- return icon;
1108
+ await Promise.all(imgList.map(async (src) => {
1109
+ if (!cache.has(src)) {
1110
+ const { width, height } = await fetchImageSize(src);
1111
+ if (width && height)
1112
+ cache.set(src, { width, height });
1113
+ }
1114
+ }));
1117
1115
  }
1118
- function getFileIconTypeFromExtension(fileName) {
1119
- const firstDotIndex = fileName.indexOf(".");
1120
- if (firstDotIndex === -1)
1121
- return;
1122
- let extension = fileName.slice(firstDotIndex);
1123
- while (extension !== "") {
1124
- const icon = definitions.extensions[extension];
1125
- if (icon)
1126
- return icon;
1127
- const nextDotIndex = extension.indexOf(".", 1);
1128
- if (nextDotIndex === -1)
1129
- return;
1130
- extension = extension.slice(nextDotIndex);
1116
+ function fetchImageSize(src) {
1117
+ const link = new URL(src);
1118
+ return new Promise((resolve) => {
1119
+ http.get(link, async (stream) => {
1120
+ const chunks = [];
1121
+ for await (const chunk of stream) {
1122
+ chunks.push(chunk);
1123
+ try {
1124
+ const { width: width2, height: height2 } = imageSize(Buffer.concat(chunks));
1125
+ if (width2 && height2) {
1126
+ return resolve({ width: width2, height: height2 });
1127
+ }
1128
+ } catch {
1129
+ }
1130
+ }
1131
+ const { width, height } = imageSize(Buffer.concat(chunks));
1132
+ resolve({ width, height });
1133
+ }).on("error", () => resolve({ width: 0, height: 0 }));
1134
+ });
1135
+ }
1136
+ async function resolveImageSize(app, url, remote = false) {
1137
+ if (cache.has(url))
1138
+ return cache.get(url);
1139
+ if (isLinkHttp(url) && remote) {
1140
+ return await fetchImageSize(url);
1141
+ }
1142
+ if (url[0] === "/") {
1143
+ const filepath2 = app.dir.public(url.slice(1));
1144
+ if (fs.existsSync(filepath2)) {
1145
+ const { width, height } = imageSize(fs.readFileSync(filepath2));
1146
+ return { width, height };
1147
+ }
1131
1148
  }
1149
+ return { width: 0, height: 0 };
1132
1150
  }
1133
1151
 
1134
- // src/node/utils/stringifyProp.ts
1135
- function stringifyProp(data) {
1136
- return JSON.stringify(data).replace(/'/g, "&#39");
1137
- }
1152
+ // src/node/plugin.ts
1153
+ import { addViteOptimizeDepsInclude } from "@vuepress/helper";
1154
+ import { isPackageExists as isPackageExists3 } from "local-pkg";
1138
1155
 
1139
- // src/node/container/codeTabs.ts
1140
- var codeTabs = (md, options = {}) => {
1141
- const getIcon = (filename) => {
1142
- if (options.icon === false)
1143
- return void 0;
1144
- const { named, extensions } = isPlainObject(options.icon) ? options.icon : {};
1145
- if (named === false && definitions.named[filename])
1146
- return void 0;
1147
- if (extensions === false && getFileIconTypeFromExtension(filename)) {
1148
- return void 0;
1149
- }
1150
- const hasNamed = named && named.length;
1151
- const hasExt = extensions && extensions.length;
1152
- if (hasNamed || hasExt) {
1153
- if (hasNamed && named.includes(filename))
1154
- return definitions.named[filename];
1155
- if (hasExt && extensions.some((ext) => filename.endsWith(ext)))
1156
- return getFileIconTypeFromExtension(filename);
1157
- return void 0;
1156
+ // src/node/container/index.ts
1157
+ import { isPlainObject as isPlainObject2 } from "@vuepress/helper";
1158
+
1159
+ // src/node/container/createContainer.ts
1160
+ import container from "markdown-it-container";
1161
+ function createContainerPlugin(md, type2, { before, after } = {}) {
1162
+ const render = (tokens, index, options, env) => {
1163
+ const token = tokens[index];
1164
+ const info = token.info.trim().slice(type2.length).trim() || "";
1165
+ if (token.nesting === 1) {
1166
+ return before?.(info, tokens, index, options, env) || `<div class="custom-container ${type2}">`;
1167
+ } else {
1168
+ return after?.(info, tokens, index, options, env) || "</div>";
1158
1169
  }
1159
- return getFileIconName(filename);
1160
1170
  };
1161
- tab(md, {
1162
- name: "code-tabs",
1163
- tabsOpenRenderer: ({ active, data }, tokens, index, _, env) => {
1164
- const { meta } = tokens[index];
1165
- const titles = data.map(({ title }) => md.renderInline(title, env));
1166
- const tabsData = data.map((item, dataIndex) => {
1167
- const { id = titles[dataIndex] } = item;
1168
- return { id };
1169
- });
1170
- const titlesContent = titles.map((title, index2) => {
1171
- const icon = getIcon(title);
1172
- return `<template #title${index2}="{ value, isActive }">${icon ? `<VPIcon name="${icon}"/>` : ""}<span>${title}</span></template>`;
1173
- }).join("");
1174
- return `<CodeTabs id="${index}" :data='${stringifyProp(tabsData)}'${active === -1 ? "" : ` :active="${active}"`}${meta.id ? ` tab-id="${meta.id}"` : ""}>${titlesContent}`;
1171
+ md.use(container, type2, { render });
1172
+ }
1173
+
1174
+ // src/node/container/align.ts
1175
+ var alignList = ["left", "center", "right", "justify"];
1176
+ function alignPlugin(md) {
1177
+ for (const name of alignList) {
1178
+ createContainerPlugin(md, name, {
1179
+ before: () => `<div style="text-align:${name}">`
1180
+ });
1181
+ }
1182
+ }
1183
+
1184
+ // src/node/container/card.ts
1185
+ function cardPlugin(md) {
1186
+ createContainerPlugin(md, "card", {
1187
+ before(info) {
1188
+ const { attrs: attrs2 } = resolveAttrs(info);
1189
+ const { title, icon } = attrs2;
1190
+ return `<VPCard${title ? ` title="${title}"` : ""}${icon ? ` icon="${icon}"` : ""}>`;
1175
1191
  },
1176
- tabsCloseRenderer: () => `</CodeTabs>`,
1177
- tabOpenRenderer: ({ index }, tokens, tokenIndex) => {
1178
- let foundFence = false;
1179
- for (let i = tokenIndex; i < tokens.length; i++) {
1180
- const { type: type2 } = tokens[i];
1181
- if (type2 === "code-tabs_tab_close")
1182
- break;
1183
- if ((type2 === "fence" || type2 === "import_code") && !foundFence) {
1184
- foundFence = true;
1185
- continue;
1186
- }
1187
- tokens[i].type = "code_tab_empty";
1188
- tokens[i].hidden = true;
1192
+ after: () => "</VPCard>"
1193
+ });
1194
+ createContainerPlugin(md, "card-grid", {
1195
+ before: () => "<VPCardGrid>",
1196
+ after: () => "</VPCardGrid>"
1197
+ });
1198
+ createContainerPlugin(md, "card-masonry", {
1199
+ before: (info) => {
1200
+ const { attrs: attrs2 } = resolveAttrs(info);
1201
+ let cols;
1202
+ if (attrs2.cols) {
1203
+ cols = attrs2.cols[0] === "{" ? attrs2.cols : Number.parseInt(`${attrs2.cols}`);
1189
1204
  }
1190
- return `<template #tab${index}="{ value, isActive }">`;
1205
+ const gap = Number.parseInt(`${attrs2.gap}`);
1206
+ return `<VPCardMasonry${cols ? ` :cols="${cols}"` : ""}${gap >= 0 ? ` :gap="${gap}"` : ""}>`;
1191
1207
  },
1192
- tabCloseRenderer: () => `</template>`
1208
+ after: () => "</VPCardMasonry>"
1193
1209
  });
1194
- };
1210
+ }
1211
+
1212
+ // src/node/container/collapse.ts
1213
+ function collapsePlugin(md) {
1214
+ createContainerPlugin(md, "collapse", {
1215
+ before: (info, tokens, index) => {
1216
+ const { attrs: attrs2 } = resolveAttrs(info);
1217
+ const idx = parseCollapse(tokens, index, attrs2);
1218
+ const { accordion } = attrs2;
1219
+ return `<VPCollapse${accordion ? " accordion" : ""}${idx !== void 0 ? ` :index="${idx}"` : ""}>`;
1220
+ },
1221
+ after: () => `</VPCollapse>`
1222
+ });
1223
+ md.renderer.rules.collapse_item_open = (tokens, idx) => {
1224
+ const token = tokens[idx];
1225
+ const { expand, index } = token.meta;
1226
+ return `<VPCollapseItem${expand ? " expand" : ""}${` :index="${index}"`}>`;
1227
+ };
1228
+ md.renderer.rules.collapse_item_close = () => "</VPCollapseItem>";
1229
+ md.renderer.rules.collapse_item_title_open = () => "<template #title>";
1230
+ md.renderer.rules.collapse_item_title_close = () => "</template>";
1231
+ }
1232
+ function parseCollapse(tokens, index, attrs2) {
1233
+ const listStack = [];
1234
+ let idx = -1;
1235
+ let defaultIndex;
1236
+ let hashExpand = false;
1237
+ for (let i = index + 1; i < tokens.length; i++) {
1238
+ const token = tokens[i];
1239
+ if (token.type === "container_collapse_close") {
1240
+ break;
1241
+ }
1242
+ if (token.type === "bullet_list_open") {
1243
+ listStack.push(0);
1244
+ if (listStack.length === 1)
1245
+ token.hidden = true;
1246
+ } else if (token.type === "bullet_list_close") {
1247
+ listStack.pop();
1248
+ if (listStack.length === 0)
1249
+ token.hidden = true;
1250
+ } else if (token.type === "list_item_open") {
1251
+ const currentLevel = listStack.length;
1252
+ if (currentLevel === 1) {
1253
+ token.type = "collapse_item_open";
1254
+ tokens[i + 1].type = "collapse_item_title_open";
1255
+ tokens[i + 3].type = "collapse_item_title_close";
1256
+ idx++;
1257
+ const inlineToken = tokens[i + 2];
1258
+ const firstToken = inlineToken.children[0];
1259
+ let flag = "";
1260
+ let expand;
1261
+ if (firstToken.type === "text") {
1262
+ firstToken.content = firstToken.content.trim().replace(/^:[+\-]\s*/, (match) => {
1263
+ flag = match.trim();
1264
+ return "";
1265
+ });
1266
+ }
1267
+ if (attrs2.accordion) {
1268
+ if (!hashExpand && flag === ":+") {
1269
+ expand = hashExpand = true;
1270
+ defaultIndex = idx;
1271
+ }
1272
+ } else if (flag === ":+") {
1273
+ expand = true;
1274
+ } else if (flag === ":-") {
1275
+ expand = false;
1276
+ } else {
1277
+ expand = !!attrs2.expand;
1278
+ }
1279
+ token.meta = {
1280
+ index: idx,
1281
+ expand
1282
+ };
1283
+ }
1284
+ } else if (token.type === "list_item_close") {
1285
+ const currentLevel = listStack.length;
1286
+ if (currentLevel === 1) {
1287
+ token.type = "collapse_item_close";
1288
+ }
1289
+ }
1290
+ }
1291
+ if (attrs2.accordion && attrs2.expand && !hashExpand) {
1292
+ defaultIndex = 0;
1293
+ }
1294
+ return defaultIndex;
1295
+ }
1195
1296
 
1196
1297
  // src/node/container/demoWrapper.ts
1197
1298
  function demoWrapperPlugin(md) {
@@ -1760,7 +1861,7 @@ function parseArgs(line) {
1760
1861
  isNextValue = !isBool;
1761
1862
  }
1762
1863
  if (!isKey && !isNextValue) {
1763
- cmd += `${value}`;
1864
+ cmd += ` ${value}`;
1764
1865
  } else {
1765
1866
  newLine += `${value}${i !== npmArgs.length - 1 ? v : ""}`;
1766
1867
  if (!isKey && isNextValue) {
@@ -1791,7 +1892,7 @@ var tabs = (md) => {
1791
1892
  name: "tabs",
1792
1893
  tabsOpenRenderer: ({ active, data }, tokens, index, _, env) => {
1793
1894
  const { meta } = tokens[index];
1794
- const titles = data.map(({ title }) => md.renderInline(title, env));
1895
+ const titles = data.map(({ title }) => md.renderInline(title, cleanMarkdownEnv(env)));
1795
1896
  const tabsData = data.map((item, dataIndex) => {
1796
1897
  const { id = titles[dataIndex] } = item;
1797
1898
  return { id };
@@ -1807,6 +1908,99 @@ ${titles.map(
1807
1908
  });
1808
1909
  };
1809
1910
 
1911
+ // src/node/container/timeline.ts
1912
+ import { isEmptyObject } from "@pengzhanbo/utils";
1913
+ var RE_KEY = /(\w+)=\s*/;
1914
+ var RE_SEARCH_KEY = /\s+\w+=\s*|$/;
1915
+ var RE_CLEAN_VALUE = /(?<quote>["'])(.*?)(\k<quote>)/;
1916
+ function timelinePlugin(md) {
1917
+ createContainerPlugin(md, "timeline", {
1918
+ before(info, tokens, index) {
1919
+ parseTimeline(tokens, index);
1920
+ const { attrs: attrs2 } = resolveAttrs(info);
1921
+ const { horizontal, card, placement, line } = attrs2;
1922
+ return `<VPTimeline${horizontal ? " horizontal" : ""}${card ? " card" : ' :card="undefined"'}${placement ? ` placement="${placement}"` : ""}${line ? ` line="${line}"` : ""}>`;
1923
+ },
1924
+ after: () => "</VPTimeline>"
1925
+ });
1926
+ md.renderer.rules.timeline_item_open = (tokens, idx) => {
1927
+ const token = tokens[idx];
1928
+ const { time, type: type2, icon, color, line, card, placement } = token.meta;
1929
+ return `<VPTimelineItem${time ? ` time="${time}"` : ""}${type2 ? ` type="${type2}"` : ""}${color ? ` color="${color}"` : ""}${line ? ` line="${line}"` : ""}${icon ? ` icon="${icon}"` : ""}${card === "true" ? " card" : card === "false" ? "" : ' :card="undefined"'}${placement ? ` placement="${placement}"` : ""}>${icon ? `<template #icon><VPIcon name="${icon}"/></template>` : ""}`;
1930
+ };
1931
+ md.renderer.rules.timeline_item_close = () => "</VPTimelineItem>";
1932
+ md.renderer.rules.timeline_item_title_open = () => "<template #title>";
1933
+ md.renderer.rules.timeline_item_title_close = () => "</template>";
1934
+ }
1935
+ function parseTimeline(tokens, index) {
1936
+ const listStack = [];
1937
+ for (let i = index + 1; i < tokens.length; i++) {
1938
+ const token = tokens[i];
1939
+ if (token.type === "container_timeline_close") {
1940
+ break;
1941
+ }
1942
+ if (token.type === "bullet_list_open") {
1943
+ listStack.push(0);
1944
+ if (listStack.length === 1)
1945
+ token.hidden = true;
1946
+ } else if (token.type === "bullet_list_close") {
1947
+ listStack.pop();
1948
+ if (listStack.length === 0)
1949
+ token.hidden = true;
1950
+ } else if (token.type === "list_item_open") {
1951
+ const currentLevel = listStack.length;
1952
+ if (currentLevel === 1) {
1953
+ token.type = "timeline_item_open";
1954
+ tokens[i + 1].type = "timeline_item_title_open";
1955
+ tokens[i + 3].type = "timeline_item_title_close";
1956
+ const inlineToken = tokens[i + 2];
1957
+ const softbreakIndex = inlineToken.children.findLastIndex(
1958
+ (token2) => token2.type === "softbreak"
1959
+ );
1960
+ if (softbreakIndex !== -1) {
1961
+ const lastToken = inlineToken.children[inlineToken.children.length - 1];
1962
+ token.meta = extractTimelineAttributes(lastToken.content.trim());
1963
+ if (!isEmptyObject(token.meta)) {
1964
+ inlineToken.children = inlineToken.children.slice(0, softbreakIndex);
1965
+ }
1966
+ } else {
1967
+ token.meta = {};
1968
+ }
1969
+ }
1970
+ } else if (token.type === "list_item_close") {
1971
+ const currentLevel = listStack.length;
1972
+ if (currentLevel === 1) {
1973
+ token.type = "timeline_item_close";
1974
+ }
1975
+ }
1976
+ }
1977
+ }
1978
+ function extractTimelineAttributes(rawText) {
1979
+ const attrKeys = ["time", "type", "icon", "line", "color", "card", "placement"];
1980
+ const attrs2 = {};
1981
+ let buffer = rawText.trim();
1982
+ while (buffer.length) {
1983
+ const keyMatch = buffer.match(RE_KEY);
1984
+ if (!keyMatch) {
1985
+ break;
1986
+ }
1987
+ const matchedKey = keyMatch[1].toLowerCase();
1988
+ if (!attrKeys.includes(matchedKey)) {
1989
+ break;
1990
+ }
1991
+ const keyStart = keyMatch.index;
1992
+ const keyEnd = keyStart + keyMatch[0].length;
1993
+ buffer = buffer.slice(keyEnd);
1994
+ let valueEnd = buffer.search(RE_SEARCH_KEY);
1995
+ if (valueEnd === -1)
1996
+ valueEnd = buffer.length;
1997
+ const value = buffer.slice(0, valueEnd).trim();
1998
+ attrs2[matchedKey] = value.replace(RE_CLEAN_VALUE, "$2");
1999
+ buffer = buffer.slice(valueEnd);
2000
+ }
2001
+ return attrs2;
2002
+ }
2003
+
1810
2004
  // src/node/container/index.ts
1811
2005
  async function containerPlugin(app, md, options) {
1812
2006
  alignPlugin(md);
@@ -1823,6 +2017,10 @@ async function containerPlugin(app, md, options) {
1823
2017
  if (options.fileTree) {
1824
2018
  fileTreePlugin(md, isPlainObject2(options.fileTree) ? options.fileTree : {});
1825
2019
  }
2020
+ if (options.timeline)
2021
+ timelinePlugin(md);
2022
+ if (options.collapse)
2023
+ collapsePlugin(md);
1826
2024
  }
1827
2025
 
1828
2026
  // src/node/demo/demo.ts
@@ -3116,7 +3314,7 @@ var abbrPlugin = (md) => {
3116
3314
  md.core.ruler.after("linkify", "abbr_replace", abbrReplace);
3117
3315
  md.renderer.rules.abbreviation = (tokens, idx, _, env) => {
3118
3316
  const { content, info } = tokens[idx];
3119
- return `<Abbreviation>${content}${info ? `<template #tooltip>${md.renderInline(info, env)}</template>` : ""}</Abbreviation>`;
3317
+ return `<Abbreviation>${content}${info ? `<template #tooltip>${md.renderInline(info, cleanMarkdownEnv(env))}</template>` : ""}</Abbreviation>`;
3120
3318
  };
3121
3319
  };
3122
3320
 
@@ -3412,6 +3610,18 @@ async function prepareConfigFile(app, options) {
3412
3610
  imports.add(`import Abbreviation from '${CLIENT_FOLDER}components/Abbreviation.vue'`);
3413
3611
  enhances.add(`app.component('Abbreviation', Abbreviation)`);
3414
3612
  }
3613
+ if (options.timeline) {
3614
+ imports.add(`import VPTimeline from '${CLIENT_FOLDER}components/VPTimeline.vue'`);
3615
+ imports.add(`import VPTimelineItem from '${CLIENT_FOLDER}components/VPTimelineItem.vue'`);
3616
+ enhances.add(`app.component('VPTimeline', VPTimeline)`);
3617
+ enhances.add(`app.component('VPTimelineItem', VPTimelineItem)`);
3618
+ }
3619
+ if (options.collapse) {
3620
+ imports.add(`import VPCollapse from '${CLIENT_FOLDER}components/VPCollapse.vue'`);
3621
+ imports.add(`import VPCollapseItem from '${CLIENT_FOLDER}components/VPCollapseItem.vue'`);
3622
+ enhances.add(`app.component('VPCollapse', VPCollapse)`);
3623
+ enhances.add(`app.component('VPCollapseItem', VPCollapseItem)`);
3624
+ }
3415
3625
  return app.writeTemp(
3416
3626
  "md-power/config.js",
3417
3627
  `import { defineClientConfig } from 'vuepress/client'
@@ -3480,6 +3690,7 @@ function markdownPowerPlugin(options = {}) {
3480
3690
  };
3481
3691
  }
3482
3692
  export {
3693
+ createCodeTabIconGetter,
3483
3694
  markdownPowerPlugin,
3484
3695
  resolveImageSize
3485
3696
  };