2017-06-27 22:06:55 +08:00
|
|
|
let isWhiteSpace;
|
|
|
|
|
|
|
|
function trailingSpaceOnly(src, start, max) {
|
|
|
|
let i;
|
|
|
|
for(i=start;i<max;i++) {
|
|
|
|
let code = src.charCodeAt(i);
|
|
|
|
if (code === 0x0A) { return true; }
|
|
|
|
if (!isWhiteSpace(code)) { return false; }
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-07-25 00:21:49 +08:00
|
|
|
const ATTR_REGEX = /((([a-z0-9]*)\s*)=)(["“”'].*["“”']|\S+)/ig;
|
2017-07-12 02:43:24 +08:00
|
|
|
|
2017-06-09 06:02:30 +08:00
|
|
|
// parse a tag [test a=1 b=2] to a data structure
|
|
|
|
// {tag: "test", attrs={a: "1", b: "2"}
|
2017-06-27 22:06:55 +08:00
|
|
|
export function parseBBCodeTag(src, start, max, multiline) {
|
2017-06-09 06:02:30 +08:00
|
|
|
|
|
|
|
let i;
|
|
|
|
let tag;
|
|
|
|
let attrs = {};
|
|
|
|
let closed = false;
|
|
|
|
let length = 0;
|
|
|
|
let closingTag = false;
|
|
|
|
|
|
|
|
// closing tag
|
|
|
|
if (src.charCodeAt(start+1) === 47) {
|
|
|
|
closingTag = true;
|
|
|
|
start += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i=start+1;i<max;i++) {
|
|
|
|
let letter = src[i];
|
|
|
|
if (!( (letter >= 'a' && letter <= 'z') ||
|
|
|
|
(letter >= 'A' && letter <= 'Z'))) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tag = src.slice(start+1, i);
|
|
|
|
|
|
|
|
if (!tag) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (closingTag) {
|
|
|
|
if (src[i] === ']') {
|
2017-06-27 22:06:55 +08:00
|
|
|
|
|
|
|
if (multiline && !trailingSpaceOnly(src, i+1, max)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-09 06:02:30 +08:00
|
|
|
return {tag, length: tag.length+3, closing: true};
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (;i<max;i++) {
|
|
|
|
let letter = src[i];
|
|
|
|
|
|
|
|
if (letter === ']') {
|
|
|
|
closed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (closed) {
|
2017-06-23 23:36:45 +08:00
|
|
|
length = (i-start)+1;
|
2017-06-09 06:02:30 +08:00
|
|
|
|
|
|
|
let raw = src.slice(start+tag.length+1, i);
|
|
|
|
|
|
|
|
// trivial parser that is going to have to be rewritten at some point
|
|
|
|
if (raw) {
|
|
|
|
|
2017-07-12 02:43:24 +08:00
|
|
|
let match, key, val;
|
|
|
|
|
|
|
|
while(match = ATTR_REGEX.exec(raw)) {
|
2017-07-21 01:02:31 +08:00
|
|
|
key = match[3];
|
|
|
|
if (key === '') {
|
|
|
|
key = '_default';
|
|
|
|
}
|
|
|
|
|
|
|
|
val = match[4];
|
|
|
|
|
|
|
|
if (val) {
|
2017-07-12 02:43:24 +08:00
|
|
|
val = val.trim();
|
2017-07-25 00:21:49 +08:00
|
|
|
val = val.replace(/^["'“”](.*)["'“”]$/, '$1');
|
2017-07-12 02:43:24 +08:00
|
|
|
attrs[key] = val;
|
2017-06-09 06:02:30 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
if (multiline && !trailingSpaceOnly(src, start+length, max)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-09 06:02:30 +08:00
|
|
|
tag = tag.toLowerCase();
|
|
|
|
|
|
|
|
return {tag, attrs, length};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function applyBBCode(state, startLine, endLine, silent, md) {
|
|
|
|
|
2017-07-01 03:19:07 +08:00
|
|
|
var nextLine,
|
2017-06-09 06:02:30 +08:00
|
|
|
old_parent, old_line_max, rule,
|
|
|
|
start = state.bMarks[startLine] + state.tShift[startLine],
|
|
|
|
initial = start,
|
|
|
|
max = state.eMarks[startLine];
|
|
|
|
|
|
|
|
// [ === 91
|
|
|
|
if (91 !== state.src.charCodeAt(start)) { return false; }
|
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
let info = parseBBCodeTag(state.src, start, max, true);
|
2017-06-09 06:02:30 +08:00
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
if (!info || info.closing) {
|
2017-06-09 06:02:30 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-07-18 04:21:47 +08:00
|
|
|
let ruleInfo = md.block.bbcode.ruler.getRuleForTag(info.tag);
|
2017-07-01 03:19:07 +08:00
|
|
|
if (!ruleInfo) { return false; }
|
2017-06-09 06:02:30 +08:00
|
|
|
|
2017-07-01 03:19:07 +08:00
|
|
|
rule = ruleInfo.rule;
|
2017-06-09 06:02:30 +08:00
|
|
|
|
|
|
|
// Since start is found, we can report success here in validation mode
|
|
|
|
if (silent) { return true; }
|
|
|
|
|
|
|
|
// Search for the end of the block
|
|
|
|
nextLine = startLine;
|
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
let closeTag;
|
|
|
|
let nesting = 0;
|
|
|
|
|
2017-06-09 06:02:30 +08:00
|
|
|
for (;;) {
|
|
|
|
nextLine++;
|
|
|
|
if (nextLine >= endLine) {
|
2017-06-27 04:50:46 +08:00
|
|
|
// unclosed bbcode block should not be autoclosed by end of document.
|
|
|
|
return false;
|
2017-06-09 06:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
start = state.bMarks[nextLine] + state.tShift[nextLine];
|
|
|
|
max = state.eMarks[nextLine];
|
|
|
|
|
|
|
|
if (start < max && state.sCount[nextLine] < state.blkIndent) {
|
|
|
|
// non-empty line with negative indent should stop the list:
|
|
|
|
// - ```
|
|
|
|
// test
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// bbcode close [ === 91
|
|
|
|
if (91 !== state.src.charCodeAt(start)) { continue; }
|
|
|
|
|
|
|
|
if (state.sCount[nextLine] - state.blkIndent >= 4) {
|
|
|
|
// closing fence should be indented less than 4 spaces
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
closeTag = parseBBCodeTag(state.src, start, max, true);
|
|
|
|
|
|
|
|
if (closeTag && closeTag.closing && closeTag.tag === info.tag) {
|
|
|
|
if (nesting === 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nesting--;
|
|
|
|
}
|
2017-06-09 06:02:30 +08:00
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
if (closeTag && !closeTag.closing && closeTag.tag === info.tag) {
|
|
|
|
nesting++;
|
|
|
|
}
|
2017-06-09 06:02:30 +08:00
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
closeTag = null;
|
|
|
|
}
|
2017-06-09 06:02:30 +08:00
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
if (!closeTag) {
|
|
|
|
return false;
|
2017-06-09 06:02:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
old_parent = state.parentType;
|
|
|
|
old_line_max = state.lineMax;
|
|
|
|
|
|
|
|
// this will prevent lazy continuations from ever going past our end marker
|
|
|
|
state.lineMax = nextLine;
|
|
|
|
|
2017-06-30 04:04:10 +08:00
|
|
|
if (rule.replace) {
|
|
|
|
let content = state.src.slice(state.bMarks[startLine+1], state.eMarks[nextLine-1]);
|
|
|
|
if (!rule.replace.call(this, state, info, content)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
2017-06-23 23:36:45 +08:00
|
|
|
|
2017-06-30 04:04:10 +08:00
|
|
|
if (rule.before) {
|
2017-07-22 04:09:27 +08:00
|
|
|
rule.before.call(this, state, info, state.src.slice(initial, initial + info.length + 1));
|
2017-06-30 04:04:10 +08:00
|
|
|
}
|
2017-06-23 23:36:45 +08:00
|
|
|
|
2017-06-30 04:04:10 +08:00
|
|
|
let wrapTag;
|
|
|
|
if (rule.wrap) {
|
2017-07-01 00:21:01 +08:00
|
|
|
let token;
|
2017-06-23 23:36:45 +08:00
|
|
|
|
2017-07-01 00:21:01 +08:00
|
|
|
if (typeof rule.wrap === 'function') {
|
|
|
|
token = new state.Token('wrap_bbcode', 'div', 1);
|
|
|
|
token.level = state.level+1;
|
2017-06-30 04:04:10 +08:00
|
|
|
|
2017-07-01 00:21:01 +08:00
|
|
|
if (!rule.wrap(token, info)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.tokens.push(token);
|
|
|
|
state.level = token.level;
|
|
|
|
wrapTag = token.tag;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
let split = rule.wrap.split('.');
|
|
|
|
wrapTag = split[0];
|
|
|
|
let className = split.slice(1).join(' ');
|
|
|
|
|
|
|
|
token = state.push('wrap_bbcode', wrapTag, 1);
|
|
|
|
|
|
|
|
if (className) {
|
|
|
|
token.attrs = [['class', className]];
|
|
|
|
}
|
2017-06-30 04:04:10 +08:00
|
|
|
}
|
2017-06-23 23:36:45 +08:00
|
|
|
}
|
2017-06-09 06:02:30 +08:00
|
|
|
|
2017-06-30 04:04:10 +08:00
|
|
|
let lastToken = state.tokens[state.tokens.length-1];
|
|
|
|
lastToken.map = [ startLine, nextLine ];
|
2017-06-09 06:02:30 +08:00
|
|
|
|
2017-06-30 04:04:10 +08:00
|
|
|
state.md.block.tokenize(state, startLine + 1, nextLine);
|
2017-06-09 06:02:30 +08:00
|
|
|
|
2017-06-30 04:04:10 +08:00
|
|
|
if (rule.wrap) {
|
|
|
|
state.push('wrap_bbcode', wrapTag, -1);
|
|
|
|
}
|
2017-06-23 23:36:45 +08:00
|
|
|
|
2017-06-30 04:04:10 +08:00
|
|
|
if (rule.after) {
|
2017-07-22 04:09:27 +08:00
|
|
|
rule.after.call(this, state, lastToken, state.src.slice(start-2, start + closeTag.length - 1));
|
2017-06-30 04:04:10 +08:00
|
|
|
}
|
2017-06-23 23:36:45 +08:00
|
|
|
}
|
2017-06-09 06:02:30 +08:00
|
|
|
|
|
|
|
state.parentType = old_parent;
|
|
|
|
state.lineMax = old_line_max;
|
2017-06-27 22:06:55 +08:00
|
|
|
state.line = nextLine+1;
|
2017-06-09 06:02:30 +08:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setup(helper) {
|
|
|
|
helper.registerPlugin(md => {
|
2017-07-18 04:21:47 +08:00
|
|
|
const ruler = md.block.bbcode.ruler;
|
2017-06-30 04:04:10 +08:00
|
|
|
|
|
|
|
ruler.push('code', {
|
|
|
|
tag: 'code',
|
|
|
|
replace: function(state, tagInfo, content) {
|
|
|
|
let token;
|
|
|
|
token = state.push('fence', 'code', 0);
|
|
|
|
token.content = content;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-06-27 22:06:55 +08:00
|
|
|
isWhiteSpace = md.utils.isWhiteSpace;
|
2017-06-09 06:02:30 +08:00
|
|
|
md.block.ruler.after('fence', 'bbcode', (state, startLine, endLine, silent)=> {
|
|
|
|
return applyBBCode(state, startLine, endLine, silent, md);
|
2017-06-27 22:06:55 +08:00
|
|
|
}, { alt: ['paragraph', 'reference', 'blockquote', 'list'] });
|
2017-06-09 06:02:30 +08:00
|
|
|
});
|
|
|
|
}
|