<!--
 * @Date: 2022-09-08 15:10:51
 * @LastEditTime: 2022-10-25 16:38:47
 * @LastEditors: Irene
 * @FilePath: /bi-element/packages/insert-input/src/main.vue
-->
<template>
  <div class="el-insert-input">
    <div class="el-insert-input__add-field">
      <el-dropdown @command="handleCommand">
        <span class="el-dropdown-link">
          <i class="bi-iconfont-add-circle-fill"></i>插入变量
        </span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item
            v-for="item in dropList"
            :command="item"
            :key="item.key"
            :disabled="!multiple && tagArr.includes(item.key)"
            >{{ item.value }}
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
      <span
        :class="[
          'el-insert-input__maxlength',
          length > maxLength && 'el-insert-input__maxlength-max',
        ]"
      >
        {{ length }}/{{ maxLength }}
      </span>
    </div>

    <div
      id="text"
      @keydown.delete="deleteContent"
      class="el-insert-input__textarea"
      @input="notify"
      ref="textRef"
    ></div>
  </div>
</template>

<script>
const uuid = require('uuid');
export default {
  name: 'ElInsertInput',
  props: {
    multiple: {
      type: Boolean,
      default: false
    },
    dropList: {
      type: Array,
      default: () => []
    },
    value: {
      type: String,
      default: ''
    },
    validContent: {
      // 格式校验提示语
      type: String,
      default: '格式错误!'
    },
    maxLength: {
      // 文本最大长度
      type: Number,
      default: 10000
    },
    defaultAddFn: {
      // 点击插入后默认触发事件
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      length: 0,
      selectedList: [],
      show: false,
      beforeRange: null, // 存储上一次的range
      range: null,
      areaId: 'text',
      rangeId: null,
      beforeRangeId: null,
      currentRangeId: null,
      endOffset: null,
      tagArr: [] // 插入后获取所有的icon
    };
  },
  mounted() {
    if (this.value) this.insertVal(this.value);
    document.addEventListener('selectionchange', this.selectionHandler);
  },
  beforeDestroy() {
    document.removeEventListener('selectionchange', this.selectionHandler);
  },
  methods: {
    /**
     * @description: 删除标签时，连同空格一起删除
     * @return {*}
     */
    deleteContent() {
      this.selectionHandler(); // 手动获取光标移动

      const { commonAncestorContainer: rangeEle } = this.range;

      const range = document.createRange();
      this.$refs.textRef.childNodes.forEach((element) => {
        const node = element.attributes
          ? element.attributes['tag-data-id']
          : null;
        const text = element.attributes
          ? element.attributes['text-data-id']
          : null;
        if (node && node.nodeValue === this.beforeRangeId) {
          // 当该node前面是标签时 删除前方标签
          range.selectNode(rangeEle);
          range.deleteContents();
        } else if (
          text &&
          text.nodeValue === this.currentRangeId &&
          (rangeEle.length === 1 || rangeEle.textContent === '\n') &&
          this.endOffset !== 0
        ) {
          // 此处判断当node为span且其中只有一个字符时，清空内容但阻止删除该span空标签
          console.log(this.endOffset);
          if (element.innerHTML.length === 1 || rangeEle.textContent === '\n') {
            this.beforeDel(element);
          }
        }
      });
      this.notify();
    },
    beforeDel(element) {
      const dom = this.range.createContextualFragment(' ');
      this.range.insertNode(dom);
      setTimeout(() => {
        element.innerHTML = '';
      }, 0);
    },
    /**
     * @description: 通知修改内容
     * @return {*}
     */
    notify() {
      const arr = document.querySelectorAll('#text i'); // 插入后获取所有的icon
      this.tagArr = [];

      arr.forEach((ele) => {
        if (ele.attributes) {
          this.tagArr.push(ele.attributes['icon-id'].nodeValue);
        }
      });

      this.$emit('input', this.$refs.textRef.innerHTML);
      this.$emit('getValue', this.getTextWithTem());
      this.$emit('getLength', this.length);
    },

    /**
     * @description: 移动光标
     * @param {*} lastUUID
     * @return {*}
     */
    manualHandler(lastUUID) {
      const range = document.createRange();

      this.$refs.textRef.childNodes.forEach((element) => {
        if (
          element.attributes &&
          element.attributes['text-data-id'] &&
          element.attributes['text-data-id'].nodeValue === lastUUID
        ) {
          // const sel = window.getSelection();
          // sel.collapse(element, 0);// 光标移动至最后
          range.selectNodeContents(element);
          this.range = range;
        }
      });
    },

    /**
     * @description: 校验是否含有关键字
     * @return {*}
     */
    valid() {
      const TEXT = this.$refs.textRef.innerText;
      if (!TEXT) return true;

      // if (this.length > this.maxLength) return false;

      const contentWithNoSpace = TEXT.split('\n').join('');
      return !/\$\{.*?\}/g.test(contentWithNoSpace);
    },
    /**
     * @description: 生成模板字符
     * @return {*}
     */
    getTextWithTem() {
      if (this.validContent && !this.valid()) {
        this.$message({
          message: this.validContent,
          type: 'warning'
        });
      }
      let arr = [];
      let length = 0;

      this.$refs.textRef.childNodes.forEach((element) => {
        switch (element.className) {
          case 'text':
            arr.push(element.innerText);
            length += element.innerText.length;
            break;
          case 'text-var':
            arr.push(element.attributes.key.nodeValue);
            length += 5;
            break;
          default:
            break;
        }

        if (element.className === undefined) {
          arr.push(element.data);
          length += element.length;
        }
      });

      this.length = length;
      return arr.join('');
    },
    /**
     * @description: 移动光标 记录位置
     * @return {*}
     */
    selectionHandler() {
      this.endOffset = null;
      const sel = window.getSelection();

      const range = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
      const { endOffset, commonAncestorContainer } = range;
      const {
        ownerDocument,
        textContent,
        data,
        previousElementSibling,
        className,
        parentElement,
        innerText
      } = commonAncestorContainer;
      // nodeName
      if (range && ownerDocument.activeElement.id === this.areaId) {
        // 判断是是否作用于该输入框，避免出现一个页面多个相同输入框冲突
        this.range = range;

        // 父级classList 用了就无法记录
        // const arr = Array.from(parentElement.classList);

        if (className !== 'text') {
          // 判断当用户直接点到text文本本身 此时是没有text样式的。
          this.endOffset = endOffset;

          this.beforeRangeId = null;
          this.currentRangeId =
            parentElement.className === 'text'
              ? parentElement.attributes['text-data-id'].nodeValue
              : null;
        } else if (innerText === '\n\n') {
          // 排除class为text的换行符innerText;
          this.currentRangeId =
            commonAncestorContainer.attributes['text-data-id'].nodeValue;
        } else {
          // textContent内容以判断换行内容，data只能记录每一行的内容
          this.beforeRangeId =
            !data && textContent === ''
              ? previousElementSibling && previousElementSibling.attributes
                ? previousElementSibling.attributes['tag-data-id'].nodeValue
                : null
              : null;
        }
      }
    },
    /**
     * @description: 未选中时初始化range
     * @return {*}
     */
    initRange() {
      this.range = document.createRange();
      this.range.selectNodeContents(this.$refs.textRef);
    },
    /**
     * @description: 插入模块
     * @param {*} node
     * @return {*}
     */
    insertVal(node) {
      if (!this.range) this.initRange();

      const {
        parentElement,
        nodeValue,
        className
      } = this.range.commonAncestorContainer;
      if (parentElement.parentElement.className === 'text-var') return; // 点击到了标签内，不作处理

      let dom = ''; // 要插入的节点
      const placeholderDom = (val, uuid) =>
        `<span text-data-id="${uuid}" class="text">${val}</span>`; // 占位标签模板
      let lastUUID = ''; // 随机ID

      if ((nodeValue && nodeValue.length > 0) || className === 'text') {
        // 选中的位置为span
        const index = this.range.endOffset;

        const start = !nodeValue ? '' : nodeValue.substring(0, index);
        const end = !nodeValue
          ? ''
          : nodeValue.substring(index, nodeValue.length);
        lastUUID = uuid.v1();

        dom = this.range.createContextualFragment(
          `${placeholderDom(start, uuid.v1())}${node}${placeholderDom(
            end,
            lastUUID
          )}`
        );

        const range = document.createRange();
        [parentElement.className].includes('text')
          ? range.selectNode(parentElement)
          : range.selectNode(this.range.commonAncestorContainer);
        range.deleteContents();
      } else {
        // 选中的node为初始化文本
        lastUUID = uuid.v1();
        dom = this.range.createContextualFragment(
          `${node}${placeholderDom('', lastUUID)}`
        );

        if ([parentElement.className].includes('text')) {
          dom =
            parentElement.innerText.length > 1
              ? this.range.createContextualFragment(
                `${node}${placeholderDom('', lastUUID)}`
              )
              : this.range.createContextualFragment(
                `${placeholderDom('', uuid.v1())}${node}${placeholderDom(
                  '',
                  lastUUID
                )}`
              );
          // dom = this.range.createContextualFragment(`${placeholderDom('', uuid.v1())}${node}${placeholderDom('', lastUUID)}`);
          const range = document.createRange();
          range.selectNode(parentElement);
          range.deleteContents();
        }
      }

      this.range.insertNode(dom);
      const arr = document.querySelectorAll('.text-var .bi-iconfont-setting'); // 插入后获取所有的icon

      this.manualHandler(lastUUID); // 光标位置移动
      this.notify(); // 通知

      arr.forEach((item) => {
        const id = item.getAttribute('icon-id');
        if (!item.onclick) {
          item.onclick = () => {
            this.clickIcon(id);
          };
        }
      });
    },

    /**
     * @description: 点击icon回调
     * @param {*} e
     * @return {*}
     */
    clickIcon(e) {
      this.$emit('getTagId', e);
    },

    /**
     * @description: 生成HTML并添加
     * @param {*} val
     * @return {*}
     */
    handleCommand(val) {
      const div = `<span class="text-var" tag-data-id="${uuid.v1()}" contentEditable="false" key="${
        val.key
      }"><span class="text">${val.value}</span><i icon-id=${
        val.key
      } class="bi-iconfont-setting"></i></span>`;
      this.insertVal(div);
      if (this.defaultAddFn) this.clickIcon(val.key);
    }
  }
};
</script>
