Skip to content

Commit

Permalink
feat: add directive plugin and helpers (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
d3m1d0v authored Nov 12, 2024
1 parent 61c41c9 commit 08be405
Show file tree
Hide file tree
Showing 16 changed files with 10,789 additions and 8,661 deletions.
172 changes: 171 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,173 @@
# directive
# Directive syntax parser

[![NPM version](https://img.shields.io/npm/v/@diplodoc/directive.svg?style=flat)](https://www.npmjs.org/package/@diplodoc/directive)

This is a pluggable parser for directive syntax for markdown markup. With it you can easily add implementation for a new block in you markdown project.

## Quickstart

Add new MarkdownIt-plugin or transformer extension, that plug-in a directive parser and register handler for new `block` directive:

```ts
import type MarkdownIt from 'markdown-it';
import {directiveParser, registerContainerDirective} from '@diplodoc/directive';

export function simpleBlockPlugin(): MarkdownIt.PluginSimple {
return (md) => {
md.use(directiveParser());

// register container directive using handler
registerContainerDirective(md, 'block', (state, params) => {
if (!params.content) return false;

let token = state.push('simple_block_open', 'div', 1);
token.attrSet('class', 'simple-block');

tokenizeBlockContent(state, params.content);

token = state.push('simple_block_close', 'div', -1);

return true;
});

// or using config-object
registerContainerDirective(md, {
name: 'block',
match(_params, state) {
// here you can add something to state.env
return true;
},
container: {
tag: 'div',
token: 'simple_block',
attrs: {
class: 'simple-block',
},
},
});
};
}
```

Then attach this plugin/extension to transformer or markdown-it instance:

```ts
import transform from '@diplodoc/transform';

const markup = `
::: block
### Heading 3 inside a simple block
:::
`;

const {result: {html}} = await transform(markup, plugins: [
simpleBlockPlugin(),
]);

// or

import MarkdownIt from 'markdown-it';

const md = new MarkdownIt().use(simpleBlockPlugin());

const html = md.render(markup);
```

`html` variable will have the value:

```html
<div class="simple-block">
<h3>Heading 3 inside a simple block</h3>
</div>
```

## Directive syntax

Supported inline and block directive syntax. Inline directives are found in the text and start with `:`. Block directive is may be leaf block (without content, start with `::`) and container block (with content, start with `:::`).

- Inline: `:name [content] (identifier) {key=value}`
- Leaf block: `::name [inline content] (identifier) {key=value}`
- Container block:
```
:::name [inline content] (identifier) {key=value}
content
:::
```

All of parameters groups – `[]`, `()`, `{}` – are optional, but their order is fixed.

- `[]` – used for something like inline-content;
- `()` – used for something like required identifier (id, url, etc.);
- `{}` – used to pass optional named arguments / attributes / `key=value` pairs.

> Note: inline directives disabled by default. You can enable them using `enableInlineDirectives()` helper.
## Helpers

### Enable or disable some of directive types

- `enableInlineDirectives(md: MarkdownIt): void` – enable parsing of inline directives;

- `disableInlineDirectives(md: MarkdownIt): void` – disable parsing of inline directives;

- `enableBlockDirectives(md: MarkdownIt): void` – enable parsing of leaf and container blocks directives;

- `disableBlockDirectives(md: MarkdownIt): void` – disable parsing of leaf and container blocks directives.

### Register directive handler

- `registerInlineDirective()` – register handler for new inline directive. Name of directive used in markdown markup after `:`, for example: `:dir`.

```ts
function registerInlineDirective(
md: MarkdownIt,
name: string,
handler: InlineDirectiveHandler,
): void;
```

- `registerLeafBlockDirective()` – register handler for new leaf block directive.

```ts
function registerLeafBlockDirective(
md: MarkdownIt,
name: string,
handler: LeafBlockDirectiveHandler,
): void;
```

- `registerContainerDirective()` – register handler for new container block or configure it using config-object.
```ts
function registerContainerDirective(md: MarkdownIt, config: ContainerDirectiveConfig): void;
function registerContainerDirective(
md: MarkdownIt,
name: string,
handler: ContainerDirectiveHandler,
): void;
```

### Tokenizers

- `tokenizeInlineContent()` – can be used inside inline directive handler for parse and tokenize content of `[]`-section.

```ts
function tokenizeInlineContent(
state: MarkdownIt['inline']['State'],
content: InlineContent,
): void;
```

- `tokenizeBlockContent()` – can be used inside block directive handler for parse and tokenize content between opening `:::name` and closing `:::` markup of container block directive.

```ts
function tokenizeBlockContent(state: MarkdownIt['block']['State'], content: BlockContent): void;
```

- `createBlockInlineToken()` – can be used inside block directive handler for creating token with inline content of `[]`-section.

```ts
function createBlockInlineToken(
state: MarkdownIt['block']['State'],
params: BlockDirectiveParams,
): MarkdownIt.Token;
```
4 changes: 2 additions & 2 deletions esbuild/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import {build} from 'esbuild';

import tsConfig from '../tsconfig.json' assert { type: "json" };
import tsConfig from '../tsconfig.json' assert {type: 'json'};

const outDir = 'build';

Expand All @@ -11,7 +11,7 @@ const common = {
bundle: true,
sourcemap: true,
target: tsConfig.compilerOptions.target,
tsconfig: './tsconfig.json',
tsconfig: './tsconfig.publish.json',
};

build({
Expand Down
31 changes: 17 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@
"scripts": {
"build": "run-p build:*",
"build:js": "./esbuild/build.mjs",
"build:declarations": "tsc --emitDeclarationOnly --outDir ./build",
"test": "cd tests && npm ci && npm test",
"build:declarations": "tsc --project tsconfig.publish.json --emitDeclarationOnly --outDir ./build",
"test": "cd tests && npm ci && npm test -- --ci",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run build",
"lint": "lint update && lint",
"lint:fix": "lint update && lint fix",
"pre-commit": "lint update && lint-staged",
"prepare": "husky"
},
"dependencies": {
"markdown-it-directive": "2.0.2"
},
"devDependencies": {
"@diplodoc/lint": "^1.2.0",
"@diplodoc/tsconfig": "^1.0.2",
Expand Down
7 changes: 7 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const RULE = {
Inline: 'inline_directive',
Block: 'block_directive',
} as const;

export const CONTAINER_KEY = Symbol();
export const LEAF_BLOCK_KEY = Symbol();
24 changes: 24 additions & 0 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable valid-jsdoc */
import type MarkdownIt from 'markdown-it';

import {RULE} from '../const';

/** Enable parsing of inline directives */
export function enableInlineDirectives(md: MarkdownIt): void {
md.inline.ruler.enable(RULE.Inline, true);
}

/** Disable parsing of inline directives */
export function disableInlineDirectives(md: MarkdownIt): void {
md.inline.ruler.disable(RULE.Inline, true);
}

/** Enable parsing of leaf block and container directives */
export function enableBlockDirectives(md: MarkdownIt): void {
md.block.ruler.enable(RULE.Block, true);
}

/** Disable parsing of leaf block and container directives */
export function disableBlockDirectives(md: MarkdownIt): void {
md.block.ruler.disable(RULE.Block, true);
}
Loading

0 comments on commit 08be405

Please sign in to comment.