4435 lines
144 KiB
JavaScript
4435 lines
144 KiB
JavaScript
import { createRequire } from "node:module";
|
|
import { tab } from "@mdit/plugin-tab";
|
|
import { addViteOptimizeDepsInclude, encodeData, ensureEndingSlash, isArray, isLinkAbsolute, isLinkExternal, isLinkHttp, isPlainObject } from "@vuepress/helper";
|
|
import { Buffer } from "node:buffer";
|
|
import http from "node:https";
|
|
import { URL, URLSearchParams } from "node:url";
|
|
import { camelCase, isBoolean, isEmptyObject, isNull, isNumber, isPlainObject as isPlainObject$1, isString, isUndefined, kebabCase, notNullish, omit, toArray, uniqueBy, withTimeout } from "@pengzhanbo/utils";
|
|
import imageSize from "image-size";
|
|
import { colors, fs, getDirname, logger, ora, path } from "vuepress/utils";
|
|
import path$1 from "node:path";
|
|
import { globSync } from "tinyglobby";
|
|
import { isLinkHttp as isLinkHttp$1, isLinkWithProtocol, removeEndingSlash, removeLeadingSlash } from "vuepress/shared";
|
|
import fs$1, { promises } from "node:fs";
|
|
import process from "node:process";
|
|
import container from "markdown-it-container";
|
|
import { isPackageExists, resolveModule } from "local-pkg";
|
|
import { LRUCache } from "lru-cache";
|
|
import { watch } from "chokidar";
|
|
import { customAlphabet } from "nanoid";
|
|
import { removeLeadingSlash as removeLeadingSlash$1 } from "@vuepress/shared";
|
|
import { path as path$2 } from "@vuepress/utils";
|
|
import { attrs } from "@mdit/plugin-attrs";
|
|
import { footnote } from "@mdit/plugin-footnote";
|
|
import { mark } from "@mdit/plugin-mark";
|
|
import { sub } from "@mdit/plugin-sub";
|
|
import { sup } from "@mdit/plugin-sup";
|
|
import { tasklist } from "@mdit/plugin-tasklist";
|
|
|
|
//#region src/node/fileIcons/definitions.ts
|
|
const defaultFolder = "vscode-icons:default-folder";
|
|
const defaultFile = "vscode-icons:default-file";
|
|
const definitions = {
|
|
named: {
|
|
"pnpm": "vscode-icons:file-type-light-pnpm",
|
|
"PNPM": "vscode-icons:file-type-light-pnpm",
|
|
"npm": "logos:npm-icon",
|
|
"NPM": "logos:npm-icon",
|
|
"yarn": "vscode-icons:file-type-yarn",
|
|
"Yarn": "vscode-icons:file-type-yarn",
|
|
"bun": "vscode-icons:file-type-bun",
|
|
"Bun": "vscode-icons:file-type-bun",
|
|
"deno": "vscode-icons:file-type-light-deno",
|
|
"Deno": "vscode-icons:file-type-light-deno",
|
|
"rollup": "vscode-icons:file-type-rollup",
|
|
"Rollup": "vscode-icons:file-type-rollup",
|
|
"webpack": "vscode-icons:file-type-webpack",
|
|
"Webpack": "vscode-icons:file-type-webpack",
|
|
"vite": "vscode-icons:file-type-vite",
|
|
"Vite": "vscode-icons:file-type-vite",
|
|
"esbuild": "vscode-icons:file-type-esbuild",
|
|
"Esbuild": "vscode-icons:file-type-esbuild",
|
|
"vue": "vscode-icons:file-type-vue",
|
|
"Vue": "vscode-icons:file-type-vue",
|
|
"svelte": "vscode-icons:file-type-svelte",
|
|
"Svelte": "vscode-icons:file-type-svelte",
|
|
"sveltekit": "vscode-icons:file-type-svelte",
|
|
"SvelteKit": "vscode-icons:file-type-svelte",
|
|
"angular": "vscode-icons:file-type-angular",
|
|
"Angular": "vscode-icons:file-type-angular",
|
|
"react": "vscode-icons:file-type-reactjs",
|
|
"React": "vscode-icons:file-type-reactjs",
|
|
"next": "vscode-icons:file-type-light-next",
|
|
"Next": "vscode-icons:file-type-light-next",
|
|
"Nextjs": "vscode-icons:file-type-light-next",
|
|
"NextJS": "vscode-icons:file-type-light-next",
|
|
"nuxt": "vscode-icons:file-type-nuxt",
|
|
"Nuxt": "vscode-icons:file-type-nuxt",
|
|
"Nuxtjs": "vscode-icons:file-type-nuxt",
|
|
"NuxtJS": "vscode-icons:file-type-nuxt",
|
|
"solid": "logos:solidjs-icon",
|
|
"Solid": "logos:solidjs-icon",
|
|
"solidjs": "logos:solidjs-icon",
|
|
"astro": "vscode-icons:file-type-light-astro",
|
|
"Astro": "vscode-icons:file-type-light-astro",
|
|
"vitest": "vscode-icons:file-type-vitest",
|
|
"Vitest": "vscode-icons:file-type-vitest",
|
|
"playwright": "vscode-icons:file-type-playwright",
|
|
"Playwright": "vscode-icons:file-type-playwright",
|
|
"jest": "vscode-icons:file-type-jest",
|
|
"Jest": "vscode-icons:file-type-jest",
|
|
"cypress": "vscode-icons:file-type-cypress",
|
|
"Cypress": "vscode-icons:file-type-cypress",
|
|
"docker": "vscode-icons:file-type-docker",
|
|
"Docker": "vscode-icons:file-type-docker",
|
|
"html": "vscode-icons:file-type-html",
|
|
"Html": "vscode-icons:file-type-html",
|
|
"HTML": "vscode-icons:file-type-html",
|
|
"javascript": "vscode-icons:file-type-js-official",
|
|
"Javascript": "vscode-icons:file-type-js-official",
|
|
"JavaScript": "vscode-icons:file-type-js-official",
|
|
"js": "vscode-icons:file-type-js-official",
|
|
"JS": "vscode-icons:file-type-js-official",
|
|
"typescript": "vscode-icons:file-type-typescript-official",
|
|
"Typescript": "vscode-icons:file-type-typescript-official",
|
|
"TypeScript": "vscode-icons:file-type-typescript-official",
|
|
"ts": "vscode-icons:file-type-typescript-official",
|
|
"TS": "vscode-icons:file-type-typescript-official",
|
|
"css": "vscode-icons:file-type-css",
|
|
"CSS": "vscode-icons:file-type-css",
|
|
"less": "vscode-icons:file-type-less",
|
|
"Less": "vscode-icons:file-type-less",
|
|
"LESS": "vscode-icons:file-type-less",
|
|
"scss": "vscode-icons:file-type-scss",
|
|
"Scss": "vscode-icons:file-type-scss",
|
|
"SCSS": "vscode-icons:file-type-scss",
|
|
"sass": "vscode-icons:file-type-sass",
|
|
"Sass": "vscode-icons:file-type-sass",
|
|
"SASS": "vscode-icons:file-type-sass",
|
|
"stylus": "vscode-icons:file-type-light-stylus",
|
|
"Stylus": "vscode-icons:file-type-light-stylus",
|
|
"postcss": "vscode-icons:file-type-postcss",
|
|
"Postcss": "vscode-icons:file-type-postcss",
|
|
"PostCSS": "vscode-icons:file-type-postcss",
|
|
"sh": "vscode-icons:file-type-shell",
|
|
"shell": "vscode-icons:file-type-shell",
|
|
"Shell": "vscode-icons:file-type-shell",
|
|
"bash": "vscode-icons:file-type-shell",
|
|
"Bash": "vscode-icons:file-type-shell",
|
|
"java": "vscode-icons:file-type-java",
|
|
"Java": "vscode-icons:file-type-java",
|
|
"JAVA": "vscode-icons:file-type-java",
|
|
"php": "vscode-icons:file-type-php3",
|
|
"PHP": "vscode-icons:file-type-php3",
|
|
"c": "vscode-icons:file-type-c",
|
|
"C": "vscode-icons:file-type-c",
|
|
"python": "vscode-icons:file-type-python",
|
|
"Python": "vscode-icons:file-type-python",
|
|
"kotlin": "vscode-icons:file-type-kotlin",
|
|
"Kotlin": "vscode-icons:file-type-kotlin",
|
|
"go": "vscode-icons:file-type-go-gopher",
|
|
"golang": "vscode-icons:file-type-go-gopher",
|
|
"Go": "vscode-icons:file-type-go-gopher",
|
|
"GoLang": "vscode-icons:file-type-go-gopher",
|
|
"rust": "vscode-icons:file-type-rust",
|
|
"Rust": "vscode-icons:file-type-rust",
|
|
"zig": "vscode-icons:file-type-zig",
|
|
"Zig": "vscode-icons:file-type-zig",
|
|
"swift": "vscode-icons:file-type-swift",
|
|
"Swift": "vscode-icons:file-type-swift",
|
|
"c#": "vscode-icons:file-type-csharp",
|
|
"CSharp": "vscode-icons:file-type-csharp",
|
|
"c++": "vscode-icons:file-type-cpp",
|
|
"C++": "vscode-icons:file-type-cpp",
|
|
"ruby": "vscode-icons:file-type-ruby",
|
|
"Ruby": "vscode-icons:file-type-ruby",
|
|
"makefile": "vscode-icons:file-type-makefile",
|
|
"Makefile": "vscode-icons:file-type-makefile",
|
|
"Object-C": "vscode-icons:file-type-objectivec",
|
|
"object-c": "vscode-icons:file-type-objectivec",
|
|
"SQL": "vscode-icons:file-type-sql",
|
|
"sql": "vscode-icons:file-type-sql",
|
|
"mysql": "vscode-icons:file-type-mysql",
|
|
"MySQL": "vscode-icons:file-type-mysql",
|
|
"MySql": "vscode-icons:file-type-mysql",
|
|
"pgsql": "vscode-icons:file-type-pgsql",
|
|
"PostgreSQL": "vscode-icons:file-type-pgsql",
|
|
"PGSQL": "vscode-icons:file-type-pgsql",
|
|
"xml": "vscode-icons:file-type-xml",
|
|
"XML": "vscode-icons:file-type-xml",
|
|
"wasm": "vscode-icons:file-type-wasm",
|
|
"webassembly": "vscode-icons:file-type-wasm",
|
|
"WebAssembly": "vscode-icons:file-type-wasm",
|
|
"WASM": "vscode-icons:file-type-wasm",
|
|
"toml": "vscode-icons:file-type-light-toml",
|
|
"Toml": "vscode-icons:file-type-light-toml",
|
|
"TOML": "vscode-icons:file-type-light-toml",
|
|
"yaml": "vscode-icons:file-type-light-yaml",
|
|
"Yaml": "vscode-icons:file-type-light-yaml",
|
|
"YAML": "vscode-icons:file-type-light-yaml"
|
|
},
|
|
folders: {
|
|
"default": "vscode-icons:default-folder",
|
|
"src": "vscode-icons:folder-type-src",
|
|
"srcs": "vscode-icons:folder-type-src",
|
|
"source": "vscode-icons:folder-type-src",
|
|
"sources": "vscode-icons:folder-type-src",
|
|
"code": "vscode-icons:folder-type-src",
|
|
"tauri-src": "vscode-icons:folder-type-tauri",
|
|
"dist": "vscode-icons:folder-type-dist",
|
|
"out": "vscode-icons:folder-type-dist",
|
|
"output": "vscode-icons:folder-type-dist",
|
|
"release": "vscode-icons:folder-type-dist",
|
|
"bin": "vscode-icons:folder-type-dist",
|
|
"distribution": "vscode-icons:folder-type-dist",
|
|
"docs": "vscode-icons:folder-type-docs",
|
|
"doc": "vscode-icons:folder-type-docs",
|
|
"document": "vscode-icons:folder-type-docs",
|
|
"documents": "vscode-icons:folder-type-docs",
|
|
"documentation": "vscode-icons:folder-type-docs",
|
|
"post": "vscode-icons:folder-type-docs",
|
|
"posts": "vscode-icons:folder-type-docs",
|
|
"article": "vscode-icons:folder-type-docs",
|
|
"articles": "vscode-icons:folder-type-docs",
|
|
"scripts": "vscode-icons:folder-type-script",
|
|
"script": "vscode-icons:folder-type-script",
|
|
"node_modules": "vscode-icons:folder-type-light-node",
|
|
"cli": "vscode-icons:folder-type-cli",
|
|
"template": "vscode-icons:folder-type-template",
|
|
"templates": "vscode-icons:folder-type-template",
|
|
"theme": "vscode-icons:folder-type-theme",
|
|
"themes": "vscode-icons:folder-type-theme",
|
|
"color": "vscode-icons:folder-type-theme",
|
|
"colors": "vscode-icons:folder-type-theme",
|
|
"design": "vscode-icons:folder-type-theme",
|
|
"designs": "vscode-icons:folder-type-theme",
|
|
"packages": "vscode-icons:folder-type-package",
|
|
"package": "vscode-icons:folder-type-package",
|
|
"pkg": "vscode-icons:folder-type-package",
|
|
"pkgs": "vscode-icons:folder-type-package",
|
|
"shared": "vscode-icons:folder-type-shared",
|
|
"utils": "vscode-icons:folder-type-tools",
|
|
"util": "vscode-icons:folder-type-tools",
|
|
"utility": "vscode-icons:folder-type-tools",
|
|
"utilities": "vscode-icons:folder-type-tools",
|
|
"helper": "vscode-icons:folder-type-helper",
|
|
"helpers": "vscode-icons:folder-type-helper",
|
|
"tools": "vscode-icons:folder-type-tools",
|
|
"toolkit": "vscode-icons:folder-type-tools",
|
|
"toolkits": "vscode-icons:folder-type-tools",
|
|
"tooling": "vscode-icons:folder-type-tools",
|
|
"devtools": "vscode-icons:folder-type-tools",
|
|
"component": "vscode-icons:folder-type-component",
|
|
"components": "vscode-icons:folder-type-component",
|
|
"Component": "vscode-icons:folder-type-component",
|
|
"Components": "vscode-icons:folder-type-component",
|
|
"widget": "vscode-icons:folder-type-component",
|
|
"widgets": "vscode-icons:folder-type-component",
|
|
"fragments": "vscode-icons:folder-type-component",
|
|
"hooks": "vscode-icons:folder-type-hook",
|
|
"composables": "vscode-icons:folder-type-hook",
|
|
"public": "vscode-icons:folder-type-public",
|
|
"www": "vscode-icons:folder-type-public",
|
|
"web": "vscode-icons:folder-type-public",
|
|
"wwwroot": "vscode-icons:folder-type-public",
|
|
"website": "vscode-icons:folder-type-public",
|
|
"site": "vscode-icons:folder-type-public",
|
|
"browser": "vscode-icons:folder-type-public",
|
|
"browsers": "vscode-icons:folder-type-public",
|
|
"fonts": "vscode-icons:folder-type-fonts",
|
|
"font": "vscode-icons:folder-type-fonts",
|
|
"images": "vscode-icons:folder-type-images",
|
|
"image": "vscode-icons:folder-type-images",
|
|
"imgs": "vscode-icons:folder-type-images",
|
|
"img": "vscode-icons:folder-type-images",
|
|
"icon": "vscode-icons:folder-type-images",
|
|
"icons": "vscode-icons:folder-type-images",
|
|
"ico": "vscode-icons:folder-type-images",
|
|
"icos": "vscode-icons:folder-type-images",
|
|
"figure": "vscode-icons:folder-type-images",
|
|
"figures": "vscode-icons:folder-type-images",
|
|
"fig": "vscode-icons:folder-type-images",
|
|
"figs": "vscode-icons:folder-type-images",
|
|
"screenshot": "vscode-icons:folder-type-images",
|
|
"screenshots": "vscode-icons:folder-type-images",
|
|
"screengrab": "vscode-icons:folder-type-images",
|
|
"screengrabs": "vscode-icons:folder-type-images",
|
|
"pic": "vscode-icons:folder-type-images",
|
|
"pics": "vscode-icons:folder-type-images",
|
|
"picture": "vscode-icons:folder-type-images",
|
|
"pictures": "vscode-icons:folder-type-images",
|
|
"photo": "vscode-icons:folder-type-images",
|
|
"photos": "vscode-icons:folder-type-images",
|
|
"photograph": "vscode-icons:folder-type-images",
|
|
"photographs": "vscode-icons:folder-type-images",
|
|
"asset": "vscode-icons:folder-type-asset",
|
|
"assets": "vscode-icons:folder-type-asset",
|
|
"resource": "vscode-icons:folder-type-asset",
|
|
"resources": "vscode-icons:folder-type-asset",
|
|
"res": "vscode-icons:folder-type-asset",
|
|
"static": "vscode-icons:folder-type-asset",
|
|
"report": "vscode-icons:folder-type-asset",
|
|
"reports": "vscode-icons:folder-type-asset",
|
|
"apis": "vscode-icons:folder-type-api",
|
|
"api": "vscode-icons:folder-type-api",
|
|
"restapi": "vscode-icons:folder-type-api",
|
|
"style": "vscode-icons:folder-type-style",
|
|
"styles": "vscode-icons:folder-type-style",
|
|
"stylesheet": "vscode-icons:folder-type-style",
|
|
"stylesheets": "vscode-icons:folder-type-style",
|
|
"css": "vscode-icons:folder-type-css",
|
|
"scss": "vscode-icons:folder-type-light-sass",
|
|
"sass": "vscode-icons:folder-type-light-sass",
|
|
"less": "vscode-icons:folder-type-less",
|
|
"plugin": "vscode-icons:folder-type-plugin",
|
|
"plugins": "vscode-icons:folder-type-plugin",
|
|
"typings": "vscode-icons:folder-type-typings",
|
|
"types": "vscode-icons:folder-type-typings",
|
|
"mock": "vscode-icons:folder-type-mock",
|
|
"i18n": "vscode-icons:folder-type-locale",
|
|
"locales": "vscode-icons:folder-type-locale",
|
|
"locale": "vscode-icons:folder-type-locale",
|
|
"lang": "vscode-icons:folder-type-locale",
|
|
"langs": "vscode-icons:folder-type-locale",
|
|
"language": "vscode-icons:folder-type-locale",
|
|
"languages": "vscode-icons:folder-type-locale",
|
|
"l10n": "vscode-icons:folder-type-locale",
|
|
"localization": "vscode-icons:folder-type-locale",
|
|
"translation": "vscode-icons:folder-type-locale",
|
|
"translate": "vscode-icons:folder-type-locale",
|
|
"translations": "vscode-icons:folder-type-locale",
|
|
"tx": "vscode-icons:folder-type-locale",
|
|
"config": "vscode-icons:folder-type-config",
|
|
"configs": "vscode-icons:folder-type-config",
|
|
".config": "vscode-icons:folder-type-config",
|
|
".configs": "vscode-icons:folder-type-config",
|
|
"cfg": "vscode-icons:folder-type-config",
|
|
"cfgs": "vscode-icons:folder-type-config",
|
|
"conf": "vscode-icons:folder-type-config",
|
|
"confs": "vscode-icons:folder-type-config",
|
|
"configuration": "vscode-icons:folder-type-config",
|
|
"configurations": "vscode-icons:folder-type-config",
|
|
"setting": "vscode-icons:folder-type-config",
|
|
"settings": "vscode-icons:folder-type-config",
|
|
"option": "vscode-icons:folder-type-config",
|
|
"options": "vscode-icons:folder-type-config",
|
|
"controller": "vscode-icons:folder-type-controller",
|
|
"controllers": "vscode-icons:folder-type-controller",
|
|
"Controller": "vscode-icons:folder-type-controller",
|
|
"Controllers": "vscode-icons:folder-type-controller",
|
|
"model": "vscode-icons:folder-type-model",
|
|
"models": "vscode-icons:folder-type-model",
|
|
"Model": "vscode-icons:folder-type-model",
|
|
"Models": "vscode-icons:folder-type-model",
|
|
"service": "vscode-icons:folder-type-services",
|
|
"services": "vscode-icons:folder-type-services",
|
|
"Service": "vscode-icons:folder-type-services",
|
|
"Services": "vscode-icons:folder-type-services",
|
|
"view": "vscode-icons:folder-type-view",
|
|
"views": "vscode-icons:folder-type-view",
|
|
"View": "vscode-icons:folder-type-view",
|
|
"Views": "vscode-icons:folder-type-view",
|
|
"page": "vscode-icons:folder-type-view",
|
|
"pages": "vscode-icons:folder-type-view",
|
|
"html": "vscode-icons:folder-type-view",
|
|
"app": "vscode-icons:folder-type-app",
|
|
"apps": "vscode-icons:folder-type-app",
|
|
"client": "vscode-icons:folder-type-client",
|
|
"clients": "vscode-icons:folder-type-client",
|
|
"frontend": "vscode-icons:folder-type-client",
|
|
"frontends": "vscode-icons:folder-type-client",
|
|
"server": "vscode-icons:folder-type-server",
|
|
"servers": "vscode-icons:folder-type-server",
|
|
"backend": "vscode-icons:folder-type-server",
|
|
"backends": "vscode-icons:folder-type-server",
|
|
"db": "vscode-icons:folder-type-db",
|
|
"database": "vscode-icons:folder-type-db",
|
|
"databases": "vscode-icons:folder-type-db",
|
|
"data": "vscode-icons:folder-type-db",
|
|
"sql": "vscode-icons:folder-type-db",
|
|
"e2e": "vscode-icons:folder-type-e2e",
|
|
"cypress": "vscode-icons:folder-type-light-cypress",
|
|
"test": "vscode-icons:folder-type-test",
|
|
"tests": "vscode-icons:folder-type-test",
|
|
"testing": "vscode-icons:folder-type-test",
|
|
"snapshots": "vscode-icons:folder-type-test",
|
|
"spec": "vscode-icons:folder-type-test",
|
|
"specs": "vscode-icons:folder-type-test",
|
|
"lib": "vscode-icons:folder-type-library",
|
|
"libs": "vscode-icons:folder-type-library",
|
|
"library": "vscode-icons:folder-type-library",
|
|
"libraries": "vscode-icons:folder-type-library",
|
|
"vendor": "vscode-icons:folder-type-library",
|
|
"vendors": "vscode-icons:folder-type-library",
|
|
"third-party": "vscode-icons:folder-type-library",
|
|
"lib64": "vscode-icons:folder-type-library",
|
|
"include": "vscode-icons:folder-type-include",
|
|
"inc": "vscode-icons:folder-type-include",
|
|
"includes": "vscode-icons:folder-type-include",
|
|
"partial": "vscode-icons:folder-type-include",
|
|
"partials": "vscode-icons:folder-type-include",
|
|
"inc64": "vscode-icons:folder-type-include",
|
|
"temp": "vscode-icons:folder-type-temp",
|
|
"tmp": "vscode-icons:folder-type-temp",
|
|
"cache": "vscode-icons:folder-type-temp",
|
|
"cached": "vscode-icons:folder-type-temp",
|
|
".temp": "vscode-icons:folder-type-temp",
|
|
".cache": "vscode-icons:folder-type-temp",
|
|
"log": "vscode-icons:folder-type-log",
|
|
"logs": "vscode-icons:folder-type-log",
|
|
"logging": "vscode-icons:folder-type-log",
|
|
".git": "vscode-icons:folder-type-git",
|
|
".github": "vscode-icons:folder-type-github",
|
|
".gitlab": "vscode-icons:folder-type-gitlab",
|
|
".vscode": "vscode-icons:folder-type-vscode",
|
|
".husky": "vscode-icons:folder-type-husky",
|
|
".idea": "vscode-icons:folder-type-idea"
|
|
},
|
|
files: {
|
|
"package.json": "vscode-icons:file-type-node",
|
|
"pnpm-debug.log": "vscode-icons:file-type-light-pnpm",
|
|
"pnpm-lock.yaml": "vscode-icons:file-type-light-pnpm",
|
|
"pnpm-workspace.yaml": "vscode-icons:file-type-light-pnpm",
|
|
"biome.json": "vscode-icons:file-type-biome",
|
|
"bun.lockb": "vscode-icons:file-type-bun",
|
|
"COMMIT_EDITMSG": "vscode-icons:file-type-git",
|
|
"MERGE_MSG": "vscode-icons:file-type-git",
|
|
"karma.conf.js": "vscode-icons:file-type-karma",
|
|
"karma.conf.cjs": "vscode-icons:file-type-karma",
|
|
"karma.conf.mjs": "vscode-icons:file-type-karma",
|
|
"karma.conf.coffee": "vscode-icons:file-type-karma",
|
|
"README.md": "flat-color-icons:info",
|
|
"README.txt": "flat-color-icons:info",
|
|
"README": "flat-color-icons:info",
|
|
"CHANGELOG.md": "catppuccin:changelog",
|
|
"CHANGELOG.txt": "catppuccin:changelog",
|
|
"CHANGELOG": "catppuccin:changelog",
|
|
"CHANGES.md": "catppuccin:changelog",
|
|
"CHANGES.txt": "catppuccin:changelog",
|
|
"CHANGES": "catppuccin:changelog",
|
|
"VERSION.md": "catppuccin:changelog",
|
|
"VERSION.txt": "catppuccin:changelog",
|
|
"VERSION": "catppuccin:changelog",
|
|
"mvnw": "vscode-icons:file-type-maven",
|
|
"pom.xml": "vscode-icons:file-type-maven",
|
|
"tsconfig.json": "vscode-icons:file-type-tsconfig",
|
|
"swagger.json": "vscode-icons:file-type-swagger",
|
|
"swagger.yml": "vscode-icons:file-type-swagger",
|
|
"swagger.yaml": "vscode-icons:file-type-swagger",
|
|
"mime.types": "vscode-icons:file-type-light-config",
|
|
"Jenkinsfile": "vscode-icons:file-type-jenkins",
|
|
"babel.config.js": "vscode-icons:file-type-babel2",
|
|
"babel.config.json": "vscode-icons:file-type-babel2",
|
|
"babel.config.cjs": "vscode-icons:file-type-babel2",
|
|
"BUILD": "vscode-icons:file-type-bazel",
|
|
"BUILD.bazel": "vscode-icons:file-type-bazel",
|
|
"WORKSPACE": "vscode-icons:file-type-bazel",
|
|
"WORKSPACE.bazel": "vscode-icons:file-type-bazel",
|
|
"bower.json": "vscode-icons:file-type-bower2",
|
|
"Bower.json": "vscode-icons:file-type-bower2",
|
|
"eslint.config.js": "vscode-icons:file-type-eslint",
|
|
"eslint.config.ts": "vscode-icons:file-type-eslint",
|
|
"firebase.json": "vscode-icons:file-type-firebase",
|
|
"geckodriver": "openmoji:firefox",
|
|
"Gruntfile.js": "vscode-icons:file-type-grunt",
|
|
"gruntfile.babel.js": "vscode-icons:file-type-grunt",
|
|
"Gruntfile.babel.js": "vscode-icons:file-type-grunt",
|
|
"gruntfile.js": "vscode-icons:file-type-grunt",
|
|
"Gruntfile.coffee": "vscode-icons:file-type-grunt",
|
|
"gruntfile.coffee": "vscode-icons:file-type-grunt",
|
|
"ionic.config.json": "vscode-icons:file-type-ionic",
|
|
"Ionic.config.json": "vscode-icons:file-type-ionic",
|
|
"ionic.project": "vscode-icons:file-type-ionic",
|
|
"Ionic.project": "vscode-icons:file-type-ionic",
|
|
"platformio.ini": "vscode-icons:file-type-platformio",
|
|
"rollup.config.js": "vscode-icons:file-type-rollup",
|
|
"sass-lint.yml": "vscode-icons:file-type-sass",
|
|
"stylelint.config.js": "vscode-icons:file-type-light-stylelint",
|
|
"stylelint.config.cjs": "vscode-icons:file-type-light-stylelint",
|
|
"stylelint.config.mjs": "vscode-icons:file-type-light-stylelint",
|
|
"yarn.clean": "vscode-icons:file-type-yarn",
|
|
"yarn.lock": "vscode-icons:file-type-yarn",
|
|
"webpack.config.js": "vscode-icons:file-type-webpack",
|
|
"webpack.config.cjs": "vscode-icons:file-type-webpack",
|
|
"webpack.config.mjs": "vscode-icons:file-type-webpack",
|
|
"webpack.config.ts": "vscode-icons:file-type-webpack",
|
|
"webpack.config.build.js": "vscode-icons:file-type-webpack",
|
|
"webpack.config.build.cjs": "vscode-icons:file-type-webpack",
|
|
"webpack.config.build.mjs": "vscode-icons:file-type-webpack",
|
|
"webpack.config.build.ts": "vscode-icons:file-type-webpack",
|
|
"webpack.common.js": "vscode-icons:file-type-webpack",
|
|
"webpack.common.cjs": "vscode-icons:file-type-webpack",
|
|
"webpack.common.mjs": "vscode-icons:file-type-webpack",
|
|
"webpack.common.ts": "vscode-icons:file-type-webpack",
|
|
"webpack.dev.js": "vscode-icons:file-type-webpack",
|
|
"webpack.dev.cjs": "vscode-icons:file-type-webpack",
|
|
"webpack.dev.mjs": "vscode-icons:file-type-webpack",
|
|
"webpack.dev.ts": "vscode-icons:file-type-webpack",
|
|
"webpack.prod.js": "vscode-icons:file-type-webpack",
|
|
"webpack.prod.cjs": "vscode-icons:file-type-webpack",
|
|
"webpack.prod.mjs": "vscode-icons:file-type-webpack",
|
|
"webpack.prod.ts": "vscode-icons:file-type-webpack",
|
|
"vite.config.js": "vscode-icons:file-type-vite",
|
|
"vite.config.ts": "vscode-icons:file-type-vite",
|
|
"vite.config.mjs": "vscode-icons:file-type-vite",
|
|
"vite.config.cjs": "vscode-icons:file-type-vite",
|
|
"vitest.config.ts": "vscode-icons:file-type-vitest",
|
|
"vitest.config.js": "vscode-icons:file-type-vitest",
|
|
"vitest.config.mjs": "vscode-icons:file-type-vitest",
|
|
"vitest.config.cjs": "vscode-icons:file-type-vitest",
|
|
"turbo.json": "vscode-icons:file-type-light-turbo",
|
|
"vercel.json": "vscode-icons:file-type-light-vercel",
|
|
"netlify.toml": "vscode-icons:file-type-light-netlify",
|
|
"Cargo.toml": "vscode-icons:file-type-cargo",
|
|
"Cargo.lock": "vscode-icons:file-type-cargo",
|
|
"npm-debug.log": "vscode-icons:file-type-npm"
|
|
},
|
|
extensions: {
|
|
".astro": "vscode-icons:file-type-light-astro",
|
|
".mdx": "vscode-icons:file-type-light-mdx",
|
|
".cls": "vscode-icons:file-type-apex",
|
|
".apex": "vscode-icons:file-type-apex",
|
|
".asm": "vscode-icons:file-type-assembly",
|
|
".s": "vscode-icons:file-type-assembly",
|
|
".bicep": "vscode-icons:file-type-bicep",
|
|
".bzl": "vscode-icons:file-type-bazel",
|
|
".bazel": "vscode-icons:file-type-bazel",
|
|
".BUILD": "vscode-icons:file-type-bazel",
|
|
".WORKSPACE": "vscode-icons:file-type-bazel",
|
|
".bazelignore": "vscode-icons:file-type-bazel",
|
|
".bazelversion": "vscode-icons:file-type-bazel",
|
|
".c": "vscode-icons:file-type-c",
|
|
".h": "vscode-icons:file-type-c",
|
|
".m": "vscode-icons:file-type-c",
|
|
".cs": "vscode-icons:file-type-csharp",
|
|
".cshtml": "vscode-icons:file-type-html",
|
|
".aspx": "vscode-icons:file-type-aspx",
|
|
".ascx": "vscode-icons:file-type-aspx",
|
|
".asax": "vvscode-icons:file-type-aspx",
|
|
".master": "vscode-icons:file-type-html",
|
|
".cc": "vscode-icons:file-type-cpp",
|
|
".cpp": "vscode-icons:file-type-cpp",
|
|
".cxx": "vscode-icons:file-type-cpp",
|
|
".c++": "vscode-icons:file-type-cpp",
|
|
".hh": "vscode-icons:file-type-cppheader",
|
|
".hpp": "vscode-icons:file-type-cppheader",
|
|
".hxx": "vscode-icons:file-type-cppheader",
|
|
".h++": "vscode-icons:file-type-cppheader",
|
|
".mm": "vscode-icons:file-type-cpp",
|
|
".clj": "vscode-icons:file-type-clojure",
|
|
".cljs": "vscode-icons:file-type-clojure",
|
|
".cljc": "vscode-icons:file-type-clojure",
|
|
".edn": "vscode-icons:file-type-clojure",
|
|
".cfc": "vscode-icons:file-type-cfc",
|
|
".cfm": "vscode-icons:file-type-cfm",
|
|
".coffee": "vscode-icons:file-type-coffeescript",
|
|
".litcoffee": "vscode-icons:file-type-coffeescript",
|
|
".config": "vscode-icons:file-type-config",
|
|
".cfg": "vscode-icons:file-type-config",
|
|
".conf": "vscode-icons:file-type-config",
|
|
".cr": "vscode-icons:file-type-light-crystal",
|
|
".ecr": "vscode-icons:file-type-light-crystal",
|
|
".slang": "vscode-icons:file-type-slang",
|
|
".cson": "vscode-icons:file-type-json",
|
|
".css": "vscode-icons:file-type-css",
|
|
".css.map": "vscode-icons:file-type-cssmap",
|
|
".sss": "vscode-icons:file-type-sss",
|
|
".csv": "vscode-icons:file-type-excel",
|
|
".xls": "vscode-icons:file-type-excel2",
|
|
".xlsx": "vscode-icons:file-type-excel2",
|
|
".cu": "vscode-icons:file-type-cuda",
|
|
".cuh": "vscode-icons:file-type-cuda",
|
|
".hu": "vscode-icons:file-type-cuda",
|
|
".cake": "vscode-icons:file-type-cake",
|
|
".ctp": "vscode-icons:file-type-cakephp",
|
|
".d": "vscode-icons:file-type-dependencies",
|
|
".doc": "vscode-icons:file-type-word2",
|
|
".docx": "vscode-icons:file-type-word2",
|
|
".ejs": "vscode-icons:file-type-ejs",
|
|
".ex": "vscode-icons:file-type-elixir",
|
|
".exs": "vscode-icons:file-type-elixir",
|
|
".elm": "vscode-icons:file-type-elm",
|
|
".ico": "vscode-icons:file-type-favicon",
|
|
".fs": "vscode-icons:file-type-fsharp",
|
|
".fsx": "vscode-icons:file-type-fsharp",
|
|
".gitignore": "vscode-icons:file-type-git",
|
|
".gitconfig": "vscode-icons:file-type-git",
|
|
".gitkeep": "vscode-icons:file-type-git",
|
|
".gitattributes": "vscode-icons:file-type-git",
|
|
".gitmodules": "vscode-icons:file-type-git",
|
|
".go": "vscode-icons:file-type-go",
|
|
".slide": "vscode-icons:file-type-go",
|
|
".article": "vscode-icons:file-type-go",
|
|
".gd": "vscode-icons:file-type-godot",
|
|
".godot": "vscode-icons:file-type-godot",
|
|
".tres": "vscode-icons:file-type-godot",
|
|
".tscn": "vscode-icons:file-type-godot",
|
|
".gradle": "vscode-icons:file-type-light-gradle",
|
|
".groovy": "vscode-icons:file-type-groovy",
|
|
".gsp": "vscode-icons:file-type-groovy",
|
|
".gql": "vscode-icons:file-type-graphql",
|
|
".graphql": "vscode-icons:file-type-graphql",
|
|
".graphqls": "vscode-icons:file-type-graphql",
|
|
".hack": "logos:hack",
|
|
".haml": "vscode-icons:file-type-haml",
|
|
".handlebars": "vscode-icons:file-type-handlebars",
|
|
".hbs": "vscode-icons:file-type-handlebars",
|
|
".hjs": "vscode-icons:file-type-handlebars",
|
|
".hs": "vscode-icons:file-type-haskell",
|
|
".lhs": "vscode-icons:file-type-haskell",
|
|
".hx": "vscode-icons:file-type-haxe",
|
|
".hxs": "vscode-icons:file-type-haxe",
|
|
".hxp": "vscode-icons:file-type-haxe",
|
|
".hxml": "vscode-icons:file-type-haxe",
|
|
".html": "vscode-icons:file-type-html",
|
|
".jade": "file-icons:jade",
|
|
".java": "vscode-icons:file-type-java",
|
|
".class": "vscode-icons:file-type-java",
|
|
".classpath": "vscode-icons:file-type-java",
|
|
".properties": "vscode-icons:file-type-java",
|
|
".js": "vscode-icons:file-type-js",
|
|
".js.map": "vscode-icons:file-type-jsmap",
|
|
".cjs": "vscode-icons:file-type-js",
|
|
".cjs.map": "vscode-icons:file-type-jsmap",
|
|
".mjs": "vscode-icons:file-type-js",
|
|
".mjs.map": "vscode-icons:file-type-jsmap",
|
|
".spec.js": "vscode-icons:file-type-light-testjs",
|
|
".spec.cjs": "vscode-icons:file-type-light-testjs",
|
|
".spec.mjs": "vscode-icons:file-type-light-testjs",
|
|
".test.js": "vscode-icons:file-type-light-testjs",
|
|
".test.cjs": "vscode-icons:file-type-light-testjs",
|
|
".test.mjs": "vscode-icons:file-type-light-testjs",
|
|
".es": "vscode-icons:file-type-js",
|
|
".es5": "vscode-icons:file-type-js",
|
|
".es6": "vscode-icons:file-type-js",
|
|
".es7": "vscode-icons:file-type-js",
|
|
".jinja": "vscode-icons:file-type-jinja",
|
|
".jinja2": "vscode-icons:file-type-jinja",
|
|
".json": "vscode-icons:file-type-json",
|
|
".jl": "vscode-icons:file-type-julia",
|
|
".kt": "vscode-icons:file-type-kotlin",
|
|
".kts": "vscode-icons:file-type-kotlin",
|
|
".dart": "vscode-icons:file-type-dartlang",
|
|
".less": "vscode-icons:file-type-less",
|
|
".liquid": "vscode-icons:file-type-liquid",
|
|
".ls": "vscode-icons:file-type-livescript",
|
|
".lua": "vscode-icons:file-type-lua",
|
|
".markdown": "vscode-icons:file-type-markdown",
|
|
".md": "vscode-icons:file-type-markdown",
|
|
".mustache": "vscode-icons:file-type-light-mustache",
|
|
".stache": "vscode-icons:file-type-light-mustache",
|
|
".nim": "vscode-icons:file-type-light-nim",
|
|
".nims": "vscode-icons:file-type-light-nim",
|
|
".github-issues": "mdi:github",
|
|
".ipynb": "vscode-icons:file-type-jupyter",
|
|
".njk": "vscode-icons:file-type-nunjucks",
|
|
".nunjucks": "vscode-icons:file-type-nunjucks",
|
|
".nunjs": "vscode-icons:file-type-nunjucks",
|
|
".nunj": "vscode-icons:file-type-nunjucks",
|
|
".njs": "vscode-icons:file-type-nunjucks",
|
|
".nj": "vscode-icons:file-type-nunjucks",
|
|
".npm-debug.log": "vscode-icons:file-type-npm",
|
|
".npmignore": "catppuccin:npm-ignore",
|
|
".npmrc": "vscode-icons:file-type-npm",
|
|
".ml": "vscode-icons:file-type-ocaml",
|
|
".mli": "vscode-icons:file-type-ocaml",
|
|
".cmx": "vscode-icons:file-type-ocaml",
|
|
".cmxa": "vscode-icons:file-type-ocaml",
|
|
".pl": "vscode-icons:file-type-perl",
|
|
".php": "vscode-icons:file-type-php",
|
|
".php.inc": "vscode-icons:file-type-php",
|
|
".pipeline": "vscode-icons:file-type-pipeline",
|
|
".pddl": "vscode-icons:file-type-pddl",
|
|
".plan": "vscode-icons:file-type-pddl-plan",
|
|
".happenings": "vscode-icons:file-type-pddl-happenings",
|
|
".ps1": "vscode-icons:file-type-powershell",
|
|
".psd1": "vscode-icons:file-type-powershell",
|
|
".psm1": "vscode-icons:file-type-powershell",
|
|
".prisma": "vscode-icons:file-type-light-prisma",
|
|
".pug": "vscode-icons:file-type-pug",
|
|
".pp": "vscode-icons:file-type-puppet",
|
|
".epp": "vscode-icons:file-type-puppet",
|
|
".purs": "vscode-icons:file-type-light-purescript",
|
|
".py": "vscode-icons:file-type-python",
|
|
".jsx": "vscode-icons:file-type-reactjs",
|
|
".spec.jsx": "vscode-icons:file-type-reactjs",
|
|
".test.jsx": "vscode-icons:file-type-reactjs",
|
|
".cjsx": "vscode-icons:file-type-reactjs",
|
|
".tsx": "vscode-icons:file-type-reactts",
|
|
".spec.tsx": "vscode-icons:file-type-reactts",
|
|
".test.tsx": "vscode-icons:file-type-reactts",
|
|
".res": "vscode-icons:file-type-rescript",
|
|
".resi": "vscode-icons:file-type-rescript",
|
|
".R": "vscode-icons:file-type-r",
|
|
".rmd": "vscode-icons:file-type-rmd",
|
|
".rb": "vscode-icons:file-type-ruby",
|
|
".erb": "vscode-icons:file-type-html",
|
|
".erb.html": "vscode-icons:file-type-html",
|
|
".html.erb": "vscode-icons:file-type-html",
|
|
".rs": "vscode-icons:file-type-rust",
|
|
".sass": "vscode-icons:file-type-sass",
|
|
".scss": "vscode-icons:file-type-sass",
|
|
".springBeans": "seti:spring",
|
|
".slim": "vscode-icons:file-type-slim",
|
|
".smarty.tpl": "vscode-icons:file-type-smarty",
|
|
".tpl": "vscode-icons:file-type-smarty",
|
|
".sbt": "vscode-icons:file-type-sbt",
|
|
".scala": "vscode-icons:file-type-scala",
|
|
".sol": "logos:ethereum-color",
|
|
".styl": "vscode-icons:file-type-light-stylus",
|
|
".svelte": "vscode-icons:file-type-svelte",
|
|
".swift": "vscode-icons:file-type-swift",
|
|
".sql": "vscode-icons:file-type-sql",
|
|
".soql": "vscode-icons:file-type-sql",
|
|
".tf": "vscode-icons:file-type-terraform",
|
|
".tf.json": "vscode-icons:file-type-terraform",
|
|
".tfvars": "vscode-icons:file-type-terraform",
|
|
".tfvars.json": "vscode-icons:file-type-terraform",
|
|
".tex": "vscode-icons:file-type-light-tex",
|
|
".sty": "vscode-icons:file-type-light-tex",
|
|
".dtx": "vscode-icons:file-type-light-tex",
|
|
".ins": "vscode-icons:file-type-light-tex",
|
|
".txt": "vscode-icons:file-type-text",
|
|
".toml": "vscode-icons:file-type-light-toml",
|
|
".twig": "vscode-icons:file-type-twig",
|
|
".ts": "vscode-icons:file-type-typescript",
|
|
".spec.ts": "vscode-icons:file-type-testts",
|
|
".test.ts": "vscode-icons:file-type-testts",
|
|
".vala": "vscode-icons:file-type-vala",
|
|
".vapi": "vscode-icons:file-type-vala",
|
|
".component": "vscode-icons:file-type-html",
|
|
".vue": "vscode-icons:file-type-vue",
|
|
".wasm": "vscode-icons:file-type-wasm",
|
|
".wat": "vscode-icons:file-type-wasm",
|
|
".xml": "vscode-icons:file-type-xml",
|
|
".yml": "vscode-icons:file-type-light-yaml",
|
|
".yaml": "vscode-icons:file-type-light-yaml",
|
|
".pro": "vscode-icons:file-type-prolog",
|
|
".zig": "vscode-icons:file-type-zig",
|
|
".jar": "vscode-icons:file-type-jar",
|
|
".zip": "vscode-icons:file-type-zip",
|
|
".ai": "vscode-icons:file-type-ai",
|
|
".psd": "vscode-icons:file-type-photoshop",
|
|
".pdf": "vscode-icons:file-type-pdf2",
|
|
".eot": "vscode-icons:file-type-light-font",
|
|
".ttf": "vscode-icons:file-type-light-font",
|
|
".woff": "vscode-icons:file-type-light-font",
|
|
".woff2": "vscode-icons:file-type-light-font",
|
|
".otf": "vscode-icons:file-type-light-font",
|
|
".avif": "vscode-icons:file-type-avif",
|
|
".gif": "vscode-icons:file-type-image",
|
|
".jpg": "vscode-icons:file-type-image",
|
|
".jpeg": "vscode-icons:file-type-image",
|
|
".png": "vscode-icons:file-type-image",
|
|
".pxm": "vscode-icons:file-type-image",
|
|
".svg": "vscode-icons:file-type-svg",
|
|
".svgx": "vscode-icons:file-type-image",
|
|
".tiff": "vscode-icons:file-type-image",
|
|
".webp": "vscode-icons:file-type-image",
|
|
".sublime-project": "vscode-icons:file-type-sublime",
|
|
".sublime-workspace": "vscode-icons:file-type-sublime",
|
|
".code-search": "vscode-icons:file-type-search-result",
|
|
".sh": "vscode-icons:file-type-shell",
|
|
".zsh": "vscode-icons:file-type-shell",
|
|
".fish": "vscode-icons:file-type-shell",
|
|
".zshrc": "vscode-icons:file-type-shell",
|
|
".bashrc": "vscode-icons:file-type-shell",
|
|
".mov": "vscode-icons:file-type-video",
|
|
".ogv": "vscode-icons:file-type-video",
|
|
".webm": "vscode-icons:file-type-video",
|
|
".avi": "vscode-icons:file-type-video",
|
|
".mpg": "vscode-icons:file-type-video",
|
|
".mp4": "vscode-icons:file-type-video",
|
|
".mp3": "vscode-icons:file-type-audio",
|
|
".ogg": "vscode-icons:file-type-audio",
|
|
".wav": "vscode-icons:file-type-audio",
|
|
".flac": "vscode-icons:file-type-audio",
|
|
".bat": "vscode-icons:file-type-bat",
|
|
".cmd": "vscode-icons:file-type-shell",
|
|
".babelrc": "vscode-icons:file-type-light-babel",
|
|
".babelrc.js": "vscode-icons:file-type-light-babel",
|
|
".babelrc.cjs": "vscode-icons:file-type-light-babel",
|
|
".bazelrc": "vscode-icons:file-type-bazel",
|
|
".bowerrc": "vscode-icons:file-type-bower2",
|
|
".codeclimate.yml": "vscode-icons:file-type-codeclimate",
|
|
".eslintrc": "vscode-icons:file-type-eslint",
|
|
".eslintrc.js": "vscode-icons:file-type-eslint",
|
|
".eslintrc.cjs": "vscode-icons:file-type-eslint",
|
|
".eslintrc.yaml": "vscode-icons:file-type-eslint",
|
|
".eslintrc.yml": "vscode-icons:file-type-eslint",
|
|
".eslintrc.json": "vscode-icons:file-type-eslint",
|
|
".eslintignore": "vscode-icons:file-type-eslint",
|
|
".firebaserc": "vscode-icons:file-type-firebase",
|
|
".gitlab-ci.yml": "vscode-icons:file-type-gitlab",
|
|
".jshintrc": "vscode-icons:file-type-jshint",
|
|
".jscsrc": "vscode-icons:file-type-js",
|
|
".stylelintrc": "vscode-icons:file-type-light-stylelint",
|
|
".stylelintrc.json": "vscode-icons:file-type-light-stylelint",
|
|
".stylelintrc.yaml": "vscode-icons:file-type-light-stylelint",
|
|
".stylelintrc.yml": "vscode-icons:file-type-light-stylelint",
|
|
".stylelintrc.js": "vscode-icons:file-type-light-stylelint",
|
|
".stylelintignore": "vscode-icons:file-type-light-stylelint",
|
|
".direnv": "vscode-icons:file-type-dotenv",
|
|
".env": "vscode-icons:file-type-dotenv",
|
|
".static": "vscode-icons:file-type-config",
|
|
".editorconfig": "vscode-icons:file-type-editorconfig",
|
|
".slugignore": "vscode-icons:file-type-config",
|
|
".tmp": "ph:clock-bold",
|
|
".htaccess": "vscode-icons:file-type-config",
|
|
".key": "vscode-icons:file-type-key",
|
|
".cert": "vscode-icons:file-type-cert",
|
|
".cer": "vscode-icons:file-type-cert",
|
|
".crt": "vscode-icons:file-type-cert",
|
|
".pem": "vscode-icons:file-type-cert",
|
|
".DS_Store": "octicon:diff-ignored-16"
|
|
},
|
|
partials: {
|
|
"Gemfile": "vscode-icons:file-type-ruby",
|
|
"gemfile": "vscode-icons:file-type-ruby",
|
|
"dockerfile": "vscode-icons:file-type-docker",
|
|
"Dockerfile": "vscode-icons:file-type-docker",
|
|
"DOCKERFILE": "vscode-icons:file-type-docker",
|
|
".dockerignore": "vscode-icons:file-type-docker",
|
|
"docker-healthcheck": "vscode-icons:file-type-docker",
|
|
"docker-compose.yml": "vscode-icons:file-type-docker",
|
|
"docker-compose.yaml": "vscode-icons:file-type-docker",
|
|
"docker-compose.override.yml": "vscode-icons:file-type-docker",
|
|
"docker-compose.override.yaml": "vscode-icons:file-type-docker",
|
|
"GULPFILE": "vscode-icons:file-type-gulp",
|
|
"Gulpfile": "vscode-icons:file-type-gulp",
|
|
"gulpfile": "vscode-icons:file-type-gulp",
|
|
"gulpfile.js": "vscode-icons:file-type-gulp",
|
|
"LICENSE": "vscode-icons:file-type-license",
|
|
"LICENCE": "vscode-icons:file-type-license",
|
|
"LICENSE.txt": "vscode-icons:file-type-license",
|
|
"LICENCE.txt": "vscode-icons:file-type-license",
|
|
"LICENSE.md": "vscode-icons:file-type-license",
|
|
"LICENCE.md": "vscode-icons:file-type-license",
|
|
"COPYING": "vscode-icons:file-type-license",
|
|
"COPYING.txt": "vscode-icons:file-type-license",
|
|
"COPYING.md": "vscode-icons:file-type-license",
|
|
"COMPILING": "vscode-icons:file-type-license",
|
|
"COMPILING.txt": "vscode-icons:file-type-license",
|
|
"COMPILING.md": "vscode-icons:file-type-license",
|
|
"CONTRIBUTING": "vscode-icons:file-type-license",
|
|
"CONTRIBUTING.txt": "vscode-icons:file-type-license",
|
|
"CONTRIBUTING.md": "vscode-icons:file-type-license",
|
|
"MAKEFILE": "vscode-icons:file-type-makefile",
|
|
"Makefile": "vscode-icons:file-type-makefile",
|
|
"makefile": "vscode-icons:file-type-makefile",
|
|
"QMAKEFILE": "vscode-icons:file-type-makefile",
|
|
"QMakefile": "vscode-icons:file-type-makefile",
|
|
"qmakefile": "vscode-icons:file-type-makefile",
|
|
"OMAKEFILE": "vscode-icons:file-type-makefile",
|
|
"OMakefile": "vscode-icons:file-type-makefile",
|
|
"omakefile": "vscode-icons:file-type-makefile",
|
|
"CMAKELISTS.TXT": "vscode-icons:file-type-makefile",
|
|
"CMAKELISTS.txt": "vscode-icons:file-type-makefile",
|
|
"CMakeLists.txt": "vscode-icons:file-type-makefile",
|
|
"cmakelists.txt": "vscode-icons:file-type-makefile",
|
|
"Procfile": "vscode-icons:file-type-procfile",
|
|
"TODO": "vscode-icons:file-type-light-todo",
|
|
"TODO.txt": "vscode-icons:file-type-light-todo",
|
|
"TODO.md": "vscode-icons:file-type-light-todo"
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/fileIcons/findIcon.ts
|
|
function getFileIcon(fileName, type) {
|
|
const name = getFileIconName(fileName, type);
|
|
if (!name) return type !== "folder" ? defaultFile : defaultFolder;
|
|
return name;
|
|
}
|
|
function getFileIconName(fileName, type = "file") {
|
|
if (type === "folder") {
|
|
const icon$1 = definitions.folders[fileName];
|
|
if (icon$1) return icon$1;
|
|
if (fileName.includes("/")) return definitions.folders[fileName.slice(fileName.lastIndexOf("/") + 1)];
|
|
return;
|
|
}
|
|
let icon = definitions.named[fileName] || definitions.files[fileName];
|
|
if (icon) return icon;
|
|
icon = getFileIconTypeFromExtension(fileName) || void 0;
|
|
if (icon) return icon;
|
|
for (const [partial, partialIcon] of Object.entries(definitions.partials)) if (fileName.includes(partial)) return partialIcon;
|
|
return icon;
|
|
}
|
|
function getFileIconTypeFromExtension(fileName) {
|
|
const firstDotIndex = fileName.indexOf(".");
|
|
if (firstDotIndex === -1) return;
|
|
let extension = fileName.slice(firstDotIndex);
|
|
while (extension !== "") {
|
|
const icon = definitions.extensions[extension];
|
|
if (icon) return icon;
|
|
const nextDotIndex = extension.indexOf(".", 1);
|
|
if (nextDotIndex === -1) return;
|
|
extension = extension.slice(nextDotIndex);
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/utils/cleanMarkdownEnv.ts
|
|
const WHITE_LIST = [
|
|
"base",
|
|
"filePath",
|
|
"filePathRelative",
|
|
"references",
|
|
"abbreviations",
|
|
"annotations"
|
|
];
|
|
function cleanMarkdownEnv(env, excludes = []) {
|
|
const result = {};
|
|
for (const key of WHITE_LIST) {
|
|
if (excludes.includes(key)) continue;
|
|
result[key] = env[key];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/utils/stringifyProp.ts
|
|
function stringifyProp(data) {
|
|
return JSON.stringify(data).replace(/'/g, "'");
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/codeTabs.ts
|
|
function createCodeTabIconGetter(options = {}) {
|
|
const noop = () => void 0;
|
|
if (options.icon === false) return noop;
|
|
const { named, extensions } = isPlainObject(options.icon) ? options.icon : {};
|
|
return function getIcon(filename) {
|
|
if (named === false && definitions.named[filename]) return void 0;
|
|
if (extensions === false && getFileIconTypeFromExtension(filename)) return;
|
|
const hasNamed = named && named.length;
|
|
const hasExt = extensions && extensions.length;
|
|
if (hasNamed || hasExt) {
|
|
if (hasNamed && named.includes(filename)) return definitions.named[filename];
|
|
if (hasExt && extensions.some((ext) => filename.endsWith(ext))) return getFileIconTypeFromExtension(filename);
|
|
return;
|
|
}
|
|
return getFileIconName(filename);
|
|
};
|
|
}
|
|
const codeTabs = (md, options = {}) => {
|
|
const getIcon = createCodeTabIconGetter(options);
|
|
tab(md, {
|
|
name: "code-tabs",
|
|
openRender: ({ active, data }, tokens, index, _, env) => {
|
|
const { meta } = tokens[index];
|
|
const titles = data.map(({ title }) => md.renderInline(title, cleanMarkdownEnv(env)));
|
|
const tabsData = data.map((item, dataIndex) => {
|
|
const { id = titles[dataIndex] } = item;
|
|
return { id };
|
|
});
|
|
const titlesContent = titles.map((title, index$1) => {
|
|
const icon = getIcon(title);
|
|
return `<template #title${index$1}="{ value, isActive }">${icon ? `<VPIcon provider="iconify" name="${icon}"/>` : ""}<span>${title}</span></template>`;
|
|
}).join("");
|
|
return `<CodeTabs id="${index}" :data='${stringifyProp(tabsData)}'${active === -1 ? "" : ` :active="${active}"`}${meta.id ? ` tab-id="${meta.id}"` : ""}>${titlesContent}`;
|
|
},
|
|
closeRender: () => `</CodeTabs>`,
|
|
tabOpenRender: ({ index }, tokens, tokenIndex) => {
|
|
let foundFence = false;
|
|
for (let i = tokenIndex; i < tokens.length; i++) {
|
|
const { type } = tokens[i];
|
|
if (type === "code-tabs_tab_close") break;
|
|
if ((type === "fence" || type === "import_code") && !foundFence) {
|
|
foundFence = true;
|
|
continue;
|
|
}
|
|
tokens[i].type = "code_tab_empty";
|
|
tokens[i].hidden = true;
|
|
}
|
|
return `<template #tab${index}="{ value, isActive }">`;
|
|
},
|
|
tabCloseRender: () => `</template>`
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/utils/resolveAttrs.ts
|
|
const RE_ATTR_VALUE = /(?:^|\s+)(?<attr>[\w-]+)(?:=(?<quote>['"])(?<valueWithQuote>.+?)\k<quote>|=(?<valueWithoutQuote>\S+))?(?:\s+|$)/;
|
|
function resolveAttrs(info) {
|
|
info = info.trim();
|
|
if (!info) return {
|
|
rawAttrs: "",
|
|
attrs: {}
|
|
};
|
|
const attrs$1 = {};
|
|
const rawAttrs = info;
|
|
let matched;
|
|
while (matched = info.match(RE_ATTR_VALUE)) {
|
|
const { attr, valueWithQuote, valueWithoutQuote } = matched.groups;
|
|
const value = valueWithQuote || valueWithoutQuote || true;
|
|
let v = typeof value === "string" ? value.trim() : value;
|
|
if (v === "true") v = true;
|
|
else if (v === "false") v = false;
|
|
else if (v === "\"\"" || v === "''") v = "";
|
|
attrs$1[camelCase(attr)] = v;
|
|
info = info.slice(matched[0].length);
|
|
}
|
|
return {
|
|
attrs: attrs$1,
|
|
rawAttrs
|
|
};
|
|
}
|
|
function resolveAttr(info, key) {
|
|
const pattern = /* @__PURE__ */ new RegExp(`(?:^|\\s+)${key}(?:=(?<quote>['"])(?<valueWithQuote>.+?)\\k<quote>|=(?<valueWithoutQuote>\\S+))?(?:\\s+|$)`);
|
|
const groups = info.match(pattern)?.groups;
|
|
return groups?.valueWithQuote || groups?.valueWithoutQuote;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/enhance/imageSize.ts
|
|
const REG_IMG = /!\[.*?\]\(.*?\)/g;
|
|
const REG_IMG_TAG = /<img(.*?)>/g;
|
|
const REG_IMG_TAG_SRC = /src(?:set)?=(['"])(.+?)\1/g;
|
|
const BADGE_LIST = [
|
|
"https://img.shields.io",
|
|
"https://badge.fury.io",
|
|
"https://badgen.net",
|
|
"https://forthebadge.com",
|
|
"https://vercel.com/button"
|
|
];
|
|
const cache$1 = /* @__PURE__ */ new Map();
|
|
async function imageSizePlugin(app, md, type = false) {
|
|
if (!app.env.isBuild || !type) return;
|
|
if (type === "all") {
|
|
const start = performance.now();
|
|
try {
|
|
await scanRemoteImageSize(app);
|
|
} catch {}
|
|
if (app.env.isDebug) logger.info(`[vuepress-plugin-md-power] imageSizePlugin: scan all images time spent: ${performance.now() - start}ms`);
|
|
}
|
|
const imageRule = md.renderer.rules.image;
|
|
md.renderer.rules.image = (tokens, idx, options, env, self) => {
|
|
if (!env.filePathRelative || !env.filePath) return imageRule(tokens, idx, options, env, self);
|
|
const token = tokens[idx];
|
|
const src = token.attrGet("src");
|
|
const width = token.attrGet("width");
|
|
const height = token.attrGet("height");
|
|
const size = resolveSize(src, width, height, env);
|
|
if (size) {
|
|
token.attrSet("width", `${size.width}`);
|
|
token.attrSet("height", `${size.height}`);
|
|
}
|
|
return imageRule(tokens, idx, options, env, self);
|
|
};
|
|
const rawHtmlBlockRule = md.renderer.rules.html_block;
|
|
const rawHtmlInlineRule = md.renderer.rules.html_inline;
|
|
md.renderer.rules.html_block = createHtmlRule(rawHtmlBlockRule);
|
|
md.renderer.rules.html_inline = createHtmlRule(rawHtmlInlineRule);
|
|
function createHtmlRule(rawHtmlRule) {
|
|
return (tokens, idx, options, env, self) => {
|
|
const token = tokens[idx];
|
|
token.content = token.content.replace(REG_IMG_TAG, (raw, info) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const src = attrs$1.src || attrs$1.srcset;
|
|
const size = resolveSize(src, attrs$1.width, attrs$1.height, env);
|
|
if (!size) return raw;
|
|
attrs$1.width = size.width;
|
|
attrs$1.height = size.height;
|
|
return `<img ${Object.entries(attrs$1).map(([key, value]) => typeof value === "boolean" ? key : `${key}="${value}"`).join(" ")}>`;
|
|
});
|
|
return rawHtmlRule(tokens, idx, options, env, self);
|
|
};
|
|
}
|
|
function resolveSize(src, width, height, env) {
|
|
if (!src || src.startsWith("data:")) return false;
|
|
if (width && height) return false;
|
|
const isExternal = isLinkExternal(src, env.base);
|
|
const filepath$1 = isExternal ? src : resolveImageUrl(src, env, app);
|
|
if (isExternal) {
|
|
if (!cache$1.has(filepath$1)) return false;
|
|
} else if (!cache$1.has(filepath$1)) {
|
|
if (!fs.existsSync(filepath$1)) return false;
|
|
try {
|
|
const { width: w, height: h } = imageSize(fs.readFileSync(filepath$1));
|
|
if (!w || !h) return false;
|
|
cache$1.set(filepath$1, {
|
|
width: w,
|
|
height: h
|
|
});
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
const { width: originalWidth, height: originalHeight } = cache$1.get(filepath$1);
|
|
const ratio = originalWidth / originalHeight;
|
|
if (width && !height) {
|
|
const w = Number.parseInt(width, 10);
|
|
return {
|
|
width: w,
|
|
height: Math.round(w / ratio)
|
|
};
|
|
} else if (height && !width) {
|
|
const h = Number.parseInt(height, 10);
|
|
return {
|
|
width: Math.round(h * ratio),
|
|
height: h
|
|
};
|
|
} else return {
|
|
width: originalWidth,
|
|
height: originalHeight
|
|
};
|
|
}
|
|
}
|
|
function resolveImageUrl(src, env, app) {
|
|
if (src[0] === "/") return app.dir.public(src.slice(1));
|
|
if (env.filePathRelative && src[0] === ".") return app.dir.source(path.join(path.dirname(env.filePathRelative), src));
|
|
if (env.filePath && (src[0] === "." || src[0] === "/")) return path.resolve(env.filePath, src);
|
|
return path.resolve(src);
|
|
}
|
|
async function scanRemoteImageSize(app) {
|
|
if (!app.env.isBuild) return;
|
|
const cwd = app.dir.source();
|
|
const files = await fs.readdir(cwd, { recursive: true });
|
|
const imgList = [];
|
|
for (const file of files) {
|
|
const filepath$1 = path.join(cwd, file);
|
|
if ((await fs.stat(filepath$1)).isFile() && !filepath$1.includes(".vuepress") && !filepath$1.includes("node_modules") && filepath$1.endsWith(".md")) {
|
|
const content = await fs.readFile(filepath$1, "utf-8");
|
|
const syntaxMatched = content.match(REG_IMG) ?? [];
|
|
for (const img of syntaxMatched) {
|
|
const src = img.slice(img.indexOf("](") + 2, -1);
|
|
addList(src.split(/\s+/)[0]);
|
|
}
|
|
const tagMatched = content.match(REG_IMG_TAG) ?? [];
|
|
for (const img of tagMatched) {
|
|
const src = img.match(REG_IMG_TAG_SRC)?.[2] ?? "";
|
|
addList(src);
|
|
}
|
|
}
|
|
}
|
|
function addList(src) {
|
|
if (src && isLinkHttp(src) && !imgList.includes(src) && !BADGE_LIST.some((badge) => src.startsWith(badge))) imgList.push(src);
|
|
}
|
|
await Promise.all(imgList.map(async (src) => {
|
|
if (!cache$1.has(src)) {
|
|
const { width, height } = await fetchImageSize(src);
|
|
if (width && height) cache$1.set(src, {
|
|
width,
|
|
height
|
|
});
|
|
}
|
|
}));
|
|
}
|
|
function fetchImageSize(src) {
|
|
const link = new URL(src);
|
|
const promise = new Promise((resolve) => {
|
|
http.get(link, async (stream) => {
|
|
const chunks = [];
|
|
for await (const chunk of stream) {
|
|
chunks.push(chunk);
|
|
try {
|
|
const { width, height } = imageSize(Buffer.concat(chunks));
|
|
if (width && height) return resolve({
|
|
width,
|
|
height
|
|
});
|
|
} catch {}
|
|
}
|
|
try {
|
|
const { width, height } = imageSize(Buffer.concat(chunks));
|
|
resolve({
|
|
width,
|
|
height
|
|
});
|
|
} catch {
|
|
resolve({
|
|
width: 0,
|
|
height: 0
|
|
});
|
|
}
|
|
}).on("error", () => resolve({
|
|
width: 0,
|
|
height: 0
|
|
}));
|
|
});
|
|
try {
|
|
return withTimeout(() => promise, 3e3);
|
|
} catch {
|
|
return Promise.resolve({
|
|
width: 0,
|
|
height: 0
|
|
});
|
|
}
|
|
}
|
|
async function resolveImageSize(app, url, remote = false) {
|
|
if (cache$1.has(url)) return cache$1.get(url);
|
|
if (isLinkHttp(url) && remote) return await fetchImageSize(url);
|
|
if (url[0] === "/") {
|
|
const filepath$1 = app.dir.public(url.slice(1));
|
|
if (fs.existsSync(filepath$1)) try {
|
|
const { width, height } = imageSize(fs.readFileSync(filepath$1));
|
|
return {
|
|
width,
|
|
height
|
|
};
|
|
} catch {}
|
|
}
|
|
return {
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/supports/file.ts
|
|
const require = createRequire(process.cwd());
|
|
function findFile(app, env, url) {
|
|
if (url.startsWith("/")) return app.dir.source(url.slice(1));
|
|
if (url.startsWith("./") || url.startsWith("../")) return app.dir.source(path$1.dirname(env.filePathRelative), url);
|
|
if (url.startsWith("@source/")) return app.dir.source(url.slice(8));
|
|
try {
|
|
return require.resolve(url);
|
|
} catch {
|
|
return url;
|
|
}
|
|
}
|
|
function readFileSync(filepath$1) {
|
|
try {
|
|
return fs$1.readFileSync(filepath$1, "utf-8");
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
function writeFileSync(filepath$1, content) {
|
|
const dirname = path$1.dirname(filepath$1);
|
|
fs$1.mkdirSync(dirname, { recursive: true });
|
|
fs$1.writeFileSync(filepath$1, content, "utf-8");
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/embed/createEmbedRuleBlock.ts
|
|
function createEmbedRuleBlock(md, { type, name = type, syntaxPattern, beforeName = "import_code", ruleOptions = { alt: [
|
|
"paragraph",
|
|
"reference",
|
|
"blockquote",
|
|
"list"
|
|
] }, meta, content }) {
|
|
const MIN_LENGTH = type.length + 5;
|
|
const START_CODES = [
|
|
64,
|
|
91,
|
|
...type.split("").map((c) => c.charCodeAt(0))
|
|
];
|
|
md.block.ruler.before(beforeName, name, (state, startLine, endLine, silent) => {
|
|
const pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
const max = state.eMarks[startLine];
|
|
if (pos + MIN_LENGTH > max) return false;
|
|
for (let i = 0; i < START_CODES.length; i += 1) if (state.src.charCodeAt(pos + i) !== START_CODES[i]) return false;
|
|
const content$1 = state.src.slice(pos, max);
|
|
const match = content$1.match(syntaxPattern);
|
|
if (!match) return false;
|
|
/* istanbul ignore if -- @preserve */
|
|
if (silent) return true;
|
|
const token = state.push(name, "", 0);
|
|
token.meta = meta(match);
|
|
token.content = content$1;
|
|
token.map = [startLine, startLine + 1];
|
|
state.line = startLine + 1;
|
|
return true;
|
|
}, ruleOptions);
|
|
md.renderer.rules[name] = (tokens, index, _, env) => {
|
|
const token = tokens[index];
|
|
token.content = content(token.meta, token.content, env);
|
|
return token.content;
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/utils/parseRect.ts
|
|
function parseRect(str, unit = "px") {
|
|
if (Number.parseFloat(str) === Number(str)) return `${str}${unit}`;
|
|
return str;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/utils/stringifyAttrs.ts
|
|
function stringifyAttrs(attrs$1, withUndefinedOrNull = false, forceStringify = []) {
|
|
const result = Object.entries(attrs$1).map(([key, value]) => {
|
|
const k = kebabCase(key);
|
|
if (isUndefined(value) || value === "undefined") return withUndefinedOrNull ? `:${k}="undefined"` : "";
|
|
if (isNull(value) || value === "null") return withUndefinedOrNull ? `:${k}="null"` : "";
|
|
if (value === "true") value = true;
|
|
if (value === "false") value = false;
|
|
if (isBoolean(value)) return value ? `${k}` : "";
|
|
if (isNumber(value)) return `:${k}="${value}"`;
|
|
if (isString(value) && (value[0] === "{" || value[0] === "[")) {
|
|
const v = value.replaceAll("\"", "'");
|
|
if (forceStringify.includes(key)) return `${k}="${v}"`;
|
|
return `:${k}="${v}"`;
|
|
}
|
|
return `${key[0] === ":" ? ":" : ""}${k}="${String(value)}"`;
|
|
}).filter(Boolean).join(" ");
|
|
return result ? ` ${result}` : "";
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/createContainer.ts
|
|
/**
|
|
* 创建 markdown-it 的自定义容器插件。
|
|
*
|
|
* @param md markdown-it 实例
|
|
* @param type 容器类型(如 'tip', 'warning' 等)
|
|
* @param options 可选的 before/after 渲染钩子
|
|
* @param options.before 渲染容器起始标签时的回调函数
|
|
* @param options.after 渲染容器结束标签时的回调函数
|
|
*/
|
|
function createContainerPlugin(md, type, { before, after } = {}) {
|
|
const render = (tokens, index, options, env) => {
|
|
const token = tokens[index];
|
|
const info = token.info.trim().slice(type.length).trim() || "";
|
|
if (token.nesting === 1) return before?.(info, tokens, index, options, env) ?? `<div class="custom-container ${type}">`;
|
|
else return after?.(info, tokens, index, options, env) ?? "</div>";
|
|
};
|
|
md.use(container, type, { render });
|
|
}
|
|
/**
|
|
* 创建一个自定义的容器规则,内容不会交给 markdown-it 处理。
|
|
* 需要自定义 content 的处理逻辑
|
|
* ```md
|
|
* ::: type
|
|
* xxxx <-- content: 这部分的内容不会交给 markdown-it 处理
|
|
* :::
|
|
* ```
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const example = createContainerSyntaxPlugin(md, 'example', (tokens, index, options, env) => {
|
|
* const { content, meta } = tokens[index]
|
|
* return `<div class="example">${meta.title} | ${content}</div>`
|
|
* })
|
|
* ```
|
|
*/
|
|
function createContainerSyntaxPlugin(md, type, render) {
|
|
const maker = ":";
|
|
const markerMinLen = 3;
|
|
/**
|
|
* 自定义容器的 block 规则定义。
|
|
* @param state 当前 block 状态
|
|
* @param startLine 起始行
|
|
* @param endLine 结束行
|
|
* @param silent 是否为静默模式
|
|
* @returns 是否匹配到自定义容器
|
|
*/
|
|
function defineContainer(state, startLine, endLine, silent) {
|
|
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
const max = state.eMarks[startLine];
|
|
let pos = start;
|
|
if (state.src[pos] !== maker) return false;
|
|
pos += markerMinLen;
|
|
for (pos = start + 1; pos <= max; pos++) if (state.src[pos] !== maker) break;
|
|
if (pos - start < markerMinLen) return false;
|
|
const markup = state.src.slice(start, pos);
|
|
const info = state.src.slice(pos, max).trim();
|
|
if (!info.startsWith(type)) return false;
|
|
/* istanbul ignore if -- @preserve */
|
|
if (silent) return true;
|
|
let line = startLine;
|
|
let content = "";
|
|
while (++line < endLine) {
|
|
if (state.src.slice(state.bMarks[line], state.eMarks[line]).trim() === markup) break;
|
|
content += `${state.src.slice(state.bMarks[line], state.eMarks[line])}\n`;
|
|
}
|
|
const token = state.push(`${type}_container`, "", 0);
|
|
token.meta = resolveAttrs(info.slice(type.length)).attrs;
|
|
token.content = content;
|
|
token.markup = `${markup} ${type}`;
|
|
token.map = [startLine, line + 1];
|
|
state.line = line + 1;
|
|
return true;
|
|
}
|
|
const defaultRender = (tokens, index) => {
|
|
const { content } = tokens[index];
|
|
return `<div class="custom-container ${type}">${content}</div>`;
|
|
};
|
|
md.block.ruler.before("fence", `${type}_definition`, defineContainer);
|
|
md.renderer.rules[`${type}_container`] = render ?? defaultRender;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/codeTree.ts
|
|
const UNSUPPORTED_FILE_TYPES = [
|
|
"jpg",
|
|
"jpeg",
|
|
"png",
|
|
"gif",
|
|
"avif",
|
|
"webp",
|
|
"mp3",
|
|
"mp4",
|
|
"ogg",
|
|
"m3u8",
|
|
"m3u",
|
|
"flv",
|
|
"webm",
|
|
"wav",
|
|
"flac",
|
|
"aac",
|
|
"pdf",
|
|
"doc",
|
|
"docx",
|
|
"ppt",
|
|
"pptx",
|
|
"xls",
|
|
"xlsx"
|
|
];
|
|
/**
|
|
* 将文件路径数组解析为文件树节点结构
|
|
* @param files 文件路径数组
|
|
* @returns 文件树节点数组
|
|
*/
|
|
function parseFileNodes(files) {
|
|
const nodes = [];
|
|
for (const file of files) {
|
|
const parts = removeLeadingSlash(file).split("/");
|
|
let node = nodes;
|
|
for (let i = 0; i < parts.length; i++) {
|
|
const part = parts[i];
|
|
const isFile = i === parts.length - 1;
|
|
let child = node.find((n) => n.filename === part);
|
|
if (!child) {
|
|
child = {
|
|
level: i + 1,
|
|
filename: part,
|
|
filepath: isFile ? file : void 0,
|
|
children: isFile ? void 0 : []
|
|
};
|
|
node.push(child);
|
|
}
|
|
if (!isFile && child.children) node = child.children;
|
|
}
|
|
}
|
|
return nodes;
|
|
}
|
|
/**
|
|
* 注册 code-tree 容器和嵌入语法的 markdown 插件
|
|
* @param md markdown-it 实例
|
|
* @param app vuepress app 实例
|
|
* @param options code-tree 配置项
|
|
*/
|
|
function codeTreePlugin(md, app, options = {}) {
|
|
/**
|
|
* 获取文件或文件夹的图标
|
|
*/
|
|
const getIcon = (filename, type, mode) => {
|
|
mode ||= options.icon || "colored";
|
|
if (mode === "simple") return type === "folder" ? defaultFolder : defaultFile;
|
|
return getFileIcon(filename, type);
|
|
};
|
|
/**
|
|
* 渲染文件树节点为组件字符串
|
|
*/
|
|
function renderFileTree(nodes, mode) {
|
|
return nodes.map((node) => {
|
|
const props = {
|
|
filename: node.filename,
|
|
level: node.level,
|
|
type: node.children?.length ? "folder" : "file",
|
|
expanded: true,
|
|
filepath: node.filepath
|
|
};
|
|
return `<FileTreeNode${stringifyAttrs(props, false, ["filename", "filepath"])}>
|
|
<template #icon><VPIcon provider="iconify" name="${getIcon(node.filename, props.type, mode)}" /></template>
|
|
${node.children?.length ? renderFileTree(node.children, mode) : ""}
|
|
</FileTreeNode>`;
|
|
}).join("\n");
|
|
}
|
|
createContainerPlugin(md, "code-tree", {
|
|
before: (info, tokens, index) => {
|
|
const files = [];
|
|
let activeFile;
|
|
for (let i = index + 1; !(tokens[i].nesting === -1 && tokens[i].type === "container_code-tree_close"); i++) {
|
|
const token = tokens[i];
|
|
if (token.type === "fence" && token.tag === "code") {
|
|
const fenceInfo = md.utils.unescapeAll(token.info);
|
|
const title$1 = resolveAttr(fenceInfo, "title");
|
|
if (title$1) {
|
|
files.push(title$1);
|
|
if (fenceInfo.includes(":active")) activeFile = title$1;
|
|
}
|
|
}
|
|
}
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const { title, icon, height, entry } = attrs$1;
|
|
const fileTreeNodes = parseFileNodes(files);
|
|
const entryFile = activeFile || entry || files[0];
|
|
const h = height || String(options.height);
|
|
return `<VPCodeTree${stringifyAttrs({
|
|
title,
|
|
entryFile,
|
|
height: h ? parseRect(h) : void 0
|
|
})}><template #file-tree>${renderFileTree(fileTreeNodes, icon)}</template>`;
|
|
},
|
|
after: () => "</VPCodeTree>"
|
|
});
|
|
createEmbedRuleBlock(md, {
|
|
type: "code-tree",
|
|
syntaxPattern: /^@\[code-tree([^\]]*)\]\(([^)]*)\)/,
|
|
meta: ([, info, dir]) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const h = attrs$1.height || String(options.height);
|
|
return {
|
|
title: attrs$1.title,
|
|
entryFile: attrs$1.entry,
|
|
icon: attrs$1.icon,
|
|
height: h ? parseRect(h) : void 0,
|
|
dir
|
|
};
|
|
},
|
|
content: ({ dir, icon,...props }, _, env) => {
|
|
const codeTreeFiles = env.codeTreeFiles ??= [];
|
|
const root = findFile(app, env, dir);
|
|
const files = globSync("**/*", {
|
|
cwd: root,
|
|
onlyFiles: true,
|
|
dot: true,
|
|
ignore: [
|
|
"**/node_modules/**",
|
|
"**/.DS_Store",
|
|
"**/.gitkeep"
|
|
]
|
|
}).sort((a, b) => {
|
|
const al = a.split("/").length;
|
|
return b.split("/").length - al;
|
|
});
|
|
props.entryFile ||= files[0];
|
|
const codeContent = files.map((file) => {
|
|
const ext = path$1.extname(file).slice(1);
|
|
if (UNSUPPORTED_FILE_TYPES.includes(ext)) return "";
|
|
const filepath$1 = path$1.join(root, file);
|
|
codeTreeFiles.push(filepath$1);
|
|
const content = readFileSync(filepath$1);
|
|
return `\`\`\`${ext || "txt"} title="${file}"\n${content}\n\`\`\``;
|
|
}).filter(Boolean).join("\n");
|
|
const fileTreeNodes = parseFileNodes(files);
|
|
return `<VPCodeTree${stringifyAttrs(props)}><template #file-tree>${renderFileTree(fileTreeNodes, icon)}</template>${md.render(codeContent, cleanMarkdownEnv(env))}</VPCodeTree>`;
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* 扩展页面依赖,将 codeTreeFiles 添加到页面依赖中
|
|
* @param page vuepress 页面对象
|
|
*/
|
|
function extendsPageWithCodeTree(page) {
|
|
const codeTreeFiles = page.markdownEnv.codeTreeFiles ?? [];
|
|
if (codeTreeFiles.length) page.deps.push(...codeTreeFiles);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/align.ts
|
|
function alignPlugin(md) {
|
|
for (const name of [
|
|
"left",
|
|
"center",
|
|
"right",
|
|
"justify"
|
|
]) createContainerPlugin(md, name, { before: () => `<div style="text-align:${name}">` });
|
|
createContainerPlugin(md, "flex", { before: (info) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const styles = ["margin:16px 0;display:flex"];
|
|
const align = attrs$1.start ? "flex-start" : attrs$1.end ? "flex-end" : attrs$1.center ? "center" : "";
|
|
const justify = attrs$1.between ? "space-between" : attrs$1.around ? "space-around" : attrs$1.center ? "center" : "";
|
|
if (align) styles.push(`align-items:${align}`);
|
|
if (justify) styles.push(`justify-content:${justify}`);
|
|
if (attrs$1.column) styles.push("flex-direction:column");
|
|
if (attrs$1.wrap) styles.push("flex-wrap:wrap");
|
|
styles.push(`gap:${parseRect(attrs$1.gap || "16")}`);
|
|
return `<div style="${styles.join(";")}">`;
|
|
} });
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/card.ts
|
|
function cardPlugin(md) {
|
|
/**
|
|
* ::: card title="xxx" icon="xxx"
|
|
* xxx
|
|
* :::
|
|
*/
|
|
createContainerPlugin(md, "card", {
|
|
before(info) {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
return `<VPCard${stringifyAttrs(attrs$1)}>`;
|
|
},
|
|
after: () => "</VPCard>"
|
|
});
|
|
/**
|
|
* :::: card-grid
|
|
* ::: card
|
|
* xxx
|
|
* :::
|
|
* ::: card
|
|
* xxx
|
|
* :::
|
|
* ::::
|
|
*/
|
|
createContainerPlugin(md, "card-grid", {
|
|
before: () => "<VPCardGrid>",
|
|
after: () => "</VPCardGrid>"
|
|
});
|
|
/**
|
|
* ::: card-masonry cols="2" gap="10"
|
|
* ::: card
|
|
* xxx
|
|
* :::
|
|
* ::: card
|
|
* xxx
|
|
* :::
|
|
* ::::
|
|
*/
|
|
createContainerPlugin(md, "card-masonry", {
|
|
before: (info) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
if (attrs$1.cols) attrs$1.cols = attrs$1.cols[0] === "{" ? attrs$1.cols : Number.parseInt(`${attrs$1.cols}`);
|
|
if (attrs$1.gap) attrs$1.gap = Number(attrs$1.gap);
|
|
return `<VPCardMasonry${stringifyAttrs(attrs$1)}>`;
|
|
},
|
|
after: () => "</VPCardMasonry>"
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/chat.ts
|
|
function chatMessagesRender(md, env, messages) {
|
|
let currentDate = "";
|
|
return messages.map(({ sender, username, date, content }) => {
|
|
let messageContent = "";
|
|
if (!currentDate || currentDate !== date) {
|
|
currentDate = date;
|
|
messageContent += `<div class="vp-chat-date"><span>${currentDate}</span></div>\n`;
|
|
}
|
|
messageContent += `<div class="vp-chat-message ${sender}">
|
|
<div class="vp-chat-message-body">\
|
|
${sender === "user" ? `\n<p class="vp-chat-username">${username}</p>` : ""}
|
|
<div class="message-content">
|
|
${md.render(content.join("\n"), cleanMarkdownEnv(env)).trim()}
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
return messageContent;
|
|
}).join("\n");
|
|
}
|
|
function parseChatContent(content) {
|
|
const lines = content.split("\n");
|
|
const messages = [];
|
|
let currentDate = "";
|
|
let message;
|
|
for (const line of lines) {
|
|
const lineStr = line.trim();
|
|
if (lineStr.startsWith("{:") && lineStr.endsWith("}")) {
|
|
currentDate = lineStr.slice(2, -1).trim();
|
|
continue;
|
|
}
|
|
if (lineStr.startsWith("{") && lineStr.endsWith("}")) {
|
|
const username = lineStr.slice(1, -1).trim();
|
|
message = {
|
|
sender: username === "." ? "self" : "user",
|
|
username,
|
|
date: currentDate,
|
|
content: []
|
|
};
|
|
messages.push(message);
|
|
continue;
|
|
}
|
|
if (message?.sender) message.content.push(line);
|
|
}
|
|
return messages;
|
|
}
|
|
const chatPlugin = (md) => createContainerSyntaxPlugin(md, "chat", (tokens, idx, _, env) => `<div class="vp-chat">
|
|
<div class="vp-chat-header">
|
|
<p class="vp-chat-title">${tokens[idx].meta?.title || "Chat"}</p>
|
|
</div>
|
|
<div class="vp-chat-content">
|
|
${chatMessagesRender(md, env, parseChatContent(tokens[idx].content))}
|
|
</div>
|
|
</div>`);
|
|
|
|
//#endregion
|
|
//#region src/node/container/collapse.ts
|
|
function collapsePlugin(md) {
|
|
createContainerPlugin(md, "collapse", {
|
|
before: (info, tokens, index) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const idx = parseCollapse(tokens, index, attrs$1);
|
|
const { accordion } = attrs$1;
|
|
return `<VPCollapse${stringifyAttrs({
|
|
accordion,
|
|
index: idx
|
|
})}>`;
|
|
},
|
|
after: () => `</VPCollapse>`
|
|
});
|
|
md.renderer.rules.collapse_item_open = (tokens, idx) => {
|
|
const { expand, index } = tokens[idx].meta;
|
|
return `<VPCollapseItem${stringifyAttrs({
|
|
expand,
|
|
index
|
|
})}>`;
|
|
};
|
|
md.renderer.rules.collapse_item_close = () => "</VPCollapseItem>";
|
|
md.renderer.rules.collapse_item_title_open = () => "<template #title>";
|
|
md.renderer.rules.collapse_item_title_close = () => "</template>";
|
|
}
|
|
function parseCollapse(tokens, index, attrs$1) {
|
|
const listStack = [];
|
|
let idx = -1;
|
|
let defaultIndex;
|
|
let hashExpand = false;
|
|
for (let i = index + 1; i < tokens.length; i++) {
|
|
const token = tokens[i];
|
|
if (token.type === "container_collapse_close") break;
|
|
if (token.type === "bullet_list_open") {
|
|
listStack.push(0);
|
|
if (listStack.length === 1) token.hidden = true;
|
|
} else if (token.type === "bullet_list_close") {
|
|
listStack.pop();
|
|
if (listStack.length === 0) token.hidden = true;
|
|
} else if (token.type === "list_item_open") {
|
|
if (listStack.length === 1) {
|
|
token.type = "collapse_item_open";
|
|
tokens[i + 1].type = "collapse_item_title_open";
|
|
tokens[i + 3].type = "collapse_item_title_close";
|
|
idx++;
|
|
const firstToken = tokens[i + 2].children[0];
|
|
let flag = "";
|
|
let expand;
|
|
if (firstToken.type === "text") firstToken.content = firstToken.content.trim().replace(/^:[+\-]\s*/, (match) => {
|
|
flag = match.trim();
|
|
return "";
|
|
});
|
|
if (attrs$1.accordion) {
|
|
if (!hashExpand && flag === ":+") {
|
|
expand = hashExpand = true;
|
|
defaultIndex = idx;
|
|
}
|
|
} else if (flag === ":+") expand = true;
|
|
else if (flag === ":-") expand = false;
|
|
else expand = !!attrs$1.expand;
|
|
token.meta = {
|
|
index: idx,
|
|
expand
|
|
};
|
|
}
|
|
} else if (token.type === "list_item_close") {
|
|
if (listStack.length === 1) token.type = "collapse_item_close";
|
|
}
|
|
}
|
|
if (attrs$1.accordion && attrs$1.expand && !hashExpand) defaultIndex = 0;
|
|
return defaultIndex;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/demoWrapper.ts
|
|
/**
|
|
* :::demo-wrapper img no-padding title="xxx" height="100px"
|
|
* :::
|
|
*/
|
|
function demoWrapperPlugin(md) {
|
|
createContainerPlugin(md, "demo-wrapper", {
|
|
before: (info) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const wrapperClasses = ["demo-wrapper"];
|
|
let containerStyle = "";
|
|
if (attrs$1.title) wrapperClasses.push("has-title");
|
|
if (attrs$1.img) wrapperClasses.push("only-img");
|
|
if (attrs$1.noPadding) wrapperClasses.push("no-padding");
|
|
if (attrs$1.height) {
|
|
const h = Number.parseFloat(attrs$1.height) === Number(attrs$1.height) ? `${attrs$1.height}px` : attrs$1.height;
|
|
containerStyle += `--demo-container-height: ${h};`;
|
|
wrapperClasses.push("has-height");
|
|
}
|
|
return `<div class="${wrapperClasses.join(" ")}">
|
|
<div class="demo-head">
|
|
<div class="demo-ctrl"><i></i><i></i><i></i></div>
|
|
${attrs$1.title ? `<h4 class="demo-title"><p>${attrs$1.title}</p></h4>` : ""}
|
|
</div>
|
|
<div class="demo-container" ${containerStyle ? `style="${containerStyle}"` : ""}>\n`;
|
|
},
|
|
after: () => "</div></div>"
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/field.ts
|
|
function fieldPlugin(md) {
|
|
createContainerPlugin(md, "field", {
|
|
before: (info) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const { name, type, required, optional, deprecated, default: defaultValue } = attrs$1;
|
|
return `<VPField${stringifyAttrs({
|
|
name,
|
|
required,
|
|
optional,
|
|
deprecated
|
|
})}${!isUndefined(type) ? ` type="${type}"` : ""}${!isUndefined(defaultValue) ? ` default-value="${defaultValue}"` : ""}>`;
|
|
},
|
|
after: () => "</VPField>"
|
|
});
|
|
createContainerPlugin(md, "field-group", { before: () => "<div class=\"vp-field-group\">" });
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/fileTree.ts
|
|
/**
|
|
* 解析原始文件树内容为节点树结构
|
|
* @param content 文件树的原始文本内容
|
|
* @returns 文件树节点数组
|
|
*/
|
|
function parseFileTreeRawContent(content) {
|
|
const root = {
|
|
info: "",
|
|
level: -1,
|
|
children: []
|
|
};
|
|
const stack = [root];
|
|
const lines = content.trim().split("\n");
|
|
for (const line of lines) {
|
|
const match = line.match(/^(\s*)-(.*)$/);
|
|
if (!match) continue;
|
|
const level = Math.floor(match[1].length / 2);
|
|
const info = match[2].trim();
|
|
while (stack.length > 0 && stack[stack.length - 1].level >= level) stack.pop();
|
|
const parent = stack[stack.length - 1];
|
|
const node = {
|
|
info,
|
|
level,
|
|
children: []
|
|
};
|
|
parent.children.push(node);
|
|
stack.push(node);
|
|
}
|
|
return root.children;
|
|
}
|
|
const RE_FOCUS = /^\*\*(.*)\*\*(?:$|\s+)/;
|
|
/**
|
|
* 解析单个节点的 info 字符串,提取文件名、注释、类型等属性
|
|
* @param info 节点描述字符串
|
|
* @returns 文件树节点属性
|
|
*/
|
|
function parseFileTreeNodeInfo(info) {
|
|
let filename = "";
|
|
let comment = "";
|
|
let focus = false;
|
|
let expanded = true;
|
|
let type = "file";
|
|
let diff;
|
|
if (info.startsWith("++")) {
|
|
info = info.slice(2).trim();
|
|
diff = "add";
|
|
} else if (info.startsWith("--")) {
|
|
info = info.slice(2).trim();
|
|
diff = "remove";
|
|
}
|
|
info = info.replace(RE_FOCUS, (_, matched) => {
|
|
filename = matched;
|
|
focus = true;
|
|
return "";
|
|
});
|
|
if (filename === "" && !focus) {
|
|
const spaceIndex = info.indexOf(" ");
|
|
filename = info.slice(0, spaceIndex === -1 ? info.length : spaceIndex);
|
|
info = spaceIndex === -1 ? "" : info.slice(spaceIndex);
|
|
}
|
|
comment = info.trim();
|
|
if (filename.endsWith("/")) {
|
|
type = "folder";
|
|
expanded = false;
|
|
filename = removeEndingSlash(filename);
|
|
}
|
|
return {
|
|
filename,
|
|
comment,
|
|
focus,
|
|
expanded,
|
|
type,
|
|
diff
|
|
};
|
|
}
|
|
/**
|
|
* 文件树 markdown 插件主函数
|
|
* @param md markdown 实例
|
|
* @param options 文件树渲染选项
|
|
*/
|
|
function fileTreePlugin(md, options = {}) {
|
|
/**
|
|
* 获取文件或文件夹的图标
|
|
*/
|
|
const getIcon = (filename, type, mode) => {
|
|
mode ||= options.icon || "colored";
|
|
if (mode === "simple") return type === "folder" ? defaultFolder : defaultFile;
|
|
return getFileIcon(filename, type);
|
|
};
|
|
/**
|
|
* 递归渲染文件树节点
|
|
*/
|
|
const renderFileTree = (nodes, meta) => nodes.map((node) => {
|
|
const { info, level, children } = node;
|
|
const { filename, comment, focus, expanded, type, diff } = parseFileTreeNodeInfo(info);
|
|
const isOmit = filename === "…" || filename === "...";
|
|
if (children.length === 0 && type === "folder") children.push({
|
|
info: "…",
|
|
level: level + 1,
|
|
children: []
|
|
});
|
|
const nodeType = children.length > 0 ? "folder" : type;
|
|
const renderedComment = comment ? `<template #comment>${md.renderInline(comment.replaceAll("#", "#"))}</template>` : "";
|
|
const renderedIcon = !isOmit ? `<template #icon><VPIcon provider="iconify" name="${getIcon(filename, nodeType, meta.icon)}" /></template>` : "";
|
|
return `<FileTreeNode${stringifyAttrs({
|
|
expanded: nodeType === "folder" ? expanded : false,
|
|
focus,
|
|
type: nodeType,
|
|
diff,
|
|
filename,
|
|
level
|
|
}, false, ["filename"])}>
|
|
${renderedIcon}${renderedComment}${children.length > 0 ? renderFileTree(children, meta) : ""}
|
|
</FileTreeNode>`;
|
|
}).join("\n");
|
|
return createContainerSyntaxPlugin(md, "file-tree", (tokens, index) => {
|
|
const token = tokens[index];
|
|
const nodes = parseFileTreeRawContent(token.content);
|
|
const meta = token.meta;
|
|
return `<div class="vp-file-tree">${meta.title ? `<p class="vp-file-tree-title">${meta.title}</p>` : ""}${renderFileTree(nodes, meta)}</div>\n`;
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/langRepl.ts
|
|
async function langReplPlugin(app, md, { theme, go = false, kotlin = false, rust = false, python = false }) {
|
|
const container$1 = (lang) => createContainerPlugin(md, `${lang}-repl`, {
|
|
before(info) {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const { editable, title } = attrs$1;
|
|
return `<CodeRepl${stringifyAttrs({
|
|
editable,
|
|
title: title || `${lang} playground`
|
|
})}>`;
|
|
},
|
|
after: () => "</CodeRepl>"
|
|
});
|
|
if (kotlin) container$1("kotlin");
|
|
if (go) container$1("go");
|
|
if (rust) container$1("rust");
|
|
if (python) container$1("python");
|
|
theme ??= {
|
|
light: "github-light",
|
|
dark: "github-dark"
|
|
};
|
|
const data = { grammars: {} };
|
|
try {
|
|
const themesPath = path.dirname(resolveModule("tm-themes"));
|
|
const grammarsPath = path.dirname(resolveModule("tm-grammars"));
|
|
const readTheme = (theme$1) => read(path.join(themesPath, "themes", `${theme$1}.json`));
|
|
const readGrammar = (grammar) => read(path.join(grammarsPath, "grammars", `${grammar}.json`));
|
|
if (typeof theme === "string") data.theme = await readTheme(theme);
|
|
else data.theme = await Promise.all([readTheme(theme.light), readTheme(theme.dark)]).then(([light, dark]) => ({
|
|
light,
|
|
dark
|
|
}));
|
|
if (kotlin) data.grammars.kotlin = await readGrammar("kotlin");
|
|
if (go) data.grammars.go = await readGrammar("go");
|
|
if (rust) data.grammars.rust = await readGrammar("rust");
|
|
if (python) data.grammars.python = await readGrammar("python");
|
|
} catch {
|
|
/* istanbul ignore next -- @preserve */
|
|
logger.error("[vuepress-plugin-md-power]", `Failed to load packages: ${colors.green("tm-themes")}, ${colors.green("tm-grammars")}, Please install them manually.`);
|
|
}
|
|
await app.writeTemp("internal/md-power/replEditorData.js", `export default ${JSON.stringify(data, null, 2)}`);
|
|
}
|
|
async function read(file) {
|
|
try {
|
|
const content = await promises.readFile(file, "utf-8");
|
|
return JSON.parse(content);
|
|
} catch {}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/utils/logger.ts
|
|
/**
|
|
* Logger utils
|
|
*/
|
|
var Logger = class {
|
|
constructor(name = "") {
|
|
this.name = name;
|
|
}
|
|
init(subname, text) {
|
|
return ora({
|
|
prefixText: colors.blue(`${this.name}${subname ? `:${subname}` : ""}: `),
|
|
text
|
|
});
|
|
}
|
|
/**
|
|
* Create a loading spinner with text
|
|
*/
|
|
load(subname, msg) {
|
|
const instance = this.init(subname, msg);
|
|
return {
|
|
succeed: (text) => instance.succeed(text),
|
|
fail: (text) => instance.succeed(text)
|
|
};
|
|
}
|
|
info(subname, text = "", ...args) {
|
|
this.init(subname, colors.blue(text)).info();
|
|
if (args.length) console.info(...args);
|
|
}
|
|
/**
|
|
* Log success msg
|
|
*/
|
|
succeed(subname, text = "", ...args) {
|
|
this.init(subname, colors.green(text)).succeed();
|
|
if (args.length) console.log(...args);
|
|
}
|
|
/**
|
|
* Log warning msg
|
|
*/
|
|
warn(subname, text = "", ...args) {
|
|
this.init(subname, colors.yellow(text)).warn();
|
|
if (args.length) console.warn(...args);
|
|
}
|
|
/**
|
|
* Log error msg
|
|
*/
|
|
error(subname, text = "", ...args) {
|
|
this.init(subname, colors.red(text)).fail();
|
|
if (args.length) console.error(...args);
|
|
}
|
|
};
|
|
const logger$1 = new Logger("vuepress-plugin-md-power");
|
|
|
|
//#endregion
|
|
//#region src/node/container/npmToPreset.ts
|
|
const ALLOW_LIST = [
|
|
"npm",
|
|
"pnpm",
|
|
"yarn",
|
|
"bun",
|
|
"deno"
|
|
];
|
|
const BOOL_FLAGS = [
|
|
"--no-save",
|
|
"-B",
|
|
"--save-bundle",
|
|
"--save-dev",
|
|
"-D",
|
|
"--save-prod",
|
|
"-P",
|
|
"--save-peer",
|
|
"-O",
|
|
"--save-optional",
|
|
"-E",
|
|
"--save-exact",
|
|
"-y",
|
|
"--yes",
|
|
"-g",
|
|
"--global"
|
|
];
|
|
const DEFAULT_TABS = [
|
|
"npm",
|
|
"pnpm",
|
|
"yarn"
|
|
];
|
|
const MANAGERS_CONFIG = {
|
|
install: {
|
|
pattern: /(?:^|\s)npm\s+(?:install|i)$/,
|
|
pnpm: { cli: "pnpm install" },
|
|
yarn: { cli: "yarn" },
|
|
bun: { cli: "bun install" },
|
|
deno: { cli: "deno install" }
|
|
},
|
|
add: {
|
|
pattern: /(?:^|\s)npm\s+(?:install|i|add)(?:\s|$)/,
|
|
pnpm: {
|
|
cli: "pnpm add",
|
|
flags: {
|
|
"--no-save": "",
|
|
"-B": "",
|
|
"--save-bundle": ""
|
|
}
|
|
},
|
|
yarn: {
|
|
cli: "yarn add",
|
|
flags: {
|
|
"--save-dev": "--dev",
|
|
"--save-prod": "--prod",
|
|
"-P": "",
|
|
"--save-peer": "--peer",
|
|
"--save-optional": "--optional",
|
|
"--no-save": "",
|
|
"--save-exact": "--exact",
|
|
"-B": "",
|
|
"--save-bundle": ""
|
|
}
|
|
},
|
|
bun: {
|
|
cli: "bun add",
|
|
flags: {
|
|
"--save-dev": "--development",
|
|
"-P": "",
|
|
"--save-prod": "",
|
|
"--save-peer": "",
|
|
"-O": "--optional",
|
|
"--save-optional": "--optional",
|
|
"--no-save": "",
|
|
"--save-exact": "--exact",
|
|
"-B": "",
|
|
"--save-bundle": ""
|
|
}
|
|
},
|
|
deno: {
|
|
cli: "deno add",
|
|
flags: {
|
|
"-g": "",
|
|
"--global": "",
|
|
"--save-dev": "--dev",
|
|
"-P": "",
|
|
"--save-prod": "",
|
|
"--save-peer": "",
|
|
"-O": "",
|
|
"--save-optional": "",
|
|
"--no-save": "",
|
|
"-E": "",
|
|
"--save-exact": "",
|
|
"-B": "",
|
|
"--save-bundle": ""
|
|
}
|
|
}
|
|
},
|
|
run: {
|
|
pattern: /(?:^|\s)npm\s+(?:run|run-script|rum|urn)(?:\s|$)/,
|
|
pnpm: {
|
|
cli: "pnpm",
|
|
flags: {
|
|
"-w": "-F",
|
|
"--workspace": "--filter",
|
|
"--": ""
|
|
}
|
|
},
|
|
yarn: {
|
|
cli: "yarn",
|
|
flags: {
|
|
"-w": "",
|
|
"--workspace": ""
|
|
}
|
|
},
|
|
bun: {
|
|
cli: "bun run",
|
|
flags: {
|
|
"-w": "--filter",
|
|
"--workspace": "--filter"
|
|
}
|
|
},
|
|
deno: {
|
|
cli: "deno run",
|
|
flags: {
|
|
"-w": "",
|
|
"--workspace": ""
|
|
}
|
|
}
|
|
},
|
|
create: {
|
|
pattern: /(?:^|\s)npm\s+create\s/,
|
|
pnpm: {
|
|
cli: "pnpm create",
|
|
flags: {
|
|
"-y": "",
|
|
"--yes": ""
|
|
}
|
|
},
|
|
yarn: {
|
|
cli: "yarn create",
|
|
flags: {
|
|
"-y": "",
|
|
"--yes": ""
|
|
}
|
|
},
|
|
bun: {
|
|
cli: "bun create",
|
|
flags: {
|
|
"-y": "",
|
|
"--yes": ""
|
|
}
|
|
},
|
|
deno: {
|
|
cli: "deno run -A ",
|
|
flags: {
|
|
"-y": "",
|
|
"--yes": ""
|
|
}
|
|
}
|
|
},
|
|
init: {
|
|
pattern: /(?:^|\s)npm\s+init/,
|
|
pnpm: {
|
|
cli: "pnpm init",
|
|
flags: {
|
|
"-y": "",
|
|
"--yes": ""
|
|
}
|
|
},
|
|
yarn: {
|
|
cli: "yarn init",
|
|
flags: {
|
|
"-y": "",
|
|
"--yes": ""
|
|
}
|
|
},
|
|
bun: {
|
|
cli: "bun init",
|
|
flags: {
|
|
"-y": "",
|
|
"--yes": ""
|
|
}
|
|
},
|
|
deno: {
|
|
cli: "deno init",
|
|
flags: {
|
|
"-y": "",
|
|
"--yes": ""
|
|
}
|
|
}
|
|
},
|
|
npx: {
|
|
pattern: /(?:^|\s)npx\s+/,
|
|
pnpm: { cli: "pnpm dlx" },
|
|
yarn: { cli: "yarn dlx" },
|
|
bun: { cli: "bunx" },
|
|
deno: { cli: "deno run -A" }
|
|
},
|
|
remove: {
|
|
pattern: /(?:^|\s)npm\s+(?:uninstall|r|rm|remove|unlink|un)(?:\s|$)/,
|
|
pnpm: {
|
|
cli: "pnpm remove",
|
|
flags: {
|
|
"--no-save": "",
|
|
"--save": "",
|
|
"-S": ""
|
|
}
|
|
},
|
|
yarn: {
|
|
cli: "yarn remove",
|
|
flags: {
|
|
"--save-dev": "--dev",
|
|
"--save": "",
|
|
"-S": "",
|
|
"-g": "",
|
|
"--global": ""
|
|
}
|
|
},
|
|
bun: {
|
|
cli: "bun remove",
|
|
flags: {
|
|
"--save-dev": "--development",
|
|
"--save": "",
|
|
"-S": "",
|
|
"-g": "",
|
|
"--global": ""
|
|
}
|
|
},
|
|
deno: {
|
|
cli: "deno uninstall",
|
|
flags: {
|
|
"--save-dev": "--dev",
|
|
"--save": "",
|
|
"-S": ""
|
|
}
|
|
}
|
|
},
|
|
ci: {
|
|
pattern: /(?:^|\s)npm\s+ci$/,
|
|
pnpm: { cli: "pnpm install --frozen-lockfile" },
|
|
yarn: { cli: "yarn install --immutable" },
|
|
bun: { cli: "bun install --frozen-lockfile" },
|
|
deno: { cli: "deno install --frozen" }
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/container/npmTo.ts
|
|
/**
|
|
* 注册 npm-to 容器插件,将 npm 代码块自动转换为多包管理器命令分组
|
|
*/
|
|
function npmToPlugins(md, options = {}) {
|
|
const opt = isArray(options) ? { tabs: options } : options;
|
|
const defaultTabs = opt.tabs?.length ? opt.tabs : DEFAULT_TABS;
|
|
createContainerPlugin(md, "npm-to", {
|
|
before: (info, tokens, idx, _opt, env) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const tabs$1 = attrs$1.tabs ? attrs$1.tabs.split(/,\s*/) : defaultTabs;
|
|
const token = tokens[idx + 1];
|
|
if (token.type === "fence") {
|
|
const content = token.content;
|
|
token.hidden = true;
|
|
token.type = "text";
|
|
token.content = "";
|
|
const lines = content.split(/(\n|\s*&&\s*)/);
|
|
return md.render(resolveNpmTo(lines, token.info.trim(), idx, tabs$1), cleanMarkdownEnv(env));
|
|
}
|
|
logger$1.warn("npm-to", `Invalid npm-to container in ${colors.gray(env.filePathRelative || env.filePath)}`);
|
|
return "";
|
|
},
|
|
after: () => ""
|
|
});
|
|
}
|
|
/**
|
|
* 将 npm 命令转换为各包管理器命令分组
|
|
* @param lines 命令行数组
|
|
* @param info 代码块类型
|
|
* @param idx token 索引
|
|
* @param tabs 需要支持的包管理器
|
|
*/
|
|
function resolveNpmTo(lines, info, idx, tabs$1) {
|
|
tabs$1 = validateTabs(tabs$1);
|
|
const res = [];
|
|
const map = {};
|
|
for (const tab$1 of tabs$1) {
|
|
const newLines = [];
|
|
for (const line of lines) {
|
|
const config = findConfig(line);
|
|
if (config && config[tab$1]) {
|
|
const parsed = map[line] ??= parseLine(line);
|
|
const { cli, flags } = config[tab$1];
|
|
let newLine = `${parsed.env ? `${parsed.env} ` : ""}${cli}`;
|
|
if (parsed.args && flags) {
|
|
let args = parsed.args;
|
|
for (const [key, value] of Object.entries(flags)) args = args.replaceAll(key, value);
|
|
newLine += ` ${args.replace(/\s+-/g, " -").trim()}`;
|
|
}
|
|
if (parsed.cmd) newLine += ` ${parsed.cmd}`;
|
|
if (parsed.scriptArgs) newLine += ` ${parsed.scriptArgs}`;
|
|
newLines.push(newLine.trim());
|
|
} else newLines.push(line);
|
|
}
|
|
res.push(`@tab ${tab$1}\n\`\`\`${info}\n${newLines.join("")}\n\`\`\``);
|
|
}
|
|
return `:::code-tabs#npm-to-${tabs$1.join("-")}\n${res.join("\n")}\n:::`;
|
|
}
|
|
/**
|
|
* 根据命令行内容查找对应的包管理器配置
|
|
*/
|
|
function findConfig(line) {
|
|
for (const { pattern,...config } of Object.values(MANAGERS_CONFIG)) if (pattern.test(line)) return config;
|
|
}
|
|
/**
|
|
* 校验 tabs 合法性,返回允许的包管理器列表
|
|
*/
|
|
function validateTabs(tabs$1) {
|
|
tabs$1 = tabs$1.filter((tab$1) => ALLOW_LIST.includes(tab$1));
|
|
if (tabs$1.length === 0) return DEFAULT_TABS;
|
|
return tabs$1;
|
|
}
|
|
const LINE_REG = /(.*)(npm|npx)\s+(.*)/;
|
|
/**
|
|
* 解析一行 npm/npx 命令,拆分出环境变量、命令、参数等
|
|
*/
|
|
function parseLine(line) {
|
|
const match = line.match(LINE_REG);
|
|
if (!match) return false;
|
|
const [, env, cli, rest] = match;
|
|
const idx = rest.trim().indexOf(" ");
|
|
if (cli === "npx") {
|
|
let cmd = "";
|
|
let scriptArgs = "";
|
|
if (idx !== -1) {
|
|
cmd = rest.slice(0, idx);
|
|
scriptArgs = rest.slice(idx + 1).trim();
|
|
} else cmd = rest;
|
|
return {
|
|
env,
|
|
cli,
|
|
cmd,
|
|
scriptArgs
|
|
};
|
|
}
|
|
if (idx === -1) return {
|
|
env,
|
|
cli: `${cli} ${rest.trim()}`,
|
|
cmd: ""
|
|
};
|
|
return {
|
|
env,
|
|
cli: `${cli} ${rest.slice(0, idx)}`,
|
|
...parseArgs(rest.slice(idx + 1))
|
|
};
|
|
}
|
|
/**
|
|
* 解析 npm 命令参数,区分命令、参数、脚本参数
|
|
*/
|
|
function parseArgs(line) {
|
|
line = line?.trim();
|
|
const [npmArgs, scriptArgs] = line.split(/\s+--\s+/);
|
|
let cmd = "";
|
|
let args = "";
|
|
if (npmArgs[0] !== "-") if (npmArgs[0] === "\"" || npmArgs[0] === "'") {
|
|
const idx = npmArgs.slice(1).indexOf(npmArgs[0]);
|
|
cmd = npmArgs.slice(0, idx + 2);
|
|
args = npmArgs.slice(idx + 2);
|
|
} else {
|
|
const idx = npmArgs.indexOf(" -");
|
|
if (idx === -1) cmd = npmArgs;
|
|
else {
|
|
cmd = npmArgs.slice(0, idx);
|
|
args = npmArgs.slice(idx + 1);
|
|
}
|
|
}
|
|
else {
|
|
let newLine = "";
|
|
let value = "";
|
|
let isQuote = false;
|
|
let isBool = false;
|
|
let isNextValue = false;
|
|
let quote = "";
|
|
for (let i = 0; i < npmArgs.length; i++) {
|
|
const v = npmArgs[i];
|
|
if (!isQuote && (v === "\"" || v === "'")) {
|
|
quote = v;
|
|
isQuote = true;
|
|
value += v;
|
|
} else if (isQuote && v === quote) {
|
|
isQuote = false;
|
|
value += v;
|
|
} else if ((v === " " || v === "=" || i === npmArgs.length - 1) && !isQuote && value) {
|
|
if (i === npmArgs.length - 1) value += v;
|
|
const isKey = value[0] === "-";
|
|
if (isKey) {
|
|
isBool = BOOL_FLAGS.includes(value);
|
|
isNextValue = !isBool;
|
|
}
|
|
if (!isKey && !isNextValue) cmd += ` ${value}`;
|
|
else {
|
|
newLine += `${value}${i !== npmArgs.length - 1 ? v : ""}`;
|
|
if (!isKey && isNextValue) isNextValue = false;
|
|
}
|
|
value = "";
|
|
} else value += v;
|
|
}
|
|
args = newLine;
|
|
}
|
|
return {
|
|
cmd: cmd.trim(),
|
|
args: args.trim(),
|
|
scriptArgs
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/steps.ts
|
|
/**
|
|
* :::steps
|
|
* 1. 步骤 1
|
|
* xxx
|
|
* 2. 步骤 2
|
|
* xxx
|
|
* 3. ...
|
|
* :::
|
|
*/
|
|
function stepsPlugin(md) {
|
|
createContainerPlugin(md, "steps", { before: () => "<div class=\"vp-steps\">" });
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/table.ts
|
|
/**
|
|
* 在不破坏表格语法的前提下,通过容器语法将表格包裹起来,为表格提供增强功能
|
|
*
|
|
* @example
|
|
* ```md
|
|
* ::: table title="表格标题" max-content copy align="center" hl-rows="warning:1,2,3;error:4,5,6" hl-cols="warning:1;error:2,3" hl-cells="warning:(1,2)(2,3);"
|
|
*
|
|
* | xx | xx | xx |
|
|
* | -- | -- | -- |
|
|
* | xx | xx | xx |
|
|
* :::
|
|
* ```
|
|
*/
|
|
function tablePlugin(md, options = {}) {
|
|
createContainerSyntaxPlugin(md, "table", (tokens, index, opt, env) => {
|
|
const { hlCols = "", hlRows = "", hlCells = "",...meta } = tokens[index].meta;
|
|
const props = {
|
|
copy: true,
|
|
maxContent: false,
|
|
...options,
|
|
...meta
|
|
};
|
|
const content = tokens[index].content;
|
|
if (props.copy) {
|
|
props.copy = props.copy === true ? "all" : props.copy;
|
|
if (props.copy === "all" || props.copy === "md") props.markdown = encodeData(content.trim());
|
|
}
|
|
if (!hlCols && !hlRows && !hlCells) return `<VPTable ${stringifyAttrs(props)}>${md.render(content, env)}</VPTable>`;
|
|
const rows = parseHl(hlRows);
|
|
const cols = parseHl(hlCols);
|
|
const cells = parseHlCells(hlCells);
|
|
const tableTokens = md.parse(content, env);
|
|
let isTable = false;
|
|
let colIndex = 0;
|
|
let rowIndex = 0;
|
|
for (const token of tableTokens) {
|
|
if (token.type === "table_open") isTable = true;
|
|
if (token.type === "table_close") isTable = false;
|
|
if (!isTable) continue;
|
|
if (token.type === "tr_open") {
|
|
rowIndex++;
|
|
colIndex = 0;
|
|
}
|
|
if (token.type === "th_open" || token.type === "td_open") {
|
|
colIndex++;
|
|
const classes = cells[rowIndex]?.[colIndex] || rows[rowIndex] || cols[colIndex];
|
|
if (classes) token.attrJoin("class", classes);
|
|
}
|
|
}
|
|
return `<VPTable ${stringifyAttrs(props)}>${md.renderer.render(tableTokens, opt, env)}</VPTable>`;
|
|
});
|
|
}
|
|
function parseHl(hl) {
|
|
const res = {};
|
|
if (!hl) return res;
|
|
hl.split(";").forEach((item) => {
|
|
const [key, value = "1"] = item.split(":");
|
|
String(value).split(",").forEach((v) => res[v.trim()] = key.trim());
|
|
});
|
|
return res;
|
|
}
|
|
function parseHlCells(hl) {
|
|
const res = {};
|
|
if (!hl) return res;
|
|
hl.split(";").forEach((item) => {
|
|
const [key, value = ""] = item.split(":");
|
|
value.trim().replace(/\s*\((\d+)\s*,\s*(\d+)\)\s*/g, (_, row, col) => {
|
|
res[row] ??= {};
|
|
res[row][col] = key.trim();
|
|
return "";
|
|
});
|
|
});
|
|
return res;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/tabs.ts
|
|
const tabs = (md) => {
|
|
tab(md, {
|
|
name: "tabs",
|
|
openRender: ({ active, data }, tokens, index, _, env) => {
|
|
const { meta } = tokens[index];
|
|
const titles = data.map(({ title }) => md.renderInline(title, cleanMarkdownEnv(env)));
|
|
const tabsData = data.map((item, dataIndex) => {
|
|
const { id = titles[dataIndex] } = item;
|
|
return { id };
|
|
});
|
|
return `<Tabs id="${index}" :data='${stringifyProp(tabsData)}'${active === -1 ? "" : ` :active="${active}"`}${meta.id ? ` tab-id="${meta.id}"` : ""}>
|
|
${titles.map((title, titleIndex) => `<template #title${titleIndex}="{ value, isActive }">${title}</template>`).join("")}`;
|
|
},
|
|
closeRender: () => `</Tabs>`,
|
|
tabOpenRender: ({ index }) => `<template #tab${index}="{ value, isActive }">`,
|
|
tabCloseRender: () => `</template>`
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/container/timeline.ts
|
|
const RE_KEY = /(\w+)=\s*/;
|
|
const RE_SEARCH_KEY = /\s+\w+=\s*|$/;
|
|
const RE_CLEAN_VALUE = /(?<quote>["'])(.*?)(\k<quote>)/;
|
|
function timelinePlugin(md) {
|
|
createContainerPlugin(md, "timeline", {
|
|
before(info, tokens, index) {
|
|
parseTimeline(tokens, index);
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
attrs$1.card ??= void 0;
|
|
return `<VPTimeline${stringifyAttrs(attrs$1, true)}>`;
|
|
},
|
|
after: () => "</VPTimeline>"
|
|
});
|
|
md.renderer.rules.timeline_item_open = (tokens, idx) => {
|
|
const attrs$1 = tokens[idx].meta;
|
|
attrs$1.card ??= void 0;
|
|
const icon = attrs$1.icon;
|
|
return `<VPTimelineItem${stringifyAttrs(attrs$1, true)}>${icon ? `<template #icon><VPIcon provider="iconify" name="${icon}"/></template>` : ""}`;
|
|
};
|
|
md.renderer.rules.timeline_item_close = () => "</VPTimelineItem>";
|
|
md.renderer.rules.timeline_item_title_open = () => "<template #title>";
|
|
md.renderer.rules.timeline_item_title_close = () => "</template>";
|
|
}
|
|
function parseTimeline(tokens, index) {
|
|
const listStack = [];
|
|
for (let i = index + 1; i < tokens.length; i++) {
|
|
const token = tokens[i];
|
|
if (token.type === "container_timeline_close") break;
|
|
if (token.type === "bullet_list_open") {
|
|
listStack.push(0);
|
|
if (listStack.length === 1) token.hidden = true;
|
|
} else if (token.type === "bullet_list_close") {
|
|
listStack.pop();
|
|
if (listStack.length === 0) token.hidden = true;
|
|
} else if (token.type === "list_item_open") {
|
|
if (listStack.length === 1) {
|
|
token.type = "timeline_item_open";
|
|
tokens[i + 1].type = "timeline_item_title_open";
|
|
tokens[i + 3].type = "timeline_item_title_close";
|
|
const inlineToken = tokens[i + 2];
|
|
const softbreakIndex = inlineToken.children.findLastIndex((token$1) => token$1.type === "softbreak");
|
|
if (softbreakIndex !== -1) {
|
|
const lastToken = inlineToken.children[inlineToken.children.length - 1];
|
|
token.meta = extractTimelineAttributes(lastToken.content.trim());
|
|
if (!isEmptyObject(token.meta)) inlineToken.children = inlineToken.children.slice(0, softbreakIndex);
|
|
} else token.meta = {};
|
|
}
|
|
} else if (token.type === "list_item_close") {
|
|
if (listStack.length === 1) token.type = "timeline_item_close";
|
|
}
|
|
}
|
|
}
|
|
function extractTimelineAttributes(rawText) {
|
|
const attrKeys = [
|
|
"time",
|
|
"type",
|
|
"icon",
|
|
"line",
|
|
"color",
|
|
"card",
|
|
"placement"
|
|
];
|
|
const attrs$1 = {};
|
|
let buffer = rawText.trim();
|
|
while (buffer.length) {
|
|
const keyMatch = buffer.match(RE_KEY);
|
|
if (!keyMatch) break;
|
|
const matchedKey = keyMatch[1].toLowerCase();
|
|
if (!attrKeys.includes(matchedKey)) break;
|
|
const keyEnd = keyMatch.index + keyMatch[0].length;
|
|
buffer = buffer.slice(keyEnd);
|
|
let valueEnd = buffer.search(RE_SEARCH_KEY);
|
|
/* istanbul ignore if -- @preserve */
|
|
if (valueEnd === -1) valueEnd = buffer.length;
|
|
attrs$1[matchedKey] = buffer.slice(0, valueEnd).trim().replace(RE_CLEAN_VALUE, "$2");
|
|
buffer = buffer.slice(valueEnd);
|
|
}
|
|
return attrs$1;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/container/index.ts
|
|
async function containerPlugin(app, md, options) {
|
|
alignPlugin(md);
|
|
tabs(md);
|
|
codeTabs(md, options.codeTabs);
|
|
demoWrapperPlugin(md);
|
|
stepsPlugin(md);
|
|
cardPlugin(md);
|
|
if (options.npmTo) npmToPlugins(md, typeof options.npmTo === "boolean" ? {} : options.npmTo);
|
|
if (options.repl) await langReplPlugin(app, md, options.repl);
|
|
if (options.fileTree) fileTreePlugin(md, isPlainObject(options.fileTree) ? options.fileTree : {});
|
|
if (options.codeTree) codeTreePlugin(md, app, isPlainObject(options.codeTree) ? options.codeTree : {});
|
|
if (options.timeline) timelinePlugin(md);
|
|
if (options.collapse) collapsePlugin(md);
|
|
if (options.chat) chatPlugin(md);
|
|
if (options.field) fieldPlugin(md);
|
|
if (options.table) tablePlugin(md, isPlainObject(options.table) ? options.table : {});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/markdown.ts
|
|
function markdownEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded = false }) {
|
|
const filepath$1 = findFile(app, env, url);
|
|
const code = readFileSync(filepath$1);
|
|
if (code === false) {
|
|
logger$1.warn("demo-markdown", `Cannot read markdown file: ${colors.gray(filepath$1)}\n at: ${colors.gray(env.filePathRelative || "")}`);
|
|
return "";
|
|
}
|
|
const demo = {
|
|
type: "markdown",
|
|
path: filepath$1
|
|
};
|
|
env.demoFiles ??= [];
|
|
if (!env.demoFiles.some((d) => d.path === filepath$1)) env.demoFiles.push(demo);
|
|
return `<VPDemoBasic${stringifyAttrs({
|
|
type: "markdown",
|
|
title,
|
|
desc,
|
|
expanded
|
|
})}>
|
|
${md.render(code, {
|
|
filepath: env.filePath,
|
|
filepathRelative: env.filePathRelative
|
|
})}
|
|
<template #code>
|
|
${md.render(`\`\`\`md ${codeSetting}\n${code}\n\`\`\``, {})}
|
|
</template>
|
|
</VPDemoBasic>`;
|
|
}
|
|
const markdownContainerRender = {
|
|
before(app, md, env, meta, codeMap) {
|
|
const { title, desc, expanded = false } = meta;
|
|
const code = codeMap.md || "";
|
|
return `<VPDemoBasic${stringifyAttrs({
|
|
type: "markdown",
|
|
title,
|
|
desc,
|
|
expanded
|
|
})}>
|
|
${md.render(code, {
|
|
filepath: env.filePath,
|
|
filepathRelative: env.filePathRelative
|
|
})}
|
|
<template #code>`;
|
|
},
|
|
after: () => "</template></VPDemoBasic>"
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/utils/package.ts
|
|
async function interopDefault(m) {
|
|
const resolved = await m;
|
|
return resolved.default || resolved;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/supports/compiler.ts
|
|
const cache = new LRUCache({ max: 64 });
|
|
const compiler = {
|
|
script: importer(async () => {
|
|
const { transform } = await import("esbuild");
|
|
return transform;
|
|
}),
|
|
less: importer(() => import("less")),
|
|
sass: importer(async () => {
|
|
if (isPackageExists("sass-embedded")) return await import("sass-embedded");
|
|
return await import("sass");
|
|
}),
|
|
stylus: importer(() => import("stylus"))
|
|
};
|
|
async function compileScript(source, type) {
|
|
const key = `${type}:::${source}`;
|
|
if (cache.has(key)) return cache.get(key);
|
|
const res = await (await compiler.script())(source, {
|
|
target: "es2018",
|
|
platform: "browser",
|
|
format: "cjs",
|
|
loader: type === "ts" ? "ts" : "js",
|
|
sourcemap: false
|
|
});
|
|
cache.set(key, res.code);
|
|
return res.code;
|
|
}
|
|
async function compileStyle(source, type) {
|
|
const key = `${type}:::${source}`;
|
|
if (cache.has(key)) return cache.get(key);
|
|
if (type === "css") return source;
|
|
if (type === "less") {
|
|
const res = await (await compiler.less()).render(source);
|
|
cache.set(key, res.css);
|
|
return res.css;
|
|
}
|
|
if (type === "scss") {
|
|
const res = (await compiler.sass()).compileString(source);
|
|
cache.set(key, res.css);
|
|
return res.css;
|
|
}
|
|
if (type === "stylus") {
|
|
const res = (await compiler.stylus()).render(source);
|
|
cache.set(key, res);
|
|
return res;
|
|
}
|
|
return source;
|
|
}
|
|
function importer(func) {
|
|
let imported;
|
|
return async () => {
|
|
if (!imported) imported = interopDefault(await func());
|
|
return imported;
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/supports/insertScript.ts
|
|
const SCRIPT_RE$1 = /<script.*?>/;
|
|
function insertSetupScript({ export: name, path: path$3 }, env) {
|
|
const imports = `import ${name ? `${name} from ` : ""}'${path$3}';`;
|
|
const scriptSetup = env.sfcBlocks.scriptSetup ??= {
|
|
type: "script",
|
|
content: "<script setup>\n<\/script>",
|
|
contentStripped: "",
|
|
tagOpen: "<script setup>",
|
|
tagClose: "<\/script>"
|
|
};
|
|
scriptSetup.contentStripped = `${imports}\n${scriptSetup.contentStripped}`;
|
|
scriptSetup.content = scriptSetup.content.replace(SCRIPT_RE$1, (matched) => `${matched}\n${imports}`);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/watcher.ts
|
|
/**
|
|
* 消除异步编译 demo 代码 与 markdown 同步 render 的时间差问题
|
|
* 确保 在 vuepress onPrepared 阶段完成所有 demo 代码的编译与输出
|
|
*/
|
|
let renderDone = null;
|
|
let renderCount = 0;
|
|
let renderPromise;
|
|
function createDemoRender() {
|
|
renderPromise = new Promise((resolve) => {
|
|
renderDone = resolve;
|
|
});
|
|
}
|
|
async function waitDemoRender() {
|
|
if (renderCount === 0) {
|
|
renderDone?.();
|
|
renderDone = null;
|
|
}
|
|
await renderPromise;
|
|
}
|
|
function markDemoRender() {
|
|
renderCount++;
|
|
}
|
|
function checkDemoRender() {
|
|
if (renderCount > 0) renderCount--;
|
|
if (renderCount === 0) {
|
|
renderDone?.();
|
|
renderDone = null;
|
|
}
|
|
}
|
|
let watcher = null;
|
|
const tasks = {};
|
|
const target$2 = "md-power/demo/watcher.txt";
|
|
function demoWatcher(app, watchers) {
|
|
if (!watcher) watcher = watch([], { ignoreInitial: true });
|
|
Object.keys(tasks).forEach((path$3) => {
|
|
watcher.add(path$3);
|
|
});
|
|
const code = readFileSync(app.dir.temp(target$2));
|
|
if (code) {
|
|
const paths = JSON.parse(code || "{}");
|
|
Object.entries(paths).forEach(([path$3, output]) => {
|
|
watcher.add(path$3);
|
|
tasks[path$3] = output;
|
|
});
|
|
}
|
|
updateWatchFiles(app);
|
|
watcher.on("change", (path$3) => {
|
|
if (tasks[path$3]) {
|
|
const code$1 = readFileSync(path$3);
|
|
if (code$1 === false) return;
|
|
const source = parseEmbedCode(code$1);
|
|
compileCode(source, tasks[path$3]);
|
|
}
|
|
});
|
|
watcher.on("unlink", (path$3) => {
|
|
delete tasks[path$3];
|
|
watcher.unwatch(path$3);
|
|
});
|
|
watchers.push({ close: () => {
|
|
watcher.close();
|
|
watcher = null;
|
|
} });
|
|
}
|
|
function addTask(app, path$3, output) {
|
|
if (tasks[path$3]) return;
|
|
tasks[path$3] = output;
|
|
if (watcher) watcher.add(path$3);
|
|
updateWatchFiles(app);
|
|
}
|
|
async function updateWatchFiles(app) {
|
|
await fs$1.promises.mkdir(app.dir.temp(path$1.dirname(target$2)), { recursive: true });
|
|
await fs$1.promises.writeFile(app.dir.temp(target$2), JSON.stringify(tasks));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/normal.ts
|
|
const CONFIG_RE = /<script type="config">([\s\S]*?)\n<\/script>/;
|
|
const SCRIPT_RE = /<script\s?(?:lang="(\w+)")?>([\s\S]*?)\n<\/script>/;
|
|
const STYLE_RE$1 = /<style\s?(?:lang="(\w+)")?>([\s\S]*?)\n<\/style>/;
|
|
const scriptSupported = ["ts", "js"];
|
|
const styleSupported = [
|
|
"css",
|
|
"scss",
|
|
"less",
|
|
"stylus",
|
|
"styl"
|
|
];
|
|
const target$1 = "md-power/demo/normal";
|
|
const FENCE = "```";
|
|
function parseEmbedCode(code) {
|
|
const res = {
|
|
html: "",
|
|
css: "",
|
|
script: "",
|
|
imports: "",
|
|
jsType: "js",
|
|
cssType: "css"
|
|
};
|
|
res.html = code.replace(CONFIG_RE, (_, config) => {
|
|
res.imports = config;
|
|
return "";
|
|
}).replace(SCRIPT_RE, (_, lang, script) => {
|
|
res.script = script;
|
|
res.jsType = scriptSupported.includes(lang) ? lang : "js";
|
|
return "";
|
|
}).replace(STYLE_RE$1, (_, lang, style) => {
|
|
res.css = style;
|
|
res.cssType = styleSupported.includes(lang) ? lang === "styl" ? "stylus" : lang : "css";
|
|
return "";
|
|
});
|
|
return res;
|
|
}
|
|
function codeToHtml(md, source, info) {
|
|
let content = "::: code-tabs\n";
|
|
if (source.html) content += `@tab HTML\n${FENCE}html ${info}\n${source.html.trim()}\n${FENCE}\n`;
|
|
if (source.script) {
|
|
const title = source.jsType === "ts" ? "Typescript" : "Javascript";
|
|
content += `@tab ${title}\n${FENCE}${source.jsType} ${info}\n${source.script.trim()}\n${FENCE}\n`;
|
|
}
|
|
if (source.css) {
|
|
const title = source.cssType === "stylus" ? "Stylus" : source.cssType.toUpperCase();
|
|
content += `@tab ${title}\n${FENCE}${source.cssType} ${info}\n${source.css.trim()}\n${FENCE}\n`;
|
|
}
|
|
content += "\n:::";
|
|
return md.render(content, {});
|
|
}
|
|
async function compileCode(code, output) {
|
|
markDemoRender();
|
|
const res = {
|
|
jsLib: [],
|
|
cssLib: [],
|
|
script: "",
|
|
css: "",
|
|
html: ""
|
|
};
|
|
if (!fs$1.existsSync(output)) writeFileSync(output, `import { ref } from "vue"\nexport default ref(${JSON.stringify(res, null, 2)})`);
|
|
try {
|
|
if (code.imports) {
|
|
const imports = JSON.parse(code.imports);
|
|
res.jsLib = imports.jsLib ?? [];
|
|
res.cssLib = imports.cssLib ?? [];
|
|
}
|
|
if (code.script) res.script = await compileScript(code.script.trim(), code.jsType);
|
|
if (code.css) res.css = await compileStyle(code.css.trim(), code.cssType);
|
|
if (code.html) res.html = code.html.trim();
|
|
} catch (e) {
|
|
logger$1.error("demo-normal", "demo parse error: \n", e);
|
|
}
|
|
writeFileSync(output, `import { ref } from "vue"\nexport default ref(${JSON.stringify(res, null, 2)})`);
|
|
checkDemoRender();
|
|
}
|
|
function normalEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded = false }) {
|
|
const filepath$1 = findFile(app, env, url);
|
|
const code = readFileSync(filepath$1);
|
|
if (code === false) {
|
|
logger$1.warn("demo-normal", `Cannot read demo file: ${colors.gray(filepath$1)}\n at: ${colors.gray(env.filePathRelative || "")}`);
|
|
return "";
|
|
}
|
|
const source = parseEmbedCode(code);
|
|
const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
|
|
const basename = path$1.basename(filepath$1).replace(/-|\./g, "_");
|
|
const name = `Demo${basename[0].toUpperCase()}${basename.slice(1)}`;
|
|
const demo = {
|
|
type: "normal",
|
|
export: name,
|
|
path: filepath$1
|
|
};
|
|
const output = app.dir.temp(target$1, `${prefix}-${name}.js`);
|
|
compileCode(source, output);
|
|
addTask(app, filepath$1, output);
|
|
env.demoFiles ??= [];
|
|
if (!env.demoFiles.some((d) => d.path === filepath$1)) {
|
|
env.demoFiles.push(demo);
|
|
insertSetupScript({
|
|
...demo,
|
|
path: output
|
|
}, env);
|
|
}
|
|
return `<VPDemoNormal${stringifyAttrs({
|
|
":config": name,
|
|
title,
|
|
desc,
|
|
expanded
|
|
})}>
|
|
${codeToHtml(md, source, codeSetting)}
|
|
</VPDemoNormal>`;
|
|
}
|
|
const normalContainerRender = {
|
|
before(app, md, env, meta, codeMap) {
|
|
const { url, title, desc, expanded = false } = meta;
|
|
const name = `DemoContainer${url}`;
|
|
const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
|
|
const output = app.dir.temp(path$1.join(target$1, `${prefix}-${name}.js`));
|
|
env.demoFiles ??= [];
|
|
if (!env.demoFiles.some((d) => d.path === output)) {
|
|
const demo = {
|
|
type: "normal",
|
|
export: name,
|
|
gitignore: true,
|
|
path: output
|
|
};
|
|
env.demoFiles.push(demo);
|
|
insertSetupScript(demo, env);
|
|
}
|
|
const source = parseContainerCode(codeMap);
|
|
compileCode(source, output);
|
|
return `<VPDemoNormal${stringifyAttrs({
|
|
":config": name,
|
|
title,
|
|
desc,
|
|
expanded
|
|
})}>`;
|
|
},
|
|
after: () => "</VPDemoNormal>",
|
|
token(token) {
|
|
if (token.info.trim().startsWith("json")) {
|
|
token.hidden = true;
|
|
token.type = "text";
|
|
token.content = "";
|
|
}
|
|
}
|
|
};
|
|
function parseContainerCode(map) {
|
|
const res = {
|
|
html: "",
|
|
css: "",
|
|
script: "",
|
|
imports: "",
|
|
jsType: "js",
|
|
cssType: "css"
|
|
};
|
|
if (map.html) res.html = map.html;
|
|
if (map.js) {
|
|
res.script = map.js;
|
|
res.jsType = "js";
|
|
}
|
|
if (map.ts) {
|
|
res.script = map.ts;
|
|
res.jsType = "ts";
|
|
}
|
|
if (map.css) {
|
|
res.css = map.css;
|
|
res.cssType = "css";
|
|
}
|
|
if (map.less) {
|
|
res.css = map.less;
|
|
res.cssType = "less";
|
|
}
|
|
if (map.scss) {
|
|
res.css = map.scss;
|
|
res.cssType = "scss";
|
|
}
|
|
if (map.styl || map.stylus) {
|
|
res.css = map.styl || map.stylus;
|
|
res.cssType = "stylus";
|
|
}
|
|
if (map.json) res.imports = map.json;
|
|
return res;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/supports/alias.ts
|
|
function normalizeAlias(info) {
|
|
const [lang] = info.trim().split(/\s+|:|\{/);
|
|
switch (lang) {
|
|
case "vue": return "vue";
|
|
case "js":
|
|
case "javascript": return "js";
|
|
case "ts":
|
|
case "typescript": return "ts";
|
|
case "stylus":
|
|
case "styl": return "stylus";
|
|
case "md":
|
|
case "markdown": return "md";
|
|
}
|
|
return lang;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/vue.ts
|
|
function vueEmbed(app, md, env, { url, title, desc, codeSetting = "", expanded = false }) {
|
|
const filepath$1 = findFile(app, env, url);
|
|
const code = readFileSync(filepath$1);
|
|
if (code === false) {
|
|
logger$1.warn("demo-vue", `Cannot read vue demo file: ${colors.gray(filepath$1)}\n at: ${colors.gray(env.filePathRelative || "")}`);
|
|
return "";
|
|
}
|
|
const basename = path$1.basename(filepath$1).replace(/-|\./g, "_");
|
|
const ext = path$1.extname(filepath$1).slice(1);
|
|
const name = `Demo${basename[0].toUpperCase()}${basename.slice(1)}`;
|
|
const demo = {
|
|
type: "vue",
|
|
export: name,
|
|
path: filepath$1
|
|
};
|
|
env.demoFiles ??= [];
|
|
if (!env.demoFiles.some((d) => d.path === filepath$1)) {
|
|
env.demoFiles.push(demo);
|
|
insertSetupScript(demo, env);
|
|
}
|
|
return `<VPDemoBasic${stringifyAttrs({
|
|
type: "vue",
|
|
title,
|
|
desc,
|
|
expanded
|
|
})}>
|
|
<${name} />
|
|
<template #code>
|
|
${md.render(`\`\`\`${ext}${codeSetting}\n${code}\n\`\`\``, {})}
|
|
</template>
|
|
</VPDemoBasic>`;
|
|
}
|
|
const target = "md-power/demo/vue";
|
|
const vueContainerRender = {
|
|
before: (app, md, env, meta, codeMap) => {
|
|
const { url, title, desc, expanded = false } = meta;
|
|
const componentName = `DemoContainer${url}`;
|
|
const prefix = (env.filePathRelative || "").replace(/\.md$/, "").replace(/\//g, "-");
|
|
env.demoFiles ??= [];
|
|
const output = app.dir.temp(path$1.join(target, `${prefix}-${componentName}`));
|
|
if (codeMap.vue || codeMap.js || codeMap.ts) {
|
|
let scriptOutput = output;
|
|
let content = "";
|
|
if (codeMap.vue) {
|
|
scriptOutput += ".vue";
|
|
content = transformStyle(codeMap.vue);
|
|
} else if (codeMap.ts) {
|
|
scriptOutput += ".ts";
|
|
content = codeMap.ts;
|
|
} else if (codeMap.js) {
|
|
scriptOutput += ".js";
|
|
content = codeMap.js;
|
|
}
|
|
content = transformImports(content, env.filePath || "");
|
|
const script = {
|
|
type: "vue",
|
|
export: componentName,
|
|
path: scriptOutput,
|
|
gitignore: true
|
|
};
|
|
writeFileSync(scriptOutput, content);
|
|
if (!env.demoFiles.some((d) => d.path === scriptOutput)) {
|
|
env.demoFiles.push(script);
|
|
insertSetupScript(script, env);
|
|
}
|
|
}
|
|
if (codeMap.css || codeMap.scss || codeMap.less || codeMap.stylus) {
|
|
let styleOutput = output;
|
|
let content = "";
|
|
if (codeMap.css) {
|
|
styleOutput += ".css";
|
|
content = codeMap.css;
|
|
} else if (codeMap.scss) {
|
|
styleOutput += ".scss";
|
|
content = codeMap.scss;
|
|
} else if (codeMap.less) {
|
|
styleOutput += ".less";
|
|
content = codeMap.less;
|
|
} else if (codeMap.stylus) {
|
|
styleOutput += ".styl";
|
|
content = codeMap.stylus;
|
|
}
|
|
writeFileSync(styleOutput, content);
|
|
const style = {
|
|
type: "css",
|
|
path: styleOutput,
|
|
gitignore: true
|
|
};
|
|
if (!env.demoFiles.some((d) => d.path === styleOutput)) {
|
|
env.demoFiles.push(style);
|
|
insertSetupScript(style, env);
|
|
}
|
|
}
|
|
return `<VPDemoBasic${stringifyAttrs({
|
|
type: "vue",
|
|
title,
|
|
desc,
|
|
expanded
|
|
})}>
|
|
<${componentName} />
|
|
<template #code>\n`;
|
|
},
|
|
after: () => "</template></VPDemoBasic>"
|
|
};
|
|
const IMPORT_RE = /import\s+(?:\w+\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
const STYLE_RE = /<style.*?>/;
|
|
function transformImports(code, filepath$1) {
|
|
return code.replace(IMPORT_RE, (matched, url) => {
|
|
if (url.startsWith("./") || url.startsWith("../")) return matched.replace(url, `${path$1.resolve(path$1.dirname(filepath$1), url)}`);
|
|
return matched;
|
|
});
|
|
}
|
|
function transformStyle(code) {
|
|
return code.replace(STYLE_RE, (matched) => {
|
|
if (matched.includes("scoped")) return matched;
|
|
return matched.replace("<style", "<style scoped");
|
|
});
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/demo.ts
|
|
const embedMap = {
|
|
vue: vueEmbed,
|
|
normal: normalEmbed,
|
|
markdown: markdownEmbed
|
|
};
|
|
/**
|
|
* 嵌入语法
|
|
* @[demo type info](url)
|
|
*/
|
|
function demoEmbed(app, md) {
|
|
createEmbedRuleBlock(md, {
|
|
type: "demo",
|
|
syntaxPattern: /^@\[demo(?:\s(vue|normal|markdown))?\s?(.*)\]\((.*)\)/,
|
|
meta: ([, type, info, url]) => ({
|
|
type: type || "normal",
|
|
url,
|
|
...resolveAttrs(info).attrs
|
|
}),
|
|
content: (meta, content, env) => {
|
|
const { url, type } = meta;
|
|
if (!url) {
|
|
logger$1.warn("demo-vue", `Invalid filepath: ${colors.gray(url)}`);
|
|
return content;
|
|
}
|
|
if (embedMap[type]) return embedMap[type](app, md, env, meta);
|
|
return content;
|
|
}
|
|
});
|
|
}
|
|
const INFO_RE = /(vue|normal|markdown)?\s?(.*)/;
|
|
const renderMap = {
|
|
vue: vueContainerRender,
|
|
normal: normalContainerRender,
|
|
markdown: markdownContainerRender
|
|
};
|
|
function demoContainer(app, md) {
|
|
let currentRender;
|
|
const render = (tokens, index, _, env) => {
|
|
const token = tokens[index];
|
|
if (token.nesting === 1) {
|
|
const meta = getContainerMeta(token.info);
|
|
meta.url = `${index}`;
|
|
currentRender = renderMap[meta.type];
|
|
return currentRender?.before(app, md, env, meta, parseCodeMapping(tokens, index, currentRender.token)) || "";
|
|
} else {
|
|
const res = currentRender?.after() || "";
|
|
currentRender = void 0;
|
|
return res;
|
|
}
|
|
};
|
|
md.use(container, "demo", { render });
|
|
}
|
|
function parseCodeMapping(tokens, index, cb) {
|
|
const codeMap = {};
|
|
for (let i = index + 1; !(tokens[i].nesting === -1 && tokens[i].type === "container_demo_close"); ++i) {
|
|
const token = tokens[i];
|
|
if (token.type === "fence") {
|
|
codeMap[normalizeAlias(token.info)] = token.content.trim();
|
|
cb?.(token, tokens, i);
|
|
}
|
|
}
|
|
return codeMap;
|
|
}
|
|
function getContainerMeta(info) {
|
|
const [, type, raw] = (info.trim().slice(4).trim() || "").match(INFO_RE) || [];
|
|
const { attrs: attrs$1 } = resolveAttrs(raw);
|
|
return {
|
|
url: "",
|
|
type: type || "normal",
|
|
...attrs$1
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/extendPage.ts
|
|
function extendsPageWithDemo(page) {
|
|
const demoFiles = page.markdownEnv.demoFiles ?? [];
|
|
page.deps.push(...demoFiles.filter(({ type }) => type === "markdown").map(({ path: path$3 }) => path$3));
|
|
(page.frontmatter.gitInclude ??= []).push(...demoFiles.filter(({ gitignore }) => !gitignore).map(({ path: path$3 }) => path$3));
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/demo/index.ts
|
|
function demoPlugin(app, md) {
|
|
createDemoRender();
|
|
demoEmbed(app, md);
|
|
demoContainer(app, md);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/embed/audio/reader.ts
|
|
const audioReader = (state, silent) => {
|
|
const max = state.posMax;
|
|
let start = state.pos;
|
|
let href = "";
|
|
if (state.src.slice(start, start + 13) !== "@[audioReader") return false;
|
|
if (max - start < 17) return false;
|
|
const labelStart = state.pos + 13;
|
|
const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, true);
|
|
if (labelEnd < 0) return false;
|
|
let pos = labelEnd + 1;
|
|
if (pos < max && state.src.charCodeAt(pos) === 40) {
|
|
pos++;
|
|
const code = state.src.charCodeAt(pos);
|
|
if (code === 10 || code === 32) return false;
|
|
start = pos;
|
|
const res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax);
|
|
if (res.ok) {
|
|
href = state.md.normalizeLink(res.str);
|
|
if (state.md.validateLink(href)) pos = res.pos;
|
|
else href = "";
|
|
}
|
|
if (pos >= max || state.src.charCodeAt(pos) !== 41) return false;
|
|
} else return false;
|
|
/* istanbul ignore else -- @preserve */
|
|
if (!silent) {
|
|
state.pos = labelStart;
|
|
state.posMax = labelEnd;
|
|
const info = state.src.slice(labelStart, labelEnd).trim();
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const token = state.push("audio_reader", "AudioReader", 0);
|
|
token.info = info;
|
|
token.meta = {
|
|
src: href,
|
|
...attrs$1
|
|
};
|
|
}
|
|
state.pos = pos + 1;
|
|
state.posMax = max;
|
|
return true;
|
|
};
|
|
const audioReaderPlugin = (md) => {
|
|
md.renderer.rules.audio_reader = (tokens, idx) => {
|
|
const meta = tokens[idx].meta;
|
|
if (meta.startTime) meta.startTime = Number(meta.startTime);
|
|
if (meta.endTime) meta.endTime = Number(meta.endTime);
|
|
if (meta.volume) meta.volume = Number(meta.volume);
|
|
return `<AudioReader${stringifyAttrs(meta)} />`;
|
|
};
|
|
md.inline.ruler.before("link", "audio-reader", audioReader);
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/utils/nanoid.ts
|
|
const nanoid = customAlphabet("abcdefghijklmnopqrstuvwxyz", 5);
|
|
|
|
//#endregion
|
|
//#region src/node/embed/caniuse.ts
|
|
const UNDERLINE_RE = /_+/g;
|
|
/**
|
|
* @example
|
|
* ```md
|
|
* @[caniuse](feature_name)
|
|
* ```
|
|
*/
|
|
const caniusePlugin = (md, { mode: defaultMode = "embed" } = {}) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "caniuse",
|
|
syntaxPattern: /^@\[caniuse\s*(embed|image)?(?:\{([0-9,\-]*)\})?\]\(([^)]*)\)/,
|
|
meta: ([, mode, versions = "", feature]) => ({
|
|
feature,
|
|
mode: mode || defaultMode,
|
|
versions
|
|
}),
|
|
content: (meta) => resolveCanIUse(meta)
|
|
});
|
|
};
|
|
/**
|
|
* @deprecated use caniuse plugin
|
|
*
|
|
* 兼容旧语法
|
|
* @example
|
|
* ```md
|
|
* :::caniuse <feature_name>
|
|
* :::
|
|
* ```
|
|
*/
|
|
function legacyCaniuse(md, { mode = "embed" } = {}) {
|
|
const modeMap = ["image", "embed"];
|
|
const isMode = (mode$1) => modeMap.includes(mode$1);
|
|
mode = isMode(mode) ? mode : modeMap[0];
|
|
createContainerPlugin(md, "caniuse", {
|
|
before: (info) => {
|
|
const feature = info.split(/\s+/)[0];
|
|
const versions = info.match(/\{(.*)\}/)?.[1] || "";
|
|
return feature ? resolveCanIUse({
|
|
feature,
|
|
mode,
|
|
versions
|
|
}) : "";
|
|
},
|
|
after: () => ""
|
|
});
|
|
}
|
|
function resolveCanIUse({ feature, mode, versions }) {
|
|
if (!feature) return "";
|
|
if (mode === "image") {
|
|
const link = "https://caniuse.bitsofco.de/image/";
|
|
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`;
|
|
return `<ClientOnly><p><picture>
|
|
<source type="image/webp" srcset="${link}${feature}.webp">
|
|
<source type="image/png" srcset="${link}${feature}.png">
|
|
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
|
|
</picture></p></ClientOnly>`;
|
|
}
|
|
feature = feature.replace(UNDERLINE_RE, "_");
|
|
const { past, future } = resolveVersions(versions);
|
|
const meta = nanoid();
|
|
return `<CanIUseViewer${stringifyAttrs({
|
|
feature,
|
|
meta,
|
|
past,
|
|
future
|
|
})} />`;
|
|
}
|
|
function resolveVersions(versions) {
|
|
if (!versions) return {
|
|
past: 2,
|
|
future: 1
|
|
};
|
|
const list = versions.split(",").map((v) => Number(v.trim())).filter((v) => !Number.isNaN(v) && v >= -5 && v <= 3);
|
|
list.push(0);
|
|
const uniq = [...new Set(list)].sort((a, b) => b - a);
|
|
return {
|
|
future: uniq[0],
|
|
past: Math.abs(uniq[uniq.length - 1])
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/embed/code/codepen.ts
|
|
const codepenPlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "codepen",
|
|
syntaxPattern: /^@\[codepen([^\]]*)\]\(([^)]*)\)/,
|
|
meta: ([, info, source]) => {
|
|
const { width, height, title, tab: tab$1, preview, editable, theme } = resolveAttrs(info).attrs;
|
|
const [user, slash] = source.split("/");
|
|
return {
|
|
title: title || "Code Pen",
|
|
tab: tab$1 || "result",
|
|
width: width ? parseRect(width) : "100%",
|
|
height: height ? parseRect(height) : "400px",
|
|
user,
|
|
slash,
|
|
preview,
|
|
editable,
|
|
theme
|
|
};
|
|
},
|
|
content: (meta) => `<CodePenViewer${stringifyAttrs(meta)} />`
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/embed/code/codeSandbox.ts
|
|
const codeSandboxPlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "codesandbox",
|
|
syntaxPattern: /^@\[codesandbox(?:\s+(embed|button))?([^\]]*)\]\(([^)]*)\)/,
|
|
meta([, type, info, source]) {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const [profile, filepath$1 = ""] = source.split("#");
|
|
const [user, id] = profile.includes("/") ? profile.split("/") : ["", profile];
|
|
return {
|
|
width: attrs$1.width ? parseRect(attrs$1.width) : "100%",
|
|
height: attrs$1.height ? parseRect(attrs$1.height) : "500px",
|
|
user,
|
|
id,
|
|
title: attrs$1.title ?? "",
|
|
console: attrs$1.console ?? false,
|
|
navbar: attrs$1.navbar ?? true,
|
|
layout: attrs$1.layout ?? "",
|
|
type: type || "embed",
|
|
filepath: filepath$1
|
|
};
|
|
},
|
|
content: (meta) => `<CodeSandboxViewer${stringifyAttrs(meta)} />`
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/embed/code/jsfiddle.ts
|
|
const jsfiddlePlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "jsfiddle",
|
|
syntaxPattern: /^@\[jsfiddle([^\]]*)\]\(([^)]*)\)/,
|
|
meta([, info, source]) {
|
|
const { width, height, title, tab: tab$1, theme } = resolveAttrs(info).attrs;
|
|
return {
|
|
width: width ? parseRect(width) : "100%",
|
|
height: height ? parseRect(height) : "400px",
|
|
source,
|
|
title: title || "JS Fiddle",
|
|
tab: tab$1?.replace(/\s+/g, "") || "js,css,html,result",
|
|
theme
|
|
};
|
|
},
|
|
content: (meta) => `<JSFiddleViewer${stringifyAttrs(meta)} />`
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/embed/code/replit.ts
|
|
const replitPlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "replit",
|
|
syntaxPattern: /^@\[replit([^\]]*)\]\(([^)]*)\)/,
|
|
meta: ([, info = "", source = ""]) => {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
return {
|
|
width: attrs$1.width ? parseRect(attrs$1.width) : "100%",
|
|
height: attrs$1.height ? parseRect(attrs$1.height) : "450px",
|
|
source: source.startsWith("@") ? source : `@${source}`,
|
|
title: attrs$1.title,
|
|
theme: attrs$1.theme || ""
|
|
};
|
|
},
|
|
content: (meta) => `<ReplitViewer${stringifyAttrs(meta)} />`
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/embed/pdf.ts
|
|
const pdfPlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "pdf",
|
|
syntaxPattern: /^@\[pdf(?:\s+(\d+))?([^\]]*)\]\(([^)]*)\)/,
|
|
meta([, page, info, src]) {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
return {
|
|
src,
|
|
page: +page || 1,
|
|
noToolbar: Boolean(attrs$1.noToolbar ?? false),
|
|
zoom: +attrs$1.zoom || 50,
|
|
width: attrs$1.width ? parseRect(attrs$1.width) : "100%",
|
|
height: attrs$1.height ? parseRect(attrs$1.height) : "",
|
|
ratio: attrs$1.ratio ? parseRect(attrs$1.ratio) : "",
|
|
title: path.basename(src || "")
|
|
};
|
|
},
|
|
content: (meta) => `<PDFViewer${stringifyAttrs(meta)} />`
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/embed/video/acfun.ts
|
|
const AC_FUN_LINK = "https://www.acfun.cn/player";
|
|
const acfunPlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "acfun",
|
|
name: "video_acfun",
|
|
syntaxPattern: /^@\[acfun([^\]]*)\]\(([^)]*)\)/,
|
|
meta([, info, id]) {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
return {
|
|
id,
|
|
title: attrs$1.title || "AcFun",
|
|
width: attrs$1.width ? parseRect(attrs$1.width) : "100%",
|
|
height: attrs$1.height ? parseRect(attrs$1.height) : void 0,
|
|
ratio: attrs$1.ratio ?? "16:10"
|
|
};
|
|
},
|
|
content(meta) {
|
|
const { id, width, height, ratio, title } = meta;
|
|
const src = `${AC_FUN_LINK}/${id}`;
|
|
return `<VPVideoEmbed${stringifyAttrs({
|
|
src,
|
|
width,
|
|
height,
|
|
ratio,
|
|
title,
|
|
type: "acfun"
|
|
})} />`;
|
|
}
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/embed/video/artPlayer.ts
|
|
const installed = {
|
|
dashjs: isPackageExists("dashjs"),
|
|
hlsjs: isPackageExists("hls.js"),
|
|
mpegtsjs: isPackageExists("mpegts.js")
|
|
};
|
|
const SUPPORTED_VIDEO_TYPES = [
|
|
"mp4",
|
|
"mp3",
|
|
"webm",
|
|
"ogg",
|
|
"mpd",
|
|
"dash",
|
|
"m3u8",
|
|
"hls",
|
|
"ts",
|
|
"flv"
|
|
];
|
|
const artPlayerPlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "artPlayer",
|
|
name: "video_artPlayer",
|
|
syntaxPattern: /^@\[artPlayer([^\]]*)\]\(([^)]*)\)/,
|
|
meta([, info, source]) {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const url = source.trim();
|
|
checkSupportType(attrs$1.type ?? url.split(".").pop());
|
|
return {
|
|
url,
|
|
type: attrs$1.type,
|
|
autoplay: attrs$1.autoplay ?? false,
|
|
muted: attrs$1.muted ?? attrs$1.autoplay ?? false,
|
|
autoMini: attrs$1.autoMini ?? false,
|
|
loop: attrs$1.loop ?? false,
|
|
volume: typeof attrs$1.volume !== "undefined" ? Number(attrs$1.volume) : .75,
|
|
poster: attrs$1.poster,
|
|
width: attrs$1.width ? parseRect(attrs$1.width) : "100%",
|
|
height: attrs$1.height ? parseRect(attrs$1.height) : void 0,
|
|
ratio: attrs$1.ratio ? parseRect(`${attrs$1.ratio}`) : void 0
|
|
};
|
|
},
|
|
content({ url,...meta }) {
|
|
meta.muted = meta.muted || meta.autoplay;
|
|
return `<ArtPlayer src="${url}" fullscreen flip playback-rate aspect-ratio setting pip${stringifyAttrs(meta)}/>`;
|
|
}
|
|
});
|
|
};
|
|
function checkSupportType(type) {
|
|
if (!type) return;
|
|
/* istanbul ignore if -- @preserve */
|
|
if (SUPPORTED_VIDEO_TYPES.includes(type)) {
|
|
let name = "";
|
|
switch (type.toLowerCase()) {
|
|
case "m3u8":
|
|
case "hls":
|
|
name = !installed.hlsjs ? "hls.js" : "";
|
|
break;
|
|
case "flv":
|
|
case "ts":
|
|
name = !installed.mpegtsjs ? "mpegts.js" : "";
|
|
break;
|
|
case "mpd":
|
|
case "dash":
|
|
name = !installed.dashjs ? "dashjs" : "";
|
|
break;
|
|
}
|
|
/* istanbul ignore if -- @preserve */
|
|
if (name) logger$1.warn("artPlayer", `${colors.cyan(name)} is not installed, please install it via npm or yarn or pnpm`);
|
|
} else
|
|
/* istanbul ignore next -- @preserve */
|
|
logger$1.warn("artPlayer", `unsupported video type: ${colors.cyan(type)}`);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/utils/timeToSeconds.ts
|
|
function timeToSeconds(time) {
|
|
if (!time) return 0;
|
|
if (Number.parseFloat(time) === Number(time)) return Number(time);
|
|
const [s, m, h = 0] = time.split(/\s*:\s*/).reverse().map((n) => Number(n) || 0);
|
|
return s + m * 60 + h * 3600;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/embed/video/bilibili.ts
|
|
const BILIBILI_LINK = "https://player.bilibili.com/player.html";
|
|
const bilibiliPlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "bilibili",
|
|
name: "video_bilibili",
|
|
syntaxPattern: /^@\[bilibili(?:\s+p(\d+))?([^\]]*)\]\(([^)]*)\)/,
|
|
meta([, page, info, source]) {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
const ids = source.trim().split(/\s+/);
|
|
const bvid = ids.find((id) => id.startsWith("BV"));
|
|
const [aid, cid] = ids.filter((id) => !id.startsWith("BV"));
|
|
return {
|
|
page: +page,
|
|
bvid,
|
|
aid,
|
|
cid,
|
|
autoplay: attrs$1.autoplay ?? false,
|
|
time: timeToSeconds(attrs$1.time),
|
|
title: attrs$1.title || "Bilibili",
|
|
width: attrs$1.width ? parseRect(attrs$1.width) : "100%",
|
|
height: attrs$1.height ? parseRect(attrs$1.height) : void 0,
|
|
ratio: attrs$1.ratio
|
|
};
|
|
},
|
|
content(meta) {
|
|
const params = new URLSearchParams();
|
|
if (meta.bvid) params.set("bvid", meta.bvid);
|
|
if (meta.aid) params.set("aid", meta.aid);
|
|
if (meta.cid) params.set("cid", meta.cid);
|
|
if (meta.page) params.set("p", meta.page.toString());
|
|
if (meta.time) params.set("t", meta.time.toString());
|
|
params.set("autoplay", meta.autoplay ? "1" : "0");
|
|
params.set("high_quality", "1");
|
|
const src = `${BILIBILI_LINK}?${params.toString()}`;
|
|
const { width, height, ratio, title } = meta;
|
|
return `<VPVideoEmbed${stringifyAttrs({
|
|
src,
|
|
width,
|
|
height,
|
|
ratio,
|
|
title,
|
|
type: "bilibili"
|
|
})} />`;
|
|
}
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/embed/video/youtube.ts
|
|
const YOUTUBE_LINK = "https://www.youtube.com/embed/";
|
|
const youtubePlugin = (md) => {
|
|
createEmbedRuleBlock(md, {
|
|
type: "youtube",
|
|
name: "video_youtube",
|
|
syntaxPattern: /^@\[youtube([^\]]*)\]\(([^)]*)\)/,
|
|
meta([, info, id]) {
|
|
const { attrs: attrs$1 } = resolveAttrs(info);
|
|
return {
|
|
id,
|
|
autoplay: attrs$1.autoplay ?? false,
|
|
loop: attrs$1.loop ?? false,
|
|
start: timeToSeconds(attrs$1.start),
|
|
end: timeToSeconds(attrs$1.end),
|
|
title: attrs$1.title || "YouTube",
|
|
width: attrs$1.width ? parseRect(attrs$1.width) : "100%",
|
|
height: attrs$1.height ? parseRect(attrs$1.height) : void 0,
|
|
ratio: attrs$1.ratio
|
|
};
|
|
},
|
|
content(meta) {
|
|
const params = new URLSearchParams();
|
|
if (meta.autoplay) params.set("autoplay", "1");
|
|
if (meta.loop) params.set("loop", "1");
|
|
if (meta.start) params.set("start", meta.start.toString());
|
|
if (meta.end) params.set("end", meta.end.toString());
|
|
const src = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}`;
|
|
const { width, height, ratio, title } = meta;
|
|
return `<VPVideoEmbed${stringifyAttrs({
|
|
src,
|
|
width,
|
|
height,
|
|
ratio,
|
|
title,
|
|
type: "youtube"
|
|
})} />`;
|
|
}
|
|
});
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/embed/index.ts
|
|
function embedSyntaxPlugin(md, options) {
|
|
if (options.caniuse) {
|
|
const caniuse = options.caniuse === true ? {} : options.caniuse;
|
|
md.use(caniusePlugin, caniuse);
|
|
legacyCaniuse(md, caniuse);
|
|
}
|
|
if (options.pdf) md.use(pdfPlugin);
|
|
if (options.acfun) md.use(acfunPlugin);
|
|
if (options.bilibili) md.use(bilibiliPlugin);
|
|
if (options.youtube) md.use(youtubePlugin);
|
|
if (options.artPlayer) md.use(artPlayerPlugin);
|
|
if (options.audioReader) md.use(audioReaderPlugin);
|
|
if (options.codepen) md.use(codepenPlugin);
|
|
if (options.replit) md.use(replitPlugin);
|
|
if (options.codeSandbox) md.use(codeSandboxPlugin);
|
|
if (options.jsfiddle) md.use(jsfiddlePlugin);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/enhance/docsTitle.ts
|
|
const REG_HEADING = /^#\s*?([^#\s].*)?\n/;
|
|
/**
|
|
* 适配 主题的 文档页面标题,将 markdown 中的 h1 标题提取到 frontmatter 中,并将其删除,
|
|
* 以避免重复显示标题。
|
|
*/
|
|
function docsTitlePlugin(md) {
|
|
const render = md.render;
|
|
md.render = (source, env) => {
|
|
if (!env.filePathRelative) return render(source, env);
|
|
let { matter, content } = parseSource(source.trim());
|
|
let title = "";
|
|
content = content.trim().replace(REG_HEADING, (_, match) => {
|
|
title = match.trim();
|
|
return "";
|
|
});
|
|
source = `${matter}\n${content}`;
|
|
const result = render(source, env);
|
|
if (title) {
|
|
env.frontmatter ??= {};
|
|
env.frontmatter.title ??= title;
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
function parseSource(source) {
|
|
const char = "---";
|
|
if (!source.startsWith(char)) return {
|
|
matter: "",
|
|
content: source
|
|
};
|
|
else {
|
|
const end = source.indexOf(`\n${char}`);
|
|
const len = 4;
|
|
return {
|
|
matter: source.slice(0, end + len),
|
|
content: source.slice(end + len)
|
|
};
|
|
}
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/enhance/links.ts
|
|
function linksPlugin(md) {
|
|
const externalAttrs = {
|
|
target: "_blank",
|
|
rel: "noopener noreferrer"
|
|
};
|
|
let hasOpenInternalLink = false;
|
|
const internalTag = "VPLink";
|
|
function handleLinkOpen(tokens, idx, env) {
|
|
hasOpenInternalLink = false;
|
|
const token = tokens[idx];
|
|
const hrefIndex = token.attrIndex("href");
|
|
/* istanbul ignore if -- @preserve */
|
|
if (hrefIndex < 0) return;
|
|
const hrefLink = token.attrs[hrefIndex][1];
|
|
if (isLinkWithProtocol(hrefLink)) {
|
|
Object.entries(externalAttrs).forEach(([key, val]) => {
|
|
token.attrSet(key, val);
|
|
});
|
|
return;
|
|
}
|
|
if (hrefLink[0] === "#") return;
|
|
hasOpenInternalLink = true;
|
|
token.tag = internalTag;
|
|
const matched = hrefLink.match(/^([^#?]*?(?:\/|\.md|\.html))([#?].*)?$/);
|
|
if (matched) {
|
|
const rawPath = matched[1];
|
|
const { absolutePath, relativePath } = resolvePaths(rawPath, env.base || "/", env.filePathRelative ?? null);
|
|
(env.links ??= []).push({
|
|
raw: rawPath,
|
|
absolute: absolutePath,
|
|
relative: relativePath
|
|
});
|
|
}
|
|
}
|
|
md.renderer.rules.link_open = (tokens, idx, opts, env, self) => {
|
|
handleLinkOpen(tokens, idx, env);
|
|
return self.renderToken(tokens, idx, opts);
|
|
};
|
|
md.renderer.rules.link_close = (tokens, idx, opts, _env, self) => {
|
|
if (hasOpenInternalLink) {
|
|
hasOpenInternalLink = false;
|
|
tokens[idx].tag = internalTag;
|
|
}
|
|
return self.renderToken(tokens, idx, opts);
|
|
};
|
|
}
|
|
/**
|
|
* Resolve relative and absolute paths according to the `base` and `filePathRelative`
|
|
*/
|
|
function resolvePaths(rawPath, base, filePathRelative) {
|
|
let absolutePath;
|
|
let relativePath;
|
|
if (rawPath.startsWith("/")) if (rawPath.endsWith(".md")) {
|
|
absolutePath = path$2.join(base, rawPath);
|
|
relativePath = removeLeadingSlash$1(rawPath);
|
|
} else {
|
|
absolutePath = rawPath;
|
|
relativePath = path$2.relative(base, absolutePath);
|
|
}
|
|
else if (filePathRelative) {
|
|
relativePath = path$2.join(path$2.dirname(encodeURI(filePathRelative)), rawPath);
|
|
absolutePath = path$2.join(base, relativePath);
|
|
} else {
|
|
relativePath = rawPath.replace(/^(?:\.\/)?(.*)$/, "$1");
|
|
absolutePath = null;
|
|
}
|
|
return {
|
|
absolutePath,
|
|
relativePath
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/icon/createIconRule.ts
|
|
function createIconRule([l1, l2, r1, r2], deprecated) {
|
|
return (state, silent) => {
|
|
let found = false;
|
|
const max = state.posMax;
|
|
const start = state.pos;
|
|
if (state.src.charCodeAt(start) !== l1 || state.src.charCodeAt(start + 1) !== l2) return false;
|
|
const next = state.src.charCodeAt(start + 2);
|
|
if (next === 32 || next === 58) return false;
|
|
/* istanbul ignore if -- @preserve */
|
|
if (silent) return false;
|
|
if (max - start < 5) return false;
|
|
state.pos = start + 2;
|
|
while (state.pos < max) {
|
|
if (state.src.charCodeAt(state.pos) === r1 && state.src.charCodeAt(state.pos + 1) === r2) {
|
|
found = true;
|
|
break;
|
|
}
|
|
state.md.inline.skipToken(state);
|
|
}
|
|
if (!found || start + 2 === state.pos || state.src.charCodeAt(state.pos - 1) === 32) {
|
|
state.pos = start;
|
|
return false;
|
|
}
|
|
const info = state.src.slice(start + 2, state.pos);
|
|
state.posMax = state.pos;
|
|
state.pos = start + 2;
|
|
const icon = state.push("icon", "i", 0);
|
|
icon.markup = "::";
|
|
icon.content = info;
|
|
icon.meta = { deprecated };
|
|
state.pos = state.posMax + 2;
|
|
state.posMax = max;
|
|
return true;
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/icon/resolveIcon.ts
|
|
const RE_SIZE = /(?<=\s|^)=(.+?)(?:\s|$)/;
|
|
const RE_COLOR = /(?<=\s|^)\/(.+?)(?:\s|$)/;
|
|
const RE_PROVIDER = /^(iconify|iconfont|fontawesome)\s+/;
|
|
const RE_EXTRA_KEY = /(?:^|-)\d-/g;
|
|
function resolveIcon(content, options) {
|
|
let size = options.size;
|
|
let color = options.color;
|
|
let provider = options.provider || "iconify";
|
|
content = content.replace(RE_PROVIDER, (_, p) => {
|
|
provider = p;
|
|
return "";
|
|
}).replace(RE_SIZE, (_, s) => {
|
|
size = s;
|
|
return "";
|
|
}).replace(RE_COLOR, (_, c) => {
|
|
color = c;
|
|
return "";
|
|
}).trim();
|
|
const index = content.indexOf(" ");
|
|
const name = index === -1 ? content : content.slice(0, index);
|
|
const extra = index === -1 ? "" : content.slice(index + 1);
|
|
const props = {
|
|
provider,
|
|
size,
|
|
color,
|
|
name
|
|
};
|
|
if (!extra) return props;
|
|
const { attrs: attrs$1 } = resolveAttrs(extra);
|
|
const info = [];
|
|
const excludes = [];
|
|
for (const key in attrs$1) if (attrs$1[key] === true) {
|
|
excludes.push(key);
|
|
info.push(kebabCase(key).replace(RE_EXTRA_KEY, (m) => `${m.slice(0, -1)}`));
|
|
}
|
|
return {
|
|
...props,
|
|
extra: info.join(" "),
|
|
...omit(attrs$1, excludes)
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/icon/icon.ts
|
|
function iconRender(content, options) {
|
|
const icon = resolveIcon(content, options);
|
|
return `<VPIcon${stringifyAttrs(icon)} />`;
|
|
}
|
|
const iconPlugin = (md, options = {}) => {
|
|
/**
|
|
* ::collect:icon_name =size /color::
|
|
*/
|
|
md.inline.ruler.before("link", "icon", createIconRule([
|
|
58,
|
|
58,
|
|
58,
|
|
58
|
|
]));
|
|
/**
|
|
* :[collect:icon_name size/color]:
|
|
* @deprecated
|
|
*/
|
|
md.inline.ruler.before("link", "icon_deprecated", createIconRule([
|
|
58,
|
|
91,
|
|
93,
|
|
58
|
|
], true));
|
|
md.renderer.rules.icon = (tokens, idx, _, env) => {
|
|
const { content, meta } = tokens[idx];
|
|
let icon = content;
|
|
/* istanbul ignore if -- @preserve */
|
|
if (meta.deprecated) {
|
|
const [name, opt = ""] = content.split(" ");
|
|
const [size, color] = opt.trim().split("/");
|
|
icon = `${name}${size ? ` =${size}` : ""}${color ? ` /${color}` : ""}`;
|
|
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)})`);
|
|
}
|
|
return iconRender(icon, options);
|
|
};
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/icon/prepareIcon.ts
|
|
function getFontAwesomeCDNLink(type) {
|
|
return `https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/js/${type}.min.js`;
|
|
}
|
|
function prepareIcon(imports, options = {}) {
|
|
const setupContent = [];
|
|
const assets = [];
|
|
if (options.provider === "iconfont") assets.push(...toArray(options.assets).map((asset) => normalizeAsset(asset)).filter(notNullish));
|
|
else if (options.provider === "fontawesome") assets.push(...toArray(options.assets || "fontawesome").map((asset) => {
|
|
if (asset === "fontawesome") return [
|
|
"solid",
|
|
"regular",
|
|
"fontawesome"
|
|
].map(getFontAwesomeCDNLink).map((asset$1) => normalizeAsset(asset$1, "fontawesome"));
|
|
if (asset === "fontawesome-with-brands") return normalizeAsset(getFontAwesomeCDNLink("brands"), "fontawesome");
|
|
return null;
|
|
}).flat().filter(notNullish));
|
|
let hasStyle = false;
|
|
let hasScript = false;
|
|
for (const asset of uniqueBy(assets, (a, b) => a.link === b.link)) if (asset.type === "style") {
|
|
hasStyle = true;
|
|
setupContent.push(`useStyleTag('@import url("${asset.link}");')`);
|
|
} else if (asset.type === "script") {
|
|
hasScript = true;
|
|
setupContent.push(asset.provide === "fontawesome" ? `useScriptTag("${asset.link}", () => {}, { attrs: { "data-auto-replace-svg": "nest" } })` : `useScriptTag("${asset.link}")`);
|
|
}
|
|
if (hasScript || hasStyle) {
|
|
const exports = [];
|
|
if (hasScript) exports.push("useScriptTag");
|
|
if (hasStyle) exports.push("useStyleTag");
|
|
imports.add(`import { ${exports.join(", ")} } from '@vueuse/core'`);
|
|
}
|
|
return setupContent.join("\n ");
|
|
}
|
|
function normalizeAsset(asset, provide) {
|
|
const link = normalizeLink(asset);
|
|
if (asset.endsWith(".js")) return {
|
|
type: "script",
|
|
link,
|
|
provide
|
|
};
|
|
if (asset.endsWith(".css")) return {
|
|
type: "style",
|
|
link,
|
|
provide
|
|
};
|
|
logger$1.error("icon", `Can not recognize icon link: "${asset}"`);
|
|
return null;
|
|
}
|
|
function normalizeLink(link) {
|
|
if (isLinkHttp$1(link) || isLinkAbsolute(link)) return link;
|
|
return `//${link}`;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/inline/abbr.ts
|
|
const abbrPlugin = (md) => {
|
|
const { arrayReplaceAt, escapeRE, lib } = md.utils;
|
|
const OTHER_CHARS = " \r\n$+<=>^`|~";
|
|
const UNICODE_PUNCTUATION_REGEXP = lib.ucmicro.P.source;
|
|
const UNICODE_SPACE_REGEXP = lib.ucmicro.Z.source;
|
|
const WORDING_REGEXP_TEXT = `${UNICODE_PUNCTUATION_REGEXP}|${UNICODE_SPACE_REGEXP}|[${OTHER_CHARS.split("").map(escapeRE).join("")}]`;
|
|
const abbrDefinition = (state, startLine, _endLine, silent) => {
|
|
let labelEnd = -1;
|
|
let pos = state.bMarks[startLine] + state.tShift[startLine];
|
|
const max = state.eMarks[startLine];
|
|
if (pos + 2 >= max || state.src.charAt(pos++) !== "*" || state.src.charAt(pos++) !== "[") return false;
|
|
const labelStart = pos;
|
|
while (pos < max) {
|
|
const ch = state.src.charAt(pos);
|
|
if (ch === "[") return false;
|
|
if (ch === "]") {
|
|
labelEnd = pos;
|
|
break;
|
|
}
|
|
if (ch === "\\") pos++;
|
|
pos++;
|
|
}
|
|
if (labelEnd < 0 || state.src.charAt(labelEnd + 1) !== ":") return false;
|
|
if (silent) return true;
|
|
const label = state.src.slice(labelStart, labelEnd).replace(/\\(.)/g, "$1");
|
|
const title = state.src.slice(labelEnd + 2, max).trim();
|
|
if (!label.length || !title.length) return false;
|
|
(state.env.abbreviations ??= {})[`:${label}`] ??= title;
|
|
state.line = startLine + 1;
|
|
return true;
|
|
};
|
|
const abbrReplace = (state) => {
|
|
const tokens = state.tokens;
|
|
const { abbreviations } = state.env;
|
|
if (!abbreviations) return;
|
|
const abbreviationsRegExpText = Object.keys(abbreviations).map((x) => x.substring(1)).sort((a, b) => b.length - a.length).map(escapeRE).join("|");
|
|
const regexpSimple = /* @__PURE__ */ new RegExp(`(?:${abbreviationsRegExpText})`);
|
|
const regExp = new RegExp(`(^|${WORDING_REGEXP_TEXT})(${abbreviationsRegExpText})($|${WORDING_REGEXP_TEXT})`, "g");
|
|
for (const token of tokens) {
|
|
if (token.type !== "inline") continue;
|
|
let children = token.children;
|
|
for (let index = children.length - 1; index >= 0; index--) {
|
|
const currentToken = children[index];
|
|
if (currentToken.type !== "text") continue;
|
|
const text = currentToken.content;
|
|
regExp.lastIndex = 0;
|
|
if (!regexpSimple.test(text)) continue;
|
|
const nodes = [];
|
|
let match;
|
|
let pos = 0;
|
|
while (match = regExp.exec(text)) {
|
|
const [, before, word, after] = match;
|
|
if (match.index > 0 || before.length > 0) {
|
|
const token$1 = new state.Token("text", "", 0);
|
|
token$1.content = text.slice(pos, match.index + before.length);
|
|
nodes.push(token$1);
|
|
}
|
|
const abbrToken = new state.Token("abbreviation", "Abbreviation", 0);
|
|
abbrToken.content = word;
|
|
abbrToken.info = abbreviations[`:${word}`];
|
|
nodes.push(abbrToken);
|
|
regExp.lastIndex -= after.length;
|
|
pos = regExp.lastIndex;
|
|
}
|
|
if (!nodes.length) continue;
|
|
if (pos < text.length) {
|
|
const token$1 = new state.Token("text", "", 0);
|
|
token$1.content = text.slice(pos);
|
|
nodes.push(token$1);
|
|
}
|
|
token.children = children = arrayReplaceAt(children, index, nodes);
|
|
}
|
|
}
|
|
};
|
|
md.block.ruler.before("reference", "abbr_definition", abbrDefinition, { alt: ["paragraph", "reference"] });
|
|
md.core.ruler.after("linkify", "abbr_replace", abbrReplace);
|
|
md.renderer.rules.abbreviation = (tokens, idx, _, env) => {
|
|
const { content, info } = tokens[idx];
|
|
const rendered = md.renderInline(info, cleanMarkdownEnv(env));
|
|
return `<Abbreviation aria-label="${rendered.replace(/<[^>]*>/g, "")}">${content}${info ? `<template #tooltip>${rendered}</template>` : ""}</Abbreviation>`;
|
|
};
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/inline/annotation.ts
|
|
const annotationDef = (state, startLine, endLine, silent) => {
|
|
const start = state.bMarks[startLine] + state.tShift[startLine];
|
|
const max = state.eMarks[startLine];
|
|
if (start + 4 > max || state.src.charAt(start) !== "[" || state.src.charAt(start + 1) !== "+") return false;
|
|
let pos = start + 2;
|
|
while (pos < max) {
|
|
if (state.src.charAt(pos) === " ") return false;
|
|
if (state.src.charAt(pos) === "]") break;
|
|
pos++;
|
|
}
|
|
if (pos === start + 2 || pos + 1 >= max || state.src.charAt(++pos) !== ":") return false;
|
|
if (silent) return true;
|
|
pos++;
|
|
const data = state.env.annotations ??= {};
|
|
const label = state.src.slice(start + 2, pos - 2);
|
|
let annotation = state.src.slice(pos, max).trim();
|
|
let nextLine = startLine + 1;
|
|
while (nextLine < endLine) {
|
|
const nextStart = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
const nextMax = state.eMarks[nextLine];
|
|
const source = state.src.slice(nextStart, nextMax).trim();
|
|
if (state.sCount[nextLine] < state.blkIndent + 2 && source !== "") break;
|
|
annotation += `\n${source}`;
|
|
nextLine++;
|
|
}
|
|
(data[`:${label}`] ??= {
|
|
sources: [],
|
|
rendered: []
|
|
}).sources.push(annotation);
|
|
state.line = nextLine;
|
|
return true;
|
|
};
|
|
const annotationRef = (state, silent) => {
|
|
const start = state.pos;
|
|
const max = state.posMax;
|
|
if (start + 3 > max || typeof state.env.annotations === "undefined" || state.src.charAt(start) !== "[" || state.src.charAt(start + 1) !== "+") return false;
|
|
let pos = start + 2;
|
|
while (pos < max) {
|
|
if (state.src.charAt(pos) === " " || state.src.charAt(pos) === "\n") return false;
|
|
if (state.src.charAt(pos) === "]") break;
|
|
pos++;
|
|
}
|
|
if (pos === start + 2 || pos >= max) return false;
|
|
pos++;
|
|
const label = state.src.slice(start + 2, pos - 1);
|
|
if ((state.env.annotations?.[`:${label}`]?.sources ?? []).length === 0) return false;
|
|
if (!silent) {
|
|
const refToken = state.push("annotation_ref", "", 0);
|
|
refToken.meta = { label };
|
|
}
|
|
state.pos = pos;
|
|
state.posMax = max;
|
|
return true;
|
|
};
|
|
const annotationPlugin = (md) => {
|
|
md.renderer.rules.annotation_ref = (tokens, idx, _, env) => {
|
|
const label = tokens[idx].meta.label;
|
|
const data = env.annotations[`:${label}`];
|
|
return `<Annotation label="${label}" :total="${data.sources.length}">${data.sources.map((source, i) => {
|
|
const annotation = data.rendered[i] ??= md.render(source, cleanMarkdownEnv(env, ["references"]));
|
|
return `<template #item-${i}>${annotation}</template>`;
|
|
}).join("")}</Annotation>`;
|
|
};
|
|
md.inline.ruler.before("image", "annotation_ref", annotationRef);
|
|
md.block.ruler.before("reference", "annotation", annotationDef, { alt: ["paragraph", "reference"] });
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/inline/plot.ts
|
|
const plotDef = (state, silent) => {
|
|
let found = false;
|
|
const max = state.posMax;
|
|
const start = state.pos;
|
|
if (state.src.charCodeAt(start) !== 33 || state.src.charCodeAt(start + 1) !== 33) return false;
|
|
const next = state.src.charCodeAt(start + 2);
|
|
if (next === 32 || next === 33) return false;
|
|
/* istanbul ignore if -- @preserve */
|
|
if (silent) return false;
|
|
if (max - start < 5) return false;
|
|
state.pos = start + 2;
|
|
while (state.pos < max) {
|
|
if (state.src.charCodeAt(state.pos) === 33 && state.src.charCodeAt(state.pos + 1) === 33) {
|
|
found = true;
|
|
break;
|
|
}
|
|
state.md.inline.skipToken(state);
|
|
}
|
|
if (!found || start + 2 === state.pos || state.src.charCodeAt(state.pos - 1) === 32) {
|
|
state.pos = start;
|
|
return false;
|
|
}
|
|
const content = state.src.slice(start + 2, state.pos);
|
|
state.posMax = state.pos;
|
|
state.pos = start + 2;
|
|
const openToken = state.push("plot_inline_open", "Plot", 1);
|
|
openToken.markup = "!!";
|
|
openToken.content = content;
|
|
const contentToken = state.push("text", "", 0);
|
|
contentToken.content = content;
|
|
const closeToken = state.push("plot_inline_close", "Plot", -1);
|
|
closeToken.markup = "!!";
|
|
state.pos = state.posMax + 2;
|
|
state.posMax = max;
|
|
return true;
|
|
};
|
|
const plotPlugin = (md) => {
|
|
md.inline.ruler.before("emphasis", "plot", plotDef);
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/node/inline/index.ts
|
|
function inlineSyntaxPlugin(md, options) {
|
|
md.use(attrs);
|
|
md.use(mark);
|
|
md.use(sub);
|
|
md.use(sup);
|
|
md.use(footnote);
|
|
md.use(tasklist);
|
|
if (options.annotation)
|
|
/**
|
|
* xxx [+foo] xxx
|
|
*
|
|
* [+foo]: xxx
|
|
*/
|
|
md.use(annotationPlugin);
|
|
if (options.abbr)
|
|
/**
|
|
* a HTML element
|
|
*
|
|
* [HTML]: A HTML element description
|
|
*/
|
|
md.use(abbrPlugin);
|
|
if (options.plot === true || isPlainObject(options.plot)) md.use(plotPlugin);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/prepareConfigFile.ts
|
|
const { url: filepath } = import.meta;
|
|
const __dirname = getDirname(filepath);
|
|
const CLIENT_FOLDER = ensureEndingSlash(path.resolve(__dirname, "../client"));
|
|
async function prepareConfigFile(app, options) {
|
|
const imports = /* @__PURE__ */ new Set();
|
|
const enhances = /* @__PURE__ */ new Set();
|
|
imports.add(`import Tabs from '${CLIENT_FOLDER}components/Tabs.vue'`);
|
|
enhances.add(`app.component('Tabs', Tabs)`);
|
|
imports.add(`import CodeTabs from '${CLIENT_FOLDER}components/CodeTabs.vue'`);
|
|
enhances.add(`app.component('CodeTabs', CodeTabs)`);
|
|
if (options.pdf) {
|
|
imports.add(`import PDFViewer from '${CLIENT_FOLDER}components/PDFViewer.vue'`);
|
|
enhances.add(`app.component('PDFViewer', PDFViewer)`);
|
|
}
|
|
if (options.acfun || options.bilibili || options.youtube) {
|
|
imports.add(`import VPVideoEmbed from '${CLIENT_FOLDER}components/VPVideoEmbed.vue'`);
|
|
enhances.add(`app.component('VPVideoEmbed', VPVideoEmbed)`);
|
|
}
|
|
if (options.codepen) {
|
|
imports.add(`import CodePen from '${CLIENT_FOLDER}components/CodePen.vue'`);
|
|
enhances.add(`app.component('CodePenViewer', CodePen)`);
|
|
}
|
|
if (options.jsfiddle) {
|
|
imports.add(`import JSFiddle from '${CLIENT_FOLDER}components/JsFiddle.vue'`);
|
|
enhances.add(`app.component('JSFiddleViewer', JSFiddle)`);
|
|
}
|
|
if (options.replit) {
|
|
imports.add(`import Replit from '${CLIENT_FOLDER}components/Replit.vue'`);
|
|
enhances.add(`app.component('ReplitViewer', Replit)`);
|
|
}
|
|
if (options.codeSandbox) {
|
|
imports.add(`import CodeSandbox from '${CLIENT_FOLDER}components/CodeSandbox.vue'`);
|
|
enhances.add(`app.component('CodeSandboxViewer', CodeSandbox)`);
|
|
}
|
|
if (options.plot) {
|
|
imports.add(`import Plot from '${CLIENT_FOLDER}components/Plot.vue'`);
|
|
enhances.add(`app.component('Plot', Plot)`);
|
|
}
|
|
if (options.repl) {
|
|
imports.add(`import CodeRepl from '${CLIENT_FOLDER}components/CodeRepl.vue'`);
|
|
enhances.add(`app.component('CodeRepl', CodeRepl)`);
|
|
}
|
|
if (options.caniuse) {
|
|
imports.add(`import CanIUse from '${CLIENT_FOLDER}components/CanIUse.vue'`);
|
|
enhances.add(`app.component('CanIUseViewer', CanIUse)`);
|
|
}
|
|
if (options.fileTree || options.codeTree) {
|
|
imports.add(`import FileTreeNode from '${CLIENT_FOLDER}components/FileTreeNode.vue'`);
|
|
enhances.add(`app.component('FileTreeNode', FileTreeNode)`);
|
|
}
|
|
if (options.codeTree) {
|
|
imports.add(`import VPCodeTree from '${CLIENT_FOLDER}components/VPCodeTree.vue'`);
|
|
enhances.add(`app.component('VPCodeTree', VPCodeTree)`);
|
|
}
|
|
if (options.artPlayer) {
|
|
imports.add(`import ArtPlayer from '${CLIENT_FOLDER}components/ArtPlayer.vue'`);
|
|
enhances.add(`app.component('ArtPlayer', ArtPlayer)`);
|
|
}
|
|
if (options.audioReader) {
|
|
imports.add(`import AudioReader from '${CLIENT_FOLDER}components/AudioReader.vue'`);
|
|
enhances.add(`app.component('AudioReader', AudioReader)`);
|
|
}
|
|
if (options.demo) {
|
|
imports.add(`import VPDemoBasic from '${CLIENT_FOLDER}components/VPDemoBasic.vue'`);
|
|
imports.add(`import VPDemoNormal from '${CLIENT_FOLDER}components/VPDemoNormal.vue'`);
|
|
enhances.add(`app.component('VPDemoBasic', VPDemoBasic)`);
|
|
enhances.add(`app.component('VPDemoNormal', VPDemoNormal)`);
|
|
}
|
|
if (options.annotation) {
|
|
imports.add(`import Annotation from '${CLIENT_FOLDER}components/Annotation.vue'`);
|
|
enhances.add(`app.component('Annotation', Annotation)`);
|
|
}
|
|
if (options.abbr) {
|
|
imports.add(`import Abbreviation from '${CLIENT_FOLDER}components/Abbreviation.vue'`);
|
|
enhances.add(`app.component('Abbreviation', Abbreviation)`);
|
|
}
|
|
if (options.timeline) {
|
|
imports.add(`import VPTimeline from '${CLIENT_FOLDER}components/VPTimeline.vue'`);
|
|
imports.add(`import VPTimelineItem from '${CLIENT_FOLDER}components/VPTimelineItem.vue'`);
|
|
enhances.add(`app.component('VPTimeline', VPTimeline)`);
|
|
enhances.add(`app.component('VPTimelineItem', VPTimelineItem)`);
|
|
}
|
|
if (options.collapse) {
|
|
imports.add(`import VPCollapse from '${CLIENT_FOLDER}components/VPCollapse.vue'`);
|
|
imports.add(`import VPCollapseItem from '${CLIENT_FOLDER}components/VPCollapseItem.vue'`);
|
|
enhances.add(`app.component('VPCollapse', VPCollapse)`);
|
|
enhances.add(`app.component('VPCollapseItem', VPCollapseItem)`);
|
|
}
|
|
if (options.chat) imports.add(`import '${CLIENT_FOLDER}styles/chat.css'`);
|
|
if (options.field) {
|
|
imports.add(`import VPField from '${CLIENT_FOLDER}components/VPField.vue'`);
|
|
enhances.add(`app.component('VPField', VPField)`);
|
|
}
|
|
if (options.table) {
|
|
imports.add(`import VPTable from '${CLIENT_FOLDER}components/VPTable.vue'`);
|
|
enhances.add(`app.component('VPTable', VPTable)`);
|
|
}
|
|
const setupIcon = prepareIcon(imports, options.icon);
|
|
return app.writeTemp("md-power/config.js", `\
|
|
import { defineClientConfig } from 'vuepress/client'
|
|
${Array.from(imports.values()).join("\n")}
|
|
|
|
import '${CLIENT_FOLDER}styles/index.css'
|
|
|
|
export default defineClientConfig({
|
|
enhance({ router, app }) {
|
|
${Array.from(enhances.values()).map((item) => ` ${item}`).join("\n")}
|
|
},
|
|
setup() {
|
|
${setupIcon}
|
|
}
|
|
})
|
|
`);
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/provideData.ts
|
|
function provideData(options) {
|
|
const mardownOptions = {
|
|
plot: options.plot,
|
|
pdf: options.pdf
|
|
};
|
|
const icon = options.icon ?? { provider: "iconify" };
|
|
return {
|
|
__MD_POWER_INJECT_OPTIONS__: mardownOptions,
|
|
__MD_POWER_DASHJS_INSTALLED__: isPackageExists("dashjs"),
|
|
__MD_POWER_HLSJS_INSTALLED__: isPackageExists("hls.js"),
|
|
__MD_POWER_MPEGTSJS_INSTALLED__: isPackageExists("mpegts.js"),
|
|
__MD_POWER_ICON_PROVIDER__: icon.provider || "iconify",
|
|
__MD_POWER_ICON_PREFIX__: icon.prefix || ""
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/node/plugin.ts
|
|
function markdownPowerPlugin(options = {}) {
|
|
return {
|
|
name: "vuepress-plugin-md-power",
|
|
clientConfigFile: (app) => prepareConfigFile(app, options),
|
|
define: provideData(options),
|
|
extendsBundlerOptions(bundlerOptions, app) {
|
|
if (options.repl) {
|
|
addViteOptimizeDepsInclude(bundlerOptions, app, [
|
|
"shiki/core",
|
|
"shiki/wasm",
|
|
"shiki/engine/oniguruma"
|
|
]);
|
|
if (options.repl.python) addViteOptimizeDepsInclude(bundlerOptions, app, ["pyodide"]);
|
|
}
|
|
if (options.artPlayer) addViteOptimizeDepsInclude(bundlerOptions, app, [
|
|
"artplayer",
|
|
"dashjs",
|
|
"hls.js",
|
|
"mpegts.js/dist/mpegts.js"
|
|
]);
|
|
},
|
|
extendsMarkdown: async (md, app) => {
|
|
linksPlugin(md);
|
|
docsTitlePlugin(md);
|
|
embedSyntaxPlugin(md, options);
|
|
inlineSyntaxPlugin(md, options);
|
|
iconPlugin(md, options.icon ?? (isPlainObject$1(options.icons) ? options.icons : {}));
|
|
if (options.demo) demoPlugin(app, md);
|
|
await containerPlugin(app, md, options);
|
|
await imageSizePlugin(app, md, options.imageSize);
|
|
},
|
|
onPrepared: async () => {
|
|
if (options.demo) await waitDemoRender();
|
|
},
|
|
onWatched(app, watchers) {
|
|
if (options.demo) demoWatcher(app, watchers);
|
|
},
|
|
extendsPage: (page) => {
|
|
if (options.demo) extendsPageWithDemo(page);
|
|
if (options.codeTree) extendsPageWithCodeTree(page);
|
|
}
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
export { createCodeTabIconGetter, markdownPowerPlugin, resolveImageSize }; |