提交 af823f10 编写于 作者: 璃白.'s avatar 璃白. 🌻

refactor:重构状态管理逻辑

上级 f529497f
<template>
<div :class="['md_container', { active: isFocus }]">
<markdown-header />
<markdownPreview v-show="showPreview" />
<markdown-editor v-show="!showPreview" />
<markdown-header
:text.sync="text"
:selectionInfo.sync="selectionInfo"
:showPreview.sync="showPreview"
:isFocus.sync="isFocus"
:fullScreen.sync="fullScreen"
/>
<markdownPreview :text="text" :html.sync="html" v-show="showPreview" />
<markdown-editor
:selectionInfo.sync="selectionInfo"
:text.sync="text"
:fileList.sync="fileList"
:placeholder="placeholder"
:isFocus.sync="isFocus"
:fullScreen.sync="fullScreen"
v-show="!showPreview"
/>
<markdown-footer
:fileList.sync="fileList"
:canAttachFile="canAttachFile"
:isFocus.sync="isFocus"
:can-attach-file="canAttachFile"
v-if="!showPreview && canAttachFile"
/>
......@@ -14,7 +31,7 @@ import markdownHeader from "./components/header/md-header";
import markdownFooter from "./components/footer/md-footer";
import markdownEditor from "./components/content/md-textarea";
import markdownPreview from "./components/content/md-preview";
import { mapState } from "vuex";
import { formatText } from "@/assets/js/utils";
export default {
components: {
markdownHeader,
......@@ -22,16 +39,32 @@ export default {
markdownEditor,
markdownPreview
},
computed: {
...mapState([
"showPreview",
"isFocus",
"canAttachFile",
"text",
"html",
"fileList"
])
props: {
placeholder: {
type: String,
default: "请输入内容"
},
canAttachFile: {
type: Boolean,
default: true
}
},
data() {
return {
fullScreen: false,
isFocus: false,
showPreview: false,
fileList: [],
text: "",
html: "",
selectionInfo: {
selectorId: "",
selectionStart: "",
selectionEnd: ""
}
};
},
watch: {
html: {
immediate: true,
......@@ -46,7 +79,23 @@ export default {
immediate: false,
deep: true,
handler: function(val) {
this.$emit("upload", val);
const _this = this;
if (!val.length) return;
this.$emit("upload", {
val: val[0],
callback: function(url) {
const originalText = _this.text;
const selectionInfo = _this.selectionInfo;
const newText = formatText(
originalText,
selectionInfo,
"\n\n![img](",
`${url})\n`
);
_this.text = newText;
}
});
this.fileList = [];
}
}
}
......
import store from "@/store";
// 获取选中文本信息
export function getSelectionInfo(selectorId) {
......@@ -40,15 +38,6 @@ export function formatText(text, selectionInfo, startStr = "", endStr = "") {
return newText;
}
//
export function updateText(startStr, endStr) {
const selectionInfo = store.state.selectionInfo;
const originalText = store.state.text;
const newText = formatText(originalText, selectionInfo, startStr, endStr);
if (!newText) return;
store.commit("setText", newText);
}
// 初始化样式
export function initStyle({
borderColor,
......
......@@ -4,18 +4,23 @@
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
import marked from "marked";
import "highlight.js/styles/github.css";
export default {
data() {
return {};
},
computed: {
...mapState(["text", "html"])
props: {
text: {
type: String,
default: ""
},
html: {
type: String,
default: ""
}
},
methods: {
...mapMutations(["setHtml"]),
transferMarkdown(val) {
marked.setOptions({
highlight: function(code, lang, callback) {
......@@ -25,7 +30,7 @@ export default {
});
if (!val.trim()) return;
const html = marked(val);
this.setHtml(html);
this.$emit("update:html", html);
}
},
watch: {
......
......@@ -2,7 +2,7 @@
<div :class="['md_textarea', { fullScreen }]">
<textarea
:id="id"
@change="setText(textContent)"
@change="$emit('update:text', textContent)"
@focus="setFocus(true)"
@blur="setFocus(false)"
@paste="pasteFile"
......@@ -12,20 +12,44 @@
>
</textarea>
<span
@click="setFullScreen(false)"
@click="$emit('update:fullScreen', false)"
v-if="fullScreen"
class="icon iconfont icon-quxiaoquanping_o"
></span>
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
import { getSelectionInfo, getPosition } from "@/assets/js/utils";
export default {
props: {
fullScreen: {
type: Boolean,
default: false
},
isFocus: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: false
},
fileList: {
type: Array,
default: ()=>[]
},
text: {
type: String,
default: ''
},
selectionInfo: {
type: Object,
default: ()=>{}
}
},
data() {
return {
id: new Date().getTime(),
isFocus: false,
textContent: ""
};
},
......@@ -43,29 +67,24 @@ export default {
beforeDestroy() {
document.removeEventListener("mouseup", this.checkSelection);
},
computed: {
...mapState(["text", "fullScreen", "placeholder"])
},
methods: {
...mapMutations([
"setText",
"setFullScreen",
"setSelectionInfo",
"setFileList",
"setFocus"
]),
setFocus(val) {
this.$emit('update:isFocus', val)
},
checkSelection() {
const info = getSelectionInfo(this.id);
if (!info) {
const cursorPoint = getPosition(this.id);
this.setSelectionInfo({
this.$emit('update:selectionInfo',{
selectorId: this.id,
selectionStart: cursorPoint,
selectionEnd: cursorPoint
});
})
return;
}
this.setSelectionInfo(info);
this.$emit('update:selectionInfo',info)
},
pasteFile(event) {
let fileList = [];
......@@ -77,7 +96,7 @@ export default {
}
}
if (!fileList.length) return;
this.setFileList(fileList[0]);
this.$emit('update:fileList', fileList)
}
}
};
......
<template>
<div :class="['md_footer', { active: isFocus }]">
<div class="doc"></div>
<upload-files v-if="canAttachFile" />
<upload-files @changeFileList="$emit('update:fileList', $event)" :fileList="fileList" v-if="canAttachFile" />
</div>
</template>
<script>
import uploadFiles from "./upload-files";
import { mapState } from "vuex";
export default {
components: { uploadFiles },
computed: {
...mapState(["isFocus", "canAttachFile"])
}
props: {
isFocus: {
type: Boolean,
default: false
},
canAttachFile: {
type: Boolean,
default: false
},
fileList: {
type: Array,
default: ()=>[]
}
},
};
</script>
<style lang="less" scoped>
......
......@@ -13,20 +13,20 @@
</div>
</template>
<script>
import { mapState, mapMutations } from "vuex";
export default {
props: {
fileList: {
type: Array,
default: () => []
}
},
data() {
return {};
},
computed: {
...mapState(["text", "selectionInfo"])
},
methods: {
...mapMutations(["setText", "setFileList"]),
upload(e) {
const fileList = Array.from(e.target.files);
this.setFileList(fileList[0]);
this.$emit("changeFileList", fileList);
}
}
};
......
......@@ -17,22 +17,131 @@
<div class="header_tools" v-if="!showPreview">
<tool-button
:info="item"
:fullScreen="fullScreen"
@setFullScreen="$emit('update:fullScreen', true)"
@updateText="updateText"
v-for="(item, index) in toolButtonList"
:key="index"
:text="text"
:selectionInfo="selectionInfo"
/>
</div>
</div>
</template>
<script>
import toolButton from "./tool-button";
import { mapState, mapMutations } from "vuex";
export default {
components: { toolButton },
computed: {
...mapState(["toolButtonList", "isFocus", "showPreview"])
props: {
fullScreen: {
type: Boolean,
default: false
},
isFocus: {
type: Boolean,
default: false
},
showPreview: {
type: Boolean,
default: false
},
text: {
type: String,
default: ""
},
selectionInfo: {
type: Object,
default: () => {}
}
},
data() {
return {
toolButtonList: [
{
name: "bold",
icon: "bold",
tip: "粗体",
startStr: "**",
endStr: "**"
},
{
name: "italic",
icon: "italic",
tip: "斜体",
startStr: "_",
endStr: "_"
},
{
name: "quote",
icon: "baojiaquotation",
tip: "插入引用",
startStr: "\n> ",
endStr: ""
},
{
name: "code",
icon: "code",
tip: "插入代码",
startStr: "`",
endStr: "`"
},
{
name: "link",
icon: "lianjie",
tip: "添加链接",
startStr: "[",
endStr: "](url)"
},
{
name: "ul",
icon: "unorderedList",
tip: "添加无序列表",
startStr: "\n- ",
endStr: ""
},
{
name: "ol",
icon: "youxuliebiao",
tip: "添加有序列表",
startStr: "",
endStr: ""
},
{
name: "task",
icon: "renwu",
tip: "添加任务列表",
startStr: "\n- [ ] ",
endStr: ""
},
{
name: "table",
icon: "biaoge",
tip: "添加表格",
startStr:
"\n\n| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |\n\n",
endStr: ""
},
{
name: "fullScreen",
icon: "fullScreen",
tip: "全屏模式"
}
]
};
},
methods: {
...mapMutations(["setShowPreview"])
setShowPreview(val) {
this.$emit("update:showPreview", val);
},
updateText(val) {
this.$emit("update:text", val);
this.$emit("update:selectionInfo", {
selectorId: "",
selectionStart: "",
selectionEnd: ""
});
}
}
};
</script>
......
......@@ -8,20 +8,36 @@
</div>
</template>
<script>
import { mapMutations, mapState } from "vuex";
import { updateText } from "@/assets/js/utils";
import { formatText } from "@/assets/js/utils";
export default {
props: {
info: {
type: Object,
default: () => {}
},
fullScreen: {
type: Boolean,
default: false
},
text: {
type: String,
default: ''
},
selectionInfo: {
type: Object,
default: ()=>{}
},
uploadPath: {
type: String,
default: ''
}
},
computed: {
...mapState(["selectionInfo", "text", "ulNum"])
data() {
return {
ulNum: 1
}
},
methods: {
...mapMutations(["setFullScreen", "setText", "setUlNum"]),
handleTool(type, startStr, endStr) {
switch (type) {
case "bold":
......@@ -32,20 +48,26 @@ export default {
case "ul":
case "task":
case "table":
updateText(startStr, endStr);
this.updateText(startStr, endStr);
break;
case "ol":
let ulNum = this.ulNum;
updateText(`\n${ulNum++}. `, "");
this.setUlNum(ulNum);
this.updateText(`\n${ulNum}. `, "");
this.ulNum++
break;
case "fullScreen":
this.setFullScreen(true);
this.$emit('setFullScreen', true)
break;
default:
break;
}
}
},
updateText(startStr, endStr) {
const originalText = this.text
const selectionInfo = this.selectionInfo
const newText = formatText(originalText, selectionInfo, startStr, endStr)
this.$emit('updateText',newText)
}
}
};
</script>
......
import Vue from "vue";
import App from "./App";
import store from "./store";
import Vtip from "vtip";
import "vtip/lib/index.min.css";
import { initStyle, isNotEmpty, updateText } from "@/assets/js/utils";
import { initStyle, isNotEmpty } from "@/assets/js/utils";
import "@/assets/style/global.less";
Vue.use(Vtip.directive);
......@@ -19,23 +18,23 @@ function initMdEditor(obj) {
} = obj;
if (!el || !document.querySelector(el)) throw new Error("请指定容器");
if (isNotEmpty(themeOptions)) initStyle(themeOptions);
if (isNotEmpty(canAttachFile))
store.commit("setCanAttachFile", canAttachFile);
if (isNotEmpty(placeholder)) store.commit("setPlaceholder", placeholder);
new Vue({
store,
render: h =>
h(App, {
on: {
change(val) {
onChange(val);
},
upload(val) {
upload({ val, callback }) {
onUpload(val, function(res) {
updateText("\n\n![img](", `${res})\n`);
callback(res);
});
}
},
props: {
canAttachFile,
placeholder
}
})
}).$mount(el);
......
import Vue from "vue";
import Vuex from "vuex";
import { formatText } from "@/assets/js/utils";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
fullScreen: false,
isFocus: false,
showPreview: false,
placeholder: "请输入内容",
toolButtonList: [
{
name: "bold",
icon: "bold",
tip: "粗体",
startStr: "**",
endStr: "**"
},
{
name: "italic",
icon: "italic",
tip: "斜体",
startStr: "_",
endStr: "_"
},
{
name: "quote",
icon: "baojiaquotation",
tip: "插入引用",
startStr: "\n> ",
endStr: ""
},
{
name: "code",
icon: "code",
tip: "插入代码",
startStr: "`",
endStr: "`"
},
{
name: "link",
icon: "lianjie",
tip: "添加链接",
startStr: "[",
endStr: "](url)"
},
{
name: "ul",
icon: "unorderedList",
tip: "添加无序列表",
startStr: "\n- ",
endStr: ""
},
{
name: "ol",
icon: "youxuliebiao",
tip: "添加有序列表",
startStr: "",
endStr: ""
},
{
name: "task",
icon: "renwu",
tip: "添加任务列表",
startStr: "\n- [ ] ",
endStr: ""
},
{
name: "table",
icon: "biaoge",
tip: "添加表格",
startStr:
"\n\n| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |\n\n",
endStr: ""
},
{
name: "fullScreen",
icon: "fullScreen",
tip: "全屏模式"
}
],
fileList: "",
ulNum: 1,
// text: `
// # 标题一标题一标题一
// ## 标题二标题二
// 666\`行内代码\`666
// \`\`\`js
// // 是注释呀
// /**
// * @params x
// */
// function fn() {
// return null;
// }
// \`\`\`
// **粗体文字**
// _斜体文字_
// > 这段是引用的内容\n
// > 这段是引用的内容
// > 这段是引用的内容
// [链接](url)
// - 无序列表
// - 无序列表
// - 无序列表
// 1. 有序列表
// 2. 有序列表
// 3. 有序列表
// - [ ] 任务列表
// - [x] 任务列表
// - [ ] 任务列表
// | header | header |
// | ------ | ------ |
// | cell | cell |
// | cell | cell |`,
selectionInfo: "",
text: "",
html: "",
canAttachFile: true
},
mutations: {
setFullScreen(state, val) {
state.fullScreen = val;
},
setShowPreview(state, val) {
state.showPreview = val;
},
setFocus(state, val) {
state.isFocus = val;
},
setText(state, val) {
state.text = val;
state.selectionInfo = "";
},
setSelectionInfo(state, val) {
state.selectionInfo = val;
},
setHtml(state, val) {
state.html = val;
},
setUlNum(state, val) {
state.ulNum = val;
},
setCanAttachFile(state, val) {
state.canAttachFile = val;
},
setFileList(state, val) {
state.fileList = val;
},
setPlaceholder(state, val) {
state.placeholder = val;
}
}
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册