rangy-rails 1.3alpha.804.0 → 1.3.1.pre.dev
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.
- 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);
|