md-textarea.vue 7.5 KB
Newer Older
璃白.'s avatar
璃白. 已提交
1
<template>
璃白.'s avatar
璃白. 已提交
2
  <div :class="['md_textarea', { fullScreen, isFocus }]">
璃白.'s avatar
璃白. 已提交
3 4
    <textarea
      :id="id"
璃白.'s avatar
璃白. 已提交
5
      @change="$emit('update:text', textContent)"
6
      @input="input"
璃白.'s avatar
璃白. 已提交
7 8
      @focus="setFocus(true)"
      @blur="setFocus(false)"
G
guoweijia 已提交
9
      @paste="pasteFile"
璃白.'s avatar
fix  
璃白. 已提交
10
      @keydown.enter="$emit('enter')"
璃白.'s avatar
璃白. 已提交
11 12
      @keydown.meta.enter.exact="submit"
      @keydown.ctrl.enter.exact="submit"
璃白.'s avatar
fix  
璃白. 已提交
13
      @keydown.tab.prevent="$emit('tab')"
璃白.'s avatar
璃白. 已提交
14
      v-model="textContent"
璃白.'s avatar
璃白. 已提交
15
      :placeholder="placeholder"
16 17
      :maxlength="maxLength"
      :rows="rows"
18
      :style="{ height: editorHeight, overflow: editorOverFlow }"
璃白.'s avatar
璃白. 已提交
19 20 21 22 23
    >
    </textarea>
  </div>
