<template>
  <view class="sv-editor-wrapper" @longpress="eLongpress">
    <slot name="header"></slot>
    <editor
      :id="eid"
      class="sv-editor-container"
      show-img-size
      show-img-toolbar
      show-img-resize
      :placeholder="placeholder"
      :read-only="isReadOnly"
      @statuschange="onStatusChange"
      @ready="onEditorReady"
      @input="onEditorInput"
      @focus="onEditorFocus"
      @blur="onEditorBlur"
    ></editor>
    <view class="maxlength-tip" v-if="maxlength > 0 && !hideMax">{{ textlength }}/{{ maxlength }}</view>
    <slot name="footer"></slot>
    <!-- renderjs辅助插件 -->
    <!-- #ifdef APP || H5 -->
    <sv-editor-render ref="editorRenderRef" :eid="editorEID"></sv-editor-render>
    <sv-editor-plugin ref="editorPluginRef" :sid="startID" :eid="editorEID" @epaste="ePaste"></sv-editor-plugin>
    <!-- #endif -->
  </view>
</template>

<script>
/**
 * sv-editor
 * @author sonve
 * @version 1.0.0
 * @date 2024-12-04
 */

import store from '../common/store.js'
import { linkFlag, copyrightPrint } from '../common/utils.js'
import { parseHtmlWithVideo, replaceVideoWithImageRender } from '../common/parse.js'
import SvEditorRender from './sv-editor-render.vue'
import SvEditorPlugin from '../plugins/sv-editor-plugin.vue'
import wxplugin from '../plugins/sv-editor-wxplugin.js'

