<script setup>
/**
 * CustomEditor 组件
 * 
 * 这是一个自定义富文本编辑器组件，支持以下功能：
 * - 文本编辑和格式化
 * - 表情符号插入
 * - 图片上传和插入
 * - 文件上传
 * - @提及功能
 * - 引用回复
 * - 消息编辑
 * - 草稿保存和恢复
 */

// Vue 核心功能导入
import { ref, computed, onMounted, onBeforeUnmount, nextTick, markRaw, watch } from 'vue'

// Naive UI 组件导入
import { NPopover, NIcon } from 'naive-ui'

// 图标导入
import {
  SmilingFace, // 表情图标
  Pic,         // 图片图标
  FolderUpload // 文件上传图标
} from '@icon-park/vue-next'
import {IosSend} from '@vicons/ionicons4' // 发送图标

// 工具和API导入
import { bus } from '@/utils/event-bus'                              // 事件总线工具，用于组件间通信
import { EditorConst } from '@/constant/event-bus'                   // 编辑器相关的事件常量定义
import { emitCall } from '@/utils/common'                            // 通用工具函数，用于创建事件数据
import { deltaToMessage, deltaToString, isEmptyDelta } from './util' // 编辑器内容处理工具函数
import { uploadImg } from '@/api/upload'                             // 图片上传API
import { defAvatar } from '@/constant/default'                       // 默认头像常量
import { getImageInfo } from '@/utils/functions'                     // 获取图片信息函数

// Store导入
import { useDialogueStore, useEditorDraftStore, useUserStore } from '@/store' // Pinia Store

// 子组件导入
import MeEditorEmoticon from './MeEditorEmoticon.vue' // 表情选择器组件
/**
 * 组件属性定义
 */
const props = defineProps({
  vote: {
    type: Boolean,
    default: false,
    // 是否启用投票功能
  },
  members: {
    type: Array,
    default: () => [],
    // 可@提及的成员列表
  },
  placeholder: {
    type: String,
    default: 'Enter-发送消息  [Ctrl+Enter/Shift+Enter]-换行',
    // 编辑器占位文本
  }
})

/**
 * 组件事件定义
 * editor-event: 编辑器各种事件的统一出口，通过event字段区分不同事件类型
 */
const emit = defineEmits(['editor-event'])

/**
 * Store实例
 */
const userStore = useUserStore()         // 用户信息Store
const dialogueStore = useDialogueStore()  // 对话信息Store
console.log('dialogueStore',dialogueStore.talk.talk_type)
const editorDraftStore = useEditorDraftStore() // 编辑器草稿Store

/**
 * 编辑器核心状态
 */
const editorRef = ref(null)      // 编辑器DOM引用
const content = ref('')          // 编辑器内容
const editorContent = ref('')    // 编辑器纯文本内容
const editorHtml = ref('')       // 编辑器HTML内容
const isFocused = ref(false)     // 编辑器是否聚焦

/**
 * @提及相关状态
 */
const showMention = ref(false)   // 是否显示@提及列表
const mentionQuery = ref('')     // @提及搜索查询
const mentionPosition = ref({ top: 0, left: 0 }) // @提及列表位置
const selectedMentionIndex = ref(0) // 当前选中的@提及项索引
const mentionList = ref([])      // 过滤后的@提及成员列表
const currentMentionQuery = ref('') // 当前@提及查询文本

/**
 * 其他功能状态
 */
const quoteData = ref(null)      // 引用数据
const editingMessage = ref(null) // 正在编辑的消息
const isShowEmoticon = ref(false) // 是否显示表情选择器

/**
 * 文件上传相关引用
 */
const fileImageRef = ref(null)   // 图片文件输入框引用
const uploadFileRef = ref(null)  // 文件输入框引用
const emoticonRef = ref(null)    // 表情选择器引用


/**
 * 计算属性 - 当前对话索引名称
 * 用于标识当前对话，作为草稿保存的键名
 */
const indexName = computed(() => dialogueStore.index_name)

/**
 * 工具栏按钮配置
 * 定义工具栏上的功能按钮，包括图标、标题和点击事件
 */
const navs = ref([
  {
    title: '图片',          // 按钮标题
    icon: markRaw(Pic),    // 按钮图标，使用markRaw优化性能
    show: true,            // 是否显示
    click: () => {
      // 点击触发隐藏的文件输入框，打开图片选择对话框
      fileImageRef.value.click()
    }
  },
  {
    title: '文件',                // 按钮标题
    icon: markRaw(FolderUpload),  // 按钮图标
    show: true,                   // 是否显示
    click: () => {
      // 点击触发隐藏的文件输入框，打开文件选择对话框
      uploadFileRef.value.click()
    }
  }
])

/**
 * 调试代码 - 延迟打印成员列表
 * 用于开发调试，查看传入的成员列表
 */
setTimeout(() => {
  console.log('props.members', props.members)
}, 1000)


/**
 * 计算属性 - 工具栏配置
 * 定义编辑器工具栏的配置项，包括类型、图标和标题
 * 返回一个配置数组，用于渲染工具栏按钮
 */
const toolbarConfig = computed(() => {
  const config = [
    { type: 'emoticon', icon: 'icon-biaoqing', title: '表情' },  // 表情按钮
    { type: 'image', icon: 'icon-image', title: '图片' },        // 图片按钮
    { type: 'file', icon: 'icon-folder-plus', title: '文件' }     // 文件按钮
  ]
  
  return config
})

/**
 * 处理编辑器输入事件
 * 
 * @param {Event} event - 输入事件对象
 * 
 * 该函数负责处理编辑器内容变化时的逻辑：
 * 1. 获取编辑器DOM节点
 * 2. 克隆编辑器内容并移除引用元素
 * 3. 提取纯文本内容和表情图片
 * 4. 检查@提及功能
 * 5. 保存草稿
 * 6. 触发input_event事件
 */
const handleInput = (event) => {
  const editorNode = (event && event.target) ? event.target : editorRef.value; // 获取编辑器DOM节点
  if (!editorNode) {
    // 如果找不到编辑器节点，直接返回
    // console.warn('handleInput called without a valid editor node.');
    return;
  }
  const target = editorNode; 
  const editorClone = editorNode.cloneNode(true);
    editorClone.querySelectorAll('.editor-quote').forEach(quote => quote.remove());

        let rawTextContent = editorClone.textContent || '';
  const emojiImages = editorClone.querySelectorAll('img.editor-emoji');
      if (emojiImages.length > 0) {
    emojiImages.forEach(emoji => {
      const altText = emoji.getAttribute('alt');
      if (altText) {
                rawTextContent += altText; 
      }
    });
  }
  editorContent.value = rawTextContent;

    const currentText = editorNode.textContent.trim();
  const hasSpecialElements = editorNode.querySelector('img, .editor-file, .mention');

      if (currentText === '' && !hasSpecialElements) {
                if (editorNode.innerHTML !== '') { 
        editorNode.innerHTML = '';
    }
  }
  
  editorHtml.value = editorNode.innerHTML || '';
  
    const currentEditorItems = parseEditorContent().items;
  
  checkMention(target);
  saveDraft();

  emit('editor-event', {
    event: 'input_event',
    data: currentEditorItems.reduce((result, item) => {
      if (item.type === 1) return result + item.content;
      if (item.type === 3) return result + '[图片]';
            return result;
    }, '')
  });
};


/**
 * 检查@提及功能
 * 
 * 该函数检查编辑器中是否有@提及操作：
 * 1. 获取当前选择范围
 * 2. 提取光标前的文本
 * 3. 使用正则表达式匹配@符号后的内容
 * 4. 如果匹配成功，显示@提及列表并更新位置
 * 5. 如果没有匹配，隐藏@提及列表
 * 
 * @param {HTMLElement} target - 编辑器DOM元素
 */
const checkMention = (target) => {
  // 只有群聊才启用@功能
  if (dialogueStore.talk.talk_type !== 2) {
    hideMentionList()
    return
  }
  
  // 获取当前选择范围
  const selection = window.getSelection()
  if (!selection.rangeCount) return
  
  // 获取当前光标位置的范围
  const range = selection.getRangeAt(0)
  // 提取光标前的文本内容
  const textBeforeCursor = range.startContainer.textContent?.substring(0, range.startOffset) || ''
  // 使用正则表达式匹配@符号后的内容
  const mentionMatch = textBeforeCursor.match(/@([^@\s]*)$/)
  
  if (mentionMatch) {
    // 如果匹配成功，设置当前@提及查询文本
    currentMentionQuery.value = mentionMatch[1]
    // 显示@提及列表
    showMentionList()
    // 更新@提及列表位置
    updateMentionPosition(range)
  } else {
    // 如果没有匹配，隐藏@提及列表
    hideMentionList()
  }
}


/**
 * 显示@提及列表
 * 
 * 该函数根据当前查询文本过滤并显示@提及列表：
 * 1. 获取当前查询文本并转为小写
 * 2. 过滤成员列表，排除当前用户自己
 * 3. 如果当前用户是群管理员，添加"全体成员"选项
 * 4. 根据过滤结果决定是否显示@提及列表
 * 5. 重置选中项索引为第一项
 */
const showMentionList = () => {
  // 获取当前查询文本并转为小写，用于不区分大小写的匹配
  const query = currentMentionQuery.value.toLowerCase()
  // 过滤成员列表，查找以查询文本开头的成员，并排除当前用户自己
  mentionList.value = [{ id: 0, nickname: '全体成员', avatar: defAvatar, value: '全体成员' },...props.members].filter(member => {
    return member.value.toLowerCase().startsWith(query) && member.id !== userStore.uid
  })
  // 只有当列表有内容时才显示
  showMention.value = mentionList.value.length > 0
  // 重置选中项索引为第一项
  selectedMentionIndex.value = 0
}


/**
 * 处理鼠标点击选择@提及成员
 * 
 * 该函数处理用户通过鼠标点击选择@提及列表中的成员：
 * 1. 获取当前选择范围
 * 2. 如果有有效的选择范围，直接插入@提及元素
 * 3. 如果没有有效的选择范围，先聚焦编辑器，然后在下一个渲染周期尝试插入
 * 
 * @param {Object} member - 被选中的成员对象，包含id、value等属性
 */
const handleMentionSelectByMouse = (member) => {
  // 获取当前选择范围
  const selection = window.getSelection();
  if (selection && selection.rangeCount > 0) {
    // 如果有有效的选择范围，直接插入@提及元素
    insertMention(member, selection.getRangeAt(0).cloneRange());
  } else {
    // 如果没有有效的选择范围，先聚焦编辑器
    editorRef.value?.focus();
    // 在下一个渲染周期尝试获取新的选择范围并插入
    nextTick(() => {
      const newSelection = window.getSelection();
      if (newSelection && newSelection.rangeCount > 0) {
        insertMention(member, newSelection.getRangeAt(0).cloneRange());
      }
    });
  }
};


/**
 * 隐藏@提及列表
 * 
 * 该函数负责隐藏@提及列表并清空相关状态：
 * 1. 设置显示标志为false
 * 2. 清空成员列表
 * 3. 清空当前查询文本
 */
const hideMentionList = () => {
  // 隐藏@提及列表
  showMention.value = false
  // 清空成员列表
  mentionList.value = []
  // 清空当前查询文本
  currentMentionQuery.value = ''
}


/**
 * 更新@提及列表位置
 * 
 * 该函数根据当前光标位置计算并更新@提及列表的显示位置：
 * 1. 获取当前选择范围的边界矩形
 * 2. 获取编辑器的边界矩形
 * 3. 计算@提及列表相对于编辑器的位置
 * 
 * @param {Range} range - 当前选择范围
 */
const updateMentionPosition = (range) => {
  // 获取当前选择范围的边界矩形
  const rect = range.getBoundingClientRect()
  // 获取编辑器的边界矩形
  const editorRect = editorRef.value.getBoundingClientRect()
  
  // 计算@提及列表相对于编辑器的位置
  mentionPosition.value = {
    // 垂直位置：光标底部位置 + 5px的偏移
    top: rect.bottom - editorRect.top + 5,
    // 水平位置：与光标左侧对齐
    left: rect.left - editorRect.left
  }
}


/**
 * 插入@提及元素
 * 
 * 该函数负责在编辑器中插入@提及元素：
 * 1. 创建一个不可编辑的span元素，包含@符号和成员名称
 * 2. 确定插入位置（替换@符号及其后面的文本，或在当前光标位置插入）
 * 3. 插入元素并更新选择范围
 * 4. 触发输入事件并隐藏@提及列表
 * 
 * @param {Object} member - 要@提及的成员对象
 * @param {Range} clonedRange - 克隆的选择范围
 */
const insertMention = (member, clonedRange) => {
  console.log('插入mention', member);
  const selection = window.getSelection();
  if (!clonedRange || !selection || !editorRef.value) return;

  const range = clonedRange;
  const editor = editorRef.value;

  // 获取当前文本节点、偏移量和文本内容
  const textNode = range.startContainer;
  const offset = range.startOffset;
  const textContent = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent || '' : '';

  // 查找@符号在文本中的位置
  const atIndex = (textNode.nodeType === Node.TEXT_NODE && offset > 0) ? textContent.lastIndexOf('@', offset - 1) : -1;

  // 创建@提及元素
  const mentionSpan = document.createElement('span');
  mentionSpan.className = 'mention';
  mentionSpan.setAttribute('data-user-id', String(member.id));
  mentionSpan.textContent = `@${member.value || member.nickname} `;
  mentionSpan.contentEditable = 'false'; // 设置为不可编辑

  // 根据@符号位置决定如何插入元素
  if (atIndex !== -1 && textNode.nodeType === Node.TEXT_NODE) {
    // 如果找到了@符号，替换@符号及其后面的文本
    const parent = textNode.parentNode;
    if (!parent) return;

    // 设置范围从@符号开始到当前光标位置
    range.setStart(textNode, atIndex);
    range.setEnd(textNode, offset);
    range.deleteContents(); // 删除范围内容
    range.insertNode(mentionSpan); // 插入@提及元素
  } else {
    // 如果没有找到@符号，在当前光标位置插入
    if (!range.collapsed) {
      range.deleteContents(); // 如果有选中内容，先删除
    }
    range.insertNode(mentionSpan);
  }

  // 将光标移动到@提及元素后面
  range.setStartAfter(mentionSpan);
  range.collapse(true);

  // 更新选择范围
  selection.removeAllRanges();
  selection.addRange(range);

  // 保持编辑器焦点
  editor.focus();

  // 在下一个渲染周期触发输入事件并隐藏@提及列表
  nextTick(() => {
    handleInput({ target: editor });
    hideMentionList();
  });
};


/**
 * 处理粘贴事件
 * 
 * 该函数负责处理编辑器中的粘贴事件：
 * 1. 检测粘贴内容是否包含图片
 * 2. 如果是图片，创建临时URL并插入编辑器，然后上传到服务器
 * 3. 如果是文本，直接插入到当前光标位置
 * 
 * @param {ClipboardEvent} event - 粘贴事件对象
 */
const handlePaste = (event) => {
  // 阻止默认粘贴行为，以便自定义处理
  event.preventDefault();
  if (!editorRef.value) return;

  const clipboardData = event.clipboardData;
  if (!clipboardData) return;

  const items = clipboardData.items;
  let imagePasted = false;

  // 检查粘贴内容是否包含图片
  if (items) {
    for (let i = 0; i < items.length; i++) {
      if (items[i].type.indexOf('image') !== -1) {
        const file = items[i].getAsFile();
        if (file) {
          imagePasted = true;
          // 创建临时URL用于预览
          const tempUrl = URL.createObjectURL(file);
          const image = new Image();
          image.src = tempUrl;

          image.onload = () => {
            // 创建表单数据用于上传
            const form = new FormData();
            form.append('file', file);
            form.append('source', 'fonchain-chat');
            form.append('urlParam', `width=${image.width}&height=${image.height}`);

            // 先插入临时图片到编辑器，并获取插入的img元素
            const insertedImgElement = insertImage(tempUrl, image.width, image.height);

            // 如果图片成功插入且有父节点（wrapper span），添加加载中样式
            if (insertedImgElement && insertedImgElement.parentNode) {
              insertedImgElement.parentNode.classList.add('image-upload-loading');
            }

            // 上传图片到服务器
            uploadImg(form).then(({ code, data, message }) => {
              // Query images inside the callback to get the most current list
              const currentEditorImages = editorRef.value.querySelectorAll('img.editor-image');
              if (code === 0 && data && data.ori_url) {
                // 上传成功，替换临时URL为服务器URL
                for (let j = currentEditorImages.length - 1; j >= 0; j--) {
                  if (currentEditorImages[j].src === tempUrl) {
                    currentEditorImages[j].src = data.ori_url;
                    // 移除加载中样式
                    if (currentEditorImages[j].parentNode) {
                      currentEditorImages[j].parentNode.classList.remove('image-upload-loading');
                    }
                    break;
                  }
                }
                // 触发输入事件更新编辑器状态
                handleInput({ target: editorRef.value });
              } else {
                // 上传失败，显示错误消息
                window['$message'].error(message || '图片上传失败');
                // 移除临时图片及其wrapper
                for (let j = currentEditorImages.length - 1; j >= 0; j--) {
                  if (currentEditorImages[j].src === tempUrl) {
                    if (currentEditorImages[j].parentNode) {
                      currentEditorImages[j].parentNode.remove(); // Remove wrapper
                    } else {
                      currentEditorImages[j].remove(); // Fallback
                    }
                    break;
                  }
                }
                // 触发输入事件更新编辑器状态
                handleInput({ target: editorRef.value });
              }
              URL.revokeObjectURL(tempUrl); // 在上传完成后（成功或失败）释放URL
            }).catch(error => {
              // 处理上传过程中的错误
              console.error('Upload image error:', error);
              window['$message'].error('图片上传过程中发生错误');
              // Query images inside the callback
              const currentEditorImages = editorRef.value.querySelectorAll('img.editor-image');
              // 移除临时图片及其wrapper
              for (let j = currentEditorImages.length - 1; j >= 0; j--) {
                if (currentEditorImages[j].src === tempUrl) {
                  if (currentEditorImages[j].parentNode) {
                    currentEditorImages[j].parentNode.remove(); // Remove wrapper
                  } else {
                    currentEditorImages[j].remove(); // Fallback
                  }
                  break;
                }
              }
              // 触发输入事件更新编辑器状态
              handleInput({ target: editorRef.value });
              URL.revokeObjectURL(tempUrl); // 在上传完成后（成功或失败）释放URL
            });
          };
          image.onerror = () => {
            // 图片加载失败
            URL.revokeObjectURL(tempUrl);
            window['$message'].error('无法加载粘贴的图片');
          };
          return; // 找到并处理了图片，不再继续处理
        }
      }
    }
  }

  // 如果没有粘贴图片，处理文本内容
  if (!imagePasted) {
    const text = clipboardData.getData('text/plain') || '';
    if (text) {
      const selection = window.getSelection();
      if (selection && selection.rangeCount > 0) {
        // 获取当前选择范围
        const range = selection.getRangeAt(0);
        // 删除选中内容
        range.deleteContents();
        // 创建文本节点并插入
        const textNode = document.createTextNode(text);
        range.insertNode(textNode);
        // 将光标移动到插入文本后面
        range.setStartAfter(textNode);
        range.collapse(true);
        // 更新选择范围
        selection.removeAllRanges();
        selection.addRange(range);

        // 触发输入事件更新编辑器状态
        handleInput({ target: editorRef.value });
      }
    }
  }
};
/**
 * 插入换行符
 * 
 * 该函数负责在编辑器中插入换行符：
 * 1. 创建<br>元素并插入到当前选择范围
 * 2. 在<br>元素后插入零宽空格，确保光标可以正确定位
 * 3. 更新选择范围，将光标移动到换行符后
 * 4. 触发输入事件更新编辑器状态
 * 
 * @param {Range} range - 当前选择范围
 */
const insertLineBreak = (range) => {
  const editor = editorRef.value;
  if (!editor) return;

  // 创建<br>元素
  const br = document.createElement('br');
  // 删除选中内容
  range.deleteContents();
  // 插入<br>元素
  range.insertNode(br);

  // 插入零宽空格，确保光标可以正确定位
  // \u200B是零宽空格字符，在视觉上不可见但可以作为光标位置
  const nbsp = document.createTextNode('\u200B'); 
  // 将范围移动到<br>元素后
  range.setStartAfter(br);
  // 插入零宽空格
  range.insertNode(nbsp);
  // 将范围移动到零宽空格后
  range.setStartAfter(nbsp);
  // 折叠范围，确保没有选中内容
  range.collapse(true);

  // 更新选择范围
  const selection = window.getSelection();
  if (selection) {
    selection.removeAllRanges();
    selection.addRange(range);
  }

  // 保持编辑器焦点
  editor.focus();
  // 在下一个渲染周期触发输入事件
  nextTick(() => {
    handleInput({ target: editor });
  });
};

/**
 * 处理键盘事件
 * 
 * 该函数负责处理编辑器中的键盘事件：
 * 1. 处理@提及列表的导航和选择
 * 2. 处理删除键和退格键，特别是删除@提及元素
 * 3. 处理组合键插入换行符
 * 4. 处理回车键发送消息
 * 
 * @param {KeyboardEvent} event - 键盘事件对象
 */
const handleKeydown = (event) => {
  const editor = editorRef.value;
  if (!editor) return;

  // 处理@提及列表的导航和选择
  if (showMention.value) {
    const mentionUl = document.querySelector('.mention-list ul');
    let handled = false;
    switch (event.key) {
      case 'ArrowUp':
        // 向上导航@提及列表
        selectedMentionIndex.value = Math.max(0, selectedMentionIndex.value - 1);
        if (mentionUl) {
          // 确保选中项在可视区域内
          const selectedItem = mentionUl.children[selectedMentionIndex.value];
          if (selectedItem && selectedItem.offsetTop < mentionUl.scrollTop) {
            mentionUl.scrollTop = selectedItem.offsetTop;
          }
        }
        handled = true;
        break;
      case 'ArrowDown':
        // 向下导航@提及列表
        selectedMentionIndex.value = Math.min(mentionList.value.length - 1, selectedMentionIndex.value + 1);
        if (mentionUl) {
          // 确保选中项在可视区域内
          const selectedItem = mentionUl.children[selectedMentionIndex.value];
          if (selectedItem) {
            const itemBottom = selectedItem.offsetTop + selectedItem.offsetHeight;
            const listBottom = mentionUl.scrollTop + mentionUl.clientHeight;
            if (itemBottom > listBottom) {
              mentionUl.scrollTop = itemBottom - mentionUl.clientHeight;
            }
          }
        }
        handled = true;
        break;
      case 'Enter':
      case 'Tab':
        // 选择当前高亮的@提及项
        const selectedMember = mentionList.value[selectedMentionIndex.value];
        if (selectedMember) {
          const selection = window.getSelection();
          if (selection && selection.rangeCount > 0) {
            insertMention(selectedMember, selection.getRangeAt(0).cloneRange());
          }
        }
        handled = true;
        break;
      case 'Escape':
        // 关闭@提及列表
        hideMentionList();
        handled = true;
        break;
    }
    if (handled) {
      // 阻止默认行为，避免与编辑器冲突
      event.preventDefault();
      return;
    }
  }

  // 处理删除键和退格键，特别是删除@提及元素
  if (event.key === 'Backspace' || event.key === 'Delete') {
    const selection = window.getSelection();
    if (!selection || !selection.rangeCount) return;

    const range = selection.getRangeAt(0);
    if (range.collapsed) {
      // 确定要检查的节点
      let nodeToCheck = null;
      let positionRelativeToCheck = ''; 
      const container = range.startContainer;
      const offset = range.startOffset;

      if (event.key === 'Backspace') {
        // 退格键：检查光标前的节点
        if (offset === 0) {
          // 如果光标在文本开头，检查前一个兄弟节点
          nodeToCheck = container.previousSibling;
          positionRelativeToCheck = 'before';
        } else if (container.nodeType === Node.ELEMENT_NODE && offset > 0) {
          // 如果光标在元素内部，检查前一个子节点
          nodeToCheck = container.childNodes[offset - 1];
          positionRelativeToCheck = 'before';
        }
      } else if (event.key === 'Delete') {
        // 删除键：检查光标后的节点
        if (container.nodeType === Node.TEXT_NODE && offset === container.textContent.length) {
          // 如果光标在文本末尾，检查下一个兄弟节点
          nodeToCheck = container.nextSibling;
          positionRelativeToCheck = 'after';
        } else if (container.nodeType === Node.ELEMENT_NODE && offset < container.childNodes.length) {
          // 如果光标在元素内部，检查后一个子节点
          nodeToCheck = container.childNodes[offset];
          positionRelativeToCheck = 'after';
        }
      }
      
      // 如果要检查的节点是@提及元素，则删除它
      if (nodeToCheck && nodeToCheck.nodeType === Node.ELEMENT_NODE && nodeToCheck.classList.contains('mention')) {
        event.preventDefault(); // 阻止默认删除行为
        const parent = nodeToCheck.parentNode;
        parent.removeChild(nodeToCheck); // 移除@提及元素
        
        // 触发输入事件更新编辑器状态
        handleInput({ target: editor });
        return;
      }
    }
  }

  // 处理组合键插入换行符（Ctrl+Enter、Shift+Enter、Meta+Enter）
  if (event.key === 'Enter' && (event.ctrlKey || event.metaKey || event.shiftKey)) {
    event.preventDefault(); // 阻止默认回车行为
    const selection = window.getSelection();
    if (!selection || selection.rangeCount === 0) {
      // 如果没有选择范围，先聚焦编辑器
      editor.focus();
      nextTick(() => {
        // 在下一个渲染周期尝试获取新的选择范围
        const newSelection = window.getSelection();
        if (newSelection && newSelection.rangeCount > 0) {
          insertLineBreak(newSelection.getRangeAt(0));
        }
      });
      return;
    }
    // 插入换行符
    insertLineBreak(selection.getRangeAt(0));
    return;
  }

  // 处理回车键发送消息
  if (event.key === 'Enter' && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
    event.preventDefault(); // 阻止默认回车行为
    // 解析编辑器内容
    const messageData = parseEditorContent();
    // 检查消息是否为空
    const isEmptyMessage = messageData.items.length === 0 ||
                         (messageData.items.length === 1 &&
                          messageData.items[0].type === 1 &&
                          !messageData.items[0].content.trimEnd());

    if (isEmptyMessage) {
      // 如果消息为空但编辑器不为空，清空编辑器
      if (editor.innerHTML.trim() !== '' && editor.innerHTML.trim() !== '<br>') {
        clearEditor();
      }
      return;
    }

    // 检查引用元素是否存在，如果不存在但引用数据存在，清空引用数据
    const quoteElement = editor.querySelector('.editor-quote');
    if (!quoteElement && quoteData.value) {
      quoteData.value = null;
    }

    // 发送消息
    sendMessage();
  }
};


/**
 * 发送消息函数
 * 
 * 该函数负责处理编辑器内容的发送：
 * 1. 解析编辑器内容，获取结构化数据
 * 2. 清理和格式化文本内容
 * 3. 根据内容类型（纯文本、单图片、单文件）触发不同的事件
 * 4. 发送后清空编辑器
 */
const sendMessage = () => {
  // 获取编辑器DOM引用
  const editor = editorRef.value;
  if (!editor) return;

  // 解析编辑器内容，获取结构化数据
  const parsedData = parseEditorContent();

  /**
   * 清理不可见字符
   * 移除零宽字符和其他不可见字符
   * @param {string} text - 需要清理的文本
   * @returns {string} 清理后的文本
   */
  const cleanInvisibleChars = (text) => {
    return text ? String(text).replace(/[\u200B-\u200D\uFEFF]/g, '') : '';
  };

  // 处理和过滤消息项
  let finalItems = [];
  if (parsedData && parsedData.items) {
    // 处理每个消息项
    finalItems = parsedData.items.map(item => {
      // 处理文本类型的消息项
      if (item.type === 1 && typeof item.content === 'string') {
        let content = cleanInvisibleChars(item.content);
        // 将HTML换行标签转换为文本换行符并去除首尾空白
        content = content.replace(/<br\s*\/?>/gi, '\n').trim();
        return { ...item, content };
      }
      return item;
    }).filter(item => {
      // 过滤掉空内容的消息项（除非有@提及）
      if (item.type === 1 && !item.content && !(parsedData.mentionUids && parsedData.mentionUids.length > 0)) return false;
      // 过滤掉没有内容的图片项
      if (item.type === 3 && !item.content) return false;
      // 过滤掉没有内容的文件项
      if (item.type === 4 && !item.content) return false;
      return true;
    });
  }

  // 检查是否有实际内容（文本、图片或文件）
  const hasActualContent = finalItems.some(item => (item.type === 1 && item.content) || item.type === 3 || item.type === 4);
  // 如果没有实际内容且没有@提及和引用，则不发送
  if (!hasActualContent && !(parsedData.mentionUids && parsedData.mentionUids.length > 0) && !parsedData.quoteId) {
    // 如果编辑器不为空，则清空编辑器
    if (editor.innerHTML.trim() !== '' && editor.innerHTML.trim() !== '<br>') {
      clearEditor();
    }
    return;
  }

  // 构建要发送的消息对象
  const messageToSend = {
    // 消息项数组，如果为空则添加一个空文本项
    items: finalItems.length > 0 ? finalItems : [{ type: 1, content: '' }],
    // @提及的用户ID数组
    mentionUids: parsedData.mentionUids || [],
    // @提及的用户信息数组
    mentions: (parsedData.mentionUids || []).map(uid => {
      const member = mentionList.value.find(m => m.id === uid);
      return { atid: uid, name: member ? member.nickname : '' };
    }),
    // 引用的消息ID
    quoteId: parsedData.quoteId || null
  };

  // 处理引用数据
  if (messageToSend.quoteId && quoteData.value && quoteData.value.id === messageToSend.quoteId) {
    // 如果引用ID与当前引用数据匹配，添加完整引用数据
    messageToSend.quote = { ...quoteData.value };
  } else if (messageToSend.quoteId) {
    // 如果有引用ID但没有匹配的引用数据（这里似乎缺少处理逻辑）
  } else {
    // 如果没有引用ID，删除引用数据
    delete messageToSend.quote;
  }
  messageToSend.items.forEach(item => {
    
    if (item.type === 1 && cleanInvisibleChars(item.content.trimEnd())) {  
     const data = {
       items: [{
         content: cleanInvisibleChars(item.content),
         type: 1
       }],
       mentionUids: messageToSend.mentionUids,
       mentions: messageToSend.mentionUids.map(uid => {
         return {
           atid: uid,
           name: mentionList.value.find(member => member.id === uid)?.nickname || ''
         }
       }),
       quoteId: messageToSend.quoteId,
     }
     emit(
       'editor-event',
       emitCall('text_event', data)
     )
   } else if (item.type === 3) { 
     const data = {
       height: 0,
       width: 0,
       size: 10000,
       url: item.content,
     }
     emit(
       'editor-event',
       emitCall('image_event', data)
     )
   } else if (item.type === 4) { 
     
   }
 })
  // 发送后清空编辑器
  clearEditor();
}


/**
 * 解析编辑器内容函数
 * 
 * 该函数负责将编辑器的HTML内容解析为结构化数据：
 * 1. 提取文本内容、图片、文件和@提及信息
 * 2. 处理引用元素
 * 3. 将不同类型的内容转换为统一的数据结构
 * 
 * @returns {Object} 包含items（内容项数组）、mentionUids（@提及用户ID数组）和quoteId（引用消息ID）的对象
 */
