Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(theme-classic): new navbar item linking to a sidebar #6139

Merged
merged 26 commits into from
Jan 6, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
54f1468
<feat>(content-docs): added GlobalSidebar, GlobalSidebarLink, toGloba…
lmpham1 Dec 19, 2021
1f71b22
<feat>(theme-classic): added DocSidebarNavbarItem as the new NavbarIt…
lmpham1 Dec 19, 2021
99f52a3
<feat>(theme-classic, content-doc): normalized doc id into URL in plu…
lmpham1 Dec 20, 2021
e75ab58
<feat>(content-docs): fixed a bug in getFirstDocIdOfSidebar function …
lmpham1 Dec 21, 2021
7a6813b
Merge branch 'main' into navbar-sidebar-first-doc
Josh-Cena Dec 21, 2021
4c9015b
Various refactors
Josh-Cena Dec 21, 2021
1b30b5a
id => sidebarId
Josh-Cena Dec 21, 2021
e41a956
Link should always be defined
Josh-Cena Dec 21, 2021
8d79727
Merge branch 'main' into navbar-sidebar-first-doc
Josh-Cena Dec 21, 2021
3e3c280
Fallback to sidebar ID
Josh-Cena Dec 21, 2021
939d0a3
Added unit tests for getFirstDocOfSidebar
lmpham1 Dec 21, 2021
5941f7b
added documentation for the new DocSidebarNavbarItem type
lmpham1 Dec 22, 2021
8b926c6
Update theme-configuration.md
Josh-Cena Dec 22, 2021
00bc1ac
Merge branch 'main' into navbar-sidebar-first-doc
slorber Dec 28, 2021
95bf5ea
Update packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSide…
lmpham1 Dec 30, 2021
04e70b4
changed getSidebarInVersion function in the DocSidebarNavbarItem modu…
lmpham1 Dec 30, 2021
1c7c848
refactor findFirstLink and toGlobalSidebars functions in doc plugin, …
lmpham1 Dec 30, 2021
3192dcb
make findFirstLink receive a LoadedVersion object to find a doc's per…
lmpham1 Dec 30, 2021
2ae7dfe
Merge branch 'main' into navbar-sidebar-first-doc
Josh-Cena Jan 2, 2022
95643c8
Revert "make findFirstLink receive a LoadedVersion object to find a d…
Josh-Cena Jan 2, 2022
ae7be3a
fix tests
Josh-Cena Jan 2, 2022
794ac8c
Lift type
Josh-Cena Jan 2, 2022
04c196a
Update packages/docusaurus-plugin-content-docs/src/globalData.ts
Josh-Cena Jan 5, 2022
ec7dd46
fixes
Josh-Cena Jan 6, 2022
a2ad091
Fix docs
Josh-Cena Jan 6, 2022
bcd01a9
Update utils.ts
Josh-Cena Jan 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/docusaurus-plugin-content-docs/src/globalData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/

import {normalizeUrl} from '@docusaurus/utils';
import {Sidebar} from './sidebars/types';
import {getFirstDocIdOfSidebar} from './sidebars/utils';
import type {
DocMetadata,
GlobalDoc,
LoadedVersion,
GlobalVersion,
GlobalSidebar,
} from './types';

export function toGlobalDataDoc(doc: DocMetadata): GlobalDoc {
Expand All @@ -20,13 +24,44 @@ export function toGlobalDataDoc(doc: DocMetadata): GlobalDoc {
};
}

export function toGlobalSidebar(
sidebarId: string,
sidebar: Sidebar,
version: LoadedVersion,
): GlobalSidebar {
const sidebarFirstDocId = getFirstDocIdOfSidebar(sidebar);
let sidebarFirstDocPermalink;
Josh-Cena marked this conversation as resolved.
Show resolved Hide resolved
if (!sidebarFirstDocId.isAutoGeneratedIndex) {
sidebarFirstDocPermalink = version.docs.find(
(doc) => doc.id === sidebarFirstDocId.firstDocId,
)?.permalink;
} else {
sidebarFirstDocPermalink = normalizeUrl([
version.versionPath,
sidebarFirstDocId.firstDocId,
]);
}
return {
link: {
label: sidebarId,
path: sidebarFirstDocPermalink ?? sidebarFirstDocId.firstDocId,
},
};
}

export function toGlobalDataVersion(version: LoadedVersion): GlobalVersion {
const globalSidebarRecord: Record<string, GlobalSidebar> = {};
Object.entries(version.sidebars).forEach(([sidebarId, sidebar]) => {
Josh-Cena marked this conversation as resolved.
Show resolved Hide resolved
const globalSidebarObject = toGlobalSidebar(sidebarId, sidebar, version);
globalSidebarRecord[sidebarId] = globalSidebarObject;
});
return {
name: version.versionName,
label: version.versionLabel,
isLast: version.isLast,
path: version.versionPath,
mainDocId: version.mainDocId,
docs: version.docs.map(toGlobalDataDoc),
sidebars: globalSidebarRecord,
};
}
34 changes: 34 additions & 0 deletions packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,37 @@ export function toNavigationLink(
throw new Error('unexpected navigation item');
}
}

export function getFirstDocIdOfSidebar(sidebar: Sidebar): {
isAutoGeneratedIndex: boolean;
firstDocId: string;
} {
const firstSidebarItem = sidebar[0];
let firstDocId = '';
let isAutoGeneratedIndex = false;
if (firstSidebarItem.type === 'doc' || firstSidebarItem.type === 'ref') {
firstDocId = firstSidebarItem.id;
Josh-Cena marked this conversation as resolved.
Show resolved Hide resolved
} else if (firstSidebarItem.type === 'link') {
firstDocId = firstSidebarItem.href;
} else if (firstSidebarItem.type === 'category') {
const firstCategoryLink = firstSidebarItem.link;
if (firstCategoryLink) {
if (firstCategoryLink.type === 'doc') {
firstDocId = firstCategoryLink.id;
} else if (firstCategoryLink.type === 'generated-index') {
isAutoGeneratedIndex = true;
firstDocId = firstCategoryLink.slug;
}
} else {
const childSidebarItemFirstDocId = getFirstDocIdOfSidebar([
firstSidebarItem.items[0],
]);
firstDocId = childSidebarItemFirstDocId.firstDocId;
isAutoGeneratedIndex = childSidebarItemFirstDocId.isAutoGeneratedIndex;
}
}
return {
isAutoGeneratedIndex,
firstDocId,
};
}
8 changes: 8 additions & 0 deletions packages/docusaurus-plugin-content-docs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ export type GlobalVersion = {
path: string;
mainDocId: string; // home doc (if docs homepage configured), or first doc
docs: GlobalDoc[];
sidebars?: Record<string, GlobalSidebar>;
};

export type GlobalSidebarLink = {label: string; path: string};

export type GlobalSidebar = {
link?: GlobalSidebarLink;
// ... we may add other things here later
};

