2020-02-11 17:51:04 +08:00
|
|
|
<template>
|
2020-05-17 16:53:21 +08:00
|
|
|
<div class="container" :class="{'container_night': nightMode}">
|
2020-05-02 11:50:26 +08:00
|
|
|
<el-container>
|
2020-05-17 22:19:45 +08:00
|
|
|
<el-header class="editor__header">
|
2020-05-02 11:50:26 +08:00
|
|
|
<editor-header
|
2020-07-13 00:26:29 +08:00
|
|
|
ref="header"
|
2020-05-04 11:02:13 +08:00
|
|
|
@refresh="onEditorRefresh"
|
2020-05-02 17:40:03 +08:00
|
|
|
@uploaded="uploaded"
|
2020-05-02 12:33:44 +08:00
|
|
|
@cssChanged="cssChanged"
|
2020-07-13 00:26:29 +08:00
|
|
|
@downLoad="downloadEditorContent"
|
|
|
|
@showCssEditor="showCssEditor = !showCssEditor"
|
2020-05-02 11:50:26 +08:00
|
|
|
@showAboutDialog="aboutDialogVisible = true"
|
|
|
|
@showDialogForm="dialogFormVisible = true"
|
2020-05-17 23:04:16 +08:00
|
|
|
@startCopy="isCoping = true, backLight = true"
|
|
|
|
@endCopy="endCopy"
|
2020-05-02 11:50:26 +08:00
|
|
|
/>
|
|
|
|
</el-header>
|
|
|
|
<el-main class="main-body">
|
2020-06-29 16:12:41 +08:00
|
|
|
<el-row class="main-section">
|
2020-07-13 00:26:29 +08:00
|
|
|
<el-col :span="12"
|
|
|
|
@contextmenu.prevent.native="openMenu($event)"
|
|
|
|
>
|
2020-05-02 11:50:26 +08:00
|
|
|
<textarea id="editor" type="textarea" placeholder="Your markdown text here." v-model="source">
|
|
|
|
</textarea>
|
|
|
|
</el-col>
|
2020-07-12 18:33:46 +08:00
|
|
|
<el-col
|
|
|
|
:span="12"
|
|
|
|
class="preview-wrapper"
|
|
|
|
id="preview"
|
|
|
|
ref="preview"
|
|
|
|
:class="{'preview-wrapper_night': nightMode && isCoping}"
|
|
|
|
>
|
2020-05-17 23:04:16 +08:00
|
|
|
<section id="output-wrapper" :class="{'output_night': nightMode && !backLight}">
|
2020-05-04 16:06:51 +08:00
|
|
|
<div class="preview">
|
2020-05-02 11:50:26 +08:00
|
|
|
<section id="output" v-html="output">
|
|
|
|
</section>
|
2020-05-17 22:19:45 +08:00
|
|
|
<div class="loading-mask" v-if="nightMode && isCoping">
|
|
|
|
<div class="loading__img"></div>
|
|
|
|
<span>正在生成</span>
|
|
|
|
</div>
|
2020-05-02 11:50:26 +08:00
|
|
|
</div>
|
|
|
|
</section>
|
|
|
|
</el-col>
|
2020-07-04 00:48:27 +08:00
|
|
|
<transition name="custom-classes-transition" enter-active-class="bounceInRight">
|
2020-07-13 00:26:29 +08:00
|
|
|
<el-col id="cssBox" :span="12" v-show="showCssEditor">
|
2020-05-02 11:50:26 +08:00
|
|
|
<textarea id="cssEditor" type="textarea" placeholder="Your custom css here.">
|
|
|
|
</textarea>
|
|
|
|
</el-col>
|
|
|
|
</transition>
|
|
|
|
</el-row>
|
|
|
|
</el-main>
|
|
|
|
</el-container>
|
2020-07-13 00:26:29 +08:00
|
|
|
<about-dialog v-model="aboutDialogVisible"/>
|
|
|
|
<insert-form-dialog v-model="dialogFormVisible"/>
|
|
|
|
<right-click-menu
|
|
|
|
v-model="rightClickMenuVisible"
|
|
|
|
:left="mouseLeft"
|
|
|
|
:top="mouseTop"
|
|
|
|
@menuTick="onMenuEvent"
|
|
|
|
/>
|
2020-05-02 11:50:26 +08:00
|
|
|
</div>
|
2020-02-11 17:51:04 +08:00
|
|
|
</template>
|
|
|
|
<script>
|
2020-05-02 12:33:44 +08:00
|
|
|
import fileApi from '../api/file';
|
2020-07-11 19:13:09 +08:00
|
|
|
import editorHeader from '../components/CodemirrorEditor/header';
|
|
|
|
import aboutDialog from '../components/CodemirrorEditor/aboutDialog';
|
|
|
|
import insertFormDialog from '../components/CodemirrorEditor/insertForm';
|
2020-07-13 00:26:29 +08:00
|
|
|
import rightClickMenu from '../components/CodemirrorEditor/rightClickMenu';
|
2020-05-02 12:33:44 +08:00
|
|
|
import {
|
|
|
|
css2json,
|
2020-07-13 00:26:29 +08:00
|
|
|
downLoadMD,
|
|
|
|
setFontSize,
|
|
|
|
isImageIllegal,
|
2020-05-01 21:30:25 +08:00
|
|
|
saveEditorContent,
|
2020-07-13 00:26:29 +08:00
|
|
|
customCssWithTemplate
|
2020-07-09 21:55:10 +08:00
|
|
|
} from '../assets/scripts/util'
|
2020-05-02 12:33:44 +08:00
|
|
|
|
|
|
|
require('codemirror/mode/javascript/javascript')
|
|
|
|
import {mapState, mapMutations} from 'vuex';
|
|
|
|
export default {
|
2020-05-01 21:30:25 +08:00
|
|
|
data() {
|
2020-05-02 11:50:26 +08:00
|
|
|
return {
|
2020-07-13 00:26:29 +08:00
|
|
|
showCssEditor: false,
|
2020-05-02 11:50:26 +08:00
|
|
|
aboutDialogVisible: false,
|
|
|
|
dialogFormVisible: false,
|
2020-07-13 00:26:29 +08:00
|
|
|
rightClickMenuVisible: false,
|
2020-05-17 22:19:45 +08:00
|
|
|
isCoping: false,
|
2020-05-17 23:04:16 +08:00
|
|
|
backLight: false,
|
2020-05-02 11:50:26 +08:00
|
|
|
timeout: null,
|
2020-05-03 11:02:27 +08:00
|
|
|
changeTimer: null,
|
2020-07-13 00:26:29 +08:00
|
|
|
source: '',
|
|
|
|
mouseLeft: 0,
|
|
|
|
mouseTop: 0
|
2020-05-02 11:50:26 +08:00
|
|
|
}
|
2020-02-11 17:51:04 +08:00
|
|
|
},
|
2020-05-01 21:30:25 +08:00
|
|
|
components: {
|
2020-07-13 00:26:29 +08:00
|
|
|
editorHeader,
|
|
|
|
aboutDialog,
|
|
|
|
insertFormDialog,
|
|
|
|
rightClickMenu
|
2020-02-11 17:51:04 +08:00
|
|
|
},
|
2020-05-01 21:30:25 +08:00
|
|
|
computed: {
|
|
|
|
...mapState({
|
|
|
|
wxRenderer: state=> state.wxRenderer,
|
|
|
|
output: state=> state.output,
|
|
|
|
editor: state=> state.editor,
|
|
|
|
cssEditor: state=> state.cssEditor,
|
2020-05-02 12:33:44 +08:00
|
|
|
currentSize: state=> state.currentSize,
|
|
|
|
currentColor: state=> state.currentColor,
|
2020-05-17 16:53:21 +08:00
|
|
|
nightMode: state=> state.nightMode
|
2020-02-11 17:51:04 +08:00
|
|
|
})
|
|
|
|
},
|
2020-05-01 21:30:25 +08:00
|
|
|
created() {
|
|
|
|
this.initEditorState()
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.initEditor()
|
|
|
|
this.initCssEditor()
|
2020-05-04 11:02:13 +08:00
|
|
|
this.onEditorRefresh()
|
2020-02-11 17:51:04 +08:00
|
|
|
})
|
|
|
|
},
|
2020-05-01 21:30:25 +08:00
|
|
|
methods: {
|
|
|
|
initEditor() {
|
|
|
|
this.initEditorEntity();
|
|
|
|
this.editor.on('change', (cm, e) => {
|
2020-05-03 11:02:27 +08:00
|
|
|
if (this.changeTimer) clearTimeout(this.changeTimer);
|
|
|
|
this.changeTimer = setTimeout(() => {
|
2020-05-04 11:02:13 +08:00
|
|
|
this.onEditorRefresh()
|
2020-05-03 11:02:27 +08:00
|
|
|
saveEditorContent(this.editor, '__editor_content')
|
2020-05-03 10:26:26 +08:00
|
|
|
}, 300);
|
2020-05-01 21:30:25 +08:00
|
|
|
});
|
2020-02-11 17:51:04 +08:00
|
|
|
|
2020-05-01 21:30:25 +08:00
|
|
|
// 粘贴上传图片并插入
|
|
|
|
this.editor.on('paste', (cm, e) => {
|
|
|
|
if (!(e.clipboardData && e.clipboardData.items)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for (let i = 0, len = e.clipboardData.items.length; i < len; ++i) {
|
|
|
|
let item = e.clipboardData.items[i]
|
|
|
|
if (item.kind === 'file') {
|
|
|
|
const pasteFile = item.getAsFile()
|
2020-05-02 11:50:26 +08:00
|
|
|
const checkImageResult = isImageIllegal(pasteFile);
|
2020-02-11 17:51:04 +08:00
|
|
|
|
2020-05-01 21:30:25 +08:00
|
|
|
if (checkImageResult) {
|
|
|
|
this.$message({
|
|
|
|
showClose: true,
|
|
|
|
message: checkImageResult,
|
|
|
|
type: 'error'
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let data = new FormData()
|
|
|
|
data.append('file', pasteFile)
|
|
|
|
|
|
|
|
fileApi.fileUpload(data).then(res => {
|
2020-05-02 16:28:00 +08:00
|
|
|
this.uploaded(res)
|
2020-05-01 21:30:25 +08:00
|
|
|
}).catch(err => {
|
|
|
|
console.log(err.message)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
initCssEditor() {
|
|
|
|
this.initCssEditorEntity();
|
|
|
|
// 自动提示
|
|
|
|
this.cssEditor.on('keyup', (cm, e) => {
|
|
|
|
if ((e.keyCode >= 65 && e.keyCode <= 90) || e.keyCode === 189) {
|
|
|
|
cm.showHint(e)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.cssEditor.on('update', (instance) => {
|
2020-07-13 00:26:29 +08:00
|
|
|
this.cssChanged();
|
2020-05-01 21:30:25 +08:00
|
|
|
saveEditorContent(this.cssEditor, '__css_content')
|
|
|
|
})
|
|
|
|
},
|
2020-05-02 12:33:44 +08:00
|
|
|
cssChanged() {
|
2020-07-13 00:26:29 +08:00
|
|
|
let json = css2json(this.cssEditor.getValue(0));
|
|
|
|
let theme = setFontSize(this.currentSize.replace('px', ''));
|
2020-05-02 12:33:44 +08:00
|
|
|
|
|
|
|
theme = customCssWithTemplate(json, this.currentColor, theme)
|
|
|
|
this.setWxRendererOptions({
|
|
|
|
theme: theme
|
|
|
|
});
|
2020-07-13 00:26:29 +08:00
|
|
|
this.onEditorRefresh();
|
2020-05-02 12:33:44 +08:00
|
|
|
},
|
2020-05-01 21:30:25 +08:00
|
|
|
// 图片上传结束
|
2020-07-13 00:26:29 +08:00
|
|
|
uploaded(response) {
|
2020-05-02 17:40:03 +08:00
|
|
|
if (response) {
|
|
|
|
if (response.success) {
|
|
|
|
// 上传成功,获取光标
|
2020-07-13 00:26:29 +08:00
|
|
|
const cursor = this.editor.getCursor();
|
|
|
|
const imageUrl = response.data;
|
|
|
|
const markdownImage = `![](${imageUrl})`;
|
2020-05-02 17:40:03 +08:00
|
|
|
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
2020-07-13 00:26:29 +08:00
|
|
|
this.editor.replaceSelection(`\n${markdownImage}\n`, cursor);
|
2020-05-02 17:40:03 +08:00
|
|
|
this.$message({
|
|
|
|
showClose: true,
|
|
|
|
message: '图片插入成功',
|
|
|
|
type: 'success'
|
2020-07-13 00:26:29 +08:00
|
|
|
});
|
|
|
|
this.onEditorRefresh();
|
2020-05-02 17:40:03 +08:00
|
|
|
} else {
|
|
|
|
// 上传失败
|
|
|
|
this.$message({
|
|
|
|
showClose: true,
|
|
|
|
message: response.message,
|
|
|
|
type: 'error'
|
2020-07-13 00:26:29 +08:00
|
|
|
});
|
2020-05-02 17:40:03 +08:00
|
|
|
}
|
2020-05-01 21:30:25 +08:00
|
|
|
} else {
|
|
|
|
this.$message({
|
|
|
|
showClose: true,
|
2020-05-02 17:40:03 +08:00
|
|
|
message: '上传图片未知异常',
|
2020-05-01 21:30:25 +08:00
|
|
|
type: 'error'
|
2020-07-13 00:26:29 +08:00
|
|
|
});
|
2020-05-01 21:30:25 +08:00
|
|
|
}
|
|
|
|
},
|
2020-07-13 00:26:29 +08:00
|
|
|
// 左右滚动
|
2020-05-01 21:30:25 +08:00
|
|
|
leftAndRightScroll() {
|
2020-07-12 18:33:46 +08:00
|
|
|
const scrollCB = text=> {
|
|
|
|
let source, target;
|
2020-02-13 21:50:27 +08:00
|
|
|
|
2020-07-12 18:33:46 +08:00
|
|
|
clearTimeout(this.timeout);
|
|
|
|
if (text === 'preview') {
|
|
|
|
source = this.$refs.preview.$el;
|
|
|
|
target = document.getElementsByClassName('CodeMirror-scroll')[0];
|
|
|
|
this.editor.off('scroll', editorScrollCB);
|
|
|
|
this.timeout = setTimeout(()=> {
|
|
|
|
this.editor.on('scroll', editorScrollCB);
|
|
|
|
}, 300);
|
|
|
|
} else if (text === 'editor') {
|
|
|
|
source = document.getElementsByClassName('CodeMirror-scroll')[0];
|
|
|
|
target = this.$refs.preview.$el;
|
|
|
|
target.removeEventListener("scroll", previewScrollCB, false);
|
|
|
|
this.timeout = setTimeout(()=> {
|
|
|
|
target.addEventListener("scroll", previewScrollCB, false);
|
|
|
|
}, 300);
|
|
|
|
}
|
|
|
|
|
|
|
|
let percentage = source.scrollTop / (source.scrollHeight - source.offsetHeight);
|
|
|
|
let height = percentage * (target.scrollHeight - target.offsetHeight);
|
2020-07-05 11:31:03 +08:00
|
|
|
|
2020-07-12 18:33:46 +08:00
|
|
|
target.scrollTo(0, height);
|
|
|
|
};
|
|
|
|
const editorScrollCB = ()=> {
|
|
|
|
scrollCB('editor');
|
|
|
|
};
|
|
|
|
const previewScrollCB = ()=> {
|
|
|
|
scrollCB('preview');
|
|
|
|
};
|
2020-02-13 21:50:27 +08:00
|
|
|
|
2020-07-12 18:33:46 +08:00
|
|
|
this.$refs.preview.$el.addEventListener("scroll", previewScrollCB, false);
|
|
|
|
this.editor.on('scroll', editorScrollCB);
|
2020-05-01 21:30:25 +08:00
|
|
|
},
|
2020-07-13 00:26:29 +08:00
|
|
|
// 更新编辑器
|
2020-05-04 11:02:13 +08:00
|
|
|
onEditorRefresh() {
|
|
|
|
this.editorRefresh();
|
2020-07-11 20:00:47 +08:00
|
|
|
setTimeout(()=> PR.prettyPrint(), 0);
|
2020-05-04 11:02:13 +08:00
|
|
|
},
|
2020-07-13 00:26:29 +08:00
|
|
|
// 复制结束
|
2020-05-17 23:04:16 +08:00
|
|
|
endCopy() {
|
|
|
|
this.backLight = false;
|
|
|
|
setTimeout(()=> {
|
|
|
|
this.isCoping = false;
|
|
|
|
}, 800);
|
2020-05-17 22:19:45 +08:00
|
|
|
},
|
2020-07-13 00:26:29 +08:00
|
|
|
// 下载编辑器内容到本地
|
|
|
|
downloadEditorContent() {
|
|
|
|
downLoadMD(this.editor.getValue(0));
|
|
|
|
},
|
|
|
|
// 右键菜单
|
|
|
|
openMenu(e) {
|
|
|
|
const menuMinWidth = 105;
|
|
|
|
const offsetLeft = this.$el.getBoundingClientRect().left;
|
|
|
|
const offsetWidth = this.$el.offsetWidth;
|
|
|
|
const maxLeft = offsetWidth - menuMinWidth;
|
|
|
|
const left = e.clientX - offsetLeft;
|
|
|
|
|
|
|
|
if (left > maxLeft) {
|
|
|
|
this.mouseLeft = maxLeft;
|
|
|
|
} else {
|
|
|
|
this.mouseLeft = left;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.mouseTop = e.clientY + 10;
|
|
|
|
this.rightClickMenuVisible = true;
|
|
|
|
},
|
|
|
|
onMenuEvent(type, info = {}) {
|
|
|
|
switch (type) {
|
|
|
|
case 'pageReset':
|
|
|
|
this.$refs.header.showResetConfirm = true;
|
|
|
|
break;
|
|
|
|
case 'insertPic':
|
|
|
|
this.uploaded(info);
|
|
|
|
break;
|
2020-07-13 20:34:36 +08:00
|
|
|
case 'downLoad':
|
|
|
|
this.downloadEditorContent();
|
|
|
|
break;
|
|
|
|
case 'insertTable':
|
|
|
|
this.dialogFormVisible = true;
|
2020-07-13 00:26:29 +08:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
2020-07-11 19:13:09 +08:00
|
|
|
...mapMutations([
|
|
|
|
'initEditorState',
|
|
|
|
'initEditorEntity',
|
|
|
|
'setWxRendererOptions',
|
|
|
|
'editorRefresh',
|
|
|
|
'initCssEditorEntity'])
|
2020-05-01 21:30:25 +08:00
|
|
|
},
|
|
|
|
mounted() {
|
2020-07-11 20:00:47 +08:00
|
|
|
setTimeout(() => {
|
2020-07-12 18:33:46 +08:00
|
|
|
this.leftAndRightScroll();
|
2020-07-11 20:00:47 +08:00
|
|
|
PR.prettyPrint()
|
|
|
|
}, 300);
|
2020-02-11 17:51:04 +08:00
|
|
|
}
|
2020-05-02 11:50:26 +08:00
|
|
|
}
|
2020-05-01 21:30:25 +08:00
|
|
|
|
2020-02-11 17:51:04 +08:00
|
|
|
</script>
|
2020-05-01 22:35:39 +08:00
|
|
|
<style lang="less" scoped>
|
2020-05-02 11:50:26 +08:00
|
|
|
.main-body {
|
2020-05-19 22:17:12 +08:00
|
|
|
padding-top: 12px;
|
2020-05-17 16:53:21 +08:00
|
|
|
overflow: hidden;
|
|
|
|
}
|
2020-05-17 18:16:26 +08:00
|
|
|
.el-main {
|
|
|
|
transition: all .3s;
|
|
|
|
padding: 0;
|
|
|
|
margin: 20px;
|
2020-05-19 22:17:12 +08:00
|
|
|
margin-top: 0;
|
2020-05-17 18:16:26 +08:00
|
|
|
}
|
2020-05-17 16:53:21 +08:00
|
|
|
.container {
|
|
|
|
transition: all .3s;
|
2020-05-01 22:35:39 +08:00
|
|
|
}
|
2020-05-17 23:04:16 +08:00
|
|
|
.preview {
|
|
|
|
transition: background 0s;
|
|
|
|
transition-delay: .2s;
|
2020-05-17 22:19:45 +08:00
|
|
|
}
|
|
|
|
.preview-wrapper_night {
|
|
|
|
overflow-y: inherit;
|
|
|
|
position: relative;
|
|
|
|
left: -3px;
|
|
|
|
.preview {
|
|
|
|
background-color: #fff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#output-wrapper {
|
|
|
|
position: relative;
|
|
|
|
}
|
|
|
|
.loading-mask {
|
|
|
|
position: absolute;
|
|
|
|
top: 50%;
|
|
|
|
left: 50%;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
width: 376px;
|
|
|
|
height: 101%;
|
|
|
|
padding-top: 1px;
|
|
|
|
font-size: 15px;
|
|
|
|
color: gray;
|
|
|
|
background-color: #1e1e1e;
|
|
|
|
.loading__img {
|
|
|
|
position: absolute;
|
|
|
|
left: 50%;
|
|
|
|
top: 330px;
|
|
|
|
width: 50px;
|
|
|
|
height: 50px;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
background: url('../assets/images/favicon.png') no-repeat;
|
|
|
|
background-size: cover;
|
|
|
|
}
|
|
|
|
span {
|
|
|
|
position: absolute;
|
|
|
|
left: 50%;
|
|
|
|
top: 390px;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
}
|
|
|
|
}
|
2020-07-04 00:48:27 +08:00
|
|
|
.bounceInRight {
|
|
|
|
animation-name: bounceInRight;
|
|
|
|
animation-duration: 1s;
|
|
|
|
animation-fill-mode: both;
|
|
|
|
}
|
|
|
|
@keyframes bounceInRight {
|
|
|
|
0%,60%,75%,90%,100% {
|
|
|
|
transition-timing-function: cubic-bezier(0.215,.610,.355,1.000)
|
|
|
|
}
|
|
|
|
0% {
|
|
|
|
opacity:0;
|
|
|
|
transform:translate3d(3000px,0,0)}
|
|
|
|
60% {
|
|
|
|
opacity:1;
|
|
|
|
transform:translate3d(-25px,0,0)
|
|
|
|
}
|
|
|
|
75% {
|
|
|
|
transform:translate3d(10px,0,0)
|
|
|
|
}
|
|
|
|
90% {
|
|
|
|
transform:translate3d(-5px,0,0)
|
|
|
|
}
|
|
|
|
100% {
|
|
|
|
transform:none
|
|
|
|
}
|
|
|
|
}
|
2020-02-11 17:51:04 +08:00
|
|
|
</style>
|
2020-07-04 00:48:27 +08:00
|
|
|
<style lang="less">
|
|
|
|
@import url('../assets/less/app.less');
|
|
|
|
@import url('../assets/less/style-mirror.css');
|
|
|
|
</style>
|
|
|
|
|