wiki调试修改,增加自研编辑器
This commit is contained in:
199
zyplayer-doc-ui/wiki-ui/src/components/editor/MgEditor.vue
Normal file
199
zyplayer-doc-ui/wiki-ui/src/components/editor/MgEditor.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<!--
|
||||
-- zyplayer-doc 自研编辑器,集各大厂商优秀的设计(拷它的css!拷它的图标!借鉴它的内容处理),造一个现代化、好用的轮子。
|
||||
-- 带着情怀开写,路途曲折,希望能完美呈现。
|
||||
-- 参考:飞书文档、语雀文档、wangEditor
|
||||
-- @author 暮光:城中城
|
||||
-- @since 2020-06-06
|
||||
-->
|
||||
<template>
|
||||
<div class="mg-editor-box">
|
||||
<div ref="mgEditor" class="mg-editor" contenteditable="true"></div>
|
||||
<div class="mg-editor-toolbar" :style="getMgEditorToolbarStyle(selectionInfo)">
|
||||
<span class="iconfont icon-h1" @click.stop="handleToolbarHn('h1')"></span>
|
||||
<span class="iconfont icon-h2" @click.stop="handleToolbarHn('h2')"></span>
|
||||
<span class="iconfont icon-h3" @click.stop="handleToolbarHn('h3')"></span>
|
||||
<span class="iconfont icon-hn"></span>
|
||||
<span class="iconfont icon-border"></span>
|
||||
<span class="iconfont icon-delete"></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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "./css/MgEditor.css";
|
||||
import "./css/MgEditorIconfont.css";
|
||||
import utilPast from './util/past';
|
||||
import utilBase from './util/util';
|
||||
import toolbarHn from "./toolbar/hn";
|
||||
import toolbarCommon from './toolbar/common';
|
||||
import toolbarCodeList from "./toolbar/inlineCodeList";
|
||||
|
||||
const $ = require("jquery");
|
||||
|
||||
export default {
|
||||
name: "mg-editor",
|
||||
data() {
|
||||
return {
|
||||
editor: {},
|
||||
selectionInfo: {
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
haveSelect: false,
|
||||
},
|
||||
compositionEnd: true,
|
||||
};
|
||||
},
|
||||
mounted: function () {
|
||||
this.editor = this.$refs.mgEditor;
|
||||
this.appendEditorLastLine();
|
||||
this.editor.addEventListener('mouseup', e => {
|
||||
// 不延时还能获取到选中的文字(选择文字,单击选中文字的中间)
|
||||
setTimeout(() => {
|
||||
let selectText = window.getSelection().getRangeAt(0).toString();
|
||||
this.selectionInfo = {
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY,
|
||||
haveSelect: !!selectText,
|
||||
};
|
||||
console.log("mouseup", selectText, e);
|
||||
}, 100);
|
||||
});
|
||||
document.body.addEventListener('click', e => {
|
||||
this.selectionInfo.haveSelect = false;
|
||||
});
|
||||
// 粘贴事件
|
||||
this.editor.addEventListener('paste', e => {
|
||||
e.preventDefault();
|
||||
let pasteText = utilPast.getPasteText(e);
|
||||
let pasteHtml = utilPast.getPasteHtml(e, true, true);
|
||||
document.execCommand('insertHTML', false, pasteText);
|
||||
setTimeout(() => this.handleUserInput(e), 300);
|
||||
});
|
||||
this.editor.addEventListener('compositionstart', () => {
|
||||
this.compositionEnd = false;
|
||||
});
|
||||
this.editor.addEventListener('compositionend', () => {
|
||||
this.compositionEnd = true;
|
||||
});
|
||||
this.editor.addEventListener('click', e => {
|
||||
this.handleUserInput(e);
|
||||
});
|
||||
this.editor.addEventListener('keyup', e => {
|
||||
this.handleUserInput(e);
|
||||
});
|
||||
this.editor.addEventListener('keydown', e => {
|
||||
if (e.which == 13) {
|
||||
let nowContainerOrigin = window.getSelection().getRangeAt(0).commonAncestorContainer;
|
||||
let nowContainer = this.findListCodeNode(nowContainerOrigin);
|
||||
// 如果是回车,而且是code行,修改行号
|
||||
if (nowContainer != null) {
|
||||
e.preventDefault();
|
||||
let newDomText = '';
|
||||
let innerText = nowContainer.innerText;
|
||||
if (!!innerText) {
|
||||
let startOffset = window.getSelection().getRangeAt(0).startOffset;
|
||||
nowContainer.innerText = innerText.substring(0, startOffset);
|
||||
newDomText = innerText.substring(startOffset, innerText.length);
|
||||
}
|
||||
let nowNum = parseInt(nowContainer.getAttribute("start")) + 1;
|
||||
let codeEle = $(`<code start='${nowNum}'>${newDomText}</code>`)[0];
|
||||
$(nowContainer).after(codeEle);
|
||||
utilBase.moveSelectionRange(codeEle, 0, 0);
|
||||
// 改后面的序号
|
||||
$(codeEle).nextAll().each(function () {
|
||||
$(this).attr("start", ++nowNum);
|
||||
});
|
||||
}
|
||||
let handleClassDom = toolbarCommon.getSelectionContainer(nowContainerOrigin);
|
||||
// 回车时上一行是最后一行,取消标记,防止复制了
|
||||
if (this.domHaveClass(handleClassDom, "locate")) {
|
||||
handleClassDom.classList.remove("locate");
|
||||
setTimeout(() => this.editor.lastChild.classList.add("locate"), 100);
|
||||
}
|
||||
// 在特定元素里面按回车,如果任由系统则会复制当前行的class等内容,所以自己处理下,插入一行空白内容
|
||||
if (toolbarCommon.domHaveClass(handleClassDom, 'head')) {
|
||||
let domInnerText = handleClassDom.innerText;
|
||||
let selectRange = window.getSelection().getRangeAt(0);
|
||||
// 光标在行的最后才执行此操作,否则用系统默认的
|
||||
if (selectRange.startOffset == selectRange.endOffset && selectRange.endOffset == domInnerText.length) {
|
||||
e.preventDefault();
|
||||
let newEle = $(`<div><br/></div>`)[0];
|
||||
$(handleClassDom).after(newEle);
|
||||
utilBase.moveSelectionRange(newEle, 0, 0);
|
||||
this.selectionInfo.haveSelect = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
handleUserInput(e) {
|
||||
if (!this.compositionEnd) return;
|
||||
// 判断输入内容,如果是markdown语法则处理成目标样式
|
||||
let nowContainer = window.getSelection().getRangeAt(0).commonAncestorContainer;
|
||||
let lineText = nowContainer.data;
|
||||
if (lineText == '```') {
|
||||
toolbarCodeList.createCodeListBlock();
|
||||
}
|
||||
// 判断是否是在最后一行输入,如果是就再在最后加一行
|
||||
let locate = this.findParentClassDom(nowContainer, 'locate');
|
||||
if (locate != null && !!locate.innerText && !!locate.innerText.trim()) {
|
||||
locate.classList.remove("locate");
|
||||
this.appendEditorLastLine();
|
||||
} else if (!this.domHaveClass(this.editor.lastChild, "locate")) {
|
||||
this.editor.lastChild.classList.add("locate");
|
||||
}
|
||||
},
|
||||
findListCodeNode(container) {
|
||||
// 如果自己是root,创建一个div放最后
|
||||
if (this.isRootBox(container)) {
|
||||
return null;
|
||||
}
|
||||
let parentNode = container.parentNode;
|
||||
if (this.domHaveClass(parentNode, "list-code")) {
|
||||
return container;
|
||||
}
|
||||
return this.findListCodeNode(parentNode);
|
||||
},
|
||||
findParentClassDom(container, cls) {
|
||||
if (!container || this.isRootBox(container)) return null;
|
||||
if (this.domHaveClass(container, cls)) return container;
|
||||
return this.findParentClassDom(container.parentNode, cls);
|
||||
},
|
||||
appendEditorLastLine() {
|
||||
// 最后再加一行,防止光标没法到最后了
|
||||
$(this.editor).append("<div class='locate'><br/></div>");
|
||||
},
|
||||
isRootBox(container) {
|
||||
return this.domHaveClass(container, "mg-editor");
|
||||
},
|
||||
domHaveClass(container, cls) {
|
||||
return container && container.classList && container.classList.contains(cls);
|
||||
},
|
||||
handleToolbarHn(hn) {
|
||||
toolbarHn.toolbarHn(hn);
|
||||
this.selectionInfo.haveSelect = false;
|
||||
},
|
||||
getMgEditorToolbarStyle(selectionInfo) {
|
||||
let style = {};
|
||||
style.display = selectionInfo.haveSelect ? 'block' : 'none';
|
||||
if (selectionInfo.haveSelect) {
|
||||
let top = selectionInfo.pageY - 20 - 50;
|
||||
if (top < 0) top = selectionInfo.pageY + 20;
|
||||
let left = selectionInfo.pageX - 220;
|
||||
if (left < 10) left = 10;
|
||||
style.top = top + 'px';
|
||||
style.left = left + 'px';
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
147
zyplayer-doc-ui/wiki-ui/src/components/editor/css/MgEditor.css
Normal file
147
zyplayer-doc-ui/wiki-ui/src/components/editor/css/MgEditor.css
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
.mg-editor {
|
||||
padding: 10px;
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.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-*/
|
||||
@@ -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-border: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";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
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;
|
||||
},
|
||||
isRootBox(container) {
|
||||
return this.domHaveClass(container, "mg-editor");
|
||||
},
|
||||
domHaveClass(container, cls) {
|
||||
return container && container.classList && container.classList.contains(cls);
|
||||
},
|
||||
}
|
||||
|
||||
20
zyplayer-doc-ui/wiki-ui/src/components/editor/toolbar/hn.js
Normal file
20
zyplayer-doc-ui/wiki-ui/src/components/editor/toolbar/hn.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import toolbarCommon from './common';
|
||||
|
||||
const $ = require("jquery");
|
||||
|
||||
export default {
|
||||
toolbarHn(hn) {
|
||||
// 找到内容,生成div把每一行包住
|
||||
let container = toolbarCommon.getSelectionContainer(true);
|
||||
if (container == null) return;
|
||||
let innerTextArr = container.innerText.split("\n");
|
||||
if (innerTextArr.length >= 1) {
|
||||
innerTextArr.filter(item => !!item).forEach(item => {
|
||||
$(container).before(`<div class="head head-${hn}">${item}</div>`);
|
||||
});
|
||||
}
|
||||
// 最后把当前行移出掉
|
||||
container.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import toolbarCommon from './common';
|
||||
import utilBase from '../util/util';
|
||||
|
||||
const $ = require("jquery");
|
||||
|
||||
export default {
|
||||
createCodeListBlock() {
|
||||
let containerNow = window.getSelection().getRangeAt(0).commonAncestorContainer;
|
||||
let divEle = this.findRootDomOnNotCode(containerNow);
|
||||
if (divEle == null) return;
|
||||
divEle.classList.add("list-code");
|
||||
let codeEle = $(`<code start='1'></code>`)[0];
|
||||
$(divEle).append(codeEle);
|
||||
// 光标放到代码框里
|
||||
utilBase.moveSelectionRange(codeEle, 0, 0);
|
||||
},
|
||||
findRootDomOnNotCode(container) {
|
||||
container = container.nodeType == 3 ? container.parentNode : container;
|
||||
// 如果自己是root,创建一个div放最后
|
||||
if (toolbarCommon.isRootBox(container)) {
|
||||
let lastEle = document.createElement("div");
|
||||
container.appendChild(lastEle);
|
||||
return lastEle;
|
||||
}
|
||||
// 如果父元素是root,添加一个新的div,再把自己移除
|
||||
let parentNode = container.parentNode;
|
||||
if (toolbarCommon.isRootBox(parentNode)) {
|
||||
let lastEle = document.createElement("div");
|
||||
parentNode.insertBefore(lastEle, container);
|
||||
container.remove();
|
||||
return lastEle;
|
||||
}
|
||||
// 在非根结点里面输入```无效
|
||||
return null;
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="mg-editor-box">
|
||||
<div ref="mgEditor" class="mg-editor" contenteditable="true"></div>
|
||||
<div class="mg-editor-toolbar" :style="getMgEditorToolbarStyle(selectionInfo)">
|
||||
<span class="iconfont icon-h1" @click.stop="toolbarHn('h1')"></span>
|
||||
<span class="iconfont icon-h2" @click.stop="toolbarHn('h2')"></span>
|
||||
<span class="iconfont icon-h3" @click.stop="toolbarHn('h3')"></span>
|
||||
<span class="iconfont icon-hn"></span>
|
||||
<span class="iconfont icon-border"></span>
|
||||
<span class="iconfont icon-delete"></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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "./MgEditor.css";
|
||||
import "./MgEditorIconfont.css";
|
||||
const $ = require("jquery")(window);
|
||||
|
||||
export default {
|
||||
name: "mg-editor",
|
||||
data() {
|
||||
return {
|
||||
editor: {},
|
||||
selectionInfo: {
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
haveSelect: false,
|
||||
},
|
||||
compositionEnd: true,
|
||||
};
|
||||
},
|
||||
mounted: function () {
|
||||
this.editor = this.$refs.mgEditor;
|
||||
this.appendEditorLastLine();
|
||||
this.editor.addEventListener('mouseup', e => {
|
||||
// 不延时还能获取到选中的文字(选择文字,单击选中文字的中间)
|
||||
setTimeout(() => {
|
||||
let selectText = window.getSelection().getRangeAt(0).toString();
|
||||
this.selectionInfo = {
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY,
|
||||
haveSelect: !!selectText,
|
||||
};
|
||||
console.log("mouseup", selectText, e);
|
||||
}, 100);
|
||||
});
|
||||
document.body.addEventListener('click', e => {
|
||||
this.selectionInfo.haveSelect = false;
|
||||
});
|
||||
// 粘贴事件
|
||||
this.editor.addEventListener('paste', e => {
|
||||
let cbd = e.clipboardData;
|
||||
if (!(e.clipboardData && e.clipboardData.items)) {
|
||||
return;
|
||||
}
|
||||
debugger
|
||||
for (var i = 0; i < cbd.items.length; i++) {
|
||||
var item = cbd.items[i];
|
||||
}
|
||||
setTimeout(() => this.handleUserInput(e), 300);
|
||||
});
|
||||
this.editor.addEventListener('compositionstart', () => {
|
||||
this.compositionEnd = false;
|
||||
});
|
||||
this.editor.addEventListener('compositionend', () => {
|
||||
this.compositionEnd = true;
|
||||
});
|
||||
this.editor.addEventListener('click', e => {
|
||||
this.handleUserInput(e);
|
||||
});
|
||||
this.editor.addEventListener('keyup', e => {
|
||||
this.handleUserInput(e);
|
||||
});
|
||||
this.editor.addEventListener('keydown', e => {
|
||||
if (e.which == 13) {
|
||||
let nowContainerOrigin = window.getSelection().getRangeAt(0).commonAncestorContainer;
|
||||
let nowContainer = this.findListCodeNode(nowContainerOrigin);
|
||||
// 如果是回车,而且是code行,修改行号
|
||||
if (nowContainer != null) {
|
||||
e.preventDefault();
|
||||
let parentNode = nowContainer.parentNode;
|
||||
let nextSibling = nowContainer.nextSibling;
|
||||
// 在最后加一行
|
||||
let nowNum = parseInt(nowContainer.getAttribute("start")) + 1;
|
||||
// let codeEle = document.createElement("code");
|
||||
// codeEle.setAttribute("start", nowNum);
|
||||
// if (parentNode.lastChild == nowContainer) {
|
||||
// parentNode.appendChild(codeEle);
|
||||
// } else {
|
||||
// parentNode.insertBefore(codeEle, nextSibling);
|
||||
// }
|
||||
// 修改内容
|
||||
|
||||
let innerText = nowContainer.innerText;
|
||||
if (!!innerText) {
|
||||
let startOffset = window.getSelection().getRangeAt(0).startOffset;
|
||||
nowContainer.innerText = innerText.substring(0, startOffset);
|
||||
codeEle.innerText = innerText.substring(startOffset, innerText.length);
|
||||
}
|
||||
$(nowContainer).after("<code start='" + nowNum + "'></code>");
|
||||
// 光标下移一行
|
||||
let rangeNew = document.createRange();
|
||||
rangeNew.setStart(codeEle, 0);
|
||||
rangeNew.setEnd(codeEle, 0);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(rangeNew);
|
||||
// 改后面的序号
|
||||
for (; nextSibling; nextSibling = nextSibling.nextSibling) {
|
||||
if (nextSibling.nodeType === 1) {
|
||||
nextSibling.setAttribute("start", ++nowNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 回车时上一行是最后一行,取消标记
|
||||
if (this.domHaveClass(nowContainerOrigin, "locate")) {
|
||||
nowContainerOrigin.classList.remove("locate");
|
||||
setTimeout(() => this.editor.lastChild.classList.add("locate"), 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
handleUserInput(e) {
|
||||
if (!this.compositionEnd) return;
|
||||
// 判断输入内容,如果是markdown语法则处理成目标样式
|
||||
let nowContainer = window.getSelection().getRangeAt(0).commonAncestorContainer;
|
||||
let parentNode = nowContainer.parentNode;
|
||||
let lineText = nowContainer.data;
|
||||
if (lineText == '```') {
|
||||
let containerNow = window.getSelection().getRangeAt(0).commonAncestorContainer;
|
||||
let divEle = this.findRootDomOnNotCode(containerNow);
|
||||
if (divEle == null) {
|
||||
return;
|
||||
}
|
||||
divEle.classList.add("list-code");
|
||||
let codeEle = document.createElement("code");
|
||||
codeEle.setAttribute("start", "1");
|
||||
divEle.appendChild(codeEle);
|
||||
// 光标放到代码框里
|
||||
let rangeNew = document.createRange();
|
||||
rangeNew.setStart(codeEle, 0);
|
||||
rangeNew.setEnd(codeEle, 0);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(rangeNew);
|
||||
}
|
||||
// 判断是否是在最后一行输入,如果是就再在最后加一行
|
||||
let locate = this.findParentClassDom(nowContainer, 'locate');
|
||||
if (locate != null && !!locate.innerText && !!locate.innerText.trim()) {
|
||||
locate.classList.remove("locate");
|
||||
this.appendEditorLastLine();
|
||||
} else if (!this.domHaveClass(this.editor.lastChild, "locate")) {
|
||||
this.editor.lastChild.classList.add("locate");
|
||||
}
|
||||
},
|
||||
findRootDomOnNotCode(container) {
|
||||
// 如果自己是root,创建一个div放最后
|
||||
if (this.isRootBox(container)) {
|
||||
let lastEle = document.createElement("div");
|
||||
this.editor.appendChild(lastEle);
|
||||
return lastEle;
|
||||
}
|
||||
// 如果父元素是root,添加一个新的div,再把自己移除
|
||||
let parentNode = container.parentNode;
|
||||
if (this.isRootBox(parentNode)) {
|
||||
let lastEle = document.createElement("div");
|
||||
parentNode.insertBefore(lastEle, container);
|
||||
container.remove();
|
||||
return lastEle;
|
||||
}
|
||||
// 在code里面输入```无效
|
||||
if (this.domHaveClass(parentNode, "list-code")) {
|
||||
return null;
|
||||
}
|
||||
return this.findRootDomOnNotCode(parentNode);
|
||||
},
|
||||
findListCodeNode(container) {
|
||||
// 如果自己是root,创建一个div放最后
|
||||
if (this.isRootBox(container)) {
|
||||
return null;
|
||||
}
|
||||
let parentNode = container.parentNode;
|
||||
if (this.domHaveClass(parentNode, "list-code")) {
|
||||
return container;
|
||||
}
|
||||
return this.findListCodeNode(parentNode);
|
||||
},
|
||||
findParentClassDom(container, cls) {
|
||||
if (!container || this.isRootBox(container)) return null;
|
||||
if (this.domHaveClass(container, cls)) return container;
|
||||
return this.findParentClassDom(container.parentNode, cls);
|
||||
},
|
||||
appendEditorLastLine() {
|
||||
// 最后再加一行,防止光标没法到最后了
|
||||
let lastEle = document.createElement("div");
|
||||
lastEle.classList.add("locate");
|
||||
lastEle.appendChild(document.createElement("br"));
|
||||
this.editor.appendChild(lastEle);
|
||||
},
|
||||
isRootBox(container) {
|
||||
return this.domHaveClass(container, "mg-editor");
|
||||
},
|
||||
domHaveClass(container, cls) {
|
||||
return container && container.classList && container.classList.contains(cls);
|
||||
},
|
||||
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;
|
||||
},
|
||||
toolbarHn(hn) {
|
||||
let container = this.getSelectionContainer();
|
||||
if (container == null) return;
|
||||
document.execCommand("formatBlock", false, "<" + hn + ">");
|
||||
container = this.getSelectionContainer(true);
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
let innerTextArr = container.innerText.split("\n");
|
||||
if (innerTextArr.length > 1) {
|
||||
innerTextArr.forEach(item => {
|
||||
if (!!item) {
|
||||
let newEle = document.createElement(hn);
|
||||
newEle.innerText = item;
|
||||
container.parentNode.insertBefore(newEle, container);
|
||||
}
|
||||
});
|
||||
container.remove();
|
||||
}
|
||||
},
|
||||
getMgEditorToolbarStyle(selectionInfo) {
|
||||
let style = {};
|
||||
style.display = selectionInfo.haveSelect ? 'block' : 'none';
|
||||
if (selectionInfo.haveSelect) {
|
||||
let top = selectionInfo.pageY - 20 - 50;
|
||||
if (top < 0) top = selectionInfo.pageY + 20;
|
||||
let left = selectionInfo.pageX - 220;
|
||||
if (left < 10) left = 10;
|
||||
style.top = top + 'px';
|
||||
style.left = left + 'px';
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
87
zyplayer-doc-ui/wiki-ui/src/components/editor/util/past.js
Normal file
87
zyplayer-doc-ui/wiki-ui/src/components/editor/util/past.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 本文件内容拷贝自:https://github.com/wangfupeng1988/wangEditor
|
||||
* zyplayer-doc在此基础上有稍作修改
|
||||
*/
|
||||
|
||||
import utilBase from './util'
|
||||
|
||||
export default {
|
||||
// 获取粘贴的纯文本
|
||||
getPasteText(e) {
|
||||
const clipboardData = e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData);
|
||||
let pasteText;
|
||||
if (clipboardData == null) {
|
||||
pasteText = window.clipboardData && window.clipboardData.getData('text');
|
||||
} else {
|
||||
pasteText = clipboardData.getData('text/plain');
|
||||
}
|
||||
let resultStr = '';
|
||||
let htmlStr = utilBase.replaceHtmlSymbol(pasteText);
|
||||
htmlStr.split('<br/>').forEach(item => {
|
||||
if (resultStr) resultStr += '<br/>';
|
||||
resultStr += item.trim();
|
||||
});
|
||||
return resultStr;
|
||||
},
|
||||
// 获取粘贴的html
|
||||
getPasteHtml(e, filterStyle, ignoreImg) {
|
||||
const clipboardData = e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData);
|
||||
let pasteText, pasteHtml;
|
||||
if (clipboardData == null) {
|
||||
pasteText = window.clipboardData && window.clipboardData.getData('text');
|
||||
} else {
|
||||
pasteText = clipboardData.getData('text/plain');
|
||||
pasteHtml = clipboardData.getData('text/html');
|
||||
}
|
||||
if (!pasteHtml && pasteText) {
|
||||
pasteHtml = utilBase.replaceHtmlSymbol(pasteText);
|
||||
}
|
||||
if (!pasteHtml) {
|
||||
return;
|
||||
}
|
||||
// 过滤word中状态过来的无用字符
|
||||
const docSplitHtml = pasteHtml.split('</html>');
|
||||
if (docSplitHtml.length === 2) {
|
||||
pasteHtml = docSplitHtml[0];
|
||||
}
|
||||
// 过滤无用标签
|
||||
pasteHtml = pasteHtml.replace(/<(meta|script|link).+?>/igm, '');
|
||||
// 去掉注释
|
||||
pasteHtml = pasteHtml.replace(/<!--.*?-->/mg, '');
|
||||
// 过滤 data-xxx 属性
|
||||
pasteHtml = pasteHtml.replace(/\s?data-.+?=('|").+?('|")/igm, '');
|
||||
if (ignoreImg) {
|
||||
// 忽略图片
|
||||
pasteHtml = pasteHtml.replace(/<img.+?>/igm, '');
|
||||
}
|
||||
if (filterStyle) {
|
||||
// 过滤样式
|
||||
pasteHtml = pasteHtml.replace(/\s?(class|style)=('|").*?('|")/igm, '');
|
||||
} else {
|
||||
// 保留样式
|
||||
pasteHtml = pasteHtml.replace(/\s?class=('|").*?('|")/igm, '');
|
||||
}
|
||||
return pasteHtml
|
||||
},
|
||||
// 获取粘贴的图片文件
|
||||
getPasteImgs(e) {
|
||||
const result = [];
|
||||
const txt = utilBase.getPasteText(e);
|
||||
if (txt) {
|
||||
// 有文字,就忽略图片
|
||||
return result;
|
||||
}
|
||||
const clipboardData = e.clipboardData || (e.originalEvent && e.originalEvent.clipboardData) || {};
|
||||
const items = clipboardData.items;
|
||||
if (!items) {
|
||||
return result;
|
||||
}
|
||||
utilBase.objForEach(items, (key, value) => {
|
||||
const type = value.type;
|
||||
if (/image/i.test(type)) {
|
||||
result.push(value.getAsFile());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
}
|
||||
72
zyplayer-doc-ui/wiki-ui/src/components/editor/util/util.js
Normal file
72
zyplayer-doc-ui/wiki-ui/src/components/editor/util/util.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 本文件内容拷贝自:https://github.com/wangfupeng1988/wangEditor
|
||||
* zyplayer-doc在此基础上有稍作修改
|
||||
*/
|
||||
|
||||
// 和 UA 相关的属性
|
||||
|
||||
export default {
|
||||
_ua: navigator.userAgent,
|
||||
// 是否 webkit
|
||||
isWebkit: function () {
|
||||
const reg = /webkit/i;
|
||||
return reg.test(this._ua);
|
||||
},
|
||||
// 是否 IE
|
||||
isIE: function () {
|
||||
return 'ActiveXObject' in window;
|
||||
},
|
||||
// 遍历对象
|
||||
objForEach(obj, fn) {
|
||||
let key, result;
|
||||
for (key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
result = fn.call(obj, key, obj[key]);
|
||||
if (result === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 遍历类数组
|
||||
arrForEach(fakeArr, fn) {
|
||||
let i, item, result;
|
||||
const length = fakeArr.length || 0;
|
||||
for (i = 0; i < length; i++) {
|
||||
item = fakeArr[i];
|
||||
result = fn.call(fakeArr, item, i);
|
||||
if (result === false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取随机数
|
||||
getRandom(prefix) {
|
||||
return prefix + Math.random().toString().slice(2);
|
||||
},
|
||||
// 替换 html 特殊字符
|
||||
replaceHtmlSymbol(html) {
|
||||
if (!html) return '';
|
||||
return html.replace(/</gm, '<')
|
||||
.replace(/>/gm, '>')
|
||||
.replace(/"/gm, '"')
|
||||
.replace(/(\r\n|\r|\n)/g, '<br/>');
|
||||
},
|
||||
// 返回百分比的格式
|
||||
percentFormat(number) {
|
||||
number = (parseInt(number * 100));
|
||||
return number + '%';
|
||||
},
|
||||
// 判断是不是 function
|
||||
isFunction(fn) {
|
||||
return typeof fn === 'function';
|
||||
},
|
||||
moveSelectionRange(dom, start, end) {
|
||||
let rangeNew = document.createRange();
|
||||
rangeNew.setStart(dom, start);
|
||||
rangeNew.setEnd(dom, end);
|
||||
window.getSelection().removeAllRanges();
|
||||
window.getSelection().addRange(rangeNew);
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user