112 lines
2.4 KiB
JavaScript
112 lines
2.4 KiB
JavaScript
const STYLES = [
|
|
{
|
|
attrs: {'font-style': 'italic'},
|
|
begin: /[\s_~`]\*(?=\S)/g,
|
|
end: /\S\*(?=[\s_~`])/g,
|
|
}, {
|
|
attrs: {'font-style': 'italic'},
|
|
begin: /[\s*~`]_(?=\S)/g,
|
|
end: /\S_(?=[\s*~`])/g,
|
|
}, {
|
|
attrs: {'font-weight': 'bolder'},
|
|
begin: /[\s_~`]\*\*(?=\S)/g,
|
|
end: /\S\*\*(?=[\s_~`])/g,
|
|
}, {
|
|
attrs: {'font-weight': 'bolder'},
|
|
begin: /[\s*~`]__(?=\S)/g,
|
|
end: /\S__(?=[\s*~`])/g,
|
|
}, {
|
|
attrs: {'text-decoration': 'line-through'},
|
|
begin: /[\s_*`]~(?=\S)/g,
|
|
end: /\S~(?=[\s_*`])/g,
|
|
}, {
|
|
attrs: {'font-family': 'Courier New,Liberation Mono,monospace'},
|
|
begin: /[\s_*~.]`(?=\S)/g,
|
|
end: /\S`(?=[\s_*~.])/g,
|
|
},
|
|
];
|
|
|
|
function findNext(line, p, active) {
|
|
const virtLine = ' ' + line + ' ';
|
|
let styleIndex = -1;
|
|
let bestStart = virtLine.length;
|
|
let bestEnd = 0;
|
|
|
|
STYLES.forEach(({begin, end}, ind) => {
|
|
const search = active[ind] ? end : begin;
|
|
search.lastIndex = p;
|
|
const m = search.exec(virtLine);
|
|
if(m && (
|
|
m.index < bestStart ||
|
|
(m.index === bestStart && search.lastIndex > bestEnd)
|
|
)) {
|
|
styleIndex = ind;
|
|
bestStart = m.index;
|
|
bestEnd = search.lastIndex;
|
|
}
|
|
});
|
|
|
|
return {end: bestEnd - 1, start: bestStart, styleIndex};
|
|
}
|
|
|
|
function combineAttrs(activeCount, active) {
|
|
if(!activeCount) {
|
|
return null;
|
|
}
|
|
const attrs = {};
|
|
active.forEach((on, ind) => {
|
|
if(on) {
|
|
Object.assign(attrs, STYLES[ind].attrs);
|
|
}
|
|
});
|
|
return attrs;
|
|
}
|
|
|
|
function shrinkWhitespace(text) {
|
|
return text.replace(/[\f\n\r\t\v ]+/g, ' ');
|
|
}
|
|
|
|
function trimCollapsible(text) {
|
|
return text.replace(/^[\f\n\r\t\v ]+|[\f\n\r\t\v ]+$/g, '');
|
|
}
|
|
|
|
export default function parseMarkdown(text) {
|
|
if(!text) {
|
|
return [];
|
|
}
|
|
|
|
const active = STYLES.map(() => false);
|
|
let activeCount = 0;
|
|
let attrs = null;
|
|
const lines = trimCollapsible(text).split('\n');
|
|
const result = [];
|
|
lines.forEach((line) => {
|
|
const ln = shrinkWhitespace(trimCollapsible(line));
|
|
const parts = [];
|
|
let p = 0;
|
|
for(;;) {
|
|
const {styleIndex, start, end} = findNext(ln, p, active);
|
|
if(styleIndex === -1) {
|
|
break;
|
|
}
|
|
if(active[styleIndex]) {
|
|
active[styleIndex] = false;
|
|
-- activeCount;
|
|
} else {
|
|
active[styleIndex] = true;
|
|
++ activeCount;
|
|
}
|
|
if(start > p) {
|
|
parts.push({attrs, text: ln.substring(p, start)});
|
|
}
|
|
attrs = combineAttrs(activeCount, active);
|
|
p = end;
|
|
}
|
|
if(p < ln.length) {
|
|
parts.push({attrs, text: ln.substr(p)});
|
|
}
|
|
result.push(parts);
|
|
});
|
|
return result;
|
|
}
|