refactor(theme-classic): completely migrate package to TypeScript (#5459)

* Migrate

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Migrate prism as well

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Fix

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Fix lock file

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Fix typing

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* refactor a bit CodeBlock

* simplify versionBanner typing => use null instead of "none" (apart plugin options for retrocompatibility)

* Remove return signatures

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
Joshua Chen 2021-09-01 20:34:26 +08:00 committed by GitHub
parent 5f003bcabd
commit 78d84006bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 153 additions and 95 deletions

View file

@ -514,7 +514,7 @@ Object {
\\"pluginId\\": \\"default\\",
\\"version\\": \\"current\\",
\\"label\\": \\"Next\\",
\\"banner\\": \\"none\\",
\\"banner\\": null,
\\"badge\\": false,
\\"className\\": \\"docs-version-current\\",
\\"isLast\\": true,
@ -1025,7 +1025,7 @@ Object {
\\"pluginId\\": \\"community\\",
\\"version\\": \\"1.0.0\\",
\\"label\\": \\"1.0.0\\",
\\"banner\\": \\"none\\",
\\"banner\\": null,
\\"badge\\": true,
\\"className\\": \\"docs-version-1.0.0\\",
\\"isLast\\": true,
@ -1722,7 +1722,7 @@ Object {
\\"pluginId\\": \\"default\\",
\\"version\\": \\"1.0.1\\",
\\"label\\": \\"1.0.1\\",
\\"banner\\": \\"none\\",
\\"banner\\": null,
\\"badge\\": true,
\\"className\\": \\"docs-version-1.0.1\\",
\\"isLast\\": true,

View file

@ -81,7 +81,7 @@ describe('simple site', () => {
versionLabel: 'Next',
versionName: 'current',
versionPath: '/docs',
versionBanner: 'none',
versionBanner: null,
versionBadge: false,
versionClassName: 'docs-version-current',
};
@ -262,7 +262,7 @@ describe('versioned site, pluginId=default', () => {
versionLabel: '1.0.1',
versionName: '1.0.1',
versionPath: '/docs',
versionBanner: 'none',
versionBanner: null,
versionBadge: true,
versionClassName: 'docs-version-1.0.1',
};
@ -554,7 +554,7 @@ describe('versioned site, pluginId=default', () => {
routePriority: -1,
tagsPath: '/docs/tags',
versionPath: '/docs',
versionBanner: 'none',
versionBanner: null,
versionBadge: false,
},
]);
@ -702,7 +702,7 @@ describe('versioned site, pluginId=community', () => {
versionLabel: '1.0.0',
versionName: '1.0.0',
versionPath: '/communityBasePath',
versionBanner: 'none',
versionBanner: null,
versionBadge: true,
versionClassName: 'docs-version-1.0.0',
};
@ -750,7 +750,7 @@ describe('versioned site, pluginId=community', () => {
routePriority: -1,
tagsPath: '/communityBasePath/tags',
versionPath: '/communityBasePath',
versionBanner: 'none',
versionBanner: null,
versionBadge: false,
},
]);

View file

@ -11,7 +11,7 @@ declare module '@docusaurus/plugin-content-docs' {
// TODO public api surface types should rather be exposed as "@docusaurus/plugin-content-docs"
declare module '@docusaurus/plugin-content-docs-types' {
type VersionBanner = import('./types').VersionBanner;
export type VersionBanner = import('./types').VersionBanner;
type GlobalDataVersion = import('./types').GlobalVersion;
type GlobalDataDoc = import('./types').GlobalDoc;
type VersionTag = import('./types').VersionTag;
@ -22,7 +22,7 @@ declare module '@docusaurus/plugin-content-docs-types' {
pluginId: string;
version: string;
label: string;
banner: VersionBanner;
banner: VersionBanner | null;
badge: boolean;
className: string;
isLast: boolean;

View file

@ -32,7 +32,7 @@ export type VersionMetadata = ContentPaths & {
tagsPath: string;
versionEditUrl?: string | undefined;
versionEditUrlLocalized?: string | undefined;
versionBanner: VersionBanner;
versionBanner: VersionBanner | null;
versionBadge: boolean;
versionClassName: string;
isLast: boolean;
@ -65,12 +65,12 @@ export type PathOptions = {
};
// TODO support custom version banner? {type: "error", content: "html content"}
export type VersionBanner = 'none' | 'unreleased' | 'unmaintained';
export type VersionBanner = 'unreleased' | 'unmaintained';
export type VersionOptions = {
path?: string;
label?: string;
banner?: VersionBanner;
banner?: 'none' | VersionBanner;
badge?: boolean;
className?: string;
};

View file

@ -264,10 +264,10 @@ function getDefaultVersionBanner({
versionName: string;
versionNames: string[];
lastVersionName: string;
}): VersionBanner {
}): VersionBanner | null {
// Current version: good, no banner
if (versionName === lastVersionName) {
return 'none';
return null;
}
// Upcoming versions: unreleased banner
else if (
@ -291,17 +291,16 @@ function getVersionBanner({
versionNames: string[];
lastVersionName: string;
options: Pick<PluginOptions, 'versions'>;
}): VersionBanner {
const versionOptionBanner = options.versions[versionName]?.banner;
return (
versionOptionBanner ??
getDefaultVersionBanner({
versionName,
versionNames,
lastVersionName,
})
);
}): VersionBanner | null {
const versionBannerOption = options.versions[versionName]?.banner;
if (versionBannerOption) {
return versionBannerOption === 'none' ? null : versionBannerOption;
}
return getDefaultVersionBanner({
versionName,
versionNames,
lastVersionName,
});
}
function getVersionBadge({

View file

@ -51,7 +51,9 @@
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.5",
"@types/mdx-js__react": "^1.5.4",
"@types/parse-numeric-range": "^0.0.1",
"@types/rtlcss": "^3.1.1",
"utility-types": "^3.10.0"
},
"peerDependencies": {

View file

@ -10,7 +10,7 @@ import type {ThemeConfig} from '@docusaurus/theme-common';
import {getTranslationFiles, translateThemeConfig} from './translations';
import path from 'path';
import Module from 'module';
import type {AcceptedPlugin, Result, Plugin as PostCssPlugin} from 'postcss';
import type {AcceptedPlugin, Plugin as PostCssPlugin} from 'postcss';
import rtlcss from 'rtlcss';
import {readDefaultCodeTranslationMessages} from '@docusaurus/utils';
@ -25,7 +25,10 @@ const ContextReplacementPlugin = requireFromDocusaurusCore(
// Need to be inlined to prevent dark mode FOUC
// Make sure that the 'storageKey' is the same as the one in `/theme/hooks/useTheme.js`
const ThemeStorageKey = 'theme';
const noFlashColorMode = ({defaultMode, respectPrefersColorScheme}) => {
const noFlashColorMode = ({
defaultMode,
respectPrefersColorScheme,
}: ThemeConfig['colorMode']) => {
return `(function() {
var defaultMode = '${defaultMode}';
var respectPrefersColorScheme = ${respectPrefersColorScheme};
@ -83,7 +86,7 @@ const AnnouncementBarInlineJavaScript = `
document.documentElement.setAttribute('${AnnouncementBarDismissDataAttribute}', isDismissed());
})();`;
function getInfimaCSSFile(direction) {
function getInfimaCSSFile(direction: string) {
return `infima/dist/css/default/default${
direction === 'rtl' ? '-rtl' : ''
}.css`;
@ -183,13 +186,13 @@ export default function docusaurusThemeClassic(
const resolvedInfimaFile = require.resolve(getInfimaCSSFile(direction));
const plugin: PostCssPlugin = {
postcssPlugin: 'RtlCssPlugin',
prepare: (result: Result) => {
prepare: (result) => {
const file = result.root?.source?.input?.file;
// Skip Infima as we are using the its RTL version.
if (file === resolvedInfimaFile) {
return {};
}
return rtlcss(result.root);
return rtlcss((result.root as unknown) as rtlcss.ConfigOptions);
},
};
postCssOptions.plugins.push(plugin);

View file

@ -18,49 +18,61 @@ import styles from './styles.module.css';
import {useThemeConfig, parseCodeBlockTitle} from '@docusaurus/theme-common';
const highlightLinesRangeRegex = /{([\d,-]+)}/;
const HighlightLinesRangeRegex = /{([\d,-]+)}/;
const HighlightLanguages = ['js', 'jsBlock', 'jsx', 'python', 'html'] as const;
type HighlightLanguage = typeof HighlightLanguages[number];
type HighlightLanguageConfig = {
start: string;
end: string;
};
// Supported types of highlight comments
const HighlightComments: Record<HighlightLanguage, HighlightLanguageConfig> = {
js: {
start: '\\/\\/',
end: '',
},
jsBlock: {
start: '\\/\\*',
end: '\\*\\/',
},
jsx: {
start: '\\{\\s*\\/\\*',
end: '\\*\\/\\s*\\}',
},
python: {
start: '#',
end: '',
},
html: {
start: '<!--',
end: '-->',
},
};
// Supported highlight directives
const HighlightDirectives = [
'highlight-next-line',
'highlight-start',
'highlight-end',
];
const getHighlightDirectiveRegex = (
languages = ['js', 'jsBlock', 'jsx', 'python', 'html'],
languages: readonly HighlightLanguage[] = HighlightLanguages,
) => {
// supported types of comments
const comments = {
js: {
start: '\\/\\/',
end: '',
},
jsBlock: {
start: '\\/\\*',
end: '\\*\\/',
},
jsx: {
start: '\\{\\s*\\/\\*',
end: '\\*\\/\\s*\\}',
},
python: {
start: '#',
end: '',
},
html: {
start: '<!--',
end: '-->',
},
};
// supported directives
const directives = [
'highlight-next-line',
'highlight-start',
'highlight-end',
].join('|');
// to be more reliable, the opening and closing comment must match
const commentPattern = languages
.map(
(lang) =>
`(?:${comments[lang].start}\\s*(${directives})\\s*${comments[lang].end})`,
)
.map((lang) => {
const {start, end} = HighlightComments[lang];
return `(?:${start}\\s*(${HighlightDirectives.join('|')})\\s*${end})`;
})
.join('|');
// white space is allowed, but otherwise it should be on it's own line
return new RegExp(`^\\s*(?:${commentPattern})\\s*$`);
};
// select comment styles based on language
const highlightDirectiveRegex = (lang: string) => {
switch (lang) {
@ -123,9 +135,9 @@ export default function CodeBlock({
? children.join('')
: (children as string);
if (metastring && highlightLinesRangeRegex.test(metastring)) {
if (metastring && HighlightLinesRangeRegex.test(metastring)) {
// Tested above
const highlightLinesRange = metastring.match(highlightLinesRangeRegex)![1];
const highlightLinesRange = metastring.match(HighlightLinesRangeRegex)![1];
highlightLines = rangeParser(highlightLinesRange).filter((n) => n > 0);
}

View file

@ -35,7 +35,7 @@ function useShowAnnouncementBar() {
return showAnnouncementBar;
}
function HideableSidebarButton({onClick}) {
function HideableSidebarButton({onClick}: {onClick: React.MouseEventHandler}) {
return (
<button
type="button"

View file

@ -26,7 +26,10 @@ import type {
import styles from './styles.module.css';
const isActiveSidebarItem = (item: Props['item'], activePath: string) => {
const isActiveSidebarItem = (
item: Props['item'],
activePath: string,
): boolean => {
if (item.type === 'link') {
return isSamePath(item.href, activePath);
}

View file

@ -21,6 +21,7 @@ import {
import type {Props} from '@theme/DocVersionBanner';
import clsx from 'clsx';
import type {VersionBanner} from '@docusaurus/plugin-content-docs-types';
type BannerLabelComponentProps = {
siteTitle: string;
@ -66,7 +67,7 @@ function UnmaintainedVersionLabel({
}
const BannerLabelComponents: Record<
Exclude<Props['versionMetadata']['banner'], 'none'>,
VersionBanner,
ComponentType<BannerLabelComponentProps>
> = {
unreleased: UnreleasedVersionLabel,
@ -75,7 +76,7 @@ const BannerLabelComponents: Record<
function BannerLabel(props: BannerLabelComponentProps) {
const BannerLabelComponent =
BannerLabelComponents[props.versionMetadata.banner];
BannerLabelComponents[props.versionMetadata.banner!];
return <BannerLabelComponent {...props} />;
}
@ -156,11 +157,10 @@ function DocVersionBannerEnabled({versionMetadata}: Props): JSX.Element {
}
function DocVersionBanner({versionMetadata}: Props): JSX.Element {
if (versionMetadata.banner === 'none') {
return <></>;
} else {
if (versionMetadata.banner) {
return <DocVersionBannerEnabled versionMetadata={versionMetadata} />;
}
return <></>;
}
export default DocVersionBanner;

View file

@ -16,9 +16,10 @@ import {
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';
const getVersionMainDoc = (version) =>
version.docs.find((doc) => doc.id === version.mainDocId);
const getVersionMainDoc = (version: GlobalDataVersion) =>
version.docs.find((doc) => doc.id === version.mainDocId)!;
export default function DocsVersionDropdownNavbarItem({
mobile,

View file

@ -16,7 +16,9 @@ import type {Types, Props} from '@theme/NavbarItem';
const NavbarItemComponents: Record<
Exclude<Types, undefined>,
() => (props) => JSX.Element
// TODO: properly type this
// eslint-disable-next-line @typescript-eslint/no-explicit-any
() => (props: any) => JSX.Element
> = {
default: () => DefaultNavbarItem,
localeDropdown: () => LocaleDropdownNavbarItem,

View file

@ -21,11 +21,11 @@ const useTabGroupChoice = (): useTabGroupChoiceReturns => {
useEffect(() => {
try {
const localStorageChoices = {};
const localStorageChoices: Record<string, string> = {};
listStorageKeys().forEach((storageKey) => {
if (storageKey.startsWith(TAB_CHOICE_PREFIX)) {
const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length);
localStorageChoices[groupId] = createStorageSlot(storageKey).get();
localStorageChoices[groupId] = createStorageSlot(storageKey).get()!;
}
});
setChoices(localStorageChoices);

View file

@ -756,3 +756,18 @@ declare module '@theme/Tag' {
export default function Tag(props: Props): JSX.Element;
}
declare module '@theme/prism-include-languages' {
import type * as PrismNamespace from 'prismjs';
export default function prismIncludeLanguages(
PrismObject: typeof PrismNamespace,
): void;
}
declare module 'prism-react-renderer/prism' {
import type * as PrismNamespace from 'prismjs';
const Prism: typeof PrismNamespace;
export default Prism;
}

View file

@ -5,7 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
const {Joi, URISchema} = require('@docusaurus/utils-validation');
import {Joi, URISchema} from '@docusaurus/utils-validation';
import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types';
const DEFAULT_DOCS_CONFIG = {
versionPersistence: 'localStorage',
@ -81,7 +82,7 @@ const DocItemSchema = NavbarItemBaseSchema.append({
docsPluginId: Joi.string(),
});
const itemWithType = (type) => {
const itemWithType = (type: string | undefined) => {
// because equal(undefined) is not supported :/
const typeSchema = type
? Joi.string().required().equal(type)
@ -95,7 +96,7 @@ const itemWithType = (type) => {
const DropdownSubitemSchema = Joi.object({
position: Joi.forbidden(),
}).when({
}).when('.', {
switch: [
{
is: itemWithType('docsVersion'),
@ -150,7 +151,7 @@ const SearchItemSchema = Joi.object({
const NavbarItemSchema = Joi.object({
position: NavbarItemPosition,
}).when({
}).when('.', {
switch: [
{
is: itemWithType('docsVersion'),
@ -178,7 +179,7 @@ const NavbarItemSchema = Joi.object({
},
{
is: itemWithType(undefined),
then: Joi.object().when({
then: Joi.object().when('.', {
// Dropdown item can be specified without type field
is: Joi.object({
items: Joi.array().required(),
@ -246,12 +247,12 @@ const CustomCssSchema = Joi.alternatives()
const ThemeConfigSchema = Joi.object({
// TODO temporary (@alpha-58)
disableDarkMode: Joi.any().forbidden(false).messages({
disableDarkMode: Joi.any().forbidden().messages({
'any.unknown':
'disableDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.disableSwitch = true',
}),
// TODO temporary (@alpha-58)
defaultDarkMode: Joi.any().forbidden(false).messages({
defaultDarkMode: Joi.any().forbidden().messages({
'any.unknown':
'defaultDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.defaultMode = "dark"',
}),
@ -329,8 +330,15 @@ const ThemeConfigSchema = Joi.object({
'The themeConfig.sidebarCollapsible has been moved to docs plugin options. See: https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-docs',
}),
});
exports.ThemeConfigSchema = ThemeConfigSchema;
exports.validateThemeConfig = ({validate, themeConfig}) => {
export {ThemeConfigSchema};
export function validateThemeConfig({
validate,
themeConfig,
}: {
validate: Validate<ThemeConfig>;
themeConfig: ThemeConfig;
}): ValidationResult<ThemeConfig> {
return validate(ThemeConfigSchema, themeConfig);
};
}

View file

@ -4,7 +4,6 @@
"lib": ["DOM", "ES2019"],
"module": "esnext",
"noEmit": true,
"noImplicitAny": false,
"jsx": "react",
"baseUrl": "src"
},

View file

@ -4220,6 +4220,13 @@
dependencies:
"@types/unist" "*"
"@types/mdx-js__react@^1.5.4":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@types/mdx-js__react/-/mdx-js__react-1.5.4.tgz#75ed343b5a1f56d9518e902d956e1b611da4a297"
integrity sha512-hce+Hymj3cEMOma13eQRhpT3mhQrOkN4CAYEwKWB/qj2BiuUrrTpCkOShEERGmLVgDc3nxF4Dc0k7W999EQxnQ==
dependencies:
"@types/react" "*"
"@types/micromatch@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.2.tgz#ce29c8b166a73bf980a5727b1e4a4d099965151d"
@ -4419,6 +4426,13 @@
dependencies:
"@types/node" "*"
"@types/rtlcss@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@types/rtlcss/-/rtlcss-3.1.1.tgz#f091b04a2c48b92317228113a2a235c755d51c77"
integrity sha512-xj8SSY1sadjWrU9we6pm0jaSsZiZoZeEuzN5ebYW6SKCi6xIWLFCkKr58bHTaopqoDiyhwLbCvxIQ7uW4SK98g==
dependencies:
postcss "^8.2.x"
"@types/sax@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.1.tgz#e0248be936ece791a82db1a57f3fb5f7c87e8172"
@ -16035,10 +16049,10 @@ postcss-zindex@^5.0.1:
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@^8.1.7, postcss@^8.2.15, postcss@^8.2.4, postcss@^8.2.9:
version "8.3.1"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.1.tgz#71f380151c227f83b898294a46481f689f86b70a"
integrity sha512-9qH0MGjsSm+fjxOi3GnwViL1otfi7qkj+l/WX5gcRGmZNGsIcqc+A5fBkE6PUobEQK4APqYVaES+B3Uti98TCw==
postcss@^8.1.7, postcss@^8.2.15, postcss@^8.2.4, postcss@^8.2.9, postcss@^8.2.x:
version "8.3.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea"
integrity sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.23"