UNPKG

markdo

Version:

Tiny markdown formatter

100 lines (77 loc) 3.19 kB
const styles = { heading1: {left: '# '}, heading2: {left: '## '}, heading3: {left: '### '}, bold: {left: '**', 'right': '**'}, italic: {left: '_', 'right': '_'}, link: {left: '[', right: '](url)'}, quote: {left: '> ', list: true}, taskList: {left: '- [ ] ', list: true}, orderedList: {left: (index = 0) => `${index + 1}. `, list: true}, bulletedList: {left: '- ', list: true}, }; const value = (value, arg) => typeof value === 'function' ? value(arg) : value; const escape = string => string.split('').map(char => `\\${char}`).join(''); const wrapRegExp = (left, right) => new RegExp(`^${left}(.+)${right}$`); const word = (text, position) => { const spaceBefore = text.slice(0, position).lastIndexOf(' ') + 1; const newlineBefore = text.slice(0, position).lastIndexOf('\n') + 1; const start = spaceBefore > newlineBefore ? spaceBefore : newlineBefore; const end = position + text.slice(position).search(/\s|\n/); return text.slice( start === -1 ? 0 : start, end === -1 ? text.length : end ); }; const wordSplit = (text, start, end) => { if (start === end) { const spaceBefore = text.slice(0, start).lastIndexOf(' ') + 1; const newlineBefore = text.slice(0, start).lastIndexOf('\n') + 1; start = spaceBefore > newlineBefore ? spaceBefore : newlineBefore; const lastMarker = text.slice(start).search(/\s|\n/); if (lastMarker !== -1) { end = start + lastMarker; } } return [ text.slice(0, start === -1 ? 0 : start), text.slice(end === -1 ? text.length : end), ]; }; const isApplied = (text, {left = '', right = ''} = {}) => wrapRegExp(escape(left), escape(right)).test(text); const wrap = (text, {left = '', right = ''} = {}) => `${left}${text}${right}`; const unwrap = (text, {left = '', right = ''} = {}) => wrapRegExp(escape(left), escape(right)).exec(text)[1]; const prepareStyle = ({left, right}, arg) => ({ left: value(left, arg), right: value(right, arg), }); const format = (text, style) => isApplied(text, style) ? unwrap(text, style) : wrap(text, style); const join = (textBefore, textFormatted, textAfter, isList) => { if (isList) { const textJoined = [textBefore, textFormatted, textAfter].filter(Boolean); return textJoined.length === 1 ? textJoined[0] : textJoined.map(str => str.replace(/^\n+|\n+$/g, '')).join('\n\n'); } return `${textBefore}${textFormatted}${textAfter}`; }; const markdo = (text, action, {start, end}) => { const textPlain = start === end ? word(text, start) : text.slice(start, end); const [textBefore, textAfter] = wordSplit(text, start, end); const style = styles[action]; if (!style) { return text; } const textPlainArray = style.list && textPlain.split('\n').length > 1 ? textPlain.split('\n') : [textPlain]; const textFormatted = textPlainArray .map((text, index) => format(text, prepareStyle(style, index))) .join('\n'); return join(textBefore, textFormatted, textAfter, style.list); }; export default markdo;