完成markd组件的开发;优化checkbox
parent
f60c1df69e
commit
8034fc29b4
|
@ -207,7 +207,6 @@ class CheckboxItem extends Component {
|
||||||
`,
|
`,
|
||||||
// 状态
|
// 状态
|
||||||
css`
|
css`
|
||||||
:host([readonly]),
|
|
||||||
:host([disabled]) {
|
:host([disabled]) {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
|
|
|
@ -0,0 +1,588 @@
|
||||||
|
/**
|
||||||
|
* markdown解析器
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2020/02/07 17:14:19
|
||||||
|
*/
|
||||||
|
|
||||||
|
const HR_LIST = ['=', '-', '_', '*']
|
||||||
|
const LIST_RE = /^(([\+\-\*])|(\d+\.))\s/
|
||||||
|
const TODO_RE = /^[\+\-\*]\s\[(x|\s)\]\s/
|
||||||
|
const ESCAPE_RE = /\\([-+*_`\]\[\(\)])/g
|
||||||
|
const QLINK_RE = /^\[(\d+)\]: ([\S]+)\s*?((['"])[\s\S]*?\4)?\s*?$/
|
||||||
|
const TAG_RE = /<([\w\-]+)([\w\W]*?)>/g
|
||||||
|
const ATTR_RE = /\s*?on[a-zA-Z]+="[^"]*?"\s*?/g
|
||||||
|
const CODEBLOCK_RE = /```(.*?)([\w\W]*?)```/g
|
||||||
|
const BLOCK_RE = /<([\w\-]+)([^>]*?)>([\w\W]*?)<\/\1>/g
|
||||||
|
const IS_DOM_RE = /^<([\w\-]+)[^>]*?>.*?<\/\1>$/
|
||||||
|
const STYLE_RE = /<style[^>]*?>([\w\W]*?)<\/style>/g
|
||||||
|
|
||||||
|
const INLINE = {
|
||||||
|
code: /`([^`]*?[^`\\\s])`/g,
|
||||||
|
strong: [/__([\s\S]*?[^\s\\])__(?!_)/g, /\*\*([\s\S]*?[^\s\\])\*\*(?!\*)/g],
|
||||||
|
em: [/_([\s\S]*?[^\s\\])_(?!_)/g, /\*([\s\S]*?[^\s\\*])\*(?!\*)/g],
|
||||||
|
del: /~~([\s\S]*?[^\s\\~])~~/g,
|
||||||
|
qlink: /\[([^\]]*?)\]\[(\d*?)\]/g, // 引用链接
|
||||||
|
img: /\!\[([^\]]*?)\]\(([^)]*?)\)/g,
|
||||||
|
a: /\[([^\]]*?)\]\(([^)]*?)(\s+"([\s\S]*?)")*?\)/g,
|
||||||
|
qlist: /((<blockquote class="md\-quote">)*?)([\+\-\*]|\d+\.) (.*)/ // 引用中的列表
|
||||||
|
}
|
||||||
|
|
||||||
|
const ATTR_BR_SYMBOL = '⨨☇'
|
||||||
|
const NODE_BR_SYMBOL = '⨨⤶'
|
||||||
|
const ATTR_BR_EXP = new RegExp(ATTR_BR_SYMBOL, 'g')
|
||||||
|
const NODE_BR_EXP = new RegExp(NODE_BR_SYMBOL, 'g')
|
||||||
|
|
||||||
|
const Helper = {
|
||||||
|
// 是否分割线
|
||||||
|
isHr(str) {
|
||||||
|
var s = str[0]
|
||||||
|
if (HR_LIST.includes(s)) {
|
||||||
|
return str.slice(0, 3) === s.repeat(3) ? str.slice(3) : false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
// 是否列表, -1不是, 1为有序列表, 0为无序列表
|
||||||
|
isList(str) {
|
||||||
|
var v = str.trim()
|
||||||
|
if (LIST_RE.test(v)) {
|
||||||
|
var n = +v[0]
|
||||||
|
if (n === n) {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
},
|
||||||
|
// 是否任务列表
|
||||||
|
isTodo(str) {
|
||||||
|
var v = str.trim()
|
||||||
|
if (TODO_RE.test(v)) {
|
||||||
|
return v[3] === 'x' ? 1 : 0
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
},
|
||||||
|
ltrim(str) {
|
||||||
|
if (str.trimStart) {
|
||||||
|
return str.trimStart()
|
||||||
|
}
|
||||||
|
return str.replace(/^\s+/, '')
|
||||||
|
},
|
||||||
|
isQLink(str) {
|
||||||
|
if (QLINK_RE.test(str)) {
|
||||||
|
// l: link, t: title, $1: index
|
||||||
|
return { [RegExp.$1]: { l: RegExp.$2, t: RegExp.$3 } }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
isTable(str) {
|
||||||
|
return /^\|.+?\|$/.test(str)
|
||||||
|
},
|
||||||
|
// 是否原生dom节点
|
||||||
|
isNativeDom(str) {
|
||||||
|
return IS_DOM_RE.test(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Decoder = {
|
||||||
|
// 内联样式
|
||||||
|
inline(str) {
|
||||||
|
return str
|
||||||
|
.replace(INLINE.code, (m, str) => {
|
||||||
|
str = str.replace(/([_*~])/g, '\\$1')
|
||||||
|
return `<code class="inline">${str}</code>`
|
||||||
|
})
|
||||||
|
.replace(INLINE.strong[0], '<strong>$1</strong>')
|
||||||
|
.replace(INLINE.strong[1], '<strong>$1</strong>')
|
||||||
|
.replace(INLINE.em[0], '<em>$1</em>')
|
||||||
|
.replace(INLINE.em[1], '<em>$1</em>')
|
||||||
|
.replace(INLINE.del, '<del>$1</del>')
|
||||||
|
.replace(INLINE.img, '<img src="$2" alt="$1">')
|
||||||
|
.replace(INLINE.a, (m1, txt, link, m2, attr = '') => {
|
||||||
|
var tmp = attr
|
||||||
|
.split(';')
|
||||||
|
.filter(_ => _)
|
||||||
|
.map(_ => {
|
||||||
|
var a = _.split('=')
|
||||||
|
if (a.length > 1) {
|
||||||
|
return `${a[0]}="${a[1]}"`
|
||||||
|
} else {
|
||||||
|
return `title="${_}"`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(' ')
|
||||||
|
|
||||||
|
return `<a href="${link.trim()}" ${tmp}>${txt}</a>`
|
||||||
|
})
|
||||||
|
.replace(INLINE.qlink, (m, txt, n) => {
|
||||||
|
var _ = this.__LINKS__[n]
|
||||||
|
if (_) {
|
||||||
|
var a = _.t ? `title=${_.t}` : ''
|
||||||
|
return `<a href="${_.l}" ${a}>${txt}</a>`
|
||||||
|
} else {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.replace(ESCAPE_RE, '$1') // 处理转义字符
|
||||||
|
},
|
||||||
|
// 分割线
|
||||||
|
hr(name = '') {
|
||||||
|
return `<fieldset class="md-hr"><legend name="${name}"></legend></fieldset>`
|
||||||
|
},
|
||||||
|
// 标题
|
||||||
|
head(str) {
|
||||||
|
if (str.startsWith('#')) {
|
||||||
|
return str.replace(/^(#{1,6}) (.*)/, (p, m1, m2) => {
|
||||||
|
m2 = m2.trim()
|
||||||
|
let level = m1.trim().length
|
||||||
|
let hash = m2.replace(/\s/g, '').replace(/<\/?[^>]*?>/g, '')
|
||||||
|
|
||||||
|
if (level === 1) {
|
||||||
|
return `<h1>${m2}</h1>`
|
||||||
|
} else {
|
||||||
|
return `<h${level}><a href="#${hash}" id="${hash}" class="md-head-link">${m2}</a></h${level}>`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
// 引用模块
|
||||||
|
blockquote(str) {
|
||||||
|
//
|
||||||
|
},
|
||||||
|
// 任务
|
||||||
|
task(str) {
|
||||||
|
var todoChecked = Helper.isTodo(str)
|
||||||
|
if (~todoChecked) {
|
||||||
|
var word = str.replace(TODO_RE, '').trim()
|
||||||
|
var stat = todoChecked === 1 ? 'checked' : ''
|
||||||
|
var txt = todoChecked === 1 ? `<del>${word}</del>` : word
|
||||||
|
|
||||||
|
return `<section><wc-checkbox readonly ${stat}>${txt}</wc-checkbox></section>`
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixed(str) {
|
||||||
|
// 去掉\r, 将\t转为空格(2个)
|
||||||
|
return str
|
||||||
|
.replace(/\r\n|\r/g, '\n')
|
||||||
|
.replace(/\t/g, ' ')
|
||||||
|
.replace(/\u00a0/g, ' ')
|
||||||
|
.replace(/\u2424/g, '\n')
|
||||||
|
.replace(TAG_RE, (m, name, attr) => {
|
||||||
|
// 标签内的换行, 转为一组特殊字符, 方便后面还原
|
||||||
|
return `<${name + attr.replace(/\n/g, ATTR_BR_SYMBOL)}>`
|
||||||
|
})
|
||||||
|
.replace(BLOCK_RE, (m, tag, attr, txt) => {
|
||||||
|
return `<${tag + attr}>${txt.replace(/\n/g, NODE_BR_SYMBOL)}</${tag}>`
|
||||||
|
})
|
||||||
|
.replace(CODEBLOCK_RE, (m, lang, txt) => {
|
||||||
|
// 还原换行
|
||||||
|
let rollback = txt.replace(NODE_BR_EXP, '\n').replace(ATTR_BR_EXP, '\n')
|
||||||
|
return '```' + lang + rollback + '```'
|
||||||
|
})
|
||||||
|
.replace(BLOCK_RE, (m, tag, attr, txt) => {
|
||||||
|
return `<${tag + attr.replace(ATTR_BR_EXP, ' ')}>${txt
|
||||||
|
.replace(NODE_BR_EXP, '\n')
|
||||||
|
.replace(ATTR_BR_EXP, ' ')}</${tag}>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tool {
|
||||||
|
constructor(list, links) {
|
||||||
|
this.list = list
|
||||||
|
this.__LINKS__ = links
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化字符串, 处理多余换行等
|
||||||
|
static init(str = '') {
|
||||||
|
var links = {}
|
||||||
|
var list = []
|
||||||
|
var lines = fixed(str).split('\n')
|
||||||
|
var isCodeBlock = false // 是否代码块
|
||||||
|
var isTable = false // 是否表格
|
||||||
|
var emptyLineLength = 0 //连续空行的数量
|
||||||
|
|
||||||
|
// console.log(lines)
|
||||||
|
|
||||||
|
for (let it of lines) {
|
||||||
|
let tmp = it.trim()
|
||||||
|
|
||||||
|
// 非空行
|
||||||
|
if (tmp) {
|
||||||
|
emptyLineLength = 0
|
||||||
|
if (tmp.startsWith('```')) {
|
||||||
|
if (isCodeBlock) {
|
||||||
|
list.push('</xmp></wc-code>')
|
||||||
|
} else {
|
||||||
|
list.push(
|
||||||
|
tmp.replace(/^```([\w\#\-]*?)$/, `<wc-code lang="$1"><xmp>`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
isCodeBlock = !isCodeBlock
|
||||||
|
} else {
|
||||||
|
var qlink
|
||||||
|
if (isCodeBlock) {
|
||||||
|
it = it
|
||||||
|
.replace(/<(\/?)([a-z][a-z\d\-]*?)([^>]*?)>/g, '<$1$2$3>')
|
||||||
|
.replace('\\`\\`\\`', '```')
|
||||||
|
} else {
|
||||||
|
if (Helper.isTable(tmp) && !isTable) {
|
||||||
|
var thead = tmp.split('|')
|
||||||
|
// 去头去尾
|
||||||
|
thead.shift()
|
||||||
|
thead.pop()
|
||||||
|
list.push(
|
||||||
|
`<table><thead><tr>${thead
|
||||||
|
.map(_ => `<th>${_}</th>`)
|
||||||
|
.join('')}</tr></thead><tbody>`
|
||||||
|
)
|
||||||
|
isTable = true
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
it = it
|
||||||
|
// 非代码块进行xss过滤
|
||||||
|
.replace(INLINE.code, (m, txt) => {
|
||||||
|
return `\`${txt
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')}\``
|
||||||
|
})
|
||||||
|
.replace(/<(\/?)script[^>]*?>/g, '<$1script>')
|
||||||
|
.replace(TAG_RE, (m, name, attr = '') => {
|
||||||
|
// 过滤所有onXX=""事件属性
|
||||||
|
attr = attr.replace(ATTR_RE, ' ').trim()
|
||||||
|
if (attr) {
|
||||||
|
attr = ' ' + attr
|
||||||
|
}
|
||||||
|
return `<${name}${attr}>`
|
||||||
|
})
|
||||||
|
// 不在代码块中, 才判断引用声明
|
||||||
|
qlink = Helper.isQLink(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qlink) {
|
||||||
|
Object.assign(links, qlink)
|
||||||
|
} else {
|
||||||
|
list.push(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isTable) {
|
||||||
|
isTable = false
|
||||||
|
list.push('</tbody></table>')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (list.length === 0 || (!isCodeBlock && emptyLineLength > 0)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
emptyLineLength++
|
||||||
|
list.push(tmp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new this(list, links)
|
||||||
|
}
|
||||||
|
|
||||||
|
parse() {
|
||||||
|
var html = ''
|
||||||
|
var isCodeBlock = false // 是否代码块
|
||||||
|
var emptyLineLength = 0 //连续空行的数量
|
||||||
|
var isBlockquote = false
|
||||||
|
var isTable = false
|
||||||
|
var tableAlign = null
|
||||||
|
var blockquoteLevel = 0
|
||||||
|
var isParagraph = false
|
||||||
|
|
||||||
|
var isList = false
|
||||||
|
var orderListLevel = -1
|
||||||
|
var unorderListLevel = -1
|
||||||
|
|
||||||
|
var isQuoteList = false // 引用中的列表, 只支持一层级
|
||||||
|
var quoteListStyle = 0 // 1有序, 2 无序
|
||||||
|
|
||||||
|
//
|
||||||
|
for (let it of this.list) {
|
||||||
|
// 非空行
|
||||||
|
if (it) {
|
||||||
|
emptyLineLength = 0
|
||||||
|
|
||||||
|
if (~it.indexOf('<table>') || ~it.indexOf('</table>')) {
|
||||||
|
html += it
|
||||||
|
isTable = !isTable
|
||||||
|
tableAlign = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTable) {
|
||||||
|
let tmp = it.split('|').map(_ => _.trim())
|
||||||
|
tmp.shift()
|
||||||
|
tmp.pop()
|
||||||
|
|
||||||
|
// 表格分割行, 配置对齐方式的
|
||||||
|
if (tableAlign === true) {
|
||||||
|
tableAlign = tmp.map(a => {
|
||||||
|
a = a.split(/\-+/)
|
||||||
|
if (a[0] === ':' && a[1] === ':') {
|
||||||
|
return 'align="center"'
|
||||||
|
}
|
||||||
|
if (a[1] === ':') {
|
||||||
|
return 'align="right"'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
html += `<tr>${tmp
|
||||||
|
.map(
|
||||||
|
(_, i) =>
|
||||||
|
`<td ${tableAlign[i]}>${Decoder.inline.call(this, _)}</td>`
|
||||||
|
)
|
||||||
|
.join('')}</tr>`
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// wc-code标签直接拼接, 判断时多拼一个 < 和 >,
|
||||||
|
// 是为了避免在 wc-markd嵌入代码块示例时, 将其内容编译为html
|
||||||
|
if (~it.indexOf('<wc-code') || ~it.indexOf('wc-code>')) {
|
||||||
|
if (isParagraph) {
|
||||||
|
isParagraph = false
|
||||||
|
html += '</p>'
|
||||||
|
}
|
||||||
|
html += it
|
||||||
|
isCodeBlock = !isCodeBlock
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同上代码块的处理
|
||||||
|
if (isCodeBlock) {
|
||||||
|
html += '\n' + it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无属性标签
|
||||||
|
|
||||||
|
let hrName = Helper.isHr(it)
|
||||||
|
if (typeof hrName === 'string') {
|
||||||
|
html += Decoder.hr(hrName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先处理一些常规样式
|
||||||
|
it = Decoder.inline.call(this, it)
|
||||||
|
|
||||||
|
// 标题只能是单行
|
||||||
|
|
||||||
|
let head = Decoder.head(it)
|
||||||
|
if (head) {
|
||||||
|
isParagraph = false
|
||||||
|
html += head
|
||||||
|
// console.log(html)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 引用
|
||||||
|
if (it.startsWith('>')) {
|
||||||
|
let innerQuote // 是否有缩进引用
|
||||||
|
it = it.replace(/^(>+) /, (p, m) => {
|
||||||
|
let len = m.length
|
||||||
|
let tmp = ''
|
||||||
|
let loop = len
|
||||||
|
// 若之前已经有一个未闭合的引用, 需要减去已有缩进级别, 避免产生新的引用标签
|
||||||
|
if (isBlockquote) {
|
||||||
|
loop = len - blockquoteLevel
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
|
||||||
|
while (loop > 0) {
|
||||||
|
loop--
|
||||||
|
tmp += '<blockquote class="md-quote">'
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquoteLevel = len
|
||||||
|
innerQuote = !!tmp
|
||||||
|
return tmp
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isBlockquote) {
|
||||||
|
// 没有新的缩进引用时, 才添加换行
|
||||||
|
if (innerQuote) {
|
||||||
|
// 之前有引用的列表时, 直接结束列表
|
||||||
|
if (isQuoteList) {
|
||||||
|
html += `</${quoteListStyle === 1 ? 'ul' : 'ul'}>`
|
||||||
|
isQuoteList = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let qListChecked = it.match(INLINE.qlist)
|
||||||
|
if (qListChecked) {
|
||||||
|
let tmp1 = qListChecked[1] // 缩进的标签
|
||||||
|
let tmp2 = +qListChecked[3] // 有序还是无序
|
||||||
|
let tmp3 = qListChecked.pop() // 文本
|
||||||
|
let currListStyle = tmp2 === tmp2 ? 1 : 2
|
||||||
|
var qlist = ''
|
||||||
|
|
||||||
|
// 已有列表
|
||||||
|
if (isQuoteList) {
|
||||||
|
// 因为只支持一层级的列表, 所以同一级别不区分有序无序, 强制统一
|
||||||
|
} else {
|
||||||
|
isQuoteList = true
|
||||||
|
if (currListStyle === 1) {
|
||||||
|
qlist += '<ol>'
|
||||||
|
} else {
|
||||||
|
qlist += '<ul>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteListStyle = currListStyle
|
||||||
|
|
||||||
|
qlist += `<li>${tmp3}</li>`
|
||||||
|
html += tmp1 + qlist
|
||||||
|
} else {
|
||||||
|
if (innerQuote === false) {
|
||||||
|
html += '<br>'
|
||||||
|
}
|
||||||
|
html += it
|
||||||
|
}
|
||||||
|
|
||||||
|
isParagraph = false
|
||||||
|
isBlockquote = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务
|
||||||
|
let task = Decoder.task(it)
|
||||||
|
if (task) {
|
||||||
|
html += task
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列表
|
||||||
|
let listChecked = Helper.isList(it)
|
||||||
|
if (~listChecked) {
|
||||||
|
// 左侧空格长度
|
||||||
|
let tmp = Helper.ltrim(it)
|
||||||
|
let ltrim = it.length - tmp.length
|
||||||
|
let word = tmp.replace(LIST_RE, '').trim()
|
||||||
|
let level = Math.floor(ltrim / 2)
|
||||||
|
let tag = listChecked > 0 ? 'ol' : 'ul'
|
||||||
|
|
||||||
|
if (isList) {
|
||||||
|
if (listChecked === 1) {
|
||||||
|
if (level > orderListLevel) {
|
||||||
|
html = html.replace(/<\/li>$/, '')
|
||||||
|
html += `<${tag}><li>${word}</li>`
|
||||||
|
} else if (level === orderListLevel) {
|
||||||
|
html += `<li>${word}</li>`
|
||||||
|
} else {
|
||||||
|
html += `</${tag}></li><li>${word}</li>`
|
||||||
|
}
|
||||||
|
orderListLevel = level
|
||||||
|
} else {
|
||||||
|
if (level > unorderListLevel) {
|
||||||
|
html = html.replace(/<\/li>$/, '')
|
||||||
|
html += `<${tag}><li>${word}</li>`
|
||||||
|
} else if (level === unorderListLevel) {
|
||||||
|
html += `<li>${word}</li>`
|
||||||
|
} else {
|
||||||
|
html += `</${tag}></li><li>${word}</li>`
|
||||||
|
}
|
||||||
|
unorderListLevel = level
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html += `<${tag}>`
|
||||||
|
if (listChecked === 1) {
|
||||||
|
orderListLevel = level
|
||||||
|
} else {
|
||||||
|
unorderListLevel = level
|
||||||
|
}
|
||||||
|
html += `<li>${word}</li>`
|
||||||
|
}
|
||||||
|
|
||||||
|
isList = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无"> "前缀的引用, 继续拼到之前的, 并且不换行
|
||||||
|
if (isBlockquote) {
|
||||||
|
html += it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Helper.isNativeDom(it)) {
|
||||||
|
html += it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isParagraph) {
|
||||||
|
html += `${it}<br>`
|
||||||
|
} else {
|
||||||
|
html += `<p>${it}<br>`
|
||||||
|
}
|
||||||
|
isParagraph = true
|
||||||
|
} else {
|
||||||
|
// 如果是在代码中, 直接拼接, 并加上换行
|
||||||
|
if (isCodeBlock) {
|
||||||
|
html += it + '\n'
|
||||||
|
} else {
|
||||||
|
emptyLineLength++
|
||||||
|
|
||||||
|
// 引用结束
|
||||||
|
if (isBlockquote) {
|
||||||
|
isBlockquote = false
|
||||||
|
if (emptyLineLength > 1) {
|
||||||
|
emptyLineLength = 0
|
||||||
|
while (blockquoteLevel > 0) {
|
||||||
|
blockquoteLevel--
|
||||||
|
html += '</blockquote>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isList) {
|
||||||
|
if (emptyLineLength > 1) {
|
||||||
|
while (orderListLevel > -1 || unorderListLevel > -1) {
|
||||||
|
if (orderListLevel > unorderListLevel) {
|
||||||
|
html += '</ol>'
|
||||||
|
orderListLevel--
|
||||||
|
} else {
|
||||||
|
html += '</ul>'
|
||||||
|
unorderListLevel--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isList = false
|
||||||
|
emptyLineLength = 0
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (isParagraph) {
|
||||||
|
if (emptyLineLength > 1) {
|
||||||
|
isParagraph = false
|
||||||
|
html += '</p>'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修正内嵌样式
|
||||||
|
html = html.replace(STYLE_RE, (m, code) => {
|
||||||
|
return `<style>${code
|
||||||
|
.replace(/<br>/g, '')
|
||||||
|
.replace(/<p>/g, '')
|
||||||
|
.replace(/<\/p>/g, '')}</style>`
|
||||||
|
})
|
||||||
|
delete this.list
|
||||||
|
delete this.__LINKS__
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (str) {
|
||||||
|
return Tool.init(str).parse()
|
||||||
|
}
|
|
@ -1,588 +1,200 @@
|
||||||
/**
|
/**
|
||||||
* markdown解析器
|
* {markd组件}
|
||||||
* @author yutent<yutent.io@gmail.com>
|
* @author yutent<yutent.io@gmail.com>
|
||||||
* @date 2020/02/07 17:14:19
|
* @date 2023/03/06 15:17:25
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const HR_LIST = ['=', '-', '_', '*']
|
import { css, html, raw, Component, bind, styleMap, classMap } from '@bd/core'
|
||||||
const LIST_RE = /^(([\+\-\*])|(\d+\.))\s/
|
|
||||||
const TODO_RE = /^[\+\-\*]\s\[(x|\s)\]\s/
|
|
||||||
const ESCAPE_RE = /\\([-+*_`\]\[\(\)])/g
|
|
||||||
const QLINK_RE = /^\[(\d+)\]: ([\S]+)\s*?((['"])[\s\S]*?\4)?\s*?$/
|
|
||||||
const TAG_RE = /<([\w\-]+)([\w\W]*?)>/g
|
|
||||||
const ATTR_RE = /\s*?on[a-zA-Z]+="[^"]*?"\s*?/g
|
|
||||||
const CODEBLOCK_RE = /```(.*?)([\w\W]*?)```/g
|
|
||||||
const BLOCK_RE = /<([\w\-]+)([^>]*?)>([\w\W]*?)<\/\1>/g
|
|
||||||
const IS_DOM_RE = /^<([\w\-]+)[^>]*?>.*?<\/\1>$/
|
|
||||||
const STYLE_RE = /<style[^>]*?>([\w\W]*?)<\/style>/g
|
|
||||||
|
|
||||||
const INLINE = {
|
import md2html from './core.js'
|
||||||
code: /`([^`]*?[^`\\\s])`/g,
|
import '../code/index.js'
|
||||||
strong: [/__([\s\S]*?[^\s\\])__(?!_)/g, /\*\*([\s\S]*?[^\s\\])\*\*(?!\*)/g],
|
import '../form/checkbox.js'
|
||||||
em: [/_([\s\S]*?[^\s\\])_(?!_)/g, /\*([\s\S]*?[^\s\\*])\*(?!\*)/g],
|
|
||||||
del: /~~([\s\S]*?[^\s\\~])~~/g,
|
|
||||||
qlink: /\[([^\]]*?)\]\[(\d*?)\]/g, // 引用链接
|
|
||||||
img: /\!\[([^\]]*?)\]\(([^)]*?)\)/g,
|
|
||||||
a: /\[([^\]]*?)\]\(([^)]*?)(\s+"([\s\S]*?)")*?\)/g,
|
|
||||||
qlist: /((<blockquote class="md\-quote">)*?)([\+\-\*]|\d+\.) (.*)/ // 引用中的列表
|
|
||||||
}
|
|
||||||
|
|
||||||
const ATTR_BR_SYMBOL = '⨨☇'
|
class Markd extends Component {
|
||||||
const NODE_BR_SYMBOL = '⨨⤶'
|
static props = {
|
||||||
const ATTR_BR_EXP = new RegExp(ATTR_BR_SYMBOL, 'g')
|
code: ''
|
||||||
const NODE_BR_EXP = new RegExp(NODE_BR_SYMBOL, 'g')
|
}
|
||||||
|
|
||||||
const Helper = {
|
static styles = css`
|
||||||
// 是否分割线
|
:host {
|
||||||
isHr(str) {
|
display: block;
|
||||||
var s = str[0]
|
line-height: 1.5;
|
||||||
if (HR_LIST.includes(s)) {
|
color: var(--color-dark-1);
|
||||||
return str.slice(0, 3) === s.repeat(3) ? str.slice(3) : false
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
return false
|
a {
|
||||||
},
|
text-decoration: underline;
|
||||||
// 是否列表, -1不是, 1为有序列表, 0为无序列表
|
color: var(--color-teal-2);
|
||||||
isList(str) {
|
}
|
||||||
var v = str.trim()
|
a:hover {
|
||||||
if (LIST_RE.test(v)) {
|
color: var(--color-teal-1);
|
||||||
var n = +v[0]
|
text-decoration: none;
|
||||||
if (n === n) {
|
}
|
||||||
return 1
|
|
||||||
} else {
|
em,
|
||||||
return 0
|
del {
|
||||||
|
color: var(--color-grey-2);
|
||||||
|
}
|
||||||
|
strong,
|
||||||
|
strong em,
|
||||||
|
strong {
|
||||||
|
color: var(--color-dark-3);
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
strong,
|
||||||
|
em {
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1
|
em,
|
||||||
},
|
strong,
|
||||||
// 是否任务列表
|
del {
|
||||||
isTodo(str) {
|
padding: 0 2px;
|
||||||
var v = str.trim()
|
|
||||||
if (TODO_RE.test(v)) {
|
|
||||||
return v[3] === 'x' ? 1 : 0
|
|
||||||
}
|
}
|
||||||
return -1
|
p {
|
||||||
},
|
margin: 12px 0;
|
||||||
ltrim(str) {
|
|
||||||
if (str.trimStart) {
|
|
||||||
return str.trimStart()
|
|
||||||
}
|
}
|
||||||
return str.replace(/^\s+/, '')
|
img {
|
||||||
},
|
max-width: 100%;
|
||||||
isQLink(str) {
|
|
||||||
if (QLINK_RE.test(str)) {
|
|
||||||
// l: link, t: title, $1: index
|
|
||||||
return { [RegExp.$1]: { l: RegExp.$2, t: RegExp.$3 } }
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
},
|
|
||||||
isTable(str) {
|
|
||||||
return /^\|.+?\|$/.test(str)
|
|
||||||
},
|
|
||||||
// 是否原生dom节点
|
|
||||||
isNativeDom(str) {
|
|
||||||
return IS_DOM_RE.test(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Decoder = {
|
blockquote.md-quote {
|
||||||
// 内联样式
|
margin: 10px 0;
|
||||||
inline(str) {
|
padding: 5px 10px;
|
||||||
return str
|
line-height: 1.5;
|
||||||
.replace(INLINE.code, (m, str) => {
|
border-left: 5px solid var(--color-teal-1);
|
||||||
str = str.replace(/([_*~])/g, '\\$1')
|
background: #f2faf7;
|
||||||
return `<code class="inline">${str}</code>`
|
color: var(--color-grey-2);
|
||||||
})
|
p {
|
||||||
.replace(INLINE.strong[0], '<strong>$1</strong>')
|
margin: 0;
|
||||||
.replace(INLINE.strong[1], '<strong>$1</strong>')
|
|
||||||
.replace(INLINE.em[0], '<em>$1</em>')
|
|
||||||
.replace(INLINE.em[1], '<em>$1</em>')
|
|
||||||
.replace(INLINE.del, '<del>$1</del>')
|
|
||||||
.replace(INLINE.img, '<img src="$2" alt="$1">')
|
|
||||||
.replace(INLINE.a, (m1, txt, link, m2, attr = '') => {
|
|
||||||
var tmp = attr
|
|
||||||
.split(';')
|
|
||||||
.filter(_ => _)
|
|
||||||
.map(_ => {
|
|
||||||
var a = _.split('=')
|
|
||||||
if (a.length > 1) {
|
|
||||||
return `${a[0]}="${a[1]}"`
|
|
||||||
} else {
|
|
||||||
return `title="${_}"`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.join(' ')
|
|
||||||
|
|
||||||
return `<a href="${link.trim()}" ${tmp}>${txt}</a>`
|
|
||||||
})
|
|
||||||
.replace(INLINE.qlink, (m, txt, n) => {
|
|
||||||
var _ = this.__LINKS__[n]
|
|
||||||
if (_) {
|
|
||||||
var a = _.t ? `title=${_.t}` : ''
|
|
||||||
return `<a href="${_.l}" ${a}>${txt}</a>`
|
|
||||||
} else {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.replace(ESCAPE_RE, '$1') // 处理转义字符
|
|
||||||
},
|
|
||||||
// 分割线
|
|
||||||
hr(name = '') {
|
|
||||||
return `<fieldset class="md-hr"><legend name="${name}"></legend></fieldset>`
|
|
||||||
},
|
|
||||||
// 标题
|
|
||||||
head(str) {
|
|
||||||
if (str.startsWith('#')) {
|
|
||||||
return str.replace(/^(#{1,6}) (.*)/, (p, m1, m2) => {
|
|
||||||
m2 = m2.trim()
|
|
||||||
let level = m1.trim().length
|
|
||||||
let hash = m2.replace(/\s/g, '').replace(/<\/?[^>]*?>/g, '')
|
|
||||||
|
|
||||||
if (level === 1) {
|
|
||||||
return `<h1>${m2}</h1>`
|
|
||||||
} else {
|
|
||||||
return `<h${level}><a href="#${hash}" id="${hash}" class="md-head-link">${m2}</a></h${level}>`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
// 引用模块
|
|
||||||
blockquote(str) {
|
|
||||||
//
|
|
||||||
},
|
|
||||||
// 任务
|
|
||||||
task(str) {
|
|
||||||
var todoChecked = Helper.isTodo(str)
|
|
||||||
if (~todoChecked) {
|
|
||||||
var word = str.replace(TODO_RE, '').trim()
|
|
||||||
var stat = todoChecked === 1 ? 'checked' : ''
|
|
||||||
var txt = todoChecked === 1 ? `<del>${word}</del>` : word
|
|
||||||
|
|
||||||
return `<section><wc-checkbox readonly ${stat}>${txt}</wc-checkbox></section>`
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixed(str) {
|
|
||||||
// 去掉\r, 将\t转为空格(2个)
|
|
||||||
return str
|
|
||||||
.replace(/\r\n|\r/g, '\n')
|
|
||||||
.replace(/\t/g, ' ')
|
|
||||||
.replace(/\u00a0/g, ' ')
|
|
||||||
.replace(/\u2424/g, '\n')
|
|
||||||
.replace(TAG_RE, (m, name, attr) => {
|
|
||||||
// 标签内的换行, 转为一组特殊字符, 方便后面还原
|
|
||||||
return `<${name + attr.replace(/\n/g, ATTR_BR_SYMBOL)}>`
|
|
||||||
})
|
|
||||||
.replace(BLOCK_RE, (m, tag, attr, txt) => {
|
|
||||||
return `<${tag + attr}>${txt.replace(/\n/g, NODE_BR_SYMBOL)}</${tag}>`
|
|
||||||
})
|
|
||||||
.replace(CODEBLOCK_RE, (m, lang, txt) => {
|
|
||||||
// 还原换行
|
|
||||||
let rollback = txt.replace(NODE_BR_EXP, '\n').replace(ATTR_BR_EXP, '\n')
|
|
||||||
return '```' + lang + rollback + '```'
|
|
||||||
})
|
|
||||||
.replace(BLOCK_RE, (m, tag, attr, txt) => {
|
|
||||||
return `<${tag + attr.replace(ATTR_BR_EXP, ' ')}>${txt
|
|
||||||
.replace(NODE_BR_EXP, '\n')
|
|
||||||
.replace(ATTR_BR_EXP, ' ')}</${tag}>`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
class Tool {
|
|
||||||
constructor(list, links) {
|
|
||||||
this.list = list
|
|
||||||
this.__LINKS__ = links
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化字符串, 处理多余换行等
|
|
||||||
static init(str = '') {
|
|
||||||
var links = {}
|
|
||||||
var list = []
|
|
||||||
var lines = fixed(str).split('\n')
|
|
||||||
var isCodeBlock = false // 是否代码块
|
|
||||||
var isTable = false // 是否表格
|
|
||||||
var emptyLineLength = 0 //连续空行的数量
|
|
||||||
|
|
||||||
// console.log(lines)
|
|
||||||
|
|
||||||
for (let it of lines) {
|
|
||||||
let tmp = it.trim()
|
|
||||||
|
|
||||||
// 非空行
|
|
||||||
if (tmp) {
|
|
||||||
emptyLineLength = 0
|
|
||||||
if (tmp.startsWith('```')) {
|
|
||||||
if (isCodeBlock) {
|
|
||||||
list.push('</xmp></wc-code>')
|
|
||||||
} else {
|
|
||||||
list.push(
|
|
||||||
tmp.replace(/^```([\w\#\-]*?)$/, `<wc-code lang="$1"><xmp>`)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
isCodeBlock = !isCodeBlock
|
|
||||||
} else {
|
|
||||||
var qlink
|
|
||||||
if (isCodeBlock) {
|
|
||||||
it = it
|
|
||||||
.replace(/<(\/?)([a-z][a-z\d\-]*?)([^>]*?)>/g, '<$1$2$3>')
|
|
||||||
.replace('\\`\\`\\`', '```')
|
|
||||||
} else {
|
|
||||||
if (Helper.isTable(tmp) && !isTable) {
|
|
||||||
var thead = tmp.split('|')
|
|
||||||
// 去头去尾
|
|
||||||
thead.shift()
|
|
||||||
thead.pop()
|
|
||||||
list.push(
|
|
||||||
`<table><thead><tr>${thead
|
|
||||||
.map(_ => `<th>${_}</th>`)
|
|
||||||
.join('')}</tr></thead><tbody>`
|
|
||||||
)
|
|
||||||
isTable = true
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
it = it
|
|
||||||
// 非代码块进行xss过滤
|
|
||||||
.replace(INLINE.code, (m, txt) => {
|
|
||||||
return `\`${txt
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')}\``
|
|
||||||
})
|
|
||||||
.replace(/<(\/?)script[^>]*?>/g, '<$1script>')
|
|
||||||
.replace(TAG_RE, (m, name, attr = '') => {
|
|
||||||
// 过滤所有onXX=""事件属性
|
|
||||||
attr = attr.replace(ATTR_RE, ' ').trim()
|
|
||||||
if (attr) {
|
|
||||||
attr = ' ' + attr
|
|
||||||
}
|
|
||||||
return `<${name}${attr}>`
|
|
||||||
})
|
|
||||||
// 不在代码块中, 才判断引用声明
|
|
||||||
qlink = Helper.isQLink(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qlink) {
|
|
||||||
Object.assign(links, qlink)
|
|
||||||
} else {
|
|
||||||
list.push(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isTable) {
|
|
||||||
isTable = false
|
|
||||||
list.push('</tbody></table>')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (list.length === 0 || (!isCodeBlock && emptyLineLength > 0)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
emptyLineLength++
|
|
||||||
list.push(tmp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new this(list, links)
|
|
||||||
}
|
|
||||||
|
|
||||||
parse() {
|
|
||||||
var html = ''
|
|
||||||
var isCodeBlock = false // 是否代码块
|
|
||||||
var emptyLineLength = 0 //连续空行的数量
|
|
||||||
var isBlockquote = false
|
|
||||||
var isTable = false
|
|
||||||
var tableAlign = null
|
|
||||||
var blockquoteLevel = 0
|
|
||||||
var isParagraph = false
|
|
||||||
|
|
||||||
var isList = false
|
|
||||||
var orderListLevel = -1
|
|
||||||
var unorderListLevel = -1
|
|
||||||
|
|
||||||
var isQuoteList = false // 引用中的列表, 只支持一层级
|
|
||||||
var quoteListStyle = 0 // 1有序, 2 无序
|
|
||||||
|
|
||||||
//
|
|
||||||
for (let it of this.list) {
|
|
||||||
// 非空行
|
|
||||||
if (it) {
|
|
||||||
emptyLineLength = 0
|
|
||||||
|
|
||||||
if (~it.indexOf('<table>') || ~it.indexOf('</table>')) {
|
|
||||||
html += it
|
|
||||||
isTable = !isTable
|
|
||||||
tableAlign = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTable) {
|
|
||||||
let tmp = it.split('|').map(_ => _.trim())
|
|
||||||
tmp.shift()
|
|
||||||
tmp.pop()
|
|
||||||
|
|
||||||
// 表格分割行, 配置对齐方式的
|
|
||||||
if (tableAlign === true) {
|
|
||||||
tableAlign = tmp.map(a => {
|
|
||||||
a = a.split(/\-+/)
|
|
||||||
if (a[0] === ':' && a[1] === ':') {
|
|
||||||
return 'align="center"'
|
|
||||||
}
|
|
||||||
if (a[1] === ':') {
|
|
||||||
return 'align="right"'
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
html += `<tr>${tmp
|
|
||||||
.map(
|
|
||||||
(_, i) =>
|
|
||||||
`<td ${tableAlign[i]}>${Decoder.inline.call(this, _)}</td>`
|
|
||||||
)
|
|
||||||
.join('')}</tr>`
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// wc-code标签直接拼接, 判断时多拼一个 < 和 >,
|
|
||||||
// 是为了避免在 wc-markd嵌入代码块示例时, 将其内容编译为html
|
|
||||||
if (~it.indexOf('<wc-code') || ~it.indexOf('wc-code>')) {
|
|
||||||
if (isParagraph) {
|
|
||||||
isParagraph = false
|
|
||||||
html += '</p>'
|
|
||||||
}
|
|
||||||
html += it
|
|
||||||
isCodeBlock = !isCodeBlock
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同上代码块的处理
|
|
||||||
if (isCodeBlock) {
|
|
||||||
html += '\n' + it
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无属性标签
|
|
||||||
|
|
||||||
let hrName = Helper.isHr(it)
|
|
||||||
if (typeof hrName === 'string') {
|
|
||||||
html += Decoder.hr(hrName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优先处理一些常规样式
|
|
||||||
it = Decoder.inline.call(this, it)
|
|
||||||
|
|
||||||
// 标题只能是单行
|
|
||||||
|
|
||||||
let head = Decoder.head(it)
|
|
||||||
if (head) {
|
|
||||||
isParagraph = false
|
|
||||||
html += head
|
|
||||||
// console.log(html)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 引用
|
|
||||||
if (it.startsWith('>')) {
|
|
||||||
let innerQuote // 是否有缩进引用
|
|
||||||
it = it.replace(/^(>+) /, (p, m) => {
|
|
||||||
let len = m.length
|
|
||||||
let tmp = ''
|
|
||||||
let loop = len
|
|
||||||
// 若之前已经有一个未闭合的引用, 需要减去已有缩进级别, 避免产生新的引用标签
|
|
||||||
if (isBlockquote) {
|
|
||||||
loop = len - blockquoteLevel
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
|
|
||||||
while (loop > 0) {
|
|
||||||
loop--
|
|
||||||
tmp += '<blockquote class="md-quote">'
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquoteLevel = len
|
|
||||||
innerQuote = !!tmp
|
|
||||||
return tmp
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isBlockquote) {
|
|
||||||
// 没有新的缩进引用时, 才添加换行
|
|
||||||
if (innerQuote) {
|
|
||||||
// 之前有引用的列表时, 直接结束列表
|
|
||||||
if (isQuoteList) {
|
|
||||||
html += `</${quoteListStyle === 1 ? 'ul' : 'ul'}>`
|
|
||||||
isQuoteList = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let qListChecked = it.match(INLINE.qlist)
|
|
||||||
if (qListChecked) {
|
|
||||||
let tmp1 = qListChecked[1] // 缩进的标签
|
|
||||||
let tmp2 = +qListChecked[3] // 有序还是无序
|
|
||||||
let tmp3 = qListChecked.pop() // 文本
|
|
||||||
let currListStyle = tmp2 === tmp2 ? 1 : 2
|
|
||||||
var qlist = ''
|
|
||||||
|
|
||||||
// 已有列表
|
|
||||||
if (isQuoteList) {
|
|
||||||
// 因为只支持一层级的列表, 所以同一级别不区分有序无序, 强制统一
|
|
||||||
} else {
|
|
||||||
isQuoteList = true
|
|
||||||
if (currListStyle === 1) {
|
|
||||||
qlist += '<ol>'
|
|
||||||
} else {
|
|
||||||
qlist += '<ul>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quoteListStyle = currListStyle
|
|
||||||
|
|
||||||
qlist += `<li>${tmp3}</li>`
|
|
||||||
html += tmp1 + qlist
|
|
||||||
} else {
|
|
||||||
if (innerQuote === false) {
|
|
||||||
html += '<br>'
|
|
||||||
}
|
|
||||||
html += it
|
|
||||||
}
|
|
||||||
|
|
||||||
isParagraph = false
|
|
||||||
isBlockquote = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 任务
|
|
||||||
let task = Decoder.task(it)
|
|
||||||
if (task) {
|
|
||||||
html += task
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 列表
|
|
||||||
let listChecked = Helper.isList(it)
|
|
||||||
if (~listChecked) {
|
|
||||||
// 左侧空格长度
|
|
||||||
let tmp = Helper.ltrim(it)
|
|
||||||
let ltrim = it.length - tmp.length
|
|
||||||
let word = tmp.replace(LIST_RE, '').trim()
|
|
||||||
let level = Math.floor(ltrim / 2)
|
|
||||||
let tag = listChecked > 0 ? 'ol' : 'ul'
|
|
||||||
|
|
||||||
if (isList) {
|
|
||||||
if (listChecked === 1) {
|
|
||||||
if (level > orderListLevel) {
|
|
||||||
html = html.replace(/<\/li>$/, '')
|
|
||||||
html += `<${tag}><li>${word}</li>`
|
|
||||||
} else if (level === orderListLevel) {
|
|
||||||
html += `<li>${word}</li>`
|
|
||||||
} else {
|
|
||||||
html += `</${tag}></li><li>${word}</li>`
|
|
||||||
}
|
|
||||||
orderListLevel = level
|
|
||||||
} else {
|
|
||||||
if (level > unorderListLevel) {
|
|
||||||
html = html.replace(/<\/li>$/, '')
|
|
||||||
html += `<${tag}><li>${word}</li>`
|
|
||||||
} else if (level === unorderListLevel) {
|
|
||||||
html += `<li>${word}</li>`
|
|
||||||
} else {
|
|
||||||
html += `</${tag}></li><li>${word}</li>`
|
|
||||||
}
|
|
||||||
unorderListLevel = level
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
html += `<${tag}>`
|
|
||||||
if (listChecked === 1) {
|
|
||||||
orderListLevel = level
|
|
||||||
} else {
|
|
||||||
unorderListLevel = level
|
|
||||||
}
|
|
||||||
html += `<li>${word}</li>`
|
|
||||||
}
|
|
||||||
|
|
||||||
isList = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无"> "前缀的引用, 继续拼到之前的, 并且不换行
|
|
||||||
if (isBlockquote) {
|
|
||||||
html += it
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Helper.isNativeDom(it)) {
|
|
||||||
html += it
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isParagraph) {
|
|
||||||
html += `${it}<br>`
|
|
||||||
} else {
|
|
||||||
html += `<p>${it}<br>`
|
|
||||||
}
|
|
||||||
isParagraph = true
|
|
||||||
} else {
|
|
||||||
// 如果是在代码中, 直接拼接, 并加上换行
|
|
||||||
if (isCodeBlock) {
|
|
||||||
html += it + '\n'
|
|
||||||
} else {
|
|
||||||
emptyLineLength++
|
|
||||||
|
|
||||||
// 引用结束
|
|
||||||
if (isBlockquote) {
|
|
||||||
isBlockquote = false
|
|
||||||
if (emptyLineLength > 1) {
|
|
||||||
emptyLineLength = 0
|
|
||||||
while (blockquoteLevel > 0) {
|
|
||||||
blockquoteLevel--
|
|
||||||
html += '</blockquote>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isList) {
|
|
||||||
if (emptyLineLength > 1) {
|
|
||||||
while (orderListLevel > -1 || unorderListLevel > -1) {
|
|
||||||
if (orderListLevel > unorderListLevel) {
|
|
||||||
html += '</ol>'
|
|
||||||
orderListLevel--
|
|
||||||
} else {
|
|
||||||
html += '</ul>'
|
|
||||||
unorderListLevel--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isList = false
|
|
||||||
emptyLineLength = 0
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
if (isParagraph) {
|
|
||||||
if (emptyLineLength > 1) {
|
|
||||||
isParagraph = false
|
|
||||||
html += '</p>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修正内嵌样式
|
fieldset.md-hr {
|
||||||
html = html.replace(STYLE_RE, (m, code) => {
|
margin: 30px 0;
|
||||||
return `<style>${code
|
border: 0;
|
||||||
.replace(/<br>/g, '')
|
border-top: 1px dashed var(--color-plain-3);
|
||||||
.replace(/<p>/g, '')
|
|
||||||
.replace(/<\/p>/g, '')}</style>`
|
legend {
|
||||||
})
|
color: var(--color-grey-1);
|
||||||
delete this.list
|
text-align: center;
|
||||||
delete this.__LINKS__
|
font-size: 12px;
|
||||||
return html
|
&::before {
|
||||||
|
content: attr(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ol {
|
||||||
|
margin-left: 1em;
|
||||||
|
list-style: decimal outside none;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
margin-left: 1em;
|
||||||
|
list-style: disc outside none;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
li ol {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
li ul {
|
||||||
|
margin-left: 1em;
|
||||||
|
list-style-type: circle;
|
||||||
|
}
|
||||||
|
li ol ul,
|
||||||
|
li ul ul {
|
||||||
|
list-style-type: square;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin: 15px 0;
|
||||||
|
line-height: 2;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
code.inline {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
a {
|
||||||
|
&::before {
|
||||||
|
content: '∮ ';
|
||||||
|
color: var(--color-teal-1);
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 30px;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 20px 0;
|
||||||
|
font-size: 22px;
|
||||||
|
border-bottom: 1px solid var(--color-plain-2);
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 20px 0 15px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
tr {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
thead tr {
|
||||||
|
background: var(--color-plain-1);
|
||||||
|
}
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 6px 13px;
|
||||||
|
border: 1px solid var(--color-plain-2);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
tr:nth-child(2n) {
|
||||||
|
background: #fcfdff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code.inline {
|
||||||
|
display: inline;
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 0 2px;
|
||||||
|
color: var(--color-red-1);
|
||||||
|
background: var(--color-plain-1);
|
||||||
|
border-radius: 2px;
|
||||||
|
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`<div class="container">${raw(md2html(this.code))}</div>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (str) {
|
Markd.reg('markd')
|
||||||
return Tool.init(str).parse()
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue