@livingdocs/editable.js 4.0.0 → 4.0.2
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/dist/editable.js +1 -1
- package/dist/editable.js.map +1 -1
- package/package.json +5 -6
- package/src/content.js +92 -20
- package/src/core.js +10 -21
- package/src/cursor.js +20 -14
- package/src/dispatcher.js +8 -8
- package/src/highlight-support.js +38 -57
- package/src/highlight-text.js +20 -12
- package/src/keyboard.js +6 -7
- package/src/{highlighting.js → monitored-highlighting.js} +20 -15
- package/src/plugins/highlighting/match-collection.js +2 -3
- package/src/plugins/highlighting/text-search.js +100 -0
- package/src/plugins/highlighting/whitespace-highlighting.js +2 -7
- package/src/range-container.js +6 -5
- package/src/range-save-restore.js +26 -33
- package/src/selection-watcher.js +16 -17
- package/src/selection.js +28 -18
- package/src/util/dom.js +218 -0
- package/src/plugins/highlighting/text-highlighting.js +0 -102
package/src/selection.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import 'rangy/lib/rangy-textrange'
|
|
3
1
|
import Cursor from './cursor'
|
|
4
2
|
import * as content from './content'
|
|
5
3
|
import * as parser from './parser'
|
|
@@ -7,6 +5,7 @@ import * as block from './block'
|
|
|
7
5
|
import config from './config'
|
|
8
6
|
import highlightSupport from './highlight-support'
|
|
9
7
|
import highlightText from './highlight-text'
|
|
8
|
+
import {toCharacterRange, rangeToHtml} from './util/dom'
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* The Selection module provides a cross-browser abstraction layer for range
|
|
@@ -37,7 +36,7 @@ export default class Selection extends Cursor {
|
|
|
37
36
|
|
|
38
37
|
// Get the html inside the selection.
|
|
39
38
|
html () {
|
|
40
|
-
return this.range
|
|
39
|
+
return rangeToHtml(this.range)
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
isAllSelected () {
|
|
@@ -53,7 +52,7 @@ export default class Selection extends Cursor {
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
getTextRange () {
|
|
56
|
-
return this.range
|
|
55
|
+
return toCharacterRange(this.range, this.host)
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
// Return a plain string of the current selection content.
|
|
@@ -72,10 +71,9 @@ export default class Selection extends Cursor {
|
|
|
72
71
|
getRects () {
|
|
73
72
|
// consider: translate into absolute positions
|
|
74
73
|
// just like Cursor#getCoordinates()
|
|
75
|
-
return this.range.
|
|
74
|
+
return this.range.getClientRects()
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
|
|
79
77
|
link (href, attrs = {}) {
|
|
80
78
|
if (href) attrs.href = href
|
|
81
79
|
const link = this.createElement(config.linkMarkup.name, config.linkMarkup.attribs)
|
|
@@ -121,6 +119,18 @@ export default class Selection extends Cursor {
|
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
121
|
|
|
122
|
+
highlightComment ({highlightId, textRange}) {
|
|
123
|
+
highlightSupport.highlightRange(
|
|
124
|
+
this.host,
|
|
125
|
+
highlightId,
|
|
126
|
+
textRange.text,
|
|
127
|
+
textRange.start,
|
|
128
|
+
textRange.end,
|
|
129
|
+
undefined, // dispatcher
|
|
130
|
+
'comment'
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
124
134
|
// Manually add a highlight
|
|
125
135
|
// Note: the current code does not work with newlines (LP)
|
|
126
136
|
highlight ({highlightId}) {
|
|
@@ -143,12 +153,12 @@ export default class Selection extends Cursor {
|
|
|
143
153
|
highlightText.highlightMatches(this.host, [match])
|
|
144
154
|
}
|
|
145
155
|
|
|
146
|
-
// toggle('<em>')
|
|
156
|
+
// e.g. toggle('<em>')
|
|
147
157
|
toggle (elem) {
|
|
148
158
|
if (block.isPlainTextBlock(this.host)) return
|
|
149
159
|
elem = this.adoptElement(elem)
|
|
150
160
|
this.range = content.toggleTag(this.host, this.range, elem)
|
|
151
|
-
this.
|
|
161
|
+
this.setVisibleSelection()
|
|
152
162
|
}
|
|
153
163
|
|
|
154
164
|
toggleCustom ({tagName, attributes, trim = false}) {
|
|
@@ -203,7 +213,7 @@ export default class Selection extends Cursor {
|
|
|
203
213
|
const cursor = this.deleteContent()
|
|
204
214
|
const textNode = cursor.createTextNode(character)
|
|
205
215
|
cursor.insertBefore(textNode)
|
|
206
|
-
cursor.
|
|
216
|
+
cursor.setVisibleSelection()
|
|
207
217
|
return cursor
|
|
208
218
|
}
|
|
209
219
|
|
|
@@ -213,13 +223,13 @@ export default class Selection extends Cursor {
|
|
|
213
223
|
// @param {String} E.g. '»'
|
|
214
224
|
surround (startCharacter, endCharacter) {
|
|
215
225
|
this.range = content.surround(this.host, this.range, startCharacter, endCharacter)
|
|
216
|
-
this.
|
|
226
|
+
this.setVisibleSelection()
|
|
217
227
|
}
|
|
218
228
|
|
|
219
229
|
removeSurround (startCharacter, endCharacter) {
|
|
220
230
|
this.range = content.deleteCharacter(this.host, this.range, startCharacter)
|
|
221
231
|
this.range = content.deleteCharacter(this.host, this.range, endCharacter)
|
|
222
|
-
this.
|
|
232
|
+
this.setVisibleSelection()
|
|
223
233
|
}
|
|
224
234
|
|
|
225
235
|
removeChars (chars = []) {
|
|
@@ -227,7 +237,7 @@ export default class Selection extends Cursor {
|
|
|
227
237
|
const char = chars[i]
|
|
228
238
|
this.range = content.deleteCharacter(this.host, this.range, char)
|
|
229
239
|
}
|
|
230
|
-
this.
|
|
240
|
+
this.setVisibleSelection()
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
toggleSurround (startCharacter, endCharacter) {
|
|
@@ -244,7 +254,7 @@ export default class Selection extends Cursor {
|
|
|
244
254
|
// remove all.
|
|
245
255
|
removeFormatting (selector) {
|
|
246
256
|
this.range = content.removeFormatting(this.host, this.range, selector)
|
|
247
|
-
this.
|
|
257
|
+
this.setVisibleSelection()
|
|
248
258
|
}
|
|
249
259
|
|
|
250
260
|
// Delete the farest ancestor that is an exact selection
|
|
@@ -284,7 +294,7 @@ export default class Selection extends Cursor {
|
|
|
284
294
|
// @param {DOM Node}
|
|
285
295
|
expandTo (elem) {
|
|
286
296
|
this.range = content.expandTo(this.host, this.range, elem)
|
|
287
|
-
this.
|
|
297
|
+
this.setVisibleSelection()
|
|
288
298
|
}
|
|
289
299
|
|
|
290
300
|
// Collapse the selection at the beginning of the selection
|
|
@@ -292,7 +302,7 @@ export default class Selection extends Cursor {
|
|
|
292
302
|
// @return Cursor instance
|
|
293
303
|
collapseAtBeginning (elem) {
|
|
294
304
|
this.range.collapse(true)
|
|
295
|
-
this.
|
|
305
|
+
this.setVisibleSelection()
|
|
296
306
|
return new Cursor(this.host, this.range)
|
|
297
307
|
}
|
|
298
308
|
|
|
@@ -301,7 +311,7 @@ export default class Selection extends Cursor {
|
|
|
301
311
|
// @return Cursor instance
|
|
302
312
|
collapseAtEnd (elem) {
|
|
303
313
|
this.range.collapse(false)
|
|
304
|
-
this.
|
|
314
|
+
this.setVisibleSelection()
|
|
305
315
|
return new Cursor(this.host, this.range)
|
|
306
316
|
}
|
|
307
317
|
|
|
@@ -312,7 +322,7 @@ export default class Selection extends Cursor {
|
|
|
312
322
|
if (block.isPlainTextBlock(this.host)) return
|
|
313
323
|
elem = this.adoptElement(elem)
|
|
314
324
|
this.range = content.forceWrap(this.host, this.range, elem)
|
|
315
|
-
this.
|
|
325
|
+
this.setVisibleSelection()
|
|
316
326
|
}
|
|
317
327
|
|
|
318
328
|
// Check if the selection is the same as the elements contents.
|
|
@@ -339,6 +349,6 @@ export default class Selection extends Cursor {
|
|
|
339
349
|
// selection.
|
|
340
350
|
deleteCharacter (character) {
|
|
341
351
|
this.range = content.deleteCharacter(this.host, this.range, character)
|
|
342
|
-
this.
|
|
352
|
+
this.setVisibleSelection()
|
|
343
353
|
}
|
|
344
354
|
}
|
package/src/util/dom.js
CHANGED
|
@@ -42,3 +42,221 @@ export const closest = (elem, selector) => {
|
|
|
42
42
|
if (!elem.closest) elem = elem.parentNode
|
|
43
43
|
if (elem && elem.closest) return elem.closest(selector)
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
export const createRange = (win = window) => {
|
|
47
|
+
return win.document.createRange()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const getSelection = (win = window) => {
|
|
51
|
+
return win.document.getSelection()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const getNodes = (range, nodeTypes, filterFunc, win = window) => {
|
|
55
|
+
const nodes = []
|
|
56
|
+
|
|
57
|
+
const nodeIterator = win.document.createNodeIterator(
|
|
58
|
+
range.commonAncestorContainer,
|
|
59
|
+
NodeFilter.SHOW_ALL,
|
|
60
|
+
{
|
|
61
|
+
acceptNode (node) {
|
|
62
|
+
if (
|
|
63
|
+
range.intersectsNode(node) &&
|
|
64
|
+
nodeTypes.includes(node.nodeType) &&
|
|
65
|
+
node !== range.commonAncestorContainer // Exclude the common ancestor container
|
|
66
|
+
) {
|
|
67
|
+
if (typeof filterFunc === 'function') {
|
|
68
|
+
return filterFunc(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
|
|
69
|
+
}
|
|
70
|
+
return NodeFilter.FILTER_ACCEPT
|
|
71
|
+
}
|
|
72
|
+
return NodeFilter.FILTER_SKIP
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
false
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
let currentNode
|
|
79
|
+
while ((currentNode = nodeIterator.nextNode())) {
|
|
80
|
+
nodes.push(currentNode)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return nodes
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// export const getNodes = (range, nodeTypes, filterFunc, win = window) => {
|
|
87
|
+
// const nodes = []
|
|
88
|
+
|
|
89
|
+
// const nodeIterator = win.document.createNodeIterator(
|
|
90
|
+
// range.startContainer,
|
|
91
|
+
// NodeFilter.SHOW_ALL,
|
|
92
|
+
// {
|
|
93
|
+
// acceptNode (node) {
|
|
94
|
+
// if (
|
|
95
|
+
// range.intersectsNode(node) &&
|
|
96
|
+
// nodeTypes.includes(node.nodeType)
|
|
97
|
+
// ) {
|
|
98
|
+
// if (typeof filterFunc === 'function') {
|
|
99
|
+
// return filterFunc(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
|
|
100
|
+
// }
|
|
101
|
+
// return NodeFilter.FILTER_ACCEPT
|
|
102
|
+
// }
|
|
103
|
+
// return NodeFilter.FILTER_SKIP
|
|
104
|
+
// }
|
|
105
|
+
// },
|
|
106
|
+
// false
|
|
107
|
+
// )
|
|
108
|
+
|
|
109
|
+
// let currentNode
|
|
110
|
+
// while ((currentNode = nodeIterator.nextNode())) {
|
|
111
|
+
// if (range.intersectsNode(currentNode)) {
|
|
112
|
+
// nodes.push(currentNode)
|
|
113
|
+
// }
|
|
114
|
+
// }
|
|
115
|
+
|
|
116
|
+
// return nodes
|
|
117
|
+
// }
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
export const normalizeBoundaries = (range) => {
|
|
121
|
+
if (range.startContainer.compareDocumentPosition(range.endContainer) === Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
122
|
+
range.setStartBefore(range.endContainer)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (range.endContainer.compareDocumentPosition(range.startContainer) === Node.DOCUMENT_POSITION_PRECEDING) {
|
|
126
|
+
range.setEndAfter(range.startContainer)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const containsRange = (containerRange, testRange) => {
|
|
131
|
+
return (
|
|
132
|
+
containerRange.compareBoundaryPoints(Range.START_TO_START, testRange) <= 0 &&
|
|
133
|
+
containerRange.compareBoundaryPoints(Range.END_TO_END, testRange) >= 0
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const containsNodeText = (range, node) => {
|
|
138
|
+
const nodeRange = document.createRange()
|
|
139
|
+
nodeRange.selectNodeContents(node)
|
|
140
|
+
const comparisonStart = range.compareBoundaryPoints(Range.START_TO_START, nodeRange)
|
|
141
|
+
const comparisonEnd = range.compareBoundaryPoints(Range.END_TO_END, nodeRange)
|
|
142
|
+
return comparisonStart <= 0 && comparisonEnd >= 0
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const nodeContainsRange = (node, range) => {
|
|
146
|
+
const nodeRange = document.createRange()
|
|
147
|
+
nodeRange.selectNodeContents(node)
|
|
148
|
+
const comparisonStart = range.compareBoundaryPoints(Range.START_TO_START, nodeRange)
|
|
149
|
+
const comparisonEnd = range.compareBoundaryPoints(Range.END_TO_END, nodeRange)
|
|
150
|
+
return comparisonStart >= 0 && comparisonEnd <= 0
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const isCharacterDataNode = (node) => {
|
|
154
|
+
return node && (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.COMMENT_NODE)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const splitDataNode = (node, offset) => {
|
|
158
|
+
return node.splitText(offset)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const splitBoundaries = (range) => {
|
|
162
|
+
const startContainer = range.startContainer
|
|
163
|
+
const startOffset = range.startOffset
|
|
164
|
+
const endContainer = range.endContainer
|
|
165
|
+
const endOffset = range.endOffset
|
|
166
|
+
|
|
167
|
+
if (isCharacterDataNode(endContainer) && endOffset > 0 && endOffset < endContainer.length) {
|
|
168
|
+
splitDataNode(endContainer, endOffset)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isCharacterDataNode(startContainer) && startOffset > 0 && startOffset < startContainer.length) {
|
|
172
|
+
const newStartContainer = splitDataNode(startContainer, startOffset)
|
|
173
|
+
range.setStart(newStartContainer, 0)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const toCharacterRange = (range, container) => {
|
|
178
|
+
const startRange = range.cloneRange()
|
|
179
|
+
startRange.setStart(container, 0)
|
|
180
|
+
startRange.setEnd(range.startContainer, range.startOffset)
|
|
181
|
+
|
|
182
|
+
const rangeText = range.toString()
|
|
183
|
+
const start = startRange.toString().length
|
|
184
|
+
const end = start + rangeText.length
|
|
185
|
+
|
|
186
|
+
return {start, end, text: rangeText}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const rangesAreEqual = (range1, range2) => {
|
|
190
|
+
return (
|
|
191
|
+
range1.startContainer === range2.startContainer &&
|
|
192
|
+
range1.startOffset === range2.startOffset &&
|
|
193
|
+
range1.endContainer === range2.endContainer &&
|
|
194
|
+
range1.endOffset === range2.endOffset
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const rangeToHtml = (range, win = window) => {
|
|
199
|
+
const div = win.document.createElement('div')
|
|
200
|
+
div.appendChild(range.cloneContents())
|
|
201
|
+
return div.innerHTML
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const getSelectionCoordinates = (selection) => {
|
|
205
|
+
const range = selection.getRangeAt(0) // Assuming you want coordinates of the first range
|
|
206
|
+
|
|
207
|
+
const rects = range.getClientRects()
|
|
208
|
+
const coordinates = []
|
|
209
|
+
|
|
210
|
+
for (let i = 0; i < rects.length; i++) {
|
|
211
|
+
const rect = rects[i]
|
|
212
|
+
coordinates.push({
|
|
213
|
+
top: rect.top,
|
|
214
|
+
left: rect.left,
|
|
215
|
+
bottom: rect.bottom,
|
|
216
|
+
right: rect.right,
|
|
217
|
+
width: rect.width,
|
|
218
|
+
height: rect.height
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return coordinates
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const createRangeFromCharacterRange = (element, actualStartIndex, actualEndIndex) => {
|
|
226
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false)
|
|
227
|
+
let currentIndex = 0
|
|
228
|
+
let startNode, endNode, startOffset, endOffset
|
|
229
|
+
|
|
230
|
+
while (walker.nextNode()) {
|
|
231
|
+
const textNode = walker.currentNode
|
|
232
|
+
const nodeLength = textNode.nodeValue.length
|
|
233
|
+
|
|
234
|
+
if (currentIndex + nodeLength <= actualStartIndex) {
|
|
235
|
+
currentIndex += nodeLength
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!startNode) {
|
|
240
|
+
startNode = textNode
|
|
241
|
+
startOffset = actualStartIndex - currentIndex
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (currentIndex + nodeLength >= actualEndIndex) {
|
|
245
|
+
endNode = textNode
|
|
246
|
+
endOffset = actualEndIndex - currentIndex
|
|
247
|
+
break
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
currentIndex += nodeLength
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (startNode && endNode) {
|
|
254
|
+
const range = createRange()
|
|
255
|
+
range.setStart(startNode, startOffset)
|
|
256
|
+
range.setEnd(endNode, endOffset)
|
|
257
|
+
return range
|
|
258
|
+
} else {
|
|
259
|
+
throw new Error('Invalid character offsets.')
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
export default class WordHighlighting {
|
|
2
|
-
|
|
3
|
-
// @param markerNode {DOM Node}
|
|
4
|
-
// @param matchMode {String} either 'word' or 'text'
|
|
5
|
-
constructor (markerNode, matchMode = 'word') {
|
|
6
|
-
this.marker = markerNode
|
|
7
|
-
this.matchMode = matchMode
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
isElement (obj) {
|
|
11
|
-
try {
|
|
12
|
-
if (!obj) return false
|
|
13
|
-
return obj instanceof obj.ownerDocument?.defaultView.HTMLElement
|
|
14
|
-
} catch (e) {
|
|
15
|
-
// Browsers not supporting W3 DOM2 don't have HTMLElement and
|
|
16
|
-
// an exception is thrown and we end up here. Testing some
|
|
17
|
-
// properties that all elements have (works on IE7)
|
|
18
|
-
return (typeof obj === 'object') &&
|
|
19
|
-
(obj.nodeType === 1) && (typeof obj.style === 'object') &&
|
|
20
|
-
(typeof obj.ownerDocument === 'object')
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
findMatches (text, highlights) {
|
|
25
|
-
if (!text || text === '' || !this.isElement(this.marker)) return
|
|
26
|
-
|
|
27
|
-
if (highlights && highlights.length > 0) {
|
|
28
|
-
return this.searchMatches(text, highlights)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
searchMatches (text, highlights) {
|
|
33
|
-
const createRegex = this.matchMode === 'word'
|
|
34
|
-
? createWordRegex
|
|
35
|
-
: createHighlightRegex
|
|
36
|
-
|
|
37
|
-
const regex = createRegex(highlights)
|
|
38
|
-
const matches = [...text.matchAll(regex)]
|
|
39
|
-
|
|
40
|
-
return matches.map((entry) => this.prepareMatch(entry))
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
prepareMatch (match) {
|
|
44
|
-
let startIndex
|
|
45
|
-
let matchedText
|
|
46
|
-
if (this.matchMode === 'word') {
|
|
47
|
-
startIndex = match.index + match[1].length
|
|
48
|
-
matchedText = match[2]
|
|
49
|
-
} else {
|
|
50
|
-
startIndex = match.index
|
|
51
|
-
matchedText = match[0]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
startIndex,
|
|
56
|
-
endIndex: startIndex + matchedText.length,
|
|
57
|
-
match: matchedText,
|
|
58
|
-
marker: this.marker
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function createHighlightRegex (words) {
|
|
65
|
-
const escapedWords = (words || []).map((word) => escapeRegEx(word))
|
|
66
|
-
|
|
67
|
-
const regex = `(${escapedWords.join('|')})`
|
|
68
|
-
return new RegExp(regex, 'g')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Regex to find whole words within a string
|
|
72
|
-
//
|
|
73
|
-
// Unicode character blocks for letters.
|
|
74
|
-
// See: http://jrgraphix.net/research/unicode_blocks.php
|
|
75
|
-
//
|
|
76
|
-
// \\u0041-\\u005A A-Z (Basic Latin)
|
|
77
|
-
// \\u0061-\\u007A a-z (Basic Latin)
|
|
78
|
-
// \\u0030-\\u0039 0-9 (Basic Latin)
|
|
79
|
-
// \\u00AA ª (Latin-1 Supplement)
|
|
80
|
-
// \\u00B5 µ (Latin-1 Supplement)
|
|
81
|
-
// \\u00BA º (Latin-1 Supplement)
|
|
82
|
-
// \\u00C0-\\u00D6 À-Ö (Latin-1 Supplement)
|
|
83
|
-
// \\u00D8-\\u00F6 Ø-ö (Latin-1 Supplement)
|
|
84
|
-
// \\u00F8-\\u00FF ø-ÿ (Latin-1 Supplement)
|
|
85
|
-
// \\u0100-\\u017F Ā-ſ (Latin Extended-A)
|
|
86
|
-
// \\u0180-\\u024F ƀ-ɏ (Latin Extended-B)
|
|
87
|
-
const letterChars = '\\u0041-\\u005A\\u0061-\\u007A\\u0030-\\u0039\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\u017F\\u0180-\\u024F'
|
|
88
|
-
|
|
89
|
-
function createWordRegex (words) {
|
|
90
|
-
const escapedWords = (words || []).map((word) => escapeRegEx(word))
|
|
91
|
-
|
|
92
|
-
// (notLetter|^)(words)(?=notLetter|$)
|
|
93
|
-
const regex = `([^${letterChars}]|^)` +
|
|
94
|
-
`(${escapedWords.join('|')})` +
|
|
95
|
-
`(?=[^${letterChars}]|$)`
|
|
96
|
-
|
|
97
|
-
return new RegExp(regex, 'g')
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function escapeRegEx (s) {
|
|
101
|
-
return String(s).replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1')
|
|
102
|
-
}
|