diff --git a/src/editor/index.js b/src/editor/index.js index d94f863..1ef8c9e 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -6,14 +6,11 @@ import { css, - raw, html, Component, bind, - unbind, + range, nextTick, - styleMap, - classMap, outsideClick, clearOutsideClick } from 'wkit' @@ -201,49 +198,24 @@ class Editor extends Component { } } - .scroll-outerbox { + .wrapper { overflow: hidden; - position: relative; - flex: 1; - padding: 5px 8px; - } - .scroll-innerbox { overflow-y: auto; - width: 100%; - height: 100%; - scrollbar-width: 0; + flex: 1; + padding: 6px 12px; &::-webkit-scrollbar { - display: none; - } - - .wrapper { - min-height: 100%; - } - } - - .is-vertical { - position: absolute; - right: 0; - top: 0; - display: flex; - justify-content: flex-end; - width: 10px; - height: 100%; - - .thumb { - display: block; width: 6px; - height: 0; - border-radius: 5px; - background: rgba(44, 47, 53, 0.25); - cursor: default; - transition: width 0.1s linear; + } - &:hover { - width: 10px; - background: rgba(44, 47, 53, 0.5); - } + &::-webkit-scrollbar-thumb { + visibility: hidden; + border-radius: 3px; + } + + &:hover::-webkit-scrollbar-thumb { + visibility: visible; + background: rgba(0, 0, 0, 0.3); } } @@ -251,6 +223,7 @@ class Editor extends Component { width: 100%; height: 100%; min-height: 54px; + line-height: 1.5; outline: none; text-wrap: wrap; word-break: break-all; @@ -397,7 +370,6 @@ class Editor extends Component { ] #value = '' - #cache = { bar: 0, y: 0 } #gridx = 0 #gridy = 0 @@ -419,33 +391,12 @@ class Editor extends Component { if (this.$refs.editor) { this.#value = val this.$refs.editor.innerHTML = val + this.$emit('input') } 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) { @@ -458,23 +409,6 @@ class Editor extends Component { } } - #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') @@ -707,83 +641,11 @@ class Editor extends Component { } mounted() { - let startY, - moveY, - mousemoveFn = ev => { - let { y } = this.#cache - if (startY !== undefined) { - moveY = this.#fetchScroll(y + ev.pageY - startY) - } - }, - mouseupFn = ev => { - startY = undefined - this.#cache.y = moveY || 0 - delete this._active - unbind(document, 'mousemove', mousemoveFn) - unbind(document, 'mouseup', mouseupFn) - } - this.exec('styleWithCSS', true) - - bind(this.$refs.thumb, 'mousedown', ev => { - startY = ev.pageY - - this._active = true - this.#hideLayers() - - bind(document, 'mousemove', mousemoveFn) - bind(document, 'mouseup', mouseupFn) - }) - - // 鼠标滚动事件 - bind(this.$refs.inner, 'scroll', ev => { - // 拖拽时忽略滚动事件 - if (this._active) { - return - } - - let { bar, y } = this.#cache - let { outer, thumb, inner } = this.$refs - let height = outer.offsetHeight - let scrollHeight = inner.scrollHeight + 10 - let scrollTop = inner.scrollTop - - this.#hideLayers() - - // y轴 都为0时, 不作任何处理 - if (bar === 0) { - return - } - - // 修正滚动条的位置 - // 滚动比例 y 滚动条的可移动距离 - let fixedY = ~~((scrollTop / (scrollHeight - height)) * (height - bar)) - - if (fixedY !== y) { - this.#cache.y = fixedY - thumb.style.transform = `translateY(${fixedY}px)` - } - }) - this._clickoutsideFn = outsideClick(this, _ => this.#hideLayers()) - - this._scrollFn = new ResizeObserver(this.__init__.bind(this)) - this._scrollFn.observe(this.$refs.cont) - - this._inputFn = new MutationObserver(_ => { - this.$emit('input') - }) - - this._inputFn.observe(this.$refs.editor, { - childList: true, - subtree: true, - characterData: true - }) } unmounted() { - this._scrollFn?.disconnect() - this._inputFn?.disconnect() clearOutsideClick(this._clickoutsideFn) } @@ -809,22 +671,17 @@ class Editor extends Component { ` )} -
-
-
-
-
-
-
- -
+ +
+
+
3号字体 2号字体
+
${COLORS.map(c => html``)}
+
- ${Array(81) - .fill(0) - .map((_, n) => html``)} + ${range(81).map((_, n) => html``)}
+
diff --git a/src/meditor/addon.js b/src/meditor/addon.js index dd78c1d..57273b8 100644 --- a/src/meditor/addon.js +++ b/src/meditor/addon.js @@ -4,134 +4,94 @@ * @date 2020/10/14 17:52:44 */ -import { offset } from 'wkit' - -var placeholder = '在此输入文本' +const PLACEHOLDER = '在此输入文本' function trim(str, sign) { return str.replace(new RegExp('^' + sign + '|' + sign + '$', 'g'), '') } -function docScroll(k = 'X') { - return window[`page${k.toUpperCase()}Offset`] -} - -// 通用的弹层触发 -function showDialog(dialog, elem) { - var { left, top } = offset(elem) - left -= docScroll('X') - top += 29 - docScroll('Y') - left += 'px' - top += 'px' - dialog.moveTo({ top, left }) - dialog.show() - return Promise.resolve(dialog) -} - export default { - header(elem) { + header() { this.$refs.header.classList.add('fadein') }, h(level) { - var wrap = this.selection(true) || placeholder + var wrap = this.selection(true) || PLACEHOLDER wrap = wrap.replace(/^(#+ )?/, '#'.repeat(level) + ' ') this.insert(wrap, true) }, - quote(elem) { - var wrap = this.selection(true) || placeholder + quote() { + var wrap = this.selection(true) || PLACEHOLDER wrap = wrap.replace(/^(>+ )?/, '> ') this.insert(wrap, true) }, - bold(elem) { - var wrap = this.selection() || placeholder + bold() { + var wrap = this.selection() || PLACEHOLDER var unwrap = trim(wrap, '\\*\\*') wrap = wrap === unwrap ? `**${wrap}**` : unwrap this.insert(wrap, true) }, - italic(elem) { - var wrap = this.selection() || placeholder + italic() { + var wrap = this.selection() || PLACEHOLDER var unwrap = trim(wrap, '_') wrap = wrap === unwrap ? `_${wrap}_` : unwrap this.insert(wrap, true) }, - through(elem) { - var wrap = this.selection() || placeholder + through() { + var wrap = this.selection() || PLACEHOLDER var unwrap = trim(wrap, '~~') wrap = wrap === unwrap ? `~~${wrap}~~` : unwrap this.insert(wrap, true) }, - list(elem) { - var wrap = this.selection(true) || placeholder + list() { + var wrap = this.selection(true) || PLACEHOLDER wrap = wrap.replace(/^([+\-*] )?/, '+ ') this.insert(wrap, true) }, - order(elem) { - var wrap = this.selection(true) || placeholder + order() { + var wrap = this.selection(true) || PLACEHOLDER wrap = wrap.replace(/^(\d+\. )?/, '1. ') this.insert(wrap, true) }, - line(elem) { + line() { this.insert('\n\n---\n\n', false) }, - code(elem) { - var wrap = this.selection() || placeholder + code() { + var wrap = this.selection() || PLACEHOLDER var unwrap = trim(wrap, '`') wrap = wrap === unwrap ? `\`${wrap}\`` : unwrap this.insert(wrap, true) }, - codeblock(elem) { + codeblock() { this.insert('\n```language\n\n```\n') }, - table(elem) { - // showDialog(this.__TABLE_ADDON__, elem) + table() { this.$refs.table.classList.add('fadein') }, - link(elem) { - showDialog(this.__LINK_ADDON__, elem).then(dialog => { - var wrap = this.selection() || placeholder - dialog.__txt__.value = wrap - }) + link() { + this.$refs.link.classList.add('fadein') }, - image(elem) { - var $file = this.__ATTACH_ADDON__.querySelector('input') - - this._attach = 'image' - $file.setAttribute('accept', 'image/*') - - showDialog(this.__ATTACH_ADDON__, elem) - }, - - attach(elem) { - var $file = this.__ATTACH_ADDON__.querySelector('input') - this._attach = 'file' - $file.removeAttribute('accept') - showDialog(this.__ATTACH_ADDON__, elem) - }, - - fullscreen(elem) { + fullscreen() { this.classList.toggle('fullscreen') - elem.classList.toggle('active') }, - preview(elem) { + preview() { this.previewEnabled = !this.previewEnabled this.$refs.view.classList.toggle('active') - elem.classList.toggle('active') if (this.previewEnabled) { this.$refs.view.code = this.value } diff --git a/src/meditor/helper.js b/src/meditor/helper.js index d133522..b1b7b48 100644 --- a/src/meditor/helper.js +++ b/src/meditor/helper.js @@ -4,9 +4,6 @@ * @date 2020/10/12 18:23:23 */ -import { html } from 'wkit' -import ICONS from './svg.js' - const ELEMS = { a: function (str, attr, inner) { let href = attr.match(attrExp('href')) @@ -83,31 +80,6 @@ export const DEFAULT_TOOLS = [ 'preview' ] -export const TOOL_TITLE = { - header: '插入标题', - h1: '一级标题', - h2: '二级标题', - h3: '三级标题', - h4: '四级标题', - h5: '五级标题', - h6: '六级标题', - quote: '引用文本', - bold: '粗体', - italic: '斜体', - through: '横线', - list: '无序列表', - order: '有序列表', - line: '分割线', - code: '行内代码', - codeblock: '插入代码块', - table: '插入表格', - link: '插入连接', - image: '上传图片', - attach: '上传附件', - fullscreen: '全屏编辑', - preview: '预览' -} - const LI_EXP = /<(ul|ol)>(?:(?!/gi // html标签的属性正则 @@ -126,21 +98,6 @@ function tagExp(tag, open) { return new RegExp(exp, 'gi') } -/** - * 渲染工具栏图标 - */ -export function renderToolbar(list = [], dict = {}, showText = false) { - return list.map(it => { - let title = showText ? '' : dict[it] - let text = showText ? dict[it] || '' : '' - - return html` - - ${text} - ` - }) -} - /** * html转成md */ diff --git a/src/meditor/index.js b/src/meditor/index.js index abc1491..1742770 100644 --- a/src/meditor/index.js +++ b/src/meditor/index.js @@ -9,6 +9,7 @@ import { raw, html, Component, + range, bind, unbind, nextTick, @@ -18,7 +19,7 @@ import { clearOutsideClick } from 'wkit' -import { renderToolbar, DEFAULT_TOOLS, html2md } from './helper.js' +import { DEFAULT_TOOLS, html2md } from './helper.js' import Addon from './addon.js' import markd from '../markd/index.js' @@ -26,6 +27,8 @@ import '../form/input.js' import '../form/button.js' import '../code/index.js' +import ICONS from './svg.js' + const COLORS = [ '#f3f5fb', '#dae1e9', @@ -83,6 +86,7 @@ class MEditor extends Component { display: flex; min-width: 200px; max-height: 720px; + min-height: 64px; border-radius: 3px; transition: box-shadow 0.15s linear; background: var(--wc-meditor-background, #fff); @@ -135,6 +139,12 @@ class MEditor extends Component { fill: currentColor; color: #62778d; } + input { + position: absolute; + width: 100%; + height: 100%; + opacity: 0; + } &:hover, &.active { @@ -155,7 +165,6 @@ class MEditor extends Component { flex: 1; display: flex; width: 100%; - min-height: 200px; border-radius: 3px; .editor, @@ -197,16 +206,12 @@ class MEditor extends Component { `, css` :host([readonly]) { - .editor { - cursor: default; - opacity: 0.8; - } + cursor: default; + opacity: 0.8; } :host([disabled]) { - .editor { - cursor: not-allowed; - opacity: 0.6; - } + cursor: not-allowed; + opacity: 0.6; } :host([readonly]), :host([disabled]) { @@ -235,6 +240,7 @@ class MEditor extends Component { css` .font-layer, + .link-layer, .table-layer { visibility: hidden; position: absolute; @@ -310,119 +316,16 @@ class MEditor extends Component { } } - .addon-link { - width: 320px; - padding: 8px 5px; - background: #fff; - font-size: 13px; + .link-layer { + display: flex; + flex-direction: column; + left: 330px; + width: 230px; + padding: 8px; - li { - display: flex; - align-items: center; - padding: 0 12px; - margin-top: 6px; - - label { - width: 60px; - margin-right: 8px; - } - - wc-input { - flex: 1; - } - - wc-button { - width: 80px; - } - } - } - - .addon-attach { - width: 320px; - padding: 8px 5px; - background: #fff; - font-size: 13px; - - .tabs { - display: flex; - border-bottom: 1px solid var(--color-plain-2); - user-select: none; - - span { - height: 28px; - padding: 0 8px; - line-height: 28px; - cursor: pointer; - - &.active { - color: var(--color-teal-1); - } - } - } - - .remote, - .locale { - display: none; - - &.active { - display: block; - } - } - - .locale { - height: 120px; - padding: 24px 32px; - - .button { - position: relative; - width: 100%; - height: 100%; - padding: 12px 16px; - line-height: 46px; - border: 1px dashed var(--color-plain-3); - border-radius: 4px; - text-align: center; - cursor: pointer; - transition: background 0.1s ease-in-out; - - &::after { - content: '点击选择文件,或拖拽文件到此处'; - } - &:hover, - &.active { - background: rgba(255, 228, 196, 0.15); - } - } - - input { - position: absolute; - left: 0; - top: 0; - z-index: 0; - width: 100%; - height: 100%; - opacity: 0; - } - } - - li { - display: flex; - align-items: center; - padding: 0 12px; - margin-top: 6px; - - label { - width: 60px; - margin-right: 8px; - } - - wc-input { - flex: 1; - } - - wc-button { - width: 80px; - } + wc-button { + width: 40px; + margin-top: 8px; } } `, @@ -481,7 +384,7 @@ class MEditor extends Component { #hideLayers() { this.$refs.header.classList.remove('fadein') // this.$refs.color.classList.remove('fadein') - // this.$refs.link.classList.remove('fadein') + this.$refs.link.classList.remove('fadein') this.$refs.table.classList.remove('fadein') } @@ -490,6 +393,7 @@ class MEditor extends Component { if (ev && ev.type === 'change') { file = ev.target.files[0] ev.target.value = '' + t = ev.target.dataset.type === 'image' ? '!' : '' } this.$emit('upload', { detail: { @@ -564,6 +468,10 @@ class MEditor extends Component { this.#hideLayers() + if (act === 'image' || act === 'attach') { + return + } + Addon[act].call(this, elem) } @@ -582,9 +490,7 @@ class MEditor extends Component { if (value) { this.$refs.link.classList.remove('fadein') this.$refs.editor.focus() - this.restoreSelection() - this.exec(ACTTION.link, value) - this.saveSelection() + this.insert(`[${value}](${value}) `) this.$refs.linkinput.value = '' } } @@ -889,14 +795,25 @@ class MEditor extends Component { } } - mounted() { - let pb = ~~(this.clientHeight * 0.6) - pb = pb < 64 ? 64 : pb - Addon.preview.call(this, this) - this.$refs.editor.style.paddingBottom = pb + 'px' + #fixedPadding() { + if (this.clientHeight > 64) { + let pb = ~~(this.clientHeight * 0.6) + pb = pb < 64 ? 64 : pb + this.$refs.editor.style.paddingBottom = pb + 'px' + } else { + this.$refs.editor.style.cssText = 'padding-bottom:;' + } } - unmounted() {} + mounted() { + this.#fixedPadding() + this.__observer = new ResizeObserver(this.#fixedPadding.bind(this)) + this.__observer.observe(this) + } + + unmounted() { + this.__observer?.disconnect() + } render() { return html` @@ -905,7 +822,22 @@ class MEditor extends Component { class=${classMap({ toolbar: true, active: this.#toolbar.length })} @click=${this.#toolbarClick} > - ${renderToolbar(this.#toolbar)} + ${this.#toolbar.map(it => { + return html` + + + + ${it === 'image' || it === 'attach' + ? html`` + : ''} + ` + })}
+
@@ -942,9 +875,12 @@ class MEditor extends Component { @mousemove=${this.#tableSelect} @mouseleave=${this.#tableSelect} > - ${Array(81) - .fill(0) - .map((_, n) => html``)} + ${range(81).map((_, n) => html``)} + + + `