<script setup>
// 引入Tiptap编辑器相关依赖
import { Editor, EditorContent, useEditor } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Placeholder from '@tiptap/extension-placeholder'
import Mention from '@tiptap/extension-mention'
import Link from '@tiptap/extension-link'
import { Extension, Node } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'

// 引入Vue核心功能
import { reactive, watch, ref, markRaw, computed, onMounted, onUnmounted, shallowRef } from 'vue'
// 引入Naive UI的弹出框组件
import { NPopover, NIcon } from 'naive-ui'
// 引入图标组件
import {
  Voice as IconVoice,    // 语音图标
  SourceCode,            // 代码图标
  Local,                 // 地理位置图标
  SmilingFace,           // 表情图标
  Pic,                   // 图片图标
  FolderUpload,          // 文件上传图标
  Ranking,               // 排名图标（用于投票）
  History,               // 历史记录图标
  Close                  // 关闭图标
} from '@icon-park/vue-next'

// 引入状态管理
import { useDialogueStore, useEditorDraftStore } from '@/store'
// 引入获取图片信息的工具函数
import { getImageInfo } from '@/utils/functions'
// 引入编辑器常量定义
import { EditorConst } from '@/constant/event-bus'
// 引入事件调用工具
import { emitCall } from '@/utils/common'
// 引入默认头像常量
import { defAvatar } from '@/constant/default'
// 引入编辑器各子组件
import MeEditorVote from './MeEditorVote.vue'            // 投票组件
import MeEditorEmoticon from './MeEditorEmoticon.vue'    // 表情组件
import MeEditorCode from './MeEditorCode.vue'            // 代码编辑组件
import MeEditorRecorder from './MeEditorRecorder.vue'    // 录音组件
// 引入上传API
import { uploadImg } from '@/api/upload'
// 引入事件总线钩子
import { useEventBus } from '@/hooks'

// 定义组件的事件
const emit = defineEmits(['editor-event'])
// 获取对话状态管理
const dialogueStore = useDialogueStore()
// 获取编辑器草稿状态管理
const editorDraftStore = useEditorDraftStore()
// 定义组件props
const props = defineProps({
  vote: {
    type: Boolean,
    default: false  // 是否显示投票功能
  },
  members: {
    default: () => []  // 聊天成员列表，用于@功能
  }
})

// 计算当前对话索引名称（标识当前聊天）
const indexName = computed(() => dialogueStore.index_name)
// 控制是否显示编辑器的投票界面
const isShowEditorVote = ref(false)
// 控制是否显示编辑器的代码界面
const isShowEditorCode = ref(false)
// 控制是否显示录音界面
const isShowEditorRecorder = ref(false)
const uploadingImages = ref(new Map())
// 图片文件上传DOM引用
const fileImageRef = ref()
// 文件上传DOM引用
const uploadFileRef = ref()
// 表情面板引用
const emoticonRef = ref()
// 表情面板显示状态
const showEmoticon = ref(false)
// 引用消息数据
const quoteData = ref(null)

// 自定义Emoji扩展
const Emoji = Node.create({
  name: 'emoji',
  group: 'inline',
  inline: true,
  selectable: true,
  atom: true,
  
  addAttributes() {
    return {
      alt: {
        default: null,
      },
      src: {
        default: null,
      },
      width: {
        default: '24px',
      },
      height: {
        default: '24px',
      },
      class: {
        default: 'ed-emoji',
      },
    }
  },
  
  parseHTML() {
    return [
      {
        tag: 'img.ed-emoji',
      },
    ]
  },
  
  renderHTML({ HTMLAttributes }) {
    return ['img', HTMLAttributes]
  },
})



// 创建自定义键盘处理插件，处理Enter键发送消息
const EnterKeyPlugin = new Plugin({
  key: new PluginKey('enterKey'),
  props: {
    handleKeyDown: (view, event) => {
      // 如果按下Enter键且没有按下Shift键，则发送消息
      if (event.key === 'Enter' && !event.shiftKey) {
        event.preventDefault()
        onSendMessage()
        return true
      }
      return false
    },
  },
})

// 自定义键盘扩展
const CustomKeyboard = Extension.create({
  name: 'customKeyboard',
  
  addProseMirrorPlugins() {
    return [
      EnterKeyPlugin,
    ]
  },
})

