lk-text-select-highlight 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +56 -0
- package/LICENSE +21 -0
- package/README-cn.md +128 -0
- package/README.md +78 -0
- package/dist/lk-text-select-highlight.cjs.js +182 -0
- package/dist/lk-text-select-highlight.cjs.js.map +1 -0
- package/dist/lk-text-select-highlight.esm.js +180 -0
- package/dist/lk-text-select-highlight.esm.js.map +1 -0
- package/dist/lk-text-select-highlight.min.js +2 -0
- package/dist/lk-text-select-highlight.min.js.map +1 -0
- package/dist/lk-text-select-highlight.umd.js +188 -0
- package/dist/lk-text-select-highlight.umd.js.map +1 -0
- package/index.js +186 -0
- package/package.json +51 -0
- package/src/index.js +179 -0
- package/types/index.d.ts +89 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).TextHighlighter=e()}(this,function(){"use strict";return class{constructor(t={}){this.options={className:t.className||"highlighted-text",color:t.color||"#ffff00",container:t.container||document.body,strictMode:t.strictMode||!1,caseSensitive:void 0===t.caseSensitive||t.caseSensitive,...t},this.highlights=[]}highlightSelection(){const t=window.getSelection();if(!t.toString().trim())return!1;const e=t.getRangeAt(0),n=this.options.container;if(!n.contains(e.startContainer)||!n.contains(e.endContainer))return!1;const i=e.commonAncestorContainer;if(!n.contains(i))return!1;if(this.options.strictMode&&this.hasTargetClassInSelection(e))return!1;try{const n=this.wrapRange(e);return this.highlights.push({element:n,text:t.toString()}),!0}catch(t){return console.warn("Could not highlight selection:",t),!1}}hasTargetClassInSelection(t){const e=t.startContainer.nodeType===Node.ELEMENT_NODE?t.startContainer:t.startContainer.parentElement,n=t.endContainer.nodeType===Node.ELEMENT_NODE?t.endContainer:t.endContainer.parentElement;if(e&&e.classList&&e.classList.contains(this.options.className))return!0;if(n&&n.classList&&n.classList.contains(this.options.className))return!0;const i=document.createElement("div");i.appendChild(t.cloneContents());return i.querySelectorAll(`.${this.options.className}`).length>0}wrapRange(t){const e=document.createElement("span");return e.className=this.options.className,e.style.backgroundColor=this.options.color,e.style.padding="0",e.style.margin="0",t.surroundContents(e),e}removeAllHighlights(){this.highlights.forEach(t=>{if(t.element.parentNode){const e=document.createTextNode(t.text);t.element.parentNode.replaceChild(e,t.element)}}),this.highlights=[]}getHighlights(){return[...this.highlights]}removeHighlight(t){const e=this.highlights.indexOf(t);if(-1!==e){if(t.element.parentNode){const e=document.createTextNode(t.text);t.element.parentNode.replaceChild(e,t.element)}return this.highlights.splice(e,1),!0}return!1}}});
|
|
2
|
+
//# sourceMappingURL=lk-text-select-highlight.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lk-text-select-highlight.min.js","sources":["../src/index.js"],"sourcesContent":["/**\r\n * lk-text-select-highlight - A lightweight text selection highlight library\r\n * @module lk-text-select-highlight\r\n */\r\n\r\nclass TextHighlighter {\r\n\tconstructor(options = {}) {\r\n\t\tthis.options = {\r\n\t\t\tclassName: options.className || \"highlighted-text\",\r\n\t\t\tcolor: options.color || \"#ffff00\",\r\n\t\t\tcontainer: options.container || document.body, // DOM容器,默认为整个body\r\n\t\t\tstrictMode: options.strictMode || false, // 严格模式,默认为false\r\n\t\t\tcaseSensitive:\r\n\t\t\t\toptions.caseSensitive !== undefined\r\n\t\t\t\t\t? options.caseSensitive\r\n\t\t\t\t\t: true,\r\n\t\t\t...options,\r\n\t\t};\r\n\r\n\t\tthis.highlights = [];\r\n\t}\r\n\r\n\t/**\r\n\t * Highlight selected text\r\n\t */\r\n\thighlightSelection() {\r\n\t\tconst selection = window.getSelection();\r\n\t\tif (!selection.toString().trim()) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tconst range = selection.getRangeAt(0);\r\n\r\n\t\t// 检查选择的范围是否在指定的容器内\r\n\t\tconst container = this.options.container;\r\n\t\tif (\r\n\t\t\t!container.contains(range.startContainer) ||\r\n\t\t\t!container.contains(range.endContainer)\r\n\t\t) {\r\n\t\t\treturn false; // 选择超出了指定容器范围\r\n\t\t}\r\n\r\n\t\t// 更全面地检查整个范围是否在容器内\r\n\t\tconst commonAncestor = range.commonAncestorContainer;\r\n\t\tif (!container.contains(commonAncestor)) {\r\n\t\t\treturn false; // 整个范围不在容器内\r\n\t\t}\r\n\r\n\t\t// 严格模式检查:如果启用严格模式,检查选择范围内是否包含目标className的元素\r\n\t\tif (this.options.strictMode) {\r\n\t\t\tif (this.hasTargetClassInSelection(range)) {\r\n\t\t\t\treturn false; // 选择范围内包含目标className的元素,不允许高亮\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst highlightedElement = this.wrapRange(range);\r\n\r\n\t\t\tthis.highlights.push({\r\n\t\t\t\telement: highlightedElement,\r\n\t\t\t\ttext: selection.toString(),\r\n\t\t\t});\r\n\r\n\t\t\treturn true;\r\n\t\t} catch (error) {\r\n\t\t\tconsole.warn(\"Could not highlight selection:\", error);\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Check if the selection range contains elements with the target class\r\n\t * @param {Range} range - The range to check\r\n\t */\r\n\thasTargetClassInSelection(range) {\r\n\t\t// Check if startContainer or endContainer themselves have the target class\r\n\t\tconst startElement =\r\n\t\t\trange.startContainer.nodeType === Node.ELEMENT_NODE\r\n\t\t\t\t? range.startContainer\r\n\t\t\t\t: range.startContainer.parentElement;\r\n\r\n\t\tconst endElement =\r\n\t\t\trange.endContainer.nodeType === Node.ELEMENT_NODE\r\n\t\t\t\t? range.endContainer\r\n\t\t\t\t: range.endContainer.parentElement;\r\n\r\n\t\tif (\r\n\t\t\tstartElement &&\r\n\t\t\tstartElement.classList &&\r\n\t\t\tstartElement.classList.contains(this.options.className)\r\n\t\t) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tif (\r\n\t\t\tendElement &&\r\n\t\t\tendElement.classList &&\r\n\t\t\tendElement.classList.contains(this.options.className)\r\n\t\t) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t// Create a temporary div to work with the range content\r\n\t\tconst tempDiv = document.createElement(\"div\");\r\n\t\ttempDiv.appendChild(range.cloneContents());\r\n\r\n\t\t// Check if any child elements have the target class\r\n\t\tconst elementsWithTargetClass = tempDiv.querySelectorAll(\r\n\t\t\t`.${this.options.className}`,\r\n\t\t);\r\n\t\treturn elementsWithTargetClass.length > 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Wrap a range with highlight element\r\n\t * @param {Range} range - The range to wrap\r\n\t */\r\n\twrapRange(range) {\r\n\t\tconst highlightEl = document.createElement(\"span\");\r\n\t\thighlightEl.className = this.options.className;\r\n\t\thighlightEl.style.backgroundColor = this.options.color;\r\n\t\thighlightEl.style.padding = \"0\";\r\n\t\thighlightEl.style.margin = \"0\";\r\n\r\n\t\t// Clone the range and insert the highlight element\r\n\t\trange.surroundContents(highlightEl);\r\n\r\n\t\treturn highlightEl;\r\n\t}\r\n\r\n\t/**\r\n\t * Remove all highlights\r\n\t */\r\n\tremoveAllHighlights() {\r\n\t\tthis.highlights.forEach((highlight) => {\r\n\t\t\tif (highlight.element.parentNode) {\r\n\t\t\t\tconst textNode = document.createTextNode(highlight.text);\r\n\t\t\t\thighlight.element.parentNode.replaceChild(\r\n\t\t\t\t\ttextNode,\r\n\t\t\t\t\thighlight.element,\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tthis.highlights = [];\r\n\t}\r\n\r\n\t/**\r\n\t * Get all highlighted elements\r\n\t */\r\n\tgetHighlights() {\r\n\t\treturn [...this.highlights];\r\n\t}\r\n\r\n\t/**\r\n\t * Remove a specific highlight\r\n\t * @param {Object} highlight - The highlight object to remove\r\n\t */\r\n\tremoveHighlight(highlight) {\r\n\t\tconst index = this.highlights.indexOf(highlight);\r\n\t\tif (index !== -1) {\r\n\t\t\t// Restore the original text\r\n\t\t\tif (highlight.element.parentNode) {\r\n\t\t\t\tconst textNode = document.createTextNode(highlight.text);\r\n\t\t\t\thighlight.element.parentNode.replaceChild(\r\n\t\t\t\t\ttextNode,\r\n\t\t\t\t\thighlight.element,\r\n\t\t\t\t);\r\n\t\t\t}\r\n\r\n\t\t\t// Remove from the highlights array\r\n\t\t\tthis.highlights.splice(index, 1);\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n}\r\n\r\nexport default TextHighlighter;\r\n"],"names":["constructor","options","this","className","color","container","document","body","strictMode","caseSensitive","undefined","highlights","highlightSelection","selection","window","getSelection","toString","trim","range","getRangeAt","contains","startContainer","endContainer","commonAncestor","commonAncestorContainer","hasTargetClassInSelection","highlightedElement","wrapRange","push","element","text","error","console","warn","startElement","nodeType","Node","ELEMENT_NODE","parentElement","endElement","classList","tempDiv","createElement","appendChild","cloneContents","querySelectorAll","length","highlightEl","style","backgroundColor","padding","margin","surroundContents","removeAllHighlights","forEach","highlight","parentNode","textNode","createTextNode","replaceChild","getHighlights","removeHighlight","index","indexOf","splice"],"mappings":"sPAKA,MACC,WAAAA,CAAYC,EAAU,IACrBC,KAAKD,QAAU,CACdE,UAAWF,EAAQE,WAAa,mBAChCC,MAAOH,EAAQG,OAAS,UACxBC,UAAWJ,EAAQI,WAAaC,SAASC,KACzCC,WAAYP,EAAQO,aAAc,EAClCC,mBAC2BC,IAA1BT,EAAQQ,eACLR,EAAQQ,iBAETR,GAGJC,KAAKS,WAAa,EACnB,CAKA,kBAAAC,GACC,MAAMC,EAAYC,OAAOC,eACzB,IAAKF,EAAUG,WAAWC,OACzB,OAAO,EAGR,MAAMC,EAAQL,EAAUM,WAAW,GAG7Bd,EAAYH,KAAKD,QAAQI,UAC/B,IACEA,EAAUe,SAASF,EAAMG,kBACzBhB,EAAUe,SAASF,EAAMI,cAE1B,OAAO,EAIR,MAAMC,EAAiBL,EAAMM,wBAC7B,IAAKnB,EAAUe,SAASG,GACvB,OAAO,EAIR,GAAIrB,KAAKD,QAAQO,YACZN,KAAKuB,0BAA0BP,GAClC,OAAO,EAIT,IACC,MAAMQ,EAAqBxB,KAAKyB,UAAUT,GAO1C,OALAhB,KAAKS,WAAWiB,KAAK,CACpBC,QAASH,EACTI,KAAMjB,EAAUG,cAGV,CACR,CAAE,MAAOe,GAER,OADAC,QAAQC,KAAK,iCAAkCF,IACxC,CACR,CACD,CAMA,yBAAAN,CAA0BP,GAEzB,MAAMgB,EACLhB,EAAMG,eAAec,WAAaC,KAAKC,aACpCnB,EAAMG,eACNH,EAAMG,eAAeiB,cAEnBC,EACLrB,EAAMI,aAAaa,WAAaC,KAAKC,aAClCnB,EAAMI,aACNJ,EAAMI,aAAagB,cAEvB,GACCJ,GACAA,EAAaM,WACbN,EAAaM,UAAUpB,SAASlB,KAAKD,QAAQE,WAE7C,OAAO,EAGR,GACCoC,GACAA,EAAWC,WACXD,EAAWC,UAAUpB,SAASlB,KAAKD,QAAQE,WAE3C,OAAO,EAIR,MAAMsC,EAAUnC,SAASoC,cAAc,OACvCD,EAAQE,YAAYzB,EAAM0B,iBAM1B,OAHgCH,EAAQI,iBACvC,IAAI3C,KAAKD,QAAQE,aAEa2C,OAAS,CACzC,CAMA,SAAAnB,CAAUT,GACT,MAAM6B,EAAczC,SAASoC,cAAc,QAS3C,OARAK,EAAY5C,UAAYD,KAAKD,QAAQE,UACrC4C,EAAYC,MAAMC,gBAAkB/C,KAAKD,QAAQG,MACjD2C,EAAYC,MAAME,QAAU,IAC5BH,EAAYC,MAAMG,OAAS,IAG3BjC,EAAMkC,iBAAiBL,GAEhBA,CACR,CAKA,mBAAAM,GACCnD,KAAKS,WAAW2C,QAASC,IACxB,GAAIA,EAAU1B,QAAQ2B,WAAY,CACjC,MAAMC,EAAWnD,SAASoD,eAAeH,EAAUzB,MACnDyB,EAAU1B,QAAQ2B,WAAWG,aAC5BF,EACAF,EAAU1B,QAEZ,IAGD3B,KAAKS,WAAa,EACnB,CAKA,aAAAiD,GACC,MAAO,IAAI1D,KAAKS,WACjB,CAMA,eAAAkD,CAAgBN,GACf,MAAMO,EAAQ5D,KAAKS,WAAWoD,QAAQR,GACtC,IAAc,IAAVO,EAAc,CAEjB,GAAIP,EAAU1B,QAAQ2B,WAAY,CACjC,MAAMC,EAAWnD,SAASoD,eAAeH,EAAUzB,MACnDyB,EAAU1B,QAAQ2B,WAAWG,aAC5BF,EACAF,EAAU1B,QAEZ,CAIA,OADA3B,KAAKS,WAAWqD,OAAOF,EAAO,IACvB,CACR,CACA,OAAO,CACR"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.TextHighlighter = factory());
|
|
5
|
+
})(this, (function () { 'use strict';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* lk-text-select-highlight - A lightweight text selection highlight library
|
|
9
|
+
* @module lk-text-select-highlight
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
class TextHighlighter {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.options = {
|
|
15
|
+
className: options.className || "highlighted-text",
|
|
16
|
+
color: options.color || "#ffff00",
|
|
17
|
+
container: options.container || document.body, // DOM容器,默认为整个body
|
|
18
|
+
strictMode: options.strictMode || false, // 严格模式,默认为false
|
|
19
|
+
caseSensitive:
|
|
20
|
+
options.caseSensitive !== undefined
|
|
21
|
+
? options.caseSensitive
|
|
22
|
+
: true,
|
|
23
|
+
...options,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
this.highlights = [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Highlight selected text
|
|
31
|
+
*/
|
|
32
|
+
highlightSelection() {
|
|
33
|
+
const selection = window.getSelection();
|
|
34
|
+
if (!selection.toString().trim()) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const range = selection.getRangeAt(0);
|
|
39
|
+
|
|
40
|
+
// 检查选择的范围是否在指定的容器内
|
|
41
|
+
const container = this.options.container;
|
|
42
|
+
if (
|
|
43
|
+
!container.contains(range.startContainer) ||
|
|
44
|
+
!container.contains(range.endContainer)
|
|
45
|
+
) {
|
|
46
|
+
return false; // 选择超出了指定容器范围
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 更全面地检查整个范围是否在容器内
|
|
50
|
+
const commonAncestor = range.commonAncestorContainer;
|
|
51
|
+
if (!container.contains(commonAncestor)) {
|
|
52
|
+
return false; // 整个范围不在容器内
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 严格模式检查:如果启用严格模式,检查选择范围内是否包含目标className的元素
|
|
56
|
+
if (this.options.strictMode) {
|
|
57
|
+
if (this.hasTargetClassInSelection(range)) {
|
|
58
|
+
return false; // 选择范围内包含目标className的元素,不允许高亮
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const highlightedElement = this.wrapRange(range);
|
|
64
|
+
|
|
65
|
+
this.highlights.push({
|
|
66
|
+
element: highlightedElement,
|
|
67
|
+
text: selection.toString(),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.warn("Could not highlight selection:", error);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if the selection range contains elements with the target class
|
|
79
|
+
* @param {Range} range - The range to check
|
|
80
|
+
*/
|
|
81
|
+
hasTargetClassInSelection(range) {
|
|
82
|
+
// Check if startContainer or endContainer themselves have the target class
|
|
83
|
+
const startElement =
|
|
84
|
+
range.startContainer.nodeType === Node.ELEMENT_NODE
|
|
85
|
+
? range.startContainer
|
|
86
|
+
: range.startContainer.parentElement;
|
|
87
|
+
|
|
88
|
+
const endElement =
|
|
89
|
+
range.endContainer.nodeType === Node.ELEMENT_NODE
|
|
90
|
+
? range.endContainer
|
|
91
|
+
: range.endContainer.parentElement;
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
startElement &&
|
|
95
|
+
startElement.classList &&
|
|
96
|
+
startElement.classList.contains(this.options.className)
|
|
97
|
+
) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
endElement &&
|
|
103
|
+
endElement.classList &&
|
|
104
|
+
endElement.classList.contains(this.options.className)
|
|
105
|
+
) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Create a temporary div to work with the range content
|
|
110
|
+
const tempDiv = document.createElement("div");
|
|
111
|
+
tempDiv.appendChild(range.cloneContents());
|
|
112
|
+
|
|
113
|
+
// Check if any child elements have the target class
|
|
114
|
+
const elementsWithTargetClass = tempDiv.querySelectorAll(
|
|
115
|
+
`.${this.options.className}`,
|
|
116
|
+
);
|
|
117
|
+
return elementsWithTargetClass.length > 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Wrap a range with highlight element
|
|
122
|
+
* @param {Range} range - The range to wrap
|
|
123
|
+
*/
|
|
124
|
+
wrapRange(range) {
|
|
125
|
+
const highlightEl = document.createElement("span");
|
|
126
|
+
highlightEl.className = this.options.className;
|
|
127
|
+
highlightEl.style.backgroundColor = this.options.color;
|
|
128
|
+
highlightEl.style.padding = "0";
|
|
129
|
+
highlightEl.style.margin = "0";
|
|
130
|
+
|
|
131
|
+
// Clone the range and insert the highlight element
|
|
132
|
+
range.surroundContents(highlightEl);
|
|
133
|
+
|
|
134
|
+
return highlightEl;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Remove all highlights
|
|
139
|
+
*/
|
|
140
|
+
removeAllHighlights() {
|
|
141
|
+
this.highlights.forEach((highlight) => {
|
|
142
|
+
if (highlight.element.parentNode) {
|
|
143
|
+
const textNode = document.createTextNode(highlight.text);
|
|
144
|
+
highlight.element.parentNode.replaceChild(
|
|
145
|
+
textNode,
|
|
146
|
+
highlight.element,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
this.highlights = [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get all highlighted elements
|
|
156
|
+
*/
|
|
157
|
+
getHighlights() {
|
|
158
|
+
return [...this.highlights];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Remove a specific highlight
|
|
163
|
+
* @param {Object} highlight - The highlight object to remove
|
|
164
|
+
*/
|
|
165
|
+
removeHighlight(highlight) {
|
|
166
|
+
const index = this.highlights.indexOf(highlight);
|
|
167
|
+
if (index !== -1) {
|
|
168
|
+
// Restore the original text
|
|
169
|
+
if (highlight.element.parentNode) {
|
|
170
|
+
const textNode = document.createTextNode(highlight.text);
|
|
171
|
+
highlight.element.parentNode.replaceChild(
|
|
172
|
+
textNode,
|
|
173
|
+
highlight.element,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Remove from the highlights array
|
|
178
|
+
this.highlights.splice(index, 1);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return TextHighlighter;
|
|
186
|
+
|
|
187
|
+
}));
|
|
188
|
+
//# sourceMappingURL=lk-text-select-highlight.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lk-text-select-highlight.umd.js","sources":["../src/index.js"],"sourcesContent":["/**\r\n * lk-text-select-highlight - A lightweight text selection highlight library\r\n * @module lk-text-select-highlight\r\n */\r\n\r\nclass TextHighlighter {\r\n\tconstructor(options = {}) {\r\n\t\tthis.options = {\r\n\t\t\tclassName: options.className || \"highlighted-text\",\r\n\t\t\tcolor: options.color || \"#ffff00\",\r\n\t\t\tcontainer: options.container || document.body, // DOM容器,默认为整个body\r\n\t\t\tstrictMode: options.strictMode || false, // 严格模式,默认为false\r\n\t\t\tcaseSensitive:\r\n\t\t\t\toptions.caseSensitive !== undefined\r\n\t\t\t\t\t? options.caseSensitive\r\n\t\t\t\t\t: true,\r\n\t\t\t...options,\r\n\t\t};\r\n\r\n\t\tthis.highlights = [];\r\n\t}\r\n\r\n\t/**\r\n\t * Highlight selected text\r\n\t */\r\n\thighlightSelection() {\r\n\t\tconst selection = window.getSelection();\r\n\t\tif (!selection.toString().trim()) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tconst range = selection.getRangeAt(0);\r\n\r\n\t\t// 检查选择的范围是否在指定的容器内\r\n\t\tconst container = this.options.container;\r\n\t\tif (\r\n\t\t\t!container.contains(range.startContainer) ||\r\n\t\t\t!container.contains(range.endContainer)\r\n\t\t) {\r\n\t\t\treturn false; // 选择超出了指定容器范围\r\n\t\t}\r\n\r\n\t\t// 更全面地检查整个范围是否在容器内\r\n\t\tconst commonAncestor = range.commonAncestorContainer;\r\n\t\tif (!container.contains(commonAncestor)) {\r\n\t\t\treturn false; // 整个范围不在容器内\r\n\t\t}\r\n\r\n\t\t// 严格模式检查:如果启用严格模式,检查选择范围内是否包含目标className的元素\r\n\t\tif (this.options.strictMode) {\r\n\t\t\tif (this.hasTargetClassInSelection(range)) {\r\n\t\t\t\treturn false; // 选择范围内包含目标className的元素,不允许高亮\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst highlightedElement = this.wrapRange(range);\r\n\r\n\t\t\tthis.highlights.push({\r\n\t\t\t\telement: highlightedElement,\r\n\t\t\t\ttext: selection.toString(),\r\n\t\t\t});\r\n\r\n\t\t\treturn true;\r\n\t\t} catch (error) {\r\n\t\t\tconsole.warn(\"Could not highlight selection:\", error);\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Check if the selection range contains elements with the target class\r\n\t * @param {Range} range - The range to check\r\n\t */\r\n\thasTargetClassInSelection(range) {\r\n\t\t// Check if startContainer or endContainer themselves have the target class\r\n\t\tconst startElement =\r\n\t\t\trange.startContainer.nodeType === Node.ELEMENT_NODE\r\n\t\t\t\t? range.startContainer\r\n\t\t\t\t: range.startContainer.parentElement;\r\n\r\n\t\tconst endElement =\r\n\t\t\trange.endContainer.nodeType === Node.ELEMENT_NODE\r\n\t\t\t\t? range.endContainer\r\n\t\t\t\t: range.endContainer.parentElement;\r\n\r\n\t\tif (\r\n\t\t\tstartElement &&\r\n\t\t\tstartElement.classList &&\r\n\t\t\tstartElement.classList.contains(this.options.className)\r\n\t\t) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\tif (\r\n\t\t\tendElement &&\r\n\t\t\tendElement.classList &&\r\n\t\t\tendElement.classList.contains(this.options.className)\r\n\t\t) {\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t// Create a temporary div to work with the range content\r\n\t\tconst tempDiv = document.createElement(\"div\");\r\n\t\ttempDiv.appendChild(range.cloneContents());\r\n\r\n\t\t// Check if any child elements have the target class\r\n\t\tconst elementsWithTargetClass = tempDiv.querySelectorAll(\r\n\t\t\t`.${this.options.className}`,\r\n\t\t);\r\n\t\treturn elementsWithTargetClass.length > 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Wrap a range with highlight element\r\n\t * @param {Range} range - The range to wrap\r\n\t */\r\n\twrapRange(range) {\r\n\t\tconst highlightEl = document.createElement(\"span\");\r\n\t\thighlightEl.className = this.options.className;\r\n\t\thighlightEl.style.backgroundColor = this.options.color;\r\n\t\thighlightEl.style.padding = \"0\";\r\n\t\thighlightEl.style.margin = \"0\";\r\n\r\n\t\t// Clone the range and insert the highlight element\r\n\t\trange.surroundContents(highlightEl);\r\n\r\n\t\treturn highlightEl;\r\n\t}\r\n\r\n\t/**\r\n\t * Remove all highlights\r\n\t */\r\n\tremoveAllHighlights() {\r\n\t\tthis.highlights.forEach((highlight) => {\r\n\t\t\tif (highlight.element.parentNode) {\r\n\t\t\t\tconst textNode = document.createTextNode(highlight.text);\r\n\t\t\t\thighlight.element.parentNode.replaceChild(\r\n\t\t\t\t\ttextNode,\r\n\t\t\t\t\thighlight.element,\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tthis.highlights = [];\r\n\t}\r\n\r\n\t/**\r\n\t * Get all highlighted elements\r\n\t */\r\n\tgetHighlights() {\r\n\t\treturn [...this.highlights];\r\n\t}\r\n\r\n\t/**\r\n\t * Remove a specific highlight\r\n\t * @param {Object} highlight - The highlight object to remove\r\n\t */\r\n\tremoveHighlight(highlight) {\r\n\t\tconst index = this.highlights.indexOf(highlight);\r\n\t\tif (index !== -1) {\r\n\t\t\t// Restore the original text\r\n\t\t\tif (highlight.element.parentNode) {\r\n\t\t\t\tconst textNode = document.createTextNode(highlight.text);\r\n\t\t\t\thighlight.element.parentNode.replaceChild(\r\n\t\t\t\t\ttextNode,\r\n\t\t\t\t\thighlight.element,\r\n\t\t\t\t);\r\n\t\t\t}\r\n\r\n\t\t\t// Remove from the highlights array\r\n\t\t\tthis.highlights.splice(index, 1);\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n}\r\n\r\nexport default TextHighlighter;\r\n"],"names":[],"mappings":"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"}
|
package/index.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lk-text-select-highlight - A lightweight text selection highlight library
|
|
3
|
+
* @module lk-text-select-highlight
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class TextHighlighter {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.options = {
|
|
9
|
+
className: options.className || "highlighted-text",
|
|
10
|
+
color: options.color || "#ffff00",
|
|
11
|
+
container: options.container || document.body, // DOM容器,默认为整个body
|
|
12
|
+
strictMode: options.strictMode || false, // 严格模式,默认为false
|
|
13
|
+
caseSensitive:
|
|
14
|
+
options.caseSensitive !== undefined
|
|
15
|
+
? options.caseSensitive
|
|
16
|
+
: true,
|
|
17
|
+
...options,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
this.highlights = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Highlight selected text
|
|
25
|
+
*/
|
|
26
|
+
highlightSelection() {
|
|
27
|
+
const selection = window.getSelection();
|
|
28
|
+
if (!selection.toString().trim()) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const range = selection.getRangeAt(0);
|
|
33
|
+
|
|
34
|
+
// 检查选择的范围是否在指定的容器内
|
|
35
|
+
const container = this.options.container;
|
|
36
|
+
if (
|
|
37
|
+
!container.contains(range.startContainer) ||
|
|
38
|
+
!container.contains(range.endContainer)
|
|
39
|
+
) {
|
|
40
|
+
return false; // 选择超出了指定容器范围
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 更全面地检查整个范围是否在容器内
|
|
44
|
+
const commonAncestor = range.commonAncestorContainer;
|
|
45
|
+
if (!container.contains(commonAncestor)) {
|
|
46
|
+
return false; // 整个范围不在容器内
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 严格模式检查:如果启用严格模式,检查选择范围内是否包含目标className的元素
|
|
50
|
+
if (this.options.strictMode) {
|
|
51
|
+
if (this.hasTargetClassInSelection(range)) {
|
|
52
|
+
return false; // 选择范围内包含目标className的元素,不允许高亮
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const highlightedElement = this.wrapRange(range);
|
|
58
|
+
|
|
59
|
+
this.highlights.push({
|
|
60
|
+
element: highlightedElement,
|
|
61
|
+
text: selection.toString(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.warn("Could not highlight selection:", error);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if the selection range contains elements with the target class
|
|
73
|
+
* @param {Range} range - The range to check
|
|
74
|
+
*/
|
|
75
|
+
hasTargetClassInSelection(range) {
|
|
76
|
+
// Check if startContainer or endContainer themselves have the target class
|
|
77
|
+
const startElement =
|
|
78
|
+
range.startContainer.nodeType === Node.ELEMENT_NODE
|
|
79
|
+
? range.startContainer
|
|
80
|
+
: range.startContainer.parentElement;
|
|
81
|
+
|
|
82
|
+
const endElement =
|
|
83
|
+
range.endContainer.nodeType === Node.ELEMENT_NODE
|
|
84
|
+
? range.endContainer
|
|
85
|
+
: range.endContainer.parentElement;
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
startElement &&
|
|
89
|
+
startElement.classList &&
|
|
90
|
+
startElement.classList.contains(this.options.className)
|
|
91
|
+
) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
endElement &&
|
|
97
|
+
endElement.classList &&
|
|
98
|
+
endElement.classList.contains(this.options.className)
|
|
99
|
+
) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create a temporary div to work with the range content
|
|
104
|
+
const tempDiv = document.createElement("div");
|
|
105
|
+
tempDiv.appendChild(range.cloneContents());
|
|
106
|
+
|
|
107
|
+
// Check if any child elements have the target class
|
|
108
|
+
const elementsWithTargetClass = tempDiv.querySelectorAll(
|
|
109
|
+
`.${this.options.className}`,
|
|
110
|
+
);
|
|
111
|
+
return elementsWithTargetClass.length > 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Wrap a range with highlight element
|
|
116
|
+
* @param {Range} range - The range to wrap
|
|
117
|
+
*/
|
|
118
|
+
wrapRange(range) {
|
|
119
|
+
const highlightEl = document.createElement("span");
|
|
120
|
+
highlightEl.className = this.options.className;
|
|
121
|
+
highlightEl.style.backgroundColor = this.options.color;
|
|
122
|
+
highlightEl.style.padding = "0";
|
|
123
|
+
highlightEl.style.margin = "0";
|
|
124
|
+
|
|
125
|
+
// Clone the range and insert the highlight element
|
|
126
|
+
range.surroundContents(highlightEl);
|
|
127
|
+
|
|
128
|
+
return highlightEl;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove all highlights
|
|
133
|
+
*/
|
|
134
|
+
removeAllHighlights() {
|
|
135
|
+
this.highlights.forEach((highlight) => {
|
|
136
|
+
if (highlight.element.parentNode) {
|
|
137
|
+
const textNode = document.createTextNode(highlight.text);
|
|
138
|
+
highlight.element.parentNode.replaceChild(
|
|
139
|
+
textNode,
|
|
140
|
+
highlight.element,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.highlights = [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get all highlighted elements
|
|
150
|
+
*/
|
|
151
|
+
getHighlights() {
|
|
152
|
+
return [...this.highlights];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Remove a specific highlight
|
|
157
|
+
* @param {Object} highlight - The highlight object to remove
|
|
158
|
+
*/
|
|
159
|
+
removeHighlight(highlight) {
|
|
160
|
+
const index = this.highlights.indexOf(highlight);
|
|
161
|
+
if (index !== -1) {
|
|
162
|
+
// Restore the original text
|
|
163
|
+
if (highlight.element.parentNode) {
|
|
164
|
+
const textNode = document.createTextNode(highlight.text);
|
|
165
|
+
highlight.element.parentNode.replaceChild(
|
|
166
|
+
textNode,
|
|
167
|
+
highlight.element,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Remove from the highlights array
|
|
172
|
+
this.highlights.splice(index, 1);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Export for both Node.js and browser environments
|
|
180
|
+
if (typeof module !== "undefined" && module.exports) {
|
|
181
|
+
module.exports = TextHighlighter;
|
|
182
|
+
} else if (typeof window !== "undefined") {
|
|
183
|
+
window.TextHighlighter = TextHighlighter;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default TextHighlighter;
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lk-text-select-highlight",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight JavaScript library for highlighting selected text on web pages",
|
|
5
|
+
"main": "dist/lk-text-select-highlight.cjs.js",
|
|
6
|
+
"module": "dist/lk-text-select-highlight.esm.js",
|
|
7
|
+
"browser": "dist/lk-text-select-highlight.umd.js",
|
|
8
|
+
"umd": "dist/lk-text-select-highlight.umd.js",
|
|
9
|
+
"unpkg": "dist/lk-text-select-highlight.min.js",
|
|
10
|
+
"jsdelivr": "dist/lk-text-select-highlight.min.js",
|
|
11
|
+
"types": "types/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/lk-text-select-highlight.esm.js",
|
|
15
|
+
"require": "./dist/lk-text-select-highlight.cjs.js",
|
|
16
|
+
"umd": "./dist/lk-text-select-highlight.umd.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "rollup -c",
|
|
21
|
+
"dev": "rollup -c -w",
|
|
22
|
+
"test": "jest --config=jest.config.cjs",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"text",
|
|
27
|
+
"highlight",
|
|
28
|
+
"selection",
|
|
29
|
+
"ui",
|
|
30
|
+
"javascript"
|
|
31
|
+
],
|
|
32
|
+
"author": "刘凯",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/yourusername/lk-text-select-highlight.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/yourusername/lk-text-select-highlight/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/yourusername/lk-text-select-highlight#readme",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
44
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
45
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
46
|
+
"jest": "^29.7.0",
|
|
47
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
48
|
+
"rollup": "^4.9.0"
|
|
49
|
+
},
|
|
50
|
+
"type": "module"
|
|
51
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lk-text-select-highlight - A lightweight text selection highlight library
|
|
3
|
+
* @module lk-text-select-highlight
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class TextHighlighter {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.options = {
|
|
9
|
+
className: options.className || "highlighted-text",
|
|
10
|
+
color: options.color || "#ffff00",
|
|
11
|
+
container: options.container || document.body, // DOM容器,默认为整个body
|
|
12
|
+
strictMode: options.strictMode || false, // 严格模式,默认为false
|
|
13
|
+
caseSensitive:
|
|
14
|
+
options.caseSensitive !== undefined
|
|
15
|
+
? options.caseSensitive
|
|
16
|
+
: true,
|
|
17
|
+
...options,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
this.highlights = [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Highlight selected text
|
|
25
|
+
*/
|
|
26
|
+
highlightSelection() {
|
|
27
|
+
const selection = window.getSelection();
|
|
28
|
+
if (!selection.toString().trim()) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const range = selection.getRangeAt(0);
|
|
33
|
+
|
|
34
|
+
// 检查选择的范围是否在指定的容器内
|
|
35
|
+
const container = this.options.container;
|
|
36
|
+
if (
|
|
37
|
+
!container.contains(range.startContainer) ||
|
|
38
|
+
!container.contains(range.endContainer)
|
|
39
|
+
) {
|
|
40
|
+
return false; // 选择超出了指定容器范围
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 更全面地检查整个范围是否在容器内
|
|
44
|
+
const commonAncestor = range.commonAncestorContainer;
|
|
45
|
+
if (!container.contains(commonAncestor)) {
|
|
46
|
+
return false; // 整个范围不在容器内
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 严格模式检查:如果启用严格模式,检查选择范围内是否包含目标className的元素
|
|
50
|
+
if (this.options.strictMode) {
|
|
51
|
+
if (this.hasTargetClassInSelection(range)) {
|
|
52
|
+
return false; // 选择范围内包含目标className的元素,不允许高亮
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const highlightedElement = this.wrapRange(range);
|
|
58
|
+
|
|
59
|
+
this.highlights.push({
|
|
60
|
+
element: highlightedElement,
|
|
61
|
+
text: selection.toString(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.warn("Could not highlight selection:", error);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if the selection range contains elements with the target class
|
|
73
|
+
* @param {Range} range - The range to check
|
|
74
|
+
*/
|
|
75
|
+
hasTargetClassInSelection(range) {
|
|
76
|
+
// Check if startContainer or endContainer themselves have the target class
|
|
77
|
+
const startElement =
|
|
78
|
+
range.startContainer.nodeType === Node.ELEMENT_NODE
|
|
79
|
+
? range.startContainer
|
|
80
|
+
: range.startContainer.parentElement;
|
|
81
|
+
|
|
82
|
+
const endElement =
|
|
83
|
+
range.endContainer.nodeType === Node.ELEMENT_NODE
|
|
84
|
+
? range.endContainer
|
|
85
|
+
: range.endContainer.parentElement;
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
startElement &&
|
|
89
|
+
startElement.classList &&
|
|
90
|
+
startElement.classList.contains(this.options.className)
|
|
91
|
+
) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
endElement &&
|
|
97
|
+
endElement.classList &&
|
|
98
|
+
endElement.classList.contains(this.options.className)
|
|
99
|
+
) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create a temporary div to work with the range content
|
|
104
|
+
const tempDiv = document.createElement("div");
|
|
105
|
+
tempDiv.appendChild(range.cloneContents());
|
|
106
|
+
|
|
107
|
+
// Check if any child elements have the target class
|
|
108
|
+
const elementsWithTargetClass = tempDiv.querySelectorAll(
|
|
109
|
+
`.${this.options.className}`,
|
|
110
|
+
);
|
|
111
|
+
return elementsWithTargetClass.length > 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Wrap a range with highlight element
|
|
116
|
+
* @param {Range} range - The range to wrap
|
|
117
|
+
*/
|
|
118
|
+
wrapRange(range) {
|
|
119
|
+
const highlightEl = document.createElement("span");
|
|
120
|
+
highlightEl.className = this.options.className;
|
|
121
|
+
highlightEl.style.backgroundColor = this.options.color;
|
|
122
|
+
highlightEl.style.padding = "0";
|
|
123
|
+
highlightEl.style.margin = "0";
|
|
124
|
+
|
|
125
|
+
// Clone the range and insert the highlight element
|
|
126
|
+
range.surroundContents(highlightEl);
|
|
127
|
+
|
|
128
|
+
return highlightEl;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove all highlights
|
|
133
|
+
*/
|
|
134
|
+
removeAllHighlights() {
|
|
135
|
+
this.highlights.forEach((highlight) => {
|
|
136
|
+
if (highlight.element.parentNode) {
|
|
137
|
+
const textNode = document.createTextNode(highlight.text);
|
|
138
|
+
highlight.element.parentNode.replaceChild(
|
|
139
|
+
textNode,
|
|
140
|
+
highlight.element,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.highlights = [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get all highlighted elements
|
|
150
|
+
*/
|
|
151
|
+
getHighlights() {
|
|
152
|
+
return [...this.highlights];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Remove a specific highlight
|
|
157
|
+
* @param {Object} highlight - The highlight object to remove
|
|
158
|
+
*/
|
|
159
|
+
removeHighlight(highlight) {
|
|
160
|
+
const index = this.highlights.indexOf(highlight);
|
|
161
|
+
if (index !== -1) {
|
|
162
|
+
// Restore the original text
|
|
163
|
+
if (highlight.element.parentNode) {
|
|
164
|
+
const textNode = document.createTextNode(highlight.text);
|
|
165
|
+
highlight.element.parentNode.replaceChild(
|
|
166
|
+
textNode,
|
|
167
|
+
highlight.element,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Remove from the highlights array
|
|
172
|
+
this.highlights.splice(index, 1);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default TextHighlighter;
|