Docs: add basic search feature for install and API

This commit is contained in:
Lovell Fuller 2020-04-14 21:26:41 +01:00
parent 1d6ef630a5
commit 70e730bb67
6 changed files with 179 additions and 6 deletions

View File

@ -4,9 +4,10 @@
"public": ".", "public": ".",
"ignore": [ "ignore": [
".*", ".*",
"*.json", "firebase.json",
"*.md", "*.md",
"image/**" "image/**",
"search-index/**"
], ],
"headers": [ "headers": [
{ {

View File

@ -55,11 +55,11 @@
<script src="https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/docute-google-analytics@1/dist/index.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/docute-google-analytics@1/dist/index.min.js"></script>
<script> <script>
var docuteApiTitlePlugin = { const docuteApiTitlePlugin = {
name: 'apiTitle', name: 'apiTitle',
extend(api) { extend(api) {
api.processMarkdown(function (md) { api.processMarkdown(function (md) {
var title = api.store.getters.sidebarLinks const title = api.store.getters.sidebarLinks
.filter(function (sidebarLink) { .filter(function (sidebarLink) {
return sidebarLink.link === api.router.history.current.path return sidebarLink.link === api.router.history.current.path
}) })
@ -72,6 +72,37 @@
}); });
} }
}; };
const docuteApiSearchPlugin = {
name: 'apiSearch',
extend(api) {
api.enableSearch({
handler: function (keyword) {
if (keyword.trim().length > 2) {
return fetch('/search-index.json')
.then(function (res) {
return res.json();
})
.then(function (entries) {
const results = [];
entries.forEach(function (entry) {
if (entry.k.includes(keyword)) {
results.push({
title: entry.t,
link: entry.l,
description: entry.d,
label: entry.l.startsWith("/api") ? "API" : "Install"
});
}
})
return results.slice(0, 3);
})
} else {
return [];
}
}
});
}
};
new Docute({ new Docute({
router: { mode: 'history' }, router: { mode: 'history' },
logo: '<div style="display:flex;align-items:center">' logo: '<div style="display:flex;align-items:center">'
@ -85,7 +116,8 @@
footer: '<a href="https://pixelplumbing.com/" target="_blank">pixelplumbing.com<a>', footer: '<a href="https://pixelplumbing.com/" target="_blank">pixelplumbing.com<a>',
plugins: [ plugins: [
docuteGoogleAnalytics('UA-13034748-12'), docuteGoogleAnalytics('UA-13034748-12'),
docuteApiTitlePlugin docuteApiTitlePlugin,
docuteApiSearchPlugin
], ],
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.2/docs', sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.2/docs',
nav: [ nav: [

1
docs/search-index.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,59 @@
'use strict';
const fs = require('fs');
const { extractDescription, extractKeywords } = require('./extract');
const searchIndex = [];
// Install
const contents = fs.readFileSync(`${__dirname}/../install.md`, 'utf8');
const matches = contents.matchAll(
/## (?<title>[A-Za-z ]+)\n\n(?<body>[^#]+)/gs
);
for (const match of matches) {
const { title, body } = match.groups;
const description = extractDescription(body);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/install#${title.toLowerCase().replace(/ /g, '-')}`
});
}
// API
[
'constructor',
'input',
'output',
'resize',
'composite',
'operation',
'channel',
'colour',
'utility'
].forEach((section) => {
const contents = fs.readFileSync(`${__dirname}/../api-${section}.md`, 'utf8');
const matches = contents.matchAll(
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n/gs
);
for (const match of matches) {
const { title, firstparagraph } = match.groups;
const description = firstparagraph.startsWith('###')
? 'Constructor'
: extractDescription(firstparagraph);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/api-${section}#${title.toLowerCase()}`
});
}
});
fs.writeFileSync(
`${__dirname}/../search-index.json`,
JSON.stringify(searchIndex)
);

View File

@ -0,0 +1,80 @@
'use strict';
const stopWords = [
'a',
'about',
'all',
'already',
'always',
'an',
'and',
'any',
'are',
'as',
'at',
'be',
'been',
'by',
'can',
'do',
'does',
'each',
'either',
'etc',
'for',
'from',
'get',
'gets',
'has',
'have',
'how',
'if',
'in',
'is',
'it',
'its',
'may',
'more',
'much',
'no',
'not',
'of',
'on',
'or',
'over',
'set',
'sets',
'should',
'that',
'the',
'their',
'there',
'therefore',
'these',
'this',
'to',
'use',
'using',
'when',
'which',
'will',
'with'
];
const extractDescription = (str) =>
str
.replace(/\(http[^)]+/g, '')
.replace(/\s+/g, ' ')
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
.replace(/\s+/g, ' ')
.substr(0, 140)
.trim();
const extractKeywords = (str) =>
str
.split(/[ -/]/)
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
.filter((word) => word.length > 2 && !stopWords.includes(word))
.join(' ');
module.exports = { extractDescription, extractKeywords };

View File

@ -76,7 +76,7 @@
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"", "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh", "test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"docs-build": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done", "docs-build": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && node docs/search-index/build",
"docs-serve": "cd docs && npx serve", "docs-serve": "cd docs && npx serve",
"docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp" "docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
}, },