编辑器开发测试

This commit is contained in:
暮光:城中城
2020-06-13 20:37:18 +08:00
parent cafe72fcc2
commit 61938a840f
9 changed files with 1079 additions and 1 deletions

View File

@@ -0,0 +1,222 @@
<!--
-- zyplayer-doc 自研编辑器集各大厂商优秀的设计拷它的css拷它的图标借鉴它的内容处理造一个现代化好用的轮子
-- 带着情怀开写路途曲折希望能完美呈现
-- 参考飞书文档语雀文档wangEditor
-- @author 暮光城中城
-- @since 2020-06-06
-->
<template>
<div class="mg-editor-box">
<div ref="mgEditor" class="mg-editor" @click.stop="editorClick($event)">
<template v-for="(item, index) in editorDom">
<div v-if="item.type=='text'" :class="item.cls" :index="index" @click.stop="domClick(item, $event)">
<template v-if="item.dom && item.dom.length > 0">
<span v-for="(span, subIndex) in item.dom" :class="span.cls" :index="index" :subindex="subIndex">{{span.text}}</span>
</template>
<template v-else-if="!!item.text">{{item.text}}</template>
<br v-else/>
</div>
<div v-else-if="item.type=='locate'" :class="item.cls" @click.stop="domClick(item, $event)">
<br/>
</div>
</template>
</div>
<div class="mg-editor-toolbar" :style="editorToolbarStyle">
<span class="iconfont icon-h1" @click="handleToolbarHn('h1')"></span>
<span class="iconfont icon-h2" @click="handleToolbarHn('h2')"></span>
<span class="iconfont icon-h3" @click="handleToolbarHn('h3')"></span>
<span class="iconfont icon-hn"></span>
<span class="iconfont icon-bold" @click="handleToolbarBold()"></span>
<span class="iconfont icon-delete" @click="handleToolbarStrikeThrough()"></span>
<span class="iconfont icon-backcolor"></span>
<span class="iconfont icon-orderedlist"></span>
<span class="iconfont icon-unorderedlist"></span>
<span class="iconfont icon-checkbox"></span>
<span class="iconfont icon-link"></span>
<span class="iconfont icon-more"></span>
</div>
<textarea ref="userInput" @input="userInputDataChange()" v-model="userInputData" class="user-input" :style="userInputStyle"></textarea>
<div class="mg-editor-cursor" :style="editorCursorStyle"></div>
</div>
</template>
<script>
import "./css/MgEditor.css";
import "./css/MgEditorIconfont.css";
import Dom from './util/dom';
import toolbarCommon from './toolbar/common';
const $ = require("jquery");
export default {
name: "mg-editor",
data() {
return {
editor: {},
editorRange: {
startOffset: 0,
endOffset: 0,
},
userInput: {},
userInputStyle: {
top: 0, left: 0, display: 'none'
},
editorCursorStyle: {
top: 0, left: 0, display: 'none'
},
editorToolbarStyle: {
top: 0, left: 0, display: 'none'
},
editorDom: [],
editDom: {},
userInputData: '',
};
},
mounted: function () {
this.editor = this.$refs.mgEditor;
this.userInput = this.$refs.userInput;
this.editorDom.push(new Dom('text', 'head head-h1'));
// 监听输入框的特殊按键
this.userInput.addEventListener('keydown', e => {
if (e.which == 13) {
e.preventDefault();
this.editDom.keyEnter(this.editorDom, this.editorRange);
}
});
// 鼠标选中事件
this.editor.addEventListener('mouseup', e => {
// 不延时还能获取到选中的文字(选择文字,单击选中文字的中间)
let selectionRange = window.getSelection().getRangeAt(0);
let selectText = selectionRange.toString();
if (!!selectText) {
let isOneDom = selectionRange.startContainer == selectionRange.endContainer;
let startNode = toolbarCommon.getRootDom(selectionRange.startContainer);
let index = startNode.getAttribute("index");
if (index >= 0) {
let startOffset = selectionRange.startOffset;
let previousSibling = toolbarCommon.getRealElem(selectionRange.startContainer).previousSibling;
for (; previousSibling;) {
startOffset += previousSibling.innerText.length;
previousSibling = previousSibling.previousSibling;
}
let domTemp = this.editorDom[index];
let endOffset = isOneDom ? selectionRange.endOffset : domTemp.text.length;
domTemp.setOffset(startOffset, endOffset);
}
if (!isOneDom) {
let endOffset = selectionRange.endOffset;
let previousSibling = toolbarCommon.getRealElem(selectionRange.endContainer).previousSibling;
for (; previousSibling;) {
endOffset += previousSibling.innerText.length;
previousSibling = previousSibling.previousSibling;
}
let endNode = toolbarCommon.getRootDom(selectionRange.endContainer);
let index = endNode.getAttribute("index");
if (index >= 0) {
let domTemp = this.editorDom[index];
domTemp.setOffset(0, endOffset);
}
}
// let documentFragment = selectionRange.cloneContents();
// let childNodesLen = documentFragment.childNodes.length;
// if (childNodesLen > 2) {
// for (let i = 1; i < childNodesLen - 1; i++) {
// let childNode = documentFragment.childNodes[i];
// let index = childNode.getAttribute("index");
// if (index >= 0) {
// let domTemp = this.editorDom[index];
// if (i == 0) {
// domTemp.setOffset(selectionRange.startOffset, domTemp.text.length);
// } else if (i == childNodesLen - 1) {
// domTemp.setOffset(0, selectionRange.endOffset);
// } else {
// domTemp.setOffsetAll();
// }
// }
// }
// }
this.editorToolbarStyle.display = 'block';
}
// console.log("mouseup", selectText, e);
});
},
methods: {
editorClick(event) {
let lastDom = this.editorDom[this.editorDom.length - 1];
if (lastDom.type != 'locate') {
lastDom = new Dom('locate', 'locate');
this.editorDom.push(lastDom);
}
setTimeout(() => event.target.lastChild.click(), 100);
},
domClick(dom, event) {
this.editDom = dom;
this.editDom.target = event.target;
// 设置接收用户输入的输入框绝对位置
this.userInputStyle.top = event.pageY + 'px';
this.userInputStyle.left = event.pageX + 'px';
this.userInputStyle.display = 'block';
// 设置光标绝对位置
let computedStyle = window.getComputedStyle(event.target);
let fontSize = parseInt(computedStyle.fontSize);
let offsetTop = event.target.offsetTop + (fontSize / 2 / 2);
let offsetLeft = event.target.offsetLeft;
this.editorCursorStyle.top = offsetTop + 'px';
this.editorCursorStyle.left = offsetLeft + 'px';
this.editorCursorStyle.height = computedStyle.fontSize;
this.editorCursorStyle.display = 'block';
// 设置光标所在对象的位置
let range = window.getSelection().getRangeAt(0);
this.editorRange.startOffset = range.startOffset;
this.editorRange.endOffset = range.endOffset;
// 没展示出来时不能获取焦点
let selectText = window.getSelection().getRangeAt(0).toString();
if (!selectText) {
setTimeout(() => this.userInput.focus(), 50);
}
},
userInputDataChange() {
// 如果在最后一个div里面输入则改为非最后一个然后在最后再加一行
if (this.editDom.type == 'locate') {
this.editDom.type = 'text';
this.editDom.removeClass('locate');
this.editorDom.push(new Dom('locate', 'locate'));
}
let oldText = this.editDom.text || '';
// 如果文字的中间位置点击,则把内容放到指定位置
let startOffset = this.editorRange.startOffset;
this.editDom.addText(startOffset, this.userInputData);
if (startOffset < oldText.length) {
this.editorRange.startOffset = this.editorRange.endOffset = (startOffset + this.userInputData.length);
} else {
// 否则放到最后
this.editorRange.startOffset = this.editorRange.endOffset = this.editDom.text.length;
}
// let newLength = this.userInputData.replace(/[\u0391-\uFFE5]/g, "aa").length / 2;
// let letterSpacing = this.userInputData.length * 0.52;
// this.editorCursorStyle.left = (parseInt(this.editorCursorStyle.left) + (parseInt(fontSize) * newLength) + letterSpacing) + 'px';
this.userInputData = '';
},
handleToolbarBold() {
for (let i = 0; i < this.editorDom.length; i++) {
this.editorDom[i].addSelectionTextStyle('bold', 1);
}
this.editorToolbarStyle.display = 'none';
window.getSelection().removeAllRanges();
},
handleToolbarStrikeThrough() {
for (let i = 0; i < this.editorDom.length; i++) {
this.editorDom[i].addSelectionTextStyle('strikethrough', 1);
}
this.editorToolbarStyle.display = 'none';
window.getSelection().removeAllRanges();
},
handleToolbarHn(hn) {
debugger
this.editorToolbarStyle.display = 'none';
window.getSelection().removeAllRanges();
},
}
}
</script>

View File

@@ -0,0 +1,223 @@
.mg-editor-box {
overflow: auto;
}
.mg-editor-box .user-input {
width: 1px;
height: 1px;
font-size: 1px;
background: transparent;
opacity: 0;
position: fixed;
top: 0;
left: 0;
display: none;
z-index: 1;
-moz-appearance: none;
appearance: none;
border: none;
resize: none;
outline: none;
overflow: hidden;
padding: 0 1px;
margin: 0 -1px;
contain: strict;
-ms-user-select: text;
-moz-user-select: text;
-webkit-user-select: text;
user-select: text;
white-space: pre !important;
}
.mg-editor-box .mg-editor-cursor {
display: block;
top: 42px;
left: 202px;
width: 7px;
height: 14px;
color: #F8F8F0;
opacity: 0.2;
z-index: 4;
position: absolute;
box-sizing: border-box;
border-left: 2px solid;
animation-duration: 1000ms;
animation-timing-function: step-end;
animation-name: blink-ace-animate;
animation-iteration-count: infinite;
transform: translatez(0);
}
@keyframes blink-ace-animate {
from, to {opacity: 1;}
60% {opacity: 0;}
}
@keyframes blink-ace-animate-smooth {
from, to {opacity: 1;}
45% {opacity: 1;}
60% {opacity: 0;}
85% {opacity: 0;}
}
.mg-editor {
padding: 10px;
height: 100%;
width: 100%;
font-size: 16px;
box-sizing: border-box;
cursor: text;
background: #272822;
color: #fff;
}
.mg-editor:focus {
outline: none;
}
.mg-editor:empty::before {
content: attr(placeholder);
font-size: 14px;
color: #ccc;
line-height: 20px;
padding-top: 10px;
}
.mg-editor-toolbar {
position: fixed;
top: 0;
left: 0;
display: none;
z-index: 1;
padding: 5px 6px;
background-color: #fff;
border-radius: 4px;
border: 1px solid #dee0e3;
user-select: none;
box-shadow: 0 6px 24px 0 rgba(31, 35, 41, .1);
}
.mg-editor-toolbar .iconfont {
padding: 5px;
font-size: 24px;
cursor: pointer;
}
.mg-editor .list-code {
font-size: 14px !important;
line-height: 1.68 !important
}
.mg-editor .list-code code {
border: 1px solid #dee0e3;
display: block;
background-color: #f5f6f7;
line-height: 1.5;
padding: 6px 8px 3px 45px;
font-size: 14px;
word-break: break-word;
margin: 5px 0;
}
.mg-editor .list-code code .list-code-span {
padding-top: 0 !important;
padding-bottom: 0 !important;
line-height: 1.5;
}
.mg-editor .list-code code:before {
content: attr(start);
display: inline-block;
font-size: 14px;
text-align: right;
direction: rtl;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
overflow: visible;
white-space: nowrap;
width: 0;
margin-left: -11px;
padding-right: 11px;
font-weight: 400;
background-color: transparent;
line-height: 1.5;
color: #8f959e;
vertical-align: bottom;
}
.mg-editor .list-code code + code {
border-top: none;
margin-top: -9px;
}
.mg-editor .ace-editor .list-code code:before {
left: 4px;
}
/*Hn样式-S-*/
/*我很懒,样式都是抄的*/
.mg-editor .head-h1, .mg-editor .head-h2, .mg-editor .head-h3, .mg-editor .head-h4, .mg-editor .head-h5, .mg-editor .head-h6, .mg-editor .head-h7, .mg-editor .head-h8, .mg-editor .head-h9 {
font-weight: 600;
letter-spacing: .02em;
line-height: 1.65
}
.mg-editor .head-h1 {
font-size: 26px;
margin-bottom: 10px
}
.mg-editor .head-h1 .collapsable-section-handle {
font-size: 26px
}
.mg-editor .head-h2 {
font-size: 22px;
margin-bottom: 8px
}
.mg-editor .head-h2 .collapsable-section-handle {
font-size: 22px
}
.mg-editor .head-h3 {
font-size: 20px;
margin-bottom: 8px
}
.mg-editor .head-h3 .collapsable-section-handle {
font-size: 20px
}
.mg-editor .head-h4 {
font-size: 18px;
margin-bottom: 8px
}
.mg-editor .head-h4 .collapsable-section-handle {
font-size: 18px
}
.mg-editor .head-h5,.mg-editor .head-h6,.mg-editor .head-h7,.mg-editor .head-h8,.mg-editor .head-h9 {
font-size: 16px;
margin-bottom: 6px
}
.mg-editor .head-h5 .collapsable-section-handle,.mg-editor .head-h6 .collapsable-section-handle,.mg-editor .head-h7 .collapsable-section-handle,.mg-editor .head-h8 .collapsable-section-handle,.mg-editor .head-h9 .collapsable-section-handle {
font-size: 16px
}
/*Hn样式-E-*/
.mg-editor .bold {
font-weight: bold;
}
.mg-editor .strikethrough {
text-decoration: line-through;
}

