实现简易前端文档检索

This commit is contained in:
wuduoyi 2020-08-20 23:56:28 +08:00
parent fdc4bf546a
commit f98173fbb9
4 changed files with 121 additions and 39 deletions

View File

@ -0,0 +1,39 @@
/**
* @file 生成给前端全文搜索用的文件
* @author wuduoyi
*/
const glob = require('glob');
const fs = require('fs');
let yaml = require('js-yaml');
var rYml = /^\s*---([\s\S]*?)---\s/;
const resultData = {docs: []};
glob('./docs/**/*.md', {}, function (er, docs) {
for (const doc of docs) {
let content = fs.readFileSync(doc, {encoding: 'utf8'});
let m = rYml.exec(content);
let info = {};
if (m && m[1]) {
info = yaml.safeLoad(m[1]);
content = content.substring(m[0].length);
}
const title = info.title || doc;
// todo: 属性列表单独处理,检索的时候优先检索
resultData.docs.push({
title: title,
// 去掉注释、换行、图片等
body: content
.replace(/<\!---.+-->/g, '')
.replace(/!?\[.*\]\(.*\)/g, '')
.replace(/\n/g, '')
.replace(/```.*```/g, '')
.toLowerCase(),
path: doc.replace('.md', '')
});
}
fs.writeFileSync('./public/docs/docs.json', JSON.stringify(resultData));
});

View File

@ -1,47 +1,79 @@
/**
* @file 实现前端文档搜索
*/
import React from 'react';
import makeSchemaRenderer from './SchemaRender';
const FormComponent = makeSchemaRenderer({
type: 'form',
mode: 'inline',
wrapWithPanel: false,
className: ':Doc-search',
controls: [
{
type: 'input-group',
size: 'sm',
controls: [
{
type: 'icon',
addOnclassName: 'no-bg no-border p-r-none p-l',
className: 'text-sm',
icon: 'search',
vendor: 'iconfont'
},
{
type: 'text',
placeholder: '搜索...',
inputClassName: 'no-border',
name: 'docsearch'
}
]
}
]
});
import Axios from 'axios';
import SearchBox from '../../src/components/SearchBox';
export default class DocSearch extends React.Component {
docs;
constructor(props) {
super(props);
this.state = {
searchResults: [],
loadError: false
};
this.onSearch = this.onSearch.bind(this);
this.onSearchCancel = this.onSearchCancel.bind(this);
}
componentDidMount() {
// const inputSelector = 'input[name="docsearch"]';
// docsearch({
// appId: 'S08MJHBHFJ',
// apiKey: '5fba814bb773d08b5d2a3f6074f926a5',
// indexName: 'gh_pages',
// inputSelector,
// debug: false
// });
Axios.get('/docs/docs.json')
.then(result => {
this.docs = result.data.docs;
})
.catch(err => {
this.setState({loadError: true});
});
}
onSearch(query) {
if (query === '') {
this.setState({searchResults: []});
return;
}
let results = [];
for (let doc of this.docs) {
let index = doc.body.indexOf(query);
if (index !== -1) {
results.push({
title: doc.title,
path: doc.path,
abstract: doc.body
.substring(Math.max(0, index - 20), index + 60)
.replace(query, `<strong>${query}</strong>`)
});
} else if (doc.title.indexOf(query) !== -1) {
results.push({
title: doc.title,
path: doc.path,
abstract: ''
});
}
}
this.setState({searchResults: results});
}
onSearchCancel() {
this.setState({searchResults: []});
}
render() {
return <FormComponent showCode={false} theme={this.props.theme} />;
const searchResults = this.state.searchResults;
return (
<div className="p-l p-t">
<SearchBox onSearch={this.onSearch} onCancel={this.onSearchCancel} />
<div className="search-result">
{searchResults.map(item => {
return (
<a href={'/' + item.path} key={`list_${item.path}`}>
<h5>{item.title}</h5>
<p dangerouslySetInnerHTML={{__html: item.abstract}} />
</a>
);
})}
</div>
</div>
);
}
}

View File

@ -123,7 +123,7 @@ a {
.gh-icon {
position: fixed;
right: 20px;
right: 15px;
top: 15px;
font-size: 22px;
padding: 0 10px;
@ -711,3 +711,13 @@ a {
}
}
}
.search-result {
background-color: #fff;
}
.dark {
.search-result {
background-color: #333538;
}
}

View File

@ -126,6 +126,7 @@
"fis3-preprocessor-js-require-css": "^0.1.3",
"font-awesome": "4.7.0",
"fs-walk": "0.0.2",
"glob": "^7.1.6",
"husky": "^2.2.0",
"jest": "^24.5.0",
"jest-canvas-mock": "^2.1.0",