This is an automated email from the ASF dual-hosted git repository.
guoqi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-website.git
The following commit(s) were added to refs/heads/master by this push:
new 8b75fe12d3d feat: apisix docs version with badge (#1248)
8b75fe12d3d is described below
commit 8b75fe12d3dbf6fbb817c96fa8845c02d3465b00
Author: Young <[email protected]>
AuthorDate: Fri Jul 29 14:54:52 2022 +0800
feat: apisix docs version with badge (#1248)
---
.eslintrc.js | 40 ++-----
config/apisix-versions.js | 7 +-
doc/src/theme/DocSidebar/index.tsx | 44 ++++----
.../NavbarItem/DocsVersionDropdownNavbarItem.tsx | 123 +++++++++++++++++++++
doc/src/theme/NavbarItem/index.tsx | 64 +++++++++++
doc/src/theme/NavbarItem/style.module.scss | 17 +++
6 files changed, 237 insertions(+), 58 deletions(-)
diff --git a/.eslintrc.js b/.eslintrc.js
index afeaab67102..3212fe5ca0b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -9,11 +9,7 @@ module.exports = {
es2021: true,
node: true,
},
- extends: [
- 'airbnb',
- 'plugin:react/recommended',
- 'plugin:@typescript-eslint/recommended',
- ],
+ extends: ['airbnb', 'plugin:react/recommended',
'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
@@ -36,25 +32,13 @@ module.exports = {
quotes: [ERROR, 'single', { allowTemplateLiterals: true }],
'no-unused-vars': OFF,
'@typescript-eslint/no-unused-vars': [ERROR, { ignoreRestSiblings: true }],
- '@typescript-eslint/ban-ts-comment': [
- ERROR,
- { 'ts-expect-error': 'allow-with-description' },
- ],
- '@typescript-eslint/consistent-indexed-object-style': [
- WARNING,
- 'index-signature',
- ],
- '@typescript-eslint/consistent-type-imports': [
- WARNING,
- { disallowTypeAnnotations: false },
- ],
+ '@typescript-eslint/ban-ts-comment': [ERROR, { 'ts-expect-error':
'allow-with-description' }],
+ '@typescript-eslint/consistent-indexed-object-style': [WARNING,
'index-signature'],
+ '@typescript-eslint/consistent-type-imports': [WARNING, {
disallowTypeAnnotations: false }],
'@typescript-eslint/explicit-module-boundary-types': WARNING,
'@typescript-eslint/method-signature-style': ERROR,
'@typescript-eslint/no-empty-function': OFF,
- '@typescript-eslint/no-empty-interface': [
- ERROR,
- { allowSingleExtends: true },
- ],
+ '@typescript-eslint/no-empty-interface': [ERROR, { allowSingleExtends:
true }],
'@typescript-eslint/no-inferrable-types': OFF,
'@typescript-eslint/no-namespace': [WARNING, { allowDeclarations: true }],
'no-use-before-define': OFF,
@@ -70,22 +54,14 @@ module.exports = {
'import/no-unresolved': [
ERROR,
{
- ignore: [
- '^@theme',
- '^@docusaurus',
- '^@generated',
- '^@site',
- '^@testing-utils',
- ],
+ ignore: ['^@theme', '^@docusaurus', '^@generated', '^@site',
'^@testing-utils'],
},
],
'no-lonely-if': OFF,
'no-lone-blocks': OFF,
'react/jsx-filename-extension': [ERROR, { extensions: ['.jsx', '.tsx'] }],
- 'import/extensions': [
- ERROR,
- { tsx: 'never', svg: 'always', json: 'never' },
- ],
+ 'import/extensions': [ERROR, { tsx: 'never', svg: 'always', json: 'never'
}],
+ 'import/no-relative-packages': OFF,
'react/jsx-props-no-spreading': OFF,
'react/function-component-definition': [
ERROR,
diff --git a/config/apisix-versions.js b/config/apisix-versions.js
index 5f7da584299..6d3289247cf 100644
--- a/config/apisix-versions.js
+++ b/config/apisix-versions.js
@@ -5,6 +5,11 @@
*/
const versions = ['2.12', '2.13', '2.14', '2.15'];
+/**
+ * @type {Array<string>} LTS version list
+ */
+const LTSVersions = ['2.13'];
+
/**
* @type {Array<{label: string, href: string}>}
*/
@@ -43,4 +48,4 @@ const archivedVersions = [
},
];
-module.exports = { versions, archivedVersions };
+module.exports = { versions, LTSVersions, archivedVersions };
diff --git a/doc/src/theme/DocSidebar/index.tsx
b/doc/src/theme/DocSidebar/index.tsx
index 904df878ed1..fb9f0ba0ee8 100644
--- a/doc/src/theme/DocSidebar/index.tsx
+++ b/doc/src/theme/DocSidebar/index.tsx
@@ -5,10 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/
+/* eslint-disable import/no-extraneous-dependencies, max-len */
import type { FC } from 'react';
import React, { useState } from 'react';
import clsx from 'clsx';
-// eslint-disable-next-line import/no-extraneous-dependencies
import {
useThemeConfig,
useAnnouncementBar,
@@ -23,6 +23,7 @@ import { translate } from '@docusaurus/Translate';
import { DocSidebarItems } from '@theme/DocSidebarItem';
import DocsVersionDropdownNavbarItem from
'@theme/NavbarItem/DocsVersionDropdownNavbarItem';
import type { Props } from '@theme/DocSidebar';
+// eslint-disable-next-line import/no-unresolved
import { archivedVersions } from '../../../../config/apisix-versions';
import styles from './styles.module.css';
@@ -38,7 +39,7 @@ function useShowAnnouncementBar() {
return showAnnouncementBar;
}
-const HideableSidebarButton = ({ onClick }: {onClick:
React.MouseEventHandler}) => (
+const HideableSidebarButton = ({ onClick }: { onClick: React.MouseEventHandler
}) => (
<button
type="button"
title={translate({
@@ -51,17 +52,14 @@ const HideableSidebarButton = ({ onClick }: {onClick:
React.MouseEventHandler})
message: 'Collapse sidebar',
description: 'The title attribute for collapse button of doc sidebar',
})}
- className={clsx(
- 'button button--secondary button--outline',
- styles.collapseSidebarButton,
- )}
+ className={clsx('button button--secondary button--outline',
styles.collapseSidebarButton)}
onClick={onClick}
>
<IconArrow className={styles.collapseSidebarButtonIcon} />
</button>
);
-const DocsVersionWrapper = (props: {docsPluginId: string}) => {
+const DocsVersionWrapper = (props: { docsPluginId: string }) => {
const { docsPluginId } = props;
return (
<div className={styles.sidebarVersionSwitch}>
@@ -78,10 +76,10 @@ const DocsVersionWrapper = (props: {docsPluginId: string})
=> {
const DocsVersionWrapperMemo = React.memo(DocsVersionWrapper);
- interface DocSidebarMobileSecondaryMenuProps extends Props {
- docsPluginId: string,
- toggleSidebar: () => void
- }
+interface DocSidebarMobileSecondaryMenuProps extends Props {
+ docsPluginId: string;
+ toggleSidebar: () => void;
+}
const DocSidebarMobileSecondaryMenu: FC<DocSidebarMobileSecondaryMenuProps> =
({
toggleSidebar,
@@ -92,25 +90,22 @@ const DocSidebarMobileSecondaryMenu:
FC<DocSidebarMobileSecondaryMenuProps> = ({
<>
<DocsVersionWrapperMemo docsPluginId={docsPluginId} />
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
- <DocSidebarItems
- items={sidebar}
- activePath={path}
- onItemClick={() => toggleSidebar()}
- />
+ <DocSidebarItems items={sidebar} activePath={path} onItemClick={() =>
toggleSidebar()} />
</ul>
</>
);
const DocSidebarMobile = (props: Props) => (
- <MobileSecondaryMenuFiller
- component={DocSidebarMobileSecondaryMenu}
- props={props}
- />
+ <MobileSecondaryMenuFiller component={DocSidebarMobileSecondaryMenu}
props={props} />
);
const DocSidebarDesktop = ({
- path, sidebar, onCollapse, isHidden, docsPluginId,
-}: Props & {docsPluginId: string}) => {
+ path,
+ sidebar,
+ onCollapse,
+ isHidden,
+ docsPluginId,
+}: Props & { docsPluginId: string }) => {
const showAnnouncementBar = useShowAnnouncementBar();
const {
navbar: { hideOnScroll },
@@ -129,8 +124,7 @@ const DocSidebarDesktop = ({
<DocsVersionWrapperMemo docsPluginId={docsPluginId} />
<nav
className={clsx('menu thin-scrollbar', styles.menu, {
- [styles.menuWithAnnouncementBar]:
- !isAnnouncementBarClosed && showAnnouncementBar,
+ [styles.menuWithAnnouncementBar]: !isAnnouncementBarClosed &&
showAnnouncementBar,
})}
>
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu,
'menu__list')}>
@@ -145,7 +139,7 @@ const DocSidebarDesktop = ({
const DocSidebarMobileMemo = React.memo(DocSidebarMobile);
const DocSidebarDesktopMemo = React.memo(DocSidebarDesktop);
-const DocSidebar: FC<Props & {docsPluginId: string}> = (props) => {
+const DocSidebar: FC<Props & { docsPluginId: string }> = (props) => {
const windowSize = useWindowSize();
// Desktop sidebar visible on hydration: need SSR rendering
diff --git a/doc/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx
b/doc/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx
new file mode 100644
index 00000000000..72cc6d7238f
--- /dev/null
+++ b/doc/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx
@@ -0,0 +1,123 @@
+/**
+ * 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.
+ */
+
+/* eslint-disable import/no-extraneous-dependencies, max-len,
import/no-unresolved */
+import type { FC } from 'react';
+import React from 'react';
+import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
+import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
+import type { GlobalVersion } from '@theme/hooks/useDocs';
+import { useVersions, useLatestVersion, useActiveDocContext } from
'@theme/hooks/useDocs';
+import type { Props } from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
+import { useDocsPreferredVersion } from '@docusaurus/theme-common';
+import { translate } from '@docusaurus/Translate';
+import type { GlobalDataVersion } from '@docusaurus/plugin-content-docs-types';
+import clsx from 'clsx';
+import { LTSVersions } from '../../../../config/apisix-versions';
+import style from './style.module.scss';
+
+const getVersionMainDoc = (version: GlobalDataVersion) =>
version.docs.find((doc) => doc.id === version.mainDocId)!;
+
+const badgeObj = {
+ LTS: <div className={clsx(style.badge, style.LTS)}>LTS</div>,
+ Latest: <div className={clsx(style.badge, style.Latest)}>Latest</div>,
+};
+
+interface LabelWithBadgeProps {
+ version: GlobalVersion;
+ isApisx: boolean;
+}
+const LabelWithBadge: FC<LabelWithBadgeProps> = (props) => {
+ const { version, isApisx } = props;
+ return (
+ <div>
+ {version.label}
+ {(() => {
+ if (version.isLast) return badgeObj.Latest;
+ if (isApisx && LTSVersions.includes(version.label)) return
badgeObj.LTS;
+ return null;
+ })()}
+ </div>
+ );
+};
+
+const DocsVersionDropdownNavbarItem = ({
+ mobile,
+ docsPluginId,
+ dropdownActiveClassDisabled,
+ dropdownItemsBefore,
+ dropdownItemsAfter,
+ ...props
+}: Props): JSX.Element => {
+ const activeDocContext = useActiveDocContext(docsPluginId);
+ const versions = useVersions(docsPluginId);
+ const latestVersion = useLatestVersion(docsPluginId);
+ const isApisix = docsPluginId === 'docs-apisix';
+
+ const { preferredVersion, savePreferredVersionName } =
useDocsPreferredVersion(docsPluginId);
+
+ function getItems() {
+ const versionLinks = versions.map((version) => {
+ // We try to link to the same doc, in another version
+ // When not possible, fallback to the "main doc" of the version
+ const versionDoc = activeDocContext?.alternateDocVersions[version.name]
|| getVersionMainDoc(version);
+ return {
+ isNavLink: true,
+ label: <LabelWithBadge version={version} isApisx={isApisix} />,
+ to: versionDoc.path,
+ isActive: () => version === activeDocContext?.activeVersion,
+ onClick: () => {
+ savePreferredVersionName(version.name);
+ },
+ };
+ });
+
+ return [...dropdownItemsBefore, ...versionLinks, ...dropdownItemsAfter];
+ }
+
+ const items = getItems();
+
+ const dropdownVersion = activeDocContext.activeVersion ?? preferredVersion
?? latestVersion;
+
+ // Mobile dropdown is handled a bit differently
+ const dropdownLabel = mobile && items
+ ? translate({
+ id: 'theme.navbar.mobileVersionsDropdown.label',
+ message: 'Versions',
+ description: 'The label for the navbar versions dropdown on mobile view',
+ })
+ : dropdownVersion.label;
+ const dropdownTo = mobile && items ? undefined :
getVersionMainDoc(dropdownVersion).path;
+
+ // We don't want to render a version dropdown with 0 or 1 item
+ // If we build the site with a single docs version (onlyIncludeVersions:
['1.0.0'])
+ // We'd rather render a button instead of a dropdown
+ if (items.length <= 1) {
+ return (
+ <DefaultNavbarItem
+ {...props}
+ mobile={mobile}
+ label={dropdownLabel}
+ to={dropdownTo}
+ isActive={dropdownActiveClassDisabled ? () => false : undefined}
+ />
+ );
+ }
+
+ return (
+ <DropdownNavbarItem
+ {...props}
+ mobile={mobile}
+ label={dropdownLabel}
+ to={dropdownTo}
+ items={items}
+ isActive={dropdownActiveClassDisabled ? () => false : undefined}
+ />
+ );
+};
+
+export default DocsVersionDropdownNavbarItem;
diff --git a/doc/src/theme/NavbarItem/index.tsx
b/doc/src/theme/NavbarItem/index.tsx
new file mode 100644
index 00000000000..02aa9fa098c
--- /dev/null
+++ b/doc/src/theme/NavbarItem/index.tsx
@@ -0,0 +1,64 @@
+/**
+ * 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.
+ */
+
+/* eslint-disable max-len, @typescript-eslint/no-explicit-any */
+import React from 'react';
+import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
+import type {
+ Props as DropdownNavbarItemProps,
+} from '@theme/NavbarItem/DropdownNavbarItem';
+import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
+import LocaleDropdownNavbarItem from
'@theme/NavbarItem/LocaleDropdownNavbarItem';
+import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem';
+import type { Types, Props } from '@theme/NavbarItem';
+
+const NavbarItemComponents: { [key: Exclude<Types, undefined>]: () => (props:
any) => JSX.Element } = {
+ default: () => DefaultNavbarItem,
+ localeDropdown: () => LocaleDropdownNavbarItem,
+ search: () => SearchNavbarItem,
+ dropdown: () => DropdownNavbarItem,
+
+ // Need to lazy load these items as we don't know for sure the docs plugin
is loaded
+ // See https://github.com/facebook/docusaurus/issues/3360
+ /* eslint-disable @typescript-eslint/no-var-requires, global-require */
+ docsVersion: () =>
require('@theme/NavbarItem/DocsVersionNavbarItem').default,
+ docsVersionDropdown: () =>
require('@theme/NavbarItem/DocsVersionDropdownNavbarItem').default,
+ doc: () => require('@theme/NavbarItem/DocNavbarItem').default,
+ /* eslint-enable @typescript-eslint/no-var-requires, global-require */
+} as const;
+
+type NavbarItemComponentType = keyof typeof NavbarItemComponents;
+
+const getNavbarItemComponent = (type: NavbarItemComponentType) => {
+ const navbarItemComponentFn = NavbarItemComponents[type];
+ if (!navbarItemComponentFn) {
+ throw new Error(`No NavbarItem component found for type "${type}".`);
+ }
+ return navbarItemComponentFn();
+};
+
+function getComponentType(type: Types, isDropdown: boolean):
NavbarItemComponentType {
+ // Backward compatibility: navbar item with no type set
+ // but containing dropdown items should use the type "dropdown"
+ if (!type || type === 'default') {
+ return isDropdown ? 'dropdown' : 'default';
+ }
+ return type as NavbarItemComponentType;
+}
+
+export const getInfimaActiveClassName = (mobile?: boolean): string => (mobile
? 'menu__link--active' : 'navbar__link--active');
+
+const NavbarItem = ({ type, ...props }: Props): JSX.Element => {
+ const componentType = getComponentType(
+ type,
+ (props as DropdownNavbarItemProps).items !== undefined,
+ );
+ const NavbarItemComponent = getNavbarItemComponent(componentType);
+ return <NavbarItemComponent {...props} />;
+};
+
+export default NavbarItem;
diff --git a/doc/src/theme/NavbarItem/style.module.scss
b/doc/src/theme/NavbarItem/style.module.scss
new file mode 100644
index 00000000000..2344cc56bf4
--- /dev/null
+++ b/doc/src/theme/NavbarItem/style.module.scss
@@ -0,0 +1,17 @@
+.badge {
+ padding: 0 8px;
+ margin-left: 9px;
+ border-radius: 8px;
+ font-size: 0.8rem;
+ text-transform: uppercase;
+
+ &.LTS {
+ color: hsl(198deg 77% 20%);
+ background-color: hsl(198deg 100% 92%);
+ }
+
+ &.Latest {
+ color: hsl(129deg 81% 14%);
+ background-color: hsl(130deg 71% 93%);
+ }
+}