md-textarea.vue 14.0 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
      @keydown.stop.50="handleCallUser"
      @keydown.stop.229="handleCallUser"
璃白.'s avatar
璃白. 已提交
10
      @focus="setFocus(true)"
璃白.'s avatar
璃白. 已提交
11
      @blur="blur"
G
guoweijia 已提交
12
      @paste="pasteFile"
璃白.'s avatar
璃白. 已提交
13 14 15 16
      @keydown.stop.left="handlePointMove"
      @keydown.stop.right="handlePointMove"
      @keydown.stop.up="changeActiveSelectIndex($event, 'up')"
      @keydown.stop.down="changeActiveSelectIndex($event, 'down')"
璃白.'s avatar
璃白. 已提交
17
      @keydown.enter="handleEnter"
璃白.'s avatar
璃白. 已提交
18 19
      @keydown.meta.enter.exact="submit"
      @keydown.ctrl.enter.exact="submit"
璃白.'s avatar
fix  
璃白. 已提交
20
      @keydown.tab.prevent="$emit('tab')"
璃白.'s avatar
璃白. 已提交
21
      v-model="textContent"
璃白.'s avatar
璃白. 已提交
22
      :placeholder="placeholder"
23 24
      :maxlength="maxLength"
      :rows="rows"
璃白.'s avatar
璃白. 已提交
25
      :disabled="disabled"
璃白.'s avatar
璃白. 已提交
26
      :class="{ disabled }"
璃白.'s avatar
璃白. 已提交
27 28 29
      :style="{
        height: editorHeight,
        overflow: editorOverFlow,
璃白.'s avatar
璃白. 已提交
30 31
        cursor: disabled
          ? 'not-allowed'
璃白.'s avatar
璃白. 已提交
32 33
          : waiting
          ? 'wait'
璃白.'s avatar
璃白. 已提交
34
          : formatType
璃白.'s avatar
璃白. 已提交
35 36 37
          ? `url(https://codechina.csdn.net/codechina/operation-work/uploads/a1b7c2a995b2320dca911e2f2ecb9b88/format.png),text`
          : 'text'
      }"
璃白.'s avatar
璃白. 已提交
38 39
    >
    </textarea>
璃白.'s avatar
璃白. 已提交
40 41
    <transition name="slide-fade">
      <helpDoc
郭维嘉 已提交
42 43 44 45 46 47 48
        v-if="showHelp === 'help'"
        @updateShowDoc="$emit('updateShowDoc', $event)"
        :showHelp.sync="showHelp"
      />
      <dirDoc
        v-if="showHelp === 'dir'"
        @updateShowDoc="$emit('updateShowDoc', $event)"
璃白.'s avatar
璃白. 已提交
49 50 51
        :showHelp.sync="showHelp"
      />
    </transition>
璃白.'s avatar
璃白. 已提交
52
    <transition-group name="slideup-fade">
璃白.'s avatar
璃白. 已提交
53
      <selectUser
璃白.'s avatar
璃白. 已提交
54
        key="selectUser"
璃白.'s avatar
璃白. 已提交
55
        :userList="userList"
璃白.'s avatar
璃白. 已提交
56
        :activeUserIndex.sync="activeUserIndex"
璃白.'s avatar
璃白. 已提交
57 58
        v-show="showSelectUser"
        :position="selectUserPosition"
璃白.'s avatar
璃白. 已提交
59
        @selectUser="handleSelectUser"
璃白.'s avatar
璃白. 已提交
60
      />
璃白.'s avatar
璃白. 已提交
61 62 63 64 65 66 67 68
      <selectLinkType
        key="selectLinkType"
        :activeLinkTypeIndex.sync="activeLinkTypeIndex"
        :position="selectLinkTypePosition"
        v-show="showSelectLinkType"
        @selectLinkType="handleSelectLinkType"
      />
    </transition-group>
璃白.'s avatar
璃白. 已提交
69 70 71
  </div>
