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
@@ -3,233 +3,250 @@
|
|
3
3
|
* Saves and restores user selections using marker invisible elements in the DOM.
|
4
4
|
*
|
5
5
|
* Part of Rangy, a cross-browser JavaScript range and selection library
|
6
|
-
*
|
6
|
+
* https://github.com/timdown/rangy
|
7
7
|
*
|
8
8
|
* Depends on Rangy core.
|
9
9
|
*
|
10
|
-
* Copyright
|
10
|
+
* Copyright 2015, Tim Down
|
11
11
|
* Licensed under the MIT license.
|
12
|
-
* Version: 1.
|
13
|
-
* Build date:
|
12
|
+
* Version: 1.3.1-dev
|
13
|
+
* Build date: 20 May 2015
|
14
14
|
*/
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
(function(factory, root) {
|
16
|
+
if (typeof define == "function" && define.amd) {
|
17
|
+
// AMD. Register as an anonymous module with a dependency on Rangy.
|
18
|
+
define(["./rangy-core"], factory);
|
19
|
+
} else if (typeof module != "undefined" && typeof exports == "object") {
|
20
|
+
// Node/CommonJS style
|
21
|
+
module.exports = factory( require("rangy") );
|
22
|
+
} else {
|
23
|
+
// No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
|
24
|
+
factory(root.rangy);
|
22
25
|
}
|
23
|
-
|
24
|
-
function
|
25
|
-
var
|
26
|
-
var
|
27
|
-
var
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
// Create the marker element containing a single invisible character using DOM methods and insert it
|
34
|
-
markerEl = doc.createElement("span");
|
35
|
-
markerEl.id = markerId;
|
36
|
-
markerEl.style.lineHeight = "0";
|
37
|
-
markerEl.style.display = "none";
|
38
|
-
markerEl.className = "rangySelectionBoundary";
|
39
|
-
markerEl.appendChild(doc.createTextNode(markerTextChar));
|
40
|
-
|
41
|
-
boundaryRange.insertNode(markerEl);
|
42
|
-
boundaryRange.detach();
|
43
|
-
return markerEl;
|
44
|
-
}
|
45
|
-
|
46
|
-
function setRangeBoundary(doc, range, markerId, atStart) {
|
47
|
-
var markerEl = gEBI(markerId, doc);
|
48
|
-
if (markerEl) {
|
49
|
-
range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
|
50
|
-
markerEl.parentNode.removeChild(markerEl);
|
51
|
-
} else {
|
52
|
-
module.warn("Marker element has been removed. Cannot restore selection.");
|
26
|
+
})(function(rangy) {
|
27
|
+
rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
|
28
|
+
var dom = api.dom;
|
29
|
+
var removeNode = dom.removeNode;
|
30
|
+
var isDirectionBackward = api.Selection.isDirectionBackward;
|
31
|
+
var markerTextChar = "\ufeff";
|
32
|
+
|
33
|
+
function gEBI(id, doc) {
|
34
|
+
return (doc || document).getElementById(id);
|
53
35
|
}
|
54
|
-
}
|
55
|
-
|
56
|
-
function compareRanges(r1, r2) {
|
57
|
-
return r2.compareBoundaryPoints(r1.START_TO_START, r1);
|
58
|
-
}
|
59
36
|
|
60
|
-
|
61
|
-
|
37
|
+
function insertRangeBoundaryMarker(range, atStart) {
|
38
|
+
var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
|
39
|
+
var markerEl;
|
40
|
+
var doc = dom.getDocument(range.startContainer);
|
41
|
+
|
42
|
+
// Clone the Range and collapse to the appropriate boundary point
|
43
|
+
var boundaryRange = range.cloneRange();
|
44
|
+
boundaryRange.collapse(atStart);
|
45
|
+
|
46
|
+
// Create the marker element containing a single invisible character using DOM methods and insert it
|
47
|
+
markerEl = doc.createElement("span");
|
48
|
+
markerEl.id = markerId;
|
49
|
+
markerEl.style.lineHeight = "0";
|
50
|
+
markerEl.style.display = "none";
|
51
|
+
markerEl.className = "rangySelectionBoundary";
|
52
|
+
markerEl.appendChild(doc.createTextNode(markerTextChar));
|
53
|
+
|
54
|
+
boundaryRange.insertNode(markerEl);
|
55
|
+
return markerEl;
|
56
|
+
}
|
62
57
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
startEl = insertRangeBoundaryMarker(range, true);
|
58
|
+
function setRangeBoundary(doc, range, markerId, atStart) {
|
59
|
+
var markerEl = gEBI(markerId, doc);
|
60
|
+
if (markerEl) {
|
61
|
+
range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
|
62
|
+
removeNode(markerEl);
|
63
|
+
} else {
|
64
|
+
module.warn("Marker element has been removed. Cannot restore selection.");
|
65
|
+
}
|
66
|
+
}
|
73
67
|
|
74
|
-
|
75
|
-
|
76
|
-
startMarkerId: startEl.id,
|
77
|
-
endMarkerId: endEl.id,
|
78
|
-
collapsed: false,
|
79
|
-
backward: backward,
|
80
|
-
toString: function() {
|
81
|
-
return "original text: '" + text + "', new text: '" + range.toString() + "'";
|
82
|
-
}
|
83
|
-
};
|
68
|
+
function compareRanges(r1, r2) {
|
69
|
+
return r2.compareBoundaryPoints(r1.START_TO_START, r1);
|
84
70
|
}
|
85
|
-
}
|
86
71
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
72
|
+
function saveRange(range, direction) {
|
73
|
+
var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
|
74
|
+
var backward = isDirectionBackward(direction);
|
75
|
+
|
76
|
+
if (range.collapsed) {
|
77
|
+
endEl = insertRangeBoundaryMarker(range, false);
|
78
|
+
return {
|
79
|
+
document: doc,
|
80
|
+
markerId: endEl.id,
|
81
|
+
collapsed: true
|
82
|
+
};
|
83
|
+
} else {
|
84
|
+
endEl = insertRangeBoundaryMarker(range, false);
|
85
|
+
startEl = insertRangeBoundaryMarker(range, true);
|
86
|
+
|
87
|
+
return {
|
88
|
+
document: doc,
|
89
|
+
startMarkerId: startEl.id,
|
90
|
+
endMarkerId: endEl.id,
|
91
|
+
collapsed: false,
|
92
|
+
backward: backward,
|
93
|
+
toString: function() {
|
94
|
+
return "original text: '" + text + "', new text: '" + range.toString() + "'";
|
95
|
+
}
|
96
|
+
};
|
97
|
+
}
|
91
98
|
}
|
92
|
-
var range = api.createRange(doc);
|
93
|
-
if (rangeInfo.collapsed) {
|
94
|
-
var markerEl = gEBI(rangeInfo.markerId, doc);
|
95
|
-
if (markerEl) {
|
96
|
-
markerEl.style.display = "inline";
|
97
|
-
var previousNode = markerEl.previousSibling;
|
98
99
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
100
|
+
function restoreRange(rangeInfo, normalize) {
|
101
|
+
var doc = rangeInfo.document;
|
102
|
+
if (typeof normalize == "undefined") {
|
103
|
+
normalize = true;
|
104
|
+
}
|
105
|
+
var range = api.createRange(doc);
|
106
|
+
if (rangeInfo.collapsed) {
|
107
|
+
var markerEl = gEBI(rangeInfo.markerId, doc);
|
108
|
+
if (markerEl) {
|
109
|
+
markerEl.style.display = "inline";
|
110
|
+
var previousNode = markerEl.previousSibling;
|
111
|
+
|
112
|
+
// Workaround for issue 17
|
113
|
+
if (previousNode && previousNode.nodeType == 3) {
|
114
|
+
removeNode(markerEl);
|
115
|
+
range.collapseToPoint(previousNode, previousNode.length);
|
116
|
+
} else {
|
117
|
+
range.collapseBefore(markerEl);
|
118
|
+
removeNode(markerEl);
|
119
|
+
}
|
103
120
|
} else {
|
104
|
-
|
105
|
-
markerEl.parentNode.removeChild(markerEl);
|
121
|
+
module.warn("Marker element has been removed. Cannot restore selection.");
|
106
122
|
}
|
107
123
|
} else {
|
108
|
-
|
124
|
+
setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
|
125
|
+
setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
|
109
126
|
}
|
110
|
-
} else {
|
111
|
-
setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
|
112
|
-
setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
|
113
|
-
}
|
114
127
|
|
115
|
-
|
116
|
-
|
117
|
-
|
128
|
+
if (normalize) {
|
129
|
+
range.normalizeBoundaries();
|
130
|
+
}
|
118
131
|
|
119
|
-
|
120
|
-
|
132
|
+
return range;
|
133
|
+
}
|
121
134
|
|
122
|
-
|
123
|
-
|
135
|
+
function saveRanges(ranges, direction) {
|
136
|
+
var rangeInfos = [], range, doc;
|
137
|
+
var backward = isDirectionBackward(direction);
|
124
138
|
|
125
|
-
|
126
|
-
|
127
|
-
|
139
|
+
// Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
|
140
|
+
ranges = ranges.slice(0);
|
141
|
+
ranges.sort(compareRanges);
|
128
142
|
|
129
|
-
|
130
|
-
|
131
|
-
|
143
|
+
for (var i = 0, len = ranges.length; i < len; ++i) {
|
144
|
+
rangeInfos[i] = saveRange(ranges[i], backward);
|
145
|
+
}
|
132
146
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
147
|
+
// Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
|
148
|
+
// between its markers
|
149
|
+
for (i = len - 1; i >= 0; --i) {
|
150
|
+
range = ranges[i];
|
151
|
+
doc = api.DomRange.getRangeDocument(range);
|
152
|
+
if (range.collapsed) {
|
153
|
+
range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
|
154
|
+
} else {
|
155
|
+
range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
|
156
|
+
range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
|
157
|
+
}
|
143
158
|
}
|
159
|
+
|
160
|
+
return rangeInfos;
|
144
161
|
}
|
145
162
|
|
146
|
-
|
147
|
-
|
163
|
+
function saveSelection(win) {
|
164
|
+
if (!api.isSelectionValid(win)) {
|
165
|
+
module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
|
166
|
+
return null;
|
167
|
+
}
|
168
|
+
var sel = api.getSelection(win);
|
169
|
+
var ranges = sel.getAllRanges();
|
170
|
+
var backward = (ranges.length == 1 && sel.isBackward());
|
148
171
|
|
149
|
-
|
150
|
-
if (!api.isSelectionValid(win)) {
|
151
|
-
module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
|
152
|
-
return null;
|
153
|
-
}
|
154
|
-
var sel = api.getSelection(win);
|
155
|
-
var ranges = sel.getAllRanges();
|
156
|
-
var backward = (ranges.length == 1 && sel.isBackward());
|
172
|
+
var rangeInfos = saveRanges(ranges, backward);
|
157
173
|
|
158
|
-
|
174
|
+
// Ensure current selection is unaffected
|
175
|
+
if (backward) {
|
176
|
+
sel.setSingleRange(ranges[0], backward);
|
177
|
+
} else {
|
178
|
+
sel.setRanges(ranges);
|
179
|
+
}
|
159
180
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
181
|
+
return {
|
182
|
+
win: win,
|
183
|
+
rangeInfos: rangeInfos,
|
184
|
+
restored: false
|
185
|
+
};
|
165
186
|
}
|
166
187
|
|
167
|
-
|
168
|
-
|
169
|
-
rangeInfos: rangeInfos,
|
170
|
-
restored: false
|
171
|
-
};
|
172
|
-
}
|
188
|
+
function restoreRanges(rangeInfos) {
|
189
|
+
var ranges = [];
|
173
190
|
|
174
|
-
|
175
|
-
|
191
|
+
// Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
|
192
|
+
// normalization affecting previously restored ranges.
|
193
|
+
var rangeCount = rangeInfos.length;
|
176
194
|
|
177
|
-
|
178
|
-
|
179
|
-
|
195
|
+
for (var i = rangeCount - 1; i >= 0; i--) {
|
196
|
+
ranges[i] = restoreRange(rangeInfos[i], true);
|
197
|
+
}
|
180
198
|
|
181
|
-
|
182
|
-
ranges[i] = restoreRange(rangeInfos[i], true);
|
199
|
+
return ranges;
|
183
200
|
}
|
184
201
|
|
185
|
-
|
186
|
-
|
202
|
+
function restoreSelection(savedSelection, preserveDirection) {
|
203
|
+
if (!savedSelection.restored) {
|
204
|
+
var rangeInfos = savedSelection.rangeInfos;
|
205
|
+
var sel = api.getSelection(savedSelection.win);
|
206
|
+
var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
|
187
207
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
208
|
+
if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
|
209
|
+
sel.removeAllRanges();
|
210
|
+
sel.addRange(ranges[0], true);
|
211
|
+
} else {
|
212
|
+
sel.setRanges(ranges);
|
213
|
+
}
|
193
214
|
|
194
|
-
|
195
|
-
sel.removeAllRanges();
|
196
|
-
sel.addRange(ranges[0], true);
|
197
|
-
} else {
|
198
|
-
sel.setRanges(ranges);
|
215
|
+
savedSelection.restored = true;
|
199
216
|
}
|
200
|
-
|
201
|
-
savedSelection.restored = true;
|
202
217
|
}
|
203
|
-
}
|
204
218
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
219
|
+
function removeMarkerElement(doc, markerId) {
|
220
|
+
var markerEl = gEBI(markerId, doc);
|
221
|
+
if (markerEl) {
|
222
|
+
removeNode(markerEl);
|
223
|
+
}
|
209
224
|
}
|
210
|
-
}
|
211
225
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
226
|
+
function removeMarkers(savedSelection) {
|
227
|
+
var rangeInfos = savedSelection.rangeInfos;
|
228
|
+
for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
|
229
|
+
rangeInfo = rangeInfos[i];
|
230
|
+
if (rangeInfo.collapsed) {
|
231
|
+
removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
|
232
|
+
} else {
|
233
|
+
removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
|
234
|
+
removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
|
235
|
+
}
|
221
236
|
}
|
222
237
|
}
|
223
|
-
}
|
224
238
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
239
|
+
api.util.extend(api, {
|
240
|
+
saveRange: saveRange,
|
241
|
+
restoreRange: restoreRange,
|
242
|
+
saveRanges: saveRanges,
|
243
|
+
restoreRanges: restoreRanges,
|
244
|
+
saveSelection: saveSelection,
|
245
|
+
restoreSelection: restoreSelection,
|
246
|
+
removeMarkerElement: removeMarkerElement,
|
247
|
+
removeMarkers: removeMarkers
|
248
|
+
});
|
234
249
|
});
|
235
|
-
|
250
|
+
|
251
|
+
return rangy;
|
252
|
+
}, this);
|
@@ -4,292 +4,311 @@
|
|
4
4
|
* cookie or local storage and restore it on the user's next visit to the same page.
|
5
5
|
*
|
6
6
|
* Part of Rangy, a cross-browser JavaScript range and selection library
|
7
|
-
*
|
7
|
+
* https://github.com/timdown/rangy
|
8
8
|
*
|
9
9
|
* Depends on Rangy core.
|
10
10
|
*
|
11
|
-
* Copyright
|
11
|
+
* Copyright 2015, Tim Down
|
12
12
|
* Licensed under the MIT license.
|
13
|
-
* Version: 1.
|
14
|
-
* Build date:
|
13
|
+
* Version: 1.3.1-dev
|
14
|
+
* Build date: 20 May 2015
|
15
15
|
*/
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
if (typeof
|
21
|
-
|
16
|
+
(function(factory, root) {
|
17
|
+
if (typeof define == "function" && define.amd) {
|
18
|
+
// AMD. Register as an anonymous module with a dependency on Rangy.
|
19
|
+
define(["./rangy-core"], factory);
|
20
|
+
} else if (typeof module != "undefined" && typeof exports == "object") {
|
21
|
+
// Node/CommonJS style
|
22
|
+
module.exports = factory( require("rangy") );
|
23
|
+
} else {
|
24
|
+
// No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
|
25
|
+
factory(root.rangy);
|
22
26
|
}
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
if (c < 128) {
|
32
|
-
utf8CharCodes.push(c);
|
33
|
-
} else if (c < 2048) {
|
34
|
-
utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128);
|
35
|
-
} else {
|
36
|
-
utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128);
|
37
|
-
}
|
38
|
-
}
|
39
|
-
return utf8CharCodes;
|
27
|
+
})(function(rangy) {
|
28
|
+
rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) {
|
29
|
+
var UNDEF = "undefined";
|
30
|
+
var util = api.util;
|
31
|
+
|
32
|
+
// encodeURIComponent and decodeURIComponent are required for cookie handling
|
33
|
+
if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) {
|
34
|
+
module.fail("encodeURIComponent and/or decodeURIComponent method is missing");
|
40
35
|
}
|
41
36
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
37
|
+
// Checksum for checking whether range can be serialized
|
38
|
+
var crc32 = (function() {
|
39
|
+
function utf8encode(str) {
|
40
|
+
var utf8CharCodes = [];
|
41
|
+
|
42
|
+
for (var i = 0, len = str.length, c; i < len; ++i) {
|
43
|
+
c = str.charCodeAt(i);
|
44
|
+
if (c < 128) {
|
45
|
+
utf8CharCodes.push(c);
|
46
|
+
} else if (c < 2048) {
|
47
|
+
utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128);
|
52
48
|
} else {
|
53
|
-
|
49
|
+
utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128);
|
54
50
|
}
|
55
51
|
}
|
56
|
-
|
52
|
+
return utf8CharCodes;
|
57
53
|
}
|
58
|
-
return table;
|
59
|
-
}
|
60
54
|
|
61
|
-
|
62
|
-
|
63
|
-
|
55
|
+
var cachedCrcTable = null;
|
56
|
+
|
57
|
+
function buildCRCTable() {
|
58
|
+
var table = [];
|
59
|
+
for (var i = 0, j, crc; i < 256; ++i) {
|
60
|
+
crc = i;
|
61
|
+
j = 8;
|
62
|
+
while (j--) {
|
63
|
+
if ((crc & 1) == 1) {
|
64
|
+
crc = (crc >>> 1) ^ 0xEDB88320;
|
65
|
+
} else {
|
66
|
+
crc >>>= 1;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
table[i] = crc >>> 0;
|
70
|
+
}
|
71
|
+
return table;
|
64
72
|
}
|
65
|
-
return cachedCrcTable;
|
66
|
-
}
|
67
73
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
74
|
+
function getCrcTable() {
|
75
|
+
if (!cachedCrcTable) {
|
76
|
+
cachedCrcTable = buildCRCTable();
|
77
|
+
}
|
78
|
+
return cachedCrcTable;
|
73
79
|
}
|
74
|
-
return (crc ^ -1) >>> 0;
|
75
|
-
};
|
76
|
-
})();
|
77
80
|
|
78
|
-
|
81
|
+
return function(str) {
|
82
|
+
var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable();
|
83
|
+
for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) {
|
84
|
+
y = (crc ^ utf8CharCodes[i]) & 0xFF;
|
85
|
+
crc = (crc >>> 8) ^ crcTable[y];
|
86
|
+
}
|
87
|
+
return (crc ^ -1) >>> 0;
|
88
|
+
};
|
89
|
+
})();
|
79
90
|
|
80
|
-
|
81
|
-
return str.replace(/</g, "<").replace(/>/g, ">");
|
82
|
-
}
|
91
|
+
var dom = api.dom;
|
83
92
|
|
84
|
-
|
85
|
-
|
86
|
-
var nodeType = node.nodeType, children = node.childNodes, childCount = children.length;
|
87
|
-
var nodeInfo = [nodeType, node.nodeName, childCount].join(":");
|
88
|
-
var start = "", end = "";
|
89
|
-
switch (nodeType) {
|
90
|
-
case 3: // Text node
|
91
|
-
start = escapeTextForHtml(node.nodeValue);
|
92
|
-
break;
|
93
|
-
case 8: // Comment
|
94
|
-
start = "<!--" + escapeTextForHtml(node.nodeValue) + "-->";
|
95
|
-
break;
|
96
|
-
default:
|
97
|
-
start = "<" + nodeInfo + ">";
|
98
|
-
end = "</>";
|
99
|
-
break;
|
100
|
-
}
|
101
|
-
if (start) {
|
102
|
-
infoParts.push(start);
|
103
|
-
}
|
104
|
-
for (var i = 0; i < childCount; ++i) {
|
105
|
-
nodeToInfoString(children[i], infoParts);
|
93
|
+
function escapeTextForHtml(str) {
|
94
|
+
return str.replace(/</g, "<").replace(/>/g, ">");
|
106
95
|
}
|
107
|
-
if (end) {
|
108
|
-
infoParts.push(end);
|
109
|
-
}
|
110
|
-
return infoParts;
|
111
|
-
}
|
112
|
-
|
113
|
-
// Creates a string representation of the specified element's contents that is similar to innerHTML but omits all
|
114
|
-
// attributes and comments and includes child node counts. This is done instead of using innerHTML to work around
|
115
|
-
// IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's
|
116
|
-
// innerHTML whenever the user changes an input within the element.
|
117
|
-
function getElementChecksum(el) {
|
118
|
-
var info = nodeToInfoString(el).join("");
|
119
|
-
return crc32(info).toString(16);
|
120
|
-
}
|
121
96
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
97
|
+
function nodeToInfoString(node, infoParts) {
|
98
|
+
infoParts = infoParts || [];
|
99
|
+
var nodeType = node.nodeType, children = node.childNodes, childCount = children.length;
|
100
|
+
var nodeInfo = [nodeType, node.nodeName, childCount].join(":");
|
101
|
+
var start = "", end = "";
|
102
|
+
switch (nodeType) {
|
103
|
+
case 3: // Text node
|
104
|
+
start = escapeTextForHtml(node.nodeValue);
|
105
|
+
break;
|
106
|
+
case 8: // Comment
|
107
|
+
start = "<!--" + escapeTextForHtml(node.nodeValue) + "-->";
|
108
|
+
break;
|
109
|
+
default:
|
110
|
+
start = "<" + nodeInfo + ">";
|
111
|
+
end = "</>";
|
112
|
+
break;
|
113
|
+
}
|
114
|
+
if (start) {
|
115
|
+
infoParts.push(start);
|
116
|
+
}
|
117
|
+
for (var i = 0; i < childCount; ++i) {
|
118
|
+
nodeToInfoString(children[i], infoParts);
|
119
|
+
}
|
120
|
+
if (end) {
|
121
|
+
infoParts.push(end);
|
122
|
+
}
|
123
|
+
return infoParts;
|
128
124
|
}
|
129
|
-
return pathParts.join("/") + ":" + offset;
|
130
|
-
}
|
131
125
|
|
132
|
-
|
133
|
-
|
134
|
-
|
126
|
+
// Creates a string representation of the specified element's contents that is similar to innerHTML but omits all
|
127
|
+
// attributes and comments and includes child node counts. This is done instead of using innerHTML to work around
|
128
|
+
// IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's
|
129
|
+
// innerHTML whenever the user changes an input within the element.
|
130
|
+
function getElementChecksum(el) {
|
131
|
+
var info = nodeToInfoString(el).join("");
|
132
|
+
return crc32(info).toString(16);
|
135
133
|
}
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
node = node.childNodes[nodeIndex];
|
144
|
-
} else {
|
145
|
-
throw module.createError("deserializePosition() failed: node " + dom.inspectNode(node) +
|
146
|
-
" has no child with index " + nodeIndex + ", " + i);
|
134
|
+
|
135
|
+
function serializePosition(node, offset, rootNode) {
|
136
|
+
var pathParts = [], n = node;
|
137
|
+
rootNode = rootNode || dom.getDocument(node).documentElement;
|
138
|
+
while (n && n != rootNode) {
|
139
|
+
pathParts.push(dom.getNodeIndex(n, true));
|
140
|
+
n = n.parentNode;
|
147
141
|
}
|
142
|
+
return pathParts.join("/") + ":" + offset;
|
148
143
|
}
|
149
144
|
|
150
|
-
|
151
|
-
|
145
|
+
function deserializePosition(serialized, rootNode, doc) {
|
146
|
+
if (!rootNode) {
|
147
|
+
rootNode = (doc || document).documentElement;
|
148
|
+
}
|
149
|
+
var parts = serialized.split(":");
|
150
|
+
var node = rootNode;
|
151
|
+
var nodeIndices = parts[0] ? parts[0].split("/") : [], i = nodeIndices.length, nodeIndex;
|
152
|
+
|
153
|
+
while (i--) {
|
154
|
+
nodeIndex = parseInt(nodeIndices[i], 10);
|
155
|
+
if (nodeIndex < node.childNodes.length) {
|
156
|
+
node = node.childNodes[nodeIndex];
|
157
|
+
} else {
|
158
|
+
throw module.createError("deserializePosition() failed: node " + dom.inspectNode(node) +
|
159
|
+
" has no child with index " + nodeIndex + ", " + i);
|
160
|
+
}
|
161
|
+
}
|
152
162
|
|
153
|
-
|
154
|
-
rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement;
|
155
|
-
if (!dom.isOrIsAncestorOf(rootNode, range.commonAncestorContainer)) {
|
156
|
-
throw module.createError("serializeRange(): range " + range.inspect() +
|
157
|
-
" is not wholly contained within specified root node " + dom.inspectNode(rootNode));
|
163
|
+
return new dom.DomPosition(node, parseInt(parts[1], 10));
|
158
164
|
}
|
159
|
-
var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," +
|
160
|
-
serializePosition(range.endContainer, range.endOffset, rootNode);
|
161
|
-
if (!omitChecksum) {
|
162
|
-
serialized += "{" + getElementChecksum(rootNode) + "}";
|
163
|
-
}
|
164
|
-
return serialized;
|
165
|
-
}
|
166
165
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
throw module.createError("deserializeRange(): checksums of serialized range root node (" + checksum +
|
180
|
-
") and target root node (" + rootNodeChecksum + ") do not match");
|
166
|
+
function serializeRange(range, omitChecksum, rootNode) {
|
167
|
+
rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement;
|
168
|
+
if (!dom.isOrIsAncestorOf(rootNode, range.commonAncestorContainer)) {
|
169
|
+
throw module.createError("serializeRange(): range " + range.inspect() +
|
170
|
+
" is not wholly contained within specified root node " + dom.inspectNode(rootNode));
|
171
|
+
}
|
172
|
+
var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," +
|
173
|
+
serializePosition(range.endContainer, range.endOffset, rootNode);
|
174
|
+
if (!omitChecksum) {
|
175
|
+
serialized += "{" + getElementChecksum(rootNode) + "}";
|
176
|
+
}
|
177
|
+
return serialized;
|
181
178
|
}
|
182
|
-
var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc);
|
183
|
-
var range = api.createRange(doc);
|
184
|
-
range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
|
185
|
-
return range;
|
186
|
-
}
|
187
179
|
|
188
|
-
|
189
|
-
if (!rootNode) {
|
190
|
-
rootNode = (doc || document).documentElement;
|
191
|
-
}
|
192
|
-
var result = deserializeRegex.exec(serialized);
|
193
|
-
var checksum = result[3];
|
194
|
-
return !checksum || checksum === getElementChecksum(rootNode);
|
195
|
-
}
|
180
|
+
var deserializeRegex = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/;
|
196
181
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
182
|
+
function deserializeRange(serialized, rootNode, doc) {
|
183
|
+
if (rootNode) {
|
184
|
+
doc = doc || dom.getDocument(rootNode);
|
185
|
+
} else {
|
186
|
+
doc = doc || document;
|
187
|
+
rootNode = doc.documentElement;
|
188
|
+
}
|
189
|
+
var result = deserializeRegex.exec(serialized);
|
190
|
+
var checksum = result[4];
|
191
|
+
if (checksum) {
|
192
|
+
var rootNodeChecksum = getElementChecksum(rootNode);
|
193
|
+
if (checksum !== rootNodeChecksum) {
|
194
|
+
throw module.createError("deserializeRange(): checksums of serialized range root node (" + checksum +
|
195
|
+
") and target root node (" + rootNodeChecksum + ") do not match");
|
196
|
+
}
|
197
|
+
}
|
198
|
+
var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc);
|
199
|
+
var range = api.createRange(doc);
|
200
|
+
range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
|
201
|
+
return range;
|
202
202
|
}
|
203
|
-
return serializedRanges.join("|");
|
204
|
-
}
|
205
203
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
204
|
+
function canDeserializeRange(serialized, rootNode, doc) {
|
205
|
+
if (!rootNode) {
|
206
|
+
rootNode = (doc || document).documentElement;
|
207
|
+
}
|
208
|
+
var result = deserializeRegex.exec(serialized);
|
209
|
+
var checksum = result[3];
|
210
|
+
return !checksum || checksum === getElementChecksum(rootNode);
|
212
211
|
}
|
213
|
-
var serializedRanges = serialized.split("|");
|
214
|
-
var sel = api.getSelection(win);
|
215
|
-
var ranges = [];
|
216
212
|
|
217
|
-
|
218
|
-
|
213
|
+
function serializeSelection(selection, omitChecksum, rootNode) {
|
214
|
+
selection = api.getSelection(selection);
|
215
|
+
var ranges = selection.getAllRanges(), serializedRanges = [];
|
216
|
+
for (var i = 0, len = ranges.length; i < len; ++i) {
|
217
|
+
serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode);
|
218
|
+
}
|
219
|
+
return serializedRanges.join("|");
|
219
220
|
}
|
220
|
-
sel.setRanges(ranges);
|
221
|
-
|
222
|
-
return sel;
|
223
|
-
}
|
224
221
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
222
|
+
function deserializeSelection(serialized, rootNode, win) {
|
223
|
+
if (rootNode) {
|
224
|
+
win = win || dom.getWindow(rootNode);
|
225
|
+
} else {
|
226
|
+
win = win || window;
|
227
|
+
rootNode = win.document.documentElement;
|
228
|
+
}
|
229
|
+
var serializedRanges = serialized.split("|");
|
230
|
+
var sel = api.getSelection(win);
|
231
|
+
var ranges = [];
|
234
232
|
|
235
|
-
|
236
|
-
|
237
|
-
return false;
|
233
|
+
for (var i = 0, len = serializedRanges.length; i < len; ++i) {
|
234
|
+
ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document);
|
238
235
|
}
|
239
|
-
|
236
|
+
sel.setRanges(ranges);
|
240
237
|
|
241
|
-
|
242
|
-
|
238
|
+
return sel;
|
239
|
+
}
|
243
240
|
|
244
|
-
|
241
|
+
function canDeserializeSelection(serialized, rootNode, win) {
|
242
|
+
var doc;
|
243
|
+
if (rootNode) {
|
244
|
+
doc = win ? win.document : dom.getDocument(rootNode);
|
245
|
+
} else {
|
246
|
+
win = win || window;
|
247
|
+
rootNode = win.document.documentElement;
|
248
|
+
}
|
249
|
+
var serializedRanges = serialized.split("|");
|
245
250
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
nameVal = parts[i].split("=");
|
250
|
-
if (nameVal[0].replace(/^\s+/, "") == cookieName) {
|
251
|
-
val = nameVal[1];
|
252
|
-
if (val) {
|
253
|
-
return decodeURIComponent(val.replace(/\s+$/, ""));
|
251
|
+
for (var i = 0, len = serializedRanges.length; i < len; ++i) {
|
252
|
+
if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) {
|
253
|
+
return false;
|
254
254
|
}
|
255
255
|
}
|
256
|
-
}
|
257
|
-
return null;
|
258
|
-
}
|
259
256
|
|
260
|
-
|
261
|
-
win = win || window;
|
262
|
-
var serialized = getSerializedSelectionFromCookie(win.document.cookie);
|
263
|
-
if (serialized) {
|
264
|
-
deserializeSelection(serialized, win.doc);
|
257
|
+
return true;
|
265
258
|
}
|
266
|
-
}
|
267
|
-
|
268
|
-
function saveSelectionCookie(win, props) {
|
269
|
-
win = win || window;
|
270
|
-
props = (typeof props == "object") ? props : {};
|
271
|
-
var expires = props.expires ? ";expires=" + props.expires.toUTCString() : "";
|
272
|
-
var path = props.path ? ";path=" + props.path : "";
|
273
|
-
var domain = props.domain ? ";domain=" + props.domain : "";
|
274
|
-
var secure = props.secure ? ";secure" : "";
|
275
|
-
var serialized = serializeSelection(api.getSelection(win));
|
276
|
-
win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure;
|
277
|
-
}
|
278
259
|
|
279
|
-
|
280
|
-
api.deserializePosition = deserializePosition;
|
260
|
+
var cookieName = "rangySerializedSelection";
|
281
261
|
|
282
|
-
|
283
|
-
|
284
|
-
|
262
|
+
function getSerializedSelectionFromCookie(cookie) {
|
263
|
+
var parts = cookie.split(/[;,]/);
|
264
|
+
for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) {
|
265
|
+
nameVal = parts[i].split("=");
|
266
|
+
if (nameVal[0].replace(/^\s+/, "") == cookieName) {
|
267
|
+
val = nameVal[1];
|
268
|
+
if (val) {
|
269
|
+
return decodeURIComponent(val.replace(/\s+$/, ""));
|
270
|
+
}
|
271
|
+
}
|
272
|
+
}
|
273
|
+
return null;
|
274
|
+
}
|
285
275
|
|
286
|
-
|
287
|
-
|
288
|
-
|
276
|
+
function restoreSelectionFromCookie(win) {
|
277
|
+
win = win || window;
|
278
|
+
var serialized = getSerializedSelectionFromCookie(win.document.cookie);
|
279
|
+
if (serialized) {
|
280
|
+
deserializeSelection(serialized, win.doc);
|
281
|
+
}
|
282
|
+
}
|
289
283
|
|
290
|
-
|
291
|
-
|
284
|
+
function saveSelectionCookie(win, props) {
|
285
|
+
win = win || window;
|
286
|
+
props = (typeof props == "object") ? props : {};
|
287
|
+
var expires = props.expires ? ";expires=" + props.expires.toUTCString() : "";
|
288
|
+
var path = props.path ? ";path=" + props.path : "";
|
289
|
+
var domain = props.domain ? ";domain=" + props.domain : "";
|
290
|
+
var secure = props.secure ? ";secure" : "";
|
291
|
+
var serialized = serializeSelection(api.getSelection(win));
|
292
|
+
win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure;
|
293
|
+
}
|
292
294
|
|
293
|
-
|
294
|
-
|
295
|
-
|
295
|
+
util.extend(api, {
|
296
|
+
serializePosition: serializePosition,
|
297
|
+
deserializePosition: deserializePosition,
|
298
|
+
serializeRange: serializeRange,
|
299
|
+
deserializeRange: deserializeRange,
|
300
|
+
canDeserializeRange: canDeserializeRange,
|
301
|
+
serializeSelection: serializeSelection,
|
302
|
+
deserializeSelection: deserializeSelection,
|
303
|
+
canDeserializeSelection: canDeserializeSelection,
|
304
|
+
restoreSelectionFromCookie: restoreSelectionFromCookie,
|
305
|
+
saveSelectionCookie: saveSelectionCookie,
|
306
|
+
getElementChecksum: getElementChecksum,
|
307
|
+
nodeToInfoString: nodeToInfoString
|
308
|
+
});
|
309
|
+
|
310
|
+
util.crc32 = crc32;
|
311
|
+
});
|
312
|
+
|
313
|
+
return rangy;
|
314
|
+
}, this);
|