123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- /**
- * Selection save and restore module for Rangy.
- * Saves and restores user selections using marker invisible elements in the DOM.
- *
- * Part of Rangy, a cross-browser JavaScript range and selection library
- * https://github.com/timdown/rangy
- *
- * Depends on Rangy core.
- *
- * Copyright 2015, Tim Down
- * Licensed under the MIT license.
- * Version: 1.3.0
- * Build date: 10 May 2015
- */
- (function(factory, root) {
- if (typeof define == "function" && define.amd) {
- // AMD. Register as an anonymous module with a dependency on Rangy.
- define(["./rangy-core"], factory);
- } else if (typeof module != "undefined" && typeof exports == "object") {
- // Node/CommonJS style
- module.exports = factory( require("rangy") );
- } else {
- // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
- factory(root.rangy);
- }
- })(function(rangy) {
- rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
- var dom = api.dom;
- var removeNode = dom.removeNode;
- var isDirectionBackward = api.Selection.isDirectionBackward;
- var markerTextChar = "\ufeff";
- function gEBI(id, doc) {
- return (doc || document).getElementById(id);
- }
- function insertRangeBoundaryMarker(range, atStart) {
- var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
- var markerEl;
- var doc = dom.getDocument(range.startContainer);
- // Clone the Range and collapse to the appropriate boundary point
- var boundaryRange = range.cloneRange();
- boundaryRange.collapse(atStart);
- // Create the marker element containing a single invisible character using DOM methods and insert it
- markerEl = doc.createElement("span");
- markerEl.id = markerId;
- markerEl.style.lineHeight = "0";
- markerEl.style.display = "none";
- markerEl.className = "rangySelectionBoundary";
- markerEl.appendChild(doc.createTextNode(markerTextChar));
- boundaryRange.insertNode(markerEl);
- return markerEl;
- }
- function setRangeBoundary(doc, range, markerId, atStart) {
- var markerEl = gEBI(markerId, doc);
- if (markerEl) {
- range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
- removeNode(markerEl);
- } else {
- module.warn("Marker element has been removed. Cannot restore selection.");
- }
- }
- function compareRanges(r1, r2) {
- return r2.compareBoundaryPoints(r1.START_TO_START, r1);
- }
- function saveRange(range, direction) {
- var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
- var backward = isDirectionBackward(direction);
- if (range.collapsed) {
- endEl = insertRangeBoundaryMarker(range, false);
- return {
- document: doc,
- markerId: endEl.id,
- collapsed: true
- };
- } else {
- endEl = insertRangeBoundaryMarker(range, false);
- startEl = insertRangeBoundaryMarker(range, true);
- return {
- document: doc,
- startMarkerId: startEl.id,
- endMarkerId: endEl.id,
- collapsed: false,
- backward: backward,
- toString: function() {
- return "original text: '" + text + "', new text: '" + range.toString() + "'";
- }
- };
- }
- }
- function restoreRange(rangeInfo, normalize) {
- var doc = rangeInfo.document;
- if (typeof normalize == "undefined") {
- normalize = true;
- }
- var range = api.createRange(doc);
- if (rangeInfo.collapsed) {
- var markerEl = gEBI(rangeInfo.markerId, doc);
- if (markerEl) {
- markerEl.style.display = "inline";
- var previousNode = markerEl.previousSibling;
- // Workaround for issue 17
- if (previousNode && previousNode.nodeType == 3) {
- removeNode(markerEl);
- range.collapseToPoint(previousNode, previousNode.length);
- } else {
- range.collapseBefore(markerEl);
- removeNode(markerEl);
- }
- } else {
- module.warn("Marker element has been removed. Cannot restore selection.");
- }
- } else {
- setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
- setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
- }
- if (normalize) {
- range.normalizeBoundaries();
- }
- return range;
- }
- function saveRanges(ranges, direction) {
- var rangeInfos = [], range, doc;
- var backward = isDirectionBackward(direction);
- // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
- ranges = ranges.slice(0);
- ranges.sort(compareRanges);
- for (var i = 0, len = ranges.length; i < len; ++i) {
- rangeInfos[i] = saveRange(ranges[i], backward);
- }
- // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
- // between its markers
- for (i = len - 1; i >= 0; --i) {
- range = ranges[i];
- doc = api.DomRange.getRangeDocument(range);
- if (range.collapsed) {
- range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
- } else {
- range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
- range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
- }
- }
- return rangeInfos;
- }
- function saveSelection(win) {
- if (!api.isSelectionValid(win)) {
- module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
- return null;
- }
- var sel = api.getSelection(win);
- var ranges = sel.getAllRanges();
- var backward = (ranges.length == 1 && sel.isBackward());
- var rangeInfos = saveRanges(ranges, backward);
- // Ensure current selection is unaffected
- if (backward) {
- sel.setSingleRange(ranges[0], backward);
- } else {
- sel.setRanges(ranges);
- }
- return {
- win: win,
- rangeInfos: rangeInfos,
- restored: false
- };
- }
- function restoreRanges(rangeInfos) {
- var ranges = [];
- // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
- // normalization affecting previously restored ranges.
- var rangeCount = rangeInfos.length;
- for (var i = rangeCount - 1; i >= 0; i--) {
- ranges[i] = restoreRange(rangeInfos[i], true);
- }
- return ranges;
- }
- function restoreSelection(savedSelection, preserveDirection) {
- if (!savedSelection.restored) {
- var rangeInfos = savedSelection.rangeInfos;
- var sel = api.getSelection(savedSelection.win);
- var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
- if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
- sel.removeAllRanges();
- sel.addRange(ranges[0], true);
- } else {
- sel.setRanges(ranges);
- }
- savedSelection.restored = true;
- }
- }
- function removeMarkerElement(doc, markerId) {
- var markerEl = gEBI(markerId, doc);
- if (markerEl) {
- removeNode(markerEl);
- }
- }
- function removeMarkers(savedSelection) {
- var rangeInfos = savedSelection.rangeInfos;
- for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
- rangeInfo = rangeInfos[i];
- if (rangeInfo.collapsed) {
- removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
- } else {
- removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
- removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
- }
- }
- }
- api.util.extend(api, {
- saveRange: saveRange,
- restoreRange: restoreRange,
- saveRanges: saveRanges,
- restoreRanges: restoreRanges,
- saveSelection: saveSelection,
- restoreSelection: restoreSelection,
- removeMarkerElement: removeMarkerElement,
- removeMarkers: removeMarkers
- });
- });
-
- return rangy;
- }, this);
|