mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 10:17:55 +02:00
chore: upgrade TypeScript & other ESLint related deps (#5963)
* chore: upgrade ESLint related deps * Upgrade TS * Fix lock * Bump Babel * Update config
This commit is contained in:
parent
2f7d6fea1e
commit
0374426ce3
104 changed files with 2662 additions and 2487 deletions
23
.eslintrc.js
23
.eslintrc.js
|
@ -31,7 +31,6 @@ module.exports = {
|
|||
'plugin:react-hooks/recommended',
|
||||
'airbnb',
|
||||
'prettier',
|
||||
'prettier/react',
|
||||
],
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
|
@ -61,6 +60,7 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
'import/extensions': OFF,
|
||||
'no-restricted-exports': OFF,
|
||||
'header/header': [
|
||||
ERROR,
|
||||
'block',
|
||||
|
@ -90,6 +90,14 @@ module.exports = {
|
|||
'react/prefer-stateless-function': WARNING,
|
||||
'react/jsx-props-no-spreading': OFF,
|
||||
'react/require-default-props': [ERROR, {ignoreFunctionalComponents: true}],
|
||||
'react/function-component-definition': [
|
||||
WARNING,
|
||||
{
|
||||
namedComponents: 'function-declaration',
|
||||
unnamedComponents: 'arrow-function',
|
||||
},
|
||||
],
|
||||
'react/no-unstable-nested-components': [WARNING, {allowAsProps: true}],
|
||||
'@typescript-eslint/no-inferrable-types': OFF,
|
||||
'import/first': OFF,
|
||||
'import/order': OFF,
|
||||
|
@ -104,19 +112,16 @@ module.exports = {
|
|||
'no-unused-vars': OFF,
|
||||
'no-nested-ternary': WARNING,
|
||||
'@typescript-eslint/no-empty-function': OFF,
|
||||
'@typescript-eslint/no-non-null-assertion': OFF, // Have to use type assertion anyways
|
||||
'@typescript-eslint/no-non-null-assertion': OFF,
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
ERROR,
|
||||
{argsIgnorePattern: '^_', ignoreRestSiblings: true},
|
||||
],
|
||||
'@typescript-eslint/explicit-module-boundary-types': WARNING,
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
ERROR,
|
||||
{'ts-expect-error': 'allow-with-description'},
|
||||
],
|
||||
|
||||
// TODO re-enable some these as errors
|
||||
// context: https://github.com/facebook/docusaurus/pull/2949
|
||||
'@typescript-eslint/ban-types': WARNING,
|
||||
'import/no-extraneous-dependencies': ERROR,
|
||||
'no-useless-escape': WARNING,
|
||||
'prefer-template': WARNING,
|
||||
|
@ -189,6 +194,12 @@ module.exports = {
|
|||
'import/no-duplicates': OFF,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {
|
||||
'import/no-import-module-exports': OFF,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.js'],
|
||||
rules: {
|
||||
|
|
|
@ -22,12 +22,10 @@ async function getPackagesJsonFiles(): Promise<PackageJsonFile[]> {
|
|||
const files = await glob('packages/*/package.json');
|
||||
|
||||
return Promise.all(
|
||||
files.map(async (file) => {
|
||||
return {
|
||||
file,
|
||||
content: JSON.parse(await readFile(file, 'utf8')),
|
||||
};
|
||||
}),
|
||||
files.map(async (file) => ({
|
||||
file,
|
||||
content: JSON.parse(await readFile(file, 'utf8')),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ export function createPlaygroundResponse(
|
|||
// Inspired by https://stackoverflow.com/a/3409200/82609
|
||||
function parseCookieString(cookieString: string): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
cookieString.split(';').forEach(function (cookie) {
|
||||
cookieString.split(';').forEach((cookie) => {
|
||||
const [name, value] = cookie.split('=');
|
||||
result[name.trim()] = decodeURI(value);
|
||||
});
|
||||
|
|
36
package.json
36
package.json
|
@ -58,13 +58,13 @@
|
|||
"lock:update": "npx yarn-deduplicate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.15.7",
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.15.7",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.13",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.12.16",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.16",
|
||||
"@babel/cli": "^7.16.0",
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/eslint-parser": "^7.16.3",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.16.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.16.0",
|
||||
"@babel/preset-typescript": "^7.16.0",
|
||||
"@crowdin/cli": "^3.7.1",
|
||||
"@formatjs/intl-datetimeformat": "^3.2.12",
|
||||
"@formatjs/intl-numberformat": "^6.2.2",
|
||||
|
@ -87,18 +87,18 @@
|
|||
"@types/shelljs": "^0.8.6",
|
||||
"@types/wait-on": "^5.2.0",
|
||||
"@types/webpack-dev-server": "^4.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"concurrently": "^6.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-config-prettier": "^6.15.0",
|
||||
"eslint-plugin-header": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-airbnb": "^19.0.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-react": "^7.27.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"glob": "^7.1.6",
|
||||
"husky": "^5.0.9",
|
||||
"is-ci": "^3.0.0",
|
||||
|
@ -117,7 +117,7 @@
|
|||
"serve": "^12.0.1",
|
||||
"stylelint": "^13.10.0",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.1.5"
|
||||
"typescript": "^4.5.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
|
|
|
@ -42,7 +42,12 @@ program
|
|||
.option('--typescript')
|
||||
.description('Initialize website.')
|
||||
.action(
|
||||
(siteName, template, rootDir = '.', {useNpm, skipInstall, typescript}) => {
|
||||
(
|
||||
siteName,
|
||||
template,
|
||||
rootDir = '.',
|
||||
{useNpm, skipInstall, typescript} = {},
|
||||
) => {
|
||||
wrapCommand(init)(path.resolve(rootDir), siteName, template, {
|
||||
useNpm,
|
||||
skipInstall,
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.0.0-beta.9",
|
||||
"@tsconfig/docusaurus": "^1.0.4",
|
||||
"typescript": "^4.3.5"
|
||||
"typescript": "^4.5.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
|
|
@ -23,7 +23,7 @@ module.exports = {
|
|||
parserOptions: {
|
||||
allowImportExportEverywhere: true,
|
||||
},
|
||||
extends: ['airbnb', 'prettier', 'prettier/react'],
|
||||
extends: ['airbnb', 'prettier'],
|
||||
plugins: ['react-hooks', 'header'],
|
||||
rules: {
|
||||
// Ignore certain webpack alias because it can't be resolved
|
||||
|
@ -56,5 +56,12 @@ module.exports = {
|
|||
'react/jsx-filename-extension': OFF,
|
||||
'react-hooks/rules-of-hooks': ERROR,
|
||||
'react/prop-types': OFF, // PropTypes aren't used much these days.
|
||||
'react/function-component-definition': [
|
||||
WARNING,
|
||||
{
|
||||
namedComponents: 'function-declaration',
|
||||
unnamedComponents: 'arrow-function',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -30,15 +30,15 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.15.7",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-config-prettier": "^6.15.0",
|
||||
"eslint-plugin-header": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"prettier": "^2.2.1",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint-config-airbnb": "^19.0.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-react": "^7.27.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"prettier": "^2.4.1",
|
||||
"stylelint": "^13.2.1"
|
||||
},
|
||||
"browserslist": {
|
||||
|
|
|
@ -38,7 +38,7 @@ cli
|
|||
.option('--mdx', 'try to migrate MD to MDX too')
|
||||
.option('--page', 'try to migrate pages too')
|
||||
.description('Migrate between versions of Docusaurus website.')
|
||||
.action((siteDir = '.', newDir = '.', {mdx, page}) => {
|
||||
.action((siteDir = '.', newDir = '.', {mdx, page} = {}) => {
|
||||
const sitePath = path.resolve(siteDir);
|
||||
const newSitePath = path.resolve(newDir);
|
||||
wrapCommand(migrateDocusaurusProject)(sitePath, newSitePath, mdx, page);
|
||||
|
|
|
@ -47,11 +47,10 @@ function sanitizedFileContent(
|
|||
): string {
|
||||
const extractedData = extractMetadata(content);
|
||||
const extractedMetaData = Object.entries(extractedData.metadata).reduce(
|
||||
(metaData, [key, value]) => {
|
||||
return `${metaData}\n${key}: ${
|
||||
(metaData, [key, value]) =>
|
||||
`${metaData}\n${key}: ${
|
||||
shouldQuotifyFrontMatter([key, value]) ? `"${value}"` : value
|
||||
}`;
|
||||
},
|
||||
}`,
|
||||
'',
|
||||
);
|
||||
const sanitizedData = `---${extractedMetaData}\n---\n${
|
||||
|
@ -618,28 +617,26 @@ function migrateVersionedSidebar(
|
|||
const newSidebar = Object.entries(sidebar.entries).reduce(
|
||||
(acc: SidebarEntries, val) => {
|
||||
const key = `version-${sidebar.version}/${val[0]}`;
|
||||
acc[key] = Object.entries(val[1]).map((value) => {
|
||||
return {
|
||||
type: 'category',
|
||||
label: value[0],
|
||||
items: (value[1] as Array<SidebarEntry>).map((sidebarItem) => {
|
||||
if (typeof sidebarItem === 'string') {
|
||||
return {
|
||||
type: 'doc',
|
||||
id: `version-${sidebar.version}/${sidebarItem}`,
|
||||
};
|
||||
}
|
||||
acc[key] = Object.entries(val[1]).map((value) => ({
|
||||
type: 'category',
|
||||
label: value[0],
|
||||
items: (value[1] as Array<SidebarEntry>).map((sidebarItem) => {
|
||||
if (typeof sidebarItem === 'string') {
|
||||
return {
|
||||
type: 'category',
|
||||
label: sidebarItem.label,
|
||||
items: sidebarItem.ids.map((id: string) => ({
|
||||
type: 'doc',
|
||||
id: `version-${sidebar.version}/${id}`,
|
||||
})),
|
||||
type: 'doc',
|
||||
id: `version-${sidebar.version}/${sidebarItem}`,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
return {
|
||||
type: 'category',
|
||||
label: sidebarItem.label,
|
||||
items: sidebarItem.ids.map((id: string) => ({
|
||||
type: 'doc',
|
||||
id: `version-${sidebar.version}/${id}`,
|
||||
})),
|
||||
};
|
||||
}),
|
||||
}));
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
|
|
|
@ -131,10 +131,8 @@ export default function transformer(file: string): string {
|
|||
type: 'Identifier',
|
||||
},
|
||||
})
|
||||
.filter(function (p) {
|
||||
return p.parentPath.parentPath.name === 'body';
|
||||
})
|
||||
.forEach(function (p) {
|
||||
.filter((p) => p.parentPath.parentPath.name === 'body')
|
||||
.forEach((p) => {
|
||||
const exportDecl = jscodeshift.exportDeclaration(
|
||||
true,
|
||||
jscodeshift.arrowFunctionExpression(
|
||||
|
@ -177,9 +175,7 @@ function getDefaultImportDeclarators(rootAst: Collection) {
|
|||
},
|
||||
},
|
||||
})
|
||||
.filter((variableDeclarator) => {
|
||||
return !!variableDeclarator.value;
|
||||
});
|
||||
.filter((variableDeclarator) => !!variableDeclarator.value);
|
||||
}
|
||||
|
||||
function getNamedImportDeclarators(rootAst: Collection) {
|
||||
|
|
|
@ -220,12 +220,10 @@ describe('collectRedirects', () => {
|
|||
collectRedirects(
|
||||
createTestPluginContext(
|
||||
{
|
||||
createRedirects: (routePath) => {
|
||||
return [
|
||||
`${removeTrailingSlash(routePath)}/some/path/suffix1`,
|
||||
`${removeTrailingSlash(routePath)}/some/other/path/suffix2`,
|
||||
];
|
||||
},
|
||||
createRedirects: (routePath) => [
|
||||
`${removeTrailingSlash(routePath)}/some/path/suffix1`,
|
||||
`${removeTrailingSlash(routePath)}/some/other/path/suffix2`,
|
||||
],
|
||||
},
|
||||
['/', '/testpath', '/otherPath.html'],
|
||||
),
|
||||
|
|
|
@ -28,9 +28,7 @@ describe('normalizePluginOptions', () => {
|
|||
});
|
||||
|
||||
test('should override all default options with valid user options', () => {
|
||||
const createRedirects: CreateRedirectsFnOption = (_routePath: string) => {
|
||||
return [];
|
||||
};
|
||||
const createRedirects: CreateRedirectsFnOption = (_routePath: string) => [];
|
||||
expect(
|
||||
normalizePluginOptions({
|
||||
fromExtensions: ['exe', 'zip'],
|
||||
|
|
|
@ -47,12 +47,10 @@ function applyRedirectsTrailingSlash(
|
|||
redirects: RedirectMetadata[],
|
||||
params: ApplyTrailingSlashParams,
|
||||
) {
|
||||
return redirects.map((redirect) => {
|
||||
return {
|
||||
...redirect,
|
||||
to: applyTrailingSlash(redirect.to, params),
|
||||
};
|
||||
});
|
||||
return redirects.map((redirect) => ({
|
||||
...redirect,
|
||||
to: applyTrailingSlash(redirect.to, params),
|
||||
}));
|
||||
}
|
||||
|
||||
function validateCollectedRedirects(
|
||||
|
@ -181,12 +179,10 @@ function createCreateRedirectsOptionRedirects(
|
|||
const froms: string[] =
|
||||
typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed;
|
||||
|
||||
return froms.map((from) => {
|
||||
return {
|
||||
from,
|
||||
to: path,
|
||||
};
|
||||
});
|
||||
return froms.map((from) => ({
|
||||
from,
|
||||
to: path,
|
||||
}));
|
||||
}
|
||||
|
||||
return paths.flatMap(createPathRedirects);
|
||||
|
|
|
@ -13,9 +13,9 @@ type CreateRedirectPageOptions = {
|
|||
toUrl: string;
|
||||
};
|
||||
|
||||
const getCompiledRedirectPageTemplate = memoize(() => {
|
||||
return eta.compile(redirectPageTemplate.trim());
|
||||
});
|
||||
const getCompiledRedirectPageTemplate = memoize(() =>
|
||||
eta.compile(redirectPageTemplate.trim()),
|
||||
);
|
||||
|
||||
function renderRedirectPageTemplate(data: Record<string, unknown>) {
|
||||
const compiled = getCompiledRedirectPageTemplate();
|
||||
|
|
|
@ -60,9 +60,9 @@ export function toRedirectFilesMetadata(
|
|||
// Perf: avoid rendering the template twice with the exact same "props"
|
||||
// We might create multiple redirect pages for the same destination url
|
||||
// note: the first fn arg is the cache key!
|
||||
const createPageContentMemoized = memoize((toUrl: string) => {
|
||||
return createRedirectPageContent({toUrl});
|
||||
});
|
||||
const createPageContentMemoized = memoize((toUrl: string) =>
|
||||
createRedirectPageContent({toUrl}),
|
||||
);
|
||||
|
||||
const createFileMetadata = (redirect: RedirectMetadata) => {
|
||||
const fileRelativePath = getRedirectFilePath(redirect.from, trailingSlash);
|
||||
|
|
|
@ -39,7 +39,6 @@ const BlogPostFrontMatterAuthorSchema = Joi.object({
|
|||
.rename('image_url', 'imageURL', {alias: true});
|
||||
|
||||
export type BlogPostFrontMatter = {
|
||||
/* eslint-disable camelcase */
|
||||
id?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
|
@ -68,7 +67,6 @@ export type BlogPostFrontMatter = {
|
|||
hide_table_of_contents?: boolean;
|
||||
toc_min_heading_level?: number;
|
||||
toc_max_heading_level?: number;
|
||||
/* eslint-enable camelcase */
|
||||
};
|
||||
|
||||
const FrontMatterAuthorErrorMessage =
|
||||
|
|
|
@ -52,13 +52,11 @@ export function getBlogTags(blogPosts: BlogPost[]): BlogTags {
|
|||
blogPosts,
|
||||
(blogPost) => blogPost.metadata.tags,
|
||||
);
|
||||
return mapValues(groups, (group) => {
|
||||
return {
|
||||
name: group.tag.label,
|
||||
items: group.items.map((item) => item.id),
|
||||
permalink: group.tag.permalink,
|
||||
};
|
||||
});
|
||||
return mapValues(groups, (group) => ({
|
||||
name: group.tag.label,
|
||||
items: group.items.map((item) => item.id),
|
||||
permalink: group.tag.permalink,
|
||||
}));
|
||||
}
|
||||
|
||||
const DATE_FILENAME_REGEX =
|
||||
|
@ -112,9 +110,8 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
|||
};
|
||||
}
|
||||
|
||||
const defaultReadingTime: ReadingTimeFunction = ({content, options}) => {
|
||||
return readingTime(content, options).minutes;
|
||||
};
|
||||
const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
|
||||
readingTime(content, options).minutes;
|
||||
|
||||
async function processBlogSourceFile(
|
||||
blogSourceRelative: string,
|
||||
|
|
|
@ -118,7 +118,7 @@ export async function createBlogFeedFiles({
|
|||
}
|
||||
|
||||
await Promise.all(
|
||||
feedTypes.map(async function (feedType) {
|
||||
feedTypes.map(async (feedType) => {
|
||||
await createBlogFeedFile({
|
||||
feed,
|
||||
feedType,
|
||||
|
|
|
@ -322,9 +322,9 @@ export default function pluginContentBlog(
|
|||
exact: true,
|
||||
modules: {
|
||||
sidebar: aliasedSource(sidebarProp),
|
||||
items: items.map((postID) => {
|
||||
items: items.map((postID) =>
|
||||
// To tell routes.js this is an import and not a nested object to recurse.
|
||||
return {
|
||||
({
|
||||
content: {
|
||||
__import: true,
|
||||
path: blogItemsToMetadata[postID].source,
|
||||
|
@ -332,8 +332,8 @@ export default function pluginContentBlog(
|
|||
truncated: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}),
|
||||
}),
|
||||
),
|
||||
metadata: aliasedSource(pageMetadataPath),
|
||||
},
|
||||
});
|
||||
|
@ -490,14 +490,12 @@ export default function pluginContentBlog(
|
|||
}: {
|
||||
frontMatter: BlogPostFrontMatter;
|
||||
metadata: MetaData;
|
||||
}): Assets => {
|
||||
return {
|
||||
image: frontMatter.image,
|
||||
authorsImageUrls: metadata.authors.map(
|
||||
(author) => author.imageURL,
|
||||
),
|
||||
};
|
||||
},
|
||||
}): Assets => ({
|
||||
image: frontMatter.image,
|
||||
authorsImageUrls: metadata.authors.map(
|
||||
(author) => author.imageURL,
|
||||
),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -600,11 +600,9 @@ describe('versioned site, pluginId=default', () => {
|
|||
test('readVersionsMetadata versioned site with invalid versions.json file', async () => {
|
||||
const {defaultOptions, defaultContext} = await loadSite();
|
||||
|
||||
const mock = jest.spyOn(JSON, 'parse').mockImplementationOnce(() => {
|
||||
return {
|
||||
invalid: 'json',
|
||||
};
|
||||
});
|
||||
const mock = jest.spyOn(JSON, 'parse').mockImplementationOnce(() => ({
|
||||
invalid: 'json',
|
||||
}));
|
||||
|
||||
expect(() => {
|
||||
readVersionsMetadata({
|
||||
|
|
|
@ -31,13 +31,12 @@ export function getActivePlugin(
|
|||
options: GetActivePluginOptions = {},
|
||||
): ActivePlugin | undefined {
|
||||
const activeEntry = Object.entries(allPluginDatas).find(
|
||||
([_id, pluginData]) => {
|
||||
return !!matchPath(pathname, {
|
||||
([_id, pluginData]) =>
|
||||
!!matchPath(pathname, {
|
||||
path: pluginData.path,
|
||||
exact: false,
|
||||
strict: false,
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const activePlugin: ActivePlugin | undefined = activeEntry
|
||||
|
@ -63,9 +62,8 @@ export type ActiveDocContext = {
|
|||
alternateDocVersions: Record<string, Doc>;
|
||||
};
|
||||
|
||||
export const getLatestVersion = (data: GlobalPluginData): Version => {
|
||||
return data.versions.find((version) => version.isLast)!;
|
||||
};
|
||||
export const getLatestVersion = (data: GlobalPluginData): Version =>
|
||||
data.versions.find((version) => version.isLast)!;
|
||||
|
||||
// Note: return undefined on doc-unrelated pages,
|
||||
// because there's no version currently considered as active
|
||||
|
@ -80,13 +78,14 @@ export const getActiveVersion = (
|
|||
...data.versions.filter((version) => version !== lastVersion),
|
||||
lastVersion,
|
||||
];
|
||||
return orderedVersionsMetadata.find((version) => {
|
||||
return !!matchPath(pathname, {
|
||||
path: version.path,
|
||||
exact: false,
|
||||
strict: false,
|
||||
});
|
||||
});
|
||||
return orderedVersionsMetadata.find(
|
||||
(version) =>
|
||||
!!matchPath(pathname, {
|
||||
path: version.path,
|
||||
exact: false,
|
||||
strict: false,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const getActiveDocContext = (
|
||||
|
|
|
@ -70,12 +70,10 @@ declare module '@theme/DocItem' {
|
|||
readonly title: string;
|
||||
readonly image?: string;
|
||||
readonly keywords?: readonly string[];
|
||||
/* eslint-disable camelcase */
|
||||
readonly hide_title?: boolean;
|
||||
readonly hide_table_of_contents?: boolean;
|
||||
readonly toc_min_heading_level?: number;
|
||||
readonly toc_max_heading_level?: number;
|
||||
/* eslint-enable camelcase */
|
||||
};
|
||||
|
||||
export type Metadata = {
|
||||
|
|
|
@ -36,14 +36,16 @@ describe('DefaultSidebarItemsGenerator', () => {
|
|||
function mockCategoryMetadataFiles(
|
||||
categoryMetadataFiles: Record<string, Partial<CategoryMetadataFile>>,
|
||||
) {
|
||||
jest.spyOn(fs, 'pathExists').mockImplementation((metadataFilePath) => {
|
||||
return typeof categoryMetadataFiles[metadataFilePath] !== 'undefined';
|
||||
});
|
||||
jest
|
||||
.spyOn(fs, 'pathExists')
|
||||
.mockImplementation(
|
||||
(metadataFilePath) =>
|
||||
typeof categoryMetadataFiles[metadataFilePath] !== 'undefined',
|
||||
);
|
||||
jest.spyOn(fs, 'readFile').mockImplementation(
|
||||
// @ts-expect-error: annoying TS error due to overrides
|
||||
async (metadataFilePath: string) => {
|
||||
return JSON.stringify(categoryMetadataFiles[metadataFilePath]);
|
||||
},
|
||||
async (metadataFilePath: string) =>
|
||||
JSON.stringify(categoryMetadataFiles[metadataFilePath]),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,9 +21,7 @@ describe('processSidebars', () => {
|
|||
];
|
||||
|
||||
const StaticSidebarItemsGenerator: SidebarItemsGenerator = jest.fn(
|
||||
async () => {
|
||||
return StaticGeneratedSidebarSlice;
|
||||
},
|
||||
async () => StaticGeneratedSidebarSlice,
|
||||
);
|
||||
|
||||
async function testProcessSidebars(unprocessedSidebars: NormalizedSidebars) {
|
||||
|
|
|
@ -73,9 +73,9 @@ export function collectSidebarLinks(sidebar: Sidebar): SidebarItemLink[] {
|
|||
export function collectSidebarsDocIds(
|
||||
sidebars: Sidebars,
|
||||
): Record<string, string[]> {
|
||||
return mapValues(sidebars, (sidebar) => {
|
||||
return collectSidebarDocItems(sidebar).map((docItem) => docItem.id);
|
||||
});
|
||||
return mapValues(sidebars, (sidebar) =>
|
||||
collectSidebarDocItems(sidebar).map((docItem) => docItem.id),
|
||||
);
|
||||
}
|
||||
|
||||
export function createSidebarsUtils(sidebars: Sidebars): {
|
||||
|
|
|
@ -11,11 +11,9 @@ import {mapValues} from 'lodash';
|
|||
|
||||
export function getVersionTags(docs: DocMetadata[]): VersionTags {
|
||||
const groups = groupTaggedItems(docs, (doc) => doc.tags);
|
||||
return mapValues(groups, (group) => {
|
||||
return {
|
||||
name: group.tag.label,
|
||||
docIds: group.items.map((item) => item.id),
|
||||
permalink: group.tag.permalink,
|
||||
};
|
||||
});
|
||||
return mapValues(groups, (group) => ({
|
||||
name: group.tag.label,
|
||||
docIds: group.items.map((item) => item.id),
|
||||
permalink: group.tag.permalink,
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -164,16 +164,16 @@ function translateSidebars(
|
|||
version: LoadedVersion,
|
||||
sidebarsTranslations: TranslationFileContent,
|
||||
): Sidebars {
|
||||
return mapValues(version.sidebars, (sidebar, sidebarName) => {
|
||||
return translateSidebar({
|
||||
return mapValues(version.sidebars, (sidebar, sidebarName) =>
|
||||
translateSidebar({
|
||||
sidebar,
|
||||
sidebarName: getNormalizedSidebarName({
|
||||
sidebarName,
|
||||
versionName: version.versionName,
|
||||
}),
|
||||
sidebarsTranslations,
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function getVersionTranslationFiles(version: LoadedVersion): TranslationFiles {
|
||||
|
|
|
@ -113,7 +113,6 @@ export type LastUpdateData = {
|
|||
|
||||
export type DocFrontMatter = {
|
||||
// Front matter uses snake case
|
||||
/* eslint-disable camelcase */
|
||||
id?: string;
|
||||
title?: string;
|
||||
tags?: FrontMatterTag[];
|
||||
|
@ -133,7 +132,6 @@ export type DocFrontMatter = {
|
|||
toc_max_heading_level?: number;
|
||||
pagination_next?: string | null;
|
||||
pagination_prev?: string | null;
|
||||
/* eslint-enable camelcase */
|
||||
};
|
||||
|
||||
export type DocMetadataBase = LastUpdateData & {
|
||||
|
|
|
@ -18,11 +18,9 @@ declare module '@theme/MDXPage' {
|
|||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly wrapperClassName?: string;
|
||||
/* eslint-disable camelcase */
|
||||
readonly hide_table_of_contents?: string;
|
||||
readonly toc_min_heading_level?: number;
|
||||
readonly toc_max_heading_level?: number;
|
||||
/* eslint-enable camelcase */
|
||||
};
|
||||
readonly metadata: {readonly permalink: string};
|
||||
readonly toc: readonly TOCItem[];
|
||||
|
|
|
@ -11,26 +11,28 @@ import DebugLayout from '@theme/DebugLayout';
|
|||
import DebugJsonView from '@theme/DebugJsonView';
|
||||
import type {Props} from '@theme/DebugContent';
|
||||
|
||||
const PluginInstanceContent = ({
|
||||
function PluginInstanceContent({
|
||||
pluginId,
|
||||
pluginInstanceContent,
|
||||
}: {
|
||||
pluginId: string;
|
||||
pluginInstanceContent: unknown;
|
||||
}) => (
|
||||
<section style={{marginBottom: 30}}>
|
||||
<code>{pluginId}</code>
|
||||
<DebugJsonView src={pluginInstanceContent} collapseDepth={2} />
|
||||
</section>
|
||||
);
|
||||
}) {
|
||||
return (
|
||||
<section style={{marginBottom: 30}}>
|
||||
<code>{pluginId}</code>
|
||||
<DebugJsonView src={pluginInstanceContent} collapseDepth={2} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const PluginContent = ({
|
||||
function PluginContent({
|
||||
pluginName,
|
||||
pluginContent,
|
||||
}: {
|
||||
pluginName: string;
|
||||
pluginContent: Record<string, unknown>;
|
||||
}) => {
|
||||
}) {
|
||||
return (
|
||||
<section style={{marginBottom: 60}}>
|
||||
<h3>{pluginName}</h3>
|
||||
|
@ -40,19 +42,17 @@ const PluginContent = ({
|
|||
.filter(
|
||||
([_pluginId, pluginInstanceContent]) => !!pluginInstanceContent,
|
||||
)
|
||||
.map(([pluginId, pluginInstanceContent]) => {
|
||||
return (
|
||||
<PluginInstanceContent
|
||||
key={pluginId}
|
||||
pluginId={pluginId}
|
||||
pluginInstanceContent={pluginInstanceContent}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
.map(([pluginId, pluginInstanceContent]) => (
|
||||
<PluginInstanceContent
|
||||
key={pluginId}
|
||||
pluginId={pluginId}
|
||||
pluginInstanceContent={pluginInstanceContent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function DebugContent({allContent}: Props): JSX.Element {
|
||||
return (
|
||||
|
@ -66,15 +66,13 @@ function DebugContent({allContent}: Props): JSX.Element {
|
|||
(instanceContent) => !!instanceContent,
|
||||
),
|
||||
)
|
||||
.map(([pluginName, pluginContent]) => {
|
||||
return (
|
||||
<PluginContent
|
||||
key={pluginName}
|
||||
pluginName={pluginName}
|
||||
pluginContent={pluginContent}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
.map(([pluginName, pluginContent]) => (
|
||||
<PluginContent
|
||||
key={pluginName}
|
||||
pluginName={pluginName}
|
||||
pluginContent={pluginContent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</DebugLayout>
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ const RootName = null;
|
|||
|
||||
// Seems ReactJson does not work with SSR
|
||||
// https://github.com/mac-s-g/react-json-view/issues/121
|
||||
const BrowserOnlyReactJson = (props: ReactJsonViewProps) => {
|
||||
function BrowserOnlyReactJson(props: ReactJsonViewProps) {
|
||||
return (
|
||||
<BrowserOnly>
|
||||
{() => {
|
||||
|
@ -25,13 +25,11 @@ const BrowserOnlyReactJson = (props: ReactJsonViewProps) => {
|
|||
}}
|
||||
</BrowserOnly>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function DebugJsonView({src, collapseDepth}: Props): JSX.Element {
|
||||
return (
|
||||
<BrowserOnlyReactJson
|
||||
// Prop type defined by react-json-view
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
src={src as object}
|
||||
style={{
|
||||
marginTop: '10px',
|
||||
|
@ -41,12 +39,12 @@ function DebugJsonView({src, collapseDepth}: Props): JSX.Element {
|
|||
}}
|
||||
name={RootName}
|
||||
theme="paraiso"
|
||||
shouldCollapse={(field) => {
|
||||
shouldCollapse={(field) =>
|
||||
// By default, we collapse the json for performance reasons
|
||||
// See https://github.com/mac-s-g/react-json-view/issues/235
|
||||
// Non-root elements that are larger than 50 fields are collapsed
|
||||
return field.name !== RootName && Object.keys(field.src).length > 50;
|
||||
}}
|
||||
field.name !== RootName && Object.keys(field.src).length > 50
|
||||
}
|
||||
collapsed={collapseDepth}
|
||||
groupArraysAfterLength={5}
|
||||
enableClipboard={false}
|
||||
|
|
|
@ -10,18 +10,20 @@ import Head from '@docusaurus/Head';
|
|||
import Link from '@docusaurus/Link';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const DebugNavLink = ({to, children}: {to: string; children: ReactNode}) => (
|
||||
<Link
|
||||
className={styles.navlink}
|
||||
isNavLink
|
||||
to={to}
|
||||
exact
|
||||
activeStyle={{
|
||||
backgroundColor: '#363739',
|
||||
}}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
function DebugNavLink({to, children}: {to: string; children: ReactNode}) {
|
||||
return (
|
||||
<Link
|
||||
className={styles.navlink}
|
||||
isNavLink
|
||||
to={to}
|
||||
exact
|
||||
activeStyle={{
|
||||
backgroundColor: '#363739',
|
||||
}}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function DebugLayout({children}: {children: ReactNode}): JSX.Element {
|
||||
return (
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
/// <reference types="@docusaurus/module-type-aliases" />
|
||||
|
||||
interface Window {
|
||||
/* eslint-disable camelcase */
|
||||
gtag: (
|
||||
command: string,
|
||||
fields: string,
|
||||
|
@ -18,5 +17,4 @@ interface Window {
|
|||
page_path?: string;
|
||||
},
|
||||
) => void;
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
|
|
|
@ -27,13 +27,12 @@ describe('createSitemap', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('empty site', () => {
|
||||
return expect(async () => {
|
||||
test('empty site', () =>
|
||||
expect(async () => {
|
||||
await createSitemap({} as DocusaurusConfig, [], {});
|
||||
}).rejects.toThrow(
|
||||
'URL in docusaurus.config.js cannot be empty/undefined.',
|
||||
);
|
||||
});
|
||||
));
|
||||
|
||||
test('exclusion of 404 page', async () => {
|
||||
const sitemap = await createSitemap(
|
||||
|
|
|
@ -27,8 +27,7 @@ const ThemeStorageKey = 'theme';
|
|||
const noFlashColorMode = ({
|
||||
defaultMode,
|
||||
respectPrefersColorScheme,
|
||||
}: ThemeConfig['colorMode']) => {
|
||||
return `(function() {
|
||||
}: ThemeConfig['colorMode']) => `(function() {
|
||||
var defaultMode = '${defaultMode}';
|
||||
var respectPrefersColorScheme = ${respectPrefersColorScheme};
|
||||
|
||||
|
@ -63,7 +62,6 @@ const noFlashColorMode = ({
|
|||
}
|
||||
}
|
||||
})();`;
|
||||
};
|
||||
|
||||
// Duplicated constant. Unfortunately we can't import it from theme-common, as we need to support older nodejs versions without ESM support
|
||||
// TODO: import from theme-common once we only support Node.js with ESM support
|
||||
|
@ -136,12 +134,11 @@ export default function docusaurusThemeClassic(
|
|||
getTranslationFiles: async () => getTranslationFiles({themeConfig}),
|
||||
translateThemeConfig,
|
||||
|
||||
getDefaultCodeTranslationMessages: () => {
|
||||
return readDefaultCodeTranslationMessages({
|
||||
getDefaultCodeTranslationMessages: () =>
|
||||
readDefaultCodeTranslationMessages({
|
||||
dirPath: path.resolve(__dirname, '..', 'codeTranslations'),
|
||||
locale: currentLocale,
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
getClientModules() {
|
||||
const modules = [
|
||||
|
|
|
@ -13,10 +13,13 @@ import BlogPostAuthor from '@theme/BlogPostAuthor';
|
|||
import styles from './styles.module.css';
|
||||
|
||||
// Component responsible for the authors layout
|
||||
export default function BlogPostAuthors({authors, assets}: Props): JSX.Element {
|
||||
export default function BlogPostAuthors({
|
||||
authors,
|
||||
assets,
|
||||
}: Props): JSX.Element | null {
|
||||
const authorsCount = authors.length;
|
||||
if (authorsCount === 0) {
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="row margin-top--md margin-bottom--sm">
|
||||
|
|
|
@ -28,19 +28,17 @@ export default function BlogSidebar({sidebar}: Props): JSX.Element | null {
|
|||
{sidebar.title}
|
||||
</div>
|
||||
<ul className={styles.sidebarItemList}>
|
||||
{sidebar.items.map((item) => {
|
||||
return (
|
||||
<li key={item.permalink} className={styles.sidebarItem}>
|
||||
<Link
|
||||
isNavLink
|
||||
to={item.permalink}
|
||||
className={styles.sidebarItemLink}
|
||||
activeClassName={styles.sidebarItemLinkActive}>
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{sidebar.items.map((item) => (
|
||||
<li key={item.permalink} className={styles.sidebarItem}>
|
||||
<Link
|
||||
isNavLink
|
||||
to={item.permalink}
|
||||
className={styles.sidebarItemLink}
|
||||
activeClassName={styles.sidebarItemLinkActive}>
|
||||
{item.title}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
|
|
|
@ -59,7 +59,7 @@ function EditMetaRow({
|
|||
);
|
||||
}
|
||||
|
||||
export default function DocItemFooter(props: Props): JSX.Element {
|
||||
export default function DocItemFooter(props: Props): JSX.Element | null {
|
||||
const {content: DocContent} = props;
|
||||
const {metadata} = DocContent;
|
||||
const {editUrl, lastUpdatedAt, formattedLastUpdatedAt, lastUpdatedBy, tags} =
|
||||
|
@ -71,7 +71,7 @@ export default function DocItemFooter(props: Props): JSX.Element {
|
|||
const canDisplayFooter = canDisplayTagsRow || canDisplayEditMetaRow;
|
||||
|
||||
if (!canDisplayFooter) {
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -90,22 +90,21 @@ function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
const DocSidebarMobileSecondaryMenu: MobileSecondaryMenuComponent<Props> = ({
|
||||
toggleSidebar,
|
||||
sidebar,
|
||||
path,
|
||||
}) => {
|
||||
return (
|
||||
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
||||
<DocSidebarItems
|
||||
items={sidebar}
|
||||
activePath={path}
|
||||
onItemClick={() => toggleSidebar()}
|
||||
level={1}
|
||||
/>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
||||
<DocSidebarItems
|
||||
items={sidebar}
|
||||
activePath={path}
|
||||
onItemClick={() => toggleSidebar()}
|
||||
level={1}
|
||||
/>
|
||||
</ul>
|
||||
);
|
||||
|
||||
function DocSidebarMobile(props: Props) {
|
||||
return (
|
||||
|
|
|
@ -44,11 +44,8 @@ const isActiveSidebarItem = (
|
|||
// Optimize sidebar at each "level"
|
||||
// TODO this item should probably not receive the "activePath" props
|
||||
// TODO this triggers whole sidebar re-renders on navigation
|
||||
export const DocSidebarItems = memo(function DocSidebarItems({
|
||||
items,
|
||||
...props
|
||||
}: DocSidebarItemsProps): JSX.Element {
|
||||
return (
|
||||
export const DocSidebarItems = memo(
|
||||
({items, ...props}: DocSidebarItemsProps): JSX.Element => (
|
||||
<>
|
||||
{items.map((item, index) => (
|
||||
<DocSidebarItem
|
||||
|
@ -58,8 +55,8 @@ export const DocSidebarItems = memo(function DocSidebarItems({
|
|||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
|
||||
export default function DocSidebarItem({
|
||||
item,
|
||||
|
|
|
@ -154,11 +154,11 @@ function DocVersionBannerEnabled({versionMetadata}: Props): JSX.Element {
|
|||
);
|
||||
}
|
||||
|
||||
function DocVersionBanner({versionMetadata}: Props): JSX.Element {
|
||||
function DocVersionBanner({versionMetadata}: Props): JSX.Element | null {
|
||||
if (versionMetadata.banner) {
|
||||
return <DocVersionBannerEnabled versionMetadata={versionMetadata} />;
|
||||
}
|
||||
return <></>;
|
||||
return null;
|
||||
}
|
||||
|
||||
export default DocVersionBanner;
|
||||
|
|
|
@ -49,20 +49,22 @@ function FooterLink({
|
|||
);
|
||||
}
|
||||
|
||||
const FooterLogo = ({
|
||||
function FooterLogo({
|
||||
sources,
|
||||
alt,
|
||||
width,
|
||||
height,
|
||||
}: Pick<ThemedImageProps, 'sources' | 'alt' | 'width' | 'height'>) => (
|
||||
<ThemedImage
|
||||
className="footer__logo"
|
||||
alt={alt}
|
||||
sources={sources}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
);
|
||||
}: Pick<ThemedImageProps, 'sources' | 'alt' | 'width' | 'height'>) {
|
||||
return (
|
||||
<ThemedImage
|
||||
className="footer__logo"
|
||||
alt={alt}
|
||||
sources={sources}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer(): JSX.Element | null {
|
||||
const {footer} = useThemeConfig();
|
||||
|
|
|
@ -16,23 +16,21 @@ import styles from './styles.module.css';
|
|||
|
||||
type HeadingComponent = (props: Props) => JSX.Element;
|
||||
|
||||
export const MainHeading: HeadingComponent = function MainHeading({...props}) {
|
||||
return (
|
||||
<header>
|
||||
<h1
|
||||
{...props}
|
||||
id={undefined} // h1 headings do not need an id because they don't appear in the TOC
|
||||
>
|
||||
{props.children}
|
||||
</h1>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
// eslint-disable-next-line react/function-component-definition
|
||||
export const MainHeading: HeadingComponent = ({...props}) => (
|
||||
<header>
|
||||
<h1
|
||||
{...props}
|
||||
id={undefined} // h1 headings do not need an id because they don't appear in the TOC
|
||||
>
|
||||
{props.children}
|
||||
</h1>
|
||||
</header>
|
||||
);
|
||||
|
||||
const createAnchorHeading = (
|
||||
Tag: HeadingType,
|
||||
): ((props: Props) => JSX.Element) =>
|
||||
function TargetComponent({id, ...props}) {
|
||||
const createAnchorHeading =
|
||||
(Tag: HeadingType) =>
|
||||
({id, ...props}: Props) => {
|
||||
const {
|
||||
navbar: {hideOnScroll},
|
||||
} = useThemeConfig();
|
||||
|
@ -65,8 +63,7 @@ const createAnchorHeading = (
|
|||
);
|
||||
};
|
||||
|
||||
const Heading = (headingType: HeadingType): ((props: Props) => JSX.Element) => {
|
||||
return headingType === 'h1' ? MainHeading : createAnchorHeading(headingType);
|
||||
};
|
||||
const Heading = (headingType: HeadingType): ((props: Props) => JSX.Element) =>
|
||||
headingType === 'h1' ? MainHeading : createAnchorHeading(headingType);
|
||||
|
||||
export default Heading;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React from 'react';
|
||||
import type {Props} from '@theme/IconArrow';
|
||||
|
||||
const IconArrow = (props: Props): JSX.Element => {
|
||||
function IconArrow(props: Props): JSX.Element {
|
||||
return (
|
||||
<svg width="20" height="20" aria-hidden="true" {...props}>
|
||||
<g fill="#7a7a7a">
|
||||
|
@ -17,6 +17,6 @@ const IconArrow = (props: Props): JSX.Element => {
|
|||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default IconArrow;
|
||||
|
|
|
@ -12,7 +12,7 @@ import type {Props} from '@theme/IconEdit';
|
|||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const IconEdit = ({className, ...restProps}: Props): JSX.Element => {
|
||||
function IconEdit({className, ...restProps}: Props): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
|
@ -27,6 +27,6 @@ const IconEdit = ({className, ...restProps}: Props): JSX.Element => {
|
|||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default IconEdit;
|
||||
|
|
|
@ -10,10 +10,7 @@ import type {Props} from '@theme/IconExternalLink';
|
|||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const IconExternalLink = ({
|
||||
width = 13.5,
|
||||
height = 13.5,
|
||||
}: Props): JSX.Element => {
|
||||
function IconExternalLink({width = 13.5, height = 13.5}: Props): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
width={width}
|
||||
|
@ -27,6 +24,6 @@ const IconExternalLink = ({
|
|||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default IconExternalLink;
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
import React from 'react';
|
||||
import type {Props} from '@theme/IconLanguage';
|
||||
|
||||
const IconLanguage = ({
|
||||
width = 20,
|
||||
height = 20,
|
||||
...props
|
||||
}: Props): JSX.Element => {
|
||||
function IconLanguage({width = 20, height = 20, ...props}: Props): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
|
@ -26,6 +22,6 @@ const IconLanguage = ({
|
|||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default IconLanguage;
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
import React from 'react';
|
||||
import type {Props} from '@theme/IconMenu';
|
||||
|
||||
const IconMenu = ({
|
||||
function IconMenu({
|
||||
width = 30,
|
||||
height = 30,
|
||||
className,
|
||||
...restProps
|
||||
}: Props): JSX.Element => {
|
||||
}: Props): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
|
@ -31,6 +31,6 @@ const IconMenu = ({
|
|||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default IconMenu;
|
||||
|
|
|
@ -101,7 +101,7 @@ export default function LayoutHead(props: Props): JSX.Element {
|
|||
<>
|
||||
<Head>
|
||||
<html lang={htmlLang} dir={htmlDir} />
|
||||
{favicon && <link rel="shortcut icon" href={faviconUrl} />}
|
||||
{favicon && <link rel="icon" href={faviconUrl} />}
|
||||
<title>{pageTitle}</title>
|
||||
<meta property="og:title" content={pageTitle} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
|
|
|
@ -14,7 +14,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
|
|||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import {useThemeConfig} from '@docusaurus/theme-common';
|
||||
|
||||
const Logo = (props: Props): JSX.Element => {
|
||||
function Logo(props: Props): JSX.Element {
|
||||
const {
|
||||
siteConfig: {title},
|
||||
} = useDocusaurusContext();
|
||||
|
@ -46,11 +46,11 @@ const Logo = (props: Props): JSX.Element => {
|
|||
(imageClassName ? (
|
||||
<div className={imageClassName}>{themedImage}</div>
|
||||
) : (
|
||||
<>{themedImage}</>
|
||||
themedImage
|
||||
))}
|
||||
{navbarTitle != null && <b className={titleClassName}>{navbarTitle}</b>}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Logo;
|
||||
|
|
|
@ -105,12 +105,13 @@ function useSecondaryMenu({
|
|||
});
|
||||
const previousContent = usePrevious(content);
|
||||
|
||||
const [shown, setShown] = useState<boolean>(() => {
|
||||
// /!\ content is set with useEffect,
|
||||
// so it's not available on mount anyway
|
||||
// "return !!content" => always returns false
|
||||
return false;
|
||||
});
|
||||
const [shown, setShown] = useState<boolean>(
|
||||
() =>
|
||||
// /!\ content is set with useEffect,
|
||||
// so it's not available on mount anyway
|
||||
// "return !!content" => always returns false
|
||||
false,
|
||||
);
|
||||
|
||||
// When content is become available for the first time (set in useEffect)
|
||||
// we set this content to be shown!
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {useMemo} from 'react';
|
||||
|
||||
import useTheme from '@theme/hooks/useTheme';
|
||||
import ThemeContext from '@theme/ThemeContext';
|
||||
|
@ -13,9 +13,13 @@ import type {Props} from '@theme/ThemeProvider';
|
|||
|
||||
function ThemeProvider(props: Props): JSX.Element {
|
||||
const {isDarkTheme, setLightTheme, setDarkTheme} = useTheme();
|
||||
const contextValue = useMemo(
|
||||
() => ({isDarkTheme, setLightTheme, setDarkTheme}),
|
||||
[isDarkTheme, setLightTheme, setDarkTheme],
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{isDarkTheme, setLightTheme, setDarkTheme}}>
|
||||
<ThemeContext.Provider value={contextValue}>
|
||||
{props.children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ import type {Props} from '@theme/ThemedImage';
|
|||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const ThemedImage = (props: Props): JSX.Element => {
|
||||
function ThemedImage(props: Props): JSX.Element {
|
||||
const isBrowser = useIsBrowser();
|
||||
const {isDarkTheme} = useThemeContext();
|
||||
const {sources, className, alt = '', ...propsRest} = props;
|
||||
|
@ -46,6 +46,6 @@ const ThemedImage = (props: Props): JSX.Element => {
|
|||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default ThemedImage;
|
||||
|
|
|
@ -18,16 +18,20 @@ interface IconProps {
|
|||
style: CSSProperties;
|
||||
}
|
||||
|
||||
const Dark = ({icon, style}: IconProps): JSX.Element => (
|
||||
<span className={clsx(styles.toggleIcon, styles.dark)} style={style}>
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
const Light = ({icon, style}: IconProps): JSX.Element => (
|
||||
<span className={clsx(styles.toggleIcon, styles.light)} style={style}>
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
function Dark({icon, style}: IconProps): JSX.Element {
|
||||
return (
|
||||
<span className={clsx(styles.toggleIcon, styles.dark)} style={style}>
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
function Light({icon, style}: IconProps): JSX.Element {
|
||||
return (
|
||||
<span className={clsx(styles.toggleIcon, styles.light)} style={style}>
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Based on react-toggle (https://github.com/aaronshaf/react-toggle/).
|
||||
const Toggle = memo(
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {useMemo} from 'react';
|
||||
|
||||
import useTabGroupChoice from '@theme/hooks/useTabGroupChoice';
|
||||
import UserPreferencesContext from '@theme/UserPreferencesContext';
|
||||
|
@ -13,12 +13,15 @@ import type {Props} from '@theme/UserPreferencesProvider';
|
|||
|
||||
function UserPreferencesProvider(props: Props): JSX.Element {
|
||||
const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoice();
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
tabGroupChoices,
|
||||
setTabGroupChoices,
|
||||
}),
|
||||
[tabGroupChoices, setTabGroupChoices],
|
||||
);
|
||||
return (
|
||||
<UserPreferencesContext.Provider
|
||||
value={{
|
||||
tabGroupChoices,
|
||||
setTabGroupChoices,
|
||||
}}>
|
||||
<UserPreferencesContext.Provider value={contextValue}>
|
||||
{props.children}
|
||||
</UserPreferencesContext.Provider>
|
||||
);
|
||||
|
|
|
@ -21,9 +21,8 @@ const themes = {
|
|||
type Themes = typeof themes[keyof typeof themes];
|
||||
|
||||
// Ensure to always return a valid theme even if input is invalid
|
||||
const coerceToTheme = (theme?: string | null): Themes => {
|
||||
return theme === themes.dark ? themes.dark : themes.light;
|
||||
};
|
||||
const coerceToTheme = (theme?: string | null): Themes =>
|
||||
theme === themes.dark ? themes.dark : themes.light;
|
||||
|
||||
const getInitialTheme = (defaultMode: Themes | undefined): Themes => {
|
||||
if (!ExecutionEnvironment.canUseDOM) {
|
||||
|
|
|
@ -80,9 +80,10 @@ ${warning}
|
|||
});
|
||||
|
||||
const translations = filesExtractedTranslations.reduce(
|
||||
(acc, extractedTranslations) => {
|
||||
return {...acc, ...extractedTranslations.translations};
|
||||
},
|
||||
(acc, extractedTranslations) => ({
|
||||
...acc,
|
||||
...extractedTranslations.translations,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
|
@ -193,9 +194,7 @@ ${logKeys(unknownMessages)}`),
|
|||
};
|
||||
|
||||
const untranslatedKeys = Object.entries(newLocaleFileMessages)
|
||||
.filter(([key, value]) => {
|
||||
return value === baseFileMessages[key];
|
||||
})
|
||||
.filter(([key, value]) => value === baseFileMessages[key])
|
||||
.map(([key]) => key);
|
||||
|
||||
if (untranslatedKeys.length) {
|
||||
|
|
|
@ -29,7 +29,7 @@ export type DetailsProps = {
|
|||
summary?: ReactElement;
|
||||
} & ComponentProps<'details'>;
|
||||
|
||||
const Details = ({summary, children, ...props}: DetailsProps): JSX.Element => {
|
||||
function Details({summary, children, ...props}: DetailsProps): JSX.Element {
|
||||
const isBrowser = useIsBrowser();
|
||||
const detailsRef = useRef<HTMLDetailsElement>(null);
|
||||
|
||||
|
@ -89,6 +89,6 @@ const Details = ({summary, children, ...props}: DetailsProps): JSX.Element => {
|
|||
</Collapsible>
|
||||
</details>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Details;
|
||||
|
|
|
@ -41,13 +41,13 @@ const useAnnouncementBarContextValue = (): AnnouncementBarAPI => {
|
|||
const {announcementBar} = useThemeConfig();
|
||||
const isBrowser = useIsBrowser();
|
||||
|
||||
const [isClosed, setClosed] = useState(() => {
|
||||
return isBrowser
|
||||
const [isClosed, setClosed] = useState(() =>
|
||||
isBrowser
|
||||
? // On client navigation: init with localstorage value
|
||||
isDismissedInStorage()
|
||||
: // On server/hydration: always visible to prevent layout shifts (will be hidden with css if needed)
|
||||
false;
|
||||
});
|
||||
false,
|
||||
);
|
||||
// Update state after hydration
|
||||
useEffect(() => {
|
||||
setClosed(isDismissedInStorage());
|
||||
|
@ -85,28 +85,29 @@ const useAnnouncementBarContextValue = (): AnnouncementBarAPI => {
|
|||
}
|
||||
}, [announcementBar]);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
return useMemo(
|
||||
() => ({
|
||||
isActive: !!announcementBar && !isClosed,
|
||||
close: handleClose,
|
||||
};
|
||||
}, [announcementBar, isClosed, handleClose]);
|
||||
}),
|
||||
[announcementBar, isClosed, handleClose],
|
||||
);
|
||||
};
|
||||
|
||||
const AnnouncementBarContext = createContext<AnnouncementBarAPI | null>(null);
|
||||
|
||||
export const AnnouncementBarProvider = ({
|
||||
export function AnnouncementBarProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element => {
|
||||
}): JSX.Element {
|
||||
const value = useAnnouncementBarContextValue();
|
||||
return (
|
||||
<AnnouncementBarContext.Provider value={value}>
|
||||
{children}
|
||||
</AnnouncementBarContext.Provider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export const useAnnouncementBar = (): AnnouncementBarAPI => {
|
||||
const api = useContext(AnnouncementBarContext);
|
||||
|
|
|
@ -132,7 +132,7 @@ const Context = createContext<DocsPreferredVersionContextValue | null>(null);
|
|||
export function DocsPreferredVersionContextProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
children: JSX.Element;
|
||||
}): JSX.Element {
|
||||
if (isDocsPluginEnabled) {
|
||||
return (
|
||||
|
@ -141,7 +141,7 @@ export function DocsPreferredVersionContextProvider({
|
|||
</DocsPreferredVersionContextProviderUnsafe>
|
||||
);
|
||||
} else {
|
||||
return <>{children}</>;
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,8 @@ const DocsPreferredVersionStorage = {
|
|||
read: (
|
||||
pluginId: string,
|
||||
persistence: DocsVersionPersistence,
|
||||
): string | null => {
|
||||
return createStorageSlot(storageKey(pluginId), {persistence}).get();
|
||||
},
|
||||
): string | null =>
|
||||
createStorageSlot(storageKey(pluginId), {persistence}).get(),
|
||||
|
||||
clear: (pluginId: string, persistence: DocsVersionPersistence): void => {
|
||||
createStorageSlot(storageKey(pluginId), {persistence}).del();
|
||||
|
|
|
@ -24,12 +24,12 @@ export function useHistoryActionHandler(handler: HistoryBlockHandler): void {
|
|||
lastHandlerRef.current = handler;
|
||||
}, [handler]);
|
||||
|
||||
useEffect(() => {
|
||||
// See https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md
|
||||
return block((location, action) => {
|
||||
return lastHandlerRef.current(location, action);
|
||||
});
|
||||
}, [block, lastHandlerRef]);
|
||||
useEffect(
|
||||
() =>
|
||||
// See https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md
|
||||
block((location, action) => lastHandlerRef.current(location, action)),
|
||||
[block, lastHandlerRef],
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -29,7 +29,7 @@ type ExtraProps = {
|
|||
toggleSidebar: () => void;
|
||||
};
|
||||
|
||||
export type MobileSecondaryMenuComponent<Props extends unknown> = ComponentType<
|
||||
export type MobileSecondaryMenuComponent<Props> = ComponentType<
|
||||
Props & ExtraProps
|
||||
>;
|
||||
|
||||
|
@ -108,9 +108,7 @@ export function MobileSecondaryMenuFiller<
|
|||
setState({component, props: memoizedProps});
|
||||
}, [setState, component, memoizedProps]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => setState(null);
|
||||
}, [setState]);
|
||||
useEffect(() => () => setState(null), [setState]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -10,8 +10,7 @@ export const isSamePath = (
|
|||
path1: string | undefined,
|
||||
path2: string | undefined,
|
||||
): boolean => {
|
||||
const normalize = (pathname: string | undefined) => {
|
||||
return !pathname || pathname?.endsWith('/') ? pathname : `${pathname}/`;
|
||||
};
|
||||
const normalize = (pathname: string | undefined) =>
|
||||
!pathname || pathname?.endsWith('/') ? pathname : `${pathname}/`;
|
||||
return normalize(path1) === normalize(path2);
|
||||
};
|
||||
|
|
|
@ -83,14 +83,13 @@ export function useScrollController(): ScrollController {
|
|||
return context;
|
||||
}
|
||||
|
||||
const getScrollPosition = (): ScrollPosition | null => {
|
||||
return ExecutionEnvironment.canUseDOM
|
||||
const getScrollPosition = (): ScrollPosition | null =>
|
||||
ExecutionEnvironment.canUseDOM
|
||||
? {
|
||||
scrollX: window.pageXOffset,
|
||||
scrollY: window.pageYOffset,
|
||||
}
|
||||
: null;
|
||||
};
|
||||
|
||||
type ScrollPosition = {scrollX: number; scrollY: number};
|
||||
|
||||
|
|
|
@ -48,7 +48,8 @@ export function useTOCFilter({
|
|||
minHeadingLevel,
|
||||
maxHeadingLevel,
|
||||
}: FilterTOCParam): readonly TOCItem[] {
|
||||
return useMemo(() => {
|
||||
return filterTOC({toc, minHeadingLevel, maxHeadingLevel});
|
||||
}, [toc, minHeadingLevel, maxHeadingLevel]);
|
||||
return useMemo(
|
||||
() => filterTOC({toc, minHeadingLevel, maxHeadingLevel}),
|
||||
[toc, minHeadingLevel, maxHeadingLevel],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -112,8 +112,7 @@ export function usePluralForm(): {
|
|||
} {
|
||||
const localePluralForm = useLocalePluralForms();
|
||||
return {
|
||||
selectMessage: (count: number, pluralMessages: string): string => {
|
||||
return selectPluralMessage(pluralMessages, count, localePluralForm);
|
||||
},
|
||||
selectMessage: (count: number, pluralMessages: string): string =>
|
||||
selectPluralMessage(pluralMessages, count, localePluralForm),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ import ReactLiveScope from '@theme/ReactLiveScope';
|
|||
import CodeBlock from '@theme-init/CodeBlock';
|
||||
|
||||
const withLiveEditor = (Component) => {
|
||||
const WrappedComponent = (props) => {
|
||||
function WrappedComponent(props) {
|
||||
if (props.live) {
|
||||
return <Playground scope={ReactLiveScope} {...props} />;
|
||||
}
|
||||
|
||||
return <Component {...props} />;
|
||||
};
|
||||
}
|
||||
|
||||
return WrappedComponent;
|
||||
};
|
||||
|
|
|
@ -14,9 +14,9 @@ import {memoize} from 'lodash';
|
|||
|
||||
import type {DocusaurusContext, Plugin} from '@docusaurus/types';
|
||||
|
||||
const getCompiledOpenSearchTemplate = memoize(() => {
|
||||
return compile(openSearchTemplate.trim());
|
||||
});
|
||||
const getCompiledOpenSearchTemplate = memoize(() =>
|
||||
compile(openSearchTemplate.trim()),
|
||||
);
|
||||
|
||||
function renderOpenSearchTemplate(data: {
|
||||
title: string;
|
||||
|
|
|
@ -152,8 +152,8 @@ function DocSearch({
|
|||
}).current;
|
||||
|
||||
const transformItems = useRef<DocSearchModalProps['transformItems']>(
|
||||
(items) => {
|
||||
return items.map((item) => {
|
||||
(items) =>
|
||||
items.map((item) => {
|
||||
// If Algolia contains a external domain, we should navigate without relative URL
|
||||
if (isRegexpStringMatch(externalUrlRegex, item.url)) {
|
||||
return item;
|
||||
|
@ -165,11 +165,11 @@ function DocSearch({
|
|||
...item,
|
||||
url: withBaseUrl(`${url.pathname}${url.hash}`),
|
||||
};
|
||||
});
|
||||
},
|
||||
}),
|
||||
).current;
|
||||
|
||||
const resultsFooterComponent = useMemo(
|
||||
// eslint-disable-next-line react/no-unstable-nested-components
|
||||
() => (footerProps: ResultsFooterProps) =>
|
||||
<ResultsFooter {...footerProps} onClose={onClose} />,
|
||||
[onClose],
|
||||
|
@ -250,7 +250,7 @@ function DocSearch({
|
|||
);
|
||||
}
|
||||
|
||||
function SearchBar() {
|
||||
function SearchBar(): JSX.Element {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
// @ts-ignore
|
||||
return <DocSearch {...siteConfig.themeConfig.algolia} />;
|
||||
|
|
|
@ -54,14 +54,14 @@ function useDocsSearchVersionsHelpers() {
|
|||
// State of the version select menus / algolia facet filters
|
||||
// docsPluginId -> versionName map
|
||||
const [searchVersions, setSearchVersions] = useState<Record<string, string>>(
|
||||
() => {
|
||||
return Object.entries(allDocsData).reduce(
|
||||
(acc, [pluginId, pluginData]) => {
|
||||
return {...acc, [pluginId]: pluginData.versions[0].name};
|
||||
},
|
||||
() =>
|
||||
Object.entries(allDocsData).reduce(
|
||||
(acc, [pluginId, pluginData]) => ({
|
||||
...acc,
|
||||
[pluginId]: pluginData.versions[0].name,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Set the value of a single select menu
|
||||
|
@ -81,11 +81,11 @@ function useDocsSearchVersionsHelpers() {
|
|||
}
|
||||
|
||||
// We want to display one select per versioned docs plugin instance
|
||||
const SearchVersionSelectList = ({
|
||||
function SearchVersionSelectList({
|
||||
docsSearchVersionsHelpers,
|
||||
}: {
|
||||
docsSearchVersionsHelpers: ReturnType<typeof useDocsSearchVersionsHelpers>;
|
||||
}) => {
|
||||
}) {
|
||||
const versionedPluginEntries = Object.entries(
|
||||
docsSearchVersionsHelpers.allDocsData,
|
||||
)
|
||||
|
@ -126,7 +126,7 @@ const SearchVersionSelectList = ({
|
|||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
type ResultDispatcherState = {
|
||||
items: {
|
||||
|
@ -225,12 +225,11 @@ function SearchPage(): JSX.Element {
|
|||
return;
|
||||
}
|
||||
|
||||
const sanitizeValue = (value: string) => {
|
||||
return value.replace(
|
||||
const sanitizeValue = (value: string) =>
|
||||
value.replace(
|
||||
/algolia-docsearch-suggestion--highlight/g,
|
||||
'search-result-match',
|
||||
);
|
||||
};
|
||||
|
||||
const items = hits.map(
|
||||
({
|
||||
|
@ -239,9 +238,9 @@ function SearchPage(): JSX.Element {
|
|||
_snippetResult: snippet = {},
|
||||
}) => {
|
||||
const parsedURL = new URL(url);
|
||||
const titles = Object.keys(hierarchy).map((key) => {
|
||||
return sanitizeValue(hierarchy[key].value);
|
||||
});
|
||||
const titles = Object.keys(hierarchy).map((key) =>
|
||||
sanitizeValue(hierarchy[key].value),
|
||||
);
|
||||
|
||||
return {
|
||||
title: titles.pop()!,
|
||||
|
|
|
@ -47,10 +47,9 @@ function useSearchQuery(): SearchQuery {
|
|||
);
|
||||
|
||||
const generateSearchPageLink = useCallback(
|
||||
(targetSearchQuery: string) => {
|
||||
(targetSearchQuery: string) =>
|
||||
// Refer to https://github.com/facebook/docusaurus/pull/2838
|
||||
return `${baseUrl}search?q=${encodeURIComponent(targetSearchQuery)}`;
|
||||
},
|
||||
`${baseUrl}search?q=${encodeURIComponent(targetSearchQuery)}`,
|
||||
[baseUrl],
|
||||
);
|
||||
|
||||
|
|
|
@ -351,7 +351,9 @@ describe('mergeTranslations', () => {
|
|||
|
||||
describe('mapAsyncSequencial', () => {
|
||||
function sleep(timeout: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, timeout));
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
test('map sequentially', async () => {
|
||||
|
@ -390,7 +392,9 @@ describe('mapAsyncSequencial', () => {
|
|||
|
||||
describe('findAsyncSequencial', () => {
|
||||
function sleep(timeout: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, timeout));
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
test('find sequentially', async () => {
|
||||
|
|
|
@ -10,7 +10,6 @@ import fs from 'fs-extra';
|
|||
|
||||
// Return an ordered list of locales we should try
|
||||
export function codeTranslationLocalesToTry(locale: string): string[] {
|
||||
// @ts-expect-error: TODO until available in TS, see https://github.com/microsoft/TypeScript/issues/37326
|
||||
const intlLocale = Intl.Locale ? new Intl.Locale(locale) : undefined;
|
||||
if (!intlLocale) {
|
||||
return [locale];
|
||||
|
@ -24,7 +23,7 @@ export function codeTranslationLocalesToTry(locale: string): string[] {
|
|||
}
|
||||
// if locale is like "pt-BR", we want to fallback to "pt"
|
||||
else {
|
||||
return [locale, intlLocale.language];
|
||||
return [locale, intlLocale.language!];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ export function removePrefix(str: string, prefix: string): string {
|
|||
return str.startsWith(prefix) ? str.slice(prefix.length) : str;
|
||||
}
|
||||
|
||||
export function getElementsAround<T extends unknown>(
|
||||
export function getElementsAround<T>(
|
||||
array: T[],
|
||||
aroundIndex: number,
|
||||
): {
|
||||
|
@ -306,7 +306,7 @@ export function getPluginI18nPath({
|
|||
);
|
||||
}
|
||||
|
||||
export async function mapAsyncSequencial<T extends unknown, R extends unknown>(
|
||||
export async function mapAsyncSequencial<T, R>(
|
||||
array: T[],
|
||||
action: (t: T) => Promise<R>,
|
||||
): Promise<R[]> {
|
||||
|
@ -389,9 +389,7 @@ export function reportMessage(
|
|||
export function mergeTranslations(
|
||||
contents: TranslationFileContent[],
|
||||
): TranslationFileContent {
|
||||
return contents.reduce((acc, content) => {
|
||||
return {...acc, ...content};
|
||||
}, {});
|
||||
return contents.reduce((acc, content) => ({...acc, ...content}), {});
|
||||
}
|
||||
|
||||
export function getSwizzledComponent(
|
||||
|
|
|
@ -16,11 +16,10 @@ const SPACE_FOR_APPENDING = 10;
|
|||
const isMacOs = process.platform === `darwin`;
|
||||
const isWindows = process.platform === `win32`;
|
||||
|
||||
export const isNameTooLong = (str: string): boolean => {
|
||||
return isMacOs || isWindows
|
||||
export const isNameTooLong = (str: string): boolean =>
|
||||
isMacOs || isWindows
|
||||
? str.length + SPACE_FOR_APPENDING > MAX_PATH_SEGMENT_CHARS // MacOS (APFS) and Windows (NTFS) filename length limit (255 chars)
|
||||
: Buffer.from(str).length + SPACE_FOR_APPENDING > MAX_PATH_SEGMENT_BYTES; // Other (255 bytes)
|
||||
};
|
||||
|
||||
export const shortName = (str: string): string => {
|
||||
if (isMacOs || isWindows) {
|
||||
|
|
|
@ -91,7 +91,7 @@ export default async function choosePort(
|
|||
(port) =>
|
||||
new Promise((resolve) => {
|
||||
if (port === defaultPort) {
|
||||
return resolve(port);
|
||||
resolve(port);
|
||||
}
|
||||
const message =
|
||||
process.platform !== 'win32' && defaultPort < 1024 && !isRoot()
|
||||
|
@ -121,7 +121,6 @@ export default async function choosePort(
|
|||
console.log(chalk.red(message));
|
||||
resolve(null);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
(err) => {
|
||||
throw new Error(
|
||||
|
|
|
@ -22,9 +22,7 @@ export const createStatefulLinksCollector = (): StatefulLinksCollector => {
|
|||
collectLink: (link: string): void => {
|
||||
allLinks.add(link);
|
||||
},
|
||||
getCollectedLinks: (): string[] => {
|
||||
return [...allLinks];
|
||||
},
|
||||
getCollectedLinks: (): string[] => [...allLinks],
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -35,16 +33,14 @@ const Context = createContext<LinksCollector>({
|
|||
},
|
||||
});
|
||||
|
||||
export const useLinksCollector = (): LinksCollector => {
|
||||
return useContext(Context);
|
||||
};
|
||||
export const useLinksCollector = (): LinksCollector => useContext(Context);
|
||||
|
||||
export const ProvideLinksCollector = ({
|
||||
export function ProvideLinksCollector({
|
||||
children,
|
||||
linksCollector,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
linksCollector: LinksCollector;
|
||||
}): JSX.Element => {
|
||||
}): JSX.Element {
|
||||
return <Context.Provider value={linksCollector}>{children}</Context.Provider>;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -49,19 +49,18 @@ const flatten = <T>(arrays: T[][]): T[] =>
|
|||
// output: /blog/2018/12/14/Happy-First-Birthday-Slash
|
||||
const removeRouteNameHash = (str: string) => str.replace(/(-[^-]+)$/, '');
|
||||
|
||||
const getChunkNamesToLoad = (path: string): string[] => {
|
||||
return flatten(
|
||||
const getChunkNamesToLoad = (path: string): string[] =>
|
||||
flatten(
|
||||
Object.entries(routesChunkNames)
|
||||
.filter(
|
||||
([routeNameWithHash]) =>
|
||||
removeRouteNameHash(routeNameWithHash) === path,
|
||||
)
|
||||
.map(([, routeChunks]) => {
|
||||
.map(([, routeChunks]) =>
|
||||
// flat() is useful for nested chunk names, it's not like array.flat()
|
||||
return Object.values(flat(routeChunks));
|
||||
}),
|
||||
Object.values(flat(routeChunks)),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const docusaurus = {
|
||||
prefetch: (routePath: string): boolean => {
|
||||
|
@ -82,6 +81,7 @@ const docusaurus = {
|
|||
chunkNamesNeeded.forEach((chunkName) => {
|
||||
// "__webpack_require__.gca" is a custom function provided by ChunkAssetPlugin.
|
||||
// Pass it the chunkName or chunkId you want to load and it will return the URL for that chunk.
|
||||
// eslint-disable-next-line camelcase
|
||||
const chunkAsset = __webpack_require__.gca(chunkName);
|
||||
|
||||
// In some cases, webpack might decide to optimize further & hence the chunk assets are merged to another chunk/previous chunk.
|
||||
|
|
|
@ -68,23 +68,24 @@ export function interpolate<Str extends string, Value extends ReactNode>(
|
|||
else if (elements.every((el) => typeof el === 'string')) {
|
||||
return processedText
|
||||
.split(ValueFoundMarker)
|
||||
.reduce<string>((str, value, index) => {
|
||||
return str.concat(value).concat((elements[index] as string) ?? '');
|
||||
}, '');
|
||||
.reduce<string>(
|
||||
(str, value, index) =>
|
||||
str.concat(value).concat((elements[index] as string) ?? ''),
|
||||
'',
|
||||
);
|
||||
}
|
||||
// JSX interpolation: returns ReactNode
|
||||
else {
|
||||
return processedText
|
||||
.split(ValueFoundMarker)
|
||||
.reduce<ReactNode[]>((array, value, index) => {
|
||||
return [
|
||||
...array,
|
||||
<React.Fragment key={index}>
|
||||
{value}
|
||||
{elements[index]}
|
||||
</React.Fragment>,
|
||||
];
|
||||
}, []);
|
||||
return processedText.split(ValueFoundMarker).reduce<ReactNode[]>(
|
||||
(array, value, index) => [
|
||||
...array,
|
||||
<React.Fragment key={index}>
|
||||
{value}
|
||||
{elements[index]}
|
||||
</React.Fragment>,
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,9 +45,7 @@ export function useBaseUrlUtils(): BaseUrlUtils {
|
|||
const {siteConfig: {baseUrl = '/', url: siteUrl} = {}} =
|
||||
useDocusaurusContext();
|
||||
return {
|
||||
withBaseUrl: (url, options) => {
|
||||
return addBaseUrl(siteUrl, baseUrl, url, options);
|
||||
},
|
||||
withBaseUrl: (url, options) => addBaseUrl(siteUrl, baseUrl, url, options),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -28,11 +28,11 @@ import chalk from 'chalk';
|
|||
// eslint-disable-next-line no-restricted-imports
|
||||
import {memoize} from 'lodash';
|
||||
|
||||
const getCompiledSSRTemplate = memoize((template) => {
|
||||
return eta.compile(template.trim(), {
|
||||
const getCompiledSSRTemplate = memoize((template) =>
|
||||
eta.compile(template.trim(), {
|
||||
rmWhitespace: true,
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
function renderSSRTemplate(ssrTemplate, data) {
|
||||
const compiled = getCompiledSSRTemplate(ssrTemplate);
|
||||
|
|
|
@ -20,7 +20,7 @@ function Layout(props) {
|
|||
<>
|
||||
<Head defaultTitle={`${defaultTitle}${tagline ? ` · ${tagline}` : ''}`}>
|
||||
{title && <title>{`${title} · ${tagline}`}</title>}
|
||||
{favicon && <link rel="shortcut icon" href={faviconUrl} />}
|
||||
{favicon && <link rel="icon" href={faviconUrl} />}
|
||||
{description && <meta name="description" content={description} />}
|
||||
{description && (
|
||||
<meta property="og:description" content={description} />
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
export default ({error, retry, pastDelay}) => {
|
||||
export default function Loading({error, retry, pastDelay}) {
|
||||
if (error) {
|
||||
return (
|
||||
<div
|
||||
|
@ -133,4 +133,4 @@ export default ({error, retry, pastDelay}) => {
|
|||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// Wrapper at the very top of the app, that is applied constantly
|
||||
// and does not depend on current route (unlike the layout)
|
||||
//
|
||||
|
@ -15,7 +13,7 @@ import React from 'react';
|
|||
//
|
||||
// See https://github.com/facebook/docusaurus/issues/3919
|
||||
function Root({children}) {
|
||||
return <>{children}</>;
|
||||
return children;
|
||||
}
|
||||
|
||||
export default Root;
|
||||
|
|
|
@ -75,9 +75,9 @@ export function getAllBrokenLinks({
|
|||
}): Record<string, BrokenLink[]> {
|
||||
const filteredRoutes = filterIntermediateRoutes(routes);
|
||||
|
||||
const allBrokenLinks = mapValues(allCollectedLinks, (pageLinks, pagePath) => {
|
||||
return getPageBrokenLinks({pageLinks, pagePath, routes: filteredRoutes});
|
||||
});
|
||||
const allBrokenLinks = mapValues(allCollectedLinks, (pageLinks, pagePath) =>
|
||||
getPageBrokenLinks({pageLinks, pagePath, routes: filteredRoutes}),
|
||||
);
|
||||
|
||||
// remove pages without any broken link
|
||||
return pickBy(allBrokenLinks, (brokenLinks) => brokenLinks.length > 0);
|
||||
|
@ -186,9 +186,9 @@ export async function filterExistingFileLinks({
|
|||
return filePathsToTry.some(isExistingFile);
|
||||
}
|
||||
|
||||
return mapValues(allCollectedLinks, (links) => {
|
||||
return links.filter((link) => !linkFileExists(link));
|
||||
});
|
||||
return mapValues(allCollectedLinks, (links) =>
|
||||
links.filter((link) => !linkFileExists(link)),
|
||||
);
|
||||
}
|
||||
|
||||
export async function handleBrokenLinks({
|
||||
|
|
|
@ -108,7 +108,7 @@ const I18N_CONFIG_SCHEMA = Joi.object<I18nConfig>({
|
|||
.optional()
|
||||
.default(DEFAULT_I18N_CONFIG);
|
||||
|
||||
const SiteUrlSchema = URISchema.required().custom(function (value, helpers) {
|
||||
const SiteUrlSchema = URISchema.required().custom((value, helpers) => {
|
||||
try {
|
||||
const {pathname} = new URL(value);
|
||||
if (pathname !== '/') {
|
||||
|
@ -124,7 +124,7 @@ const SiteUrlSchema = URISchema.required().custom(function (value, helpers) {
|
|||
export const ConfigSchema = Joi.object({
|
||||
baseUrl: Joi.string()
|
||||
.required()
|
||||
.regex(new RegExp('/$', 'm'))
|
||||
.regex(/\/$/m)
|
||||
.message('{{#label}} must be a string with a trailing slash.'),
|
||||
baseUrlIssueBanner: Joi.boolean().default(DEFAULT_CONFIG.baseUrlIssueBanner),
|
||||
favicon: Joi.string().optional(),
|
||||
|
|
|
@ -14,9 +14,7 @@ import chalk from 'chalk';
|
|||
function getDefaultLocaleLabel(locale: string) {
|
||||
// Intl.DisplayNames is ES2021 - Node14+
|
||||
// https://v8.dev/features/intl-displaynames
|
||||
// @ts-expect-error: wait for TS support of ES2021 feature
|
||||
if (typeof Intl.DisplayNames !== 'undefined') {
|
||||
// @ts-expect-error: wait for TS support of ES2021 feature
|
||||
return new Intl.DisplayNames([locale], {type: 'language'}).of(locale);
|
||||
}
|
||||
return locale;
|
||||
|
@ -76,9 +74,10 @@ Note: Docusaurus only support running one locale at a time.`,
|
|||
};
|
||||
}
|
||||
|
||||
const localeConfigs = locales.reduce((acc, locale) => {
|
||||
return {...acc, [locale]: getLocaleConfig(locale)};
|
||||
}, {});
|
||||
const localeConfigs = locales.reduce(
|
||||
(acc, locale) => ({...acc, [locale]: getLocaleConfig(locale)}),
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
defaultLocale: i18nConfig.defaultLocale,
|
||||
|
|
|
@ -124,12 +124,12 @@ export async function loadPlugins({
|
|||
|
||||
const allContent: AllContent = chain(loadedPlugins)
|
||||
.groupBy((item) => item.name)
|
||||
.mapValues((nameItems) => {
|
||||
return chain(nameItems)
|
||||
.mapValues((nameItems) =>
|
||||
chain(nameItems)
|
||||
.groupBy((item) => item.options.id ?? DEFAULT_PLUGIN_ID)
|
||||
.mapValues((idItems) => idItems[0].content)
|
||||
.value();
|
||||
})
|
||||
.value(),
|
||||
)
|
||||
.value();
|
||||
|
||||
// 3. Plugin Lifecycle - contentLoaded.
|
||||
|
|
|
@ -19,9 +19,9 @@ export function sortAliases(aliases: ThemeAliases): ThemeAliases {
|
|||
// Alphabetical order by default
|
||||
const entries = sortBy(Object.entries(aliases), ([alias]) => alias);
|
||||
// @theme/NavbarItem should be after @theme/NavbarItem/LocaleDropdown
|
||||
entries.sort(([alias1], [alias2]) => {
|
||||
return alias1.includes(`${alias2}/`) ? -1 : 0;
|
||||
});
|
||||
entries.sort(([alias1], [alias2]) =>
|
||||
alias1.includes(`${alias2}/`) ? -1 : 0,
|
||||
);
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
|
|
|
@ -271,9 +271,10 @@ export async function getPluginsDefaultCodeTranslationMessages(
|
|||
plugins.map((plugin) => plugin.getDefaultCodeTranslationMessages?.() ?? {}),
|
||||
);
|
||||
|
||||
return pluginsMessages.reduce((allMessages, pluginMessages) => {
|
||||
return {...allMessages, ...pluginMessages};
|
||||
}, {});
|
||||
return pluginsMessages.reduce(
|
||||
(allMessages, pluginMessages) => ({...allMessages, ...pluginMessages}),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
export function applyDefaultCodeTranslations({
|
||||
|
@ -298,11 +299,9 @@ Please report this Docusaurus issue.
|
|||
|
||||
return mapValues(
|
||||
extractedCodeTranslations,
|
||||
(messageTranslation, messageId) => {
|
||||
return {
|
||||
...messageTranslation,
|
||||
message: defaultCodeMessages[messageId] ?? messageTranslation.message,
|
||||
};
|
||||
},
|
||||
(messageTranslation, messageId) => ({
|
||||
...messageTranslation,
|
||||
message: defaultCodeMessages[messageId] ?? messageTranslation.message,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -85,9 +85,10 @@ export async function extractSiteSourceCodeTranslations(
|
|||
function toTranslationFileContent(
|
||||
sourceCodeFileTranslations: SourceCodeFileTranslations[],
|
||||
): TranslationFileContent {
|
||||
return sourceCodeFileTranslations.reduce((acc, item) => {
|
||||
return {...acc, ...item.translations};
|
||||
}, {});
|
||||
return sourceCodeFileTranslations.reduce(
|
||||
(acc, item) => ({...acc, ...item.translations}),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
const sourceCodeFilePaths = await getSourceCodeFilePaths(siteDir, plugins);
|
||||
|
|
|
@ -250,35 +250,38 @@ describe('extending PostCSS', () => {
|
|||
}
|
||||
|
||||
// Run multiple times: ensure last run does not override previous runs
|
||||
webpackConfig = applyConfigurePostCss((postCssOptions) => {
|
||||
return {
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
...postCssOptions.plugins,
|
||||
createFakePlugin('postcss-plugin-1'),
|
||||
],
|
||||
};
|
||||
}, webpackConfig);
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
webpackConfig = applyConfigurePostCss((postCssOptions) => {
|
||||
return {
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
createFakePlugin('postcss-plugin-2'),
|
||||
...postCssOptions.plugins,
|
||||
],
|
||||
};
|
||||
}, webpackConfig);
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
webpackConfig = applyConfigurePostCss((postCssOptions) => {
|
||||
return {
|
||||
webpackConfig = applyConfigurePostCss(
|
||||
(postCssOptions) => ({
|
||||
...postCssOptions,
|
||||
plugins: [
|
||||
...postCssOptions.plugins,
|
||||
createFakePlugin('postcss-plugin-3'),
|
||||
],
|
||||
};
|
||||
}, webpackConfig);
|
||||
}),
|
||||
webpackConfig,
|
||||
);
|
||||
|
||||
// @ts-expect-error: relax type
|
||||
const postCssLoader1 = webpackConfig.module?.rules[0].use[2];
|
||||
|
|
|
@ -176,18 +176,16 @@ class CleanWebpackPlugin {
|
|||
all: false,
|
||||
assets: true,
|
||||
}).assets || [];
|
||||
const assets = statsAssets.map((asset: {name: string}) => {
|
||||
return asset.name;
|
||||
});
|
||||
const assets = statsAssets.map((asset: {name: string}) => asset.name);
|
||||
|
||||
/**
|
||||
* Get all files that were in the previous build but not the current
|
||||
*
|
||||
* (relies on del's cwd: outputPath option)
|
||||
*/
|
||||
const staleFiles = this.currentAssets.filter((previousAsset) => {
|
||||
return assets.includes(previousAsset) === false;
|
||||
});
|
||||
const staleFiles = this.currentAssets.filter(
|
||||
(previousAsset) => assets.includes(previousAsset) === false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Save assets for next compilation
|
||||
|
|
|
@ -164,7 +164,7 @@ export const getCustomizableJSLoader =
|
|||
: jsLoader(isServer);
|
||||
|
||||
// TODO remove this before end of 2021?
|
||||
const warnBabelLoaderOnce = memoize(function () {
|
||||
const warnBabelLoaderOnce = memoize(() => {
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
'Docusaurus plans to support multiple JS loader strategies (Babel, esbuild...): "getBabelLoader(isServer)" is now deprecated in favor of "getJSLoader({isServer})".',
|
||||
|
@ -180,7 +180,7 @@ const getBabelLoaderDeprecated = function getBabelLoaderDeprecated(
|
|||
};
|
||||
|
||||
// TODO remove this before end of 2021 ?
|
||||
const warnCacheLoaderOnce = memoize(function () {
|
||||
const warnCacheLoaderOnce = memoize(() => {
|
||||
console.warn(
|
||||
chalk.yellow(
|
||||
'Docusaurus uses Webpack 5 and getCacheLoader() usage is now deprecated.',
|
||||
|
@ -335,24 +335,20 @@ export function getFileLoaderUtils(): FileLoaderUtils {
|
|||
`${OUTPUT_STATIC_ASSETS_DIR_NAME}/${folder}/[name]-[hash].[ext]`;
|
||||
|
||||
const loaders: FileLoaderUtils['loaders'] = {
|
||||
file: (options: {folder: AssetFolder}) => {
|
||||
return {
|
||||
loader: require.resolve(`file-loader`),
|
||||
options: {
|
||||
name: fileLoaderFileName(options.folder),
|
||||
},
|
||||
};
|
||||
},
|
||||
url: (options: {folder: AssetFolder}) => {
|
||||
return {
|
||||
loader: require.resolve(`url-loader`),
|
||||
options: {
|
||||
limit: urlLoaderLimit,
|
||||
name: fileLoaderFileName(options.folder),
|
||||
fallback: require.resolve(`file-loader`),
|
||||
},
|
||||
};
|
||||
},
|
||||
file: (options: {folder: AssetFolder}) => ({
|
||||
loader: require.resolve(`file-loader`),
|
||||
options: {
|
||||
name: fileLoaderFileName(options.folder),
|
||||
},
|
||||
}),
|
||||
url: (options: {folder: AssetFolder}) => ({
|
||||
loader: require.resolve(`url-loader`),
|
||||
options: {
|
||||
limit: urlLoaderLimit,
|
||||
name: fileLoaderFileName(options.folder),
|
||||
fallback: require.resolve(`file-loader`),
|
||||
},
|
||||
}),
|
||||
|
||||
// TODO find a better solution to avoid conflicts with the ideal-image plugin
|
||||
// TODO this may require a little breaking change for ideal-image users?
|
||||
|
@ -372,69 +368,59 @@ export function getFileLoaderUtils(): FileLoaderUtils {
|
|||
* Loads image assets, inlines images via a data URI if they are below
|
||||
* the size threshold
|
||||
*/
|
||||
images: () => {
|
||||
return {
|
||||
use: [loaders.url({folder: 'images'})],
|
||||
test: /\.(ico|jpg|jpeg|png|gif|webp)(\?.*)?$/,
|
||||
};
|
||||
},
|
||||
images: () => ({
|
||||
use: [loaders.url({folder: 'images'})],
|
||||
test: /\.(ico|jpg|jpeg|png|gif|webp)(\?.*)?$/,
|
||||
}),
|
||||
|
||||
fonts: () => {
|
||||
return {
|
||||
use: [loaders.url({folder: 'fonts'})],
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/,
|
||||
};
|
||||
},
|
||||
fonts: () => ({
|
||||
use: [loaders.url({folder: 'fonts'})],
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Loads audio and video and inlines them via a data URI if they are below
|
||||
* the size threshold
|
||||
*/
|
||||
media: () => {
|
||||
return {
|
||||
use: [loaders.url({folder: 'medias'})],
|
||||
test: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/,
|
||||
};
|
||||
},
|
||||
media: () => ({
|
||||
use: [loaders.url({folder: 'medias'})],
|
||||
test: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/,
|
||||
}),
|
||||
|
||||
svg: () => {
|
||||
return {
|
||||
test: /\.svg?$/,
|
||||
oneOf: [
|
||||
{
|
||||
use: [
|
||||
{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
prettier: false,
|
||||
svgo: true,
|
||||
svgoConfig: {
|
||||
plugins: [{removeViewBox: false}],
|
||||
},
|
||||
titleProp: true,
|
||||
ref: ![path],
|
||||
svg: () => ({
|
||||
test: /\.svg?$/,
|
||||
oneOf: [
|
||||
{
|
||||
use: [
|
||||
{
|
||||
loader: '@svgr/webpack',
|
||||
options: {
|
||||
prettier: false,
|
||||
svgo: true,
|
||||
svgoConfig: {
|
||||
plugins: [{removeViewBox: false}],
|
||||
},
|
||||
titleProp: true,
|
||||
ref: ![path],
|
||||
},
|
||||
],
|
||||
// We don't want to use SVGR loader for non-React source code
|
||||
// ie we don't want to use SVGR for CSS files...
|
||||
issuer: {
|
||||
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
|
||||
},
|
||||
],
|
||||
// We don't want to use SVGR loader for non-React source code
|
||||
// ie we don't want to use SVGR for CSS files...
|
||||
issuer: {
|
||||
and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
|
||||
},
|
||||
{
|
||||
use: [loaders.url({folder: 'images'})],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
use: [loaders.url({folder: 'images'})],
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
otherAssets: () => {
|
||||
return {
|
||||
use: [loaders.file({folder: 'files'})],
|
||||
test: /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/,
|
||||
};
|
||||
},
|
||||
otherAssets: () => ({
|
||||
use: [loaders.file({folder: 'files'})],
|
||||
test: /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/,
|
||||
}),
|
||||
};
|
||||
|
||||
return {loaders, rules};
|
||||
|
|
|
@ -12,9 +12,8 @@ import type {Palette} from 'node-vibrant/lib/color';
|
|||
* it returns a Base64 image string with required formatting
|
||||
* to work on the web (<img src=".." /> or in CSS url('..'))
|
||||
*/
|
||||
const toBase64 = (extMimeType: string, data: Buffer): string => {
|
||||
return `data:${extMimeType};base64,${data.toString('base64')}`;
|
||||
};
|
||||
const toBase64 = (extMimeType: string, data: Buffer): string =>
|
||||
`data:${extMimeType};base64,${data.toString('base64')}`;
|
||||
|
||||
/**
|
||||
* takes a color swatch object, converts it to an array & returns
|
||||
|
|
|
@ -12,8 +12,9 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
|
|||
rejected: 'Missing copyright in the header comment',
|
||||
});
|
||||
|
||||
module.exports = stylelint.createPlugin(ruleName, (actual) => {
|
||||
return (root, result) => {
|
||||
module.exports = stylelint.createPlugin(
|
||||
ruleName,
|
||||
(actual) => (root, result) => {
|
||||
const validOptions = stylelint.utils.validateOptions(result, ruleName, {
|
||||
actual,
|
||||
});
|
||||
|
@ -48,8 +49,8 @@ module.exports = stylelint.createPlugin(ruleName, (actual) => {
|
|||
ruleName,
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
module.exports.ruleName = ruleName;
|
||||
module.exports.messages = messages;
|
||||
|
|
|
@ -14,7 +14,9 @@ Used in conjunction with waitForCrowdin.js (which is not enough)
|
|||
*/
|
||||
|
||||
async function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function run() {
|
||||
|
|
|
@ -43,55 +43,53 @@ function ShowcaseCardTagIcons({tags}: {tags: TagType[]}) {
|
|||
);
|
||||
}
|
||||
|
||||
const ShowcaseCard = memo(function ({user}: {user: User}) {
|
||||
return (
|
||||
<div key={user.title} className="col col--4 margin-bottom--lg">
|
||||
<div className={clsx('card', styles.showcaseCard)}>
|
||||
<div className={clsx('card__image', styles.showcaseCardImage)}>
|
||||
<Image img={user.preview} alt={user.title} />
|
||||
</div>
|
||||
<div className="card__body">
|
||||
<div className="avatar">
|
||||
<div className="avatar__intro margin-left--none">
|
||||
<div className={styles.titleIconsRow}>
|
||||
<div className={styles.titleIconsRowTitle}>
|
||||
<div className="avatar__name">{user.title}</div>
|
||||
</div>
|
||||
<div className={styles.titleIconsRowIcons}>
|
||||
<ShowcaseCardTagIcons tags={user.tags} />
|
||||
</div>
|
||||
</div>
|
||||
<small className="avatar__subtitle">{user.description}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(user.website || user.source) && (
|
||||
<div className="card__footer">
|
||||
<div className="button-group button-group--block">
|
||||
{user.website && (
|
||||
<a
|
||||
className="button button--small button--secondary button--block"
|
||||
href={user.website}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
Website
|
||||
</a>
|
||||
)}
|
||||
{user.source && (
|
||||
<a
|
||||
className="button button--small button--secondary button--block"
|
||||
href={user.source}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
Source
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
const ShowcaseCard = memo(({user}: {user: User}) => (
|
||||
<div key={user.title} className="col col--4 margin-bottom--lg">
|
||||
<div className={clsx('card', styles.showcaseCard)}>
|
||||
<div className={clsx('card__image', styles.showcaseCardImage)}>
|
||||
<Image img={user.preview} alt={user.title} />
|
||||
</div>
|
||||
<div className="card__body">
|
||||
<div className="avatar">
|
||||
<div className="avatar__intro margin-left--none">
|
||||
<div className={styles.titleIconsRow}>
|
||||
<div className={styles.titleIconsRowTitle}>
|
||||
<div className="avatar__name">{user.title}</div>
|
||||
</div>
|
||||
<div className={styles.titleIconsRowIcons}>
|
||||
<ShowcaseCardTagIcons tags={user.tags} />
|
||||
</div>
|
||||
</div>
|
||||
<small className="avatar__subtitle">{user.description}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(user.website || user.source) && (
|
||||
<div className="card__footer">
|
||||
<div className="button-group button-group--block">
|
||||
{user.website && (
|
||||
<a
|
||||
className="button button--small button--secondary button--block"
|
||||
href={user.website}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
Website
|
||||
</a>
|
||||
)}
|
||||
{user.source && (
|
||||
<a
|
||||
className="button button--small button--secondary button--block"
|
||||
href={user.source}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener">
|
||||
Source
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
</div>
|
||||
));
|
||||
|
||||
export default ShowcaseCard;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue