Skip to content

Commit f75a4b4

Browse files
committed
Initial commit
0 parents  commit f75a4b4

File tree

9 files changed

+666
-0
lines changed

9 files changed

+666
-0
lines changed

.gitignore

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
2+
# Created by https://www.gitignore.io/api/node
3+
# Edit at https://www.gitignore.io/?templates=node
4+
5+
### Node ###
6+
# Logs
7+
logs
8+
*.log
9+
npm-debug.log*
10+
yarn-debug.log*
11+
yarn-error.log*
12+
lerna-debug.log*
13+
14+
# Diagnostic reports (https://nodejs.org/api/report.html)
15+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16+
17+
# Runtime data
18+
pids
19+
*.pid
20+
*.seed
21+
*.pid.lock
22+
23+
# Directory for instrumented libs generated by jscoverage/JSCover
24+
lib-cov
25+
26+
# Coverage directory used by tools like istanbul
27+
coverage
28+
*.lcov
29+
30+
# nyc test coverage
31+
.nyc_output
32+
33+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34+
.grunt
35+
36+
# Bower dependency directory (https://bower.io/)
37+
bower_components
38+
39+
# node-waf configuration
40+
.lock-wscript
41+
42+
# Compiled binary addons (https://nodejs.org/api/addons.html)
43+
build/Release
44+
45+
# Dependency directories
46+
node_modules/
47+
jspm_packages/
48+
49+
# TypeScript v1 declaration files
50+
typings/
51+
52+
# TypeScript cache
53+
*.tsbuildinfo
54+
55+
# Optional npm cache directory
56+
.npm
57+
58+
# Optional eslint cache
59+
.eslintcache
60+
61+
# Optional REPL history
62+
.node_repl_history
63+
64+
# Output of 'npm pack'
65+
*.tgz
66+
67+
# Yarn Integrity file
68+
.yarn-integrity
69+
70+
# dotenv environment variables file
71+
.env
72+
.env.test
73+
74+
# parcel-bundler cache (https://parceljs.org/)
75+
.cache
76+
77+
# next.js build output
78+
.next
79+
80+
# nuxt.js build output
81+
.nuxt
82+
83+
# rollup.js default build output
84+
dist/
85+
86+
# Uncomment the public line if your project uses Gatsby
87+
# https://nextjs.org/blog/next-9-1#public-directory-support
88+
# https://create-react-app.dev/docs/using-the-public-folder/#docsNav
89+
# public
90+
91+
# Storybook build outputs
92+
.out
93+
.storybook-out
94+
95+
# vuepress build output
96+
.vuepress/dist
97+
98+
# Serverless directories
99+
.serverless/
100+
101+
# FuseBox cache
102+
.fusebox/
103+
104+
# DynamoDB Local files
105+
.dynamodb/
106+
107+
# Temporary folders
108+
tmp/
109+
temp/
110+
111+
# End of https://www.gitignore.io/api/node

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Christian Flach
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Local Search for Docusaurus v2
2+
3+
Local search for Docusaurus v2.
4+
5+
## Installation
6+
7+
```bash
8+
yarn add @cmfcmf/docusaurus-search-local
9+
```
10+
11+
or
12+
13+
```bash
14+
npm install @cmfcmf/docusaurus-search-local
15+
```
16+
17+
## Usage
18+
19+
Add it to the `plugins` array in `docusaurus.config.js`:
20+
21+
```js
22+
module.exports = {
23+
// ...
24+
plugins: [
25+
'@cmfcmf/docusaurus-search-local'
26+
],
27+
// ...
28+
}
29+
```
30+
31+
## License
32+
33+
MIT

package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "@cmfcmf/docusaurus-search-local",
3+
"version": "0.0.1",
4+
"description": "Local Search for Docusaurus",
5+
"main": "src/index.js",
6+
"author": "Christian Flach",
7+
"license": "MIT",
8+
"dependencies": {
9+
"autocomplete.js": "^0.37.0",
10+
"classnames": "^2.2.6",
11+
"htmlparser2": "^4.0.0",
12+
"lunr": "^2.3.8"
13+
},
14+
"engines": {
15+
"node": ">=10.9.0"
16+
},
17+
"peerDependencies": {
18+
"@docusaurus/core": "^2.0.0"
19+
}
20+
}