</template>
<script>
24 25 26 27 28
import {
  getSelectionInfo,
  getPosition,
  throttle as throttleFn
} from "@/assets/js/utils";
璃白.'s avatar
fix  
璃白. 已提交
29
import marked from "marked";
璃白.'s avatar
璃白. 已提交
30
export default {
璃白.'s avatar
璃白. 已提交
31
  props: {
32 33 34 35
    id: {
      type: String,
      default: ""
    },
璃白.'s avatar
fix  
璃白. 已提交
36 37 38 39 40 41 42
    html: {
      type: String,
      default: ""
    },
    htmlMinHeight: {
      default: ""
    },
璃白.'s avatar
璃白. 已提交
43 44 45 46
    fullScreen: {
      type: Boolean,
      default: false
    },
47 48 49 50
    throttleTime: {
      type: Number,
      default: 1000
    },
璃白.'s avatar
璃白. 已提交
51 52 53 54 55 56 57 58 59 60
    isFocus: {
      type: Boolean,
      default: false
    },
    placeholder: {
      type: String,
      default: false
    },
    fileList: {
      type: Array,
璃白.'s avatar
璃白. 已提交
61
      default: () => []
璃白.'s avatar
璃白. 已提交
62 63
    },
    text: {
璃白.'s avatar
璃白. 已提交
64 65
      type: [String, Number],
      default: ""
璃白.'s avatar
璃白. 已提交
66
    },
67 68 69 70 71 72 73 74
    maxLength: {
      type: [String, Number],
      default: ""
    },
    rows: {
      type: [String, Number],
      default: ""
    },
璃白.'s avatar
璃白. 已提交
75 76
    selectionInfo: {
      type: Object,
璃白.'s avatar
璃白. 已提交
77
      default: () => {}
璃白.'s avatar
璃白. 已提交
78 79
    }
  },
80

璃白.'s avatar
璃白. 已提交
81 82
  data() {
    return {
83
      textContent: "",
84 85
      editorHeight: "auto",
      editorOverFlow: "auto"
璃白.'s avatar
璃白. 已提交
86 87 88 89 90
    };
  },
  created() {
    document.addEventListener("mouseup", this.checkSelection);
  },
璃白.'s avatar
fix  
璃白. 已提交
91
  mounted() {
璃白.'s avatar
fix  
璃白. 已提交
92
    this.resetPreviewMinHeight();
璃白.'s avatar
fix  
璃白. 已提交
93
  },
璃白.'s avatar
璃白. 已提交
94
  watch: {
璃白.'s avatar
fix  
璃白. 已提交
95 96 97 98 99 100 101
    isFocus: {
      handler: function(val) {
        if (val) {
          this.resetPreviewMinHeight();
        }
      }
    },
璃白.'s avatar
璃白. 已提交
102 103 104
    text: {
      immediate: true,
      handler: function(val) {
璃白.'s avatar
fix  
璃白. 已提交
105
        const cursorPoint = getPosition(this.id);
璃白.'s avatar
璃白. 已提交
106
        this.textContent = val;
璃白.'s avatar
fix  
璃白. 已提交
107
        this.transferMarkdown(val);
璃白.'s avatar
璃白. 已提交
108
      }
109
    },
110 111
    fullScreen: {
      immediate: true,
璃白.'s avatar
璃白. 已提交
112 113 114 115 116 117
      handler: function(val) {
        if (val) {
          document.body.style.overflow = "hidden";
        } else {
          document.body.style.overflow = "auto";
        }
118
        setTimeout(() => {
璃白.'s avatar
fix  
璃白. 已提交
119
          this.reSizeTextareaHeight();
120 121 122
        }, 0);
      }
    },
123 124 125 126 127
    textContent: {
      immediate: true,
      handler: function() {
        setTimeout(() => {
          if (!this.autoSize) return;
璃白.'s avatar
fix  
璃白. 已提交
128
          this.reSizeTextareaHeight();
129 130
        }, 0);
      }
璃白.'s avatar
璃白. 已提交
131 132 133 134 135
    }
  },
  beforeDestroy() {
    document.removeEventListener("mouseup", this.checkSelection);
  },
136 137
  computed: {
    emitText() {
璃白.'s avatar
fix  
璃白. 已提交
138 139
      // return throttleFn(() => {}, this.throttleTime);
      return () => {
140
        this.$emit("update:text", this.textContent);
璃白.'s avatar
fix  
璃白. 已提交
141
      };
142 143 144 145 146
    },
    autoSize() {
      return this.rows === "auto";
    }
  },
璃白.'s avatar
璃白. 已提交
147
  methods: {
璃白.'s avatar
fix  
璃白. 已提交
148 149 150 151 152 153 154
    resetPreviewMinHeight() {
      setTimeout(() => {
        const textEl = document.getElementById(this.id);
        if (!textEl) return;
        const height = textEl.offsetHeight;
        this.$emit("update:htmlMinHeight", height);
      }, 0);
璃白.'s avatar
fix  
璃白. 已提交
155 156 157
    },
    transferMarkdown(val) {
      marked.setOptions({
158 159 160
        breaks: true,
        gfm: true,
        langPrefix: "language-",
璃白.'s avatar
fix  
璃白. 已提交
161 162
        highlight: function(code, lang, callback) {
          const html = require("highlight.js").highlightAuto(code).value;
163 164 165 166

          // const html = require("highlight.js").highlight(code, {
          //   language: lang || "xml"
          // }).value;
璃白.'s avatar
fix  
璃白. 已提交
167 168 169 170 171 172
          return html;
        }
      });
      const str = val + "";
      // if (!str.trim()) return;
      const html = marked(str);
173 174 175 176 177 178 179 180 181 182 183 184
      const virtualDom = document.createElement("div");
      virtualDom.innerHTML = html;
      virtualDom.querySelectorAll("code").forEach(item => {
        if (!/language-/.test(item.className)) {
          item.className = "language-xml";
        }
      });
      const DOMPurify = require("dompurify");
      const cleanHtml = DOMPurify.sanitize(virtualDom.innerHTML, {
        FORBID_TAGS: ["style", "script"]
      });
      this.$emit("update:html", cleanHtml);
璃白.'s avatar
fix  
璃白. 已提交
185
    },
186 187 188 189
    input() {
      this.$emit("update:textLength", this.textContent.length);
      this.emitText();
    },
璃白.'s avatar
fix  
璃白. 已提交
190
    reSizeTextareaHeight() {
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
      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;
214 215 216 217 218 219 220
      this.editorHeight = this.fullScreen
        ? "calc(100% - 42px)"
        : this.autoSize
        ? `${contentHeight + parseFloat(fontSize) * 1.2}px`
        : "auto";
      this.editorOverFlow =
        this.autoSize && !this.fullScreen ? "hidden" : "auto";
221 222
      textEl.parentNode.removeChild(hideEl);
    },
璃白.'s avatar
璃白. 已提交
223 224 225
    submit() {
      this.$emit("submit");
    },
璃白.'s avatar
璃白. 已提交
226
    setFocus(val) {
璃白.'s avatar
璃白. 已提交
227
      this.$emit("update:isFocus", val);
璃白.'s avatar
璃白. 已提交
228
    },
璃白.'s avatar
璃白. 已提交
229 230
    checkSelection() {
      const info = getSelectionInfo(this.id);
璃白.'s avatar
璃白. 已提交
231 232
      if (!info) {
        const cursorPoint = getPosition(this.id);
璃白.'s avatar
璃白. 已提交
233
        this.$emit("update:selectionInfo", {
璃白.'s avatar
璃白. 已提交
234 235 236
          selectorId: this.id,
          selectionStart: cursorPoint,
          selectionEnd: cursorPoint
璃白.'s avatar
璃白. 已提交
237
        });
璃白.'s avatar
璃白. 已提交
238 239
        return;
      }
璃白.'s avatar
璃白. 已提交
240
      this.$emit("update:selectionInfo", info);
G
guoweijia 已提交
241 242 243 244 245 246 247 248 249 250 251
    },
    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
璃白. 已提交
252
      this.$emit("update:fileList", fileList);
璃白.'s avatar
璃白. 已提交
253 254 255 256 257 258 259
    }
  }
};
</script>
<style lang="less" scoped>
.md_textarea {
  position: relative;
260
  padding: 14px 0;
璃白.'s avatar
璃白. 已提交
261
  background: var(--md-editor-content-bg-color);
262 263
  // border-left: 1px solid var(--md-editor-border-color);
  // border-right: 1px solid var(--md-editor-border-color);
璃白.'s avatar
璃白. 已提交
264
  transition: border 0.3s;
265
  // padding: 14px;
璃白.'s avatar
璃白. 已提交
266
  box-sizing: border-box;
267 268 269 270
  // &.isFocus {
  //   border-left: 1px solid var(--md-editor-border-color-active);
  //   border-right: 1px solid var(--md-editor-border-color-active);
  // }
271

璃白.'s avatar
璃白. 已提交
272
  &.fullScreen {
273
    height: 100%;
璃白.'s avatar
璃白. 已提交
274 275
    textarea {
      font-size: 20px;
276 277
      max-height: 100%;
      overflow-y: auto;
璃白.'s avatar
璃白. 已提交
278 279
    }
  }
280

璃白.'s avatar
璃白. 已提交
281 282 283 284 285
  textarea {
    display: block;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
璃白.'s avatar
璃白. 已提交
286
    background: var(--md-editor-content-bg-color);
287 288
    color: var(--md-editor-text-color-active);
    height: var(--md-editor-height);
璃白.'s avatar
璃白. 已提交
289
    resize: none;
290

璃白.'s avatar
璃白. 已提交
291 292
    font-family: "Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas",
      "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace;
293 294 295
    &::placeholder {
      color: var(--md-editor-text-color);
    }
璃白.'s avatar
璃白. 已提交
296 297 298 299 300 301 302 303 304 305
  }
  .icon {
    position: absolute;
    top: 20px;
    right: 20px;
    font-size: 32px;
    cursor: pointer;
  }
}
</style>