export default {
  // #ifdef MP-WEIXIN
  // 微信小程序特殊配置
  options: {
    addGlobalClass: true,
    virtualHost: true,
    styleIsolation: 'shared'
  },
  // #endif
  components: {
    SvEditorRender,
    SvEditorPlugin
  },
  props: {
    // 编辑器id可传入，以便循环组件使用，防止id重复
    eid: {
      type: String,
      default: 'sv-editor' // 唯一，禁止重复
    },
    placeholder: {
      type: String,
      default: '写点什么吧 ~'
    },
    // 是否只读
    readOnly: {
      type: Boolean,
      default: false
    },
    // 最大字数限制，<=0时表示不限
    maxlength: {
      type: Number,
      default: -1
    },
    // 是否关闭最大字数显示
    hideMax: {
      type: Boolean,
      default: false
    },
    // 粘贴模式，可选 text 纯文本(默认) | origin 尽可能保持原格式
    pasteMode: {
      type: String,
      default: 'text'
    }
  },
  emits: ['ready', 'input', 'statuschange', 'focus', 'blur', 'overmax', 'epaste'],
  data() {
    return {
      textlength: 0, // 当前字数统计
      startID: '',
      // #ifdef VUE2
      // #ifdef MP-WEIXIN
      editorIns: null // 仅vue2环境下的微信小程序需要声明实例变量，否则报错，属实逆天
      // #endif
      // #endif
    }
  },
  computed: {
    editorEID: {
      set(newVal) {
        store.actions.setEID(newVal)
      },
      get() {
        return store.actions.getEID()
      }
    },
    editorCtx: {
      set(newVal) {
        store.actions.setEditor(newVal.eid, newVal.ctx)
        // #ifdef VUE2
        this.editorIns = newVal.ctx
        this.editorIns.id = newVal.eid
        // #endif
      },
      get() {
        let instance = store.actions.getEditor(this.eid)
        // #ifdef VUE2
        instance = store.actions.getEditor(this.eid) || this.editorIns
        // #endif
        return instance
      }
    },
    isReadOnly: {
      set(newVal) {
        store.actions.setReadOnly(newVal)
      },
      get() {
        return store.actions.getReadOnly()
      }
    }
  },
  watch: {
    readOnly(newVal) {
      this.isReadOnly = newVal
    }
  },
  mounted() {
    // 首个实例初始化时执行
    if (!store.state.firstInstanceFlag) {
      this.editorEID = this.eid
      store.state.firstInstanceFlag = this.eid
      copyrightPrint()
    }
  },
  destroyed() {
    store.actions.destroy()
  },
  unmounted() {
    store.actions.destroy()
  },
  methods: {
    onEditorReady() {
      this.$nextTick(() => {
        uni
          .createSelectorQuery()
          .in(this)
          .select('#' + this.eid)
          .context((res) => {
            // 存储上下文
            this.editorCtx = { eid: this.eid, ctx: res.context }
            // 挂载实例api
            this.bindMethods()
            // 初始化完成
            this.$emit('ready', this.editorCtx)

            // #ifdef APP || H5
            if (this.pasteMode == 'origin') this.editorCtx.changePasteMode('origin')
            // #endif
          })
          .exec()
      })
    },
    /**
     * 挂载实例api
     */
    bindMethods() {
      // ===== renderjs相关扩展api =====

      // #ifdef APP || H5
      /**
       * 主动聚焦
       * @returns {void}
       */
      this.editorCtx.focus = this.$refs.editorRenderRef.focus

      /**
       * 退格
       * @returns {void}
       */
      this.editorCtx.backspace = this.$refs.editorRenderRef.backspace

      /**
       * 键盘输入模式
       * @param {String} type 模式，可选：none | remove
       * @returns {void}
       */
      this.editorCtx.changeInputMode = this.$refs.editorRenderRef.changeInputMode

      /**
       * 粘贴模式
       * @param {String} type 模式，可选：text纯文本(默认) | origin尽可能保持原格式
       * @returns {void}
       */
      this.editorCtx.changePasteMode = (type) => {
        // 告知plugin启动
        this.startID = this.eid
        if (this.$refs.editorPluginRef?.changePasteMode) {
          this.$refs.editorPluginRef.changePasteMode(type)
        }
      }

      /**
       * 生成视频封面图
       * @param {String} url 封面图片地址
       * @returns {Promise} 携带播放图标的封面图地址
       */
      this.editorCtx.createCoverThumbnail = (url) => {
        return new Promise((resolve) => {
          if (this.$refs.editorPluginRef?.createCoverThumbnail) {
            // 事件名必须唯一，否则会覆盖
            uni.$once(`E_EDITOR_GET_COVER_THUMBNAIL_${url}`, (res) => {
              resolve(res.cover)
            })
            setTimeout(() => {
              this.$refs.editorPluginRef?.createCoverThumbnail(url)
            })
          }
        })
      }

      /**
       * 生成视频封面图
       * @param {String} url 视频地址
       * @returns {Promise} 封面图地址
       */
      this.editorCtx.createVideoThumbnail = (url) => {
        return new Promise((resolve) => {
          if (this.$refs.editorPluginRef?.createVideoThumbnail) {
            // 事件名必须唯一，否则会覆盖
            uni.$once(`E_EDITOR_GET_VIDEO_THUMBNAIL_${url}`, (res) => {
              resolve(res.cover)
            })
            setTimeout(() => {
              this.$refs.editorPluginRef?.createVideoThumbnail(url)
            })
          }
        })
      }

      // #endif

      // ===== 微信小程序扩展api =====

      // #ifdef MP-WEIXIN

      /**
       * 生成视频封面图
       * @param {String} url 视频地址
       * @returns {Promise} 封面图地址
       */
      this.editorCtx.createCoverThumbnail = wxplugin?.wxCreateCoverThumbnail

      // #endif

      // ===== 通用扩展api =====

      /**
       * 主动触发input回调事件
       * @returns {void}
       */
      this.editorCtx.changeInput = () => {
        this.editorCtx.getContents({
          success: (res) => {
            this.$emit('input', { ctx: this.editorCtx, html: res.html, text: res.text })
          }
        })
      }

      /**
       * 获取最新内容
       * @returns {Promise} 内容对象 { html, text... }
       */
      this.editorCtx.getLastContent = async () => {
        return new Promise((resolve) => {
          this.editorCtx.getContents({
            success: (res) => {
              resolve(res)
            }
          })
        })
      }

      /**
       * 富文本内容初始化
       * 注意：微信小程序会导致聚焦滚动，建议先将编辑器v-show=false，待initHtml内容初始化完成后再true
       * 也正是因为微信小程序端会聚焦滚动，所以editorEID在初始阶段会默认保持最后一个实例eid，需要手动重新聚焦
       * @param {String} html 初始化的富文本
       * @param {Function<Promise>} customCallback 自定义处理封面回调，需要以Promise形式返回封面图片资源
       * @returns {void}
       */
      this.editorCtx.initHtml = async (html, customCallback) => {
        let transHtml = await replaceVideoWithImageRender(html, customCallback)
        // #ifdef APP || H5
        this.editorCtx.changePasteMode('text') // text模式下可以防止初始化时对格式的影响
        // #endif
        setTimeout(() => {
          this.editorCtx.setContents({
            html: transHtml,
            success: () => {
              // 主动触发一次input回调事件
              this.editorCtx.changeInput()
              // #ifdef APP || H5
              if (this.pasteMode == 'origin') this.editorCtx.changePasteMode('origin')
              // #endif
            }
          })
        })
      }

      /**
       * 导出处理
       * @param {String} html 要导出的富文本
       * @returns {String} 处理后的富文本
       */
      this.editorCtx.exportHtml = (html) => {
        return parseHtmlWithVideo(html)
      }
    },
    onEditorInput(e) {
      // 注意不要使用getContents获取html和text，会导致重复触发onStatusChange从而失去toolbar工具的高亮状态
      // 复制粘贴的时候detail会为空，此时应当直接return
      if (Object.keys(e.detail).length <= 0) return
      const { html, text } = e.detail

      // 识别到链接特殊标识立即return
      if (text.indexOf(linkFlag) !== -1) return

      /**
       * 因为uni-editor不提供最大字符限制，故需要手动进行以下特殊处理
       */
      const maxlength = parseInt(this.maxlength)
      const textStr = text.replace(/[ \t\r\n]/g, '')
      this.textlength = textStr.length // 当前字符数
      if (this.textlength >= maxlength && maxlength > 0) {
        this.textlength = maxlength // 因为editor特性，需要手动赋阈值
        if (!this.lockHtmlFlag) {
          this.lockHtml = html // 锁定最后一次超出字数前的html
          this.lockHtmlFlag = true // 锁定标志
          // 首次到达最大限制时还需最后回调一次input事件
          this.$emit('input', { ctx: this.editorCtx, html, text })
        } else {
          // 在超过字数时锁定，若再编辑则抛出超出事件
          this.$emit('overmax', { ctx: this.editorCtx })
        }
        // 超过字数时，锁定最后一次超出字数前的html
        this.editorCtx.setContents({ html: this.lockHtml })
      } else {
        // 正常输入
        this.$emit('input', { ctx: this.editorCtx, html, text })
        this.lockHtmlFlag = false // 锁定标志
      }
    },
    /**
     * 样式格式改变时触发
     * 注意：微信小程序端在多编辑器实例下切换编辑器后可能不会及时触发onStatusChange
     */
    onStatusChange(e) {
      store.actions.setFormats(e.detail)
      this.$emit('statuschange', { ...e, ctx: this.editorCtx })
      uni.$emit('E_EDITOR_STATUSCHANGE', { ...e, ctx: this.editorCtx })
    },
    onEditorFocus(e) {
      this.editorEID = this.eid
      this.$emit('focus', { ...e, ctx: this.editorCtx })
    },
    onEditorBlur(e) {
      this.$emit('blur', { ...e, ctx: this.editorCtx })
    },
    ePaste(e) {
      this.$emit('epaste', { ...e, ctx: this.editorCtx })
      uni.$emit('E_EDITOR_PASTE', { ...e, ctx: this.editorCtx })
    },
    /**
     * 微信小程序官方editor的长按事件有bug，需要重写覆盖，不需做任何逻辑，可见下面小程序社区问题链接
     * @tutorial https://developers.weixin.qq.com/community/develop/doc/000c04b3e1c1006f660065e4f61000
     */
    eLongpress() {}
  }
}
</script>

<style lang="scss">
.sv-editor-wrapper {
  --maxlength-text-color: #666666;

  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;

  .sv-editor-container {
    flex: 1;
  }

  .maxlength-tip {
    position: absolute;
    bottom: 0;
    right: 0;
    font-size: 24rpx;
    color: var(--maxlength-text-color);
    opacity: 0.6;
  }
}

// placeholder字样
.sv-editor-container ::v-deep .ql-blank::before {
  font-style: normal;
  color: #cccccc;
}

// 图片工具样式
::v-deep .ql-container {
  min-height: unset;

  .ql-image-overlay {
    pointer-events: none;

    .ql-image-size {
      right: 28px !important;
    }
    .ql-image-toolbar {
      // 删除按钮
      pointer-events: auto;
    }
    .ql-image-handle {
      // 四角缩放按钮
      width: 30px;
      height: 30px;
      pointer-events: auto;
    }
  }
}
</style>
