odania 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/{LICENSE.txt → MIT-LICENSE} +1 -3
- data/README.md +3 -3
- data/Rakefile +33 -3
- data/app/assets/config/odania_manifest.js +0 -0
- data/app/assets/javascripts/application.js +13 -0
- data/app/assets/javascripts/textAngular.js +15 -0
- data/app/assets/javascripts/textAngular/textAngular-rangy.min.js +478 -0
- data/app/assets/javascripts/textAngular/textAngular-sanitize.min.js +322 -0
- data/app/assets/javascripts/textAngular/textAngular.min.js +1481 -0
- data/app/assets/stylesheets/scaffold.css +80 -0
- data/app/assets/stylesheets/textAngular/application.css +15 -0
- data/app/assets/stylesheets/textAngular/textAngular.css +204 -0
- data/app/controllers/admin/home_controller.rb +2 -0
- data/app/controllers/admin/languages_controller.rb +74 -0
- data/app/controllers/admin_controller.rb +4 -0
- data/app/controllers/application_controller.rb +3 -0
- data/app/controllers/categories_controller.rb +34 -0
- data/app/controllers/home_controller.rb +11 -0
- data/app/controllers/protected/home_controller.rb +2 -0
- data/app/controllers/protected_controller.rb +22 -0
- data/app/controllers/registration_controller.rb +12 -0
- data/app/helpers/standard_form_builder.rb +71 -0
- data/app/helpers/standard_form_helper.rb +13 -0
- data/app/models/admin.rb +37 -0
- data/app/models/language.rb +5 -0
- data/app/models/user.rb +47 -0
- data/app/views/admin/home/index.html.erb +1 -0
- data/app/views/admin/languages/_form.html.erb +16 -0
- data/app/views/admin/languages/_language.json.jbuilder +2 -0
- data/app/views/admin/languages/edit.html.erb +6 -0
- data/app/views/admin/languages/index.html.erb +27 -0
- data/app/views/admin/languages/index.json.jbuilder +1 -0
- data/app/views/admin/languages/new.html.erb +5 -0
- data/app/views/admin/languages/show.html.erb +9 -0
- data/app/views/admin/languages/show.json.jbuilder +1 -0
- data/app/views/categories/index.html.erb +9 -0
- data/app/views/categories/show.html.erb +16 -0
- data/app/views/devise/confirmations/new.html.erb +11 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/email_changed.html.erb +7 -0
- data/app/views/devise/mailer/password_change.html.erb +4 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +9 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +8 -0
- data/app/views/devise/passwords/edit.html.erb +13 -0
- data/app/views/devise/passwords/new.html.erb +11 -0
- data/app/views/devise/registrations/edit.html.erb +39 -0
- data/app/views/devise/registrations/new.html.erb +15 -0
- data/app/views/devise/sessions/new.html.erb +14 -0
- data/app/views/devise/shared/_links.html.erb +25 -0
- data/app/views/devise/unlocks/new.html.erb +11 -0
- data/app/views/home/index.html.erb +11 -0
- data/app/views/languages/_form.html.erb +20 -0
- data/app/views/languages/edit.html.erb +6 -0
- data/app/views/languages/index.html.erb +27 -0
- data/app/views/languages/new.html.erb +5 -0
- data/app/views/languages/show.html.erb +9 -0
- data/app/views/protected/home/index.html.erb +1 -0
- data/config/initializers/elasticsearch.rb +5 -0
- data/config/locales/devise.en.yml +64 -0
- data/config/routes.rb +23 -0
- data/db/seeds.rb +5 -0
- data/lib/odania.rb +7 -56
- data/lib/odania/engine.rb +14 -0
- data/lib/odania/version.rb +1 -1
- data/lib/tasks/odania_tasks.rake +4 -0
- data/lib/templates/erb/scaffold/_form.html.erb +27 -0
- data/lib/templates/erb/scaffold/edit.html.erb +6 -0
- data/lib/templates/erb/scaffold/index.html.erb +31 -0
- data/lib/templates/erb/scaffold/new.html.erb +5 -0
- data/lib/templates/erb/scaffold/show.html.erb +11 -0
- metadata +129 -84
- data/.codeclimate.yml +0 -30
- data/.gitignore +0 -17
- data/.rspec +0 -2
- data/.rubocop.yml +0 -1156
- data/.travis.yml +0 -20
- data/Gemfile +0 -4
- data/Gemfile.lock +0 -113
- data/Guardfile +0 -31
- data/features/plugin.feature +0 -35
- data/features/step_definitions/plugin_steps.rb +0 -75
- data/features/support/env.rb +0 -1
- data/lib/odania/config.rb +0 -17
- data/lib/odania/config/backend.rb +0 -31
- data/lib/odania/config/backend_group.rb +0 -43
- data/lib/odania/config/domain.rb +0 -59
- data/lib/odania/config/duplicates.rb +0 -28
- data/lib/odania/config/global_config.rb +0 -210
- data/lib/odania/config/layout.rb +0 -30
- data/lib/odania/config/page.rb +0 -29
- data/lib/odania/config/page_base.rb +0 -47
- data/lib/odania/config/plugin_config.rb +0 -58
- data/lib/odania/config/style.rb +0 -36
- data/lib/odania/config/sub_domain.rb +0 -113
- data/lib/odania/config/subdomain_config.rb +0 -124
- data/lib/odania/consul.rb +0 -138
- data/lib/odania/plugin.rb +0 -103
- data/odania.gemspec +0 -34
- data/spec/fixtures/global_config.json +0 -135
- data/spec/fixtures/plugin_config_1.json +0 -102
- data/spec/lib/odania/config/global_config_spec.rb +0 -69
- data/spec/lib/odania/config/plugin_config_spec.rb +0 -31
- data/spec/lib/odania/plugin_spec.rb +0 -25
- data/spec/lib/odania_spec.rb +0 -10
- data/spec/spec_helper.rb +0 -19
- data/spec/support/consul_mock.rb +0 -123
- data/tasks/odania.rake +0 -8
- data/tasks/rspec.rake +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f412df2e10b3691f82b456d3cdad052ebb5f5fa
|
4
|
+
data.tar.gz: ed34bef05350e45ccbd5c6fa2456696612300d9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7da776dc4877d893fc77c152118df4ec1c0f14ef24f52dd594e44683fa5f9e5454995e07e57afa1fc61154706600b20a46b1da492818c5b92fb820408df7f92
|
7
|
+
data.tar.gz: 6e2a7afa3295221a7b70160136a054380c494772c6805078d9a524c9cb16a9072f767dd5746e9e3795a36d6ff5c05ea5b09a49052a449e30933b8437180f2e49
|
data/{LICENSE.txt → MIT-LICENSE}
RENAMED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
|
1
|
+
= OdaniaCore
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/Odania-IT/odania-
|
4
|
-
[![Code Climate](https://codeclimate.com/github/Odania-IT/odania-
|
3
|
+
[![Build Status](https://travis-ci.org/Odania-IT/odania-core.png?branch=master)](https://travis-ci.org/Odania-IT/odania-core)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/Odania-IT/odania-core/badges/gpa.svg)](https://codeclimate.com/github/Odania-IT/odania-core)
|
5
5
|
|
6
6
|
Helper for the Odania Portal
|
7
7
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,36 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Odania'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
1
25
|
require 'bundler/gem_tasks'
|
2
|
-
require_relative 'lib/odania'
|
3
26
|
|
4
|
-
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'test'
|
31
|
+
t.pattern = 'test/**/*_test.rb'
|
32
|
+
t.verbose = false
|
33
|
+
end
|
34
|
+
|
5
35
|
|
6
|
-
task :
|
36
|
+
task default: :test
|
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_self
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require ./textAngular/textAngular-rangy
|
14
|
+
//= require ./textAngular/textAngular-sanitize
|
15
|
+
//= require ./textAngular/textAngular
|
@@ -0,0 +1,478 @@
|
|
1
|
+
/**
|
2
|
+
* Rangy, a cross-browser JavaScript range and selection library
|
3
|
+
* https://github.com/timdown/rangy
|
4
|
+
*
|
5
|
+
* Copyright 2015, Tim Down
|
6
|
+
* Licensed under the MIT license.
|
7
|
+
* Version: 1.3.0
|
8
|
+
* Build date: 10 May 2015
|
9
|
+
*/
|
10
|
+
!function(a,b){"function"==typeof define&&define.amd?
|
11
|
+
// AMD. Register as an anonymous module.
|
12
|
+
define(a):"undefined"!=typeof module&&"object"==typeof exports?
|
13
|
+
// Node/CommonJS style
|
14
|
+
module.exports=a():
|
15
|
+
// No AMD or CommonJS support so we place Rangy in (probably) the global variable
|
16
|
+
b.rangy=a()}(function(){/*----------------------------------------------------------------------------------------------------------------*/
|
17
|
+
// Trio of functions taken from Peter Michaux's article:
|
18
|
+
// http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
|
19
|
+
function a(a,b){var c=typeof a[b];return c==u||!(c!=t||!a[b])||"unknown"==c}function b(a,b){return!(typeof a[b]!=t||!a[b])}function c(a,b){return typeof a[b]!=v}
|
20
|
+
// Creates a convenience function to save verbose repeated calls to tests functions
|
21
|
+
function d(a){return function(b,c){for(var d=c.length;d--;)if(!a(b,c[d]))return!1;return!0}}function e(a){return a&&A(a,z)&&C(a,y)}function f(a){return b(a,"body")?a.body:a.getElementsByTagName("body")[0]}function g(b){typeof console!=v&&a(console,"log")&&console.log(b)}function h(a,b){F&&b?alert(a):g(a)}function i(a){H.initialized=!0,H.supported=!1,h("Rangy is not supported in this environment. Reason: "+a,H.config.alertOnFail)}function j(a){h("Rangy warning: "+a,H.config.alertOnWarn)}function k(a){return a.message||a.description||String(a)}
|
22
|
+
// Initialization
|
23
|
+
function l(){if(F&&!H.initialized){var b,c=!1,d=!1;
|
24
|
+
// First, perform basic feature tests
|
25
|
+
a(document,"createRange")&&(b=document.createRange(),A(b,x)&&C(b,w)&&(c=!0));var h=f(document);if(!h||"body"!=h.nodeName.toLowerCase())return void i("No body element found");if(h&&a(h,"createTextRange")&&(b=h.createTextRange(),e(b)&&(d=!0)),!c&&!d)return void i("Neither Range nor TextRange are available");H.initialized=!0,H.features={implementsDomRange:c,implementsTextRange:d};
|
26
|
+
// Initialize modules
|
27
|
+
var j,l;for(var m in E)(j=E[m])instanceof p&&j.init(j,H);
|
28
|
+
// Call init listeners
|
29
|
+
for(var n=0,o=K.length;n<o;++n)try{K[n](H)}catch(a){l="Rangy init listener threw an exception. Continuing. Detail: "+k(a),g(l)}}}function m(a,b,c){c&&(a+=" in module "+c.name),H.warn("DEPRECATED: "+a+" is deprecated. Please use "+b+" instead.")}function n(a,b,c,d){a[b]=function(){return m(b,c,d),a[c].apply(a,G.toArray(arguments))}}function o(a){a=a||window,l();
|
30
|
+
// Notify listeners
|
31
|
+
for(var b=0,c=L.length;b<c;++b)L[b](a)}function p(a,b,c){this.name=a,this.dependencies=b,this.initialized=!1,this.supported=!1,this.initializer=c}function q(a,b,c){var d=new p(a,b,function(b){if(!b.initialized){b.initialized=!0;try{c(H,b),b.supported=!0}catch(b){var d="Module '"+a+"' failed to load: "+k(b);g(d),b.stack&&g(b.stack)}}});return E[a]=d,d}/*----------------------------------------------------------------------------------------------------------------*/
|
32
|
+
// Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately
|
33
|
+
function r(){}function s(){}var t="object",u="function",v="undefined",w=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],x=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],y=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],z=["collapse","compareEndPoints","duplicate","moveToElementText","parentElement","select","setEndPoint","getBoundingClientRect"],A=d(a),B=d(b),C=d(c),D=[].forEach?function(a,b){a.forEach(b)}:function(a,b){for(var c=0,d=a.length;c<d;++c)b(a[c],c)},E={},F=typeof window!=v&&typeof document!=v,G={isHostMethod:a,isHostObject:b,isHostProperty:c,areHostMethods:A,areHostObjects:B,areHostProperties:C,isTextRange:e,getBody:f,forEach:D},H={version:"1.3.0",initialized:!1,isBrowser:F,supported:!0,util:G,features:{},modules:E,config:{alertOnFail:!1,alertOnWarn:!1,preferTextRange:!1,autoInitialize:typeof rangyAutoInitialize==v||rangyAutoInitialize}};H.fail=i,H.warn=j;
|
34
|
+
// Add utility extend() method
|
35
|
+
var I;({}).hasOwnProperty?(G.extend=I=function(a,b,c){var d,e;for(var f in b)b.hasOwnProperty(f)&&(d=a[f],e=b[f],c&&null!==d&&"object"==typeof d&&null!==e&&"object"==typeof e&&I(d,e,!0),a[f]=e);
|
36
|
+
// Special case for toString, which does not show up in for...in loops in IE <= 8
|
37
|
+
return b.hasOwnProperty("toString")&&(a.toString=b.toString),a},G.createOptions=function(a,b){var c={};return I(c,b),a&&I(c,a),c}):i("hasOwnProperty not supported"),
|
38
|
+
// Test whether we're in a browser and bail out if not
|
39
|
+
F||i("Rangy can only run in a browser"),
|
40
|
+
// Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
|
41
|
+
function(){var a;if(F){var b=document.createElement("div");b.appendChild(document.createElement("span"));var c=[].slice;try{1==c.call(b.childNodes,0)[0].nodeType&&(a=function(a){return c.call(a,0)})}catch(a){}}a||(a=function(a){for(var b=[],c=0,d=a.length;c<d;++c)b[c]=a[c];return b}),G.toArray=a}();
|
42
|
+
// Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
|
43
|
+
// normalization of event properties
|
44
|
+
var J;F&&(a(document,"addEventListener")?J=function(a,b,c){a.addEventListener(b,c,!1)}:a(document,"attachEvent")?J=function(a,b,c){a.attachEvent("on"+b,c)}:i("Document does not have required addEventListener or attachEvent method"),G.addListener=J);var K=[];G.deprecationNotice=m,G.createAliasForDeprecatedMethod=n,
|
45
|
+
// Allow external scripts to initialize this library in case it's loaded after the document has loaded
|
46
|
+
H.init=l,
|
47
|
+
// Execute listener immediately if already initialized
|
48
|
+
H.addInitListener=function(a){H.initialized?a(H):K.push(a)};var L=[];H.addShimListener=function(a){L.push(a)},F&&(H.shim=H.createMissingNativeApi=o,n(H,"createMissingNativeApi","shim")),p.prototype={init:function(){for(var a,b,c=this.dependencies||[],d=0,e=c.length;d<e;++d){if(b=c[d],a=E[b],!(a&&a instanceof p))throw new Error("required module '"+b+"' not found");if(a.init(),!a.supported)throw new Error("required module '"+b+"' not supported")}
|
49
|
+
// Now run initializer
|
50
|
+
this.initializer(this)},fail:function(a){throw this.initialized=!0,this.supported=!1,new Error(a)},warn:function(a){H.warn("Module "+this.name+": "+a)},deprecationNotice:function(a,b){H.warn("DEPRECATED: "+a+" in module "+this.name+" is deprecated. Please use "+b+" instead")},createError:function(a){return new Error("Error in Rangy "+this.name+" module: "+a)}},H.createModule=function(a){
|
51
|
+
// Allow 2 or 3 arguments (second argument is an optional array of dependencies)
|
52
|
+
var b,c;2==arguments.length?(b=arguments[1],c=[]):(b=arguments[2],c=arguments[1]);var d=q(a,c,b);
|
53
|
+
// Initialize the module immediately if the core is already initialized
|
54
|
+
H.initialized&&H.supported&&d.init()},H.createCoreModule=function(a,b,c){q(a,b,c)},H.RangePrototype=r,H.rangePrototype=new r,H.selectionPrototype=new s,/*----------------------------------------------------------------------------------------------------------------*/
|
55
|
+
// DOM utility methods used by Rangy
|
56
|
+
H.createCoreModule("DomUtil",[],function(a,b){
|
57
|
+
// Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
|
58
|
+
function c(a){var b;return typeof a.namespaceURI==F||null===(b=a.namespaceURI)||"http://www.w3.org/1999/xhtml"==b}function d(a){var b=a.parentNode;return 1==b.nodeType?b:null}function e(a){for(var b=0;a=a.previousSibling;)++b;return b}function f(a){switch(a.nodeType){case 7:case 10:return 0;case 3:case 8:return a.length;default:return a.childNodes.length}}function g(a,b){var c,d=[];for(c=a;c;c=c.parentNode)d.push(c);for(c=b;c;c=c.parentNode)if(K(d,c))return c;return null}function h(a,b,c){for(var d=c?b:b.parentNode;d;){if(d===a)return!0;d=d.parentNode}return!1}function i(a,b){return h(a,b,!0)}function j(a,b,c){for(var d,e=c?a:a.parentNode;e;){if(d=e.parentNode,d===b)return e;e=d}return null}function k(a){var b=a.nodeType;return 3==b||4==b||8==b}function l(a){if(!a)return!1;var b=a.nodeType;return 3==b||8==b}function m(a,b){var c=b.nextSibling,d=b.parentNode;return c?d.insertBefore(a,c):d.appendChild(a),a}
|
59
|
+
// Note that we cannot use splitText() because it is bugridden in IE 9.
|
60
|
+
function n(a,b,c){var d=a.cloneNode(!1);
|
61
|
+
// Preserve positions
|
62
|
+
if(d.deleteData(0,b),a.deleteData(b,a.length-b),m(d,a),c)for(var f,g=0;f=c[g++];)
|
63
|
+
// Handle case where position was inside the portion of node after the split point
|
64
|
+
f.node==a&&f.offset>b?(f.node=d,f.offset-=b):f.node==a.parentNode&&f.offset>e(a)&&++f.offset;return d}function o(a){if(9==a.nodeType)return a;if(typeof a.ownerDocument!=F)return a.ownerDocument;if(typeof a.document!=F)return a.document;if(a.parentNode)return o(a.parentNode);throw b.createError("getDocument: no document found for node")}function p(a){var c=o(a);if(typeof c.defaultView!=F)return c.defaultView;if(typeof c.parentWindow!=F)return c.parentWindow;throw b.createError("Cannot get a window object for node")}function q(a){if(typeof a.contentDocument!=F)return a.contentDocument;if(typeof a.contentWindow!=F)return a.contentWindow.document;throw b.createError("getIframeDocument: No Document object found for iframe element")}function r(a){if(typeof a.contentWindow!=F)return a.contentWindow;if(typeof a.contentDocument!=F)return a.contentDocument.defaultView;throw b.createError("getIframeWindow: No Window object found for iframe element")}
|
65
|
+
// This looks bad. Is it worth it?
|
66
|
+
function s(a){return a&&G.isHostMethod(a,"setTimeout")&&G.isHostObject(a,"document")}function t(a,b,c){var d;if(a?G.isHostProperty(a,"nodeType")?d=1==a.nodeType&&"iframe"==a.tagName.toLowerCase()?q(a):o(a):s(a)&&(d=a.document):d=document,!d)throw b.createError(c+"(): Parameter must be a Window object or DOM node");return d}function u(a){for(var b;b=a.parentNode;)a=b;return a}function v(a,c,d,f){
|
67
|
+
// See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
|
68
|
+
var h,i,k,l,m;if(a==d)
|
69
|
+
// Case 1: nodes are the same
|
70
|
+
return c===f?0:c<f?-1:1;if(h=j(d,a,!0))
|
71
|
+
// Case 2: node C (container B or an ancestor) is a child node of A
|
72
|
+
return c<=e(h)?-1:1;if(h=j(a,d,!0))
|
73
|
+
// Case 3: node C (container A or an ancestor) is a child node of B
|
74
|
+
return e(h)<f?-1:1;if(i=g(a,d),!i)throw new Error("comparePoints error: nodes have no common ancestor");if(
|
75
|
+
// Case 4: containers are siblings or descendants of siblings
|
76
|
+
k=a===i?i:j(a,i,!0),l=d===i?i:j(d,i,!0),k===l)
|
77
|
+
// This shouldn't be possible
|
78
|
+
throw b.createError("comparePoints got to case 4 and childA and childB are the same!");for(m=i.firstChild;m;){if(m===k)return-1;if(m===l)return 1;m=m.nextSibling}}function w(a){var b;try{return b=a.parentNode,!1}catch(a){return!0}}/*----------------------------------------------------------------------------------------------------------------*/
|
79
|
+
function x(a){if(!a)return"[No node]";if(L&&w(a))return"[Broken node]";if(k(a))return'"'+a.data+'"';if(1==a.nodeType){var b=a.id?' id="'+a.id+'"':"";return"<"+a.nodeName+b+">[index:"+e(a)+",length:"+a.childNodes.length+"]["+(a.innerHTML||"[innerHTML not supported]").slice(0,25)+"]"}return a.nodeName}function y(a){for(var b,c=o(a).createDocumentFragment();b=a.firstChild;)c.appendChild(b);return c}function z(a,b,c){var d=H(a),e=a.createElement("div");e.contentEditable=""+!!c,b&&(e.innerHTML=b);
|
80
|
+
// Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292)
|
81
|
+
var f=d.firstChild;return f?d.insertBefore(e,f):d.appendChild(e),e}function A(a){return a.parentNode.removeChild(a)}function B(a){this.root=a,this._next=a}function C(a){return new B(a)}function D(a,b){this.node=a,this.offset=b}function E(a){this.code=this[a],this.codeName=a,this.message="DOMException: "+this.codeName}var F="undefined",G=a.util,H=G.getBody;
|
82
|
+
// Perform feature tests
|
83
|
+
G.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||b.fail("document missing a Node creation method"),G.isHostMethod(document,"getElementsByTagName")||b.fail("document missing getElementsByTagName method");var I=document.createElement("div");G.areHostMethods(I,["insertBefore","appendChild","cloneNode"]||!G.areHostObjects(I,["previousSibling","nextSibling","childNodes","parentNode"]))||b.fail("Incomplete Element implementation"),
|
84
|
+
// innerHTML is required for Range's createContextualFragment method
|
85
|
+
G.isHostProperty(I,"innerHTML")||b.fail("Element is missing innerHTML property");var J=document.createTextNode("test");G.areHostMethods(J,["splitText","deleteData","insertData","appendData","cloneNode"]||!G.areHostObjects(I,["previousSibling","nextSibling","childNodes","parentNode"])||!G.areHostProperties(J,["data"]))||b.fail("Incomplete Text Node implementation");/*----------------------------------------------------------------------------------------------------------------*/
|
86
|
+
// Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
|
87
|
+
// able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
|
88
|
+
// contains just the document as a single element and the value searched for is the document.
|
89
|
+
var K=/*Array.prototype.indexOf ?
|
90
|
+
function(arr, val) {
|
91
|
+
return arr.indexOf(val) > -1;
|
92
|
+
}:*/
|
93
|
+
function(a,b){for(var c=a.length;c--;)if(a[c]===b)return!0;return!1},L=!1;!function(){var b=document.createElement("b");b.innerHTML="1";var c=b.firstChild;b.innerHTML="<br />",L=w(c),a.features.crashyTextNodes=L}();var M;typeof window.getComputedStyle!=F?M=function(a,b){return p(a).getComputedStyle(a,null)[b]}:typeof document.documentElement.currentStyle!=F?M=function(a,b){return a.currentStyle?a.currentStyle[b]:""}:b.fail("No means of obtaining computed style properties found"),B.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var a,b,c=this._current=this._next;if(this._current)if(a=c.firstChild)this._next=a;else{for(b=null;c!==this.root&&!(b=c.nextSibling);)c=c.parentNode;this._next=b}return this._current},detach:function(){this._current=this._next=this.root=null}},D.prototype={equals:function(a){return!!a&&this.node===a.node&&this.offset==a.offset},inspect:function(){return"[DomPosition("+x(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},E.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,INVALID_NODE_TYPE_ERR:24},E.prototype.toString=function(){return this.message},a.dom={arrayContains:K,isHtmlNamespace:c,parentElement:d,getNodeIndex:e,getNodeLength:f,getCommonAncestor:g,isAncestorOf:h,isOrIsAncestorOf:i,getClosestAncestorIn:j,isCharacterDataNode:k,isTextOrCommentNode:l,insertAfter:m,splitDataNode:n,getDocument:o,getWindow:p,getIframeWindow:r,getIframeDocument:q,getBody:H,isWindow:s,getContentDocument:t,getRootContainer:u,comparePoints:v,isBrokenNode:w,inspectNode:x,getComputedStyleProperty:M,createTestElement:z,removeNode:A,fragmentFromNodeChildren:y,createIterator:C,DomPosition:D},a.DOMException=E}),/*----------------------------------------------------------------------------------------------------------------*/
|
94
|
+
// Pure JavaScript implementation of DOM Range
|
95
|
+
H.createCoreModule("DomRange",["DomUtil"],function(a,b){/*----------------------------------------------------------------------------------------------------------------*/
|
96
|
+
// Utility functions
|
97
|
+
function c(a,b){return 3!=a.nodeType&&(P(a,b.startContainer)||P(a,b.endContainer))}function d(a){return a.document||Q(a.startContainer)}function e(a){return W(a.startContainer)}function f(a){return new L(a.parentNode,O(a))}function g(a){return new L(a.parentNode,O(a)+1)}function h(a,b,c){var d=11==a.nodeType?a.firstChild:a;return N(b)?c==b.length?J.insertAfter(a,b):b.parentNode.insertBefore(a,0==c?b:S(b,c)):c>=b.childNodes.length?b.appendChild(a):b.insertBefore(a,b.childNodes[c]),d}function i(a,b,c){if(z(a),z(b),d(b)!=d(a))throw new M("WRONG_DOCUMENT_ERR");var e=R(a.startContainer,a.startOffset,b.endContainer,b.endOffset),f=R(a.endContainer,a.endOffset,b.startContainer,b.startOffset);return c?e<=0&&f>=0:e<0&&f>0}function j(a){for(var b,c,e,f=d(a.range).createDocumentFragment();c=a.next();){if(b=a.isPartiallySelectedSubtree(),c=c.cloneNode(!b),b&&(e=a.getSubtreeIterator(),c.appendChild(j(e)),e.detach()),10==c.nodeType)// DocumentType
|
98
|
+
throw new M("HIERARCHY_REQUEST_ERR");f.appendChild(c)}return f}function k(a,b,c){var d,e;c=c||{stop:!1};for(var f,g;f=a.next();)if(a.isPartiallySelectedSubtree()){if(b(f)===!1)return void(c.stop=!0);if(
|
99
|
+
// The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
|
100
|
+
// the node selected by the Range.
|
101
|
+
g=a.getSubtreeIterator(),k(g,b,c),g.detach(),c.stop)return}else for(
|
102
|
+
// The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
|
103
|
+
// descendants
|
104
|
+
d=J.createIterator(f);e=d.next();)if(b(e)===!1)return void(c.stop=!0)}function l(a){for(var b;a.next();)a.isPartiallySelectedSubtree()?(b=a.getSubtreeIterator(),l(b),b.detach()):a.remove()}function m(a){for(var b,c,e=d(a.range).createDocumentFragment();b=a.next();){if(a.isPartiallySelectedSubtree()?(b=b.cloneNode(!1),c=a.getSubtreeIterator(),b.appendChild(m(c)),c.detach()):a.remove(),10==b.nodeType)// DocumentType
|
105
|
+
throw new M("HIERARCHY_REQUEST_ERR");e.appendChild(b)}return e}function n(a,b,c){var d,e=!(!b||!b.length),f=!!c;e&&(d=new RegExp("^("+b.join("|")+")$"));var g=[];return k(new p(a,!1),function(b){if((!e||d.test(b.nodeType))&&(!f||c(b))){
|
106
|
+
// Don't include a boundary container if it is a character data node and the range does not contain any
|
107
|
+
// of its character data. See issue 190.
|
108
|
+
var h=a.startContainer;if(b!=h||!N(h)||a.startOffset!=h.length){var i=a.endContainer;b==i&&N(i)&&0==a.endOffset||g.push(b)}}}),g}function o(a){var b="undefined"==typeof a.getName?"Range":a.getName();return"["+b+"("+J.inspectNode(a.startContainer)+":"+a.startOffset+", "+J.inspectNode(a.endContainer)+":"+a.endOffset+")]"}/*----------------------------------------------------------------------------------------------------------------*/
|
109
|
+
// RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
|
110
|
+
function p(a,b){if(this.range=a,this.clonePartiallySelectedTextNodes=b,!a.collapsed){this.sc=a.startContainer,this.so=a.startOffset,this.ec=a.endContainer,this.eo=a.endOffset;var c=a.commonAncestorContainer;this.sc===this.ec&&N(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc!==c||N(this.sc)?T(this.sc,c,!0):this.sc.childNodes[this.so],this._last=this.ec!==c||N(this.ec)?T(this.ec,c,!0):this.ec.childNodes[this.eo-1])}}function q(a){return function(b,c){for(var d,e=c?b:b.parentNode;e;){if(d=e.nodeType,V(a,d))return e;e=e.parentNode}return null}}function r(a,b){if(ea(a,b))throw new M("INVALID_NODE_TYPE_ERR")}function s(a,b){if(!V(b,a.nodeType))throw new M("INVALID_NODE_TYPE_ERR")}function t(a,b){if(b<0||b>(N(a)?a.length:a.childNodes.length))throw new M("INDEX_SIZE_ERR")}function u(a,b){if(ca(a,!0)!==ca(b,!0))throw new M("WRONG_DOCUMENT_ERR")}function v(a){if(da(a,!0))throw new M("NO_MODIFICATION_ALLOWED_ERR")}function w(a,b){if(!a)throw new M(b)}function x(a,b){return b<=(N(a)?a.length:a.childNodes.length)}function y(a){return!!a.startContainer&&!!a.endContainer&&!(X&&(J.isBrokenNode(a.startContainer)||J.isBrokenNode(a.endContainer)))&&W(a.startContainer)==W(a.endContainer)&&x(a.startContainer,a.startOffset)&&x(a.endContainer,a.endOffset)}function z(a){if(!y(a))throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: ("+a.inspect()+")")}function A(a,b){z(a);var c=a.startContainer,d=a.startOffset,e=a.endContainer,f=a.endOffset,g=c===e;N(e)&&f>0&&f<e.length&&S(e,f,b),N(c)&&d>0&&d<c.length&&(c=S(c,d,b),g?(f-=d,e=c):e==c.parentNode&&f>=O(c)&&f++,d=0),a.setStartAndEnd(c,d,e,f)}function B(a){z(a);var b=a.commonAncestorContainer.parentNode.cloneNode(!1);return b.appendChild(a.cloneContents()),b.innerHTML}function C(a){a.START_TO_START=ja,a.START_TO_END=ka,a.END_TO_END=la,a.END_TO_START=ma,a.NODE_BEFORE=na,a.NODE_AFTER=oa,a.NODE_BEFORE_AND_AFTER=pa,a.NODE_INSIDE=qa}function D(a){C(a),C(a.prototype)}function E(a,b){return function(){z(this);var c,d,e=this.startContainer,f=this.startOffset,h=this.commonAncestorContainer,i=new p(this,!0);e!==h&&(c=T(e,h,!0),d=g(c),e=d.node,f=d.offset),
|
111
|
+
// Check none of the range is read-only
|
112
|
+
k(i,v),i.reset();
|
113
|
+
// Remove the content
|
114
|
+
var j=a(i);
|
115
|
+
// Move to the new position
|
116
|
+
return i.detach(),b(this,e,f,e,f),j}}function F(b,d){function e(a,b){return function(c){s(c,Z),s(W(c),$);var d=(a?f:g)(c);(b?h:i)(this,d.node,d.offset)}}function h(a,b,c){var e=a.endContainer,f=a.endOffset;b===a.startContainer&&c===a.startOffset||(
|
117
|
+
// Check the root containers of the range and the new boundary, and also check whether the new boundary
|
118
|
+
// is after the current end. In either case, collapse the range to the new position
|
119
|
+
W(b)==W(e)&&1!=R(b,c,e,f)||(e=b,f=c),d(a,b,c,e,f))}function i(a,b,c){var e=a.startContainer,f=a.startOffset;b===a.endContainer&&c===a.endOffset||(
|
120
|
+
// Check the root containers of the range and the new boundary, and also check whether the new boundary
|
121
|
+
// is after the current end. In either case, collapse the range to the new position
|
122
|
+
W(b)==W(e)&&R(b,c,e,f)!=-1||(e=b,f=c),d(a,e,f,b,c))}
|
123
|
+
// Set up inheritance
|
124
|
+
var j=function(){};j.prototype=a.rangePrototype,b.prototype=new j,K.extend(b.prototype,{setStart:function(a,b){r(a,!0),t(a,b),h(this,a,b)},setEnd:function(a,b){r(a,!0),t(a,b),i(this,a,b)},/**
|
125
|
+
* Convenience method to set a range's start and end boundaries. Overloaded as follows:
|
126
|
+
* - Two parameters (node, offset) creates a collapsed range at that position
|
127
|
+
* - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
|
128
|
+
* startOffset and ending at endOffset
|
129
|
+
* - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
|
130
|
+
* startNode and ending at endOffset in endNode
|
131
|
+
*/
|
132
|
+
setStartAndEnd:function(){var a=arguments,b=a[0],c=a[1],e=b,f=c;switch(a.length){case 3:f=a[2];break;case 4:e=a[2],f=a[3]}d(this,b,c,e,f)},setBoundary:function(a,b,c){this["set"+(c?"Start":"End")](a,b)},setStartBefore:e(!0,!0),setStartAfter:e(!1,!0),setEndBefore:e(!0,!1),setEndAfter:e(!1,!1),collapse:function(a){z(this),a?d(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):d(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(a){r(a,!0),d(this,a,0,a,U(a))},selectNode:function(a){r(a,!1),s(a,Z);var b=f(a),c=g(a);d(this,b.node,b.offset,c.node,c.offset)},extractContents:E(m,d),deleteContents:E(l,d),canSurroundContents:function(){z(this),v(this.startContainer),v(this.endContainer);
|
133
|
+
// Check if the contents can be surrounded. Specifically, this means whether the range partially selects
|
134
|
+
// no non-text nodes.
|
135
|
+
var a=new p(this,!0),b=a._first&&c(a._first,this)||a._last&&c(a._last,this);return a.detach(),!b},splitBoundaries:function(){A(this)},splitBoundariesPreservingPositions:function(a){A(this,a)},normalizeBoundaries:function(){z(this);var a,b=this.startContainer,c=this.startOffset,e=this.endContainer,f=this.endOffset,g=function(a){var b=a.nextSibling;b&&b.nodeType==a.nodeType&&(e=a,f=a.length,a.appendData(b.data),Y(b))},h=function(a){var d=a.previousSibling;if(d&&d.nodeType==a.nodeType){b=a;var g=a.length;if(c=d.length,a.insertData(0,d.data),Y(d),b==e)f+=c,e=b;else if(e==a.parentNode){var h=O(a);f==h?(e=a,f=g):f>h&&f--}}},i=!0;if(N(e))f==e.length?g(e):0==f&&(a=e.previousSibling,a&&a.nodeType==e.nodeType&&(f=a.length,b==e&&(i=!1),a.appendData(e.data),Y(e),e=a));else{if(f>0){var j=e.childNodes[f-1];j&&N(j)&&g(j)}i=!this.collapsed}if(i){if(N(b))0==c?h(b):c==b.length&&(a=b.nextSibling,a&&a.nodeType==b.nodeType&&(e==a&&(e=b,f+=b.length),b.appendData(a.data),Y(a)));else if(c<b.childNodes.length){var k=b.childNodes[c];k&&N(k)&&h(k)}}else b=e,c=f;d(this,b,c,e,f)},collapseToPoint:function(a,b){r(a,!0),t(a,b),this.setStartAndEnd(a,b)}}),D(b)}/*----------------------------------------------------------------------------------------------------------------*/
|
136
|
+
// Updates commonAncestorContainer and collapsed after boundary change
|
137
|
+
function G(a){a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset,a.commonAncestorContainer=a.collapsed?a.startContainer:J.getCommonAncestor(a.startContainer,a.endContainer)}function H(a,b,c,d,e){a.startContainer=b,a.startOffset=c,a.endContainer=d,a.endOffset=e,a.document=J.getDocument(b),G(a)}function I(a){this.startContainer=a,this.startOffset=0,this.endContainer=a,this.endOffset=0,this.document=a,G(this)}var J=a.dom,K=a.util,L=J.DomPosition,M=a.DOMException,N=J.isCharacterDataNode,O=J.getNodeIndex,P=J.isOrIsAncestorOf,Q=J.getDocument,R=J.comparePoints,S=J.splitDataNode,T=J.getClosestAncestorIn,U=J.getNodeLength,V=J.arrayContains,W=J.getRootContainer,X=a.features.crashyTextNodes,Y=J.removeNode;p.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:!1,reset:function(){this._current=null,this._next=this._first},hasNext:function(){return!!this._next},next:function(){
|
138
|
+
// Move to next node
|
139
|
+
var a=this._current=this._next;
|
140
|
+
// Check for partially selected text nodes
|
141
|
+
return a&&(this._next=a!==this._last?a.nextSibling:null,N(a)&&this.clonePartiallySelectedTextNodes&&(a===this.ec&&(a=a.cloneNode(!0)).deleteData(this.eo,a.length-this.eo),this._current===this.sc&&(a=a.cloneNode(!0)).deleteData(0,this.so))),a},remove:function(){var a,b,c=this._current;!N(c)||c!==this.sc&&c!==this.ec?c.parentNode&&Y(c):(a=c===this.sc?this.so:0,b=c===this.ec?this.eo:c.length,a!=b&&c.deleteData(a,b-a))},
|
142
|
+
// Checks if the current node is partially selected
|
143
|
+
isPartiallySelectedSubtree:function(){var a=this._current;return c(a,this.range)},getSubtreeIterator:function(){var a;if(this.isSingleCharacterDataNode)a=this.range.cloneRange(),a.collapse(!1);else{a=new I(d(this.range));var b=this._current,c=b,e=0,f=b,g=U(b);P(b,this.sc)&&(c=this.sc,e=this.so),P(b,this.ec)&&(f=this.ec,g=this.eo),H(a,c,e,f,g)}return new p(a,this.clonePartiallySelectedTextNodes)},detach:function(){this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}};/*----------------------------------------------------------------------------------------------------------------*/
|
144
|
+
var Z=[1,3,4,5,7,8,10],$=[2,9,11],_=[5,6,10,12],aa=[1,3,4,5,7,8,10,11],ba=[1,3,4,5,7,8],ca=q([9,11]),da=q(_),ea=q([6,10,12]),fa=document.createElement("style"),ga=!1;try{fa.innerHTML="<b>x</b>",ga=3==fa.firstChild.nodeType}catch(a){}a.features.htmlParsingConforms=ga;var ha=ga?
|
145
|
+
// Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
|
146
|
+
// discussion and base code for this implementation at issue 67.
|
147
|
+
// Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
|
148
|
+
// Thanks to Aleks Williams.
|
149
|
+
function(a){
|
150
|
+
// "Let node the context object's start's node."
|
151
|
+
var b=this.startContainer,c=Q(b);
|
152
|
+
// "If the context object's start's node is null, raise an INVALID_STATE_ERR
|
153
|
+
// exception and abort these steps."
|
154
|
+
if(!b)throw new M("INVALID_STATE_ERR");
|
155
|
+
// "Let element be as follows, depending on node's interface:"
|
156
|
+
// Document, Document Fragment: null
|
157
|
+
var d=null;
|
158
|
+
// "If this raises an exception, then abort these steps. Otherwise, let new
|
159
|
+
// children be the nodes returned."
|
160
|
+
// "Let fragment be a new DocumentFragment."
|
161
|
+
// "Append all new children to fragment."
|
162
|
+
// "Return fragment."
|
163
|
+
// "Element: node"
|
164
|
+
// "If either element is null or element's ownerDocument is an HTML document
|
165
|
+
// and element's local name is "html" and element's namespace is the HTML
|
166
|
+
// namespace"
|
167
|
+
// "let element be a new Element with "body" as its local name and the HTML
|
168
|
+
// namespace as its namespace.""
|
169
|
+
// "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
|
170
|
+
// "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
|
171
|
+
// "In either case, the algorithm must be invoked with fragment as the input
|
172
|
+
// and element as the context element."
|
173
|
+
return 1==b.nodeType?d=b:N(b)&&(d=J.parentElement(b)),d=null===d||"HTML"==d.nodeName&&J.isHtmlNamespace(Q(d).documentElement)&&J.isHtmlNamespace(d)?c.createElement("body"):d.cloneNode(!1),d.innerHTML=a,J.fragmentFromNodeChildren(d)}:
|
174
|
+
// In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
|
175
|
+
// previous versions of Rangy used (with the exception of using a body element rather than a div)
|
176
|
+
function(a){var b=d(this),c=b.createElement("body");return c.innerHTML=a,J.fragmentFromNodeChildren(c)},ia=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],ja=0,ka=1,la=2,ma=3,na=0,oa=1,pa=2,qa=3;K.extend(a.rangePrototype,{compareBoundaryPoints:function(a,b){z(this),u(this.startContainer,b.startContainer);var c,d,e,f,g=a==ma||a==ja?"start":"end",h=a==ka||a==ja?"start":"end";return c=this[g+"Container"],d=this[g+"Offset"],e=b[h+"Container"],f=b[h+"Offset"],R(c,d,e,f)},insertNode:function(a){if(z(this),s(a,aa),v(this.startContainer),P(a,this.startContainer))throw new M("HIERARCHY_REQUEST_ERR");
|
177
|
+
// No check for whether the container of the start of the Range is of a type that does not allow
|
178
|
+
// children of the type of node: the browser's DOM implementation should do this for us when we attempt
|
179
|
+
// to add the node
|
180
|
+
var b=h(a,this.startContainer,this.startOffset);this.setStartBefore(b)},cloneContents:function(){z(this);var a,b;if(this.collapsed)return d(this).createDocumentFragment();if(this.startContainer===this.endContainer&&N(this.startContainer))return a=this.startContainer.cloneNode(!0),a.data=a.data.slice(this.startOffset,this.endOffset),b=d(this).createDocumentFragment(),b.appendChild(a),b;var c=new p(this,!0);return a=j(c),c.detach(),a},canSurroundContents:function(){z(this),v(this.startContainer),v(this.endContainer);
|
181
|
+
// Check if the contents can be surrounded. Specifically, this means whether the range partially selects
|
182
|
+
// no non-text nodes.
|
183
|
+
var a=new p(this,!0),b=a._first&&c(a._first,this)||a._last&&c(a._last,this);return a.detach(),!b},surroundContents:function(a){if(s(a,ba),!this.canSurroundContents())throw new M("INVALID_STATE_ERR");
|
184
|
+
// Extract the contents
|
185
|
+
var b=this.extractContents();
|
186
|
+
// Clear the children of the node
|
187
|
+
if(a.hasChildNodes())for(;a.lastChild;)a.removeChild(a.lastChild);
|
188
|
+
// Insert the new node and add the extracted contents
|
189
|
+
h(a,this.startContainer,this.startOffset),a.appendChild(b),this.selectNode(a)},cloneRange:function(){z(this);for(var a,b=new I(d(this)),c=ia.length;c--;)a=ia[c],b[a]=this[a];return b},toString:function(){z(this);var a=this.startContainer;if(a===this.endContainer&&N(a))return 3==a.nodeType||4==a.nodeType?a.data.slice(this.startOffset,this.endOffset):"";var b=[],c=new p(this,!0);return k(c,function(a){
|
190
|
+
// Accept only text or CDATA nodes, not comments
|
191
|
+
3!=a.nodeType&&4!=a.nodeType||b.push(a.data)}),c.detach(),b.join("")},
|
192
|
+
// The methods below are all non-standard. The following batch were introduced by Mozilla but have since
|
193
|
+
// been removed from Mozilla.
|
194
|
+
compareNode:function(a){z(this);var b=a.parentNode,c=O(a);if(!b)throw new M("NOT_FOUND_ERR");var d=this.comparePoint(b,c),e=this.comparePoint(b,c+1);return d<0?e>0?pa:na:e>0?oa:qa},comparePoint:function(a,b){return z(this),w(a,"HIERARCHY_REQUEST_ERR"),u(a,this.startContainer),R(a,b,this.startContainer,this.startOffset)<0?-1:R(a,b,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:ha,toHtml:function(){return B(this)},
|
195
|
+
// touchingIsIntersecting determines whether this method considers a node that borders a range intersects
|
196
|
+
// with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
|
197
|
+
intersectsNode:function(a,b){if(z(this),W(a)!=e(this))return!1;var c=a.parentNode,d=O(a);if(!c)return!0;var f=R(c,d,this.endContainer,this.endOffset),g=R(c,d+1,this.startContainer,this.startOffset);return b?f<=0&&g>=0:f<0&&g>0},isPointInRange:function(a,b){return z(this),w(a,"HIERARCHY_REQUEST_ERR"),u(a,this.startContainer),R(a,b,this.startContainer,this.startOffset)>=0&&R(a,b,this.endContainer,this.endOffset)<=0},
|
198
|
+
// The methods below are non-standard and invented by me.
|
199
|
+
// Sharing a boundary start-to-end or end-to-start does not count as intersection.
|
200
|
+
intersectsRange:function(a){return i(this,a,!1)},
|
201
|
+
// Sharing a boundary start-to-end or end-to-start does count as intersection.
|
202
|
+
intersectsOrTouchesRange:function(a){return i(this,a,!0)},intersection:function(a){if(this.intersectsRange(a)){var b=R(this.startContainer,this.startOffset,a.startContainer,a.startOffset),c=R(this.endContainer,this.endOffset,a.endContainer,a.endOffset),d=this.cloneRange();return b==-1&&d.setStart(a.startContainer,a.startOffset),1==c&&d.setEnd(a.endContainer,a.endOffset),d}return null},union:function(a){if(this.intersectsOrTouchesRange(a)){var b=this.cloneRange();return R(a.startContainer,a.startOffset,this.startContainer,this.startOffset)==-1&&b.setStart(a.startContainer,a.startOffset),1==R(a.endContainer,a.endOffset,this.endContainer,this.endOffset)&&b.setEnd(a.endContainer,a.endOffset),b}throw new M("Ranges do not intersect")},containsNode:function(a,b){return b?this.intersectsNode(a,!1):this.compareNode(a)==qa},containsNodeContents:function(a){return this.comparePoint(a,0)>=0&&this.comparePoint(a,U(a))<=0},containsRange:function(a){var b=this.intersection(a);return null!==b&&a.equals(b)},containsNodeText:function(a){var b=this.cloneRange();b.selectNode(a);var c=b.getNodes([3]);if(c.length>0){b.setStart(c[0],0);var d=c.pop();return b.setEnd(d,d.length),this.containsRange(b)}return this.containsNodeContents(a)},getNodes:function(a,b){return z(this),n(this,a,b)},getDocument:function(){return d(this)},collapseBefore:function(a){this.setEndBefore(a),this.collapse(!1)},collapseAfter:function(a){this.setStartAfter(a),this.collapse(!0)},getBookmark:function(b){var c=d(this),e=a.createRange(c);b=b||J.getBody(c),e.selectNodeContents(b);var f=this.intersection(e),g=0,h=0;return f&&(e.setEnd(f.startContainer,f.startOffset),g=e.toString().length,h=g+f.toString().length),{start:g,end:h,containerNode:b}},moveToBookmark:function(a){var b=a.containerNode,c=0;this.setStart(b,0),this.collapse(!0);for(var d,e,f,g,h=[b],i=!1,j=!1;!j&&(d=h.pop());)if(3==d.nodeType)e=c+d.length,!i&&a.start>=c&&a.start<=e&&(this.setStart(d,a.start-c),i=!0),i&&a.end>=c&&a.end<=e&&(this.setEnd(d,a.end-c),j=!0),c=e;else for(g=d.childNodes,f=g.length;f--;)h.push(g[f])},getName:function(){return"DomRange"},equals:function(a){return I.rangesEqual(this,a)},isValid:function(){return y(this)},inspect:function(){return o(this)},detach:function(){}}),F(I,H),K.extend(I,{rangeProperties:ia,RangeIterator:p,copyComparisonConstants:D,createPrototypeRange:F,inspect:o,toHtml:B,getRangeDocument:d,rangesEqual:function(a,b){return a.startContainer===b.startContainer&&a.startOffset===b.startOffset&&a.endContainer===b.endContainer&&a.endOffset===b.endOffset}}),a.DomRange=I}),/*----------------------------------------------------------------------------------------------------------------*/
|
203
|
+
// Wrappers for the browser's native DOM Range and/or TextRange implementation
|
204
|
+
H.createCoreModule("WrappedRange",["DomRange"],function(a,b){var c,d,e=a.dom,f=a.util,g=e.DomPosition,h=a.DomRange,i=e.getBody,j=e.getContentDocument,k=e.isCharacterDataNode;if(/*----------------------------------------------------------------------------------------------------------------*/
|
205
|
+
a.features.implementsDomRange&&
|
206
|
+
// This is a wrapper around the browser's native DOM Range. It has two aims:
|
207
|
+
// - Provide workarounds for specific browser bugs
|
208
|
+
// - provide convenient extensions, which are inherited from Rangy's DomRange
|
209
|
+
!function(){function d(a){for(var b,c=m.length;c--;)b=m[c],a[b]=a.nativeRange[b];
|
210
|
+
// Fix for broken collapsed property in IE 9.
|
211
|
+
a.collapsed=a.startContainer===a.endContainer&&a.startOffset===a.endOffset}function g(a,b,c,d,e){var f=a.startContainer!==b||a.startOffset!=c,g=a.endContainer!==d||a.endOffset!=e,h=!a.equals(a.nativeRange);
|
212
|
+
// Always set both boundaries for the benefit of IE9 (see issue 35)
|
213
|
+
(f||g||h)&&(a.setEnd(d,e),a.setStart(b,c))}var k,l,m=h.rangeProperties;c=function(a){if(!a)throw b.createError("WrappedRange: Range must be specified");this.nativeRange=a,d(this)},h.createPrototypeRange(c,g),k=c.prototype,k.selectNode=function(a){this.nativeRange.selectNode(a),d(this)},k.cloneContents=function(){return this.nativeRange.cloneContents()},
|
214
|
+
// Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
|
215
|
+
// insertNode() is never delegated to the native range.
|
216
|
+
k.surroundContents=function(a){this.nativeRange.surroundContents(a),d(this)},k.collapse=function(a){this.nativeRange.collapse(a),d(this)},k.cloneRange=function(){return new c(this.nativeRange.cloneRange())},k.refresh=function(){d(this)},k.toString=function(){return this.nativeRange.toString()};
|
217
|
+
// Create test range and node for feature detection
|
218
|
+
var n=document.createTextNode("test");i(document).appendChild(n);var o=document.createRange();/*--------------------------------------------------------------------------------------------------------*/
|
219
|
+
// Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
|
220
|
+
// correct for it
|
221
|
+
o.setStart(n,0),o.setEnd(n,0);try{o.setStart(n,1),k.setStart=function(a,b){this.nativeRange.setStart(a,b),d(this)},k.setEnd=function(a,b){this.nativeRange.setEnd(a,b),d(this)},l=function(a){return function(b){this.nativeRange[a](b),d(this)}}}catch(a){k.setStart=function(a,b){try{this.nativeRange.setStart(a,b)}catch(c){this.nativeRange.setEnd(a,b),this.nativeRange.setStart(a,b)}d(this)},k.setEnd=function(a,b){try{this.nativeRange.setEnd(a,b)}catch(c){this.nativeRange.setStart(a,b),this.nativeRange.setEnd(a,b)}d(this)},l=function(a,b){return function(c){try{this.nativeRange[a](c)}catch(d){this.nativeRange[b](c),this.nativeRange[a](c)}d(this)}}}k.setStartBefore=l("setStartBefore","setEndBefore"),k.setStartAfter=l("setStartAfter","setEndAfter"),k.setEndBefore=l("setEndBefore","setStartBefore"),k.setEndAfter=l("setEndAfter","setStartAfter"),/*--------------------------------------------------------------------------------------------------------*/
|
222
|
+
// Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
|
223
|
+
// whether the native implementation can be trusted
|
224
|
+
k.selectNodeContents=function(a){this.setStartAndEnd(a,0,e.getNodeLength(a))},/*--------------------------------------------------------------------------------------------------------*/
|
225
|
+
// Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
|
226
|
+
// constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
|
227
|
+
o.selectNodeContents(n),o.setEnd(n,3);var p=document.createRange();p.selectNodeContents(n),p.setEnd(n,4),p.setStart(n,2),o.compareBoundaryPoints(o.START_TO_END,p)==-1&&1==o.compareBoundaryPoints(o.END_TO_START,p)?
|
228
|
+
// This is the wrong way round, so correct for it
|
229
|
+
k.compareBoundaryPoints=function(a,b){return b=b.nativeRange||b,a==b.START_TO_END?a=b.END_TO_START:a==b.END_TO_START&&(a=b.START_TO_END),this.nativeRange.compareBoundaryPoints(a,b)}:k.compareBoundaryPoints=function(a,b){return this.nativeRange.compareBoundaryPoints(a,b.nativeRange||b)};/*--------------------------------------------------------------------------------------------------------*/
|
230
|
+
// Test for IE deleteContents() and extractContents() bug and correct it. See issue 107.
|
231
|
+
var q=document.createElement("div");q.innerHTML="123";var r=q.firstChild,s=i(document);s.appendChild(q),o.setStart(r,1),o.setEnd(r,2),o.deleteContents(),"13"==r.data&&(
|
232
|
+
// Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
|
233
|
+
// extractContents()
|
234
|
+
k.deleteContents=function(){this.nativeRange.deleteContents(),d(this)},k.extractContents=function(){var a=this.nativeRange.extractContents();return d(this),a}),s.removeChild(q),s=null,/*--------------------------------------------------------------------------------------------------------*/
|
235
|
+
// Test for existence of createContextualFragment and delegate to it if it exists
|
236
|
+
f.isHostMethod(o,"createContextualFragment")&&(k.createContextualFragment=function(a){return this.nativeRange.createContextualFragment(a)}),/*--------------------------------------------------------------------------------------------------------*/
|
237
|
+
// Clean up
|
238
|
+
i(document).removeChild(n),k.getName=function(){return"WrappedRange"},a.WrappedRange=c,a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),a.createRange()}}(),a.features.implementsTextRange){/*
|
239
|
+
This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
|
240
|
+
method. For example, in the following (where pipes denote the selection boundaries):
|
241
|
+
|
242
|
+
<ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
|
243
|
+
|
244
|
+
var range = document.selection.createRange();
|
245
|
+
alert(range.parentElement().id); // Should alert "ul" but alerts "b"
|
246
|
+
|
247
|
+
This method returns the common ancestor node of the following:
|
248
|
+
- the parentElement() of the textRange
|
249
|
+
- the parentElement() of the textRange after calling collapse(true)
|
250
|
+
- the parentElement() of the textRange after calling collapse(false)
|
251
|
+
*/
|
252
|
+
var l=function(a){var b=a.parentElement(),c=a.duplicate();c.collapse(!0);var d=c.parentElement();c=a.duplicate(),c.collapse(!1);var f=c.parentElement(),g=d==f?d:e.getCommonAncestor(d,f);return g==b?g:e.getCommonAncestor(b,g)},m=function(a){return 0==a.compareEndPoints("StartToEnd",a)},n=function(a,b,c,d,f){var h=a.duplicate();h.collapse(c);var i=h.parentElement();
|
253
|
+
// Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
|
254
|
+
// similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
|
255
|
+
if(
|
256
|
+
// Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
|
257
|
+
// check for that
|
258
|
+
e.isOrIsAncestorOf(b,i)||(i=b),!i.canHaveHTML){var j=new g(i.parentNode,e.getNodeIndex(i));return{boundaryPosition:j,nodeInfo:{nodeIndex:j.offset,containerElement:j.node}}}var l=e.getDocument(i).createElement("span");
|
259
|
+
// Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
|
260
|
+
// Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
|
261
|
+
l.parentNode&&e.removeNode(l);for(var m,n,o,p,q,r=c?"StartToStart":"StartToEnd",s=f&&f.containerElement==i?f.nodeIndex:0,t=i.childNodes.length,u=t,v=u;;){if(v==t?i.appendChild(l):i.insertBefore(l,i.childNodes[v]),h.moveToElementText(l),m=h.compareEndPoints(r,a),0==m||s==u)break;if(m==-1){if(u==s+1)
|
262
|
+
// We know the endth child node is after the range boundary, so we must be done.
|
263
|
+
break;s=v}else u=u==s+1?s:v;v=Math.floor((s+u)/2),i.removeChild(l)}if(
|
264
|
+
// We've now reached or gone past the boundary of the text range we're interested in
|
265
|
+
// so have identified the node we want
|
266
|
+
q=l.nextSibling,m==-1&&q&&k(q)){
|
267
|
+
// This is a character data node (text, comment, cdata). The working range is collapsed at the start of
|
268
|
+
// the node containing the text range's boundary, so we move the end of the working range to the
|
269
|
+
// boundary point and measure the length of its text to get the boundary's offset within the node.
|
270
|
+
h.setEndPoint(c?"EndToStart":"EndToEnd",a);var w;if(/[\r\n]/.test(q.data)){/*
|
271
|
+
For the particular case of a boundary within a text node containing rendered line breaks (within a
|
272
|
+
<pre> element, for example), we need a slightly complicated approach to get the boundary's offset in
|
273
|
+
IE. The facts:
|
274
|
+
|
275
|
+
- Each line break is represented as \r in the text node's data/nodeValue properties
|
276
|
+
- Each line break is represented as \r\n in the TextRange's 'text' property
|
277
|
+
- The 'text' property of the TextRange does not contain trailing line breaks
|
278
|
+
|
279
|
+
To get round the problem presented by the final fact above, we can use the fact that TextRange's
|
280
|
+
moveStart() and moveEnd() methods return the actual number of characters moved, which is not
|
281
|
+
necessarily the same as the number of characters it was instructed to move. The simplest approach is
|
282
|
+
to use this to store the characters moved when moving both the start and end of the range to the
|
283
|
+
start of the document body and subtracting the start offset from the end offset (the
|
284
|
+
"move-negative-gazillion" method). However, this is extremely slow when the document is large and
|
285
|
+
the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
|
286
|
+
the end of the document) has the same problem.
|
287
|
+
|
288
|
+
Another approach that works is to use moveStart() to move the start boundary of the range up to the
|
289
|
+
end boundary one character at a time and incrementing a counter with the value returned by the
|
290
|
+
moveStart() call. However, the check for whether the start boundary has reached the end boundary is
|
291
|
+
expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
|
292
|
+
by the location of the range within the document).
|
293
|
+
|
294
|
+
The approach used below is a hybrid of the two methods above. It uses the fact that a string
|
295
|
+
containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
|
296
|
+
be longer than the text of the TextRange, so the start of the range is moved that length initially
|
297
|
+
and then a character at a time to make up for any trailing line breaks not contained in the 'text'
|
298
|
+
property. This has good performance in most situations compared to the previous two methods.
|
299
|
+
*/
|
300
|
+
var x=h.duplicate(),y=x.text.replace(/\r\n/g,"\r").length;for(w=x.moveStart("character",y);(m=x.compareEndPoints("StartToEnd",x))==-1;)w++,x.moveStart("character",1)}else w=h.text.length;p=new g(q,w)}else
|
301
|
+
// If the boundary immediately follows a character data node and this is the end boundary, we should favour
|
302
|
+
// a position within that, and likewise for a start boundary preceding a character data node
|
303
|
+
n=(d||!c)&&l.previousSibling,o=(d||c)&&l.nextSibling,p=o&&k(o)?new g(o,0):n&&k(n)?new g(n,n.data.length):new g(i,e.getNodeIndex(l));
|
304
|
+
// Clean up
|
305
|
+
return e.removeNode(l),{boundaryPosition:p,nodeInfo:{nodeIndex:v,containerElement:i}}},o=function(a,b){var c,d,f,g,h=a.offset,j=e.getDocument(a.node),l=i(j).createTextRange(),m=k(a.node);
|
306
|
+
// Position the range immediately before the node containing the boundary
|
307
|
+
// Making the working element non-empty element persuades IE to consider the TextRange boundary to be within
|
308
|
+
// the element rather than immediately before or after it
|
309
|
+
// insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
|
310
|
+
// for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
|
311
|
+
// Clean up
|
312
|
+
// Move the working range to the text offset, if required
|
313
|
+
return m?(c=a.node,d=c.parentNode):(g=a.node.childNodes,c=h<g.length?g[h]:null,d=a.node),f=j.createElement("span"),f.innerHTML="&#feff;",c?d.insertBefore(f,c):d.appendChild(f),l.moveToElementText(f),l.collapse(!b),d.removeChild(f),m&&l[b?"moveStart":"moveEnd"]("character",h),l};/*------------------------------------------------------------------------------------------------------------*/
|
314
|
+
// This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
|
315
|
+
// prototype
|
316
|
+
d=function(a){this.textRange=a,this.refresh()},d.prototype=new h(document),d.prototype.refresh=function(){var a,b,c,d=l(this.textRange);m(this.textRange)?b=a=n(this.textRange,d,!0,!0).boundaryPosition:(c=n(this.textRange,d,!0,!1),a=c.boundaryPosition,
|
317
|
+
// An optimization used here is that if the start and end boundaries have the same parent element, the
|
318
|
+
// search scope for the end boundary can be limited to exclude the portion of the element that precedes
|
319
|
+
// the start boundary
|
320
|
+
b=n(this.textRange,d,!1,!1,c.nodeInfo).boundaryPosition),this.setStart(a.node,a.offset),this.setEnd(b.node,b.offset)},d.prototype.getName=function(){return"WrappedTextRange"},h.copyComparisonConstants(d);var p=function(a){if(a.collapsed)return o(new g(a.startContainer,a.startOffset),!0);var b=o(new g(a.startContainer,a.startOffset),!0),c=o(new g(a.endContainer,a.endOffset),!1),d=i(h.getRangeDocument(a)).createTextRange();return d.setEndPoint("StartToStart",b),d.setEndPoint("EndToEnd",c),d};
|
321
|
+
// IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
|
322
|
+
// implementation to use by default.
|
323
|
+
if(d.rangeToTextRange=p,d.prototype.toTextRange=function(){return p(this)},a.WrappedTextRange=d,!a.features.implementsDomRange||a.config.preferTextRange){
|
324
|
+
// Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
|
325
|
+
var q=function(a){return a("return this;")()}(Function);"undefined"==typeof q.Range&&(q.Range=d),a.createNativeRange=function(a){return a=j(a,b,"createNativeRange"),i(a).createTextRange()},a.WrappedRange=d}}a.createRange=function(c){return c=j(c,b,"createRange"),new a.WrappedRange(a.createNativeRange(c))},a.createRangyRange=function(a){return a=j(a,b,"createRangyRange"),new h(a)},f.createAliasForDeprecatedMethod(a,"createIframeRange","createRange"),f.createAliasForDeprecatedMethod(a,"createIframeRangyRange","createRangyRange"),a.addShimListener(function(b){var c=b.document;"undefined"==typeof c.createRange&&(c.createRange=function(){return a.createRange(c)}),c=b=null})}),/*----------------------------------------------------------------------------------------------------------------*/
|
326
|
+
// This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
|
327
|
+
// in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
|
328
|
+
H.createCoreModule("WrappedSelection",["DomRange","WrappedRange"],function(a,b){
|
329
|
+
// Utility function to support direction parameters in the API that may be a string ("backward", "backwards",
|
330
|
+
// "forward" or "forwards") or a Boolean (true for backwards).
|
331
|
+
function c(a){return"string"==typeof a?/^backward(s)?$/i.test(a):!!a}function d(a,c){if(a){if(C.isWindow(a))return a;if(a instanceof r)return a.win;var d=C.getContentDocument(a,b,c);return C.getWindow(d)}return window}function e(a){return d(a,"getWinSelection").getSelection()}function f(a){return d(a,"getDocSelection").document.selection}function g(a){var b=!1;return a.anchorNode&&(b=1==C.comparePoints(a.anchorNode,a.anchorOffset,a.focusNode,a.focusOffset)),b}function h(a,b,c){var d=c?"end":"start",e=c?"start":"end";a.anchorNode=b[d+"Container"],a.anchorOffset=b[d+"Offset"],a.focusNode=b[e+"Container"],a.focusOffset=b[e+"Offset"]}function i(a){var b=a.nativeSelection;a.anchorNode=b.anchorNode,a.anchorOffset=b.anchorOffset,a.focusNode=b.focusNode,a.focusOffset=b.focusOffset}function j(a){a.anchorNode=a.focusNode=null,a.anchorOffset=a.focusOffset=0,a.rangeCount=0,a.isCollapsed=!0,a._ranges.length=0}function k(b){var c;return b instanceof F?(c=a.createNativeRange(b.getDocument()),c.setEnd(b.endContainer,b.endOffset),c.setStart(b.startContainer,b.startOffset)):b instanceof G?c=b.nativeRange:J.implementsDomRange&&b instanceof C.getWindow(b.startContainer).Range&&(c=b),c}function l(a){if(!a.length||1!=a[0].nodeType)return!1;for(var b=1,c=a.length;b<c;++b)if(!C.isAncestorOf(a[0],a[b]))return!1;return!0}function m(a){var c=a.getNodes();if(!l(c))throw b.createError("getSingleElementFromRange: range "+a.inspect()+" did not consist of a single element");return c[0]}
|
332
|
+
// Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
|
333
|
+
function n(a){return!!a&&"undefined"!=typeof a.text}function o(a,b){
|
334
|
+
// Create a Range from the selected TextRange
|
335
|
+
var c=new G(b);a._ranges=[c],h(a,c,!1),a.rangeCount=1,a.isCollapsed=c.collapsed}function p(b){if(
|
336
|
+
// Update the wrapped selection based on what's now in the native selection
|
337
|
+
b._ranges.length=0,"None"==b.docSelection.type)j(b);else{var c=b.docSelection.createRange();if(n(c))
|
338
|
+
// This case (where the selection type is "Control" and calling createRange() on the selection returns
|
339
|
+
// a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
|
340
|
+
// ControlRange have been removed from the ControlRange and removed from the document.
|
341
|
+
o(b,c);else{b.rangeCount=c.length;for(var d,e=L(c.item(0)),f=0;f<b.rangeCount;++f)d=a.createRange(e),d.selectNode(c.item(f)),b._ranges.push(d);b.isCollapsed=1==b.rangeCount&&b._ranges[0].collapsed,h(b,b._ranges[b.rangeCount-1],!1)}}}function q(a,c){for(var d=a.docSelection.createRange(),e=m(c),f=L(d.item(0)),g=M(f).createControlRange(),h=0,i=d.length;h<i;++h)g.add(d.item(h));try{g.add(e)}catch(a){throw b.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)")}g.select(),
|
342
|
+
// Update the wrapped selection based on what's now in the native selection
|
343
|
+
p(a)}function r(a,b,c){this.nativeSelection=a,this.docSelection=b,this._ranges=[],this.win=c,this.refresh()}function s(a){a.win=a.anchorNode=a.focusNode=a._ranges=null,a.rangeCount=a.anchorOffset=a.focusOffset=0,a.detached=!0}function t(a,b){for(var c,d,e=ba.length;e--;)if(c=ba[e],d=c.selection,"deleteAll"==b)s(d);else if(c.win==a)return"delete"==b?(ba.splice(e,1),!0):d;return"deleteAll"==b&&(ba.length=0),null}function u(a,c){for(var d,e=L(c[0].startContainer),f=M(e).createControlRange(),g=0,h=c.length;g<h;++g){d=m(c[g]);try{f.add(d)}catch(a){throw b.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)")}}f.select(),
|
344
|
+
// Update the wrapped selection based on what's now in the native selection
|
345
|
+
p(a)}function v(a,b){if(a.win.document!=L(b))throw new H("WRONG_DOCUMENT_ERR")}function w(b){return function(c,d){var e;this.rangeCount?(e=this.getRangeAt(0),e["set"+(b?"Start":"End")](c,d)):(e=a.createRange(this.win.document),e.setStartAndEnd(c,d)),this.setSingleRange(e,this.isBackward())}}function x(a){var b=[],c=new I(a.anchorNode,a.anchorOffset),d=new I(a.focusNode,a.focusOffset),e="function"==typeof a.getName?a.getName():"Selection";if("undefined"!=typeof a.rangeCount)for(var f=0,g=a.rangeCount;f<g;++f)b[f]=F.inspect(a.getRangeAt(f));return"["+e+"(Ranges: "+b.join(", ")+")(anchor: "+c.inspect()+", focus: "+d.inspect()+"]"}a.config.checkSelectionRanges=!0;var y,z,A="boolean",B="number",C=a.dom,D=a.util,E=D.isHostMethod,F=a.DomRange,G=a.WrappedRange,H=a.DOMException,I=C.DomPosition,J=a.features,K="Control",L=C.getDocument,M=C.getBody,N=F.rangesEqual,O=E(window,"getSelection"),P=D.isHostObject(document,"selection");J.implementsWinGetSelection=O,J.implementsDocSelection=P;var Q=P&&(!O||a.config.preferTextRange);if(Q)y=f,a.isSelectionValid=function(a){var b=d(a,"isSelectionValid").document,c=b.selection;
|
346
|
+
// Check whether the selection TextRange is actually contained within the correct document
|
347
|
+
return"None"!=c.type||L(c.createRange().parentElement())==b};else{if(!O)return b.fail("Neither document.selection or window.getSelection() detected."),!1;y=e,a.isSelectionValid=function(){return!0}}a.getNativeSelection=y;var R=y();
|
348
|
+
// In Firefox, the selection is null in an iframe with display: none. See issue #138.
|
349
|
+
if(!R)return b.fail("Native selection was null (possibly issue 138?)"),!1;var S=a.createNativeRange(document),T=M(document),U=D.areHostProperties(R,["anchorNode","focusNode","anchorOffset","focusOffset"]);J.selectionHasAnchorAndFocus=U;
|
350
|
+
// Test for existence of native selection extend() method
|
351
|
+
var V=E(R,"extend");J.selectionHasExtend=V;
|
352
|
+
// Test if rangeCount exists
|
353
|
+
var W=typeof R.rangeCount==B;J.selectionHasRangeCount=W;var X=!1,Y=!0,Z=V?function(b,c){var d=F.getRangeDocument(c),e=a.createRange(d);e.collapseToPoint(c.endContainer,c.endOffset),b.addRange(k(e)),b.extend(c.startContainer,c.startOffset)}:null;D.areHostMethods(R,["addRange","getRangeAt","removeAllRanges"])&&typeof R.rangeCount==B&&J.implementsDomRange&&!function(){
|
354
|
+
// Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
|
355
|
+
// performed on the current document's selection. See issue 109.
|
356
|
+
// Note also that if a selection previously existed, it is wiped and later restored by these tests. This
|
357
|
+
// will result in the selection direction begin reversed if the original selection was backwards and the
|
358
|
+
// browser does not support setting backwards selections (Internet Explorer, I'm looking at you).
|
359
|
+
var b=window.getSelection();if(b){for(var c=b.rangeCount,d=c>1,e=[],f=g(b),h=0;h<c;++h)e[h]=b.getRangeAt(h);
|
360
|
+
// Create some test elements
|
361
|
+
var i=C.createTestElement(document,"",!1),j=i.appendChild(document.createTextNode(" ")),k=document.createRange();
|
362
|
+
// Test whether the native selection is capable of supporting multiple ranges.
|
363
|
+
if(k.setStart(j,1),k.collapse(!0),b.removeAllRanges(),b.addRange(k),Y=1==b.rangeCount,b.removeAllRanges(),!d){
|
364
|
+
// Doing the original feature test here in Chrome 36 (and presumably later versions) prints a
|
365
|
+
// console error of "Discontiguous selection is not supported." that cannot be suppressed. There's
|
366
|
+
// nothing we can do about this while retaining the feature test so we have to resort to a browser
|
367
|
+
// sniff. I'm not happy about it. See
|
368
|
+
// https://code.google.com/p/chromium/issues/detail?id=399791
|
369
|
+
var l=window.navigator.appVersion.match(/Chrome\/(.*?) /);if(l&&parseInt(l[1])>=36)X=!1;else{var m=k.cloneRange();k.setStart(j,0),m.setEnd(j,3),m.setStart(j,2),b.addRange(k),b.addRange(m),X=2==b.rangeCount}}for(
|
370
|
+
// Clean up
|
371
|
+
C.removeNode(i),b.removeAllRanges(),h=0;h<c;++h)0==h&&f?Z?Z(b,e[h]):(a.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"),b.addRange(e[h])):b.addRange(e[h])}}(),J.selectionSupportsMultipleRanges=X,J.collapsedNonEditableSelectionsSupported=Y;
|
372
|
+
// ControlRanges
|
373
|
+
var $,_=!1;T&&E(T,"createControlRange")&&($=T.createControlRange(),D.areHostProperties($,["item","add"])&&(_=!0)),J.implementsControlRange=_,
|
374
|
+
// Selection collapsedness
|
375
|
+
z=U?function(a){return a.anchorNode===a.focusNode&&a.anchorOffset===a.focusOffset}:function(a){return!!a.rangeCount&&a.getRangeAt(a.rangeCount-1).collapsed};var aa;E(R,"getRangeAt")?
|
376
|
+
// try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
|
377
|
+
// Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
|
378
|
+
// lesson to us all, especially me.
|
379
|
+
aa=function(a,b){try{return a.getRangeAt(b)}catch(a){return null}}:U&&(aa=function(b){var c=L(b.anchorNode),d=a.createRange(c);
|
380
|
+
// Handle the case when the selection was selected backwards (from the end to the start in the
|
381
|
+
// document)
|
382
|
+
return d.setStartAndEnd(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset),d.collapsed!==this.isCollapsed&&d.setStartAndEnd(b.focusNode,b.focusOffset,b.anchorNode,b.anchorOffset),d}),r.prototype=a.selectionPrototype;var ba=[],ca=function(a){
|
383
|
+
// Check if the parameter is a Rangy Selection object
|
384
|
+
if(a&&a instanceof r)return a.refresh(),a;a=d(a,"getNativeSelection");var b=t(a),c=y(a),e=P?f(a):null;return b?(b.nativeSelection=c,b.docSelection=e,b.refresh()):(b=new r(c,e,a),ba.push({win:a,selection:b})),b};a.getSelection=ca,D.createAliasForDeprecatedMethod(a,"getIframeSelection","getSelection");var da=r.prototype;
|
385
|
+
// Selecting a range
|
386
|
+
if(!Q&&U&&D.areHostMethods(R,["removeAllRanges","addRange"])){da.removeAllRanges=function(){this.nativeSelection.removeAllRanges(),j(this)};var ea=function(a,b){Z(a.nativeSelection,b),a.refresh()};W?da.addRange=function(b,d){if(_&&P&&this.docSelection.type==K)q(this,b);else if(c(d)&&V)ea(this,b);else{var e;X?e=this.rangeCount:(this.removeAllRanges(),e=0);
|
387
|
+
// Clone the native range so that changing the selected range does not affect the selection.
|
388
|
+
// This is contrary to the spec but is the only way to achieve consistency between browsers. See
|
389
|
+
// issue 80.
|
390
|
+
var f=k(b).cloneRange();try{this.nativeSelection.addRange(f)}catch(a){}if(
|
391
|
+
// Check whether adding the range was successful
|
392
|
+
this.rangeCount=this.nativeSelection.rangeCount,this.rangeCount==e+1){
|
393
|
+
// The range was added successfully
|
394
|
+
// Check whether the range that we added to the selection is reflected in the last range extracted from
|
395
|
+
// the selection
|
396
|
+
if(a.config.checkSelectionRanges){var g=aa(this.nativeSelection,this.rangeCount-1);g&&!N(g,b)&&(
|
397
|
+
// Happens in WebKit with, for example, a selection placed at the start of a text node
|
398
|
+
b=new G(g))}this._ranges[this.rangeCount-1]=b,h(this,b,ha(this.nativeSelection)),this.isCollapsed=z(this)}else
|
399
|
+
// The range was not added successfully. The simplest thing is to refresh
|
400
|
+
this.refresh()}}:da.addRange=function(a,b){c(b)&&V?ea(this,a):(this.nativeSelection.addRange(k(a)),this.refresh())},da.setRanges=function(a){if(_&&P&&a.length>1)u(this,a);else{this.removeAllRanges();for(var b=0,c=a.length;b<c;++b)this.addRange(a[b])}}}else{if(!(E(R,"empty")&&E(S,"select")&&_&&Q))return b.fail("No means of selecting a Range or TextRange was found"),!1;da.removeAllRanges=function(){
|
401
|
+
// Added try/catch as fix for issue #21
|
402
|
+
try{
|
403
|
+
// Check for empty() not working (issue #24)
|
404
|
+
if(this.docSelection.empty(),"None"!=this.docSelection.type){
|
405
|
+
// Work around failure to empty a control selection by instead selecting a TextRange and then
|
406
|
+
// calling empty()
|
407
|
+
var a;if(this.anchorNode)a=L(this.anchorNode);else if(this.docSelection.type==K){var b=this.docSelection.createRange();b.length&&(a=L(b.item(0)))}if(a){var c=M(a).createTextRange();c.select(),this.docSelection.empty()}}}catch(a){}j(this)},da.addRange=function(b){this.docSelection.type==K?q(this,b):(a.WrappedTextRange.rangeToTextRange(b).select(),this._ranges[0]=b,this.rangeCount=1,this.isCollapsed=this._ranges[0].collapsed,h(this,b,!1))},da.setRanges=function(a){this.removeAllRanges();var b=a.length;b>1?u(this,a):b&&this.addRange(a[0])}}da.getRangeAt=function(a){if(a<0||a>=this.rangeCount)throw new H("INDEX_SIZE_ERR");
|
408
|
+
// Clone the range to preserve selection-range independence. See issue 80.
|
409
|
+
return this._ranges[a].cloneRange()};var fa;if(Q)fa=function(b){var c;a.isSelectionValid(b.win)?c=b.docSelection.createRange():(c=M(b.win.document).createTextRange(),c.collapse(!0)),b.docSelection.type==K?p(b):n(c)?o(b,c):j(b)};else if(E(R,"getRangeAt")&&typeof R.rangeCount==B)fa=function(b){if(_&&P&&b.docSelection.type==K)p(b);else if(b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount,b.rangeCount){for(var c=0,d=b.rangeCount;c<d;++c)b._ranges[c]=new a.WrappedRange(b.nativeSelection.getRangeAt(c));h(b,b._ranges[b.rangeCount-1],ha(b.nativeSelection)),b.isCollapsed=z(b)}else j(b)};else{if(!U||typeof R.isCollapsed!=A||typeof S.collapsed!=A||!J.implementsDomRange)return b.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;fa=function(a){var b,c=a.nativeSelection;c.anchorNode?(b=aa(c,0),a._ranges=[b],a.rangeCount=1,i(a),a.isCollapsed=z(a)):j(a)}}da.refresh=function(a){var b=a?this._ranges.slice(0):null,c=this.anchorNode,d=this.anchorOffset;if(fa(this),a){
|
410
|
+
// Check the range count first
|
411
|
+
var e=b.length;if(e!=this._ranges.length)return!0;
|
412
|
+
// Now check the direction. Checking the anchor position is the same is enough since we're checking all the
|
413
|
+
// ranges after this
|
414
|
+
if(this.anchorNode!=c||this.anchorOffset!=d)return!0;
|
415
|
+
// Finally, compare each range in turn
|
416
|
+
for(;e--;)if(!N(b[e],this._ranges[e]))return!0;return!1}};
|
417
|
+
// Removal of a single range
|
418
|
+
var ga=function(a,b){var c=a.getAllRanges();a.removeAllRanges();for(var d=0,e=c.length;d<e;++d)N(b,c[d])||a.addRange(c[d]);a.rangeCount||j(a)};_&&P?da.removeRange=function(a){if(this.docSelection.type==K){for(var b,c=this.docSelection.createRange(),d=m(a),e=L(c.item(0)),f=M(e).createControlRange(),g=!1,h=0,i=c.length;h<i;++h)b=c.item(h),b!==d||g?f.add(c.item(h)):g=!0;f.select(),
|
419
|
+
// Update the wrapped selection based on what's now in the native selection
|
420
|
+
p(this)}else ga(this,a)}:da.removeRange=function(a){ga(this,a)};
|
421
|
+
// Detecting if a selection is backward
|
422
|
+
var ha;!Q&&U&&J.implementsDomRange?(ha=g,da.isBackward=function(){return ha(this)}):ha=da.isBackward=function(){return!1},
|
423
|
+
// Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
|
424
|
+
da.isBackwards=da.isBackward,
|
425
|
+
// Selection stringifier
|
426
|
+
// This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
|
427
|
+
// The current spec does not yet define this method.
|
428
|
+
da.toString=function(){for(var a=[],b=0,c=this.rangeCount;b<c;++b)a[b]=""+this._ranges[b];return a.join("")},
|
429
|
+
// No current browser conforms fully to the spec for this method, so Rangy's own method is always used
|
430
|
+
da.collapse=function(b,c){v(this,b);var d=a.createRange(b);d.collapseToPoint(b,c),this.setSingleRange(d),this.isCollapsed=!0},da.collapseToStart=function(){if(!this.rangeCount)throw new H("INVALID_STATE_ERR");var a=this._ranges[0];this.collapse(a.startContainer,a.startOffset)},da.collapseToEnd=function(){if(!this.rangeCount)throw new H("INVALID_STATE_ERR");var a=this._ranges[this.rangeCount-1];this.collapse(a.endContainer,a.endOffset)},
|
431
|
+
// The spec is very specific on how selectAllChildren should be implemented and not all browsers implement it as
|
432
|
+
// specified so the native implementation is never used by Rangy.
|
433
|
+
da.selectAllChildren=function(b){v(this,b);var c=a.createRange(b);c.selectNodeContents(b),this.setSingleRange(c)},da.deleteFromDocument=function(){
|
434
|
+
// Sepcial behaviour required for IE's control selections
|
435
|
+
if(_&&P&&this.docSelection.type==K){for(var a,b=this.docSelection.createRange();b.length;)a=b.item(0),b.remove(a),C.removeNode(a);this.refresh()}else if(this.rangeCount){var c=this.getAllRanges();if(c.length){this.removeAllRanges();for(var d=0,e=c.length;d<e;++d)c[d].deleteContents();
|
436
|
+
// The spec says nothing about what the selection should contain after calling deleteContents on each
|
437
|
+
// range. Firefox moves the selection to where the final selected range was, so we emulate that
|
438
|
+
this.addRange(c[e-1])}}},
|
439
|
+
// The following are non-standard extensions
|
440
|
+
da.eachRange=function(a,b){for(var c=0,d=this._ranges.length;c<d;++c)if(a(this.getRangeAt(c)))return b},da.getAllRanges=function(){var a=[];return this.eachRange(function(b){a.push(b)}),a},da.setSingleRange=function(a,b){this.removeAllRanges(),this.addRange(a,b)},da.callMethodOnEachRange=function(a,b){var c=[];return this.eachRange(function(d){c.push(d[a].apply(d,b||[]))}),c},da.setStart=w(!0),da.setEnd=w(!1),
|
441
|
+
// Add select() method to Range prototype. Any existing selection will be removed.
|
442
|
+
a.rangePrototype.select=function(a){ca(this.getDocument()).setSingleRange(this,a)},da.changeEachRange=function(a){var b=[],c=this.isBackward();this.eachRange(function(c){a(c),b.push(c)}),this.removeAllRanges(),c&&1==b.length?this.addRange(b[0],"backward"):this.setRanges(b)},da.containsNode=function(a,b){return this.eachRange(function(c){return c.containsNode(a,b)},!0)||!1},da.getBookmark=function(a){return{backward:this.isBackward(),rangeBookmarks:this.callMethodOnEachRange("getBookmark",[a])}},da.moveToBookmark=function(b){for(var c,d,e=[],f=0;c=b.rangeBookmarks[f++];)d=a.createRange(this.win),d.moveToBookmark(c),e.push(d);b.backward?this.setSingleRange(e[0],"backward"):this.setRanges(e)},da.saveRanges=function(){return{backward:this.isBackward(),ranges:this.callMethodOnEachRange("cloneRange")}},da.restoreRanges=function(a){this.removeAllRanges();for(var b,c=0;b=a.ranges[c];++c)this.addRange(b,a.backward&&0==c)},da.toHtml=function(){var a=[];return this.eachRange(function(b){a.push(F.toHtml(b))}),a.join("")},J.implementsTextRange&&(da.getNativeTextRange=function(){var c;if(c=this.docSelection){var d=c.createRange();if(n(d))return d;throw b.createError("getNativeTextRange: selection is a control selection")}if(this.rangeCount>0)return a.WrappedTextRange.rangeToTextRange(this.getRangeAt(0));throw b.createError("getNativeTextRange: selection contains no range")}),da.getName=function(){return"WrappedSelection"},da.inspect=function(){return x(this)},da.detach=function(){t(this.win,"delete"),s(this)},r.detachAll=function(){t(null,"deleteAll")},r.inspect=x,r.isDirectionBackward=c,a.Selection=r,a.selectionPrototype=da,a.addShimListener(function(a){"undefined"==typeof a.getSelection&&(a.getSelection=function(){return ca(a)}),a=null})});/*----------------------------------------------------------------------------------------------------------------*/
|
443
|
+
// Wait for document to load before initializing
|
444
|
+
var M=!1,N=function(a){M||(M=!0,!H.initialized&&H.config.autoInitialize&&l())};
|
445
|
+
// Test whether the document has already been loaded and initialize immediately if so
|
446
|
+
// Add a fallback in case the DOMContentLoaded event isn't supported
|
447
|
+
return F&&("complete"==document.readyState?N():(a(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",N,!1),J(window,"load",N))),H},this),/**
|
448
|
+
* Selection save and restore module for Rangy.
|
449
|
+
* Saves and restores user selections using marker invisible elements in the DOM.
|
450
|
+
*
|
451
|
+
* Part of Rangy, a cross-browser JavaScript range and selection library
|
452
|
+
* https://github.com/timdown/rangy
|
453
|
+
*
|
454
|
+
* Depends on Rangy core.
|
455
|
+
*
|
456
|
+
* Copyright 2015, Tim Down
|
457
|
+
* Licensed under the MIT license.
|
458
|
+
* Version: 1.3.0
|
459
|
+
* Build date: 10 May 2015
|
460
|
+
*/
|
461
|
+
function(a,b){"function"==typeof define&&define.amd?
|
462
|
+
// AMD. Register as an anonymous module with a dependency on Rangy.
|
463
|
+
define(["./rangy-core"],a):"undefined"!=typeof module&&"object"==typeof exports?
|
464
|
+
// Node/CommonJS style
|
465
|
+
module.exports=a(require("rangy")):
|
466
|
+
// No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
|
467
|
+
a(b.rangy)}(function(a){return a.createModule("SaveRestore",["WrappedRange"],function(a,b){function c(a,b){return(b||document).getElementById(a)}function d(a,b){var c,d="selectionBoundary_"+ +new Date+"_"+(""+Math.random()).slice(2),e=o.getDocument(a.startContainer),f=a.cloneRange();
|
468
|
+
// Create the marker element containing a single invisible character using DOM methods and insert it
|
469
|
+
return f.collapse(b),c=e.createElement("span"),c.id=d,c.style.lineHeight="0",c.style.display="none",c.className="rangySelectionBoundary",c.appendChild(e.createTextNode(r)),f.insertNode(c),c}function e(a,d,e,f){var g=c(e,a);g?(d[f?"setStartBefore":"setEndBefore"](g),p(g)):b.warn("Marker element has been removed. Cannot restore selection.")}function f(a,b){return b.compareBoundaryPoints(a.START_TO_START,a)}function g(b,c){var e,f,g=a.DomRange.getRangeDocument(b),h=b.toString(),i=q(c);return b.collapsed?(f=d(b,!1),{document:g,markerId:f.id,collapsed:!0}):(f=d(b,!1),e=d(b,!0),{document:g,startMarkerId:e.id,endMarkerId:f.id,collapsed:!1,backward:i,toString:function(){return"original text: '"+h+"', new text: '"+b.toString()+"'"}})}function h(d,f){var g=d.document;"undefined"==typeof f&&(f=!0);var h=a.createRange(g);if(d.collapsed){var i=c(d.markerId,g);if(i){i.style.display="inline";var j=i.previousSibling;
|
470
|
+
// Workaround for issue 17
|
471
|
+
j&&3==j.nodeType?(p(i),h.collapseToPoint(j,j.length)):(h.collapseBefore(i),p(i))}else b.warn("Marker element has been removed. Cannot restore selection.")}else e(g,h,d.startMarkerId,!0),e(g,h,d.endMarkerId,!1);return f&&h.normalizeBoundaries(),h}function i(b,d){var e,h,i=[],j=q(d);
|
472
|
+
// Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
|
473
|
+
b=b.slice(0),b.sort(f);for(var k=0,l=b.length;k<l;++k)i[k]=g(b[k],j);
|
474
|
+
// Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
|
475
|
+
// between its markers
|
476
|
+
for(k=l-1;k>=0;--k)e=b[k],h=a.DomRange.getRangeDocument(e),e.collapsed?e.collapseAfter(c(i[k].markerId,h)):(e.setEndBefore(c(i[k].endMarkerId,h)),e.setStartAfter(c(i[k].startMarkerId,h)));return i}function j(c){if(!a.isSelectionValid(c))return b.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."),null;var d=a.getSelection(c),e=d.getAllRanges(),f=1==e.length&&d.isBackward(),g=i(e,f);
|
477
|
+
// Ensure current selection is unaffected
|
478
|
+
return f?d.setSingleRange(e[0],f):d.setRanges(e),{win:c,rangeInfos:g,restored:!1}}function k(a){for(var b=[],c=a.length,d=c-1;d>=0;d--)b[d]=h(a[d],!0);return b}function l(b,c){if(!b.restored){var d=b.rangeInfos,e=a.getSelection(b.win),f=k(d),g=d.length;1==g&&c&&a.features.selectionHasExtend&&d[0].backward?(e.removeAllRanges(),e.addRange(f[0],!0)):e.setRanges(f),b.restored=!0}}function m(a,b){var d=c(b,a);d&&p(d)}function n(a){for(var b,c=a.rangeInfos,d=0,e=c.length;d<e;++d)b=c[d],b.collapsed?m(a.doc,b.markerId):(m(a.doc,b.startMarkerId),m(a.doc,b.endMarkerId))}var o=a.dom,p=o.removeNode,q=a.Selection.isDirectionBackward,r="\ufeff";a.util.extend(a,{saveRange:g,restoreRange:h,saveRanges:i,restoreRanges:k,saveSelection:j,restoreSelection:l,removeMarkerElement:m,removeMarkers:n})}),a},this);
|