/** * 通用工具函数集合:与 HTMLEditor 实例 (this) 无关的逻辑 */ import { type Position } from '../types'; import { type HTMLEditor } from './editor'; export function getElementType(element: HTMLElement): string { const tagName = element.tagName.toLowerCase(); const typeMap: Record = { 'h1': '标题1', 'h2': '标题2', 'h3': '标题3', 'h4': '标题4', 'h5': '标题5', 'h6': '标题6', 'p': '段落', 'div': '区块', 'span': '文本', 'a': '链接', 'img': '图片', 'ul': '无序列表', 'ol': '有序列表', 'li': '列表项' }; return typeMap[tagName] || tagName.toUpperCase(); } export function isDivWithText(element: HTMLElement): boolean { const elementWithText = !!element.textContent?.trim() && element.children.length === 0; return element.tagName.toLowerCase() === 'div' && elementWithText } export function isTextElement(element: HTMLElement): boolean { const textTags = ['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'strong', 'em']; return textTags.includes(element.tagName.toLowerCase()) || isDivWithText(element); } export function isDivWithImage(element: HTMLElement): boolean { const computedStyle = window.getComputedStyle(element); const { backgroundImage, background } = computedStyle const elementWithBgImage = !!backgroundImage && backgroundImage !== 'none' && backgroundImage.includes('url(') const elementBgWithUrl = !!background && background.includes('url(') const divWithbg = element.tagName.toLowerCase() === 'div' && element.children.length === 0 && (elementWithBgImage || elementBgWithUrl); return divWithbg } export function isBlockElement(element: HTMLElement): boolean { const blockTags = ['div', 'section', 'article', 'header', 'footer', 'main', 'body', 'ol', 'ul', 'li', 'button', 'i']; return blockTags.includes(element.tagName.toLowerCase()) && !isDivWithImage(element) && !isDivWithText(element); } export const isInlineElement = (element: HTMLElement): boolean => { const display = getComputedStyle(element).display; return display.startsWith('inline') && display !== 'inline-block'; } export const isTableElement = (element: HTMLElement): boolean => { const tableTags = ['tr', 'td', 'th', 'tbody', 'thead', 'tfoot', 'caption']; const tagName = element.tagName.toLowerCase().toLowerCase(); return tableTags.includes(tagName); } export function isImageElement(element: HTMLElement): boolean { return isDivWithImage(element) || element.tagName.toLowerCase() === 'img'; } export function createElement(type: string, content: string = ''): HTMLElement { const element = document.createElement(type); if (content) { element.textContent = content; } else { element.textContent = type === 'div' ? '新区块' : '新文本'; } // 添加基础样式 element.style.padding = '10px'; element.style.margin = '5px'; element.style.backgroundColor = '#f8f9fa'; element.style.border = '1px dashed #dee2e6'; element.style.borderRadius = '4px'; return element; } export const elementWatcher = (editor: HTMLEditor) => { let ele: HTMLElement | null = null; let running = false; let frameId: number | null = null; let lastRect: Position | null = null; const update = (element: HTMLElement, callback?: (postition: Position) => void) => { if (!running || !element.isConnected) return; const postition = editor.getBoundPostion(element); const hasChanged = !lastRect || postition.left !== lastRect.left || postition.top !== lastRect.top || postition.width !== lastRect.width || postition.height !== lastRect.height || postition.right !== lastRect.right || postition.bottom !== lastRect.bottom; if (hasChanged) { lastRect = postition; callback?.(postition); } // 下一帧继续 frameId = requestAnimationFrame(() => update(element, callback)); } return { start: (element: HTMLElement, callback?: (position: Position) => void) => { if (!running) { ele = element; running = true; update(element, callback); } }, stop: (element: HTMLElement) => { if (ele !== element) return; running = false; if (frameId) cancelAnimationFrame(frameId); frameId = null; }, }; } export const cleanDom = (doc: HTMLElement): string => { // 克隆一份 const ele = doc.cloneNode(true) as HTMLElement // 移除所有hover以及selected样式 ele.querySelectorAll('*').forEach(node => { const tagName = node.tagName.toLowerCase() const classList = node.classList const nodeStyle = (node as HTMLElement).style if (classList.contains('hover-highlight')) { node.classList.remove('hover-highlight') node.removeAttribute('data-element-type') } if (classList.contains('selected-element')) { node.classList.remove('selected-element') node.removeAttribute('data-element-type') } if (tagName === 'style' && node.hasAttribute('data-styled-id')) { node.parentNode?.removeChild(node) } if ( node.getAttribute('id') === 'html-editor-helper-box' || classList.contains('moveable-control-box') ) { node.parentNode?.removeChild(node) } if (nodeStyle && nodeStyle.cursor === 'move') { nodeStyle.cursor = 'auto' } if (node.hasAttribute('contenteditable')) { node.removeAttribute('contenteditable') } if (nodeStyle.userSelect === 'none') { nodeStyle.userSelect = 'auto' } }) return ele.outerHTML }