Skip to content

Commit

Permalink
feat: search page to view all results
Browse files Browse the repository at this point in the history
  • Loading branch information
weareoutman committed Oct 31, 2020
1 parent 506b5c3 commit 0495e71
Show file tree
Hide file tree
Showing 21 changed files with 626 additions and 30 deletions.
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"build": "npm run build:client && npm run build:server",
"build:client": "tsc --project tsconfig.client.json",
"build:server": "tsc --project tsconfig.server.json",
"postbuild": "copyfiles -f src/client/theme/SearchBar/SearchBar.css dist/client/client/theme/SearchBar",
"postbuild": "copyfiles -u 3 \"src/client/theme/**/*.css\" dist/client/client/theme",
"release": "standard-version",
"coveralls": "coveralls < .coverage/lcov.info"
},
Expand Down Expand Up @@ -42,6 +42,9 @@
"@babel/preset-env": "^7.12.1",
"@babel/preset-react": "^7.12.1",
"@babel/preset-typescript": "^7.12.1",
"@docusaurus/module-type-aliases": "^2.0.0-alpha.66",
"@docusaurus/utils": "^2.0.0-alpha.66",
"@tsconfig/docusaurus": "^1.0.2",
"@types/debug": "^4.1.5",
"@types/enzyme": "^3.10.7",
"@types/enzyme-adapter-react-16": "^1.0.6",
Expand All @@ -50,8 +53,10 @@
"@types/jest": "^26.0.14",
"@types/klaw-sync": "^6.0.0",
"@types/lunr": "^2.3.3",
"@types/react": "^16.9.53",
"@types/react": "^16.9.55",
"@types/react-dom": "^16.9.8",
"@types/react-helmet": "^6.1.0",
"@types/react-router-dom": "^5.1.6",
"@typescript-eslint/eslint-plugin": "^4.4.1",
"@typescript-eslint/parser": "^4.4.1",
"babel-jest": "^26.5.2",
Expand All @@ -75,6 +80,7 @@
"typescript": "^4.0.3"
},
"peerDependencies": {
"@docusaurus/utils": "^2.0.0-alpha.66",
"nodejieba": "^2.4.1"
}
}
27 changes: 21 additions & 6 deletions src/client/theme/SearchBar/SearchBar.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ html[data-theme="dark"] .aa-dropdown-menu .aa-suggestion {
.doc-search-hit-tree,
.doc-search-hit-icon,
.doc-search-hit-path,
.doc-search-empty-icon {
.doc-search-empty-icon,
.doc-search-hit-footer a {
color: var(--search-local-muted-color, #969faf);
}

Expand Down Expand Up @@ -117,15 +118,19 @@ html[data-theme="dark"] .doc-search-empty-icon {

.doc-search-hit-title {
font-size: 0.9em;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}

.doc-search-hit-path {
font-size: 0.75em;
}

.doc-search-hit-path,
.doc-search-hit-title {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}

.doc-search-hit-action {
height: 20px;
width: 20px;
Expand All @@ -140,11 +145,21 @@ html[data-theme="dark"] .doc-search-empty-icon {
flex-direction: column;
align-items: center;
justify-content: center;
padding: 12px 0;
padding: var(--search-local-spacing, 12px) 0;
}

.doc-search-empty-icon {
margin-bottom: 12px;
margin-bottom: var(--search-local-spacing, 12px);
}

.doc-search-hit-footer {
text-align: center;
margin-top: var(--search-local-spacing, 12px);
font-size: 0.85em;
}

.doc-search-hit-footer a {
text-decoration: underline;
}

.aa-cursor .doc-search-hit-action-icon {
Expand Down
22 changes: 21 additions & 1 deletion src/client/theme/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { ReactElement, useCallback, useRef, useState } from "react";
import clsx from "clsx";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { useHistory } from "@docusaurus/router";
import clsx from "clsx";
import { fetchIndexes } from "./fetchIndexes";
import { SearchSourceFactory } from "../../utils/SearchSourceFactory";
import { SuggestionTemplate } from "../../utils/SuggestionTemplate.js";
Expand Down Expand Up @@ -67,6 +67,26 @@ export default function SearchBar({
templates: {
suggestion: SuggestionTemplate,
empty: EmptyTemplate,
footer: ({ query, isEmpty }: any) => {
if (isEmpty) {
return;
}
const a = document.createElement("a");
const url = `${baseUrl}search?q=${encodeURIComponent(query)}`;
a.href = url;
a.textContent = "See all results";
a.addEventListener("click", (e) => {
if (!e.ctrlKey && !e.metaKey) {
e.preventDefault();
search.autocomplete.close();
history.push(url);
}
});
const div = document.createElement("div");
div.className = "doc-search-hit-footer";
div.appendChild(a);
return div;
},
},
},
]
Expand Down
80 changes: 80 additions & 0 deletions src/client/theme/SearchPage/SearchPage.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
.searchQueryInput {
border-radius: var(--ifm-global-radius);
border: var(--ifm-global-border-width) solid
var(--ifm-color-content-secondary);
font-size: var(--ifm-font-size-base);
padding: 0.5rem;
width: 100%;
background: #fff;
margin-bottom: 1rem;
}

.searchResultItem {
padding: 1rem 0px;
border-bottom: 1px solid rgb(223, 227, 232);
}

.searchResultItem > h2 {
margin-bottom: 0;
}

.searchResultItemPath {
color: var(--ifm-color-content-secondary);
font-size: 0.8rem;
margin: 0.5rem 0px 0px;
}

.searchResultItemSummary {
font-style: italic;
margin: 0.5rem 0px 0px;
}

/* Start: pure CSS loaders */
/* https://loading.io/css/ */
.ldsRing {
display: inline-block;
position: absolute;
width: 20px;
height: 20px;
opacity: var(--search-local-loading-icon-opacity, 0.5);
}

.ldsRing div {
box-sizing: border-box;
display: block;
position: absolute;
width: 16px;
height: 16px;
margin: 2px;
border: 2px solid
var(--search-load-loading-icon-color, var(--ifm-navbar-search-input-color));
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: var(
--search-load-loading-icon-color,
var(--ifm-navbar-search-input-color)
)
transparent transparent transparent;
}

.ldsRing div:nth-child(1) {
animation-delay: -0.45s;
}

.ldsRing div:nth-child(2) {
animation-delay: -0.3s;
}

.ldsRing div:nth-child(3) {
animation-delay: -0.15s;
}

@keyframes lds-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* End: pure CSS loaders */
179 changes: 179 additions & 0 deletions src/client/theme/SearchPage/SearchPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import Layout from "@theme/Layout";
import Head from "@docusaurus/Head";
import Link from "@docusaurus/Link";
import useSearchQuery from "../hooks/useSearchQuery";

import styles from "./SearchPage.module.css";
import { fetchIndexes } from "../SearchBar/fetchIndexes";
import { SearchSourceFactory } from "../../utils/SearchSourceFactory";
import { SearchDocument, SearchResult } from "../../../shared/interfaces";
import { highlight } from "../../utils/highlight";
import { highlightStemmed } from "../../utils/highlightStemmed";
import { getStemmedPositions } from "../../utils/getStemmedPositions";

export default function SearchPage(): React.ReactElement {
const {
siteConfig: { baseUrl },
} = useDocusaurusContext();
const { searchValue, updateSearchPath } = useSearchQuery();
const [searchQuery, setSearchQuery] = useState(searchValue);
const [searchSource, setSearchSource] = useState<
(input: string, callback: (results: SearchResult[]) => void) => void
>();
const [searchResults, setSearchResults] = useState<SearchResult[]>();

const pageTitle = useMemo(
() =>
searchQuery
? `Search results for "${searchQuery}"`
: "Search the documentation",
[searchQuery]
);

useEffect(() => {
updateSearchPath(searchQuery);

if (searchSource) {
if (searchQuery) {
searchSource(searchQuery, (results) => {
setSearchResults(results);
});
} else {
setSearchResults(undefined);
}
}

// `updateSearchPath` should not be in the deps,
// otherwise will cause call stack overflow.
}, [searchQuery, searchSource]);

const handleSearchInputChange = useCallback((e) => {
setSearchQuery(e.target.value);
}, []);

useEffect(() => {
if (searchValue && searchValue !== searchQuery) {
setSearchQuery(searchValue);
}
}, [searchValue]);

useEffect(() => {
async function doFetchIndexes() {
const { wrappedIndexes, zhDictionary } = await fetchIndexes(baseUrl);
setSearchSource(() =>
SearchSourceFactory(wrappedIndexes, zhDictionary, 100)
);
}
doFetchIndexes();
}, [baseUrl]);

return (
<Layout title={pageTitle}>
<Head>
{/*
We should not index search pages
See https://github.com/facebook/docusaurus/pull/3233
*/}
<meta property="robots" content="noindex, follow" />
</Head>

<div className="container margin-vert--lg">
<h1>{pageTitle}</h1>

<form>
<input
type="search"
name="q"
className={styles.searchQueryInput}
aria-label="Search"
onChange={handleSearchInputChange}
value={searchQuery}
autoComplete="off"
autoFocus
/>
</form>

{!searchSource && searchQuery && (
<div>
<div className={styles.ldsRing}>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
)}

{searchResults &&
(searchResults.length > 0 ? (
<p>
{searchResults.length} document
{searchResults.length === 1 ? "" : "s"} found
</p>
) : (
<p>No documents were found</p>
))}

<section>
{searchResults &&
searchResults.map((item) => (
<SearchResultItem key={item.document.i} searchResult={item} />
))}
</section>
</div>
</Layout>
);
}

function SearchResultItem({
searchResult: { document, type, page, tokens, metadata },
}: {
searchResult: SearchResult;
}): React.ReactElement {
const isTitle = type === 0;
const isContent = type === 2;
const pathItems = ((isTitle
? document.b
: (page as SearchDocument).b) as string[]).slice();
const articleTitle = (isContent ? document.s : document.t) as string;
if (!isTitle) {
pathItems.push((page as SearchDocument).t);
}
return (
<article className={styles.searchResultItem}>
<h2>
<Link
to={document.u}
dangerouslySetInnerHTML={{
__html: isContent
? highlight(articleTitle, tokens)
: highlightStemmed(
articleTitle,
getStemmedPositions(metadata, "t"),
tokens,
100
),
}}
></Link>
</h2>
{pathItems.length > 0 && (
<p className={styles.searchResultItemPath}>{pathItems.join(" › ")}</p>
)}
{isContent && (
<p
className={styles.searchResultItemSummary}
dangerouslySetInnerHTML={{
__html: highlightStemmed(
document.t,
getStemmedPositions(metadata, "t"),
tokens,
100
),
}}
/>
)}
</article>
);
}
3 changes: 3 additions & 0 deletions src/client/theme/SearchPage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import SearchPage from "./SearchPage";

export default SearchPage;
Loading

0 comments on commit 0495e71

Please sign in to comment.