const parseEditorContent = () => {
  // 初始化内容项数组
  const items = [];
  // 使用Set存储@提及的用户ID，确保唯一性
  const mentionUids = new Set();
  // 初始化引用消息ID
  let parsedQuoteId = null;

  // 获取编辑器DOM节点
  const editorNode = editorRef.value;
  // 如果编辑器不存在，返回空内容
  if (!editorNode) {
    return { items: [{ type: 1, content: '' }], mentionUids: [], quoteId: null };
  }

  // 创建临时div用于解析HTML内容
  const tempDiv = document.createElement('div');
  tempDiv.innerHTML = editorHtml.value;

  // 查找并处理引用元素
  const quoteElement = tempDiv.querySelector('.editor-quote');
  if (quoteElement && quoteData.value && quoteData.value.id) {
    // 保存引用消息ID
    parsedQuoteId = quoteData.value.id;
    // 从临时div中移除引用元素，避免重复处理
    quoteElement.remove(); 
  }

  // 当前文本缓冲区，用于累积连续的文本内容
  let currentTextBuffer = '';

  /**
   * 将当前文本缓冲区内容添加到items数组中（如果有内容）
   * 然后清空缓冲区
   */
  const flushTextBufferIfNeeded = () => {
    if (currentTextBuffer) {
      items.push({ type: 1, content: currentTextBuffer });
    }
    currentTextBuffer = '';
  };

  /**
   * 递归处理DOM节点及其子节点
   * 根据节点类型和标签名提取不同类型的内容
   * 
   * @param {Node} node - 要处理的DOM节点
   */
  const processNodeRecursively = (node) => {
    // 处理文本节点
    if (node.nodeType === Node.TEXT_NODE) {
      // 将文本内容添加到缓冲区
      currentTextBuffer += node.textContent;
      return;
    }

    // 忽略非元素节点
    if (node.nodeType !== Node.ELEMENT_NODE) return;

    // 根据元素标签名处理不同类型的内容
    switch (node.tagName) {
      case 'BR':
        // 将换行标签转换为换行符
        currentTextBuffer += '\n';
        break;
      case 'IMG':
        // 处理图片元素
        flushTextBufferIfNeeded();
        const src = node.getAttribute('src');
        const alt = node.getAttribute('alt');
        const isEmojiPic = node.classList.contains('editor-emoji');
        const isTextEmojiPlaceholder = node.classList.contains('emoji'); 

        if (isTextEmojiPlaceholder && alt) {
          // 如果是文本表情占位符，将其alt属性（表情文本）添加到缓冲区
          currentTextBuffer += alt; 
        } else if (src) {
          // 如果是普通图片或图片表情，添加为图片类型的内容项
          items.push({
            type: 3, // 图片类型
            content: src, // 图片URL
            isEmoji: isEmojiPic, // 是否为表情图片
            // 获取图片原始宽度和高度
            width: node.getAttribute('data-original-width') || node.width || null,
            height: node.getAttribute('data-original-height') || node.height || null,
          });
        }
        break;
      default:
        // 处理其他元素
        if (node.classList.contains('mention')) {
          // 处理@提及元素
          const userId = node.getAttribute('data-user-id');
          if (userId) {
            // 将用户ID添加到Set中
            mentionUids.add(Number(userId));
          }
          // 将@提及文本添加到缓冲区
          currentTextBuffer += node.textContent || '';
        } else if (node.classList.contains('editor-file')) {
          // 处理文件元素
          flushTextBufferIfNeeded();
          const fileUrl = node.getAttribute('data-url');
          const fileName = node.getAttribute('data-name');
          const fileSize = node.getAttribute('data-size-raw') || node.getAttribute('data-size') || 0;
          if (fileUrl && fileName) {
            // 添加为文件类型的内容项
            items.push({
              type: 4, // 文件类型
              content: fileUrl, // 文件URL
              name: fileName, // 文件名
              size: parseInt(fileSize, 10), // 文件大小
            });
          }
        } else if (node.childNodes && node.childNodes.length > 0) {
          // 如果元素有子节点，递归处理所有子节点
          Array.from(node.childNodes).forEach(processNodeRecursively);
        } else if (node.textContent) {
          // 如果元素有文本内容但没有子节点，将文本内容添加到缓冲区
          currentTextBuffer += node.textContent;
        }
        break;
    }
  };

  // 处理临时div中的所有子节点
  Array.from(tempDiv.childNodes).forEach(processNodeRecursively);
  // 处理最后的文本缓冲区
  flushTextBufferIfNeeded(); 

  // 返回解析结果
  return {
    // 内容项数组，如果为空则添加一个空文本项
    items: items.length > 0 ? items : [{ type: 1, content: '' }],
    // 将Set转换为数组
    mentionUids: Array.from(mentionUids),
    // 引用消息ID
    quoteId: parsedQuoteId
  };
};


/**
 * 清空编辑器函数
 * 
 * 该函数负责清空编辑器内容和相关状态：
 * 1. 清空编辑器DOM内容和相关变量
 * 2. 清空引用数据
 * 3. 隐藏@提及列表
 * 4. 触发清空事件
 * 5. 重新聚焦编辑器
 */
const clearEditor = () => {
  // 清空编辑器DOM内容
  if (editorRef.value) {
    editorRef.value.innerHTML = '';
  }
  // 清空编辑器内容变量
  editorContent.value = '';
  editorHtml.value = '';
  // 清空引用数据
  quoteData.value = null;

  // 隐藏@提及列表
  hideMentionList();
  // 移除可能存在的引用元素
  const existingQuoteElement = editorRef.value ? editorRef.value.querySelector('.editor-quote') : null;
  if (existingQuoteElement) {
    existingQuoteElement.remove();
  }

  // 触发输入事件，更新编辑器状态
  handleInput();

  // 触发清空事件
  emit('editor-event', {
    event: 'clear_event',
    data: ''
  });

  // 重新聚焦编辑器
  if (editorRef.value) {
    nextTick(() => {
      // 聚焦编辑器
      editorRef.value.focus();
      // 如果编辑器内容只有一个<br>标签，清空它
      if (editorRef.value && editorRef.value.innerHTML.toLowerCase() === '<br>') {
        editorRef.value.innerHTML = '';
      }
    });
  }
};




const insertImage = (fileOrSrc, isUploaded = false, uploadedUrl = '') => {
  if (!editorRef.value) return;

  const img = document.createElement('img');
  img.className = 'editor-image'; 
  img.alt = '图片';
  img.style.maxWidth = '200px'; 
  img.style.maxHeight = '200px'; 
  img.style.borderRadius = '4px';
  img.style.objectFit = 'contain';
  img.style.margin = '5px';

  // 创建包装器
  const wrapper = document.createElement('span');
  wrapper.className = 'editor-image-wrapper';
  wrapper.style.position = 'relative';
  wrapper.style.display = 'inline-block';
  wrapper.appendChild(img);

  const setupAndInsert = (imageUrl, naturalWidth, naturalHeight) => {
    img.src = imageUrl;
    if (naturalWidth) img.setAttribute('data-original-width', naturalWidth);
    if (naturalHeight) img.setAttribute('data-original-height', naturalHeight);
    if (isUploaded && uploadedUrl) {
      img.setAttribute('data-uploaded-url', uploadedUrl);
      img.setAttribute('data-status', 'uploaded');
    } else {
      img.setAttribute('data-status', 'local-preview');
    }

    const selection = window.getSelection();
    let range;

    if (selection && selection.rangeCount > 0) {
      range = selection.getRangeAt(0);
      if (!editorRef.value.contains(range.commonAncestorContainer)) {
        editorRef.value.focus();
        range = document.createRange();
        range.selectNodeContents(editorRef.value);
        range.collapse(false); 
      }
    } else {
      editorRef.value.focus();
      range = document.createRange();
      range.selectNodeContents(editorRef.value);
      range.collapse(false); 
    }

    range.deleteContents();
    range.insertNode(wrapper);
    const spaceNode = document.createTextNode('\u00A0');
    range.insertNode(spaceNode);
    range.setStartAfter(spaceNode);
    range.collapse(false);
    selection.removeAllRanges();
    selection.addRange(range);

    editorRef.value.focus();
    handleInput();
  };

  if (typeof fileOrSrc === 'string') { 
    const tempImageForSize = new Image();
    tempImageForSize.onload = () => {
      setupAndInsert(fileOrSrc, tempImageForSize.naturalWidth, tempImageForSize.naturalHeight);
    };
    tempImageForSize.onerror = () => {
      console.warn('Failed to load image from URL for size calculation:', fileOrSrc);
      setupAndInsert(fileOrSrc); 
    };
    tempImageForSize.src = fileOrSrc;
  } else if (fileOrSrc instanceof File && fileOrSrc.type.startsWith('image/')) { 
    const reader = new FileReader();
    reader.onload = (e) => {
      const dataUrl = e.target.result;
      const tempImageForSize = new Image();
      tempImageForSize.onload = () => {
        setupAndInsert(dataUrl, tempImageForSize.naturalWidth, tempImageForSize.naturalHeight);
      };
      tempImageForSize.onerror = () => {
        console.warn('Failed to load image from FileReader for size calculation.');
        setupAndInsert(dataUrl); 
      };
      tempImageForSize.src = dataUrl;
    };
    reader.onerror = (error) => {
      console.error('FileReader error:', error);
    };
    reader.readAsDataURL(fileOrSrc);
  } else {
    console.warn('insertImage: Invalid file object or URL provided.');
  }
  
  return img; // 返回 img 元素以便外部访问
};


const formatFileSize = (size) => {
  if (size < 1024) {
    return size + ' B'
  } else if (size < 1024 * 1024) {
    return (size / 1024).toFixed(2) + ' KB'
  } else if (size < 1024 * 1024 * 1024) {
    return (size / (1024 * 1024)).toFixed(2) + ' MB'
  } else {
    return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
  }
}

/**
 * 处理图片上传并发送
 * 
 * @param {Event} event - 文件选择事件
 * 
 * 处理从工具栏上传图片的逻辑：
 * 1. 获取选择的图片文件
 * 2. 上传图片到服务器
 * 3. 更新编辑器中的临时图片
 * 4. 触发image_event事件
 * 5. 处理上传失败情况
 */
