mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-12 08:37:25 +02:00
refactor: remove deprecated docs homePageId option (#6065)
This commit is contained in:
parent
c64987e2c8
commit
f96a051fbe
17 changed files with 96 additions and 175 deletions
|
@ -37,7 +37,6 @@ module.exports = {
|
|||
'@docusaurus/preset-classic',
|
||||
{
|
||||
docs: {
|
||||
homePageId: 'installation',
|
||||
showLastUpdateAuthor: true,
|
||||
showLastUpdateTime: true,
|
||||
editUrl: 'https://github.com/facebook/docusaurus/edit/main/docs/',
|
||||
|
@ -53,7 +52,7 @@ module.exports = {
|
|||
title: 'Docusaurus',
|
||||
logo: {src: 'img/docusaurus.svg'},
|
||||
items: [
|
||||
{to: 'docs/', label: 'Docs', position: 'left'},
|
||||
{to: 'docs/installation', label: 'Docs', position: 'left'},
|
||||
{to: 'docs/tutorial-setup', label: 'Tutorial', position: 'left'},
|
||||
{to: '/users', label: 'Users', position: 'left'},
|
||||
{
|
||||
|
|
|
@ -226,9 +226,6 @@ export function createConfigFile({
|
|||
'v1Config' | 'siteDir' | 'newDir'
|
||||
>): VersionTwoConfig {
|
||||
const siteConfig = v1Config;
|
||||
const homePageId = siteConfig.headerLinks?.filter((value) => value.doc)[0]
|
||||
.doc;
|
||||
|
||||
const customConfigFields: Record<string, unknown> = {};
|
||||
// add fields that are unknown to v2 to customConfigFields
|
||||
Object.keys(siteConfig).forEach((key) => {
|
||||
|
@ -309,7 +306,6 @@ export function createConfigFile({
|
|||
{
|
||||
docs: {
|
||||
...(v2DocsPath && {path: v2DocsPath}),
|
||||
homePageId,
|
||||
showLastUpdateAuthor: true,
|
||||
showLastUpdateTime: true,
|
||||
editUrl: siteConfig.editUrl,
|
||||
|
@ -334,7 +330,7 @@ export function createConfigFile({
|
|||
const position = 'left';
|
||||
if (doc) {
|
||||
return {
|
||||
to: `docs/${doc === homePageId ? '' : doc}`,
|
||||
to: `docs/${doc}`,
|
||||
label,
|
||||
position,
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ id: hello
|
|||
title: Hello, World !
|
||||
sidebar_label: Hello sidebar_label
|
||||
tags: [tag-1, tag 3]
|
||||
slug: /
|
||||
---
|
||||
|
||||
Hi, Endilie here :)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
id: hello-1
|
||||
title: Hello 1
|
||||
slug: /
|
||||
---
|
||||
|
||||
Hello World 1!
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
---
|
||||
slug: /
|
||||
---
|
||||
Hello `next` !
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
---
|
||||
slug: /
|
||||
---
|
||||
Hello `1.0.0` ! (translated en)
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
---
|
||||
slug: /
|
||||
---
|
||||
Hello `1.0.0` ! (translated fr)
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
---
|
||||
slug: /
|
||||
---
|
||||
Hello `1.0.0` !
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
---
|
||||
slug: /
|
||||
---
|
||||
Hello `1.0.1` !
|
||||
|
|
|
@ -25,7 +25,7 @@ Array [
|
|||
Array [
|
||||
undefined,
|
||||
Object {
|
||||
"permalink": "/docs/hello",
|
||||
"permalink": "/docs/",
|
||||
"title": "Hello sidebar_label",
|
||||
},
|
||||
],
|
||||
|
@ -41,7 +41,7 @@ Array [
|
|||
],
|
||||
Array [
|
||||
Object {
|
||||
"permalink": "/docs/hello",
|
||||
"permalink": "/docs/",
|
||||
"title": "Hello sidebar_label",
|
||||
},
|
||||
Object {
|
||||
|
|
|
@ -314,7 +314,8 @@ Object {
|
|||
\\"tags\\": [
|
||||
\\"tag-1\\",
|
||||
\\"tag 3\\"
|
||||
]
|
||||
],
|
||||
\\"slug\\": \\"/\\"
|
||||
},
|
||||
\\"sidebar\\": \\"docs\\",
|
||||
\\"previous\\": {
|
||||
|
@ -1528,7 +1529,9 @@ Object {
|
|||
\\"permalink\\": \\"/docs/next/\\",
|
||||
\\"tags\\": [],
|
||||
\\"version\\": \\"current\\",
|
||||
\\"frontMatter\\": {},
|
||||
\\"frontMatter\\": {
|
||||
\\"slug\\": \\"/\\"
|
||||
},
|
||||
\\"sidebar\\": \\"docs\\",
|
||||
\\"previous\\": {
|
||||
\\"title\\": \\"bar\\",
|
||||
|
@ -1606,7 +1609,9 @@ Object {
|
|||
\\"permalink\\": \\"/docs/1.0.0/\\",
|
||||
\\"tags\\": [],
|
||||
\\"version\\": \\"1.0.0\\",
|
||||
\\"frontMatter\\": {},
|
||||
\\"frontMatter\\": {
|
||||
\\"slug\\": \\"/\\"
|
||||
},
|
||||
\\"sidebar\\": \\"version-1.0.0/docs\\",
|
||||
\\"previous\\": {
|
||||
\\"title\\": \\"baz\\",
|
||||
|
@ -1684,7 +1689,9 @@ Object {
|
|||
\\"permalink\\": \\"/docs/\\",
|
||||
\\"tags\\": [],
|
||||
\\"version\\": \\"1.0.1\\",
|
||||
\\"frontMatter\\": {},
|
||||
\\"frontMatter\\": {
|
||||
\\"slug\\": \\"/\\"
|
||||
},
|
||||
\\"sidebar\\": \\"VersionedSideBarNameDoesNotMatter/docs\\",
|
||||
\\"previous\\": {
|
||||
\\"title\\": \\"bar\\",
|
||||
|
|
|
@ -236,14 +236,15 @@ describe('simple site', () => {
|
|||
id: 'hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/docs/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/docs/',
|
||||
slug: '/',
|
||||
title: 'Hello, World !',
|
||||
description: `Hi, Endilie here :)`,
|
||||
frontMatter: {
|
||||
id: 'hello',
|
||||
title: 'Hello, World !',
|
||||
sidebar_label: 'Hello sidebar_label',
|
||||
slug: '/',
|
||||
tags: ['tag-1', 'tag 3'],
|
||||
},
|
||||
tags: [
|
||||
|
@ -259,76 +260,6 @@ describe('simple site', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('homePageId doc', async () => {
|
||||
const {siteDir, context, options, currentVersion} = await loadSite({
|
||||
options: {homePageId: 'hello'},
|
||||
});
|
||||
|
||||
const testUtilsLocal = createTestUtils({
|
||||
siteDir,
|
||||
context,
|
||||
options,
|
||||
versionMetadata: currentVersion,
|
||||
});
|
||||
|
||||
await testUtilsLocal.testMeta(path.join('hello.md'), {
|
||||
version: 'current',
|
||||
id: 'hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/docs/',
|
||||
slug: '/',
|
||||
title: 'Hello, World !',
|
||||
description: `Hi, Endilie here :)`,
|
||||
frontMatter: {
|
||||
id: 'hello',
|
||||
title: 'Hello, World !',
|
||||
sidebar_label: 'Hello sidebar_label',
|
||||
tags: ['tag-1', 'tag 3'],
|
||||
},
|
||||
tags: [
|
||||
{
|
||||
label: 'tag-1',
|
||||
permalink: '/docs/tags/tag-1',
|
||||
},
|
||||
{
|
||||
label: 'tag 3',
|
||||
permalink: '/docs/tags/tag-3',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('homePageId doc nested', async () => {
|
||||
const {siteDir, context, options, currentVersion} = await loadSite({
|
||||
options: {homePageId: 'foo/bar'},
|
||||
});
|
||||
|
||||
const testUtilsLocal = createTestUtils({
|
||||
siteDir,
|
||||
context,
|
||||
options,
|
||||
versionMetadata: currentVersion,
|
||||
});
|
||||
|
||||
await testUtilsLocal.testMeta(path.join('foo', 'bar.md'), {
|
||||
version: 'current',
|
||||
id: 'foo/bar',
|
||||
unversionedId: 'foo/bar',
|
||||
sourceDirName: 'foo',
|
||||
permalink: '/docs/',
|
||||
slug: '/',
|
||||
title: 'Bar',
|
||||
description: 'This is custom description',
|
||||
frontMatter: {
|
||||
description: 'This is custom description',
|
||||
id: 'bar',
|
||||
title: 'Bar',
|
||||
},
|
||||
tags: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('docs with editUrl', async () => {
|
||||
const {siteDir, context, options, currentVersion} = await loadSite({
|
||||
options: {
|
||||
|
@ -550,33 +481,6 @@ describe('simple site', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('docs with slug on doc home', async () => {
|
||||
const {siteDir, context, options, currentVersion} = await loadSite({
|
||||
options: {
|
||||
homePageId: 'homePageId',
|
||||
},
|
||||
});
|
||||
|
||||
const testUtilsLocal = createTestUtils({
|
||||
siteDir,
|
||||
context,
|
||||
options,
|
||||
versionMetadata: currentVersion,
|
||||
});
|
||||
expect(() => {
|
||||
testUtilsLocal.processDocFile(
|
||||
createFakeDocFile({
|
||||
source: 'homePageId',
|
||||
frontmatter: {
|
||||
slug: '/x/y',
|
||||
},
|
||||
}),
|
||||
);
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"The docs homepage (homePageId=homePageId) is not allowed to have a frontmatter slug=/x/y => you have to choose either homePageId or slug, not both"`,
|
||||
);
|
||||
});
|
||||
|
||||
test('custom pagination', async () => {
|
||||
const {defaultTestUtils, options, versionsMetadata} = await loadSite();
|
||||
const docs = await readVersionDocs(versionsMetadata[0], options);
|
||||
|
@ -706,11 +610,13 @@ describe('versioned site', () => {
|
|||
version: 'current',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/docs/next/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/docs/next/',
|
||||
slug: '/',
|
||||
title: 'hello',
|
||||
description: 'Hello next !',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
tags: [],
|
||||
});
|
||||
});
|
||||
|
@ -734,11 +640,13 @@ describe('versioned site', () => {
|
|||
id: 'version-1.0.0/hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/docs/1.0.0/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/docs/1.0.0/',
|
||||
slug: '/',
|
||||
title: 'hello',
|
||||
description: 'Hello 1.0.0 ! (translated en)',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
version: '1.0.0',
|
||||
source:
|
||||
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||
|
@ -760,12 +668,14 @@ describe('versioned site', () => {
|
|||
id: 'version-1.0.1/hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/docs/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/docs/',
|
||||
slug: '/',
|
||||
title: 'hello',
|
||||
description: 'Hello 1.0.1 !',
|
||||
version: '1.0.1',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
tags: [],
|
||||
});
|
||||
});
|
||||
|
@ -851,11 +761,13 @@ describe('versioned site', () => {
|
|||
id: 'version-1.0.0/hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/docs/1.0.0/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/docs/1.0.0/',
|
||||
slug: '/',
|
||||
title: 'hello',
|
||||
description: 'Hello 1.0.0 ! (translated en)',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
version: '1.0.0',
|
||||
source:
|
||||
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||
|
@ -868,7 +780,7 @@ describe('versioned site', () => {
|
|||
version: '1.0.0',
|
||||
versionDocsDirPath: 'versioned_docs/version-1.0.0',
|
||||
docPath: path.join('hello.md'),
|
||||
permalink: '/docs/1.0.0/hello',
|
||||
permalink: '/docs/1.0.0/',
|
||||
locale: 'en',
|
||||
});
|
||||
});
|
||||
|
@ -892,11 +804,13 @@ describe('versioned site', () => {
|
|||
id: 'version-1.0.0/hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/docs/1.0.0/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/docs/1.0.0/',
|
||||
slug: '/',
|
||||
title: 'hello',
|
||||
description: 'Hello 1.0.0 ! (translated en)',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
version: '1.0.0',
|
||||
source:
|
||||
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||
|
@ -925,11 +839,13 @@ describe('versioned site', () => {
|
|||
id: 'version-1.0.0/hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/docs/1.0.0/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/docs/1.0.0/',
|
||||
slug: '/',
|
||||
title: 'hello',
|
||||
description: 'Hello 1.0.0 ! (translated en)',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
version: '1.0.0',
|
||||
source:
|
||||
'@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||
|
@ -959,11 +875,13 @@ describe('versioned site', () => {
|
|||
id: 'version-1.0.0/hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/fr/docs/1.0.0/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/fr/docs/1.0.0/',
|
||||
slug: '/',
|
||||
title: 'hello',
|
||||
description: 'Hello 1.0.0 ! (translated fr)',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
version: '1.0.0',
|
||||
source:
|
||||
'@site/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||
|
@ -994,11 +912,13 @@ describe('versioned site', () => {
|
|||
id: 'version-1.0.0/hello',
|
||||
unversionedId: 'hello',
|
||||
sourceDirName: '.',
|
||||
permalink: '/fr/docs/1.0.0/hello',
|
||||
slug: '/hello',
|
||||
permalink: '/fr/docs/1.0.0/',
|
||||
slug: '/',
|
||||
title: 'hello',
|
||||
description: 'Hello 1.0.0 ! (translated fr)',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
version: '1.0.0',
|
||||
source:
|
||||
'@site/i18n/fr/docusaurus-plugin-content-docs/version-1.0.0/hello.md',
|
||||
|
|
|
@ -247,7 +247,6 @@ describe('simple website', () => {
|
|||
normalizePluginOptions(OptionsSchema, {
|
||||
path: 'docs',
|
||||
sidebarPath,
|
||||
homePageId: 'hello',
|
||||
}),
|
||||
);
|
||||
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
|
||||
|
@ -405,6 +404,7 @@ describe('simple website', () => {
|
|||
id: 'hello',
|
||||
title: 'Hello, World !',
|
||||
sidebar_label: 'Hello sidebar_label',
|
||||
slug: '/',
|
||||
tags: ['tag-1', 'tag 3'],
|
||||
},
|
||||
tags: [
|
||||
|
@ -476,7 +476,6 @@ describe('versioned website', () => {
|
|||
normalizePluginOptions(OptionsSchema, {
|
||||
routeBasePath,
|
||||
sidebarPath,
|
||||
homePageId: 'hello',
|
||||
}),
|
||||
);
|
||||
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
|
||||
|
@ -654,7 +653,9 @@ describe('versioned website', () => {
|
|||
),
|
||||
title: 'hello',
|
||||
description: 'Hello next !',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
version: 'current',
|
||||
sidebar: 'docs',
|
||||
previous: {
|
||||
|
@ -676,7 +677,9 @@ describe('versioned website', () => {
|
|||
),
|
||||
title: 'hello',
|
||||
description: 'Hello 1.0.1 !',
|
||||
frontMatter: {},
|
||||
frontMatter: {
|
||||
slug: '/',
|
||||
},
|
||||
version: '1.0.1',
|
||||
sidebar: 'VersionedSideBarNameDoesNotMatter/docs',
|
||||
previous: {
|
||||
|
@ -892,7 +895,6 @@ describe('site with doc label', () => {
|
|||
normalizePluginOptions(OptionsSchema, {
|
||||
path: 'docs',
|
||||
sidebarPath,
|
||||
homePageId: 'hello-1',
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
@ -41,7 +41,6 @@ describe('normalizeDocsPluginOptions', () => {
|
|||
path: 'my-docs', // Path to data on filesystem, relative to site dir.
|
||||
routeBasePath: 'my-docs', // URL Route.
|
||||
tagsBasePath: 'tags', // URL Tags Route.
|
||||
homePageId: 'home', // Document id for docs home page.
|
||||
include: ['**/*.{md,mdx}'], // Extensions to include.
|
||||
exclude: GlobExcludeDefault,
|
||||
sidebarPath: 'my-sidebar', // Path to sidebar configuration for showing a list of markdown pages.
|
||||
|
|
|
@ -126,7 +126,6 @@ function doProcessDocMetadata({
|
|||
options: MetadataOptions;
|
||||
}): DocMetadataBase {
|
||||
const {source, content, lastUpdate, contentPath, filePath} = docFile;
|
||||
const {homePageId} = options;
|
||||
const {siteDir, i18n} = context;
|
||||
|
||||
const {
|
||||
|
@ -196,24 +195,14 @@ function doProcessDocMetadata({
|
|||
// legacy versioned id, requires a breaking change to modify this
|
||||
const id = [versionIdPrefix, unversionedId].filter(Boolean).join('/');
|
||||
|
||||
// TODO remove soon, deprecated homePageId
|
||||
const isDocsHomePage = unversionedId === (homePageId ?? '_index');
|
||||
if (frontMatter.slug && isDocsHomePage) {
|
||||
throw new Error(
|
||||
`The docs homepage (homePageId=${homePageId}) is not allowed to have a frontmatter slug=${frontMatter.slug} => you have to choose either homePageId or slug, not both`,
|
||||
);
|
||||
}
|
||||
|
||||
const docSlug = isDocsHomePage
|
||||
? '/'
|
||||
: getSlug({
|
||||
baseID,
|
||||
source,
|
||||
sourceDirName,
|
||||
frontmatterSlug: frontMatter.slug,
|
||||
stripDirNumberPrefixes: parseNumberPrefixes,
|
||||
numberPrefixParser: options.numberPrefixParser,
|
||||
});
|
||||
const docSlug = getSlug({
|
||||
baseID,
|
||||
source,
|
||||
sourceDirName,
|
||||
frontmatterSlug: frontMatter.slug,
|
||||
stripDirNumberPrefixes: parseNumberPrefixes,
|
||||
numberPrefixParser: options.numberPrefixParser,
|
||||
});
|
||||
|
||||
// Note: the title is used by default for page title, sidebar label, pagination buttons...
|
||||
// frontMatter.title should be used in priority over contentTitle (because it can contain markdown/JSX syntax)
|
||||
|
|
|
@ -30,7 +30,6 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
|
|||
path: 'docs', // Path to data on filesystem, relative to site dir.
|
||||
routeBasePath: 'docs', // URL Route.
|
||||
tagsBasePath: 'tags', // URL Tags Route.
|
||||
homePageId: undefined, // TODO remove soon, deprecated
|
||||
include: ['**/*.{md,mdx}'], // Extensions to include.
|
||||
exclude: GlobExcludeDefault,
|
||||
sidebarItemsGenerator: DefaultSidebarItemsGenerator,
|
||||
|
@ -79,7 +78,11 @@ export const OptionsSchema = Joi.object({
|
|||
// .allow('') ""
|
||||
.default(DEFAULT_OPTIONS.routeBasePath),
|
||||
tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
|
||||
homePageId: Joi.string().optional(),
|
||||
homePageId: Joi.any().forbidden().messages({
|
||||
'any.unknown':
|
||||
'The docs plugin option homePageId is not supported anymore. To make a doc the "home", please add "slug: /" in its front matter. See: https://docusaurus.io/docs/next/docs-introduction#home-page-docs',
|
||||
}),
|
||||
|
||||
include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
|
||||
exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
|
||||
sidebarPath: Joi.alternatives().try(
|
||||
|
@ -165,16 +168,6 @@ export function validateOptions({
|
|||
}
|
||||
}
|
||||
|
||||
// TODO remove homePageId before end of 2020
|
||||
// "slug: /" is better because the home doc can be different across versions
|
||||
if (options.homePageId) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
`The docs plugin option homePageId=${options.homePageId} is deprecated. To make a doc the "home", prefer frontmatter: "slug: /"`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedOptions = validate(OptionsSchema, options);
|
||||
|
||||
if (normalizedOptions.admonitions) {
|
||||
|
|
|
@ -50,7 +50,6 @@ export type EditUrlFunction = (editUrlParams: {
|
|||
|
||||
export type MetadataOptions = {
|
||||
routeBasePath: string;
|
||||
homePageId?: string;
|
||||
editUrl?: string | EditUrlFunction;
|
||||
editCurrentVersion: boolean;
|
||||
editLocalizedFiles: boolean;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue