vuepress-plugin-md-power 1.0.0-rc.96 → 1.0.0-rc.98

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,3 +1,211 @@
1
+ // src/node/features/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, 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 attrs = {};
16
+ const rawAttrs = info;
17
+ let matched;
18
+ while (matched = info.match(RE_ATTR_VALUE)) {
19
+ const { attr, value } = matched.groups || {};
20
+ attrs[attr] = value ?? true;
21
+ info = info.slice(matched[0].length);
22
+ }
23
+ Object.keys(attrs).forEach((key) => {
24
+ let value = attrs[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
+ attrs[key] = value;
31
+ if (key.includes("-")) {
32
+ const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase());
33
+ attrs[_key] = value;
34
+ }
35
+ });
36
+ return { attrs, rawAttrs };
37
+ }
38
+
39
+ // src/node/features/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
+ try {
56
+ await scanRemoteImageSize(app);
57
+ } catch {
58
+ }
59
+ }
60
+ const imageRule = md.renderer.rules.image;
61
+ md.renderer.rules.image = (tokens, idx, options, env, self) => {
62
+ if (!env.filePathRelative || !env.filePath)
63
+ return imageRule(tokens, idx, options, env, self);
64
+ const token = tokens[idx];
65
+ const src = token.attrGet("src");
66
+ const width = token.attrGet("width");
67
+ const height = token.attrGet("height");
68
+ const size = resolveSize(src, width, height, env);
69
+ if (size) {
70
+ token.attrSet("width", `${size.width}`);
71
+ token.attrSet("height", `${size.height}`);
72
+ }
73
+ return imageRule(tokens, idx, options, env, self);
74
+ };
75
+ const rawHtmlBlockRule = md.renderer.rules.html_block;
76
+ const rawHtmlInlineRule = md.renderer.rules.html_inline;
77
+ md.renderer.rules.html_block = createHtmlRule(rawHtmlBlockRule);
78
+ md.renderer.rules.html_inline = createHtmlRule(rawHtmlInlineRule);
79
+ function createHtmlRule(rawHtmlRule) {
80
+ return (tokens, idx, options, env, self) => {
81
+ const token = tokens[idx];
82
+ token.content = token.content.replace(REG_IMG_TAG, (raw, info) => {
83
+ const { attrs } = resolveAttrs(info);
84
+ const src = attrs.src || attrs.srcset;
85
+ const size = resolveSize(src, attrs.width, attrs.height, env);
86
+ if (!size)
87
+ return raw;
88
+ attrs.width = size.width;
89
+ attrs.height = size.height;
90
+ const imgAttrs = Object.entries(attrs).map(([key, value]) => typeof value === "boolean" ? key : `${key}="${value}"`).join(" ");
91
+ return `<img ${imgAttrs}>`;
92
+ });
93
+ return rawHtmlRule(tokens, idx, options, env, self);
94
+ };
95
+ }
96
+ function resolveSize(src, width, height, env) {
97
+ if (!src || src.startsWith("data:"))
98
+ return false;
99
+ if (width && height)
100
+ return false;
101
+ const isExternal = isLinkExternal(src, env.base);
102
+ const filepath2 = isExternal ? src : resolveImageUrl(src, env, app);
103
+ if (isExternal) {
104
+ if (!cache.has(filepath2))
105
+ return false;
106
+ } else {
107
+ if (!cache.has(filepath2)) {
108
+ if (!fs.existsSync(filepath2))
109
+ return false;
110
+ const { width: w, height: h } = imageSize(filepath2);
111
+ if (!w || !h)
112
+ return false;
113
+ cache.set(filepath2, { width: w, height: h });
114
+ }
115
+ }
116
+ const { width: originalWidth, height: originalHeight } = cache.get(filepath2);
117
+ const ratio = originalWidth / originalHeight;
118
+ if (width && !height) {
119
+ const w = Number.parseInt(width, 10);
120
+ return { width: w, height: Math.round(w / ratio) };
121
+ } else if (height && !width) {
122
+ const h = Number.parseInt(height, 10);
123
+ return { width: Math.round(h * ratio), height: h };
124
+ } else {
125
+ return { width: originalWidth, height: originalHeight };
126
+ }
127
+ }
128
+ }
129
+ function resolveImageUrl(src, env, app) {
130
+ if (src[0] === "/")
131
+ return app.dir.public(src.slice(1));
132
+ if (env.filePathRelative && src[0] === ".")
133
+ return app.dir.source(path.join(path.dirname(env.filePathRelative), src));
134
+ if (env.filePath && (src[0] === "." || src[0] === "/"))
135
+ return path.resolve(env.filePath, src);
136
+ return path.resolve(src);
137
+ }
138
+ async function scanRemoteImageSize(app) {
139
+ if (!app.env.isBuild)
140
+ return;
141
+ const cwd = app.dir.source();
142
+ const files = await fs.readdir(cwd, { recursive: true });
143
+ const imgList = [];
144
+ for (const file of files) {
145
+ const filepath2 = path.join(cwd, file);
146
+ if ((await fs.stat(filepath2)).isFile() && !filepath2.includes(".vuepress") && !filepath2.includes("node_modules") && filepath2.endsWith(".md")) {
147
+ const content = await fs.readFile(filepath2, "utf-8");
148
+ const syntaxMatched = content.match(REG_IMG) ?? [];
149
+ for (const img of syntaxMatched) {
150
+ const src = img.slice(img.indexOf("](") + 2, -1);
151
+ addList(src.split(/\s+/)[0]);
152
+ }
153
+ const tagMatched = content.match(REG_IMG_TAG) ?? [];
154
+ for (const img of tagMatched) {
155
+ const src = img.match(REG_IMG_TAG_SRC)?.[2] ?? "";
156
+ addList(src);
157
+ }
158
+ }
159
+ }
160
+ function addList(src) {
161
+ if (src && isLinkHttp(src) && !imgList.includes(src) && !BADGE_LIST.some((badge) => src.startsWith(badge))) {
162
+ imgList.push(src);
163
+ }
164
+ }
165
+ await Promise.all(imgList.map(async (src) => {
166
+ if (!cache.has(src)) {
167
+ const { width, height } = await fetchImageSize(src);
168
+ if (width && height)
169
+ cache.set(src, { width, height });
170
+ }
171
+ }));
172
+ }
173
+ function fetchImageSize(src) {
174
+ const link = new URL(src);
175
+ return new Promise((resolve) => {
176
+ http.get(link, async (stream) => {
177
+ const chunks = [];
178
+ for await (const chunk of stream) {
179
+ chunks.push(chunk);
180
+ try {
181
+ const { width: width2, height: height2 } = imageSize(Buffer.concat(chunks));
182
+ if (width2 && height2) {
183
+ return resolve({ width: width2, height: height2 });
184
+ }
185
+ } catch {
186
+ }
187
+ }
188
+ const { width, height } = imageSize(Buffer.concat(chunks));
189
+ resolve({ width, height });
190
+ }).on("error", () => resolve({ width: 0, height: 0 }));
191
+ });
192
+ }
193
+ async function resolveImageSize(app, url, remote = false) {
194
+ if (cache.has(url))
195
+ return cache.get(url);
196
+ if (isLinkHttp(url) && remote) {
197
+ return await fetchImageSize(url);
198
+ }
199
+ if (url[0] === "/") {
200
+ const filepath2 = app.dir.public(url.slice(1));
201
+ if (fs.existsSync(filepath2)) {
202
+ const { width, height } = imageSize(filepath2);
203
+ return { width, height };
204
+ }
205
+ }
206
+ return { width: 0, height: 0 };
207
+ }
208
+
1
209
  // src/node/plugin.ts
2
210
  import { addViteOptimizeDepsInclude } from "@vuepress/helper";
3
211
 
@@ -115,39 +323,6 @@ function resolveVersions(versions) {
115
323
  };
116
324
  }
117
325
 