const onUploadSendImg = async (event) => {
  // 检查事件对象和文件列表是否存在
  if (!event.target || !event.target.files) return;
  const files = event.target.files;

  // 遍历所有选择的文件
  for (const file of files) {
    // 验证文件类型是否为图片
    if (!file.type.startsWith('image/')) {
      console.warn('Invalid file type for image upload:', file.type);
      continue;
    }
    // 注释掉的代码：不再在编辑器中插入图片预览
    // insertImage(file, false);

    // 创建FormData对象用于上传
    const formData = new FormData();
    formData.append('file', file);
    formData.append('source', 'fonchain-chat'); // 指定来源

    try {
      // 调用上传API
      const res = await uploadImg(formData);
      // 上传成功且返回有效URL
      if (res && res.status === 0 && res.data && res.data.ori_url) {
        // 查找编辑器中的临时预览图片
        const previewImages = editorRef.value.querySelectorAll('img[data-status="local-preview"][src^="data:image"]:not([data-uploaded-url])');
        if (previewImages.length > 0) {
          // 获取最后一个预览图片（通常是刚刚插入的）
          const lastPreviewImage = previewImages[previewImages.length - 1];
          if (lastPreviewImage && lastPreviewImage.src.startsWith('data:image')) { 
            // 更新图片属性为上传后的URL和状态
            lastPreviewImage.src = res.data.ori_url;
            lastPreviewImage.setAttribute('data-uploaded-url', res.data.ori_url);
            lastPreviewImage.setAttribute('data-status', 'uploaded');
            if (res.data.width) lastPreviewImage.setAttribute('data-original-width', res.data.width);
            if (res.data.height) lastPreviewImage.setAttribute('data-original-height', res.data.height);
            handleInput(); // 触发输入事件更新编辑器状态
          }
        }
        // 触发图片事件，通知父组件处理上传的图片
        emit('editor-event', emitCall('image_event', {
          url: res.data.ori_url,
          width: res.data.width || 0,
          height: res.data.height || 0,
          size: 10000
        }));

      } else {
        // 上传失败或返回无效响应
        console.error('Image upload failed or received invalid response:', res);
        // 查找并标记失败的预览图片
        const previewImages = editorRef.value.querySelectorAll('img[data-status="local-preview"][src^="data:image"]:not([data-uploaded-url])');
        if (previewImages.length > 0) {
            const lastPreviewImage = previewImages[previewImages.length -1];
            if(lastPreviewImage) {
                // 添加红色边框和提示，指示上传失败
                lastPreviewImage.style.border = '2px dashed red';
                lastPreviewImage.title = 'Upload failed';
            } 
        }
      }
    } catch (error) {
      // 捕获上传过程中的错误
      console.error('Error during image upload process:', error);
      // 查找并标记错误的预览图片
      const previewImages = editorRef.value.querySelectorAll('img[data-status="local-preview"][src^="data:image"]:not([data-uploaded-url])');
        if (previewImages.length > 0) {
            const lastPreviewImage = previewImages[previewImages.length -1];
             if(lastPreviewImage) {
                // 添加红色边框和提示，指示上传错误
                lastPreviewImage.style.border = '2px dashed red';
                lastPreviewImage.title = 'Upload error';
            } 
        }
    }
  }
  // 清空文件输入框，允许选择相同文件再次上传
  if (event.target) event.target.value = ''; 
};
/**
 * 处理文件上传事件
 * 
 * @param {Event} e - 文件选择事件
 * 
 * 根据上传文件类型触发不同的事件：
 * - 图片文件：触发image_event事件
 * - 视频文件：触发video_event事件
 * - 其他文件：触发file_event事件
 */
async function onUploadFile(e) {
  // 检查事件对象和文件列表是否存在且不为空
  if (!e.target || !e.target.files || e.target.files.length === 0) return;
  const file = e.target.files[0];

  // 清空文件输入框，允许再次选择相同文件
  e.target.value = null;

  // 获取文件类型并初始化事件名称
  const fileType = file.type;
  let eventName = '';

  // 根据文件类型确定事件名称并触发相应事件
  if (fileType.startsWith('image/')) {
    eventName = 'image_event';
  
    emit('editor-event', emitCall(eventName, file)); 
  } else if (fileType.startsWith('video/')) {
    eventName = 'video_event';
    emit('editor-event', emitCall(eventName, file));
  } else {
    eventName = 'file_event';
    
    emit('editor-event', emitCall(eventName, file));
   
  }
}


/**
 * 处理表情符号选择事件
 * 
 * @param {Object} emoji - 表情符号对象，包含类型、值、图片URL等信息
 * 
 * 根据表情符号类型执行不同的插入操作：
 * - text/emoji类型：插入文本表情
 * - image类型：插入图片表情
 * - 类型为1：根据是否有图片URL决定插入图片或文本表情
 * - 其他类型：触发emoticon_event事件
 */
const onEmoticonEvent = (emoji) => {
  // 关闭表情选择器面板
  emoticonRef.value?.setShow(false)
  
  // 根据表情类型执行不同操作
  switch (emoji.type) {
    case 'text':
    case 'emoji':
      // 插入文本表情（如：😊）
      insertTextEmoji(emoji.value)
      break
      
    case 'image':
      // 插入图片表情（带图片URL的表情）
      insertImageEmoji(emoji.img, emoji.value)
      break
      
    case 1: 
      // 兼容旧版表情格式，根据是否有图片决定插入方式
      emoji.img ? insertImageEmoji(emoji.img, emoji.value) : insertTextEmoji(emoji.value)
      break
      
    default:
      // 其他类型表情，触发事件让父组件处理
      emit('editor-event', {
        event: 'emoticon_event',
        data: emoji.value || emoji.id
      })
      break
  }
}


/**
 * 在编辑器中插入文本表情
 * 
 * @param {string} emojiText - 要插入的表情文本（如：😊）
 * 
 * 该函数在当前光标位置插入文本表情：
 * 1. 获取当前选区
 * 2. 如果选区不在编辑器内，则创建新选区在编辑器末尾
 * 3. 删除选中内容（如果有）
 * 4. 插入表情文本
 * 5. 将光标移动到插入的表情后面
 * 6. 触发输入事件更新编辑器状态
 */
const insertTextEmoji = (emojiText) => {
  // 验证编辑器引用和表情文本
  if (!editorRef.value || typeof emojiText !== 'string') return;

  const editor = editorRef.value;
  editor.focus(); // 确保编辑器获得焦点

  // 获取当前选区
  const selection = window.getSelection();
  let range;

  if (selection && selection.rangeCount > 0) {
    range = selection.getRangeAt(0);
    // 检查选区是否在编辑器内
    if (!editor.contains(range.commonAncestorContainer)) {
      // 如果不在编辑器内，创建新选区在编辑器末尾
      range = document.createRange();
      range.selectNodeContents(editor);
      range.collapse(false); // 折叠到末尾
    }
  } else {
    // 如果没有选区，创建新选区在编辑器末尾
    range = document.createRange();
    range.selectNodeContents(editor);
    range.collapse(false); // 折叠到末尾
  }

  // 删除选中内容（如果有）
  range.deleteContents();

  // 创建文本节点并插入
  const textNode = document.createTextNode(emojiText);
  range.insertNode(textNode);

  // 将光标移动到插入的表情后面
  range.setStartAfter(textNode);
  range.collapse(true);
  selection.removeAllRanges();
  selection.addRange(range);   

  // 触发输入事件更新编辑器状态
  handleInput(); 
};


/**
 * 在编辑器中插入图片表情
 * 
 * @param {string} imgSrc - 表情图片的URL
 * @param {string} altText - 图片的替代文本
 * 
 * 该函数在当前光标位置插入图片表情：
 * 1. 获取当前选区
 * 2. 如果选区不在编辑器内，则创建新选区在编辑器末尾
 * 3. 删除选中内容（如果有）
 * 4. 创建并插入图片元素
 * 5. 在图片后插入空格
 * 6. 将光标移动到插入的空格后面
 * 7. 触发输入事件更新编辑器状态
 */
const insertImageEmoji = (imgSrc, altText) => {
  // 验证编辑器引用和图片URL
  if (!editorRef.value || !imgSrc) return;

  const editor = editorRef.value;
  editor.focus(); // 确保编辑器获得焦点

  // 获取当前选区
  const selection = window.getSelection();
  let range;

  if (selection && selection.rangeCount > 0) {
    range = selection.getRangeAt(0);
    // 检查选区是否在编辑器内
    if (!editor.contains(range.commonAncestorContainer)) {
      // 如果不在编辑器内，创建新选区在编辑器末尾
      range = document.createRange();
      range.selectNodeContents(editor);
      range.collapse(false); // 折叠到末尾
    }
  } else {
    // 如果没有选区，创建新选区在编辑器末尾
    range = document.createRange();
    range.selectNodeContents(editor);
    range.collapse(false); // 折叠到末尾
  }

  // 删除选中内容（如果有）
  range.deleteContents(); 

  // 创建图片元素
  const img = document.createElement('img');
  img.src = imgSrc;
  img.alt = altText || 'emoji'; // 设置替代文本，默认为'emoji'
  img.className = 'editor-emoji'; // 添加样式类
  img.setAttribute('data-role', 'emoji'); // 设置角色属性，用于标识

  // 插入图片
  range.insertNode(img);

  // 在图片后插入空格（非断行空格）
  const spaceNode = document.createTextNode('\u00A0');
  range.setStartAfter(img);
  range.collapse(true);
  range.insertNode(spaceNode);

  // 将光标移动到空格后面
  range.setStartAfter(spaceNode);
  range.collapse(true);
  
  // 更新选区
  if (selection) {
    selection.removeAllRanges();
    selection.addRange(range);
  }

  // 触发输入事件更新编辑器状态
  handleInput(); 
};


/**
 * 处理@提及事件，在编辑器中插入@提及元素
 * 
 * @param {Object} data - 提及的用户数据，包含用户ID、名称等信息
 * 
 * 该函数在当前光标位置插入@提及元素：
 * 1. 获取当前选区
 * 2. 如果选区不在编辑器内，则创建新选区在编辑器末尾
 * 3. 更新选区
 * 4. 调用insertMention函数插入@提及元素
 */
const onSubscribeMention = async (data) => {
  // 验证编辑器引用和用户数据
  if (!editorRef.value || !data) return;
  const editorNode = editorRef.value;

  // 确保编辑器获得焦点
  editorNode.focus();
  // 等待下一个DOM更新周期，确保焦点设置生效
  await nextTick(); 

  // 获取当前选区
  let selection = window.getSelection();
  let range;

  if (selection && selection.rangeCount > 0) {
    range = selection.getRangeAt(0);
    // 检查选区是否在编辑器内
    if (!editorNode.contains(range.commonAncestorContainer)) {
      // 如果不在编辑器内，创建新选区在编辑器末尾
      range = document.createRange();
      range.selectNodeContents(editorNode);
      range.collapse(false); // 折叠到末尾
    }
  } else {
    // 如果没有选区，创建新选区在编辑器末尾
    range = document.createRange();
    range.selectNodeContents(editorNode);
    range.collapse(false); // 折叠到末尾
  }
  
  // 尝试使用主选区
  if (selection) {
    selection.removeAllRanges();
    selection.addRange(range); 
    insertMention(data, range); // 插入@提及元素
  } else {
    // 如果主选区不可用，尝试创建备用选区
    const fallbackRange = document.createRange();
    fallbackRange.selectNodeContents(editorNode);
    fallbackRange.collapse(false); // 折叠到末尾
    const newSelection = window.getSelection();
    if (newSelection){
        newSelection.removeAllRanges();
        newSelection.addRange(fallbackRange);
        insertMention(data, fallbackRange); // 使用备用选区插入@提及元素
    } else {
        console.error("Could not get window selection to insert mention.");
    }
  }
};

/**
 * 处理删除引用元素的键盘事件
 * 
 * @param {KeyboardEvent} e - 键盘事件对象
 * 
 * 该函数监听Backspace和Delete键，处理引用元素的删除：
 * 1. 检查是否按下了Backspace或Delete键
 * 2. 获取当前选区
 * 3. 准备处理引用元素的删除逻辑
 */
