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/client/components/CodeRepl.vue +2 -2
- package/lib/client/components/VPCollapse.vue +31 -0
- package/lib/client/components/VPCollapseItem.vue +118 -0
- package/lib/client/components/VPFadeInExpandTransition.vue +154 -0
- package/lib/client/components/VPTimeline.vue +52 -0
- package/lib/client/components/VPTimelineItem.vue +330 -0
- package/lib/client/options.d.ts +3 -1
- package/lib/client/options.js +8 -0
- package/lib/client/styles/demo.css +2 -2
- package/lib/node/index.d.ts +40 -1
- package/lib/node/index.js +570 -359
- package/lib/shared/index.d.ts +37 -0
- package/package.json +21 -20
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, "'");
|
|
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
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
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
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
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/
|
|
1135
|
-
|
|
1136
|
-
|
|
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/
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
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
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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
|
-
|
|
1205
|
+
const gap = Number.parseInt(`${attrs2.gap}`);
|
|
1206
|
+
return `<VPCardMasonry${cols ? ` :cols="${cols}"` : ""}${gap >= 0 ? ` :gap="${gap}"` : ""}>`;
|
|
1191
1207
|
},
|
|
1192
|
-
|
|
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 +=
|
|
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
|
};
|