jquery-ui实现一个多tab标签页可拖动编辑器记事本效果代码
代码语言:html
所属分类:其他
代码描述:jquery-ui实现一个多tab标签页可拖动编辑器效果代码
代码标签: jquery-ui tab 标签页 编辑器 记事本 拖动
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> @font-face { font-family: 'icomoon'; src:url('data:application/font-woff;base64,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') format('woff'), url('') format('truetype'), url('') format('svg'); font-weight: normal; font-style: normal; } [class^="icon-"], [class*=" icon-"] { font-family: 'icomoon'; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-paragraph-left:before { content: "\e600"; } .icon-paragraph-center:before { content: "\e601"; } .icon-paragraph-right:before { content: "\e602"; } .icon-bold:before { content: "\e603"; } .icon-underline:before { content: "\e604"; } .icon-italic:before { content: "\e605"; } .icon-plus:before { content: "\e606"; } .icon-minus:before { content: "\e607"; } .icon-quill:before { content: "\e608"; } @import url(https://fonts.googleapis.com/css?family=Telex); * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; -webkit-font-smoothing: antialiased; } body { background: #5deaba; } .launch { font-family: "Telex", sans-serif; text-decoration: none; width: 240px; height: 60px; display: block; text-align: center; line-height: 38px; border-radius: 3px; font-size: 60px; color: #fff; position: absolute; left: 50%; top: 50%; margin: -30px 0 0 -120px; display: none; } .texteditor { width: 550px; height: 450px; position: absolute; left: 50%; top: 50%; background: #fff; margin: -205px 0 0 -275px; border-radius: 0 0 10px 10px; box-shadow: 0px -12px 200px #20b080; } .texteditor .ops { height: 288px; width: 50px; background: #ebebeb; position: absolute; right: 0px; border-radius: 0 10px 10px 0; z-index: 1; display: none; } .texteditor .ops ul { list-style: none; margin: 0; padding: 0; } .texteditor .ops ul li { text-align: center; padding: 10px 0; } .texteditor .ops ul li:first-child { border-radius: 0 10px 0 0; } .texteditor .ops ul li:last-child { border-radius: 0 0 10px 0; } .texteditor .ops ul li:hover { background: #31d687; } .texteditor .ops ul li:hover a { color: #fff; } .texteditor .ops ul li a { text-decoration: none; color: grey; display: block; } .texteditor .textareawrap .fontsize { background: #5deaba; position: absolute; z-index: 400; bottom: 5px; left: 5px; padding: 5px 10px; font-family: "Telex", sans-serif; color: #fff; font-size: 16px; border-radius: 0 0 0 5px; display: none; } .texteditor .textareawrap .textarea { display: none; margin: 0; padding: 10px; width: 100%; height: 100%; position: absolute; border: 0; outline: none; resize: none; overflow: auto; border-radius: 0 0 10px 10px; font-family: "Telex", sans-serif; font-size: 13px; color: #666; background: #fff; z-index: 2; } .texteditor .top { font-family: "Telex", sans-serif; position: absolute; height: 45px; background: #ebebeb; top: -45px; width: 100%; border-radius: 10px 10px 0 0; border-bottom: 1px solid #bababa; cursor: move; overflow: hidden; } .texteditor .top .icon-quill { font-size: 25px; float: right; margin: 0 10px 0 0; color: grey; text-decoration: none; } .texteditor .top .icon-quill:hover { color: #31d687; } .texteditor ul.controls { padding: 0; margin: 16px 0 0 15px; list-style: none; float: left; width: 65px; } .texteditor ul.controls li { width: 15px; height: 15px; float: left; margin: 0 4%; border-radius: 50%; cursor: pointer; } .texteditor ul.controls li:after { content: ""; background: linear-gradient(225deg, #ffffff 50%, transparent 50%); width: 15px; height: 15px; position: absolute; z-index: 2; border-radius: 50%; opacity: 0.4; } .texteditor ul.controls .close { background: #e57069; } .texteditor ul.controls .min { background: #f5ce56; } .texteditor ul.controls .expand { background: #31d687; } .texteditor ul.tabs { margin: 9px 0 0 100px; padding: 0; list-style: none; } .texteditor ul.tabs .addtab { font-size: 22px; z-index: 305; color: grey; position: relative; cursor: pointer; float: left; margin: 0 0 0 5px; } .texteditor ul.tabs .addtab:hover { color: #31d687; } .texteditor ul.tabs li { float: left; width: auto; margin: 0 -5px; padding: 0 15px; border-bottom: 35px solid #dad8d8; border-right: 20px solid transparent; border-left: 20px solid transparent; height: 0; line-height: 37px; position: relative; z-index: 2; cursor: pointer; } .texteditor ul.tabs li span { position: absolute; top: -12px; left: 2px; display: none; width: 17px; height: 17px; border-radius: 50%; top: -5px; left: -7px; text-align: center; line-height: 16px; background: #e57069; font-size: 20px; z-index: 3; } .texteditor ul.tabs li span:after { content: ""; background: linear-gradient(225deg, #ffffff 50%, transparent 50%); width: 15px; height: 15px; position: absolute; z-index: 2; border-radius: 50%; opacity: 0.2; left: 2px; } .texteditor ul.tabs li a { text-decoration: none; color: #fff; } .ui-resizable-e { right: 0; position: absolute; width: 5px; height: 100%; } .ui-resizable-s { bottom: 0; height: 5px; width: 100%; position: absolute; } .ui-icon-gripsmall-diagonal-se { z-index: 90; bottom: 0; right: 0; position: absolute; width: 10px; height: 10px; } .ui-resizable-s:hover { cursor: ns-resize; } .ui-resizable-e:hover { cursor: ew-resize; } .ui-icon-gripsmall-diagonal-se:hover { cursor: nwse-resize; } .active { border-bottom-color: #bababa !important; z-index: 300 !important; } .bold { font-weight: bold; } .italic { font-style: italic; } .underline { text-decoration: underline; } .leftalign { text-align: left; display: inline; } .rightalign { text-align: right; display: block; } .centeralign { text-align: center; display: block; } </style> </head> <body> <a href="#" class="launch"> LAUNCH </a> <div class="texteditor"> <div class="top"> <ul class="controls"> <li class="close"> <a href="#"></a> </li> <li class="min"> <a href="#"></a> </li> <li class="expand"> <a href="#"></a> </li> </ul> <ul class="tabs"> <span class="tabwrap"> </span> </ul> <a href="#" class="icon-quill"></a> </div> <div class="textareawrap"> <span class="fontsize"></span> </div> <aside class="ops"> <ul> <li> <a href="#" class="icon-bold"></a> </li> <li> <a class="icon-underline" href="#"></a> </li> <li> <a class="icon-italic" href="#"></a> </li> <li> <a class="icon-paragraph-left" href="#"></a> </li> <li> <a class="icon-paragraph-right" href="#"></a> </li> <li> <a class="icon-paragraph-center" href="#"></a> </li> <li> <a class="icon-plus" href="#"></a> </li> <li> <a class="icon-minus" href="#"></a> </li> </ul> </aside> </div> <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/jquery.2.11.js"></script> <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/jquery-ui-1.10.3.min.js"></script> <script> (function (factory, root) { if (typeof define == "function" && define.amd) { // AMD. Register as an anonymous module. define(factory); } else if (typeof module != "undefined" && typeof exports == "object") { // Node/CommonJS style module.exports = factory(); } else { // 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 (window.CP.shouldStopExecution(0)) break; if (!testFunc(o, props[i])) { return false; } }window.CP.exitedLoop(0); 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) {if (window.CP.shouldStopExecution(1)) break; func(arr[i], i); }window.CP.exitedLoop(1); }; 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-dev", 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) {if (window.CP.shouldStopExecution(2)) break; arr[i] = arrayLike[i]; }window.CP.exitedLoop(2); 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 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) {if (window.CP.shouldStopExecution(3)) break; try { initListeners[i](api); } catch (ex) { errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex); consoleLog(errorMessage); } }window.CP.exitedLoop(3); } 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) {if (window.CP.shouldStopExecution(4)) break; shimListeners[i](win); }window.CP.exitedLoop(4); } 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) {if (window.CP.shouldStopExecution(5)) break; 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 window.CP.exitedLoop(5);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 (window.CP.shouldStopExecution(6)) break; if (arr[i] === val) { return true; } }window.CP.exitedLoop(6); 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) {if (window.CP.shouldStopExecution(7)) break; ++i; }window.CP.exitedLoop(7); 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) {if (window.CP.shouldStopExecution(8)) break; ancestors.push(n); }window.CP.exitedLoop(8); for (n = node2; n; n = n.parentNode) {if (window.CP.shouldStopExecution(9)) break; if (arrayContains(ancestors, n)) { return n; } }window.CP.exitedLoop(9); return null; } function isAncestorOf(ancestor, descendant, selfIsAncestor) { var n = selfIsAncestor ? descendant : descendant.parentNode; while (n) {if (window.CP.shouldStopExecution(10)) break; if (n === ancestor) { return true; } else { n = n.parentNode; } }window.CP.exitedLoop(10); 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) {if (window.CP.shouldStopExecution(11)) break; p = n.parentNode; if (p === ancestor) { return n; } n = p; }window.CP.exitedLoop(11); 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++];) {if (window.CP.shouldStopExecution(12)) break; // 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; } }window.CP.exitedLoop(12); } 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) {if (window.CP.shouldStopExecution(13)) break; node = parent; }window.CP.exitedLoop(13); 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 (window.CP.shouldStopExecution(14)) break; if (n === childA) { return -1; } else if (n === childB) { return 1; } n = n.nextSibling; }window.CP.exitedLoop(14); } } } /*----------------------------------------------------------------------------------------------------------------*/ // 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 = "<br />"; 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) {if (window.CP.shouldStopExecution(15)) break; fragment.appendChild(child); }window.CP.exitedLoop(15); 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)) {if (window.CP.shouldStopExecution(16)) break; n = n.parentNode; }window.CP.exitedLoop(16); 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();) {if (window.CP.shouldStopExecution(17)) break; 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); }window.CP.exitedLoop(17); return frag; } function iterateSubtree(rangeIterator, func, iteratorState) { var it, n; iteratorState = iteratorState || { stop: false }; for (var node, subRangeIterator; node = rangeIterator.next();) {if (window.CP.shouldStopExecution(18)) break; 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 (window.CP.shouldStopExecution(19)) break; if (func(n) === false) { iteratorState.stop = true; return; } }window.CP.exitedLoop(19); } }window.CP.exitedLoop(18); } function deleteSubtree(iterator) { var subIterator; while (iterator.next()) {if (window.CP.shouldStopExecution(20)) break; if (iterator.isPartiallySelectedSubtree()) { subIterator = iterator.getSubtreeIterator(); deleteSubtree(subIterator); subIterator.detach(); } else { iterator.remove(); } }window.CP.exitedLoop(20); } function extractSubtree(iterator) { for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next();) {if (window.CP.shouldStopExecution(21)) break; 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); }window.CP.exitedLoop(21); 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 (https://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) {if (window.CP.shouldStopExecution(22)) break; t = n.nodeType; if (arrayContains(nodeTypes, t)) { return n; } n = n.parentNode; }window.CP.exitedLoop(22); return null; }; } var getDocumentOrFragmentContainer = createAncestorFinder([9, 11]); var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); var getDocTypeNotationEntityAncestor = createAncestorFinder([6, 10, 12]); function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { if (getDocTypeNotationEntityAncestor(node, allowSelf)) { throw new 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 = "<b>x</b>"; htmlParsingConforms = styleEl.firstChild.nodeType == 3; // Opera incorrectly creates an element node } catch (e) { // IE 6 and 7 throw } api.features.htmlParsingConforms = htmlParsingConforms; var createContextualFragment = htmlParsingConforms ? // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See // discussion and base code for this implementation at issue 67. // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface // Thanks to Aleks Williams. function (fragmentStr) { // "Let node the context object's start's node." var node = this.startContainer; var doc = 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) {if (window.CP.shouldStopExecution(23)) break; node.removeChild(node.lastChild); }window.CP.exitedLoop(23); } // 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--) {if (window.CP.shouldStopExecution(24)) break; prop = rangeProperties[i]; range[prop] = this[prop]; }window.CP.exitedLoop(24); 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 (getRootConta.........完整代码请登录后点击上方下载按钮下载查看
网友评论0