View File

@@ -0,0 +1,117 @@
@font-face {
font-family: "iconfont";
src: url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-h2:before {
content: "\e503";
}
.icon-h1:before {
content: "\e504";
}
.icon-h3:before {
content: "\e505";
}
.icon-hn:before {
content: "\e506";
}
.icon-h4:before {
content: "\e507";
}
.icon-h5:before {
content: "\e508";
}
.icon-h6:before {
content: "\e509";
}
.icon-h7:before {
content: "\e50b";
}
.icon-h8:before {
content: "\e50c";
}
.icon-h9:before {
content: "\e50d";
}
.icon-bold:before {
content: "\e50e";
}
.icon-delete:before {
content: "\e50f";
}
.icon-backcolor:before {
content: "\e510";
}
.icon-orderedlist:before {
content: "\e511";
}
.icon-unorderedlist:before {
content: "\e512";
}
.icon-checkbox:before {
content: "\e513";
}
.icon-link:before {
content: "\e514";
}
.icon-more:before {
content: "\e515";
}
.icon-underline:before {
content: "\e516";
}
.icon-aligncenter:before {
content: "\e517";
}
.icon-alignleft:before {
content: "\e518";
}
.icon-blockquote:before {
content: "\e519";
}
.icon-italic:before {
content: "\e51a";
}
.icon-alignright:before {
content: "\e51b";
}
.icon-inlinecode:before {
content: "\e51c";
}
.icon-inlinecodelist:before {
content: "\e51d";
}

View File

@@ -0,0 +1,30 @@
export default {
getSelectionContainer(judgeRoot) {
let container = window.getSelection().getRangeAt(0).commonAncestorContainer;
if (container.nodeType != 1) {
container = container.parentNode;
}
if (container.nodeType != 1 || (judgeRoot && this.isRootBox(container))) {
return null;
}
return container;
},
getRootDom(nowDom) {
let newDom = this.getRealElem(nowDom);
if (!newDom || this.isRootBox(nowDom)) return null;
if (this.isRootBox(newDom.parentNode)) return newDom;
return this.getRootDom(newDom.parentNode);
},
getRealElem: function (elem) {
return !elem || elem.nodeType === 1 ? elem : elem.parentNode;
},
isRootBox(container) {
return this.domHaveClass(container, "mg-editor");
},
domHaveClass(container, cls) {
return container && container.classList && container.classList.contains(cls);
},
}

View File

@@ -0,0 +1,152 @@
// 构造函数
import TextStyle from "./textStyle";
import StyleRange from "./styleRange";
function Dom(type = 'text', cls = '', text = '') {
this.type = type;
this.text = text;
this.target = '';
this.cls = cls;
this.clsSet = new Set(cls.split(" "));
this.startOffset = -1;
this.endOffset = -1;
this.dom = [];
this.textStyle = [];
// 一个范围的样式,例:{start: 1, end: 2, class: 'xx xxx'}
this.styleRange = [];
}
// 原型
Dom.prototype = {
constructor: Dom,
// 删除class
removeClass(cls) {
if (!this.hasClass(cls)) return;
this.clsSet.delete(cls);
this.cls = Array.from(this.clsSet).join(" ");
},
hasClass(cls) {
return this.clsSet.has(cls);
},
addClass(cls) {
if (this.hasClass(cls)) return;
this.clsSet.add(cls);
this.cls = Array.from(this.clsSet).join(" ");
},
setOffset(start, end) {
this.startOffset = start;
this.endOffset = end;
},
setOffsetAll() {
this.startOffset = 0;
this.endOffset = this.text.length;
},
addText(startOffset, data) {
if (!data) return;
// todo 删除选中的内容
// 如果在某个样式范围内,则加入
for (let i = 0; i < this.styleRange.length; i++) {
let item = this.styleRange[i];
if (startOffset >= item.start && startOffset < item.end) {
item.end += data.length;
}
}
if (startOffset < this.text.length) {
this.text = this.text.substring(0, startOffset) + data + this.text.substring(startOffset, this.text.length);
} else {
this.text = this.text + data;
}
this.computerStyleRangeToDom();
},
addSelectionTextStyle(cls) {
if (this.startOffset < 0 || this.endOffset < 0 || this.startOffset == this.endOffset) {
return;
}
let styleRangeNew = [];
// 拆分
for (let i = 0; i < this.styleRange.length; i++) {
let item = this.styleRange[i];
// 全选 前面 中间 后面 全包含
if (this.startOffset == item.start && this.endOffset == item.end) {
item.addClass(cls);
styleRangeNew.push(item);
} else if (this.startOffset == item.start && this.endOffset < item.end) {
styleRangeNew.push(new StyleRange(this.startOffset, this.endOffset, item.cls + ' ' + cls));
styleRangeNew.push(new StyleRange(this.endOffset, item.end, item.cls));
} else if (this.startOffset > item.start && this.endOffset < item.end) {
styleRangeNew.push(new StyleRange(item.start, this.startOffset, item.cls));
styleRangeNew.push(new StyleRange(this.startOffset, this.endOffset, item.cls + ' ' + cls));
styleRangeNew.push(new StyleRange(this.endOffset, item.end, item.cls));
} else if (this.startOffset > item.start && this.endOffset >= item.end) {
styleRangeNew.push(new StyleRange(0, this.startOffset, item.cls));
styleRangeNew.push(new StyleRange(this.startOffset, item.end, item.cls + ' ' + cls));
} else if (this.startOffset <= item.start && this.endOffset >= item.end) {
if (this.startOffset < item.start) {
styleRangeNew.push(new StyleRange(this.startOffset, item.start, cls));
}
item.addClass(cls);
styleRangeNew.push(item);
if (this.endOffset > item.end) {
styleRangeNew.push(new StyleRange(item.end, this.endOffset, cls));
}
} else {
styleRangeNew.push(item);
}
}
if (styleRangeNew.length <= 0) {
styleRangeNew.push(new StyleRange(this.startOffset, this.endOffset, cls));
}
styleRangeNew.sort((val1, val2) => val1.start - val2.start);
// 合并同一个范围内样式表重叠的
let styleRangeLast = [];
let len = styleRangeNew.length;
for (let i = 0; i < len; i++) {
let item = styleRangeNew[i];
for (let j = i + 1; j < len; j++, i++) {
let itemNext = styleRangeNew[j];
if (!item.classSameAll(itemNext)) break;
item.start = Math.min(item.start, itemNext.start);
item.end = Math.max(item.end, itemNext.end);
}
styleRangeLast.push(item);
}
this.styleRange = styleRangeLast;
this.startOffset = this.endOffset = -1;
this.computerStyleRangeToDom();
},
computerStyleRangeToDom() {
// 把样式表渲染为dom列表
this.dom = [];
let lastStart = 0;
for (let i = 0; i < this.styleRange.length; i++) {
let item = this.styleRange[i];
if (lastStart < item.start) {
this.dom.push(new Dom('', '', this.text.substring(lastStart, item.start)));
}
let text = this.text.substring(item.start, item.end);
this.dom.push(new Dom('', item.cls, text));
lastStart = item.end;
}
if (lastStart < this.text.length) {
this.dom.push(new Dom('', '', this.text.substring(lastStart, this.text.length)));
}
},
// 回车事件处理
keyEnter(editorDom, editorRange) {
let nextText = '';
let oldText = this.text || '';
// 如果文字的中间位置点击,则把内容分割到两行
if (editorRange.startOffset < oldText.length) {
this.text = oldText.substring(0, editorRange.startOffset);
nextText = oldText.substring(editorRange.startOffset, oldText.length);
}
for (let i = 0; i < editorDom.length; i++) {
if (this == editorDom[i]) {
editorDom.splice(i + 1, 0, new Dom('text', this.cls, nextText));
break;
}
}
},
};
export default Dom;

View File

@@ -0,0 +1,242 @@
// 构造函数
import TextStyle from "./textStyle";
function Dom(type = 'text', cls = '', text = '') {
this.type = type;
this.text = text;
this.target = '';
this.cls = cls;
this.clsSet = new Set(cls.split(" "));
this.startOffset = -1;
this.endOffset = -1;
this.dom = [];
this.textStyle = [];
}
// 原型
Dom.prototype = {
constructor: Dom,
// 删除class
removeClass(cls) {
if (!this.hasClass(cls)) return;
this.clsSet.delete(cls);
this.cls = Array.from(this.clsSet).join(" ");
},
hasClass(cls) {
return this.clsSet.has(cls);
},
addClass(cls) {
if (this.hasClass(cls)) return;
this.clsSet.add(cls);
this.cls = Array.from(this.clsSet).join(" ");
},
setOffset(start, end) {
this.startOffset = start;
this.endOffset = end;
},
setOffsetAll() {
this.startOffset = 0;
this.endOffset = this.text.length;
},
addText(startOffset, data) {
if (!data) return;
// todo 删除选中的内容
let textStyleNew = [];
let len = this.text.length;
if (len > 0) {
if (startOffset < this.text.length) {
for (let i = 0; i < len; i++) {
if (i == startOffset) {
let styleCopy = this.textStyle[i];
for (let j = 0; j < data.length; j++) {
textStyleNew.push(styleCopy.clone());
}
}
textStyleNew.push(this.textStyle[i]);
}
this.text = this.text.substring(0, startOffset) + data + this.text.substring(startOffset, this.text.length);
} else {
textStyleNew = textStyleNew.concat(this.textStyle);
let styleCopyNew = this.textStyle[this.textStyle.length - 1];
for (let j = 0; j < data.length; j++) {
textStyleNew.push(styleCopyNew.clone());
}
this.text = this.text + data;
}
} else {
for (let j = 0; j < data.length; j++) {
textStyleNew.push(new TextStyle());
}
this.text = data;
}
this.textStyle = textStyleNew;
this.computerTextStyleToDom();
},
addSelectionTextStyle(type, value) {
if (this.startOffset < 0 || this.endOffset < 0) {
return;
}
for (let i = this.startOffset; i < this.endOffset; i++) {
this.textStyle[i][type] = value;
}
this.startOffset = this.endOffset = -1;
this.computerTextStyleToDom();
},
computerTextStyleToDom() {
this.dom = [];
let len = this.text.length;
for (let i = 0; i < len; i++) {
let startIndex = i;
for (let j = i + 1; j < len; j++, i++) {
if (this.textStyle[i].notEq(this.textStyle[j])) break;
}
let text = this.text.substring(startIndex, i + 1);
this.dom.push(new Dom('', this.textStyle[i].getCls(), text));
}
},
borderSelection1() {
this.removeClass('border');
if (this.startOffset == 0 && this.endOffset > 0) {
if (this.endOffset == this.text.length) {
// 全选
this.addClass('border');
} else {
// 选中前面的部分 xx-xxxxxx-xx
if (this.dom.length > 0) {
// 有元素的时候
let len = 0;
let newDomArr = [];
this.dom.forEach(item => {
let domSub = false;
let textLen = item.text.length;
if (len < this.endOffset) {
if (!this.hasClass('border') && !item.hasClass('border')) {
if (len + textLen <= this.endOffset) {
item.addClass('border');
} else {
// 再次拆分
domSub = true;
let textEnd = this.endOffset - len;
newDomArr.push(new Dom('', item.cls + ' border', item.text.substring(0, textEnd)));
newDomArr.push(new Dom('', item.cls, item.text.substring(textEnd, textLen)));
}
}
}
if (!domSub) {
newDomArr.push(item);
}
len += textLen;
});
this.dom = newDomArr;
} else {
let newDom = new Dom('', 'border', this.text.substring(0, this.endOffset));
let lastDom = new Dom('', '', this.text.substring(this.endOffset, this.text.length));
this.dom = [newDom, lastDom];
}
}
}
else if (this.startOffset > 0) {
if (this.dom.length > 0) {
// 有元素的时候
if (this.endOffset == this.text.length) {
// 选中后面的部分 xx-xxxxxx-xx
let len = 0;
let newDomArr = [];
this.dom.forEach(item => {
let domSub = false;
let textLen = item.text.length;
if (len + textLen > this.startOffset) {
if (!this.hasClass('border') && !item.hasClass('border')) {
if (len == this.startOffset) {
item.addClass('border');
} else {
// 再次拆分
domSub = true;
let textStart = this.startOffset - len;
newDomArr.push(new Dom('', item.cls, item.text.substring(0, textStart)));
newDomArr.push(new Dom('', item.cls + ' border', item.text.substring(textStart, textLen)));
}
}
}
if (!domSub) {
newDomArr.push(item);
}
len += textLen;
});
this.dom = newDomArr;
} else {
// 选中中间一部分 xx xx-xxxxxx-xx
let len = 0;
let newDomArr = [];
this.dom.forEach(item => {
let domSub = false;
let textLen = item.text.length;
len += textLen;
if (len > this.startOffset && len <= this.endOffset) {
if (!this.hasClass('border') && !item.hasClass('border')) {
let textEnd = textLen - (len - this.endOffset);
if (len - textLen <= this.startOffset) {
if (this.endOffset >= len) {
// 全选
item.addClass('border');
} else {
// 选择前面
domSub = true;
newDomArr.push(new Dom('', item.cls + ' border', item.text.substring(0, textEnd)));
newDomArr.push(new Dom('', item.cls, item.text.substring(textEnd, textLen)));
}
} else {
domSub = true;
let textStart = len - this.startOffset;
newDomArr.push(new Dom('', item.cls, item.text.substring(0, textStart)));
if (this.endOffset >= len) {
// 选择后面
newDomArr.push(new Dom('', item.cls + ' border', item.text.substring(textStart, textLen)));
} else {
// 选择中间
newDomArr.push(new Dom('', 'border', item.text.substring(textStart, textEnd)));
newDomArr.push(new Dom('', '', item.text.substring(textEnd, textLen)));
}
}
}
}
if (!domSub) {
newDomArr.push(item);
}
});
this.dom = newDomArr;
}
} else {
// 空的时候
this.dom.push(new Dom('', '', this.text.substring(0, this.startOffset)));
if (this.endOffset == this.text.length) {
// 选中后面的部分
this.dom.push(new Dom('', 'border', this.text.substring(this.startOffset, this.text.length)));
} else {
// 选中中间一部分
this.dom.push(new Dom('', 'border', this.text.substring(this.startOffset, this.endOffset)));
this.dom.push(new Dom('', '', this.text.substring(this.endOffset, this.text.length)));
}
}
}
this.startOffset = this.endOffset = -1;
},
// 回车事件处理
keyEnter(editorDom, editorRange) {
let nextText = '';
let oldText = this.text || '';
// 如果文字的中间位置点击,则把内容分割到两行
if (editorRange.startOffset < oldText.length) {
this.text = oldText.substring(0, editorRange.startOffset);
nextText = oldText.substring(editorRange.startOffset, oldText.length);
}
for (let i = 0; i < editorDom.length; i++) {
if (this == editorDom[i]) {
editorDom.splice(i + 1, 0, new Dom('text', this.cls, nextText));
break;
}
}
},
};
export default Dom;

View File

@@ -0,0 +1,42 @@
// 构造函数
function StyleRange(start, end, cls = '') {
cls = cls.trim();
this.start = start;
this.end = end;
this.cls = cls;
this.clsSet = new Set(cls.split(" "));
}
// 原型
StyleRange.prototype = {
constructor: StyleRange,
// 删除class
removeClass(cls = '') {
cls = cls.trim();
if (!this.hasClass(cls)) return;
this.clsSet.delete(cls);
this.cls = Array.from(this.clsSet).join(" ");
},
hasClass(cls = '') {
cls = cls.trim();
return this.clsSet.has(cls);
},
addClass(cls = '') {
cls = cls.trim();
if (this.hasClass(cls)) return;
this.clsSet.add(cls);
this.cls = Array.from(this.clsSet).join(" ");
},
classSameAll(compare) {
if (compare.clsSet.size != this.clsSet.size) return false;
let values = Array.from(compare.clsSet);
for (let i = 0; i < values.length; i++) {
if (!this.clsSet.has(values[i])) {
return false;
}
}
return true;
},
};
export default StyleRange;

View File

@@ -0,0 +1,50 @@
// 构造函数
function TextStyle() {
this.hn = '';// hn 标题1~9
this.strikeThrough = 0;// 删除线 1=有
this.backColor = '';// 背景色,颜色值
this.underline = 0;// 下划线 1=有
this.align = 0;// 对齐 1=左 2=中 3=右
this.bold = 0;// 加粗 1=有
}
// 原型
TextStyle.prototype = {
constructor: TextStyle,
notEq(compare) {
return !this.eq(compare);
},
eq(compare) {
if (!compare) return false;
if (this == compare) return true;
return this.hn == compare.hn
&& this.strikeThrough == compare.strikeThrough
&& this.backColor == compare.backColor
&& this.underline == compare.underline
&& this.align == compare.align
&& this.bold == compare.bold;
},
getCls() {
let clsSet = new Set();
if (this.hn) clsSet.add('head').add('head-' + this.hn);
if (this.strikeThrough) clsSet.add('strikethrough');
if (this.backColor) clsSet.add('backcolor-' + this.backColor);
if (this.underline) clsSet.add('underline');
if (this.align) clsSet.add('align-' + this.align);
if (this.bold) clsSet.add('bold');
return Array.from(clsSet).join(" ");
},
clone() {
let result = new TextStyle();
result.hn = this.hn;// hn 标题1~9
result.strikeThrough = this.strikeThrough;// 删除线 1=有
result.backColor = this.backColor;// 背景色,颜色值
result.underline = this.underline;// 下划线 1=有
result.align = this.align;// 对齐 1=左 2=中 3=右
result.bold = this.bold;// 加粗 1=有
return result;
},
};
export default TextStyle;

View File

@@ -5,7 +5,7 @@
</template>
<script>
import mgEditor from '../../components/editor/MgEditor'
import mgEditor from '../../components/editor2/MgEditor'
export default {
data() {