Skip to content

Commit cbd78b9

Browse files
committed
Finish highlight-text functionality
1 parent 899549c commit cbd78b9

File tree

4 files changed

+67
-12
lines changed

4 files changed

+67
-12
lines changed

spec/highlight-text.spec.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,14 @@ describe('highlightText', function() {
7171
describe('iterator', function() {
7272

7373
beforeEach(function() {
74-
this.wrapWord = sinon.stub(highlightText, 'wrapWord');
74+
this.wrapWord = sinon.spy(highlightText, 'wrapWord');
7575
});
7676

7777
afterEach(function() {
7878
this.wrapWord.restore();
7979
});
8080

81-
it('finds a letter that it is own text node', function() {
81+
it('finds a letter that is its own text node', function() {
8282
var elem = createParagraphWithTextNodes('a', 'b', 'c');
8383
iterateOverElement(elem, /b/g);
8484
var portions = this.wrapWord.firstCall.args[0];
@@ -176,6 +176,33 @@ describe('highlightText', function() {
176176
expect(range.commonAncestorContainer.outerHTML)
177177
.toEqual('<div>Some <span data-awesome="crazy">jui</span><em><span data-awesome="crazy">ce</span>.</em></div>')
178178
});
179+
180+
it('wraps two words in the same text node', function() {
181+
var elem = $('<div>a or b</div>')[0];
182+
var range = highlightText.getRange(elem);
183+
var matches = highlightText.find(range, /a|b/g);
184+
highlightText.highlightMatches(range, matches);
185+
expect(range.commonAncestorContainer.outerHTML)
186+
.toEqual('<div><span data-awesome="crazy">a</span> or <span data-awesome="crazy">b</span></div>')
187+
});
188+
189+
it('wraps a word in a <em> element', function() {
190+
var elem = $('<div><em>word</em></div>')[0];
191+
var range = highlightText.getRange(elem);
192+
var matches = highlightText.find(range, /word/g);
193+
highlightText.highlightMatches(range, matches);
194+
expect(range.commonAncestorContainer.outerHTML)
195+
.toEqual('<div><em><span data-awesome="crazy">word</span></em></div>')
196+
});
197+
198+
it('can handle a non-match', function() {
199+
var elem = $('<div><em>word</em></div>')[0];
200+
var range = highlightText.getRange(elem);
201+
var matches = highlightText.find(range, /xxx/g);
202+
highlightText.highlightMatches(range, matches);
203+
expect(range.commonAncestorContainer.outerHTML)
204+
.toEqual('<div><em>word</em></div>')
205+
});
179206
});
180207

181208
});

spec/interator.spec.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ describe('Iterator', function() {
33
// Helper methods
44
// --------------
55

6-
// todo: remove
7-
rangy.init()
8-
96
var callnTimes = function(object, methodName, count) {
107
var returnValue;
118
while (count--) {

src/highlight-text.js

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@ var highlightText = (function() {
1919
},
2020

2121
highlightMatches: function(range, matches) {
22+
if (!matches || matches.length == 0) {
23+
return;
24+
}
25+
2226
var textNode, length, firstPortion, lastPortion;
2327
var currentMatchIndex = 0;
2428
var currentMatch = matches[currentMatchIndex];
2529
var totalOffset = 0;
2630
var iterator = this.getTextIterator(range);
2731
var portions = [];
28-
while ( (textNode = iterator.next()) ) {
32+
var portionsLength = 0;
33+
while ( textNode = iterator.getNextTextNode() ) {
2934
var nodeText = textNode.data;
3035
var nodeEndOffset = totalOffset + nodeText.length;
3136
if (nodeEndOffset > currentMatch.startIndex && totalOffset < currentMatch.endIndex) {
@@ -63,11 +68,18 @@ var highlightText = (function() {
6368
lastPortion: lastPortion
6469
}
6570

71+
portionsLength += portion.length;
6672
portions.push(portion);
6773

6874
if (lastPortion) {
69-
this.wrapWord(portions);
75+
var lastNode = this.wrapWord(portions);
76+
iterator.replaceCurrent(lastNode);
77+
78+
// recalculate nodeEndOffset if we have to replace the current node.
79+
nodeEndOffset = totalOffset + portionsLength;
80+
7081
portions = [];
82+
portionsLength = 0;
7183
currentMatchIndex += 1;
7284
if (currentMatchIndex < matches.length) {
7385
currentMatch = matches[currentMatchIndex];
@@ -87,12 +99,12 @@ var highlightText = (function() {
8799
},
88100

89101
getTextIterator: function(range) {
90-
return range.createNodeIterator([3], function(node) {
91-
// no filter needed right now.
92-
return true;
93-
});
102+
var root = range.commonAncestorContainer;
103+
var iterator = new Iterator(root);
104+
return iterator;
94105
},
95106

107+
// @return the last wrapped element
96108
wrapWord: function(portions) {
97109
var element;
98110
for (var i = 0; i < portions.length; i++) {
@@ -108,7 +120,17 @@ var highlightText = (function() {
108120
range.setStart(portion.element, portion.offset);
109121
range.setEnd(portion.element, portion.offset + portion.length);
110122
var node = $('<span data-awesome="crazy">')[0];
111-
return range.surroundContents(node);
123+
range.surroundContents(node);
124+
125+
// Fix a weird behaviour where an empty text node is inserted after the range
126+
if (node.nextSibling) {
127+
var next = node.nextSibling;
128+
if (next.nodeType === 3 && next.data === '') {
129+
next.parentNode.removeChild(next);
130+
}
131+
}
132+
133+
return node;
112134
},
113135

114136
prepareMatch: function (match, matchIndex) {

src/iterator.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ var Iterator = (function() {
55
this.current = this.next = this.root;
66
};
77

8+
Iterator.prototype.getNextTextNode = function() {
9+
var next;
10+
while (next = this.getNext()) {
11+
if (next.nodeType === 3 && next.data !== '') {
12+
return next;
13+
}
14+
}
15+
},
16+
817
Iterator.prototype.getNext = function() {
918
var child, n;
1019
n = this.current = this.next;

0 commit comments

Comments
 (0)