118
- // src/node/features/pdf.ts
119
- import { path } from "vuepress/utils";
120
-
121
- // src/node/utils/resolveAttrs.ts
122
- var RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=\s*(?<quote>['"])(?<value>.+?)\k<quote>)?(?:\s+|$)/;
123
- function resolveAttrs(info) {
124
- info = info.trim();
125
- if (!info)
126
- return { rawAttrs: "", attrs: {} };
127
- const attrs = {};
128
- const rawAttrs = info;
129
- let matched;
130
- while (matched = info.match(RE_ATTR_VALUE)) {
131
- const { attr, value } = matched.groups || {};
132
- attrs[attr] = value ?? true;
133
- info = info.slice(matched[0].length);
134
- }
135
- Object.keys(attrs).forEach((key) => {
136
- let value = attrs[key];
137
- value = typeof value === "string" ? value.trim() : value;
138
- if (value === "true")
139
- value = true;
140
- else if (value === "false")
141
- value = false;
142
- attrs[key] = value;
143
- if (key.includes("-")) {
144
- const _key = key.replace(/-(\w)/g, (_, c) => c.toUpperCase());
145
- attrs[_key] = value;
146
- }
147
- });
148
- return { attrs, rawAttrs };
149
- }
150
-
151
326
  // src/node/utils/parseRect.ts
152
327
  function parseRect(str, unit = "px") {
153
328
  if (Number.parseFloat(str) === Number(str))
@@ -155,271 +330,67 @@ function parseRect(str, unit = "px") {
155
330
  return str;
156
331
  }
157
332
 
158
- // src/node/features/pdf.ts
159
- var pdfPlugin = (md) => {
333
+ // src/node/features/codepen.ts
334
+ var CODEPEN_LINK = "https://codepen.io/";
335
+ var codepenPlugin = (md) => {
160
336
  createRuleBlock(md, {
161
- type: "pdf",
162
- // eslint-disable-next-line regexp/no-super-linear-backtracking
163
- syntaxPattern: /^@\[pdf(?:\s+(\d+))?([^\]]*)\]\(([^)]*)\)/,
164
- meta([, page, info = "", src = ""]) {
337
+ type: "codepen",
338
+ syntaxPattern: /^@\[codepen([^\]]*)\]\(([^)]*)\)/,
339
+ meta: ([, info = "", source = ""]) => {
165
340
  const { attrs } = resolveAttrs(info);
341
+ const [user, slash] = source.split("/");
166
342
  return {
167
- src,
168
- page: +page || 1,
169
- noToolbar: Boolean(attrs.noToolbar ?? false),
170
- zoom: +attrs.zoom || 50,
171
343
  width: attrs.width ? parseRect(attrs.width) : "100%",
172
- height: attrs.height ? parseRect(attrs.height) : "",
173
- ratio: attrs.ratio ? parseRect(attrs.ratio) : "",
174
- title: path.basename(src || "")
344
+ height: attrs.height ? parseRect(attrs.height) : "400px",
345
+ user,
346
+ slash,
347
+ title: attrs.title,
348
+ preview: attrs.preview,
349
+ editable: attrs.editable,
350
+ tab: attrs.tab ?? "result",
351
+ theme: attrs.theme
175
352
  };
176
353
  },
177
- content({ title, src, page, noToolbar, width, height, ratio, zoom }) {
178
- return `<PDFViewer src="${src}" title="${title}" :page="${page}" :no-toolbar="${noToolbar}" width="${width}" height="${height}" ratio="${ratio}" :zoom="${zoom}" />`;
179
- }
180
- });
181
- };
182
-
183
- // src/node/features/icons.ts
184
- var [openTag, endTag] = [":[", "]:"];
185
- function createTokenizer() {
186
- return (state, silent) => {
187
- let found = false;
188
- const max = state.posMax;
189
- const start = state.pos;
190
- if (state.src.slice(start, start + 2) !== openTag)
191
- return false;
192
- if (silent)
193
- return false;
194
- if (max - start < 5)
195
- return false;
196
- state.pos = start + 2;
197
- while (state.pos < max) {
198
- if (state.src.slice(state.pos, state.pos + 2) === endTag) {
199
- found = true;
200
- break;
354
+ content: (meta) => {
355
+ const { title = "Codepen", height, width } = meta;
356
+ const params = new URLSearchParams();
357
+ if (meta.editable) {
358
+ params.set("editable", "true");
201
359
  }
202
- state.md.inline.skipToken(state);
203
- }
204
- if (!found || start + 2 === state.pos) {
205
- state.pos = start;
206
- return false;
207
- }
208
- const content = state.src.slice(start + 2, state.pos);
209
- if (/^\s|\s$/.test(content)) {
210
- state.pos = start;
211
- return false;
360
+ if (meta.tab) {
361
+ params.set("default-tab", meta.tab);
362
+ }
363
+ if (meta.theme) {
364
+ params.set("theme-id", meta.theme);
365
+ }
366
+ const middle = meta.preview ? "/embed/preview/" : "/embed/";
367
+ const link = `${CODEPEN_LINK}${meta.user}${middle}${meta.slash}?${params.toString()}`;
368
+ const style = `width:${width};height:${height};margin:16px auto;border-radius:5px;`;
369
+ 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>`;
212
370
  }
213
- state.posMax = state.pos;
214
- state.pos = start + 2;
215
- const [name, options = ""] = content.split(/\s+/);
216
- const [size, color] = options.split("/");
217
- const icon = state.push("vp_iconify_open", "VPIcon", 1);
218
- icon.markup = openTag;
219
- if (name)
220
- icon.attrSet("name", name);
221
- if (size)
222
- icon.attrSet("size", size);
223
- if (color)
224
- icon.attrSet("color", color);
225
- const close = state.push("vp_iconify_close", "VPIcon", -1);
226
- close.markup = endTag;
227
- state.pos = state.posMax + 2;
228
- state.posMax = max;
229
- return true;
230
- };
231
- }
232
- var iconsPlugin = (md) => {
233
- md.inline.ruler.before("emphasis", "iconify", createTokenizer());
371
+ });
234
372
  };
235
373
 
236
- // src/node/features/video/bilibili.ts
237
- import { URLSearchParams as URLSearchParams2 } from "node:url";
238
-
239
- // src/node/utils/timeToSeconds.ts
240
- function timeToSeconds(time) {
241
- if (!time)
242
- return 0;
243
- if (Number.parseFloat(time) === Number(time))
244
- return Number(time);
245
- const [s, m, h] = time.split(":").reverse().map((n) => Number(n) || 0);
246
- return s + m * 60 + h * 3600;
247
- }
248
-
249
- // src/node/features/video/bilibili.ts
250
- var BILIBILI_LINK = "https://player.bilibili.com/player.html";
251
- var bilibiliPlugin = (md) => {
374
+ // src/node/features/codeSandbox.ts
375
+ var codeSandboxPlugin = (md) => {
252
376
  createRuleBlock(md, {
253
- type: "bilibili",
254
- name: "video_bilibili",
255
- // eslint-disable-next-line regexp/no-super-linear-backtracking
256
- syntaxPattern: /^@\[bilibili(?:\s+p(\d+))?([^\]]*)\]\(([^)]*)\)/,
257
- meta([, page, info = "", source = ""]) {
377
+ type: "codesandbox",
378
+ syntaxPattern: /^@\[codesandbox(?:\s+(embed|button))?([^\]]*)\]\(([^)]*)\)/,
379
+ meta([, type2, info = "", source = ""]) {
258
380
  const { attrs } = resolveAttrs(info);
259
- const ids = source.trim().split(/\s+/);
260
- const bvid = ids.find((id) => id.startsWith("BV"));
261
- const [aid, cid] = ids.filter((id) => !id.startsWith("BV"));
381
+ const [profile, filepath2 = ""] = source.split("#");
382
+ const [user, id] = profile.includes("/") ? profile.split("/") : ["", profile];
262
383
  return {
263
- page: +page || 1,
264
- bvid,
265
- aid,
266
- cid,
267
- autoplay: attrs.autoplay ?? false,
268
- time: timeToSeconds(attrs.time),
269
- title: attrs.title,
270
384
  width: attrs.width ? parseRect(attrs.width) : "100%",
271
- height: attrs.height ? parseRect(attrs.height) : "",
272
- ratio: attrs.ratio ? parseRect(attrs.ratio) : ""
273
- };
274
- },
275
- content(meta) {
276
- const params = new URLSearchParams2();
277
- if (meta.bvid) {
278
- params.set("bvid", meta.bvid);
279
- }
280
- if (meta.aid) {
281
- params.set("aid", meta.aid);
282
- }
283
- if (meta.cid) {
284
- params.set("cid", meta.cid);
285
- }
286
- if (meta.page) {
287
- params.set("p", meta.page.toString());
288
- }
289
- if (meta.time) {
290
- params.set("t", meta.time.toString());
291
- }
292
- params.set("autoplay", meta.autoplay ? "1" : "0");
293
- const source = `${BILIBILI_LINK}?${params.toString()}`;
294
- return `<VideoBilibili src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`;
295
- }
296
- });
297
- };
298
-
299
- // src/node/features/video/youtube.ts
300
- import { URLSearchParams as URLSearchParams3 } from "node:url";
301
- var YOUTUBE_LINK = "https://www.youtube.com/embed/";
302
- var youtubePlugin = (md) => {
303
- createRuleBlock(md, {
304
- type: "youtube",
305
- name: "video_youtube",
306
- syntaxPattern: /^@\[youtube([^\]]*)\]\(([^)]*)\)/,
307
- meta([, info = "", id = ""]) {
308
- const { attrs } = resolveAttrs(info);
309
- return {
310
- id,
311
- autoplay: attrs.autoplay ?? false,
312
- loop: attrs.loop ?? false,
313
- start: timeToSeconds(attrs.start),
314
- end: timeToSeconds(attrs.end),
315
- title: attrs.title,
316
- width: attrs.width ? parseRect(attrs.width) : "100%",
317
- height: attrs.height ? parseRect(attrs.height) : "",
318
- ratio: attrs.ratio ? parseRect(attrs.ratio) : ""
319
- };
320
- },
321
- content(meta) {
322
- const params = new URLSearchParams3();
323
- if (meta.autoplay) {
324
- params.set("autoplay", "1");
325
- }
326
- if (meta.loop) {
327
- params.set("loop", "1");
328
- }
329
- if (meta.start) {
330
- params.set("start", meta.start.toString());
331
- }
332
- if (meta.end) {
333
- params.set("end", meta.end.toString());
334
- }
335
- const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`;
336
- return `<VideoYoutube src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`;
337
- }
338
- });
339
- };
340
-
341
- // src/node/features/codepen.ts
342
- var CODEPEN_LINK = "https://codepen.io/";
343
- var codepenPlugin = (md) => {
344
- createRuleBlock(md, {
345
- type: "codepen",
346
- syntaxPattern: /^@\[codepen([^\]]*)\]\(([^)]*)\)/,
347
- meta: ([, info = "", source = ""]) => {
348
- const { attrs } = resolveAttrs(info);
349
- const [user, slash] = source.split("/");
350
- return {
351
- width: attrs.width ? parseRect(attrs.width) : "100%",
352
- height: attrs.height ? parseRect(attrs.height) : "400px",
353
- user,
354
- slash,
355
- title: attrs.title,
356
- preview: attrs.preview,
357
- editable: attrs.editable,
358
- tab: attrs.tab ?? "result",
359
- theme: attrs.theme
360
- };
361
- },
362
- content: (meta) => {
363
- const { title = "Codepen", height, width } = meta;
364
- const params = new URLSearchParams();
365
- if (meta.editable) {
366
- params.set("editable", "true");
367
- }
368
- if (meta.tab) {
369
- params.set("default-tab", meta.tab);
370
- }
371
- if (meta.theme) {
372
- params.set("theme-id", meta.theme);
373
- }
374
- const middle = meta.preview ? "/embed/preview/" : "/embed/";
375
- const link = `${CODEPEN_LINK}${meta.user}${middle}${meta.slash}?${params.toString()}`;
376
- const style = `width:${width};height:${height};margin:16px auto;border-radius:5px;`;
377
- 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>`;
378
- }
379
- });
380
- };
381
-
382
- // src/node/features/replit.ts
383
- var replitPlugin = (md) => {
384
- createRuleBlock(md, {
385
- type: "replit",
386
- syntaxPattern: /^@\[replit([^\]]*)\]\(([^)]*)\)/,
387
- meta: ([, info = "", source = ""]) => {
388
- const { attrs } = resolveAttrs(info);
389
- return {
390
- width: attrs.width ? parseRect(attrs.width) : "100%",
391
- height: attrs.height ? parseRect(attrs.height) : "450px",
392
- source: source.startsWith("@") ? source : `@${source}`,
393
- title: attrs.title,
394
- theme: attrs.theme || ""
395
- };
396
- },
397
- content({ title, height, width, source, theme }) {
398
- return `<ReplitViewer title="${title || ""}" height="${height}" width="${width}" source="${source}" theme="${theme}" />`;
399
- }
400
- });
401
- };
402
-
403
- // src/node/features/codeSandbox.ts
404
- var codeSandboxPlugin = (md) => {
405
- createRuleBlock(md, {
406
- type: "codesandbox",
407
- syntaxPattern: /^@\[codesandbox(?:\s+(embed|button))?([^\]]*)\]\(([^)]*)\)/,
408
- meta([, type2, info = "", source = ""]) {
409
- const { attrs } = resolveAttrs(info);
410
- const [profile, filepath2 = ""] = source.split("#");
411
- const [user, id] = profile.includes("/") ? profile.split("/") : ["", profile];
412
- return {
413
- width: attrs.width ? parseRect(attrs.width) : "100%",
414
- height: attrs.height ? parseRect(attrs.height) : "500px",
415
- user,
416
- id,
417
- title: attrs.title ?? "",
418
- console: attrs.console ?? false,
419
- navbar: attrs.navbar ?? true,
420
- layout: attrs.layout ?? "",
421
- type: type2 || "embed",
422
- filepath: filepath2
385
+ height: attrs.height ? parseRect(attrs.height) : "500px",
386
+ user,
387
+ id,
388
+ title: attrs.title ?? "",
389
+ console: attrs.console ?? false,
390
+ navbar: attrs.navbar ?? true,
391
+ layout: attrs.layout ?? "",
392
+ type: type2 || "embed",
393
+ filepath: filepath2
423
394
  };
424
395
  },
425
396
  content({ title, height, width, user, id, type: type2, filepath: filepath2, console, navbar, layout }) {
@@ -428,302 +399,9 @@ var codeSandboxPlugin = (md) => {
428
399
  });
429
400
  };
430
401
 
431
- // src/node/features/jsfiddle.ts
432
- var jsfiddlePlugin = (md) => {
433
- createRuleBlock(md, {
434
- type: "jsfiddle",
435
- syntaxPattern: /^@\[jsfiddle([^\]]*)\]\(([^)]*)\)/,
436
- meta([, info = "", source]) {
437
- const { attrs } = resolveAttrs(info);
438
- const [user, id] = source.split("/");
439
- return {
440
- width: attrs.width ? parseRect(attrs.width) : "100%",
441
- height: attrs.height ? parseRect(attrs.height) : "400px",
442
- user,
443
- id,
444
- title: attrs.title || "JS Fiddle",
445
- tab: attrs.tab?.replace(/\s+/g, "") || "js,css,html,result",
446
- theme: attrs.theme || "dark"
447
- };
448
- },
449
- content: ({ title = "JS Fiddle", height, width, user, id, tab, theme }) => {
450
- theme = theme === "dark" ? "/dark/" : "";
451
- const link = `https://jsfiddle.net/${user}/${id}/embedded/${tab}${theme}`;
452
- const style = `width:${width};height:${height};margin:16px auto;border:none;border-radius:5px;`;
453
- return `<iframe class="js-fiddle-iframe-wrapper" style="${style}" title="${title}" src="${link}" allowfullscreen="true" allowpaymentrequest="true"></iframe>`;
454
- }
455
- });
456
- };
457
-
458
- // src/node/features/plot.ts
459
- var [openTag2, endTag2] = ["!!", "!!"];
460
- function createTokenizer2() {
461
- return (state, silent) => {
462
- let found = false;
463
- const max = state.posMax;
464
- const start = state.pos;
465
- if (state.src.slice(start, start + 2) !== openTag2)
466
- return false;
467
- if (silent)
468
- return false;
469
- if (max - start < 5)
470
- return false;
471
- state.pos = start + 2;
472
- while (state.pos < max) {
473
- if (state.src.slice(state.pos - 1, state.pos + 1) === endTag2) {
474
- found = true;
475
- break;
476
- }
477
- state.md.inline.skipToken(state);
478
- }
479
- if (!found || start + 2 === state.pos) {
480
- state.pos = start;
481
- return false;
482
- }
483
- const content = state.src.slice(start + 2, state.pos - 1);
484
- if (/^\s|\s$/.test(content)) {
485
- state.pos = start;
486
- return false;
487
- }
488
- state.posMax = state.pos - 1;
489
- state.pos = start + 2;
490
- const open = state.push("plot_open", "Plot", 1);
491
- open.markup = openTag2;
492
- const text = state.push("text", "", 0);
493
- text.content = content;
494
- const close = state.push("plot_close", "Plot", -1);
495
- close.markup = endTag2;
496
- state.pos = state.posMax + 2;
497
- state.posMax = max;
498
- return true;
499
- };
500
- }
501
- var plotPlugin = (md) => {
502
- md.inline.ruler.before("emphasis", "plot", createTokenizer2());
503
- };
504
-
505
- // src/node/features/langRepl.ts
402
+ // src/node/features/fileTree/index.ts
403
+ import fs2 from "node:fs";
506
404
  import container2 from "markdown-it-container";
