Skip to content

Commit

Permalink
feat: register leaf blocks and containers separately
Browse files Browse the repository at this point in the history
  • Loading branch information
d3m1d0v committed Nov 7, 2024
1 parent 3d06c73 commit 397d316
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 74 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,23 @@ All of parameters groups – `[]`, `()`, `{}` – are optional, but their order
): void;
```

- `registerBlockDirective()` – register handler for new block directive – leaf and container block together.
- `registerLeafBlockDirective()` – register handler for new leaf block directive.

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

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

Expand Down
3 changes: 3 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ export const RULE = {
Inline: 'inline_directive',
Block: 'block_directive',
} as const;

export const CONTAINER_KEY = Symbol();
export const LEAF_BLOCK_KEY = Symbol();
5 changes: 5 additions & 0 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +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);
}
92 changes: 60 additions & 32 deletions src/helpers/registrars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@

import type MarkdownIt from 'markdown-it';
import type {
DirectiveBlockHandler,
DirectiveBlockHandlerArgs,
DirectiveInlineHandlerArgs,
MarkdownItWithDirectives,
} from 'markdown-it-directive';
import type {
BlockDirectiveConfig,
BlockDirectiveHandler,
BlockDirectiveParams,
ContainerDirectiveConfig,
ContainerDirectiveHandler,
ContainerDirectiveParams,
DirectiveAttrs,
DirectiveDests,
DirectiveDestsOrig,
InlineDirectiveHandler,
InlineDirectiveParams,
LeafBlockDirectiveHandler,
LeafBlockDirectiveParams,
MdItWithHandlers,
} from '../types';

import {isFunction, isString} from '../utils';
import {CONTAINER_KEY, LEAF_BLOCK_KEY} from '../const';

import {createBlockInlineToken, tokenizeBlockContent} from './tokenizers';

Expand All @@ -32,32 +37,56 @@ export function registerInlineDirective(
};
}

export function registerBlockDirective(md: MarkdownIt, config: BlockDirectiveConfig): void;
export function registerBlockDirective(
export function registerLeafBlockDirective(
md: MarkdownIt,
name: string,
handler: BlockDirectiveHandler,
handler: LeafBlockDirectiveHandler,
): void {
(md as MdItWithHandlers)[LEAF_BLOCK_KEY] ||= {};
(md as MdItWithHandlers)[LEAF_BLOCK_KEY][name] = handler;

(md as MarkdownItWithDirectives).blockDirectives[name] = getBlockDefaultHandler(md, name);
}

export function registerContainerDirective(md: MarkdownIt, config: ContainerDirectiveConfig): void;
export function registerContainerDirective(
md: MarkdownIt,
name: string,
handler: ContainerDirectiveHandler,
): void;
export function registerBlockDirective(
export function registerContainerDirective(
md: MarkdownIt,
nameOrConfig: string | BlockDirectiveConfig,
maybeHandler?: BlockDirectiveHandler,
nameOrConfig: string | ContainerDirectiveConfig,
maybeHandler?: ContainerDirectiveHandler,
): void {
const [name, handler]: [string, BlockDirectiveHandler] = isString(nameOrConfig)
const [name, handler]: [string, ContainerDirectiveHandler] = isString(nameOrConfig)
? [nameOrConfig, maybeHandler!]
: [nameOrConfig.name, buildContainerHandler(nameOrConfig)];

(md as MarkdownItWithDirectives).blockDirectives[name] = (args) => {
const params: BlockDirectiveParams = buildBlockParams(args);
return handler(args.state, params);
};
(md as MdItWithHandlers)[CONTAINER_KEY] ||= {};
(md as MdItWithHandlers)[CONTAINER_KEY][name] = handler;

(md as MarkdownItWithDirectives).blockDirectives[name] = getBlockDefaultHandler(md, name);
}

function buildContainerHandler(config: BlockDirectiveConfig): BlockDirectiveHandler {
if (config.type !== 'container') {
throw new Error(`Unknown type in ${config.name} directive config: ${config.type}`);
}
function getBlockDefaultHandler(md: MarkdownIt, name: string): DirectiveBlockHandler {
return (args) => {
const containerHandler = (md as MdItWithHandlers)[CONTAINER_KEY]?.[name];
const leafBlockHandler = (md as MdItWithHandlers)[LEAF_BLOCK_KEY]?.[name];

if (isString(args.content)) {
if (containerHandler) {
return containerHandler(args.state, buildContainerParams(args));
}
} else if (leafBlockHandler) {
return leafBlockHandler(args.state, buildLeafBlockParams(args));
}

return false;
};
}

function buildContainerHandler(config: ContainerDirectiveConfig): ContainerDirectiveHandler {
return (state, params) => {
if (!params.content) {
return false;
Expand All @@ -78,7 +107,6 @@ function buildContainerHandler(config: BlockDirectiveConfig): BlockDirectiveHand
let token = state.push(container.token + '_open', container.tag, 1);
token.map = [params.startLine, params.endLine];
token.markup = ':::' + config.name;
token.block = true;
if (container.attrs) {
const attrs: DirectiveAttrs = isFunction(container.attrs)
? container.attrs(params)
Expand All @@ -88,7 +116,6 @@ function buildContainerHandler(config: BlockDirectiveConfig): BlockDirectiveHand

if (inlineContent) {
token = state.push(inlineContent.token + '_open', inlineContent.tag, 1);
token.block = true;
if (inlineContent.attrs) {
const attrs: DirectiveAttrs = isFunction(inlineContent.attrs)
? inlineContent.attrs(params)
Expand All @@ -99,13 +126,11 @@ function buildContainerHandler(config: BlockDirectiveConfig): BlockDirectiveHand
token = createBlockInlineToken(state, params);

token = state.push(inlineContent.token + '_close', inlineContent.tag, -1);
token.block = true;
}

if (content) {
token = state.push(content.token + '_open', content.tag, 1);
token.map = [params.startLine + 1, params.endLine - 1];
token.block = true;
if (content.attrs) {
const attrs: DirectiveAttrs = isFunction(content.attrs)
? content.attrs(params)
Expand All @@ -122,11 +147,9 @@ function buildContainerHandler(config: BlockDirectiveConfig): BlockDirectiveHand

if (content) {
token = state.push(content.token + '_close', content.tag, -1);
token.block = true;
}

token = state.push(container.token + '_close', container.tag, -1);
token.block = true;

return true;
};
Expand Down Expand Up @@ -157,8 +180,8 @@ function buildInlineParams(args: DirectiveInlineHandlerArgs): InlineDirectivePar
return params;
}

function buildBlockParams(args: DirectiveBlockHandlerArgs): BlockDirectiveParams {
const params: BlockDirectiveParams = {
function buildLeafBlockParams(args: DirectiveBlockHandlerArgs): LeafBlockDirectiveParams {
const params: LeafBlockDirectiveParams = {
startLine: args.directiveStartLine,
endLine: args.directiveEndLine,
};
Expand All @@ -179,14 +202,19 @@ function buildBlockParams(args: DirectiveBlockHandlerArgs): BlockDirectiveParams
endPos: args.inlineContentEnd!,
};
}
if (args.content !== undefined) {
params.content = {
raw: args.content,
return params;
}

function buildContainerParams(args: DirectiveBlockHandlerArgs): ContainerDirectiveParams {
const params = buildLeafBlockParams(args);
return {
...params,
content: {
raw: args.content!,
startLine: args.contentStartLine!,
endLine: args.contentEndLine!,
};
}
return params;
},
};
}

function buildDests(orig: DirectiveDestsOrig): DirectiveDests {
Expand Down
12 changes: 9 additions & 3 deletions src/helpers/tokenizers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type MarkdownIt from 'markdown-it';
import type {BlockDirectiveParams, InlineDirectiveParams, StateBlock, StateInline} from '../types';
import type {
ContainerDirectiveParams,
InlineDirectiveParams,
LeafBlockDirectiveParams,
StateBlock,
StateInline,
} from '../types';

export function tokenizeInlineContent(
state: StateInline,
Expand All @@ -19,7 +25,7 @@ export function tokenizeInlineContent(

export function tokenizeBlockContent(
state: StateBlock,
content: NonNullable<BlockDirectiveParams['content']>,
content: NonNullable<ContainerDirectiveParams['content']>,
parentType?: string,
): void {
const oldParent = state.parentType;
Expand All @@ -38,7 +44,7 @@ export function tokenizeBlockContent(

export function createBlockInlineToken(
state: StateBlock,
{inlineContent, startLine}: BlockDirectiveParams,
{inlineContent, startLine}: LeafBlockDirectiveParams,
): MarkdownIt.Token {
const token = state.push('inline', '', 0);
token.children = [];
Expand Down
14 changes: 10 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ export {
disableBlockDirectives,
disableInlineDirectives,
} from './helpers';
export {registerBlockDirective, registerInlineDirective} from './helpers/registrars';
export {
registerContainerDirective,
registerLeafBlockDirective,
registerInlineDirective,
} from './helpers/registrars';
export {
tokenizeBlockContent,
tokenizeInlineContent,
Expand All @@ -20,9 +24,11 @@ export {
export type {
DirectiveAttrs,
DirectiveDests,
BlockDirectiveParams,
BlockDirectiveConfig,
BlockDirectiveHandler,
LeafBlockDirectiveParams,
LeafBlockDirectiveHandler,
ContainerDirectiveConfig,
ContainerDirectiveParams,
ContainerDirectiveHandler,
InlineDirectiveParams,
InlineDirectiveHandler,
} from './types';
Expand Down
32 changes: 24 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type MarkdownIt from 'markdown-it';
import type StateBlock from 'markdown-it/lib/rules_block/state_block';
import type StateInline from 'markdown-it/lib/rules_inline/state_inline';
import type {CONTAINER_KEY, LEAF_BLOCK_KEY} from './const';

export type {StateBlock, StateInline};

Expand All @@ -25,15 +27,18 @@ type BlockContent = {
endLine: number;
};

export type BlockDirectiveParams = {
export type LeafBlockDirectiveParams = {
startLine: number;
endLine: number;
attrs?: DirectiveAttrs;
dests?: DirectiveDests;
content?: BlockContent;
inlineContent?: InlineContent;
};

export type ContainerDirectiveParams = LeafBlockDirectiveParams & {
content: BlockContent;
};

export type InlineDirectiveParams = {
startPos: number;
endPos: number;
Expand All @@ -42,19 +47,25 @@ export type InlineDirectiveParams = {
content?: InlineContent;
};

export type BlockDirectiveHandler = (state: StateBlock, params: BlockDirectiveParams) => boolean;
export type ContainerDirectiveHandler = (
state: StateBlock,
params: ContainerDirectiveParams,
) => boolean;
export type LeafBlockDirectiveHandler = (
state: StateBlock,
params: LeafBlockDirectiveParams,
) => boolean;
export type InlineDirectiveHandler = (state: StateInline, params: InlineDirectiveParams) => boolean;

type TokensDesc = {
tag: string;
token: string;
attrs?: DirectiveAttrs | ((params: BlockDirectiveParams) => DirectiveAttrs);
attrs?: DirectiveAttrs | ((params: LeafBlockDirectiveParams) => DirectiveAttrs);
};

export type BlockDirectiveConfig = {
export type ContainerDirectiveConfig = {
name: string;
type: 'container';
match: (params: BlockDirectiveParams, state: StateBlock) => boolean;
match: (params: LeafBlockDirectiveParams, state: StateBlock) => boolean;
container: TokensDesc;
inlineContent?: TokensDesc & {
/** @default true */
Expand All @@ -65,6 +76,11 @@ export type BlockDirectiveConfig = {
contentTokenizer?: (
state: StateBlock,
content: BlockContent,
params: BlockDirectiveParams,
params: LeafBlockDirectiveParams,
) => void;
};

export interface MdItWithHandlers extends MarkdownIt {
[CONTAINER_KEY]: Record<string, ContainerDirectiveHandler>;
[LEAF_BLOCK_KEY]: Record<string, LeafBlockDirectiveHandler>;
}
Loading

0 comments on commit 397d316

Please sign in to comment.