261 lines
9.2 KiB
JavaScript
261 lines
9.2 KiB
JavaScript
/**
|
|
* 简单的代码着色 (html, css, js)
|
|
* @author yutent<yutent.io@gmail.com>
|
|
* @date 2021/01/30 14:00:12
|
|
*/
|
|
|
|
const DOCTYPE_EXP = /<\!DOCTYPE html>/
|
|
const TAG_START_EXP = /<([\w\-]+)([\w\W]*?)>/g
|
|
const TAG_END_EXP = /<\/([\w\-]+)>/g
|
|
const TAG_ATTR_EXP =
|
|
/[@a-zA-Z\-.:#]+=(["'])[^"]+\1|[@a-zA-Z\-.:#]+=[a-zA-Z0-9]+|[@a-zA-Z\-.:#]+=\$\{.+\}|[@a-zA-Z\-.]+/g
|
|
const TAG_CM_EXP = /<!--([\w\W]*?)-->/g
|
|
const SCRIPT_TAG = /(<script[^>]*?>)([\w\W]*?)(<\/script>)/g
|
|
const IMPORT_1 = /import (['"])([^'"]*?)\1/g
|
|
const IMPORT_2 = /import ([\w\W]*?) from (['"])([^'"]*?)\2/g
|
|
const IMPORT_3 = /import\((['"])([^'"]*?)\1\)/g
|
|
const KEYWOWRD1 =
|
|
/\b(var|const|let|function|for|switch|with|if|else|export|import|assert|as|from|async|await|break|continue|return|class|extends|try|catch|throw|new|while|this|super|default|case|debugger|delete|do|goto|in|static|get|set|public|private|protected|package|typeof|void)\b/g
|
|
const KEYWOWRD2 = /\s(=|-|\+|\/|\*|<|>|%)\s/g
|
|
const KEYWOWRD3 =
|
|
/(\+\=|-=|\/=|\*=|--|\+\+|===|==|=>|\.\.\.|\.|\?\.|\?\?|&&|\|\|)/g
|
|
const BUILDIN1 = /\b(null|undefined|true|false|NaN|Infinity)\b/g
|
|
const BUILDIN2 =
|
|
/\b(Object|String|Array|Boolean|Number|Function|Promise|Map|Set|WeakMap|WeakSet|URL)\b/g
|
|
const STR = /(['"`])(.*?)(?<!\\)\1/g
|
|
const NUM = /\b(\d+)\b/g
|
|
const FN = /([\.\s])((?!if|for)[a-zA-Z$_#][\da-zA-Z_]*)\s?(\(.*?\)?)/g
|
|
const CM1 = /(?=\s)?([ ]*\/\/.*)|(^\/\/.*)/g
|
|
const CM2 = /\/\*([\w\W]*?)\*\//g
|
|
const EXP = /([=\(][ ]*)\/(.+?)\/([gmi]*)/g
|
|
const INLINE = {
|
|
code: /`([^`]*?[^`\\\s])`/g,
|
|
codeBlock: /^```(.*?)$/gm,
|
|
strong: [/__([\s\S]*?[^\s\\])__(?!_)/g, /\*\*([\s\S]*?[^\s\\])\*\*(?!\*)/g],
|
|
em: [/_([\s\S]*?[^\s\\_])_(?!_)/g, /\*([\s\S]*?[^\s\\*])\*(?!\*)/g],
|
|
del: /~~([\s\S]*?[^\s\\~])~~/g,
|
|
qlinkVar: /^\[(\d+)\]: ([\S]+)\s*?((['"])[\s\S]*?\4)?\s*?$/gm, // 引用声明
|
|
qlink: /\[([^\]]*?)\]\[(\d*?)\]/g, // 引用链接
|
|
img: /\!\[([^\]]*?)\]\(([^)]*?)\)/g,
|
|
a: /\[([^\]]*?)\]\(([^)]*?)(\s+"([\s\S]*?)")*?\)/g,
|
|
head: /^(#{1,6} )(.*)$/gm,
|
|
quote: /^(>{1,} )(.*)$/gm,
|
|
task: /^([\-\+\*]) \[( |x)\] (.*)$/gm,
|
|
list: /^([ \t]*?([\-\+\*]|\d+\.) )(.*)$/gm,
|
|
br: /^([\-\*_=]{3})(.*?)$/gm
|
|
}
|
|
|
|
function parseJs(code) {
|
|
return code
|
|
.replace(IMPORT_1, (m, pun, dep) => {
|
|
return `import ${pun}[link]${dep}[/link]${pun}`
|
|
})
|
|
.replace(IMPORT_2, (m, alias, pun, dep) => {
|
|
alias = alias.replace(/(\{|\}|,)/g, '[cm]$1[/cm]')
|
|
return `import ${alias} from ${pun}[link]${dep}[/link]${pun}`
|
|
})
|
|
.replace(IMPORT_3, (m, pun, dep) => {
|
|
return `import(${pun}[link]${dep}[/link]${pun})`
|
|
})
|
|
.replace(EXP, (m, pun, str, flag) => {
|
|
str = str.replace(/&/g, '&').replace(/(\\.)/g, '[fn]$1[/fn]')
|
|
|
|
return `${pun}[type]/[/type][cm]${str}[/cm][type]/[/type]${
|
|
flag ? `[num]${flag}[/num]` : ''
|
|
}`
|
|
})
|
|
.replace(FN, '$1[fn]$2[/fn]$3')
|
|
.replace(KEYWOWRD1, '[key]$1[/key]')
|
|
.replace(KEYWOWRD2, ' [key]$1[/key] ')
|
|
.replace(KEYWOWRD3, '[key]$1[/key]')
|
|
.replace(BUILDIN1, '[buildin]$1[/buildin]')
|
|
.replace(BUILDIN2, '[type]$1[/type]')
|
|
.replace(NUM, '[num]$1[/num]')
|
|
.replace(STR, (m, pun, str) => {
|
|
if (str.startsWith('[link]') && str.endsWith('[/link]')) {
|
|
str = str.slice(6, -7).replace(/\[(\w+)\](.*?)\[\/\1\]/g, '$2')
|
|
return `[cm]${pun}[/cm][link]${str}[/link][cm]${pun}[/cm]`
|
|
}
|
|
str = str
|
|
.replace(/\[(\w+)\](.*?)\[\/\1\]/g, '$2')
|
|
.replace(/<(\/?)xmp>/g, '<$1xmp>')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/(\\.)/g, '[fn]$1[/fn]')
|
|
|
|
return `[cm]${pun}[/cm][str]${str}[/str][cm]${pun}[/cm]`
|
|
})
|
|
.replace(CM1, (m, str) => {
|
|
if (str.includes('[/link]') || str.includes('[link]')) {
|
|
return str
|
|
}
|
|
str = str
|
|
.replace(/\[(\w+)\](.*?)\[\/\1\]/g, '$2')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
return `[cm]${str}[/cm]`
|
|
})
|
|
.replace(CM2, (m, str) => {
|
|
str = str
|
|
.replace(/\[(\w+)\](.*?)\[\/\1\]/g, '$2')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
|
|
str = `/*${str}*/`.split('\n').map(s => `[cm]${s}[/cm]`)
|
|
return str.join('\n')
|
|
})
|
|
}
|
|
|
|
function rebuild(code) {
|
|
// console.log(code)
|
|
return code
|
|
.replace(/\[(\/?)tag\]/g, (m, s) => (s ? '</c>' : '<c class="t-tag">'))
|
|
.replace(/\[(\/?)attr\]/g, (m, s) => (s ? '</c>' : '<c class="t-attr">'))
|
|
.replace(/\[(\/?)str\]/g, (m, s) => (s ? '</c>' : '<c class="t-str">'))
|
|
.replace(/\[(\/?)key\]/g, (m, s) => (s ? '</c>' : '<c class="t-keyword">'))
|
|
.replace(/\[(\/?)num\]/g, (m, s) => (s ? '</c>' : '<c class="t-num">'))
|
|
.replace(/\[(\/?)fn\]/g, (m, s) => (s ? '</c>' : '<c class="t-fn">'))
|
|
.replace(/\[(\/?)cm\]/g, (m, s) => (s ? '</c>' : '<c class="t-comment">'))
|
|
.replace(/\[(\/?)buildin\]/g, (m, s) =>
|
|
s ? '</c>' : '<c class="t-buildin">'
|
|
)
|
|
.replace(/\[(\/?)type\]/g, (m, s) => (s ? '</c>' : '<c class="t-type">'))
|
|
.replace(/\[(\/?)link\]/g, (m, s) => (s ? '</c>' : '<c class="t-link">'))
|
|
}
|
|
|
|
export function colorMd(code) {
|
|
code = code
|
|
.replace(INLINE.head, '[cm]$1[/cm][tag]<strong>$2</strong>[/tag]')
|
|
.replace(INLINE.br, '[cm]$1[/cm][tag]$2[/tag]')
|
|
.replace(INLINE.quote, '[cm]$1[/cm]<em>$2</em>')
|
|
.replace(
|
|
INLINE.task,
|
|
'[cm]$1 [[/cm][attr]$2[/attr][cm]][/cm] <strong>$3</strong>'
|
|
)
|
|
.replace(INLINE.list, '[cm]$1[/cm]<strong>$3</strong>')
|
|
.replace(INLINE.code, '[cm]`[/cm][tag]$1[/tag][cm]`[/cm]')
|
|
.replace(INLINE.codeBlock, '[cm]```[/cm][tag]$1[/tag]')
|
|
.replace(INLINE.strong[0], '[cm]__[/cm]<strong>$1</strong>[cm]__[/cm]')
|
|
.replace(INLINE.strong[1], '[cm]**[/cm]<strong>$1</strong>[cm]**[/cm]')
|
|
.replace(INLINE.em[0], '[cm]_[/cm]<em>$1</em>[cm]_[/cm]')
|
|
.replace(INLINE.em[1], '[cm]*[/cm]<em>$1</em>[cm]*[/cm]')
|
|
.replace(INLINE.del, '[cm]~~[/cm]<del>$1</del>[cm]~~[/cm]')
|
|
.replace(
|
|
INLINE.qlinkVar,
|
|
'[[attr]$1[/attr]]: [link]$2[/link] [tag]$3[/tag]'
|
|
)
|
|
.replace(INLINE.qlink, '[[attr]$1[/attr]][[link]$2[/link]]')
|
|
.replace(INLINE.img, '![[attr]$1[/attr]]([link]$2[/link])')
|
|
.replace(INLINE.a, (m1, txt, link, m2, attr = '') => {
|
|
if (attr) {
|
|
attr = ` "[tag]${attr}[/tag]"`
|
|
}
|
|
return `[[attr]${txt}[/attr]]([link]${link}[/link]${attr})`
|
|
})
|
|
return rebuild(code)
|
|
}
|
|
|
|
export function colorHtml(code) {
|
|
code = code
|
|
.replace(DOCTYPE_EXP, '[tag]<!DOCTYPE [attr]html[/attr]>[/tag]')
|
|
.replace(SCRIPT_TAG, (m, tag1, txt, tag2) => {
|
|
return tag1 + parseJs(txt) + tag2
|
|
})
|
|
.replace(TAG_START_EXP, (m, tag, attr) => {
|
|
if (attr) {
|
|
attr = attr.replace(TAG_ATTR_EXP, function (t) {
|
|
if (~t.indexOf('=')) {
|
|
t = t.split('=')
|
|
let a = t.shift()
|
|
let b = t.join('=').replace(/(\n+)/g, '[/str]\n[str]')
|
|
|
|
if (b.startsWith('${') && b.endsWith('}')) {
|
|
return `[attr]${a}[/attr]=${b}`
|
|
}
|
|
return `[attr]${a}[/attr]=[str]${b}[/str]`
|
|
} else {
|
|
return `[attr]${t}[/attr]`
|
|
}
|
|
})
|
|
}
|
|
|
|
return `[tag]<${tag}[/tag]${attr}[tag]>[/tag]`
|
|
})
|
|
.replace(TAG_END_EXP, (m, tag) => {
|
|
return `[tag]</${tag}>[/tag]`
|
|
})
|
|
.replace(TAG_CM_EXP, '[cm]<!--$1-->[/cm]')
|
|
return rebuild(code)
|
|
}
|
|
|
|
// css 不走rebuild, 因为css有 [xxx] 这种选择器语法
|
|
export function colorCss(code) {
|
|
code = code
|
|
.replace(STR, '<cc class="t-str">$1$2$1</cc>')
|
|
.replace(/(\$[a-zA-Z\d_\-]+)/g, '<c class="t-var">$1</c>')
|
|
.replace(/(\-\-[a-z\d]+\-[a-z\d]+)/g, '<c class="t-const">$1</c>')
|
|
.replace(
|
|
/:(hover|after|active|last\-child|first\-child|nth\-child)/g,
|
|
'<c class="t-buildin">:$1</c>'
|
|
)
|
|
.replace(
|
|
/^((?:[ ])*)([\.#])([\w\-]+)/gm,
|
|
'$1<c class="t-comment">$2</c><c class="t-keyword">$3</c>'
|
|
)
|
|
.replace(
|
|
/:(root|host)/g,
|
|
'<c class="t-comment">:</c><c class="t-keyword">$1</c>'
|
|
)
|
|
.replace(/([a-zA-Z\-]+):(\s?[^\n]+)/g, (m, k, v) => {
|
|
if (v.trim() !== '(' && !v.endsWith('</cc>;')) {
|
|
v = v.replace(
|
|
/([^,;]*)([,;]?$)/,
|
|
'<c class="t-num">$1</c><c class="t-comment">$2</c>'
|
|
)
|
|
}
|
|
return `<c class="t-comment"><b>${k}</b>:</c>${v}`
|
|
})
|
|
.replace(/([,\{\}])/g, '<c class="t-comment">$1</c>')
|
|
.replace(/\b(in|from|to|throught)\b/g, '<c class="t-keyword">$1</c>')
|
|
.replace(/(&|@use|@each|@for)/g, '<c class="t-keyword">$1</c>')
|
|
.replace(/<cc class="t-str">(.*?)<\/cc>/g, (m, str) => {
|
|
str = str.replace(/<\/?\w+[^>]*?>/g, '')
|
|
return `<c class="t-str">${str}</c>`
|
|
})
|
|
|
|
return code
|
|
}
|
|
|
|
export function colorJs(code) {
|
|
let cache = {}
|
|
let num = 160 // to hex is a0
|
|
|
|
let restore = str => {
|
|
for (let k in cache) {
|
|
if (str.includes(k)) {
|
|
str = str.replace(k, cache[k])
|
|
delete cache[k]
|
|
}
|
|
}
|
|
return str
|
|
}
|
|
// 匹配单行的
|
|
code = code.replace(/html`(.*)`/g, (m, v) => {
|
|
let _key = '' + (num++).toString(16)
|
|
cache[_key] = `html\`${colorHtml(v)}\``
|
|
return _key
|
|
})
|
|
// 匹配多行的
|
|
code = code.replace(/html`([\w\W]*?)`/g, (m, v) => {
|
|
let _key = '' + (num++).toString(16)
|
|
cache[_key] = restore(`html\`${colorHtml(v)}\``)
|
|
|
|
return _key
|
|
})
|
|
|
|
return restore(rebuild(parseJs(code)))
|
|
}
|
JavaScript
98.9%
CSS
1.1%