// 创建编辑器实例
const editor = useEditor({
  extensions: [
    StarterKit,
    Image.extend({
      addNodeView() {
        return ({ node, getPos, editor }) => {
          const container = document.createElement('span')
          container.style.position = 'relative'
          container.style.display = 'inline-block'

          const img = document.createElement('img')
          img.setAttribute('src', node.attrs.src)
          img.style.maxWidth = '100px'
          img.style.borderRadius = '3px'
          img.style.backgroundColor = '#48484d'
          img.style.margin = '0px 2px'

          container.appendChild(img)

          if (uploadingImages.value.has(node.attrs.src)) {
            container.classList.add('image-upload-loading')
          }

          const stopWatch = watch(uploadingImages, () => {
            if (uploadingImages.value.has(node.attrs.src)) {
              container.classList.add('image-upload-loading')
            } else {
              container.classList.remove('image-upload-loading')
            }
          }, { deep: true })

          return {
            dom: container,
            destroy() {
              stopWatch()
            }
          }
        }
      }
    }).configure({
      inline: true,
      allowBase64: true,
    }),
    Placeholder.configure({
      placeholder: '按Enter发送 / Shift+Enter 换行',
    }),
    Mention.configure({
      HTMLAttributes: {
        class: 'mention',
      },
      suggestion: {
        allowedPrefixes: null,
        hideOnClickOutside: true,
        hideOnKeyDown: true,
        emptyQueryClass: 'is-empty-query',
        items: ({ query }) => {
          if (!props.members.length) {
            return []
          }
          
          let list = [...props.members]
          
          if ((dialogueStore.groupInfo).is_manager) {
            list.unshift({ id: 0, nickname: '所有人', avatar: defAvatar, value: '所有人' })
          }
          
          const filteredItems = list.filter(
            (item) => item.nickname.toLowerCase().includes(query.toLowerCase())
          )
          
          // 如果没有匹配项，返回空数组以关闭弹窗
          if (filteredItems.length === 0) {
            return []
          }
          
          return filteredItems
        },
        render: () => {
          let component
          let popup
          let handleClickOutside
          
          return {
            onStart: (props) => {
              // 创建提及列表容器
              popup = document.createElement('div')
              popup.classList.add('ql-mention-list-container', 'me-scrollbar', 'me-scrollbar-thumb')
              document.body.appendChild(popup)
              
              // 添加全局点击事件监听器，点击弹窗外部时关闭弹窗
              handleClickOutside = (event) => {
                if (popup && !popup.contains(event.target)) {
                  popup.remove()
                  document.removeEventListener('click', handleClickOutside)
                }
              }
              // 使用setTimeout确保事件不会立即触发
              setTimeout(() => {
                document.addEventListener('click', handleClickOutside)
              }, 100)
              
              // 渲染提及列表
              props.items.forEach((item, index) => {
                const mentionItem = document.createElement('div')
                mentionItem.classList.add('ed-member-item')
                mentionItem.innerHTML = `<img src="${item.avatar}" class="avator"/><span class="nickname">${item.nickname}</span>`
                mentionItem.addEventListener('click', () => {
                  props.command({ id: item.id, label: item.nickname })
                })
                
                if (index === props.selectedIndex) {
                  mentionItem.classList.add('selected')
                }
                
                popup.appendChild(mentionItem)
              })
              
              // 定位提及列表
              const coords = props.clientRect()
              popup.style.position = 'fixed'
              popup.style.top = `${coords.top + window.scrollY}px`
              popup.style.left = `${coords.left + window.scrollX}px`
            },
            
            onUpdate: (props) => {
              // 更新选中项
              const items = popup.querySelectorAll('.ed-member-item')
              items.forEach((item, index) => {
                if (index === props.selectedIndex) {
                  item.classList.add('selected')
                } else {
                  item.classList.remove('selected')
                }
              })
            },
            
            onKeyDown: (props) => {
              // 处理键盘事件
              // Escape键关闭弹窗
              if (props.event.key === 'Escape') {
                popup.remove()
                return true
              }
              
              // 空格键、回车键或其他非导航键也关闭弹窗
              const navigationKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Tab']
              if (!navigationKeys.includes(props.event.key) && props.items.length === 0) {
                popup.remove()
                return false
              }
              
              return false
            },
            
            onExit: () => {
              // 清理弹窗和事件监听器
              if (popup) {
                popup.remove()
                // 移除所有可能的点击事件监听器
                document.removeEventListener('click', handleClickOutside)
              }
            },
          }
        },
      },
    }),
    Link,
    Emoji,
    CustomKeyboard,
  ],
  content: '',
  autofocus: true,
  editable: true,
  injectCSS: false,
  onUpdate: () => {
    onEditorChange()
  },
  editorProps: {
    handlePaste: (view, event) => {
      const items = (event.clipboardData || event.originalEvent.clipboardData).items
      for (const item of items) {
        if (item.type.indexOf('image') === 0) {
          event.preventDefault()
          const file = item.getAsFile()
          if (!file) continue

          const tempUrl = URL.createObjectURL(file)
          const { state, dispatch } = view
          const { tr } = state
          const node = state.schema.nodes.image.create({ src: tempUrl })
          dispatch(tr.replaceSelectionWith(node))

          const form = new FormData()
          form.append('file', file)
          form.append('source', 'fonchain-chat')

          uploadingImages.value.set(tempUrl, true)

          uploadImg(form)
            .then(({ code, data, message }) => {
              if (code === 0 && data.ori_url) {
                const pos = findImagePos(tempUrl)
                if (pos !== -1) {
                  const { tr } = view.state
                  view.dispatch(
                    tr.setNodeMarkup(pos, null, { src: data.ori_url })
                  )
                }
              } else {
                window['$message'].error(message || '图片上传失败')
                removeImage(tempUrl)
              }
            })
            .catch(() => {
              window['$message'].error('图片上传失败')
              removeImage(tempUrl)
            })
            .finally(() => {
              uploadingImages.value.delete(tempUrl)
              URL.revokeObjectURL(tempUrl)
            })

          return true
        }
      }
      return false
    }
  }
})

