App.vue 10.6 KB
Newer Older
璃白.'s avatar
璃白. 已提交
1
<template>
2 3 4 5
  <div
    :class="['md_container', { active: isFocus, fullScreen }]"
    :id="'md_' + id"
  >
璃白.'s avatar
璃白. 已提交
6
    <markdown-header
璃白.'s avatar
fix  
璃白. 已提交
7
      :id="textareaId"
璃白.'s avatar
fix  
璃白. 已提交
8
      :ref="'md_header' + id"
璃白.'s avatar
璃白. 已提交
9 10 11 12
      :text.sync="text"
      :selectionInfo.sync="selectionInfo"
      :showPreview.sync="showPreview"
      :isFocus.sync="isFocus"
璃白.'s avatar
璃白. 已提交
13 14
      :canPreview="canPreview"
      :toolsOptions="toolsOptions"
15
      :zIndex="zIndex"
璃白.'s avatar
fix  
璃白. 已提交
16
      :tabSize="tabSize"
璃白.'s avatar
璃白. 已提交
17
      :disabled="disabled"
璃白.'s avatar
璃白. 已提交
18
      :fullScreen.sync="fullScreen"
19
      :themeOptions="themeOptions"
璃白.'s avatar
璃白. 已提交
20 21 22
      @upload="handleUpload"
      @getFormatType="formatType = $event"
      @updateShowHelp="showHelp = $event"
璃白.'s avatar
璃白. 已提交
23
      @renderLinks="$emit('renderLinks', $event)"
24 25 26 27 28 29 30 31 32
    />
    <input
      ref="mdUploadFile"
      class="md_upload"
      type="file"
      hidden
      name="md-upload-file"
      id="md-upload-file"
      @change="upload"
璃白.'s avatar
璃白. 已提交
33
    />
璃白.'s avatar
璃白. 已提交
34 35 36 37
    <markdownPreview
      :id="textareaId"
      :fullScreen.sync="fullScreen"
      :text="text"
璃白.'s avatar
璃白. 已提交
38
      :height="textareaHeight"
璃白.'s avatar
璃白. 已提交
39
      :html.sync="html"
璃白.'s avatar
fix  
璃白. 已提交
40 41
      :htmlMinHeight="htmlMinHeight"
      v-if="showPreview"
璃白.'s avatar
璃白. 已提交
42
    />
璃白.'s avatar
璃白. 已提交
43

璃白.'s avatar
璃白. 已提交
44 45 46 47 48 49
    <markdown-editor
      :selectionInfo.sync="selectionInfo"
      :text.sync="text"
      :fileList.sync="fileList"
      :placeholder="placeholder"
      :isFocus.sync="isFocus"
50
      :throttleTime="throttle"
璃白.'s avatar
fix  
璃白. 已提交
51
      :htmlMinHeight.sync="htmlMinHeight"
璃白.'s avatar
璃白. 已提交
52
      :fullScreen.sync="fullScreen"
53 54 55
      :maxLength="maxLength"
      :textLength.sync="textLength"
      :rows="rows"
璃白.'s avatar
璃白. 已提交
56
      :height="textareaHeight"
璃白.'s avatar
fix  
璃白. 已提交
57
      :html.sync="html"
58
      :id="textareaId"
璃白.'s avatar
璃白. 已提交
59
      :disabled="disabled"
璃白.'s avatar
璃白. 已提交
60 61
      :show-help="showHelp"
      :formatType="formatType"
璃白.'s avatar
璃白. 已提交
62
      :userList="userList"
璃白.'s avatar
fix  
璃白. 已提交
63
      :ref="'md_textarea' + id"
璃白.'s avatar
fix  
璃白. 已提交
64
      @tab="$refs['md_header' + id].tab()"
璃白.'s avatar
璃白. 已提交
65
      @submit="submit"
璃白.'s avatar
fix  
璃白. 已提交
66
      @enter="handleEnter"
璃白.'s avatar
璃白. 已提交
67
      @getFilteredTags="filteredTags = $event"
璃白.'s avatar
璃白. 已提交
68
      @updateShowHelp="showHelp = $event"
璃白.'s avatar
璃白. 已提交
69
      @renderLinksHtml="renderLinksHtml"
璃白.'s avatar
璃白. 已提交
70
      @queryUserList="queryUserList"
璃白.'s avatar
璃白. 已提交
71
      @callUserList="callUserList = $event"
璃白.'s avatar
fix  
璃白. 已提交
72
      v-else
璃白.'s avatar
璃白. 已提交
73
    />
璃白.'s avatar
璃白. 已提交
74
    <div v-if="maxLength && showWordLimit && !showPreview" class="word_limit">
75 76 77 78
      <span>{{ textLength }}</span
      >/<span>{{ maxLength }}</span>
    </div>
    <!-- <markdown-footer
