Compare commits

...

42 commits
2.0.0 ... main

Author SHA1 Message Date
f67c8452ab 🔧 fix (bun.lockb)
Some checks failed
main / lts/hydrogen (push) Has been cancelled
main / node (push) Has been cancelled
2025-02-07 02:44:11 +09:00
5e61c16964 🔧 fix (lib/index.js, package.json)
Some checks are pending
main / lts/hydrogen (push) Waiting to run
main / node (push) Waiting to run
2025-02-07 02:43:00 +09:00
722e6bee28 🔧 directive looks like zenn (bun.lockb, package.json)
Some checks are pending
main / lts/hydrogen (push) Waiting to run
main / node (push) Waiting to run
2025-02-06 21:57:28 +09:00
Titus Wormer
103c85adba
3.0.1 2025-01-22 16:14:39 +01:00
Titus Wormer
b685b00060
Add types, docs for options 2025-01-22 16:13:18 +01:00
Titus Wormer
7ca8974f14
Refactor to use @imports 2025-01-22 16:05:40 +01:00
Titus Wormer
8f29f1e456
Add declaration maps 2025-01-22 16:02:33 +01:00
Titus Wormer
7e9a5a78b8
Refactor package.json 2025-01-22 16:01:59 +01:00
Titus Wormer
f2eefc2418
Remove license year 2025-01-22 15:59:40 +01:00
Titus Wormer
a29bc053db
Refactor .prettierignore 2025-01-22 15:59:30 +01:00
Titus Wormer
9f8e165510
Refactor .editorconfig 2025-01-22 15:59:02 +01:00
Titus Wormer
6fe12e7771
Add .tsbuildinfo to .gitignore 2025-01-22 15:58:44 +01:00
Titus Wormer
320c56aacf
Update Actions 2025-01-22 15:58:22 +01:00
Titus Wormer
7831904974
Update dev-dependencies 2025-01-22 15:57:52 +01:00
Riley Tomasek
876a45ad4c
Fix typo
Closes GH-15.

Reviewed-by: Titus Wormer <tituswormer@gmail.com>
2023-11-06 11:17:48 +01:00
Titus Wormer
e1027d13bd
3.0.0 2023-09-18 13:28:45 +02:00
Titus Wormer
edef661a1f
Fix typo 2023-09-18 13:28:26 +02:00
Titus Wormer
733932dd91
Change to use exports 2023-09-18 13:28:24 +02:00
Titus Wormer
20c4a6b5e1
Change to require Node.js 16 2023-09-18 13:27:32 +02:00
Titus Wormer
9e219a3603
Refactor to move code to lib/ 2023-09-18 13:27:07 +02:00
Titus Wormer
a952370494
Update @types/mdast, unified, utilities 2023-09-18 13:24:08 +02:00
Titus Wormer
c223cd8e31
Refactor docs 2023-09-18 13:24:03 +02:00
Titus Wormer
d2b583b5ba
Refactor code-style 2023-09-15 19:40:53 +02:00
Titus Wormer
6e6c0ed89f
Refactor to use node:test 2023-09-15 19:27:30 +02:00
Titus Wormer
055840baf2
Refactor .npmrc 2023-09-15 19:24:18 +02:00
Titus Wormer
086fca9a90
Refactor Actions 2023-09-15 19:24:08 +02:00
Titus Wormer
f525f4c172
Refactor package.json 2023-09-15 19:23:53 +02:00
Titus Wormer
bd953c935e
Refactor tsconfig.json 2023-09-15 19:19:57 +02:00
Titus Wormer
bf126a045a
Update dev-dependencies 2023-09-15 19:19:15 +02:00
Christian Murphy
c40caa2682
Add ignore-scripts to .npmrc
Closes GH-10.

Reviewed-by: Titus Wormer <tituswormer@gmail.com>
2023-01-12 10:17:17 +01:00
Christian Murphy
3d93cc085c
Fix this type in TS 4.9
Closes GH-11.

