diff --git a/package.json b/package.json index fc0704f..932d283 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bd/ui", - "version": "0.1.4", + "version": "0.1.5", "description": "", "files": [ "dist/*" diff --git a/src/code/colorful.js b/src/code/colorful.js new file mode 100644 index 0000000..e713d04 --- /dev/null +++ b/src/code/colorful.js @@ -0,0 +1,206 @@ +/** + * 简单的代码着色 (html, css, js) + * @author yutent + * @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\-.]+/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 +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 +const STR = /(['"`])(.*?)(?{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, '[num]$1[/num]') + .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, '[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, '>') + return `[cm]${str}[/cm]` + }) + .replace(CM2, (m, str) => { + str = str + .replace(/\[(\w+)\](.*?)\[\/\1\]/g, '$2') + .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 ? '' : '')) + .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 ? '' : '')) +} + +export function colorMd(code) { + code = code + .replace(INLINE.head, '[cm]$1[/cm][tag]$2[/tag]') + .replace(INLINE.br, '[cm]$1[/cm][tag]$2[/tag]') + .replace(INLINE.quote, '[cm]$1[/cm]$2') + .replace( + INLINE.task, + '[cm]$1 [[/cm][attr]$2[/attr][cm]][/cm] $3' + ) + .replace(INLINE.list, '[cm]$1[/cm]$3') + .replace(INLINE.code, '[cm]`[/cm][tag]$1[/tag][cm]`[/cm]') + .replace(INLINE.codeBlock, '[cm]```[/cm][tag]$1[/tag]') + .replace(INLINE.strong[0], '[cm]__[/cm]$1[cm]__[/cm]') + .replace(INLINE.strong[1], '[cm]**[/cm]$1[cm]**[/cm]') + .replace(INLINE.em[0], '[cm]_[/cm]$1[cm]_[/cm]') + .replace(INLINE.em[1], '[cm]*[/cm]$1[cm]*[/cm]') + .replace(INLINE.del, '[cm]~~[/cm]$1[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]') + 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) +} + +export function colorCss(code) { + code = code + .replace( + /:(hover|after|active|last\-child|first\-child)/g, + ':$1' + ) + .replace(/([\.#])([\w\-]+)/g, '$1$2') + .replace( + /([a-zA-Z\-]+):\s?([^;\n]+);?/g, + '$1: $2;' + ) + .replace(/([,\{\}])/g, '$1') + .replace(/&/g, '&') + return code +} + +export function colorJs(code) { + return rebuild(parseJs(code)) +} diff --git a/src/code/index.js b/src/code/index.js index c091b69..24de9f1 100644 --- a/src/code/index.js +++ b/src/code/index.js @@ -3,11 +3,29 @@ * @author yutent * @date 2023/03/20 18:02:01 */ -import { html, css, Component, nextTick } from '@bd/core' +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 }, + code: { + type: String, + default: '', + attribute: false, + observer(v) { + this.setCode(v.trim()) + } + }, lang: '' } @@ -60,6 +78,17 @@ class Code extends Component { background: var(--color-green-1); } } + + .act { + --size: 18px; + margin: 0 6px; + color: var(--color-grey-2); + cursor: pointer; + + &:hover { + color: var(--color-grey-3); + } + } } } `, @@ -95,21 +124,94 @@ class Code extends Component { content: counter(code); counter-increment: code; } + + i { + font-style: normal; + } + + .r { + color: var(--color-red-2); + } + .b { + color: var(--color-blue-2); + } + .g { + color: var(--color-green-2); + } + .gr { + color: var(--color-grey-2); + } + .o { + color: var(--color-orange-1); + } + .pp { + color: #a46ad3; + } + .link { + font-style: italic; + text-decoration: underline; + color: var(--color-grey-2); + } } } ` ] + #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) { - this.textContent = '' nextTick(_ => { - this.code = txt.replace(/</g, '<').replace(/>/g, '>') + this.code = txt }) } } @@ -120,9 +222,15 @@ class Code extends Component {
${this.lang}
+
- ${this.code.split('\n').map(s => html`${s}`)} + ${this.#code.map(s => html`${raw(s)}`)}
` diff --git a/src/sandbox/index.js b/src/sandbox/index.js index 06704a8..f34c934 100644 --- a/src/sandbox/index.js +++ b/src/sandbox/index.js @@ -4,17 +4,18 @@ * @date 2023/03/06 15:17:25 */ -import { - css, - html, - bind, - Component, - nextTick, - styleMap, - classMap -} from '@bd/core' +import { css, html, raw, bind, Component, nextTick, classMap } from '@bd/core' import '../icon/index.js' import { gzip } from '@bytedo/gzip' +import { colorHtml, colorJs, colorCss } from '../code/colorful.js' + +function trim(str) { + return str + .trim() + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') +} class Sandbox extends Component { static props = { @@ -80,6 +81,7 @@ class Sandbox extends Component { } iframe { + display: none; width: 100%; border: 0; background: #fff; @@ -114,9 +116,8 @@ class Sandbox extends Component { return } - this.#cache[this.tab].panel - .$animate(true) - .then(_ => this.#cache[key].panel.$animate()) + this.#cache[this.tab].panel.style.display = '' + this.#cache[key].panel.style.display = 'block' this.tab = key } else { @@ -197,6 +198,7 @@ class Sandbox extends Component { mounted() { // this.#cache.preview.panel = this.$refs.preview + this.$refs.preview.style.display = 'block' } render() { @@ -226,7 +228,7 @@ class Sandbox extends Component { >
- + @@ -236,21 +238,23 @@ class Sandbox extends Component { } class Lang extends Component { - static animation = {} - static props = { - code: { type: String, default: '', attribute: false } + code: { + type: String, + default: '', + attribute: false, + observer(v) { + this.setCode(v.trim()) + } + } } static styles = [ css` :host { - display: flex; - position: relative; + display: none; width: 100%; - margin: 10px 0; - border-radius: 3px; - background: #f7f8fb; + background: #fff; color: var(--color-dark-1); } `, @@ -287,21 +291,79 @@ class Lang extends Component { content: counter(code); counter-increment: code; } + + i { + font-style: normal; + } + + .r { + color: var(--color-red-2); + } + .b { + color: var(--color-blue-2); + } + .g { + color: var(--color-green-2); + } + .gr { + color: var(--color-grey-2); + } + .o { + color: var(--color-orange-1); + } + .pp { + color: #a46ad3; + } + .link { + font-style: italic; + text-decoration: underline; + color: var(--color-grey-2); + } } } ` ] + #code = [] + + setCode(txt, a) { + let lang = this.getAttribute('slot') + + switch (lang) { + case 'javascript': + txt = colorJs(txt) + break + + case 'html': + txt = colorHtml(txt) + break + + case 'css': + txt = colorCss(txt) + break + } + + this.#code = txt.split('\n') + } + 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) { - this.textContent = '' nextTick(_ => { - this.code = txt.replace(/</g, '<').replace(/>/g, '>') + this.code = txt }) } } @@ -309,7 +371,7 @@ class Lang extends Component { render() { return html`
- ${this.code.split('\n').map(s => html`${s}`)} + ${this.#code.map(s => html`${raw(s)}`)}
` }