diff --git a/src/editor/index.js b/src/editor/index.js index d96f25f..799fd52 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -4,7 +4,19 @@ * @date 2023/09/14 16:49:15 */ -import { css, raw, html, Component, nextTick, styleMap } from 'wkit' +import { + css, + raw, + html, + Component, + bind, + unbind, + nextTick, + styleMap, + classMap, + outsideClick, + clearOutsideClick +} from 'wkit' import ICONS from './svg.js' import '../form/input.js' import '../form/button.js' @@ -34,11 +46,13 @@ const DEFAULT_TOOLS = [ 'delete', 'ordered', 'unordered', + 'table', 'left', 'center', 'right', 'link', - 'image' + 'image', + 'fullscreen' ] const COLORS = [ @@ -56,7 +70,18 @@ const COLORS = [ '#000000' ] +// 获取一维数组转二维的行 +function getY(i) { + return (i / 9) >> 0 +} +//获取一维数组转二维的列 +function getX(i) { + return i % 9 +} + class Editor extends Component { + static watches = ['value'] + static props = { toolbar: { type: String, @@ -70,9 +95,18 @@ class Editor extends Component { } } }, - value: 'str!', - readonly: false, - disabled: false + readonly: { + type: Boolean, + observer(v) { + this.#updateStat() + } + }, + disabled: { + type: Boolean, + observer(v) { + this.#updateStat() + } + } } static styles = [ @@ -84,6 +118,7 @@ class Editor extends Component { max-height: 720px; border-radius: 3px; transition: box-shadow 0.15s linear; + background: var(--wc-editor-background, #fff); } table { width: 100%; @@ -132,7 +167,7 @@ class Editor extends Component { display: flex; flex-direction: column; width: 100%; - border: 1px solid #e7e8eb; + border: 1px solid var(--wc-editor-border-color, var(--color-grey-2)); border-radius: inherit; font-size: 14px; } @@ -141,7 +176,7 @@ class Editor extends Component { height: 34px; padding: 5px; line-height: 24px; - border-bottom: 1px solid #e7e8eb; + border-bottom: 1px solid var(--color-grey-1); span { position: relative; @@ -189,10 +224,13 @@ class Editor extends Component { &::-webkit-scrollbar { display: none; } + + .wrapper { + min-height: 100%; + } } .is-vertical { - // visibility: hidden; position: absolute; right: 0; top: 0; @@ -200,17 +238,15 @@ class Editor extends Component { justify-content: flex-end; width: 10px; height: 100%; - // opacity: 0; - transition: opacity 0.3s linear, visibility 0.3s linear; .thumb { display: block; width: 6px; - height: 90px; + height: 0; border-radius: 5px; background: rgba(44, 47, 53, 0.25); cursor: default; - transition: width 0.1s linear, height 0.1s linear; + transition: width 0.1s linear; &:hover { width: 10px; @@ -222,6 +258,7 @@ class Editor extends Component { .typearea { width: 100%; height: 100%; + min-height: 54px; outline: none; text-wrap: wrap; word-break: break-all; @@ -233,10 +270,10 @@ class Editor extends Component { `, css` - :host(:hover) { - .is-vertical { - visibility: visible; - opacity: 1; + :host([readonly]) { + .editor { + cursor: default; + opacity: 0.8; } } :host([disabled]) { @@ -245,13 +282,6 @@ class Editor extends Component { opacity: 0.6; } } - - :host([readonly]) { - .editor { - cursor: default; - opacity: 0.8; - } - } :host([readonly]), :host([disabled]) { .toolbar { @@ -264,6 +294,17 @@ class Editor extends Component { :host(:focus-within) { box-shadow: 0 0 0 2px var(--color-plain-a); } + + :host(.fullscreen) { + position: fixed; + top: 0; + left: 0; + z-index: 9; + width: 100vw; + height: 100vh; + max-height: 100vh; + border-radius: 0; + } `, css` @@ -271,7 +312,8 @@ class Editor extends Component { .font-layer, .color-layer, - .link-layer { + .link-layer, + .table-layer { visibility: hidden; position: absolute; left: 0; @@ -326,6 +368,27 @@ class Editor extends Component { } } + .table-layer { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + left: 240px; + width: 200px; + height: 200px; + padding: 2px; + background: #fff; + + span { + width: 20px; + height: 20px; + background: var(--color-plain-1); + + &.active { + background: rgba(77, 182, 172, 0.3); + } + } + } + .link-layer { display: flex; flex-direction: column; @@ -342,6 +405,384 @@ class Editor extends Component { ] #toolbar = [] + #value = '' + #cache = { bar: 0, y: 0 } + #gridx = 0 + #gridy = 0 + + #select = null + + get value() { + let html = this.$refs.editor?.innerHTML || '' + + html = html.replace(/<\!\-\-(.*?)\-\->/g, '') + if (~html.indexOf('') && !html.startsWith('' + + html + } + return html + } + + set value(val) { + if (this.$refs.editor) { + this.#value = val + this.$refs.editor.innerHTML = val + } else { + nextTick(_ => (this.value = val)) + } + } + + __init__() { + // + let { outer, inner, thumb } = this.$refs + let height = outer.offsetHeight + let scrollHeight = inner.scrollHeight + 10 + + let bar = 50 // 滚动条的高度 + bar = (height * (height / scrollHeight)) >> 0 + + if (bar < 50) { + bar = 50 + } + + // 100%或主体高度比滚动条还短时不显示 + if (bar >= height) { + bar = 0 + } + + this.#cache.bar = bar + thumb.style.height = bar + 'px' + } + + #updateStat() { + if (this.$refs.editor) { + if (this.readOnly || this.disabled) { + this.$refs.editor.removeAttribute('contenteditable') + } else { + this.$refs.editor.setAttribute('contenteditable', '') + } + } else { + nextTick(_ => this.#updateStat()) + } + } + + #fetchScroll(moveY) { + let { bar } = this.#cache + let { outer, thumb, inner } = this.$refs + let height = outer.offsetHeight + let scrollHeight = inner.scrollHeight + 10 + + if (moveY < 0) { + moveY = 0 + } else if (moveY > height - bar) { + moveY = height - bar + } + + inner.scrollTop = (scrollHeight - height) * (moveY / (height - bar)) + thumb.style.transform = `translateY(${moveY}px)` + return moveY + } + + #hideLayers() { + this.$refs.font.classList.remove('fadein') + this.$refs.color.classList.remove('fadein') + this.$refs.link.classList.remove('fadein') + this.$refs.table.classList.remove('fadein') + } + + // 处理图片 + #handleImage(ev, file) { + if (ev && ev.type === 'change') { + file = ev.target.files[0] + ev.target.value = '' + } + this.$emit('upload', { + detail: { + file, + send: link => { + this.$refs.editor.focus() + this.restoreSelection() + this.exec(ACTTION.image, link) + this.saveSelection() + } + } + }) + } + + #handlePaste(ev) { + let html = ev.clipboardData.getData('text/html') + let txt = ev.clipboardData.getData('text/plain') + let items = ev.clipboardData.items + + // 先文件判断, 避免右键单击复制图片时, 当成html处理 + if (items && items.length) { + let blob = null + for (let it of items) { + if (it.type.indexOf('image') > -1) { + blob = it.getAsFile() + } + } + + if (blob) { + return this.#handleImage(null, blob) + } + } + + if (html) { + html = html + .replace(/\t/g, ' ') + .replace(/<\/?(meta|link|script)[^>]*?>/g, '') + .replace(//g, '') + .replace( + /]*? href\s?=\s?["']?([^"']*)["']?[^>]*?>/g, + '' + ) + .replace( + /]*? src\s?=\s?["']?([^"']*)["']?[^>]*?>/g, + '' + ) + .replace(/<(?!a|img)([\w\-]+)[^>]*>/g, '<$1>') + .replace(/]*?>[\w\W]*?<\/xml>/g, '') + .replace(/