diff --git a/packages/create-react-doc/.eslintrc.js b/.eslintrc.js similarity index 100% rename from packages/create-react-doc/.eslintrc.js rename to .eslintrc.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b91e1d3c1..381c11455 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,17 +1,16 @@ -# How to contribute +# HOW TO CONTRIBUTE -* 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 next step; -* Switch to the new branch based master, submit the pr with after finishing the feature. +* 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 master, submit the pr with after finishing the feature. -## Dev +## DEV Run these bash command firstly. ```bash $ git clone https://github.com/MuYunyun/create-react-doc $ cd create-react-doc -$ npm install -$ npm run start +$ yarn && yarn bootstrap && yarn start ``` And now you can see the document is running at http://localhost:3000. diff --git a/config.yml b/config.yml index 82dde1bad..c38613256 100644 --- a/config.yml +++ b/config.yml @@ -17,5 +17,13 @@ branch: master # the default value of branch is master deploy_branch: gh-pages # which branch to deploy.(default: gh-pages) # publish: # if you want upload to gitlab or other git platform, you can set full git url in it +# use search plugin: provide ability for searching site globally in the site. +# default value: true +search: true +host: 'muyunyun.cn' # the url host to search +search_map: { # search_map is connected to menu props + '/packages/templates/default/Introduction': 'Introduction' +} + # Available values: en | zh-cn language: en \ No newline at end of file diff --git a/package.json b/package.json index 87f08db8a..88a25ab51 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "css-loader": "^0.28.7", "detect-port": "^1.2.2", "diana": "^1.0.2", - "directory-tree-md": "^2.0.7", "eslint": "^4.19.1", "eslint-config-airbnb": "^16.1.0", "eslint-loader": "^2.0.0", diff --git a/packages/create-react-doc/.editorconfig b/packages/create-react-doc/.editorconfig deleted file mode 100644 index 867d041ad..000000000 --- a/packages/create-react-doc/.editorconfig +++ /dev/null @@ -1,20 +0,0 @@ -# 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 diff --git a/packages/create-react-doc/.gitignore b/packages/create-react-doc/.gitignore deleted file mode 100644 index 51554dd20..000000000 --- a/packages/create-react-doc/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -node_modules -.create-react-doc-dist -package-lock.json -.cache -.DS_Store -.crd-dist/ - -*.bak -*.tem -*.log -*.temp -#.swp -*.*~ -~*.* diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 400b9e722..6a113c756 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -28,7 +28,6 @@ "css-loader": "^0.28.7", "detect-port": "^1.2.2", "diana": "^1.0.2", - "directory-tree-md": "^2.0.7", "eslint": "^4.19.1", "eslint-config-airbnb": "^16.1.0", "eslint-loader": "^2.0.0", @@ -75,5 +74,8 @@ }, "author": "muyunyun", "license": "MIT", - "gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199" + "gitHead": "ffc5e4cbc94a7356da558c2dbf46e2f39bb8b199", + "devDependencies": { + "yamljs": "^0.3.0" + } } diff --git a/packages/scripts/src/conf/createSpareWebpackPlugin.js b/packages/scripts/src/conf/createSpareWebpackPlugin.js index d2f3dc4f1..98ecf8900 100644 --- a/packages/scripts/src/conf/createSpareWebpackPlugin.js +++ b/packages/scripts/src/conf/createSpareWebpackPlugin.js @@ -1,8 +1,8 @@ -const DirectoryTree = require('directory-tree-md'); const PATH = require('path'); const UPATH = require('upath'); const write = require('write'); const fs = require('fs'); +const DirectoryTree = require('./node-directory-tree'); const { ifInGitIgnore } = require('../utils/index'); function getAllWatchPath(arr, pathArr = []) { diff --git a/packages/scripts/src/conf/node-directory-tree.js b/packages/scripts/src/conf/node-directory-tree.js index 1b41d45cc..c965d9320 100644 --- a/packages/scripts/src/conf/node-directory-tree.js +++ b/packages/scripts/src/conf/node-directory-tree.js @@ -65,6 +65,8 @@ function directoryTree(path, options, onEachFile) { item.isEmpty = contentMatch ? !String.prototype.trim.call(contentStr.replace(contentMatch[0], '')) : true; + const uglifyContent = contentStr.replace(/\s/g, ''); + item.content = uglifyContent; 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`); diff --git a/packages/scripts/src/conf/path.js b/packages/scripts/src/conf/path.js index 5a34b0743..56bdf8ecc 100644 --- a/packages/scripts/src/conf/path.js +++ b/packages/scripts/src/conf/path.js @@ -88,13 +88,13 @@ function getExcludeFoldersRegExp() { module.exports = { // markdown dir crdConf: getCrdConf(), - // docsPackage: resolveApp('./package.json'), docsGitIgnore: resolveApp('.gitignore'), docsNodeModules: resolveApp(''), docsConfig: resolveApp('config.yml'), docsReadme: resolveApp('README.md'), docsBuildDist: resolveApp('.crd-dist'), cacheDirPath: resolveApp('.cache'), + searchFilePath: resolveApp('.cache/search.js'), watchFilePath: resolveApp('.cache/watch-dir.js'), defaultHTMLPath: resolveApp('node_modules/crd-theme/index.html'), defaultTemplatePath: resolveTool('node_modules/crd-templates/default'), diff --git a/packages/scripts/src/conf/webpack.config.dev.js b/packages/scripts/src/conf/webpack.config.dev.js index aec1a09c3..a8c35ba2c 100644 --- a/packages/scripts/src/conf/webpack.config.dev.js +++ b/packages/scripts/src/conf/webpack.config.dev.js @@ -112,7 +112,7 @@ module.exports = function (cmd) { // 将模块名称添加到工厂功能,以便它们显示在浏览器分析器中。 // 当接收到热更新信号时,在浏览器 console 控制台打印更多可读性高的模块名称等信息 new webpack.NamedModulesPlugin(), - // to search + // hot reload md file new CreateSpareWebpackPlugin({ // 备用文件目录,比对是否存在,不存在生成,根据 sep 目录规则生成 path: path.join(paths.cacheDirPath, './md'), diff --git a/packages/scripts/src/conf/webpack.config.js b/packages/scripts/src/conf/webpack.config.js index b74271dd4..c1c08e5b0 100644 --- a/packages/scripts/src/conf/webpack.config.js +++ b/packages/scripts/src/conf/webpack.config.js @@ -3,7 +3,7 @@ const path = require('path'); const webpack = require('webpack'); const webpackbar = require('webpackbar'); -const { getDocsConfig } = require('../utils'); +const { getDocsConfig, getSearchContent } = require('../utils'); const paths = require('./path'); const pkg = require('../../package.json'); @@ -14,10 +14,11 @@ const define = { if (paths.crdConf && paths.crdConf.footer && typeof paths.crdConf.footer === 'string') { define.FOOTER = JSON.stringify(paths.crdConf.footer); } - /* custom define docs config */ if (paths.docsConfig) { + const searchContent = getSearchContent(); define.DOCSCONFIG = JSON.stringify(getDocsConfig()); + define.SEARCHCONTENT = searchContent && searchContent.toString(); } module.exports = { @@ -71,11 +72,12 @@ module.exports = { test: /\.md$/, use: [ { + // https://github.com/react-doc/raw-content-replace-loader/blob/master/index.js loader: require.resolve('raw-content-replace-loader'), options: { - path: path.join(paths.cacheDirPath, './md'), // 需要替换的目录 - replace: paths.projectPath, // 替换成目标目录 - sep: /___/g, // 文件名存储,文件夹+下划线间隔+文件名 + path: path.join(paths.cacheDirPath, './md'), // dir need to replace + replace: paths.projectPath, // the dir to replace + sep: /___/g, // name saved, folder + __ + file }, }, ], @@ -98,6 +100,7 @@ module.exports = { ], }, plugins: [ + // eslint-disable-next-line new-cap new webpackbar({ name: pkg.name }), new webpack.DefinePlugin({ VERSION: JSON.stringify(pkg.version), diff --git a/packages/scripts/src/server.js b/packages/scripts/src/server.js index 2b5f886be..bc9872faa 100644 --- a/packages/scripts/src/server.js +++ b/packages/scripts/src/server.js @@ -8,7 +8,7 @@ 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'); + // process.stdout.write(process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H'); } module.exports = function server(cmd) { diff --git a/packages/scripts/src/utils/index.js b/packages/scripts/src/utils/index.js index 8e0f55e05..066d803c5 100644 --- a/packages/scripts/src/utils/index.js +++ b/packages/scripts/src/utils/index.js @@ -53,3 +53,11 @@ exports.getDocsConfig = () => { } return yaml.safeLoad(fs.readFileSync(paths.docsConfig)); }; + +exports.getSearchContent = () => { + if (!fs.existsSync(paths.searchFilePath)) { + console.log('there is no find .cache/search.js in root dir!\n'); + return null; + } + return fs.readFileSync(paths.searchFilePath); +}; diff --git a/packages/scripts/src/utils/initCache.js b/packages/scripts/src/utils/initCache.js index 9e3fd46e5..13083441a 100644 --- a/packages/scripts/src/utils/initCache.js +++ b/packages/scripts/src/utils/initCache.js @@ -1,16 +1,15 @@ -const DirectoryTree = require('directory-tree-md'); const write = require('write'); const path = require('path'); const fs = require('fs'); +const DirectoryTree = require('../conf/node-directory-tree'); const paths = require('../conf/path'); -const { ifInGitIgnore } = require('./index'); +const { ifInGitIgnore, getDocsConfig } = require('./index'); function restRuctureMarkdown(items, arr = []) { items.forEach((item) => { if (item.type === 'directory') { restRuctureMarkdown(item.children, arr); } else if (/\.md$/.test(item.path)) { - // console.log('item.path', item.path); arr.push(item.path); } }); @@ -18,36 +17,89 @@ function restRuctureMarkdown(items, arr = []) { } module.exports = function (program, cb) { - const treeData = program.markdownPaths.map((path) => { - return DirectoryTree(path, { + const treeData = program.markdownPaths.map((markdownPath) => { + return DirectoryTree(markdownPath, { mdconf: true, // 存在 Markdown 设置 extensions: /\.md/, }); }); - // 缓存 Markdown 存储 Markdown - // Markdown 文件命名规则 `文件夹__文件夹__Markdown名.md` + // cache Markdown, Markdown file name rule: `folder__folder__Markdown name.md` const flatTreeData = restRuctureMarkdown(treeData); + // to collect search data + const searchData = []; + const docsConfig = getDocsConfig(); + const useSearchPlugin = docsConfig.search && docsConfig.host; // eslint-disable-next-line no-plusplus for (let i = 0; i < flatTreeData.length; i++) { const mdfile = flatTreeData[i]; - const mdfilePathInProject = mdfile.replace( + const mdfilePath = mdfile.replace( process.cwd() + path.sep, '' ); // generate file cache only it isn't in .gitignore - if (!ifInGitIgnore(mdfilePathInProject)) { - const underlineFileName = mdfilePathInProject.split(path.sep).join('___'); - let writeMarkdownPath = path.resolve( + if (!ifInGitIgnore(mdfilePath)) { + const underlineFileName = mdfilePath.split(path.sep).join('___'); + const writeMarkdownPath = path.resolve( process.cwd(), paths.cacheDirPath, - 'md' + 'md', + underlineFileName ); - writeMarkdownPath = path.resolve(writeMarkdownPath, underlineFileName); if (fs.existsSync(mdfile)) { const content = fs.readFileSync(mdfile); write.sync(writeMarkdownPath, content); } } } + + 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(), + paths.cacheDirPath, + 'search.js' + ); + write.sync( + writeSearchPath, + `${JSON.stringify( + searchData + )}` + ); + } cb(); }; diff --git a/packages/templates/default/_config.yml b/packages/templates/default/_config.yml index 5137609e8..a0ab1d5fa 100644 --- a/packages/templates/default/_config.yml +++ b/packages/templates/default/_config.yml @@ -17,5 +17,11 @@ branch: master # the default value of branch is master deploy_branch: gh-pages # which branch to deploy.(default: gh-pages) # publish: # if you want upload to gitlab or other git platform, you can set full git url in it +# use search plugin: provide ability for searching site globally in the site. +# default value: true +search: true +# host: '' # the url host to search +# search_map: {} # search_map is connected to menu props + # Available values: en | zh-cn language: en \ No newline at end of file diff --git a/packages/theme/component/Header/index.js b/packages/theme/component/Header/index.js index 846403d0e..f5238df84 100644 --- a/packages/theme/component/Header/index.js +++ b/packages/theme/component/Header/index.js @@ -3,6 +3,8 @@ import React from 'react'; import classNames from 'classnames'; import { Link } from 'react-router-dom'; import Switch from 'react-switch'; +import { isMobile } from '../../utils'; +import Search from '../Search'; import styles from './index.less'; const { useState } = React; @@ -21,12 +23,17 @@ const Header = ({ return (
- +
{logo && logo} - {(DOCSCONFIG && DOCSCONFIG.title) || 'Create React Doc'} + {!isMobile && ( + + {(DOCSCONFIG && DOCSCONFIG.title) || 'Create React Doc'} + + )}
+ {DOCSCONFIG && DOCSCONFIG.search ? : null}
', 'create-time': '', + search: + '', }; diff --git a/packages/theme/component/Search/README.md b/packages/theme/component/Search/README.md new file mode 100644 index 000000000..640d88e1e --- /dev/null +++ b/packages/theme/component/Search/README.md @@ -0,0 +1,6 @@ +### API + +| props | description | type | default | +| :---------: | :---------: | :----: | :------: | +| placeholder | placeholder | string | 'Search' | +| className | css | string | -- | diff --git a/packages/theme/component/Search/index.js b/packages/theme/component/Search/index.js new file mode 100644 index 000000000..a25fc785b --- /dev/null +++ b/packages/theme/component/Search/index.js @@ -0,0 +1,57 @@ +import React from 'react'; +import cx from 'classnames'; +import Icon from '../Icon'; +import styles from './index.less'; + +const { useState, useEffect } = React; + +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; diff --git a/packages/theme/component/Search/index.less b/packages/theme/component/Search/index.less new file mode 100644 index 000000000..cec3b9be7 --- /dev/null +++ b/packages/theme/component/Search/index.less @@ -0,0 +1,50 @@ +.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; + } +} \ No newline at end of file diff --git a/packages/theme/package.json b/packages/theme/package.json index bdd77f4da..7b9c8f25d 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -26,7 +26,6 @@ "css-loader": "^0.28.7", "detect-port": "^1.2.2", "diana": "^1.0.2", - "directory-tree-md": "^2.0.7", "eslint": "^4.19.1", "eslint-config-airbnb": "^16.1.0", "eslint-loader": "^2.0.0", diff --git a/packages/theme/style/base.less b/packages/theme/style/base.less index b0befc2e4..07004ac8c 100644 --- a/packages/theme/style/base.less +++ b/packages/theme/style/base.less @@ -1,3 +1,4 @@ // z-index -@menu-zIndex: 9999; -@menu-mask-zIndex: 9990; \ No newline at end of file +@menu-zIndex: 99; +@menu-mask-zIndex: 90; +@header-zIndex: 100; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 001fac0f2..5491c7af0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4471,13 +4471,6 @@ dir-glob@^2.2.2: dependencies: path-type "^3.0.0" -directory-tree-md@^2.0.7: - version "2.0.7" - resolved "https://registry.npmjs.org/directory-tree-md/-/directory-tree-md-2.0.7.tgz#0d264c580503e7d89c7a44721e324cdcd40c679d" - integrity sha512-ICqdSHbayuiEVqzTHH+5tnq97AEj0O+J6CHHkf8YgFK3VzLyaGNXlB5VfsX1Vs013z2ueUDn+hU/3flGzsxh7A== - dependencies: - yamljs "^0.3.0" - dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"