/**
 * 上传图片函数
 * @param file 文件对象
 * @returns Promise，成功时返回图片URL
 */
function findImagePos(url) {
  if (!editor.value) return -1
  let pos = -1
  editor.value.state.doc.descendants((node, p) => {
    if (node.type.name === 'image' && node.attrs.src === url) {
      pos = p
      return false
    }
    return true
  })
  return pos
}

function removeImage(url) {
  if (!editor.value) return
  const pos = findImagePos(url)
  if (pos !== -1) {
    const { tr } = editor.value.state
    editor.value.view.dispatch(tr.delete(pos, pos + 1))
  }
}

function onUploadImage(file) {
  return new Promise((resolve) => {
    let image = new Image()
    image.src = URL.createObjectURL(file)
    image.onload = () => {
      const form = new FormData()
      form.append('file', file)
      form.append("source", "fonchain-chat");  // 图片来源标识
      // 添加图片尺寸信息作为URL参数
      form.append("urlParam", `width=${image.width}&height=${image.height}`);

      // 调用上传API
      uploadImg(form).then(({ code, data, message }) => {
        if (code == 0) {
          resolve(data.ori_url)  // 返回原始图片URL
        } else {
          resolve('')
          window['$message'].error(message)  // 显示错误信息
        }
      })
    }
  })
}

/**
 * 投票事件处理
 * @param data 投票数据
 */
function onVoteEvent(data) {
  const msg = emitCall('vote_event', data, (ok) => {
    if (ok) {
      isShowEditorVote.value = false  // 成功后关闭投票界面
    }
  })

  emit('editor-event', msg)
}

/**
 * 表情事件处理
 * @param data 表情数据
 */
function onEmoticonEvent(data) {
  // 关闭表情面板
  showEmoticon.value = false

  if (data.type == 1) {
    // 插入文本表情
    if (!editor.value) return
    
    if (data.img) {
      // 插入图片表情
      editor.value.chain().focus().insertContent({
        type: 'emoji',
        attrs: {
          alt: data.value,
          src: data.img,
          width: '24px',
          height: '24px',
        },
      }).run()
    } else {
      // 插入文本表情
      editor.value.chain().focus().insertContent(data.value).run()
    }
  } else {
    // 发送整个表情包
    let fn = emitCall('emoticon_event', data.value, () => {})
    emit('editor-event', fn)
  }
}

