Skip to content

Commit

Permalink
Add plugin docs to algolia search index
Browse files Browse the repository at this point in the history
Also move search bar to primary navigation and improve the design.

Committed-by: Eduard Heimbuch <[email protected]>
  • Loading branch information
Pilopa authored and SCM-Manager committed Feb 6, 2023
1 parent 6e57cdf commit 3221506
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ GATSBY_ALGOLIA_SEARCH_KEY=?
ALGOLIA_ADMIN_KEY=?
```
Replace the question marks with values from the [Algolia](algolia.com) web app.
Do not use the production application (SCM-Manager Website)!
Do not use the production application (SCM-Manager Website) or use `GATSBY_ALGOLIA_INDEX` to set a custom test index!

Afterwards you have to run the build script once, so that the index is published to Algolia.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"prism-react-renderer": "^1.0.2",
"prismjs": "^1.27.0",
"prop-types": "^15.7.2",
"qs": "^6.11.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
Expand Down
16 changes: 10 additions & 6 deletions src/components/search/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import algoliasearch from "algoliasearch/lite";
// @ts-ignore
import React, { createRef, useMemo, useState } from "react";
import { Configure, InstantSearch } from "react-instantsearch-dom";
import { InstantSearch } from "react-instantsearch-dom";
import styled, { ThemeProvider } from "styled-components";
import SearchBox from "./SearchBox";
import StyledSearchResult from "./StyledSearchResult";
Expand All @@ -17,7 +17,12 @@ const theme = {
faded: "#888",
};

export default function Search({ indices, version }) {
const SEARCH_INDEX = {
name: process.env.GATSBY_ALGOLIA_INDEX || "Pages",
title: process.env.GATSBY_ALGOLIA_INDEX || "Pages",
};

export default function Search() {
const rootRef = createRef();
const [query, setQuery] = useState();
const [hasFocus, setFocus] = useState(false);
Expand All @@ -34,17 +39,16 @@ export default function Search({ indices, version }) {

return (
<ThemeProvider theme={theme}>
<StyledSearchRoot ref={rootRef}>
<StyledSearchRoot ref={rootRef} className="navbar-item">
<InstantSearch
searchClient={searchClient}
indexName={indices[0].name}
indexName={SEARCH_INDEX.name}
onSearchStateChange={({ query }) => setQuery(query)}
>
<Configure filters={`version:${version}`} />
<SearchBox onFocus={() => setFocus(true)} hasFocus={hasFocus} />
<StyledSearchResult
show={query && query.length > 0 && hasFocus}
indices={indices}
indices={[SEARCH_INDEX]}
/>
</InstantSearch>
</StyledSearchRoot>
Expand Down
13 changes: 10 additions & 3 deletions src/components/search/SearchBox.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
// @ts-ignore
import React from "react";
import React, { useRef } from "react";
import { connectSearchBox } from "react-instantsearch-dom";
import { faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import classNames from "classnames";
import { navigate } from "gatsby";

export default connectSearchBox(
({ refine, currentRefinement, className, onFocus }) => (
<form className={classNames(className, "mb-4")}>
<form
className={className}
onSubmit={e => {
e.preventDefault();
navigate(`/search?query=${currentRefinement}`);
refine("");
}}
>
<div className="field">
<p className="control has-icons-left">
<span className="icon is-left">
Expand Down
12 changes: 7 additions & 5 deletions src/components/search/SearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Link } from "gatsby";
import React from "react";
import {
connectStateResults,
Highlight,
Hits,
Index,
PoweredBy,
Expand All @@ -23,12 +22,15 @@ const HitCount = connectStateResults(({ searchResults }) => {
const PageHit = ({ hit }) => (
<div>
<Link to={hit.slug}>
<h4>
<Highlight attribute="title" hit={hit} tagName="mark" />
<span className="tag is-light ml-2 is-uppercase">{hit.language}</span>
</h4>
<h4>{hit.title}</h4>
</Link>
<Snippet attribute="excerpt" hit={hit} tagName="mark" />
{hit.plugin ? (
<div>
<span className="tag mt-2">{hit.plugin}</span>
</div>
) : null}
<hr />
</div>
);

Expand Down
26 changes: 16 additions & 10 deletions src/components/search/StyledSearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ import SearchResult from "./SearchResult";

const Popover = css`
max-height: 80vh;
overflow: scroll;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
position: absolute;
z-index: 200;
right: 0;
right: 10px;
top: 100%;
margin-top: 0.5em;
width: 80vw;
max-width: 30em;
box-shadow: 0 0 5px 0;
max-width: 40em;
padding: 1em;
border-radius: 2px;
border: 1px solid #dbdbdb;
border-radius: 0.25rem;
box-shadow: 0 0.5em 1em -0.125em hsl(0deg 0% 4% / 10%),
0 0 0 1px hsl(0deg 0% 4% / 2%);
background: ${({ theme }) => theme.background};
margin-top: 0;
@media (min-width: 1024px) {
margin-top: -2rem;
}
`;

export default styled(SearchResult)`
Expand All @@ -34,15 +40,15 @@ export default styled(SearchResult)`
}
li.ais-Hits-item {
margin-bottom: 1em;
a {
color: ${({ theme }) => theme.foreground};
h4 {
margin-bottom: 0.2em;
}
}
hr {
margin: 1rem 0;
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/layout/NavbarMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { NavbarProps } from "./NavbarProps";
import NavbarItem from "./NavbarItem";
import useLatestRelease from "../hooks/useLatestRelease";
import Search from "../components/search/Search";

type Props = NavbarProps;

Expand All @@ -20,8 +21,9 @@ const NavbarMenu = ({ active, toggleActive }: Props) => {
};

return (
<div className={classes} onClick={closeOnMobile}>
<div className={classes}>
<div className="navbar-end">
<Search />
<NavbarItem to="/" value="Home" />
<NavbarItem to="/blog/" value="Blog" partiallyActive={true} />
<NavbarItem
Expand Down
45 changes: 40 additions & 5 deletions src/lib/algoliaQueries.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const pagePath = `content\/docs`;
const indexName = `Pages`;
const lodash = require("lodash");
const semver = require("semver");

const pagePath = `docs`;
const DEFAULT_INDEX_NAME = `Pages`;

const pageQuery = `{
pages: allMarkdownRemark(
Expand All @@ -17,6 +20,7 @@ const pageQuery = `{
slug
language
version
plugin
}
excerpt(pruneLength: 5000)
internal {
Expand All @@ -27,7 +31,14 @@ const pageQuery = `{
}
}`;

function pageToAlgoliaRecord({ node: { id, frontmatter, fields, ...rest } }) {
function pageToAlgoliaRecord({
node: {
id,
frontmatter,
fields: { semverVersion, ...fields },
...rest
},
}) {
return {
objectID: fields.slug,
...frontmatter,
Expand All @@ -36,11 +47,35 @@ function pageToAlgoliaRecord({ node: { id, frontmatter, fields, ...rest } }) {
};
}

function filterToLatest(edges) {
edges.forEach(
edge =>
(edge.node.fields.semverVersion = semver.coerce(edge.node.fields.version))
);
const grouped = lodash.groupBy(edges, "node.fields.plugin");
const mapped = lodash.mapValues(grouped, items =>
items.filter(
item =>
!items.some(
other =>
other.node.fields.semverVersion !==
item.node.fields.semverVersion &&
semver.gt(
other.node.fields.semverVersion,
item.node.fields.semverVersion
)
)
)
);
return lodash.flatten(Object.values(mapped));
}

const queries = [
{
query: pageQuery,
transformer: ({ data }) => data.pages.edges.map(pageToAlgoliaRecord),
indexName,
transformer: ({ data }) =>
filterToLatest(data.pages.edges).map(pageToAlgoliaRecord),
indexName: process.env.GATSBY_ALGOLIA_INDEX || DEFAULT_INDEX_NAME,
settings: { attributesToSnippet: [`excerpt:20`] },
},
];
Expand Down
83 changes: 83 additions & 0 deletions src/pages/search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import SEO from "../components/SEO";
import React, { useEffect, useMemo, useRef, useState } from "react";
import Title from "../components/Title";
import PageContainer from "../layout/PageContainer";
import { InstantSearch } from "react-instantsearch-dom";
import algoliasearch from "algoliasearch/lite";
import SearchResult from "../components/search/SearchResult";
import SearchBox from "../components/search/SearchBox";
import qs from "qs";
import { navigate } from "@reach/router";
import styled from "styled-components";

const DEBOUNCE_TIME = 400;

const createURL = state => `?${qs.stringify(state)}`;

const searchStateToUrl = searchState =>
searchState ? createURL(searchState) : "";

const urlToSearchState = ({ search }) => qs.parse(search.slice(1));

const SEARCH_INDEX = {
name: process.env.GATSBY_ALGOLIA_INDEX || "Pages",
title: process.env.GATSBY_ALGOLIA_INDEX || "Pages",
};

const StyledResult = styled(SearchResult)`
.HitCount {
display: flex;
justify-content: flex-end;
}
hr {
margin: 1rem 0;
}
`;

const SearchPage = ({ location }) => {
const [searchState, setSearchState] = useState(urlToSearchState(location));
const debouncedSetStateRef = useRef(null);
const searchClient = useMemo(
() =>
algoliasearch(
process.env.GATSBY_ALGOLIA_APP_ID,
process.env.GATSBY_ALGOLIA_SEARCH_KEY
),
[]
);

function onSearchStateChange(updatedSearchState) {
clearTimeout(debouncedSetStateRef.current);

debouncedSetStateRef.current = setTimeout(
() => navigate(searchStateToUrl(updatedSearchState)),
DEBOUNCE_TIME
);

setSearchState(updatedSearchState);
}

useEffect(() => {
setSearchState(urlToSearchState(location));
}, [location]);

return (
<PageContainer>
<SEO title="Search" />
<Title>Search</Title>
<InstantSearch
searchClient={searchClient}
indexName={SEARCH_INDEX.name}
searchState={searchState}
onSearchStateChange={onSearchStateChange}
createURL={createURL}
>
<SearchBox className="mb-4" />
<StyledResult indices={[SEARCH_INDEX]} />
</InstantSearch>
</PageContainer>
);
};

export default SearchPage;
4 changes: 0 additions & 4 deletions src/templates/doc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ import {
} from "../components/NavigationSettings";
import CanonicalLink from "../components/CanonicalLink";
import WarningBanner from "../components/WarningBanner";
import Search from "../components/search/Search";

const searchIndices = [{ name: `Pages`, title: `Pages` }];

const renderToc = page => {
if (page.frontmatter.displayToc) {
Expand Down Expand Up @@ -59,7 +56,6 @@ const Doc: FC<PageProps<any, PageContext>> = ({ data, pageContext }) => (
<HtmlContent content={data.markdownRemark.html} />
</div>
<div className="column is-one-quarter">
<Search indices={searchIndices} version={pageContext.latestVersion} />
<DocNavigation
versions={data.versions}
languages={data.languages}
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10804,7 +10804,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee"
integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==

qs@^6.9.4:
qs@^6.11.0, qs@^6.9.4:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
Expand Down

0 comments on commit 3221506

Please sign in to comment.