const handleDeleteQuote = function(e) {
    // 只处理Backspace和Delete键
    if (e.key !== 'Backspace' && e.key !== 'Delete') return;
  
  // 获取当前选区
  const selection = window.getSelection();
  if (selection.rangeCount === 0) return;
  
  // 获取选区范围和编辑器引用
  const range = selection.getRangeAt(0);
  const editor = editorRef.value;
  // 如果编辑器不存在，直接返回
  if (!editor) return;
  
  // 查找编辑器中的引用元素
  const quoteElement = editor.querySelector('.editor-quote');
  
  // 如果没有引用元素，移除事件监听器并返回
  if (!quoteElement) {
        editor.removeEventListener('keydown', handleDeleteQuote);
    return;
  }
  
  // 获取引用元素在编辑器子节点中的索引位置
  const quoteIndex = Array.from(editor.childNodes).indexOf(quoteElement);
  
  // 判断是否在引用元素前按下Backspace键
  // 条件：按下Backspace键 + 光标已折叠 + 光标容器是编辑器 + 光标位置正好在引用元素前
  const isBeforeQuote = e.key === 'Backspace' && 
                     range.collapsed && 
                     range.startContainer === editor && 
                     quoteIndex === range.startOffset;
  
  // 判断是否在引用元素后按下Delete键
  // 条件：按下Delete键 + 光标已折叠 + 光标容器是编辑器 + 光标位置正好在引用元素后
  const isAfterQuote = e.key === 'Delete' && 
                    range.collapsed && 
                    range.startContainer === editor && 
                    quoteIndex === range.startOffset - 1;
  
  // 如果满足删除引用元素的条件
  if (isBeforeQuote || isAfterQuote) {
        // 阻止默认删除行为
        e.preventDefault();
    
        // 移除引用元素
        quoteElement.remove();
        // 清空引用数据
        quoteData.value = null;
    
        // 触发输入事件更新编辑器状态
        handleInput({ target: editor });
  }
};

/**
 * 处理引用事件，在编辑器中插入引用元素
 * 
 * @param {Object} data - 引用数据，包含标题、描述、图片等信息
 * 
 * 该函数在编辑器中插入引用元素：
 * 1. 保存当前引用数据
 * 2. 移除已存在的引用元素
 * 3. 创建新的引用元素（包含标题、图片、描述和关闭按钮）
 * 4. 将引用元素插入到编辑器开头
 * 5. 在引用元素后添加零宽空格，并将光标放置在该位置
 * 6. 设置引用元素的点击事件处理程序
 */
const onSubscribeQuote = (data) => {
  // 验证编辑器引用和引用数据
  if (!editorRef.value || !data) return;
  // 保存引用数据到响应式变量
  quoteData.value = data;
  const editor = editorRef.value;

  // 移除编辑器中已存在的引用元素
  editor.querySelectorAll('.editor-quote').forEach(quote => quote.remove());
  // 获取当前选区，并尝试保存范围（用于后续恢复光标位置）
  const selection = window.getSelection();
  let savedRange = null;
  if (selection && selection.rangeCount > 0) {
    const currentRange = selection.getRangeAt(0);
    if (editor.contains(currentRange.commonAncestorContainer)) {
        savedRange = currentRange.cloneRange();
    }
  }

  // 创建引用元素
  const quoteElement = document.createElement('div');
  quoteElement.className = 'editor-quote';
  quoteElement.contentEditable = 'false'; // 设置为不可编辑

  // 创建引用内容包装器
  const wrapper = document.createElement('div');
  wrapper.className = 'quote-content-wrapper';
  
  // 创建并添加标题元素
  const titleDiv = document.createElement('div');
  titleDiv.className = 'quote-title';
  titleDiv.textContent = data.title || ' '; // 使用提供的标题或空格
  wrapper.appendChild(titleDiv);
  
  // 如果有图片，创建并添加图片元素
  if (data.image) {
    const imageDiv = document.createElement('div');
    imageDiv.className = 'quote-image';
    const img = document.createElement('img');
    img.src = data.image;
    img.alt = '引用图片';
    imageDiv.appendChild(img);
    wrapper.appendChild(imageDiv);
  }
  
  // 如果有描述，创建并添加描述元素
  if (data.describe) {
    const contentDiv = document.createElement('div');
    contentDiv.className = 'quote-content';
    contentDiv.textContent = data.describe;
    wrapper.appendChild(contentDiv);
  }
  
  // 将内容包装器添加到引用元素
  quoteElement.appendChild(wrapper);
  
  // 创建并添加关闭按钮
  const closeButton = document.createElement('div');
  closeButton.className = 'quote-close';
  closeButton.textContent = '×';
  quoteElement.appendChild(closeButton);
  
  // 将引用元素插入到编辑器开头
  if (editor.firstChild) {
    editor.insertBefore(quoteElement, editor.firstChild);
  } else {
    editor.appendChild(quoteElement);
  }

  // 在引用元素后添加零宽空格，并将光标放置在该位置
  let nodeToPlaceCursorAfter = quoteElement;
  const zeroWidthSpace = document.createTextNode('\u200B'); // 零宽空格，用于放置光标
  if (editor.lastChild === quoteElement || !quoteElement.nextSibling) {
      // 如果引用元素是最后一个子元素，在其后添加零宽空格
      editor.appendChild(zeroWidthSpace);
      nodeToPlaceCursorAfter = zeroWidthSpace;
  } else {
      // 否则，在引用元素和下一个元素之间插入零宽空格
      editor.insertBefore(zeroWidthSpace, quoteElement.nextSibling);
      nodeToPlaceCursorAfter = zeroWidthSpace;
  }

  // 处理引用元素的点击事件
  const handleQuoteClick = (e) => {
    e.stopPropagation(); // 阻止事件冒泡
    // 如果点击的是关闭按钮，移除引用元素
    if (e.target === closeButton || closeButton.contains(e.target)) {
      // 移除引用元素
      quoteElement.remove();
      // 如果零宽空格仍在编辑器中，也将其移除
      if (nodeToPlaceCursorAfter.parentNode === editor && nodeToPlaceCursorAfter.nodeValue === '\u200B') {
        nodeToPlaceCursorAfter.remove();
      }
      // 清空引用数据
      quoteData.value = null;
      // 移除键盘事件监听器
      editor.removeEventListener('keydown', handleDeleteQuote);
      // 触发输入事件更新编辑器状态
      handleInput(); 
      // 让编辑器重新获得焦点
      editor.focus();
    } else {
      // 如果点击的不是关闭按钮，将光标放在引用元素后面
      const newRange = document.createRange();
      // 根据节点位置决定光标放置位置
      newRange.setStartAfter(nodeToPlaceCursorAfter.parentNode === editor ? nodeToPlaceCursorAfter : quoteElement);
      newRange.collapse(true);
      if (selection) {
        selection.removeAllRanges();
        selection.addRange(newRange);
      }
      editor.focus();
    }
  };

  // 为引用元素添加点击事件监听器
  quoteElement.addEventListener('click', handleQuoteClick);
  // 为编辑器添加键盘事件监听器，处理引用元素的删除
  editor.addEventListener('keydown', handleDeleteQuote);

  // 使用setTimeout确保DOM操作完成后再设置光标位置
  setTimeout(() => {
    // 让编辑器获得焦点
    editor.focus();
    const newSelection = window.getSelection();
    if (!newSelection) return;

    // 尝试恢复光标位置
    let cursorPlaced = false;
    // 如果有保存的选区范围，尝试恢复
    if (savedRange) {
        try {
            // 检查保存的范围是否仍然有效
            if (editor.contains(savedRange.commonAncestorContainer) && savedRange.startContainer) {
                newSelection.removeAllRanges();
                newSelection.addRange(savedRange);
                cursorPlaced = true;
            }
        } catch (err) {
            // 忽略恢复选区时可能出现的错误
        }
    }

    // 如果未能恢复光标位置，设置新的光标位置
    if (!cursorPlaced) {
        const newRange = document.createRange();
        // 根据节点位置决定光标放置位置
        if (nodeToPlaceCursorAfter && nodeToPlaceCursorAfter.parentNode === editor) {
            // 如果零宽空格在编辑器中，将光标放在其后
            newRange.setStartAfter(nodeToPlaceCursorAfter);
        } else if (quoteElement.parentNode === editor && quoteElement.nextSibling) {
            // 如果引用元素有下一个兄弟节点，将光标放在下一个节点前
            newRange.setStartAfter(quoteElement.nextSibling);
        } else if (quoteElement.parentNode === editor) {
            // 如果引用元素是编辑器的子节点，将光标放在其后
            newRange.setStartAfter(quoteElement);
        } else {
            // 如果以上条件都不满足，将光标放在编辑器末尾
            newRange.selectNodeContents(editor);
            newRange.collapse(false);
        }
        newRange.collapse(true);
        newSelection.removeAllRanges();
        newSelection.addRange(newRange);
    }

    // 滚动到编辑器底部并触发输入事件
    editor.scrollTop = editor.scrollHeight;
    handleInput();
  }, 0); // 0毫秒延迟，确保在当前事件循环结束后执行
};

/**
 * 处理编辑消息事件
 * 
 * @param {Object} data - 要编辑的消息数据
 * 
 * 该函数在收到编辑消息事件时：
 * 1. 保存正在编辑的消息数据
 * 2. 清空编辑器
 * 3. 如果消息有内容，将其加载到编辑器中
 */
const onSubscribeEdit = (data) => {
  // 保存正在编辑的消息数据
  editingMessage.value = data
  // 清空编辑器当前内容
  clearEditor()
  
  // 如果消息有内容，加载到编辑器中
  if (data.content) {
    editorRef.value.innerHTML = data.content
    editorContent.value = data.content
    editorHtml.value = data.content
  }
}

/**
 * 处理清空编辑器事件
 * 
 * 该函数在收到清空编辑器事件时调用clearEditor函数
 */
const onSubscribeClear = () => {
  clearEditor()
}

/**
 * 保存编辑器草稿
 * 
 * 该函数负责保存编辑器内容到Pinia Store中：
 * 1. 创建临时DOM元素复制编辑器内容
 * 2. 移除引用元素
 * 3. 提取纯文本和HTML内容
 * 4. 检查内容是否为空
 * 5. 根据内容是否为空，更新或删除Pinia Store中的草稿数据
 */
