md-textarea.vue 8.6 KB
Newer Older
璃白.'s avatar
璃白. 已提交
1
<template>
璃白.'s avatar
璃白. 已提交
2
  <div :class="['md_textarea', { fullScreen, isFocus }]">
璃白.'s avatar
璃白. 已提交
3
    <textarea
璃白.'s avatar
璃白. 已提交
4
      spellcheck="false"
璃白.'s avatar
璃白. 已提交
5
      :id="id"
璃白.'s avatar
璃白. 已提交
6
      @change="$emit('update:text', textContent)"
7
      @input="input"
璃白.'s avatar
璃白. 已提交
8 9
      @focus="setFocus(true)"
      @blur="setFocus(false)"
G
guoweijia 已提交
10
      @paste="pasteFile"
璃白.'s avatar
fix  
璃白. 已提交
11
      @keydown.enter="$emit('enter')"
璃白.'s avatar
璃白. 已提交
12 13
      @keydown.meta.enter.exact="submit"
      @keydown.ctrl.enter.exact="submit"
璃白.'s avatar
fix  
璃白. 已提交
14
      @keydown.tab.prevent="$emit('tab')"
璃白.'s avatar
璃白. 已提交
15
      v-model="textContent"
璃白.'s avatar
璃白. 已提交
16
      :placeholder="placeholder"
17 18
      :maxlength="maxLength"
      :rows="rows"
璃白.'s avatar
璃白. 已提交
19
      :disabled="disabled"
璃白.'s avatar
璃白. 已提交
20 21 22
      :style="{
        height: editorHeight,
        overflow: editorOverFlow,
璃白.'s avatar
璃白. 已提交
23 24 25
        cursor: disabled
          ? 'not-allowed'
          : formatType
璃白.'s avatar
璃白. 已提交
26 27 28
          ? `url(https://codechina.csdn.net/codechina/operation-work/uploads/a1b7c2a995b2320dca911e2f2ecb9b88/format.png),text`
          : 'text'
      }"
璃白.'s avatar
璃白. 已提交
29 30
    >
    </textarea>
璃白.'s avatar
璃白. 已提交
31 32 33 34 35 36 37
    <transition name="slide-fade">
      <helpDoc
        v-if="showHelp"
        @updateShowHelp="$emit('updateShowHelp', $event)"
        :showHelp.sync="showHelp"
      />
    </transition>
璃白.'s avatar
璃白. 已提交
38 39 40
  </div>
