rangy-rails 1.3alpha.804.0 → 1.3.1.pre.dev
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/lib/rangy-rails/version.rb +1 -1
- data/vendor/assets/javascripts/rangy-core.js +3157 -3050
- data/vendor/assets/javascripts/rangy-cssclassapplier.js +908 -790
- data/vendor/assets/javascripts/rangy-highlighter.js +550 -429
- data/vendor/assets/javascripts/rangy-selectionsaverestore.js +199 -182
- data/vendor/assets/javascripts/rangy-serializer.js +255 -236
- data/vendor/assets/javascripts/rangy-textrange.js +1565 -1547
- metadata +3 -5
- data/vendor/assets/javascripts/rangy-position.js +0 -524
@@ -1,498 +1,619 @@
|
|
1
1
|
/**
|
2
2
|
* Highlighter module for Rangy, a cross-browser JavaScript range and selection library
|
3
|
-
*
|
3
|
+
* https://github.com/timdown/rangy
|
4
4
|
*
|
5
|
-
* Depends on Rangy core,
|
5
|
+
* Depends on Rangy core, ClassApplier and optionally TextRange modules.
|
6
6
|
*
|
7
|
-
* Copyright
|
7
|
+
* Copyright 2015, Tim Down
|
8
8
|
* Licensed under the MIT license.
|
9
|
-
* Version: 1.
|
10
|
-
* Build date:
|
9
|
+
* Version: 1.3.1-dev
|
10
|
+
* Build date: 20 May 2015
|
11
11
|
*/
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
(function(factory, root) {
|
13
|
+
if (typeof define == "function" && define.amd) {
|
14
|
+
// AMD. Register as an anonymous module with a dependency on Rangy.
|
15
|
+
define(["./rangy-core"], factory);
|
16
|
+
} else if (typeof module != "undefined" && typeof exports == "object") {
|
17
|
+
// Node/CommonJS style
|
18
|
+
module.exports = factory( require("rangy") );
|
19
|
+
} else {
|
20
|
+
// No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
|
21
|
+
factory(root.rangy);
|
20
22
|
}
|
23
|
+
})(function(rangy) {
|
24
|
+
rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) {
|
25
|
+
var dom = api.dom;
|
26
|
+
var contains = dom.arrayContains;
|
27
|
+
var getBody = dom.getBody;
|
28
|
+
var createOptions = api.util.createOptions;
|
29
|
+
var forEach = api.util.forEach;
|
30
|
+
var nextHighlightId = 1;
|
31
|
+
|
32
|
+
// Puts highlights in order, last in document first.
|
33
|
+
function compareHighlights(h1, h2) {
|
34
|
+
return h1.characterRange.start - h2.characterRange.start;
|
35
|
+
}
|
21
36
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
} :
|
26
|
-
function(arr, func) {
|
27
|
-
for (var i = 0, len = arr.length; i < len; ++i) {
|
28
|
-
func( arr[i] );
|
29
|
-
}
|
30
|
-
};
|
37
|
+
function getContainerElement(doc, id) {
|
38
|
+
return id ? doc.getElementById(id) : getBody(doc);
|
39
|
+
}
|
31
40
|
|
32
|
-
|
41
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
33
42
|
|
34
|
-
|
43
|
+
var highlighterTypes = {};
|
35
44
|
|
36
|
-
|
45
|
+
function HighlighterType(type, converterCreator) {
|
46
|
+
this.type = type;
|
47
|
+
this.converterCreator = converterCreator;
|
48
|
+
}
|
37
49
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
50
|
+
HighlighterType.prototype.create = function() {
|
51
|
+
var converter = this.converterCreator();
|
52
|
+
converter.type = this.type;
|
53
|
+
return converter;
|
54
|
+
};
|
55
|
+
|
56
|
+
function registerHighlighterType(type, converterCreator) {
|
57
|
+
highlighterTypes[type] = new HighlighterType(type, converterCreator);
|
58
|
+
}
|
42
59
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
60
|
+
function getConverter(type) {
|
61
|
+
var highlighterType = highlighterTypes[type];
|
62
|
+
if (highlighterType instanceof HighlighterType) {
|
63
|
+
return highlighterType.create();
|
64
|
+
} else {
|
65
|
+
throw new Error("Highlighter type '" + type + "' is not valid");
|
66
|
+
}
|
67
|
+
}
|
48
68
|
|
49
|
-
|
50
|
-
highlighterTypes[type] = new HighlighterType(type, converterCreator);
|
51
|
-
}
|
69
|
+
api.registerHighlighterType = registerHighlighterType;
|
52
70
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
throw new Error("Highlighter type '" + type + "' is not valid");
|
71
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
72
|
+
|
73
|
+
function CharacterRange(start, end) {
|
74
|
+
this.start = start;
|
75
|
+
this.end = end;
|
59
76
|
}
|
60
|
-
}
|
61
77
|
|
62
|
-
|
78
|
+
CharacterRange.prototype = {
|
79
|
+
intersects: function(charRange) {
|
80
|
+
return this.start < charRange.end && this.end > charRange.start;
|
81
|
+
},
|
63
82
|
|
64
|
-
|
83
|
+
isContiguousWith: function(charRange) {
|
84
|
+
return this.start == charRange.end || this.end == charRange.start;
|
85
|
+
},
|
65
86
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
}
|
87
|
+
union: function(charRange) {
|
88
|
+
return new CharacterRange(Math.min(this.start, charRange.start), Math.max(this.end, charRange.end));
|
89
|
+
},
|
70
90
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
return new CharacterRange(charRange.start, charRange.end);
|
91
|
-
};
|
92
|
-
|
93
|
-
/*----------------------------------------------------------------------------------------------------------------*/
|
94
|
-
|
95
|
-
var textContentConverter = {
|
96
|
-
rangeToCharacterRange: function(range, containerNode) {
|
97
|
-
var bookmark = range.getBookmark(containerNode);
|
98
|
-
return new CharacterRange(bookmark.start, bookmark.end);
|
99
|
-
},
|
100
|
-
|
101
|
-
characterRangeToRange: function(doc, characterRange, containerNode) {
|
102
|
-
var range = api.createRange(doc);
|
103
|
-
range.moveToBookmark({
|
104
|
-
start: characterRange.start,
|
105
|
-
end: characterRange.end,
|
106
|
-
containerNode: containerNode
|
107
|
-
});
|
108
|
-
|
109
|
-
return range;
|
110
|
-
},
|
111
|
-
|
112
|
-
serializeSelection: function(selection, containerNode) {
|
113
|
-
var ranges = selection.getAllRanges(), rangeCount = ranges.length;
|
114
|
-
var rangeInfos = [];
|
115
|
-
|
116
|
-
var backward = rangeCount == 1 && selection.isBackward();
|
117
|
-
|
118
|
-
for (var i = 0, len = ranges.length; i < len; ++i) {
|
119
|
-
rangeInfos[i] = {
|
120
|
-
characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
|
121
|
-
backward: backward
|
122
|
-
};
|
123
|
-
}
|
91
|
+
intersection: function(charRange) {
|
92
|
+
return new CharacterRange(Math.max(this.start, charRange.start), Math.min(this.end, charRange.end));
|
93
|
+
},
|
94
|
+
|
95
|
+
getComplements: function(charRange) {
|
96
|
+
var ranges = [];
|
97
|
+
if (this.start >= charRange.start) {
|
98
|
+
if (this.end <= charRange.end) {
|
99
|
+
return [];
|
100
|
+
}
|
101
|
+
ranges.push(new CharacterRange(charRange.end, this.end));
|
102
|
+
} else {
|
103
|
+
ranges.push(new CharacterRange(this.start, Math.min(this.end, charRange.start)));
|
104
|
+
if (this.end > charRange.end) {
|
105
|
+
ranges.push(new CharacterRange(charRange.end, this.end));
|
106
|
+
}
|
107
|
+
}
|
108
|
+
return ranges;
|
109
|
+
},
|
124
110
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
restoreSelection: function(selection, savedSelection, containerNode) {
|
129
|
-
selection.removeAllRanges();
|
130
|
-
var doc = selection.win.document;
|
131
|
-
for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
|
132
|
-
rangeInfo = savedSelection[i];
|
133
|
-
characterRange = rangeInfo.characterRange;
|
134
|
-
range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
|
135
|
-
selection.addRange(range, rangeInfo.backward);
|
111
|
+
toString: function() {
|
112
|
+
return "[CharacterRange(" + this.start + ", " + this.end + ")]";
|
136
113
|
}
|
137
|
-
}
|
138
|
-
};
|
114
|
+
};
|
139
115
|
|
140
|
-
|
141
|
-
|
142
|
-
|
116
|
+
CharacterRange.fromCharacterRange = function(charRange) {
|
117
|
+
return new CharacterRange(charRange.start, charRange.end);
|
118
|
+
};
|
143
119
|
|
144
|
-
|
120
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
145
121
|
|
146
|
-
|
147
|
-
|
148
|
-
|
122
|
+
var textContentConverter = {
|
123
|
+
rangeToCharacterRange: function(range, containerNode) {
|
124
|
+
var bookmark = range.getBookmark(containerNode);
|
125
|
+
return new CharacterRange(bookmark.start, bookmark.end);
|
126
|
+
},
|
149
127
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
}
|
157
|
-
|
128
|
+
characterRangeToRange: function(doc, characterRange, containerNode) {
|
129
|
+
var range = api.createRange(doc);
|
130
|
+
range.moveToBookmark({
|
131
|
+
start: characterRange.start,
|
132
|
+
end: characterRange.end,
|
133
|
+
containerNode: containerNode
|
134
|
+
});
|
135
|
+
|
136
|
+
return range;
|
137
|
+
},
|
138
|
+
|
139
|
+
serializeSelection: function(selection, containerNode) {
|
140
|
+
var ranges = selection.getAllRanges(), rangeCount = ranges.length;
|
141
|
+
var rangeInfos = [];
|
142
|
+
|
143
|
+
var backward = rangeCount == 1 && selection.isBackward();
|
144
|
+
|
145
|
+
for (var i = 0, len = ranges.length; i < len; ++i) {
|
146
|
+
rangeInfos[i] = {
|
147
|
+
characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
|
148
|
+
backward: backward
|
149
|
+
};
|
158
150
|
}
|
159
151
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
152
|
+
return rangeInfos;
|
153
|
+
},
|
154
|
+
|
155
|
+
restoreSelection: function(selection, savedSelection, containerNode) {
|
156
|
+
selection.removeAllRanges();
|
157
|
+
var doc = selection.win.document;
|
158
|
+
for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
|
159
|
+
rangeInfo = savedSelection[i];
|
160
|
+
characterRange = rangeInfo.characterRange;
|
161
|
+
range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
|
162
|
+
selection.addRange(range, rangeInfo.backward);
|
163
|
+
}
|
164
|
+
}
|
165
|
+
};
|
166
|
+
|
167
|
+
registerHighlighterType("textContent", function() {
|
168
|
+
return textContentConverter;
|
169
|
+
});
|
164
170
|
|
165
|
-
|
166
|
-
var range = api.createRange(doc);
|
167
|
-
range.selectCharacters(containerNode, characterRange.start, characterRange.end);
|
168
|
-
return range;
|
169
|
-
},
|
171
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
170
172
|
|
171
|
-
|
172
|
-
|
173
|
-
|
173
|
+
// Lazily load the TextRange-based converter so that the dependency is only checked when required.
|
174
|
+
registerHighlighterType("TextRange", (function() {
|
175
|
+
var converter;
|
174
176
|
|
175
|
-
|
176
|
-
|
177
|
+
return function() {
|
178
|
+
if (!converter) {
|
179
|
+
// Test that textRangeModule exists and is supported
|
180
|
+
var textRangeModule = api.modules.TextRange;
|
181
|
+
if (!textRangeModule) {
|
182
|
+
throw new Error("TextRange module is missing.");
|
183
|
+
} else if (!textRangeModule.supported) {
|
184
|
+
throw new Error("TextRange module is present but not supported.");
|
177
185
|
}
|
178
|
-
};
|
179
|
-
}
|
180
186
|
|
181
|
-
|
182
|
-
|
183
|
-
|
187
|
+
converter = {
|
188
|
+
rangeToCharacterRange: function(range, containerNode) {
|
189
|
+
return CharacterRange.fromCharacterRange( range.toCharacterRange(containerNode) );
|
190
|
+
},
|
184
191
|
|
185
|
-
|
192
|
+
characterRangeToRange: function(doc, characterRange, containerNode) {
|
193
|
+
var range = api.createRange(doc);
|
194
|
+
range.selectCharacters(containerNode, characterRange.start, characterRange.end);
|
195
|
+
return range;
|
196
|
+
},
|
186
197
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
}
|
198
|
+
serializeSelection: function(selection, containerNode) {
|
199
|
+
return selection.saveCharacterRanges(containerNode);
|
200
|
+
},
|
201
|
+
|
202
|
+
restoreSelection: function(selection, savedSelection, containerNode) {
|
203
|
+
selection.restoreCharacterRanges(containerNode, savedSelection);
|
204
|
+
}
|
205
|
+
};
|
206
|
+
}
|
207
|
+
|
208
|
+
return converter;
|
209
|
+
};
|
210
|
+
})());
|
201
211
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
this.
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
return this.getRange().toString();
|
217
|
-
},
|
218
|
-
|
219
|
-
containsElement: function(el) {
|
220
|
-
return this.getRange().containsNodeContents(el.firstChild);
|
221
|
-
},
|
222
|
-
|
223
|
-
unapply: function() {
|
224
|
-
this.classApplier.undoToRange(this.getRange());
|
212
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
213
|
+
|
214
|
+
function Highlight(doc, characterRange, classApplier, converter, id, containerElementId) {
|
215
|
+
if (id) {
|
216
|
+
this.id = id;
|
217
|
+
nextHighlightId = Math.max(nextHighlightId, id + 1);
|
218
|
+
} else {
|
219
|
+
this.id = nextHighlightId++;
|
220
|
+
}
|
221
|
+
this.characterRange = characterRange;
|
222
|
+
this.doc = doc;
|
223
|
+
this.classApplier = classApplier;
|
224
|
+
this.converter = converter;
|
225
|
+
this.containerElementId = containerElementId || null;
|
225
226
|
this.applied = false;
|
226
|
-
},
|
227
|
-
|
228
|
-
apply: function() {
|
229
|
-
this.classApplier.applyToRange(this.getRange());
|
230
|
-
this.applied = true;
|
231
|
-
},
|
232
|
-
|
233
|
-
getHighlightElements: function() {
|
234
|
-
return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
|
235
|
-
},
|
236
|
-
|
237
|
-
toString: function() {
|
238
|
-
return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.cssClass + ", character range: " +
|
239
|
-
this.characterRange.start + " - " + this.characterRange.end + ")]";
|
240
227
|
}
|
241
|
-
};
|
242
228
|
|
243
|
-
|
229
|
+
Highlight.prototype = {
|
230
|
+
getContainerElement: function() {
|
231
|
+
return getContainerElement(this.doc, this.containerElementId);
|
232
|
+
},
|
244
233
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
this.classAppliers = {};
|
249
|
-
this.highlights = [];
|
250
|
-
this.converter = getConverter(type);
|
251
|
-
}
|
234
|
+
getRange: function() {
|
235
|
+
return this.converter.characterRangeToRange(this.doc, this.characterRange, this.getContainerElement());
|
236
|
+
},
|
252
237
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
},
|
238
|
+
fromRange: function(range) {
|
239
|
+
this.characterRange = this.converter.rangeToCharacterRange(range, this.getContainerElement());
|
240
|
+
},
|
257
241
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
}
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
242
|
+
getText: function() {
|
243
|
+
return this.getRange().toString();
|
244
|
+
},
|
245
|
+
|
246
|
+
containsElement: function(el) {
|
247
|
+
return this.getRange().containsNodeContents(el.firstChild);
|
248
|
+
},
|
249
|
+
|
250
|
+
unapply: function() {
|
251
|
+
this.classApplier.undoToRange(this.getRange());
|
252
|
+
this.applied = false;
|
253
|
+
},
|
254
|
+
|
255
|
+
apply: function() {
|
256
|
+
this.classApplier.applyToRange(this.getRange());
|
257
|
+
this.applied = true;
|
258
|
+
},
|
259
|
+
|
260
|
+
getHighlightElements: function() {
|
261
|
+
return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
|
262
|
+
},
|
263
|
+
|
264
|
+
toString: function() {
|
265
|
+
return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.className + ", character range: " +
|
266
|
+
this.characterRange.start + " - " + this.characterRange.end + ")]";
|
275
267
|
}
|
276
|
-
}
|
268
|
+
};
|
277
269
|
|
278
|
-
|
279
|
-
this.removeHighlights(this.highlights);
|
280
|
-
},
|
270
|
+
/*----------------------------------------------------------------------------------------------------------------*/
|
281
271
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
272
|
+
function Highlighter(doc, type) {
|
273
|
+
type = type || "textContent";
|
274
|
+
this.doc = doc || document;
|
275
|
+
this.classAppliers = {};
|
276
|
+
this.highlights = [];
|
277
|
+
this.converter = getConverter(type);
|
278
|
+
}
|
279
|
+
|
280
|
+
Highlighter.prototype = {
|
281
|
+
addClassApplier: function(classApplier) {
|
282
|
+
this.classAppliers[classApplier.className] = classApplier;
|
283
|
+
},
|
284
|
+
|
285
|
+
getHighlightForElement: function(el) {
|
286
|
+
var highlights = this.highlights;
|
287
|
+
for (var i = 0, len = highlights.length; i < len; ++i) {
|
288
|
+
if (highlights[i].containsElement(el)) {
|
289
|
+
return highlights[i];
|
290
290
|
}
|
291
|
-
});
|
292
|
-
});
|
293
|
-
|
294
|
-
return intersectingHighlights;
|
295
|
-
},
|
296
|
-
|
297
|
-
highlightCharacterRanges: function(className, charRanges, containerElementId) {
|
298
|
-
var i, len, j;
|
299
|
-
var highlights = this.highlights;
|
300
|
-
var converter = this.converter;
|
301
|
-
var doc = this.doc;
|
302
|
-
var highlightsToRemove = [];
|
303
|
-
var classApplier = this.classAppliers[className];
|
304
|
-
containerElementId = containerElementId || null;
|
305
|
-
|
306
|
-
var containerElement, containerElementRange, containerElementCharRange;
|
307
|
-
if (containerElementId) {
|
308
|
-
containerElement = this.doc.getElementById(containerElementId);
|
309
|
-
if (containerElement) {
|
310
|
-
containerElementRange = api.createRange(this.doc);
|
311
|
-
containerElementRange.selectNodeContents(containerElement);
|
312
|
-
containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
|
313
|
-
containerElementRange.detach();
|
314
291
|
}
|
315
|
-
|
292
|
+
return null;
|
293
|
+
},
|
294
|
+
|
295
|
+
removeHighlights: function(highlights) {
|
296
|
+
for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) {
|
297
|
+
highlight = this.highlights[i];
|
298
|
+
if (contains(highlights, highlight)) {
|
299
|
+
highlight.unapply();
|
300
|
+
this.highlights.splice(i--, 1);
|
301
|
+
}
|
302
|
+
}
|
303
|
+
},
|
304
|
+
|
305
|
+
removeAllHighlights: function() {
|
306
|
+
this.removeHighlights(this.highlights);
|
307
|
+
},
|
308
|
+
|
309
|
+
getIntersectingHighlights: function(ranges) {
|
310
|
+
// Test each range against each of the highlighted ranges to see whether they overlap
|
311
|
+
var intersectingHighlights = [], highlights = this.highlights;
|
312
|
+
forEach(ranges, function(range) {
|
313
|
+
//var selCharRange = converter.rangeToCharacterRange(range);
|
314
|
+
forEach(highlights, function(highlight) {
|
315
|
+
if (range.intersectsRange( highlight.getRange() ) && !contains(intersectingHighlights, highlight)) {
|
316
|
+
intersectingHighlights.push(highlight);
|
317
|
+
}
|
318
|
+
});
|
319
|
+
});
|
320
|
+
|
321
|
+
return intersectingHighlights;
|
322
|
+
},
|
316
323
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
324
|
+
highlightCharacterRanges: function(className, charRanges, options) {
|
325
|
+
var i, len, j;
|
326
|
+
var highlights = this.highlights;
|
327
|
+
var converter = this.converter;
|
328
|
+
var doc = this.doc;
|
329
|
+
var highlightsToRemove = [];
|
330
|
+
var classApplier = className ? this.classAppliers[className] : null;
|
321
331
|
|
322
|
-
|
323
|
-
|
324
|
-
|
332
|
+
options = createOptions(options, {
|
333
|
+
containerElementId: null,
|
334
|
+
exclusive: true
|
335
|
+
});
|
336
|
+
|
337
|
+
var containerElementId = options.containerElementId;
|
338
|
+
var exclusive = options.exclusive;
|
339
|
+
|
340
|
+
var containerElement, containerElementRange, containerElementCharRange;
|
341
|
+
if (containerElementId) {
|
342
|
+
containerElement = this.doc.getElementById(containerElementId);
|
343
|
+
if (containerElement) {
|
344
|
+
containerElementRange = api.createRange(this.doc);
|
345
|
+
containerElementRange.selectNodeContents(containerElement);
|
346
|
+
containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
|
347
|
+
}
|
325
348
|
}
|
326
349
|
|
327
|
-
|
328
|
-
|
329
|
-
for (
|
330
|
-
|
331
|
-
|
350
|
+
var charRange, highlightCharRange, removeHighlight, isSameClassApplier, highlightsToKeep, splitHighlight;
|
351
|
+
|
352
|
+
for (i = 0, len = charRanges.length; i < len; ++i) {
|
353
|
+
charRange = charRanges[i];
|
354
|
+
highlightsToKeep = [];
|
355
|
+
|
356
|
+
// Restrict character range to container element, if it exists
|
357
|
+
if (containerElementCharRange) {
|
358
|
+
charRange = charRange.intersection(containerElementCharRange);
|
359
|
+
}
|
360
|
+
|
361
|
+
// Ignore empty ranges
|
362
|
+
if (charRange.start == charRange.end) {
|
363
|
+
continue;
|
364
|
+
}
|
365
|
+
|
366
|
+
// Check for intersection with existing highlights. For each intersection, create a new highlight
|
367
|
+
// which is the union of the highlight range and the selected range
|
368
|
+
for (j = 0; j < highlights.length; ++j) {
|
369
|
+
removeHighlight = false;
|
370
|
+
|
371
|
+
if (containerElementId == highlights[j].containerElementId) {
|
372
|
+
highlightCharRange = highlights[j].characterRange;
|
373
|
+
isSameClassApplier = (classApplier == highlights[j].classApplier);
|
374
|
+
splitHighlight = !isSameClassApplier && exclusive;
|
375
|
+
|
376
|
+
// Replace the existing highlight if it needs to be:
|
377
|
+
// 1. merged (isSameClassApplier)
|
378
|
+
// 2. partially or entirely erased (className === null)
|
379
|
+
// 3. partially or entirely replaced (isSameClassApplier == false && exclusive == true)
|
380
|
+
if ( (highlightCharRange.intersects(charRange) || highlightCharRange.isContiguousWith(charRange)) &&
|
381
|
+
(isSameClassApplier || splitHighlight) ) {
|
382
|
+
|
383
|
+
// Remove existing highlights, keeping the unselected parts
|
384
|
+
if (splitHighlight) {
|
385
|
+
forEach(highlightCharRange.getComplements(charRange), function(rangeToAdd) {
|
386
|
+
highlightsToKeep.push( new Highlight(doc, rangeToAdd, highlights[j].classApplier, converter, null, containerElementId) );
|
387
|
+
});
|
388
|
+
}
|
389
|
+
|
390
|
+
removeHighlight = true;
|
391
|
+
if (isSameClassApplier) {
|
392
|
+
charRange = highlightCharRange.union(charRange);
|
393
|
+
}
|
394
|
+
}
|
395
|
+
}
|
332
396
|
|
333
|
-
if (
|
334
|
-
// Replace the existing highlight in the list of current highlights and add it to the list for
|
335
|
-
// removal
|
397
|
+
if (removeHighlight) {
|
336
398
|
highlightsToRemove.push(highlights[j]);
|
337
399
|
highlights[j] = new Highlight(doc, highlightCharRange.union(charRange), classApplier, converter, null, containerElementId);
|
400
|
+
} else {
|
401
|
+
highlightsToKeep.push(highlights[j]);
|
338
402
|
}
|
339
403
|
}
|
340
|
-
}
|
341
404
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
// Remove the old highlights
|
348
|
-
forEach(highlightsToRemove, function(highlightToRemove) {
|
349
|
-
highlightToRemove.unapply();
|
350
|
-
});
|
351
|
-
|
352
|
-
// Apply new highlights
|
353
|
-
var newHighlights = [];
|
354
|
-
forEach(highlights, function(highlight) {
|
355
|
-
if (!highlight.applied) {
|
356
|
-
highlight.apply();
|
357
|
-
newHighlights.push(highlight);
|
405
|
+
// Add new range
|
406
|
+
if (classApplier) {
|
407
|
+
highlightsToKeep.push(new Highlight(doc, charRange, classApplier, converter, null, containerElementId));
|
408
|
+
}
|
409
|
+
this.highlights = highlights = highlightsToKeep;
|
358
410
|
}
|
359
|
-
});
|
360
|
-
|
361
|
-
return newHighlights;
|
362
|
-
},
|
363
|
-
|
364
|
-
highlightRanges: function(className, ranges, containerElement) {
|
365
|
-
var selCharRanges = [];
|
366
|
-
var converter = this.converter;
|
367
|
-
var containerElementId = containerElement ? containerElement.id : null;
|
368
|
-
var containerElementRange;
|
369
|
-
if (containerElement) {
|
370
|
-
containerElementRange = api.createRange(containerElement);
|
371
|
-
containerElementRange.selectNodeContents(containerElement);
|
372
|
-
}
|
373
|
-
|
374
|
-
forEach(ranges, function(range) {
|
375
|
-
var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
|
376
|
-
selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
|
377
|
-
});
|
378
|
-
|
379
|
-
return this.highlightCharacterRanges(selCharRanges, ranges, containerElementId);
|
380
|
-
},
|
381
|
-
|
382
|
-
highlightSelection: function(className, selection, containerElementId) {
|
383
|
-
var converter = this.converter;
|
384
|
-
selection = selection || api.getSelection();
|
385
|
-
var classApplier = this.classAppliers[className];
|
386
|
-
var doc = selection.win.document;
|
387
|
-
var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);
|
388
|
-
|
389
|
-
if (!classApplier) {
|
390
|
-
throw new Error("No class applier found for class '" + className + "'");
|
391
|
-
}
|
392
411
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
},
|
426
|
-
|
427
|
-
serialize: function(options) {
|
428
|
-
var highlights = this.highlights;
|
429
|
-
highlights.sort(compareHighlights);
|
430
|
-
var serializedHighlights = ["type:" + this.converter.type];
|
431
|
-
|
432
|
-
forEach(highlights, function(highlight) {
|
433
|
-
var characterRange = highlight.characterRange;
|
434
|
-
var parts = [
|
435
|
-
characterRange.start,
|
436
|
-
characterRange.end,
|
437
|
-
highlight.id,
|
438
|
-
highlight.classApplier.cssClass,
|
439
|
-
highlight.containerElementId
|
440
|
-
];
|
441
|
-
if (options && options.serializeHighlightText) {
|
442
|
-
parts.push(highlight.getText());
|
412
|
+
// Remove the old highlights
|
413
|
+
forEach(highlightsToRemove, function(highlightToRemove) {
|
414
|
+
highlightToRemove.unapply();
|
415
|
+
});
|
416
|
+
|
417
|
+
// Apply new highlights
|
418
|
+
var newHighlights = [];
|
419
|
+
forEach(highlights, function(highlight) {
|
420
|
+
if (!highlight.applied) {
|
421
|
+
highlight.apply();
|
422
|
+
newHighlights.push(highlight);
|
423
|
+
}
|
424
|
+
});
|
425
|
+
|
426
|
+
return newHighlights;
|
427
|
+
},
|
428
|
+
|
429
|
+
highlightRanges: function(className, ranges, options) {
|
430
|
+
var selCharRanges = [];
|
431
|
+
var converter = this.converter;
|
432
|
+
|
433
|
+
options = createOptions(options, {
|
434
|
+
containerElement: null,
|
435
|
+
exclusive: true
|
436
|
+
});
|
437
|
+
|
438
|
+
var containerElement = options.containerElement;
|
439
|
+
var containerElementId = containerElement ? containerElement.id : null;
|
440
|
+
var containerElementRange;
|
441
|
+
if (containerElement) {
|
442
|
+
containerElementRange = api.createRange(containerElement);
|
443
|
+
containerElementRange.selectNodeContents(containerElement);
|
443
444
|
}
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
445
|
+
|
446
|
+
forEach(ranges, function(range) {
|
447
|
+
var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
|
448
|
+
selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
|
449
|
+
});
|
450
|
+
|
451
|
+
return this.highlightCharacterRanges(className, selCharRanges, {
|
452
|
+
containerElementId: containerElementId,
|
453
|
+
exclusive: options.exclusive
|
454
|
+
});
|
455
|
+
},
|
456
|
+
|
457
|
+
highlightSelection: function(className, options) {
|
458
|
+
var converter = this.converter;
|
459
|
+
var classApplier = className ? this.classAppliers[className] : false;
|
460
|
+
|
461
|
+
options = createOptions(options, {
|
462
|
+
containerElementId: null,
|
463
|
+
exclusive: true
|
464
|
+
});
|
465
|
+
|
466
|
+
var containerElementId = options.containerElementId;
|
467
|
+
var exclusive = options.exclusive;
|
468
|
+
var selection = options.selection || api.getSelection(this.doc);
|
469
|
+
var doc = selection.win.document;
|
470
|
+
var containerElement = getContainerElement(doc, containerElementId);
|
471
|
+
|
472
|
+
if (!classApplier && className !== false) {
|
473
|
+
throw new Error("No class applier found for class '" + className + "'");
|
462
474
|
}
|
463
|
-
serializedHighlights.shift();
|
464
|
-
} else {
|
465
|
-
throw new Error("Serialized highlights are invalid.");
|
466
|
-
}
|
467
|
-
|
468
|
-
var classApplier, highlight, characterRange, containerElementId, containerElement;
|
469
475
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
476
|
+
// Store the existing selection as character ranges
|
477
|
+
var serializedSelection = converter.serializeSelection(selection, containerElement);
|
478
|
+
|
479
|
+
// Create an array of selected character ranges
|
480
|
+
var selCharRanges = [];
|
481
|
+
forEach(serializedSelection, function(rangeInfo) {
|
482
|
+
selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
|
483
|
+
});
|
484
|
+
|
485
|
+
var newHighlights = this.highlightCharacterRanges(className, selCharRanges, {
|
486
|
+
containerElementId: containerElementId,
|
487
|
+
exclusive: exclusive
|
488
|
+
});
|
489
|
+
|
490
|
+
// Restore selection
|
491
|
+
converter.restoreSelection(selection, serializedSelection, containerElement);
|
492
|
+
|
493
|
+
return newHighlights;
|
494
|
+
},
|
495
|
+
|
496
|
+
unhighlightSelection: function(selection) {
|
497
|
+
selection = selection || api.getSelection(this.doc);
|
498
|
+
var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
|
499
|
+
this.removeHighlights(intersectingHighlights);
|
500
|
+
selection.removeAllRanges();
|
501
|
+
return intersectingHighlights;
|
502
|
+
},
|
503
|
+
|
504
|
+
getHighlightsInSelection: function(selection) {
|
505
|
+
selection = selection || api.getSelection(this.doc);
|
506
|
+
return this.getIntersectingHighlights(selection.getAllRanges());
|
507
|
+
},
|
508
|
+
|
509
|
+
selectionOverlapsHighlight: function(selection) {
|
510
|
+
return this.getHighlightsInSelection(selection).length > 0;
|
511
|
+
},
|
512
|
+
|
513
|
+
serialize: function(options) {
|
514
|
+
var highlighter = this;
|
515
|
+
var highlights = highlighter.highlights;
|
516
|
+
var serializedType, serializedHighlights, convertType, serializationConverter;
|
517
|
+
|
518
|
+
highlights.sort(compareHighlights);
|
519
|
+
options = createOptions(options, {
|
520
|
+
serializeHighlightText: false,
|
521
|
+
type: highlighter.converter.type
|
522
|
+
});
|
523
|
+
|
524
|
+
serializedType = options.type;
|
525
|
+
convertType = (serializedType != highlighter.converter.type);
|
475
526
|
|
476
|
-
// Convert to the current Highlighter's type, if different from the serialization type
|
477
527
|
if (convertType) {
|
478
|
-
|
479
|
-
serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
|
480
|
-
containerElement
|
481
|
-
);
|
528
|
+
serializationConverter = getConverter(serializedType);
|
482
529
|
}
|
483
530
|
|
484
|
-
|
485
|
-
|
486
|
-
highlight
|
487
|
-
|
531
|
+
serializedHighlights = ["type:" + serializedType];
|
532
|
+
|
533
|
+
forEach(highlights, function(highlight) {
|
534
|
+
var characterRange = highlight.characterRange;
|
535
|
+
var containerElement;
|
536
|
+
|
537
|
+
// Convert to the current Highlighter's type, if different from the serialization type
|
538
|
+
if (convertType) {
|
539
|
+
containerElement = highlight.getContainerElement();
|
540
|
+
characterRange = serializationConverter.rangeToCharacterRange(
|
541
|
+
highlighter.converter.characterRangeToRange(highlighter.doc, characterRange, containerElement),
|
542
|
+
containerElement
|
543
|
+
);
|
544
|
+
}
|
545
|
+
|
546
|
+
var parts = [
|
547
|
+
characterRange.start,
|
548
|
+
characterRange.end,
|
549
|
+
highlight.id,
|
550
|
+
highlight.classApplier.className,
|
551
|
+
highlight.containerElementId
|
552
|
+
];
|
553
|
+
|
554
|
+
if (options.serializeHighlightText) {
|
555
|
+
parts.push(highlight.getText());
|
556
|
+
}
|
557
|
+
serializedHighlights.push( parts.join("$") );
|
558
|
+
});
|
559
|
+
|
560
|
+
return serializedHighlights.join("|");
|
561
|
+
},
|
562
|
+
|
563
|
+
deserialize: function(serialized) {
|
564
|
+
var serializedHighlights = serialized.split("|");
|
565
|
+
var highlights = [];
|
566
|
+
|
567
|
+
var firstHighlight = serializedHighlights[0];
|
568
|
+
var regexResult;
|
569
|
+
var serializationType, serializationConverter, convertType = false;
|
570
|
+
if ( firstHighlight && (regexResult = /^type:(\w+)$/.exec(firstHighlight)) ) {
|
571
|
+
serializationType = regexResult[1];
|
572
|
+
if (serializationType != this.converter.type) {
|
573
|
+
serializationConverter = getConverter(serializationType);
|
574
|
+
convertType = true;
|
575
|
+
}
|
576
|
+
serializedHighlights.shift();
|
577
|
+
} else {
|
578
|
+
throw new Error("Serialized highlights are invalid.");
|
579
|
+
}
|
580
|
+
|
581
|
+
var classApplier, highlight, characterRange, containerElementId, containerElement;
|
582
|
+
|
583
|
+
for (var i = serializedHighlights.length, parts; i-- > 0; ) {
|
584
|
+
parts = serializedHighlights[i].split("$");
|
585
|
+
characterRange = new CharacterRange(+parts[0], +parts[1]);
|
586
|
+
containerElementId = parts[4] || null;
|
587
|
+
|
588
|
+
// Convert to the current Highlighter's type, if different from the serialization type
|
589
|
+
if (convertType) {
|
590
|
+
containerElement = getContainerElement(this.doc, containerElementId);
|
591
|
+
characterRange = this.converter.rangeToCharacterRange(
|
592
|
+
serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
|
593
|
+
containerElement
|
594
|
+
);
|
595
|
+
}
|
596
|
+
|
597
|
+
classApplier = this.classAppliers[ parts[3] ];
|
598
|
+
|
599
|
+
if (!classApplier) {
|
600
|
+
throw new Error("No class applier found for class '" + parts[3] + "'");
|
601
|
+
}
|
602
|
+
|
603
|
+
highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parseInt(parts[2]), containerElementId);
|
604
|
+
highlight.apply();
|
605
|
+
highlights.push(highlight);
|
606
|
+
}
|
607
|
+
this.highlights = highlights;
|
488
608
|
}
|
489
|
-
|
490
|
-
}
|
491
|
-
};
|
609
|
+
};
|
492
610
|
|
493
|
-
|
611
|
+
api.Highlighter = Highlighter;
|
494
612
|
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
});
|
613
|
+
api.createHighlighter = function(doc, rangeCharacterOffsetConverterType) {
|
614
|
+
return new Highlighter(doc, rangeCharacterOffsetConverterType);
|
615
|
+
};
|
616
|
+
});
|
617
|
+
|
618
|
+
return rangy;
|
619
|
+
}, this);
|