</template>
<script>
72 73 74
import {
  getSelectionInfo,
  getPosition,
璃白.'s avatar
璃白. 已提交
75
  isAndroid,
76 77
  throttle as throttleFn
} from "@/assets/js/utils";
璃白.'s avatar
璃白. 已提交
78 79 80
import selectUser from "./components/user-select.vue";
import selectLinkType from "./components/link-type-select.vue";
import helpDoc from "./components/help-doc.vue";
郭维嘉 已提交
81
import dirDoc from "./components/toc-doc.vue";
璃白.'s avatar
璃白. 已提交
82 83
import renderMix from "./mixins/render-mixins";
import selectUserMix from "./mixins/select-user-mixins";
璃白.'s avatar
璃白. 已提交
84
import selectLinkTypeMix from "./mixins/select-link-type-mixins";
璃白.'s avatar
璃白. 已提交
85
export default {
郭维嘉 已提交
86
  components: { helpDoc, dirDoc, selectUser, selectLinkType },
璃白.'s avatar
璃白. 已提交
87
  mixins: [renderMix, selectUserMix, selectLinkTypeMix],
璃白.'s avatar
璃白. 已提交
88
  props: {
89 90 91 92
    id: {
      type: String,
      default: ""
    },
璃白.'s avatar
fix  
璃白. 已提交
93
    html: {
璃白.'s avatar
璃白. 已提交
94
      type: [String, Promise],
璃白.'s avatar
fix  
璃白. 已提交
95 96 97 98 99
      default: ""
    },
    htmlMinHeight: {
      default: ""
    },
璃白.'s avatar
璃白. 已提交
100 101 102 103
    fullScreen: {
      type: Boolean,
      default: false
    },
104 105 106 107
    throttleTime: {
      type: Number,
      default: 1000
    },
璃白.'s avatar
璃白. 已提交
108 109 110 111
    isFocus: {
      type: Boolean,
      default: false
    },
璃白.'s avatar
璃白. 已提交
112 113 114 115
    disabled: {
      type: Boolean,
      default: false
    },
璃白.'s avatar
璃白. 已提交
116 117 118 119 120 121
    placeholder: {
      type: String,
      default: false
    },
    fileList: {
      type: Array,
璃白.'s avatar
璃白. 已提交
122
      default: () => []
璃白.'s avatar
璃白. 已提交
123 124
    },
    text: {
璃白.'s avatar
璃白. 已提交
125 126
      type: [String, Number],
      default: ""
璃白.'s avatar
璃白. 已提交
127
    },
128 129 130 131 132 133 134 135
    maxLength: {
      type: [String, Number],
      default: ""
    },
    rows: {
      type: [String, Number],
      default: ""
    },
璃白.'s avatar
璃白. 已提交
136 137 138 139
    height: {
      type: Number,
      default: 0
    },
璃白.'s avatar
璃白. 已提交
140 141
    selectionInfo: {
      type: Object,
璃白.'s avatar
璃白. 已提交
142
      default: () => {}
璃白.'s avatar
璃白. 已提交
143 144 145 146 147
    },
    formatType: {
      default: ""
    },
    showHelp: {
郭维嘉 已提交
148
      type: [Boolean, String],
璃白.'s avatar
璃白. 已提交
149
      default: false
璃白.'s avatar
璃白. 已提交
150 151
    },
    userList: {
璃白.'s avatar
璃白. 已提交
152 153
      type: [Boolean, Array],
      default: false
璃白.'s avatar
璃白. 已提交
154 155 156 157
    },
    renderLinks: {
      type: Boolean,
      default: false
璃白.'s avatar
璃白. 已提交
158 159
    }
  },
160

璃白.'s avatar
璃白. 已提交
161 162
  data() {
    return {
163
      textContent: "",
164
      editorHeight: "auto",
璃白.'s avatar
璃白. 已提交
165 166
      editorOverFlow: "auto",
      showSelectUser: false,
璃白.'s avatar
璃白. 已提交
167
      showSelectLinkType: false,
璃白.'s avatar
璃白. 已提交
168 169 170 171 172
      queryInfo: {
        startPosition: "",
        endPosition: "",
        keyWord: ""
      },
璃白.'s avatar
璃白. 已提交
173
      waiting: false,
璃白.'s avatar
璃白. 已提交
174
      activeUserIndex: 0,
璃白.'s avatar
璃白. 已提交
175 176 177
      activeLinkTypeIndex: 0,
      selectUserPosition: { left: 0, top: 0 },
      selectLinkTypePosition: { left: 0, top: 0 }
璃白.'s avatar
璃白. 已提交
178 179 180 181 182
    };
  },
  created() {
    document.addEventListener("mouseup", this.checkSelection);
  },
璃白.'s avatar
fix  
璃白. 已提交
183
  mounted() {
璃白.'s avatar
fix  
璃白. 已提交
184
    this.resetPreviewMinHeight();
璃白.'s avatar
fix  
璃白. 已提交
185
  },
璃白.'s avatar
璃白. 已提交
186
  watch: {
187 188 189 190 191
    userList: {
      handler: function (val, old) {
        if (!old && val) this.createSelectUserDialog();
      }
    },
璃白.'s avatar
fix  
璃白. 已提交
192
    isFocus: {
193
      handler: function (val) {
璃白.'s avatar
fix  
璃白. 已提交
194 195
        if (val) {
          this.resetPreviewMinHeight();
璃白.'s avatar
璃白. 已提交
196
        } else {
璃白.'s avatar
璃白. 已提交
197
          setTimeout(() => {
璃白.'s avatar
璃白. 已提交
198
            this.showSelectUser = false;
璃白.'s avatar
璃白. 已提交
199
            this.showSelectLinkType = false;
200
          }, 100);
璃白.'s avatar
fix  
璃白. 已提交
201 202 203
        }
      }
    },
璃白.'s avatar
璃白. 已提交
204 205
    text: {
      immediate: true,
206
      handler: function (val) {
璃白.'s avatar
璃白. 已提交
207
        this.textContent = val;
璃白.'s avatar
fix  
璃白. 已提交
208
        this.transferMarkdown(val);
璃白.'s avatar
璃白. 已提交
209
      }
210
    },
211 212
    fullScreen: {
      immediate: true,
213
      handler: function (val) {
璃白.'s avatar
璃白. 已提交
214 215 216 217 218
        if (val) {
          document.body.style.overflow = "hidden";
        } else {
          document.body.style.overflow = "auto";
        }
219
        setTimeout(() => {
璃白.'s avatar
fix  
璃白. 已提交
220
          this.reSizeTextareaHeight();
221 222 223
        }, 0);
      }
    },
224 225
    textContent: {
      immediate: true,
226
      handler: function () {
227 228
        setTimeout(() => {
          if (!this.autoSize) return;
璃白.'s avatar
fix  
璃白. 已提交
229
          this.reSizeTextareaHeight();
230 231
        }, 0);
      }
璃白.'s avatar
璃白. 已提交
232 233
    },
    showSelectUser: {
234
      handler: function (val) {
璃白.'s avatar
璃白. 已提交
235
        if (!val) {
236 237 238
          setTimeout(() => {
            this.resetQueryInfo();
          }, 50);
璃白.'s avatar
璃白. 已提交
239 240
        }
      }
璃白.'s avatar
璃白. 已提交
241 242 243 244 245
    }
  },
  beforeDestroy() {
    document.removeEventListener("mouseup", this.checkSelection);
  },
246 247
  computed: {
    emitText() {
璃白.'s avatar
fix  
璃白. 已提交
248 249
      // return throttleFn(() => {}, this.throttleTime);
      return () => {
250
        this.$emit("update:text", this.textContent);
璃白.'s avatar
fix  
璃白. 已提交
251
      };
252 253 254 255 256
    },
    autoSize() {
      return this.rows === "auto";
    }
  },
璃白.'s avatar
璃白. 已提交
257
  methods: {
璃白.'s avatar
璃白. 已提交
258
    changeActiveSelectIndex(event, type) {
璃白.'s avatar
璃白. 已提交
259
      if (this.showSelectUser) {
璃白.'s avatar
璃白. 已提交
260
        event.preventDefault();
璃白.'s avatar
璃白. 已提交
261 262 263 264 265 266
        const max = this.userList.length;
        if (type === "down") {
          this.activeUserIndex++;
          if (this.activeUserIndex >= max) {
            this.activeUserIndex = 0;
          }
璃白.'s avatar
璃白. 已提交
267
          const index = this.activeUserIndex;
璃白.'s avatar
璃白. 已提交
268 269 270 271 272
          const activeItem = document.getElementById("md_user_id_" + index);
          const activeTop =
            activeItem.offsetTop - activeItem.parentNode.scrollTop;

          if (index === 0) {
璃白.'s avatar
璃白. 已提交
273
            document
璃白.'s avatar
璃白. 已提交
274 275 276 277 278 279 280
              .getElementById("md_user_id_" + index)
              .parentNode.scrollTo(0, 0);
          }
          if (index > 3 && activeTop > 126) {
            document
              .getElementById("md_user_id_" + index)
              .parentNode.scrollTo(0, 40 * (index - 3));
璃白.'s avatar
璃白. 已提交
281 282
            // .scrollIntoView({ behavior: "smooth" });
          }
璃白.'s avatar
璃白. 已提交
283 284 285 286 287
        } else {
          this.activeUserIndex--;
          if (this.activeUserIndex < 0) {
            this.activeUserIndex = max - 1;
          }
璃白.'s avatar
璃白. 已提交
288
          const index = this.activeUserIndex;
璃白.'s avatar
璃白. 已提交
289 290 291 292 293 294 295 296 297 298 299 300 301 302
          const activeItem = document.getElementById("md_user_id_" + index);
          const activeTop =
            activeItem.offsetTop - activeItem.parentNode.scrollTop;
          if (index === max - 1) {
            activeItem.parentNode.scrollTo(
              0,
              activeItem.parentNode.scrollHeight
            );
          }
          if (activeTop < 46) {
            document
              .getElementById("md_user_id_" + this.activeUserIndex)
              .parentNode.scrollTo(0, 40 * index);
          }
璃白.'s avatar
璃白. 已提交
303
          // .scrollIntoView({ behavior: "smooth" });
璃白.'s avatar
璃白. 已提交
304
        }
璃白.'s avatar
璃白. 已提交
305 306 307 308 309 310 311 312 313 314 315 316 317 318
      } else if (this.showSelectLinkType) {
        event.preventDefault();
        const max = 3;
        if (type === "down") {
          this.activeLinkTypeIndex++;
          if (this.activeLinkTypeIndex >= max) {
            this.activeLinkTypeIndex = 0;
          }
        } else {
          this.activeLinkTypeIndex--;
          if (this.activeLinkTypeIndex < 0) {
            this.activeLinkTypeIndex = max - 1;
          }
        }
璃白.'s avatar
璃白. 已提交
319 320
      }
    },
璃白.'s avatar
fix  
璃白. 已提交
321 322 323 324 325 326 327
    resetPreviewMinHeight() {
      setTimeout(() => {
        const textEl = document.getElementById(this.id);
        if (!textEl) return;
        const height = textEl.offsetHeight;
        this.$emit("update:htmlMinHeight", height);
      }, 0);
璃白.'s avatar
fix  
璃白. 已提交
328
    },
璃白.'s avatar
璃白. 已提交
329 330 331 332 333 334 335
    resetQueryInfo() {
      this.queryInfo = {
        startPosition: "",
        endPosition: "",
        keyWord: ""
      };
    },
璃白.'s avatar
璃白. 已提交
336
    input(e) {
璃白.'s avatar
璃白. 已提交
337 338
      if (isAndroid() && e.data === "@") {
        this.createSelectUserDialog("android");
璃白.'s avatar
璃白. 已提交
339
      }
璃白.'s avatar
璃白. 已提交
340
      if (this.showSelectUser) this.handleQueryUser(e);
璃白.'s avatar
璃白. 已提交
341 342 343
      if (this.showSelectLinkType) {
        this.showSelectLinkType = false;
      }
344 345 346
      this.$emit("update:textLength", this.textContent.length);
      this.emitText();
    },
璃白.'s avatar
璃白. 已提交
347
    blur() {
璃白.'s avatar
璃白. 已提交
348
      this.renderUserTags();
璃白.'s avatar
璃白. 已提交
349 350
      this.setFocus(false);
    },
璃白.'s avatar
璃白. 已提交
351
    createHideEl(type) {
352 353 354
      const textEl = document.getElementById(this.id);
      if (!textEl) return;
      const fontSize = getComputedStyle(textEl).getPropertyValue("font-size");
355 356 357 358
      const lineHeight =
        getComputedStyle(textEl).getPropertyValue("line-height");
      const fontFamily =
        getComputedStyle(textEl).getPropertyValue("font-family");
璃白.'s avatar
璃白. 已提交
359
      const hideElId = type + this.id;
360 361 362 363 364 365 366 367 368
      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;
璃白.'s avatar
璃白. 已提交
369 370 371 372 373 374 375 376
      return hideEl;
    },
    reSizeTextareaHeight() {
      const textEl = document.getElementById(this.id);
      if (!textEl) return;
      const fontSize = getComputedStyle(textEl).getPropertyValue("font-size");

      const hideEl = this.createHideEl("clac_height_El_");
377 378
      hideEl.innerText = this.textContent;
      const contentHeight = hideEl.offsetHeight;
379 380
      this.editorHeight = this.fullScreen
        ? "calc(100% - 42px)"
璃白.'s avatar
璃白. 已提交
381 382
        : this.height
        ? this.height + "px"
383 384 385 386
        : this.autoSize
        ? `${contentHeight + parseFloat(fontSize) * 1.2}px`
        : "auto";
      this.editorOverFlow =
璃白.'s avatar
fix  
璃白. 已提交
387
        this.autoSize && !this.fullScreen && !this.height ? "hidden" : "auto";
388 389
      textEl.parentNode.removeChild(hideEl);
    },
璃白.'s avatar
璃白. 已提交
390 391 392 393 394 395
    handlePointMove() {
      if (this.showSelectLinkType) {
        this.showSelectLinkType = false;
        return;
      }
    },
璃白.'s avatar
璃白. 已提交
396
    handleEnter(e) {
璃白.'s avatar
璃白. 已提交
397
      if (this.showSelectUser) {
璃白.'s avatar
璃白. 已提交
398 399 400
        const activeUser = this.userList[this.activeUserIndex];
        this.handleSelectUser(activeUser);
        e.preventDefault();
璃白.'s avatar
璃白. 已提交
401
        return;
璃白.'s avatar
璃白. 已提交
402 403 404 405
      } else if (this.showSelectLinkType) {
        this.handleSelectLinkType(this.activeLinkTypeIndex);
        e.preventDefault();
        return;
璃白.'s avatar
璃白. 已提交
406 407 408
      }
      this.$emit("enter");
    },
璃白.'s avatar
璃白. 已提交
409 410 411
    submit() {
      this.$emit("submit");
    },
璃白.'s avatar
璃白. 已提交
412
    setFocus(val) {
璃白.'s avatar
璃白. 已提交
413
      this.$emit("update:isFocus", val);
璃白.'s avatar
璃白. 已提交
414
    },
璃白.'s avatar
璃白. 已提交
415 416
    checkSelection() {
      const info = getSelectionInfo(this.id);
璃白.'s avatar
璃白. 已提交
417 418
      if (!info) {
        const cursorPoint = getPosition(this.id);
璃白.'s avatar
璃白. 已提交
419
        this.$emit("update:selectionInfo", {
璃白.'s avatar
璃白. 已提交
420 421 422
          selectorId: this.id,
          selectionStart: cursorPoint,
          selectionEnd: cursorPoint
璃白.'s avatar
璃白. 已提交
423
        });
璃白.'s avatar
璃白. 已提交
424 425
        return;
      }
璃白.'s avatar
璃白. 已提交
426
      this.$emit("update:selectionInfo", info);
G
guoweijia 已提交
427 428 429 430 431 432 433 434 435
    },
    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;
        }
璃白.'s avatar
璃白. 已提交
436
        if (items[i].type.indexOf("text") !== -1) {
璃白.'s avatar
璃白. 已提交
437
          if (!this.renderLinks) return;
璃白.'s avatar
璃白. 已提交
438 439 440 441 442 443 444
          items[i].getAsString(str => {
            if (
              !/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/.test(
                str
              )
            )
              return;
445 446 447 448 449 450
            const cursorPoint = getPosition(this.id);
            const prefixStr = this.textContent.slice(
              cursorPoint - str.length - 5,
              cursorPoint - str.length
            );
            if (prefixStr.includes("src=")) return;
璃白.'s avatar
璃白. 已提交
451 452 453 454 455
            this.createSelectLinkTypeDialog();
          });

          break;
        }
G
guoweijia 已提交
456 457
      }
      if (!fileList.length) return;
