This repository has been archived on 2023-08-30. You can view files and clone it, but cannot push or open issues/pull-requests.
bytedo
/
wcui
Archived
1
0
Fork 0

markdown解析器完成引用,列表,任务,分割线的解析

old
宇天 2020-02-10 23:42:02 +08:00
parent 6b17ad76ba
commit 36a7f0fbe9
2 changed files with 197 additions and 62 deletions

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="markd"><slot /></div> <div class="markd-box"><slot /></div>
</template> </template>
<style lang="scss"> <style lang="scss">
@ -9,10 +9,8 @@
color: nth($cd, 1); color: nth($cd, 1);
font-size: 14px; font-size: 14px;
} }
.markd { .markd-box {
padding: 10px; padding: 10px;
// white-space: pre-wrap;
// word-wrap: break-word;
} }
a { a {
text-decoration: underline; text-decoration: underline;
@ -23,8 +21,9 @@ a:hover {
text-decoration: none; text-decoration: none;
} }
em { em,
color: nth($cgr, 3); del {
color: nth($cgr, 2);
} }
strong { strong {
color: nth($cd, 3); color: nth($cd, 3);
@ -38,7 +37,7 @@ a {
em, em,
strong, strong,
del { del {
padding: 0 3px; padding: 0 2px;
} }
p { p {
margin: 15px 0; margin: 15px 0;
@ -129,13 +128,19 @@ blockquote.md-quote {
} }
} }
hr { fieldset.md-hr {
height: 1px;
margin: 30px 0; margin: 30px 0;
line-height: 1px;
border: 0; border: 0;
border-top: 1px dashed nth($cp, 3);
legend {
padding: 0 5px;
color: nth($cgr, 1); color: nth($cgr, 1);
background-color: nth($cgr, 1); text-align: center;
&::before {
content: '华丽丽的分割线';
}
}
} }
ol { ol {
margin-left: 1em; margin-left: 1em;
@ -166,36 +171,40 @@ h3,
h4, h4,
h5, h5,
h6 { h6 {
position: relative;
margin: 15px 0; margin: 15px 0;
line-height: 2;
font-weight: bold; font-weight: bold;
font-size: 16px; font-size: 16px;
span { code.inline {
position: relative; background: none;
display: inline-block;
padding: 0 5px;
} }
a { a {
text-decoration: none; text-decoration: none;
color: #333; color: #333;
}
}
h3,
h4,
h5,
h6 {
a {
&::before { &::before {
content: '# '; content: '# ';
color: nth($ct, 1); color: nth($ct, 1);
font-weight: normal;
} }
} }
&:hover a {
visibility: visible;
}
} }
h1 { h1 {
margin: 0 0 30px; margin: 0 0 30px;
font-size: 24px; font-size: 24px;
text-align: center;
} }
h2 { h2 {
margin: 20px 0; margin: 20px 0;
font-size: 22px; font-size: 22px;
border-bottom: 1px solid nth($cp, 2);
} }
h3 { h3 {
margin: 20px 0 15px; margin: 20px 0 15px;
@ -232,10 +241,11 @@ table {
code.inline { code.inline {
display: inline-block; display: inline-block;
margin: 0 2px; margin: 0 2px;
padding: 0 3px; padding: 0 2px;
color: nth($co, 3); color: nth($co, 3);
background: nth($cp, 1); background: nth($cp, 1);
border-radius: 2px; border-radius: 2px;
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
} }
</style> </style>
@ -243,7 +253,7 @@ code.inline {
import $ from '../utils' import $ from '../utils'
import '../code/index' import '../code/index'
// import parser from './core' import core from './core'
import parser from './parser' import parser from './parser'
export default class Markd { export default class Markd {
@ -257,9 +267,16 @@ export default class Markd {
} }
mounted() { mounted() {
// this.__BOX__.innerHTML = parser.safe(this.textContent) // this.__BOX__.innerHTML = core.safe(this.textContent)
this.__BOX__.innerHTML = parser(this.textContent) this.__BOX__.innerHTML = parser(this.textContent)
this.textContent = '' this.textContent = ''
$.bind(this.__BOX__, 'click', ev => {
if (ev.target.className === 'md-head-link') {
var ot = ev.target.offsetTop
document.documentElement.scrollTop = ot
}
})
} }
} }
</script> </script>

View File

@ -6,6 +6,8 @@
'use strict' 'use strict'
const HR_LIST = ['=', '-', '_', '*'] const HR_LIST = ['=', '-', '_', '*']
const LIST_REG = /^(([\+\-\*])|(\d+\.))\s/
const TODO_REG = /^\[(x|\s)\]\s/
const log = console.log const log = console.log
const Helper = { const Helper = {
@ -16,12 +18,42 @@ const Helper = {
return str.startsWith(s.repeat(3)) return str.startsWith(s.repeat(3))
} }
return false return false
},
// 是否列表, -1不是, 1为有序列表, 0为无序列表
isList(str) {
var v = str.trim()
if (LIST_REG.test(v)) {
var n = +v[0]
if (n === n) {
return 1
} else {
return 0
}
}
return -1
},
// 是否任务列表
isTodo(str) {
var v = str.trim()
if (TODO_REG.test(v)) {
return v[1] === 'x' ? 1 : 0
}
return -1
},
ltrim(str) {
if (str.trimStart) {
return str.trimStart()
}
return str.replace(/^\s+/, '')
} }
} }
const Tool = { class Tool {
constructor(list) {
this.list = list
}
// 初始化字符串, 处理多余换行等 // 初始化字符串, 处理多余换行等
init(str) { static init(str) {
// 去掉\r, 将\t转为空格(2个) // 去掉\r, 将\t转为空格(2个)
str = str.replace(/\r/g, '').replace(/\t/g, ' ') str = str.replace(/\r/g, '').replace(/\t/g, ' ')
var list = [] var list = []
@ -52,10 +84,9 @@ const Tool = {
} }
} }
} }
log(list)
this.list = list return new this(list)
return this }
},
parse() { parse() {
var html = '' var html = ''
@ -65,6 +96,10 @@ const Tool = {
var blockquoteLevel = 0 var blockquoteLevel = 0
var isParagraph = false var isParagraph = false
var isList = false
var orderListLevel = -1
var unorderListLevel = -1
// //
for (let it of this.list) { for (let it of this.list) {
// 空行 // 空行
@ -78,10 +113,9 @@ const Tool = {
// 引用结束 // 引用结束
if (isBlockquote) { if (isBlockquote) {
isBlockquote = false isBlockquote = false
html += ''
if (emptyLineLength > 0) { if (emptyLineLength > 0) {
while (blockquoteLevel > 0) {
emptyLineLength = 0 emptyLineLength = 0
while (blockquoteLevel > 0) {
blockquoteLevel-- blockquoteLevel--
html += '</blockquote>' html += '</blockquote>'
} }
@ -89,13 +123,25 @@ const Tool = {
continue continue
} }
if (isList) {
while (orderListLevel > -1 || unorderListLevel > -1) {
if (orderListLevel > unorderListLevel) {
html += '</ol>'
orderListLevel--
} else {
html += '</ul>'
unorderListLevel--
}
}
isList = false
continue
}
// //
if (isParagraph) { if (isParagraph) {
isParagraph = false isParagraph = false
html += '</p>' html += '</p>'
} /* else { }
html += '<br>'
} */
} }
} else { } else {
// wc-code标签直接拼接 // wc-code标签直接拼接
@ -109,35 +155,45 @@ const Tool = {
continue continue
} }
// 无属性标签
if (Helper.isHr(it)) {
html += '<fieldset class="md-hr"><legend></legend></fieldset>'
continue
}
// 优先处理一些常规样式
it = it it = it
.replace(/`(.*?)`/g, '<code class="inline">$1</code>') .replace(/`(.*?)`/g, '<code class="inline">$1</code>')
.replace(/(\-\-|\*\*)(.*?)\1/g, '<strong>$2</strong>') .replace(/(\-\-|\*\*)(.*?)\1/g, '<strong>$2</strong>')
.replace(/(\-|\*)(.*?)\1/g, '<em>$2</em>') .replace(/(\-|\_|\*)(.*?)\1/g, '<em>$2</em>')
.replace(/~~(.*?)~~/g, '<del>$1</del>')
.replace(/\!\[([^]*?)\]\(([^)]*?)\)/g, '<img src="$2" alt="$1">') .replace(/\!\[([^]*?)\]\(([^)]*?)\)/g, '<img src="$2" alt="$1">')
.replace(/\[([^]*?)\]\(([^)]*?)\)/g, '<a href="$2">$1</a>') .replace(/\[([^]*?)\]\(([^)]*?)\)/g, '<a href="$2">$1</a>')
// // 引用
if (it.startsWith('>')) { if (it.startsWith('>')) {
html += it.replace(/^(>+) /, m => {
let len = m.trim().length
let tmp = ''
if (isBlockquote) {
// 若之前已经有一个未闭合的引用, 需要减去已有缩进级别, 避免产生新的引用标签
len = len - blockquoteLevel
} else {
blockquoteLevel = len
}
log('bq: ', blockquoteLevel, it)
while (len > 0) {
len--
tmp += '<blockquote class="md-quote">'
}
return tmp
})
if (isBlockquote) { if (isBlockquote) {
html += '<br>' html += '<br>'
} }
html += 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
return tmp
})
isParagraph = false isParagraph = false
isBlockquote = true isBlockquote = true
continue continue
@ -148,17 +204,79 @@ const Tool = {
continue continue
} }
// // 标题只能是单行
if (it.startsWith('#')) { if (it.startsWith('#')) {
isParagraph = false isParagraph = false
let end = ''
html += it.replace(/^#{1,6} /, m => {
let level = m.trim().length
end = `</a></h${level}>`
return `<h${level}><a href="#">`
})
html += end
html += it.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}" class="md-head-link">${m2}</a></h${level}>`
}
})
continue
}
// 列表
let listChecked = Helper.isList(it)
if (~listChecked) {
// 左侧空格长度
let tmp = Helper.ltrim(it)
let ltrim = it.length - tmp.length
let word = tmp.replace(LIST_REG, '').trim()
let level = Math.floor(ltrim / 2)
let tag = listChecked > 0 ? 'ol' : 'ul'
if (!isList) {
html += `<${tag}>`
if (listChecked === 1) {
orderListLevel = level
} else {
unorderListLevel = level
}
html += `<li>${word}</li>`
} else {
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
}
}
isList = true
continue
}
// 任务
let todoChecked = Helper.isTodo(it)
if (~todoChecked) {
let word = it.replace(TODO_REG, '').trim()
let stat = todoChecked === 1 ? 'checked' : ''
let txt = todoChecked === 1 ? `<del>${word}</del>` : word
html += `<section><wc-checkbox readonly ${stat}>${txt}</wc-checkbox></section>`
continue continue
} }