507
- import { fs, getDirname, path as path2 } from "vuepress/utils";
508
- var RE_INFO = /^(#editable)?(.*)$/;
509
- function createReplContainer(md, lang) {
510
- const type2 = `${lang}-repl`;
511
- const validate = (info) => info.trim().startsWith(type2);
512
- const render = (tokens, index) => {
513
- const token = tokens[index];
514
- const info = token.info.trim().slice(type2.length).trim() || "";
515
- const [, editable, title] = info.match(RE_INFO) ?? [];
516
- if (token.nesting === 1)
517
- return `<CodeRepl ${editable ? "editable" : ""} title="${title || `${lang} playground`}">`;
518
- else
519
- return "</CodeRepl>";
520
- };
521
- md.use(container2, type2, { validate, render });
522
- }
523
- async function langReplPlugin(app, md, {
524
- theme,
525
- go = false,
526
- kotlin = false,
527
- rust = false
528
- }) {
529
- if (kotlin) {
530
- createReplContainer(md, "kotlin");
531
- }
532
- if (go) {
533
- createReplContainer(md, "go");
534
- }
535
- if (rust) {
536
- createReplContainer(md, "rust");
537
- }
538
- theme ??= { light: "github-light", dark: "github-dark" };
539
- const data = { grammars: {} };
540
- const themesPath = getDirname(import.meta.resolve("tm-themes"));
541
- const grammarsPath = getDirname(import.meta.resolve("tm-grammars"));
542
- const readTheme = (theme2) => read(path2.join(themesPath, "themes", `${theme2}.json`));
543
- const readGrammar = (grammar) => read(path2.join(grammarsPath, "grammars", `${grammar}.json`));
544
- if (typeof theme === "string") {
545
- data.theme = await readTheme(theme);
546
- } else {
547
- data.theme = await Promise.all([
548
- readTheme(theme.light),
549
- readTheme(theme.dark)
550
- ]).then(([light, dark]) => ({ light, dark }));
551
- }
552
- if (kotlin)
553
- data.grammars.kotlin = await readGrammar("kotlin");
554
- if (go)
555
- data.grammars.go = await readGrammar("go");
556
- if (rust)
557
- data.grammars.rust = await readGrammar("rust");
558
- await app.writeTemp(
559
- "internal/md-power/replEditorData.js",
560
- `export default ${JSON.stringify(data, null, 2)}`
561
- );
562
- }
563
- async function read(file) {
564
- try {
565
- const content = await fs.readFile(file, "utf-8");
566
- return JSON.parse(content);
567
- } catch {
568
- }
569
- return void 0;
570
- }
571
-
572
- // src/node/prepareConfigFile.ts
573
- import { getDirname as getDirname2, path as path3 } from "vuepress/utils";
574
- import { ensureEndingSlash } from "@vuepress/helper";
575
- var { url: filepath } = import.meta;
576
- var __dirname = getDirname2(filepath);
577
- var CLIENT_FOLDER = ensureEndingSlash(
578
- path3.resolve(__dirname, "../client")
579
- );
580
- async function prepareConfigFile(app, options) {
581
- const imports = /* @__PURE__ */ new Set();
582
- const enhances = /* @__PURE__ */ new Set();
583
- if (options.pdf) {
584
- imports.add(`import PDFViewer from '${CLIENT_FOLDER}components/PDFViewer.vue'`);
585
- enhances.add(`app.component('PDFViewer', PDFViewer)`);
586
- }
587
- if (options.bilibili) {
588
- imports.add(`import Bilibili from '${CLIENT_FOLDER}components/Bilibili.vue'`);
589
- enhances.add(`app.component('VideoBilibili', Bilibili)`);
590
- }
591
- if (options.youtube) {
592
- imports.add(`import Youtube from '${CLIENT_FOLDER}components/Youtube.vue'`);
593
- enhances.add(`app.component('VideoYoutube', Youtube)`);
594
- }
595
- if (options.replit) {
596
- imports.add(`import Replit from '${CLIENT_FOLDER}components/Replit.vue'`);
597
- enhances.add(`app.component('ReplitViewer', Replit)`);
598
- }
599
- if (options.codeSandbox) {
600
- imports.add(`import CodeSandbox from '${CLIENT_FOLDER}components/CodeSandbox.vue'`);
601
- enhances.add(`app.component('CodeSandboxViewer', CodeSandbox)`);
602
- }
603
- if (options.plot) {
604
- imports.add(`import Plot from '${CLIENT_FOLDER}components/Plot.vue'`);
605
- enhances.add(`app.component('Plot', Plot)`);
606
- }
607
- if (options.repl) {
608
- imports.add(`import CodeRepl from '${CLIENT_FOLDER}components/CodeRepl.vue'`);
609
- enhances.add(`app.component('CodeRepl', CodeRepl)`);
610
- }
611
- if (options.caniuse) {
612
- imports.add(`import CanIUse from '${CLIENT_FOLDER}components/CanIUse.vue'`);
613
- enhances.add(`app.component('CanIUseViewer', CanIUse)`);
614
- }
615
- if (options.fileTree) {
616
- imports.add(`import FileTreeItem from '${CLIENT_FOLDER}components/FileTreeItem.vue'`);
617
- imports.add(`import '@internal/md-power/file-tree.css'`);
618
- enhances.add(`app.component('FileTreeItem', FileTreeItem)`);
619
- }
620
- return app.writeTemp(
621
- "md-power/config.js",
622
- `import { defineClientConfig } from 'vuepress/client'
623
- ${Array.from(imports.values()).join("\n")}
624
-
625
- export default defineClientConfig({
626
- enhance({ router, app }) {
627
- ${Array.from(enhances.values()).map((item) => ` ${item}`).join("\n")}
628
- }
629
- })
630
- `
631
- );
632
- }
633
-
634
- // src/node/features/fileTree/index.ts
635
- import fs2 from "node:fs";
636
- import container3 from "markdown-it-container";
637
-
638
- // src/node/features/fileTree/resolveTreeNodeInfo.ts
639
- import { removeLeadingSlash } from "vuepress/shared";
640
- import Token from "markdown-it/lib/token.mjs";
641
- function resolveTreeNodeInfo(tokens, current, idx) {
642
- let hasInline = false;
643
- let hasChildren = false;
644
- let inline;
645
- for (let i = idx + 1; !(tokens[i].level === current.level && tokens[i].type === "list_item_close"); ++i) {
646
- if (tokens[i].type === "inline" && !hasInline) {
647
- inline = tokens[i];
648
- hasInline = true;
649
- } else if (tokens[i].tag === "ul") {
650
- hasChildren = true;
651
- }
652
- if (hasInline && hasChildren)
653
- break;
654
- }
655
- if (!hasInline)
656
- return void 0;
657
- const children = inline.children?.filter((token) => token.type === "text" && token.content || token.tag === "strong") || [];
658
- const filename = children.filter((token) => token.type === "text").map((token) => token.content).join(" ").split(/\s+/)[0] ?? "";
659
- const focus = children[0]?.tag === "strong";
660
- const type2 = hasChildren || filename.endsWith("/") ? "folder" : "file";
661
- const info = {
662
- filename: removeLeadingSlash(filename),
663
- type: type2,
664
- focus,
665
- empty: !hasChildren,
666
- expanded: type2 === "folder" && !filename.endsWith("/")
667
- };
668
- return [info, inline];
669
- }
670
- function updateInlineToken(inline, info, icon) {
671
- const children = inline.children;
672
- if (!children)
673
- return;
674
- const tokens = [];
675
- const wrapperOpen = new Token("span_open", "span", 1);
676
- const wrapperClose = new Token("span_close", "span", -1);
677
- wrapperOpen.attrSet("class", `tree-node ${info.type}`);
678
- tokens.push(wrapperOpen);
679
- if (info.filename !== "..." && info.filename !== "\u2026") {
680
- const iconOpen = new Token("span_open", "span", 1);
681
- iconOpen.attrSet("class", icon);
682
- const iconClose = new Token("span_close", "span", -1);
683
- tokens.push(iconOpen, iconClose);
684
- }
685
- const fileOpen = new Token("span_open", "span", 1);
686
- fileOpen.attrSet("class", `name${info.focus ? " focus" : ""}`);
687
- tokens.push(fileOpen);
688
- let isStrongTag = false;
689
- while (children.length) {
690
- const token = children.shift();
691
- if (token.type === "text" && token.content) {
692
- if (token.content.includes(" ")) {
693
- const [first, ...other] = token.content.split(" ");
694
- const text = new Token("text", "", 0);
695
- text.content = first;
696
- tokens.push(text);
697
- const comment = new Token("text", "", 0);
698
- comment.content = other.join(" ");
699
- children.unshift(comment);
700
- } else {
701
- tokens.push(token);
702
- }
703
- if (!isStrongTag)
704
- break;
705
- } else if (token.tag === "strong") {
706
- tokens.push(token);
707
- if (token.nesting === 1) {
708
- isStrongTag = true;
709
- } else {
710
- break;
711
- }
712
- } else {
713
- tokens.push(token);
714
- }
715
- }
716
- const fileClose = new Token("span_close", "span", -1);
717
- tokens.push(fileClose);
718
- if (children.filter((token) => token.type === "text" && token.content.trim()).length) {
719
- const commentOpen = new Token("span_open", "span", 1);
720
- commentOpen.attrSet("class", "comment");
721
- const commentClose = new Token("span_close", "span", -1);
722
- tokens.push(commentOpen, ...children, commentClose);
723
- }
724
- tokens.push(wrapperClose);
725
- inline.children = tokens;
726
- }
727
405
 
728
406
  // src/node/features/fileTree/icons.ts
729
407
  var definitions = {
@@ -1323,118 +1001,618 @@ var FileIcons = {
1323
1001
  "seti:ignored": '<path d="M4.661 20.860L21.083 4.480Q21.335 3.934 21.335 3.682L21.335 3.682Q21.167 2.968 20.600 2.779Q20.033 2.590 19.361 3.010L19.361 3.010L16.715 5.656Q16.589 5.782 16.211 5.782L16.211 5.782Q13.943 4.900 11.738 4.921Q9.533 4.942 7.307 5.908L7.307 5.908Q3.191 7.714 0.335 12.082L0.335 12.082Q0.125 12.502 0.146 12.817Q0.167 13.132 0.461 13.384L0.461 13.384Q1.469 14.602 2.687 15.610L2.687 15.610Q3.485 16.282 5.039 17.332L5.039 17.332Q4.997 17.332 4.934 17.395Q4.871 17.458 4.787 17.458L4.787 17.458L4.115 18.088Q3.317 18.886 2.939 19.306L2.939 19.306Q2.519 19.768 2.687 20.482L2.687 20.482Q2.897 21.112 3.611 21.280L3.611 21.280Q4.283 21.280 4.661 20.860L4.661 20.860ZM7.811 14.560L6.887 15.484L6.635 15.484Q4.241 14.182 2.687 12.502L2.687 12.502Q4.955 9.520 7.559 8.134L7.559 8.134Q5.627 11.410 7.811 14.560L7.811 14.560ZM12.809 8.302L12.809 8.302Q12.599 8.932 11.885 8.932L11.885 8.932Q11.003 8.932 10.373 9.478Q9.743 10.024 9.659 10.906L9.659 10.906L9.659 11.284Q9.659 11.662 9.428 11.872Q9.197 12.082 8.840 12.082Q8.483 12.082 8.273 11.830Q8.063 11.578 8.063 11.158L8.063 11.158Q8.063 9.562 9.197 8.407Q10.331 7.252 11.885 7.252L11.885 7.252Q12.347 7.252 12.620 7.567Q12.893 7.882 12.809 8.302ZM23.561 11.956L23.561 11.956Q23.225 11.578 22.574 10.717Q21.923 9.856 21.587 9.478L21.587 9.478Q21.335 9.142 20.684 8.554Q20.033 7.966 19.739 7.630L19.739 7.630L18.185 9.184Q20.033 10.654 21.335 12.502L21.335 12.502Q21.083 12.754 20.957 12.754L20.957 12.754Q20.159 13.510 19.739 13.804L19.739 13.804Q15.623 17.122 10.961 16.534L10.961 16.534Q10.583 16.534 10.415 16.702L10.415 16.702Q9.911 17.206 9.659 17.584L9.659 17.584L8.861 18.382L8.987 18.382Q12.137 19.054 14.615 18.508L14.615 18.508Q19.739 17.626 23.561 13.132L23.561 13.132Q24.149 12.754 23.561 11.956Z"/>'
1324
1002
  };
1325
1003
 
1326
- // src/node/features/fileTree/findIcon.ts
1327
- var defaultFileIcon = {
1328
- name: "default",
1329
- svg: makeSVGIcon(FileIcons["seti:default"])
1004
+ // src/node/features/fileTree/findIcon.ts
1005
+ var defaultFileIcon = {
1006
+ name: "default",
1007
+ svg: makeSVGIcon(FileIcons["seti:default"])
1008
+ };
1009
+ var folderIcon = {
1010
+ name: "folder",
1011
+ svg: makeSVGIcon(FileIcons["seti:folder"])
1012
+ };
1013
+ function getFileIcon(fileName) {
1014
+ const name = getFileIconName(fileName);
1015
+ if (!name)
1016
+ return defaultFileIcon;
1017
+ if (name in FileIcons) {
1018
+ const path5 = FileIcons[name];
1019
+ return {
1020
+ name: name.includes(":") ? name.split(":")[1] : name,
1021
+ svg: makeSVGIcon(path5)
1022
+ };
1023
+ }
1024
+ return defaultFileIcon;
1025
+ }
1026
+ function makeSVGIcon(svg) {
1027
+ svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">${svg}</svg>`.replace(/"/g, "'").replace(/%/g, "%25").replace(/#/g, "%23").replace(/\{/g, "%7B").replace(/\}/g, "%7D").replace(/</g, "%3C").replace(/>/g, "%3E");
1028
+ return `url("data:image/svg+xml,${svg}")`;
1029
+ }
1030
+ function getFileIconName(fileName) {
1031
+ let icon = definitions.files[fileName];
1032
+ if (icon)
1033
+ return icon;
1034
+ icon = getFileIconTypeFromExtension(fileName);
1035
+ if (icon)
1036
+ return icon;
1037
+ for (const [partial, partialIcon] of Object.entries(definitions.partials)) {
1038
+ if (fileName.includes(partial))
1039
+ return partialIcon;
1040
+ }
1041
+ return icon;
1042
+ }
1043
+ function getFileIconTypeFromExtension(fileName) {
1044
+ const firstDotIndex = fileName.indexOf(".");
1045
+ if (firstDotIndex === -1)
1046
+ return;
1047
+ let extension = fileName.slice(firstDotIndex);
1048
+ while (extension !== "") {
1049
+ const icon = definitions.extensions[extension];
1050
+ if (icon)
1051
+ return icon;
1052
+ const nextDotIndex = extension.indexOf(".", 1);
1053
+ if (nextDotIndex === -1)
1054
+ return;
1055
+ extension = extension.slice(nextDotIndex);
1056
+ }
1057
+ return void 0;
1058
+ }
1059
+
1060
+ // src/node/features/fileTree/resolveTreeNodeInfo.ts
1061
+ import Token from "markdown-it/lib/token.mjs";
1062
+ import { removeLeadingSlash } from "vuepress/shared";
1063
+ function resolveTreeNodeInfo(tokens, current, idx) {
1064
+ let hasInline = false;
1065
+ let hasChildren = false;
1066
+ let inline;
1067
+ for (let i = idx + 1; !(tokens[i].level === current.level && tokens[i].type === "list_item_close"); ++i) {
1068
+ if (tokens[i].type === "inline" && !hasInline) {
1069
+ inline = tokens[i];
1070
+ hasInline = true;
1071
+ } else if (tokens[i].tag === "ul") {
1072
+ hasChildren = true;
1073
+ }
1074
+ if (hasInline && hasChildren)
1075
+ break;
1076
+ }
1077
+ if (!hasInline)
1078
+ return void 0;
1079
+ const children = inline.children?.filter((token) => token.type === "text" && token.content || token.tag === "strong") || [];
1080
+ const filename = children.filter((token) => token.type === "text").map((token) => token.content).join(" ").split(/\s+/)[0] ?? "";
1081
+ const focus = children[0]?.tag === "strong";
1082
+ const type2 = hasChildren || filename.endsWith("/") ? "folder" : "file";
1083
+ const info = {
1084
+ filename: removeLeadingSlash(filename),
1085
+ type: type2,
1086
+ focus,
1087
+ empty: !hasChildren,
1088
+ expanded: type2 === "folder" && !filename.endsWith("/")
1089
+ };
1090
+ return [info, inline];
1091
+ }
1092
+ function updateInlineToken(inline, info, icon) {
1093
+ const children = inline.children;
1094
+ if (!children)
1095
+ return;
1096
+ const tokens = [];
1097
+ const wrapperOpen = new Token("span_open", "span", 1);
1098
+ const wrapperClose = new Token("span_close", "span", -1);
1099
+ wrapperOpen.attrSet("class", `tree-node ${info.type}`);
1100
+ tokens.push(wrapperOpen);
1101
+ if (info.filename !== "..." && info.filename !== "\u2026") {
1102
+ const iconOpen = new Token("span_open", "span", 1);
1103
+ iconOpen.attrSet("class", icon);
1104
+ const iconClose = new Token("span_close", "span", -1);
1105
+ tokens.push(iconOpen, iconClose);
1106
+ }
1107
+ const fileOpen = new Token("span_open", "span", 1);
1108
+ fileOpen.attrSet("class", `name${info.focus ? " focus" : ""}`);
1109
+ tokens.push(fileOpen);
1110
+ let isStrongTag = false;
1111
+ while (children.length) {
1112
+ const token = children.shift();
1113
+ if (token.type === "text" && token.content) {
1114
+ if (token.content.includes(" ")) {
1115
+ const [first, ...other] = token.content.split(" ");
1116
+ const text = new Token("text", "", 0);
1117
+ text.content = first;
1118
+ tokens.push(text);
1119
+ const comment = new Token("text", "", 0);
1120
+ comment.content = other.join(" ");
1121
+ children.unshift(comment);
1122
+ } else {
1123
+ tokens.push(token);
1124
+ }
1125
+ if (!isStrongTag)
1126
+ break;
1127
+ } else if (token.tag === "strong") {
1128
+ tokens.push(token);
1129
+ if (token.nesting === 1) {
1130
+ isStrongTag = true;
1131
+ } else {
1132
+ break;
1133
+ }
1134
+ } else {
1135
+ tokens.push(token);
1136
+ }
1137
+ }
1138
+ const fileClose = new Token("span_close", "span", -1);
1139
+ tokens.push(fileClose);
1140
+ if (children.filter((token) => token.type === "text" && token.content.trim()).length) {
1141
+ const commentOpen = new Token("span_open", "span", 1);
1142
+ commentOpen.attrSet("class", "comment");
1143
+ const commentClose = new Token("span_close", "span", -1);
1144
+ tokens.push(commentOpen, ...children, commentClose);
1145
+ }
1146
+ tokens.push(wrapperClose);
1147
+ inline.children = tokens;
1148
+ }
1149
+
1150
+ // src/node/features/fileTree/index.ts
1151
+ var type = "file-tree";
1152
+ var closeType = `container_${type}_close`;
1153
+ var componentName = "FileTreeItem";
1154
+ var itemOpen = "file_tree_item_open";
1155
+ var itemClose = "file_tree_item_close";
1156
+ var classPrefix = "vp-fti-";
1157
+ var styleFilepath = "internal/md-power/file-tree.css";
1158
+ async function fileTreePlugin(app, md) {
1159
+ const validate = (info) => info.trim().startsWith(type);
1160
+ const render = (tokens, idx) => {
1161
+ if (tokens[idx].nesting === 1) {
1162
+ for (let i = idx + 1; !(tokens[i].nesting === -1 && tokens[i].type === closeType); ++i) {
1163
+ const token = tokens[i];
1164
+ if (token.type === "list_item_open") {
1165
+ const result = resolveTreeNodeInfo(tokens, token, i);
1166
+ if (result) {
1167
+ const [info2, inline] = result;
1168
+ const { filename, type: type2, expanded, empty } = info2;
1169
+ const icon = type2 === "file" ? getFileIcon(filename) : folderIcon;
1170
+ token.type = itemOpen;
1171
+ token.tag = componentName;
1172
+ token.attrSet("type", type2);
1173
+ token.attrSet(":expanded", expanded ? "true" : "false");
1174
+ token.attrSet(":empty", empty ? "true" : "false");
1175
+ updateInlineToken(inline, info2, `${classPrefix}${icon.name}`);
1176
+ addIcon(icon);
1177
+ }
1178
+ } else if (token.type === "list_item_close") {
1179
+ token.type = itemClose;
1180
+ token.tag = componentName;
1181
+ }
1182
+ }
1183
+ const info = tokens[idx].info.trim();
1184
+ const title = info.slice(type.length).trim();
1185
+ return `<div class="vp-file-tree">${title ? `<p class="vp-file-tree-title">${title}</p>` : ""}`;
1186
+ } else {
1187
+ return "</div>";
1188
+ }
1189
+ };
1190
+ let timer = null;
1191
+ const icons = {};
1192
+ function addIcon(icon) {
1193
+ icons[icon.name] = icon;
1194
+ if (timer)
1195
+ clearTimeout(timer);
1196
+ timer = setTimeout(async () => {
1197
+ let content = "";
1198
+ for (const icon2 of Object.values(icons)) {
1199
+ content += `.${classPrefix}${icon2.name} { --icon: ${icon2.svg}; }
1200
+ `;
1201
+ }
1202
+ await app.writeTemp(styleFilepath, content);
1203
+ }, 150);
1204
+ }
1205
+ md.use(container2, type, { validate, render });
1206
+ if (!fs2.existsSync(app.dir.temp(styleFilepath)))
1207
+ await app.writeTemp(styleFilepath, "");
1208
+ }
1209
+
1210
+ // src/node/features/icons.ts
1211
+ var [openTag, endTag] = [":[", "]:"];
1212
+ function createTokenizer() {
1213
+ return (state, silent) => {
1214
+ let found = false;
1215
+ const max = state.posMax;
1216
+ const start = state.pos;
1217
+ if (state.src.slice(start, start + 2) !== openTag)
1218
+ return false;
1219
+ if (silent)
1220
+ return false;
1221
+ if (max - start < 5)
1222
+ return false;
1223
+ state.pos = start + 2;
1224
+ while (state.pos < max) {
1225
+ if (state.src.slice(state.pos, state.pos + 2) === endTag) {
1226
+ found = true;
1227
+ break;
1228
+ }
1229
+ state.md.inline.skipToken(state);
1230
+ }
1231
+ if (!found || start + 2 === state.pos) {
1232
+ state.pos = start;
1233
+ return false;
1234
+ }
1235
+ const content = state.src.slice(start + 2, state.pos);
1236
+ if (/^\s|\s$/.test(content)) {
1237
+ state.pos = start;
1238
+ return false;
1239
+ }
1240
+ state.posMax = state.pos;
1241
+ state.pos = start + 2;
1242
+ const [name, options = ""] = content.split(/\s+/);
1243
+ const [size, color] = options.split("/");
1244
+ const icon = state.push("vp_iconify_open", "VPIcon", 1);
1245
+ icon.markup = openTag;
1246
+ if (name)
1247
+ icon.attrSet("name", name);
1248
+ if (size)
1249
+ icon.attrSet("size", size);
1250
+ if (color)
1251
+ icon.attrSet("color", color);
1252
+ const close = state.push("vp_iconify_close", "VPIcon", -1);
1253
+ close.markup = endTag;
1254
+ state.pos = state.posMax + 2;
1255
+ state.posMax = max;
1256
+ return true;
1257
+ };
1258
+ }
1259
+ var iconsPlugin = (md) => {
1260
+ md.inline.ruler.before("emphasis", "iconify", createTokenizer());
1261
+ };
1262
+
1263
+ // src/node/features/jsfiddle.ts
1264
+ var jsfiddlePlugin = (md) => {
1265
+ createRuleBlock(md, {
1266
+ type: "jsfiddle",
1267
+ syntaxPattern: /^@\[jsfiddle([^\]]*)\]\(([^)]*)\)/,
1268
+ meta([, info = "", source]) {
1269
+ const { attrs } = resolveAttrs(info);
1270
+ const [user, id] = source.split("/");
1271
+ return {
1272
+ width: attrs.width ? parseRect(attrs.width) : "100%",
1273
+ height: attrs.height ? parseRect(attrs.height) : "400px",
1274
+ user,
1275
+ id,
1276
+ title: attrs.title || "JS Fiddle",
1277
+ tab: attrs.tab?.replace(/\s+/g, "") || "js,css,html,result",
1278
+ theme: attrs.theme || "dark"
1279
+ };
1280
+ },
1281
+ content: ({ title = "JS Fiddle", height, width, user, id, tab, theme }) => {
1282
+ theme = theme === "dark" ? "/dark/" : "";
1283
+ const link = `https://jsfiddle.net/${user}/${id}/embedded/${tab}${theme}`;
1284
+ const style = `width:${width};height:${height};margin:16px auto;border:none;border-radius:5px;`;
1285
+ return `<iframe class="js-fiddle-iframe-wrapper" style="${style}" title="${title}" src="${link}" allowfullscreen="true" allowpaymentrequest="true"></iframe>`;
1286
+ }
1287
+ });
1288
+ };
1289
+
1290
+ // src/node/features/langRepl.ts
1291
+ import container3 from "markdown-it-container";
1292
+ import { fs as fs3, getDirname, path as path2 } from "vuepress/utils";
1293
+ var RE_INFO = /^(#editable)?(.*)$/;
1294
+ function createReplContainer(md, lang) {
1295
+ const type2 = `${lang}-repl`;
1296
+ const validate = (info) => info.trim().startsWith(type2);
1297
+ const render = (tokens, index) => {
1298
+ const token = tokens[index];
1299
+ const info = token.info.trim().slice(type2.length).trim() || "";
1300
+ const [, editable, title] = info.match(RE_INFO) ?? [];
1301
+ if (token.nesting === 1)
1302
+ return `<CodeRepl ${editable ? "editable" : ""} title="${title || `${lang} playground`}">`;
1303
+ else
1304
+ return "</CodeRepl>";
1305
+ };
1306
+ md.use(container3, type2, { validate, render });
1307
+ }
1308
+ async function langReplPlugin(app, md, {
1309
+ theme,
1310
+ go = false,
1311
+ kotlin = false,
1312
+ rust = false
1313
+ }) {
1314
+ if (kotlin) {
1315
+ createReplContainer(md, "kotlin");
1316
+ }
1317
+ if (go) {
1318
+ createReplContainer(md, "go");
1319
+ }
1320
+ if (rust) {
1321
+ createReplContainer(md, "rust");
1322
+ }
1323
+ theme ??= { light: "github-light", dark: "github-dark" };
1324
+ const data = { grammars: {} };
1325
+ const themesPath = getDirname(import.meta.resolve("tm-themes"));
1326
+ const grammarsPath = getDirname(import.meta.resolve("tm-grammars"));
1327
+ const readTheme = (theme2) => read(path2.join(themesPath, "themes", `${theme2}.json`));
1328
+ const readGrammar = (grammar) => read(path2.join(grammarsPath, "grammars", `${grammar}.json`));
1329
+ if (typeof theme === "string") {
1330
+ data.theme = await readTheme(theme);
1331
+ } else {
1332
+ data.theme = await Promise.all([
1333
+ readTheme(theme.light),
1334
+ readTheme(theme.dark)
1335
+ ]).then(([light, dark]) => ({ light, dark }));
1336
+ }
1337
+ if (kotlin)
1338
+ data.grammars.kotlin = await readGrammar("kotlin");
1339
+ if (go)
1340
+ data.grammars.go = await readGrammar("go");
1341
+ if (rust)
1342
+ data.grammars.rust = await readGrammar("rust");
1343
+ await app.writeTemp(
1344
+ "internal/md-power/replEditorData.js",
1345
+ `export default ${JSON.stringify(data, null, 2)}`
1346
+ );
1347
+ }
1348
+ async function read(file) {
1349
+ try {
1350
+ const content = await fs3.readFile(file, "utf-8");
1351
+ return JSON.parse(content);
1352
+ } catch {
1353
+ }
1354
+ return void 0;
1355
+ }
1356
+
1357
+ // src/node/features/pdf.ts
1358
+ import { path as path3 } from "vuepress/utils";
1359
+ var pdfPlugin = (md) => {
1360
+ createRuleBlock(md, {
1361
+ type: "pdf",
1362
+ // eslint-disable-next-line regexp/no-super-linear-backtracking
1363
+ syntaxPattern: /^@\[pdf(?:\s+(\d+))?([^\]]*)\]\(([^)]*)\)/,
1364
+ meta([, page, info = "", src = ""]) {
1365
+ const { attrs } = resolveAttrs(info);
1366
+ return {
1367
+ src,
1368
+ page: +page || 1,
1369
+ noToolbar: Boolean(attrs.noToolbar ?? false),
1370
+ zoom: +attrs.zoom || 50,
1371
+ width: attrs.width ? parseRect(attrs.width) : "100%",
1372
+ height: attrs.height ? parseRect(attrs.height) : "",
1373
+ ratio: attrs.ratio ? parseRect(attrs.ratio) : "",
1374
+ title: path3.basename(src || "")
1375
+ };
1376
+ },
1377
+ content({ title, src, page, noToolbar, width, height, ratio, zoom }) {
1378
+ return `<PDFViewer src="${src}" title="${title}" :page="${page}" :no-toolbar="${noToolbar}" width="${width}" height="${height}" ratio="${ratio}" :zoom="${zoom}" />`;
1379
+ }
1380
+ });
1381
+ };
1382
+
1383
+ // src/node/features/plot.ts
1384
+ var [openTag2, endTag2] = ["!!", "!!"];
1385
+ function createTokenizer2() {
1386
+ return (state, silent) => {
1387
+ let found = false;
1388
+ const max = state.posMax;
1389
+ const start = state.pos;
1390
+ if (state.src.slice(start, start + 2) !== openTag2)
1391
+ return false;
1392
+ if (silent)
1393
+ return false;
1394
+ if (max - start < 5)
1395
+ return false;
1396
+ state.pos = start + 2;
1397
+ while (state.pos < max) {
1398
+ if (state.src.slice(state.pos - 1, state.pos + 1) === endTag2) {
1399
+ found = true;
1400
+ break;
1401
+ }
1402
+ state.md.inline.skipToken(state);
1403
+ }
1404
+ if (!found || start + 2 === state.pos) {
1405
+ state.pos = start;
1406
+ return false;
1407
+ }
1408
+ const content = state.src.slice(start + 2, state.pos - 1);
1409
+ if (/^\s|\s$/.test(content)) {
1410
+ state.pos = start;
1411
+ return false;
1412
+ }
1413
+ state.posMax = state.pos - 1;
1414
+ state.pos = start + 2;
1415
+ const open = state.push("plot_open", "Plot", 1);
1416
+ open.markup = openTag2;
1417
+ const text = state.push("text", "", 0);
1418
+ text.content = content;
1419
+ const close = state.push("plot_close", "Plot", -1);
1420
+ close.markup = endTag2;
1421
+ state.pos = state.posMax + 2;
1422
+ state.posMax = max;
1423
+ return true;
1424
+ };
1425
+ }
1426
+ var plotPlugin = (md) => {
1427
+ md.inline.ruler.before("emphasis", "plot", createTokenizer2());
1428
+ };
1429
+
1430
+ // src/node/features/replit.ts
1431
+ var replitPlugin = (md) => {
1432
+ createRuleBlock(md, {
1433
+ type: "replit",
1434
+ syntaxPattern: /^@\[replit([^\]]*)\]\(([^)]*)\)/,
1435
+ meta: ([, info = "", source = ""]) => {
1436
+ const { attrs } = resolveAttrs(info);
1437
+ return {
1438
+ width: attrs.width ? parseRect(attrs.width) : "100%",
1439
+ height: attrs.height ? parseRect(attrs.height) : "450px",
1440
+ source: source.startsWith("@") ? source : `@${source}`,
1441
+ title: attrs.title,
1442
+ theme: attrs.theme || ""
1443
+ };
1444
+ },
1445
+ content({ title, height, width, source, theme }) {
1446
+ return `<ReplitViewer title="${title || ""}" height="${height}" width="${width}" source="${source}" theme="${theme}" />`;
1447
+ }
1448
+ });
1449
+ };
1450
+
1451
+ // src/node/features/video/bilibili.ts
1452
+ import { URLSearchParams as URLSearchParams2 } from "node:url";
1453
+
1454
+ // src/node/utils/timeToSeconds.ts
1455
+ function timeToSeconds(time) {
1456
+ if (!time)
1457
+ return 0;
1458
+ if (Number.parseFloat(time) === Number(time))
1459
+ return Number(time);
1460
+ const [s, m, h] = time.split(":").reverse().map((n) => Number(n) || 0);
1461
+ return s + m * 60 + h * 3600;
1462
+ }
1463
+
1464
+ // src/node/features/video/bilibili.ts
1465
+ var BILIBILI_LINK = "https://player.bilibili.com/player.html";
1466
+ var bilibiliPlugin = (md) => {
1467
+ createRuleBlock(md, {
1468
+ type: "bilibili",
1469
+ name: "video_bilibili",
1470
+ // eslint-disable-next-line regexp/no-super-linear-backtracking
1471
+ syntaxPattern: /^@\[bilibili(?:\s+p(\d+))?([^\]]*)\]\(([^)]*)\)/,
1472
+ meta([, page, info = "", source = ""]) {
1473
+ const { attrs } = resolveAttrs(info);
1474
+ const ids = source.trim().split(/\s+/);
1475
+ const bvid = ids.find((id) => id.startsWith("BV"));
1476
+ const [aid, cid] = ids.filter((id) => !id.startsWith("BV"));
1477
+ return {
1478
+ page: +page || 1,
1479
+ bvid,
1480
+ aid,
1481
+ cid,
1482
+ autoplay: attrs.autoplay ?? false,
1483
+ time: timeToSeconds(attrs.time),
1484
+ title: attrs.title,
1485
+ width: attrs.width ? parseRect(attrs.width) : "100%",
1486
+ height: attrs.height ? parseRect(attrs.height) : "",
1487
+ ratio: attrs.ratio ? parseRect(attrs.ratio) : ""
1488
+ };
1489
+ },
1490
+ content(meta) {
1491
+ const params = new URLSearchParams2();
1492
+ if (meta.bvid) {
1493
+ params.set("bvid", meta.bvid);
1494
+ }
1495
+ if (meta.aid) {
1496
+ params.set("aid", meta.aid);
1497
+ }
1498
+ if (meta.cid) {
1499
+ params.set("cid", meta.cid);
1500
+ }
1501
+ if (meta.page) {
1502
+ params.set("p", meta.page.toString());
1503
+ }
1504
+ if (meta.time) {
1505
+ params.set("t", meta.time.toString());
1506
+ }
1507
+ params.set("autoplay", meta.autoplay ? "1" : "0");
1508
+ const source = `${BILIBILI_LINK}?${params.toString()}`;
1509
+ return `<VideoBilibili src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`;
1510
+ }
1511
+ });
1330
1512
  };
