diff --git a/packages/docusaurus-plugin-content-pages/package.json b/packages/docusaurus-plugin-content-pages/package.json index 36c2c8d78939..8738d0b67e78 100644 --- a/packages/docusaurus-plugin-content-pages/package.json +++ b/packages/docusaurus-plugin-content-pages/package.json @@ -11,9 +11,12 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/types": "^2.0.0-alpha.56", + "@docusaurus/mdx-loader": "^2.0.0-alpha.56", "@docusaurus/utils": "^2.0.0-alpha.56", - "globby": "^10.0.1" + "@docusaurus/types": "^2.0.0-alpha.56", + "loader-utils": "^1.2.3", + "globby": "^10.0.1", + "remark-admonitions": "^1.2.1" }, "peerDependencies": { "@docusaurus/core": "^2.0.0", diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js new file mode 100644 index 000000000000..6fa02ca1028e --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/docusaurus.config.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +module.exports = { + title: 'My Site', + tagline: 'The tagline of my site', + url: 'https://your-docusaurus-test-site.com', + baseUrl: '/', + favicon: 'img/favicon.ico', +}; diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/index.md b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/index.md new file mode 100644 index 000000000000..3d83ddb74e13 --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/index.md @@ -0,0 +1,2 @@ + +Markdown index page \ No newline at end of file diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/mdxPage.mdx b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/mdxPage.mdx new file mode 100644 index 000000000000..54ce38beb5ce --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__fixtures__/website/src/pages/hello/mdxPage.mdx @@ -0,0 +1,5 @@ +--- +title: mdx page +description: my mdx page +--- +MDX page diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts index f79fa915246a..187f52b1714b 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts @@ -6,38 +6,44 @@ */ import path from 'path'; +import {loadContext} from '@docusaurus/core/lib/server'; import pluginContentPages from '../index'; -import {LoadContext} from '@docusaurus/types'; describe('docusaurus-plugin-content-pages', () => { test('simple pages', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); - const siteConfig = { - title: 'Hello', - baseUrl: '/', - url: 'https://docusaurus.io', - }; - const context = { - siteDir, - siteConfig, - } as LoadContext; + const context = loadContext(siteDir); const pluginPath = 'src/pages'; const plugin = pluginContentPages(context, { path: pluginPath, }); + const pagesMetadatas = await plugin.loadContent(); expect(pagesMetadatas).toEqual([ { + type: 'jsx', permalink: '/', source: path.join('@site', pluginPath, 'index.js'), }, { + type: 'jsx', permalink: '/typescript', source: path.join('@site', pluginPath, 'typescript.tsx'), }, { + type: 'mdx', + permalink: '/hello/', + source: path.join('@site', pluginPath, 'hello', 'index.md'), + }, + { + type: 'mdx', + permalink: '/hello/mdxPage', + source: path.join('@site', pluginPath, 'hello', 'mdxPage.mdx'), + }, + { + type: 'jsx', permalink: '/hello/world', source: path.join('@site', pluginPath, 'hello', 'world.js'), }, diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index bf9f974221b7..c6b310091093 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -8,24 +8,50 @@ import globby from 'globby'; import fs from 'fs'; import path from 'path'; -import {encodePath, fileToPath, aliasedSitePath} from '@docusaurus/utils'; -import {LoadContext, Plugin} from '@docusaurus/types'; +import { + encodePath, + fileToPath, + aliasedSitePath, + docuHash, +} from '@docusaurus/utils'; +import {LoadContext, Plugin, ConfigureWebpackUtils} from '@docusaurus/types'; +import {Configuration, Loader} from 'webpack'; +import admonitions from 'remark-admonitions'; -import {PluginOptions, LoadedContent} from './types'; +import {PluginOptions, LoadedContent, Metadata} from './types'; const DEFAULT_OPTIONS: PluginOptions = { path: 'src/pages', // Path to data on filesystem, relative to site dir. routeBasePath: '', // URL Route. - include: ['**/*.{js,jsx,ts,tsx}'], // Extensions to include. + include: ['**/*.{js,jsx,ts,tsx,md,mdx}'], // Extensions to include. + mdxPageComponent: '@theme/MDXPage', + remarkPlugins: [], + rehypePlugins: [], + admonitions: {}, }; +const isMarkdownSource = (source: string) => + source.endsWith('.md') || source.endsWith('.mdx'); + export default function pluginContentPages( context: LoadContext, opts: Partial, ): Plugin { - const options = {...DEFAULT_OPTIONS, ...opts}; + const options: PluginOptions = {...DEFAULT_OPTIONS, ...opts}; + if (options.admonitions) { + options.remarkPlugins = options.remarkPlugins.concat([ + [admonitions, opts.admonitions || {}], + ]); + } + const contentPath = path.resolve(context.siteDir, options.path); + const {siteDir, generatedFilesDir} = context; + const dataDir = path.join( + generatedFilesDir, + 'docusaurus-plugin-content-pages', + ); + return { name: 'docusaurus-plugin-content-pages', @@ -35,6 +61,16 @@ export default function pluginContentPages( return [...globPattern]; }, + getClientModules() { + const modules = []; + + if (options.admonitions) { + modules.push(require.resolve('remark-admonitions/styles/infima.css')); + } + + return modules; + }, + async loadContent() { const {include} = options; const {siteConfig, siteDir} = context; @@ -49,16 +85,27 @@ export default function pluginContentPages( cwd: pagesDir, }); - return pagesFiles.map((relativeSource) => { + function toMetadata(relativeSource: string): Metadata { const source = path.join(pagesDir, relativeSource); const aliasedSource = aliasedSitePath(source, siteDir); const pathName = encodePath(fileToPath(relativeSource)); - // Default Language. - return { - permalink: pathName.replace(/^\//, baseUrl || ''), - source: aliasedSource, - }; - }); + const permalink = pathName.replace(/^\//, baseUrl || ''); + if (isMarkdownSource(relativeSource)) { + return { + type: 'mdx', + permalink, + source: aliasedSource, + }; + } else { + return { + type: 'jsx', + permalink, + source: aliasedSource, + }; + } + } + + return pagesFiles.map(toMetadata); }, async contentLoaded({content, actions}) { @@ -66,18 +113,85 @@ export default function pluginContentPages( return; } - const {addRoute} = actions; + const {addRoute, createData} = actions; await Promise.all( - content.map(async (metadataItem) => { - const {permalink, source} = metadataItem; - addRoute({ - path: permalink, - component: source, - exact: true, - }); + content.map(async (metadata) => { + const {permalink, source} = metadata; + if (metadata.type === 'mdx') { + await createData( + // Note that this created data path must be in sync with + // metadataPath provided to mdx-loader. + `${docuHash(metadata.source)}.json`, + JSON.stringify(metadata, null, 2), + ); + addRoute({ + path: permalink, + component: options.mdxPageComponent, + exact: true, + modules: { + content: source, + }, + }); + } else { + addRoute({ + path: permalink, + component: source, + exact: true, + }); + } }), ); }, + + configureWebpack( + _config: Configuration, + isServer: boolean, + {getBabelLoader, getCacheLoader}: ConfigureWebpackUtils, + ) { + const {rehypePlugins, remarkPlugins} = options; + return { + resolve: { + alias: { + '~pages': dataDir, + }, + }, + module: { + rules: [ + { + test: /(\.mdx?)$/, + include: [contentPath], + use: [ + getCacheLoader(isServer), + getBabelLoader(isServer), + { + loader: require.resolve('@docusaurus/mdx-loader'), + options: { + remarkPlugins, + rehypePlugins, + // Note that metadataPath must be the same/in-sync as + // the path from createData for each MDX. + metadataPath: (mdxPath: string) => { + const aliasedSource = aliasedSitePath(mdxPath, siteDir); + return path.join( + dataDir, + `${docuHash(aliasedSource)}.json`, + ); + }, + }, + }, + { + loader: path.resolve(__dirname, './markdownLoader.js'), + options: { + // siteDir, + // contentPath, + }, + }, + ].filter(Boolean) as Loader[], + }, + ], + }, + }; + }, }; } diff --git a/packages/docusaurus-plugin-content-pages/src/markdownLoader.ts b/packages/docusaurus-plugin-content-pages/src/markdownLoader.ts new file mode 100644 index 000000000000..94fb1539904c --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/markdownLoader.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {loader} from 'webpack'; +import {getOptions} from 'loader-utils'; + +const markdownLoader: loader.Loader = function (fileString) { + const callback = this.async(); + const {} = getOptions(this); + + // TODO provide additinal md processing here? like interlinking pages? + // fileString = linkify(fileString) + + return callback && callback(null, fileString); +}; + +export default markdownLoader; diff --git a/packages/docusaurus-plugin-content-pages/src/types.ts b/packages/docusaurus-plugin-content-pages/src/types.ts index 74e47d170787..9c9e2706d996 100644 --- a/packages/docusaurus-plugin-content-pages/src/types.ts +++ b/packages/docusaurus-plugin-content-pages/src/types.ts @@ -9,11 +9,24 @@ export interface PluginOptions { path: string; routeBasePath: string; include: string[]; + mdxPageComponent: string; + remarkPlugins: ([Function, object] | Function)[]; + rehypePlugins: string[]; + admonitions: any; } -export interface Metadata { +export type JSXPageMetadata = { + type: 'jsx'; permalink: string; source: string; -} +}; + +export type MDXPageMetadata = { + type: 'mdx'; + permalink: string; + source: string; +}; + +export type Metadata = JSXPageMetadata | MDXPageMetadata; export type LoadedContent = Metadata[]; diff --git a/packages/docusaurus-plugin-content-pages/types.d.ts b/packages/docusaurus-plugin-content-pages/types.d.ts new file mode 100644 index 000000000000..c0c9f3a8ec03 --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/types.d.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +declare module 'remark-admonitions' { + type Options = any; + + const plugin: (options?: Options) => void; + export = plugin; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.js b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.js new file mode 100644 index 000000000000..e58723a01a13 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Layout from '@theme/Layout'; +import {MDXProvider} from '@mdx-js/react'; +import MDXComponents from '@theme/MDXComponents'; + +function MDXPage(props) { + const {content: MDXPageContent} = props; + const {frontMatter, metadata} = MDXPageContent; + const {title, description} = frontMatter; + const {permalink} = metadata; + + return ( + +
+
+ + + +
+
+
+ ); +} + +export default MDXPage; diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index 69076f3985e5..155ce0cb0d94 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -59,8 +59,8 @@ export function objectWithKeySorted(obj: {[index: string]: any}) { }, {}); } -const indexRE = /(^|.*\/)index\.(md|js|jsx|ts|tsx)$/i; -const extRE = /\.(md|js|tsx)$/; +const indexRE = /(^|.*\/)index\.(md|mdx|js|jsx|ts|tsx)$/i; +const extRE = /\.(md|mdx|js|tsx)$/; /** * Convert filepath to url path. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 43c06f7943ef..ceb59a78cebc 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -74,6 +74,9 @@ module.exports = { copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`, }, }, + pages: { + remarkPlugins: [require('./src/plugins/remark-npm2yarn')], + }, theme: { customCss: require.resolve('./src/css/custom.css'), }, diff --git a/website/src/pages/hey/index.md b/website/src/pages/hey/index.md new file mode 100644 index 000000000000..b45194aa8490 --- /dev/null +++ b/website/src/pages/hey/index.md @@ -0,0 +1,12 @@ +--- +title: custom title +description: custom description +--- + +# Test markdown page + +**hello** world + +[test link with pathname](/hey/test) + +[test link with relative file path](./test.mdx) diff --git a/website/src/pages/hey/test.mdx b/website/src/pages/hey/test.mdx new file mode 100644 index 000000000000..9d304f11d69a --- /dev/null +++ b/website/src/pages/hey/test.mdx @@ -0,0 +1,937 @@ +--- +title: custom title +description: custom description +--- + +# Test markdown page + +**hello** world + +Documentation is one of your product's interfaces with your users. A well-written and well-organized set of docs helps your users understand your product quickly. Our aligned goal here is to help your users find and understand the information they need, as quickly as possible. + +Docusaurus 2 uses modern tooling to help you compose your interactive documentations with ease. You may embed React components, or build live coding blocks where your users may play with the code on the spot. Start sharing your eureka moments with the code your audience cannot walk away from. It is perhaps the most effective way of attracting potential users. + +In this section, we'd like to introduce you to the tools we've picked that we believe will help you build powerful documentation. Let us walk you through with an example. + +:::important + +All the following content assumes you are using `@docusaurus/preset-classic` or `@docusaurus/plugin-content-docs`. + +::: + +--- + +Markdown is a syntax that enables you to write formatted content in a readable syntax. The [standard Markdown syntax](https://daringfireball.net/projects/markdown/syntax) is supported and we use [MDX](https://mdxjs.com/) as the parsing engine, which can do much more than just parsing Markdown. More on that later. + +Create a markdown file, `greeting.md`, and place it under the `docs` directory. + +```bash +website # root directory of your site +├── docs +│   └── greeting.md +├── src +│   └── pages +├── docusaurus.config.js +├── ... +``` + + + +At the top of the file, specify `id` and `title` in the front matter, so that Docusaurus will pick them up correctly when generating your site. + +```yml +--- +id: greeting +title: Hello +--- + +## Hello from Docusaurus + +Are you ready to create the documentation site for your open source project? + +### Headers + +will show up on the table of contents on the upper right + +So that your users will know what this page is all about without scrolling down or even without reading too much. + +### Only h2 and h3 will be in the toc + +The headers are well-spaced so that the hierarchy is clear. + +- lists will help you +- present the key points +- that you want your users to remember + - and you may nest them + - multiple times +``` + +This will render in the browser as follows: + +import BrowserWindow from '@site/src/components/BrowserWindow'; + + + +

Hello from Docusaurus

+ +Are you ready to create the documentation site for your open source project? + +

Headers

+ +will show up on the table of contents on the upper right + +So that your users will know what this page is all about without scrolling down or even without reading too much. + +

Only h2 and h3 will be in the toc

+ +The headers are well-spaced so that the hierarchy is clear. + +- lists will help you +- present the key points +- that you want your users to remember + - and you may nest them + - multiple times + +
+ +## Markdown headers + +Documents use the following markdown header fields that are enclosed by a line `---` on either side: + +- `id`: A unique document id. If this field is not present, the document's `id` will default to its file name (without the extension). +- `title`: The title of your document. If this field is not present, the document's `title` will default to its `id`. +- `hide_title`: Whether to hide the title at the top of the doc. By default it is `false`. +- `hide_table_of_contents`: Whether to hide the table of contents to the right. By default it is `false`. +- `sidebar_label`: The text shown in the document sidebar and in the next/previous button for this document. If this field is not present, the document's `sidebar_label` will default to its `title`. +- `custom_edit_url`: The URL for editing this document. If this field is not present, the document's edit URL will fall back to `editUrl` from options fields passed to `docusaurus-plugin-content-docs`. +- `keywords`: Keywords meta tag for the document page, for search engines. +- `description`: The description of your document, which will become the `` and `` in ``, used by search engines. If this field is not present, it will default to the first line of the contents. +- `image`: Cover or thumbnail image that will be used when displaying the link to your post. + +Example: + +```yml +--- +id: doc-markdown +title: Markdown Features +hide_title: false +hide_table_of_contents: false +sidebar_label: Markdown :) +custom_edit_url: https://github.com/facebook/docusaurus/edit/master/docs/api-doc-markdown.md +description: How do I find you when I cannot solve this problem +keywords: + - docs + - docusaurus +image: https://i.imgur.com/mErPwqL.png +--- + +``` + +## Referencing other documents + +If you want to reference another document file, you could use the name of the document you want to reference. Docusaurus will convert the file path to be the final website path (and remove the `.md`). + +For example, if you are in `doc2.md` and you want to reference `doc1.md` and `folder/doc3.md`: + +```md +I am referencing a [document](doc1.md). Reference to another [document in a folder](folder/doc3.md). + +[Relative document](../doc2.md) referencing works as well. +``` + +One benefit of this approach is that the links to external files will still work if you are viewing the file on GitHub. + +## Embedding React components with MDX + +Docusaurus has built-in support for [MDX](https://mdxjs.com/), which allows you to write JSX within your Markdown files and render them as React components. + +**Note 1:** While both `.md` and `.mdx` files are parsed using MDX, some of the syntax are treated slightly differently. For the most accurate parsing and better editor support, we recommend using the `.mdx` extension for files containing MDX syntax. Let's rename the previous file to `greeting.mdx`. + +**Note 2:** Since all doc files are parsed using MDX, any HTML is treated as JSX. Therefore, if you need to inline-style a component, follow JSX flavor and provide style objects. This behavior is different from Docusaurus 1. See also [Migrating from v1 to v2](migrating-from-v1-to-v2.md#convert-style-attributes-to-style-objects-in-mdx). + +Try this block here: + +```jsx +export const Highlight = ({children, color}) => ( + + {children} + +); + +Docusaurus green and Facebook blue are my favorite colors. + +I can write **Markdown** alongside my _JSX_! +``` + +Notice how it renders both the markup from your React component and the Markdown syntax: + +export const Highlight = ({children, color}) => ( + + {children} + +); + + + +Docusaurus green +{` `}and Facebook blue are my favorite colors. + +I can write **Markdown** alongside my _JSX_! + + + +
+ +You can also import your own components defined in other files or third-party components installed via npm! Check out the [MDX docs](https://mdxjs.com/) to see what other fancy stuff you can do with MDX. + +### Configuring plugins + +You can expand the MDX functionalities, using plugins. An MDX plugin is usually a npm package, so you install them like other npm packages using npm. Docusaurus supports both [Remark](https://github.com/remarkjs/remark) and [Rehype](https://github.com/rehypejs/rehype) plugins that work with MDX. + +First, install your [Remark](https://github.com/remarkjs/remark/blob/master/doc/plugins.md#list-of-plugins) and [Rehype](https://github.com/rehypejs/rehype/blob/master/doc/plugins.md#list-of-plugins) plugins. + +For example: + +```bash npm2yarn +npm install --save remark-images +npm install --save rehype-truncate +``` + +Next, import the plugins: + +```js +const remarkImages = require('remark-images'); +const rehypeTruncate = require('rehype-truncate'); +``` + +Finally, add them to the `@docusaurus/preset-classic` options in `docusaurus.config.js`: + +```js {10,11} title="docusaurus.config.js" +module.exports = { + // ... + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + sidebarPath: require.resolve('./sidebars.js'), + // ... + remarkPlugins: [remarkImages], + rehypePlugins: [rehypeTruncate], + }, + }, + ], + ], +}; +``` + +### Configuring plugin options + +Some plugins can be configured and accept their own options. In that case, use the `[plugin, pluginOptions]` syntax, like so: + +```jsx {10-13} title="docusaurus.config.js" +module.exports = { + // ... + presets: [ + [ + '@docusaurus/preset-classic', + { + docs: { + sidebarPath: require.resolve('./sidebars.js'), + // ... + remarkPlugins: [ + plugin1, + [plugin2, {option1: {...}}], + ], + }, + }, + ], + ], +}; +``` + +See more information in the [MDX documentation](https://mdxjs.com/advanced/plugins). + +## Tabs + +To show tabbed content within Markdown files, you can fall back on MDX. Docusaurus provides `` components out-of-the-box. + +```jsx +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + This is an apple 🍎 + This is an orange 🍊 + This is a banana 🍌 +; +``` + +will result in + + + This is an apple 🍎 + This is an orange 🍊 + This is a banana 🍌 + + +### Syncing tab choices + +You may want choices of the same kind of tabs to sync with each other. For example, you might want to provide different instructions for users on Windows vs users on macOS, and you want to changing all OS-specific instructions tabs in one click. To achieve that, you can give all related tabs the same `groupId` prop. Note that doing this will persist the choice in `localStorage` and all `` instances with the same `groupId` will update automatically when the value of one of them is changed. Not that `groupID` are globally-namespaced. + +```jsx {2,14} + +Use Ctrl + C to copy. +Use Command + C to copy. + + + +Use Ctrl + V to paste. +Use Command + V to paste. + +``` + + + Use Ctrl + C to copy. + Use Command + C to copy. + + + + Use Ctrl + V to paste. + Use Command + V to paste. + + +For all tab groups that have the same `groupId`, the possible values do not need to be the same. If one tab group with chooses an value that does not exist in another tab group with the same `groupId`, the tab group with the missing value won't change its tab. You can see that from the following example. Try to select Linux, and the above tab groups doesn't change. + +```jsx + + I am Windows. + I am macOS. + I am Linux. + +``` + + + I am Windows. + I am macOS. + I am Linux. + + +--- + +Tab choices with different `groupId`s will not interfere with each other: + +```jsx {2,14} + +Windows in windows. +macOS is macOS. + + + +Windows is windows. +Unix is unix. + +``` + + + Windows in windows. + macOS is macOS. + + + + Windows is windows. + Unix is unix. + + +## Callouts/admonitions + +In addition to the basic Markdown syntax, we use [remark-admonitions](https://github.com/elviswolcott/remark-admonitions) alongside MDX to add support for admonitions. Admonitions are wrapped by a set of 3 colons. + +Example: + + :::note + The content and title *can* include markdown. + ::: + + :::tip You can specify an optional title + Heads up! Here's a pro-tip. + ::: + + :::info + Useful information. + ::: + + :::caution + Warning! You better pay attention! + ::: + + :::danger + Danger danger, mayday! + ::: + +:::note + +The content and title _can_ include markdown. + +::: + +:::tip You can specify an optional title + +Heads up! Here's a pro-tip. + +::: + +:::info + +Useful information. + +::: + +:::caution + +Warning! You better pay attention! + +::: + +:::danger + +Danger danger, mayday! + +::: + +### Specifying title + +You may also specify an optional title + + :::note Your Title + The content and title *can* include markdown. + ::: + +:::note Your Title + +The content and title _can_ include Markdown. + +::: + +## Code blocks + +Code blocks within documentation are super-powered 💪. + +### Code title + +You can add a title to the code block by adding `title` key after the language (leave a space between them). + + ```jsx title="/src/components/HelloCodeTitle.js" + function HelloCodeTitle(props) { + return

Hello, {props.name}

; + } + ``` + +```jsx title="/src/components/HelloCodeTitle.js" +function HelloCodeTitle(props) { + return

Hello, {props.name}

; +} +``` + +### Syntax highlighting + +Code blocks are text blocks wrapped around by strings of 3 backticks. You may check out [this reference](https://github.com/mdx-js/specification) for specifications of MDX. + + ```jsx + console.log('Every repo must come with a mascot.'); + ``` + + + +Use the matching language meta string for your code block, and Docusaurus will pick up syntax highlighting automatically, powered by [Prism React Renderer](https://github.com/FormidableLabs/prism-react-renderer). + +```jsx +console.log('Every repo must come with a mascot.'); +``` + +By default, the Prism [syntax highlighting theme](https://github.com/FormidableLabs/prism-react-renderer#theming) we use is [Palenight](https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/themes/palenight.js). You can change this to another theme by passing `theme` field in `prism` as `themeConfig` in your docusaurus.config.js. + +For example, if you prefer to use the `dracula` highlighting theme: + +```js {4} title="docusaurus.config.js" +module.exports = { + themeConfig: { + prism: { + theme: require('prism-react-renderer/themes/dracula'), + }, + }, +}; +``` + +By default, Docusaurus comes with this subset of [commonly used languages](https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/vendor/prism/includeLangs.js). + +To add syntax highlighting for any of the other [Prism supported languages](https://prismjs.com/#supported-languages), define it in an array of additional languages. + +For example, if you want to add highlighting for the `powershell` language: + +```js {5} title="docusaurus.config.js" +module.exports = { + // ... + themeConfig: { + prism: { + additionalLanguages: ['powershell'], + }, + // ... + }, +}; +``` + +If you want to add highlighting for languages not yet supported by Prism, you can swizzle `prism-include-languages`: + +```bash npm2yarn +yarn swizzle @docusaurus/theme-classic prism-include-languages +``` + +It will produce `prism-include-languages.js` in your `src/theme` folder. You can add highlighting support for custom languages by editing `prism-include-languages.js`: + +```js {8} title="src/theme/prism-include-languages.js" +const prismIncludeLanguages = (Prism) => { + // ... + + additionalLanguages.forEach((lang) => { + require(`prismjs/components/prism-${lang}`); // eslint-disable-line + }); + + require('/path/to/your/prism-language-definition'); + + // ... +} +``` + +You can refer to [Prism's official language definitions](https://github.com/PrismJS/prism/tree/master/components) when you are writing your own language definitions. + +### Line highlighting + +You can bring emphasis to certain lines of code by specifying line ranges after the language meta string (leave a space after the language). + + ```jsx {3} + function HighlightSomeText(highlight) { + if (highlight) { + return 'This text is highlighted!'; + } + + return 'Nothing highlighted'; + } + ``` + +```jsx {3} +function HighlightSomeText(highlight) { + if (highlight) { + return 'This text is highlighted!'; + } + + return 'Nothing highlighted'; +} +``` + +To accomplish this, Docusaurus adds the `docusaurus-highlight-code-line` class to the highlighted lines. You will need to define your own styling for this CSS, possibly in your `src/css/custom.css` with a custom background color which is dependent on your selected syntax highlighting theme. The color given below works for the default highlighting theme (Palenight), so if you are using another theme, you will have to tweak the color accordingly. + +```css title="/src/css/custom.css" +.docusaurus-highlight-code-line { + background-color: rgb(72, 77, 91); + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); +} + +/* If you have a different syntax highlighting theme for dark mode. */ +html[data-theme='dark'] .docusaurus-highlight-code-line { + background-color: ; /* Color which works with dark mode syntax highlighting theme */ +} +``` + +To highlight multiple lines, separate the line numbers by commas or use the range syntax to select a chunk of lines. This feature uses the `parse-number-range` library and you can find [more syntax](https://www.npmjs.com/package/parse-numeric-range) on their project details. + + ```jsx {1,4-6,11} + import React from 'react'; + + function MyComponent(props) { + if (props.isBar) { + return
Bar
; + } + + return
Foo
; + } + + export default MyComponent; + ``` + +```jsx {1,4-6,11} +import React from 'react'; + +function MyComponent(props) { + if (props.isBar) { + return
Bar
; + } + + return
Foo
; +} + +export default MyComponent; +``` + +You can also use comments with `highlight-next-line`, `highlight-start`, and `highlight-end` to select which lines are highlighted. + + ```jsx + function HighlightSomeText(highlight) { + if (highlight) { + // highlight-next-line + return 'This text is highlighted!'; + } + + return 'Nothing highlighted'; + } + + function HighlightMoreText(highlight) { + // highlight-start + if (highlight) { + return 'This range is highlighted!'; + } + // highlight-end + + return 'Nothing highlighted'; + } + ``` + +```jsx +function HighlightSomeText(highlight) { + if (highlight) { + // highlight-next-line + return 'This text is highlighted!'; + } + + return 'Nothing highlighted'; +} + +function HighlightMoreText(highlight) { + // highlight-start + if (highlight) { + return 'This range is highlighted!'; + } + // highlight-end + + return 'Nothing highlighted'; +} +``` + +Supported commenting syntax: + +| Language | Syntax | +| ---------- | ------------------------ | +| JavaScript | `/* ... */` and `// ...` | +| JSX | `{/* ... */}` | +| Python | `# ...` | +| HTML | `` | + +If there's a syntax that is not currently supported, we are open to adding them! Pull requests welcome. + +### Interactive code editor + +(Powered by [React Live](https://github.com/FormidableLabs/react-live)) + +You can create an interactive coding editor with the `@docusaurus/theme-live-codeblock` plugin. + +First, add the plugin to your package. + +```bash npm2yarn +npm install --save @docusaurus/theme-live-codeblock +``` + +You will also need to add the plugin to your `docusaurus.config.js`. + +```js {3} +module.exports = { + // ... + themes: ['@docusaurus/theme-live-codeblock'], + // ... +}; +``` + +To use the plugin, create a code block with `live` attached to the language meta string. + + ```jsx live + function Clock(props) { + const [date, setDate] = useState(new Date()); + useEffect(() => { + var timerID = setInterval(() => tick(), 1000); + + return function cleanup() { + clearInterval(timerID); + }; + }); + + function tick() { + setDate(new Date()); + } + + return ( +
+

It is {date.toLocaleTimeString()}.

+
+ ); + } + ``` + +The code block will be rendered as an interactive editor. Changes to the code will reflect on the result panel live. + +```jsx live +function Clock(props) { + const [date, setDate] = useState(new Date()); + useEffect(() => { + var timerID = setInterval(() => tick(), 1000); + + return function cleanup() { + clearInterval(timerID); + }; + }); + + function tick() { + setDate(new Date()); + } + + return ( +
+

It is {date.toLocaleTimeString()}.

+
+ ); +} +``` + +:::caution react-live and imports + +It is not possible to import components directly from the react-live code editor, you have to define available imports upfront. + +::: + +By default, all React imports are available. If you need more imports available, swizzle the react-live scope: + +```bash npm2yarn +npm run swizzle @docusaurus/theme-live-codeblock ReactLiveScope +``` + +```jsx {3-15,21} title="src/theme/ReactLiveScope/index.js" +import React from 'react'; + +const ButtonExample = (props) => ( +