diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current.json
index e9287466d1002c..08715a162ae394 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/current.json
+++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current.json
@@ -23,6 +23,14 @@
"message": "版本发布",
"description": "The label for category Releases in sidebar docs"
},
+ "sidebar.docs.category.Tutorials": {
+ "message": "使用教程",
+ "description": "The label for category Tutorials in sidebar docs"
+ },
+ "sidebar.docs.category.Building lakehouse": {
+ "message": "构建 lakehouse",
+ "description": "The label for category Building lakehouse in sidebar docs"
+ },
"sidebar.docs.category.Quick Start": {
"message": "快速体验",
"description": "The label for category Quick Start in sidebar docs"
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.json
index 4986b459b704bb..682456cb51b071 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.json
+++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.json
@@ -27,6 +27,14 @@
"message": "Doris 介绍",
"description": "The label for category Doris Introduction in sidebar docs"
},
+ "sidebar.docs.category.Tutorials": {
+ "message": "使用教程",
+ "description": "The label for category Tutorials in sidebar docs"
+ },
+ "sidebar.docs.category.Building lakehouse": {
+ "message": "构建 lakehouse",
+ "description": "The label for category Building lakehouse in sidebar docs"
+ },
"sidebar.docs.category.Install And Deploy": {
"message": "安装部署",
"description": "The label for category Install And Deploy in sidebar docs"
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-2.0.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-2.0.json
index cbd360115929b0..94048c4dff25dd 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/version-2.0.json
+++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-2.0.json
@@ -27,6 +27,14 @@
"message": "快速体验",
"description": "The label for category Quick Start in sidebar docs"
},
+ "sidebar.docs.category.Tutorials": {
+ "message": "使用教程",
+ "description": "The label for category Tutorials in sidebar docs"
+ },
+ "sidebar.docs.category.Building lakehouse": {
+ "message": "构建 lakehouse",
+ "description": "The label for category Building lakehouse in sidebar docs"
+ },
"sidebar.docs.category.Installation and Deployment": {
"message": "安装部署",
"description": "The label for category Installation and Deployment in sidebar docs"
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-2.1.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-2.1.json
index 247ea98c9eda43..fb787a4de597b1 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/version-2.1.json
+++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-2.1.json
@@ -27,6 +27,14 @@
"message": "快速体验",
"description": "The label for category Quick Start in sidebar docs"
},
+ "sidebar.docs.category.Tutorials": {
+ "message": "使用教程",
+ "description": "The label for category Tutorials in sidebar docs"
+ },
+ "sidebar.docs.category.Building lakehouse": {
+ "message": "构建 lakehouse",
+ "description": "The label for category Building lakehouse in sidebar docs"
+ },
"sidebar.docs.category.Installation and Deployment": {
"message": "安装部署",
"description": "The label for category Install and Deploy in sidebar docs"
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-3.0.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-3.0.json
index 631798c3d4e3ec..678f65519d4412 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-docs/version-3.0.json
+++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-3.0.json
@@ -27,6 +27,14 @@
"message": "快速体验",
"description": "The label for category Quick Start in sidebar docs"
},
+ "sidebar.docs.category.Tutorials": {
+ "message": "使用教程",
+ "description": "The label for category Tutorials in sidebar docs"
+ },
+ "sidebar.docs.category.Building lakehouse": {
+ "message": "构建 lakehouse",
+ "description": "The label for category Building lakehouse in sidebar docs"
+ },
"sidebar.docs.category.Installation and Deployment": {
"message": "安装部署",
"description": "The label for category Installation and Deployment in sidebar docs"
diff --git a/shared/interfaces.js b/shared/interfaces.js
new file mode 100644
index 00000000000000..f4037df8f9d8d6
--- /dev/null
+++ b/shared/interfaces.js
@@ -0,0 +1,8 @@
+export var SearchDocumentType;
+(function (SearchDocumentType) {
+ SearchDocumentType[SearchDocumentType["Title"] = 0] = "Title";
+ SearchDocumentType[SearchDocumentType["Heading"] = 1] = "Heading";
+ SearchDocumentType[SearchDocumentType["Description"] = 2] = "Description";
+ SearchDocumentType[SearchDocumentType["Keywords"] = 3] = "Keywords";
+ SearchDocumentType[SearchDocumentType["Content"] = 4] = "Content";
+})(SearchDocumentType || (SearchDocumentType = {}));
diff --git a/src/constant/common.tsx b/src/constant/common.tsx
index 4d8c292309d840..b1f71a6bb9a282 100644
--- a/src/constant/common.tsx
+++ b/src/constant/common.tsx
@@ -18,3 +18,6 @@ export const BLOG_TAG_ICONS = {
'Top News': ,
All: ,
};
+
+export const VERSIONS = ['1.2', '2.0', '2.1', '3.0', 'dev'];
+export const DEFAULT_VERSION = '2.1';
\ No newline at end of file
diff --git a/src/scss/components/markdown.scss b/src/scss/components/markdown.scss
index 1945ab4484d98c..f9e54982293a62 100644
--- a/src/scss/components/markdown.scss
+++ b/src/scss/components/markdown.scss
@@ -113,6 +113,7 @@
.theme-admonition {
padding: calc(var(--custom-leading) * 2);
+ box-shadow: none;
}
h2 {
diff --git a/src/scss/components/navbar.scss b/src/scss/components/navbar.scss
index a9700af9bde207..0dc57d431e6431 100644
--- a/src/scss/components/navbar.scss
+++ b/src/scss/components/navbar.scss
@@ -30,7 +30,6 @@
display: flex !important;
align-items: center;
justify-content: center;
- pointer-events: none;
padding: 5px 7px;
background-color: #fff;
border: none;
@@ -74,8 +73,6 @@
outline: none !important;
}
.navbar__search span span {
- max-height: 580px !important;
- overflow: scroll !important;
top: 2.5rem !important;
}
}
diff --git a/src/scss/components/toc.scss b/src/scss/components/toc.scss
index 3ada29d42e5312..54f3b7206854d6 100644
--- a/src/scss/components/toc.scss
+++ b/src/scss/components/toc.scss
@@ -3,6 +3,7 @@
.toc-icon-content {
display: flex;
margin-bottom: 0.5rem;
+ align-items: center;
cursor: pointer;
}
diff --git a/src/shared/interfaces.js b/src/shared/interfaces.js
new file mode 100644
index 00000000000000..f4037df8f9d8d6
--- /dev/null
+++ b/src/shared/interfaces.js
@@ -0,0 +1,8 @@
+export var SearchDocumentType;
+(function (SearchDocumentType) {
+ SearchDocumentType[SearchDocumentType["Title"] = 0] = "Title";
+ SearchDocumentType[SearchDocumentType["Heading"] = 1] = "Heading";
+ SearchDocumentType[SearchDocumentType["Description"] = 2] = "Description";
+ SearchDocumentType[SearchDocumentType["Keywords"] = 3] = "Keywords";
+ SearchDocumentType[SearchDocumentType["Content"] = 4] = "Content";
+})(SearchDocumentType || (SearchDocumentType = {}));
diff --git a/src/theme/Admonition/Layout/styles.module.css b/src/theme/Admonition/Layout/styles.module.css
index 88df7e639be32a..3f5fe3be2859bf 100644
--- a/src/theme/Admonition/Layout/styles.module.css
+++ b/src/theme/Admonition/Layout/styles.module.css
@@ -3,9 +3,12 @@
}
.admonitionHeading {
- font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) /
- var(--ifm-heading-line-height) var(--ifm-heading-font-family);
+ font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / var(--ifm-heading-line-height) var(--ifm-heading-font-family);
text-transform: uppercase;
+ font-size: 1rem;
+ color: #00000A;
+ margin-bottom: 0.75rem !important;
+ font-weight: 600;
}
/* Heading alone without content (does not handle fragment content) */
@@ -30,6 +33,6 @@
fill: var(--ifm-alert-foreground-color);
}
-.admonitionContent > :last-child {
+.admonitionContent> :last-child {
margin-bottom: 0;
-}
+}
\ No newline at end of file
diff --git a/src/theme/DocSidebarItem/Category/style.scss b/src/theme/DocSidebarItem/Category/style.scss
index c642c4779f41fd..2398c4329954d5 100644
--- a/src/theme/DocSidebarItem/Category/style.scss
+++ b/src/theme/DocSidebarItem/Category/style.scss
@@ -58,7 +58,7 @@
.divider {
width: calc(100% - 0.625rem);
- border: 0.5px solid #edf2fa;
+ border: 0.5px solid #DFE5F0;
margin: 1rem 0 1rem 0;
position: relative;
left: 0.625rem;
diff --git a/src/theme/Footer/index.tsx b/src/theme/Footer/index.tsx
index 3ce1831341e5ca..097a13a2446643 100644
--- a/src/theme/Footer/index.tsx
+++ b/src/theme/Footer/index.tsx
@@ -25,11 +25,14 @@ function Footer(): JSX.Element | null {
}
const { copyright, links, logo, style } = footer;
- const [isDocsPage, setIsDocsPage] = useState(false);
+ const [isDocsPage, setIsDocsPage] = useState(false); // docs page or community page
useEffect(() => {
if (typeof window !== 'undefined') {
const pathname = location.pathname.split('/')[1];
- const docsPage = pathname === 'docs' || location.pathname.includes('zh-CN/docs');
+ const docsPage =
+ pathname === 'docs' ||
+ location.pathname.includes('zh-CN/docs') ||
+ location.pathname.includes('community');
setIsDocsPage(docsPage);
}
}, [typeof window !== 'undefined' && location.pathname]);
@@ -39,30 +42,13 @@ function Footer(): JSX.Element | null {
if (isDocsPage) {
return (
-
-
-
-
-
- {/* border-b border-[#F7F9FE] */}
-
-
RESOURCES
- {ResourcesItems.map(({ label, href }) => (
-
- {label}
-
- ))}
-
-
-
COMMUNITY
- {CommunityItems.map(({ label, href }) => (
-
- {label}
-
- ))}
-
-
-
+
+ The contents of this website are © 2024{' '}
+ Apache Software Foundation under the terms of the{' '}
+ Apache License v2. Apache
+ Doris, Doris, and the Doris logo are either registered trademarks or trademarks of The Apache
+ Software Foundation in the United States and other countries.
+
);
}
diff --git a/src/theme/Footer/styles.scss b/src/theme/Footer/styles.scss
index e70eebf6ee39c2..2320381d3fe840 100644
--- a/src/theme/Footer/styles.scss
+++ b/src/theme/Footer/styles.scss
@@ -212,23 +212,20 @@
}
.docs-footer {
- height: 7.625rem;
+ height: 3.25rem;
background-color: #fff;
- display: flex;
- border-top: 1px solid #edf2fa;
- .logo {
- position: relative;
- .footer__logo {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 8.5625rem;
- height: 3.5rem;
- margin-top: 0;
- }
+ padding: 1rem 1.5rem;
+ border-top: 1px solid #EDF2FA;
+ .docs-footer-p {
+ text-align: center;
+ color: rgba(29, 29, 29, 0.4);
+ font-family: Inter;
+ font-size: 9px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px;
}
- .content {
- flex: 1;
+ .docs-footer-p a {
+ text-decoration: underline;
}
}
diff --git a/src/theme/Navbar/Content/index.tsx b/src/theme/Navbar/Content/index.tsx
index 275af09cc8f42d..0243b53757aee5 100644
--- a/src/theme/Navbar/Content/index.tsx
+++ b/src/theme/Navbar/Content/index.tsx
@@ -86,11 +86,13 @@ export default function NavbarContent(): JSX.Element {
const [isEN, setIsEN] = useState(true);
const docItems = isEN ? useThemeConfig().docNavbarEN.items : useThemeConfig().docNavbarZH.items;
const [leftItems, rightItems] = splitNavbarItems(items);
- const [leftDocItems,rightDocItems] = splitNavbarItems(docItems);
- const [isDocsPage, setIsDocsPage] = useState(false);
+ const [leftDocItems, rightDocItems] = splitNavbarItems(docItems);
+ const [isDocsPage, setIsDocsPage] = useState(
+ typeof window !== 'undefined' ? location.pathname.includes('docs') : false,
+ );
const [isCommunity, setIsCommunity] = useState(false);
const searchBarItem = items.find(item => item.type === 'search');
-
+
const [currentVersion, setCurrentVersion] = useState('');
useEffect(() => {
getGithubStar();
@@ -139,8 +141,7 @@ export default function NavbarContent(): JSX.Element {
function getNavItem(type: string) {
return items.find(item => item.type === type);
}
-
-
+
return (
) : (
-
+
)}
{/* */}
diff --git a/src/theme/SearchBar/EmptyTemplate.js b/src/theme/SearchBar/EmptyTemplate.js
new file mode 100644
index 00000000000000..0c67ba65f422a1
--- /dev/null
+++ b/src/theme/SearchBar/EmptyTemplate.js
@@ -0,0 +1,12 @@
+import { translate } from "@docusaurus/Translate";
+import { iconNoResults } from "./icons";
+import styles from "./SearchBar.module.css";
+export function EmptyTemplate() {
+ if (process.env.NODE_ENV === "production") {
+ return `${iconNoResults}${translate({
+ id: "theme.SearchBar.noResultsText",
+ message: "No results",
+ })}`;
+ }
+ return `⚠️ The search index is only available when you run docusaurus build!`;
+}
diff --git a/src/theme/SearchBar/SearchBar.jsx b/src/theme/SearchBar/SearchBar.jsx
new file mode 100644
index 00000000000000..3272a111f72811
--- /dev/null
+++ b/src/theme/SearchBar/SearchBar.jsx
@@ -0,0 +1,402 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import clsx from 'clsx';
+import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
+import useIsBrowser from '@docusaurus/useIsBrowser';
+import { useHistory, useLocation } from '@docusaurus/router';
+import { translate } from '@docusaurus/Translate';
+import { ReactContextError, useDocsPreferredVersion } from '@docusaurus/theme-common';
+import { useActivePlugin } from '@docusaurus/plugin-content-docs/client';
+import { fetchIndexesByWorker, searchByWorker } from './searchByWorker';
+import { SuggestionTemplate } from './SuggestionTemplate';
+import { EmptyTemplate } from './EmptyTemplate';
+import {
+ Mark,
+ searchBarShortcut,
+ searchBarShortcutHint,
+ searchBarPosition,
+ docsPluginIdForPreferredVersion,
+ indexDocs,
+ searchContextByPaths,
+ hideSearchBarWithNoSearchContext,
+ useAllContextsWithNoSearchContext,
+} from '../../utils/proxiedGenerated';
+import LoadingRing from '../LoadingRing/LoadingRing';
+import styles from './SearchBar.module.css';
+import { normalizeContextByPath } from '../../utils/normalizeContextByPath';
+import useIsDocPage from '@site/src/hooks/use-is-doc';
+import { VERSIONS } from '@site/src/constant/common';
+async function fetchAutoCompleteJS() {
+ const autoCompleteModule = await import('@easyops-cn/autocomplete.js');
+ const autoComplete = autoCompleteModule.default;
+ if (autoComplete.noConflict) {
+ // For webpack v5 since docusaurus v2.0.0-alpha.75
+ autoComplete.noConflict();
+ } else if (autoCompleteModule.noConflict) {
+ // For webpack v4 before docusaurus v2.0.0-alpha.74
+ autoCompleteModule.noConflict();
+ }
+ return autoComplete;
+}
+const SEARCH_PARAM_HIGHLIGHT = '_highlight';
+export default function SearchBar({ handleSearchBarToggle }) {
+ const isBrowser = useIsBrowser();
+ const location = useLocation();
+ const {
+ siteConfig: { baseUrl },
+ i18n: { currentLocale },
+ } = useDocusaurusContext();
+ // It returns undefined for non-docs pages
+ const activePlugin = useActivePlugin();
+ const [isDocsPage] = useIsDocPage(false);
+ let versionUrl = baseUrl;
+ if (location?.pathname && location.pathname.includes('zh-CN') && !versionUrl.includes('zh-CN')) {
+ versionUrl = baseUrl + 'zh-CN/';
+ }
+ if (location?.pathname) {
+ VERSIONS.forEach(version => {
+ if (location.pathname.includes(version)) {
+ versionUrl += `docs/${version}/`;
+ }
+ });
+ }
+
+ // For non-docs pages while using plugin-content-docs with custom ids,
+ // this will throw an error of:
+ // > Docusaurus plugin global data not found for "docusaurus-plugin-content-docs" plugin with id "default".
+ // It seems that we can not get the correct id for non-docs pages.
+ // try {
+ // // The try-catch is a hack because useDocsPreferredVersion just throws an
+ // // exception when versions are not used.
+ // // The same hack is used in SearchPage.tsx
+ // // eslint-disable-next-line react-hooks/rules-of-hooks
+ // const { preferredVersion } = useDocsPreferredVersion(activePlugin?.pluginId ?? docsPluginIdForPreferredVersion);
+ // console.log('preferredVersion',preferredVersion);
+
+ // if (preferredVersion && !preferredVersion.isLast) {
+ // versionUrl = preferredVersion.path + "/";
+ // }
+ // }
+ // catch (e) {
+ // if (indexDocs) {
+ // if (e instanceof ReactContextError) {
+ // /* ignore, happens when website doesn't use versions */
+ // }
+ // else {
+ // throw e;
+ // }
+ // }
+ // }
+
+ const history = useHistory();
+ const searchBarRef = useRef(null);
+ const indexStateMap = useRef(new Map());
+ // Should the input be focused after the index is loaded?
+ const focusAfterIndexLoaded = useRef(false);
+ const [loading, setLoading] = useState(false);
+ const [inputChanged, setInputChanged] = useState(false);
+ const [inputValue, setInputValue] = useState('');
+ const search = useRef(null);
+ const prevSearchContext = useRef('');
+ const [searchContext, setSearchContext] = useState('');
+ useEffect(() => {
+ if (!Array.isArray(searchContextByPaths)) {
+ return;
+ }
+ let nextSearchContext = '';
+ if (location.pathname.startsWith(versionUrl)) {
+ const uri = location.pathname.substring(versionUrl.length);
+ let matchedPath;
+ for (const _path of searchContextByPaths) {
+ const path = typeof _path === 'string' ? _path : _path.path;
+ if (uri === path || uri.startsWith(`${path}/`)) {
+ matchedPath = path;
+ break;
+ }
+ }
+ if (matchedPath) {
+ nextSearchContext = matchedPath;
+ }
+ }
+ if (prevSearchContext.current !== nextSearchContext) {
+ // Reset index state map once search context is changed.
+ indexStateMap.current.delete(nextSearchContext);
+ prevSearchContext.current = nextSearchContext;
+ }
+ setSearchContext(nextSearchContext);
+ }, [location.pathname, versionUrl]);
+ const hidden = !!hideSearchBarWithNoSearchContext && Array.isArray(searchContextByPaths) && searchContext === '';
+ const loadIndex = useCallback(async () => {
+ if (hidden || indexStateMap.current.get(searchContext)) {
+ // Do not load the index (again) if its already loaded or in the process of being loaded.
+ return;
+ }
+ indexStateMap.current.set(searchContext, 'loading');
+ search.current?.autocomplete.destroy();
+ setLoading(true);
+ const [autoComplete] = await Promise.all([
+ fetchAutoCompleteJS(),
+ fetchIndexesByWorker(versionUrl, searchContext),
+ ]);
+ const searchFooterLinkElement = ({ query, isEmpty }) => {
+ const a = document.createElement('a');
+ const params = new URLSearchParams();
+ params.set('q', query);
+ let linkText;
+ if (searchContext) {
+ const detailedSearchContext =
+ searchContext && Array.isArray(searchContextByPaths)
+ ? searchContextByPaths.find(item =>
+ typeof item === 'string' ? item === searchContext : item.path === searchContext,
+ )
+ : searchContext;
+ const translatedSearchContext = detailedSearchContext
+ ? normalizeContextByPath(detailedSearchContext, currentLocale).label
+ : searchContext;
+ if (useAllContextsWithNoSearchContext && isEmpty) {
+ linkText = translate(
+ {
+ id: 'theme.SearchBar.seeAllOutsideContext',
+ message: 'See all results outside "{context}"',
+ },
+ { context: translatedSearchContext },
+ );
+ } else {
+ linkText = translate(
+ {
+ id: 'theme.SearchBar.searchInContext',
+ message: 'See all results within "{context}"',
+ },
+ { context: translatedSearchContext },
+ );
+ }
+ } else {
+ linkText = translate({
+ id: 'theme.SearchBar.seeAll',
+ message: 'See all results',
+ });
+ }
+ if (
+ searchContext &&
+ Array.isArray(searchContextByPaths) &&
+ (!useAllContextsWithNoSearchContext || !isEmpty)
+ ) {
+ params.set('ctx', searchContext);
+ }
+ if (versionUrl !== baseUrl) {
+ if (!versionUrl.startsWith(baseUrl)) {
+ throw new Error(
+ `Version url '${versionUrl}' does not start with base url '${baseUrl}', this is a bug of \`@easyops-cn/docusaurus-search-local\`, please report it.`,
+ );
+ }
+ params.set('version', versionUrl.substring(baseUrl.length));
+ }
+ const url = `${baseUrl}search/?${params.toString()}`;
+ a.href = url;
+ a.textContent = linkText;
+ a.addEventListener('click', e => {
+ if (!e.ctrlKey && !e.metaKey) {
+ e.preventDefault();
+ search.current?.autocomplete.close();
+ history.push(url);
+ }
+ });
+ return a;
+ };
+ search.current = autoComplete(
+ searchBarRef.current,
+ {
+ hint: false,
+ autoselect: true,
+ openOnFocus: true,
+ cssClasses: {
+ root: clsx(styles.searchBar, {
+ [styles.searchBarLeft]: searchBarPosition === 'left',
+ }),
+ 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,
+ },
+ },
+ [
+ {
+ source: async (input, callback) => {
+ const result = await searchByWorker(versionUrl, searchContext, input);
+ callback(result);
+ },
+ templates: {
+ suggestion: SuggestionTemplate,
+ empty: EmptyTemplate,
+ footer: ({ query, isEmpty }) => {
+ if (isEmpty && (!searchContext || !useAllContextsWithNoSearchContext)) {
+ return;
+ }
+ const a = searchFooterLinkElement({ query, isEmpty });
+ const div = document.createElement('div');
+ div.className = styles.hitFooter;
+ div.appendChild(a);
+ return div;
+ },
+ },
+ },
+ ],
+ )
+ .on('autocomplete:selected', function (event, { document: { u, h }, tokens }) {
+ searchBarRef.current?.blur();
+ let url = u;
+ if (Mark && tokens.length > 0) {
+ const params = new URLSearchParams();
+ for (const token of tokens) {
+ params.append(SEARCH_PARAM_HIGHLIGHT, token);
+ }
+ url += `?${params.toString()}`;
+ }
+ if (h) {
+ url += h;
+ }
+ history.push(url);
+ })
+ .on('autocomplete:closed', () => {
+ searchBarRef.current?.blur();
+ });
+ indexStateMap.current.set(searchContext, 'done');
+ setLoading(false);
+ if (focusAfterIndexLoaded.current) {
+ const input = searchBarRef.current;
+ if (input.value) {
+ search.current?.autocomplete.open();
+ }
+ input.focus();
+ }
+ }, [hidden, searchContext, versionUrl, baseUrl, history]);
+ useEffect(() => {
+ if (!Mark) {
+ return;
+ }
+ const keywords = isBrowser ? new URLSearchParams(location.search).getAll(SEARCH_PARAM_HIGHLIGHT) : [];
+ // A workaround to fix an issue of highlighting in code blocks.
+ // See https://github.com/easyops-cn/docusaurus-search-local/issues/92
+ // Code blocks will be re-rendered after this `useEffect` ran.
+ // So we make the marking run after a macro task.
+ setTimeout(() => {
+ const root = document.querySelector('article');
+ if (!root) {
+ return;
+ }
+ const mark = new Mark(root);
+ mark.unmark();
+ if (keywords.length !== 0) {
+ mark.mark(keywords);
+ }
+ // Apply any keywords to the search input so that we can clear marks in case we loaded a page with a highlight in the url
+ setInputValue(keywords.join(' '));
+ search.current?.autocomplete.setVal(keywords.join(' '));
+ });
+ }, [isBrowser, location.search, location.pathname]);
+ const [focused, setFocused] = useState(false);
+ const onInputFocus = useCallback(() => {
+ focusAfterIndexLoaded.current = true;
+ loadIndex();
+ setFocused(true);
+ handleSearchBarToggle?.(true);
+ }, [handleSearchBarToggle, loadIndex]);
+ const onInputBlur = useCallback(() => {
+ setFocused(false);
+ handleSearchBarToggle?.(false);
+ }, [handleSearchBarToggle]);
+ const onInputMouseEnter = useCallback(() => {
+ loadIndex();
+ }, [loadIndex]);
+ const onInputChange = useCallback(event => {
+ setInputValue(event.target.value);
+ if (event.target.value) {
+ setInputChanged(true);
+ }
+ }, []);
+ // Implement hint icons for the search shortcuts on mac and the rest operating systems.
+ const isMac = isBrowser ? /mac/i.test(navigator.userAgentData?.platform ?? navigator.platform) : false;
+ useEffect(() => {
+ if (!searchBarShortcut) {
+ return;
+ }
+ // Add shortcuts command/ctrl + K
+ const handleShortcut = event => {
+ if ((isMac ? event.metaKey : event.ctrlKey) && (event.key === 'k' || event.key === 'K')) {
+ event.preventDefault();
+ searchBarRef.current?.focus();
+ onInputFocus();
+ }
+ };
+ document.addEventListener('keydown', handleShortcut);
+ return () => {
+ document.removeEventListener('keydown', handleShortcut);
+ };
+ }, [isMac, onInputFocus]);
+
+ useEffect(() => {
+ if (isDocsPage) {
+ loadIndex();
+ }
+ }, [location.pathname, isDocsPage]);
+
+ const onClearSearch = useCallback(() => {
+ const params = new URLSearchParams(location.search);
+ params.delete(SEARCH_PARAM_HIGHLIGHT);
+ const paramsStr = params.toString();
+ const searchUrl = location.pathname + (paramsStr != '' ? `?${paramsStr}` : '') + location.hash;
+ if (searchUrl != location.pathname + location.search + location.hash) {
+ history.push(searchUrl);
+ }
+ // We always clear these here because in case no match was selected the above history push wont happen
+ setInputValue('');
+ search.current?.autocomplete.setVal('');
+ }, [location.pathname, location.search, location.hash, history]);
+
+ return (
+
+
+
+ {searchBarShortcut &&
+ searchBarShortcutHint &&
+ (inputValue !== '' ? (
+
+ ) : (
+ isBrowser && (
+
+ {isMac ? '⌘' : 'ctrl'}
+ K
+
+ )
+ ))}
+
+ );
+}
diff --git a/src/theme/SearchBar/SearchBar.module.css b/src/theme/SearchBar/SearchBar.module.css
new file mode 100644
index 00000000000000..c5d9dfbb682007
--- /dev/null
+++ b/src/theme/SearchBar/SearchBar.module.css
@@ -0,0 +1,298 @@
+.searchBar .dropdownMenu {
+ left: auto !important;
+ right: 0 !important;
+
+ background: var(--search-local-modal-background, #f5f6f7);
+ border-radius: 6px;
+ box-shadow: var(
+ --search-local-modal-shadow,
+ inset 1px 1px 0 0 hsla(0, 0%, 100%, 0.5),
+ 0 3px 8px 0 #555a64
+ );
+ margin-top: 8px;
+ width: var(--search-local-modal-width, 560px);
+ position: relative;
+
+ padding: var(--search-local-spacing, 12px);
+}
+
+.navbarSearchInput {
+ border: none !important;
+ background-color: #f7f9fe !important;
+ background-image: url('/static/images/search-icon.svg');
+ height: 2.5rem !important;
+ background-position: 0.625rem center !important;
+ padding-left: 2.25rem !important;
+ color: #4c576c !important;
+ width: 15.43rem !important;
+ border-radius: 0.5rem;
+}
+
+
+@media not (max-width: 996px) {
+ .searchBar.searchBarLeft .dropdownMenu {
+ left: 0 !important;
+ right: auto !important;
+ }
+}
+
+@media (max-width: 576px) {
+ :global(.navbar__search-input):not(:focus) {
+ width: 2rem;
+ }
+
+ .searchBar .dropdownMenu {
+ width: var(--search-local-modal-width-sm, 340px);
+ max-width: calc(100vw - var(--ifm-navbar-padding-horizontal) * 2);
+ }
+}
+
+html[data-theme="dark"] .searchBar .dropdownMenu {
+ background: var(--search-local-modal-background, var(--ifm-background-color));
+ box-shadow: var(
+ --search-local-modal-shadow,
+ inset 1px 1px 0 0 #2c2e40,
+ 0 3px 8px 0 #000309
+ );
+}
+
+.searchBar .dropdownMenu .suggestion {
+ cursor: pointer;
+ background: var(--search-local-hit-background, #fff);
+ border-radius: 4px;
+ box-shadow: var(--search-local-hit-shadow, 0 1px 3px 0 #d4d9e1);
+ padding: 0 var(--search-local-spacing, 12px);
+ width: 100%;
+
+ align-items: center;
+ color: var(--search-local-hit-color, #444950);
+ display: flex;
+ flex-direction: row;
+ height: var(--search-local-hit-height, 56px);
+}
+
+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));
+}
+
+.searchBar .dropdownMenu .suggestion:not(:last-child) {
+ margin-bottom: 4px;
+}
+
+.searchBar .dropdownMenu .suggestion.cursor {
+ background-color: var(
+ --search-local-highlight-color,
+ var(--ifm-color-primary)
+ );
+}
+
+.hitTree,
+.hitIcon,
+.hitPath,
+.noResultsIcon,
+.hitFooter a {
+ color: var(--search-local-muted-color, #969faf);
+}
+
+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));
+}
+
+.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;
+}
+
+.hitIcon {
+ stroke-width: var(--search-local-icon-stroke-width, 1.4);
+
+ height: 20px;
+ width: 20px;
+}
+
+.hitWrapper {
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+ font-weight: 500;
+ justify-content: center;
+ margin: 0 8px;
+ overflow-x: hidden;
+ width: 80%;
+}
+
+.hitWrapper mark {
+ background: none;
+ color: var(--search-local-highlight-color, var(--ifm-color-primary));
+}
+
+.hitTitle {
+ font-size: 0.9em;
+}
+
+.hitPath {
+ font-size: 0.75em;
+}
+
+.hitPath,
+.hitTitle {
+ white-space: nowrap;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+}
+
+.hitAction {
+ height: 20px;
+ width: 20px;
+}
+
+.hideAction > svg {
+ display: none;
+}
+
+.noResults {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: var(--search-local-spacing, 12px) 0;
+}
+
+.noResultsIcon {
+ margin-bottom: var(--search-local-spacing, 12px);
+}
+
+.hitFooter {
+ text-align: center;
+ margin-top: var(--search-local-spacing, 12px);
+ font-size: 0.85em;
+}
+
+.hitFooter a {
+ text-decoration: underline;
+}
+
+.cursor .hideAction > svg {
+ display: block;
+}
+
+.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;
+}
+
+.suggestion.cursor mark {
+ text-decoration: underline;
+}
+
+.searchBarContainer {
+ margin-left: 16px;
+}
+
+.searchBarContainer .searchBarLoadingRing {
+ display: none;
+ position: absolute;
+ left: 10px;
+ top: 6px;
+}
+
+.searchBarContainer .searchClearButton {
+ position: absolute;
+ right: 0.8rem;
+ top: 50%;
+ transform: translate(0, -50%);
+ padding: 0;
+ background: none;
+ border: none;
+ line-height: 1rem;
+}
+
+:global(.navbar__search) {
+ position: relative;
+}
+
+.searchIndexLoading :global(.navbar__search-input) {
+ background-image: none;
+}
+
+.searchBarContainer.searchIndexLoading .searchBarLoadingRing {
+ display: inline-block;
+}
+
+.searchHintContainer {
+ position: absolute;
+ right: 10px;
+ top: 0px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ pointer-events: none;
+ gap: 4px;
+}
+
+.searchHint {
+ color: var(--ifm-navbar-search-input-placeholder-color);
+ background-color: var(--ifm-navbar-search-input-background-color);
+ border: 1px solid var(--ifm-color-emphasis-500);
+ box-shadow: inset 0 -1px 0 var(--ifm-color-emphasis-500);
+}
+
+@media (max-width: 576px) {
+ .searchBarContainer:not(.focused) .searchClearButton,
+ .searchHintContainer {
+ display: none;
+ }
+}
+
+html[dir="rtl"] .searchHintContainer {
+ right: auto;
+ left: 10px;
+}
+
+html[dir="rtl"] .searchBarContainer .searchClearButton {
+ right: auto;
+ left: 0.8rem;
+}
+
+html[dir="rtl"] .searchBarContainer .searchBarLoadingRing {
+ left: auto;
+ right: 10px;
+}
+
+html[dir="rtl"] :global(.navbar__search-input) {
+ padding: 0 2.25em 0 0.5em;
+}
+
+/* For autocomplete.js only. */
+.input {
+}
+.hint {
+}
+.suggestions {
+ max-height: 500px !important;
+ overflow: scroll !important;
+}
+.dataset {
+}
+.empty {
+}
+/**/
diff --git a/src/theme/SearchBar/SuggestionTemplate.js b/src/theme/SearchBar/SuggestionTemplate.js
new file mode 100644
index 00000000000000..427a6a6b225be0
--- /dev/null
+++ b/src/theme/SearchBar/SuggestionTemplate.js
@@ -0,0 +1,54 @@
+import { SearchDocumentType, } from "../../../shared/interfaces";
+import { concatDocumentPath } from "../../utils/concatDocumentPath";
+import { getStemmedPositions } from "../../utils/getStemmedPositions";
+import { highlight } from "../../utils/highlight";
+import { highlightStemmed } from "../../utils/highlightStemmed";
+import { explicitSearchResultPath } from "../../utils/proxiedGenerated";
+import { iconAction, iconContent, iconHeading, iconTitle, iconTreeInter, iconTreeLast, } from "./icons";
+import styles from "./SearchBar.module.css";
+export function SuggestionTemplate({ document, type, page, metadata, tokens, isInterOfTree, isLastOfTree, }) {
+ const isTitle = type === SearchDocumentType.Title;
+ const isKeywords = type === SearchDocumentType.Keywords;
+ const isTitleRelated = isTitle || isKeywords;
+ const isHeading = type === SearchDocumentType.Heading;
+ const tree = [];
+ if (isInterOfTree) {
+ tree.push(iconTreeInter);
+ }
+ else if (isLastOfTree) {
+ tree.push(iconTreeLast);
+ }
+ const treeWrapper = tree.map((item) => `${item}`);
+ const icon = `${isTitleRelated ? iconTitle : isHeading ? iconHeading : iconContent}`;
+ const wrapped = [
+ `${isKeywords
+ ? highlight(document.s, tokens)
+ : highlightStemmed(document.t, getStemmedPositions(metadata, "t"), tokens)}`,
+ ];
+ const needsExplicitHitPath = !isInterOfTree && !isLastOfTree && explicitSearchResultPath;
+ if (needsExplicitHitPath) {
+ const pathItems = page
+ ? page.b
+ ?.concat(page.t)
+ .concat(!document.s || document.s === page.t ? [] : document.s)
+ : document.b;
+ wrapped.push(`${concatDocumentPath(pathItems ?? [])}`);
+ }
+ else if (!isTitleRelated) {
+ wrapped.push(`${highlight(page.t ||
+ // Todo(weareoutman): This is for EasyOps only.
+ // istanbul ignore next
+ (document.u.startsWith("/docs/api-reference/")
+ ? "API Reference"
+ : ""), tokens)}`);
+ }
+ const action = `${iconAction}`;
+ return [
+ ...treeWrapper,
+ icon,
+ ``,
+ ...wrapped,
+ "",
+ action,
+ ].join("");
+}
diff --git a/src/theme/SearchBar/__mocks__/icons.js b/src/theme/SearchBar/__mocks__/icons.js
new file mode 100644
index 00000000000000..7c2f79e1343b84
--- /dev/null
+++ b/src/theme/SearchBar/__mocks__/icons.js
@@ -0,0 +1,7 @@
+export const iconTitle = '';
+export const iconHeading = '';
+export const iconContent = '';
+export const iconAction = '';
+export const iconNoResults = '';
+export const iconTreeInter = '';
+export const iconTreeLast = '';
diff --git a/src/theme/SearchBar/icons.js b/src/theme/SearchBar/icons.js
new file mode 100644
index 00000000000000..d5380213e9834a
--- /dev/null
+++ b/src/theme/SearchBar/icons.js
@@ -0,0 +1,7 @@
+export const iconTitle = '';
+export const iconHeading = '';
+export const iconContent = '';
+export const iconAction = '';
+export const iconNoResults = '';
+export const iconTreeInter = '';
+export const iconTreeLast = '';
diff --git a/src/theme/SearchBar/index.js b/src/theme/SearchBar/index.js
index 381d9012c0510c..369df710bfdef3 100644
--- a/src/theme/SearchBar/index.js
+++ b/src/theme/SearchBar/index.js
@@ -1,10 +1,3 @@
-import React from 'react';
-import SearchBar from '@theme-original/SearchBar';
-
-export default function SearchBarWrapper(props) {
- return (
- <>
-
- >
- );
-}
+import "../../utils/proxiedGenerated";
+import SearchBar from "./SearchBar";
+export default SearchBar;
diff --git a/src/theme/SearchBar/searchByWorker.js b/src/theme/SearchBar/searchByWorker.js
new file mode 100644
index 00000000000000..cd53fa72b9740c
--- /dev/null
+++ b/src/theme/SearchBar/searchByWorker.js
@@ -0,0 +1,24 @@
+import * as Comlink from "comlink";
+let remoteWorkerPromise;
+function getRemoteWorker() {
+ if (process.env.NODE_ENV === "production" && !remoteWorkerPromise) {
+ remoteWorkerPromise = (async () => {
+ const Remote = Comlink.wrap(new Worker(new URL("./worker.js", import.meta.url)));
+ return await new Remote();
+ })();
+ }
+ return remoteWorkerPromise;
+}
+export async function fetchIndexesByWorker(baseUrl, searchContext) {
+ if (process.env.NODE_ENV === "production") {
+ const remoteWorker = await getRemoteWorker();
+ await remoteWorker.fetchIndexes(baseUrl, searchContext);
+ }
+}
+export async function searchByWorker(baseUrl, searchContext, input) {
+ if (process.env.NODE_ENV === "production") {
+ const remoteWorker = await getRemoteWorker();
+ return remoteWorker.search(baseUrl, searchContext, input);
+ }
+ return [];
+}
diff --git a/src/theme/SearchBar/worker.js b/src/theme/SearchBar/worker.js
new file mode 100644
index 00000000000000..b2d3d11d08a777
--- /dev/null
+++ b/src/theme/SearchBar/worker.js
@@ -0,0 +1,94 @@
+import * as Comlink from "comlink";
+import lunr from "lunr";
+import { searchIndexUrl, searchResultLimits, language } from "@easyops-cn/docusaurus-search-local/dist/client/client/utils/proxiedGeneratedConstants";
+import { tokenize } from "@easyops-cn/docusaurus-search-local/dist/client/client/utils/tokenize";
+import { smartQueries } from "@easyops-cn/docusaurus-search-local/dist/client/client/utils/smartQueries";
+import { SearchDocumentType, } from "../../shared/interfaces";
+import { sortSearchResults } from "@easyops-cn/docusaurus-search-local/dist/client/client/utils/sortSearchResults";
+import { processTreeStatusOfSearchResults } from "@easyops-cn/docusaurus-search-local/dist/client/client/utils/processTreeStatusOfSearchResults";
+const cache = new Map();
+export class SearchWorker {
+ async fetchIndexes(baseUrl, searchContext) {
+ await this.lowLevelFetchIndexes(baseUrl, searchContext);
+ }
+ async lowLevelFetchIndexes(baseUrl, searchContext) {
+ const cacheKey = `${baseUrl}${searchContext}`;
+ let promise = cache.get(cacheKey);
+ if (!promise) {
+ promise = legacyFetchIndexes(baseUrl, searchContext);
+ cache.set(cacheKey, promise);
+ }
+ return promise;
+ }
+ async search(baseUrl, searchContext, input) {
+ const rawTokens = tokenize(input, language);
+ if (rawTokens.length === 0) {
+ return [];
+ }
+ const { wrappedIndexes, zhDictionary } = await this.lowLevelFetchIndexes(baseUrl, searchContext);
+ const queries = smartQueries(rawTokens, zhDictionary);
+ const results = [];
+ search: for (const { term, tokens } of queries) {
+ for (const { documents, index, type } of wrappedIndexes) {
+ results.push(...index
+ .query((query) => {
+ for (const item of term) {
+ query.term(item.value, {
+ wildcard: item.wildcard,
+ presence: item.presence,
+ });
+ }
+ })
+ .slice(0, searchResultLimits)
+ // Remove duplicated results.
+ .filter((result) => !results.some((item) => item.document.i.toString() === result.ref))
+ .slice(0, searchResultLimits - results.length)
+ .map((result) => {
+ const document = documents.find((doc) => doc.i.toString() === result.ref);
+ return {
+ document,
+ type,
+ page: type !== SearchDocumentType.Title &&
+ wrappedIndexes[0].documents.find((doc) => doc.i === document.p),
+ metadata: result.matchData.metadata,
+ tokens,
+ score: result.score,
+ };
+ }));
+ if (results.length >= searchResultLimits) {
+ break search;
+ }
+ }
+ }
+ sortSearchResults(results);
+ processTreeStatusOfSearchResults(results);
+ return results;
+ }
+}
+async function legacyFetchIndexes(baseUrl, searchContext) {
+ const url = `${baseUrl}${searchIndexUrl.replace("{dir}", searchContext ? `-${searchContext.replace(/\//g, "-")}` : "")}`;
+ // Catch potential attacks.
+ const fullUrl = new URL(url, location.origin);
+ if (fullUrl.origin !== location.origin) {
+ throw new Error("Unexpected version url");
+ }
+ const json = (await (await fetch(url)).json());
+ const wrappedIndexes = json.map(({ documents, index }, type) => ({
+ type: type,
+ documents,
+ index: lunr.Index.load(index),
+ }));
+ const zhDictionary = json.reduce((acc, item) => {
+ for (const tuple of item.index.invertedIndex) {
+ if (/\p{Unified_Ideograph}/u.test(tuple[0][0])) {
+ acc.add(tuple[0]);
+ }
+ }
+ return acc;
+ }, new Set());
+ return {
+ wrappedIndexes,
+ zhDictionary: Array.from(zhDictionary),
+ };
+}
+Comlink.expose(SearchWorker);
diff --git a/src/theme/TOC/index.tsx b/src/theme/TOC/index.tsx
index d08817f4abd6fe..5571d8e1fa79a1 100644
--- a/src/theme/TOC/index.tsx
+++ b/src/theme/TOC/index.tsx
@@ -6,7 +6,9 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import HomeIcon from '@site/static/images/toc-icon/home.svg';
import PdfIcon from '@site/static/images/toc-icon/pdf.svg';
import GithubIcon from '@site/static/images/toc-icon/github.svg';
+import ConcatIcon from '@site/static/images/toc-icon/concat.svg';
import { DOWNLOAD_PDFS } from '@site/src/constant/download.data';
+import { VERSIONS } from '@site/src/constant/common';
import Link from '@docusaurus/Link';
import styles from './styles.module.css';
@@ -37,14 +39,14 @@ export default function TOC({ className, ...props }: Props): JSX.Element {
const [currentVersion, setCurrentVersion] = useState(DEFAULT_VERSION);
const handleMouseEnter = (id: string) => {
const dom = document.getElementById(id);
- dom.style.color = '#444FD9';
- dom.firstChild.style.fill = '#444FD9';
+ dom!.style.color = '#444FD9';
+ dom!.firstChild!.style.fill = '#444FD9';
};
const handleMouseLeave = (id: string) => {
const dom = document.getElementById(id);
- dom.style.color = '#1F1F26';
- dom.firstChild.style.fill = '#7F7F83';
+ dom!.style.color = '#1F1F26';
+ dom!.firstChild!.style.fill = '#7F7F83';
};
useEffect(() => {
@@ -55,7 +57,7 @@ export default function TOC({ className, ...props }: Props): JSX.Element {
const secPath = location.pathname.includes('zh-CN/docs')
? location.pathname.split('/')[3]
: location.pathname.split('/')[2];
- if (pathname === 'docs' && ['dev', '3.0', '2.0', '1.2'].includes(secPath)) {
+ if (pathname === 'docs' && VERSIONS.includes(secPath)) {
setCurrentVersion(secPath);
} else {
setCurrentVersion(DEFAULT_VERSION);
@@ -100,7 +102,7 @@ export default function TOC({ className, ...props }: Props): JSX.Element {
onMouseEnter={() => handleMouseEnter('toc-icon-github')}
onMouseLeave={() => handleMouseLeave('toc-icon-github')}
>
-
+ {isCN ? : }
{isCN ? '技术论坛' : 'Ask Questions on Discussion'}
diff --git a/src/utils/normalizeContextByPath.js b/src/utils/normalizeContextByPath.js
new file mode 100644
index 00000000000000..99c29d64034092
--- /dev/null
+++ b/src/utils/normalizeContextByPath.js
@@ -0,0 +1,24 @@
+export function normalizeContextByPath(context, currentLocale) {
+ if (typeof context === "string") {
+ return {
+ label: context,
+ path: context,
+ };
+ }
+ else {
+ const { label, path } = context;
+ if (typeof label === "string") {
+ return { label, path };
+ }
+ if (Object.prototype.hasOwnProperty.call(label, currentLocale)) {
+ return {
+ label: label[currentLocale],
+ path,
+ };
+ }
+ return {
+ label: path,
+ path,
+ };
+ }
+}
diff --git a/static/images/toc-icon/concat.svg b/static/images/toc-icon/concat.svg
new file mode 100644
index 00000000000000..7b7754644a468c
--- /dev/null
+++ b/static/images/toc-icon/concat.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/static/images/toc-icon/github-active.svg b/static/images/toc-icon/github-active.svg
deleted file mode 100644
index 904b6428759f16..00000000000000
--- a/static/images/toc-icon/github-active.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
\ No newline at end of file
diff --git a/static/images/toc-icon/home-active.svg b/static/images/toc-icon/home-active.svg
deleted file mode 100644
index 1cb9a98ae428f0..00000000000000
--- a/static/images/toc-icon/home-active.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
\ No newline at end of file
diff --git a/static/images/toc-icon/pdf-active.svg b/static/images/toc-icon/pdf-active.svg
deleted file mode 100644
index 3e9bf42eb7f43b..00000000000000
--- a/static/images/toc-icon/pdf-active.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
\ No newline at end of file