From 8d75c5d446a6d8ff189b95752520e9616c3dd970 Mon Sep 17 00:00:00 2001 From: Leah Velleman Date: Wed, 26 Oct 2022 17:05:23 -0400 Subject: [PATCH 1/2] IA changes, and making sure full articles are more informative than syntax.md --- components/Shell/SideNav.js | 24 +- pages/docs/attributes.md | 65 +++++ pages/docs/configs.md | 94 +++++++ .../{examples/index.md => customization.md} | 2 +- pages/docs/syntax.md | 234 ++++-------------- pages/docs/tags.md | 19 ++ 6 files changed, 238 insertions(+), 200 deletions(-) create mode 100644 pages/docs/configs.md rename pages/docs/{examples/index.md => customization.md} (99%) diff --git a/components/Shell/SideNav.js b/components/Shell/SideNav.js index 4b16cf02..d6a7c1e3 100644 --- a/components/Shell/SideNav.js +++ b/components/Shell/SideNav.js @@ -13,7 +13,7 @@ const items = [ ] }, { - title: 'Core concepts', + title: 'Core features', links: [ { href: '/docs/syntax', children: 'Syntax and schema' }, { href: '/docs/nodes', children: 'Nodes' }, @@ -21,29 +21,27 @@ const items = [ { href: '/docs/attributes', children: 'Attributes' }, { href: '/docs/variables', children: 'Variables' }, { href: '/docs/functions', children: 'Functions' }, - { - href: '/docs/render', - children: 'Rendering' - }, + { href: '/docs/configs', children: 'Configuration'}, + ] + }, + { + title: 'Techniques', + links: [ + { href: '/docs/render', children: 'Rendering' }, + { href: '/docs/customization', children: 'Customization examples' }, + { href: '/docs/frontmatter', children: 'Frontmatter' }, + { href: '/docs/partials', children: 'Partials' }, { href: '/docs/validation', children: 'Validation' } ] }, { title: 'Integration guides', links: [ - { href: '/docs/examples', children: 'Common examples' }, { href: '/docs/examples/html', children: 'Using with HTML' }, { href: '/docs/nextjs', children: 'Using with Next.js' }, { href: '/docs/examples/react', children: 'Using with React' } ] }, - { - title: 'Advanced concepts', - links: [ - { href: '/docs/frontmatter', children: 'Frontmatter' }, - { href: '/docs/partials', children: 'Partials' } - ] - } ]; export function SideNav() { diff --git a/pages/docs/attributes.md b/pages/docs/attributes.md index a93b6e08..ba7111aa 100644 --- a/pages/docs/attributes.md +++ b/pages/docs/attributes.md @@ -7,6 +7,71 @@ description: Attributes are used to pass data to tags in Markdoc. Attributes let you pass data to Markdoc tags, similar to [HTML attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes) or [React props](https://reactjs.org/docs/components-and-props.html). +You can pass values of type: `number`, `string`, `boolean`, JSON `array`, or JSON `object`, either directly or using [variables](/docs/variables). With a tag, you can use HTML-like syntax. + +{% markdoc-example %} + +``` +{% city + index=0 + name="San Francisco" + deleted=false + coordinates=[1, 4, 9] + meta={id: "id_123"} + color=$color /%} +``` + +{% /markdoc-example %} + +To pass attributes to a node, you can't use the HTML-like syntax. Instead, use _annotation_ syntax. Put the attributes after the node, in their own set of `{%` and `%}`. + +{% markdoc-example %} + +``` +{% table %} + +- Function {% width="25%" %} +- Returns {% colspan=2 %} +- Example {% align=$side %} + +{% /table %} +``` + +{% /markdoc-example %} + +(Annotation syntax also works with tags. But it's required with nodes.) + +Strings within attributes must be double-quoted. If you want to include a literal double-quote in a string you can escape it with using \\". + +{% markdoc-example %} + +``` {% process=false %} +{% data delimiter="\"" %} +``` + +{% /markdoc-example %} + + +## Attribute shorthand + + +In either syntax, you can use `.my-class-name` and `#my-id` as shorthand for `class=my-class-name` and `id=my-id`. + +{% markdoc-example %} + +``` {% process=false %} +# Examples {% #examples %} + +{% table .striped #exampletable %} +- One +- Two +- Three +{% /table %} +``` + +{% /markdoc-example %} + + ## Defining attributes Markdoc lets you configure custom attribute types for each [tag](/docs/tags). Assigning a type to an attribute limits which values an attribute can pass to a tag and, as a result, which values create errors during [validation](/docs/validation). diff --git a/pages/docs/configs.md b/pages/docs/configs.md new file mode 100644 index 00000000..c6c4858c --- /dev/null +++ b/pages/docs/configs.md @@ -0,0 +1,94 @@ +--- +title: Configuration +description: Config objects let you pass options to Markdoc.transform. +--- + +# {% $markdoc.frontmatter.title %} + +This table outlines the various options you can pass to `Markdoc.transform`. Each option adjusts how a document is [transformed](/docs/render#transform) and [rendered](/docs/render#render). + +{% table %} + +- Key +- Type +- Description + +--- + +- [`nodes`](/docs/nodes) +- {% code %}{ [nodeType: [NodeType](/docs/nodes#built-in-nodes)]: [Schema](https://github.com/markdoc/markdoc/blob/6bcb8a0c48a181ca9df577534d841280646cea09/src/types.ts#L94-L101) }{% /code%} +- Register [custom nodes](/docs/nodes) in your schema + +--- + +- [`tags`](/docs/tags) +- {% code %}{ [tagName: string]: [Schema](https://github.com/markdoc/markdoc/blob/6bcb8a0c48a181ca9df577534d841280646cea09/src/types.ts#L94-L101) }{% /code%} +- Register [custom tags](/docs/tags) in your schema + +--- + +- [`variables`](/docs/variables) +- `{ [variableName: string]: any }` +- Register [variables](/docs/variables) to use in your document + +--- + +- [`functions`](/docs/functions) +- {% code %}{ [functionName: string]: [ConfigFunction](https://github.com/markdoc/markdoc/blob/6bcb8a0c48a181ca9df577534d841280646cea09/src/types.ts#L31-L36) }{% /code %} +- Register [custom functions](/docs/functions) to use in your document + +--- + +- [`partials`](/docs/partials) +- `{ [partialPath: string]: Ast.Node }` +- Register reusable pieces of content to used by the [`partial` tag](/docs/partials) + +{% /table %} + +### Full example + +Here's an example of what a Markdoc config would look like: + +```js +const config = { + nodes: { + heading: { + render: 'Heading', + attributes: { + id: { type: String }, + level: { type: Number } + } + } + }, + tags: { + callout: { + render: 'Callout', + attributes: { + title: { + type: String + } + } + } + }, + variables: { + name: 'Dr. Mark', + frontmatter: { + title: 'Configuration options' + } + }, + functions: { + includes: { + transform(parameters, config) { + const [array, value] = Object.values(parameters); + + return Array.isArray(array) ? array.includes(value) : false; + } + } + }, + partials: { + 'header.md': Markdoc.parse(`# My header`) + } +}; + +const content = Markdoc.transform(ast, config); +``` diff --git a/pages/docs/examples/index.md b/pages/docs/customization.md similarity index 99% rename from pages/docs/examples/index.md rename to pages/docs/customization.md index cd808288..1ff04772 100644 --- a/pages/docs/examples/index.md +++ b/pages/docs/customization.md @@ -1,5 +1,5 @@ --- -title: Common examples +title: Customization examples description: --- diff --git a/pages/docs/syntax.md b/pages/docs/syntax.md index 87c3b562..25d415ee 100644 --- a/pages/docs/syntax.md +++ b/pages/docs/syntax.md @@ -10,7 +10,7 @@ For a formal grammar of the Markdoc tag syntax, refer to the [Markdoc syntax spe ## Nodes -Nodes are elements that Markdoc inherits from Markdown, which you can customize with [annotations](#annotations). +Nodes are elements that Markdoc inherits from Markdown. {% side-by-side %} @@ -25,8 +25,6 @@ _Italic_ [Links](/docs/nodes) -![Images](/logo.svg) - Lists - Item 1 - Item 1 @@ -67,130 +65,65 @@ Code fences {% /side-by-side %} -\ +You can't change Markdoc syntax. But you can create custom nodes that change how this syntax is rendered. For instance, write a custom `header` node to make `# Header` produce a different result. + For more information, check out the [Nodes docs](/docs/nodes). ## Tags -Tags are the main syntactic extension that Markdoc adds on top of Markdown. Each tag is enclosed with `{%` and `%}`, and includes the tag name, [attributes](#attributes), and the content body. - -Similar to HTML, you can nest Markdoc tags, and customize them with [attributes](#attributes). - -{% markdoc-example %} - -``` -{% tag %} -Content -{% /tag %} -``` - -{% /markdoc-example %} - -Tags can be self-closing (similar to HTML). In this example, you'll see that the content body is removed, and that the tag is closed with a `/`. - -{% markdoc-example %} - -``` -{% image width=40 /%} -``` - -{% /markdoc-example %} - -If your tag doesn't contain any new lines, then it's treated as an inline tag. Inline tags are automatically wrapped with a single `paragraph` [Node](/docs/nodes) (which renders a `

` element by default), to follow the [CommonMark paragraph spec](https://spec.commonmark.org/0.30/#paragraphs). +Markdoc extends Markdown syntax with tags. Each tag is enclosed with `{%` and `%}`, and includes the tag name, [attributes](#attributes), and the content body. Similar to HTML, most tags can be nested, and tags with no content can be self-closing {% markdoc-example %} ``` -{% code %} - -{% highlight %}Inline tag 1{% /highlight %} -{% highlight %}Inline tag 2{% /highlight %} - -{% /code %} +{% alert %} +{% icon src="alert.png" /%} +An error occurred. +{% /alert %} ``` {% /markdoc-example %} -\ -For more information, check out the [Tags docs](/docs/tags). - -## Annotations - -Customize how individual nodes are rendered with annotations. Annotations are useful when passing properties to customize the output, such as an `id` or `class`. You can also use annotations to apply [attributes](#attributes) to HTML and React elements. +The built-in tags are `table`, `if`, `else`, and `partial`. You can extend Markdoc by writing custom tags. -You can access annotation values as [attributes](/docs/attributes) within your schema [`transform`](/docs/nodes#customizing-markdoc-nodes) functions. -\ -To add an `id`, you can use this syntax: +For more information, check out the [Tags docs](/docs/tag). -{% markdoc-example %} - -``` -# Header {% #custom-id %} -``` +## Attributes -{% /markdoc-example %} +Pass attributes to nodes and tags to customize their behavior. You can pass values of type: `number`, `string`, `boolean`, JSON `array`, or JSON `object`, either directly or using [variables](#variables). -To set a `class`, use class syntax: +With tags, you can use an HTML-like syntax: {% markdoc-example %} ``` -# Heading {% .custom-class-name-here %} -``` - -{% /markdoc-example %} - -which also works within your tags. - -{% markdoc-example %} - -```md -{% section #id .class %} - -My section - -{% /section %} +{% city + index=0 + name="San Francisco" + deleted=false + coordinates=[1, 4, 9] + meta={id: "id_123"} + color=$color /%} ``` {% /markdoc-example %} -You can also set [attributes](#attributes) on a node, such as `width` or `colspan`. +Because the HTML-like syntax doesn't work with nodes, we offer another option: write the attributes after the tag or node you're passing them to, in a separate set of `{%` and `%}`. {% markdoc-example %} ``` {% table %} - -- Function {% width="25%" %} -- Returns {% colspan=2 %} -- Example {% align="right" %} - +* Cell +* Cell +--- +* Cell {% colspan=2 %} {% /table %} ``` {% /markdoc-example %} -## Attributes - -Pass attributes to tags to customize their behavior. You can pass values of type: `number`, `string`, `boolean`, JSON `array`, or JSON `object`. - -{% markdoc-example %} - -``` -{% city - index=0 - name="San Francisco" - deleted=false - coordinates=[1, 4, 9] - meta={id: "id_123"} /%} -``` - -{% /markdoc-example %} - -All Markdoc strings use double-quotes. This includes when passing a string as an attribute or as a [function](#functions) parameter. -If you want to include a double-quote in a string you can escape it with using `\"`. - \ For more information, check out the [Attributes docs](/docs/attributes). @@ -206,8 +139,7 @@ Here I am rendering a custom {% $variable %} {% /markdoc-example %} -Variables must contain JSON-serializable content, such as strings, booleans, numbers, arrays, and JSON objects.\ -You can access nested values using dot-notation, similar to JavaScript: +Variables must contain JSON-serializable content, such as strings, booleans, numbers, arrays, and JSON objects. You can access nested values using dot-notation, similar to JavaScript: {% markdoc-example %} @@ -217,23 +149,12 @@ Here's a deeply nested variable {% $markdoc.frontmatter.title %} {% /markdoc-example %} -You can use variables throughout your document, as content itself: - -{% markdoc-example %} - -``` -Β© {% $currentYear %} Stripe -``` - -{% /markdoc-example %} - \ For more information, check out the [Variables docs](/docs/variables). ## Functions -Functions look and feel similar to JavaScript functions. They're callable from the body of the document, inside an annotation, or within tag attributes. -Function parameters are comma-separated. Trailing commas aren't supported in function calls. +Functions look and feel similar to JavaScript functions. They're callable from the body of the document or within an [attribute](#attributes). Function parameters are comma-separated. Trailing commas aren't supported in function calls. {% markdoc-example %} @@ -269,95 +190,36 @@ Markdoc supports [Markdown comment syntax](https://spec.commonmark.org/0.30/#exa {% /markdoc-example %} -## Config - -This table outlines the various options you can pass to `Markdoc.transform`. Each option adjusts how a document is [transformed](/docs/render#transform) and [rendered](/docs/render#render). +## Configuration -{% table %} - -- Key -- Type -- Description +To pass options into the rendering process, use a config object. For instance, a config object is one way to provide values for variables: ---- - -- [`nodes`](/docs/nodes) -- {% code %}{ [nodeType: [NodeType](/docs/nodes#built-in-nodes)]: [Schema](https://github.com/markdoc/markdoc/blob/6bcb8a0c48a181ca9df577534d841280646cea09/src/types.ts#L94-L101) }{% /code%} -- Register [custom nodes](/docs/nodes) in your schema - ---- - -- [`tags`](/docs/tags) -- {% code %}{ [tagName: string]: [Schema](https://github.com/markdoc/markdoc/blob/6bcb8a0c48a181ca9df577534d841280646cea09/src/types.ts#L94-L101) }{% /code%} -- Register [custom tags](/docs/tags) in your schema - ---- - -- [`variables`](/docs/variables) -- `{ [variableName: string]: any }` -- Register [variables](/docs/variables) to use in your document - ---- - -- [`functions`](/docs/functions) -- {% code %}{ [functionName: string]: [ConfigFunction](https://github.com/markdoc/markdoc/blob/6bcb8a0c48a181ca9df577534d841280646cea09/src/types.ts#L31-L36) }{% /code %} -- Register [custom functions](/docs/functions) to use in your document - ---- - -- [`partials`](/docs/partials) -- `{ [partialPath: string]: Ast.Node }` -- Register reusable pieces of content to used by the [`partial` tag](/docs/partials) - -{% /table %} - -### Full example - -Here's an example of what a Markdoc config would look like: +{% side-by-side %} +{% markdoc-example %} ```js +const source = ` +The answer is {% $x %}. +`; const config = { - nodes: { - heading: { - render: 'Heading', - attributes: { - id: { type: String }, - level: { type: Number } - } - } - }, - tags: { - callout: { - render: 'Callout', - attributes: { - title: { - type: String - } - } - } - }, variables: { - name: 'Dr. Mark', - frontmatter: { - title: 'Configuration options' - } - }, - functions: { - includes: { - transform(parameters, config) { - const [array, value] = Object.values(parameters); - - return Array.isArray(array) ? array.includes(value) : false; - } - } - }, - partials: { - 'header.md': Markdoc.parse(`# My header`) + x: 10 } -}; +} +const ast = Markdoc.parse(source); +const content = Markdoc.transform(source, config); +return Markdoc.renderers.html(content); +``` + +{% /markdoc-example %} -const content = Markdoc.transform(ast, config); +```html +

The answer is 42.

``` +{% /side-by-side %} + +\ +For more information, check out the [Configuration docs](/docs/configs). ## Next steps diff --git a/pages/docs/tags.md b/pages/docs/tags.md index b10b4c69..d051f434 100644 --- a/pages/docs/tags.md +++ b/pages/docs/tags.md @@ -385,6 +385,25 @@ Here is an example of including the `header.md` file as a partial. For more information on partials, check out the full [partials docs](/docs/partials). +## Inline tags + + +If your tag doesn't contain any new lines, then it's treated as an inline tag. Inline tags are automatically wrapped with a single `paragraph` [Node](/docs/nodes) (which renders a `

` element by default), to follow the [CommonMark paragraph spec](https://spec.commonmark.org/0.30/#paragraphs). + +{% markdoc-example %} + +``` +{% code %} + +{% highlight %}Inline tag 1{% /highlight %} +{% highlight %}Inline tag 2{% /highlight %} + +{% /code %} +``` + +{% /markdoc-example %} + + ## Next steps - [Customize tags with attributes](/docs/attributes) From 47428c8947f9cd301effb95cf97c38fc745402ec Mon Sep 17 00:00:00 2001 From: Leah Velleman Date: Thu, 27 Oct 2022 11:15:38 -0400 Subject: [PATCH 2/2] Built-ins to top --- pages/docs/functions.md | 100 +++++++------- pages/docs/nodes.md | 256 +++++++++++++++++----------------- pages/docs/tags.md | 298 ++++++++++++++++++++-------------------- 3 files changed, 330 insertions(+), 324 deletions(-) diff --git a/pages/docs/functions.md b/pages/docs/functions.md index 6a1969f9..b2461129 100644 --- a/pages/docs/functions.md +++ b/pages/docs/functions.md @@ -7,55 +7,6 @@ description: Functions let you extend Markdoc to run custom code. Functions enable you extend Markdoc with custom utilities, which let you transform your content and [variables](/docs/syntax#variables) at runtime. -## Creating a custom function - -To extend Markdoc with your own functions, first create custom function definitions: - -```js -const includes = { - transform(parameters) { - const [array, value] = Object.values(parameters); - - return Array.isArray(array) ? array.includes(value) : false; - } -}; - -const uppercase = { - transform(parameters) { - const string = parameters[0]; - - return typeof string === 'string' ? string.toUpperCase() : string; - } -}; -``` - -Then, pass the functions to your [`Config` object](/docs/syntax#config) - -```js -const config = { - functions: { - includes, - uppercase - } -}; - -const content = Markdoc.transform(ast, config); -``` - -Finally, call the functions within your Markdoc content - -{% markdoc-example %} - -```md -{% if includes($countries, "AR") %} πŸ‡¦πŸ‡· {% /if %} -{% if includes($countries, "AU") %} πŸ‡¦πŸ‡Ί {% /if %} -{% if includes($countries, "ES") %} πŸ‡ͺπŸ‡Έ {% /if %} -{% if includes($countries, "JP") %} πŸ‡―πŸ‡΅ {% /if %} -{% if includes($countries, "NG") %} πŸ‡³πŸ‡¬ {% /if %} -{% if includes($countries, "US") %} πŸ‡ΊπŸ‡Έ {% /if %} -``` - -{% /markdoc-example %} ## Built-in functions @@ -171,6 +122,57 @@ This function simply renders the value as a serialized JSON value in the documen {% /markdoc-example %} + +## Creating a custom function + +To extend Markdoc with your own functions, first create custom function definitions: + +```js +const includes = { + transform(parameters) { + const [array, value] = Object.values(parameters); + + return Array.isArray(array) ? array.includes(value) : false; + } +}; + +const uppercase = { + transform(parameters) { + const string = parameters[0]; + + return typeof string === 'string' ? string.toUpperCase() : string; + } +}; +``` + +Then, pass the functions to your [`Config` object](/docs/syntax#config) + +```js +const config = { + functions: { + includes, + uppercase + } +}; + +const content = Markdoc.transform(ast, config); +``` + +Finally, call the functions within your Markdoc content + +{% markdoc-example %} + +```md +{% if includes($countries, "AR") %} πŸ‡¦πŸ‡· {% /if %} +{% if includes($countries, "AU") %} πŸ‡¦πŸ‡Ί {% /if %} +{% if includes($countries, "ES") %} πŸ‡ͺπŸ‡Έ {% /if %} +{% if includes($countries, "JP") %} πŸ‡―πŸ‡΅ {% /if %} +{% if includes($countries, "NG") %} πŸ‡³πŸ‡¬ {% /if %} +{% if includes($countries, "US") %} πŸ‡ΊπŸ‡Έ {% /if %} +``` + +{% /markdoc-example %} + ## Next steps - [Validate your content](/docs/validation) diff --git a/pages/docs/nodes.md b/pages/docs/nodes.md index 082e1fd8..19f543e9 100644 --- a/pages/docs/nodes.md +++ b/pages/docs/nodes.md @@ -7,133 +7,6 @@ description: Markdoc nodes enable you to customize how your document renders without using any custom syntaxβ€”it consists entirely of Markdown. Customizing nodes lets you extend your implementation incrementally. -## Customizing Markdoc nodes - -Nodes are elements that Markdoc inherits from Markdown, specifically the [CommonMark specification](https://commonmark.org/). - -You define custom nodes by passing a custom Node to your [`Config`](/docs/syntax#config), like: - -```js -import { heading } from './schema/Heading.markdoc'; -import * as components from './components'; - -const config = { - nodes: { - heading - } -}; - -const ast = Markdoc.parse(doc); -const content = Markdoc.transform(ast, config); - -const children = Markdoc.renderers.react(content, React, { components }); -``` - -where `heading` looks something like: - -```js -// ./schema/Heading.markdoc.js - -import { Tag } from '@markdoc/markdoc'; - -// Or replace this with your own function -function generateID(children, attributes) { - if (attributes.id && typeof attributes.id === 'string') { - return attributes.id; - } - return children - .filter((child) => typeof child === 'string') - .join(' ') - .replace(/[?]/g, '') - .replace(/\s+/g, '-') - .toLowerCase(); -} - -export const heading = { - children: ['inline'], - attributes: { - id: { type: String }, - level: { type: Number, required: true, default: 1 } - }, - transform(node, config) { - const attributes = node.transformAttributes(config); - const children = node.transformChildren(config); - - const id = generateID(children, attributes); - - return new Tag( - `h${node.attributes['level']}`, - { ...attributes, id }, - children - ); - } -}; -``` - -After registering this custom node, you can then use it in your Markdoc, like: - -{% side-by-side %} - -{% markdoc-example %} - -```md -#### My header -``` - -{% /markdoc-example %} - -#### My header - -{% /side-by-side %} - -## Options - -These are the optional fields you can use to customize your `Node`: - -{% table %} - -- Option -- Type -- Description {% width="40%" %} - ---- - -- `render` -- `string` -- Name of the output (for example, HTML tag, React component name) to render - ---- - -- `children` -- `string[]` -- Determines which tag or node types can be rendered as children of this node. Used in schema validation. - ---- - -- `attributes` -- `{ [string]: SchemaAttribute }` -- Determines which [values (and their types)](/docs/attributes) can be passed to this node. - ---- - -- `transform` -- ```js - (Ast.Node, ?Options) => - | RenderableTreeNode - | RenderableTreeNode[] - | null - ``` -- Customize the Markdoc transform function for this node, returning the custom output you want to eventually render. This is called during the [`transform` step](/docs/render#transform). - ---- - -- `validate` -- ```js - (Node, ?Options) => ValidationError[]; - ``` -- Extend Markdoc validation. This validates that the content meets validation requirements, and is called during the [`validate` step](/docs/render#validate) - -{% /table %} ## Built-in nodes @@ -292,6 +165,135 @@ Markdoc comes out of the box with built-in nodes for each of the [CommonMark](ht {% /table %} + +## Customizing Markdoc nodes + +Nodes are elements that Markdoc inherits from Markdown, specifically the [CommonMark specification](https://commonmark.org/). + +You define custom nodes by passing a custom Node to your [`Config`](/docs/syntax#config), like: + +```js +import { heading } from './schema/Heading.markdoc'; +import * as components from './components'; + +const config = { + nodes: { + heading + } +}; + +const ast = Markdoc.parse(doc); +const content = Markdoc.transform(ast, config); + +const children = Markdoc.renderers.react(content, React, { components }); +``` + +where `heading` looks something like: + +```js +// ./schema/Heading.markdoc.js + +import { Tag } from '@markdoc/markdoc'; + +// Or replace this with your own function +function generateID(children, attributes) { + if (attributes.id && typeof attributes.id === 'string') { + return attributes.id; + } + return children + .filter((child) => typeof child === 'string') + .join(' ') + .replace(/[?]/g, '') + .replace(/\s+/g, '-') + .toLowerCase(); +} + +export const heading = { + children: ['inline'], + attributes: { + id: { type: String }, + level: { type: Number, required: true, default: 1 } + }, + transform(node, config) { + const attributes = node.transformAttributes(config); + const children = node.transformChildren(config); + + const id = generateID(children, attributes); + + return new Tag( + `h${node.attributes['level']}`, + { ...attributes, id }, + children + ); + } +}; +``` + +After registering this custom node, you can then use it in your Markdoc, like: + +{% side-by-side %} + +{% markdoc-example %} + +```md +#### My header +``` + +{% /markdoc-example %} + +#### My header + +{% /side-by-side %} + +## Options + +These are the optional fields you can use to customize your `Node`: + +{% table %} + +- Option +- Type +- Description {% width="40%" %} + +--- + +- `render` +- `string` +- Name of the output (for example, HTML tag, React component name) to render + +--- + +- `children` +- `string[]` +- Determines which tag or node types can be rendered as children of this node. Used in schema validation. + +--- + +- `attributes` +- `{ [string]: SchemaAttribute }` +- Determines which [values (and their types)](/docs/attributes) can be passed to this node. + +--- + +- `transform` +- ```js + (Ast.Node, ?Options) => + | RenderableTreeNode + | RenderableTreeNode[] + | null + ``` +- Customize the Markdoc transform function for this node, returning the custom output you want to eventually render. This is called during the [`transform` step](/docs/render#transform). + +--- + +- `validate` +- ```js + (Node, ?Options) => ValidationError[]; + ``` +- Extend Markdoc validation. This validates that the content meets validation requirements, and is called during the [`validate` step](/docs/render#validate) + +{% /table %} + ## Next steps - [Create custom tags](/docs/tags) diff --git a/pages/docs/tags.md b/pages/docs/tags.md index d051f434..8cb2420a 100644 --- a/pages/docs/tags.md +++ b/pages/docs/tags.md @@ -29,154 +29,6 @@ Tags aren't composable! {% /markdoc-example %} -## Create a custom tag - -To extend Markdoc with a custom tag, first, create a tag definition. In this example, you're creating a `callout` tag: - -```js -// ./schema/Callout.markdoc.js - -export const callout = { - render: 'Callout', - children: ['paragraph', 'tag', 'list'], - attributes: { - type: { - type: String, - default: 'note', - matches: ['caution', 'check', 'note', 'warning'], - errorLevel: 'critical' - }, - title: { - type: String - } - } -}; -``` - -Then, pass the tag definition to your [`Config` object](/docs/syntax#config): - -```js -import { callout } from './schema/Callout.markdoc'; -import * as components from './components'; - -const config = { - tags: { - callout - } -}; - -const doc = ` -# My first custom tag -`; - -const ast = Markdoc.parse(doc); -const content = Markdoc.transform(ast, config); - -const children = Markdoc.renderers.react(content, React, { components }); -``` - -Next, pass your content to the Markdoc renderer. If you want to render a React component, specify which component should render this type of tag in the `components` mapping. - -```jsx -import * as React from 'react'; -import { Icon } from './Icon'; - -function Callout({ title, icon, children }) { - return ( -

-
- -
- {title} - {children} -
-
-
- ); -} - -return Markdoc.renderers.react(content, React, { - components: { - // The key here is the same string as `tag` in the previous step - Callout: Callout - } -}); -``` - -Now you can use your custom tag in your Markdoc content. - -{% side-by-side %} - -{% markdoc-example %} - -```md -{% callout title="Hey you!" icon="note" %} -I have a message for you -{% /callout %} -``` - -{% /markdoc-example %} - -{% callout title="Hey you!" type="note" %} -I have a message for you -{% /callout %} - -{% /side-by-side %} - -## Options - -These are the optional fields you can use to customize your `Tag`: - -{% table %} - -- Option -- Type -- Description {% width="40%" %} - ---- - -- `render` -- `string` -- Name of the output (for example, HTML tag, React component name) to render - ---- - -- `children` -- `string[]` -- Specifies which node types can be rendered as children of this tag. Used in schema validation. - ---- - -- `attributes` -- `{ [string]: SchemaAttribute }` -- Specifies which [values (and their types)](/docs/attributes) can be passed to this tag. - ---- - -- `transform` -- ```js - (Ast.Node, ?Options) => - | RenderableTreeNode - | RenderableTreeNode[] - | null - ``` -- Customize the Markdoc transform function for this tag, returning the custom output you want to eventually render. This is called during the [`transform` step](/docs/render#transform). - ---- - -- `validate` -- ```js - (Node, ?Options) => ValidationError[]; - ``` -- Extend Markdoc validation. Used to validate that the content meets validation requirements. This is called during the [`validate` step](/docs/render#validate) - ---- - -- `selfClosing` -- `boolean` -- Specifies whether a tag can contain children (`false`) or not (`true`). Used in schema validation. - -{% /table %} ## Built-in tags @@ -404,6 +256,156 @@ If your tag doesn't contain any new lines, then it's treated as an inline tag. I {% /markdoc-example %} + +## Create a custom tag + +To extend Markdoc with a custom tag, first, create a tag definition. In this example, you're creating a `callout` tag: + +```js +// ./schema/Callout.markdoc.js + +export const callout = { + render: 'Callout', + children: ['paragraph', 'tag', 'list'], + attributes: { + type: { + type: String, + default: 'note', + matches: ['caution', 'check', 'note', 'warning'], + errorLevel: 'critical' + }, + title: { + type: String + } + } +}; +``` + +Then, pass the tag definition to your [`Config` object](/docs/syntax#config): + +```js +import { callout } from './schema/Callout.markdoc'; +import * as components from './components'; + +const config = { + tags: { + callout + } +}; + +const doc = ` +# My first custom tag +`; + +const ast = Markdoc.parse(doc); +const content = Markdoc.transform(ast, config); + +const children = Markdoc.renderers.react(content, React, { components }); +``` + +Next, pass your content to the Markdoc renderer. If you want to render a React component, specify which component should render this type of tag in the `components` mapping. + +```jsx +import * as React from 'react'; +import { Icon } from './Icon'; + +function Callout({ title, icon, children }) { + return ( +
+
+ +
+ {title} + {children} +
+
+
+ ); +} + +return Markdoc.renderers.react(content, React, { + components: { + // The key here is the same string as `tag` in the previous step + Callout: Callout + } +}); +``` + +Now you can use your custom tag in your Markdoc content. + +{% side-by-side %} + +{% markdoc-example %} + +```md +{% callout title="Hey you!" icon="note" %} +I have a message for you +{% /callout %} +``` + +{% /markdoc-example %} + +{% callout title="Hey you!" type="note" %} +I have a message for you +{% /callout %} + +{% /side-by-side %} + +## Options + +These are the optional fields you can use to customize your `Tag`: + +{% table %} + +- Option +- Type +- Description {% width="40%" %} + +--- + +- `render` +- `string` +- Name of the output (for example, HTML tag, React component name) to render + +--- + +- `children` +- `string[]` +- Specifies which node types can be rendered as children of this tag. Used in schema validation. + +--- + +- `attributes` +- `{ [string]: SchemaAttribute }` +- Specifies which [values (and their types)](/docs/attributes) can be passed to this tag. + +--- + +- `transform` +- ```js + (Ast.Node, ?Options) => + | RenderableTreeNode + | RenderableTreeNode[] + | null + ``` +- Customize the Markdoc transform function for this tag, returning the custom output you want to eventually render. This is called during the [`transform` step](/docs/render#transform). + +--- + +- `validate` +- ```js + (Node, ?Options) => ValidationError[]; + ``` +- Extend Markdoc validation. Used to validate that the content meets validation requirements. This is called during the [`validate` step](/docs/render#validate) + +--- + +- `selfClosing` +- `boolean` +- Specifies whether a tag can contain children (`false`) or not (`true`). Used in schema validation. + +{% /table %} + ## Next steps - [Customize tags with attributes](/docs/attributes)