From 93afc6adbf2e7fe2866602018f7e7186c3d3b1f2 Mon Sep 17 00:00:00 2001 From: yutent Date: Mon, 24 Apr 2023 17:34:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=9D=97,=20=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=AB=98=E4=BA=AE,markdown=E8=A7=A3=E6=9E=90=E5=99=A8?= =?UTF-8?q?=E5=A4=A7=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/code/colorful.js | 100 ++++++++++++++++++++++++-------- src/code/index.js | 35 +++++++----- src/markd/core.js | 5 +- src/markd/index.js | 1 + src/sandbox/index.js | 35 +++++++----- test.md | 133 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 256 insertions(+), 53 deletions(-) create mode 100644 test.md diff --git a/src/code/colorful.js b/src/code/colorful.js index e713d04..29de7ff 100644 --- a/src/code/colorful.js +++ b/src/code/colorful.js @@ -8,23 +8,23 @@ 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\-.]+/g + /[@a-zA-Z\-.:#]+=(["'])[^"]+\1|[@a-zA-Z\-.:#]+=[a-zA-Z0-9]+|[@a-zA-Z\-.:#]+=\$\{.+\}|[@a-zA-Z\-.]+/g const TAG_CM_EXP = //g const SCRIPT_TAG = /(]*?>)([\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|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 + /\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|class|Promise|Map|Set|WeakMap|WeakSet|URL)\b/g + /\b(Object|String|Array|Boolean|Number|Function|Promise|Map|Set|WeakMap|WeakSet|URL)\b/g const STR = /(['"`])(.*?)(?$1[/key]') + .replace(KEYWOWRD1, '[key]$1[/key]') .replace(KEYWOWRD2, ' [key]$1[/key] ') .replace(KEYWOWRD3, '[key]$1[/key]') - .replace(BUILDIN1, '[num]$1[/num]') - .replace(BUILDIN2, '[type]$1[/type]') + .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]')) { @@ -112,16 +112,18 @@ function parseJs(code) { function rebuild(code) { // console.log(code) return code - .replace(/\[(\/?)tag\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)attr\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)str\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)key\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)str\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)num\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)fn\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)cm\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)type\]/g, (m, s) => (s ? '' : '')) - .replace(/\[(\/?)link\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)tag\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)attr\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)str\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)key\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)num\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)fn\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)cm\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)buildin\]/g, (m, s) => + s ? '' : '' + ) + .replace(/\[(\/?)type\]/g, (m, s) => (s ? '' : '')) + .replace(/\[(\/?)link\]/g, (m, s) => (s ? '' : '')) } export function colorMd(code) { @@ -169,6 +171,10 @@ export function colorHtml(code) { 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]` @@ -185,22 +191,66 @@ export function colorHtml(code) { return rebuild(code) } +// css 不走rebuild, 因为css有 [xxx] 这种选择器语法 export function colorCss(code) { code = code + .replace(STR, '$1$2$1') + .replace(/(\$[a-zA-Z\d_]+)/g, '$1') + .replace(/(\-\-[a-z\d]+\-[a-z\d]+)/g, '$1') .replace( - /:(hover|after|active|last\-child|first\-child)/g, - ':$1' + /:(hover|after|active|last\-child|first\-child|nth\-child)/g, + ':$1' ) - .replace(/([\.#])([\w\-]+)/g, '$1$2') .replace( - /([a-zA-Z\-]+):\s?([^;\n]+);?/g, - '$1: $2;' + /(?=\s)?([ ][\.#])([\w\-]+)/g, + '$1$2' ) - .replace(/([,\{\}])/g, '$1') - .replace(/&/g, '&') + .replace(/([a-zA-Z\-]+):(\s?[^\n]+)/g, (m, k, v) => { + if (v.trim() !== '(' && !v.endsWith(';')) { + v = v.replace( + /([^,;]*)([,;]?$)/, + '$1$2' + ) + } + return `${k}:${v}` + }) + .replace(/([,\{\}])/g, '$1') + .replace(/\b(in|from|to|throught)\b/g, '$1') + .replace(/(&|@use|@each|@for)/g, '$1') + .replace(/(.*?)<\/cc>/g, (m, str) => { + str = str.replace(/<\/?\w+[^>]*?>/g, '') + return `${str}` + }) + return code } export function colorJs(code) { - return rebuild(parseJs(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))) } diff --git a/src/code/index.js b/src/code/index.js index 24de9f1..ee61075 100644 --- a/src/code/index.js +++ b/src/code/index.js @@ -104,7 +104,7 @@ class Code extends Component { cursor: text; counter-reset: code; - code { + > code { display: block; position: relative; min-height: 20px; @@ -125,33 +125,42 @@ class Code extends Component { counter-increment: code; } - i { - font-style: normal; - } - - .r { + .t-tag, + .t-keyword, + .t-const { color: var(--color-red-2); } - .b { + .t-attr, + .t-fn, + .t-var { color: var(--color-blue-2); } - .g { + .t-str { color: var(--color-green-2); } - .gr { + .t-comment { color: var(--color-grey-2); } - .o { + + .t-type { + font-weight: bold; color: var(--color-orange-1); } - .pp { + .t-num, + .t-buildin { color: #a46ad3; } - .link { - font-style: italic; + .t-link { text-decoration: underline; color: var(--color-grey-2); } + .t-buildin, + .t-keyword, + .t-type, + .t-link, + .t-comment { + font-style: italic; + } } } ` diff --git a/src/markd/core.js b/src/markd/core.js index 2ed4d70..e7aead5 100644 --- a/src/markd/core.js +++ b/src/markd/core.js @@ -205,8 +205,8 @@ class Tool { var qlink if (isCodeBlock) { it = it - .replace(/<(\/?)([a-z][a-z\d\-]*?)([^>]*?)>/g, '<$1$2$3>') .replace('\\`\\`\\`', '```') + .replace(/<(\/?)xmp>/g, '<$1xmp>') // 转义这个特殊标签 } else { if (Helper.isTable(tmp) && !isTable) { var thead = tmp.split('|') @@ -261,6 +261,7 @@ class Tool { list.push(tmp) } } + return new this(list, links) } @@ -595,5 +596,5 @@ class Tool { } export default function (str) { - return Tool.init(str).parse() + return Tool.init(str).parse() //.replace(/\\/g, '>') } diff --git a/src/markd/index.js b/src/markd/index.js index da6a806..e16c7e3 100644 --- a/src/markd/index.js +++ b/src/markd/index.js @@ -193,6 +193,7 @@ class Markd extends Component { ` render() { + // console.log(md2html(this.code)) return html`
${raw(md2html(this.code))}
` } } diff --git a/src/sandbox/index.js b/src/sandbox/index.js index dcd5300..4753b98 100644 --- a/src/sandbox/index.js +++ b/src/sandbox/index.js @@ -277,7 +277,7 @@ class Lang extends Component { cursor: text; counter-reset: code; - code { + > code { display: block; position: relative; min-height: 20px; @@ -298,33 +298,42 @@ class Lang extends Component { counter-increment: code; } - i { - font-style: normal; - } - - .r { + .t-tag, + .t-keyword, + .t-const { color: var(--color-red-2); } - .b { + .t-attr, + .t-fn, + .t-var { color: var(--color-blue-2); } - .g { + .t-str { color: var(--color-green-2); } - .gr { + .t-comment { color: var(--color-grey-2); } - .o { + + .t-type { + font-weight: bold; color: var(--color-orange-1); } - .pp { + .t-num, + .t-buildin { color: #a46ad3; } - .link { - font-style: italic; + .t-link { text-decoration: underline; color: var(--color-grey-2); } + .t-buildin, + .t-keyword, + .t-type, + .t-link, + .t-comment { + font-style: italic; + } } } ` diff --git a/test.md b/test.md new file mode 100644 index 0000000..0dc3efa --- /dev/null +++ b/test.md @@ -0,0 +1,133 @@ +```html +
+
+
+
${this.lang}
+ +
+
+ ${this.#code.map(s => html`${raw(s)}`)} +
+
+``` + + +```js +import { html, raw, css, Component, nextTick } from '@bd/core' +import { colorHtml, colorJs, colorCss, colorMd } from './colorful.js' +import '../icon/index.js' +import '../layer/index.js' + +function trim(str) { + return str + .trim() + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') +} + +class Code extends Component { + static props = { + code: { + type: String, + default: '', + attribute: false, + observer(v) { + this.setCode(v.trim()) + } + }, + lang: '' + } + + + + #code = [] + + setCode(txt, a) { + let lang = this.lang + + switch (lang) { + case 'js': + case 'javascript': + case 'ts': + case 'typescript': + txt = colorJs(txt) + break + + case 'html': + txt = colorHtml(txt) + break + + case 'css': + case 'scss': + case 'less': + txt = colorCss(txt) + break + + case 'md': + case 'markdown': + txt = colorMd(txt) + break + } + + this.#code = txt.split('\n') + } + + copyCode() { + navigator.clipboard.writeText(this.code) + layer.toast('复制到粘贴板成功', 'success') + } + + mounted() { + var txt = this.innerHTML || this.textContent + txt = txt.trim().replace(/^[\r\n]|\s{2,}$/g, '') + + if (txt.startsWith('') && txt.endsWith('')) { + txt = txt.slice(5, -6).trim() + } else if (this.firstElementChild?.tagName === 'TEXTAREA') { + txt = this.firstElementChild.value.trim() + } else if (txt.startsWith('
') && txt.endsWith('
')) { + txt = trim(txt.slice(5, -6)) + } else { + txt = trim(txt) + } + + this.textContent = '' + if (txt) { + nextTick(_ => { + this.code = txt + }) + } + } + + render() { + let foo = html`
${this.#code.map(s => html`${s}`)}
` + + return html` +
+
+
+
${this.lang}
+ +
+
+ ${this.#code.map(s => html`${raw(s)}`)} +
+
+ ` + } +} + +Code.reg('code') + +``` \ No newline at end of file