/**
 * 代码事件处理
 * @param data 代码数据
 */
function onCodeEvent(data) {
  const msg = emitCall('code_event', data, (ok) => {
    isShowEditorCode.value = false  // 成功后关闭代码界面
  })

  emit('editor-event', msg)
}

/**
 * 文件上传处理
 * @param e 上传事件对象
 */
async function onUploadFile(e) {
  let file = e.target.files[0]

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

  if (file.type.indexOf('image/') === 0) {
    // 处理图片文件 - 立即显示临时消息，然后上传
    let fn = emitCall('image_event', file, () => {})
    emit('editor-event', fn)

    return
  }

  if (file.type.indexOf('video/') === 0) {
    // 处理视频文件
    let fn = emitCall('video_event', file, () => {})
    emit('editor-event', fn)
  } else {
    // 处理其他类型文件
    let fn = emitCall('file_event', file, () => {})
    emit('editor-event', fn)
  }
}

/**
 * 录音事件处理
 * @param file 录音文件
 */
function onRecorderEvent(file) {
  emit('editor-event', emitCall('file_event', file))
  isShowEditorRecorder.value = false  // 关闭录音界面
}

// 将Tiptap内容转换为消息格式
function tiptapToMessage() {
  if (!editor.value) return []

  const json = editor.value.getJSON()
  const messages = []
  let currentTextBuffer = ''
  let currentMentions = []
  let currentMentionUids = new Set()

  const flushTextBuffer = () => {
    const content = currentTextBuffer.trim()
    if (content) {
      const data = {
        items: [{ type: 1, content: content }],
        mentions: [...currentMentions],
        mentionUids: Array.from(currentMentionUids)
      }
      messages.push({ type: 'text', data })
    }
    currentTextBuffer = ''
    currentMentions = []
    currentMentionUids.clear()
  }

  const processInlines = nodes => {
    nodes.forEach(node => {
      if (node.type === 'text') {
        currentTextBuffer += node.text
      } else if (node.type === 'mention') {
        currentTextBuffer += `@${node.attrs.label} `
        const uid = parseInt(node.attrs.id)
        if (!currentMentionUids.has(uid)) {
          currentMentionUids.add(uid)
          currentMentions.push({ name: `@${node.attrs.label}`, atid: uid })
        }
      } else if (node.type === 'emoji') {
        currentTextBuffer += node.attrs.alt
      } else if (node.type === 'hardBreak') {
        currentTextBuffer += '\n'
      } else if (node.type === 'image') {
        // 处理段落内的图片
        flushTextBuffer()
        const data = {
          ...getImageInfo(node.attrs.src),
          url: node.attrs.src
        }
        messages.push({ type: 'image', data })
      }
    })
  }

  if (json.content) {
    json.content.forEach(node => {
      if (node.type === 'paragraph') {
        if (node.content) {
          processInlines(node.content)
        }
        currentTextBuffer += '\n' // Add newline after each paragraph
      } else if (node.type === 'image') {
        flushTextBuffer()
        const data = {
          ...getImageInfo(node.attrs.src),
          url: node.attrs.src
        }
        messages.push({ type: 'image', data })
      }
    })
  }

  flushTextBuffer()

  if (messages.length > 0) {
    const lastMessage = messages[messages.length - 1]
    if (lastMessage.type === 'text') {
      lastMessage.data.items[0].content = lastMessage.data.items[0].content.trim()
      if (!lastMessage.data.items[0].content) {
        messages.pop()
      }
    }
  }

  return messages
}

// 将Tiptap内容转换为纯文本
function tiptapToString() {
  if (!editor.value) return ''
  
  return editor.value.getText()
}

// 检查编辑器是否为空
function isEditorEmpty() {
  if (!editor.value) return true
  
  const json = editor.value.getJSON()
  
  // 检查是否只有一个空段落
  return !json.content || (
    json.content.length === 1 && 
    json.content[0].type === 'paragraph' && 
    (!json.content[0].content || json.content[0].content.length === 0)
  )
}

