(function() { var exports = this, Bookmark; Bookmark = function(env, range, keepOldBookmarks) { this.env = env; this.element = env.element; this.selection = this.env.selection; // Remove all bookmarks? if (!keepOldBookmarks) { this.removeBookmarks(this.element); } var currRange = range || this.selection.getRangeAt(0); range = currRange.cloneRange(); var startContainer = range.startContainer; var endContainer = range.endContainer; var startOffset = range.startOffset; var endOffset = range.endOffset; var tmp; // Collapse to the end of range. range.collapse(false); var endBookmark = this.env.document.createElement('span'); endBookmark.style.display = 'none'; ice.dom.setHtml(endBookmark, ' '); ice.dom.addClass(endBookmark, 'iceBookmark iceBookmark_end'); endBookmark.setAttribute('iceBookmark', 'end'); range.insertNode(endBookmark); if(!ice.dom.isChildOf(endBookmark, this.element)) { this.element.appendChild(endBookmark); } // Move the range to where it was before. range.setStart(startContainer, startOffset); range.collapse(true); // Create the start bookmark. var startBookmark = this.env.document.createElement('span'); startBookmark.style.display = 'none'; ice.dom.addClass(startBookmark, 'iceBookmark iceBookmark_start'); ice.dom.setHtml(startBookmark, ' '); startBookmark.setAttribute('iceBookmark', 'start'); try { range.insertNode(startBookmark); // Make sure start and end are in correct position. if (startBookmark.previousSibling === endBookmark) { // Reverse.. tmp = startBookmark; startBookmark = endBookmark; endBookmark = tmp; } } catch (e) { // NS_ERROR_UNEXPECTED: I believe this is a Firefox bug. // It seems like if the range is collapsed and the text node is empty // (i.e. length = 0) then Firefox tries to split the node for no reason and fails... ice.dom.insertBefore(endBookmark, startBookmark); } if (ice.dom.isChildOf(startBookmark, this.element) === false) { if (this.element.firstChild) { ice.dom.insertBefore(this.element.firstChild, startBookmark); } else { // Should not happen... this.element.appendChild(startBookmark); } } if (!endBookmark.previousSibling) { tmp = this.env.document.createTextNode(''); ice.dom.insertBefore(endBookmark, tmp); } // The original range object must be changed. if (!startBookmark.nextSibling) { tmp = this.env.document.createTextNode(''); ice.dom.insertAfter(startBookmark, tmp); } currRange.setStart(startBookmark.nextSibling, 0); currRange.setEnd(endBookmark.previousSibling, (endBookmark.previousSibling.length || 0)); this.start = startBookmark; this.end = endBookmark; }; Bookmark.prototype = { selectBookmark: function() { var range = this.selection.getRangeAt(0); var startPos = null; var endPos = null; var startOffset = 0; var endOffset = null; if (this.start.nextSibling === this.end || ice.dom.getElementsBetween(this.start, this.end).length === 0) { // Bookmark is collapsed. if (this.end.nextSibling) { startPos = ice.dom.getFirstChild(this.end.nextSibling); } else if (this.start.previousSibling) { startPos = ice.dom.getFirstChild(this.start.previousSibling); if (startPos.nodeType === ice.dom.TEXT_NODE) { startOffset = startPos.length; } } else { // Create a text node in parent. this.end.parentNode.appendChild(this.env.document.createTextNode('')); startPos = ice.dom.getFirstChild(this.end.nextSibling); } } else { if (this.start.nextSibling) { startPos = ice.dom.getFirstChild(this.start.nextSibling); } else { if (!this.start.previousSibling) { var tmp = this.env.document.createTextNode(''); ice.dom.insertBefore(this.start, tmp); } startPos = ice.dom.getLastChild(this.start.previousSibling); startOffset = startPos.length; } if (this.end.previousSibling) { endPos = ice.dom.getLastChild(this.end.previousSibling); } else { endPos = ice.dom.getFirstChild(this.end.nextSibling || this.end); endOffset = 0; } } ice.dom.remove([this.start, this.end]); if (endPos === null) { range.setEnd(startPos, startOffset); range.collapse(false); } else { range.setStart(startPos, startOffset); if (endOffset === null) { endOffset = (endPos.length || 0); } range.setEnd(endPos, endOffset); } try { this.selection.addRange(range); } catch (e) { // IE may throw exception for hidden elements.. } }, getBookmark: function(parent, type) { var elem = ice.dom.getClass('iceBookmark_' + type, parent)[0]; return elem; }, removeBookmarks: function(elem) { ice.dom.remove(ice.dom.getClass('iceBookmark', elem, 'span')); } }; exports.Bookmark = Bookmark; }).call(this.ice);