src/index.js

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const util = require("util");
4+
5+
const readFileAsync = util.promisify(fs.readFile);
6+
const writeFileAsync = util.promisify(fs.writeFile);
7+
8+
const lunr = require("lunr");
9+
const htmlparser2 = require("htmlparser2");
10+
11+
function html2text(html) {
12+
let pageTitle = "";
13+
const sections = [];
14+
let sectionTitle = "";
15+
let sectionContent = "";
16+
let hashLink = "";
17+
18+
let insideMain = false;
19+
let insideArticle = false;
20+
let insideHeader = false;
21+
let insidePre = false;
22+
let insideH1 = false;
23+
let insideH2 = false;
24+
let insideH3 = false;
25+
let insideHashLink = false;
26+
let insideTitle = false;
27+
let insideSVG = false;
28+
29+
function sectionEnd() {
30+
if (sections.length === 0) {
31+
return;
32+
}
33+
sections[sections.length - 1].content = sectionContent.trim();
34+
sectionContent = "";
35+
}
36+
37+
function sectionStart() {
38+
sections.push({
39+
title: sectionTitle.trim(),
40+
hash: hashLink
41+
});
42+
sectionTitle = "";
43+
}
44+
45+
const parser = new htmlparser2.Parser(
46+
{
47+
onopentag(tagname, attributes) {
48+
if (["h1", "h2", "h3"].includes(tagname)) {
49+
sectionEnd();
50+
}
51+
52+
if (tagname === "main") {
53+
insideMain = true;
54+
} else if (tagname === "article") {
55+
insideArticle = true;
56+
} else if (tagname === "pre") {
57+
insidePre = true;
58+
} else if (tagname === "header") {
59+
insideHeader = true;
60+
} else if (tagname === "h1") {
61+
insideH1 = true;
62+
} else if (tagname === "h2") {
63+
insideH2 = true;
64+
} else if (tagname === "h3") {
65+
insideH3 = true;
66+
} else if (tagname === "a" && attributes["class"] === "hash-link") {
67+
insideHashLink = true;
68+
hashLink = attributes["href"];
69+
} else if (tagname === "title") {
70+
insideTitle = true;
71+
} else if (tagname === "svg") {
72+
insideSVG = true;
73+
}
74+
},
75+
ontext(text) {
76+
if (insideSVG) {
77+
return;
78+
}
79+
if (insideMain && !insideHashLink) {
80+
if (text.length) {
81+
if (insideH1 || insideH2 || insideH3) {
82+
sectionTitle += text;
83+
} else {
84+
sectionContent += text;
85+
}
86+
}
87+
} else if (insideTitle) {
88+
pageTitle += text;
89+
}
90+
},
91+
onclosetag(tagname) {
92+
if (tagname === "main") {
93+
insideMain = false;
94+
} else if (tagname === "article") {
95+
insideArticle = false;
96+
} else if (tagname === "pre") {
97+
insidePre = false;
98+
} else if (tagname === "header") {
99+
insideHeader = false;
100+
} else if (tagname === "h1") {
101+
insideH1 = false;
102+
} else if (tagname === "h2") {
103+
insideH2 = false;
104+
} else if (tagname === "h3") {
105+
insideH3 = false;
106+
} else if (tagname === "a" && insideHashLink) {
107+
insideHashLink = false;
108+
} else if (tagname === "title") {
109+
insideTitle = false;
110+
} else if (tagname === "svg") {
111+
insideSVG = false;
112+
}
113+
114+
if (insideMain && ["h1", "h2", "h3"].includes(tagname)) {
115+
if (insideArticle && insideHeader) {
116+
pageTitle = sectionTitle.trim();
117+
}
118+
sectionStart();
119+
} else if (!insidePre) {
120+
sectionTitle += " ";
121+
sectionContent += " ";
122+
}
123+
}
124+
},
125+
{ decodeEntities: true, lowerCaseTags: true }
126+
);
127+
parser.write(html);
128+
parser.end();
129+
130+
sectionEnd();
131+
132+
return { pageTitle, sections };
133+
}
134+
135+
module.exports = function(context, options) {
136+
return {
137+
name: 'docusaurus-plugin',
138+
getThemePath() {
139+
return path.resolve(__dirname, './theme');
140+
},
141+
async postBuild({ routesPaths = [], outDir, baseUrl }) {
142+
const data = routesPaths.map(route => {
143+
let file = route;
144+
if (file.startsWith(baseUrl)) {
145+
file = file.replace(baseUrl, "");
146+
}
147+
if (!file.endsWith(".html")) {
148+
if (!file.endsWith("/")) {
149+
file += "/";
150+
}
151+
file += "index.html";
152+
}
153+
file = path.join(outDir, file);
154+
155+
return {
156+
file,
157+
route
158+
};
159+
});
160+
161+
const documents = (await Promise.all(data.map(async ({ file, route }) => {
162+
const html = await readFileAsync(file, { encoding: "utf8" });
163+
164+
const { pageTitle, sections } = html2text(html);
165+
166+
return sections.map(section => ({
167+
pageTitle,
168+
pageRoute: route,
169+
sectionRoute: route + section.hash,
170+
sectionTitle: section.title,
171+
sectionContent: section.content
172+
}));
173+
}))).reduce((acc, val) => acc.concat(val), []); // .flat()
174+
175+
const index = lunr(function () {
176+
this.ref("route");
177+
this.field("title");
178+
this.field("content");
179+
documents.forEach(function ({ sectionRoute, sectionTitle, sectionContent }) {
180+
this.add({
181+
route: sectionRoute,
182+
title: sectionTitle,
183+
content: sectionContent
184+
});
185+
}, this)
186+
});
187+
188+
await writeFileAsync(
189+
path.join(outDir, "search-index.json"),
190+
JSON.stringify({
191+
documents: documents.map(({ pageTitle, sectionTitle, sectionRoute }) => ({ pageTitle, sectionTitle, sectionRoute })),
192+
index
193+
}),
194+
{ encoding: "utf8" }
195+
);
196+
},
197+
};
198+
};

0 commit comments

Comments
 (0)