@ge-ge/highlight
Version:
178 lines (165 loc) • 5.48 kB
text/typescript
import { MarkElement } from '../types/index';
import { customMark, getNodeList } from './utils';
function addStyle(ele: Element, style: Record<string, string>) {
Object.keys(style).forEach((key) => {
// @ts-ignore
(<HTMLElement>ele).style[key] = style[key];
});
}
export default class Highlight {
private readonly style: Record<string, string> = {};
private readonly className: string = '';
private readonly tagName: string = 'mark';
private readonly tagNameUp: string = 'MARK';
/**
*
* @param options
*/
constructor(options?: {
className?: string;
style?: Record<string, string>;
tagName?: string;
}) {
if (options) {
this.className = options.className || this.className;
this.style = options.style || this.style;
this.tagName = options.tagName || this.tagName;
this.tagNameUp = this.tagName.toUpperCase();
}
}
/**
*
* @param {HTMLElement} ele
* @description 将<mark>this is text content</mark> 转为 文字,并插入到原来的位置
*/
public reset(ele: HTMLElement) {
if (ele.nodeName === this.tagNameUp && ele.parentNode) {
ele.outerHTML = ele.textContent || '';
}
}
/**
*
* @description 使传入的Node节点高亮
* @param {Node} ele 高亮的元素
*/
private highLight(ele: Node): Array<MarkElement> {
//ele 为text节点或者document
const node = ele;
const markNode: Array<MarkElement> = []; // 高亮后的元素
const needMark: Array<Node> = getNodeList(node, false); // 待高亮处理的元素,包含mark元素
const doMark = (node: Text) => {
const mark = this.highLightText(<Text>node);
if (mark) {
const obj: MarkElement = { mix: false, el: mark };
markNode.push(obj);
}
};
for (const node of needMark) {
if (node.nodeType === Node.TEXT_NODE && node.textContent) {
// 不对mark元素内的文字再次进行高亮
if (node.parentNode && node.parentNode.nodeName === this.tagNameUp) {
continue;
}
doMark(<Text>node);
} else if (node.nodeName === this.tagNameUp) {
const obj: MarkElement = { mix: true, el: <HTMLElement>node };
markNode.push(obj);
} else {
getNodeList(node, true).forEach((node) => {
doMark(<Text>node);
});
}
}
return markNode;
}
private addStyle(
ele: Node | DocumentFragment | HTMLElement,
): Array<MarkElement> {
console.log('addStyle');
const markNode: Array<MarkElement> = []; // 高亮后的元素
if (
ele.nodeType === Node.DOCUMENT_NODE ||
ele.nodeType === Node.DOCUMENT_FRAGMENT_NODE
) {
for (let i = 0; i < ele.childNodes.length; i++) {
const item = ele.childNodes[i];
markNode.push(...this.addStyle(item));
}
} else if (ele.nodeType === Node.ELEMENT_NODE) {
addStyle(<Element>ele, this.style);
markNode.push({ mix: false, el: <HTMLElement>ele });
} else if (ele.nodeType === Node.TEXT_NODE) {
const markEle = this.highLightText(<Text>ele);
if (markEle) {
addStyle(markEle, this.style);
markNode.push({ mix: false, el: <HTMLElement>ele });
}
}
return markNode;
}
/**
* @description 高亮选中的区间
*/
public mark(range: Range): Array<MarkElement> {
const marks: MarkElement[] = [];
customMark(range, (docFragment) => {
// 对docFragment进行处理
const markNode = this.highLight(docFragment);
marks.push(...markNode);
});
return marks;
}
public markStyle(range: Range): MarkElement[] {
const marks: MarkElement[] = [];
customMark(range, (docFragment) => {
const markNode = this.addStyle(docFragment);
marks.push(...markNode);
});
return marks;
}
public createRange(start: Node, end: Node) {
const range: Range = document.createRange();
range.setStartBefore(start);
range.setEndAfter(end);
return range;
}
/**
* @description 使TextNode高亮
* @param {Text} node 文本节点
* @param {number} start 开始的位置,默认从0开始
* @param {number} count 高亮文字的数量,默认为 从开始位置之后的全部文字
*/
private highLightText(
node: Text,
start: number = 0,
count?: number,
): HTMLElement | undefined {
if (node.nodeType === Node.TEXT_NODE && node.textContent) {
if (node.textContent.length === 0) return;
count = count || node.textContent.length - start;
const textContent = node.textContent.substr(start, count);
const mark = this.getMarkElement(textContent); // 生成highlight的元素
const range = document.createRange();
range.setStart(node, start);
range.setEnd(node, start + count); // TODO:把选取内的文字移入mark,而不是删除
range.deleteContents(); // 删除选中的文字,之后插入高亮元素
if (mark) range.insertNode(mark);
range.detach();
// console.log(mark)
return mark;
}
}
/**
*
* @description 返回高亮的mark Element
* @param {string} text
* @returns {HTMLElement}
*/
private getMarkElement(text: string): HTMLElement {
const content = text;
const mark: HTMLElement = document.createElement(this.tagName);
if (this.className) mark.classList.add(this.className); // 添加默认className
mark.textContent = content;
return mark;
}
}