diff --git a/.gitignore b/.gitignore index 8cc7cf99..f301ea9d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ out/ dist/ # next-sitemap sitemap*.xml -robots.txt \ No newline at end of file +robots.txt diff --git a/package-lock.json b/package-lock.json index 8e4d15ef..df76deff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -548,12 +548,57 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "peer": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "peer": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "peer": true + }, "node_modules/@types/node": { "version": "18.7.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", "dev": true }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "peer": true + }, + "node_modules/@types/react": { + "version": "18.0.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.23.tgz", + "integrity": "sha512-R1wTULtCiJkudAN2DJGoYYySbGtOdzZyUWAACYinKdiQC8auxso4kLDUhQ7AJ2kh3F6A6z4v69U6tNY39hihVQ==", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "peer": true + }, "node_modules/@typescript-eslint/parser": { "version": "5.32.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.32.0.tgz", @@ -1131,6 +1176,12 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "peer": true + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -3704,6 +3755,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -4045,7 +4110,8 @@ "@markdoc/markdoc": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/@markdoc/markdoc/-/markdoc-0.1.13.tgz", - "integrity": "sha512-zUdUpr2U3tf0uBQKr1aAiNDvwtZChAjCw3WPVv4PMKcPjYYlVHE0lizIS3NjChruRPJ2FvVQAI4ceyNpJWobiw==" + "integrity": "sha512-zUdUpr2U3tf0uBQKr1aAiNDvwtZChAjCw3WPVv4PMKcPjYYlVHE0lizIS3NjChruRPJ2FvVQAI4ceyNpJWobiw==", + "requires": {} }, "@markdoc/next.js": { "version": "0.1.10", @@ -4193,12 +4259,57 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "peer": true + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "peer": true, + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "peer": true + }, "@types/node": { "version": "18.7.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", "dev": true }, + "@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", + "peer": true + }, + "@types/react": { + "version": "18.0.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.23.tgz", + "integrity": "sha512-R1wTULtCiJkudAN2DJGoYYySbGtOdzZyUWAACYinKdiQC8auxso4kLDUhQ7AJ2kh3F6A6z4v69U6tNY39hihVQ==", + "peer": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "peer": true + }, "@typescript-eslint/parser": { "version": "5.32.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.32.0.tgz", @@ -4317,7 +4428,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv": { "version": "6.12.6", @@ -4607,6 +4719,12 @@ "which": "^2.0.1" } }, + "csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "peer": true + }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -5012,7 +5130,8 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "7.1.1", @@ -6027,7 +6146,8 @@ "react-codemirror2": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.2.1.tgz", - "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==" + "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==", + "requires": {} }, "react-dom": { "version": "17.0.2", @@ -6311,7 +6431,8 @@ "styled-jsx": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.4.tgz", - "integrity": "sha512-sDFWLbg4zR+UkNzfk5lPilyIgtpddfxXEULxhujorr5jtePTUqiPDc5BC0v1NRqTr/WaFBGQQUoYToGlF4B2KQ==" + "integrity": "sha512-sDFWLbg4zR+UkNzfk5lPilyIgtpddfxXEULxhujorr5jtePTUqiPDc5BC0v1NRqTr/WaFBGQQUoYToGlF4B2KQ==", + "requires": {} }, "supports-color": { "version": "7.2.0", @@ -6441,6 +6562,13 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true, + "peer": true + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -6465,7 +6593,8 @@ "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==" + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} }, "util-deprecate": { "version": "1.0.2", diff --git a/pages/docs/attributes.md b/pages/docs/attributes.md index a93b6e08..b8f3cb27 100644 --- a/pages/docs/attributes.md +++ b/pages/docs/attributes.md @@ -5,8 +5,74 @@ description: Attributes are used to pass data to tags in Markdoc. # {% $markdoc.frontmatter.title %} + 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/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..6b9f09f1 100644 --- a/pages/docs/nodes.md +++ b/pages/docs/nodes.md @@ -5,135 +5,9 @@ description: # {% $markdoc.frontmatter.title %} -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: +Nodes are elements that Markdoc inherits from Markdown, specifically the [CommonMark specification](https://commonmark.org/). 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. -{% 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 +166,133 @@ Markdoc comes out of the box with built-in nodes for each of the [CommonMark](ht {% /table %} + +## Customizing Markdoc nodes + +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/syntax.md b/pages/docs/syntax.md index 87c3b562..34be9a87 100644 --- a/pages/docs/syntax.md +++ b/pages/docs/syntax.md @@ -173,7 +173,7 @@ You can also set [attributes](#attributes) on a node, such as `width` or `colspa ## Attributes -Pass attributes to tags to customize their behavior. You can pass values of type: `number`, `string`, `boolean`, JSON `array`, or JSON `object`. +With tags, you can use an HTML-like syntax: {% markdoc-example %} @@ -183,13 +183,26 @@ Pass attributes to tags to customize their behavior. You can pass values of type name="San Francisco" deleted=false coordinates=[1, 4, 9] - meta={id: "id_123"} /%} + meta={id: "id_123"} + color=$color /%} ``` {% /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 `\"`. +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 %} +* Cell +* Cell +--- +* Cell {% colspan=2 %} +{% /table %} +``` + +{% /markdoc-example %} \ For more information, check out the [Attributes docs](/docs/attributes). diff --git a/pages/docs/tags.md b/pages/docs/tags.md index b10b4c69..7689bcf1 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 @@ -385,6 +237,156 @@ 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). + +## 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)