</template>
<script>
41 42 43
import {
  getSelectionInfo,
  getPosition,
璃白.'s avatar
璃白. 已提交
44
  getFilteredTags,
璃白.'s avatar
璃白. 已提交
45 46
  getLinkTags,
  addLanguageClass,
47 48
  throttle as throttleFn
} from "@/assets/js/utils";
璃白.'s avatar
璃白. 已提交
49
import eventBus from "@/assets/js/eventBus";
璃白.'s avatar
fix  
璃白. 已提交
50
import marked from "marked";
璃白.'s avatar
璃白. 已提交
51 52
import helpDoc from "./components/help-doc";
import DOMPurify from "dompurify";
璃白.'s avatar
璃白. 已提交
53
export default {
璃白.'s avatar
璃白. 已提交
54
  components: { helpDoc },
璃白.'s avatar
璃白. 已提交
55
  props: {
56 57 58 59
    id: {
      type: String,
      default: ""
    },
璃白.'s avatar
fix  
璃白. 已提交
60 61 62 63 64 65 66
    html: {
      type: String,
      default: ""
    },
    htmlMinHeight: {
      default: ""
    },
璃白.'s avatar
璃白. 已提交
67 68 69 70
    fullScreen: {
      type: Boolean,
      default: false
    },
71 72 73 74
    throttleTime: {
      type: Number,
      default: 1000
    },
璃白.'s avatar
璃白. 已提交
75 76 77 78
    isFocus: {
      type: Boolean,
      default: false
    },
璃白.'s avatar
璃白. 已提交
79 80 81 82
    disabled: {
      type: Boolean,
      default: false
    },
璃白.'s avatar
璃白. 已提交
83 84 85 86 87 88
    placeholder: {
      type: String,
      default: false
    },
    fileList: {
      type: Array,
璃白.'s avatar
璃白. 已提交
89
      default: () => []
璃白.'s avatar
璃白. 已提交
90 91
    },
    text: {
璃白.'s avatar
璃白. 已提交
92 93
      type: [String, Number],
      default: ""
璃白.'s avatar
璃白. 已提交
94
    },
95 96 97 98 99 100 101 102
    maxLength: {
      type: [String, Number],
      default: ""
    },
    rows: {
      type: [String, Number],
      default: ""
    },
璃白.'s avatar
璃白. 已提交
103 104 105 106
    height: {
      type: Number,
      default: 0
    },
璃白.'s avatar
璃白. 已提交
107 108
    selectionInfo: {
      type: Object,
璃白.'s avatar
璃白. 已提交
109
      default: () => {}
璃白.'s avatar
璃白. 已提交
110 111 112 113 114 115 116
    },
    formatType: {
      default: ""
    },
    showHelp: {
      type: Boolean,
      default: false
璃白.'s avatar
璃白. 已提交
117 118
    }
  },
119

璃白.'s avatar
璃白. 已提交
120 121
  data() {
    return {
122
      textContent: "",
123 124
      editorHeight: "auto",
      editorOverFlow: "auto"
璃白.'s avatar
璃白. 已提交
125 126 127 128 129
    };
  },
  created() {
    document.addEventListener("mouseup", this.checkSelection);
  },
璃白.'s avatar
fix  
璃白. 已提交
130
  mounted() {
璃白.'s avatar
fix  
璃白. 已提交
131
    this.resetPreviewMinHeight();
璃白.'s avatar
fix  
璃白. 已提交
132
  },
璃白.'s avatar
璃白. 已提交
133
  watch: {
璃白.'s avatar
fix  
璃白. 已提交
134 135 136 137 138 139 140
    isFocus: {
      handler: function(val) {
        if (val) {
          this.resetPreviewMinHeight();
        }
      }
    },
璃白.'s avatar
璃白. 已提交
141 142 143 144
    text: {
      immediate: true,
      handler: function(val) {
        this.textContent = val;
璃白.'s avatar
fix  
璃白. 已提交
145
        this.transferMarkdown(val);
璃白.'s avatar
璃白. 已提交
146
      }
147
    },
148 149
    fullScreen: {
      immediate: true,
璃白.'s avatar
璃白. 已提交
150 151 152 153 154 155
      handler: function(val) {
        if (val) {
          document.body.style.overflow = "hidden";
        } else {
          document.body.style.overflow = "auto";
        }
156
        setTimeout(() => {
璃白.'s avatar
fix  
璃白. 已提交
157
          this.reSizeTextareaHeight();
158 159 160
        }, 0);
      }
    },
161 162 163 164 165
    textContent: {
      immediate: true,
      handler: function() {
        setTimeout(() => {
          if (!this.autoSize) return;
璃白.'s avatar
fix  
璃白. 已提交
166
          this.reSizeTextareaHeight();
167 168
        }, 0);
      }
璃白.'s avatar
璃白. 已提交
169 170 171 172 173
    }
  },
  beforeDestroy() {
    document.removeEventListener("mouseup", this.checkSelection);
  },
174 175
  computed: {
    emitText() {
璃白.'s avatar
fix  
璃白. 已提交
176 177
      // return throttleFn(() => {}, this.throttleTime);
      return () => {
178
        this.$emit("update:text", this.textContent);
璃白.'s avatar
fix  
璃白. 已提交
179
      };
180 181 182 183 184
    },
    autoSize() {
      return this.rows === "auto";
    }
  },
璃白.'s avatar
璃白. 已提交
185
  methods: {
璃白.'s avatar
fix  
璃白. 已提交
186 187 188 189 190 191 192
    resetPreviewMinHeight() {
      setTimeout(() => {
        const textEl = document.getElementById(this.id);
        if (!textEl) return;
        const height = textEl.offsetHeight;
        this.$emit("update:htmlMinHeight", height);
      }, 0);
璃白.'s avatar
fix  
璃白. 已提交
193 194 195
    },
    transferMarkdown(val) {
      marked.setOptions({
196 197 198
        breaks: true,
        gfm: true,
        langPrefix: "language-",
璃白.'s avatar
fix  
璃白. 已提交
199
        highlight: function(code, lang, callback) {
璃白.'s avatar
璃白. 已提交
200
          let html = require("highlight.js").highlightAuto(code).value;
璃白.'s avatar
fix  
璃白. 已提交
201 202 203 204
          return html;
        }
      });
      const str = val + "";
璃白.'s avatar
璃白. 已提交
205 206
      const html = marked(str); // 解析markdown
      const virtualDom = addLanguageClass(html); // 如果没指定语言,添加默认语言
207 208
      const cleanHtml = DOMPurify.sanitize(virtualDom.innerHTML, {
        FORBID_TAGS: ["style", "script"]
璃白.'s avatar
璃白. 已提交
209 210 211 212
      }); // 去除标签
      const filteredTags = getFilteredTags(html, cleanHtml); // 计算是否有标签被过滤
      // 链接转换为卡片
      const { vDom, links } = getLinkTags(this.id, cleanHtml);
璃白.'s avatar
璃白. 已提交
213 214

      this.$emit("getFilteredTags", filteredTags);
215
      this.$emit("update:html", cleanHtml);
璃白.'s avatar
璃白. 已提交
216
      this.$emit("renderLinksHtml", { vDom, links });
璃白.'s avatar
fix  
璃白. 已提交
217
    },
218 219 220 221
    input() {
      this.$emit("update:textLength", this.textContent.length);
      this.emitText();
    },
璃白.'s avatar
fix  
璃白. 已提交
222
    reSizeTextareaHeight() {
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
      const textEl = document.getElementById(this.id);
      if (!textEl) return;
      const fontSize = getComputedStyle(textEl).getPropertyValue("font-size");
      const lineHeight = getComputedStyle(textEl).getPropertyValue(
        "line-height"
      );

      const fontFamily = getComputedStyle(textEl).getPropertyValue(
        "font-family"
      );
      const hideElId = "hdieEl" + this.id;
      let hideEl = document.getElementById(hideElId);

      if (!hideEl) {
        hideEl = document.createElement("div");
        textEl.parentNode.appendChild(hideEl);
      }
      hideEl.id = hideElId;
      hideEl.style.fontSize = fontSize;
      hideEl.style.lineHeight = lineHeight;
      hideEl.style.fontFamily = fontFamily;
      hideEl.innerText = this.textContent;
      const contentHeight = hideEl.offsetHeight;
246 247
      this.editorHeight = this.fullScreen
        ? "calc(100% - 42px)"
璃白.'s avatar
璃白. 已提交
248 249
        : this.height
        ? this.height + "px"
250 251 252 253
        : this.autoSize
        ? `${contentHeight + parseFloat(fontSize) * 1.2}px`
        : "auto";
      this.editorOverFlow =
璃白.'s avatar
fix  
璃白. 已提交
254
        this.autoSize && !this.fullScreen && !this.height ? "hidden" : "auto";
255 256
      textEl.parentNode.removeChild(hideEl);
    },
璃白.'s avatar
璃白. 已提交
257 258 259
    submit() {
      this.$emit("submit");
    },
璃白.'s avatar
璃白. 已提交
260
    setFocus(val) {
璃白.'s avatar
璃白. 已提交
261
      this.$emit("update:isFocus", val);
璃白.'s avatar
璃白. 已提交
262
    },
璃白.'s avatar
璃白. 已提交
263 264
    checkSelection() {
      const info = getSelectionInfo(this.id);
璃白.'s avatar
璃白. 已提交
265 266
      if (!info) {
        const cursorPoint = getPosition(this.id);
璃白.'s avatar
璃白. 已提交
267
        this.$emit("update:selectionInfo", {
璃白.'s avatar
璃白. 已提交
268 269 270
          selectorId: this.id,
          selectionStart: cursorPoint,
          selectionEnd: cursorPoint
璃白.'s avatar
璃白. 已提交
271
        });
璃白.'s avatar
璃白. 已提交
272 273
        return;
      }
璃白.'s avatar
璃白. 已提交
274
      this.$emit("update:selectionInfo", info);
G
guoweijia 已提交
275 276 277 278 279 280 281 282 283 284 285
    },
    pasteFile(event) {
      let fileList = [];
      const items = (event.clipboardData || window.clipboardData).items;
      for (let i = 0; i < items.length; i++) {
        if (items[i].type.indexOf("image") !== -1) {
          fileList.push(items[i].getAsFile());
          break;
        }
      }
      if (!fileList.length) return;
璃白.'s avatar
璃白. 已提交
286
      this.$emit("update:fileList", fileList);
璃白.'s avatar
璃白. 已提交
287 288 289 290 291 292 293
    }
  }
};
</script>
<style lang="less" scoped>
.md_textarea {
  position: relative;
294
  padding: 14px 0;
璃白.'s avatar
璃白. 已提交
295
  background: var(--md-editor-content-bg-color);
296 297
  // border-left: 1px solid var(--md-editor-border-color);
  // border-right: 1px solid var(--md-editor-border-color);
璃白.'s avatar
璃白. 已提交
298
  transition: border 0.3s;
299
  // padding: 14px;
璃白.'s avatar
璃白. 已提交
300
  box-sizing: border-box;
301 302 303 304
  // &.isFocus {
  //   border-left: 1px solid var(--md-editor-border-color-active);
  //   border-right: 1px solid var(--md-editor-border-color-active);
  // }
305

璃白.'s avatar
璃白. 已提交
306
  &.fullScreen {
307
    height: 100%;
璃白.'s avatar
璃白. 已提交
308 309
    textarea {
      font-size: 20px;
310 311
      max-height: 100%;
      overflow-y: auto;
璃白.'s avatar
璃白. 已提交
312 313
    }
  }
璃白.'s avatar
璃白. 已提交
314 315 316
  &.disabled {
    background: var(--md-editor-content-bg-color-disabled);
  }
317

璃白.'s avatar
璃白. 已提交
318 319 320 321 322
  textarea {
    display: block;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
璃白.'s avatar
璃白. 已提交
323
    background: var(--md-editor-content-bg-color);
324 325
    color: var(--md-editor-text-color-active);
    height: var(--md-editor-height);
璃白.'s avatar
璃白. 已提交
326
    resize: none;
璃白.'s avatar
璃白. 已提交
327 328 329
    font-family: "Menlo", -apple-system, SF UI Text, Arial, PingFang SC,
      Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif, SimHei,
      SimSun;
330 331 332
    &::placeholder {
      color: var(--md-editor-text-color);
    }
璃白.'s avatar
璃白. 已提交
333 334 335
    // &:disabled {
    //   background: var(--md-editor-content-bg-color-disabled);
    // }
璃白.'s avatar
璃白. 已提交
336 337 338 339 340 341 342 343 344 345
  }
  .icon {
    position: absolute;
    top: 20px;
    right: 20px;
    font-size: 32px;
    cursor: pointer;
  }
}
</style>