mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-03 04:07:32 +02:00
refactor(theme-search-algolia): migrate package to TS (#5935)
This commit is contained in:
parent
284cdabb0a
commit
425144afc7
20 changed files with 301 additions and 91 deletions
|
@ -14,6 +14,7 @@ packages/docusaurus/lib/
|
|||
packages/docusaurus-*/lib/*
|
||||
packages/docusaurus-*/lib-next/
|
||||
packages/docusaurus-plugin-ideal-image/copyUntypedFiles.js
|
||||
packages/docusaurus-theme-search-algolia/copyUntypedFiles.js
|
||||
|
||||
packages/create-docusaurus/lib/*
|
||||
packages/create-docusaurus/templates/facebook/.eslintrc.js
|
||||
|
|
|
@ -15,6 +15,6 @@ const srcDir = path.resolve(__dirname, 'src');
|
|||
const libDir = path.resolve(__dirname, 'lib');
|
||||
fs.copySync(srcDir, libDir, {
|
||||
filter(filepath) {
|
||||
return !/__tests__/.test(filepath) && !/\.ts$/.test(filepath);
|
||||
return !/__tests__/.test(filepath) && !/\.tsx?$/.test(filepath);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ const CodeDirPaths = [
|
|||
path.join(__dirname, 'lib-next'),
|
||||
// TODO other themes should rather define their own translations in the future?
|
||||
path.join(__dirname, '..', 'docusaurus-theme-common', 'lib'),
|
||||
path.join(__dirname, '..', 'docusaurus-theme-search-algolia', 'src', 'theme'),
|
||||
path.join(__dirname, '..', 'docusaurus-theme-search-algolia', 'lib', 'theme'),
|
||||
path.join(__dirname, '..', 'docusaurus-theme-live-codeblock', 'src', 'theme'),
|
||||
path.join(__dirname, '..', 'docusaurus-plugin-pwa', 'src', 'theme'),
|
||||
];
|
||||
|
|
|
@ -22,6 +22,8 @@ export {createStorageSlot, listStorageKeys} from './utils/storageUtils';
|
|||
|
||||
export {useAlternatePageUtils} from './utils/useAlternatePageUtils';
|
||||
|
||||
export {useContextualSearchFilters} from './utils/useContextualSearchFilters';
|
||||
|
||||
export {
|
||||
parseCodeBlockTitle,
|
||||
parseLanguage,
|
||||
|
|
|
@ -5,21 +5,18 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import {useAllDocsData, useActivePluginAndVersion} from '@theme/hooks/useDocs';
|
||||
import {
|
||||
useDocsPreferredVersionByPluginId,
|
||||
DEFAULT_SEARCH_TAG,
|
||||
docVersionSearchTag,
|
||||
} from '@docusaurus/theme-common';
|
||||
import {useDocsPreferredVersionByPluginId} from './docsPreferredVersion/useDocsPreferredVersion';
|
||||
import {docVersionSearchTag, DEFAULT_SEARCH_TAG} from './searchUtils';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
|
||||
type ContextualSearchFilters = {
|
||||
export type useContextualSearchFiltersReturns = {
|
||||
locale: string;
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
// We may want to support multiple search engines, don't couple that to Algolia/DocSearch
|
||||
// Maybe users will want to use its own search engine solution
|
||||
export default function useContextualSearchFilters(): ContextualSearchFilters {
|
||||
export function useContextualSearchFilters(): useContextualSearchFiltersReturns {
|
||||
const {i18n} = useDocusaurusContext();
|
||||
const allDocsData = useAllDocsData();
|
||||
const activePluginAndVersion = useActivePluginAndVersion();
|
20
packages/docusaurus-theme-search-algolia/copyUntypedFiles.js
Normal file
20
packages/docusaurus-theme-search-algolia/copyUntypedFiles.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
/**
|
||||
* Copy all untyped and static assets files to lib.
|
||||
*/
|
||||
const srcDir = path.resolve(__dirname, 'src');
|
||||
const libDir = path.resolve(__dirname, 'lib');
|
||||
fs.copySync(srcDir, libDir, {
|
||||
filter(filepath) {
|
||||
return !/__tests__/.test(filepath) && !/\.tsx?$/.test(filepath);
|
||||
},
|
||||
});
|
|
@ -2,7 +2,8 @@
|
|||
"name": "@docusaurus/theme-search-algolia",
|
||||
"version": "2.0.0-beta.9",
|
||||
"description": "Algolia search component for Docusaurus.",
|
||||
"main": "src/index.js",
|
||||
"main": "lib/index.js",
|
||||
"types": "src/theme-search-algolia.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
@ -12,6 +13,12 @@
|
|||
"directory": "packages/docusaurus-theme-search-algolia"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "yarn build:server && yarn build:browser && yarn build:copy",
|
||||
"build:server": "tsc --project tsconfig.server.json",
|
||||
"build:browser": "tsc --project tsconfig.browser.json",
|
||||
"build:copy": "node copyUntypedFiles.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docsearch/react": "^3.0.0-alpha.39",
|
||||
"@docusaurus/core": "2.0.0-beta.9",
|
||||
|
@ -24,6 +31,10 @@
|
|||
"eta": "^1.12.3",
|
||||
"lodash": "^4.17.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.0.0-beta.9",
|
||||
"fs-extra": "^10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.4 || ^17.0.0",
|
||||
"react-dom": "^16.8.4 || ^17.0.0"
|
||||
|
|
|
@ -5,26 +5,33 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const eta = require('eta');
|
||||
const {normalizeUrl, getSwizzledComponent} = require('@docusaurus/utils');
|
||||
const openSearchTemplate = require('./templates/opensearch');
|
||||
const {validateThemeConfig} = require('./validateThemeConfig');
|
||||
const {memoize} = require('lodash');
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import {defaultConfig, compile} from 'eta';
|
||||
import {normalizeUrl, getSwizzledComponent} from '@docusaurus/utils';
|
||||
import openSearchTemplate from './templates/opensearch';
|
||||
import {memoize} from 'lodash';
|
||||
|
||||
import type {DocusaurusContext, Plugin} from '@docusaurus/types';
|
||||
|
||||
const getCompiledOpenSearchTemplate = memoize(() => {
|
||||
return eta.compile(openSearchTemplate.trim());
|
||||
return compile(openSearchTemplate.trim());
|
||||
});
|
||||
|
||||
function renderOpenSearchTemplate(data) {
|
||||
function renderOpenSearchTemplate(data: {
|
||||
title: string;
|
||||
url: string;
|
||||
favicon: string | null;
|
||||
}) {
|
||||
const compiled = getCompiledOpenSearchTemplate();
|
||||
return compiled(data, eta.defaultConfig);
|
||||
return compiled(data, defaultConfig);
|
||||
}
|
||||
|
||||
const OPEN_SEARCH_FILENAME = 'opensearch.xml';
|
||||
|
||||
function theme(context) {
|
||||
export default function theme(
|
||||
context: DocusaurusContext & {baseUrl: string},
|
||||
): Plugin<void> {
|
||||
const {
|
||||
baseUrl,
|
||||
siteConfig: {title, url, favicon},
|
||||
|
@ -37,12 +44,16 @@ function theme(context) {
|
|||
return {
|
||||
name: 'docusaurus-theme-search-algolia',
|
||||
|
||||
getPathsToWatch() {
|
||||
return [pagePath];
|
||||
},
|
||||
|
||||
getThemePath() {
|
||||
return path.resolve(__dirname, './theme');
|
||||
},
|
||||
|
||||
getPathsToWatch() {
|
||||
return [pagePath];
|
||||
getTypeScriptThemePath() {
|
||||
return path.resolve(__dirname, '..', 'src', 'theme');
|
||||
},
|
||||
|
||||
async contentLoaded({actions: {addRoute}}) {
|
||||
|
@ -87,6 +98,4 @@ function theme(context) {
|
|||
};
|
||||
}
|
||||
|
||||
module.exports = theme;
|
||||
|
||||
theme.validateThemeConfig = validateThemeConfig;
|
||||
export {validateThemeConfig} from './validateThemeConfig';
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
module.exports = `
|
||||
export default `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
|
||||
xmlns:moz="http://www.mozilla.org/2006/browser/search/">
|
49
packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts
vendored
Normal file
49
packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
declare module '@docusaurus/theme-search-algolia' {
|
||||
export type Options = never;
|
||||
}
|
||||
|
||||
declare module '@theme/hooks/useSearchQuery' {
|
||||
export interface SearchQuery {
|
||||
searchQuery: string;
|
||||
|
||||
setSearchQuery(newSearchQuery: string): void;
|
||||
|
||||
generateSearchPageLink(targetSearchQuery: string): string;
|
||||
}
|
||||
|
||||
export default function useSearchQuery(): SearchQuery;
|
||||
}
|
||||
|
||||
declare module '@theme/hooks/useAlgoliaContextualFacetFilters' {
|
||||
export type useAlgoliaContextualFacetFiltersReturns = [string, string[]];
|
||||
|
||||
export default function useAlgoliaContextualFacetFilters(): useAlgoliaContextualFacetFiltersReturns;
|
||||
}
|
||||
|
||||
declare module '@theme/SearchPage' {
|
||||
const SearchPage: () => JSX.Element;
|
||||
export default SearchPage;
|
||||
}
|
||||
|
||||
declare module '@theme/SearchMetadata' {
|
||||
export type SearchMetadataProps = {
|
||||
readonly locale?: string;
|
||||
readonly version?: string;
|
||||
readonly tag?: string;
|
||||
};
|
||||
|
||||
const SearchMetadata: (props: SearchMetadataProps) => JSX.Element;
|
||||
export default SearchMetadata;
|
||||
}
|
||||
|
||||
declare module '@theme/SearchBar' {
|
||||
const SearchBar: () => JSX.Element;
|
||||
export default SearchBar;
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
import React, {useState, useRef, useCallback, useMemo} from 'react';
|
||||
import {createPortal} from 'react-dom';
|
||||
|
@ -19,13 +20,42 @@ import useAlgoliaContextualFacetFilters from '@theme/hooks/useAlgoliaContextualF
|
|||
import {translate} from '@docusaurus/Translate';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
let DocSearchModal = null;
|
||||
import type {
|
||||
DocSearchModal as DocSearchModalType,
|
||||
DocSearchModalProps,
|
||||
} from '@docsearch/react';
|
||||
import type {
|
||||
InternalDocSearchHit,
|
||||
StoredDocSearchHit,
|
||||
} from '@docsearch/react/dist/esm/types';
|
||||
import type {AutocompleteState} from '@algolia/autocomplete-core';
|
||||
|
||||
function Hit({hit, children}) {
|
||||
type DocSearchProps = Omit<
|
||||
DocSearchModalProps,
|
||||
'onClose' | 'initialScrollY'
|
||||
> & {
|
||||
contextualSearch?: string;
|
||||
externalUrlRegex?: string;
|
||||
};
|
||||
|
||||
let DocSearchModal: typeof DocSearchModalType | null = null;
|
||||
|
||||
function Hit({
|
||||
hit,
|
||||
children,
|
||||
}: {
|
||||
hit: InternalDocSearchHit | StoredDocSearchHit;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <Link to={hit.url}>{children}</Link>;
|
||||
}
|
||||
|
||||
function ResultsFooter({state, onClose}) {
|
||||
type ResultsFooterProps = {
|
||||
state: AutocompleteState<InternalDocSearchHit>;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function ResultsFooter({state, onClose}: ResultsFooterProps) {
|
||||
const {generateSearchPageLink} = useSearchQuery();
|
||||
|
||||
return (
|
||||
|
@ -35,7 +65,11 @@ function ResultsFooter({state, onClose}) {
|
|||
);
|
||||
}
|
||||
|
||||
function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
||||
function DocSearch({
|
||||
contextualSearch,
|
||||
externalUrlRegex,
|
||||
...props
|
||||
}: DocSearchProps) {
|
||||
const {siteMetadata} = useDocusaurusContext();
|
||||
|
||||
const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
|
||||
|
@ -56,10 +90,12 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|||
|
||||
const {withBaseUrl} = useBaseUrlUtils();
|
||||
const history = useHistory();
|
||||
const searchContainer = useRef(null);
|
||||
const searchButtonRef = useRef(null);
|
||||
const searchContainer = useRef<HTMLDivElement | null>(null);
|
||||
const searchButtonRef = useRef<HTMLButtonElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [initialQuery, setInitialQuery] = useState(null);
|
||||
const [initialQuery, setInitialQuery] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const importDocSearchModalIfNeeded = useCallback(() => {
|
||||
if (DocSearchModal) {
|
||||
|
@ -67,7 +103,9 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|||
}
|
||||
|
||||
return Promise.all([
|
||||
// @ts-ignore
|
||||
import('@docsearch/react/modal'),
|
||||
// @ts-ignore
|
||||
import('@docsearch/react/style'),
|
||||
import('./styles.css'),
|
||||
]).then(([{DocSearchModal: Modal}]) => {
|
||||
|
@ -88,7 +126,7 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|||
|
||||
const onClose = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
searchContainer.current.remove();
|
||||
searchContainer.current?.remove();
|
||||
}, [setIsOpen]);
|
||||
|
||||
const onInput = useCallback(
|
||||
|
@ -102,35 +140,38 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|||
);
|
||||
|
||||
const navigator = useRef({
|
||||
navigate({itemUrl}) {
|
||||
navigate({itemUrl}: {itemUrl?: string}) {
|
||||
// Algolia results could contain URL's from other domains which cannot
|
||||
// be served through history and should navigate with window.location
|
||||
if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
|
||||
window.location.href = itemUrl;
|
||||
window.location.href = itemUrl!;
|
||||
} else {
|
||||
history.push(itemUrl);
|
||||
history.push(itemUrl!);
|
||||
}
|
||||
},
|
||||
}).current;
|
||||
|
||||
const transformItems = useRef((items) => {
|
||||
return items.map((item) => {
|
||||
// If Algolia contains a external domain, we should navigate without relative URL
|
||||
if (isRegexpStringMatch(externalUrlRegex, item.url)) {
|
||||
return item;
|
||||
}
|
||||
const transformItems = useRef<DocSearchModalProps['transformItems']>(
|
||||
(items) => {
|
||||
return items.map((item) => {
|
||||
// If Algolia contains a external domain, we should navigate without relative URL
|
||||
if (isRegexpStringMatch(externalUrlRegex, item.url)) {
|
||||
return item;
|
||||
}
|
||||
|
||||
// We transform the absolute URL into a relative URL.
|
||||
const url = new URL(item.url);
|
||||
return {
|
||||
...item,
|
||||
url: withBaseUrl(`${url.pathname}${url.hash}`),
|
||||
};
|
||||
});
|
||||
}).current;
|
||||
// We transform the absolute URL into a relative URL.
|
||||
const url = new URL(item.url);
|
||||
return {
|
||||
...item,
|
||||
url: withBaseUrl(`${url.pathname}${url.hash}`),
|
||||
};
|
||||
});
|
||||
},
|
||||
).current;
|
||||
|
||||
const resultsFooterComponent = useMemo(
|
||||
() => (footerProps) => <ResultsFooter {...footerProps} onClose={onClose} />,
|
||||
() => (footerProps: ResultsFooterProps) =>
|
||||
<ResultsFooter {...footerProps} onClose={onClose} />,
|
||||
[onClose],
|
||||
);
|
||||
|
||||
|
@ -188,6 +229,8 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|||
</div>
|
||||
|
||||
{isOpen &&
|
||||
DocSearchModal &&
|
||||
searchContainer.current &&
|
||||
createPortal(
|
||||
<DocSearchModal
|
||||
onClose={onClose}
|
||||
|
@ -209,6 +252,7 @@ function DocSearch({contextualSearch, externalUrlRegex, ...props}) {
|
|||
|
||||
function SearchBar() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
// @ts-ignore
|
||||
return <DocSearch {...siteConfig.themeConfig.algolia} />;
|
||||
}
|
||||
|
|
@ -8,9 +8,14 @@
|
|||
import React from 'react';
|
||||
|
||||
import Head from '@docusaurus/Head';
|
||||
import type {SearchMetadataProps} from '@theme/SearchMetadata';
|
||||
|
||||
// Override default/agnostic SearchMetas to use Algolia-specific metadata
|
||||
export default function AlgoliaSearchMetadata({locale, version, tag}) {
|
||||
function SearchMetadata({
|
||||
locale,
|
||||
version,
|
||||
tag,
|
||||
}: SearchMetadataProps): JSX.Element {
|
||||
// Seems safe to consider here the locale is the language,
|
||||
// as the existing docsearch:language filter is afaik a regular string-based filter
|
||||
const language = locale;
|
||||
|
@ -23,3 +28,5 @@ export default function AlgoliaSearchMetadata({locale, version, tag}) {
|
|||
</Head>
|
||||
);
|
||||
}
|
||||
|
||||
export default SearchMetadata;
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
/* eslint-disable jsx-a11y/no-autofocus */
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
import React, {useEffect, useState, useReducer, useRef} from 'react';
|
||||
|
||||
|
@ -32,7 +33,7 @@ import styles from './styles.module.css';
|
|||
// Very simple pluralization: probably good enough for now
|
||||
function useDocumentsFoundPlural() {
|
||||
const {selectMessage} = usePluralForm();
|
||||
return (count) =>
|
||||
return (count: number) =>
|
||||
selectMessage(
|
||||
count,
|
||||
translate(
|
||||
|
@ -52,14 +53,19 @@ function useDocsSearchVersionsHelpers() {
|
|||
|
||||
// State of the version select menus / algolia facet filters
|
||||
// docsPluginId -> versionName map
|
||||
const [searchVersions, setSearchVersions] = useState(() => {
|
||||
return Object.entries(allDocsData).reduce((acc, [pluginId, pluginData]) => {
|
||||
return {...acc, [pluginId]: pluginData.versions[0].name};
|
||||
}, {});
|
||||
});
|
||||
const [searchVersions, setSearchVersions] = useState<Record<string, string>>(
|
||||
() => {
|
||||
return Object.entries(allDocsData).reduce(
|
||||
(acc, [pluginId, pluginData]) => {
|
||||
return {...acc, [pluginId]: pluginData.versions[0].name};
|
||||
},
|
||||
{},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Set the value of a single select menu
|
||||
const setSearchVersion = (pluginId, searchVersion) =>
|
||||
const setSearchVersion = (pluginId: string, searchVersion: string) =>
|
||||
setSearchVersions((s) => ({...s, [pluginId]: searchVersion}));
|
||||
|
||||
const versioningEnabled = Object.values(allDocsData).some(
|
||||
|
@ -75,7 +81,11 @@ function useDocsSearchVersionsHelpers() {
|
|||
}
|
||||
|
||||
// We want to display one select per versioned docs plugin instance
|
||||
const SearchVersionSelectList = ({docsSearchVersionsHelpers}) => {
|
||||
const SearchVersionSelectList = ({
|
||||
docsSearchVersionsHelpers,
|
||||
}: {
|
||||
docsSearchVersionsHelpers: ReturnType<typeof useDocsSearchVersionsHelpers>;
|
||||
}) => {
|
||||
const versionedPluginEntries = Object.entries(
|
||||
docsSearchVersionsHelpers.allDocsData,
|
||||
)
|
||||
|
@ -118,10 +128,32 @@ const SearchVersionSelectList = ({docsSearchVersionsHelpers}) => {
|
|||
);
|
||||
};
|
||||
|
||||
function SearchPage() {
|
||||
type ResultDispatcherState = {
|
||||
items: {
|
||||
title: string;
|
||||
url: string;
|
||||
summary: string;
|
||||
breadcrumbs: string[];
|
||||
}[];
|
||||
query: string | null;
|
||||
totalResults: number | null;
|
||||
totalPages: number | null;
|
||||
lastPage: number | null;
|
||||
hasMore: boolean | null;
|
||||
loading: boolean | null;
|
||||
};
|
||||
|
||||
type ResultDispatcher =
|
||||
| {type: 'reset'; value?: undefined}
|
||||
| {type: 'loading'; value?: undefined}
|
||||
| {type: 'update'; value: ResultDispatcherState}
|
||||
| {type: 'advance'; value?: undefined};
|
||||
|
||||
function SearchPage(): JSX.Element {
|
||||
const {
|
||||
siteConfig: {
|
||||
themeConfig: {
|
||||
// @ts-ignore
|
||||
algolia: {appId, apiKey, indexName, externalUrlRegex},
|
||||
},
|
||||
},
|
||||
|
@ -131,7 +163,7 @@ function SearchPage() {
|
|||
|
||||
const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers();
|
||||
const {searchQuery, setSearchQuery} = useSearchQuery();
|
||||
const initialSearchResultState = {
|
||||
const initialSearchResultState: ResultDispatcherState = {
|
||||
items: [],
|
||||
query: null,
|
||||
totalResults: null,
|
||||
|
@ -141,8 +173,8 @@ function SearchPage() {
|
|||
loading: null,
|
||||
};
|
||||
const [searchResultState, searchResultStateDispatcher] = useReducer(
|
||||
(prevState, {type, value: state}) => {
|
||||
switch (type) {
|
||||
(prevState: ResultDispatcherState, data: ResultDispatcher) => {
|
||||
switch (data.type) {
|
||||
case 'reset': {
|
||||
return initialSearchResultState;
|
||||
}
|
||||
|
@ -150,24 +182,24 @@ function SearchPage() {
|
|||
return {...prevState, loading: true};
|
||||
}
|
||||
case 'update': {
|
||||
if (searchQuery !== state.query) {
|
||||
if (searchQuery !== data.value.query) {
|
||||
return prevState;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
...data.value,
|
||||
items:
|
||||
state.lastPage === 0
|
||||
? state.items
|
||||
: prevState.items.concat(state.items),
|
||||
data.value.lastPage === 0
|
||||
? data.value.items
|
||||
: prevState.items.concat(data.value.items),
|
||||
};
|
||||
}
|
||||
case 'advance': {
|
||||
const hasMore = prevState.totalPages > prevState.lastPage + 1;
|
||||
const hasMore = prevState.totalPages! > prevState.lastPage! + 1;
|
||||
|
||||
return {
|
||||
...prevState,
|
||||
lastPage: hasMore ? prevState.lastPage + 1 : prevState.lastPage,
|
||||
lastPage: hasMore ? prevState.lastPage! + 1 : prevState.lastPage,
|
||||
hasMore,
|
||||
};
|
||||
}
|
||||
|
@ -193,7 +225,7 @@ function SearchPage() {
|
|||
return;
|
||||
}
|
||||
|
||||
const sanitizeValue = (value) => {
|
||||
const sanitizeValue = (value: string) => {
|
||||
return value.replace(
|
||||
/algolia-docsearch-suggestion--highlight/g,
|
||||
'search-result-match',
|
||||
|
@ -212,7 +244,7 @@ function SearchPage() {
|
|||
});
|
||||
|
||||
return {
|
||||
title: titles.pop(),
|
||||
title: titles.pop()!,
|
||||
url: isRegexpStringMatch(externalUrlRegex, parsedURL.href)
|
||||
? parsedURL.href
|
||||
: parsedURL.pathname + parsedURL.hash,
|
||||
|
@ -239,7 +271,7 @@ function SearchPage() {
|
|||
},
|
||||
);
|
||||
|
||||
const [loaderRef, setLoaderRef] = useState(null);
|
||||
const [loaderRef, setLoaderRef] = useState<HTMLDivElement | null>(null);
|
||||
const prevY = useRef(0);
|
||||
const observer = useRef(
|
||||
ExecutionEnvironment.canUseDOM &&
|
||||
|
@ -278,7 +310,7 @@ function SearchPage() {
|
|||
description: 'The search page title for empty query',
|
||||
});
|
||||
|
||||
const makeSearch = useDynamicCallback((page = 0) => {
|
||||
const makeSearch = useDynamicCallback((page: number = 0) => {
|
||||
algoliaHelper.addDisjunctiveFacetRefinement('docusaurus_tag', 'default');
|
||||
algoliaHelper.addDisjunctiveFacetRefinement('language', currentLocale);
|
||||
|
||||
|
@ -299,9 +331,11 @@ function SearchPage() {
|
|||
return undefined;
|
||||
}
|
||||
const currentObserver = observer.current;
|
||||
|
||||
currentObserver.observe(loaderRef);
|
||||
return () => currentObserver.unobserve(loaderRef);
|
||||
if (currentObserver) {
|
||||
currentObserver.observe(loaderRef);
|
||||
return () => currentObserver.unobserve(loaderRef);
|
||||
}
|
||||
return () => true;
|
||||
}, [loaderRef]);
|
||||
|
||||
useEffect(() => {
|
|
@ -5,10 +5,11 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import useContextualSearchFilters from '@theme/hooks/useContextualSearchFilters';
|
||||
import type {useAlgoliaContextualFacetFiltersReturns} from '@theme/hooks/useAlgoliaContextualFacetFilters';
|
||||
import {useContextualSearchFilters} from '@docusaurus/theme-common';
|
||||
|
||||
// Translate search-engine agnostic search filters to Algolia search filters
|
||||
export default function useAlgoliaContextualFacetFilters() {
|
||||
export default function useAlgoliaContextualFacetFilters(): useAlgoliaContextualFacetFiltersReturns {
|
||||
const {locale, tags} = useContextualSearchFilters();
|
||||
|
||||
// seems safe to convert locale->language, see AlgoliaSearchMetadata comment
|
|
@ -8,10 +8,11 @@
|
|||
import {useHistory} from '@docusaurus/router';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {useCallback, useEffect, useState} from 'react';
|
||||
import type {SearchQuery} from '@theme/hooks/useSearchQuery';
|
||||
|
||||
const SEARCH_PARAM_QUERY = 'q';
|
||||
|
||||
function useSearchQuery() {
|
||||
function useSearchQuery(): SearchQuery {
|
||||
const history = useHistory();
|
||||
const {
|
||||
siteConfig: {baseUrl},
|
||||
|
@ -28,7 +29,7 @@ function useSearchQuery() {
|
|||
}, []);
|
||||
|
||||
const setSearchQuery = useCallback(
|
||||
(newSearchQuery) => {
|
||||
(newSearchQuery: string) => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (newSearchQuery) {
|
||||
|
@ -46,7 +47,7 @@ function useSearchQuery() {
|
|||
);
|
||||
|
||||
const generateSearchPageLink = useCallback(
|
||||
(targetSearchQuery) => {
|
||||
(targetSearchQuery: string) => {
|
||||
// Refer to https://github.com/facebook/docusaurus/pull/2838
|
||||
return `${baseUrl}search?q=${encodeURIComponent(targetSearchQuery)}`;
|
||||
},
|
10
packages/docusaurus-theme-search-algolia/src/types.d.ts
vendored
Normal file
10
packages/docusaurus-theme-search-algolia/src/types.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/// <reference types="@docusaurus/module-type-aliases" />
|
||||
/// <reference types="@docusaurus/theme-common" />
|
||||
/// <reference types="@docusaurus/theme-classic" />
|
|
@ -5,7 +5,8 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const {Joi} = require('@docusaurus/utils-validation');
|
||||
import {Joi} from '@docusaurus/utils-validation';
|
||||
import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types';
|
||||
|
||||
const DEFAULT_CONFIG = {
|
||||
contextualSearch: false, // future: maybe we want to enable this by default
|
||||
|
@ -18,7 +19,7 @@ const DEFAULT_CONFIG = {
|
|||
};
|
||||
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
||||
|
||||
const Schema = Joi.object({
|
||||
export const Schema = Joi.object({
|
||||
algolia: Joi.object({
|
||||
// Docusaurus attributes
|
||||
contextualSearch: Joi.boolean().default(DEFAULT_CONFIG.contextualSearch),
|
||||
|
@ -35,11 +36,13 @@ const Schema = Joi.object({
|
|||
.required()
|
||||
.unknown(), // DocSearch 3 is still alpha: don't validate the rest for now
|
||||
});
|
||||
exports.Schema = Schema;
|
||||
|
||||
exports.validateThemeConfig = function validateThemeConfig({
|
||||
export function validateThemeConfig({
|
||||
validate,
|
||||
themeConfig,
|
||||
}) {
|
||||
}: {
|
||||
validate: Validate<ThemeConfig>;
|
||||
themeConfig: ThemeConfig;
|
||||
}): ValidationResult<ThemeConfig> {
|
||||
return validate(Schema, themeConfig);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"jsx": "react-native"
|
||||
},
|
||||
"include": ["src/theme/", "src/*.d.ts"]
|
||||
}
|
9
packages/docusaurus-theme-search-algolia/tsconfig.json
Normal file
9
packages/docusaurus-theme-search-algolia/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "ES2019"],
|
||||
"rootDir": "src",
|
||||
"baseUrl": "src",
|
||||
"outDir": "lib"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["src/*.ts", "src/templates/*.ts"]
|
||||
}
|
Loading…
Add table
Reference in a new issue