hoj-vue 项目中的编辑器支持latex公式
Editor.vue:
1 <template> 2 <div class="mavonEditor"> 3 <mavon-editor 4 ref="md" 5 @imgAdd="$imgAdd" 6 @imgDel="$imgDel" 7 :ishljs="true" 8 :html="openHtml" 9 :autofocus="false" 10 :toolbars="toolbars" 11 v-model="currentValue" 12 codeStyle="arduino-light" 13 @save="saveMavon" 14 > 15 <template v-slot:left-toolbar-after v-if="isAdminRole"> 16 <button 17 type="button" 18 :title="$t('m.Upload_file')" 19 class="op-icon fa markdown-upload" 20 aria-hidden="true" 21 @click="uploadFile" 22 > 23 <!-- 这里用的是element-ui给出的图标 --> 24 <i class="el-icon-upload" /> 25 </button> 26 </template> 27 <!-- <template slot="right-toolbar-after" v-if="isAdminRole"> 28 <button 29 type="button" 30 :title="$t('m.Upload_file')" 31 class="op-icon fa markdown-upload" 32 aria-hidden="true" 33 @click="uploadFile" 34 > 35 <i class="el-icon-upload" /> 36 插入数学公式 37 </button> 38 </template> --> 39 </mavon-editor> 40 <!-- 在这里放一个隐藏的input,用来选择文件 --> 41 <input 42 ref="uploadInput" 43 style="display: none" 44 type="file" 45 @change="uploadFileChange" 46 /> 47 </div> 48 </template> 49 <script> 50 import { mapGetters } from "vuex"; 51 import { addCodeBtn } from "@/common/codeblock"; 52 export default { 53 name: "Editor", 54 props: { 55 value: { 56 type: String, 57 default: "", 58 }, 59 openHtml: { 60 type: Boolean, 61 default: true, 62 }, 63 }, 64 data() { 65 return { 66 currentValue: this.value, 67 img_file: {}, 68 toolbars: { 69 bold: true, // 粗体 70 italic: true, // 斜体 71 header: true, // 标题 72 underline: true, // 下划线 73 strikethrough: true, // 中划线 74 mark: true, // 标记 75 superscript: true, // 上角标 76 subscript: true, // 下角标 77 quote: true, // 引用 78 ol: true, // 有序列表 79 ul: true, // 无序列表 80 link: true, // 链接 81 imagelink: false, // 图片链接 82 code: true, // code 83 table: true, // 表格 84 fullscreen: true, // 全屏编辑 85 readmodel: true, // 沉浸式阅读 86 htmlcode: true, // 展示html源码 87 help: true, // 帮助 88 /* 1.3.5 */ 89 undo: true, // 上一步 90 redo: true, // 下一步 91 trash: true, // 清空 92 save: true, // 保存(触发events中的save事件) 93 /* 1.4.2 */ 94 navigation: true, // 导航目录 95 /* 2.1.8 */ 96 alignleft: true, // 左对齐 97 aligncenter: true, // 居中 98 alignright: true, // 右对齐 99 /* 2.2.1 */ 100 subfield: true, // 单双栏模式 101 preview: true, // 预览 102 }, 103 s_external_link: { 104 katex_js: function () { 105 return "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.8.3/katex.min.js"; 106 }, 107 katex_css: function () { 108 return "https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.8.3/katex.min.css"; 109 }, 110 }, 111 }; 112 }, 113 created() { 114 if (this.isAdminRole || this.isGroupAdmin) { 115 this.toolbars.imagelink = true; 116 } 117 }, 118 mounted() { 119 console.log("值1", this.openHtml); 120 console.log("值2", this.currentValue); 121 }, 122 methods: { 123 saveMavon(value, render) { 124 console.log("this is render" + render); 125 console.log("this is value" + value); 126 }, 127 loadExternalLink(name, type, callback) { 128 if (typeof this.p_external_link[name] !== "function") { 129 if (this.p_external_link[name] !== false) { 130 console.error( 131 "external_link." + name, 132 "is not a function, if you want to disabled this error log, set external_link." + 133 name, 134 "to function or false" 135 ); 136 } 137 return; 138 } 139 var _obj = { 140 css: loadLink, 141 js: loadScript, 142 }; 143 if (_obj.hasOwnProperty(type)) { 144 _obj[type](this.p_external_link[name](), callback); 145 } 146 }, 147 $paste($e) { 148 var clipboardData = $e.clipboardData; 149 if (clipboardData) { 150 var items = clipboardData.items; 151 if (!items) return; 152 var types = clipboardData.types || []; 153 var item = null; 154 for (var i = 0; i < types.length; i++) { 155 if (types[i] === "Files") { 156 item = items[i]; 157 break; 158 } 159 } 160 if (item && item.kind === "file") { 161 stopEvent($e); 162 var oFile = item.getAsFile(); 163 } 164 } 165 }, 166 initExternalFuc() { 167 var $vm = this; 168 var _external_ = [ 169 ]; 170 var _type_ = typeof $vm.externalLink; 171 var _is_object = _type_ === "object"; 172 var _is_boolean = _type_ === "boolean"; 173 for (var i = 0; i < _external_.length; i++) { 174 if ( 175 (_is_boolean && !$vm.externalLink) || 176 (_is_object && $vm.externalLink[_external_[i]] === false) 177 ) { 178 $vm.p_external_link[_external_[i]] = false; 179 } else if ( 180 _is_object && 181 typeof $vm.externalLink[_external_[i]] === "function" 182 ) { 183 $vm.p_external_link[_external_[i]] = $vm.externalLink[_external_[i]]; 184 } else { 185 $vm.p_external_link[_external_[i]] = 186 $vm.s_external_link[_external_[i]]; 187 } 188 } 189 }, 190 // 编辑开关 191 editableTextarea() { 192 let text_dom = this.$refs.vNoteTextarea.$refs.vTextarea; 193 if (this.editable) { 194 text_dom.removeAttribute("disabled"); 195 } else { 196 text_dom.setAttribute("disabled", "disabled"); 197 } 198 }, 199 // 将图片上传到服务器,返回地址替换到md中 200 $imgAdd(pos, $file) { 201 if (!this.isAdminRole && !this.isGroupAdmin) { 202 return; 203 } 204 var formdata = new FormData(); 205 formdata.append("image", $file); 206 let gid = this.$route.params.groupID; 207 if (gid != null && gid != undefined) { 208 formdata.append("gid", gid); 209 } 210 //将下面上传接口替换为你自己的服务器接口 211 this.$http({ 212 url: "接口1", 213 method: "post", 214 data: formdata, 215 headers: { "Content-Type": "multipart/form-data" }, 216 }).then((res) => { 217 this.$refs.md.$img2Url(pos, res.data.data.link); 218 this.img_file[res.data.data.link] = res.data.data.fileId; 219 }); 220 }, 221 $imgDel(pos) { 222 // 删除文件 223 this.$http({ 224 url: "接口2", 225 method: "get", 226 params: { 227 fileId: this.img_file[pos[0]], 228 }, 229 }); 230 }, 231 uploadFile() { 232 // 通过ref找到隐藏的input标签,触发它的点击方法 233 this.$refs.uploadInput.click(); 234 }, 235 // 监听input获取文件的状态 236 uploadFileChange(e) { 237 // 获取到input选取的文件 238 const file = e.target.files[0]; 239 // 创建form格式的数据,将文件放入form中 240 const formdata = new FormData(); 241 formdata.append("file", file); 242 let gid = this.$route.params.groupID; 243 if (gid != null && gid != undefined) { 244 formdata.append("gid", gid); 245 } 246 this.$http({ 247 url: "接口3", 248 method: "post", 249 data: formdata, 250 headers: { "Content-Type": "multipart/form-data" }, 251 }).then((res) => { 252 // 这里获取到的是mavon编辑器实例,上面挂载着很多方法 253 const $vm = this.$refs.md; 254 // 将文件名与文件路径插入当前光标位置,这是mavon-editor 内置的方法 255 $vm.insertText($vm.getTextareaDom(), { 256 prefix: `[${file.name}](${res.data.data.link})`, 257 subfix: "", 258 str: "", 259 }); 260 }); 261 }, 262 }, 263 computed: { 264 ...mapGetters(["isAdminRole", "isGroupAdmin"]), 265 }, 266 watch: { 267 value(val) { 268 if (this.currentValue !== val) { 269 this.currentValue = val; 270 } 271 }, 272 currentValue(newVal, oldVal) { 273 if (newVal !== oldVal) { 274 this.$emit("update:value", newVal); 275 this.$nextTick((_) => { 276 addCodeBtn(); 277 }); 278 } 279 }, 280 isAdminRole(val) { 281 if (!val) { 282 this.toolbars.imagelink = false; 283 } else { 284 this.toolbars.imagelink = true; 285 } 286 }, 287 editable: function () { 288 this.editableTextarea(); 289 }, 290 }, 291 }; 292 </script> 293 <style> 294 .auto-textarea-wrapper .auto-textarea-block { 295 white-space: pre-wrap !important; 296 } 297 </style>