From 0fc69ce3ad89844dd5fb2f87532e7b6fca55923d Mon Sep 17 00:00:00 2001 From: Daniel Schmitzer Date: Tue, 22 Mar 2016 13:59:16 +0000 Subject: - sync git-svn-id: https://[internal]/wordpress/plugins/wpsg4@5439 0d5edaf7-2b12-4e95-93f4-047927463d4b --- .../inputs-ext/address/address.css | 9 + .../inputs-ext/address/address.js | 170 + .../inputs-ext/typeaheadjs/lib/typeahead.js | 1139 +++ .../typeaheadjs/lib/typeahead.js-bootstrap.css | 49 + .../inputs-ext/typeaheadjs/typeaheadjs.js | 86 + .../bootstrap-wysihtml5-0.0.2.css | 102 + .../bootstrap-wysihtml5-0.0.2.js | 499 + .../bootstrap-wysihtml5-0.0.2.min.js | 1 + .../bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0.js | 9521 ++++++++++++++++++++ .../wysihtml5-0.3.0.min.js | 261 + .../bootstrap-wysihtml5-0.0.2/wysiwyg-color.css | 67 + .../inputs-ext/wysihtml5/wysihtml5.js | 138 + 12 files changed, 12042 insertions(+) create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/address/address.css create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/address/address.js create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/lib/typeahead.js create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/lib/typeahead.js-bootstrap.css create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/typeaheadjs.js create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.js create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0.js create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0.min.js create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysiwyg-color.css create mode 100644 views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/wysihtml5.js (limited to 'views/js/bootstrap3-editable-1.5.1/inputs-ext') diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/address/address.css b/views/js/bootstrap3-editable-1.5.1/inputs-ext/address/address.css new file mode 100644 index 0000000..f37a43b --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/address/address.css @@ -0,0 +1,9 @@ +.editable-address { + display: block; + margin-bottom: 5px; +} + +.editable-address span { + width: 70px; + display: inline-block; +} \ No newline at end of file diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/address/address.js b/views/js/bootstrap3-editable-1.5.1/inputs-ext/address/address.js new file mode 100644 index 0000000..de3af81 --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/address/address.js @@ -0,0 +1,170 @@ +/** +Address editable input. +Internally value stored as {city: "Moscow", street: "Lenina", building: "15"} + +@class address +@extends abstractinput +@final +@example +awesome + +**/ +(function ($) { + "use strict"; + + var Address = function (options) { + this.init('address', options, Address.defaults); + }; + + //inherit from Abstract input + $.fn.editableutils.inherit(Address, $.fn.editabletypes.abstractinput); + + $.extend(Address.prototype, { + /** + Renders input from tpl + + @method render() + **/ + render: function() { + this.$input = this.$tpl.find('input'); + }, + + /** + Default method to show value in element. Can be overwritten by display option. + + @method value2html(value, element) + **/ + value2html: function(value, element) { + if(!value) { + $(element).empty(); + return; + } + var html = $('
').text(value.city).html() + ', ' + $('
').text(value.street).html() + ' st., bld. ' + $('
').text(value.building).html(); + $(element).html(html); + }, + + /** + Gets value from element's html + + @method html2value(html) + **/ + html2value: function(html) { + /* + you may write parsing method to get value by element's html + e.g. "Moscow, st. Lenina, bld. 15" => {city: "Moscow", street: "Lenina", building: "15"} + but for complex structures it's not recommended. + Better set value directly via javascript, e.g. + editable({ + value: { + city: "Moscow", + street: "Lenina", + building: "15" + } + }); + */ + return null; + }, + + /** + Converts value to string. + It is used in internal comparing (not for sending to server). + + @method value2str(value) + **/ + value2str: function(value) { + var str = ''; + if(value) { + for(var k in value) { + str = str + k + ':' + value[k] + ';'; + } + } + return str; + }, + + /* + Converts string to value. Used for reading value from 'data-value' attribute. + + @method str2value(str) + */ + str2value: function(str) { + /* + this is mainly for parsing value defined in data-value attribute. + If you will always set value by javascript, no need to overwrite it + */ + return str; + }, + + /** + Sets value of input. + + @method value2input(value) + @param {mixed} value + **/ + value2input: function(value) { + if(!value) { + return; + } + this.$input.filter('[name="city"]').val(value.city); + this.$input.filter('[name="street"]').val(value.street); + this.$input.filter('[name="building"]').val(value.building); + }, + + /** + Returns value of input. + + @method input2value() + **/ + input2value: function() { + return { + city: this.$input.filter('[name="city"]').val(), + street: this.$input.filter('[name="street"]').val(), + building: this.$input.filter('[name="building"]').val() + }; + }, + + /** + Activates input: sets focus on the first field. + + @method activate() + **/ + activate: function() { + this.$input.filter('[name="city"]').focus(); + }, + + /** + Attaches handler to submit form in case of 'showbuttons=false' mode + + @method autosubmit() + **/ + autosubmit: function() { + this.$input.keydown(function (e) { + if (e.which === 13) { + $(this).closest('form').submit(); + } + }); + } + }); + + Address.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { + tpl: '
'+ + '
'+ + '
', + + inputclass: '' + }); + + $.fn.editabletypes.address = Address; + +}(window.jQuery)); \ No newline at end of file diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/lib/typeahead.js b/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/lib/typeahead.js new file mode 100644 index 0000000..9365bd6 --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/lib/typeahead.js @@ -0,0 +1,1139 @@ +/*! + * typeahead.js 0.9.3 + * https://github.com/twitter/typeahead + * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function($) { + var VERSION = "0.9.3"; + var utils = { + isMsie: function() { + var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); + return match ? parseInt(match[2], 10) : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + bind: $.proxy, + bindAll: function(obj) { + var val; + for (var key in obj) { + $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); + } + }, + indexOf: function(haystack, needle) { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return i; + } + } + return -1; + }, + each: $.each, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + getUniqueId: function() { + var counter = 0; + return function() { + return counter++; + }; + }(), + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + tokenizeQuery: function(str) { + return $.trim(str).toLowerCase().split(/[\s]+/); + }, + tokenizeText: function(str) { + return $.trim(str).toLowerCase().split(/[\s\-_]+/); + }, + getProtocol: function() { + return location.protocol; + }, + noop: function() {} + }; + var EventTarget = function() { + var eventSplitter = /\s+/; + return { + on: function(events, callback) { + var event; + if (!callback) { + return this; + } + this._callbacks = this._callbacks || {}; + events = events.split(eventSplitter); + while (event = events.shift()) { + this._callbacks[event] = this._callbacks[event] || []; + this._callbacks[event].push(callback); + } + return this; + }, + trigger: function(events, data) { + var event, callbacks; + if (!this._callbacks) { + return this; + } + events = events.split(eventSplitter); + while (event = events.shift()) { + if (callbacks = this._callbacks[event]) { + for (var i = 0; i < callbacks.length; i += 1) { + callbacks[i].call(this, { + type: event, + data: data + }); + } + } + } + return this; + } + }; + }(); + var EventBus = function() { + var namespace = "typeahead:"; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + utils.mixin(EventBus.prototype, { + trigger: function(type) { + var args = [].slice.call(arguments, 1); + this.$el.trigger(namespace + type, args); + } + }); + return EventBus; + }(); + var PersistentStorage = function() { + var ls, methods; + try { + ls = window.localStorage; + ls.setItem("~~~", "!"); + ls.removeItem("~~~"); + } catch (err) { + ls = null; + } + function PersistentStorage(namespace) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + this.prefix); + } + if (ls && window.JSON) { + methods = { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (utils.isNumber(ttl)) { + ls.setItem(this._ttlKey(key), encode(now() + ttl)); + } else { + ls.removeItem(this._ttlKey(key)); + } + return ls.setItem(this._prefix(key), encode(val)); + }, + remove: function(key) { + ls.removeItem(this._ttlKey(key)); + ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, key, keys = [], len = ls.length; + for (i = 0; i < len; i++) { + if ((key = ls.key(i)).match(this.keyMatcher)) { + keys.push(key.replace(this.keyMatcher, "")); + } + } + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(ls.getItem(this._ttlKey(key))); + return utils.isNumber(ttl) && now() > ttl ? true : false; + } + }; + } else { + methods = { + get: utils.noop, + set: utils.noop, + remove: utils.noop, + clear: utils.noop, + isExpired: utils.noop + }; + } + utils.mixin(PersistentStorage.prototype, methods); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(utils.isUndefined(val) ? null : val); + } + function decode(val) { + return JSON.parse(val); + } + }(); + var RequestCache = function() { + function RequestCache(o) { + utils.bindAll(this); + o = o || {}; + this.sizeLimit = o.sizeLimit || 10; + this.cache = {}; + this.cachedKeysByAge = []; + } + utils.mixin(RequestCache.prototype, { + get: function(url) { + return this.cache[url]; + }, + set: function(url, resp) { + var requestToEvict; + if (this.cachedKeysByAge.length === this.sizeLimit) { + requestToEvict = this.cachedKeysByAge.shift(); + delete this.cache[requestToEvict]; + } + this.cache[url] = resp; + this.cachedKeysByAge.push(url); + } + }); + return RequestCache; + }(); + var Transport = function() { + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; + function Transport(o) { + utils.bindAll(this); + o = utils.isString(o) ? { + url: o + } : o; + requestCache = requestCache || new RequestCache(); + maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; + this.url = o.url; + this.wildcard = o.wildcard || "%QUERY"; + this.filter = o.filter; + this.replace = o.replace; + this.ajaxSettings = { + type: "get", + cache: o.cache, + timeout: o.timeout, + dataType: o.dataType || "json", + beforeSend: o.beforeSend + }; + this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); + } + utils.mixin(Transport.prototype, { + _get: function(url, cb) { + var that = this; + if (belowPendingRequestsThreshold()) { + this._sendRequest(url).done(done); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + var data = that.filter ? that.filter(resp) : resp; + cb && cb(data); + requestCache.set(url, resp); + } + }, + _sendRequest: function(url) { + var that = this, jqXhr = pendingRequests[url]; + if (!jqXhr) { + incrementPendingRequests(); + jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); + } + return jqXhr; + function always() { + decrementPendingRequests(); + pendingRequests[url] = null; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function(query, cb) { + var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; + cb = cb || utils.noop; + url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); + if (resp = requestCache.get(url)) { + utils.defer(function() { + cb(that.filter ? that.filter(resp) : resp); + }); + } else { + this._get(url, cb); + } + return !!resp; + } + }); + return Transport; + function incrementPendingRequests() { + pendingRequestsCount++; + } + function decrementPendingRequests() { + pendingRequestsCount--; + } + function belowPendingRequestsThreshold() { + return pendingRequestsCount < maxPendingRequests; + } + }(); + var Dataset = function() { + var keys = { + thumbprint: "thumbprint", + protocol: "protocol", + itemHash: "itemHash", + adjacencyList: "adjacencyList" + }; + function Dataset(o) { + utils.bindAll(this); + if (utils.isString(o.template) && !o.engine) { + $.error("no template engine specified"); + } + if (!o.local && !o.prefetch && !o.remote) { + $.error("one of local, prefetch, or remote is required"); + } + this.name = o.name || utils.getUniqueId(); + this.limit = o.limit || 5; + this.minLength = o.minLength || 1; + this.header = o.header; + this.footer = o.footer; + this.valueKey = o.valueKey || "value"; + this.template = compileTemplate(o.template, o.engine, this.valueKey); + this.local = o.local; + this.prefetch = o.prefetch; + this.remote = o.remote; + this.itemHash = {}; + this.adjacencyList = {}; + this.storage = o.name ? new PersistentStorage(o.name) : null; + } + utils.mixin(Dataset.prototype, { + _processLocalData: function(data) { + this._mergeProcessedData(this._processData(data)); + }, + _loadPrefetchData: function(o) { + var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; + if (this.storage) { + storedThumbprint = this.storage.get(keys.thumbprint); + storedProtocol = this.storage.get(keys.protocol); + storedItemHash = this.storage.get(keys.itemHash); + storedAdjacencyList = this.storage.get(keys.adjacencyList); + } + isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); + o = utils.isString(o) ? { + url: o + } : o; + o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; + if (storedItemHash && storedAdjacencyList && !isExpired) { + this._mergeProcessedData({ + itemHash: storedItemHash, + adjacencyList: storedAdjacencyList + }); + deferred = $.Deferred().resolve(); + } else { + deferred = $.getJSON(o.url).done(processPrefetchData); + } + return deferred; + function processPrefetchData(data) { + var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; + if (that.storage) { + that.storage.set(keys.itemHash, itemHash, o.ttl); + that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); + that.storage.set(keys.thumbprint, thumbprint, o.ttl); + that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); + } + that._mergeProcessedData(processedData); + } + }, + _transformDatum: function(datum) { + var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { + value: value, + tokens: tokens + }; + if (utils.isString(datum)) { + item.datum = {}; + item.datum[this.valueKey] = datum; + } else { + item.datum = datum; + } + item.tokens = utils.filter(item.tokens, function(token) { + return !utils.isBlankString(token); + }); + item.tokens = utils.map(item.tokens, function(token) { + return token.toLowerCase(); + }); + return item; + }, + _processData: function(data) { + var that = this, itemHash = {}, adjacencyList = {}; + utils.each(data, function(i, datum) { + var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); + itemHash[id] = item; + utils.each(item.tokens, function(i, token) { + var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); + !~utils.indexOf(adjacency, id) && adjacency.push(id); + }); + }); + return { + itemHash: itemHash, + adjacencyList: adjacencyList + }; + }, + _mergeProcessedData: function(processedData) { + var that = this; + utils.mixin(this.itemHash, processedData.itemHash); + utils.each(processedData.adjacencyList, function(character, adjacency) { + var masterAdjacency = that.adjacencyList[character]; + that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; + }); + }, + _getLocalSuggestions: function(terms) { + var that = this, firstChars = [], lists = [], shortestList, suggestions = []; + utils.each(terms, function(i, term) { + var firstChar = term.charAt(0); + !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); + }); + utils.each(firstChars, function(i, firstChar) { + var list = that.adjacencyList[firstChar]; + if (!list) { + return false; + } + lists.push(list); + if (!shortestList || list.length < shortestList.length) { + shortestList = list; + } + }); + if (lists.length < firstChars.length) { + return []; + } + utils.each(shortestList, function(i, id) { + var item = that.itemHash[id], isCandidate, isMatch; + isCandidate = utils.every(lists, function(list) { + return ~utils.indexOf(list, id); + }); + isMatch = isCandidate && utils.every(terms, function(term) { + return utils.some(item.tokens, function(token) { + return token.indexOf(term) === 0; + }); + }); + isMatch && suggestions.push(item); + }); + return suggestions; + }, + initialize: function() { + var deferred; + this.local && this._processLocalData(this.local); + this.transport = this.remote ? new Transport(this.remote) : null; + deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); + this.local = this.prefetch = this.remote = null; + this.initialize = function() { + return deferred; + }; + return deferred; + }, + getSuggestions: function(query, cb) { + var that = this, terms, suggestions, cacheHit = false; + if (query.length < this.minLength) { + return; + } + terms = utils.tokenizeQuery(query); + suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); + if (suggestions.length < this.limit && this.transport) { + cacheHit = this.transport.get(query, processRemoteData); + } + !cacheHit && cb && cb(suggestions); + function processRemoteData(data) { + suggestions = suggestions.slice(0); + utils.each(data, function(i, datum) { + var item = that._transformDatum(datum), isDuplicate; + isDuplicate = utils.some(suggestions, function(suggestion) { + return item.value === suggestion.value; + }); + !isDuplicate && suggestions.push(item); + return suggestions.length < that.limit; + }); + cb && cb(suggestions); + } + } + }); + return Dataset; + function compileTemplate(template, engine, valueKey) { + var renderFn, compiledTemplate; + if (utils.isFunction(template)) { + renderFn = template; + } else if (utils.isString(template)) { + compiledTemplate = engine.compile(template); + renderFn = utils.bind(compiledTemplate.render, compiledTemplate); + } else { + renderFn = function(context) { + return "

" + context[valueKey] + "

"; + }; + } + return renderFn; + } + }(); + var InputView = function() { + function InputView(o) { + var that = this; + utils.bindAll(this); + this.specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + this.$hint = $(o.hint); + this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); + if (!utils.isMsie()) { + this.$input.on("input.tt", this._compareQueryToInputValue); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + utils.defer(that._compareQueryToInputValue); + }); + } + this.query = this.$input.val(); + this.$overflowHelper = buildOverflowHelper(this.$input); + } + utils.mixin(InputView.prototype, EventTarget, { + _handleFocus: function() { + this.trigger("focused"); + }, + _handleBlur: function() { + this.trigger("blured"); + }, + _handleSpecialKeyEvent: function($e) { + var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; + keyName && this.trigger(keyName + "Keyed", $e); + }, + _compareQueryToInputValue: function() { + var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; + if (isSameQueryExceptWhitespace) { + this.trigger("whitespaceChanged", { + value: this.query + }); + } else if (!isSameQuery) { + this.trigger("queryChanged", { + value: this.query = inputValue + }); + } + }, + destroy: function() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$hint = this.$input = this.$overflowHelper = null; + }, + focus: function() { + this.$input.focus(); + }, + blur: function() { + this.$input.blur(); + }, + getQuery: function() { + return this.query; + }, + setQuery: function(query) { + this.query = query; + }, + getInputValue: function() { + return this.$input.val(); + }, + setInputValue: function(value, silent) { + this.$input.val(value); + !silent && this._compareQueryToInputValue(); + }, + getHintValue: function() { + return this.$hint.val(); + }, + setHintValue: function(value) { + this.$hint.val(value); + }, + getLanguageDirection: function() { + return (this.$input.css("direction") || "ltr").toLowerCase(); + }, + isOverflow: function() { + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() > this.$input.width(); + }, + isCursorAtEnd: function() { + var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; + if (utils.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + } + }); + return InputView; + function buildOverflowHelper($input) { + return $("").css({ + position: "absolute", + left: "-9999px", + visibility: "hidden", + whiteSpace: "nowrap", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function compareQueries(a, b) { + a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + return a === b; + } + }(); + var DropdownView = function() { + var html = { + suggestionsList: '' + }, css = { + suggestionsList: { + display: "block" + }, + suggestion: { + whiteSpace: "nowrap", + cursor: "pointer" + }, + suggestionChild: { + whiteSpace: "normal" + } + }; + function DropdownView(o) { + utils.bindAll(this); + this.isOpen = false; + this.isEmpty = true; + this.isMouseOverDropdown = false; + this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); + } + utils.mixin(DropdownView.prototype, EventTarget, { + _handleMouseenter: function() { + this.isMouseOverDropdown = true; + }, + _handleMouseleave: function() { + this.isMouseOverDropdown = false; + }, + _handleMouseover: function($e) { + var $suggestion = $($e.currentTarget); + this._getSuggestions().removeClass("tt-is-under-cursor"); + $suggestion.addClass("tt-is-under-cursor"); + }, + _handleSelection: function($e) { + var $suggestion = $($e.currentTarget); + this.trigger("suggestionSelected", extractSuggestion($suggestion)); + }, + _show: function() { + this.$menu.css("display", "block"); + }, + _hide: function() { + this.$menu.hide(); + }, + _moveCursor: function(increment) { + var $suggestions, $cur, nextIndex, $underCursor; + if (!this.isVisible()) { + return; + } + $suggestions = this._getSuggestions(); + $cur = $suggestions.filter(".tt-is-under-cursor"); + $cur.removeClass("tt-is-under-cursor"); + nextIndex = $suggestions.index($cur) + increment; + nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; + if (nextIndex === -1) { + this.trigger("cursorRemoved"); + return; + } else if (nextIndex < -1) { + nextIndex = $suggestions.length - 1; + } + $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); + this._ensureVisibility($underCursor); + this.trigger("cursorMoved", extractSuggestion($underCursor)); + }, + _getSuggestions: function() { + return this.$menu.find(".tt-suggestions > .tt-suggestion"); + }, + _ensureVisibility: function($el) { + var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); + if (elTop < 0) { + this.$menu.scrollTop(menuScrollTop + elTop); + } else if (menuHeight < elBottom) { + this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); + } + }, + destroy: function() { + this.$menu.off(".tt"); + this.$menu = null; + }, + isVisible: function() { + return this.isOpen && !this.isEmpty; + }, + closeUnlessMouseIsOverDropdown: function() { + if (!this.isMouseOverDropdown) { + this.close(); + } + }, + close: function() { + if (this.isOpen) { + this.isOpen = false; + this.isMouseOverDropdown = false; + this._hide(); + this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); + this.trigger("closed"); + } + }, + open: function() { + if (!this.isOpen) { + this.isOpen = true; + !this.isEmpty && this._show(); + this.trigger("opened"); + } + }, + setLanguageDirection: function(dir) { + var ltrCss = { + left: "0", + right: "auto" + }, rtlCss = { + left: "auto", + right: " 0" + }; + dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); + }, + moveCursorUp: function() { + this._moveCursor(-1); + }, + moveCursorDown: function() { + this._moveCursor(+1); + }, + getSuggestionUnderCursor: function() { + var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + getFirstSuggestion: function() { + var $suggestion = this._getSuggestions().first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + renderSuggestions: function(dataset, suggestions) { + var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
%body
', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; + if ($dataset.length === 0) { + $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); + $dataset = $("
").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); + } + if (suggestions.length > 0) { + this.isEmpty = false; + this.isOpen && this._show(); + elBuilder = document.createElement("div"); + fragment = document.createDocumentFragment(); + utils.each(suggestions, function(i, suggestion) { + suggestion.dataset = dataset.name; + compiledHtml = dataset.template(suggestion.datum); + elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); + $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); + $el.children().each(function() { + $(this).css(css.suggestionChild); + }); + fragment.appendChild($el[0]); + }); + $dataset.show().find(".tt-suggestions").html(fragment); + } else { + this.clearSuggestions(dataset.name); + } + this.trigger("suggestionsRendered"); + }, + clearSuggestions: function(datasetName) { + var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); + $datasets.hide(); + $suggestions.empty(); + if (this._getSuggestions().length === 0) { + this.isEmpty = true; + this._hide(); + } + } + }); + return DropdownView; + function extractSuggestion($el) { + return $el.data("suggestion"); + } + }(); + var TypeaheadView = function() { + var html = { + wrapper: '', + hint: '', + dropdown: '' + }, css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none" + }, + query: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + dropdown: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + } + }; + if (utils.isMsie()) { + utils.mixin(css.query, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + if (utils.isMsie() && utils.isMsie() <= 7) { + utils.mixin(css.wrapper, { + display: "inline", + zoom: "1" + }); + utils.mixin(css.query, { + marginTop: "-1px" + }); + } + function TypeaheadView(o) { + var $menu, $input, $hint; + utils.bindAll(this); + this.$node = buildDomStructure(o.input); + this.datasets = o.datasets; + this.dir = null; + this.eventBus = o.eventBus; + $menu = this.$node.find(".tt-dropdown-menu"); + $input = this.$node.find(".tt-query"); + $hint = this.$node.find(".tt-hint"); + this.dropdownView = new DropdownView({ + menu: $menu + }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); + this.inputView = new InputView({ + input: $input, + hint: $hint + }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); + } + utils.mixin(TypeaheadView.prototype, EventTarget, { + _managePreventDefault: function(e) { + var $e = e.data, hint, inputValue, preventDefault = false; + switch (e.type) { + case "tabKeyed": + hint = this.inputView.getHintValue(); + inputValue = this.inputView.getInputValue(); + preventDefault = hint && hint !== inputValue; + break; + + case "upKeyed": + case "downKeyed": + preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; + break; + } + preventDefault && $e.preventDefault(); + }, + _setLanguageDirection: function() { + var dir = this.inputView.getLanguageDirection(); + if (dir !== this.dir) { + this.dir = dir; + this.$node.css("direction", dir); + this.dropdownView.setLanguageDirection(dir); + } + }, + _updateHint: function() { + var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; + if (hint && dropdownIsVisible && !inputHasOverflow) { + inputValue = this.inputView.getInputValue(); + query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); + escapedQuery = utils.escapeRegExChars(query); + beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); + match = beginsWithQuery.exec(hint); + this.inputView.setHintValue(inputValue + (match ? match[1] : "")); + } + }, + _clearHint: function() { + this.inputView.setHintValue(""); + }, + _clearSuggestions: function() { + this.dropdownView.clearSuggestions(); + }, + _setInputValueToQuery: function() { + this.inputView.setInputValue(this.inputView.getQuery()); + }, + _setInputValueToSuggestionUnderCursor: function(e) { + var suggestion = e.data; + this.inputView.setInputValue(suggestion.value, true); + }, + _openDropdown: function() { + this.dropdownView.open(); + }, + _closeDropdown: function(e) { + this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); + }, + _moveDropdownCursor: function(e) { + var $e = e.data; + if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { + this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); + } + }, + _handleSelection: function(e) { + var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); + if (suggestion) { + this.inputView.setInputValue(suggestion.value); + byClick ? this.inputView.focus() : e.data.preventDefault(); + byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); + this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); + } + }, + _getSuggestions: function() { + var that = this, query = this.inputView.getQuery(); + if (utils.isBlankString(query)) { + return; + } + utils.each(this.datasets, function(i, dataset) { + dataset.getSuggestions(query, function(suggestions) { + if (query === that.inputView.getQuery()) { + that.dropdownView.renderSuggestions(dataset, suggestions); + } + }); + }); + }, + _autocomplete: function(e) { + var isCursorAtEnd, ignoreEvent, query, hint, suggestion; + if (e.type === "rightKeyed" || e.type === "leftKeyed") { + isCursorAtEnd = this.inputView.isCursorAtEnd(); + ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; + if (!isCursorAtEnd || ignoreEvent) { + return; + } + } + query = this.inputView.getQuery(); + hint = this.inputView.getHintValue(); + if (hint !== "" && query !== hint) { + suggestion = this.dropdownView.getFirstSuggestion(); + this.inputView.setInputValue(suggestion.value); + this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); + } + }, + _propagateEvent: function(e) { + this.eventBus.trigger(e.type); + }, + destroy: function() { + this.inputView.destroy(); + this.dropdownView.destroy(); + destroyDomStructure(this.$node); + this.$node = null; + }, + setQuery: function(query) { + this.inputView.setQuery(query); + this.inputView.setInputValue(query); + this._clearHint(); + this._clearSuggestions(); + this._getSuggestions(); + } + }); + return TypeaheadView; + function buildDomStructure(input) { + var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); + $wrapper = $wrapper.css(css.wrapper); + $dropdown = $dropdown.css(css.dropdown); + $hint.css(css.hint).css({ + backgroundAttachment: $input.css("background-attachment"), + backgroundClip: $input.css("background-clip"), + backgroundColor: $input.css("background-color"), + backgroundImage: $input.css("background-image"), + backgroundOrigin: $input.css("background-origin"), + backgroundPosition: $input.css("background-position"), + backgroundRepeat: $input.css("background-repeat"), + backgroundSize: $input.css("background-size") + }); + $input.data("ttAttrs", { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass("tt-query").attr({ + autocomplete: "off", + spellcheck: false + }).css(css.query); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); + } + function destroyDomStructure($node) { + var $input = $node.find(".tt-query"); + utils.each($input.data("ttAttrs"), function(key, val) { + utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); + $node.remove(); + } + }(); + (function() { + var cache = {}, viewKey = "ttView", methods; + methods = { + initialize: function(datasetDefs) { + var datasets; + datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; + if (datasetDefs.length === 0) { + $.error("no datasets provided"); + } + datasets = utils.map(datasetDefs, function(o) { + var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); + if (o.name) { + cache[o.name] = dataset; + } + return dataset; + }); + return this.each(initialize); + function initialize() { + var $input = $(this), deferreds, eventBus = new EventBus({ + el: $input + }); + deferreds = utils.map(datasets, function(dataset) { + return dataset.initialize(); + }); + $input.data(viewKey, new TypeaheadView({ + input: $input, + eventBus: eventBus = new EventBus({ + el: $input + }), + datasets: datasets + })); + $.when.apply($, deferreds).always(function() { + utils.defer(function() { + eventBus.trigger("initialized"); + }); + }); + } + }, + destroy: function() { + return this.each(destroy); + function destroy() { + var $this = $(this), view = $this.data(viewKey); + if (view) { + view.destroy(); + $this.removeData(viewKey); + } + } + }, + setQuery: function(query) { + return this.each(setQuery); + function setQuery() { + var view = $(this).data(viewKey); + view && view.setQuery(query); + } + } + }; + jQuery.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + })(); +})(window.jQuery); \ No newline at end of file diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/lib/typeahead.js-bootstrap.css b/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/lib/typeahead.js-bootstrap.css new file mode 100644 index 0000000..e44b673 --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/lib/typeahead.js-bootstrap.css @@ -0,0 +1,49 @@ +.twitter-typeahead .tt-query, +.twitter-typeahead .tt-hint { + margin-bottom: 0; +} + +.tt-dropdown-menu { + min-width: 160px; + margin-top: 2px; + padding: 5px 0; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + *border-right-width: 2px; + *border-bottom-width: 2px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); + -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); + box-shadow: 0 5px 10px rgba(0,0,0,.2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.tt-suggestion { + display: block; + padding: 3px 20px; +} + +.tt-suggestion.tt-is-under-cursor { + color: #fff; + background-color: #0081c2; + background-image: -moz-linear-gradient(top, #0088cc, #0077b3); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); + background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); + background-image: -o-linear-gradient(top, #0088cc, #0077b3); + background-image: linear-gradient(to bottom, #0088cc, #0077b3); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0) +} + +.tt-suggestion.tt-is-under-cursor a { + color: #fff; +} + +.tt-suggestion p { + margin: 0; +} diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/typeaheadjs.js b/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/typeaheadjs.js new file mode 100644 index 0000000..db2256a --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/typeaheadjs/typeaheadjs.js @@ -0,0 +1,86 @@ +/** +Typeahead.js input, based on [Twitter Typeahead](http://twitter.github.io/typeahead.js). +It is mainly replacement of typeahead in Bootstrap 3. + + +@class typeaheadjs +@extends text +@since 1.5.0 +@final +@example + + +**/ +(function ($) { + "use strict"; + + var Constructor = function (options) { + this.init('typeaheadjs', options, Constructor.defaults); + }; + + $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.text); + + $.extend(Constructor.prototype, { + render: function() { + this.renderClear(); + this.setClass(); + this.setAttr('placeholder'); + this.$input.typeahead(this.options.typeahead); + + // copy `input-sm | input-lg` classes to placeholder input + if($.fn.editableform.engine === 'bs3') { + if(this.$input.hasClass('input-sm')) { + this.$input.siblings('input.tt-hint').addClass('input-sm'); + } + if(this.$input.hasClass('input-lg')) { + this.$input.siblings('input.tt-hint').addClass('input-lg'); + } + } + } + }); + + Constructor.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { + /** + @property tpl + @default + **/ + tpl:'', + /** + Configuration of typeahead itself. + [Full list of options](https://github.com/twitter/typeahead.js#dataset). + + @property typeahead + @type object + @default null + **/ + typeahead: null, + /** + Whether to show `clear` button + + @property clear + @type boolean + @default true + **/ + clear: true + }); + + $.fn.editabletypes.typeaheadjs = Constructor; + +}(window.jQuery)); \ No newline at end of file diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css b/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css new file mode 100644 index 0000000..44ed777 --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.css @@ -0,0 +1,102 @@ +ul.wysihtml5-toolbar { + margin: 0; + padding: 0; + display: block; +} + +ul.wysihtml5-toolbar::after { + clear: both; + display: table; + content: ""; +} + +ul.wysihtml5-toolbar > li { + float: left; + display: list-item; + list-style: none; + margin: 0 5px 10px 0; +} + +ul.wysihtml5-toolbar a[data-wysihtml5-command=bold] { + font-weight: bold; +} + +ul.wysihtml5-toolbar a[data-wysihtml5-command=italic] { + font-style: italic; +} + +ul.wysihtml5-toolbar a[data-wysihtml5-command=underline] { + text-decoration: underline; +} + +ul.wysihtml5-toolbar a.btn.wysihtml5-command-active { + background-image: none; + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05); + background-color: #E6E6E6; + background-color: #D9D9D9; + outline: 0; +} + +ul.wysihtml5-commands-disabled .dropdown-menu { + display: none !important; +} + +ul.wysihtml5-toolbar div.wysihtml5-colors { + display:block; + width: 50px; + height: 20px; + margin-top: 2px; + margin-left: 5px; + position: absolute; + pointer-events: none; +} + +ul.wysihtml5-toolbar a.wysihtml5-colors-title { + padding-left: 70px; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="black"] { + background: black !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="silver"] { + background: silver !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="gray"] { + background: gray !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="maroon"] { + background: maroon !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="red"] { + background: red !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="purple"] { + background: purple !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="green"] { + background: green !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="olive"] { + background: olive !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="navy"] { + background: navy !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="blue"] { + background: blue !important; +} + +ul.wysihtml5-toolbar div[data-wysihtml5-command-value="orange"] { + background: orange !important; +} diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.js b/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.js new file mode 100644 index 0000000..695d264 --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.js @@ -0,0 +1,499 @@ +!function($, wysi) { + "use strict"; + + var tpl = { + "font-styles": function(locale, options) { + var size = (options && options.size) ? ' btn-'+options.size : ''; + return ""; + }, + + "emphasis": function(locale, options) { + var size = (options && options.size) ? ' btn-'+options.size : ''; + return "
  • " + + "" + + "
  • "; + }, + + "lists": function(locale, options) { + var size = (options && options.size) ? ' btn-'+options.size : ''; + return "
  • " + + "
    " + + "" + + "" + + "" + + "" + + "
    " + + "
  • "; + }, + + "link": function(locale, options) { + var size = (options && options.size) ? ' btn-'+options.size : ''; + return "
  • " + + "" + + "" + + "
  • "; + }, + + "image": function(locale, options) { + var size = (options && options.size) ? ' btn-'+options.size : ''; + return "
  • " + + "" + + "" + + "
  • "; + }, + + "html": function(locale, options) { + var size = (options && options.size) ? ' btn-'+options.size : ''; + return "
  • " + + "
    " + + "" + + "
    " + + "
  • "; + }, + + "color": function(locale, options) { + var size = (options && options.size) ? ' btn-'+options.size : ''; + return ""; + } + }; + + var templates = function(key, locale, options) { + return tpl[key](locale, options); + }; + + + var Wysihtml5 = function(el, options) { + this.el = el; + var toolbarOpts = options || defaultOptions; + for(var t in toolbarOpts.customTemplates) { + tpl[t] = toolbarOpts.customTemplates[t]; + } + this.toolbar = this.createToolbar(el, toolbarOpts); + this.editor = this.createEditor(options); + + window.editor = this.editor; + + $('iframe.wysihtml5-sandbox').each(function(i, el){ + $(el.contentWindow).off('focus.wysihtml5').on({ + 'focus.wysihtml5' : function(){ + $('li.dropdown').removeClass('open'); + } + }); + }); + }; + + Wysihtml5.prototype = { + + constructor: Wysihtml5, + + createEditor: function(options) { + options = options || {}; + + // Add the toolbar to a clone of the options object so multiple instances + // of the WYISYWG don't break because "toolbar" is already defined + options = $.extend(true, {}, options); + options.toolbar = this.toolbar[0]; + + var editor = new wysi.Editor(this.el[0], options); + + if(options && options.events) { + for(var eventName in options.events) { + editor.on(eventName, options.events[eventName]); + } + } + return editor; + }, + + createToolbar: function(el, options) { + var self = this; + var toolbar = $("
      ", { + 'class' : "wysihtml5-toolbar", + 'style': "display:none" + }); + var culture = options.locale || defaultOptions.locale || "en"; + for(var key in defaultOptions) { + var value = false; + + if(options[key] !== undefined) { + if(options[key] === true) { + value = true; + } + } else { + value = defaultOptions[key]; + } + + if(value === true) { + toolbar.append(templates(key, locale[culture], options)); + + if(key === "html") { + this.initHtml(toolbar); + } + + if(key === "link") { + this.initInsertLink(toolbar); + } + + if(key === "image") { + this.initInsertImage(toolbar); + } + } + } + + if(options.toolbar) { + for(key in options.toolbar) { + toolbar.append(options.toolbar[key]); + } + } + + toolbar.find("a[data-wysihtml5-command='formatBlock']").click(function(e) { + var target = e.target || e.srcElement; + var el = $(target); + self.toolbar.find('.current-font').text(el.html()); + }); + + toolbar.find("a[data-wysihtml5-command='foreColor']").click(function(e) { + var target = e.target || e.srcElement; + var el = $(target); + self.toolbar.find('.current-color').text(el.html()); + }); + + this.el.before(toolbar); + + return toolbar; + }, + + initHtml: function(toolbar) { + var changeViewSelector = "a[data-wysihtml5-action='change_view']"; + toolbar.find(changeViewSelector).click(function(e) { + toolbar.find('a.btn').not(changeViewSelector).toggleClass('disabled'); + }); + }, + + initInsertImage: function(toolbar) { + var self = this; + var insertImageModal = toolbar.find('.bootstrap-wysihtml5-insert-image-modal'); + var urlInput = insertImageModal.find('.bootstrap-wysihtml5-insert-image-url'); + var insertButton = insertImageModal.find('a.btn-primary'); + var initialValue = urlInput.val(); + var caretBookmark; + + var insertImage = function() { + var url = urlInput.val(); + urlInput.val(initialValue); + self.editor.currentView.element.focus(); + if (caretBookmark) { + self.editor.composer.selection.setBookmark(caretBookmark); + caretBookmark = null; + } + self.editor.composer.commands.exec("insertImage", url); + }; + + urlInput.keypress(function(e) { + if(e.which == 13) { + insertImage(); + insertImageModal.modal('hide'); + } + }); + + insertButton.click(insertImage); + + insertImageModal.on('shown', function() { + urlInput.focus(); + }); + + insertImageModal.on('hide', function() { + self.editor.currentView.element.focus(); + }); + + toolbar.find('a[data-wysihtml5-command=insertImage]').click(function() { + var activeButton = $(this).hasClass("wysihtml5-command-active"); + + if (!activeButton) { + self.editor.currentView.element.focus(false); + caretBookmark = self.editor.composer.selection.getBookmark(); + insertImageModal.appendTo('body').modal('show'); + insertImageModal.on('click.dismiss.modal', '[data-dismiss="modal"]', function(e) { + e.stopPropagation(); + }); + return false; + } + else { + return true; + } + }); + }, + + initInsertLink: function(toolbar) { + var self = this; + var insertLinkModal = toolbar.find('.bootstrap-wysihtml5-insert-link-modal'); + var urlInput = insertLinkModal.find('.bootstrap-wysihtml5-insert-link-url'); + var insertButton = insertLinkModal.find('a.btn-primary'); + var initialValue = urlInput.val(); + var caretBookmark; + + var insertLink = function() { + var url = urlInput.val(); + urlInput.val(initialValue); + self.editor.currentView.element.focus(); + if (caretBookmark) { + self.editor.composer.selection.setBookmark(caretBookmark); + caretBookmark = null; + } + self.editor.composer.commands.exec("createLink", { + href: url, + target: "_blank", + rel: "nofollow" + }); + }; + var pressedEnter = false; + + urlInput.keypress(function(e) { + if(e.which == 13) { + insertLink(); + insertLinkModal.modal('hide'); + } + }); + + insertButton.click(insertLink); + + insertLinkModal.on('shown', function() { + urlInput.focus(); + }); + + insertLinkModal.on('hide', function() { + self.editor.currentView.element.focus(); + }); + + toolbar.find('a[data-wysihtml5-command=createLink]').click(function() { + var activeButton = $(this).hasClass("wysihtml5-command-active"); + + if (!activeButton) { + self.editor.currentView.element.focus(false); + caretBookmark = self.editor.composer.selection.getBookmark(); + insertLinkModal.appendTo('body').modal('show'); + insertLinkModal.on('click.dismiss.modal', '[data-dismiss="modal"]', function(e) { + e.stopPropagation(); + }); + return false; + } + else { + return true; + } + }); + } + }; + + // these define our public api + var methods = { + resetDefaults: function() { + $.fn.wysihtml5.defaultOptions = $.extend(true, {}, $.fn.wysihtml5.defaultOptionsCache); + }, + bypassDefaults: function(options) { + return this.each(function () { + var $this = $(this); + $this.data('wysihtml5', new Wysihtml5($this, options)); + }); + }, + shallowExtend: function (options) { + var settings = $.extend({}, $.fn.wysihtml5.defaultOptions, options || {}); + var that = this; + return methods.bypassDefaults.apply(that, [settings]); + }, + deepExtend: function(options) { + var settings = $.extend(true, {}, $.fn.wysihtml5.defaultOptions, options || {}); + var that = this; + return methods.bypassDefaults.apply(that, [settings]); + }, + init: function(options) { + var that = this; + return methods.shallowExtend.apply(that, [options]); + } + }; + + $.fn.wysihtml5 = function ( method ) { + if ( methods[method] ) { + return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); + } else if ( typeof method === 'object' || ! method ) { + return methods.init.apply( this, arguments ); + } else { + $.error( 'Method ' + method + ' does not exist on jQuery.wysihtml5' ); + } + }; + + $.fn.wysihtml5.Constructor = Wysihtml5; + + var defaultOptions = $.fn.wysihtml5.defaultOptions = { + "font-styles": true, + "color": false, + "emphasis": true, + "lists": true, + "html": false, + "link": true, + "image": true, + events: {}, + parserRules: { + classes: { + // (path_to_project/lib/css/wysiwyg-color.css) + "wysiwyg-color-silver" : 1, + "wysiwyg-color-gray" : 1, + "wysiwyg-color-white" : 1, + "wysiwyg-color-maroon" : 1, + "wysiwyg-color-red" : 1, + "wysiwyg-color-purple" : 1, + "wysiwyg-color-fuchsia" : 1, + "wysiwyg-color-green" : 1, + "wysiwyg-color-lime" : 1, + "wysiwyg-color-olive" : 1, + "wysiwyg-color-yellow" : 1, + "wysiwyg-color-navy" : 1, + "wysiwyg-color-blue" : 1, + "wysiwyg-color-teal" : 1, + "wysiwyg-color-aqua" : 1, + "wysiwyg-color-orange" : 1 + }, + tags: { + "b": {}, + "i": {}, + "br": {}, + "ol": {}, + "ul": {}, + "li": {}, + "h1": {}, + "h2": {}, + "h3": {}, + "blockquote": {}, + "u": 1, + "img": { + "check_attributes": { + "width": "numbers", + "alt": "alt", + "src": "url", + "height": "numbers" + } + }, + "a": { + set_attributes: { + target: "_blank", + rel: "nofollow" + }, + check_attributes: { + href: "url" // important to avoid XSS + } + }, + "span": 1, + "div": 1, + // to allow save and edit files with code tag hacks + "code": 1, + "pre": 1 + } + }, + stylesheets: ["./lib/css/wysiwyg-color.css"], // (path_to_project/lib/css/wysiwyg-color.css) + locale: "en" + }; + + if (typeof $.fn.wysihtml5.defaultOptionsCache === 'undefined') { + $.fn.wysihtml5.defaultOptionsCache = $.extend(true, {}, $.fn.wysihtml5.defaultOptions); + } + + var locale = $.fn.wysihtml5.locale = { + en: { + font_styles: { + normal: "Normal text", + h1: "Heading 1", + h2: "Heading 2", + h3: "Heading 3" + }, + emphasis: { + bold: "Bold", + italic: "Italic", + underline: "Underline" + }, + lists: { + unordered: "Unordered list", + ordered: "Ordered list", + outdent: "Outdent", + indent: "Indent" + }, + link: { + insert: "Insert link", + cancel: "Cancel" + }, + image: { + insert: "Insert image", + cancel: "Cancel" + }, + html: { + edit: "Edit HTML" + }, + colours: { + black: "Black", + silver: "Silver", + gray: "Grey", + maroon: "Maroon", + red: "Red", + purple: "Purple", + green: "Green", + olive: "Olive", + navy: "Navy", + blue: "Blue", + orange: "Orange" + } + } + }; + +}(window.jQuery, window.wysihtml5); diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js b/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js new file mode 100644 index 0000000..59d2961 --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/bootstrap-wysihtml5-0.0.2.min.js @@ -0,0 +1 @@ +!function($,wysi){"use strict";var tpl={"font-styles":function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return""},emphasis:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
    • "+""+"
    • "},lists:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
    • "+"
      "+""+""+""+""+"
      "+"
    • "},link:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
    • "+""+""+"
    • "},image:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
    • "+""+""+"
    • "},html:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return"
    • "+"
      "+""+"
      "+"
    • "},color:function(locale,options){var size=options&&options.size?" btn-"+options.size:"";return""}};var templates=function(key,locale,options){return tpl[key](locale,options)};var Wysihtml5=function(el,options){this.el=el;var toolbarOpts=options||defaultOptions;for(var t in toolbarOpts.customTemplates){tpl[t]=toolbarOpts.customTemplates[t]}this.toolbar=this.createToolbar(el,toolbarOpts);this.editor=this.createEditor(options);window.editor=this.editor;$("iframe.wysihtml5-sandbox").each(function(i,el){$(el.contentWindow).off("focus.wysihtml5").on({"focus.wysihtml5":function(){$("li.dropdown").removeClass("open")}})})};Wysihtml5.prototype={constructor:Wysihtml5,createEditor:function(options){options=options||{};options=$.extend(true,{},options);options.toolbar=this.toolbar[0];var editor=new wysi.Editor(this.el[0],options);if(options&&options.events){for(var eventName in options.events){editor.on(eventName,options.events[eventName])}}return editor},createToolbar:function(el,options){var self=this;var toolbar=$("
        ",{"class":"wysihtml5-toolbar",style:"display:none"});var culture=options.locale||defaultOptions.locale||"en";for(var key in defaultOptions){var value=false;if(options[key]!==undefined){if(options[key]===true){value=true}}else{value=defaultOptions[key]}if(value===true){toolbar.append(templates(key,locale[culture],options));if(key==="html"){this.initHtml(toolbar)}if(key==="link"){this.initInsertLink(toolbar)}if(key==="image"){this.initInsertImage(toolbar)}}}if(options.toolbar){for(key in options.toolbar){toolbar.append(options.toolbar[key])}}toolbar.find("a[data-wysihtml5-command='formatBlock']").click(function(e){var target=e.target||e.srcElement;var el=$(target);self.toolbar.find(".current-font").text(el.html())});toolbar.find("a[data-wysihtml5-command='foreColor']").click(function(e){var target=e.target||e.srcElement;var el=$(target);self.toolbar.find(".current-color").text(el.html())});this.el.before(toolbar);return toolbar},initHtml:function(toolbar){var changeViewSelector="a[data-wysihtml5-action='change_view']";toolbar.find(changeViewSelector).click(function(e){toolbar.find("a.btn").not(changeViewSelector).toggleClass("disabled")})},initInsertImage:function(toolbar){var self=this;var insertImageModal=toolbar.find(".bootstrap-wysihtml5-insert-image-modal");var urlInput=insertImageModal.find(".bootstrap-wysihtml5-insert-image-url");var insertButton=insertImageModal.find("a.btn-primary");var initialValue=urlInput.val();var caretBookmark;var insertImage=function(){var url=urlInput.val();urlInput.val(initialValue);self.editor.currentView.element.focus();if(caretBookmark){self.editor.composer.selection.setBookmark(caretBookmark);caretBookmark=null}self.editor.composer.commands.exec("insertImage",url)};urlInput.keypress(function(e){if(e.which==13){insertImage();insertImageModal.modal("hide")}});insertButton.click(insertImage);insertImageModal.on("shown",function(){urlInput.focus()});insertImageModal.on("hide",function(){self.editor.currentView.element.focus()});toolbar.find("a[data-wysihtml5-command=insertImage]").click(function(){var activeButton=$(this).hasClass("wysihtml5-command-active");if(!activeButton){self.editor.currentView.element.focus(false);caretBookmark=self.editor.composer.selection.getBookmark();insertImageModal.appendTo("body").modal("show");insertImageModal.on("click.dismiss.modal",'[data-dismiss="modal"]',function(e){e.stopPropagation()});return false}else{return true}})},initInsertLink:function(toolbar){var self=this;var insertLinkModal=toolbar.find(".bootstrap-wysihtml5-insert-link-modal");var urlInput=insertLinkModal.find(".bootstrap-wysihtml5-insert-link-url");var insertButton=insertLinkModal.find("a.btn-primary");var initialValue=urlInput.val();var caretBookmark;var insertLink=function(){var url=urlInput.val();urlInput.val(initialValue);self.editor.currentView.element.focus();if(caretBookmark){self.editor.composer.selection.setBookmark(caretBookmark);caretBookmark=null}self.editor.composer.commands.exec("createLink",{href:url,target:"_blank",rel:"nofollow"})};var pressedEnter=false;urlInput.keypress(function(e){if(e.which==13){insertLink();insertLinkModal.modal("hide")}});insertButton.click(insertLink);insertLinkModal.on("shown",function(){urlInput.focus()});insertLinkModal.on("hide",function(){self.editor.currentView.element.focus()});toolbar.find("a[data-wysihtml5-command=createLink]").click(function(){var activeButton=$(this).hasClass("wysihtml5-command-active");if(!activeButton){self.editor.currentView.element.focus(false);caretBookmark=self.editor.composer.selection.getBookmark();insertLinkModal.appendTo("body").modal("show");insertLinkModal.on("click.dismiss.modal",'[data-dismiss="modal"]',function(e){e.stopPropagation()});return false}else{return true}})}};var methods={resetDefaults:function(){$.fn.wysihtml5.defaultOptions=$.extend(true,{},$.fn.wysihtml5.defaultOptionsCache)},bypassDefaults:function(options){return this.each(function(){var $this=$(this);$this.data("wysihtml5",new Wysihtml5($this,options))})},shallowExtend:function(options){var settings=$.extend({},$.fn.wysihtml5.defaultOptions,options||{});var that=this;return methods.bypassDefaults.apply(that,[settings])},deepExtend:function(options){var settings=$.extend(true,{},$.fn.wysihtml5.defaultOptions,options||{});var that=this;return methods.bypassDefaults.apply(that,[settings])},init:function(options){var that=this;return methods.shallowExtend.apply(that,[options])}};$.fn.wysihtml5=function(method){if(methods[method]){return methods[method].apply(this,Array.prototype.slice.call(arguments,1))}else if(typeof method==="object"||!method){return methods.init.apply(this,arguments)}else{$.error("Method "+method+" does not exist on jQuery.wysihtml5")}};$.fn.wysihtml5.Constructor=Wysihtml5;var defaultOptions=$.fn.wysihtml5.defaultOptions={"font-styles":true,color:false,emphasis:true,lists:true,html:false,link:true,image:true,events:{},parserRules:{classes:{"wysiwyg-color-silver":1,"wysiwyg-color-gray":1,"wysiwyg-color-white":1,"wysiwyg-color-maroon":1,"wysiwyg-color-red":1,"wysiwyg-color-purple":1,"wysiwyg-color-fuchsia":1,"wysiwyg-color-green":1,"wysiwyg-color-lime":1,"wysiwyg-color-olive":1,"wysiwyg-color-yellow":1,"wysiwyg-color-navy":1,"wysiwyg-color-blue":1,"wysiwyg-color-teal":1,"wysiwyg-color-aqua":1,"wysiwyg-color-orange":1},tags:{b:{},i:{},br:{},ol:{},ul:{},li:{},h1:{},h2:{},h3:{},blockquote:{},u:1,img:{check_attributes:{width:"numbers",alt:"alt",src:"url",height:"numbers"}},a:{set_attributes:{target:"_blank",rel:"nofollow"},check_attributes:{href:"url"}},span:1,div:1,code:1,pre:1}},stylesheets:["./lib/css/wysiwyg-color.css"],locale:"en"};if(typeof $.fn.wysihtml5.defaultOptionsCache==="undefined"){$.fn.wysihtml5.defaultOptionsCache=$.extend(true,{},$.fn.wysihtml5.defaultOptions)}var locale=$.fn.wysihtml5.locale={en:{font_styles:{normal:"Normal text",h1:"Heading 1",h2:"Heading 2",h3:"Heading 3"},emphasis:{bold:"Bold",italic:"Italic",underline:"Underline"},lists:{unordered:"Unordered list",ordered:"Ordered list",outdent:"Outdent",indent:"Indent"},link:{insert:"Insert link",cancel:"Cancel"},image:{insert:"Insert image",cancel:"Cancel"},html:{edit:"Edit HTML"},colours:{black:"Black",silver:"Silver",gray:"Grey",maroon:"Maroon",red:"Red",purple:"Purple",green:"Green",olive:"Olive",navy:"Navy",blue:"Blue",orange:"Orange"}}}}(window.jQuery,window.wysihtml5); \ No newline at end of file diff --git a/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0.js b/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0.js new file mode 100644 index 0000000..939dca9 --- /dev/null +++ b/views/js/bootstrap3-editable-1.5.1/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.2/wysihtml5-0.3.0.js @@ -0,0 +1,9521 @@ +/** + * @license wysihtml5 v0.3.0 + * https://github.com/xing/wysihtml5 + * + * Author: Christopher Blum (https://github.com/tiff) + * + * Copyright (C) 2012 XING AG + * Licensed under the MIT license (MIT) + * + */ +var wysihtml5 = { + version: "0.3.0", + + // namespaces + commands: {}, + dom: {}, + quirks: {}, + toolbar: {}, + lang: {}, + selection: {}, + views: {}, + + INVISIBLE_SPACE: "\uFEFF", + + EMPTY_FUNCTION: function() {}, + + ELEMENT_NODE: 1, + TEXT_NODE: 3, + + BACKSPACE_KEY: 8, + ENTER_KEY: 13, + ESCAPE_KEY: 27, + SPACE_KEY: 32, + DELETE_KEY: 46 +};/** + * @license Rangy, a cross-browser JavaScript range and selection library + * http://code.google.com/p/rangy/ + * + * Copyright 2011, Tim Down + * Licensed under the MIT license. + * Version: 1.2.2 + * Build date: 13 November 2011 + */ +window['rangy'] = (function() { + + + var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; + + var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", + "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"]; + + var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", + "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", + "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; + + var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; + + // Subset of TextRange's full set of methods that we're interested in + var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark", + "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"]; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Trio of functions taken from Peter Michaux's article: + // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting + function isHostMethod(o, p) { + var t = typeof o[p]; + return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; + } + + function isHostObject(o, p) { + return !!(typeof o[p] == OBJECT && o[p]); + } + + function isHostProperty(o, p) { + return typeof o[p] != UNDEFINED; + } + + // Creates a convenience function to save verbose repeated calls to tests functions + function createMultiplePropertyTest(testFunc) { + return function(o, props) { + var i = props.length; + while (i--) { + if (!testFunc(o, props[i])) { + return false; + } + } + return true; + }; + } + + // Next trio of functions are a convenience to save verbose repeated calls to previous two functions + var areHostMethods = createMultiplePropertyTest(isHostMethod); + var areHostObjects = createMultiplePropertyTest(isHostObject); + var areHostProperties = createMultiplePropertyTest(isHostProperty); + + function isTextRange(range) { + return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); + } + + var api = { + version: "1.2.2", + initialized: false, + supported: true, + + util: { + isHostMethod: isHostMethod, + isHostObject: isHostObject, + isHostProperty: isHostProperty, + areHostMethods: areHostMethods, + areHostObjects: areHostObjects, + areHostProperties: areHostProperties, + isTextRange: isTextRange + }, + + features: {}, + + modules: {}, + config: { + alertOnWarn: false, + preferTextRange: false + } + }; + + function fail(reason) { + window.alert("Rangy not supported in your browser. Reason: " + reason); + api.initialized = true; + api.supported = false; + } + + api.fail = fail; + + function warn(msg) { + var warningMessage = "Rangy warning: " + msg; + if (api.config.alertOnWarn) { + window.alert(warningMessage); + } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) { + window.console.log(warningMessage); + } + } + + api.warn = warn; + + if ({}.hasOwnProperty) { + api.util.extend = function(o, props) { + for (var i in props) { + if (props.hasOwnProperty(i)) { + o[i] = props[i]; + } + } + }; + } else { + fail("hasOwnProperty not supported"); + } + + var initListeners = []; + var moduleInitializers = []; + + // Initialization + function init() { + if (api.initialized) { + return; + } + var testRange; + var implementsDomRange = false, implementsTextRange = false; + + // First, perform basic feature tests + + if (isHostMethod(document, "createRange")) { + testRange = document.createRange(); + if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { + implementsDomRange = true; + } + testRange.detach(); + } + + var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0]; + + if (body && isHostMethod(body, "createTextRange")) { + testRange = body.createTextRange(); + if (isTextRange(testRange)) { + implementsTextRange = true; + } + } + + if (!implementsDomRange && !implementsTextRange) { + fail("Neither Range nor TextRange are implemented"); + } + + api.initialized = true; + api.features = { + implementsDomRange: implementsDomRange, + implementsTextRange: implementsTextRange + }; + + // Initialize modules and call init listeners + var allListeners = moduleInitializers.concat(initListeners); + for (var i = 0, len = allListeners.length; i < len; ++i) { + try { + allListeners[i](api); + } catch (ex) { + if (isHostObject(window, "console") && isHostMethod(window.console, "log")) { + window.console.log("Init listener threw an exception. Continuing.", ex); + } + + } + } + } + + // Allow external scripts to initialize this library in case it's loaded after the document has loaded + api.init = init; + + // Execute listener immediately if already initialized + api.addInitListener = function(listener) { + if (api.initialized) { + listener(api); + } else { + initListeners.push(listener); + } + }; + + var createMissingNativeApiListeners = []; + + api.addCreateMissingNativeApiListener = function(listener) { + createMissingNativeApiListeners.push(listener); + }; + + function createMissingNativeApi(win) { + win = win || window; + init(); + + // Notify listeners + for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) { + createMissingNativeApiListeners[i](win); + } + } + + api.createMissingNativeApi = createMissingNativeApi; + + /** + * @constructor + */ + function Module(name) { + this.name = name; + this.initialized = false; + this.supported = false; + } + + Module.prototype.fail = function(reason) { + this.initialized = true; + this.supported = false; + + throw new Error("Module '" + this.name + "' failed to load: " + reason); + }; + + Module.prototype.warn = function(msg) { + api.warn("Module " + this.name + ": " + msg); + }; + + Module.prototype.createError = function(msg) { + return new Error("Error in Rangy " + this.name + " module: " + msg); + }; + + api.createModule = function(name, initFunc) { + var module = new Module(name); + api.modules[name] = module; + + moduleInitializers.push(function(api) { + initFunc(api, module); + module.initialized = true; + module.supported = true; + }); + }; + + api.requireModules = function(modules) { + for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) { + moduleName = modules[i]; + module = api.modules[moduleName]; + if (!module || !(module instanceof Module)) { + throw new Error("Module '" + moduleName + "' not found"); + } + if (!module.supported) { + throw new Error("Module '" + moduleName + "' not supported"); + } + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Wait for document to load before running tests + + var docReady = false; + + var loadHandler = function(e) { + + if (!docReady) { + docReady = true; + if (!api.initialized) { + init(); + } + } + }; + + // Test whether we have window and document objects that we will need + if (typeof window == UNDEFINED) { + fail("No window found"); + return; + } + if (typeof document == UNDEFINED) { + fail("No document found"); + return; + } + + if (isHostMethod(document, "addEventListener")) { + document.addEventListener("DOMContentLoaded", loadHandler, false); + } + + // Add a fallback in case the DOMContentLoaded event isn't supported + if (isHostMethod(window, "addEventListener")) { + window.addEventListener("load", loadHandler, false); + } else if (isHostMethod(window, "attachEvent")) { + window.attachEvent("onload", loadHandler); + } else { + fail("Window does not have required addEventListener or attachEvent method"); + } + + return api; +})(); +rangy.createModule("DomUtil", function(api, module) { + + var UNDEF = "undefined"; + var util = api.util; + + // Perform feature tests + if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { + module.fail("document missing a Node creation method"); + } + + if (!util.isHostMethod(document, "getElementsByTagName")) { + module.fail("document missing getElementsByTagName method"); + } + + var el = document.createElement("div"); + if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { + module.fail("Incomplete Element implementation"); + } + + // innerHTML is required for Range's createContextualFragment method + if (!util.isHostProperty(el, "innerHTML")) { + module.fail("Element is missing innerHTML property"); + } + + var textNode = document.createTextNode("test"); + if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || + !util.areHostProperties(textNode, ["data"]))) { + module.fail("Incomplete Text Node implementation"); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been + // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that + // contains just the document as a single element and the value searched for is the document. + var arrayContains = /*Array.prototype.indexOf ? + function(arr, val) { + return arr.indexOf(val) > -1; + }:*/ + + function(arr, val) { + var i = arr.length; + while (i--) { + if (arr[i] === val) { + return true; + } + } + return false; + }; + + // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI + function isHtmlNamespace(node) { + var ns; + return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); + } + + function parentElement(node) { + var parent = node.parentNode; + return (parent.nodeType == 1) ? parent : null; + } + + function getNodeIndex(node) { + var i = 0; + while( (node = node.previousSibling) ) { + i++; + } + return i; + } + + function getNodeLength(node) { + var childNodes; + return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0); + } + + function getCommonAncestor(node1, node2) { + var ancestors = [], n; + for (n = node1; n; n = n.parentNode) { + ancestors.push(n); + } + + for (n = node2; n; n = n.parentNode) { + if (arrayContains(ancestors, n)) { + return n; + } + } + + return null; + } + + function isAncestorOf(ancestor, descendant, selfIsAncestor) { + var n = selfIsAncestor ? descendant : descendant.parentNode; + while (n) { + if (n === ancestor) { + return true; + } else { + n = n.parentNode; + } + } + return false; + } + + function getClosestAncestorIn(node, ancestor, selfIsAncestor) { + var p, n = selfIsAncestor ? node : node.parentNode; + while (n) { + p = n.parentNode; + if (p === ancestor) { + return n; + } + n = p; + } + return null; + } + + function isCharacterDataNode(node) { + var t = node.nodeType; + return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment + } + + function insertAfter(node, precedingNode) { + var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; + if (nextNode) { + parent.insertBefore(node, nextNode); + } else { + parent.appendChild(node); + } + return node; + } + + // Note that we cannot use splitText() because it is bugridden in IE 9. + function splitDataNode(node, index) { + var newNode = node.cloneNode(false); + newNode.deleteData(0, index); + node.deleteData(index, node.length - index); + insertAfter(newNode, node); + return newNode; + } + + function getDocument(node) { + if (node.nodeType == 9) { + return node; + } else if (typeof node.ownerDocument != UNDEF) { + return node.ownerDocument; + } else if (typeof node.document != UNDEF) { + return node.document; + } else if (node.parentNode) { + return getDocument(node.parentNode); + } else { + throw new Error("getDocument: no document found for node"); + } + } + + function getWindow(node) { + var doc = getDocument(node); + if (typeof doc.defaultView != UNDEF) { + return doc.defaultView; + } else if (typeof doc.parentWindow != UNDEF) { + return doc.parentWindow; + } else { + throw new Error("Cannot get a window object for node"); + } + } + + function getIframeDocument(iframeEl) { + if (typeof iframeEl.contentDocument != UNDEF) { + return iframeEl.contentDocument; + } else if (typeof iframeEl.contentWindow != UNDEF) { + return iframeEl.contentWindow.document; + } else { + throw new Error("getIframeWindow: No Document object found for iframe element"); + } + } + + function getIframeWindow(iframeEl) { + if (typeof iframeEl.contentWindow != UNDEF) { + return iframeEl.contentWindow; + } else if (typeof iframeEl.contentDocument != UNDEF) { + return iframeEl.contentDocument.defaultView; + } else { + throw new Error("getIframeWindow: No Window object found for iframe element"); + } + } + + function getBody(doc) { + return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; + } + + function getRootContainer(node) { + var parent; + while ( (parent = node.parentNode) ) { + node = parent; + } + return node; + } + + function comparePoints(nodeA, offsetA, nodeB, offsetB) { + // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing + var nodeC, root, childA, childB, n; + if (nodeA == nodeB) { + + // Case 1: nodes are the same + return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; + } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { + + // Case 2: node C (container B or an ancestor) is a child node of A + return offsetA <= getNodeIndex(nodeC) ? -1 : 1; + } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { + + // Case 3: node C (container A or an ancestor) is a child node of B + return getNodeIndex(nodeC) < offsetB ? -1 : 1; + } else { + + // Case 4: containers are siblings or descendants of siblings + root = getCommonAncestor(nodeA, nodeB); + childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); + childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); + + if (childA === childB) { + // This shouldn't be possible + + throw new Error("comparePoints got to case 4 and childA and childB are the same!"); + } else { + n = root.firstChild; + while (n) { + if (n === childA) { + return -1; + } else if (n === childB) { + return 1; + } + n = n.nextSibling; + } + throw new Error("Should not be here!"); + } + } + } + + function fragmentFromNodeChildren(node) { + var fragment = getDocument(node).createDocumentFragment(), child; + while ( (child = node.firstChild) ) { + fragment.appendChild(child); + } + return fragment; + } + + function inspectNode(node) { + if (!node) { + return "[No node]"; + } + if (isCharacterDataNode(node)) { + return '"' + node.data + '"'; + } else if (node.nodeType == 1) { + var idAttr = node.id ? ' id="' + node.id + '"' : ""; + return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]"; + } else { + return node.nodeName; + } + } + + /** + * @constructor + */ + function NodeIterator(root) { + this.root = root; + this._next = root; + } + + NodeIterator.prototype = { + _current: null, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + var n = this._current = this._next; + var child, next; + if (this._current) { + child = n.firstChild; + if (child) { + this._next = child; + } else { + next = null; + while ((n !== this.root) && !(next = n.nextSibling)) { + n = n.parentNode; + } + this._next = next; + } + } + return this._current; + }, + + detach: function() { + this._current = this._next = this.root = null; + } + }; + + function createIterator(root) { + return new NodeIterator(root); + } + + /** + * @constructor + */ + function DomPosition(node, offset) { + this.node = node; + this.offset = offset; + } + + DomPosition.prototype = { + equals: function(pos) { + return this.node === pos.node & this.offset == pos.offset; + }, + + inspect: function() { + return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; + } + }; + + /** + * @constructor + */ + function DOMException(codeName) { + this.code = this[codeName]; + this.codeName = codeName; + this.message = "DOMException: " + this.codeName; + } + + DOMException.prototype = { + INDEX_SIZE_ERR: 1, + HIERARCHY_REQUEST_ERR: 3, + WRONG_DOCUMENT_ERR: 4, + NO_MODIFICATION_ALLOWED_ERR: 7, + NOT_FOUND_ERR: 8, + NOT_SUPPORTED_ERR: 9, + INVALID_STATE_ERR: 11 + }; + + DOMException.prototype.toString = function() { + return this.message; + }; + + api.dom = { + arrayContains: arrayContains, + isHtmlNamespace: isHtmlNamespace, + parentElement: parentElement, + getNodeIndex: getNodeIndex, + getNodeLength: getNodeLength, + getCommonAncestor: getCommonAncestor, + isAncestorOf: isAncestorOf, + getClosestAncestorIn: getClosestAncestorIn, + isCharacterDataNode: isCharacterDataNode, + insertAfter: insertAfter, + splitDataNode: splitDataNode, + getDocument: getDocument, + getWindow: getWindow, + getIframeWindow: getIframeWindow, + getIframeDocument: getIframeDocument, + getBody: getBody, + getRootContainer: getRootContainer, + comparePoints: comparePoints, + inspectNode: inspectNode, + fragmentFromNodeChildren: fragmentFromNodeChildren, + createIterator: createIterator, + DomPosition: DomPosition + }; + + api.DOMException = DOMException; +});rangy.createModule("DomRange", function(api, module) { + api.requireModules( ["DomUtil"] ); + + + var dom = api.dom; + var DomPosition = dom.DomPosition; + var DOMException = api.DOMException; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Utility functions + + function isNonTextPartiallySelected(node, range) { + return (node.nodeType != 3) && + (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true)); + } + + function getRangeDocument(range) { + return dom.getDocument(range.startContainer); + } + + function dispatchEvent(range, type, args) { + var listeners = range._listeners[type]; + if (listeners) { + for (var i = 0, len = listeners.length; i < len; ++i) { + listeners[i].call(range, {target: range, args: args}); + } + } + } + + function getBoundaryBeforeNode(node) { + return new DomPosition(node.parentNode, dom.getNodeIndex(node)); + } + + function getBoundaryAfterNode(node) { + return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1); + } + + function insertNodeAtPosition(node, n, o) { + var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; + if (dom.isCharacterDataNode(n)) { + if (o == n.length) { + dom.insertAfter(node, n); + } else { + n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o)); + } + } else if (o >= n.childNodes.length) { + n.appendChild(node); + } else { + n.insertBefore(node, n.childNodes[o]); + } + return firstNodeInserted; + } + + function cloneSubtree(iterator) { + var partiallySelected; + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { + partiallySelected = iterator.isPartiallySelectedSubtree(); + + node = node.cloneNode(!partiallySelected); + if (partiallySelected) { + subIterator = iterator.getSubtreeIterator(); + node.appendChild(cloneSubtree(subIterator)); + subIterator.detach(true); + } + + if (node.nodeType == 10) { // DocumentType + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + frag.appendChild(node); + } + return frag; + } + + function iterateSubtree(rangeIterator, func, iteratorState) { + var it, n; + iteratorState = iteratorState || { stop: false }; + for (var node, subRangeIterator; node = rangeIterator.next(); ) { + //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node)); + if (rangeIterator.isPartiallySelectedSubtree()) { + // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the + // node selected by the Range. + if (func(node) === false) { + iteratorState.stop = true; + return; + } else { + subRangeIterator = rangeIterator.getSubtreeIterator(); + iterateSubtree(subRangeIterator, func, iteratorState); + subRangeIterator.detach(true); + if (iteratorState.stop) { + return; + } + } + } else { + // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its + // descendant + it = dom.createIterator(node); + while ( (n = it.next()) ) { + if (func(n) === false) { + iteratorState.stop = true; + return; + } + } + } + } + } + + function deleteSubtree(iterator) { + var subIterator; + while (iterator.next()) { + if (iterator.isPartiallySelectedSubtree()) { + subIterator = iterator.getSubtreeIterator(); + deleteSubtree(subIterator); + subIterator.detach(true); + } else { + iterator.remove(); + } + } + } + + function extractSubtree(iterator) { + + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { + + + if (iterator.isPartiallySelectedSubtree()) { + node = node.cloneNode(false); + subIterator = iterator.getSubtreeIterator(); + node.appendChild(extractSubtree(subIterator)); + subIterator.detach(true); + } else { + iterator.remove(); + } + if (node.nodeType == 10) { // DocumentType + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + frag.appendChild(node); + } + return frag; + } + + function getNodesInRange(range, nodeTypes, filter) { + //log.info("getNodesInRange, " + nodeTypes.join(",")); + var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; + var filterExists = !!filter; + if (filterNodeTypes) { + regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); + } + + var nodes = []; + iterateSubtree(new RangeIterator(range, false), function(node) { + if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) { + nodes.push(node); + } + }); + return nodes; + } + + function inspect(range) { + var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); + return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) + + /** + * @constructor + */ + function RangeIterator(range, clonePartiallySelectedTextNodes) { + this.range = range; + this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; + + + + if (!range.collapsed) { + this.sc = range.startContainer; + this.so = range.startOffset; + this.ec = range.endContainer; + this.eo = range.endOffset; + var root = range.commonAncestorContainer; + + if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) { + this.isSingleCharacterDataNode = true; + this._first = this._last = this._next = this.sc; + } else { + this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ? + this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true); + this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ? + this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true); + } + + } + } + + RangeIterator.prototype = { + _current: null, + _next: null, + _first: null, + _last: null, + isSingleCharacterDataNode: false, + + reset: function() { + this._current = null; + this._next = this._first; + }, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + // Move to next node + var current = this._current = this._next; + if (current) { + this._next = (current !== this._last) ? current.nextSibling : null; + + // Check for partially selected text nodes + if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { + if (current === this.ec) { + + (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); + } + if (this._current === this.sc) { + + (current = current.cloneNode(true)).deleteData(0, this.so); + } + } + } + + return current; + }, + + remove: function() { + var current = this._current, start, end; + + if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { + start = (current === this.sc) ? this.so : 0; + end = (current === this.ec) ? this.eo : current.length; + if (start != end) { + current.deleteData(start, end - start); + } + } else { + if (current.parentNode) { + current.parentNode.removeChild(current); + } else { + + } + } + }, + + // Checks if the current node is partially selected + isPartiallySelectedSubtree: function() { + var current = this._current; + return isNonTextPartiallySelected(current, this.range); + }, + + getSubtreeIterator: function() { + var subRange; + if (this.isSingleCharacterDataNode) { + subRange = this.range.cloneRange(); + subRange.collapse(); + } else { + subRange = new Range(getRangeDocument(this.range)); + var current = this._current; + var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current); + + if (dom.isAncestorOf(current, this.sc, true)) { + startContainer = this.sc; + startOffset = this.so; + } + if (dom.isAncestorOf(current, this.ec, true)) { + endContainer = this.ec; + endOffset = this.eo; + } + + updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); + } + return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); + }, + + detach: function(detachRange) { + if (detachRange) { + this.range.detach(); + } + this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Exceptions + + /** + * @constructor + */ + function RangeException(codeName) { + this.code = this[codeName]; + this.codeName = codeName; + this.message = "RangeException: " + this.codeName; + } + + RangeException.prototype = { + BAD_BOUNDARYPOINTS_ERR: 1, + INVALID_NODE_TYPE_ERR: 2 + }; + + RangeException.prototype.toString = function() { + return this.message; + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + /** + * Currently iterates through all nodes in the range on creation until I think of a decent way to do it + * TODO: Look into making this a proper iterator, not requiring preloading everything first + * @constructor + */ + function RangeNodeIterator(range, nodeTypes, filter) { + this.nodes = getNodesInRange(range, nodeTypes, filter); + this._next = this.nodes[0]; + this._position = 0; + } + + RangeNodeIterator.prototype = { + _current: null, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + this._current = this._next; + this._next = this.nodes[ ++this._position ]; + return this._current; + }, + + detach: function() { + this._current = this._next = this.nodes = null; + } + }; + + var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; + var rootContainerNodeTypes = [2, 9, 11]; + var readonlyNodeTypes = [5, 6, 10, 12]; + var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; + var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; + + function createAncestorFinder(nodeTypes) { + return function(node, selfIsAncestor) { + var t, n = selfIsAncestor ? node : node.parentNode; + while (n) { + t = n.nodeType; + if (dom.arrayContains(nodeTypes, t)) { + return n; + } + n = n.parentNode; + } + return null; + }; + } + + var getRootContainer = dom.getRootContainer; + var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); + var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); + var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); + + function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { + if (getDocTypeNotationEntityAncestor(node, allowSelf)) { + throw new RangeException("INVALID_NODE_TYPE_ERR"); + } + } + + function assertNotDetached(range) { + if (!range.startContainer) { + throw new DOMException("INVALID_STATE_ERR"); + } + } + + function assertValidNodeType(node, invalidTypes) { + if (!dom.arrayContains(invalidTypes, node.nodeType)) { + throw new RangeException("INVALID_NODE_TYPE_ERR"); + } + } + + function assertValidOffset(node, offset) { + if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) { + throw new DOMException("INDEX_SIZE_ERR"); + } + } + + function assertSameDocumentOrFragment(node1, node2) { + if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + } + + function assertNodeNotReadOnly(node) { + if (getReadonlyAncestor(node, true)) { + throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); + } + } + + function assertNode(node, codeName) { + if (!node) { + throw new DOMException(codeName); + } + } + + function isOrphan(node) { + return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true); + } + + function isValidOffset(node, offset) { + return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length); + } + + function assertRangeValid(range) { + assertNotDetached(range); + if (isOrphan(range.startContainer) || isOrphan(range.endContainer) || + !isValidOffset(range.startContainer, range.startOffset) || + !isValidOffset(range.endContainer, range.endOffset)) { + throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")"); + } + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Test the browser's innerHTML support to decide how to implement createContextualFragment + var styleEl = document.createElement("style"); + var htmlParsingConforms = false; + try { + styleEl.innerHTML = "x"; + htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node + } catch (e) { + // IE 6 and 7 throw + } + + api.features.htmlParsingConforms = htmlParsingConforms; + + var createContextualFragment = htmlParsingConforms ? + + // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See + // discussion and base code for this implementation at issue 67. + // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface + // Thanks to Aleks Williams. + function(fragmentStr) { + // "Let node the context object's start's node." + var node = this.startContainer; + var doc = dom.getDocument(node); + + // "If the context object's start's node is null, raise an INVALID_STATE_ERR + // exception and abort these steps." + if (!node) { + throw new DOMException("INVALID_STATE_ERR"); + } + + // "Let element be as follows, depending on node's interface:" + // Document, Document Fragment: null + var el = null; + + // "Element: node" + if (node.nodeType == 1) { + el = node; + + // "Text, Comment: node's parentElement" + } else if (dom.isCharacterDataNode(node)) { + el = dom.parentElement(node); + } + + // "If either element is null or element's ownerDocument is an HTML document + // and element's local name is "html" and element's namespace is the HTML + // namespace" + if (el === null || ( + el.nodeName == "HTML" + && dom.isHtmlNamespace(dom.getDocument(el).documentElement) + && dom.isHtmlNamespace(el) + )) { + + // "let element be a new Element with "body" as its local name and the HTML + // namespace as its namespace."" + el = doc.createElement("body"); + } else { + el = el.cloneNode(false); + } + + // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." + // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." + // "In either case, the algorithm must be invoked with fragment as the input + // and element as the context element." + el.innerHTML = fragmentStr; + + // "If this raises an exception, then abort these steps. Otherwise, let new + // children be the nodes returned." + + // "Let fragment be a new DocumentFragment." + // "Append all new children to fragment." + // "Return fragment." + return dom.fragmentFromNodeChildren(el); + } : + + // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that + // previous versions of Rangy used (with the exception of using a body element rather than a div) + function(fragmentStr) { + assertNotDetached(this); + var doc = getRangeDocument(this); + var el = doc.createElement("body"); + el.innerHTML = fragmentStr; + + return dom.fragmentFromNodeChildren(el); + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", + "commonAncestorContainer"]; + + var s2s = 0, s2e = 1, e2e = 2, e2s = 3; + var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; + + function RangePrototype() {} + + RangePrototype.prototype = { + attachListener: function(type, listener) { + this._listeners[type].push(listener); + }, + + compareBoundaryPoints: function(how, range) { + assertRangeValid(this); + assertSameDocumentOrFragment(this.startContainer, range.startContainer); + + var nodeA, offsetA, nodeB, offsetB; + var prefixA = (how == e2s || how == s2s) ? "start" : "end"; + var prefixB = (how == s2e || how == s2s) ? "start" : "end"; + nodeA = this[prefixA + "Container"]; + offsetA = this[prefixA + "Offset"]; + nodeB = range[prefixB + "Container"]; + offsetB = range[prefixB + "Offset"]; + return dom.comparePoints(nodeA, offsetA, nodeB, offsetB); + }, + + insertNode: function(node) { + assertRangeValid(this); + assertValidNodeType(node, insertableNodeTypes); + assertNodeNotReadOnly(this.startContainer); + + if (dom.isAncestorOf(node, this.startContainer, true)) { + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + + // No check for whether the container of the start of the Range is of a type that does not allow + // children of the type of node: the browser's DOM implementation should do this for us when we attempt + // to add the node + + var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); + this.setStartBefore(firstNodeInserted); + }, + + cloneContents: function() { + assertRangeValid(this); + + var clone, frag; + if (this.collapsed) { + return getRangeDocument(this).createDocumentFragment(); + } else { + if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) { + clone = this.startContainer.cloneNode(true); + clone.data = clone.data.slice(this.startOffset, this.endOffset); + frag = getRangeDocument(this).createDocumentFragment(); + frag.appendChild(clone); + return frag; + } else { + var iterator = new RangeIterator(this, true); + clone = cloneSubtree(iterator); + iterator.detach(); + } + return clone; + } + }, + + canSurroundContents: function() { + assertRangeValid(this); + assertNodeNotReadOnly(this.startContainer); + assertNodeNotReadOnly(this.endContainer); + + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects + // no non-text nodes. + var iterator = new RangeIterator(this, true); + var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); + iterator.detach(); + return !boundariesInvalid; + }, + + surroundContents: function(node) { + assertValidNodeType(node, surroundNodeTypes); + + if (!this.canSurroundContents()) { + throw new RangeException("BAD_BOUNDARYPOINTS_ERR"); + } + + // Extract the contents + var content = this.extractContents(); + + // Clear the children of the node + if (node.hasChildNodes()) { + while (node.lastChild) { + node.removeChild(node.lastChild); + } + } + + // Insert the new node and add the extracted contents + insertNodeAtPosition(node, this.startContainer, this.startOffset); + node.appendChild(content); + + this.selectNode(node); + }, + + cloneRange: function() { + assertRangeValid(this); + var range = new Range(getRangeDocument(this)); + var i = rangeProperties.length, prop; + while (i--) { + prop = rangeProperties[i]; + range[prop] = this[prop]; + } + return range; + }, + + toString: function() { + assertRangeValid(this); + var sc = this.startContainer; + if (sc === this.endContainer && dom.isCharacterDataNode(sc)) { + return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; + } else { + var textBits = [], iterator = new RangeIterator(this, true); + + iterateSubtree(iterator, function(node) { + // Accept only text or CDATA nodes, not comments + + if (node.nodeType == 3 || node.nodeType == 4) { + textBits.push(node.data); + } + }); + iterator.detach(); + return textBits.join(""); + } + }, + + // The methods below are all non-standard. The following batch were introduced by Mozilla but have since + // been removed from Mozilla. + + compareNode: function(node) { + assertRangeValid(this); + + var parent = node.parentNode; + var nodeIndex = dom.getNodeIndex(node); + + if (!parent) { + throw new DOMException("NOT_FOUND_ERR"); + } + + var startComparison = this.comparePoint(parent, nodeIndex), + endComparison = this.comparePoint(parent, nodeIndex + 1); + + if (startComparison < 0) { // Node starts before + return (endComparison > 0) ? n_b_a : n_b; + } else { + return (endComparison > 0) ? n_a : n_i; + } + }, + + comparePoint: function(node, offset) { + assertRangeValid(this); + assertNode(node, "HIERARCHY_REQUEST_ERR"); + assertSameDocumentOrFragment(node, this.startContainer); + + if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { + return -1; + } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { + return 1; + } + return 0; + }, + + createContextualFragment: createContextualFragment, + + toHtml: function() { + assertRangeValid(this); + var container = getRangeDocument(this).createElement("div"); + container.appendChild(this.cloneContents()); + return container.innerHTML; + }, + + // touchingIsIntersecting determines whether this method considers a node that borders a range intersects + // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) + intersectsNode: function(node, touchingIsIntersecting) { + assertRangeValid(this); + assertNode(node, "NOT_FOUND_ERR"); + if (dom.getDocument(node) !== getRangeDocument(this)) { + return false; + } + + var parent = node.parentNode, offset = dom.getNodeIndex(node); + assertNode(parent, "NOT_FOUND_ERR"); + + var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset), + endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset); + + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; + }, + + + isPointInRange: function(node, offset) { + assertRangeValid(this); + assertNode(node, "HIERARCHY_REQUEST_ERR"); + assertSameDocumentOrFragment(node, this.startContainer); + + return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && + (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); + }, + + // The methods below are non-standard and invented by me. + + // Sharing a boundary start-to-end or end-to-start does not count as intersection. + intersectsRange: function(range, touchingIsIntersecting) { + assertRangeValid(this); + + if (getRangeDocument(range) != getRangeDocument(this)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + + var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset), + endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset); + + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; + }, + + intersection: function(range) { + if (this.intersectsRange(range)) { + var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), + endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); + + var intersectionRange = this.cloneRange(); + + if (startComparison == -1) { + intersectionRange.setStart(range.startContainer, range.startOffset); + } + if (endComparison == 1) { + intersectionRange.setEnd(range.endContainer, range.endOffset); + } + return intersectionRange; + } + return null; + }, + + union: function(range) { + if (this.intersectsRange(range, true)) { + var unionRange = this.cloneRange(); + if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { + unionRange.setStart(range.startContainer, range.startOffset); + } + if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { + unionRange.setEnd(range.endContainer, range.endOffset); + } + return unionRange; + } else { + throw new RangeException("Ranges do not intersect"); + } + }, + + containsNode: function(node, allowPartial) { + if (allowPartial) { + return this.intersectsNode(node, false); + } else { + return this.compareNode(node) == n_i; + } + }, + + containsNodeContents: function(node) { + return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0; + }, + + containsRange: function(range) { + return this.intersection(range).equals(range); + }, + + containsNodeText: function(node) { + var nodeRange = this.cloneRange(); + nodeRange.selectNode(node); + var textNodes = nodeRange.getNodes([3]); + if (textNodes.length > 0) { + nodeRange.setStart(textNodes[0], 0); + var lastTextNode = textNodes.pop(); + nodeRange.setEnd(lastTextNode, lastTextNode.length); + var contains = this.containsRange(nodeRange); + nodeRange.detach(); + return contains; + } else { + return this.containsNodeContents(node); + } + }, + + createNodeIterator: function(nodeTypes, filter) { + assertRangeValid(this); + return new RangeNodeIterator(this, nodeTypes, filter); + }, + + getNodes: function(nodeTypes, filter) { + assertRangeValid(this); + return getNodesInRange(this, nodeTypes, filter); + }, + + getDocument: function() { + return getRangeDocument(this); + }, + + collapseBefore: function(node) { + assertNotDetached(this); + + this.setEndBefore(node); + this.collapse(false); + }, + + collapseAfter: function(node) { + assertNotDetached(this); + + this.setStartAfter(node); + this.collapse(true); + }, + + getName: function() { + return "DomRange"; + }, + + equals: function(range) { + return Range.rangesEqual(this, range); + }, + + inspect: function() { + return inspect(this); + } + }; + + function copyComparisonConstantsToObject(obj) { + obj.START_TO_START = s2s; + obj.START_TO_END = s2e; + obj.END_TO_END = e2e; + obj.END_TO_START = e2s; + + obj.NODE_BEFORE = n_b; + obj.NODE_AFTER = n_a; + obj.NODE_BEFORE_AND_AFTER = n_b_a; + obj.NODE_INSIDE = n_i; + } + + function copyComparisonConstants(constructor) { + copyComparisonConstantsToObject(constructor); + copyComparisonConstantsToObject(constructor.prototype); + } + + function createRangeContentRemover(remover, boundaryUpdater) { + return function() { + assertRangeValid(this); + + var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; + + var iterator = new RangeIterator(this, true); + + // Work out where to position the range after content removal + var node, boundary; + if (sc !== root) { + node = dom.getClosestAncestorIn(sc, root, true); + boundary = getBoundaryAfterNode(node); + sc = boundary.node; + so = boundary.offset; + } + + // Check none of the range is read-only + iterateSubtree(iterator, assertNodeNotReadOnly); + + iterator.reset(); + + // Remove the content + var returnValue = remover(iterator); + iterator.detach(); + + // Move to the new position + boundaryUpdater(this, sc, so, sc, so); + + return returnValue; + }; + } + + function createPrototypeRange(constructor, boundaryUpdater, detacher) { + function createBeforeAfterNodeSetter(isBefore, isStart) { + return function(node) { + assertNotDetached(this); + assertValidNodeType(node, beforeAfterNodeTypes); + assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); + + var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); + (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); + }; + } + + function setRangeStart(range, node, offset) { + var ec = range.endContainer, eo = range.endOffset; + if (node !== range.startContainer || offset !== range.startOffset) { + // Check the root containers of the range and the new boundary, and also check whether the new boundary + // is after the current end. In either case, collapse the range to the new position + if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) { + ec = node; + eo = offset; + } + boundaryUpdater(range, node, offset, ec, eo); + } + } + + function setRangeEnd(range, node, offset) { + var sc = range.startContainer, so = range.startOffset; + if (node !== range.endContainer || offset !== range.endOffset) { + // Check the root containers of the range and the new boundary, and also check whether the new boundary + // is after the current end. In either case, collapse the range to the new position + if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) { + sc = node; + so = offset; + } + boundaryUpdater(range, sc, so, node, offset); + } + } + + function setRangeStartAndEnd(range, node, offset) { + if (node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) { + boundaryUpdater(range, node, offset, node, offset); + } + } + + constructor.prototype = new RangePrototype(); + + api.util.extend(constructor.prototype, { + setStart: function(node, offset) { + assertNotDetached(this); + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeStart(this, node, offset); + }, + + setEnd: function(node, offset) { + assertNotDetached(this); + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeEnd(this, node, offset); + }, + + setStartBefore: createBeforeAfterNodeSetter(true, true), + setStartAfter: createBeforeAfterNodeSetter(false, true), + setEndBefore: createBeforeAfterNodeSetter(true, false), + setEndAfter: createBeforeAfterNodeSetter(false, false), + + collapse: function(isStart) { + assertRangeValid(this); + if (isStart) { + boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); + } else { + boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); + } + }, + + selectNodeContents: function(node) { + // This doesn't seem well specified: the spec talks only about selecting the node's contents, which + // could be taken to mean only its children. However, browsers implement this the same as selectNode for + // text nodes, so I shall do likewise + assertNotDetached(this); + assertNoDocTypeNotationEntityAncestor(node, true); + + boundaryUpdater(this, node, 0, node, dom.getNodeLength(node)); + }, + + selectNode: function(node) { + assertNotDetached(this); + assertNoDocTypeNotationEntityAncestor(node, false); + assertValidNodeType(node, beforeAfterNodeTypes); + + var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); + boundaryUpdater(this, start.node, start.offset, end.node, end.offset); + }, + + extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), + + deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), + + canSurroundContents: function() { + assertRangeValid(this); + assertNodeNotReadOnly(this.startContainer); + assertNodeNotReadOnly(this.endContainer); + + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects + // no non-text nodes. + var iterator = new RangeIterator(this, true); + var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); + iterator.detach(); + return !boundariesInvalid; + }, + + detach: function() { + detacher(this); + }, + + splitBoundaries: function() { + assertRangeValid(this); + + + var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; + var startEndSame = (sc === ec); + + if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { + dom.splitDataNode(ec, eo); + + } + + if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) { + + sc = dom.splitDataNode(sc, so); + if (startEndSame) { + eo -= so; + ec = sc; + } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) { + eo++; + } + so = 0; + + } + boundaryUpdater(this, sc, so, ec, eo); + }, + + normalizeBoundaries: function() { + assertRangeValid(this); + + var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; + + var mergeForward = function(node) { + var sibling = node.nextSibling; + if (sibling && sibling.nodeType == node.nodeType) { + ec = node; + eo = node.length; + node.appendData(sibling.data); + sibling.parentNode.removeChild(sibling); + } + }; + + var mergeBackward = function(node) { + var sibling = node.previousSibling; + if (sibling && sibling.nodeType == node.nodeType) { + sc = node; + var nodeLength = node.length; + so = sibling.length; + node.insertData(0, sibling.data); + sibling.parentNode.removeChild(sibling); + if (sc == ec) { + eo += so; + ec = sc; + } else if (ec == node.parentNode) { + var nodeIndex = dom.getNodeIndex(node); + if (eo == nodeIndex) { + ec = node; + eo = nodeLength; + } else if (eo > nodeIndex) { + eo--; + } + } + } + }; + + var normalizeStart = true; + + if (dom.isCharacterDataNode(ec)) { + if (ec.length == eo) { + mergeForward(ec); + } + } else { + if (eo > 0) { + var endNode = ec.childNodes[eo - 1]; + if (endNode && dom.isCharacterDataNode(endNode)) { + mergeForward(endNode); + } + } + normalizeStart = !this.collapsed; + } + + if (normalizeStart) { + if (dom.isCharacterDataNode(sc)) { + if (so == 0) { + mergeBackward(sc); + } + } else { + if (so < sc.childNodes.length) { + var startNode = sc.childNodes[so]; + if (startNode && dom.isCharacterDataNode(startNode)) { + mergeBackward(startNode); + } + } + } + } else { + sc = ec; + so = eo; + } + + boundaryUpdater(this, sc, so, ec, eo); + }, + + collapseToPoint: function(node, offset) { + assertNotDetached(this); + + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeStartAndEnd(this, node, offset); + } + }); + + copyComparisonConstants(constructor); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Updates commonAncestorContainer and collapsed after boundary change + function updateCollapsedAndCommonAncestor(range) { + range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); + range.commonAncestorContainer = range.collapsed ? + range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); + } + + function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { + var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset); + var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset); + + range.startContainer = startContainer; + range.startOffset = startOffset; + range.endContainer = endContainer; + range.endOffset = endOffset; + + updateCollapsedAndCommonAncestor(range); + dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved}); + } + + function detach(range) { + assertNotDetached(range); + range.startContainer = range.startOffset = range.endContainer = range.endOffset = null; + range.collapsed = range.commonAncestorContainer = null; + dispatchEvent(range, "detach", null); + range._listeners = null; + } + + /** + * @constructor + */ + function Range(doc) { + this.startContainer = doc; + this.startOffset = 0; + this.endContainer = doc; + this.endOffset = 0; + this._listeners = { + boundarychange: [], + detach: [] + }; + updateCollapsedAndCommonAncestor(this); + } + + createPrototypeRange(Range, updateBoundaries, detach); + + api.rangePrototype = RangePrototype.prototype; + + Range.rangeProperties = rangeProperties; + Range.RangeIterator = RangeIterator; + Range.copyComparisonConstants = copyComparisonConstants; + Range.createPrototypeRange = createPrototypeRange; + Range.inspect = inspect; + Range.getRangeDocument = getRangeDocument; + Range.rangesEqual = function(r1, r2) { + return r1.startContainer === r2.startContainer && + r1.startOffset === r2.startOffset && + r1.endContainer === r2.endContainer && + r1.endOffset === r2.endOffset; + }; + + api.DomRange = Range; + api.RangeException = RangeException; +});rangy.createModule("WrappedRange", function(api, module) { + api.requireModules( ["DomUtil", "DomRange"] ); + + /** + * @constructor + */ + var WrappedRange; + var dom = api.dom; + var DomPosition = dom.DomPosition; + var DomRange = api.DomRange; + + + + /*----------------------------------------------------------------------------------------------------------------*/ + + /* + This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() + method. For example, in the following (where pipes denote the selection boundaries): + +
        • | a
        • b |
        + + var range = document.selection.createRange(); + alert(range.parentElement().id); // Should alert "ul" but alerts "b" + + This method returns the common ancestor node of the following: + - the parentElement() of the textRange + - the parentElement() of the textRange after calling collapse(true) + - the parentElement() of the textRange after calling collapse(false) + */ + function getTextRangeContainerElement(textRange) { + var parentEl = textRange.parentElement(); + + var range = textRange.duplicate(); + range.collapse(true); + var startEl = range.parentElement(); + range = textRange.duplicate(); + range.collapse(false); + var endEl = range.parentElement(); + var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); + + return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); + } + + function textRangeIsCollapsed(textRange) { + return textRange.compareEndPoints("StartToEnd", textRange) == 0; + } + + // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as + // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has + // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling + // for inputs and images, plus optimizations. + function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) { + var workingRange = textRange.duplicate(); + + workingRange.collapse(isStart); + var containerElement = workingRange.parentElement(); + + // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so + // check for that + // TODO: Find out when. Workaround for wholeRangeContainerElement may break this + if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) { + containerElement = wholeRangeContainerElement; + + } + + + + // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and + // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx + if (!containerElement.canHaveHTML) { + return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); + } + + var workingNode = dom.getDocument(containerElement).createElement("span"); + var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; + var previousNode, nextNode, boundaryPosition, boundaryNode; + + // Move the working range through the container's children, starting at the end and working backwards, until the + // working range reaches or goes past the boundary we're interested in + do { + containerElement.insertBefore(workingNode, workingNode.previousSibling); + workingRange.moveToElementText(workingNode); + } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 && + workingNode.previousSibling); + + // We've now reached or gone past the boundary of the text range we're interested in + // so have identified the node we want + boundaryNode = workingNode.nextSibling; + + if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) { + // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the + // node containing the text range's boundary, so we move the end of the working range to the boundary point + // and measure the length of its text to get the boundary's offset within the node. + workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); + + + var offset; + + if (/[\r\n]/.test(boundaryNode.data)) { + /* + For the particular case of a boundary within a text node containing line breaks (within a
         element,
        +                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:
        +
        +                - Each line break is represented as \r in the text node's data/nodeValue properties
        +                - Each line break is represented as \r\n in the TextRange's 'text' property
        +                - The 'text' property of the TextRange does not contain trailing line breaks
        +
        +                To get round the problem presented by the final fact above, we can use the fact that TextRange's
        +                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
        +                the same as the number of characters it was instructed to move. The simplest approach is to use this to
        +                store the characters moved when moving both the start and end of the range to the start of the document
        +                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
        +                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
        +                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
        +                problem.
        +
        +                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
        +                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
        +                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
        +                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
        +                the range within the document).
        +
        +                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
        +                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
        +                text of the TextRange, so the start of the range is moved that length initially and then a character at
        +                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
        +                performance in most situations compared to the previous two methods.
        +                */
        +                var tempRange = workingRange.duplicate();
        +                var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
        +
        +                offset = tempRange.moveStart("character", rangeLength);
        +                while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
        +                    offset++;
        +                    tempRange.moveStart("character", 1);
        +                }
        +            } else {
        +                offset = workingRange.text.length;
        +            }
        +            boundaryPosition = new DomPosition(boundaryNode, offset);
        +        } else {
        +
        +
        +            // If the boundary immediately follows a character data node and this is the end boundary, we should favour
        +            // a position within that, and likewise for a start boundary preceding a character data node
        +            previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
        +            nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
        +
        +
        +
        +            if (nextNode && dom.isCharacterDataNode(nextNode)) {
        +                boundaryPosition = new DomPosition(nextNode, 0);
        +            } else if (previousNode && dom.isCharacterDataNode(previousNode)) {
        +                boundaryPosition = new DomPosition(previousNode, previousNode.length);
        +            } else {
        +                boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
        +            }
        +        }
        +
        +        // Clean up
        +        workingNode.parentNode.removeChild(workingNode);
        +
        +        return boundaryPosition;
        +    }
        +
        +    // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
        +    // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
        +    // (http://code.google.com/p/ierange/)
        +    function createBoundaryTextRange(boundaryPosition, isStart) {
        +        var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
        +        var doc = dom.getDocument(boundaryPosition.node);
        +        var workingNode, childNodes, workingRange = doc.body.createTextRange();
        +        var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);
        +
        +        if (nodeIsDataNode) {
        +            boundaryNode = boundaryPosition.node;
        +            boundaryParent = boundaryNode.parentNode;
        +        } else {
        +            childNodes = boundaryPosition.node.childNodes;
        +            boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
        +            boundaryParent = boundaryPosition.node;
        +        }
        +
        +        // Position the range immediately before the node containing the boundary
        +        workingNode = doc.createElement("span");
        +
        +        // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
        +        // element rather than immediately before or after it, which is what we want
        +        workingNode.innerHTML = "&#feff;";
        +
        +        // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
        +        // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
        +        if (boundaryNode) {
        +            boundaryParent.insertBefore(workingNode, boundaryNode);
        +        } else {
        +            boundaryParent.appendChild(workingNode);
        +        }
        +
        +        workingRange.moveToElementText(workingNode);
        +        workingRange.collapse(!isStart);
        +
        +        // Clean up
        +        boundaryParent.removeChild(workingNode);
        +
        +        // Move the working range to the text offset, if required
        +        if (nodeIsDataNode) {
        +            workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
        +        }
        +
        +        return workingRange;
        +    }
        +
        +    /*----------------------------------------------------------------------------------------------------------------*/
        +
        +    if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
        +        // This is a wrapper around the browser's native DOM Range. It has two aims:
        +        // - Provide workarounds for specific browser bugs
        +        // - provide convenient extensions, which are inherited from Rangy's DomRange
        +
        +        (function() {
        +            var rangeProto;
        +            var rangeProperties = DomRange.rangeProperties;
        +            var canSetRangeStartAfterEnd;
        +
        +            function updateRangeProperties(range) {
        +                var i = rangeProperties.length, prop;
        +                while (i--) {
        +                    prop = rangeProperties[i];
        +                    range[prop] = range.nativeRange[prop];
        +                }
        +            }
        +
        +            function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {
        +                var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
        +                var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
        +
        +                // Always set both boundaries for the benefit of IE9 (see issue 35)
        +                if (startMoved || endMoved) {
        +                    range.setEnd(endContainer, endOffset);
        +                    range.setStart(startContainer, startOffset);
        +                }
        +            }
        +
        +            function detach(range) {
        +                range.nativeRange.detach();
        +                range.detached = true;
        +                var i = rangeProperties.length, prop;
        +                while (i--) {
        +                    prop = rangeProperties[i];
        +                    range[prop] = null;
        +                }
        +            }
        +
        +            var createBeforeAfterNodeSetter;
        +
        +            WrappedRange = function(range) {
        +                if (!range) {
        +                    throw new Error("Range must be specified");
        +                }
        +                this.nativeRange = range;
        +                updateRangeProperties(this);
        +            };
        +
        +            DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);
        +
        +            rangeProto = WrappedRange.prototype;
        +
        +            rangeProto.selectNode = function(node) {
        +                this.nativeRange.selectNode(node);
        +                updateRangeProperties(this);
        +            };
        +
        +            rangeProto.deleteContents = function() {
        +                this.nativeRange.deleteContents();
        +                updateRangeProperties(this);
        +            };
        +
        +            rangeProto.extractContents = function() {
        +                var frag = this.nativeRange.extractContents();
        +                updateRangeProperties(this);
        +                return frag;
        +            };
        +
        +            rangeProto.cloneContents = function() {
        +                return this.nativeRange.cloneContents();
        +            };
        +
        +            // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
        +            // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
        +            // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
        +            // insertNode, which works but is almost certainly slower than the native implementation.
        +/*
        +            rangeProto.insertNode = function(node) {
        +                this.nativeRange.insertNode(node);
        +                updateRangeProperties(this);
        +            };
        +*/
        +
        +            rangeProto.surroundContents = function(node) {
        +                this.nativeRange.surroundContents(node);
        +                updateRangeProperties(this);
        +            };
        +
        +            rangeProto.collapse = function(isStart) {
        +                this.nativeRange.collapse(isStart);
        +                updateRangeProperties(this);
        +            };
        +
        +            rangeProto.cloneRange = function() {
        +                return new WrappedRange(this.nativeRange.cloneRange());
        +            };
        +
        +            rangeProto.refresh = function() {
        +                updateRangeProperties(this);
        +            };
        +
        +            rangeProto.toString = function() {
        +                return this.nativeRange.toString();
        +            };
        +
        +            // Create test range and node for feature detection
        +
        +            var testTextNode = document.createTextNode("test");
        +            dom.getBody(document).appendChild(testTextNode);
        +            var range = document.createRange();
        +
        +            /*--------------------------------------------------------------------------------------------------------*/
        +
        +            // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
        +            // correct for it
        +
        +            range.setStart(testTextNode, 0);
        +            range.setEnd(testTextNode, 0);
        +
        +            try {
        +                range.setStart(testTextNode, 1);
        +                canSetRangeStartAfterEnd = true;
        +
        +                rangeProto.setStart = function(node, offset) {
        +                    this.nativeRange.setStart(node, offset);
        +                    updateRangeProperties(this);
        +                };
        +
        +                rangeProto.setEnd = function(node, offset) {
        +                    this.nativeRange.setEnd(node, offset);
        +                    updateRangeProperties(this);
        +                };
        +
        +                createBeforeAfterNodeSetter = function(name) {
        +                    return function(node) {
        +                        this.nativeRange[name](node);
        +                        updateRangeProperties(this);
        +                    };
        +                };
        +
        +            } catch(ex) {
        +
        +
        +                canSetRangeStartAfterEnd = false;
        +
        +                rangeProto.setStart = function(node, offset) {
        +                    try {
        +                        this.nativeRange.setStart(node, offset);
        +                    } catch (ex) {
        +                        this.nativeRange.setEnd(node, offset);
        +                        this.nativeRange.setStart(node, offset);
        +                    }
        +                    updateRangeProperties(this);
        +                };
        +
        +                rangeProto.setEnd = function(node, offset) {
        +                    try {
        +                        this.nativeRange.setEnd(node, offset);
        +                    } catch (ex) {
        +                        this.nativeRange.setStart(node, offset);
        +                        this.nativeRange.setEnd(node, offset);
        +                    }
        +                    updateRangeProperties(this);
        +                };
        +
        +                createBeforeAfterNodeSetter = function(name, oppositeName) {
        +                    return function(node) {
        +                        try {
        +                            this.nativeRange[name](node);
        +                        } catch (ex) {
        +                            this.nativeRange[oppositeName](node);
        +                            this.nativeRange[name](node);
        +                        }
        +                        updateRangeProperties(this);
        +                    };
        +                };
        +            }
        +
        +            rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
        +            rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
        +            rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
        +            rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
        +
        +            /*--------------------------------------------------------------------------------------------------------*/
        +
        +            // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
        +            // the 0th character of the text node
        +            range.selectNodeContents(testTextNode);
        +            if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
        +                    range.startOffset == 0 && range.endOffset == testTextNode.length) {
        +                rangeProto.selectNodeContents = function(node) {
        +                    this.nativeRange.selectNodeContents(node);
        +                    updateRangeProperties(this);
        +                };
        +            } else {
        +                rangeProto.selectNodeContents = function(node) {
        +                    this.setStart(node, 0);
        +                    this.setEnd(node, DomRange.getEndOffset(node));
        +                };
        +            }
        +
        +            /*--------------------------------------------------------------------------------------------------------*/
        +
        +            // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
        +            // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
        +
        +            range.selectNodeContents(testTextNode);
        +            range.setEnd(testTextNode, 3);
        +
        +            var range2 = document.createRange();
        +            range2.selectNodeContents(testTextNode);
        +            range2.setEnd(testTextNode, 4);
        +            range2.setStart(testTextNode, 2);
        +
        +            if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &
        +                    range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
        +                // This is the wrong way round, so correct for it
        +
        +
        +                rangeProto.compareBoundaryPoints = function(type, range) {
        +                    range = range.nativeRange || range;
        +                    if (type == range.START_TO_END) {
        +                        type = range.END_TO_START;
        +                    } else if (type == range.END_TO_START) {
        +                        type = range.START_TO_END;
        +                    }
        +                    return this.nativeRange.compareBoundaryPoints(type, range);
        +                };
        +            } else {
        +                rangeProto.compareBoundaryPoints = function(type, range) {
        +                    return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
        +                };
        +            }
        +
        +            /*--------------------------------------------------------------------------------------------------------*/
        +
        +            // Test for existence of createContextualFragment and delegate to it if it exists
        +            if (api.util.isHostMethod(range, "createContextualFragment")) {
        +                rangeProto.createContextualFragment = function(fragmentStr) {
        +                    return this.nativeRange.createContextualFragment(fragmentStr);
        +                };
        +            }
        +
        +            /*--------------------------------------------------------------------------------------------------------*/
        +
        +            // Clean up
        +            dom.getBody(document).removeChild(testTextNode);
        +            range.detach();
        +            range2.detach();
        +        })();
        +
        +        api.createNativeRange = function(doc) {
        +            doc = doc || document;
        +            return doc.createRange();
        +        };
        +    } else if (api.features.implementsTextRange) {
        +        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
        +        // prototype
        +
        +        WrappedRange = function(textRange) {
        +            this.textRange = textRange;
        +            this.refresh();
        +        };
        +
        +        WrappedRange.prototype = new DomRange(document);
        +
        +        WrappedRange.prototype.refresh = function() {
        +            var start, end;
        +
        +            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
        +            var rangeContainerElement = getTextRangeContainerElement(this.textRange);
        +
        +            if (textRangeIsCollapsed(this.textRange)) {
        +                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
        +            } else {
        +
        +                start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
        +                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
        +            }
        +
        +            this.setStart(start.node, start.offset);
        +            this.setEnd(end.node, end.offset);
        +        };
        +
        +        DomRange.copyComparisonConstants(WrappedRange);
        +
        +        // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
        +        var globalObj = (function() { return this; })();
        +        if (typeof globalObj.Range == "undefined") {
        +            globalObj.Range = WrappedRange;
        +        }
        +
        +        api.createNativeRange = function(doc) {
        +            doc = doc || document;
        +            return doc.body.createTextRange();
        +        };
        +    }
        +
        +    if (api.features.implementsTextRange) {
        +        WrappedRange.rangeToTextRange = function(range) {
        +            if (range.collapsed) {
        +                var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
        +
        +
        +
        +                return tr;
        +
        +                //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
        +            } else {
        +                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
        +                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
        +                var textRange = dom.getDocument(range.startContainer).body.createTextRange();
        +                textRange.setEndPoint("StartToStart", startRange);
        +                textRange.setEndPoint("EndToEnd", endRange);
        +                return textRange;
        +            }
        +        };
        +    }
        +
        +    WrappedRange.prototype.getName = function() {
        +        return "WrappedRange";
        +    };
        +
        +    api.WrappedRange = WrappedRange;
        +
        +    api.createRange = function(doc) {
        +        doc = doc || document;
        +        return new WrappedRange(api.createNativeRange(doc));
        +    };
        +
        +    api.createRangyRange = function(doc) {
        +        doc = doc || document;
        +        return new DomRange(doc);
        +    };
        +
        +    api.createIframeRange = function(iframeEl) {
        +        return api.createRange(dom.getIframeDocument(iframeEl));
        +    };
        +
        +    api.createIframeRangyRange = function(iframeEl) {
        +        return api.createRangyRange(dom.getIframeDocument(iframeEl));
        +    };
        +
        +    api.addCreateMissingNativeApiListener(function(win) {
        +        var doc = win.document;
        +        if (typeof doc.createRange == "undefined") {
        +            doc.createRange = function() {
        +                return api.createRange(this);
        +            };
        +        }
        +        doc = win = null;
        +    });
        +});rangy.createModule("WrappedSelection", function(api, module) {
        +    // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
        +    // spec (http://html5.org/specs/dom-range.html)
        +
        +    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
        +
        +    api.config.checkSelectionRanges = true;
        +
        +    var BOOLEAN = "boolean",
        +        windowPropertyName = "_rangySelection",
        +        dom = api.dom,
        +        util = api.util,
        +        DomRange = api.DomRange,
        +        WrappedRange = api.WrappedRange,
        +        DOMException = api.DOMException,
        +        DomPosition = dom.DomPosition,
        +        getSelection,
        +        selectionIsCollapsed,
        +        CONTROL = "Control";
        +
        +
        +
        +    function getWinSelection(winParam) {
        +        return (winParam || window).getSelection();
        +    }
        +
        +    function getDocSelection(winParam) {
        +        return (winParam || window).document.selection;
        +    }
        +
        +    // Test for the Range/TextRange and Selection features required
        +    // Test for ability to retrieve selection
        +    var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
        +        implementsDocSelection = api.util.isHostObject(document, "selection");
        +
        +    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
        +
        +    if (useDocumentSelection) {
        +        getSelection = getDocSelection;
        +        api.isSelectionValid = function(winParam) {
        +            var doc = (winParam || window).document, nativeSel = doc.selection;
        +
        +            // Check whether the selection TextRange is actually contained within the correct document
        +            return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
        +        };
        +    } else if (implementsWinGetSelection) {
        +        getSelection = getWinSelection;
        +        api.isSelectionValid = function() {
        +            return true;
        +        };
        +    } else {
        +        module.fail("Neither document.selection or window.getSelection() detected.");
        +    }
        +
        +    api.getNativeSelection = getSelection;
        +
        +    var testSelection = getSelection();
        +    var testRange = api.createNativeRange(document);
        +    var body = dom.getBody(document);
        +
        +    // Obtaining a range from a selection
        +    var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&
        +                                     util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
        +    api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
        +
        +    // Test for existence of native selection extend() method
        +    var selectionHasExtend = util.isHostMethod(testSelection, "extend");
        +    api.features.selectionHasExtend = selectionHasExtend;
        +
        +    // Test if rangeCount exists
        +    var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
        +    api.features.selectionHasRangeCount = selectionHasRangeCount;
        +
        +    var selectionSupportsMultipleRanges = false;
        +    var collapsedNonEditableSelectionsSupported = true;
        +
        +    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
        +            typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {
        +
        +        (function() {
        +            var iframe = document.createElement("iframe");
        +            body.appendChild(iframe);
        +
        +            var iframeDoc = dom.getIframeDocument(iframe);
        +            iframeDoc.open();
        +            iframeDoc.write("12");
        +            iframeDoc.close();
        +
        +            var sel = dom.getIframeWindow(iframe).getSelection();
        +            var docEl = iframeDoc.documentElement;
        +            var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;
        +
        +            // Test whether the native selection will allow a collapsed selection within a non-editable element
        +            var r1 = iframeDoc.createRange();
        +            r1.setStart(textNode, 1);
        +            r1.collapse(true);
        +            sel.addRange(r1);
        +            collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
        +            sel.removeAllRanges();
        +
        +            // Test whether the native selection is capable of supporting multiple ranges
        +            var r2 = r1.cloneRange();
        +            r1.setStart(textNode, 0);
        +            r2.setEnd(textNode, 2);
        +            sel.addRange(r1);
        +            sel.addRange(r2);
        +
        +            selectionSupportsMultipleRanges = (sel.rangeCount == 2);
        +
        +            // Clean up
        +            r1.detach();
        +            r2.detach();
        +
        +            body.removeChild(iframe);
        +        })();
        +    }
        +
        +    api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
        +    api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
        +
        +    // ControlRanges
        +    var implementsControlRange = false, testControlRange;
        +
        +    if (body && util.isHostMethod(body, "createControlRange")) {
        +        testControlRange = body.createControlRange();
        +        if (util.areHostProperties(testControlRange, ["item", "add"])) {
        +            implementsControlRange = true;
        +        }
        +    }
        +    api.features.implementsControlRange = implementsControlRange;
        +
        +    // Selection collapsedness
        +    if (selectionHasAnchorAndFocus) {
        +        selectionIsCollapsed = function(sel) {
        +            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
        +        };
        +    } else {
        +        selectionIsCollapsed = function(sel) {
        +            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
        +        };
        +    }
        +
        +    function updateAnchorAndFocusFromRange(sel, range, backwards) {
        +        var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";
        +        sel.anchorNode = range[anchorPrefix + "Container"];
        +        sel.anchorOffset = range[anchorPrefix + "Offset"];
        +        sel.focusNode = range[focusPrefix + "Container"];
        +        sel.focusOffset = range[focusPrefix + "Offset"];
        +    }
        +
        +    function updateAnchorAndFocusFromNativeSelection(sel) {
        +        var nativeSel = sel.nativeSelection;
        +        sel.anchorNode = nativeSel.anchorNode;
        +        sel.anchorOffset = nativeSel.anchorOffset;
        +        sel.focusNode = nativeSel.focusNode;
        +        sel.focusOffset = nativeSel.focusOffset;
        +    }
        +
        +    function updateEmptySelection(sel) {
        +        sel.anchorNode = sel.focusNode = null;
        +        sel.anchorOffset = sel.focusOffset = 0;
        +        sel.rangeCount = 0;
        +        sel.isCollapsed = true;
        +        sel._ranges.length = 0;
        +    }
        +
        +    function getNativeRange(range) {
        +        var nativeRange;
        +        if (range instanceof DomRange) {
        +            nativeRange = range._selectionNativeRange;
        +            if (!nativeRange) {
        +                nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
        +                nativeRange.setEnd(range.endContainer, range.endOffset);
        +                nativeRange.setStart(range.startContainer, range.startOffset);
        +                range._selectionNativeRange = nativeRange;
        +                range.attachListener("detach", function() {
        +
        +                    this._selectionNativeRange = null;
        +                });
        +            }
        +        } else if (range instanceof WrappedRange) {
        +            nativeRange = range.nativeRange;
        +        } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
        +            nativeRange = range;
        +        }
        +        return nativeRange;
        +    }
        +
        +    function rangeContainsSingleElement(rangeNodes) {
        +        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
        +            return false;
        +        }
        +        for (var i = 1, len = rangeNodes.length; i < len; ++i) {
        +            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
        +                return false;
        +            }
        +        }
        +        return true;
        +    }
        +
        +    function getSingleElementFromRange(range) {
        +        var nodes = range.getNodes();
        +        if (!rangeContainsSingleElement(nodes)) {
        +            throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
        +        }
        +        return nodes[0];
        +    }
        +
        +    function isTextRange(range) {
        +        return !!range && typeof range.text != "undefined";
        +    }
        +
        +    function updateFromTextRange(sel, range) {
        +        // Create a Range from the selected TextRange
        +        var wrappedRange = new WrappedRange(range);
        +        sel._ranges = [wrappedRange];
        +
        +        updateAnchorAndFocusFromRange(sel, wrappedRange, false);
        +        sel.rangeCount = 1;
        +        sel.isCollapsed = wrappedRange.collapsed;
        +    }
        +
        +    function updateControlSelection(sel) {
        +        // Update the wrapped selection based on what's now in the native selection
        +        sel._ranges.length = 0;
        +        if (sel.docSelection.type == "None") {
        +            updateEmptySelection(sel);
        +        } else {
        +            var controlRange = sel.docSelection.createRange();
        +            if (isTextRange(controlRange)) {
        +                // This case (where the selection type is "Control" and calling createRange() on the selection returns
        +                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
        +                // ControlRange have been removed from the ControlRange and removed from the document.
        +                updateFromTextRange(sel, controlRange);
        +            } else {
        +                sel.rangeCount = controlRange.length;
        +                var range, doc = dom.getDocument(controlRange.item(0));
        +                for (var i = 0; i < sel.rangeCount; ++i) {
        +                    range = api.createRange(doc);
        +                    range.selectNode(controlRange.item(i));
        +                    sel._ranges.push(range);
        +                }
        +                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
        +                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
        +            }
        +        }
        +    }
        +
        +    function addRangeToControlSelection(sel, range) {
        +        var controlRange = sel.docSelection.createRange();
        +        var rangeElement = getSingleElementFromRange(range);
        +
        +        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
        +        // contained by the supplied range
        +        var doc = dom.getDocument(controlRange.item(0));
        +        var newControlRange = dom.getBody(doc).createControlRange();
        +        for (var i = 0, len = controlRange.length; i < len; ++i) {
        +            newControlRange.add(controlRange.item(i));
        +        }
        +        try {
        +            newControlRange.add(rangeElement);
        +        } catch (ex) {
        +            throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
        +        }
        +        newControlRange.select();
        +
        +        // Update the wrapped selection based on what's now in the native selection
        +        updateControlSelection(sel);
        +    }
        +
        +    var getSelectionRangeAt;
        +
        +    if (util.isHostMethod(testSelection,  "getRangeAt")) {
        +        getSelectionRangeAt = function(sel, index) {
        +            try {
        +                return sel.getRangeAt(index);
        +            } catch(ex) {
        +                return null;
        +            }
        +        };
        +    } else if (selectionHasAnchorAndFocus) {
        +        getSelectionRangeAt = function(sel) {
        +            var doc = dom.getDocument(sel.anchorNode);
        +            var range = api.createRange(doc);
        +            range.setStart(sel.anchorNode, sel.anchorOffset);
        +            range.setEnd(sel.focusNode, sel.focusOffset);
        +
        +            // Handle the case when the selection was selected backwards (from the end to the start in the
        +            // document)
        +            if (range.collapsed !== this.isCollapsed) {
        +                range.setStart(sel.focusNode, sel.focusOffset);
        +                range.setEnd(sel.anchorNode, sel.anchorOffset);
        +            }
        +
        +            return range;
        +        };
        +    }
        +
        +    /**
        +     * @constructor
        +     */
        +    function WrappedSelection(selection, docSelection, win) {
        +        this.nativeSelection = selection;
        +        this.docSelection = docSelection;
        +        this._ranges = [];
        +        this.win = win;
        +        this.refresh();
        +    }
        +
        +    api.getSelection = function(win) {
        +        win = win || window;
        +        var sel = win[windowPropertyName];
        +        var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
        +        if (sel) {
        +            sel.nativeSelection = nativeSel;
        +            sel.docSelection = docSel;
        +            sel.refresh(win);
        +        } else {
        +            sel = new WrappedSelection(nativeSel, docSel, win);
        +            win[windowPropertyName] = sel;
        +        }
        +        return sel;
        +    };
        +
        +    api.getIframeSelection = function(iframeEl) {
        +        return api.getSelection(dom.getIframeWindow(iframeEl));
        +    };
        +
        +    var selProto = WrappedSelection.prototype;
        +
        +    function createControlSelection(sel, ranges) {
        +        // Ensure that the selection becomes of type "Control"
        +        var doc = dom.getDocument(ranges[0].startContainer);
        +        var controlRange = dom.getBody(doc).createControlRange();
        +        for (var i = 0, el; i < rangeCount; ++i) {
        +            el = getSingleElementFromRange(ranges[i]);
        +            try {
        +                controlRange.add(el);
        +            } catch (ex) {
        +                throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
        +            }
        +        }
        +        controlRange.select();
        +
        +        // Update the wrapped selection based on what's now in the native selection
        +        updateControlSelection(sel);
        +    }
        +
        +    // Selecting a range
        +    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
        +        selProto.removeAllRanges = function() {
        +            this.nativeSelection.removeAllRanges();
        +            updateEmptySelection(this);
        +        };
        +
        +        var addRangeBackwards = function(sel, range) {
        +            var doc = DomRange.getRangeDocument(range);
        +            var endRange = api.createRange(doc);
        +            endRange.collapseToPoint(range.endContainer, range.endOffset);
        +            sel.nativeSelection.addRange(getNativeRange(endRange));
        +            sel.nativeSelection.extend(range.startContainer, range.startOffset);
        +            sel.refresh();
        +        };
        +
        +        if (selectionHasRangeCount) {
        +            selProto.addRange = function(range, backwards) {
        +                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
        +                    addRangeToControlSelection(this, range);
        +                } else {
        +                    if (backwards && selectionHasExtend) {
        +                        addRangeBackwards(this, range);
        +                    } else {
        +                        var previousRangeCount;
        +                        if (selectionSupportsMultipleRanges) {
        +                            previousRangeCount = this.rangeCount;
        +                        } else {
        +                            this.removeAllRanges();
        +                            previousRangeCount = 0;
        +                        }
        +                        this.nativeSelection.addRange(getNativeRange(range));
        +
        +                        // Check whether adding the range was successful
        +                        this.rangeCount = this.nativeSelection.rangeCount;
        +
        +                        if (this.rangeCount == previousRangeCount + 1) {
        +                            // The range was added successfully
        +
        +                            // Check whether the range that we added to the selection is reflected in the last range extracted from
        +                            // the selection
        +                            if (api.config.checkSelectionRanges) {
        +                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
        +                                if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
        +                                    // Happens in WebKit with, for example, a selection placed at the start of a text node
        +                                    range = new WrappedRange(nativeRange);
        +                                }
        +                            }
        +                            this._ranges[this.rangeCount - 1] = range;
        +                            updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
        +                            this.isCollapsed = selectionIsCollapsed(this);
        +                        } else {
        +                            // The range was not added successfully. The simplest thing is to refresh
        +                            this.refresh();
        +                        }
        +                    }
        +                }
        +            };
        +        } else {
        +            selProto.addRange = function(range, backwards) {
        +                if (backwards && selectionHasExtend) {
        +                    addRangeBackwards(this, range);
        +                } else {
        +                    this.nativeSelection.addRange(getNativeRange(range));
        +                    this.refresh();
        +                }
        +            };
        +        }
        +
        +        selProto.setRanges = function(ranges) {
        +            if (implementsControlRange && ranges.length > 1) {
        +                createControlSelection(this, ranges);
        +            } else {
        +                this.removeAllRanges();
        +                for (var i = 0, len = ranges.length; i < len; ++i) {
        +                    this.addRange(ranges[i]);
        +                }
        +            }
        +        };
        +    } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&
        +               implementsControlRange && useDocumentSelection) {
        +
        +        selProto.removeAllRanges = function() {
        +            // Added try/catch as fix for issue #21
        +            try {
        +                this.docSelection.empty();
        +
        +                // Check for empty() not working (issue #24)
        +                if (this.docSelection.type != "None") {
        +                    // Work around failure to empty a control selection by instead selecting a TextRange and then
        +                    // calling empty()
        +                    var doc;
        +                    if (this.anchorNode) {
        +                        doc = dom.getDocument(this.anchorNode);
        +                    } else if (this.docSelection.type == CONTROL) {
        +                        var controlRange = this.docSelection.createRange();
        +                        if (controlRange.length) {
        +                            doc = dom.getDocument(controlRange.item(0)).body.createTextRange();
        +                        }
        +                    }
        +                    if (doc) {
        +                        var textRange = doc.body.createTextRange();
        +                        textRange.select();
        +                        this.docSelection.empty();
        +                    }
        +                }
        +            } catch(ex) {}
        +            updateEmptySelection(this);
        +        };
        +
        +        selProto.addRange = function(range) {
        +            if (this.docSelection.type == CONTROL) {
        +                addRangeToControlSelection(this, range);
        +            } else {
        +                WrappedRange.rangeToTextRange(range).select();
        +                this._ranges[0] = range;
        +                this.rangeCount = 1;
        +                this.isCollapsed = this._ranges[0].collapsed;
        +                updateAnchorAndFocusFromRange(this, range, false);
        +            }
        +        };
        +
        +        selProto.setRanges = function(ranges) {
        +            this.removeAllRanges();
        +            var rangeCount = ranges.length;
        +            if (rangeCount > 1) {
        +                createControlSelection(this, ranges);
        +            } else if (rangeCount) {
        +                this.addRange(ranges[0]);
        +            }
        +        };
        +    } else {
        +        module.fail("No means of selecting a Range or TextRange was found");
        +        return false;
        +    }
        +
        +    selProto.getRangeAt = function(index) {
        +        if (index < 0 || index >= this.rangeCount) {
        +            throw new DOMException("INDEX_SIZE_ERR");
        +        } else {
        +            return this._ranges[index];
        +        }
        +    };
        +
        +    var refreshSelection;
        +
        +    if (useDocumentSelection) {
        +        refreshSelection = function(sel) {
        +            var range;
        +            if (api.isSelectionValid(sel.win)) {
        +                range = sel.docSelection.createRange();
        +            } else {
        +                range = dom.getBody(sel.win.document).createTextRange();
        +                range.collapse(true);
        +            }
        +
        +
        +            if (sel.docSelection.type == CONTROL) {
        +                updateControlSelection(sel);
        +            } else if (isTextRange(range)) {
        +                updateFromTextRange(sel, range);
        +            } else {
        +                updateEmptySelection(sel);
        +            }
        +        };
        +    } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
        +        refreshSelection = function(sel) {
        +            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
        +                updateControlSelection(sel);
        +            } else {
        +                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
        +                if (sel.rangeCount) {
        +                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {
        +                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
        +                    }
        +                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
        +                    sel.isCollapsed = selectionIsCollapsed(sel);
        +                } else {
        +                    updateEmptySelection(sel);
        +                }
        +            }
        +        };
        +    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
        +        refreshSelection = function(sel) {
        +            var range, nativeSel = sel.nativeSelection;
        +            if (nativeSel.anchorNode) {
        +                range = getSelectionRangeAt(nativeSel, 0);
        +                sel._ranges = [range];
        +                sel.rangeCount = 1;
        +                updateAnchorAndFocusFromNativeSelection(sel);
        +                sel.isCollapsed = selectionIsCollapsed(sel);
        +            } else {
        +                updateEmptySelection(sel);
        +            }
        +        };
        +    } else {
        +        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
        +        return false;
        +    }
        +
        +    selProto.refresh = function(checkForChanges) {
        +        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
        +        refreshSelection(this);
        +        if (checkForChanges) {
        +            var i = oldRanges.length;
        +            if (i != this._ranges.length) {
        +                return false;
        +            }
        +            while (i--) {
        +                if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
        +                    return false;
        +                }
        +            }
        +            return true;
        +        }
        +    };
        +
        +    // Removal of a single range
        +    var removeRangeManually = function(sel, range) {
        +        var ranges = sel.getAllRanges(), removed = false;
        +        sel.removeAllRanges();
        +        for (var i = 0, len = ranges.length; i < len; ++i) {
        +            if (removed || range !== ranges[i]) {
        +                sel.addRange(ranges[i]);
        +            } else {
        +                // According to the draft WHATWG Range spec, the same range may be added to the selection multiple
        +                // times. removeRange should only remove the first instance, so the following ensures only the first
        +                // instance is removed
        +                removed = true;
        +            }
        +        }
        +        if (!sel.rangeCount) {
        +            updateEmptySelection(sel);
        +        }
        +    };
        +
        +    if (implementsControlRange) {
        +        selProto.removeRange = function(range) {
        +            if (this.docSelection.type == CONTROL) {
        +                var controlRange = this.docSelection.createRange();
        +                var rangeElement = getSingleElementFromRange(range);
        +
        +                // Create a new ControlRange containing all the elements in the selected ControlRange minus the
        +                // element contained by the supplied range
        +                var doc = dom.getDocument(controlRange.item(0));
        +                var newControlRange = dom.getBody(doc).createControlRange();
        +                var el, removed = false;
        +                for (var i = 0, len = controlRange.length; i < len; ++i) {
        +                    el = controlRange.item(i);
        +                    if (el !== rangeElement || removed) {
        +                        newControlRange.add(controlRange.item(i));
        +                    } else {
        +                        removed = true;
        +                    }
        +                }
        +                newControlRange.select();
        +
        +                // Update the wrapped selection based on what's now in the native selection
        +                updateControlSelection(this);
        +            } else {
        +                removeRangeManually(this, range);
        +            }
        +        };
        +    } else {
        +        selProto.removeRange = function(range) {
        +            removeRangeManually(this, range);
        +        };
        +    }
        +
        +    // Detecting if a selection is backwards
        +    var selectionIsBackwards;
        +    if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
        +        selectionIsBackwards = function(sel) {
        +            var backwards = false;
        +            if (sel.anchorNode) {
        +                backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
        +            }
        +            return backwards;
        +        };
        +
        +        selProto.isBackwards = function() {
        +            return selectionIsBackwards(this);
        +        };
        +    } else {
        +        selectionIsBackwards = selProto.isBackwards = function() {
        +            return false;
        +        };
        +    }
        +
        +    // Selection text
        +    // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
        +    selProto.toString = function() {
        +
        +        var rangeTexts = [];
        +        for (var i = 0, len = this.rangeCount; i < len; ++i) {
        +            rangeTexts[i] = "" + this._ranges[i];
        +        }
        +        return rangeTexts.join("");
        +    };
        +
        +    function assertNodeInSameDocument(sel, node) {
        +        if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
        +            throw new DOMException("WRONG_DOCUMENT_ERR");
        +        }
        +    }
        +
        +    // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
        +    selProto.collapse = function(node, offset) {
        +        assertNodeInSameDocument(this, node);
        +        var range = api.createRange(dom.getDocument(node));
        +        range.collapseToPoint(node, offset);
        +        this.removeAllRanges();
        +        this.addRange(range);
        +        this.isCollapsed = true;
        +    };
        +
        +    selProto.collapseToStart = function() {
        +        if (this.rangeCount) {
        +            var range = this._ranges[0];
        +            this.collapse(range.startContainer, range.startOffset);
        +        } else {
        +            throw new DOMException("INVALID_STATE_ERR");
        +        }
        +    };
        +
        +    selProto.collapseToEnd = function() {
        +        if (this.rangeCount) {
        +            var range = this._ranges[this.rangeCount - 1];
        +            this.collapse(range.endContainer, range.endOffset);
        +        } else {
        +            throw new DOMException("INVALID_STATE_ERR");
        +        }
        +    };
        +
        +    // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
        +    // never used by Rangy.
        +    selProto.selectAllChildren = function(node) {
        +        assertNodeInSameDocument(this, node);
        +        var range = api.createRange(dom.getDocument(node));
        +        range.selectNodeContents(node);
        +        this.removeAllRanges();
        +        this.addRange(range);
        +    };
        +
        +    selProto.deleteFromDocument = function() {
        +        // Sepcial behaviour required for Control selections
        +        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
        +            var controlRange = this.docSelection.createRange();
        +            var element;
        +            while (controlRange.length) {
        +                element = controlRange.item(0);
        +                controlRange.remove(element);
        +                element.parentNode.removeChild(element);
        +            }
        +            this.refresh();
        +        } else if (this.rangeCount) {
        +            var ranges = this.getAllRanges();
        +            this.removeAllRanges();
        +            for (var i = 0, len = ranges.length; i < len; ++i) {
        +                ranges[i].deleteContents();
        +            }
        +            // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
        +            // range. Firefox moves the selection to where the final selected range was, so we emulate that
        +            this.addRange(ranges[len - 1]);
        +        }
        +    };
        +
        +    // The following are non-standard extensions
        +    selProto.getAllRanges = function() {
        +        return this._ranges.slice(0);
        +    };
        +
        +    selProto.setSingleRange = function(range) {
        +        this.setRanges( [range] );
        +    };
        +
        +    selProto.containsNode = function(node, allowPartial) {
        +        for (var i = 0, len = this._ranges.length; i < len; ++i) {
        +            if (this._ranges[i].containsNode(node, allowPartial)) {
        +                return true;
        +            }
        +        }
        +        return false;
        +    };
        +
        +    selProto.toHtml = function() {
        +        var html = "";
        +        if (this.rangeCount) {
        +            var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
        +            for (var i = 0, len = this._ranges.length; i < len; ++i) {
        +                container.appendChild(this._ranges[i].cloneContents());
        +            }
        +            html = container.innerHTML;
        +        }
        +        return html;
        +    };
        +
        +    function inspect(sel) {
        +        var rangeInspects = [];
        +        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
        +        var focus = new DomPosition(sel.focusNode, sel.focusOffset);
        +        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
        +
        +        if (typeof sel.rangeCount != "undefined") {
        +            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
        +                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
        +            }
        +        }
        +        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
        +                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
        +
        +    }
        +
        +    selProto.getName = function() {
        +        return "WrappedSelection";
        +    };
        +
        +    selProto.inspect = function() {
        +        return inspect(this);
        +    };
        +
        +    selProto.detach = function() {
        +        this.win[windowPropertyName] = null;
        +        this.win = this.anchorNode = this.focusNode = null;
        +    };
        +
        +    WrappedSelection.inspect = inspect;
        +
        +    api.Selection = WrappedSelection;
        +
        +    api.selectionPrototype = selProto;
        +
        +    api.addCreateMissingNativeApiListener(function(win) {
        +        if (typeof win.getSelection == "undefined") {
        +            win.getSelection = function() {
        +                return api.getSelection(this);
        +            };
        +        }
        +        win = null;
        +    });
        +});
        +/*
        +	Base.js, version 1.1a
        +	Copyright 2006-2010, Dean Edwards
        +	License: http://www.opensource.org/licenses/mit-license.php
        +*/
        +
        +var Base = function() {
        +	// dummy
        +};
        +
        +Base.extend = function(_instance, _static) { // subclass
        +	var extend = Base.prototype.extend;
        +	
        +	// build the prototype
        +	Base._prototyping = true;
        +	var proto = new this;
        +	extend.call(proto, _instance);
        +  proto.base = function() {
        +    // call this method from any other method to invoke that method's ancestor
        +  };
        +	delete Base._prototyping;
        +	
        +	// create the wrapper for the constructor function
        +	//var constructor = proto.constructor.valueOf(); //-dean
        +	var constructor = proto.constructor;
        +	var klass = proto.constructor = function() {
        +		if (!Base._prototyping) {
        +			if (this._constructing || this.constructor == klass) { // instantiation
        +				this._constructing = true;
        +				constructor.apply(this, arguments);
        +				delete this._constructing;
        +			} else if (arguments[0] != null) { // casting
        +				return (arguments[0].extend || extend).call(arguments[0], proto);
        +			}
        +		}
        +	};
        +	
        +	// build the class interface
        +	klass.ancestor = this;
        +	klass.extend = this.extend;
        +	klass.forEach = this.forEach;
        +	klass.implement = this.implement;
        +	klass.prototype = proto;
        +	klass.toString = this.toString;
        +	klass.valueOf = function(type) {
        +		//return (type == "object") ? klass : constructor; //-dean
        +		return (type == "object") ? klass : constructor.valueOf();
        +	};
        +	extend.call(klass, _static);
        +	// class initialisation
        +	if (typeof klass.init == "function") klass.init();
        +	return klass;
        +};
        +
        +Base.prototype = {	
        +	extend: function(source, value) {
        +		if (arguments.length > 1) { // extending with a name/value pair
        +			var ancestor = this[source];
        +			if (ancestor && (typeof value == "function") && // overriding a method?
        +				// the valueOf() comparison is to avoid circular references
        +				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
        +				/\bbase\b/.test(value)) {
        +				// get the underlying method
        +				var method = value.valueOf();
        +				// override
        +				value = function() {
        +					var previous = this.base || Base.prototype.base;
        +					this.base = ancestor;
        +					var returnValue = method.apply(this, arguments);
        +					this.base = previous;
        +					return returnValue;
        +				};
        +				// point to the underlying method
        +				value.valueOf = function(type) {
        +					return (type == "object") ? value : method;
        +				};
        +				value.toString = Base.toString;
        +			}
        +			this[source] = value;
        +		} else if (source) { // extending with an object literal
        +			var extend = Base.prototype.extend;
        +			// if this object has a customised extend method then use it
        +			if (!Base._prototyping && typeof this != "function") {
        +				extend = this.extend || extend;
        +			}
        +			var proto = {toSource: null};
        +			// do the "toString" and other methods manually
        +			var hidden = ["constructor", "toString", "valueOf"];
        +			// if we are prototyping then include the constructor
        +			var i = Base._prototyping ? 0 : 1;
        +			while (key = hidden[i++]) {
        +				if (source[key] != proto[key]) {
        +					extend.call(this, key, source[key]);
        +
        +				}
        +			}
        +			// copy each of the source object's properties to this object
        +			for (var key in source) {
        +				if (!proto[key]) extend.call(this, key, source[key]);
        +			}
        +		}
        +		return this;
        +	}
        +};
        +
        +// initialise
        +Base = Base.extend({
        +	constructor: function() {
        +		this.extend(arguments[0]);
        +	}
        +}, {
        +	ancestor: Object,
        +	version: "1.1",
        +	
        +	forEach: function(object, block, context) {
        +		for (var key in object) {
        +			if (this.prototype[key] === undefined) {
        +				block.call(context, object[key], key, object);
        +			}
        +		}
        +	},
        +		
        +	implement: function() {
        +		for (var i = 0; i < arguments.length; i++) {
        +			if (typeof arguments[i] == "function") {
        +				// if it's a function, call it
        +				arguments[i](this.prototype);
        +			} else {
        +				// add the interface using the extend method
        +				this.prototype.extend(arguments[i]);
        +			}
        +		}
        +		return this;
        +	},
        +	
        +	toString: function() {
        +		return String(this.valueOf());
        +	}
        +});/**
        + * Detect browser support for specific features
        + */
        +wysihtml5.browser = (function() {
        +  var userAgent   = navigator.userAgent,
        +      testElement = document.createElement("div"),
        +      // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
        +      isIE        = userAgent.indexOf("MSIE")         !== -1 && userAgent.indexOf("Opera") === -1,
        +      isGecko     = userAgent.indexOf("Gecko")        !== -1 && userAgent.indexOf("KHTML") === -1,
        +      isWebKit    = userAgent.indexOf("AppleWebKit/") !== -1,
        +      isChrome    = userAgent.indexOf("Chrome/")      !== -1,
        +      isOpera     = userAgent.indexOf("Opera/")       !== -1;
        +  
        +  function iosVersion(userAgent) {
        +    return ((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [, 0])[1];
        +  }
        +  
        +  return {
        +    // Static variable needed, publicly accessible, to be able override it in unit tests
        +    USER_AGENT: userAgent,
        +    
        +    /**
        +     * Exclude browsers that are not capable of displaying and handling
        +     * contentEditable as desired:
        +     *    - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
        +     *    - IE < 8 create invalid markup and crash randomly from time to time
        +     *
        +     * @return {Boolean}
        +     */
        +    supported: function() {
        +      var userAgent                   = this.USER_AGENT.toLowerCase(),
        +          // Essential for making html elements editable
        +          hasContentEditableSupport   = "contentEditable" in testElement,
        +          // Following methods are needed in order to interact with the contentEditable area
        +          hasEditingApiSupport        = document.execCommand && document.queryCommandSupported && document.queryCommandState,
        +          // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
        +          hasQuerySelectorSupport     = document.querySelector && document.querySelectorAll,
        +          // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
        +          isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
        +      
        +      return hasContentEditableSupport
        +        && hasEditingApiSupport
        +        && hasQuerySelectorSupport
        +        && !isIncompatibleMobileBrowser;
        +    },
        +    
        +    isTouchDevice: function() {
        +      return this.supportsEvent("touchmove");
        +    },
        +    
        +    isIos: function() {
        +      var userAgent = this.USER_AGENT.toLowerCase();
        +      return userAgent.indexOf("webkit") !== -1 && userAgent.indexOf("mobile") !== -1;
        +    },
        +    
        +    /**
        +     * Whether the browser supports sandboxed iframes
        +     * Currently only IE 6+ offers such feature