vuepress-plugin-md-power 1.0.0-rc.152 → 1.0.0-rc.154

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
@@ -6,10 +6,10 @@ import http from "node:https";
6
6
  import { URL, URLSearchParams } from "node:url";
7
7
  import { camelCase, isBoolean, isEmptyObject, isNull, isNumber, isPlainObject as isPlainObject$1, isString, isUndefined, kebabCase, notNullish, omit, toArray, uniqueBy, withTimeout } from "@pengzhanbo/utils";
8
8
  import imageSize from "image-size";
9
- import { colors, fs, getDirname, logger, path } from "vuepress/utils";
9
+ import { colors, fs, getDirname, logger, ora, path } from "vuepress/utils";
10
10
  import path$1 from "node:path";
11
11
  import { globSync } from "tinyglobby";
12
- import { isLinkHttp as isLinkHttp$1, removeEndingSlash, removeLeadingSlash } from "vuepress/shared";
12
+ import { isLinkHttp as isLinkHttp$1, isLinkWithProtocol, removeEndingSlash, removeLeadingSlash } from "vuepress/shared";
13
13
  import fs$1, { promises } from "node:fs";
14
14
  import process from "node:process";
15
15
  import container from "markdown-it-container";
@@ -908,7 +908,7 @@ const codeTabs = (md, options = {}) => {
908
908
  const getIcon = createCodeTabIconGetter(options);
909
909
  tab(md, {
910
910
  name: "code-tabs",
911
- tabsOpenRenderer: ({ active, data }, tokens, index, _, env) => {
911
+ openRender: ({ active, data }, tokens, index, _, env) => {
912
912
  const { meta } = tokens[index];
913
913
  const titles = data.map(({ title }) => md.renderInline(title, cleanMarkdownEnv(env)));
914
914
  const tabsData = data.map((item, dataIndex) => {
@@ -921,8 +921,8 @@ const codeTabs = (md, options = {}) => {
921
921
  }).join("");
922
922
  return `<CodeTabs id="${index}" :data='${stringifyProp(tabsData)}'${active === -1 ? "" : ` :active="${active}"`}${meta.id ? ` tab-id="${meta.id}"` : ""}>${titlesContent}`;
923
923
  },
924
- tabsCloseRenderer: () => `</CodeTabs>`,
925
- tabOpenRenderer: ({ index }, tokens, tokenIndex) => {
924
+ closeRender: () => `</CodeTabs>`,
925
+ tabOpenRender: ({ index }, tokens, tokenIndex) => {
926
926
  let foundFence = false;
927
927
  for (let i = tokenIndex; i < tokens.length; i++) {
928
928
  const { type } = tokens[i];
@@ -936,7 +936,7 @@ const codeTabs = (md, options = {}) => {
936
936
  }
937
937
  return `<template #tab${index}="{ value, isActive }">`;
938
938
  },
939
- tabCloseRenderer: () => `</template>`
939
+ tabCloseRender: () => `</template>`
940
940
  });
941
941
  };
942
942
 
@@ -985,7 +985,7 @@ const BADGE_LIST = [
985
985
  "https://forthebadge.com",
986
986
  "https://vercel.com/button"
987
987
  ];
988
- const cache$1 = new Map();
988
+ const cache$1 = /* @__PURE__ */ new Map();
989
989
  async function imageSizePlugin(app, md, type = false) {
990
990
  if (!app.env.isBuild || !type) return;
991
991
  if (type === "all") {
@@ -1113,18 +1113,25 @@ function fetchImageSize(src) {
1113
1113
  for await (const chunk of stream) {
1114
1114
  chunks.push(chunk);
1115
1115
  try {
1116
- const { width: width$1, height: height$1 } = imageSize(Buffer.concat(chunks));
1117
- if (width$1 && height$1) return resolve({
1118
- width: width$1,
1119
- height: height$1
1116
+ const { width, height } = imageSize(Buffer.concat(chunks));
1117
+ if (width && height) return resolve({
1118
+ width,
1119
+ height
1120
1120
  });
1121
1121
  } catch {}
1122
1122
  }
1123
- const { width, height } = imageSize(Buffer.concat(chunks));
1124
- resolve({
1125
- width,
1126
- height
1127
- });
1123
+ try {
1124
+ const { width, height } = imageSize(Buffer.concat(chunks));
1125
+ resolve({
1126
+ width,
1127
+ height
1128
+ });
1129
+ } catch {
1130
+ resolve({
1131
+ width: 0,
1132
+ height: 0
1133
+ });
1134
+ }
1128
1135
  }).on("error", () => resolve({
1129
1136
  width: 0,
1130
1137
  height: 0
@@ -1249,6 +1256,15 @@ function stringifyAttrs(attrs$1, withUndefined = false) {
1249
1256
 
1250
1257
  //#endregion
1251
1258
  //#region src/node/container/createContainer.ts
1259
+ /**
1260
+ * 创建 markdown-it 的自定义容器插件。
1261
+ *
1262
+ * @param md markdown-it 实例
1263
+ * @param type 容器类型(如 'tip', 'warning' 等)
1264
+ * @param options 可选的 before/after 渲染钩子
1265
+ * @param options.before 渲染容器起始标签时的回调函数
1266
+ * @param options.after 渲染容器结束标签时的回调函数
1267
+ */
1252
1268
  function createContainerPlugin(md, type, { before, after } = {}) {
1253
1269
  const render = (tokens, index, options, env) => {
1254
1270
  const token = tokens[index];
@@ -1278,6 +1294,14 @@ function createContainerPlugin(md, type, { before, after } = {}) {
1278
1294
  function createContainerSyntaxPlugin(md, type, render) {
1279
1295
  const maker = ":";
1280
1296
  const markerMinLen = 3;
1297
+ /**
1298
+ * 自定义容器的 block 规则定义。
1299
+ * @param state 当前 block 状态
1300
+ * @param startLine 起始行
1301
+ * @param endLine 结束行
1302
+ * @param silent 是否为静默模式
1303
+ * @returns 是否匹配到自定义容器
1304
+ */
1281
1305
  function defineContainer(state, startLine, endLine, silent) {
1282
1306
  const start = state.bMarks[startLine] + state.tShift[startLine];
1283
1307
  const max = state.eMarks[startLine];
@@ -1340,6 +1364,11 @@ const UNSUPPORTED_FILE_TYPES = [
1340
1364
  "xls",
1341
1365
  "xlsx"
1342
1366
  ];
1367
+ /**
1368
+ * 将文件路径数组解析为文件树节点结构
1369
+ * @param files 文件路径数组
1370
+ * @returns 文件树节点数组
1371
+ */
1343
1372
  function parseFileNodes(files) {
1344
1373
  const nodes = [];
1345
1374
  for (const file of files) {
@@ -1363,12 +1392,24 @@ function parseFileNodes(files) {
1363
1392
  }
1364
1393
  return nodes;
1365
1394
  }
1395
+ /**
1396
+ * 注册 code-tree 容器和嵌入语法的 markdown 插件
1397
+ * @param md markdown-it 实例
1398
+ * @param app vuepress app 实例
1399
+ * @param options code-tree 配置项
1400
+ */
1366
1401
  function codeTreePlugin(md, app, options = {}) {
1402
+ /**
1403
+ * 获取文件或文件夹的图标
1404
+ */
1367
1405
  const getIcon = (filename, type, mode) => {
1368
1406
  mode ||= options.icon || "colored";
1369
1407
  if (mode === "simple") return type === "folder" ? defaultFolder : defaultFile;
1370
1408
  return getFileIcon(filename, type);
1371
1409
  };
1410
+ /**
1411
+ * 渲染文件树节点为组件字符串
1412
+ */
1372
1413
  function renderFileTree(nodes, mode) {
1373
1414
  return nodes.map((node) => {
1374
1415
  const props = {
@@ -1457,6 +1498,10 @@ function codeTreePlugin(md, app, options = {}) {
1457
1498
  }
1458
1499
  });
1459
1500
  }
1501
+ /**
1502
+ * 扩展页面依赖,将 codeTreeFiles 添加到页面依赖中
1503
+ * @param page vuepress 页面对象
1504
+ */
1460
1505
  function extendsPageWithCodeTree(page) {
1461
1506
  const markdownEnv = page.markdownEnv;
1462
1507
  const codeTreeFiles = markdownEnv.codeTreeFiles ?? [];
@@ -1465,13 +1510,13 @@ function extendsPageWithCodeTree(page) {
1465
1510
 
1466
1511
  //#endregion
1467
1512
  //#region src/node/container/align.ts
1468
- const alignList = [
1469
- "left",
1470
- "center",
1471
- "right",
1472
- "justify"
1473
- ];
1474
1513
  function alignPlugin(md) {
1514
+ const alignList = [
1515
+ "left",
1516
+ "center",
1517
+ "right",
1518
+ "justify"
1519
+ ];
1475
1520
  for (const name of alignList) createContainerPlugin(md, name, { before: () => `<div style="text-align:${name}">` });
1476
1521
  createContainerPlugin(md, "flex", { before: (info) => {
1477
1522
  const { attrs: attrs$1 } = resolveAttrs(info);
@@ -1723,6 +1768,11 @@ function fieldPlugin(md) {
1723
1768
 
1724
1769
  //#endregion
1725
1770
  //#region src/node/container/fileTree.ts
1771
+ /**
1772
+ * 解析原始文件树内容为节点树结构
1773
+ * @param content 文件树的原始文本内容
1774
+ * @returns 文件树节点数组
1775
+ */
1726
1776
  function parseFileTreeRawContent(content) {
1727
1777
  const root = {
1728
1778
  info: "",
@@ -1749,6 +1799,11 @@ function parseFileTreeRawContent(content) {
1749
1799
  return root.children;
1750
1800
  }
1751
1801
  const RE_FOCUS = /^\*\*(.*)\*\*(?:$|\s+)/;
1802
+ /**
1803
+ * 解析单个节点的 info 字符串,提取文件名、注释、类型等属性
1804
+ * @param info 节点描述字符串
1805
+ * @returns 文件树节点属性
1806
+ */
1752
1807
  function parseFileTreeNodeInfo(info) {
1753
1808
  let filename = "";
1754
1809
  let comment = "";
@@ -1788,12 +1843,23 @@ function parseFileTreeNodeInfo(info) {
1788
1843
  diff
1789
1844
  };
1790
1845
  }
1846
+ /**
1847
+ * 文件树 markdown 插件主函数
1848
+ * @param md markdown 实例
1849
+ * @param options 文件树渲染选项
1850
+ */
1791
1851
  function fileTreePlugin(md, options = {}) {
1852
+ /**
1853
+ * 获取文件或文件夹的图标
1854
+ */
1792
1855
  const getIcon = (filename, type, mode) => {
1793
1856
  mode ||= options.icon || "colored";
1794
1857
  if (mode === "simple") return type === "folder" ? defaultFolder : defaultFile;
1795
1858
  return getFileIcon(filename, type);
1796
1859
  };
1860
+ /**
1861
+ * 递归渲染文件树节点
1862
+ */
1797
1863
  const renderFileTree = (nodes, meta) => nodes.map((node) => {
1798
1864
  const { info, level, children } = node;
1799
1865
  const { filename, comment, focus, expanded, type, diff } = parseFileTreeNodeInfo(info);
@@ -1828,7 +1894,7 @@ ${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children
1828
1894
 
1829
1895
  //#endregion
1830
1896
  //#region src/node/container/langRepl.ts
1831
- async function langReplPlugin(app, md, { theme, go = false, kotlin = false, rust = false }) {
1897
+ async function langReplPlugin(app, md, { theme, go = false, kotlin = false, rust = false, python = false }) {
1832
1898
  const container$1 = (lang) => createContainerPlugin(md, `${lang}-repl`, {
1833
1899
  before(info) {
1834
1900
  const { attrs: attrs$1 } = resolveAttrs(info);
@@ -1843,6 +1909,7 @@ async function langReplPlugin(app, md, { theme, go = false, kotlin = false, rust
1843
1909
  if (kotlin) container$1("kotlin");
1844
1910
  if (go) container$1("go");
1845
1911
  if (rust) container$1("rust");
1912
+ if (python) container$1("python");
1846
1913
  theme ??= {
1847
1914
  light: "github-light",
1848
1915
  dark: "github-dark"
@@ -1861,6 +1928,7 @@ async function langReplPlugin(app, md, { theme, go = false, kotlin = false, rust
1861
1928
  if (kotlin) data.grammars.kotlin = await readGrammar("kotlin");
1862
1929
  if (go) data.grammars.go = await readGrammar("go");
1863
1930
  if (rust) data.grammars.rust = await readGrammar("rust");
1931
+ if (python) data.grammars.python = await readGrammar("python");
1864
1932
  } catch {
1865
1933
  /* istanbul ignore next -- @preserve */
1866
1934
  logger.error("[vuepress-plugin-md-power]", `Failed to load packages: ${colors.green("tm-themes")}, ${colors.green("tm-grammars")}, Please install them manually.`);
@@ -1875,6 +1943,59 @@ async function read(file) {
1875
1943
  return void 0;
1876
1944
  }
1877
1945
 
1946
+ //#endregion
1947
+ //#region src/node/utils/logger.ts
1948
+ /**
1949
+ * Logger utils
1950
+ */
1951
+ var Logger = class {
1952
+ constructor(name = "") {
1953
+ this.name = name;
1954
+ }
1955
+ init(subname, text) {
1956
+ return ora({
1957
+ prefixText: colors.blue(`${this.name}${subname ? `:${subname}` : ""}: `),
1958
+ text
1959
+ });
1960
+ }
1961
+ /**
1962
+ * Create a loading spinner with text
1963
+ */
1964
+ load(subname, msg) {
1965
+ const instance = this.init(subname, msg);
1966
+ return {
1967
+ succeed: (text) => instance.succeed(text),
1968
+ fail: (text) => instance.succeed(text)
1969
+ };
1970
+ }
1971
+ info(subname, text = "", ...args) {
1972
+ this.init(subname, colors.blue(text)).info();
1973
+ if (args.length) console.info(...args);
1974
+ }
1975
+ /**
1976
+ * Log success msg
1977
+ */
1978
+ succeed(subname, text = "", ...args) {
1979
+ this.init(subname, colors.green(text)).succeed();
1980
+ if (args.length) console.log(...args);
1981
+ }
1982
+ /**
1983
+ * Log warning msg
1984
+ */
1985
+ warn(subname, text = "", ...args) {
1986
+ this.init(subname, colors.yellow(text)).warn();
1987
+ if (args.length) console.warn(...args);
1988
+ }
1989
+ /**
1990
+ * Log error msg
1991
+ */
1992
+ error(subname, text = "", ...args) {
1993
+ this.init(subname, colors.red(text)).fail();
1994
+ if (args.length) console.error(...args);
1995
+ }
1996
+ };
1997
+ const logger$1 = new Logger("vuepress-plugin-md-power");
1998
+
1878
1999
  //#endregion
1879
2000
  //#region src/node/container/npmToPreset.ts
1880
2001
  const ALLOW_LIST = [
@@ -2124,6 +2245,9 @@ const MANAGERS_CONFIG = {
2124
2245
 
2125
2246
  //#endregion
2126
2247
  //#region src/node/container/npmTo.ts
2248
+ /**
2249
+ * 注册 npm-to 容器插件,将 npm 代码块自动转换为多包管理器命令分组
2250
+ */
2127
2251
  function npmToPlugins(md, options = {}) {
2128
2252
  const opt = isArray(options) ? { tabs: options } : options;
2129
2253
  const defaultTabs = opt.tabs?.length ? opt.tabs : DEFAULT_TABS;
@@ -2140,12 +2264,19 @@ function npmToPlugins(md, options = {}) {
2140
2264
  const lines = content.split(/(\n|\s*&&\s*)/);
2141
2265
  return md.render(resolveNpmTo(lines, token.info.trim(), idx, tabs$1), cleanMarkdownEnv(env));
2142
2266
  }
2143
- console.warn(`${colors.yellow("[vuepress-plugin-md-power]")} Invalid npm-to container in ${colors.gray(env.filePathRelative || env.filePath)}`);
2267
+ logger$1.warn("npm-to", `Invalid npm-to container in ${colors.gray(env.filePathRelative || env.filePath)}`);
2144
2268
  return "";
2145
2269
  },
2146
2270
  after: () => ""
2147
2271
  });
2148
2272
  }
2273
+ /**
2274
+ * 将 npm 命令转换为各包管理器命令分组
2275
+ * @param lines 命令行数组
2276
+ * @param info 代码块类型
2277
+ * @param idx token 索引
2278
+ * @param tabs 需要支持的包管理器
2279
+ */
2149
2280
  function resolveNpmTo(lines, info, idx, tabs$1) {
2150
2281
  tabs$1 = validateTabs(tabs$1);
2151
2282
  const res = [];
@@ -2172,16 +2303,25 @@ function resolveNpmTo(lines, info, idx, tabs$1) {
2172
2303
  }
2173
2304
  return `:::code-tabs#npm-to-${tabs$1.join("-")}\n${res.join("\n")}\n:::`;
2174
2305
  }
2306
+ /**
2307
+ * 根据命令行内容查找对应的包管理器配置
2308
+ */
2175
2309
  function findConfig(line) {
2176
2310
  for (const { pattern,...config } of Object.values(MANAGERS_CONFIG)) if (pattern.test(line)) return config;
2177
2311
  return void 0;
2178
2312
  }
2313
+ /**
2314
+ * 校验 tabs 合法性,返回允许的包管理器列表
2315
+ */
2179
2316
  function validateTabs(tabs$1) {
2180
2317
  tabs$1 = tabs$1.filter((tab$1) => ALLOW_LIST.includes(tab$1));
2181
2318
  if (tabs$1.length === 0) return DEFAULT_TABS;
2182
2319
  return tabs$1;
2183
2320
  }
2184
2321
  const LINE_REG = /(.*)(npm|npx)\s+(.*)/;
2322
+ /**
2323
+ * 解析一行 npm/npx 命令,拆分出环境变量、命令、参数等
2324
+ */
2185
2325
  function parseLine(line) {
2186
2326
  const match = line.match(LINE_REG);
2187
2327
  if (!match) return false;
@@ -2212,6 +2352,9 @@ function parseLine(line) {
2212
2352
  ...parseArgs(rest.slice(idx + 1))
2213
2353
  };
2214
2354
  }
2355
+ /**
2356
+ * 解析 npm 命令参数,区分命令、参数、脚本参数
2357
+ */
2215
2358
  function parseArgs(line) {
2216
2359
  line = line?.trim();
2217
2360
  const [npmArgs, scriptArgs] = line.split(/\s+--\s+/);
@@ -2289,7 +2432,7 @@ function stepsPlugin(md) {
2289
2432
  const tabs = (md) => {
2290
2433
  tab(md, {
2291
2434
  name: "tabs",
2292
- tabsOpenRenderer: ({ active, data }, tokens, index, _, env) => {
2435
+ openRender: ({ active, data }, tokens, index, _, env) => {
2293
2436
  const { meta } = tokens[index];
2294
2437
  const titles = data.map(({ title }) => md.renderInline(title, cleanMarkdownEnv(env)));
2295
2438
  const tabsData = data.map((item, dataIndex) => {
@@ -2299,9 +2442,9 @@ const tabs = (md) => {
2299
2442
  return `<Tabs id="${index}" :data='${stringifyProp(tabsData)}'${active === -1 ? "" : ` :active="${active}"`}${meta.id ? ` tab-id="${meta.id}"` : ""}>
2300
2443
  ${titles.map((title, titleIndex) => `<template #title${titleIndex}="{ value, isActive }">${title}</template>`).join("")}`;
2301
2444
  },
2302
- tabsCloseRenderer: () => `</Tabs>`,
2303
- tabOpenRenderer: ({ index }) => `<template #tab${index}="{ value, isActive }">`,
2304
- tabCloseRenderer: () => `</template>`
2445
+ closeRender: () => `</Tabs>`,
2446
+ tabOpenRender: ({ index }) => `<template #tab${index}="{ value, isActive }">`,
2447
+ tabCloseRender: () => `</template>`
2305
2448
  });
2306
2449
  };
2307
2450
 
@@ -2417,7 +2560,7 @@ function markdownEmbed(app, md, env, { url, title, desc, codeSetting = "", expan
2417
2560
  const filepath$1 = findFile(app, env, url);
2418
2561
  const code = readFileSync(filepath$1);
2419
2562
  if (code === false) {
2420
- console.warn("[vuepress-plugin-md-power] Cannot read markdown file:", filepath$1);
2563
+ logger$1.warn("demo-markdown", `Cannot read markdown file: ${colors.gray(filepath$1)}\n at: ${colors.gray(env.filePathRelative || "")}`);
2421
2564
  return "";
2422
2565
  }
2423
2566
  const demo = {
@@ -2692,7 +2835,7 @@ async function compileCode(code, output) {
2692
2835
  if (code.css) res.css = await compileStyle(code.css.trim(), code.cssType);
2693
2836
  if (code.html) res.html = code.html.trim();
2694
2837
  } catch (e) {
2695
- console.error("[vuepress-plugin-md-power] demo parse error: \n", e);
2838
+ logger$1.error("demo-normal", "demo parse error: \n", e);
2696
2839
  }
2697
2840
  writeFileSync(output, `import { ref } from "vue"\nexport default ref(${JSON.stringify(res, null, 2)})`);
2698
2841
  checkDemoRender();
@@ -2701,7 +2844,7 @@ function normalEmbed(app, md, env, { url, title, desc, codeSetting = "", expande
2701
2844
  const filepath$1 = findFile(app, env, url);
2702
2845
  const code = readFileSync(filepath$1);
2703
2846
  if (code === false) {
2704
- console.warn("[vuepress-plugin-md-power] Cannot read demo file:", filepath$1);
2847
+ logger$1.warn("demo-normal", `Cannot read demo file: ${colors.gray(filepath$1)}\n at: ${colors.gray(env.filePathRelative || "")}`);
2705
2848
  return "";
2706
2849
  }
2707
2850
  const source = parseEmbedCode(code);
@@ -2830,7 +2973,7 @@ function vueEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded =
2830
2973
  const filepath$1 = findFile(app, env, url);
2831
2974
  const code = readFileSync(filepath$1);
2832
2975
  if (code === false) {
2833
- console.warn("[vuepress-plugin-md-power] Cannot read vue file:", filepath$1);
2976
+ logger$1.warn("demo-vue", `Cannot read vue demo file: ${colors.gray(filepath$1)}\n at: ${colors.gray(env.filePathRelative || "")}`);
2834
2977
  return "";
2835
2978
  }
2836
2979
  const basename = path$1.basename(filepath$1).replace(/-|\./g, "_");
@@ -2947,6 +3090,15 @@ function transformStyle(code) {
2947
3090
 
2948
3091
  //#endregion
2949
3092
  //#region src/node/demo/demo.ts
3093
+ const embedMap = {
3094
+ vue: vueEmbed,
3095
+ normal: normalEmbed,
3096
+ markdown: markdownEmbed
3097
+ };
3098
+ /**
3099
+ * 嵌入语法
3100
+ * @[demo type info](url)
3101
+ */
2950
3102
  function demoEmbed(app, md) {
2951
3103
  createEmbedRuleBlock(md, {
2952
3104
  type: "demo",
@@ -2959,12 +3111,10 @@ function demoEmbed(app, md) {
2959
3111
  content: (meta, content, env) => {
2960
3112
  const { url, type } = meta;
2961
3113
  if (!url) {
2962
- console.warn("[vuepress-plugin-md-power] Invalid demo url: ", url);
3114
+ logger$1.warn("demo-vue", `Invalid filepath: ${colors.gray(url)}`);
2963
3115
  return content;
2964
3116
  }
2965
- if (type === "vue") return vueEmbed(app, md, env, meta);
2966
- if (type === "normal") return normalEmbed(app, md, env, meta);
2967
- if (type === "markdown") return markdownEmbed(app, md, env, meta);
3117
+ if (embedMap[type]) return embedMap[type](app, md, env, meta);
2968
3118
  return content;
2969
3119
  }
2970
3120
  });
@@ -3074,7 +3224,7 @@ const audioReader = (state, silent) => {
3074
3224
  };
3075
3225
  const audioReaderPlugin = (md) => {
3076
3226
  md.renderer.rules.audio_reader = (tokens, idx) => {
3077
- const meta = tokens[idx].meta ?? {};
3227
+ const meta = tokens[idx].meta;
3078
3228
  if (meta.startTime) meta.startTime = Number(meta.startTime);
3079
3229
  if (meta.endTime) meta.endTime = Number(meta.endTime);
3080
3230
  if (meta.volume) meta.volume = Number(meta.volume);
@@ -3355,10 +3505,10 @@ function checkSupportType(type) {
3355
3505
  break;
3356
3506
  }
3357
3507
  /* istanbul ignore if -- @preserve */
3358
- if (name) console.warn(`${colors.yellow("[vuepress-plugin-md-power] artPlayer: ")} ${colors.cyan(name)} is not installed, please install it via npm or yarn or pnpm`);
3508
+ if (name) logger$1.warn("artPlayer", `${colors.cyan(name)} is not installed, please install it via npm or yarn or pnpm`);
3359
3509
  } else
3360
3510
  /* istanbul ignore next -- @preserve */
3361
- console.warn(`${colors.yellow("[vuepress-plugin-md-power] artPlayer: ")} unsupported video type: ${colors.cyan(type)}`);
3511
+ logger$1.warn("artPlayer", `unsupported video type: ${colors.cyan(type)}`);
3362
3512
  }
3363
3513
 
3364
3514
  //#endregion
@@ -3520,6 +3670,46 @@ function parseSource(source) {
3520
3670
  }
3521
3671
  }
3522
3672
 
3673
+ //#endregion
3674
+ //#region src/node/enhance/links.ts
3675
+ function linksPlugin(md) {
3676
+ const externalAttrs = {
3677
+ target: "_blank",
3678
+ rel: "noopener noreferrer"
3679
+ };
3680
+ let hasOpenInternalLink = false;
3681
+ const internalTag = "VPLink";
3682
+ function handleLinkOpen(tokens, idx) {
3683
+ hasOpenInternalLink = false;
3684
+ const token = tokens[idx];
3685
+ const hrefIndex = token.attrIndex("href");
3686
+ /* istanbul ignore if -- @preserve */
3687
+ if (hrefIndex < 0) return;
3688
+ const hrefAttr = token.attrs[hrefIndex];
3689
+ const hrefLink = hrefAttr[1];
3690
+ if (isLinkWithProtocol(hrefLink)) {
3691
+ Object.entries(externalAttrs).forEach(([key, val]) => {
3692
+ token.attrSet(key, val);
3693
+ });
3694
+ return;
3695
+ }
3696
+ if (hrefLink[0] === "#") return;
3697
+ hasOpenInternalLink = true;
3698
+ token.tag = internalTag;
3699
+ }
3700
+ md.renderer.rules.link_open = (tokens, idx, opts, env, self) => {
3701
+ handleLinkOpen(tokens, idx);
3702
+ return self.renderToken(tokens, idx, opts);
3703
+ };
3704
+ md.renderer.rules.link_close = (tokens, idx, opts, _env, self) => {
3705
+ if (hasOpenInternalLink) {
3706
+ hasOpenInternalLink = false;
3707
+ tokens[idx].tag = internalTag;
3708
+ }
3709
+ return self.renderToken(tokens, idx, opts);
3710
+ };
3711
+ }
3712
+
3523
3713
  //#endregion
3524
3714
  //#region src/node/icon/createIconRule.ts
3525
3715
  function createIconRule([l1, l2, r1, r2], deprecated) {
@@ -3636,7 +3826,7 @@ const iconPlugin = (md, options = {}) => {
3636
3826
  const [name, opt = ""] = content.split(" ");
3637
3827
  const [size, color] = opt.trim().split("/");
3638
3828
  icon = `${name}${size ? ` =${size}` : ""}${color ? ` /${color}` : ""}`;
3639
- console.warn(`The icon syntax of \`${colors.yellow(`:[${content}]:`)}\` is deprecated, please use \`${colors.green(`::${icon}::`)}\` instead. (${colors.gray(env.filePathRelative || env.filePath)})`);
3829
+ logger$1.warn("icon", `The icon syntax of \`${colors.yellow(`:[${content}]:`)}\` is deprecated, please use \`${colors.green(`::${icon}::`)}\` instead. (${colors.gray(env.filePathRelative || env.filePath)})`);
3640
3830
  }
3641
3831
  return iconRender(icon, options);
3642
3832
  };
@@ -3689,7 +3879,7 @@ function normalizeAsset(asset, provide) {
3689
3879
  link,
3690
3880
  provide
3691
3881
  };
3692
- console.error(`[vuepress:icon] Can not recognize icon link: "${asset}"`);
3882
+ logger$1.error("icon", `Can not recognize icon link: "${asset}"`);
3693
3883
  return null;
3694
3884
  }
3695
3885
  function normalizeLink(link) {
@@ -3881,18 +4071,18 @@ const plotDef = (state, silent) => {
3881
4071
  const content = state.src.slice(start + 2, state.pos);
3882
4072
  state.posMax = state.pos;
3883
4073
  state.pos = start + 2;
3884
- const token = state.push("plot_inline", "Plot", 0);
3885
- token.markup = "!!";
3886
- token.content = content;
4074
+ const openToken = state.push("plot_inline_open", "Plot", 1);
4075
+ openToken.markup = "!!";
4076
+ openToken.content = content;
4077
+ const contentToken = state.push("text", "", 0);
4078
+ contentToken.content = content;
4079
+ const closeToken = state.push("plot_inline_close", "Plot", -1);
4080
+ closeToken.markup = "!!";
3887
4081
  state.pos = state.posMax + 2;
3888
4082
  state.posMax = max;
3889
4083
  return true;
3890
4084
  };
3891
4085
  const plotPlugin = (md) => {
3892
- md.renderer.rules.plot_inline = (tokens, idx) => {
3893
- const token = tokens[idx];
3894
- return `<Plot>${token.content}</Plot>`;
3895
- };
3896
4086
  md.inline.ruler.before("emphasis", "plot", plotDef);
3897
4087
  };
3898
4088
 
@@ -3928,8 +4118,8 @@ const { url: filepath } = import.meta;
3928
4118
  const __dirname = getDirname(filepath);
3929
4119
  const CLIENT_FOLDER = ensureEndingSlash(path.resolve(__dirname, "../client"));
3930
4120
  async function prepareConfigFile(app, options) {
3931
- const imports = new Set();
3932
- const enhances = new Set();
4121
+ const imports = /* @__PURE__ */ new Set();
4122
+ const enhances = /* @__PURE__ */ new Set();
3933
4123
  imports.add(`import Tabs from '${CLIENT_FOLDER}components/Tabs.vue'`);
3934
4124
  enhances.add(`app.component('Tabs', Tabs)`);
3935
4125
  imports.add(`import CodeTabs from '${CLIENT_FOLDER}components/CodeTabs.vue'`);
@@ -4065,11 +4255,14 @@ function markdownPowerPlugin(options = {}) {
4065
4255
  clientConfigFile: (app) => prepareConfigFile(app, options),
4066
4256
  define: provideData(options),
4067
4257
  extendsBundlerOptions(bundlerOptions, app) {
4068
- if (options.repl) addViteOptimizeDepsInclude(bundlerOptions, app, [
4069
- "shiki/core",
4070
- "shiki/wasm",
4071
- "shiki/engine/oniguruma"
4072
- ]);
4258
+ if (options.repl) {
4259
+ addViteOptimizeDepsInclude(bundlerOptions, app, [
4260
+ "shiki/core",
4261
+ "shiki/wasm",
4262
+ "shiki/engine/oniguruma"
4263
+ ]);
4264
+ if (options.repl.python) addViteOptimizeDepsInclude(bundlerOptions, app, ["pyodide"]);
4265
+ }
4073
4266
  if (options.artPlayer) addViteOptimizeDepsInclude(bundlerOptions, app, [
4074
4267
  "artplayer",
4075
4268
  "dashjs",
@@ -4078,6 +4271,7 @@ function markdownPowerPlugin(options = {}) {
4078
4271
  ]);
4079
4272
  },
4080
4273
  extendsMarkdown: async (md, app) => {
4274
+ linksPlugin(md);
4081
4275
  docsTitlePlugin(md);
4082
4276
  embedSyntaxPlugin(md, options);
4083
4277
  inlineSyntaxPlugin(md, options);