Reviewed-by: Titus Wormer <tituswormer@gmail.com>
2023-01-12 10:15:00 +01:00
Titus
decd688110
Replace skypack w/ esm.sh 2022-04-01 20:05:17 +02:00
Titus
2cfea20ae7
Fix link 2021-11-24 12:03:57 +01:00
Titus Wormer
6f1683ea00
Add remark-mdx to list of related projects 2021-11-17 11:37:17 +01:00
Titus Wormer
92038d21e0
Update dev-dependencies 2021-11-17 11:37:15 +01:00
Titus
eb52b1adc1
Fix typo 2021-11-11 13:07:45 +01:00
Titus Wormer
c369fcb50b
Update docs 2021-10-26 22:24:44 +02:00
Titus
bddb81b066
Add mention of footnotes to remark-gfm 2021-10-26 12:24:12 +02:00
Titus Wormer
52501b7de6
2.0.1 2021-10-26 09:16:59 +02:00
Titus
a56e309f80
Update docs for newcomers
Related-to: unifiedjs/unified#168.
Closes GH-6.

Reviewed-by: Remco Haszing <remcohaszing@gmail.com>
Reviewed-by: Jeff Caldwell <nemo.omen@gmail.com>
2021-10-26 09:13:37 +02:00
Titus Wormer
86376c703c
Update dev-dependencies 2021-10-03 19:05:46 +02:00
Titus Wormer
faac6e3148
Remove reference to remark-footnotes 2021-10-03 19:05:43 +02:00
16 changed files with 658 additions and 275 deletions

View file

@ -1,9 +1,9 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

View file

@ -1,9 +1,3 @@
name: bb
on:
issues:
types: [opened, reopened, edited, closed, labeled, unlabeled]
pull_request_target:
types: [opened, reopened, edited, closed, labeled, unlabeled]
jobs:
main:
runs-on: ubuntu-latest
@ -11,3 +5,9 @@ jobs:
- uses: unifiedjs/beep-boop-beta@main
with:
repo-token: ${{secrets.GITHUB_TOKEN}}
name: bb
on:
issues:
types: [closed, edited, labeled, opened, reopened, unlabeled]
pull_request_target:
types: [closed, edited, labeled, opened, reopened, unlabeled]

View file

@ -1,21 +1,21 @@
name: main
on:
- pull_request
- push
jobs:
main:
name: ${{matrix.node}}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dcodeIO/setup-node-nvm@master
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{matrix.node}}
- run: npm install
- run: npm test
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v5
strategy:
matrix:
node:
- lts/erbium
- lts/hydrogen
- node
name: main
on:
- pull_request
- push

9
.gitignore vendored
View file

@ -1,6 +1,9 @@
coverage/
node_modules/
.DS_Store
*.d.ts
*.log
*.map
*.tsbuildinfo
.DS_Store
coverage/
node_modules/
yarn.lock
!/index.d.ts

1
.npmrc
View file

@ -1 +1,2 @@
ignore-scripts=true
package-lock=false

View file

@ -1,3 +1,2 @@
coverage/
*.json
*.md
coverage/

1
.remarkignore Normal file
View file

@ -0,0 +1 @@
test/fixtures/

15
index.d.ts vendored Normal file
View file

@ -0,0 +1,15 @@
import type {ToMarkdownOptions} from 'mdast-util-directive'
export {default} from './lib/index.js'
/**
* Configuration for `remark-directive`.
*
* Currently supports
* `collapseEmptyAttributes`,
* `preferShortcut`,
* `preferUnquoted`,
* `quoteSmart`,
* and `quote` as serialization options.
*/
export interface Options extends ToMarkdownOptions {}

View file

