remark-bracketed-spans
Version:
Add an id, classes, and data attributes to `<span>` tags in markdown.
184 lines (159 loc) • 4.41 kB
JavaScript
var visit = require('unist-util-visit')
var remove = require('unist-util-remove')
function md2html () {
return function transformer (tree) {
visit(tree, function visitor (node, i, parent) {
if (!node.children) return
var data = parseMarkdown(node, i, parent, tree)
if (data) {
parent.children[i] = {
type: 'html',
value: data.html
}
if (data.trailingText) {
parent.children[i + 1] = {
type: 'text',
value: data.trailingText
}
} else {
remove(parent, parent.children[i + 1])
}
}
})
}
}
/*
* if a bracket span statement is found: returns an object
* with id, classList, attr, and children properties
* else returns false
*/
function parseMarkdown (node, i, parent, tree) {
if (!node.children) return false
if (node.type &&
node.type === 'linkReference' &&
node.children &&
parent.children[i + 1].value &&
parent.children[i + 1].value.match(/\{(.+)\}/).length
) {
var text = node.children && node.children[0] && node.children[0].value
var value = parent.children[i + 1].value
var data = {
id: null,
classList: [],
attr: {}
}
var idMatch = value.match(/#\w+/)
var classMatch = value.match(/\.\w+(-)?\w+/g)
var attrMatch = value.match(/(?:\w*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^}\s]+))/g)
if (idMatch) {
data.id = idMatch[0].replace('#', '')
}
if (classMatch) {
data.classList = classMatch.map(function (cls) {
return cls.replace('.', '')
})
}
if (attrMatch) {
attrMatch.map(function (item) {
var split = item.split('=')
var key = split[0].trim().replace('{', '')
var val = split[1].trim().replace(/"/g, '')
data.attr[key] = val
})
}
data.text = text
data.trailingText = value.split('}')[1]
data.html = createSpan(data)
return data
} else {
return false
}
}
function createSpan (data) {
var classes = data.classList ? data.classList.join(' ') : ''
var text = data.text
var id = data.id
var attr = Object.keys(data.attr).map(function (key) {
return `data-${key}="${data.attr[key]}"`
}).join(' ')
return `<span${id ? ` id="${id}"` : ''} ${classes ? `class="${classes}"` : ''} ${attr || ''}>${text}</span>`
}
function html2md () {
return function transformer (tree) {
visit(tree, visitor)
}
function visitor (node, index, parent) {
if (
node.tagName &&
node.tagName === 'span' &&
node.properties &&
(
node.properties.className ||
node.properties.id ||
hasDataAttr(node.properties)
)
) {
var props = node.properties
var text = '[' + node.children[0].value + ']{'
var attrs = []
if (props.id) {
attrs.push('#' + props.id)
delete props.id
}
if (props.className) {
attrs.push(props.className.map(function (name) {
return '.' + name
}).join(' '))
delete props.className
}
var dataKeys = Object.keys(props)
dataKeys.forEach(function (key) {
var attrkey = key.replace('data', '').toLowerCase()
attrs.push(attrkey + '=' + props[key])
})
text += attrs.join(' ') + '}'
parent.children[index] = {
type: 'text',
value: text.trim()
}
}
}
}
function hasDataAttr (props) {
if (!props) return false
var keys = Object.keys(props)
if (!keys.length) return false
var i = 0
for (i; i < keys.length; i++) {
var key = keys[i]
if (key.indexOf('data') === 0) {
return true
}
}
return false
}
/* clean up md output */
function mdVisitors () {
var processor = this
var Compiler = processor.Compiler
var visitors = Compiler.prototype.visitors
var text = visitors.text
/* Add a visitor for `heading`s. */
visitors.text = function (node, parent) {
var textMatch = node.value.match(/\[(.*?)\]/)
var bracketMatch = node.value.match(/\{(.+)\}/)
if (
textMatch &&
bracketMatch
) {
node.value = node.value.replace(/\//g, '')
return node.value.replace(/\/+?(?=\[)/g, '')
} else {
return text.apply(this, arguments)
}
}
}
module.exports = md2html
md2html.html2md = html2md
md2html.mdVisitors = mdVisitors
md2html.parseMarkdown = parseMarkdown