markdown解析器完成引用,列表,任务,分割线的解析
parent
6b17ad76ba
commit
36a7f0fbe9
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in New Issue