export type GlobalPluginData = {
Expand Down
16 changes: 15 additions & 1 deletion packages/docusaurus-theme-classic/src/theme-classic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,23 @@ declare module '@theme/NavbarItem/DocNavbarItem' {
export default DocsSidebarNavbarItem;
}

declare module '@theme/NavbarItem/DocSidebarNavbarItem' {
import type {Props as DefaultNavbarItemProps} from '@theme/NavbarItem/DefaultNavbarItem';

export interface Props extends DefaultNavbarItemProps {
readonly id: string;
readonly docsPluginId?: string;
}

const DocSidebarNavbarItem: (props: Props) => JSX.Element;
export default DocSidebarNavbarItem;
}

declare module '@theme/NavbarItem' {
import type {ComponentProps} from 'react';
import type {Props as DefaultNavbarItemProps} from '@theme/NavbarItem/DefaultNavbarItem';
import type {Props as DocNavbarItemProps} from '@theme/NavbarItem/DocNavbarItem';
import type {Props as DocSidebarNavbarItemProps} from '@theme/NavbarItem/DocSidebarNavbarItem';
import type {Props as DocsVersionNavbarItemProps} from '@theme/NavbarItem/DocsVersionNavbarItem';
import type {Props as DropdownNavbarItemProps} from '@theme/NavbarItem/DropdownNavbarItem';
import type {Props as DocsVersionDropdownNavbarItemProps} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
Expand All @@ -490,7 +503,8 @@ declare module '@theme/NavbarItem' {
export type LinkLikeNavbarItemProps =
| ({readonly type?: 'default'} & DefaultNavbarItemProps)
| ({readonly type: 'doc'} & DocNavbarItemProps)
| ({readonly type: 'docsVersion'} & DocsVersionNavbarItemProps);
| ({readonly type: 'docsVersion'} & DocsVersionNavbarItemProps)
| ({readonly type: 'docSidebar'} & DocSidebarNavbarItemProps);

export type Props = ComponentProps<'a'> & {
readonly position?: 'left' | 'right';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* 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.
*/

import React from 'react';
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import {useLatestVersion, useActiveDocContext} from '@theme/hooks/useDocs';
import clsx from 'clsx';
import {getInfimaActiveClassName} from './index';
import type {Props} from '@theme/NavbarItem/DocSidebarNavbarItem';
import {useDocsPreferredVersion, uniq} from '@docusaurus/theme-common';
import type {GlobalDataVersion} from '@docusaurus/plugin-content-docs';

function getSidebarInVersion(versions: GlobalDataVersion[], sidebarId: string) {
Josh-Cena marked this conversation as resolved.
Show resolved Hide resolved
const allSidebars = versions.map((version) => version.sidebars)[0];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const allSidebars = versions.map((version) => version.sidebars)[0];
const allSidebars = versions.flatMap((version) => version.sidebars);

We want to look up for sidebars in all the provided versions, otherwise, it's not useful to provide an array of versions in the first place

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @slorber, flatmap doesn't work I think since version.sidebars is an array of Record<string, GlobalSidebar> instead of an array of GlobalSidebar arrays. I did change it to look for sidebars in all versions in my recent commit.

My question is which version are we getting the sidebar from if there are multiple versions that have the same sidebar?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lmpham1 versions are provided ordered to this method:

[activeVersion, preferredVersion, latestVersion]

So sidebars will also be ordered. We'll look for a sidebar in the active version (the one the user is currently browsing) in priority, so this should be fine already: just pick the first sidebar that matches the id

if (!allSidebars) {
throw new Error(`DocSidebarNavbarItem: couldn't find any sidebar`);
Josh-Cena marked this conversation as resolved.
Show resolved Hide resolved
}
const sidebar = allSidebars[sidebarId];
Josh-Cena marked this conversation as resolved.
Show resolved Hide resolved
if (!sidebar) {
const sidebarIds = Object.keys(allSidebars).join('\n- ');
throw new Error(
`DocSidebarNavbarItem: couldn't find any sidebar with id "${sidebarId}" in version${
versions.length ? 's' : ''
} ${versions.map((version) => version.name).join(', ')}".
Available sidebar ids are:\n- ${sidebarIds}`,
);
}
if (sidebar.link && sidebar.link.path) {
return sidebar.link;
}
throw new Error(
`DocSidebarNavbarItem: couldn't find a path for sidebar with id "${sidebarId}"`,
);
}

export default function DocSidebarNavbarItem({
id,
label: staticLabel,
docsPluginId,
...props
}: Props): JSX.Element {
const {activeVersion, activeDoc} = useActiveDocContext(docsPluginId);
const {preferredVersion} = useDocsPreferredVersion(docsPluginId);
const latestVersion = useLatestVersion(docsPluginId);

// Versions used to look for the doc to link to, ordered + no duplicate
const versions = uniq(
[activeVersion, preferredVersion, latestVersion].filter(
Boolean,
) as GlobalDataVersion[],
);
const sidebarLink = getSidebarInVersion(versions, id);
const activeDocInfimaClassName = getInfimaActiveClassName(props.mobile);

return (
<DefaultNavbarItem
exact
{...props}
className={clsx(props.className, {
[activeDocInfimaClassName]:
activeDoc?.sidebar && activeDoc.sidebar === id,
})}
activeClassName={activeDocInfimaClassName}
label={staticLabel ?? sidebarLink.label ?? sidebarLink.label}
to={sidebarLink.path}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const NavbarItemComponents: Record<
docsVersionDropdown: () =>
require('@theme/NavbarItem/DocsVersionDropdownNavbarItem').default,
doc: () => require('@theme/NavbarItem/DocNavbarItem').default,
docSidebar: () => require('@theme/NavbarItem/DocSidebarNavbarItem').default,
/* eslint-enable @typescript-eslint/no-var-requires, global-require */
} as const;

Expand Down
10 changes: 10 additions & 0 deletions packages/docusaurus-theme-classic/src/validateThemeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ const DocItemSchema = NavbarItemBaseSchema.append({
docsPluginId: Joi.string(),
});

const DocSidebarItemSchema = NavbarItemBaseSchema.append({
type: Joi.string().equal('docSidebar').required(),
id: Joi.string().required(),
docsPluginId: Joi.string(),
});

const itemWithType = (type: string | undefined) => {
// because equal(undefined) is not supported :/
const typeSchema = type
Expand Down Expand Up @@ -172,6 +178,10 @@ const NavbarItemSchema = Joi.object({
is: itemWithType('doc'),
then: DocItemSchema,
},
{
is: itemWithType('docSidebar'),
then: DocSidebarItemSchema,
},
{
is: itemWithType('localeDropdown'),
then: LocaleDropdownNavbarItemSchema,
Expand Down