Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(v2): frontmatter-less: read first heading as title and use it in front-matter #4485

Merged
merged 9 commits into from
Mar 23, 2021
4 changes: 2 additions & 2 deletions packages/docusaurus-mdx-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"access": "public"
},
"scripts": {
"build": "tsc"
"build": "tsc",
"watch": "tsc --watch"
},
"repository": {
"type": "git",
Expand All @@ -27,7 +28,6 @@
"file-loader": "^6.2.0",
"fs-extra": "^9.1.0",
"github-slugger": "^1.3.0",
"gray-matter": "^4.0.2",
"loader-utils": "^2.0.0",
"mdast-util-to-string": "^2.0.0",
"remark-emoji": "^2.1.0",
Expand Down
19 changes: 11 additions & 8 deletions packages/docusaurus-mdx-loader/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {getOptions} = require('loader-utils');
const {readFile} = require('fs-extra');
const mdx = require('@mdx-js/mdx');
const emoji = require('remark-emoji');
const matter = require('gray-matter');
const {readFrontMatter} = require('@docusaurus/utils');
const stringifyObject = require('stringify-object');
const headings = require('./remark/headings');
const toc = require('./remark/toc');
Expand All @@ -24,9 +24,15 @@ const DEFAULT_OPTIONS = {

module.exports = async function docusaurusMdxLoader(fileString) {
const callback = this.async();

const {data, content} = matter(fileString);
const reqOptions = getOptions(this) || {};

const {frontMatter, content, hasFrontMatter} = readFrontMatter(
fileString,
this.resourcePath,
{},
reqOptions.removeTitleHeading,
);

const options = {
...reqOptions,
remarkPlugins: [
Expand Down Expand Up @@ -58,7 +64,7 @@ module.exports = async function docusaurusMdxLoader(fileString) {
return callback(err);
}

let exportStr = `export const frontMatter = ${stringifyObject(data)};`;
let exportStr = `export const frontMatter = ${stringifyObject(frontMatter)};`;

// Read metadata for this MDX and export it.
if (options.metadataPath && typeof options.metadataPath === 'function') {
Expand All @@ -77,10 +83,7 @@ module.exports = async function docusaurusMdxLoader(fileString) {
options.forbidFrontMatter &&
typeof options.forbidFrontMatter === 'function'
) {
if (
options.forbidFrontMatter(this.resourcePath) &&
Object.keys(data).length > 0
) {
if (options.forbidFrontMatter(this.resourcePath) && hasFrontMatter) {
return callback(new Error(`Front matter is forbidden in this file`));
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-plugin-content-blog/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ export default function pluginContentBlog(
beforeDefaultRemarkPlugins,
beforeDefaultRehypePlugins,
staticDir: path.join(siteDir, STATIC_DIR_NAME),
removeTitleHeading: true,
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
metadataPath: (mdxPath: string) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/docusaurus-plugin-content-docs/src/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function processDocMetadata({
// ex: myDoc -> .
const docsFileDirName = path.dirname(source);

const {frontMatter = {}, excerpt} = parseMarkdownString(content);
const {frontMatter = {}, excerpt} = parseMarkdownString(content, source);
const {
sidebar_label: sidebarLabel,
custom_edit_url: customEditURL,
Expand All @@ -131,16 +131,16 @@ export function processDocMetadata({
throw new Error(`Document id [${baseID}] cannot include "/".`);
}

// TODO legacy retrocompatibility
// TODO legacy retro-compatibility
// The same doc in 2 distinct version could keep the same id,
// we just need to namespace the data by version
const versionIdPart =
versionMetadata.versionName === CURRENT_VERSION_NAME
? ''
: `version-${versionMetadata.versionName}/`;

// TODO legacy retrocompatibility
// I think it's bad to affect the frontmatter id with the dirname
// TODO legacy retro-compatibility
// I think it's bad to affect the front-matter id with the dirname
const dirNameIdPart = docsFileDirName === '.' ? '' : `${docsFileDirName}/`;

// TODO legacy composite id, requires a breaking change to modify this
Expand Down
1 change: 1 addition & 0 deletions packages/docusaurus-plugin-content-docs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ export default function pluginContentDocs(
beforeDefaultRehypePlugins,
beforeDefaultRemarkPlugins,
staticDir: path.join(siteDir, STATIC_DIR_NAME),
removeTitleHeading: true,
metadataPath: (mdxPath: string) => {
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
Expand Down
68 changes: 48 additions & 20 deletions packages/docusaurus-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,43 +252,71 @@ export function createExcerpt(fileString: string): string | undefined {
}

type ParsedMarkdown = {
frontMatter: {
// Returned by gray-matter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
// Returned by gray-matter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
frontMatter: Record<string, any>;
content: string;
excerpt: string | undefined;
hasFrontMatter: boolean;
};
export function parseMarkdownString(markdownString: string): ParsedMarkdown {
const options: Record<string, unknown> = {
excerpt: (file: matter.GrayMatterFile<string>): void => {
// Hacky way of stripping out import statements from the excerpt
// TODO: Find a better way to do so, possibly by compiling the Markdown content,
// stripping out HTML tags and obtaining the first line.
file.excerpt = createExcerpt(file.content);
},
};

export function readFrontMatter(
markdownString: string,
source?: string,
options: Record<string, unknown> = {},
removeTitleHeading?: boolean,
): ParsedMarkdown {
try {
const {data: frontMatter, content, excerpt} = matter(
markdownString,
options,
);
return {frontMatter, content, excerpt};
const result = matter(markdownString, options);
result.data = result.data || {};

const hasFrontMatter = Object.keys(result.data).length > 0;

const heading = /^# (.*)[\n\r]/gi.exec(result.content.trim());
if (heading) {
if (removeTitleHeading) {
result.content = result.content.replace(heading[0], '');
}
if (result.data.title) {
console.warn(`Duplicate title detected in ${source || 'this file'}`);
} else {
result.data.title = heading[1].trim();
}
armano2 marked this conversation as resolved.
Show resolved Hide resolved
}

return {
frontMatter: result.data,
content: result.content,
excerpt: result.excerpt,
hasFrontMatter,
};
} catch (e) {
throw new Error(`Error while parsing markdown front matter.
This can happen if you use special characters like : in frontmatter values (try using "" around that value)
${e.message}`);
}
}

export function parseMarkdownString(
markdownString: string,
source?: string,
): ParsedMarkdown {
return readFrontMatter(markdownString, source, {
excerpt: (file: matter.GrayMatterFile<string>): void => {
// Hacky way of stripping out import statements from the excerpt
// TODO: Find a better way to do so, possibly by compiling the Markdown content,
// stripping out HTML tags and obtaining the first line.
file.excerpt = createExcerpt(file.content);
},
});
}

export async function parseMarkdownFile(
source: string,
): Promise<ParsedMarkdown> {
const markdownString = await fs.readFile(source, 'utf-8');
try {
return parseMarkdownString(markdownString);
return parseMarkdownString(markdownString, source);
} catch (e) {
throw new Error(
`Error while parsing markdown file ${source}
Expand Down
3 changes: 2 additions & 1 deletion website/docs/cli.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
---
id: cli
title: CLI
---

# CLI
Comment on lines 3 to +5
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can rollback this md file change as proper tests are now added

Suggested change
---
# CLI
title: CLI
---

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I think it's fine and we should dogfood this a bit too ;)


Docusaurus provides a set of scripts to help you generate, serve, and deploy your website.

Once your website is bootstrapped, the website source will contain the Docusaurus scripts that you can invoke with your package manager:
Expand Down