add: add right-side menu

This commit is contained in:
JimQing 2020-07-13 00:26:29 +08:00
parent 67c38d8127
commit 326192edf0
6 changed files with 246 additions and 47 deletions

View File

@ -214,3 +214,16 @@ export function fixCodeWhiteSpace(value = 'pre') {
}) })
} }
} }
export function downLoadMD(doc) {
let downLink = document.createElement('a');
downLink.download = 'content.md';
downLink.style.display = 'none';
let blob = new Blob([doc]);
downLink.href = URL.createObjectURL(blob);
document.body.appendChild(downLink);
downLink.click();
document.body.removeChild(downLink);
}

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog title="关于" class="about__dialog" :visible="aboutDialogVisible" @close="$emit('close')" width="30%" center> <el-dialog title="关于" class="about__dialog" :visible="value" @close="$emit('input', false)" width="30%" center>
<div style="text-align: center;"> <div style="text-align: center;">
<h3>一款高度简洁的微信 Markdown 编辑器</h3> <h3>一款高度简洁的微信 Markdown 编辑器</h3>
</div> </div>
@ -17,7 +17,7 @@
<script> <script>
export default { export default {
props: { props: {
aboutDialogVisible: { value: {
type: Boolean, type: Boolean,
default: false default: false
} }

View File

@ -11,7 +11,7 @@
</el-upload> </el-upload>
<!-- 下载文本文档 --> <!-- 下载文本文档 -->
<el-tooltip class="header__item" :effect="effect" content="下载编辑框Markdown文档" placement="bottom-start"> <el-tooltip class="header__item" :effect="effect" content="下载编辑框Markdown文档" placement="bottom-start">
<i class="el-icon-download" size="medium" @click="downloadEditorContent"></i> <i class="el-icon-download" size="medium" @click="$emit('downLoad')"></i>
</el-tooltip> </el-tooltip>
<!-- 页面重置 --> <!-- 页面重置 -->
<el-tooltip class="header__item" :effect="effect" content="重置页面" placement="bottom-start"> <el-tooltip class="header__item" :effect="effect" content="重置页面" placement="bottom-start">
@ -71,10 +71,11 @@
<script> <script>
import { import {
setColorWithCustomTemplate, downLoadMD,
setFontSize, setFontSize,
isImageIllegal, isImageIllegal,
fixCodeWhiteSpace fixCodeWhiteSpace,
setColorWithCustomTemplate
} from '../../assets/scripts/util' } from '../../assets/scripts/util'
import fileApi from '../../api/file'; import fileApi from '../../api/file';
import { import {
@ -205,7 +206,7 @@ export default {
}, },
// CSS // CSS
async customStyle () { async customStyle () {
this.$emit('showBox'); this.$emit('showCssEditor');
this.$nextTick(() => { this.$nextTick(() => {
if(!this.cssEditor) { if(!this.cssEditor) {
this.cssEditor.refresh() this.cssEditor.refresh()
@ -240,17 +241,6 @@ export default {
this.showResetConfirm = false; this.showResetConfirm = false;
this.editor.focus() this.editor.focus()
}, },
//
downloadEditorContent () {
let downLink = document.createElement('a')
downLink.download = 'content.md'
downLink.style.display = 'none'
let blob = new Blob([this.editor.getValue(0)])
downLink.href = URL.createObjectURL(blob)
document.body.appendChild(downLink)
downLink.click()
document.body.removeChild(downLink)
},
...mapMutations(['clearEditorToDefault','setCurrentColor', 'setCiteStatus', 'themeChanged', ...mapMutations(['clearEditorToDefault','setCurrentColor', 'setCiteStatus', 'themeChanged',
'setCurrentFont', 'setCurrentSize', 'setCssEditorValue', 'setWxRendererOptions']) 'setCurrentFont', 'setCurrentSize', 'setCssEditorValue', 'setWxRendererOptions'])
}, },

View File

@ -1,5 +1,5 @@
<template> <template>
<el-dialog title="插入表格" class="insert__dialog" :visible="dialogFormVisible" @close="$emit('close')"> <el-dialog title="插入表格" class="insert__dialog" :visible="value" @close="$emit('input', false)">
<el-form class="insert__form" :model="config.form"> <el-form class="insert__form" :model="config.form">
<el-form-item label="行数(表头不计入行数)"> <el-form-item label="行数(表头不计入行数)">
<el-input v-model="config.form.rows"></el-input> <el-input v-model="config.form.rows"></el-input>
@ -9,7 +9,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button :type="btnType" plain @click="$emit('close')"> </el-button> <el-button :type="btnType" plain @click="$emit('input', false)"> </el-button>
<el-button :type="btnType" @click="insertTable" plain> </el-button> <el-button :type="btnType" @click="insertTable" plain> </el-button>
</div> </div>
</el-dialog> </el-dialog>
@ -20,7 +20,7 @@ import config from '../../assets/scripts/config'
import {mapState, mapMutations} from 'vuex'; import {mapState, mapMutations} from 'vuex';
export default { export default {
props: { props: {
dialogFormVisible: { value: {
type: Boolean, type: Boolean,
default: false default: false
} }
@ -63,7 +63,7 @@ export default {
} }
this.editor.replaceSelection(`\n${table}\n`, cursor) this.editor.replaceSelection(`\n${table}\n`, cursor)
this.$emit('close') this.$emit('input', false)
this.editorRefresh() this.editorRefresh()
}, },
...mapMutations(['editorRefresh']) ...mapMutations(['editorRefresh'])

View File

@ -0,0 +1,142 @@
<template>
<ul v-show="value" id="menu" class="menu" :style="`left: ${left}px;top: ${top}px;`">
<li v-for="item of list" :key="item.key" class="menu_item">
<el-upload v-if="item.key === 'insertPic'" action="" class="li__upload"
:show-file-list="false" :multiple="true" accept=".jpg,.jpeg,.png,.gif" name="file"
:before-upload="beforeUpload">
<span>{{item.text}}</span>
</el-upload>
<span v-else @click="$emit('menuTick', item.key)">{{item.text}}</span>
</li>
</ul>
</template>
<script>
import {
isImageIllegal,
} from '../../assets/scripts/util';
import fileApi from '../../api/file';
export default {
props: {
value: {
type: Boolean,
default: false
},
top: {
type: Number,
default: 0
},
left: {
type: Number,
default: 0
}
},
data() {
return {
list: [
{
text: '上传图片',
key: 'insertPic'
},
{
text: '插入表格',
key: 'insertTable'
},
{
text: '页面重置',
key: 'pageReset'
},
{
text: '下载MD文档',
key: 'downLoad'
}
]
}
},
methods: {
closeCB() {
this.$emit('input', false);
},
// el-upload
//
beforeUpload(file) {
const checkImageResult = isImageIllegal(file);
if (checkImageResult) {
this.$message({
showClose: true,
message: checkImageResult,
type: 'error'
});
return false;
}
let fd = new FormData();
fd.append('file', file);
fileApi.fileUpload(fd).then(res => {
this.$emit('menuTick', 'insertPic', res)
}).catch(err => {
console.log(err.message)
})
return false;
},
},
watch: {
value(newVal) {
if (newVal) {
document.body.addEventListener('click', this.closeCB.bind(this));
} else {
document.body.removeEventListener('click', this.closeCB.bind(this));
}
}
},
}
</script>
<style lang="less" scoped>
.menu {
position: absolute;
padding: 6px 0;
border-radius: 4px;
border: 1px solid #aaaaaa;
background-color: #ffffff;
z-index: 9999;
}
.menu_item {
margin-top: 10px;
min-width: 125px;
font-size: 14px;
line-height: 20px;
color: #303133;
cursor: pointer;
&:first-of-type {
margin-top: 0;
}
&:hover {
color: white;
background: rgb(139, 146, 148);
}
span {
text-align: center;
display: inline-block;
padding: 4px 0;
width: 100%;
}
/deep/ .el-upload {
width: 100%;
}
}
li:hover {
background-color: #1790ff;
color: white;
}
li {
font-size: 15px;
list-style: none;
}
</style>

View File

@ -3,10 +3,12 @@
<el-container> <el-container>
<el-header class="editor__header"> <el-header class="editor__header">
<editor-header <editor-header
ref="header"
@refresh="onEditorRefresh" @refresh="onEditorRefresh"
@uploaded="uploaded" @uploaded="uploaded"
@cssChanged="cssChanged" @cssChanged="cssChanged"
@showBox="showBox = !showBox" @downLoad="downloadEditorContent"
@showCssEditor="showCssEditor = !showCssEditor"
@showAboutDialog="aboutDialogVisible = true" @showAboutDialog="aboutDialogVisible = true"
@showDialogForm="dialogFormVisible = true" @showDialogForm="dialogFormVisible = true"
@startCopy="isCoping = true, backLight = true" @startCopy="isCoping = true, backLight = true"
@ -15,7 +17,9 @@
</el-header> </el-header>
<el-main class="main-body"> <el-main class="main-body">
<el-row class="main-section"> <el-row class="main-section">
<el-col :span="12"> <el-col :span="12"
@contextmenu.prevent.native="openMenu($event)"
>
<textarea id="editor" type="textarea" placeholder="Your markdown text here." v-model="source"> <textarea id="editor" type="textarea" placeholder="Your markdown text here." v-model="source">
</textarea> </textarea>
</el-col> </el-col>
@ -38,7 +42,7 @@
</section> </section>
</el-col> </el-col>
<transition name="custom-classes-transition" enter-active-class="bounceInRight"> <transition name="custom-classes-transition" enter-active-class="bounceInRight">
<el-col id="cssBox" :span="12" v-show="showBox"> <el-col id="cssBox" :span="12" v-show="showCssEditor">
<textarea id="cssEditor" type="textarea" placeholder="Your custom css here."> <textarea id="cssEditor" type="textarea" placeholder="Your custom css here.">
</textarea> </textarea>
</el-col> </el-col>
@ -46,10 +50,16 @@
</el-row> </el-row>
</el-main> </el-main>
</el-container> </el-container>
<about-dialog :aboutDialogVisible="aboutDialogVisible" <about-dialog v-model="aboutDialogVisible"/>
@close="aboutDialogVisible = false" /> <insert-form-dialog v-model="dialogFormVisible"/>
<insert-form-dialog :dialogFormVisible="dialogFormVisible" <right-click-menu
@close="dialogFormVisible = false" /> v-model="rightClickMenuVisible"
:left="mouseLeft"
:top="mouseTop"
@downLoad="downloadEditorContent"
@menuTick="onMenuEvent"
@insertTable="dialogFormVisible = true"
/>
</div> </div>
</template> </template>
<script> <script>
@ -57,12 +67,14 @@ import fileApi from '../api/file';
import editorHeader from '../components/CodemirrorEditor/header'; import editorHeader from '../components/CodemirrorEditor/header';
import aboutDialog from '../components/CodemirrorEditor/aboutDialog'; import aboutDialog from '../components/CodemirrorEditor/aboutDialog';
import insertFormDialog from '../components/CodemirrorEditor/insertForm'; import insertFormDialog from '../components/CodemirrorEditor/insertForm';
import rightClickMenu from '../components/CodemirrorEditor/rightClickMenu';
import { import {
setFontSize,
css2json, css2json,
customCssWithTemplate, downLoadMD,
setFontSize,
isImageIllegal,
saveEditorContent, saveEditorContent,
isImageIllegal customCssWithTemplate
} from '../assets/scripts/util' } from '../assets/scripts/util'
require('codemirror/mode/javascript/javascript') require('codemirror/mode/javascript/javascript')
@ -70,18 +82,24 @@ import {mapState, mapMutations} from 'vuex';
export default { export default {
data() { data() {
return { return {
showBox: false, showCssEditor: false,
aboutDialogVisible: false, aboutDialogVisible: false,
dialogFormVisible: false, dialogFormVisible: false,
rightClickMenuVisible: false,
isCoping: false, isCoping: false,
backLight: false, backLight: false,
timeout: null, timeout: null,
changeTimer: null, changeTimer: null,
source: '' source: '',
mouseLeft: 0,
mouseTop: 0
} }
}, },
components: { components: {
editorHeader, aboutDialog, insertFormDialog editorHeader,
aboutDialog,
insertFormDialog,
rightClickMenu
}, },
computed: { computed: {
...mapState({ ...mapState({
@ -153,52 +171,53 @@ export default {
} }
}); });
this.cssEditor.on('update', (instance) => { this.cssEditor.on('update', (instance) => {
this.cssChanged() this.cssChanged();
saveEditorContent(this.cssEditor, '__css_content') saveEditorContent(this.cssEditor, '__css_content')
}) })
}, },
cssChanged() { cssChanged() {
let json = css2json(this.cssEditor.getValue(0)) let json = css2json(this.cssEditor.getValue(0));
let theme = setFontSize(this.currentSize.replace('px', '')) let theme = setFontSize(this.currentSize.replace('px', ''));
theme = customCssWithTemplate(json, this.currentColor, theme) theme = customCssWithTemplate(json, this.currentColor, theme)
this.setWxRendererOptions({ this.setWxRendererOptions({
theme: theme theme: theme
}); });
this.onEditorRefresh() this.onEditorRefresh();
}, },
// //
uploaded(response, file, fileList) { uploaded(response) {
if (response) { if (response) {
if (response.success) { if (response.success) {
// //
const cursor = this.editor.getCursor() const cursor = this.editor.getCursor();
const imageUrl = response.data const imageUrl = response.data;
const markdownImage = `![](${imageUrl})` const markdownImage = `![](${imageUrl})`;
// Markdown URL // Markdown URL
this.editor.replaceSelection(`\n${markdownImage}\n`, cursor) this.editor.replaceSelection(`\n${markdownImage}\n`, cursor);
this.$message({ this.$message({
showClose: true, showClose: true,
message: '图片插入成功', message: '图片插入成功',
type: 'success' type: 'success'
}) });
this.onEditorRefresh() this.onEditorRefresh();
} else { } else {
// //
this.$message({ this.$message({
showClose: true, showClose: true,
message: response.message, message: response.message,
type: 'error' type: 'error'
}) });
} }
} else { } else {
this.$message({ this.$message({
showClose: true, showClose: true,
message: '上传图片未知异常', message: '上传图片未知异常',
type: 'error' type: 'error'
}) });
} }
}, },
//
leftAndRightScroll() { leftAndRightScroll() {
const scrollCB = text=> { const scrollCB = text=> {
let source, target; let source, target;
@ -235,16 +254,51 @@ export default {
this.$refs.preview.$el.addEventListener("scroll", previewScrollCB, false); this.$refs.preview.$el.addEventListener("scroll", previewScrollCB, false);
this.editor.on('scroll', editorScrollCB); this.editor.on('scroll', editorScrollCB);
}, },
//
onEditorRefresh() { onEditorRefresh() {
this.editorRefresh(); this.editorRefresh();
setTimeout(()=> PR.prettyPrint(), 0); setTimeout(()=> PR.prettyPrint(), 0);
}, },
//
endCopy() { endCopy() {
this.backLight = false; this.backLight = false;
setTimeout(()=> { setTimeout(()=> {
this.isCoping = false; this.isCoping = false;
}, 800); }, 800);
}, },
//
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;
default:
break;
}
},
...mapMutations([ ...mapMutations([
'initEditorState', 'initEditorState',
'initEditorEntity', 'initEditorEntity',