璃白.'s avatar
璃白. 已提交
79 80 81
      :fileList.sync="fileList"
      :canAttachFile="canAttachFile"
      :isFocus.sync="isFocus"
璃白.'s avatar
璃白. 已提交
82 83
      :can-attach-file="canAttachFile"
      v-if="!showPreview && canAttachFile"
84
    /> -->
璃白.'s avatar
璃白. 已提交
85 86 87
  </div>
</template>
<script>
璃白.'s avatar
璃白. 已提交
88 89 90 91
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";
璃白.'s avatar
璃白. 已提交
92 93 94 95 96 97
import {
  formatText,
  checktUrl,
  getLinkTitle,
  renderLinkCard
} from "@/assets/js/utils";
璃白.'s avatar
璃白. 已提交
98
export default {
璃白.'s avatar
璃白. 已提交
99 100 101 102
  components: {
    markdownHeader,
    markdownFooter,
    markdownEditor,
璃白.'s avatar
璃白. 已提交
103
    markdownPreview
璃白.'s avatar
璃白. 已提交
104
  },
璃白.'s avatar
璃白. 已提交
105 106 107 108 109
  props: {
    placeholder: {
      type: String,
      default: "请输入内容"
    },
110 111 112
    id: {
      type: String,
      default: ""
璃白.'s avatar
璃白. 已提交
113
    },
璃白.'s avatar
fix  
璃白. 已提交
114 115 116 117
    tabSize: {
      type: [String, Number],
      default: ""
    },
118 119 120 121 122
    // canAttachFile: {
    //   type: Boolean,
    //   default: true
    // },
    // 初始化时赋值
璃白.'s avatar
璃白. 已提交
123 124
    value: {
      type: [String, Number],
璃白.'s avatar
璃白. 已提交
125
      default: ""
璃白.'s avatar
璃白. 已提交
126
    },
127 128 129 130 131 132
    // 全屏时的z-index
    zIndex: {
      type: [String, Number],
      default: ""
    },
    // input时间节流
133 134
    throttle: {
      type: Number,
135
      default: 0
136
    },
璃白.'s avatar
fix  
璃白. 已提交
137 138 139 140
    setPreview: {
      type: Boolean,
      default: false
    },
璃白.'s avatar
璃白. 已提交
141 142 143 144
    disabled: {
      type: Boolean,
      default: false
    },
璃白.'s avatar
fix  
璃白. 已提交
145 146
    setFullScreen: {
      // type: Boolean,
璃白.'s avatar
fix  
璃白. 已提交
147 148
      default: ""
    },
149
    // 是否可以预览
璃白.'s avatar
璃白. 已提交
150 151 152 153
    canPreview: {
      type: Boolean,
      default: true
    },
154 155 156 157 158 159 160 161 162 163
    // 主题
    themeOptions: {
      type: Object,
      default: () => {}
    },
    focus: {
      type: Boolean,
      default: false
    },
    // 工具栏
璃白.'s avatar
璃白. 已提交
164 165 166
    toolsOptions: {
      type: Object,
      default: () => {}
167
    },
168
    // 行高度
169 170 171 172
    rows: {
      type: [Number, String],
      default: ""
    },
璃白.'s avatar
璃白. 已提交
173 174 175 176 177
    // 高度
    height: {
      type: Number,
      default: 0
    },
178
    // 最大长度
179 180 181 182
    maxLength: {
      type: [Number, String],
      default: ""
    },
183
    // 显示字数限制
184 185 186 187
    showWordLimit: {
      type: Boolean,
      default: false
    },
188 189
    // 图片路径规则
    filePathRule: {
190
      type: RegExp,
191 192 193 194
      default: () => {}
    }
  },
  computed: {
璃白.'s avatar
璃白. 已提交
195 196 197 198
    // isMobile() {
    //   const isMobile = checkBoswer();
    //   return isMobile;
    // },
199 200
    textareaId() {
      return "textarea_" + this.id;
璃白.'s avatar
璃白. 已提交
201
    },
璃白.'s avatar
fix  
璃白. 已提交
202 203 204
    headId() {
      return "md_header" + this.id;
    },
璃白.'s avatar
璃白. 已提交
205 206 207 208
    textareaHeight() {
      const height = this.height;
      if (!height) return 0;
      return height - 83;
璃白.'s avatar
璃白. 已提交
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
    },
    uploadImgCallBack() {
      const _this = this;
      return function({ url, file: { name } }) {
        const originalText = _this.text;
        const selectionInfo = _this.selectionInfo;
        const newText = formatText(
          originalText,
          selectionInfo,
          "\n![img](",
          `${url} '${name}')\n`
        );
        _this.text = newText;
        _this.$refs.mdUploadFile.value = "";
      };
    },
    uploadFileCallBack() {
      const _this = this;
      return function({ url, file: { name } }) {
        // url = "http://www.baidu.com";
        const originalText = _this.text;
        const selectionInfo = _this.selectionInfo;
        const newText = formatText(
          originalText,
          selectionInfo,
          "\n![file](",
          `${url} '${name}')\n`
        );
        _this.text = newText;
        _this.$refs.mdUploadFile.value = "";
      };
璃白.'s avatar
璃白. 已提交
240
    }
G
guoweijia 已提交
241
  },
璃白.'s avatar
璃白. 已提交
242 243 244 245 246
  data() {
    return {
      fullScreen: false,
      isFocus: false,
      showPreview: false,
璃白.'s avatar
璃白. 已提交
247
      uploadType: "img",
璃白.'s avatar
璃白. 已提交
248
      fileList: [],
璃白.'s avatar
璃白. 已提交
249
      filteredTags: [],
璃白.'s avatar
璃白. 已提交
250 251
      text: "",
      html: "",
璃白.'s avatar
fix  
璃白. 已提交
252
      ulNum: 1,
璃白.'s avatar
璃白. 已提交
253
      formatType: "",
璃白.'s avatar
fix  
璃白. 已提交
254
      htmlMinHeight: 150,
璃白.'s avatar
璃白. 已提交
255
      showHelp: false,
256
      textLength: "",
璃白.'s avatar
璃白. 已提交
257 258
      userList: false,
      callUserList: [],
璃白.'s avatar
璃白. 已提交
259 260 261 262 263 264 265
      selectionInfo: {
        selectorId: "",
        selectionStart: "",
        selectionEnd: ""
      }
    };
  },
266 267 268 269 270 271 272 273
  created() {
    setTimeout(() => {
      this.$emit("load", {
        text: this.text,
        html: this.html
      });
    }, 0);
  },
G
guoweijia 已提交
274
  watch: {
275 276
    focus: {
      handler: function(val) {
璃白.'s avatar
fix  
璃白. 已提交
277 278
        setTimeout(() => {
          const textEl = document.getElementById(this.textareaId);
璃白.'s avatar
璃白. 已提交
279
          if (!textEl) return;
璃白.'s avatar
fix  
璃白. 已提交
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
          if (val) {
            textEl.focus();
          } else {
            textEl.blur();
          }
        });
      }
    },
    showPreview: {
      handler: function(val) {
        this.$emit("changeTab", val);
      }
    },
    setPreview: {
      handler: function(val) {
        this.showPreview = val;
296 297
      }
    },
璃白.'s avatar
fix  
璃白. 已提交
298
    setFullScreen: {
璃白.'s avatar
fix  
璃白. 已提交
299
      handler: function(val) {
璃白.'s avatar
fix  
璃白. 已提交
300 301 302 303 304 305
        this.fullScreen = val;
      }
    },
    fullScreen: {
      handler: function(val) {
        this.$emit("changeFullScreen", val);
璃白.'s avatar
fix  
璃白. 已提交
306 307
      }
    },
308 309 310 311 312 313
    text: {
      immediate: true,
      handler: function(val) {
        this.textLength = val.length;
      }
    },
G
guoweijia 已提交
314
    html: {
璃白.'s avatar
fix  
璃白. 已提交
315
      immediate: false,
G
guoweijia 已提交
316
      handler: function(val) {
317
        const emitContent = {
318 319
          text: this.text,
          html: this.html
320 321 322 323 324
        };
        if (this.filePathRule) {
          const checkResult = checktUrl(val, this.filePathRule);
          emitContent.invalidList = checkResult;
        }
璃白.'s avatar
璃白. 已提交
325 326 327
        if (this.callUserList.length) {
          emitContent.callUserList = this.callUserList;
        }
璃白.'s avatar
璃白. 已提交
328
        emitContent.filteredTags = this.filteredTags;
璃白.'s avatar
璃白. 已提交
329

330 331
        this.$emit("change", emitContent);
        this.$emit("input", emitContent);
G
guoweijia 已提交
332 333
      }
    },
璃白.'s avatar
璃白. 已提交
334 335 336 337 338 339 340
    isFocus: {
      handler: function(val) {
        const value = {
          text: this.text,
          html: this.html
        };
        if (val) {
璃白.'s avatar
璃白. 已提交
341
          this.showHelp = false;
璃白.'s avatar
璃白. 已提交
342 343 344 345 346 347
          this.$emit("focus", value);
        } else {
          this.$emit("blur", value);
        }
      }
    },
璃白.'s avatar
璃白. 已提交
348 349 350 351 352 353
    value: {
      immediate: true,
      handler: function(val) {
        this.text = val;
      }
    },
G
guoweijia 已提交
354 355 356 357
    fileList: {
      immediate: false,
      deep: true,
      handler: function(val) {
璃白.'s avatar
璃白. 已提交
358
        const uploadType = this.uploadType;
璃白.'s avatar
璃白. 已提交
359 360 361
        if (!val.length) return;
        this.$emit("upload", {
          val: val[0],
璃白.'s avatar
璃白. 已提交
362 363 364 365
          callback:
            uploadType === "img"
              ? this.uploadImgCallBack
              : this.uploadFileCallBack
璃白.'s avatar
璃白. 已提交
366 367
        });
        this.fileList = [];
G
guoweijia 已提交
368 369
      }
    }
璃白.'s avatar
璃白. 已提交
370 371
  },
  methods: {
璃白.'s avatar
璃白. 已提交
372 373
    handleUpload(type) {
      this.uploadType = type;
璃白.'s avatar
璃白. 已提交
374 375 376
      this.$refs.mdUploadFile.click();
      document.getElementById(this.textareaId).focus();
    },
璃白.'s avatar
fix  
璃白. 已提交
377 378 379
    handleEnter() {
      this.$refs[this.headId].resetUlNum();
    },
380 381 382
    upload(e) {
      this.fileList = Array.from(e.target.files);
    },
璃白.'s avatar
璃白. 已提交
383 384 385 386 387
    submit() {
      this.$emit("submit", {
        text: this.text,
        html: this.html
      });
璃白.'s avatar
璃白. 已提交
388
    },
璃白.'s avatar
璃白. 已提交
389 390 391 392 393 394 395 396 397
    queryUserList(keyWord) {
      const _this = this;
      this.$emit("queryUserList", {
        keyWord,
        callback: function(list) {
          _this.userList = list;
        }
      });
    },
璃白.'s avatar
璃白. 已提交
398 399 400 401 402 403 404 405 406 407 408
    renderLinksHtml({ vDom, links }) {
      new Promise((resolve, reject) => {
        this.$emit("renderLinks", {
          links,
          callback: function(list) {
            resolve(list);
          }
        });
      }).then(res => {
        console.log("返回的列表", res);
        res.forEach(item => {
璃白.'s avatar
璃白. 已提交
409
          item.csdn = true;
璃白.'s avatar
璃白. 已提交
410
          const linkEl = vDom.querySelector("#" + item.id);
璃白.'s avatar
璃白. 已提交
411 412 413
          const url = item.csdn
            ? "https://link.csdn.net/?target=" + item.url
            : item.url;
璃白.'s avatar
璃白. 已提交
414
          linkEl.id = "md_link_card";
璃白.'s avatar
璃白. 已提交
415 416
          linkEl.className = "md_link_card";
          linkEl.setAttribute("target", "_blank");
璃白.'s avatar
璃白. 已提交
417
          linkEl.setAttribute("href", url);
璃白.'s avatar
璃白. 已提交
418
          const title = getLinkTitle(linkEl);
璃白.'s avatar
璃白. 已提交
419
          linkEl.innerHTML = renderLinkCard(title, item);
璃白.'s avatar
璃白. 已提交
420 421 422
        });
        this.html = vDom.innerHTML;
      });
璃白.'s avatar
璃白. 已提交
423
    }
璃白.'s avatar
璃白. 已提交
424 425 426 427 428
  }
};
</script>
<style lang="less" scoped>
.md_container {
璃白.'s avatar
璃白. 已提交
429
  background: var(--md-editor-frame-bg-color);
璃白.'s avatar
璃白. 已提交
430
  border: 1px solid var(--md-editor-border-color);
璃白.'s avatar
璃白. 已提交
431 432 433
  border-radius: 4px;
  padding: 10px 16px;
  box-sizing: border-box;
璃白.'s avatar
璃白. 已提交
434
  transition: border 0.3s;
435
  position: relative;
璃白.'s avatar
璃白. 已提交
436
  // overflow: hidden; // 注释为了显示@用户的弹窗
437 438 439 440 441 442 443
  &.fullScreen {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    margin: 0;
璃白.'s avatar
璃白. 已提交
444
    border: 1px solid transparent !important;
445 446
    z-index: var(--md-editor-fullScrren-zIndex);
  }
璃白.'s avatar
璃白. 已提交
447
  &.active {
璃白.'s avatar
璃白. 已提交
448
    border: 1px solid var(--md-editor-border-color-active);
璃白.'s avatar
璃白. 已提交
449
  }
璃白.'s avatar
璃白. 已提交
450 451 452
  &.disabled {
    background: var(--md-editor-frame-bg-color-disabled);
  }
453 454 455 456 457 458 459
  .word_limit {
    position: absolute;
    right: 10px;
    bottom: 8px;
    font-size: 12px;
    color: var(--md-editor-text-color);
  }
璃白.'s avatar
璃白. 已提交
460 461
}
</style>