@livingdocs/editable.js 4.0.1 → 5.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/package.json CHANGED
@@ -1,12 +1,10 @@
1
1
  {
2
2
  "name": "@livingdocs/editable.js",
3
3
  "description": "Friendly contenteditable API",
4
- "version": "4.0.1",
4
+ "version": "5.0.0",
5
5
  "dependencies": {
6
6
  "@babel/runtime": "^7.16.7",
7
- "bowser": "2.11.0",
8
- "lodash-es": "^4.17.21",
9
- "rangy": "^1.3.0"
7
+ "lodash-es": "^4.17.21"
10
8
  },
11
9
  "devDependencies": {
12
10
  "@babel/cli": "^7.16.8",
@@ -82,11 +80,11 @@
82
80
  "isCi": true,
83
81
  "name": "Drone",
84
82
  "service": "drone",
85
- "commit": "246306a1bfebb93f5cbaf511621a773cb36058a3",
86
- "build": "1296",
83
+ "commit": "6be0ace70ff4180dfe612c8026ecfb9a88a92651",
84
+ "build": "1333",
87
85
  "branch": "master",
88
86
  "isPr": false,
89
87
  "slug": "livingdocsIO/editable.js",
90
- "date": "2023-10-11T21:05:31.171Z"
88
+ "date": "2023-10-20T10:13:40.826Z"
91
89
  }
92
90
  }
package/src/content.js CHANGED
@@ -1,9 +1,8 @@
1
- import rangy from 'rangy'
2
1
  import * as nodeType from './node-type'
3
2
  import * as rangeSaveRestore from './range-save-restore'
4
3
  import * as parser from './parser'
5
4
  import * as string from './util/string'
6
- import {createElement} from './util/dom'
5
+ import {createElement, createRange, getNodes, normalizeBoundaries, splitBoundaries, containsNodeText} from './util/dom'
7
6
  import config from './config'
8
7
 
