remark-flexible-code-titles
Version:
Remark plugin to add title or/and container for code blocks with customizable properties in markdown
710 lines (542 loc) • 22.1 kB
Markdown
# remark-flexible-code-titles
[![npm version][badge-npm-version]][url-npm-package]
[![npm downloads][badge-npm-download]][url-npm-package]
[![publish to npm][badge-publish-to-npm]][url-publish-github-actions]
[![code-coverage][badge-codecov]][url-codecov]
[![type-coverage][badge-type-coverage]][url-github-package]
[![typescript][badge-typescript]][url-typescript]
[![license][badge-license]][url-license]
This package is a [**unified**][unified] ([**remark**][remark]) plugin **to add title or/and container for code blocks with customizable properties in markdown.**
[**unified**][unified] is a project that transforms content with abstract syntax trees (ASTs) using the new parser [**micromark**][micromark]. [**remark**][remark] adds support for markdown to unified. [**mdast**][mdast] is the Markdown Abstract Syntax Tree (AST) which is a specification for representing markdown in a syntax tree.
**This plugin is a remark plugin that transforms the mdast.**
## When should I use this?
This plugin is useful if you want to **add title and container or any of two** for code blocks in markdown.
The plugin `remark-flexible-code-titles` can:
- add `title` node above the `code` node, providing _custom tag name, custom class name and also additional properties_.
- add `container` node for the `code` node, providing _custom tag name, custom class name and also additional properties_.
- correct the syntax of code highligting directives on behalf of related rehype plugins (like [rehype-prism-plus][rehypeprismplus])
- handle the titles even if there is no language provided,
- handle the titles composed by more than one word (handle spaces in the title),
- provide a fallback language as an option if the language is missing.
## Installation
This package is suitable for ESM only. In Node.js (16.0+), install with npm:
```bash
npm install remark-flexible-code-titles
```
or
```bash
yarn add remark-flexible-code-titles
```
## Usage
Say we have the following file, `example.md`, which consists a code block. The code block's language is "javascript" and its title is "file.js" specified _after a colon_ **`:`**
````markdown
```javascript:file.js
/* code block */
```
````
And our module, `example.js`, looks as follows:
```javascript
import { read } from "to-vfile";
import remark from "remark";
import gfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import remarkCodeTitles from "remark-flexible-code-titles";
main();
async function main() {
const file = await remark()
.use(gfm)
.use(remarkCodeTitles)
.use(remarkRehype)
.use(rehypeStringify)
.process(await read("example.md"));
console.log(String(file));
}
```
Now, running `node example.js` yields:
```html
<div class="remark-code-container">
<div class="remark-code-title">title.js</div>
<!-- <pre><code> elements -->
</div>
```
Without `remark-flexible-code-titles`, you’d get:
```html
<pre>
<code class="language-javascript:file.js"><!-- code block --></code>
</pre>
```
You can use the `remark-flexible-code-titles` **without a language**, _setting the title just after a colon_ **`:`**
````markdown
```:title
This is a line of pseudo code.
```
````
## Options
All options are **optional** and some of them have **default values**.
```tsx
type RestrictedRecord = Record<string, unknown> & { className?: never };
type PropertyFunction = (language?: string, title?: string) => RestrictedRecord;
use(remarkCodeTitles, {
title?: boolean; // default is true
titleTagName?: string; // default is "div"
titleClassName?: string; // default is "remark-code-title"
titleProperties?: PropertyFunction;
container?: boolean; // default is true
containerTagName?: string; // default is "div"
containerClassName?: string; // default is "remark-code-container"
containerProperties?: PropertyFunction;
handleMissingLanguageAs?: string;
tokenForSpaceInTitle?: string;
} as CodeTitleOptions);
```
#### `title`
It is a **boolean** option for whether or not to add a `title` node.
By default, it is `true`, meaningly adds a `title` node if a title is provided in the language part of the code block.
```javascript
use(remarkCodeTitles, {
title: false,
});
```
If the option is `false`, the plugin will not add any `title` node.
````markdown
```javascript:file.js
console.log("Hi")
```
````
```html
<div class="remark-code-container">
<!-- there is no title element ! -->
<pre>
<code class="language-javascript">console.log("Hi")</code>
<pre>
</div>
```
#### `titleTagName`
It is a **string** option for providing custom HTML tag name for `title` nodes.
By default, it is `div`.
```javascript
use(remarkCodeTitles, {
titleTagName: "span",
});
```
Now, the title element tag names will be `span`.
````markdown
```javascript:file.js
console.log("Hi")
```
````
```html
<div class="remark-code-container">
<span class="remark-code-title">file.js</span>
<pre>
<code class="language-javascript">console.log("Hi")</code>
<pre>
</div>
```
#### `titleClassName`
It is a **string** option for providing custom class name for `title` nodes.
By default, it is `remark-code-title`, and all title elements' class names will contain `remark-code-title`.
```javascript
use(remarkCodeTitles, {
titleClassName: "custom-code-title",
});
```
Now, the title element class names will be `custom-code-title`.
````markdown
```javascript:file.js
console.log("Hi")
```
````
```html
<div class="remark-code-container">
<div class="custom-code-title">file.js</div>
<pre>
<code class="language-javascript">console.log("Hi")</code>
<pre>
</div>
```
#### `titleProperties`
It is a **callback** `(language?: string, title?: string) => Record<string, unknown> & { className?: never }` option to set additional properties for the `title` node.
The callback function that takes the `language` and the `title` as optional arguments and returns **object** which is going to be used for adding additional properties into the `title` node.
**The `className` key is forbidden and effectless in the returned object.**
```javascript
use(remarkCodeTitles, {
titleProperties(language, title) {
return {
title,
["data-language"]: language,
};
},
});
```
Now, the title elements will contain `title` and `data-color` properties.
````markdown
```javascript:file.js
console.log("Hi")
```
````
```html
<div class="remark-code-container">
<div class="remark-code-title" title="file.js" data-language="javascript">file.js</div>
<pre>
<code class="language-javascript">console.log("Hi")</code>
<pre>
</div>
```
#### `container`
It is a **boolean** option for whether or not to add a `container` node.
By default, it is `true`, meaningly adds a `container` node.
```javascript
use(remarkCodeTitles, {
container: false,
});
```
If the option is `false`, the plugin doesn't add any `container` node.
````markdown
```javascript:file.js
console.log("Hi")
```
````
```html
<!-- there is no container element ! -->
<div class="remark-code-title">file.js</div>
<pre>
<code class="language-javascript">console.log("Hi")</code>
<pre>
```
#### `containerTagName`
It is a **string** option for providing custom HTML tag name for `container` nodes.
By default, it is `div`.
```javascript
use(remarkCodeTitles, {
containerTagName: "section",
});
```
Now, the container element tag names will be `section`.
````markdown
```javascript:file.js
console.log("Hi")
```
````
```html
<section class="remark-code-container">
<div class="remark-code-title">file.js</div>
<pre>
<code class="language-javascript">console.log("Hi")</code>
<pre>
</div>
```
#### `containerClassName`
It is a **string** option for providing custom class name for `container` nodes.
By default, it is `remark-code-container`, and all container elements' class names will contain `remark-code-container`.
```javascript
use(remarkCodeTitles, {
containerClassName: "custom-code-container",
});
```
Now, the container element class names will be `custom-code-container`.
````markdown
```javascript:file.js
console.log("Hi")
```
````
```html
<div class="custom-code-container">
<div class="remark-code-title">file.js</div>
<pre>
<code class="language-javascript">console.log("Hi")</code>
<pre>
</div>
```
#### `containerProperties`
It is a **callback** `(language?: string, title?: string) => Record<string, unknown> & { className?: never }` option to set additional properties for the `container` node.
The callback function that takes the `language` and the `title` as optional arguments and returns **object** which is going to be used for adding additional properties into the `container` node.
**The `className` key is forbidden and effectless in the returned object.**
```javascript
use(remarkCodeTitles, {
titleProperties(language, title) {
return {
title,
["data-language"]: language,
};
},
});
```
Now, the container elements will contain `title` and `data-color` properties.
````markdown
```javascript:file.js
console.log("Hi")
```
````
```html
<div class="remark-code-container" title="file.js" data-language="javascript">
<div class="remark-code-title">file.js</div>
<pre>
<code class="language-javascript">console.log("Hi")</code>
<pre>
</div>
```
#### `handleMissingLanguageAs`
It is a **string** option for providing a fallback language if the language is missing.
```javascript
use(remarkCodeTitles, {
handleMissingLanguageAs: "unknown",
});
```
Now, the class name of `<code>` elements will contain `language-unknown` if the language is missing. If this option was not set, the `class` property would not be presented in the `<code>`element.
````markdown
```
Hello from code block
```
````
```html
<div class="remark-code-container">
<pre>
<code class="language-unknown">Hello from code block</code>
<pre>
</div>
```
#### `tokenForSpaceInTitle`
It is a **string** option for composing the title with more than one word.
Normally, the `remark-flexible-code-titles` can match a code title which is the word that comes after a colon and ends in the first space it encounters. This option is provided to replace a space with a token in order to specify a code title consisting of more than one word.
```javascript
use(remarkCodeTitles, {
tokenForSpaceInTitle: "@",
});
```
Now, the titles that have more than one word can be set using the token `@`.
````markdown
```bash:Useful@Bash@Commands
mkdir project-directory
```
````
```html
<div class="remark-code-container">
<div class="remark-code-title">Useful Bash Commands</div>
<pre>
<code class="language-bash">mkdir project-directory</code>
<pre>
</div>
```
## Examples:
#### Example for only container
````markdown
```javascript:file.js
let me = "ipikuka";
```
````
```javascript
use(remarkCodeTitles, {
title: false,
containerTagName: "section",
containerClassName: "custom-code-wrapper",
containerProperties(language, title) {
return {
["data-language"]: language,
title,
};
},
});
```
is going to produce the container `section` element like below:
```html
<section class="custom-code-wrapper" data-language="javascript" title="file.js">
<pre>
<code class="language-javascript">let me = "ipikuka";</code>
</pre>
</section>
```
#### Example for only title
````markdown
```javascript:file.js
let me = "ipikuka";
```
````
```javascript
use(remarkCodeTitles, {
container: false,
titleTagName: "span",
titleClassName: "custom-code-title",
titleProperties: (language, title) => {
["data-language"]: language,
title,
},
});
```
is going to produce the title `span` element just before the code block, like below:
```html
<span class="custom-code-title" data-language="javascript" title="file.js">file.js</span>
<pre>
<code class="language-javascript">let me = "ipikuka";</code>
</pre>
```
#### Example for line highlighting and numbering
> [!NOTE]
> You need a rehype plugin like [**rehype-prism-plus**][rehypeprismplus] or [**rehype-highlight-code-lines**][rehypehighlightcodelines] for line highlighting and numbering features.
````markdown
```javascript:file.js {1,3-6} showLineNumbers
let me = "ipikuka";
```
````
The `remark-flexible-code-titles` takes the line highlighting and numbering syntax into consideration, and passes that information to other remark and rehype plugins.
But, if you want to highlight and number the lines **without specifying language**, you will get the language of the code block as for example `language-{2}` like strings. Let me give an example:
````markdown
```{2} showLineNumbers
This is a line which is going to be numbered with rehype-prism-plus.
This is a line which is going to be highlighted and numbered with rehype-prism-plus.
```
````
The above markdown, with no language provided, will lead to produce a mdast "code" node as follows:
```json
{
"type": "code",
"lang": "{2}",
"meta": "showLineNumbers"
}
```
As a result, the html `code` element will have wrong language `language-{2}`:
_(The class `code-highlight` in the `code` element is added by the rehype plugin `rehype-prism-plus`)_
```html
<code class="language-{2} code-highlight">...</code>
```
The `remark-flexible-code-titles` not only adds `title` and `container` elements but also **corrects the language** producing the below `mdast` which will lead the `<code>` element has accurate language or not have language as it sould be.
```json
{
"type": "code",
"lang": null,
"meta": "{2} showLineNumbers"
}
```
```html
<code class="code-highlight">
<!-- highlighted and numbered lines -->
</code>
```
If there is no space between the parts (_title, line range string and "showLineNumbers"_), or there is extra spaces inside the _line range string_, line highlighting or numbering features by the rehype plugin will not work. **The `remark-flexible-code-titles` can handles and corrects this kind of mis-typed situations**.
````markdown
```typescript:title{ 1, 3 - 6 }showLineNumbers
content
```
````
There is mis-typed syntax in above markdown example; and without `remark-flexible-code-titles` will cause to produce the following `mdast`; and the rehype plugin not to work properly:
```json
{
"type": "code",
"lang": "typescript:title{",
"meta": " 1, 3 - 6 }showLineNumbers"
}
```
The `remark-flexible-code-titles` will correct the syntax and ensure to produce the following `mdast` and `html`:
```json
{
"type": "code",
"lang": "typescript",
"meta": "{1,3-6} showLineNumbers"
}
```
```html
<div class="remark-code-container">
<div class="remark-code-title">title</div>
<pre>
<code class="language-typescript code-highlight">
<!-- highlighted and numbered lines -->
</code>
</pre>
</div>
```
#### Example for providing a title without any language
You can provide a `title` without any language just using a colon **`:`** at the beginning.
````markdown
```:title
content
```
````
```html
<div class="remark-code-container">
<div class="remark-code-title">title</div>
<pre>
<code>content</code>
</pre>
</div>
```
### Another flexible usage
You can use `remark-flexible-code-titles` **just for only correcting language, line highlighting and numbering syntax** on behalf of related rehype plugins.
```javascript
use(remarkCodeTitles, {
container: false,
title: false,
});
```
Now, the `remark-flexible-code-titles` will not add any node, but will correct language, line highlighting and numbering syntax.
## Syntax tree
This plugin only modifies the mdast (markdown abstract syntax tree) as explained.
## Types
This package is fully typed with [TypeScript][url-typescript]. The plugin options' type is exported as `CodeTitleOptions`.
## Compatibility
This plugin works with `unified` version 6+ and `remark` version 7+. It is compatible with `mdx` version 2+.
## Security
Use of `remark-flexible-code-titles` does not involve rehype (hast) or user content so there are no openings for cross-site scripting (XSS) attacks.
## My Plugins
I like to contribute the Unified / Remark / MDX ecosystem, so I recommend you to have a look my plugins.
### My Remark Plugins
- [`remark-flexible-code-titles`](https://www.npmjs.com/package/remark-flexible-code-titles)
– Remark plugin to add titles or/and containers for the code blocks with customizable properties
- [`remark-flexible-containers`](https://www.npmjs.com/package/remark-flexible-containers)
– Remark plugin to add custom containers with customizable properties in markdown
- [`remark-ins`](https://www.npmjs.com/package/remark-ins)
– Remark plugin to add `ins` element in markdown
- [`remark-flexible-paragraphs`](https://www.npmjs.com/package/remark-flexible-paragraphs)
– Remark plugin to add custom paragraphs with customizable properties in markdown
- [`remark-flexible-markers`](https://www.npmjs.com/package/remark-flexible-markers)
– Remark plugin to add custom `mark` element with customizable properties in markdown
- [`remark-flexible-toc`](https://www.npmjs.com/package/remark-flexible-toc)
– Remark plugin to expose the table of contents via `vfile.data` or via an option reference
- [`remark-mdx-remove-esm`](https://www.npmjs.com/package/remark-mdx-remove-esm)
– Remark plugin to remove import and/or export statements (mdxjsEsm)
### My Rehype Plugins
- [`rehype-pre-language`](https://www.npmjs.com/package/rehype-pre-language)
– Rehype plugin to add language information as a property to `pre` element
- [`rehype-highlight-code-lines`](https://www.npmjs.com/package/rehype-highlight-code-lines)
– Rehype plugin to add line numbers to code blocks and allow highlighting of desired code lines
- [`rehype-code-meta`](https://www.npmjs.com/package/rehype-code-meta)
– Rehype plugin to copy `code.data.meta` to `code.properties.metastring`
- [`rehype-image-toolkit`](https://www.npmjs.com/package/rehype-image-toolkit)
– Rehype plugin to enhance Markdown image syntax `![]()` and Markdown/MDX media elements (`<img>`, `<audio>`, `<video>`) by auto-linking bracketed or parenthesized image URLs, wrapping them in `<figure>` with optional captions, unwrapping images/videos/audio from paragraph, parsing directives in title for styling and adding attributes, and dynamically converting images into `<video>` or `<audio>` elements based on file extension.
### My Recma Plugins
- [`recma-mdx-escape-missing-components`](https://www.npmjs.com/package/recma-mdx-escape-missing-components)
– Recma plugin to set the default value `() => null` for the Components in MDX in case of missing or not provided so as not to throw an error
- [`recma-mdx-change-props`](https://www.npmjs.com/package/recma-mdx-change-props)
– Recma plugin to change the `props` parameter into the `_props` in the `function _createMdxContent(props) {/* */}` in the compiled source in order to be able to use `{props.foo}` like expressions. It is useful for the `next-mdx-remote` or `next-mdx-remote-client` users in `nextjs` applications.
- [`recma-mdx-change-imports`](https://www.npmjs.com/package/recma-mdx-change-imports)
– Recma plugin to convert import declarations for assets and media with relative links into variable declarations with string URLs, enabling direct asset URL resolution in compiled MDX.
- [`recma-mdx-import-media`](https://www.npmjs.com/package/recma-mdx-import-media)
– Recma plugin to turn media relative paths into import declarations for both markdown and html syntax in MDX.
- [`recma-mdx-import-react`](https://www.npmjs.com/package/recma-mdx-import-react)
– Recma plugin to ensure getting `React` instance from the arguments and to make the runtime props `{React, jsx, jsxs, jsxDev, Fragment}` is available in the dynamically imported components in the compiled source of MDX.
- [`recma-mdx-html-override`](https://www.npmjs.com/package/recma-mdx-html-override)
– Recma plugin to allow selected raw HTML elements to be overridden via MDX components.
- [`recma-mdx-interpolate`](https://www.npmjs.com/package/recma-mdx-interpolate)
– Recma plugin to enable interpolation of identifiers wrapped in curly braces within the `alt`, `src`, `href`, and `title` attributes of markdown link and image syntax in MDX.
## License
[MIT License](./LICENSE) © ipikuka
[unified]: https://github.com/unifiedjs/unified
[micromark]: https://github.com/micromark/micromark
[remark]: https://github.com/remarkjs/remark
[remarkplugins]: https://github.com/remarkjs/remark/blob/main/doc/plugins.md
[mdast]: https://github.com/syntax-tree/mdast
[rehypeprismplus]: https://github.com/timlrx/rehype-prism-plus
[rehypehighlightcodelines]: https://github.com/ipikuka/rehype-highlight-code-lines
[badge-npm-version]: https://img.shields.io/npm/v/remark-flexible-code-titles
[badge-npm-download]:https://img.shields.io/npm/dt/remark-flexible-code-titles
[url-npm-package]: https://www.npmjs.com/package/remark-flexible-code-titles
[url-github-package]: https://github.com/ipikuka/remark-flexible-code-titles
[badge-license]: https://img.shields.io/github/license/ipikuka/remark-flexible-code-titles
[url-license]: https://github.com/ipikuka/remark-flexible-code-titles/blob/main/LICENSE
[badge-publish-to-npm]: https://github.com/ipikuka/remark-flexible-code-titles/actions/workflows/publish.yml/badge.svg
[url-publish-github-actions]: https://github.com/ipikuka/remark-flexible-code-titles/actions/workflows/publish.yml
[badge-typescript]: https://img.shields.io/npm/types/remark-flexible-code-titles
[url-typescript]: https://www.typescriptlang.org/
[badge-codecov]: https://codecov.io/gh/ipikuka/remark-flexible-code-titles/graph/badge.svg?token=LJKU8SQ935
[url-codecov]: https://codecov.io/gh/ipikuka/remark-flexible-code-titles
[badge-type-coverage]: https://img.shields.io/badge/dynamic/json.svg?label=type-coverage&prefix=%E2%89%A5&suffix=%&query=$.typeCoverage.atLeast&uri=https%3A%2F%2Fraw.githubusercontent.com%2Fipikuka%2Fremark-flexible-code-titles%2Fmaster%2Fpackage.json