代码语言:javascript复制
import { repeat } from './utilities'
var rules = {}
// 段落
rules.paragraph = {
filter: 'p',
replacement: function (content) {
// 前后加两个换行
return 'nn' content 'nn'
}
}
// 换行
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 (
'nn' content 'n' underline 'nn'
)
} else {
// 如果是 atx 风格,标题前面加上级别等量的井号
return 'nn' repeat('#', hLevel) ' ' content 'nn'
}
// 前后加上两个换行
}
}
// 引用块
rules.blockquote = {
filter: 'blockquote',
replacement: function (content) {
// 替换掉前导和尾随换行
content = content.replace(/^n |n $/g, '')
// 每行开头加上前缀
content = content.replace(/^/gm, '> ')
// 前后加上两个换行
return 'nn' content 'nn'
}
}
// 列表
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 'nn' content 'nn'
}
}
}
// 列表项
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 (
'nn '
node.firstChild.textContent.replace(/n/g, 'n ')
'nn'
)
}
}
// 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 (
'nn' fence language 'n'
code.replace(/n$/, '')
'n' fence 'nn'
)
}
}
// 水平线
rules.horizontalRule = {
filter: 'hr',
replacement: function (content, node, options) {
// 使用 options.hr 的样式,前后两个换行
return 'nn' options.hr 'nn'
}
}
// 内联连接
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 = 'nn' this.references.join('n') 'nn'
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