export default { extensions: [ { name: 'mfm', level: 'inline', start (src) { return src.match(/\$\[/)?.index }, tokenizer (src, tokens) { // regex doesn't do well finding MFM tags: it's always either too lazy // or too greedy. let level = 0 let walk = 0 while (level > 0 || walk === 0) { if (src[walk] + src[walk + 1] === '$[') { level++ walk++ } else if (src[walk] === ']') { level-- } walk++ } // original regex, now definitely on the correct tag const rule = /^\$\[(?[\w\d]+)(?:\.(?\S+))? (?[\S\s]+)\]/ const match = rule.exec(src.slice(0, walk)) if (match) { const token = { type: 'mfm', raw: match[0], markup: match.groups.markup, options: match.groups.options, text: match.groups.text, tokens: [], } this.lexer.inline(token.text, token.tokens) return token } }, renderer (token) { let options = {} if (token.options) { options = token.options.split(',').reduce((i, opt) => { i[opt.split('=')[0]] = opt.split('=')[1] || true return i }, {}) } const el = (mfmClass, mfmStyle = '') => `${this.parser.parseInline(token.tokens)}` switch (token.markup) { case 'blur': { return el('_mfm_blur_') } case 'bounce': { return el('_mfm_bounce_') } case 'flip': { const transform = (options.x && options.v) ? 'scale(-1, -1)' : options.v ? 'scaleY(-1)' : 'scaleX(-1)' return el('_mfm_flip_', ` transform: ${transform};`) } case 'font': { const family = options.serif ? 'serif' : options.monospace ? 'monospace' : options.cursive ? 'cursive' : options.fantasy ? 'fantasy' : options.emoji ? 'emoji' : options.math ? 'math' : 'inherit' return el('_mfm_font_', ` font-family: ${family};`) } case 'jelly': { const speed = options.speed || '1s' return el('_mfm_jelly_', ` animation: mfm-rubberBand ${speed} linear infinite both;`) } case 'jump': { return el('_mfm_jump_') } case 'rainbow': { return el('_mfm_rainbow_') } case 'rotate': { const degrees = options.deg || '90' return el('_mfm_rotate_', ` transform: rotate(${degrees}deg); transform-origin: center center;`) } case 'shake': { const speed = options.speed || '0.5s' return el('_mfm_shake_', ` animation: mfm-shake ${speed} ease infinite;`) } case 'spin': { const direction = options.left ? 'reverse' : options.alternate ? 'alternate' : 'normal' const anime = options.x ? 'mfm-spinX' : options.y ? 'mfm-spinY' : 'mfm-spin' const speed = options.speed || '1.5s' return el('_mfm_spin_', ` animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};`) } case 'tada': { return el('_mfm_tada_') } case 'twitch': { const speed = options.speed || '0.5s' return el('_mfm_twitch_', ` animation: mfm-twitch ${speed} ease infinite;`) } case 'sparkle': { return el('_mfm_sparkle_') } case 'x2': { return el('_mfm_x2_') } case 'x3': { return el('_mfm_x3_') } case 'x4': { return el('_mfm_x4_') } default: { return `$[${token.markup} ${this.parser.parseInline(token.tokens)}]` } } }, }, { name: 'mfmSearch', level: 'block', start (src) { return src.match(/[^\n]+ search/)?.index }, tokenizer (src, tokens) { const rule = /^([^\n]+) search$/ const match = rule.exec(src) if (match) { const token = { type: 'mfmSearch', raw: match[0], query: match[1].trim(), tokens: [], } this.lexer.inline(token.text, token.tokens) return token } }, renderer (token) { return `${token.raw}` }, }, ], }