const saveDraft = () => {
  // 如果没有索引名称或编辑器引用，直接返回
  if (!indexName.value || !editorRef.value) return
  
  // 创建文档片段和临时div元素
  const fragment = document.createDocumentFragment()
  const tempDiv = document.createElement('div')
  // 复制编辑器内容到临时div
  tempDiv.innerHTML = editorRef.value.innerHTML
  fragment.appendChild(tempDiv)
  
  // 移除引用元素
  const quoteElements = tempDiv.querySelectorAll('.editor-quote')
  quoteElements.forEach(quote => quote.remove())
  
  // 提取纯文本和HTML内容
  const contentToSave = tempDiv.textContent || ''
  const htmlToSave = tempDiv.innerHTML || ''
  // 解析编辑器内容获取结构化数据
  const currentEditor= parseEditorContent().items
  
  // 检查内容是否为空（文本、图片或文件）
  const hasContent = contentToSave.trim().length > 0 || 
                    htmlToSave.includes('<img') || 
                    htmlToSave.includes('editor-file')
  
  // 根据内容是否为空，更新或删除草稿数据
  if (currentEditor.length>0) {
    // 有内容，保存草稿
    editorDraftStore.items[indexName.value] = JSON.stringify({
      // 将结构化内容转换为纯文本（图片转为[图片]标记）
      content: currentEditor.reduce((result, x) => {
        if (x.type === 3) return result + '[图片]' // 图片类型转为[图片]标记
        if (x.type === 1) return result + x.content // 文本类型直接添加内容
        return result // 其他类型不处理
      }, ''),
      html: htmlToSave // 保存HTML内容
    })
  } else {
    // 没有内容，删除草稿
    delete editorDraftStore.items[indexName.value]
  }
}


/**
 * 加载编辑器草稿
 * 
 * 该函数负责从Pinia Store中加载草稿数据到编辑器：
 * 1. 清空编辑器内容和相关变量
 * 2. 尝试从Store中加载并解析草稿数据
 * 3. 如果解析成功，更新编辑器内容和相关变量
 * 4. 如果加载草稿时存在引用数据，调用onSubscribeQuote函数处理
 */
const loadDraft = () => {
  // 如果没有索引名称，直接返回
  if (!indexName.value) return
  
  // 使用nextTick确保DOM更新后再加载草稿
  nextTick(() => {
    // 保存当前引用数据
    const currentQuoteData = quoteData.value
    
    // 清空引用数据
    quoteData.value = null
    
    // 如果编辑器引用不存在，直接返回
    if (!editorRef.value) return
    
    // 清空编辑器内容和相关变量
    editorRef.value.innerHTML = ''
    editorContent.value = ''
    editorHtml.value = ''
    
    // 从Store中获取草稿数据
    const draft = editorDraftStore.items[indexName.value]
    
    // 如果存在草稿数据，尝试加载
    if (draft) {
      try {
        // 解析JSON格式的草稿数据
        const draftData = JSON.parse(draft)
        
        // 更新编辑器内容和相关变量
        editorRef.value.innerHTML = draftData.html || ''
        editorContent.value = draftData.content || ''
        editorHtml.value = draftData.html || ''
      } catch (error) {
        // 解析失败时记录警告并使用空内容
        console.warn('加载草稿失败，使用空内容', error)
      }
    }
    
    // 如果存在引用数据，重新添加到编辑器中
    if (currentQuoteData) {
      onSubscribeQuote(currentQuoteData)
    }
  })
}

/**
 * 监听索引名称变化，自动加载草稿
 * 当索引名称变化时，调用loadDraft函数加载对应的草稿
 * immediate: true 表示组件初始化时立即执行一次
 */
watch(indexName, loadDraft, { immediate: true })

/**
 * 处理文档点击事件
 * 当点击编辑器外部区域时，隐藏@提及列表
 * 
 * @param {Event} event - 点击事件对象
 */
const handleDocumentClick = (event) => {
  // 检查点击是否在编辑器外部
  if (!editorRef.value?.contains(event.target)) {
    // 隐藏@提及列表
    hideMentionList()
  }
}


/**
 * 组件生命周期钩子 - 组件挂载完成
 * 初始化编辑器，设置事件监听和订阅
 */
onMounted(() => {
  // 定义需要订阅的事件和对应的处理函数
  const subscriptions = [
    [EditorConst.Mention, onSubscribeMention], // @提及事件
    [EditorConst.Quote, onSubscribeQuote],     // 引用事件
    [EditorConst.Edit, onSubscribeEdit],       // 编辑事件
    [EditorConst.Clear, onSubscribeClear]      // 清空事件
  ]
  
  // 订阅所有事件
  subscriptions.forEach(([event, handler]) => {
    bus.subscribe(event, handler)
  })

  // 为编辑器添加点击事件监听器
  editorRef.value?.addEventListener('click', handleEditorClick)
  
  // 为文档添加点击事件监听器，用于处理编辑器外部点击
  document.addEventListener('click', handleDocumentClick)
  
  // 加载草稿
  loadDraft()
})

/**
 * 组件生命周期钩子 - 组件卸载前
 * 清理所有事件订阅和监听器，防止内存泄漏
 */
onBeforeUnmount(() => {
  
  const subscriptions = [
    [EditorConst.Mention, onSubscribeMention],
    [EditorConst.Quote, onSubscribeQuote],
    [EditorConst.Edit, onSubscribeEdit],
    [EditorConst.Clear, onSubscribeClear]
  ]
  
  
  subscriptions.forEach(([event, handler]) => {
    bus.unsubscribe(event, handler)
  })

  
  editorRef.value?.removeEventListener('click', handleEditorClick)
  
  
  document.removeEventListener('click', handleDocumentClick)
  const editor = editorRef.value
  if (editor && handleDeleteQuote) {
    editor.removeEventListener('keydown', handleDeleteQuote)
  }
})

/**
 * 表情选择事件处理函数
 * 
 * @param {Object} emoji - 选中的表情对象
 * 
 * 当用户从表情选择器中选择表情时触发
 * 调用onEmoticonEvent插入表情到编辑器
 * 然后关闭表情选择器面板
 */
const onEmoticonSelect = (emoji) => {
  
  onEmoticonEvent(emoji)
  isShowEmoticon.value = false
}

/**
 * 代码块提交事件处理函数
 * 
 * @param {Object} data - 代码块数据
 * 
 * 当用户提交代码块时触发
 * 通过emit向父组件发送code_event事件
 * 传递代码块数据并关闭代码块面板
 */
const onCodeSubmit = (data) => {
  emit('editor-event', {
    event: 'code_event',
    data,
    callBack: () => {}
  })
  isShowCode.value = false
}

/**
 * 投票提交事件处理函数
 * 
 * @param {Object} data - 投票数据
 * 
 * 当用户提交投票时触发
 * 通过emit向父组件发送vote_event事件
 * 传递投票数据并关闭投票面板
 */
const onVoteSubmit = (data) => {
  emit('editor-event', {
    event: 'vote_event',
    data,
    callBack: () => {}
  })
  isShowVote.value = false
}


/**
 * 处理编辑器点击事件
 * 用于处理引用元素的关闭按钮点击和隐藏@提及列表
 * 
 * @param {Event} event - 点击事件对象
 */
const handleEditorClick = (event) => {
  // 检查点击的是否是引用关闭按钮
  const closeButton = event.target.closest('.quote-close');

  // 如果点击了关闭按钮
  if (closeButton) {
    // 获取关闭按钮所在的引用元素
    const quoteElement = event.target.closest('.editor-quote');
    if (quoteElement) {
      // 移除引用元素
      quoteElement.remove();
      // 清空引用数据
      quoteData.value = null;

      // 触发输入事件，更新编辑器内容
      handleInput({ target: editorRef.value });

      // 阻止事件默认行为和冒泡
      event.preventDefault();
      event.stopPropagation();
    }
  }
  
  // 检查点击的是否是@提及列表
  const isMentionListClick = event.target.closest('.mention-list');
  
  // 如果@提及列表显示中，且点击的不是@提及列表，则隐藏@提及列表
  if (showMention.value && !isMentionListClick) {
    hideMentionList();
  }
};
</script>

<template>
  <!-- 
    编辑器容器组件
    使用el-container布局组件创建垂直布局的编辑器
    包含工具栏和编辑区域两个主要部分
  -->
  <section class="el-container editor">
    <section class="el-container is-vertical">
      <!-- 
        工具栏区域 
        使用el-header组件作为工具栏容器
        包含各种编辑工具按钮
      -->
      <header class="el-header toolbar bdr-t">
        <div class="tools pr-30px">
          <!-- 
            表情选择器弹出框
            使用n-popover组件创建悬浮的表情选择面板
            通过trigger="click"设置点击触发
            raw属性表示内容不会被包装在额外的div中
          -->
          <n-popover
            placement="top-start"
            trigger="click"
            raw
            :show-arrow="false"
            :width="300"
            ref="emoticonRef"
            style="width: 500px; height: 250px; border-radius: 10px; overflow: hidden"
          >
            <!-- 
              触发器模板
              点击此区域会显示表情选择器
            -->
            <template #trigger>
              <div class="item pointer">
                <n-icon size="18" class="icon" :component="SmilingFace" />
                <p class="tip-title">表情符号</p>
              </div>
            </template>

            <!-- 
              表情选择器组件
              通过on-select事件将选中的表情传递给onEmoticonEvent处理函数
            -->
            <MeEditorEmoticon @on-select="onEmoticonEvent" />
          </n-popover>

          <!-- 
            工具栏其他功能按钮
            通过v-for循环渲染navs数组中定义的工具按钮
            每个按钮包含图标和提示文本
            通过v-show="nav.show"控制按钮的显示/隐藏
            点击时执行nav.click定义的函数
          -->
          <div
            class="item pointer"
            v-for="nav in navs"
            :key="nav.title"
            v-show="nav.show"
            @click="nav.click"
          >
            <n-icon size="18" class="icon" :component="nav.icon" />
            <p class="tip-title">{{ nav.title }}</p>
          </div>
          <n-button class="w-80px h-30px ml-auto" type="primary" @click="sendMessage">
            <template #icon>
        <n-icon>
          <IosSend />
        </n-icon>
      </template>
      发送
    </n-button>
        </div>
      </header>

      <!-- 
        编辑器主体区域
        使用el-main组件作为主要内容区域
      -->
      <main class="el-main height100">
        <!-- 
          可编辑区域
          使用contenteditable="true"使div可编辑，这是HTML5的属性
          ref="editorRef"用于获取DOM引用
          placeholder属性用于显示占位文本
          绑定多个事件处理函数处理用户交互
        -->
        <div
          ref="editorRef"
          class="custom-editor"
          contenteditable="true"
          :placeholder="placeholder"
          @input="handleInput"
          @keydown="handleKeydown"
          @paste="handlePaste"
          @focus="handleFocus"
          @blur="handleBlur"
        ></div>

        <!-- 
          @提及列表
          当用户输入@时显示的用户列表
          通过v-if="showMention"控制显示/隐藏
          使用动态样式定位列表位置
        -->
        <div
          v-if="showMention && dialogueStore.talk.talk_type === 2"
          class="mention-list py-5px"
          :style="{ top: mentionPosition.top + 'px', left: mentionPosition.left + 'px' }"
        >
          <ul class="max-h-140px w-163px overflow-auto hide-scrollbar">
            <li
              v-for="(member, index) in mentionList"
              :key="member.user_id || member.id"
              class="cursor-pointer px-14px h-42px"
              :class="{ 'bg-#EEE9F9': index === selectedMentionIndex }"
              @mousedown.prevent="handleMentionSelectByMouse(member)"
              @mouseover="selectedMentionIndex = index"
            >
              <div class="flex items-center border-b-1px border-b-solid border-b-#F8F8F8 h-full">
                <img class="w-26px h-26px rounded-50% mr-11px" :src="member.avatar" alt="">
                <span>{{ member.nickname }}</span>
              </div>
            </li>
          </ul>
        </div>
      </main>
    </section>
  </section>

  <!-- 
    隐藏的文件上传表单
    使用display: none隐藏表单
    包含两个文件输入框，分别用于上传图片和其他文件
    通过ref获取DOM引用
  -->
  <form enctype="multipart/form-data" style="display: none">
    <input type="file" ref="fileImageRef" accept="image/*" @change="onUploadSendImg" />
    <input type="file" ref="uploadFileRef" @change="onUploadFile" />
  </form>


