@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.
@@ -1,16 +1,15 @@
1
- import rangy from 'rangy'
2
-
3
1
  import NodeIterator from './node-iterator'
4
2
  import * as nodeType from './node-type'
3
+ import {createRange} from './util/dom'
5
4
 
6
5
  export default {
7
6
 
8
7
  // Get the text from an editable block with a NodeIterator.
9
8
  // This must work the same as when later iterating over the text
10
9
  // in highlightMatches().
11
- extractText (element) {
10
+ extractText (element, convertBRs = true) {
12
11
  let text = ''
13
- getText(element, (part) => { text += part })
12
+ getText(element, convertBRs, (part) => { text += part })
14
13
  return text
15
14
  },
16
15
 
@@ -21,12 +20,21 @@ export default {
21
20
  // @params
22
21
  // - matches
23
22
  // Array of positions in the string to highlight:
24
- // e.g [{startIndex: 0, endIndex: 1, match: 'The'}]
25
- highlightMatches (element, matches) {
23
+ // e.g [{
24
+ // startIndex: 0,
25
+ // endIndex: 1,
26
+ // match: 'The', // not used, only the indexes are used for highlighting)
27
+ // marker: DOMNode, // A clone of this element will be inserted
28
+ // id: 'a7382', // used in word-id attribute
29
+ // title: 'The World' // used in title attribute (optional)
30
+ // }]
31
+ highlightMatches (element, matches, countBRs = true) {
26
32
  if (!matches || matches.length === 0) {
27
33
  return
28
34
  }
29
35
 
36
+ element.normalize() // mend text nodes
37
+
30
38
  const iterator = new NodeIterator(element)
31
39
  let currentMatchIndex = 0
32
40
  let totalOffset = 0
@@ -39,7 +47,7 @@ export default {
39
47
  // Account for <br> elements
40
48
  if (next.nodeType === nodeType.textNode && next.data !== '') {
41
49
  textNode = next
42
- } else if (next.nodeType === nodeType.elementNode && next.nodeName === 'BR') {
50
+ } else if (countBRs && next.nodeType === nodeType.elementNode && next.nodeName === 'BR') {
43
51
  totalOffset += 1
44
52
  continue
45
53
  } else {
@@ -109,7 +117,7 @@ export default {
109
117
  },
110
118
 
111
119
  wrapPortion (portion, stencilElement, title) {
112
- const range = rangy.createRange()
120
+ const range = createRange()
113
121
  range.setStart(portion.element, portion.offset)
114
122
  range.setEnd(portion.element, portion.offset + portion.length)
115
123
  const node = stencilElement.cloneNode(true)
@@ -136,14 +144,14 @@ export default {
136
144
  // with data-editable="remove"
137
145
  // - It returns a \n for <br> elements
138
146
  // (The only block level element allowed inside of editables)
139
- function getText (element, callback) {
147
+ function getText (element, convertBRs, func) {
140
148
  const iterator = new NodeIterator(element)
141
149
  let next
142
150
  while ((next = iterator.getNext())) {
143
151
  if (next.nodeType === nodeType.textNode && next.data !== '') {
144
- callback(next.data)
145
- } else if (next.nodeType === nodeType.elementNode && next.nodeName === 'BR') {
146
- callback('\n')
152
+ func(next.data)
153
+ } else if (convertBRs && next.nodeType === nodeType.elementNode && next.nodeName === 'BR') {
154
+ func('\n')
147
155
  }
148
156
  }
149
157
  }
package/src/keyboard.js CHANGED
@@ -1,5 +1,4 @@
1
- import rangy from 'rangy'
2
-
1
+ import {createRange, containsRange} from './util/dom'
3
2
  import {contenteditableSpanBug} from './feature-detection'
4
3
  import * as nodeType from './node-type'
5
4
  import eventable from './eventable'
@@ -118,10 +117,10 @@ export default class Keyboard {
118
117
  //
119
118
  // Manually remove the element that would be removed anyway before inserting
120
119
  // the new letter.
121
- const rangyInstance = this.selectionWatcher.getFreshRange()
122
- if (!rangyInstance.isSelection) return
120
+ const rangeContainer = this.selectionWatcher.getFreshRange()
121
+ if (!rangeContainer.isSelection) return
123
122
 
124
- const nodeToRemove = Keyboard.getNodeToRemove(rangyInstance.range, target)
123
+ const nodeToRemove = Keyboard.getNodeToRemove(rangeContainer.range, target)
125
124
  if (nodeToRemove) nodeToRemove.remove()
126
125
  }
127
126
 
@@ -144,10 +143,10 @@ export default class Keyboard {
144
143
 
145
144
  // We get a range that contains everything within the sartNodeElement to test
146
145
  // if the selectionRange is within the startNode, we have nothing to do.
147
- const startNodeRange = rangy.createRange()
146
+ const startNodeRange = createRange()
148
147
  startNodeRange.setStartBefore(startNodeElement.firstChild)
149
148
  startNodeRange.setEndAfter(startNodeElement.lastChild)
150
- if (startNodeRange.containsRange(selectionRange)) return
149
+ if (containsRange(startNodeRange, selectionRange)) return
151
150
 
152
151
  // If the selectionRange.startContainer was a textNode, we have to make sure
153
152
  // that its parent's content starts with this node. Content is either a
@@ -4,12 +4,18 @@ import * as content from './content'
4
4
  import highlightText from './highlight-text'
5
5
  import SpellcheckService from './plugins/highlighting/spellcheck-service'
6
6
  import WhitespaceHighlighting from './plugins/highlighting/whitespace-highlighting'
7
- import WordHighlighting from './plugins/highlighting/text-highlighting'
7
+ import {searchAllWords} from './plugins/highlighting/text-search'
8
8
  import MatchCollection from './plugins/highlighting/match-collection'
9
9
  import highlightSupport from './highlight-support'
10
10
  import {domArray, domSelector} from './util/dom'
11
11
 
12
- export default class Highlighting {
12
+ // Spellcheck and Whitespace Highlighting
13
+ // --------------------------------------
14
+ //
15
+ // This instance monitors an editable block for changes and
16
+ // updates highlights accordingly. It also calls the spellcheck
17
+ // service after the content has changed.
18
+ export default class MonitoredHighlighting {
13
19
 
14
20
  constructor (editable, configuration, spellcheckConfig) {
15
21
  this.editable = editable
@@ -41,13 +47,12 @@ export default class Highlighting {
41
47
  const spellcheckService = this.config.spellcheck.spellcheckService
42
48
  const spellcheckMarker = this.config.spellcheck.marker
43
49
  const whitespaceMarker = this.config.whitespace.marker
44
- const spellcheckMarkerNode = highlightSupport
45
- .createMarkerNode(spellcheckMarker, 'spellcheck', this.win)
46
50
  const whitespaceMarkerNode = highlightSupport
47
- .createMarkerNode(whitespaceMarker, 'spellcheck', this.win)
51
+ .createMarkerNode(whitespaceMarker, 'whitespace', this.win)
52
+ this.spellcheckMarkerNode = highlightSupport
53
+ .createMarkerNode(spellcheckMarker, 'spellcheck', this.win)
48
54
 
49
55
  this.spellcheckService = new SpellcheckService(spellcheckService)
50
- this.spellcheck = new WordHighlighting(spellcheckMarkerNode)
51
56
  this.whitespace = new WhitespaceHighlighting(whitespaceMarkerNode)
52
57
 
53
58
  this.setupListeners()
@@ -116,22 +121,22 @@ export default class Highlighting {
116
121
  }
117
122
 
118
123
  highlight (editableHost) {
119
- let text = highlightText.extractText(editableHost)
124
+ const textBefore = highlightText.extractText(editableHost)
120
125
 
121
126
  // getSpellcheck
122
- this.spellcheckService.check(text, (err, misspelledWords) => {
123
- if (err) { return }
127
+ this.spellcheckService.check(textBefore, (err, misspelledWords) => {
128
+ if (err || !editableHost.isConnected) { return } // return in case the host was removed from the dom
124
129
 
125
130
  // refresh the text
126
- text = highlightText.extractText(editableHost)
131
+ const text = highlightText.extractText(editableHost)
127
132
 
128
133
  const matchCollection = new MatchCollection()
129
134
 
130
- let matches = this.spellcheck.findMatches(text, misspelledWords)
131
- matchCollection.addMatches('spellcheck', matches)
135
+ let matches = searchAllWords(text, misspelledWords, this.spellcheckMarkerNode)
136
+ matchCollection.addMatches(matches)
132
137
 
133
138
  matches = this.whitespace.findMatches(text)
134
- matchCollection.addMatches('whitespace', matches)
139
+ matchCollection.addMatches(matches)
135
140
 
136
141
  this.safeHighlightMatches(editableHost, matchCollection.matches)
137
142
  })
@@ -166,7 +171,7 @@ export default class Highlighting {
166
171
 
167
172
  removeHighlights (editableHost) {
168
173
  editableHost = domSelector(editableHost, this.win.document)
169
- for (const elem of domArray('[data-highlight="spellcheck"]', editableHost)) {
174
+ for (const elem of domArray('[data-highlight="spellcheck"], [data-highlight="whitespace"]', editableHost)) {
170
175
  content.unwrap(elem)
171
176
  }
172
177
  }
@@ -184,7 +189,7 @@ export default class Highlighting {
184
189
  do {
185
190
  if (elementAtCursor === editableHost) return
186
191
  const highlightType = elementAtCursor.getAttribute('data-highlight')
187
- if (highlightType === 'spellcheck') {
192
+ if (highlightType === 'spellcheck' || highlightType === 'whitespace') {
188
193
  wordId = elementAtCursor.getAttribute('data-word-id')
189
194
  break
190
195
  }
@@ -4,9 +4,8 @@ export default class MatchCollection {
4
4
  this.matches = []
5
5
  }
6
6
 
7
- addMatches (name, matches) {
8
- if (!matches || !matches.length) return
9
-
7
+ addMatches (matches) {
8
+ if (!matches?.length) return
10
9
  this.matches = mergeMatches(this.matches, matches)
11
10
  }
12
11
 
@@ -0,0 +1,100 @@
1
+ export function searchText (text, searchTerm, marker) {
2
+ const matchMode = 'text'
3
+ return findMatches(text, [searchTerm], matchMode, marker)
4
+ }
5
+
6
+ export function searchWord (text, word, marker) {
7
+ const matchMode = 'word'
8
+ return findMatches(text, [word], matchMode, marker)
9
+ }
10
+
11
+ export function searchAllWords (text, words, marker) {
12
+ const matchMode = 'word'
13
+ return findMatches(text, words, matchMode, marker)
14
+ }
15
+
16
+ function findMatches (text, searchTexts, matchMode, marker) {
17
+ if (!text || text === '') return []
18
+ if (marker && !isElement(marker)) return []
19
+ if (!searchTexts?.length) return []
20
+
21
+ const createRegex = matchMode === 'word'
22
+ ? createWordRegex
23
+ : createHighlightRegex
24
+
25
+ const regex = createRegex(searchTexts)
26
+ const matches = [...text.matchAll(regex)]
27
+
28
+ return matches.map((match) => {
29
+ let startIndex
30
+ let matchedText
31
+ if (matchMode === 'word') {
32
+ startIndex = match.index + match[1].length
33
+ matchedText = match[2]
34
+ } else {
35
+ startIndex = match.index
36
+ matchedText = match[0]
37
+ }
38
+
39
+ return {
40
+ startIndex,
41
+ endIndex: startIndex + matchedText.length,
42
+ match: matchedText,
43
+ marker
44
+ }
45
+ })
46
+ }
47
+
48
+ function isElement (obj) {
49
+ try {
50
+ if (!obj) return false
51
+ return obj instanceof obj.ownerDocument?.defaultView.HTMLElement
52
+ } catch (e) {
53
+ // Browsers not supporting W3 DOM2 don't have HTMLElement and
54
+ // an exception is thrown and we end up here. Testing some
55
+ // properties that all elements have (works on IE7)
56
+ return (typeof obj === 'object') &&
57
+ (obj.nodeType === 1) && (typeof obj.style === 'object') &&
58
+ (typeof obj.ownerDocument === 'object')
59
+ }
60
+ }
61
+
62
+ function createHighlightRegex (words = []) {
63
+ const escapedWords = words.map((word) => escapeRegEx(word))
64
+
65
+ const regex = `(${escapedWords.join('|')})`
66
+ return new RegExp(regex, 'g')
67
+ }
68
+
69
+ // Regex to find whole words within a string
70
+ //
71
+ // Unicode character blocks for letters.
72
+ // See: http://jrgraphix.net/research/unicode_blocks.php
73
+ //
74
+ // \\u0041-\\u005A A-Z (Basic Latin)
75
+ // \\u0061-\\u007A a-z (Basic Latin)
76
+ // \\u0030-\\u0039 0-9 (Basic Latin)
77
+ // \\u00AA ª (Latin-1 Supplement)
78
+ // \\u00B5 µ (Latin-1 Supplement)
79
+ // \\u00BA º (Latin-1 Supplement)
80
+ // \\u00C0-\\u00D6 À-Ö (Latin-1 Supplement)
81
+ // \\u00D8-\\u00F6 Ø-ö (Latin-1 Supplement)
82
+ // \\u00F8-\\u00FF ø-ÿ (Latin-1 Supplement)
83
+ // \\u0100-\\u017F Ā-ſ (Latin Extended-A)
84
+ // \\u0180-\\u024F ƀ-ɏ (Latin Extended-B)
85
+ const letterChars = '\\u0041-\\u005A\\u0061-\\u007A\\u0030-\\u0039\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u00FF\\u0100-\\u017F\\u0180-\\u024F'
86
+
87
+ function createWordRegex (words = []) {
88
+ const escapedWords = words.map((word) => escapeRegEx(word))
89
+
90
+ // (notLetter|^)(words)(?=notLetter|$)
91
+ const regex = `([^${letterChars}]|^)` +
92
+ `(${escapedWords.join('|')})` +
93
+ `(?=[^${letterChars}]|$)`
94
+
95
+ return new RegExp(regex, 'g')
96
+ }
97
+
98
+ function escapeRegEx (s) {
99
+ return String(s).replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1')
100
+ }
@@ -23,6 +23,7 @@ const characters = {
23
23
  // a paragraph and the highlighting prevents browsers from converting
24
24
  // the no-break space back to a normal space when the user keeps typing.
25
25
  const specialWhitespaceChars = '\\u2000-\\u200A\\u202F\\u205F\\u3000'
26
+ const specialWhitespaceCharsRegex = new RegExp(`[${specialWhitespaceChars}]`, 'g')
26
27
 
27
28
  export default class WhitespaceHighlighting {
28
29
 
@@ -33,13 +34,7 @@ export default class WhitespaceHighlighting {
33
34
  findMatches (text) {
34
35
  if (!text) return
35
36
 
36
- let regex = `[${specialWhitespaceChars}]`
37
- regex = new RegExp(regex, 'g')
38
-
39
- const matches = []
40
- let match
41
- while ((match = regex.exec(text))) matches.push(match)
42
-
37
+ const matches = [...text.matchAll(specialWhitespaceCharsRegex)]
43
38
  return matches.map((entry) => this.prepareMatch(entry))
44
39
  }
45
40
 
@@ -1,5 +1,6 @@
1
1
  import Cursor from './cursor'
2
2
  import Selection from './selection'
3
+ import {rangesAreEqual} from './util/dom'
3
4
 
4
5
  /** RangeContainer
5
6
  *
@@ -10,13 +11,13 @@ import Selection from './selection'
10
11
  */
11
12
 
12
13
  export default class RangeContainer {
13
- constructor (editableHost, rangyRange) {
14
+ constructor (editableHost, range) {
14
15
  this.host = editableHost && editableHost.jquery
15
16
  ? editableHost[0]
16
17
  : editableHost
17
- this.range = rangyRange
18
- this.isAnythingSelected = (rangyRange !== undefined)
19
- this.isCursor = (this.isAnythingSelected && rangyRange.collapsed)
18
+ this.range = range
19
+ this.isAnythingSelected = (range !== undefined)
20
+ this.isCursor = (this.isAnythingSelected && range.collapsed)
20
21
  this.isSelection = (this.isAnythingSelected && !this.isCursor)
21
22
  }
22
23
 
@@ -36,7 +37,7 @@ export default class RangeContainer {
36
37
  isDifferentFrom (otherRangeContainer = new RangeContainer()) {
37
38
  const self = this.range
38
39
  const other = otherRangeContainer.range
39
- if (self && other) return !self.equals(other)
40
+ if (self && other) return !rangesAreEqual(self, other)
40
41
  if (!self && !other) return false
41
42
  return true
42
43
  }
@@ -1,6 +1,6 @@
1
- import rangy from 'rangy'
2
1
  import error from './util/error'
3
2
  import * as nodeType from './node-type'
3
+ import {createRange, normalizeBoundaries} from './util/dom'
4
4
 
5
5
  /**
6
6
  * Inspired by the Selection save and restore module for Rangy by Tim Down
@@ -11,35 +11,19 @@ let boundaryMarkerId = 0
11
11
  // (U+FEFF) zero width no-break space
12
12
  const markerTextChar = '\ufeff'
13
13
 
14
- function isChildOf (parent, possibleChild) {
15
- for (let i = 0; i < parent.children.length; i++) {
16
- if (parent.children[i] === possibleChild) return true
17
- }
18
- return false
19
- }
20
-
21
- function startContainerIsChild (range) {
22
- const parent = range.commonAncestorContainer
23
- if (parent.nodeType === 3) return false // if we are on the text node it can't be a parent
24
- const possibleChild = range.startContainer.parentElement
25
- return isChildOf(parent, possibleChild)
26
- }
27
-
28
- function endContainerIsChild (range) {
14
+ function isSecondChildOfCommonAncestor (range, rangeContainer) {
29
15
  const parent = range.commonAncestorContainer
30
16
  if (parent.nodeType === 3) return false // if we are on the text node it can't be a parent
31
- const possibleChild = range.endContainer.parentElement
32
- return isChildOf(parent, possibleChild)
17
+ const possibleChild = rangeContainer.parentElement
18
+ return possibleChild?.parentElement === parent
33
19
  }
34
20
 
35
21
  export function insertRangeBoundaryMarker (range, atStart) {
36
22
  const container = range.commonAncestorContainer
37
- const directlyBeforeFormatTag = startContainerIsChild(range) && atStart
38
- const directlyAfterFormatTag = endContainerIsChild(range) && !atStart
39
23
 
40
24
  // If ownerDocument is null the commonAncestorContainer is window.document
41
- if (container.ownerDocument === null || container.ownerDocument === undefined) {
42
- error('Cannot save range: range is emtpy')
25
+ if (!container.ownerDocument) {
26
+ error('Cannot save range: range is empty')
43
27
  }
44
28
 
45
29
  // Create the marker element containing a single
@@ -52,14 +36,21 @@ export function insertRangeBoundaryMarker (range, atStart) {
52
36
  markerEl.style.display = 'none'
53
37
  markerEl.appendChild(doc.createTextNode(markerTextChar))
54
38
 
55
- // if another tag is trailing exactly at the start or end, prepend
56
- // or append the marker element directly on the parent in order to prevent breaking HTML markup.
39
+ // This logic can expand the selection by inserting the marker before the element
40
+ // of the start or end container. In some cases this prevents breaking up existing tags.
41
+ // The solution is not perfect, but has been here a while.
42
+ // And it is somewhat inconsistent as it check if the container is a second grade child
43
+ // of the common ancestor.
44
+ // It can help to prevent the nuking of e.g. comments when formatting like bold
45
+ // is applied.
46
+ const directlyBeforeFormatTag = atStart && isSecondChildOfCommonAncestor(range, range.startContainer)
47
+ const directlyAfterFormatTag = !atStart && isSecondChildOfCommonAncestor(range, range.endContainer)
57
48
  if (directlyBeforeFormatTag) {
58
- range.endContainer.parentElement.insertBefore(markerEl, range.startContainer.parentElement)
49
+ const startParentElem = range.startContainer.parentElement
50
+ startParentElem.parentElement.insertBefore(markerEl, startParentElem)
59
51
  } else if (directlyAfterFormatTag) {
60
- // emulating insertAfter with nextSibling
61
- range.startContainer.parentElement.insertBefore(markerEl,
62
- range.endContainer.parentElement.nextSibling)
52
+ const endParentElem = range.endContainer.parentElement
53
+ endParentElem.parentElement.insertBefore(markerEl, endParentElem.nextSibling)
63
54
  } else {
64
55
  // Clone the Range and collapse to the appropriate boundary point
65
56
  const boundaryRange = range.cloneRange()
@@ -100,7 +91,8 @@ export function save (range) {
100
91
 
101
92
  // Adjust each range's boundaries to lie between its markers
102
93
  if (range.collapsed) {
103
- range.collapseBefore(endEl)
94
+ range.setStartBefore(endEl)
95
+ range.collapse(true)
104
96
  } else {
105
97
  range.setEndBefore(endEl)
106
98
  range.setStartAfter(startEl)
@@ -112,7 +104,7 @@ export function save (range) {
112
104
  export function restore (host, rangeInfo) {
113
105
  if (rangeInfo.restored) return
114
106
 
115
- const range = rangy.createRange()
107
+ const range = createRange()
116
108
  if (rangeInfo.collapsed) {
117
109
  const markerEl = getMarker(host, rangeInfo.markerId)
118
110
  if (markerEl) {
@@ -122,9 +114,10 @@ export function restore (host, rangeInfo) {
122
114
  // Workaround for rangy issue 17
123
115
  if (previousNode && previousNode.nodeType === nodeType.textNode) {
124
116
  markerEl.remove()
125
- range.collapseToPoint(previousNode, previousNode.length)
117
+ range.setStart(previousNode, previousNode.length)
126
118
  } else {
127
- range.collapseBefore(markerEl)
119
+ range.setStartBefore(markerEl)
120
+ range.collapse(true)
128
121
  markerEl.remove()
129
122
  }
130
123
  } else {
@@ -135,7 +128,7 @@ export function restore (host, rangeInfo) {
135
128
  setRangeBoundary(host, range, rangeInfo.endMarkerId, false)
136
129
  }
137
130
 
138
- range.normalizeBoundaries()
131
+ normalizeBoundaries(range)
139
132
  return range
140
133
  }
141
134
 
@@ -1,9 +1,8 @@
1
- import rangy from 'rangy'
2
-
3
1
  import * as parser from './parser'
4
2
  import RangeContainer from './range-container'
5
3
  import Cursor from './cursor'
6
4
  import Selection from './selection'
5
+ import {getSelection} from './util/dom'
7
6
 
8
7
  /**
9
8
  * The SelectionWatcher module watches for selection changes inside
@@ -17,19 +16,19 @@ export default class SelectionWatcher {
17
16
  constructor (dispatcher, win) {
18
17
  this.dispatcher = dispatcher
19
18
  this.win = win || window
20
- this.rangySelection = undefined
19
+ this.selection = undefined
21
20
  this.currentSelection = undefined
22
21
  this.currentRange = undefined
23
22
  }
24
23
 
25
24
  /**
26
- * Updates the internal selection pointer to the current rangy selection.
25
+ * Updates the internal selection pointer to the current selection.
27
26
  * Returns true if no exception occurred.
28
27
  */
29
28
  syncSelection () {
30
- // it is possible that rangy has a problem with the nativeSelection
29
+ // Note: the try catch was introduced because of rangy exceptions with nativeSelections
31
30
  try {
32
- this.rangySelection = rangy.getSelection(this.win)
31
+ this.selection = getSelection(this.win)
33
32
  } catch (err) {
34
33
  return false
35
34
  }
@@ -47,8 +46,8 @@ export default class SelectionWatcher {
47
46
  // rangeCount is 0 or 1 in all browsers except firefox
48
47
  // firefox can work with multiple ranges
49
48
  // (on a mac hold down the command key to select multiple ranges)
50
- if (this.rangySelection.rangeCount && successfulSync) {
51
- const range = this.rangySelection.getRangeAt(0)
49
+ if (this.selection.rangeCount && successfulSync) {
50
+ const range = this.selection.getRangeAt(0)
52
51
  const hostNode = parser.getHost(range.commonAncestorContainer)
53
52
  if (hostNode) return new RangeContainer(hostNode, range)
54
53
  }
@@ -73,11 +72,11 @@ export default class SelectionWatcher {
73
72
  * there is neither a selection or cursor.
74
73
  */
75
74
  getFreshSelection () {
76
- const range = this.getRangeContainer()
75
+ const rangeContainer = this.getRangeContainer()
77
76
 
78
- return range.isCursor
79
- ? range.getCursor(this.win)
80
- : range.getSelection(this.win)
77
+ return rangeContainer.isCursor
78
+ ? rangeContainer.getCursor(this.win)
79
+ : rangeContainer.getSelection(this.win)
81
80
  }
82
81
 
83
82
  /**
@@ -94,15 +93,15 @@ export default class SelectionWatcher {
94
93
  }
95
94
 
96
95
  forceCursor () {
97
- const range = this.getRangeContainer()
98
- return range.forceCursor()
96
+ const rangeContainer = this.getRangeContainer()
97
+ return rangeContainer.forceCursor()
99
98
  }
100
99
 
101
100
  selectionChanged () {
102
- const newRange = this.getRangeContainer()
103
- if (newRange.isDifferentFrom(this.currentRange)) {
101
+ const newRangeContainer = this.getRangeContainer()
102
+ if (newRangeContainer.isDifferentFrom(this.currentRange)) {
104
103
  const lastSelection = this.currentSelection
105
- this.currentRange = newRange
104
+ this.currentRange = newRangeContainer
106
105
 
107
106
  // empty selection or cursor
108
107
  if (lastSelection) {