├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── question.md └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── .nuxtrc ├── README.md ├── app.config.ts ├── content │ └── 0.index.md ├── nuxt.config.ts ├── package.json ├── pnpm-lock.yaml ├── public │ ├── cover.png │ └── favicon.ico ├── renovate.json ├── tokens.config.ts └── tsconfig.json ├── eslint.config.mjs ├── package.json ├── playground ├── assets │ └── css │ │ └── main.css ├── composables │ ├── useMarkdownGenerator.ts │ └── useMarkdownParser.ts ├── nuxt.config.ts ├── pages │ └── index.vue ├── server │ └── api │ │ └── parse.ts └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── renovate.json ├── scripts ├── bump-edge.ts ├── release-edge.sh └── release.sh ├── src ├── from-markdown.ts ├── frontmatter.ts ├── index.ts ├── mdast-util-to-markdown.ts ├── micromark-extension │ ├── constants.ts │ ├── factory-attributes.ts │ ├── factory-label.ts │ ├── factory-name.ts │ ├── index.ts │ ├── tokenize-attribute.ts │ ├── tokenize-binding.ts │ ├── tokenize-container-indented.ts │ ├── tokenize-container-suger.ts │ ├── tokenize-container.ts │ ├── tokenize-frontmatter.ts │ ├── tokenize-inline.ts │ ├── tokenize-span.ts │ ├── types.ts │ └── utils.ts ├── to-markdown.ts ├── types.d.ts └── utils.ts ├── test ├── __snapshots__ │ ├── attributes.test.ts.snap │ ├── basic.test.ts.snap │ ├── block-component.test.ts.snap │ ├── codeblock-props.test.ts.snap │ ├── inline-component.test.ts.snap │ ├── label.test.ts.snap │ ├── span.test.ts.snap │ └── unwrap.test.ts.snap ├── attributes.test.ts ├── basic.test.ts ├── block-component.test.ts ├── codeblock-props.test.ts ├── inline-component.test.ts ├── label.test.ts ├── span.test.ts ├── unwrap.test.ts └── utils │ └── index.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug report to help us improve the module. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Version 11 | module: 12 | nuxt: 13 | 14 | ### Nuxt configuration 15 | #### [mode](https://nuxtjs.org/api/configuration-mode): 16 | - [ ] universal 17 | - [ ] spa 18 | 19 | ### Nuxt configuration 20 | 26 | 27 | ## Reproduction 28 | > :warning: without a minimal reproduction we wont be able to look into your issue 29 | 30 | **Link:** 31 | - [ ] https://codesandbox.io/ 32 | - [ ] GitHub repository 33 | 34 | #### What is expected? 35 | #### What is actually happening? 36 | #### Steps to reproduce 37 | ## Additional information 38 | ## Checklist 39 | * [ ] I have tested with the latest Nuxt version and the issue still occurs 40 | * [ ] I have tested with the latest module version and the issue still occurs 41 | * [ ] I have searched the issue tracker and this issue hasn't been reported yet 42 | 43 | ### Steps to reproduce 44 | 45 | 46 | ### What is expected? 47 | 48 | 49 | ### What is actually happening? 50 | 51 | 52 | ### Performance analysis? 53 | 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Nuxt Community Discord 4 | url: https://discord.nuxtjs.org/ 5 | about: Consider asking questions about the module here. 6 | # - name: Documentation 7 | # url: /README.md 8 | # about: Check our documentation before reporting issues or questions. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea or enhancement for this project. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Is your feature request related to a problem? Please describe. 11 | 12 | 13 | ### Describe the solution you'd like to see 14 | 15 | 16 | ### Describe alternatives you've considered 17 | 18 | 19 | ### Additional context 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about the module. 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | ci: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-node@v4 18 | with: 19 | node-version: '20' 20 | - run: corepack enable 21 | - run: pnpm install 22 | - run: pnpm dev:prepare 23 | - run: pnpm lint 24 | - run: pnpm test 25 | - run: pnpm build 26 | - uses: codecov/codecov-action@v2 27 | - name: Release Edge 28 | if: | 29 | github.event_name == 'push' && 30 | !contains(github.event.head_commit.message, '[skip-release]') && 31 | !startsWith(github.event.head_commit.message, 'docs') 32 | run: ./scripts/release-edge.sh 33 | env: 34 | NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | jspm_packages 4 | 5 | package-lock.json 6 | # */**/yarn.lock 7 | 8 | # Logs 9 | *.log 10 | 11 | # Temp directories 12 | .temp 13 | .tmp 14 | .cache 15 | 16 | # Yarn 17 | **/.yarn/cache 18 | **/.yarn/*state* 19 | 20 | # Generated dirs 21 | dist 22 | .nuxt 23 | .nuxt-* 24 | .output 25 | .gen 26 | nuxt.d.ts 27 | 28 | # Junit reports 29 | reports 30 | 31 | # Coverage reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode 38 | 39 | # Intellij idea 40 | *.iml 41 | .idea 42 | 43 | # OSX 44 | .DS_Store 45 | .AppleDouble 46 | .LSOverride 47 | 48 | # Files that might appear in the root of a volume 49 | .DocumentRevisions-V100 50 | .fseventsd 51 | .Spotlight-V100 52 | .TemporaryItems 53 | .Trashes 54 | .VolumeIcon.icns 55 | .com.apple.timemachine.donotpresent 56 | 57 | # Directories potentially created on remote AFP share 58 | .AppleDB 59 | .AppleDesktop 60 | Network Trash Folder 61 | Temporary Items 62 | .apdisk 63 | 64 | .vercel_build_output 65 | .build-* 66 | .env 67 | .netlify 68 | .data -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) NuxtLabs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remark MDC 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![License][license-src]][license-href] 6 | 7 | Remark plugin to parse Markdown Components syntax. 8 | 9 | For MDC syntax highlight on VS Code, checkout [vscode-mdc](https://github.com/nuxtlabs/vscode-mdc). 10 | 11 | ## Setup 12 | 13 | Add the `remark-mdc` dependency to your project: 14 | 15 | ```bash 16 | # yarn 17 | yarn add --dev remark-mdc 18 | # npm 19 | npm install --save-dev remark-mdc 20 | # pnpm 21 | pnpm add --save-dev remark-mdc 22 | ``` 23 | 24 | Then, add `remark-mdc` to the `unified` streams: 25 | 26 | ```ts 27 | import { unified } from 'unified' 28 | import remarkParse from 'remark-parse' 29 | import remarkMDC from 'remark-mdc' 30 | 31 | function parse(md: string) { 32 | const processor = unified() 33 | processor.use(remarkParse) 34 | 35 | // Use `remark-mdc` plugin to parse MDC syntax 36 | processor.use(remarkMDC) 37 | 38 | // ... 39 | 40 | return processor.process({ value: content, data: frontmatter }) 41 | } 42 | ``` 43 | 44 | That's it! ✨ 45 | 46 | ## Syntax 47 | 48 | ### `^-` Frontmatter 49 | 50 | Front-matter is a convention of Markdown-based CMS to provide metadata to documents, like description or title. Remark MDC uses the YAML syntax with `key: value` pairs. 51 | 52 | To define frontmatter, start your document with `---\n---` section and put your desired data in YAML format within this section. 53 | 54 | ```md 55 | --- 56 | title: 'Title of the page' 57 | description: 'meta description of the page' 58 | --- 59 | 60 | 61 | ``` 62 | 63 | ### `:` Inline Components 64 | 65 | Inline components are entries that will stick inside the parent paragraph. Like spans, emojis, icons, etc. Inline components can be defined by a single `:` followed by the component name. 66 | 67 | ```md 68 | A simple :inline-component 69 | ``` 70 | 71 | You may want to pass some text into an inline component; you can do it using the `[TEXT]` syntax. 72 | 73 | ```md 74 | A simple :inline-component[John Doe] 75 | ``` 76 | 77 | If you want to use an inline component followed by specific characters like `-`, `_`, or `:`, you can use a dummy props specifier after it. 78 | 79 | ```md 80 | How to say :hello{}-world in Markdown 81 | ``` 82 | 83 | In this example, `:hello{}` will search for the `` component, and `-world` will be plain text. 84 | 85 | > Note: If you put an inline component alone in a single line, it will be transformed into a block component. This is sugar syntax for block components. 86 | > ```md 87 | > Paragraph a 88 | > 89 | > :block-component 90 | > 91 | > Paragraph b 92 | > ``` 93 | 94 | 95 | ### `::` Block Components 96 | 97 | Block components are components that accept Markdown content or another component as a slot. 98 | 99 | Block components are defined by the `::` identifier. 100 | 101 | ```md 102 | ::card 103 | The content of the card 104 | :: 105 | ``` 106 | 107 | Block components can be used without any content. 108 | 109 | ```md 110 | ::card 111 | :: 112 | ``` 113 | 114 | Or with sugar syntax. Note that in sugar syntax, it is important to put the component alone on a separate line. 115 | 116 | ```md 117 | A paragraph 118 | 119 | :card 120 | ``` 121 | 122 | ### `#` Slots 123 | 124 | Block components can accept slots (like Vue slots) with different names. The content of these slots can be anything from a normal markdown paragraph to a nested block component. 125 | 126 | - The `default` slot renders the top-level content inside the block component. 127 | - Named slots use the `#` identifier to render the corresponding content. 128 | 129 | ```md 130 | ::hero 131 | Default slot text 132 | 133 | #description 134 | This will be rendered inside the `description` slot. 135 | ``` 136 | 137 | ### `:::` Nesting 138 | 139 | MDC supports nested components inside slots by indenting them. To make nested components visually distinguishable, you can indent nested components and add more `:` when you define them. 140 | 141 | ```md 142 | ::hero 143 | :::card 144 | A nested card 145 | 146 | ::::card 147 | A super nested card 148 | :::: 149 | ::: 150 | :: 151 | ``` 152 | 153 | ### `[]` Span 154 | 155 | To create inline spans in your text, you can use the `[]` identifier. 156 | 157 | ```md 158 | Hello [World] 159 | ``` 160 | 161 | This syntax is useful in combination with inline props to make text visually different from the rest of the paragraph. Check out the inline props section to read more about props. 162 | 163 | ```md 164 | Hello [World]{.bg-blue-500}! 165 | ``` 166 | 167 | ### `{}` Inline Props 168 | 169 | Using the inline props syntax, you can pass props and attributes to your components. MDC goes a step further and allows you to pass attributes to markdown native elements like images, links, bold texts, and more. 170 | 171 | To define properties for a component or a markdown element, you need to create a props scope `{}` exactly after the component/element definition. Then you can define the properties inline within this scope using a `key=value` syntax. 172 | 173 | ```md 174 | Inline :component{key="value" key2=value2} 175 | 176 | ::block-component{no-border title="My Component"} 177 | :: 178 | 179 | [Link](https://nuxt.com){class="nuxt"} 180 | 181 | ![Nuxt Logo](https://nuxt.com/assets/design-kit/logo/icon-green.svg){class=".nuxt-logo"} 182 | 183 | `code`{style="color: red"} 184 | 185 | _italic_{style="color: blue"} 186 | 187 | **bold**{style="color: blue"} 188 | ``` 189 | 190 | There are also a couple of sugar syntaxes for common use-cases: 191 | 192 | - `id` attribute: `_italic_{#the_italic_text}` 193 | - `class` attribute: `**bold**{.bold .text.with_attribute}` 194 | - No value (boolean props): `:component{no-border}` 195 | - Single string without any space: `**bold**{class=red}` 196 | 197 | If you want to pass arrays or objects as props to components, you can pass them as a JSON string and prefix the prop key with a colon to automatically decode the JSON string. Note that in this case, you should use single quotes for the value string so you can use double quotes to pass a valid JSON string: 198 | 199 | ```md 200 | ::dropdown{:items='["Nuxt", "Vue", "React"]'} 201 | String Array 202 | :: 203 | 204 | ::dropdown{:items='[1,2,3.5]'} 205 | Number Array 206 | :: 207 | 208 | ::chart{:options='{"responsive": true, "scales": {"y": {"beginAtZero": true}}}'} 209 | Object 210 | :: 211 | ``` 212 | 213 | > **Escape Character Behavior:** In MDC, the escape character (backslash `\`) can be used to escape special characters within attribute values. However, this behavior only applies to bound attributes. For example, in a bound attribute like `:list="[\"item 1\"]"`, the escape character will allow you to include quotes within the value. In contrast, for non-bound attributes such as `class="my-class"`, the escape character will not have any effect, and the value will be rendered as is. Therefore, it's important to use binding when you need to include special characters in attribute values. 214 | 215 | ### `---` Yaml Props 216 | 217 | The YAML method uses the `---` identifier to declare one prop per line, which can be useful for readability. 218 | 219 | ```md 220 | ::icon-card 221 | --- 222 | icon: IconNuxt 223 | description: Harness the full power of Nuxt and the Nuxt ecosystem. 224 | title: Nuxt Architecture. 225 | --- 226 | :: 227 | ``` 228 | 229 | ### `{{}}` Binding Variables 230 | 231 | The `{{ $doc.variable || 'defaultValue' }}` syntax allows you to bind variables in your Markdown content. This is especially useful when you want to dynamically insert values into your document. 232 | 233 | To use this syntax, simply enclose the variable name within double curly braces, like so: 234 | 235 | ```md 236 | --- 237 | color: blue 238 | --- 239 | 240 | # The color is {{ $doc.color || 'red' }}. 241 | ``` 242 | 243 | ## Contributing 244 | 245 | You can contribute to this module online with CodeSandbox: 246 | 247 | [![Edit remark-mdc](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/nuxtlabs/remark-mdc/tree/main/?fontsize=14&hidenavigation=1&theme=dark) 248 | 249 | Or locally: 250 | 251 | 1. Clone this repository 252 | 2. Install dependencies using `pnpm install` 253 | 3. Start the development server using `pnpm dev` 254 | 255 | ## License 256 | 257 | [MIT License](https://github.com/nuxtlabs/remark-mdc/blob/main/LICENSE) 258 | 259 | Copyright (c) NuxtLabs 260 | 261 | 262 | 263 | [npm-version-src]: https://img.shields.io/npm/v/remark-mdc/latest.svg?style=flat&colorA=020420&colorB=28CF8D 264 | [npm-version-href]: https://npmjs.com/package/remark-mdc 265 | 266 | [npm-downloads-src]: https://img.shields.io/npm/dm/remark-mdc.svg?style=flat&colorA=020420&colorB=28CF8D 267 | [npm-downloads-href]: https://npmjs.com/package/remark-mdc 268 | 269 | [license-src]: https://img.shields.io/github/license/nuxtlabs/remark-mdc.svg?style=flat&colorA=020420&colorB=28CF8D 270 | [license-href]: https://github.com/nuxtlabs/remark-mdc/blob/main/LICENSE 271 | -------------------------------------------------------------------------------- /docs/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .output 4 | .nuxt -------------------------------------------------------------------------------- /docs/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@nuxt/eslint-config', 4 | rules: { 5 | 'vue/max-attributes-per-line': 'off', 6 | 'vue/multi-word-component-names': 'off', 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | .output 13 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /docs/.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=true -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docus Starter 2 | 3 | Starter template for [Docus](https://docus.dev). 4 | 5 | ## Clone 6 | 7 | Clone the repository (using `nuxi`): 8 | 9 | ```bash 10 | npx nuxi init -t themes/docus 11 | ``` 12 | 13 | ## Setup 14 | 15 | Install dependencies: 16 | 17 | ```bash 18 | yarn install 19 | ``` 20 | 21 | ## Development 22 | 23 | ```bash 24 | yarn dev 25 | ``` 26 | 27 | ## Edge Side Rendering 28 | 29 | Can be deployed to Vercel Functions, Netlify Functions, AWS, and most Node-compatible environments. 30 | 31 | Look at all the available presets [here](https://v3.nuxtjs.org/guide/deploy/presets). 32 | 33 | ```bash 34 | yarn build 35 | ``` 36 | 37 | ## Static Generation 38 | 39 | Use the `generate` command to build your application. 40 | 41 | The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting. 42 | 43 | ```bash 44 | yarn generate 45 | ``` 46 | 47 | ## Preview build 48 | 49 | You might want to preview the result of your build locally, to do so, run the following command: 50 | 51 | ```bash 52 | yarn preview 53 | ``` 54 | 55 | --- 56 | 57 | For a detailed explanation of how things work, check out [Docus](https://docus.dev). 58 | -------------------------------------------------------------------------------- /docs/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | docus: { 3 | title: 'Remark MDC', 4 | description: 'Remark plugin to parse Markdown Components syntax.', 5 | image: 'https://remark-mdc.nuxt.space/cover.png', 6 | 7 | socials: { 8 | twitter: 'nuxt_js', 9 | github: 'nuxtlabs/remark-mdc', 10 | nuxt: { 11 | label: 'Nuxt', 12 | icon: 'simple-icons:nuxtdotjs', 13 | href: 'https://nuxt.com', 14 | }, 15 | }, 16 | 17 | github: { 18 | dir: 'docs/content', 19 | branch: 'main', 20 | repo: 'remark-mdc', 21 | owner: 'nuxtlabs', 22 | edit: true, 23 | }, 24 | 25 | aside: { 26 | level: 0, 27 | collapsed: false, 28 | exclude: [], 29 | }, 30 | 31 | main: { 32 | padded: true, 33 | fluid: false, 34 | }, 35 | 36 | header: { 37 | logo: false, 38 | showLinkIcon: true, 39 | exclude: [], 40 | fluid: false, 41 | title: 'Remark MDC', 42 | }, 43 | }, 44 | }) 45 | -------------------------------------------------------------------------------- /docs/content/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Remark MDC 3 | --- 4 | 5 | 6 | # Remark MDC 7 | 8 | Remark plugin to parse Markdown Components syntax. 9 | 10 | ## Setup 11 | 12 | Add the `remark-mdc` dependency to your project: 13 | 14 | ::code-group 15 | ```bash [Yarn] 16 | yarn add --dev remark-mdc 17 | ``` 18 | ```bash [NPM] 19 | npm install --save-dev remark-mdc 20 | ``` 21 | ```bash [PNPM] 22 | pnpm add --save-dev remark-mdc 23 | ``` 24 | :: 25 | 26 | Then, add `remark-mdc` to the `unified` streams: 27 | 28 | ```ts 29 | import { unified } from 'unified' 30 | import remarkParse from 'remark-parse' 31 | import remarkMDC from 'remark-mdc' 32 | 33 | function parse(md: string) { 34 | const processor = unified() 35 | processor.use(remarkParse) 36 | 37 | // Use `remark-mdc` plugin to parse MDC syntax 38 | processor.use(remarkMDC) 39 | 40 | // ... 41 | 42 | return processor.process({ value: content, data: frontmatter }) 43 | } 44 | ``` 45 | 46 | That's it! ✨ 47 | 48 | ## Syntax 49 | 50 | ### `^-` Frontmatter 51 | 52 | Front-matter is a convention of Markdown-based CMS to provide metadata to documents, like description or title. Remark MDC uses the YAML syntax with `key: value` pairs. 53 | 54 | To define frontmatter, start your document with `---\n---` section and put your desired data in YAML format within this section. 55 | 56 | ```md 57 | --- 58 | title: 'Title of the page' 59 | description: 'meta description of the page' 60 | --- 61 | 62 | 63 | ``` 64 | 65 | ### `:` Inline Components 66 | 67 | Inline components are entries that will stick inside the parent paragraph. Like spans, emojis, icons, etc. Inline components can be defined by a single `:` followed by the component name. 68 | 69 | ```md 70 | A simple :inline-component 71 | ``` 72 | 73 | You may want to pass some text into an inline component; you can do it using the `[TEXT]` syntax. 74 | 75 | ```md 76 | A simple :inline-component[John Doe] 77 | ``` 78 | 79 | If you want to use an inline component followed by specific characters like `-`, `_`, or `:`, you can use a dummy props specifier after it. 80 | 81 | ```md 82 | How to say :hello{}-world in Markdown 83 | ``` 84 | 85 | In this example, `:hello{}` will search for the `` component, and `-world` will be plain text. 86 | 87 | ::alert 88 | Note: If you put an inline component alone in a single line, it will be transformed into a block component. This is sugar syntax for block components. 89 | ```md 90 | Paragraph a 91 | 92 | :block-component 93 | 94 | Paragraph b 95 | ``` 96 | :: 97 | 98 | ### `::` Block Components 99 | 100 | Block components are components that accept Markdown content or another component as a slot. 101 | 102 | Block components are defined by the `::` identifier. 103 | 104 | ```md 105 | ::card 106 | The content of the card 107 | :: 108 | ``` 109 | 110 | Block components can be used without any content. 111 | 112 | ```md 113 | ::card 114 | :: 115 | ``` 116 | 117 | Or with sugar syntax. Note that in sugar syntax, it is important to put the component alone on a separate line. 118 | 119 | ```md 120 | A paragraph 121 | 122 | :card 123 | ``` 124 | 125 | ### `#` Slots 126 | 127 | Block components can accept slots (like Vue slots) with different names. The content of these slots can be anything from a normal markdown paragraph to a nested block component. 128 | 129 | - The `default` slot renders the top-level content inside the block component. 130 | - Named slots use the `#` identifier to render the corresponding content. 131 | 132 | ```md 133 | ::hero 134 | Default slot text 135 | 136 | #description 137 | This will be rendered inside the `description` slot. 138 | ``` 139 | 140 | ### `:::` Nesting 141 | 142 | MDC supports nested components inside slots by indenting them. To make nested components visually distinguishable, you can indent nested components and add more `:` when you define them. 143 | 144 | ```md 145 | ::hero 146 | :::card 147 | A nested card 148 | 149 | ::::card 150 | A super nested card 151 | :::: 152 | ::: 153 | :: 154 | ``` 155 | 156 | ### `[]` Span 157 | 158 | To create inline spans in your text, you can use the `[]` identifier. 159 | 160 | ```md 161 | Hello [World] 162 | ``` 163 | 164 | This syntax is useful in combination with inline props to make text visually different from the rest of the paragraph. Check out the inline props section to read more about props. 165 | 166 | ```md 167 | Hello [World]{.bg-blue-500}! 168 | ``` 169 | 170 | ### `{}` Inline Props 171 | 172 | Using the inline props syntax, you can pass props and attributes to your components. MDC goes a step further and allows you to pass attributes to markdown native elements like images, links, bold texts, and more. 173 | 174 | To define properties for a component or a markdown element, you need to create a props scope `{}` exactly after the component/element definition. Then you can define the properties inline within this scope using a `key=value` syntax. 175 | 176 | ```md 177 | Inline :component{key="value" key2=value2} 178 | 179 | ::block-component{no-border title="My Component"} 180 | :: 181 | 182 | [Link](https://nuxt.com){class="nuxt"} 183 | 184 | ![Nuxt Logo](https://nuxt.com/assets/design-kit/logo/icon-green.svg){class=".nuxt-logo"} 185 | 186 | `code`{style="color: red"} 187 | 188 | _italic_{style="color: blue"} 189 | 190 | **bold**{style="color: blue"} 191 | ``` 192 | 193 | There are also a couple of sugar syntaxes for common use-cases: 194 | 195 | - `id` attribute: `_italic_{#the_italic_text}` 196 | - `class` attribute: `**bold**{.bold .text.with_attribute}` 197 | - No value (boolean props): `:component{no-border}` 198 | - Single string without any space: `**bold**{class=red}` 199 | 200 | If you want to pass arrays or objects as props to components, you can pass them as a JSON string and prefix the prop key with a colon to automatically decode the JSON string. Note that in this case, you should use single quotes for the value string so you can use double quotes to pass a valid JSON string: 201 | 202 | ```md 203 | ::dropdown{:items='["Nuxt", "Vue", "React"]'} 204 | String Array 205 | :: 206 | 207 | ::dropdown{:items='[1,2,3.5]'} 208 | Number Array 209 | :: 210 | 211 | ::chart{:options='{"responsive": true, "scales": {"y": {"beginAtZero": true}}}'} 212 | Object 213 | :: 214 | ``` 215 | 216 | ### `---` Yaml Props 217 | 218 | The YAML method uses the `---` identifier to declare one prop per line, which can be useful for readability. 219 | 220 | ```md 221 | ::icon-card 222 | --- 223 | icon: IconNuxt 224 | description: Harness the full power of Nuxt and the Nuxt ecosystem. 225 | title: Nuxt Architecture. 226 | --- 227 | :: 228 | ``` 229 | 230 | ### `{{}}` Binding Variables 231 | 232 | The `{{ $doc.variable || 'defaultValue' }}` syntax allows you to bind variables in your Markdown content. This is especially useful when you want to dynamically insert values into your document. 233 | 234 | To use this syntax, simply enclose the variable name within double curly braces, like so: 235 | 236 | ```md 237 | --- 238 | color: blue 239 | --- 240 | 241 | # The color is {{ $doc.color || 'red' }}. 242 | ``` 243 | 244 | ## Contributing 245 | 246 | You can contribute to this module online with CodeSandbox: 247 | 248 | [![Edit remark-mdc](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/nuxtlabs/remark-mdc/tree/main/?fontsize=14&hidenavigation=1&theme=dark) 249 | 250 | Or locally: 251 | 252 | 1. Clone this repository 253 | 2. Install dependencies using `pnpm install` 254 | 3. Start the development server using `pnpm dev` 255 | 256 | ## License 257 | 258 | [MIT License](https://github.com/nuxtlabs/remark-mdc/blob/main/LICENSE) 259 | 260 | Copyright (c) NuxtLabs 261 | -------------------------------------------------------------------------------- /docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | // https://github.com/nuxt-themes/docus 3 | extends: '@nuxt-themes/docus', 4 | 5 | modules: [ 6 | // https://github.com/nuxt-modules/plausible 7 | '@nuxtjs/plausible', 8 | // https://github.com/nuxt/devtools 9 | '@nuxt/devtools', 10 | ], 11 | }) 12 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-mdc-docs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate", 9 | "preview": "nuxi preview", 10 | "lint": "eslint ." 11 | }, 12 | "devDependencies": { 13 | "@nuxt-themes/docus": "^1.15.1", 14 | "@nuxt/devtools": "^2.4.0", 15 | "@nuxt/eslint-config": "^1.3.0", 16 | "@nuxtjs/plausible": "^1.2.0", 17 | "@types/node": "^22.14.1", 18 | "eslint": "^9.25.1", 19 | "nuxt": "^3.16.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/public/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxtlabs/remark-mdc/72dc99ca2a87511ad9d9415f02a4c6a78f389855/docs/public/cover.png -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxtlabs/remark-mdc/72dc99ca2a87511ad9d9415f02a4c6a78f389855/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ], 5 | "lockFileMaintenance": { 6 | "enabled": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/tokens.config.ts: -------------------------------------------------------------------------------- 1 | import { defineTheme } from 'pinceau' 2 | 3 | export default defineTheme({ 4 | }) 5 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { createConfigForNuxt } from '@nuxt/eslint-config/flat' 3 | 4 | // Run `npx @eslint/config-inspector` to inspect the resolved config interactively 5 | export default createConfigForNuxt({ 6 | features: { 7 | // Rules for module authors 8 | tooling: true, 9 | // Rules for formatting 10 | stylistic: true, 11 | }, 12 | dirs: { 13 | src: [ 14 | './playground', 15 | './examples/blog', 16 | ], 17 | }, 18 | }) 19 | .append( 20 | { 21 | rules: { 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | 'vue/multi-word-component-names': 'off', 24 | '@typescript-eslint/no-empty-object-type': 'off', 25 | }, 26 | }, 27 | ) 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-mdc", 3 | "version": "3.6.0", 4 | "description": "Remark plugin to support MDC syntax", 5 | "keywords": [ 6 | "remark", 7 | "mdc" 8 | ], 9 | "repository": "https://github.com/nuxtlabs/remark-mdc", 10 | "license": "MIT", 11 | "exports": "./dist/index.mjs", 12 | "types": "./dist/index.d.ts", 13 | "type": "module", 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "build": "unbuild", 19 | "dev": "nuxi dev playground", 20 | "dev:prepare": "nuxi prepare playground", 21 | "generate": "nuxi generate playground", 22 | "lint": "eslint", 23 | "test:ui": "pnpm lint && vitest --ui --open=false", 24 | "test": "vitest run", 25 | "prepack": "pnpm build", 26 | "release": "release-it", 27 | "typecheck": "nuxt typecheck", 28 | "verify": "npm run dev:prepare && npm run lint && npm run test && npm run typecheck" 29 | }, 30 | "dependencies": { 31 | "@types/mdast": "^4.0.4", 32 | "@types/unist": "^3.0.3", 33 | "flat": "^6.0.1", 34 | "mdast-util-from-markdown": "^2.0.2", 35 | "mdast-util-to-markdown": "^2.1.2", 36 | "micromark": "^4.0.2", 37 | "micromark-core-commonmark": "^2.0.3", 38 | "micromark-factory-space": "^2.0.1", 39 | "micromark-factory-whitespace": "^2.0.1", 40 | "micromark-util-character": "^2.1.1", 41 | "micromark-util-types": "^2.0.2", 42 | "parse-entities": "^4.0.2", 43 | "scule": "^1.3.0", 44 | "stringify-entities": "^4.0.4", 45 | "unified": "^11.0.5", 46 | "unist-util-visit": "^5.0.0", 47 | "unist-util-visit-parents": "^6.0.1", 48 | "yaml": "^2.7.1" 49 | }, 50 | "devDependencies": { 51 | "@nuxt/eslint-config": "^1.3.0", 52 | "@nuxt/kit": "^3.16.2", 53 | "@nuxt/ui": "3.0.2", 54 | "@nuxthub/core": "^0.8.25", 55 | "@nuxtjs/eslint-config-typescript": "latest", 56 | "@types/flat": "^5.0.5", 57 | "@types/js-yaml": "^4.0.9", 58 | "@types/node": "^22.14.1", 59 | "eslint": "^9.25.1", 60 | "eslint-plugin-nuxt": "latest", 61 | "jiti": "^2.4.2", 62 | "nuxt": "^3.16.2", 63 | "release-it": "^19.0.1", 64 | "remark-gfm": "^4.0.1", 65 | "remark-parse": "^11.0.0", 66 | "remark-stringify": "^11.0.0", 67 | "remark-wiki-link": "^2.0.1", 68 | "unbuild": "^3.5.0", 69 | "vitest": "latest" 70 | }, 71 | "release-it": { 72 | "git": { 73 | "commitMessage": "chore(release): release v${version}" 74 | }, 75 | "github": { 76 | "release": true, 77 | "releaseName": "v${version}" 78 | }, 79 | "hooks": { 80 | "after:bump": "npx changelogen@latest --no-commit --no-tag --output --r $(node -p \"require('./package.json').version\")" 81 | } 82 | }, 83 | "packageManager": "pnpm@10.9.0" 84 | } 85 | -------------------------------------------------------------------------------- /playground/assets/css/main.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "@nuxt/ui"; 3 | -------------------------------------------------------------------------------- /playground/composables/useMarkdownGenerator.ts: -------------------------------------------------------------------------------- 1 | import type { Root, Node } from 'mdast' 2 | import type { Processor } from 'unified' 3 | import type { Ref } from 'vue' 4 | 5 | // workaround for kleur 6 | process.env = process.env || {} 7 | 8 | function jsonParser(this: any) { 9 | this.Parser = function (root: any) { 10 | return JSON.parse(root) 11 | } 12 | } 13 | export function useMarkdownGenerator(input: Ref, mdcOptions = ref({})) { 14 | let _stream: Processor | null = null 15 | const markdown = ref('') 16 | const generate = async (ast: object) => { 17 | if (!_stream) { 18 | const unified = await import('unified').then(r => r.unified) 19 | const stringify = await import('remark-stringify').then(r => r.default) 20 | const gfm = await import('remark-gfm').then(r => r.default) 21 | const mdc = await import('../../src').then(r => r.default) 22 | 23 | _stream = await unified() 24 | .use(jsonParser) 25 | .use(gfm) 26 | .use(mdc, mdcOptions.value) 27 | .use(stringify, { 28 | bullet: '-', 29 | }) 30 | } 31 | const res = await _stream.process(JSON.stringify(ast)).then(file => file.value as string) 32 | markdown.value = res 33 | } 34 | 35 | watch(() => input.value, v => generate(v)) 36 | watch(() => mdcOptions.value, () => { 37 | _stream = null 38 | generate(input.value) 39 | }, { deep: true }) 40 | 41 | if (input.value) { 42 | generate(input.value) 43 | } 44 | 45 | return markdown 46 | } 47 | -------------------------------------------------------------------------------- /playground/composables/useMarkdownParser.ts: -------------------------------------------------------------------------------- 1 | import type { Root, Node } from 'mdast' 2 | import type { Preset, Processor } from 'unified' 3 | import type { Ref } from 'vue' 4 | 5 | // workaround for kleur 6 | process.env = process.env || {} 7 | 8 | function compiler(this: any) { 9 | this.Compiler = function (root: any) { 10 | return root 11 | } 12 | } 13 | 14 | export function useMarkdownParser(input: Ref, mdcOptions = ref({})) { 15 | let _stream: Processor | null = null 16 | const ast = ref() 17 | const parse = async (str: string) => { 18 | if (!_stream) { 19 | const unified = await import('unified').then(r => r.unified) 20 | const parse = await import('remark-parse').then(r => r.default) 21 | const gfm = await import('remark-gfm').then(r => r.default) 22 | const mdc = await import('../../src').then(r => r.default) 23 | 24 | _stream = unified() 25 | .use(parse) 26 | .use(gfm) 27 | .use(mdc, mdcOptions.value) 28 | .use(compiler as Preset) 29 | } 30 | const res = await _stream.process(str).then(file => file.result) 31 | ast.value = res 32 | } 33 | 34 | watch(() => input.value, v => parse(v)) 35 | watch(() => mdcOptions.value, () => { 36 | _stream = null 37 | parse(input.value) 38 | }, { deep: true }) 39 | 40 | parse(input.value) 41 | 42 | return ast 43 | } 44 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | 3 | export default defineNuxtConfig({ 4 | modules: [ 5 | '@nuxt/ui', 6 | '@nuxthub/core', 7 | ], 8 | css: ['~/assets/css/main.css'], 9 | }) 10 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 |