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

refactor:重构状态管理逻辑

上级 f529497f
<template> <template>
<div :class="['md_container', { active: isFocus }]"> <div :class="['md_container', { active: isFocus }]">
<markdown-header /> <markdown-header
<markdownPreview v-show="showPreview" /> :text.sync="text"
<markdown-editor v-show="!showPreview" /> :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 <markdown-footer
:fileList.sync="fileList"
:canAttachFile="canAttachFile"
:isFocus.sync="isFocus"
:can-attach-file="canAttachFile" :can-attach-file="canAttachFile"
v-if="!showPreview && canAttachFile" v-if="!showPreview && canAttachFile"
/> />
...@@ -14,7 +31,7 @@ import markdownHeader from "./components/header/md-header"; ...@@ -14,7 +31,7 @@ import markdownHeader from "./components/header/md-header";
import markdownFooter from "./components/footer/md-footer"; import markdownFooter from "./components/footer/md-footer";
import markdownEditor from "./components/content/md-textarea"; import markdownEditor from "./components/content/md-textarea";
import markdownPreview from "./components/content/md-preview"; import markdownPreview from "./components/content/md-preview";
import { mapState } from "vuex"; import { formatText } from "@/assets/js/utils";
export default { export default {
components: { components: {
markdownHeader, markdownHeader,
...@@ -22,16 +39,32 @@ export default { ...@@ -22,16 +39,32 @@ export default {
markdownEditor, markdownEditor,
markdownPreview markdownPreview
}, },
computed: { props: {
...mapState([ placeholder: {
"showPreview", type: String,
"isFocus", default: "请输入内容"
"canAttachFile", },
"text", canAttachFile: {
"html", type: Boolean,
"fileList" default: true
]) }
}, },
data() {
return {
fullScreen: false,
isFocus: false,
showPreview: false,
fileList: [],
text: "",
html: "",
selectionInfo: {
selectorId: "",
selectionStart: "",
selectionEnd: ""
}
};
},
watch: { watch: {
html: { html: {
immediate: true, immediate: true,
...@@ -46,7 +79,23 @@ export default { ...@@ -46,7 +79,23 @@ export default {
immediate: false, immediate: false,
deep: true, deep: true,
handler: function(val) { 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) { export function getSelectionInfo(selectorId) {
...@@ -40,15 +38,6 @@ export function formatText(text, selectionInfo, startStr = "", endStr = "") { ...@@ -40,15 +38,6 @@ export function formatText(text, selectionInfo, startStr = "", endStr = "") {
return newText; 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({ export function initStyle({
borderColor, borderColor,
......
...@@ -4,18 +4,23 @@ ...@@ -4,18 +4,23 @@
</div> </div>
</template> </template>
<script> <script>
import { mapState, mapMutations } from "vuex";
import marked from "marked"; import marked from "marked";
import "highlight.js/styles/github.css"; import "highlight.js/styles/github.css";
export default { export default {
data() { data() {
return {}; return {};
}, },
computed: { props: {
...mapState(["text", "html"]) text: {
type: String,
default: ""
},
html: {
type: String,
default: ""
}
}, },
methods: { methods: {
...mapMutations(["setHtml"]),
transferMarkdown(val) { transferMarkdown(val) {
marked.setOptions({ marked.setOptions({
highlight: function(code, lang, callback) { highlight: function(code, lang, callback) {
...@@ -25,7 +30,7 @@ export default { ...@@ -25,7 +30,7 @@ export default {
}); });
if (!val.trim()) return; if (!val.trim()) return;
const html = marked(val); const html = marked(val);
this.setHtml(html); this.$emit("update:html", html);
} }
}, },
watch: { watch: {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div :class="['md_textarea', { fullScreen }]"> <div :class="['md_textarea', { fullScreen }]">
<textarea <textarea
:id="id" :id="id"
@change="setText(textContent)" @change="$emit('update:text', textContent)"
@focus="setFocus(true)" @focus="setFocus(true)"
@blur="setFocus(false)" @blur="setFocus(false)"
@paste="pasteFile" @paste="pasteFile"
...@@ -12,20 +12,44 @@ ...@@ -12,20 +12,44 @@
> >
</textarea> </textarea>
<span <span
@click="setFullScreen(false)" @click="$emit('update:fullScreen', false)"
v-if="fullScreen" v-if="fullScreen"
class="icon iconfont icon-quxiaoquanping_o" class="icon iconfont icon-quxiaoquanping_o"
></span> ></span>
</div> </div>
</template> </template>
<script> <script>
import { mapState, mapMutations } from "vuex";
import { getSelectionInfo, getPosition } from "@/assets/js/utils"; import { getSelectionInfo, getPosition } from "@/assets/js/utils";
export default { 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() { data() {
return { return {
id: new Date().getTime(), id: new Date().getTime(),
isFocus: false,
textContent: "" textContent: ""
}; };
}, },
...@@ -43,29 +67,24 @@ export default { ...@@ -43,29 +67,24 @@ export default {
beforeDestroy() { beforeDestroy() {
document.removeEventListener("mouseup", this.checkSelection); document.removeEventListener("mouseup", this.checkSelection);
}, },
computed: {
...mapState(["text", "fullScreen", "placeholder"])
},
methods: { methods: {
...mapMutations([
"setText", setFocus(val) {
"setFullScreen", this.$emit('update:isFocus', val)
"setSelectionInfo", },
"setFileList",
"setFocus"
]),
checkSelection() { checkSelection() {
const info = getSelectionInfo(this.id); const info = getSelectionInfo(this.id);
if (!info) { if (!info) {
const cursorPoint = getPosition(this.id); const cursorPoint = getPosition(this.id);
this.setSelectionInfo({ this.$emit('update:selectionInfo',{
selectorId: this.id, selectorId: this.id,
selectionStart: cursorPoint, selectionStart: cursorPoint,
selectionEnd: cursorPoint selectionEnd: cursorPoint
}); })
return; return;
} }
this.setSelectionInfo(info); this.$emit('update:selectionInfo',info)
}, },
pasteFile(event) { pasteFile(event) {
let fileList = []; let fileList = [];
...@@ -77,7 +96,7 @@ export default { ...@@ -77,7 +96,7 @@ export default {
} }
} }
if (!fileList.length) return; if (!fileList.length) return;
this.setFileList(fileList[0]); this.$emit('update:fileList', fileList)
} }
} }
}; };
......
<template> <template>
<div :class="['md_footer', { active: isFocus }]"> <div :class="['md_footer', { active: isFocus }]">
<div class="doc"></div> <div class="doc"></div>
<upload-files v-if="canAttachFile" /> <upload-files @changeFileList="$emit('update:fileList', $event)" :fileList="fileList" v-if="canAttachFile" />
</div> </div>
</template> </template>
<script> <script>
import uploadFiles from "./upload-files"; import uploadFiles from "./upload-files";
import { mapState } from "vuex";
export default { export default {
components: { uploadFiles }, components: { uploadFiles },
computed: { props: {
...mapState(["isFocus", "canAttachFile"]) isFocus: {
} type: Boolean,
default: false
},
canAttachFile: {
type: Boolean,
default: false
},
fileList: {
type: Array,
default: ()=>[]
}
},
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
......
...@@ -13,20 +13,20 @@ ...@@ -13,20 +13,20 @@
</div> </div>
</template> </template>
<script> <script>
import { mapState, mapMutations } from "vuex";
export default { export default {
props: {
fileList: {
type: Array,
default: () => []
}
},
data() { data() {
return {}; return {};
}, },
computed: {
...mapState(["text", "selectionInfo"])
},
methods: { methods: {
...mapMutations(["setText", "setFileList"]),
upload(e) { upload(e) {
const fileList = Array.from(e.target.files); const fileList = Array.from(e.target.files);
this.setFileList(fileList[0]); this.$emit("changeFileList", fileList);
} }
} }
}; };
......
...@@ -17,22 +17,131 @@ ...@@ -17,22 +17,131 @@
<div class="header_tools" v-if="!showPreview"> <div class="header_tools" v-if="!showPreview">
<tool-button <tool-button
:info="item" :info="item"
:fullScreen="fullScreen"
@setFullScreen="$emit('update:fullScreen', true)"
@updateText="updateText"
v-for="(item, index) in toolButtonList" v-for="(item, index) in toolButtonList"
:key="index" :key="index"
:text="text"
:selectionInfo="selectionInfo"
/> />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import toolButton from "./tool-button"; import toolButton from "./tool-button";
import { mapState, mapMutations } from "vuex";
export default { export default {
components: { toolButton }, components: { toolButton },
computed: { props: {
...mapState(["toolButtonList", "isFocus", "showPreview"]) 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: { 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> </script>
......
...@@ -8,20 +8,36 @@ ...@@ -8,20 +8,36 @@
</div> </div>
</template> </template>
<script> <script>
import { mapMutations, mapState } from "vuex"; import { formatText } from "@/assets/js/utils";
import { updateText } from "@/assets/js/utils";
export default { export default {
props: { props: {
info: { info: {
type: Object, type: Object,
default: () => {} default: () => {}
},
fullScreen: {
type: Boolean,
default: false
},
text: {
type: String,
default: ''
},
selectionInfo: {
type: Object,
default: ()=>{}
},
uploadPath: {
type: String,
default: ''
} }
}, },
computed: { data() {
...mapState(["selectionInfo", "text", "ulNum"]) return {
ulNum: 1
}
}, },
methods: { methods: {
...mapMutations(["setFullScreen", "setText", "setUlNum"]),
handleTool(type, startStr, endStr) { handleTool(type, startStr, endStr) {
switch (type) { switch (type) {
case "bold": case "bold":
...@@ -32,20 +48,26 @@ export default { ...@@ -32,20 +48,26 @@ export default {
case "ul": case "ul":
case "task": case "task":
case "table": case "table":
updateText(startStr, endStr); this.updateText(startStr, endStr);
break; break;
case "ol": case "ol":
let ulNum = this.ulNum; let ulNum = this.ulNum;
updateText(`\n${ulNum++}. `, ""); this.updateText(`\n${ulNum}. `, "");
this.setUlNum(ulNum); this.ulNum++
break; break;
case "fullScreen": case "fullScreen":
this.setFullScreen(true); this.$emit('setFullScreen', true)
break; break;
default: default:
break; break;
} }
} },
updateText(startStr, endStr) {
const originalText = this.text
const selectionInfo = this.selectionInfo
const newText = formatText(originalText, selectionInfo, startStr, endStr)
this.$emit('updateText',newText)
}
} }
}; };
</script> </script>
......
import Vue from "vue"; import Vue from "vue";
import App from "./App"; import App from "./App";
import store from "./store";
import Vtip from "vtip"; import Vtip from "vtip";
import "vtip/lib/index.min.css"; 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"; import "@/assets/style/global.less";
Vue.use(Vtip.directive); Vue.use(Vtip.directive);
...@@ -19,23 +18,23 @@ function initMdEditor(obj) { ...@@ -19,23 +18,23 @@ function initMdEditor(obj) {
} = obj; } = obj;
if (!el || !document.querySelector(el)) throw new Error("请指定容器"); if (!el || !document.querySelector(el)) throw new Error("请指定容器");
if (isNotEmpty(themeOptions)) initStyle(themeOptions); if (isNotEmpty(themeOptions)) initStyle(themeOptions);
if (isNotEmpty(canAttachFile))
store.commit("setCanAttachFile", canAttachFile);
if (isNotEmpty(placeholder)) store.commit("setPlaceholder", placeholder);
new Vue({ new Vue({
store,
render: h => render: h =>
h(App, { h(App, {
on: { on: {
change(val) { change(val) {
onChange(val); onChange(val);
}, },
upload(val) { upload({ val, callback }) {
onUpload(val, function(res) { onUpload(val, function(res) {
updateText("\n\n![img](", `${res})\n`); callback(res);
}); });
} }
},
props: {
canAttachFile,
placeholder
} }
}) })
}).$mount(el); }).$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.
先完成此消息的编辑!
想要评论请 注册