Repository: MuYunyun/create-react-doc
Branch: main
Commit: 36c56cecb7ae
Files: 171
Total size: 288.2 KB
Directory structure:
gitextract_uke23iaa/
├── .editorconfig
├── .eslintrc.js
├── .github/
│ ├── FUNDING.yml
│ ├── config.yml
│ ├── dependabot.yml
│ ├── stale.yml
│ └── workflows/
│ ├── gh-pages.yml
│ ├── greetings.yml
│ └── traffic2badge.yml
├── .gitignore
├── .npmignore
├── .npmrc
├── .yarnrc
├── CONTRIBUTING.md
├── README-en.md
├── README.md
├── components/
│ ├── Button/
│ │ ├── index.jsx
│ │ └── index.less
│ └── index.jsx
├── config.yml
├── docs/
│ ├── Front-matter.md
│ ├── 主题/
│ │ ├── 自定义主题.md
│ │ └── 默认主题.md
│ ├── 书写组件.md
│ ├── 其它工具.md
│ ├── 快速上手.md
│ ├── 数学公式.md
│ ├── 更新日志.md
│ ├── 测试/
│ │ ├── 测试标签.md
│ │ └── 测试路由.md
│ ├── 站点发布.md
│ └── 高阶用法.md
├── gh-pages.js
├── injectLogic/
│ └── index.js
├── lerna.json
├── package.json
├── packages/
│ ├── crd-client-utils/
│ │ ├── .npmrc
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ ├── crd-generator-sitemap/
│ │ ├── .npmrc
│ │ ├── README.md
│ │ ├── generate.js
│ │ ├── index.js
│ │ └── package.json
│ ├── crd-scripts/
│ │ ├── .npmrc
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── package.json
│ │ └── src/
│ │ ├── build.js
│ │ ├── commands/
│ │ │ ├── initProject.js
│ │ │ └── initTheme.js
│ │ ├── conf/
│ │ │ ├── getDirTree.js
│ │ │ ├── getPrerenderRoutes.js
│ │ │ ├── node-directory-tree.js
│ │ │ ├── path.js
│ │ │ ├── rawTreeReplaceLoader.js
│ │ │ ├── webpack.config.dev.js
│ │ │ ├── webpack.config.js
│ │ │ ├── webpack.config.prod.js
│ │ │ └── webpack.config.server.js
│ │ ├── deploy.js
│ │ ├── generate.js
│ │ ├── server.js
│ │ ├── utils/
│ │ │ ├── index.js
│ │ │ └── initCache.js
│ │ └── web/
│ │ ├── Router.js
│ │ ├── crd.json
│ │ └── index.js
│ ├── crd-seed/
│ │ ├── .npmrc
│ │ ├── README.md
│ │ ├── component/
│ │ │ ├── Affix/
│ │ │ │ ├── affix.js
│ │ │ │ ├── index.js
│ │ │ │ └── utils/
│ │ │ │ └── index.js
│ │ │ ├── Footer/
│ │ │ │ ├── index.js
│ │ │ │ └── index.less
│ │ │ ├── Header/
│ │ │ │ ├── index.js
│ │ │ │ └── index.less
│ │ │ ├── Icon/
│ │ │ │ ├── Icon.js
│ │ │ │ ├── iconsInfo.js
│ │ │ │ ├── index.js
│ │ │ │ ├── loadSprite.js
│ │ │ │ └── style/
│ │ │ │ └── index.less
│ │ │ ├── Menu/
│ │ │ │ ├── Menu.js
│ │ │ │ ├── MenuItem.js
│ │ │ │ ├── SubMenu.js
│ │ │ │ ├── context.js
│ │ │ │ ├── index.js
│ │ │ │ ├── style/
│ │ │ │ │ ├── index.less
│ │ │ │ │ └── theme.less
│ │ │ │ ├── transition.js
│ │ │ │ └── util.js
│ │ │ ├── NoMatch/
│ │ │ │ ├── index.js
│ │ │ │ └── index.less
│ │ │ ├── Search/
│ │ │ │ ├── README.md
│ │ │ │ ├── index.js
│ │ │ │ └── index.less
│ │ │ └── Tags/
│ │ │ ├── index.js
│ │ │ └── index.less
│ │ ├── index.js
│ │ ├── index.less
│ │ ├── language/
│ │ │ └── index.js
│ │ ├── layout/
│ │ │ ├── index.js
│ │ │ ├── index.less
│ │ │ └── utils.js
│ │ ├── package.json
│ │ ├── style/
│ │ │ ├── base.less
│ │ │ └── mobile.less
│ │ └── utils/
│ │ └── index.js
│ ├── crd-templates/
│ │ ├── .npmrc
│ │ ├── README.md
│ │ ├── default/
│ │ │ ├── .github/
│ │ │ │ └── workflows/
│ │ │ │ └── gh-pages.yml
│ │ │ ├── .npmrc
│ │ │ ├── Introduction/
│ │ │ │ └── hello_world.md
│ │ │ ├── README.md
│ │ │ ├── _.gitignore
│ │ │ ├── _config.yml
│ │ │ └── _package.json
│ │ ├── package.json
│ │ └── theme/
│ │ └── default/
│ │ ├── Introduction/
│ │ │ └── hello_world.md
│ │ ├── README.md
│ │ ├── _.gitignore
│ │ ├── _.npmrc
│ │ ├── _config.yml
│ │ ├── _index.js
│ │ ├── _index.less
│ │ └── _package.json
│ ├── crd-theme/
│ │ ├── .npmrc
│ │ ├── README.md
│ │ ├── component/
│ │ │ └── Loading/
│ │ │ ├── index.js
│ │ │ └── index.less
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── index.less
│ │ ├── markdown/
│ │ │ ├── Link.js
│ │ │ ├── Link.less
│ │ │ ├── codeBlock.js
│ │ │ ├── index.js
│ │ │ └── style/
│ │ │ ├── css.less
│ │ │ ├── default.less
│ │ │ ├── diff.less
│ │ │ ├── index.less
│ │ │ ├── javascript.less
│ │ │ ├── swift.less
│ │ │ └── xml.less
│ │ └── package.json
│ ├── crd-utils/
│ │ ├── .npmrc
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── package.json
│ │ └── path.js
│ ├── create-react-doc/
│ │ ├── .npmignore
│ │ ├── .npmrc
│ │ ├── .yarnrc
│ │ ├── README.md
│ │ ├── index.js
│ │ └── package.json
│ └── leetcode-cli/
│ ├── .npmrc
│ ├── README.md
│ ├── leetcode-table.md
│ ├── package.json
│ ├── problems.json
│ └── src/
│ ├── cli.js
│ ├── download.js
│ ├── leetcode-table.md
│ ├── leetcode.js
│ ├── logout.js
│ ├── problems.json
│ └── utils.js
└── utils/
├── uppackage-dev.sh
└── uppackage.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.less]
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab
================================================
FILE: .eslintrc.js
================================================
const eslintrc = {
"parser": "babel-eslint",
"extends": "airbnb",
"env": {
"browser": true,
"node": true,
"es6": true,
"mocha": true,
"jest": true,
"jasmine": true
},
"plugins": [
"react",
"import"
],
"parserOptions": {
parser: 'babel-eslint',
},
"rules": {
"linebreak-style": 0,
"func-names": 0,
"sort-imports": 0,
"arrow-body-style": 0,
"prefer-destructuring": 0,
"max-len": 0,
"consistent-return": 0,
"comma-dangle": [
"error",
"always-multiline"
],
"function-paren-newline": 0,
"class-methods-use-this": 0,
"react/sort-comp": 0,
"react/prop-types": 0,
"react/jsx-first-prop-new-line": 0,
"react/require-extension": 0,
"react/jsx-filename-extension": [
1,
{
"extensions": [
".js",
".jsx"
]
}
],
"import/extensions": 0,
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0,
"import/prefer-default-export": 0,
"jsx-a11y/no-static-element-interactions": 0,
"jsx-a11y/anchor-has-content": 0,
"jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/anchor-is-valid": 0,
"jsx-a11y/label-has-for": 0,
"jsx-a11y/no-noninteractive-element-interactions": 0,
"jsx-a11y/mouse-events-have-key-events": 0,
"react/no-danger": 0,
"react/jsx-no-bind": 0,
"react/forbid-prop-types": 0,
"react/require-default-props": 0,
"react/no-did-mount-set-state": 0,
"react/no-array-index-key": 0,
"react/no-find-dom-node": 0,
"react/no-unused-state": 0,
"react/no-unused-prop-types": 0,
"react/default-props-match-prop-types": 0,
"react/jsx-curly-spacing": 0,
"react/no-render-return-value": 0,
'react/jsx-uses-react': 0,
'react/react-in-jsx-scope': 0,
"object-curly-newline": 0,
"no-param-reassign": 0,
"no-return-assign": 0,
"no-redeclare": 0,
"no-restricted-globals": 0,
"no-restricted-syntax": 0,
"no-underscore-dangle": 0,
"no-unused-expressions": 0,
"no-use-before-define": 0,
"semi": ["error", "never"],
"quotes": 0,
"no-plusplus": 0
}
}
if (process.env.NODE_ENV === 'development') {
Object.assign(eslintrc.rules,
{
'no-console': 0,
'no-unused-vars': 0,
});
}
module.exports = eslintrc
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['http://muyunyun.cn/sponsor/']
================================================
FILE: .github/config.yml
================================================
todo:
keyword: "@makeAnIssue"
================================================
FILE: .github/dependabot.yml
================================================
# see https://github.com/yi-Xu-0100/traffic-to-badge/blob/main/.github/dependabot.yml
version: 2
updates:
# Enable version updates for npm
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'
# Maintain dependencies for GitHub Actions
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'daily'
================================================
FILE: .github/stale.yml
================================================
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- pinned
- security
- "[Status] Maybe Later"
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: wontfix
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
# closeComment: >
# Your comment here.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# issues:
# exemptLabels:
# - confirmed
================================================
FILE: .github/workflows/gh-pages.yml
================================================
name: GitHub Pages
on:
push:
branches:
- main
pull_request:
jobs:
deploy:
runs-on: ubuntu-22.04
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Get yarn cache
id: yarn-cache
run: echo "YARN_CACHE_DIR=$(yarn cache dir)" >> "${GITHUB_OUTPUT}"
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.yarn-cache.outputs.YARN_CACHE_DIR }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile
- run: yarn build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/main' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .crd-dist
================================================
FILE: .github/workflows/greetings.yml
================================================
name: Greetings
on: [pull_request, issues]
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Welcome your first issue here, thanks'
pr-message: 'Welcome your first pr here, thanks'
================================================
FILE: .github/workflows/traffic2badge.yml
================================================
name: traffic2badge
on:
push:
branches:
- main
schedule:
- cron: '1 0 * * *' #UTC
jobs:
run:
name: Make GitHub Traffic to Badge
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2.3.3
- name: Get Commit Message
id: message
uses: actions/github-script@v3.0.0
env:
FULL_COMMIT_MESSAGE: '${{ github.event.head_commit.message }}'
with:
result-encoding: string
script: |
var message = `${process.env.FULL_COMMIT_MESSAGE}`;
core.info(message);
if (message != '') return message;
var time = new Date(Date.now()).toISOString();
core.info(time);
return `Get traffic data at ${time}`;
- name: Set Traffic
id: traffic
uses: yi-Xu-0100/traffic-to-badge@v1.4.0
with:
my_token: ${{ secrets.TRAFFIC_TOKEN }}
#(default) static_list: ${{ github.repository }}
#(default) traffic_branch: traffic
#(default) views_color: brightgreen
#(default) clones_color: brightgreen
#(default) logo: github
#(default) year:
- name: Deploy
uses: peaceiris/actions-gh-pages@v3.7.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: ${{ steps.traffic.outputs.traffic_branch }}
publish_dir: ${{ steps.traffic.outputs.traffic_path }}
user_name: 'github-actions[bot]'
user_email: 'github-actions[bot]@users.noreply.github.com'
full_commit_message: ${{ steps.message.outputs.result }}
- name: Show Traffic Data
run: |
echo ${{ steps.traffic.outputs.traffic_branch }}
echo ${{ steps.traffic.outputs.traffic_path }}
cd ${{ steps.traffic.outputs.traffic_path }}
ls -a
================================================
FILE: .gitignore
================================================
node_modules
.create-react-doc-dist
package-lock.json
.cache
.DS_Store
.crd-dist/
*.bak
*.tem
*.log
*.temp
#.swp
*.*~
~*.*
docs/忽略文件.md
================================================
FILE: .npmignore
================================================
.cache
.gitignore
.editorconfig
.create-react-doc-dist
node_modules
package-lock.json
dist
================================================
FILE: .npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
================================================
FILE: .yarnrc
================================================
# .yarnrc
registry "https://registry.npmjs.org/"
================================================
FILE: CONTRIBUTING.md
================================================
# HOW TO CONTRIBUTE
1. Welcome your pr! Before pr, talk about situations in the [issue](https://github.com/MuYunyun/create-react-doc/issues/new) firstly. If the situation is reasonable, go to the next step;
2. Switch to the new branch based main, submit the pr to branch `qa/latest` after finishing development.
## DEV
Run these bash command firstly.
```bash
$ git clone https://github.com/MuYunyun/create-react-doc
$ cd create-react-doc
$ yarn && yarn bootstrap && yarn start
```
And now you can see the document is running at http://localhost:3000.
## Test
After merging pr to qa/latest and publish beta package. You should verify the feature/bugfix with following bash:
```js
yarn add create-react-doc@beta
```
================================================
FILE: README-en.md
================================================
_.-"\
_.-" \
,-" \
\ create \
\ \ react \
\ \ doc \
\ \ _.-;
\ \ _.-" :
\ \,-" _.-"
\( _.-"
`--"
[](https://badge.fury.io/js/create-react-doc)
[](https://www.npmjs.com/package/create-react-doc)




English | [简体中文](./README.md)
# Create React Doc
[Create React Doc](https://github.com/MuYunyun/create-react-doc) is a markdown document site generation tool using React just like [create-react-app](https://github.com/facebook/create-react-app), developers can use Create React Doc to develop, deploy documents or blog sites without worrying about additional environment configuration information.
## Features
* The idea of building a site: Just write markdown files as a blog site [like me](https://github.com/MuYunyun/blog).
* Out of box: One-click generation of documents and blog sites by specifying directories or documents, no need to care about site environment configuration information.
* Performance: greatly improve site loading speed through pre-rendering and lazy loading.
* Based on mdx: Support writing React components, mathematical formulas, etc. in markdown.
* Search engine optimization: Support SEO, making documents easier to search.
* Personalization: Support [custom theme](https://muyunyun.cn/create-react-doc/9f41fc98).
* Workflow: Integrate Github actions, support automated packaging and publishing sites.
> [Quick Start](https://muyunyun.cn/create-react-doc/290a4219)
## Subject
Create React Doc provides the official default theme [crd-seed](https://github.com/MuYunyun/create-react-doc/tree/main/packages/crd-seed). The theme supports the following features:
* Adapt to mobile and PC multi-terminal display.
* Support dark mode.
* The document supports embedded codepen, codesandbox.
* GitHub linkage.
* Support using tags to customize aggregate article content.
[my blog](http://muyunyun.cn/blog) is based [crd-seed](https://github.com/MuYunyun/create-react-doc/tree/main/packages/crd-seed) theme to build。


If you want to customize or share personal themes, you can refer to the [Custom Theme](https://muyunyun.cn/create-react-doc/9f41fc98) chapter.
## Get started quickly
**Create React Doc** is very easy to use. Developers don't need to install or configure additional tools such as webpack or Babel, they are built-in and hidden in the scaffolding, so developers can concentrate on document writing.
If you want to create a site file `doc` under the current file, here are three ways to quickly build a site:
### npx
```bash
npx create-react-doc doc
```
### npm
```bash
npm init create-react-doc doc
```
### yarn
```bash
yarn create react-doc doc
```

> If you want to pull the content of the template to the current folder, you can replace the `doc` of the above command with `.`, such as executing `npx create-react-doc .`.
Then execute `cd doc && yarn && yarn start`, you can preview the site at `localhost: 3000`, if the site document changes, the site will automatically reload.
## Site release
In the [Quick Start](http://muyunyun.cn/create-react-doc/QuickStart) section, it introduces how to quickly build a site. This section will introduce how to package and publish the built site to gh-pages.
### Automatically package and publish to gh-pages (recommended)
The initialized template project integrates the [ci configuration](https://github.com/MuYunyun/create-react-doc/blob/main/packages/templates/default/.github/workflows/gh-pages.yml) of `Github action`, the user only needs to execute `git push` on the main branch to complete the automatic deployment of the site.

If it is the first deployment, after performing the following operations, you need to select Github Pages as gh-pages in the setting tab of the project. (See [First Deployment with GITHUB_TOKEN](https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-first-deployment-with-github_token) for details)
```bash
git init
git add.
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/user or organization name/project name.git
git push -u origin main
```
> For more content, please visit [Site Release](http://muyunyun.cn/create-react-doc/SiteRelease), [Advanced Usage](http://muyunyun.cn/create-react-doc/HighOrderusage), [other tools](http://muyunyun.cn/create-react-doc/othertools) and other chapters.
## Practice Sharing
* [基于 SSR 的预渲染首屏直出方案](http://muyunyun.cn/blog/g3v1c5bq)
* [SEO 在 SPA 站点中的实践](http://muyunyun.cn/blog/ettzfags)
================================================
FILE: README.md
================================================
_.-"\
_.-" \
,-" \
\ create \
\ \ react \
\ \ doc \
\ \ _.-;
\ \ _.-" :
\ \,-" _.-"
\( _.-"
`--"
[](https://badge.fury.io/js/create-react-doc)
[](https://www.npmjs.com/package/create-react-doc)




[English](./README-en.md) | 简体中文
# Create React Doc
[Create React Doc](https://github.com/MuYunyun/create-react-doc) 是一个使用 React 的 markdown 文档站点生成工具。就像 [create-react-app](https://github.com/facebook/create-react-app) 一样,开发者可以使用 Create React Doc 来开发、部署文档或者博客站点而无需关心额外的环境配置信息。
## 特性
* 建站理念: `文件即站点` (Files as a Site)。
* 开箱即用: 通过指定目录或文档, 一键生成文档、博客站点, 无需关心站点环境配置信息。
* 流畅的用户体验: 内置 SSR 首屏直出方案(基于 gp-pages 服务),以提升用户体验。
* 基于 mdx: 支持在 markdown 中`书写 React 组件`、数学公式等。
* 搜索引擎优化: 支持 SEO, 让文档更易被搜索。
* 个性化: 支持[自定义主题](https://muyunyun.cn/create-react-doc/9f41fc98)。
* 工作流: 集成 Github action, 支持自动化打包、发布站点。
> [快速上手](https://muyunyun.cn/create-react-doc/290a4219)
## 主题
create-react-doc 提供了默认主题 [crd-seed](https://github.com/MuYunyun/create-react-doc/tree/main/packages/crd-seed)。
该主题支持以下特性:
- [x] 适配网页/移动端展示。
- [x] 支持暗黑模式。
- [x] 支持标签页自定义聚合文章内容。
- [x] 内置评论模块。
- [x] 支持内嵌展示 codepen、codesandbox 案例。
- [x] 支持从文档页快速跳转到对应的 Github 文档页进行在线编辑。
该主题效果可以参考[笔者博客](http://muyunyun.cn/blog)。


如果您想定制化或者分享个人主题,可以参考[自定义主题](https://muyunyun.cn/create-react-doc/9f41fc98)章节。
## 快速上手
**create-react-doc** 非常容易上手。开发者不需要额外安装或配置 webpack 或者 Babel 等工具,它们被内置隐藏在脚手架中,因此开发者可以专心于文档的书写。
如果你想在当前文件下建立站点文件 `doc`, 这里提供如下三种方式快速建站:
### npx
```bash
npx create-react-doc doc
```
### npm
```bash
npm init create-react-doc doc
```
### yarn
```bash
yarn create react-doc doc
```

> 如果想把模板内容内容拉取到当前文件夹, 则可以将如上命令的 `doc` 替换为 `.`, 比如执行 `npx create-react-doc .`。
接着执行 `cd doc && yarn && yarn start`, 可以在 `localhost: 3000` 预览站点, 如果站点文档发生改变, 站点将自动重新加载。
## 站点发布
在 [快速上手](https://muyunyun.cn/create-react-doc/290a4219) 一节中介绍了如何快速搭建站点, 本节将介绍如何将搭建好的站点打包、发布到 gh-pages。
### 自动打包发布到 gh-pages (推荐)
初始化的模板项目集成了 `Github action` 的 [ci 配置](https://github.com/MuYunyun/create-react-doc/blob/main/packages/templates/default/.github/workflows/gh-pages.yml), 使用方只需在 main 分支执行 `git push` 即可以完成站点的自动部署。

如果是第一次部署, 在执行以下操作后, 需要在项目的 setting 选项卡中将 Github Pages 选择为 gh-pages。(详情见 [First Deployment with GITHUB_TOKEN](https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-first-deployment-with-github_token))
```bash
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/用户或组织名/项目名.git
git push -u origin main
```
## 更多内容
* [站点发布](http://muyunyun.cn/create-react-doc/ude9296y)
* [高阶用法](http://muyunyun.cn/create-react-doc/9v9ug9h8)
* [其它工具](http://muyunyun.cn/create-react-doc/292h2c5k)
* [Front-matter](http://muyunyun.cn/create-react-doc/49g6b239)
## 实践分享
* [基于 SSR 的预渲染首屏直出方案](http://muyunyun.cn/blog/g3v1c5bq)
* [SEO 在 SPA 站点中的实践](http://muyunyun.cn/blog/ettzfags)
================================================
FILE: components/Button/index.jsx
================================================
import styles from './index.less'
const Button = ({
children,
}) => {
return {children}
}
export default Button
================================================
FILE: components/Button/index.less
================================================
.btn {
color: #fff;
background: #1890ff;
border-color: #1890ff;
text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
box-shadow: 0 2px #0000000b;
height: 40px;
padding: 6.4px 15px;
font-size: 16px;
border-radius: 2px;
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
}
================================================
FILE: components/index.jsx
================================================
import Button from './Button/index.jsx'
export {
Button,
}
================================================
FILE: config.yml
================================================
# create-react-doc configuration.
# Site
title: Create React Doc
# Menu dir
## you can also set detailed dir, such as BasicSkill/css
## todo: auto menu
menu: [
docs/快速上手.md,
docs/更新日志.md,
docs/主题,
docs/站点发布.md,
docs/Front-matter.md,
docs/数学公式.md,
docs/书写组件.md,
docs/高阶用法.md,
docs/其它工具.md,
docs/测试
]
## set init open menu keys
# menuOpenKeys:
# site theme
devTheme: packages/crd-seed/index
# theme: crd-seed
# Github
## if you want to editing pages on github, you should config these arguments.
user: MuYunyun
repo: create-react-doc
github-ribbons: true
domain: https://muyunyun.cn
seo:
google: true
# Available values: en | zh-cn
language: en
# Inject Custom Logic
inject: injectLogic/index.js
# Use abbrlink
abbrlink: true
# Show Tags in head
tags: true
# Config comment section
comment:
# Giscus Config, The config parameter that's supported can be seen in [giscus-component](https://github.com/giscus/giscus-component#documentation).
GiscusConfig:
repoId: MDEwOlJlcG9zaXRvcnkyNjgwMTE4MzA=
categoryId: DIC_kwDOD_mJNs4CSd1W
================================================
FILE: docs/Front-matter.md
================================================
## Front-matter
Front-matter 是文件最上方包裹在 `` 之间的区域,用于指定个别文件的变量,举例来说:
```md
```
以下是预先定义的参数,您可在模板中使用这些参数值并加以利用。
| 参数 | 描述 | 类型 |
| :------- | :-------------------------------------------------------------- | :------- |
| abbrlink | 短链。用于指定页面路由展示为指定短链,使用短链有助于 SEO 搜索。 | |
| tags | 自定义标签 | string[] |
## 链接持久化
在以下场景需求场合中,可以展示短链以优化 URL 的显示。
* SEO 场景下需要链接持久化。
* URL 链接中存在中文会被转码展示。
* 文档的路径与文件名经常变更。
### 如何使用短链
在 `config.yml` 增加配置 `abbrlink: true`
```diff
+ abbrlink: true
```
做好上述配置后,接着在控制台执行 `react-doc generate` 即可给 menu 配置属性中的所有文章目录文件加上短链资源。
```bash
react-doc generate // 一键给所有文章加上短链
```
================================================
FILE: docs/主题/自定义主题.md
================================================
## 使用自定义主题
切换主题非常简单, 只需要将根目录文件 `config.yml` 中的 `theme` 更改为您想使用的主题即可。
```diff
+ theme: custom-theme
```
### 如何开发自定义主题包
create-react-doc 脚手架提供了脚本命令 `react-doc theme` 用来一键创建主题包开发环境。

```js
// 安装 create-react-doc
yarn add create-react-doc -g
// 执行 react-doc theme 并输入主题包名字
react-doc theme
```
进入到所创建主题目录, 执行 `yarn && yarn start`, 此时会自动打开浏览器, 并在屏幕中央显示 `Write docs happily now.`。如下图所示:

恭喜你, 此时你已经将主题开发环境配置完成。接着便可以开始愉快地定制个人主题了。
在所创建的主题项目中使用了 `react v18`,`react-router-dom v6`,项目支持使用 `less` 语法。
```js
import { Switch, Route } from 'react-router-dom'
import styles from './index.less'
const CustomTheme = (props: CustomThemeProps) => {
return (
Welcome to your own theme
)
}
export default CustomTheme
```
CustomThemeProps 的接口类型暴露了菜单资源 `menuSource` 与路由资源 `routeData`, 自定义主题时可以按需使用它们。
```js
interface CustomThemeProps {
/** 菜单资源 */
menuSource: {
/** 文件名称 eg: '快速上手.md' */
name: string
/** 文件扩展名 eg: '.md' */
extension: string
/** 文件路径 eg: '/docs/快速上手.md' */
path: string
/** 路由路径 eg: ‘/快速上手’ */
routePath: string
/** 文件大小 eg: 924 */
size: number
/** 文件类型 eg: 'file' */
type: string
/** 文件创建日期 eg: '2020-11-11' */
birthtime: string
/** 文件修改日期 eg: '2021-01-14' */
mtime: string
}[]
/** 路由资源 */
routeData: {
/** 文件名称 eg: '快速上手.md' */
article: string
/** 异步加载 markdown 组件函数 */
component: AsyncRouteComponent(props)
/** markdown 文章信息对象。若为文件则有 title 字段, 若为文件夹则无 title 字段 */
mdconf: { title?: string }
/** 文件路径 eg: '/docs/快速上手' */
path: string
}[]
}
```
此外在自定义主题文件中可以自由使用由 webpack 注入的 `DOCSCONFIG` 对象中的变量, DOCSCONFIG 中的变量与项目根目录中的 `config.yml` 文件变量一一对应。
比如 `config.yml` 配置如下所示:
```bash
menu: ['Introduction']
theme: crd-seed
user: muyunyun
repo: https://github.com/MuYunyun/create-react-doc
language: en
```
则主题项目中可以通过如下方式获取到 `config.yml` 配置属性。
```js
const { menu, theme, user } = DOCSCONFIG || {}
```
================================================
FILE: docs/主题/默认主题.md
================================================
## 默认主题
create-react-doc 的默认主题为 [crd-seed](https://github.com/MuYunyun/create-react-doc/tree/main/packages/crd-seed)。
该主题支持以下特性:
- [x] 适配网页/移动端展示。
- [x] 支持暗黑模式。
- [x] 支持标签页自定义聚合文章内容。
- [x] 内置评论模块。
- [x] 支持内嵌展示 codepen、codesandbox 案例。
- [x] 支持从文档页快速跳转到对应的 Github 文档页进行在线编辑。
使用该主题搭建的项目有:
* [blog](https://github.com/MuYunyun/blog), [站点](http://muyunyun.cn/blog)
* 
* 
* [diana](https://github.com/MuYunyun/diana), [站点](https://muyunyun.cn/diana/)
> 如果您有其它的改进优化想法, 欢迎留言补充
## config.yml
[config.yml](https://github.com/MuYunyun/create-react-doc/blob/main/packages/templates/default/_config.yml) 文件是配置站点主题功能的地方。
它支持配置的属性如下:
| 属性名 | 作用 | 类型 | 默认 |
| :------------: | :----------------------------------: | :---------------------------------------------------------------------------------: | :------: |
| title | 站点名 | string | |
| menu | 作为站点菜单的文件/文件夹路径 | string[] | |
| menuOpenKeys | 默认展开菜单的文件夹路径 | string | |
| user | Github 用户名 | string | |
| repo | Github 项目名 | string | |
| language | 站点语言 | en \| zh-cn | en |
| github-ribbons | 是否在右上角显示 github 丝带 | boolean | false |
| theme | 使用主题 | string | crd-seed |
| devTheme | 开发自定义主题时, 需设置其为 true | string | ./index |
| seo | 是否开启 SEO 优化 | { google?: boolean } | |
| domain | SEO 优化的站点域名, 用于生成 sitemap | string | |
| comment | 开启评论区,并进行相关配置 | { GiscusConfig: [Props](https://github.com/giscus/giscus-component#documentation) } | |
详细用法可以参考 [config.yml](https://github.com/MuYunyun/blog/blob/main/config.yml)。
================================================
FILE: docs/书写组件.md
================================================
import { Button } from '../components/index.jsx'
## 书写组件
[create-react-doc](https://github.com/MuYunyun/create-react-doc) 内置了 [mdx](https://github.com/mdx-js/mdx), 你可以在 .md 文件中书写 React 组件, 因此你可以选择 create-react-doc 来快速搭建组件站点。
## 例子
### Button 组件
```js
import { Button } from '../components/index.jsx'
Primary
```
Primary
================================================
FILE: docs/其它工具.md
================================================
## 其它工具
### crd-leetcode-cli
#### 背景
当新增 LeetCode 题解时需要[手动更新表格](https://github.com/MuYunyun/blog/blob/main/LeetCode/README.md), 较为不便。[crd-leetcode-cli](https://github.com/MuYunyun/create-react-doc/tree/main/packages/leetcode-cli) 提供了更新 leetcode 站点中已 ac 题解的能力。
#### 安装
执行 `yarn add crd-leetcode-cli -g`, 国内用户可以执行 `cnpm install crd-leetcode-cli -g`
#### 使用
```bash
leetcode download // 增量拉取 AC 题目(若无登录, 则会先执行登录逻辑)
leetcode download -a // 全量拉取 AC 题目
leetcode login // 登录
leetcode logout // 登出
```
#### 自定义渲染表格
插件提供了自定义渲染 markdown table 的能力。
在项目根目录创建 [config.js](https://github.com/MuYunyun/blog/blob/main/config.js) 文件。
在 config.js 内自定义生成 markdown 的 [transform_markdown_table 函数](https://github.com/MuYunyun/blog/blob/main/config.js#L5-L22)。
```js
const transform_markdown_table = (dataArr: QuestionProps[]): string => {}
module.exports = { transform_markdown_table }
```
QuestionProps 接口定义如下:
| 名称 | 含义 | 例子 |
| :--------: | :--------------: | :-----: |
| questionId | 题号 | |
| title | 标题 | Two Sum |
| titleSlug | 标题的另一种模式 | two-sum |
| difficulty | 难度 | |
| topicTags | 题目所属标签 | |
通过自定义 transform_markdown_table 函数, 便可得到如下 markdown table:

#### 技术细节
* 使用 puppeteer 登录 leetcode 获取 cookie 信息。
* 获取 cookie 后, 使用 graphql-request 调用 graphql 接口获取题目详情信息。
* 自定义生成 markdown table。
================================================
FILE: docs/快速上手.md
================================================
## 快速上手
**create-react-doc** 非常容易上手。开发者不需要额外安装或配置 webpack 或者 Babel 等工具,它们被内置隐藏在脚手架中,因此开发者可以专心于文档的书写。
如果你想在当前文件下建立站点文件 `doc`, 这里提供如下三种方式快速建站:
### npx
```bash
npx create-react-doc doc
```
### npm
```bash
npm init create-react-doc doc
```
### yarn
```bash
yarn create react-doc doc
```

> 如果想把模板内容内容拉取到当前文件夹, 则可以将如上命令的 `doc` 替换为 `.`, 比如执行 `npx create-react-doc .`。
接着执行 `cd doc && yarn && yarn start`, 可以在 `localhost: 3000` 预览站点, 如果站点文档发生改变, 站点将自动重新加载。
================================================
FILE: docs/数学公式.md
================================================
## 数学公式
支持在 `$$` 与 `$$` 之间书写 latex 数学公式即能显示在网页上。
```js
$$
z = \frac{x}{y}
$$
```
被转化为:
$$
z = \frac{x}{y}
$$
```js
$$
C_1 \quad= \quad c_2 + c_4^3
$$
```
被转化为:
$$
C_1 \quad= \quad c_2 + c_4^3
$$
```js
$$
y = \sqrt {x_1^2 + x_2^2 + x_3^2 + x_4^2}
$$
```
被转化为:
$$
y = \sqrt {x_1^2 + x_2^2 + x_3^2 + x_4^2}
$$
## 参考链接
* [latex 数学公式示例汇总](https://zhuanlan.zhihu.com/p/34799800)
================================================
FILE: docs/更新日志.md
================================================
# CHANGELOG
`create-react-doc` 严格遵循 [Semantic Versioning 2.0.0](http://semver.org/lang/zh-CN/) 语义化版本规范。
### 1.10.3
`2022-11-26`
- **Enhancement**
- 🎈 更新主题字体样式为手写体风格。
### 1.10.2
`2022-11-19`
- **Fix**
- 🐞 修复 @giscus/react 包结构变更导致站点崩溃的问题。[issue](https://github.com/giscus/giscus-component/issues/783)
- **Enhancement**
- 🎈 使用 esbuild 代替 babel-loader 进行打包构建。[issue](https://github.com/MuYunyun/create-react-doc/issues/337)
### 1.10.0
`2022-11-10`
- **Feature**
- 🚀 主题内置评论模块,支持在 [config.yml](https://muyunyun.cn/create-react-doc/85li8wdd) 配置开启评论模块。
### 1.9.2
`2022-04-09`
- **Fix**
- 🐞 修复标签页包含重复标签归档的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/286)
### 1.9.1
`2022-04-05`
- **Enhancement**
- 🎈 升级基础包版本。[issue](https://github.com/MuYunyun/create-react-doc/issues/278)
- 更新 react 版本从 v17 至 v18
- 更新 react-router-dom 版本从 v4 至 v6。
### 1.9.0
`2022-04-01`
- **Feature**
- 🚀 支持配置展示标签页以自定义聚合文章内容。[issue](https://github.com/MuYunyun/create-react-doc/issues/264)
### 1.8.2
`2022-02-02`
- **Enhancement**
- 🎈 支持在本地环境调试项目源代码。[mr](https://github.com/MuYunyun/create-react-doc/pull/249)
### 1.8.1
`2022-01-17`
- **Fix**
- 🐞 修复 crd-scripts、crd-seed 遗漏指定安装 crd-client-utils 的问题。
### 1.8.0
`2022-01-16`
- **Feature**
- 🚀 支持 SSR 首屏直出方案(基于 gp-pages 服务)以避免预渲染带来的二次刷新产生页面抖动的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/103)
- 🚀 新增 crd-client-utils 包以收录公用方法。
### 1.7.0
`2022-01-02`
- **Feature**
- 🚀 访问内容页路由时,收起菜单侧边栏以提高首屏加载体验。[issue](https://github.com/MuYunyun/create-react-doc/issues/219)
- 优势一: 用户可以聚焦访问内容区,提升阅读体验。
- 优势二: 菜单区域渲染内容复杂,隐藏其加载过程,提升首屏体验。
- 🚀 菜单侧边栏展开后,选中项自动滚动到视口内。
### 1.6.1
`2021-12-27`
- **Fix**
- 🐞 修复访问多层级子菜单目录对应的路由时,对应菜单未被展开的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/222)。
### 1.6.0
`2021-10-30`
- **Feature**
- 🚀 SEO 搜索标题优化,优化 document.title 与 meta 标签。[issue](https://github.com/MuYunyun/create-react-doc/issues/203)
### 1.5.3
`2021-10-25`
- **Fix**
- 🐞 修复在使用短链时,站点 sitemap.xml 文件生成丢失的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/206)。
### 1.5.2
`2021-10-19`
- **Fix**
- 🐞 修复点击右上角 `Edit in GitHub` 链接跳转错误的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/205)。
### 1.5.1
`2021-10-19`
- **Fix**
- 🐞 修复页面首屏菜单栏未高亮选中的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/195)。
### 1.5.0
`2021-10-13`
- **Feature**
- 🚀 支持 react-doc generate 命令,给 md 文件自动补全短链。[issue](https://github.com/MuYunyun/create-react-doc/issues/87)、[mr](https://github.com/MuYunyun/create-react-doc/pull/194)
### 1.4.0
`2021-10-08`
- **Feature**
- 🚀 支持展示短链以让文章链接持久化。[issue](https://github.com/MuYunyun/create-react-doc/issues/87)、[mr](https://github.com/MuYunyun/create-react-doc/pull/193)
- 🚀 支持在 Front-matter 区域中书写个别文件的变量。
### 1.3.5
`2021-09-24`
- **Fix**
- 🐞 修复路由过多时导致预渲染编译超时的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/182)
- 🐞 修复初始化模板 menu 参数类型错误的问题。[mr](https://github.com/MuYunyun/create-react-doc/pull/181)
### 1.3.4
`2021-06-27`
- **Fix**
- 🐞 修复 npx create-react-doc doc 初始化生成文档项目报错的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/157)。
### 1.3.3
`2021-06-24`
- **Fix**
- 🐞 修复编译预渲染时, 缺少多层级目录文件生成的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/147)。
### 1.3.0
`2021-06-09`
- **Feature**
- 🚀 create-react-doc 集成 MDX。[issue](https://github.com/MuYunyun/create-react-doc/issues/138)、[mr](https://github.com/MuYunyun/create-react-doc/pull/143)
- 🚀 支持在 markdown 文件中`书写 React 组件`。
- 🚀 支持在 markdown 文件中`书写数学公式`。
- **Enhancement**
- 🎈 支持 yarn up、yarn up:dev 在 lerna 项目中快速安装包。[mr](https://github.com/MuYunyun/create-react-doc/pull/143/files?file-filters%5B%5D=.html&file-filters%5B%5D=.js&file-filters%5B%5D=.json&file-filters%5B%5D=.less&file-filters%5B%5D=.lock&file-filters%5B%5D=.sh)
### 1.2.0
- **Fix**
- 🐞 修复路由数量过多, puppeteer 运行超时的问题。[issue](https://github.com/MuYunyun/blog/issues/115)。
### 1.1.4
- **Fix**
- 🐞 修复点击 Edit In Github 失效的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/86)。
### 1.1.0
- **Feature**
- 🚀 新增 crd-generator-sitemap 包, 用于生成 sitemap。
- 🚀 配置文件中新增 domain 字段用于生成 sitemap。
### 1.0.0
- **Feature**
- 🚀 文档支持预渲染。[pr](https://github.com/MuYunyun/create-react-doc/pull/95/files)
- 🚀 路由由 hash 路由调整为 browser 路由。
🎈 站点 SEO 优化。[doc](https://github.com/MuYunyun/blog/issues/84#issuecomment-786418891)
- 🚀 配置文件中 menu 字段类型从 string 调整为 array。
### 0.3.30
- **Feature**
- 🚀 支持显示展示 pv、uv。[pr](https://github.com/MuYunyun/create-react-doc/pull/85)
### 0.2.29
- **Fix**
- 🐞 修复首次进入菜单未高亮的问题。[issue](https://github.com/MuYunyun/create-react-doc/issues/78)。
### 0.2.28
- **Feature**
- 🚀 支持自定义主题 ci。[pr](https://github.com/MuYunyun/create-react-doc/pull/80)
### 0.2.27
- **Feature**
- 🚀 支持自定义主题。[pr](https://github.com/MuYunyun/create-react-doc/pull/77)
### 0.2.22
- **Feature**
- 🚀 升级 React 16 至 17。[pr](https://github.com/MuYunyun/create-react-doc/pull/71)
### 0.2.21
- **Feature**
- 🚀 升级 webpack4 至 webpack5。[pr](https://github.com/MuYunyun/create-react-doc/pull/65)
### 0.2.14
- **Feature**
- 🚀 支持 inject 与 injectWithPathname 注入自定义逻辑。[pr](https://github.com/MuYunyun/create-react-doc/pull/65)
### 0.2.14
- **Feature**
- 🚀 集成 Github action, 支持自动化打包、发布站点。
### 0.2.7
`2020-09-25`
- **Feature**
- 🚀 提供 @crd/leetcode-cli 提供将 leetcode 已 AC 的题目转化为 markdown table 的能力。[pr](https://github.com/MuYunyun/create-react-doc/pull/22)
### 0.2.0
`2020-08-02`
- **Feature**
- 🚀 站点支持全局搜索菜单标题与文件内容。[pr](https://github.com/MuYunyun/create-react-doc/pull/22)
### 0.1.20
`2020-07-13`
- **Fix**
- 🐞 fix [20](https://github.com/MuYunyun/create-react-doc/issues/20)。
- 🐞 fix [17](https://github.com/MuYunyun/create-react-doc/issues/17)。
- **Enhancement**
- 🎈 项目结构重构为 monorepo。[pr](https://github.com/MuYunyun/create-react-doc/pull/16)
### 0.1.0
- **Feature**
- 🚀 详细内容见 [issue](https://github.com/MuYunyun/create-react-doc/issues/2)
================================================
FILE: docs/测试/测试标签.md
================================================
该页面用来测试自定义标签。
================================================
FILE: docs/测试/测试路由.md
================================================
* 该页面用来测试未使用 abbrlink 的中文路径。
================================================
FILE: docs/站点发布.md
================================================
## 站点发布
在 [快速上手](http://muyunyun.cn/create-react-doc/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B) 一节中介绍了如何快速搭建站点, 本节将介绍如何将搭建好的站点打包、发布到 gh-pages。
### 自动打包发布到 gh-pages (推荐)
初始化的模板项目集成了 `Github action` 的 [ci 配置](https://github.com/MuYunyun/create-react-doc/blob/main/packages/templates/default/.github/workflows/gh-pages.yml), 使用方只需在 main 分支执行 `git push` 即可以完成站点的自动部署。

如果是第一次部署, 在执行以下操作后, 需要在项目的 setting 选项卡中将 Github Pages 选择为 gh-pages。(详情见 [First Deployment with GITHUB_TOKEN](https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-first-deployment-with-github_token))
```bash
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/用户或组织名/项目名.git
git push -u origin main
```
### 手动打包发布
如果需要发布到自定义服务器, 可以执行 `npm run build` 或者 `yarn build` 对将要发布的文档站点进行打包构建, 此时的文档网站已准备好进行部署。
接着将打包出的 `.crd-dist` 的文件资源同步到目标服务器即可。
================================================
FILE: docs/高阶用法.md
================================================
## 高阶用法
与 git 文件结构类似, 如果在展示的文件夹中有私有文件不方便展示在文档站点, 可以在 `.gitignore` 文件中设置过滤文件, 这样它们就不会展示在文档站点中了。eg: [.gitignore](https://github.com/MuYunyun/blog/blob/main/.gitignore)
### 插入自定义脚本
在 `config.yml` 文件中加入 `inject` 字段。
```diff
+ inject: injectLogic/index.js
```
然后在根目录新建与 `inject` 字段相对应的文件, 声明 `injectWithPathname` 函数, 写入[自定义逻辑](https://github.com/MuYunyun/create-react-doc/injectLogic/index.js)。
```js
// perf injectWithPathname logic every pathname changes
const injectWithPathname = (pathname) => {}
module.exports = { injectWithPathname }
```
================================================
FILE: gh-pages.js
================================================
const ghPages = require('gh-pages')
ghPages.publish(
'.crd-dist',
{
branch: 'gh-pages',
repo: 'https://github.com/MuYunyun/create-react-doc.git',
},
(error) => {
if (error) {
console.error(error)
} else {
console.log('docs sync success')
}
}
)
================================================
FILE: injectLogic/index.js
================================================
/* eslint-disable no-empty */
// seo: perf inject logic only once
const inject = () => {
// SEO for Google through https://search.google.com/search-console/welcome
const meta = document.createElement('meta')
meta.name = 'google-site-verification'
meta.content = '7fyp1NuvXSRLM9KpMq5_YNE_0zFZkPnuV-SbVVFgWbI'
document.head.appendChild(meta)
}
// perf injectWithPathname logic every pathname changes
const injectWithPathname = (pathname) => {}
module.exports = { inject, injectWithPathname }
================================================
FILE: lerna.json
================================================
{
"version": "1.10.3",
"command": {
"bootstrap": {
"npmClientArgs": [
"--no-package-lock"
]
},
"publish": {
"allowBranch": [
"main",
"qa/latest"
],
"message": "chore: publish"
}
},
"//": "set yarn workspaces in root",
"useWorkspaces": true,
"npmClient": "yarn"
}
================================================
FILE: package.json
================================================
{
"name": "create-react-doc",
"private": true,
"workspaces": [
"packages/*"
],
"description": "Fast static generated site. Just write markdown file.",
"homepage": "http://muyunyun.cn/create-react-doc",
"bin": {
"react-doc": "bin/react-doc.js"
},
"scripts": {
"bootstrap": "lerna bootstrap",
"bootstrap --hoist": "lerna bootstrap --hoist",
"clean": "lerna clean",
"start": "yarn bootstrap && node packages/create-react-doc/index.js start",
"build": "node packages/create-react-doc/index.js build",
"deploy": "node packages/create-react-doc/index.js deploy",
"release": "lerna publish",
"release-qa": "lerna publish --npm-tag=beta",
"cleanup": "rm -rf node_modules/gh-pages/.cache",
"deploy:site": "npm run cleanup && node gh-pages",
"up:dev": "sh utils/uppackage-dev.sh",
"up": "sh utils/uppackage.sh"
},
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc"
},
"keywords": [
"react",
"create-react-doc",
"blog",
"markdown"
],
"author": "muyunyun",
"license": "MIT",
"devDependencies": {
"eslint": "^4.19.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.7.0",
"gh-pages": "^3.1.0",
"lerna": "^3.22.1"
}
}
================================================
FILE: packages/crd-client-utils/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
================================================
FILE: packages/crd-client-utils/README.md
================================================
### crd-client-utils
[create-react-doc](https://github.com/MuYunyun/create-react-doc) 的工具包。
================================================
FILE: packages/crd-client-utils/index.js
================================================
import { useLayoutEffect, useEffect } from 'react'
const ifDev = env === 'dev'
const ifProd = env === 'prod'
const ifPrerender = window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.prerender
// Not only Prod env but also [some part of Dev env](https://github.com/MuYunyun/create-react-doc/blob/main/packages/crd-scripts/src/web/index.js#L10-L13)
// need using useLayoutEffect. If meeting this case in the future, thinking about passing extra tag from .
const useEnhancedEffect = ifProd
? useLayoutEffect
: useEffect
export {
ifDev,
ifProd,
ifPrerender,
useEnhancedEffect
}
================================================
FILE: packages/crd-client-utils/package.json
================================================
{
"name": "crd-client-utils",
"version": "1.8.2",
"description": "Utils with create react doc",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/utils"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199"
}
================================================
FILE: packages/crd-generator-sitemap/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
================================================
FILE: packages/crd-generator-sitemap/README.md
================================================
### crd-generator-sitemap
[create-react-doc](https://github.com/MuYunyun/create-react-doc) SEO 插件, 用于生成站点地图。
================================================
FILE: packages/crd-generator-sitemap/generate.js
================================================
const { getDocsConfig } = require('crd-utils')
// template for google SEO
//
// muyunyun.cn/blog/xxx
// {{ sNow | formatDate }}
// daily
// 1.0
//
const docsConfig = getDocsConfig()
const template = content =>
`
${content}
`
/**
* generate sitemap for google.
*/
const generateSiteMap = (routes) => {
const domain = docsConfig && docsConfig.domain
const repo = docsConfig && docsConfig.repo
let content = ''
for (let i = 0; i < routes.length; i++) {
if (i === routes.length - 1) {
content +=
`
${domain}/${repo}${routes[i]}
`
} else {
content +=
`
${domain}/${repo}${routes[i]}
\n`
}
}
return template(content)
}
module.exports = {
generateSiteMap,
}
================================================
FILE: packages/crd-generator-sitemap/index.js
================================================
const { generateSiteMap } = require('./generate')
module.exports = {
generateSiteMap,
}
================================================
FILE: packages/crd-generator-sitemap/package.json
================================================
{
"name": "crd-generator-sitemap",
"version": "1.3.4",
"description": "generator sitemap for create-react-doc",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/crd-generator-sitemap"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199"
}
================================================
FILE: packages/crd-scripts/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
================================================
FILE: packages/crd-scripts/README.md
================================================
_.-"\
_.-" \
,-" \
\ create \
\ \ react \
\ \ doc \
\ \ _.-;
\ \ _.-" :
\ \,-" _.-"
\( _.-"
`--"
# crd-scripts
crd-scripts 封装提供了一系列构建、发布脚本。
================================================
FILE: packages/crd-scripts/index.js
================================================
const initProject = require('./src/commands/initProject')
const initTheme = require('./src/commands/initTheme')
const initCache = require('./src/utils/initCache')
const Servers = require('./src/server')
const Build = require('./src/build')
const Deploy = require('./src/deploy')
const Generate = require('./src/generate')
const paths = require('./src/conf/path')
module.exports = {
initProject,
initTheme,
initCache,
Servers,
Build,
Deploy,
Generate,
paths,
}
================================================
FILE: packages/crd-scripts/package.json
================================================
{
"name": "crd-scripts",
"version": "1.10.3",
"description": "Scripts using with Create React Doc",
"main": "index.js",
"dependencies": {
"@mdx-js/loader": "^1.6.22",
"@nuxtjs/friendly-errors-webpack-plugin": "^2.0.2",
"babel-eslint": "^8.0.1",
"chalk": "^4.1.0",
"colors-cli": "^1.0.13",
"copy-markdown-image-webpack-plugin": "^2.0.0",
"copy-template-dir": "^1.3.0",
"crd-client-utils": "^1.8.2",
"crd-generator-sitemap": "^1.3.4",
"crd-prerender-spa-plugin": "^0.2.0",
"crd-theme": "^1.10.3",
"crd-utils": "^1.5.0",
"css-loader": "^0.28.7",
"css-minimizer-webpack-plugin": "^1.2.0",
"detect-port": "^1.2.2",
"esbuild-loader": "^2.20.0",
"eslint": "^7.11.0",
"eslint-loader": "^4.0.2",
"file-loader": "^1.1.11",
"fs-extra": "^5.0.0",
"gh-pages": "^1.2.0",
"html-webpack-plugin": "^4.5.1",
"less-loader": "^7.2.1",
"loading-cli": "^1.0.6",
"local-ip-url": "^1.0.1",
"mini-css-extract-plugin": "^0.4.0",
"open-browsers": "^1.1.1",
"path-browserify": "^1.0.1",
"postcss-flexbugs-fixes": "^3.3.1",
"postcss-loader": "^2.0.9",
"process": "^0.11.10",
"rehype-katex": "^5.0.0",
"remark-math": "^3.0.1",
"rimraf": "^2.6.2",
"string-replace-loader": "^3.0.1",
"style-loader": "^0.19.1",
"upath": "^1.0.2",
"url-replace-loader": "^1.0.0",
"webpack": "^5.12.2",
"webpack-dev-middleware": "^3.7.1",
"webpack-dev-server": "^3.8.1",
"webpack-hot-dev-clients": "^1.0.4",
"webpackbar": "^4.0.0",
"write": "^1.0.3",
"yamljs": "^0.3.0"
},
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/scripts"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199"
}
================================================
FILE: packages/crd-scripts/src/build.js
================================================
const webpack = require('webpack')
const fs = require('fs')
const { docsConfig } = require('crd-utils')
const conf = require('./conf/webpack.config.prod')
require('colors-cli/toxic')
module.exports = function serve(program) {
if (!fs.existsSync(docsConfig)) {
console.log('please check config.yml in root dir!\n')
return
}
const webpackConf = conf(program)
const compiler = webpack(webpackConf)
compiler.run((err, stats) => {
if (err) { throw err }
// 官方输出参数
// https://webpack.js.org/configuration/stats/
// https://github.com/webpack/webpack/issues/538#issuecomment-59586196
/* eslint-disable */
console.log(stats.toString({
colors: true,
children: false,
chunks: false,
modules: false,
errors: true,
errorDetails: true,
errorStack: true,
warningsFilter: (warning) => {
return true
}
}))
})
}
================================================
FILE: packages/crd-scripts/src/commands/initProject.js
================================================
const path = require('path')
const { execSync } = require('child_process')
const fs = require('fs-extra')
const { templatePath, resolveApp } = require('crd-utils')
const copyTemplate = require('copy-template-dir')
const paths = require('../conf/path')
const log = console.log; // eslint-disable-line
// eslint-disable-next-line no-unused-vars
module.exports = function (params) {
// process.argv[2] means the first argument after react-doc, eg react doc abc, process.argv[2] is abc
const outDir = path.join(paths.projectPath, process.argv[2])
const projectName = path.basename(outDir)
const crdpkg = require(paths.crdPackage); // eslint-disable-line
// replace the last vertion with x, so it'll install autoly when the last vertion changes.
const CRD_VERSION = crdpkg.version.split('.').slice(0, 2).concat('x').join('.')
// clear output dir
if (!fs.pathExistsSync(outDir)) {
fs.ensureDirSync(outDir)
}
const defaultTemplatePath = `${templatePath}/default`
// copy template, see https://github.com/MuYunyun/create-react-doc/issues/50
if (!fs.pathExistsSync(defaultTemplatePath)) {
execSync('mkdir temp && cd temp && yarn add crd-templates -D')
}
const templatePathInTemp = resolveApp('temp/node_modules/crd-templates/default')
if (fs.pathExistsSync(templatePathInTemp)) {
copyTemplate(templatePathInTemp, outDir, {
name: projectName,
crdVersion: CRD_VERSION,
}, (err, createdFiles) => {
if (err) return log(`Copy Tamplate Error: ${err} !!!`.red)
createdFiles.sort().forEach((createdFile) => {
log(` ${'create'.green} ${createdFile.replace(paths.projectPath, '')}`)
})
// to hack https://github.com/yoshuawuyts/copy-template-dir/issues/16
execSync(`cp -r ${templatePathInTemp}/.github ${outDir}`)
execSync('rm -rf temp')
log('\n initialization finished!\n'.green)
const cmdstr = `cd ${projectName} && yarn && yarn start`.cyan
log(` Run the ${cmdstr} to start the website.\n\n`)
})
} else {
log(` ❎ crd-templates install fail.\n\n`)
}
}
================================================
FILE: packages/crd-scripts/src/commands/initTheme.js
================================================
/**
* This file is to init theme quickly.
*/
const path = require('path')
const { execSync } = require('child_process')
const fs = require('fs-extra')
const { templatePath } = require('crd-utils')
const copyTemplate = require('copy-template-dir')
const paths = require('../conf/path')
const log = console.log; // eslint-disable-line
// eslint-disable-next-line no-unused-vars
module.exports = function (themeName) {
const outDir = path.join(paths.projectPath, themeName)
const crdpkg = require(paths.crdPackage); // eslint-disable-line
// replace the last vertion with x, so it'll install autoly when the last vertion changes.
const CRD_VERSION = crdpkg.version.split('.').slice(0, 2).concat('x').join('.')
// clear output dir
if (!fs.pathExistsSync(outDir)) {
fs.ensureDirSync(outDir)
}
const defaultTemplatePath = `${templatePath}/theme/default`
if (!fs.pathExistsSync(defaultTemplatePath)) {
execSync('mkdir temp && cd temp && yarn add crd-templates -D')
}
copyTemplate(defaultTemplatePath, outDir, {
name: themeName,
crdVersion: CRD_VERSION,
}, (err, createdFiles) => {
if (err) return log(`Copy Tamplate Error: ${err} !!!`.red)
createdFiles.sort().forEach((createdFile) => {
log(` ${'create'.green} ${createdFile.replace(paths.projectPath, '')}`)
})
execSync('rm -rf temp')
log('\n initialization finished!\n'.green)
const cmdstr = `cd ${themeName} && yarn && yarn start`.cyan
log(` Run the ${cmdstr} to start the website.\n\n`)
})
}
================================================
FILE: packages/crd-scripts/src/conf/getDirTree.js
================================================
const { directoryTree } = require('./node-directory-tree')
const getDirTree = (cmd) => {
const dir = cmd.markdownPaths
const dirs = Array.isArray(dir) ? dir : [dir]
const otherProps = {
mdconf: true,
extensions: /\.md/,
prerender: true,
}
const mapTagsWithArticle = []
const dirTree = dirs.map(path => directoryTree({
path,
options: otherProps,
mapTagsWithArticle
}))
return {
dirTree,
// map tags with path. [{ tagName: 'custom Tag 1', mapArticle: [{ path, name }]}]
mapTagsWithArticle
}
}
module.exports = {
getDirTree,
}
================================================
FILE: packages/crd-scripts/src/conf/getPrerenderRoutes.js
================================================
// eg: ['docs/quick_start.md', 'a']
// output: ['/quick_start', '/a/b', '/a/b/c']
const getPrerenderRoutes = (dirTree) => {
const dpCloneDirTree = JSON.parse(JSON.stringify(dirTree))
const result = recursiveDirTree(dpCloneDirTree)
result.push('/404')
result.push('/tags')
return result
}
function recursiveDirTree(data) {
return recursive(
data,
'',
[]
)
}
function recursive(
data,
routePath,
prerenderRouteArr,
) {
data.forEach((item) => {
const { mdconf } = item || {}
const { abbrlink, tags } = mdconf || {}
const composeRouteName = `${routePath}/${item.name}`.replace(/.md$/, '')
if (item.type === 'directory') {
if (item.children && item.children.length > 0) {
item.children = recursive(
item.children,
composeRouteName,
prerenderRouteArr,
)
} else {
item.children = []
}
} else if (item.type === 'file') {
const prerenderRouteName = abbrlink
? `/${abbrlink}`
: composeRouteName
prerenderRouteArr.push(prerenderRouteName)
}
})
return prerenderRouteArr
}
module.exports = {
getPrerenderRoutes
}
================================================
FILE: packages/crd-scripts/src/conf/node-directory-tree.js
================================================
/* eslint-disable no-undef */
const fs = require('fs')
const PATH = require('path')
const YAML = require('yamljs')
const { execSync } = require('child_process')
const {
replaceForFrontMatter,
generateRandomId,
} = require('crd-utils')
const { getDigitFromDir, timeFormat } = require('../utils')
const constants = {
DIRECTORY: 'directory',
FILE: 'file',
}
function safeReadDirSync(path) {
let dirData = {}
try {
/**
* to make sure it's ordered like such rules, link issue: https://github.com/nodejs/node/issues/3232
* 1.x 1.x
* 2.x instead of 10.x
* 10.x 2.x
*/
dirData = fs.readdirSync(path).sort((dir1, dir2) => {
const dir1Digit = getDigitFromDir(dir1)
const dir2Digit = getDigitFromDir(dir2)
if (dir1Digit && dir2Digit) {
return dir1Digit - dir2Digit
}
return 0
})
} catch (ex) {
if (ex.code === 'EACCES')
// User does not have permissions, ignore directory
// eslint-disable-next-line brace-style
{ return null }
throw ex
}
return dirData
}
// collect unique tags from all articles.
const tagsArr = []
/** build directory Tree, fork from https://github.com/mihneadb/node-directory-tree
* path: path for file
* options: {
* exclude: RegExp|RegExp[] - A RegExp or an array of RegExp to test for exclusion of directories.
* extensions : RegExp - A RegExp to test for exclusion of files with the matching extension.
* mdconf: Boolean.
* prerender: Boolean. Used for prerender.
* generate: Boolean. Used for generating info in front-matter.
* }
* mapTagsWithArticle: [{
* tagName: 'customTag1', // tag type name
* mapArticle: [{
* path, // click tag to jump route such as /tags/customTag1
* title // the name of article
* }]
* }]
*/
function directoryTree({
path,
options,
routePath = '',
mapTagsWithArticle
}) {
const name = PATH.basename(path, '.md')
const item = { name }
const routePropsCurrent = `${routePath}/${name}`
if (!options.prerender) {
item.path = path
}
let stats
try {
stats = fs.statSync(path)
} catch (e) {
return null
}
// Skip if it matches the exclude regex
if (options && options.exclude && options.exclude.test(path)) return null
if (stats.isFile()) {
const ext = PATH.extname(path).toLowerCase()
// Skip if it does not match the extension regex
if (options && options.extensions && !options.extensions.test(ext)) { return null }
if (options && options.mdconf) {
item.type = constants.FILE
const contentStr = fs.readFileSync(path).toString()
if (!contentStr) return
const contentMatch = contentStr.match(/^/)
/** generate abbrlink in FrontMatter */
if (options.generate) {
const randomId = generateRandomId(8)
if (!contentMatch) {
replaceForFrontMatter({
path,
target: `\n`
})
}
if (contentMatch && contentMatch[1].indexOf('abbrlink') === -1) {
replaceForFrontMatter({
path,
source: contentMatch[1],
target: `\nabbrlink: ${randomId}${contentMatch[1]}`
})
console.log('✅ replaceForFrontMatter success')
}
}
const yamlParse = contentMatch ? YAML.parse(contentMatch[1]) : {}
const { tags: articleTags, abbrlink } = yamlParse
if (Array.isArray(articleTags) && Array.isArray(mapTagsWithArticle)) {
const cpArticleTags = Array.from(new Set(articleTags))
for (let i = 0; i < cpArticleTags.length; i++) {
const articleTag = cpArticleTags[i]
const articleTagIndex = tagsArr.indexOf(articleTag)
if (articleTagIndex > -1) {
mapTagsWithArticle[articleTagIndex]['mapArticle'].push({
path: abbrlink ? `/${abbrlink}` : routePropsCurrent,
title: name
})
} else {
tagsArr.push(cpArticleTags[i])
mapTagsWithArticle.push({
tagName: cpArticleTags[i],
mapArticle: [{
path: abbrlink ? `/${abbrlink}` : routePropsCurrent,
title: name
}]
})
}
}
}
item.mdconf = yamlParse
try {
// see https://stackoverflow.com/questions/2390199/finding-the-date-time-a-file-was-first-added-to-a-git-repository/2390382#2390382
const result = execSync(`git log --format=%aD ${path} | tail -1`)
item.birthtime =
Buffer.isBuffer(result) && timeFormat(new Date(result))
} catch (error) {
console.log(`❎ error: ${error.message}`)
}
try {
// see https://stackoverflow.com/questions/22497597/get-the-last-modification-data-of-a-file-in-git-repo
const result = execSync(`git log -1 --pretty="format:%ci" ${path}`)
item.mtime = Buffer.isBuffer(result) && timeFormat(new Date(result))
} catch (error) {
console.log(`❎ error: ${error.message}`)
}
item.size = stats.size // File size in bytes
item.extension = ext
if (!options.prerender) {
item.relative = item.path.replace(process.cwd(), '')
item.isEmpty = contentMatch
? !String.prototype.trim.call(contentStr.replace(contentMatch[0], ''))
: true
const uglifyContent = contentStr.replace(/\s/g, '')
item.content = uglifyContent
}
}
} else if (stats.isDirectory()) {
const dirData = safeReadDirSync(path)
if (dirData === null) return null
item.children = dirData
.map(child =>
directoryTree({
path: PATH.join(path, child),
options,
routePath: routePropsCurrent,
mapTagsWithArticle
}),
)
.filter(e => !!e)
item.type = constants.DIRECTORY
if (!options.prerender) {
item.size = item.children.reduce((prev, cur) => prev + cur.size, 0)
}
} else {
return null // Or set item.size = 0 for devices, FIFO and sockets ?
}
return item
}
module.exports = {
directoryTree
}
================================================
FILE: packages/crd-scripts/src/conf/path.js
================================================
const path = require('path')
const fs = require('fs')
const { execSync } = require('child_process')
const { resolveApp, docsConfig, getDocsConfig } = require('crd-utils')
const chalk = require('chalk')
// handle the problem of symbol in any platform
const appDirectory = fs.realpathSync(process.cwd())
const modPath = resolveApp('node_modules')
// get config crd from package.json
function getCrdConf() {
const packagePath = resolveApp('./package.json')
let conf = {}
if (fs.existsSync(packagePath)) {
const confPkg = require(packagePath); // eslint-disable-line
conf = confPkg.crd
}
return conf
}
function getConfigFilePath(fileName, type) {
const conf = getCrdConf()
// read config
if (conf && conf[type]) {
// load theme dir
if (type === 'theme') {
if (!conf[type]) conf[type] = fileName
const _path = path.resolve(appDirectory, 'theme', conf[type])
const _NodeModulesPath = path.resolve(
appDirectory,
'node_modules',
conf[type],
)
if (fs.existsSync(_path)) {
return fs.realpathSync(_path)
} else if (fs.existsSync(_NodeModulesPath)) {
return fs.realpathSync(_NodeModulesPath)
}
return false
}
if (/^(favicon|logo)$/.test(type)) {
return path.resolve(appDirectory, conf[type])
}
}
const _filepath = path.resolve(appDirectory, fileName)
if (fs.existsSync(_filepath)) {
// favicon|logo in default root dir.
return _filepath
}
return false
}
// Get favicon path
const faviconPath = () => {
const _path = getConfigFilePath('./favicon.ico', 'favicon')
if (_path) return _path
// the path'll be writen dynamiclly in the future
return resolveApp('node_modules/crd-theme/favicon.ico')
}
// Get logo path
const logoPath = () => {
const _path = getConfigFilePath('./logo.svg', 'logo')
if (_path) return _path
return false
}
let theme = ''
// theme in develop mode.
let devTheme = false
const getTheme = () => {
if (docsConfig) {
const docsConfigObj = getDocsConfig()
if (!docsConfigObj) return
if (docsConfigObj.devTheme) {
devTheme = docsConfigObj.devTheme
theme = docsConfigObj.devTheme
} else {
theme = docsConfigObj.theme
// install custom theme
if (!fs.existsSync(resolveApp(`node_modules/${theme}`))) {
// todo: chalkblue(xxx) not show in the terminal
chalk.blue(`Install theme ${theme}`)
// -W means ignore-workspace-root-check
execSync(`yarn add ${theme} -D -W`)
chalk.blue(`Install theme ${theme} done`)
} else {
chalk.blue(`Upgrade theme ${theme}`)
// -W means ignore-workspace-root-check
execSync(`yarn upgrade ${theme}`)
chalk.blue(`Upgrade theme ${theme} done`)
}
}
}
}
getTheme()
// get exclude folders
function getExcludeFoldersRegExp() {
if (!fs.existsSync(modPath)) return []
let regexp = fs.readdirSync(modPath)
/** whitelist to include */
const whiteListRegExp = new RegExp(`create-react-doc(.*)|crd-scripts|crd-theme|${theme}`)
regexp = regexp.filter(
item => !whiteListRegExp.test(item),
// item => !/create-react-doc(.*)|crd-scripts|crd-theme/.test(item),
)
regexp = regexp.map((item) => {
let rgxPath = `node_modules${path.sep}${item}`
if (path.sep === '\\') {
// to watch: is '\\' needful?
rgxPath = `node_modules\\${path.sep}${item}`
}
return new RegExp(rgxPath)
})
return regexp
}
// crd tool dir
const toolDirectory = fs.realpathSync(__dirname)
const resolveTool = relativePath => path.resolve(toolDirectory, relativePath)
module.exports = {
// markdown dir
crdConf: getCrdConf(),
defaultTheme: devTheme
? resolveApp(`${devTheme}`)
: resolveApp(`node_modules/${theme}`),
defaultNodeModules: modPath,
projectPath: appDirectory,
publicPath: '',
logoPath: logoPath(),
// crd tool dir
getExcludeFoldersRegExp: getExcludeFoldersRegExp(),
crdPackage: resolveTool('../../package.json'),
defaultFaviconPath: faviconPath(),
appIndexJs: resolveTool('../web/index.js'),
appDir: resolveTool('../web'),
}
================================================
FILE: packages/crd-scripts/src/conf/rawTreeReplaceLoader.js
================================================
// A variation of https://github.com/react-doc/node-directory-tree-md/blob/master/lib/directory-tree-md.js
const { directoryTree } = require('./node-directory-tree')
const PATH = require('path')
const { ifInGitIgnore } = require('../utils/index')
function getAllWatchPath(arr, pathArr = []) {
arr.forEach((item) => {
const mdfilePathInProject = item.path.replace(
process.cwd() + PATH.sep,
'',
)
if (!ifInGitIgnore(mdfilePathInProject) && item.type === 'file') {
pathArr.push(item.path)
}
if (item.children && item.children.length > 0) {
pathArr.concat(getAllWatchPath(item.children, pathArr))
}
})
return pathArr
}
function replacePath(dirs, path) {
for (let i = 0; i < dirs.length; i += 1) {
const element = PATH.dirname(dirs[i])
const reg = new RegExp(`^${element}`, 'gi')
if (reg.test(path)) {
path = path.replace(reg, '')
break
}
}
return path
}
function getRelativePath(arr, relativePath, dirs) {
const pathArr = []
arr.forEach((item) => {
if (relativePath && item.path) {
item.path = replacePath(dirs, item.path)
}
if (item.children && item.children.length > 0) {
item.children = getRelativePath(item.children, relativePath, dirs)
}
const notInGitIgnore = !ifInGitIgnore(item.path.replace(PATH.sep, ''))
if (notInGitIgnore) {
pathArr.push(item)
}
})
return pathArr
}
module.exports = function (source) {
// get option config from webpack loader, here is https://github.com/MuYunyun/create-react-doc/blob/main/packages/scripts/src/conf/webpack.config.prod.js#L61-L70
const options = this.getOptions() || {}
const { include, directoryTrees } = options
const { dir, relativePath, ...otherProps } = directoryTrees
let content = typeof source === 'string' ? JSON.parse(source) : source
// It's said loader resuls are flagged as cacheable. See https://webpack.js.org/api/loaders/#thiscacheable.
// if (this.cacheable) this.cacheable()
if (directoryTrees && (!include || include.test(this.resourcePath))) {
const dirs = Array.isArray(dir) ? dir : [dir]
const dirTree = dirs.map(path => directoryTree({
path,
options: otherProps,
}))
const filemd = getAllWatchPath(dirTree)
filemd.forEach((fileItem) => {
this.addDependency(fileItem)
})
// replace full file path with relative path
content = getRelativePath(
dirTree,
relativePath,
dirs,
)
}
content = JSON.stringify(content)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029')
return `module.exports = ${content}`
}
================================================
FILE: packages/crd-scripts/src/conf/webpack.config.dev.js
================================================
const autoprefixer = require('autoprefixer')
const webpack = require('webpack')
const path = require('path')
const upath = require('upath')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { defaultHTMLPath } = require('crd-utils')
const FriendlyErrorsWebpackPlugin = require('@nuxtjs/friendly-errors-webpack-plugin')
const { getDocsConfig } = require('crd-utils')
const { getDirTree } = require('./getDirTree')
const config = require('./webpack.config')
const paths = require('./path')
module.exports = function (cmd) {
const docsConfig = getDocsConfig()
const { mapTagsWithArticle } = getDirTree(cmd)
config.mode = 'development'
config.devtool = 'eval-source-map'
config.entry = [
require.resolve('react-hot-loader/patch'),
require.resolve('webpack-hot-dev-clients/webpackHotDevClient'),
paths.appIndexJs,
]
config.output.publicPath = '/'
config.module.rules = config.module.rules.map((item) => {
if (item.oneOf) {
const loaders = []
loaders.push({
// Process JS with Babel.
test: /\.(js|jsx)$/,
exclude: paths.getExcludeFoldersRegExp.concat(/\.(cache)/),
use: [
{
loader: require.resolve('string-replace-loader'),
options: {
multiple: [
{ search: '__project_root__', replace: upath.normalizeSafe(paths.projectPath), flags: 'ig' },
{ search: '__project_theme__', replace: upath.normalizeSafe(paths.defaultTheme), flags: 'ig' },
],
},
},
{
loader: 'esbuild-loader',
options: {
loader: 'jsx',
target: 'es2015',
// This will make esbuild automatically generate import statements,
// making the ProviderPlugin unnecesary if used only for "react".
// Note that this option makes sense only when used in conjuction
// with React >16.40.0 || >17
// https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
jsx: 'automatic',
}
},
],
})
// https://ilikekillnerds.com/2018/03/disable-webpack-4-native-json-loader/
loaders.push({
test: /crd\.json$/,
// 禁用 Webpack 4 本身的 JSON 加载程序
type: 'javascript/auto',
use: [
{
loader: `${path.join(__dirname, './rawTreeReplaceLoader.js')}`,
options: {
include: /crd\.json$/,
directoryTrees: {
dir: cmd.markdownPaths,
mdconf: true,
extensions: /\.md/,
relativePath: true,
},
},
},
],
})
loaders.push({
test: /\.(css|less)$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
modules: true,
localIdentName: '[local]--[hash:base64:5]',
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require("postcss-flexbugs-fixes"), // eslint-disable-line
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
require.resolve('less-loader'),
],
})
item.oneOf = loaders.concat(item.oneOf)
}
return item
})
config.optimization = {
// 将模块名称添加到工厂功能,以便它们显示在浏览器分析器中。
// 当接收到热更新信号时,在浏览器 console 控制台打印更多可读性高的模块名称等信息
moduleIds: 'named',
}
config.plugins = config.plugins.concat([
new webpack.DefinePlugin({
env: JSON.stringify('dev'),
mapTagsWithArticle: JSON.stringify(mapTagsWithArticle)
}),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
inject: true,
favicon: paths.defaultFaviconPath,
template: defaultHTMLPath,
title: docsConfig && docsConfig.title ? docsConfig.title : 'Create React Doc',
}),
new FriendlyErrorsWebpackPlugin({
clearConsole: true,
}),
])
return config
}
================================================
FILE: packages/crd-scripts/src/conf/webpack.config.js
================================================
/* eslint-disable global-require */
/* eslint-disable import/no-dynamic-require */
const webpack = require('webpack')
const webpackbar = require('webpackbar')
const fs = require('fs')
const { resolveApp, docsConfig, docsBuildDist } = require('crd-utils')
const { getDocsConfig } = require('crd-utils')
// const { getSearchContent } = require('../utils');
const remarkMath = require('remark-math')
const rehypeKatex = require('rehype-katex')
const paths = require('./path')
const pkg = require('../../package.json')
const define = {
FOOTER: null,
DOCSCONFIG: null,
INJECT: null,
}
if (paths.crdConf && paths.crdConf.footer && typeof paths.crdConf.footer === 'string') {
define.FOOTER = JSON.stringify(paths.crdConf.footer)
}
/* custom define docs config */
if (docsConfig) {
// const searchContent = getSearchContent();
const docsConfigObj = getDocsConfig()
define.DOCSCONFIG = JSON.stringify(docsConfigObj)
// todo: searchContent affects the performance, so take annotation here templately.
// define.SEARCHCONTENT = searchContent && searchContent.toString();
// if there is inject logic in docsConfigObj
if (docsConfigObj && docsConfigObj.inject && fs.existsSync(resolveApp(docsConfigObj.inject))) {
define.INJECT = require(resolveApp(docsConfigObj.inject))
}
}
module.exports = {
entry: {},
output: {
path: docsBuildDist,
publicPath: paths.publicPath,
filename: 'js/[name].[hash:8].js',
chunkFilename: 'js/[name].[hash:8].js',
},
module: {
rules: [
{
// “oneOf”将遍历所有以下加载程序,直到一个符合要求。
// 当没有加载器匹配时,它将返回到加载程序列表末尾的“file”加载器。
oneOf: [
{
test: /\.(svg|png|bmp|jpg|jpeg|gif)$/,
loader: require.resolve('url-replace-loader'),
options: {
limit: 10000,
name: 'img/[name].[hash:8].[ext]',
replace: [
{
test: /crd\.logo\.svg$/,
path: paths.logoPath,
},
],
},
},
{
test: /\.md$/,
use: [
{
loader: 'esbuild-loader',
options: {
loader: 'jsx',
target: 'es2015',
// This will make esbuild automatically generate import statements,
// making the ProviderPlugin unnecesary if used only for "react".
// Note that this option makes sense only when used in conjuction
// with React >16.40.0 || >17
// https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
jsx: 'automatic',
}
},
{
loader: require.resolve('@mdx-js/loader'),
options: {
remarkPlugins: [
[
remarkMath,
{
/* options */
},
],
],
rehypePlugins: [
[
rehypeKatex,
{
/* options */
},
],
],
},
},
],
exclude: /(node_modules)/,
},
// “file-loader”确保这些资源由WebpackDevServer服务。
// 当您导入资源时,您将获得(虚拟)文件名。
// 在生产中,它们将被复制到`build`文件夹。
// 此加载程序不使用“test”,因此它将捕获所有模块
{
// 排除`js`文件以保持“css”加载器工作,因为它注入它的运行时,否则将通过“文件”加载器处理。
// 还可以排除“html”和“json”扩展名,以便它们被webpacks内部加载器处理。
exclude: [/\.js$/, /\.html$/, /\.json$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/[name].[hash:8].[ext]',
},
},
],
},
],
},
plugins: [
// eslint-disable-next-line new-cap
new webpackbar({ name: pkg.name }),
new webpack.DefinePlugin({
VERSION: JSON.stringify(pkg.version),
...define,
}),
// fix "process is not defined" error:
new webpack.ProvidePlugin({
process: 'process/browser',
}),
],
resolve: {
fallback: {
path: require.resolve('path-browserify'),
},
},
}
================================================
FILE: packages/crd-scripts/src/conf/webpack.config.prod.js
================================================
const autoprefixer = require('autoprefixer')
const path = require('path')
const upath = require('upath')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyMarkdownImageWebpackPlugin = require('copy-markdown-image-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PrerenderSPAPlugin = require('crd-prerender-spa-plugin')
const { generateSiteMap } = require('crd-generator-sitemap')
const fs = require('fs-extra')
const { defaultHTMLPath, docsBuildDist } = require('crd-utils')
const { getDocsConfig } = require('crd-utils')
const config = require('./webpack.config')
const paths = require('./path')
const { getPrerenderRoutes } = require('./getPrerenderRoutes')
const { getDirTree } = require('./getDirTree')
const Renderer = PrerenderSPAPlugin.PuppeteerRenderer
module.exports = function (cmd) {
const docsConfig = getDocsConfig()
const { dirTree, mapTagsWithArticle } = getDirTree(cmd)
const routes = getPrerenderRoutes(dirTree)
config.mode = 'production'
config.entry = [paths.appIndexJs]
// config.output.filename = 'js/[hash:8].js'
config.output.chunkFilename = 'js/[name].[hash:8].js'
config.output.publicPath = docsConfig.repo ? `/${docsConfig.repo}/` : '/'
config.output.path = docsConfig.repo ? `${docsBuildDist}/${docsConfig.repo}` : docsBuildDist
config.module.rules = config.module.rules.map((item) => {
if (item.oneOf) {
const loaders = []
loaders.push({
// Process JS with Babel.
test: /\.(js|jsx)$/,
exclude: paths.getExcludeFoldersRegExp.concat(/\.(cache)/),
use: [
{
loader: require.resolve('string-replace-loader'),
options: {
multiple: [
{ search: '__project_root__', replace: upath.normalizeSafe(paths.projectPath), flags: 'ig' },
{ search: '__project_theme__', replace: upath.normalizeSafe(paths.defaultTheme), flags: 'ig' },
],
},
},
{
loader: 'esbuild-loader',
options: {
loader: 'jsx',
target: 'es2015',
// This will make esbuild automatically generate import statements,
// making the ProviderPlugin unnecesary if used only for "react".
// Note that this option makes sense only when used in conjuction
// with React >16.40.0 || >17
// https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
jsx: 'automatic',
}
},
],
})
// https://ilikekillnerds.com/2018/03/disable-webpack-4-native-json-loader/
loaders.push({
test: /crd\.json$/,
// 禁用 Webpack 4 本身的 JSON 加载程序
type: 'javascript/auto',
use: [
{
loader: `${path.join(__dirname, './rawTreeReplaceLoader.js')}`,
options: {
include: /crd\.json$/, // 检查包含的文件名字
directoryTrees: {
// 指定目录生成目录树,json
dir: cmd.markdownPaths,
mdconf: true,
extensions: /\.md/,
relativePath: true,
},
},
},
],
})
loaders.push({
test: /\.(css|less)$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: require.resolve('css-loader'),
options: {
modules: true,
localIdentName: '[local]-[hash:base64:5]',
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'), // eslint-disable-line
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
require.resolve('less-loader'),
],
})
item.oneOf = loaders.concat(item.oneOf)
}
return item
})
config.optimization = {
// minimize: true,
// minimizer: [
// // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line
// // `...`,
// new CssMinimizerPlugin(),
// ],
}
config.plugins = config.plugins.concat([
new webpack.DefinePlugin({
env: JSON.stringify('prod'),
mapTagsWithArticle: JSON.stringify(mapTagsWithArticle)
}),
new HtmlWebpackPlugin({
inject: true,
favicon: paths.defaultFaviconPath,
template: defaultHTMLPath,
title:
docsConfig && docsConfig.title ? docsConfig.title : 'Create React Doc',
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true,
html5: true,
minifyCSS: true,
removeComments: true,
removeEmptyAttributes: true,
},
}),
new CopyMarkdownImageWebpackPlugin({
dir: cmd.markdownPaths,
toDir: config.output.path,
}),
// new webpack.optimize.DedupePlugin(),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: 'css/[contenthash].css',
chunkFilename: 'css/[id].css',
}),
new PrerenderSPAPlugin({
// Required - The path to the webpack-outputted app to prerender.
staticDir: docsBuildDist,
outputDir: docsConfig.repo
? `${docsBuildDist}/${docsConfig.repo}`
: docsBuildDist,
indexPath: docsConfig.repo
? `${docsBuildDist}/${docsConfig.repo}/index.html`
: `${docsBuildDist}/index.html`,
// Required - Routes to render.
routes,
successCb: async () => {
if (docsConfig.repo) {
// not use fs.move here or it'll throw error in github action
await fs.copy(`${docsBuildDist}/${docsConfig.repo}`, docsBuildDist)
await fs.remove(`${docsBuildDist}/${docsConfig.repo}`)
const defaultPath = (dirTree.find(data => data.name === 'README.md')
&& dirTree.find(data => data.name === 'README.md').mdconf
&& dirTree.find(data => data.name === 'README.md').mdconf.abbrlink) || 'README'
// move README as root index.html
await fs.copy(`${docsBuildDist}/${defaultPath}/index.html`, `${docsBuildDist}/index.html`)
console.log('✅ generate prerender file success!')
if (docsConfig.seo) {
if (docsConfig.seo.google) {
fs.writeFileSync(`${docsBuildDist}/sitemap.xml`, generateSiteMap(routes))
}
}
console.log('✅ generate sitemap file success!')
}
},
// The actual renderer to use. (Feel free to write your own)
// Available renderers: https://github.com/Tribex/prerenderer/tree/master/renderers
renderer: new Renderer({
// Optional - The name of the property to add to the window object with the contents of `inject`.
injectProperty: '__PRERENDER_INJECTED',
// Optional - Any values you'd like your app to have access to via `window.injectProperty`.
inject: {
prerender: true,
},
// Optional - defaults to 0, no limit.
// Routes are rendered asynchronously.
// Use this to limit the number of routes rendered in parallel.
maxConcurrentRoutes: 4,
// https://pptr.dev/#?product=Puppeteer&version=v5.5.0&show=api-pagegotourl-options
navigationOptions: {
timeout: 0
}
}),
}),
])
return config
}
================================================
FILE: packages/crd-scripts/src/conf/webpack.config.server.js
================================================
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'
module.exports = (cmd, webpackConf) => {
return {
// 启用生成文件的gzip压缩。
compress: true,
// 沉默WebpackDevServer自己的日志,因为它们通常没有用处。
// 这个设置仍然会显示编译警告和错误。
clientLogLevel: 'none',
// contentBase: conf.output.appPublic,
publicPath: webpackConf.output.publicPath,
hot: true,
historyApiFallback: {
// 带点的路径仍应使用历史回退。
// See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true,
},
// historyApiFallback: true,
// WebpackDevServer默认是嘈杂的,所以我们发出自定义消息
// 通过上面的`compiler.plugin`调用来监听编译器事件。
quiet: true,
// 如果HTTPS环境变量设置为“true”,则启用HTTPS
https: protocol === 'https',
// 告诉服务器从哪里提供内容。提供静态文件,这只是必要的。
contentBase: cmd.markdownPaths,
// 通知服务器观察由devServer.contentBase选项提供的文件。
// it'll reload page when file change.
watchContentBase: true,
// avoid cpu overload in some case
watchOptions: {
ignored: /node_modules/,
},
}
}
================================================
FILE: packages/crd-scripts/src/deploy.js
================================================
const ghpages = require('gh-pages')
const loading = require('loading-cli')
const log = console.log; // eslint-disable-line
module.exports = function server(cmd, docsConfig) {
if (!docsConfig) {
console.log('please check config.yml in root dir!\n')
return
}
if (!docsConfig.user || !docsConfig.repo) {
console.log('please check user and repo in config.yml!\n')
return
}
const { user, repo, publish } = docsConfig
log(' Start deploy to your git repo'.green)
const load = loading({
text: 'Please wait ...'.blue,
color: 'blue',
interval: 100,
stream: process.stdout,
}).start()
ghpages.publish(
cmd.output,
{
branch: cmd.branch,
repo: publish || `https://github.com/${user}/${repo}.git`,
message: `Update website, ${new Date()}!`,
},
(err) => {
load.stop()
if (err) {
return log(err)
}
log(`\n Push to ${cmd.branch} success!\n`.green.bold)
},
)
}
================================================
FILE: packages/crd-scripts/src/generate.js
================================================
const fs = require('fs')
const { docsConfig } = require('crd-utils')
const { directoryTree } = require('./conf/node-directory-tree')
module.exports = function generate(program) {
if (!fs.existsSync(docsConfig)) {
console.log('❎ please check config.yml in root dir!\n')
return
}
const dir = program.markdownPaths
const dirs = Array.isArray(dir) ? dir : [dir]
const otherProps = {
mdconf: true,
extensions: /\.md/,
generate: true
}
dirs.map(path => directoryTree({
path,
options: otherProps,
}))
console.log('✅ generate success!')
}
================================================
FILE: packages/crd-scripts/src/server.js
================================================
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const openBrowsers = require('open-browsers')
const detect = require('detect-port')
const fs = require('fs')
const { docsConfig } = require('crd-utils')
const prepareUrls = require('local-ip-url/prepareUrls')
const conf = require('./conf/webpack.config.dev')
const createDevServerConfig = require('./conf/webpack.config.server')
require('colors-cli/toxic')
function clearConsole() {
// process.stdout.write(process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H');
}
module.exports = function server(cmd) {
if (!fs.existsSync(docsConfig)) {
console.log('please check config.yml in root dir!\n')
return
}
const HOST = cmd.host
let DEFAULT_PORT = cmd.port
const webpackConf = conf(cmd)
const compiler = webpack(webpackConf)
detect(DEFAULT_PORT).then((_port) => {
if (DEFAULT_PORT !== _port) DEFAULT_PORT = _port
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'
const urls = prepareUrls({ protocol, host: HOST, port: DEFAULT_PORT })
// https://webpack.js.org/api/compiler-hooks/#aftercompile
// print log after being compiled
compiler.hooks.done.tap('done', () => {
/* eslint-disable */
console.log(`Dev Server Listening at Local: ${urls.localUrl.green}`);
console.log(` On Your Network: ${urls.lanUrl.green}`);
console.log(`\nTo create a production build, use ${'npm run build'.blue_bt}.`);
/* eslint-enable */
})
new WebpackDevServer(compiler, createDevServerConfig(cmd, webpackConf)).listen(DEFAULT_PORT, HOST, (err) => {
if (err) {
return console.log(err); // eslint-disable-line
}
clearConsole()
// open browser
openBrowsers(urls.localUrl)
})
}).catch((err) => {
console.log(err); // eslint-disable-line
})
}
================================================
FILE: packages/crd-scripts/src/utils/index.js
================================================
const fs = require('fs')
const { docsGitIgnore, searchFilePath } = require('crd-utils')
/**
* judege cur file if in git ignore.
*/
exports.ifInGitIgnore = (mdfilePathInProject) => {
let gitIgnoreContentArr = []
if (fs.existsSync(docsGitIgnore)) {
const gitIgnoreContent = fs.readFileSync(docsGitIgnore)
gitIgnoreContentArr = gitIgnoreContent.toString().split('\n')
}
return gitIgnoreContentArr.indexOf(mdfilePathInProject) > -1
}
/**
* to get dight from cur dir.
* If there are order && unorder file in one same folder, the unorder file'll be in front of order file.
* eg:
* '1.xx' => 1
* 'xx' => 0
*/
exports.getDigitFromDir = (dir) => {
const matchedResult = dir.match(/^((\d)*)\.(\s|\S)*$/)
if (matchedResult && matchedResult[1]) {
return parseInt(matchedResult[1], 10)
}
return 0
}
function paddingTwoDigits(digit) {
return digit < 10 ? `0${digit}` : digit
}
/**
* format time
*/
exports.timeFormat = (date) => {
if (isNaN(date.getFullYear()) || isNaN(date.getMonth()) || isNaN(date.getDate())) return null
return `${date.getFullYear()}-${paddingTwoDigits(
date.getMonth() + 1,
)}-${paddingTwoDigits(date.getDate())}`
}
exports.getSearchContent = () => {
if (!fs.existsSync(searchFilePath)) {
console.log('there is no find .cache/search.js in root dir!\n')
return null
}
return fs.readFileSync(searchFilePath)
}
================================================
FILE: packages/crd-scripts/src/utils/initCache.js
================================================
const write = require('write')
const path = require('path')
const { cacheDirPath, getDocsConfig } = require('crd-utils')
const { directoryTree } = require('../conf/node-directory-tree')
module.exports = function (program, cb) {
const treeData = program.markdownPaths.map((markdownPath) => {
return directoryTree({
path: markdownPath,
options: {
mdconf: true, // Markdown config for exsiting file.
extensions: /\.md/,
},
})
})
// to collect search data
const searchData = []
const docsConfig = getDocsConfig()
const useSearchPlugin = docsConfig.search && docsConfig.host
function dfsMap(data) {
// eslint-disable-next-line no-plusplus
for (let i = 0; i < data.length; i++) {
if (data[i].children) {
dfsMap(data[i].children)
} else {
const searchMapKeys = docsConfig.search_map ? Object.keys(docsConfig.search_map) : []
// eslint-disable-next-line no-plusplus
for (let x = 0; x < searchMapKeys.length; x++) {
if (data[i].relative) {
const searchMapIndex = data[i].relative.indexOf(searchMapKeys[x])
if (searchMapIndex !== -1 && typeof searchMapIndex === 'number') {
const effectedPath = data[i].relative.replace(
searchMapKeys[x],
docsConfig.search_map[searchMapKeys[x]],
)
searchData.push({
title: data[i].name,
url: `${effectedPath.replace(/.md/g, '')}`,
content: data[i].content,
})
break
}
}
}
}
}
}
if (useSearchPlugin) {
// README
searchData.push({
title: 'README',
url: 'README',
content: treeData[0].content,
})
// map treeData to generate search data source
dfsMap(treeData)
const writeSearchPath = path.resolve(
process.cwd(),
cacheDirPath,
'search.js',
)
write.sync(
writeSearchPath,
`${JSON.stringify(
searchData,
)}`,
)
}
cb()
}
================================================
FILE: packages/crd-scripts/src/web/Router.js
================================================
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import theme from 'crd-theme'
import menuSource from './crd.json'
/**
* serialize router data
*/
function routeData(data, arrayRoute = [], routePath = '/', article) {
data.forEach((item) => {
const routePropsCurrent = `${routePath}${item.name}`.replace(/.md$/, '')
const { mdconf, ...otherItem } = item
arrayRoute.push({
path: routePropsCurrent,
mdconf: mdconf || { title: item.name },
props: { ...otherItem },
article: article || item.name,
})
if (item.children && item.children.length > 0) {
arrayRoute.concat(routeData(item.children, arrayRoute, `${routePropsCurrent}/`, article || item.name))
}
})
return arrayRoute
}
function menuSourceFormat(data, routePath, article) {
const arr = []
data.forEach((item) => {
const routePropsCurrent = `${routePath || ''}/${item.name}`.replace(/.md$/, '')
if (item.type === 'directory') {
if (item.children && item.children.length > 0) {
item.title = item.name.replace(item.extension, '')
item.mdconf = {}
item.props = { isEmpty: true }
item.children = menuSourceFormat(item.children, routePropsCurrent, article || item.name)
} else {
item.title = item.name.replace(item.extension, '')
item.mdconf = { title: item.name }
item.props = { isEmpty: true }
item.children = []
}
} else {
item.title = item.mdconf && item.mdconf.title ? item.mdconf.title : item.name.replace(item.extension, '')
if (!item.mdconf) {
item.props = { isEmpty: true }
}
}
item.routePath = routePropsCurrent
item.article = article || item.name
arr.push(item)
})
return arr
}
const RoutersContainer = ({ ...props }) => {
return (
)
}
export default function RouterRoot() {
return (
)
}
================================================
FILE: packages/crd-scripts/src/web/crd.json
================================================
[]
================================================
FILE: packages/crd-scripts/src/web/index.js
================================================
import { hydrate } from 'react-dom'
import { renderToString } from 'react-dom/server';
// import { hydrateRoot } from 'react-dom/client'
import { ifDev, ifPrerender } from 'crd-client-utils'
import RouterRoot from './Router'
if (ifDev) {
// dev render
document.getElementById('root').innerHTML = renderToString( )
hydrate(
,
document.getElementById('root'),
)
// hydrateRoot(
// document.getElementById('root'),
// ,
// )
} else if (ifPrerender) {
// prerender
document.getElementById('root').innerHTML = renderToString( )
} else {
// prod render:
// It'll cause some [unkown error](https://github.com/MuYunyun/create-react-doc/issues/278) using hydrateRoot here.
// So still using hydrate temporarily.
hydrate(
,
document.getElementById('root'),
)
// hydrateRoot(
// document.getElementById('root'),
// ,
// )
}
================================================
FILE: packages/crd-seed/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
================================================
FILE: packages/crd-seed/README.md
================================================
## 主题
create-react-doc 提供了官方默认主题 [crd-seed](https://github.com/MuYunyun/create-react-doc/tree/main/packages/crd-seed)。该主题支持以下特性:
* 适配移动、PC 多端展示。
* 支持暗黑模式。
* 文档支持内嵌 codepen、codesandbox。
* GitHub 联动。
使用该主题搭建的项目有:
* [blog](https://github.com/MuYunyun/blog), [站点](http://muyunyun.cn/blog)
* 
* 
* [diana](https://github.com/MuYunyun/diana), [站点](https://muyunyun.cn/diana/)
如果您想定制化或者分享个人主题, 可以参考[自定义主题](http://muyunyun.cn/create-react-doc/自定义主题)章节。
================================================
FILE: packages/crd-seed/component/Affix/affix.js
================================================
import { useState, useEffect, useRef } from 'react'
import { throttle } from './utils'
const Affix = ({
offsetTop,
offsetBottom,
children,
target,
onChange,
className,
wrapperClassName,
style,
width,
affixStyle,
}) => {
const placeholderRef = useRef(null)
const wrapperRef = useRef(null)
const widthRef = useRef(width)
const [positionStyle, setPositionStyle] = useState({})
// 滚动元素
let scrollElm = window
// 是否是绝对布局模式
const fixedRef = useRef(false)
const [fixed, setFixed] = useState(fixedRef.current)
useEffect(() => {
widthRef.current = width
}, [width])
useEffect(() => {
// 在子节点移开父节点后保持原来占位
setWrapperDimension()
}, [fixed, width])
useEffect(() => {
if (target) scrollElm = target()
scrollElm.addEventListener('scroll', scroll)
return () => {
if (target) scrollElm = target()
scrollElm.removeEventListener('scroll', scroll)
}
}, [offsetTop, offsetBottom])
const validValue = (value) => {
return typeof value === 'number'
}
const setWrapperDimension = () => {
const { width: wrapperRefWidth, height: wrapperRefHeight } = wrapperRef.current
? wrapperRef.current.getBoundingClientRect()
: {}
placeholderRef.current &&
(placeholderRef.current.style.height = `${wrapperRefHeight}px`)
placeholderRef.current &&
(placeholderRef.current.style.width =
typeof width === 'number' ? `${width}px` : `${wrapperRefWidth}px`)
wrapperRef.current &&
(wrapperRef.current.style.width =
typeof width === 'number' ? `${width}px` : `${wrapperRefWidth}px`)
}
const updateFixed = () => {
fixedRef.current = !fixedRef.current
setFixed(fixedRef.current)
}
const handleScroll = () => {
const rect =
placeholderRef.current && placeholderRef.current.getBoundingClientRect()
if (!rect) return
let { top, bottom } = rect
const updatePositionStyle = {
width:
typeof widthRef.current === 'number'
? widthRef.current
: placeholderRef.current &&
placeholderRef.current.getBoundingClientRect().width,
zIndex: 999,
}
let containerTop = 0 // 容器距离视口上侧的距离
let containerBottom = 0 // 容器距离视口下侧的距离
if (scrollElm === window) {
bottom = window.innerHeight - bottom
} else {
const containerRect = scrollElm && scrollElm.getBoundingClientRect()
containerTop = containerRect && containerRect.top
containerBottom = containerRect && containerRect.bottom
top -= containerTop // 距离容器顶部的距离
bottom = containerBottom - bottom // 距离容器底部的距离
}
if (
(validValue(offsetTop) && top <= offsetTop) ||
(validValue(offsetBottom) && bottom <= offsetBottom)
) {
if (!fixedRef.current) {
updatePositionStyle.position = 'fixed'
validValue(offsetTop) && (updatePositionStyle.top = offsetTop + containerTop)
validValue(offsetBottom) &&
(updatePositionStyle.bottom =
scrollElm === window
? bottom
: window.innerHeight - (containerBottom - offsetBottom))
onChange && onChange(true)
updateFixed()
setPositionStyle(updatePositionStyle)
}
} else if (fixedRef.current) {
updatePositionStyle.position = 'relative'
onChange && onChange(false)
updateFixed()
setPositionStyle(updatePositionStyle)
}
}
const scroll = throttle(handleScroll, 20)
return (
)
}
export default Affix
================================================
FILE: packages/crd-seed/component/Affix/index.js
================================================
import Affix from './affix'
export default Affix
================================================
FILE: packages/crd-seed/component/Affix/utils/index.js
================================================
const throttle = (fn, wait) => {
let inThrottle
let lastFn
let lastTime
return function () {
const context = this
// eslint-disable-next-line prefer-rest-params
const args = arguments
if (!inThrottle) {
fn.apply(context, args)
lastTime = Date.now()
inThrottle = true
} else {
clearTimeout(lastFn)
lastFn = setTimeout(() => {
if (wait - (Date.now() - lastTime) <= 0) {
fn.apply(context, args)
lastTime = Date.now()
}
}, Math.max(wait - (Date.now() - lastTime), 0))
}
}
}
export { throttle }
================================================
FILE: packages/crd-seed/component/Footer/index.js
================================================
import cx from 'classnames'
import styles from './index.less'
const version = VERSION; // eslint-disable-line
const footer = FOOTER; // eslint-disable-line
const FooterView = ({ inlineCollapsed }) => {
return (
{footer ? (
) : (
<>
|
>
)}
)
}
export default FooterView
================================================
FILE: packages/crd-seed/component/Footer/index.less
================================================
.footer {
font-size: 14px;
text-align: center;
border-top: 1px solid #e9e9e9;
margin: 50px 0 0 240px;
padding: 20px 0 50px 0;
clear: both;
color: #999;
transition: margin .2s ease-in-out;
a {
color: #758AC5;
&:hover {
color: #0800ff;
}
}
.powered_by {
margin-bottom: 8px;
}
&-inlineCollapsed {
margin: 50px 0 0 0;
}
.uv_count, .pv_count {
margin-left: 5px;
}
.split {
margin: 0 5px;
}
}
================================================
FILE: packages/crd-seed/component/Header/index.js
================================================
import { useState } from 'react'
import cx from 'classnames'
import { Link } from 'react-router-dom'
import Switch from 'react-switch'
import { ifProd } from 'crd-client-utils'
import { isMobile } from '../../utils'
import Search from '../Search'
import styles from './index.less'
const Header = ({
className,
logo,
}) => {
// eslint-disable-next-line no-undef
const { user, repo, tags } = DOCSCONFIG || {}
const [checked, setChecked] = useState(false)
const handleChange = (value) => {
value
? document.body.classList.add(styles.darkMode)
: document.body.classList.remove(styles.darkMode)
setChecked(value)
}
return (
{logo &&
}
{!isMobile && (
{(DOCSCONFIG && DOCSCONFIG.title) || 'Create React Doc'}
)}
{DOCSCONFIG && DOCSCONFIG.search ?
: null}
{
tags
?
标签
: null
}
}
checkedIcon={
}
/>
)
}
export default Header
================================================
FILE: packages/crd-seed/component/Header/index.less
================================================
@import '../../style/base.less';
.logo {
float: left;
padding: 0 0 0 15px;
img {
height: 28px;
vertical-align: middle;
}
span {
vertical-align: middle;
}
img+span {
margin-left: 10px;
}
}
.header {
box-shadow: 0 2px 8px #f0f1f2;
line-height: 60px;
height: 60px;
position: relative;
width: 100%;
top: 0;
z-index: @header-zIndex;
display: flex;
.wrapper {
flex: 1;
display: flex;
align-items: center;
.titleLink {
display: inline-block;
}
.search {
margin-left: 40px;
max-width: 200px;
display: flex;
align-items: center;
}
.select {
width: 200px;
}
}
.rightArea {
display: flex;
align-items: center;
.sun {
position: absolute;
left: 5px;
top: 4px;
}
.moon {
position: absolute;
right: 5px;
top: 4px;
}
.github-corner {
display: block;
&:hover {
.octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
}
svg {
vertical-align: bottom;
}
}
}
.tags {
font-size: 15px;
margin-right: 16px;
}
@keyframes octocat-wave {
0%, 100% {
transform: rotate(0);
}
20%, 60% {
transform: rotate(-25deg);
}
40%, 80% {
transform: rotate(100deg);
}
}
.pageTitle {
font-size: 30px;
line-height: 38px;
color: #0d1a26;
font-weight: 500;
margin-bottom: 20px;
margin-top: 8px;
padding-left: 20px;
}
.darkMode {
filter: invert(100%) hue-rotate(180deg);
.no-dark-mode {
filter: invert(100%) hue-rotate(180deg);
}
}
================================================
FILE: packages/crd-seed/component/Icon/Icon.js
================================================
import { useEffect } from 'react'
import cx from 'classnames'
import loadSprite from './loadSprite'
import styles from './style/index.less'
/* omit some props depends on arr */
const omit = (props, arr) =>
Object.keys(props)
.filter(k => arr.indexOf(k) === -1)
// eslint-disable-next-line no-sequences
.reduce((acc, key) => ((acc[key] = props[key]), acc), {})
function Icon(props) {
const { type, color, prefixCls = 'icon', size, style, className, ...rest } = props
useEffect(() => {
loadSprite(props)
}, [type])
const newClassName = cx(styles[prefixCls], className)
const cloneStyle = { ...style }
if (color) {
cloneStyle.color = color
}
if (size) {
cloneStyle.fontSize = size
}
const restProps = omit(rest, ['svgContent'])
return (
)
}
export default Icon
================================================
FILE: packages/crd-seed/component/Icon/iconsInfo.js
================================================
export const IconsInfo = {
folder:
' ',
file:
' ',
edit:
' ',
'update-time':
' ',
'create-time':
' ',
search:
' ',
}
================================================
FILE: packages/crd-seed/component/Icon/index.js
================================================
import Icon from './Icon'
export default Icon
================================================
FILE: packages/crd-seed/component/Icon/loadSprite.js
================================================
import { IconsInfo } from './iconsInfo'
/* tslint:disable:max-line-length */
// inspried by https://github.com/kisenka/svg-sprite-loader/blob/master/runtime/browser-sprite.js
// Much simplified, do make sure run this after document ready
const svgSprite = contents => `
${contents}
`
/**
* ' ' =>
* ' viewBox="0 0 1024 1024"> '
*/
const handleSvgContent = (svgContent) => {
return svgContent.split('svg')[1]
}
const renderSvgSprite = (props) => {
const { svgContent, type } = props
const customIconsInfo = svgContent
? {
[`${type}`]: svgContent,
}
: {}
const mergeSvgInfo = { ...IconsInfo, ...customIconsInfo }
const symbols = Object.keys(mergeSvgInfo)
.map((iconName) => {
const getContent = handleSvgContent(mergeSvgInfo[iconName])
return ``
})
.join('')
return svgSprite(symbols)
}
const loadSprite = (props) => {
const { type, svgContent } = props
if (!document) {
return
}
const existing = document.getElementById('__CRD_SVG_SPRITE_NODE__')
const mountNode = document.body
if (!existing) {
mountNode &&
typeof mountNode.insertAdjacentHTML === 'function' &&
mountNode.insertAdjacentHTML('afterbegin', renderSvgSprite(props))
} else if (svgContent) {
const defs = existing.children[0]
const svgChildren = defs.children
const svgChildrenIds = svgChildren ? [].slice.call(svgChildren).map(r => (r).id) : []
if (svgChildrenIds.indexOf(type) !== -1) return
defs.innerHTML += ``
}
}
export default loadSprite
================================================
FILE: packages/crd-seed/component/Icon/style/index.less
================================================
.icon {
font-size: 24px;
vertical-align: middle;
fill: currentColor;
background-size: cover;
width: 1em;
height: 1em;
}
================================================
FILE: packages/crd-seed/component/Menu/Menu.js
================================================
import { useState } from 'react'
import cx from 'classnames'
import MenuItem from './MenuItem'
import { SubMenu } from './SubMenu'
import { MenuProvider } from './context'
import styles from './style/index.less'
const Menu = ({
theme = 'light',
children,
selectedKey,
onSelect = () => {},
inlineCollapsed = false,
defaultOpenKeys = [],
menuStyle,
toggle,
}) => {
/* 存储 hover 状态的 key 值, 在垂直模式中需要根据 hover 的 key 值高亮父节点 */
const [hoverKey, setHoverKey] = useState('')
const MenuContext = {
theme,
mode: 'inline',
inlineCollapsed,
defaultOpenKeys,
selectedKey,
onSelect,
hoverKey,
onHoverKey: setHoverKey,
}
const renderToggle = () => {
return (
)
}
const renderMenu = () => {
return (
)
}
return (
{renderToggle()}
{renderMenu()}
)
}
Menu.Item = MenuItem
Menu.SubMenu = SubMenu
export default Menu
================================================
FILE: packages/crd-seed/component/Menu/MenuItem.js
================================================
import { useEffect, useRef } from 'react'
import cx from 'classnames'
import { getMenuStyle } from './util'
import { useMenuContext } from './context'
import styles from './style/index.less'
function MenuItem({
title = '',
icon,
keyValue = '',
level = 0,
}) {
const {
theme,
selectedKey,
onSelect,
onHoverKey,
inlineCollapsed
} = useMenuContext()
const menuItemRef = useRef(null)
const menuItemselected = keyValue.indexOf(selectedKey) > -1
const menuUnFoldDelayTime = 300
useEffect(() => {
if (menuItemselected && (inlineCollapsed === false)) {
setTimeout(() => {
menuItemRef.current.scrollIntoView({
block: 'center',
behavior: 'smooth'
})
}, menuUnFoldDelayTime)
}
}, [keyValue, selectedKey, inlineCollapsed])
const handleOnClick = () => {
onSelect(keyValue)
}
const renderMenuItem = () => {
return (
{
onHoverKey(keyValue)
}}
onMouseLeave={() => onHoverKey('')}
onClick={handleOnClick}
style={getMenuStyle(level, 'menuItem')}
ref={menuItemRef}
>
{icon ? {icon} : null}
{title}
)
}
return renderMenuItem()
}
export default MenuItem
================================================
FILE: packages/crd-seed/component/Menu/SubMenu.js
================================================
import { useState, useRef, Fragment, Children, cloneElement } from 'react'
import cx from 'classnames'
import { useEnhancedEffect } from 'crd-client-utils'
import Transition from './transition'
import { getMenuStyle } from './util'
import { useMenuContext } from './context'
import styles from './style/index.less'
function useCurrent(
initialValue
) {
const currentRef = useRef(initialValue)
const [state, setState] = useState(initialValue)
currentRef.current = state
const set = (value) => {
currentRef.current = value
setState(value)
}
const get = () => currentRef.current
return [get, set]
}
function SubMenu({
children,
title,
icon,
level = 0,
keyValue = '',
onTitleClick = () => {},
}) {
const {
selectedKey,
mode,
hoverKey,
onHoverKey,
defaultOpenKeys = [],
} = useMenuContext()
const [menuOpen, setMenuOpen] = useState(defaultOpenKeys.indexOf(keyValue) !== -1)
const curSubmenu = useRef(null)
const popupSubMenu = useRef(null)
const [getParentMenuHover, setParentMenuHover] = useCurrent(false)
const gapDistance = 4
useEnhancedEffect(() => {
if (popupSubMenu.current && curSubmenu.current) {
popupSubMenu.current.style.left = `${curSubmenu.current.getBoundingClientRect().right +
gapDistance}px`
popupSubMenu.current.style.top = `${curSubmenu.current.getBoundingClientRect().top}px`
}
}, [getParentMenuHover()])
/**
* judege if is React Fragment.
*/
function isReactFragment(variableToInspect) {
if (variableToInspect.type) {
return variableToInspect.type === Fragment
}
return variableToInspect === Fragment
}
/* 行内模式下, 渲染子节点 */
const renderChild = (child) => {
return (
// eslint-disable-next-line quotes
<>
{Children.map(child || children, (reactNode) => {
if (!reactNode || typeof reactNode !== 'object') {
return null
}
const childElement = reactNode
if (
isReactFragment(childElement) &&
childElement.props.children
) {
return renderChild(childElement.props.children)
}
return cloneElement(childElement, {
level: level + 1,
...childElement.props,
})
// eslint-disable-next-line quotes
})}
>
)
}
const handleParentMouseEnter = () => {
setParentMenuHover(true)
onHoverKey(keyValue)
}
const handleParentMouseLeave = () => {
onHoverKey('')
}
/* 处理 menu 开闭状态 */
const handleMenuStatus = () => {
onTitleClick(keyValue)
mode === 'inline' && setMenuOpen(!menuOpen)
}
/* 判断 subMenu 是否被选中, 当子节点被选中时, 父节点也会被高亮;
同时在 vertical 模式时, 当子节点被 hover 时, 父节点也会被高亮; */
const judgeSubmenuSelect = (reactChildren) => {
const result = Children.toArray(reactChildren).some((reactNode) => {
if (!reactNode || typeof reactNode !== 'object') {
return false
}
const childElement = reactNode
// eslint-disable-next-line no-shadow
const { keyValue } = childElement.props
const originKey = keyValue ? String(keyValue) : ''
if (childElement.type.name === 'MenuItem') {
return selectedKey.split('').indexOf(originKey) !== -1 || hoverKey === originKey
}
if (childElement.type.name === 'SubMenu') {
return judgeSubmenuSelect(childElement.props.children) || hoverKey === originKey
}
return false
})
return result
}
return (
onHoverKey(keyValue)}
ref={curSubmenu}
>
{icon ? {icon} : null}
{title}
{mode === 'inline' ? (
) : null}
)
}
export { SubMenu }
================================================
FILE: packages/crd-seed/component/Menu/context.js
================================================
import { createContext, useContext } from 'react'
const MenuContext = createContext(undefined)
export const MenuProvider = ({ children, value }) => (
{children}
)
export const useMenuContext = () => useContext(MenuContext)
================================================
FILE: packages/crd-seed/component/Menu/index.js
================================================
import Menu from './Menu'
export default Menu
================================================
FILE: packages/crd-seed/component/Menu/style/index.less
================================================
@import './theme.less';
.menu {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0;
color: @menu-color;
background: @menu-background;
user-select: none;
transition: background .2s ease-in-out, width .2s ease-in-out;
overflow: auto;
user-select: none;
font-weight: 600;
&-item {
// margin: 0 16px 0 15px;
white-space: nowrap;
cursor: pointer;
height: 40px;
line-height: 40px;
padding: 0 16px;
color: @menu-color;
position: relative;
cursor: pointer;
// overflow: hidden;
// white-space: nowrap;
// text-overflow: ellipsis;
&-selected {
position: relative;
background: @menu-background-selected;
color: @menu-color-selected;
a {
color: @menu-color-selected!important;
}
}
/* icon */
i {
margin-right: 10px;
}
a {
color: rgba(0, 0, 0, 0.8);
cursor: pointer;
display: inline-block;
text-decoration: none;
&::before {
top: 0;
left: 0;
right: 0;
bottom: 0;
content: '';
position: absolute;
background-color: transparent;
}
}
&-title {
vertical-align: middle;
}
}
&-item:hover {
color: @menu-color-hover;
& .menu-icon {
color: @menu-color-hover;
}
}
.submenu {
line-height: 40px;
cursor: pointer;
overflow: hidden;
&-title {
position: relative;
padding: 0 28px 0 0;
font-size: 14px;
cursor: pointer;
margin-left: 20px;
// overflow: hidden;
// white-space: nowrap;
// text-overflow: ellipsis;
}
&-title:hover {
color: @menu-color-hover;
.submenu-arrow {
&::before,
&::after {
background: @menu-color-hover;
}
}
}
&-arrow {
display: inline-block;
width: 10px;
flex-shrink: 0;
// margin: 0 3px 0 1px;
margin: 0 3px 0 -12px;
position: relative;
top: -5px;
&::before,
&::after {
content: '';
position: absolute;
width: 6px;
height: 1.5px;
background: @arrow-background;
transition: background .2s cubic-bezier(0.645, 0.045, 0.355, 1),
transform .2s cubic-bezier(0.645, 0.045, 0.355, 1),
top .2s cubic-bezier(0.645, 0.045, 0.355, 1),
-webkit-transform .2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
&::before {
transform: rotate(45deg) translateY(-2px);
}
&::after {
transform: rotate(-45deg) translateY(2px);
}
&-open {
&::before {
transform: rotate(-45deg) translateX(2px);
}
&::after {
transform: rotate(45deg) translateX(-2px);
}
}
}
&-selected {
color: @menu-color-selected;
}
}
&-inline {
width: 100%;
.menu-item {
&-selected {
&::after {
content: '';
position: absolute;
width: 3px;
background: @menu-inline-selected;
top: 0;
bottom: 0;
right: 0;
}
}
}
.submenu {
&-arrow {
&::before {
transform: rotate(45deg) translateY(-2px);
}
&::after {
transform: rotate(-45deg) translateY(2px);
}
&-open {
&::before {
transform: rotate(-45deg) translateX(2px);
}
&::after {
transform: rotate(45deg) translateX(-2px);
}
}
}
}
/* fold */
&-collapsed {
width: 0px;
.menu-item {
padding: 0 24px !important;
&-title {
opacity: 0;
}
}
.submenu {
&-title {
padding: 0 24px !important;
&-field {
opacity: 0;
}
.submenu-arrow {
display: none;
}
}
}
}
}
&-icon {
margin-right: 4px;
}
}
/* submenu fold/unfold animation */
.collapse-transition {
overflow: hidden;
transition: height .2s ease-in-out;
}
.toggle {
position: absolute;
width: 30px;
height: 30px;
left: 240px;
top: 200px;
transition: left .2s linear;
box-shadow: 2px 0 8px rgba(0, 0, 0, .15);
border-radius: 0 4px 4px 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
&-icon {
position: relative;
background: 0 0;
width: 12px;
height: 2px;
background: #333;
transition: background .2s cubic-bezier(.78, .14, .15, .86);
&::before,
&::after {
content: '';
display: block;
position: absolute;
background: #333;
width: 50%;
height: 2px;
}
&::before {
transform: translateY(-2px) rotate(-45deg);
}
&::after {
transform: translateY(2px) rotate(45deg);
}
&-close {
background: #333;
&::before,
&::after {
content: '';
display: block;
position: absolute;
background: #333;
width: 100%;
height: 2px;
transform: rotate(0deg);
}
&::before {
top: -4px;
}
&::after {
top: 4px;
}
}
}
&-collapsed {
left: 0px;
}
}
================================================
FILE: packages/crd-seed/component/Menu/style/theme.less
================================================
/* light */
@menu-color: rgba(0, 0, 0, 0.65);
@menu-background: #fff;
@menu-color-hover: #1890ff;
@menu-color-selected: #1890ff;
@menu-inline-selected: #1199ee;
@menu-background-selected: #e6f7ff;
@arrow-background: #333;
================================================
FILE: packages/crd-seed/component/Menu/transition.js
================================================
import { useEffect, useRef, useCallback } from 'react'
import styles from './style/index.less'
const ANIMATION_DURATION = 200
export default function Transition({
isShow,
children,
}) {
const mounted = useRef(false)
const collapseRef = useRef(null)
const timer = useRef({})
// prepare
const beforeEnter = () => {
const el = collapseRef.current
el.style.height = '0px'
}
const afterEnter = useCallback(() => {
const el = collapseRef.current
el.style.display = 'block'
el.style.height = ''
}, [])
// start
const enter = useCallback(() => {
const el = collapseRef.current
el.style.display = 'block'
if (el.scrollHeight !== 0) {
el.style.height = `${el.scrollHeight}px`
}
timer.current.enterTimer = setTimeout(() => afterEnter(), ANIMATION_DURATION)
}, [afterEnter])
const beforeLeave = useCallback(() => {
const el = collapseRef.current
el.style.display = 'block'
if (el.scrollHeight !== 0) {
el.style.height = `${el.scrollHeight}px`
}
}, [])
const afterLeave = useCallback(() => {
const el = collapseRef.current
el.style.display = 'none'
el.style.height = ''
}, [])
const leave = useCallback(() => {
const el = collapseRef.current
if (el.scrollHeight !== 0) {
el.style.height = '0px'
}
timer.current.leaveTimer = setTimeout(() => afterLeave(), ANIMATION_DURATION)
}, [afterLeave])
const triggerChange = useCallback(
(isShow) => {
clearTimeout(timer.current.enterTimer)
clearTimeout(timer.current.leaveTimer)
if (isShow) {
beforeEnter()
enter()
} else {
beforeLeave()
leave()
}
},
[beforeLeave, enter, leave]
)
useEffect(() => {
if (!mounted.current) {
mounted.current = true
beforeEnter()
if (isShow) {
enter()
}
} else {
triggerChange(isShow)
}
}, [enter, isShow, triggerChange])
return (
{children}
)
}
================================================
FILE: packages/crd-seed/component/Menu/util.js
================================================
/* 获取 menu 样式
level: 层级
*/
const getMenuStyle = (level, type) => {
const basicStyle = {
fontSize: level === 0 ? '14px' : '12px',
}
if (type === 'menuItem') {
return {
...basicStyle,
paddingLeft: `${21 + (level * 16)}px`,
}
}
return {
...basicStyle,
paddingLeft: `${level * 16}px`,
}
}
export { getMenuStyle }
================================================
FILE: packages/crd-seed/component/NoMatch/index.js
================================================
import styles from './index.less'
const NoMatch = () => {
// eslint-disable-next-line no-undef
const { user, repo } = DOCSCONFIG || {}
return (
)
}
export default NoMatch
================================================
FILE: packages/crd-seed/component/NoMatch/index.less
================================================
.noMatch {
position: relative;
width: 100%;
height: 80%;
text-align: center;
border-spacing: 0;
border-collapse: collapse;
color: #a2a2a2;
h1 {
color: #717171;
font-size: 68px;
line-height: 54px;
font-weight: 500;
}
}
================================================
FILE: packages/crd-seed/component/Search/README.md
================================================
### API
| props | description | type | default |
| :---------: | :---------: | :----: | :------: |
| placeholder | placeholder | string | 'Search' |
| className | css | string | -- |
================================================
FILE: packages/crd-seed/component/Search/index.js
================================================
import { useState, useEffect } from 'react'
import cx from 'classnames'
import Icon from '../Icon'
import styles from './index.less'
const Search = ({
placeholder = 'Search',
className,
}) => {
const [value, setValue] = useState('')
// const [searchContent, setSearchContent] = useState([]);
// const showSearchContent = value.length > 0 && searchContent.length > 0;
useEffect(() => {
/* eslint-disable-next-line no-undef */
// if (SEARCHCONTENT) {
// /* eslint-disable-next-line no-undef */
// const filterSearch = SEARCHCONTENT.filter((r) => {
// return r.title.includes(value) || r.content.includes(value);
// });
// setSearchContent(filterSearch);
// }
}, [value])
return (
)
}
export default Search
================================================
FILE: packages/crd-seed/component/Search/index.less
================================================
.search {
position: relative;
input {
line-height: 1.5;
font-size: 14px;
color: #999;
outline: 0;
border-radius: 4px;
border: 0;
width: 200px;
}
.panel {
position: absolute;
width: 350px;
top: 50px;
left: 0;
background: rgba(255, 255, 255);
max-height: 400px;
box-shadow: rgba(101, 119, 134, 0.2) 0px 0px 15px, rgba(101, 119, 134, 0.15) 0px 0px 3px 1px;
overflow-y: auto;
li {
padding: 0 5px;
list-style: none;
border: 1px solid rgb(230, 236, 240);
}
}
.searchItem {
display: flex;
}
.title {
display: inline-block;
width: 90px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.content {
display: inline-block;
width: 245px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
================================================
FILE: packages/crd-seed/component/Tags/index.js
================================================
import { Link, useMatch } from 'react-router-dom'
import { ifProd } from 'crd-client-utils'
import { ifAddPrefix } from '../../utils'
import styles from './index.less'
/**
* name: the name of tag category.
*/
const Tags = () => {
const { user, repo } = DOCSCONFIG || {}
const path = ifAddPrefix ? `/${repo}/tags/:name` : '/tags/:name'
const routeMatch = useMatch(path) || {}
const { name } = routeMatch.params || {}
return (
{name || 'Tags'}
{
name
? mapTagsWithArticle.find(({ tagName }) => tagName === name)?.mapArticle.map(({ path, title }) => {
return
{title}
})
: mapTagsWithArticle.map(({ tagName }) => {
return
{tagName}
})
}
)
}
export default Tags
================================================
FILE: packages/crd-seed/component/Tags/index.less
================================================
.tags {
width: 100%;
padding: 0 20px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
&-title {
font-size: 24px;
font-weight: 400;
text-align: center;
}
&-content {
width: 100%;
margin-top: 20px;
}
&-text {
color: #6f6f6f;
padding: 10px;
display: inline-flex;
}
}
================================================
FILE: packages/crd-seed/index.js
================================================
import { Routes, Route, Navigate } from 'react-router-dom'
import BasicLayout from './layout'
import NoMatch from './component/NoMatch'
import './index.less'
// run in the Web/Router.js
const ThemeSeed = (props) => {
return (
} />
} />
)
}
export default ThemeSeed
================================================
FILE: packages/crd-seed/index.less
================================================
body {
font-family: cursive, sans-serif;
}
body,
html {
position: relative;
}
body,
html,
ul,
li {
margin: 0;
padding: 0;
}
a {
color: #314659;
text-decoration: none;
transition: color .3s;
&:hover {
color: #1890ff;
}
}
*,
*::before,
*::after {
box-sizing: border-box;
}
:global {
#root {
height: 100%;
background: white;
}
}
================================================
FILE: packages/crd-seed/language/index.js
================================================
const languageMap = {
en: {
create_tm: 'create',
modify_tm: 'modify',
},
'zh-cn': {
create_tm: '创建',
modify_tm: '修改',
},
}
export default languageMap
================================================
FILE: packages/crd-seed/layout/index.js
================================================
import * as React from 'react'
import { Routes, Link, Route, Navigate, useLocation } from 'react-router-dom'
import cx from 'classnames'
import { ifDev, ifProd, ifPrerender } from 'crd-client-utils'
const Giscus = require('@giscus/react')
import Menu from '../component/Menu'
import Icon from '../component/Icon'
import Affix from '../component/Affix'
import Header from '../component/Header'
import Footer from '../component/Footer'
import Tags from '../component/Tags'
import languageMap from '../language'
import { isMobile, ifAddPrefix } from '../utils'
import { getOpenSubMenuKeys } from './utils'
import logo from '../crd.logo.svg'
import styles from './index.less'
import '../style/mobile.less'
const { useState, useEffect, useMemo } = React
const SubMenu = Menu.SubMenu
function BasicLayout({
routeData,
menuSource
}) {
const location = useLocation()
const { pathname } = location
const {
user,
repo,
branch = 'main',
language = 'en',
menuOpenKeys,
tags,
comment
} = DOCSCONFIG || {}
const [inlineCollapsed, setInlineCollapsed] = useState(true)
const [selectedKey, setSelectedKey] = useState('')
const curOpenKeys = getOpenSubMenuKeys({
pathname,
menuSource,
menuOpenKeys
})
const defaultPath = (routeData.find(data => data.path === '/README')
&& routeData.find(data => data.path === '/README').mdconf
&& routeData.find(data => data.path === '/README').mdconf.abbrlink) || 'README'
useEffect(() => {
if (ifPrerender) {
scrollToTop()
INJECT?.inject?.()
}
}, [])
useEffect(() => {
INJECT?.injectWithPathname?.(pathname)
}, [pathname])
useEffect(() => {
const { pathname } = location
let newPathName = pathname
// fix https://github.com/MuYunyun/create-react-doc/issues/195
if (newPathName.endsWith('/')) {
newPathName = newPathName.slice(0, newPathName.length - 1)
}
if (newPathName.startsWith(`/${repo}`)) {
newPathName = newPathName.slice(`/${repo}`.length, newPathName.length)
}
setSelectedKey(newPathName || defaultPath)
}, location.pathname)
const scrollToTop = () => {
document.body.scrollTop = 0
document.documentElement.scrollTop = 0
window.scrollTo(0, 0)
}
const renderSubMenuItem = (menus) => {
return (
<>
{menus.map((item, index) => {
const { mdconf, routePath } = item || {}
const { abbrlink } = mdconf || {}
const path = abbrlink ? `/${abbrlink}` : routePath
// item.path carrys .md here.
return item.children && item.children.length > 0 ? (
}
>
{renderSubMenuItem(item.children)}
) : (
}
keyValue={abbrlink ? `/${abbrlink}` : item.path}
title={
item &&
item.type === "directory" &&
item.props &&
item.props.isEmpty ? (
{(item.mdconf && item.mdconf.title) || item.name}
) : (
-1}
>
{item && item.mdconf && item.mdconf.title
? item.mdconf.title
: item.title}
)
}
/>
)
})}
>
)
}
const renderMenu = (menus) => {
if (menus.length < 1) return null
return (
{
setInlineCollapsed(!inlineCollapsed)
}}
menuStyle={{
height: "100vh",
overflow: "auto",
}}
selectedKey={selectedKey}
onSelect={(keyValue) => {
setSelectedKey(keyValue)
}}
defaultOpenKeys={curOpenKeys}
>
{renderSubMenuItem(menus || [])}
)
}
/**
* This section is to show article's relevant information
* such as edit in github and so on.
*/
const renderPageHeader = () => {
const curMenuSource = routeData.filter(r => {
if (r.props.type === 'directory') return false
return pathname.indexOf(r.mdconf.abbrlink) > -1 || decodeURIComponent(pathname).indexOf(r.path) > -1
})
const editPathName = curMenuSource[0] && curMenuSource[0].props.path
const isNotTagPage = location.pathname.indexOf('/tags') === -1
return (
)
}
/**
* This section is to render comment area.
* Every pathname should has its own comment module.
*/
const renderComment = useMemo(() => {
return
}, [pathname])
/**
* This section is to show article's relevant information
* such as edit in created time、edited time and so on.
*/
const renderPageFooter = () => {
// in local env, data.path is to be /READEME, however pathname may be /Users/mac/.../.crd-dist/READEME/index.html
const matchData = routeData.find((data) => pathname.indexOf(data.path) > -1)
const matchProps = matchData && matchData.props
return (
{matchProps && matchProps.birthtime ? (
{languageMap[language].create_tm}:
{matchProps.birthtime}
) : null}
{matchProps && matchProps.mtime ? (
{languageMap[language].modify_tm}:
{routeData.find((data) => pathname.indexOf(data.path) > -1).props.mtime}
): null}
)
}
const isCurentChildren = () => {
const getRoute = routeData.filter((data) => pathname.indexOf(data.path) > -1)
const article = getRoute.length > 0 ? getRoute[0].article : null
const childs = menuSource.filter(
(data) =>
article === data.article && data.children && data.children.length > 1
)
return childs.length > 0
}
const isChild = isCurentChildren()
const renderMenuContainer = () => {
return (
<>
{renderMenu(menuSource)}
{
e.stopPropagation()
setInlineCollapsed(true)
}}
/>
>
)
}
const renderContent = () => {
return (
{/* see https://reacttraining.com/react-router/web/api/Redirect/exact-bool */}
}
/>
{routeData.map((item) => {
const { path, mdconf, component } = item
const { abbrlink } = mdconf
const enhancePath = abbrlink ? `/${abbrlink}` : path
const Comp = component
return (
}
/>
)
})}
{
tags
? <>
}
/>
}
/>
>
: null
}
{/* Todo: follow up how to use Redirect to back up the rest of route. */}
{/* */}
{comment?.GiscusConfig ? renderComment : null}
{renderPageFooter()}
)
}
return (
{renderPageHeader()}
{renderMenuContainer()}
{renderContent()}
)
}
export default BasicLayout
================================================
FILE: packages/crd-seed/layout/index.less
================================================
@import '../style/base.less';
.wrapper {
&::after {
content: '';
display: block;
clear: both;
}
}
.wrapperContent {
padding: 40px 0 0 0;
margin: 0px auto 0;
position: relative;
min-height: 100vh;
.pageHeader,
.pageFooter {
position: absolute;
right: 40px;
font-size: 13px;
display: flex;
align-items: center;
}
.pageHeader {
top: 20px;
}
.pageFooter {
bottom: -23px;
.position {
margin-right: 6px;
display: flex;
align-items: center;
}
}
.icon {
margin-right: 3px;
}
}
.wrapperMobile {
// padding-top: 0;
}
.menuWrapper {
width: 240px;
float: left;
position: absolute;
font-size: 14px;
color: #6d6d6d;
transition: width .2s linear;
z-index: @menu-zIndex;
ul li {
list-style: none;
}
ul ul {
padding: 0 0 0 0;
}
li {
// padding: 0 0 0 21px;
line-height: 40px;
}
li li {
padding: 0 0 0 0;
}
// :global {
// .active a {
// color: #1890ff;
// }
// }
&-inlineCollapsed {
width: 0;
}
.affixPlaceholder {
transition: width .2s linear;
}
.affixWrapper {
border-right: 1px solid rgb(233, 233, 233);
transition: width .2s linear;
width: 0px;
}
}
.menuMask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, .3);
z-index: @menu-mask-zIndex;
transition: opacity .3s cubic-bezier(.78, .14, .15, .86)
}
.content {
margin-left: 240px;
min-height: 300px;
transition: margin-left .2s ease-in-out;
position: relative;
&-fullpage {
margin-left: 0px;
}
}
.contentNoMenu {
min-height: 400px;
}
giscus-widget {
display: flex;
margin: auto;
max-width: 940px;
}
================================================
FILE: packages/crd-seed/layout/utils.js
================================================
/**
* get keys of open sub menu from pathname
* {
* pathname: pathname of location,
* when pathname is /9f41fc98, the result is ['/docs/主题'].
* when pathname is /测试/测试路由, the result is ['/docs/测试']
* menuOpenKeys: means extra show open keys in config.yml
* }
*/
function getOpenSubMenuKeys({
pathname,
menuSource,
menuOpenKeys
}) {
const result = []
getOpenSubMenuKeysForAbbrLink(
menuSource,
decodeURI(pathname),
result
)
/** default open menu from config.yml */
if (menuOpenKeys) {
result.push(...menuOpenKeys.split(','))
}
return result
}
function getOpenSubMenuKeysForAbbrLink(source, pathname, result) {
for (let i = 0; i < source.length; i++) {
const { type, path, mdconf } = source[i]
if (type === 'directory') {
result.push(path)
const ifFind = getOpenSubMenuKeysForAbbrLink(source[i].children, pathname, result)
if (ifFind) return true
result.pop()
} else {
if (
pathname.indexOf(mdconf.abbrlink) > -1 // used with abbrlink
|| (pathname.indexOf(source[i].routePath) > -1) // used not with abbrlink
) {
return true
}
}
}
}
export { getOpenSubMenuKeys }
================================================
FILE: packages/crd-seed/package.json
================================================
{
"name": "crd-seed",
"version": "1.10.3",
"description": "Default Theme with Create React Doc",
"main": "index.js",
"dependencies": {
"@giscus/react": "2.2.2",
"crd-client-utils": "^1.8.2",
"react-router-dom": "^6.3.0",
"react-switch": "^5.0.1"
},
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/theme-seed"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199"
}
================================================
FILE: packages/crd-seed/style/base.less
================================================
// z-index
@menu-zIndex: 99;
@menu-mask-zIndex: 90;
@header-zIndex: 100;
================================================
FILE: packages/crd-seed/style/mobile.less
================================================
// remove the blue area in the mobile
:global {
div,
input,
textarea,
button,
select,
a,
li {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
}
================================================
FILE: packages/crd-seed/utils/index.js
================================================
import isClient from 'diana/lib/isClient'
import { ifProd, ifPrerender } from 'crd-client-utils'
/** judge if is in mobile */
const isMobile = isClient() ? 'ontouchend' in window : false
// decide to if add prefix for path, eg: '/' or '/${repo}'
const ifAddPrefix = ifProd && !ifPrerender
export { isMobile, ifAddPrefix }
================================================
FILE: packages/crd-templates/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
================================================
FILE: packages/crd-templates/README.md
================================================
_.-"\
_.-" \
,-" \
\ create \
\ \ react \
\ \ doc \
\ \ _.-;
\ \ _.-" :
\ \,-" _.-"
\( _.-"
`--"
# crd-templates
crd-templates 集成了 create-react-doc 中相关模板文件。
================================================
FILE: packages/crd-templates/default/.github/workflows/gh-pages.yml
================================================
name: github pages
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.3
with:
node-version: '12.x'
- name: Get yarn cache
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ runner.os }}-website-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-website-
- run: yarn install --frozen-lockfile
- run: yarn build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .crd-dist
================================================
FILE: packages/crd-templates/default/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
================================================
FILE: packages/crd-templates/default/Introduction/hello_world.md
================================================
Write docs happily now.
If there is any problem, welcome give issue [there](https://github.com/MuYunyun/create-react-doc/issues).
================================================
FILE: packages/crd-templates/default/README.md
================================================
_.-"\
_.-" \
,-" \
\ create \
\ \ react \
\ \ doc \
\ \ _.-;
\ \ _.-" :
\ \,-" _.-"
\( _.-"
`--"
[](https://badge.fury.io/js/create-react-doc)
[](https://www.npmjs.com/package/create-react-doc)




# Create React Doc
Create React Doc 是一个使用 React 的 markdown 文档站点生成工具。就像 [create-react-app](https://github.com/facebook/create-react-app) 一样,开发者可以使用 Create React Doc 来开发、部署 markdown 站点或者博客而无需关心站点环境配置信息。
## 特性
* 建站理念: 文件即站点 (Files as a site)。
* 开箱即用: 一键生成可运行文档站点, 无需关心站点环境配置信息。
* 自定义展示目录: 天然适合搭建 monorepo 文档、博客等站点。
* 性能: 文档支持懒加载提升站点加载速度。
* 工作流: 集成 Github action, 自动化打包、发布站点。
> [快速上手](http://muyunyun.cn/create-react-doc/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B)
## 主题
当前默认使用的主题是 [crd-seed](https://github.com/MuYunyun/create-react-doc/tree/main/packages/crd-seed)。
使用该主题搭建的站点
* [blog](http://muyunyun.cn/blog)
* 
* 
* [diana](https://muyunyun.cn/diana/)
> 如果你的产品从中受益,欢迎
留言补充
## 快速上手
**create-react-doc** 非常容易上手。开发者不需要额外安装或配置 webpack 或者 Babel 等工具,它们被内置隐藏在脚手架中,因此开发者可以专心于文档的书写。
如果你想在当前文件下建立站点文件 `doc`, 这里提供如下三种方式快速建站:
### npx
```bash
npx create-react-doc doc
```
### npm
```bash
npm init create-react-doc doc
```
### yarn
```bash
yarn create react-doc doc
```

> 如果想把模板内容内容拉取到当前文件夹, 则可以将如上命令的 `doc` 替换为 `.`, 比如执行 `npx create-react-doc .`。
接着执行 `cd doc && yarn && yarn start`, 可以在 `localhost: 3000` 预览站点, 如果站点文档发生改变, 站点将自动重新加载。
## 高阶用法
与 git 文件结构类似, 如果在展示的文件夹中有私有文件不方便展示在文档站点, 可以在 `.gitignore` 文件中设置过滤文件, 这样它们就不会展示在文档站点中了。eg: [.gitignore](https://github.com/MuYunyun/blog/blob/main/.gitignore)
## 其它工具
* [crd-leetcode-cli](https://github.com/MuYunyun/create-react-doc/tree/main/packages/leetcode-cli): 提供将 [leetcode](https://leetcode-cn.com/) 中已 AC 的题目转化为 markdown 表格的能力。
================================================
FILE: packages/crd-templates/default/_.gitignore
================================================
node_modules
package-lock.json
.DS_Store
.cache
.crd-dist
================================================
FILE: packages/crd-templates/default/_config.yml
================================================
# details see http://muyunyun.cn/create-react-doc/默认主题
# Site
# title:
# Menu dir
## you can also set detailed dir, such as BasicSkill/css
menu: ['Introduction']
## set init open menu keys
# menuOpenKeys:
# site theme
theme: crd-seed
# Github
## if you want to editing pages on github, you should config these arguments.
# user:
# repo:
# Available values: en | zh-cn
language: en
================================================
FILE: packages/crd-templates/default/_package.json
================================================
{
"name": "{{name}}",
"version": "1.0.0",
"description": "Describe {{name}} here",
"scripts": {
"start": "react-doc start",
"build": "react-doc build",
"deploy": "react-doc deploy"
},
"keywords": [
"{{name}}",
"react-doc",
"react"
],
"devDependencies": {
"create-react-doc": "{{crdVersion}}"
},
"author": "",
"license": "MIT"
}
================================================
FILE: packages/crd-templates/package.json
================================================
{
"name": "crd-templates",
"version": "1.10.0",
"description": "Default Templates with Create React Doc",
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/templates"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": ""
}
================================================
FILE: packages/crd-templates/theme/default/Introduction/hello_world.md
================================================
Write docs happily now.
If there is any problem, welcome give issue [here](https://github.com/MuYunyun/create-react-doc/issues).
================================================
FILE: packages/crd-templates/theme/default/README.md
================================================
_.-"\
_.-" \
,-" \
\ create \
\ \ react \
\ \ doc \
\ \ _.-;
\ \ _.-" :
\ \,-" _.-"
\( _.-"
`--"
### {{name}}
This is {{name}} theme for [create-react-doc](https://github.com/MuYunyun/create-react-doc).
================================================
FILE: packages/crd-templates/theme/default/_.gitignore
================================================
node_modules
package-lock.json
.DS_Store
.cache
.crd-dist
config.yml
Introduction
================================================
FILE: packages/crd-templates/theme/default/_.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
================================================
FILE: packages/crd-templates/theme/default/_config.yml
================================================
# details see http://muyunyun.cn/create-react-doc/默认主题
# Site
# title:
# Menu dir
## you can also set detailed dir, such as BasicSkill/css
menu: ['Introduction']
## set init open menu keys
# menuOpenKeys:
# site theme
devTheme: ./index
# Github
## if you want to editing pages on github, you should config these arguments.
# user:
# repo:
# Available values: en | zh-cn
language: en
================================================
FILE: packages/crd-templates/theme/default/_index.js
================================================
// The position of current index.js should be kept.
import { Routes, Route, Navigate } from 'react-router-dom'
import styles from './index.less'
const {{name}} = ({routeData, menuSource}) => {
return (
}
/>
{routeData.map((item) => {
const Comp = item.component
return (
}
/>
)
})}
)
}
export default {{name}}
================================================
FILE: packages/crd-templates/theme/default/_index.less
================================================
.center {
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
}
================================================
FILE: packages/crd-templates/theme/default/_package.json
================================================
{
"name": "{{name}}",
"version": "0.1.0",
"description": "{{name}} theme for create-react-doc",
"main": "index.js",
"scripts": {
"start": "react-doc start"
},
"keywords": [
"{{name}}",
"react-doc",
"react",
"create-react-doc",
"theme"
],
"devDependencies": {
"create-react-doc": "{{crdVersion}}"
},
"dependencies": {
"react-router-dom": "^6.3.0"
},
"author": "",
"license": "MIT"
}
================================================
FILE: packages/crd-theme/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
================================================
FILE: packages/crd-theme/README.md
================================================
### crd-theme
[create-react-doc](https://github.com/MuYunyun/create-react-doc) 的主题加载包。提供了懒加载, 文件内容解析等能力。
================================================
FILE: packages/crd-theme/component/Loading/index.js
================================================
import styles from './index.less'
const Loading = () => {
return (
正在加载中....
)
}
export default Loading
================================================
FILE: packages/crd-theme/component/Loading/index.less
================================================
.loading {
text-align: center;
min-height: 450px;
padding: 30px 0;
}
================================================
FILE: packages/crd-theme/index.html
================================================
<%= htmlWebpackPlugin.options.title %>
================================================
FILE: packages/crd-theme/index.js
================================================
import * as React from 'react'
import Markdown from './markdown'
import './index.less'
export default function (props) {
// routing load component
if (props.routeData && props.routeData.length > 0) {
props.routeData.map((item) => {
item.component = Markdown
return item
})
}
// support for custom theme.
const CustomTheme = require('__project_theme__').default
// use custom theme here.
return (
)
}
================================================
FILE: packages/crd-theme/index.less
================================================
body,
html {
position: relative;
}
body,
html,
ul,
li {
margin: 0;
padding: 0;
}
a {
color: #314659;
text-decoration: none;
transition: color .3s;
&:hover {
color: #1890ff;
}
}
*,
*::before,
*::after {
box-sizing: border-box;
}
:global {
#root {
height: 100%;
background: white;
}
}
================================================
FILE: packages/crd-theme/markdown/Link.js
================================================
import styles from './Link.less'
export default ({ title, href, children }) => {
let link = href.replace(/(\/|\/show\/|\/show)$/g, '')
if (
/^(http(?:|s):)\/\/(jsfiddle.net|runjs.cn|codepen.io|codesandbox.io)/.test(link) &&
!/^(https|http):\/\/(jsfiddle.net|runjs.cn|codepen.io|codesandbox.io)(?:|\/)$/.test(link)
) {
const regexRunjs = /(https|http):\/\/runjs.cn\/code\/(.*)/gi
const regexCodepen = /(https|http):\/\/codepen.io\/(.*)\/pen\/(.*)/gi
const regexCodesandbox = /(https|http):\/\/codesandbox.io\/(s|embed)\/(.*)/gi
const runjs = regexRunjs.exec(link)
const codepen = regexCodepen.exec(link)
const codesandbox = regexCodesandbox.exec(link)
if (runjs && runjs.length > 2) {
link = `http://sandbox.runjs.cn/show/${runjs[2]}`
} else if (codepen && codepen.length === 4) {
link = `https://codepen.io/${codepen[2]}/embed/${codepen[3]}?height=400`
} else if (codesandbox && codesandbox.length === 4) {
link = `https://codesandbox.io/embed/${codesandbox[3]}`
} else {
link = `${link}/show/`
}
return (
)
}
return (
{children}
)
}
================================================
FILE: packages/crd-theme/markdown/Link.less
================================================
.frame {
width: 100%;
min-height: 400px;
border: 1px solid #e9e9e9;
border-radius: 3px;
background: #fff;
}
================================================
FILE: packages/crd-theme/markdown/codeBlock.js
================================================
/** @jsxRuntime classic /
/* @jsx jsx */
import Highlight, { defaultProps } from 'prism-react-renderer'
import nightOwlLight from 'prism-react-renderer/themes/nightOwlLight'
import { mdx } from '@mdx-js/react'
// import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live'
export default ({ children, className, live, render }) => {
// const language = className.replace(/language-/, '')
// if (live) {
// return (
//
// '/** @jsx mdx */' + code}
// scope={{mdx}}
// >
//
//
//
//
//
// )
// }
// if (render) {
// return (
//
//
//
//
//
// )
// }
return (
{({ className, style, tokens, getLineProps, getTokenProps }) => (
{tokens.map((line, i) => (
{line.map((token, key) => (
))}
))}
)}
)
}
================================================
FILE: packages/crd-theme/markdown/index.js
================================================
import * as React from 'react'
import cx from 'classnames'
import { MDXProvider } from '@mdx-js/react'
import { Helmet } from 'react-helmet'
import CodeBlock from './codeBlock'
import Link from './Link'
import styles from './style/index.less'
const { useState, useEffect, useRef } = React
const components = {
code: CodeBlock,
link: Link,
}
function Markdown(markdownProps) {
const { props } = markdownProps
const { relative, name } = props
const getRmFirstSlashMarkdownName = () => {
const relativeMd = relative
if (!relativeMd) return null
return relative.slice(1, relative.length - 3)
}
const getInitMarkdownCP = () => {
const markdownName = getRmFirstSlashMarkdownName()
if (!markdownName) return
return () => require(`__project_root__/${markdownName}.md`).default
}
const [MarkdownCP, setMarkdownCP] = useState(getInitMarkdownCP())
const markdownWrapperRef = useRef(null)
const renderMarkdown = () => {
const markdownName = getRmFirstSlashMarkdownName()
if (!markdownName) return
// it must be writen with / & .md in dynamic import
import(`__project_root__/${markdownName}.md`).then((data) => {
// data.default is a function, so we should write () => data.default in setState here.
setMarkdownCP(() => (data.default || data))
})
}
useEffect(() => {
renderMarkdown()
}, [getRmFirstSlashMarkdownName()])
const getName = () => {
return name ? name.replace('.md', '') : ''
}
return (
<>
{getName()}
{
MarkdownCP
?
: null
}
>
)
}
export default Markdown
================================================
FILE: packages/crd-theme/markdown/style/css.less
================================================
.language-css, .language-scss, .language-stylus, .language-less {
.keyword, .selector-tag, .subst {
color: #d73a49;
font-weight: normal;
}
.tag, .name, .attribute {
color: #005cc5;
font-weight: normal;
}
.selector-pseudo {
color: #6f42c1;
}
.selector-attr {
color: #008c41;
}
.selector-class {
color: #6f42c1;
}
.selector-id {
font-weight: normal;
}
}
================================================
FILE: packages/crd-theme/markdown/style/default.less
================================================
code {
word-wrap: normal;
}
pre {
max-height: 35em;
position: relative;
overflow: auto;
background-color: rgb(251, 251, 251);
border-radius: 3px;
}
pre code {
background: none;
font-size: 1em;
overflow-wrap: normal;
white-space: inherit;
}
ul,
ol {
padding-left: 2em;
}
dl {
padding: 0;
dt {
padding: 0;
margin-top: 16px;
font-size: 14px;
font-style: italic;
font-weight: 600;
}
}
li+li {
margin-top: 3px;
}
a {
color: #0366d6;
}
p {
margin-bottom: 16px;
}
blockquote {
margin: 0;
padding: 0 1em;
margin: 16px 0 16px 0;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
&>:first-child {
margin-top: 0;
}
&>:last-child {
margin-bottom: 0;
}
}
hr {
height: 0.25em;
padding: 0;
margin: 24px 0;
background-color: #e1e4e8;
border: 0;
}
h1 tt,
h1 code,
h2 tt,
h2 code,
h3 tt,
h3 code,
h4 tt,
h4 code,
h5 tt,
h5 code,
h6 tt,
h6 code {
font-size: inherit
}
h1 {
padding-bottom: 0.3em;
font-size: 2em;
border-bottom: 1px solid #eaecef
}
h2 {
padding-bottom: 0.3em;
font-size: 1.5em;
border-bottom: 1px solid #eaecef
}
h3 {
font-size: 1.25em
}
h4 {
font-size: 1em
}
h5 {
font-size: 0.875em
}
h6 {
font-size: 0.85em;
color: #6a737d
}
table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
&+table {
margin-top: 16px;
}
}
table th {
font-weight: 600;
white-space: nowrap;
color: #5c6b77;
background: rgba(0, 0, 0, .02);
}
table th,
table td {
padding: 8px 13px;
border: 1px solid #dfe2e5;
}
table tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
}
table tr:nth-child(2n) {
background-color: #f6f8fa;
}
table img {
background-color: transparent;
}
img {
max-width: 100%;
box-sizing: content-box;
background-color: #fff;
vertical-align: middle;
}
img[align=right] {
padding-left: 20px;
}
img[align=left] {
padding-right: 20px;
}
.comment,
.quote {
color: #998;
}
.keyword,
.selector-tag,
.subst {
color: #333;
font-weight: bold;
}
.number,
.literal,
.variable,
.template-variable,
.tag .attr {
color: #008080;
}
.string,
.doctag {
color: #d14;
}
.title,
.section,
.selector-id {
color: #900;
font-weight: bold;
}
.subst {
font-weight: normal;
}
.type,
.class .title {
color: #458;
font-weight: bold;
}
.tag,
.name,
.attribute {
color: #000098;
font-weight: normal;
}
.regexp,
.link {
color: #009926;
}
.symbol,
.bullet {
color: #990073;
}
.built_in,
.builtin-name {
color: #0086b3;
}
.meta {
color: #999;
font-weight: bold;
}
.deletion {
background: #fdd;
}
.addition {
background: #dfd;
}
.emphasis {
font-style: italic;
}
.strong {
font-weight: bold;
}
================================================
FILE: packages/crd-theme/markdown/style/diff.less
================================================
.language-diff {
}
================================================
FILE: packages/crd-theme/markdown/style/index.less
================================================
.markdown {
padding: 0 40px 20px 40px;
margin: 0 auto;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
color: #314659;
& >*:first-child {
margin-top: 0 !important
}
& >*:last-child {
margin-bottom: 0 !important
}
:global {
@import "./default.less";
@import "./xml.less";
@import "./swift.less";
@import "./diff.less";
@import "./css.less";
@import "./javascript.less";
}
}
.pageTitle {
font-size: 30px;
line-height: 38px;
color: #0d1a26;
font-weight: 500;
margin-bottom: 20px;
margin-top: 8px;
padding-left: 20px;
}
================================================
FILE: packages/crd-theme/markdown/style/javascript.less
================================================
.language-jsx, .language-js,.language-javascript{
.keyword, .selector-tag {
color: #b111bf;
font-weight: 600;
}
.subst {
color: #0000ca;
}
.title, .section {
color: #4e00dc;
font-weight: normal;
}
.string, .doctag {
color: #d73a49;
}
.attr {
color: #0000ca;
}
.class .keyword{
color: #008cd4;
}
.function {
color: #0000ca;
.params {
color: #008cd4;
}
}
}
.language-json {
color: #444;
.attr {
color: #0000ca;
}
}
================================================
FILE: packages/crd-theme/markdown/style/swift.less
================================================
.language-swift {
.keyword, .selector-tag, .subst {
color: #C600AA;
font-weight: normal;
}
.meta {
color: #C526D0;
font-weight: normal;
}
.type, .class .title {
color: #7C10B2;
font-weight: normal;
}
}
================================================
FILE: packages/crd-theme/markdown/style/xml.less
================================================
.language-html, .xml{
}
================================================
FILE: packages/crd-theme/package.json
================================================
{
"name": "crd-theme",
"version": "1.10.3",
"description": "Default Theme with Create React Doc",
"main": "index.js",
"dependencies": {
"@mdx-js/react": "^1.6.22",
"classnames": "^2.2.6",
"diana": "^1.0.2",
"less": "^3.0.2",
"prism-react-renderer": "^1.2.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-helmet": "^6.1.0",
"react-hot-loader": "^4.1.1",
"react-markdown": "^3.3.0"
},
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/theme"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199",
"devDependencies": {
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14"
}
}
================================================
FILE: packages/crd-utils/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
================================================
FILE: packages/crd-utils/README.md
================================================
### crd-utils
[create-react-doc](https://github.com/MuYunyun/create-react-doc) 的工具包。
================================================
FILE: packages/crd-utils/index.js
================================================
const fs = require('fs')
const yaml = require('js-yaml')
const { resolveApp, resolveTool } = require('./path')
/* avoid reference loop, so use resolveApp('config.yml') instead of refrence from paths. */
const docsConfig = resolveApp('config.yml')
/**
* get docs config, see https://github.com/nodeca/js-yaml/blob/2d1fbed8f3a76ff93cccb9a8a418b4c4a482d3d9/lib/js-yaml/loader.js#L1590-L1592
*/
const getDocsConfig = () => {
if (!fs.existsSync(docsConfig)) {
return null
}
return yaml.safeLoad(fs.readFileSync(docsConfig))
}
/**
* replace file content for Front-matter
* path: file path
* source?: source content
* target: target content
*/
const replaceForFrontMatter = ({
path,
source,
target
}) => {
fs.readFile(path, (err, data) => {
if (err) {
console.log(`❎ readFileContent error in ${path}`)
return
}
console.log(`✅ readFileContent success in ${path}`)
const replaceResult = source
? data.toString().replace(source, target)
: `${target}\n${data.toString()}`
fs.writeFile(path, replaceResult, (err) => {
if (err) {
console.log(`❎ writeFileContent error in ${path}`)
return
}
})
console.log(`✅ writeFileContent success in ${path}`)
})
}
// generate a random string, length of it is n.
const generateRandomId = (n) => {
const str = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
let res = ""
for (let i = 0; i < n; i++) {
const id = Math.ceil(Math.random() * 35)
res += str[id]
}
return res
}
module.exports = {
resolveApp,
resolveTool,
getDocsConfig,
replaceForFrontMatter,
generateRandomId,
// common paths
docsGitIgnore: resolveApp('.gitignore'),
docsBase: resolveApp(''),
docsConfig,
docsReadme: resolveApp('README.md'),
docsBuildDist: resolveApp('.crd-dist'),
cacheDirPath: resolveApp('.cache'),
searchFilePath: resolveApp('.cache/search.js'),
templatePath: resolveApp('node_modules/crd-templates'),
defaultHTMLPath: resolveApp('node_modules/crd-theme/index.html'),
}
================================================
FILE: packages/crd-utils/package.json
================================================
{
"name": "crd-utils",
"version": "1.5.0",
"description": "Utils with create react doc",
"main": "index.js",
"dependencies": {
"js-yaml": "^3.14.0"
},
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/utils"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199"
}
================================================
FILE: packages/crd-utils/path.js
================================================
const path = require('path')
const fs = require('fs')
// handle the problem of symbol in any platform
const appDirectory = fs.realpathSync(process.cwd())
// Markdown dir
const resolveApp = relativePath => path.resolve(appDirectory, relativePath)
module.exports = {
resolveApp,
}
================================================
FILE: packages/create-react-doc/.npmignore
================================================
.cache
.gitignore
.editorconfig
.create-react-doc-dist
node_modules
package-lock.json
dist
================================================
FILE: packages/create-react-doc/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
================================================
FILE: packages/create-react-doc/.yarnrc
================================================
# .yarnrc
registry "https://registry.npmjs.org/"
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site "https://npm.taobao.org/mirrors/node-sass/"
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl "http://cnpmjs.org/downloads"
phantomjs_cdnurl "https://npm.taobao.org/dist/phantomjs"
================================================
FILE: packages/create-react-doc/README.md
================================================
_.-"\
_.-" \
,-" \
\ create \
\ \ react \
\ \ doc \
\ \ _.-;
\ \ _.-" :
\ \,-" _.-"
\( _.-"
`--"
# react-doc
react-doc 是配合 [Create React Doc](https://github.com/MuYunyun/create-react-doc) 使用的脚手架,其内置如下命令。
```bash
$ react-doc doc // 在当前文件夹下创建名为 doc 的文档站点
$ react-doc start // 启动项目
$ react-doc build // 项目打包
$ react-doc deploy // 手动发布项目
$ react-doc theme // 创建新主题包
$ react-doc generate // 用于自动生成 Front-matter 中相关字段信息
```
# 调试
进入该目录, 执行 `yarn link`, 即可在全局使用 react-doc xxx 命令。
================================================
FILE: packages/create-react-doc/index.js
================================================
#!/usr/bin/env node
const fs = require('fs-extra')
const path = require('path')
const program = require('commander')
const {
initProject,
initTheme,
initCache,
Deploy,
Generate,
Servers,
Build,
} = require('crd-scripts')
const { docsReadme, docsBuildDist, getDocsConfig } = require('crd-utils')
const input = require('@inquirer/input')
const pkg = require('./package.json')
program
.version(pkg.version, '-v, --version')
.description('Markdown doc site generator for React.')
.option('start', 'Start the documents site in local env.')
.option('build', 'Build the documents generated.')
.option('deploy', 'Deploy site to gh-pages.')
.option('theme', 'Create a new theme')
.option('generate', 'Generate info in front-matter')
.option('-o, --output
', 'Writes the compiled file to the disk directory.', '.crd-dist')
.option('-p, --port [number]', 'The port.', 3000)
.option('--host [host]', 'The host.', '0.0.0.0')
.option('-b, --branch ', 'Name of the branch you are pushing to.', 'gh-pages')
.on('--help', () => {
console.log('\n Examples:')
console.log()
console.log(' $ react-doc xxx')
console.log(' $ react-doc start')
console.log(' $ react-doc build')
console.log(' $ react-doc deploy')
console.log(' $ react-doc theme')
console.log(' $ react-doc generate')
console.log()
})
// the third value in process.argv is the value we want.
.parse(process.argv)
const { start, build, deploy, theme, generate } = program
if (!start && !build && !deploy && !theme && !generate) return initProject(program)
if (theme) {
return input({
message: "What's the theme name?",
validate: (inputThemeName) => {
if (inputThemeName.length === 0) return 'Please input correct theme name'
return true
},
}).then((themeName) => {
initTheme(themeName)
})
}
// create-react-doc tool root dir.
// program.crdPath = path.join(__dirname, '../');
// all markdown dir
program.markdownPaths = []
// dir for output
program.output = path.join(process.cwd(), program.output)
const docsConfig = getDocsConfig()
// assign all the markdown dir
if (start || build || generate) {
fs.existsSync(docsReadme) &&
program.markdownPaths.push(docsReadme)
docsConfig &&
docsConfig.menu
.forEach(itemPath =>
program.markdownPaths.push(path.join(process.cwd(), itemPath))
)
}
if (build && fs.pathExistsSync(docsBuildDist)) {
// clean dir
fs.emptyDirSync(docsBuildDist)
}
// deploy code to special git repo and branch.
if (deploy) {
return Deploy(program, docsConfig)
}
// no point markdown paths
if (program.markdownPaths.length === 0) {
return console.log('❎ Please specify the markdownPaths props in config.yml.'.red)
}
let isExists = true
// judge if files exist.
program.markdownPaths.forEach((item) => {
if (!fs.existsSync(item)) {
isExists = false
}
})
if (!isExists) {
console.log(`❎ Error: Directory ${item.yellow} does not exist`.red)
return
}
if (generate) {
return Generate(program)
}
initCache(program, () => {
if (build) {
Build(program)
} else {
Servers(program)
}
})
================================================
FILE: packages/create-react-doc/package.json
================================================
{
"name": "create-react-doc",
"version": "1.10.3",
"description": "Fast static generated site. Just write markdown file.",
"homepage": "",
"bin": {
"react-doc": "./index.js"
},
"scripts": {},
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/create-react-doc"
},
"keywords": [
"react",
"create-react-doc",
"blog",
"markdown"
],
"author": "muyunyun",
"publishConfig": {
"access": "public"
},
"license": "MIT",
"dependencies": {
"@inquirer/input": "^0.0.13-alpha.0",
"commander": "^2.12.2",
"crd-scripts": "^1.10.3",
"crd-templates": "^1.10.0",
"crd-theme": "^1.10.3",
"fs-extra": "^5.0.0"
},
"gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199"
}
================================================
FILE: packages/leetcode-cli/.npmrc
================================================
# .npmrc
registry=https://registry.npmjs.org/
# https://github.com/sass/node-sass#binary-configuration-parameters
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# https://github.com/Medium/phantomjs#deciding-where-to-get-phantomjs
# phantomjs_cdnurl=http://cnpmjs.org/downloads
phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs
================================================
FILE: packages/leetcode-cli/README.md
================================================
### crd-leetcode-cli
crd-leetcode-cli 提供将 [leetcode](https://leetcode-cn.com/) 中已 AC 的题目转化为 markdown 表格的能力。
### Install
执行 `yarn add crd-leetcode-cli -g`, 国内用户可以执行 `cnpm install crd-leetcode-cli -g`
### Usage
```
leetcode download // 增量拉取 AC 题目(若无登录, 则会先执行登录逻辑)
leetcode download -a // 全量拉取 AC 题目
leetcode login // 登录
leetcode logout // 登出
```
> [接入项目示例](https://github.com/MuYunyun/blog/blob/main/package.json#L8-L9)
### Render Markdown Table Customly
插件提供了自定义渲染 markdown table 的能力。
* 在项目根目录创建 [config.js](https://github.com/MuYunyun/blog/blob/main/config.js) 文件。
* 在 config.js 内自定义生成 markdown 的 [transform_markdown_table](https://github.com/MuYunyun/blog/blob/main/config.js#L5-L22) 函数。
```js
const transform_markdown_table = (dataArr) => {
const beforeDescription = `The markdown table is generated by [crd-leetcode-cli](https://github.com/MuYunyun/create-react-doc/tree/main/packages/leetcode-cli)`;
let result = beforeDescription + '\n' +
'| # | Title | Explanation | Difficulty | Type |' +
'\n' +
'|:---:|:---:|:---:|:---:|:---:|';
for (let i = 0; i < dataArr.length; i++) {
result += `\n| ${dataArr[i].questionId} | [${dataArr[i].title
}](https://leetcode.com/problems/${dataArr[i].titleSlug
}/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/${dataArr[i].questionId
}.${dataArr[i].title.split(' ').join('_')}.md) | ${dataArr[i].difficulty
} | ${dataArr[i].topicTags} |`;
}
return result;
};
module.exports = { transform_markdown_table }
```
通过自定义 transform_markdown_table 函数, 便可得到如下 [markdown table](https://github.com/MuYunyun/blog/tree/main/LeetCode):

### Technology Details
* 使用 [puppeteer](https://github.com/puppeteer/puppeteer) 登录 leetcode 获取 cookie 信息。
* 
* 获取 cookie 后, 使用 [graphql-request](https://github.com/prisma-labs/graphql-request) 调用 graphql 接口获取题目详情信息。
* 自定义生成 [markdown table](https://github.com/MuYunyun/blog/tree/main/LeetCode)。
### Q & A
* 如何开发调试?
进入[项目目录](https://github.com/MuYunyun/create-react-doc/tree/main/packages/leetcode-cli), 执行上述 Usage 中的 leetcode 命令即可。
> 因为 [puppeteer](https://github.com/puppeteer/puppeteer/issues/6425) 已知问题, 暂时只支持 mac 系统使用, 后续更新。
================================================
FILE: packages/leetcode-cli/leetcode-table.md
================================================
| # | Title | Explanation | Difficulty | Type |
|:---:|:---:|:---:|:---:|:---:|
| 1 | [Two Sum](https://leetcode.com/problems/two-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/1.Two_Sum.md) | Easy | Array, Hash Table |
| 2 | [Add Two Numbers](https://leetcode.com/problems/add-two-numbers/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/2.Add_Two_Numbers.md) | Medium | Recursion, Linked List, Math |
| 3 | [Longest Substring Without Repeating Characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/3.Longest_Substring_Without_Repeating_Characters.md) | Medium | Hash Table, Two Pointers, String, Sliding Window |
| 4 | [Median of Two Sorted Arrays](https://leetcode.com/problems/median-of-two-sorted-arrays/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/4.Median_of_Two_Sorted_Arrays.md) | Hard | Array, Binary Search, Divide and Conquer |
| 7 | [Reverse Integer](https://leetcode.com/problems/reverse-integer/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/7.Reverse_Integer.md) | Easy | Math |
| 9 | [Palindrome Number](https://leetcode.com/problems/palindrome-number/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/9.Palindrome_Number.md) | Easy | Math |
| 11 | [Container With Most Water](https://leetcode.com/problems/container-with-most-water/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/11.Container_With_Most_Water.md) | Medium | Array, Two Pointers |
| 13 | [Roman to Integer](https://leetcode.com/problems/roman-to-integer/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/13.Roman_to_Integer.md) | Easy | Math, String |
| 14 | [Longest Common Prefix](https://leetcode.com/problems/longest-common-prefix/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/14.Longest_Common_Prefix.md) | Easy | String |
| 15 | [3Sum](https://leetcode.com/problems/3sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/15.3Sum.md) | Medium | Array, Two Pointers |
| 16 | [3Sum Closest](https://leetcode.com/problems/3sum-closest/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/16.3Sum_Closest.md) | Medium | Array, Two Pointers |
| 17 | [Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/17.Letter_Combinations_of_a_Phone_Number.md) | Medium | Depth-first Search, Recursion, String, Backtracking |
| 18 | [4Sum](https://leetcode.com/problems/4sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/18.4Sum.md) | Medium | Array, Hash Table, Two Pointers |
| 19 | [Remove Nth Node From End of List](https://leetcode.com/problems/remove-nth-node-from-end-of-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/19.Remove_Nth_Node_From_End_of_List.md) | Medium | Linked List, Two Pointers |
| 20 | [Valid Parentheses](https://leetcode.com/problems/valid-parentheses/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/20.Valid_Parentheses.md) | Easy | Stack, String |
| 21 | [Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/21.Merge_Two_Sorted_Lists.md) | Easy | Recursion, Linked List |
| 22 | [Generate Parentheses](https://leetcode.com/problems/generate-parentheses/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/22.Generate_Parentheses.md) | Medium | String, Backtracking |
| 23 | [Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/23.Merge_k_Sorted_Lists.md) | Hard | Heap, Linked List, Divide and Conquer |
| 24 | [Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/24.Swap_Nodes_in_Pairs.md) | Medium | Recursion, Linked List |
| 25 | [Reverse Nodes in k-Group](https://leetcode.com/problems/reverse-nodes-in-k-group/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/25.Reverse_Nodes_in_k-Group.md) | Hard | Linked List |
| 26 | [Remove Duplicates from Sorted Array](https://leetcode.com/problems/remove-duplicates-from-sorted-array/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/26.Remove_Duplicates_from_Sorted_Array.md) | Easy | Array, Two Pointers |
| 27 | [Remove Element](https://leetcode.com/problems/remove-element/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/27.Remove_Element.md) | Easy | Array, Two Pointers |
| 28 | [Implement strStr()](https://leetcode.com/problems/implement-strstr/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/28.Implement_strStr().md) | Easy | Two Pointers, String |
| 33 | [Search in Rotated Sorted Array](https://leetcode.com/problems/search-in-rotated-sorted-array/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/33.Search_in_Rotated_Sorted_Array.md) | Medium | Array, Binary Search |
| 35 | [Search Insert Position](https://leetcode.com/problems/search-insert-position/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/35.Search_Insert_Position.md) | Easy | Array, Binary Search |
| 36 | [Valid Sudoku](https://leetcode.com/problems/valid-sudoku/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/36.Valid_Sudoku.md) | Medium | Hash Table |
| 37 | [Sudoku Solver](https://leetcode.com/problems/sudoku-solver/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/37.Sudoku_Solver.md) | Hard | Hash Table, Backtracking |
| 38 | [Count and Say](https://leetcode.com/problems/count-and-say/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/38.Count_and_Say.md) | Easy | String |
| 39 | [Combination Sum](https://leetcode.com/problems/combination-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/39.Combination_Sum.md) | Medium | Array, Backtracking |
| 40 | [Combination Sum II](https://leetcode.com/problems/combination-sum-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/40.Combination_Sum_II.md) | Medium | Array, Backtracking |
| 46 | [Permutations](https://leetcode.com/problems/permutations/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/46.Permutations.md) | Medium | Backtracking |
| 47 | [Permutations II](https://leetcode.com/problems/permutations-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/47.Permutations_II.md) | Medium | Backtracking |
| 48 | [Rotate Image](https://leetcode.com/problems/rotate-image/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/48.Rotate_Image.md) | Medium | Array |
| 49 | [Group Anagrams](https://leetcode.com/problems/group-anagrams/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/49.Group_Anagrams.md) | Medium | Hash Table, String |
| 50 | [Pow(x, n)](https://leetcode.com/problems/powx-n/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/50.Pow(x,_n).md) | Medium | Math, Binary Search |
| 51 | [N-Queens](https://leetcode.com/problems/n-queens/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/51.N-Queens.md) | Hard | Backtracking |
| 52 | [N-Queens II](https://leetcode.com/problems/n-queens-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/52.N-Queens_II.md) | Hard | Backtracking |
| 61 | [Rotate List](https://leetcode.com/problems/rotate-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/61.Rotate_List.md) | Medium | Linked List, Two Pointers |
| 62 | [Unique Paths](https://leetcode.com/problems/unique-paths/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/62.Unique_Paths.md) | Medium | Array, Dynamic Programming |
| 64 | [Minimum Path Sum](https://leetcode.com/problems/minimum-path-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/64.Minimum_Path_Sum.md) | Medium | Array, Dynamic Programming |
| 66 | [Plus One](https://leetcode.com/problems/plus-one/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/66.Plus_One.md) | Easy | Array |
| 67 | [Add Binary](https://leetcode.com/problems/add-binary/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/67.Add_Binary.md) | Easy | Math, String |
| 69 | [Sqrt(x)](https://leetcode.com/problems/sqrtx/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/69.Sqrt(x).md) | Easy | Math, Binary Search |
| 70 | [Climbing Stairs](https://leetcode.com/problems/climbing-stairs/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/70.Climbing_Stairs.md) | Easy | Dynamic Programming |
| 71 | [Simplify Path](https://leetcode.com/problems/simplify-path/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/71.Simplify_Path.md) | Medium | Stack, String |
| 75 | [Sort Colors](https://leetcode.com/problems/sort-colors/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/75.Sort_Colors.md) | Medium | Sort, Array, Two Pointers |
| 77 | [Combinations](https://leetcode.com/problems/combinations/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/77.Combinations.md) | Medium | Backtracking |
| 78 | [Subsets](https://leetcode.com/problems/subsets/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/78.Subsets.md) | Medium | Bit Manipulation, Array, Backtracking |
| 79 | [Word Search](https://leetcode.com/problems/word-search/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/79.Word_Search.md) | Medium | Array, Backtracking |
| 80 | [Remove Duplicates from Sorted Array II](https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/80.Remove_Duplicates_from_Sorted_Array_II.md) | Medium | Array, Two Pointers |
| 82 | [Remove Duplicates from Sorted List II](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/82.Remove_Duplicates_from_Sorted_List_II.md) | Medium | Linked List |
| 83 | [Remove Duplicates from Sorted List](https://leetcode.com/problems/remove-duplicates-from-sorted-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/83.Remove_Duplicates_from_Sorted_List.md) | Easy | Linked List |
| 86 | [Partition List](https://leetcode.com/problems/partition-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/86.Partition_List.md) | Medium | Linked List, Two Pointers |
| 88 | [Merge Sorted Array](https://leetcode.com/problems/merge-sorted-array/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/88.Merge_Sorted_Array.md) | Easy | Array, Two Pointers |
| 89 | [Gray Code](https://leetcode.com/problems/gray-code/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/89.Gray_Code.md) | Medium | Backtracking |
| 90 | [Subsets II](https://leetcode.com/problems/subsets-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/90.Subsets_II.md) | Medium | Array, Backtracking |
| 92 | [Reverse Linked List II](https://leetcode.com/problems/reverse-linked-list-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/92.Reverse_Linked_List_II.md) | Medium | Linked List |
| 93 | [Restore IP Addresses](https://leetcode.com/problems/restore-ip-addresses/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/93.Restore_IP_Addresses.md) | Medium | String, Backtracking |
| 94 | [Binary Tree Inorder Traversal](https://leetcode.com/problems/binary-tree-inorder-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/94.Binary_Tree_Inorder_Traversal.md) | Medium | Stack, Tree, Hash Table |
| 98 | [Validate Binary Search Tree](https://leetcode.com/problems/validate-binary-search-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/98.Validate_Binary_Search_Tree.md) | Medium | Tree, Depth-first Search, Recursion |
| 100 | [Same Tree](https://leetcode.com/problems/same-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/100.Same_Tree.md) | Easy | Tree, Depth-first Search |
| 101 | [Symmetric Tree](https://leetcode.com/problems/symmetric-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/101.Symmetric_Tree.md) | Easy | Tree, Depth-first Search, Breadth-first Search |
| 102 | [Binary Tree Level Order Traversal](https://leetcode.com/problems/binary-tree-level-order-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/102.Binary_Tree_Level_Order_Traversal.md) | Medium | Tree, Breadth-first Search |
| 103 | [Binary Tree Zigzag Level Order Traversal](https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/103.Binary_Tree_Zigzag_Level_Order_Traversal.md) | Medium | Stack, Tree, Breadth-first Search |
| 104 | [Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/104.Maximum_Depth_of_Binary_Tree.md) | Easy | Tree, Depth-first Search, Recursion |
| 107 | [Binary Tree Level Order Traversal II](https://leetcode.com/problems/binary-tree-level-order-traversal-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/107.Binary_Tree_Level_Order_Traversal_II.md) | Medium | Tree, Breadth-first Search |
| 108 | [Convert Sorted Array to Binary Search Tree](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/108.Convert_Sorted_Array_to_Binary_Search_Tree.md) | Easy | Tree, Depth-first Search |
| 110 | [Balanced Binary Tree](https://leetcode.com/problems/balanced-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/110.Balanced_Binary_Tree.md) | Easy | Tree, Depth-first Search, Recursion |
| 111 | [Minimum Depth of Binary Tree](https://leetcode.com/problems/minimum-depth-of-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/111.Minimum_Depth_of_Binary_Tree.md) | Easy | Tree, Depth-first Search, Breadth-first Search |
| 112 | [Path Sum](https://leetcode.com/problems/path-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/112.Path_Sum.md) | Easy | Tree, Depth-first Search |
| 113 | [Path Sum II](https://leetcode.com/problems/path-sum-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/113.Path_Sum_II.md) | Medium | Tree, Depth-first Search |
| 120 | [Triangle](https://leetcode.com/problems/triangle/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/120.Triangle.md) | Medium | Array, Dynamic Programming |
| 121 | [Best Time to Buy and Sell Stock](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/121.Best_Time_to_Buy_and_Sell_Stock.md) | Easy | Array, Dynamic Programming |
| 125 | [Valid Palindrome](https://leetcode.com/problems/valid-palindrome/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/125.Valid_Palindrome.md) | Easy | Two Pointers, String |
| 127 | [Word Ladder](https://leetcode.com/problems/word-ladder/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/127.Word_Ladder.md) | Hard | Breadth-first Search |
| 129 | [Sum Root to Leaf Numbers](https://leetcode.com/problems/sum-root-to-leaf-numbers/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/129.Sum_Root_to_Leaf_Numbers.md) | Medium | Tree, Depth-first Search |
| 130 | [Surrounded Regions](https://leetcode.com/problems/surrounded-regions/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/130.Surrounded_Regions.md) | Medium | Depth-first Search, Breadth-first Search, Union Find |
| 131 | [Palindrome Partitioning](https://leetcode.com/problems/palindrome-partitioning/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/131.Palindrome_Partitioning.md) | Medium | Depth-first Search, Dynamic Programming, Backtracking |
| 143 | [Reorder List](https://leetcode.com/problems/reorder-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/143.Reorder_List.md) | Medium | Linked List |
| 144 | [Binary Tree Preorder Traversal](https://leetcode.com/problems/binary-tree-preorder-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/144.Binary_Tree_Preorder_Traversal.md) | Medium | Stack, Tree |
| 145 | [Binary Tree Postorder Traversal](https://leetcode.com/problems/binary-tree-postorder-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/145.Binary_Tree_Postorder_Traversal.md) | Medium | Stack, Tree |
| 147 | [Insertion Sort List](https://leetcode.com/problems/insertion-sort-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/147.Insertion_Sort_List.md) | Medium | Sort, Linked List |
| 148 | [Sort List](https://leetcode.com/problems/sort-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/148.Sort_List.md) | Medium | Sort, Linked List |
| 149 | [Max Points on a Line](https://leetcode.com/problems/max-points-on-a-line/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/149.Max_Points_on_a_Line.md) | Hard | Hash Table, Math |
| 150 | [Evaluate Reverse Polish Notation](https://leetcode.com/problems/evaluate-reverse-polish-notation/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/150.Evaluate_Reverse_Polish_Notation.md) | Medium | Stack |
| 151 | [Reverse Words in a String](https://leetcode.com/problems/reverse-words-in-a-string/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/151.Reverse_Words_in_a_String.md) | Medium | String |
| 167 | [Two Sum II - Input array is sorted](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/167.Two_Sum_II_-_Input_array_is_sorted.md) | Easy | Array, Two Pointers, Binary Search |
| 199 | [Binary Tree Right Side View](https://leetcode.com/problems/binary-tree-right-side-view/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/199.Binary_Tree_Right_Side_View.md) | Medium | Tree, Depth-first Search, Breadth-first Search, Recursion, Queue |
| 200 | [Number of Islands](https://leetcode.com/problems/number-of-islands/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/200.Number_of_Islands.md) | Medium | Depth-first Search, Breadth-first Search, Union Find |
| 202 | [Happy Number](https://leetcode.com/problems/happy-number/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/202.Happy_Number.md) | Easy | Hash Table, Math |
| 203 | [Remove Linked List Elements](https://leetcode.com/problems/remove-linked-list-elements/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/203.Remove_Linked_List_Elements.md) | Easy | Linked List |
| 205 | [Isomorphic Strings](https://leetcode.com/problems/isomorphic-strings/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/205.Isomorphic_Strings.md) | Easy | Hash Table |
| 206 | [Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/206.Reverse_Linked_List.md) | Easy | Linked List |
| 209 | [Minimum Size Subarray Sum](https://leetcode.com/problems/minimum-size-subarray-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/209.Minimum_Size_Subarray_Sum.md) | Medium | Array, Two Pointers, Binary Search |
| 215 | [Kth Largest Element in an Array](https://leetcode.com/problems/kth-largest-element-in-an-array/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/215.Kth_Largest_Element_in_an_Array.md) | Medium | Heap, Divide and Conquer |
| 216 | [Combination Sum III](https://leetcode.com/problems/combination-sum-iii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/216.Combination_Sum_III.md) | Medium | Array, Backtracking |
| 217 | [Contains Duplicate](https://leetcode.com/problems/contains-duplicate/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/217.Contains_Duplicate.md) | Easy | Array, Hash Table |
| 219 | [Contains Duplicate II](https://leetcode.com/problems/contains-duplicate-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/219.Contains_Duplicate_II.md) | Easy | Array, Hash Table |
| 220 | [Contains Duplicate III](https://leetcode.com/problems/contains-duplicate-iii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/220.Contains_Duplicate_III.md) | Medium | Sort, Ordered Map |
| 222 | [Count Complete Tree Nodes](https://leetcode.com/problems/count-complete-tree-nodes/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/222.Count_Complete_Tree_Nodes.md) | Medium | Tree, Binary Search |
| 226 | [Invert Binary Tree](https://leetcode.com/problems/invert-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/226.Invert_Binary_Tree.md) | Easy | Tree |
| 230 | [Kth Smallest Element in a BST](https://leetcode.com/problems/kth-smallest-element-in-a-bst/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/230.Kth_Smallest_Element_in_a_BST.md) | Medium | Tree, Binary Search |
| 234 | [Palindrome Linked List](https://leetcode.com/problems/palindrome-linked-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/234.Palindrome_Linked_List.md) | Easy | Linked List, Two Pointers |
| 235 | [Lowest Common Ancestor of a Binary Search Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/235.Lowest_Common_Ancestor_of_a_Binary_Search_Tree.md) | Easy | Tree |
| 236 | [Lowest Common Ancestor of a Binary Tree](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/236.Lowest_Common_Ancestor_of_a_Binary_Tree.md) | Medium | Tree |
| 237 | [Delete Node in a Linked List](https://leetcode.com/problems/delete-node-in-a-linked-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/237.Delete_Node_in_a_Linked_List.md) | Easy | Linked List |
| 242 | [Valid Anagram](https://leetcode.com/problems/valid-anagram/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/242.Valid_Anagram.md) | Easy | Sort, Hash Table |
| 257 | [Binary Tree Paths](https://leetcode.com/problems/binary-tree-paths/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/257.Binary_Tree_Paths.md) | Easy | Tree, Depth-first Search |
| 279 | [Perfect Squares](https://leetcode.com/problems/perfect-squares/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/279.Perfect_Squares.md) | Medium | Breadth-first Search, Math, Dynamic Programming |
| 283 | [Move Zeroes](https://leetcode.com/problems/move-zeroes/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/283.Move_Zeroes.md) | Easy | Array, Two Pointers |
| 290 | [Word Pattern](https://leetcode.com/problems/word-pattern/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/290.Word_Pattern.md) | Easy | Hash Table |
| 328 | [Odd Even Linked List](https://leetcode.com/problems/odd-even-linked-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/328.Odd_Even_Linked_List.md) | Medium | Linked List |
| 341 | [Flatten Nested List Iterator](https://leetcode.com/problems/flatten-nested-list-iterator/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/341.Flatten_Nested_List_Iterator.md) | Medium | Stack, Design |
| 344 | [Reverse String](https://leetcode.com/problems/reverse-string/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/344.Reverse_String.md) | Easy | Two Pointers, String |
| 347 | [Top K Frequent Elements](https://leetcode.com/problems/top-k-frequent-elements/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/347.Top_K_Frequent_Elements.md) | Medium | Heap, Hash Table |
| 349 | [Intersection of Two Arrays](https://leetcode.com/problems/intersection-of-two-arrays/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/349.Intersection_of_Two_Arrays.md) | Easy | Sort, Hash Table, Two Pointers, Binary Search |
| 350 | [Intersection of Two Arrays II](https://leetcode.com/problems/intersection-of-two-arrays-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/350.Intersection_of_Two_Arrays_II.md) | Easy | Sort, Hash Table, Two Pointers, Binary Search |
| 401 | [Binary Watch](https://leetcode.com/problems/binary-watch/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/401.Binary_Watch.md) | Easy | Bit Manipulation, Backtracking |
| 404 | [Sum of Left Leaves](https://leetcode.com/problems/sum-of-left-leaves/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/404.Sum_of_Left_Leaves.md) | Easy | Tree |
| 417 | [Pacific Atlantic Water Flow](https://leetcode.com/problems/pacific-atlantic-water-flow/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/417.Pacific_Atlantic_Water_Flow.md) | Medium | Depth-first Search, Breadth-first Search |
| 437 | [Path Sum III](https://leetcode.com/problems/path-sum-iii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/437.Path_Sum_III.md) | Medium | Tree |
| 438 | [Find All Anagrams in a String](https://leetcode.com/problems/find-all-anagrams-in-a-string/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/438.Find_All_Anagrams_in_a_String.md) | Medium | Hash Table |
| 445 | [Add Two Numbers II](https://leetcode.com/problems/add-two-numbers-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/445.Add_Two_Numbers_II.md) | Medium | Linked List |
| 447 | [Number of Boomerangs](https://leetcode.com/problems/number-of-boomerangs/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/447.Number_of_Boomerangs.md) | Medium | Hash Table, Math |
| 450 | [Delete Node in a BST](https://leetcode.com/problems/delete-node-in-a-bst/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/450.Delete_Node_in_a_BST.md) | Medium | Tree |
| 451 | [Sort Characters By Frequency](https://leetcode.com/problems/sort-characters-by-frequency/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/451.Sort_Characters_By_Frequency.md) | Medium | Heap, Hash Table |
| 454 | [4Sum II](https://leetcode.com/problems/4sum-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/454.4Sum_II.md) | Medium | Hash Table, Binary Search |
| 567 | [Permutation in String](https://leetcode.com/problems/permutation-in-string/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/567.Permutation_in_String.md) | Medium | Two Pointers, Sliding Window |
================================================
FILE: packages/leetcode-cli/package.json
================================================
{
"name": "crd-leetcode-cli",
"version": "1.10.2",
"description": "Generate leetcode table autoly",
"bin": {
"leetcode": "./src/cli.js"
},
"dependencies": {
"axios": "^0.21.1",
"commander": "^2.20.0",
"crd-utils": "^1.5.0",
"enquirer": "^2.3.0",
"graphql-request": "^1.8.2",
"inquirer": "^7.3.3",
"node-fs-extra": "^0.8.2",
"ora": "^3.4.0",
"puppeteer": "^5.3.0",
"puppeteer-extra": "^3.1.15",
"puppeteer-extra-plugin-stealth": "^2.6.1",
"request": "^2.88.0",
"shelljs": "^0.8.3"
},
"repository": {
"type": "git",
"url": "https://github.com/MuYunyun/create-react-doc",
"directory": "packages/leetcode"
},
"keywords": [],
"publishConfig": {
"access": "public"
},
"author": "muyunyun",
"license": "MIT",
"gitHead": "d8a8dff5697da28623361231bd9b3d848d050e8b"
}
================================================
FILE: packages/leetcode-cli/problems.json
================================================
[
{
"title": "Two Sum",
"titleSlug": "two-sum",
"status": "ac",
"difficulty": "Easy",
"questionId": "1",
"topicTags": "Array, Hash Table"
},
{
"title": "Add Two Numbers",
"titleSlug": "add-two-numbers",
"status": "ac",
"difficulty": "Medium",
"questionId": "2",
"topicTags": "Recursion, Linked List, Math"
},
{
"title": "Longest Substring Without Repeating Characters",
"titleSlug": "longest-substring-without-repeating-characters",
"status": "ac",
"difficulty": "Medium",
"questionId": "3",
"topicTags": "Hash Table, Two Pointers, String, Sliding Window"
},
{
"title": "Median of Two Sorted Arrays",
"titleSlug": "median-of-two-sorted-arrays",
"status": "ac",
"difficulty": "Hard",
"questionId": "4",
"topicTags": "Array, Binary Search, Divide and Conquer"
},
{
"title": "Reverse Integer",
"titleSlug": "reverse-integer",
"status": "ac",
"difficulty": "Easy",
"questionId": "7",
"topicTags": "Math"
},
{
"title": "Palindrome Number",
"titleSlug": "palindrome-number",
"status": "ac",
"difficulty": "Easy",
"questionId": "9",
"topicTags": "Math"
},
{
"title": "Container With Most Water",
"titleSlug": "container-with-most-water",
"status": "ac",
"difficulty": "Medium",
"questionId": "11",
"topicTags": "Array, Two Pointers"
},
{
"title": "Roman to Integer",
"titleSlug": "roman-to-integer",
"status": "ac",
"difficulty": "Easy",
"questionId": "13",
"topicTags": "Math, String"
},
{
"title": "Longest Common Prefix",
"titleSlug": "longest-common-prefix",
"status": "ac",
"difficulty": "Easy",
"questionId": "14",
"topicTags": "String"
},
{
"title": "3Sum",
"titleSlug": "3sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "15",
"topicTags": "Array, Two Pointers"
},
{
"title": "3Sum Closest",
"titleSlug": "3sum-closest",
"status": "ac",
"difficulty": "Medium",
"questionId": "16",
"topicTags": "Array, Two Pointers"
},
{
"title": "Letter Combinations of a Phone Number",
"titleSlug": "letter-combinations-of-a-phone-number",
"status": "ac",
"difficulty": "Medium",
"questionId": "17",
"topicTags": "Depth-first Search, Recursion, String, Backtracking"
},
{
"title": "4Sum",
"titleSlug": "4sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "18",
"topicTags": "Array, Hash Table, Two Pointers"
},
{
"title": "Remove Nth Node From End of List",
"titleSlug": "remove-nth-node-from-end-of-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "19",
"topicTags": "Linked List, Two Pointers"
},
{
"title": "Valid Parentheses",
"titleSlug": "valid-parentheses",
"status": "ac",
"difficulty": "Easy",
"questionId": "20",
"topicTags": "Stack, String"
},
{
"title": "Merge Two Sorted Lists",
"titleSlug": "merge-two-sorted-lists",
"status": "ac",
"difficulty": "Easy",
"questionId": "21",
"topicTags": "Recursion, Linked List"
},
{
"title": "Generate Parentheses",
"titleSlug": "generate-parentheses",
"status": "ac",
"difficulty": "Medium",
"questionId": "22",
"topicTags": "String, Backtracking"
},
{
"title": "Merge k Sorted Lists",
"titleSlug": "merge-k-sorted-lists",
"status": "ac",
"difficulty": "Hard",
"questionId": "23",
"topicTags": "Heap, Linked List, Divide and Conquer"
},
{
"title": "Swap Nodes in Pairs",
"titleSlug": "swap-nodes-in-pairs",
"status": "ac",
"difficulty": "Medium",
"questionId": "24",
"topicTags": "Recursion, Linked List"
},
{
"title": "Reverse Nodes in k-Group",
"titleSlug": "reverse-nodes-in-k-group",
"status": "ac",
"difficulty": "Hard",
"questionId": "25",
"topicTags": "Linked List"
},
{
"title": "Remove Duplicates from Sorted Array",
"titleSlug": "remove-duplicates-from-sorted-array",
"status": "ac",
"difficulty": "Easy",
"questionId": "26",
"topicTags": "Array, Two Pointers"
},
{
"title": "Remove Element",
"titleSlug": "remove-element",
"status": "ac",
"difficulty": "Easy",
"questionId": "27",
"topicTags": "Array, Two Pointers"
},
{
"title": "Implement strStr()",
"titleSlug": "implement-strstr",
"status": "ac",
"difficulty": "Easy",
"questionId": "28",
"topicTags": "Two Pointers, String"
},
{
"title": "Search in Rotated Sorted Array",
"titleSlug": "search-in-rotated-sorted-array",
"status": "ac",
"difficulty": "Medium",
"questionId": "33",
"topicTags": "Array, Binary Search"
},
{
"title": "Search Insert Position",
"titleSlug": "search-insert-position",
"status": "ac",
"difficulty": "Easy",
"questionId": "35",
"topicTags": "Array, Binary Search"
},
{
"title": "Valid Sudoku",
"titleSlug": "valid-sudoku",
"status": "ac",
"difficulty": "Medium",
"questionId": "36",
"topicTags": "Hash Table"
},
{
"title": "Sudoku Solver",
"titleSlug": "sudoku-solver",
"status": "ac",
"difficulty": "Hard",
"questionId": "37",
"topicTags": "Hash Table, Backtracking"
},
{
"title": "Count and Say",
"titleSlug": "count-and-say",
"status": "ac",
"difficulty": "Easy",
"questionId": "38",
"topicTags": "String"
},
{
"title": "Combination Sum",
"titleSlug": "combination-sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "39",
"topicTags": "Array, Backtracking"
},
{
"title": "Combination Sum II",
"titleSlug": "combination-sum-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "40",
"topicTags": "Array, Backtracking"
},
{
"title": "Permutations",
"titleSlug": "permutations",
"status": "ac",
"difficulty": "Medium",
"questionId": "46",
"topicTags": "Backtracking"
},
{
"title": "Permutations II",
"titleSlug": "permutations-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "47",
"topicTags": "Backtracking"
},
{
"title": "Rotate Image",
"titleSlug": "rotate-image",
"status": "ac",
"difficulty": "Medium",
"questionId": "48",
"topicTags": "Array"
},
{
"title": "Group Anagrams",
"titleSlug": "group-anagrams",
"status": "ac",
"difficulty": "Medium",
"questionId": "49",
"topicTags": "Hash Table, String"
},
{
"title": "Pow(x, n)",
"titleSlug": "powx-n",
"status": "ac",
"difficulty": "Medium",
"questionId": "50",
"topicTags": "Math, Binary Search"
},
{
"title": "N-Queens",
"titleSlug": "n-queens",
"status": "ac",
"difficulty": "Hard",
"questionId": "51",
"topicTags": "Backtracking"
},
{
"title": "N-Queens II",
"titleSlug": "n-queens-ii",
"status": "ac",
"difficulty": "Hard",
"questionId": "52",
"topicTags": "Backtracking"
},
{
"title": "Rotate List",
"titleSlug": "rotate-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "61",
"topicTags": "Linked List, Two Pointers"
},
{
"title": "Unique Paths",
"titleSlug": "unique-paths",
"status": "ac",
"difficulty": "Medium",
"questionId": "62",
"topicTags": "Array, Dynamic Programming"
},
{
"title": "Minimum Path Sum",
"titleSlug": "minimum-path-sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "64",
"topicTags": "Array, Dynamic Programming"
},
{
"title": "Plus One",
"titleSlug": "plus-one",
"status": "ac",
"difficulty": "Easy",
"questionId": "66",
"topicTags": "Array"
},
{
"title": "Add Binary",
"titleSlug": "add-binary",
"status": "ac",
"difficulty": "Easy",
"questionId": "67",
"topicTags": "Math, String"
},
{
"title": "Sqrt(x)",
"titleSlug": "sqrtx",
"status": "ac",
"difficulty": "Easy",
"questionId": "69",
"topicTags": "Math, Binary Search"
},
{
"title": "Climbing Stairs",
"titleSlug": "climbing-stairs",
"status": "ac",
"difficulty": "Easy",
"questionId": "70",
"topicTags": "Dynamic Programming"
},
{
"title": "Simplify Path",
"titleSlug": "simplify-path",
"status": "ac",
"difficulty": "Medium",
"questionId": "71",
"topicTags": "Stack, String"
},
{
"title": "Sort Colors",
"titleSlug": "sort-colors",
"status": "ac",
"difficulty": "Medium",
"questionId": "75",
"topicTags": "Sort, Array, Two Pointers"
},
{
"title": "Combinations",
"titleSlug": "combinations",
"status": "ac",
"difficulty": "Medium",
"questionId": "77",
"topicTags": "Backtracking"
},
{
"title": "Subsets",
"titleSlug": "subsets",
"status": "ac",
"difficulty": "Medium",
"questionId": "78",
"topicTags": "Bit Manipulation, Array, Backtracking"
},
{
"title": "Word Search",
"titleSlug": "word-search",
"status": "ac",
"difficulty": "Medium",
"questionId": "79",
"topicTags": "Array, Backtracking"
},
{
"title": "Remove Duplicates from Sorted Array II",
"titleSlug": "remove-duplicates-from-sorted-array-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "80",
"topicTags": "Array, Two Pointers"
},
{
"title": "Remove Duplicates from Sorted List II",
"titleSlug": "remove-duplicates-from-sorted-list-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "82",
"topicTags": "Linked List"
},
{
"title": "Remove Duplicates from Sorted List",
"titleSlug": "remove-duplicates-from-sorted-list",
"status": "ac",
"difficulty": "Easy",
"questionId": "83",
"topicTags": "Linked List"
},
{
"title": "Partition List",
"titleSlug": "partition-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "86",
"topicTags": "Linked List, Two Pointers"
},
{
"title": "Merge Sorted Array",
"titleSlug": "merge-sorted-array",
"status": "ac",
"difficulty": "Easy",
"questionId": "88",
"topicTags": "Array, Two Pointers"
},
{
"title": "Gray Code",
"titleSlug": "gray-code",
"status": "ac",
"difficulty": "Medium",
"questionId": "89",
"topicTags": "Backtracking"
},
{
"title": "Subsets II",
"titleSlug": "subsets-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "90",
"topicTags": "Array, Backtracking"
},
{
"title": "Reverse Linked List II",
"titleSlug": "reverse-linked-list-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "92",
"topicTags": "Linked List"
},
{
"title": "Restore IP Addresses",
"titleSlug": "restore-ip-addresses",
"status": "ac",
"difficulty": "Medium",
"questionId": "93",
"topicTags": "String, Backtracking"
},
{
"title": "Binary Tree Inorder Traversal",
"titleSlug": "binary-tree-inorder-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "94",
"topicTags": "Stack, Tree, Hash Table"
},
{
"title": "Validate Binary Search Tree",
"titleSlug": "validate-binary-search-tree",
"status": "ac",
"difficulty": "Medium",
"questionId": "98",
"topicTags": "Tree, Depth-first Search, Recursion"
},
{
"title": "Same Tree",
"titleSlug": "same-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "100",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Symmetric Tree",
"titleSlug": "symmetric-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "101",
"topicTags": "Tree, Depth-first Search, Breadth-first Search"
},
{
"title": "Binary Tree Level Order Traversal",
"titleSlug": "binary-tree-level-order-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "102",
"topicTags": "Tree, Breadth-first Search"
},
{
"title": "Binary Tree Zigzag Level Order Traversal",
"titleSlug": "binary-tree-zigzag-level-order-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "103",
"topicTags": "Stack, Tree, Breadth-first Search"
},
{
"title": "Maximum Depth of Binary Tree",
"titleSlug": "maximum-depth-of-binary-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "104",
"topicTags": "Tree, Depth-first Search, Recursion"
},
{
"title": "Binary Tree Level Order Traversal II",
"titleSlug": "binary-tree-level-order-traversal-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "107",
"topicTags": "Tree, Breadth-first Search"
},
{
"title": "Convert Sorted Array to Binary Search Tree",
"titleSlug": "convert-sorted-array-to-binary-search-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "108",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Balanced Binary Tree",
"titleSlug": "balanced-binary-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "110",
"topicTags": "Tree, Depth-first Search, Recursion"
},
{
"title": "Minimum Depth of Binary Tree",
"titleSlug": "minimum-depth-of-binary-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "111",
"topicTags": "Tree, Depth-first Search, Breadth-first Search"
},
{
"title": "Path Sum",
"titleSlug": "path-sum",
"status": "ac",
"difficulty": "Easy",
"questionId": "112",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Path Sum II",
"titleSlug": "path-sum-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "113",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Triangle",
"titleSlug": "triangle",
"status": "ac",
"difficulty": "Medium",
"questionId": "120",
"topicTags": "Array, Dynamic Programming"
},
{
"title": "Best Time to Buy and Sell Stock",
"titleSlug": "best-time-to-buy-and-sell-stock",
"status": "ac",
"difficulty": "Easy",
"questionId": "121",
"topicTags": "Array, Dynamic Programming"
},
{
"title": "Valid Palindrome",
"titleSlug": "valid-palindrome",
"status": "ac",
"difficulty": "Easy",
"questionId": "125",
"topicTags": "Two Pointers, String"
},
{
"title": "Word Ladder",
"titleSlug": "word-ladder",
"status": "ac",
"difficulty": "Hard",
"questionId": "127",
"topicTags": "Breadth-first Search"
},
{
"title": "Sum Root to Leaf Numbers",
"titleSlug": "sum-root-to-leaf-numbers",
"status": "ac",
"difficulty": "Medium",
"questionId": "129",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Surrounded Regions",
"titleSlug": "surrounded-regions",
"status": "ac",
"difficulty": "Medium",
"questionId": "130",
"topicTags": "Depth-first Search, Breadth-first Search, Union Find"
},
{
"title": "Palindrome Partitioning",
"titleSlug": "palindrome-partitioning",
"status": "ac",
"difficulty": "Medium",
"questionId": "131",
"topicTags": "Depth-first Search, Dynamic Programming, Backtracking"
},
{
"title": "Reorder List",
"titleSlug": "reorder-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "143",
"topicTags": "Linked List"
},
{
"title": "Binary Tree Preorder Traversal",
"titleSlug": "binary-tree-preorder-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "144",
"topicTags": "Stack, Tree"
},
{
"title": "Binary Tree Postorder Traversal",
"titleSlug": "binary-tree-postorder-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "145",
"topicTags": "Stack, Tree"
},
{
"title": "Insertion Sort List",
"titleSlug": "insertion-sort-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "147",
"topicTags": "Sort, Linked List"
},
{
"title": "Sort List",
"titleSlug": "sort-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "148",
"topicTags": "Sort, Linked List"
},
{
"title": "Max Points on a Line",
"titleSlug": "max-points-on-a-line",
"status": "ac",
"difficulty": "Hard",
"questionId": "149",
"topicTags": "Hash Table, Math"
},
{
"title": "Evaluate Reverse Polish Notation",
"titleSlug": "evaluate-reverse-polish-notation",
"status": "ac",
"difficulty": "Medium",
"questionId": "150",
"topicTags": "Stack"
},
{
"title": "Reverse Words in a String",
"titleSlug": "reverse-words-in-a-string",
"status": "ac",
"difficulty": "Medium",
"questionId": "151",
"topicTags": "String"
},
{
"title": "Two Sum II - Input array is sorted",
"titleSlug": "two-sum-ii-input-array-is-sorted",
"status": "ac",
"difficulty": "Easy",
"questionId": "167",
"topicTags": "Array, Two Pointers, Binary Search"
},
{
"title": "Binary Tree Right Side View",
"titleSlug": "binary-tree-right-side-view",
"status": "ac",
"difficulty": "Medium",
"questionId": "199",
"topicTags": "Tree, Depth-first Search, Breadth-first Search, Recursion, Queue"
},
{
"title": "Number of Islands",
"titleSlug": "number-of-islands",
"status": "ac",
"difficulty": "Medium",
"questionId": "200",
"topicTags": "Depth-first Search, Breadth-first Search, Union Find"
},
{
"title": "Happy Number",
"titleSlug": "happy-number",
"status": "ac",
"difficulty": "Easy",
"questionId": "202",
"topicTags": "Hash Table, Math"
},
{
"title": "Remove Linked List Elements",
"titleSlug": "remove-linked-list-elements",
"status": "ac",
"difficulty": "Easy",
"questionId": "203",
"topicTags": "Linked List"
},
{
"title": "Isomorphic Strings",
"titleSlug": "isomorphic-strings",
"status": "ac",
"difficulty": "Easy",
"questionId": "205",
"topicTags": "Hash Table"
},
{
"title": "Reverse Linked List",
"titleSlug": "reverse-linked-list",
"status": "ac",
"difficulty": "Easy",
"questionId": "206",
"topicTags": "Linked List"
},
{
"title": "Minimum Size Subarray Sum",
"titleSlug": "minimum-size-subarray-sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "209",
"topicTags": "Array, Two Pointers, Binary Search"
},
{
"title": "Kth Largest Element in an Array",
"titleSlug": "kth-largest-element-in-an-array",
"status": "ac",
"difficulty": "Medium",
"questionId": "215",
"topicTags": "Heap, Divide and Conquer"
},
{
"title": "Combination Sum III",
"titleSlug": "combination-sum-iii",
"status": "ac",
"difficulty": "Medium",
"questionId": "216",
"topicTags": "Array, Backtracking"
},
{
"title": "Contains Duplicate",
"titleSlug": "contains-duplicate",
"status": "ac",
"difficulty": "Easy",
"questionId": "217",
"topicTags": "Array, Hash Table"
},
{
"title": "Contains Duplicate II",
"titleSlug": "contains-duplicate-ii",
"status": "ac",
"difficulty": "Easy",
"questionId": "219",
"topicTags": "Array, Hash Table"
},
{
"title": "Contains Duplicate III",
"titleSlug": "contains-duplicate-iii",
"status": "ac",
"difficulty": "Medium",
"questionId": "220",
"topicTags": "Sort, Ordered Map"
},
{
"title": "Count Complete Tree Nodes",
"titleSlug": "count-complete-tree-nodes",
"status": "ac",
"difficulty": "Medium",
"questionId": "222",
"topicTags": "Tree, Binary Search"
},
{
"title": "Invert Binary Tree",
"titleSlug": "invert-binary-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "226",
"topicTags": "Tree"
},
{
"title": "Kth Smallest Element in a BST",
"titleSlug": "kth-smallest-element-in-a-bst",
"status": "ac",
"difficulty": "Medium",
"questionId": "230",
"topicTags": "Tree, Binary Search"
},
{
"title": "Palindrome Linked List",
"titleSlug": "palindrome-linked-list",
"status": "ac",
"difficulty": "Easy",
"questionId": "234",
"topicTags": "Linked List, Two Pointers"
},
{
"title": "Lowest Common Ancestor of a Binary Search Tree",
"titleSlug": "lowest-common-ancestor-of-a-binary-search-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "235",
"topicTags": "Tree"
},
{
"title": "Lowest Common Ancestor of a Binary Tree",
"titleSlug": "lowest-common-ancestor-of-a-binary-tree",
"status": "ac",
"difficulty": "Medium",
"questionId": "236",
"topicTags": "Tree"
},
{
"title": "Delete Node in a Linked List",
"titleSlug": "delete-node-in-a-linked-list",
"status": "ac",
"difficulty": "Easy",
"questionId": "237",
"topicTags": "Linked List"
},
{
"title": "Valid Anagram",
"titleSlug": "valid-anagram",
"status": "ac",
"difficulty": "Easy",
"questionId": "242",
"topicTags": "Sort, Hash Table"
},
{
"title": "Binary Tree Paths",
"titleSlug": "binary-tree-paths",
"status": "ac",
"difficulty": "Easy",
"questionId": "257",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Perfect Squares",
"titleSlug": "perfect-squares",
"status": "ac",
"difficulty": "Medium",
"questionId": "279",
"topicTags": "Breadth-first Search, Math, Dynamic Programming"
},
{
"title": "Move Zeroes",
"titleSlug": "move-zeroes",
"status": "ac",
"difficulty": "Easy",
"questionId": "283",
"topicTags": "Array, Two Pointers"
},
{
"title": "Word Pattern",
"titleSlug": "word-pattern",
"status": "ac",
"difficulty": "Easy",
"questionId": "290",
"topicTags": "Hash Table"
},
{
"title": "Odd Even Linked List",
"titleSlug": "odd-even-linked-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "328",
"topicTags": "Linked List"
},
{
"title": "Flatten Nested List Iterator",
"titleSlug": "flatten-nested-list-iterator",
"status": "ac",
"difficulty": "Medium",
"questionId": "341",
"topicTags": "Stack, Design"
},
{
"title": "Reverse String",
"titleSlug": "reverse-string",
"status": "ac",
"difficulty": "Easy",
"questionId": "344",
"topicTags": "Two Pointers, String"
},
{
"title": "Top K Frequent Elements",
"titleSlug": "top-k-frequent-elements",
"status": "ac",
"difficulty": "Medium",
"questionId": "347",
"topicTags": "Heap, Hash Table"
},
{
"title": "Intersection of Two Arrays",
"titleSlug": "intersection-of-two-arrays",
"status": "ac",
"difficulty": "Easy",
"questionId": "349",
"topicTags": "Sort, Hash Table, Two Pointers, Binary Search"
},
{
"title": "Intersection of Two Arrays II",
"titleSlug": "intersection-of-two-arrays-ii",
"status": "ac",
"difficulty": "Easy",
"questionId": "350",
"topicTags": "Sort, Hash Table, Two Pointers, Binary Search"
},
{
"title": "Binary Watch",
"titleSlug": "binary-watch",
"status": "ac",
"difficulty": "Easy",
"questionId": "401",
"topicTags": "Bit Manipulation, Backtracking"
},
{
"title": "Sum of Left Leaves",
"titleSlug": "sum-of-left-leaves",
"status": "ac",
"difficulty": "Easy",
"questionId": "404",
"topicTags": "Tree"
},
{
"title": "Pacific Atlantic Water Flow",
"titleSlug": "pacific-atlantic-water-flow",
"status": "ac",
"difficulty": "Medium",
"questionId": "417",
"topicTags": "Depth-first Search, Breadth-first Search"
},
{
"title": "Path Sum III",
"titleSlug": "path-sum-iii",
"status": "ac",
"difficulty": "Medium",
"questionId": "437",
"topicTags": "Tree"
},
{
"title": "Find All Anagrams in a String",
"titleSlug": "find-all-anagrams-in-a-string",
"status": "ac",
"difficulty": "Medium",
"questionId": "438",
"topicTags": "Hash Table"
},
{
"title": "Add Two Numbers II",
"titleSlug": "add-two-numbers-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "445",
"topicTags": "Linked List"
},
{
"title": "Number of Boomerangs",
"titleSlug": "number-of-boomerangs",
"status": "ac",
"difficulty": "Medium",
"questionId": "447",
"topicTags": "Hash Table, Math"
},
{
"title": "Delete Node in a BST",
"titleSlug": "delete-node-in-a-bst",
"status": "ac",
"difficulty": "Medium",
"questionId": "450",
"topicTags": "Tree"
},
{
"title": "Sort Characters By Frequency",
"titleSlug": "sort-characters-by-frequency",
"status": "ac",
"difficulty": "Medium",
"questionId": "451",
"topicTags": "Heap, Hash Table"
},
{
"title": "4Sum II",
"titleSlug": "4sum-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "454",
"topicTags": "Hash Table, Binary Search"
},
{
"title": "Permutation in String",
"titleSlug": "permutation-in-string",
"status": "ac",
"difficulty": "Medium",
"questionId": "567",
"topicTags": "Two Pointers, Sliding Window"
}
]
================================================
FILE: packages/leetcode-cli/src/cli.js
================================================
#!/usr/bin/env node
const commander = require('commander')
const packageJson = require('../package.json')
const download = require('./download')
const logout = require('./logout')
const { login } = require('./leetcode')
commander.version(packageJson.version)
commander
.command('download')
.option('-a, --all', 'Download all your accepted code from LeetCode.')
.description('Download your new accepted code from LeetCode.')
.action(download)
commander
.command('login')
.description('Log in to your Leetcode account.')
.action(login)
commander
.command('logout')
.description('Logout current LeetCode account')
.description('Log out of current account.')
.action(logout)
commander.parse(process.argv)
================================================
FILE: packages/leetcode-cli/src/download.js
================================================
/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
const fs = require('fs')
const ora = require('ora')
const { transformToMarkdownTable, stringify } = require('./utils')
const { getAllACQuestions, getQuestionData } = require('./leetcode')
const { resolveApp } = require('crd-utils')
const difference = (problemsA = [], problemsB = []) => {
const map = problemsB.reduce((acc, problem) => {
acc[problem.titleSlug] = true
return acc
}, {})
return problemsA.filter(problem => !map[problem.titleSlug])
}
const download = async (command) => {
const problemsPath = 'problems.json'
// get incremental problems through comapre json
let problems = []
let questions = await getAllACQuestions()
if (!command.all) {
if (fs.existsSync(problemsPath)) {
problems = JSON.parse(fs.readFileSync(problemsPath))
}
questions = difference(questions, problems)
}
const spinner = ora('Downloading accepted code...\n').start()
const aux = async (xs = []) => {
if (xs.length === 0) {
return
}
const current = xs.shift()
try {
const { topicTags } = await getQuestionData(current.titleSlug)
const result = topicTags.reduce((prev, cur) => {
if (prev === '') return cur.name
return `${prev}, ${cur.name}`
}, '')
current.topicTags = result
} catch (error) {
console.error(error.message)
}
spinner.text = `${questions.length - xs.length}/${questions.length}: [${
current.title
}] has downloaded.`
problems.push(current)
// it can be extracted in to config if there is need.
let leetcodeReadme = 'leetcode-table.md'
if (fs.existsSync(resolveApp('LeetCode/README.md'))) {
leetcodeReadme = resolveApp('LeetCode/README.md')
}
let transformMarkdownTable = transformToMarkdownTable
if (fs.existsSync(resolveApp('config.js'))) {
transformMarkdownTable = require(resolveApp('config.js')).transform_markdown_table
}
fs.writeFileSync(leetcodeReadme, transformMarkdownTable(problems))
// the problemsPath is to get incremental data.
fs.writeFileSync(problemsPath, stringify(problems))
await aux(xs)
}
await aux([...questions])
spinner.stop()
}
module.exports = download
================================================
FILE: packages/leetcode-cli/src/leetcode-table.md
================================================
| # | Title | Explanation | Difficulty | Type |
|:---:|:---:|:---:|:---:|:---:|
| 1 | [Two Sum](https://leetcode.com/problems/two-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/1.Two_Sum.md) | Easy | Array, Hash Table |
| 2 | [Add Two Numbers](https://leetcode.com/problems/add-two-numbers/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/2.Add_Two_Numbers.md) | Medium | Linked List, Math |
| 3 | [Longest Substring Without Repeating Characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/3.Longest_Substring_Without_Repeating_Characters.md) | Medium | Hash Table, Two Pointers, String, Sliding Window |
| 4 | [Median of Two Sorted Arrays](https://leetcode.com/problems/median-of-two-sorted-arrays/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/4.Median_of_Two_Sorted_Arrays.md) | Hard | Array, Binary Search, Divide and Conquer |
| 7 | [Reverse Integer](https://leetcode.com/problems/reverse-integer/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/7.Reverse_Integer.md) | Easy | Math |
| 9 | [Palindrome Number](https://leetcode.com/problems/palindrome-number/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/9.Palindrome_Number.md) | Easy | Math |
| 11 | [Container With Most Water](https://leetcode.com/problems/container-with-most-water/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/11.Container_With_Most_Water.md) | Medium | Array, Two Pointers |
| 13 | [Roman to Integer](https://leetcode.com/problems/roman-to-integer/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/13.Roman_to_Integer.md) | Easy | Math, String |
| 14 | [Longest Common Prefix](https://leetcode.com/problems/longest-common-prefix/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/14.Longest_Common_Prefix.md) | Easy | String |
| 15 | [3Sum](https://leetcode.com/problems/3sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/15.3Sum.md) | Medium | Array, Two Pointers |
| 16 | [3Sum Closest](https://leetcode.com/problems/3sum-closest/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/16.3Sum_Closest.md) | Medium | Array, Two Pointers |
| 17 | [Letter Combinations of a Phone Number](https://leetcode.com/problems/letter-combinations-of-a-phone-number/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/17.Letter_Combinations_of_a_Phone_Number.md) | Medium | String, Backtracking |
| 18 | [4Sum](https://leetcode.com/problems/4sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/18.4Sum.md) | Medium | Array, Hash Table, Two Pointers |
| 19 | [Remove Nth Node From End of List](https://leetcode.com/problems/remove-nth-node-from-end-of-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/19.Remove_Nth_Node_From_End_of_List.md) | Medium | Linked List, Two Pointers |
| 20 | [Valid Parentheses](https://leetcode.com/problems/valid-parentheses/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/20.Valid_Parentheses.md) | Easy | Stack, String |
| 21 | [Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/21.Merge_Two_Sorted_Lists.md) | Easy | Linked List |
| 22 | [Generate Parentheses](https://leetcode.com/problems/generate-parentheses/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/22.Generate_Parentheses.md) | Medium | String, Backtracking |
| 23 | [Merge k Sorted Lists](https://leetcode.com/problems/merge-k-sorted-lists/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/23.Merge_k_Sorted_Lists.md) | Hard | Heap, Linked List, Divide and Conquer |
| 24 | [Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/24.Swap_Nodes_in_Pairs.md) | Medium | Linked List |
| 25 | [Reverse Nodes in k-Group](https://leetcode.com/problems/reverse-nodes-in-k-group/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/25.Reverse_Nodes_in_k-Group.md) | Hard | Linked List |
| 26 | [Remove Duplicates from Sorted Array](https://leetcode.com/problems/remove-duplicates-from-sorted-array/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/26.Remove_Duplicates_from_Sorted_Array.md) | Easy | Array, Two Pointers |
| 27 | [Remove Element](https://leetcode.com/problems/remove-element/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/27.Remove_Element.md) | Easy | Array, Two Pointers |
| 28 | [Implement strStr()](https://leetcode.com/problems/implement-strstr/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/28.Implement_strStr().md) | Easy | Two Pointers, String |
| 33 | [Search in Rotated Sorted Array](https://leetcode.com/problems/search-in-rotated-sorted-array/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/33.Search_in_Rotated_Sorted_Array.md) | Medium | Array, Binary Search |
| 35 | [Search Insert Position](https://leetcode.com/problems/search-insert-position/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/35.Search_Insert_Position.md) | Easy | Array, Binary Search |
| 36 | [Valid Sudoku](https://leetcode.com/problems/valid-sudoku/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/36.Valid_Sudoku.md) | Medium | Hash Table |
| 38 | [Count and Say](https://leetcode.com/problems/count-and-say/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/38.Count_and_Say.md) | Easy | String |
| 39 | [Combination Sum](https://leetcode.com/problems/combination-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/39.Combination_Sum.md) | Medium | Array, Backtracking |
| 40 | [Combination Sum II](https://leetcode.com/problems/combination-sum-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/40.Combination_Sum_II.md) | Medium | Array, Backtracking |
| 46 | [Permutations](https://leetcode.com/problems/permutations/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/46.Permutations.md) | Medium | Backtracking |
| 47 | [Permutations II](https://leetcode.com/problems/permutations-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/47.Permutations_II.md) | Medium | Backtracking |
| 48 | [Rotate Image](https://leetcode.com/problems/rotate-image/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/48.Rotate_Image.md) | Medium | Array |
| 49 | [Group Anagrams](https://leetcode.com/problems/group-anagrams/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/49.Group_Anagrams.md) | Medium | Hash Table, String |
| 50 | [Pow(x, n)](https://leetcode.com/problems/powx-n/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/50.Pow(x,_n).md) | Medium | Math, Binary Search |
| 61 | [Rotate List](https://leetcode.com/problems/rotate-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/61.Rotate_List.md) | Medium | Linked List, Two Pointers |
| 62 | [Unique Paths](https://leetcode.com/problems/unique-paths/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/62.Unique_Paths.md) | Medium | Array, Dynamic Programming |
| 64 | [Minimum Path Sum](https://leetcode.com/problems/minimum-path-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/64.Minimum_Path_Sum.md) | Medium | Array, Dynamic Programming |
| 66 | [Plus One](https://leetcode.com/problems/plus-one/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/66.Plus_One.md) | Easy | Array |
| 67 | [Add Binary](https://leetcode.com/problems/add-binary/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/67.Add_Binary.md) | Easy | Math, String |
| 69 | [Sqrt(x)](https://leetcode.com/problems/sqrtx/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/69.Sqrt(x).md) | Easy | Math, Binary Search |
| 70 | [Climbing Stairs](https://leetcode.com/problems/climbing-stairs/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/70.Climbing_Stairs.md) | Easy | Dynamic Programming |
| 71 | [Simplify Path](https://leetcode.com/problems/simplify-path/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/71.Simplify_Path.md) | Medium | Stack, String |
| 75 | [Sort Colors](https://leetcode.com/problems/sort-colors/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/75.Sort_Colors.md) | Medium | Sort, Array, Two Pointers |
| 78 | [Subsets](https://leetcode.com/problems/subsets/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/78.Subsets.md) | Medium | Bit Manipulation, Array, Backtracking |
| 80 | [Remove Duplicates from Sorted Array II](https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/80.Remove_Duplicates_from_Sorted_Array_II.md) | Medium | Array, Two Pointers |
| 82 | [Remove Duplicates from Sorted List II](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/82.Remove_Duplicates_from_Sorted_List_II.md) | Medium | Linked List |
| 83 | [Remove Duplicates from Sorted List](https://leetcode.com/problems/remove-duplicates-from-sorted-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/83.Remove_Duplicates_from_Sorted_List.md) | Easy | Linked List |
| 86 | [Partition List](https://leetcode.com/problems/partition-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/86.Partition_List.md) | Medium | Linked List, Two Pointers |
| 88 | [Merge Sorted Array](https://leetcode.com/problems/merge-sorted-array/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/88.Merge_Sorted_Array.md) | Easy | Array, Two Pointers |
| 89 | [Gray Code](https://leetcode.com/problems/gray-code/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/89.Gray_Code.md) | Medium | Backtracking |
| 92 | [Reverse Linked List II](https://leetcode.com/problems/reverse-linked-list-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/92.Reverse_Linked_List_II.md) | Medium | Linked List |
| 93 | [Restore IP Addresses](https://leetcode.com/problems/restore-ip-addresses/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/93.Restore_IP_Addresses.md) | Medium | String, Backtracking |
| 94 | [Binary Tree Inorder Traversal](https://leetcode.com/problems/binary-tree-inorder-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/94.Binary_Tree_Inorder_Traversal.md) | Medium | Stack, Tree, Hash Table |
| 100 | [Same Tree](https://leetcode.com/problems/same-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/100.Same_Tree.md) | Easy | Tree, Depth-first Search |
| 101 | [Symmetric Tree](https://leetcode.com/problems/symmetric-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/101.Symmetric_Tree.md) | Easy | Tree, Depth-first Search, Breadth-first Search |
| 102 | [Binary Tree Level Order Traversal](https://leetcode.com/problems/binary-tree-level-order-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/102.Binary_Tree_Level_Order_Traversal.md) | Medium | Tree, Breadth-first Search |
| 103 | [Binary Tree Zigzag Level Order Traversal](https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/103.Binary_Tree_Zigzag_Level_Order_Traversal.md) | Medium | Stack, Tree, Breadth-first Search |
| 104 | [Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/104.Maximum_Depth_of_Binary_Tree.md) | Easy | Tree, Depth-first Search |
| 107 | [Binary Tree Level Order Traversal II](https://leetcode.com/problems/binary-tree-level-order-traversal-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/107.Binary_Tree_Level_Order_Traversal_II.md) | Easy | Tree, Breadth-first Search |
| 110 | [Balanced Binary Tree](https://leetcode.com/problems/balanced-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/110.Balanced_Binary_Tree.md) | Easy | Tree, Depth-first Search |
| 111 | [Minimum Depth of Binary Tree](https://leetcode.com/problems/minimum-depth-of-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/111.Minimum_Depth_of_Binary_Tree.md) | Easy | Tree, Depth-first Search, Breadth-first Search |
| 112 | [Path Sum](https://leetcode.com/problems/path-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/112.Path_Sum.md) | Easy | Tree, Depth-first Search |
| 113 | [Path Sum II](https://leetcode.com/problems/path-sum-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/113.Path_Sum_II.md) | Medium | Tree, Depth-first Search |
| 121 | [Best Time to Buy and Sell Stock](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/121.Best_Time_to_Buy_and_Sell_Stock.md) | Easy | Array, Dynamic Programming |
| 125 | [Valid Palindrome](https://leetcode.com/problems/valid-palindrome/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/125.Valid_Palindrome.md) | Easy | Two Pointers, String |
| 127 | [Word Ladder](https://leetcode.com/problems/word-ladder/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/127.Word_Ladder.md) | Medium | Breadth-first Search |
| 129 | [Sum Root to Leaf Numbers](https://leetcode.com/problems/sum-root-to-leaf-numbers/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/129.Sum_Root_to_Leaf_Numbers.md) | Medium | Tree, Depth-first Search |
| 143 | [Reorder List](https://leetcode.com/problems/reorder-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/143.Reorder_List.md) | Medium | Linked List |
| 144 | [Binary Tree Preorder Traversal](https://leetcode.com/problems/binary-tree-preorder-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/144.Binary_Tree_Preorder_Traversal.md) | Medium | Stack, Tree |
| 145 | [Binary Tree Postorder Traversal](https://leetcode.com/problems/binary-tree-postorder-traversal/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/145.Binary_Tree_Postorder_Traversal.md) | Medium | Stack, Tree |
| 147 | [Insertion Sort List](https://leetcode.com/problems/insertion-sort-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/147.Insertion_Sort_List.md) | Medium | Sort, Linked List |
| 148 | [Sort List](https://leetcode.com/problems/sort-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/148.Sort_List.md) | Medium | Sort, Linked List |
| 149 | [Max Points on a Line](https://leetcode.com/problems/max-points-on-a-line/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/149.Max_Points_on_a_Line.md) | Hard | Hash Table, Math |
| 150 | [Evaluate Reverse Polish Notation](https://leetcode.com/problems/evaluate-reverse-polish-notation/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/150.Evaluate_Reverse_Polish_Notation.md) | Medium | Stack |
| 151 | [Reverse Words in a String](https://leetcode.com/problems/reverse-words-in-a-string/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/151.Reverse_Words_in_a_String.md) | Medium | String |
| 167 | [Two Sum II - Input array is sorted](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/167.Two_Sum_II_-_Input_array_is_sorted.md) | Easy | Array, Two Pointers, Binary Search |
| 199 | [Binary Tree Right Side View](https://leetcode.com/problems/binary-tree-right-side-view/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/199.Binary_Tree_Right_Side_View.md) | Medium | Tree, Depth-first Search, Breadth-first Search |
| 202 | [Happy Number](https://leetcode.com/problems/happy-number/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/202.Happy_Number.md) | Easy | Hash Table, Math |
| 203 | [Remove Linked List Elements](https://leetcode.com/problems/remove-linked-list-elements/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/203.Remove_Linked_List_Elements.md) | Easy | Linked List |
| 205 | [Isomorphic Strings](https://leetcode.com/problems/isomorphic-strings/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/205.Isomorphic_Strings.md) | Easy | Hash Table |
| 206 | [Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/206.Reverse_Linked_List.md) | Easy | Linked List |
| 209 | [Minimum Size Subarray Sum](https://leetcode.com/problems/minimum-size-subarray-sum/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/209.Minimum_Size_Subarray_Sum.md) | Medium | Array, Two Pointers, Binary Search |
| 215 | [Kth Largest Element in an Array](https://leetcode.com/problems/kth-largest-element-in-an-array/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/215.Kth_Largest_Element_in_an_Array.md) | Medium | Heap, Divide and Conquer |
| 217 | [Contains Duplicate](https://leetcode.com/problems/contains-duplicate/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/217.Contains_Duplicate.md) | Easy | Array, Hash Table |
| 219 | [Contains Duplicate II](https://leetcode.com/problems/contains-duplicate-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/219.Contains_Duplicate_II.md) | Easy | Array, Hash Table |
| 220 | [Contains Duplicate III](https://leetcode.com/problems/contains-duplicate-iii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/220.Contains_Duplicate_III.md) | Medium | Sort, Ordered Map |
| 222 | [Count Complete Tree Nodes](https://leetcode.com/problems/count-complete-tree-nodes/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/222.Count_Complete_Tree_Nodes.md) | Medium | Tree, Binary Search |
| 226 | [Invert Binary Tree](https://leetcode.com/problems/invert-binary-tree/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/226.Invert_Binary_Tree.md) | Easy | Tree |
| 234 | [Palindrome Linked List](https://leetcode.com/problems/palindrome-linked-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/234.Palindrome_Linked_List.md) | Easy | Linked List, Two Pointers |
| 237 | [Delete Node in a Linked List](https://leetcode.com/problems/delete-node-in-a-linked-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/237.Delete_Node_in_a_Linked_List.md) | Easy | Linked List |
| 242 | [Valid Anagram](https://leetcode.com/problems/valid-anagram/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/242.Valid_Anagram.md) | Easy | Sort, Hash Table |
| 257 | [Binary Tree Paths](https://leetcode.com/problems/binary-tree-paths/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/257.Binary_Tree_Paths.md) | Easy | Tree, Depth-first Search |
| 279 | [Perfect Squares](https://leetcode.com/problems/perfect-squares/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/279.Perfect_Squares.md) | Medium | Breadth-first Search, Math, Dynamic Programming |
| 283 | [Move Zeroes](https://leetcode.com/problems/move-zeroes/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/283.Move_Zeroes.md) | Easy | Array, Two Pointers |
| 290 | [Word Pattern](https://leetcode.com/problems/word-pattern/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/290.Word_Pattern.md) | Easy | Hash Table |
| 328 | [Odd Even Linked List](https://leetcode.com/problems/odd-even-linked-list/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/328.Odd_Even_Linked_List.md) | Medium | Linked List |
| 341 | [Flatten Nested List Iterator](https://leetcode.com/problems/flatten-nested-list-iterator/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/341.Flatten_Nested_List_Iterator.md) | Medium | Stack, Design |
| 344 | [Reverse String](https://leetcode.com/problems/reverse-string/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/344.Reverse_String.md) | Easy | Two Pointers, String |
| 347 | [Top K Frequent Elements](https://leetcode.com/problems/top-k-frequent-elements/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/347.Top_K_Frequent_Elements.md) | Medium | Heap, Hash Table |
| 349 | [Intersection of Two Arrays](https://leetcode.com/problems/intersection-of-two-arrays/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/349.Intersection_of_Two_Arrays.md) | Easy | Sort, Hash Table, Two Pointers, Binary Search |
| 350 | [Intersection of Two Arrays II](https://leetcode.com/problems/intersection-of-two-arrays-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/350.Intersection_of_Two_Arrays_II.md) | Easy | Sort, Hash Table, Two Pointers, Binary Search |
| 404 | [Sum of Left Leaves](https://leetcode.com/problems/sum-of-left-leaves/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/404.Sum_of_Left_Leaves.md) | Easy | Tree |
| 438 | [Find All Anagrams in a String](https://leetcode.com/problems/find-all-anagrams-in-a-string/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/438.Find_All_Anagrams_in_a_String.md) | Medium | Hash Table |
| 445 | [Add Two Numbers II](https://leetcode.com/problems/add-two-numbers-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/445.Add_Two_Numbers_II.md) | Medium | Linked List |
| 447 | [Number of Boomerangs](https://leetcode.com/problems/number-of-boomerangs/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/447.Number_of_Boomerangs.md) | Easy | Hash Table |
| 451 | [Sort Characters By Frequency](https://leetcode.com/problems/sort-characters-by-frequency/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/451.Sort_Characters_By_Frequency.md) | Medium | Heap, Hash Table |
| 454 | [4Sum II](https://leetcode.com/problems/4sum-ii/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/454.4Sum_II.md) | Medium | Hash Table, Binary Search |
| 567 | [Permutation in String](https://leetcode.com/problems/permutation-in-string/) | [Analyze](https://github.com/MuYunyun/blog/blob/master/LeetCode/567.Permutation_in_String.md) | Medium | Two Pointers, Sliding Window |
================================================
FILE: packages/leetcode-cli/src/leetcode.js
================================================
const { GraphQLClient } = require('graphql-request')
const ora = require('ora')
const inquirer = require('inquirer')
const puppeteer = require('puppeteer-extra')
const StealthPlugin = require('puppeteer-extra-plugin-stealth')
const {
// request,
getHeaders,
// unicodeToChar,
removeConfig,
getConfig,
setConfig,
} = require('./utils')
// use this plugin to close to the real login.
puppeteer.use(StealthPlugin())
const { country } = getConfig()
const usUrl = 'https://leetcode.com'
const cnUrl = 'https://leetcode-cn.com'
const baseUrl = country === 'us' ? usUrl : cnUrl
const graphqlUrl = `${baseUrl}/graphql`
const login = async () => {
let loginUrl = baseUrl
if (country === undefined) {
loginUrl = (
await inquirer.prompt({
name: 'baseUrl',
type: 'list',
message: 'Log in to:',
choices: [usUrl, cnUrl],
})
).baseUrl
setConfig({ country: loginUrl === cnUrl ? 'cn' : 'us' })
}
loginUrl += '/accounts/login/'
const spinner = ora('Login...').start()
try {
// it have to set executablePath or it'll be broken.https://github.com/puppeteer/puppeteer/issues/6425
// temporary way: https://stackoverflow.com/questions/47122579/run-puppeteer-on-already-installed-chrome-on-macos
const browser = await puppeteer.launch({ headless: false, executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' })
const page = await browser.newPage()
await page.goto(loginUrl)
await page.waitForFunction('window.location.href.indexOf("login") === -1')
let cookies = await page.cookies()
await browser.close()
spinner.stop()
cookies = cookies.reduce((acc, cookie) => {
const { name } = cookie
acc[name] = cookie
return acc
}, {})
setConfig({ cookies })
return cookies
} catch (error) {
console.error('Login failure, retry...', error.message)
throw error
} finally {
spinner.stop()
}
}
const getCookie = async () => {
// eslint-disable-line
try {
const { cookies } = getConfig()
const { LEETCODE_SESSION } = cookies
if (
!LEETCODE_SESSION ||
new Date(LEETCODE_SESSION.expires) <= new Date().getTime() / 1000
) {
console.error('Cookie expires, retry...')
removeConfig('cookies')
return getCookie()
}
return Object.keys(cookies).reduce((acc, name) => {
acc[name] = cookies[name].value
return acc
}, {})
} catch (error) {
const cookies = await login()
return cookies
}
}
const createGqlRequest = async () => {
const cookies = await getCookie()
const client = new GraphQLClient(graphqlUrl, {
headers: getHeaders(cookies),
})
return client.request.bind(client)
}
// restful request
// const createRequest = async () => {
// const cookies = await getCookie();
// return url =>
// request(url, {
// headers: getHeaders(cookies),
// });
// };
const filterAcQuestions = (questions = []) =>
questions.filter(({ status }) => status === 'ac')
const getAllACQuestions = async () => {
const gqlRequest = await createGqlRequest()
const spinner = ora('Fetching all questions...').start()
// interface see https://leetcode-cn.com/problems/add-two-numbers/
const json = await gqlRequest(`{
allQuestions{
title
titleSlug
status
difficulty
questionId,
}
}`)
spinner.stop()
const questions = json.allQuestions || []
return filterAcQuestions(questions)
}
// get details of ac code.
const getQuestionData = async (titleSlug) => {
const qglRequest = await createGqlRequest()
// interface from 'https://leetcode-cn.com/problems/add-two-numbers/'
const json = await qglRequest(`{
question(titleSlug: "${titleSlug}") {
topicTags {
name
slug
}
}
}`)
const question = json.question || {}
return question
}
module.exports = {
login,
getAllACQuestions,
getQuestionData,
getCookie,
}
================================================
FILE: packages/leetcode-cli/src/logout.js
================================================
const { removeConfig } = require('./utils')
const logOut = () => {
removeConfig('cookies')
}
module.exports = logOut
================================================
FILE: packages/leetcode-cli/src/problems.json
================================================
[
{
"title": "Two Sum",
"titleSlug": "two-sum",
"status": "ac",
"difficulty": "Easy",
"questionId": "1",
"topicTags": "Array, Hash Table"
},
{
"title": "Add Two Numbers",
"titleSlug": "add-two-numbers",
"status": "ac",
"difficulty": "Medium",
"questionId": "2",
"topicTags": "Linked List, Math"
},
{
"title": "Longest Substring Without Repeating Characters",
"titleSlug": "longest-substring-without-repeating-characters",
"status": "ac",
"difficulty": "Medium",
"questionId": "3",
"topicTags": "Hash Table, Two Pointers, String, Sliding Window"
},
{
"title": "Median of Two Sorted Arrays",
"titleSlug": "median-of-two-sorted-arrays",
"status": "ac",
"difficulty": "Hard",
"questionId": "4",
"topicTags": "Array, Binary Search, Divide and Conquer"
},
{
"title": "Reverse Integer",
"titleSlug": "reverse-integer",
"status": "ac",
"difficulty": "Easy",
"questionId": "7",
"topicTags": "Math"
},
{
"title": "Palindrome Number",
"titleSlug": "palindrome-number",
"status": "ac",
"difficulty": "Easy",
"questionId": "9",
"topicTags": "Math"
},
{
"title": "Container With Most Water",
"titleSlug": "container-with-most-water",
"status": "ac",
"difficulty": "Medium",
"questionId": "11",
"topicTags": "Array, Two Pointers"
},
{
"title": "Roman to Integer",
"titleSlug": "roman-to-integer",
"status": "ac",
"difficulty": "Easy",
"questionId": "13",
"topicTags": "Math, String"
},
{
"title": "Longest Common Prefix",
"titleSlug": "longest-common-prefix",
"status": "ac",
"difficulty": "Easy",
"questionId": "14",
"topicTags": "String"
},
{
"title": "3Sum",
"titleSlug": "3sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "15",
"topicTags": "Array, Two Pointers"
},
{
"title": "3Sum Closest",
"titleSlug": "3sum-closest",
"status": "ac",
"difficulty": "Medium",
"questionId": "16",
"topicTags": "Array, Two Pointers"
},
{
"title": "Letter Combinations of a Phone Number",
"titleSlug": "letter-combinations-of-a-phone-number",
"status": "ac",
"difficulty": "Medium",
"questionId": "17",
"topicTags": "String, Backtracking"
},
{
"title": "4Sum",
"titleSlug": "4sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "18",
"topicTags": "Array, Hash Table, Two Pointers"
},
{
"title": "Remove Nth Node From End of List",
"titleSlug": "remove-nth-node-from-end-of-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "19",
"topicTags": "Linked List, Two Pointers"
},
{
"title": "Valid Parentheses",
"titleSlug": "valid-parentheses",
"status": "ac",
"difficulty": "Easy",
"questionId": "20",
"topicTags": "Stack, String"
},
{
"title": "Merge Two Sorted Lists",
"titleSlug": "merge-two-sorted-lists",
"status": "ac",
"difficulty": "Easy",
"questionId": "21",
"topicTags": "Linked List"
},
{
"title": "Generate Parentheses",
"titleSlug": "generate-parentheses",
"status": "ac",
"difficulty": "Medium",
"questionId": "22",
"topicTags": "String, Backtracking"
},
{
"title": "Merge k Sorted Lists",
"titleSlug": "merge-k-sorted-lists",
"status": "ac",
"difficulty": "Hard",
"questionId": "23",
"topicTags": "Heap, Linked List, Divide and Conquer"
},
{
"title": "Swap Nodes in Pairs",
"titleSlug": "swap-nodes-in-pairs",
"status": "ac",
"difficulty": "Medium",
"questionId": "24",
"topicTags": "Linked List"
},
{
"title": "Reverse Nodes in k-Group",
"titleSlug": "reverse-nodes-in-k-group",
"status": "ac",
"difficulty": "Hard",
"questionId": "25",
"topicTags": "Linked List"
},
{
"title": "Remove Duplicates from Sorted Array",
"titleSlug": "remove-duplicates-from-sorted-array",
"status": "ac",
"difficulty": "Easy",
"questionId": "26",
"topicTags": "Array, Two Pointers"
},
{
"title": "Remove Element",
"titleSlug": "remove-element",
"status": "ac",
"difficulty": "Easy",
"questionId": "27",
"topicTags": "Array, Two Pointers"
},
{
"title": "Implement strStr()",
"titleSlug": "implement-strstr",
"status": "ac",
"difficulty": "Easy",
"questionId": "28",
"topicTags": "Two Pointers, String"
},
{
"title": "Search in Rotated Sorted Array",
"titleSlug": "search-in-rotated-sorted-array",
"status": "ac",
"difficulty": "Medium",
"questionId": "33",
"topicTags": "Array, Binary Search"
},
{
"title": "Search Insert Position",
"titleSlug": "search-insert-position",
"status": "ac",
"difficulty": "Easy",
"questionId": "35",
"topicTags": "Array, Binary Search"
},
{
"title": "Valid Sudoku",
"titleSlug": "valid-sudoku",
"status": "ac",
"difficulty": "Medium",
"questionId": "36",
"topicTags": "Hash Table"
},
{
"title": "Count and Say",
"titleSlug": "count-and-say",
"status": "ac",
"difficulty": "Easy",
"questionId": "38",
"topicTags": "String"
},
{
"title": "Combination Sum",
"titleSlug": "combination-sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "39",
"topicTags": "Array, Backtracking"
},
{
"title": "Combination Sum II",
"titleSlug": "combination-sum-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "40",
"topicTags": "Array, Backtracking"
},
{
"title": "Permutations",
"titleSlug": "permutations",
"status": "ac",
"difficulty": "Medium",
"questionId": "46",
"topicTags": "Backtracking"
},
{
"title": "Permutations II",
"titleSlug": "permutations-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "47",
"topicTags": "Backtracking"
},
{
"title": "Rotate Image",
"titleSlug": "rotate-image",
"status": "ac",
"difficulty": "Medium",
"questionId": "48",
"topicTags": "Array"
},
{
"title": "Group Anagrams",
"titleSlug": "group-anagrams",
"status": "ac",
"difficulty": "Medium",
"questionId": "49",
"topicTags": "Hash Table, String"
},
{
"title": "Pow(x, n)",
"titleSlug": "powx-n",
"status": "ac",
"difficulty": "Medium",
"questionId": "50",
"topicTags": "Math, Binary Search"
},
{
"title": "Rotate List",
"titleSlug": "rotate-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "61",
"topicTags": "Linked List, Two Pointers"
},
{
"title": "Unique Paths",
"titleSlug": "unique-paths",
"status": "ac",
"difficulty": "Medium",
"questionId": "62",
"topicTags": "Array, Dynamic Programming"
},
{
"title": "Minimum Path Sum",
"titleSlug": "minimum-path-sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "64",
"topicTags": "Array, Dynamic Programming"
},
{
"title": "Plus One",
"titleSlug": "plus-one",
"status": "ac",
"difficulty": "Easy",
"questionId": "66",
"topicTags": "Array"
},
{
"title": "Add Binary",
"titleSlug": "add-binary",
"status": "ac",
"difficulty": "Easy",
"questionId": "67",
"topicTags": "Math, String"
},
{
"title": "Sqrt(x)",
"titleSlug": "sqrtx",
"status": "ac",
"difficulty": "Easy",
"questionId": "69",
"topicTags": "Math, Binary Search"
},
{
"title": "Climbing Stairs",
"titleSlug": "climbing-stairs",
"status": "ac",
"difficulty": "Easy",
"questionId": "70",
"topicTags": "Dynamic Programming"
},
{
"title": "Simplify Path",
"titleSlug": "simplify-path",
"status": "ac",
"difficulty": "Medium",
"questionId": "71",
"topicTags": "Stack, String"
},
{
"title": "Sort Colors",
"titleSlug": "sort-colors",
"status": "ac",
"difficulty": "Medium",
"questionId": "75",
"topicTags": "Sort, Array, Two Pointers"
},
{
"title": "Subsets",
"titleSlug": "subsets",
"status": "ac",
"difficulty": "Medium",
"questionId": "78",
"topicTags": "Bit Manipulation, Array, Backtracking"
},
{
"title": "Remove Duplicates from Sorted Array II",
"titleSlug": "remove-duplicates-from-sorted-array-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "80",
"topicTags": "Array, Two Pointers"
},
{
"title": "Remove Duplicates from Sorted List II",
"titleSlug": "remove-duplicates-from-sorted-list-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "82",
"topicTags": "Linked List"
},
{
"title": "Remove Duplicates from Sorted List",
"titleSlug": "remove-duplicates-from-sorted-list",
"status": "ac",
"difficulty": "Easy",
"questionId": "83",
"topicTags": "Linked List"
},
{
"title": "Partition List",
"titleSlug": "partition-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "86",
"topicTags": "Linked List, Two Pointers"
},
{
"title": "Merge Sorted Array",
"titleSlug": "merge-sorted-array",
"status": "ac",
"difficulty": "Easy",
"questionId": "88",
"topicTags": "Array, Two Pointers"
},
{
"title": "Gray Code",
"titleSlug": "gray-code",
"status": "ac",
"difficulty": "Medium",
"questionId": "89",
"topicTags": "Backtracking"
},
{
"title": "Reverse Linked List II",
"titleSlug": "reverse-linked-list-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "92",
"topicTags": "Linked List"
},
{
"title": "Restore IP Addresses",
"titleSlug": "restore-ip-addresses",
"status": "ac",
"difficulty": "Medium",
"questionId": "93",
"topicTags": "String, Backtracking"
},
{
"title": "Binary Tree Inorder Traversal",
"titleSlug": "binary-tree-inorder-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "94",
"topicTags": "Stack, Tree, Hash Table"
},
{
"title": "Same Tree",
"titleSlug": "same-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "100",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Symmetric Tree",
"titleSlug": "symmetric-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "101",
"topicTags": "Tree, Depth-first Search, Breadth-first Search"
},
{
"title": "Binary Tree Level Order Traversal",
"titleSlug": "binary-tree-level-order-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "102",
"topicTags": "Tree, Breadth-first Search"
},
{
"title": "Binary Tree Zigzag Level Order Traversal",
"titleSlug": "binary-tree-zigzag-level-order-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "103",
"topicTags": "Stack, Tree, Breadth-first Search"
},
{
"title": "Maximum Depth of Binary Tree",
"titleSlug": "maximum-depth-of-binary-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "104",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Binary Tree Level Order Traversal II",
"titleSlug": "binary-tree-level-order-traversal-ii",
"status": "ac",
"difficulty": "Easy",
"questionId": "107",
"topicTags": "Tree, Breadth-first Search"
},
{
"title": "Balanced Binary Tree",
"titleSlug": "balanced-binary-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "110",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Minimum Depth of Binary Tree",
"titleSlug": "minimum-depth-of-binary-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "111",
"topicTags": "Tree, Depth-first Search, Breadth-first Search"
},
{
"title": "Path Sum",
"titleSlug": "path-sum",
"status": "ac",
"difficulty": "Easy",
"questionId": "112",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Path Sum II",
"titleSlug": "path-sum-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "113",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Best Time to Buy and Sell Stock",
"titleSlug": "best-time-to-buy-and-sell-stock",
"status": "ac",
"difficulty": "Easy",
"questionId": "121",
"topicTags": "Array, Dynamic Programming"
},
{
"title": "Valid Palindrome",
"titleSlug": "valid-palindrome",
"status": "ac",
"difficulty": "Easy",
"questionId": "125",
"topicTags": "Two Pointers, String"
},
{
"title": "Word Ladder",
"titleSlug": "word-ladder",
"status": "ac",
"difficulty": "Medium",
"questionId": "127",
"topicTags": "Breadth-first Search"
},
{
"title": "Sum Root to Leaf Numbers",
"titleSlug": "sum-root-to-leaf-numbers",
"status": "ac",
"difficulty": "Medium",
"questionId": "129",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Reorder List",
"titleSlug": "reorder-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "143",
"topicTags": "Linked List"
},
{
"title": "Binary Tree Preorder Traversal",
"titleSlug": "binary-tree-preorder-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "144",
"topicTags": "Stack, Tree"
},
{
"title": "Binary Tree Postorder Traversal",
"titleSlug": "binary-tree-postorder-traversal",
"status": "ac",
"difficulty": "Medium",
"questionId": "145",
"topicTags": "Stack, Tree"
},
{
"title": "Insertion Sort List",
"titleSlug": "insertion-sort-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "147",
"topicTags": "Sort, Linked List"
},
{
"title": "Sort List",
"titleSlug": "sort-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "148",
"topicTags": "Sort, Linked List"
},
{
"title": "Max Points on a Line",
"titleSlug": "max-points-on-a-line",
"status": "ac",
"difficulty": "Hard",
"questionId": "149",
"topicTags": "Hash Table, Math"
},
{
"title": "Evaluate Reverse Polish Notation",
"titleSlug": "evaluate-reverse-polish-notation",
"status": "ac",
"difficulty": "Medium",
"questionId": "150",
"topicTags": "Stack"
},
{
"title": "Reverse Words in a String",
"titleSlug": "reverse-words-in-a-string",
"status": "ac",
"difficulty": "Medium",
"questionId": "151",
"topicTags": "String"
},
{
"title": "Two Sum II - Input array is sorted",
"titleSlug": "two-sum-ii-input-array-is-sorted",
"status": "ac",
"difficulty": "Easy",
"questionId": "167",
"topicTags": "Array, Two Pointers, Binary Search"
},
{
"title": "Binary Tree Right Side View",
"titleSlug": "binary-tree-right-side-view",
"status": "ac",
"difficulty": "Medium",
"questionId": "199",
"topicTags": "Tree, Depth-first Search, Breadth-first Search"
},
{
"title": "Happy Number",
"titleSlug": "happy-number",
"status": "ac",
"difficulty": "Easy",
"questionId": "202",
"topicTags": "Hash Table, Math"
},
{
"title": "Remove Linked List Elements",
"titleSlug": "remove-linked-list-elements",
"status": "ac",
"difficulty": "Easy",
"questionId": "203",
"topicTags": "Linked List"
},
{
"title": "Isomorphic Strings",
"titleSlug": "isomorphic-strings",
"status": "ac",
"difficulty": "Easy",
"questionId": "205",
"topicTags": "Hash Table"
},
{
"title": "Reverse Linked List",
"titleSlug": "reverse-linked-list",
"status": "ac",
"difficulty": "Easy",
"questionId": "206",
"topicTags": "Linked List"
},
{
"title": "Minimum Size Subarray Sum",
"titleSlug": "minimum-size-subarray-sum",
"status": "ac",
"difficulty": "Medium",
"questionId": "209",
"topicTags": "Array, Two Pointers, Binary Search"
},
{
"title": "Kth Largest Element in an Array",
"titleSlug": "kth-largest-element-in-an-array",
"status": "ac",
"difficulty": "Medium",
"questionId": "215",
"topicTags": "Heap, Divide and Conquer"
},
{
"title": "Contains Duplicate",
"titleSlug": "contains-duplicate",
"status": "ac",
"difficulty": "Easy",
"questionId": "217",
"topicTags": "Array, Hash Table"
},
{
"title": "Contains Duplicate II",
"titleSlug": "contains-duplicate-ii",
"status": "ac",
"difficulty": "Easy",
"questionId": "219",
"topicTags": "Array, Hash Table"
},
{
"title": "Contains Duplicate III",
"titleSlug": "contains-duplicate-iii",
"status": "ac",
"difficulty": "Medium",
"questionId": "220",
"topicTags": "Sort, Ordered Map"
},
{
"title": "Count Complete Tree Nodes",
"titleSlug": "count-complete-tree-nodes",
"status": "ac",
"difficulty": "Medium",
"questionId": "222",
"topicTags": "Tree, Binary Search"
},
{
"title": "Invert Binary Tree",
"titleSlug": "invert-binary-tree",
"status": "ac",
"difficulty": "Easy",
"questionId": "226",
"topicTags": "Tree"
},
{
"title": "Palindrome Linked List",
"titleSlug": "palindrome-linked-list",
"status": "ac",
"difficulty": "Easy",
"questionId": "234",
"topicTags": "Linked List, Two Pointers"
},
{
"title": "Delete Node in a Linked List",
"titleSlug": "delete-node-in-a-linked-list",
"status": "ac",
"difficulty": "Easy",
"questionId": "237",
"topicTags": "Linked List"
},
{
"title": "Valid Anagram",
"titleSlug": "valid-anagram",
"status": "ac",
"difficulty": "Easy",
"questionId": "242",
"topicTags": "Sort, Hash Table"
},
{
"title": "Binary Tree Paths",
"titleSlug": "binary-tree-paths",
"status": "ac",
"difficulty": "Easy",
"questionId": "257",
"topicTags": "Tree, Depth-first Search"
},
{
"title": "Perfect Squares",
"titleSlug": "perfect-squares",
"status": "ac",
"difficulty": "Medium",
"questionId": "279",
"topicTags": "Breadth-first Search, Math, Dynamic Programming"
},
{
"title": "Move Zeroes",
"titleSlug": "move-zeroes",
"status": "ac",
"difficulty": "Easy",
"questionId": "283",
"topicTags": "Array, Two Pointers"
},
{
"title": "Word Pattern",
"titleSlug": "word-pattern",
"status": "ac",
"difficulty": "Easy",
"questionId": "290",
"topicTags": "Hash Table"
},
{
"title": "Odd Even Linked List",
"titleSlug": "odd-even-linked-list",
"status": "ac",
"difficulty": "Medium",
"questionId": "328",
"topicTags": "Linked List"
},
{
"title": "Flatten Nested List Iterator",
"titleSlug": "flatten-nested-list-iterator",
"status": "ac",
"difficulty": "Medium",
"questionId": "341",
"topicTags": "Stack, Design"
},
{
"title": "Reverse String",
"titleSlug": "reverse-string",
"status": "ac",
"difficulty": "Easy",
"questionId": "344",
"topicTags": "Two Pointers, String"
},
{
"title": "Top K Frequent Elements",
"titleSlug": "top-k-frequent-elements",
"status": "ac",
"difficulty": "Medium",
"questionId": "347",
"topicTags": "Heap, Hash Table"
},
{
"title": "Intersection of Two Arrays",
"titleSlug": "intersection-of-two-arrays",
"status": "ac",
"difficulty": "Easy",
"questionId": "349",
"topicTags": "Sort, Hash Table, Two Pointers, Binary Search"
},
{
"title": "Intersection of Two Arrays II",
"titleSlug": "intersection-of-two-arrays-ii",
"status": "ac",
"difficulty": "Easy",
"questionId": "350",
"topicTags": "Sort, Hash Table, Two Pointers, Binary Search"
},
{
"title": "Sum of Left Leaves",
"titleSlug": "sum-of-left-leaves",
"status": "ac",
"difficulty": "Easy",
"questionId": "404",
"topicTags": "Tree"
},
{
"title": "Find All Anagrams in a String",
"titleSlug": "find-all-anagrams-in-a-string",
"status": "ac",
"difficulty": "Medium",
"questionId": "438",
"topicTags": "Hash Table"
},
{
"title": "Add Two Numbers II",
"titleSlug": "add-two-numbers-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "445",
"topicTags": "Linked List"
},
{
"title": "Number of Boomerangs",
"titleSlug": "number-of-boomerangs",
"status": "ac",
"difficulty": "Easy",
"questionId": "447",
"topicTags": "Hash Table"
},
{
"title": "Sort Characters By Frequency",
"titleSlug": "sort-characters-by-frequency",
"status": "ac",
"difficulty": "Medium",
"questionId": "451",
"topicTags": "Heap, Hash Table"
},
{
"title": "4Sum II",
"titleSlug": "4sum-ii",
"status": "ac",
"difficulty": "Medium",
"questionId": "454",
"topicTags": "Hash Table, Binary Search"
},
{
"title": "Permutation in String",
"titleSlug": "permutation-in-string",
"status": "ac",
"difficulty": "Medium",
"questionId": "567",
"topicTags": "Two Pointers, Sliding Window"
}
]
================================================
FILE: packages/leetcode-cli/src/utils.js
================================================
/* eslint-disable no-plusplus */
const { promisify } = require('util')
const homedir = require('os').homedir()
const fs = require('fs')
const path = require('path')
let request = require('request')
const parseCookie = response =>
response.headers['set-cookie']
.map((x = '') => x.split('; '))
.reduce((acc, item) => acc.concat(item))
.reduce((acc, item) => {
const [key, value] = item.split('=')
acc[key] = value
return acc
}, {})
request = promisify(request)
request.post = promisify(request.post)
const getHeaders = session => ({
'Content-Type': 'application/json',
'x-csrftoken': session.csrftoken,
cookie: `LEETCODE_SESSION=${session.LEETCODE_SESSION};csrftoken=${session.csrftoken};`,
})
const unicodeToChar = text =>
text.replace(/\\u[\dA-F]{4}/gi, match =>
String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16))
)
const configPath = path.join(homedir, '.crd-leetcode.json')
const getConfig = () => {
try {
const config = JSON.parse(fs.readFileSync(configPath))
return config
} catch (error) {
return {
country: undefined,
cookies: undefined,
}
}
}
const stringify = data => JSON.stringify(data, null, 2)
const setConfig = (payload = {}) => {
const config = {
...getConfig(),
...payload,
}
fs.writeFileSync(configPath, stringify(config))
}
const removeConfig = (key) => {
const config = getConfig()
config[key] = undefined
fs.writeFileSync(configPath, stringify(config))
}
const transformToMarkdownTable = (dataArr) => {
let result =
'| # | Title | Explanation | Difficulty | Type |' +
'\n' +
'|:---:|:---:|:---:|:---:|:---:|'
for (let i = 0; i < dataArr.length; i++) {
result += `\n| ${dataArr[i].questionId} | [${
dataArr[i].title
}](https://leetcode.com/problems/${
dataArr[i].titleSlug
}/) | [Analyze](https://github.com/MuYunyun/blog/blob/main/LeetCode/${
dataArr[i].questionId
}.${dataArr[i].title.split(' ').join('_')}.md) | ${
dataArr[i].difficulty
} | ${dataArr[i].topicTags} |`
}
return result
}
module.exports = {
parseCookie,
request,
getHeaders,
unicodeToChar,
getConfig,
setConfig,
removeConfig,
stringify,
transformToMarkdownTable,
}
================================================
FILE: utils/uppackage-dev.sh
================================================
lerna add $1 --scope=$2 --dev
================================================
FILE: utils/uppackage.sh
================================================
lerna add $1 --scope=$2