Compare commits

...

38 commits
2.0.1 ... 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
16 changed files with 507 additions and 330 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.1",
"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": "^9.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.45.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"
}
}
}

326
readme.md
View file

@ -8,9 +8,9 @@
[![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).
## Contents
@ -19,10 +19,14 @@ such).
* [Install](#install)
* [Use](#use)
* [API](#api)
* [`unified().use(remarkDirective)`](#unifieduseremarkdirective)
* [`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)
@ -36,56 +40,54 @@ such).
This package is a [unified][] ([remark][]) plugin to add support for directives:
one syntax for arbitrary extensions in markdown.
You can use this with some more code to match your specific needs, to allow for
anything from callouts, citations, styled blocks, forms, embeds, spoilers, etc.
unified is an AST (abstract syntax tree) based transform project.
**remark** is everything unified that relates to markdown.
The layer under remark is called mdast, which is only concerned with syntax
trees.
Another layer underneath is micromark, which is only concerned with parsing.
This package is a small wrapper to integrate all of these.
## When should I use this?
Directives are one of the four ways to extend markdown: an arbitrary extension
syntax (see [Extending markdown](https://github.com/micromark/micromark#extending-markdown)
in micromarks docs for the alternatives and more info).
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.
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).
In Node.js (12.20+, 14.14+, 16.0+), install with [npm][]:
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][]:
```sh
npm install remark-directive
```
In Deno with [Skypack][]:
In Deno with [`esm.sh`][esmsh]:
```js
import remarkDirective from 'https://cdn.skypack.dev/remark-directive@2?dts'
import remarkDirective from 'https://esm.sh/remark-directive@3'
```
In browsers with [Skypack][]:
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import remarkDirective from 'https://cdn.skypack.dev/remark-directive@2?min'
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}
@ -100,22 +102,25 @@ 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 {read} from 'to-vfile'
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'
main()
async function main() {
const file = await unified()
.use(remarkParse)
.use(remarkDirective)
@ -126,22 +131,26 @@ async function main() {
.process(await read('example.md'))
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.
/** @type {import('unified').Plugin<[], import('mdast').Root>} */
function myRemarkPlugin() {
return (tree) => {
visit(tree, (node) => {
/**
* @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
@ -151,7 +160,7 @@ function myRemarkPlugin() {
}
```
Now, running `node example` yields:
…then running `node example.js` yields:
```html
<main id="readme">
@ -164,13 +173,48 @@ Now, running `node example` yields:
## 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.
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
@ -181,15 +225,31 @@ 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.
/** @type {import('unified').Plugin<[], import('mdast').Root>} */
function myRemarkPlugin() {
/**
* @param {Root} tree
* Tree.
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
return (tree, file) => {
visit(tree, (node) => {
visit(tree, function (node) {
if (
node.type === 'textDirective' ||
node.type === 'containerDirective' ||
node.type === 'leafDirective' ||
node.type === 'containerDirective'
node.type === 'textDirective'
) {
if (node.name !== 'youtube') return
@ -197,8 +257,16 @@ function myRemarkPlugin() {
const attributes = node.attributes || {}
const id = attributes.id
if (node.type === 'textDirective') file.fail('Text directives for `youtube` not supported', node)
if (!id) file.fail('Missing video id', node)
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 = {
@ -223,7 +291,7 @@ function myRemarkPlugin() {
::youtube[Video of a cat in a box]{#01ab2cd3efg}
```
…then running `node example` yields:
…then running `node example.js` yields:
```html
<h1>Cat videos</h1>
@ -232,23 +300,37 @@ function myRemarkPlugin() {
### Example: Styled blocks
Note: This is sometimes called admonitions, callouts, etc.
> 👉 **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.
/** @type {import('unified').Plugin<[], import('mdast').Root>} */
function myRemarkPlugin() {
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return (tree) => {
visit(tree, (node) => {
if (
node.type === 'textDirective' ||
node.type === 'containerDirective' ||
node.type === 'leafDirective' ||
node.type === 'containerDirective'
node.type === 'textDirective'
) {
if (node.name !== 'note') return
@ -256,7 +338,7 @@ function myRemarkPlugin() {
const tagName = node.type === 'textDirective' ? 'span' : 'div'
data.hName = tagName
data.hProperties = h(tagName, node.attributes).properties
data.hProperties = h(tagName, node.attributes || {}).properties
}
})
}
@ -285,36 +367,57 @@ if you chose xxx, you should also use yyy somewhere…
</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
This plugin applies a micromark extensions to parse the syntax.
See its readme for parse details:
* [`micromark-extension-directive`](https://github.com/micromark/micromark-extension-directive#syntax)
See [*Syntax* in
`micromark-extension-directive`](https://github.com/micromark/micromark-extension-directive#syntax).
## Syntax tree
This plugin applies one mdast utility to build and serialize the AST.
See its readme for the node types supported in the tree:
* [`mdast-util-directive`](https://github.com/syntax-tree/mdast-util-directive#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][].
If youre working with the syntax tree, make sure to import this plugin
somewhere in your types, as that registers the new node types in the tree.
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
/** @typedef {import('remark-directive')} */
/**
* @import {} from 'mdast-util-directive'
* @import {Root} from 'mdast'
*/
import {visit} from 'unist-util-visit'
/** @type {import('unified').Plugin<[], import('mdast').Root>} */
export default function myRemarkPlugin() => {
function myRemarkPlugin() {
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
return (tree) => {
visit(tree, (node) => {
// `node` can now be one of the nodes for directives.
visit(tree, function (node) {
console.log(node) // `node` can now be one of the nodes for directives.
})
}
}
@ -322,30 +425,31 @@ export default function myRemarkPlugin() => {
## Compatibility
Projects maintained by the unified collective are compatible with all maintained
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
As of now, that is Node.js 12.20+, 14.14+, and 16.0+.
Our projects sometimes work with older versions, but this is not guaranteed.
This plugin works with unified 9+ and remark 14+.
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)
— support GFM (autolink literals, strikethrough, tables, tasklists)
* [`remark-github`](https://github.com/remarkjs/remark-github)
— link references to commits, issues, pull-requests, and users, like on
GitHub
— support GFM (autolink literals, footnotes, strikethrough, tables,
tasklists)
* [`remark-frontmatter`](https://github.com/remarkjs/remark-frontmatter)
— support frontmatter (YAML, TOML, and more)
* [`remark-math`](https://github.com/remarkjs/remark-math)
— support math
* [`remark-mdx`](https://github.com/mdx-js/mdx/tree/main/packages/remark-mdx)
— support MDX (ESM, JSX, expressions)
## Contribute
@ -375,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
@ -391,32 +495,50 @@ abide by its terms.
[npm]: https://docs.npmjs.com/cli/install
[skypack]: https://www.skypack.dev
[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
[unified]: https://github.com/unifiedjs/unified
[remark]: https://github.com/remarkjs/remark
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting
[typescript]: https://www.typescriptlang.org
[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
[micromark]: https://github.com/micromark/micromark
[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"]
}