Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/**
2
 * Selection save and restore module for Rangy.
3
 * Saves and restores user selections using marker invisible elements in the DOM.
4
 *
5
 * Part of Rangy, a cross-browser JavaScript range and selection library
6
 * https://github.com/timdown/rangy
7
 *
8
 * Depends on Rangy core.
9
 *
10
 * Copyright 2022, Tim Down
11
 * Licensed under the MIT license.
12
 * Version: 1.3.1
13
 * Build date: 17 August 2022
14
 */
15
(function(factory, root) {
16
    // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
17
    factory(root.rangy);
18
})(function(rangy) {
19
    rangy.createModule("SaveRestore", ["WrappedSelection"], function(api, module) {
20
        var dom = api.dom;
21
        var removeNode = dom.removeNode;
22
        var isDirectionBackward = api.Selection.isDirectionBackward;
23
        var markerTextChar = "\ufeff";
24
 
25
        function gEBI(id, doc) {
26
            return (doc || document).getElementById(id);
27
        }
28
 
29
        function insertRangeBoundaryMarker(range, atStart) {
30
            var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
31
            var markerEl;
32
            var doc = dom.getDocument(range.startContainer);
33
 
34
            // Clone the Range and collapse to the appropriate boundary point
35
            var boundaryRange = range.cloneRange();
36
            boundaryRange.collapse(atStart);
37
 
38
            // Create the marker element containing a single invisible character using DOM methods and insert it
39
            markerEl = doc.createElement("span");
40
            markerEl.id = markerId;
41
            markerEl.style.lineHeight = "0";
42
            markerEl.style.display = "none";
43
            markerEl.className = "rangySelectionBoundary";
44
            markerEl.appendChild(doc.createTextNode(markerTextChar));
45
 
46
            boundaryRange.insertNode(markerEl);
47
            return markerEl;
48
        }
49
 
50
        function setRangeBoundary(doc, range, markerId, atStart) {
51
            var markerEl = gEBI(markerId, doc);
52
            if (markerEl) {
53
                range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
54
                removeNode(markerEl);
55
            } else {
56
                module.warn("Marker element has been removed. Cannot restore selection.");
57
            }
58
        }
59
 
60
        function compareRanges(r1, r2) {
61
            return r2.compareBoundaryPoints(r1.START_TO_START, r1);
62
        }
63
 
64
        function saveRange(range, direction) {
65
            var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
66
            var backward = isDirectionBackward(direction);
67
 
68
            if (range.collapsed) {
69
                endEl = insertRangeBoundaryMarker(range, false);
70
                return {
71
                    document: doc,
72
                    markerId: endEl.id,
73
                    collapsed: true
74
                };
75
            } else {
76
                endEl = insertRangeBoundaryMarker(range, false);
77
                startEl = insertRangeBoundaryMarker(range, true);
78
 
79
                return {
80
                    document: doc,
81
                    startMarkerId: startEl.id,
82
                    endMarkerId: endEl.id,
83
                    collapsed: false,
84
                    backward: backward,
85
                    toString: function() {
86
                        return "original text: '" + text + "', new text: '" + range.toString() + "'";
87
                    }
88
                };
89
            }
90
        }
91
 
92
        function restoreRange(rangeInfo, normalize) {
93
            var doc = rangeInfo.document;
94
            if (typeof normalize == "undefined") {
95
                normalize = true;
96
            }
97
            var range = api.createRange(doc);
98
            if (rangeInfo.collapsed) {
99
                var markerEl = gEBI(rangeInfo.markerId, doc);
100
                if (markerEl) {
101
                    markerEl.style.display = "inline";
102
                    var previousNode = markerEl.previousSibling;
103
 
104
                    // Workaround for issue 17
105
                    if (previousNode && previousNode.nodeType == 3) {
106
                        removeNode(markerEl);
107
                        range.collapseToPoint(previousNode, previousNode.length);
108
                    } else {
109
                        range.collapseBefore(markerEl);
110
                        removeNode(markerEl);
111
                    }
112
                } else {
113
                    module.warn("Marker element has been removed. Cannot restore selection.");
114
                }
115
            } else {
116
                setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
117
                setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
118
            }
119
 
120
            if (normalize) {
121
                range.normalizeBoundaries();
122
            }
123
 
124
            return range;
125
        }
126
 
127
        function saveRanges(ranges, direction) {
128
            var rangeInfos = [], range, doc;
129
            var backward = isDirectionBackward(direction);
130
 
131
            // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
132
            ranges = ranges.slice(0);
133
            ranges.sort(compareRanges);
134
 
135
            for (var i = 0, len = ranges.length; i < len; ++i) {
136
                rangeInfos[i] = saveRange(ranges[i], backward);
137
            }
138
 
139
            // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
140
            // between its markers
141
            for (i = len - 1; i >= 0; --i) {
142
                range = ranges[i];
143
                doc = api.DomRange.getRangeDocument(range);
144
                if (range.collapsed) {
145
                    range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
146
                } else {
147
                    range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
148
                    range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
149
                }
150
            }
151
 
152
            return rangeInfos;
153
        }
154
 
155
        function saveSelection(win) {
156
            if (!api.isSelectionValid(win)) {
157
                module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
158
                return null;
159
            }
160
            var sel = api.getSelection(win);
161
            var ranges = sel.getAllRanges();
162
            var backward = (ranges.length == 1 && sel.isBackward());
163
 
164
            var rangeInfos = saveRanges(ranges, backward);
165
 
166
            // Ensure current selection is unaffected
167
            if (backward) {
168
                sel.setSingleRange(ranges[0], backward);
169
            } else {
170
                sel.setRanges(ranges);
171
            }
172
 
173
            return {
174
                win: win,
175
                rangeInfos: rangeInfos,
176
                restored: false
177
            };
178
        }
179
 
180
        function restoreRanges(rangeInfos) {
181
            var ranges = [];
182
 
183
            // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
184
            // normalization affecting previously restored ranges.
185
            var rangeCount = rangeInfos.length;
186
 
187
            for (var i = rangeCount - 1; i >= 0; i--) {
188
                ranges[i] = restoreRange(rangeInfos[i], true);
189
            }
190
 
191
            return ranges;
192
        }
193
 
194
        function restoreSelection(savedSelection, preserveDirection) {
195
            if (!savedSelection.restored) {
196
                var rangeInfos = savedSelection.rangeInfos;
197
                var sel = api.getSelection(savedSelection.win);
198
                var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
199
 
200
                if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
201
                    sel.removeAllRanges();
202
                    sel.addRange(ranges[0], true);
203
                } else {
204
                    sel.setRanges(ranges);
205
                }
206
 
207
                savedSelection.restored = true;
208
            }
209
        }
210
 
211
        function removeMarkerElement(doc, markerId) {
212
            var markerEl = gEBI(markerId, doc);
213
            if (markerEl) {
214
                removeNode(markerEl);
215
            }
216
        }
217
 
218
        function removeMarkers(savedSelection) {
219
            var rangeInfos = savedSelection.rangeInfos;
220
            for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
221
                rangeInfo = rangeInfos[i];
222
                if (rangeInfo.collapsed) {
223
                    removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
224
                } else {
225
                    removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
226
                    removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
227
                }
228
            }
229
        }
230
 
231
        api.util.extend(api, {
232
            saveRange: saveRange,
233
            restoreRange: restoreRange,
234
            saveRanges: saveRanges,
235
            restoreRanges: restoreRanges,
236
            saveSelection: saveSelection,
237
            restoreSelection: restoreSelection,
238
            removeMarkerElement: removeMarkerElement,
239
            removeMarkers: removeMarkers
240
        });
241
    });
242
 
243
    return rangy;
244
}, this);