Property changes on: trunk/phase3/maintenance/archives/patch-af_page_user_token-index.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 1 | + native |
Property changes on: trunk/extensions/Example/Example.i18n.php |
___________________________________________________________________ |
Added: svn:eol-style |
2 | 2 | + native |
Property changes on: trunk/extensions/Example/Example.php |
___________________________________________________________________ |
Added: svn:eol-style |
3 | 3 | + native |
Property changes on: trunk/extensions/Example/Example.body.php |
___________________________________________________________________ |
Added: svn:eol-style |
4 | 4 | + native |
Property changes on: trunk/extensions/Example/README |
___________________________________________________________________ |
Added: svn:eol-style |
5 | 5 | + native |
Property changes on: trunk/extensions/ArticleCreationWorkflow/INSTALL |
___________________________________________________________________ |
Added: svn:eol-style |
6 | 6 | + native |
Property changes on: trunk/extensions/ArticleCreationWorkflow/README |
___________________________________________________________________ |
Added: svn:eol-style |
7 | 7 | + native |
Index: trunk/extensions/VisualEditor/modules/rangy/rangy-selectionsaverestore.js |
— | — | @@ -1,195 +1,195 @@ |
2 | | -/**
|
3 | | - * @license Selection save and restore module for Rangy.
|
4 | | - * Saves and restores user selections using marker invisible elements in the DOM.
|
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 2011, Tim Down
|
12 | | - * Licensed under the MIT license.
|
13 | | - * Version: 1.2.2
|
14 | | - * Build date: 13 November 2011
|
15 | | - */
|
16 | | -rangy.createModule("SaveRestore", function(api, module) {
|
17 | | - api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
|
18 | | -
|
19 | | - var dom = api.dom;
|
20 | | -
|
21 | | - var markerTextChar = "\ufeff";
|
22 | | -
|
23 | | - function gEBI(id, doc) {
|
24 | | - return (doc || document).getElementById(id);
|
25 | | - }
|
26 | | -
|
27 | | - function insertRangeBoundaryMarker(range, atStart) {
|
28 | | - var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
|
29 | | - var markerEl;
|
30 | | - var doc = dom.getDocument(range.startContainer);
|
31 | | -
|
32 | | - // Clone the Range and collapse to the appropriate boundary point
|
33 | | - var boundaryRange = range.cloneRange();
|
34 | | - boundaryRange.collapse(atStart);
|
35 | | -
|
36 | | - // Create the marker element containing a single invisible character using DOM methods and insert it
|
37 | | - markerEl = doc.createElement("span");
|
38 | | - markerEl.id = markerId;
|
39 | | - markerEl.style.lineHeight = "0";
|
40 | | - markerEl.style.display = "none";
|
41 | | - markerEl.className = "rangySelectionBoundary";
|
42 | | - markerEl.appendChild(doc.createTextNode(markerTextChar));
|
43 | | -
|
44 | | - boundaryRange.insertNode(markerEl);
|
45 | | - boundaryRange.detach();
|
46 | | - return markerEl;
|
47 | | - }
|
48 | | -
|
49 | | - function setRangeBoundary(doc, range, markerId, atStart) {
|
50 | | - var markerEl = gEBI(markerId, doc);
|
51 | | - if (markerEl) {
|
52 | | - range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
|
53 | | - markerEl.parentNode.removeChild(markerEl);
|
54 | | - } else {
|
55 | | - module.warn("Marker element has been removed. Cannot restore selection.");
|
56 | | - }
|
57 | | - }
|
58 | | -
|
59 | | - function compareRanges(r1, r2) {
|
60 | | - return r2.compareBoundaryPoints(r1.START_TO_START, r1);
|
61 | | - }
|
62 | | -
|
63 | | - function saveSelection(win) {
|
64 | | - win = win || window;
|
65 | | - var doc = win.document;
|
66 | | - if (!api.isSelectionValid(win)) {
|
67 | | - module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
|
68 | | - return;
|
69 | | - }
|
70 | | - var sel = api.getSelection(win);
|
71 | | - var ranges = sel.getAllRanges();
|
72 | | - var rangeInfos = [], startEl, endEl, range;
|
73 | | -
|
74 | | - // Order the ranges by position within the DOM, latest first
|
75 | | - ranges.sort(compareRanges);
|
76 | | -
|
77 | | - for (var i = 0, len = ranges.length; i < len; ++i) {
|
78 | | - range = ranges[i];
|
79 | | - if (range.collapsed) {
|
80 | | - endEl = insertRangeBoundaryMarker(range, false);
|
81 | | - rangeInfos.push({
|
82 | | - markerId: endEl.id,
|
83 | | - collapsed: true
|
84 | | - });
|
85 | | - } else {
|
86 | | - endEl = insertRangeBoundaryMarker(range, false);
|
87 | | - startEl = insertRangeBoundaryMarker(range, true);
|
88 | | -
|
89 | | - rangeInfos[i] = {
|
90 | | - startMarkerId: startEl.id,
|
91 | | - endMarkerId: endEl.id,
|
92 | | - collapsed: false,
|
93 | | - backwards: ranges.length == 1 && sel.isBackwards()
|
94 | | - };
|
95 | | - }
|
96 | | - }
|
97 | | -
|
98 | | - // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
|
99 | | - // between its markers
|
100 | | - for (i = len - 1; i >= 0; --i) {
|
101 | | - range = ranges[i];
|
102 | | - if (range.collapsed) {
|
103 | | - range.collapseBefore(gEBI(rangeInfos[i].markerId, doc));
|
104 | | - } else {
|
105 | | - range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
|
106 | | - range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
|
107 | | - }
|
108 | | - }
|
109 | | -
|
110 | | - // Ensure current selection is unaffected
|
111 | | - sel.setRanges(ranges);
|
112 | | - return {
|
113 | | - win: win,
|
114 | | - doc: doc,
|
115 | | - rangeInfos: rangeInfos,
|
116 | | - restored: false
|
117 | | - };
|
118 | | - }
|
119 | | -
|
120 | | - function restoreSelection(savedSelection, preserveDirection) {
|
121 | | - if (!savedSelection.restored) {
|
122 | | - var rangeInfos = savedSelection.rangeInfos;
|
123 | | - var sel = api.getSelection(savedSelection.win);
|
124 | | - var ranges = [];
|
125 | | -
|
126 | | - // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
|
127 | | - // normalization affecting previously restored ranges.
|
128 | | - for (var len = rangeInfos.length, i = len - 1, rangeInfo, range; i >= 0; --i) {
|
129 | | - rangeInfo = rangeInfos[i];
|
130 | | - range = api.createRange(savedSelection.doc);
|
131 | | - if (rangeInfo.collapsed) {
|
132 | | - var markerEl = gEBI(rangeInfo.markerId, savedSelection.doc);
|
133 | | - if (markerEl) {
|
134 | | - markerEl.style.display = "inline";
|
135 | | - var previousNode = markerEl.previousSibling;
|
136 | | -
|
137 | | - // Workaround for issue 17
|
138 | | - if (previousNode && previousNode.nodeType == 3) {
|
139 | | - markerEl.parentNode.removeChild(markerEl);
|
140 | | - range.collapseToPoint(previousNode, previousNode.length);
|
141 | | - } else {
|
142 | | - range.collapseBefore(markerEl);
|
143 | | - markerEl.parentNode.removeChild(markerEl);
|
144 | | - }
|
145 | | - } else {
|
146 | | - module.warn("Marker element has been removed. Cannot restore selection.");
|
147 | | - }
|
148 | | - } else {
|
149 | | - setRangeBoundary(savedSelection.doc, range, rangeInfo.startMarkerId, true);
|
150 | | - setRangeBoundary(savedSelection.doc, range, rangeInfo.endMarkerId, false);
|
151 | | - }
|
152 | | -
|
153 | | - // Normalizing range boundaries is only viable if the selection contains only one range. For example,
|
154 | | - // if the selection contained two ranges that were both contained within the same single text node,
|
155 | | - // both would alter the same text node when restoring and break the other range.
|
156 | | - if (len == 1) {
|
157 | | - range.normalizeBoundaries();
|
158 | | - }
|
159 | | - ranges[i] = range;
|
160 | | - }
|
161 | | - if (len == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backwards) {
|
162 | | - sel.removeAllRanges();
|
163 | | - sel.addRange(ranges[0], true);
|
164 | | - } else {
|
165 | | - sel.setRanges(ranges);
|
166 | | - }
|
167 | | -
|
168 | | - savedSelection.restored = true;
|
169 | | - }
|
170 | | - }
|
171 | | -
|
172 | | - function removeMarkerElement(doc, markerId) {
|
173 | | - var markerEl = gEBI(markerId, doc);
|
174 | | - if (markerEl) {
|
175 | | - markerEl.parentNode.removeChild(markerEl);
|
176 | | - }
|
177 | | - }
|
178 | | -
|
179 | | - function removeMarkers(savedSelection) {
|
180 | | - var rangeInfos = savedSelection.rangeInfos;
|
181 | | - for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
|
182 | | - rangeInfo = rangeInfos[i];
|
183 | | - if (rangeInfo.collapsed) {
|
184 | | - removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
|
185 | | - } else {
|
186 | | - removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
|
187 | | - removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
|
188 | | - }
|
189 | | - }
|
190 | | - }
|
191 | | -
|
192 | | - api.saveSelection = saveSelection;
|
193 | | - api.restoreSelection = restoreSelection;
|
194 | | - api.removeMarkerElement = removeMarkerElement;
|
195 | | - api.removeMarkers = removeMarkers;
|
196 | | -});
|
| 2 | +/** |
| 3 | + * @license Selection save and restore module for Rangy. |
| 4 | + * Saves and restores user selections using marker invisible elements in the DOM. |
| 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 2011, Tim Down |
| 12 | + * Licensed under the MIT license. |
| 13 | + * Version: 1.2.2 |
| 14 | + * Build date: 13 November 2011 |
| 15 | + */ |
| 16 | +rangy.createModule("SaveRestore", function(api, module) { |
| 17 | + api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] ); |
| 18 | + |
| 19 | + var dom = api.dom; |
| 20 | + |
| 21 | + var markerTextChar = "\ufeff"; |
| 22 | + |
| 23 | + function gEBI(id, doc) { |
| 24 | + return (doc || document).getElementById(id); |
| 25 | + } |
| 26 | + |
| 27 | + function insertRangeBoundaryMarker(range, atStart) { |
| 28 | + var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2); |
| 29 | + var markerEl; |
| 30 | + var doc = dom.getDocument(range.startContainer); |
| 31 | + |
| 32 | + // Clone the Range and collapse to the appropriate boundary point |
| 33 | + var boundaryRange = range.cloneRange(); |
| 34 | + boundaryRange.collapse(atStart); |
| 35 | + |
| 36 | + // Create the marker element containing a single invisible character using DOM methods and insert it |
| 37 | + markerEl = doc.createElement("span"); |
| 38 | + markerEl.id = markerId; |
| 39 | + markerEl.style.lineHeight = "0"; |
| 40 | + markerEl.style.display = "none"; |
| 41 | + markerEl.className = "rangySelectionBoundary"; |
| 42 | + markerEl.appendChild(doc.createTextNode(markerTextChar)); |
| 43 | + |
| 44 | + boundaryRange.insertNode(markerEl); |
| 45 | + boundaryRange.detach(); |
| 46 | + return markerEl; |
| 47 | + } |
| 48 | + |
| 49 | + function setRangeBoundary(doc, range, markerId, atStart) { |
| 50 | + var markerEl = gEBI(markerId, doc); |
| 51 | + if (markerEl) { |
| 52 | + range[atStart ? "setStartBefore" : "setEndBefore"](markerEl); |
| 53 | + markerEl.parentNode.removeChild(markerEl); |
| 54 | + } else { |
| 55 | + module.warn("Marker element has been removed. Cannot restore selection."); |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + function compareRanges(r1, r2) { |
| 60 | + return r2.compareBoundaryPoints(r1.START_TO_START, r1); |
| 61 | + } |
| 62 | + |
| 63 | + function saveSelection(win) { |
| 64 | + win = win || window; |
| 65 | + var doc = win.document; |
| 66 | + if (!api.isSelectionValid(win)) { |
| 67 | + module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."); |
| 68 | + return; |
| 69 | + } |
| 70 | + var sel = api.getSelection(win); |
| 71 | + var ranges = sel.getAllRanges(); |
| 72 | + var rangeInfos = [], startEl, endEl, range; |
| 73 | + |
| 74 | + // Order the ranges by position within the DOM, latest first |
| 75 | + ranges.sort(compareRanges); |
| 76 | + |
| 77 | + for (var i = 0, len = ranges.length; i < len; ++i) { |
| 78 | + range = ranges[i]; |
| 79 | + if (range.collapsed) { |
| 80 | + endEl = insertRangeBoundaryMarker(range, false); |
| 81 | + rangeInfos.push({ |
| 82 | + markerId: endEl.id, |
| 83 | + collapsed: true |
| 84 | + }); |
| 85 | + } else { |
| 86 | + endEl = insertRangeBoundaryMarker(range, false); |
| 87 | + startEl = insertRangeBoundaryMarker(range, true); |
| 88 | + |
| 89 | + rangeInfos[i] = { |
| 90 | + startMarkerId: startEl.id, |
| 91 | + endMarkerId: endEl.id, |
| 92 | + collapsed: false, |
| 93 | + backwards: ranges.length == 1 && sel.isBackwards() |
| 94 | + }; |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie |
| 99 | + // between its markers |
| 100 | + for (i = len - 1; i >= 0; --i) { |
| 101 | + range = ranges[i]; |
| 102 | + if (range.collapsed) { |
| 103 | + range.collapseBefore(gEBI(rangeInfos[i].markerId, doc)); |
| 104 | + } else { |
| 105 | + range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc)); |
| 106 | + range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc)); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + // Ensure current selection is unaffected |
| 111 | + sel.setRanges(ranges); |
| 112 | + return { |
| 113 | + win: win, |
| 114 | + doc: doc, |
| 115 | + rangeInfos: rangeInfos, |
| 116 | + restored: false |
| 117 | + }; |
| 118 | + } |
| 119 | + |
| 120 | + function restoreSelection(savedSelection, preserveDirection) { |
| 121 | + if (!savedSelection.restored) { |
| 122 | + var rangeInfos = savedSelection.rangeInfos; |
| 123 | + var sel = api.getSelection(savedSelection.win); |
| 124 | + var ranges = []; |
| 125 | + |
| 126 | + // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid |
| 127 | + // normalization affecting previously restored ranges. |
| 128 | + for (var len = rangeInfos.length, i = len - 1, rangeInfo, range; i >= 0; --i) { |
| 129 | + rangeInfo = rangeInfos[i]; |
| 130 | + range = api.createRange(savedSelection.doc); |
| 131 | + if (rangeInfo.collapsed) { |
| 132 | + var markerEl = gEBI(rangeInfo.markerId, savedSelection.doc); |
| 133 | + if (markerEl) { |
| 134 | + markerEl.style.display = "inline"; |
| 135 | + var previousNode = markerEl.previousSibling; |
| 136 | + |
| 137 | + // Workaround for issue 17 |
| 138 | + if (previousNode && previousNode.nodeType == 3) { |
| 139 | + markerEl.parentNode.removeChild(markerEl); |
| 140 | + range.collapseToPoint(previousNode, previousNode.length); |
| 141 | + } else { |
| 142 | + range.collapseBefore(markerEl); |
| 143 | + markerEl.parentNode.removeChild(markerEl); |
| 144 | + } |
| 145 | + } else { |
| 146 | + module.warn("Marker element has been removed. Cannot restore selection."); |
| 147 | + } |
| 148 | + } else { |
| 149 | + setRangeBoundary(savedSelection.doc, range, rangeInfo.startMarkerId, true); |
| 150 | + setRangeBoundary(savedSelection.doc, range, rangeInfo.endMarkerId, false); |
| 151 | + } |
| 152 | + |
| 153 | + // Normalizing range boundaries is only viable if the selection contains only one range. For example, |
| 154 | + // if the selection contained two ranges that were both contained within the same single text node, |
| 155 | + // both would alter the same text node when restoring and break the other range. |
| 156 | + if (len == 1) { |
| 157 | + range.normalizeBoundaries(); |
| 158 | + } |
| 159 | + ranges[i] = range; |
| 160 | + } |
| 161 | + if (len == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backwards) { |
| 162 | + sel.removeAllRanges(); |
| 163 | + sel.addRange(ranges[0], true); |
| 164 | + } else { |
| 165 | + sel.setRanges(ranges); |
| 166 | + } |
| 167 | + |
| 168 | + savedSelection.restored = true; |
| 169 | + } |
| 170 | + } |
| 171 | + |
| 172 | + function removeMarkerElement(doc, markerId) { |
| 173 | + var markerEl = gEBI(markerId, doc); |
| 174 | + if (markerEl) { |
| 175 | + markerEl.parentNode.removeChild(markerEl); |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + function removeMarkers(savedSelection) { |
| 180 | + var rangeInfos = savedSelection.rangeInfos; |
| 181 | + for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) { |
| 182 | + rangeInfo = rangeInfos[i]; |
| 183 | + if (rangeInfo.collapsed) { |
| 184 | + removeMarkerElement(savedSelection.doc, rangeInfo.markerId); |
| 185 | + } else { |
| 186 | + removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId); |
| 187 | + removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId); |
| 188 | + } |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + api.saveSelection = saveSelection; |
| 193 | + api.restoreSelection = restoreSelection; |
| 194 | + api.removeMarkerElement = removeMarkerElement; |
| 195 | + api.removeMarkers = removeMarkers; |
| 196 | +}); |
Property changes on: trunk/extensions/VisualEditor/modules/rangy/rangy-selectionsaverestore.js |
___________________________________________________________________ |
Added: svn:eol-style |
197 | 197 | + native |
Index: trunk/extensions/VisualEditor/modules/rangy/rangy-serializer.js |
— | — | @@ -1,300 +1,300 @@ |
2 | | -/**
|
3 | | - * @license Serializer module for Rangy.
|
4 | | - * Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a
|
5 | | - * cookie or local storage and restore it on the user's next visit to the same page.
|
6 | | - *
|
7 | | - * Part of Rangy, a cross-browser JavaScript range and selection library
|
8 | | - * http://code.google.com/p/rangy/
|
9 | | - *
|
10 | | - * Depends on Rangy core.
|
11 | | - *
|
12 | | - * Copyright 2011, Tim Down
|
13 | | - * Licensed under the MIT license.
|
14 | | - * Version: 1.2.2
|
15 | | - * Build date: 13 November 2011
|
16 | | - */
|
17 | | -rangy.createModule("Serializer", function(api, module) {
|
18 | | - api.requireModules( ["WrappedSelection", "WrappedRange"] );
|
19 | | - var UNDEF = "undefined";
|
20 | | -
|
21 | | - // encodeURIComponent and decodeURIComponent are required for cookie handling
|
22 | | - if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) {
|
23 | | - module.fail("Global object is missing encodeURIComponent and/or decodeURIComponent method");
|
24 | | - }
|
25 | | -
|
26 | | - // Checksum for checking whether range can be serialized
|
27 | | - var crc32 = (function() {
|
28 | | - function utf8encode(str) {
|
29 | | - var utf8CharCodes = [];
|
30 | | -
|
31 | | - for (var i = 0, len = str.length, c; i < len; ++i) {
|
32 | | - c = str.charCodeAt(i);
|
33 | | - if (c < 128) {
|
34 | | - utf8CharCodes.push(c);
|
35 | | - } else if (c < 2048) {
|
36 | | - utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128);
|
37 | | - } else {
|
38 | | - utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128);
|
39 | | - }
|
40 | | - }
|
41 | | - return utf8CharCodes;
|
42 | | - }
|
43 | | -
|
44 | | - var cachedCrcTable = null;
|
45 | | -
|
46 | | - function buildCRCTable() {
|
47 | | - var table = [];
|
48 | | - for (var i = 0, j, crc; i < 256; ++i) {
|
49 | | - crc = i;
|
50 | | - j = 8;
|
51 | | - while (j--) {
|
52 | | - if ((crc & 1) == 1) {
|
53 | | - crc = (crc >>> 1) ^ 0xEDB88320;
|
54 | | - } else {
|
55 | | - crc >>>= 1;
|
56 | | - }
|
57 | | - }
|
58 | | - table[i] = crc >>> 0;
|
59 | | - }
|
60 | | - return table;
|
61 | | - }
|
62 | | -
|
63 | | - function getCrcTable() {
|
64 | | - if (!cachedCrcTable) {
|
65 | | - cachedCrcTable = buildCRCTable();
|
66 | | - }
|
67 | | - return cachedCrcTable;
|
68 | | - }
|
69 | | -
|
70 | | - return function(str) {
|
71 | | - var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable();
|
72 | | - for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) {
|
73 | | - y = (crc ^ utf8CharCodes[i]) & 0xFF;
|
74 | | - crc = (crc >>> 8) ^ crcTable[y];
|
75 | | - }
|
76 | | - return (crc ^ -1) >>> 0;
|
77 | | - };
|
78 | | - })();
|
79 | | -
|
80 | | - var dom = api.dom;
|
81 | | -
|
82 | | - function escapeTextForHtml(str) {
|
83 | | - return str.replace(/</g, "<").replace(/>/g, ">");
|
84 | | - }
|
85 | | -
|
86 | | - function nodeToInfoString(node, infoParts) {
|
87 | | - infoParts = infoParts || [];
|
88 | | - var nodeType = node.nodeType, children = node.childNodes, childCount = children.length;
|
89 | | - var nodeInfo = [nodeType, node.nodeName, childCount].join(":");
|
90 | | - var start = "", end = "";
|
91 | | - switch (nodeType) {
|
92 | | - case 3: // Text node
|
93 | | - start = escapeTextForHtml(node.nodeValue);
|
94 | | - break;
|
95 | | - case 8: // Comment
|
96 | | - start = "<!--" + escapeTextForHtml(node.nodeValue) + "-->";
|
97 | | - break;
|
98 | | - default:
|
99 | | - start = "<" + nodeInfo + ">";
|
100 | | - end = "</>";
|
101 | | - break;
|
102 | | - }
|
103 | | - if (start) {
|
104 | | - infoParts.push(start);
|
105 | | - }
|
106 | | - for (var i = 0; i < childCount; ++i) {
|
107 | | - nodeToInfoString(children[i], infoParts);
|
108 | | - }
|
109 | | - if (end) {
|
110 | | - infoParts.push(end);
|
111 | | - }
|
112 | | - return infoParts;
|
113 | | - }
|
114 | | -
|
115 | | - // Creates a string representation of the specified element's contents that is similar to innerHTML but omits all
|
116 | | - // attributes and comments and includes child node counts. This is done instead of using innerHTML to work around
|
117 | | - // IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's
|
118 | | - // innerHTML whenever the user changes an input within the element.
|
119 | | - function getElementChecksum(el) {
|
120 | | - var info = nodeToInfoString(el).join("");
|
121 | | - return crc32(info).toString(16);
|
122 | | - }
|
123 | | -
|
124 | | - function serializePosition(node, offset, rootNode) {
|
125 | | - var pathBits = [], n = node;
|
126 | | - rootNode = rootNode || dom.getDocument(node).documentElement;
|
127 | | - while (n && n != rootNode) {
|
128 | | - pathBits.push(dom.getNodeIndex(n, true));
|
129 | | - n = n.parentNode;
|
130 | | - }
|
131 | | - return pathBits.join("/") + ":" + offset;
|
132 | | - }
|
133 | | -
|
134 | | - function deserializePosition(serialized, rootNode, doc) {
|
135 | | - if (rootNode) {
|
136 | | - doc = doc || dom.getDocument(rootNode);
|
137 | | - } else {
|
138 | | - doc = doc || document;
|
139 | | - rootNode = doc.documentElement;
|
140 | | - }
|
141 | | - var bits = serialized.split(":");
|
142 | | - var node = rootNode;
|
143 | | - var nodeIndices = bits[0] ? bits[0].split("/") : [], i = nodeIndices.length, nodeIndex;
|
144 | | -
|
145 | | - while (i--) {
|
146 | | - nodeIndex = parseInt(nodeIndices[i], 10);
|
147 | | - if (nodeIndex < node.childNodes.length) {
|
148 | | - node = node.childNodes[parseInt(nodeIndices[i], 10)];
|
149 | | - } else {
|
150 | | - throw module.createError("deserializePosition failed: node " + dom.inspectNode(node) +
|
151 | | - " has no child with index " + nodeIndex + ", " + i);
|
152 | | - }
|
153 | | - }
|
154 | | -
|
155 | | - return new dom.DomPosition(node, parseInt(bits[1], 10));
|
156 | | - }
|
157 | | -
|
158 | | - function serializeRange(range, omitChecksum, rootNode) {
|
159 | | - rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement;
|
160 | | - if (!dom.isAncestorOf(rootNode, range.commonAncestorContainer, true)) {
|
161 | | - throw new Error("serializeRange: range is not wholly contained within specified root node");
|
162 | | - }
|
163 | | - var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," +
|
164 | | - serializePosition(range.endContainer, range.endOffset, rootNode);
|
165 | | - if (!omitChecksum) {
|
166 | | - serialized += "{" + getElementChecksum(rootNode) + "}";
|
167 | | - }
|
168 | | - return serialized;
|
169 | | - }
|
170 | | -
|
171 | | - function deserializeRange(serialized, rootNode, doc) {
|
172 | | - if (rootNode) {
|
173 | | - doc = doc || dom.getDocument(rootNode);
|
174 | | - } else {
|
175 | | - doc = doc || document;
|
176 | | - rootNode = doc.documentElement;
|
177 | | - }
|
178 | | - var result = /^([^,]+),([^,\{]+)({([^}]+)})?$/.exec(serialized);
|
179 | | - var checksum = result[4], rootNodeChecksum = getElementChecksum(rootNode);
|
180 | | - if (checksum && checksum !== getElementChecksum(rootNode)) {
|
181 | | - throw new Error("deserializeRange: checksums of serialized range root node (" + checksum +
|
182 | | - ") and target root node (" + rootNodeChecksum + ") do not match");
|
183 | | - }
|
184 | | - var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc);
|
185 | | - var range = api.createRange(doc);
|
186 | | - range.setStart(start.node, start.offset);
|
187 | | - range.setEnd(end.node, end.offset);
|
188 | | - return range;
|
189 | | - }
|
190 | | -
|
191 | | - function canDeserializeRange(serialized, rootNode, doc) {
|
192 | | - if (rootNode) {
|
193 | | - doc = doc || dom.getDocument(rootNode);
|
194 | | - } else {
|
195 | | - doc = doc || document;
|
196 | | - rootNode = doc.documentElement;
|
197 | | - }
|
198 | | - var result = /^([^,]+),([^,]+)({([^}]+)})?$/.exec(serialized);
|
199 | | - var checksum = result[3];
|
200 | | - return !checksum || checksum === getElementChecksum(rootNode);
|
201 | | - }
|
202 | | -
|
203 | | - function serializeSelection(selection, omitChecksum, rootNode) {
|
204 | | - selection = selection || api.getSelection();
|
205 | | - var ranges = selection.getAllRanges(), serializedRanges = [];
|
206 | | - for (var i = 0, len = ranges.length; i < len; ++i) {
|
207 | | - serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode);
|
208 | | - }
|
209 | | - return serializedRanges.join("|");
|
210 | | - }
|
211 | | -
|
212 | | - function deserializeSelection(serialized, rootNode, win) {
|
213 | | - if (rootNode) {
|
214 | | - win = win || dom.getWindow(rootNode);
|
215 | | - } else {
|
216 | | - win = win || window;
|
217 | | - rootNode = win.document.documentElement;
|
218 | | - }
|
219 | | - var serializedRanges = serialized.split("|");
|
220 | | - var sel = api.getSelection(win);
|
221 | | - var ranges = [];
|
222 | | -
|
223 | | - for (var i = 0, len = serializedRanges.length; i < len; ++i) {
|
224 | | - ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document);
|
225 | | - }
|
226 | | - sel.setRanges(ranges);
|
227 | | -
|
228 | | - return sel;
|
229 | | - }
|
230 | | -
|
231 | | - function canDeserializeSelection(serialized, rootNode, win) {
|
232 | | - var doc;
|
233 | | - if (rootNode) {
|
234 | | - doc = win ? win.document : dom.getDocument(rootNode);
|
235 | | - } else {
|
236 | | - win = win || window;
|
237 | | - rootNode = win.document.documentElement;
|
238 | | - }
|
239 | | - var serializedRanges = serialized.split("|");
|
240 | | -
|
241 | | - for (var i = 0, len = serializedRanges.length; i < len; ++i) {
|
242 | | - if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) {
|
243 | | - return false;
|
244 | | - }
|
245 | | - }
|
246 | | -
|
247 | | - return true;
|
248 | | - }
|
249 | | -
|
250 | | -
|
251 | | - var cookieName = "rangySerializedSelection";
|
252 | | -
|
253 | | - function getSerializedSelectionFromCookie(cookie) {
|
254 | | - var parts = cookie.split(/[;,]/);
|
255 | | - for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) {
|
256 | | - nameVal = parts[i].split("=");
|
257 | | - if (nameVal[0].replace(/^\s+/, "") == cookieName) {
|
258 | | - val = nameVal[1];
|
259 | | - if (val) {
|
260 | | - return decodeURIComponent(val.replace(/\s+$/, ""));
|
261 | | - }
|
262 | | - }
|
263 | | - }
|
264 | | - return null;
|
265 | | - }
|
266 | | -
|
267 | | - function restoreSelectionFromCookie(win) {
|
268 | | - win = win || window;
|
269 | | - var serialized = getSerializedSelectionFromCookie(win.document.cookie);
|
270 | | - if (serialized) {
|
271 | | - deserializeSelection(serialized, win.doc)
|
272 | | - }
|
273 | | - }
|
274 | | -
|
275 | | - function saveSelectionCookie(win, props) {
|
276 | | - win = win || window;
|
277 | | - props = (typeof props == "object") ? props : {};
|
278 | | - var expires = props.expires ? ";expires=" + props.expires.toUTCString() : "";
|
279 | | - var path = props.path ? ";path=" + props.path : "";
|
280 | | - var domain = props.domain ? ";domain=" + props.domain : "";
|
281 | | - var secure = props.secure ? ";secure" : "";
|
282 | | - var serialized = serializeSelection(api.getSelection(win));
|
283 | | - win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure;
|
284 | | - }
|
285 | | -
|
286 | | - api.serializePosition = serializePosition;
|
287 | | - api.deserializePosition = deserializePosition;
|
288 | | -
|
289 | | - api.serializeRange = serializeRange;
|
290 | | - api.deserializeRange = deserializeRange;
|
291 | | - api.canDeserializeRange = canDeserializeRange;
|
292 | | -
|
293 | | - api.serializeSelection = serializeSelection;
|
294 | | - api.deserializeSelection = deserializeSelection;
|
295 | | - api.canDeserializeSelection = canDeserializeSelection;
|
296 | | -
|
297 | | - api.restoreSelectionFromCookie = restoreSelectionFromCookie;
|
298 | | - api.saveSelectionCookie = saveSelectionCookie;
|
299 | | -
|
300 | | - api.getElementChecksum = getElementChecksum;
|
301 | | -});
|
| 2 | +/** |
| 3 | + * @license Serializer module for Rangy. |
| 4 | + * Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a |
| 5 | + * cookie or local storage and restore it on the user's next visit to the same page. |
| 6 | + * |
| 7 | + * Part of Rangy, a cross-browser JavaScript range and selection library |
| 8 | + * http://code.google.com/p/rangy/ |
| 9 | + * |
| 10 | + * Depends on Rangy core. |
| 11 | + * |
| 12 | + * Copyright 2011, Tim Down |
| 13 | + * Licensed under the MIT license. |
| 14 | + * Version: 1.2.2 |
| 15 | + * Build date: 13 November 2011 |
| 16 | + */ |
| 17 | +rangy.createModule("Serializer", function(api, module) { |
| 18 | + api.requireModules( ["WrappedSelection", "WrappedRange"] ); |
| 19 | + var UNDEF = "undefined"; |
| 20 | + |
| 21 | + // encodeURIComponent and decodeURIComponent are required for cookie handling |
| 22 | + if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) { |
| 23 | + module.fail("Global object is missing encodeURIComponent and/or decodeURIComponent method"); |
| 24 | + } |
| 25 | + |
| 26 | + // Checksum for checking whether range can be serialized |
| 27 | + var crc32 = (function() { |
| 28 | + function utf8encode(str) { |
| 29 | + var utf8CharCodes = []; |
| 30 | + |
| 31 | + for (var i = 0, len = str.length, c; i < len; ++i) { |
| 32 | + c = str.charCodeAt(i); |
| 33 | + if (c < 128) { |
| 34 | + utf8CharCodes.push(c); |
| 35 | + } else if (c < 2048) { |
| 36 | + utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128); |
| 37 | + } else { |
| 38 | + utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128); |
| 39 | + } |
| 40 | + } |
| 41 | + return utf8CharCodes; |
| 42 | + } |
| 43 | + |
| 44 | + var cachedCrcTable = null; |
| 45 | + |
| 46 | + function buildCRCTable() { |
| 47 | + var table = []; |
| 48 | + for (var i = 0, j, crc; i < 256; ++i) { |
| 49 | + crc = i; |
| 50 | + j = 8; |
| 51 | + while (j--) { |
| 52 | + if ((crc & 1) == 1) { |
| 53 | + crc = (crc >>> 1) ^ 0xEDB88320; |
| 54 | + } else { |
| 55 | + crc >>>= 1; |
| 56 | + } |
| 57 | + } |
| 58 | + table[i] = crc >>> 0; |
| 59 | + } |
| 60 | + return table; |
| 61 | + } |
| 62 | + |
| 63 | + function getCrcTable() { |
| 64 | + if (!cachedCrcTable) { |
| 65 | + cachedCrcTable = buildCRCTable(); |
| 66 | + } |
| 67 | + return cachedCrcTable; |
| 68 | + } |
| 69 | + |
| 70 | + return function(str) { |
| 71 | + var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable(); |
| 72 | + for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) { |
| 73 | + y = (crc ^ utf8CharCodes[i]) & 0xFF; |
| 74 | + crc = (crc >>> 8) ^ crcTable[y]; |
| 75 | + } |
| 76 | + return (crc ^ -1) >>> 0; |
| 77 | + }; |
| 78 | + })(); |
| 79 | + |
| 80 | + var dom = api.dom; |
| 81 | + |
| 82 | + function escapeTextForHtml(str) { |
| 83 | + return str.replace(/</g, "<").replace(/>/g, ">"); |
| 84 | + } |
| 85 | + |
| 86 | + function nodeToInfoString(node, infoParts) { |
| 87 | + infoParts = infoParts || []; |
| 88 | + var nodeType = node.nodeType, children = node.childNodes, childCount = children.length; |
| 89 | + var nodeInfo = [nodeType, node.nodeName, childCount].join(":"); |
| 90 | + var start = "", end = ""; |
| 91 | + switch (nodeType) { |
| 92 | + case 3: // Text node |
| 93 | + start = escapeTextForHtml(node.nodeValue); |
| 94 | + break; |
| 95 | + case 8: // Comment |
| 96 | + start = "<!--" + escapeTextForHtml(node.nodeValue) + "-->"; |
| 97 | + break; |
| 98 | + default: |
| 99 | + start = "<" + nodeInfo + ">"; |
| 100 | + end = "</>"; |
| 101 | + break; |
| 102 | + } |
| 103 | + if (start) { |
| 104 | + infoParts.push(start); |
| 105 | + } |
| 106 | + for (var i = 0; i < childCount; ++i) { |
| 107 | + nodeToInfoString(children[i], infoParts); |
| 108 | + } |
| 109 | + if (end) { |
| 110 | + infoParts.push(end); |
| 111 | + } |
| 112 | + return infoParts; |
| 113 | + } |
| 114 | + |
| 115 | + // Creates a string representation of the specified element's contents that is similar to innerHTML but omits all |
| 116 | + // attributes and comments and includes child node counts. This is done instead of using innerHTML to work around |
| 117 | + // IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's |
| 118 | + // innerHTML whenever the user changes an input within the element. |
| 119 | + function getElementChecksum(el) { |
| 120 | + var info = nodeToInfoString(el).join(""); |
| 121 | + return crc32(info).toString(16); |
| 122 | + } |
| 123 | + |
| 124 | + function serializePosition(node, offset, rootNode) { |
| 125 | + var pathBits = [], n = node; |
| 126 | + rootNode = rootNode || dom.getDocument(node).documentElement; |
| 127 | + while (n && n != rootNode) { |
| 128 | + pathBits.push(dom.getNodeIndex(n, true)); |
| 129 | + n = n.parentNode; |
| 130 | + } |
| 131 | + return pathBits.join("/") + ":" + offset; |
| 132 | + } |
| 133 | + |
| 134 | + function deserializePosition(serialized, rootNode, doc) { |
| 135 | + if (rootNode) { |
| 136 | + doc = doc || dom.getDocument(rootNode); |
| 137 | + } else { |
| 138 | + doc = doc || document; |
| 139 | + rootNode = doc.documentElement; |
| 140 | + } |
| 141 | + var bits = serialized.split(":"); |
| 142 | + var node = rootNode; |
| 143 | + var nodeIndices = bits[0] ? bits[0].split("/") : [], i = nodeIndices.length, nodeIndex; |
| 144 | + |
| 145 | + while (i--) { |
| 146 | + nodeIndex = parseInt(nodeIndices[i], 10); |
| 147 | + if (nodeIndex < node.childNodes.length) { |
| 148 | + node = node.childNodes[parseInt(nodeIndices[i], 10)]; |
| 149 | + } else { |
| 150 | + throw module.createError("deserializePosition failed: node " + dom.inspectNode(node) + |
| 151 | + " has no child with index " + nodeIndex + ", " + i); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + return new dom.DomPosition(node, parseInt(bits[1], 10)); |
| 156 | + } |
| 157 | + |
| 158 | + function serializeRange(range, omitChecksum, rootNode) { |
| 159 | + rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement; |
| 160 | + if (!dom.isAncestorOf(rootNode, range.commonAncestorContainer, true)) { |
| 161 | + throw new Error("serializeRange: range is not wholly contained within specified root node"); |
| 162 | + } |
| 163 | + var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," + |
| 164 | + serializePosition(range.endContainer, range.endOffset, rootNode); |
| 165 | + if (!omitChecksum) { |
| 166 | + serialized += "{" + getElementChecksum(rootNode) + "}"; |
| 167 | + } |
| 168 | + return serialized; |
| 169 | + } |
| 170 | + |
| 171 | + function deserializeRange(serialized, rootNode, doc) { |
| 172 | + if (rootNode) { |
| 173 | + doc = doc || dom.getDocument(rootNode); |
| 174 | + } else { |
| 175 | + doc = doc || document; |
| 176 | + rootNode = doc.documentElement; |
| 177 | + } |
| 178 | + var result = /^([^,]+),([^,\{]+)({([^}]+)})?$/.exec(serialized); |
| 179 | + var checksum = result[4], rootNodeChecksum = getElementChecksum(rootNode); |
| 180 | + if (checksum && checksum !== getElementChecksum(rootNode)) { |
| 181 | + throw new Error("deserializeRange: checksums of serialized range root node (" + checksum + |
| 182 | + ") and target root node (" + rootNodeChecksum + ") do not match"); |
| 183 | + } |
| 184 | + var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc); |
| 185 | + var range = api.createRange(doc); |
| 186 | + range.setStart(start.node, start.offset); |
| 187 | + range.setEnd(end.node, end.offset); |
| 188 | + return range; |
| 189 | + } |
| 190 | + |
| 191 | + function canDeserializeRange(serialized, rootNode, doc) { |
| 192 | + if (rootNode) { |
| 193 | + doc = doc || dom.getDocument(rootNode); |
| 194 | + } else { |
| 195 | + doc = doc || document; |
| 196 | + rootNode = doc.documentElement; |
| 197 | + } |
| 198 | + var result = /^([^,]+),([^,]+)({([^}]+)})?$/.exec(serialized); |
| 199 | + var checksum = result[3]; |
| 200 | + return !checksum || checksum === getElementChecksum(rootNode); |
| 201 | + } |
| 202 | + |
| 203 | + function serializeSelection(selection, omitChecksum, rootNode) { |
| 204 | + selection = selection || api.getSelection(); |
| 205 | + var ranges = selection.getAllRanges(), serializedRanges = []; |
| 206 | + for (var i = 0, len = ranges.length; i < len; ++i) { |
| 207 | + serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode); |
| 208 | + } |
| 209 | + return serializedRanges.join("|"); |
| 210 | + } |
| 211 | + |
| 212 | + function deserializeSelection(serialized, rootNode, win) { |
| 213 | + if (rootNode) { |
| 214 | + win = win || dom.getWindow(rootNode); |
| 215 | + } else { |
| 216 | + win = win || window; |
| 217 | + rootNode = win.document.documentElement; |
| 218 | + } |
| 219 | + var serializedRanges = serialized.split("|"); |
| 220 | + var sel = api.getSelection(win); |
| 221 | + var ranges = []; |
| 222 | + |
| 223 | + for (var i = 0, len = serializedRanges.length; i < len; ++i) { |
| 224 | + ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document); |
| 225 | + } |
| 226 | + sel.setRanges(ranges); |
| 227 | + |
| 228 | + return sel; |
| 229 | + } |
| 230 | + |
| 231 | + function canDeserializeSelection(serialized, rootNode, win) { |
| 232 | + var doc; |
| 233 | + if (rootNode) { |
| 234 | + doc = win ? win.document : dom.getDocument(rootNode); |
| 235 | + } else { |
| 236 | + win = win || window; |
| 237 | + rootNode = win.document.documentElement; |
| 238 | + } |
| 239 | + var serializedRanges = serialized.split("|"); |
| 240 | + |
| 241 | + for (var i = 0, len = serializedRanges.length; i < len; ++i) { |
| 242 | + if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) { |
| 243 | + return false; |
| 244 | + } |
| 245 | + } |
| 246 | + |
| 247 | + return true; |
| 248 | + } |
| 249 | + |
| 250 | + |
| 251 | + var cookieName = "rangySerializedSelection"; |
| 252 | + |
| 253 | + function getSerializedSelectionFromCookie(cookie) { |
| 254 | + var parts = cookie.split(/[;,]/); |
| 255 | + for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) { |
| 256 | + nameVal = parts[i].split("="); |
| 257 | + if (nameVal[0].replace(/^\s+/, "") == cookieName) { |
| 258 | + val = nameVal[1]; |
| 259 | + if (val) { |
| 260 | + return decodeURIComponent(val.replace(/\s+$/, "")); |
| 261 | + } |
| 262 | + } |
| 263 | + } |
| 264 | + return null; |
| 265 | + } |
| 266 | + |
| 267 | + function restoreSelectionFromCookie(win) { |
| 268 | + win = win || window; |
| 269 | + var serialized = getSerializedSelectionFromCookie(win.document.cookie); |
| 270 | + if (serialized) { |
| 271 | + deserializeSelection(serialized, win.doc) |
| 272 | + } |
| 273 | + } |
| 274 | + |
| 275 | + function saveSelectionCookie(win, props) { |
| 276 | + win = win || window; |
| 277 | + props = (typeof props == "object") ? props : {}; |
| 278 | + var expires = props.expires ? ";expires=" + props.expires.toUTCString() : ""; |
| 279 | + var path = props.path ? ";path=" + props.path : ""; |
| 280 | + var domain = props.domain ? ";domain=" + props.domain : ""; |
| 281 | + var secure = props.secure ? ";secure" : ""; |
| 282 | + var serialized = serializeSelection(api.getSelection(win)); |
| 283 | + win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure; |
| 284 | + } |
| 285 | + |
| 286 | + api.serializePosition = serializePosition; |
| 287 | + api.deserializePosition = deserializePosition; |
| 288 | + |
| 289 | + api.serializeRange = serializeRange; |
| 290 | + api.deserializeRange = deserializeRange; |
| 291 | + api.canDeserializeRange = canDeserializeRange; |
| 292 | + |
| 293 | + api.serializeSelection = serializeSelection; |
| 294 | + api.deserializeSelection = deserializeSelection; |
| 295 | + api.canDeserializeSelection = canDeserializeSelection; |
| 296 | + |
| 297 | + api.restoreSelectionFromCookie = restoreSelectionFromCookie; |
| 298 | + api.saveSelectionCookie = saveSelectionCookie; |
| 299 | + |
| 300 | + api.getElementChecksum = getElementChecksum; |
| 301 | +}); |
Property changes on: trunk/extensions/VisualEditor/modules/rangy/rangy-serializer.js |
___________________________________________________________________ |
Added: svn:eol-style |
302 | 302 | + native |
Index: trunk/extensions/VisualEditor/modules/rangy/rangy-cssclassapplier.js |
— | — | @@ -1,713 +1,713 @@ |
2 | | -/**
|
3 | | - * @license CSS Class Applier module for Rangy.
|
4 | | - * Adds, removes and toggles CSS classes on Ranges and Selections
|
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 2011, Tim Down
|
12 | | - * Licensed under the MIT license.
|
13 | | - * Version: 1.2.2
|
14 | | - * Build date: 13 November 2011
|
15 | | - */
|
16 | | -rangy.createModule("CssClassApplier", function(api, module) {
|
17 | | - api.requireModules( ["WrappedSelection", "WrappedRange"] );
|
18 | | -
|
19 | | - var dom = api.dom;
|
20 | | -
|
21 | | -
|
22 | | -
|
23 | | - var defaultTagName = "span";
|
24 | | -
|
25 | | - function trim(str) {
|
26 | | - return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
|
27 | | - }
|
28 | | -
|
29 | | - function hasClass(el, cssClass) {
|
30 | | - return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className);
|
31 | | - }
|
32 | | -
|
33 | | - function addClass(el, cssClass) {
|
34 | | - if (el.className) {
|
35 | | - if (!hasClass(el, cssClass)) {
|
36 | | - el.className += " " + cssClass;
|
37 | | - }
|
38 | | - } else {
|
39 | | - el.className = cssClass;
|
40 | | - }
|
41 | | - }
|
42 | | -
|
43 | | - var removeClass = (function() {
|
44 | | - function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
|
45 | | - return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
|
46 | | - }
|
47 | | -
|
48 | | - return function(el, cssClass) {
|
49 | | - if (el.className) {
|
50 | | - el.className = el.className.replace(new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)"), replacer);
|
51 | | - }
|
52 | | - };
|
53 | | - })();
|
54 | | -
|
55 | | - function sortClassName(className) {
|
56 | | - return className.split(/\s+/).sort().join(" ");
|
57 | | - }
|
58 | | -
|
59 | | - function getSortedClassName(el) {
|
60 | | - return sortClassName(el.className);
|
61 | | - }
|
62 | | -
|
63 | | - function haveSameClasses(el1, el2) {
|
64 | | - return getSortedClassName(el1) == getSortedClassName(el2);
|
65 | | - }
|
66 | | -
|
67 | | - function replaceWithOwnChildren(el) {
|
68 | | -
|
69 | | - var parent = el.parentNode;
|
70 | | - while (el.hasChildNodes()) {
|
71 | | - parent.insertBefore(el.firstChild, el);
|
72 | | - }
|
73 | | - parent.removeChild(el);
|
74 | | - }
|
75 | | -
|
76 | | - function rangeSelectsAnyText(range, textNode) {
|
77 | | - var textRange = range.cloneRange();
|
78 | | - textRange.selectNodeContents(textNode);
|
79 | | -
|
80 | | - var intersectionRange = textRange.intersection(range);
|
81 | | - var text = intersectionRange ? intersectionRange.toString() : "";
|
82 | | - textRange.detach();
|
83 | | -
|
84 | | - return text != "";
|
85 | | - }
|
86 | | -
|
87 | | - function getEffectiveTextNodes(range) {
|
88 | | - return range.getNodes([3], function(textNode) {
|
89 | | - return rangeSelectsAnyText(range, textNode);
|
90 | | - });
|
91 | | - }
|
92 | | -
|
93 | | - function elementsHaveSameNonClassAttributes(el1, el2) {
|
94 | | - if (el1.attributes.length != el2.attributes.length) return false;
|
95 | | - for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
|
96 | | - attr1 = el1.attributes[i];
|
97 | | - name = attr1.name;
|
98 | | - if (name != "class") {
|
99 | | - attr2 = el2.attributes.getNamedItem(name);
|
100 | | - if (attr1.specified != attr2.specified) return false;
|
101 | | - if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
|
102 | | - }
|
103 | | - }
|
104 | | - return true;
|
105 | | - }
|
106 | | -
|
107 | | - function elementHasNonClassAttributes(el, exceptions) {
|
108 | | - for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
|
109 | | - attrName = el.attributes[i].name;
|
110 | | - if ( !(exceptions && dom.arrayContains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
|
111 | | - return true;
|
112 | | - }
|
113 | | - }
|
114 | | - return false;
|
115 | | - }
|
116 | | -
|
117 | | - function elementHasProps(el, props) {
|
118 | | - for (var p in props) {
|
119 | | - if (props.hasOwnProperty(p) && el[p] !== props[p]) {
|
120 | | - return false;
|
121 | | - }
|
122 | | - }
|
123 | | - return true;
|
124 | | - }
|
125 | | -
|
126 | | - var getComputedStyleProperty;
|
127 | | -
|
128 | | - if (typeof window.getComputedStyle != "undefined") {
|
129 | | - getComputedStyleProperty = function(el, propName) {
|
130 | | - return dom.getWindow(el).getComputedStyle(el, null)[propName];
|
131 | | - };
|
132 | | - } else if (typeof document.documentElement.currentStyle != "undefined") {
|
133 | | - getComputedStyleProperty = function(el, propName) {
|
134 | | - return el.currentStyle[propName];
|
135 | | - };
|
136 | | - } else {
|
137 | | - module.fail("No means of obtaining computed style properties found");
|
138 | | - }
|
139 | | -
|
140 | | - var isEditableElement;
|
141 | | -
|
142 | | - (function() {
|
143 | | - var testEl = document.createElement("div");
|
144 | | - if (typeof testEl.isContentEditable == "boolean") {
|
145 | | - isEditableElement = function(node) {
|
146 | | - return node && node.nodeType == 1 && node.isContentEditable;
|
147 | | - };
|
148 | | - } else {
|
149 | | - isEditableElement = function(node) {
|
150 | | - if (!node || node.nodeType != 1 || node.contentEditable == "false") {
|
151 | | - return false;
|
152 | | - }
|
153 | | - return node.contentEditable == "true" || isEditableElement(node.parentNode);
|
154 | | - };
|
155 | | - }
|
156 | | - })();
|
157 | | -
|
158 | | - function isEditingHost(node) {
|
159 | | - var parent;
|
160 | | - return node && node.nodeType == 1
|
161 | | - && (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on")
|
162 | | - || (isEditableElement(node) && !isEditableElement(node.parentNode)));
|
163 | | - }
|
164 | | -
|
165 | | - function isEditable(node) {
|
166 | | - return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
|
167 | | - }
|
168 | | -
|
169 | | - var inlineDisplayRegex = /^inline(-block|-table)?$/i;
|
170 | | -
|
171 | | - function isNonInlineElement(node) {
|
172 | | - return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
|
173 | | - }
|
174 | | -
|
175 | | - // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
|
176 | | - var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
|
177 | | -
|
178 | | - function isUnrenderedWhiteSpaceNode(node) {
|
179 | | - if (node.data.length == 0) {
|
180 | | - return true;
|
181 | | - }
|
182 | | - if (htmlNonWhiteSpaceRegex.test(node.data)) {
|
183 | | - return false;
|
184 | | - }
|
185 | | - var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
|
186 | | - switch (cssWhiteSpace) {
|
187 | | - case "pre":
|
188 | | - case "pre-wrap":
|
189 | | - case "-moz-pre-wrap":
|
190 | | - return false;
|
191 | | - case "pre-line":
|
192 | | - if (/[\r\n]/.test(node.data)) {
|
193 | | - return false;
|
194 | | - }
|
195 | | - }
|
196 | | -
|
197 | | - // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
|
198 | | - // non-inline element, it will not be rendered. This seems to be a good enough definition.
|
199 | | - return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
|
200 | | - }
|
201 | | -
|
202 | | - function isSplitPoint(node, offset) {
|
203 | | - if (dom.isCharacterDataNode(node)) {
|
204 | | - if (offset == 0) {
|
205 | | - return !!node.previousSibling;
|
206 | | - } else if (offset == node.length) {
|
207 | | - return !!node.nextSibling;
|
208 | | - } else {
|
209 | | - return true;
|
210 | | - }
|
211 | | - }
|
212 | | -
|
213 | | - return offset > 0 && offset < node.childNodes.length;
|
214 | | - }
|
215 | | -
|
216 | | - function splitNodeAt(node, descendantNode, descendantOffset, rangesToPreserve) {
|
217 | | - var newNode;
|
218 | | - var splitAtStart = (descendantOffset == 0);
|
219 | | -
|
220 | | - if (dom.isAncestorOf(descendantNode, node)) {
|
221 | | -
|
222 | | - return node;
|
223 | | - }
|
224 | | -
|
225 | | - if (dom.isCharacterDataNode(descendantNode)) {
|
226 | | - if (descendantOffset == 0) {
|
227 | | - descendantOffset = dom.getNodeIndex(descendantNode);
|
228 | | - descendantNode = descendantNode.parentNode;
|
229 | | - } else if (descendantOffset == descendantNode.length) {
|
230 | | - descendantOffset = dom.getNodeIndex(descendantNode) + 1;
|
231 | | - descendantNode = descendantNode.parentNode;
|
232 | | - } else {
|
233 | | - throw module.createError("splitNodeAt should not be called with offset in the middle of a data node ("
|
234 | | - + descendantOffset + " in " + descendantNode.data);
|
235 | | - }
|
236 | | - }
|
237 | | -
|
238 | | - if (isSplitPoint(descendantNode, descendantOffset)) {
|
239 | | - if (!newNode) {
|
240 | | - newNode = descendantNode.cloneNode(false);
|
241 | | - if (newNode.id) {
|
242 | | - newNode.removeAttribute("id");
|
243 | | - }
|
244 | | - var child;
|
245 | | - while ((child = descendantNode.childNodes[descendantOffset])) {
|
246 | | - newNode.appendChild(child);
|
247 | | - }
|
248 | | - dom.insertAfter(newNode, descendantNode);
|
249 | | - }
|
250 | | - return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, dom.getNodeIndex(newNode), rangesToPreserve);
|
251 | | - } else if (node != descendantNode) {
|
252 | | - newNode = descendantNode.parentNode;
|
253 | | -
|
254 | | - // Work out a new split point in the parent node
|
255 | | - var newNodeIndex = dom.getNodeIndex(descendantNode);
|
256 | | -
|
257 | | - if (!splitAtStart) {
|
258 | | - newNodeIndex++;
|
259 | | - }
|
260 | | - return splitNodeAt(node, newNode, newNodeIndex, rangesToPreserve);
|
261 | | - }
|
262 | | - return node;
|
263 | | - }
|
264 | | -
|
265 | | - function areElementsMergeable(el1, el2) {
|
266 | | - return el1.tagName == el2.tagName && haveSameClasses(el1, el2) && elementsHaveSameNonClassAttributes(el1, el2);
|
267 | | - }
|
268 | | -
|
269 | | - function createAdjacentMergeableTextNodeGetter(forward) {
|
270 | | - var propName = forward ? "nextSibling" : "previousSibling";
|
271 | | -
|
272 | | - return function(textNode, checkParentElement) {
|
273 | | - var el = textNode.parentNode;
|
274 | | - var adjacentNode = textNode[propName];
|
275 | | - if (adjacentNode) {
|
276 | | - // Can merge if the node's previous/next sibling is a text node
|
277 | | - if (adjacentNode && adjacentNode.nodeType == 3) {
|
278 | | - return adjacentNode;
|
279 | | - }
|
280 | | - } else if (checkParentElement) {
|
281 | | - // Compare text node parent element with its sibling
|
282 | | - adjacentNode = el[propName];
|
283 | | -
|
284 | | - if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
|
285 | | - return adjacentNode[forward ? "firstChild" : "lastChild"];
|
286 | | - }
|
287 | | - }
|
288 | | - return null;
|
289 | | - }
|
290 | | - }
|
291 | | -
|
292 | | - var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
|
293 | | - getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
|
294 | | -
|
295 | | -
|
296 | | - function Merge(firstNode) {
|
297 | | - this.isElementMerge = (firstNode.nodeType == 1);
|
298 | | - this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
|
299 | | - this.textNodes = [this.firstTextNode];
|
300 | | - }
|
301 | | -
|
302 | | - Merge.prototype = {
|
303 | | - doMerge: function() {
|
304 | | - var textBits = [], textNode, parent, text;
|
305 | | - for (var i = 0, len = this.textNodes.length; i < len; ++i) {
|
306 | | - textNode = this.textNodes[i];
|
307 | | - parent = textNode.parentNode;
|
308 | | - textBits[i] = textNode.data;
|
309 | | - if (i) {
|
310 | | - parent.removeChild(textNode);
|
311 | | - if (!parent.hasChildNodes()) {
|
312 | | - parent.parentNode.removeChild(parent);
|
313 | | - }
|
314 | | - }
|
315 | | - }
|
316 | | - this.firstTextNode.data = text = textBits.join("");
|
317 | | - return text;
|
318 | | - },
|
319 | | -
|
320 | | - getLength: function() {
|
321 | | - var i = this.textNodes.length, len = 0;
|
322 | | - while (i--) {
|
323 | | - len += this.textNodes[i].length;
|
324 | | - }
|
325 | | - return len;
|
326 | | - },
|
327 | | -
|
328 | | - toString: function() {
|
329 | | - var textBits = [];
|
330 | | - for (var i = 0, len = this.textNodes.length; i < len; ++i) {
|
331 | | - textBits[i] = "'" + this.textNodes[i].data + "'";
|
332 | | - }
|
333 | | - return "[Merge(" + textBits.join(",") + ")]";
|
334 | | - }
|
335 | | - };
|
336 | | -
|
337 | | - var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly"];
|
338 | | -
|
339 | | - // Allow "class" as a property name in object properties
|
340 | | - var mappedPropertyNames = {"class" : "className"};
|
341 | | -
|
342 | | - function CssClassApplier(cssClass, options, tagNames) {
|
343 | | - this.cssClass = cssClass;
|
344 | | - var normalize, i, len, propName;
|
345 | | -
|
346 | | - var elementPropertiesFromOptions = null;
|
347 | | -
|
348 | | - // Initialize from options object
|
349 | | - if (typeof options == "object" && options !== null) {
|
350 | | - tagNames = options.tagNames;
|
351 | | - elementPropertiesFromOptions = options.elementProperties;
|
352 | | -
|
353 | | - for (i = 0; propName = optionProperties[i++]; ) {
|
354 | | - if (options.hasOwnProperty(propName)) {
|
355 | | - this[propName] = options[propName];
|
356 | | - }
|
357 | | - }
|
358 | | - normalize = options.normalize;
|
359 | | - } else {
|
360 | | - normalize = options;
|
361 | | - }
|
362 | | -
|
363 | | - // Backwards compatibility: the second parameter can also be a Boolean indicating whether normalization
|
364 | | - this.normalize = (typeof normalize == "undefined") ? true : normalize;
|
365 | | -
|
366 | | - // Initialize element properties and attribute exceptions
|
367 | | - this.attrExceptions = [];
|
368 | | - var el = document.createElement(this.elementTagName);
|
369 | | - this.elementProperties = {};
|
370 | | - for (var p in elementPropertiesFromOptions) {
|
371 | | - if (elementPropertiesFromOptions.hasOwnProperty(p)) {
|
372 | | - // Map "class" to "className"
|
373 | | - if (mappedPropertyNames.hasOwnProperty(p)) {
|
374 | | - p = mappedPropertyNames[p];
|
375 | | - }
|
376 | | - el[p] = elementPropertiesFromOptions[p];
|
377 | | -
|
378 | | - // Copy the property back from the dummy element so that later comparisons to check whether elements
|
379 | | - // may be removed are checking against the right value. For example, the href property of an element
|
380 | | - // returns a fully qualified URL even if it was previously assigned a relative URL.
|
381 | | - this.elementProperties[p] = el[p];
|
382 | | - this.attrExceptions.push(p);
|
383 | | - }
|
384 | | - }
|
385 | | -
|
386 | | - this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ?
|
387 | | - sortClassName(this.elementProperties.className + " " + cssClass) : cssClass;
|
388 | | -
|
389 | | - // Initialize tag names
|
390 | | - this.applyToAnyTagName = false;
|
391 | | - var type = typeof tagNames;
|
392 | | - if (type == "string") {
|
393 | | - if (tagNames == "*") {
|
394 | | - this.applyToAnyTagName = true;
|
395 | | - } else {
|
396 | | - this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
|
397 | | - }
|
398 | | - } else if (type == "object" && typeof tagNames.length == "number") {
|
399 | | - this.tagNames = [];
|
400 | | - for (i = 0, len = tagNames.length; i < len; ++i) {
|
401 | | - if (tagNames[i] == "*") {
|
402 | | - this.applyToAnyTagName = true;
|
403 | | - } else {
|
404 | | - this.tagNames.push(tagNames[i].toLowerCase());
|
405 | | - }
|
406 | | - }
|
407 | | - } else {
|
408 | | - this.tagNames = [this.elementTagName];
|
409 | | - }
|
410 | | - }
|
411 | | -
|
412 | | - CssClassApplier.prototype = {
|
413 | | - elementTagName: defaultTagName,
|
414 | | - elementProperties: {},
|
415 | | - ignoreWhiteSpace: true,
|
416 | | - applyToEditableOnly: false,
|
417 | | -
|
418 | | - hasClass: function(node) {
|
419 | | - return node.nodeType == 1 && dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && hasClass(node, this.cssClass);
|
420 | | - },
|
421 | | -
|
422 | | - getSelfOrAncestorWithClass: function(node) {
|
423 | | - while (node) {
|
424 | | - if (this.hasClass(node, this.cssClass)) {
|
425 | | - return node;
|
426 | | - }
|
427 | | - node = node.parentNode;
|
428 | | - }
|
429 | | - return null;
|
430 | | - },
|
431 | | -
|
432 | | - isModifiable: function(node) {
|
433 | | - return !this.applyToEditableOnly || isEditable(node);
|
434 | | - },
|
435 | | -
|
436 | | - // White space adjacent to an unwrappable node can be ignored for wrapping
|
437 | | - isIgnorableWhiteSpaceNode: function(node) {
|
438 | | - return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
|
439 | | - },
|
440 | | -
|
441 | | - // Normalizes nodes after applying a CSS class to a Range.
|
442 | | - postApply: function(textNodes, range, isUndo) {
|
443 | | -
|
444 | | - var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
|
445 | | -
|
446 | | - var merges = [], currentMerge;
|
447 | | -
|
448 | | - var rangeStartNode = firstNode, rangeEndNode = lastNode;
|
449 | | - var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
|
450 | | -
|
451 | | - var textNode, precedingTextNode;
|
452 | | -
|
453 | | - for (var i = 0, len = textNodes.length; i < len; ++i) {
|
454 | | - textNode = textNodes[i];
|
455 | | - precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
|
456 | | -
|
457 | | - if (precedingTextNode) {
|
458 | | - if (!currentMerge) {
|
459 | | - currentMerge = new Merge(precedingTextNode);
|
460 | | - merges.push(currentMerge);
|
461 | | - }
|
462 | | - currentMerge.textNodes.push(textNode);
|
463 | | - if (textNode === firstNode) {
|
464 | | - rangeStartNode = currentMerge.firstTextNode;
|
465 | | - rangeStartOffset = rangeStartNode.length;
|
466 | | - }
|
467 | | - if (textNode === lastNode) {
|
468 | | - rangeEndNode = currentMerge.firstTextNode;
|
469 | | - rangeEndOffset = currentMerge.getLength();
|
470 | | - }
|
471 | | - } else {
|
472 | | - currentMerge = null;
|
473 | | - }
|
474 | | - }
|
475 | | -
|
476 | | - // Test whether the first node after the range needs merging
|
477 | | - var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
|
478 | | -
|
479 | | - if (nextTextNode) {
|
480 | | - if (!currentMerge) {
|
481 | | - currentMerge = new Merge(lastNode);
|
482 | | - merges.push(currentMerge);
|
483 | | - }
|
484 | | - currentMerge.textNodes.push(nextTextNode);
|
485 | | - }
|
486 | | -
|
487 | | - // Do the merges
|
488 | | - if (merges.length) {
|
489 | | -
|
490 | | - for (i = 0, len = merges.length; i < len; ++i) {
|
491 | | - merges[i].doMerge();
|
492 | | - }
|
493 | | -
|
494 | | -
|
495 | | - // Set the range boundaries
|
496 | | - range.setStart(rangeStartNode, rangeStartOffset);
|
497 | | - range.setEnd(rangeEndNode, rangeEndOffset);
|
498 | | - }
|
499 | | -
|
500 | | - },
|
501 | | -
|
502 | | - createContainer: function(doc) {
|
503 | | - var el = doc.createElement(this.elementTagName);
|
504 | | - api.util.extend(el, this.elementProperties);
|
505 | | - addClass(el, this.cssClass);
|
506 | | - return el;
|
507 | | - },
|
508 | | -
|
509 | | - applyToTextNode: function(textNode) {
|
510 | | -
|
511 | | -
|
512 | | - var parent = textNode.parentNode;
|
513 | | - if (parent.childNodes.length == 1 && dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) {
|
514 | | - addClass(parent, this.cssClass);
|
515 | | - } else {
|
516 | | - var el = this.createContainer(dom.getDocument(textNode));
|
517 | | - textNode.parentNode.insertBefore(el, textNode);
|
518 | | - el.appendChild(textNode);
|
519 | | - }
|
520 | | -
|
521 | | - },
|
522 | | -
|
523 | | - isRemovable: function(el) {
|
524 | | - return el.tagName.toLowerCase() == this.elementTagName
|
525 | | - && getSortedClassName(el) == this.elementSortedClassName
|
526 | | - && elementHasProps(el, this.elementProperties)
|
527 | | - && !elementHasNonClassAttributes(el, this.attrExceptions)
|
528 | | - && this.isModifiable(el);
|
529 | | - },
|
530 | | -
|
531 | | - undoToTextNode: function(textNode, range, ancestorWithClass) {
|
532 | | -
|
533 | | - if (!range.containsNode(ancestorWithClass)) {
|
534 | | - // Split out the portion of the ancestor from which we can remove the CSS class
|
535 | | - //var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);
|
536 | | - var ancestorRange = range.cloneRange();
|
537 | | - ancestorRange.selectNode(ancestorWithClass);
|
538 | | -
|
539 | | - if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)/* && isSplitPoint(range.endContainer, range.endOffset)*/) {
|
540 | | - splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, [range]);
|
541 | | - range.setEndAfter(ancestorWithClass);
|
542 | | - }
|
543 | | - if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)/* && isSplitPoint(range.startContainer, range.startOffset)*/) {
|
544 | | - ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, [range]);
|
545 | | - }
|
546 | | - }
|
547 | | -
|
548 | | - if (this.isRemovable(ancestorWithClass)) {
|
549 | | - replaceWithOwnChildren(ancestorWithClass);
|
550 | | - } else {
|
551 | | - removeClass(ancestorWithClass, this.cssClass);
|
552 | | - }
|
553 | | - },
|
554 | | -
|
555 | | - applyToRange: function(range) {
|
556 | | - range.splitBoundaries();
|
557 | | - var textNodes = getEffectiveTextNodes(range);
|
558 | | -
|
559 | | - if (textNodes.length) {
|
560 | | - var textNode;
|
561 | | -
|
562 | | - for (var i = 0, len = textNodes.length; i < len; ++i) {
|
563 | | - textNode = textNodes[i];
|
564 | | -
|
565 | | - if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode)
|
566 | | - && this.isModifiable(textNode)) {
|
567 | | - this.applyToTextNode(textNode);
|
568 | | - }
|
569 | | - }
|
570 | | - range.setStart(textNodes[0], 0);
|
571 | | - textNode = textNodes[textNodes.length - 1];
|
572 | | - range.setEnd(textNode, textNode.length);
|
573 | | - if (this.normalize) {
|
574 | | - this.postApply(textNodes, range, false);
|
575 | | - }
|
576 | | - }
|
577 | | - },
|
578 | | -
|
579 | | - applyToSelection: function(win) {
|
580 | | -
|
581 | | - win = win || window;
|
582 | | - var sel = api.getSelection(win);
|
583 | | -
|
584 | | - var range, ranges = sel.getAllRanges();
|
585 | | - sel.removeAllRanges();
|
586 | | - var i = ranges.length;
|
587 | | - while (i--) {
|
588 | | - range = ranges[i];
|
589 | | - this.applyToRange(range);
|
590 | | - sel.addRange(range);
|
591 | | - }
|
592 | | -
|
593 | | - },
|
594 | | -
|
595 | | - undoToRange: function(range) {
|
596 | | -
|
597 | | - range.splitBoundaries();
|
598 | | - var textNodes = getEffectiveTextNodes(range);
|
599 | | - var textNode, ancestorWithClass;
|
600 | | - var lastTextNode = textNodes[textNodes.length - 1];
|
601 | | -
|
602 | | - if (textNodes.length) {
|
603 | | - for (var i = 0, len = textNodes.length; i < len; ++i) {
|
604 | | - textNode = textNodes[i];
|
605 | | - ancestorWithClass = this.getSelfOrAncestorWithClass(textNode);
|
606 | | - if (ancestorWithClass && this.isModifiable(textNode)) {
|
607 | | - this.undoToTextNode(textNode, range, ancestorWithClass);
|
608 | | - }
|
609 | | -
|
610 | | - // Ensure the range is still valid
|
611 | | - range.setStart(textNodes[0], 0);
|
612 | | - range.setEnd(lastTextNode, lastTextNode.length);
|
613 | | - }
|
614 | | -
|
615 | | -
|
616 | | -
|
617 | | - if (this.normalize) {
|
618 | | - this.postApply(textNodes, range, true);
|
619 | | - }
|
620 | | - }
|
621 | | - },
|
622 | | -
|
623 | | - undoToSelection: function(win) {
|
624 | | - win = win || window;
|
625 | | - var sel = api.getSelection(win);
|
626 | | - var ranges = sel.getAllRanges(), range;
|
627 | | - sel.removeAllRanges();
|
628 | | - for (var i = 0, len = ranges.length; i < len; ++i) {
|
629 | | - range = ranges[i];
|
630 | | - this.undoToRange(range);
|
631 | | - sel.addRange(range);
|
632 | | - }
|
633 | | - },
|
634 | | -
|
635 | | - getTextSelectedByRange: function(textNode, range) {
|
636 | | - var textRange = range.cloneRange();
|
637 | | - textRange.selectNodeContents(textNode);
|
638 | | -
|
639 | | - var intersectionRange = textRange.intersection(range);
|
640 | | - var text = intersectionRange ? intersectionRange.toString() : "";
|
641 | | - textRange.detach();
|
642 | | -
|
643 | | - return text;
|
644 | | - },
|
645 | | -
|
646 | | - isAppliedToRange: function(range) {
|
647 | | - if (range.collapsed) {
|
648 | | - return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
|
649 | | - } else {
|
650 | | - var textNodes = range.getNodes( [3] );
|
651 | | - for (var i = 0, textNode; textNode = textNodes[i++]; ) {
|
652 | | - if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode)
|
653 | | - && this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
|
654 | | - return false;
|
655 | | - }
|
656 | | - }
|
657 | | - return true;
|
658 | | - }
|
659 | | - },
|
660 | | -
|
661 | | - isAppliedToSelection: function(win) {
|
662 | | - win = win || window;
|
663 | | - var sel = api.getSelection(win);
|
664 | | - var ranges = sel.getAllRanges();
|
665 | | - var i = ranges.length;
|
666 | | - while (i--) {
|
667 | | - if (!this.isAppliedToRange(ranges[i])) {
|
668 | | - return false;
|
669 | | - }
|
670 | | - }
|
671 | | -
|
672 | | - return true;
|
673 | | - },
|
674 | | -
|
675 | | - toggleRange: function(range) {
|
676 | | - if (this.isAppliedToRange(range)) {
|
677 | | - this.undoToRange(range);
|
678 | | - } else {
|
679 | | - this.applyToRange(range);
|
680 | | - }
|
681 | | - },
|
682 | | -
|
683 | | - toggleSelection: function(win) {
|
684 | | - if (this.isAppliedToSelection(win)) {
|
685 | | - this.undoToSelection(win);
|
686 | | - } else {
|
687 | | - this.applyToSelection(win);
|
688 | | - }
|
689 | | - },
|
690 | | -
|
691 | | - detach: function() {}
|
692 | | - };
|
693 | | -
|
694 | | - function createCssClassApplier(cssClass, options, tagNames) {
|
695 | | - return new CssClassApplier(cssClass, options, tagNames);
|
696 | | - }
|
697 | | -
|
698 | | - CssClassApplier.util = {
|
699 | | - hasClass: hasClass,
|
700 | | - addClass: addClass,
|
701 | | - removeClass: removeClass,
|
702 | | - hasSameClasses: haveSameClasses,
|
703 | | - replaceWithOwnChildren: replaceWithOwnChildren,
|
704 | | - elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
|
705 | | - elementHasNonClassAttributes: elementHasNonClassAttributes,
|
706 | | - splitNodeAt: splitNodeAt,
|
707 | | - isEditableElement: isEditableElement,
|
708 | | - isEditingHost: isEditingHost,
|
709 | | - isEditable: isEditable
|
710 | | - };
|
711 | | -
|
712 | | - api.CssClassApplier = CssClassApplier;
|
713 | | - api.createCssClassApplier = createCssClassApplier;
|
714 | | -});
|
| 2 | +/** |
| 3 | + * @license CSS Class Applier module for Rangy. |
| 4 | + * Adds, removes and toggles CSS classes on Ranges and Selections |
| 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 2011, Tim Down |
| 12 | + * Licensed under the MIT license. |
| 13 | + * Version: 1.2.2 |
| 14 | + * Build date: 13 November 2011 |
| 15 | + */ |
| 16 | +rangy.createModule("CssClassApplier", function(api, module) { |
| 17 | + api.requireModules( ["WrappedSelection", "WrappedRange"] ); |
| 18 | + |
| 19 | + var dom = api.dom; |
| 20 | + |
| 21 | + |
| 22 | + |
| 23 | + var defaultTagName = "span"; |
| 24 | + |
| 25 | + function trim(str) { |
| 26 | + return str.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); |
| 27 | + } |
| 28 | + |
| 29 | + function hasClass(el, cssClass) { |
| 30 | + return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className); |
| 31 | + } |
| 32 | + |
| 33 | + function addClass(el, cssClass) { |
| 34 | + if (el.className) { |
| 35 | + if (!hasClass(el, cssClass)) { |
| 36 | + el.className += " " + cssClass; |
| 37 | + } |
| 38 | + } else { |
| 39 | + el.className = cssClass; |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + var removeClass = (function() { |
| 44 | + function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) { |
| 45 | + return (whiteSpaceBefore && whiteSpaceAfter) ? " " : ""; |
| 46 | + } |
| 47 | + |
| 48 | + return function(el, cssClass) { |
| 49 | + if (el.className) { |
| 50 | + el.className = el.className.replace(new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)"), replacer); |
| 51 | + } |
| 52 | + }; |
| 53 | + })(); |
| 54 | + |
| 55 | + function sortClassName(className) { |
| 56 | + return className.split(/\s+/).sort().join(" "); |
| 57 | + } |
| 58 | + |
| 59 | + function getSortedClassName(el) { |
| 60 | + return sortClassName(el.className); |
| 61 | + } |
| 62 | + |
| 63 | + function haveSameClasses(el1, el2) { |
| 64 | + return getSortedClassName(el1) == getSortedClassName(el2); |
| 65 | + } |
| 66 | + |
| 67 | + function replaceWithOwnChildren(el) { |
| 68 | + |
| 69 | + var parent = el.parentNode; |
| 70 | + while (el.hasChildNodes()) { |
| 71 | + parent.insertBefore(el.firstChild, el); |
| 72 | + } |
| 73 | + parent.removeChild(el); |
| 74 | + } |
| 75 | + |
| 76 | + function rangeSelectsAnyText(range, textNode) { |
| 77 | + var textRange = range.cloneRange(); |
| 78 | + textRange.selectNodeContents(textNode); |
| 79 | + |
| 80 | + var intersectionRange = textRange.intersection(range); |
| 81 | + var text = intersectionRange ? intersectionRange.toString() : ""; |
| 82 | + textRange.detach(); |
| 83 | + |
| 84 | + return text != ""; |
| 85 | + } |
| 86 | + |
| 87 | + function getEffectiveTextNodes(range) { |
| 88 | + return range.getNodes([3], function(textNode) { |
| 89 | + return rangeSelectsAnyText(range, textNode); |
| 90 | + }); |
| 91 | + } |
| 92 | + |
| 93 | + function elementsHaveSameNonClassAttributes(el1, el2) { |
| 94 | + if (el1.attributes.length != el2.attributes.length) return false; |
| 95 | + for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) { |
| 96 | + attr1 = el1.attributes[i]; |
| 97 | + name = attr1.name; |
| 98 | + if (name != "class") { |
| 99 | + attr2 = el2.attributes.getNamedItem(name); |
| 100 | + if (attr1.specified != attr2.specified) return false; |
| 101 | + if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false; |
| 102 | + } |
| 103 | + } |
| 104 | + return true; |
| 105 | + } |
| 106 | + |
| 107 | + function elementHasNonClassAttributes(el, exceptions) { |
| 108 | + for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) { |
| 109 | + attrName = el.attributes[i].name; |
| 110 | + if ( !(exceptions && dom.arrayContains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") { |
| 111 | + return true; |
| 112 | + } |
| 113 | + } |
| 114 | + return false; |
| 115 | + } |
| 116 | + |
| 117 | + function elementHasProps(el, props) { |
| 118 | + for (var p in props) { |
| 119 | + if (props.hasOwnProperty(p) && el[p] !== props[p]) { |
| 120 | + return false; |
| 121 | + } |
| 122 | + } |
| 123 | + return true; |
| 124 | + } |
| 125 | + |
| 126 | + var getComputedStyleProperty; |
| 127 | + |
| 128 | + if (typeof window.getComputedStyle != "undefined") { |
| 129 | + getComputedStyleProperty = function(el, propName) { |
| 130 | + return dom.getWindow(el).getComputedStyle(el, null)[propName]; |
| 131 | + }; |
| 132 | + } else if (typeof document.documentElement.currentStyle != "undefined") { |
| 133 | + getComputedStyleProperty = function(el, propName) { |
| 134 | + return el.currentStyle[propName]; |
| 135 | + }; |
| 136 | + } else { |
| 137 | + module.fail("No means of obtaining computed style properties found"); |
| 138 | + } |
| 139 | + |
| 140 | + var isEditableElement; |
| 141 | + |
| 142 | + (function() { |
| 143 | + var testEl = document.createElement("div"); |
| 144 | + if (typeof testEl.isContentEditable == "boolean") { |
| 145 | + isEditableElement = function(node) { |
| 146 | + return node && node.nodeType == 1 && node.isContentEditable; |
| 147 | + }; |
| 148 | + } else { |
| 149 | + isEditableElement = function(node) { |
| 150 | + if (!node || node.nodeType != 1 || node.contentEditable == "false") { |
| 151 | + return false; |
| 152 | + } |
| 153 | + return node.contentEditable == "true" || isEditableElement(node.parentNode); |
| 154 | + }; |
| 155 | + } |
| 156 | + })(); |
| 157 | + |
| 158 | + function isEditingHost(node) { |
| 159 | + var parent; |
| 160 | + return node && node.nodeType == 1 |
| 161 | + && (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on") |
| 162 | + || (isEditableElement(node) && !isEditableElement(node.parentNode))); |
| 163 | + } |
| 164 | + |
| 165 | + function isEditable(node) { |
| 166 | + return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node); |
| 167 | + } |
| 168 | + |
| 169 | + var inlineDisplayRegex = /^inline(-block|-table)?$/i; |
| 170 | + |
| 171 | + function isNonInlineElement(node) { |
| 172 | + return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display")); |
| 173 | + } |
| 174 | + |
| 175 | + // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html) |
| 176 | + var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/; |
| 177 | + |
| 178 | + function isUnrenderedWhiteSpaceNode(node) { |
| 179 | + if (node.data.length == 0) { |
| 180 | + return true; |
| 181 | + } |
| 182 | + if (htmlNonWhiteSpaceRegex.test(node.data)) { |
| 183 | + return false; |
| 184 | + } |
| 185 | + var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace"); |
| 186 | + switch (cssWhiteSpace) { |
| 187 | + case "pre": |
| 188 | + case "pre-wrap": |
| 189 | + case "-moz-pre-wrap": |
| 190 | + return false; |
| 191 | + case "pre-line": |
| 192 | + if (/[\r\n]/.test(node.data)) { |
| 193 | + return false; |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a |
| 198 | + // non-inline element, it will not be rendered. This seems to be a good enough definition. |
| 199 | + return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling); |
| 200 | + } |
| 201 | + |
| 202 | + function isSplitPoint(node, offset) { |
| 203 | + if (dom.isCharacterDataNode(node)) { |
| 204 | + if (offset == 0) { |
| 205 | + return !!node.previousSibling; |
| 206 | + } else if (offset == node.length) { |
| 207 | + return !!node.nextSibling; |
| 208 | + } else { |
| 209 | + return true; |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + return offset > 0 && offset < node.childNodes.length; |
| 214 | + } |
| 215 | + |
| 216 | + function splitNodeAt(node, descendantNode, descendantOffset, rangesToPreserve) { |
| 217 | + var newNode; |
| 218 | + var splitAtStart = (descendantOffset == 0); |
| 219 | + |
| 220 | + if (dom.isAncestorOf(descendantNode, node)) { |
| 221 | + |
| 222 | + return node; |
| 223 | + } |
| 224 | + |
| 225 | + if (dom.isCharacterDataNode(descendantNode)) { |
| 226 | + if (descendantOffset == 0) { |
| 227 | + descendantOffset = dom.getNodeIndex(descendantNode); |
| 228 | + descendantNode = descendantNode.parentNode; |
| 229 | + } else if (descendantOffset == descendantNode.length) { |
| 230 | + descendantOffset = dom.getNodeIndex(descendantNode) + 1; |
| 231 | + descendantNode = descendantNode.parentNode; |
| 232 | + } else { |
| 233 | + throw module.createError("splitNodeAt should not be called with offset in the middle of a data node (" |
| 234 | + + descendantOffset + " in " + descendantNode.data); |
| 235 | + } |
| 236 | + } |
| 237 | + |
| 238 | + if (isSplitPoint(descendantNode, descendantOffset)) { |
| 239 | + if (!newNode) { |
| 240 | + newNode = descendantNode.cloneNode(false); |
| 241 | + if (newNode.id) { |
| 242 | + newNode.removeAttribute("id"); |
| 243 | + } |
| 244 | + var child; |
| 245 | + while ((child = descendantNode.childNodes[descendantOffset])) { |
| 246 | + newNode.appendChild(child); |
| 247 | + } |
| 248 | + dom.insertAfter(newNode, descendantNode); |
| 249 | + } |
| 250 | + return (descendantNode == node) ? newNode : splitNodeAt(node, newNode.parentNode, dom.getNodeIndex(newNode), rangesToPreserve); |
| 251 | + } else if (node != descendantNode) { |
| 252 | + newNode = descendantNode.parentNode; |
| 253 | + |
| 254 | + // Work out a new split point in the parent node |
| 255 | + var newNodeIndex = dom.getNodeIndex(descendantNode); |
| 256 | + |
| 257 | + if (!splitAtStart) { |
| 258 | + newNodeIndex++; |
| 259 | + } |
| 260 | + return splitNodeAt(node, newNode, newNodeIndex, rangesToPreserve); |
| 261 | + } |
| 262 | + return node; |
| 263 | + } |
| 264 | + |
| 265 | + function areElementsMergeable(el1, el2) { |
| 266 | + return el1.tagName == el2.tagName && haveSameClasses(el1, el2) && elementsHaveSameNonClassAttributes(el1, el2); |
| 267 | + } |
| 268 | + |
| 269 | + function createAdjacentMergeableTextNodeGetter(forward) { |
| 270 | + var propName = forward ? "nextSibling" : "previousSibling"; |
| 271 | + |
| 272 | + return function(textNode, checkParentElement) { |
| 273 | + var el = textNode.parentNode; |
| 274 | + var adjacentNode = textNode[propName]; |
| 275 | + if (adjacentNode) { |
| 276 | + // Can merge if the node's previous/next sibling is a text node |
| 277 | + if (adjacentNode && adjacentNode.nodeType == 3) { |
| 278 | + return adjacentNode; |
| 279 | + } |
| 280 | + } else if (checkParentElement) { |
| 281 | + // Compare text node parent element with its sibling |
| 282 | + adjacentNode = el[propName]; |
| 283 | + |
| 284 | + if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) { |
| 285 | + return adjacentNode[forward ? "firstChild" : "lastChild"]; |
| 286 | + } |
| 287 | + } |
| 288 | + return null; |
| 289 | + } |
| 290 | + } |
| 291 | + |
| 292 | + var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false), |
| 293 | + getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true); |
| 294 | + |
| 295 | + |
| 296 | + function Merge(firstNode) { |
| 297 | + this.isElementMerge = (firstNode.nodeType == 1); |
| 298 | + this.firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode; |
| 299 | + this.textNodes = [this.firstTextNode]; |
| 300 | + } |
| 301 | + |
| 302 | + Merge.prototype = { |
| 303 | + doMerge: function() { |
| 304 | + var textBits = [], textNode, parent, text; |
| 305 | + for (var i = 0, len = this.textNodes.length; i < len; ++i) { |
| 306 | + textNode = this.textNodes[i]; |
| 307 | + parent = textNode.parentNode; |
| 308 | + textBits[i] = textNode.data; |
| 309 | + if (i) { |
| 310 | + parent.removeChild(textNode); |
| 311 | + if (!parent.hasChildNodes()) { |
| 312 | + parent.parentNode.removeChild(parent); |
| 313 | + } |
| 314 | + } |
| 315 | + } |
| 316 | + this.firstTextNode.data = text = textBits.join(""); |
| 317 | + return text; |
| 318 | + }, |
| 319 | + |
| 320 | + getLength: function() { |
| 321 | + var i = this.textNodes.length, len = 0; |
| 322 | + while (i--) { |
| 323 | + len += this.textNodes[i].length; |
| 324 | + } |
| 325 | + return len; |
| 326 | + }, |
| 327 | + |
| 328 | + toString: function() { |
| 329 | + var textBits = []; |
| 330 | + for (var i = 0, len = this.textNodes.length; i < len; ++i) { |
| 331 | + textBits[i] = "'" + this.textNodes[i].data + "'"; |
| 332 | + } |
| 333 | + return "[Merge(" + textBits.join(",") + ")]"; |
| 334 | + } |
| 335 | + }; |
| 336 | + |
| 337 | + var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly"]; |
| 338 | + |
| 339 | + // Allow "class" as a property name in object properties |
| 340 | + var mappedPropertyNames = {"class" : "className"}; |
| 341 | + |
| 342 | + function CssClassApplier(cssClass, options, tagNames) { |
| 343 | + this.cssClass = cssClass; |
| 344 | + var normalize, i, len, propName; |
| 345 | + |
| 346 | + var elementPropertiesFromOptions = null; |
| 347 | + |
| 348 | + // Initialize from options object |
| 349 | + if (typeof options == "object" && options !== null) { |
| 350 | + tagNames = options.tagNames; |
| 351 | + elementPropertiesFromOptions = options.elementProperties; |
| 352 | + |
| 353 | + for (i = 0; propName = optionProperties[i++]; ) { |
| 354 | + if (options.hasOwnProperty(propName)) { |
| 355 | + this[propName] = options[propName]; |
| 356 | + } |
| 357 | + } |
| 358 | + normalize = options.normalize; |
| 359 | + } else { |
| 360 | + normalize = options; |
| 361 | + } |
| 362 | + |
| 363 | + // Backwards compatibility: the second parameter can also be a Boolean indicating whether normalization |
| 364 | + this.normalize = (typeof normalize == "undefined") ? true : normalize; |
| 365 | + |
| 366 | + // Initialize element properties and attribute exceptions |
| 367 | + this.attrExceptions = []; |
| 368 | + var el = document.createElement(this.elementTagName); |
| 369 | + this.elementProperties = {}; |
| 370 | + for (var p in elementPropertiesFromOptions) { |
| 371 | + if (elementPropertiesFromOptions.hasOwnProperty(p)) { |
| 372 | + // Map "class" to "className" |
| 373 | + if (mappedPropertyNames.hasOwnProperty(p)) { |
| 374 | + p = mappedPropertyNames[p]; |
| 375 | + } |
| 376 | + el[p] = elementPropertiesFromOptions[p]; |
| 377 | + |
| 378 | + // Copy the property back from the dummy element so that later comparisons to check whether elements |
| 379 | + // may be removed are checking against the right value. For example, the href property of an element |
| 380 | + // returns a fully qualified URL even if it was previously assigned a relative URL. |
| 381 | + this.elementProperties[p] = el[p]; |
| 382 | + this.attrExceptions.push(p); |
| 383 | + } |
| 384 | + } |
| 385 | + |
| 386 | + this.elementSortedClassName = this.elementProperties.hasOwnProperty("className") ? |
| 387 | + sortClassName(this.elementProperties.className + " " + cssClass) : cssClass; |
| 388 | + |
| 389 | + // Initialize tag names |
| 390 | + this.applyToAnyTagName = false; |
| 391 | + var type = typeof tagNames; |
| 392 | + if (type == "string") { |
| 393 | + if (tagNames == "*") { |
| 394 | + this.applyToAnyTagName = true; |
| 395 | + } else { |
| 396 | + this.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/); |
| 397 | + } |
| 398 | + } else if (type == "object" && typeof tagNames.length == "number") { |
| 399 | + this.tagNames = []; |
| 400 | + for (i = 0, len = tagNames.length; i < len; ++i) { |
| 401 | + if (tagNames[i] == "*") { |
| 402 | + this.applyToAnyTagName = true; |
| 403 | + } else { |
| 404 | + this.tagNames.push(tagNames[i].toLowerCase()); |
| 405 | + } |
| 406 | + } |
| 407 | + } else { |
| 408 | + this.tagNames = [this.elementTagName]; |
| 409 | + } |
| 410 | + } |
| 411 | + |
| 412 | + CssClassApplier.prototype = { |
| 413 | + elementTagName: defaultTagName, |
| 414 | + elementProperties: {}, |
| 415 | + ignoreWhiteSpace: true, |
| 416 | + applyToEditableOnly: false, |
| 417 | + |
| 418 | + hasClass: function(node) { |
| 419 | + return node.nodeType == 1 && dom.arrayContains(this.tagNames, node.tagName.toLowerCase()) && hasClass(node, this.cssClass); |
| 420 | + }, |
| 421 | + |
| 422 | + getSelfOrAncestorWithClass: function(node) { |
| 423 | + while (node) { |
| 424 | + if (this.hasClass(node, this.cssClass)) { |
| 425 | + return node; |
| 426 | + } |
| 427 | + node = node.parentNode; |
| 428 | + } |
| 429 | + return null; |
| 430 | + }, |
| 431 | + |
| 432 | + isModifiable: function(node) { |
| 433 | + return !this.applyToEditableOnly || isEditable(node); |
| 434 | + }, |
| 435 | + |
| 436 | + // White space adjacent to an unwrappable node can be ignored for wrapping |
| 437 | + isIgnorableWhiteSpaceNode: function(node) { |
| 438 | + return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node); |
| 439 | + }, |
| 440 | + |
| 441 | + // Normalizes nodes after applying a CSS class to a Range. |
| 442 | + postApply: function(textNodes, range, isUndo) { |
| 443 | + |
| 444 | + var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1]; |
| 445 | + |
| 446 | + var merges = [], currentMerge; |
| 447 | + |
| 448 | + var rangeStartNode = firstNode, rangeEndNode = lastNode; |
| 449 | + var rangeStartOffset = 0, rangeEndOffset = lastNode.length; |
| 450 | + |
| 451 | + var textNode, precedingTextNode; |
| 452 | + |
| 453 | + for (var i = 0, len = textNodes.length; i < len; ++i) { |
| 454 | + textNode = textNodes[i]; |
| 455 | + precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo); |
| 456 | + |
| 457 | + if (precedingTextNode) { |
| 458 | + if (!currentMerge) { |
| 459 | + currentMerge = new Merge(precedingTextNode); |
| 460 | + merges.push(currentMerge); |
| 461 | + } |
| 462 | + currentMerge.textNodes.push(textNode); |
| 463 | + if (textNode === firstNode) { |
| 464 | + rangeStartNode = currentMerge.firstTextNode; |
| 465 | + rangeStartOffset = rangeStartNode.length; |
| 466 | + } |
| 467 | + if (textNode === lastNode) { |
| 468 | + rangeEndNode = currentMerge.firstTextNode; |
| 469 | + rangeEndOffset = currentMerge.getLength(); |
| 470 | + } |
| 471 | + } else { |
| 472 | + currentMerge = null; |
| 473 | + } |
| 474 | + } |
| 475 | + |
| 476 | + // Test whether the first node after the range needs merging |
| 477 | + var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo); |
| 478 | + |
| 479 | + if (nextTextNode) { |
| 480 | + if (!currentMerge) { |
| 481 | + currentMerge = new Merge(lastNode); |
| 482 | + merges.push(currentMerge); |
| 483 | + } |
| 484 | + currentMerge.textNodes.push(nextTextNode); |
| 485 | + } |
| 486 | + |
| 487 | + // Do the merges |
| 488 | + if (merges.length) { |
| 489 | + |
| 490 | + for (i = 0, len = merges.length; i < len; ++i) { |
| 491 | + merges[i].doMerge(); |
| 492 | + } |
| 493 | + |
| 494 | + |
| 495 | + // Set the range boundaries |
| 496 | + range.setStart(rangeStartNode, rangeStartOffset); |
| 497 | + range.setEnd(rangeEndNode, rangeEndOffset); |
| 498 | + } |
| 499 | + |
| 500 | + }, |
| 501 | + |
| 502 | + createContainer: function(doc) { |
| 503 | + var el = doc.createElement(this.elementTagName); |
| 504 | + api.util.extend(el, this.elementProperties); |
| 505 | + addClass(el, this.cssClass); |
| 506 | + return el; |
| 507 | + }, |
| 508 | + |
| 509 | + applyToTextNode: function(textNode) { |
| 510 | + |
| 511 | + |
| 512 | + var parent = textNode.parentNode; |
| 513 | + if (parent.childNodes.length == 1 && dom.arrayContains(this.tagNames, parent.tagName.toLowerCase())) { |
| 514 | + addClass(parent, this.cssClass); |
| 515 | + } else { |
| 516 | + var el = this.createContainer(dom.getDocument(textNode)); |
| 517 | + textNode.parentNode.insertBefore(el, textNode); |
| 518 | + el.appendChild(textNode); |
| 519 | + } |
| 520 | + |
| 521 | + }, |
| 522 | + |
| 523 | + isRemovable: function(el) { |
| 524 | + return el.tagName.toLowerCase() == this.elementTagName |
| 525 | + && getSortedClassName(el) == this.elementSortedClassName |
| 526 | + && elementHasProps(el, this.elementProperties) |
| 527 | + && !elementHasNonClassAttributes(el, this.attrExceptions) |
| 528 | + && this.isModifiable(el); |
| 529 | + }, |
| 530 | + |
| 531 | + undoToTextNode: function(textNode, range, ancestorWithClass) { |
| 532 | + |
| 533 | + if (!range.containsNode(ancestorWithClass)) { |
| 534 | + // Split out the portion of the ancestor from which we can remove the CSS class |
| 535 | + //var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass); |
| 536 | + var ancestorRange = range.cloneRange(); |
| 537 | + ancestorRange.selectNode(ancestorWithClass); |
| 538 | + |
| 539 | + if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)/* && isSplitPoint(range.endContainer, range.endOffset)*/) { |
| 540 | + splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, [range]); |
| 541 | + range.setEndAfter(ancestorWithClass); |
| 542 | + } |
| 543 | + if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)/* && isSplitPoint(range.startContainer, range.startOffset)*/) { |
| 544 | + ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, [range]); |
| 545 | + } |
| 546 | + } |
| 547 | + |
| 548 | + if (this.isRemovable(ancestorWithClass)) { |
| 549 | + replaceWithOwnChildren(ancestorWithClass); |
| 550 | + } else { |
| 551 | + removeClass(ancestorWithClass, this.cssClass); |
| 552 | + } |
| 553 | + }, |
| 554 | + |
| 555 | + applyToRange: function(range) { |
| 556 | + range.splitBoundaries(); |
| 557 | + var textNodes = getEffectiveTextNodes(range); |
| 558 | + |
| 559 | + if (textNodes.length) { |
| 560 | + var textNode; |
| 561 | + |
| 562 | + for (var i = 0, len = textNodes.length; i < len; ++i) { |
| 563 | + textNode = textNodes[i]; |
| 564 | + |
| 565 | + if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode) |
| 566 | + && this.isModifiable(textNode)) { |
| 567 | + this.applyToTextNode(textNode); |
| 568 | + } |
| 569 | + } |
| 570 | + range.setStart(textNodes[0], 0); |
| 571 | + textNode = textNodes[textNodes.length - 1]; |
| 572 | + range.setEnd(textNode, textNode.length); |
| 573 | + if (this.normalize) { |
| 574 | + this.postApply(textNodes, range, false); |
| 575 | + } |
| 576 | + } |
| 577 | + }, |
| 578 | + |
| 579 | + applyToSelection: function(win) { |
| 580 | + |
| 581 | + win = win || window; |
| 582 | + var sel = api.getSelection(win); |
| 583 | + |
| 584 | + var range, ranges = sel.getAllRanges(); |
| 585 | + sel.removeAllRanges(); |
| 586 | + var i = ranges.length; |
| 587 | + while (i--) { |
| 588 | + range = ranges[i]; |
| 589 | + this.applyToRange(range); |
| 590 | + sel.addRange(range); |
| 591 | + } |
| 592 | + |
| 593 | + }, |
| 594 | + |
| 595 | + undoToRange: function(range) { |
| 596 | + |
| 597 | + range.splitBoundaries(); |
| 598 | + var textNodes = getEffectiveTextNodes(range); |
| 599 | + var textNode, ancestorWithClass; |
| 600 | + var lastTextNode = textNodes[textNodes.length - 1]; |
| 601 | + |
| 602 | + if (textNodes.length) { |
| 603 | + for (var i = 0, len = textNodes.length; i < len; ++i) { |
| 604 | + textNode = textNodes[i]; |
| 605 | + ancestorWithClass = this.getSelfOrAncestorWithClass(textNode); |
| 606 | + if (ancestorWithClass && this.isModifiable(textNode)) { |
| 607 | + this.undoToTextNode(textNode, range, ancestorWithClass); |
| 608 | + } |
| 609 | + |
| 610 | + // Ensure the range is still valid |
| 611 | + range.setStart(textNodes[0], 0); |
| 612 | + range.setEnd(lastTextNode, lastTextNode.length); |
| 613 | + } |
| 614 | + |
| 615 | + |
| 616 | + |
| 617 | + if (this.normalize) { |
| 618 | + this.postApply(textNodes, range, true); |
| 619 | + } |
| 620 | + } |
| 621 | + }, |
| 622 | + |
| 623 | + undoToSelection: function(win) { |
| 624 | + win = win || window; |
| 625 | + var sel = api.getSelection(win); |
| 626 | + var ranges = sel.getAllRanges(), range; |
| 627 | + sel.removeAllRanges(); |
| 628 | + for (var i = 0, len = ranges.length; i < len; ++i) { |
| 629 | + range = ranges[i]; |
| 630 | + this.undoToRange(range); |
| 631 | + sel.addRange(range); |
| 632 | + } |
| 633 | + }, |
| 634 | + |
| 635 | + getTextSelectedByRange: function(textNode, range) { |
| 636 | + var textRange = range.cloneRange(); |
| 637 | + textRange.selectNodeContents(textNode); |
| 638 | + |
| 639 | + var intersectionRange = textRange.intersection(range); |
| 640 | + var text = intersectionRange ? intersectionRange.toString() : ""; |
| 641 | + textRange.detach(); |
| 642 | + |
| 643 | + return text; |
| 644 | + }, |
| 645 | + |
| 646 | + isAppliedToRange: function(range) { |
| 647 | + if (range.collapsed) { |
| 648 | + return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer); |
| 649 | + } else { |
| 650 | + var textNodes = range.getNodes( [3] ); |
| 651 | + for (var i = 0, textNode; textNode = textNodes[i++]; ) { |
| 652 | + if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode) |
| 653 | + && this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) { |
| 654 | + return false; |
| 655 | + } |
| 656 | + } |
| 657 | + return true; |
| 658 | + } |
| 659 | + }, |
| 660 | + |
| 661 | + isAppliedToSelection: function(win) { |
| 662 | + win = win || window; |
| 663 | + var sel = api.getSelection(win); |
| 664 | + var ranges = sel.getAllRanges(); |
| 665 | + var i = ranges.length; |
| 666 | + while (i--) { |
| 667 | + if (!this.isAppliedToRange(ranges[i])) { |
| 668 | + return false; |
| 669 | + } |
| 670 | + } |
| 671 | + |
| 672 | + return true; |
| 673 | + }, |
| 674 | + |
| 675 | + toggleRange: function(range) { |
| 676 | + if (this.isAppliedToRange(range)) { |
| 677 | + this.undoToRange(range); |
| 678 | + } else { |
| 679 | + this.applyToRange(range); |
| 680 | + } |
| 681 | + }, |
| 682 | + |
| 683 | + toggleSelection: function(win) { |
| 684 | + if (this.isAppliedToSelection(win)) { |
| 685 | + this.undoToSelection(win); |
| 686 | + } else { |
| 687 | + this.applyToSelection(win); |
| 688 | + } |
| 689 | + }, |
| 690 | + |
| 691 | + detach: function() {} |
| 692 | + }; |
| 693 | + |
| 694 | + function createCssClassApplier(cssClass, options, tagNames) { |
| 695 | + return new CssClassApplier(cssClass, options, tagNames); |
| 696 | + } |
| 697 | + |
| 698 | + CssClassApplier.util = { |
| 699 | + hasClass: hasClass, |
| 700 | + addClass: addClass, |
| 701 | + removeClass: removeClass, |
| 702 | + hasSameClasses: haveSameClasses, |
| 703 | + replaceWithOwnChildren: replaceWithOwnChildren, |
| 704 | + elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes, |
| 705 | + elementHasNonClassAttributes: elementHasNonClassAttributes, |
| 706 | + splitNodeAt: splitNodeAt, |
| 707 | + isEditableElement: isEditableElement, |
| 708 | + isEditingHost: isEditingHost, |
| 709 | + isEditable: isEditable |
| 710 | + }; |
| 711 | + |
| 712 | + api.CssClassApplier = CssClassApplier; |
| 713 | + api.createCssClassApplier = createCssClassApplier; |
| 714 | +}); |
Property changes on: trunk/extensions/VisualEditor/modules/rangy/rangy-cssclassapplier.js |
___________________________________________________________________ |
Added: svn:eol-style |
715 | 715 | + native |
Index: trunk/extensions/VisualEditor/modules/rangy/rangy-position.js |
— | — | @@ -1,364 +1,364 @@ |
2 | | -/**
|
3 | | - * @license Position module for Rangy.
|
4 | | - * Extensions to Range and Selection objects to provide access to pixel positions relative to the viewport or document.
|
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 %%build:year%%, Tim Down
|
12 | | - * Licensed under the MIT license.
|
13 | | - * Version: %%build:version%%
|
14 | | - * Build date: %%build:date%%
|
15 | | - */
|
16 | | -rangy.createModule("Coordinates", function(api, module) {
|
17 | | - api.requireModules( ["WrappedSelection", "WrappedRange"] );
|
18 | | -
|
19 | | - var NUMBER = "number";
|
20 | | - var WrappedRange = api.WrappedRange;
|
21 | | - var dom = api.dom, util = api.util;
|
22 | | -
|
23 | | - // Since Rangy can deal with multiple documents, we have to do the checks every time, unless we cache a
|
24 | | - // getScrollPosition function in each document. This would necessarily pollute the document's global
|
25 | | - // namespace, which I'm choosing to view as a greater evil than a slight performance hit.
|
26 | | - function getScrollPosition(win) {
|
27 | | - var x = 0, y = 0;
|
28 | | - if (typeof win.pageXOffset == NUMBER && typeof win.pageYOffset == NUMBER) {
|
29 | | - x = win.pageXOffset;
|
30 | | - y = win.pageYOffset;
|
31 | | - } else {
|
32 | | - var doc = win.document;
|
33 | | - var docEl = doc.documentElement;
|
34 | | - var compatMode = doc.compatMode;
|
35 | | - var scrollEl = (typeof compatMode == "string" && compatMode.indexOf("CSS") >= 0 && docEl)
|
36 | | - ? docEl : dom.getBody(doc);
|
37 | | -
|
38 | | - if (scrollEl && typeof scrollEl.scrollLeft == NUMBER && typeof scrollEl.scrollTop == NUMBER) {
|
39 | | - try {
|
40 | | - x = scrollEl.scrollLeft;
|
41 | | - y = scrollEl.scrollTop;
|
42 | | - } catch (ex) {}
|
43 | | - }
|
44 | | - }
|
45 | | - return { x: x, y: y };
|
46 | | - }
|
47 | | -
|
48 | | - function getAncestorElement(node, tagName) {
|
49 | | - tagName = tagName.toLowerCase();
|
50 | | - while (node) {
|
51 | | - if (node.nodeType == 1 && node.tagName.toLowerCase() == tagName) {
|
52 | | - return node;
|
53 | | - }
|
54 | | - node = node.parentNode;
|
55 | | - }
|
56 | | - return null;
|
57 | | - }
|
58 | | -
|
59 | | - function Rect(top, right, bottom, left) {
|
60 | | - this.top = top;
|
61 | | - this.right = right;
|
62 | | - this.bottom = bottom;
|
63 | | - this.left = left;
|
64 | | - this.width = right - left;
|
65 | | - this.height = bottom - top;
|
66 | | - }
|
67 | | -
|
68 | | - function createRelativeRect(rect, dx, dy) {
|
69 | | - return new Rect(rect.top + dy, rect.right + dx, rect.bottom + dy, rect.left + dx);
|
70 | | - }
|
71 | | -
|
72 | | - function adjustClientRect(rect, doc) {
|
73 | | - // Older IEs have an issue with a two pixel margin on the body element
|
74 | | - var dx = 0, dy = 0;
|
75 | | - var docEl = doc.documentElement, body = dom.getBody(doc);
|
76 | | - var container = (docEl.clientWidth === 0 && typeof body.clientTop == NUMBER) ? body : docEl;
|
77 | | - var clientLeft = container.clientLeft, clientTop = container.clientTop;
|
78 | | - if (clientLeft) {
|
79 | | - dx = -clientLeft;
|
80 | | - }
|
81 | | - if (clientTop) {
|
82 | | - dy = -clientTop;
|
83 | | - }
|
84 | | - return createRelativeRect(rect, dx, dy);
|
85 | | - }
|
86 | | -
|
87 | | - function mergeRects(rects) {
|
88 | | - var tops = [], bottoms = [], lefts = [], rights = [];
|
89 | | - for (var i = 0, len = rects.length, rect; i < len; ++i) {
|
90 | | - rect = rects[i];
|
91 | | - if (rect) {
|
92 | | - tops.push(rect.top);
|
93 | | - bottoms.push(rect.bottom);
|
94 | | - lefts.push(rect.left);
|
95 | | - rights.push(rect.right);
|
96 | | - }
|
97 | | - }
|
98 | | - return new Rect(
|
99 | | - Math.min.apply(Math, tops),
|
100 | | - Math.max.apply(Math, rights),
|
101 | | - Math.max.apply(Math, bottoms),
|
102 | | - Math.min.apply(Math, lefts)
|
103 | | - );
|
104 | | - }
|
105 | | -
|
106 | | - (function() {
|
107 | | -
|
108 | | - // Test that <span> elements support getBoundingClientRect
|
109 | | - var span = document.createElement("span");
|
110 | | - var elementSupportsGetBoundingClientRect = util.isHostMethod(span, "getBoundingClientRect");
|
111 | | - span = null;
|
112 | | -
|
113 | | - // Test for getBoundingClientRect support in Range
|
114 | | - var rangeSupportsGetClientRects = false, rangeSupportsGetBoundingClientRect = false;
|
115 | | - if (api.features.implementsDomRange) {
|
116 | | - var testRange = api.createNativeRange();
|
117 | | - rangeSupportsGetClientRects = util.isHostMethod(testRange, "getClientRects");
|
118 | | - rangeSupportsGetBoundingClientRect = util.isHostMethod(testRange, "getBoundingClientRect");
|
119 | | - testRange.detach();
|
120 | | - }
|
121 | | -
|
122 | | - util.extend(api.features, {
|
123 | | - rangeSupportsGetBoundingClientRect: rangeSupportsGetBoundingClientRect,
|
124 | | - rangeSupportsGetClientRects: rangeSupportsGetClientRects,
|
125 | | - elementSupportsGetBoundingClientRect: elementSupportsGetBoundingClientRect
|
126 | | - });
|
127 | | -
|
128 | | - var createClientBoundaryPosGetter = function(isStart) {
|
129 | | - return function() {
|
130 | | - var boundaryRange = this.cloneRange();
|
131 | | - boundaryRange.collapse(isStart);
|
132 | | - var rect = boundaryRange.getBoundingClientRect();
|
133 | | - return { x: rect[isStart ? "left" : "right"], y: rect[isStart ? "top" : "bottom"] };
|
134 | | - };
|
135 | | - };
|
136 | | -
|
137 | | - var rangeProto = api.rangePrototype;
|
138 | | -
|
139 | | - if (api.features.implementsTextRange && elementSupportsGetBoundingClientRect) {
|
140 | | - rangeProto.getBoundingClientRect = function() {
|
141 | | - // We need a TextRange
|
142 | | - var textRange = WrappedRange.rangeToTextRange(this);
|
143 | | -
|
144 | | - // Work around table problems (table cell bounding rects seem not to count if TextRange spans cells)
|
145 | | - var cells = this.getNodes([1], function(el) {
|
146 | | - return /^t[dh]$/i.test(el.tagName);
|
147 | | - });
|
148 | | -
|
149 | | - // Merge rects for each cell selected by the range into overall rect
|
150 | | - var rect, rects = [];
|
151 | | - if (cells.length > 0) {
|
152 | | - var lastTable = getAncestorElement(this.startContainer, "table");
|
153 | | -
|
154 | | - for (var i = 0, cell, tempTextRange, table, subRange, subRect; cell = cells[i]; ++i) {
|
155 | | - // Handle non-table sections of the range
|
156 | | - table = getAncestorElement(cell, "table");
|
157 | | - if (!lastTable || table != lastTable) {
|
158 | | - // There is a section of the range prior to the current table, or lying between tables.
|
159 | | - // Merge in its rect
|
160 | | - subRange = this.cloneRange();
|
161 | | - if (lastTable) {
|
162 | | - subRange.setStartAfter(lastTable);
|
163 | | - }
|
164 | | - subRange.setEndBefore(table);
|
165 | | - rects.push(WrappedRange.rangeToTextRange(subRange).getBoundingClientRect());
|
166 | | - }
|
167 | | -
|
168 | | - if (this.containsNode(cell)) {
|
169 | | - rects.push(cell.getBoundingClientRect());
|
170 | | - } else {
|
171 | | - tempTextRange = textRange.duplicate();
|
172 | | - tempTextRange.moveToElementText(cell);
|
173 | | - if (tempTextRange.compareEndPoints("StartToStart", textRange) == -1) {
|
174 | | - tempTextRange.setEndPoint("StartToStart", textRange);
|
175 | | - } else if (tempTextRange.compareEndPoints("EndToEnd", textRange) == 1) {
|
176 | | - tempTextRange.setEndPoint("EndToEnd", textRange);
|
177 | | - }
|
178 | | - rects.push(tempTextRange.getBoundingClientRect());
|
179 | | - }
|
180 | | - lastTable = table;
|
181 | | - }
|
182 | | -
|
183 | | - // Merge in the rect for any content lying after the final table
|
184 | | - var endTable = getAncestorElement(this.endContainer, "table");
|
185 | | - if (!endTable && lastTable) {
|
186 | | - subRange = this.cloneRange();
|
187 | | - subRange.setStartAfter(lastTable);
|
188 | | - rects.push(WrappedRange.rangeToTextRange(subRange).getBoundingClientRect());
|
189 | | - }
|
190 | | - rect = mergeRects(rects);
|
191 | | - } else {
|
192 | | - rect = textRange.getBoundingClientRect();
|
193 | | - }
|
194 | | -
|
195 | | - return adjustClientRect(rect, dom.getDocument(this.startContainer));
|
196 | | - };
|
197 | | - } else if (api.features.implementsDomRange) {
|
198 | | - var createWrappedRange = function(range) {
|
199 | | - return (range instanceof WrappedRange) ? range : new WrappedRange(range);
|
200 | | - };
|
201 | | -
|
202 | | - if (rangeSupportsGetBoundingClientRect) {
|
203 | | - rangeProto.getBoundingClientRect = function() {
|
204 | | - var nativeRange = createWrappedRange(this).nativeRange;
|
205 | | - // Test for WebKit getBoundingClientRect bug (https://bugs.webkit.org/show_bug.cgi?id=65324)
|
206 | | - var rect = nativeRange.getBoundingClientRect() || nativeRange.getClientRects()[0];
|
207 | | - return adjustClientRect(rect, dom.getDocument(this.startContainer));
|
208 | | - };
|
209 | | -
|
210 | | - if (rangeSupportsGetClientRects) {
|
211 | | - createClientBoundaryPosGetter = function(isStart) {
|
212 | | - return function() {
|
213 | | - var rect, nativeRange = createWrappedRange(this).nativeRange;
|
214 | | - if (isStart) {
|
215 | | - rect = nativeRange.getClientRects()[0];
|
216 | | - return { x: rect.left, y: rect.top };
|
217 | | - } else {
|
218 | | - var rects = nativeRange.getClientRects();
|
219 | | - rect = rects[rects.length - 1];
|
220 | | - return { x: rect.right, y: rect.bottom };
|
221 | | - }
|
222 | | - };
|
223 | | - }
|
224 | | - }
|
225 | | - } else {
|
226 | | - var getElementBoundingClientRect = elementSupportsGetBoundingClientRect ?
|
227 | | - function(el) {
|
228 | | - return adjustClientRect(el.getBoundingClientRect(), dom.getDocument(el));
|
229 | | - } :
|
230 | | -
|
231 | | - // This implementation is very naive. There are many browser quirks that make it extremely
|
232 | | - // difficult to get accurate element coordinates in all situations
|
233 | | - function(el) {
|
234 | | - var x = 0, y = 0, offsetEl = el, width = el.offsetWidth, height = el.offsetHeight;
|
235 | | - while (offsetEl) {
|
236 | | - x += offsetEl.offsetLeft;
|
237 | | - y += offsetEl.offsetTop;
|
238 | | - offsetEl = offsetEl.offsetParent;
|
239 | | - }
|
240 | | -
|
241 | | - return adjustClientRect(new Rect(y, x + width, y + height, x), dom.getDocument(el));
|
242 | | - };
|
243 | | -
|
244 | | - var getRectFromBoundaries = function(range) {
|
245 | | - var rect;
|
246 | | - range.splitBoundaries();
|
247 | | - var span = document.createElement("span");
|
248 | | -
|
249 | | - if (range.collapsed) {
|
250 | | - range.insertNode(span);
|
251 | | - rect = getElementBoundingClientRect(span);
|
252 | | - span.parentNode.removeChild(span);
|
253 | | - } else {
|
254 | | - // TODO: This isn't right. I'm not sure it can be made right sensibly. Consider what to do.
|
255 | | - // This doesn't consider all the line boxes it needs to consider.
|
256 | | - var workingRange = range.cloneRange();
|
257 | | -
|
258 | | - // Get the start rectangle
|
259 | | - workingRange.collapse(true);
|
260 | | - workingRange.insertNode(span);
|
261 | | - var startRect = getElementBoundingClientRect(span);
|
262 | | - span.parentNode.removeChild(span);
|
263 | | -
|
264 | | - // Get the end rectangle
|
265 | | - workingRange.collapseToPoint(range.endContainer, range.endOffset);
|
266 | | - workingRange.insertNode(span);
|
267 | | - var endRect = getElementBoundingClientRect(span);
|
268 | | - span.parentNode.removeChild(span);
|
269 | | -
|
270 | | - // Merge the start and end rects
|
271 | | - var rects = [startRect, endRect];
|
272 | | -
|
273 | | - // Merge in rectangles for all elements in the range
|
274 | | - var elements = range.getNodes([1], function(el) {
|
275 | | - return range.containsNode(el);
|
276 | | - });
|
277 | | -
|
278 | | - for (var i = 0, len = elements.length; i < len; ++i) {
|
279 | | - rects.push(getElementBoundingClientRect(elements[i]));
|
280 | | - }
|
281 | | - rect = mergeRects(rects)
|
282 | | - }
|
283 | | -
|
284 | | - // Clean up
|
285 | | - range.normalizeBoundaries();
|
286 | | - return rect;
|
287 | | - };
|
288 | | -
|
289 | | - rangeProto.getBoundingClientRect = function(range) {
|
290 | | - return getRectFromBoundaries(createWrappedRange(range));
|
291 | | - };
|
292 | | - }
|
293 | | -
|
294 | | - function createDocumentBoundaryPosGetter(isStart) {
|
295 | | - return function() {
|
296 | | - var pos = this["get" + (isStart ? "Start" : "End") + "ClientPos"]();
|
297 | | - var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) );
|
298 | | - return { x: pos.x + scrollPos.x, y: pos.y + scrollPos.y };
|
299 | | - };
|
300 | | - }
|
301 | | - }
|
302 | | -
|
303 | | - util.extend(rangeProto, {
|
304 | | - getBoundingDocumentRect: function() {
|
305 | | - var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) );
|
306 | | - return createRelativeRect(this.getBoundingClientRect(), scrollPos.x, scrollPos.y);
|
307 | | - },
|
308 | | -
|
309 | | - getStartClientPos: createClientBoundaryPosGetter(true),
|
310 | | - getEndClientPos: createClientBoundaryPosGetter(false),
|
311 | | -
|
312 | | - getStartDocumentPos: createDocumentBoundaryPosGetter(true),
|
313 | | - getEndDocumentPos: createDocumentBoundaryPosGetter(false)
|
314 | | - });
|
315 | | - })();
|
316 | | -
|
317 | | - // Add Selection methods
|
318 | | - (function() {
|
319 | | - function compareRanges(r1, r2) {
|
320 | | - return r1.compareBoundaryPoints(r2.START_TO_START, r2);
|
321 | | - }
|
322 | | -
|
323 | | - function createSelectionRectGetter(isDocument) {
|
324 | | - return function() {
|
325 | | - var rangeMethodName = "getBounding" + (isDocument ? "Document" : "Client") + "Rect";
|
326 | | - var rects = [];
|
327 | | - for (var i = 0, rect = null, rangeRect; i < this.rangeCount; ++i) {
|
328 | | - rects.push(this.getRangeAt(i)[rangeMethodName]());
|
329 | | - }
|
330 | | - return mergeRects(rects);
|
331 | | - };
|
332 | | - }
|
333 | | -
|
334 | | - function createSelectionBoundaryPosGetter(isStart, isDocument) {
|
335 | | - return function() {
|
336 | | - if (this.rangeCount == 0) {
|
337 | | - return null;
|
338 | | - }
|
339 | | -
|
340 | | - var posType = isDocument ? "Document" : "Client";
|
341 | | -
|
342 | | - var ranges = this.getAllRanges();
|
343 | | - if (ranges.length > 1) {
|
344 | | - // Order the ranges by position within the DOM
|
345 | | - ranges.sort(compareRanges);
|
346 | | - }
|
347 | | -
|
348 | | - return isStart ?
|
349 | | - ranges[0]["getStart" + posType + "Pos"]() :
|
350 | | - ranges[ranges.length - 1]["getEnd" + posType + "Pos"]();
|
351 | | - };
|
352 | | - }
|
353 | | -
|
354 | | - util.extend(api.selectionPrototype, {
|
355 | | - getBoundingClientRect: createSelectionRectGetter(false),
|
356 | | - getBoundingDocumentRect: createSelectionRectGetter(true),
|
357 | | -
|
358 | | - getStartClientPos: createSelectionBoundaryPosGetter(true, false),
|
359 | | - getEndClientPos: createSelectionBoundaryPosGetter(false, false),
|
360 | | -
|
361 | | - getStartDocumentPos: createSelectionBoundaryPosGetter(true, true),
|
362 | | - getEndDocumentPos: createSelectionBoundaryPosGetter(false, true)
|
363 | | - });
|
364 | | - })();
|
365 | | -});
|
| 2 | +/** |
| 3 | + * @license Position module for Rangy. |
| 4 | + * Extensions to Range and Selection objects to provide access to pixel positions relative to the viewport or document. |
| 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 %%build:year%%, Tim Down |
| 12 | + * Licensed under the MIT license. |
| 13 | + * Version: %%build:version%% |
| 14 | + * Build date: %%build:date%% |
| 15 | + */ |
| 16 | +rangy.createModule("Coordinates", function(api, module) { |
| 17 | + api.requireModules( ["WrappedSelection", "WrappedRange"] ); |
| 18 | + |
| 19 | + var NUMBER = "number"; |
| 20 | + var WrappedRange = api.WrappedRange; |
| 21 | + var dom = api.dom, util = api.util; |
| 22 | + |
| 23 | + // Since Rangy can deal with multiple documents, we have to do the checks every time, unless we cache a |
| 24 | + // getScrollPosition function in each document. This would necessarily pollute the document's global |
| 25 | + // namespace, which I'm choosing to view as a greater evil than a slight performance hit. |
| 26 | + function getScrollPosition(win) { |
| 27 | + var x = 0, y = 0; |
| 28 | + if (typeof win.pageXOffset == NUMBER && typeof win.pageYOffset == NUMBER) { |
| 29 | + x = win.pageXOffset; |
| 30 | + y = win.pageYOffset; |
| 31 | + } else { |
| 32 | + var doc = win.document; |
| 33 | + var docEl = doc.documentElement; |
| 34 | + var compatMode = doc.compatMode; |
| 35 | + var scrollEl = (typeof compatMode == "string" && compatMode.indexOf("CSS") >= 0 && docEl) |
| 36 | + ? docEl : dom.getBody(doc); |
| 37 | + |
| 38 | + if (scrollEl && typeof scrollEl.scrollLeft == NUMBER && typeof scrollEl.scrollTop == NUMBER) { |
| 39 | + try { |
| 40 | + x = scrollEl.scrollLeft; |
| 41 | + y = scrollEl.scrollTop; |
| 42 | + } catch (ex) {} |
| 43 | + } |
| 44 | + } |
| 45 | + return { x: x, y: y }; |
| 46 | + } |
| 47 | + |
| 48 | + function getAncestorElement(node, tagName) { |
| 49 | + tagName = tagName.toLowerCase(); |
| 50 | + while (node) { |
| 51 | + if (node.nodeType == 1 && node.tagName.toLowerCase() == tagName) { |
| 52 | + return node; |
| 53 | + } |
| 54 | + node = node.parentNode; |
| 55 | + } |
| 56 | + return null; |
| 57 | + } |
| 58 | + |
| 59 | + function Rect(top, right, bottom, left) { |
| 60 | + this.top = top; |
| 61 | + this.right = right; |
| 62 | + this.bottom = bottom; |
| 63 | + this.left = left; |
| 64 | + this.width = right - left; |
| 65 | + this.height = bottom - top; |
| 66 | + } |
| 67 | + |
| 68 | + function createRelativeRect(rect, dx, dy) { |
| 69 | + return new Rect(rect.top + dy, rect.right + dx, rect.bottom + dy, rect.left + dx); |
| 70 | + } |
| 71 | + |
| 72 | + function adjustClientRect(rect, doc) { |
| 73 | + // Older IEs have an issue with a two pixel margin on the body element |
| 74 | + var dx = 0, dy = 0; |
| 75 | + var docEl = doc.documentElement, body = dom.getBody(doc); |
| 76 | + var container = (docEl.clientWidth === 0 && typeof body.clientTop == NUMBER) ? body : docEl; |
| 77 | + var clientLeft = container.clientLeft, clientTop = container.clientTop; |
| 78 | + if (clientLeft) { |
| 79 | + dx = -clientLeft; |
| 80 | + } |
| 81 | + if (clientTop) { |
| 82 | + dy = -clientTop; |
| 83 | + } |
| 84 | + return createRelativeRect(rect, dx, dy); |
| 85 | + } |
| 86 | + |
| 87 | + function mergeRects(rects) { |
| 88 | + var tops = [], bottoms = [], lefts = [], rights = []; |
| 89 | + for (var i = 0, len = rects.length, rect; i < len; ++i) { |
| 90 | + rect = rects[i]; |
| 91 | + if (rect) { |
| 92 | + tops.push(rect.top); |
| 93 | + bottoms.push(rect.bottom); |
| 94 | + lefts.push(rect.left); |
| 95 | + rights.push(rect.right); |
| 96 | + } |
| 97 | + } |
| 98 | + return new Rect( |
| 99 | + Math.min.apply(Math, tops), |
| 100 | + Math.max.apply(Math, rights), |
| 101 | + Math.max.apply(Math, bottoms), |
| 102 | + Math.min.apply(Math, lefts) |
| 103 | + ); |
| 104 | + } |
| 105 | + |
| 106 | + (function() { |
| 107 | + |
| 108 | + // Test that <span> elements support getBoundingClientRect |
| 109 | + var span = document.createElement("span"); |
| 110 | + var elementSupportsGetBoundingClientRect = util.isHostMethod(span, "getBoundingClientRect"); |
| 111 | + span = null; |
| 112 | + |
| 113 | + // Test for getBoundingClientRect support in Range |
| 114 | + var rangeSupportsGetClientRects = false, rangeSupportsGetBoundingClientRect = false; |
| 115 | + if (api.features.implementsDomRange) { |
| 116 | + var testRange = api.createNativeRange(); |
| 117 | + rangeSupportsGetClientRects = util.isHostMethod(testRange, "getClientRects"); |
| 118 | + rangeSupportsGetBoundingClientRect = util.isHostMethod(testRange, "getBoundingClientRect"); |
| 119 | + testRange.detach(); |
| 120 | + } |
| 121 | + |
| 122 | + util.extend(api.features, { |
| 123 | + rangeSupportsGetBoundingClientRect: rangeSupportsGetBoundingClientRect, |
| 124 | + rangeSupportsGetClientRects: rangeSupportsGetClientRects, |
| 125 | + elementSupportsGetBoundingClientRect: elementSupportsGetBoundingClientRect |
| 126 | + }); |
| 127 | + |
| 128 | + var createClientBoundaryPosGetter = function(isStart) { |
| 129 | + return function() { |
| 130 | + var boundaryRange = this.cloneRange(); |
| 131 | + boundaryRange.collapse(isStart); |
| 132 | + var rect = boundaryRange.getBoundingClientRect(); |
| 133 | + return { x: rect[isStart ? "left" : "right"], y: rect[isStart ? "top" : "bottom"] }; |
| 134 | + }; |
| 135 | + }; |
| 136 | + |
| 137 | + var rangeProto = api.rangePrototype; |
| 138 | + |
| 139 | + if (api.features.implementsTextRange && elementSupportsGetBoundingClientRect) { |
| 140 | + rangeProto.getBoundingClientRect = function() { |
| 141 | + // We need a TextRange |
| 142 | + var textRange = WrappedRange.rangeToTextRange(this); |
| 143 | + |
| 144 | + // Work around table problems (table cell bounding rects seem not to count if TextRange spans cells) |
| 145 | + var cells = this.getNodes([1], function(el) { |
| 146 | + return /^t[dh]$/i.test(el.tagName); |
| 147 | + }); |
| 148 | + |
| 149 | + // Merge rects for each cell selected by the range into overall rect |
| 150 | + var rect, rects = []; |
| 151 | + if (cells.length > 0) { |
| 152 | + var lastTable = getAncestorElement(this.startContainer, "table"); |
| 153 | + |
| 154 | + for (var i = 0, cell, tempTextRange, table, subRange, subRect; cell = cells[i]; ++i) { |
| 155 | + // Handle non-table sections of the range |
| 156 | + table = getAncestorElement(cell, "table"); |
| 157 | + if (!lastTable || table != lastTable) { |
| 158 | + // There is a section of the range prior to the current table, or lying between tables. |
| 159 | + // Merge in its rect |
| 160 | + subRange = this.cloneRange(); |
| 161 | + if (lastTable) { |
| 162 | + subRange.setStartAfter(lastTable); |
| 163 | + } |
| 164 | + subRange.setEndBefore(table); |
| 165 | + rects.push(WrappedRange.rangeToTextRange(subRange).getBoundingClientRect()); |
| 166 | + } |
| 167 | + |
| 168 | + if (this.containsNode(cell)) { |
| 169 | + rects.push(cell.getBoundingClientRect()); |
| 170 | + } else { |
| 171 | + tempTextRange = textRange.duplicate(); |
| 172 | + tempTextRange.moveToElementText(cell); |
| 173 | + if (tempTextRange.compareEndPoints("StartToStart", textRange) == -1) { |
| 174 | + tempTextRange.setEndPoint("StartToStart", textRange); |
| 175 | + } else if (tempTextRange.compareEndPoints("EndToEnd", textRange) == 1) { |
| 176 | + tempTextRange.setEndPoint("EndToEnd", textRange); |
| 177 | + } |
| 178 | + rects.push(tempTextRange.getBoundingClientRect()); |
| 179 | + } |
| 180 | + lastTable = table; |
| 181 | + } |
| 182 | + |
| 183 | + // Merge in the rect for any content lying after the final table |
| 184 | + var endTable = getAncestorElement(this.endContainer, "table"); |
| 185 | + if (!endTable && lastTable) { |
| 186 | + subRange = this.cloneRange(); |
| 187 | + subRange.setStartAfter(lastTable); |
| 188 | + rects.push(WrappedRange.rangeToTextRange(subRange).getBoundingClientRect()); |
| 189 | + } |
| 190 | + rect = mergeRects(rects); |
| 191 | + } else { |
| 192 | + rect = textRange.getBoundingClientRect(); |
| 193 | + } |
| 194 | + |
| 195 | + return adjustClientRect(rect, dom.getDocument(this.startContainer)); |
| 196 | + }; |
| 197 | + } else if (api.features.implementsDomRange) { |
| 198 | + var createWrappedRange = function(range) { |
| 199 | + return (range instanceof WrappedRange) ? range : new WrappedRange(range); |
| 200 | + }; |
| 201 | + |
| 202 | + if (rangeSupportsGetBoundingClientRect) { |
| 203 | + rangeProto.getBoundingClientRect = function() { |
| 204 | + var nativeRange = createWrappedRange(this).nativeRange; |
| 205 | + // Test for WebKit getBoundingClientRect bug (https://bugs.webkit.org/show_bug.cgi?id=65324) |
| 206 | + var rect = nativeRange.getBoundingClientRect() || nativeRange.getClientRects()[0]; |
| 207 | + return adjustClientRect(rect, dom.getDocument(this.startContainer)); |
| 208 | + }; |
| 209 | + |
| 210 | + if (rangeSupportsGetClientRects) { |
| 211 | + createClientBoundaryPosGetter = function(isStart) { |
| 212 | + return function() { |
| 213 | + var rect, nativeRange = createWrappedRange(this).nativeRange; |
| 214 | + if (isStart) { |
| 215 | + rect = nativeRange.getClientRects()[0]; |
| 216 | + return { x: rect.left, y: rect.top }; |
| 217 | + } else { |
| 218 | + var rects = nativeRange.getClientRects(); |
| 219 | + rect = rects[rects.length - 1]; |
| 220 | + return { x: rect.right, y: rect.bottom }; |
| 221 | + } |
| 222 | + }; |
| 223 | + } |
| 224 | + } |
| 225 | + } else { |
| 226 | + var getElementBoundingClientRect = elementSupportsGetBoundingClientRect ? |
| 227 | + function(el) { |
| 228 | + return adjustClientRect(el.getBoundingClientRect(), dom.getDocument(el)); |
| 229 | + } : |
| 230 | + |
| 231 | + // This implementation is very naive. There are many browser quirks that make it extremely |
| 232 | + // difficult to get accurate element coordinates in all situations |
| 233 | + function(el) { |
| 234 | + var x = 0, y = 0, offsetEl = el, width = el.offsetWidth, height = el.offsetHeight; |
| 235 | + while (offsetEl) { |
| 236 | + x += offsetEl.offsetLeft; |
| 237 | + y += offsetEl.offsetTop; |
| 238 | + offsetEl = offsetEl.offsetParent; |
| 239 | + } |
| 240 | + |
| 241 | + return adjustClientRect(new Rect(y, x + width, y + height, x), dom.getDocument(el)); |
| 242 | + }; |
| 243 | + |
| 244 | + var getRectFromBoundaries = function(range) { |
| 245 | + var rect; |
| 246 | + range.splitBoundaries(); |
| 247 | + var span = document.createElement("span"); |
| 248 | + |
| 249 | + if (range.collapsed) { |
| 250 | + range.insertNode(span); |
| 251 | + rect = getElementBoundingClientRect(span); |
| 252 | + span.parentNode.removeChild(span); |
| 253 | + } else { |
| 254 | + // TODO: This isn't right. I'm not sure it can be made right sensibly. Consider what to do. |
| 255 | + // This doesn't consider all the line boxes it needs to consider. |
| 256 | + var workingRange = range.cloneRange(); |
| 257 | + |
| 258 | + // Get the start rectangle |
| 259 | + workingRange.collapse(true); |
| 260 | + workingRange.insertNode(span); |
| 261 | + var startRect = getElementBoundingClientRect(span); |
| 262 | + span.parentNode.removeChild(span); |
| 263 | + |
| 264 | + // Get the end rectangle |
| 265 | + workingRange.collapseToPoint(range.endContainer, range.endOffset); |
| 266 | + workingRange.insertNode(span); |
| 267 | + var endRect = getElementBoundingClientRect(span); |
| 268 | + span.parentNode.removeChild(span); |
| 269 | + |
| 270 | + // Merge the start and end rects |
| 271 | + var rects = [startRect, endRect]; |
| 272 | + |
| 273 | + // Merge in rectangles for all elements in the range |
| 274 | + var elements = range.getNodes([1], function(el) { |
| 275 | + return range.containsNode(el); |
| 276 | + }); |
| 277 | + |
| 278 | + for (var i = 0, len = elements.length; i < len; ++i) { |
| 279 | + rects.push(getElementBoundingClientRect(elements[i])); |
| 280 | + } |
| 281 | + rect = mergeRects(rects) |
| 282 | + } |
| 283 | + |
| 284 | + // Clean up |
| 285 | + range.normalizeBoundaries(); |
| 286 | + return rect; |
| 287 | + }; |
| 288 | + |
| 289 | + rangeProto.getBoundingClientRect = function(range) { |
| 290 | + return getRectFromBoundaries(createWrappedRange(range)); |
| 291 | + }; |
| 292 | + } |
| 293 | + |
| 294 | + function createDocumentBoundaryPosGetter(isStart) { |
| 295 | + return function() { |
| 296 | + var pos = this["get" + (isStart ? "Start" : "End") + "ClientPos"](); |
| 297 | + var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) ); |
| 298 | + return { x: pos.x + scrollPos.x, y: pos.y + scrollPos.y }; |
| 299 | + }; |
| 300 | + } |
| 301 | + } |
| 302 | + |
| 303 | + util.extend(rangeProto, { |
| 304 | + getBoundingDocumentRect: function() { |
| 305 | + var scrollPos = getScrollPosition( dom.getWindow(this.startContainer) ); |
| 306 | + return createRelativeRect(this.getBoundingClientRect(), scrollPos.x, scrollPos.y); |
| 307 | + }, |
| 308 | + |
| 309 | + getStartClientPos: createClientBoundaryPosGetter(true), |
| 310 | + getEndClientPos: createClientBoundaryPosGetter(false), |
| 311 | + |
| 312 | + getStartDocumentPos: createDocumentBoundaryPosGetter(true), |
| 313 | + getEndDocumentPos: createDocumentBoundaryPosGetter(false) |
| 314 | + }); |
| 315 | + })(); |
| 316 | + |
| 317 | + // Add Selection methods |
| 318 | + (function() { |
| 319 | + function compareRanges(r1, r2) { |
| 320 | + return r1.compareBoundaryPoints(r2.START_TO_START, r2); |
| 321 | + } |
| 322 | + |
| 323 | + function createSelectionRectGetter(isDocument) { |
| 324 | + return function() { |
| 325 | + var rangeMethodName = "getBounding" + (isDocument ? "Document" : "Client") + "Rect"; |
| 326 | + var rects = []; |
| 327 | + for (var i = 0, rect = null, rangeRect; i < this.rangeCount; ++i) { |
| 328 | + rects.push(this.getRangeAt(i)[rangeMethodName]()); |
| 329 | + } |
| 330 | + return mergeRects(rects); |
| 331 | + }; |
| 332 | + } |
| 333 | + |
| 334 | + function createSelectionBoundaryPosGetter(isStart, isDocument) { |
| 335 | + return function() { |
| 336 | + if (this.rangeCount == 0) { |
| 337 | + return null; |
| 338 | + } |
| 339 | + |
| 340 | + var posType = isDocument ? "Document" : "Client"; |
| 341 | + |
| 342 | + var ranges = this.getAllRanges(); |
| 343 | + if (ranges.length > 1) { |
| 344 | + // Order the ranges by position within the DOM |
| 345 | + ranges.sort(compareRanges); |
| 346 | + } |
| 347 | + |
| 348 | + return isStart ? |
| 349 | + ranges[0]["getStart" + posType + "Pos"]() : |
| 350 | + ranges[ranges.length - 1]["getEnd" + posType + "Pos"](); |
| 351 | + }; |
| 352 | + } |
| 353 | + |
| 354 | + util.extend(api.selectionPrototype, { |
| 355 | + getBoundingClientRect: createSelectionRectGetter(false), |
| 356 | + getBoundingDocumentRect: createSelectionRectGetter(true), |
| 357 | + |
| 358 | + getStartClientPos: createSelectionBoundaryPosGetter(true, false), |
| 359 | + getEndClientPos: createSelectionBoundaryPosGetter(false, false), |
| 360 | + |
| 361 | + getStartDocumentPos: createSelectionBoundaryPosGetter(true, true), |
| 362 | + getEndDocumentPos: createSelectionBoundaryPosGetter(false, true) |
| 363 | + }); |
| 364 | + })(); |
| 365 | +}); |
Property changes on: trunk/extensions/VisualEditor/modules/rangy/rangy-position.js |
___________________________________________________________________ |
Added: svn:eol-style |
366 | 366 | + native |
Index: trunk/extensions/VisualEditor/modules/ve/ce/styles/ve.es.Content.css |
— | — | @@ -1,89 +1,89 @@ |
2 | | -.es-contentView {
|
3 | | - position: relative;
|
4 | | - z-index: 1;
|
5 | | -}
|
6 | | -
|
7 | | -.es-contentView-line,
|
8 | | -.es-contentView-ruler {
|
9 | | - line-height: 1.5em;
|
10 | | - cursor: text;
|
11 | | - white-space: nowrap;
|
12 | | - color: #000000;
|
13 | | -}
|
14 | | -
|
15 | | -.es-contentView-ruler {
|
16 | | - position: absolute;
|
17 | | - top: 0;
|
18 | | - left: 0;
|
19 | | - display: inline-block;
|
20 | | - z-index: -1000;
|
21 | | -}
|
22 | | -
|
23 | | -.es-contentView-line.empty {
|
24 | | - display: block;
|
25 | | - width: 0px;
|
26 | | -}
|
27 | | -
|
28 | | -.es-contentView-whitespace {
|
29 | | - color: #ffffff;
|
30 | | -}
|
31 | | -
|
32 | | -.es-contentView-range {
|
33 | | - display: none;
|
34 | | - position: absolute;
|
35 | | - background-color: #b3d6f6;
|
36 | | - cursor: text;
|
37 | | - z-index: -1;
|
38 | | -}
|
39 | | -
|
40 | | -.es-contentView-format-object {
|
41 | | - background-color: rgba(0,0,0,0.05);
|
42 | | - border-radius: 0.25em;
|
43 | | - margin: 1px 0 1px 1px;
|
44 | | - padding: 0.25em 0;
|
45 | | - cursor: default;
|
46 | | -}
|
47 | | -
|
48 | | -.es-contentView-format-object * {
|
49 | | - cursor: default !important;
|
50 | | -}
|
51 | | -
|
52 | | -.es-contentView-format-object a:link,
|
53 | | -.es-contentView-format-object a:visited,
|
54 | | -.es-contentView-format-object a:active {
|
55 | | - color: #0645AD;
|
56 | | - text-decoration: none;
|
57 | | -}
|
58 | | -
|
59 | | -.es-contentView-format-textStyle-italic,
|
60 | | -.es-contentView-format-textStyle-emphasize {
|
61 | | - font-style: italic;
|
62 | | -}
|
63 | | -
|
64 | | -.es-contentView-format-textStyle-bold,
|
65 | | -.es-contentView-format-textStyle-strong {
|
66 | | - font-weight: bold;
|
67 | | -}
|
68 | | -
|
69 | | -.es-contentView-format-link {
|
70 | | - color: #0645AD;
|
71 | | - text-decoration: underline;
|
72 | | -}
|
73 | | -
|
74 | | -.es-contentView-format-textStyle-big {
|
75 | | - font-size: 1.2em;
|
76 | | -}
|
77 | | -
|
78 | | -.es-contentView-format-textStyle-small,
|
79 | | -.es-contentView-format-textStyle-subScript,
|
80 | | -.es-contentView-format-textStyle-superScript {
|
81 | | - font-size: .8em;
|
82 | | -}
|
83 | | -
|
84 | | -.es-contentView-format-textStyle-subScript {
|
85 | | - vertical-align: sub;
|
86 | | -}
|
87 | | -
|
88 | | -.es-contentView-format-textStyle-superScript {
|
89 | | - vertical-align: super;
|
90 | | -}
|
| 2 | +.es-contentView { |
| 3 | + position: relative; |
| 4 | + z-index: 1; |
| 5 | +} |
| 6 | + |
| 7 | +.es-contentView-line, |
| 8 | +.es-contentView-ruler { |
| 9 | + line-height: 1.5em; |
| 10 | + cursor: text; |
| 11 | + white-space: nowrap; |
| 12 | + color: #000000; |
| 13 | +} |
| 14 | + |
| 15 | +.es-contentView-ruler { |
| 16 | + position: absolute; |
| 17 | + top: 0; |
| 18 | + left: 0; |
| 19 | + display: inline-block; |
| 20 | + z-index: -1000; |
| 21 | +} |
| 22 | + |
| 23 | +.es-contentView-line.empty { |
| 24 | + display: block; |
| 25 | + width: 0px; |
| 26 | +} |
| 27 | + |
| 28 | +.es-contentView-whitespace { |
| 29 | + color: #ffffff; |
| 30 | +} |
| 31 | + |
| 32 | +.es-contentView-range { |
| 33 | + display: none; |
| 34 | + position: absolute; |
| 35 | + background-color: #b3d6f6; |
| 36 | + cursor: text; |
| 37 | + z-index: -1; |
| 38 | +} |
| 39 | + |
| 40 | +.es-contentView-format-object { |
| 41 | + background-color: rgba(0,0,0,0.05); |
| 42 | + border-radius: 0.25em; |
| 43 | + margin: 1px 0 1px 1px; |
| 44 | + padding: 0.25em 0; |
| 45 | + cursor: default; |
| 46 | +} |
| 47 | + |
| 48 | +.es-contentView-format-object * { |
| 49 | + cursor: default !important; |
| 50 | +} |
| 51 | + |
| 52 | +.es-contentView-format-object a:link, |
| 53 | +.es-contentView-format-object a:visited, |
| 54 | +.es-contentView-format-object a:active { |
| 55 | + color: #0645AD; |
| 56 | + text-decoration: none; |
| 57 | +} |
| 58 | + |
| 59 | +.es-contentView-format-textStyle-italic, |
| 60 | +.es-contentView-format-textStyle-emphasize { |
| 61 | + font-style: italic; |
| 62 | +} |
| 63 | + |
| 64 | +.es-contentView-format-textStyle-bold, |
| 65 | +.es-contentView-format-textStyle-strong { |
| 66 | + font-weight: bold; |
| 67 | +} |
| 68 | + |
| 69 | +.es-contentView-format-link { |
| 70 | + color: #0645AD; |
| 71 | + text-decoration: underline; |
| 72 | +} |
| 73 | + |
| 74 | +.es-contentView-format-textStyle-big { |
| 75 | + font-size: 1.2em; |
| 76 | +} |
| 77 | + |
| 78 | +.es-contentView-format-textStyle-small, |
| 79 | +.es-contentView-format-textStyle-subScript, |
| 80 | +.es-contentView-format-textStyle-superScript { |
| 81 | + font-size: .8em; |
| 82 | +} |
| 83 | + |
| 84 | +.es-contentView-format-textStyle-subScript { |
| 85 | + vertical-align: sub; |
| 86 | +} |
| 87 | + |
| 88 | +.es-contentView-format-textStyle-superScript { |
| 89 | + vertical-align: super; |
| 90 | +} |
Property changes on: trunk/extensions/VisualEditor/modules/ve/ce/styles/ve.es.Content.css |
___________________________________________________________________ |
Added: svn:eol-style |
91 | 91 | + native |
Index: trunk/extensions/VisualEditor/modules/ve/ce/ve.es.LeafNode.js |
— | — | @@ -1,41 +1,41 @@ |
2 | | -/**
|
3 | | - * Creates an ve.es.LeafNode object.
|
4 | | - *
|
5 | | - * @class
|
6 | | - * @abstract
|
7 | | - * @constructor
|
8 | | - * @extends {ve.LeafNode}
|
9 | | - * @extends {ve.es.Node}
|
10 | | - * @param model {ve.ModelNode} Model to observe
|
11 | | - * @param {jQuery} [$element] Element to use as a container
|
12 | | - */
|
13 | | -ve.es.LeafNode = function( model, $element ) {
|
14 | | - // Inheritance
|
15 | | - ve.LeafNode.call( this );
|
16 | | - ve.es.Node.call( this, model, $element );
|
17 | | -
|
18 | | - this.$.data('view', this);
|
19 | | - this.$.addClass('ce-leafNode');
|
20 | | -
|
21 | | - // Properties
|
22 | | - this.contentView = new ve.es.Content( this.$, model );
|
23 | | -
|
24 | | - // Events
|
25 | | - this.contentView.on( 'update', this.emitUpdate );
|
26 | | -};
|
27 | | -
|
28 | | -/* Methods */
|
29 | | -
|
30 | | -/**
|
31 | | - * Render content.
|
32 | | - *
|
33 | | - * @method
|
34 | | - */
|
35 | | -ve.es.LeafNode.prototype.renderContent = function() {
|
36 | | - this.contentView.render();
|
37 | | -};
|
38 | | -
|
39 | | -/* Inheritance */
|
40 | | -
|
41 | | -ve.extendClass( ve.es.LeafNode, ve.LeafNode );
|
42 | | -ve.extendClass( ve.es.LeafNode, ve.es.Node );
|
| 2 | +/** |
| 3 | + * Creates an ve.es.LeafNode object. |
| 4 | + * |
| 5 | + * @class |
| 6 | + * @abstract |
| 7 | + * @constructor |
| 8 | + * @extends {ve.LeafNode} |
| 9 | + * @extends {ve.es.Node} |
| 10 | + * @param model {ve.ModelNode} Model to observe |
| 11 | + * @param {jQuery} [$element] Element to use as a container |
| 12 | + */ |
| 13 | +ve.es.LeafNode = function( model, $element ) { |
| 14 | + // Inheritance |
| 15 | + ve.LeafNode.call( this ); |
| 16 | + ve.es.Node.call( this, model, $element ); |
| 17 | + |
| 18 | + this.$.data('view', this); |
| 19 | + this.$.addClass('ce-leafNode'); |
| 20 | + |
| 21 | + // Properties |
| 22 | + this.contentView = new ve.es.Content( this.$, model ); |
| 23 | + |
| 24 | + // Events |
| 25 | + this.contentView.on( 'update', this.emitUpdate ); |
| 26 | +}; |
| 27 | + |
| 28 | +/* Methods */ |
| 29 | + |
| 30 | +/** |
| 31 | + * Render content. |
| 32 | + * |
| 33 | + * @method |
| 34 | + */ |
| 35 | +ve.es.LeafNode.prototype.renderContent = function() { |
| 36 | + this.contentView.render(); |
| 37 | +}; |
| 38 | + |
| 39 | +/* Inheritance */ |
| 40 | + |
| 41 | +ve.extendClass( ve.es.LeafNode, ve.LeafNode ); |
| 42 | +ve.extendClass( ve.es.LeafNode, ve.es.Node ); |
Property changes on: trunk/extensions/VisualEditor/modules/ve/ce/ve.es.LeafNode.js |
___________________________________________________________________ |
Added: svn:eol-style |
43 | 43 | + native |
Index: trunk/extensions/VisualEditor/modules/ve/ce/nodes/ve.es.ParagraphNode.js |
— | — | @@ -1,26 +1,26 @@ |
2 | | -/**
|
3 | | - * Creates an ve.es.ParagraphNode object.
|
4 | | - *
|
5 | | - * @class
|
6 | | - * @constructor
|
7 | | - * @extends {ve.es.LeafNode}
|
8 | | - * @param {ve.dm.ParagraphNode} model Paragraph model to view
|
9 | | - */
|
10 | | -ve.es.ParagraphNode = function( model ) {
|
11 | | - // Inheritance
|
12 | | - ve.es.LeafNode.call( this, model, $( '<p></p>' ) );
|
13 | | -
|
14 | | - // DOM Changes
|
15 | | - this.$.addClass( 'es-paragraphView' );
|
16 | | -};
|
17 | | -
|
18 | | -/* Registration */
|
19 | | -
|
20 | | -ve.es.DocumentNode.splitRules.paragraph = {
|
21 | | - 'self': true,
|
22 | | - 'children': null
|
23 | | -};
|
24 | | -
|
25 | | -/* Inheritance */
|
26 | | -
|
27 | | -ve.extendClass( ve.es.ParagraphNode, ve.es.LeafNode );
|
| 2 | +/** |
| 3 | + * Creates an ve.es.ParagraphNode object. |
| 4 | + * |
| 5 | + * @class |
| 6 | + * @constructor |
| 7 | + * @extends {ve.es.LeafNode} |
| 8 | + * @param {ve.dm.ParagraphNode} model Paragraph model to view |
| 9 | + */ |
| 10 | +ve.es.ParagraphNode = function( model ) { |
| 11 | + // Inheritance |
| 12 | + ve.es.LeafNode.call( this, model, $( '<p></p>' ) ); |
| 13 | + |
| 14 | + // DOM Changes |
| 15 | + this.$.addClass( 'es-paragraphView' ); |
| 16 | +}; |
| 17 | + |
| 18 | +/* Registration */ |
| 19 | + |
| 20 | +ve.es.DocumentNode.splitRules.paragraph = { |
| 21 | + 'self': true, |
| 22 | + 'children': null |
| 23 | +}; |
| 24 | + |
| 25 | +/* Inheritance */ |
| 26 | + |
| 27 | +ve.extendClass( ve.es.ParagraphNode, ve.es.LeafNode ); |
Property changes on: trunk/extensions/VisualEditor/modules/ve/ce/nodes/ve.es.ParagraphNode.js |
___________________________________________________________________ |
Added: svn:eol-style |
28 | 28 | + native |
Property changes on: trunk/extensions/LiveTranslate/includes/ext.lt.load.js |
___________________________________________________________________ |
Added: svn:eol-style |
29 | 29 | + native |
Property changes on: trunk/extensions/EducationProgram/includes/EPRevisionAction.php |
___________________________________________________________________ |
Added: svn:eol-style |
30 | 30 | + native |