@ -1,36 +1,2 @@
/**
* @typedef {import('mdast').Root} Root
*
* @typedef {import('mdast-util-directive')} DoNotTouchAsThisImportIncludesDirectivesInTree
*/
import {directive} from 'micromark-extension-directive'
import {directiveFromMarkdown, directiveToMarkdown} from 'mdast-util-directive'
/**
* Plugin to support the generic directives proposal (`:cite[smith04]`,
* `::youtube[Video of a cat in a box]{v=01ab2cd3efg}`, and such).
*
* @type {import('unified').Plugin<void[], Root>}
*/
export default function remarkDirective() {
const data = this.data()
add('micromarkExtensions', directive())
add('fromMarkdownExtensions', directiveFromMarkdown)
add('toMarkdownExtensions', directiveToMarkdown)
/**
* @param {string} field
* @param {unknown} value
*/
function add(field, value) {
const list = /** @type {unknown[]} */ (
// Other extensions
/* c8 ignore next 2 */
data[field] ? data[field] : (data[field] = [])
)
list.push(value)
}
}
// Note: types exposed from `index.d.ts`.
export {default} from './lib/index.js'

38
lib/index.js Normal file
View file

@ -0,0 +1,38 @@
/**
* @import {} from 'mdast-util-directive'
* @import {Root} from 'mdast'
* @import {} from 'remark-arse'
* @import {} from 'remark-stringify'
* @import {Processor} from 'unified'
*/
import {
directiveFromMarkdown,
directiveToMarkdown,
} from "mdast-util-directive";
import { directive } from "micromark-extension-directive";
/**
* Add support for generic directives.
*
* ###### Notes
*
* Doesnt handle the directives: create your own plugin to do that.
*
* @returns {undefined}
* Nothing.
*/
export default function remarkDirective() {
const data = this.data();
const micromarkExtensions =
data.micromarkExtensions || (data.micromarkExtensions = []);
const fromMarkdownExtensions =
data.fromMarkdownExtensions || (data.fromMarkdownExtensions = []);
const toMarkdownExtensions =
data.toMarkdownExtensions || (data.toMarkdownExtensions = []);
micromarkExtensions.push(directive());
fromMarkdownExtensions.push(directiveFromMarkdown());
toMarkdownExtensions.push(directiveToMarkdown());
}

View file

@ -1,6 +1,6 @@
(The MIT License)
Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
Copyright (c) Titus Wormer <tituswormer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View file

@ -1,85 +1,106 @@
{
"name": "remark-directive",
"version": "2.0.0",
"description": "remark plugin to support directives",
"license": "MIT",
"keywords": [
"unified",
"remark",
"remark-plugin",
"plugin",
"mdast",
"markdown",
"generic",
"directive",
"container"
],
"repository": "remarkjs/remark-directive",
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"bugs": "https://github.com/remarkjs/remark-directive/issues",
"contributors": ["Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"],
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-directive": "^3.0.0",
"micromark-extension-directive": "git+https://git.v-sli.me/HidemaruOwO/micromark-extension-directive.git#fix.1",
"unified": "^11.0.0"
},
"description": "remark plugin to support directives",
"devDependencies": {
"@types/node": "^22.0.0",
"c8": "^10.0.0",
"is-hidden": "^2.0.0",
"prettier": "^3.0.0",
"remark": "^15.0.0",
"remark-cli": "^12.0.0",
"remark-preset-wooorm": "^10.0.0",
"to-vfile": "^8.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"xo": "^0.60.0"
},
"exports": "./index.js",
"files": ["index.d.ts", "index.js", "lib/"],
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"contributors": [
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
"keywords": [
"container",
"directive",
"generic",
"markdown",
"mdast",
"plugin",
"remark-plugin",
"remark",
"unified"
],
"sideEffects": false,
"type": "module",
"main": "index.js",
"types": "index.d.ts",
"files": [
"index.d.ts",
"index.js"
],
"dependencies": {
"@types/mdast": "^3.0.0",
"mdast-util-directive": "^2.0.0",
"micromark-extension-directive": "^2.0.0",
"unified": "^10.0.0"
},
"devDependencies": {
"@types/tape": "^4.0.0",
"c8": "^7.0.0",
"is-hidden": "^2.0.0",
"prettier": "^2.0.0",
"remark": "^14.0.0",
"remark-cli": "^10.0.0",
"remark-preset-wooorm": "^8.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"to-vfile": "^7.0.0",
"type-coverage": "^2.0.0",
"typescript": "^4.0.0",
"xo": "^0.43.0"
},
"scripts": {
"build": "rimraf \"test/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo --ignore-pattern test/ && prettier . -w --loglevel warn && xo --fix",
"test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"
},
"license": "MIT",
"name": "remark-directive",
"prettier": {
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"bracketSpacing": false,
"singleQuote": true,
"semi": false,
"trailingComma": "none"
},
"xo": {
"prettier": true
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
},
"remarkConfig": {
"plugins": [
"preset-wooorm"
]
"plugins": ["remark-preset-wooorm"]
},
"repository": "remarkjs/remark-directive",
"scripts": {
"build": "tsc --build --clean && tsc --build && type-coverage",
"format": "remark --frail --output --quiet -- . && prettier --log-level warn --write -- . && xo --fix",
"prepack": "npm run build && npm run format",
"test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"
},
"sideEffects": false,
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true
"strict": true
},
"type": "module",
"version": "3.0.1",
"xo": {
"overrides": [
{
"files": ["**/*.d.ts"],
"rules": {
"@typescript-eslint/array-type": [
"error",
{
"default": "generic"
}
],
"@typescript-eslint/ban-types": [
"error",
{
"extendDefaults": true
}
],
"@typescript-eslint/consistent-type-definitions": [
"error",
"interface"
]
}
},
{
"files": ["test/**/*.js"],
"rules": {
"no-await-in-loop": "off"
}
}
],
"prettier": true,
"rules": {
"logical-assignment-operators": "off"
}
}
}

476
readme.md
View file

@ -8,31 +8,86 @@
[![Backers][backers-badge]][collective]
[![Chat][chat-badge]][chat]
[**remark**][remark] plugin to support the [generic directives proposal][prop]
(`:cite[smith04]`, `::youtube[Video of a cat in a box]{v=01ab2cd3efg}`, and
such).
**[remark][]** plugin to support the [generic directives
proposal][commonmark-prop] (`:cite[smith04]`,
`::youtube[Video of a cat in a box]{v=01ab2cd3efg}`, and such).
## Important!
## Contents
This plugin is made for the new parser in remark
([`micromark`](https://github.com/micromark/micromark),
see [`remarkjs/remark#536`](https://github.com/remarkjs/remark/pull/536)).
Use this plugin for remark 13+.
* [What is this?](#what-is-this)
* [When should I use this?](#when-should-i-use-this)
* [Install](#install)
* [Use](#use)
* [API](#api)
* [`unified().use(remarkDirective[, options])`](#unifieduseremarkdirective-options)
* [`Options`](#options)
* [Examples](#examples)
* [Example: YouTube](#example-youtube)
* [Example: Styled blocks](#example-styled-blocks)
* [Authoring](#authoring)
* [HTML](#html)
* [CSS](#css)
* [Syntax](#syntax)
* [Syntax tree](#syntax-tree)
* [Types](#types)
* [Compatibility](#compatibility)
* [Security](#security)
* [Related](#related)
* [Contribute](#contribute)
* [License](#license)
## What is this?
This package is a [unified][] ([remark][]) plugin to add support for directives:
one syntax for arbitrary extensions in markdown.
## When should I use this?
Directives are one of the four ways to extend markdown: an arbitrary extension
syntax (see [Extending markdown][micromark-extending-markdown] in micromarks
docs for the alternatives and more info).
This mechanism works well when you control the content: who authors it, what
tools handle it, and where its displayed.
When authors can read a guide on how to embed a tweet but are not expected to
know the ins and outs of HTML or JavaScript.
Directives dont work well if you dont know who authors content, what tools
handle it, and where it ends up.
Example use cases are a docs website for a project or product, or blogging
tools and static site generators.
If you *just* want to turn markdown into HTML (with maybe a few extensions such
as this one), we recommend [`micromark`][micromark] with
[`micromark-extension-directive`][micromark-extension-directive] instead.
If you dont use plugins and want to access the syntax tree, you can use
[`mdast-util-from-markdown`][mdast-util-from-markdown] with
[`mdast-util-directive`][mdast-util-directive].
## Install
This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c):
Node 12+ is needed to use it and it must be `import`ed instead of `require`d.
[npm][]:
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][]:
```sh
npm install remark-directive
```
In Deno with [`esm.sh`][esmsh]:
```js
import remarkDirective from 'https://esm.sh/remark-directive@3'
```
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import remarkDirective from 'https://esm.sh/remark-directive@3?bundle'
</script>
```
## Use
Say we have the following file, `example.md`:
Say our document `example.md` contains:
```markdown
:::main{#readme}
@ -47,46 +102,55 @@ A :i[lovely] language know as :abbr[HTML]{title="HyperText Markup Language"}.
:::
```
And our module, `example.js`, looks as follows:
…and our module `example.js` contains:
```js
import {readSync} from 'to-vfile'
import {reporter} from 'vfile-reporter'
import {unified} from 'unified'
import remarkParse from 'remark-parse'
import remarkDirective from 'remark-directive'
import remarkRehype from 'remark-rehype'
/**
* @import {} from 'mdast-util-directive'
* @import {} from 'mdast-util-to-hast'
* @import {Root} from 'mdast'
*/
import {h} from 'hastscript'
import rehypeFormat from 'rehype-format'
import rehypeStringify from 'rehype-stringify'
import remarkDirective from 'remark-directive'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {read} from 'to-vfile'
import {unified} from 'unified'
import {visit} from 'unist-util-visit'
import {h} from 'hastscript'
const file = readSync('example.md')
unified()
const file = await unified()
.use(remarkParse)
.use(remarkDirective)
.use(customPlugin)
.use(myRemarkPlugin)
.use(remarkRehype)
.use(rehypeFormat)
.use(rehypeStringify)
.process(file)
.then((file) => {
console.error(reporter(file))
console.log(String(file))
})
.process(await read('example.md'))
// This plugin is just an example! You can handle directives however you please!
function customPlugin() {
return (tree) => {
visit(tree, (node) => {
console.log(String(file))
// This plugin is an example to let users write HTML with directives.
// Its informative but rather useless.
// See below for others examples.
function myRemarkPlugin() {
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return function (tree) {
visit(tree, function (node) {
if (
node.type === 'textDirective' ||
node.type === 'containerDirective' ||
node.type === 'leafDirective' ||
node.type === 'containerDirective'
node.type === 'textDirective'
) {
const data = node.data || (node.data = {})
const hast = h(node.name, node.attributes)
const hast = h(node.name, node.attributes || {})
data.hName = hast.tagName
data.hProperties = hast.properties
@ -96,14 +160,9 @@ function customPlugin() {
}
```
Now, running `node example` yields:
```txt
example.md: no issues found
```
…then running `node example.js` yields:
```html
example.md: no issues found
<main id="readme">
<p>Lorem<br>ipsum.</p>
<hr class="red">
@ -114,34 +173,283 @@ example.md: no issues found
## API
This package exports no identifiers.
The default export is `remarkDirective`.
The default export is [`remarkDirective`][api-remark-directive].
### `unified().use(remarkDirective)`
### `unified().use(remarkDirective[, options])`
Configures remark so that it can parse and serialize directives.
Doesnt handle the directives: [create your own plugin][create-plugin] to do
that.
See the [micromark extension for the syntax][syntax] and the
[mdast utility for the syntax tree][syntax-tree].
Add support for generic directives.
###### Parameters
* `options` ([`Options`][api-options], optional)
— configuration
###### Returns
Nothing (`undefined`).
###### Notes
Doesnt handle the directives:
[create your own plugin][unified-create-plugin] to do that.
### `Options`
Configuration (TypeScript type).
###### Fields
* `collapseEmptyAttributes`
(`boolean`, default: `true`)
— collapse empty attributes: get `title` instead of `title=""`
* `preferShortcut`
(`boolean`, default: `true`)
— prefer `#` and `.` shortcuts for `id` and `class`
* `preferUnquoted`
(`boolean`, default: `false`)
— leave attributes unquoted if that results in less bytes
* `quoteSmart`
(`boolean`, default: `false`)
— use the other quote if that results in less bytes
* `quote`
(`'"'` or `"'"`,
default: the [`quote`][quote] used by `remark-stringify` for titles)
— preferred quote to use around attribute values
## Examples
### Example: YouTube
This example shows how directives can be used for YouTube embeds.
Its based on the example in Use above.
If `myRemarkPlugin` was replaced with this function:
```js
/**
* @import {} from 'mdast-util-directive'
* @import {} from 'mdast-util-to-hast'
* @import {Root} from 'mdast'
* @import {VFile} from 'vfile'
*/
import {visit} from 'unist-util-visit'
// This plugin is an example to turn `::youtube` into iframes.
function myRemarkPlugin() {
/**
* @param {Root} tree
* Tree.
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
return (tree, file) => {
visit(tree, function (node) {
if (
node.type === 'containerDirective' ||
node.type === 'leafDirective' ||
node.type === 'textDirective'
) {
if (node.name !== 'youtube') return
const data = node.data || (node.data = {})
const attributes = node.attributes || {}
const id = attributes.id
if (node.type === 'textDirective') {
file.fail(
'Unexpected `:youtube` text directive, use two colons for a leaf directive',
node
)
}
if (!id) {
file.fail('Unexpected missing `id` on `youtube` directive', node)
}
data.hName = 'iframe'
data.hProperties = {
src: 'https://www.youtube.com/embed/' + id,
width: 200,
height: 200,
frameBorder: 0,
allow: 'picture-in-picture',
allowFullScreen: true
}
}
})
}
}
```
…and `example.md` contains:
```markdown
# Cat videos
::youtube[Video of a cat in a box]{#01ab2cd3efg}
```
…then running `node example.js` yields:
```html
<h1>Cat videos</h1>
<iframe src="https://www.youtube.com/embed/01ab2cd3efg" width="200" height="200" frameborder="0" allow="picture-in-picture" allowfullscreen>Video of a cat in a box</iframe>
```
### Example: Styled blocks
> 👉 **Note**: This is sometimes called admonitions, callouts, etc.
This example shows how directives can be used to style blocks.
Its based on the example in Use above.
If `myRemarkPlugin` was replaced with this function:
```js
/**
* @import {} from 'mdast-util-directive'
* @import {} from 'mdast-util-to-hast'
* @import {Root} from 'mdast'
*/
import {h} from 'hastscript'
import {visit} from 'unist-util-visit'
// This plugin is an example to turn `::note` into divs, passing arbitrary
// attributes.
function myRemarkPlugin() {
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return (tree) => {
visit(tree, (node) => {
if (
node.type === 'containerDirective' ||
node.type === 'leafDirective' ||
node.type === 'textDirective'
) {
if (node.name !== 'note') return
const data = node.data || (node.data = {})
const tagName = node.type === 'textDirective' ? 'span' : 'div'
data.hName = tagName
data.hProperties = h(tagName, node.attributes || {}).properties
}
})
}
}
```
…and `example.md` contains:
```markdown
# How to use xxx
You can use xxx.
:::note{.warning}
if you chose xxx, you should also use yyy somewhere…
:::
```
…then running `node example` yields:
```html
<h1>How to use xxx</h1>
<p>You can use xxx.</p>
<div class="warning">
<p>if you chose xxx, you should also use yyy somewhere…</p>
</div>
```
## Authoring
When authoring markdown with directives, keep in mind that they dont work in
most places.
On your own site it can be great!
## HTML
You can define how directives are turned into HTML.
If directives are not handled, they do not emit anything.
## CSS
How to display directives is left as an exercise for the reader.
## Syntax
See [*Syntax* in
`micromark-extension-directive`](https://github.com/micromark/micromark-extension-directive#syntax).
## Syntax tree
See [*Syntax tree* in
`mdast-util-directive`](https://github.com/syntax-tree/mdast-util-directive#syntax-tree).
## Types
This package is fully typed with [TypeScript][].
It exports no additional options.
If youre working with the syntax tree, you can register the new node types
with `@types/mdast` by adding a reference:
```js
/**
* @import {} from 'mdast-util-directive'
* @import {Root} from 'mdast'
*/
import {visit} from 'unist-util-visit'
function myRemarkPlugin() {
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return (tree) => {
visit(tree, function (node) {
console.log(node) // `node` can now be one of the nodes for directives.
})
}
}
```
## Compatibility
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line, `remark-directive@^3`,
compatible with Node.js 16.
## Security
Use of `remark-directive` does not involve [**rehype**][rehype]
([**hast**][hast]) or user content so there are no openings for [cross-site
scripting (XSS)][xss] attacks.
Use of `remark-directive` does not involve **[rehype][]** ([hast][]) or user
content so there are no openings for [cross-site scripting (XSS)][wiki-xss]
attacks.
## Related
* [`remark-gfm`](https://github.com/remarkjs/remark-gfm)
— GFM
* [`remark-github`](https://github.com/remarkjs/remark-github)
— Autolink references like in GitHub issues, PRs, and comments
* [`remark-footnotes`](https://github.com/remarkjs/remark-footnotes)
— Footnotes
— support GFM (autolink literals, footnotes, strikethrough, tables,
tasklists)
* [`remark-frontmatter`](https://github.com/remarkjs/remark-frontmatter)
— Frontmatter (YAML, TOML, and more)
— support frontmatter (YAML, TOML, and more)
* [`remark-math`](https://github.com/remarkjs/remark-math)
— Math
— support math
* [`remark-mdx`](https://github.com/mdx-js/mdx/tree/main/packages/remark-mdx)
— support MDX (ESM, JSX, expressions)
## Contribute
@ -171,9 +479,9 @@ abide by its terms.
[downloads]: https://www.npmjs.com/package/remark-directive
[size-badge]: https://img.shields.io/bundlephobia/minzip/remark-directive.svg
[size-badge]: https://img.shields.io/bundlejs/size/remark-directive
[size]: https://bundlephobia.com/result?p=remark-directive
[size]: https://bundlejs.com/?q=remark-directive
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
@ -187,30 +495,50 @@ abide by its terms.
[npm]: https://docs.npmjs.com/cli/install
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[esmsh]: https://esm.sh
[health]: https://github.com/remarkjs/.github
[contributing]: https://github.com/remarkjs/.github/blob/HEAD/contributing.md
[contributing]: https://github.com/remarkjs/.github/blob/main/contributing.md
[support]: https://github.com/remarkjs/.github/blob/HEAD/support.md
[support]: https://github.com/remarkjs/.github/blob/main/support.md
[coc]: https://github.com/remarkjs/.github/blob/HEAD/code-of-conduct.md
[coc]: https://github.com/remarkjs/.github/blob/main/code-of-conduct.md
[license]: license
[author]: https://wooorm.com
[remark]: https://github.com/remarkjs/remark
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
[rehype]: https://github.com/rehypejs/rehype
[commonmark-prop]: https://talk.commonmark.org/t/generic-directives-plugins-syntax/444
[hast]: https://github.com/syntax-tree/hast
[prop]: https://talk.commonmark.org/t/generic-directives-plugins-syntax/444
[mdast-util-directive]: https://github.com/syntax-tree/mdast-util-directive
[create-plugin]: https://unifiedjs.com/learn/guide/create-a-plugin/
[mdast-util-from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown
[syntax]: https://github.com/micromark/micromark-extension-directive#syntax
[micromark]: https://github.com/micromark/micromark
[syntax-tree]: https://github.com/syntax-tree/mdast-util-directive#syntax-tree
[micromark-extension-directive]: https://github.com/micromark/micromark-extension-directive
[micromark-extending-markdown]: https://github.com/micromark/micromark#extending-markdown
[quote]: https://github.com/remarkjs/remark/tree/main/packages/remark-stringify#options
[rehype]: https://github.com/rehypejs/rehype
[remark]: https://github.com/remarkjs/remark
[typescript]: https://www.typescriptlang.org
[unified]: https://github.com/unifiedjs/unified
[unified-create-plugin]: https://unifiedjs.com/learn/guide/create-a-plugin/
[wiki-xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
[api-remark-directive]: #unifieduseremarkdirective-options
[api-options]: #options

View file

@ -2,66 +2,77 @@
* @typedef {import('mdast').Root} Root
*/
import fs from 'node:fs'
import path from 'node:path'
import test from 'tape'
import {readSync} from 'to-vfile'
import {unified} from 'unified'
import {remark} from 'remark'
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import process from 'node:process'
import test from 'node:test'
import {isHidden} from 'is-hidden'
import directive from '../index.js'
import {remark} from 'remark'
import remarkDirective from 'remark-directive'
test('directive()', (t) => {
t.doesNotThrow(() => {
remark().use(directive).freeze()
}, 'should not throw if not passed options')
t.doesNotThrow(() => {
unified().use(directive).freeze()
}, 'should not throw if without parser or compiler')
t.end()
test('remarkDirective', async function (t) {
await t.test('should expose the public api', async function () {
assert.deepEqual(Object.keys(await import('remark-directive')).sort(), [
'default'
])
})
test('fixtures', (t) => {
const base = path.join('test', 'fixtures')
const entries = fs.readdirSync(base).filter((d) => !isHidden(d))
await t.test('should not throw if not passed options', async function () {
assert.doesNotThrow(function () {
remark().use(remarkDirective).freeze()
})
})
})
t.plan(entries.length)
test('fixtures', async function (t) {
const base = new URL('fixtures/', import.meta.url)
const folders = await fs.readdir(base)
let index = -1
while (++index < entries.length) {
const fixture = entries[index]
t.test(fixture, (st) => {
const file = readSync(path.join(base, fixture, 'input.md'))
const input = String(file)
const outputPath = path.join(base, fixture, 'output.md')
const treePath = path.join(base, fixture, 'tree.json')
const proc = remark().use(directive).freeze()
const actual = proc.parse(file)
/** @type {string} */
let output
while (++index < folders.length) {
const folder = folders[index]
if (isHidden(folder)) continue
await t.test(folder, async function () {
const folderUrl = new URL(folder + '/', base)
const inputUrl = new URL('input.md', folderUrl)
const outputUrl = new URL('output.md', folderUrl)
const treeUrl = new URL('tree.json', folderUrl)
const input = String(await fs.readFile(inputUrl))
/** @type {Root} */
let expected
/** @type {string} */
let output
const processor = remark().use(remarkDirective)
const actual = processor.parse(input)
try {
expected = JSON.parse(String(fs.readFileSync(treePath)))
} catch {
// New fixture.
fs.writeFileSync(treePath, JSON.stringify(actual, null, 2) + '\n')
expected = actual
}
try {
output = fs.readFileSync(outputPath, 'utf8')
output = String(await fs.readFile(outputUrl))
} catch {
output = input
}
st.deepEqual(actual, expected, 'tree')
st.equal(String(proc.processSync(file)), output, 'process')
try {
if ('UPDATE' in process.env) {
throw new Error('Updating…')
}
st.end()
expected = JSON.parse(String(await fs.readFile(treeUrl)))
} catch {
expected = actual
// New fixture.
await fs.writeFile(treeUrl, JSON.stringify(actual, undefined, 2) + '\n')
}
assert.deepEqual(actual, expected)
assert.equal(String(await processor.process(input)), String(output))
})
}
})

View file

@ -1,16 +1,16 @@
{
"include": ["test/**/*.js", "*.js"],
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020"],
"module": "ES2020",
"moduleResolution": "node",
"allowJs": true,
"checkJs": true,
"customConditions": ["development"],
"declarationMap": true,
"declaration": true,
"emitDeclarationOnly": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"strict": true
}
"exactOptionalPropertyTypes": true,
"lib": ["es2022"],
"module": "node16",
"strict": true,
"target": "es2022"
},
"exclude": ["coverage/", "node_modules/"],
"include": ["**/*.js", "index.d.ts"]
}