rangy-rails 1.3alpha.772.0

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