9
8
  function restoreRange (host, range, func) {
@@ -129,11 +128,10 @@ export function adoptElement (node, doc) {
129
128
  : node
130
129
  }
131
130
 
132
- // This is a slight variation of the cloneContents method of a rangyRange.
133
131
  // It will return a fragment with the cloned contents of the range
134
132
  // without the commonAncestorElement.
135
133
  //
136
- // @param {rangyRange}
134
+ // @param {Range}
137
135
  // @return {DocumentFragment}
138
136
  export function cloneRangeContents (range) {
139
137
  const rangeFragment = range.cloneContents()
@@ -241,13 +239,13 @@ export function areSameAttributes (attrs1, attrs2) {
241
239
 
242
240
  // Get all tags that start or end inside the range
243
241
  export function getInnerTags (range, filterFunc) {
244
- return range.getNodes([nodeType.elementNode], filterFunc)
242
+ return getNodes(range, [nodeType.elementNode], filterFunc)
245
243
  }
246
244
 
247
245
  // Get all tags whose text is completely within the current selection.
248
246
  export function getContainedTags (range, filterFunc) {
249
- return range.getNodes([nodeType.elementNode], filterFunc)
250
- .filter(elem => range.containsNodeText(elem))
247
+ return getNodes(range, [nodeType.elementNode], filterFunc)
248
+ .filter(elem => containsNodeText(range, elem))
251
249
  }
252
250
 
253
251
  // Transform an array of elements into an array
@@ -265,21 +263,26 @@ export function isAffectedBy (host, range, tagName) {
265
263
 
266
264
  // select a whole element
267
265
  export function selectNodeContents (element) {
268
- const range = rangy.createRange()
266
+ const range = createRange()
269
267
  range.selectNodeContents(element)
270
268
  return range
271
269
  }
272
270
 
271
+ function intersectsRange (range1, range2) {
272
+ return range1.compareBoundaryPoints(Range.END_TO_START, range2) === -1 &&
273
+ range2.compareBoundaryPoints(Range.END_TO_START, range1) === -1
274
+ }
275
+
273
276
  // Check if the range selects all of the elements contents,
274
277
  // not less or more.
275
278
  //
276
279
  // @param visible: Only compare visible text. That way it does not
277
280
  // matter if the user selects an additional whitespace or not.
278
281
  export function isExactSelection (range, elem, visible) {
279
- const elemRange = rangy.createRange()
282
+ const elemRange = createRange()
280
283
  elemRange.selectNodeContents(elem)
281
284
 
282
- if (!range.intersectsRange(elemRange)) return false
285
+ if (!intersectsRange(range, elemRange)) return false
283
286
 
284
287
  let rangeText = range.toString()
285
288
  let elemText = (elem.jquery ? elem[0] : elem).textContent
@@ -309,7 +312,7 @@ export function toggleTag (host, range, elem) {
309
312
  }
310
313
 
311
314
  export function isWrappable (range) {
312
- return range.canSurroundContents()
315
+ return canSurroundContents(range)
313
316
  }
314
317
 
315
318
  export function forceWrap (host, range, elem) {
@@ -384,11 +387,10 @@ export function nukeElem (host, range, node) {
384
387
  export function insertCharacter (range, character, atStart) {
385
388
  const insertEl = document.createTextNode(character)
386
389
  const boundaryRange = range.cloneRange()
387
-
388
390
  boundaryRange.collapse(atStart)
389
391
  boundaryRange.insertNode(insertEl)
390
392
  range[atStart ? 'setStartBefore' : 'setEndAfter'](insertEl)
391
- range.normalizeBoundaries()
393
+ normalizeBoundaries(range)
392
394
  }
393
395
 
394
396
  // Surround the range with characters like start and end quotes.
@@ -406,19 +408,18 @@ export function surround (host, range, startCharacter, endCharacter) {
406
408
  export function deleteCharacter (host, range, character) {
407
409
  if (!containsString(range, character)) return range
408
410
 
409
- range.splitBoundaries()
411
+ // check for selection.rangeCount > 0 ?
412
+ if (window.getSelection().rangeCount > 0) splitBoundaries(range)
410
413
  const restoredRange = restoreRange(host, range, () => {
411
- const charRegexp = string.regexp(character)
412
-
413
- range.getNodes([nodeType.textNode], (node) => {
414
- return node.nodeValue.search(charRegexp) >= 0
414
+ getNodes(range, [nodeType.textNode], (node) => {
415
+ return node.nodeValue.indexOf(character) >= 0
415
416
  })
416
417
  .forEach((node) => {
417
- node.nodeValue = node.nodeValue.replace(charRegexp, '')
418
+ node.nodeValue = node.nodeValue.replaceAll(character, '')
418
419
  })
419
420
  })
420
421
 
421
- restoredRange.normalizeBoundaries()
422
+ normalizeBoundaries(restoredRange)
422
423
  return restoredRange
423
424
  }
424
425
 
@@ -433,3 +434,74 @@ export function nukeTag (host, range, tagName) {
433
434
  if (elem.nodeName.toUpperCase() === tagName.toUpperCase()) unwrap(elem)
434
435
  })
435
436
  }
437
+
438
+ function createNodeIterator (root, filter) {
439
+ let currentNode = root
440
+ let previousNode = null
441
+
442
+ function nextNode () {
443
+ if (!currentNode) {
444
+ return null
445
+ }
446
+
447
+ if (currentNode.firstChild && previousNode !== currentNode.firstChild) {
448
+ previousNode = currentNode
449
+ currentNode = currentNode.firstChild
450
+ } else if (currentNode.nextSibling) {
451
+ previousNode = currentNode
452
+ currentNode = currentNode.nextSibling
453
+ } else {
454
+ let parent = currentNode.parentNode
455
+ while (parent && parent !== root) {
456
+ if (parent.nextSibling) {
457
+ previousNode = currentNode = parent.nextSibling
458
+ break
459
+ }
460
+ parent = parent.parentNode
461
+ }
462
+ if (!parent || parent === root) {
463
+ previousNode = currentNode = null
464
+ }
465
+ }
466
+
467
+ return currentNode
468
+ }
469
+
470
+ return {
471
+ next: nextNode
472
+ }
473
+ }
474
+
475
+ function isNodeFullyContained (node, range) {
476
+ const nodeRange = document.createRange()
477
+ nodeRange.selectNodeContents(node)
478
+ return range.compareBoundaryPoints(Range.START_TO_START, nodeRange) <= 0 &&
479
+ range.compareBoundaryPoints(Range.END_TO_END, nodeRange) >= 0
480
+ }
481
+
482
+ function canSurroundContents (range) {
483
+ if (!range || !range.startContainer || !range.endContainer) {
484
+ return false
485
+ }
486
+
487
+ if (range.startContainer === range.endContainer) return true
488
+
489
+ // Create a custom node iterator for the common ancestor container
490
+ const iterator = createNodeIterator(range.commonAncestorContainer, function (node) {
491
+ return range.isPointInRange(node, 0)
492
+ })
493
+
494
+ let currentNode
495
+ let boundariesInvalid = false
496
+
497
+ while ((currentNode = iterator.next())) {
498
+ if (currentNode.nodeType === Node.ELEMENT_NODE) {
499
+ if (!isNodeFullyContained(currentNode, range)) {
500
+ boundariesInvalid = true
501
+ break
502
+ }
503
+ }
504
+ }
505
+
506
+ return !boundariesInvalid
507
+ }
package/src/core.js CHANGED
@@ -1,4 +1,3 @@
1
- import rangy from 'rangy'
2
1
  import config from './config'
3
2
  import error from './util/error'
4
3
  import * as parser from './parser'
@@ -8,12 +7,11 @@ import * as clipboard from './clipboard'
8
7
  import Dispatcher from './dispatcher'
9
8
  import Cursor from './cursor'
10
9
  import highlightSupport from './highlight-support'
11
- import Highlighting from './highlighting'
10
+ import MonitoredHighlighting from './monitored-highlighting'
12
11
  import createDefaultEvents from './create-default-events'
13
- import browser from 'bowser'
14
12
  import {textNodesUnder, getTextNodeAndRelativeOffset} from './util/element'
15
13
  import {binaryCursorSearch} from './util/binary_search'
16
- import {domArray} from './util/dom'
14
+ import {domArray, createRange, nodeContainsRange} from './util/dom'
17
15
 
18
16
  /**
19
17
  * The Core module provides the Editable class that defines the Editable.JS
@@ -49,10 +47,6 @@ export class Editable {
49
47
  this.win = this.config.window
50
48
  this.editableSelector = `.${config.editableClass}`
51
49
 
52
- if (!rangy.initialized) {
53
- rangy.init()
54
- }
55
-
56
50
  this.dispatcher = new Dispatcher(this)
57
51
  if (this.config.defaultBehavior === true) {
58
52
  this.dispatcher.on(createDefaultEvents(this))
@@ -217,7 +211,7 @@ export class Editable {
217
211
  const host = Cursor.findHost(element, this.editableSelector)
218
212
  if (!host) return undefined
219
213
 
220
- const range = rangy.createRange()
214
+ const range = createRange()
221
215
 
222
216
  if (position === 'beginning' || position === 'end') {
223
217
  range.selectNodeContents(element)
@@ -240,7 +234,7 @@ export class Editable {
240
234
  createCursorAtCharacterOffset ({element, offset}) {
241
235
  const textNodes = textNodesUnder(element)
242
236
  const {node, relativeOffset} = getTextNodeAndRelativeOffset({textNodes, absOffset: offset})
243
- const newRange = rangy.createRange()
237
+ const newRange = createRange()
244
238
  newRange.setStart(node, relativeOffset)
245
239
  newRange.collapse(true)
246
240
 
@@ -320,21 +314,15 @@ export class Editable {
320
314
  * @returns A Cursor or Selection object or undefined.
321
315
  */
322
316
  getSelection (editableHost) {
323
- let selection = this.dispatcher.selectionWatcher.getFreshSelection()
324
- if (!(editableHost && selection)) return selection
317
+ const selection = this.dispatcher.selectionWatcher.getFreshSelection()
318
+ if (!editableHost || !selection) return selection
325
319
 
326
320
  const range = selection.range
321
+
327
322
  // Check if the selection is inside the editableHost
328
- // The try...catch is required if the editableHost was removed from the DOM.
329
- try {
330
- if (range.compareNode(editableHost) !== range.NODE_BEFORE_AND_AFTER) {
331
- selection = undefined
332
- }
333
- } catch (e) {
334
- selection = undefined
323
+ if (editableHost?.isConnected && nodeContainsRange(editableHost, range)) {
324
+ return selection
335
325
  }
336
-
337
- return selection
338
326
  }
339
327
 
340
328
  /**
@@ -343,7 +331,7 @@ export class Editable {
343
331
  * @chainable
344
332
  */
345
333
  setupHighlighting (hightlightingConfig) {
346
- this.highlighting = new Highlighting(this, hightlightingConfig)
334
+ this.highlighting = new MonitoredHighlighting(this, hightlightingConfig)
347
335
 
348
336
  return this
349
337
  }
@@ -524,7 +512,6 @@ export class Editable {
524
512
  // Expose modules and editable
525
513
  Editable.parser = parser
526
514
  Editable.content = content
527
- Editable.browser = browser
528
515
 
529
516
  // Set up callback functions for several events.
530
517
  ;['focus', 'blur', 'flow', 'selection', 'cursor', 'newline',
package/src/cursor.js CHANGED
@@ -1,4 +1,3 @@
1
- import rangy from 'rangy'
2
1
  import * as viewport from './util/viewport'
3
2
 
4
3
  import * as content from './content'
@@ -8,7 +7,7 @@ import {elementNode, documentFragmentNode} from './node-type'
8
7
  import error from './util/error'
9
8
  import * as rangeSaveRestore from './range-save-restore'
10
9
  // import printRange from './util/print_range'
11
- import {closest} from './util/dom'
10
+ import {closest, getSelection, rangesAreEqual} from './util/dom'
12
11
 
13
12
  /**
14
13
  * The Cursor module provides a cross-browser abstraction layer for cursor.
@@ -29,9 +28,9 @@ export default class Cursor {
29
28
  * @class Cursor
30
29
  * @constructor
31
30
  */
32
- constructor (editableHost, rangyRange) {
31
+ constructor (editableHost, range) {
33
32
  this.setHost(editableHost)
34
- this.range = rangyRange
33
+ this.range = range
35
34
  this.isCursor = true
36
35
  }
37
36
 
@@ -100,7 +99,7 @@ export default class Cursor {
100
99
  hostRange.selectNodeContents(this.host)
101
100
  hostRange.collapse(false)
102
101
  const hostCoords = getRangeBoundingClientRect(hostRange, this.win)
103
- const cursorCoords = getRangeBoundingClientRect(this.range.nativeRange, this.win)
102
+ const cursorCoords = getRangeBoundingClientRect(this.range, this.win)
104
103
  return isCloseTo(hostCoords.bottom, cursorCoords.bottom)
105
104
  }
106
105
 
@@ -109,7 +108,7 @@ export default class Cursor {
109
108
  hostRange.selectNodeContents(this.host)
110
109
  hostRange.collapse(true)
111
110
  const hostCoords = getRangeBoundingClientRect(hostRange, this.win)
112
- const cursorCoords = getRangeBoundingClientRect(this.range.nativeRange, this.win)
111
+ const cursorCoords = getRangeBoundingClientRect(this.range, this.win)
113
112
  return isCloseTo(hostCoords.top, cursorCoords.top)
114
113
  }
115
114
 
@@ -139,6 +138,7 @@ export default class Cursor {
139
138
  this.range.insertNode(element)
140
139
  this.range.setStartAfter(preceedingElement)
141
140
  this.range.setEndAfter(preceedingElement)
141
+ this.host.normalize() // mend text nodes
142
142
  }
143
143
 
144
144
  // Insert content after the cursor
@@ -149,7 +149,12 @@ export default class Cursor {
149
149
  if (parser.isDocumentFragmentWithoutChildren(element)) return
150
150
 
151
151
  element = this.adoptElement(element)
152
- this.range.insertNode(element)
152
+
153
+ const after = this.range.cloneRange()
154
+ after.setStart(after.endContainer, after.endOffset)
155
+ after.collapse(true)
156
+ after.insertNode(element)
157
+ this.host.normalize() // mend text nodes
153
158
  }
154
159
 
155
160
  // Alias for #setVisibleSelection()
@@ -162,7 +167,10 @@ export default class Cursor {
162
167
  const {x, y} = viewport.getScrollPosition(this.win)
163
168
  this.win.scrollTo(x, y)
164
169
  }
165
- rangy.getSelection(this.win).setSingleRange(this.range)
170
+
171
+ const selection = getSelection(this.win)
172
+ selection.removeAllRanges()
173
+ selection.addRange(this.range)
166
174
  }
167
175
 
168
176
  // Take the following example:
@@ -218,17 +226,16 @@ export default class Cursor {
218
226
  }
219
227
 
220
228
  getBoundingClientRect () {
221
- return this.range.nativeRange.getBoundingClientRect()
229
+ return this.range.getBoundingClientRect()
222
230
  }
223
231
 
224
232
  // Get the BoundingClientRect of the cursor.
225
233
  // The returned values are transformed to be absolute
226
234
  // (relative to the document).
227
235
  getCoordinates (positioning = 'absolute') {
228
- const coords = this.range.nativeRange.getBoundingClientRect()
236
+ const coords = this.range.getBoundingClientRect()
229
237
  if (positioning === 'fixed') return coords
230
238
 
231
-
232
239
  // translate into absolute positions
233
240
  const {x, y} = viewport.getScrollPosition(this.win)
234
241
  return {
@@ -315,7 +322,7 @@ export default class Cursor {
315
322
  if (!cursor.host.isEqualNode(this.host)) return false
316
323
 
317
324
  if (!cursor.range) return false
318
- if (!cursor.range.equals(this.range)) return false
325
+ if (!rangesAreEqual(cursor.range, this.range)) return false
319
326
 
320
327
  return true
321
328
  }
@@ -345,8 +352,7 @@ export default class Cursor {
345
352
  // This is to prevent excessive triggering of the change event during
346
353
  // merge or split operations or other manipulations by scripts.
347
354
  triggerChange () {
348
- const event = document.createEvent('HTMLEvents')
349
- event.initEvent('formatEditable', true, false)
355
+ const event = new Event('formatEditable', {bubbles: true, cancelable: false})
350
356
  this.host.dispatchEvent(event)
351
357
  }
352
358
  }
package/src/dispatcher.js CHANGED
@@ -252,10 +252,10 @@ export default class Dispatcher {
252
252
  })
253
253
 
254
254
  .on('backspace', function (event) {
255
- const range = self.selectionWatcher.getFreshRange()
256
- if (!range.isCursor) return self.triggerChangeEvent(this)
255
+ const rangeContainer = self.selectionWatcher.getFreshRange()
256
+ if (!rangeContainer.isCursor) return self.triggerChangeEvent(this)
257
257
 
258
- const cursor = range.getCursor()
258
+ const cursor = rangeContainer.getCursor()
259
259
  if (!cursor.isAtBeginning()) return self.triggerChangeEvent(this)
260
260
 
261
261
  event.preventDefault()
@@ -264,10 +264,10 @@ export default class Dispatcher {
264
264
  })
265
265
 
266
266
  .on('delete', function (event) {
267
- const range = self.selectionWatcher.getFreshRange()
268
- if (!range.isCursor) return self.triggerChangeEvent(this)
267
+ const rangeContainer = self.selectionWatcher.getFreshRange()
268
+ if (!rangeContainer.isCursor) return self.triggerChangeEvent(this)
269
269
 
270
- const cursor = range.getCursor()
270
+ const cursor = rangeContainer.getCursor()
271
271
  if (!cursor.isAtTextEnd()) return self.triggerChangeEvent(this)
272
272
 
273
273
  event.preventDefault()
@@ -278,8 +278,8 @@ export default class Dispatcher {
278
278
  .on('enter', function (event) {
279
279
  event.preventDefault()
280
280
  event.stopPropagation()
281
- const range = self.selectionWatcher.getFreshRange()
282
- const cursor = range.forceCursor()
281
+ const rangeContainer = self.selectionWatcher.getFreshRange()
282
+ const cursor = rangeContainer.forceCursor()
283
283
 
284
284
  if (cursor.isAtTextEnd()) {
285
285
  self.notify('insert', this, 'after', cursor)
@@ -1,5 +1,3 @@
1
- import browser from 'bowser'
2
-
3
1
  /**
4
2
  * Check for contenteditable support
5
3
  *
@@ -10,9 +8,13 @@ import browser from 'bowser'
10
8
  */
11
9
  export const contenteditable = typeof document.documentElement.contentEditable !== 'undefined'
12
10
 
13
- const parser = browser.getParser(window.navigator.userAgent)
14
- const browserEngine = parser.getEngineName()
15
- const webKit = browserEngine === 'WebKit'
11
+ // Detect webkit browser engine
12
+ // That way we can detect the contenteditable span bug on safari, but exclude chrome
13
+ // Regex taken from: https://github.com/lancedikson/bowser/blob/f09411489ced05811c91cc6670a8e4ca9cbe39a7/src/parser-engines.js#L93-L106
14
+ // Attention, this might be error prone as any engine version change breaks this.
15
+ const isBlink = /(apple)?webkit\/537\.36/i.test(window.navigator.userAgent)
16
+ const isWebkit = /(apple)?webkit/i.test(window.navigator.userAgent)
17
+ const webKit = !isBlink && isWebkit
16
18
 
17
19
  /**
18
20
  * Check selectionchange event (supported in IE, Chrome, Firefox and Safari)
@@ -1,15 +1,12 @@
1
- import rangy from 'rangy'
2
1
  import * as content from './content'
3
2
  import highlightText from './highlight-text'
4
- import TextHighlighting from './plugins/highlighting/text-highlighting'
5
- import {closest, createElement} from './util/dom'
6
-
7
- function isInHost (elem, host) {
8
- return closest(elem, '[data-editable]:not([data-word-id])') === host
9
- }
3
+ import {searchText} from './plugins/highlighting/text-search'
4
+ import {createElement, createRange, toCharacterRange} from './util/dom'
10
5
 
11
6
  const highlightSupport = {
12
7
 
8
+ // Used to highlight arbitrary text in an editable. All occurrences
9
+ // will be highlighted.
13
10
  highlightText (editableHost, text, highlightId, type) {
14
11
  if (this.hasHighlight(editableHost, highlightId)) return
15
12
  const blockText = highlightText.extractText(editableHost)
@@ -17,8 +14,7 @@ const highlightSupport = {
17
14
  const marker = `<span class="highlight-${type}"></span>`
18
15
  const markerNode = highlightSupport.createMarkerNode(marker, type, this.win)
19
16
 
20
- const textSearch = new TextHighlighting(markerNode, 'text')
21
- const matches = textSearch.findMatches(blockText, [text])
17
+ const matches = searchText(blockText, text, markerNode)
22
18
 
23
19
  if (matches && matches.length) {
24
20
  if (highlightId) matches[0].id = highlightId
@@ -27,42 +23,39 @@ const highlightSupport = {
27
23
  }
28
24
  },
29
25
 
30
-
31
- // This function was changed to track matches when text is added to the start of a component, but multiple white spaces break it in a strict sense
32
- // The function works in the editor and in browsers, but tests with multiple white spaces will fail.
33
- // Browsers change the white spaces to &nbsp and the function works, and the tests in highlight.spec.js have been updated to represent this.
34
-
26
+ // Used to highlight comments.
27
+ // This function was changed to track matches when text is added to the start
28
+ // of a component, but multiple white spaces break it in a strict sense
29
+ // The function works in the editor and in browsers, but tests with
30
+ // multiple white spaces will fail.
31
+ // Browsers change the white spaces to &nbsp and the function works,
32
+ // and the tests in highlight.spec.js have been updated to represent this.
35
33
  highlightRange (editableHost, text, highlightId, startIndex, endIndex, dispatcher, type = 'comment') {
36
34
  if (this.hasHighlight(editableHost, highlightId)) {
37
35
  this.removeHighlight(editableHost, highlightId)
38
36
  }
39
- const firstMarker = `<span class="highlight-${type}"></span>`
40
- const markerNode = highlightSupport.createMarkerNode(firstMarker, type, this.win)
41
- const textSearch = new TextHighlighting(markerNode, 'text')
42
- const blockText = highlightText.extractText(editableHost)
43
- if (blockText === '') return -1 // the text was deleted so we can't highlight it
44
- const matchesArray = textSearch.findMatches(blockText, [text])
45
- const {actualStartIndex, actualEndIndex} = this.getIndex(matchesArray, startIndex, endIndex)
46
- const range = rangy.createRange()
47
- range.selectCharacters(editableHost, actualStartIndex, actualEndIndex)
48
-
49
- if (!isInHost(range.commonAncestorContainer, editableHost)) {
50
- return -1
51
- }
37
+
38
+ const blockText = highlightText.extractText(editableHost, false)
39
+ if (blockText === '') return -1 // the text was deleted so we can't highlight anything
52
40
 
53
41
  const marker = highlightSupport.createMarkerNode(
54
- `<span class="highlight-${type}" data-word-id="${highlightId}"></span>`,
42
+ `<span class="highlight-${type}"></span>`,
55
43
  type,
56
44
  this.win
57
45
  )
58
- const fragment = range.extractContents()
59
- marker.appendChild(fragment)
60
- range.deleteContents()
61
- range.insertNode(marker)
62
- highlightSupport.cleanupStaleMarkerNodes(editableHost, 'comment')
63
- if (dispatcher) {
64
- dispatcher.notify('change', editableHost)
65
- }
46
+
47
+ const actualStartIndex = startIndex
48
+ const actualEndIndex = endIndex
49
+
50
+ highlightText.highlightMatches(editableHost, [{
51
+ startIndex: actualStartIndex,
52
+ endIndex: actualEndIndex,
53
+ id: highlightId,
54
+ marker
55
+ }], false)
56
+
57
+ if (dispatcher) dispatcher.notify('change', editableHost)
58
+
66
59
  return actualStartIndex
67
60
  },
68
61
 
@@ -80,10 +73,12 @@ const highlightSupport = {
80
73
  const elems = editableHost.querySelectorAll(`[data-word-id="${highlightId}"]`)
81
74
  for (const elem of elems) {
82
75
  content.unwrap(elem)
83
- // in Chrome browsers the unwrap method leaves the host node split into 2 (lastChild !== firstChild)
84
- editableHost.normalize()
85
- if (dispatcher) dispatcher.notify('change', editableHost)
86
76
  }
77
+
78
+ // remove empty text nodes, combine adjacent text nodes
79
+ editableHost.normalize()
80
+
81
+ if (dispatcher) dispatcher.notify('change', editableHost)
87
82
  },
88
83
 
89
84
  hasHighlight (editableHost, highlightId) {
@@ -103,7 +98,6 @@ const highlightSupport = {
103
98
  if (!groups[highlightId]) {
104
99
  groups[highlightId] = editableHost.querySelectorAll(`[data-word-id="${highlightId}"]`)
105
100
  }
106
-
107
101
  }
108
102
 
109
103
  const res = {}
@@ -116,7 +110,7 @@ const highlightSupport = {
116
110
  },
117
111
 
118
112
  extractMarkerNodePosition (editableHost, markers) {
119
- const range = rangy.createRange()
113
+ const range = createRange()
120
114
  if (markers.length > 1) {
121
115
  range.setStartBefore(markers[0])
122
116
  range.setEndAfter(markers[markers.length - 1])
@@ -124,12 +118,12 @@ const highlightSupport = {
124
118
  range.selectNode(markers[0])
125
119
  }
126
120
 
127
- const textRange = range.toCharacterRange(editableHost)
121
+ const textRange = toCharacterRange(range, editableHost)
128
122
  return {
129
123
  start: textRange.start,
130
124
  end: textRange.end,
131
- text: range.text(),
132
- nativeRange: range.nativeRange
125
+ text: textRange.text, // browser range result (does whitespace normalization)
126
+ nativeRange: range
133
127
  }
134
128
  },
135
129
 
@@ -147,20 +141,7 @@ const highlightSupport = {
147
141
  marker.setAttribute('data-editable', 'ui-unwrap')
148
142
  marker.setAttribute('data-highlight', highlightType)
149
143
  return marker
150
- },
151
-
152
- // This function checks to see if text has been added to the component before the comment
153
- // If it has, the start index is updated, otherwise it remains the same
154
- getIndex (matchesArray, startIndex, endIndex) {
155
- const newStartIndex = matchesArray.find((match) => {
156
- return match.startIndex >= startIndex // checks if the startIndex has increased
157
- })
158
- const actualStartIndex = newStartIndex ? newStartIndex.startIndex : startIndex
159
- const actualEndIndex = actualStartIndex ? (actualStartIndex + (endIndex - startIndex)) : endIndex
160
-
161
- return {actualStartIndex, actualEndIndex}
162
144
  }
163
-
164
145
  }
165
146
 
166
147
  export default highlightSupport