marked-mfm/src/index.js

146 lines
4.8 KiB
JavaScript

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 = /^\$\[(?<markup>[\w\d]+)(?:\.(?<options>\S+))? (?<text>[\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 = '') => `<span style="display: inline-block;${mfmStyle}" class="${mfmClass}">${this.parser.parseInline(token.tokens)}</span>`
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 `<a href="https://google.com/search?q=${token.query}" class="_mfm_search_">${token.raw}</a>`
},
},
],
}