feat(v2): Support swizzling TypeScript components (#2671)

* feat(v2): Support swizzling TypeScript components

* Add tsc --noEmit to tsc script in theme-classic

Now everything can pass the type checker! (although still a lot of any)

* Add tsconfig and types.d.ts to website

Improve developer experience.

As an example, I converted NotFound to tsx

* Apply type annotation suggestions

* Do not fallback to `getThemePath` if getTypeScriptThemePath is undefined

* Fix tsc

* Add module declaration for @theme-original/*

* Move babel cli to root package.json
This commit is contained in:
Sam Zhou 2020-06-25 10:07:30 -04:00 committed by GitHub
parent 20930dc837
commit 5ccd24cc1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 345 additions and 108 deletions

View file

@ -22,6 +22,7 @@ packages/docusaurus-plugin-debug/lib/
packages/docusaurus-plugin-sitemap/lib/ packages/docusaurus-plugin-sitemap/lib/
packages/docusaurus-plugin-ideal-image/lib/ packages/docusaurus-plugin-ideal-image/lib/
packages/docusaurus-plugin-ideal-image/copyUntypedFiles.js packages/docusaurus-plugin-ideal-image/copyUntypedFiles.js
packages/docusaurus-theme-classic/lib/
packages/docusaurus-1.x/.eslintrc.js packages/docusaurus-1.x/.eslintrc.js
packages/docusaurus-init/templates/facebook/.eslintrc.js packages/docusaurus-init/templates/facebook/.eslintrc.js

1
.gitignore vendored
View file

@ -26,3 +26,4 @@ packages/docusaurus-plugin-content-pages/lib/
packages/docusaurus-plugin-debug/lib/ packages/docusaurus-plugin-debug/lib/
packages/docusaurus-plugin-sitemap/lib/ packages/docusaurus-plugin-sitemap/lib/
packages/docusaurus-plugin-ideal-image/lib/ packages/docusaurus-plugin-ideal-image/lib/
packages/docusaurus-theme-classic/lib/

View file

@ -34,6 +34,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.0", "@babel/core": "^7.9.0",
"@babel/cli": "^7.9.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.9.0", "@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@babel/preset-typescript": "^7.9.0", "@babel/preset-typescript": "^7.9.0",
@ -52,6 +53,7 @@
"@types/lodash.pick": "^4.4.6", "@types/lodash.pick": "^4.4.6",
"@types/lodash.pickby": "^4.6.6", "@types/lodash.pickby": "^4.6.6",
"@types/node": "^13.11.0", "@types/node": "^13.11.0",
"@types/prismjs": "^1.16.1",
"@types/react": "^16.9.38", "@types/react": "^16.9.38",
"@types/react-dev-utils": "^9.0.1", "@types/react-dev-utils": "^9.0.1",
"@types/react-helmet": "^6.0.0", "@types/react-helmet": "^6.0.0",

View file

@ -37,10 +37,9 @@ declare module '@generated/routesChunkNames' {
export default routesChunkNames; export default routesChunkNames;
} }
declare module '@theme/*' { declare module '@theme/*';
const component: any;
export default component; declare module '@theme-original/*';
}
declare module '@docusaurus/*'; declare module '@docusaurus/*';

View file

@ -0,0 +1,10 @@
/**
* 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.
*/
module.exports = {
presets: [['@babel/preset-typescript', {isTSX: true, allExtensions: true}]],
};

View file

@ -7,6 +7,11 @@
"access": "public" "access": "public"
}, },
"license": "MIT", "license": "MIT",
"scripts": {
"tsc": "tsc --noEmit && yarn babel && yarn prettier",
"babel": "babel src -d lib --extensions \".tsx,.ts\" --copy-files",
"prettier": "prettier --config ../../.prettierrc --write \"**/*.{js,ts}\""
},
"dependencies": { "dependencies": {
"@hapi/joi": "^17.1.1", "@hapi/joi": "^17.1.1",
"@mdx-js/mdx": "^1.5.8", "@mdx-js/mdx": "^1.5.8",
@ -21,6 +26,9 @@
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-toggle": "^4.1.1" "react-toggle": "^4.1.1"
}, },
"devDependencies": {
"@docusaurus/module-type-aliases": "^2.0.0-alpha.58"
},
"peerDependencies": { "peerDependencies": {
"@docusaurus/core": "^2.0.0", "@docusaurus/core": "^2.0.0",
"react": "^16.8.4", "react": "^16.8.4",

View file

@ -61,6 +61,10 @@ module.exports = function (context, options) {
name: 'docusaurus-theme-classic', name: 'docusaurus-theme-classic',
getThemePath() { getThemePath() {
return path.join(__dirname, '..', 'lib', 'theme');
},
getTypeScriptThemePath() {
return path.resolve(__dirname, './theme'); return path.resolve(__dirname, './theme');
}, },

View file

@ -11,9 +11,9 @@ import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext';
import styles from './styles.module.css'; import styles from './styles.module.css';
function AnnouncementBar() { function AnnouncementBar(): JSX.Element | null {
const { const {
siteConfig: {themeConfig: {announcementBar = {}}} = {}, siteConfig: {themeConfig: {announcementBar = {}} = {}} = {},
} = useDocusaurusContext(); } = useDocusaurusContext();
const {content, backgroundColor, textColor} = announcementBar; const {content, backgroundColor, textColor} = announcementBar;
const { const {

View file

@ -12,7 +12,12 @@ import Layout from '@theme/Layout';
import BlogPostItem from '@theme/BlogPostItem'; import BlogPostItem from '@theme/BlogPostItem';
import BlogListPaginator from '@theme/BlogListPaginator'; import BlogListPaginator from '@theme/BlogListPaginator';
function BlogListPage(props) { type Props = {
metadata: {permalink: string; title: string};
items: {content}[];
};
function BlogListPage(props: Props): JSX.Element {
const {metadata, items} = props; const {metadata, items} = props;
const { const {
siteConfig: {title: siteTitle}, siteConfig: {title: siteTitle},

View file

@ -8,7 +8,7 @@
import React from 'react'; import React from 'react';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
function BlogListPaginator(props) { function BlogListPaginator(props): JSX.Element {
const {metadata} = props; const {metadata} = props;
const {previousPage, nextPage} = metadata; const {previousPage, nextPage} = metadata;

View file

@ -31,7 +31,7 @@ const MONTHS = [
'December', 'December',
]; ];
function BlogPostItem(props) { function BlogPostItem(props): JSX.Element {
const { const {
children, children,
frontMatter, frontMatter,

View file

@ -11,7 +11,7 @@ import Layout from '@theme/Layout';
import BlogPostItem from '@theme/BlogPostItem'; import BlogPostItem from '@theme/BlogPostItem';
import BlogPostPaginator from '@theme/BlogPostPaginator'; import BlogPostPaginator from '@theme/BlogPostPaginator';
function BlogPostPage(props) { function BlogPostPage(props): JSX.Element {
const {content: BlogPostContents} = props; const {content: BlogPostContents} = props;
const {frontMatter, metadata} = BlogPostContents; const {frontMatter, metadata} = BlogPostContents;
const {title, description, nextItem, prevItem, editUrl} = metadata; const {title, description, nextItem, prevItem, editUrl} = metadata;

View file

@ -8,7 +8,7 @@
import React from 'react'; import React from 'react';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
function BlogPostPaginator(props) { function BlogPostPaginator(props): JSX.Element {
const {nextItem, prevItem} = props; const {nextItem, prevItem} = props;
return ( return (

View file

@ -10,15 +10,17 @@ import React from 'react';
import Layout from '@theme/Layout'; import Layout from '@theme/Layout';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
function getCategoryOfTag(tag) { function getCategoryOfTag(tag: string) {
// tag's category should be customizable // tag's category should be customizable
return tag[0].toUpperCase(); return tag[0].toUpperCase();
} }
function BlogTagsListPage(props) { type Tag = {permalink: string; name: string; count: number};
function BlogTagsListPage(props: {tags: Record<string, Tag>}): JSX.Element {
const {tags} = props; const {tags} = props;
const tagCategories = {}; const tagCategories: {[category: string]: string[]} = {};
Object.keys(tags).forEach((tag) => { Object.keys(tags).forEach((tag) => {
const category = getCategoryOfTag(tag); const category = getCategoryOfTag(tag);
tagCategories[category] = tagCategories[category] || []; tagCategories[category] = tagCategories[category] || [];

View file

@ -11,11 +11,11 @@ import Layout from '@theme/Layout';
import BlogPostItem from '@theme/BlogPostItem'; import BlogPostItem from '@theme/BlogPostItem';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
function pluralize(count, word) { function pluralize(count: number, word: string) {
return count > 1 ? `${word}s` : word; return count > 1 ? `${word}s` : word;
} }
function BlogTagsPostPage(props) { function BlogTagsPostPage(props): JSX.Element {
const {metadata, items} = props; const {metadata, items} = props;
const {allTagsPath, name: tagName, count} = metadata; const {allTagsPath, name: tagName, count} = metadata;

View file

@ -87,7 +87,15 @@ const highlightDirectiveRegex = (lang) => {
}; };
const codeBlockTitleRegex = /title=".*"/; const codeBlockTitleRegex = /title=".*"/;
export default ({children, className: languageClassName, metastring}) => { export default ({
children,
className: languageClassName,
metastring,
}: {
children: string;
className: string;
metastring: string;
}): JSX.Element => {
const { const {
siteConfig: { siteConfig: {
themeConfig: {prism = {}}, themeConfig: {prism = {}},
@ -108,21 +116,25 @@ export default ({children, className: languageClassName, metastring}) => {
}, []); }, []);
const button = useRef(null); const button = useRef(null);
let highlightLines = []; let highlightLines: number[] = [];
let codeBlockTitle = ''; let codeBlockTitle = '';
const prismTheme = usePrismTheme(); const prismTheme = usePrismTheme();
if (metastring && highlightLinesRangeRegex.test(metastring)) { if (metastring && highlightLinesRangeRegex.test(metastring)) {
const highlightLinesRange = metastring.match(highlightLinesRangeRegex)[1]; // Tested above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const highlightLinesRange = metastring.match(highlightLinesRangeRegex)![1];
highlightLines = rangeParser highlightLines = rangeParser
.parse(highlightLinesRange) .parse(highlightLinesRange)
.filter((n) => n > 0); .filter((n) => n > 0);
} }
if (metastring && codeBlockTitleRegex.test(metastring)) { if (metastring && codeBlockTitleRegex.test(metastring)) {
// Tested above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
codeBlockTitle = metastring codeBlockTitle = metastring
.match(codeBlockTitleRegex)[0] .match(codeBlockTitleRegex)![0]
.split('title=')[1] .split('title=')[1]
.replace(/"+/g, ''); .replace(/"+/g, '');
} }
@ -151,7 +163,10 @@ export default ({children, className: languageClassName, metastring}) => {
if (match !== null) { if (match !== null) {
const directive = match const directive = match
.slice(1) .slice(1)
.reduce((final, item) => final || item, undefined); .reduce(
(final: string | undefined, item) => final || item,
undefined,
);
switch (directive) { switch (directive) {
case 'highlight-next-line': case 'highlight-next-line':
range += `${lineNumber},`; range += `${lineNumber},`;
@ -188,9 +203,10 @@ export default ({children, className: languageClassName, metastring}) => {
return ( return (
<Highlight <Highlight
{...defaultProps} {...defaultProps}
key={mounted} key={String(mounted)}
theme={prismTheme} theme={prismTheme}
code={code} code={code}
// @ts-expect-error: prism-react-renderer doesn't export Language type
language={language}> language={language}>
{({className, style, tokens, getLineProps, getTokenProps}) => ( {({className, style, tokens, getLineProps, getTokenProps}) => (
<> <>
@ -211,7 +227,7 @@ export default ({children, className: languageClassName, metastring}) => {
{showCopied ? 'Copied' : 'Copy'} {showCopied ? 'Copied' : 'Copy'}
</button> </button>
<div <div
tabIndex="0" tabIndex={0}
className={clsx(className, styles.codeBlock, { className={clsx(className, styles.codeBlock, {
[styles.codeBlockWithTitle]: codeBlockTitle, [styles.codeBlockWithTitle]: codeBlockTitle,
})}> })}>

View file

@ -33,7 +33,7 @@ function DocTOC({headings}) {
} }
/* eslint-disable jsx-a11y/control-has-associated-label */ /* eslint-disable jsx-a11y/control-has-associated-label */
function Headings({headings, isChild}) { function Headings({headings, isChild}: {headings; isChild?: boolean}) {
if (!headings.length) { if (!headings.length) {
return null; return null;
} }
@ -58,7 +58,7 @@ function Headings({headings, isChild}) {
); );
} }
function DocItem(props) { function DocItem(props): JSX.Element {
const {siteConfig = {}} = useDocusaurusContext(); const {siteConfig = {}} = useDocusaurusContext();
const {url: siteUrl, title: siteTitle} = siteConfig; const {url: siteUrl, title: siteTitle} = siteConfig;
const {content: DocContent} = props; const {content: DocContent} = props;

View file

@ -18,7 +18,7 @@ import {matchPath} from '@docusaurus/router';
import styles from './styles.module.css'; import styles from './styles.module.css';
function DocPage(props) { function DocPage(props): JSX.Element {
const {route: baseRoute, docsMetadata, location} = props; const {route: baseRoute, docsMetadata, location} = props;
// case-sensitive route such as it is defined in the sidebar // case-sensitive route such as it is defined in the sidebar
const currentRoute = const currentRoute =

View file

@ -8,7 +8,13 @@
import React from 'react'; import React from 'react';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
function DocPaginator(props) { type PageInfo = {permalink: string; title: string};
type Props = {
metadata: {previous: PageInfo; next: PageInfo};
};
function DocPaginator(props: Props): JSX.Element {
const {metadata} = props; const {metadata} = props;
return ( return (

View file

@ -163,11 +163,11 @@ function DocSidebarItem(props) {
} }
} }
function DocSidebar(props) { function DocSidebar(props): JSX.Element | null {
const [showResponsiveSidebar, setShowResponsiveSidebar] = useState(false); const [showResponsiveSidebar, setShowResponsiveSidebar] = useState(false);
const { const {
siteConfig: { siteConfig: {
themeConfig: {navbar: {title, hideOnScroll = false} = {}}, themeConfig: {navbar: {title = '', hideOnScroll = false} = {}} = {},
} = {}, } = {},
isClient, isClient,
} = useDocusaurusContext(); } = useDocusaurusContext();

View file

@ -39,7 +39,7 @@ const FooterLogo = ({url, alt}) => (
<img className="footer__logo" alt={alt} src={url} /> <img className="footer__logo" alt={alt} src={url} />
); );
function Footer() { function Footer(): JSX.Element | null {
const context = useDocusaurusContext(); const context = useDocusaurusContext();
const {siteConfig = {}} = context; const {siteConfig = {}} = context;
const {themeConfig = {}} = siteConfig; const {themeConfig = {}} = siteConfig;

View file

@ -7,14 +7,14 @@
/* eslint-disable jsx-a11y/anchor-has-content, jsx-a11y/anchor-is-valid */ /* eslint-disable jsx-a11y/anchor-has-content, jsx-a11y/anchor-is-valid */
import React from 'react'; import React, {ComponentType} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import './styles.css'; import './styles.css';
import styles from './styles.module.css'; import styles from './styles.module.css';
const Heading = (Tag) => const Heading = (Tag: ComponentType): ((props) => JSX.Element) =>
function TargetComponent({id, ...props}) { function TargetComponent({id, ...props}) {
const { const {
siteConfig: { siteConfig: {
@ -30,7 +30,7 @@ const Heading = (Tag) =>
<Tag {...props}> <Tag {...props}>
<a <a
aria-hidden="true" aria-hidden="true"
tabIndex="-1" tabIndex={-1}
className={clsx('anchor', { className={clsx('anchor', {
[styles.enhancedAnchor]: !hideOnScroll, [styles.enhancedAnchor]: !hideOnScroll,
})} })}
@ -39,7 +39,7 @@ const Heading = (Tag) =>
{props.children} {props.children}
<a <a
aria-hidden="true" aria-hidden="true"
tabIndex="-1" tabIndex={-1}
className="hash-link" className="hash-link"
href={`#${id}`} href={`#${id}`}
title="Direct link to heading"> title="Direct link to heading">

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React from 'react'; import React, {ReactNode} from 'react';
import Head from '@docusaurus/Head'; import Head from '@docusaurus/Head';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useBaseUrl from '@docusaurus/useBaseUrl'; import useBaseUrl from '@docusaurus/useBaseUrl';
@ -18,7 +18,18 @@ import Footer from '@theme/Footer';
import './styles.css'; import './styles.css';
function Layout(props) { type Props = {
children: ReactNode;
title?: string;
noFooter?: boolean;
description?: string;
image?: string;
keywords?: string[];
permalink?: string;
version?: string;
};
function Layout(props: Props): JSX.Element {
const {siteConfig = {}} = useDocusaurusContext(); const {siteConfig = {}} = useDocusaurusContext();
const { const {
favicon, favicon,

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React from 'react'; import React, {ComponentProps} from 'react';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import CodeBlock from '@theme/CodeBlock'; import CodeBlock from '@theme/CodeBlock';
import Heading from '@theme/Heading'; import Heading from '@theme/Heading';
@ -13,7 +13,7 @@ import Heading from '@theme/Heading';
import styles from './styles.module.css'; import styles from './styles.module.css';
export default { export default {
code: (props) => { code: (props: ComponentProps<typeof CodeBlock>): JSX.Element => {
const {children} = props; const {children} = props;
if (typeof children === 'string') { if (typeof children === 'string') {
if (!children.includes('\n')) { if (!children.includes('\n')) {
@ -23,14 +23,16 @@ export default {
} }
return children; return children;
}, },
a: (props) => { a: (props: ComponentProps<'a'>): JSX.Element => {
if (/\.[^./]+$/.test(props.href)) { if (/\.[^./]+$/.test(props.href || '')) {
// eslint-disable-next-line jsx-a11y/anchor-has-content // eslint-disable-next-line jsx-a11y/anchor-has-content
return <a {...props} />; return <a {...props} />;
} }
return <Link {...props} />; return <Link {...props} />;
}, },
pre: (props) => <div className={styles.mdxCodeBlock} {...props} />, pre: (props: ComponentProps<'div'>): JSX.Element => (
<div className={styles.mdxCodeBlock} {...props} />
),
h1: Heading('h1'), h1: Heading('h1'),
h2: Heading('h2'), h2: Heading('h2'),
h3: Heading('h3'), h3: Heading('h3'),

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React, {useCallback, useState, useEffect} from 'react'; import React, {useCallback, useState, useEffect, ComponentProps} from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
@ -33,7 +33,15 @@ function NavLink({
activeClassName = 'navbar__link--active', activeClassName = 'navbar__link--active',
prependBaseUrlToHref, prependBaseUrlToHref,
...props ...props
}) { }: {
activeBasePath?: string;
activeBaseRegex?: string;
to?: string;
href?: string;
label?: string;
activeClassName?: string;
prependBaseUrlToHref?: string;
} & ComponentProps<'a'>) {
const toUrl = useBaseUrl(to); const toUrl = useBaseUrl(to);
const activeBaseUrl = useBaseUrl(activeBasePath); const activeBaseUrl = useBaseUrl(activeBasePath);
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true}); const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
@ -96,7 +104,8 @@ function NavItem({
onClick={(e) => e.preventDefault()} onClick={(e) => e.preventDefault()}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.target.parentNode.classList.toggle('dropdown--show'); ((e.target as HTMLElement)
.parentNode as HTMLElement).classList.toggle('dropdown--show');
} }
}}> }}>
{props.label} {props.label}
@ -171,11 +180,11 @@ function splitLinks(links) {
}; };
} }
function Navbar() { function Navbar(): JSX.Element {
const { const {
siteConfig: { siteConfig: {
themeConfig: { themeConfig: {
navbar: {title, links = [], hideOnScroll = false} = {}, navbar: {title = '', links = [], hideOnScroll = false} = {},
disableDarkMode = false, disableDarkMode = false,
}, },
}, },

View file

@ -8,7 +8,7 @@
import React from 'react'; import React from 'react';
import Layout from '@theme/Layout'; import Layout from '@theme/Layout';
function NotFound() { function NotFound(): JSX.Element {
return ( return (
<Layout title="Page Not Found"> <Layout title="Page Not Found">
<div className="container margin-vert--xl"> <div className="container margin-vert--xl">

View file

@ -5,9 +5,9 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React from 'react'; import React, {ReactNode} from 'react';
function TabItem(props) { function TabItem(props: {readonly children: ReactNode}): JSX.Element {
return <div>{props.children}</div>; return <div>{props.children}</div>;
} }

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React, {useState, Children} from 'react'; import React, {useState, Children, ReactElement} from 'react';
import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext'; import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext';
import clsx from 'clsx'; import clsx from 'clsx';
@ -17,7 +17,15 @@ const keys = {
right: 39, right: 39,
}; };
function Tabs(props) { type Props = {
block?: boolean;
children: ReactElement<{value: string}>[];
defaultValue?: string;
values: {value: string; label: string}[];
groupId?: string;
};
function Tabs(props: Props): JSX.Element {
const {block, children, defaultValue, values, groupId} = props; const {block, children, defaultValue, values, groupId} = props;
const {tabGroupChoices, setTabGroupChoices} = useUserPreferencesContext(); const {tabGroupChoices, setTabGroupChoices} = useUserPreferencesContext();
const [selectedValue, setSelectedValue] = useState(defaultValue); const [selectedValue, setSelectedValue] = useState(defaultValue);
@ -40,7 +48,7 @@ function Tabs(props) {
} }
}; };
const tabRefs = []; const tabRefs: (HTMLLIElement | null)[] = [];
const focusNextTab = (tabs, target) => { const focusNextTab = (tabs, target) => {
const next = tabs.indexOf(target) + 1; const next = tabs.indexOf(target) + 1;
@ -86,7 +94,7 @@ function Tabs(props) {
{values.map(({value, label}) => ( {values.map(({value, label}) => (
<li <li
role="tab" role="tab"
tabIndex="0" tabIndex={0}
aria-selected={selectedValue === value} aria-selected={selectedValue === value}
className={clsx('tabs__item', styles.tabItem, { className={clsx('tabs__item', styles.tabItem, {
'tabs__item--active': selectedValue === value, 'tabs__item--active': selectedValue === value,
@ -103,7 +111,9 @@ function Tabs(props) {
<div role="tabpanel" className="margin-vert--md"> <div role="tabpanel" className="margin-vert--md">
{ {
Children.toArray(children).filter( Children.toArray(children).filter(
(child) => child.props.value === selectedValue, (child) =>
(child as ReactElement<{value: string}>).props.value ===
selectedValue,
)[0] )[0]
} }
</div> </div>

View file

@ -7,6 +7,6 @@
import React from 'react'; import React from 'react';
const ThemeContext = React.createContext(); const ThemeContext = React.createContext(undefined);
export default ThemeContext; export default ThemeContext;

View file

@ -5,12 +5,12 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React from 'react'; import React, {ReactNode} from 'react';
import useTheme from '@theme/hooks/useTheme'; import useTheme from '@theme/hooks/useTheme';
import ThemeContext from '@theme/ThemeContext'; import ThemeContext from '@theme/ThemeContext';
function ThemeProvider(props) { function ThemeProvider(props: {readonly children: ReactNode}): JSX.Element {
const {isDarkTheme, setLightTheme, setDarkTheme} = useTheme(); const {isDarkTheme, setLightTheme, setDarkTheme} = useTheme();
return ( return (

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React from 'react'; import React, {ComponentProps} from 'react';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
@ -16,7 +16,7 @@ import styles from './styles.module.css';
const Moon = () => <span className={clsx(styles.toggle, styles.moon)} />; const Moon = () => <span className={clsx(styles.toggle, styles.moon)} />;
const Sun = () => <span className={clsx(styles.toggle, styles.sun)} />; const Sun = () => <span className={clsx(styles.toggle, styles.sun)} />;
export default function (props) { export default function (props: ComponentProps<typeof Toggle>): JSX.Element {
const {isClient} = useDocusaurusContext(); const {isClient} = useDocusaurusContext();
return ( return (
<Toggle <Toggle

View file

@ -7,6 +7,6 @@
import {createContext} from 'react'; import {createContext} from 'react';
const UserPreferencesContext = createContext(); const UserPreferencesContext = createContext(undefined);
export default UserPreferencesContext; export default UserPreferencesContext;

View file

@ -5,13 +5,13 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import React from 'react'; import React, {ReactNode} from 'react';
import useTabGroupChoice from '@theme/hooks/useTabGroupChoice'; import useTabGroupChoice from '@theme/hooks/useTabGroupChoice';
import useAnnouncementBar from '@theme/hooks/useAnnouncementBar'; import useAnnouncementBar from '@theme/hooks/useAnnouncementBar';
import UserPreferencesContext from '@theme/UserPreferencesContext'; import UserPreferencesContext from '@theme/UserPreferencesContext';
function UserPreferencesProvider(props) { function UserPreferencesProvider(props: {children: ReactNode}): JSX.Element {
const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoice(); const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoice();
const {isAnnouncementBarClosed, closeAnnouncementBar} = useAnnouncementBar(); const {isAnnouncementBarClosed, closeAnnouncementBar} = useAnnouncementBar();

View file

@ -11,13 +11,18 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
const STORAGE_DISMISS_KEY = 'docusaurus.announcement.dismiss'; const STORAGE_DISMISS_KEY = 'docusaurus.announcement.dismiss';
const STORAGE_ID_KEY = 'docusaurus.announcement.id'; const STORAGE_ID_KEY = 'docusaurus.announcement.id';
const useAnnouncementBar = () => { const useAnnouncementBar = (): {
isAnnouncementBarClosed: boolean;
closeAnnouncementBar: () => void;
} => {
const { const {
siteConfig: {themeConfig: {announcementBar: {id} = {}}} = {}, siteConfig: {
themeConfig: {announcementBar: {id = 'annoucement-bar'} = {}} = {},
} = {},
} = useDocusaurusContext(); } = useDocusaurusContext();
const [isClosed, setClosed] = useState(true); const [isClosed, setClosed] = useState(true);
const handleClose = () => { const handleClose = () => {
localStorage.setItem(STORAGE_DISMISS_KEY, true); localStorage.setItem(STORAGE_DISMISS_KEY, 'true');
setClosed(true); setClosed(true);
}; };
@ -32,7 +37,7 @@ const useAnnouncementBar = () => {
localStorage.setItem(STORAGE_ID_KEY, id); localStorage.setItem(STORAGE_ID_KEY, id);
if (isNewAnnouncement) { if (isNewAnnouncement) {
localStorage.setItem(STORAGE_DISMISS_KEY, false); localStorage.setItem(STORAGE_DISMISS_KEY, 'false');
} }
if ( if (

View file

@ -10,7 +10,7 @@ import {useLocation} from '@docusaurus/router';
import useLocationHash from '@theme/hooks/useLocationHash'; import useLocationHash from '@theme/hooks/useLocationHash';
import useScrollPosition from '@theme/hooks/useScrollPosition'; import useScrollPosition from '@theme/hooks/useScrollPosition';
const useHideableNavbar = (hideOnScroll) => { const useHideableNavbar = (hideOnScroll: boolean) => {
const [isNavbarVisible, setIsNavbarVisible] = useState(true); const [isNavbarVisible, setIsNavbarVisible] = useState(true);
const [isFocusedAnchor, setIsFocusedAnchor] = useState(false); const [isFocusedAnchor, setIsFocusedAnchor] = useState(false);
const [lastScrollTop, setLastScrollTop] = useState(0); const [lastScrollTop, setLastScrollTop] = useState(0);

View file

@ -5,9 +5,11 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {useState, useEffect} from 'react'; import {useState, useEffect, Dispatch, SetStateAction} from 'react';
function useLocationHash(initialHash) { function useLocationHash(
initialHash: string,
): [string, Dispatch<SetStateAction<string>>] {
const [hash, setHash] = useState(initialHash); const [hash, setHash] = useState(initialHash);
useEffect(() => { useEffect(() => {

View file

@ -7,7 +7,7 @@
import {useEffect} from 'react'; import {useEffect} from 'react';
function useLockBodyScroll(lock = true) { function useLockBodyScroll(lock: boolean = true): void {
useEffect(() => { useEffect(() => {
document.body.style.overflow = lock ? 'hidden' : 'visible'; document.body.style.overflow = lock ? 'hidden' : 'visible';

View file

@ -10,13 +10,20 @@ import useThemeContext from '@theme/hooks/useThemeContext';
import useBaseUrl from '@docusaurus/useBaseUrl'; import useBaseUrl from '@docusaurus/useBaseUrl';
import isInternalUrl from '@docusaurus/isInternalUrl'; import isInternalUrl from '@docusaurus/isInternalUrl';
const useLogo = () => { type LogoLinkProps = {target?: string; rel?: string};
const useLogo = (): {
logoLink: string;
logoLinkProps: LogoLinkProps;
logoImageUrl: string;
logoAlt: string;
} => {
const { const {
siteConfig: {themeConfig: {navbar: {logo = {}} = {}}} = {}, siteConfig: {themeConfig: {navbar: {logo = {}} = {}} = {}} = {},
} = useDocusaurusContext(); } = useDocusaurusContext();
const {isDarkTheme} = useThemeContext(); const {isDarkTheme} = useThemeContext();
const logoLink = useBaseUrl(logo.href || '/'); const logoLink = useBaseUrl(logo.href || '/');
let logoLinkProps = {}; let logoLinkProps: LogoLinkProps = {};
if (logo.target) { if (logo.target) {
logoLinkProps = {target: logo.target}; logoLinkProps = {target: logo.target};

View file

@ -9,7 +9,7 @@ import defaultTheme from 'prism-react-renderer/themes/palenight';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useThemeContext from '@theme/hooks/useThemeContext'; import useThemeContext from '@theme/hooks/useThemeContext';
const usePrismTheme = () => { const usePrismTheme = (): typeof defaultTheme => {
const { const {
siteConfig: { siteConfig: {
themeConfig: {prism = {}}, themeConfig: {prism = {}},

View file

@ -8,12 +8,17 @@
import {useState, useEffect} from 'react'; import {useState, useEffect} from 'react';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
const getScrollPosition = () => ({ type ScrollPosition = {scrollX: number; scrollY: number};
const getScrollPosition = (): ScrollPosition => ({
scrollX: ExecutionEnvironment.canUseDOM ? window.pageXOffset : 0, scrollX: ExecutionEnvironment.canUseDOM ? window.pageXOffset : 0,
scrollY: ExecutionEnvironment.canUseDOM ? window.pageYOffset : 0, scrollY: ExecutionEnvironment.canUseDOM ? window.pageYOffset : 0,
}); });
const useScrollPosition = (effect, deps = []) => { const useScrollPosition = (
effect: (position: ScrollPosition) => void,
deps = [],
): ScrollPosition => {
const [scrollPosition, setScrollPosition] = useState(getScrollPosition()); const [scrollPosition, setScrollPosition] = useState(getScrollPosition());
const handleScroll = () => { const handleScroll = () => {
@ -31,6 +36,7 @@ const useScrollPosition = (effect, deps = []) => {
return () => return () =>
window.removeEventListener('scroll', handleScroll, { window.removeEventListener('scroll', handleScroll, {
// @ts-expect-error: See https://github.com/microsoft/TypeScript/issues/32912
passive: true, passive: true,
}); });
}, deps); }, deps);

View file

@ -7,17 +7,23 @@
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
function useTOCHighlight(linkClassName, linkActiveClassName, topOffset) { function useTOCHighlight(
const [lastActiveLink, setLastActiveLink] = useState(undefined); linkClassName: string,
linkActiveClassName: string,
topOffset: number,
): void {
const [lastActiveLink, setLastActiveLink] = useState<HTMLAnchorElement>(
undefined!,
);
useEffect(() => { useEffect(() => {
let headersAnchors = []; let headersAnchors: HTMLCollectionOf<Element>;
let links = []; let links: HTMLCollectionOf<HTMLAnchorElement>;
function setActiveLink() { function setActiveLink() {
function getActiveHeaderAnchor() { function getActiveHeaderAnchor() {
let index = 0; let index = 0;
let activeHeaderAnchor = null; let activeHeaderAnchor: Element | null = null;
headersAnchors = document.getElementsByClassName('anchor'); headersAnchors = document.getElementsByClassName('anchor');
while (index < headersAnchors.length && !activeHeaderAnchor) { while (index < headersAnchors.length && !activeHeaderAnchor) {
@ -40,6 +46,7 @@ function useTOCHighlight(linkClassName, linkActiveClassName, topOffset) {
let index = 0; let index = 0;
let itemHighlighted = false; let itemHighlighted = false;
// @ts-expect-error: Must be <a> tags.
links = document.getElementsByClassName(linkClassName); links = document.getElementsByClassName(linkClassName);
while (index < links.length && !itemHighlighted) { while (index < links.length && !itemHighlighted) {
const link = links[index]; const link = links[index];

View file

@ -9,8 +9,13 @@ import {useState, useCallback, useEffect} from 'react';
const TAB_CHOICE_PREFIX = 'docusaurus.tab.'; const TAB_CHOICE_PREFIX = 'docusaurus.tab.';
const useTabGroupChoice = () => { const useTabGroupChoice = (): {
const [tabGroupChoices, setChoices] = useState({}); tabGroupChoices: {readonly [groupId: string]: string};
setTabGroupChoices: (groupId: string, newChoice: string) => void;
} => {
const [tabGroupChoices, setChoices] = useState<{
readonly [groupId: string]: string;
}>({});
const setChoiceSyncWithLocalStorage = useCallback((groupId, newChoice) => { const setChoiceSyncWithLocalStorage = useCallback((groupId, newChoice) => {
try { try {
localStorage.setItem(`${TAB_CHOICE_PREFIX}${groupId}`, newChoice); localStorage.setItem(`${TAB_CHOICE_PREFIX}${groupId}`, newChoice);
@ -23,7 +28,7 @@ const useTabGroupChoice = () => {
try { try {
const localStorageChoices = {}; const localStorageChoices = {};
for (let i = 0; i < localStorage.length; i += 1) { for (let i = 0; i < localStorage.length; i += 1) {
const storageKey = localStorage.key(i); const storageKey = localStorage.key(i) as string;
if (storageKey.startsWith(TAB_CHOICE_PREFIX)) { if (storageKey.startsWith(TAB_CHOICE_PREFIX)) {
const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length); const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length);
localStorageChoices[groupId] = localStorage.getItem(storageKey); localStorageChoices[groupId] = localStorage.getItem(storageKey);
@ -37,7 +42,7 @@ const useTabGroupChoice = () => {
return { return {
tabGroupChoices, tabGroupChoices,
setTabGroupChoices: (groupId, newChoice) => { setTabGroupChoices: (groupId: string, newChoice: string) => {
setChoices((oldChoices) => ({...oldChoices, [groupId]: newChoice})); setChoices((oldChoices) => ({...oldChoices, [groupId]: newChoice}));
setChoiceSyncWithLocalStorage(groupId, newChoice); setChoiceSyncWithLocalStorage(groupId, newChoice);
}, },

View file

@ -14,9 +14,13 @@ const themes = {
dark: 'dark', dark: 'dark',
}; };
const useTheme = () => { const useTheme = (): {
isDarkTheme: boolean;
setLightTheme: () => void;
setDarkTheme: () => void;
} => {
const { const {
siteConfig: {themeConfig: {disableDarkMode}} = {}, siteConfig: {themeConfig: {disableDarkMode = false} = {}} = {},
} = useDocusaurusContext(); } = useDocusaurusContext();
const [theme, setTheme] = useState( const [theme, setTheme] = useState(
typeof document !== 'undefined' typeof document !== 'undefined'
@ -43,6 +47,7 @@ const useTheme = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
// @ts-expect-error: safe to set null as attribute
document.documentElement.setAttribute('data-theme', theme); document.documentElement.setAttribute('data-theme', theme);
}, [theme]); }, [theme]);

View file

@ -9,8 +9,14 @@ import {useContext} from 'react';
import ThemeContext from '@theme/ThemeContext'; import ThemeContext from '@theme/ThemeContext';
function useThemeContext() { type ThemeContextProps = {
const context = useContext(ThemeContext); isDarkTheme: boolean;
setLightTheme: () => void;
setDarkTheme: () => void;
};
function useThemeContext(): ThemeContextProps {
const context = useContext<ThemeContextProps>(ThemeContext);
if (context == null) { if (context == null) {
throw new Error( throw new Error(
'`useThemeContext` is used outside of `Layout` Component. See https://v2.docusaurus.io/docs/theme-classic#usethemecontext.', '`useThemeContext` is used outside of `Layout` Component. See https://v2.docusaurus.io/docs/theme-classic#usethemecontext.',

View file

@ -9,8 +9,17 @@ import {useContext} from 'react';
import UserPreferencesContext from '@theme/UserPreferencesContext'; import UserPreferencesContext from '@theme/UserPreferencesContext';
function useUserPreferencesContext() { type UserPreferencesContextProps = {
const context = useContext(UserPreferencesContext); tabGroupChoices: {readonly [groupId: string]: string};
setTabGroupChoices: (groupId: string, newChoice: string) => void;
isAnnouncementBarClosed: boolean;
closeAnnouncementBar: () => void;
};
function useUserPreferencesContext(): UserPreferencesContextProps {
const context = useContext<UserPreferencesContextProps>(
UserPreferencesContext,
);
if (context == null) { if (context == null) {
throw new Error( throw new Error(
'`useUserPreferencesContext` is used outside of `Layout` Component.', '`useUserPreferencesContext` is used outside of `Layout` Component.',

View file

@ -12,9 +12,11 @@ const desktopThresholdWidth = 996;
const windowSizes = { const windowSizes = {
desktop: 'desktop', desktop: 'desktop',
mobile: 'mobile', mobile: 'mobile',
}; } as const;
function useWindowSize() { type WindowSize = keyof typeof windowSizes;
function useWindowSize(): WindowSize | undefined {
const isClient = typeof window !== 'undefined'; const isClient = typeof window !== 'undefined';
function getSize() { function getSize() {
@ -30,7 +32,7 @@ function useWindowSize() {
useEffect(() => { useEffect(() => {
if (!isClient) { if (!isClient) {
return false; return undefined;
} }
function handleResize() { function handleResize() {

View file

@ -7,16 +7,17 @@
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
import siteConfig from '@generated/docusaurus.config'; import siteConfig from '@generated/docusaurus.config';
import type * as PrismNamespace from 'prismjs';
const prismIncludeLanguages = (Prism) => { const prismIncludeLanguages = (PrismObject: typeof PrismNamespace): void => {
if (ExecutionEnvironment.canUseDOM) { if (ExecutionEnvironment.canUseDOM) {
const { const {
themeConfig: {prism: {additionalLanguages = []} = {}}, themeConfig: {prism: {additionalLanguages = []} = {}},
} = siteConfig; } = siteConfig;
window.Prism = Prism; window.Prism = PrismObject;
additionalLanguages.forEach((lang) => { additionalLanguages.forEach((lang: string) => {
require(`prismjs/components/prism-${lang}`); // eslint-disable-line require(`prismjs/components/prism-${lang}`); // eslint-disable-line
}); });

View file

@ -0,0 +1,9 @@
/**
* 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-next-line spaced-comment
/// <reference types="@docusaurus/module-type-aliases" />

View file

@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"lib": ["DOM"],
"module": "esnext",
"noEmit": true,
"noImplicitAny": false,
"jsx": "react",
"baseUrl": "src"
},
"include": ["src/"]
}

View file

@ -120,6 +120,7 @@ export interface Plugin<T, U = unknown> {
utils: ConfigureWebpackUtils, utils: ConfigureWebpackUtils,
): Configuration; ): Configuration;
getThemePath?(): string; getThemePath?(): string;
getTypeScriptThemePath?(): string;
getPathsToWatch?(): string[]; getPathsToWatch?(): string[];
getClientModules?(): string[]; getClientModules?(): string[];
extendCli?(cli: Command): void; extendCli?(cli: Command): void;

View file

@ -60,7 +60,7 @@ export function objectWithKeySorted(obj: {[index: string]: any}) {
} }
const indexRE = /(^|.*\/)index\.(md|js|jsx|ts|tsx)$/i; const indexRE = /(^|.*\/)index\.(md|js|jsx|ts|tsx)$/i;
const extRE = /\.(md|js|tsx)$/; const extRE = /\.(md|js|ts|tsx)$/;
/** /**
* Convert filepath to url path. * Convert filepath to url path.

View file

@ -60,8 +60,17 @@ cli
cli cli
.command('swizzle <themeName> [componentName] [siteDir]') .command('swizzle <themeName> [componentName] [siteDir]')
.description('Copy the theme files into website folder for customization.') .description('Copy the theme files into website folder for customization.')
.action((themeName, componentName, siteDir = '.') => { .option(
wrapCommand(swizzle)(path.resolve(siteDir), themeName, componentName); '--typescript',
'Copy TypeScript theme files when possible (default: false)',
)
.action((themeName, componentName, siteDir = '.', {typescript}) => {
wrapCommand(swizzle)(
path.resolve(siteDir),
themeName,
componentName,
typescript,
);
}); });
cli cli

View file

@ -18,13 +18,16 @@ export default async function swizzle(
siteDir: string, siteDir: string,
themeName: string, themeName: string,
componentName?: string, componentName?: string,
typescript?: boolean,
): Promise<void> { ): Promise<void> {
const plugin = importFresh(themeName) as ( const plugin = importFresh(themeName) as (
context: LoadContext, context: LoadContext,
) => Plugin<unknown>; ) => Plugin<unknown>;
const context = loadContext(siteDir); const context = loadContext(siteDir);
const pluginInstance = plugin(context); const pluginInstance = plugin(context);
let fromPath = pluginInstance.getThemePath?.(); let fromPath = typescript
? pluginInstance.getTypeScriptThemePath?.()
: pluginInstance.getThemePath?.();
if (fromPath) { if (fromPath) {
let toPath = path.resolve(siteDir, THEME_PATH); let toPath = path.resolve(siteDir, THEME_PATH);
@ -32,10 +35,16 @@ export default async function swizzle(
fromPath = path.join(fromPath, componentName); fromPath = path.join(fromPath, componentName);
toPath = path.join(toPath, componentName); toPath = path.join(toPath, componentName);
// Handle single JavaScript file only. // Handle single TypeScript/JavaScript file only.
// E.g: if <fromPath> does not exist, we try to swizzle <fromPath>.js instead // E.g: if <fromPath> does not exist, we try to swizzle <fromPath>.(ts|tsx|js) instead
if (!fs.existsSync(fromPath) && fs.existsSync(`${fromPath}.js`)) { if (!fs.existsSync(fromPath)) {
[fromPath, toPath] = [`${fromPath}.js`, `${toPath}.js`]; if (fs.existsSync(`${fromPath}.ts`)) {
[fromPath, toPath] = [`${fromPath}.ts`, `${toPath}.ts`];
} else if (fs.existsSync(`${fromPath}.tsx`)) {
[fromPath, toPath] = [`${fromPath}.tsx`, `${toPath}.tsx`];
} else if (fs.existsSync(`${fromPath}.js`)) {
[fromPath, toPath] = [`${fromPath}.js`, `${toPath}.js`];
}
} }
} }
await fs.copy(fromPath, toPath); await fs.copy(fromPath, toPath);
@ -48,5 +57,13 @@ export default async function swizzle(
console.log( console.log(
`\n${chalk.green('Success!')} Copied ${fromMsg} to ${toMsg}.\n`, `\n${chalk.green('Success!')} Copied ${fromMsg} to ${toMsg}.\n`,
); );
} else if (typescript) {
console.warn(
chalk.yellow(
`${themeName} does not provide TypeScript theme code via getTypeScriptThemePath().`,
),
);
} else {
console.warn(chalk.yellow(`${themeName} does not provide any theme code.`));
} }
} }

View file

@ -2,7 +2,7 @@
"compilerOptions": { "compilerOptions": {
"target": "es2017", "target": "es2017",
"module": "commonjs", "module": "commonjs",
"lib": ["es2017","es2019.array"], "lib": ["es2017","es2019.array", "DOM"],
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,

View file

@ -9,7 +9,7 @@ import React from 'react';
import Layout from '@theme/Layout'; import Layout from '@theme/Layout';
import Feedback from '../pages/feedback'; import Feedback from '../pages/feedback';
function NotFound({location}) { function NotFound({location}: {location: {pathname: string}}): JSX.Element {
if (/^\/feedback/.test(location.pathname)) { if (/^\/feedback/.test(location.pathname)) {
return <Feedback />; return <Feedback />;
} }

9
website/src/types.d.ts vendored Normal file
View file

@ -0,0 +1,9 @@
/**
* 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-next-line spaced-comment
/// <reference types="@docusaurus/module-type-aliases" />

13
website/tsconfig.json Normal file
View file

@ -0,0 +1,13 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "../tsconfig.json",
"compilerOptions": {
"lib": ["DOM"],
"module": "esnext",
"noEmit": true,
"noImplicitAny": false,
"jsx": "react",
"baseUrl": "src"
},
"include": ["src/"]
}

View file

@ -2,6 +2,22 @@
# yarn lockfile v1 # yarn lockfile v1
"@babel/cli@^7.9.0":
version "7.10.3"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.10.3.tgz#4ea83bd997d2a41c78d07263ada3ec466fb3764b"
integrity sha512-lWB3yH5/fWY8pi2Kj5/fA+17guJ9feSBw5DNjTju3/nRi9sXnl1JPh7aKQOSvdNbiDbkzzoGYtsr46M8dGmXDQ==
dependencies:
commander "^4.0.1"
convert-source-map "^1.1.0"
fs-readdir-recursive "^1.1.0"
glob "^7.0.0"
lodash "^4.17.13"
make-dir "^2.1.0"
slash "^2.0.0"
source-map "^0.5.0"
optionalDependencies:
chokidar "^2.1.8"
"@babel/code-frame@7.5.5": "@babel/code-frame@7.5.5":
version "7.5.5" version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
@ -3051,6 +3067,11 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
"@types/prismjs@^1.16.1":
version "1.16.1"
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.1.tgz#50b82947207847db6abcbcd14caa89e3b897c259"
integrity sha512-RNgcK3FEc1GpeOkamGDq42EYkb6yZW5OWQwTS56NJIB8WL0QGISQglA7En7NUx9RGP8AC52DOe+squqbAckXlA==
"@types/prop-types@*": "@types/prop-types@*":
version "15.7.3" version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
@ -5807,7 +5828,7 @@ conventional-recommended-bump@^5.0.0:
meow "^4.0.0" meow "^4.0.0"
q "^1.5.1" q "^1.5.1"
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.7.0" version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
@ -8301,6 +8322,11 @@ fs-minipass@^2.0.0:
dependencies: dependencies:
minipass "^3.0.0" minipass "^3.0.0"
fs-readdir-recursive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==
fs-write-stream-atomic@^1.0.8: fs-write-stream-atomic@^1.0.8:
version "1.0.10" version "1.0.10"
resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"