</template>

<style lang="less" scoped>
/**
 * 编辑器组件样式
 * 使用CSS变量定义可复用的颜色值
 * --tip-bg-color: 提示背景色
 */
.editor {
  --tip-bg-color: rgb(241 241 241 / 90%);
  height: 100%;

  /**
   * 工具栏样式
   * 固定高度的顶部工具栏
   */
  .toolbar {
    height: 38px;
    display: flex;

    /**
     * 工具按钮容器
     * 使用flex布局排列工具按钮
     */
    .tools {
      height: 40px;
      flex: auto;
      display: flex;
      align-items: center;
      
      /**
       * 单个工具按钮样式
       * 使用flex布局居中对齐内容
       * 相对定位用于放置提示文本
       */
      .item {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 35px;
        margin: 0 2px;
        position: relative;
        user-select: none; 

        /**
         * 提示文本样式
         * 默认隐藏，悬停时显示
         * 使用绝对定位在按钮下方显示
         */
        .tip-title {
          display: none;
          position: absolute;
          top: 40px;
          left: 0px;
          line-height: 26px;
          background-color: var(--tip-bg-color);
          color: var(--im-text-color);
          min-width: 20px;
          font-size: 12px;
          padding: 0 5px;
          border-radius: 2px;
          white-space: pre; 
          user-select: none;
          z-index: 999999999999; 
        }

        /**
         * 悬停效果
         * 当鼠标悬停在按钮上时显示提示文本
         */
        &:hover {
          .tip-title {
            display: block;
          }
        }
      }
    }
  }
  :deep(.editor-file) {
    display: inline-block;
    padding: 5px 10px;
    margin: 5px 0;
    background-color: #f5f5f5;
    border: 1px solid #e0e0e0;
    border-radius: 4px;
    color: #462AA0;
    text-decoration: none;
    position: relative;
    padding-right: 60px; 
    max-width: 100%;
    box-sizing: border-box;
    overflow: hidden;
    text-overflow: ellipsis; 
    white-space: nowrap; 
    
    &::after {
      content: attr(data-size);
      position: absolute;
      right: 10px;
      color: #757575;
      font-size: 12px;
    }
    
    &:hover {
      background-color: #e3f2fd;
    }
  }

  :deep(.editor-emoji) {
    display: inline-block;
    width: 24px;
    height: 24px;
    vertical-align: middle; 
    margin: 0 2px;
  }
  :deep(.editor-quote) {
    margin-bottom: 8px;
    padding: 8px 12px;
    background-color: var(--im-message-left-bg-color, #f5f5f5);
    border-left: 3px solid var(--im-primary-color, #409eff);
    border-radius: 4px;
    font-size: 13px;
    position: relative;
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 
    cursor: pointer; 
    user-select: none; 
    transition: background-color 0.2s ease; 
    
    /**
     * 引用卡片悬停效果
     * 改变背景色提供视觉反馈
     */
    &:hover {
      background-color: var(--im-message-left-bg-hover-color, #eaeaea);
    }
    
    /**
     * 引用内容容器
     * 使用flex: 1占据剩余空间
     * 处理内容溢出
     */
    .quote-content-wrapper {
      flex: 1;
      overflow: hidden;
    }
    
    /**
     * 引用标题样式
     * 使用主题色显示引用来源
     */
    .quote-title {
      color: var(--im-primary-color, #409eff);
      margin-bottom: 4px;
      font-weight: 500;
    }
    
    /**
     * 引用内容样式
     * 限制显示行数，超出部分显示省略号
     * 使用-webkit-line-clamp实现多行文本截断
     */
    .quote-content {
      color: var(--im-text-color, #333);
      word-break: break-all; 
      white-space: normal; 
      overflow: hidden;
      text-overflow: ellipsis; 
      display: -webkit-box;
      -webkit-line-clamp: 2; 
      -webkit-box-orient: vertical;
    }
    
    /**
     * 引用图片样式
     * 限制图片大小，添加圆角
     */
    .quote-image img {
      max-width: 100px;
      max-height: 60px;
      border-radius: 3px;
      pointer-events: none; 
    }
    
    /**
     * 引用关闭按钮样式
     * 圆形按钮，悬停时改变背景色和文字颜色
     */
    .quote-close {
      width: 18px;
      height: 18px;
      line-height: 16px;
      text-align: center;
      border-radius: 50%;
      background-color: rgba(0, 0, 0, 0.1);
      color: #666;
      cursor: pointer;
      font-size: 16px;
      margin-left: 8px;
      user-select: none;
      
      &:hover {
        background-color: rgba(0, 0, 0, 0.2);
        color: #333;
      }
    }
  }

  .custom-editor {
    width: 100%;
    height: 100%;
    padding: 8px;
    border: none;
    outline: none;
    resize: none;
    font-size: 14px;
    line-height: 1.5;
    color: #333;
    background: transparent;
    overflow-y: auto;

    
    &:empty:before {
      content: attr(placeholder);
      color: #999;
      pointer-events: none;
    }

    /**
     * 自定义滚动条样式 - 轨道
     * 使用::-webkit-scrollbar伪元素自定义滚动条轨道
     * 设置宽度和高度为3px，背景色为透明
     */
    &::-webkit-scrollbar {
      width: 3px;
      height: 3px;
      background-color: unset;
    }

    /**
     * 自定义滚动条样式 - 滑块
     * 使用::-webkit-scrollbar-thumb伪元素自定义滚动条滑块
     * 设置圆角和透明背景色
     */
    &::-webkit-scrollbar-thumb {
      border-radius: 3px;
      background-color: transparent;
    }

    /**
     * 滚动条悬停效果
     * 当鼠标悬停在编辑器上时，显示滚动条滑块
     * 使用CSS变量适配不同主题的滚动条颜色
     */
    &:hover {
      &::-webkit-scrollbar-thumb {
        background-color: var(--im-scrollbar-thumb);
      }
    }
  }

  /**
   * 编辑器占位符样式
   * 使用:empty::before伪元素在编辑器为空时显示占位文本
   * content: attr(placeholder)获取placeholder属性的值作为显示内容
   */
  .custom-editor:empty::before {
    content: attr(placeholder);
    color: #999;
    pointer-events: none; 
    font-family: PingFang SC, Microsoft YaHei, 'Alibaba PuHuiTi 2.0 45' !important;
  }

  /**
   * 编辑器聚焦样式
   * 移除默认的聚焦轮廓
   */
  .custom-editor:focus {
    outline: none;
  }



  /**
   * @提及悬停效果
   * 当鼠标悬停在@提及上时改变背景色
   */
  .mention:hover {
    background-color: #bae7ff;
  }

  /**
   * 图片样式
   * 限制编辑器中插入的图片大小
   * 添加圆角和鼠标指针样式
   */
  
  
  
  
  
  
  
  
  

  /**
   * 表情样式
   * 设置表情图片的大小和对齐方式
   */
  .editor-emoji {
    width: 20px;
    height: 20px;
    vertical-align: middle;
    margin: 0 2px;
  }

  /**
   * @提及列表样式
   * 定位和样式化@提及的下拉列表
   * 使用绝对定位、白色背景和阴影效果
   */
  .mention-list {
    position: absolute;
    background-color: white;
    border: 1px solid #eee;
    border-radius: 4px;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    z-index: 1000;

  }

  
  .quote-card {
    background: #f5f5f5;
    border-left: 3px solid #1890ff;
    padding: 8px 12px;
    margin: 8px 0;
    border-radius: 4px;
    position: relative;
  }

  /**
   * 引用内容样式
   * 设置较小的字体大小
   */
  .quote-content {
    font-size: 12px;
  }

  /**
   * 引用标题样式
   * 使用粗体和主题蓝色
   * 添加底部间距
   */
  .quote-title {
    font-weight: bold;
    color: #1890ff;
    margin-bottom: 4px;
  }

  /**
   * 引用文本样式
   * 使用灰色文本和适当的行高
   */
  .quote-text {
    color: #666;
    line-height: 1.4;
  }

  /**
   * 引用关闭按钮样式
   * 绝对定位在右上角
   * 移除默认按钮样式，添加鼠标指针
   */
  .quote-close {
    position: absolute;
    top: 4px;
    right: 4px;
    background: none;
    border: none;
    cursor: pointer;
    color: #999;
    font-size: 12px;
  }

  /**
   * 编辑提示样式
   * 使用橙色背景和边框创建警示效果
   * 添加内边距、圆角和适当的字体大小
   * 使用flex布局对齐内容
   */
  .edit-tip {
    background: #fff7e6;
    border: 1px solid #ffd591;
    padding: 6px 12px;
    margin-bottom: 8px;
    border-radius: 4px;
    font-size: 12px;
    color: #d46b08;
    display: flex;
    align-items: center;
    gap: 8px;
  }

  /**
   * 编辑提示按钮样式
   * 移除默认按钮样式，添加鼠标指针
   * 使用margin-left: auto将按钮推到右侧
   */
  .edit-tip button {
    background: none;
    border: none;
    cursor: pointer;
    color: #d46b08;
    margin-left: auto;
  }
}

/**
 * 暗色模式样式调整
 * 使用HTML属性选择器检测暗色主题
 * 重新定义暗色模式下的CSS变量
 */
html[theme-mode='dark'] {
  .editor {
    --tip-bg-color: #48484d;
  }
}

/**
 * 图片上传加载样式
 */
:deep(.editor-image-wrapper.image-upload-loading::before) {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 24px;
  height: 24px;
  margin-top: -12px;
  margin-left: -12px;
  border: 2px solid rgba(0, 0, 0, 0.1);
  border-top-color: #333;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
  z-index: 1;
}

:deep(.editor-image-wrapper.image-upload-loading img) {
  opacity: 0.5;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/**
 * 隐藏滚动条样式
 * 保留滚动功能但隐藏滚动条的视觉显示
 */
.hide-scrollbar {
  
  &::-webkit-scrollbar {
    width: 0;
    display: none;
  }
  
  
  scrollbar-width: none;
  
  
  -ms-overflow-style: none;
}
</style>