璃白.'s avatar
璃白. 已提交
458
      this.checkSelection();
璃白.'s avatar
璃白. 已提交
459
      this.$emit("update:fileList", fileList);
璃白.'s avatar
璃白. 已提交
460 461 462 463 464 465 466
    }
  }
};
</script>
<style lang="less" scoped>
.md_textarea {
  position: relative;
467
  padding: 14px 0;
璃白.'s avatar
璃白. 已提交
468
  background: var(--md-editor-content-bg-color);
469 470
  // border-left: 1px solid var(--md-editor-border-color);
  // border-right: 1px solid var(--md-editor-border-color);
璃白.'s avatar
璃白. 已提交
471
  transition: border 0.3s;
472
  // padding: 14px;
璃白.'s avatar
璃白. 已提交
473
  box-sizing: border-box;
474 475 476 477
  // &.isFocus {
  //   border-left: 1px solid var(--md-editor-border-color-active);
  //   border-right: 1px solid var(--md-editor-border-color-active);
  // }
478

璃白.'s avatar
璃白. 已提交
479
  &.fullScreen {
480
    height: 100%;
璃白.'s avatar
璃白. 已提交
481 482
    textarea {
      font-size: 20px;
483 484
      max-height: 100%;
      overflow-y: auto;
璃白.'s avatar
璃白. 已提交
485 486
    }
  }
璃白.'s avatar
璃白. 已提交
487 488 489
  &.disabled {
    background: var(--md-editor-content-bg-color-disabled);
  }
490

璃白.'s avatar
璃白. 已提交
491 492 493 494 495
  textarea {
    display: block;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
璃白.'s avatar
璃白. 已提交
496
    background: var(--md-editor-content-bg-color);
497 498
    color: var(--md-editor-text-color-active);
    height: var(--md-editor-height);
璃白.'s avatar
璃白. 已提交
499
    resize: none;
璃白.'s avatar
璃白. 已提交
500
    font-size: 14px;
璃白.'s avatar
璃白. 已提交
501
    line-height: 1.625;
璃白.'s avatar
璃白. 已提交
502
    word-break: break-all;
璃白.'s avatar
璃白. 已提交
503 504 505
    font-family: "Menlo", -apple-system, SF UI Text, Arial, PingFang SC,
      Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif, SimHei,
      SimSun;
506 507 508
    &::placeholder {
      color: var(--md-editor-text-color);
    }
璃白.'s avatar
璃白. 已提交
509 510 511
    &.disabled {
      opacity: var(--md-editor-disabled-opacity);
    }
璃白.'s avatar
璃白. 已提交
512 513 514
    // &:disabled {
    //   background: var(--md-editor-content-bg-color-disabled);
    // }
璃白.'s avatar
璃白. 已提交
515 516 517 518 519 520 521 522 523 524
  }
  .icon {
    position: absolute;
    top: 20px;
    right: 20px;
    font-size: 32px;
    cursor: pointer;
  }
}
</style>