vitepress-plugin-toolkit 0.1.0
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/LICENSE +21 -0
- package/dist/client/browser/index.d.ts +126 -0
- package/dist/client/browser/index.js +181 -0
- package/dist/client/ssr/index.d.ts +126 -0
- package/dist/client/ssr/index.js +171 -0
- package/dist/client/styles/copy-button.css +75 -0
- package/dist/client/styles/loading.css +18 -0
- package/dist/client/styles/transition/fade-in-down.css +25 -0
- package/dist/client/styles/transition/fade-in-height-expand.css +25 -0
- package/dist/client/styles/transition/fade-in-left.css +25 -0
- package/dist/client/styles/transition/fade-in-right.css +25 -0
- package/dist/client/styles/transition/fade-in-scale-up.css +29 -0
- package/dist/client/styles/transition/fade-in-up.css +25 -0
- package/dist/client/styles/transition/fade-in-width-expand.css +23 -0
- package/dist/client/styles/transition/fade-in.css +16 -0
- package/dist/client/styles/transition/slide-in-down.css +23 -0
- package/dist/client/styles/transition/slide-in-left.css +23 -0
- package/dist/client/styles/transition/slide-in-right.css +23 -0
- package/dist/client/styles/transition/slide-in-up.css +23 -0
- package/dist/client/styles/transition/vars.css +10 -0
- package/dist/node/index.d.ts +331 -0
- package/dist/node/index.js +446 -0
- package/package.json +51 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import container from "markdown-it-container";
|
|
2
|
+
import { camelCase, deepMerge, isBoolean, isNull, isNumber, isString, isUndefined, kebabCase, objectEntries, objectKeys, slash } from "@pengzhanbo/utils";
|
|
3
|
+
import ansis from "ansis";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
//#region src/shared/link.ts
|
|
6
|
+
const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i;
|
|
7
|
+
function isExternal(path) {
|
|
8
|
+
return EXTERNAL_URL_RE.test(path);
|
|
9
|
+
}
|
|
10
|
+
const URL_PROTOCOL_RE = /^[a-z][a-z0-9+.-]*:/;
|
|
11
|
+
function isLinkWithProtocol(link) {
|
|
12
|
+
return URL_PROTOCOL_RE.test(link) || link.startsWith("//");
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/node/utils/resolve-attrs.ts
|
|
16
|
+
/**
|
|
17
|
+
* Regular expression for matching attribute values
|
|
18
|
+
*
|
|
19
|
+
* 匹配属性值的正则表达式
|
|
20
|
+
*/
|
|
21
|
+
const RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=(?<quote>['"])(?<valueWithQuote>.+?)\k<quote>|=(?<valueWithoutQuote>\S+))?(?:\s+|$)/;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve attribute string to object
|
|
24
|
+
*
|
|
25
|
+
* 将属性字符串解析为对象
|
|
26
|
+
*
|
|
27
|
+
* @param info - Attribute string / 属性字符串
|
|
28
|
+
* @returns Object with attrs and rawAttrs / 包含 attrs 和 rawAttrs 的对象
|
|
29
|
+
* @typeParam T - Attribute type / 属性类型
|
|
30
|
+
*/
|
|
31
|
+
function resolveAttrs(info) {
|
|
32
|
+
info = info.trim();
|
|
33
|
+
if (!info) return {};
|
|
34
|
+
const attrs = {};
|
|
35
|
+
let matched;
|
|
36
|
+
while (matched = info.match(RE_ATTR_VALUE)) {
|
|
37
|
+
const { attr, valueWithQuote, valueWithoutQuote } = matched.groups;
|
|
38
|
+
const value = valueWithQuote || valueWithoutQuote || true;
|
|
39
|
+
let v = isString(value) ? value.trim() : value;
|
|
40
|
+
if (v === "true") v = true;
|
|
41
|
+
else if (v === "false") v = false;
|
|
42
|
+
else if (v === "\"\"" || v === "''") v = "";
|
|
43
|
+
attrs[camelCase(attr)] = v;
|
|
44
|
+
info = info.slice(matched[0].length);
|
|
45
|
+
}
|
|
46
|
+
return attrs;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve single attribute value from info string
|
|
50
|
+
*
|
|
51
|
+
* 从信息字符串中解析单个属性值
|
|
52
|
+
*
|
|
53
|
+
* @param info - Info string / 信息字符串
|
|
54
|
+
* @param key - Attribute key / 属性键
|
|
55
|
+
* @returns Attribute value or undefined / 属性值或 undefined
|
|
56
|
+
*/
|
|
57
|
+
function resolveAttr(info, key) {
|
|
58
|
+
const pattern = new RegExp(`(?:^|\\s+)${key}(?:=(?<quote>['"])(?<valueWithQuote>.+?)\\k<quote>|=(?<valueWithoutQuote>\\S+))?(?:\\s+|$)`);
|
|
59
|
+
const groups = info.match(pattern)?.groups;
|
|
60
|
+
return groups?.valueWithQuote || groups?.valueWithoutQuote;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/node/markdown/container.ts
|
|
64
|
+
/**
|
|
65
|
+
* Create markdown-it custom container plugin
|
|
66
|
+
*
|
|
67
|
+
* 创建 markdown-it 的自定义容器插件
|
|
68
|
+
*
|
|
69
|
+
* @param md - Markdown-it instance / Markdown-it 实例
|
|
70
|
+
* @param type - Container type (e.g., 'tip', 'warning') / 容器类型(如 'tip', 'warning' 等)
|
|
71
|
+
* @param options - Optional before/after render hooks / 可选的 before/after 渲染钩子
|
|
72
|
+
* @param options.before - Callback for rendering container opening tag / 渲染容器起始标签时的回调函数
|
|
73
|
+
* @param options.after - Callback for rendering container closing tag / 渲染容器结束标签时的回调函数
|
|
74
|
+
*/
|
|
75
|
+
function createContainerPlugin(md, type, { before, after } = {}) {
|
|
76
|
+
const render = (tokens, index, options, env) => {
|
|
77
|
+
const token = tokens[index];
|
|
78
|
+
const info = token.info.trim().slice(type.length).trim() || "";
|
|
79
|
+
if (token.nesting === 1) return before?.(info, tokens, index, options, env) ?? `<div class="custom-container ${type}">`;
|
|
80
|
+
else return after?.(info, tokens, index, options, env) ?? "</div>";
|
|
81
|
+
};
|
|
82
|
+
md.use(container, type, { render });
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create a custom container rule where content is not processed by markdown-it
|
|
86
|
+
* Requires custom content processing logic
|
|
87
|
+
* ```md
|
|
88
|
+
* ::: type
|
|
89
|
+
* xxxx <-- content: this part will not be processed by markdown-it
|
|
90
|
+
* :::
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const example = createContainerSyntaxPlugin(md, 'example', (tokens, index, options, env) => {
|
|
96
|
+
* const { content, meta } = tokens[index]
|
|
97
|
+
* return `<div class="example">${meta.title} | ${content}</div>`
|
|
98
|
+
* })
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @param md - Markdown-it instance / Markdown-it 实例
|
|
102
|
+
* @param type - Container type / 容器类型
|
|
103
|
+
* @param render - Custom render rule / 自定义渲染规则
|
|
104
|
+
*/
|
|
105
|
+
function createContainerSyntaxPlugin(md, type, render) {
|
|
106
|
+
const maker = ":";
|
|
107
|
+
const markerMinLen = 3;
|
|
108
|
+
/**
|
|
109
|
+
* Custom container block rule definition
|
|
110
|
+
*
|
|
111
|
+
* 自定义容器的 block 规则定义
|
|
112
|
+
*
|
|
113
|
+
* @param state - Current block state / 当前 block 状态
|
|
114
|
+
* @param startLine - Start line / 起始行
|
|
115
|
+
* @param endLine - End line / 结束行
|
|
116
|
+
* @param silent - Silent mode / 是否为静默模式
|
|
117
|
+
* @returns Whether matched / 是否匹配到自定义容器
|
|
118
|
+
*/
|
|
119
|
+
function defineContainer(state, startLine, endLine, silent) {
|
|
120
|
+
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
121
|
+
const max = state.eMarks[startLine];
|
|
122
|
+
let pos = start;
|
|
123
|
+
if (state.src[pos] !== maker) return false;
|
|
124
|
+
for (pos = start + 1; pos <= max; pos++) if (state.src[pos] !== maker) break;
|
|
125
|
+
if (pos - start < markerMinLen) return false;
|
|
126
|
+
const markup = state.src.slice(start, pos);
|
|
127
|
+
const info = state.src.slice(pos, max).trim();
|
|
128
|
+
if (!info.startsWith(type)) return false;
|
|
129
|
+
/* istanbul ignore if -- @preserve */
|
|
130
|
+
if (silent) return true;
|
|
131
|
+
let line = startLine;
|
|
132
|
+
let content = "";
|
|
133
|
+
while (++line < endLine) {
|
|
134
|
+
if (state.src.slice(state.bMarks[line], state.eMarks[line]).trim() === markup) break;
|
|
135
|
+
content += `${state.src.slice(state.bMarks[line], state.eMarks[line])}\n`;
|
|
136
|
+
}
|
|
137
|
+
const token = state.push(`${type}_container`, "", 0);
|
|
138
|
+
token.meta = resolveAttrs(info.slice(type.length));
|
|
139
|
+
token.content = content;
|
|
140
|
+
token.markup = `${markup} ${type}`;
|
|
141
|
+
token.map = [startLine, line + 1];
|
|
142
|
+
state.line = line + 1;
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
const defaultRender = (tokens, index) => {
|
|
146
|
+
const { content } = tokens[index];
|
|
147
|
+
return `<div class="custom-container ${type}">${content}</div>`;
|
|
148
|
+
};
|
|
149
|
+
md.block.ruler.before("fence", `${type}_definition`, defineContainer);
|
|
150
|
+
md.renderer.rules[`${type}_container`] = render ?? defaultRender;
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/node/markdown/embed.ts
|
|
154
|
+
/**
|
|
155
|
+
* Create embed rule block
|
|
156
|
+
*
|
|
157
|
+
* 创建嵌入规则块
|
|
158
|
+
*
|
|
159
|
+
* Syntax: \@\[name]()
|
|
160
|
+
*
|
|
161
|
+
* 语法:\@\[name]()
|
|
162
|
+
*
|
|
163
|
+
* @param md - Markdown instance / Markdown 实例
|
|
164
|
+
* @param {EmbedRuleBlockOptions} options - Embed rule block options / 嵌入规则块选项
|
|
165
|
+
* @typeParam Meta - Metadata type / 元数据类型
|
|
166
|
+
*/
|
|
167
|
+
function createEmbedRuleBlock(md, { type, name = type, syntaxPattern, beforeName = "code", ruleOptions = { alt: [
|
|
168
|
+
"paragraph",
|
|
169
|
+
"reference",
|
|
170
|
+
"blockquote",
|
|
171
|
+
"list"
|
|
172
|
+
] }, meta, content }) {
|
|
173
|
+
const MIN_LENGTH = type.length + 5;
|
|
174
|
+
const START_CODES = [
|
|
175
|
+
64,
|
|
176
|
+
91,
|
|
177
|
+
...type.split("").map((c) => c.charCodeAt(0))
|
|
178
|
+
];
|
|
179
|
+
md.block.ruler.before(beforeName, name, (state, startLine, endLine, silent) => {
|
|
180
|
+
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
181
|
+
const max = state.eMarks[startLine];
|
|
182
|
+
if (pos + MIN_LENGTH > max) return false;
|
|
183
|
+
for (let i = 0; i < START_CODES.length; i += 1) if (state.src.charCodeAt(pos + i) !== START_CODES[i]) return false;
|
|
184
|
+
const content = state.src.slice(pos, max);
|
|
185
|
+
const match = content.match(syntaxPattern);
|
|
186
|
+
if (!match) return false;
|
|
187
|
+
/* istanbul ignore if -- @preserve */
|
|
188
|
+
if (silent) return true;
|
|
189
|
+
const token = state.push(name, "", 0);
|
|
190
|
+
token.meta = meta(match);
|
|
191
|
+
token.content = content;
|
|
192
|
+
token.map = [startLine, startLine + 1];
|
|
193
|
+
state.line = startLine + 1;
|
|
194
|
+
return true;
|
|
195
|
+
}, ruleOptions);
|
|
196
|
+
md.renderer.rules[name] = (tokens, index, _, env) => {
|
|
197
|
+
const token = tokens[index];
|
|
198
|
+
token.content = content(token.meta, token.content, env);
|
|
199
|
+
return token.content;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/node/utils/constants.ts
|
|
204
|
+
/**
|
|
205
|
+
* 支持的视频文件名扩展名
|
|
206
|
+
*/
|
|
207
|
+
const EXTENSION_VIDEOS = [
|
|
208
|
+
"mp4",
|
|
209
|
+
"mp3",
|
|
210
|
+
"webm",
|
|
211
|
+
"ogg",
|
|
212
|
+
"mpd",
|
|
213
|
+
"dash",
|
|
214
|
+
"m3u8",
|
|
215
|
+
"hls",
|
|
216
|
+
"ts",
|
|
217
|
+
"flv",
|
|
218
|
+
"mkv",
|
|
219
|
+
"mov",
|
|
220
|
+
"ogv"
|
|
221
|
+
];
|
|
222
|
+
/**
|
|
223
|
+
* 支持的图片文件名扩展名
|
|
224
|
+
*/
|
|
225
|
+
const EXTENSION_IMAGES = [
|
|
226
|
+
"jpg",
|
|
227
|
+
"jpeg",
|
|
228
|
+
"png",
|
|
229
|
+
"gif",
|
|
230
|
+
"avif",
|
|
231
|
+
"webp",
|
|
232
|
+
"svg",
|
|
233
|
+
"bmp",
|
|
234
|
+
"ico",
|
|
235
|
+
"tiff",
|
|
236
|
+
"apng",
|
|
237
|
+
"jfif",
|
|
238
|
+
"pjpeg",
|
|
239
|
+
"pjp",
|
|
240
|
+
"xbm"
|
|
241
|
+
];
|
|
242
|
+
/**
|
|
243
|
+
* 支持的音频文件名扩展名
|
|
244
|
+
*/
|
|
245
|
+
const EXTENSION_AUDIOS = [
|
|
246
|
+
"mp3",
|
|
247
|
+
"flac",
|
|
248
|
+
"wav",
|
|
249
|
+
"ogg",
|
|
250
|
+
"opus",
|
|
251
|
+
"webm",
|
|
252
|
+
"acc"
|
|
253
|
+
];
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/node/utils/logger.ts
|
|
256
|
+
/**
|
|
257
|
+
* Log levels mapping
|
|
258
|
+
*
|
|
259
|
+
* 日志级别映射
|
|
260
|
+
*/
|
|
261
|
+
const logLevels = {
|
|
262
|
+
silent: 0,
|
|
263
|
+
error: 1,
|
|
264
|
+
warn: 2,
|
|
265
|
+
info: 3,
|
|
266
|
+
debug: 4
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Create logger instance
|
|
270
|
+
*
|
|
271
|
+
* 创建日志实例
|
|
272
|
+
*
|
|
273
|
+
* @param prefix - Log prefix / 日志前缀
|
|
274
|
+
* @param defaultLevel - Default log level / 默认日志级别
|
|
275
|
+
* @returns Logger instance / 日志实例
|
|
276
|
+
*/
|
|
277
|
+
function createLogger(prefix, defaultLevel = "info") {
|
|
278
|
+
prefix = `[${prefix}]`;
|
|
279
|
+
/**
|
|
280
|
+
* Output log
|
|
281
|
+
*
|
|
282
|
+
* 输出日志
|
|
283
|
+
*/
|
|
284
|
+
function output(type, msg, level) {
|
|
285
|
+
level = isBoolean(level) ? level ? defaultLevel : "error" : level;
|
|
286
|
+
if (logLevels[level] >= logLevels[type]) {
|
|
287
|
+
const method = type === "info" || type === "debug" ? "log" : type;
|
|
288
|
+
const tag = type === "debug" ? ansis.magenta.bold(prefix) : type === "info" ? ansis.cyan.bold(prefix) : type === "warn" ? ansis.yellow.bold(prefix) : ansis.red.bold(prefix);
|
|
289
|
+
const format = `${ansis.dim((/* @__PURE__ */ new Date()).toLocaleTimeString())} ${tag} ${msg}`;
|
|
290
|
+
console[method](format);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
debug(msg, level = defaultLevel) {
|
|
295
|
+
output("debug", msg, level);
|
|
296
|
+
},
|
|
297
|
+
info(msg, level = defaultLevel) {
|
|
298
|
+
output("info", msg, level);
|
|
299
|
+
},
|
|
300
|
+
warn(msg, level = defaultLevel) {
|
|
301
|
+
output("warn", msg, level);
|
|
302
|
+
},
|
|
303
|
+
error(msg, level = defaultLevel) {
|
|
304
|
+
output("error", msg, level);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/node/utils/parseRect.ts
|
|
310
|
+
/**
|
|
311
|
+
* Parse rect size string, add unit if it's a number
|
|
312
|
+
*
|
|
313
|
+
* 解析矩形尺寸字符串,如果是数字则添加单位
|
|
314
|
+
*
|
|
315
|
+
* @param str - Size string / 尺寸字符串
|
|
316
|
+
* @param unit - Unit to append (default: 'px') / 要添加的单位(默认:'px')
|
|
317
|
+
* @returns Size string with unit / 带单位的尺寸字符串
|
|
318
|
+
*/
|
|
319
|
+
function parseRect(str, unit = "px") {
|
|
320
|
+
if (Number.parseFloat(str) === Number(str)) return `${str}${unit}`;
|
|
321
|
+
return str;
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/node/utils/slugify.ts
|
|
325
|
+
const rControl = /[\u0000-\u001F]/g;
|
|
326
|
+
const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'“”‘’<>,.?/]+/g;
|
|
327
|
+
const rCombining = /[\u0300-\u036F]/g;
|
|
328
|
+
/**
|
|
329
|
+
* Default slugification function
|
|
330
|
+
*/
|
|
331
|
+
function slugify(str) {
|
|
332
|
+
return str.normalize("NFKD").replace(rCombining, "").replace(rControl, "").replace(rSpecial, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").replace(/^(\d)/, "_$1").toLowerCase();
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/node/utils/stringify-attrs.ts
|
|
336
|
+
/**
|
|
337
|
+
* Stringify attributes object to HTML attribute string
|
|
338
|
+
*
|
|
339
|
+
* 将属性对象字符串化为 HTML 属性字符串
|
|
340
|
+
*
|
|
341
|
+
* @param attrs - Attributes object / 属性对象
|
|
342
|
+
* @param withUndefinedOrNull - Whether to include undefined/null values / 是否包含 undefined/null 值
|
|
343
|
+
* @param forceStringify - Keys to force stringify / 强制字符串化的键
|
|
344
|
+
* @returns HTML attribute string / HTML 属性字符串
|
|
345
|
+
* @typeParam T - Attribute type / 属性类型
|
|
346
|
+
*/
|
|
347
|
+
function stringifyAttrs(attrs, withUndefinedOrNull = false, forceStringify = []) {
|
|
348
|
+
const result = objectEntries(attrs).map(([key, value]) => {
|
|
349
|
+
const k = kebabCase(key);
|
|
350
|
+
if (isUndefined(value) || value === "undefined") return withUndefinedOrNull ? `:${k}="undefined"` : "";
|
|
351
|
+
if (isNull(value) || value === "null") return withUndefinedOrNull ? `:${k}="null"` : "";
|
|
352
|
+
if (value === "true") value = true;
|
|
353
|
+
if (value === "false") value = false;
|
|
354
|
+
if (isBoolean(value)) return value ? `${k}` : "";
|
|
355
|
+
if (isNumber(value)) return `:${k}="${value}"`;
|
|
356
|
+
if (isString(value) && (value[0] === "{" || value[0] === "[")) {
|
|
357
|
+
const v = value.replaceAll("\"", "'");
|
|
358
|
+
if (forceStringify.includes(key)) return `${k}="${v}"`;
|
|
359
|
+
return `:${k}="${v}"`;
|
|
360
|
+
}
|
|
361
|
+
return `${isString(key) && key[0] === ":" ? ":" : ""}${k}="${String(value)}"`;
|
|
362
|
+
}).filter(Boolean).join(" ");
|
|
363
|
+
return result ? ` ${result}` : "";
|
|
364
|
+
}
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/node/utils/treat-as-html.ts
|
|
367
|
+
const KNOWN_EXTENSIONS = /* @__PURE__ */ new Set();
|
|
368
|
+
function treatAsHtml(filename) {
|
|
369
|
+
if (KNOWN_EXTENSIONS.size === 0) {
|
|
370
|
+
const extraExts = typeof process === "object" && process.env?.VITE_EXTRA_EXTENSIONS || import.meta.env?.VITE_EXTRA_EXTENSIONS || "";
|
|
371
|
+
`3g2,3gp,aac,ai,apng,au,avif,bin,bmp,cer,class,conf,crl,css,csv,dll,doc,eps,epub,exe,gif,gz,ics,ief,jar,jpe,jpeg,jpg,js,json,jsonld,m4a,man,mid,midi,mjs,mov,mp2,mp3,mp4,mpe,mpeg,mpg,mpp,oga,ogg,ogv,ogx,opus,otf,p10,p7c,p7m,p7s,pdf,png,ps,qt,roff,rtf,rtx,ser,svg,t,tif,tiff,tr,ts,tsv,ttf,txt,vtt,wav,weba,webm,webp,woff,woff2,xhtml,xml,yaml,yml,zip${extraExts && typeof extraExts === "string" ? `,${extraExts}` : ""}`.split(",").forEach((ext) => KNOWN_EXTENSIONS.add(ext));
|
|
372
|
+
}
|
|
373
|
+
const ext = filename.split(".").pop();
|
|
374
|
+
return ext == null || !KNOWN_EXTENSIONS.has(ext.toLowerCase());
|
|
375
|
+
}
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region src/node/vitepress/get-vitepress-config.ts
|
|
378
|
+
function getVitepressConfig() {
|
|
379
|
+
const config = globalThis.VITEPRESS_CONFIG;
|
|
380
|
+
if (!config) throw new Error("VITEPRESS_CONFIG is not initialized");
|
|
381
|
+
return config;
|
|
382
|
+
}
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/node/vitepress/createLocales.ts
|
|
385
|
+
/**
|
|
386
|
+
* 创建 locales
|
|
387
|
+
* @param builtinLocales 内置的 locales
|
|
388
|
+
* @param userLocales 用户的 locales
|
|
389
|
+
* @returns locales
|
|
390
|
+
*/
|
|
391
|
+
function createLocales(builtinLocales, userLocales = {}) {
|
|
392
|
+
const locales = {};
|
|
393
|
+
const vitepressLocales = getVitepressConfig().userConfig.locales || {};
|
|
394
|
+
for (const [key, { lang }] of objectEntries(vitepressLocales)) for (const [langs, localeData] of builtinLocales) if (langs.includes(key) || lang && langs.includes(lang)) {
|
|
395
|
+
locales[key] = localeData;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
if (!locales.root) locales.root = builtinLocales[0][1];
|
|
399
|
+
deepMerge(locales, userLocales);
|
|
400
|
+
return locales;
|
|
401
|
+
}
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/node/vitepress/get-locale-with-path.ts
|
|
404
|
+
function getLocaleWithPath(path) {
|
|
405
|
+
const locales = getVitepressConfig().userConfig?.locales || {};
|
|
406
|
+
const keys = objectKeys(locales);
|
|
407
|
+
const key = keys.find((locale) => path.startsWith(locale)) || keys[0] || "";
|
|
408
|
+
if (!key || !locales[key]) return {
|
|
409
|
+
lang: "",
|
|
410
|
+
locale: ""
|
|
411
|
+
};
|
|
412
|
+
return {
|
|
413
|
+
lang: locales[key].lang || key,
|
|
414
|
+
locale: key === "root" ? "" : key
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region src/node/vitepress/resolve-route-link.ts
|
|
419
|
+
const indexRE = /(^|.*\/)index.md(.*)$/i;
|
|
420
|
+
function resolveRouteLink(url, env) {
|
|
421
|
+
if (isExternal(url)) return url;
|
|
422
|
+
const config = getVitepressConfig();
|
|
423
|
+
if (url.startsWith("/")) return slash(config.site.base + url);
|
|
424
|
+
if (url.startsWith("#")) return decodeURI(normalizeHash(url));
|
|
425
|
+
const { pathname, protocol } = new URL(url, "http://a.com");
|
|
426
|
+
if (!url.startsWith("#") && protocol.startsWith("http") && treatAsHtml(pathname)) {
|
|
427
|
+
const indexMatch = url.match(indexRE);
|
|
428
|
+
if (indexMatch) {
|
|
429
|
+
const [, path, hash] = indexMatch;
|
|
430
|
+
url = path + normalizeHash(hash);
|
|
431
|
+
} else {
|
|
432
|
+
let cleanUrl = url.replace(/[?#].*$/, "");
|
|
433
|
+
if (cleanUrl.endsWith(".md")) cleanUrl = cleanUrl.replace(/\.md$/, env.cleanUrls ? "" : ".html");
|
|
434
|
+
if (!env.cleanUrls && !cleanUrl.endsWith(".html") && !cleanUrl.endsWith("/")) cleanUrl += ".html";
|
|
435
|
+
const parsed = new URL(url, "http://a.com");
|
|
436
|
+
url = cleanUrl + parsed.search + normalizeHash(parsed.hash);
|
|
437
|
+
}
|
|
438
|
+
if (!url.startsWith("/") && !url.startsWith("./")) url = `./${url}`;
|
|
439
|
+
}
|
|
440
|
+
return url;
|
|
441
|
+
}
|
|
442
|
+
function normalizeHash(str) {
|
|
443
|
+
return str ? encodeURI(`#${slugify(decodeURI(str).slice(1))}`) : "";
|
|
444
|
+
}
|
|
445
|
+
//#endregion
|
|
446
|
+
export { EXTENSION_AUDIOS, EXTENSION_IMAGES, EXTENSION_VIDEOS, EXTERNAL_URL_RE, URL_PROTOCOL_RE, createContainerPlugin, createContainerSyntaxPlugin, createEmbedRuleBlock, createLocales, createLogger, getLocaleWithPath, getVitepressConfig, isExternal, isLinkWithProtocol, logLevels, parseRect, resolveAttr, resolveAttrs, resolveRouteLink, slugify, stringifyAttrs, treatAsHtml };
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vitepress-plugin-toolkit",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Development toolkit for vitepress plugins",
|
|
6
|
+
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"vitepress",
|
|
10
|
+
"vitepress-plugin",
|
|
11
|
+
"toolkit"
|
|
12
|
+
],
|
|
13
|
+
"sideEffects": false,
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/node/index.d.ts",
|
|
17
|
+
"default": "./dist/node/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./client": {
|
|
20
|
+
"browser": "./dist/client/browser/index.js",
|
|
21
|
+
"default": "./dist/client/ssr/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./styles/*": "./dist/client/styles/*"
|
|
24
|
+
},
|
|
25
|
+
"module": "./dist/node/index.js",
|
|
26
|
+
"types": "./dist/node/index.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"vitepress": "^1.6.4 || ^2.0.0-alpha.17",
|
|
32
|
+
"vue": "^3.5.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@pengzhanbo/utils": "^3.7.3",
|
|
36
|
+
"@vueuse/core": "^14.3.0",
|
|
37
|
+
"ansis": "^4.3.1",
|
|
38
|
+
"markdown-it-container": "^4.0.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"clean": "rimraf --glob ./dist",
|
|
45
|
+
"dev": "pnpm '/(tsdown|copy):watch/'",
|
|
46
|
+
"build": "pnpm tsdown && pnpm copy",
|
|
47
|
+
"copy": "cpx \"src/**/*.css\" dist",
|
|
48
|
+
"copy:watch": "pnpm copy -w",
|
|
49
|
+
"tsdown": "tsdown --config-loader unrun"
|
|
50
|
+
}
|
|
51
|
+
}
|