1331
- var folderIcon = {
1332
- name: "folder",
1333
- svg: makeSVGIcon(FileIcons["seti:folder"])
1513
+
1514
+ // src/node/features/video/youtube.ts
1515
+ import { URLSearchParams as URLSearchParams3 } from "node:url";
1516
+ var YOUTUBE_LINK = "https://www.youtube.com/embed/";
1517
+ var youtubePlugin = (md) => {
1518
+ createRuleBlock(md, {
1519
+ type: "youtube",
1520
+ name: "video_youtube",
1521
+ syntaxPattern: /^@\[youtube([^\]]*)\]\(([^)]*)\)/,
1522
+ meta([, info = "", id = ""]) {
1523
+ const { attrs } = resolveAttrs(info);
1524
+ return {
1525
+ id,
1526
+ autoplay: attrs.autoplay ?? false,
1527
+ loop: attrs.loop ?? false,
1528
+ start: timeToSeconds(attrs.start),
1529
+ end: timeToSeconds(attrs.end),
1530
+ title: attrs.title,
1531
+ width: attrs.width ? parseRect(attrs.width) : "100%",
1532
+ height: attrs.height ? parseRect(attrs.height) : "",
1533
+ ratio: attrs.ratio ? parseRect(attrs.ratio) : ""
1534
+ };
1535
+ },
1536
+ content(meta) {
1537
+ const params = new URLSearchParams3();
1538
+ if (meta.autoplay) {
1539
+ params.set("autoplay", "1");
1540
+ }
1541
+ if (meta.loop) {
1542
+ params.set("loop", "1");
1543
+ }
1544
+ if (meta.start) {
1545
+ params.set("start", meta.start.toString());
1546
+ }
1547
+ if (meta.end) {
1548
+ params.set("end", meta.end.toString());
1549
+ }
1550
+ const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`;
1551
+ return `<VideoYoutube src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />`;
1552
+ }
1553
+ });
1334
1554
  };
1335
- function getFileIcon(fileName) {
1336
- const name = getFileIconName(fileName);
1337
- if (!name)
1338
- return defaultFileIcon;
1339
- if (name in FileIcons) {
1340
- const path4 = FileIcons[name];
1341
- return {
1342
- name: name.includes(":") ? name.split(":")[1] : name,
1343
- svg: makeSVGIcon(path4)
1344
- };
1555
+
1556
+ // src/node/prepareConfigFile.ts
1557
+ import { ensureEndingSlash } from "@vuepress/helper";
1558
+ import { getDirname as getDirname2, path as path4 } from "vuepress/utils";
1559
+ var { url: filepath } = import.meta;
1560
+ var __dirname = getDirname2(filepath);
1561
+ var CLIENT_FOLDER = ensureEndingSlash(
1562
+ path4.resolve(__dirname, "../client")
1563
+ );
1564
+ async function prepareConfigFile(app, options) {
1565
+ const imports = /* @__PURE__ */ new Set();
1566
+ const enhances = /* @__PURE__ */ new Set();
1567
+ if (options.pdf) {
1568
+ imports.add(`import PDFViewer from '${CLIENT_FOLDER}components/PDFViewer.vue'`);
1569
+ enhances.add(`app.component('PDFViewer', PDFViewer)`);
1345
1570
  }
1346
- return defaultFileIcon;
1347
- }
1348
- function makeSVGIcon(svg) {
1349
- svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">${svg}</svg>`.replace(/"/g, "'").replace(/%/g, "%25").replace(/#/g, "%23").replace(/\{/g, "%7B").replace(/\}/g, "%7D").replace(/</g, "%3C").replace(/>/g, "%3E");
1350
- return `url("data:image/svg+xml,${svg}")`;
1351
- }
1352
- function getFileIconName(fileName) {
1353
- let icon = definitions.files[fileName];
1354
- if (icon)
1355
- return icon;
1356
- icon = getFileIconTypeFromExtension(fileName);
1357
- if (icon)
1358
- return icon;
1359
- for (const [partial, partialIcon] of Object.entries(definitions.partials)) {
1360
- if (fileName.includes(partial))
1361
- return partialIcon;
1571
+ if (options.bilibili) {
1572
+ imports.add(`import Bilibili from '${CLIENT_FOLDER}components/Bilibili.vue'`);
1573
+ enhances.add(`app.component('VideoBilibili', Bilibili)`);
1362
1574
  }
1363
- return icon;
1364
- }
1365
- function getFileIconTypeFromExtension(fileName) {
1366
- const firstDotIndex = fileName.indexOf(".");
1367
- if (firstDotIndex === -1)
1368
- return;
1369
- let extension = fileName.slice(firstDotIndex);
1370
- while (extension !== "") {
1371
- const icon = definitions.extensions[extension];
1372
- if (icon)
1373
- return icon;
1374
- const nextDotIndex = extension.indexOf(".", 1);
1375
- if (nextDotIndex === -1)
1376
- return;
1377
- extension = extension.slice(nextDotIndex);
1575
+ if (options.youtube) {
1576
+ imports.add(`import Youtube from '${CLIENT_FOLDER}components/Youtube.vue'`);
1577
+ enhances.add(`app.component('VideoYoutube', Youtube)`);
1378
1578
  }
1379
- return void 0;
1380
- }
1579
+ if (options.replit) {
1580
+ imports.add(`import Replit from '${CLIENT_FOLDER}components/Replit.vue'`);
1581
+ enhances.add(`app.component('ReplitViewer', Replit)`);
1582
+ }
1583
+ if (options.codeSandbox) {
1584
+ imports.add(`import CodeSandbox from '${CLIENT_FOLDER}components/CodeSandbox.vue'`);
1585
+ enhances.add(`app.component('CodeSandboxViewer', CodeSandbox)`);
1586
+ }
1587
+ if (options.plot) {
1588
+ imports.add(`import Plot from '${CLIENT_FOLDER}components/Plot.vue'`);
1589
+ enhances.add(`app.component('Plot', Plot)`);
1590
+ }
1591
+ if (options.repl) {
1592
+ imports.add(`import CodeRepl from '${CLIENT_FOLDER}components/CodeRepl.vue'`);
1593
+ enhances.add(`app.component('CodeRepl', CodeRepl)`);
1594
+ }
1595
+ if (options.caniuse) {
1596
+ imports.add(`import CanIUse from '${CLIENT_FOLDER}components/CanIUse.vue'`);
1597
+ enhances.add(`app.component('CanIUseViewer', CanIUse)`);
1598
+ }
1599
+ if (options.fileTree) {
1600
+ imports.add(`import FileTreeItem from '${CLIENT_FOLDER}components/FileTreeItem.vue'`);
1601
+ imports.add(`import '@internal/md-power/file-tree.css'`);
1602
+ enhances.add(`app.component('FileTreeItem', FileTreeItem)`);
1603
+ }
1604
+ return app.writeTemp(
1605
+ "md-power/config.js",
1606
+ `import { defineClientConfig } from 'vuepress/client'
1607
+ ${Array.from(imports.values()).join("\n")}
1381
1608
 
1382
- // src/node/features/fileTree/index.ts
1383
- var type = "file-tree";
1384
- var closeType = `container_${type}_close`;
1385
- var componentName = "FileTreeItem";
1386
- var itemOpen = "file_tree_item_open";
1387
- var itemClose = "file_tree_item_close";
1388
- var classPrefix = "vp-fti-";
1389
- var styleFilepath = "internal/md-power/file-tree.css";
1390
- async function fileTreePlugin(app, md) {
1391
- const validate = (info) => info.trim().startsWith(type);
1392
- const render = (tokens, idx) => {
1393
- if (tokens[idx].nesting === 1) {
1394
- for (let i = idx + 1; !(tokens[i].nesting === -1 && tokens[i].type === closeType); ++i) {
1395
- const token = tokens[i];
1396
- if (token.type === "list_item_open") {
1397
- const result = resolveTreeNodeInfo(tokens, token, i);
1398
- if (result) {
1399
- const [info, inline] = result;
1400
- const { filename, type: type2, expanded, empty } = info;
1401
- const icon = type2 === "file" ? getFileIcon(filename) : folderIcon;
1402
- token.type = itemOpen;
1403
- token.tag = componentName;
1404
- token.attrSet("type", type2);
1405
- token.attrSet(":expanded", expanded ? "true" : "false");
1406
- token.attrSet(":empty", empty ? "true" : "false");
1407
- updateInlineToken(inline, info, `${classPrefix}${icon.name}`);
1408
- addIcon(icon);
1409
- }
1410
- } else if (token.type === "list_item_close") {
1411
- token.type = itemClose;
1412
- token.tag = componentName;
1413
- }
1414
- }
1415
- return '<div class="vp-file-tree">';
1416
- } else {
1417
- return "</div>";
1418
- }
1419
- };
1420
- let timer = null;
1421
- const icons = {};
1422
- function addIcon(icon) {
1423
- icons[icon.name] = icon;
1424
- if (timer)
1425
- clearTimeout(timer);
1426
- timer = setTimeout(async () => {
1427
- let content = "";
1428
- for (const icon2 of Object.values(icons)) {
1429
- content += `.${classPrefix}${icon2.name} { --icon: ${icon2.svg}; }
1430
- `;
1431
- }
1432
- await app.writeTemp(styleFilepath, content);
1433
- }, 150);
1609
+ export default defineClientConfig({
1610
+ enhance({ router, app }) {
1611
+ ${Array.from(enhances.values()).map((item) => ` ${item}`).join("\n")}
1434
1612
  }
1435
- md.use(container3, type, { validate, render });
1436
- if (!fs2.existsSync(app.dir.temp(styleFilepath)))
1437
- await app.writeTemp(styleFilepath, "");
1613
+ })
1614
+ `
1615
+ );
1438
1616
  }
1439
1617
 
1440
1618
  // src/node/plugin.ts
@@ -1456,6 +1634,7 @@ function markdownPowerPlugin(options = {}) {
1456
1634
  }
1457
1635
  },
1458
1636
  extendsMarkdown: async (md, app2) => {
1637
+ await imageSizePlugin(app2, md, options.imageSize);
1459
1638
  if (options.caniuse) {
1460
1639
  const caniuse = options.caniuse === true ? {} : options.caniuse;
1461
1640
  md.use(caniusePlugin, caniuse);
@@ -1498,5 +1677,6 @@ function markdownPowerPlugin(options = {}) {
1498
1677
  };
1499
1678
  }
1500
1679
  export {
1501
- markdownPowerPlugin
1680
+ markdownPowerPlugin,
1681
+ resolveImageSize
1502
1682
  };