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 \ \ \ _.-; \ \ _.-" : \ \,-" _.-" \( _.-" `--" [![npm version](https://img.shields.io/npm/v/create-react-doc)](https://badge.fury.io/js/create-react-doc) [![week download](https://img.shields.io/npm/dw/create-react-doc.svg)](https://www.npmjs.com/package/create-react-doc) ![views](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/views.svg) ![views](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/views_per_week.svg) ![clones](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/clones_per_week.svg) ![LICENSE MIT](https://img.shields.io/npm/l/create-react-doc.svg) 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。 ![](http://with.muyunyun.cn/90d3e357a31649b9466a828a92b6d88d.jpg) ![](http://with.muyunyun.cn/2e7440e4256debda2d73a4e6392c7146.jpg-300) 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 ``` ![](http://with.muyunyun.cn/0f0cf6e8cb68b18399eac2927f74b063.jpg) > 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. ![](http://with.muyunyun.cn/ea24d511f76efe5ba5d13bb6b1609aac.jpg) 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 \ \ \ _.-; \ \ _.-" : \ \,-" _.-" \( _.-" `--" [![npm version](https://img.shields.io/npm/v/create-react-doc)](https://badge.fury.io/js/create-react-doc) [![week download](https://img.shields.io/npm/dw/create-react-doc.svg)](https://www.npmjs.com/package/create-react-doc) ![views](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/views.svg) ![views](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/views_per_week.svg) ![clones](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/clones_per_week.svg) ![LICENSE MIT](https://img.shields.io/npm/l/create-react-doc.svg) [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)。 ![](http://with.muyunyun.cn/90d3e357a31649b9466a828a92b6d88d.jpg) ![](http://with.muyunyun.cn/2e7440e4256debda2d73a4e6392c7146.jpg-300) 如果您想定制化或者分享个人主题,可以参考[自定义主题](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 ``` ![](http://with.muyunyun.cn/0f0cf6e8cb68b18399eac2927f74b063.jpg) > 如果想把模板内容内容拉取到当前文件夹, 则可以将如上命令的 `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` 即可以完成站点的自动部署。 ![](http://with.muyunyun.cn/ea24d511f76efe5ba5d13bb6b1609aac.jpg) 如果是第一次部署, 在执行以下操作后, 需要在项目的 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 } 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` 用来一键创建主题包开发环境。 ![](http://with.muyunyun.cn/2e4a4b11f96c0d38759700c05fe96267.gif) ```js // 安装 create-react-doc yarn add create-react-doc -g // 执行 react-doc theme 并输入主题包名字 react-doc theme ``` 进入到所创建主题目录, 执行 `yarn && yarn start`, 此时会自动打开浏览器, 并在屏幕中央显示 `Write docs happily now.`。如下图所示: ![](http://with.muyunyun.cn/1a2bf34700afd77a95014d2d5f359ffa.jpg) 恭喜你, 此时你已经将主题开发环境配置完成。接着便可以开始愉快地定制个人主题了。 在所创建的主题项目中使用了 `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) * ![](http://with.muyunyun.cn/90d3e357a31649b9466a828a92b6d88d.jpg) * ![](http://with.muyunyun.cn/2e7440e4256debda2d73a4e6392c7146.jpg-300) * [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' ``` ================================================ 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: ![](http://with.muyunyun.cn/1938e43a45410090e8486e495e6d9fee.jpg) #### 技术细节 * 使用 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 ``` ![](http://with.muyunyun.cn/0f0cf6e8cb68b18399eac2927f74b063.jpg) > 如果想把模板内容内容拉取到当前文件夹, 则可以将如上命令的 `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` 即可以完成站点的自动部署。 ![](http://with.muyunyun.cn/ea24d511f76efe5ba5d13bb6b1609aac.jpg) 如果是第一次部署, 在执行以下操作后, 需要在项目的 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) * ![](http://with.muyunyun.cn/90d3e357a31649b9466a828a92b6d88d.jpg) * ![](http://with.muyunyun.cn/2e7440e4256debda2d73a4e6392c7146.jpg-300) * [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 (
{children}
) } 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 ? (
) : ( <>
{`Powered by${' '}`} Create React Doc .
|
)}
) } 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 && logo} {!isMobile && ( {(DOCSCONFIG && DOCSCONFIG.title) || 'Create React Doc'} )}
{DOCSCONFIG && DOCSCONFIG.search ? : null}
{ tags ? 标签 : null } } checkedIcon={ moon } />
) } 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 => ` ` /** * '' => * ' viewBox="0 0 1024 1024"> { 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 (
    {children}
) } 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' ? (
      {renderChild()}
    ) : 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 (

    404

    你似乎来到了没有知识存在的荒原...
    在 github 访问该项目
    ) } 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 (
    { setValue(e.target.value) }} /> {/* {showSearchContent ? (
      {searchContent.map((search) => { return (
    • { location.hash = search.url; }} key={search.url} > {search.title} {search.content}
    • ); })}
    ) : null} */}
    ) } 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 (
    {user && repo && isNotTagPage ? ( Edit in GitHub ) : null}
    ) } /** * 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 ( <>
    { 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 \ \ \ _.-; \ \ _.-" : \ \,-" _.-" \( _.-" `--" [![npm version](https://img.shields.io/npm/v/create-react-doc)](https://badge.fury.io/js/create-react-doc) [![week download](https://img.shields.io/npm/dw/create-react-doc.svg)](https://www.npmjs.com/package/create-react-doc) ![views](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/views.svg) ![views](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/views_per_week.svg) ![clones](https://raw.githubusercontent.com/MuYunyun/create-react-doc/traffic/traffic-create-react-doc/clones_per_week.svg) ![LICENSE MIT](https://img.shields.io/npm/l/create-react-doc.svg) # 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) * ![](http://with.muyunyun.cn/90d3e357a31649b9466a828a92b6d88d.jpg) * ![](http://with.muyunyun.cn/2e7440e4256debda2d73a4e6392c7146.jpg-300) * [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 ``` ![](http://with.muyunyun.cn/0f0cf6e8cb68b18399eac2927f74b063.jpg) > 如果想把模板内容内容拉取到当前文件夹, 则可以将如上命令的 `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 (