diff --git a/buildstandalone.php b/buildstandalone.php deleted file mode 100644 index 24bd58d..0000000 --- a/buildstandalone.php +++ /dev/null @@ -1,276 +0,0 @@ -. - -/** - * Builds a standalone demonstration version of the ousupsub editor - * - * This script is designed to run from the command line and is safe to re-run - * at any time when the plugin is updated. - * - * @package editor_ousupsub - * @copyright 2015 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define('CLI_SCRIPT', true); -define('CACHE_DISABLE_ALL', true); - -require_once(__DIR__ . '/../../../config.php'); -require_once($CFG->libdir . '/filelib.php'); - -error_reporting(E_ALL | E_STRICT); -error_reporting(-1); -ini_set('display_errors', true); -ini_set('display_startup_errors', true); - -ousupsub_texteditor_standalone_builder::create_standalone(); - -/** - * Creates demonstration editor. - */ -class ousupsub_texteditor_standalone_builder { - private static $paths = array( - 'root' => 'standalone', - 'index' => 'index.html', - 'ousupsubjs' => 'ousupsub.js', - 'stylecss' => 'styles.css', - 'readme' => 'readme.txt', - 'readmestandalone' => 'standalone-src/readme.txt', - 'yuiversion' => '3.18.1', - 'wwwroot' => '../../..' - ); - private static $yuisuffix = '-min'; - - public static function create_standalone () { - self::delete_standalone(); - self::create_standalone_folder(); - self::create_readme_file(); - self::create_index_page(); - self::copy_icons(); - self::create_css_file(); - self::create_javascript_file(); - } - - public static function delete_standalone () { - $path = self::create_path('root'); - if (fulldelete($path)) { - self::echo_result("Emptied standalone folder."); - } - } - - /** - * Create the root folder. - */ - public static function create_standalone_folder() { - $path = self::create_path('root/ousupsub'); - self::create_folder($path); - } - - /** - * Create the language string. - */ - public static function create_language_string() { - $components = array( - 'moodle' => array('error', 'morehelp'), - 'editor_ousupsub' => array('editor_command_keycode', 'editor_control_keycode', - 'editor_shift_keycode', 'plugin_title_shortcut', - 'subscript', 'superscript', 'undo', 'redo'), - ); - - $output = array(); - foreach ($components as $component => $keys) { - $output[$component] = array(); - foreach ($keys as $key) { - $output[$component][$key] = get_string($key, $component); - } - } - self::echo_result("Create language strings."); - return json_encode($output); - } - - /** - * Create readme file. - */ - public static function create_readme_file() { - - // Create the readme file. - $pathfrom = self::create_path('readmestandalone'); - $contents = file_get_contents($pathfrom); - - // Path to save file to. - $pathto = self::create_path('root/readme'); - if (file_put_contents($pathto, $contents, 0)) { - self::echo_result("Created readme.txt."); - } - } - - /** - * Create the index page. - */ - public static function create_index_page() { - $replacements = array( - '%%jsurl%%' => self::create_path('ousupsub/ousupsubjs'), - '%%stylesurl%%' => self::create_path('ousupsub/stylecss'), - ); - - $html = file_get_contents(self::create_path('standalone-src/index.html')); - $html = str_replace(array_keys($replacements), array_values($replacements), $html); - - $path = self::create_path('root/index'); - if (file_put_contents($path, $html, 0)) { - self::echo_result("Create index file."); - } - } - - /** - * Copy button icons. - */ - public static function copy_icons() { - global $CFG; - $names = array('subscript', 'superscript'); - $preferredlocation = $CFG->dirroot . '/theme/ou/pix/editor/'; - $fallbacklocation = $CFG->dirroot . '/pix/e/'; - - // OU sup sub icons. - foreach ($names as $name) { - $source = $preferredlocation . $name . '.svg'; - if (!is_readable($source)) { - $source = $fallbacklocation . $name . '.svg'; - } - $destination = self::create_path('root/ousupsub/'.$name.'.svg'); - if (copy($source, $destination)) { - self::echo_result("Copy ousupsub ".$name." icon."); - } - } - } - - /** - * Create CSS file. - */ - public static function create_css_file() { - - // Create the static file. The unconventional indenting is required to produce conventional - // indenting in the file produced. - $contents = ' -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 20px; - color: #333; - background-color: #fff; -} -'; - $contents .= file_get_contents(self::create_path('stylecss')); - - // Path to save file to. - if (file_put_contents(self::create_path('root/ousupsub/stylecss'), $contents, 0)) { - self::echo_result("Created styles.css."); - } - } - - /** - * Copy the javascript files required by the editor. - */ - public static function create_javascript_file() { - $replacements = array( - '%%yuilibraries%%' => self::create_yui_javascript(), - '%%ousupsubcode%%' => self::create_supsub_javascript(), - '%%langstrings%%' => self::create_language_string(), - ); - - $js = file_get_contents(self::create_path('standalone-src/standalone.js')); - $js = str_replace(array_keys($replacements), array_values($replacements), $js); - - if (file_put_contents(self::create_path('root/ousupsub/ousupsubjs'), $js, 0)) { - self::echo_result("Created editor javascript file."); - } - } - - /** - * Get the javascript that makes up the editor. - */ - public static function create_supsub_javascript() { - $supsubjs = ''; - $editorcodepath = 'yui/build/moodle-editor_ousupsub-%%PART%%/moodle-editor_ousupsub-%%PART%%' . - self::$yuisuffix . '.js'; - $names = array('rangy', 'editor'); - foreach ($names as $name) { - $supsubjs .= file_get_contents(str_replace('%%PART%%', $name, $editorcodepath)); - } - return $supsubjs; - } - - /** - * Copy YUI js files. - */ - public static function create_yui_javascript() { - $yuijs = ''; - - $source = self::create_path('wwwroot/lib/yuilib/yuiversion'); - $names = array('yui', 'attribute-base', 'attribute-core', 'attribute-extras', - 'attribute-observable', 'base-base', 'base-build', 'base-core', - 'base-observable', 'base-pluginhost', 'dom-base', - 'dom-core', 'dom-screen', 'dom-style', 'event-base', 'event-custom-base', - 'event-custom-complex', 'event-delegate', 'event-flick', 'event-focus', 'event-hover', 'event-key', - 'event-mousewheel', 'event-mouseenter', 'event-move', 'event-outside', - 'event-resize', 'event-synthetic', 'event-tap', 'event-touch', - 'event-valuechange', 'node-base', 'node-core', - 'node-event-delegate', 'node-pluginhost', 'node-screen', 'node-style', 'oop', - 'pluginhost-base', 'pluginhost-config', - 'selector', 'selector-native'); - foreach ($names as $name) { - $newjs = file_get_contents($source . '/' . $name . '/' . $name . self::$yuisuffix . '.js'); - // We don't acutally load anything from this URL, but the presence of the - // non-https URL causes a suprious error in IE8, so we change it. - // Note, the https version of this URL does not actually work. - $yuijs .= str_replace('http://yui.yahooapis.com/', 'https://yui.yahooapis.com/', $newjs); - } - - return $yuijs; - } - - /** - * Create a folder on the file system give a path. - */ - public static function create_folder($path) { - global $CFG; - if (!file_exists($path)) { - mkdir($path, $CFG->directorypermissions, true); - } - - return true; - } - - /** - * Create a php folder path given keys from the $paths array. - */ - public static function create_path($ids) { - $keys = explode('/', $ids); - $path = ''; - foreach ($keys as $key) { - $path .= strlen($path) ? '/' : ''; - $path .= array_key_exists($key, self::$paths) ? self::$paths[$key] : $key; - } - return $path; - } - - /** - * Create a folder on the file system give a path. - */ - public static function echo_result($msg) { - echo $msg."\r\n"; - } -} diff --git a/standalone-src/index.html b/standalone-src/index.html deleted file mode 100644 index 020f198..0000000 --- a/standalone-src/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - OU SupSub demo - - - - - - - - -
- - - - - -
- - - - - diff --git a/standalone-src/readme.txt b/standalone-src/readme.txt deleted file mode 100644 index bb97fa0..0000000 --- a/standalone-src/readme.txt +++ /dev/null @@ -1,26 +0,0 @@ -This folder contains the files for the standalone superscript subscript editor. -index.html contains a demonstration of the editor and the required resources are -in the resources folder - -To view a demonstration of the editor, download the standalone folder to your -desktop and open the index.html file in a browser. - -You will then see a text editor with two buttons. One for superscript and one -for subscript. - -The editor features are summarised as: -* A text editor with sup/sub buttons based on the moodle atto editor. -* Allow only alphanumeric text. No html tags except and -* Provide a superscript or subscript button or both along with related functionality -* Prevent nesting of superscript and subscript tags -* No text wrapping is allowed along with no paragraphs. Everything is on one line -* Configurable height and width of editor -* Provide a standalone version of the same editor for offline situations such as ereaders -* Editor can placed where required including inline with text - -Features delivered -The features that have been delivered are: -* a text editor with sup/sub buttons based on the moodle atto editor. -* a standalone demonstration. -* remove features and trim resources: Atto editor has multiple buttons and - toolbars that aren't needed along with scripts and icons. \ No newline at end of file diff --git a/standalone-src/standalone.js b/standalone-src/standalone.js deleted file mode 100644 index 62a9274..0000000 --- a/standalone-src/standalone.js +++ /dev/null @@ -1,71 +0,0 @@ -// We encapsulate all our JavaScript inside an anonymouse function which just -// returns the init_ousupsub function that we want to be available. -(function () { - -%%yuilibraries%% - -%%ousupsubcode%% - -M = { - str: %%langstrings%%, - iconRootUrl: null, // Set in a minute. - util: { - pending_js: [], - image_url: function (imageName) { - return M.iconRootUrl + imageName.replace("e/", "/") + ".svg"; - }, - get_string: function(identifier, component, a) { - var stringvalue; - - if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) { - stringvalue = '[[' + identifier + ',' + component + ']]'; - if (M.cfg.developerdebug) { - console.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string'); - } - return stringvalue; - } - - stringvalue = M.str[component][identifier]; - - if (typeof a === 'undefined') { - // no placeholder substitution requested - return stringvalue; - } - - if (typeof a === 'number' || typeof a === 'string') { - // replace all occurrences of {$a} with the placeholder value - stringvalue = stringvalue.replace(/\{\$a\}/g, a); - return stringvalue; - } - - if (typeof a === 'object') { - // replace {$a->key} placeholders - for (var key in a) { - if (typeof a[key] != 'number' && typeof a[key] != 'string') { - if (M.cfg.developerdebug) { - console.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string'); - } - continue; - } - var search = '{$a->' + key + '}'; - stringvalue = stringvalue.replace(search, a[key]); - } - return stringvalue; - } - - if (M.cfg.developerdebug) { - console.log('incorrect placeholder type', 'warn', 'M.util.get_string'); - } - return stringvalue; - } - }, -}; - -var thisScriptUrl = document.getElementById('ousupsubloader').getAttribute('src'); -M.iconRootUrl = thisScriptUrl.substring(0, thisScriptUrl.lastIndexOf("/")); - -YUI().use("moodle-editor_ousupsub-editor", function(Y) { - window.editor_ousupsub = Y.M.editor_ousupsub; -}); - -}()); diff --git a/standalone/index.html b/standalone/index.html deleted file mode 100644 index eaa40f1..0000000 --- a/standalone/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - OU SupSub demo - - - - - - - - -
- - - - - -
- - - - - diff --git a/standalone/ousupsub/ousupsub.js b/standalone/ousupsub/ousupsub.js deleted file mode 100644 index a0047e9..0000000 --- a/standalone/ousupsub/ousupsub.js +++ /dev/null @@ -1,153 +0,0 @@ -// We encapsulate all our JavaScript inside an anonymouse function which just -// returns the init_ousupsub function that we want to be available. -(function () { - -typeof YUI!="undefined"&&(YUI._YUI=YUI);var YUI=function(){var e=0,t=this,n=arguments,r=n.length,i=function(e,t){return e&&e.hasOwnProperty&&e instanceof t},s=typeof YUI_config!="undefined"&&YUI_config;i(t,YUI)?(t._init(),YUI.GlobalConfig&&t.applyConfig(YUI.GlobalConfig),s&&t.applyConfig(s),r||(t._afterConfig(),t._setup())):t=new YUI;if(r){for(;e-1&&(n="3.5.0"),e={applyConfig:function(e){e=e||u;var t,n,r=this.config,i=r.modules,s=r.groups,o=r.aliases,a=this.Env._loader;for(n in e)e.hasOwnProperty(n)&&(t=e[n],i&&n=="modules"?S(i,t):o&&n=="aliases"?S(o,t):s&&n=="groups"?S(s,t):n=="win"?(r[n]=t&&t.contentWindow||t,r.doc=r[n]?r[n].document:null):n!="_yuid"&&(r[n]=t));a&&a._config(e)},_config:function(e){this.applyConfig(e)},_init:function(){var e,t,r=this,s=YUI.Env,u=r.Env,a;r.version=n;if(!u){r.Env={core:["get","features","intl-base","yui-log","yui-later","loader-base","loader-rollup","loader-yui3"],loaderExtras:["loader-rollup","loader-yui3"],mods:{},versions:{},base:i,cdn:i+n+"/",_idx:0,_used:{},_attached:{},_exported:{},_missed:[],_yidx:0,_uidx:0,_guidp:"y",_loaded:{},_BASE_RE:/(?:\?(?:[^&]*&)*([^&]*))?\b(yui(?:-\w+)?)\/\2(?:-(min|debug))?\.js/,parseBasePath:function(e,t){var n=e.match(t),r,i;return n&&(r=RegExp.leftContext||e.slice(0,e.indexOf(n[0])),i=n[3],n[1]&&(r+="?"+n[1]),r={filter:i,path:r}),r},getBase:s&&s.getBase||function(t){var n=h&&h.getElementsByTagName("script")||[],i=u.cdn,s,o,a,f;for(o=0,a=n.length;o',YUI.Env.cssStampEl=t.firstChild,h.body?h.body.appendChild(YUI.Env.cssStampEl):p.insertBefore(YUI.Env.cssStampEl,p.firstChild)):h&&h.getElementById(o)&&!YUI.Env.cssStampEl&&(YUI.Env.cssStampEl=h.getElementById(o)),r.config.lang=r.config.lang||"en-US",r.config.base=YUI.config.base||YUI.config.defaultBase&&YUI.config.root&&YUI.config.defaultBase+YUI.config.root||r.Env.getBase(r.Env._BASE_RE);if(!e||!"mindebug".indexOf(e))e="min";e=e?"-"+e:e,r.config.loaderPath=YUI.config.loaderPath||"loader/loader"+e+".js"},_afterConfig:function(){var e=this;e.config.hasOwnProperty("global")||(e.config.global=Function("return this")())},_setup:function(){var e,t=this,n=[],r=YUI.Env.mods,i=t.config.extendedCore||[],s=t.config.core||[].concat(YUI.Env.core).concat(i);for(e=0;e-1){s=o.split(r);for(i=s[0]=="YAHOO"?1:0;i-1?n(t,r):t[r];return typeof i=="undefined"?e:i}):e},n.trim=n._isNative(r.trim)&&!u.trim()?function(e){return e&&e.trim?e.trim():e}:function(e){try{return e.replace(c,"")}catch(t){return e}},n.trimLeft=n._isNative(r.trimLeft)&&!u.trimLeft()?function(e){return e.trimLeft()}:function(e){return e.replace(f,"")},n.trimRight=n._isNative(r.trimRight)&&!u.trimRight()?function(e){return e.trimRight()}:function(e){return e.replace(l,"")},n.type=function(e){return s[typeof e]||s[i.call(e)]||(e?"object":"null")};var p=e.Lang,d=Array.prototype,v=Object.prototype.hasOwnProperty;e.Array=m,m.dedupe=p._isNative(Object.create)?function(e){var t=Object.create(null),n=[],r,i,s;for(r=0,s=e.length;ri&&i in t?t[i]:!0);return n},m.indexOf=p._isNative(d.indexOf)?function(e,t,n){return d.indexOf.call(e,t,n)}:function(e,t,n){var r=e.length;n=+n||0,n=(n>0||-1)*Math.floor(Math.abs(n)),n<0&&(n+=r,n<0&&(n=0));for(;n1?Array.prototype.join.call(arguments,y):String(r);if(!(i in t)||n&&t[i]==n)t[i]=e.apply(e,arguments);return t[i]}},e.getLocation=function(){var t=e.config.win;return t&&t.location},e.merge=function(){var e=0,t=arguments.length,n={},r,i;for(;e-1},E.each=function(t,n,r,i){var s;for(s in t)(i||N(t,s))&&n.call(r||e,t[s],s,t);return e},E.some=function(t,n,r,i){var s;for(s in t)if(i||N(t,s))if(n.call(r||e,t[s],s,t))return!0;return!1},E.getValue=function(t,n){if(!p.isObject(t))return w;var r,i=e.Array(n),s=i.length;for(r=0;t!==w&&r=0){for(i=0;u!==w&&i0),t||(typeof process=="object"&&process.versions&&process.versions.node&&(s.os=process.platform,s.nodejs=n(process.versions.node)),YUI.Env.UA=s),s},e.UA=YUI.Env.UA||YUI.Env.parseUA(),e.UA.compareVersions=function(e,t){var n,r,i,s,o,u;if(e===t)return 0;r=(e+"").split("."),s=(t+"").split(".");for(o=0,u=Math.max(r.length,s.length);oi)return 1}return 0},YUI.Env.aliases={anim:["anim-base","anim-color","anim-curve","anim-easing","anim-node-plugin","anim-scroll","anim-xy"],"anim-shape-transform":["anim-shape"],app:["app-base","app-content","app-transitions","lazy-model-list","model","model-list","model-sync-rest","model-sync-local","router","view","view-node-map"],attribute:["attribute-base","attribute-complex"],"attribute-events":["attribute-observable"],autocomplete:["autocomplete-base","autocomplete-sources","autocomplete-list","autocomplete-plugin"],axes:["axis-numeric","axis-category","axis-time","axis-stacked"],"axes-base":["axis-numeric-base","axis-category-base","axis-time-base","axis-stacked-base"],base:["base-base","base-pluginhost","base-build"],cache:["cache-base","cache-offline","cache-plugin"],charts:["charts-base"],collection:["array-extras","arraylist","arraylist-add","arraylist-filter","array-invoke"],color:["color-base","color-hsl","color-harmony"],controller:["router"],dataschema:["dataschema-base","dataschema-json","dataschema-xml","dataschema-array","dataschema-text"],datasource:["datasource-local","datasource-io","datasource-get","datasource-function","datasource-cache","datasource-jsonschema","datasource-xmlschema","datasource-arrayschema","datasource-textschema","datasource-polling"],datatable:["datatable-core","datatable-table","datatable-head","datatable-body","datatable-base","datatable-column-widths","datatable-message","datatable-mutable","datatable-sort","datatable-datasource"],datatype:["datatype-date","datatype-number","datatype-xml"],"datatype-date":["datatype-date-parse","datatype-date-format","datatype-date-math"],"datatype-number":["datatype-number-parse","datatype-number-format"],"datatype-xml":["datatype-xml-parse","datatype-xml-format"],dd:["dd-ddm-base","dd-ddm","dd-ddm-drop","dd-drag","dd-proxy","dd-constrain","dd-drop","dd-scroll","dd-delegate"],dom:["dom-base","dom-screen","dom-style","selector-native","selector"],editor:["frame","editor-selection","exec-command","editor-base","editor-para","editor-br","editor-bidi","editor-tab","createlink-base"],event:["event-base","event-delegate","event-synthetic","event-mousewheel","event-mouseenter","event-key","event-focus","event-resize","event-hover","event-outside","event-touch","event-move","event-flick","event-valuechange","event-tap"],"event-custom":["event-custom-base","event-custom-complex"],"event-gestures":["event-flick","event-move"],handlebars:["handlebars-compiler"],highlight:["highlight-base","highlight-accentfold"],history:["history-base","history-hash","history-html5"],io:["io-base","io-xdr","io-form","io-upload-iframe","io-queue"],json:["json-parse","json-stringify"],loader:["loader-base","loader-rollup","loader-yui3"],"loader-pathogen-encoder":["loader-base","loader-rollup","loader-yui3","loader-pathogen-combohandler"],node:["node-base","node-event-delegate","node-pluginhost","node-screen","node-style"],pluginhost:["pluginhost-base","pluginhost-config"],querystring:["querystring-parse","querystring-stringify"],recordset:["recordset-base","recordset-sort","recordset-filter","recordset-indexer"],resize:["resize-base","resize-proxy","resize-constrain"],slider:["slider-base","slider-value-range","clickable-rail","range-slider" -],template:["template-base","template-micro"],text:["text-accentfold","text-wordbreak"],widget:["widget-base","widget-htmlparser","widget-skin","widget-uievents"]}},"3.18.1",{use:["yui-base","get","features","intl-base","yui-log","yui-later","loader-base","loader-rollup","loader-yui3"]}),YUI.add("get",function(e,t){var n=e.Lang,r,i,s;e.Get=i={cssOptions:{attributes:{rel:"stylesheet"},doc:e.config.linkDoc||e.config.doc,pollInterval:50},jsOptions:{autopurge:!0,doc:e.config.scriptDoc||e.config.doc},options:{attributes:{charset:"utf-8"},purgethreshold:20},REGEX_CSS:/\.css(?:[?;].*)?$/i,REGEX_JS:/\.js(?:[?;].*)?$/i,_insertCache:{},_pending:null,_purgeNodes:[],_queue:[],abort:function(e){var t,n,r,i,s;if(!e.abort){n=e,s=this._pending,e=null;if(s&&s.transaction.id===n)e=s.transaction,this._pending=null;else for(t=0,i=this._queue.length;t=e&&this._purge(this._purgeNodes)},_getEnv:function(){var t=e.config.doc,n=e.UA;return this._env={async:t&&t.createElement("script").async===!0||n.ie>=10,cssFail:n.gecko>=9||n.compareVersions(n.webkit,535.24)>=0,cssLoad:(!n.gecko&&!n.webkit||n.gecko>=9||n.compareVersions(n.webkit,535.24)>=0)&&!(n.chrome&&n.chrome<=18),preservesScriptOrder:!!(n.gecko||n.opera||n.ie&&n.ie>=10)}},_getTransaction:function(t,r){var i=[],o,u,a,f;n.isArray(t)||(t=[t]),r=e.merge(this.options,r),r.attributes=e.merge(this.options.attributes,r.attributes);for(o=0,u=t.length;o-1&&n.splice(i,1))}}},i.script=i.js,i.Transaction=s=function(t,n){var r=this;r.id=s._lastId+=1,r.data=n.data,r.errors=[],r.nodes=[],r.options=n,r.requests=t,r._callbacks=[],r._queue=[],r._reqsWaiting=0,r.tId=r.id,r.win=n.win||e.config.win},s._lastId=0,s.prototype={_state:"new",abort:function(e){this._pending=null,this._pendingCSS=null,this._pollTimer=clearTimeout(this._pollTimer),this._queue=[],this._reqsWaiting=0,this.errors.push({error:e||"Aborted"}),this._finish()},execute:function(e){var t=this,n=t.requests,r=t._state,i,s,o,u;if(r==="done"){e&&e(t.errors.length?t.errors:null,t);return}e&&t._callbacks.push(e);if(r==="executing")return;t._state="executing",t._queue=o=[],t.options.timeout&&(t._timeout=setTimeout(function(){t.abort("Timeout")},t.options.timeout)),t._reqsWaiting=n.length;for(i=0,s=n.length;i=10?(o.onerror=function(){setTimeout(c,0)},o.onload=function(){setTimeout(h,0)}):(o.onerror=c,o.onload=h),!n.cssFail&&!s&&(f=setTimeout(c,t.timeout||3e3))),this.nodes.push(o),r.parentNode.insertBefore(o,r)},_next:function(){if(this._pending)return;this._queue.length?this._insert(this._queue.shift()):this._reqsWaiting||this._finish()},_poll:function( -t){var n=this,r=n._pendingCSS,i=e.UA.webkit,s,o,u,a,f,l;if(t){r||(r=n._pendingCSS=[]),r.push(t);if(n._pollTimer)return}n._pollTimer=null;for(s=0;s=0)if(l[u].href===a){r.splice(s,1),s-=1,n._progress(null,f);break}}else try{o=!!f.node.sheet.cssRules,r.splice(s,1),s-=1,n._progress(null,f)}catch(c){}}r.length&&(n._pollTimer=setTimeout(function(){n._poll.call(n)},n.options.pollInterval))},_progress:function(e,t){var n=this.options;e&&(t.error=e,this.errors.push({error:e,request:t})),t.node._yuiget_finished=t.finished=!0,n.onProgress&&n.onProgress.call(n.context||this,this._getEventData(t)),t.autopurge&&(i._autoPurge(this.options.purgethreshold),i._purgeNodes.push(t.node)),this._pending===t&&(this._pending=null),this._reqsWaiting-=1,this._next()}}},"3.18.1",{requires:["yui-base"]}),YUI.add("features",function(e,t){var n={};e.mix(e.namespace("Features"),{tests:n,add:function(e,t,r){n[e]=n[e]||{},n[e][t]=r},all:function(t,r){var i=n[t],s=[];return i&&e.Object.each(i,function(n,i){s.push(i+":"+(e.Features.test(t,i,r)?1:0))}),s.length?s.join(";"):""},test:function(t,r,i){i=i||[];var s,o,u,a=n[t],f=a&&a[r];return!f||(s=f.result,e.Lang.isUndefined(s)&&(o=f.ua,o&&(s=e.UA[o]),u=f.test,u&&(!o||s)&&(s=u.apply(e,i)),f.result=s)),s}});var r=e.Features.add;r("load","0",{name:"app-transitions-native",test:function(e){var t=e.config.doc,n=t?t.documentElement:null;return n&&n.style?"MozTransition"in n.style||"WebkitTransition"in n.style||"transition"in n.style:!1},trigger:"app-transitions"}),r("load","1",{name:"autocomplete-list-keys",test:function(e){return!e.UA.ios&&!e.UA.android},trigger:"autocomplete-list"}),r("load","2",{name:"dd-gestures",trigger:"dd-drag",ua:"touchEnabled"}),r("load","3",{name:"dom-style-ie",test:function(e){var t=e.Features.test,n=e.Features.add,r=e.config.win,i=e.config.doc,s="documentElement",o=!1;return n("style","computedStyle",{test:function(){return r&&"getComputedStyle"in r}}),n("style","opacity",{test:function(){return i&&"opacity"in i[s].style}}),o=!t("style","opacity")&&!t("style","computedStyle"),o},trigger:"dom-style"}),r("load","4",{name:"editor-para-ie",trigger:"editor-para",ua:"ie",when:"instead"}),r("load","5",{name:"event-base-ie",test:function(e){var t=e.config.doc&&e.config.doc.implementation;return t&&!t.hasFeature("Events","2.0")},trigger:"node-base"}),r("load","6",{name:"graphics-canvas",test:function(e){var t=e.config.doc,n=e.config.defaultGraphicEngine&&e.config.defaultGraphicEngine=="canvas",r=t&&t.createElement("canvas"),i=t&&t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");return(!i||n)&&r&&r.getContext&&r.getContext("2d")},trigger:"graphics"}),r("load","7",{name:"graphics-canvas-default",test:function(e){var t=e.config.doc,n=e.config.defaultGraphicEngine&&e.config.defaultGraphicEngine=="canvas",r=t&&t.createElement("canvas"),i=t&&t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");return(!i||n)&&r&&r.getContext&&r.getContext("2d")},trigger:"graphics"}),r("load","8",{name:"graphics-svg",test:function(e){var t=e.config.doc,n=!e.config.defaultGraphicEngine||e.config.defaultGraphicEngine!="canvas",r=t&&t.createElement("canvas"),i=t&&t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");return i&&(n||!r)},trigger:"graphics"}),r("load","9",{name:"graphics-svg-default",test:function(e){var t=e.config.doc,n=!e.config.defaultGraphicEngine||e.config.defaultGraphicEngine!="canvas",r=t&&t.createElement("canvas"),i=t&&t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");return i&&(n||!r)},trigger:"graphics"}),r("load","10",{name:"graphics-vml",test:function(e){var t=e.config.doc,n=t&&t.createElement("canvas");return t&&!t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")&&(!n||!n.getContext||!n.getContext("2d"))},trigger:"graphics"}),r("load","11",{name:"graphics-vml-default",test:function(e){var t=e.config.doc,n=t&&t.createElement("canvas");return t&&!t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")&&(!n||!n.getContext||!n.getContext("2d"))},trigger:"graphics"}),r("load","12",{name:"history-hash-ie",test:function(e){var t=e.config.doc&&e.config.doc.documentMode;return e.UA.ie&&(!("onhashchange"in e.config.win)||!t||t<8)},trigger:"history-hash"}),r("load","13",{name:"io-nodejs",trigger:"io-base",ua:"nodejs"}),r("load","14",{name:"json-parse-shim",test:function(e){function i(e,t){return e==="ok"?!0:t}var t=e.config.global.JSON,n=Object.prototype.toString.call(t)==="[object JSON]"&&t,r=e.config.useNativeJSONParse!==!1&&!!n;if(r)try{r=n.parse('{"ok":false}',i).ok}catch(s){r=!1}return!r},trigger:"json-parse"}),r("load","15",{name:"json-stringify-shim",test:function(e){var t=e.config.global.JSON,n=Object.prototype.toString.call(t)==="[object JSON]"&&t,r=e.config.useNativeJSONStringify!==!1&&!!n;if(r)try{r="0"===n.stringify(0)}catch(i){r=!1}return!r},trigger:"json-stringify"}),r("load","16",{name:"scrollview-base-ie",trigger:"scrollview-base",ua:"ie"}),r("load","17",{name:"selector-css2",test:function(e){var t=e.config.doc,n=t&&!("querySelectorAll"in t);return n},trigger:"selector"}),r("load","18",{name:"transition-timer",test:function(e){var t=e.config.doc,n=t?t.documentElement:null,r=!0;return n&&n.style&&(r=!("MozTransition"in n.style||"WebkitTransition"in n.style||"transition"in n.style)),r},trigger:"transition"}),r("load","19",{name:"widget-base-ie",trigger:"widget-base",ua:"ie"}),r("load","20",{name:"yql-jsonp",test:function(e){return!e.UA.nodejs&&!e.UA.winjs},trigger:"yql"}),r("load","21",{name:"yql-nodejs",trigger:"yql",ua:"nodejs"}),r("load","22",{name:"yql-winjs",trigger:"yql",ua:"winjs"})},"3.18.1",{requires:["yui-base"]}),YUI.add("intl-base",function(e,t){var n=/[, ]/;e.mix(e.namespace("Intl"),{lookupBestLang:function(t,r){function a(e){var t;for(t=0;t0){o=a(s);if(o)return o;u=s.lastIndexOf("-");if(!(u>=0))break;s=s.substring(0,u),u>=2&&s.charAt(u-2)==="-"&&(s=s.substring(0,u-2))}}return""}})},"3.18.1",{requires:["yui-base"]}),YUI.add("yui-log",function(e,t){var n=e,r="yui:log",i="undefined",s={debug:1,info:2,warn:4,error:8};n.log=function(e,t,o,u){var a,f,l,c,h,p,d=n,v=d.config,m=d.fire?d:YUI.Env.globalEvents;return v.debug&&(o=o||"",typeof o!="undefined"&&(f=v.logExclude,l=v.logInclude,!l||o in l?l&&o in l?a=!l[o]:f&&o in f&&(a=f[o]):a=1,typeof t=="undefined"&&(t="info"),d.config.logLevel=d.config.logLevel||"debug",p=s[d.config.logLevel.toLowerCase()],t in s&&s[t]-1,n.comboSep="&",n.maxURLLength=i,n.ignoreRegistered=t.ignoreRegistered,n.root=e.Env.meta.root,n.timeout=0,n.forceMap={},n.allowRollup=!1,n.filters={},n.required={},n.patterns={},n.moduleInfo={},n.groups=e.merge(e.Env.meta.groups),n.skin=e.merge(e.Env.meta.skin),n.conditions={},n.config=t,n._internal=!0,n._populateConditionsCache(),n.loaded=o[c],n.async=!0,n._inspectPage(),n._internal=!1,n._config(t),n.forceMap=n.force?e.Array.hash(n.force):{},n.testresults=null,e.config.tests&&(n.testresults=e.config.tests),n.sorted=[],n.dirty=!0,n.inserted={},n.skipped={},n.tested={},n.ignoreRegistered&&n._resetModules()},e.Loader.prototype={getModuleInfo:function(t){var n=this.moduleInfo[t],r,i,o,a;return n?n:(r=g.modules,i=s._renderedMods,o=this._internal,i&&i.hasOwnProperty(t)&&!this.ignoreRegistered?this.moduleInfo[t]=e.merge(i[t]):r.hasOwnProperty(t)&&(this._internal=!0,a=this.addModule(r[t],t),a&&a.type===u&&this.isCSSLoaded(a.name,!0)&&(this.loaded[a.name]=!0),this._internal=o),this.moduleInfo[t])},_expandAliases:function(t){var n=[],r=YUI.Env.aliases,i,s;t=e.Array(t);for(i=0;i-1&&(A=n);if(L&&(L[m]||A&&L[A])){O=m,L[A]&&(O=A);for(n=0;n-1},getModule:function(t){if(!t)return null;var n,r,i,s=this.getModuleInfo(t),o=this.patterns;if(!s||s&&s.ext)for(i in o)if(o.hasOwnProperty(i)){n=o[i],n.test||(n.test=this._patternTest);if(n.test(t,i)){r=n;break}}return s?r&&s&&r.configFn&&!s.configFn&&(s.configFn=r.configFn,s.configFn(s)):r&&(n.action?n.action.call(this,t,i):(s=this.addModule(e.merge(r,{test:void 0,temp:!0}),t),s&&r.configFn&&(s.configFn=r.configFn))),s},_rollup:function(){},_reduce:function(e){e=e||this.required;var t,n,r,i,s=this.loadType,o=this.ignore?v.hash(this.ignore):!1;for(t in e)if(e.hasOwnProperty(t)){i=this.getModule(t),((this.loaded[t]||w[t])&&!this.forceMap[t]&&!this.ignoreRegistered||s&&i&&i.type!==s)&&delete e[t],o&&o[t]&&delete e[t],r=i&&i.supersedes;if(r)for(n=0;n0&&(m.running=!0,m.next()())},insert:function(t,n,r){var i=this,s=e.merge(this);delete s.require,delete s.dirty,m.add(function(){i._insert(s,t,n,r)}),this._continue()},loadNext:function(){return},_filter:function(e,t,n){var r=this.filter,i=t&&t in this.filters,s=i&&this.filters[t],o=n||(this.getModuleInfo(t)||{}).group||null;return o&&this.groups[o]&&this.groups[o].filter&&(s=this.groups[o].filter,i=!0),e&&(i&&(r=b.isString(s)?this.FILTER_DEFS[s.toUpperCase()]||null:s),r&&(e=e.replace(new RegExp(r.searchExp,"g"),r.replaceStr))),e},_url:function(e,t,n){return this._filter((n||this.base||"")+e,t)},resolve:function(t,r){var i=this,s={js:[],jsMods:[],css:[],cssMods:[]},o,f=e.config.comboLoader&&e.config.customComboBase;(i.skin.overrides||i.skin.defaultSkin!==l||i.ignoreRegistered)&&i._resetModules(),t&&i.calculate(),r=r||i.sorted,o=function(e){if(e){var t=e.group&&i.groups[e.group]||n,r;t.async===!1&&(e.async=t.async),r=e.fullpath?i._filter(e.fullpath,e.name):i._url(e.path,e.name,t.base||e.base);if(e.attributes||e.async===!1)r={url:r,async:e.async},e.attributes&&(r.attributes=e.attributes);s[e.type].push(r),s[e.type+"Mods"].push(e)}};var c=i.ignoreRegistered?{}:i.inserted,h,p,d,v,m,g,y,b,w,E=!1;for(w=0,b=r.length;wp){n=[];for(g=0,y=f.length;gp&&(l=n.pop(),s=d+n.join(m),e[c].push(b._filter(s,null,v.group)),n=[],l&&n.push(l));n.length&&(s=d+n.join(m),e[c].push(b._filter(s,null,v.group)))}else e[c].push(b._filter(s,null,v.group))}}return e},load:function(e){if(!e)return;var t=this,n=t.resolve(!0);t.data=n,t.onEnd=function(){e.apply(t.context||t,arguments)},t.insert()}}},"3.18.1",{requires:["get","features"]}),YUI.add("loader-rollup",function(e,t){e.Loader.prototype._rollup=function(){var e,t,n,r,i=this.required,s,o=this.moduleInfo,u,a,f;if(this.dirty||!this.rollups){this.rollups={};for(e in o)o.hasOwnProperty(e)&&(n=this.getModule(e),n&&n.rollup&&(this.rollups[e]=n))}for(;;){u=!1;for(e in this.rollups)if(this.rollups.hasOwnProperty(e)&&!i[e]&&(!this.loaded[e]||this.forceMap[e])){n=this.getModule(e),r=n.supersedes||[],s=!1;if(!n.rollup)continue;a=0;for(t=0;t=n.rollup;if(s)break}}s&&(i[e]=!0,u=!0,this.getRequires(n))}if(!u)break}}},"3.18.1",{requires:["loader-base"]}),YUI.add("loader-yui3",function(e,t){YUI.Env[e.version].modules=YUI.Env[e.version].modules||{},e.mix(YUI.Env[e.version].modules,{"align-plugin":{requires:["node-screen","node-pluginhost"]},anim:{use:["anim-base","anim-color","anim-curve","anim-easing","anim-node-plugin","anim-scroll","anim-xy"]},"anim-base":{requires:["base-base","node-style","color-base"]},"anim-color":{requires:["anim-base"]},"anim-curve":{requires:["anim-xy"]},"anim-easing":{requires:["anim-base"]},"anim-node-plugin":{requires:["node-pluginhost","anim-base"]},"anim-scroll":{requires:["anim-base"]},"anim-shape":{requires:["anim-base","anim-easing","anim-color","matrix"]},"anim-shape-transform":{use:["anim-shape"]},"anim-xy":{requires:["anim-base","node-screen"]},app:{use:["app-base","app-content","app-transitions","lazy-model-list","model","model-list","model-sync-rest","model-sync-local","router","view","view-node-map"]},"app-base":{requires:["classnamemanager","pjax-base","router","view"]},"app-content":{requires:["app-base","pjax-content"]},"app-transitions":{requires:["app-base"]},"app-transitions-css":{type:"css"},"app-transitions-native":{condition:{name:"app-transitions-native",test:function(e){var t=e.config.doc,n=t?t.documentElement:null;return n&&n.style?"MozTransition"in n.style||"WebkitTransition"in n.style||"transition"in n.style:!1},trigger:"app-transitions"},requires:["app-transitions","app-transitions-css","parallel","transition"]},"array-extras":{requires:["yui-base"]},"array-invoke":{requires:["yui-base"]},arraylist:{requires:["yui-base"]},"arraylist-add":{requires:["arraylist"]},"arraylist-filter":{requires:["arraylist"]},arraysort:{requires:["yui-base"]},"async-queue":{requires:["event-custom"]},attribute:{use:["attribute-base","attribute-complex"]},"attribute-base":{requires:["attribute-core","attribute-observable","attribute-extras"]},"attribute-complex":{requires:["attribute-base"]},"attribute-core":{requires:["oop"]},"attribute-events":{use:["attribute-observable"]},"attribute-extras":{requires:["oop"]},"attribute-observable":{requires:["event-custom"]},autocomplete:{use:["autocomplete-base","autocomplete-sources","autocomplete-list","autocomplete-plugin"]},"autocomplete-base":{optional:["autocomplete-sources"],requires:["array-extras","base-build","escape","event-valuechange","node-base"]},"autocomplete-filters":{requires:["array-extras","text-wordbreak"]},"autocomplete-filters-accentfold":{requires:["array-extras","text-accentfold","text-wordbreak"]},"autocomplete-highlighters":{requires:["array-extras","highlight-base"]},"autocomplete-highlighters-accentfold":{requires:["array-extras","highlight-accentfold"]},"autocomplete-list":{after:["autocomplete-sources"],lang:["en","es","hu","it"],requires:["autocomplete-base","event-resize","node-screen","selector-css3","shim-plugin","widget","widget-position","widget-position-align"],skinnable:!0},"autocomplete-list-keys":{condition:{name:"autocomplete-list-keys",test:function(e){return!e.UA.ios&&!e.UA.android},trigger:"autocomplete-list"},requires:["autocomplete-list","base-build"]},"autocomplete-plugin":{requires:["autocomplete-list","node-pluginhost"]},"autocomplete-sources":{optional:["io-base","json-parse","jsonp","yql"],requires:["autocomplete-base"]},axes:{use:["axis-numeric","axis-category","axis-time","axis-stacked"]},"axes-base":{use:["axis-numeric-base","axis-category-base","axis-time-base","axis-stacked-base"]},axis:{requires:["dom","widget","widget-position","widget-stack","graphics","axis-base"]},"axis-base":{requires:["classnamemanager","datatype-number","datatype-date","base","event-custom"]},"axis-category":{requires:["axis","axis-category-base"]},"axis-category-base":{requires:["axis-base"]},"axis-numeric":{requires:["axis","axis-numeric-base"]},"axis-numeric-base":{requires:["axis-base"]},"axis-stacked":{requires:["axis-numeric","axis-stacked-base"]},"axis-stacked-base":{requires:["axis-numeric-base"]},"axis-time":{requires:["axis","axis-time-base"]},"axis-time-base":{requires:["axis-base"]},base:{use:["base-base","base-pluginhost","base-build"]},"base-base":{requires:["attribute-base","base-core","base-observable"]},"base-build":{requires:["base-base"]},"base-core":{requires:["attribute-core"]},"base-observable":{requires:["attribute-observable","base-core"]},"base-pluginhost":{requires:["base-base","pluginhost"]},button:{requires:["button-core","cssbutton","widget"]},"button-core":{requires:["attribute-core","classnamemanager","node-base","escape"]},"button-group":{requires:["button-plugin","cssbutton","widget"]},"button-plugin":{requires:["button-core","cssbutton","node-pluginhost"]},cache:{use:["cache-base","cache-offline","cache-plugin"]},"cache-base":{requires:["base"]},"cache-offline":{requires:["cache-base","json"]},"cache-plugin":{requires:["plugin","cache-base"]},calendar:{requires:["calendar-base","calendarnavigator"],skinnable:!0},"calendar-base":{lang:["de","en","es","es-AR","fr","hu","it","ja","nb-NO","nl","pt-BR","ru","zh-Hans","zh-Hans-CN","zh-Hant","zh-Hant-HK","zh-HANT-TW"],requires:["widget","datatype-date","datatype-date-math","cssgrids"],skinnable:!0},calendarnavigator:{requires:["plugin","classnamemanager","datatype-date","node"],skinnable:!0},charts:{use:["charts-base"]},"charts-base":{requires:["dom","event-mouseenter","event-touch","graphics-group","axes","series-pie","series-line","series-marker","series-area","series-spline","series-column","series-bar","series-areaspline","series-combo","series-combospline","series-line-stacked","series-marker-stacked","series-area-stacked","series-spline-stacked","series-column-stacked","series-bar-stacked","series-areaspline-stacked","series-combo-stacked","series-combospline-stacked"]},"charts-legend":{requires:["charts-base"]},classnamemanager:{requires:["yui-base" -]},"clickable-rail":{requires:["slider-base"]},collection:{use:["array-extras","arraylist","arraylist-add","arraylist-filter","array-invoke"]},color:{use:["color-base","color-hsl","color-harmony"]},"color-base":{requires:["yui-base"]},"color-harmony":{requires:["color-hsl"]},"color-hsl":{requires:["color-base"]},"color-hsv":{requires:["color-base"]},console:{lang:["en","es","hu","it","ja"],requires:["yui-log","widget"],skinnable:!0},"console-filters":{requires:["plugin","console"],skinnable:!0},"content-editable":{requires:["node-base","editor-selection","stylesheet","plugin"]},controller:{use:["router"]},cookie:{requires:["yui-base"]},"createlink-base":{requires:["editor-base"]},cssbase:{after:["cssreset","cssfonts","cssgrids","cssreset-context","cssfonts-context","cssgrids-context"],type:"css"},"cssbase-context":{after:["cssreset","cssfonts","cssgrids","cssreset-context","cssfonts-context","cssgrids-context"],type:"css"},cssbutton:{type:"css"},cssfonts:{type:"css"},"cssfonts-context":{type:"css"},cssgrids:{optional:["cssnormalize"],type:"css"},"cssgrids-base":{optional:["cssnormalize"],type:"css"},"cssgrids-responsive":{optional:["cssnormalize"],requires:["cssgrids","cssgrids-responsive-base"],type:"css"},"cssgrids-units":{optional:["cssnormalize"],requires:["cssgrids-base"],type:"css"},cssnormalize:{type:"css"},"cssnormalize-context":{type:"css"},cssreset:{type:"css"},"cssreset-context":{type:"css"},dataschema:{use:["dataschema-base","dataschema-json","dataschema-xml","dataschema-array","dataschema-text"]},"dataschema-array":{requires:["dataschema-base"]},"dataschema-base":{requires:["base"]},"dataschema-json":{requires:["dataschema-base","json"]},"dataschema-text":{requires:["dataschema-base"]},"dataschema-xml":{requires:["dataschema-base"]},datasource:{use:["datasource-local","datasource-io","datasource-get","datasource-function","datasource-cache","datasource-jsonschema","datasource-xmlschema","datasource-arrayschema","datasource-textschema","datasource-polling"]},"datasource-arrayschema":{requires:["datasource-local","plugin","dataschema-array"]},"datasource-cache":{requires:["datasource-local","plugin","cache-base"]},"datasource-function":{requires:["datasource-local"]},"datasource-get":{requires:["datasource-local","get"]},"datasource-io":{requires:["datasource-local","io-base"]},"datasource-jsonschema":{requires:["datasource-local","plugin","dataschema-json"]},"datasource-local":{requires:["base"]},"datasource-polling":{requires:["datasource-local"]},"datasource-textschema":{requires:["datasource-local","plugin","dataschema-text"]},"datasource-xmlschema":{requires:["datasource-local","plugin","datatype-xml","dataschema-xml"]},datatable:{use:["datatable-core","datatable-table","datatable-head","datatable-body","datatable-base","datatable-column-widths","datatable-message","datatable-mutable","datatable-sort","datatable-datasource"]},"datatable-base":{requires:["datatable-core","datatable-table","datatable-head","datatable-body","base-build","widget"],skinnable:!0},"datatable-body":{requires:["datatable-core","view","classnamemanager"]},"datatable-column-widths":{requires:["datatable-base"]},"datatable-core":{requires:["escape","model-list","node-event-delegate"]},"datatable-datasource":{requires:["datatable-base","plugin","datasource-local"]},"datatable-foot":{requires:["datatable-core","view"]},"datatable-formatters":{requires:["datatable-body","datatype-number-format","datatype-date-format","escape"]},"datatable-head":{requires:["datatable-core","view","classnamemanager"]},"datatable-highlight":{requires:["datatable-base","event-hover"],skinnable:!0},"datatable-keynav":{requires:["datatable-base"]},"datatable-message":{lang:["en","fr","es","hu","it"],requires:["datatable-base"],skinnable:!0},"datatable-mutable":{requires:["datatable-base"]},"datatable-paginator":{lang:["en","fr"],requires:["model","view","paginator-core","datatable-foot","datatable-paginator-templates"],skinnable:!0},"datatable-paginator-templates":{requires:["template"]},"datatable-scroll":{requires:["datatable-base","datatable-column-widths","dom-screen"],skinnable:!0},"datatable-sort":{lang:["en","fr","es","hu"],requires:["datatable-base"],skinnable:!0},"datatable-table":{requires:["datatable-core","datatable-head","datatable-body","view","classnamemanager"]},datatype:{use:["datatype-date","datatype-number","datatype-xml"]},"datatype-date":{use:["datatype-date-parse","datatype-date-format","datatype-date-math"]},"datatype-date-format":{lang:["ar","ar-JO","ca","ca-ES","da","da-DK","de","de-AT","de-DE","el","el-GR","en","en-AU","en-CA","en-GB","en-IE","en-IN","en-JO","en-MY","en-NZ","en-PH","en-SG","en-US","es","es-AR","es-BO","es-CL","es-CO","es-EC","es-ES","es-MX","es-PE","es-PY","es-US","es-UY","es-VE","fi","fi-FI","fr","fr-BE","fr-CA","fr-FR","hi","hi-IN","hu","id","id-ID","it","it-IT","ja","ja-JP","ko","ko-KR","ms","ms-MY","nb","nb-NO","nl","nl-BE","nl-NL","pl","pl-PL","pt","pt-BR","ro","ro-RO","ru","ru-RU","sv","sv-SE","th","th-TH","tr","tr-TR","vi","vi-VN","zh-Hans","zh-Hans-CN","zh-Hant","zh-Hant-HK","zh-Hant-TW"]},"datatype-date-math":{requires:["yui-base"]},"datatype-date-parse":{},"datatype-number":{use:["datatype-number-parse","datatype-number-format"]},"datatype-number-format":{},"datatype-number-parse":{requires:["escape"]},"datatype-xml":{use:["datatype-xml-parse","datatype-xml-format"]},"datatype-xml-format":{},"datatype-xml-parse":{},dd:{use:["dd-ddm-base","dd-ddm","dd-ddm-drop","dd-drag","dd-proxy","dd-constrain","dd-drop","dd-scroll","dd-delegate"]},"dd-constrain":{requires:["dd-drag"]},"dd-ddm":{requires:["dd-ddm-base","event-resize"]},"dd-ddm-base":{requires:["node","base","yui-throttle","classnamemanager"]},"dd-ddm-drop":{requires:["dd-ddm"]},"dd-delegate":{requires:["dd-drag","dd-drop-plugin","event-mouseenter"]},"dd-drag":{requires:["dd-ddm-base","selector-css2"]},"dd-drop":{requires:["dd-drag","dd-ddm-drop"]},"dd-drop-plugin":{requires:["dd-drop"]},"dd-gestures":{condition:{name:"dd-gestures" -,trigger:"dd-drag",ua:"touchEnabled"},requires:["dd-drag","event-synthetic","event-gestures"]},"dd-plugin":{optional:["dd-constrain","dd-proxy"],requires:["dd-drag"]},"dd-proxy":{requires:["dd-drag"]},"dd-scroll":{requires:["dd-drag"]},dial:{lang:["en","es","hu"],requires:["widget","dd-drag","event-mouseenter","event-move","event-key","transition","intl"],skinnable:!0},dom:{use:["dom-base","dom-screen","dom-style","selector-native","selector"]},"dom-base":{requires:["dom-core"]},"dom-core":{requires:["oop","features"]},"dom-screen":{requires:["dom-base","dom-style"]},"dom-style":{requires:["dom-base"]},"dom-style-ie":{condition:{name:"dom-style-ie",test:function(e){var t=e.Features.test,n=e.Features.add,r=e.config.win,i=e.config.doc,s="documentElement",o=!1;return n("style","computedStyle",{test:function(){return r&&"getComputedStyle"in r}}),n("style","opacity",{test:function(){return i&&"opacity"in i[s].style}}),o=!t("style","opacity")&&!t("style","computedStyle"),o},trigger:"dom-style"},requires:["dom-style","color-base"]},dump:{requires:["yui-base"]},editor:{use:["frame","editor-selection","exec-command","editor-base","editor-para","editor-br","editor-bidi","editor-tab","createlink-base"]},"editor-base":{requires:["base","frame","node","exec-command","editor-selection"]},"editor-bidi":{requires:["editor-base"]},"editor-br":{requires:["editor-base"]},"editor-inline":{requires:["editor-base","content-editable"]},"editor-lists":{requires:["editor-base"]},"editor-para":{requires:["editor-para-base"]},"editor-para-base":{requires:["editor-base"]},"editor-para-ie":{condition:{name:"editor-para-ie",trigger:"editor-para",ua:"ie",when:"instead"},requires:["editor-para-base"]},"editor-selection":{requires:["node"]},"editor-tab":{requires:["editor-base"]},escape:{requires:["yui-base"]},event:{after:["node-base"],use:["event-base","event-delegate","event-synthetic","event-mousewheel","event-mouseenter","event-key","event-focus","event-resize","event-hover","event-outside","event-touch","event-move","event-flick","event-valuechange","event-tap"]},"event-base":{after:["node-base"],requires:["event-custom-base"]},"event-base-ie":{after:["event-base"],condition:{name:"event-base-ie",test:function(e){var t=e.config.doc&&e.config.doc.implementation;return t&&!t.hasFeature("Events","2.0")},trigger:"node-base"},requires:["node-base"]},"event-contextmenu":{requires:["event-synthetic","dom-screen"]},"event-custom":{use:["event-custom-base","event-custom-complex"]},"event-custom-base":{requires:["oop"]},"event-custom-complex":{requires:["event-custom-base"]},"event-delegate":{requires:["node-base"]},"event-flick":{requires:["node-base","event-touch","event-synthetic"]},"event-focus":{requires:["event-synthetic"]},"event-gestures":{use:["event-flick","event-move"]},"event-hover":{requires:["event-mouseenter"]},"event-key":{requires:["event-synthetic"]},"event-mouseenter":{requires:["event-synthetic"]},"event-mousewheel":{requires:["node-base"]},"event-move":{requires:["node-base","event-touch","event-synthetic"]},"event-outside":{requires:["event-synthetic"]},"event-resize":{requires:["node-base","event-synthetic"]},"event-simulate":{requires:["event-base"]},"event-synthetic":{requires:["node-base","event-custom-complex"]},"event-tap":{requires:["node-base","event-base","event-touch","event-synthetic"]},"event-touch":{requires:["node-base"]},"event-valuechange":{requires:["event-focus","event-synthetic"]},"exec-command":{requires:["frame"]},features:{requires:["yui-base"]},file:{requires:["file-flash","file-html5"]},"file-flash":{requires:["base"]},"file-html5":{requires:["base"]},frame:{requires:["base","node","plugin","selector-css3","yui-throttle"]},"gesture-simulate":{requires:["async-queue","event-simulate","node-screen"]},get:{requires:["yui-base"]},graphics:{requires:["node","event-custom","pluginhost","matrix","classnamemanager"]},"graphics-canvas":{condition:{name:"graphics-canvas",test:function(e){var t=e.config.doc,n=e.config.defaultGraphicEngine&&e.config.defaultGraphicEngine=="canvas",r=t&&t.createElement("canvas"),i=t&&t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");return(!i||n)&&r&&r.getContext&&r.getContext("2d")},trigger:"graphics"},requires:["graphics","color-base"]},"graphics-canvas-default":{condition:{name:"graphics-canvas-default",test:function(e){var t=e.config.doc,n=e.config.defaultGraphicEngine&&e.config.defaultGraphicEngine=="canvas",r=t&&t.createElement("canvas"),i=t&&t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");return(!i||n)&&r&&r.getContext&&r.getContext("2d")},trigger:"graphics"}},"graphics-group":{requires:["graphics"]},"graphics-svg":{condition:{name:"graphics-svg",test:function(e){var t=e.config.doc,n=!e.config.defaultGraphicEngine||e.config.defaultGraphicEngine!="canvas",r=t&&t.createElement("canvas"),i=t&&t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");return i&&(n||!r)},trigger:"graphics"},requires:["graphics"]},"graphics-svg-default":{condition:{name:"graphics-svg-default",test:function(e){var t=e.config.doc,n=!e.config.defaultGraphicEngine||e.config.defaultGraphicEngine!="canvas",r=t&&t.createElement("canvas"),i=t&&t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1");return i&&(n||!r)},trigger:"graphics"}},"graphics-vml":{condition:{name:"graphics-vml",test:function(e){var t=e.config.doc,n=t&&t.createElement("canvas");return t&&!t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")&&(!n||!n.getContext||!n.getContext("2d"))},trigger:"graphics"},requires:["graphics","color-base"]},"graphics-vml-default":{condition:{name:"graphics-vml-default",test:function(e){var t=e.config.doc,n=t&&t.createElement("canvas");return t&&!t.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")&&(!n||!n.getContext||!n.getContext("2d"))},trigger:"graphics"}},handlebars:{use:["handlebars-compiler" -]},"handlebars-base":{requires:[]},"handlebars-compiler":{requires:["handlebars-base"]},highlight:{use:["highlight-base","highlight-accentfold"]},"highlight-accentfold":{requires:["highlight-base","text-accentfold"]},"highlight-base":{requires:["array-extras","classnamemanager","escape","text-wordbreak"]},history:{use:["history-base","history-hash","history-html5"]},"history-base":{requires:["event-custom-complex"]},"history-hash":{after:["history-html5"],requires:["event-synthetic","history-base","yui-later"]},"history-hash-ie":{condition:{name:"history-hash-ie",test:function(e){var t=e.config.doc&&e.config.doc.documentMode;return e.UA.ie&&(!("onhashchange"in e.config.win)||!t||t<8)},trigger:"history-hash"},requires:["history-hash","node-base"]},"history-html5":{optional:["json"],requires:["event-base","history-base","node-base"]},imageloader:{requires:["base-base","node-style","node-screen"]},intl:{requires:["intl-base","event-custom"]},"intl-base":{requires:["yui-base"]},io:{use:["io-base","io-xdr","io-form","io-upload-iframe","io-queue"]},"io-base":{requires:["event-custom-base","querystring-stringify-simple"]},"io-form":{requires:["io-base","node-base"]},"io-nodejs":{condition:{name:"io-nodejs",trigger:"io-base",ua:"nodejs"},requires:["io-base"]},"io-queue":{requires:["io-base","queue-promote"]},"io-upload-iframe":{requires:["io-base","node-base"]},"io-xdr":{requires:["io-base","datatype-xml-parse"]},json:{use:["json-parse","json-stringify"]},"json-parse":{requires:["yui-base"]},"json-parse-shim":{condition:{name:"json-parse-shim",test:function(e){function i(e,t){return e==="ok"?!0:t}var t=e.config.global.JSON,n=Object.prototype.toString.call(t)==="[object JSON]"&&t,r=e.config.useNativeJSONParse!==!1&&!!n;if(r)try{r=n.parse('{"ok":false}',i).ok}catch(s){r=!1}return!r},trigger:"json-parse"},requires:["json-parse"]},"json-stringify":{requires:["yui-base"]},"json-stringify-shim":{condition:{name:"json-stringify-shim",test:function(e){var t=e.config.global.JSON,n=Object.prototype.toString.call(t)==="[object JSON]"&&t,r=e.config.useNativeJSONStringify!==!1&&!!n;if(r)try{r="0"===n.stringify(0)}catch(i){r=!1}return!r},trigger:"json-stringify"},requires:["json-stringify"]},jsonp:{requires:["get","oop"]},"jsonp-url":{requires:["jsonp"]},"lazy-model-list":{requires:["model-list"]},loader:{use:["loader-base","loader-rollup","loader-yui3"]},"loader-base":{requires:["get","features"]},"loader-pathogen-combohandler":{},"loader-pathogen-encoder":{use:["loader-base","loader-rollup","loader-yui3","loader-pathogen-combohandler"]},"loader-rollup":{requires:["loader-base"]},"loader-yui3":{requires:["loader-base"]},matrix:{requires:["yui-base"]},model:{requires:["base-build","escape","json-parse"]},"model-list":{requires:["array-extras","array-invoke","arraylist","base-build","escape","json-parse","model"]},"model-sync-local":{requires:["model","json-stringify"]},"model-sync-rest":{requires:["model","io-base","json-stringify"]},node:{use:["node-base","node-event-delegate","node-pluginhost","node-screen","node-style"]},"node-base":{requires:["event-base","node-core","dom-base","dom-style"]},"node-core":{requires:["dom-core","selector"]},"node-event-delegate":{requires:["node-base","event-delegate"]},"node-event-html5":{requires:["node-base"]},"node-event-simulate":{requires:["node-base","event-simulate","gesture-simulate"]},"node-flick":{requires:["classnamemanager","transition","event-flick","plugin"],skinnable:!0},"node-focusmanager":{requires:["attribute","node","plugin","node-event-simulate","event-key","event-focus"]},"node-load":{requires:["node-base","io-base"]},"node-menunav":{requires:["node","classnamemanager","plugin","node-focusmanager"],skinnable:!0},"node-pluginhost":{requires:["node-base","pluginhost"]},"node-screen":{requires:["dom-screen","node-base"]},"node-scroll-info":{requires:["array-extras","base-build","event-resize","node-pluginhost","plugin","selector"]},"node-style":{requires:["dom-style","node-base"]},oop:{requires:["yui-base"]},overlay:{requires:["widget","widget-stdmod","widget-position","widget-position-align","widget-stack","widget-position-constrain"],skinnable:!0},paginator:{requires:["paginator-core"]},"paginator-core":{requires:["base"]},"paginator-url":{requires:["paginator"]},panel:{requires:["widget","widget-autohide","widget-buttons","widget-modality","widget-position","widget-position-align","widget-position-constrain","widget-stack","widget-stdmod"],skinnable:!0},parallel:{requires:["yui-base"]},pjax:{requires:["pjax-base","pjax-content"]},"pjax-base":{requires:["classnamemanager","node-event-delegate","router"]},"pjax-content":{requires:["io-base","node-base","router"]},"pjax-plugin":{requires:["node-pluginhost","pjax","plugin"]},plugin:{requires:["base-base"]},pluginhost:{use:["pluginhost-base","pluginhost-config"]},"pluginhost-base":{requires:["yui-base"]},"pluginhost-config":{requires:["pluginhost-base"]},promise:{requires:["timers"]},querystring:{use:["querystring-parse","querystring-stringify"]},"querystring-parse":{requires:["yui-base","array-extras"]},"querystring-parse-simple":{requires:["yui-base"]},"querystring-stringify":{requires:["yui-base"]},"querystring-stringify-simple":{requires:["yui-base"]},"queue-promote":{requires:["yui-base"]},"range-slider":{requires:["slider-base","slider-value-range","clickable-rail"]},recordset:{use:["recordset-base","recordset-sort","recordset-filter","recordset-indexer"]},"recordset-base":{requires:["base","arraylist"]},"recordset-filter":{requires:["recordset-base","array-extras","plugin"]},"recordset-indexer":{requires:["recordset-base","plugin"]},"recordset-sort":{requires:["arraysort","recordset-base","plugin"]},resize:{use:["resize-base","resize-proxy","resize-constrain"]},"resize-base":{requires:["base","widget","event","oop","dd-drag","dd-delegate","dd-drop"],skinnable:!0},"resize-constrain":{requires:["plugin","resize-base"]},"resize-plugin":{optional:["resize-constrain"],requires:["resize-base","plugin"]},"resize-proxy" -:{requires:["plugin","resize-base"]},router:{optional:["querystring-parse"],requires:["array-extras","base-build","history"]},scrollview:{requires:["scrollview-base","scrollview-scrollbars"]},"scrollview-base":{requires:["widget","event-gestures","event-mousewheel","transition"],skinnable:!0},"scrollview-base-ie":{condition:{name:"scrollview-base-ie",trigger:"scrollview-base",ua:"ie"},requires:["scrollview-base"]},"scrollview-list":{requires:["plugin","classnamemanager"],skinnable:!0},"scrollview-paginator":{requires:["plugin","classnamemanager"]},"scrollview-scrollbars":{requires:["classnamemanager","transition","plugin"],skinnable:!0},selector:{requires:["selector-native"]},"selector-css2":{condition:{name:"selector-css2",test:function(e){var t=e.config.doc,n=t&&!("querySelectorAll"in t);return n},trigger:"selector"},requires:["selector-native"]},"selector-css3":{requires:["selector-native","selector-css2"]},"selector-native":{requires:["dom-base"]},"series-area":{requires:["series-cartesian","series-fill-util"]},"series-area-stacked":{requires:["series-stacked","series-area"]},"series-areaspline":{requires:["series-area","series-curve-util"]},"series-areaspline-stacked":{requires:["series-stacked","series-areaspline"]},"series-bar":{requires:["series-marker","series-histogram-base"]},"series-bar-stacked":{requires:["series-stacked","series-bar"]},"series-base":{requires:["graphics","axis-base"]},"series-candlestick":{requires:["series-range"]},"series-cartesian":{requires:["series-base"]},"series-column":{requires:["series-marker","series-histogram-base"]},"series-column-stacked":{requires:["series-stacked","series-column"]},"series-combo":{requires:["series-cartesian","series-line-util","series-plot-util","series-fill-util"]},"series-combo-stacked":{requires:["series-stacked","series-combo"]},"series-combospline":{requires:["series-combo","series-curve-util"]},"series-combospline-stacked":{requires:["series-combo-stacked","series-curve-util"]},"series-curve-util":{},"series-fill-util":{},"series-histogram-base":{requires:["series-cartesian","series-plot-util"]},"series-line":{requires:["series-cartesian","series-line-util"]},"series-line-stacked":{requires:["series-stacked","series-line"]},"series-line-util":{},"series-marker":{requires:["series-cartesian","series-plot-util"]},"series-marker-stacked":{requires:["series-stacked","series-marker"]},"series-ohlc":{requires:["series-range"]},"series-pie":{requires:["series-base","series-plot-util"]},"series-plot-util":{},"series-range":{requires:["series-cartesian"]},"series-spline":{requires:["series-line","series-curve-util"]},"series-spline-stacked":{requires:["series-stacked","series-spline"]},"series-stacked":{requires:["axis-stacked"]},"shim-plugin":{requires:["node-style","node-pluginhost"]},slider:{use:["slider-base","slider-value-range","clickable-rail","range-slider"]},"slider-base":{requires:["widget","dd-constrain","event-key"],skinnable:!0},"slider-value-range":{requires:["slider-base"]},sortable:{requires:["dd-delegate","dd-drop-plugin","dd-proxy"]},"sortable-scroll":{requires:["dd-scroll","sortable"]},stylesheet:{requires:["yui-base"]},substitute:{optional:["dump"],requires:["yui-base"]},swf:{requires:["event-custom","node","swfdetect","escape"]},swfdetect:{requires:["yui-base"]},tabview:{requires:["widget","widget-parent","widget-child","tabview-base","node-pluginhost","node-focusmanager"],skinnable:!0},"tabview-base":{requires:["node-event-delegate","classnamemanager"]},"tabview-plugin":{requires:["tabview-base"]},template:{use:["template-base","template-micro"]},"template-base":{requires:["yui-base"]},"template-micro":{requires:["escape"]},test:{requires:["event-simulate","event-custom","json-stringify"]},"test-console":{requires:["console-filters","test","array-extras"],skinnable:!0},text:{use:["text-accentfold","text-wordbreak"]},"text-accentfold":{requires:["array-extras","text-data-accentfold"]},"text-data-accentfold":{requires:["yui-base"]},"text-data-wordbreak":{requires:["yui-base"]},"text-wordbreak":{requires:["array-extras","text-data-wordbreak"]},timers:{requires:["yui-base"]},transition:{requires:["node-style"]},"transition-timer":{condition:{name:"transition-timer",test:function(e){var t=e.config.doc,n=t?t.documentElement:null,r=!0;return n&&n.style&&(r=!("MozTransition"in n.style||"WebkitTransition"in n.style||"transition"in n.style)),r},trigger:"transition"},requires:["transition"]},tree:{requires:["base-build","tree-node"]},"tree-labelable":{requires:["tree"]},"tree-lazy":{requires:["base-pluginhost","plugin","tree"]},"tree-node":{},"tree-openable":{requires:["tree"]},"tree-selectable":{requires:["tree"]},"tree-sortable":{requires:["tree"]},uploader:{requires:["uploader-html5","uploader-flash"]},"uploader-flash":{requires:["swfdetect","escape","widget","base","cssbutton","node","event-custom","uploader-queue"]},"uploader-html5":{requires:["widget","node-event-simulate","file-html5","uploader-queue"]},"uploader-queue":{requires:["base"]},view:{requires:["base-build","node-event-delegate"]},"view-node-map":{requires:["view"]},widget:{use:["widget-base","widget-htmlparser","widget-skin","widget-uievents"]},"widget-anim":{requires:["anim-base","plugin","widget"]},"widget-autohide":{requires:["base-build","event-key","event-outside","widget"]},"widget-base":{requires:["attribute","base-base","base-pluginhost","classnamemanager","event-focus","node-base","node-style"],skinnable:!0},"widget-base-ie":{condition:{name:"widget-base-ie",trigger:"widget-base",ua:"ie"},requires:["widget-base"]},"widget-buttons":{requires:["button-plugin","cssbutton","widget-stdmod"]},"widget-child":{requires:["base-build","widget"]},"widget-htmlparser":{requires:["widget-base"]},"widget-modality":{requires:["base-build","event-outside","widget"],skinnable:!0},"widget-parent":{requires:["arraylist","base-build","widget"]},"widget-position":{requires:["base-build","node-screen","widget"]},"widget-position-align":{requires:["widget-position" -]},"widget-position-constrain":{requires:["widget-position"]},"widget-skin":{requires:["widget-base"]},"widget-stack":{requires:["base-build","widget"],skinnable:!0},"widget-stdmod":{requires:["base-build","widget"]},"widget-uievents":{requires:["node-event-delegate","widget-base"]},yql:{requires:["oop"]},"yql-jsonp":{condition:{name:"yql-jsonp",test:function(e){return!e.UA.nodejs&&!e.UA.winjs},trigger:"yql"},requires:["yql","jsonp","jsonp-url"]},"yql-nodejs":{condition:{name:"yql-nodejs",trigger:"yql",ua:"nodejs"},requires:["yql"]},"yql-winjs":{condition:{name:"yql-winjs",trigger:"yql",ua:"winjs"},requires:["yql"]},yui:{},"yui-base":{},"yui-later":{requires:["yui-base"]},"yui-log":{requires:["yui-base"]},"yui-throttle":{requires:["yui-base"]}}),YUI.Env[e.version].md5="2fd2be6b12ee9f999b4367499ae61aae"},"3.18.1",{requires:["loader-base"]}),YUI.add("yui",function(e,t){},"3.18.1",{use:["yui-base","get","features","intl-base","yui-log","yui-later","loader-base","loader-rollup","loader-yui3"]}); -YUI.add("attribute-base",function(e,t){function n(){e.AttributeCore.apply(this,arguments),e.AttributeObservable.apply(this,arguments),e.AttributeExtras.apply(this,arguments)}e.mix(n,e.AttributeCore,!1,null,1),e.mix(n,e.AttributeExtras,!1,null,1),e.mix(n,e.AttributeObservable,!0,null,1),n.INVALID_VALUE=e.AttributeCore.INVALID_VALUE,n._ATTR_CFG=e.AttributeCore._ATTR_CFG.concat(e.AttributeObservable._ATTR_CFG),n.protectAttrs=e.AttributeCore.protectAttrs,e.Attribute=n},"3.18.1",{requires:["attribute-core","attribute-observable","attribute-extras"]}); -YUI.add("attribute-core",function(e,t){function b(e,t,n){this._yuievt=null,this._initAttrHost(e,t,n)}e.State=function(){this.data={}},e.State.prototype={add:function(e,t,n){var r=this.data[e];r||(r=this.data[e]={}),r[t]=n},addAll:function(e,t){var n=this.data[e],r;n||(n=this.data[e]={});for(r in t)t.hasOwnProperty(r)&&(n[r]=t[r])},remove:function(e,t){var n=this.data[e];n&&delete n[t]},removeAll:function(t,n){var r;n?e.each(n,function(e,n){this.remove(t,typeof n=="string"?n:e)},this):(r=this.data,t in r&&delete r[t])},get:function(e,t){var n=this.data[e];if(n)return n[t]},getAll:function(e,t){var n=this.data[e],r,i;if(t)i=n;else if(n){i={};for(r in n)n.hasOwnProperty(r)&&(i[r]=n[r])}return i}};var n=e.Object,r=e.Lang,i=".",s="getter",o="setter",u="readOnly",a="writeOnce",f="initOnly",l="validator",c="value",h="valueFn",p="lazyAdd",d="added",v="_bypassProxy",m="initValue",g="lazy",y;b.INVALID_VALUE={},y=b.INVALID_VALUE,b._ATTR_CFG=[o,s,l,c,h,a,u,p,v],b.protectAttrs=function(t){if(t){t=e.merge(t);for(var n in t)t.hasOwnProperty(n)&&(t[n]=e.merge(t[n]))}return t},b.prototype={_initAttrHost:function(t,n,r){this._state=new e.State,this._initAttrs(t,n,r)},addAttr:function(e,t,n){var r=this,i=r._state,s=i.data,o,u,a;t=t||{},p in t&&(n=t[p]),u=i.get(e,d);if(n&&!u)i.data[e]={lazy:t,added:!0};else if(!u||t.isLazyAdd)a=c in t,a&&(o=t.value,t.value=undefined),t.added=!0,t.initializing=!0,s[e]=t,a&&r.set(e,o),t.initializing=!1;return r},attrAdded:function(e){return!!this._state.get(e,d)},get:function(e){return this._getAttr(e)},_isLazyAttr:function(e){return this._state.get(e,g)},_addLazyAttr:function(e,t){var n=this._state;t=t||n.get(e,g),t&&(n.data[e].lazy=undefined,t.isLazyAdd=!0,this.addAttr(e,t))},set:function(e,t,n){return this._setAttr(e,t,n)},_set:function(e,t,n){return this._setAttr(e,t,n,!0)},_setAttr:function(t,r,s,o){var u=!0,a=this._state,l=this._stateProxy,c=this._tCfgs,h,p,d,v,m,g,y;return t.indexOf(i)!==-1&&(d=t,v=t.split(i),t=v.shift()),c&&c[t]&&this._addOutOfOrder(t,c[t]),h=a.data[t]||{},h.lazy&&(h=h.lazy,this._addLazyAttr(t,h)),p=h.value===undefined,l&&t in l&&!h._bypassProxy&&(p=!1),g=h.writeOnce,y=h.initializing,!p&&!o&&(g&&(u=!1),h.readOnly&&(u=!1)),!y&&!o&&g===f&&(u=!1),u&&(p||(m=this.get(t)),v&&(r=n.setValue(e.clone(m),v,r),r===undefined&&(u=!1)),u&&(!this._fireAttrChange||y?this._setAttrVal(t,d,m,r,s,h):this._fireAttrChange(t,d,m,r,s,h))),this},_addOutOfOrder:function(e,t){var n={};n[e]=t,delete this._tCfgs[e],this._addAttrs(n,this._tVals)},_getAttr:function(e){var t=e,r=this._tCfgs,s,o,u,a;return e.indexOf(i)!==-1&&(s=e.split(i),e=s.shift()),r&&r[e]&&this._addOutOfOrder(e,r[e]),a=this._state.data[e]||{},a.lazy&&(a=a.lazy,this._addLazyAttr(e,a)),u=this._getStateVal(e,a),o=a.getter,o&&!o.call&&(o=this[o]),u=o?o.call(this,u,t):u,u=s?n.getValue(u,s):u,u},_getStateVal:function(e,t){var n=this._stateProxy;return t||(t=this._state.getAll(e)||{}),n&&e in n&&!t._bypassProxy?n[e]:t.value},_setStateVal:function(e,t){var n=this._stateProxy;n&&e in n&&!this._state.get(e,v)?n[e]=t:this._state.add(e,c,t)},_setAttrVal:function(e,t,n,i,s,o){var u=this,a=!0,f=o||this._state.data[e]||{},l=f.validator,c=f.setter,h=f.initializing,p=this._getStateVal(e,f),d=t||e,v,g;return l&&(l.call||(l=this[l]),l&&(g=l.call(u,i,d,s),!g&&h&&(i=f.defaultValue,g=!0))),!l||g?(c&&(c.call||(c=this[c]),c&&(v=c.call(u,i,d,s),v===y?h?i=f.defaultValue:a=!1:v!==undefined&&(i=v))),a&&(!t&&i===p&&!r.isObject(i)?a=!1:(m in f||(f.initValue=i),u._setStateVal(e,i)))):a=!1,a},setAttrs:function(e,t){return this._setAttrs(e,t)},_setAttrs:function(e,t){var n;for(n in e)e.hasOwnProperty(n)&&this.set(n,e[n],t);return this},getAttrs:function(e){return this._getAttrs(e)},_getAttrs:function(e){var t={},r,i,s,o=e===!0;if(!e||o)e=n.keys(this._state.data);for(i=0,s=e.length;i=0;--u){n=e[u];for(t in n)n.hasOwnProperty(t)&&(s=d({},n[t],f),o=null,t.indexOf(i)!==-1&&(o=t.split(i),t=o.shift()),l=c[t],o&&l&&l.value?(r=c._subAttrs,r||(r=c._subAttrs={}),r[t]||(r[t]={}),r[t][o.join(i)]={value:s.value,path:o}):o||(l?(l.valueFn&&a in s&&(l.valueFn=null),d(l,s,f)):c[t]=s))}return c},_initHierarchy:function(e){var t=this._lazyAddAttrs,n,r,i,s,o,a,f,l,c,h,p,d=[],v=this._getClasses(),m=this._getAttrCfgs(),g=v.length-1;for(o=g;o>=0;o--){n=v[o],r=n.prototype,h=n._yuibuild&&n._yuibuild.exts,r.hasOwnProperty(u)&&(d[d.length]=r.initializer);if(h)for(a=0,f=h.length;a-1&&(t=r.getValue(n[e.selectedIndex]))),t}});var f,l,c;e.mix(e.DOM,{hasClass:function(t,n){var r=e.DOM._getRegExp("(?:^|\\s+)"+n+"(?:\\s+|$)");return r.test(t.className)},addClass:function(t,n){e.DOM.hasClass(t,n)||(t.className=e.Lang.trim([t.className,n].join(" ")))},removeClass:function(t,n){n&&l(t,n)&&(t.className=e.Lang.trim(t.className.replace(e.DOM._getRegExp("(?:^|\\s+)"+n+"(?:\\s+|$)")," ")),l(t,n)&&c(t,n))},replaceClass:function(e,t,n){c(e,t),f(e,n)},toggleClass:function(e,t,n){var r=n!==undefined?n:!l(e,t);r?f(e,t):c(e,t)}}),l=e.DOM.hasClass,c=e.DOM.removeClass,f=e.DOM.addClass;var h=/<([a-z]+)/i,r=e.DOM,u=e.Features.add,a=e.Features.test,p={},d=function(t,n){var r=e.config.doc.createElement("div"),i=!0;r.innerHTML=t;if(!r.firstChild||r.firstChild.tagName!==n.toUpperCase())i=!1;return i},v=/(?:\/(?:thead|tfoot|tbody|caption|col|colgroup)>)+\s*0&&(t.selectedIndex=y-1),a},wrap:function(t,n){var r=n&&n.nodeType?n:e.DOM.create(n),i=r.getElementsByTagName("*");i.length&&(r=i[i.length-1]),t.parentNode&&t.parentNode.replaceChild(r,t),r.appendChild(t)},unwrap:function(e){var t=e.parentNode,n=t.lastChild,r=e,i;if(t){i=t.parentNode;if(i){e=t.firstChild;while(e!==n)r=e.nextSibling,i.insertBefore(e,t),e=r;i.replaceChild(n,t)}else t.removeChild(e)}}}),u("innerhtml","table",{test:function(){var t=e.config.doc.createElement("table");try{t.innerHTML=""}catch(n){return!1}return t.firstChild&&t.firstChild.nodeName==="TBODY"}}),u("innerhtml-div","tr",{test:function(){return d("","tr")}}),u("innerhtml-div","script",{test:function(){return d("","script")}}),a("innerhtml","table")||(p.tbody=function(t,n){var i=r.create(m+t+g,n),s=e.DOM._children(i,"tbody")[0];return i.children.length>1&&s&&!v.test(t)&&s.parentNode.removeChild(s),i}),a("innerhtml-div","script")||(p.script=function(e,t){var n=t.createElement("div");return n.innerHTML="-"+e,n.removeChild(n.firstChild),n},p.link=p.style=p.script),a("innerhtml-div","tr")||(e.mix(p,{option:function(e,t){return r.create('",t)},tr:function(e,t){return r.create(""+e+"",t)},td:function(e,t){return r.create(""+e+"",t)},col:function(e,t){return r.create(""+e+"",t)},tbody:"table"}),e.mix(p,{legend:"fieldset" -,th:p.td,thead:p.tbody,tfoot:p.tbody,caption:p.tbody,colgroup:p.tbody,optgroup:p.option})),r.creators=p,e.mix(e.DOM,{setWidth:function(t,n){e.DOM._setSize(t,"width",n)},setHeight:function(t,n){e.DOM._setSize(t,"height",n)},_setSize:function(e,t,n){n=n>0?n:0;var r=0;e.style[t]=n+"px",r=t==="height"?e.offsetHeight:e.offsetWidth,r>n&&(n-=r-n,n<0&&(n=0),e.style[t]=n+"px")}})},"3.18.1",{requires:["dom-core"]}); -YUI.add("dom-core",function(e,t){var n="nodeType",r="ownerDocument",i="documentElement",s="defaultView",o="parentWindow",u="tagName",a="parentNode",f="previousSibling",l="nextSibling",c="contains",h="compareDocumentPosition",p=[],d=function(){var t=e.config.doc.createElement("div"),n=t.appendChild(e.config.doc.createTextNode("")),r=!1;try{r=t.contains(n)}catch(i){}return r}(),v={byId:function(e,t){return v.allById(e,t)[0]||null},getId:function(e){var t;return e.id&&!e.id.tagName&&!e.id.item?t=e.id:e.attributes&&e.attributes.id&&(t=e.attributes.id.value),t},setId:function(e,t){e.setAttribute?e.setAttribute("id",t):e.id=t},ancestor:function(e,t,n,r){var i=null;return n&&(i=!t||t(e)?e:null),i||v.elementByAxis(e,a,t,null,r)},ancestors:function(e,t,n,r){var i=e,s=[];while(i=v.ancestor(i,t,n,r)){n=!1;if(i){s.unshift(i);if(r&&r(i))return s}}return s},elementByAxis:function(e,t,n,r,i){while(e&&(e=e[t])){if((r||e[u])&&(!n||n(e)))return e;if(i&&i(e))return null}return null},contains:function(e,t){var r=!1;if(!t||!e||!t[n]||!e[n])r=!1;else if(e[c]&&(t[n]===1||d))r=e[c](t);else if(e[h]){if(e===t||!!(e[h](t)&16))r=!0}else r=v._bruteContains(e,t);return r},inDoc:function(e,t){var n=!1,s;return e&&e.nodeType&&(t||(t=e[r]),s=t[i],s&&s.contains&&e.tagName?n=s.contains(e):n=v.contains(s,e)),n},allById:function(t,n){n=n||e.config.doc;var r=[],i=[],s,o;if(n.querySelectorAll)i=n.querySelectorAll('[id="'+t+'"]');else if(n.all){r=n.all(t);if(r){r.nodeName&&(r.id===t?(i.push(r),r=p):r=[r]);if(r.length)for(s=0;o=r[s++];)(o.id===t||o.attributes&&o.attributes.id&&o.attributes.id.value===t)&&i.push(o)}}else i=[v._getDoc(n).getElementById(t)];return i},isWindow:function(e){return!!(e&&e.scrollTo&&e.document)},_removeChildNodes:function(e){while(e.firstChild)e.removeChild(e.firstChild)},siblings:function(e,t){var n=[],r=e;while(r=r[f])r[u]&&(!t||t(r))&&n.unshift(r);r=e;while(r=r[l])r[u]&&(!t||t(r))&&n.push(r);return n},_bruteContains:function(e,t){while(t){if(e===t)return!0;t=t.parentNode}return!1},_getRegExp:function(e,t){return t=t||"",v._regexCache=v._regexCache||{},v._regexCache[e+t]||(v._regexCache[e+t]=new RegExp(e,t)),v._regexCache[e+t]},_getDoc:function(t){var i=e.config.doc;return t&&(i=t[n]===9?t:t[r]||t.document||e.config.doc),i},_getWin:function(t){var n=v._getDoc(t);return n[s]||n[o]||e.config.win},_batch:function(e,t,n,r,i,s){t=typeof t=="string"?v[t]:t;var o,u=0,a,f;if(t&&e)while(a=e[u++])o=o=t.call(v,a,n,r,i,s),typeof o!="undefined"&&(f||(f=[]),f.push(o));return typeof f!="undefined"?f:e},generateID:function(t){var n=t.id;return n||(n=e.stamp(t),t.id=n),n}};e.DOM=v},"3.18.1",{requires:["oop","features"]}); -YUI.add("dom-screen",function(e,t){(function(e){var t="documentElement",n="compatMode",r="position",i="fixed",s="relative",o="left",u="top",a="BackCompat",f="medium",l="borderLeftWidth",c="borderTopWidth",h="getBoundingClientRect",p="getComputedStyle",d=e.DOM,v=/^t(?:able|d|h)$/i,m;e.UA.ie&&(e.config.doc[n]!=="BackCompat"?m=t:m="body"),e.mix(d,{winHeight:function(e){var t=d._getWinSize(e).height;return t},winWidth:function(e){var t=d._getWinSize(e).width;return t},docHeight:function(e){var t=d._getDocSize(e).height;return Math.max(t,d._getWinSize(e).height)},docWidth:function(e){var t=d._getDocSize(e).width;return Math.max(t,d._getWinSize(e).width)},docScrollX:function(n,r){r=r||n?d._getDoc(n):e.config.doc;var i=r.defaultView,s=i?i.pageXOffset:0;return Math.max(r[t].scrollLeft,r.body.scrollLeft,s)},docScrollY:function(n,r){r=r||n?d._getDoc(n):e.config.doc;var i=r.defaultView,s=i?i.pageYOffset:0;return Math.max(r[t].scrollTop,r.body.scrollTop,s)},getXY:function(){return e.config.doc[t][h]?function(r){var i=null,s,o,u,f,l,c,p,v,g,y;if(r&&r.tagName){p=r.ownerDocument,u=p[n],u!==a?y=p[t]:y=p.body,y.contains?g=y.contains(r):g=e.DOM.contains(y,r);if(g){v=p.defaultView,v&&"pageXOffset"in v?(s=v.pageXOffset,o=v.pageYOffset):(s=m?p[m].scrollLeft:d.docScrollX(r,p),o=m?p[m].scrollTop:d.docScrollY(r,p)),e.UA.ie&&(!p.documentMode||p.documentMode<8||u===a)&&(l=y.clientLeft,c=y.clientTop),f=r[h](),i=[f.left,f.top];if(l||c)i[0]-=l,i[1]-=c;if(o||s)if(!e.UA.ios||e.UA.ios>=4.2)i[0]+=s,i[1]+=o}else i=d._getOffset(r)}return i}:function(t){var n=null,s,o,u,a,f;if(t)if(d.inDoc(t)){n=[t.offsetLeft,t.offsetTop],s=t.ownerDocument,o=t,u=e.UA.gecko||e.UA.webkit>519?!0:!1;while(o=o.offsetParent)n[0]+=o.offsetLeft,n[1]+=o.offsetTop,u&&(n=d._calcBorders(o,n));if(d.getStyle(t,r)!=i){o=t;while(o=o.parentNode){a=o.scrollTop,f=o.scrollLeft,e.UA.gecko&&d.getStyle(o,"overflow")!=="visible"&&(n=d._calcBorders(o,n));if(a||f)n[0]-=f,n[1]-=a}n[0]+=d.docScrollX(t,s),n[1]+=d.docScrollY(t,s)}else n[0]+=d.docScrollX(t,s),n[1]+=d.docScrollY(t,s)}else n=d._getOffset(t);return n}}(),getScrollbarWidth:e.cached(function(){var t=e.config.doc,n=t.createElement("div"),r=t.getElementsByTagName("body")[0],i=.1;return r&&(n.style.cssText="position:absolute;visibility:hidden;overflow:scroll;width:20px;",n.appendChild(t.createElement("p")).style.height="1px",r.insertBefore(n,r.firstChild),i=n.offsetWidth-n.clientWidth,r.removeChild(n)),i},null,.1),getX:function(e){return d.getXY(e)[0]},getY:function(e){return d.getXY(e)[1]},setXY:function(e,t,n){var i=d.setStyle,a,f,l,c;e&&t&&(a=d.getStyle(e,r),f=d._getOffset(e),a=="static"&&(a=s,i(e,r,a)),c=d.getXY(e),t[0]!==null&&i(e,o,t[0]-c[0]+f[0]+"px"),t[1]!==null&&i(e,u,t[1]-c[1]+f[1]+"px"),n||(l=d.getXY(e),(l[0]!==t[0]||l[1]!==t[1])&&d.setXY(e,t,!0)))},setX:function(e,t){return d.setXY(e,[t,null])},setY:function(e,t){return d.setXY(e,[null,t])},swapXY:function(e,t){var n=d.getXY(e);d.setXY(e,d.getXY(t)),d.setXY(t,n)},_calcBorders:function(t,n){var r=parseInt(d[p](t,c),10)||0,i=parseInt(d[p](t,l),10)||0;return e.UA.gecko&&v.test(t.tagName)&&(r=0,i=0),n[0]+=i,n[1]+=r,n},_getWinSize:function(r,i){i=i||r?d._getDoc(r):e.config.doc;var s=i.defaultView||i.parentWindow,o=i[n],u=s.innerHeight,a=s.innerWidth,f=i[t];return o&&!e.UA.opera&&(o!="CSS1Compat"&&(f=i.body),u=f.clientHeight,a=f.clientWidth),{height:u,width:a}},_getDocSize:function(r){var i=r?d._getDoc(r):e.config.doc,s=i[t];return i[n]!="CSS1Compat"&&(s=i.body),{height:s.scrollHeight,width:s.scrollWidth}}})})(e),function(e){var t="top",n="right",r="bottom",i="left",s=function(e,s){var o=Math.max(e[t],s[t]),u=Math.min(e[n],s[n]),a=Math.min(e[r],s[r]),f=Math.max(e[i],s[i]),l={};return l[t]=o,l[n]=u,l[r]=a,l[i]=f,l},o=e.DOM;e.mix(o,{region:function(e){var t=o.getXY(e),n=!1;return e&&t&&(n=o._getRegion(t[1],t[0]+e.offsetWidth,t[1]+e.offsetHeight,t[0])),n},intersect:function(u,a,f){var l=f||o.region(u),c={},h=a,p;if(h.tagName)c=o.region(h);else{if(!e.Lang.isObject(a))return!1;c=a}return p=s(c,l),{top:p[t],right:p[n],bottom:p[r],left:p[i],area:(p[r]-p[t])*(p[n]-p[i]),yoff:p[r]-p[t],xoff:p[n]-p[i],inRegion:o.inRegion(u,a,!1,f)}},inRegion:function(u,a,f,l){var c={},h=l||o.region(u),p=a,d;if(p.tagName)c=o.region(p);else{if(!e.Lang.isObject(a))return!1;c=a}return f?h[i]>=c[i]&&h[n]<=c[n]&&h[t]>=c[t]&&h[r]<=c[r]:(d=s(c,h),d[r]>=d[t]&&d[n]>=d[i]?!0:!1)},inViewportRegion:function(e,t,n){return o.inRegion(e,o.viewportRegion(e),t,n)},_getRegion:function(e,s,o,u){var a={};return a[t]=a[1]=e,a[i]=a[0]=u,a[r]=o,a[n]=s,a.width=a[n]-a[i],a.height=a[r]-a[t],a},viewportRegion:function(t){t=t||e.config.doc.documentElement;var n=!1,r,i;return t&&(r=o.docScrollX(t),i=o.docScrollY(t),n=o._getRegion(i,o.winWidth(t)+r,i+o.winHeight(t),r)),n}})}(e)},"3.18.1",{requires:["dom-base","dom-style"]}); -YUI.add("dom-style",function(e,t){var n="documentElement",r="defaultView",i="ownerDocument",s="style",o="float",u="cssFloat",a="styleFloat",f="transparent",l="getComputedStyle",c="getBoundingClientRect",h=e.config.doc,p=e.DOM,d,v,m=["WebkitTransform","MozTransform","OTransform","msTransform","transform"],g=/width|height|top|left|right|bottom|margin|padding/i;e.Array.each(m,function(e){e in h[n].style&&(d=e,v=e+"Origin")}),e.mix(p,{DEFAULT_UNIT:"px",CUSTOM_STYLES:{},setStyle:function(e,t,n,r){r=r||e.style;var i=p.CUSTOM_STYLES;if(r){n===null||n===""?n="":!isNaN(Number(n))&&g.test(t)&&(n+=p.DEFAULT_UNIT);if(t in i){if(i[t].set){i[t].set(e,n,r);return}typeof i[t]=="string"&&(t=i[t])}else t===""&&(t="cssText",n="");r[t]=n}},getStyle:function(e,t,n){n=n||e.style;var r=p.CUSTOM_STYLES,i="";if(n){if(t in r){if(r[t].get)return r[t].get(e,t,n);typeof r[t]=="string"&&(t=r[t])}i=n[t],i===""&&(i=p[l](e,t))}return i},setStyles:function(t,n){var r=t.style;e.each(n,function(e,n){p.setStyle(t,n,e,r)},p)},getComputedStyle:function(e,t){var n="",o=e[i],u;return e[s]&&o[r]&&o[r][l]&&(u=o[r][l](e,null),u&&(n=u[t])),n}}),h[n][s][u]!==undefined?p.CUSTOM_STYLES[o]=u:h[n][s][a]!==undefined&&(p.CUSTOM_STYLES[o]=a),e.UA.webkit&&(p[l]=function(e,t){var n=e[i][r],s=n[l](e,"")[t];return s==="rgba(0, 0, 0, 0)"&&(s=f),s}),e.DOM._getAttrOffset=function(t,n){var r=e.DOM[l](t,n),i=t.offsetParent,s,o,u;return r==="auto"&&(s=e.DOM.getStyle(t,"position"),s==="static"||s==="relative"?r=0:i&&i[c]&&(o=i[c]()[n],u=t[c]()[n],n==="left"||n==="top"?r=u-o:r=o-t[c]()[n])),r},e.DOM._getOffset=function(e){var t,n=null;return e&&(t=p.getStyle(e,"position"),n=[parseInt(p[l](e,"left"),10),parseInt(p[l](e,"top"),10)],isNaN(n[0])&&(n[0]=parseInt(p.getStyle(e,"left"),10),isNaN(n[0])&&(n[0]=t==="relative"?0:e.offsetLeft||0)),isNaN(n[1])&&(n[1]=parseInt(p.getStyle(e,"top"),10),isNaN(n[1])&&(n[1]=t==="relative"?0:e.offsetTop||0))),n},d&&(p.CUSTOM_STYLES.transform={set:function(e,t,n){n[d]=t},get:function(e){return p[l](e,d)}},p.CUSTOM_STYLES.transformOrigin={set:function(e,t,n){n[v]=t},get:function(e){return p[l](e,v)}})},"3.18.1",{requires:["dom-base"]}); -YUI.add("event-base",function(e,t){e.publish("domready",{fireOnce:!0,async:!0}),YUI.Env.DOMReady?e.fire("domready"):e.Do.before(function(){e.fire("domready")},YUI.Env,"_ready");var n=e.UA,r={},i={63232:38,63233:40,63234:37,63235:39,63276:33,63277:34,25:9,63272:46,63273:36,63275:35},s=function(t){if(!t)return t;try{t&&3==t.nodeType&&(t=t.parentNode)}catch(n){return null}return e.one(t)},o=function(e,t,n){this._event=e,this._currentTarget=t,this._wrapper=n||r,this.init()};e.extend(o,Object,{init:function(){var e=this._event,t=this._wrapper.overrides,r=e.pageX,o=e.pageY,u,a=this._currentTarget;this.altKey=e.altKey,this.ctrlKey=e.ctrlKey,this.metaKey=e.metaKey,this.shiftKey=e.shiftKey,this.type=t&&t.type||e.type,this.clientX=e.clientX,this.clientY=e.clientY,this.pageX=r,this.pageY=o,u=e.keyCode||e.charCode,n.webkit&&u in i&&(u=i[u]),this.keyCode=u,this.charCode=u,this.which=e.which||e.charCode||u,this.button=this.which,this.target=s(e.target),this.currentTarget=s(a),this.relatedTarget=s(e.relatedTarget);if(e.type=="mousewheel"||e.type=="DOMMouseScroll")this.wheelDelta=e.detail?e.detail*-1:Math.round(e.wheelDelta/80)||(e.wheelDelta<0?-1:1);this._touch&&this._touch(e,a,this._wrapper)},stopPropagation:function(){this._event.stopPropagation(),this._wrapper.stopped=1,this.stopped=1},stopImmediatePropagation:function(){var e=this._event;e.stopImmediatePropagation?e.stopImmediatePropagation():this.stopPropagation(),this._wrapper.stopped=2,this.stopped=2},preventDefault:function(e){var t=this._event;t.preventDefault(),e&&(t.returnValue=e),this._wrapper.prevented=1,this.prevented=1},halt:function(e){e?this.stopImmediatePropagation():this.stopPropagation(),this.preventDefault()}}),o.resolve=s,e.DOM2EventFacade=o,e.DOMEventFacade=o,function(){e.Env.evt.dom_wrappers={},e.Env.evt.dom_map={};var t=e.Env.evt,n=e.config,r=n.win,i=YUI.Env.add,s=YUI.Env.remove,o=function(){YUI.Env.windowLoaded=!0,e.Event._load(),s(r,"load",o)},u=function(){e.Event._unload()},a="domready",f="~yui|2|compat~",l=function(t){try{return t&&typeof t!="string"&&e.Lang.isNumber(t.length)&&!t.tagName&&!e.DOM.isWindow(t)}catch(n){return!1}},c=e.CustomEvent.prototype._delete,h=function(t){var n=c.apply(this,arguments);return this.hasSubs()||e.Event._clean(this),n},p=function(){var n=!1,o=0,c=[],d=t.dom_wrappers,v=null,m=t.dom_map;return{POLL_RETRYS:1e3,POLL_INTERVAL:40,lastError:null,_interval:null,_dri:null,DOMReady:!1,startInterval:function(){p._interval||(p._interval=setInterval(p._poll,p.POLL_INTERVAL))},onAvailable:function(t,n,r,i,s,u){var a=e.Array(t),f,l;for(f=0;f4?t.slice(4):null),c&&u.fire(),h):!1},detach:function(t,n,r,i){var s=e.Array(arguments,0,!0),o,u,a,c,h,v;s[s.length-1]===f&&(o=!0);if(t&&t.detach)return t.detach();typeof r=="string"&&(o?r=e.DOM.byId(r):(r=e.Selector.query(r),u=r.length,u<1?r=null:u==1&&(r=r[0])));if(!r)return!1;if(r.detach)return s.splice(2,1),r.detach.apply(r,s);if(l(r)){a=!0;for(c=0,u=r.length;c0),u=[],a=function(t,n){var r,i=n.override;try{n.compat?(n.override?i===!0?r=n.obj:r=i:r=t,n.fn.call(r,n.obj)):(r=n.obj||e.one(t),n.fn.apply(r,e.Lang.isArray(i)?i:[]))}catch(s){}};for(t=0,r=c.length;t4?e.Array(arguments,4,!0):null;return e.Event.onAvailable.call(e.Event,r,n,i,s)}},e.Env.evt.plugins.contentready={on:function(t,n,r,i){var s=arguments.length>4?e.Array(arguments,4,!0):null;return e.Event.onContentReady.call(e.Event,r,n,i,s)}}},"3.18.1",{requires:["event-custom-base"]}); -YUI.add("event-custom-base",function(e,t){e.Env.evt={handles:{},plugins:{}};var n=0,r=1,i={objs:null,before:function(t,r,i,s){var o=t,u;return s&&(u=[t,s].concat(e.Array(arguments,4,!0)),o=e.rbind.apply(e,u)),this._inject(n,o,r,i)},after:function(t,n,i,s){var o=t,u;return s&&(u=[t,s].concat(e.Array(arguments,4,!0)),o=e.rbind.apply(e,u)),this._inject(r,o,n,i)},_inject:function(t,n,r,i){var s=e.stamp(r),o,u;return r._yuiaop||(r._yuiaop={}),o=r._yuiaop,o[i]||(o[i]=new e.Do.Method(r,i),r[i]=function(){return o[i].exec.apply(o[i],arguments)}),u=s+e.stamp(n)+i,o[i].register(u,n,t),new e.EventHandle(o[i],u)},detach:function(e){e.detach&&e.detach()}};e.Do=i,i.Method=function(e,t){this.obj=e,this.methodName=t,this.method=e[t],this.before={},this.after={}},i.Method.prototype.register=function(e,t,n){n?this.after[e]=t:this.before[e]=t},i.Method.prototype._delete=function(e){delete this.before[e],delete this.after[e]},i.Method.prototype.exec=function(){var t=e.Array(arguments,0,!0),n,r,s,o=this.before,u=this.after,a=!1;for(n in o)if(o.hasOwnProperty(n)){r=o[n].apply(this.obj,t);if(r)switch(r.constructor){case i.Halt:return r.retVal;case i.AlterArgs:t=r.newArgs;break;case i.Prevent:a=!0;break;default:}}a||(r=this.method.apply(this.obj,t)),i.originalRetVal=r,i.currentRetVal=r;for(n in u)if(u.hasOwnProperty(n)){s=u[n].apply(this.obj,t);if(s&&s.constructor===i.Halt)return s.retVal;s&&s.constructor===i.AlterReturn&&(r=s.newRetVal,i.currentRetVal=r)}return r},i.AlterArgs=function(e,t){this.msg=e,this.newArgs=t},i.AlterReturn=function(e,t){this.msg=e,this.newRetVal=t},i.Halt=function(e,t){this.msg=e,this.retVal=t},i.Prevent=function(e){this.msg=e},i.Error=i.Halt;var s=e.Array,o="after",u=["broadcast","monitored","bubbles","context","contextFn","currentTarget","defaultFn","defaultTargetOnly","details","emitFacade","fireOnce","async","host","preventable","preventedFn","queuable","silent","stoppedFn","target","type"],a=s.hash(u),f=Array.prototype.slice,l=9,c="yui:log",h=function(e,t,n){var r;for(r in t)a[r]&&(n||!(r in e))&&(e[r]=t[r]);return e};e.CustomEvent=function(t,n){this._kds=e.CustomEvent.keepDeprecatedSubs,this.id=e.guid(),this.type=t,this.silent=this.logSystem=t===c,this._kds&&(this.subscribers={},this.afters={}),n&&h(this,n,!0)},e.CustomEvent.keepDeprecatedSubs=!1,e.CustomEvent.mixConfigs=h,e.CustomEvent.prototype={constructor:e.CustomEvent,signature:l,context:e,preventable:!0,bubbles:!0,hasSubs:function(e){var t=0,n=0,r=this._subscribers,i=this._afters,s=this.sibling;return r&&(t=r.length),i&&(n=i.length),s&&(r=s._subscribers,i=s._afters,r&&(t+=r.length),i&&(n+=i.length)),e?e==="after"?n:t:t+n},monitor:function(e){this.monitored=!0;var t=this.id+"|"+this.type+"_"+e,n=f.call(arguments,0);return n[0]=t,this.host.on.apply(this.host,n)},getSubs:function(){var e=this.sibling,t=this._subscribers,n=this._afters,r,i;return e&&(r=e._subscribers,i=e._afters),r?t?t=t.concat(r):t=r.concat():t?t=t.concat():t=[],i?n?n=n.concat(i):n=i.concat():n?n=n.concat():n=[],[t,n]},applyConfig:function(e,t){h(this,e,t)},_on:function(t,n,r,i){var s=new e.Subscriber(t,n,r,i),u;return this.fireOnce&&this.fired&&(u=this.firedWith,this.emitFacade&&this._addFacadeToArgs&&this._addFacadeToArgs(u),this.async?setTimeout(e.bind(this._notify,this,s,u),0):this._notify(s,u)),i===o?(this._afters||(this._afters=[]),this._afters.push(s)):(this._subscribers||(this._subscribers=[]),this._subscribers.push(s)),this._kds&&(i===o?this.afters[s.id]=s:this.subscribers[s.id]=s),new e.EventHandle(this,s)},subscribe:function(e,t){var n=arguments.length>2?f.call(arguments,2):null;return this._on(e,t,n,!0)},on:function(e,t){var n=arguments.length>2?f.call(arguments,2):null;return this.monitored&&this.host&&this.host._monitor("attach",this,{args:arguments}),this._on(e,t,n,!0)},after:function(e,t){var n=arguments.length>2?f.call(arguments,2):null;return this._on(e,t,n,o)},detach:function(e,t){if(e&&e.detach)return e.detach();var n,r,i=0,s=this._subscribers,o=this._afters;if(s)for(n=s.length;n>=0;n--)r=s[n],r&&(!e||e===r.fn)&&(this._delete(r,s,n),i++);if(o)for(n=o.length;n>=0;n--)r=o[n],r&&(!e||e===r.fn)&&(this._delete(r,o,n),i++);return i},unsubscribe:function(){return this.detach.apply(this,arguments)},_notify:function(e,t,n){var r;return r=e.notify(t,this),!1===r||this.stopped>1?!1:!0},log:function(e,t){},fire:function(){var e=[];return e.push.apply(e,arguments),this._fire(e)},_fire:function(e){return this.fireOnce&&this.fired?!0:(this.fired=!0,this.fireOnce&&(this.firedWith=e),this.emitFacade?this.fireComplex(e):this.fireSimple(e))},fireSimple:function(e){this.stopped=0,this.prevented=0;if(this.hasSubs()){var t=this.getSubs();this._procSubs(t[0],e),this._procSubs(t[1],e)}return this.broadcast&&this._broadcast(e),this.stopped?!1:!0},fireComplex:function(e){return e[0]=e[0]||{},this.fireSimple(e)},_procSubs:function(e,t,n){var r,i,s;for(i=0,s=e.length;i-1?e:t+d+e},w=e.cached(function(e,t){var n=e,r,i,s;return p.isString(n)?(s=n.indexOf(m),s>-1&&(i=!0,n=n.substr(m.length)),s=n.indexOf(v),s>-1&&(r=n.substr(0,s),n=n.substr(s+1),n==="*"&&(n=null)),[r,t?b(n,t):n,i,n]):n}),E=function(t){var n=this._yuievt,r;n||(n=this._yuievt={events:{},targets:null,config:{host:this,context:this},chain:e.config.chain}),r=n.config,t&&(h(r,t,!0),t.chain!==undefined&&(n.chain=t.chain),t.prefix&&(r.prefix=t.prefix))};E.prototype={constructor:E,once:function(){var e=this.on.apply(this,arguments);return e.batch(function(e){e.sub&&(e.sub.once=!0)}),e},onceAfter:function(){var e=this.after.apply(this,arguments);return e.batch(function(e){e.sub&&(e.sub.once=!0)}),e},parseType:function(e,t){return w(e,t||this._yuievt.config.prefix)},on:function(t,n,r){var i=this._yuievt,s=w(t,i.config.prefix),o,u,a,l,c,h,d,v=e.Env.evt.handles,g,y,b,E=e.Node,S,x,T;this._monitor("attach",s[1],{args:arguments,category:s[0],after:s[2]});if(p.isObject(t))return p.isFunction(t)?e.Do.before.apply(e.Do,arguments):(o=n,u=r,a=f.call(arguments,0),l=[],p.isArray(t)&&(T=!0),g=t._after,delete t._after,e.each(t,function(e,t){p.isObject(e)&&(o=e.fn||(p.isFunction(e)?e:o),u=e.context||u);var n=g?m:"";a[0]=n+(T?e:t),a[1]=o,a[2]=u,l.push(this.on.apply(this,a))},this),i.chain?this:new e.EventHandle(l));h=s[0],g=s[2],b=s[3];if(E&&e.instanceOf(this,E)&&b in E.DOM_EVENTS)return a=f.call(arguments,0),a.splice(2,0,E.getDOMNode(this)),e.on.apply(e,a);t=s[1];if(e.instanceOf(this,YUI)){y=e.Env.evt.plugins[t],a=f.call(arguments,0),a[0]=b,E&&(S=a[2],e.instanceOf(S,e.NodeList)?S=e.NodeList.getDOMNodes(S):e.instanceOf(S,E)&&(S=E.getDOMNode(S)),x=b in E.DOM_EVENTS,x&&(a[2]=S));if(y)d=y.on.apply(e,a);else if(!t||x)d=e.Event._attach(a)}return d||(c=i.events[t]||this.publish(t),d=c._on(n,r,arguments.length>3?f.call(arguments,3):null,g?"after":!0),t.indexOf("*:")!==-1&&(this._hasSiblings=!0)),h&&(v[h]=v[h]||{},v[h][t]=v[h][t]||[],v[h][t].push(d)),i.chain?this:d},subscribe:function(){return this.on.apply(this,arguments)},detach:function(t,n,r){var i=this._yuievt.events,s,o=e.Node,u=o&&e.instanceOf(this,o);if(!t&&this!==e){for(s in i)i.hasOwnProperty(s)&&i[s].detach(n,r);return u&&e.Event.purgeElement(o.getDOMNode(this)),this}var a=w(t,this._yuievt.config.prefix),l=p.isArray(a)?a[0]:null,c=a?a[3]:null,h,d=e.Env.evt.handles,v,m,g,y,b=function(e,t,n){var r=e[t],i,s;if(r)for(s=r.length-1;s>=0;--s)i=r[s].evt,(i.host===n||i.el===n)&&r[s].detach()};if(l){m=d[l],t=a[1],v=u?e.Node.getDOMNode(this):this;if(m){if(t)b(m,t,v);else for(s in m)m.hasOwnProperty(s)&&b(m,s,v);return this}}else{if(p.isObject(t)&&t.detach)return t.detach(),this;if(u&&(!c||c in o.DOM_EVENTS))return g=f.call(arguments,0),g[2]=o.getDOMNode(this),e.detach.apply(e,g),this}h=e.Env.evt.plugins[c];if(e.instanceOf(this,YUI)){g=f.call(arguments,0);if(h&&h.detach)return h.detach.apply(e,g),this;if(!t||!h&&o&&t in o.DOM_EVENTS)return g[0]=t,e.Event.detach.apply(e.Event,g),this}return y=i[a[1]],y&&y.detach(n,r),this},unsubscribe:function(){return this.detach.apply(this,arguments)},detachAll:function(e){return this.detach(e)},unsubscribeAll:function(){return this.detachAll.apply(this,arguments)},publish:function(t,n){var r,i=this._yuievt,s=i.config,o=s.prefix;return typeof t=="string"?(o&&(t=b(t,o)),r=this._publish(t,s,n)):(r={},e.each(t,function(e,t){o&&(t=b(t,o)),r[t]=this._publish(t,s,e||n)},this)),r},_getFullType:function(e){var t=this._yuievt.config.prefix;return t?t+d+e:e},_publish:function(t,n,r){var i,s=this._yuievt,o=s.config,u=o.host,a=o.context,f=s.events;return i=f[t],(o.monitored&&!i||i&&i.monitored)&&this._monitor("publish",t,{args:arguments}),i||(i=f[t]=new e.CustomEvent(t,n),n||(i.host=u,i.context=a)),r&&h(i,r,!0),i},_monitor:function(e,t,n){var r,i,s;if(t){typeof t=="string"?(s=t,i=this.getEvent(t,!0)):(i=t,s=t.type);if(this._yuievt.config.monitored&&(!i||i.monitored)||i&&i.monitored)r=s+"_"+e,n.monitored=e,this.fire.call(this,r,n)}},fire:function(e){var t=typeof e=="string",n=arguments.length,r=e,i=this._yuievt,s=i.config,o=s.prefix,u,a,l,c;t&&n<=3?n===2?c=[arguments[1]]:n===3?c=[arguments[1],arguments[2]]:c=[]:c=f.call(arguments,t?1:0),t||(r=e&&e.type),o&&(r=b(r,o)),a=i.events[r],this._hasSiblings&&(l=this.getSibling(r,a),l&&!a&&(a=this.publish(r))),(s.monitored&&(!a||a.monitored)||a&&a.monitored)&&this._monitor("fire",a||r,{args:c});if(!a){if(i.hasTargets)return this.bubble({type:r},c,this);u=!0}else l&&(a.sibling=l),u=a._fire(c);return i.chain?this:u},getSibling:function(e,t){var n;return e.indexOf(d)>-1&&(e=y(e),n=this.getEvent(e,!0),n&&(n.applyConfig(t),n.bubbles=!1,n.broadcast=0)),n},getEvent:function(e,t){var n,r;return t||(n=this._yuievt.config.prefix,e=n?b(e,n):e),r=this._yuievt.events,r[e]||null},after:function(t,n){var r=f.call(arguments,0);switch(p.type(t)){case"function":return e.Do.after.apply(e.Do,arguments);case"array":case"object":r[0]._after=!0;break;default:r[0]=m+t}return this.on.apply(this,r)},before:function(){return this.on.apply -(this,arguments)}},e.EventTarget=E,e.mix(e,E.prototype),E.call(e,{bubbles:!1}),YUI.Env.globalEvents=YUI.Env.globalEvents||new E,e.Global=YUI.Env.globalEvents},"3.18.1",{requires:["oop"]}); -YUI.add("event-custom-complex",function(e,t){var n,r,i=e.Object,s,o={},u=e.CustomEvent.prototype,a=e.EventTarget.prototype,f=function(e,t){var n;for(n in t)r.hasOwnProperty(n)||(e[n]=t[n])};e.EventFacade=function(e,t){e||(e=o),this._event=e,this.details=e.details,this.type=e.type,this._type=e.type,this.target=e.target,this.currentTarget=t,this.relatedTarget=e.relatedTarget},e.mix(e.EventFacade.prototype,{stopPropagation:function(){this._event.stopPropagation(),this.stopped=1},stopImmediatePropagation:function(){this._event.stopImmediatePropagation(),this.stopped=2},preventDefault:function(){this._event.preventDefault(),this.prevented=1},halt:function(e){this._event.halt(e),this.prevented=1,this.stopped=e?2:1}}),u.fireComplex=function(t){var n,r,i,s,o,u=!0,a,f,l,c,h,p,d,v,m,g=this,y=g.host||g,b,w,E=g.stack,S=y._yuievt,x;if(E&&g.queuable&&g.type!==E.next.type)return E.queue||(E.queue=[]),E.queue.push([g,t]),!0;x=g.hasSubs()||S.hasTargets||g.broadcast,g.target=g.target||y,g.currentTarget=y,g.details=t.concat();if(x){n=E||{id:g.id,next:g,silent:g.silent,stopped:0,prevented:0,bubbling:null,type:g.type,defaultTargetOnly:g.defaultTargetOnly},f=g.getSubs(),l=f[0],c=f[1],g.stopped=g.type!==n.type?0:n.stopped,g.prevented=g.type!==n.type?0:n.prevented,g.stoppedFn&&(a=new e.EventTarget({fireOnce:!0,context:y}),g.events=a,a.on("stopped",g.stoppedFn)),g._facade=null,r=g._createFacade(t),l&&g._procSubs(l,t,r),g.bubbles&&y.bubble&&!g.stopped&&(w=n.bubbling,n.bubbling=g.type,n.type!==g.type&&(n.stopped=0,n.prevented=0),u=y.bubble(g,t,null,n),g.stopped=Math.max(g.stopped,n.stopped),g.prevented=Math.max(g.prevented,n.prevented),n.bubbling=w),d=g.prevented,d?(v=g.preventedFn,v&&v.apply(y,t)):(m=g.defaultFn,m&&(!g.defaultTargetOnly&&!n.defaultTargetOnly||y===r.target)&&m.apply(y,t)),g.broadcast&&g._broadcast(t);if(c&&!g.prevented&&g.stopped<2){h=n.afterQueue;if(n.id===g.id||g.type!==S.bubbling){g._procSubs(c,t,r);if(h)while(b=h.last())b()}else p=c,n.execDefaultCnt&&(p=e.merge(p),e.each(p,function(e){e.postponed=!0})),h||(n.afterQueue=new e.Queue),n.afterQueue.add(function(){g._procSubs(p,t,r)})}g.target=null;if(n.id===g.id){s=n.queue;if(s)while(s.length)i=s.pop(),o=i[0],n.next=o,o._fire(i[1]);g.stack=null}u=!g.stopped,g.type!==S.bubbling&&(n.stopped=0,n.prevented=0,g.stopped=0,g.prevented=0)}else m=g.defaultFn,m&&(r=g._createFacade(t),(!g.defaultTargetOnly||y===r.target)&&m.apply(y,t));return g._facade=null,u},u._hasPotentialSubscribers=function(){return this.hasSubs()||this.host._yuievt.hasTargets||this.broadcast},u._createFacade=u._getFacade=function(t){var n=this.details,r=n&&n[0],i=r&&typeof r=="object",s=this._facade;return s||(s=new e.EventFacade(this,this.currentTarget)),i?(f(s,r),r.type&&(s.type=r.type),t&&(t[0]=s)):t&&t.unshift(s),s.details=this.details,s.target=this.originalTarget||this.target,s.currentTarget=this.currentTarget,s.stopped=0,s.prevented=0,this._facade=s,this._facade},u._addFacadeToArgs=function(e){var t=e[0];t&&t.halt&&t.stopImmediatePropagation&&t.stopPropagation&&t._event||this._createFacade(e)},u.stopPropagation=function(){this.stopped=1,this.stack&&(this.stack.stopped=1),this.events&&this.events.fire("stopped",this)},u.stopImmediatePropagation=function(){this.stopped=2,this.stack&&(this.stack.stopped=2),this.events&&this.events.fire("stopped",this)},u.preventDefault=function(){this.preventable&&(this.prevented=1,this.stack&&(this.stack.prevented=1))},u.halt=function(e){e?this.stopImmediatePropagation():this.stopPropagation(),this.preventDefault()},a.addTarget=function(t){var n=this._yuievt;return n.targets||(n.targets={}),n.targets[e.stamp(t)]=t,n.hasTargets=!0,this},a.getTargets=function(){var e=this._yuievt.targets;return e?i.values(e):[]},a.removeTarget=function(t){var n=this._yuievt.targets;return n&&(delete n[e.stamp(t,!0)],i.size(n)===0&&(this._yuievt.hasTargets=!1)),this},a.bubble=function(e,t,n,r){var i=this._yuievt.targets,s=!0,o,u,a,f,l,c=e&&e.type,h=n||e&&e.target||this,p;if(!e||!e.stopped&&i)for(a in i)if(i.hasOwnProperty(a)){o=i[a],u=o._yuievt.events[c],o._hasSiblings&&(l=o.getSibling(c,u)),l&&!u&&(u=o.publish(c)),p=o._yuievt.bubbling,o._yuievt.bubbling=c;if(!u)o._yuievt.hasTargets&&o.bubble(e,t,h,r);else{l&&(u.sibling=l),u.target=h,u.originalTarget=h,u.currentTarget=o,f=u.broadcast,u.broadcast=!1,u.emitFacade=!0,u.stack=r,s=s&&u.fire.apply(u,t||e.details||[]),u.broadcast=f,u.originalTarget=null;if(u.stopped)break}o._yuievt.bubbling=p}return s},a._hasPotentialSubscribers=function(e){var t=this._yuievt,n=t.events[e];return n?n.hasSubs()||t.hasTargets||n.broadcast:!1},n=new e.EventFacade,r={};for(s in n)r[s]=!0},"3.18.1",{requires:["event-custom-base"]}); -YUI.add("event-delegate",function(e,t){function f(t,r,u,l){var c=n(arguments,0,!0),h=i(u)?u:null,p,d,v,m,g,y,b,w,E;if(s(t)){w=[];if(o(t))for(y=0,b=t.length;y1&&(g=p.shift(),c[0]=t=p.shift()),d=e.Node.DOM_EVENTS[t],s(d)&&d.delegate&&(E=d.delegate.apply(d,arguments));if(!E){if(!t||!r||!u||!l)return;v=h?e.Selector.query(h,null,!0):u,!v&&i(u)&&(E=e.on("available",function(){e.mix(E,e.delegate.apply(e,c),!0)},u)),!E&&v&&(c.splice(2,2,v),E=e.Event._attach(c,{facade:!1}),E.sub.filter=l,E.sub._notify=f.notifySub)}return E&&g&&(m=a[g]||(a[g]={}),m=m[t]||(m[t]=[]),m.push(E)),E}var n=e.Array,r=e.Lang,i=r.isString,s=r.isObject,o=r.isArray,u=e.Selector.test,a=e.Env.evt.handles;f.notifySub=function(t,r,i){r=r.slice(),this.args&&r.push.apply(r,this.args);var s=f._applyFilter(this.filter,r,i),o,u,a,l;if(s){s=n(s),o=r[0]=new e.DOMEventFacade(r[0],i.el,i),o.container=e.one(i.el);for(u=0,a=s.length;u3?e.merge(t.splice(3,1)[0]):{};return a in n||(n[a]=this.MIN_VELOCITY),f in n||(n[f]=this.MIN_DISTANCE),l in n||(n[l]=this.PREVENT_DEFAULT),n},_onStart:function(t,n,i,a){var f=!0,l,h,m,g=i._extra.preventDefault,y=t;t.touches&&(f=t.touches.length===1,t=t.touches[0]),f&&(g&&(!g.call||g(t))&&y.preventDefault(),t.flick={time:(new Date).getTime()},i[c]=t,l=i[p],m=n.get(v)===9?n:n.get(u),l||(l=m.on(r[s],e.bind(this._onEnd,this),null,n,i,a),i[p]=l),i[d]=m.once(r[o],e.bind(this._onMove,this),null,n,i,a))},_onMove:function(e,t,n,r){var i=n[c];i&&i.flick&&(i.flick.time=(new Date).getTime())},_onEnd:function(e,t,n,r){var i=(new Date).getTime(),s=n[c],o=!!s,u=e,h,p,v,m,g,y,b,w,E=n[d];E&&(E.detach(),delete n[d]),o&&(e.changedTouches&&(e.changedTouches.length===1&&e.touches.length===0?u=e.changedTouches[0]:o=!1),o&&(m=n._extra,v=m[l],v&&(!v.call||v(e))&&e.preventDefault(),h=s.flick.time,i=(new Date).getTime(),p=i-h,g=[u.pageX-s.pageX,u.pageY-s.pageY],m.axis?w=m.axis:w=Math.abs(g[0])>=Math.abs(g[1])?"x":"y",y=g[w==="x"?0:1],b=p!==0?y/p:0,isFinite(b)&&Math.abs(y)>=m[f]&&Math.abs(b)>=m[a]&&(e.type="flick",e.flick={time:p,distance:y,velocity:b,axis:w,start:s},r.fire(e)),n[c]=null))},MIN_VELOCITY:0,MIN_DISTANCE:0,PREVENT_DEFAULT:!1})},"3.18.1",{requires:["node-base","event-touch","event-synthetic"]}); -YUI.add("event-focus",function(e,t){function u(t,r,u){var a="_"+t+"Notifiers";e.Event.define(t,{_useActivate:o,_attach:function(i,s,o){return e.DOM.isWindow(i)?n._attach([t,function(e){s.fire(e)},i]):n._attach([r,this._proxy,i,this,s,o],{capture:!0})},_proxy:function(t,r,i){var s=t.target,f=t.currentTarget,l=s.getData(a),c=e.stamp(f._node),h=o||s!==f,p;r.currentTarget=i?s:f,r.container=i?f:null,l?h=!0:(l={},s.setData(a,l),h&&(p=n._attach([u,this._notify,s._node]).sub,p.once=!0)),l[c]||(l[c]=[]),l[c].push(r),h||this._notify(t)},_notify:function(t,n){var r=t.currentTarget,i=r.getData(a),o=r.ancestors(),u=r.get("ownerDocument"),f=[],l=i?e.Object.keys(i).length:0,c,h,p,d,v,m,g,y,b,w;r.clearData(a),o.push(r),u&&o.unshift(u),o._nodes.reverse(),l&&(m=l,o.some(function(t){var n=e.stamp(t),r=i[n],s,o;if(r){l--;for(s=0,o=r.length;s=0;--l){u=o(s[l]);if(!u)continue;+u==u?i.keys[u]=r:(f=u.toLowerCase(),this.KEY_MAP[f]?(i.keys[this.KEY_MAP[f]]=r,i.type||(i.type="down")):(u=u.charAt(0),a=u.toUpperCase(),r["+shift"]&&(u=a),i.keys[u.charCodeAt(0)]=u===a?e.merge(r,{"+shift":!0}):r))}}return i.type||(i.type="press"),i},on:function(e,t,o,u){var a=t._extra,f="key"+a.type,l=a.keys,c=u?"delegate":"on";t._detach=e[c](f,function(e){var t=l?l[e.which]:a.mods;t&&(!t[n]||t[n]&&e.altKey)&&(!t[r]||t[r]&&e.ctrlKey)&&(!t[i]||t[i]&&e.metaKey)&&(!t[s]||t[s]&&e.shiftKey)&&o.fire(e)},u)},detach:function(e,t,n){t._detach.detach()}};u.delegate=u.on,u.detachDelegate=u.detach,e.Event.define("key",u,!0)},"3.18.1",{requires:["event-synthetic"]}); -YUI.add("event-mousewheel",function(e,t){var n="DOMMouseScroll",r=function(t){var r=e.Array(t,0,!0),i;return e.UA.gecko?(r[0]=n,i=e.config.win):i=e.config.doc,r.length<3?r[2]=i:r.splice(2,0,i),r};e.Env.evt.plugins.mousewheel={on:function(){return e.Event._attach(r(arguments))},detach:function(){return e.Event.detach.apply(e.Event,r(arguments))}}},"3.18.1",{requires:["node-base"]}); -YUI.add("event-mouseenter",function(e,t){var n=e.Env.evt.dom_wrappers,r=e.DOM.contains,i=e.Array,s=function(){},o={proxyType:"mouseover",relProperty:"fromElement",_notify:function(t,i,s){var o=this._node,u=t.relatedTarget||t[i];o!==u&&!r(o,u)&&s.fire(new e.DOMEventFacade(t,o,n["event:"+e.stamp(o)+t.type]))},on:function(t,n,r){var i=e.Node.getDOMNode(t),s=[this.proxyType,this._notify,i,null,this.relProperty,r];n.handle=e.Event._attach(s,{facade:!1})},detach:function(e,t){t.handle.detach()},delegate:function(t,n,r,i){var o=e.Node.getDOMNode(t),u=[this.proxyType,s,o,null,r];n.handle=e.Event._attach(u,{facade:!1}),n.handle.sub.filter=i,n.handle.sub.relProperty=this.relProperty,n.handle.sub._notify=this._filterNotify},_filterNotify:function(t,n,s){n=n.slice(),this.args&&n.push.apply(n,this.args);var o=e.delegate._applyFilter(this.filter,n,s),u=n[0].relatedTarget||n[0][this.relProperty],a,f,l,c,h;if(o){o=i(o);for(f=0,l=o.length&&(!a||!a.stopped);fi?e.merge(n.splice(i,1)[0]):{};return w in s||(s[w]=t.PREVENT_DEFAULT),s},_=function(e,t){return t._extra.root||e.get(N)===9?e:e.get(S)},D=function(t){var n=t.getDOMNode();return t.compareTo(e.config.doc)&&n.documentElement?n.documentElement:!1},P=function(e,t,n){e.pageX=t.pageX,e.pageY=t.pageY,e.screenX=t.screenX,e.screenY=t.screenY,e.clientX=t.clientX,e.clientY=t.clientY,e[T]=e[T]||t[T],e[x]=e[x]||t[x],e[E]=n&&n[E]||1},H=function(t){var n=t.getDOMNode(),r=t.getData(A);L&&(r||(r=0,t.setData(O,n.style[k])),n.style[k]=e.Event._DEFAULT_TOUCH_ACTION,r++,t.setData(A,r))},B=function(e){var t=e.getDOMNode(),n=e.getData(A),r=e.getData(O);L&&(n--,e.setData(A,n),n===0&&t.style[k]!==r&&(t.style[k]=r))},j=function(e,t){t&&(!t.call||t(e))&&e.preventDefault()},F=e.Event.define;e.Event._DEFAULT_TOUCH_ACTION="none",F(f,{on:function(e,t,n){D(e)||H(e),t[l]=e.on(r[i],this._onStart,this,e,t,n)},delegate:function(e,t,n,s){var o=this;t[p]=e.delegate(r[i],function(r){o._onStart(r,e,t,n,!0)},s)},detachDelegate:function(e,t,n,r){var i=t[p];i&&(i.detach(),t[p]=null),D(e)||B(e)},detach:function(e,t,n){var r=t[l];r&&(r.detach(),t[l]=null),D(e)||B(e)},processArgs:function(e,t){var n=M(this,e,t);return y in n||(n[y]=this.MIN_TIME),b in n||(n[b]=this.MIN_DISTANCE),n},_onStart:function(t,n,i,u,a){a&&(n=t[x]);var f=i._extra,l=!0,c=f[y],h=f[b],p=f.button,d=f[w],v=_(n,i),m;t.touches?t.touches.length===1?P(t,t.touches[0],f):l=!1:l=p===undefined||p===t.button,l&&(j(t,d),c===0||h===0?this._start(t,n,u,f):(m=[t.pageX,t.pageY],c>0&&(f._ht=e.later(c,this,this._start,[t,n,u,f]),f._hme=v.on(r[o],e.bind(function(){this._cancel(f)},this))),h>0&&(f._hm=v.on(r[s],e.bind(function(e){(Math.abs(e.pageX-m[0])>h||Math.abs(e.pageY-m[1])>h)&&this._start(t,n,u,f)},this)))))},_cancel:function(e){e._ht&&(e._ht.cancel(),e._ht=null),e._hme&&(e._hme.detach(),e._hme=null),e._hm&&(e._hm.detach(),e._hm=null)},_start:function(e,t,n,r){r&&this._cancel(r),e.type=f,t.setData(m,e),n.fire(e)},MIN_TIME:0,MIN_DISTANCE:0,PREVENT_DEFAULT:!1}),F(u,{on:function(e,t,n){var i,o;D(e)||H(e),o=_(e,t,r[s]),i=o.on(r[s],this._onMove,this,e,t,n),t[c]=i},delegate:function(e,t,n,i){var o=this;t[d]=e.delegate(r[s],function(r){o._onMove(r,e,t,n,!0)},i)},detach:function(e,t,n){var r=t[c];r&&(r.detach(),t[c]=null),D(e)||B(e)},detachDelegate:function(e,t,n,r){var i=t[d];i&&(i.detach(),t[d]=null),D(e)||B(e)},processArgs:function(e,t){return M(this,e,t)},_onMove:function(e,t,n,r,i){i&&(t=e[x]);var s=n._extra.standAlone||t.getData(m),o=n._extra.preventDefault;s&&(e.touches&&(e.touches.length===1?P(e,e.touches[0]):s=!1),s&&(j(e,o),e.type=u,r.fire(e)))},PREVENT_DEFAULT:!1}),F(a,{on:function(e,t,n){var i,s;D(e)||H(e),s=_(e,t),i=s.on(r[o],this._onEnd,this,e,t,n),t[h]=i},delegate:function(e,t,n,i){var s=this;t[v]=e.delegate(r[o],function(r){s._onEnd(r,e,t,n,!0)},i)},detachDelegate:function(e,t,n,r){var i=t[v];i&&(i.detach(),t[v]=null),D(e)||B(e)},detach:function(e,t,n){var r=t[h];r&&(r.detach(),t[h]=null),D(e)||B(e)},processArgs:function(e,t){return M(this,e,t)},_onEnd:function(e,t,n,r,i){i&&(t=e[x]);var s=n._extra.standAlone||t.getData(g)||t.getData(m),o=n._extra.preventDefault;s&&(e.changedTouches&&(e.changedTouches.length===1?P(e,e.changedTouches[0]):s=!1),s&&(j(e,o),e.type=a,r.fire(e),t.clearData(m),t.clearData(g)))},PREVENT_DEFAULT:!1})},"3.18.1",{requires:["node-base","event-touch","event-synthetic"]}); -YUI.add("event-outside",function(e,t){var n=["blur","change","click","dblclick","focus","keydown","keypress","keyup","mousedown","mousemove","mouseout","mouseover","mouseup","select","submit"];e.Event.defineOutside=function(t,n){n=n||t+"outside";var r={on:function(n,r,i){r.handle=e.one("doc").on(t,function(e){this.isOutside(n,e.target)&&(e.currentTarget=n,i.fire(e))},this)},detach:function(e,t,n){t.handle.detach()},delegate:function(n,r,i,s){r.handle=e.one("doc").delegate(t,function(e){this.isOutside(n,e.target)&&i.fire(e)},s,this)},isOutside:function(e,t){return t!==e&&!t.ancestor(function(t){return t===e})}};r.detachDelegate=r.detach,e.Event.define(n,r)},e.Array.each(n,function(t){e.Event.defineOutside(t)})},"3.18.1",{requires:["event-synthetic"]}); -YUI.add("event-resize",function(e,t){e.Event.define("windowresize",{on:e.UA.gecko&&e.UA.gecko<1.91?function(t,n,r){n._handle=e.Event.attach("resize",function(e){r.fire(e)})}:function(t,n,r){var i=e.config.windowResizeDelay||100;n._handle=e.Event.attach("resize",function(t){n._timer&&n._timer.cancel(),n._timer=e.later(i,e,function(){r.fire(t)})})},detach:function(e,t){t._timer&&t._timer.cancel(),t._handle.detach()}})},"3.18.1",{requires:["node-base","event-synthetic"]}); -YUI.add("event-synthetic",function(e,t){function c(e,t){this.handle=e,this.emitFacade=t}function h(e,t,n){this.handles=[],this.el=e,this.key=n,this.domkey=t}function p(){this._init.apply(this,arguments)}var n=e.CustomEvent,r=e.Env.evt.dom_map,i=e.Array,s=e.Lang,o=s.isObject,u=s.isString,a=s.isArray,f=e.Selector.query,l=function(){};c.prototype.fire=function(t){var n=i(arguments,0,!0),r=this.handle,s=r.evt,u=r.sub,a=u.context,f=u.filter,l=t||{},c;if(this.emitFacade){if(!t||!t.preventDefault)l=s._getFacade(),o(t)&&!t.preventDefault?(e.mix(l,t,!0),n[0]=l):n.unshift(l);l.type=s.type,l.details=n.slice(),f&&(l.container=s.host)}else f&&o(t)&&t.currentTarget&&n.shift();return u.context=a||l.currentTarget||s.host,c=s.fire.apply(s,n),t.prevented&&s.preventedFn&&s.preventedFn.apply(s,n),t.stopped&&s.stoppedFn&&s.stoppedFn.apply(s,n),u.context=a,c},h.prototype={constructor:h,type:"_synth",fn:l,capture:!1,register:function(e){e.evt.registry=this,this.handles.push(e)},unregister:function(t){var n=this.handles,i=r[this.domkey],s;for(s=n.length-1;s>=0;--s)if(n[s].sub===t){n.splice(s,1);break}n.length||(delete i[this.key],e.Object.size(i)||delete r[this.domkey])},detachAll:function(){var e=this.handles,t=e.length;while(--t>=0)e[t].detach()}},e.mix(p,{Notifier:c,SynthRegistry:h,getRegistry:function(t,n,i){var s=t._node,o=e.stamp(s),u="event:"+o+n+"_synth",a=r[o];return i&&(a||(a=r[o]={}),a[u]||(a[u]=new h(s,o,u))),a&&a[u]||null},_deleteSub:function(e){if(e&&e.fn){var t=this.eventDef,r=e.filter?"detachDelegate":"detach";this._subscribers=[],n.keepDeprecatedSubs&&(this.subscribers={}),t[r](e.node,e,this.notifier,e.filter),this.registry.unregister(e),delete e.fn,delete e.node,delete e.context}},prototype:{constructor:p,_init:function(){var e=this.publishConfig||(this.publishConfig={});this.emitFacade="emitFacade"in e?e.emitFacade:!0,e.emitFacade=!1},processArgs:l,on:l,detach:l,delegate:l,detachDelegate:l,_on:function(t,n){var r=[],s=t.slice(),o=this.processArgs(t,n),a=t[2],l=n?"delegate":"on",c,h;return c=u(a)?f(a):i(a||e.one(e.config.win)),!c.length&&u(a)?(h=e.on("available",function(){e.mix(h,e[l].apply(e,s),!0)},a),h):(e.Array.each(c,function(i){var s=t.slice(),u;i=e.one(i),i&&(n&&(u=s.splice(3,1)[0]),s.splice(0,4,s[1],s[3]),(!this.preventDups||!this.getSubs(i,t,null,!0))&&r.push(this._subscribe(i,l,s,o,u)))},this),r.length===1?r[0]:new e.EventHandle(r))},_subscribe:function(t,n,r,i,s){var o=new e.CustomEvent(this.type,this.publishConfig),u=o.on.apply(o,r),a=new c(u,this.emitFacade),f=p.getRegistry(t,this.type,!0),l=u.sub;return l.node=t,l.filter=s,i&&this.applyArgExtras(i,l),e.mix(o,{eventDef:this,notifier:a,host:t,currentTarget:t,target:t,el:t._node,_delete:p._deleteSub},!0),u.notifier=a,f.register(u),this[n](t,l,a,s),u},applyArgExtras:function(e,t){t._extra=e},_detach:function(t){var n=t[2],r=u(n)?f(n):i(n),s,o,a,l,c;t.splice(2,1);for(o=0,a=r.length;o=0;--c)l[c].detach()}}},getSubs:function(e,t,n,r){var i=p.getRegistry(e,this.type),s=[],o,u,a,f;if(i){o=i.handles,n||(n=this.subMatch);for(u=0,a=o.length;u=0&&(h=n._extra.sensitivity),e.changedTouches?(l=[e.changedTouches[0].pageX,e.changedTouches[0].pageY],c=[e.changedTouches[0].clientX,e.changedTouches[0].clientY]):(l=[e.pageX,e.pageY],c=[e.clientX,e.clientY]),Math.abs(l[0]-f[0])<=h&&Math.abs(l[1]-f[1])<=h&&(e.type=s,e.pageX=l[0],e.pageY=l[1],e.clientX=c[0],e.clientY=c[1],e.currentTarget=o.node,r.fire(e)),a(n,[u.END,u.CANCEL])}})},"3.18.1",{requires:["node-base","event-base","event-touch","event-synthetic"]}); -YUI.add("event-touch",function(e,t){var n="scale",r="rotation",i="identifier",s=e.config.win,o={};e.DOMEventFacade.prototype._touch=function(t,s,o){var u,a,f,l,c;if(t.touches){this.touches=[],c={};for(u=0,a=t.touches.length;ua&&(a=n,a===1&&(d=t.el));if(a===2)return!0}),o._refreshTimeout(t))},_refreshTimeout:function(e,t){if(!e._node)return;var r=e.getData(n);o._stopTimeout(e),r.timeout=setTimeout(function(){o._stopPolling(e,t)},o.TIMEOUT)},_startPolling:function(t,s,u){var a,f;if(!t.test("input,textarea,select")&&!(f=o._isEditable(t)))return;a=t.getData(n),a||(a={nodeName:t.get(i).toLowerCase(),isEditable:f,prevVal:f?t.getDOMNode().innerHTML:t.get(r)},t.setData(n,a)),a.notifiers||(a.notifiers={});if(a.interval){if(!u.force){a.notifiers[e.stamp(s)]=s;return}o._stopPolling(t,s)}a.notifiers[e.stamp(s)]=s,a.interval=setInterval(function(){o._poll(t,u)},o.POLL_INTERVAL),o._refreshTimeout(t,s)},_stopPolling:function(t,r){if(!t._node)return;var i=t.getData(n)||{};clearInterval(i.interval),delete i.interval,o._stopTimeout(t),r?i.notifiers&&delete i.notifiers[e.stamp(r)]:i.notifiers={}},_stopTimeout:function(e){var t=e.getData(n)||{};clearTimeout(t.timeout),delete t.timeout},_isEditable:function(e){var t=e._node;return t.contentEditable==="true"||t.contentEditable===""},_onBlur:function(e,t){o._stopPolling(e.currentTarget,t)},_onFocus:function(e,t){var s=e.currentTarget,u=s.getData(n);u||(u={isEditable:o._isEditable(s),nodeName:s.get(i).toLowerCase()},s.setData(n,u)),u.prevVal=u.isEditable?s.getDOMNode().innerHTML:s.get(r),o._startPolling(s,t,{e:e})},_onKeyDown:function(e,t){o._startPolling(e.currentTarget,t,{e:e})},_onKeyUp:function(e,t){(e.charCode===229||e.charCode===197)&&o._startPolling(e.currentTarget,t,{e:e,force:!0})},_onMouseDown:function(e,t){o._startPolling(e.currentTarget,t,{e:e})},_onSubscribe:function(t,s,u,a){var f,l,c,h,p;l={blur:o._onBlur,focus:o._onFocus,keydown:o._onKeyDown,keyup:o._onKeyUp,mousedown:o._onMouseDown},f=u._valuechange={};if(a)f.delegated=!0,f.getNodes=function(){return h=t.all("input,textarea,select").filter(a),p=t.all('[contenteditable="true"],[contenteditable=""]').filter(a),h.concat(p)},f.getNodes().each(function(e){e.getData(n)||e.setData(n,{nodeName:e.get(i).toLowerCase(),isEditable:o._isEditable(e),prevVal:c?e.getDOMNode().innerHTML:e.get(r)})}),u._handles=e.delegate(l,t,a,null,u);else{c=o._isEditable(t);if(!t.test("input,textarea,select")&&!c)return;t.getData(n)||t.setData(n,{nodeName:t.get(i).toLowerCase(),isEditable:c,prevVal:c?t.getDOMNode().innerHTML:t.get(r)}),u._handles=t.on(l,null,null,u)}},_onUnsubscribe:function(e,t,n){var r=n._valuechange;n._handles&&n._handles.detach(),r.delegated?r.getNodes().each(function(e){o._stopPolling(e,n)}):o._stopPolling(e,n)}};s={detach:o._onUnsubscribe,on:o._onSubscribe,delegate:o._onSubscribe,detachDelegate:o._onUnsubscribe,publishConfig:{emitFacade:!0}},e.Event.define("valuechange",s),e.Event.define("valueChange",s),e.ValueChange=o},"3.18.1",{requires:["event-focus","event-synthetic"]}); -YUI.add("node-base",function(e,t){var n=["hasClass","addClass","removeClass","replaceClass","toggleClass"];e.Node.importMethod(e.DOM,n),e.NodeList.importMethod(e.Node.prototype,n);var r=e.Node,i=e.DOM;r.create=function(t,n){return n&&n._node&&(n=n._node),e.one(i.create(t,n))},e.mix(r.prototype,{create:r.create,insert:function(e,t){return this._insert(e,t),this},_insert:function(e,t){var n=this._node,r=null;return typeof t=="number"?t=this._node.childNodes[t]:t&&t._node&&(t=t._node),e&&typeof e!="string"&&(e=e._node||e._nodes||e),r=i.addHTML(n,e,t),r},prepend:function(e){return this.insert(e,0)},append:function(e){return this.insert(e,null)},appendChild:function(e){return r.scrubVal(this._insert(e))},insertBefore:function(t,n){return e.Node.scrubVal(this._insert(t,n))},appendTo:function(t){return e.one(t).append(this),this},setContent:function(e){return this._insert(e,"replace"),this},getContent:function(){var e=this;return e._node.nodeType===11&&(e=e.create("
").append(e.cloneNode(!0))),e.get("innerHTML")}}),e.Node.prototype.setHTML=e.Node.prototype.setContent,e.Node.prototype.getHTML=e.Node.prototype.getContent,e.NodeList.importMethod(e.Node.prototype,["append","insert","appendChild","insertBefore","prepend","setContent","getContent","setHTML","getHTML"]);var r=e.Node,i=e.DOM;r.ATTRS={text:{getter:function(){return i.getText(this._node)},setter:function(e){return i.setText(this._node,e),e}},"for":{getter:function(){return i.getAttribute(this._node,"for")},setter:function(e){return i.setAttribute(this._node,"for",e),e}},options:{getter:function(){return this._node.getElementsByTagName("option")}},children:{getter:function(){var t=this._node,n=t.children,r,i,s;if(!n){r=t.childNodes,n=[];for(i=0,s=r.length;i1?this._data[e]=t:this._data=e,this},clearData:function(e){return"_data"in this&&(typeof e!="undefined"?delete this._data[e]:delete this._data),this}}),e.mix(e.NodeList.prototype,{getData:function(e){var t=arguments.length?[e]:[];return this._invoke("getData",t,!0)},setData:function(e,t){var n=arguments.length>1?[e,t]:[e];return this._invoke("setData",n)},clearData:function(e){var t=arguments.length?[e]:[];return this._invoke("clearData",[e])}})},"3.18.1",{requires:["event-base","node-core","dom-base","dom-style"]}); -YUI.add("node-core",function(e,t){var n=".",r="nodeName",i="nodeType",s="ownerDocument",o="tagName",u="_yuid",a={},f=Array.prototype.slice,l=e.DOM,c=function(t){if(!this.getDOMNode)return new c(t);if(typeof t=="string"){t=c._fromString(t);if(!t)return null}var n=t.nodeType!==9?t.uniqueID:t[u];n&&c._instances[n]&&c._instances[n]._node!==t&&(t[u]=null),n=n||e.stamp(t),n||(n=e.guid()),this[u]=n,this._node=t,this._stateProxy=t,this._initPlugins&&this._initPlugins()},h=function(t){var n=null;return t&&(n=typeof t=="string"?function(n){return e.Selector.test(n,t)}:function(n){return t(e.one(n))}),n};c.ATTRS={},c.DOM_EVENTS={},c._fromString=function(t){return t&&(t.indexOf("doc")===0?t=e.config.doc:t.indexOf("win")===0?t=e.config.win:t=e.Selector.query(t,null,!0)),t||null},c.NAME="node",c.re_aria=/^(?:role$|aria-)/,c.SHOW_TRANSITION="fadeIn",c.HIDE_TRANSITION="fadeOut",c._instances={},c.getDOMNode=function(e){return e?e.nodeType?e:e._node||null:null},c.scrubVal=function(t,n){if(t){if(typeof t=="object"||typeof t=="function")if(i in t||l.isWindow(t))t=e.one(t);else if(t.item&&!t._nodes||t[0]&&t[0][i])t=e.all(t)}else typeof t=="undefined"?t=n:t===null&&(t=null);return t},c.addMethod=function(e,t,n){e&&t&&typeof t=="function"&&(c.prototype[e]=function(){var e=f.call(arguments),r=this,i;return e[0]&&e[0]._node&&(e[0]=e[0]._node),e[1]&&e[1]._node&&(e[1]=e[1]._node),e.unshift(r._node),i=t.apply(n||r,e),i&&(i=c.scrubVal(i,r)),typeof i!="undefined"||(i=r),i})},c.importMethod=function(t,n,r){typeof n=="string"?(r=r||n,c.addMethod(r,t[n],t)):e.Array.each(n,function(e){c.importMethod(t,e)})},c.one=function(t){var n=null,r,i;if(t){if(typeof t=="string"){t=c._fromString(t);if(!t)return null}else if(t.getDOMNode)return t;if(t.nodeType||e.DOM.isWindow(t)){i=t.uniqueID&&t.nodeType!==9?t.uniqueID:t._yuid,n=c._instances[i],r=n?n._node:null;if(!n||r&&t!==r)n=new c(t),t.nodeType!=11&&(c._instances[n[u]]=n)}}return n},c.DEFAULT_SETTER=function(t,r){var i=this._stateProxy,s;return t.indexOf(n)>-1?(s=t,t=t.split(n),e.Object.setValue(i,t,r)):typeof i[t]!="undefined"&&(i[t]=r),r},c.DEFAULT_GETTER=function(t){var r=this._stateProxy,i;return t.indexOf&&t.indexOf(n)>-1?i=e.Object.getValue(r,t.split(n)):typeof r[t]!="undefined"&&(i=r[t]),i},e.mix(c.prototype,{DATA_PREFIX:"data-",toString:function(){var e=this[u]+": not bound to a node",t=this._node,n,i,s;return t&&(n=t.attributes,i=n&&n.id?t.getAttribute("id"):null,s=n&&n.className?t.getAttribute("className"):null,e=t[r],i&&(e+="#"+i),s&&(e+="."+s.replace(" ",".")),e+=" "+this[u]),e},get:function(e){var t;return this._getAttr?t=this._getAttr(e):t=this._get(e),t?t=c.scrubVal(t,this):t===null&&(t=null),t},_get:function(e){var t=c.ATTRS[e],n;return t&&t.getter?n=t.getter.call(this):c.re_aria.test(e)?n=this._node.getAttribute(e,2):n=c.DEFAULT_GETTER.apply(this,arguments),n},set:function(e,t){var n=c.ATTRS[e];return this._setAttr?this._setAttr.apply(this,arguments):n&&n.setter?n.setter.call(this,t,e):c.re_aria.test(e)?this._node.setAttribute(e,t):c.DEFAULT_SETTER.apply(this,arguments),this},setAttrs:function(t){return this._setAttrs?this._setAttrs(t):e.Object.each(t,function(e,t){this.set(t,e)},this),this},getAttrs:function(t){var n={};return this._getAttrs?this._getAttrs(t):e.Array.each(t,function(e,t){n[e]=this.get(e)},this),n},compareTo:function(e){var t=this._node;return e&&e._node&&(e=e._node),t===e},inDoc:function(e){var t=this._node;if(t){e=e?e._node||e:t[s];if(e.documentElement)return l.contains(e.documentElement,t)}return!1},getById:function(t){var n=this._node,r=l.byId(t,n[s]);return r&&l.contains(n,r)?r=e.one(r):r=null,r},ancestor:function(t,n,r){return arguments.length===2&&(typeof n=="string"||typeof n=="function")&&(r=n),e.one(l.ancestor(this._node,h(t),n,h(r)))},ancestors:function(t,n,r){return arguments.length===2&&(typeof n=="string"||typeof n=="function")&&(r=n),e.all(l.ancestors(this._node,h(t),n,h(r)))},previous:function(t,n){return e.one(l.elementByAxis(this._node,"previousSibling",h(t),n))},next:function(t,n){return e.one(l.elementByAxis(this._node,"nextSibling",h(t),n))},siblings:function(t){return e.all(l.siblings(this._node,h(t)))},one:function(t){return e.one(e.Selector.query(t,this._node,!0))},all:function(t){var n;return this._node&&(n=e.all(e.Selector.query(t,this._node)),n._query=t,n._queryRoot=this._node),n||e.all([])},test:function(t){return e.Selector.test(this._node,t)},remove:function(e){var t=this._node;return t&&t.parentNode&&t.parentNode.removeChild(t),e&&this.destroy(),this},replace:function(e){var t=this._node;return typeof e=="string"&&(e=c.create(e)),t.parentNode.replaceChild(c.getDOMNode(e),t),this},replaceChild:function(t,n){return typeof t=="string"&&(t=l.create(t)),e.one(this._node.replaceChild(c.getDOMNode(t),c.getDOMNode(n)))},destroy:function(t){var n=e.config.doc.uniqueID?"uniqueID":"_yuid",r;this.purge(),this.unplug&&this.unplug(),this.clearData(),t&&e.NodeList.each(this.all("*"),function(t){r=c._instances[t[n]],r?r.destroy():e.Event.purgeElement(t)}),this._node=null,this._stateProxy=null,delete c._instances[this._yuid]},invoke:function(e,t,n,r,i,s){var o=this._node,u;return t&&t._node&&(t=t._node),n&&n._node&&(n=n._node),u=o[e](t,n,r,i,s),c.scrubVal(u,this)},swap:e.config.doc.documentElement.swapNode?function(e){this._node.swapNode(c.getDOMNode(e))}:function(e){e=c.getDOMNode(e);var t=this._node,n=e.parentNode,r=e.nextSibling;return r===t?n.insertBefore(t,e):e===t.nextSibling?n.insertBefore(e,t):(t.parentNode.replaceChild(e,t),l.addHTML(n,t,r)),this},hasMethod:function(e){var t=this._node;return!(!(t&&e in t&&typeof t[e]!="unknown")||typeof t[e]!="function"&&String(t[e]).indexOf("function")!==1)},isFragment:function(){return this.get("nodeType")===11},empty:function(){return this.get("childNodes").remove().destroy(!0),this},getDOMNode:function(){return this._node}},!0),e.Node=c,e.one=c.one;var p=function(t){var n=[];t&&(typeof t=="string"?(this._query=t,t=e.Selector.query(t)):t.nodeType||l.isWindow(t)?t=[t]:t._node?t=[t._node]: -t[0]&&t[0]._node?(e.Array.each(t,function(e){e._node&&n.push(e._node)}),t=n):t=e.Array(t,0,!0)),this._nodes=t||[]};p.NAME="NodeList",p.getDOMNodes=function(e){return e&&e._nodes?e._nodes:e},p.each=function(t,n,r){var i=t._nodes;i&&i.length&&e.Array.each(i,n,r||t)},p.addMethod=function(t,n,r){t&&n&&(p.prototype[t]=function(){var t=[],i=arguments;return e.Array.each(this._nodes,function(s){var o=s.uniqueID&&s.nodeType!==9?"uniqueID":"_yuid",u=e.Node._instances[s[o]],a,f;u||(u=p._getTempNode(s)),a=r||u,f=n.apply(a,i),f!==undefined&&f!==u&&(t[t.length]=f)}),t.length?t:this})},p.importMethod=function(t,n,r){typeof n=="string"?(r=r||n,p.addMethod(r,t[n])):e.Array.each(n,function(e){p.importMethod(t,e)})},p._getTempNode=function(t){var n=p._tempNode;return n||(n=e.Node.create("
"),p._tempNode=n),n._node=t,n._stateProxy=t,n},e.mix(p.prototype,{_invoke:function(e,t,n){var r=n?[]:this;return this.each(function(i){var s=i[e].apply(i,t);n&&r.push(s)}),r},item:function(t){return e.one((this._nodes||[])[t])},each:function(t,n){var r=this;return e.Array.each(this._nodes,function(i,s){return i=e.one(i),t.call(n||i,i,s,r)}),r},batch:function(t,n){var r=this;return e.Array.each(this._nodes,function(i,s){var o=e.Node._instances[i[u]];return o||(o=p._getTempNode(i)),t.call(n||o,o,s,r)}),r},some:function(t,n){var r=this;return e.Array.some(this._nodes,function(i,s){return i=e.one(i),n=n||i,t.call(n,i,s,r)})},toFrag:function(){return e.one(e.DOM._nl2frag(this._nodes))},indexOf:function(t){return e.Array.indexOf(this._nodes,e.Node.getDOMNode(t))},filter:function(t){return e.all(e.Selector.filter(this._nodes,t))},modulus:function(t,n){n=n||0;var r=[];return p.each(this,function(e,i){i%t===n&&r.push(e)}),e.all(r)},odd:function(){return this.modulus(2,1)},even:function(){return this.modulus(2)},destructor:function(){},refresh:function(){var t,n=this._nodes,r=this._query,i=this._queryRoot;return r&&(i||n&&n[0]&&n[0].ownerDocument&&(i=n[0].ownerDocument),this._nodes=e.Selector.query(r,i)),this},size:function(){return this._nodes.length},isEmpty:function(){return this._nodes.length<1},toString:function(){var e="",t=this[u]+": not bound to any nodes",n=this._nodes,i;return n&&n[0]&&(i=n[0],e+=i[r],i.id&&(e+="#"+i.id),i.className&&(e+="."+i.className.replace(" ",".")),n.length>1&&(e+="...["+n.length+" items]")),e||t},getDOMNodes:function(){return this._nodes}},!0),p.importMethod(e.Node.prototype,["destroy","empty","remove","set"]),p.prototype.get=function(t){var n=[],r=this._nodes,i=!1,s=p._getTempNode,o,u;return r[0]&&(o=e.Node._instances[r[0]._yuid]||s(r[0]),u=o._get(t),u&&u.nodeType&&(i=!0)),e.Array.each(r,function(r){o=e.Node._instances[r._yuid],o||(o=s(r)),u=o._get(t),i||(u=e.Node.scrubVal(u,o)),n.push(u)}),i?e.all(n):n},e.NodeList=p,e.all=function(e){return new p(e)},e.Node.all=e.all;var d=e.NodeList,v=Array.prototype,m={concat:1,pop:0,push:0,shift:0,slice:1,splice:1,unshift:0};e.Object.each(m,function(t,n){d.prototype[n]=function(){var r=[],i=0,s,o;while(typeof (s=arguments[i++])!="undefined")r.push(s._node||s._nodes||s);return o=v[n].apply(this._nodes,r),t?o=e.all(o):o=e.Node.scrubVal(o),o}}),e.Array.each(["removeChild","hasChildNodes","cloneNode","hasAttribute","scrollIntoView","getElementsByTagName","focus","blur","submit","reset","select","createCaption"],function(t){e.Node.prototype[t]=function(e,n,r){var i=this.invoke(t,e,n,r);return i}}),e.Node.prototype.removeAttribute=function(e){var t=this._node;return t&&t.removeAttribute(e,0),this},e.Node.importMethod(e.DOM,["contains","setAttribute","getAttribute","wrap","unwrap","generateID"]),e.NodeList.importMethod(e.Node.prototype,["getAttribute","setAttribute","removeAttribute","unwrap","wrap","generateID"])},"3.18.1",{requires:["dom-core","selector"]}); -YUI.add("node-event-delegate",function(e,t){e.Node.prototype.delegate=function(t){var n=e.Array(arguments,0,!0),r=e.Lang.isObject(t)&&!e.Lang.isArray(t)?1:2;return n.splice(r,0,this._node),e.delegate.apply(e,n)}},"3.18.1",{requires:["node-base","event-delegate"]}); -YUI.add("node-pluginhost",function(e,t){e.Node.plug=function(){var t=e.Array(arguments);return t.unshift(e.Node),e.Plugin.Host.plug.apply(e.Base,t),e.Node},e.Node.unplug=function(){var t=e.Array(arguments);return t.unshift(e.Node),e.Plugin.Host.unplug.apply(e.Base,t),e.Node},e.mix(e.Node,e.Plugin.Host,!1,null,1),e.Object.each(e.Node._instances,function(t){e.Plugin.Host.apply(t)}),e.NodeList.prototype.plug=function(){var t=arguments;return e.NodeList.each(this,function(n){e.Node.prototype.plug.apply(e.one(n),t)}),this},e.NodeList.prototype.unplug=function(){var t=arguments;return e.NodeList.each(this,function(n){e.Node.prototype.unplug.apply(e.one(n),t)}),this}},"3.18.1",{requires:["node-base","pluginhost"]}); -YUI.add("node-screen",function(e,t){e.each(["winWidth","winHeight","docWidth","docHeight","docScrollX","docScrollY"],function(t){e.Node.ATTRS[t]={getter:function(){var n=Array.prototype.slice.call(arguments);return n.unshift(e.Node.getDOMNode(this)),e.DOM[t].apply(this,n)}}}),e.Node.ATTRS.scrollLeft={getter:function(){var t=e.Node.getDOMNode(this);return"scrollLeft"in t?t.scrollLeft:e.DOM.docScrollX(t)},setter:function(t){var n=e.Node.getDOMNode(this);n&&("scrollLeft"in n?n.scrollLeft=t:(n.document||n.nodeType===9)&&e.DOM._getWin(n).scrollTo(t,e.DOM.docScrollY(n)))}},e.Node.ATTRS.scrollTop={getter:function(){var t=e.Node.getDOMNode(this);return"scrollTop"in t?t.scrollTop:e.DOM.docScrollY(t)},setter:function(t){var n=e.Node.getDOMNode(this);n&&("scrollTop"in n?n.scrollTop=t:(n.document||n.nodeType===9)&&e.DOM._getWin(n).scrollTo(e.DOM.docScrollX(n),t))}},e.Node.importMethod(e.DOM,["getXY","setXY","getX","setX","getY","setY","swapXY"]),e.Node.ATTRS.region={getter:function(){var t=this.getDOMNode(),n;return t&&!t.tagName&&t.nodeType===9&&(t=t.documentElement),e.DOM.isWindow(t)?n=e.DOM.viewportRegion(t):n=e.DOM.region(t),n}},e.Node.ATTRS.viewportRegion={getter:function(){return e.DOM.viewportRegion(e.Node.getDOMNode(this))}},e.Node.importMethod(e.DOM,"inViewportRegion"),e.Node.prototype.intersect=function(t,n){var r=e.Node.getDOMNode(this);return e.instanceOf(t,e.Node)&&(t=e.Node.getDOMNode(t)),e.DOM.intersect(r,t,n)},e.Node.prototype.inRegion=function(t,n,r){var i=e.Node.getDOMNode(this);return e.instanceOf(t,e.Node)&&(t=e.Node.getDOMNode(t)),e.DOM.inRegion(i,t,n,r)}},"3.18.1",{requires:["dom-screen","node-base"]}); -YUI.add("node-style",function(e,t){(function(e){e.mix(e.Node.prototype,{setStyle:function(t,n){return e.DOM.setStyle(this._node,t,n),this},setStyles:function(t){return e.DOM.setStyles(this._node,t),this},getStyle:function(t){return e.DOM.getStyle(this._node,t)},getComputedStyle:function(t){return e.DOM.getComputedStyle(this._node,t)}}),e.NodeList.importMethod(e.Node.prototype,["getStyle","getComputedStyle","setStyle","setStyles"])})(e);var n=e.Node;e.mix(n.prototype,{show:function(e){return e=arguments[arguments.length-1],this.toggleView(!0,e),this},_show:function(){this.removeAttribute("hidden"),this.setStyle("display","")},_isHidden:function(){return this.hasAttribute("hidden")||e.DOM.getComputedStyle(this._node,"display")==="none"},toggleView:function(e,t){return this._toggleView.apply(this,arguments),this},_toggleView:function(e,t){return t=arguments[arguments.length-1],typeof e!="boolean"&&(e=this._isHidden()?1:0),e?this._show():this._hide(),typeof t=="function"&&t.call(this),this},hide:function(e){return e=arguments[arguments.length-1],this.toggleView(!1,e),this},_hide:function(){this.setAttribute("hidden","hidden"),this.setStyle("display","none")}}),e.NodeList.importMethod(e.Node.prototype,["show","hide","toggleView"])},"3.18.1",{requires:["dom-style","node-base"]}); -YUI.add("oop",function(e,t){function a(t,n,i,s,o){if(t&&t[o]&&t!==e)return t[o].call(t,n,i);switch(r.test(t)){case 1:return r[o](t,n,i);case 2:return r[o](e.Array(t,0,!0),n,i);default:return e.Object[o](t,n,i,s)}}var n=e.Lang,r=e.Array,i=Object.prototype,s="_~yuim~_",o=i.hasOwnProperty,u=i.toString;e.augment=function(t,n,r,i,s){var a=t.prototype,f=a&&n,l=n.prototype,c=a||t,h,p,d,v,m;return s=s?e.Array(s):[],f&&(p={},d={},v={},h=function(e,t){if(r||!(t in a))u.call(e)==="[object Function]"?(v[t]=e,p[t]=d[t]=function(){return m(this,e,arguments)}):p[t]=e},m=function(e,t,r){for(var i in v)o.call(v,i)&&e[i]===d[i]&&(e[i]=v[i]);return n.apply(e,s),t.apply(e,r)},i?e.Array.each(i,function(e){e in l&&h(l[e],e)}):e.Object.each(l,h,null,!0)),e.mix(c,p||l,r,i),f||n.apply(c,s),t},e.aggregate=function(t,n,r,i){return e.mix(t,n,r,i,0,!0)},e.extend=function(t,n,r,s){(!n||!t)&&e.error("extend failed, verify dependencies");var o=n.prototype,u=e.Object(o);return t.prototype=u,u.constructor=t,t.superclass=o,n!=Object&&o.constructor==i.constructor&&(o.constructor=n),r&&e.mix(u,r,!0),s&&e.mix(t,s,!0),t},e.each=function(e,t,n,r){return a(e,t,n,r,"each")},e.some=function(e,t,n,r){return a(e,t,n,r,"some")},e.clone=function(t,r,i,o,u,a){var f,l,c;if(!n.isObject(t)||e.instanceOf(t,YUI)||t.addEventListener||t.attachEvent)return t;l=a||{};switch(n.type(t)){case"date":return new Date(t);case"regexp":return t;case"function":return t;case"array":f=[];break;default:if(t[s])return l[t[s]];c=e.guid(),f=r?{}:e.Object(t),t[s]=c,l[c]=t}return e.each(t,function(n,a){(a||a===0)&&(!i||i.call(o||this,n,a,this,t)!==!1)&&a!==s&&a!="prototype"&&(this[a]=e.clone(n,r,i,o,u||t,l))},f),a||(e.Object.each(l,function(e,t){if(e[s])try{delete e[s]}catch(n){e[s]=null}},this),l=null),f},e.bind=function(t,r){var i=arguments.length>2?e.Array(arguments,2,!0):null;return function(){var s=n.isString(t)?r[t]:t,o=i?i.concat(e.Array(arguments,0,!0)):arguments;return s.apply(r||s,o)}},e.rbind=function(t,r){var i=arguments.length>2?e.Array(arguments,2,!0):null;return function(){var s=n.isString(t)?r[t]:t,o=i?e.Array(arguments,0,!0).concat(i):arguments;return s.apply(r||s,o)}}},"3.18.1",{requires:["yui-base"]}); -YUI.add("pluginhost-base",function(e,t){function r(){this._plugins={}}var n=e.Lang;r.prototype={plug:function(e,t){var r,i,s;if(n.isArray(e))for(r=0,i=e.length;r=0;o--)s=n[o],a=s._UNPLUG,a&&e.mix(i,a,!0),u=s._PLUG,u&&e.mix(r,u,!0);for(f in r)r.hasOwnProperty(f)&&(i[f]||this.plug(r[f]));t&&t.plugins&&this.plug(t.plugins)},n.plug=function(t,n,i){var s,o,u,a;if(t!==e.Base){t._PLUG=t._PLUG||{},r.isArray(n)||(i&&(n={fn:n,cfg:i}),n=[n]);for(o=0,u=n.length;o+~"]/gi},attr:{token:"\ue001",re:/(\[[^\]]*\])/g},pseudo:{token:"\ue002",re:/(\([^\)]*\))/g}},useNative:!0,_escapeId:function(e){return e&&(e=e.replace(/([:\[\]\(\)#\.'<>+~"])/g,"\\$1")),e},_compare:"sourceIndex"in e.config.doc.documentElement?function(e,t){var n=e.sourceIndex,r=t.sourceIndex;return n===r?0:n>r?1:-1}:e.config.doc.documentElement[t]?function(e,n){return e[t](n)&4?-1:1}:function(e,t){var r,i,s;return e&&t&&(r=e[n].createRange(),r.setStart(e,0),i=t[n].createRange(),i.setStart(t,0),s=r.compareBoundaryPoints(1,i)),s},_sort:function(t){return t&&(t=e.Array(t,0,!0),t.sort&&t.sort(r._compare)),t},_deDupe:function(e){var t=[],n,r;for(n=0;r=e[n++];)r._found||(t[t.length]=r,r._found=!0);for(n=0;r=t[n++];)r._found=null,r.removeAttribute("_found");return t},query:function(t,n,i,s){n=n||e.config.doc;var o=[],u=e.Selector.useNative&&e.config.doc.querySelector&&!s,a=[[t,n]],f,l,c,h=u?e.Selector._nativeQuery:e.Selector._bruteQuery;if(t&&h){!s&&(!u||n.tagName)&&(a=r._splitQueries(t,n));for(c=0;f=a[c++];)l=h(f[0],f[1],i),i||(l=e.Array(l,0,!0)),l&&(o=o.concat(l));a.length>1&&(o=r._sort(r._deDupe(o)))}return i?o[0]||null:o},_replaceSelector:function(t){var n=e.Selector._parse("esc",t),i,s;return t=e.Selector._replace("esc",t),s=e.Selector._parse("pseudo",t),t=r._replace("pseudo",t),i=e.Selector._parse("attr",t),t=e.Selector._replace("attr",t),{esc:n,attrs:i,pseudos:s,selector:t}},_restoreSelector:function(t){var n=t.selector;return n=e.Selector._restore("attr",n,t.attrs),n=e.Selector._restore("pseudo",n,t.pseudos),n=e.Selector._restore("esc",n,t.esc),n},_replaceCommas:function(t){var n=e.Selector._replaceSelector(t),t=n.selector;return t&&(t=t.replace(/,/g,"\ue007"),n.selector=t,t=e.Selector._restoreSelector(n)),t},_splitQueries:function(t,n){t.indexOf(",")>-1&&(t=e.Selector._replaceCommas(t));var r=t.split("\ue007"),i=[],s="",o,u,a;if(n){n.nodeType===1&&(o=e.Selector._escapeId(e.DOM.getId(n)),o||(o=e.guid(),e.DOM.setId(n,o)),s='[id="'+o+'"] ');for(u=0,a=r.length;u-1&&e.Selector.pseudos&&e.Selector.pseudos.checked)return e.Selector.query(t,n,r,!0);try{return n["querySelector"+(r?"":"All")](t)}catch(i){return e.Selector.query(t,n,r,!0)}},filter:function(t,n){var r=[],i,s;if(t&&n)for(i=0;s=t[i++];)e.Selector.test(s,n)&&(r[r.length]=s);return r},test:function(t,r,i){var s=!1,o=!1,u,a,f,l,c,h,p,d,v;if(t&&t.tagName)if(typeof r=="function")s=r.call(t,t);else{u=r.split(","),!i&&!e.DOM.inDoc(t)&&(a=t.parentNode,a?i=a:(c=t[n].createDocumentFragment(),c.appendChild(t),i=c,o=!0)),i=i||t[n],h=e.Selector._escapeId(e.DOM.getId(t)),h||(h=e.guid(),e.DOM.setId(t,h));for(p=0;v=u[p++];){v+='[id="'+h+'"]',l=e.Selector.query(v,i);for(d=0;f=l[d++];)if(f===t){s=!0;break}if(s)break}o&&c.removeChild(t)}return s},ancestor:function(t,n,r){return e.DOM.ancestor(t,function(t){return e.Selector.test(t,n)},r)},_parse:function(t,n){return n.match(e.Selector._types[t].re)},_replace:function(t,n){var r=e.Selector._types[t];return n.replace(r.re,r.token)},_restore:function(t,n,r){if(r){var i=e.Selector._types[t].token,s,o;for(s=0,o=r.length;s[index:"+l(e)+",length:"+e.childNodes.length+"]["+(e.innerHTML||"[innerHTML not supported]").slice(0,25)+"]"):e.nodeName:"[No node]";var t}function y(e){this.root=e,this._next=e}function S(e,t){this.node=e,this.offset=t}function T(e){this.code=this[e],this.codeName=e,this.message="DOMException: "+this.codeName}i.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||c.fail("document missing a Node creation method"),i.isHostMethod(document,"getElementsByTagName")||c.fail("document missing getElementsByTagName method"),t=document.createElement("div"),i.areHostMethods(t,["insertBefore","appendChild","cloneNode"])||c.fail("Incomplete Element implementation"),i.isHostProperty(t,"innerHTML")||c.fail("Element is missing innerHTML property"),t=document.createTextNode("test"),i.areHostMethods(t,["splitText","deleteData","insertData","appendData","cloneNode"])||c.fail("Incomplete Text Node implementation"),o=function(e,t){for(var n=e.length;n--;)if(e[n]===t)return!0;return!1},(t=document.createElement("b")).innerHTML="1",u=t.firstChild,t.innerHTML="
",n=N(u),e.features.crashyTextNodes=n,typeof window.getComputedStyle!=r?s=function(e,t){return R(e).getComputedStyle(e,null)[t]}:typeof document.documentElement.currentStyle!=r?s=function(e,t){return e.currentStyle?e.currentStyle[t]:""}:c.fail("No means of obtaining computed style properties found"),y.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var e,t,n=this._current=this._next;if(this._current)if(e=n.firstChild)this._next=e;else{for(t=null;n!==this.root&&!(t=n.nextSibling);)n=n.parentNode;this._next=t}return this._current},detach:function(){this._current=this._next=this.root=null}},S.prototype={equals:function(e){return!!e&&this.node===e.node&&this.offset==e.offset},inspect:function(){return"[DomPosition("+E(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},(T.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}).toString=function(){return this.message},e.dom={arrayContains:o,isHtmlNamespace:function(e){var t;return typeof e.namespaceURI==r||null===(t=e.namespaceURI)||"http://www.w3.org/1999/xhtml"==t},parentElement:function(e){return 1==(e=e.parentNode).nodeType?e:null},getNodeIndex:l,getNodeLength:function(e){switch(e.nodeType){case 7:case 10:return 0;case 3:case 8:return e.length;default:return e.childNodes.length}},getCommonAncestor:f,isAncestorOf:g,isOrIsAncestorOf:function(e,t){return g(e,t,!0)},getClosestAncestorIn:d,isCharacterDataNode:p,isTextOrCommentNode:function(e){return!!e&&(3==(e=e.nodeType)||8==e)},insertAfter:m,splitDataNode:function(e,t,n){var r,o,i=e.cloneNode(!1);if(i.deleteData(0,t),e.deleteData(t,e.length-t),m(i,e),n)for(r=0;o=n[r++];)o.node==e&&o.offset>t?(o.node=i,o.offset-=t):o.node==e.parentNode&&o.offset>l(e)&&++o.offset;return i},getDocument:a,getWindow:R,getIframeWindow:function(e){if(typeof e.contentWindow!=r)return e.contentWindow;if(typeof e.contentDocument!=r)return e.contentDocument.defaultView;throw c.createError("getIframeWindow: No Window object found for iframe element")},getIframeDocument:C,getBody:h,isWindow:v,getContentDocument:function(e,t,n){var r;if(e?i.isHostProperty(e,"nodeType")?r=(1==e.nodeType&&"iframe"==e.tagName.toLowerCase()?C:a)(e):v(e)&&(r=e.document):r=document,!r)throw t.createError(n+"(): Parameter must be a Window object or DOM node");return r},getRootContainer:function(e){for(var t;t=e.parentNode;)e=t;return e},comparePoints:function(e,t,n,r){var o,i,a,s;if(e==n)return t===r?0:t=t.childNodes.length?t.appendChild(e):t.insertBefore(e,t.childNodes[n]),r}function $(e,t,n){if(g(e),g(t),d(t)!=d(e))throw new a("WRONG_DOCUMENT_ERR");var r=c(e.startContainer,e.startOffset,t.endContainer,t.endOffset),e=c(e.endContainer,e.endOffset,t.startContainer,t.startOffset);return n?r<=0&&0<=e:r<0&&0(u(e)?e:e.childNodes).length)throw new a("INDEX_SIZE_ERR")}function J(e,t){if(r(e,!0)!==r(t,!0))throw new a("WRONG_DOCUMENT_ERR")}function ee(e){if(o(e,!0))throw new a("NO_MODIFICATION_ALLOWED_ERR")}function te(e,t){if(!e)throw new a(t)}function ne(e,t){return t<=(u(e)?e:e.childNodes).length}function re(e){return!!e.startContainer&&!!e.endContainer&&!(W&&(s.isBrokenNode(e.startContainer)||s.isBrokenNode(e.endContainer)))&&l(e.startContainer)==l(e.endContainer)&&ne(e.startContainer,e.startOffset)&&ne(e.endContainer,e.endOffset)}function g(e){if(!re(e))throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: ("+e.inspect()+")")}h.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(){var e=this._current=this._next;return e&&(this._next=e!==this._last?e.nextSibling:null,u(e)&&this.clonePartiallySelectedTextNodes&&(e===this.ec&&(e=e.cloneNode(!0)).deleteData(this.eo,e.length-this.eo),this._current===this.sc&&(e=e.cloneNode(!0)).deleteData(0,this.so))),e},remove:function(){var e,t,n=this._current;!u(n)||n!==this.sc&&n!==this.ec?n.parentNode&&z(n):(e=n===this.sc?this.so:0)!=(t=n===this.ec?this.eo:n.length)&&n.deleteData(e,t-e)},isPartiallySelectedSubtree:function(){return F(this._current,this.range)},getSubtreeIterator:function(){var e,t,n,r,o,i;return this.isSingleCharacterDataNode?(e=this.range.cloneRange()).collapse(!1):(e=new ue(d(this.range)),t=this._current,r=0,i=M(o=n=t),D(t,this.sc)&&(n=this.sc,r=this.so),D(t,this.ec)&&(o=this.ec,i=this.eo),de(e,n,r,o,i)),new h(e,this.clonePartiallySelectedTextNodes)},detach:function(){this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}},p=[1,3,4,5,7,8,10],m=[2,9,11],t=[1,3,4,5,7,8,10,11],n=[1,3,4,5,7,8],r=K([9,11]),o=K([5,6,10,12]),R=K([6,10,12]),C=K([1]),v=document.createElement("style"),N=!1;try{v.innerHTML="x",N=3==v.firstChild.nodeType}catch(he){}function oe(e,t){var n,r,o,i,a;g(e),n=e.startContainer,r=e.startOffset,o=e.endContainer,i=e.endOffset,a=n===o,u(o)&&0=_(n)&&i++,r=0),e.setStartAndEnd(n,r,o,i)}function ie(e){g(e);var t=e.commonAncestorContainer.parentNode.cloneNode(!1);return t.appendChild(e.cloneContents()),t.innerHTML}function ae(e){e.START_TO_START=y,e.START_TO_END=S,e.END_TO_END=T,e.END_TO_START=w,e.NODE_BEFORE=A,e.NODE_AFTER=x,e.NODE_BEFORE_AND_AFTER=O,e.NODE_INSIDE=b}function se(e){ae(e),ae(e.prototype)}function ce(o,i){return function(){var e,t,n,r;return g(this),e=this.startContainer,t=this.startOffset,r=this.commonAncestorContainer,n=new h(this,!0),e!==r&&(e=(r=j(L(e,r,!0))).node,t=r.offset),q(n,ee),n.reset(),r=o(n),n.detach(),i(this,e,t,e,t),r}}function le(e,d){function t(t,n){return function(e){X(e,p),X(l(e),m);e=(t?V:j)(e);(n?r:o)(this,e.node,e.offset)}}function r(e,t,n){var r=e.endContainer,o=e.endOffset;t===e.startContainer&&n===e.startOffset||(l(t)==l(r)&&1!=c(t,n,r,o)||(r=t,o=n),d(e,t,n,r,o))}function o(e,t,n){var r=e.startContainer,o=e.startOffset;t===e.endContainer&&n===e.endOffset||(l(t)==l(r)&&-1!=c(t,n,r,o)||(r=t,o=n),d(e,r,o,t,n))}var n=function(){};n.prototype=i.rangePrototype,e.prototype=new n,P.extend(e.prototype,{setStart:function(e,t){f(e,!0),Z(e,t),r(this,e,t)},setEnd:function(e,t){f(e,!0),Z(e,t),o(this,e,t)},setStartAndEnd:function(){var e=arguments,t=e[0],n=e[1],r=t,o=n;switch(e.length){case 3:o=e[2];break;case 4:r=e[2],o=e[3]}f(t,!0),Z(t,n),f(r,!0),Z(r,o),d(this,t,n,r,o)},setBoundary:function(e,t,n){this["set"+(n?"Start":"End")](e,t)},setStartBefore:t(!0,!0),setStartAfter:t(!1,!0),setEndBefore:t(!0,!1),setEndAfter:t(!1,!1),collapse:function(e){g(this),e?d(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):d(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(e){f(e,!0),d(this,e,0,e,M(e))}, -selectNode:function(e){f(e,!1),X(e,p);var t=V(e),e=j(e);d(this,t.node,t.offset,e.node,e.offset)},extractContents:ce(G,d),deleteContents:ce(Y,d),canSurroundContents:function(){var e,t;return g(this),ee(this.startContainer),ee(this.endContainer),t=(e=new h(this,!0))._first&&F(e._first,this)||e._last&&F(e._last,this),e.detach(),!t},splitBoundaries:function(){oe(this)},splitBoundariesPreservingPositions:function(e){oe(this,e)},normalizeBoundaries:function(){var r,o,i,a,e,t,n,s,c,l;g(this),r=this.startContainer,o=this.startOffset,i=this.endContainer,a=this.endOffset,e=function(e){var t=e.nextSibling;t&&t.nodeType==e.nodeType&&(a=(i=e).length,e.appendData(t.data),z(t))},t=function(e){var t,n=e.previousSibling;n&&n.nodeType==e.nodeType&&(t=(r=e).length,o=n.length,e.insertData(0,n.data),z(n),r==i?(a+=o,i=r):i==e.parentNode&&(n=_(e),a==n?(i=e,a=t):n=l&&e.start<=i&&(this.setStart(n,e.start-l),r=!0),r&&e.end>=l&&e.end<=i&&(this.setEnd(n,e.end-l),o=!0),l=i;else for(a=(s=n.childNodes).length;a--;)t.push(s[a])},getName:function(){return"DomRange"},equals:function(e){return ue.rangesEqual(this,e)},isValid:function(){return re(this)},inspect:function(){return Q(this)},detach:function(){}}),le(ue,de),P.extend(ue,{rangeProperties:E,RangeIterator:h,copyComparisonConstants:se,createPrototypeRange:le,inspect:Q,toHtml:ie,getRangeDocument:d,rangesEqual:function(e,t){return e.startContainer===t.startContainer&&e.startOffset===t.startOffset&&e.endContainer===t.endContainer&&e.endOffset===t.endOffset}}),i.DomRange=ue}),f.createCoreModule("WrappedRange",["DomRange"],function(n,c){var l,d,u,h,f,g,R=n.dom,p=n.util,C=R.DomPosition,m=n.DomRange,v=R.getBody,N=R.getContentDocument,E=R.isCharacterDataNode;if(n.features.implementsDomRange){var e,t,r,o,i,y,S=m.rangeProperties;function a(e){for(var t,n=S.length;n--;)e[t=S[n]]=e.nativeRange[t];e.collapsed=e.startContainer===e.endContainer&&e.startOffset===e.endOffset}m.createPrototypeRange(l=function(e){if(!e)throw c.createError("WrappedRange: Range must be specified");this.nativeRange=e,a(this)},function(e,t,n,r,o){var i=e.startContainer!==t||e.startOffset!=n,a=e.endContainer!==r||e.endOffset!=o,s=!e.equals(e.nativeRange);(i||a||s)&&(e.setEnd(r,o),e.setStart(t,n))}),(e=l.prototype).selectNode=function(e){this.nativeRange.selectNode(e),a(this)},e.cloneContents=function(){return this.nativeRange.cloneContents()},e.surroundContents=function(e){this.nativeRange.surroundContents(e),a(this)},e.collapse=function(e){this.nativeRange.collapse(e),a(this)},e.cloneRange=function(){return new l(this.nativeRange.cloneRange())},e.refresh=function(){a(this)},e.toString=function(){return this.nativeRange.toString()},t=document.createTextNode("test"),v(document).appendChild(t),(r=document.createRange()).setStart(t,0),r.setEnd(t,0);try{r.setStart(t,1),e.setStart=function(e,t){this.nativeRange.setStart(e,t),a(this)},e.setEnd=function(e,t){this.nativeRange.setEnd(e,t),a(this)},o=function(t){return function(e){this.nativeRange[t](e),a(this)}}}catch(s){e.setStart=function(e,t){try{this.nativeRange.setStart(e,t)}catch(s){this.nativeRange.setEnd(e,t),this.nativeRange.setStart(e,t)}a(this)},e.setEnd=function(e,t){try{this.nativeRange.setEnd(e,t)}catch(s){this.nativeRange.setStart(e,t),this.nativeRange.setEnd(e,t)}a(this)},o=function(t,n){return function(e){try{this.nativeRange[t](e)}catch(s){this.nativeRange[n](e),this.nativeRange[t](e)}a(this)}}}e.setStartBefore=o("setStartBefore","setEndBefore"),e.setStartAfter=o("setStartAfter","setEndAfter"),e.setEndBefore=o("setEndBefore","setStartBefore"),e.setEndAfter=o("setEndAfter","setStartAfter"),e.selectNodeContents=function(e){this.setStartAndEnd(e,0,R.getNodeLength(e))},r.selectNodeContents(t),r.setEnd(t,3),(o=document.createRange()).selectNodeContents(t),o.setEnd(t,4),o.setStart(t,2),-1==r.compareBoundaryPoints(r.START_TO_END,o)&&1==r.compareBoundaryPoints(r.END_TO_START,o)?e.compareBoundaryPoints=function(e,t){return e==(t=t.nativeRange||t).START_TO_END?e=t.END_TO_START:e==t.END_TO_START&&(e=t.START_TO_END),this.nativeRange.compareBoundaryPoints(e,t)}:e.compareBoundaryPoints=function(e,t){return this.nativeRange.compareBoundaryPoints(e,t.nativeRange||t)},(o=document.createElement("div")).innerHTML="123",i=o.firstChild,(y=v(document)).appendChild(o),r.setStart(i,1),r.setEnd(i,2),r.deleteContents(),"13"==i.data&&(e.deleteContents=function(){this.nativeRange.deleteContents(),a(this)},e.extractContents=function(){var e=this.nativeRange.extractContents();return a(this),e}),y.removeChild(o),y=null,p.isHostMethod(r,"createContextualFragment")&&(e.createContextualFragment=function(e){return this.nativeRange.createContextualFragment(e)}),v(document).removeChild(t),e.getName=function(){return"WrappedRange"},n.WrappedRange=l,n.createNativeRange=function(e){return(e=N(e,c,"createNativeRange")).createRange()}}n.features.implementsTextRange&&(d=function(e){var t,n=e.parentElement(),r=e.duplicate();return r.collapse(!0),t=r.parentElement(),(r=e.duplicate()).collapse(!1),(r=t==(e=r.parentElement())?t:R.getCommonAncestor(t,e))==n?r:R.getCommonAncestor(n,r)},u=function(e){return 0==e.compareEndPoints("StartToEnd",e)},h=function(d,e,t,u,n){var r,o,h,f,g,i,p,a,s,m,c,l=d.duplicate();if(l.collapse(t),r=l.parentElement(),!(r=R.isOrIsAncestorOf(e,r)?r:e).canHaveHTML)return{boundaryPosition:e=new C(r.parentNode,R.getNodeIndex(r)),nodeInfo:{nodeIndex:e.offset,containerElement:e.node}};for((o=R.getDocument(r).createElement("span")).parentNode&&R.removeNode(o), -f=t?"StartToStart":"StartToEnd",i=n&&n.containerElement==r?n.nodeIndex:0,s=a=p=r.childNodes.length;;){if(s==p?r.appendChild(o):r.insertBefore(o,r.childNodes[s]),l.moveToElementText(o),0==(h=l.compareEndPoints(f,d))||i==a)break;if(-1==h){if(a==i+1)break;i=s}else a=a==i+1?i:s;s=Math.floor((i+a)/2),r.removeChild(o)}if(e=o.nextSibling,-1==h&&e&&E(e)){if(l.setEndPoint(t?"EndToStart":"EndToEnd",d),/[\r\n]/.test(e.data))for(n=(c=l.duplicate()).text.replace(/\r\n/g,"\r").length,m=c.moveStart("character",n);-1==(h=c.compareEndPoints("StartToEnd",c));)m++,c.moveStart("character",1);else m=l.text.length;g=new C(e,m)}else n=(u||!t)&&o.previousSibling,g=(e=(u||t)&&o.nextSibling)&&E(e)?new C(e,0):n&&E(n)?new C(n,n.data.length):new C(r,R.getNodeIndex(o));return R.removeNode(o),{boundaryPosition:g,nodeInfo:{nodeIndex:s,containerElement:r}}},f=function(e,t){var n,r=e.offset,o=R.getDocument(e.node),i=v(o).createTextRange(),a=E(e.node),s=a?(n=e.node).parentNode:(n=r<(s=e.node.childNodes).length?s[r]:null,e.node),e=o.createElement("span");return e.innerHTML="&#feff;",n?s.insertBefore(e,n):s.appendChild(e),i.moveToElementText(e),i.collapse(!t),s.removeChild(e),a&&i[t?"moveStart":"moveEnd"]("character",r),i},((i=function(e){this.textRange=e,this.refresh()}).prototype=new m(document)).refresh=function(){var e,t,n=d(this.textRange),n=u(this.textRange)?e=h(this.textRange,n,!0,!0).boundaryPosition:(e=(t=h(this.textRange,n,!0,!1)).boundaryPosition,h(this.textRange,n,!1,!1,t.nodeInfo).boundaryPosition);this.setStart(e.node,e.offset),this.setEnd(n.node,n.offset)},i.prototype.getName=function(){return"WrappedTextRange"},m.copyComparisonConstants(i),i.rangeToTextRange=g=function(e){var t,n;return e.collapsed?f(new C(e.startContainer,e.startOffset),!0):(t=f(new C(e.startContainer,e.startOffset),!0),n=f(new C(e.endContainer,e.endOffset),!1),(e=v(m.getRangeDocument(e)).createTextRange()).setEndPoint("StartToStart",t),e.setEndPoint("EndToEnd",n),e)},i.prototype.toTextRange=function(){return g(this)},n.WrappedTextRange=i,n.features.implementsDomRange&&!n.config.preferTextRange||("undefined"==typeof(o=Function("return this;")()).Range&&(o.Range=i),n.createNativeRange=function(e){return e=N(e,c,"createNativeRange"),v(e).createTextRange()},n.WrappedRange=i)),n.createRange=function(e){return e=N(e,c,"createRange"),new n.WrappedRange(n.createNativeRange(e))},n.createRangyRange=function(e){return e=N(e,c,"createRangyRange"),new m(e)},p.createAliasForDeprecatedMethod(n,"createIframeRange","createRange"),p.createAliasForDeprecatedMethod(n,"createIframeRangyRange","createRangyRange"),n.addShimListener(function(e){var t=e.document;"undefined"==typeof t.createRange&&(t.createRange=function(){return n.createRange(t)}),t=null})}),f.createCoreModule("WrappedSelection",["DomRange","WrappedRange"],function(a,c){var d,u,s,h,e,f,g,p,m,R,C,n,v,l,N,E,y,S,t,T,w,A,x,O,b,P,I,_,D,B,k,L,M,H,W,z;function F(e){return"string"==typeof e?/^backward(s)?$/i.test(e):!!e}function V(e,t){return e?s.isWindow(e)?e:e instanceof ae?e.win:(e=s.getContentDocument(e,c,t),s.getWindow(e)):window}function j(e){return V(e,"getDocSelection").document.selection}function U(e){var t=!1;return t=e.anchorNode?1==s.comparePoints(e.anchorNode,e.anchorOffset,e.focusNode,e.focusOffset):t}if(a.config.checkSelectionRanges=!0,d="boolean",u="number",s=a.dom,e=(h=a.util).isHostMethod,f=a.DomRange,g=a.WrappedRange,p=a.DOMException,m=s.DomPosition,n=a.features,v="Control",l=s.getDocument,N=s.getBody,E=f.rangesEqual,t=e(window,"getSelection"),y=h.isHostObject(document,"selection"),n.implementsWinGetSelection=t,S=(n.implementsDocSelection=y)&&(!t||a.config.preferTextRange))R=j,a.isSelectionValid=function(e){var e=V(e,"isSelectionValid").document,t=e.selection;return"None"!=t.type||l(t.createRange().parentElement())==e};else{if(!t)return c.fail("Neither document.selection or window.getSelection() detected."),!1;R=function(e){return V(e,"getWinSelection").getSelection()},a.isSelectionValid=function(){return!0}}if(!(t=(a.getNativeSelection=R)()))return c.fail("Native selection was null (possibly issue 138?)"),!1;if(T=a.createNativeRange(document),w=N(document),A=h.areHostProperties(t,["anchorNode","focusNode","anchorOffset","focusOffset"]),n.selectionHasAnchorAndFocus=A,x=e(t,"extend"),n.selectionHasExtend=x,O=e(t,"setBaseAndExtent"),n.selectionHasSetBaseAndExtent=O,b=typeof t.rangeCount==u,n.selectionHasRangeCount=b,I=!(P=!1),_=x?function(e,t){var n=f.getRangeDocument(t),n=a.createRange(n);n.collapseToPoint(t.endContainer,t.endOffset),e.addRange(ee(n)),e.extend(t.startContainer,t.startOffset)}:null,h.areHostMethods(t,["addRange","getRangeAt","removeAllRanges"])&&typeof t.rangeCount==u&&n.implementsDomRange){var $,q,Y,r,G,Q,o,K,i=window.getSelection();if(i){for(K=1<($=i.rangeCount),q=[],Y=U(i),r=0;r<$;++r)q[r]=i.getRangeAt(r);for(Q=(G=s.createTestElement(document,"",!1)).appendChild(document.createTextNode("   ")),(o=document.createRange()).setStart(Q,1),o.collapse(!0),i.removeAllRanges(),i.addRange(o),I=1==i.rangeCount,i.removeAllRanges(),K||(K=window.navigator.appVersion.match(/Chrome\/(.*?) /),P=!(K&&36<=parseInt(K[1]))&&(K=o.cloneRange(),o.setStart(Q,0),K.setEnd(Q,3),K.setStart(Q,2),i.addRange(o),i.addRange(K),2==i.rangeCount)),s.removeNode(G),i.removeAllRanges(),r=0;r<$;++r)0==r&&Y?_?_(i,q[r]):(a.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"),i.addRange(q[r])):i.addRange(q[r])}}function X(e,t,n){var r=n?"end":"start",n=n?"start":"end";e.anchorNode=t[r+"Container"],e.anchorOffset=t[r+"Offset"],e.focusNode=t[n+"Container"],e.focusOffset=t[n+"Offset"]}function Z(e){e.anchorNode=e.focusNode=null,e.anchorOffset=e.focusOffset=0,e.rangeCount=0,e.isCollapsed=!0,e._ranges.length=0,J(e)}function J(e){e.type=0==e.rangeCount?"None":C(e)?"Caret":"Range"}function ee(e){var t;return e instanceof f?((t=a.createNativeRange(e.getDocument())).setEnd(e.endContainer,e.endOffset),t.setStart( -e.startContainer,e.startOffset)):e instanceof g?t=e.nativeRange:n.implementsDomRange&&e instanceof s.getWindow(e.startContainer).Range&&(t=e),t}function te(e){var t=e.getNodes();if(!function(e){if(e.length&&1==e[0].nodeType){for(var t=1,n=e.length;t=this.rangeCount)throw new p("INDEX_SIZE_ERR");return this._ranges[e].cloneRange()},S)H=function(e){var t;a.isSelectionValid(e.win)?t=e.docSelection.createRange():(t=N(e.win.document).createTextRange()).collapse(!0),e.docSelection.type==v?oe(e):ne(t)?re(e,t):Z(e)};else if(e(t,"getRangeAt")&&typeof t.rangeCount==u)H=function(e){if(D&&y&&e.docSelection.type==v)oe(e);else if(e._ranges.length=e.rangeCount=e.nativeSelection.rangeCount,e.rangeCount){for(var t=0,n=e.rangeCount;t(s.isCharacterDataNode(e)?e:e.childNodes).length)throw new p("INDEX_SIZE_ERR")} -function he(r){return function(e,t){var n;this.rangeCount?(n=this.getRangeAt(0))["set"+(r?"Start":"End")](e,t):(n=a.createRange(this.win.document)).setStartAndEnd(e,t),this.setSingleRange(n,this.isBackward())}}function fe(e){var t,n,r=[],o=new m(e.anchorNode,e.anchorOffset),i=new m(e.focusNode,e.focusOffset),a="function"==typeof e.getName?e.getName():"Selection";if("undefined"!=typeof e.rangeCount)for(t=0,n=e.rangeCount;t>>1^3988292384:t>>>=1;n[r]=t>>>0}return n}()}function f(e){return e.replace(//g,">")}function d(e,t){var n,r,o,i,a,s,c;switch(t=t||[],n=e.nodeType,o=(r=e.childNodes).length,i=[n,e.nodeName,o].join(":"),s=a="",n){case 3:a=f(e.nodeValue);break;case 8:a="\x3c!--"+f(e.nodeValue)+"--\x3e";break;default:a="<"+i+">",s=""}for(a&&t.push(a),c=0;c>6|192,63&t|128):n.push(t>>12|224,t>>6&63|128,63&t|128);return n}(e),n=-1,r=h(),o=0,i=t.length;o>>8^r[255&(n^t[o])];return(-1^n)>>>0},l=s.dom,i=/^([^,]+),([^,\{]+)(\{([^}]+)\})?$/,a="rangySerializedSelection",o.extend(s,{serializePosition:r,deserializePosition:g,serializeRange:p,deserializeRange:m,canDeserializeRange:R,serializeSelection:C,deserializeSelection:v,canDeserializeSelection:function(e,t,n){var r,o,i,a;for(t?r=n?n.document:l.getDocument(t):t=(n=n||window).document.documentElement,i=0,a=(o=e.split("|")).length;it&&--e.offset}),h.removeNode(e)}function _(e,t){for(var n,r=e,o=e.parentNode,i=h.getNodeIndex(e),e=!0,a=t,s=[];n=r.firstChild;)P(n,o,i++,a),s.push(n);return e&&I(r,a),s}function D(e,t){var n=e.cloneRange();return n.selectNodeContents(t),""!=((t=n.intersection(e))?t.toString():"")}function B(e){for(var t,n,r=e.getNodes([3]),o=0;(t=r[o])&&!D(e,t);)++o;for(n=r.length-1;(t=r[n])&&!D(e,t);)--n;return r.slice(o,n+1)}function k(e,t){if(e.attributes.length!=t.attributes.length)return!1;for(var n,r,o=0,i=e.attributes.length;or&&(--e.offset,e.offset==r+1&&ne.start},isContiguousWith:function(e){return this.start==e.end||this.end==e.start},union:function(e){return new w(Math.min(this.start,e.start),Math.max(this.end,e.end))},intersection:function(e){return new w(Math.max(this.start,e.start),Math.min(this.end,e.end))},getComplements:function(e){var t=[];if(this.start>=e.start){if(this.end<=e.end)return[];t.push(new w(e.end,this.end))}else t.push(new w(this.start,Math.min(this.end,e.start))),this.end>e.end&&t.push(new w(e.end,this.end));return t},toString:function(){return"[CharacterRange("+this.start+", "+this.end+")]"}},w.fromCharacterRange=function(e){return new w(e.start,e.end)},o={rangeToCharacterRange:function(e,t){t=e.getBookmark(t);return new w(t.start,t.end)},characterRangeToRange:function(e,t,n){e=y.createRange(e);return e.moveToBookmark({start:t.start,end:t.end,containerNode:n}),e},serializeSelection:function(e,t){for(var n=e.getAllRanges(),r=n.length,o=[],i=1==r&&e.isBackward(),a=0,s=n.length;a1

",!0),P=e.firstChild,(t=m.getSelection()).collapse(P.lastChild,2),t.setStart(P.firstChild,0),e.innerHTML="1
",t.collapse(e,2),t.setStart(e.firstChild,0),P=1==(""+t).length,e.innerHTML="1

1

",t.collapse(e,2),t.setStart(e.firstChild,0),b=1==(""+t).length,a.removeNode(e),t.removeAllRanges(),f={caseSensitive:!(h={en:{wordRegex:/[a-z0-9]+('[a-z0-9]+)*/gi,includeTrailingSpace:!(u={includeBlockContentTrailingSpace:!(l={includeBlockContentTrailingSpace:!0,includeSpaceBeforeBr:!0,includeSpaceBeforeBlock:!0,includePreLineTrailingSpace:!0,ignoreCharacters:""}),includeSpaceBeforeBr:!P,includeSpaceBeforeBlock:!b,includePreLineTrailingSpace:!0}),tokenizer:function(e,t){var n,r,o,i,a=e.join(""),s=[];function c(e,t,n){s.push({start:e,end:t,isWord:n})}for(r=0;n=t.wordRegex.exec(a);){if(i=(o=n.index)+n[0].length,r
'),this.editor=a.Node.create('
'),this.textareaLabel=a.one('[for="'+this.get("elementid")+'"]'),this.textareaLabel&&(this.textareaLabel.generateID(),this.editor.setAttribute("aria-labelledby",this.textareaLabel.get("id"))),this.setupToolbar(),this.setupTemplateEditor(),this.disableCssStyling(),document.queryCommandSupported("DefaultParagraphSeparator")&&document.execCommand("DefaultParagraphSeparator",!1,"p"),this.textarea.get("parentNode").insert(this._wrapper,this.textarea),this.textarea.hide(),this.updateFromTextArea(),this.setupTextareaNavigation(),this._preventEnter(),this.publishEvents(),this.setupSelectionWatchers(),this.setupAutomaticPolling(),this.setupPlugins())},destructor:function(){a.Array.each(this.plugins,function(e,t){e.destroy(),this.plugins[t]=undefined},this),new a.EventHandle(this._eventHandles).detach(),this.textarea.show(),this._wrapper.remove(!0),a.M.editor_ousupsub.removeEditorReference(this.get("elementid"),this)},focus:function(){return this.editor.focus(),this},publishEvents:function(){return this.publish("change",{broadcast:!0,preventable:!0}),this.publish("pluginsloaded",{fireOnce:!0}),this.publish("ousupsub:selectionchanged",{prefix:"ousupsub"}),this},setupAutomaticPolling:function(){return this._registerEventHandle(this.editor.on(["keyup","cut"],this.updateOriginal,this)),this._registerEventHandle(this.editor.on(["keypress","delete"],this.cleanEditorHTMLSimple,this)),this._registerEventHandle(this.editor.on("paste",this.pasteCleanup,this)),this._registerEventHandle(this.editor.on("drop",this.updateOriginalDelayed,this)),this},updateOriginalDelayed:function(){return setTimeout(a.bind(this.updateOriginal,this),0),this},setupPlugins:function(){var e,t,i,s,n;for(t in this.plugins={},e=this.get("plugins"))if((i=e[t]).plugins)for(s in i.plugins)"superscript"===(n=i.plugins[s]).name?this.plugins.superscript=new a.M.editor_ousupsub.EditorPlugin({name:"superscript",group:i.group,editor:this.editor,toolbar:this.toolbar,host:this,exec:"superscript",tags:"sup",keys:["94"],icon:"e/superscript",keyDescription:"Shift + ^ or Up arrow"}):"subscript"===n.name&&(this.plugins.subscript=new a.M.editor_ousupsub.EditorPlugin({name:"subscript",group:i.group,editor:this.editor,toolbar:this.toolbar,host:this,exec:"subscript",tags:"sub",keys:["95"],icon:"e/subscript",keyDescription:"Shift + _ or Down arrow"}));return this._undoStack=[],this._redoStack=[],this.plugins.undo=new a.M.editor_ousupsub.EditorPlugin({name:"undo",group:i.group,editor:this.editor,toolbar:this.toolbar,host:this,keys:["90"],callback:this._undoHandler}),this.plugins.redo=new a.M.editor_ousupsub.EditorPlugin({name:"redo",group:i.group,editor:this.editor,toolbar:this.toolbar,host:this,keys:["89"],callback:this._redoHandler}),this.on("pluginsloaded",function(){this._addToUndo(this._getHTML()),this.on("ousupsub:selectionchanged",this._changeListener,this)},this),this._updateButtonsStates(),this.setupUndoHandlers(),this.fire("pluginsloaded"),this},setupUndoHandlers:function(){return this._registerEventHandle(this._wrapper.delegate("key",this._undoHandler,"down:90+ctrl","."+e,this)),this._registerEventHandle(this._wrapper.delegate("key",this._redoHandler,"down:89+ctrl","."+e,this)),this},pluginEnabled:function(e){return!!this.plugins[e]},enablePlugins:function(e){this._setPluginState(!0,e)},disablePlugins:function(e){this._setPluginState(!1,e)},_setPluginState:function(e,t){var i=e?"enableButtons":"disableButtons";t?this.plugins[t][i]():a.Object.each(this.plugins,function(e){e[i]()},this)},_registerEventHandle:function(e){this._eventHandles.push(e)},setupToolbar:function(){return this.toolbar=a.Node.create(''),this._wrapper.appendChild(this.toolbar),this.textareaLabel&&this.toolbar.setAttribute("aria-labelledby",this.textareaLabel.get("id")),this.setupToolbarNavigation(),this},setupToolbarNavigation:function(){return this._wrapper.delegate("key",this.toolbarKeyboardNavigation,"down:37,39","."+t,this),this._wrapper.delegate("focus",function(e){this._setTabFocus(e.currentTarget)},"."+t+" button",this),this},toolbarKeyboardNavigation:function(e){e.preventDefault();var t=this.toolbar.all("button"),i=1,s=e.target.ancestor("button",!0);37===e.keyCode&&(i=-1),(e=this._findFirstFocusable(t,s,i))&&(e.focus(),this._setTabFocus(e))},_findFirstFocusable:function(e,t,i){var s,n,o=0,r=e.indexOf(t);for(r<-1&&(r=0);o=e.size()&&(r=0),o++,!(s=e.item(r)).hasAttribute("hidden")&&!s.hasAttribute("disabled")&&!s.ancestor(".ousupsub_group").hasAttribute("hidden")){n=s;break}return n},checkTabFocus:function(){var e;return this._tabFocus&&(!( -this._tabFocus.hasAttribute("disabled")||this._tabFocus.hasAttribute("hidden")||this._tabFocus.ancestor(".ousupsub_group").hasAttribute("hidden"))||(e=this._findFirstFocusable(this.toolbar.all("button"),this._tabFocus,-1))&&(this._tabFocus.compareTo(document.activeElement)&&e.focus(),this._setTabFocus(e))),this},_setTabFocus:function(e){return this._tabFocus&&this._tabFocus.setAttribute("tabindex","-1"),this._tabFocus=e,this._tabFocus.setAttribute("tabindex",0),this.toolbar.setAttribute("aria-activedescendant",this._tabFocus.generateID()),this},disableCssStyling:function(){try{document.execCommand("styleWithCSS",0,!1)}catch(e){try{document.execCommand("useCSS",0,!0)}catch(t){try{document.execCommand("styleWithCSS",!1,!1)}catch(i){}}}},setupTemplateEditor:function(){var t,e,i,s,n=a.Node.create('
');n.appendChild(this.editor),this._wrapper.appendChild(n),i=6*this.textarea.getAttribute("cols")+41+"px",this.editor.setStyle("width",i),this.editor.setStyle("minWidth",i),this.editor.setStyle("maxWidth",i),i=this.textarea.getAttribute("rows"),i=(t=6*i+13)-6+"px",this.editor.setStyle("height",e=t-10+"px"),this.editor.setStyle("minHeight",e),this.editor.setStyle("maxHeight",e),this.editor.setStyle("line-height",i),n.setStyle("minHeight",i=1+t+"px"),this.textareaLabel.setStyle("display","inline-block"),this.textareaLabel.setStyle("margin",0),this.textareaLabel.setStyle("height",i),this.textareaLabel.setStyle("minHeight",i),this.textareaLabel.setStyle("maxheight",i),this.textareaLabel.hasClass("accesshide")?(this.textareaLabel.removeClass("accesshide"),this.textareaLabel.setStyle("visibility","hidden"),this._wrapper.setStyle("margin-left",-parseInt(this.textareaLabel.get("offsetWidth")))):(this.textareaLabel.getDOMNode().parentNode.style.paddingBottom=e,this.textareaLabel.setStyle("vertical-align","bottom")),n="#"+(s=this).get("elementid").replace(/:/g,"\\:")+"editable",a.on("contentready",function(){s.textareaLabel.setStyle("line-height",s.editor.getComputedStyle("line-height"));var e=1+t+parseInt(s.toolbar.get("offsetHeight"));s._wrapper.setStyle("height",e),s._wrapper.setStyle("minHeight",e),s._wrapper.setStyle("maxHeight",e),a.UA.ie&&"hidden"===s.textareaLabel.getComputedStyle("visibility")&&s._wrapper.setStyle("vertical-align",parseInt(s.toolbar.get("offsetHeight"))-1+"px")},n)},_getEmptyContent:function(){return""},updateFromTextArea:function(){this.editor.setHTML(""),this.editor.append(this.textarea.get("value")),this.cleanEditorHTML(),""===this.editor.getHTML()&&this.editor.setHTML(this._getEmptyContent())},updateOriginal:function(){var e=this.textarea.get("value"),t=this.getCleanHTML();return""===t&&this.isActive()&&(t=this._getEmptyContent()),e!==(t=(t=this._removeUnicodeCharacters(t)).trim())&&(this.textarea.set("value",t),this.fire("change")),this},setupTextareaNavigation:function(){return this._registerEventHandle(this._wrapper.delegate("key",this.textareaKeyboardNavigation,"down:38,40","."+e,this)),this._registerEventHandle(this._wrapper.delegate("key",this.textareaKeyboardNavigation,"press:94, 95","."+e,this)),this},textareaKeyboardNavigation:function(e){var t;e.preventDefault(),YUI.Env.UA.android||this.isActive()||this.focus(),t="",38===(e=(e=window.event||e).keyCode||e.charCode)||94===e?t="superscript":40!==e&&95!==e||(t="subscript"),t&&this._applyTextCommand(t,1)},_preventEnter:function(){var e="keypress";(a.UA.webkit||a.UA.ie)&&(e="keydown"),this.editor.on(e,function(e){e=window.event||e;13===e.keyCode&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},this)},_addToRedo:function(e){this._redoStack.push(e)},_addToUndo:function(e,t){for(void 0===t&&(t=!1),this._undoStack[this._undoStack.length-1]!==e&&(this._undoStack.push(e),t&&(this._redoStack=[]));this._undoStack.length>this._maxUndos;)this._undoStack.shift()},_getHTML:function(){return this.getCleanHTML()},_getRedo:function(){return this._redoStack.pop()},_getUndo:function(e){if(1===this._undoStack.length)return this._undoStack[0];var t=this._undoStack.pop();return t===e&&(t=this._undoStack.pop()),0===this._undoStack.length&&this._addToUndo(t),t},_restoreValue:function(e){this.editor.setHTML(e),this._addToUndo(e)},_updateButtonsStates:function(){1"===t?"":(0===t.indexOf("")&&(e=t.length-("".length+"".length),t=t.substr("".length,e)),this._cleanHTML(t))},cleanEditorHTML:function(){return this.editor.set("innerHTML",this._cleanHTML(this.editor.get("innerHTML"))),this},cleanEditorHTMLSimple:function(){var e=window.rangy.saveSelection();return this.editor.set("innerHTML",this._cleanHTMLSimple(this.editor.get("innerHTML"))),window.rangy.restoreSelection(e,!0),this},_cleanHTMLSimple:function(e){return this._filterContentWithRules(e,[{regex:/]*?rangySelectionBoundary[^>]*?)[^>]*>(.+)<\/span>/gi,replace:"$1"}])},_cleanHTML:function(e){return this._filterContentWithRules(e,[{ -regex:/]*>( |\s)*<\/p>/gi,replace:""},{regex:/]*( |\s)*>/gi,replace:""},{regex:/]*( |\s)*>/gi,replace:""},{regex:/ /gi,replace:" "},{regex:/<\/sup>(\s*)+/gi,replace:"$1"},{regex:/<\/sub>(\s*)+/gi,replace:"$1"},{regex:/(\s*)+/gi,replace:"$1"},{regex:/(\s*)+/gi,replace:"$1"},{regex:/(\s*)+<\/sup>/gi,replace:"$1"},{regex:/(\s*)+<\/sub>/gi,replace:"$1"},{regex:/
/gi,replace:""},{regex:/]*>[\s\S]*?<\/style>/gi,replace:""},{regex:/)/gi,replace:""},{regex:/]*>[\s\S]*?<\/script>/gi,replace:""},{regex:/<\/?(?:br|title|meta|style|std|font|html|body|link|a|ul|li|ol)[^>]*?>/gi,replace:""},{regex:/<\/?(?:b|i|u|ul|ol|li|img)[^>]*?>/gi,replace:""},{regex:/<\/?(?:abbr|address|area|article|aside|audio|base|bdi|bdo|blockquote)[^>]*?>/gi,replace:""},{regex:/<\/?(?:button|canvas|caption|cite|code|col|colgroup|content|data)[^>]*?>/gi,replace:""},{regex:/<\/?(?:datalist|dd|decorator|del|details|dialog|dfn|div|dl|dt|element)[^>]*?>/gi,replace:""},{regex:/<\/?(?:em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5)[^>]*?>/gi,replace:""},{regex:/<\/?(?:h6|header|hgroup|hr|iframe|input|ins|kbd|keygen|label|legend)[^>]*?>/gi,replace:""},{regex:/<\/?(?:main|map|mark|menu|menuitem|meter|nav|noscript|object|optgroup)[^>]*?>/gi,replace:""},{regex:/<\/?(?:option|output|p|param|pre|progress|q|rp|rt|rtc|ruby|samp)[^>]*?>/gi,replace:""},{regex:/<\/?(?:section|select|script|shadow|small|source|std|strong|summary)[^>]*?>/gi,replace:""},{regex:/<\/?(?:svg|table|tbody|td|template|textarea|time|tfoot|th|thead|tr|track)[^>]*?>/gi,replace:""},{regex:/<\/?(?:var|wbr|video)[^>]*?>/gi,replace:""},{regex:/<\/?(?:acronym|applet|basefont|big|blink|center|dir|frame|frameset|isindex)[^>]*?>/gi,replace:""},{regex:/<\/?(?:listing|noembed|plaintext|spacer|strike|tt|xmp)[^>]*?>/gi,replace:""},{regex:/<\/?(?:jsl|nobr)[^>]*?>/gi,replace:""},{regex:/]*?rangySelectionBoundary[^>]*?)[^>]*>[\s\S]*?([\s\S]*?)<\/span>/gi,replace:"$1"},{regex:/]*?rangySelectionBoundary[^>]*?)[^>]*>( |\s)*<\/span>/gi,replace:""},{regex:/]*?rangySelectionBoundary[^>]*?)[^>]*>[\s\S]*?([\s\S]*?)<\/span>/gi,replace:"$1"},{regex:/]*>( |\s)*<\/sup>/gi,replace:""},{regex:/]*>( |\s)*<\/sub>/gi,replace:""},{regex:/(.*?)<\/xmlns.*?>/gi,replace:"$1"}])},cleanEditorHTMLEmptySupAndSubTags:function(){var e=window.rangy.saveSelection(),t=this.editor.get("innerHTML"),t=this._cleanEditorHTMLEmptySupAndSubTags(t),t=this._removeUnicodeCharacters(t);return this.editor.set("innerHTML",t),window.rangy.restoreSelection(e,!0),this},_cleanEditorHTMLEmptySupAndSubTags:function(e){return this._filterContentWithRules(e,[{regex:/]*>(|\s)*<\/su[bp]>/gi,replace:""}])},_filterContentWithRules:function(e,t){for(var i=0,i=0;i([\s\S]+)$/gi,replace:""},{regex://gi,replace:""},{regex://gi,replace:""},{regex:/]*>[\s\S]*?<\/xml>/gi,replace:""},{regex:/<\?xml[^>]*>[\s\S]*?<\\\?xml>/gi,replace:""},{regex:/<\/?\w+:[^>]*>/gi,replace:""}]),0!==(e=this._cleanHTML(e)).length&&e.match(/\S/)?((t=document.createElement("div")).innerHTML=e,e=t.innerHTML,t.innerHTML="",e=this._filterContentWithRules(e,[{regex:/(<[^>]*?style\s*?=\s*?"[^>"]*?)(?:[\s]*MSO[-:][^>;"]*;?)+/gi,replace:"$1"},{regex:/(<[^>]*?class\s*?=\s*?"[^>"]*?)(?:[\s]*MSO[_a-zA-Z0-9\-]*)+/gi,replace:"$1"},{regex:/(<[^>]*?class\s*?=\s*?"[^>"]*?)(?:[\s]*Apple-[_a-zA-Z0-9\-]*)+/gi,replace:"$1"},{regex:/]*?name\s*?=\s*?"OLE_LINK\d*?"[^>]*?>\s*?<\/a>/gi,replace:""}]),this._cleanHTML(e)):e):""},_applyTextCommand:function(e,t){var i;if(t){if("superscript"===(i=this.getCursorTag())&&e===i||"subscript"===i&&e===i)return;if("superscript"===i&&"subscript"===e?e="superscript":"subscript"===i&&"superscript"===e&&(e="subscript"),!this.pluginEnabled(e))return}document.execCommand(e,!1,null),(t=window.rangy.getSelection()).isCollapsed&&(this.cleanEditorHTMLEmptySupAndSubTags(),e=this.insertContentAtFocusPoint("<"+(i="superscript"===e?"sup":"sub")+">\ufeff"),(i=window.rangy.createRange()).selectNode(e._node.childNodes[0]),this.setSelection([i]),t.rangeCount&&t.collapseToEnd()),this._normaliseTextarea(),this.cleanEditorHTMLSimple(),this.saveSelection(),this.updateOriginal()},getCursorTag:function(){var e="text",t=window.rangy.getSelection(),i=t.focusNode.nodeName.toLowerCase(),s=t.focusNode.parentNode.nodeName.toLowerCase(),n=""; -return t.focusNode.childNodes&&t.focusNode.childNodes[t.focusOffset-1]&&(n=t.focusNode.childNodes[t.focusOffset-1].nodeName.toLowerCase()),"sup"===i||"sup"===s||"sup"===n?e="superscript":"sub"!==i&&"sub"!==s&&"sub"!==n||(e="subscript"),e},_normaliseTextarea:function(){var e,t,i=window.rangy.saveSelection(),s=this._getEditorNode();for(this._removeSingleNodesByName(s,"br"),e=["p","b","i","u","ul","ol","li"],t=0;t", expected: "1", event: "paste"}, - - // Spans with rangy and without. - {input: "12345", expected: "12345"}, // Keep sub. - - /* Check for disallowed characters */ - {input: "

12

", expected: "12"}, - {input: "12", expected: "12"}, - {input: "12", expected: "12"}, - {input: "12", expected: "12"}, - {input: "1
", expected: "1"}, - {input: "1
", expected: "1"}, - {input: "1.2x103 g", expected: "1.2x103 g"}, // Empty trailing sup tag removed - {input: "1.2x103 g", expected: "1.2x103 g"}, // Empty trailing sub tag removed - {input: "mm s", expected: "mm s"} // Empty special xmlns tag from browser plugin. - ]; - - // Elements to remove completely including contents. - var disallowed_characters_and_text = ['style','script']; - for (var x = 0; x < disallowed_characters_and_text.length; x++) { - testcases[testcases.length] = {input: "<" + disallowed_characters_and_text[x] + ">1", expected: ""}; - } - - // Elements to remove while contents are left. - var disallowed_characters = ['br','title','std','font','html','body','link', - 'a','ul','li','ol','b','i','u','ul','ol','li','img', - 'abbr','address','area','article','address','article', - 'aside','audio','base','bdi','bdo','blockquote','button', - 'canvas','caption','cite','code','col','colgroup','content', - 'data','datalist','dd','decorator','del','details','dialog', - 'dfn','div','dl','dt','element','em','embed','fieldset', - 'figcaption','figure','footer','form','h1','h2','h3','h4', - 'h5','h6','head','header','hgroup','hr','iframe','input', - 'ins','kbd','keygen','label','legend','main','map','mark', - 'menu','menuitem','meter','meta','nav','noscript', - 'object','optgroup','option','output','optgroup','options', - 'p','param','pre','progress','q','rp','rt','rtc','ruby', - 'samp','section','select','shadow','small','source','std', - 'strong','summary','span','table','tbody','td','template', - 'textarea','time','tfoot','th','thead','tr','track','var', - 'wbr','video', - // Deprecated elements - 'acronym','applet','basefont','big','blink','center','dir', - 'frame','frameset','isindex','listing','noembed', - 'spacer','strike','tt','xmp', - // Elements from common sites including google.com. - 'jsl','nobr' - ]; - for (var x = 0; x < disallowed_characters.length; x++) { - testcases[testcases.length] = {input: "<" + disallowed_characters[x] + ">1", expected: "1"}; - } - - - function init_ousupsub(id, params) { - M.str = { - "moodle": { - "error": "Error", - "morehelp": "More help" - }, - "editor_ousupsub": { - "editor_command_keycode":"Cmd + {$a}", - "editor_control_keycode":"Ctrl + {$a}", - "editor_shift_keycode":"Shift + {$a}", - "plugin_title_shortcut":"{$a->title} [{$a->shortcut}]", - "subscript":"Subscript", - "superscript":"Superscript" - }, - } - - plugins = []; - if (params.superscript) { - plugins.push({"name": "superscript", "params": []}); - } - if (params.subscript) { - plugins.push({"name": "subscript", "params": []}); - } - - Y.M.editor_ousupsub.createEditor( - {"elementid":id,"content_css":"","contextid":0,"language":"en", - "directionality":"ltr","plugins":[{"group":"style1","plugins":plugins}],"pageHash":""}); - }; - - // Initialise an editor to test with. - init_ousupsub("id_description_editor", {"subscript":true, "superscript":true}); - - function get_editor(id) { - return Y.M.editor_ousupsub.getEditor(id); - } - - function escape_html(str) { - return str.replace(/&/g,'&').replace(//g,'>').replace(/ /g,'.'); - } - - function run_tests(Y) { - var editor = get_editor("id_description_editor"); - for(var i = 0; i < testcases.length; i++) { - run_test(editor, testcases[i]); - } - } - - - function run_test(editor, test) { - var input = test.input - if(test.event && test.event == 'paste') { - input = editor._cleanPasteHTML(input); - } - editor.editor.set('innerHTML', input); - // Fake the subscript button. - editor.plugins.subscript._applyTextCommand(); - // Fake submit - editor.updateFromTextArea(); - test.actual = editor.editor.get('innerHTML'); - test.matched = test.expected == test.actual; - } - - function update_display() { - // Update table. - var table = Y.one('#results'); - var showPasses = false; - var numberPassed = 0, numberFailed = 0; - var summary = ''; - var summaryNode = Y.one('#summary'); - for(var i = 0; i < testcases.length; i++) { - test = testcases[i]; - test.matched ? ++numberPassed : ++numberFailed; - - if (!showPasses && test.matched) { - continue; - } - - var rowText = ''; - rowText += '' + escape_html(test.input) + ''; - rowText += '' + escape_html(test.expected) + ''; - rowText += '' + escape_html(test.actual) + ''; - rowText += '' + test.matched + ''; - rowText += ''; - var row = Y.Node.create(rowText); - table.appendChild(row); - } - - // Explain if there are no failures to show. - if (!numberFailed) { - var rowText = ''; - rowText += 'There were no failures to show.'; - rowText += ''; - var row = Y.Node.create(rowText); - table.appendChild(row); - } - - summary = 'Of ' + testcases.length + ' tests run there were ' + numberPassed + ' test passes and '; - summary += numberFailed + ' failures.'; - summaryNode.set('innerHTML', summary); - - var statusNode = Y.one('#status'); - var status = numberFailed ? 'failure' : 'success'; - statusNode.set('innerHTML', 'Overall status = ' + status + ''); - } - - run_tests(); - update_display(); -}); diff --git a/thirdpartylibs.xml b/thirdpartylibs.xml deleted file mode 100644 index 8b19f67..0000000 --- a/thirdpartylibs.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - yui/src/rangy/js/*.* - Rangy - MIT - 1.3.1 - - - diff --git a/yui/build/moodle-editor_ousupsub-editor/moodle-editor_ousupsub-editor-debug.js b/yui/build/moodle-editor_ousupsub-editor/moodle-editor_ousupsub-editor-debug.js deleted file mode 100644 index 17c515e..0000000 --- a/yui/build/moodle-editor_ousupsub-editor/moodle-editor_ousupsub-editor-debug.js +++ /dev/null @@ -1,3456 +0,0 @@ -YUI.add('moodle-editor_ousupsub-editor', function (Y, NAME) { - -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * The ousupsub WYSIWG pluggable editor, written for Moodle. - * - * @module moodle-editor_ousupsub-editor - * @package editor_ousupsub - * @copyright 2013 Damyon Wiese - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @main moodle-editor_ousupsub-editor - */ - -/** - * @module moodle-editor_ousupsub-editor - * @submodule editor-base - */ - -var LOGNAME = 'moodle-editor_ousupsub-editor'; -var CSS = { - CONTENT: 'editor_ousupsub_content', - CONTENTWRAPPER: 'editor_ousupsub_content_wrap', - EDITORWRAPPER: '.editor_ousupsub_content', - TOOLBAR: 'editor_ousupsub_toolbar', - WRAPPER: 'editor_ousupsub', - HIGHLIGHT: 'highlight' - }; - -/** - * The ousupsub editor for Moodle. - * - * @namespace M.editor_ousupsub - * @class Editor - * @constructor - * @uses M.editor_ousupsub.EditorClean - * @uses M.editor_ousupsub.EditorSelection - */ - -function Editor() { - Editor.superclass.constructor.apply(this, arguments); -} - -Y.extend(Editor, Y.Base, { - - /** - * List of known block level tags. - * Taken from "https://developer.mozilla.org/en-US/docs/HTML/Block-level_elements". - * - * @property BLOCK_TAGS - * @type {Array} - */ - BLOCK_TAGS : [ - 'address', - 'article', - 'aside', - 'audio', - 'blockquote', - 'canvas', - 'dd', - 'div', - 'dl', - 'fieldset', - 'figcaption', - 'figure', - 'footer', - 'form', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'header', - 'hgroup', - 'hr', - 'noscript', - 'ol', - 'output', - 'p', - 'pre', - 'section', - 'table', - 'tfoot', - 'ul', - 'video' - ], - - PLACEHOLDER_CLASS: 'ousupsub-tmp-class', - ALL_NODES_SELECTOR: '[style],font[face]', - FONT_FAMILY: 'fontFamily', - - /** - * The wrapper containing the editor. - * - * @property _wrapper - * @type Node - * @private - */ - _wrapper: null, - - /** - * A reference to the content editable Node. - * - * @property editor - * @type Node - */ - editor: null, - - /** - * A reference to the toolbar Node. - * - * @property toolbar - * @type Node - */ - toolbar: null, - - /** - * A reference to the original text area. - * - * @property textarea - * @type Node - */ - textarea: null, - - /** - * A reference to the label associated with the original text area. - * - * @property textareaLabel - * @type Node - */ - textareaLabel: null, - - /** - * A reference to the list of plugins. - * - * @property plugins - * @type object - */ - plugins: null, - - /** - * Event Handles to clear on editor destruction. - * - * @property _eventHandles - * @private - */ - _eventHandles: null, - - /** - * The current focal point for tabbing. - * - * @property _tabFocus - * @type Node - * @default null - * @private - */ - _tabFocus: null, - - /** - * The maximum saved number of undo steps. - * - * @property _maxUndos - * @type {Number} The maximum number of saved undos. - * @default 40 - * @private - */ - _maxUndos: 40, - - /** - * History of edits. - * - * @property _undoStack - * @type {Array} The elements of the array are the html strings that make a snapshot - * @private - */ - _undoStack: null, - - /** - * History of edits. - * - * @property _redoStack - * @type {Array} The elements of the array are the html strings that make a snapshot - * @private - */ - _redoStack: null, - - initializer: function() { - // Note - it is not safe to use a CSS selector like '#' + elementid because the id - // may have colons in it - e.g. quiz. - this.textarea = Y.one(document.getElementById(this.get('elementid'))); - - if (!this.textarea) { - // No text area found. - Y.log('Text area not found - unable to setup editor for ' + this.get('elementid'), - 'error', LOGNAME); - return; - } - - // Add the editor to the manager. - Y.M.editor_ousupsub.addEditorReference(this.get('elementid'), this); - - this._eventHandles = []; - - this._wrapper = Y.Node.create('
'); - this.editor = Y.Node.create('
'); - - // Add a labelled-by attribute to the contenteditable. - this.textareaLabel = Y.one('[for="' + this.get('elementid') + '"]'); - if (this.textareaLabel) { - this.textareaLabel.generateID(); - this.editor.setAttribute('aria-labelledby', this.textareaLabel.get("id")); - } - - // Add everything to the wrapper. - this.setupToolbar(); - - this.setupTemplateEditor(); - - // Disable odd inline CSS styles. - this.disableCssStyling(); - - // Use paragraphs not divs. - if (document.queryCommandSupported('DefaultParagraphSeparator')) { - document.execCommand('DefaultParagraphSeparator', false, 'p'); - } - - // Add the toolbar and editable zone to the page. - this.textarea.get('parentNode').insert(this._wrapper, this.textarea); - - // Hide the old textarea. - this.textarea.hide(); - - // Copy the text to the contenteditable div. - this.updateFromTextArea(); - - // Add keyboard navigation for the textarea. - this.setupTextareaNavigation(); - - // Prevent carriage return to produce a new line. - this._preventEnter(); - - // Publish the events that are defined by this editor. - this.publishEvents(); - - // Add handling for saving and restoring selections on cursor/focus changes. - this.setupSelectionWatchers(); - - // Add polling to update the textarea periodically when typing long content. - this.setupAutomaticPolling(); - - // Setup plugins. - this.setupPlugins(); - }, - - destructor: function() { - // Destroy each of the plugins - they may have destruction phases. - Y.Array.each(this.plugins, function(item, key) { - item.destroy(); - this.plugins[key] = undefined; - }, this); - - // Clear any event handles we created. - new Y.EventHandle(this._eventHandles).detach(); - - // Return the editor back to it's original state. - this.textarea.show(); - this._wrapper.remove(true); - - // Finally remove this reference from the manager. - Y.M.editor_ousupsub.removeEditorReference(this.get('elementid'), this); - }, - - /** - * Focus on the editable area for this editor. - * - * @method focus - * @chainable - */ - focus: function() { - this.editor.focus(); - return this; - }, - - /** - * Publish events for this editor instance. - * - * @method publishEvents - * @private - * @chainable - */ - publishEvents: function() { - /** - * Fired when changes are made within the editor. - * - * @event change - */ - this.publish('change', { - broadcast: true, - preventable: true - }); - - /** - * Fired when all plugins have completed loading. - * - * @event pluginsloaded - */ - this.publish('pluginsloaded', { - fireOnce: true - }); - - this.publish('ousupsub:selectionchanged', { - prefix: 'ousupsub' - }); - - return this; - }, - - /** - * Set up automated polling of the text area to update the textarea. - * - * @method setupAutomaticPolling - * @chainable - */ - setupAutomaticPolling: function() { - this._registerEventHandle(this.editor.on(['keyup', 'cut'], this.updateOriginal, this)); - this._registerEventHandle(this.editor.on(['keypress', 'delete'], this.cleanEditorHTMLSimple, this)); - this._registerEventHandle(this.editor.on('paste', this.pasteCleanup, this)); - - // Call this.updateOriginal after dropped content has been processed. - this._registerEventHandle(this.editor.on('drop', this.updateOriginalDelayed, this)); - - return this; - }, - - /** - * Calls updateOriginal on a short timer to allow native event handlers to run first. - * - * @method updateOriginalDelayed - * @chainable - */ - updateOriginalDelayed: function() { - setTimeout(Y.bind(this.updateOriginal, this), 0); - - return this; - }, - - setupPlugins: function() { - // Clear the list of plugins. - this.plugins = {}; - - var plugins = this.get('plugins'); - - var groupIndex, - group, - pluginIndex, - plugin; - - for (groupIndex in plugins) { - group = plugins[groupIndex]; - if (!group.plugins) { - // No plugins in this group - skip it. - continue; - } - for (pluginIndex in group.plugins) { - plugin = group.plugins[pluginIndex]; - if (plugin.name === 'superscript') { - this.plugins.superscript = new Y.M.editor_ousupsub.EditorPlugin({ - name: 'superscript', - group: group.group, - editor: this.editor, - toolbar: this.toolbar, - host: this, - exec: 'superscript', - tags: 'sup', - // Key code (up arrow) for the keyboard shortcut which triggers this button: - // Up arrow should be 38 but doesn't register and is handled elsewhere. - keys: ['94'], - icon: 'e/superscript', - keyDescription: "Shift + ^ or Up arrow" - }); - } else if (plugin.name === 'subscript') { - this.plugins.subscript = new Y.M.editor_ousupsub.EditorPlugin({ - name: 'subscript', - group: group.group, - editor: this.editor, - toolbar: this.toolbar, - host: this, - exec: 'subscript', - tags: 'sub', - // Key codes (underscore) for the keyboard shortcut which triggers this button: - // Down arrow should be 40 but doesn't register. - keys: ['95'], - icon: 'e/subscript', - keyDescription: "Shift + _ or Down arrow" - }); - } - } - } - - // Initialise the undo and redo stacks. - this._undoStack = []; - this._redoStack = []; - - // Add undo plugin - this.plugins.undo = new Y.M.editor_ousupsub.EditorPlugin({ - name: 'undo', - group: group.group, - editor: this.editor, - toolbar: this.toolbar, - host: this, - keys: ['90'], - callback: this._undoHandler - }); - - // Add redo plugin - this.plugins.redo = new Y.M.editor_ousupsub.EditorPlugin({ - name: 'redo', - group: group.group, - editor: this.editor, - toolbar: this.toolbar, - host: this, - keys: ['89'], - callback: this._redoHandler - }); - - // Enable the undo once everything has loaded. - this.on('pluginsloaded', function() { - // Adds the current value to the stack. - this._addToUndo(this._getHTML()); - this.on('ousupsub:selectionchanged', this._changeListener, this); - }, this); - - this._updateButtonsStates(); - this.setupUndoHandlers(); - - // Some plugins need to perform actions once all plugins have loaded. - this.fire('pluginsloaded'); - - return this; - }, - - /** - * Set up the watchers for undo/redo. - * - * @method setupUndoHandlers - * @chainable - */ - setupUndoHandlers: function() { - // Listen for ctrl+z and ctrl+y keys. - this._registerEventHandle(this._wrapper.delegate('key', - this._undoHandler, - 'down:90+ctrl', - '.' + CSS.CONTENT, - this)); - this._registerEventHandle(this._wrapper.delegate('key', - this._redoHandler, - 'down:89+ctrl', - '.' + CSS.CONTENT, - this)); - - return this; - }, - - pluginEnabled: function(plugin) { - return this.plugins[plugin] ? true : false; - }, - - enablePlugins: function(plugin) { - this._setPluginState(true, plugin); - }, - - disablePlugins: function(plugin) { - this._setPluginState(false, plugin); - }, - - _setPluginState: function(enable, plugin) { - var target = 'disableButtons'; - if (enable) { - target = 'enableButtons'; - } - - if (plugin) { - this.plugins[plugin][target](); - } else { - Y.Object.each(this.plugins, function(currentPlugin) { - currentPlugin[target](); - }, this); - } - }, - - /** - * Register an event handle for disposal in the destructor. - * - * @method _registerEventHandle - * @param {EventHandle} The Event Handle as returned by Y.on, and Y.delegate. - * @private - */ - _registerEventHandle: function(handle) { - this._eventHandles.push(handle); - }, - - /** - * Setup the toolbar on the editor. - * - * @method setupToolbar - * @chainable - */ - setupToolbar: function() { - this.toolbar = Y.Node.create(''); - this._wrapper.appendChild(this.toolbar); - - if (this.textareaLabel) { - this.toolbar.setAttribute('aria-labelledby', this.textareaLabel.get("id")); - } - - // Add keyboard navigation for the toolbar. - this.setupToolbarNavigation(); - - return this; - }, - - /** - * Set up the watchers for toolbar navigation. - * - * @method setupToolbarNavigation - * @chainable - */ - setupToolbarNavigation: function() { - // Listen for Arrow left and Arrow right keys. - this._wrapper.delegate('key', - this.toolbarKeyboardNavigation, - 'down:37,39', - '.' + CSS.TOOLBAR, - this); - this._wrapper.delegate('focus', - function(e) { - this._setTabFocus(e.currentTarget); - }, '.' + CSS.TOOLBAR + ' button', this); - - return this; - }, - - /** - * Implement arrow key navigation for the buttons in the toolbar. - * - * @method toolbarKeyboardNavigation - * @param {EventFacade} e - the keyboard event. - */ - toolbarKeyboardNavigation: function(e) { - // Prevent the default browser behaviour. - e.preventDefault(); - - // On cursor moves we loops through the buttons. - var buttons = this.toolbar.all('button'), - direction = 1, - button, - current = e.target.ancestor('button', true); - - if (e.keyCode === 37) { - // Moving left so reverse the direction. - direction = -1; - } - - button = this._findFirstFocusable(buttons, current, direction); - if (button) { - button.focus(); - this._setTabFocus(button); - } else { - Y.log("Unable to find a button to focus on", 'debug', LOGNAME); - } - }, - - /** - * Find the first focusable button. - * - * @param {NodeList} buttons A list of nodes. - * @param {Node} startAt The node in the list to start the search from. - * @param {Number} direction The direction in which to search (1 or -1). - * @return {Node | Undefined} The Node or undefined. - * @method _findFirstFocusable - * @private - */ - _findFirstFocusable: function(buttons, startAt, direction) { - var checkCount = 0, - group, - candidate, - button, - index; - - // Determine which button to start the search from. - index = buttons.indexOf(startAt); - if (index < -1) { - Y.log("Unable to find the button in the list of buttons", 'debug', LOGNAME); - index = 0; - } - - // Try to find the next. - while (checkCount < buttons.size()) { - index += direction; - if (index < 0) { - index = buttons.size() - 1; - } else if (index >= buttons.size()) { - // Handle wrapping. - index = 0; - } - - candidate = buttons.item(index); - - // Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item. - checkCount++; - - // Loop while: - // * we haven't checked every button; - // * the button is hidden or disabled; - // * the group is hidden. - if (candidate.hasAttribute('hidden') || candidate.hasAttribute('disabled')) { - continue; - } - group = candidate.ancestor('.ousupsub_group'); - if (group.hasAttribute('hidden')) { - continue; - } - - button = candidate; - break; - } - - return button; - }, - - /** - * Check the tab focus. - * - * When we disable or hide a button, we should call this method to ensure that the - * focus is not currently set on an inaccessible button, otherwise tabbing to the toolbar - * would be impossible. - * - * @method checkTabFocus - * @chainable - */ - checkTabFocus: function() { - if (this._tabFocus) { - if (this._tabFocus.hasAttribute('disabled') || this._tabFocus.hasAttribute('hidden') - || this._tabFocus.ancestor('.ousupsub_group').hasAttribute('hidden')) { - // Find first available button. - var button = this._findFirstFocusable(this.toolbar.all('button'), this._tabFocus, -1); - if (button) { - if (this._tabFocus.compareTo(document.activeElement)) { - // We should also move the focus, because the inaccessible button also has the focus. - button.focus(); - } - this._setTabFocus(button); - } - } - } - return this; - }, - - /** - * Sets tab focus for the toolbar to the specified Node. - * - * @method _setTabFocus - * @param {Node} button The node that focus should now be set to - * @chainable - * @private - */ - _setTabFocus: function(button) { - if (this._tabFocus) { - // Unset the previous entry. - this._tabFocus.setAttribute('tabindex', '-1'); - } - - // Set up the new entry. - this._tabFocus = button; - this._tabFocus.setAttribute('tabindex', 0); - - // And update the activedescendant to point at the currently selected button. - this.toolbar.setAttribute('aria-activedescendant', this._tabFocus.generateID()); - - return this; - }, - - /** - * Disable CSS styling. Use HTML elements instead. - * - * @method disableCssStyling - */ - disableCssStyling: function() { - try { - document.execCommand("styleWithCSS", 0, false); - } catch (e1) { - try { - document.execCommand("useCSS", 0, true); - } catch (e2) { - try { - document.execCommand('styleWithCSS', false, false); - } catch (e3) { - // We did our best. - } - } - } - }, - - /** - * Setup Template for Editor. - * Because of the limitation of css when make align question text and answer text on same baseline in differences qtypes, - * also themes. Example font-size, line-height difference each other is very small: 0.1px. - * So we need to use this function to get the computed style to apply to both question text and answer text, - * to make sure they have the same baseline. - * - * - * @method setupTemplateEditor - */ - setupTemplateEditor: function() { - var content = Y.Node.create('
'); - content.appendChild(this.editor); - this._wrapper.appendChild(content); - // Set the visible width and height. - var width = (this.textarea.getAttribute('cols') * 6 + 41) + 'px'; - this.editor.setStyle('width', width); - this.editor.setStyle('minWidth', width); - this.editor.setStyle('maxWidth', width); - var rows = this.textarea.getAttribute('rows'); - var height = (rows * 6 + 13); - var heightEditor = (height - 10) + 'px'; - var lineHeightEditor = (height - 6) + 'px'; - // Style the editor. - this.editor.setStyle('height', heightEditor); - this.editor.setStyle('minHeight', heightEditor); - this.editor.setStyle('maxHeight', heightEditor); - // Align the content in the editor with the textarea label. - this.editor.setStyle('line-height', lineHeightEditor); - // Style the textarea label with the content editor. - var heightContent = (height + 1) + 'px'; - content.setStyle('minHeight', heightContent); - // Align the textarea label with the content editor. - this.textareaLabel.setStyle('display', 'inline-block'); - this.textareaLabel.setStyle('margin', 0); - this.textareaLabel.setStyle('height', heightContent); - this.textareaLabel.setStyle('minHeight', heightContent); - this.textareaLabel.setStyle('maxheight', heightContent); - // Align for the case using Supsub on the editor. - if (this.textareaLabel.hasClass('accesshide')) { - this.textareaLabel.removeClass('accesshide'); - this.textareaLabel.setStyle('visibility', 'hidden'); - this._wrapper.setStyle('margin-left', -parseInt(this.textareaLabel.get('offsetWidth'))); - } else { - // Get parent node of the label. - var labelParentNode = this.textareaLabel.getDOMNode().parentNode; - labelParentNode.style.paddingBottom = heightEditor; - this.textareaLabel.setStyle('vertical-align', 'bottom'); - } - // Update the height of the editor and label for correct align after document ready. - var self = this; - var selectorEditor = '#' + self.get('elementid').replace(/:/g, "\\:") + 'editable'; - Y.on('contentready', function() { - self.textareaLabel.setStyle('line-height', self.editor.getComputedStyle('line-height')); - var heightWrapper = height + 1 + parseInt(self.toolbar.get('offsetHeight')); - self._wrapper.setStyle('height', heightWrapper); - self._wrapper.setStyle('minHeight', heightWrapper); - self._wrapper.setStyle('maxHeight', heightWrapper); - if (Y.UA.ie && self.textareaLabel.getComputedStyle('visibility') === 'hidden') { - // IE have problem with vertical-align: bottom. We need to calculate the exact pixel for it. - self._wrapper.setStyle('vertical-align', parseInt(self.toolbar.get('offsetHeight')) - 1 + 'px'); - } - }, selectorEditor); - }, - - /** - * Return the appropriate empty content value for the current browser. - * - * Different browsers use a different content when they are empty and - * we must set this reliable across the board. - * - * @method _getEmptyContent - * @return String The content to use representing no user-provided content - * @private - */ - _getEmptyContent: function() { - return ''; - }, - - /** - * Copy and clean the text from the textarea into the contenteditable div. - * - * If the text is empty, provide a default paragraph tag to hold the content. - * - * @method updateFromTextArea - * @chainable - */ - updateFromTextArea: function() { - // Clear it first. - this.editor.setHTML(''); - - // Copy text to editable div. - this.editor.append(this.textarea.get('value')); - - // Clean it. - this.cleanEditorHTML(); - - // Insert a paragraph in the empty contenteditable div. - if (this.editor.getHTML() === '') { - this.editor.setHTML(this._getEmptyContent()); - } - }, - - /** - * Copy the text from the contenteditable to the textarea which it replaced. - * - * @method updateOriginal - * @chainable - */ - updateOriginal : function() { - // Get the previous and current value to compare them. - var oldValue = this.textarea.get('value'), - newValue = this.getCleanHTML(); - - if (newValue === "" && this.isActive()) { - // The content was entirely empty so get the empty content placeholder. - newValue = this._getEmptyContent(); - } - - newValue = this._removeUnicodeCharacters(newValue); - newValue = newValue.trim(); - - // Only call this when there has been an actual change to reduce processing. - if (oldValue !== newValue) { - // Insert the cleaned content. - this.textarea.set('value', newValue); - - // Trigger handlers for this action. - this.fire('change'); - } - - return this; - }, - - /** - * Set up the watchers for textarea navigation. - * - * @method setupTextareaNavigation - * @chainable - */ - setupTextareaNavigation: function() { - // Listen for Up and down Arrow keys. - this._registerEventHandle(this._wrapper.delegate('key', - this.textareaKeyboardNavigation, - 'down:38,40', - '.' + CSS.CONTENT, - this)); - - // Listen for hat (^), underscore. - this._registerEventHandle(this._wrapper.delegate('key', - this.textareaKeyboardNavigation, - 'press:94, 95', - '.' + CSS.CONTENT, - this)); - - return this; - }, - - /** - * Implement arrow key navigation for the buttons in the toolbar. - * - * @method textareaKeyboardNavigation - * @param {EventFacade} e - the keyboard event. - */ - textareaKeyboardNavigation: function(e) { - - // Prevent the default browser behaviour. - e.preventDefault(); - - // From editor-plugins_buttons::callbackWrapper(). - if (!(YUI.Env.UA.android || this.isActive())) { - // We must not focus for Android here, even if the editor is not active because the keyboard auto-completion - // changes the cursor position. - // If we save that change, then when we restore the change later we get put in the wrong place. - // Android is fine to save the selection without the editor being in focus. - this.focus(); - } - - var command = '', mode = 1; - // Cross browser event object. - var evt = window.event || e; - var code = evt.keyCode ? evt.keyCode : evt.charCode; - // Call superscript. - if ((code === 38) || (code === 94)) { - command = 'superscript'; - // Call subscript. - } else if ((code === 40) || (code === 95)) { - command = 'subscript'; - } - - if (!command) { - return; - } - - this._applyTextCommand(command, mode); - }, - - /** - * Prevent carriage return to produce a new line. - */ - _preventEnter: function() { - var keyEvent = 'keypress'; - if (Y.UA.webkit || Y.UA.ie) { - keyEvent = 'keydown'; - } - this.editor.on(keyEvent, function(e) { - //Cross browser event object. - var evt = window.event || e; - if (evt.keyCode === 13) { // Enter. - // do nothing. - if(!evt.preventDefault) { - evt.returnValue = false; - return; - } - evt.preventDefault(); - } - }, this); - }, - - /** - * Adds an element to the redo stack. - * - * @method _addToRedo - * @private - * @param {String} html The HTML content to save. - */ - _addToRedo: function(html) { - this._redoStack.push(html); - }, - - /** - * Adds an element to the undo stack. - * - * @method _addToUndo - * @private - * @param {String} html The HTML content to save. - * @param {Boolean} [clearRedo=false] Whether or not we should clear the redo stack. - */ - _addToUndo: function(html, clearRedo) { - var last = this._undoStack[this._undoStack.length - 1]; - - if (typeof clearRedo === 'undefined') { - clearRedo = false; - } - - if (last !== html) { - this._undoStack.push(html); - if (clearRedo) { - this._redoStack = []; - } - } - - while (this._undoStack.length > this._maxUndos) { - this._undoStack.shift(); - } - }, - - /** - * Get the editor HTML. - * - * @method _getHTML - * @private - * @return {String} The HTML. - */ - _getHTML: function() { - return this.getCleanHTML(); - }, - - /** - * Get an element on the redo stack. - * - * @method _getRedo - * @private - * @return {String} The HTML to restore, or undefined. - */ - _getRedo: function() { - return this._redoStack.pop(); - }, - - /** - * Get an element on the undo stack. - * - * @method _getUndo - * @private - * @param {String} current The current HTML. - * @return {String} The HTML to restore. - */ - _getUndo: function(current) { - if (this._undoStack.length === 1) { - return this._undoStack[0]; - } - - var last = this._undoStack.pop(); - if (last === current) { - // Oops, the latest undo step is the current content, we should unstack once more. - // There is no need to do that in a loop as the same stack should never contain duplicates. - last = this._undoStack.pop(); - } - - // We always need to keep the first element of the stack. - if (this._undoStack.length === 0) { - this._addToUndo(last); - } - - return last; - }, - - /** - * Restore a value from a stack. - * - * @method _restoreValue - * @private - * @param {String} html The HTML to restore in the editor. - */ - _restoreValue: function(html) { - this.editor.setHTML(html); - // We always add the restored value to the stack, otherwise an event could think that - // the content has changed and clear the redo stack. - this._addToUndo(html); - }, - - /** - * Update the states of the buttons. - * - * @method _updateButtonsStates - * @private - */ - _updateButtonsStates: function() { - if (this._undoStack.length > 1) { - this.enablePlugins('undo'); - } else { - this.disablePlugins('undo'); - } - - if (this._redoStack.length > 0) { - this.enablePlugins('redo'); - } else { - this.disablePlugins('redo'); - } - }, - - /** - * Handle a click on undo - * - * @method _undoHandler - * @param {Event} The click event - * @private - */ - _undoHandler: function(e) { - e.preventDefault(); - var html = this._getHTML(), - undo = this._getUndo(html); - - // Edge case, but that could happen. We do nothing when the content equals the undo step. - if (html === undo) { - this._updateButtonsStates(); - return; - } - - // Restore the value. - this._restoreValue(undo); - - // Add to the redo stack. - this._addToRedo(html); - - // Update the button states. - this._updateButtonsStates(); - }, - - /** - * Handle a click on redo - * - * @method _redoHandler - * @param {Event} The click event - * @private - */ - _redoHandler: function(e) { - e.preventDefault(); - var html = this._getHTML(), - redo = this._getRedo(); - - // Edge case, but that could happen. We do nothing when the content equals the redo step. - if (redo === undefined || html === redo) { - this._updateButtonsStates(); - return; - } - // Restore the value. - this._restoreValue(redo); - - // Update the button states. - this._updateButtonsStates(); - }, - - /** - * If we are significantly different from the last saved version, save a new version. - * - * @method _changeListener - * @param {EventFacade} The click event - * @private - */ - _changeListener: function(e) { - if (e.event && e.event.type.indexOf('key') !== -1) { - // These are the 4 arrow keys. - if ((e.event.keyCode !== 39) && - (e.event.keyCode !== 37) && - (e.event.keyCode !== 40) && - (e.event.keyCode !== 38)) { - // Skip this event type. We only want focus/mouse/arrow events. - return; - } - } - - this._addToUndo(this._getHTML(), true); - this._updateButtonsStates(); - } - -}, { - NS: 'editor_ousupsub', - ATTRS: { - /** - * The unique identifier for the form element representing the editor. - * - * @attribute elementid - * @type String - * @writeOnce - */ - elementid: { - value: null, - writeOnce: true - }, - - /** - * The contextid of the form. - * - * @attribute contextid - * @type Integer - * @writeOnce - */ - contextid: { - value: null, - writeOnce: true - }, - - /** - * Plugins with their configuration. - * - * The plugins structure is: - * - * [ - * { - * "group": "groupName", - * "plugins": [ - * "pluginName": { - * "configKey": "configValue" - * }, - * "pluginName": { - * "configKey": "configValue" - * } - * ] - * }, - * { - * "group": "groupName", - * "plugins": [ - * "pluginName": { - * "configKey": "configValue" - * } - * ] - * } - * ] - * - * @attribute plugins - * @type Object - * @writeOnce - */ - plugins: { - value: {}, - writeOnce: true - } - } -}); - -// The Editor publishes custom events that can be subscribed to. -Y.augment(Editor, Y.EventTarget); - -Y.namespace('M.editor_ousupsub').Editor = Editor; -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * @module moodle-editor_ousupsub-editor - * @submodule clean - */ - -/** - * Functions for the ousupsub editor to clean the generated content. - * - * See {{#crossLink "M.editor_ousupsub.Editor"}}{{/crossLink}} for details. - * - * @namespace M.editor_ousupsub - * @class EditorClean - */ - -function EditorClean() {} - -EditorClean.ATTRS = { -}; - -EditorClean.prototype = { - /** - * Clean the generated HTML content without modifying the editor content. - * - * This includes removes all YUI ids from the generated content. - * - * @return {string} The cleaned HTML content. - */ - getCleanHTML: function() { - // Clone the editor so that we don't actually modify the real content. - var editorClone = this.editor.cloneNode(true), - html, startParagraph = '', endParagraph = ''; - - // Remove all YUI IDs. - Y.each(editorClone.all('[id^="yui"]'), function(node) { - node.removeAttribute('id'); - }); - - editorClone.all('.ousupsub_control').remove(true); - html = editorClone.get('innerHTML'); - - // Revert untouched editor contents to an empty string. - if (html === '' || html === '
') { - return ''; - } - - // Revert untouched editor contents to an empty string. - if (html.indexOf(startParagraph) === 0) { - var length = html.length - (startParagraph.length + endParagraph.length); - html = html.substr(startParagraph.length, length); - } - - // Remove any and all nasties from source. - return this._cleanHTML(html); - }, - - /** - * Clean the HTML content of the editor. - * - * @method cleanEditorHTML - * @chainable - */ - cleanEditorHTML: function() { - this.editor.set('innerHTML', this._cleanHTML(this.editor.get('innerHTML'))); - return this; - }, - - /** - * Clean the HTML content of the editor. - * - * @method cleanEditorHTML - * @chainable - */ - cleanEditorHTMLSimple: function() { - // Using saveSelection as it produces a more consistent experience. - var selection = window.rangy.saveSelection(); - - // Update the content. - this.editor.set('innerHTML', this._cleanHTMLSimple(this.editor.get('innerHTML'))); - - // Restore the selection, and collapse to end. - window.rangy.restoreSelection(selection, true); - return this; - }, - - /** - * Clean the specified HTML content and remove any content which could cause issues. - * - * @method _cleanHTML - * @private - * @param {String} content The content to clean - * @return {String} The cleaned HTML - */ - _cleanHTMLSimple: function(content) { - // Removing limited things that can break the page or a disallowed, like unclosed comments, style blocks, etc. - - var rules = [ - //Remove empty spans, but not ones from Rangy. - {regex: /]*?rangySelectionBoundary[^>]*?)[^>]*>(.+)<\/span>/gi, replace: "$1"} - ]; - - return this._filterContentWithRules(content, rules); - }, - - /** - * Clean the specified HTML content and remove any content which could cause issues. - * - * @method _cleanHTML - * @private - * @param {String} content The content to clean - * @return {String} The cleaned HTML - */ - _cleanHTML: function(content) { - // Removing limited things that can break the page or a disallowed, like unclosed comments, style blocks, etc. - - var rules = [ - //Remove empty paragraphs. - {regex: /]*>( |\s)*<\/p>/gi, replace: ""}, - - //Remove attributes on sup and sub tags. - {regex: /]*( |\s)*>/gi, replace: ""}, - {regex: /]*( |\s)*>/gi, replace: ""}, - - //Replace   with space. - {regex: / /gi, replace: " "}, - - //Combine matching tags with spaces in between. - {regex: /<\/sup>(\s*)+/gi, replace: "$1"}, - {regex: /<\/sub>(\s*)+/gi, replace: "$1"}, - - //Move spaces after start sup and sub tags to before. - {regex: /(\s*)+/gi, replace: "$1"}, - {regex: /(\s*)+/gi, replace: "$1"}, - - //Move spaces before end sup and sub tags to after. - {regex: /(\s*)+<\/sup>/gi, replace: "$1"}, - {regex: /(\s*)+<\/sub>/gi, replace: "$1"}, - - //Remove empty br tags. - {regex: /
/gi, replace: ""}, - - // Remove any style blocks. Some browsers do not work well with them in a contenteditable. - // Plus style blocks are not allowed in body html, except with "scoped", which most browsers don't support as of 2015. - // Reference: "http://stackoverflow.com/questions/1068280/javascript-regex-multiline-flag-doesnt-work" - {regex: /]*>[\s\S]*?<\/style>/gi, replace: ""}, - - // Remove any open HTML comment opens that are not followed by a close. This can completely break page layout. - {regex: /)/gi, replace: ""}, - - // Remove elements that can not contain visible text. - {regex: /]*>[\s\S]*?<\/script>/gi, replace: ""}, - - // Source: "http://www.codinghorror.com/blog/2006/01/cleaning-words-nasty-html.html" - // Remove forbidden tags for content, title, meta, style, st0-9, head, font, html, body, link. - {regex: /<\/?(?:br|title|meta|style|std|font|html|body|link|a|ul|li|ol)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:b|i|u|ul|ol|li|img)[^>]*?>/gi, replace: ""}, - // Source:"https://developer.mozilla.org/en/docs/Web/HTML/Element" - // Remove all elements except sup and sub. - {regex: /<\/?(?:abbr|address|area|article|aside|audio|base|bdi|bdo|blockquote)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:button|canvas|caption|cite|code|col|colgroup|content|data)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:datalist|dd|decorator|del|details|dialog|dfn|div|dl|dt|element)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:h6|header|hgroup|hr|iframe|input|ins|kbd|keygen|label|legend)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:main|map|mark|menu|menuitem|meter|nav|noscript|object|optgroup)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:option|output|p|param|pre|progress|q|rp|rt|rtc|ruby|samp)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:section|select|script|shadow|small|source|std|strong|summary)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:svg|table|tbody|td|template|textarea|time|tfoot|th|thead|tr|track)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:var|wbr|video)[^>]*?>/gi, replace: ""}, - - // Deprecated elements that might still be used by older sites. - {regex: /<\/?(?:acronym|applet|basefont|big|blink|center|dir|frame|frameset|isindex)[^>]*?>/gi, replace: ""}, - {regex: /<\/?(?:listing|noembed|plaintext|spacer|strike|tt|xmp)[^>]*?>/gi, replace: ""}, - - // Elements from common sites including google.com. - {regex: /<\/?(?:jsl|nobr)[^>]*?>/gi, replace: ""}, - - {regex: /]*?rangySelectionBoundary[^>]*?)[^>]*>[\s\S]*?([\s\S]*?)<\/span>/gi, replace: "$1"}, - - // Remove empty spans, but not ones from Rangy. - {regex: /]*?rangySelectionBoundary[^>]*?)[^>]*>( |\s)*<\/span>/gi, replace: ""}, - {regex: /]*?rangySelectionBoundary[^>]*?)[^>]*>[\s\S]*?([\s\S]*?)<\/span>/gi, replace: "$1"}, - - // Remove empty sup and sub tags that appear after pasting text. - {regex: /]*>( |\s)*<\/sup>/gi, replace: ""}, - {regex: /]*>( |\s)*<\/sub>/gi, replace: ""}, - - // Remove special xml namespace tag xmlns generate by browser plugin. - {regex: /(.*?)<\/xmlns.*?>/gi, replace: "$1"} - ]; - - return this._filterContentWithRules(content, rules); - }, - - /** - * Clean the HTML content of the editor by removing empty sup and sub tags. - * - * @method cleanEditorHTMLEmptySupAndSubTags - * @chainable - */ - cleanEditorHTMLEmptySupAndSubTags: function() { - // Using saveSelection as it produces a more consistent experience. - var selection = window.rangy.saveSelection(); - - var newValue = this.editor.get('innerHTML'); - newValue = this._cleanEditorHTMLEmptySupAndSubTags(newValue); - newValue = this._removeUnicodeCharacters(newValue); - // Update the content. - this.editor.set('innerHTML', newValue); - - // Restore the selection, and collapse to end. - window.rangy.restoreSelection(selection, true); - return this; - }, - - /** - * Clean the specified HTML content and remove any content which could cause issues. - * - * @method _cleanHTML - * @private - * @param {String} content The content to clean - * @return {String} The cleaned HTML - */ - _cleanEditorHTMLEmptySupAndSubTags: function(content) { - // Removing limited things that can break the page or a disallowed, like unclosed comments, style blocks, etc. - - var rules = [ - //Remove empty sup tags. - {regex: /]*>(|\s)*<\/su[bp]>/gi, replace: ""} - ]; - - return this._filterContentWithRules(content, rules); - }, - - /** - * Take the supplied content and run on the supplied regex rules. - * - * @method _filterContentWithRules - * @private - * @param {String} content The content to clean - * @param {Array} rules An array of structures: [ {regex: /something/, replace: "something"}, {...}, ...] - * @return {String} The cleaned content - */ - _filterContentWithRules: function(content, rules) { - var i = 0; - for (i = 0; i < rules.length; i++) { - content = content.replace(rules[i].regex, rules[i].replace); - } - return content; - }, - - /** - * Intercept and clean html paste events. - * - * @method pasteCleanup - * @param {Object} sourceEvent The YUI EventFacade object - * @return {Boolean} True if the passed event should continue, false if not. - */ - pasteCleanup: function(sourceEvent) { - // We only expect paste events, but we will check anyways. - if (sourceEvent.type === 'paste') { - // The YUI event wrapper doesn't provide paste event info, so we need the underlying event. - var event = sourceEvent._event; - // Check if we have a valid clipboardData object in the event. - // IE has a clipboard object at window.clipboardData, but as of IE 11, it does not provide HTML content access. - if (event && event.clipboardData && event.clipboardData.getData) { - // Check if there is HTML type to be pasted, this is all we care about. - var types = event.clipboardData.types; - var isHTML = false; - // Different browsers use different things to hold the types, so test various functions. - if (!types) { - isHTML = false; - } else if (typeof types.contains === 'function') { - isHTML = types.contains('text/html'); - } else if (typeof types.indexOf === 'function') { - isHTML = (types.indexOf('text/html') > -1); - if (!isHTML) { - if ((types.indexOf('com.apple.webarchive') > -1) || (types.indexOf('com.apple.iWork.TSPNativeData') > -1)) { - // This is going to be a specialized Apple paste paste. We cannot capture this, so clean everything. - this.fallbackPasteCleanupDelayed(); - return true; - } - } - } else { - // We don't know how to handle the clipboard info, so wait for the clipboard event to finish then fallback. - this.fallbackPasteCleanupDelayed(); - return true; - } - - if (isHTML) { - // Get the clipboard content. - var content; - try { - content = event.clipboardData.getData('text/html'); - } catch (error) { - // Something went wrong. Fallback. - this.fallbackPasteCleanupDelayed(); - return true; - } - - // Stop the original paste. - sourceEvent.preventDefault(); - - // Scrub the paste content. - content = this._cleanPasteHTML(content); - - // Save the current selection. - // Using saveSelection as it produces a more consistent experience. - var selection = window.rangy.saveSelection(); - - // Insert the content. - this.insertContentAtFocusPoint(content); - - // Restore the selection, and collapse to end. - window.rangy.restoreSelection(selection); - window.rangy.getSelection().collapseToEnd(); - - // Update the text area. - this.updateOriginal(); - this._normaliseTextarea(); - return false; - } else { - // Due to poor cross browser clipboard compatibility, the - // failure to find HTML doesn't mean it isn't there. - // Wait for the clipboard event to finish then fallback - // clean the entire editor. - this.fallbackPasteCleanupDelayed(); - return true; - } - } else { - // If we reached a here, this probably means the browser has limited (or no) clipboard support. - // Wait for the clipboard event to finish then fallback. - this.fallbackPasteCleanupDelayed(); - return true; - } - } - - // We should never get here - we must have received a non-paste event for some reason. - // Um, just call updateOriginalDelayed() - it's safe. - this.updateOriginalDelayed(); - return true; - }, - - /** - * Cleanup code after a paste event if we couldn't intercept the paste content. - * - * @method fallbackPasteCleanup - * @chainable - */ - fallbackPasteCleanup: function() { - Y.log('Using fallbackPasteCleanup for ousupsub cleanup', 'debug', LOGNAME); - - // Save the current selection (cursor position). - var selection = window.rangy.saveSelection(); - - // Get, clean, and replace the content in the editable. - var content = this.editor.get('innerHTML'); - this.editor.set('innerHTML', this._cleanPasteHTML(content)); - - // Update the textarea. - this.updateOriginal(); - - // Restore the selection (cursor position). - window.rangy.restoreSelection(selection, true); - - return this; - }, - - /** - * Calls fallbackPasteCleanup on a short timer to allow the paste event handlers to complete. - * - * @method fallbackPasteCleanupDelayed - * @chainable - */ - fallbackPasteCleanupDelayed: function() { - setTimeout(Y.bind(this.fallbackPasteCleanup, this), 0); - - return this; - }, - - /** - * Cleanup html that comes from WYSIWYG paste events. These are more likely to contain messy code that we should strip. - * - * @method _cleanPasteHTML - * @private - * @param {String} content The html content to clean - * @return {String} The cleaned HTML - */ - _cleanPasteHTML: function(content) { - // Return an empty string if passed an invalid or empty object. - if (!content || content.length === 0) { - return ""; - } - - // Rules that get rid of the real-nasties and don't care about normalize code (correct quotes, white spaces, etc). - var rules = [ - // Stuff that is specifically from MS Word and similar office packages. - // Remove all garbage after closing html tag. - {regex: /<\s*\/html\s*>([\s\S]+)$/gi, replace: ""}, - // Remove if comment blocks. - {regex: //gi, replace: ""}, - // Remove start and end fragment comment blocks. - {regex: //gi, replace: ""}, - // Remove any xml blocks. - {regex: /]*>[\s\S]*?<\/xml>/gi, replace: ""}, - // Remove any <\?xml> blocks. - {regex: /<\?xml[^>]*>[\s\S]*?<\\\?xml>/gi, replace: ""}, - // Remove , <\o:blah>. - {regex: /<\/?\w+:[^>]*>/gi, replace: ""} - ]; - - // Apply the first set of harsher rules. - content = this._filterContentWithRules(content, rules); - - // Apply the standard rules, which mainly cleans things like headers, links, and style blocks. - content = this._cleanHTML(content); - - // Check if the string is empty or only contains whitespace. - if (content.length === 0 || !content.match(/\S/)) { - return content; - } - - // Now we let the browser normalize the code by loading it into the DOM and then get the html back. - // This gives us well quoted, well formatted code to continue our work on. Word may provide very poorly formatted code. - var holder = document.createElement('div'); - holder.innerHTML = content; - content = holder.innerHTML; - // Free up the DOM memory. - holder.innerHTML = ""; - - // Run some more rules that care about quotes and whitespace. - rules = [ - // Remove MSO-blah, MSO:blah in style attributes. Only removes one or more that appear in succession. - {regex: /(<[^>]*?style\s*?=\s*?"[^>"]*?)(?:[\s]*MSO[-:][^>;"]*;?)+/gi, replace: "$1"}, - // Remove MSO classes in class attributes. Only removes one or more that appear in succession. - {regex: /(<[^>]*?class\s*?=\s*?"[^>"]*?)(?:[\s]*MSO[_a-zA-Z0-9\-]*)+/gi, replace: "$1"}, - // Remove Apple- classes in class attributes. Only removes one or more that appear in succession. - {regex: /(<[^>]*?class\s*?=\s*?"[^>"]*?)(?:[\s]*Apple-[_a-zA-Z0-9\-]*)+/gi, replace: "$1"}, - // Remove OLE_LINK# anchors that may litter the code. - {regex: /]*?name\s*?=\s*?"OLE_LINK\d*?"[^>]*?>\s*?<\/a>/gi, replace: ""} - ]; - - // Apply the rules. - content = this._filterContentWithRules(content, rules); - - // Reapply the standard cleaner to the content. - content = this._cleanHTML(content); - - return content; - }, - - /** - * Apply the given document.execCommand and tidy up the editor dom afterwards. - * - * @method _applyTextCommand - * @private - * @param int mode (optional) default is button (0), keyboard is 1 - * @return void - */ - _applyTextCommand: function(command, mode) { - var selection, tag; - // Handle keyboard mode. - if (mode) { - tag = this.getCursorTag(); - if (tag === 'superscript' && command === tag || - tag === 'subscript' && command === tag) { - return; // Do nothing. - } else if (tag === 'superscript' && command === 'subscript') { - command = 'superscript'; - } else if (tag === 'subscript' && command === 'superscript') { - command = 'subscript'; - } - - if (!this.pluginEnabled(command)) { - return; - } - } - - // Apply command. - document.execCommand(command, false, null); - - // If nothing is selected add a relevant tag. - selection = window.rangy.getSelection(); - // If it's a collapsed selection the cursor is in the editor but no selection has been made. - if (selection.isCollapsed) { - - // Remove empty sup and sub tags. - this.cleanEditorHTMLEmptySupAndSubTags(); - // Insert tag at cursor focus point. - tag = command === 'superscript' ? 'sup' : 'sub'; - //  is is the Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF). Used - // by TinyMCE to add empty sup/sub tags when nothing is selected. This causes lint - // errors but I couldn't find a better solution. - // http://stackoverflow.com/questions/9691771/why-is-65279-appearing-in-my-html. - var node = this.insertContentAtFocusPoint('<' + tag + '>'); - var range = window.rangy.createRange(); - range.selectNode(node._node.childNodes[0]); - this.setSelection([range]); - // Restore the selection (cursor position). - if (selection.rangeCount) { - selection.collapseToEnd(); - } - } - this._normaliseTextarea(); - this.cleanEditorHTMLSimple(); - - // And mark the text area as updated. - // Save selection after changes to the DOM. If you don't do this here, - // subsequent calls to restoreSelection() will fail expecting the - // previous DOM state. - this.saveSelection(); - this.updateOriginal(); - }, - - /** - * What type of tag surrounds the cursor. - * - * @method _getCursorTag - * @private - * @return string - */ - getCursorTag: function() { - var tag = 'text'; - var selection = window.rangy.getSelection(); - var nodeName = selection.focusNode.nodeName.toLowerCase(); - var parentNodeName = selection.focusNode.parentNode.nodeName.toLowerCase(); - - var childNodeName = ''; - if (selection.focusNode.childNodes && selection.focusNode.childNodes[selection.focusOffset-1]) { - childNodeName = selection.focusNode.childNodes[selection.focusOffset-1].nodeName.toLowerCase(); - } - if (nodeName === 'sup' || parentNodeName === 'sup' || childNodeName === 'sup') { - tag = 'superscript'; - } else if (nodeName === 'sub' || parentNodeName === 'sub' || childNodeName === 'sub') { - tag = 'subscript'; - } - return tag; - }, - - /** - * Get a normalised array of the currently selected nodes. Chrome splits text nodes - * at the end of each selection and also creates empty text nodes. Fix these changes - * and provide a standard array of nodes to match the existing selection to. - * - * @method _normaliseTextarea - * @private - * @return string - */ - _normaliseTextarea: function() { - // Save the current selection (cursor position). - var selection = window.rangy.saveSelection(); - // Remove all the span tags added to the editor textarea by the browser. - // Get the html directly inside the editor

tag and remove span tags from the html inside it. - - var editor_node = this._getEditorNode(); - this._removeSingleNodesByName(editor_node, 'br'); - - // Remove specific tags that can be added through keyboard shortcuts. - var tagsToRemove = ['p', 'b', 'i', 'u', 'ul', 'ol', 'li']; - for (var i = 0; i < tagsToRemove.length; i++) { - this._removeNodesByName(editor_node, tagsToRemove[i]); - } - this._normaliseTagInTextarea('sup'); - this._normaliseTagInTextarea('sub'); - this._removeNodesByName(editor_node, 'span'); - - // Restore the selection (cursor position). - window.rangy.restoreSelection(selection, true); - - // Normalise the editor html. - editor_node.normalize(); - }, - - /** - * Remove all tags nested inside other tags of the same name. No nesting of - * similar tags e.g. is not allowed. - * - * @method _normaliseTagInTextarea - * @private - * @param string name Name of tag to normalise. - * @return string. - */ - _normaliseTagInTextarea: function(name) { - var nodes = [], container = this._getEditorNode(), parentNode, removeParent = false, node; - - // Remove nested nodes. - /* - * Where the node.firstChild == nodes[i+1] since it ignores text elements - * I know it's the first node. Since the two elements match they should cancel - * each other out. Currently we remove only the child sup. We should remove - * both and move their children out. - */ - // Nodelists change as nodes are added and removed. Use an array of nodes instead. - nodes = this._copyArray(container.querySelectorAll(name), nodes); - - for (var i = 0; i < nodes.length; i++) { - node = nodes[i]; - parentNode = node.parentNode; - removeParent = false; - if (parentNode === container ) { - continue; - } - if (parentNode.firstChild === node && parentNode.lastChild === node && - parentNode.nodeName.toLowerCase() === name) { - removeParent = true; - } - if (!removeParent && node && parentNode.nodeName.toLowerCase() === name) { - removeParent = true; - this._splitParentNode(parentNode, name); - } - this._removeNodesByName(node, name); - if (removeParent) { - this._removeNodesByName(parentNode, name); - } - } - - // Combine Sibling nodes. - // Get a new node array and fill with the a fresh nodelist. - nodes = []; - nodes = this._copyArray(container.querySelectorAll(name), nodes); - - for (i = 0; i < nodes.length; i++) { - node = nodes[i]; - if (!node.previousSibling || node.previousSibling.nodeName.toLowerCase() !== name) { - continue; - } - this._mergeNodes(node, node.previousSibling); - } - }, - - /** - * Merge the from and to nodes by moving all elements in from node to the to node. - * Append nodes in order to the to node. - * - * Can't use other dom methods like querySelectorAll because they don't return text elements. - * @method _mergeNodes - * @private - * @return void. - */ - _mergeNodes: function(from, to) { - var nodes = []; - var merge_nodes = from.childNodes; - - // Node lists reduce in size as nodes are removed. Use an array of nodes instead. - for (var i = 0; i < merge_nodes.length; i++) { - nodes.push(merge_nodes.item(i)); - } - - for (i = 0; i < nodes.length; i++) { - to.appendChild(nodes[i]); - } - this._removeNode(from); - }, - - /** - * Split the parent node into two with the node with the given name in the middle. - * - * Can't use other dom methods like querySelectorAll because they don't return text elements. - * @method _splitParentNode - * @private - * @return void. - */ - _splitParentNode: function(container_node, name) { - var nodes = [], node, nodesToAppend = []; - nodes = this._copyArray(container_node.childNodes, nodes); - - var i,j; - for (i = 0; i < nodes.length; i++) { - node = nodes[i]; - nodesToAppend = []; - if (node.nodeName.toLowerCase() === name) { - nodesToAppend = this._copyArray(node.childNodes, nodesToAppend); - } else { - nodesToAppend[0] = document.createElement(name); - nodesToAppend[0].appendChild(node); - } - for (j = 0; j < nodesToAppend.length; j++) { - container_node.parentNode.insertBefore(nodesToAppend[j], container_node); - } - } - }, - - /** - * Copy array values from a dom node list to the given array. - * - * A dom node list reduces as children are removed. Copying to a standard array provides - * an array that doesn't change. - * @method _copyArray - * @private - * @return array. - */ - _copyArray: function(from, to) { - for (var i = 0; i < from.length; i++) { - to.push(from[i]); - } - - return to; - }, - - /** - * Move all elements in container node before the reference node. - * If recursive mode is equired then where childnodes exist that are not - * text nodes. Move their children and remove the existing node. - * - * Can't use other dom methods like querySelectorAll because they don't return text elements. - * @method _removeNodesByName - * @private - * @return void. - */ - _removeNodesByName: function(container_node, name) { - var node, remove_node = container_node.nodeName.toLowerCase() === name; - var nodes = []; - var container_nodes = container_node.childNodes; - - // Don't remove the span used by rangy to save and restore the user selection. - if (container_node.nodeName.toLowerCase() === 'span' && - container_node.id.indexOf('selectionBoundary_') > -1) { - remove_node = false; - } - - nodes = this._copyArray(container_nodes, nodes); - for (var i = 0; i < nodes.length; i++) { - node = nodes[i]; - if (node.childNodes && node.childNodes.length) { - this._removeNodesByName(node, name); - - } - if (remove_node) { - var parentNode = container_node.parentNode; - parentNode.insertBefore(node, container_node); - } - - } - if (remove_node) { - this._removeNode(container_node); - } - }, - - /** - * Recursively remove any tag with the given name. Removes child nodes too. - * - * Can't use other dom methods like querySelectorAll because they don't return text elements. - * @method _removeSingleNodesByName - * @private - * @return void. - */ - _removeSingleNodesByName: function(container_node, name) { - if (!container_node.childNodes) { - return; - } - var node; - var nodes = []; - nodes = this._copyArray(container_node.childNodes, nodes); - for (var i = 0; i < nodes.length; i++) { - node = nodes[i]; - if (node.childNodes && node.childNodes.length) { - this._removeSingleNodesByName(node, name); - } - - if (node.nodeName.toLowerCase() === name) { - this._removeNode(node); - } - } - }, - - /** - * Remove a dom node in a cross browser way. - * - * @method _removeNode - * @private - * @return bool. - */ - _removeNode: function(node) { - if(!node.remove) { - return node.parentNode.removeChild(node); - } - return node.remove(); - }, - - /** - * Get the editor object. - * - * @method _getEditor - * @private - * @return node. - */ - _getEditor: function(host) { - if (!host) { - host = this.get('host'); - } - - return this; - }, - - /** - * Get the node containing the editor html to be updated. - * - * @method _getEditorNode - * @private - * @return node. - */ - _getEditorNode: function(host) { - return this._getEditor(host).editor._node; - }, - - /** - * Remove specific unicode characters from the given string. - * - * @method _removeUnicodeCharacters - * @private - * @return string. - */ - _removeUnicodeCharacters: function(text) { - var values = []; - for ( var i = 0; i < text.length; i++ ) { - if (text.charCodeAt(i) == "65279") { - continue; - } - values.push(text.charAt(i)); - } - return values.join(''); - } -}; - -Y.Base.mix(Y.M.editor_ousupsub.Editor, [EditorClean]); -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * @module moodle-editor_ousupsub-editor - * @submodule selection - */ - -/** - * Selection functions for the ousupsub editor. - * - * See {{#crossLink "M.editor_ousupsub.Editor"}}{{/crossLink}} for details. - * - * @namespace M.editor_ousupsub - * @class EditorSelection - */ - -function EditorSelection() {} - -EditorSelection.ATTRS = { -}; - -EditorSelection.prototype = { - - /** - * List of saved selections per editor instance. - * - * @property _selections - * @private - */ - _selections: null, - - /** - * A unique identifier for the last selection recorded. - * - * @property _lastSelection - * @param lastselection - * @type string - * @private - */ - _lastSelection: null, - - /** - * Whether focus came from a click event. - * - * This is used to determine whether to restore the selection or not. - * - * @property _focusFromClick - * @type Boolean - * @default false - * @private - */ - _focusFromClick: false, - - /** - * Set up the watchers for selection save and restoration. - * - * @method setupSelectionWatchers - * @chainable - */ - setupSelectionWatchers: function() { - // Save the selection when a change was made. - this._registerEventHandle(this.on('ousupsub:selectionchanged', this.saveSelection, this)); - - this._registerEventHandle(this.editor.on('focus', this.restoreSelection, this)); - - // Do not restore selection when focus is from a click event. - this._registerEventHandle(this.editor.on('mousedown', function() { - this._focusFromClick = true; - }, this)); - - // Copy the current value back to the textarea when focus leaves us and save the current selection. - this._registerEventHandle(this.editor.on('blur', function() { - // Clear the _focusFromClick value. - this._focusFromClick = false; - - // Update the original text area. - this.updateOriginal(); - }, this)); - - this._registerEventHandle(this.editor.on(['keyup', 'focus'], function(e) { - setTimeout(Y.bind(this._hasSelectionChanged, this, e), 0); - }, this)); - - // To capture both mouseup and touchend events, we need to track the gesturemoveend event in standAlone mode. Without - // standAlone, it will only fire if we listened to a gesturemovestart too. - this._registerEventHandle(this.editor.on('gesturemoveend', function(e) { - setTimeout(Y.bind(this._hasSelectionChanged, this, e), 0); - }, { - standAlone: true - }, this)); - - return this; - }, - - /** - * Work out if the cursor is in the editable area for this editor instance. - * - * @method isActive - * @return {boolean} - */ - isActive: function() { - var range = window.rangy.createRange(), - selection = window.rangy.getSelection(); - - if (!selection.rangeCount) { - // If there was no range count, then there is no selection. - return false; - } - - // We can't be active if the editor doesn't have focus at the moment. - if (!document.activeElement || - !(this.editor.compareTo(document.activeElement) || this.editor.contains(document.activeElement))) { - return false; - } - - // Check whether the range intersects the editor selection. - range.selectNode(this.editor.getDOMNode()); - return range.intersectsRange(selection.getRangeAt(0)); - }, - - /** - * Create a cross browser selection object that represents a YUI node. - * - * @method getSelectionFromNode - * @param {Node} YUI Node to base the selection upon. - * @return {[rangy.Range]} - */ - getSelectionFromNode: function(node) { - var range = window.rangy.createRange(); - range.selectNode(node.getDOMNode()); - return [range]; - }, - - /** - * Save the current selection to an internal property. - * - * This allows more reliable return focus, helping improve keyboard navigation. - * - * Should be used in combination with {{#crossLink "M.editor_ousupsub.EditorSelection/restoreSelection"}}{{/crossLink}}. - * - * @method saveSelection - */ - saveSelection: function() { - if (this.isActive()) { - this._selections = this.getSelection(); - } - }, - - /** - * Restore any stored selection when the editor gets focus again. - * - * Should be used in combination with {{#crossLink "M.editor_ousupsub.EditorSelection/saveSelection"}}{{/crossLink}}. - * - * @method restoreSelection - */ - restoreSelection: function() { - if (!this._focusFromClick) { - if (this._selections) { - this.setSelection(this._selections); - } - } - this._focusFromClick = false; - }, - - /** - * Get the selection object that can be passed back to setSelection. - * - * @method getSelection - * @return {array} An array of rangy ranges. - */ - getSelection: function() { - return window.rangy.getSelection().getAllRanges(); - }, - - /** - * Check that a YUI node it at least partly contained by the current selection. - * - * @method selectionContainsNode - * @param {Node} The node to check. - * @return {boolean} - */ - selectionContainsNode: function(node) { - return window.rangy.getSelection().containsNode(node.getDOMNode(), true); - }, - - /** - * Runs a filter on each node in the selection, and report whether the - * supplied selector(s) were found in the supplied Nodes. - * - * By default, all specified nodes must match the selection, but this - * can be controlled with the requireall property. - * - * @method selectionFilterMatches - * @param {String} selector - * @param {NodeList} [selectednodes] For performance this should be passed. If not passed, this will be looked up each time. - * @param {Boolean} [requireall=true] Used to specify that "any" match is good enough. - * @return {Boolean} - */ - selectionFilterMatches: function(selector, selectednodes, requireall) { - if (typeof requireall === 'undefined') { - requireall = true; - } - if (!selectednodes) { - // Find this because it was not passed as a param. - selectednodes = this.getSelectedNodes(); - } - var allmatch = selectednodes.size() > 0, - anymatch = false; - - var editor = this.editor, - stopFn = function(node) { - // The function getSelectedNodes only returns nodes within the editor, so this test is safe. - return node === editor; - }; - - // If we do not find at least one match in the editor, no point trying to find them in the selection. - if (!editor.one(selector)) { - return false; - } - - selectednodes.each(function(node){ - // Check each node, if it doesn't match the tags AND is not within the specified tags then fail this thing. - if (requireall) { - // Check for at least one failure. - if (!allmatch || !node.ancestor(selector, true, stopFn)) { - allmatch = false; - } - } else { - // Check for at least one match. - if (!anymatch && node.ancestor(selector, true, stopFn)) { - anymatch = true; - } - } - }, this); - if (requireall) { - return allmatch; - } else { - return anymatch; - } - }, - - /** - * Get the deepest possible list of nodes in the current selection. - * - * @method getSelectedNodes - * @return {NodeList} - */ - getSelectedNodes: function() { - var results = new Y.NodeList(), - nodes, - selection, - range, - node, - i; - - selection = window.rangy.getSelection(); - - if (selection.rangeCount) { - range = selection.getRangeAt(0); - } else { - // Empty range. - range = window.rangy.createRange(); - } - - if (range.collapsed) { - // We do not want to select all the nodes in the editor if we managed to - // have a collapsed selection directly in the editor. - // It's also possible for the commonAncestorContainer to be the document, which selectNode does not handle - // so we must filter that out here too. - if (range.commonAncestorContainer !== this.editor.getDOMNode() - && range.commonAncestorContainer !== Y.config.doc) { - range = range.cloneRange(); - range.selectNode(range.commonAncestorContainer); - } - } - - nodes = range.getNodes(); - - for (i = 0; i < nodes.length; i++) { - node = Y.one(nodes[i]); - if (this.editor.contains(node)) { - results.push(node); - } - } - return results; - }, - - /** - * Check whether the current selection has changed since this method was last called. - * - * If the selection has changed, the ousupsub:selectionchanged event is also fired. - * - * @method _hasSelectionChanged - * @private - * @param {EventFacade} e - * @return {Boolean} - */ - _hasSelectionChanged: function(e) { - var selection = window.rangy.getSelection(), - range, - changed = false; - - if (selection.rangeCount) { - range = selection.getRangeAt(0); - } else { - // Empty range. - range = window.rangy.createRange(); - } - - if (this._lastSelection) { - if (!this._lastSelection.equals(range)) { - changed = true; - return this._fireSelectionChanged(e); - } - } - this._lastSelection = range; - return changed; - }, - - /** - * Fires the ousupsub:selectionchanged event. - * - * When the selectionchanged event is fired, the following arguments are provided: - * - event : the original event that lead to this event being fired. - * - selectednodes : an array containing nodes that are entirely selected of contain partially selected content. - * - * @method _fireSelectionChanged - * @private - * @param {EventFacade} e - */ - _fireSelectionChanged: function(e) { - this.fire('ousupsub:selectionchanged', { - event: e, - selectedNodes: this.getSelectedNodes() - }); - }, - - /** - * Get the DOM node representing the common anscestor of the selection nodes. - * - * @method getSelectionParentNode - * @return {Element|boolean} The DOM Node for this parent, or false if no seletion was made. - */ - getSelectionParentNode: function() { - var selection = window.rangy.getSelection(); - if (selection.rangeCount) { - return selection.getRangeAt(0).commonAncestorContainer; - } - return false; - }, - - /** - * Set the current selection. Used to restore a selection. - * - * @method selection - * @param {array} ranges A list of rangy.range objects in the selection. - */ - setSelection: function(ranges) { - var selection = window.rangy.getSelection(); - selection.setRanges(ranges); - }, - - /** - * Inserts the given HTML into the editable content at the currently focused point. - * - * @method insertContentAtFocusPoint - * @param {String} html - * @return {Node} The YUI Node object added to the DOM. - */ - insertContentAtFocusPoint: function(html) { - var selection = window.rangy.getSelection(), - range, - node = Y.Node.create(html); - if (selection.rangeCount) { - range = selection.getRangeAt(0); - } - if (range) { - range.deleteContents(); - range.insertNode(node.getDOMNode()); - } - return node; - } - -}; - -Y.Base.mix(Y.M.editor_ousupsub.Editor, [EditorSelection]); -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * ousupsub editor plugin. - * - * @module moodle-editor_ousupsub-editor - * @submodule plugin-base - * @package editor_ousupsub - * @copyright 2014 Andrew Nicols - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * A Plugin for the ousupsub Editor used in Moodle. - * - * This class should not be directly instantiated, and all Editor plugins - * should extend this class. - * - * @namespace M.editor_ousupsub - * @class EditorPlugin - * @main - * @constructor - * @uses M.editor_ousupsub.EditorPluginButtons - */ - -function EditorPlugin() { - EditorPlugin.superclass.constructor.apply(this, arguments); -} - -var DISABLED = 'disabled', - HIGHLIGHT = 'highlight', - GROUPSELECTOR = '.ousupsub_group.', - GROUP = '_group'; - -Y.extend(EditorPlugin, Y.Base, { - /** - * The name of the current plugin. - * - * @property name - * @type string - */ - name: null, - - /** - * The name of the command to execute when the button is clicked. - * - * @property exec - * @type string - */ - exec: null, - - /** - * A Node reference to the editor. - * - * @property editor - * @type Node - */ - editor: null, - - /** - * A Node reference to the editor toolbar. - * - * @property toolbar - * @type Node - */ - toolbar: null, - - /** - * Event Handles to clear on plugin destruction. - * - * @property _eventHandles - * @private - */ - _eventHandles: null, - - /** - * All of the buttons that belong to this plugin instance. - * - * Buttons are stored by button name. - * - * @property buttons - * @type object - */ - buttons: null, - - /** - * A list of each of the button names. - * - * @property buttonNames - * @type array - */ - buttonNames: null, - - /** - * A read-only view of the current state for each button. Mappings are stored by name. - * - * Possible states are: - *

    - *
  • {{#crossLink "M.editor_ousupsub.EditorPluginButtons/ENABLED:property"}}{{/crossLink}}; and
  • - *
  • {{#crossLink "M.editor_ousupsub.EditorPluginButtons/DISABLED:property"}}{{/crossLink}}.
  • - *
- * - * @property buttonStates - * @type object - */ - buttonStates: null, - - /** - * The state for a disabled button. - * - * @property DISABLED - * @type Number - * @static - * @value 0 - */ - DISABLED: 0, - - /** - * The state for an enabled button. - * - * @property ENABLED - * @type Number - * @static - * @value 1 - */ - ENABLED: 1, - - /** - * The list of Event Handlers for buttons. - * - * @property _buttonHandlers - * @protected - * @type array - */ - _buttonHandlers: null, - - /** - * A textual description of the primary keyboard shortcut for this - * plugin. - * - * This will be null if no keyboard shortcut has been registered. - * - * @property _primaryKeyboardShortcut - * @protected - * @type String - * @default null - */ - _primaryKeyboardShortcut: null, - - /** - * An list of handles returned by setTimeout(). - * - * The keys will be the buttonName of the button, and the value the handles. - * - * @property _highlightQueue - * @protected - * @type Object - * @default null - */ - _highlightQueue: null, - - initializer: function(config) { - // Set the references to configuration parameters. - this.name = config.name; - this.exec = config.exec; - this.toolbar = config.toolbar; - this.editor = config.editor; - - // Set up the prototypal properties. - // These must be set up here because prototypal arrays and objects are copied across instances. - this.buttons = {}; - this.buttonNames = []; - this.buttonStates = {}; - this._primaryKeyboardShortcut = []; - this._buttonHandlers = []; - this._menuHideHandlers = []; - this._highlightQueue = {}; - this._eventHandles = []; - this.addButton(config); - }, - - destructor: function() { - // Detach all EventHandles. - new Y.EventHandle(this._eventHandles).detach(); - }, - - /** - * Mark the content ediable content as having been changed. - * - * This is a convenience function and passes through to - * {{#crossLink "M.editor_ousupsub.EditorTextArea/updateOriginal"}}updateOriginal{{/crossLink}}. - * - * @method markUpdated - */ - markUpdated: function() { - // Save selection after changes to the DOM. If you don't do this here, - // subsequent calls to restoreSelection() will fail expecting the - // previous DOM state. - this.get('host').saveSelection(); - - return this.get('host').updateOriginal(); - }, - - /** - * Register an event handle for disposal in the destructor. - * - * @method registerEventHandle - * @param {EventHandle} The Event Handle as returned by Y.on, and Y.delegate. - */ - registerEventHandle: function(handle) { - this._eventHandles.push(handle); - }, - - /** - * Add a button for this plugin to the toolbar. - * - * @method addButton - * @param {object} config The configuration for this button - * @param {string} [config.iconurl] The URL for the icon. If not specified, then the icon and component will be used instead. - * @param {string} [config.icon] The icon identifier. - * @param {string} [config.iconComponent='core'] The icon component. - * @param {string} [config.keys] The shortcut key that can call this plugin from the keyboard. - * @param {string} [config.keyDescription] An optional description for the keyboard shortcuts. - * If not specified, this is automatically generated based on config.keys. - * If multiple key bindings are supplied to config.keys, then only the first is used. - * If set to false, then no description is added to the title. - * @param {string} [config.tags] The tags that trigger this button to be highlighted. - * @param {boolean} [config.tagMatchRequiresAll=true] Working in combination with the tags parameter, when true - * every tag of the selection has to match. When false, only one match is needed. Only set this to false when - * necessary as it is much less efficient. - * See {{#crossLink "M.editor_ousupsub.EditorSelection/selectionFilterMatches:method"}}{{/crossLink}} for more information. - * @param {string} [config.title=this.name] The string identifier in the plugin's language file. - * @param {string} [config.buttonName=this.name] The name of the button. This is used in the buttons object, and if - * specified, in the class for the button. - * @param {function} config.callback A callback function to call when the button is clicked. - * @param {object} [config.callbackArgs] Any arguments to pass to the callback. - * @return {Node} The Node representing the newly created button. - */ - addButton: function(config) { - var group = this.get('group'), - pluginname = this.name, - buttonClass = 'ousupsub_' + pluginname + '_button', - button, - host = this.get('host'); - - if (config.exec) { - buttonClass = buttonClass + '_' + config.exec; - } - - if (!config.buttonName) { - // Set a default button name - this is used as an identifier in the button object. - config.buttonName = config.exec || pluginname; - } else { - buttonClass = buttonClass + '_' + config.buttonName; - } - config.buttonClass = buttonClass; - - // Normalize icon configuration. - config = this._normalizeIcon(config); - - if (!config.title) { - config.title = 'pluginname'; - } - var title = M.util.get_string(pluginname, 'editor_ousupsub'); - - // Create the actual button. - var icon = ''; - if (config.iconurl) { - icon = ''; - } - button = Y.Node.create(''); - button.setAttribute('title', title); - - // Append it to the group. - group.append(button); - - var currentfocus = this.toolbar.getAttribute('aria-activedescendant'); - if (!currentfocus) { - // Initially set the first button in the toolbar to be the default on keyboard focus. - // Initially set the first button in the toolbar to be the default on keyboard focus. - button.setAttribute('tabindex', '0'); - this.toolbar.setAttribute('aria-activedescendant', button.generateID()); - this.get('host')._tabFocus = button; - } - // Normalize the callback parameters. - if (!config.callback) { - config.callback = this._applyTextCommand; - } - config.callback = Y.rbind(this._callbackWrapper, this, config.callback); - - // Add the standard click handler to the button. - this._buttonHandlers.push( - this.toolbar.delegate('click', config.callback, '.' + buttonClass, this) - ); - - // Handle button click via shortcut key. - if (config.keys) { - if (typeof config.keyDescription !== 'undefined') { - // A keyboard shortcut description was specified - use it. - this._primaryKeyboardShortcut[buttonClass] = config.keyDescription; - } - this._addKeyboardListener(config.callback, config.keys, buttonClass); - - if (this._primaryKeyboardShortcut[buttonClass]) { - // If we have a valid keyboard shortcut description, then set it with the title. - button.setAttribute('title', M.util.get_string('plugin_title_shortcut', 'editor_ousupsub', { - title: title, - shortcut: this._primaryKeyboardShortcut[buttonClass] - })); - } - } - - // Handle highlighting of the button. - if (config.tags) { - var tagMatchRequiresAll = true; - if (typeof config.tagMatchRequiresAll === 'boolean') { - tagMatchRequiresAll = config.tagMatchRequiresAll; - } - this._buttonHandlers.push( - host.on(['ousupsub:selectionchanged', 'change'], function(e) { - if (typeof this._highlightQueue[config.buttonName] !== 'undefined') { - clearTimeout(this._highlightQueue[config.buttonName]); - } - // Async the highlighting. - this._highlightQueue[config.buttonName] = setTimeout(Y.bind(function(e) { - if (host.selectionFilterMatches(config.tags, e.selectedNodes, tagMatchRequiresAll)) { - this.highlightButtons(config.buttonName); - } else { - this.unHighlightButtons(config.buttonName); - } - }, this, e), 0); - }, this) - ); - } - - // Add the button reference to the buttons array for later reference. - this.buttonNames.push(config.buttonName); - this.buttons[config.buttonName] = button; - this.buttonStates[config.buttonName] = this.ENABLED; - return button; - }, - - /** - * Normalize and sanitize the configuration variables relating to callbacks. - * - * @method _normalizeCallback - * @param {object} config - * @param {function} config.callback A callback function to call when the button is clicked. - * @param {object} [config.callbackArgs] Any arguments to pass to the callback. - * @param {object} [inheritFrom] A parent configuration that this configuration may inherit from. - * @return {object} The normalized configuration - * @private - */ - _normalizeCallback: function(config, inheritFrom) { - if (config._callbackNormalized) { - // Return early if the callback has already been normalized. - return config; - } - - if (!inheritFrom) { - // Create an empty inheritFrom to make life easier below. - inheritFrom = {}; - } - - // We wrap the callback in function to prevent the default action, check whether the editor is - // active and focus it, and then mark the field as updated. - config._callback = config.callback || inheritFrom.callback; - config.callback = Y.rbind(this._callbackWrapper, this, this._applyTextCommand, config.callbackArgs); - - config._callbackNormalized = true; - - return config; - }, - - /** - * Normalize and sanitize the configuration variables relating to icons. - * - * @method _normalizeIcon - * @param {object} config - * @param {string} [config.iconurl] The URL for the icon. If not specified, then the icon and component will be used instead. - * @param {string} [config.icon] The icon identifier. - * @param {string} [config.iconComponent='core'] The icon component. - * @return {object} The normalized configuration - * @private - */ - _normalizeIcon: function(config) { - if (config.icon && !config.iconurl) { - // The default icon component. - if (!config.iconComponent) { - config.iconComponent = 'core'; - } - config.iconurl = M.util.image_url(config.icon, config.iconComponent); - } - - return config; - }, - - /** - * A wrapper in which to run the callbacks. - * - * This handles common functionality such as: - *
    - *
  • preventing the default action; and
  • - *
  • focusing the editor if relevant.
  • - *
- * - * @method _callbackWrapper - * @param {EventFacade} e - * @param {Function} callback The function to call which makes the relevant changes. - * @param {Array} [callbackArgs] The arguments passed to this callback. - * @return {Mixed} The value returned by the callback. - * @private - */ - _callbackWrapper: function(e, callback, callbackArgs) { - e.preventDefault(); - - if (!this.isEnabled()) { - // Exit early if the plugin is disabled. - return; - } - - var creatorButton = e.currentTarget.ancestor('button', true); - - if (creatorButton && creatorButton.hasAttribute(DISABLED)) { - // Exit early if the clicked button was disabled. - return; - } - - if (!(YUI.Env.UA.android || this.get('host').isActive())) { - // We must not focus for Android here, even if the editor is not active because the keyboard auto-completion - // changes the cursor position. - // If we save that change, then when we restore the change later we get put in the wrong place. - // Android is fine to save the selection without the editor being in focus. - this.get('host').focus(); - } - - // Save the selection. - this.get('host').saveSelection(); - - // Build the arguments list, but remove the callback we're calling. - var args = [e, callbackArgs]; - - // Restore selection before making changes. - this.get('host').restoreSelection(); - - // Actually call the callback now. - return callback.apply(this, args); - }, - - /** - * Add a keyboard listener to call the callback. - * - * The keyConfig will take either an array of keyConfigurations, in - * which case _addKeyboardListener is called multiple times; an object - * containing an optional eventtype, optional container, and a set of - * keyCodes, or just a string containing the keyCodes. When keyConfig is - * not an object, it is wrapped around a function that ensures that - * only the expected key modifiers were used. For instance, it checks - * that space+ctrl is not triggered when the user presses ctrl+shift+space. - * When using an object, the developer should check that manually. - * - * @method _addKeyboardListener - * @param {function} callback - * @param {array|object|string} keyConfig - * @param {string} [keyConfig.eventtype=key] The type of event - * @param {string} [keyConfig.container=.editor_ousupsub_content] The containing element. - * @param {string} keyConfig.keyCodes The keycodes to user for the event. - * @private - * - */ - _addKeyboardListener: function(callback, keyConfig, buttonName) { - var eventtype = 'key', - container = CSS.EDITORWRAPPER, - keys, - handler, - modifier; - - if (Y.Lang.isArray(keyConfig)) { - // If an Array was specified, call the add function for each element. - Y.Array.each(keyConfig, function(config) { - this._addKeyboardListener(callback, config, buttonName); - }, this); - - return this; - - } else if (typeof keyConfig === "object") { - if (keyConfig.eventtype) { - eventtype = keyConfig.eventtype; - } - - if (keyConfig.container) { - container = keyConfig.container; - } - - // Must be specified. - keys = keyConfig.keyCodes; - handler = callback; - - } else { - modifier = ''; - keys = keyConfig; - if (typeof this._primaryKeyboardShortcut[buttonName] === 'undefined') { - this._primaryKeyboardShortcut[buttonName] = this._getDefaultMetaKeyDescription(keyConfig); - } - // Wrap the callback into a handler to check if it uses the specified modifiers, not more. - handler = Y.bind(function(modifiers, e) { - callback.apply(this, [e]); - }, this, [modifier]); - } - - this._buttonHandlers.push( - this.editor.delegate( - eventtype, - handler, - keys, - container, - this - ) - ); - - }, - - /** - * Checks if a key event was strictly defined for the modifiers passed. - * - * @method _eventUsesExactKeyModifiers - * @param {Array} modifiers List of key modifiers to check for (alt, ctrl, meta or shift). - * @param {EventFacade} e The event facade. - * @return {Boolean} True if the event was stricly using the modifiers specified. - */ - _eventUsesExactKeyModifiers: function(modifiers, e) { - var exactMatch = true, - hasKey; - - if (e.type !== 'key') { - return false; - } - - hasKey = Y.Array.indexOf(modifiers, 'alt') > -1; - exactMatch = exactMatch && ((e.altKey && hasKey) || (!e.altKey && !hasKey)); - hasKey = Y.Array.indexOf(modifiers, 'ctrl') > -1; - exactMatch = exactMatch && ((e.ctrlKey && hasKey) || (!e.ctrlKey && !hasKey)); - hasKey = Y.Array.indexOf(modifiers, 'meta') > -1; - exactMatch = exactMatch && ((e.metaKey && hasKey) || (!e.metaKey && !hasKey)); - hasKey = Y.Array.indexOf(modifiers, 'shift') > -1; - exactMatch = exactMatch && ((e.shiftKey && hasKey) || (!e.shiftKey && !hasKey)); - - return exactMatch; - }, - - /** - * Determine if this plugin is enabled, based upon the state of it's buttons. - * - * @method isEnabled - * @return {boolean} - */ - isEnabled: function() { - // The first instance of an undisabled button will make this return true. - var found = Y.Object.some(this.buttonStates, function(button) { - return (button === this.ENABLED); - }, this); - - return found; - }, - - /** - * Enable one button, or all buttons relating to this Plugin. - * - * If no button is specified, all buttons are disabled. - * - * @method disableButtons - * @param {String} [button] The name of a specific plugin to enable. - * @chainable - */ - disableButtons: function(button) { - return this._setButtonState(false, button); - }, - - /** - * Enable one button, or all buttons relating to this Plugin. - * - * If no button is specified, all buttons are enabled. - * - * @method enableButtons - * @param {String} [button] The name of a specific plugin to enable. - * @chainable - */ - enableButtons: function(button) { - return this._setButtonState(true, button); - }, - - /** - * Set the button state for one button, or all buttons associated with this plugin. - * - * @method _setButtonState - * @param {Boolean} enable Whether to enable this button. - * @param {String} [button] The name of a specific plugin to set state for. - * @chainable - * @private - */ - _setButtonState: function(enable, button) { - var attributeChange = 'setAttribute'; - if (enable) { - attributeChange = 'removeAttribute'; - } - if (button) { - if (this.buttons[button]) { - this.buttons[button][attributeChange](DISABLED, DISABLED); - this.buttonStates[button] = enable ? this.ENABLED : this.DISABLED; - } - } else { - Y.Array.each(this.buttonNames, function(button) { - this.buttons[button][attributeChange](DISABLED, DISABLED); - this.buttonStates[button] = enable ? this.ENABLED : this.DISABLED; - }, this); - } - - this.get('host').checkTabFocus(); - return this; - }, - - /** - * Highlight a button, or buttons in the toolbar. - * - * If no button is specified, all buttons are highlighted. - * - * @method highlightButtons - * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. - * @chainable - */ - highlightButtons: function(button) { - return this._changeButtonHighlight(true, button); - }, - - /** - * Un-highlight a button, or buttons in the toolbar. - * - * If no button is specified, all buttons are un-highlighted. - * - * @method unHighlightButtons - * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. - * @chainable - */ - unHighlightButtons: function(button) { - return this._changeButtonHighlight(false, button); - }, - - /** - * Highlight a button, or buttons in the toolbar. - * - * @method _changeButtonHighlight - * @param {boolean} highlight true - * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. - * @protected - * @chainable - */ - _changeButtonHighlight: function(highlight, button) { - var method = 'addClass'; - - if (!highlight) { - method = 'removeClass'; - } - if (button) { - if (this.buttons[button]) { - this.buttons[button][method](HIGHLIGHT); - } - } else { - Y.Object.each(this.buttons, function(button) { - button[method](HIGHLIGHT); - }, this); - } - - return this; - }, - - /** - * Get the default meta key to use with keyboard events. - * - * On a Mac, this will be the 'meta' key for Command; otherwise it will - * be the Control key. - * - * @method _getDefaultMetaKey - * @return {string} - * @private - */ - _getDefaultMetaKey: function() { - if (Y.UA.os === 'macintosh') { - return 'meta'; - } else { - return 'ctrl'; - } - }, - - /** - * Get the user-visible description of the meta key to use with keyboard events. - * - * On a Mac, this will be 'Command' ; otherwise it will be 'Control'. - * - * @method _getDefaultMetaKeyDescription - * @return {string} - * @private - */ - _getDefaultMetaKeyDescription: function(keyCode) { - if (Y.UA.os === 'macintosh') { - return M.util.get_string('editor_command_keycode', 'editor_ousupsub', String.fromCharCode(keyCode).toLowerCase()); - } else { - return M.util.get_string('editor_control_keycode', 'editor_ousupsub', String.fromCharCode(keyCode).toLowerCase()); - } - }, - - /** - * Get the standard key event to use for keyboard events. - * - * @method _getKeyEvent - * @return {string} - * @private - */ - _getKeyEvent: function() { - return 'down:'; - }, - - /** - * Apply the given document.execCommand and tidy up the editor dom afterwards. - * - * @method _applyTextCommand - * @private - * @return void - */ - _applyTextCommand: function(e) { - var mode = 0; - - if(e && e.type === 'key') { - // handled by this._getEditor().textareaKeyboardNavigation(e); - return; - } - - this._getEditor()._applyTextCommand(this.exec, mode); - }, - - /** - * Get the editor object. - * - * @method _getEditor - * @private - * @return node. - */ - _getEditor: function(host) { - if (!host) { - host = this.get('host'); - } - - return host; - }, - - /** - * Get the node containing the editor html to be updated. - * - * @method _getEditorNode - * @private - * @return node. - */ - _getEditorNode: function(host) { - return this._getEditor(host).editor._node; - } - -}, { - NAME: 'editorPlugin', - ATTRS: { - /** - * The editor instance that this plugin was instantiated by. - * - * @attribute host - * @type M.editor_ousupsub.Editor - * @writeOnce - */ - host: { - writeOnce: true - }, - - /** - * The toolbar group that this button belongs to. - * - * When setting, the name of the group should be specified. - * - * When retrieving, the Node for the toolbar group is returned. If - * the group doesn't exist yet, then it is created first. - * - * @attribute group - * @type Node - * @writeOnce - */ - group: { - writeOnce: true, - getter: function(groupName) { - var group = this.toolbar.one(GROUPSELECTOR + groupName + GROUP); - if (!group) { - group = Y.Node.create('
'); - this.toolbar.append(group); - } - - return group; - } - } - } -}); - -Y.namespace('M.editor_ousupsub').EditorPlugin = EditorPlugin; -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * The manager for the OUSupSub Editor. - * - * @module moodle-editor_ousupsub-editor - * @submodule manager - * @package editor_ousupsub - * @copyright 2014 Andrew Nicols - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @main moodle-editor_ousupsub-editor - */ - -/** - * @module moodle-editor_ousupsub-editor - */ - -/** - * The manager for the OUSupSub editor. - * - * @class editor_ousupsub - */ - -Y.M.editor_ousupsub = Y.M.editor_ousupsub || {}; - -/** - * List of editor_ousupsub instances. Intentionally placed on window.M, not - * something in the namespace, so we can be sure it is really global. - */ -M = M || {}; -M.editor_ousupsub = M.editor_ousupsub || {}; -M.editor_ousupsub._instances = M.editor_ousupsub._instances || {}; - -/** - * Add a reference to an editor. - * Note: This is an internal method which should only be called by the editor itself. - * - * @method addEditorReference - * @param {String} name The name of the editor instance to add - * @private - */ -Y.M.editor_ousupsub.addEditorReference = function(name, reference) { - Y.log("Registering a new ousupsub editor: " + name, 'debug', LOGNAME); - if (typeof M.editor_ousupsub._instances[name] === 'undefined') { - M.editor_ousupsub._instances[name] = reference; - } else { - Y.log("An ousupsub editor with the name '" + name + "' already exists. Unable to add.", 'warn', LOGNAME); - } - - return Y.M.editor_ousupsub; -}; - -/** - * Create a new editor using simple options. - * - * @method createEditor - * @param {String} id of the textarea to turn into an editor. - * @param {String} type 'superscript', 'subscript' or 'both'. - * @return {M.editor_ousupsub.Editor} The newly created editor instance - */ -Y.M.editor_ousupsub.createEditorSimple = function(id, type) { - var plugins = []; - if (type === 'both' || type === 'superscript') { - plugins.push({"name": "superscript", "params": []}); - } - if (type === 'both' || type === 'subscript') { - plugins.push({"name": "subscript", "params": []}); - } - - Y.M.editor_ousupsub.createEditor( - {"elementid" : id, "content_css" : "", "contextid" : 0, "language" : "en", - "directionality" : "ltr", "plugins" : [{"group" : "style1", "plugins" : plugins}],"pageHash" : ""}); -}; - -/** - * Create a new editor using the specified configuration. - * - * @method createEditor - * @param {Object} config See the attributes for {{#crossLink - * "M.editor_ousupsub.Editor"}}{{/crossLink}} for configuration options. The - * elementid provided will be used as the name of this editor within - * the editor Manager. - * @return {M.editor_ousupsub.Editor} The newly created editor instance - */ -Y.M.editor_ousupsub.createEditor = function(config) { - - var instance = new Y.M.editor_ousupsub.Editor(config); - Y.M.editor_ousupsub.fire('editor_ousupsub:created', { - id: instance.get('elementid'), - instance: instance - }); - return instance; -}; - -/** - * Get the requested Editor instance. - * - * @method getEditor - * @param {String} name The name of the editor instance to retrieve - * @return {M.editor_ousupsub.Editor} The requested editor instance - */ -Y.M.editor_ousupsub.getEditor = function(name) { - return M.editor_ousupsub._instances[name]; -}; - -/** - * Remove the reference for an editor. - * - * @method removeEditorReference - * @param {String} name The name of the editor instance to remove - */ -Y.M.editor_ousupsub.removeEditor = function(name) { - var instance = Y.M.editor_ousupsub.getEditor(name); - if (instance) { - instance.destroy(); - this.fire('editor_ousupsub:removed', { - id: name - }); - } - return Y.M.editor_ousupsub; -}; - -/** - * Remove the reference for an editor. - * Note: This is an internal method which should only be called by the editor itself. - * - * @method removeEditorReference - * @param {String} name The name of the editor instance to remove - * @private - */ -Y.M.editor_ousupsub.removeEditorReference = function(name) { - if (Y.M.editor_ousupsub.getEditor(name)) { - delete M.editor_ousupsub._instances[name]; - } -}; - -/** - * Add the supplied function to the manager using the specified name. - * - * @method addMethod - * @param {String} name The name to store the method on within the editor manager. - * @param {Function} fn The function to be added. - * @param {Object} [context] The context to apply the function with. If not specified, the Editor itself is used. - */ -Y.M.editor_ousupsub.addMethod = function(name, fn) { - if (typeof this[name] !== 'undefined') { - Y.log('Overwriting existing method: ' + name, 'warn', LOGNAME); - } - - Y.M.editor_ousupsub[name] = function() { - var ret = [], - args = arguments; - - Y.Object.each(M.editor_ousupsub._instances, function(editor) { - var result = fn.apply(editor, args); - - if (result !== undefined && result !== editor) { - ret[ret.length] = result; - } - }); - - // If we received a set of results, return them, otherwise make this method chainable. - return ret.length ? ret : this; - }; -}; - -Y.augment(Y.M.editor_ousupsub, Y.EventTarget); - -Y.Array.each(['saveSelection', 'updateFromTextArea', 'updateOriginal', 'cleanEditorHTML', 'destroy'], function(name) { - Y.M.editor_ousupsub.addMethod(name, Y.M.editor_ousupsub.Editor.prototype[name]); -}); - - -}, '@VERSION@', {"requires": ["base", "node", "event", "event-custom", "moodle-editor_ousupsub-rangy"]}); diff --git a/yui/build/moodle-editor_ousupsub-editor/moodle-editor_ousupsub-editor-min.js b/yui/build/moodle-editor_ousupsub-editor/moodle-editor_ousupsub-editor-min.js deleted file mode 100644 index 2671a85..0000000 --- a/yui/build/moodle-editor_ousupsub-editor/moodle-editor_ousupsub-editor-min.js +++ /dev/null @@ -1,6 +0,0 @@ -YUI.add("moodle-editor_ousupsub-editor",function(a,d){var n,s,i,e="editor_ousupsub_content",c="editor_ousupsub_content_wrap",h=".editor_ousupsub_content",t="editor_ousupsub_toolbar",p="editor_ousupsub";function o(){o.superclass.constructor.apply(this,arguments)}function r(){}function u(){}function l(){l.superclass.constructor.apply(this,arguments)}a.extend(o,a.Base,{BLOCK_TAGS:["address","article","aside","audio","blockquote","canvas","dd","div","dl","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","hr","noscript","ol","output","p","pre","section","table","tfoot","ul","video"],PLACEHOLDER_CLASS:"ousupsub-tmp-class",ALL_NODES_SELECTOR:"[style],font[face]",FONT_FAMILY:"fontFamily",_wrapper:null,editor:null,toolbar:null,textarea:null,textareaLabel:null,plugins:null,_eventHandles:null,_tabFocus:null,_maxUndos:40,_undoStack:null,_redoStack:null,initializer:function(){this.textarea=a.one(document.getElementById(this.get("elementid"))),this.textarea&&(a.M.editor_ousupsub.addEditorReference(this.get("elementid"),this),this._eventHandles=[],this._wrapper=a.Node.create('
'),this.editor=a.Node.create('
'),this.textareaLabel=a.one('[for="'+this.get("elementid")+'"]'),this.textareaLabel&&(this.textareaLabel.generateID(),this.editor.setAttribute("aria-labelledby",this.textareaLabel.get("id"))),this.setupToolbar(),this.setupTemplateEditor(),this.disableCssStyling(),document.queryCommandSupported("DefaultParagraphSeparator")&&document.execCommand("DefaultParagraphSeparator",!1,"p"),this.textarea.get("parentNode").insert(this._wrapper,this.textarea),this.textarea.hide(),this.updateFromTextArea(),this.setupTextareaNavigation(),this._preventEnter(),this.publishEvents(),this.setupSelectionWatchers(),this.setupAutomaticPolling(),this.setupPlugins())},destructor:function(){a.Array.each(this.plugins,function(e,t){e.destroy(),this.plugins[t]=undefined},this),new a.EventHandle(this._eventHandles).detach(),this.textarea.show(),this._wrapper.remove(!0),a.M.editor_ousupsub.removeEditorReference(this.get("elementid"),this)},focus:function(){return this.editor.focus(),this},publishEvents:function(){return this.publish("change",{broadcast:!0,preventable:!0}),this.publish("pluginsloaded",{fireOnce:!0}),this.publish("ousupsub:selectionchanged",{prefix:"ousupsub"}),this},setupAutomaticPolling:function(){return this._registerEventHandle(this.editor.on(["keyup","cut"],this.updateOriginal,this)),this._registerEventHandle(this.editor.on(["keypress","delete"],this.cleanEditorHTMLSimple,this)),this._registerEventHandle(this.editor.on("paste",this.pasteCleanup,this)),this._registerEventHandle(this.editor.on("drop",this.updateOriginalDelayed,this)),this},updateOriginalDelayed:function(){return setTimeout(a.bind(this.updateOriginal,this),0),this},setupPlugins:function(){var e,t,i,s,n;for(t in this.plugins={},e=this.get("plugins"))if((i=e[t]).plugins)for(s in i.plugins)"superscript"===(n=i.plugins[s]).name?this.plugins.superscript=new a.M.editor_ousupsub.EditorPlugin({name:"superscript",group:i.group,editor:this.editor,toolbar:this.toolbar,host:this,exec:"superscript",tags:"sup",keys:["94"],icon:"e/superscript",keyDescription:"Shift + ^ or Up arrow"}):"subscript"===n.name&&(this.plugins.subscript=new a.M.editor_ousupsub.EditorPlugin({name:"subscript",group:i.group,editor:this.editor,toolbar:this.toolbar,host:this,exec:"subscript",tags:"sub",keys:["95"],icon:"e/subscript",keyDescription:"Shift + _ or Down arrow"}));return this._undoStack=[],this._redoStack=[],this.plugins.undo=new a.M.editor_ousupsub.EditorPlugin({name:"undo",group:i.group,editor:this.editor,toolbar:this.toolbar,host:this,keys:["90"],callback:this._undoHandler}),this.plugins.redo=new a.M.editor_ousupsub.EditorPlugin({name:"redo",group:i.group,editor:this.editor,toolbar:this.toolbar,host:this,keys:["89"],callback:this._redoHandler}),this.on("pluginsloaded",function(){this._addToUndo(this._getHTML()),this.on("ousupsub:selectionchanged",this._changeListener,this)},this),this._updateButtonsStates(),this.setupUndoHandlers(),this.fire("pluginsloaded"),this},setupUndoHandlers:function(){return this._registerEventHandle(this._wrapper.delegate("key",this._undoHandler,"down:90+ctrl","."+e,this)),this._registerEventHandle(this._wrapper.delegate("key",this._redoHandler,"down:89+ctrl","."+e,this)),this},pluginEnabled:function(e){return!!this.plugins[e]},enablePlugins:function(e){this._setPluginState(!0,e)},disablePlugins:function(e){this._setPluginState(!1,e)},_setPluginState:function(e,t){var i=e?"enableButtons":"disableButtons";t?this.plugins[t][i]():a.Object.each(this.plugins,function(e){e[i]()},this)},_registerEventHandle:function(e){this._eventHandles.push(e)},setupToolbar:function(){return this.toolbar=a.Node.create(''),this._wrapper.appendChild(this.toolbar),this.textareaLabel&&this.toolbar.setAttribute("aria-labelledby",this.textareaLabel.get("id")),this.setupToolbarNavigation(),this},setupToolbarNavigation:function(){return this._wrapper.delegate("key",this.toolbarKeyboardNavigation,"down:37,39","."+t,this),this._wrapper.delegate("focus",function(e){this._setTabFocus(e.currentTarget)},"."+t+" button",this),this},toolbarKeyboardNavigation:function(e){e.preventDefault();var t=this.toolbar.all("button"),i=1,s=e.target.ancestor("button",!0);37===e.keyCode&&(i=-1),(e=this._findFirstFocusable(t,s,i))&&(e.focus(),this._setTabFocus(e))},_findFirstFocusable:function(e,t,i){var s,n,o=0,r=e.indexOf(t);for(r<-1&&(r=0);o=e.size()&&(r=0),o++,!(s=e.item(r)).hasAttribute("hidden")&&!s.hasAttribute("disabled")&&!s.ancestor(".ousupsub_group").hasAttribute("hidden")){n=s;break}return n},checkTabFocus:function(){var e;return this._tabFocus&&(!( -this._tabFocus.hasAttribute("disabled")||this._tabFocus.hasAttribute("hidden")||this._tabFocus.ancestor(".ousupsub_group").hasAttribute("hidden"))||(e=this._findFirstFocusable(this.toolbar.all("button"),this._tabFocus,-1))&&(this._tabFocus.compareTo(document.activeElement)&&e.focus(),this._setTabFocus(e))),this},_setTabFocus:function(e){return this._tabFocus&&this._tabFocus.setAttribute("tabindex","-1"),this._tabFocus=e,this._tabFocus.setAttribute("tabindex",0),this.toolbar.setAttribute("aria-activedescendant",this._tabFocus.generateID()),this},disableCssStyling:function(){try{document.execCommand("styleWithCSS",0,!1)}catch(e){try{document.execCommand("useCSS",0,!0)}catch(t){try{document.execCommand("styleWithCSS",!1,!1)}catch(i){}}}},setupTemplateEditor:function(){var t,e,i,s,n=a.Node.create('
');n.appendChild(this.editor),this._wrapper.appendChild(n),i=6*this.textarea.getAttribute("cols")+41+"px",this.editor.setStyle("width",i),this.editor.setStyle("minWidth",i),this.editor.setStyle("maxWidth",i),i=this.textarea.getAttribute("rows"),i=(t=6*i+13)-6+"px",this.editor.setStyle("height",e=t-10+"px"),this.editor.setStyle("minHeight",e),this.editor.setStyle("maxHeight",e),this.editor.setStyle("line-height",i),n.setStyle("minHeight",i=1+t+"px"),this.textareaLabel.setStyle("display","inline-block"),this.textareaLabel.setStyle("margin",0),this.textareaLabel.setStyle("height",i),this.textareaLabel.setStyle("minHeight",i),this.textareaLabel.setStyle("maxheight",i),this.textareaLabel.hasClass("accesshide")?(this.textareaLabel.removeClass("accesshide"),this.textareaLabel.setStyle("visibility","hidden"),this._wrapper.setStyle("margin-left",-parseInt(this.textareaLabel.get("offsetWidth")))):(this.textareaLabel.getDOMNode().parentNode.style.paddingBottom=e,this.textareaLabel.setStyle("vertical-align","bottom")),n="#"+(s=this).get("elementid").replace(/:/g,"\\:")+"editable",a.on("contentready",function(){s.textareaLabel.setStyle("line-height",s.editor.getComputedStyle("line-height"));var e=1+t+parseInt(s.toolbar.get("offsetHeight"));s._wrapper.setStyle("height",e),s._wrapper.setStyle("minHeight",e),s._wrapper.setStyle("maxHeight",e),a.UA.ie&&"hidden"===s.textareaLabel.getComputedStyle("visibility")&&s._wrapper.setStyle("vertical-align",parseInt(s.toolbar.get("offsetHeight"))-1+"px")},n)},_getEmptyContent:function(){return""},updateFromTextArea:function(){this.editor.setHTML(""),this.editor.append(this.textarea.get("value")),this.cleanEditorHTML(),""===this.editor.getHTML()&&this.editor.setHTML(this._getEmptyContent())},updateOriginal:function(){var e=this.textarea.get("value"),t=this.getCleanHTML();return""===t&&this.isActive()&&(t=this._getEmptyContent()),e!==(t=(t=this._removeUnicodeCharacters(t)).trim())&&(this.textarea.set("value",t),this.fire("change")),this},setupTextareaNavigation:function(){return this._registerEventHandle(this._wrapper.delegate("key",this.textareaKeyboardNavigation,"down:38,40","."+e,this)),this._registerEventHandle(this._wrapper.delegate("key",this.textareaKeyboardNavigation,"press:94, 95","."+e,this)),this},textareaKeyboardNavigation:function(e){var t;e.preventDefault(),YUI.Env.UA.android||this.isActive()||this.focus(),t="",38===(e=(e=window.event||e).keyCode||e.charCode)||94===e?t="superscript":40!==e&&95!==e||(t="subscript"),t&&this._applyTextCommand(t,1)},_preventEnter:function(){var e="keypress";(a.UA.webkit||a.UA.ie)&&(e="keydown"),this.editor.on(e,function(e){e=window.event||e;13===e.keyCode&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},this)},_addToRedo:function(e){this._redoStack.push(e)},_addToUndo:function(e,t){for(void 0===t&&(t=!1),this._undoStack[this._undoStack.length-1]!==e&&(this._undoStack.push(e),t&&(this._redoStack=[]));this._undoStack.length>this._maxUndos;)this._undoStack.shift()},_getHTML:function(){return this.getCleanHTML()},_getRedo:function(){return this._redoStack.pop()},_getUndo:function(e){if(1===this._undoStack.length)return this._undoStack[0];var t=this._undoStack.pop();return t===e&&(t=this._undoStack.pop()),0===this._undoStack.length&&this._addToUndo(t),t},_restoreValue:function(e){this.editor.setHTML(e),this._addToUndo(e)},_updateButtonsStates:function(){1"===t?"":(0===t.indexOf("")&&(e=t.length-("".length+"".length),t=t.substr("".length,e)),this._cleanHTML(t))},cleanEditorHTML:function(){return this.editor.set("innerHTML",this._cleanHTML(this.editor.get("innerHTML"))),this},cleanEditorHTMLSimple:function(){var e=window.rangy.saveSelection();return this.editor.set("innerHTML",this._cleanHTMLSimple(this.editor.get("innerHTML"))),window.rangy.restoreSelection(e,!0),this},_cleanHTMLSimple:function(e){return this._filterContentWithRules(e,[{regex:/]*?rangySelectionBoundary[^>]*?)[^>]*>(.+)<\/span>/gi,replace:"$1"}])},_cleanHTML:function(e){return this._filterContentWithRules(e,[{ -regex:/]*>( |\s)*<\/p>/gi,replace:""},{regex:/]*( |\s)*>/gi,replace:""},{regex:/]*( |\s)*>/gi,replace:""},{regex:/ /gi,replace:" "},{regex:/<\/sup>(\s*)+/gi,replace:"$1"},{regex:/<\/sub>(\s*)+/gi,replace:"$1"},{regex:/(\s*)+/gi,replace:"$1"},{regex:/(\s*)+/gi,replace:"$1"},{regex:/(\s*)+<\/sup>/gi,replace:"$1"},{regex:/(\s*)+<\/sub>/gi,replace:"$1"},{regex:/
/gi,replace:""},{regex:/]*>[\s\S]*?<\/style>/gi,replace:""},{regex:/)/gi,replace:""},{regex:/]*>[\s\S]*?<\/script>/gi,replace:""},{regex:/<\/?(?:br|title|meta|style|std|font|html|body|link|a|ul|li|ol)[^>]*?>/gi,replace:""},{regex:/<\/?(?:b|i|u|ul|ol|li|img)[^>]*?>/gi,replace:""},{regex:/<\/?(?:abbr|address|area|article|aside|audio|base|bdi|bdo|blockquote)[^>]*?>/gi,replace:""},{regex:/<\/?(?:button|canvas|caption|cite|code|col|colgroup|content|data)[^>]*?>/gi,replace:""},{regex:/<\/?(?:datalist|dd|decorator|del|details|dialog|dfn|div|dl|dt|element)[^>]*?>/gi,replace:""},{regex:/<\/?(?:em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5)[^>]*?>/gi,replace:""},{regex:/<\/?(?:h6|header|hgroup|hr|iframe|input|ins|kbd|keygen|label|legend)[^>]*?>/gi,replace:""},{regex:/<\/?(?:main|map|mark|menu|menuitem|meter|nav|noscript|object|optgroup)[^>]*?>/gi,replace:""},{regex:/<\/?(?:option|output|p|param|pre|progress|q|rp|rt|rtc|ruby|samp)[^>]*?>/gi,replace:""},{regex:/<\/?(?:section|select|script|shadow|small|source|std|strong|summary)[^>]*?>/gi,replace:""},{regex:/<\/?(?:svg|table|tbody|td|template|textarea|time|tfoot|th|thead|tr|track)[^>]*?>/gi,replace:""},{regex:/<\/?(?:var|wbr|video)[^>]*?>/gi,replace:""},{regex:/<\/?(?:acronym|applet|basefont|big|blink|center|dir|frame|frameset|isindex)[^>]*?>/gi,replace:""},{regex:/<\/?(?:listing|noembed|plaintext|spacer|strike|tt|xmp)[^>]*?>/gi,replace:""},{regex:/<\/?(?:jsl|nobr)[^>]*?>/gi,replace:""},{regex:/]*?rangySelectionBoundary[^>]*?)[^>]*>[\s\S]*?([\s\S]*?)<\/span>/gi,replace:"$1"},{regex:/]*?rangySelectionBoundary[^>]*?)[^>]*>( |\s)*<\/span>/gi,replace:""},{regex:/]*?rangySelectionBoundary[^>]*?)[^>]*>[\s\S]*?([\s\S]*?)<\/span>/gi,replace:"$1"},{regex:/]*>( |\s)*<\/sup>/gi,replace:""},{regex:/]*>( |\s)*<\/sub>/gi,replace:""},{regex:/(.*?)<\/xmlns.*?>/gi,replace:"$1"}])},cleanEditorHTMLEmptySupAndSubTags:function(){var e=window.rangy.saveSelection(),t=this.editor.get("innerHTML"),t=this._cleanEditorHTMLEmptySupAndSubTags(t),t=this._removeUnicodeCharacters(t);return this.editor.set("innerHTML",t),window.rangy.restoreSelection(e,!0),this},_cleanEditorHTMLEmptySupAndSubTags:function(e){return this._filterContentWithRules(e,[{regex:/]*>(|\s)*<\/su[bp]>/gi,replace:""}])},_filterContentWithRules:function(e,t){for(var i=0,i=0;i([\s\S]+)$/gi,replace:""},{regex://gi,replace:""},{regex://gi,replace:""},{regex:/]*>[\s\S]*?<\/xml>/gi,replace:""},{regex:/<\?xml[^>]*>[\s\S]*?<\\\?xml>/gi,replace:""},{regex:/<\/?\w+:[^>]*>/gi,replace:""}]),0!==(e=this._cleanHTML(e)).length&&e.match(/\S/)?((t=document.createElement("div")).innerHTML=e,e=t.innerHTML,t.innerHTML="",e=this._filterContentWithRules(e,[{regex:/(<[^>]*?style\s*?=\s*?"[^>"]*?)(?:[\s]*MSO[-:][^>;"]*;?)+/gi,replace:"$1"},{regex:/(<[^>]*?class\s*?=\s*?"[^>"]*?)(?:[\s]*MSO[_a-zA-Z0-9\-]*)+/gi,replace:"$1"},{regex:/(<[^>]*?class\s*?=\s*?"[^>"]*?)(?:[\s]*Apple-[_a-zA-Z0-9\-]*)+/gi,replace:"$1"},{regex:/
]*?name\s*?=\s*?"OLE_LINK\d*?"[^>]*?>\s*?<\/a>/gi,replace:""}]),this._cleanHTML(e)):e):""},_applyTextCommand:function(e,t){var i;if(t){if("superscript"===(i=this.getCursorTag())&&e===i||"subscript"===i&&e===i)return;if("superscript"===i&&"subscript"===e?e="superscript":"subscript"===i&&"superscript"===e&&(e="subscript"),!this.pluginEnabled(e))return}document.execCommand(e,!1,null),(t=window.rangy.getSelection()).isCollapsed&&(this.cleanEditorHTMLEmptySupAndSubTags(),e=this.insertContentAtFocusPoint("<"+(i="superscript"===e?"sup":"sub")+">\ufeff"),(i=window.rangy.createRange()).selectNode(e._node.childNodes[0]),this.setSelection([i]),t.rangeCount&&t.collapseToEnd()),this._normaliseTextarea(),this.cleanEditorHTMLSimple(),this.saveSelection(),this.updateOriginal()},getCursorTag:function(){var e="text",t=window.rangy.getSelection(),i=t.focusNode.nodeName.toLowerCase(),s=t.focusNode.parentNode.nodeName.toLowerCase(),n=""; -return t.focusNode.childNodes&&t.focusNode.childNodes[t.focusOffset-1]&&(n=t.focusNode.childNodes[t.focusOffset-1].nodeName.toLowerCase()),"sup"===i||"sup"===s||"sup"===n?e="superscript":"sub"!==i&&"sub"!==s&&"sub"!==n||(e="subscript"),e},_normaliseTextarea:function(){var e,t,i=window.rangy.saveSelection(),s=this._getEditorNode();for(this._removeSingleNodesByName(s,"br"),e=["p","b","i","u","ul","ol","li"],t=0;t]*?name\s*?=\s*?"OLE_LINK\d*?"[^>]*?>\s*?<\/a>/gi, replace: ""} - ]; - - // Apply the rules. - content = this._filterContentWithRules(content, rules); - - // Reapply the standard cleaner to the content. - content = this._cleanHTML(content); - - return content; - }, - - /** - * Apply the given document.execCommand and tidy up the editor dom afterwards. - * - * @method _applyTextCommand - * @private - * @param int mode (optional) default is button (0), keyboard is 1 - * @return void - */ - _applyTextCommand: function(command, mode) { - var selection, tag; - // Handle keyboard mode. - if (mode) { - tag = this.getCursorTag(); - if (tag === 'superscript' && command === tag || - tag === 'subscript' && command === tag) { - return; // Do nothing. - } else if (tag === 'superscript' && command === 'subscript') { - command = 'superscript'; - } else if (tag === 'subscript' && command === 'superscript') { - command = 'subscript'; - } - - if (!this.pluginEnabled(command)) { - return; - } - } - - // Apply command. - document.execCommand(command, false, null); - - // If nothing is selected add a relevant tag. - selection = window.rangy.getSelection(); - // If it's a collapsed selection the cursor is in the editor but no selection has been made. - if (selection.isCollapsed) { - - // Remove empty sup and sub tags. - this.cleanEditorHTMLEmptySupAndSubTags(); - // Insert tag at cursor focus point. - tag = command === 'superscript' ? 'sup' : 'sub'; - //  is is the Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF). Used - // by TinyMCE to add empty sup/sub tags when nothing is selected. This causes lint - // errors but I couldn't find a better solution. - // http://stackoverflow.com/questions/9691771/why-is-65279-appearing-in-my-html. - var node = this.insertContentAtFocusPoint('<' + tag + '>'); - var range = window.rangy.createRange(); - range.selectNode(node._node.childNodes[0]); - this.setSelection([range]); - // Restore the selection (cursor position). - if (selection.rangeCount) { - selection.collapseToEnd(); - } - } - this._normaliseTextarea(); - this.cleanEditorHTMLSimple(); - - // And mark the text area as updated. - // Save selection after changes to the DOM. If you don't do this here, - // subsequent calls to restoreSelection() will fail expecting the - // previous DOM state. - this.saveSelection(); - this.updateOriginal(); - }, - - /** - * What type of tag surrounds the cursor. - * - * @method _getCursorTag - * @private - * @return string - */ - getCursorTag: function() { - var tag = 'text'; - var selection = window.rangy.getSelection(); - var nodeName = selection.focusNode.nodeName.toLowerCase(); - var parentNodeName = selection.focusNode.parentNode.nodeName.toLowerCase(); - - var childNodeName = ''; - if (selection.focusNode.childNodes && selection.focusNode.childNodes[selection.focusOffset-1]) { - childNodeName = selection.focusNode.childNodes[selection.focusOffset-1].nodeName.toLowerCase(); - } - if (nodeName === 'sup' || parentNodeName === 'sup' || childNodeName === 'sup') { - tag = 'superscript'; - } else if (nodeName === 'sub' || parentNodeName === 'sub' || childNodeName === 'sub') { - tag = 'subscript'; - } - return tag; - }, - - /** - * Get a normalised array of the currently selected nodes. Chrome splits text nodes - * at the end of each selection and also creates empty text nodes. Fix these changes - * and provide a standard array of nodes to match the existing selection to. - * - * @method _normaliseTextarea - * @private - * @return string - */ - _normaliseTextarea: function() { - // Save the current selection (cursor position). - var selection = window.rangy.saveSelection(); - // Remove all the span tags added to the editor textarea by the browser. - // Get the html directly inside the editor

tag and remove span tags from the html inside it. - - var editor_node = this._getEditorNode(); - this._removeSingleNodesByName(editor_node, 'br'); - - // Remove specific tags that can be added through keyboard shortcuts. - var tagsToRemove = ['p', 'b', 'i', 'u', 'ul', 'ol', 'li']; - for (var i = 0; i < tagsToRemove.length; i++) { - this._removeNodesByName(editor_node, tagsToRemove[i]); - } - this._normaliseTagInTextarea('sup'); - this._normaliseTagInTextarea('sub'); - this._removeNodesByName(editor_node, 'span'); - - // Restore the selection (cursor position). - window.rangy.restoreSelection(selection, true); - - // Normalise the editor html. - editor_node.normalize(); - }, - - /** - * Remove all tags nested inside other tags of the same name. No nesting of - * similar tags e.g. is not allowed. - * - * @method _normaliseTagInTextarea - * @private - * @param string name Name of tag to normalise. - * @return string. - */ - _normaliseTagInTextarea: function(name) { - var nodes = [], container = this._getEditorNode(), parentNode, removeParent = false, node; - - // Remove nested nodes. - /* - * Where the node.firstChild == nodes[i+1] since it ignores text elements - * I know it's the first node. Since the two elements match they should cancel - * each other out. Currently we remove only the child sup. We should remove - * both and move their children out. - */ - // Nodelists change as nodes are added and removed. Use an array of nodes instead. - nodes = this._copyArray(container.querySelectorAll(name), nodes); - - for (var i = 0; i < nodes.length; i++) { - node = nodes[i]; - parentNode = node.parentNode; - removeParent = false; - if (parentNode === container ) { - continue; - } - if (parentNode.firstChild === node && parentNode.lastChild === node && - parentNode.nodeName.toLowerCase() === name) { - removeParent = true; - } - if (!removeParent && node && parentNode.nodeName.toLowerCase() === name) { - removeParent = true; - this._splitParentNode(parentNode, name); - } - this._removeNodesByName(node, name); - if (removeParent) { - this._removeNodesByName(parentNode, name); - } - } - - // Combine Sibling nodes. - // Get a new node array and fill with the a fresh nodelist. - nodes = []; - nodes = this._copyArray(container.querySelectorAll(name), nodes); - - for (i = 0; i < nodes.length; i++) { - node = nodes[i]; - if (!node.previousSibling || node.previousSibling.nodeName.toLowerCase() !== name) { - continue; - } - this._mergeNodes(node, node.previousSibling); - } - }, - - /** - * Merge the from and to nodes by moving all elements in from node to the to node. - * Append nodes in order to the to node. - * - * Can't use other dom methods like querySelectorAll because they don't return text elements. - * @method _mergeNodes - * @private - * @return void. - */ - _mergeNodes: function(from, to) { - var nodes = []; - var merge_nodes = from.childNodes; - - // Node lists reduce in size as nodes are removed. Use an array of nodes instead. - for (var i = 0; i < merge_nodes.length; i++) { - nodes.push(merge_nodes.item(i)); - } - - for (i = 0; i < nodes.length; i++) { - to.appendChild(nodes[i]); - } - this._removeNode(from); - }, - - /** - * Split the parent node into two with the node with the given name in the middle. - * - * Can't use other dom methods like querySelectorAll because they don't return text elements. - * @method _splitParentNode - * @private - * @return void. - */ - _splitParentNode: function(container_node, name) { - var nodes = [], node, nodesToAppend = []; - nodes = this._copyArray(container_node.childNodes, nodes); - - var i,j; - for (i = 0; i < nodes.length; i++) { - node = nodes[i]; - nodesToAppend = []; - if (node.nodeName.toLowerCase() === name) { - nodesToAppend = this._copyArray(node.childNodes, nodesToAppend); - } else { - nodesToAppend[0] = document.createElement(name); - nodesToAppend[0].appendChild(node); - } - for (j = 0; j < nodesToAppend.length; j++) { - container_node.parentNode.insertBefore(nodesToAppend[j], container_node); - } - } - }, - - /** - * Copy array values from a dom node list to the given array. - * - * A dom node list reduces as children are removed. Copying to a standard array provides - * an array that doesn't change. - * @method _copyArray - * @private - * @return array. - */ - _copyArray: function(from, to) { - for (var i = 0; i < from.length; i++) { - to.push(from[i]); - } - - return to; - }, - - /** - * Move all elements in container node before the reference node. - * If recursive mode is equired then where childnodes exist that are not - * text nodes. Move their children and remove the existing node. - * - * Can't use other dom methods like querySelectorAll because they don't return text elements. - * @method _removeNodesByName - * @private - * @return void. - */ - _removeNodesByName: function(container_node, name) { - var node, remove_node = container_node.nodeName.toLowerCase() === name; - var nodes = []; - var container_nodes = container_node.childNodes; - - // Don't remove the span used by rangy to save and restore the user selection. - if (container_node.nodeName.toLowerCase() === 'span' && - container_node.id.indexOf('selectionBoundary_') > -1) { - remove_node = false; - } - - nodes = this._copyArray(container_nodes, nodes); - for (var i = 0; i < nodes.length; i++) { - node = nodes[i]; - if (node.childNodes && node.childNodes.length) { - this._removeNodesByName(node, name); - - } - if (remove_node) { - var parentNode = container_node.parentNode; - parentNode.insertBefore(node, container_node); - } - - } - if (remove_node) { - this._removeNode(container_node); - } - }, - - /** - * Recursively remove any tag with the given name. Removes child nodes too. - * - * Can't use other dom methods like querySelectorAll because they don't return text elements. - * @method _removeSingleNodesByName - * @private - * @return void. - */ - _removeSingleNodesByName: function(container_node, name) { - if (!container_node.childNodes) { - return; - } - var node; - var nodes = []; - nodes = this._copyArray(container_node.childNodes, nodes); - for (var i = 0; i < nodes.length; i++) { - node = nodes[i]; - if (node.childNodes && node.childNodes.length) { - this._removeSingleNodesByName(node, name); - } - - if (node.nodeName.toLowerCase() === name) { - this._removeNode(node); - } - } - }, - - /** - * Remove a dom node in a cross browser way. - * - * @method _removeNode - * @private - * @return bool. - */ - _removeNode: function(node) { - if(!node.remove) { - return node.parentNode.removeChild(node); - } - return node.remove(); - }, - - /** - * Get the editor object. - * - * @method _getEditor - * @private - * @return node. - */ - _getEditor: function(host) { - if (!host) { - host = this.get('host'); - } - - return this; - }, - - /** - * Get the node containing the editor html to be updated. - * - * @method _getEditorNode - * @private - * @return node. - */ - _getEditorNode: function(host) { - return this._getEditor(host).editor._node; - }, - - /** - * Remove specific unicode characters from the given string. - * - * @method _removeUnicodeCharacters - * @private - * @return string. - */ - _removeUnicodeCharacters: function(text) { - var values = []; - for ( var i = 0; i < text.length; i++ ) { - if (text.charCodeAt(i) == "65279") { - continue; - } - values.push(text.charAt(i)); - } - return values.join(''); - } -}; - -Y.Base.mix(Y.M.editor_ousupsub.Editor, [EditorClean]); -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * @module moodle-editor_ousupsub-editor - * @submodule selection - */ - -/** - * Selection functions for the ousupsub editor. - * - * See {{#crossLink "M.editor_ousupsub.Editor"}}{{/crossLink}} for details. - * - * @namespace M.editor_ousupsub - * @class EditorSelection - */ - -function EditorSelection() {} - -EditorSelection.ATTRS = { -}; - -EditorSelection.prototype = { - - /** - * List of saved selections per editor instance. - * - * @property _selections - * @private - */ - _selections: null, - - /** - * A unique identifier for the last selection recorded. - * - * @property _lastSelection - * @param lastselection - * @type string - * @private - */ - _lastSelection: null, - - /** - * Whether focus came from a click event. - * - * This is used to determine whether to restore the selection or not. - * - * @property _focusFromClick - * @type Boolean - * @default false - * @private - */ - _focusFromClick: false, - - /** - * Set up the watchers for selection save and restoration. - * - * @method setupSelectionWatchers - * @chainable - */ - setupSelectionWatchers: function() { - // Save the selection when a change was made. - this._registerEventHandle(this.on('ousupsub:selectionchanged', this.saveSelection, this)); - - this._registerEventHandle(this.editor.on('focus', this.restoreSelection, this)); - - // Do not restore selection when focus is from a click event. - this._registerEventHandle(this.editor.on('mousedown', function() { - this._focusFromClick = true; - }, this)); - - // Copy the current value back to the textarea when focus leaves us and save the current selection. - this._registerEventHandle(this.editor.on('blur', function() { - // Clear the _focusFromClick value. - this._focusFromClick = false; - - // Update the original text area. - this.updateOriginal(); - }, this)); - - this._registerEventHandle(this.editor.on(['keyup', 'focus'], function(e) { - setTimeout(Y.bind(this._hasSelectionChanged, this, e), 0); - }, this)); - - // To capture both mouseup and touchend events, we need to track the gesturemoveend event in standAlone mode. Without - // standAlone, it will only fire if we listened to a gesturemovestart too. - this._registerEventHandle(this.editor.on('gesturemoveend', function(e) { - setTimeout(Y.bind(this._hasSelectionChanged, this, e), 0); - }, { - standAlone: true - }, this)); - - return this; - }, - - /** - * Work out if the cursor is in the editable area for this editor instance. - * - * @method isActive - * @return {boolean} - */ - isActive: function() { - var range = window.rangy.createRange(), - selection = window.rangy.getSelection(); - - if (!selection.rangeCount) { - // If there was no range count, then there is no selection. - return false; - } - - // We can't be active if the editor doesn't have focus at the moment. - if (!document.activeElement || - !(this.editor.compareTo(document.activeElement) || this.editor.contains(document.activeElement))) { - return false; - } - - // Check whether the range intersects the editor selection. - range.selectNode(this.editor.getDOMNode()); - return range.intersectsRange(selection.getRangeAt(0)); - }, - - /** - * Create a cross browser selection object that represents a YUI node. - * - * @method getSelectionFromNode - * @param {Node} YUI Node to base the selection upon. - * @return {[rangy.Range]} - */ - getSelectionFromNode: function(node) { - var range = window.rangy.createRange(); - range.selectNode(node.getDOMNode()); - return [range]; - }, - - /** - * Save the current selection to an internal property. - * - * This allows more reliable return focus, helping improve keyboard navigation. - * - * Should be used in combination with {{#crossLink "M.editor_ousupsub.EditorSelection/restoreSelection"}}{{/crossLink}}. - * - * @method saveSelection - */ - saveSelection: function() { - if (this.isActive()) { - this._selections = this.getSelection(); - } - }, - - /** - * Restore any stored selection when the editor gets focus again. - * - * Should be used in combination with {{#crossLink "M.editor_ousupsub.EditorSelection/saveSelection"}}{{/crossLink}}. - * - * @method restoreSelection - */ - restoreSelection: function() { - if (!this._focusFromClick) { - if (this._selections) { - this.setSelection(this._selections); - } - } - this._focusFromClick = false; - }, - - /** - * Get the selection object that can be passed back to setSelection. - * - * @method getSelection - * @return {array} An array of rangy ranges. - */ - getSelection: function() { - return window.rangy.getSelection().getAllRanges(); - }, - - /** - * Check that a YUI node it at least partly contained by the current selection. - * - * @method selectionContainsNode - * @param {Node} The node to check. - * @return {boolean} - */ - selectionContainsNode: function(node) { - return window.rangy.getSelection().containsNode(node.getDOMNode(), true); - }, - - /** - * Runs a filter on each node in the selection, and report whether the - * supplied selector(s) were found in the supplied Nodes. - * - * By default, all specified nodes must match the selection, but this - * can be controlled with the requireall property. - * - * @method selectionFilterMatches - * @param {String} selector - * @param {NodeList} [selectednodes] For performance this should be passed. If not passed, this will be looked up each time. - * @param {Boolean} [requireall=true] Used to specify that "any" match is good enough. - * @return {Boolean} - */ - selectionFilterMatches: function(selector, selectednodes, requireall) { - if (typeof requireall === 'undefined') { - requireall = true; - } - if (!selectednodes) { - // Find this because it was not passed as a param. - selectednodes = this.getSelectedNodes(); - } - var allmatch = selectednodes.size() > 0, - anymatch = false; - - var editor = this.editor, - stopFn = function(node) { - // The function getSelectedNodes only returns nodes within the editor, so this test is safe. - return node === editor; - }; - - // If we do not find at least one match in the editor, no point trying to find them in the selection. - if (!editor.one(selector)) { - return false; - } - - selectednodes.each(function(node){ - // Check each node, if it doesn't match the tags AND is not within the specified tags then fail this thing. - if (requireall) { - // Check for at least one failure. - if (!allmatch || !node.ancestor(selector, true, stopFn)) { - allmatch = false; - } - } else { - // Check for at least one match. - if (!anymatch && node.ancestor(selector, true, stopFn)) { - anymatch = true; - } - } - }, this); - if (requireall) { - return allmatch; - } else { - return anymatch; - } - }, - - /** - * Get the deepest possible list of nodes in the current selection. - * - * @method getSelectedNodes - * @return {NodeList} - */ - getSelectedNodes: function() { - var results = new Y.NodeList(), - nodes, - selection, - range, - node, - i; - - selection = window.rangy.getSelection(); - - if (selection.rangeCount) { - range = selection.getRangeAt(0); - } else { - // Empty range. - range = window.rangy.createRange(); - } - - if (range.collapsed) { - // We do not want to select all the nodes in the editor if we managed to - // have a collapsed selection directly in the editor. - // It's also possible for the commonAncestorContainer to be the document, which selectNode does not handle - // so we must filter that out here too. - if (range.commonAncestorContainer !== this.editor.getDOMNode() - && range.commonAncestorContainer !== Y.config.doc) { - range = range.cloneRange(); - range.selectNode(range.commonAncestorContainer); - } - } - - nodes = range.getNodes(); - - for (i = 0; i < nodes.length; i++) { - node = Y.one(nodes[i]); - if (this.editor.contains(node)) { - results.push(node); - } - } - return results; - }, - - /** - * Check whether the current selection has changed since this method was last called. - * - * If the selection has changed, the ousupsub:selectionchanged event is also fired. - * - * @method _hasSelectionChanged - * @private - * @param {EventFacade} e - * @return {Boolean} - */ - _hasSelectionChanged: function(e) { - var selection = window.rangy.getSelection(), - range, - changed = false; - - if (selection.rangeCount) { - range = selection.getRangeAt(0); - } else { - // Empty range. - range = window.rangy.createRange(); - } - - if (this._lastSelection) { - if (!this._lastSelection.equals(range)) { - changed = true; - return this._fireSelectionChanged(e); - } - } - this._lastSelection = range; - return changed; - }, - - /** - * Fires the ousupsub:selectionchanged event. - * - * When the selectionchanged event is fired, the following arguments are provided: - * - event : the original event that lead to this event being fired. - * - selectednodes : an array containing nodes that are entirely selected of contain partially selected content. - * - * @method _fireSelectionChanged - * @private - * @param {EventFacade} e - */ - _fireSelectionChanged: function(e) { - this.fire('ousupsub:selectionchanged', { - event: e, - selectedNodes: this.getSelectedNodes() - }); - }, - - /** - * Get the DOM node representing the common anscestor of the selection nodes. - * - * @method getSelectionParentNode - * @return {Element|boolean} The DOM Node for this parent, or false if no seletion was made. - */ - getSelectionParentNode: function() { - var selection = window.rangy.getSelection(); - if (selection.rangeCount) { - return selection.getRangeAt(0).commonAncestorContainer; - } - return false; - }, - - /** - * Set the current selection. Used to restore a selection. - * - * @method selection - * @param {array} ranges A list of rangy.range objects in the selection. - */ - setSelection: function(ranges) { - var selection = window.rangy.getSelection(); - selection.setRanges(ranges); - }, - - /** - * Inserts the given HTML into the editable content at the currently focused point. - * - * @method insertContentAtFocusPoint - * @param {String} html - * @return {Node} The YUI Node object added to the DOM. - */ - insertContentAtFocusPoint: function(html) { - var selection = window.rangy.getSelection(), - range, - node = Y.Node.create(html); - if (selection.rangeCount) { - range = selection.getRangeAt(0); - } - if (range) { - range.deleteContents(); - range.insertNode(node.getDOMNode()); - } - return node; - } - -}; - -Y.Base.mix(Y.M.editor_ousupsub.Editor, [EditorSelection]); -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * ousupsub editor plugin. - * - * @module moodle-editor_ousupsub-editor - * @submodule plugin-base - * @package editor_ousupsub - * @copyright 2014 Andrew Nicols - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * A Plugin for the ousupsub Editor used in Moodle. - * - * This class should not be directly instantiated, and all Editor plugins - * should extend this class. - * - * @namespace M.editor_ousupsub - * @class EditorPlugin - * @main - * @constructor - * @uses M.editor_ousupsub.EditorPluginButtons - */ - -function EditorPlugin() { - EditorPlugin.superclass.constructor.apply(this, arguments); -} - -var DISABLED = 'disabled', - HIGHLIGHT = 'highlight', - GROUPSELECTOR = '.ousupsub_group.', - GROUP = '_group'; - -Y.extend(EditorPlugin, Y.Base, { - /** - * The name of the current plugin. - * - * @property name - * @type string - */ - name: null, - - /** - * The name of the command to execute when the button is clicked. - * - * @property exec - * @type string - */ - exec: null, - - /** - * A Node reference to the editor. - * - * @property editor - * @type Node - */ - editor: null, - - /** - * A Node reference to the editor toolbar. - * - * @property toolbar - * @type Node - */ - toolbar: null, - - /** - * Event Handles to clear on plugin destruction. - * - * @property _eventHandles - * @private - */ - _eventHandles: null, - - /** - * All of the buttons that belong to this plugin instance. - * - * Buttons are stored by button name. - * - * @property buttons - * @type object - */ - buttons: null, - - /** - * A list of each of the button names. - * - * @property buttonNames - * @type array - */ - buttonNames: null, - - /** - * A read-only view of the current state for each button. Mappings are stored by name. - * - * Possible states are: - *

    - *
  • {{#crossLink "M.editor_ousupsub.EditorPluginButtons/ENABLED:property"}}{{/crossLink}}; and
  • - *
  • {{#crossLink "M.editor_ousupsub.EditorPluginButtons/DISABLED:property"}}{{/crossLink}}.
  • - *
- * - * @property buttonStates - * @type object - */ - buttonStates: null, - - /** - * The state for a disabled button. - * - * @property DISABLED - * @type Number - * @static - * @value 0 - */ - DISABLED: 0, - - /** - * The state for an enabled button. - * - * @property ENABLED - * @type Number - * @static - * @value 1 - */ - ENABLED: 1, - - /** - * The list of Event Handlers for buttons. - * - * @property _buttonHandlers - * @protected - * @type array - */ - _buttonHandlers: null, - - /** - * A textual description of the primary keyboard shortcut for this - * plugin. - * - * This will be null if no keyboard shortcut has been registered. - * - * @property _primaryKeyboardShortcut - * @protected - * @type String - * @default null - */ - _primaryKeyboardShortcut: null, - - /** - * An list of handles returned by setTimeout(). - * - * The keys will be the buttonName of the button, and the value the handles. - * - * @property _highlightQueue - * @protected - * @type Object - * @default null - */ - _highlightQueue: null, - - initializer: function(config) { - // Set the references to configuration parameters. - this.name = config.name; - this.exec = config.exec; - this.toolbar = config.toolbar; - this.editor = config.editor; - - // Set up the prototypal properties. - // These must be set up here because prototypal arrays and objects are copied across instances. - this.buttons = {}; - this.buttonNames = []; - this.buttonStates = {}; - this._primaryKeyboardShortcut = []; - this._buttonHandlers = []; - this._menuHideHandlers = []; - this._highlightQueue = {}; - this._eventHandles = []; - this.addButton(config); - }, - - destructor: function() { - // Detach all EventHandles. - new Y.EventHandle(this._eventHandles).detach(); - }, - - /** - * Mark the content ediable content as having been changed. - * - * This is a convenience function and passes through to - * {{#crossLink "M.editor_ousupsub.EditorTextArea/updateOriginal"}}updateOriginal{{/crossLink}}. - * - * @method markUpdated - */ - markUpdated: function() { - // Save selection after changes to the DOM. If you don't do this here, - // subsequent calls to restoreSelection() will fail expecting the - // previous DOM state. - this.get('host').saveSelection(); - - return this.get('host').updateOriginal(); - }, - - /** - * Register an event handle for disposal in the destructor. - * - * @method registerEventHandle - * @param {EventHandle} The Event Handle as returned by Y.on, and Y.delegate. - */ - registerEventHandle: function(handle) { - this._eventHandles.push(handle); - }, - - /** - * Add a button for this plugin to the toolbar. - * - * @method addButton - * @param {object} config The configuration for this button - * @param {string} [config.iconurl] The URL for the icon. If not specified, then the icon and component will be used instead. - * @param {string} [config.icon] The icon identifier. - * @param {string} [config.iconComponent='core'] The icon component. - * @param {string} [config.keys] The shortcut key that can call this plugin from the keyboard. - * @param {string} [config.keyDescription] An optional description for the keyboard shortcuts. - * If not specified, this is automatically generated based on config.keys. - * If multiple key bindings are supplied to config.keys, then only the first is used. - * If set to false, then no description is added to the title. - * @param {string} [config.tags] The tags that trigger this button to be highlighted. - * @param {boolean} [config.tagMatchRequiresAll=true] Working in combination with the tags parameter, when true - * every tag of the selection has to match. When false, only one match is needed. Only set this to false when - * necessary as it is much less efficient. - * See {{#crossLink "M.editor_ousupsub.EditorSelection/selectionFilterMatches:method"}}{{/crossLink}} for more information. - * @param {string} [config.title=this.name] The string identifier in the plugin's language file. - * @param {string} [config.buttonName=this.name] The name of the button. This is used in the buttons object, and if - * specified, in the class for the button. - * @param {function} config.callback A callback function to call when the button is clicked. - * @param {object} [config.callbackArgs] Any arguments to pass to the callback. - * @return {Node} The Node representing the newly created button. - */ - addButton: function(config) { - var group = this.get('group'), - pluginname = this.name, - buttonClass = 'ousupsub_' + pluginname + '_button', - button, - host = this.get('host'); - - if (config.exec) { - buttonClass = buttonClass + '_' + config.exec; - } - - if (!config.buttonName) { - // Set a default button name - this is used as an identifier in the button object. - config.buttonName = config.exec || pluginname; - } else { - buttonClass = buttonClass + '_' + config.buttonName; - } - config.buttonClass = buttonClass; - - // Normalize icon configuration. - config = this._normalizeIcon(config); - - if (!config.title) { - config.title = 'pluginname'; - } - var title = M.util.get_string(pluginname, 'editor_ousupsub'); - - // Create the actual button. - var icon = ''; - if (config.iconurl) { - icon = ''; - } - button = Y.Node.create(''); - button.setAttribute('title', title); - - // Append it to the group. - group.append(button); - - var currentfocus = this.toolbar.getAttribute('aria-activedescendant'); - if (!currentfocus) { - // Initially set the first button in the toolbar to be the default on keyboard focus. - // Initially set the first button in the toolbar to be the default on keyboard focus. - button.setAttribute('tabindex', '0'); - this.toolbar.setAttribute('aria-activedescendant', button.generateID()); - this.get('host')._tabFocus = button; - } - // Normalize the callback parameters. - if (!config.callback) { - config.callback = this._applyTextCommand; - } - config.callback = Y.rbind(this._callbackWrapper, this, config.callback); - - // Add the standard click handler to the button. - this._buttonHandlers.push( - this.toolbar.delegate('click', config.callback, '.' + buttonClass, this) - ); - - // Handle button click via shortcut key. - if (config.keys) { - if (typeof config.keyDescription !== 'undefined') { - // A keyboard shortcut description was specified - use it. - this._primaryKeyboardShortcut[buttonClass] = config.keyDescription; - } - this._addKeyboardListener(config.callback, config.keys, buttonClass); - - if (this._primaryKeyboardShortcut[buttonClass]) { - // If we have a valid keyboard shortcut description, then set it with the title. - button.setAttribute('title', M.util.get_string('plugin_title_shortcut', 'editor_ousupsub', { - title: title, - shortcut: this._primaryKeyboardShortcut[buttonClass] - })); - } - } - - // Handle highlighting of the button. - if (config.tags) { - var tagMatchRequiresAll = true; - if (typeof config.tagMatchRequiresAll === 'boolean') { - tagMatchRequiresAll = config.tagMatchRequiresAll; - } - this._buttonHandlers.push( - host.on(['ousupsub:selectionchanged', 'change'], function(e) { - if (typeof this._highlightQueue[config.buttonName] !== 'undefined') { - clearTimeout(this._highlightQueue[config.buttonName]); - } - // Async the highlighting. - this._highlightQueue[config.buttonName] = setTimeout(Y.bind(function(e) { - if (host.selectionFilterMatches(config.tags, e.selectedNodes, tagMatchRequiresAll)) { - this.highlightButtons(config.buttonName); - } else { - this.unHighlightButtons(config.buttonName); - } - }, this, e), 0); - }, this) - ); - } - - // Add the button reference to the buttons array for later reference. - this.buttonNames.push(config.buttonName); - this.buttons[config.buttonName] = button; - this.buttonStates[config.buttonName] = this.ENABLED; - return button; - }, - - /** - * Normalize and sanitize the configuration variables relating to callbacks. - * - * @method _normalizeCallback - * @param {object} config - * @param {function} config.callback A callback function to call when the button is clicked. - * @param {object} [config.callbackArgs] Any arguments to pass to the callback. - * @param {object} [inheritFrom] A parent configuration that this configuration may inherit from. - * @return {object} The normalized configuration - * @private - */ - _normalizeCallback: function(config, inheritFrom) { - if (config._callbackNormalized) { - // Return early if the callback has already been normalized. - return config; - } - - if (!inheritFrom) { - // Create an empty inheritFrom to make life easier below. - inheritFrom = {}; - } - - // We wrap the callback in function to prevent the default action, check whether the editor is - // active and focus it, and then mark the field as updated. - config._callback = config.callback || inheritFrom.callback; - config.callback = Y.rbind(this._callbackWrapper, this, this._applyTextCommand, config.callbackArgs); - - config._callbackNormalized = true; - - return config; - }, - - /** - * Normalize and sanitize the configuration variables relating to icons. - * - * @method _normalizeIcon - * @param {object} config - * @param {string} [config.iconurl] The URL for the icon. If not specified, then the icon and component will be used instead. - * @param {string} [config.icon] The icon identifier. - * @param {string} [config.iconComponent='core'] The icon component. - * @return {object} The normalized configuration - * @private - */ - _normalizeIcon: function(config) { - if (config.icon && !config.iconurl) { - // The default icon component. - if (!config.iconComponent) { - config.iconComponent = 'core'; - } - config.iconurl = M.util.image_url(config.icon, config.iconComponent); - } - - return config; - }, - - /** - * A wrapper in which to run the callbacks. - * - * This handles common functionality such as: - *
    - *
  • preventing the default action; and
  • - *
  • focusing the editor if relevant.
  • - *
- * - * @method _callbackWrapper - * @param {EventFacade} e - * @param {Function} callback The function to call which makes the relevant changes. - * @param {Array} [callbackArgs] The arguments passed to this callback. - * @return {Mixed} The value returned by the callback. - * @private - */ - _callbackWrapper: function(e, callback, callbackArgs) { - e.preventDefault(); - - if (!this.isEnabled()) { - // Exit early if the plugin is disabled. - return; - } - - var creatorButton = e.currentTarget.ancestor('button', true); - - if (creatorButton && creatorButton.hasAttribute(DISABLED)) { - // Exit early if the clicked button was disabled. - return; - } - - if (!(YUI.Env.UA.android || this.get('host').isActive())) { - // We must not focus for Android here, even if the editor is not active because the keyboard auto-completion - // changes the cursor position. - // If we save that change, then when we restore the change later we get put in the wrong place. - // Android is fine to save the selection without the editor being in focus. - this.get('host').focus(); - } - - // Save the selection. - this.get('host').saveSelection(); - - // Build the arguments list, but remove the callback we're calling. - var args = [e, callbackArgs]; - - // Restore selection before making changes. - this.get('host').restoreSelection(); - - // Actually call the callback now. - return callback.apply(this, args); - }, - - /** - * Add a keyboard listener to call the callback. - * - * The keyConfig will take either an array of keyConfigurations, in - * which case _addKeyboardListener is called multiple times; an object - * containing an optional eventtype, optional container, and a set of - * keyCodes, or just a string containing the keyCodes. When keyConfig is - * not an object, it is wrapped around a function that ensures that - * only the expected key modifiers were used. For instance, it checks - * that space+ctrl is not triggered when the user presses ctrl+shift+space. - * When using an object, the developer should check that manually. - * - * @method _addKeyboardListener - * @param {function} callback - * @param {array|object|string} keyConfig - * @param {string} [keyConfig.eventtype=key] The type of event - * @param {string} [keyConfig.container=.editor_ousupsub_content] The containing element. - * @param {string} keyConfig.keyCodes The keycodes to user for the event. - * @private - * - */ - _addKeyboardListener: function(callback, keyConfig, buttonName) { - var eventtype = 'key', - container = CSS.EDITORWRAPPER, - keys, - handler, - modifier; - - if (Y.Lang.isArray(keyConfig)) { - // If an Array was specified, call the add function for each element. - Y.Array.each(keyConfig, function(config) { - this._addKeyboardListener(callback, config, buttonName); - }, this); - - return this; - - } else if (typeof keyConfig === "object") { - if (keyConfig.eventtype) { - eventtype = keyConfig.eventtype; - } - - if (keyConfig.container) { - container = keyConfig.container; - } - - // Must be specified. - keys = keyConfig.keyCodes; - handler = callback; - - } else { - modifier = ''; - keys = keyConfig; - if (typeof this._primaryKeyboardShortcut[buttonName] === 'undefined') { - this._primaryKeyboardShortcut[buttonName] = this._getDefaultMetaKeyDescription(keyConfig); - } - // Wrap the callback into a handler to check if it uses the specified modifiers, not more. - handler = Y.bind(function(modifiers, e) { - callback.apply(this, [e]); - }, this, [modifier]); - } - - this._buttonHandlers.push( - this.editor.delegate( - eventtype, - handler, - keys, - container, - this - ) - ); - - }, - - /** - * Checks if a key event was strictly defined for the modifiers passed. - * - * @method _eventUsesExactKeyModifiers - * @param {Array} modifiers List of key modifiers to check for (alt, ctrl, meta or shift). - * @param {EventFacade} e The event facade. - * @return {Boolean} True if the event was stricly using the modifiers specified. - */ - _eventUsesExactKeyModifiers: function(modifiers, e) { - var exactMatch = true, - hasKey; - - if (e.type !== 'key') { - return false; - } - - hasKey = Y.Array.indexOf(modifiers, 'alt') > -1; - exactMatch = exactMatch && ((e.altKey && hasKey) || (!e.altKey && !hasKey)); - hasKey = Y.Array.indexOf(modifiers, 'ctrl') > -1; - exactMatch = exactMatch && ((e.ctrlKey && hasKey) || (!e.ctrlKey && !hasKey)); - hasKey = Y.Array.indexOf(modifiers, 'meta') > -1; - exactMatch = exactMatch && ((e.metaKey && hasKey) || (!e.metaKey && !hasKey)); - hasKey = Y.Array.indexOf(modifiers, 'shift') > -1; - exactMatch = exactMatch && ((e.shiftKey && hasKey) || (!e.shiftKey && !hasKey)); - - return exactMatch; - }, - - /** - * Determine if this plugin is enabled, based upon the state of it's buttons. - * - * @method isEnabled - * @return {boolean} - */ - isEnabled: function() { - // The first instance of an undisabled button will make this return true. - var found = Y.Object.some(this.buttonStates, function(button) { - return (button === this.ENABLED); - }, this); - - return found; - }, - - /** - * Enable one button, or all buttons relating to this Plugin. - * - * If no button is specified, all buttons are disabled. - * - * @method disableButtons - * @param {String} [button] The name of a specific plugin to enable. - * @chainable - */ - disableButtons: function(button) { - return this._setButtonState(false, button); - }, - - /** - * Enable one button, or all buttons relating to this Plugin. - * - * If no button is specified, all buttons are enabled. - * - * @method enableButtons - * @param {String} [button] The name of a specific plugin to enable. - * @chainable - */ - enableButtons: function(button) { - return this._setButtonState(true, button); - }, - - /** - * Set the button state for one button, or all buttons associated with this plugin. - * - * @method _setButtonState - * @param {Boolean} enable Whether to enable this button. - * @param {String} [button] The name of a specific plugin to set state for. - * @chainable - * @private - */ - _setButtonState: function(enable, button) { - var attributeChange = 'setAttribute'; - if (enable) { - attributeChange = 'removeAttribute'; - } - if (button) { - if (this.buttons[button]) { - this.buttons[button][attributeChange](DISABLED, DISABLED); - this.buttonStates[button] = enable ? this.ENABLED : this.DISABLED; - } - } else { - Y.Array.each(this.buttonNames, function(button) { - this.buttons[button][attributeChange](DISABLED, DISABLED); - this.buttonStates[button] = enable ? this.ENABLED : this.DISABLED; - }, this); - } - - this.get('host').checkTabFocus(); - return this; - }, - - /** - * Highlight a button, or buttons in the toolbar. - * - * If no button is specified, all buttons are highlighted. - * - * @method highlightButtons - * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. - * @chainable - */ - highlightButtons: function(button) { - return this._changeButtonHighlight(true, button); - }, - - /** - * Un-highlight a button, or buttons in the toolbar. - * - * If no button is specified, all buttons are un-highlighted. - * - * @method unHighlightButtons - * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. - * @chainable - */ - unHighlightButtons: function(button) { - return this._changeButtonHighlight(false, button); - }, - - /** - * Highlight a button, or buttons in the toolbar. - * - * @method _changeButtonHighlight - * @param {boolean} highlight true - * @param {string} [button] If a plugin has multiple buttons, the specific button to highlight. - * @protected - * @chainable - */ - _changeButtonHighlight: function(highlight, button) { - var method = 'addClass'; - - if (!highlight) { - method = 'removeClass'; - } - if (button) { - if (this.buttons[button]) { - this.buttons[button][method](HIGHLIGHT); - } - } else { - Y.Object.each(this.buttons, function(button) { - button[method](HIGHLIGHT); - }, this); - } - - return this; - }, - - /** - * Get the default meta key to use with keyboard events. - * - * On a Mac, this will be the 'meta' key for Command; otherwise it will - * be the Control key. - * - * @method _getDefaultMetaKey - * @return {string} - * @private - */ - _getDefaultMetaKey: function() { - if (Y.UA.os === 'macintosh') { - return 'meta'; - } else { - return 'ctrl'; - } - }, - - /** - * Get the user-visible description of the meta key to use with keyboard events. - * - * On a Mac, this will be 'Command' ; otherwise it will be 'Control'. - * - * @method _getDefaultMetaKeyDescription - * @return {string} - * @private - */ - _getDefaultMetaKeyDescription: function(keyCode) { - if (Y.UA.os === 'macintosh') { - return M.util.get_string('editor_command_keycode', 'editor_ousupsub', String.fromCharCode(keyCode).toLowerCase()); - } else { - return M.util.get_string('editor_control_keycode', 'editor_ousupsub', String.fromCharCode(keyCode).toLowerCase()); - } - }, - - /** - * Get the standard key event to use for keyboard events. - * - * @method _getKeyEvent - * @return {string} - * @private - */ - _getKeyEvent: function() { - return 'down:'; - }, - - /** - * Apply the given document.execCommand and tidy up the editor dom afterwards. - * - * @method _applyTextCommand - * @private - * @return void - */ - _applyTextCommand: function(e) { - var mode = 0; - - if(e && e.type === 'key') { - // handled by this._getEditor().textareaKeyboardNavigation(e); - return; - } - - this._getEditor()._applyTextCommand(this.exec, mode); - }, - - /** - * Get the editor object. - * - * @method _getEditor - * @private - * @return node. - */ - _getEditor: function(host) { - if (!host) { - host = this.get('host'); - } - - return host; - }, - - /** - * Get the node containing the editor html to be updated. - * - * @method _getEditorNode - * @private - * @return node. - */ - _getEditorNode: function(host) { - return this._getEditor(host).editor._node; - } - -}, { - NAME: 'editorPlugin', - ATTRS: { - /** - * The editor instance that this plugin was instantiated by. - * - * @attribute host - * @type M.editor_ousupsub.Editor - * @writeOnce - */ - host: { - writeOnce: true - }, - - /** - * The toolbar group that this button belongs to. - * - * When setting, the name of the group should be specified. - * - * When retrieving, the Node for the toolbar group is returned. If - * the group doesn't exist yet, then it is created first. - * - * @attribute group - * @type Node - * @writeOnce - */ - group: { - writeOnce: true, - getter: function(groupName) { - var group = this.toolbar.one(GROUPSELECTOR + groupName + GROUP); - if (!group) { - group = Y.Node.create('
'); - this.toolbar.append(group); - } - - return group; - } - } - } -}); - -Y.namespace('M.editor_ousupsub').EditorPlugin = EditorPlugin; -// This file is part of Moodle - http://moodle.org/ -// -// Moodle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Moodle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Moodle. If not, see . - -/** - * The manager for the OUSupSub Editor. - * - * @module moodle-editor_ousupsub-editor - * @submodule manager - * @package editor_ousupsub - * @copyright 2014 Andrew Nicols - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @main moodle-editor_ousupsub-editor - */ - -/** - * @module moodle-editor_ousupsub-editor - */ - -/** - * The manager for the OUSupSub editor. - * - * @class editor_ousupsub - */ - -Y.M.editor_ousupsub = Y.M.editor_ousupsub || {}; - -/** - * List of editor_ousupsub instances. Intentionally placed on window.M, not - * something in the namespace, so we can be sure it is really global. - */ -M = M || {}; -M.editor_ousupsub = M.editor_ousupsub || {}; -M.editor_ousupsub._instances = M.editor_ousupsub._instances || {}; - -/** - * Add a reference to an editor. - * Note: This is an internal method which should only be called by the editor itself. - * - * @method addEditorReference - * @param {String} name The name of the editor instance to add - * @private - */ -Y.M.editor_ousupsub.addEditorReference = function(name, reference) { - if (typeof M.editor_ousupsub._instances[name] === 'undefined') { - M.editor_ousupsub._instances[name] = reference; - } else { - } - - return Y.M.editor_ousupsub; -}; - -/** - * Create a new editor using simple options. - * - * @method createEditor - * @param {String} id of the textarea to turn into an editor. - * @param {String} type 'superscript', 'subscript' or 'both'. - * @return {M.editor_ousupsub.Editor} The newly created editor instance - */ -Y.M.editor_ousupsub.createEditorSimple = function(id, type) { - var plugins = []; - if (type === 'both' || type === 'superscript') { - plugins.push({"name": "superscript", "params": []}); - } - if (type === 'both' || type === 'subscript') { - plugins.push({"name": "subscript", "params": []}); - } - - Y.M.editor_ousupsub.createEditor( - {"elementid" : id, "content_css" : "", "contextid" : 0, "language" : "en", - "directionality" : "ltr", "plugins" : [{"group" : "style1", "plugins" : plugins}],"pageHash" : ""}); -}; - -/** - * Create a new editor using the specified configuration. - * - * @method createEditor - * @param {Object} config See the attributes for {{#crossLink - * "M.editor_ousupsub.Editor"}}{{/crossLink}} for configuration options. The - * elementid provided will be used as the name of this editor within - * the editor Manager. - * @return {M.editor_ousupsub.Editor} The newly created editor instance - */ -Y.M.editor_ousupsub.createEditor = function(config) { - - var instance = new Y.M.editor_ousupsub.Editor(config); - Y.M.editor_ousupsub.fire('editor_ousupsub:created', { - id: instance.get('elementid'), - instance: instance - }); - return instance; -}; - -/** - * Get the requested Editor instance. - * - * @method getEditor - * @param {String} name The name of the editor instance to retrieve - * @return {M.editor_ousupsub.Editor} The requested editor instance - */ -Y.M.editor_ousupsub.getEditor = function(name) { - return M.editor_ousupsub._instances[name]; -}; - -/** - * Remove the reference for an editor. - * - * @method removeEditorReference - * @param {String} name The name of the editor instance to remove - */ -Y.M.editor_ousupsub.removeEditor = function(name) { - var instance = Y.M.editor_ousupsub.getEditor(name); - if (instance) { - instance.destroy(); - this.fire('editor_ousupsub:removed', { - id: name - }); - } - return Y.M.editor_ousupsub; -}; - -/** - * Remove the reference for an editor. - * Note: This is an internal method which should only be called by the editor itself. - * - * @method removeEditorReference - * @param {String} name The name of the editor instance to remove - * @private - */ -Y.M.editor_ousupsub.removeEditorReference = function(name) { - if (Y.M.editor_ousupsub.getEditor(name)) { - delete M.editor_ousupsub._instances[name]; - } -}; - -/** - * Add the supplied function to the manager using the specified name. - * - * @method addMethod - * @param {String} name The name to store the method on within the editor manager. - * @param {Function} fn The function to be added. - * @param {Object} [context] The context to apply the function with. If not specified, the Editor itself is used. - */ -Y.M.editor_ousupsub.addMethod = function(name, fn) { - if (typeof this[name] !== 'undefined') { - } - - Y.M.editor_ousupsub[name] = function() { - var ret = [], - args = arguments; - - Y.Object.each(M.editor_ousupsub._instances, function(editor) { - var result = fn.apply(editor, args); - - if (result !== undefined && result !== editor) { - ret[ret.length] = result; - } - }); - - // If we received a set of results, return them, otherwise make this method chainable. - return ret.length ? ret : this; - }; -}; - -Y.augment(Y.M.editor_ousupsub, Y.EventTarget); - -Y.Array.each(['saveSelection', 'updateFromTextArea', 'updateOriginal', 'cleanEditorHTML', 'destroy'], function(name) { - Y.M.editor_ousupsub.addMethod(name, Y.M.editor_ousupsub.Editor.prototype[name]); -}); - - -}, '@VERSION@', {"requires": ["base", "node", "event", "event-custom", "moodle-editor_ousupsub-rangy"]}); diff --git a/yui/build/moodle-editor_ousupsub-rangy/moodle-editor_ousupsub-rangy-debug.js b/yui/build/moodle-editor_ousupsub-rangy/moodle-editor_ousupsub-rangy-debug.js deleted file mode 100644 index 9667943..0000000 --- a/yui/build/moodle-editor_ousupsub-rangy/moodle-editor_ousupsub-rangy-debug.js +++ /dev/null @@ -1,8087 +0,0 @@ -/** - * Rangy, a cross-browser JavaScript range and selection library - * https://github.com/timdown/rangy - * - * Copyright 2022, Tim Down - * Licensed under the MIT license. - * Version: 1.3.1 - * Build date: 17 August 2022 - */ - -(function(factory, root) { - // No AMD or CommonJS support so we place Rangy in (probably) the global variable - root.rangy = factory(); -})(function() { - - var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; - - // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START - // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113. - var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", - "commonAncestorContainer"]; - - // Minimal set of methods required for DOM Level 2 Range compliance - 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", "moveToElementText", "parentElement", "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); - } - - function getBody(doc) { - return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; - } - - var forEach = [].forEach ? - function(arr, func) { - arr.forEach(func); - } : - function(arr, func) { - for (var i = 0, len = arr.length; i < len; ++i) { - func(arr[i], i); - } - }; - - var modules = {}; - - var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED); - - var util = { - isHostMethod: isHostMethod, - isHostObject: isHostObject, - isHostProperty: isHostProperty, - areHostMethods: areHostMethods, - areHostObjects: areHostObjects, - areHostProperties: areHostProperties, - isTextRange: isTextRange, - getBody: getBody, - forEach: forEach - }; - - var api = { - version: "1.3.1", - initialized: false, - isBrowser: isBrowser, - supported: true, - util: util, - features: {}, - modules: modules, - config: { - alertOnFail: false, - alertOnWarn: false, - preferTextRange: false, - autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize - } - }; - - function consoleLog(msg) { - if (typeof console != UNDEFINED && isHostMethod(console, "log")) { - console.log(msg); - } - } - - function alertOrLog(msg, shouldAlert) { - if (isBrowser && shouldAlert) { - alert(msg); - } else { - consoleLog(msg); - } - } - - function fail(reason) { - api.initialized = true; - api.supported = false; - alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail); - } - - api.fail = fail; - - function warn(msg) { - alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn); - } - - api.warn = warn; - - // Add utility extend() method - var extend; - if ({}.hasOwnProperty) { - util.extend = extend = function(obj, props, deep) { - var o, p; - for (var i in props) { - if (props.hasOwnProperty(i)) { - o = obj[i]; - p = props[i]; - if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") { - extend(o, p, true); - } - obj[i] = p; - } - } - // Special case for toString, which does not show up in for...in loops in IE <= 8 - if (props.hasOwnProperty("toString")) { - obj.toString = props.toString; - } - return obj; - }; - - util.createOptions = function(optionsParam, defaults) { - var options = {}; - extend(options, defaults); - if (optionsParam) { - extend(options, optionsParam); - } - return options; - }; - } else { - fail("hasOwnProperty not supported"); - } - - // Test whether we're in a browser and bail out if not - if (!isBrowser) { - fail("Rangy can only run in a browser"); - } - - // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not - (function() { - var toArray; - - if (isBrowser) { - var el = document.createElement("div"); - el.appendChild(document.createElement("span")); - var slice = [].slice; - try { - if (slice.call(el.childNodes, 0)[0].nodeType == 1) { - toArray = function(arrayLike) { - return slice.call(arrayLike, 0); - }; - } - } catch (e) {} - } - - if (!toArray) { - toArray = function(arrayLike) { - var arr = []; - for (var i = 0, len = arrayLike.length; i < len; ++i) { - arr[i] = arrayLike[i]; - } - return arr; - }; - } - - util.toArray = toArray; - })(); - - // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or - // normalization of event properties because we don't need this. - var addListener; - if (isBrowser) { - if (isHostMethod(document, "addEventListener")) { - addListener = function(obj, eventType, listener) { - obj.addEventListener(eventType, listener, false); - }; - } else if (isHostMethod(document, "attachEvent")) { - addListener = function(obj, eventType, listener) { - obj.attachEvent("on" + eventType, listener); - }; - } else { - fail("Document does not have required addEventListener or attachEvent method"); - } - - util.addListener = addListener; - } - - var initListeners = []; - - function getErrorDesc(ex) { - return ex.message || ex.description || String(ex); - } - - // Initialization - function init() { - if (!isBrowser || 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; - } - } - - var body = getBody(document); - if (!body || body.nodeName.toLowerCase() != "body") { - fail("No body element found"); - return; - } - - if (body && isHostMethod(body, "createTextRange")) { - testRange = body.createTextRange(); - if (isTextRange(testRange)) { - implementsTextRange = true; - } - } - - if (!implementsDomRange && !implementsTextRange) { - fail("Neither Range nor TextRange are available"); - return; - } - - api.initialized = true; - api.features = { - implementsDomRange: implementsDomRange, - implementsTextRange: implementsTextRange - }; - - // Initialize modules - var module, errorMessage; - for (var moduleName in modules) { - if ( (module = modules[moduleName]) instanceof Module ) { - module.init(module, api); - } - } - - // Call init listeners - for (var i = 0, len = initListeners.length; i < len; ++i) { - try { - initListeners[i](api); - } catch (ex) { - errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex); - consoleLog(errorMessage); - } - } - } - - function deprecationNotice(deprecated, replacement, module) { - if (module) { - deprecated += " in module " + module.name; - } - api.warn("DEPRECATED: " + deprecated + " is deprecated. Please use " + - replacement + " instead."); - } - - function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) { - owner[deprecated] = function() { - deprecationNotice(deprecated, replacement, module); - return owner[replacement].apply(owner, util.toArray(arguments)); - }; - } - - util.deprecationNotice = deprecationNotice; - util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod; - - // 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 shimListeners = []; - - api.addShimListener = function(listener) { - shimListeners.push(listener); - }; - - function shim(win) { - win = win || window; - init(); - - // Notify listeners - for (var i = 0, len = shimListeners.length; i < len; ++i) { - shimListeners[i](win); - } - } - - if (isBrowser) { - api.shim = api.createMissingNativeApi = shim; - createAliasForDeprecatedMethod(api, "createMissingNativeApi", "shim"); - } - - function Module(name, dependencies, initializer) { - this.name = name; - this.dependencies = dependencies; - this.initialized = false; - this.supported = false; - this.initializer = initializer; - } - - Module.prototype = { - init: function() { - var requiredModuleNames = this.dependencies || []; - for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) { - moduleName = requiredModuleNames[i]; - - requiredModule = modules[moduleName]; - if (!requiredModule || !(requiredModule instanceof Module)) { - throw new Error("required module '" + moduleName + "' not found"); - } - - requiredModule.init(); - - if (!requiredModule.supported) { - throw new Error("required module '" + moduleName + "' not supported"); - } - } - - // Now run initializer - this.initializer(this); - }, - - fail: function(reason) { - this.initialized = true; - this.supported = false; - throw new Error(reason); - }, - - warn: function(msg) { - api.warn("Module " + this.name + ": " + msg); - }, - - deprecationNotice: function(deprecated, replacement) { - api.warn("DEPRECATED: " + deprecated + " in module " + this.name + " is deprecated. Please use " + - replacement + " instead"); - }, - - createError: function(msg) { - return new Error("Error in Rangy " + this.name + " module: " + msg); - } - }; - - function createModule(name, dependencies, initFunc) { - var newModule = new Module(name, dependencies, function(module) { - if (!module.initialized) { - module.initialized = true; - try { - initFunc(api, module); - module.supported = true; - } catch (ex) { - var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex); - consoleLog(errorMessage); - if (ex.stack) { - consoleLog(ex.stack); - } - } - } - }); - modules[name] = newModule; - return newModule; - } - - api.createModule = function(name) { - // Allow 2 or 3 arguments (second argument is an optional array of dependencies) - var initFunc, dependencies; - if (arguments.length == 2) { - initFunc = arguments[1]; - dependencies = []; - } else { - initFunc = arguments[2]; - dependencies = arguments[1]; - } - - var module = createModule(name, dependencies, initFunc); - - // Initialize the module immediately if the core is already initialized - if (api.initialized && api.supported) { - module.init(); - } - }; - - api.createCoreModule = function(name, dependencies, initFunc) { - createModule(name, dependencies, initFunc); - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately - - function RangePrototype() {} - api.RangePrototype = RangePrototype; - api.rangePrototype = new RangePrototype(); - - function SelectionPrototype() {} - api.selectionPrototype = new SelectionPrototype(); - - /*----------------------------------------------------------------------------------------------------------------*/ - - // DOM utility methods used by Rangy - api.createCoreModule("DomUtil", [], function(api, module) { - var UNDEF = "undefined"; - var util = api.util; - var getBody = util.getBody; - - // 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) { - switch (node.nodeType) { - case 7: - case 10: - return 0; - case 3: - case 8: - return node.length; - default: - return node.childNodes.length; - } - } - - 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 isOrIsAncestorOf(ancestor, descendant) { - return isAncestorOf(ancestor, descendant, true); - } - - 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 isTextOrCommentNode(node) { - if (!node) { - return false; - } - var t = node.nodeType; - return t == 3 || t == 8 ; // Text 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, positionsToPreserve) { - var newNode = node.cloneNode(false); - newNode.deleteData(0, index); - node.deleteData(index, node.length - index); - insertAfter(newNode, node); - - // Preserve positions - if (positionsToPreserve) { - for (var i = 0, position; position = positionsToPreserve[i++]; ) { - // Handle case where position was inside the portion of node after the split point - if (position.node == node && position.offset > index) { - position.node = newNode; - position.offset -= index; - } - // Handle the case where the position is a node offset within node's parent - else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) { - ++position.offset; - } - } - } - 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 module.createError("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 module.createError("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 module.createError("getIframeDocument: 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 module.createError("getIframeWindow: No Window object found for iframe element"); - } - } - - // This looks bad. Is it worth it? - function isWindow(obj) { - return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document"); - } - - function getContentDocument(obj, module, methodName) { - var doc; - - if (!obj) { - doc = document; - } - - // Test if a DOM node has been passed and obtain a document object for it if so - else if (util.isHostProperty(obj, "nodeType")) { - doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ? - getIframeDocument(obj) : getDocument(obj); - } - - // Test if the doc parameter appears to be a Window object - else if (isWindow(obj)) { - doc = obj.document; - } - - if (!doc) { - throw module.createError(methodName + "(): Parameter must be a Window object or DOM node"); - } - - return doc; - } - - 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 { - root = getCommonAncestor(nodeA, nodeB); - if (!root) { - throw new Error("comparePoints error: nodes have no common ancestor"); - } - - // Case 4: containers are siblings or descendants of siblings - 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 module.createError("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; - } - } - } - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried - var crashyTextNodes = false; - - function isBrokenNode(node) { - var n; - try { - n = node.parentNode; - return false; - } catch (e) { - return true; - } - } - - (function() { - var el = document.createElement("b"); - el.innerHTML = "1"; - var textNode = el.firstChild; - el.innerHTML = "
"; - crashyTextNodes = isBrokenNode(textNode); - - api.features.crashyTextNodes = crashyTextNodes; - })(); - - /*----------------------------------------------------------------------------------------------------------------*/ - - function inspectNode(node) { - if (!node) { - return "[No node]"; - } - if (crashyTextNodes && isBrokenNode(node)) { - return "[Broken node]"; - } - if (isCharacterDataNode(node)) { - return '"' + node.data + '"'; - } - if (node.nodeType == 1) { - var idAttr = node.id ? ' id="' + node.id + '"' : ""; - return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; - } - return node.nodeName; - } - - function fragmentFromNodeChildren(node) { - var fragment = getDocument(node).createDocumentFragment(), child; - while ( (child = node.firstChild) ) { - fragment.appendChild(child); - } - return fragment; - } - - var getComputedStyleProperty; - if (typeof window.getComputedStyle != UNDEF) { - getComputedStyleProperty = function(el, propName) { - return getWindow(el).getComputedStyle(el, null)[propName]; - }; - } else if (typeof document.documentElement.currentStyle != UNDEF) { - getComputedStyleProperty = function(el, propName) { - return el.currentStyle ? el.currentStyle[propName] : ""; - }; - } else { - module.fail("No means of obtaining computed style properties found"); - } - - function createTestElement(doc, html, contentEditable) { - var body = getBody(doc); - var el = doc.createElement("div"); - el.contentEditable = "" + !!contentEditable; - if (html) { - el.innerHTML = html; - } - - // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292) - var bodyFirstChild = body.firstChild; - if (bodyFirstChild) { - body.insertBefore(el, bodyFirstChild); - } else { - body.appendChild(el); - } - - return el; - } - - function removeNode(node) { - return node.parentNode.removeChild(node); - } - - 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); - } - - function DomPosition(node, offset) { - this.node = node; - this.offset = offset; - } - - DomPosition.prototype = { - equals: function(pos) { - return !!pos && this.node === pos.node && this.offset == pos.offset; - }, - - inspect: function() { - return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; - }, - - toString: function() { - return this.inspect(); - } - }; - - 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, - INVALID_NODE_TYPE_ERR: 24 - }; - - DOMException.prototype.toString = function() { - return this.message; - }; - - api.dom = { - arrayContains: arrayContains, - isHtmlNamespace: isHtmlNamespace, - parentElement: parentElement, - getNodeIndex: getNodeIndex, - getNodeLength: getNodeLength, - getCommonAncestor: getCommonAncestor, - isAncestorOf: isAncestorOf, - isOrIsAncestorOf: isOrIsAncestorOf, - getClosestAncestorIn: getClosestAncestorIn, - isCharacterDataNode: isCharacterDataNode, - isTextOrCommentNode: isTextOrCommentNode, - insertAfter: insertAfter, - splitDataNode: splitDataNode, - getDocument: getDocument, - getWindow: getWindow, - getIframeWindow: getIframeWindow, - getIframeDocument: getIframeDocument, - getBody: getBody, - isWindow: isWindow, - getContentDocument: getContentDocument, - getRootContainer: getRootContainer, - comparePoints: comparePoints, - isBrokenNode: isBrokenNode, - inspectNode: inspectNode, - getComputedStyleProperty: getComputedStyleProperty, - createTestElement: createTestElement, - removeNode: removeNode, - fragmentFromNodeChildren: fragmentFromNodeChildren, - createIterator: createIterator, - DomPosition: DomPosition - }; - - api.DOMException = DOMException; - }); - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Pure JavaScript implementation of DOM Range - api.createCoreModule("DomRange", ["DomUtil"], function(api, module) { - var dom = api.dom; - var util = api.util; - var DomPosition = dom.DomPosition; - var DOMException = api.DOMException; - - var isCharacterDataNode = dom.isCharacterDataNode; - var getNodeIndex = dom.getNodeIndex; - var isOrIsAncestorOf = dom.isOrIsAncestorOf; - var getDocument = dom.getDocument; - var comparePoints = dom.comparePoints; - var splitDataNode = dom.splitDataNode; - var getClosestAncestorIn = dom.getClosestAncestorIn; - var getNodeLength = dom.getNodeLength; - var arrayContains = dom.arrayContains; - var getRootContainer = dom.getRootContainer; - var crashyTextNodes = api.features.crashyTextNodes; - - var removeNode = dom.removeNode; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Utility functions - - function isNonTextPartiallySelected(node, range) { - return (node.nodeType != 3) && - (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer)); - } - - function getRangeDocument(range) { - return range.document || getDocument(range.startContainer); - } - - function getRangeRoot(range) { - return getRootContainer(range.startContainer); - } - - function getBoundaryBeforeNode(node) { - return new DomPosition(node.parentNode, getNodeIndex(node)); - } - - function getBoundaryAfterNode(node) { - return new DomPosition(node.parentNode, getNodeIndex(node) + 1); - } - - function insertNodeAtPosition(node, n, o) { - var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; - if (isCharacterDataNode(n)) { - if (o == n.length) { - dom.insertAfter(node, n); - } else { - n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o)); - } - } else if (o >= n.childNodes.length) { - n.appendChild(node); - } else { - n.insertBefore(node, n.childNodes[o]); - } - return firstNodeInserted; - } - - function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) { - assertRangeValid(rangeA); - assertRangeValid(rangeB); - - if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) { - throw new DOMException("WRONG_DOCUMENT_ERR"); - } - - var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset), - endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset); - - return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; - } - - 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(); - } - - 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(); ) { - if (rangeIterator.isPartiallySelectedSubtree()) { - if (func(node) === false) { - iteratorState.stop = true; - return; - } else { - // 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. - subRangeIterator = rangeIterator.getSubtreeIterator(); - iterateSubtree(subRangeIterator, func, iteratorState); - subRangeIterator.detach(); - if (iteratorState.stop) { - return; - } - } - } else { - // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its - // descendants - 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(); - } 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(); - } else { - iterator.remove(); - } - if (node.nodeType == 10) { // DocumentType - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - frag.appendChild(node); - } - return frag; - } - - function getNodesInRange(range, nodeTypes, filter) { - 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)) { - return; - } - if (filterExists && !filter(node)) { - return; - } - // Don't include a boundary container if it is a character data node and the range does not contain any - // of its character data. See issue 190. - var sc = range.startContainer; - if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) { - return; - } - - var ec = range.endContainer; - if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) { - return; - } - - 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) - - 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 && isCharacterDataNode(this.sc)) { - this.isSingleCharacterDataNode = true; - this._first = this._last = this._next = this.sc; - } else { - this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ? - this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true); - this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ? - this.ec.childNodes[this.eo - 1] : 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 (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 (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) { - removeNode(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(false); - } else { - subRange = new Range(getRangeDocument(this.range)); - var current = this._current; - var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current); - - if (isOrIsAncestorOf(current, this.sc)) { - startContainer = this.sc; - startOffset = this.so; - } - if (isOrIsAncestorOf(current, this.ec)) { - endContainer = this.ec; - endOffset = this.eo; - } - - updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); - } - return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); - }, - - detach: function() { - this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = 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 (arrayContains(nodeTypes, t)) { - return n; - } - n = n.parentNode; - } - return null; - }; - } - - var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); - var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); - var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); - var getElementAncestor = createAncestorFinder( [1] ); - - function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { - if (getDocTypeNotationEntityAncestor(node, allowSelf)) { - throw new DOMException("INVALID_NODE_TYPE_ERR"); - } - } - - function assertValidNodeType(node, invalidTypes) { - if (!arrayContains(invalidTypes, node.nodeType)) { - throw new DOMException("INVALID_NODE_TYPE_ERR"); - } - } - - function assertValidOffset(node, offset) { - if (offset < 0 || offset > (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 isValidOffset(node, offset) { - return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length); - } - - function isRangeValid(range) { - return (!!range.startContainer && !!range.endContainer && - !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) && - getRootContainer(range.startContainer) == getRootContainer(range.endContainer) && - isValidOffset(range.startContainer, range.startOffset) && - isValidOffset(range.endContainer, range.endOffset)); - } - - function assertRangeValid(range) { - if (!isRangeValid(range)) { - throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: (" + 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); // Pre-Blink 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 = 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 (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(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) { - var doc = getRangeDocument(this); - var el = doc.createElement("body"); - el.innerHTML = fragmentStr; - - return dom.fragmentFromNodeChildren(el); - }; - - function splitRangeBoundaries(range, positionsToPreserve) { - assertRangeValid(range); - - var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset; - var startEndSame = (sc === ec); - - if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { - splitDataNode(ec, eo, positionsToPreserve); - } - - if (isCharacterDataNode(sc) && so > 0 && so < sc.length) { - sc = splitDataNode(sc, so, positionsToPreserve); - if (startEndSame) { - eo -= so; - ec = sc; - } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) { - eo++; - } - so = 0; - } - range.setStartAndEnd(sc, so, ec, eo); - } - - function rangeToHtml(range) { - assertRangeValid(range); - var container = range.commonAncestorContainer.parentNode.cloneNode(false); - container.appendChild( range.cloneContents() ); - return container.innerHTML; - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - 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; - - util.extend(api.rangePrototype, { - 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 comparePoints(nodeA, offsetA, nodeB, offsetB); - }, - - insertNode: function(node) { - assertRangeValid(this); - assertValidNodeType(node, insertableNodeTypes); - assertNodeNotReadOnly(this.startContainer); - - if (isOrIsAncestorOf(node, this.startContainer)) { - 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 && 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 DOMException("INVALID_STATE_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 && isCharacterDataNode(sc)) { - return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; - } else { - var textParts = [], iterator = new RangeIterator(this, true); - iterateSubtree(iterator, function(node) { - // Accept only text or CDATA nodes, not comments - if (node.nodeType == 3 || node.nodeType == 4) { - textParts.push(node.data); - } - }); - iterator.detach(); - return textParts.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 = 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 (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { - return -1; - } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { - return 1; - } - return 0; - }, - - createContextualFragment: createContextualFragment, - - toHtml: function() { - return rangeToHtml(this); - }, - - // 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); - if (getRootContainer(node) != getRangeRoot(this)) { - return false; - } - - var parent = node.parentNode, offset = getNodeIndex(node); - if (!parent) { - return true; - } - - var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset), - endComparison = 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 (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && - (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) { - return rangesIntersect(this, range, false); - }, - - // Sharing a boundary start-to-end or end-to-start does count as intersection. - intersectsOrTouchesRange: function(range) { - return rangesIntersect(this, range, true); - }, - - intersection: function(range) { - if (this.intersectsRange(range)) { - var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), - endComparison = 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.intersectsOrTouchesRange(range)) { - var unionRange = this.cloneRange(); - if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { - unionRange.setStart(range.startContainer, range.startOffset); - } - if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { - unionRange.setEnd(range.endContainer, range.endOffset); - } - return unionRange; - } else { - throw new DOMException("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, getNodeLength(node)) <= 0; - }, - - containsRange: function(range) { - var intersection = this.intersection(range); - return intersection !== null && range.equals(intersection); - }, - - 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); - return this.containsRange(nodeRange); - } else { - return this.containsNodeContents(node); - } - }, - - getNodes: function(nodeTypes, filter) { - assertRangeValid(this); - return getNodesInRange(this, nodeTypes, filter); - }, - - getDocument: function() { - return getRangeDocument(this); - }, - - collapseBefore: function(node) { - this.setEndBefore(node); - this.collapse(false); - }, - - collapseAfter: function(node) { - this.setStartAfter(node); - this.collapse(true); - }, - - getBookmark: function(containerNode) { - var doc = getRangeDocument(this); - var preSelectionRange = api.createRange(doc); - containerNode = containerNode || dom.getBody(doc); - preSelectionRange.selectNodeContents(containerNode); - var range = this.intersection(preSelectionRange); - var start = 0, end = 0; - if (range) { - preSelectionRange.setEnd(range.startContainer, range.startOffset); - start = preSelectionRange.toString().length; - end = start + range.toString().length; - } - - return { - start: start, - end: end, - containerNode: containerNode - }; - }, - - moveToBookmark: function(bookmark) { - var containerNode = bookmark.containerNode; - var charIndex = 0; - this.setStart(containerNode, 0); - this.collapse(true); - var nodeStack = [containerNode], node, foundStart = false, stop = false; - var nextCharIndex, i, childNodes; - - while (!stop && (node = nodeStack.pop())) { - if (node.nodeType == 3) { - nextCharIndex = charIndex + node.length; - if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) { - this.setStart(node, bookmark.start - charIndex); - foundStart = true; - } - if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) { - this.setEnd(node, bookmark.end - charIndex); - stop = true; - } - charIndex = nextCharIndex; - } else { - childNodes = node.childNodes; - i = childNodes.length; - while (i--) { - nodeStack.push(childNodes[i]); - } - } - } - }, - - getName: function() { - return "DomRange"; - }, - - equals: function(range) { - return Range.rangesEqual(this, range); - }, - - isValid: function() { - return isRangeValid(this); - }, - - inspect: function() { - return inspect(this); - }, - - detach: function() { - // In DOM4, detach() is now a no-op. - } - }); - - 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 = 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) { - function createBeforeAfterNodeSetter(isBefore, isStart) { - return function(node) { - 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) || 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) || comparePoints(node, offset, sc, so) == -1) { - sc = node; - so = offset; - } - boundaryUpdater(range, sc, so, node, offset); - } - } - - // Set up inheritance - var F = function() {}; - F.prototype = api.rangePrototype; - constructor.prototype = new F(); - - util.extend(constructor.prototype, { - setStart: function(node, offset) { - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeStart(this, node, offset); - }, - - setEnd: function(node, offset) { - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeEnd(this, node, offset); - }, - - /** - * Convenience method to set a range's start and end boundaries. Overloaded as follows: - * - Two parameters (node, offset) creates a collapsed range at that position - * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at - * startOffset and ending at endOffset - * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in - * startNode and ending at endOffset in endNode - */ - setStartAndEnd: function() { - var args = arguments; - var sc = args[0], so = args[1], ec = sc, eo = so; - - switch (args.length) { - case 3: - eo = args[2]; - break; - case 4: - ec = args[2]; - eo = args[3]; - break; - } - - assertNoDocTypeNotationEntityAncestor(sc, true); - assertValidOffset(sc, so); - - assertNoDocTypeNotationEntityAncestor(ec, true); - assertValidOffset(ec, eo); - - boundaryUpdater(this, sc, so, ec, eo); - }, - - setBoundary: function(node, offset, isStart) { - this["set" + (isStart ? "Start" : "End")](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) { - assertNoDocTypeNotationEntityAncestor(node, true); - - boundaryUpdater(this, node, 0, node, getNodeLength(node)); - }, - - selectNode: function(node) { - 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; - }, - - splitBoundaries: function() { - splitRangeBoundaries(this); - }, - - splitBoundariesPreservingPositions: function(positionsToPreserve) { - splitRangeBoundaries(this, positionsToPreserve); - }, - - 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); - removeNode(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); - removeNode(sibling); - if (sc == ec) { - eo += so; - ec = sc; - } else if (ec == node.parentNode) { - var nodeIndex = getNodeIndex(node); - if (eo == nodeIndex) { - ec = node; - eo = nodeLength; - } else if (eo > nodeIndex) { - eo--; - } - } - } - }; - - var normalizeStart = true; - var sibling; - - if (isCharacterDataNode(ec)) { - if (eo == ec.length) { - mergeForward(ec); - } else if (eo == 0) { - sibling = ec.previousSibling; - if (sibling && sibling.nodeType == ec.nodeType) { - eo = sibling.length; - if (sc == ec) { - normalizeStart = false; - } - sibling.appendData(ec.data); - removeNode(ec); - ec = sibling; - } - } - } else { - if (eo > 0) { - var endNode = ec.childNodes[eo - 1]; - if (endNode && isCharacterDataNode(endNode)) { - mergeForward(endNode); - } - } - normalizeStart = !this.collapsed; - } - - if (normalizeStart) { - if (isCharacterDataNode(sc)) { - if (so == 0) { - mergeBackward(sc); - } else if (so == sc.length) { - sibling = sc.nextSibling; - if (sibling && sibling.nodeType == sc.nodeType) { - if (ec == sibling) { - ec = sc; - eo += sc.length; - } - sc.appendData(sibling.data); - removeNode(sibling); - } - } - } else { - if (so < sc.childNodes.length) { - var startNode = sc.childNodes[so]; - if (startNode && isCharacterDataNode(startNode)) { - mergeBackward(startNode); - } - } - } - } else { - sc = ec; - so = eo; - } - - boundaryUpdater(this, sc, so, ec, eo); - }, - - collapseToPoint: function(node, offset) { - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - this.setStartAndEnd(node, offset); - }, - - parentElement: function() { - assertRangeValid(this); - var parentNode = this.commonAncestorContainer; - return parentNode ? getElementAncestor(this.commonAncestorContainer, true) : null; - } - }); - - 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) { - range.startContainer = startContainer; - range.startOffset = startOffset; - range.endContainer = endContainer; - range.endOffset = endOffset; - range.document = dom.getDocument(startContainer); - updateCollapsedAndCommonAncestor(range); - } - - function Range(doc) { - updateBoundaries(this, doc, 0, doc, 0); - } - - createPrototypeRange(Range, updateBoundaries); - - util.extend(Range, { - rangeProperties: rangeProperties, - RangeIterator: RangeIterator, - copyComparisonConstants: copyComparisonConstants, - createPrototypeRange: createPrototypeRange, - inspect: inspect, - toHtml: rangeToHtml, - getRangeDocument: getRangeDocument, - rangesEqual: function(r1, r2) { - return r1.startContainer === r2.startContainer && - r1.startOffset === r2.startOffset && - r1.endContainer === r2.endContainer && - r1.endOffset === r2.endOffset; - } - }); - - api.DomRange = Range; - }); - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Wrappers for the browser's native DOM Range and/or TextRange implementation - api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { - var WrappedRange, WrappedTextRange; - var dom = api.dom; - var util = api.util; - var DomPosition = dom.DomPosition; - var DomRange = api.DomRange; - var getBody = dom.getBody; - var getContentDocument = dom.getContentDocument; - var isCharacterDataNode = dom.isCharacterDataNode; - - - /*----------------------------------------------------------------------------------------------------------------*/ - - if (api.features.implementsDomRange) { - // 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; - - function updateRangeProperties(range) { - var i = rangeProperties.length, prop; - while (i--) { - prop = rangeProperties[i]; - range[prop] = range.nativeRange[prop]; - } - // Fix for broken collapsed property in IE 9. - range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); - } - - function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) { - var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset); - var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset); - var nativeRangeDifferent = !range.equals(range.nativeRange); - - // Always set both boundaries for the benefit of IE9 (see issue 35) - if (startMoved || endMoved || nativeRangeDifferent) { - range.setEnd(endContainer, endOffset); - range.setStart(startContainer, startOffset); - } - } - - var createBeforeAfterNodeSetter; - - WrappedRange = function(range) { - if (!range) { - throw module.createError("WrappedRange: Range must be specified"); - } - this.nativeRange = range; - updateRangeProperties(this); - }; - - DomRange.createPrototypeRange(WrappedRange, updateNativeRange); - - rangeProto = WrappedRange.prototype; - - rangeProto.selectNode = function(node) { - this.nativeRange.selectNode(node); - updateRangeProperties(this); - }; - - rangeProto.cloneContents = function() { - return this.nativeRange.cloneContents(); - }; - - // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect, - // insertNode() is never delegated to the native range. - - 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"); - 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); - - 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) { - - 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"); - - /*--------------------------------------------------------------------------------------------------------*/ - - // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing - // whether the native implementation can be trusted - rangeProto.selectNodeContents = function(node) { - this.setStartAndEnd(node, 0, dom.getNodeLength(node)); - }; - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for and correct WebKit bug that has the behaviour 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 IE deleteContents() and extractContents() bug and correct it. See issue 107. - - var el = document.createElement("div"); - el.innerHTML = "123"; - var textNode = el.firstChild; - var body = getBody(document); - body.appendChild(el); - - range.setStart(textNode, 1); - range.setEnd(textNode, 2); - range.deleteContents(); - - if (textNode.data == "13") { - // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and - // extractContents() - rangeProto.deleteContents = function() { - this.nativeRange.deleteContents(); - updateRangeProperties(this); - }; - - rangeProto.extractContents = function() { - var frag = this.nativeRange.extractContents(); - updateRangeProperties(this); - return frag; - }; - } else { - } - - body.removeChild(el); - body = null; - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for existence of createContextualFragment and delegate to it if it exists - if (util.isHostMethod(range, "createContextualFragment")) { - rangeProto.createContextualFragment = function(fragmentStr) { - return this.nativeRange.createContextualFragment(fragmentStr); - }; - } - - /*--------------------------------------------------------------------------------------------------------*/ - - // Clean up - getBody(document).removeChild(testTextNode); - - rangeProto.getName = function() { - return "WrappedRange"; - }; - - api.WrappedRange = WrappedRange; - - api.createNativeRange = function(doc) { - doc = getContentDocument(doc, module, "createNativeRange"); - return doc.createRange(); - }; - })(); - } - - if (api.features.implementsTextRange) { - /* - 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) - */ - var getTextRangeContainerElement = function(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); - }; - - var textRangeIsCollapsed = function(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. - var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) { - 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 - if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) { - 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) { - var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); - return { - boundaryPosition: pos, - nodeInfo: { - nodeIndex: pos.offset, - containerElement: pos.node - } - }; - } - - var workingNode = dom.getDocument(containerElement).createElement("span"); - - // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5 - // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64 - if (workingNode.parentNode) { - dom.removeNode(workingNode); - } - - var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; - var previousNode, nextNode, boundaryPosition, boundaryNode; - var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0; - var childNodeCount = containerElement.childNodes.length; - var end = childNodeCount; - - // Check end first. Code within the loop assumes that the endth child node of the container is definitely - // after the range boundary. - var nodeIndex = end; - - while (true) { - if (nodeIndex == childNodeCount) { - containerElement.appendChild(workingNode); - } else { - containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]); - } - workingRange.moveToElementText(workingNode); - comparison = workingRange.compareEndPoints(workingComparisonType, textRange); - if (comparison == 0 || start == end) { - break; - } else if (comparison == -1) { - if (end == start + 1) { - // We know the endth child node is after the range boundary, so we must be done. - break; - } else { - start = nodeIndex; - } - } else { - end = (end == start + 1) ? start : nodeIndex; - } - nodeIndex = Math.floor((start + end) / 2); - containerElement.removeChild(workingNode); - } - - - // 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 && 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 rendered 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 approach used 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 && isCharacterDataNode(nextNode)) {
-                        boundaryPosition = new DomPosition(nextNode, 0);
-                    } else if (previousNode && isCharacterDataNode(previousNode)) {
-                        boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
-                    } else {
-                        boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
-                    }
-                }
-
-                // Clean up
-                dom.removeNode(workingNode);
-
-                return {
-                    boundaryPosition: boundaryPosition,
-                    nodeInfo: {
-                        nodeIndex: nodeIndex,
-                        containerElement: containerElement
-                    }
-                };
-            };
-
-            // 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/)
-            var createBoundaryTextRange = function(boundaryPosition, isStart) {
-                var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
-                var doc = dom.getDocument(boundaryPosition.node);
-                var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
-                var nodeIsDataNode = 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
-                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;
-            };
-
-            /*------------------------------------------------------------------------------------------------------------*/
-
-            // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
-            // prototype
-
-            WrappedTextRange = function(textRange) {
-                this.textRange = textRange;
-                this.refresh();
-            };
-
-            WrappedTextRange.prototype = new DomRange(document);
-
-            WrappedTextRange.prototype.refresh = function() {
-                var start, end, startBoundary;
-
-                // 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).boundaryPosition;
-                } else {
-                    startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
-                    start = startBoundary.boundaryPosition;
-
-                    // An optimization used here is that if the start and end boundaries have the same parent element, the
-                    // search scope for the end boundary can be limited to exclude the portion of the element that precedes
-                    // the start boundary
-                    end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
-                        startBoundary.nodeInfo).boundaryPosition;
-                }
-
-                this.setStart(start.node, start.offset);
-                this.setEnd(end.node, end.offset);
-            };
-
-            WrappedTextRange.prototype.getName = function() {
-                return "WrappedTextRange";
-            };
-
-            DomRange.copyComparisonConstants(WrappedTextRange);
-
-            var rangeToTextRange = function(range) {
-                if (range.collapsed) {
-                    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 = getBody( DomRange.getRangeDocument(range) ).createTextRange();
-                    textRange.setEndPoint("StartToStart", startRange);
-                    textRange.setEndPoint("EndToEnd", endRange);
-                    return textRange;
-                }
-            };
-
-            WrappedTextRange.rangeToTextRange = rangeToTextRange;
-
-            WrappedTextRange.prototype.toTextRange = function() {
-                return rangeToTextRange(this);
-            };
-
-            api.WrappedTextRange = WrappedTextRange;
-
-            // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
-            // implementation to use by default.
-            if (!api.features.implementsDomRange || api.config.preferTextRange) {
-                // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
-                var globalObj = (function(f) { return f("return this;")(); })(Function);
-                if (typeof globalObj.Range == "undefined") {
-                    globalObj.Range = WrappedTextRange;
-                }
-
-                api.createNativeRange = function(doc) {
-                    doc = getContentDocument(doc, module, "createNativeRange");
-                    return getBody(doc).createTextRange();
-                };
-
-                api.WrappedRange = WrappedTextRange;
-            }
-        }
-
-        api.createRange = function(doc) {
-            doc = getContentDocument(doc, module, "createRange");
-            return new api.WrappedRange(api.createNativeRange(doc));
-        };
-
-        api.createRangyRange = function(doc) {
-            doc = getContentDocument(doc, module, "createRangyRange");
-            return new DomRange(doc);
-        };
-
-        util.createAliasForDeprecatedMethod(api, "createIframeRange", "createRange");
-        util.createAliasForDeprecatedMethod(api, "createIframeRangyRange", "createRangyRange");
-
-        api.addShimListener(function(win) {
-            var doc = win.document;
-            if (typeof doc.createRange == "undefined") {
-                doc.createRange = function() {
-                    return api.createRange(doc);
-                };
-            }
-            doc = win = null;
-        });
-    });
-
-    /*----------------------------------------------------------------------------------------------------------------*/
-
-    // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
-    // in the W3C Selection API spec (https://www.w3.org/TR/selection-api)
-    api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
-        api.config.checkSelectionRanges = true;
-
-        var BOOLEAN = "boolean";
-        var NUMBER = "number";
-        var dom = api.dom;
-        var util = api.util;
-        var isHostMethod = util.isHostMethod;
-        var DomRange = api.DomRange;
-        var WrappedRange = api.WrappedRange;
-        var DOMException = api.DOMException;
-        var DomPosition = dom.DomPosition;
-        var getNativeSelection;
-        var selectionIsCollapsed;
-        var features = api.features;
-        var CONTROL = "Control";
-        var getDocument = dom.getDocument;
-        var getBody = dom.getBody;
-        var rangesEqual = DomRange.rangesEqual;
-
-
-        // Utility function to support direction parameters in the API that may be a string ("backward", "backwards",
-        // "forward" or "forwards") or a Boolean (true for backwards).
-        function isDirectionBackward(dir) {
-            return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
-        }
-
-        function getWindow(win, methodName) {
-            if (!win) {
-                return window;
-            } else if (dom.isWindow(win)) {
-                return win;
-            } else if (win instanceof WrappedSelection) {
-                return win.win;
-            } else {
-                var doc = dom.getContentDocument(win, module, methodName);
-                return dom.getWindow(doc);
-            }
-        }
-
-        function getWinSelection(winParam) {
-            return getWindow(winParam, "getWinSelection").getSelection();
-        }
-
-        function getDocSelection(winParam) {
-            return getWindow(winParam, "getDocSelection").document.selection;
-        }
-
-        function winSelectionIsBackward(sel) {
-            var backward = false;
-            if (sel.anchorNode) {
-                backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
-            }
-            return backward;
-        }
-
-        // Test for the Range/TextRange and Selection features required
-        // Test for ability to retrieve selection
-        var implementsWinGetSelection = isHostMethod(window, "getSelection"),
-            implementsDocSelection = util.isHostObject(document, "selection");
-
-        features.implementsWinGetSelection = implementsWinGetSelection;
-        features.implementsDocSelection = implementsDocSelection;
-
-        var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
-
-        if (useDocumentSelection) {
-            getNativeSelection = getDocSelection;
-            api.isSelectionValid = function(winParam) {
-                var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
-
-                // Check whether the selection TextRange is actually contained within the correct document
-                return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
-            };
-        } else if (implementsWinGetSelection) {
-            getNativeSelection = getWinSelection;
-            api.isSelectionValid = function() {
-                return true;
-            };
-        } else {
-            module.fail("Neither document.selection or window.getSelection() detected.");
-            return false;
-        }
-
-        api.getNativeSelection = getNativeSelection;
-
-        var testSelection = getNativeSelection();
-
-        // In Firefox, the selection is null in an iframe with display: none. See issue #138.
-        if (!testSelection) {
-            module.fail("Native selection was null (possibly issue 138?)");
-            return false;
-        }
-
-        var testRange = api.createNativeRange(document);
-        var body = getBody(document);
-
-        // Obtaining a range from a selection
-        var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
-            ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
-
-        features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
-
-        // Test for existence of native selection extend() method
-        var selectionHasExtend = isHostMethod(testSelection, "extend");
-        features.selectionHasExtend = selectionHasExtend;
-
-        // Test for existence of native selection setBaseAndExtent() method
-        var selectionHasSetBaseAndExtent = isHostMethod(testSelection, "setBaseAndExtent");
-        features.selectionHasSetBaseAndExtent = selectionHasSetBaseAndExtent;
-
-        // Test if rangeCount exists
-        var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
-        features.selectionHasRangeCount = selectionHasRangeCount;
-
-        var selectionSupportsMultipleRanges = false;
-        var collapsedNonEditableSelectionsSupported = true;
-
-        var addRangeBackwardToNative = selectionHasExtend ?
-            function(nativeSelection, range) {
-                var doc = DomRange.getRangeDocument(range);
-                var endRange = api.createRange(doc);
-                endRange.collapseToPoint(range.endContainer, range.endOffset);
-                nativeSelection.addRange(getNativeRange(endRange));
-                nativeSelection.extend(range.startContainer, range.startOffset);
-            } : null;
-
-        if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
-                typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
-
-            (function() {
-                // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
-                // performed on the current document's selection. See issue 109.
-
-                // Note also that if a selection previously existed, it is wiped and later restored by these tests. This
-                // will result in the selection direction being reversed if the original selection was backwards and the
-                // browser does not support setting backwards selections (Internet Explorer, I'm looking at you).
-                var sel = window.getSelection();
-                if (sel) {
-                    // Store the current selection
-                    var originalSelectionRangeCount = sel.rangeCount;
-                    var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
-                    var originalSelectionRanges = [];
-                    var originalSelectionBackward = winSelectionIsBackward(sel);
-                    for (var i = 0; i < originalSelectionRangeCount; ++i) {
-                        originalSelectionRanges[i] = sel.getRangeAt(i);
-                    }
-
-                    // Create some test elements
-                    var testEl = dom.createTestElement(document, "", false);
-                    var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
-
-                    // Test whether the native selection will allow a collapsed selection within a non-editable element
-                    var r1 = document.createRange();
-
-                    r1.setStart(textNode, 1);
-                    r1.collapse(true);
-                    sel.removeAllRanges();
-                    sel.addRange(r1);
-                    collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
-                    sel.removeAllRanges();
-
-                    // Test whether the native selection is capable of supporting multiple ranges.
-                    if (!selectionHasMultipleRanges) {
-                        // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a
-                        // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's
-                        // nothing we can do about this while retaining the feature test so we have to resort to a browser
-                        // sniff. I'm not happy about it. See
-                        // https://code.google.com/p/chromium/issues/detail?id=399791
-                        var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /);
-                        if (chromeMatch && parseInt(chromeMatch[1]) >= 36) {
-                            selectionSupportsMultipleRanges = false;
-                        } else {
-                            var r2 = r1.cloneRange();
-                            r1.setStart(textNode, 0);
-                            r2.setEnd(textNode, 3);
-                            r2.setStart(textNode, 2);
-                            sel.addRange(r1);
-                            sel.addRange(r2);
-                            selectionSupportsMultipleRanges = (sel.rangeCount == 2);
-                        }
-                    }
-
-                    // Clean up
-                    dom.removeNode(testEl);
-                    sel.removeAllRanges();
-
-                    for (i = 0; i < originalSelectionRangeCount; ++i) {
-                        if (i == 0 && originalSelectionBackward) {
-                            if (addRangeBackwardToNative) {
-                                addRangeBackwardToNative(sel, originalSelectionRanges[i]);
-                            } else {
-                                api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend");
-                                sel.addRange(originalSelectionRanges[i]);
-                            }
-                        } else {
-                            sel.addRange(originalSelectionRanges[i]);
-                        }
-                    }
-                }
-            })();
-        }
-
-        features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
-        features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
-
-        // ControlRanges
-        var implementsControlRange = false, testControlRange;
-
-        if (body && isHostMethod(body, "createControlRange")) {
-            testControlRange = body.createControlRange();
-            if (util.areHostProperties(testControlRange, ["item", "add"])) {
-                implementsControlRange = true;
-            }
-        }
-        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, backward) {
-            var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "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;
-            updateType(sel);
-        }
-
-        function updateType(sel) {
-            sel.type = (sel.rangeCount == 0) ? "None" : (selectionIsCollapsed(sel) ? "Caret" : "Range");
-        }
-
-        function getNativeRange(range) {
-            var nativeRange;
-            if (range instanceof DomRange) {
-                nativeRange = api.createNativeRange(range.getDocument());
-                nativeRange.setEnd(range.endContainer, range.endOffset);
-                nativeRange.setStart(range.startContainer, range.startOffset);
-            } else if (range instanceof WrappedRange) {
-                nativeRange = range.nativeRange;
-            } else if (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 module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
-            }
-            return nodes[0];
-        }
-
-        // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
-        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;
-            updateType(sel);
-        }
-
-        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 = 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);
-                    updateType(sel);
-                }
-            }
-        }
-
-        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 = getDocument(controlRange.item(0));
-            var newControlRange = 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 module.createError("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 (isHostMethod(testSelection, "getRangeAt")) {
-            // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
-            // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
-            // lesson to us all, especially me.
-            getSelectionRangeAt = function(sel, index) {
-                try {
-                    return sel.getRangeAt(index);
-                } catch (ex) {
-                    return null;
-                }
-            };
-        } else if (selectionHasAnchorAndFocus) {
-            getSelectionRangeAt = function(sel) {
-                var doc = getDocument(sel.anchorNode);
-                var range = api.createRange(doc);
-                range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, 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.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
-                }
-
-                return range;
-            };
-        }
-
-        function WrappedSelection(selection, docSelection, win) {
-            this.nativeSelection = selection;
-            this.docSelection = docSelection;
-            this._ranges = [];
-            this.win = win;
-            this.refresh();
-        }
-
-        WrappedSelection.prototype = api.selectionPrototype;
-
-        function deleteProperties(sel) {
-            sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
-            sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
-            sel.detached = true;
-            updateType(sel);
-        }
-
-        var cachedRangySelections = [];
-
-        function actOnCachedSelection(win, action) {
-            var i = cachedRangySelections.length, cached, sel;
-            while (i--) {
-                cached = cachedRangySelections[i];
-                sel = cached.selection;
-                if (action == "deleteAll") {
-                    deleteProperties(sel);
-                } else if (cached.win == win) {
-                    if (action == "delete") {
-                        cachedRangySelections.splice(i, 1);
-                        return true;
-                    } else {
-                        return sel;
-                    }
-                }
-            }
-            if (action == "deleteAll") {
-                cachedRangySelections.length = 0;
-            }
-            return null;
-        }
-
-        var getSelection = function(win) {
-            // Check if the parameter is a Rangy Selection object
-            if (win && win instanceof WrappedSelection) {
-                win.refresh();
-                return win;
-            }
-
-            win = getWindow(win, "getNativeSelection");
-
-            var sel = actOnCachedSelection(win);
-            var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
-            if (sel) {
-                sel.nativeSelection = nativeSel;
-                sel.docSelection = docSel;
-                sel.refresh();
-            } else {
-                sel = new WrappedSelection(nativeSel, docSel, win);
-                cachedRangySelections.push( { win: win, selection: sel } );
-            }
-            return sel;
-        };
-
-        api.getSelection = getSelection;
-
-        util.createAliasForDeprecatedMethod(api, "getIframeSelection", "getSelection");
-
-        var selProto = WrappedSelection.prototype;
-
-        function createControlSelection(sel, ranges) {
-            // Ensure that the selection becomes of type "Control"
-            var doc = getDocument(ranges[0].startContainer);
-            var controlRange = getBody(doc).createControlRange();
-            for (var i = 0, el, len = ranges.length; i < len; ++i) {
-                el = getSingleElementFromRange(ranges[i]);
-                try {
-                    controlRange.add(el);
-                } catch (ex) {
-                    throw module.createError("setRanges(): Element within 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 addRangeBackward = function(sel, range) {
-                addRangeBackwardToNative(sel.nativeSelection, range);
-                sel.refresh();
-            };
-
-            if (selectionHasRangeCount) {
-                selProto.addRange = function(range, direction) {
-                    if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
-                        addRangeToControlSelection(this, range);
-                    } else {
-                        if (isDirectionBackward(direction) && selectionHasExtend) {
-                            addRangeBackward(this, range);
-                        } else {
-                            var previousRangeCount;
-                            if (selectionSupportsMultipleRanges) {
-                                previousRangeCount = this.rangeCount;
-                            } else {
-                                this.removeAllRanges();
-                                previousRangeCount = 0;
-                            }
-                            // Clone the native range so that changing the selected range does not affect the selection.
-                            // This is contrary to the spec but is the only way to achieve consistency between browsers. See
-                            // issue 80.
-                            var clonedNativeRange = getNativeRange(range).cloneRange();
-                            try {
-                                this.nativeSelection.addRange(clonedNativeRange);
-                            } catch (ex) {
-                            }
-
-                            // 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 && !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, selectionIsBackward(this.nativeSelection));
-                                this.isCollapsed = selectionIsCollapsed(this);
-                                updateType(this);
-                            } else {
-                                // The range was not added successfully. The simplest thing is to refresh
-                                this.refresh();
-                            }
-                        }
-                    }
-                };
-            } else {
-                selProto.addRange = function(range, direction) {
-                    if (isDirectionBackward(direction) && selectionHasExtend) {
-                        addRangeBackward(this, range);
-                    } else {
-                        this.nativeSelection.addRange(getNativeRange(range));
-                        this.refresh();
-                    }
-                };
-            }
-
-            selProto.setRanges = function(ranges) {
-                if (implementsControlRange && implementsDocSelection && 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 (isHostMethod(testSelection, "empty") && 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 = getDocument(this.anchorNode);
-                        } else if (this.docSelection.type == CONTROL) {
-                            var controlRange = this.docSelection.createRange();
-                            if (controlRange.length) {
-                                doc = getDocument( controlRange.item(0) );
-                            }
-                        }
-                        if (doc) {
-                            var textRange = getBody(doc).createTextRange();
-                            textRange.select();
-                            this.docSelection.empty();
-                        }
-                    }
-                } catch(ex) {}
-                updateEmptySelection(this);
-            };
-
-            selProto.addRange = function(range) {
-                if (this.docSelection.type == CONTROL) {
-                    addRangeToControlSelection(this, range);
-                } else {
-                    api.WrappedTextRange.rangeToTextRange(range).select();
-                    this._ranges[0] = range;
-                    this.rangeCount = 1;
-                    this.isCollapsed = this._ranges[0].collapsed;
-                    updateAnchorAndFocusFromRange(this, range, false);
-                    updateType(this);
-                }
-            };
-
-            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 {
-                // Clone the range to preserve selection-range independence. See issue 80.
-                return this._ranges[index].cloneRange();
-            }
-        };
-
-        var refreshSelection;
-
-        if (useDocumentSelection) {
-            refreshSelection = function(sel) {
-                var range;
-                if (api.isSelectionValid(sel.win)) {
-                    range = sel.docSelection.createRange();
-                } else {
-                    range = 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 (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], selectionIsBackward(sel.nativeSelection));
-                        sel.isCollapsed = selectionIsCollapsed(sel);
-                        updateType(sel);
-                    } else {
-                        updateEmptySelection(sel);
-                    }
-                }
-            };
-        } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && 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);
-                    updateType(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;
-            var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
-
-            refreshSelection(this);
-            if (checkForChanges) {
-                // Check the range count first
-                var i = oldRanges.length;
-                if (i != this._ranges.length) {
-                    return true;
-                }
-
-                // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
-                // ranges after this
-                if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
-                    return true;
-                }
-
-                // Finally, compare each range in turn
-                while (i--) {
-                    if (!rangesEqual(oldRanges[i], this._ranges[i])) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        };
-
-        // Removal of a single range
-        var removeRangeManually = function(sel, range) {
-            var ranges = sel.getAllRanges();
-            sel.removeAllRanges();
-            for (var i = 0, len = ranges.length; i < len; ++i) {
-                if (!rangesEqual(range, ranges[i])) {
-                    sel.addRange(ranges[i]);
-                }
-            }
-            if (!sel.rangeCount) {
-                updateEmptySelection(sel);
-            }
-        };
-
-        if (implementsControlRange && implementsDocSelection) {
-            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 = getDocument(controlRange.item(0));
-                    var newControlRange = 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 backward
-        var selectionIsBackward;
-        if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
-            selectionIsBackward = winSelectionIsBackward;
-
-            selProto.isBackward = function() {
-                return selectionIsBackward(this);
-            };
-        } else {
-            selectionIsBackward = selProto.isBackward = function() {
-                return false;
-            };
-        }
-
-        // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
-        selProto.isBackwards = selProto.isBackward;
-
-        // Selection stringifier
-        // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
-        // The current spec does not yet define this method.
-        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.win.document != getDocument(node)) {
-                throw new DOMException("WRONG_DOCUMENT_ERR");
-            }
-        }
-
-        function assertValidOffset(node, offset) {
-            if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
-                throw new DOMException("INDEX_SIZE_ERR");
-            }
-        }
-
-        // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
-        selProto.collapse = function(node, offset) {
-            assertNodeInSameDocument(this, node);
-            var range = api.createRange(node);
-            range.collapseToPoint(node, offset);
-            this.setSingleRange(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 spec is very specific on how selectAllChildren should be implemented and not all browsers implement it as
-        // specified so the native implementation is never used by Rangy.
-        selProto.selectAllChildren = function(node) {
-            assertNodeInSameDocument(this, node);
-            var range = api.createRange(node);
-            range.selectNodeContents(node);
-            this.setSingleRange(range);
-        };
-
-        if (selectionHasSetBaseAndExtent) {
-            selProto.setBaseAndExtent = function(anchorNode, anchorOffset, focusNode, focusOffset) {
-                this.nativeSelection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
-                this.refresh();
-            };
-        } else if (selectionHasExtend) {
-            selProto.setBaseAndExtent = function(anchorNode, anchorOffset, focusNode, focusOffset) {
-                assertValidOffset(anchorNode, anchorOffset);
-                assertValidOffset(focusNode, focusOffset);
-                assertNodeInSameDocument(this, anchorNode);
-                assertNodeInSameDocument(this, focusNode);
-                var range = api.createRange(node);
-                var isBackwards = (dom.comparePoints(anchorNode, anchorOffset, focusNode, focusOffset) == -1);
-                if (isBackwards) {
-                    range.setStartAndEnd(focusNode, focusOffset, anchorNode, anchorOffset);
-                } else {
-                    range.setStartAndEnd(anchorNode, anchorOffset, focusNode, focusOffset);
-                }
-                this.setSingleRange(range, isBackwards);
-            };
-        }
-
-        selProto.deleteFromDocument = function() {
-            // Sepcial behaviour required for IE's 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);
-                    dom.removeNode(element);
-                }
-                this.refresh();
-            } else if (this.rangeCount) {
-                var ranges = this.getAllRanges();
-                if (ranges.length) {
-                    this.removeAllRanges();
-                    for (var i = 0, len = ranges.length; i < len; ++i) {
-                        ranges[i].deleteContents();
-                    }
-                    // The 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.eachRange = function(func, returnValue) {
-            for (var i = 0, len = this._ranges.length; i < len; ++i) {
-                if ( func( this.getRangeAt(i) ) ) {
-                    return returnValue;
-                }
-            }
-        };
-
-        selProto.getAllRanges = function() {
-            var ranges = [];
-            this.eachRange(function(range) {
-                ranges.push(range);
-            });
-            return ranges;
-        };
-
-        selProto.setSingleRange = function(range, direction) {
-            this.removeAllRanges();
-            this.addRange(range, direction);
-        };
-
-        selProto.callMethodOnEachRange = function(methodName, params) {
-            var results = [];
-            this.eachRange( function(range) {
-                results.push( range[methodName].apply(range, params || []) );
-            } );
-            return results;
-        };
-
-        function createStartOrEndSetter(isStart) {
-            return function(node, offset) {
-                var range;
-                if (this.rangeCount) {
-                    range = this.getRangeAt(0);
-                    range["set" + (isStart ? "Start" : "End")](node, offset);
-                } else {
-                    range = api.createRange(this.win.document);
-                    range.setStartAndEnd(node, offset);
-                }
-                this.setSingleRange(range, this.isBackward());
-            };
-        }
-
-        selProto.setStart = createStartOrEndSetter(true);
-        selProto.setEnd = createStartOrEndSetter(false);
-
-        // Add select() method to Range prototype. Any existing selection will be removed.
-        api.rangePrototype.select = function(direction) {
-            getSelection( this.getDocument() ).setSingleRange(this, direction);
-        };
-
-        selProto.changeEachRange = function(func) {
-            var ranges = [];
-            var backward = this.isBackward();
-
-            this.eachRange(function(range) {
-                func(range);
-                ranges.push(range);
-            });
-
-            this.removeAllRanges();
-            if (backward && ranges.length == 1) {
-                this.addRange(ranges[0], "backward");
-            } else {
-                this.setRanges(ranges);
-            }
-        };
-
-        selProto.containsNode = function(node, allowPartial) {
-            return this.eachRange( function(range) {
-                return range.containsNode(node, allowPartial);
-            }, true ) || false;
-        };
-
-        selProto.getBookmark = function(containerNode) {
-            return {
-                backward: this.isBackward(),
-                rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
-            };
-        };
-
-        selProto.moveToBookmark = function(bookmark) {
-            var selRanges = [];
-            for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
-                range = api.createRange(this.win);
-                range.moveToBookmark(rangeBookmark);
-                selRanges.push(range);
-            }
-            if (bookmark.backward) {
-                this.setSingleRange(selRanges[0], "backward");
-            } else {
-                this.setRanges(selRanges);
-            }
-        };
-
-        selProto.saveRanges = function() {
-            return {
-                backward: this.isBackward(),
-                ranges: this.callMethodOnEachRange("cloneRange")
-            };
-        };
-
-        selProto.restoreRanges = function(selRanges) {
-            this.removeAllRanges();
-            for (var i = 0, range; range = selRanges.ranges[i]; ++i) {
-                this.addRange(range, (selRanges.backward && i == 0));
-            }
-        };
-
-        selProto.toHtml = function() {
-            var rangeHtmls = [];
-            this.eachRange(function(range) {
-                rangeHtmls.push( DomRange.toHtml(range) );
-            });
-            return rangeHtmls.join("");
-        };
-
-        if (features.implementsTextRange) {
-            selProto.getNativeTextRange = function() {
-                var sel, textRange;
-                if ( (sel = this.docSelection) ) {
-                    var range = sel.createRange();
-                    if (isTextRange(range)) {
-                        return range;
-                    } else {
-                        throw module.createError("getNativeTextRange: selection is a control selection");
-                    }
-                } else if (this.rangeCount > 0) {
-                    return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
-                } else {
-                    throw module.createError("getNativeTextRange: selection contains no range");
-                }
-            };
-        }
-
-        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() {
-            actOnCachedSelection(this.win, "delete");
-            deleteProperties(this);
-        };
-
-        WrappedSelection.detachAll = function() {
-            actOnCachedSelection(null, "deleteAll");
-        };
-
-        WrappedSelection.inspect = inspect;
-        WrappedSelection.isDirectionBackward = isDirectionBackward;
-
-        api.Selection = WrappedSelection;
-
-        api.selectionPrototype = selProto;
-
-        api.addShimListener(function(win) {
-            if (typeof win.getSelection == "undefined") {
-                win.getSelection = function() {
-                    return getSelection(win);
-                };
-            }
-            win = null;
-        });
-    });
-
-
-    /*----------------------------------------------------------------------------------------------------------------*/
-
-    // Wait for document to load before initializing
-    var docReady = false;
-
-    var loadHandler = function(e) {
-        if (!docReady) {
-            docReady = true;
-            if (!api.initialized && api.config.autoInitialize) {
-                init();
-            }
-        }
-    };
-
-    if (isBrowser) {
-        // Test whether the document has already been loaded and initialize immediately if so
-        if (document.readyState == "complete") {
-            loadHandler();
-        } else {
-            if (isHostMethod(document, "addEventListener")) {
-                document.addEventListener("DOMContentLoaded", loadHandler, false);
-            }
-
-            // Add a fallback in case the DOMContentLoaded event isn't supported
-            addListener(window, "load", loadHandler);
-        }
-    }
-
-    return api;
-}, this);
-/**
- * Selection save and restore module for Rangy.
- * Saves and restores user selections using marker invisible elements in the DOM.
- *
- * Part of Rangy, a cross-browser JavaScript range and selection library
- * https://github.com/timdown/rangy
- *
- * Depends on Rangy core.
- *
- * Copyright 2022, Tim Down
- * Licensed under the MIT license.
- * Version: 1.3.1
- * Build date: 17 August 2022
- */
-(function(factory, root) {
-    // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
-    factory(root.rangy);
-})(function(rangy) {
-    rangy.createModule("SaveRestore", ["WrappedSelection"], function(api, module) {
-        var dom = api.dom;
-        var removeNode = dom.removeNode;
-        var isDirectionBackward = api.Selection.isDirectionBackward;
-        var markerTextChar = "\ufeff";
-
-        function gEBI(id, doc) {
-            return (doc || document).getElementById(id);
-        }
-
-        function insertRangeBoundaryMarker(range, atStart) {
-            var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
-            var markerEl;
-            var doc = dom.getDocument(range.startContainer);
-
-            // Clone the Range and collapse to the appropriate boundary point
-            var boundaryRange = range.cloneRange();
-            boundaryRange.collapse(atStart);
-
-            // Create the marker element containing a single invisible character using DOM methods and insert it
-            markerEl = doc.createElement("span");
-            markerEl.id = markerId;
-            markerEl.style.lineHeight = "0";
-            markerEl.style.display = "none";
-            markerEl.className = "rangySelectionBoundary";
-            markerEl.appendChild(doc.createTextNode(markerTextChar));
-
-            boundaryRange.insertNode(markerEl);
-            return markerEl;
-        }
-
-        function setRangeBoundary(doc, range, markerId, atStart) {
-            var markerEl = gEBI(markerId, doc);
-            if (markerEl) {
-                range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
-                removeNode(markerEl);
-            } else {
-                module.warn("Marker element has been removed. Cannot restore selection.");
-            }
-        }
-
-        function compareRanges(r1, r2) {
-            return r2.compareBoundaryPoints(r1.START_TO_START, r1);
-        }
-
-        function saveRange(range, direction) {
-            var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
-            var backward = isDirectionBackward(direction);
-
-            if (range.collapsed) {
-                endEl = insertRangeBoundaryMarker(range, false);
-                return {
-                    document: doc,
-                    markerId: endEl.id,
-                    collapsed: true
-                };
-            } else {
-                endEl = insertRangeBoundaryMarker(range, false);
-                startEl = insertRangeBoundaryMarker(range, true);
-
-                return {
-                    document: doc,
-                    startMarkerId: startEl.id,
-                    endMarkerId: endEl.id,
-                    collapsed: false,
-                    backward: backward,
-                    toString: function() {
-                        return "original text: '" + text + "', new text: '" + range.toString() + "'";
-                    }
-                };
-            }
-        }
-
-        function restoreRange(rangeInfo, normalize) {
-            var doc = rangeInfo.document;
-            if (typeof normalize == "undefined") {
-                normalize = true;
-            }
-            var range = api.createRange(doc);
-            if (rangeInfo.collapsed) {
-                var markerEl = gEBI(rangeInfo.markerId, doc);
-                if (markerEl) {
-                    markerEl.style.display = "inline";
-                    var previousNode = markerEl.previousSibling;
-
-                    // Workaround for issue 17
-                    if (previousNode && previousNode.nodeType == 3) {
-                        removeNode(markerEl);
-                        range.collapseToPoint(previousNode, previousNode.length);
-                    } else {
-                        range.collapseBefore(markerEl);
-                        removeNode(markerEl);
-                    }
-                } else {
-                    module.warn("Marker element has been removed. Cannot restore selection.");
-                }
-            } else {
-                setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
-                setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
-            }
-
-            if (normalize) {
-                range.normalizeBoundaries();
-            }
-
-            return range;
-        }
-
-        function saveRanges(ranges, direction) {
-            var rangeInfos = [], range, doc;
-            var backward = isDirectionBackward(direction);
-
-            // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
-            ranges = ranges.slice(0);
-            ranges.sort(compareRanges);
-
-            for (var i = 0, len = ranges.length; i < len; ++i) {
-                rangeInfos[i] = saveRange(ranges[i], backward);
-            }
-
-            // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
-            // between its markers
-            for (i = len - 1; i >= 0; --i) {
-                range = ranges[i];
-                doc = api.DomRange.getRangeDocument(range);
-                if (range.collapsed) {
-                    range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
-                } else {
-                    range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
-                    range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
-                }
-            }
-
-            return rangeInfos;
-        }
-
-        function saveSelection(win) {
-            if (!api.isSelectionValid(win)) {
-                module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
-                return null;
-            }
-            var sel = api.getSelection(win);
-            var ranges = sel.getAllRanges();
-            var backward = (ranges.length == 1 && sel.isBackward());
-
-            var rangeInfos = saveRanges(ranges, backward);
-
-            // Ensure current selection is unaffected
-            if (backward) {
-                sel.setSingleRange(ranges[0], backward);
-            } else {
-                sel.setRanges(ranges);
-            }
-
-            return {
-                win: win,
-                rangeInfos: rangeInfos,
-                restored: false
-            };
-        }
-
-        function restoreRanges(rangeInfos) {
-            var ranges = [];
-
-            // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
-            // normalization affecting previously restored ranges.
-            var rangeCount = rangeInfos.length;
-
-            for (var i = rangeCount - 1; i >= 0; i--) {
-                ranges[i] = restoreRange(rangeInfos[i], true);
-            }
-
-            return ranges;
-        }
-
-        function restoreSelection(savedSelection, preserveDirection) {
-            if (!savedSelection.restored) {
-                var rangeInfos = savedSelection.rangeInfos;
-                var sel = api.getSelection(savedSelection.win);
-                var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
-
-                if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
-                    sel.removeAllRanges();
-                    sel.addRange(ranges[0], true);
-                } else {
-                    sel.setRanges(ranges);
-                }
-
-                savedSelection.restored = true;
-            }
-        }
-
-        function removeMarkerElement(doc, markerId) {
-            var markerEl = gEBI(markerId, doc);
-            if (markerEl) {
-                removeNode(markerEl);
-            }
-        }
-
-        function removeMarkers(savedSelection) {
-            var rangeInfos = savedSelection.rangeInfos;
-            for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
-                rangeInfo = rangeInfos[i];
-                if (rangeInfo.collapsed) {
-                    removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
-                } else {
-                    removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
-                    removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
-                }
-            }
-        }
-
-        api.util.extend(api, {
-            saveRange: saveRange,
-            restoreRange: restoreRange,
-            saveRanges: saveRanges,
-            restoreRanges: restoreRanges,
-            saveSelection: saveSelection,
-            restoreSelection: restoreSelection,
-            removeMarkerElement: removeMarkerElement,
-            removeMarkers: removeMarkers
-        });
-    });
-
-    return rangy;
-}, this);
-/**
- * Serializer module for Rangy.
- * Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a
- * cookie or local storage and restore it on the user's next visit to the same page.
- *
- * Part of Rangy, a cross-browser JavaScript range and selection library
- * https://github.com/timdown/rangy
- *
- * Depends on Rangy core.
- *
- * Copyright 2022, Tim Down
- * Licensed under the MIT license.
- * Version: 1.3.1
- * Build date: 17 August 2022
- */
-(function(factory, root) {
-    // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
-    factory(root.rangy);
-})(function(rangy) {
-    rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) {
-        var UNDEF = "undefined";
-        var util = api.util;
-
-        // encodeURIComponent and decodeURIComponent are required for cookie handling
-        if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) {
-            module.fail("encodeURIComponent and/or decodeURIComponent method is missing");
-        }
-
-        // Checksum for checking whether range can be serialized
-        var crc32 = (function() {
-            function utf8encode(str) {
-                var utf8CharCodes = [];
-
-                for (var i = 0, len = str.length, c; i < len; ++i) {
-                    c = str.charCodeAt(i);
-                    if (c < 128) {
-                        utf8CharCodes.push(c);
-                    } else if (c < 2048) {
-                        utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128);
-                    } else {
-                        utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128);
-                    }
-                }
-                return utf8CharCodes;
-            }
-
-            var cachedCrcTable = null;
-
-            function buildCRCTable() {
-                var table = [];
-                for (var i = 0, j, crc; i < 256; ++i) {
-                    crc = i;
-                    j = 8;
-                    while (j--) {
-                        if ((crc & 1) == 1) {
-                            crc = (crc >>> 1) ^ 0xEDB88320;
-                        } else {
-                            crc >>>= 1;
-                        }
-                    }
-                    table[i] = crc >>> 0;
-                }
-                return table;
-            }
-
-            function getCrcTable() {
-                if (!cachedCrcTable) {
-                    cachedCrcTable = buildCRCTable();
-                }
-                return cachedCrcTable;
-            }
-
-            return function(str) {
-                var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable();
-                for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) {
-                    y = (crc ^ utf8CharCodes[i]) & 0xFF;
-                    crc = (crc >>> 8) ^ crcTable[y];
-                }
-                return (crc ^ -1) >>> 0;
-            };
-        })();
-
-        var dom = api.dom;
-
-        function escapeTextForHtml(str) {
-            return str.replace(//g, ">");
-        }
-
-        function nodeToInfoString(node, infoParts) {
-            infoParts = infoParts || [];
-            var nodeType = node.nodeType, children = node.childNodes, childCount = children.length;
-            var nodeInfo = [nodeType, node.nodeName, childCount].join(":");
-            var start = "", end = "";
-            switch (nodeType) {
-                case 3: // Text node
-                    start = escapeTextForHtml(node.nodeValue);
-                    break;
-                case 8: // Comment
-                    start = "";
-                    break;
-                default:
-                    start = "<" + nodeInfo + ">";
-                    end = "";
-                    break;
-            }
-            if (start) {
-                infoParts.push(start);
-            }
-            for (var i = 0; i < childCount; ++i) {
-                nodeToInfoString(children[i], infoParts);
-            }
-            if (end) {
-                infoParts.push(end);
-            }
-            return infoParts;
-        }
-
-        // Creates a string representation of the specified element's contents that is similar to innerHTML but omits all
-        // attributes and comments and includes child node counts. This is done instead of using innerHTML to work around
-        // IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's
-        // innerHTML whenever the user changes an input within the element.
-        function getElementChecksum(el) {
-            var info = nodeToInfoString(el).join("");
-            return crc32(info).toString(16);
-        }
-
-        function serializePosition(node, offset, rootNode) {
-            var pathParts = [], n = node;
-            rootNode = rootNode || dom.getDocument(node).documentElement;
-            while (n && n != rootNode) {
-                pathParts.push(dom.getNodeIndex(n, true));
-                n = n.parentNode;
-            }
-            return pathParts.join("/") + ":" + offset;
-        }
-
-        function deserializePosition(serialized, rootNode, doc) {
-            if (!rootNode) {
-                rootNode = (doc || document).documentElement;
-            }
-            var parts = serialized.split(":");
-            var node = rootNode;
-            var nodeIndices = parts[0] ? parts[0].split("/") : [], i = nodeIndices.length, nodeIndex;
-
-            while (i--) {
-                nodeIndex = parseInt(nodeIndices[i], 10);
-                if (nodeIndex < node.childNodes.length) {
-                    node = node.childNodes[nodeIndex];
-                } else {
-                    throw module.createError("deserializePosition() failed: node " + dom.inspectNode(node) +
-                            " has no child with index " + nodeIndex + ", " + i);
-                }
-            }
-
-            return new dom.DomPosition(node, parseInt(parts[1], 10));
-        }
-
-        function serializeRange(range, omitChecksum, rootNode) {
-            rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement;
-            if (!dom.isOrIsAncestorOf(rootNode, range.commonAncestorContainer)) {
-                throw module.createError("serializeRange(): range " + range.inspect() +
-                    " is not wholly contained within specified root node " + dom.inspectNode(rootNode));
-            }
-            var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," +
-                serializePosition(range.endContainer, range.endOffset, rootNode);
-            if (!omitChecksum) {
-                serialized += "{" + getElementChecksum(rootNode) + "}";
-            }
-            return serialized;
-        }
-
-        var deserializeRegex = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/;
-
-        function deserializeRange(serialized, rootNode, doc) {
-            if (rootNode) {
-                doc = doc || dom.getDocument(rootNode);
-            } else {
-                doc = doc || document;
-                rootNode = doc.documentElement;
-            }
-            var result = deserializeRegex.exec(serialized);
-            var checksum = result[4];
-            if (checksum) {
-                var rootNodeChecksum = getElementChecksum(rootNode);
-                if (checksum !== rootNodeChecksum) {
-                    throw module.createError("deserializeRange(): checksums of serialized range root node (" + checksum +
-                        ") and target root node (" + rootNodeChecksum + ") do not match");
-                }
-            }
-            var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc);
-            var range = api.createRange(doc);
-            range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
-            return range;
-        }
-
-        function canDeserializeRange(serialized, rootNode, doc) {
-            if (!rootNode) {
-                rootNode = (doc || document).documentElement;
-            }
-            var result = deserializeRegex.exec(serialized);
-            var checksum = result[3];
-            return !checksum || checksum === getElementChecksum(rootNode);
-        }
-
-        function serializeSelection(selection, omitChecksum, rootNode) {
-            selection = api.getSelection(selection);
-            var ranges = selection.getAllRanges(), serializedRanges = [];
-            for (var i = 0, len = ranges.length; i < len; ++i) {
-                serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode);
-            }
-            return serializedRanges.join("|");
-        }
-
-        function deserializeSelection(serialized, rootNode, win) {
-            if (rootNode) {
-                win = win || dom.getWindow(rootNode);
-            } else {
-                win = win || window;
-                rootNode = win.document.documentElement;
-            }
-            var serializedRanges = serialized.split("|");
-            var sel = api.getSelection(win);
-            var ranges = [];
-
-            for (var i = 0, len = serializedRanges.length; i < len; ++i) {
-                ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document);
-            }
-            sel.setRanges(ranges);
-
-            return sel;
-        }
-
-        function canDeserializeSelection(serialized, rootNode, win) {
-            var doc;
-            if (rootNode) {
-                doc = win ? win.document : dom.getDocument(rootNode);
-            } else {
-                win = win || window;
-                rootNode = win.document.documentElement;
-            }
-            var serializedRanges = serialized.split("|");
-
-            for (var i = 0, len = serializedRanges.length; i < len; ++i) {
-                if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        var cookieName = "rangySerializedSelection";
-
-        function getSerializedSelectionFromCookie(cookie) {
-            var parts = cookie.split(/[;,]/);
-            for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) {
-                nameVal = parts[i].split("=");
-                if (nameVal[0].replace(/^\s+/, "") == cookieName) {
-                    val = nameVal[1];
-                    if (val) {
-                        return decodeURIComponent(val.replace(/\s+$/, ""));
-                    }
-                }
-            }
-            return null;
-        }
-
-        function restoreSelectionFromCookie(win) {
-            win = win || window;
-            var serialized = getSerializedSelectionFromCookie(win.document.cookie);
-            if (serialized) {
-                deserializeSelection(serialized, win.doc);
-            }
-        }
-
-        function saveSelectionCookie(win, props) {
-            win = win || window;
-            props = (typeof props == "object") ? props : {};
-            var expires = props.expires ? ";expires=" + props.expires.toUTCString() : "";
-            var path = props.path ? ";path=" + props.path : "";
-            var domain = props.domain ? ";domain=" + props.domain : "";
-            var secure = props.secure ? ";secure" : "";
-            var serialized = serializeSelection(api.getSelection(win));
-            win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure;
-        }
-
-        util.extend(api, {
-            serializePosition: serializePosition,
-            deserializePosition: deserializePosition,
-            serializeRange: serializeRange,
-            deserializeRange: deserializeRange,
-            canDeserializeRange: canDeserializeRange,
-            serializeSelection: serializeSelection,
-            deserializeSelection: deserializeSelection,
-            canDeserializeSelection: canDeserializeSelection,
-            restoreSelectionFromCookie: restoreSelectionFromCookie,
-            saveSelectionCookie: saveSelectionCookie,
-            getElementChecksum: getElementChecksum,
-            nodeToInfoString: nodeToInfoString
-        });
-
-        util.crc32 = crc32;
-    });
-
-    return rangy;
-}, this);
-/**
- * Class Applier module for Rangy.
- * Adds, removes and toggles classes on Ranges and Selections
- *
- * Part of Rangy, a cross-browser JavaScript range and selection library
- * https://github.com/timdown/rangy
- *
- * Depends on Rangy core.
- *
- * Copyright 2022, Tim Down
- * Licensed under the MIT license.
- * Version: 1.3.1
- * Build date: 17 August 2022
- */
-(function(factory, root) {
-    // No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
-    factory(root.rangy);
-})(function(rangy) {
-    rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) {
-        var dom = api.dom;
-        var DomPosition = dom.DomPosition;
-        var contains = dom.arrayContains;
-        var util = api.util;
-        var forEach = util.forEach;
-
-
-        var defaultTagName = "span";
-        var createElementNSSupported = util.isHostMethod(document, "createElementNS");
-
-        function each(obj, func) {
-            for (var i in obj) {
-                if (obj.hasOwnProperty(i)) {
-                    if (func(i, obj[i]) === false) {
-                        return false;
-                    }
-                }
-            }
-            return true;
-        }
-
-        function trim(str) {
-            return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
-        }
-
-        function classNameContainsClass(fullClassName, className) {
-            return !!fullClassName && new RegExp("(?:^|\\s)" + className + "(?:\\s|$)").test(fullClassName);
-        }
-
-        // Inefficient, inelegant nonsense for IE's svg element, which has no classList and non-HTML className implementation
-        function hasClass(el, className) {
-            if (typeof el.classList == "object") {
-                return el.classList.contains(className);
-            } else {
-                var classNameSupported = (typeof el.className == "string");
-                var elClass = classNameSupported ? el.className : el.getAttribute("class");
-                return classNameContainsClass(elClass, className);
-            }
-        }
-
-        function addClass(el, className) {
-            if (typeof el.classList == "object") {
-                el.classList.add(className);
-            } else {
-                var classNameSupported = (typeof el.className == "string");
-                var elClass = classNameSupported ? el.className : el.getAttribute("class");
-                if (elClass) {
-                    if (!classNameContainsClass(elClass, className)) {
-                        elClass += " " + className;
-                    }
-                } else {
-                    elClass = className;
-                }
-                if (classNameSupported) {
-                    el.className = elClass;
-                } else {
-                    el.setAttribute("class", elClass);
-                }
-            }
-        }
-
-        var removeClass = (function() {
-            function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
-                return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
-            }
-
-            return function(el, className) {
-                if (typeof el.classList == "object") {
-                    el.classList.remove(className);
-                } else {
-                    var classNameSupported = (typeof el.className == "string");
-                    var elClass = classNameSupported ? el.className : el.getAttribute("class");
-                    elClass = elClass.replace(new RegExp("(^|\\s)" + className + "(\\s|$)"), replacer);
-                    if (classNameSupported) {
-                        el.className = elClass;
-                    } else {
-                        el.setAttribute("class", elClass);
-                    }
-                }
-            };
-        })();
-
-        function getClass(el) {
-            var classNameSupported = (typeof el.className == "string");
-            return classNameSupported ? el.className : el.getAttribute("class");
-        }
-
-        function sortClassName(className) {
-            return className && className.split(/\s+/).sort().join(" ");
-        }
-
-        function getSortedClassName(el) {
-            return sortClassName( getClass(el) );
-        }
-
-        function haveSameClasses(el1, el2) {
-            return getSortedClassName(el1) == getSortedClassName(el2);
-        }
-
-        function hasAllClasses(el, className) {
-            var classes = className.split(/\s+/);
-            for (var i = 0, len = classes.length; i < len; ++i) {
-                if (!hasClass(el, trim(classes[i]))) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        function canTextBeStyled(textNode) {
-            var parent = textNode.parentNode;
-            return (parent && parent.nodeType == 1 && !/^(textarea|style|script|select|iframe)$/i.test(parent.nodeName));
-        }
-
-        function movePosition(position, oldParent, oldIndex, newParent, newIndex) {
-            var posNode = position.node, posOffset = position.offset;
-            var newNode = posNode, newOffset = posOffset;
-
-            if (posNode == newParent && posOffset > newIndex) {
-                ++newOffset;
-            }
-
-            if (posNode == oldParent && (posOffset == oldIndex  || posOffset == oldIndex + 1)) {
-                newNode = newParent;
-                newOffset += newIndex - oldIndex;
-            }
-
-            if (posNode == oldParent && posOffset > oldIndex + 1) {
-                --newOffset;
-            }
-
-            position.node = newNode;
-            position.offset = newOffset;
-        }
-
-        function movePositionWhenRemovingNode(position, parentNode, index) {
-            if (position.node == parentNode && position.offset > index) {
-                --position.offset;
-            }
-        }
-
-        function movePreservingPositions(node, newParent, newIndex, positionsToPreserve) {
-            // For convenience, allow newIndex to be -1 to mean "insert at the end".
-            if (newIndex == -1) {
-                newIndex = newParent.childNodes.length;
-            }
-
-            var oldParent = node.parentNode;
-            var oldIndex = dom.getNodeIndex(node);
-
-            forEach(positionsToPreserve, function(position) {
-                movePosition(position, oldParent, oldIndex, newParent, newIndex);
-            });
-
-            // Now actually move the node.
-            if (newParent.childNodes.length == newIndex) {
-                newParent.appendChild(node);
-            } else {
-                newParent.insertBefore(node, newParent.childNodes[newIndex]);
-            }
-        }
-
-        function removePreservingPositions(node, positionsToPreserve) {
-
-            var oldParent = node.parentNode;
-            var oldIndex = dom.getNodeIndex(node);
-
-            forEach(positionsToPreserve, function(position) {
-                movePositionWhenRemovingNode(position, oldParent, oldIndex);
-            });
-
-            dom.removeNode(node);
-        }
-
-        function moveChildrenPreservingPositions(node, newParent, newIndex, removeNode, positionsToPreserve) {
-            var child, children = [];
-            while ( (child = node.firstChild) ) {
-                movePreservingPositions(child, newParent, newIndex++, positionsToPreserve);
-                children.push(child);
-            }
-            if (removeNode) {
-                removePreservingPositions(node, positionsToPreserve);
-            }
-            return children;
-        }
-
-        function replaceWithOwnChildrenPreservingPositions(element, positionsToPreserve) {
-            return moveChildrenPreservingPositions(element, element.parentNode, dom.getNodeIndex(element), true, positionsToPreserve);
-        }
-
-        function rangeSelectsAnyText(range, textNode) {
-            var textNodeRange = range.cloneRange();
-            textNodeRange.selectNodeContents(textNode);
-
-            var intersectionRange = textNodeRange.intersection(range);
-            var text = intersectionRange ? intersectionRange.toString() : "";
-
-            return text != "";
-        }
-
-        function getEffectiveTextNodes(range) {
-            var nodes = range.getNodes([3]);
-
-            // Optimization as per issue 145
-
-            // Remove non-intersecting text nodes from the start of the range
-            var start = 0, node;
-            while ( (node = nodes[start]) && !rangeSelectsAnyText(range, node) ) {
-                ++start;
-            }
-
-            // Remove non-intersecting text nodes from the start of the range
-            var end = nodes.length - 1;
-            while ( (node = nodes[end]) && !rangeSelectsAnyText(range, node) ) {
-                --end;
-            }
-
-            return nodes.slice(start, end + 1);
-        }
-
-        function elementsHaveSameNonClassAttributes(el1, el2) {
-            if (el1.attributes.length != el2.attributes.length) return false;
-            for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
-                attr1 = el1.attributes[i];
-                name = attr1.name;
-                if (name != "class") {
-                    attr2 = el2.attributes.getNamedItem(name);
-                    if ( (attr1 === null) != (attr2 === null) ) return false;
-                    if (attr1.specified != attr2.specified) return false;
-                    if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
-                }
-            }
-            return true;
-        }
-
-        function elementHasNonClassAttributes(el, exceptions) {
-            for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
-                attrName = el.attributes[i].name;
-                if ( !(exceptions && contains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        var getComputedStyleProperty = dom.getComputedStyleProperty;
-        var isEditableElement = (function() {
-            var testEl = document.createElement("div");
-            return typeof testEl.isContentEditable == "boolean" ?
-                function (node) {
-                    return node && node.nodeType == 1 && node.isContentEditable;
-                } :
-                function (node) {
-                    if (!node || node.nodeType != 1 || node.contentEditable == "false") {
-                        return false;
-                    }
-                    return node.contentEditable == "true" || isEditableElement(node.parentNode);
-                };
-        })();
-
-        function isEditingHost(node) {
-            var parent;
-            return node && node.nodeType == 1 &&
-                (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on") ||
-                (isEditableElement(node) && !isEditableElement(node.parentNode)));
-        }
-
-        function isEditable(node) {
-            return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
-        }
-
-        var inlineDisplayRegex = /^inline(-block|-table)?$/i;
-
-        function isNonInlineElement(node) {
-            return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
-        }
-
-        // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
-        var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
-
-        function isUnrenderedWhiteSpaceNode(node) {
-            if (node.data.length == 0) {
-                return true;
-            }
-            if (htmlNonWhiteSpaceRegex.test(node.data)) {
-                return false;
-            }
-            var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
-            switch (cssWhiteSpace) {
-                case "pre":
-                case "pre-wrap":
-                case "-moz-pre-wrap":
-                    return false;
-                case "pre-line":
-                    if (/[\r\n]/.test(node.data)) {
-                        return false;
-                    }
-            }
-
-            // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
-            // non-inline element, it will not be rendered. This seems to be a good enough definition.
-            return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
-        }
-
-        function getRangeBoundaries(ranges) {
-            var positions = [], i, range;
-            for (i = 0; range = ranges[i++]; ) {
-                positions.push(
-                    new DomPosition(range.startContainer, range.startOffset),
-                    new DomPosition(range.endContainer, range.endOffset)
-                );
-            }
-            return positions;
-        }
-
-        function updateRangesFromBoundaries(ranges, positions) {
-            for (var i = 0, range, start, end, len = ranges.length; i < len; ++i) {
-                range = ranges[i];
-                start = positions[i * 2];
-                end = positions[i * 2 + 1];
-                range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
-            }
-        }
-
-        function isSplitPoint(node, offset) {
-            if (dom.isCharacterDataNode(node)) {
-                if (offset == 0) {
-                    return !!node.previousSibling;
-                } else if (offset == node.length) {
-                    return !!node.nextSibling;
-                } else {
-                    return true;
-                }
-            }
-
-            return offset > 0 && offset < node.childNodes.length;
-        }
-
-        function splitNodeAt(node, descendantNode, descendantOffset, positionsToPreserve) {
-            var newNode, parentNode;
-            var splitAtStart = (descendantOffset == 0);
-
-            if (dom.isAncestorOf(descendantNode, node)) {
-                return node;
-            }
-
-            if (dom.isCharacterDataNode(descendantNode)) {
-                var descendantIndex = dom.getNodeIndex(descendantNode);
-                if (descendantOffset == 0) {
-                    descendantOffset = descendantIndex;
-                } else if (descendantOffset == descendantNode.length) {
-                    descendantOffset = descendantIndex + 1;
-                } else {
-                    throw module.createError("splitNodeAt() should not be called with offset in the middle of a data node (" +
-                        descendantOffset + " in " + descendantNode.data);
-                }
-                descendantNode = descendantNode.parentNode;
-            }
-
-            if (isSplitPoint(descendantNode, descendantOffset)) {
-                // descendantNode is now guaranteed not to be a text or other character node
-                newNode = descendantNode.cloneNode(false);
-                parentNode = descendantNode.parentNode;
-                if (newNode.id) {
-                    newNode.removeAttribute("id");
-                }
-                var child, newChildIndex = 0;
-
-                while ( (child = descendantNode.childNodes[descendantOffset]) ) {
-                    movePreservingPositions(child, newNode, newChildIndex++, positionsToPreserve);
-                }
-                movePreservingPositions(newNode, parentNode, dom.getNodeIndex(descendantNode) + 1, positionsToPreserve);
-                return (descendantNode == node) ? newNode : splitNodeAt(node, parentNode, dom.getNodeIndex(newNode), positionsToPreserve);
-            } else if (node != descendantNode) {
-                newNode = descendantNode.parentNode;
-
-                // Work out a new split point in the parent node
-                var newNodeIndex = dom.getNodeIndex(descendantNode);
-
-                if (!splitAtStart) {
-                    newNodeIndex++;
-                }
-                return splitNodeAt(node, newNode, newNodeIndex, positionsToPreserve);
-            }
-            return node;
-        }
-
-        function areElementsMergeable(el1, el2) {
-            return el1.namespaceURI == el2.namespaceURI &&
-                el1.tagName.toLowerCase() == el2.tagName.toLowerCase() &&
-                haveSameClasses(el1, el2) &&
-                elementsHaveSameNonClassAttributes(el1, el2) &&
-                getComputedStyleProperty(el1, "display") == "inline" &&
-                getComputedStyleProperty(el2, "display") == "inline";
-        }
-
-        function createAdjacentMergeableTextNodeGetter(forward) {
-            var siblingPropName = forward ? "nextSibling" : "previousSibling";
-
-            return function(textNode, checkParentElement) {
-                var el = textNode.parentNode;
-                var adjacentNode = textNode[siblingPropName];
-                if (adjacentNode) {
-                    // Can merge if the node's previous/next sibling is a text node
-                    if (adjacentNode && adjacentNode.nodeType == 3) {
-                        return adjacentNode;
-                    }
-                } else if (checkParentElement) {
-                    // Compare text node parent element with its sibling
-                    adjacentNode = el[siblingPropName];
-                    if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
-                        var adjacentNodeChild = adjacentNode[forward ? "firstChild" : "lastChild"];
-                        if (adjacentNodeChild && adjacentNodeChild.nodeType == 3) {
-                            return adjacentNodeChild;
-                        }
-                    }
-                }
-                return null;
-            };
-        }
-
-        var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
-            getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
-
-
-        function Merge(firstNode) {
-            this.isElementMerge = (firstNode.nodeType == 1);
-            this.textNodes = [];
-            var firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
-            if (firstTextNode) {
-                this.textNodes[0] = firstTextNode;
-            }
-        }
-
-        Merge.prototype = {
-            doMerge: function(positionsToPreserve) {
-                var textNodes = this.textNodes;
-                var firstTextNode = textNodes[0];
-                if (textNodes.length > 1) {
-                    var firstTextNodeIndex = dom.getNodeIndex(firstTextNode);
-                    var textParts = [], combinedTextLength = 0, textNode, parent;
-                    forEach(textNodes, function(textNode, i) {
-                        parent = textNode.parentNode;
-                        if (i > 0) {
-                            parent.removeChild(textNode);
-                            if (!parent.hasChildNodes()) {
-                                dom.removeNode(parent);
-                            }
-                            if (positionsToPreserve) {
-                                forEach(positionsToPreserve, function(position) {
-                                    // Handle case where position is inside the text node being merged into a preceding node
-                                    if (position.node == textNode) {
-                                        position.node = firstTextNode;
-                                        position.offset += combinedTextLength;
-                                    }
-                                    // Handle case where both text nodes precede the position within the same parent node
-                                    if (position.node == parent && position.offset > firstTextNodeIndex) {
-                                        --position.offset;
-                                        if (position.offset == firstTextNodeIndex + 1 && i < textNodes.length - 1) {
-                                            position.node = firstTextNode;
-                                            position.offset = combinedTextLength;
-                                        }
-                                    }
-                                });
-                            }
-                        }
-                        textParts[i] = textNode.data;
-                        combinedTextLength += textNode.data.length;
-                    });
-                    firstTextNode.data = textParts.join("");
-                }
-                return firstTextNode.data;
-            },
-
-            getLength: function() {
-                var i = this.textNodes.length, len = 0;
-                while (i--) {
-                    len += this.textNodes[i].length;
-                }
-                return len;
-            },
-
-            toString: function() {
-                var textParts = [];
-                forEach(this.textNodes, function(textNode, i) {
-                    textParts[i] = "'" + textNode.data + "'";
-                });
-                return "[Merge(" + textParts.join(",") + ")]";
-            }
-        };
-
-        var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements",
-            "removeEmptyElements", "onElementCreate"];
-
-        // TODO: Populate this with every attribute name that corresponds to a property with a different name. Really??
-        var attrNamesForProperties = {};
-
-        function ClassApplier(className, options, tagNames) {
-            var normalize, i, len, propName, applier = this;
-            applier.cssClass = applier.className = className; // cssClass property is for backward compatibility
-
-            var elementPropertiesFromOptions = null, elementAttributes = {};
-
-            // Initialize from options object
-            if (typeof options == "object" && options !== null) {
-                if (typeof options.elementTagName !== "undefined") {
-                    options.elementTagName = options.elementTagName.toLowerCase();
-                }
-                tagNames = options.tagNames;
-                elementPropertiesFromOptions = options.elementProperties;
-                elementAttributes = options.elementAttributes;
-
-                for (i = 0; propName = optionProperties[i++]; ) {
-                    if (options.hasOwnProperty(propName)) {
-                        applier[propName] = options[propName];
-                    }
-                }
-                normalize = options.normalize;
-            } else {
-                normalize = options;
-            }
-
-            // Backward compatibility: the second parameter can also be a Boolean indicating to normalize after unapplying
-            applier.normalize = (typeof normalize == "undefined") ? true : normalize;
-
-            // Initialize element properties and attribute exceptions
-            applier.attrExceptions = [];
-            var el = document.createElement(applier.elementTagName);
-            applier.elementProperties = applier.copyPropertiesToElement(elementPropertiesFromOptions, el, true);
-            each(elementAttributes, function(attrName, attrValue) {
-                applier.attrExceptions.push(attrName);
-                // Ensure each attribute value is a string
-                elementAttributes[attrName] = "" + attrValue;
-            });
-            applier.elementAttributes = elementAttributes;
-
-            applier.elementSortedClassName = applier.elementProperties.hasOwnProperty("className") ?
-                sortClassName(applier.elementProperties.className + " " + className) : className;
-
-            // Initialize tag names
-            applier.applyToAnyTagName = false;
-            var type = typeof tagNames;
-            if (type == "string") {
-                if (tagNames == "*") {
-                    applier.applyToAnyTagName = true;
-                } else {
-                    applier.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
-                }
-            } else if (type == "object" && typeof tagNames.length == "number") {
-                applier.tagNames = [];
-                for (i = 0, len = tagNames.length; i < len; ++i) {
-                    if (tagNames[i] == "*") {
-                        applier.applyToAnyTagName = true;
-                    } else {
-                        applier.tagNames.push(tagNames[i].toLowerCase());
-                    }
-                }
-            } else {
-                applier.tagNames = [applier.elementTagName];
-            }
-        }
-
-        ClassApplier.prototype = {
-            elementTagName: defaultTagName,
-            elementProperties: {},
-            elementAttributes: {},
-            ignoreWhiteSpace: true,
-            applyToEditableOnly: false,
-            useExistingElements: true,
-            removeEmptyElements: true,
-            onElementCreate: null,
-
-            copyPropertiesToElement: function(props, el, createCopy) {
-                var s, elStyle, elProps = {}, elPropsStyle, propValue, elPropValue, attrName;
-
-                for (var p in props) {
-                    if (props.hasOwnProperty(p)) {
-                        propValue = props[p];
-                        elPropValue = el[p];
-
-                        // Special case for class. The copied properties object has the applier's class as well as its own
-                        // to simplify checks when removing styling elements
-                        if (p == "className") {
-                            addClass(el, propValue);
-                            addClass(el, this.className);
-                            el[p] = sortClassName(el[p]);
-                            if (createCopy) {
-                                elProps[p] = propValue;
-                            }
-                        }
-
-                        // Special case for style
-                        else if (p == "style") {
-                            elStyle = elPropValue;
-                            if (createCopy) {
-                                elProps[p] = elPropsStyle = {};
-                            }
-                            for (s in props[p]) {
-                                if (props[p].hasOwnProperty(s)) {
-                                    elStyle[s] = propValue[s];
-                                    if (createCopy) {
-                                        elPropsStyle[s] = elStyle[s];
-                                    }
-                                }
-                            }
-                            this.attrExceptions.push(p);
-                        } else {
-                            el[p] = propValue;
-                            // Copy the property back from the dummy element so that later comparisons to check whether
-                            // elements may be removed are checking against the right value. For example, the href property
-                            // of an element returns a fully qualified URL even if it was previously assigned a relative
-                            // URL.
-                            if (createCopy) {
-                                elProps[p] = el[p];
-
-                                // Not all properties map to identically-named attributes
-                                attrName = attrNamesForProperties.hasOwnProperty(p) ? attrNamesForProperties[p] : p;
-                                this.attrExceptions.push(attrName);
-                            }
-                        }
-                    }
-                }
-
-                return createCopy ? elProps : "";
-            },
-
-            copyAttributesToElement: function(attrs, el) {
-                for (var attrName in attrs) {
-                    if (attrs.hasOwnProperty(attrName) && !/^class(?:Name)?$/i.test(attrName)) {
-                        el.setAttribute(attrName, attrs[attrName]);
-                    }
-                }
-            },
-
-            appliesToElement: function(el) {
-                return contains(this.tagNames, el.tagName.toLowerCase());
-            },
-
-            getEmptyElements: function(range) {
-                var applier = this;
-                return range.getNodes([1], function(el) {
-                    return applier.appliesToElement(el) && !el.hasChildNodes();
-                });
-            },
-
-            hasClass: function(node) {
-                return node.nodeType == 1 &&
-                    (this.applyToAnyTagName || this.appliesToElement(node)) &&
-                    hasClass(node, this.className);
-            },
-
-            getSelfOrAncestorWithClass: function(node) {
-                while (node) {
-                    if (this.hasClass(node)) {
-                        return node;
-                    }
-                    node = node.parentNode;
-                }
-                return null;
-            },
-
-            isModifiable: function(node) {
-                return !this.applyToEditableOnly || isEditable(node);
-            },
-
-            // White space adjacent to an unwrappable node can be ignored for wrapping
-            isIgnorableWhiteSpaceNode: function(node) {
-                return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
-            },
-
-            // Normalizes nodes after applying a class to a Range.
-            postApply: function(textNodes, range, positionsToPreserve, isUndo) {
-                var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
-                var merges = [], currentMerge;
-                var rangeStartNode = firstNode, rangeEndNode = lastNode;
-                var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
-                var precedingTextNode;
-
-                // Check for every required merge and create a Merge object for each
-                forEach(textNodes, function(textNode) {
-                    precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
-                    if (precedingTextNode) {
-                        if (!currentMerge) {
-                            currentMerge = new Merge(precedingTextNode);
-                            merges.push(currentMerge);
-                        }
-                        currentMerge.textNodes.push(textNode);
-                        if (textNode === firstNode) {
-                            rangeStartNode = currentMerge.textNodes[0];
-                            rangeStartOffset = rangeStartNode.length;
-                        }
-                        if (textNode === lastNode) {
-                            rangeEndNode = currentMerge.textNodes[0];
-                            rangeEndOffset = currentMerge.getLength();
-                        }
-                    } else {
-                        currentMerge = null;
-                    }
-                });
-
-                // Test whether the first node after the range needs merging
-                var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
-
-                if (nextTextNode) {
-                    if (!currentMerge) {
-                        currentMerge = new Merge(lastNode);
-                        merges.push(currentMerge);
-                    }
-                    currentMerge.textNodes.push(nextTextNode);
-                }
-
-                // Apply the merges
-                if (merges.length) {
-                    for (var i = 0, len = merges.length; i < len; ++i) {
-                        merges[i].doMerge(positionsToPreserve);
-                    }
-
-                    // Set the range boundaries
-                    range.setStartAndEnd(rangeStartNode, rangeStartOffset, rangeEndNode, rangeEndOffset);
-                }
-            },
-
-            createContainer: function(parentNode) {
-                var doc = dom.getDocument(parentNode);
-                var namespace;
-                var el = createElementNSSupported && !dom.isHtmlNamespace(parentNode) && (namespace = parentNode.namespaceURI) ?
-                    doc.createElementNS(parentNode.namespaceURI, this.elementTagName) :
-                    doc.createElement(this.elementTagName);
-
-                this.copyPropertiesToElement(this.elementProperties, el, false);
-                this.copyAttributesToElement(this.elementAttributes, el);
-                addClass(el, this.className);
-                if (this.onElementCreate) {
-                    this.onElementCreate(el, this);
-                }
-                return el;
-            },
-
-            elementHasProperties: function(el, props) {
-                var applier = this;
-                return each(props, function(p, propValue) {
-                    if (p == "className") {
-                        // For checking whether we should reuse an existing element, we just want to check that the element
-                        // has all the classes specified in the className property. When deciding whether the element is
-                        // removable when unapplying a class, there is separate special handling to check whether the
-                        // element has extra classes so the same simple check will do.
-                        return hasAllClasses(el, propValue);
-                    } else if (typeof propValue == "object") {
-                        if (!applier.elementHasProperties(el[p], propValue)) {
-                            return false;
-                        }
-                    } else if (el[p] !== propValue) {
-                        return false;
-                    }
-                });
-            },
-
-            elementHasAttributes: function(el, attrs) {
-                return each(attrs, function(name, value) {
-                    if (el.getAttribute(name) !== value) {
-                        return false;
-                    }
-                });
-            },
-
-            applyToTextNode: function(textNode, positionsToPreserve) {
-
-                // Check whether the text node can be styled. Text within a