/**
 * 发送消息处理
 * 根据编辑器内容类型发送不同类型的消息
 */
function onSendMessage() {
  if (uploadingImages.value.size > 0) {
    return window['$message'].info('正在上传图片，请稍后再发')
  }
  if (!editor.value || (isEditorEmpty() && !quoteData.value)) return

  const messages = tiptapToMessage()

  if (messages.length === 0 && !quoteData.value) {
    return
  }

  let canClear = true
  messages.forEach(msg => {
    if (msg.type === 'text') {
      if (msg.data.items[0].content.length > 1024) {
        window['$message'].info('发送内容超长，请分条发送')
        canClear = false
        return
      }
      
      // 添加引用消息参数
      if (quoteData.value) {
        msg.data.quoteId = quoteData.value.id
        msg.data.quote = { ...quoteData.value }
      }
      
      emit('editor-event', emitCall('text_event', msg.data))
    } else if (msg.type === 'image') {
       const data = {
        height: 0,
        width: 0,
        size: 10000,
        url: msg.data.url,
      }
      
      // 添加引用消息参数
      if (quoteData.value) {
        data.quoteId = quoteData.value.id
        data.quote = { ...quoteData.value }
      }
      
      emit('editor-event', emitCall('image_event', data))
    }
  })

  // 如果只有引用消息但没有内容，也发送一条空文本消息带引用
  if (messages.length === 0 && quoteData.value) {
    const emptyData = {
      items: [{ type: 1, content: '' }],
      mentions: [],
      mentionUids: [],
      quoteId: quoteData.value.id,
      quote: { ...quoteData.value }
    }
    emit('editor-event', emitCall('text_event', emptyData))
  }

  if (canClear) {
    editor.value?.commands.clearContent(true)
    // 清空引用数据
    quoteData.value = null
  }
}

/**
 * 编辑器内容改变时的处理
 * 保存草稿并触发输入事件
 */
function onEditorChange() {
  if (!editor.value) return
  
  const text = tiptapToString()
  
  if (!isEditorEmpty() || quoteData.value) {
    // 保存草稿到store
    editorDraftStore.items[indexName.value || ''] = JSON.stringify({
      text: text,
      content: editor.value.getJSON(),
      quoteData: quoteData.value
    })
  } else {
    // 编辑器为空时删除对应草稿
    delete editorDraftStore.items[indexName.value || '']
  }

  // 触发输入事件
  emit('editor-event', emitCall('input_event', text))
}

/**
 * 加载编辑器草稿内容
 * 当切换聊天对象时，加载对应的草稿
 */
function loadEditorDraftText() {
  if (!editor.value) return

  // 保存当前引用数据
  const currentQuoteData = quoteData.value
  quoteData.value = null

  // 从缓存中加载编辑器草稿
  let draft = editorDraftStore.items[indexName.value || '']
  if (draft) {
    const parsed = JSON.parse(draft)
    if (parsed.content) {
      editor.value.commands.setContent(parsed.content)
    } else if (parsed.text) {
      editor.value.commands.setContent(parsed.text)
    }
    
    // 如果草稿中有引用数据，恢复它
    if (parsed.quoteData) {
      quoteData.value = parsed.quoteData
    }
  } else {
    editor.value.commands.clearContent(true)  // 没有草稿则清空编辑器
  }

  // 如果有当前引用数据，优先使用它
  if (currentQuoteData) {
    quoteData.value = currentQuoteData
  }

  // 设置光标位置到末尾
  editor.value.commands.focus('end')
}

/**
 * 处理@成员事件
 * @param data @成员数据
 */
function onSubscribeMention(data) {
  if (!editor.value) return
  
  // 插入@项
  editor.value.chain().focus().insertContent({
    type: 'mention',
    attrs: {
      id: data?.id,
      label: data.value,
    },
  }).run()
}

/**
 * 处理引用事件
 * @param data 引用数据
 */
function onSubscribeQuote(data) {
  if (!editor.value) return
  
  // 保存引用数据
  quoteData.value = data
}

/**
 * 处理编辑消息事件
 * @param data 消息数据
 */
