Files
2026-03-20 07:33:46 +00:00

160 lines
5.4 KiB
TypeScript

/**
* 通用工具函数集合:与 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<string, string> = {
'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
}