Skip to content

Commit

Permalink
feat: apisix docs version with badge (apache#1248)
Browse files Browse the repository at this point in the history
  • Loading branch information
SkyeYoung authored Jul 29, 2022
1 parent 3ab9290 commit 8b75fe1
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 58 deletions.
40 changes: 8 additions & 32 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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,
Expand All @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion config/apisix-versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}>}
*/
Expand Down Expand Up @@ -43,4 +48,4 @@ const archivedVersions = [
},
];

module.exports = { versions, archivedVersions };
module.exports = { versions, LTSVersions, archivedVersions };
44 changes: 19 additions & 25 deletions doc/src/theme/DocSidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand All @@ -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({
Expand All @@ -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}>
Expand All @@ -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,
Expand All @@ -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 },
Expand All @@ -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')}>
Expand All @@ -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
Expand Down
123 changes: 123 additions & 0 deletions doc/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx
Original file line number Diff line number Diff line change
@@ -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;
64 changes: 64 additions & 0 deletions doc/src/theme/NavbarItem/index.tsx
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit 8b75fe1

Please sign in to comment.