function onSubscribeEdit(data) {
  if (!editor.value) return
  
  // 清空当前编辑器内容
  editor.value.commands.clearContent(true)
  
  // 插入要编辑的文本内容
  editor.value.commands.insertContent(data.content)
  
  // 设置光标位置到末尾
  editor.value.commands.focus('end')
}

// 底部工具栏配置
const navs = reactive([
  {
    title: '图片',
    icon: markRaw(Pic),
    show: true,
    click: () => {
      fileImageRef.value.click()  // 触发图片上传
    }
  },
  {
    title: '文件',
    icon: markRaw(FolderUpload),
    show: true,
    click: () => {
      uploadFileRef.value.click()  // 触发文件上传
    }
  },
  
])

// 监听聊天索引变化，切换聊天时加载对应草稿
watch(indexName, loadEditorDraftText, { immediate: true })

// 组件挂载时初始化
onMounted(() => {
  loadEditorDraftText()
})

// 订阅编辑器相关事件总线事件
useEventBus([
  { name: EditorConst.Mention, event: onSubscribeMention },  // @成员事件
  { name: EditorConst.Quote, event: onSubscribeQuote },       // 引用事件
  { name: EditorConst.Edit, event: onSubscribeEdit }          // 编辑消息事件
])
</script>

<template>
  <!-- 编辑器容器 -->
  <section class="el-container editor">
    <section class="el-container is-vertical">
    
      
      <!-- 工具栏区域 -->
      <header class="el-header toolbar bdr-t">
        <div class="tools">
          <!-- 表情选择器弹出框 -->
          <n-popover
            placement="top-start"
            trigger="click"
            raw
            :show-arrow="false"
            :width="300"
            ref="emoticonRef"
            v-model:show="showEmoticon"
            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>

            <MeEditorEmoticon @on-select="onEmoticonEvent" />
          </n-popover>

          <!-- 工具栏其他功能按钮 -->
          <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>
        </div>
      </header>
  <!-- 引用消息块 -->
      <div v-if="quoteData" class="quote-card-wrapper">
        <div class="quote-card-content">
          <div class="quote-card-title">
            <span>{{ quoteData.title || ' ' }}</span>
            <n-icon size="18" class="quote-card-remove" :component="Close" @click="quoteData = null" />
          </div>
          <div v-if="quoteData.image" class="quote-card-image">
            <img :src="quoteData.image" alt="引用图片" />
          </div>
          <div v-if="quoteData.describe" class="quote-card-meta">
            {{ quoteData.describe }}
          </div>
        </div>
      </div>
      <!-- 编辑器主体区域 -->
      <main class="el-main height100">
        <editor-content :editor="editor" class="tiptap-editor" />
      </main>
    </section>
  </section>

  <!-- 隐藏的文件上传表单 -->
  <form enctype="multipart/form-data" style="display: none">
    <input type="file" ref="fileImageRef" accept="image/*" @change="onUploadFile" />
    <input type="file" ref="uploadFileRef" @change="onUploadFile" />
  </form>

  <!-- 条件渲染的功能组件 -->
  <MeEditorVote v-if="isShowEditorVote" @close="isShowEditorVote = false" @submit="onVoteEvent" />

  <MeEditorCode
    v-if="isShowEditorCode"
    @on-submit="onCodeEvent"
    @close="isShowEditorCode = false"
  />

  <MeEditorRecorder
    v-if="isShowEditorRecorder"
    @on-submit="onRecorderEvent"
    @close="isShowEditorRecorder = false"
  />
</template>

<style lang="less" scoped>
/* 编辑器容器样式 */
.editor {
  --tip-bg-color: rgb(241 241 241 / 90%);  /* 提示背景颜色 */
  height: 100%;
  
  /* 引用消息块样式 */
  .quote-card-wrapper {
    padding: 10px;
    background-color: #fff;
  }
  
  .quote-card-content {
    display: flex;
    background-color: #f6f6f6;
    flex-direction: column;
    padding: 8px;
    border-radius: 4px;
    cursor: pointer;
    user-select: none;

    .quote-card-title {
      height: 22px;
      line-height: 22px;
      font-size: 12px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      display: flex;
      justify-content: space-between;
      align-items: center;

      .quote-card-remove {
        cursor: pointer;
        &:hover {
          color: #333;
        }
      }
    }

    .quote-card-image {
      margin-top: 4px;
      img {
        max-width: 100px;
        max-height: 60px;
        border-radius: 4px;
      }
    }

    .quote-card-meta {
      margin-top: 4px;
      font-size: 12px;
      line-height: 20px;
      color: #999;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }

  .toolbar {
    height: 38px;
    display: flex;

    .tools {
      height: 100%;
      flex: auto;
      display: 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;  /* 悬停时显示提示文字 */
          }
        }
      }
    }
  }
}

/* 暗色模式样式调整 */
html[theme-mode='dark'] {
  .editor {
    --tip-bg-color: #48484d;
    
    .quote-card-wrapper {
      background-color: #1e1e1e;
    }
    
    .quote-card-content {
      background-color: #2c2c2c;
      
      .quote-card-title {
        color: #e0e0e0;
        
        .quote-card-remove {
          &:hover {
            color: #fff;
          }
        }
      }
      
      .quote-card-meta {
        color: #999;
      }
    }
  }
}
</style>

<style lang="less">
/* 全局编辑器样式 */
.tiptap-editor {
  height: 100%;
  overflow: auto;
  padding: 8px;
  outline: none;

  .image-upload-loading {
    position: relative;
    display: inline-block;

    &::after {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.5) url('data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="background:0 0"><circle cx="50" cy="50" r="32" stroke-width="8" stroke="%23fff" stroke-dasharray="50.26548245743669 50.26548245743669" fill="none" stroke-linecap="round"><animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" keyTimes="0;1" values="0 50 50;360 50 50"/></svg>');
      background-size: 30px 30px;
      background-position: center center;
      background-repeat: no-repeat;
      border-radius: 5px;
    }
  }
  
  /* 滚动条样式 */
  &::-webkit-scrollbar {
    width: 3px;
    height: 3px;
    background-color: unset;
  }

  &::-webkit-scrollbar-thumb {
    border-radius: 3px;
    background-color: transparent;
  }

  /* 悬停时显示滚动条 */
  &:hover {
    &::-webkit-scrollbar-thumb {
      background-color: var(--im-scrollbar-thumb);
    }
  }
  
  p {
    margin: 0;
  }
  
  /* 占位符样式 */
  .is-empty::before {
    content: attr(data-placeholder);
    float: left;
    color: #b8b3b3;
    pointer-events: none;
    height: 0;
    font-family: PingFang SC, Microsoft YaHei, 'Alibaba PuHuiTi 2.0 45' !important;
  }
  
  /* 编辑器中图片样式 */
  img {
    max-width: 100px;
    border-radius: 3px;
    background-color: #48484d;
    margin: 0px 2px;
  }
  
  /* 表情符号样式 */
  .ed-emoji {
    background-color: unset !important;
  }
  
  /* 提及样式 */
  .mention {
    color: #0366d6;
    background-color: rgba(3, 102, 214, 0.1);
    border-radius: 2px;
    padding: 0 2px;
  }
  
  /* 引用卡片样式 */
  .quote-card {
    margin-bottom: 8px;
  }

}

/* 提及列表样式 */
.ql-mention-list-container {
  width: 270px;
  max-height: 200px;
  background-color: #fff;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  border-radius: 4px;
  overflow-y: auto;
  z-index: 10000;
  
  .ed-member-item {
    display: flex;
    align-items: center;
    padding: 5px 10px;
    cursor: pointer;
    
    &:hover, &.selected {
      background-color: #f5f7fa;
    }
    
    .avator {
      width: 24px;
      height: 24px;
      border-radius: 50%;
      margin-right: 8px;
    }
    
    .nickname {
      font-size: 14px;
    }
  }
}

/* 暗色模式下的样式调整 */
html[theme-mode='dark'] {
  .tiptap-editor {
    .is-empty::before {
      color: #57575a;
    }
    
    .quote-card-content {
      background-color: var(--im-message-bg-color);
    }
  }
  
  .ql-mention-list-container {
    background-color: #1e1e1e;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.3);
    
    .ed-member-item {
      &:hover, &.selected {
        background-color: #2c2c2c;
      }
      
      .nickname {
        color: #e0e0e0;
      }
    }
  }
}
</style>