fix(v2): fix title logic (meta vs heading) + ignore fixed anchor id syntax (#4688)

* parseMarkdownContentTitle should ignore {#my-anchor-id} syntax

* use frontMatter.title in priority for page meta title

* parseMarkdownString should ignore fixed anchor ids syntax

* docs: make the distinction between headingTitle + metaTitle more clear + add useful todo

* docs: make the distinction between headingTitle + metaTitle more clear + add useful todo

* writeHeadingIds should ignore top-level md title like "# Title"
=> we are not supposed to create anchor links for h1 headers

* update tests

* fix doc tests
This commit is contained in:
Sébastien Lorber 2021-04-27 15:44:46 +02:00 committed by GitHub
parent bca796545b
commit 8ebbc17c7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 124 additions and 77 deletions

View file

@ -138,6 +138,20 @@ describe('parseMarkdownContentTitle', () => {
});
});
test('Should parse markdown h1 title with fixed anchor-id syntax', () => {
const markdown = dedent`
# Markdown Title {#my-anchor-id}
Lorem Ipsum
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
content: 'Lorem Ipsum',
contentTitle: 'Markdown Title',
});
});
test('Should parse markdown h1 title at the top (atx style with closing #)', () => {
const markdown = dedent`
@ -152,7 +166,7 @@ describe('parseMarkdownContentTitle', () => {
});
});
test('Should parse markdown h1 title at the top and next one after it', () => {
test('Should parse markdown h1 title at the top followed by h2 title', () => {
const markdown = dedent`
# Markdown Title
@ -163,11 +177,61 @@ describe('parseMarkdownContentTitle', () => {
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
content: '## Heading 2\n\nLorem Ipsum',
content: dedent`
## Heading 2
Lorem Ipsum
`,
contentTitle: 'Markdown Title',
});
});
test('Should parse only first h1 title', () => {
const markdown = dedent`
# Markdown Title
# Markdown Title 2
Lorem Ipsum
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
content: dedent`
# Markdown Title 2
Lorem Ipsum
`,
contentTitle: 'Markdown Title',
});
});
test('Should not parse title that is not at the top', () => {
const markdown = dedent`
Lorem Ipsum
# Markdown Title 2
Lorem Ipsum
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
content: dedent`
Lorem Ipsum
# Markdown Title 2
Lorem Ipsum
`,
contentTitle: undefined,
});
});
test('Should parse markdown h1 alternate title', () => {
const markdown = dedent`
@ -185,8 +249,10 @@ describe('parseMarkdownContentTitle', () => {
test('Should parse markdown h1 title placed after import declarations', () => {
const markdown = dedent`
import Component from '@site/src/components/Component';
import Component from '@site/src/components/Component'
import Component1 from '@site/src/components/Component1';
import Component2 from '@site/src/components/Component2'
import Component3 from '@site/src/components/Component3'
import './styles.css';
# Markdown Title
@ -194,8 +260,20 @@ describe('parseMarkdownContentTitle', () => {
Lorem Ipsum
`;
// remove the useless line breaks? Does not matter too much
expect(parseMarkdownContentTitle(markdown)).toEqual({
content: `import Component from '@site/src/components/Component';\nimport Component from '@site/src/components/Component'\nimport './styles.css';\n\n\n\nLorem Ipsum`,
content: dedent`
import Component1 from '@site/src/components/Component1';
import Component2 from '@site/src/components/Component2'
import Component3 from '@site/src/components/Component3'
import './styles.css';
Lorem Ipsum
`,
contentTitle: 'Markdown Title',
});
});
@ -213,7 +291,13 @@ describe('parseMarkdownContentTitle', () => {
`;
expect(parseMarkdownContentTitle(markdown)).toEqual({
content: `import Component from '@site/src/components/Component';\nimport Component from '@site/src/components/Component'\nimport './styles.css';\n\nLorem Ipsum`,
content: dedent`
import Component from '@site/src/components/Component';
import Component from '@site/src/components/Component'
import './styles.css';
Lorem Ipsum
`,
contentTitle: 'Markdown Title',
});
});
@ -277,20 +361,6 @@ describe('parseMarkdownContentTitle', () => {
});
describe('parseMarkdownString', () => {
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
beforeEach(() => {
warn.mockReset();
});
function expectDuplicateTitleWarning() {
expect(warn).toBeCalledWith(
expect.stringMatching(/Duplicate title found in this file/),
);
}
function expectNoWarning() {
expect(warn).not.toBeCalled();
}
test('parse markdown with frontmatter', () => {
expect(
parseMarkdownString(dedent`
@ -310,7 +380,6 @@ describe('parseMarkdownString', () => {
},
}
`);
expectNoWarning();
});
test('should parse first heading as contentTitle', () => {
@ -328,7 +397,6 @@ describe('parseMarkdownString', () => {
"frontMatter": Object {},
}
`);
expectNoWarning();
});
test('should warn about duplicate titles (frontmatter + markdown)', () => {
@ -352,7 +420,6 @@ describe('parseMarkdownString', () => {
},
}
`);
expectDuplicateTitleWarning();
});
test('should warn about duplicate titles (frontmatter + markdown alternate)', () => {
@ -377,7 +444,6 @@ describe('parseMarkdownString', () => {
},
}
`);
expectDuplicateTitleWarning();
});
test('should not warn for duplicate title if keepContentTitle=true', () => {
@ -406,7 +472,6 @@ describe('parseMarkdownString', () => {
},
}
`);
expectNoWarning();
});
test('should not warn for duplicate title if markdown title is not at the top', () => {
@ -432,7 +497,6 @@ describe('parseMarkdownString', () => {
},
}
`);
expectNoWarning();
});
test('should parse markdown title and keep it in content', () => {
@ -451,7 +515,6 @@ describe('parseMarkdownString', () => {
"frontMatter": Object {},
}
`);
expectNoWarning();
});
test('should delete only first heading', () => {
@ -477,7 +540,6 @@ describe('parseMarkdownString', () => {
"frontMatter": Object {},
}
`);
expectNoWarning();
});
test('should parse front-matter and ignore h2', () => {
@ -500,7 +562,6 @@ describe('parseMarkdownString', () => {
},
}
`);
expectNoWarning();
});
test('should read front matter only', () => {
@ -520,7 +581,6 @@ describe('parseMarkdownString', () => {
},
}
`);
expectNoWarning();
});
test('should parse title only', () => {
@ -532,7 +592,6 @@ describe('parseMarkdownString', () => {
"frontMatter": Object {},
}
`);
expectNoWarning();
});
test('should parse title only alternate', () => {
@ -549,7 +608,6 @@ describe('parseMarkdownString', () => {
"frontMatter": Object {},
}
`);
expectNoWarning();
});
test('should warn about duplicate titles', () => {
@ -570,7 +628,6 @@ describe('parseMarkdownString', () => {
},
}
`);
expectDuplicateTitleWarning();
});
test('should ignore markdown title if its not a first text', () => {
@ -588,7 +645,6 @@ describe('parseMarkdownString', () => {
"frontMatter": Object {},
}
`);
expectNoWarning();
});
test('should delete only first heading', () => {
@ -614,6 +670,5 @@ describe('parseMarkdownString', () => {
"frontMatter": Object {},
}
`);
expectNoWarning();
});
});

View file

@ -86,7 +86,7 @@ export function parseMarkdownContentTitle(
const content = contentUntrimmed.trim();
const regularTitleMatch = /^(?:import\s.*(from.*)?;?|\n)*?(?<pattern>#\s*(?<title>[^#\n]*)+[ \t]*#?\n*?)/g.exec(
const regularTitleMatch = /^(?:import\s.*(from.*)?;?|\n)*?(?<pattern>#\s*(?<title>[^#\n{]*)+[ \t]*(?<suffix>({#*[\w-]+})|#)?\n*?)/g.exec(
content,
);
const alternateTitleMatch = /^(?:import\s.*(from.*)?;?|\n)*?(?<pattern>\s*(?<title>[^\n]*)\s*\n[=]+)/g.exec(
@ -120,12 +120,10 @@ type ParsedMarkdown = {
export function parseMarkdownString(
markdownFileContent: string,
options?: {
source?: string;
keepContentTitle?: boolean;
},
): ParsedMarkdown {
try {
const sourceOption = options?.source;
const keepContentTitle = options?.keepContentTitle ?? false;
const {frontMatter, content: contentWithoutFrontMatter} = parseFrontMatter(
@ -141,20 +139,6 @@ export function parseMarkdownString(
const excerpt = createExcerpt(content);
// TODO not sure this is a good place for this warning
if (
frontMatter.title &&
contentTitle &&
!keepContentTitle &&
!(process.env.DOCUSAURUS_NO_DUPLICATE_TITLE_WARNING === 'false')
) {
console.warn(
chalk.yellow(`Duplicate title found in ${sourceOption ?? 'this'} file.
Use either a frontmatter title or a markdown title, not both.
If this is annoying you, use env DOCUSAURUS_NO_DUPLICATE_TITLE_WARNING=false`),
);
}
return {
frontMatter,
content,
@ -175,7 +159,7 @@ export async function parseMarkdownFile(
): Promise<ParsedMarkdown> {
const markdownString = await fs.readFile(source, 'utf-8');
try {
return parseMarkdownString(markdownString, {source});
return parseMarkdownString(markdownString);
} catch (e) {
throw new Error(
`Error while parsing markdown file ${source}