Commit 05264f0b by pangchong

refactor(editor): 优化适用性元数据标签定义及选择工具栏交互

- 在 xmlTags 配置中新增 EFFECT_METADATA_TAGS 表示适用性元数据标签
- WARNING_LIKE_TAGS 通过扩展 EFFECT_METADATA_TAGS 简化维护
- DocNodeRenderer 相关模块引入并使用 ORDERED_LIST_ITEM_TAGS 常量,改进有序列表判断逻辑
- SelectionToolbar 添加鼠标双击事件用于支持节点快速选中和工具栏弹出
- 避免鼠标抬起事件回调的重复触发,增加定时器管理
- CompareModal 使用 EFFECT_METADATA_TAGS 统一判断适用性元数据标签,增强代码一致性
parent 8362fbb7
......@@ -97,9 +97,12 @@ export const UNORDERED_LIST_ITEM_TAGS = ['UNLITEM', 'NUMLITEM']
// 所有列表条目标签
export const LIST_ITEM_TAGS = [...ORDERED_LIST_ITEM_TAGS, ...UNORDERED_LIST_ITEM_TAGS]
// 适用性元数据标签(表示适用范围的节点)
export const EFFECT_METADATA_TAGS = ['EFFECT', 'CONEFFECT', 'SBEFF', 'SBEFFC']
// 红色警示类节点标签(树节点着色、导出样式等)
// 注意:SBEFF / CONEFFECT 在 DTD 中是 EMPTY 节点,嵌在段落中作为适用性标记
export const WARNING_LIKE_TAGS = ['WARNING', 'EFFECT', 'CONEFFECT', 'SBEFF', 'SBEFFC']
export const WARNING_LIKE_TAGS = ['WARNING', ...EFFECT_METADATA_TAGS]
// 蓝色引用/注意类节点标签(树节点着色、导出样式等)
export const NOTE_AND_REF_TAGS = ['NOTE', 'REFBLOCK', 'REFINT', 'REFEXT', 'GRPHCREF', 'EIN']
......
export { LIST_ITEM_TAGS, HEADER_TAGS, ROMAN_LOOKUP } from '@/configs/xmlTags'
export { LIST_ITEM_TAGS, HEADER_TAGS, ROMAN_LOOKUP, ORDERED_LIST_ITEM_TAGS } from '@/configs/xmlTags'
import type { XmlNode } from '@/types/xmlNode'
import { useEditorStore, nodeSelectedRefs } from '@/store/editor'
import { LIST_ITEM_TAGS, HEADER_TAGS, ROMAN_LOOKUP } from '../constants'
import { LIST_ITEM_TAGS, HEADER_TAGS, ROMAN_LOOKUP, ORDERED_LIST_ITEM_TAGS } from '../constants'
import { ALERT_AND_EFF_TAGS, ALERT_BLOCK_TAGS, COMPOSITE_CONTAINER_MAP, INLINE_ELEMENTS } from '@/configs/xmlTags'
import { useI18n } from 'vue-i18n'
import { SHOW_SINGLE_CHILD_INDEX } from '@/configs'
......@@ -183,7 +183,7 @@ export function useDocNodeRenderer(props: { node: XmlNode; parent?: XmlNode | nu
// 列表标号与数字格式化
const getListBullet = (node: XmlNode): string => {
// 若配置为单子项不显示序号,且父级容器下同类型子项数量仅为 1,则不显示序号
const isOrderedList = ['L1ITEM', 'L2ITEM', 'L3ITEM', 'L4ITEM', 'L5ITEM', 'L6ITEM', 'L7ITEM', 'NUMLITEM'].includes(node.tagName)
const isOrderedList = ORDERED_LIST_ITEM_TAGS.includes(node.tagName) || node.tagName === 'NUMLITEM'
if (!SHOW_SINGLE_CHILD_INDEX && isOrderedList && props.parent) {
const siblings = props.parent.children.filter((c) => c.tagName === node.tagName)
if (siblings.length === 1) {
......
......@@ -303,6 +303,8 @@ export function useSelectionToolbar() {
}
}
let mouseupTimeoutId: any = null
// 监听鼠标按下事件:点击工具栏以外的地方时立即隐藏
const handleMousedown = (e: MouseEvent) => {
const toolbar = document.querySelector('.selection-floating-toolbar')
......@@ -314,7 +316,8 @@ export function useSelectionToolbar() {
// 监听鼠标抬起事件:在选取完成后再判定是否弹窗
const handleMouseup = () => {
setTimeout(() => {
if (mouseupTimeoutId) clearTimeout(mouseupTimeoutId)
mouseupTimeoutId = setTimeout(() => {
checkSelectionAndShow()
}, 10)
}
......@@ -420,10 +423,47 @@ export function useSelectionToolbar() {
visible.value = false
}
// 监听双击事件:双击任意 data-node-id 元素时,即使是 select-none 元素,也直接弹起工具栏并选中该节点
const handleDblclick = (e: MouseEvent) => {
if (mouseupTimeoutId) {
clearTimeout(mouseupTimeoutId)
mouseupTimeoutId = null
}
const target = e.target as HTMLElement
const wrapper = target.closest('[data-node-id]')
if (!wrapper) return
const nodeId = wrapper.getAttribute('data-node-id')
if (!nodeId) return
let realId = nodeId
if (nodeId.includes('-txt-')) {
realId = nodeId.split('-txt-')[0]
}
const item = store.nodeMap.get(realId)
if (!item) return
// 选中该节点以显示高亮框
store.setSelectedNodeId(nodeId)
// 计算定位并展示工具栏
top.value = e.clientY - 46
left.value = e.clientX
selectedText.value = ''
activeNodeId.value = nodeId
activeNode.value = item.node
parentNode.value = item.parent || null
visible.value = true
e.stopPropagation()
}
onMounted(() => {
document.addEventListener('mousedown', handleMousedown)
document.addEventListener('mouseup', handleMouseup)
document.addEventListener('keyup', handleKeyup)
document.addEventListener('dblclick', handleDblclick)
const viewport = document.querySelector('.flex-1.overflow-y-auto.min-h-0.leading-relaxed')
if (viewport) {
viewport.addEventListener('scroll', handleScroll)
......@@ -434,6 +474,7 @@ export function useSelectionToolbar() {
document.removeEventListener('mousedown', handleMousedown)
document.removeEventListener('mouseup', handleMouseup)
document.removeEventListener('keyup', handleKeyup)
document.removeEventListener('dblclick', handleDblclick)
const viewport = document.querySelector('.flex-1.overflow-y-auto.min-h-0.leading-relaxed')
if (viewport) {
viewport.removeEventListener('scroll', handleScroll)
......
......@@ -4,6 +4,7 @@ import * as Diff from 'diff'
import { useEditorStore } from '@/store/editor'
import { serializeTreeToXml, formatXmlText } from '@/utils/xmlParser'
import { openUploadModal } from '@/utils/render'
import { EFFECT_METADATA_TAGS } from '@/configs/xmlTags'
export function useCompareModal() {
const editorStore = useEditorStore()
......@@ -989,7 +990,7 @@ function buildOldDiffTree(oldNode: XmlNode | null, newNode: XmlNode | null): Dif
let diffStatus: 'added' | 'removed' | 'modified' | 'none' = 'none'
const attrsEqual = JSON.stringify(node.attributes) === JSON.stringify(other.attributes)
const isMetadataTag = ['EFFECT', 'CONEFFECT', 'SBEFF', 'SBEFFC'].includes(node.tagName)
const isMetadataTag = EFFECT_METADATA_TAGS.includes(node.tagName)
const oldText = isMetadataTag ? getMetadataRenderedText(node) : node.textContent || ''
const newText = isMetadataTag ? getMetadataRenderedText(other) : other.textContent || ''
const textEqual = oldText === newText
......@@ -1071,7 +1072,7 @@ function buildNewDiffTree(oldNode: XmlNode | null, newNode: XmlNode | null): Dif
let diffStatus: 'added' | 'removed' | 'modified' | 'none' = 'none'
const attrsEqual = JSON.stringify(node.attributes) === JSON.stringify(other.attributes)
const isMetadataTag = ['EFFECT', 'CONEFFECT', 'SBEFF', 'SBEFFC'].includes(node.tagName)
const isMetadataTag = EFFECT_METADATA_TAGS.includes(node.tagName)
const oldText = isMetadataTag ? getMetadataRenderedText(node) : node.textContent || ''
const newText = isMetadataTag ? getMetadataRenderedText(other) : other.textContent || ''
const textEqual = oldText === newText
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment