@diplodoc/transform
Version:
A simple transformer of text in YFM (Yandex Flavored Markdown) to HTML
123 lines (110 loc) • 3.78 kB
text/typescript
import type MarkdownIt from 'markdown-it';
import type Core from 'markdown-it/lib/parser_core';
import type StateCore from 'markdown-it/lib/rules_core/state_core';
import type Token from 'markdown-it/lib/token';
// eslint-disable-next-line no-useless-escape
export const pattern = /^\[(X|\s|\_|\-)\]\s(.*)/i;
export const CheckboxTokenType = {
Checkbox: 'checkbox',
CheckboxOpen: 'checkbox_open',
CheckboxClose: 'checkbox_close',
CheckboxInput: 'checkbox_input',
CheckboxLabel: 'checkbox_label',
CheckboxLabelOpen: 'checkbox_label_open',
CheckboxLabelClose: 'checkbox_label_close',
} as const;
function matchOpenToken(tokens: Token[], i: number) {
return (
tokens[i].type === 'paragraph_open' &&
tokens[i + 1].type === 'inline' &&
tokens[i + 1].content.match(pattern)
);
}
export type CheckboxOptions = {
idPrefix?: string;
divClass?: string;
/** @default true */
disabled?: boolean;
};
export const checkboxReplace = function (_md: MarkdownIt, opts?: CheckboxOptions): Core.RuleCore {
let lastId = 0;
const defaults: Required<CheckboxOptions> = {
divClass: 'checkbox',
idPrefix: 'checkbox',
disabled: true,
};
const options = Object.assign(defaults, opts);
const createTokens = function (state: StateCore, checked: boolean, label: string, i: number) {
let token: Token;
const nodes = [];
/**
* <div class="checkbox">
*/
token = new state.Token(CheckboxTokenType.CheckboxOpen, 'div', 1);
token.block = true;
token.map = state.tokens[i].map;
token.attrs = [['class', options.divClass]];
nodes.push(token);
/**
* <input type="checkbox" id="checkbox{n}" checked="true">
*/
const id = options.idPrefix + lastId;
lastId += 1;
token = new state.Token(CheckboxTokenType.CheckboxInput, 'input', 0);
token.block = true;
token.map = state.tokens[i].map;
token.attrs = [
['type', 'checkbox'],
['id', id],
];
if (options.disabled) {
token.attrSet('disabled', '');
}
if (checked === true) {
token.attrSet('checked', 'true');
}
nodes.push(token);
/**
* <label for="checkbox{n}">
*/
token = new state.Token(CheckboxTokenType.CheckboxLabelOpen, 'label', 1);
token.attrs = [['for', id]];
nodes.push(token);
/**
* content of label tag
*/
token = state.md.parseInline(label, state.env)[0];
nodes.push(token);
/**
* closing tags
*/
token = new state.Token(CheckboxTokenType.CheckboxLabelClose, 'label', -1);
token.block = true;
token.map = state.tokens[i].map;
nodes.push(token);
token = new state.Token(CheckboxTokenType.CheckboxClose, 'div', -1);
token.block = true;
token.map = state.tokens[i].map;
nodes.push(token);
return nodes;
};
const splitTextToken = function (state: StateCore, matches: RegExpMatchArray, i: number) {
let checked = false;
const value = matches[1];
const label = matches[2];
if (value === 'X' || value === 'x') {
checked = true;
}
return createTokens(state, checked, label, i);
};
return function (state) {
const blockTokens = state.tokens;
for (let i = 0; i < blockTokens.length; i++) {
const match = matchOpenToken(blockTokens, i);
if (!match) {
continue;
}
blockTokens.splice(i, 3, ...splitTextToken(state, match, i));
}
};
};