first commit
This commit is contained in:
commit
197cae9a54
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
31
bolt.config.ts
Normal file
31
bolt.config.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
const pkg = require("./package.json");
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 文档版本
|
||||
*/
|
||||
version: pkg.version,
|
||||
/**
|
||||
* <title>
|
||||
*/
|
||||
title: "API文档 • Bolt Design",
|
||||
/**
|
||||
* logo 区域标题
|
||||
*/
|
||||
subTitle: "Bolt Design",
|
||||
/**
|
||||
* 配置自定义页面页面
|
||||
*/
|
||||
customPages: [
|
||||
{
|
||||
path: "/",
|
||||
src: "Welcome",
|
||||
title: "首页",
|
||||
},
|
||||
{
|
||||
path: "/intro",
|
||||
src: "Intro",
|
||||
title: "文档",
|
||||
},
|
||||
],
|
||||
};
|
13
index.html
Normal file
13
index.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
12290
package-lock.json
generated
Normal file
12290
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
package.json
Normal file
49
package.json
Normal file
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "bolt",
|
||||
"private": true,
|
||||
"description": "基于 mdx-react 的自动化组件、文档脚手架",
|
||||
"version": "1.2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "pnpm run build:lib && pnpm run build:doc",
|
||||
"build:lib": "tsc -p ./tsconfig.lib.json && vite build --mode lib && pnpm build:less",
|
||||
"build:doc": "tsc -p ./tsconfig.prod.json && vite build --mode doc",
|
||||
"build:less": "esno plugin/build/autocss.ts",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mapbox/rehype-prism": "^0.8.0",
|
||||
"@mdx-js/react": "^2.1.3",
|
||||
"@mdx-js/rollup": "^2.1.3",
|
||||
"antd": "^4.23.5",
|
||||
"less": "^4.1.3",
|
||||
"markdown-toc": "^1.2.0",
|
||||
"object-hash": "3.0.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"rc-tooltip": "^5.2.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loadable": "^5.5.0",
|
||||
"react-router": "^6.3.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scroll": "^1.8.7",
|
||||
"react-tooltip": "^4.2.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ricons/fluent": "^0.12.0",
|
||||
"@types/node": "^18.7.9",
|
||||
"@types/object-hash": "2.2.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@types/react": "^18.0.17",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-scroll": "1.8.4",
|
||||
"@vitejs/plugin-react": "^2.0.1",
|
||||
"cpy": "^9.0.1",
|
||||
"fast-glob": "^3.2.12",
|
||||
"terser": "^5.15.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.7",
|
||||
"vite-plugin-dts": "^1.6.5"
|
||||
}
|
||||
}
|
49
plugin/build/autocss.ts
Normal file
49
plugin/build/autocss.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* css 分包,自动引入到rollup 打包后的 组件 index.js 中
|
||||
* e.g Buttom/index.js
|
||||
* before import './index.less'
|
||||
* after import './index.css'
|
||||
*/
|
||||
|
||||
import cpy from "cpy";
|
||||
import { resolve, dirname } from "path";
|
||||
import { promises as fs } from "fs";
|
||||
import less from "less";
|
||||
import glob from "fast-glob";
|
||||
|
||||
const rootPath = process.cwd();
|
||||
const sourceDir = resolve(rootPath, "src");
|
||||
//lib文件目录
|
||||
const targetLib = resolve(rootPath, "dist/ui");
|
||||
|
||||
//src目录
|
||||
const srcDir = resolve(rootPath, "src");
|
||||
|
||||
const buildLess = async () => {
|
||||
//直接将less文件复制到打包后目录
|
||||
await cpy([`${sourceDir}/**/*.less`], targetLib);
|
||||
|
||||
//获取打包后.less文件目录(lib和es一样)
|
||||
const lessFiles = await glob("**/*.less", { cwd: srcDir, onlyFiles: true });
|
||||
|
||||
//遍历含有less的目录
|
||||
for (let path in lessFiles) {
|
||||
const filePath = `${srcDir}/${lessFiles[path]}`;
|
||||
//获取less文件字符串
|
||||
const lessCode = await fs.readFile(filePath, "utf-8");
|
||||
//将less解析成css
|
||||
|
||||
const code = await less.render(lessCode, {
|
||||
//指定src下对应less文件的文件夹为目录
|
||||
paths: [srcDir, dirname(filePath)],
|
||||
});
|
||||
|
||||
//拿到.css后缀path
|
||||
const cssPath = lessFiles[path].replace(".less", ".css");
|
||||
|
||||
//将css写入对应目录
|
||||
await fs.writeFile(resolve(targetLib, cssPath), code.css);
|
||||
}
|
||||
};
|
||||
|
||||
buildLess();
|
4
plugin/build/autopkg.ts
Normal file
4
plugin/build/autopkg.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* 给 dist 构建文件夹组件生成 package.json 发包用
|
||||
*/
|
||||
const buildPkg = () => {};
|
48
plugin/build/vite.conf.ts
Normal file
48
plugin/build/vite.conf.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import path from "path";
|
||||
|
||||
/**
|
||||
* 构建 doc 网页 vite构建配置
|
||||
*/
|
||||
const docPage = { outDir: "dist/doc" };
|
||||
|
||||
/**
|
||||
* 构建组件 lib vite构建配置 组件按需
|
||||
*/
|
||||
const componentLib = {
|
||||
minify: true, // boolean | 'terser' | 'esbuild'
|
||||
sourcemap: true, // 输出单独 source文件
|
||||
brotliSize: true, // 生成压缩大小报告
|
||||
cssCodeSplit: false, // css 从 js 组件中分离出去
|
||||
rollupOptions: {
|
||||
external: ["react", "react-dom", /\.less/], // 忽略js 中的 less 文件
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
entryFileNames: "[name].js",
|
||||
preserveModules: true,
|
||||
dir: "dist/ui",
|
||||
preserveModulesRoot: "src",
|
||||
// assetFileNames: (assetInfo) => processAssets(assetInfo),
|
||||
},
|
||||
],
|
||||
},
|
||||
lib: {
|
||||
entry: path.resolve(process.cwd(), "src/index.ts"),
|
||||
name: "Bolt", // 暴露的全局变量名称
|
||||
},
|
||||
};
|
||||
|
||||
export const build = (mode: string, config = {}) => {
|
||||
switch (mode) {
|
||||
case "doc":
|
||||
config = docPage;
|
||||
break;
|
||||
case "lib":
|
||||
config = componentLib;
|
||||
break;
|
||||
default:
|
||||
config = {};
|
||||
break;
|
||||
}
|
||||
return config;
|
||||
};
|
78
plugin/common/ApiTable.tsx
Normal file
78
plugin/common/ApiTable.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { IPropMeta } from "../types";
|
||||
|
||||
interface IProps {
|
||||
propList: IPropMeta[];
|
||||
}
|
||||
|
||||
type IHeader = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 可以拓展更多的annotation tag
|
||||
*/
|
||||
const tableHeader: IHeader = {
|
||||
name: "参数名",
|
||||
description: "说明",
|
||||
type: "类型",
|
||||
default: "默认值",
|
||||
required: "必须",
|
||||
version: "版本",
|
||||
};
|
||||
|
||||
export default function ApiTable(props: IProps) {
|
||||
const [propList, setPropList] = useState<IPropMeta[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
props.propList?.length > 0 ? setPropList(props.propList) : setPropList([]);
|
||||
}, [props.propList]);
|
||||
|
||||
const format = (prop: string, v: any) => {
|
||||
if (prop === "version") return !v ? "" : "v" + v;
|
||||
if (typeof v === "boolean") return !v ? "" : "" + v;
|
||||
return v;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="anchor-api-table api-table-container">
|
||||
<h1>API</h1>
|
||||
<table className="api-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{Object.keys(tableHeader).map((prop) => {
|
||||
return (
|
||||
<th key={prop}>
|
||||
<div>{tableHeader[prop]}</div>
|
||||
{/* <div className="annotation-prop">@{prop}</div> */}
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{propList?.length > 0 ? (
|
||||
propList.map((m) => {
|
||||
return (
|
||||
<tr key={m.name}>
|
||||
{Object.keys(tableHeader).map((prop) => {
|
||||
return <td key={prop}>{format(prop, (m as any)[prop])}</td>;
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<tr key={"without-data"}>
|
||||
<td
|
||||
style={{ textAlign: "center", fontWeight: "normal" }}
|
||||
colSpan={Object.keys(tableHeader).length}
|
||||
>
|
||||
该组件 props.ts 缺失, 或未定义任何 interface
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
59
plugin/common/Aside.tsx
Normal file
59
plugin/common/Aside.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { IRoute } from "../types";
|
||||
|
||||
interface IProps {
|
||||
menus: IRoute[];
|
||||
subTitle: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export default function Aside(props: IProps) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const version = props.version;
|
||||
const [menuList, setMenuList] = useState<IRoute[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.menus?.length > 0) setMenuList(props.menus);
|
||||
}, [props.menus]);
|
||||
|
||||
useEffect(() => {
|
||||
const path = location.pathname.replace(/\//, "");
|
||||
const isCompoPath = menuList.map((i) => i.path).includes(path);
|
||||
if (!isCompoPath)
|
||||
setMenuList((p) => p.map((i) => ({ ...i, active: false })));
|
||||
}, [location.pathname]);
|
||||
|
||||
const toggle = (route: IRoute) => {
|
||||
setMenuList((prev) => {
|
||||
return prev.map((r: IRoute) => {
|
||||
r.active = r.path === route.path;
|
||||
return r;
|
||||
});
|
||||
});
|
||||
navigate(route.path);
|
||||
};
|
||||
|
||||
return (
|
||||
<aside>
|
||||
<div className="logo">
|
||||
<img src="logo.png" />
|
||||
<span data-version={version}>{props.subTitle}</span>
|
||||
</div>
|
||||
<h4>组件({menuList.length})</h4>
|
||||
<ul className="nav-list">
|
||||
{menuList.map((route: IRoute) => (
|
||||
<li key={route.path}>
|
||||
<a
|
||||
className={route.active ? "active" : ""}
|
||||
onClick={() => toggle(route)}
|
||||
>
|
||||
{route.name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</aside>
|
||||
);
|
||||
}
|
53
plugin/common/Code.tsx
Normal file
53
plugin/common/Code.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import Code20Regular from "@ricons/fluent/Code20Regular";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/plugins/line-numbers/prism-line-numbers";
|
||||
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
|
||||
// import "prismjs/components/prism";
|
||||
import "prismjs/components/prism-javascript";
|
||||
import "prismjs/components/prism-typescript";
|
||||
import "prismjs/components/prism-bash";
|
||||
import "prismjs/components/prism-css";
|
||||
|
||||
interface IProps {
|
||||
/**
|
||||
* @description prism 渲染的代码文档字符串
|
||||
*/
|
||||
raw: string;
|
||||
/**
|
||||
* 语言类型
|
||||
*/
|
||||
lang: string;
|
||||
}
|
||||
|
||||
export default function Code(props: IProps) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const codeRef = useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
Prism.highlightElement(codeRef.current);
|
||||
}, [props.raw]);
|
||||
|
||||
return (
|
||||
<div className="code-container">
|
||||
<div style={{ paddingTop: "10px", textAlign: "right" }}>
|
||||
<span
|
||||
data-placement="示例代码"
|
||||
className={`btn ${visible ? "active" : ""}`}
|
||||
onClick={() => setVisible((v) => !v)}
|
||||
>
|
||||
<Code20Regular />
|
||||
</span>
|
||||
</div>
|
||||
<pre
|
||||
className="line-numbers"
|
||||
data-lang={props.lang}
|
||||
style={{ display: visible ? "block" : "none" }}
|
||||
>
|
||||
<code ref={codeRef} className={`language-${props.lang}`}>
|
||||
{props.raw}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
65
plugin/common/MdToc.tsx
Normal file
65
plugin/common/MdToc.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { TToc } from "../types";
|
||||
import { scroller } from "react-scroll";
|
||||
|
||||
interface IProps {
|
||||
children?: any;
|
||||
toc: TToc[];
|
||||
}
|
||||
|
||||
const tocEndItem: TToc = {
|
||||
content: "API",
|
||||
slug: "API",
|
||||
lvl: 1,
|
||||
anchor: "anchor-api-table",
|
||||
};
|
||||
|
||||
export default function Toc(props: IProps) {
|
||||
const [contentTable, setContentTable] = useState<TToc[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const tocWithApiItem = [...props.toc, tocEndItem];
|
||||
props.toc?.length > 0
|
||||
? setContentTable(tocWithApiItem.map((i) => ({ ...i, active: false })))
|
||||
: setContentTable([]);
|
||||
}, [props.toc]);
|
||||
|
||||
/**
|
||||
* 点击右侧 toc 跳转
|
||||
*/
|
||||
const onClickTocItem = (anchor: string) => {
|
||||
setContentTable((p) =>
|
||||
p?.map((i) => ({ ...i, active: i.anchor === anchor }))
|
||||
);
|
||||
scroller.scrollTo(anchor, {
|
||||
duration: 800,
|
||||
delay: 0,
|
||||
smooth: "easeInOutQuart",
|
||||
offset: -28,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="toc curtain-bottom">
|
||||
<h4>快速导航</h4>
|
||||
{contentTable && (
|
||||
<ul className="hidden-scrollbar">
|
||||
{contentTable.map((item: TToc) => {
|
||||
return (
|
||||
<li key={item.content}>
|
||||
<div
|
||||
onClick={() => onClickTocItem(item.anchor)}
|
||||
className={`toc-lvl toc-lvl-${item.lvl} ${
|
||||
item.active ? "active" : ""
|
||||
}`}
|
||||
>
|
||||
{item.content}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
65
plugin/common/Nav.tsx
Normal file
65
plugin/common/Nav.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import Search20Regular from "@ricons/fluent/Search20Regular";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { ICustomPage } from "../types";
|
||||
interface IProps {
|
||||
customPages: ICustomPage[];
|
||||
}
|
||||
|
||||
interface INavItem extends ICustomPage {
|
||||
active: boolean;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export default function Nav(props: IProps) {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [navList, setNavList] = useState<INavItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const navList = props.customPages.map((i: ICustomPage) => ({
|
||||
...i,
|
||||
key: i.path,
|
||||
active: false,
|
||||
}));
|
||||
setNavList(navList);
|
||||
}, [props.customPages]);
|
||||
|
||||
/**
|
||||
* 路由变化同步页面文字 active 状态
|
||||
*/
|
||||
useEffect(() => {
|
||||
const path = location.pathname;
|
||||
setNavList((p) => p.map((i) => ({ ...i, active: path === i.path })));
|
||||
}, [location.pathname]);
|
||||
|
||||
/**
|
||||
* 点击切换页面
|
||||
*/
|
||||
const onClickNavItem = (key: string) => {
|
||||
const nav = navList.find((i: INavItem) => i.key === key);
|
||||
setNavList((p: INavItem[]) =>
|
||||
p.map((i: INavItem) => ({ ...i, active: key === i.key }))
|
||||
);
|
||||
navigate(nav!.path);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<div className="start"></div>
|
||||
<div className="end">
|
||||
<div className="search-container">
|
||||
<Search20Regular />
|
||||
<input type="text" placeholder="搜索 / 待开发" />
|
||||
</div>
|
||||
<ul>
|
||||
{navList.map((item) => (
|
||||
<li key={item.key} onClick={() => onClickNavItem(item.key)}>
|
||||
<span className={item.active ? "active" : ""}>{item.title}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
534
plugin/common/style/index.less
Normal file
534
plugin/common/style/index.less
Normal file
|
@ -0,0 +1,534 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-grey: #f2f3f5;
|
||||
--color-grey-lighter: #f7f8fa;
|
||||
--color-text-1: #1d2129;
|
||||
--color-text-2: #4e5969;
|
||||
--color-text-3: rgb(118, 124, 130);
|
||||
--color-primary: rgba(22, 93, 255, 1);
|
||||
--color-primary-lighter: rgba(106, 161, 255, 1);
|
||||
--color-primary-lighter-5: rgba(106, 161, 255, 0.1);
|
||||
--color-primary-hover: rgba(22, 93, 255, 0.05);
|
||||
}
|
||||
|
||||
@import "./prism-laserwave.less";
|
||||
|
||||
html,
|
||||
body {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
font-family: Inter, "-apple-system", BlinkMacSystemFont, "PingFang SC",
|
||||
"Hiragino Sans GB", "noto sans", "Microsoft YaHei", "Helvetica Neue",
|
||||
Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--color-primary-lighter);
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.curtain-top {
|
||||
position: relative;
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: 20px;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 32px;
|
||||
background: linear-gradient(to top, transparent, #fff 70%);
|
||||
}
|
||||
}
|
||||
|
||||
.curtain-bottom {
|
||||
position: relative;
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 32px;
|
||||
background: linear-gradient(to bottom, transparent, #fff 70%);
|
||||
}
|
||||
}
|
||||
|
||||
.hidden-scrollbar {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.box {
|
||||
margin-top: 24px;
|
||||
padding: 48px;
|
||||
color: #282f38;
|
||||
background-color: #fff;
|
||||
border: 1px solid var(--color-grey);
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
|
||||
.root-container {
|
||||
nav {
|
||||
padding: 0 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
left: 260px;
|
||||
right: 0;
|
||||
height: 60px;
|
||||
// border-bottom: 1px solid var(--color-grey);
|
||||
backdrop-filter: saturate(50%) blur(8px);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
z-index: 199401;
|
||||
|
||||
.end {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.search-container {
|
||||
position: relative;
|
||||
margin-right: 40px;
|
||||
height: 32px;
|
||||
width: 180px;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
left: 5px;
|
||||
top: 50%;
|
||||
margin-top: -10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
z-index: 10;
|
||||
}
|
||||
input {
|
||||
transition: all 0.25s ease;
|
||||
padding-left: 25px;
|
||||
padding-right: 10px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-grey-lighter);
|
||||
&:focus {
|
||||
background-color: var(--color-grey);
|
||||
}
|
||||
}
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
li {
|
||||
margin-right: 30px;
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
span {
|
||||
user-select: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
&.active {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
aside {
|
||||
transition: all 0.15s ease-in-out;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 260px;
|
||||
border-right: 1px solid var(--color-grey);
|
||||
padding: 0 20px;
|
||||
overflow: auto;
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
height: 40px;
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
span {
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
font-size: 16px;
|
||||
&::after {
|
||||
margin-left: 10px;
|
||||
padding: 0 2px;
|
||||
line-height: 1.3;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
content: attr(data-version);
|
||||
font-size: 12px;
|
||||
color: var(--color-primary);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-primary);
|
||||
background: var(--color-primary-lighter-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
> h4 {
|
||||
margin: 0;
|
||||
padding: 20px 0;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
> header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 10px 10px 30px;
|
||||
img {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
.nav-list {
|
||||
li {
|
||||
margin-bottom: 5px;
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&:hover {
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-grey-lighter);
|
||||
}
|
||||
a {
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
display: block;
|
||||
padding-left: 20px;
|
||||
color: var(--color-text-1);
|
||||
line-height: 40px;
|
||||
text-decoration: none;
|
||||
&.active {
|
||||
color: var(--color-primary);
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> main {
|
||||
padding: 20px 40px 0 40px;
|
||||
margin-left: 260px;
|
||||
margin-right: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-loading {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
z-index: 1023;
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid transparent;
|
||||
border-top-color: var(--color-primary-lighter);
|
||||
border-bottom-color: var(--color-primary-lighter);
|
||||
border-radius: 50%;
|
||||
animation: rotation 1s ease infinite;
|
||||
}
|
||||
}
|
||||
|
||||
[data-lang] {
|
||||
position: relative;
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
content: attr(data-lang);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
position: relative;
|
||||
transition: all 0.25s ease-in-out;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 1px solid #e5e6eb;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
box-shadow: 0 4px 10px #0000001a;
|
||||
&::before {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
content: attr(data-placement);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: -65px;
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
padding: 4px 0;
|
||||
color: var(--color-text-2);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-container {
|
||||
> h1,
|
||||
> h2,
|
||||
> h3,
|
||||
> h4,
|
||||
> h5,
|
||||
> h6 {
|
||||
margin: 0;
|
||||
padding: 48px 0 12px 0;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
}
|
||||
> ul {
|
||||
list-style-position: inside;
|
||||
list-style-type: square;
|
||||
> li {
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
> blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 16px;
|
||||
color: var(--color-primary);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-primary);
|
||||
background: var(--color-primary-lighter-5);
|
||||
> p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> p,
|
||||
li {
|
||||
margin: 0;
|
||||
line-height: 2;
|
||||
code {
|
||||
padding: 2px 4px;
|
||||
margin: 0 2px;
|
||||
font-size: 85%;
|
||||
color: #3c81a6;
|
||||
background-color: #d4eefc;
|
||||
border-radius: 3px;
|
||||
font-family: Inter, "-apple-system", BlinkMacSystemFont, "PingFang SC",
|
||||
"Hiragino Sans GB", "noto sans", "Microsoft YaHei", "Helvetica Neue",
|
||||
Helvetica, Arial, sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-table-container {
|
||||
> h1 {
|
||||
margin: 0;
|
||||
padding: 48px 0 12px 0;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
}
|
||||
.api-table {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 12px;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
}
|
||||
thead {
|
||||
tr {
|
||||
th {
|
||||
background: #f2f3f5;
|
||||
&:nth-of-type(1) {
|
||||
width: 10%;
|
||||
border-radius: 8px 0 0 8px;
|
||||
}
|
||||
&:nth-of-type(2) {
|
||||
width: 45%;
|
||||
}
|
||||
&:nth-of-type(3) {
|
||||
width: 21%;
|
||||
}
|
||||
&:nth-of-type(4) {
|
||||
width: 8%;
|
||||
}
|
||||
&:nth-of-type(5) {
|
||||
width: 8%;
|
||||
}
|
||||
&:nth-of-type(6) {
|
||||
width: 8%;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
.annotation-prop {
|
||||
padding-top: 3px;
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
color: #989898;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
tr {
|
||||
&:hover {
|
||||
background: rgba(60, 90, 100, 0.04);
|
||||
}
|
||||
}
|
||||
td {
|
||||
color: var(--color-text-1);
|
||||
line-height: 20px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
&:nth-of-type(1) {
|
||||
font-weight: 600;
|
||||
}
|
||||
&:nth-of-type(2) {
|
||||
color: var(--color-text-3);
|
||||
word-break: break-all;
|
||||
}
|
||||
&:nth-of-type(3),
|
||||
&:nth-of-type(4) {
|
||||
color: #40b4c4;
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
&:nth-of-type(5) {
|
||||
color: #eb64b9;
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toc {
|
||||
position: fixed;
|
||||
width: 180px;
|
||||
top: 120px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
h4 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-3);
|
||||
font-size: 12px;
|
||||
}
|
||||
ul {
|
||||
height: 100%;
|
||||
border-left: 1px solid var(--color-grey);
|
||||
li {
|
||||
.toc-lvl {
|
||||
color: var(--color-text-2);
|
||||
display: block;
|
||||
padding-right: 20px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--color-grey-lighter);
|
||||
}
|
||||
&.active {
|
||||
color: var(--color-primary);
|
||||
border-left-color: var(--color-primary);
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
&-1 {
|
||||
padding-left: 15px;
|
||||
}
|
||||
&-2 {
|
||||
padding-left: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code-container {
|
||||
.btn {
|
||||
&.active {
|
||||
background: #27212e;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
133
plugin/common/style/prism-laserwave.less
Normal file
133
plugin/common/style/prism-laserwave.less
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Laserwave Theme originally by Jared Jones for Visual Studio Code
|
||||
* https://github.com/Jaredk3nt/laserwave
|
||||
*
|
||||
* Ported for PrismJS by Simon Jespersen [https://github.com/simjes]
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #27212e;
|
||||
color: #ffffff;
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; /* this is the default */
|
||||
/* The following properties are standard, please leave them as they are */
|
||||
font-size: 1em;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 2;
|
||||
-o-tab-size: 2;
|
||||
tab-size: 2;
|
||||
/* The following properties are also standard */
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection,
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection {
|
||||
background: #eb64b927;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection,
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection {
|
||||
background: #eb64b927;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Properties specific to code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em; /* this is standard */
|
||||
margin: 0.5em 0; /* this is the default */
|
||||
overflow: auto; /* this is standard */
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
/* Properties specific to inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: 0.2em 0.3em;
|
||||
border-radius: 0.5rem;
|
||||
white-space: normal; /* this is standard */
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.cdata {
|
||||
color: #91889b;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #7b6995;
|
||||
}
|
||||
|
||||
.token.builtin,
|
||||
.token.constant,
|
||||
.token.boolean {
|
||||
color: #ffe261;
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: #b381c5;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.atrule,
|
||||
.token.property,
|
||||
.token.keyword {
|
||||
color: #40b4c4;
|
||||
}
|
||||
|
||||
.token.doctype,
|
||||
.token.operator,
|
||||
.token.inserted,
|
||||
.token.tag,
|
||||
.token.class-name,
|
||||
.token.symbol {
|
||||
color: #74dfc4;
|
||||
}
|
||||
|
||||
.token.attr-name,
|
||||
.token.function,
|
||||
.token.deleted,
|
||||
.token.selector {
|
||||
color: #eb64b9;
|
||||
}
|
||||
|
||||
.token.attr-value,
|
||||
.token.regex,
|
||||
.token.char,
|
||||
.token.string {
|
||||
color: #b4dce7;
|
||||
}
|
||||
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.token.variable {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* The following rules are pretty similar across themes, but feel free to adjust them */
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
210
plugin/common/style/prism-material-oceanic.less
Normal file
210
plugin/common/style/prism-material-oceanic.less
Normal file
|
@ -0,0 +1,210 @@
|
|||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
color: #c3cee3;
|
||||
background: #2a2d3e;
|
||||
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
code[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection {
|
||||
background: #363636;
|
||||
}
|
||||
|
||||
code[class*="language-"]::selection,
|
||||
pre[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection,
|
||||
pre[class*="language-"] ::selection {
|
||||
background: #363636;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"] {
|
||||
white-space: normal;
|
||||
border-radius: 0.2em;
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
pre[class*="language-"] {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
margin: 0.5em 0;
|
||||
padding: 1.25em 1em;
|
||||
}
|
||||
|
||||
.language-css > code,
|
||||
.language-sass > code,
|
||||
.language-scss > code {
|
||||
color: #fd9170;
|
||||
}
|
||||
|
||||
[class*="language-"] .namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.token.atrule {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.attr-name {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
|
||||
.token.attr-value {
|
||||
color: #c3e88d;
|
||||
}
|
||||
|
||||
.token.attribute {
|
||||
color: #c3e88d;
|
||||
}
|
||||
|
||||
.token.boolean {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.builtin {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
|
||||
.token.cdata {
|
||||
color: #80cbc4;
|
||||
}
|
||||
|
||||
.token.char {
|
||||
color: #80cbc4;
|
||||
}
|
||||
|
||||
.token.class {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
|
||||
.token.class-name {
|
||||
color: #f2ff00;
|
||||
}
|
||||
|
||||
.token.color {
|
||||
color: #f2ff00;
|
||||
}
|
||||
|
||||
.token.comment {
|
||||
color: #546e7a;
|
||||
}
|
||||
|
||||
.token.constant {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: #f07178;
|
||||
}
|
||||
|
||||
.token.doctype {
|
||||
color: #546e7a;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
color: #f07178;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.hexcode {
|
||||
color: #f2ff00;
|
||||
}
|
||||
|
||||
.token.id {
|
||||
color: #c792ea;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
color: #c792ea;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.inserted {
|
||||
color: #80cbc4;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #c792ea;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: #fd9170;
|
||||
}
|
||||
|
||||
.token.operator {
|
||||
color: #89ddff;
|
||||
}
|
||||
|
||||
.token.prolog {
|
||||
color: #546e7a;
|
||||
}
|
||||
|
||||
.token.property {
|
||||
color: #80cbc4;
|
||||
}
|
||||
|
||||
.token.pseudo-class {
|
||||
color: #c3e88d;
|
||||
}
|
||||
|
||||
.token.pseudo-element {
|
||||
color: #c3e88d;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #89ddff;
|
||||
}
|
||||
|
||||
.token.regex {
|
||||
color: #f2ff00;
|
||||
}
|
||||
|
||||
.token.selector {
|
||||
color: #f07178;
|
||||
}
|
||||
|
||||
.token.string {
|
||||
color: #c3e88d;
|
||||
}
|
||||
|
||||
.token.symbol {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.tag {
|
||||
color: #f07178;
|
||||
}
|
||||
|
||||
.token.unit {
|
||||
color: #f07178;
|
||||
}
|
||||
|
||||
.token.url {
|
||||
color: #fd9170;
|
||||
}
|
||||
|
||||
.token.variable {
|
||||
color: #f07178;
|
||||
}
|
158
plugin/common/style/prism-night-owl.less
Normal file
158
plugin/common/style/prism-night-owl.less
Normal file
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* MIT License
|
||||
* Copyright (c) 2018 Sarah Drasner
|
||||
* Sarah Drasner's[@sdras] Night Owl
|
||||
* Ported by Sara vieria [@SaraVieira]
|
||||
* Added by Souvik Mandal [@SimpleIndian]
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #d6deeb;
|
||||
font-family: Menlo, Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
font-size: 1em;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: rgba(29, 59, 83, 0.99);
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: rgba(29, 59, 83, 0.99);
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: white;
|
||||
background: #011627;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: 0.1em;
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.cdata {
|
||||
color: rgb(99, 119, 119);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: rgb(199, 146, 234);
|
||||
}
|
||||
|
||||
.namespace {
|
||||
color: rgb(178, 204, 214);
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: rgba(239, 83, 80, 0.56);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.symbol,
|
||||
.token.property {
|
||||
color: rgb(128, 203, 196);
|
||||
}
|
||||
|
||||
.token.tag,
|
||||
.token.operator,
|
||||
.token.keyword {
|
||||
color: rgb(127, 219, 202);
|
||||
}
|
||||
|
||||
.token.boolean {
|
||||
color: rgb(255, 88, 116);
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: rgb(247, 140, 108);
|
||||
}
|
||||
|
||||
.token.constant,
|
||||
.token.function,
|
||||
.token.builtin,
|
||||
.token.char {
|
||||
color: rgb(130, 170, 255);
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.doctype {
|
||||
color: rgb(199, 146, 234);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.attr-name,
|
||||
.token.inserted {
|
||||
color: rgb(173, 219, 103);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.string,
|
||||
.token.url,
|
||||
.token.entity,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: rgb(173, 219, 103);
|
||||
}
|
||||
|
||||
.token.class-name,
|
||||
.token.atrule,
|
||||
.token.attr-value {
|
||||
color: rgb(255, 203, 139);
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: rgb(214, 222, 235);
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
428
plugin/common/style/prism-one-light.less
Normal file
428
plugin/common/style/prism-one-light.less
Normal file
|
@ -0,0 +1,428 @@
|
|||
/**
|
||||
* One Light theme for prism.js
|
||||
* Based on Atom's One Light theme: https://github.com/atom/atom/tree/master/packages/one-light-syntax
|
||||
*/
|
||||
|
||||
/**
|
||||
* One Light colours (accurate as of commit eb064bf on 19 Feb 2021)
|
||||
* From colors.less
|
||||
* --mono-1: hsl(230, 8%, 24%);
|
||||
* --mono-2: hsl(230, 6%, 44%);
|
||||
* --mono-3: hsl(230, 4%, 64%)
|
||||
* --hue-1: hsl(198, 99%, 37%);
|
||||
* --hue-2: hsl(221, 87%, 60%);
|
||||
* --hue-3: hsl(301, 63%, 40%);
|
||||
* --hue-4: hsl(119, 34%, 47%);
|
||||
* --hue-5: hsl(5, 74%, 59%);
|
||||
* --hue-5-2: hsl(344, 84%, 43%);
|
||||
* --hue-6: hsl(35, 99%, 36%);
|
||||
* --hue-6-2: hsl(35, 99%, 40%);
|
||||
* --syntax-fg: hsl(230, 8%, 24%);
|
||||
* --syntax-bg: hsl(230, 1%, 98%);
|
||||
* --syntax-gutter: hsl(230, 1%, 62%);
|
||||
* --syntax-guide: hsla(230, 8%, 24%, 0.2);
|
||||
* --syntax-accent: hsl(230, 100%, 66%);
|
||||
* From syntax-variables.less
|
||||
* --syntax-selection-color: hsl(230, 1%, 90%);
|
||||
* --syntax-gutter-background-color-selected: hsl(230, 1%, 90%);
|
||||
* --syntax-cursor-line: hsla(230, 8%, 24%, 0.05);
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: hsl(230, 1%, 98%);
|
||||
color: hsl(230, 8%, 24%);
|
||||
font-family: Menlo, Consolas, "DejaVu Sans Mono", monospace;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
line-height: 1.4;
|
||||
-moz-tab-size: 2;
|
||||
-o-tab-size: 2;
|
||||
tab-size: 2;
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Selection */
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] *::-moz-selection,
|
||||
pre[class*="language-"] *::-moz-selection {
|
||||
background: hsl(230, 1%, 90%);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] *::selection,
|
||||
pre[class*="language-"] *::selection {
|
||||
background: hsl(230, 1%, 90%);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: 0.2em 0.3em;
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.cdata {
|
||||
color: hsl(230, 4%, 64%);
|
||||
}
|
||||
|
||||
.token.doctype,
|
||||
.token.punctuation,
|
||||
.token.entity {
|
||||
color: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
.token.attr-name,
|
||||
.token.class-name,
|
||||
.token.boolean,
|
||||
.token.constant,
|
||||
.token.number,
|
||||
.token.atrule {
|
||||
color: hsl(35, 99%, 36%);
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: hsl(301, 63%, 40%);
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.symbol,
|
||||
.token.deleted,
|
||||
.token.important {
|
||||
color: hsl(5, 74%, 59%);
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted,
|
||||
.token.regex,
|
||||
.token.attr-value,
|
||||
.token.attr-value > .token.punctuation {
|
||||
color: hsl(119, 34%, 47%);
|
||||
}
|
||||
|
||||
.token.variable,
|
||||
.token.operator,
|
||||
.token.function {
|
||||
color: hsl(221, 87%, 60%);
|
||||
}
|
||||
|
||||
.token.url {
|
||||
color: hsl(198, 99%, 37%);
|
||||
}
|
||||
|
||||
/* HTML overrides */
|
||||
.token.attr-value > .token.punctuation.attr-equals,
|
||||
.token.special-attr > .token.attr-value > .token.value.css {
|
||||
color: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
/* CSS overrides */
|
||||
.language-css .token.selector {
|
||||
color: hsl(5, 74%, 59%);
|
||||
}
|
||||
|
||||
.language-css .token.property {
|
||||
color: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
.language-css .token.function,
|
||||
.language-css .token.url > .token.function {
|
||||
color: hsl(198, 99%, 37%);
|
||||
}
|
||||
|
||||
.language-css .token.url > .token.string.url {
|
||||
color: hsl(119, 34%, 47%);
|
||||
}
|
||||
|
||||
.language-css .token.important,
|
||||
.language-css .token.atrule .token.rule {
|
||||
color: hsl(301, 63%, 40%);
|
||||
}
|
||||
|
||||
/* JS overrides */
|
||||
.language-javascript .token.operator {
|
||||
color: hsl(301, 63%, 40%);
|
||||
}
|
||||
|
||||
.language-javascript .token.template-string > .token.interpolation > .token.interpolation-punctuation.punctuation {
|
||||
color: hsl(344, 84%, 43%);
|
||||
}
|
||||
|
||||
/* JSON overrides */
|
||||
.language-json .token.operator {
|
||||
color: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
.language-json .token.null.keyword {
|
||||
color: hsl(35, 99%, 36%);
|
||||
}
|
||||
|
||||
/* MD overrides */
|
||||
.language-markdown .token.url,
|
||||
.language-markdown .token.url > .token.operator,
|
||||
.language-markdown .token.url-reference.url > .token.string {
|
||||
color: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
.language-markdown .token.url > .token.content {
|
||||
color: hsl(221, 87%, 60%);
|
||||
}
|
||||
|
||||
.language-markdown .token.url > .token.url,
|
||||
.language-markdown .token.url-reference.url {
|
||||
color: hsl(198, 99%, 37%);
|
||||
}
|
||||
|
||||
.language-markdown .token.blockquote.punctuation,
|
||||
.language-markdown .token.hr.punctuation {
|
||||
color: hsl(230, 4%, 64%);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.language-markdown .token.code-snippet {
|
||||
color: hsl(119, 34%, 47%);
|
||||
}
|
||||
|
||||
.language-markdown .token.bold .token.content {
|
||||
color: hsl(35, 99%, 36%);
|
||||
}
|
||||
|
||||
.language-markdown .token.italic .token.content {
|
||||
color: hsl(301, 63%, 40%);
|
||||
}
|
||||
|
||||
.language-markdown .token.strike .token.content,
|
||||
.language-markdown .token.strike .token.punctuation,
|
||||
.language-markdown .token.list.punctuation,
|
||||
.language-markdown .token.title.important > .token.punctuation {
|
||||
color: hsl(5, 74%, 59%);
|
||||
}
|
||||
|
||||
/* General */
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.token.namespace {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Plugin overrides */
|
||||
/* Selectors should have higher specificity than those in the plugins' default stylesheets */
|
||||
|
||||
/* Show Invisibles plugin overrides */
|
||||
.token.token.tab:not(:empty):before,
|
||||
.token.token.cr:before,
|
||||
.token.token.lf:before,
|
||||
.token.token.space:before {
|
||||
color: hsla(230, 8%, 24%, 0.2);
|
||||
}
|
||||
|
||||
/* Toolbar plugin overrides */
|
||||
/* Space out all buttons and move them away from the right edge of the code block */
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item {
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
/* Styling the buttons */
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span {
|
||||
background: hsl(230, 1%, 90%);
|
||||
color: hsl(230, 6%, 44%);
|
||||
padding: 0.1em 0.4em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus {
|
||||
background: hsl(230, 1%, 78%); /* custom: darken(--syntax-bg, 20%) */
|
||||
color: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
/* Line Highlight plugin overrides */
|
||||
/* The highlighted line itself */
|
||||
.line-highlight.line-highlight {
|
||||
background: hsla(230, 8%, 24%, 0.05);
|
||||
}
|
||||
|
||||
/* Default line numbers in Line Highlight plugin */
|
||||
.line-highlight.line-highlight:before,
|
||||
.line-highlight.line-highlight[data-end]:after {
|
||||
background: hsl(230, 1%, 90%);
|
||||
color: hsl(230, 8%, 24%);
|
||||
padding: 0.1em 0.6em;
|
||||
border-radius: 0.3em;
|
||||
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); /* same as Toolbar plugin default */
|
||||
}
|
||||
|
||||
/* Hovering over a linkable line number (in the gutter area) */
|
||||
/* Requires Line Numbers plugin as well */
|
||||
pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before {
|
||||
background-color: hsla(230, 8%, 24%, 0.05);
|
||||
}
|
||||
|
||||
/* Line Numbers and Command Line plugins overrides */
|
||||
/* Line separating gutter from coding area */
|
||||
.line-numbers.line-numbers .line-numbers-rows,
|
||||
.command-line .command-line-prompt {
|
||||
border-right-color: hsla(230, 8%, 24%, 0.2);
|
||||
}
|
||||
|
||||
/* Stuff in the gutter */
|
||||
.line-numbers .line-numbers-rows > span:before,
|
||||
.command-line .command-line-prompt > span:before {
|
||||
color: hsl(230, 1%, 62%);
|
||||
}
|
||||
|
||||
/* Match Braces plugin overrides */
|
||||
/* Note: Outline colour is inherited from the braces */
|
||||
.rainbow-braces .token.token.punctuation.brace-level-1,
|
||||
.rainbow-braces .token.token.punctuation.brace-level-5,
|
||||
.rainbow-braces .token.token.punctuation.brace-level-9 {
|
||||
color: hsl(5, 74%, 59%);
|
||||
}
|
||||
|
||||
.rainbow-braces .token.token.punctuation.brace-level-2,
|
||||
.rainbow-braces .token.token.punctuation.brace-level-6,
|
||||
.rainbow-braces .token.token.punctuation.brace-level-10 {
|
||||
color: hsl(119, 34%, 47%);
|
||||
}
|
||||
|
||||
.rainbow-braces .token.token.punctuation.brace-level-3,
|
||||
.rainbow-braces .token.token.punctuation.brace-level-7,
|
||||
.rainbow-braces .token.token.punctuation.brace-level-11 {
|
||||
color: hsl(221, 87%, 60%);
|
||||
}
|
||||
|
||||
.rainbow-braces .token.token.punctuation.brace-level-4,
|
||||
.rainbow-braces .token.token.punctuation.brace-level-8,
|
||||
.rainbow-braces .token.token.punctuation.brace-level-12 {
|
||||
color: hsl(301, 63%, 40%);
|
||||
}
|
||||
|
||||
/* Diff Highlight plugin overrides */
|
||||
/* Taken from https://github.com/atom/github/blob/master/styles/variables.less */
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix),
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix) {
|
||||
background-color: hsla(353, 100%, 66%, 0.15);
|
||||
}
|
||||
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection,
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection,
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection,
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection {
|
||||
background-color: hsla(353, 95%, 66%, 0.25);
|
||||
}
|
||||
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection,
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection,
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection,
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection {
|
||||
background-color: hsla(353, 95%, 66%, 0.25);
|
||||
}
|
||||
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix),
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix) {
|
||||
background-color: hsla(137, 100%, 55%, 0.15);
|
||||
}
|
||||
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection,
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection,
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection,
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection {
|
||||
background-color: hsla(135, 73%, 55%, 0.25);
|
||||
}
|
||||
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection,
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection,
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection,
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection {
|
||||
background-color: hsla(135, 73%, 55%, 0.25);
|
||||
}
|
||||
|
||||
/* Previewers plugin overrides */
|
||||
/* Based on https://github.com/atom-community/atom-ide-datatip/blob/master/styles/atom-ide-datatips.less and https://github.com/atom/atom/blob/master/packages/one-light-ui */
|
||||
/* Border around popup */
|
||||
.prism-previewer.prism-previewer:before,
|
||||
.prism-previewer-gradient.prism-previewer-gradient div {
|
||||
border-color: hsl(0, 0, 95%);
|
||||
}
|
||||
|
||||
/* Angle and time should remain as circles and are hence not included */
|
||||
.prism-previewer-color.prism-previewer-color:before,
|
||||
.prism-previewer-gradient.prism-previewer-gradient div,
|
||||
.prism-previewer-easing.prism-previewer-easing:before {
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
/* Triangles pointing to the code */
|
||||
.prism-previewer.prism-previewer:after {
|
||||
border-top-color: hsl(0, 0, 95%);
|
||||
}
|
||||
|
||||
.prism-previewer-flipped.prism-previewer-flipped.after {
|
||||
border-bottom-color: hsl(0, 0, 95%);
|
||||
}
|
||||
|
||||
/* Background colour within the popup */
|
||||
.prism-previewer-angle.prism-previewer-angle:before,
|
||||
.prism-previewer-time.prism-previewer-time:before,
|
||||
.prism-previewer-easing.prism-previewer-easing {
|
||||
background: hsl(0, 0%, 100%);
|
||||
}
|
||||
|
||||
/* For angle, this is the positive area (eg. 90deg will display one quadrant in this colour) */
|
||||
/* For time, this is the alternate colour */
|
||||
.prism-previewer-angle.prism-previewer-angle circle,
|
||||
.prism-previewer-time.prism-previewer-time circle {
|
||||
stroke: hsl(230, 8%, 24%);
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
/* Stroke colours of the handle, direction point, and vector itself */
|
||||
.prism-previewer-easing.prism-previewer-easing circle,
|
||||
.prism-previewer-easing.prism-previewer-easing path,
|
||||
.prism-previewer-easing.prism-previewer-easing line {
|
||||
stroke: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
/* Fill colour of the handle */
|
||||
.prism-previewer-easing.prism-previewer-easing circle {
|
||||
fill: transparent;
|
||||
}
|
193
plugin/initialize.ts
Normal file
193
plugin/initialize.ts
Normal file
|
@ -0,0 +1,193 @@
|
|||
import path from "path";
|
||||
import fs from "fs";
|
||||
import TsToJson from "./ts2json";
|
||||
import toc from "markdown-toc";
|
||||
import objHash from "object-hash";
|
||||
|
||||
const hash = (str: string) => "" + objHash(str).substring(0, 6);
|
||||
|
||||
/**
|
||||
* props 注释元信息
|
||||
*/
|
||||
interface IPropMeta {
|
||||
/**
|
||||
* 声明属性是否必须 对应 ts ?
|
||||
*/
|
||||
required?: boolean;
|
||||
/**
|
||||
* @name 参数名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type 类型
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @description 说明
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @default 默认值
|
||||
*/
|
||||
default?: string;
|
||||
/**
|
||||
* @version 版本
|
||||
*/
|
||||
version?: string | number;
|
||||
}
|
||||
|
||||
interface IProp2jsonItem {
|
||||
[key: string]: {
|
||||
props: IPropMeta[];
|
||||
};
|
||||
}
|
||||
|
||||
interface IRoute {
|
||||
active: false;
|
||||
path: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ITree {
|
||||
/**
|
||||
* 组件🎄
|
||||
*/
|
||||
compoTree: ICompoTree;
|
||||
/**
|
||||
* 文件🎄,最小粒度更新
|
||||
*/
|
||||
fileTree?: {};
|
||||
/**
|
||||
* 路由🎄,根据 menuTree 生成
|
||||
*/
|
||||
routerList: IRoute[];
|
||||
/**
|
||||
* 插件配置
|
||||
*/
|
||||
config?: {};
|
||||
}
|
||||
|
||||
interface ITocItem {
|
||||
/**
|
||||
* 锚点的 hash
|
||||
*/
|
||||
anchor?: string;
|
||||
/**
|
||||
* mardkown h标签 innerHTML
|
||||
*/
|
||||
content: string;
|
||||
i: 0;
|
||||
lvl: 1;
|
||||
seen: 0;
|
||||
/**
|
||||
* 小写 innerHTML
|
||||
*/
|
||||
slug: "hi";
|
||||
}
|
||||
|
||||
/**
|
||||
* ts interface 转 json
|
||||
*/
|
||||
const tsParser = new TsToJson();
|
||||
/**
|
||||
* 组件开发目录
|
||||
*/
|
||||
const root = path.resolve(process.cwd(), "src/components");
|
||||
/**
|
||||
* 文件映射,最小粒度更新 For HMR
|
||||
*/
|
||||
const fileTree = {};
|
||||
|
||||
/**
|
||||
* ICompoTree Item
|
||||
*/
|
||||
interface ICompoMetadata {
|
||||
/**
|
||||
* 组件 props 信息
|
||||
*/
|
||||
props: IPropMeta[] | [];
|
||||
/**
|
||||
* markdown 文件目录 table of content
|
||||
*/
|
||||
toc: ITocItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件🎄
|
||||
*/
|
||||
interface ICompoTree {
|
||||
[key: string]: ICompoMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 markdown 内容生成目录 table of content
|
||||
* @param {string} content markdown 内容
|
||||
*/
|
||||
const md2toc = (content: string) => {
|
||||
if (!content) return [];
|
||||
return toc(content).json;
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成路由
|
||||
* @param {ICompoTree} compoTree 菜单
|
||||
*/
|
||||
const processRouter = (compoTree: ICompoTree): IRoute[] => {
|
||||
return Object.keys(compoTree).map((t) => ({
|
||||
active: false,
|
||||
path: t.toLowerCase(),
|
||||
name: t,
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* props.ts 转换成 json 对象
|
||||
* @param {string} path 组件 props.ts 文件路径
|
||||
* @returns
|
||||
*/
|
||||
const parsePropsTs = (path: string) => {
|
||||
const prop2json: IProp2jsonItem = fs.existsSync(path)
|
||||
? tsParser.parse(path)
|
||||
: {};
|
||||
return Object.values(prop2json)?.[0]?.props ?? [];
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析 mdx 文件中的目录
|
||||
* @param {string} path 组件 mdx 文件路径
|
||||
*/
|
||||
const parseToc = (path: string) => {
|
||||
const isFile = fs.statSync(path).isFile();
|
||||
const mdxContent = isFile ? fs.readFileSync(path).toString() : "";
|
||||
// 注入锚点 hash
|
||||
const toc = md2toc(mdxContent).map((i) => ({
|
||||
...i,
|
||||
anchor: `anchor-${hash(i.content)}`,
|
||||
}));
|
||||
return toc;
|
||||
};
|
||||
|
||||
const walkCompoMetadata = (dirs: string[]): ICompoTree => {
|
||||
const compoTree: ICompoTree = {};
|
||||
dirs.forEach((dir) => {
|
||||
const mdxFile = path.resolve(root, dir, "index.mdx");
|
||||
const propFile = path.resolve(root, dir, "props.ts");
|
||||
fileTree[mdxFile] = dir;
|
||||
fileTree[propFile] = dir;
|
||||
const toc = parseToc(mdxFile);
|
||||
const props = parsePropsTs(propFile);
|
||||
compoTree[dir] = { toc, props };
|
||||
});
|
||||
return compoTree;
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
* @returns menuTree: 目录树, 包含markdownHeadings | propsTree: 组件 api 树
|
||||
*/
|
||||
export const initialize = (): ITree => {
|
||||
const dirs = fs.existsSync(root) ? fs.readdirSync(root) : [];
|
||||
const compoTree = walkCompoMetadata(dirs);
|
||||
const routerList = processRouter(compoTree);
|
||||
return { compoTree, fileTree, routerList };
|
||||
};
|
213
plugin/ts2json.ts
Normal file
213
plugin/ts2json.ts
Normal file
|
@ -0,0 +1,213 @@
|
|||
import ts from "typescript";
|
||||
|
||||
export interface DocsType {
|
||||
name?: string;
|
||||
type?: string;
|
||||
description?: string;
|
||||
default?: string;
|
||||
required?: boolean;
|
||||
version?: number | string;
|
||||
}
|
||||
|
||||
export interface Interfaces {
|
||||
name?: {
|
||||
description?: string;
|
||||
/** 是否不渲染 markdown */
|
||||
ignore?: string;
|
||||
props?: DocsType[];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ts Interface 类型定义转换为 json
|
||||
*/
|
||||
export default class TsToJson {
|
||||
checker: any;
|
||||
interfaces: any;
|
||||
fileNames: any;
|
||||
types: any;
|
||||
|
||||
_createProgram(fileNames: string[], options: ts.CompilerOptions) {
|
||||
this.interfaces = {};
|
||||
this.types = {};
|
||||
|
||||
// 使用filename中的根文件名构建一个程序;
|
||||
const program = ts.createProgram(fileNames, options);
|
||||
// 获取检查器,我们将使用它来查找更多关于 ast 的信息
|
||||
this.checker = program.getTypeChecker();
|
||||
|
||||
this.fileNames = fileNames;
|
||||
|
||||
// 访问程序中的每个sourceFile
|
||||
for (const sourceFile of program.getSourceFiles()) {
|
||||
if (!sourceFile.isDeclarationFile) {
|
||||
// 遍历树以搜索 interface 和 type
|
||||
ts.forEachChild(sourceFile, (node) => this._visitAst(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问查找导出的节点
|
||||
* @param node
|
||||
* @returns
|
||||
*/
|
||||
_visitAst(node: ts.Node) {
|
||||
// 只考虑导出的节点
|
||||
if (!this._isNodeExported(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ts.SyntaxKind[node.kind]) {
|
||||
case "InterfaceDeclaration":
|
||||
this.generateInterfaceDeclaration(node);
|
||||
break;
|
||||
case "TypeAliasDeclaration":
|
||||
this.generateTypeAliasDeclaration(node);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Interface 类型 json 数据
|
||||
* @param node
|
||||
*/
|
||||
generateInterfaceDeclaration(node: ts.Node) {
|
||||
if (
|
||||
// @ts-ignore
|
||||
node.parent?.fileName.replace(/\\/g, "/") !==
|
||||
this.fileNames[0].replace(/\\/g, "/")
|
||||
)
|
||||
return;
|
||||
const newNode = node as ts.InterfaceDeclaration;
|
||||
const symbol = this.checker.getSymbolAtLocation(newNode.name);
|
||||
const { escapedName } = symbol;
|
||||
const { name = escapedName as string, ...rest } = this._getDocs(symbol);
|
||||
this.interfaces[name] = {
|
||||
...rest,
|
||||
props: [],
|
||||
};
|
||||
if (newNode.heritageClauses) {
|
||||
// 是否有extends
|
||||
const firstHeritageClause = newNode.heritageClauses[0];
|
||||
const firstHeritageClauseType = firstHeritageClause.types![0];
|
||||
const extendsType = this.checker.getTypeAtLocation(
|
||||
firstHeritageClauseType.expression
|
||||
);
|
||||
if (extendsType?.symbol?.members && extendsType.symbol.members.size > 0) {
|
||||
// 接口 extends 接口(Interface)
|
||||
extendsType.symbol.members.forEach((member: any) => {
|
||||
this.interfaces[name].props.push(this._serializeSymbol(member));
|
||||
});
|
||||
} else if (firstHeritageClauseType) {
|
||||
console.log("暂时不支持接口继承Type类型");
|
||||
// 接口 extends 类型(Type)
|
||||
// const type = this.checker.typeToTypeNode(
|
||||
// this.checker.getTypeAtLocation(firstHeritageClauseType),
|
||||
// firstHeritageClauseType,
|
||||
// ts.NodeBuilderFlags.InTypeAlias
|
||||
// // ts.TypeFormatFlags.InTypeAlias
|
||||
// );
|
||||
// console.log(firstHeritageClauseType.getFullText());
|
||||
}
|
||||
}
|
||||
symbol.members.forEach((member: any) => {
|
||||
this.interfaces[name].props.push(this._serializeSymbol(member));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Type 类型 json 数据
|
||||
* @param node
|
||||
*/
|
||||
generateTypeAliasDeclaration(node: ts.Node) {
|
||||
const type = this.checker.typeToString(
|
||||
this.checker.getTypeAtLocation(node),
|
||||
node,
|
||||
ts.TypeFormatFlags.InTypeAlias
|
||||
);
|
||||
const name = (node as any).name.escapedText;
|
||||
this.types[name] = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将symbol序列化为json对象
|
||||
* @param symbol
|
||||
* @returns
|
||||
*/
|
||||
_serializeSymbol(symbol: ts.Symbol): DocsType | undefined {
|
||||
if (!symbol) return;
|
||||
const docs = this._getDocs(symbol);
|
||||
const questionToken = (symbol as any).valueDeclaration?.questionToken;
|
||||
const type = this.checker.typeToString(
|
||||
this.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)
|
||||
);
|
||||
let newType = type;
|
||||
if (type.indexOf("|") > 0) {
|
||||
newType = type
|
||||
.split("|")
|
||||
.map((item: any) => {
|
||||
const hasType = this.types[item.trim()];
|
||||
if (hasType) {
|
||||
return ` ${hasType} `;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
.join("|");
|
||||
} else if (this.types[type]) {
|
||||
newType = this.types[type];
|
||||
}
|
||||
|
||||
return {
|
||||
name: symbol.getName(),
|
||||
required: !questionToken,
|
||||
type: newType,
|
||||
...docs,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取jsDoc信息
|
||||
* @param symbol
|
||||
* @returns
|
||||
*/
|
||||
_getDocs(symbol: ts.Symbol): DocsType {
|
||||
const docs: DocsType = {};
|
||||
if (
|
||||
symbol.getJsDocTags &&
|
||||
Array.isArray(symbol.getJsDocTags(this.checker))
|
||||
) {
|
||||
symbol.getJsDocTags(this.checker).forEach((tag) => {
|
||||
(docs as any)[tag.name] = tag.text && tag.text[0].text;
|
||||
});
|
||||
}
|
||||
return docs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果在文件外部可见,则为True,否则为false
|
||||
* @param node
|
||||
* @returns boolean
|
||||
*/
|
||||
_isNodeExported(node: ts.Node): boolean {
|
||||
return (
|
||||
(ts.getCombinedModifierFlags(node as ts.Declaration) &
|
||||
ts.ModifierFlags.Export) !==
|
||||
0 ||
|
||||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
|
||||
);
|
||||
}
|
||||
|
||||
parse(
|
||||
fileNames: string,
|
||||
options: ts.CompilerOptions = {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
}
|
||||
) {
|
||||
this._createProgram([fileNames], options);
|
||||
return this.interfaces;
|
||||
}
|
||||
}
|
116
plugin/types/index.ts
Normal file
116
plugin/types/index.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
export interface ITocItem {
|
||||
/**
|
||||
* 锚点的 hash
|
||||
*/
|
||||
anchor: string;
|
||||
/**
|
||||
* mardkown h标签 innerHTML
|
||||
*/
|
||||
content: string;
|
||||
i: 0;
|
||||
lvl: 1;
|
||||
seen: 0;
|
||||
/**
|
||||
* 小写 innerHTML
|
||||
*/
|
||||
slug: "hi";
|
||||
/**
|
||||
* 高亮
|
||||
*/
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* props 注释元信息
|
||||
*/
|
||||
export interface IPropMeta {
|
||||
/**
|
||||
* 声明属性是否必须 对应 ts ?
|
||||
*/
|
||||
required?: boolean;
|
||||
/**
|
||||
* @name 参数名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @type 类型
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* @description 说明
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @default 默认值
|
||||
*/
|
||||
default?: string;
|
||||
/**
|
||||
* @version 版本
|
||||
*/
|
||||
version?: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件🎄
|
||||
*/
|
||||
export interface ICompoTree {
|
||||
[key: string]: ICompoMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* ICompoTree Item
|
||||
*/
|
||||
export interface ICompoMetadata {
|
||||
/**
|
||||
* 组件 props 信息
|
||||
*/
|
||||
props: IPropMeta[] | [];
|
||||
/**
|
||||
* markdown 文件目录 table of content
|
||||
*/
|
||||
toc: [];
|
||||
}
|
||||
|
||||
export interface ITree {
|
||||
/**
|
||||
* 组件🎄
|
||||
*/
|
||||
compoTree: ICompoTree;
|
||||
/**
|
||||
* 文件🎄,最小粒度更新
|
||||
*/
|
||||
fileTree?: {};
|
||||
/**
|
||||
* 路由🎄,根据 menuTree 生成
|
||||
*/
|
||||
routerList: IRoute[];
|
||||
/**
|
||||
* 插件配置
|
||||
*/
|
||||
config?: {
|
||||
title: string;
|
||||
subTitle: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IRoute {
|
||||
name: string;
|
||||
path: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export type TToc = {
|
||||
content: string;
|
||||
slug: string;
|
||||
lvl: 1 | 2;
|
||||
i?: number;
|
||||
seen?: number;
|
||||
active?: boolean;
|
||||
anchor: string;
|
||||
};
|
||||
|
||||
export interface ICustomPage {
|
||||
path: string;
|
||||
src: string;
|
||||
title: string;
|
||||
}
|
77
plugin/vite-plugin-bolt.ts
Normal file
77
plugin/vite-plugin-bolt.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { initialize, ITree } from "./initialize";
|
||||
|
||||
interface IMozzieOptions {
|
||||
virtualModuleName?: string;
|
||||
config?: any;
|
||||
}
|
||||
|
||||
export interface IMeta {
|
||||
name: string;
|
||||
required: boolean;
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface IRoute {
|
||||
name: string;
|
||||
path: string;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export interface IConfigs {
|
||||
config: {
|
||||
title: string; // document title
|
||||
subTitle: string; // 文档标题
|
||||
};
|
||||
}
|
||||
|
||||
export default function Bolt(options: IMozzieOptions) {
|
||||
const { config = {} } = options;
|
||||
const virtualModuleId = "virtual:@vite/plugin-bolt";
|
||||
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
||||
const TREE: ITree = {
|
||||
compoTree: {},
|
||||
fileTree: {},
|
||||
routerList: [],
|
||||
config: {},
|
||||
};
|
||||
return {
|
||||
name: "@vite/plugin-bolt",
|
||||
enforce: "pre",
|
||||
resolveId(id: string) {
|
||||
if (id === virtualModuleId) {
|
||||
return resolvedVirtualModuleId;
|
||||
}
|
||||
},
|
||||
load(id: string) {
|
||||
if (id === resolvedVirtualModuleId) {
|
||||
const { compoTree, fileTree, routerList } = initialize();
|
||||
TREE.fileTree = fileTree;
|
||||
TREE.compoTree = compoTree;
|
||||
TREE.routerList = routerList;
|
||||
TREE.config = config;
|
||||
// build 后也存在 config
|
||||
return `export const TREE = ${JSON.stringify(TREE)}`;
|
||||
}
|
||||
},
|
||||
handleHotUpdate(ctx: any) {
|
||||
const { file, server } = ctx;
|
||||
//! 开发阶段:
|
||||
//* 由于 上面load方法 只会触发一次,导致写到前端的 TREE 对象,无法更改
|
||||
//* 当动态增减 components 组件,默认会先使用 TREE,然后触发 client:refresh 方法,获取到最新的 compoTree
|
||||
//* 导致左侧菜单会变化,目前采用 loading 遮住这个跳变的过程
|
||||
server.ws.send("server:fullUpdate", initialize());
|
||||
|
||||
//! return [] 会让 mdx-plugin 不更新
|
||||
},
|
||||
configureServer(server) {
|
||||
server.ws.on("client:refresh", (data, client) => {
|
||||
client.send("client:refresh:response", initialize());
|
||||
});
|
||||
},
|
||||
transformIndexHtml(html) {
|
||||
const { title } = config;
|
||||
return html.replace(/<title>(.*?)<\/title>/, `<title>${title}</title>`);
|
||||
},
|
||||
};
|
||||
}
|
5386
pnpm-lock.yaml
Normal file
5386
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
24
readme.md
Normal file
24
readme.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# quick start
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
# 构建产物
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
- dist 文件夹 `doc` 是文档站,直接部署到 `web`容器。路由 `history`模式,配合 nginx 开启 `nginx 伪静态`
|
||||
|
||||
- dist 文件夹 `ui` 为 组件库 ,直接`ESModule`引入项目
|
||||
|
||||
- `src/pages` 文件夹自定义页面,`bolt.config.ts` 配置引入
|
||||
|
||||
- `src/components` 为组件开发目录,基本组成:
|
||||
|
||||
- index.tsx 组件的代码
|
||||
- index.less 组件的样式
|
||||
- props.ts 组件 api 接口
|
||||
- index.mdx 组件文档说明
|
174
src/App.tsx
Normal file
174
src/App.tsx
Normal file
|
@ -0,0 +1,174 @@
|
|||
import { MDXProvider } from "@mdx-js/react";
|
||||
import { TREE } from "@bolt/config";
|
||||
import { lazy, Suspense, useEffect, useState } from "react";
|
||||
import { Route, Routes, useLocation } from "react-router";
|
||||
import {
|
||||
ICompoTree,
|
||||
ICustomPage,
|
||||
IPropMeta,
|
||||
IRoute,
|
||||
ITocItem,
|
||||
ITree,
|
||||
} from "../plugin/types";
|
||||
import ApiTable from "../plugin/common/ApiTable";
|
||||
import Aside from "../plugin/common/Aside";
|
||||
import objHash from "object-hash";
|
||||
import Toc from "../plugin/common/MdToc";
|
||||
import Nav from "../plugin/common/Nav";
|
||||
|
||||
const hash = (str: string) => "" + objHash(str).substring(0, 6);
|
||||
|
||||
function App() {
|
||||
const location = useLocation();
|
||||
const [customPages, setCustomPages] = useState<ICustomPage[]>(
|
||||
TREE.config.customPages
|
||||
);
|
||||
const [compoTree, setCompoTree] = useState<ICompoTree>(TREE.compoTree);
|
||||
const [routerList, setRouterList] = useState<IRoute[]>(TREE.routerList);
|
||||
const [toc, setToc] = useState<ITocItem[]>([]);
|
||||
const [propsTable, setPropsTable] = useState<IPropMeta[]>([]);
|
||||
const [isPage, setIsPage] = useState(true);
|
||||
|
||||
/**
|
||||
* 客户端刷新通知 vite-server
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (import.meta.hot) import.meta.hot.send("client:refresh");
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 客户端刷新 vite-server 返回新的 TREE
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.on("client:refresh:response", (response: ITree) => {
|
||||
console.log("refresh", response);
|
||||
const { routerList, compoTree } = response;
|
||||
setRouterList(routerList);
|
||||
setCompoTree(compoTree);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* vite-server 监听到文件改动
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.on("server:fullUpdate", (response: ITree) => {
|
||||
console.log("fullUpdate", response);
|
||||
const { routerList, compoTree } = response;
|
||||
setRouterList(routerList);
|
||||
setCompoTree(compoTree);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getCurrent = () => {
|
||||
const path = location.pathname.replace(/\//, "");
|
||||
const route = routerList.find((i) => i.path === path);
|
||||
const compoName = route?.name ?? "";
|
||||
setIsPage(compoName === "");
|
||||
return { path, route, compoName };
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const { compoName } = getCurrent();
|
||||
if (!!compoName) {
|
||||
const { toc, props } = compoTree[compoName];
|
||||
setPropsTable(props);
|
||||
setToc(toc);
|
||||
} else {
|
||||
setToc([]);
|
||||
}
|
||||
}, [compoTree, location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
setRouterList((p) =>
|
||||
p.map((i) => ({ ...i, active: location.pathname.indexOf(i.path) > -1 }))
|
||||
);
|
||||
}, [compoTree]);
|
||||
|
||||
const H1 = (args: any) => {
|
||||
const anchorHash = "anchor-" + hash(args.children);
|
||||
const attr = { name: anchorHash };
|
||||
return <h1 {...attr}>{args.children}</h1>;
|
||||
};
|
||||
|
||||
const H2 = (args: any) => {
|
||||
const anchorHash = "anchor-" + hash(args.children);
|
||||
const attr = { name: anchorHash };
|
||||
return <h2 {...attr}>{args.children}</h2>;
|
||||
};
|
||||
|
||||
const lazyLoad = (modulePath: string) => {
|
||||
const Module = lazy(() =>
|
||||
new Promise((resolve) => setTimeout(resolve, 150)).then(() =>
|
||||
import(`./components/${modulePath}/index.mdx`).then((m) => m)
|
||||
)
|
||||
);
|
||||
return (
|
||||
<Suspense fallback={<div className="page-loading" />}>
|
||||
<MDXProvider components={{ h1: H1, h2: H2 }}>
|
||||
<Module />
|
||||
</MDXProvider>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
const lazyLoadPage = (src: string) => {
|
||||
const Module = lazy(() =>
|
||||
new Promise((resolve) => setTimeout(resolve, 150)).then(() =>
|
||||
import(`./pages/${src}.mdx`).then((m) => m)
|
||||
)
|
||||
);
|
||||
return (
|
||||
<Suspense fallback={<div className="page-loading" />}>
|
||||
<MDXProvider components={{ h1: H1, h2: H2 }}>
|
||||
<Module />
|
||||
</MDXProvider>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="root-container">
|
||||
{/* nav */}
|
||||
<Nav customPages={customPages} />
|
||||
{/* 左侧导航栏 */}
|
||||
<Aside
|
||||
menus={routerList}
|
||||
subTitle={TREE.config.subTitle}
|
||||
version={TREE.config.version}
|
||||
/>
|
||||
<main>
|
||||
<article className="markdown-container">
|
||||
<Routes>
|
||||
{/* 自定义页面 */}
|
||||
{customPages.map((page: ICustomPage) => (
|
||||
<Route
|
||||
key={page.path}
|
||||
path={page.path}
|
||||
element={lazyLoadPage(page.src)}
|
||||
/>
|
||||
))}
|
||||
{/* 组件 */}
|
||||
{routerList.map((route: IRoute) => (
|
||||
<Route
|
||||
key={route.name}
|
||||
path={route.path}
|
||||
element={lazyLoad(route.name)}
|
||||
/>
|
||||
))}
|
||||
</Routes>
|
||||
</article>
|
||||
{/* 组件 props 表 */}
|
||||
{!isPage && <ApiTable propList={propsTable} />}
|
||||
{/* table of content */}
|
||||
{toc.length > 0 && <Toc toc={toc} />}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
1
src/assets/common.less
Normal file
1
src/assets/common.less
Normal file
|
@ -0,0 +1 @@
|
|||
@import "./var.less";
|
13
src/assets/var.less
Normal file
13
src/assets/var.less
Normal file
|
@ -0,0 +1,13 @@
|
|||
:root {
|
||||
--bolt-primary-color: #1890ff;
|
||||
--bolt-primary-color-hover: #40a9ff;
|
||||
--bolt-primary-color-active: #096dd9;
|
||||
--bolt-primary-color-1: #e6f7ff;
|
||||
--bolt-primary-color-2: #bae7ff;
|
||||
--bolt-primary-color-3: #91d5ff;
|
||||
--bolt-primary-color-4: #69c0ff;
|
||||
--bolt-primary-color-5: #40a9ff;
|
||||
--bolt-primary-color-6: #1890ff;
|
||||
--bolt-primary-color-7: #096dd9;
|
||||
--bolt-border-radius: 2px;
|
||||
}
|
14
src/components/Button/demo/demo1.tsx
Normal file
14
src/components/Button/demo/demo1.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import Button from "..";
|
||||
|
||||
export default function test() {
|
||||
return (
|
||||
<div className="box">
|
||||
<p>
|
||||
<Button size={"small"}>小按钮</Button>
|
||||
</p>
|
||||
<p>
|
||||
<Button size={"large"}>大按钮</Button>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
42
src/components/Button/index.less
Normal file
42
src/components/Button/index.less
Normal file
|
@ -0,0 +1,42 @@
|
|||
@import "../../assets/common.less";
|
||||
|
||||
button {
|
||||
transition: all .25s ease-in-out;
|
||||
position: relative;
|
||||
font-size: 13px;
|
||||
height: 32px;
|
||||
padding: 4px 15px;
|
||||
background: var(--bolt-primary-color);
|
||||
border: 0;
|
||||
color: #fff;
|
||||
border-radius: var(--bolt-border-radius);
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: var(--bolt-primary-color-hover);
|
||||
}
|
||||
&:active {
|
||||
background: var(--bolt-primary-color-active);
|
||||
}
|
||||
&.halo {
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-color: var(--bolt-primary-color-hover);
|
||||
border-radius: 2px;
|
||||
animation: ani-halo 0.25s 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ani-halo {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0px rgb(24 144 255 / 60%);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 4px rgb(24 144 255 / 0%);
|
||||
}
|
||||
}
|
114
src/components/Button/index.mdx
Normal file
114
src/components/Button/index.mdx
Normal file
|
@ -0,0 +1,114 @@
|
|||
import Code from "../../../plugin/common/Code.tsx";
|
||||
import Demo1 from "./demo/demo1.tsx";
|
||||
import Demo1Code from "./demo/demo1.tsx?raw";
|
||||
|
||||
# Hi
|
||||
|
||||
按钮组件测试
|
||||
|
||||
<Demo1 />
|
||||
<Code raw={Demo1Code} lang="ts" />
|
||||
|
||||
## 使用说明
|
||||
|
||||
间距预设大、中、小三种大小。[链接](/input)
|
||||
|
||||
通过设置 `size`、`large`、`middle` 分别把间距设为大、中间距。若不设置 `size`, 则间距为小。
|
||||
|
||||
## 使用 useage
|
||||
|
||||
```bash
|
||||
# npm install pnpm -g
|
||||
pnpm install
|
||||
```
|
||||
|
||||
```css
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 测试 toc
|
||||
|
||||
是的吗,好厉害
|
||||
|
||||
## JJ
|
||||
|
||||
```ts
|
||||
const a = 1;
|
||||
const a = 3;
|
||||
```
|
||||
|
||||
## Emphasis
|
||||
|
||||
**This is bold text**
|
||||
|
||||
**This is bold text**
|
||||
|
||||
_This is italic text_
|
||||
|
||||
_This is italic text_
|
||||
|
||||
~~Strikethrough~~
|
||||
|
||||
## Blockquotes
|
||||
|
||||
> Blockquotes can also be nested...
|
||||
>
|
||||
> > by using additional greater-than signs right next to each other...
|
||||
> >
|
||||
> > > ..or with spaces between arrows.
|
||||
|
||||
## Lists
|
||||
|
||||
Unordered
|
||||
|
||||
- Create a list by starting a line with `+`, `-`, or `*`
|
||||
- Sub-lists are made by indenting 2 spaces:
|
||||
- Marker character change forces new list start:
|
||||
- Ac tristique libero volutpat at
|
||||
* Facilisis in pretium nisl aliquet
|
||||
- Nulla volutpat aliquam velit
|
||||
- Very easy!
|
||||
|
||||
Ordered
|
||||
|
||||
1. Lorem ipsum dolor sit amet
|
||||
2. Consectetur adipiscing elit
|
||||
3. Integer molestie lorem at massa
|
||||
|
||||
4. You can use sequential numbers...
|
||||
5. ...or keep all the numbers as `1.`
|
||||
|
||||
Start numbering with offset:
|
||||
|
||||
57. foo
|
||||
1. bar
|
||||
|
||||
## Code
|
||||
|
||||
Inline `code`
|
||||
|
||||
Indented code
|
||||
|
||||
// Some comments
|
||||
line 1 of code
|
||||
line 2 of code
|
||||
line 3 of code
|
||||
|
||||
Block code "fences"
|
||||
|
||||
```
|
||||
Sample text here...
|
||||
```
|
||||
|
||||
Syntax highlighting
|
||||
|
||||
```js
|
||||
var foo = function (bar) {
|
||||
return bar++;
|
||||
};
|
||||
|
||||
console.log(foo(5));
|
||||
```
|
35
src/components/Button/index.tsx
Normal file
35
src/components/Button/index.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { useRef, useState } from "react";
|
||||
import "./index.less";
|
||||
import { IButton } from "./props";
|
||||
|
||||
/**
|
||||
* Button组件
|
||||
*/
|
||||
export default function Button(props: IButton) {
|
||||
const sizeTable = {
|
||||
small: "12px",
|
||||
large: "16px",
|
||||
};
|
||||
const [animation, setAnimation] = useState(false);
|
||||
|
||||
const onClick = () => {
|
||||
setAnimation(true);
|
||||
setTimeout(() => {
|
||||
setAnimation(false);
|
||||
}, 250);
|
||||
console.log("click event handler");
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={animation ? "halo" : ""}
|
||||
style={{ fontSize: sizeTable[props.size] }}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
29
src/components/Button/props.ts
Normal file
29
src/components/Button/props.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
type TSize = "small" | "large";
|
||||
|
||||
export interface IButton {
|
||||
children: string;
|
||||
/**
|
||||
* @description 这是描述住按钮尺寸大小的属性,非常的奈一丝,克苏鲁,san 值拉满
|
||||
* @default small
|
||||
*/
|
||||
size: TSize;
|
||||
|
||||
type?: "primary" | "danger";
|
||||
|
||||
omg?: 1 | 2 | 3;
|
||||
|
||||
and?: Array<number> | Array<string>;
|
||||
|
||||
/**
|
||||
* @description 禁用
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* @description 点击按钮
|
||||
* @param {Event} e 点击dom对象
|
||||
* @version 3.0
|
||||
*/
|
||||
onClick?: (e: Event, callback: () => {}) => {};
|
||||
}
|
8
src/components/Input/demo/demo1.tsx
Normal file
8
src/components/Input/demo/demo1.tsx
Normal file
|
@ -0,0 +1,8 @@
|
|||
import Input from "..";
|
||||
export default function demo1() {
|
||||
return (
|
||||
<div>
|
||||
<Input size="small" />
|
||||
</div>
|
||||
);
|
||||
}
|
3
src/components/Input/index.less
Normal file
3
src/components/Input/index.less
Normal file
|
@ -0,0 +1,3 @@
|
|||
.bolt-input {
|
||||
border: 1px solid red;
|
||||
}
|
15
src/components/Input/index.mdx
Normal file
15
src/components/Input/index.mdx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import Code from "../../../plugin/common/Code.tsx";
|
||||
import Demo1 from "./demo/demo1.tsx";
|
||||
import Demo1Code from "./demo/demo1.tsx?raw";
|
||||
|
||||
## Input
|
||||
|
||||
<Demo1 />
|
||||
<Code raw={Demo1Code} lang="ts" />
|
||||
|
||||
开动小脑静,冷静的思考下,这个问题一定可以不被解决掉
|
||||
|
||||
- 开发阶段 HMR 会重置路由的状态
|
||||
- 切换路由懒加载, markdown 文字区域目录生成的问题
|
||||
|
||||
> 冲啊!Fighting 渣渣,我到河北省来
|
6
src/components/Input/index.tsx
Normal file
6
src/components/Input/index.tsx
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { IInput } from "./props";
|
||||
import "./index.less";
|
||||
|
||||
export default function Input(props: IInput) {
|
||||
return <input className="bolt-input" type="text" placeholder="待封装" />;
|
||||
}
|
7
src/components/Input/props.ts
Normal file
7
src/components/Input/props.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export interface IInput {
|
||||
/**
|
||||
* @description大小的属性
|
||||
* @default small
|
||||
*/
|
||||
size: "small" | "large";
|
||||
}
|
0
src/components/Slider/index.mdx
Normal file
0
src/components/Slider/index.mdx
Normal file
3
src/components/Switch/index.mdx
Normal file
3
src/components/Switch/index.mdx
Normal file
|
@ -0,0 +1,3 @@
|
|||
# switch
|
||||
|
||||
1112222
|
5
src/components/Switch/index.tsx
Normal file
5
src/components/Switch/index.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { ISwitch } from "./props";
|
||||
|
||||
export default function Switch(props: ISwitch) {
|
||||
return <div>Switch</div>;
|
||||
}
|
11
src/components/Switch/props.ts
Normal file
11
src/components/Switch/props.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export interface ISwitch {
|
||||
/**
|
||||
* @description 开关
|
||||
*/
|
||||
checked: boolean;
|
||||
|
||||
/**
|
||||
* @description 测试2
|
||||
*/
|
||||
test: 1 | 2 | 3
|
||||
}
|
5
src/index.ts
Normal file
5
src/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Button from "./components/Button";
|
||||
import Input from "./components/Input";
|
||||
import Switch from "./components/Switch";
|
||||
|
||||
export { Button, Input, Switch };
|
13
src/main.tsx
Normal file
13
src/main.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "../plugin/common/style/index.less";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
// 会执行两次
|
||||
// <React.StrictMode>
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
// </React.StrictMode>
|
||||
);
|
3
src/pages/Intro.mdx
Normal file
3
src/pages/Intro.mdx
Normal file
|
@ -0,0 +1,3 @@
|
|||
# 介绍
|
||||
|
||||
滴滴滴滴
|
46
src/pages/Welcome.mdx
Normal file
46
src/pages/Welcome.mdx
Normal file
|
@ -0,0 +1,46 @@
|
|||
# Bolt
|
||||
|
||||
Bolt 是快速简单的组件库开发脚手架,用于构建快速组件库、文档。你可以在开发`react组件库`的同时, 无缝地编写`组件文档`, 实时热更新
|
||||
|
||||
# ✨ 特性
|
||||
|
||||
- 🌈 使用 `markdown` 语法编写组件文档
|
||||
- ⚙️ 使用 `mdx` 扩展了 `markdown` 的能力, 你可以将 `Jsx.Element` 引入 `markdown`
|
||||
- 🛡 摒弃 `PropTypes`, 使用 `typescript` 约束组件 `props`, 组件文档自动化生成相应 `API`
|
||||
- 📦 借助 `vite`、`rollup`能力, `文档站`,`组件库`一并构建产出
|
||||
|
||||
# MDX
|
||||
|
||||
`MDX` 允许在降价内容中使用 JSX。您可以导入组件, 例如交互式图表或警报, 并将其嵌入到内容中。
|
||||
|
||||
```md
|
||||
import Code from "./Code.tsx";
|
||||
|
||||
# Test
|
||||
|
||||
mdx 文件引入 react 组件示例
|
||||
|
||||
<Code raw={Demo1Code} lang="ts" />
|
||||
```
|
||||
|
||||
# 项目结构
|
||||
|
||||
```bash
|
||||
├── bolt.config.ts # bolt 基本配置
|
||||
├── dist
|
||||
│ ├── doc # 文档静态资源构建产物
|
||||
│ └── ui # 组件库构建产物
|
||||
├── src
|
||||
│ ├── pages # 自定义页面
|
||||
│ │ ├── Intro.mdx
|
||||
│ │ └── Welcome.mdx
|
||||
│ ├── assets # 静态资源
|
||||
│ │ └── common.less
|
||||
│ ├── components # 组件开发目录
|
||||
│ │ ├── Button
|
||||
│ │ │ ├── index.less # 样式
|
||||
│ │ │ ├── index.mdx # 组件文档
|
||||
│ │ │ ├── index.tsx # 组件
|
||||
│ │ │ └── props.ts # 组件 props
|
||||
│ ├── index.ts # 组件是否打包进组件库
|
||||
```
|
9
src/vite-env.d.ts
vendored
Normal file
9
src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "@bolt/config" {
|
||||
export const TREE;
|
||||
}
|
||||
declare module "*.mdx" {
|
||||
let MDXComponent: (props: any) => JSX.Element;
|
||||
export default MDXComponent;
|
||||
}
|
39
tsconfig.json
Normal file
39
tsconfig.json
Normal file
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"lib",
|
||||
"common",
|
||||
"plugin/vite-plugin-mozzie",
|
||||
"plugin/ui",
|
||||
"plugin/types",
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
],
|
||||
"path": {
|
||||
"@bolt/config": "virtual:@vite/plugin-bolt",
|
||||
"@": "src"
|
||||
}
|
||||
}
|
13
tsconfig.lib.json
Normal file
13
tsconfig.lib.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true, // 输出d.ts
|
||||
"noEmit": true, // 生输出编译文件
|
||||
"removeComments": true,
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "./dist/BoltUI"
|
||||
},
|
||||
"include": [
|
||||
"src/components/**/*.tsx",
|
||||
"src/components/**/*.ts",
|
||||
],
|
||||
}
|
14
tsconfig.node.json
Normal file
14
tsconfig.node.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"bolt.config.ts",
|
||||
"plugin",
|
||||
"plugin/build",
|
||||
]
|
||||
}
|
9
tsconfig.prod.json
Normal file
9
tsconfig.prod.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": false, // 输出d.ts
|
||||
"noEmit": true, // 生输出编译文件
|
||||
"removeComments": true,
|
||||
"outDir": "./dist/doc"
|
||||
},
|
||||
}
|
62
vite.config.ts
Normal file
62
vite.config.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import Bolt from "./plugin/vite-plugin-bolt";
|
||||
import rehypePrism from "@mapbox/rehype-prism";
|
||||
import { build } from "./plugin/build/vite.conf";
|
||||
import dts from "vite-plugin-dts";
|
||||
import BoltConfig from "./bolt.config";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(async ({ mode }): Promise<{}> => {
|
||||
const mdx = await import("@mdx-js/rollup");
|
||||
return {
|
||||
server: {
|
||||
watch: {
|
||||
ignored: ["!**/dist/**"],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
process.env.MODE !== "production"
|
||||
? react({
|
||||
jsxRuntime: "classic",
|
||||
})
|
||||
: react(),
|
||||
mdx.default({
|
||||
jsxRuntime: "automatic",
|
||||
providerImportSource: "@mdx-js/react",
|
||||
rehypePlugins: [rehypePrism], // syntax highlight会compile pre > code
|
||||
remarkPlugins: [],
|
||||
}),
|
||||
Bolt({ config: BoltConfig }),
|
||||
mode === "lib" &&
|
||||
dts({
|
||||
entryRoot: "src",
|
||||
outputDir: "dist/ui",
|
||||
}),
|
||||
mode !== "doc" && {
|
||||
name: "style",
|
||||
generateBundle(config, bundle) {
|
||||
//这里可以获取打包后的文件目录以及代码code
|
||||
const keys = Object.keys(bundle);
|
||||
for (const key of keys) {
|
||||
const bundler: any = bundle[key as any];
|
||||
//rollup内置方法,将所有输出文件code中的.less换成.css
|
||||
//TODO 导致sourcemap 文件丢失,待解决
|
||||
this.emitFile({
|
||||
type: "asset",
|
||||
fileName: key, //文件名名不变
|
||||
source: bundler.code.replace(/\.less/g, ".css"),
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
alias: {
|
||||
"@bolt/config": "virtual:@vite/plugin-bolt",
|
||||
"@": "src",
|
||||
},
|
||||
},
|
||||
build: build(mode),
|
||||
};
|
||||
});
|
Loading…
Reference in New Issue
Block a user