From 6b59093321e5ad3f7e0069415ac7b417929b3a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=87=E5=A4=A9?= Date: Wed, 10 Jul 2019 20:28:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Eneditor=E5=AF=8C=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/form/next.js | 57 +++++ src/icon.js | 34 +++ src/neditor/icon.js | 75 +++++++ src/neditor/index.js | 479 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 645 insertions(+) create mode 100644 src/form/next.js create mode 100644 src/icon.js create mode 100644 src/neditor/icon.js create mode 100644 src/neditor/index.js diff --git a/src/form/next.js b/src/form/next.js new file mode 100644 index 0000000..b974baa --- /dev/null +++ b/src/form/next.js @@ -0,0 +1,57 @@ +/** + * 未来版表单组件 + * @author yutent + * @date 2019/07/04 12:03:09 + */ + +'use strict' + +export default class DoInput extends HTMLElement { + constructor() { + super() + + console.log('0000') + this.root = this.attachShadow({ mode: 'open' }) + this.root.innerHTML = ` + + + ` + } + + static get observedAttributes() { + return ['type'] + } + + get value() { + return this.__INPUT__.value + } + + set value(val) { + this.__INPUT__.value = val + } + + set type(val) { + // console.log('type', val) + this.__INPUT__.setAttribute('type', val) + this.setAttribute('type', val) + } + + connectedCallback() { + // console.log('----------', this, this.root.children) + this.__INPUT__ = this.root.children[1] + } + + attributeChangedCallback(...args) { + // console.log('======', args) + } +} + +customElements.define('do-input', DoInput) diff --git a/src/icon.js b/src/icon.js new file mode 100644 index 0000000..da93027 --- /dev/null +++ b/src/icon.js @@ -0,0 +1,34 @@ +/** + * svg图标 + * @author yutent + * @date 2019/07/08 11:17:58 + */ + +'use strict' + +function appendSVGDOM() { + var container, + svg, + str = + '' + + container = document.createElement('div') + container.innerHTML = str + + svg = container.querySelector('svg') + svg.setAttribute('aria-hidden', 'true') + svg.style.cssText = 'overflow:hidden;position: absolute;width:0;height:0' + document.head.appendChild(svg) +} + +export default !(function(params) { + if (~['complete', 'loaded', 'interactive'].indexOf(document.readyState)) { + setTimeout(appendSVGDOM, 0) + } else { + var loadedEvent = function() { + document.removeEventListener('DOMContentLoaded', loadedEvent, false) + appendSVGDOM() + } + document.addEventListener('DOMContentLoaded', loadedEvent, false) + } +})() diff --git a/src/neditor/icon.js b/src/neditor/icon.js new file mode 100644 index 0000000..d16421b --- /dev/null +++ b/src/neditor/icon.js @@ -0,0 +1,75 @@ +/** + * icon字典 + * @author yutent + * @date 2019/07/08 17:39:11 + */ + +'use strict' + +export default [ + { + key: 'font', + path: + 'M123.68639053 456.52662723l332.84023669 0 0 110.94674556-110.94674556 0 0 332.84023669-110.94674557 0 0-332.84023669-110.94674556 0zM900.31360947 234.6331361l-218.20118344 0 0 665.68047338-118.31360947 0 0-665.68047338-218.20118342 0 0-110.94674556 554.7337278 0z' + }, + { + key: 'color', + path: + 'M470.975 137l-214.462 562.5h102l44.513-131.25h217.949l48.038 131.25h102.002l-214.501-562.5h-85.5zM512 256.55l78.525 233.175h-160.538l82.013-233.212zM212 774.5v112.5h599.999v-112.5h-599.999z' + }, + { + key: 'bold', + path: + 'M584.81777778 724.57443555H414.91000889v-145.63555556H584.81777778c40.29212445 0 72.81777778 32.52565333 72.81777777 72.81777778 0 40.29326222-32.52565333 72.81777778-72.81777777 72.81777778m-169.90776889-436.90666667h145.63555556c40.29212445 0 72.81777778 32.52565333 72.81777777 72.81777778 0 40.29326222-32.52565333 72.81777778-72.81777777 72.81777778h-145.63555556m271.85265778 62.62328889C733.85187555 462.91626666 766.86222222 409.03111111 766.86222222 360.48554666c0-109.71136-84.95445333-194.17998222-194.18112-194.17998222H269.27445333V845.93777777H611.03217778c101.94488889 0 180.10225778-82.52643555 180.10225777-183.98663111 0-73.78830222-41.74848-136.89742222-104.37176888-166.02453333z' + }, + { + key: 'image', + path: + 'M552.0003125 392h219.999375L552.0003125 171.9996875V392M272 111.9996875h319.9996875l240 240v480c0 44.000625-36 80.000625-79.9996875 80.000625H272c-44.4 0-79.9996875-36-79.9996875-80.000625V192.0003125A79.723125 79.723125 0 0 1 272 111.9996875m0 720h480V512L591.9996875 672.0003125 512 591.9996875l-240 240M351.9996875 392C308 392 272 428 272 471.9996875c0 44.000625 36 80.000625 79.9996875 80.000625 44.000625 0 80.000625-36 80.000625-80.000625 0-43.9996875-36-79.9996875-80.000625-79.9996875z' + }, + { + key: 'left', + path: + 'M128 128h768v85.333H128V128m0 170.667h512V384H128v-85.333m0 170.666h768v85.334H128v-85.334M128 640h512v85.333H128V640m0 170.667h768V896H128v-85.333z' + }, + { + key: 'link', + path: + 'M879.2 131.6c-103-95.5-264.1-88-361.4 11.2L474.7 184c-13.1 13.1-3.7 35.6 13.1 37.5 26.2 1.9 52.4 7.5 78.7 15 7.5 1.9 16.9 0 22.5-5.6l9.4-9.4c54.3-54.3 142.3-59.9 198.5-11.2 63.7 54.3 65.5 151.7 7.5 209.7L662 562.3c-18.7 18.7-41.2 30-63.7 37.5-30 7.5-61.8 5.6-89.9-5.6-16.9-7.5-33.7-16.9-48.7-31.8-7.5-7.5-13.1-15-18.7-24.3-7.5-13.1-24.3-15-33.7-3.7l-52.4 52.4c-7.5 7.5-7.5 18.7-1.9 28.1 7.5 11.2 16.9 20.6 26.2 30 13.1 13.1 30 26.2 44.9 35.6 26.2 16.9 56.2 28.1 86.1 33.7 58.1 11.2 121.7 1.9 174.2-26.2 20.6-11.2 41.2-26.2 58.1-43.1l142.3-142.3c104.9-103.2 101.2-271.8-5.6-371zM534.7 803.9l-39.3-5.6s-26.2-5.6-39.3-11.2c-7.5-1.9-16.9 0-22.5 5.6l-9.4 9.4c-54.3 54.3-142.3 59.9-198.5 11.2-63.7-54.3-65.5-151.7-7.5-209.7l142.3-142.3c18.7-18.7 41.2-30 63.7-37.5 30-7.5 61.8-5.6 89.9 5.6 16.9 7.5 33.7 16.9 48.7 31.8 7.5 7.5 13.1 15 18.7 24.3 7.5 13.1 24.3 15 33.7 3.7l52.4-52.4c7.5-7.5 7.5-18.7 1.9-28.1-7.5-11.2-16.9-20.6-26.2-30-13.1-13.1-28.1-26.2-44.9-35.6-26.2-16.9-56.2-28.1-88-33.7-58.1-11.2-121.7-1.9-174.2 26.2-20.6 11.2-41.2 26.2-58.1 43.1L141.4 515.5c-99.3 99.3-106.7 260.3-11.2 361.4C229.5 985.5 398 987.4 501 884.4l46.8-46.8c13.1-9.4 3.7-31.9-13.1-33.7z' + }, + { + key: 'ordered', + path: + 'M298.667 554.667H896v-85.334H298.667m0 341.334H896v-85.334H298.667m0-426.666H896v-85.334H298.667m-213.334 256h76.8l-76.8 89.6v38.4h128v-42.666h-76.8l76.8-89.6v-38.4h-128M128 341.333h42.667V170.667H85.333v42.666H128m-42.667 512h85.334v21.334H128v42.666h42.667v21.334H85.333v42.666h128V682.667h-128v42.666z' + }, + { + key: 'italic', + path: + 'M414.91000889 163.63889777v145.63555555h107.28448L356.16995555 697.63555555H220.72888889v145.63555555h388.36110222V697.63555555H501.80551111l166.02453334-388.36110222H803.27111111v-145.63555556H414.91000889z' + }, + { + key: 'delete', + path: + 'M898.719 512v70.312H744.383c35.156 75.235 33.398 281.25-219.024 281.25-292.851 1.758-281.601-228.515-281.601-228.515l139.57 1.758c1.055 118.476 111.094 118.476 132.89 117.07 22.5-1.758 106.524-1.406 113.204-83.672 2.812-38.32-35.86-67.5-78.047-87.89H125.281V512H898.72M772.508 367.508l-139.922-1.055s5.976-97.383-115.313-97.734c-121.289-0.703-110.742 77.343-110.742 87.187 1.407 9.844 11.953 58.36 105.469 81.563H290.867S168.172 200.867 467.703 160.437c306.211-42.187 305.508 207.774 304.805 207.07z' + }, + { + key: 'under', + path: + 'M232 872h560v-80H232v80m280-160c132.4 0 240-107.6 240-240V152H652v320c0 77.2-62.8 140-140 140s-140-62.8-140-140V152H272v320c0 132.4 107.6 240 240 240z' + }, + { + key: 'center', + path: + 'M128 128h768v85.333H128V128m170.667 170.667h426.666V384H298.667v-85.333M128 469.333h768v85.334H128v-85.334M298.667 640h426.666v85.333H298.667V640M128 810.667h768V896H128v-85.333z' + }, + { + key: 'unordered', + path: + 'M298.667 213.333v85.334H896v-85.334M298.667 554.667H896v-85.334H298.667m0 341.334H896v-85.334H298.667m-128-14.08c-31.574 0-56.747 25.6-56.747 56.747s25.6 56.747 56.747 56.747c31.146 0 56.746-25.6 56.746-56.747s-25.173-56.747-56.746-56.747m0-519.253c-35.414 0-64 28.587-64 64s28.586 64 64 64c35.413 0 64-28.587 64-64s-28.587-64-64-64m0 256c-35.414 0-64 28.587-64 64s28.586 64 64 64c35.413 0 64-28.587 64-64s-28.587-64-64-64z' + }, + { + key: 'right', + path: + 'M128 128h768v85.333H128V128m256 170.667h512V384H384v-85.333M128 469.333h768v85.334H128v-85.334M384 640h512v85.333H384V640M128 810.667h768V896H128v-85.333z' + } +] diff --git a/src/neditor/index.js b/src/neditor/index.js new file mode 100644 index 0000000..cd8a1c9 --- /dev/null +++ b/src/neditor/index.js @@ -0,0 +1,479 @@ +/** + * neditor + * @author yutent + * @date 2019/07/05 13:44:10 + */ + +'use strict' + +const log = console.log + +import iconList from './icon' + +const ACTTION = { + bold: 'bold', + italic: 'italic', + under: 'underline', + delete: 'strikeThrough', + left: 'justifyLeft', + center: 'justifyCenter', + right: 'justifyRight', + image: 'insertImage', + font: 'fontSize', + color: 'foreColor', + link: 'createLink', + ordered: 'insertOrderedList', + unordered: 'insertUnorderedList' +} + +const DEFAULT_TOOLS = [ + 'font', + 'color', + 'bold', + 'italic', + 'under', + 'delete', + 'ordered', + 'unordered', + 'left', + 'center', + 'right', + 'link', + 'image' +] + +export default class Nedtior extends HTMLElement { + // 监听属性变化 + static get observedAttributes() { + return ['toolbar', 'value'] + } + + constructor() { + super() + this.root = this.attachShadow({ mode: 'open' }) + + this.render() + this.__TOOLBAR__ = this.root.children[2] + this.__FONT__ = this.root.children[3] + this.__COLOR__ = this.root.children[4] + this.__LINK__ = this.root.children[5] + this.__LINK_BTN__ = this.__LINK__.querySelector('span') + this.__EDITOR__ = this.root.lastElementChild + } + + render() { + this.root.innerHTML = ` + + + +
+ ${this._parseTools()} +
+
+ 大号字体 + 中号字体 + 小号字体 +
+
+ + + + + + + + + + + + +
+ +
+ ` + } + + // 解析工具栏 + _parseTools() { + const tools = this.tools || DEFAULT_TOOLS + + return tools + .map( + it => + `${ + it === 'image' ? '' : '' + }` + ) + .join('') + } + + get value() { + return this.__EDITOR__.innerHTML + } + + set value(val) { + if (this.__EDITOR__ && this.__EDITOR__.innerHTML !== val) { + this.__EDITOR__.innerHTML = val + } + } + + set toolbar(val) { + if (val && Array.isArray(val)) { + this.tools = val + if (this.__TOOLBAR__) { + if (this.tools.length) { + this.__TOOLBAR__.style.display = 'flex' + this.__TOOLBAR__.innerHTML = this._parseTools() + } else { + this.__TOOLBAR__.style.display = 'none' + } + } + } + } + + // 保存选中 + saveSelection() { + var gs = this.root.getSelection() + if (gs.getRangeAt && gs.rangeCount) { + this.__SELECT__ = gs.getRangeAt(0) + } + } + + // 清除选中并重置选中 + restoreSelection() { + var gs = this.root.getSelection() + if (this.__SELECT__) { + try { + gs.removeAllRanges() + } catch (err) {} + gs.addRange(this.__SELECT__) + } + } + + // 执行命令 + exec(cmd, val = '') { + document.execCommand(cmd, false, val) + } + + // 处理图片 + _handleImage(file) { + this.dispatchEvent( + new CustomEvent('upload', { + detail: { + file, + send: link => { + this.__EDITOR__.focus() + this.restoreSelection() + this.exec(ACTTION.image, link) + this.saveSelection() + // 修正插入的图片,宽度不得超出容器 + this.__EDITOR__.querySelectorAll('img').forEach(_ => { + _.style.maxWidth = '100%' + }) + } + } + }) + ) + } + + connectedCallback() { + const LINK_INPUT = this.__LINK__.querySelector('input') + const FILE_INPUT = this.__TOOLBAR__.querySelector('input') + + document.execCommand('styleWithCSS', null, true) + + // 这里有一个彩蛋 + if (FILE_INPUT) { + FILE_INPUT.addEventListener( + 'change', + ev => { + this._handleImage(FILE_INPUT.files[0]) + }, + false + ) + } + + // 工具栏点击事件 + this.__toolFn = ev => { + if (ev.target === ev.currentTarget) { + return + } + let target = ev.target + while (target.tagName !== 'SPAN') { + target = target.parentNode + } + var act = target.dataset.act + var val = '' + + switch (act) { + case 'font': + this.__COLOR__.classList.remove('fadein') + this.__LINK__.classList.remove('fadein') + + if (this.__FONT__.classList.contains('fadein')) { + this.__FONT__.classList.remove('fadein') + } else { + this.__FONT__.classList.add('fadein') + } + break + + case 'color': + this.__LINK__.classList.remove('fadein') + this.__FONT__.classList.remove('fadein') + if (this.__COLOR__.classList.contains('fadein')) { + this.__COLOR__.classList.remove('fadein') + } else { + this.__COLOR__.classList.add('fadein') + } + break + + case 'link': + this.__COLOR__.classList.remove('fadein') + this.__FONT__.classList.remove('fadein') + if (this.__LINK__.classList.contains('fadein')) { + this.__LINK__.classList.remove('fadein') + } else { + this.__LINK__.classList.add('fadein') + } + break + + case 'image': + // 这里不作任何处理 + break + + default: + this.__EDITOR__.focus() + this.restoreSelection() + this.exec(ACTTION[act]) + this.saveSelection() + } + } + + // 字体大小设置 + this.__fontFn = ev => { + if (ev.target === ev.currentTarget) { + return + } + this.__FONT__.classList.remove('fadein') + this.__EDITOR__.focus() + this.restoreSelection() + this.exec(ACTTION.font, ev.target.dataset.size) + this.saveSelection() + } + + // 颜色 + this.__colorFn = ev => { + if (ev.target === ev.currentTarget) { + return + } + this.__COLOR__.classList.remove('fadein') + this.__EDITOR__.focus() + this.restoreSelection() + this.exec(ACTTION.color, ev.target.dataset.color) + this.saveSelection() + } + + // 超链接 + this.__linkFn = ev => { + if (LINK_INPUT.value) { + this.__LINK__.classList.remove('fadein') + this.__EDITOR__.focus() + this.restoreSelection() + this.exec(ACTTION.link, LINK_INPUT.value) + this.saveSelection() + LINK_INPUT.value = '' + } + } + + //监听鼠标事件的,以缓存选中状态 + this.__mouseFn = ev => { + this.__FONT__.classList.remove('fadein') + this.__COLOR__.classList.remove('fadein') + this.__LINK__.classList.remove('fadein') + this.saveSelection() + } + + // 粘贴板事件 + this.__pasteFn = ev => { + ev.preventDefault() + + var txt = ev.clipboardData.getData('text/plain') + var items = ev.clipboardData.items + + if (txt) { + return this.exec('insertText', txt) + } + + if (items && items.length) { + let blob = null + for (let it of items) { + if (it.type.indexOf('image') > -1) { + blob = it.getAsFile() + } + } + this._handleImage(blob) + } + } + + this.__TOOLBAR__.addEventListener('click', this.__toolFn, false) + this.__FONT__.addEventListener('click', this.__fontFn, false) + this.__COLOR__.addEventListener('click', this.__colorFn, false) + this.__LINK_BTN__.addEventListener('click', this.__linkFn, false) + this.__EDITOR__.addEventListener('mouseup', this.__mouseFn, false) + this.__EDITOR__.addEventListener('paste', this.__pasteFn, false) + + this.__observer = new MutationObserver(_ => { + this.dispatchEvent( + new CustomEvent('updated', { + detail: this.value + }) + ) + }) + + this.__observer.observe(this.__EDITOR__, { + childList: true, + subtree: true, + characterData: true + }) + } + + disconnectedCallback() { + this.__TOOLBAR__.removeEventListener('click', this.__toolFn) + this.__FONT__.removeEventListener('click', this.__fontFn) + this.__COLOR__.removeEventListener('click', this.__colorFn) + this.__LINK_BTN__.removeEventListener('click', this.__linkFn) + this.__EDITOR__.removeEventListener('mouseup', this.__mouseFn) + this.__EDITOR__.removeEventListener('paste', this.__pasteFn) + + this.__observer.disconnect() + } + + attributeChangedCallback(name, old, val) { + switch (name) { + case 'toolbar': + if (typeof val === 'string') { + try { + val = val.split(',') + } catch (err) {} + } + this.toolbar = val + break + + case 'value': + this.value = val + break + + default: + break + } + } +} + +customElements.define('do-neditor', Nedtior)