1
0
Fork 0
This repository has been archived on 2024-02-06. You can view files and clone it, but cannot push or open issues or pull requests.
markdown-it-attr/lib/index.js
2021-05-25 10:43:17 +09:00

108 lines
3.5 KiB
JavaScript

const {parseAttrs, attrConcat} = require('./utils.js');
const {inlineAttrsApplyRules} = require('./rules.js');
/**
* @param {MarkdownIt} md
* @param {{deliminator: string, re: RegExp}} opts
*/
function pluginAttr(md, opts={}) {
const deliminator = opts.deliminator || '{';
const deliminatorLength = deliminator.length;
/* eslint-disable-next-line max-len */
const reDefault = /\#(?<id>[^\s#.="'}]+)|\.(?<class>[^\s#.="'}]+)|(?<attr>[^\s#.="'}]+)(?:\=(?<val>[^\s}"'][^\s}]*|(?<q>["']).*?\k<q>))?|(?<term>})/g;
const reRaw = opts.re;
const re = reRaw ?
(reRaw.sticky || reRaw.global) ? reRaw :
new RegExp(reRaw, reRaw.flags+'g') :
reDefault;
md.block.ruler.before('table', 'block_attrs', (state, l0, l1, silent) => {
const {src, bMarks, eMarks, tShift} = state;
// guard thisLine.sCount == nextLine.sCount
if (state.sCount[l0] !== state.sCount[l0+1]) return false;
// disallow contiguous two { }
if (
l1 > l0+2 &&
state.sCount[l0+1] === state.sCount[l0+2] &&
src.startsWith(deliminator, bMarks[l0+1]+tShift[l0+1])
) return false;
// parse
const indexStart = bMarks[l0]+tShift[l0];
const indexEnd = eMarks[l0];
if (src.startsWith(deliminator, indexStart)) {
// parse attrs
re.lastIndex = indexStart+deliminatorLength;
const {attrs, index} = parseAttrs(src, re) || {};
// should meet end of line
if (!(attrs && index === indexEnd)) return false;
// push
// [block] silent: no need to update state
if (silent) return true;
state.line = l0+1;
const token = state.push('block_attr', '', 0);
token.attrs = attrs;
token.hidden = true;
return true;
}
return false;
}, {alt: ['paragraph']});
md.inline.ruler.push('inline_attrs', (state, silent) => {
const {src, pos} = state;
if (src.startsWith(deliminator, pos)) {
re.lastIndex = pos+deliminatorLength;
const {attrs, index} = parseAttrs(src, re) || {};
if (!attrs) return false;
// set attr
/*
[inline] silent:
false -> push token and update state
true -> set state.pos only
*/
if (!silent) {
const token = state.push('inline_attr', '', 0);
token.attrs = attrs;
token.content = src.substring(pos, index);
token.hidden = true;
}
state.pos = index;
return true;
}
return false;
});
md.core.ruler.after('block', 'apply_block_attrs', state => {
state.tokens.forEach((token, i, tokens) => {
if (token.type === 'block_attr' && token.attrs) {
const tokenN = tokens[i+1];
if (tokenN) {
attrConcat(tokenN, token);
tokens.splice(i, 1);
}
}
});
});
md.core.ruler.after('inline', 'apply_inline_attrs', state => {
state.tokens.forEach((blockToken, iBlock, blockTokens) => {
const {children} = blockToken;
if (!children) return;
children.forEach((token, i, tokens) => {
if (token.type !== 'inline_attr') return;
const matched = inlineAttrsApplyRules.some(({handler, disabled}) =>
disabled ? false : handler(token, i, tokens, iBlock, blockTokens));
// not seen as attrs
if (!matched) {
token.type = 'text';
token.hidden = false;
}
});
// remove inline_attr here to prevent index changed
blockToken.children = children.filter(t => t.type !== 'inline_attr');
});
});
}
module.exports = Object.assign(pluginAttr, {
inlineAttrsApplyRules,
});