diff --git a/jest.config.js b/jest.config.js index e747d613..d05c9154 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,6 +8,9 @@ module.exports = { collectCoverageFrom: ["src/**/*.{ts,tsx,js,jsx}"], // setupFilesAfterEnv: ["/__jest__/setup.js"], // snapshotSerializers: ["enzyme-to-json/serializer"], + moduleNameMapper: { + "\\.module\\.css$": "identity-obj-proxy", + }, // Ref https://github.com/facebook/jest/issues/2070#issuecomment-431706685 // Todo(steve): remove next line when issue fixed. modulePathIgnorePatterns: ["/.*/__mocks__"], diff --git a/package.json b/package.json index b91fa04c..3543ed42 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "eslint-plugin-react": "^7.21.4", "eslint-plugin-react-hooks": "^4.1.2", "husky": "^4.3.0", + "identity-obj-proxy": "^3.0.0", "jest": "^26.5.3", "lint-staged": "^10.4.1", "nodejieba": "^2.4.1", diff --git a/src/client/theme/LoadingRing/LoadingRing.module.css b/src/client/theme/LoadingRing/LoadingRing.module.css new file mode 100644 index 00000000..2e569824 --- /dev/null +++ b/src/client/theme/LoadingRing/LoadingRing.module.css @@ -0,0 +1,47 @@ +/* https://loading.io/css/ */ +.loadingRing { + display: inline-block; + position: relative; + width: 20px; + height: 20px; + opacity: var(--search-local-loading-icon-opacity, 0.5); +} + +.loadingRing 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: loading-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; +} + +.loadingRing div:nth-child(1) { + animation-delay: -0.45s; +} + +.loadingRing div:nth-child(2) { + animation-delay: -0.3s; +} + +.loadingRing div:nth-child(3) { + animation-delay: -0.15s; +} + +@keyframes loading-ring { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/src/client/theme/LoadingRing/LoadingRing.tsx b/src/client/theme/LoadingRing/LoadingRing.tsx new file mode 100644 index 00000000..166a728a --- /dev/null +++ b/src/client/theme/LoadingRing/LoadingRing.tsx @@ -0,0 +1,19 @@ +// istanbul ignore file +import React from "react"; +import clsx from "clsx"; +import styles from "./LoadingRing.module.css"; + +export default function LoadingRing({ + className, +}: { + className?: string; +}): React.ReactElement { + return ( +
+
+
+
+
+
+ ); +} diff --git a/src/client/utils/EmptyTemplate.spec.ts b/src/client/theme/SearchBar/EmptyTemplate.spec.ts similarity index 100% rename from src/client/utils/EmptyTemplate.spec.ts rename to src/client/theme/SearchBar/EmptyTemplate.spec.ts diff --git a/src/client/theme/SearchBar/EmptyTemplate.ts b/src/client/theme/SearchBar/EmptyTemplate.ts new file mode 100644 index 00000000..4a4a363c --- /dev/null +++ b/src/client/theme/SearchBar/EmptyTemplate.ts @@ -0,0 +1,9 @@ +import { iconNoResults } from "./icons"; +import styles from "./SearchBar.module.css"; + +export function EmptyTemplate(): string { + if (process.env.NODE_ENV === "production") { + return `${iconNoResults}No results.`; + } + return `⚠️ The search index is only available when you run docusaurus build!`; +} diff --git a/src/client/theme/SearchBar/SearchBar.css b/src/client/theme/SearchBar/SearchBar.module.css similarity index 54% rename from src/client/theme/SearchBar/SearchBar.css rename to src/client/theme/SearchBar/SearchBar.module.css index 18108106..84d32e99 100644 --- a/src/client/theme/SearchBar/SearchBar.css +++ b/src/client/theme/SearchBar/SearchBar.module.css @@ -1,4 +1,4 @@ -.doc-search-bar .aa-dropdown-menu { +.searchBar .dropdownMenu { left: auto !important; right: 0 !important; @@ -17,16 +17,16 @@ } @media (max-width: 576px) { - .navbar__search-input:not(:focus) { + :global(.navbar__search-input):not(:focus) { width: 2rem; } - .doc-search-bar .aa-dropdown-menu { + .searchBar .dropdownMenu { width: var(--search-local-modal-width-sm, 340px); } } -html[data-theme="dark"] .doc-search-bar .aa-dropdown-menu { +html[data-theme="dark"] .searchBar .dropdownMenu { background: var(--search-local-modal-background, var(--ifm-background-color)); box-shadow: var( --search-local-modal-shadow, @@ -35,7 +35,7 @@ html[data-theme="dark"] .doc-search-bar .aa-dropdown-menu { ); } -.doc-search-bar .aa-dropdown-menu .aa-suggestion { +.searchBar .dropdownMenu .suggestion { cursor: pointer; background: var(--search-local-hit-background, #fff); border-radius: 4px; @@ -50,57 +50,58 @@ html[data-theme="dark"] .doc-search-bar .aa-dropdown-menu { height: var(--search-local-hit-height, 56px); } -html[data-theme="dark"] .aa-dropdown-menu .aa-suggestion { +html[data-theme="dark"] .dropdownMenu .suggestion { background: var(--search-local-hit-background, var(--ifm-color-emphasis-100)); box-shadow: var(--search-local-hit-shadow, none); color: var(--search-local-hit-color, var(--ifm-font-color-base)); } -.doc-search-bar .aa-dropdown-menu .aa-suggestion:not(:last-child) { +.searchBar .dropdownMenu .suggestion:not(:last-child) { margin-bottom: 4px; } -.doc-search-bar .aa-dropdown-menu .aa-suggestion.aa-cursor { +.searchBar .dropdownMenu .suggestion.cursor { background-color: var( --search-local-highlight-color, var(--ifm-color-primary) ); } -.doc-search-bar .aa-dropdown-menu .aa-suggestion-empty::after { - content: "No results"; -} - -.doc-search-hit-tree, -.doc-search-hit-icon, -.doc-search-hit-path, -.doc-search-empty-icon, -.doc-search-hit-footer a { +.hitTree, +.hitIcon, +.hitPath, +.noResultsIcon, +.hitFooter a { color: var(--search-local-muted-color, #969faf); } -html[data-theme="dark"] .doc-search-hit-tree, -html[data-theme="dark"] .doc-search-hit-icon, -html[data-theme="dark"] .doc-search-hit-path, -html[data-theme="dark"] .doc-search-empty-icon { +html[data-theme="dark"] .hitTree, +html[data-theme="dark"] .hitIcon, +html[data-theme="dark"] .hitPath, +html[data-theme="dark"] .noResultsIcon { color: var(--search-local-muted-color, var(--ifm-color-secondary-darkest)); } -.doc-search-hit-tree { +.hitTree { + display: flex; + align-items: center; +} + +.hitTree > svg { height: var(--search-local-hit-height, 56px); opacity: 0.5; stroke-width: var(--search-local-icon-stroke-width, 1.4); width: 24px; } -.doc-search-hit-icon { +.hitIcon { stroke-width: var(--search-local-icon-stroke-width, 1.4); height: 20px; width: 20px; } -.doc-search-hit-wrapper { +.hitWrapper { flex: 1 1 auto; display: flex; flex-direction: column; @@ -111,36 +112,36 @@ html[data-theme="dark"] .doc-search-empty-icon { width: 80%; } -.doc-search-hit-wrapper mark { +.hitWrapper mark { background: none; color: var(--search-local-highlight-color, var(--ifm-color-primary)); } -.doc-search-hit-title { +.hitTitle { font-size: 0.9em; } -.doc-search-hit-path { +.hitPath { font-size: 0.75em; } -.doc-search-hit-path, -.doc-search-hit-title { +.hitPath, +.hitTitle { white-space: nowrap; overflow-x: hidden; text-overflow: ellipsis; } -.doc-search-hit-action { +.hitAction { height: 20px; width: 20px; } -.doc-search-hit-action-icon { +.hideAction > svg { display: none; } -.doc-search-empty { +.noResults { display: flex; flex-direction: column; align-items: center; @@ -148,98 +149,67 @@ html[data-theme="dark"] .doc-search-empty-icon { padding: var(--search-local-spacing, 12px) 0; } -.doc-search-empty-icon { +.noResultsIcon { margin-bottom: var(--search-local-spacing, 12px); } -.doc-search-hit-footer { +.hitFooter { text-align: center; margin-top: var(--search-local-spacing, 12px); font-size: 0.85em; } -.doc-search-hit-footer a { +.hitFooter a { text-decoration: underline; } -.aa-cursor .doc-search-hit-action-icon { +.cursor .hideAction > svg { display: block; } -.aa-suggestion.aa-cursor, -.aa-suggestion.aa-cursor mark, -.aa-suggestion.aa-cursor .doc-search-hit-icon, -.aa-suggestion.aa-cursor .doc-search-hit-path { +.suggestion.cursor, +.suggestion.cursor mark, +.suggestion.cursor .hitTree, +.suggestion.cursor .hitIcon, +.suggestion.cursor .hitPath { color: var( --search-local-hit-active-color, var(--ifm-color-white) ) !important; } -.aa-suggestion.aa-cursor mark { +.suggestion.cursor mark { text-decoration: underline; } -/* Start: pure CSS loaders */ -/* https://loading.io/css/ */ -.lds-ring { +.searchBarContainer .searchBarLoadingRing { display: none; position: absolute; left: calc(var(--ifm-navbar-padding-horizontal) + 10px); top: 6px; - width: 20px; - height: 20px; - opacity: var(--search-local-loading-icon-opacity, 0.5); } -.lds-ring 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; +:global(.navbar__search) { + position: relative; } -.lds-ring div:nth-child(1) { - animation-delay: -0.45s; +.searchIndexLoading :global(.navbar__search-input) { + background-image: none; } -.lds-ring div:nth-child(2) { - animation-delay: -0.3s; +.searchIndexLoading .searchBarLoadingRing { + display: inline-block; } -.lds-ring div:nth-child(3) { - animation-delay: -0.15s; +/**/ +.input { } - -@keyframes lds-ring { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } +.hint { } -/* End: pure CSS loaders */ - -.navbar__search { - position: relative; +.suggestions { } - -.search-index-loading .navbar__search-input { - background-image: none; +.dataset { } - -.search-index-loading .lds-ring { - display: inline-block; +.empty { } +/**/ diff --git a/src/client/theme/SearchBar/SearchBar.tsx b/src/client/theme/SearchBar/SearchBar.tsx index d04b80e4..1da6b8a7 100644 --- a/src/client/theme/SearchBar/SearchBar.tsx +++ b/src/client/theme/SearchBar/SearchBar.tsx @@ -2,13 +2,16 @@ import React, { ReactElement, useCallback, useRef, useState } from "react"; import clsx from "clsx"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; import { useHistory } from "@docusaurus/router"; + import { fetchIndexes } from "./fetchIndexes"; import { SearchSourceFactory } from "../../utils/SearchSourceFactory"; -import { SuggestionTemplate } from "../../utils/SuggestionTemplate.js"; -import { EmptyTemplate } from "../../utils/EmptyTemplate.js"; +import { SuggestionTemplate } from "./SuggestionTemplate"; +import { EmptyTemplate } from "./EmptyTemplate"; import { SearchResult } from "../../../shared/interfaces"; import { searchResultLimits } from "../../utils/proxiedGenerated"; -import "./SearchBar.css"; +import LoadingRing from "../LoadingRing/LoadingRing"; + +import styles from "./SearchBar.module.css"; async function fetchAutoCompleteJS(): Promise { const autoComplete = await import("autocomplete.js"); @@ -53,8 +56,18 @@ export default function SearchBar({ { hint: false, autoselect: true, + debug: true, cssClasses: { - root: "doc-search-bar", + root: styles.searchBar, + noPrefix: true, + dropdownMenu: styles.dropdownMenu, + input: styles.input, + hint: styles.hint, + suggestions: styles.suggestions, + suggestion: styles.suggestion, + cursor: styles.cursor, + dataset: styles.dataset, + empty: styles.empty, }, }, [ @@ -83,7 +96,7 @@ export default function SearchBar({ } }); const div = document.createElement("div"); - div.className = "doc-search-hit-footer"; + div.className = styles.hitFooter; div.appendChild(a); return div; }, @@ -134,8 +147,8 @@ export default function SearchBar({ return (
-
-
-
-
-
-
+
); } diff --git a/src/client/utils/SuggestionTemplate.spec.ts b/src/client/theme/SearchBar/SuggestionTemplate.spec.ts similarity index 81% rename from src/client/utils/SuggestionTemplate.spec.ts rename to src/client/theme/SearchBar/SuggestionTemplate.spec.ts index 9334c004..e154c8b0 100644 --- a/src/client/utils/SuggestionTemplate.spec.ts +++ b/src/client/theme/SearchBar/SuggestionTemplate.spec.ts @@ -1,7 +1,7 @@ import { SuggestionTemplate } from "./SuggestionTemplate"; jest.mock("./icons"); -jest.mock("./proxiedGenerated"); +jest.mock("../../utils/proxiedGenerated"); describe("SuggestionTemplate", () => { test("page title", () => { @@ -28,17 +28,17 @@ describe("SuggestionTemplate", () => { expect(div).toMatchInlineSnapshot(`
Hello @@ -47,7 +47,7 @@ describe("SuggestionTemplate", () => { { }); expect(div).toMatchInlineSnapshot(`
- + + + Hello @@ -106,13 +110,13 @@ describe("SuggestionTemplate", () => { fruits. Hello world { }); expect(div).toMatchInlineSnapshot(`
- + + + Goodbye @@ -171,13 +179,13 @@ describe("SuggestionTemplate", () => { fruits. Hello world ${ + const treeWrapper = tree.map( + (item) => `${item}` + ); + const icon = `${ isTitle ? iconTitle : isHeading ? iconHeading : iconContent }`; const wrapped = [ - `${highlightStemmed( + `${highlightStemmed( document.t, getStemmedPositions(metadata, "t"), tokens @@ -40,7 +44,7 @@ export function SuggestionTemplate({ ]; if (!isTitle) { wrapped.push( - `${highlight( + `${highlight( (page as SearchDocument).t || // Todo(weareoutman): This is for EasyOps only. // istanbul ignore next @@ -51,11 +55,11 @@ export function SuggestionTemplate({ )}` ); } - const action = `${iconAction}`; + const action = `${iconAction}`; return [ - ...tree, + ...treeWrapper, icon, - '', + ``, ...wrapped, "", action, diff --git a/src/client/utils/__mocks__/icons.ts b/src/client/theme/SearchBar/__mocks__/icons.ts similarity index 100% rename from src/client/utils/__mocks__/icons.ts rename to src/client/theme/SearchBar/__mocks__/icons.ts diff --git a/src/client/utils/icons.ts b/src/client/theme/SearchBar/icons.ts similarity index 62% rename from src/client/utils/icons.ts rename to src/client/theme/SearchBar/icons.ts index f48aa571..1cd36c2b 100644 --- a/src/client/utils/icons.ts +++ b/src/client/theme/SearchBar/icons.ts @@ -5,10 +5,10 @@ export const iconHeading = export const iconContent = ''; export const iconAction = - ''; + ''; export const iconNoResults = ''; export const iconTreeInter = - ''; + ''; export const iconTreeLast = - ''; + ''; diff --git a/src/client/theme/SearchPage/SearchPage.tsx b/src/client/theme/SearchPage/SearchPage.tsx index 9e6bc4f9..eabb8681 100644 --- a/src/client/theme/SearchPage/SearchPage.tsx +++ b/src/client/theme/SearchPage/SearchPage.tsx @@ -3,15 +3,17 @@ 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 useSearchQuery from "../hooks/useSearchQuery"; 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"; +import LoadingRing from "../LoadingRing/LoadingRing"; + +import styles from "./SearchPage.module.css"; export default function SearchPage(): React.ReactElement { const { @@ -97,12 +99,7 @@ export default function SearchPage(): React.ReactElement { {!searchSource && searchQuery && (
-
-
-
-
-
-
+
)} diff --git a/src/client/utils/EmptyTemplate.ts b/src/client/utils/EmptyTemplate.ts deleted file mode 100644 index 6b759936..00000000 --- a/src/client/utils/EmptyTemplate.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { iconNoResults } from "./icons"; - -export function EmptyTemplate(): string { - if (process.env.NODE_ENV === "production") { - return `${iconNoResults}No results.`; - } - return `⚠️ The search index is only available when you run docusaurus build!`; -} diff --git a/src/server/index.ts b/src/server/index.ts index 17340d39..2417fbda 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -18,14 +18,14 @@ export default function DocusaurusSearchLocalPlugin( fs.ensureDirSync(dir); generate(config, dir); - const themePage = path.resolve(__dirname, "../../client/client/theme"); - const pagePath = path.join(themePage, "SearchPage/index.js"); + const themePath = path.resolve(__dirname, "../../client/client/theme"); + const pagePath = path.join(themePath, "SearchPage/index.js"); return { name: PLUGIN_NAME, getThemePath() { - return themePage; + return themePath; }, postBuild: postBuildFactory(config), @@ -37,7 +37,7 @@ export default function DocusaurusSearchLocalPlugin( async contentLoaded({ actions: { addRoute } }: any) { addRoute({ path: normalizeUrl([context.baseUrl, "search"]), - component: pagePath, + component: "@theme/SearchPage", exact: true, }); }, diff --git a/yarn.lock b/yarn.lock index e358f132..6d410097 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3742,6 +3742,11 @@ hard-rejection@^2.1.0: resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== +harmony-reflect@^1.4.6: + version "1.6.1" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9" + integrity sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -3873,6 +3878,13 @@ iconv-lite@0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +identity-obj-proxy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + integrity sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ= + dependencies: + harmony-reflect "^1.4.6" + ignore-walk@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"