import { repeat } from './utilities' var rules = {} // 段落 rules.paragraph = { filter: 'p', replacement: function (content) { // 前后加两个换行 return '\n\n' + content + '\n\n' } } // 换行 rules.lineBreak = { filter: 'br', replacement: function (content, node, options) { // 换行的规则在各个编辑器中是不统一的 // GH 实现只需要一个换行就够,但经典实现需要两个空格加一个换行 // options.br 用于配置换行符之前应该添加的字符 return options.br + '\n' } } // 标题 rules.heading = { filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], replacement: function (content, node, options) { // 确定标题级别(节点名称第二个字母) var hLevel = Number(node.nodeName.charAt(1)) if (options.headingStyle === 'setext' && hLevel < 3) { // 如果是 setext 风格,标题文本下加相同长度的等号或者中划线 var underline = repeat((hLevel === 1 ? '=' : '-'), content.length) return ( '\n\n' + content + '\n' + underline + '\n\n' ) } else { // 如果是 atx 风格,标题前面加上级别等量的井号 return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n' } // 前后加上两个换行 } } // 引用块 rules.blockquote = { filter: 'blockquote', replacement: function (content) { // 替换掉前导和尾随换行 content = content.replace(/^\n+|\n+$/g, '') // 每行开头加上前缀 content = content.replace(/^/gm, '> ') // 前后加上两个换行 return '\n\n' + content + '\n\n' } } // 列表 rules.list = { filter: ['ul', 'ol'], replacement: function (content, node) { // 检查元素是顶级列表还是子列表 var parent = node.parentNode if (parent.nodeName === 'LI' && parent.lastElementChild === node) { // 如果是子列表,前面加一个换行,后面不加 return '\n' + content } else { // 如果是顶级列表,前后加两个换行 return '\n\n' + content + '\n\n' } } } // 列表项 rules.listItem = { filter: 'li', replacement: function (content, node, options) { content = content .replace(/^\n+/, '') // 移除前导换行 .replace(/\n+$/, '\n') // 将尾随换行减少为一个 .replace(/\n/gm, '\n ') // 曾伽缩进 // 无序列表的列表项前缀 var prefix = options.bulletListMarker + ' ' var parent = node.parentNode if (parent.nodeName === 'OL') { // 如果是有序列表,获取起始序号 var start = parent.getAttribute('start') // 获取列表项的索引 var index = Array.prototype.indexOf.call(parent.children, node) // 有序号则使用序号,否则使用索引 prefix = (start ? Number(start) + index : index + 1) + '. ' } // 如果不是最后一个列表项,并且没有尾随换行,则添加一个 return ( prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '') ) } } // 缩进式代码块 rules.indentedCodeBlock = { filter: function (node, options) { return ( options.codeBlockStyle === 'indented' && node.nodeName === 'PRE' && node.firstChild && node.firstChild.nodeName === 'CODE' ) }, replacement: function (content, node, options) { // 每一行加缩进,首尾加两个换行 return ( '\n\n ' + node.firstChild.textContent.replace(/\n/g, '\n ') + '\n\n' ) } } // GH 风格代码块 rules.fencedCodeBlock = { filter: function (node, options) { return ( options.codeBlockStyle === 'fenced' && node.nodeName === 'PRE' && node.firstChild && node.firstChild.nodeName === 'CODE' ) }, replacement: function (content, node, options) { // 获取其 CODE 子元素的类名 var className = node.firstChild.getAttribute('class') || '' // 从类名获取语言 var language = (className.match(/language-(\S+)/) || [null, ''])[1] var code = node.firstChild.textContent // 确定分隔符的长度,是代码中出现的最大连续分隔字符数量加1 var fenceChar = options.fence.charAt(0) var fenceSize = 3 var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm') var match while ((match = fenceInCodeRegex.exec(code))) { if (match[0].length >= fenceSize) { fenceSize = match[0].length + 1 } } var fence = repeat(fenceChar, fenceSize) // 拼装字符串并返回 return ( '\n\n' + fence + language + '\n' + code.replace(/\n$/, '') + '\n' + fence + '\n\n' ) } } // 水平线 rules.horizontalRule = { filter: 'hr', replacement: function (content, node, options) { // 使用 options.hr 的样式,前后两个换行 return '\n\n' + options.hr + '\n\n' } } // 内联连接 rules.inlineLink = { filter: function (node, options) { return ( options.linkStyle === 'inlined' && node.nodeName === 'A' && node.getAttribute('href') ) }, replacement: function (content, node) { // 获取 URL 和标题 var href = node.getAttribute('href') var title = cleanAttribute(node.getAttribute('title')) if (title) title = ' "' + title + '"' // 拼接到一起 return '[' + content + '](' + href + title + ')' } } // 引用链接 rules.referenceLink = { filter: function (node, options) { return ( options.linkStyle === 'referenced' && node.nodeName === 'A' && node.getAttribute('href') ) }, replacement: function (content, node, options) { // 获取 URL 和标题 var href = node.getAttribute('href') var title = cleanAttribute(node.getAttribute('title')) if (title) title = ' "' + title + '"' var replacement var reference // 根据不同格式构造链接部分和引用部分 switch (options.linkReferenceStyle) { case 'collapsed': replacement = '[' + content + '][]' reference = '[' + content + ']: ' + href + title break case 'shortcut': replacement = '[' + content + ']' reference = '[' + content + ']: ' + href + title break default: var id = this.references.length + 1 replacement = '[' + content + '][' + id + ']' reference = '[' + id + ']: ' + href + title } // 将引用部分插入到引用列表中,稍后附加到文章末尾 this.references.push(reference) // 返回链接部分 return replacement }, references: [], append: function (options) { var references = '' // 将引用列表按行连接,一行一个引用,然后清空引用列表 if (this.references.length) { references = '\n\n' + this.references.join('\n') + '\n\n' this.references = [] // Reset references } return references } } // 斜体 rules.emphasis = { filter: ['em', 'i'], replacement: function (content, node, options) { // 如果内容为空或者空白, 返回空串 if (!content.trim()) return '' // 返回分隔符包围的内容 return options.emDelimiter + content + options.emDelimiter } } rules.strong = { filter: ['strong', 'b'], replacement: function (content, node, options) { // 如果内容为空或者空白, 返回空串 if (!content.trim()) return '' // 返回分隔符包围的内容 return options.strongDelimiter + content + options.strongDelimiter } } // 内联代码 rules.code = { filter: function (node) { var hasSiblings = node.previousSibling || node.nextSibling var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings return node.nodeName === 'CODE' && !isCodeBlock }, replacement: function (content) { // 如果内容为空或者空白, 返回空串 if (!content) return '' // 去掉所有换行符 content = content.replace(/\r?\n|\r/g, ' ') // 如果以反引号开头或者结尾,需要加填充避免与分隔符连上 var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : '' // 分隔符的长度是代码中出现的最大连续反引号数量加1 var delimiter = '`' var matches = content.match(/`+/gm) || [] while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`' // 拼接到一起 return delimiter + extraSpace + content + extraSpace + delimiter } } // 图象 rules.image = { filter: 'img', replacement: function (content, node) { // 获取标题、描述和链接并拼接起来 // 链接不存在时返回空串 var alt = cleanAttribute(node.getAttribute('alt')) var src = node.getAttribute('src') || '' var title = cleanAttribute(node.getAttribute('title')) var titlePart = title ? ' "' + title + '"' : '' return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '' } } function cleanAttribute (attribute) { // 将连续换行变成单个换行,将行首空格移除。 return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : '' } export default rules