Skip to content
This repository was archived by the owner on Jul 16, 2023. It is now read-only.

Commit

Permalink
feat(search): ✨ added local search engine
Browse files Browse the repository at this point in the history
  • Loading branch information
filipowm committed Aug 8, 2020
1 parent e3f3420 commit c2e0543
Show file tree
Hide file tree
Showing 21 changed files with 633 additions and 101 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,20 @@ performance, make it more configurable and easier to start with.
- customizing your page to match your branding and needs
- GitBook-like style theme, inspired by https://docs.gitbook.com/
- light / dark mode themes
- responsive design with mobile / tablet support
- rich-content and rich-text features like text formatting, graphs and diagrams,
quotes, columnar layout, emojis, highlights, live code editor,
syntax highlighting, external code snippets and many many more!
- search integration with [Algolia](https://www.algolia.com/) (local search
- capabilities are planned)
- draft pages
- search integration with [Algolia](https://www.algolia.com/)
- local search (search in a browser without need to integrate with Algolia)
- Progressive Web App which can work offline
- integration with Google Analytics
- full screen mode
- Search Engine Optimization (_SEO_) friendliness
- RSS feed
- easy way to edit content on Gitlab, Github or Bitbucket
- custom CLI to easily initialize and develop BooGi app
- easy deployment on platform of your choice

## 🔗 Docs and live Demo
Expand Down
15 changes: 12 additions & 3 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,22 @@ module.exports = {
algoliaSearchKey: null,
algoliaAdminKey: null,
excerptSize: 20000,
engine: 'algolia',
engine: 'localsearch',
placeholder: 'Search',
startComponent: 'input', // 'icon',
startComponent: 'icon', // 'input',
debounceTime: 380,
snippetLength: 22,
snippetLength: 23,
hitsPerPage: 10,
showStats: true,
localSearchEngine: {
encode: "advanced",
tokenize: "full",
threshold: 2,
resolution: 30,
depth: 20,
async: true,
worker: 5
},
pagination: {
enabled: true,
totalPages: 10,
Expand Down
169 changes: 127 additions & 42 deletions content/configuration/setting-up/search.md

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions content/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ you treat your documentation in the same way as your code.
- draft pages
- Progressive Web App which can work offline

- search integration with [Algolia](https://www.algolia.com/) (local search capabilities
are planned)
- search integration with [Algolia](https://www.algolia.com/)
- local search (search in a browser without need to integrate with Algolia)
- responsive, optimized for mobile devices
- integration with Google Analytics
- Search Engine Optimization (_SEO_) friendliness
Expand All @@ -55,7 +55,6 @@ you treat your documentation in the same way as your code.

<Info>Features listed here will arrive in Q2/Q3 2020!</Info>

- local search capabilities without need to integrate with an external system
- embed files to download
- sharing on social media
- syntax highlighting improvements (more languages, copy code snippets, show language used)
Expand Down
62 changes: 43 additions & 19 deletions gatsby-config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
// 08:46
require('dotenv').config();
const queries = require('./src/utils/algolia');
const { algolia, localsearch } = require('./src/utils/search');
const configManager = require('./src/utils/config');
const path = require('path');
const emoji = require('./src/utils/emoji');
const _ = require('lodash');
const { truncate } = require('lodash');

const config = configManager.read();
configManager.generate(__dirname + '/.generated.config.js', config);

const plugins = [
'gatsby-plugin-loadable-components-ssr',
'gatsby-plugin-sitemap',
'gatsby-plugin-instagram-embed',
'gatsby-plugin-pinterest',
Expand Down Expand Up @@ -181,7 +183,10 @@ if (config.features.rss && config.features.rss.enabled) {
const items = allMdx.edges.map((edge) => {
const frontmatter = edge.node.frontmatter;
const fields = edge.node.parent.fields;
const rawTitle = frontmatter.metaTitle && frontmatter.metaTitle.length > 0 ? frontmatter.metaTitle : frontmatter.title;
const rawTitle =
frontmatter.metaTitle && frontmatter.metaTitle.length > 0
? frontmatter.metaTitle
: frontmatter.title;
const title = emoji.clean(rawTitle);
const date = fields && fields.gitLogLatestDate ? fields.gitLogLatestDate : new Date();
const author =
Expand Down Expand Up @@ -225,30 +230,49 @@ if (config.features.rss && config.features.rss.enabled) {
`,
output: config.features.rss.outputPath,
match: config.features.rss.matchRegex,
title: config.features.rss.title ? config.features.rss.title : config.metadata.title
title: config.features.rss.title ? config.features.rss.title : config.metadata.title,
},
],
},
});
}

// check and add algolia
if (
config.features.search &&
config.features.search.enabled &&
config.features.search.algoliaAppId &&
config.features.search.algoliaAdminKey
) {
plugins.push({
resolve: `gatsby-plugin-algolia`,
options: {
appId: config.features.search.algoliaAppId, // algolia application id
apiKey: config.features.search.algoliaAdminKey, // algolia admin key to index
queries: queries(config.features.search.indexName, config.features.search.excerptSize),
chunkSize: 10000, // default: 1000
},
});
if (config.features.search && config.features.search.enabled === true) {
if (
config.features.search.algoliaAppId &&
config.features.search.algoliaAdminKey &&
config.features.search.engine.toLowerCase() === 'algolia'
) {
plugins.push({
resolve: `gatsby-plugin-algolia`,
options: {
appId: config.features.search.algoliaAppId, // algolia application id
apiKey: config.features.search.algoliaAdminKey, // algolia admin key to index
queries: algolia(config.features.search.indexName, config.features.search.excerptSize),
chunkSize: 10000, // default: 1000
},
});
plugins.push( {
resolve: require.resolve(`./plugins/gatsby-plugin-disable-localsearch`),
options: {
name: 'Boogi',
},
})
} else if (config.features.search.engine.toLowerCase() === 'localsearch') {
const conf = localsearch(config.features.search.excerptSize);
plugins.push({
resolve: 'gatsby-plugin-local-search',
options: {
engine: 'flexsearch',
engineOptions: config.features.search.localSearchEngine,
...conf,
},
});
} else {
console.warn(`Unknown search engine: ${config.features.search.engine}`);
}
}

// check and add pwa functionality
if (config.pwa && config.pwa.enabled && config.pwa.manifest) {
plugins.push({
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"gatsby-plugin-gtag": "1.0.13",
"gatsby-plugin-instagram-embed": "2.0.1",
"gatsby-plugin-layout": "1.3.10",
"gatsby-plugin-loadable-components-ssr": "2.1.0",
"gatsby-plugin-local-search": "1.1.1",
"gatsby-plugin-manifest": "2.4.21",
"gatsby-plugin-mdx": "1.2.15",
"gatsby-plugin-offline": "3.2.21",
Expand Down Expand Up @@ -80,6 +82,7 @@
"react-loadable": "5.5.0",
"react-reveal": "1.2.2",
"react-scrollspy": "3.4.3",
"react-use-flexsearch": "^0.1.1",
"react-visibility-sensor": "5.1.1",
"remark-abbr": "1.4.0",
"remark-emoji": "2.1.0",
Expand Down
59 changes: 59 additions & 0 deletions plugins/gatsby-plugin-disable-localsearch/gatsby-node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
exports.createPages = async (gatsbyContext, pluginOptions) => {
const {
actions,
createNodeId,
createContentDigest,
} = gatsbyContext
const { createNode } = actions
const {
name
} = pluginOptions

const nodeType = `localSearch${name}`;
const nodeId = createNodeId(name);
const node = {
id: nodeId,
name: name,
index: '',
store: {},
internal: {
type: nodeType,
contentDigest: createContentDigest({ index: [], store: [] }),
},
}

createNode(node)
}

exports.createSchemaCustomization = async (
gatsbyContext,
pluginOptions,
) => {
const { actions, schema } = gatsbyContext
const { createTypes } = actions
const { name } = pluginOptions

const nodeType = `localSearch${name}`;

createTypes([
schema.buildObjectType({
name: nodeType,
fields: {
name: {
type: 'String!',
description: 'The name of the index.',
},
index: {
type: 'String!',
description: 'The search index created using the selected engine.',
},
store: {
type: 'JSON!',
description:
'A JSON object used to map search results to their data.',
},
},
interfaces: ['Node'],
}),
])
}
Empty file.
11 changes: 11 additions & 0 deletions plugins/gatsby-plugin-disable-localsearch/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "gatsby-plugin-disable-localsearch",
"description": "Plugin to to disable local search",
"author": "Mateusz Filipowicz <[email protected]>",
"version": "0.1.0",
"dependencies": {
"gatsby": "2.23.4"
},
"license": "MIT",
"main": "index.js"
}
1 change: 0 additions & 1 deletion src/components/Search/Hits.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const topBottomPadding = css`
`;

export const HitsWrapper = styled.div`
height: 100%;
left: 0;
display: flex;
flex-direction: column;
Expand Down
11 changes: 9 additions & 2 deletions src/components/Search/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const CleanSearch = styled(({ ...props }) => (
}
&:hover {
svg {
fill: ${(props) => props.theme.colors.primary};
stroke: ${(props) => props.theme.colors.primary};
}
}
Expand All @@ -50,10 +49,14 @@ const Input = styled.input`

const Form = styled.form`
display: flex;
margin-top: 12px;
margin-bottom: 12px;
flex-direction: row;
align-items: center;
${onMobile} {
width: 100%;
margin-top: 8px;
margin-bottom: 8px;
}
padding: 12px 4px;
border-radius: 4px;
Expand All @@ -79,7 +82,11 @@ const Form = styled.form`
`;

const SidebarSearchInputWrapper = styled.div`
${marginLeftRight}
position: sticky;
top: 0;
background: ${(props) => props.theme.colors.background};
width: 100%;
padding: 0 24px;
`;

const SidebarSearchInput = ({ search, inputRef, showClean, ...props }) => (
Expand Down
12 changes: 10 additions & 2 deletions src/components/Search/Pagination.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import styled from '@emotion/styled';
import { ChevronLeft, ChevronRight } from 'react-feather';
import { onMobile } from '../../styles/responsive';

const Button = styled(({ refine, page, show, isCurrent, children, ...props }) => {
const changePage = (event) => {
Expand Down Expand Up @@ -47,18 +48,25 @@ const PagesList = styled.ul`
list-style: none;
margin: 0 auto;
align-items: center;
padding: 12px 0;
${onMobile} {
padding: 8px 0;
}
li {
margin: 0 1px;
cursor: pointer;
}
`;

const PagesListWrapper = styled.div`
padding-top: 14px;
padding-bottom: 2px;
border-top: 1px solid ${(props) => props.theme.search.pagination.border};
background: ${(props) => props.theme.colors.background};
width: 100%;
display: flex;
position: sticky;
bottom: 0;
box-shadow: 0 -2px 4px 0 ${(props) => props.theme.colors.shadow};
`;

const leftRightMargin = '12px';
Expand Down
22 changes: 15 additions & 7 deletions src/components/Search/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@ import { onMobile } from '../../styles/responsive';
import { visibleMobile, shadowAround } from '../../styles';

const Algolia = loadable(() => import('./algolia/'))
const LocalSearch = loadable(() => import('./localsearch/'))

const SearchWrapper = styled.div`
display: flex;
flex-direction: column;
padding: 12px 0;
padding: 0;
height: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch;
${onMobile} {
padding: 0 0 12px;
}
`;

const SearchSidebar = styled.div`
Expand Down Expand Up @@ -59,11 +57,21 @@ svg {
}
`;

const SearchEngine = React.forwardRef((props, ref) => {
const engine = config.features.search.engine.toLowerCase();
switch(engine) {
case 'algolia':
return <Algolia inputRef={ref} index={config.features.search.indexName} />
case 'localsearch':
return <LocalSearch inputRef={ref} />
}
console.warn(`Unsupported search engine: ${engine}`);
return null;
});

const Search = React.forwardRef(({ onVisibleChange, closeSelf, ...props }, ref) => {
const inputRef = useRef(null);
const searchRef = useRef(null);
const onVisibilityChange = (isVisible) => {
searchRef.current.setState({ ready: isVisible });
if (isVisible && inputRef.current) {
inputRef.current.focus();
}
Expand All @@ -79,7 +87,7 @@ const Search = React.forwardRef(({ onVisibleChange, closeSelf, ...props }, ref)
<span css={{marginLeft: '5px'}}>Close</span>
</CloseSearch>
<VisibilitySensor onChange={onVisibilityChange}>
<Algolia ref={searchRef} inputRef={inputRef} index={config.features.search.indexName} />
<SearchEngine ref={inputRef} />
</VisibilitySensor>
</SearchWrapper>
</SearchSidebar>
Expand Down
Loading

0 comments on commit c2e0543

Please sign in to comment.