How to use getActiveRange method in wpt

Best JavaScript code snippet using wpt

implementation.js

Source:implementation.js Github

copy

Full Screen

...435 if (executionStackDepth == 0 && typeof range != "undefined") {436 globalRange = range;437 } else if (executionStackDepth == 0) {438 globalRange = null;439 globalRange = getActiveRange();440 }441 executionStackDepth++;442 try {443 var ret = callback();444 } catch(e) {445 executionStackDepth--;446 throw e;447 }448 executionStackDepth--;449 return ret;450}451function myExecCommand(command, showUi, value, range) {452 // "All of these methods must treat their command argument ASCII453 // case-insensitively."454 command = command.toLowerCase();455 // "If only one argument was provided, let show UI be false."456 //457 // If range was passed, I can't actually detect how many args were passed458 // . . .459 if (arguments.length == 1460 || (arguments.length >=4 && typeof showUi == "undefined")) {461 showUi = false;462 }463 // "If only one or two arguments were provided, let value be the empty464 // string."465 if (arguments.length <= 2466 || (arguments.length >=4 && typeof value == "undefined")) {467 value = "";468 }469 return editCommandMethod(command, range, (function(command, showUi, value) { return function() {470 // "If command is not supported or not enabled, return false."471 if (!(command in commands) || !myQueryCommandEnabled(command)) {472 return false;473 }474 // "Take the action for command, passing value to the instructions as an475 // argument."476 var ret = commands[command].action(value);477 // Check for bugs478 if (ret !== true && ret !== false) {479 throw "execCommand() didn't return true or false: " + ret;480 }481 // "If the previous step returned false, return false."482 if (ret === false) {483 return false;484 }485 // "Return true."486 return true;487 }})(command, showUi, value));488}489function myQueryCommandEnabled(command, range) {490 // "All of these methods must treat their command argument ASCII491 // case-insensitively."492 command = command.toLowerCase();493 return editCommandMethod(command, range, (function(command) { return function() {494 // "Return true if command is both supported and enabled, false495 // otherwise."496 if (!(command in commands)) {497 return false;498 }499 // "Among commands defined in this specification, those listed in500 // Miscellaneous commands are always enabled, except for the cut501 // command and the paste command. The other commands defined here are502 // enabled if the active range is not null, its start node is either503 // editable or an editing host, its end node is either editable or an504 // editing host, and there is some editing host that is an inclusive505 // ancestor of both its start node and its end node."506 return ["copy", "defaultparagraphseparator", "selectall", "stylewithcss",507 "usecss"].indexOf(command) != -1508 || (509 getActiveRange() !== null510 && (isEditable(getActiveRange().startContainer) || isEditingHost(getActiveRange().startContainer))511 && (isEditable(getActiveRange().endContainer) || isEditingHost(getActiveRange().endContainer))512 && (getInclusiveAncestors(getActiveRange().commonAncestorContainer).some(isEditingHost))513 );514 }})(command));515}516function myQueryCommandIndeterm(command, range) {517 // "All of these methods must treat their command argument ASCII518 // case-insensitively."519 command = command.toLowerCase();520 return editCommandMethod(command, range, (function(command) { return function() {521 // "If command is not supported or has no indeterminacy, return false."522 if (!(command in commands) || !("indeterm" in commands[command])) {523 return false;524 }525 // "Return true if command is indeterminate, otherwise false."526 return commands[command].indeterm();527 }})(command));528}529function myQueryCommandState(command, range) {530 // "All of these methods must treat their command argument ASCII531 // case-insensitively."532 command = command.toLowerCase();533 return editCommandMethod(command, range, (function(command) { return function() {534 // "If command is not supported or has no state, return false."535 if (!(command in commands) || !("state" in commands[command])) {536 return false;537 }538 // "If the state override for command is set, return it."539 if (typeof getStateOverride(command) != "undefined") {540 return getStateOverride(command);541 }542 // "Return true if command's state is true, otherwise false."543 return commands[command].state();544 }})(command));545}546// "When the queryCommandSupported(command) method on the HTMLDocument547// interface is invoked, the user agent must return true if command is548// supported, and false otherwise."549function myQueryCommandSupported(command) {550 // "All of these methods must treat their command argument ASCII551 // case-insensitively."552 command = command.toLowerCase();553 return command in commands;554}555function myQueryCommandValue(command, range) {556 // "All of these methods must treat their command argument ASCII557 // case-insensitively."558 command = command.toLowerCase();559 return editCommandMethod(command, range, function() {560 // "If command is not supported or has no value, return the empty string."561 if (!(command in commands) || !("value" in commands[command])) {562 return "";563 }564 // "If command is "fontSize" and its value override is set, convert the565 // value override to an integer number of pixels and return the legacy566 // font size for the result."567 if (command == "fontsize"568 && getValueOverride("fontsize") !== undefined) {569 return getLegacyFontSize(getValueOverride("fontsize"));570 }571 // "If the value override for command is set, return it."572 if (typeof getValueOverride(command) != "undefined") {573 return getValueOverride(command);574 }575 // "Return command's value."576 return commands[command].value();577 });578}579//@}580//////////////////////////////581///// Common definitions /////582//////////////////////////////583//@{584// "An HTML element is an Element whose namespace is the HTML namespace."585//586// I allow an extra argument to more easily check whether something is a587// particular HTML element, like isHtmlElement(node, "OL"). It accepts arrays588// too, like isHtmlElement(node, ["OL", "UL"]) to check if it's an ol or ul.589function isHtmlElement(node, tags) {590 if (typeof tags == "string") {591 tags = [tags];592 }593 if (typeof tags == "object") {594 tags = tags.map(function(tag) { return tag.toUpperCase() });595 }596 return node597 && node.nodeType == Node.ELEMENT_NODE598 && isHtmlNamespace(node.namespaceURI)599 && (typeof tags == "undefined" || tags.indexOf(node.tagName) != -1);600}601// "A prohibited paragraph child name is "address", "article", "aside",602// "blockquote", "caption", "center", "col", "colgroup", "dd", "details",603// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",604// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",605// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",606// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or607// "xmp"."608var prohibitedParagraphChildNames = ["address", "article", "aside",609 "blockquote", "caption", "center", "col", "colgroup", "dd", "details",610 "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",611 "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",612 "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",613 "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul",614 "xmp"];615// "A prohibited paragraph child is an HTML element whose local name is a616// prohibited paragraph child name."617function isProhibitedParagraphChild(node) {618 return isHtmlElement(node, prohibitedParagraphChildNames);619}620// "A block node is either an Element whose "display" property does not have621// resolved value "inline" or "inline-block" or "inline-table" or "none", or a622// Document, or a DocumentFragment."623function isBlockNode(node) {624 return node625 && ((node.nodeType == Node.ELEMENT_NODE && ["inline", "inline-block", "inline-table", "none"].indexOf(getComputedStyle(node).display) == -1)626 || node.nodeType == Node.DOCUMENT_NODE627 || node.nodeType == Node.DOCUMENT_FRAGMENT_NODE);628}629// "An inline node is a node that is not a block node."630function isInlineNode(node) {631 return node && !isBlockNode(node);632}633// "An editing host is a node that is either an HTML element with a634// contenteditable attribute set to the true state, or the HTML element child635// of a Document whose designMode is enabled."636function isEditingHost(node) {637 return node638 && isHtmlElement(node)639 && (node.contentEditable == "true"640 || (node.parentNode641 && node.parentNode.nodeType == Node.DOCUMENT_NODE642 && node.parentNode.designMode == "on"));643}644// "Something is editable if it is a node; it is not an editing host; it does645// not have a contenteditable attribute set to the false state; its parent is646// an editing host or editable; and either it is an HTML element, or it is an647// svg or math element, or it is not an Element and its parent is an HTML648// element."649function isEditable(node) {650 return node651 && !isEditingHost(node)652 && (node.nodeType != Node.ELEMENT_NODE || node.contentEditable != "false")653 && (isEditingHost(node.parentNode) || isEditable(node.parentNode))654 && (isHtmlElement(node)655 || (node.nodeType == Node.ELEMENT_NODE && node.namespaceURI == "http://www.w3.org/2000/svg" && node.localName == "svg")656 || (node.nodeType == Node.ELEMENT_NODE && node.namespaceURI == "http://www.w3.org/1998/Math/MathML" && node.localName == "math")657 || (node.nodeType != Node.ELEMENT_NODE && isHtmlElement(node.parentNode)));658}659// Helper function, not defined in the spec660function hasEditableDescendants(node) {661 for (var i = 0; i < node.childNodes.length; i++) {662 if (isEditable(node.childNodes[i])663 || hasEditableDescendants(node.childNodes[i])) {664 return true;665 }666 }667 return false;668}669// "The editing host of node is null if node is neither editable nor an editing670// host; node itself, if node is an editing host; or the nearest ancestor of671// node that is an editing host, if node is editable."672function getEditingHostOf(node) {673 if (isEditingHost(node)) {674 return node;675 } else if (isEditable(node)) {676 var ancestor = node.parentNode;677 while (!isEditingHost(ancestor)) {678 ancestor = ancestor.parentNode;679 }680 return ancestor;681 } else {682 return null;683 }684}685// "Two nodes are in the same editing host if the editing host of the first is686// non-null and the same as the editing host of the second."687function inSameEditingHost(node1, node2) {688 return getEditingHostOf(node1)689 && getEditingHostOf(node1) == getEditingHostOf(node2);690}691// "A collapsed line break is a br that begins a line box which has nothing692// else in it, and therefore has zero height."693function isCollapsedLineBreak(br) {694 if (!isHtmlElement(br, "br")) {695 return false;696 }697 // Add a zwsp after it and see if that changes the height of the nearest698 // non-inline parent. Note: this is not actually reliable, because the699 // parent might have a fixed height or something.700 var ref = br.parentNode;701 while (getComputedStyle(ref).display == "inline") {702 ref = ref.parentNode;703 }704 var refStyle = ref.hasAttribute("style") ? ref.getAttribute("style") : null;705 ref.style.height = "auto";706 ref.style.maxHeight = "none";707 ref.style.minHeight = "0";708 var space = document.createTextNode("\u200b");709 var origHeight = ref.offsetHeight;710 if (origHeight == 0) {711 throw "isCollapsedLineBreak: original height is zero, bug?";712 }713 br.parentNode.insertBefore(space, br.nextSibling);714 var finalHeight = ref.offsetHeight;715 space.parentNode.removeChild(space);716 if (refStyle === null) {717 // Without the setAttribute() line, removeAttribute() doesn't work in718 // Chrome 14 dev. I have no idea why.719 ref.setAttribute("style", "");720 ref.removeAttribute("style");721 } else {722 ref.setAttribute("style", refStyle);723 }724 // Allow some leeway in case the zwsp didn't create a whole new line, but725 // only made an existing line slightly higher. Firefox 6.0a2 shows this726 // behavior when the first line is bold.727 return origHeight < finalHeight - 5;728}729// "An extraneous line break is a br that has no visual effect, in that730// removing it from the DOM would not change layout, except that a br that is731// the sole child of an li is not extraneous."732//733// FIXME: This doesn't work in IE, since IE ignores display: none in734// contenteditable.735function isExtraneousLineBreak(br) {736 if (!isHtmlElement(br, "br")) {737 return false;738 }739 if (isHtmlElement(br.parentNode, "li")740 && br.parentNode.childNodes.length == 1) {741 return false;742 }743 // Make the line break disappear and see if that changes the block's744 // height. Yes, this is an absurd hack. We have to reset height etc. on745 // the reference node because otherwise its height won't change if it's not746 // auto.747 var ref = br.parentNode;748 while (getComputedStyle(ref).display == "inline") {749 ref = ref.parentNode;750 }751 var refStyle = ref.hasAttribute("style") ? ref.getAttribute("style") : null;752 ref.style.height = "auto";753 ref.style.maxHeight = "none";754 ref.style.minHeight = "0";755 var brStyle = br.hasAttribute("style") ? br.getAttribute("style") : null;756 var origHeight = ref.offsetHeight;757 if (origHeight == 0) {758 throw "isExtraneousLineBreak: original height is zero, bug?";759 }760 br.setAttribute("style", "display:none");761 var finalHeight = ref.offsetHeight;762 if (refStyle === null) {763 // Without the setAttribute() line, removeAttribute() doesn't work in764 // Chrome 14 dev. I have no idea why.765 ref.setAttribute("style", "");766 ref.removeAttribute("style");767 } else {768 ref.setAttribute("style", refStyle);769 }770 if (brStyle === null) {771 br.removeAttribute("style");772 } else {773 br.setAttribute("style", brStyle);774 }775 return origHeight == finalHeight;776}777// "A whitespace node is either a Text node whose data is the empty string; or778// a Text node whose data consists only of one or more tabs (0x0009), line779// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose780// parent is an Element whose resolved value for "white-space" is "normal" or781// "nowrap"; or a Text node whose data consists only of one or more tabs782// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose783// parent is an Element whose resolved value for "white-space" is "pre-line"."784function isWhitespaceNode(node) {785 return node786 && node.nodeType == Node.TEXT_NODE787 && (node.data == ""788 || (789 /^[\t\n\r ]+$/.test(node.data)790 && node.parentNode791 && node.parentNode.nodeType == Node.ELEMENT_NODE792 && ["normal", "nowrap"].indexOf(getComputedStyle(node.parentNode).whiteSpace) != -1793 ) || (794 /^[\t\r ]+$/.test(node.data)795 && node.parentNode796 && node.parentNode.nodeType == Node.ELEMENT_NODE797 && getComputedStyle(node.parentNode).whiteSpace == "pre-line"798 ));799}800// "node is a collapsed whitespace node if the following algorithm returns801// true:"802function isCollapsedWhitespaceNode(node) {803 // "If node is not a whitespace node, return false."804 if (!isWhitespaceNode(node)) {805 return false;806 }807 // "If node's data is the empty string, return true."808 if (node.data == "") {809 return true;810 }811 // "Let ancestor be node's parent."812 var ancestor = node.parentNode;813 // "If ancestor is null, return true."814 if (!ancestor) {815 return true;816 }817 // "If the "display" property of some ancestor of node has resolved value818 // "none", return true."819 if (getAncestors(node).some(function(ancestor) {820 return ancestor.nodeType == Node.ELEMENT_NODE821 && getComputedStyle(ancestor).display == "none";822 })) {823 return true;824 }825 // "While ancestor is not a block node and its parent is not null, set826 // ancestor to its parent."827 while (!isBlockNode(ancestor)828 && ancestor.parentNode) {829 ancestor = ancestor.parentNode;830 }831 // "Let reference be node."832 var reference = node;833 // "While reference is a descendant of ancestor:"834 while (reference != ancestor) {835 // "Let reference be the node before it in tree order."836 reference = previousNode(reference);837 // "If reference is a block node or a br, return true."838 if (isBlockNode(reference)839 || isHtmlElement(reference, "br")) {840 return true;841 }842 // "If reference is a Text node that is not a whitespace node, or is an843 // img, break from this loop."844 if ((reference.nodeType == Node.TEXT_NODE && !isWhitespaceNode(reference))845 || isHtmlElement(reference, "img")) {846 break;847 }848 }849 // "Let reference be node."850 reference = node;851 // "While reference is a descendant of ancestor:"852 var stop = nextNodeDescendants(ancestor);853 while (reference != stop) {854 // "Let reference be the node after it in tree order, or null if there855 // is no such node."856 reference = nextNode(reference);857 // "If reference is a block node or a br, return true."858 if (isBlockNode(reference)859 || isHtmlElement(reference, "br")) {860 return true;861 }862 // "If reference is a Text node that is not a whitespace node, or is an863 // img, break from this loop."864 if ((reference && reference.nodeType == Node.TEXT_NODE && !isWhitespaceNode(reference))865 || isHtmlElement(reference, "img")) {866 break;867 }868 }869 // "Return false."870 return false;871}872// "Something is visible if it is a node that either is a block node, or a Text873// node that is not a collapsed whitespace node, or an img, or a br that is not874// an extraneous line break, or any node with a visible descendant; excluding875// any node with an ancestor container Element whose "display" property has876// resolved value "none"."877function isVisible(node) {878 if (!node) {879 return false;880 }881 if (getAncestors(node).concat(node)882 .filter(function(node) { return node.nodeType == Node.ELEMENT_NODE })883 .some(function(node) { return getComputedStyle(node).display == "none" })) {884 return false;885 }886 if (isBlockNode(node)887 || (node.nodeType == Node.TEXT_NODE && !isCollapsedWhitespaceNode(node))888 || isHtmlElement(node, "img")889 || (isHtmlElement(node, "br") && !isExtraneousLineBreak(node))) {890 return true;891 }892 for (var i = 0; i < node.childNodes.length; i++) {893 if (isVisible(node.childNodes[i])) {894 return true;895 }896 }897 return false;898}899// "Something is invisible if it is a node that is not visible."900function isInvisible(node) {901 return node && !isVisible(node);902}903// "A collapsed block prop is either a collapsed line break that is not an904// extraneous line break, or an Element that is an inline node and whose905// children are all either invisible or collapsed block props and that has at906// least one child that is a collapsed block prop."907function isCollapsedBlockProp(node) {908 if (isCollapsedLineBreak(node)909 && !isExtraneousLineBreak(node)) {910 return true;911 }912 if (!isInlineNode(node)913 || node.nodeType != Node.ELEMENT_NODE) {914 return false;915 }916 var hasCollapsedBlockPropChild = false;917 for (var i = 0; i < node.childNodes.length; i++) {918 if (!isInvisible(node.childNodes[i])919 && !isCollapsedBlockProp(node.childNodes[i])) {920 return false;921 }922 if (isCollapsedBlockProp(node.childNodes[i])) {923 hasCollapsedBlockPropChild = true;924 }925 }926 return hasCollapsedBlockPropChild;927}928// "The active range is the range of the selection given by calling929// getSelection() on the context object. (Thus the active range may be null.)"930//931// We cheat and return globalRange if that's defined. We also ensure that the932// active range meets the requirements that selection boundary points are933// supposed to meet, i.e., that the nodes are both Text or Element nodes that934// descend from a Document.935function getActiveRange() {936 var ret;937 if (globalRange) {938 ret = globalRange;939 } else if (getSelection().rangeCount) {940 ret = getSelection().getRangeAt(0);941 } else {942 return null;943 }944 if ([Node.TEXT_NODE, Node.ELEMENT_NODE].indexOf(ret.startContainer.nodeType) == -1945 || [Node.TEXT_NODE, Node.ELEMENT_NODE].indexOf(ret.endContainer.nodeType) == -1946 || !ret.startContainer.ownerDocument947 || !ret.endContainer.ownerDocument948 || !isDescendant(ret.startContainer, ret.startContainer.ownerDocument)949 || !isDescendant(ret.endContainer, ret.endContainer.ownerDocument)) {950 throw "Invalid active range; test bug?";951 }952 return ret;953}954// "For some commands, each HTMLDocument must have a boolean state override955// and/or a string value override. These do not change the command's state or956// value, but change the way some algorithms behave, as specified in those957// algorithms' definitions. Initially, both must be unset for every command.958// Whenever the number of ranges in the Selection changes to something959// different, and whenever a boundary point of the range at a given index in960// the Selection changes to something different, the state override and value961// override must be unset for every command."962//963// We implement this crudely by using setters and getters. To verify that the964// selection hasn't changed, we copy the active range and just check the965// endpoints match. This isn't really correct, but it's good enough for us.966// Unset state/value overrides are undefined. We put everything in a function967// so no one can access anything except via the provided functions, since968// otherwise callers might mistakenly use outdated overrides (if the selection969// has changed).970var getStateOverride, setStateOverride, unsetStateOverride,971 getValueOverride, setValueOverride, unsetValueOverride;972(function() {973 var stateOverrides = {};974 var valueOverrides = {};975 var storedRange = null;976 function resetOverrides() {977 if (!storedRange978 || storedRange.startContainer != getActiveRange().startContainer979 || storedRange.endContainer != getActiveRange().endContainer980 || storedRange.startOffset != getActiveRange().startOffset981 || storedRange.endOffset != getActiveRange().endOffset) {982 stateOverrides = {};983 valueOverrides = {};984 storedRange = getActiveRange().cloneRange();985 }986 }987 getStateOverride = function(command) {988 resetOverrides();989 return stateOverrides[command];990 };991 setStateOverride = function(command, newState) {992 resetOverrides();993 stateOverrides[command] = newState;994 };995 unsetStateOverride = function(command) {996 resetOverrides();997 delete stateOverrides[command];998 }999 getValueOverride = function(command) {1000 resetOverrides();1001 return valueOverrides[command];1002 }1003 // "The value override for the backColor command must be the same as the1004 // value override for the hiliteColor command, such that setting one sets1005 // the other to the same thing and unsetting one unsets the other."1006 setValueOverride = function(command, newValue) {1007 resetOverrides();1008 valueOverrides[command] = newValue;1009 if (command == "backcolor") {1010 valueOverrides.hilitecolor = newValue;1011 } else if (command == "hilitecolor") {1012 valueOverrides.backcolor = newValue;1013 }1014 }1015 unsetValueOverride = function(command) {1016 resetOverrides();1017 delete valueOverrides[command];1018 if (command == "backcolor") {1019 delete valueOverrides.hilitecolor;1020 } else if (command == "hilitecolor") {1021 delete valueOverrides.backcolor;1022 }1023 }1024})();1025//@}1026/////////////////////////////1027///// Common algorithms /////1028/////////////////////////////1029///// Assorted common algorithms /////1030//@{1031// Magic array of extra ranges whose endpoints we want to preserve.1032var extraRanges = [];1033function movePreservingRanges(node, newParent, newIndex) {1034 // For convenience, I allow newIndex to be -1 to mean "insert at the end".1035 if (newIndex == -1) {1036 newIndex = newParent.childNodes.length;1037 }1038 // "When the user agent is to move a Node to a new location, preserving1039 // ranges, it must remove the Node from its original parent (if any), then1040 // insert it in the new location. In doing so, however, it must ignore the1041 // regular range mutation rules, and instead follow these rules:"1042 // "Let node be the moved Node, old parent and old index be the old parent1043 // (which may be null) and index, and new parent and new index be the new1044 // parent and index."1045 var oldParent = node.parentNode;1046 var oldIndex = getNodeIndex(node);1047 // We preserve the global range object, the ranges in the selection, and1048 // any range that's in the extraRanges array. Any other ranges won't get1049 // updated, because we have no references to them.1050 var ranges = [globalRange].concat(extraRanges);1051 for (var i = 0; i < getSelection().rangeCount; i++) {1052 ranges.push(getSelection().getRangeAt(i));1053 }1054 var boundaryPoints = [];1055 ranges.forEach(function(range) {1056 boundaryPoints.push([range.startContainer, range.startOffset]);1057 boundaryPoints.push([range.endContainer, range.endOffset]);1058 });1059 boundaryPoints.forEach(function(boundaryPoint) {1060 // "If a boundary point's node is the same as or a descendant of node,1061 // leave it unchanged, so it moves to the new location."1062 //1063 // No modifications necessary.1064 // "If a boundary point's node is new parent and its offset is greater1065 // than new index, add one to its offset."1066 if (boundaryPoint[0] == newParent1067 && boundaryPoint[1] > newIndex) {1068 boundaryPoint[1]++;1069 }1070 // "If a boundary point's node is old parent and its offset is old index or1071 // old index + 1, set its node to new parent and add new index − old index1072 // to its offset."1073 if (boundaryPoint[0] == oldParent1074 && (boundaryPoint[1] == oldIndex1075 || boundaryPoint[1] == oldIndex + 1)) {1076 boundaryPoint[0] = newParent;1077 boundaryPoint[1] += newIndex - oldIndex;1078 }1079 // "If a boundary point's node is old parent and its offset is greater than1080 // old index + 1, subtract one from its offset."1081 if (boundaryPoint[0] == oldParent1082 && boundaryPoint[1] > oldIndex + 1) {1083 boundaryPoint[1]--;1084 }1085 });1086 // Now actually move it and preserve the ranges.1087 if (newParent.childNodes.length == newIndex) {1088 newParent.appendChild(node);1089 } else {1090 newParent.insertBefore(node, newParent.childNodes[newIndex]);1091 }1092 globalRange.setStart(boundaryPoints[0][0], boundaryPoints[0][1]);1093 globalRange.setEnd(boundaryPoints[1][0], boundaryPoints[1][1]);1094 for (var i = 0; i < extraRanges.length; i++) {1095 extraRanges[i].setStart(boundaryPoints[2*i + 2][0], boundaryPoints[2*i + 2][1]);1096 extraRanges[i].setEnd(boundaryPoints[2*i + 3][0], boundaryPoints[2*i + 3][1]);1097 }1098 getSelection().removeAllRanges();1099 for (var i = 1 + extraRanges.length; i < ranges.length; i++) {1100 var newRange = document.createRange();1101 newRange.setStart(boundaryPoints[2*i][0], boundaryPoints[2*i][1]);1102 newRange.setEnd(boundaryPoints[2*i + 1][0], boundaryPoints[2*i + 1][1]);1103 getSelection().addRange(newRange);1104 }1105}1106function setTagName(element, newName) {1107 // "If element is an HTML element with local name equal to new name, return1108 // element."1109 if (isHtmlElement(element, newName.toUpperCase())) {1110 return element;1111 }1112 // "If element's parent is null, return element."1113 if (!element.parentNode) {1114 return element;1115 }1116 // "Let replacement element be the result of calling createElement(new1117 // name) on the ownerDocument of element."1118 var replacementElement = element.ownerDocument.createElement(newName);1119 // "Insert replacement element into element's parent immediately before1120 // element."1121 element.parentNode.insertBefore(replacementElement, element);1122 // "Copy all attributes of element to replacement element, in order."1123 for (var i = 0; i < element.attributes.length; i++) {1124 replacementElement.setAttributeNS(element.attributes[i].namespaceURI, element.attributes[i].name, element.attributes[i].value);1125 }1126 // "While element has children, append the first child of element as the1127 // last child of replacement element, preserving ranges."1128 while (element.childNodes.length) {1129 movePreservingRanges(element.firstChild, replacementElement, replacementElement.childNodes.length);1130 }1131 // "Remove element from its parent."1132 element.parentNode.removeChild(element);1133 // "Return replacement element."1134 return replacementElement;1135}1136function removeExtraneousLineBreaksBefore(node) {1137 // "Let ref be the previousSibling of node."1138 var ref = node.previousSibling;1139 // "If ref is null, abort these steps."1140 if (!ref) {1141 return;1142 }1143 // "While ref has children, set ref to its lastChild."1144 while (ref.hasChildNodes()) {1145 ref = ref.lastChild;1146 }1147 // "While ref is invisible but not an extraneous line break, and ref does1148 // not equal node's parent, set ref to the node before it in tree order."1149 while (isInvisible(ref)1150 && !isExtraneousLineBreak(ref)1151 && ref != node.parentNode) {1152 ref = previousNode(ref);1153 }1154 // "If ref is an editable extraneous line break, remove it from its1155 // parent."1156 if (isEditable(ref)1157 && isExtraneousLineBreak(ref)) {1158 ref.parentNode.removeChild(ref);1159 }1160}1161function removeExtraneousLineBreaksAtTheEndOf(node) {1162 // "Let ref be node."1163 var ref = node;1164 // "While ref has children, set ref to its lastChild."1165 while (ref.hasChildNodes()) {1166 ref = ref.lastChild;1167 }1168 // "While ref is invisible but not an extraneous line break, and ref does1169 // not equal node, set ref to the node before it in tree order."1170 while (isInvisible(ref)1171 && !isExtraneousLineBreak(ref)1172 && ref != node) {1173 ref = previousNode(ref);1174 }1175 // "If ref is an editable extraneous line break:"1176 if (isEditable(ref)1177 && isExtraneousLineBreak(ref)) {1178 // "While ref's parent is editable and invisible, set ref to its1179 // parent."1180 while (isEditable(ref.parentNode)1181 && isInvisible(ref.parentNode)) {1182 ref = ref.parentNode;1183 }1184 // "Remove ref from its parent."1185 ref.parentNode.removeChild(ref);1186 }1187}1188// "To remove extraneous line breaks from a node, first remove extraneous line1189// breaks before it, then remove extraneous line breaks at the end of it."1190function removeExtraneousLineBreaksFrom(node) {1191 removeExtraneousLineBreaksBefore(node);1192 removeExtraneousLineBreaksAtTheEndOf(node);1193}1194//@}1195///// Wrapping a list of nodes /////1196//@{1197function wrap(nodeList, siblingCriteria, newParentInstructions) {1198 // "If not provided, sibling criteria returns false and new parent1199 // instructions returns null."1200 if (typeof siblingCriteria == "undefined") {1201 siblingCriteria = function() { return false };1202 }1203 if (typeof newParentInstructions == "undefined") {1204 newParentInstructions = function() { return null };1205 }1206 // "If every member of node list is invisible, and none is a br, return1207 // null and abort these steps."1208 if (nodeList.every(isInvisible)1209 && !nodeList.some(function(node) { return isHtmlElement(node, "br") })) {1210 return null;1211 }1212 // "If node list's first member's parent is null, return null and abort1213 // these steps."1214 if (!nodeList[0].parentNode) {1215 return null;1216 }1217 // "If node list's last member is an inline node that's not a br, and node1218 // list's last member's nextSibling is a br, append that br to node list."1219 if (isInlineNode(nodeList[nodeList.length - 1])1220 && !isHtmlElement(nodeList[nodeList.length - 1], "br")1221 && isHtmlElement(nodeList[nodeList.length - 1].nextSibling, "br")) {1222 nodeList.push(nodeList[nodeList.length - 1].nextSibling);1223 }1224 // "While node list's first member's previousSibling is invisible, prepend1225 // it to node list."1226 while (isInvisible(nodeList[0].previousSibling)) {1227 nodeList.unshift(nodeList[0].previousSibling);1228 }1229 // "While node list's last member's nextSibling is invisible, append it to1230 // node list."1231 while (isInvisible(nodeList[nodeList.length - 1].nextSibling)) {1232 nodeList.push(nodeList[nodeList.length - 1].nextSibling);1233 }1234 // "If the previousSibling of the first member of node list is editable and1235 // running sibling criteria on it returns true, let new parent be the1236 // previousSibling of the first member of node list."1237 var newParent;1238 if (isEditable(nodeList[0].previousSibling)1239 && siblingCriteria(nodeList[0].previousSibling)) {1240 newParent = nodeList[0].previousSibling;1241 // "Otherwise, if the nextSibling of the last member of node list is1242 // editable and running sibling criteria on it returns true, let new parent1243 // be the nextSibling of the last member of node list."1244 } else if (isEditable(nodeList[nodeList.length - 1].nextSibling)1245 && siblingCriteria(nodeList[nodeList.length - 1].nextSibling)) {1246 newParent = nodeList[nodeList.length - 1].nextSibling;1247 // "Otherwise, run new parent instructions, and let new parent be the1248 // result."1249 } else {1250 newParent = newParentInstructions();1251 }1252 // "If new parent is null, abort these steps and return null."1253 if (!newParent) {1254 return null;1255 }1256 // "If new parent's parent is null:"1257 if (!newParent.parentNode) {1258 // "Insert new parent into the parent of the first member of node list1259 // immediately before the first member of node list."1260 nodeList[0].parentNode.insertBefore(newParent, nodeList[0]);1261 // "If any range has a boundary point with node equal to the parent of1262 // new parent and offset equal to the index of new parent, add one to1263 // that boundary point's offset."1264 //1265 // Only try to fix the global range.1266 if (globalRange.startContainer == newParent.parentNode1267 && globalRange.startOffset == getNodeIndex(newParent)) {1268 globalRange.setStart(globalRange.startContainer, globalRange.startOffset + 1);1269 }1270 if (globalRange.endContainer == newParent.parentNode1271 && globalRange.endOffset == getNodeIndex(newParent)) {1272 globalRange.setEnd(globalRange.endContainer, globalRange.endOffset + 1);1273 }1274 }1275 // "Let original parent be the parent of the first member of node list."1276 var originalParent = nodeList[0].parentNode;1277 // "If new parent is before the first member of node list in tree order:"1278 if (isBefore(newParent, nodeList[0])) {1279 // "If new parent is not an inline node, but the last visible child of1280 // new parent and the first visible member of node list are both inline1281 // nodes, and the last child of new parent is not a br, call1282 // createElement("br") on the ownerDocument of new parent and append1283 // the result as the last child of new parent."1284 if (!isInlineNode(newParent)1285 && isInlineNode([].filter.call(newParent.childNodes, isVisible).slice(-1)[0])1286 && isInlineNode(nodeList.filter(isVisible)[0])1287 && !isHtmlElement(newParent.lastChild, "BR")) {1288 newParent.appendChild(newParent.ownerDocument.createElement("br"));1289 }1290 // "For each node in node list, append node as the last child of new1291 // parent, preserving ranges."1292 for (var i = 0; i < nodeList.length; i++) {1293 movePreservingRanges(nodeList[i], newParent, -1);1294 }1295 // "Otherwise:"1296 } else {1297 // "If new parent is not an inline node, but the first visible child of1298 // new parent and the last visible member of node list are both inline1299 // nodes, and the last member of node list is not a br, call1300 // createElement("br") on the ownerDocument of new parent and insert1301 // the result as the first child of new parent."1302 if (!isInlineNode(newParent)1303 && isInlineNode([].filter.call(newParent.childNodes, isVisible)[0])1304 && isInlineNode(nodeList.filter(isVisible).slice(-1)[0])1305 && !isHtmlElement(nodeList[nodeList.length - 1], "BR")) {1306 newParent.insertBefore(newParent.ownerDocument.createElement("br"), newParent.firstChild);1307 }1308 // "For each node in node list, in reverse order, insert node as the1309 // first child of new parent, preserving ranges."1310 for (var i = nodeList.length - 1; i >= 0; i--) {1311 movePreservingRanges(nodeList[i], newParent, 0);1312 }1313 }1314 // "If original parent is editable and has no children, remove it from its1315 // parent."1316 if (isEditable(originalParent) && !originalParent.hasChildNodes()) {1317 originalParent.parentNode.removeChild(originalParent);1318 }1319 // "If new parent's nextSibling is editable and running sibling criteria on1320 // it returns true:"1321 if (isEditable(newParent.nextSibling)1322 && siblingCriteria(newParent.nextSibling)) {1323 // "If new parent is not an inline node, but new parent's last child1324 // and new parent's nextSibling's first child are both inline nodes,1325 // and new parent's last child is not a br, call createElement("br") on1326 // the ownerDocument of new parent and append the result as the last1327 // child of new parent."1328 if (!isInlineNode(newParent)1329 && isInlineNode(newParent.lastChild)1330 && isInlineNode(newParent.nextSibling.firstChild)1331 && !isHtmlElement(newParent.lastChild, "BR")) {1332 newParent.appendChild(newParent.ownerDocument.createElement("br"));1333 }1334 // "While new parent's nextSibling has children, append its first child1335 // as the last child of new parent, preserving ranges."1336 while (newParent.nextSibling.hasChildNodes()) {1337 movePreservingRanges(newParent.nextSibling.firstChild, newParent, -1);1338 }1339 // "Remove new parent's nextSibling from its parent."1340 newParent.parentNode.removeChild(newParent.nextSibling);1341 }1342 // "Remove extraneous line breaks from new parent."1343 removeExtraneousLineBreaksFrom(newParent);1344 // "Return new parent."1345 return newParent;1346}1347//@}1348///// Allowed children /////1349//@{1350// "A name of an element with inline contents is "a", "abbr", "b", "bdi",1351// "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",1352// "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",1353// "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",1354// "xmp", "big", "blink", "font", "marquee", "nobr", or "tt"."1355var namesOfElementsWithInlineContents = ["a", "abbr", "b", "bdi", "bdo",1356 "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",1357 "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",1358 "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",1359 "xmp", "big", "blink", "font", "marquee", "nobr", "tt"];1360// "An element with inline contents is an HTML element whose local name is a1361// name of an element with inline contents."1362function isElementWithInlineContents(node) {1363 return isHtmlElement(node, namesOfElementsWithInlineContents);1364}1365function isAllowedChild(child, parent_) {1366 // "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or1367 // an HTML element with local name equal to one of those, and child is a1368 // Text node whose data does not consist solely of space characters, return1369 // false."1370 if ((["colgroup", "table", "tbody", "tfoot", "thead", "tr"].indexOf(parent_) != -11371 || isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))1372 && typeof child == "object"1373 && child.nodeType == Node.TEXT_NODE1374 && !/^[ \t\n\f\r]*$/.test(child.data)) {1375 return false;1376 }1377 // "If parent is "script", "style", "plaintext", or "xmp", or an HTML1378 // element with local name equal to one of those, and child is not a Text1379 // node, return false."1380 if ((["script", "style", "plaintext", "xmp"].indexOf(parent_) != -11381 || isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))1382 && (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {1383 return false;1384 }1385 // "If child is a Document, DocumentFragment, or DocumentType, return1386 // false."1387 if (typeof child == "object"1388 && (child.nodeType == Node.DOCUMENT_NODE1389 || child.nodeType == Node.DOCUMENT_FRAGMENT_NODE1390 || child.nodeType == Node.DOCUMENT_TYPE_NODE)) {1391 return false;1392 }1393 // "If child is an HTML element, set child to the local name of child."1394 if (isHtmlElement(child)) {1395 child = child.tagName.toLowerCase();1396 }1397 // "If child is not a string, return true."1398 if (typeof child != "string") {1399 return true;1400 }1401 // "If parent is an HTML element:"1402 if (isHtmlElement(parent_)) {1403 // "If child is "a", and parent or some ancestor of parent is an a,1404 // return false."1405 //1406 // "If child is a prohibited paragraph child name and parent or some1407 // ancestor of parent is an element with inline contents, return1408 // false."1409 //1410 // "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or1411 // some ancestor of parent is an HTML element with local name "h1",1412 // "h2", "h3", "h4", "h5", or "h6", return false."1413 var ancestor = parent_;1414 while (ancestor) {1415 if (child == "a" && isHtmlElement(ancestor, "a")) {1416 return false;1417 }1418 if (prohibitedParagraphChildNames.indexOf(child) != -11419 && isElementWithInlineContents(ancestor)) {1420 return false;1421 }1422 if (/^h[1-6]$/.test(child)1423 && isHtmlElement(ancestor)1424 && /^H[1-6]$/.test(ancestor.tagName)) {1425 return false;1426 }1427 ancestor = ancestor.parentNode;1428 }1429 // "Let parent be the local name of parent."1430 parent_ = parent_.tagName.toLowerCase();1431 }1432 // "If parent is an Element or DocumentFragment, return true."1433 if (typeof parent_ == "object"1434 && (parent_.nodeType == Node.ELEMENT_NODE1435 || parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {1436 return true;1437 }1438 // "If parent is not a string, return false."1439 if (typeof parent_ != "string") {1440 return false;1441 }1442 // "If parent is on the left-hand side of an entry on the following list,1443 // then return true if child is listed on the right-hand side of that1444 // entry, and false otherwise."1445 switch (parent_) {1446 case "colgroup":1447 return child == "col";1448 case "table":1449 return ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1;1450 case "tbody":1451 case "thead":1452 case "tfoot":1453 return ["td", "th", "tr"].indexOf(child) != -1;1454 case "tr":1455 return ["td", "th"].indexOf(child) != -1;1456 case "dl":1457 return ["dt", "dd"].indexOf(child) != -1;1458 case "dir":1459 case "ol":1460 case "ul":1461 return ["dir", "li", "ol", "ul"].indexOf(child) != -1;1462 case "hgroup":1463 return /^h[1-6]$/.test(child);1464 }1465 // "If child is "body", "caption", "col", "colgroup", "frame", "frameset",1466 // "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return1467 // false."1468 if (["body", "caption", "col", "colgroup", "frame", "frameset", "head",1469 "html", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1) {1470 return false;1471 }1472 // "If child is "dd" or "dt" and parent is not "dl", return false."1473 if (["dd", "dt"].indexOf(child) != -11474 && parent_ != "dl") {1475 return false;1476 }1477 // "If child is "li" and parent is not "ol" or "ul", return false."1478 if (child == "li"1479 && parent_ != "ol"1480 && parent_ != "ul") {1481 return false;1482 }1483 // "If parent is on the left-hand side of an entry on the following list1484 // and child is listed on the right-hand side of that entry, return false."1485 var table = [1486 [["a"], ["a"]],1487 [["dd", "dt"], ["dd", "dt"]],1488 [["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],1489 [["li"], ["li"]],1490 [["nobr"], ["nobr"]],1491 [namesOfElementsWithInlineContents, prohibitedParagraphChildNames],1492 [["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]],1493 ];1494 for (var i = 0; i < table.length; i++) {1495 if (table[i][0].indexOf(parent_) != -11496 && table[i][1].indexOf(child) != -1) {1497 return false;1498 }1499 }1500 // "Return true."1501 return true;1502}1503//@}1504//////////////////////////////////////1505///// Inline formatting commands /////1506//////////////////////////////////////1507///// Inline formatting command definitions /////1508//@{1509// "A node node is effectively contained in a range range if range is not1510// collapsed, and at least one of the following holds:"1511function isEffectivelyContained(node, range) {1512 if (range.collapsed) {1513 return false;1514 }1515 // "node is contained in range."1516 if (isContained(node, range)) {1517 return true;1518 }1519 // "node is range's start node, it is a Text node, and its length is1520 // different from range's start offset."1521 if (node == range.startContainer1522 && node.nodeType == Node.TEXT_NODE1523 && getNodeLength(node) != range.startOffset) {1524 return true;1525 }1526 // "node is range's end node, it is a Text node, and range's end offset is1527 // not 0."1528 if (node == range.endContainer1529 && node.nodeType == Node.TEXT_NODE1530 && range.endOffset != 0) {1531 return true;1532 }1533 // "node has at least one child; and all its children are effectively1534 // contained in range; and either range's start node is not a descendant of1535 // node or is not a Text node or range's start offset is zero; and either1536 // range's end node is not a descendant of node or is not a Text node or1537 // range's end offset is its end node's length."1538 if (node.hasChildNodes()1539 && [].every.call(node.childNodes, function(child) { return isEffectivelyContained(child, range) })1540 && (!isDescendant(range.startContainer, node)1541 || range.startContainer.nodeType != Node.TEXT_NODE1542 || range.startOffset == 0)1543 && (!isDescendant(range.endContainer, node)1544 || range.endContainer.nodeType != Node.TEXT_NODE1545 || range.endOffset == getNodeLength(range.endContainer))) {1546 return true;1547 }1548 return false;1549}1550// Like get(All)ContainedNodes(), but for effectively contained nodes.1551function getEffectivelyContainedNodes(range, condition) {1552 if (typeof condition == "undefined") {1553 condition = function() { return true };1554 }1555 var node = range.startContainer;1556 while (isEffectivelyContained(node.parentNode, range)) {1557 node = node.parentNode;1558 }1559 var stop = nextNodeDescendants(range.endContainer);1560 var nodeList = [];1561 while (isBefore(node, stop)) {1562 if (isEffectivelyContained(node, range)1563 && condition(node)) {1564 nodeList.push(node);1565 node = nextNodeDescendants(node);1566 continue;1567 }1568 node = nextNode(node);1569 }1570 return nodeList;1571}1572function getAllEffectivelyContainedNodes(range, condition) {1573 if (typeof condition == "undefined") {1574 condition = function() { return true };1575 }1576 var node = range.startContainer;1577 while (isEffectivelyContained(node.parentNode, range)) {1578 node = node.parentNode;1579 }1580 var stop = nextNodeDescendants(range.endContainer);1581 var nodeList = [];1582 while (isBefore(node, stop)) {1583 if (isEffectivelyContained(node, range)1584 && condition(node)) {1585 nodeList.push(node);1586 }1587 node = nextNode(node);1588 }1589 return nodeList;1590}1591// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element1592// with no attributes except possibly style; or a font element with no1593// attributes except possibly style, color, face, and/or size; or an a element1594// with no attributes except possibly style and/or href."1595function isModifiableElement(node) {1596 if (!isHtmlElement(node)) {1597 return false;1598 }1599 if (["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) != -1) {1600 if (node.attributes.length == 0) {1601 return true;1602 }1603 if (node.attributes.length == 11604 && node.hasAttribute("style")) {1605 return true;1606 }1607 }1608 if (node.tagName == "FONT" || node.tagName == "A") {1609 var numAttrs = node.attributes.length;1610 if (node.hasAttribute("style")) {1611 numAttrs--;1612 }1613 if (node.tagName == "FONT") {1614 if (node.hasAttribute("color")) {1615 numAttrs--;1616 }1617 if (node.hasAttribute("face")) {1618 numAttrs--;1619 }1620 if (node.hasAttribute("size")) {1621 numAttrs--;1622 }1623 }1624 if (node.tagName == "A"1625 && node.hasAttribute("href")) {1626 numAttrs--;1627 }1628 if (numAttrs == 0) {1629 return true;1630 }1631 }1632 return false;1633}1634function isSimpleModifiableElement(node) {1635 // "A simple modifiable element is an HTML element for which at least one1636 // of the following holds:"1637 if (!isHtmlElement(node)) {1638 return false;1639 }1640 // Only these elements can possibly be a simple modifiable element.1641 if (["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) == -1) {1642 return false;1643 }1644 // "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u1645 // element with no attributes."1646 if (node.attributes.length == 0) {1647 return true;1648 }1649 // If it's got more than one attribute, everything after this fails.1650 if (node.attributes.length > 1) {1651 return false;1652 }1653 // "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u1654 // element with exactly one attribute, which is style, which sets no CSS1655 // properties (including invalid or unrecognized properties)."1656 //1657 // Not gonna try for invalid or unrecognized.1658 if (node.hasAttribute("style")1659 && node.style.length == 0) {1660 return true;1661 }1662 // "It is an a element with exactly one attribute, which is href."1663 if (node.tagName == "A"1664 && node.hasAttribute("href")) {1665 return true;1666 }1667 // "It is a font element with exactly one attribute, which is either color,1668 // face, or size."1669 if (node.tagName == "FONT"1670 && (node.hasAttribute("color")1671 || node.hasAttribute("face")1672 || node.hasAttribute("size")1673 )) {1674 return true;1675 }1676 // "It is a b or strong element with exactly one attribute, which is style,1677 // and the style attribute sets exactly one CSS property (including invalid1678 // or unrecognized properties), which is "font-weight"."1679 if ((node.tagName == "B" || node.tagName == "STRONG")1680 && node.hasAttribute("style")1681 && node.style.length == 11682 && node.style.fontWeight != "") {1683 return true;1684 }1685 // "It is an i or em element with exactly one attribute, which is style,1686 // and the style attribute sets exactly one CSS property (including invalid1687 // or unrecognized properties), which is "font-style"."1688 if ((node.tagName == "I" || node.tagName == "EM")1689 && node.hasAttribute("style")1690 && node.style.length == 11691 && node.style.fontStyle != "") {1692 return true;1693 }1694 // "It is an a, font, or span element with exactly one attribute, which is1695 // style, and the style attribute sets exactly one CSS property (including1696 // invalid or unrecognized properties), and that property is not1697 // "text-decoration"."1698 if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN")1699 && node.hasAttribute("style")1700 && node.style.length == 11701 && node.style.textDecoration == "") {1702 return true;1703 }1704 // "It is an a, font, s, span, strike, or u element with exactly one1705 // attribute, which is style, and the style attribute sets exactly one CSS1706 // property (including invalid or unrecognized properties), which is1707 // "text-decoration", which is set to "line-through" or "underline" or1708 // "overline" or "none"."1709 //1710 // The weird extra node.style.length check is for Firefox, which as of1711 // 8.0a2 has annoying and weird behavior here.1712 if (["A", "FONT", "S", "SPAN", "STRIKE", "U"].indexOf(node.tagName) != -11713 && node.hasAttribute("style")1714 && (node.style.length == 11715 || (node.style.length == 41716 && "MozTextBlink" in node.style1717 && "MozTextDecorationColor" in node.style1718 && "MozTextDecorationLine" in node.style1719 && "MozTextDecorationStyle" in node.style)1720 )1721 && (node.style.textDecoration == "line-through"1722 || node.style.textDecoration == "underline"1723 || node.style.textDecoration == "overline"1724 || node.style.textDecoration == "none")) {1725 return true;1726 }1727 return false;1728}1729// "A formattable node is an editable visible node that is either a Text node,1730// an img, or a br."1731function isFormattableNode(node) {1732 return isEditable(node)1733 && isVisible(node)1734 && (node.nodeType == Node.TEXT_NODE1735 || isHtmlElement(node, ["img", "br"]));1736}1737// "Two quantities are equivalent values for a command if either both are null,1738// or both are strings and they're equal and the command does not define any1739// equivalent values, or both are strings and the command defines equivalent1740// values and they match the definition."1741function areEquivalentValues(command, val1, val2) {1742 if (val1 === null && val2 === null) {1743 return true;1744 }1745 if (typeof val1 == "string"1746 && typeof val2 == "string"1747 && val1 == val21748 && !("equivalentValues" in commands[command])) {1749 return true;1750 }1751 if (typeof val1 == "string"1752 && typeof val2 == "string"1753 && "equivalentValues" in commands[command]1754 && commands[command].equivalentValues(val1, val2)) {1755 return true;1756 }1757 return false;1758}1759// "Two quantities are loosely equivalent values for a command if either they1760// are equivalent values for the command, or if the command is the fontSize1761// command; one of the quantities is one of "x-small", "small", "medium",1762// "large", "x-large", "xx-large", or "xxx-large"; and the other quantity is1763// the resolved value of "font-size" on a font element whose size attribute has1764// the corresponding value set ("1" through "7" respectively)."1765function areLooselyEquivalentValues(command, val1, val2) {1766 if (areEquivalentValues(command, val1, val2)) {1767 return true;1768 }1769 if (command != "fontsize"1770 || typeof val1 != "string"1771 || typeof val2 != "string") {1772 return false;1773 }1774 // Static variables in JavaScript?1775 var callee = areLooselyEquivalentValues;1776 if (callee.sizeMap === undefined) {1777 callee.sizeMap = {};1778 var font = document.createElement("font");1779 document.body.appendChild(font);1780 ["x-small", "small", "medium", "large", "x-large", "xx-large",1781 "xxx-large"].forEach(function(keyword) {1782 font.size = cssSizeToLegacy(keyword);1783 callee.sizeMap[keyword] = getComputedStyle(font).fontSize;1784 });1785 document.body.removeChild(font);1786 }1787 return val1 === callee.sizeMap[val2]1788 || val2 === callee.sizeMap[val1];1789}1790//@}1791///// Assorted inline formatting command algorithms /////1792//@{1793function getEffectiveCommandValue(node, command) {1794 // "If neither node nor its parent is an Element, return null."1795 if (node.nodeType != Node.ELEMENT_NODE1796 && (!node.parentNode || node.parentNode.nodeType != Node.ELEMENT_NODE)) {1797 return null;1798 }1799 // "If node is not an Element, return the effective command value of its1800 // parent for command."1801 if (node.nodeType != Node.ELEMENT_NODE) {1802 return getEffectiveCommandValue(node.parentNode, command);1803 }1804 // "If command is "createLink" or "unlink":"1805 if (command == "createlink" || command == "unlink") {1806 // "While node is not null, and is not an a element that has an href1807 // attribute, set node to its parent."1808 while (node1809 && (!isHtmlElement(node)1810 || node.tagName != "A"1811 || !node.hasAttribute("href"))) {1812 node = node.parentNode;1813 }1814 // "If node is null, return null."1815 if (!node) {1816 return null;1817 }1818 // "Return the value of node's href attribute."1819 return node.getAttribute("href");1820 }1821 // "If command is "backColor" or "hiliteColor":"1822 if (command == "backcolor"1823 || command == "hilitecolor") {1824 // "While the resolved value of "background-color" on node is any1825 // fully transparent value, and node's parent is an Element, set1826 // node to its parent."1827 //1828 // Another lame hack to avoid flawed APIs.1829 while ((getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)"1830 || getComputedStyle(node).backgroundColor === ""1831 || getComputedStyle(node).backgroundColor == "transparent")1832 && node.parentNode1833 && node.parentNode.nodeType == Node.ELEMENT_NODE) {1834 node = node.parentNode;1835 }1836 // "Return the resolved value of "background-color" for node."1837 return getComputedStyle(node).backgroundColor;1838 }1839 // "If command is "subscript" or "superscript":"1840 if (command == "subscript" || command == "superscript") {1841 // "Let affected by subscript and affected by superscript be two1842 // boolean variables, both initially false."1843 var affectedBySubscript = false;1844 var affectedBySuperscript = false;1845 // "While node is an inline node:"1846 while (isInlineNode(node)) {1847 var verticalAlign = getComputedStyle(node).verticalAlign;1848 // "If node is a sub, set affected by subscript to true."1849 if (isHtmlElement(node, "sub")) {1850 affectedBySubscript = true;1851 // "Otherwise, if node is a sup, set affected by superscript to1852 // true."1853 } else if (isHtmlElement(node, "sup")) {1854 affectedBySuperscript = true;1855 }1856 // "Set node to its parent."1857 node = node.parentNode;1858 }1859 // "If affected by subscript and affected by superscript are both true,1860 // return the string "mixed"."1861 if (affectedBySubscript && affectedBySuperscript) {1862 return "mixed";1863 }1864 // "If affected by subscript is true, return "subscript"."1865 if (affectedBySubscript) {1866 return "subscript";1867 }1868 // "If affected by superscript is true, return "superscript"."1869 if (affectedBySuperscript) {1870 return "superscript";1871 }1872 // "Return null."1873 return null;1874 }1875 // "If command is "strikethrough", and the "text-decoration" property of1876 // node or any of its ancestors has resolved value containing1877 // "line-through", return "line-through". Otherwise, return null."1878 if (command == "strikethrough") {1879 do {1880 if (getComputedStyle(node).textDecoration.indexOf("line-through") != -1) {1881 return "line-through";1882 }1883 node = node.parentNode;1884 } while (node && node.nodeType == Node.ELEMENT_NODE);1885 return null;1886 }1887 // "If command is "underline", and the "text-decoration" property of node1888 // or any of its ancestors has resolved value containing "underline",1889 // return "underline". Otherwise, return null."1890 if (command == "underline") {1891 do {1892 if (getComputedStyle(node).textDecoration.indexOf("underline") != -1) {1893 return "underline";1894 }1895 node = node.parentNode;1896 } while (node && node.nodeType == Node.ELEMENT_NODE);1897 return null;1898 }1899 if (!("relevantCssProperty" in commands[command])) {1900 throw "Bug: no relevantCssProperty for " + command + " in getEffectiveCommandValue";1901 }1902 // "Return the resolved value for node of the relevant CSS property for1903 // command."1904 return getComputedStyle(node)[commands[command].relevantCssProperty];1905}1906function getSpecifiedCommandValue(element, command) {1907 // "If command is "backColor" or "hiliteColor" and element's display1908 // property does not have resolved value "inline", return null."1909 if ((command == "backcolor" || command == "hilitecolor")1910 && getComputedStyle(element).display != "inline") {1911 return null;1912 }1913 // "If command is "createLink" or "unlink":"1914 if (command == "createlink" || command == "unlink") {1915 // "If element is an a element and has an href attribute, return the1916 // value of that attribute."1917 if (isHtmlElement(element)1918 && element.tagName == "A"1919 && element.hasAttribute("href")) {1920 return element.getAttribute("href");1921 }1922 // "Return null."1923 return null;1924 }1925 // "If command is "subscript" or "superscript":"1926 if (command == "subscript" || command == "superscript") {1927 // "If element is a sup, return "superscript"."1928 if (isHtmlElement(element, "sup")) {1929 return "superscript";1930 }1931 // "If element is a sub, return "subscript"."1932 if (isHtmlElement(element, "sub")) {1933 return "subscript";1934 }1935 // "Return null."1936 return null;1937 }1938 // "If command is "strikethrough", and element has a style attribute set,1939 // and that attribute sets "text-decoration":"1940 if (command == "strikethrough"1941 && element.style.textDecoration != "") {1942 // "If element's style attribute sets "text-decoration" to a value1943 // containing "line-through", return "line-through"."1944 if (element.style.textDecoration.indexOf("line-through") != -1) {1945 return "line-through";1946 }1947 // "Return null."1948 return null;1949 }1950 // "If command is "strikethrough" and element is a s or strike element,1951 // return "line-through"."1952 if (command == "strikethrough"1953 && isHtmlElement(element, ["S", "STRIKE"])) {1954 return "line-through";1955 }1956 // "If command is "underline", and element has a style attribute set, and1957 // that attribute sets "text-decoration":"1958 if (command == "underline"1959 && element.style.textDecoration != "") {1960 // "If element's style attribute sets "text-decoration" to a value1961 // containing "underline", return "underline"."1962 if (element.style.textDecoration.indexOf("underline") != -1) {1963 return "underline";1964 }1965 // "Return null."1966 return null;1967 }1968 // "If command is "underline" and element is a u element, return1969 // "underline"."1970 if (command == "underline"1971 && isHtmlElement(element, "U")) {1972 return "underline";1973 }1974 // "Let property be the relevant CSS property for command."1975 var property = commands[command].relevantCssProperty;1976 // "If property is null, return null."1977 if (property === null) {1978 return null;1979 }1980 // "If element has a style attribute set, and that attribute has the1981 // effect of setting property, return the value that it sets property to."1982 if (element.style[property] != "") {1983 return element.style[property];1984 }1985 // "If element is a font element that has an attribute whose effect is1986 // to create a presentational hint for property, return the value that the1987 // hint sets property to. (For a size of 7, this will be the non-CSS value1988 // "xxx-large".)"1989 if (isHtmlNamespace(element.namespaceURI)1990 && element.tagName == "FONT") {1991 if (property == "color" && element.hasAttribute("color")) {1992 return element.color;1993 }1994 if (property == "fontFamily" && element.hasAttribute("face")) {1995 return element.face;1996 }1997 if (property == "fontSize" && element.hasAttribute("size")) {1998 // This is not even close to correct in general.1999 var size = parseInt(element.size);2000 if (size < 1) {2001 size = 1;2002 }2003 if (size > 7) {2004 size = 7;2005 }2006 return {2007 1: "x-small",2008 2: "small",2009 3: "medium",2010 4: "large",2011 5: "x-large",2012 6: "xx-large",2013 7: "xxx-large"2014 }[size];2015 }2016 }2017 // "If element is in the following list, and property is equal to the2018 // CSS property name listed for it, return the string listed for it."2019 //2020 // A list follows, whose meaning is copied here.2021 if (property == "fontWeight"2022 && (element.tagName == "B" || element.tagName == "STRONG")) {2023 return "bold";2024 }2025 if (property == "fontStyle"2026 && (element.tagName == "I" || element.tagName == "EM")) {2027 return "italic";2028 }2029 // "Return null."2030 return null;2031}2032function reorderModifiableDescendants(node, command, newValue) {2033 // "Let candidate equal node."2034 var candidate = node;2035 // "While candidate is a modifiable element, and candidate has exactly one2036 // child, and that child is also a modifiable element, and candidate is not2037 // a simple modifiable element or candidate's specified command value for2038 // command is not equivalent to new value, set candidate to its child."2039 while (isModifiableElement(candidate)2040 && candidate.childNodes.length == 12041 && isModifiableElement(candidate.firstChild)2042 && (!isSimpleModifiableElement(candidate)2043 || !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue))) {2044 candidate = candidate.firstChild;2045 }2046 // "If candidate is node, or is not a simple modifiable element, or its2047 // specified command value is not equivalent to new value, or its effective2048 // command value is not loosely equivalent to new value, abort these2049 // steps."2050 if (candidate == node2051 || !isSimpleModifiableElement(candidate)2052 || !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue)2053 || !areLooselyEquivalentValues(command, getEffectiveCommandValue(candidate, command), newValue)) {2054 return;2055 }2056 // "While candidate has children, insert the first child of candidate into2057 // candidate's parent immediately before candidate, preserving ranges."2058 while (candidate.hasChildNodes()) {2059 movePreservingRanges(candidate.firstChild, candidate.parentNode, getNodeIndex(candidate));2060 }2061 // "Insert candidate into node's parent immediately after node."2062 node.parentNode.insertBefore(candidate, node.nextSibling);2063 // "Append the node as the last child of candidate, preserving ranges."2064 movePreservingRanges(node, candidate, -1);2065}2066function recordValues(nodeList) {2067 // "Let values be a list of (node, command, specified command value)2068 // triples, initially empty."2069 var values = [];2070 // "For each node in node list, for each command in the list "subscript",2071 // "bold", "fontName", "fontSize", "foreColor", "hiliteColor", "italic",2072 // "strikethrough", and "underline" in that order:"2073 nodeList.forEach(function(node) {2074 ["subscript", "bold", "fontname", "fontsize", "forecolor",2075 "hilitecolor", "italic", "strikethrough", "underline"].forEach(function(command) {2076 // "Let ancestor equal node."2077 var ancestor = node;2078 // "If ancestor is not an Element, set it to its parent."2079 if (ancestor.nodeType != Node.ELEMENT_NODE) {2080 ancestor = ancestor.parentNode;2081 }2082 // "While ancestor is an Element and its specified command value2083 // for command is null, set it to its parent."2084 while (ancestor2085 && ancestor.nodeType == Node.ELEMENT_NODE2086 && getSpecifiedCommandValue(ancestor, command) === null) {2087 ancestor = ancestor.parentNode;2088 }2089 // "If ancestor is an Element, add (node, command, ancestor's2090 // specified command value for command) to values. Otherwise add2091 // (node, command, null) to values."2092 if (ancestor && ancestor.nodeType == Node.ELEMENT_NODE) {2093 values.push([node, command, getSpecifiedCommandValue(ancestor, command)]);2094 } else {2095 values.push([node, command, null]);2096 }2097 });2098 });2099 // "Return values."2100 return values;2101}2102function restoreValues(values) {2103 // "For each (node, command, value) triple in values:"2104 values.forEach(function(triple) {2105 var node = triple[0];2106 var command = triple[1];2107 var value = triple[2];2108 // "Let ancestor equal node."2109 var ancestor = node;2110 // "If ancestor is not an Element, set it to its parent."2111 if (!ancestor || ancestor.nodeType != Node.ELEMENT_NODE) {2112 ancestor = ancestor.parentNode;2113 }2114 // "While ancestor is an Element and its specified command value for2115 // command is null, set it to its parent."2116 while (ancestor2117 && ancestor.nodeType == Node.ELEMENT_NODE2118 && getSpecifiedCommandValue(ancestor, command) === null) {2119 ancestor = ancestor.parentNode;2120 }2121 // "If value is null and ancestor is an Element, push down values on2122 // node for command, with new value null."2123 if (value === null2124 && ancestor2125 && ancestor.nodeType == Node.ELEMENT_NODE) {2126 pushDownValues(node, command, null);2127 // "Otherwise, if ancestor is an Element and its specified command2128 // value for command is not equivalent to value, or if ancestor is not2129 // an Element and value is not null, force the value of command to2130 // value on node."2131 } else if ((ancestor2132 && ancestor.nodeType == Node.ELEMENT_NODE2133 && !areEquivalentValues(command, getSpecifiedCommandValue(ancestor, command), value))2134 || ((!ancestor || ancestor.nodeType != Node.ELEMENT_NODE)2135 && value !== null)) {2136 forceValue(node, command, value);2137 }2138 });2139}2140//@}2141///// Clearing an element's value /////2142//@{2143function clearValue(element, command) {2144 // "If element is not editable, return the empty list."2145 if (!isEditable(element)) {2146 return [];2147 }2148 // "If element's specified command value for command is null, return the2149 // empty list."2150 if (getSpecifiedCommandValue(element, command) === null) {2151 return [];2152 }2153 // "If element is a simple modifiable element:"2154 if (isSimpleModifiableElement(element)) {2155 // "Let children be the children of element."2156 var children = Array.prototype.slice.call(element.childNodes);2157 // "For each child in children, insert child into element's parent2158 // immediately before element, preserving ranges."2159 for (var i = 0; i < children.length; i++) {2160 movePreservingRanges(children[i], element.parentNode, getNodeIndex(element));2161 }2162 // "Remove element from its parent."2163 element.parentNode.removeChild(element);2164 // "Return children."2165 return children;2166 }2167 // "If command is "strikethrough", and element has a style attribute that2168 // sets "text-decoration" to some value containing "line-through", delete2169 // "line-through" from the value."2170 if (command == "strikethrough"2171 && element.style.textDecoration.indexOf("line-through") != -1) {2172 if (element.style.textDecoration == "line-through") {2173 element.style.textDecoration = "";2174 } else {2175 element.style.textDecoration = element.style.textDecoration.replace("line-through", "");2176 }2177 if (element.getAttribute("style") == "") {2178 element.removeAttribute("style");2179 }2180 }2181 // "If command is "underline", and element has a style attribute that sets2182 // "text-decoration" to some value containing "underline", delete2183 // "underline" from the value."2184 if (command == "underline"2185 && element.style.textDecoration.indexOf("underline") != -1) {2186 if (element.style.textDecoration == "underline") {2187 element.style.textDecoration = "";2188 } else {2189 element.style.textDecoration = element.style.textDecoration.replace("underline", "");2190 }2191 if (element.getAttribute("style") == "") {2192 element.removeAttribute("style");2193 }2194 }2195 // "If the relevant CSS property for command is not null, unset the CSS2196 // property property of element."2197 if (commands[command].relevantCssProperty !== null) {2198 element.style[commands[command].relevantCssProperty] = '';2199 if (element.getAttribute("style") == "") {2200 element.removeAttribute("style");2201 }2202 }2203 // "If element is a font element:"2204 if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {2205 // "If command is "foreColor", unset element's color attribute, if set."2206 if (command == "forecolor") {2207 element.removeAttribute("color");2208 }2209 // "If command is "fontName", unset element's face attribute, if set."2210 if (command == "fontname") {2211 element.removeAttribute("face");2212 }2213 // "If command is "fontSize", unset element's size attribute, if set."2214 if (command == "fontsize") {2215 element.removeAttribute("size");2216 }2217 }2218 // "If element is an a element and command is "createLink" or "unlink",2219 // unset the href property of element."2220 if (isHtmlElement(element, "A")2221 && (command == "createlink" || command == "unlink")) {2222 element.removeAttribute("href");2223 }2224 // "If element's specified command value for command is null, return the2225 // empty list."2226 if (getSpecifiedCommandValue(element, command) === null) {2227 return [];2228 }2229 // "Set the tag name of element to "span", and return the one-node list2230 // consisting of the result."2231 return [setTagName(element, "span")];2232}2233//@}2234///// Pushing down values /////2235//@{2236function pushDownValues(node, command, newValue) {2237 // "If node's parent is not an Element, abort this algorithm."2238 if (!node.parentNode2239 || node.parentNode.nodeType != Node.ELEMENT_NODE) {2240 return;2241 }2242 // "If the effective command value of command is loosely equivalent to new2243 // value on node, abort this algorithm."2244 if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {2245 return;2246 }2247 // "Let current ancestor be node's parent."2248 var currentAncestor = node.parentNode;2249 // "Let ancestor list be a list of Nodes, initially empty."2250 var ancestorList = [];2251 // "While current ancestor is an editable Element and the effective command2252 // value of command is not loosely equivalent to new value on it, append2253 // current ancestor to ancestor list, then set current ancestor to its2254 // parent."2255 while (isEditable(currentAncestor)2256 && currentAncestor.nodeType == Node.ELEMENT_NODE2257 && !areLooselyEquivalentValues(command, getEffectiveCommandValue(currentAncestor, command), newValue)) {2258 ancestorList.push(currentAncestor);2259 currentAncestor = currentAncestor.parentNode;2260 }2261 // "If ancestor list is empty, abort this algorithm."2262 if (!ancestorList.length) {2263 return;2264 }2265 // "Let propagated value be the specified command value of command on the2266 // last member of ancestor list."2267 var propagatedValue = getSpecifiedCommandValue(ancestorList[ancestorList.length - 1], command);2268 // "If propagated value is null and is not equal to new value, abort this2269 // algorithm."2270 if (propagatedValue === null && propagatedValue != newValue) {2271 return;2272 }2273 // "If the effective command value for the parent of the last member of2274 // ancestor list is not loosely equivalent to new value, and new value is2275 // not null, abort this algorithm."2276 if (newValue !== null2277 && !areLooselyEquivalentValues(command, getEffectiveCommandValue(ancestorList[ancestorList.length - 1].parentNode, command), newValue)) {2278 return;2279 }2280 // "While ancestor list is not empty:"2281 while (ancestorList.length) {2282 // "Let current ancestor be the last member of ancestor list."2283 // "Remove the last member from ancestor list."2284 var currentAncestor = ancestorList.pop();2285 // "If the specified command value of current ancestor for command is2286 // not null, set propagated value to that value."2287 if (getSpecifiedCommandValue(currentAncestor, command) !== null) {2288 propagatedValue = getSpecifiedCommandValue(currentAncestor, command);2289 }2290 // "Let children be the children of current ancestor."2291 var children = Array.prototype.slice.call(currentAncestor.childNodes);2292 // "If the specified command value of current ancestor for command is2293 // not null, clear the value of current ancestor."2294 if (getSpecifiedCommandValue(currentAncestor, command) !== null) {2295 clearValue(currentAncestor, command);2296 }2297 // "For every child in children:"2298 for (var i = 0; i < children.length; i++) {2299 var child = children[i];2300 // "If child is node, continue with the next child."2301 if (child == node) {2302 continue;2303 }2304 // "If child is an Element whose specified command value for2305 // command is neither null nor equivalent to propagated value,2306 // continue with the next child."2307 if (child.nodeType == Node.ELEMENT_NODE2308 && getSpecifiedCommandValue(child, command) !== null2309 && !areEquivalentValues(command, propagatedValue, getSpecifiedCommandValue(child, command))) {2310 continue;2311 }2312 // "If child is the last member of ancestor list, continue with the2313 // next child."2314 if (child == ancestorList[ancestorList.length - 1]) {2315 continue;2316 }2317 // "Force the value of child, with command as in this algorithm2318 // and new value equal to propagated value."2319 forceValue(child, command, propagatedValue);2320 }2321 }2322}2323//@}2324///// Forcing the value of a node /////2325//@{2326function forceValue(node, command, newValue) {2327 // "If node's parent is null, abort this algorithm."2328 if (!node.parentNode) {2329 return;2330 }2331 // "If new value is null, abort this algorithm."2332 if (newValue === null) {2333 return;2334 }2335 // "If node is an allowed child of "span":"2336 if (isAllowedChild(node, "span")) {2337 // "Reorder modifiable descendants of node's previousSibling."2338 reorderModifiableDescendants(node.previousSibling, command, newValue);2339 // "Reorder modifiable descendants of node's nextSibling."2340 reorderModifiableDescendants(node.nextSibling, command, newValue);2341 // "Wrap the one-node list consisting of node, with sibling criteria2342 // returning true for a simple modifiable element whose specified2343 // command value is equivalent to new value and whose effective command2344 // value is loosely equivalent to new value and false otherwise, and2345 // with new parent instructions returning null."2346 wrap([node],2347 function(node) {2348 return isSimpleModifiableElement(node)2349 && areEquivalentValues(command, getSpecifiedCommandValue(node, command), newValue)2350 && areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue);2351 },2352 function() { return null }2353 );2354 }2355 // "If node is invisible, abort this algorithm."2356 if (isInvisible(node)) {2357 return;2358 }2359 // "If the effective command value of command is loosely equivalent to new2360 // value on node, abort this algorithm."2361 if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {2362 return;2363 }2364 // "If node is not an allowed child of "span":"2365 if (!isAllowedChild(node, "span")) {2366 // "Let children be all children of node, omitting any that are2367 // Elements whose specified command value for command is neither null2368 // nor equivalent to new value."2369 var children = [];2370 for (var i = 0; i < node.childNodes.length; i++) {2371 if (node.childNodes[i].nodeType == Node.ELEMENT_NODE) {2372 var specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);2373 if (specifiedValue !== null2374 && !areEquivalentValues(command, newValue, specifiedValue)) {2375 continue;2376 }2377 }2378 children.push(node.childNodes[i]);2379 }2380 // "Force the value of each Node in children, with command and new2381 // value as in this invocation of the algorithm."2382 for (var i = 0; i < children.length; i++) {2383 forceValue(children[i], command, newValue);2384 }2385 // "Abort this algorithm."2386 return;2387 }2388 // "If the effective command value of command is loosely equivalent to new2389 // value on node, abort this algorithm."2390 if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {2391 return;2392 }2393 // "Let new parent be null."2394 var newParent = null;2395 // "If the CSS styling flag is false:"2396 if (!cssStylingFlag) {2397 // "If command is "bold" and new value is "bold", let new parent be the2398 // result of calling createElement("b") on the ownerDocument of node."2399 if (command == "bold" && (newValue == "bold" || newValue == "700")) {2400 newParent = node.ownerDocument.createElement("b");2401 }2402 // "If command is "italic" and new value is "italic", let new parent be2403 // the result of calling createElement("i") on the ownerDocument of2404 // node."2405 if (command == "italic" && newValue == "italic") {2406 newParent = node.ownerDocument.createElement("i");2407 }2408 // "If command is "strikethrough" and new value is "line-through", let2409 // new parent be the result of calling createElement("s") on the2410 // ownerDocument of node."2411 if (command == "strikethrough" && newValue == "line-through") {2412 newParent = node.ownerDocument.createElement("s");2413 }2414 // "If command is "underline" and new value is "underline", let new2415 // parent be the result of calling createElement("u") on the2416 // ownerDocument of node."2417 if (command == "underline" && newValue == "underline") {2418 newParent = node.ownerDocument.createElement("u");2419 }2420 // "If command is "foreColor", and new value is fully opaque with red,2421 // green, and blue components in the range 0 to 255:"2422 if (command == "forecolor" && parseSimpleColor(newValue)) {2423 // "Let new parent be the result of calling createElement("font")2424 // on the ownerDocument of node."2425 newParent = node.ownerDocument.createElement("font");2426 // "Set the color attribute of new parent to the result of applying2427 // the rules for serializing simple color values to new value2428 // (interpreted as a simple color)."2429 newParent.setAttribute("color", parseSimpleColor(newValue));2430 }2431 // "If command is "fontName", let new parent be the result of calling2432 // createElement("font") on the ownerDocument of node, then set the2433 // face attribute of new parent to new value."2434 if (command == "fontname") {2435 newParent = node.ownerDocument.createElement("font");2436 newParent.face = newValue;2437 }2438 }2439 // "If command is "createLink" or "unlink":"2440 if (command == "createlink" || command == "unlink") {2441 // "Let new parent be the result of calling createElement("a") on the2442 // ownerDocument of node."2443 newParent = node.ownerDocument.createElement("a");2444 // "Set the href attribute of new parent to new value."2445 newParent.setAttribute("href", newValue);2446 // "Let ancestor be node's parent."2447 var ancestor = node.parentNode;2448 // "While ancestor is not null:"2449 while (ancestor) {2450 // "If ancestor is an a, set the tag name of ancestor to "span",2451 // and let ancestor be the result."2452 if (isHtmlElement(ancestor, "A")) {2453 ancestor = setTagName(ancestor, "span");2454 }2455 // "Set ancestor to its parent."2456 ancestor = ancestor.parentNode;2457 }2458 }2459 // "If command is "fontSize"; and new value is one of "x-small", "small",2460 // "medium", "large", "x-large", "xx-large", or "xxx-large"; and either the2461 // CSS styling flag is false, or new value is "xxx-large": let new parent2462 // be the result of calling createElement("font") on the ownerDocument of2463 // node, then set the size attribute of new parent to the number from the2464 // following table based on new value: [table omitted]"2465 if (command == "fontsize"2466 && ["x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"].indexOf(newValue) != -12467 && (!cssStylingFlag || newValue == "xxx-large")) {2468 newParent = node.ownerDocument.createElement("font");2469 newParent.size = cssSizeToLegacy(newValue);2470 }2471 // "If command is "subscript" or "superscript" and new value is2472 // "subscript", let new parent be the result of calling2473 // createElement("sub") on the ownerDocument of node."2474 if ((command == "subscript" || command == "superscript")2475 && newValue == "subscript") {2476 newParent = node.ownerDocument.createElement("sub");2477 }2478 // "If command is "subscript" or "superscript" and new value is2479 // "superscript", let new parent be the result of calling2480 // createElement("sup") on the ownerDocument of node."2481 if ((command == "subscript" || command == "superscript")2482 && newValue == "superscript") {2483 newParent = node.ownerDocument.createElement("sup");2484 }2485 // "If new parent is null, let new parent be the result of calling2486 // createElement("span") on the ownerDocument of node."2487 if (!newParent) {2488 newParent = node.ownerDocument.createElement("span");2489 }2490 // "Insert new parent in node's parent before node."2491 node.parentNode.insertBefore(newParent, node);2492 // "If the effective command value of command for new parent is not loosely2493 // equivalent to new value, and the relevant CSS property for command is2494 // not null, set that CSS property of new parent to new value (if the new2495 // value would be valid)."2496 var property = commands[command].relevantCssProperty;2497 if (property !== null2498 && !areLooselyEquivalentValues(command, getEffectiveCommandValue(newParent, command), newValue)) {2499 newParent.style[property] = newValue;2500 }2501 // "If command is "strikethrough", and new value is "line-through", and the2502 // effective command value of "strikethrough" for new parent is not2503 // "line-through", set the "text-decoration" property of new parent to2504 // "line-through"."2505 if (command == "strikethrough"2506 && newValue == "line-through"2507 && getEffectiveCommandValue(newParent, "strikethrough") != "line-through") {2508 newParent.style.textDecoration = "line-through";2509 }2510 // "If command is "underline", and new value is "underline", and the2511 // effective command value of "underline" for new parent is not2512 // "underline", set the "text-decoration" property of new parent to2513 // "underline"."2514 if (command == "underline"2515 && newValue == "underline"2516 && getEffectiveCommandValue(newParent, "underline") != "underline") {2517 newParent.style.textDecoration = "underline";2518 }2519 // "Append node to new parent as its last child, preserving ranges."2520 movePreservingRanges(node, newParent, newParent.childNodes.length);2521 // "If node is an Element and the effective command value of command for2522 // node is not loosely equivalent to new value:"2523 if (node.nodeType == Node.ELEMENT_NODE2524 && !areEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {2525 // "Insert node into the parent of new parent before new parent,2526 // preserving ranges."2527 movePreservingRanges(node, newParent.parentNode, getNodeIndex(newParent));2528 // "Remove new parent from its parent."2529 newParent.parentNode.removeChild(newParent);2530 // "Let children be all children of node, omitting any that are2531 // Elements whose specified command value for command is neither null2532 // nor equivalent to new value."2533 var children = [];2534 for (var i = 0; i < node.childNodes.length; i++) {2535 if (node.childNodes[i].nodeType == Node.ELEMENT_NODE) {2536 var specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);2537 if (specifiedValue !== null2538 && !areEquivalentValues(command, newValue, specifiedValue)) {2539 continue;2540 }2541 }2542 children.push(node.childNodes[i]);2543 }2544 // "Force the value of each Node in children, with command and new2545 // value as in this invocation of the algorithm."2546 for (var i = 0; i < children.length; i++) {2547 forceValue(children[i], command, newValue);2548 }2549 }2550}2551//@}2552///// Setting the selection's value /////2553//@{2554function setSelectionValue(command, newValue) {2555 // "If there is no formattable node effectively contained in the active2556 // range:"2557 if (!getAllEffectivelyContainedNodes(getActiveRange())2558 .some(isFormattableNode)) {2559 // "If command has inline command activated values, set the state2560 // override to true if new value is among them and false if it's not."2561 if ("inlineCommandActivatedValues" in commands[command]) {2562 setStateOverride(command, commands[command].inlineCommandActivatedValues2563 .indexOf(newValue) != -1);2564 }2565 // "If command is "subscript", unset the state override for2566 // "superscript"."2567 if (command == "subscript") {2568 unsetStateOverride("superscript");2569 }2570 // "If command is "superscript", unset the state override for2571 // "subscript"."2572 if (command == "superscript") {2573 unsetStateOverride("subscript");2574 }2575 // "If new value is null, unset the value override (if any)."2576 if (newValue === null) {2577 unsetValueOverride(command);2578 // "Otherwise, if command is "createLink" or it has a value specified,2579 // set the value override to new value."2580 } else if (command == "createlink" || "value" in commands[command]) {2581 setValueOverride(command, newValue);2582 }2583 // "Abort these steps."2584 return;2585 }2586 // "If the active range's start node is an editable Text node, and its2587 // start offset is neither zero nor its start node's length, call2588 // splitText() on the active range's start node, with argument equal to the2589 // active range's start offset. Then set the active range's start node to2590 // the result, and its start offset to zero."2591 if (isEditable(getActiveRange().startContainer)2592 && getActiveRange().startContainer.nodeType == Node.TEXT_NODE2593 && getActiveRange().startOffset != 02594 && getActiveRange().startOffset != getNodeLength(getActiveRange().startContainer)) {2595 // Account for browsers not following range mutation rules2596 var newActiveRange = document.createRange();2597 var newNode;2598 if (getActiveRange().startContainer == getActiveRange().endContainer) {2599 var newEndOffset = getActiveRange().endOffset - getActiveRange().startOffset;2600 newNode = getActiveRange().startContainer.splitText(getActiveRange().startOffset);2601 newActiveRange.setEnd(newNode, newEndOffset);2602 getActiveRange().setEnd(newNode, newEndOffset);2603 } else {2604 newNode = getActiveRange().startContainer.splitText(getActiveRange().startOffset);2605 }2606 newActiveRange.setStart(newNode, 0);2607 getSelection().removeAllRanges();2608 getSelection().addRange(newActiveRange);2609 getActiveRange().setStart(newNode, 0);2610 }2611 // "If the active range's end node is an editable Text node, and its end2612 // offset is neither zero nor its end node's length, call splitText() on2613 // the active range's end node, with argument equal to the active range's2614 // end offset."2615 if (isEditable(getActiveRange().endContainer)2616 && getActiveRange().endContainer.nodeType == Node.TEXT_NODE2617 && getActiveRange().endOffset != 02618 && getActiveRange().endOffset != getNodeLength(getActiveRange().endContainer)) {2619 // IE seems to mutate the range incorrectly here, so we need correction2620 // here as well. The active range will be temporarily in orphaned2621 // nodes, so calling getActiveRange() after splitText() but before2622 // fixing the range will throw an exception.2623 var activeRange = getActiveRange();2624 var newStart = [activeRange.startContainer, activeRange.startOffset];2625 var newEnd = [activeRange.endContainer, activeRange.endOffset];2626 activeRange.endContainer.splitText(activeRange.endOffset);2627 activeRange.setStart(newStart[0], newStart[1]);2628 activeRange.setEnd(newEnd[0], newEnd[1]);2629 getSelection().removeAllRanges();2630 getSelection().addRange(activeRange);2631 }2632 // "Let element list be all editable Elements effectively contained in the2633 // active range.2634 //2635 // "For each element in element list, clear the value of element."2636 getAllEffectivelyContainedNodes(getActiveRange(), function(node) {2637 return isEditable(node) && node.nodeType == Node.ELEMENT_NODE;2638 }).forEach(function(element) {2639 clearValue(element, command);2640 });2641 // "Let node list be all editable nodes effectively contained in the active2642 // range.2643 //2644 // "For each node in node list:"2645 getAllEffectivelyContainedNodes(getActiveRange(), isEditable).forEach(function(node) {2646 // "Push down values on node."2647 pushDownValues(node, command, newValue);2648 // "If node is an allowed child of span, force the value of node."2649 if (isAllowedChild(node, "span")) {2650 forceValue(node, command, newValue);2651 }2652 });2653}2654//@}2655///// The backColor command /////2656//@{2657commands.backcolor = {2658 // Copy-pasted, same as hiliteColor2659 action: function(value) {2660 // Action is further copy-pasted, same as foreColor2661 // "If value is not a valid CSS color, prepend "#" to it."2662 //2663 // "If value is still not a valid CSS color, or if it is currentColor,2664 // return false."2665 //2666 // Cheap hack for testing, no attempt to be comprehensive.2667 if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {2668 value = "#" + value;2669 }2670 if (!/^(rgba?|hsla?)\(.*\)$/.test(value)2671 && !parseSimpleColor(value)2672 && value.toLowerCase() != "transparent") {2673 return false;2674 }2675 // "Set the selection's value to value."2676 setSelectionValue("backcolor", value);2677 // "Return true."2678 return true;2679 }, standardInlineValueCommand: true, relevantCssProperty: "backgroundColor",2680 equivalentValues: function(val1, val2) {2681 // "Either both strings are valid CSS colors and have the same red,2682 // green, blue, and alpha components, or neither string is a valid CSS2683 // color."2684 return normalizeColor(val1) === normalizeColor(val2);2685 },2686};2687//@}2688///// The bold command /////2689//@{2690commands.bold = {2691 action: function() {2692 // "If queryCommandState("bold") returns true, set the selection's2693 // value to "normal". Otherwise set the selection's value to "bold".2694 // Either way, return true."2695 if (myQueryCommandState("bold")) {2696 setSelectionValue("bold", "normal");2697 } else {2698 setSelectionValue("bold", "bold");2699 }2700 return true;2701 }, inlineCommandActivatedValues: ["bold", "600", "700", "800", "900"],2702 relevantCssProperty: "fontWeight",2703 equivalentValues: function(val1, val2) {2704 // "Either the two strings are equal, or one is "bold" and the other is2705 // "700", or one is "normal" and the other is "400"."2706 return val1 == val22707 || (val1 == "bold" && val2 == "700")2708 || (val1 == "700" && val2 == "bold")2709 || (val1 == "normal" && val2 == "400")2710 || (val1 == "400" && val2 == "normal");2711 },2712};2713//@}2714///// The createLink command /////2715//@{2716commands.createlink = {2717 action: function(value) {2718 // "If value is the empty string, return false."2719 if (value === "") {2720 return false;2721 }2722 // "For each editable a element that has an href attribute and is an2723 // ancestor of some node effectively contained in the active range, set2724 // that a element's href attribute to value."2725 //2726 // TODO: We don't actually do this in tree order, not that it matters2727 // unless you're spying with mutation events.2728 getAllEffectivelyContainedNodes(getActiveRange()).forEach(function(node) {2729 getAncestors(node).forEach(function(ancestor) {2730 if (isEditable(ancestor)2731 && isHtmlElement(ancestor, "a")2732 && ancestor.hasAttribute("href")) {2733 ancestor.setAttribute("href", value);2734 }2735 });2736 });2737 // "Set the selection's value to value."2738 setSelectionValue("createlink", value);2739 // "Return true."2740 return true;2741 }2742};2743//@}2744///// The fontName command /////2745//@{2746commands.fontname = {2747 action: function(value) {2748 // "Set the selection's value to value, then return true."2749 setSelectionValue("fontname", value);2750 return true;2751 }, standardInlineValueCommand: true, relevantCssProperty: "fontFamily"2752};2753//@}2754///// The fontSize command /////2755//@{2756// Helper function for fontSize's action plus queryOutputHelper. It's just the2757// middle of fontSize's action, ripped out into its own function. Returns null2758// if the size is invalid.2759function normalizeFontSize(value) {2760 // "Strip leading and trailing whitespace from value."2761 //2762 // Cheap hack, not following the actual algorithm.2763 value = value.trim();2764 // "If value is not a valid floating point number, and would not be a valid2765 // floating point number if a single leading "+" character were stripped,2766 // return false."2767 if (!/^[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?$/.test(value)) {2768 return null;2769 }2770 var mode;2771 // "If the first character of value is "+", delete the character and let2772 // mode be "relative-plus"."2773 if (value[0] == "+") {2774 value = value.slice(1);2775 mode = "relative-plus";2776 // "Otherwise, if the first character of value is "-", delete the character2777 // and let mode be "relative-minus"."2778 } else if (value[0] == "-") {2779 value = value.slice(1);2780 mode = "relative-minus";2781 // "Otherwise, let mode be "absolute"."2782 } else {2783 mode = "absolute";2784 }2785 // "Apply the rules for parsing non-negative integers to value, and let2786 // number be the result."2787 //2788 // Another cheap hack.2789 var num = parseInt(value);2790 // "If mode is "relative-plus", add three to number."2791 if (mode == "relative-plus") {2792 num += 3;2793 }2794 // "If mode is "relative-minus", negate number, then add three to it."2795 if (mode == "relative-minus") {2796 num = 3 - num;2797 }2798 // "If number is less than one, let number equal 1."2799 if (num < 1) {2800 num = 1;2801 }2802 // "If number is greater than seven, let number equal 7."2803 if (num > 7) {2804 num = 7;2805 }2806 // "Set value to the string here corresponding to number:" [table omitted]2807 value = {2808 1: "x-small",2809 2: "small",2810 3: "medium",2811 4: "large",2812 5: "x-large",2813 6: "xx-large",2814 7: "xxx-large"2815 }[num];2816 return value;2817}2818commands.fontsize = {2819 action: function(value) {2820 value = normalizeFontSize(value);2821 if (value === null) {2822 return false;2823 }2824 // "Set the selection's value to value."2825 setSelectionValue("fontsize", value);2826 // "Return true."2827 return true;2828 }, indeterm: function() {2829 // "True if among formattable nodes that are effectively contained in2830 // the active range, there are two that have distinct effective command2831 // values. Otherwise false."2832 return getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode)2833 .map(function(node) {2834 return getEffectiveCommandValue(node, "fontsize");2835 }).filter(function(value, i, arr) {2836 return arr.slice(0, i).indexOf(value) == -1;2837 }).length >= 2;2838 }, value: function() {2839 // "If the active range is null, return the empty string."2840 if (!getActiveRange()) {2841 return "";2842 }2843 // "Let pixel size be the effective command value of the first2844 // formattable node that is effectively contained in the active range,2845 // or if there is no such node, the effective command value of the2846 // active range's start node, in either case interpreted as a number of2847 // pixels."2848 var node = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode)[0];2849 if (node === undefined) {2850 node = getActiveRange().startContainer;2851 }2852 var pixelSize = getEffectiveCommandValue(node, "fontsize");2853 // "Return the legacy font size for pixel size."2854 return getLegacyFontSize(pixelSize);2855 }, relevantCssProperty: "fontSize"2856};2857function getLegacyFontSize(size) {2858 if (getLegacyFontSize.resultCache === undefined) {2859 getLegacyFontSize.resultCache = {};2860 }2861 if (getLegacyFontSize.resultCache[size] !== undefined) {2862 return getLegacyFontSize.resultCache[size];2863 }2864 // For convenience in other places in my code, I handle all sizes, not just2865 // pixel sizes as the spec says. This means pixel sizes have to be passed2866 // in suffixed with "px", not as plain numbers.2867 if (normalizeFontSize(size) !== null) {2868 return getLegacyFontSize.resultCache[size] = cssSizeToLegacy(normalizeFontSize(size));2869 }2870 if (["x-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"].indexOf(size) == -12871 && !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc|px)$/.test(size)) {2872 // There is no sensible legacy size for things like "2em".2873 return getLegacyFontSize.resultCache[size] = null;2874 }2875 var font = document.createElement("font");2876 document.body.appendChild(font);2877 if (size == "xxx-large") {2878 font.size = 7;2879 } else {2880 font.style.fontSize = size;2881 }2882 var pixelSize = parseInt(getComputedStyle(font).fontSize);2883 document.body.removeChild(font);2884 // "Let returned size be 1."2885 var returnedSize = 1;2886 // "While returned size is less than 7:"2887 while (returnedSize < 7) {2888 // "Let lower bound be the resolved value of "font-size" in pixels2889 // of a font element whose size attribute is set to returned size."2890 var font = document.createElement("font");2891 font.size = returnedSize;2892 document.body.appendChild(font);2893 var lowerBound = parseInt(getComputedStyle(font).fontSize);2894 // "Let upper bound be the resolved value of "font-size" in pixels2895 // of a font element whose size attribute is set to one plus2896 // returned size."2897 font.size = 1 + returnedSize;2898 var upperBound = parseInt(getComputedStyle(font).fontSize);2899 document.body.removeChild(font);2900 // "Let average be the average of upper bound and lower bound."2901 var average = (upperBound + lowerBound)/2;2902 // "If pixel size is less than average, return the one-element2903 // string consisting of the digit returned size."2904 if (pixelSize < average) {2905 return getLegacyFontSize.resultCache[size] = String(returnedSize);2906 }2907 // "Add one to returned size."2908 returnedSize++;2909 }2910 // "Return "7"."2911 return getLegacyFontSize.resultCache[size] = "7";2912}2913//@}2914///// The foreColor command /////2915//@{2916commands.forecolor = {2917 action: function(value) {2918 // Copy-pasted, same as backColor and hiliteColor2919 // "If value is not a valid CSS color, prepend "#" to it."2920 //2921 // "If value is still not a valid CSS color, or if it is currentColor,2922 // return false."2923 //2924 // Cheap hack for testing, no attempt to be comprehensive.2925 if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {2926 value = "#" + value;2927 }2928 if (!/^(rgba?|hsla?)\(.*\)$/.test(value)2929 && !parseSimpleColor(value)2930 && value.toLowerCase() != "transparent") {2931 return false;2932 }2933 // "Set the selection's value to value."2934 setSelectionValue("forecolor", value);2935 // "Return true."2936 return true;2937 }, standardInlineValueCommand: true, relevantCssProperty: "color",2938 equivalentValues: function(val1, val2) {2939 // "Either both strings are valid CSS colors and have the same red,2940 // green, blue, and alpha components, or neither string is a valid CSS2941 // color."2942 return normalizeColor(val1) === normalizeColor(val2);2943 },2944};2945//@}2946///// The hiliteColor command /////2947//@{2948commands.hilitecolor = {2949 // Copy-pasted, same as backColor2950 action: function(value) {2951 // Action is further copy-pasted, same as foreColor2952 // "If value is not a valid CSS color, prepend "#" to it."2953 //2954 // "If value is still not a valid CSS color, or if it is currentColor,2955 // return false."2956 //2957 // Cheap hack for testing, no attempt to be comprehensive.2958 if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {2959 value = "#" + value;2960 }2961 if (!/^(rgba?|hsla?)\(.*\)$/.test(value)2962 && !parseSimpleColor(value)2963 && value.toLowerCase() != "transparent") {2964 return false;2965 }2966 // "Set the selection's value to value."2967 setSelectionValue("hilitecolor", value);2968 // "Return true."2969 return true;2970 }, indeterm: function() {2971 // "True if among editable Text nodes that are effectively contained in2972 // the active range, there are two that have distinct effective command2973 // values. Otherwise false."2974 return getAllEffectivelyContainedNodes(getActiveRange(), function(node) {2975 return isEditable(node) && node.nodeType == Node.TEXT_NODE;2976 }).map(function(node) {2977 return getEffectiveCommandValue(node, "hilitecolor");2978 }).filter(function(value, i, arr) {2979 return arr.slice(0, i).indexOf(value) == -1;2980 }).length >= 2;2981 }, standardInlineValueCommand: true, relevantCssProperty: "backgroundColor",2982 equivalentValues: function(val1, val2) {2983 // "Either both strings are valid CSS colors and have the same red,2984 // green, blue, and alpha components, or neither string is a valid CSS2985 // color."2986 return normalizeColor(val1) === normalizeColor(val2);2987 },2988};2989//@}2990///// The italic command /////2991//@{2992commands.italic = {2993 action: function() {2994 // "If queryCommandState("italic") returns true, set the selection's2995 // value to "normal". Otherwise set the selection's value to "italic".2996 // Either way, return true."2997 if (myQueryCommandState("italic")) {2998 setSelectionValue("italic", "normal");2999 } else {3000 setSelectionValue("italic", "italic");3001 }3002 return true;3003 }, inlineCommandActivatedValues: ["italic", "oblique"],3004 relevantCssProperty: "fontStyle"3005};3006//@}3007///// The removeFormat command /////3008//@{3009commands.removeformat = {3010 action: function() {3011 // "A removeFormat candidate is an editable HTML element with local3012 // name "abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite",3013 // "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q",3014 // "s", "samp", "small", "span", "strike", "strong", "sub", "sup",3015 // "tt", "u", or "var"."3016 function isRemoveFormatCandidate(node) {3017 return isEditable(node)3018 && isHtmlElement(node, ["abbr", "acronym", "b", "bdi", "bdo",3019 "big", "blink", "cite", "code", "dfn", "em", "font", "i",3020 "ins", "kbd", "mark", "nobr", "q", "s", "samp", "small",3021 "span", "strike", "strong", "sub", "sup", "tt", "u", "var"]);3022 }3023 // "Let elements to remove be a list of every removeFormat candidate3024 // effectively contained in the active range."3025 var elementsToRemove = getAllEffectivelyContainedNodes(getActiveRange(), isRemoveFormatCandidate);3026 // "For each element in elements to remove:"3027 elementsToRemove.forEach(function(element) {3028 // "While element has children, insert the first child of element3029 // into the parent of element immediately before element,3030 // preserving ranges."3031 while (element.hasChildNodes()) {3032 movePreservingRanges(element.firstChild, element.parentNode, getNodeIndex(element));3033 }3034 // "Remove element from its parent."3035 element.parentNode.removeChild(element);3036 });3037 // "If the active range's start node is an editable Text node, and its3038 // start offset is neither zero nor its start node's length, call3039 // splitText() on the active range's start node, with argument equal to3040 // the active range's start offset. Then set the active range's start3041 // node to the result, and its start offset to zero."3042 if (isEditable(getActiveRange().startContainer)3043 && getActiveRange().startContainer.nodeType == Node.TEXT_NODE3044 && getActiveRange().startOffset != 03045 && getActiveRange().startOffset != getNodeLength(getActiveRange().startContainer)) {3046 // Account for browsers not following range mutation rules3047 if (getActiveRange().startContainer == getActiveRange().endContainer) {3048 var newEnd = getActiveRange().endOffset - getActiveRange().startOffset;3049 var newNode = getActiveRange().startContainer.splitText(getActiveRange().startOffset);3050 getActiveRange().setStart(newNode, 0);3051 getActiveRange().setEnd(newNode, newEnd);3052 } else {3053 getActiveRange().setStart(getActiveRange().startContainer.splitText(getActiveRange().startOffset), 0);3054 }3055 }3056 // "If the active range's end node is an editable Text node, and its3057 // end offset is neither zero nor its end node's length, call3058 // splitText() on the active range's end node, with argument equal to3059 // the active range's end offset."3060 if (isEditable(getActiveRange().endContainer)3061 && getActiveRange().endContainer.nodeType == Node.TEXT_NODE3062 && getActiveRange().endOffset != 03063 && getActiveRange().endOffset != getNodeLength(getActiveRange().endContainer)) {3064 // IE seems to mutate the range incorrectly here, so we need3065 // correction here as well. Have to be careful to set the range to3066 // something not including the text node so that getActiveRange()3067 // doesn't throw an exception due to a temporarily detached3068 // endpoint.3069 var newStart = [getActiveRange().startContainer, getActiveRange().startOffset];3070 var newEnd = [getActiveRange().endContainer, getActiveRange().endOffset];3071 getActiveRange().setEnd(document.documentElement, 0);3072 newEnd[0].splitText(newEnd[1]);3073 getActiveRange().setStart(newStart[0], newStart[1]);3074 getActiveRange().setEnd(newEnd[0], newEnd[1]);3075 }3076 // "Let node list consist of all editable nodes effectively contained3077 // in the active range."3078 //3079 // "For each node in node list, while node's parent is a removeFormat3080 // candidate in the same editing host as node, split the parent of the3081 // one-node list consisting of node."3082 getAllEffectivelyContainedNodes(getActiveRange(), isEditable).forEach(function(node) {3083 while (isRemoveFormatCandidate(node.parentNode)3084 && inSameEditingHost(node.parentNode, node)) {3085 splitParent([node]);3086 }3087 });3088 // "For each of the entries in the following list, in the given order,3089 // set the selection's value to null, with command as given."3090 [3091 "subscript",3092 "bold",3093 "fontname",3094 "fontsize",3095 "forecolor",3096 "hilitecolor",3097 "italic",3098 "strikethrough",3099 "underline",3100 ].forEach(function(command) {3101 setSelectionValue(command, null);3102 });3103 // "Return true."3104 return true;3105 }3106};3107//@}3108///// The strikethrough command /////3109//@{3110commands.strikethrough = {3111 action: function() {3112 // "If queryCommandState("strikethrough") returns true, set the3113 // selection's value to null. Otherwise set the selection's value to3114 // "line-through". Either way, return true."3115 if (myQueryCommandState("strikethrough")) {3116 setSelectionValue("strikethrough", null);3117 } else {3118 setSelectionValue("strikethrough", "line-through");3119 }3120 return true;3121 }, inlineCommandActivatedValues: ["line-through"]3122};3123//@}3124///// The subscript command /////3125//@{3126commands.subscript = {3127 action: function() {3128 // "Call queryCommandState("subscript"), and let state be the result."3129 var state = myQueryCommandState("subscript");3130 // "Set the selection's value to null."3131 setSelectionValue("subscript", null);3132 // "If state is false, set the selection's value to "subscript"."3133 if (!state) {3134 setSelectionValue("subscript", "subscript");3135 }3136 // "Return true."3137 return true;3138 }, indeterm: function() {3139 // "True if either among formattable nodes that are effectively3140 // contained in the active range, there is at least one with effective3141 // command value "subscript" and at least one with some other effective3142 // command value; or if there is some formattable node effectively3143 // contained in the active range with effective command value "mixed".3144 // Otherwise false."3145 var nodes = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode);3146 return (nodes.some(function(node) { return getEffectiveCommandValue(node, "subscript") == "subscript" })3147 && nodes.some(function(node) { return getEffectiveCommandValue(node, "subscript") != "subscript" }))3148 || nodes.some(function(node) { return getEffectiveCommandValue(node, "subscript") == "mixed" });3149 }, inlineCommandActivatedValues: ["subscript"],3150};3151//@}3152///// The superscript command /////3153//@{3154commands.superscript = {3155 action: function() {3156 // "Call queryCommandState("superscript"), and let state be the3157 // result."3158 var state = myQueryCommandState("superscript");3159 // "Set the selection's value to null."3160 setSelectionValue("superscript", null);3161 // "If state is false, set the selection's value to "superscript"."3162 if (!state) {3163 setSelectionValue("superscript", "superscript");3164 }3165 // "Return true."3166 return true;3167 }, indeterm: function() {3168 // "True if either among formattable nodes that are effectively3169 // contained in the active range, there is at least one with effective3170 // command value "superscript" and at least one with some other3171 // effective command value; or if there is some formattable node3172 // effectively contained in the active range with effective command3173 // value "mixed". Otherwise false."3174 var nodes = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode);3175 return (nodes.some(function(node) { return getEffectiveCommandValue(node, "superscript") == "superscript" })3176 && nodes.some(function(node) { return getEffectiveCommandValue(node, "superscript") != "superscript" }))3177 || nodes.some(function(node) { return getEffectiveCommandValue(node, "superscript") == "mixed" });3178 }, inlineCommandActivatedValues: ["superscript"],3179};3180//@}3181///// The underline command /////3182//@{3183commands.underline = {3184 action: function() {3185 // "If queryCommandState("underline") returns true, set the selection's3186 // value to null. Otherwise set the selection's value to "underline".3187 // Either way, return true."3188 if (myQueryCommandState("underline")) {3189 setSelectionValue("underline", null);3190 } else {3191 setSelectionValue("underline", "underline");3192 }3193 return true;3194 }, inlineCommandActivatedValues: ["underline"]3195};3196//@}3197///// The unlink command /////3198//@{3199commands.unlink = {3200 action: function() {3201 // "Let hyperlinks be a list of every a element that has an href3202 // attribute and is contained in the active range or is an ancestor of3203 // one of its boundary points."3204 //3205 // As usual, take care to ensure it's tree order. The correctness of3206 // the following is left as an exercise for the reader.3207 var range = getActiveRange();3208 var hyperlinks = [];3209 for (3210 var node = range.startContainer;3211 node;3212 node = node.parentNode3213 ) {3214 if (isHtmlElement(node, "A")3215 && node.hasAttribute("href")) {3216 hyperlinks.unshift(node);3217 }3218 }3219 for (3220 var node = range.startContainer;3221 node != nextNodeDescendants(range.endContainer);3222 node = nextNode(node)3223 ) {3224 if (isHtmlElement(node, "A")3225 && node.hasAttribute("href")3226 && (isContained(node, range)3227 || isAncestor(node, range.endContainer)3228 || node == range.endContainer)) {3229 hyperlinks.push(node);3230 }3231 }3232 // "Clear the value of each member of hyperlinks."3233 for (var i = 0; i < hyperlinks.length; i++) {3234 clearValue(hyperlinks[i], "unlink");3235 }3236 // "Return true."3237 return true;3238 }3239};3240//@}3241/////////////////////////////////////3242///// Block formatting commands /////3243/////////////////////////////////////3244///// Block formatting command definitions /////3245//@{3246// "An indentation element is either a blockquote, or a div that has a style3247// attribute that sets "margin" or some subproperty of it."3248function isIndentationElement(node) {3249 if (!isHtmlElement(node)) {3250 return false;3251 }3252 if (node.tagName == "BLOCKQUOTE") {3253 return true;3254 }3255 if (node.tagName != "DIV") {3256 return false;3257 }3258 for (var i = 0; i < node.style.length; i++) {3259 // Approximate check3260 if (/^(-[a-z]+-)?margin/.test(node.style[i])) {3261 return true;3262 }3263 }3264 return false;3265}3266// "A simple indentation element is an indentation element that has no3267// attributes except possibly3268//3269// * "a style attribute that sets no properties other than "margin",3270// "border", "padding", or subproperties of those; and/or3271// * "a dir attribute."3272function isSimpleIndentationElement(node) {3273 if (!isIndentationElement(node)) {3274 return false;3275 }3276 for (var i = 0; i < node.attributes.length; i++) {3277 if (!isHtmlNamespace(node.attributes[i].namespaceURI)3278 || ["style", "dir"].indexOf(node.attributes[i].name) == -1) {3279 return false;3280 }3281 }3282 for (var i = 0; i < node.style.length; i++) {3283 // This is approximate, but it works well enough for my purposes.3284 if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {3285 return false;3286 }3287 }3288 return true;3289}3290// "A non-list single-line container is an HTML element with local name3291// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre",3292// or "xmp"."3293function isNonListSingleLineContainer(node) {3294 return isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5",3295 "h6", "listing", "p", "pre", "xmp"]);3296}3297// "A single-line container is either a non-list single-line container, or an3298// HTML element with local name "li", "dt", or "dd"."3299function isSingleLineContainer(node) {3300 return isNonListSingleLineContainer(node)3301 || isHtmlElement(node, ["li", "dt", "dd"]);3302}3303function getBlockNodeOf(node) {3304 // "While node is an inline node, set node to its parent."3305 while (isInlineNode(node)) {3306 node = node.parentNode;3307 }3308 // "Return node."3309 return node;3310}3311//@}3312///// Assorted block formatting command algorithms /////3313//@{3314function fixDisallowedAncestors(node) {3315 // "If node is not editable, abort these steps."3316 if (!isEditable(node)) {3317 return;3318 }3319 // "If node is not an allowed child of any of its ancestors in the same3320 // editing host:"3321 if (getAncestors(node).every(function(ancestor) {3322 return !inSameEditingHost(node, ancestor)3323 || !isAllowedChild(node, ancestor)3324 })) {3325 // "If node is a dd or dt, wrap the one-node list consisting of node,3326 // with sibling criteria returning true for any dl with no attributes3327 // and false otherwise, and new parent instructions returning the3328 // result of calling createElement("dl") on the context object. Then3329 // abort these steps."3330 if (isHtmlElement(node, ["dd", "dt"])) {3331 wrap([node],3332 function(sibling) { return isHtmlElement(sibling, "dl") && !sibling.attributes.length },3333 function() { return document.createElement("dl") });3334 return;3335 }3336 // "If "p" is not an allowed child of the editing host of node, abort3337 // these steps."3338 if (!isAllowedChild("p", getEditingHostOf(node))) {3339 return;3340 }3341 // "If node is not a prohibited paragraph child, abort these steps."3342 if (!isProhibitedParagraphChild(node)) {3343 return;3344 }3345 // "Set the tag name of node to the default single-line container name,3346 // and let node be the result."3347 node = setTagName(node, defaultSingleLineContainerName);3348 // "Fix disallowed ancestors of node."3349 fixDisallowedAncestors(node);3350 // "Let children be node's children."3351 var children = [].slice.call(node.childNodes);3352 // "For each child in children, if child is a prohibited paragraph3353 // child:"3354 children.filter(isProhibitedParagraphChild)3355 .forEach(function(child) {3356 // "Record the values of the one-node list consisting of child, and3357 // let values be the result."3358 var values = recordValues([child]);3359 // "Split the parent of the one-node list consisting of child."3360 splitParent([child]);3361 // "Restore the values from values."3362 restoreValues(values);3363 });3364 // "Abort these steps."3365 return;3366 }3367 // "Record the values of the one-node list consisting of node, and let3368 // values be the result."3369 var values = recordValues([node]);3370 // "While node is not an allowed child of its parent, split the parent of3371 // the one-node list consisting of node."3372 while (!isAllowedChild(node, node.parentNode)) {3373 splitParent([node]);3374 }3375 // "Restore the values from values."3376 restoreValues(values);3377}3378function normalizeSublists(item) {3379 // "If item is not an li or it is not editable or its parent is not3380 // editable, abort these steps."3381 if (!isHtmlElement(item, "LI")3382 || !isEditable(item)3383 || !isEditable(item.parentNode)) {3384 return;3385 }3386 // "Let new item be null."3387 var newItem = null;3388 // "While item has an ol or ul child:"3389 while ([].some.call(item.childNodes, function (node) { return isHtmlElement(node, ["OL", "UL"]) })) {3390 // "Let child be the last child of item."3391 var child = item.lastChild;3392 // "If child is an ol or ul, or new item is null and child is a Text3393 // node whose data consists of zero of more space characters:"3394 if (isHtmlElement(child, ["OL", "UL"])3395 || (!newItem && child.nodeType == Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {3396 // "Set new item to null."3397 newItem = null;3398 // "Insert child into the parent of item immediately following3399 // item, preserving ranges."3400 movePreservingRanges(child, item.parentNode, 1 + getNodeIndex(item));3401 // "Otherwise:"3402 } else {3403 // "If new item is null, let new item be the result of calling3404 // createElement("li") on the ownerDocument of item, then insert3405 // new item into the parent of item immediately after item."3406 if (!newItem) {3407 newItem = item.ownerDocument.createElement("li");3408 item.parentNode.insertBefore(newItem, item.nextSibling);3409 }3410 // "Insert child into new item as its first child, preserving3411 // ranges."3412 movePreservingRanges(child, newItem, 0);3413 }3414 }3415}3416function getSelectionListState() {3417 // "If the active range is null, return "none"."3418 if (!getActiveRange()) {3419 return "none";3420 }3421 // "Block-extend the active range, and let new range be the result."3422 var newRange = blockExtend(getActiveRange());3423 // "Let node list be a list of nodes, initially empty."3424 //3425 // "For each node contained in new range, append node to node list if the3426 // last member of node list (if any) is not an ancestor of node; node is3427 // editable; node is not an indentation element; and node is either an ol3428 // or ul, or the child of an ol or ul, or an allowed child of "li"."3429 var nodeList = getContainedNodes(newRange, function(node) {3430 return isEditable(node)3431 && !isIndentationElement(node)3432 && (isHtmlElement(node, ["ol", "ul"])3433 || isHtmlElement(node.parentNode, ["ol", "ul"])3434 || isAllowedChild(node, "li"));3435 });3436 // "If node list is empty, return "none"."3437 if (!nodeList.length) {3438 return "none";3439 }3440 // "If every member of node list is either an ol or the child of an ol or3441 // the child of an li child of an ol, and none is a ul or an ancestor of a3442 // ul, return "ol"."3443 if (nodeList.every(function(node) {3444 return isHtmlElement(node, "ol")3445 || isHtmlElement(node.parentNode, "ol")3446 || (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ol"));3447 })3448 && !nodeList.some(function(node) { return isHtmlElement(node, "ul") || ("querySelector" in node && node.querySelector("ul")) })) {3449 return "ol";3450 }3451 // "If every member of node list is either a ul or the child of a ul or the3452 // child of an li child of a ul, and none is an ol or an ancestor of an ol,3453 // return "ul"."3454 if (nodeList.every(function(node) {3455 return isHtmlElement(node, "ul")3456 || isHtmlElement(node.parentNode, "ul")3457 || (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ul"));3458 })3459 && !nodeList.some(function(node) { return isHtmlElement(node, "ol") || ("querySelector" in node && node.querySelector("ol")) })) {3460 return "ul";3461 }3462 var hasOl = nodeList.some(function(node) {3463 return isHtmlElement(node, "ol")3464 || isHtmlElement(node.parentNode, "ol")3465 || ("querySelector" in node && node.querySelector("ol"))3466 || (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ol"));3467 });3468 var hasUl = nodeList.some(function(node) {3469 return isHtmlElement(node, "ul")3470 || isHtmlElement(node.parentNode, "ul")3471 || ("querySelector" in node && node.querySelector("ul"))3472 || (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ul"));3473 });3474 // "If some member of node list is either an ol or the child or ancestor of3475 // an ol or the child of an li child of an ol, and some member of node list3476 // is either a ul or the child or ancestor of a ul or the child of an li3477 // child of a ul, return "mixed"."3478 if (hasOl && hasUl) {3479 return "mixed";3480 }3481 // "If some member of node list is either an ol or the child or ancestor of3482 // an ol or the child of an li child of an ol, return "mixed ol"."3483 if (hasOl) {3484 return "mixed ol";3485 }3486 // "If some member of node list is either a ul or the child or ancestor of3487 // a ul or the child of an li child of a ul, return "mixed ul"."3488 if (hasUl) {3489 return "mixed ul";3490 }3491 // "Return "none"."3492 return "none";3493}3494function getAlignmentValue(node) {3495 // "While node is neither null nor an Element, or it is an Element but its3496 // "display" property has resolved value "inline" or "none", set node to3497 // its parent."3498 while ((node && node.nodeType != Node.ELEMENT_NODE)3499 || (node.nodeType == Node.ELEMENT_NODE3500 && ["inline", "none"].indexOf(getComputedStyle(node).display) != -1)) {3501 node = node.parentNode;3502 }3503 // "If node is not an Element, return "left"."3504 if (!node || node.nodeType != Node.ELEMENT_NODE) {3505 return "left";3506 }3507 var resolvedValue = getComputedStyle(node).textAlign3508 // Hack around browser non-standardness3509 .replace(/^-(moz|webkit)-/, "")3510 .replace(/^auto$/, "start");3511 // "If node's "text-align" property has resolved value "start", return3512 // "left" if the directionality of node is "ltr", "right" if it is "rtl"."3513 if (resolvedValue == "start") {3514 return getDirectionality(node) == "ltr" ? "left" : "right";3515 }3516 // "If node's "text-align" property has resolved value "end", return3517 // "right" if the directionality of node is "ltr", "left" if it is "rtl"."3518 if (resolvedValue == "end") {3519 return getDirectionality(node) == "ltr" ? "right" : "left";3520 }3521 // "If node's "text-align" property has resolved value "center", "justify",3522 // "left", or "right", return that value."3523 if (["center", "justify", "left", "right"].indexOf(resolvedValue) != -1) {3524 return resolvedValue;3525 }3526 // "Return "left"."3527 return "left";3528}3529function getNextEquivalentPoint(node, offset) {3530 // "If node's length is zero, return null."3531 if (getNodeLength(node) == 0) {3532 return null;3533 }3534 // "If offset is node's length, and node's parent is not null, and node is3535 // an inline node, return (node's parent, 1 + node's index)."3536 if (offset == getNodeLength(node)3537 && node.parentNode3538 && isInlineNode(node)) {3539 return [node.parentNode, 1 + getNodeIndex(node)];3540 }3541 // "If node has a child with index offset, and that child's length is not3542 // zero, and that child is an inline node, return (that child, 0)."3543 if (0 <= offset3544 && offset < node.childNodes.length3545 && getNodeLength(node.childNodes[offset]) != 03546 && isInlineNode(node.childNodes[offset])) {3547 return [node.childNodes[offset], 0];3548 }3549 // "Return null."3550 return null;3551}3552function getPreviousEquivalentPoint(node, offset) {3553 // "If node's length is zero, return null."3554 if (getNodeLength(node) == 0) {3555 return null;3556 }3557 // "If offset is 0, and node's parent is not null, and node is an inline3558 // node, return (node's parent, node's index)."3559 if (offset == 03560 && node.parentNode3561 && isInlineNode(node)) {3562 return [node.parentNode, getNodeIndex(node)];3563 }3564 // "If node has a child with index offset − 1, and that child's length is3565 // not zero, and that child is an inline node, return (that child, that3566 // child's length)."3567 if (0 <= offset - 13568 && offset - 1 < node.childNodes.length3569 && getNodeLength(node.childNodes[offset - 1]) != 03570 && isInlineNode(node.childNodes[offset - 1])) {3571 return [node.childNodes[offset - 1], getNodeLength(node.childNodes[offset - 1])];3572 }3573 // "Return null."3574 return null;3575}3576function getFirstEquivalentPoint(node, offset) {3577 // "While (node, offset)'s previous equivalent point is not null, set3578 // (node, offset) to its previous equivalent point."3579 var prev;3580 while (prev = getPreviousEquivalentPoint(node, offset)) {3581 node = prev[0];3582 offset = prev[1];3583 }3584 // "Return (node, offset)."3585 return [node, offset];3586}3587function getLastEquivalentPoint(node, offset) {3588 // "While (node, offset)'s next equivalent point is not null, set (node,3589 // offset) to its next equivalent point."3590 var next;3591 while (next = getNextEquivalentPoint(node, offset)) {3592 node = next[0];3593 offset = next[1];3594 }3595 // "Return (node, offset)."3596 return [node, offset];3597}3598//@}3599///// Block-extending a range /////3600//@{3601// "A boundary point (node, offset) is a block start point if either node's3602// parent is null and offset is zero; or node has a child with index offset −3603// 1, and that child is either a visible block node or a visible br."3604function isBlockStartPoint(node, offset) {3605 return (!node.parentNode && offset == 0)3606 || (0 <= offset - 13607 && offset - 1 < node.childNodes.length3608 && isVisible(node.childNodes[offset - 1])3609 && (isBlockNode(node.childNodes[offset - 1])3610 || isHtmlElement(node.childNodes[offset - 1], "br")));3611}3612// "A boundary point (node, offset) is a block end point if either node's3613// parent is null and offset is node's length; or node has a child with index3614// offset, and that child is a visible block node."3615function isBlockEndPoint(node, offset) {3616 return (!node.parentNode && offset == getNodeLength(node))3617 || (offset < node.childNodes.length3618 && isVisible(node.childNodes[offset])3619 && isBlockNode(node.childNodes[offset]));3620}3621// "A boundary point is a block boundary point if it is either a block start3622// point or a block end point."3623function isBlockBoundaryPoint(node, offset) {3624 return isBlockStartPoint(node, offset)3625 || isBlockEndPoint(node, offset);3626}3627function blockExtend(range) {3628 // "Let start node, start offset, end node, and end offset be the start3629 // and end nodes and offsets of the range."3630 var startNode = range.startContainer;3631 var startOffset = range.startOffset;3632 var endNode = range.endContainer;3633 var endOffset = range.endOffset;3634 // "If some ancestor container of start node is an li, set start offset to3635 // the index of the last such li in tree order, and set start node to that3636 // li's parent."3637 var liAncestors = getAncestors(startNode).concat(startNode)3638 .filter(function(ancestor) { return isHtmlElement(ancestor, "li") })3639 .slice(-1);3640 if (liAncestors.length) {3641 startOffset = getNodeIndex(liAncestors[0]);3642 startNode = liAncestors[0].parentNode;3643 }3644 // "If (start node, start offset) is not a block start point, repeat the3645 // following steps:"3646 if (!isBlockStartPoint(startNode, startOffset)) do {3647 // "If start offset is zero, set it to start node's index, then set3648 // start node to its parent."3649 if (startOffset == 0) {3650 startOffset = getNodeIndex(startNode);3651 startNode = startNode.parentNode;3652 // "Otherwise, subtract one from start offset."3653 } else {3654 startOffset--;3655 }3656 // "If (start node, start offset) is a block boundary point, break from3657 // this loop."3658 } while (!isBlockBoundaryPoint(startNode, startOffset));3659 // "While start offset is zero and start node's parent is not null, set3660 // start offset to start node's index, then set start node to its parent."3661 while (startOffset == 03662 && startNode.parentNode) {3663 startOffset = getNodeIndex(startNode);3664 startNode = startNode.parentNode;3665 }3666 // "If some ancestor container of end node is an li, set end offset to one3667 // plus the index of the last such li in tree order, and set end node to3668 // that li's parent."3669 var liAncestors = getAncestors(endNode).concat(endNode)3670 .filter(function(ancestor) { return isHtmlElement(ancestor, "li") })3671 .slice(-1);3672 if (liAncestors.length) {3673 endOffset = 1 + getNodeIndex(liAncestors[0]);3674 endNode = liAncestors[0].parentNode;3675 }3676 // "If (end node, end offset) is not a block end point, repeat the3677 // following steps:"3678 if (!isBlockEndPoint(endNode, endOffset)) do {3679 // "If end offset is end node's length, set it to one plus end node's3680 // index, then set end node to its parent."3681 if (endOffset == getNodeLength(endNode)) {3682 endOffset = 1 + getNodeIndex(endNode);3683 endNode = endNode.parentNode;3684 // "Otherwise, add one to end offset.3685 } else {3686 endOffset++;3687 }3688 // "If (end node, end offset) is a block boundary point, break from3689 // this loop."3690 } while (!isBlockBoundaryPoint(endNode, endOffset));3691 // "While end offset is end node's length and end node's parent is not3692 // null, set end offset to one plus end node's index, then set end node to3693 // its parent."3694 while (endOffset == getNodeLength(endNode)3695 && endNode.parentNode) {3696 endOffset = 1 + getNodeIndex(endNode);3697 endNode = endNode.parentNode;3698 }3699 // "Let new range be a new range whose start and end nodes and offsets3700 // are start node, start offset, end node, and end offset."3701 var newRange = startNode.ownerDocument.createRange();3702 newRange.setStart(startNode, startOffset);3703 newRange.setEnd(endNode, endOffset);3704 // "Return new range."3705 return newRange;3706}3707function followsLineBreak(node) {3708 // "Let offset be zero."3709 var offset = 0;3710 // "While (node, offset) is not a block boundary point:"3711 while (!isBlockBoundaryPoint(node, offset)) {3712 // "If node has a visible child with index offset minus one, return3713 // false."3714 if (0 <= offset - 13715 && offset - 1 < node.childNodes.length3716 && isVisible(node.childNodes[offset - 1])) {3717 return false;3718 }3719 // "If offset is zero or node has no children, set offset to node's3720 // index, then set node to its parent."3721 if (offset == 03722 || !node.hasChildNodes()) {3723 offset = getNodeIndex(node);3724 node = node.parentNode;3725 // "Otherwise, set node to its child with index offset minus one, then3726 // set offset to node's length."3727 } else {3728 node = node.childNodes[offset - 1];3729 offset = getNodeLength(node);3730 }3731 }3732 // "Return true."3733 return true;3734}3735function precedesLineBreak(node) {3736 // "Let offset be node's length."3737 var offset = getNodeLength(node);3738 // "While (node, offset) is not a block boundary point:"3739 while (!isBlockBoundaryPoint(node, offset)) {3740 // "If node has a visible child with index offset, return false."3741 if (offset < node.childNodes.length3742 && isVisible(node.childNodes[offset])) {3743 return false;3744 }3745 // "If offset is node's length or node has no children, set offset to3746 // one plus node's index, then set node to its parent."3747 if (offset == getNodeLength(node)3748 || !node.hasChildNodes()) {3749 offset = 1 + getNodeIndex(node);3750 node = node.parentNode;3751 // "Otherwise, set node to its child with index offset and set offset3752 // to zero."3753 } else {3754 node = node.childNodes[offset];3755 offset = 0;3756 }3757 }3758 // "Return true."3759 return true;3760}3761//@}3762///// Recording and restoring overrides /////3763//@{3764function recordCurrentOverrides() {3765 // "Let overrides be a list of (string, string or boolean) ordered pairs,3766 // initially empty."3767 var overrides = [];3768 // "If there is a value override for "createLink", add ("createLink", value3769 // override for "createLink") to overrides."3770 if (getValueOverride("createlink") !== undefined) {3771 overrides.push(["createlink", getValueOverride("createlink")]);3772 }3773 // "For each command in the list "bold", "italic", "strikethrough",3774 // "subscript", "superscript", "underline", in order: if there is a state3775 // override for command, add (command, command's state override) to3776 // overrides."3777 ["bold", "italic", "strikethrough", "subscript", "superscript",3778 "underline"].forEach(function(command) {3779 if (getStateOverride(command) !== undefined) {3780 overrides.push([command, getStateOverride(command)]);3781 }3782 });3783 // "For each command in the list "fontName", "fontSize", "foreColor",3784 // "hiliteColor", in order: if there is a value override for command, add3785 // (command, command's value override) to overrides."3786 ["fontname", "fontsize", "forecolor",3787 "hilitecolor"].forEach(function(command) {3788 if (getValueOverride(command) !== undefined) {3789 overrides.push([command, getValueOverride(command)]);3790 }3791 });3792 // "Return overrides."3793 return overrides;3794}3795function recordCurrentStatesAndValues() {3796 // "Let overrides be a list of (string, string or boolean) ordered pairs,3797 // initially empty."3798 var overrides = [];3799 // "Let node be the first formattable node effectively contained in the3800 // active range, or null if there is none."3801 var node = getAllEffectivelyContainedNodes(getActiveRange())3802 .filter(isFormattableNode)[0];3803 // "If node is null, return overrides."3804 if (!node) {3805 return overrides;3806 }3807 // "Add ("createLink", node's effective command value for "createLink") to3808 // overrides."3809 overrides.push(["createlink", getEffectiveCommandValue(node, "createlink")]);3810 // "For each command in the list "bold", "italic", "strikethrough",3811 // "subscript", "superscript", "underline", in order: if node's effective3812 // command value for command is one of its inline command activated values,3813 // add (command, true) to overrides, and otherwise add (command, false) to3814 // overrides."3815 ["bold", "italic", "strikethrough", "subscript", "superscript",3816 "underline"].forEach(function(command) {3817 if (commands[command].inlineCommandActivatedValues3818 .indexOf(getEffectiveCommandValue(node, command)) != -1) {3819 overrides.push([command, true]);3820 } else {3821 overrides.push([command, false]);3822 }3823 });3824 // "For each command in the list "fontName", "foreColor", "hiliteColor", in3825 // order: add (command, command's value) to overrides."3826 ["fontname", "fontsize", "forecolor", "hilitecolor"].forEach(function(command) {3827 overrides.push([command, commands[command].value()]);3828 });3829 // "Add ("fontSize", node's effective command value for "fontSize") to3830 // overrides."3831 overrides.push(["fontsize", getEffectiveCommandValue(node, "fontsize")]);3832 // "Return overrides."3833 return overrides;3834}3835function restoreStatesAndValues(overrides) {3836 // "Let node be the first formattable node effectively contained in the3837 // active range, or null if there is none."3838 var node = getAllEffectivelyContainedNodes(getActiveRange())3839 .filter(isFormattableNode)[0];3840 // "If node is not null, then for each (command, override) pair in3841 // overrides, in order:"3842 if (node) {3843 for (var i = 0; i < overrides.length; i++) {3844 var command = overrides[i][0];3845 var override = overrides[i][1];3846 // "If override is a boolean, and queryCommandState(command)3847 // returns something different from override, take the action for3848 // command, with value equal to the empty string."3849 if (typeof override == "boolean"3850 && myQueryCommandState(command) != override) {3851 commands[command].action("");3852 // "Otherwise, if override is a string, and command is neither3853 // "createLink" nor "fontSize", and queryCommandValue(command)3854 // returns something not equivalent to override, take the action3855 // for command, with value equal to override."3856 } else if (typeof override == "string"3857 && command != "createlink"3858 && command != "fontsize"3859 && !areEquivalentValues(command, myQueryCommandValue(command), override)) {3860 commands[command].action(override);3861 // "Otherwise, if override is a string; and command is3862 // "createLink"; and either there is a value override for3863 // "createLink" that is not equal to override, or there is no value3864 // override for "createLink" and node's effective command value for3865 // "createLink" is not equal to override: take the action for3866 // "createLink", with value equal to override."3867 } else if (typeof override == "string"3868 && command == "createlink"3869 && (3870 (3871 getValueOverride("createlink") !== undefined3872 && getValueOverride("createlink") !== override3873 ) || (3874 getValueOverride("createlink") === undefined3875 && getEffectiveCommandValue(node, "createlink") !== override3876 )3877 )) {3878 commands.createlink.action(override);3879 // "Otherwise, if override is a string; and command is "fontSize";3880 // and either there is a value override for "fontSize" that is not3881 // equal to override, or there is no value override for "fontSize"3882 // and node's effective command value for "fontSize" is not loosely3883 // equivalent to override:"3884 } else if (typeof override == "string"3885 && command == "fontsize"3886 && (3887 (3888 getValueOverride("fontsize") !== undefined3889 && getValueOverride("fontsize") !== override3890 ) || (3891 getValueOverride("fontsize") === undefined3892 && !areLooselyEquivalentValues(command, getEffectiveCommandValue(node, "fontsize"), override)3893 )3894 )) {3895 // "Convert override to an integer number of pixels, and set3896 // override to the legacy font size for the result."3897 override = getLegacyFontSize(override);3898 // "Take the action for "fontSize", with value equal to3899 // override."3900 commands.fontsize.action(override);3901 // "Otherwise, continue this loop from the beginning."3902 } else {3903 continue;3904 }3905 // "Set node to the first formattable node effectively contained in3906 // the active range, if there is one."3907 node = getAllEffectivelyContainedNodes(getActiveRange())3908 .filter(isFormattableNode)[0]3909 || node;3910 }3911 // "Otherwise, for each (command, override) pair in overrides, in order:"3912 } else {3913 for (var i = 0; i < overrides.length; i++) {3914 var command = overrides[i][0];3915 var override = overrides[i][1];3916 // "If override is a boolean, set the state override for command to3917 // override."3918 if (typeof override == "boolean") {3919 setStateOverride(command, override);3920 }3921 // "If override is a string, set the value override for command to3922 // override."3923 if (typeof override == "string") {3924 setValueOverride(command, override);3925 }3926 }3927 }3928}3929//@}3930///// Deleting the selection /////3931//@{3932// The flags argument is a dictionary that can have blockMerging,3933// stripWrappers, and/or direction as keys.3934function deleteSelection(flags) {3935 if (flags === undefined) {3936 flags = {};3937 }3938 var blockMerging = "blockMerging" in flags ? Boolean(flags.blockMerging) : true;3939 var stripWrappers = "stripWrappers" in flags ? Boolean(flags.stripWrappers) : true;3940 var direction = "direction" in flags ? flags.direction : "forward";3941 // "If the active range is null, abort these steps and do nothing."3942 if (!getActiveRange()) {3943 return;3944 }3945 // "Canonicalize whitespace at the active range's start."3946 canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);3947 // "Canonicalize whitespace at the active range's end."3948 canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset);3949 // "Let (start node, start offset) be the last equivalent point for the3950 // active range's start."3951 var start = getLastEquivalentPoint(getActiveRange().startContainer, getActiveRange().startOffset);3952 var startNode = start[0];3953 var startOffset = start[1];3954 // "Let (end node, end offset) be the first equivalent point for the active3955 // range's end."3956 var end = getFirstEquivalentPoint(getActiveRange().endContainer, getActiveRange().endOffset);3957 var endNode = end[0];3958 var endOffset = end[1];3959 // "If (end node, end offset) is not after (start node, start offset):"3960 if (getPosition(endNode, endOffset, startNode, startOffset) !== "after") {3961 // "If direction is "forward", call collapseToStart() on the context3962 // object's Selection."3963 //3964 // Here and in a few other places, we check rangeCount to work around a3965 // WebKit bug: it will sometimes incorrectly remove ranges from the3966 // selection if nodes are removed, so collapseToStart() will throw.3967 // This will break everything if we're using an actual selection, but3968 // if getActiveRange() is really just returning globalRange and that's3969 // all we care about, it will work fine. I only add the extra check3970 // for errors I actually hit in testing.3971 if (direction == "forward") {3972 if (getSelection().rangeCount) {3973 getSelection().collapseToStart();3974 }3975 getActiveRange().collapse(true);3976 // "Otherwise, call collapseToEnd() on the context object's Selection."3977 } else {3978 getSelection().collapseToEnd();3979 getActiveRange().collapse(false);3980 }3981 // "Abort these steps."3982 return;3983 }3984 // "If start node is a Text node and start offset is 0, set start offset to3985 // the index of start node, then set start node to its parent."3986 if (startNode.nodeType == Node.TEXT_NODE3987 && startOffset == 0) {3988 startOffset = getNodeIndex(startNode);3989 startNode = startNode.parentNode;3990 }3991 // "If end node is a Text node and end offset is its length, set end offset3992 // to one plus the index of end node, then set end node to its parent."3993 if (endNode.nodeType == Node.TEXT_NODE3994 && endOffset == getNodeLength(endNode)) {3995 endOffset = 1 + getNodeIndex(endNode);3996 endNode = endNode.parentNode;3997 }3998 // "Call collapse(start node, start offset) on the context object's3999 // Selection."4000 getSelection().collapse(startNode, startOffset);4001 getActiveRange().setStart(startNode, startOffset);4002 // "Call extend(end node, end offset) on the context object's Selection."4003 getSelection().extend(endNode, endOffset);4004 getActiveRange().setEnd(endNode, endOffset);4005 // "Let start block be the active range's start node."4006 var startBlock = getActiveRange().startContainer;4007 // "While start block's parent is in the same editing host and start block4008 // is an inline node, set start block to its parent."4009 while (inSameEditingHost(startBlock, startBlock.parentNode)4010 && isInlineNode(startBlock)) {4011 startBlock = startBlock.parentNode;4012 }4013 // "If start block is neither a block node nor an editing host, or "span"4014 // is not an allowed child of start block, or start block is a td or th,4015 // set start block to null."4016 if ((!isBlockNode(startBlock) && !isEditingHost(startBlock))4017 || !isAllowedChild("span", startBlock)4018 || isHtmlElement(startBlock, ["td", "th"])) {4019 startBlock = null;4020 }4021 // "Let end block be the active range's end node."4022 var endBlock = getActiveRange().endContainer;4023 // "While end block's parent is in the same editing host and end block is4024 // an inline node, set end block to its parent."4025 while (inSameEditingHost(endBlock, endBlock.parentNode)4026 && isInlineNode(endBlock)) {4027 endBlock = endBlock.parentNode;4028 }4029 // "If end block is neither a block node nor an editing host, or "span" is4030 // not an allowed child of end block, or end block is a td or th, set end4031 // block to null."4032 if ((!isBlockNode(endBlock) && !isEditingHost(endBlock))4033 || !isAllowedChild("span", endBlock)4034 || isHtmlElement(endBlock, ["td", "th"])) {4035 endBlock = null;4036 }4037 // "Record current states and values, and let overrides be the result."4038 var overrides = recordCurrentStatesAndValues();4039 // "If start node and end node are the same, and start node is an editable4040 // Text node:"4041 if (startNode == endNode4042 && isEditable(startNode)4043 && startNode.nodeType == Node.TEXT_NODE) {4044 // "Call deleteData(start offset, end offset − start offset) on start4045 // node."4046 startNode.deleteData(startOffset, endOffset - startOffset);4047 // "Canonicalize whitespace at (start node, start offset), with fix4048 // collapsed space false."4049 canonicalizeWhitespace(startNode, startOffset, false);4050 // "If direction is "forward", call collapseToStart() on the context4051 // object's Selection."4052 if (direction == "forward") {4053 if (getSelection().rangeCount) {4054 getSelection().collapseToStart();4055 }4056 getActiveRange().collapse(true);4057 // "Otherwise, call collapseToEnd() on the context object's Selection."4058 } else {4059 getSelection().collapseToEnd();4060 getActiveRange().collapse(false);4061 }4062 // "Restore states and values from overrides."4063 restoreStatesAndValues(overrides);4064 // "Abort these steps."4065 return;4066 }4067 // "If start node is an editable Text node, call deleteData() on it, with4068 // start offset as the first argument and (length of start node − start4069 // offset) as the second argument."4070 if (isEditable(startNode)4071 && startNode.nodeType == Node.TEXT_NODE) {4072 startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);4073 }4074 // "Let node list be a list of nodes, initially empty."4075 //4076 // "For each node contained in the active range, append node to node list4077 // if the last member of node list (if any) is not an ancestor of node;4078 // node is editable; and node is not a thead, tbody, tfoot, tr, th, or td."4079 var nodeList = getContainedNodes(getActiveRange(),4080 function(node) {4081 return isEditable(node)4082 && !isHtmlElement(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);4083 }4084 );4085 // "For each node in node list:"4086 for (var i = 0; i < nodeList.length; i++) {4087 var node = nodeList[i];4088 // "Let parent be the parent of node."4089 var parent_ = node.parentNode;4090 // "Remove node from parent."4091 parent_.removeChild(node);4092 // "If the block node of parent has no visible children, and parent is4093 // editable or an editing host, call createElement("br") on the context4094 // object and append the result as the last child of parent."4095 if (![].some.call(getBlockNodeOf(parent_).childNodes, isVisible)4096 && (isEditable(parent_) || isEditingHost(parent_))) {4097 parent_.appendChild(document.createElement("br"));4098 }4099 // "If strip wrappers is true or parent is not an ancestor container of4100 // start node, while parent is an editable inline node with length 0,4101 // let grandparent be the parent of parent, then remove parent from4102 // grandparent, then set parent to grandparent."4103 if (stripWrappers4104 || (!isAncestor(parent_, startNode) && parent_ != startNode)) {4105 while (isEditable(parent_)4106 && isInlineNode(parent_)4107 && getNodeLength(parent_) == 0) {4108 var grandparent = parent_.parentNode;4109 grandparent.removeChild(parent_);4110 parent_ = grandparent;4111 }4112 }4113 }4114 // "If end node is an editable Text node, call deleteData(0, end offset) on4115 // it."4116 if (isEditable(endNode)4117 && endNode.nodeType == Node.TEXT_NODE) {4118 endNode.deleteData(0, endOffset);4119 }4120 // "Canonicalize whitespace at the active range's start, with fix collapsed4121 // space false."4122 canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset, false);4123 // "Canonicalize whitespace at the active range's end, with fix collapsed4124 // space false."4125 canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset, false);4126 // "If block merging is false, or start block or end block is null, or4127 // start block is not in the same editing host as end block, or start block4128 // and end block are the same:"4129 if (!blockMerging4130 || !startBlock4131 || !endBlock4132 || !inSameEditingHost(startBlock, endBlock)4133 || startBlock == endBlock) {4134 // "If direction is "forward", call collapseToStart() on the context4135 // object's Selection."4136 if (direction == "forward") {4137 if (getSelection().rangeCount) {4138 getSelection().collapseToStart();4139 }4140 getActiveRange().collapse(true);4141 // "Otherwise, call collapseToEnd() on the context object's Selection."4142 } else {4143 if (getSelection().rangeCount) {4144 getSelection().collapseToEnd();4145 }4146 getActiveRange().collapse(false);4147 }4148 // "Restore states and values from overrides."4149 restoreStatesAndValues(overrides);4150 // "Abort these steps."4151 return;4152 }4153 // "If start block has one child, which is a collapsed block prop, remove4154 // its child from it."4155 if (startBlock.children.length == 14156 && isCollapsedBlockProp(startBlock.firstChild)) {4157 startBlock.removeChild(startBlock.firstChild);4158 }4159 // "If start block is an ancestor of end block:"4160 if (isAncestor(startBlock, endBlock)) {4161 // "Let reference node be end block."4162 var referenceNode = endBlock;4163 // "While reference node is not a child of start block, set reference4164 // node to its parent."4165 while (referenceNode.parentNode != startBlock) {4166 referenceNode = referenceNode.parentNode;4167 }4168 // "Call collapse() on the context object's Selection, with first4169 // argument start block and second argument the index of reference4170 // node."4171 getSelection().collapse(startBlock, getNodeIndex(referenceNode));4172 getActiveRange().setStart(startBlock, getNodeIndex(referenceNode));4173 getActiveRange().collapse(true);4174 // "If end block has no children:"4175 if (!endBlock.hasChildNodes()) {4176 // "While end block is editable and is the only child of its parent4177 // and is not a child of start block, let parent equal end block,4178 // then remove end block from parent, then set end block to4179 // parent."4180 while (isEditable(endBlock)4181 && endBlock.parentNode.childNodes.length == 14182 && endBlock.parentNode != startBlock) {4183 var parent_ = endBlock;4184 parent_.removeChild(endBlock);4185 endBlock = parent_;4186 }4187 // "If end block is editable and is not an inline node, and its4188 // previousSibling and nextSibling are both inline nodes, call4189 // createElement("br") on the context object and insert it into end4190 // block's parent immediately after end block."4191 if (isEditable(endBlock)4192 && !isInlineNode(endBlock)4193 && isInlineNode(endBlock.previousSibling)4194 && isInlineNode(endBlock.nextSibling)) {4195 endBlock.parentNode.insertBefore(document.createElement("br"), endBlock.nextSibling);4196 }4197 // "If end block is editable, remove it from its parent."4198 if (isEditable(endBlock)) {4199 endBlock.parentNode.removeChild(endBlock);4200 }4201 // "Restore states and values from overrides."4202 restoreStatesAndValues(overrides);4203 // "Abort these steps."4204 return;4205 }4206 // "If end block's firstChild is not an inline node, restore states and4207 // values from overrides, then abort these steps."4208 if (!isInlineNode(endBlock.firstChild)) {4209 restoreStatesAndValues(overrides);4210 return;4211 }4212 // "Let children be a list of nodes, initially empty."4213 var children = [];4214 // "Append the first child of end block to children."4215 children.push(endBlock.firstChild);4216 // "While children's last member is not a br, and children's last4217 // member's nextSibling is an inline node, append children's last4218 // member's nextSibling to children."4219 while (!isHtmlElement(children[children.length - 1], "br")4220 && isInlineNode(children[children.length - 1].nextSibling)) {4221 children.push(children[children.length - 1].nextSibling);4222 }4223 // "Record the values of children, and let values be the result."4224 var values = recordValues(children);4225 // "While children's first member's parent is not start block, split4226 // the parent of children."4227 while (children[0].parentNode != startBlock) {4228 splitParent(children);4229 }4230 // "If children's first member's previousSibling is an editable br,4231 // remove that br from its parent."4232 if (isEditable(children[0].previousSibling)4233 && isHtmlElement(children[0].previousSibling, "br")) {4234 children[0].parentNode.removeChild(children[0].previousSibling);4235 }4236 // "Otherwise, if start block is a descendant of end block:"4237 } else if (isDescendant(startBlock, endBlock)) {4238 // "Call collapse() on the context object's Selection, with first4239 // argument start block and second argument start block's length."4240 getSelection().collapse(startBlock, getNodeLength(startBlock));4241 getActiveRange().setStart(startBlock, getNodeLength(startBlock));4242 getActiveRange().collapse(true);4243 // "Let reference node be start block."4244 var referenceNode = startBlock;4245 // "While reference node is not a child of end block, set reference4246 // node to its parent."4247 while (referenceNode.parentNode != endBlock) {4248 referenceNode = referenceNode.parentNode;4249 }4250 // "If reference node's nextSibling is an inline node and start block's4251 // lastChild is a br, remove start block's lastChild from it."4252 if (isInlineNode(referenceNode.nextSibling)4253 && isHtmlElement(startBlock.lastChild, "br")) {4254 startBlock.removeChild(startBlock.lastChild);4255 }4256 // "Let nodes to move be a list of nodes, initially empty."4257 var nodesToMove = [];4258 // "If reference node's nextSibling is neither null nor a block node,4259 // append it to nodes to move."4260 if (referenceNode.nextSibling4261 && !isBlockNode(referenceNode.nextSibling)) {4262 nodesToMove.push(referenceNode.nextSibling);4263 }4264 // "While nodes to move is nonempty and its last member isn't a br and4265 // its last member's nextSibling is neither null nor a block node,4266 // append its last member's nextSibling to nodes to move."4267 if (nodesToMove.length4268 && !isHtmlElement(nodesToMove[nodesToMove.length - 1], "br")4269 && nodesToMove[nodesToMove.length - 1].nextSibling4270 && !isBlockNode(nodesToMove[nodesToMove.length - 1].nextSibling)) {4271 nodesToMove.push(nodesToMove[nodesToMove.length - 1].nextSibling);4272 }4273 // "Record the values of nodes to move, and let values be the result."4274 var values = recordValues(nodesToMove);4275 // "For each node in nodes to move, append node as the last child of4276 // start block, preserving ranges."4277 nodesToMove.forEach(function(node) {4278 movePreservingRanges(node, startBlock, -1);4279 });4280 // "Otherwise:"4281 } else {4282 // "Call collapse() on the context object's Selection, with first4283 // argument start block and second argument start block's length."4284 getSelection().collapse(startBlock, getNodeLength(startBlock));4285 getActiveRange().setStart(startBlock, getNodeLength(startBlock));4286 getActiveRange().collapse(true);4287 // "If end block's firstChild is an inline node and start block's4288 // lastChild is a br, remove start block's lastChild from it."4289 if (isInlineNode(endBlock.firstChild)4290 && isHtmlElement(startBlock.lastChild, "br")) {4291 startBlock.removeChild(startBlock.lastChild);4292 }4293 // "Record the values of end block's children, and let values be the4294 // result."4295 var values = recordValues([].slice.call(endBlock.childNodes));4296 // "While end block has children, append the first child of end block4297 // to start block, preserving ranges."4298 while (endBlock.hasChildNodes()) {4299 movePreservingRanges(endBlock.firstChild, startBlock, -1);4300 }4301 // "While end block has no children, let parent be the parent of end4302 // block, then remove end block from parent, then set end block to4303 // parent."4304 while (!endBlock.hasChildNodes()) {4305 var parent_ = endBlock.parentNode;4306 parent_.removeChild(endBlock);4307 endBlock = parent_;4308 }4309 }4310 // "Let ancestor be start block."4311 var ancestor = startBlock;4312 // "While ancestor has an inclusive ancestor ol in the same editing host4313 // whose nextSibling is also an ol in the same editing host, or an4314 // inclusive ancestor ul in the same editing host whose nextSibling is also4315 // a ul in the same editing host:"4316 while (getInclusiveAncestors(ancestor).some(function(node) {4317 return inSameEditingHost(ancestor, node)4318 && (4319 (isHtmlElement(node, "ol") && isHtmlElement(node.nextSibling, "ol"))4320 || (isHtmlElement(node, "ul") && isHtmlElement(node.nextSibling, "ul"))4321 ) && inSameEditingHost(ancestor, node.nextSibling);4322 })) {4323 // "While ancestor and its nextSibling are not both ols in the same4324 // editing host, and are also not both uls in the same editing host,4325 // set ancestor to its parent."4326 while (!(4327 isHtmlElement(ancestor, "ol")4328 && isHtmlElement(ancestor.nextSibling, "ol")4329 && inSameEditingHost(ancestor, ancestor.nextSibling)4330 ) && !(4331 isHtmlElement(ancestor, "ul")4332 && isHtmlElement(ancestor.nextSibling, "ul")4333 && inSameEditingHost(ancestor, ancestor.nextSibling)4334 )) {4335 ancestor = ancestor.parentNode;4336 }4337 // "While ancestor's nextSibling has children, append ancestor's4338 // nextSibling's firstChild as the last child of ancestor, preserving4339 // ranges."4340 while (ancestor.nextSibling.hasChildNodes()) {4341 movePreservingRanges(ancestor.nextSibling.firstChild, ancestor, -1);4342 }4343 // "Remove ancestor's nextSibling from its parent."4344 ancestor.parentNode.removeChild(ancestor.nextSibling);4345 }4346 // "Restore the values from values."4347 restoreValues(values);4348 // "If start block has no children, call createElement("br") on the context4349 // object and append the result as the last child of start block."4350 if (!startBlock.hasChildNodes()) {4351 startBlock.appendChild(document.createElement("br"));4352 }4353 // "Remove extraneous line breaks at the end of start block."4354 removeExtraneousLineBreaksAtTheEndOf(startBlock);4355 // "Restore states and values from overrides."4356 restoreStatesAndValues(overrides);4357}4358//@}4359///// Splitting a node list's parent /////4360//@{4361function splitParent(nodeList) {4362 // "Let original parent be the parent of the first member of node list."4363 var originalParent = nodeList[0].parentNode;4364 // "If original parent is not editable or its parent is null, do nothing4365 // and abort these steps."4366 if (!isEditable(originalParent)4367 || !originalParent.parentNode) {4368 return;4369 }4370 // "If the first child of original parent is in node list, remove4371 // extraneous line breaks before original parent."4372 if (nodeList.indexOf(originalParent.firstChild) != -1) {4373 removeExtraneousLineBreaksBefore(originalParent);4374 }4375 // "If the first child of original parent is in node list, and original4376 // parent follows a line break, set follows line break to true. Otherwise,4377 // set follows line break to false."4378 var followsLineBreak_ = nodeList.indexOf(originalParent.firstChild) != -14379 && followsLineBreak(originalParent);4380 // "If the last child of original parent is in node list, and original4381 // parent precedes a line break, set precedes line break to true.4382 // Otherwise, set precedes line break to false."4383 var precedesLineBreak_ = nodeList.indexOf(originalParent.lastChild) != -14384 && precedesLineBreak(originalParent);4385 // "If the first child of original parent is not in node list, but its last4386 // child is:"4387 if (nodeList.indexOf(originalParent.firstChild) == -14388 && nodeList.indexOf(originalParent.lastChild) != -1) {4389 // "For each node in node list, in reverse order, insert node into the4390 // parent of original parent immediately after original parent,4391 // preserving ranges."4392 for (var i = nodeList.length - 1; i >= 0; i--) {4393 movePreservingRanges(nodeList[i], originalParent.parentNode, 1 + getNodeIndex(originalParent));4394 }4395 // "If precedes line break is true, and the last member of node list4396 // does not precede a line break, call createElement("br") on the4397 // context object and insert the result immediately after the last4398 // member of node list."4399 if (precedesLineBreak_4400 && !precedesLineBreak(nodeList[nodeList.length - 1])) {4401 nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);4402 }4403 // "Remove extraneous line breaks at the end of original parent."4404 removeExtraneousLineBreaksAtTheEndOf(originalParent);4405 // "Abort these steps."4406 return;4407 }4408 // "If the first child of original parent is not in node list:"4409 if (nodeList.indexOf(originalParent.firstChild) == -1) {4410 // "Let cloned parent be the result of calling cloneNode(false) on4411 // original parent."4412 var clonedParent = originalParent.cloneNode(false);4413 // "If original parent has an id attribute, unset it."4414 originalParent.removeAttribute("id");4415 // "Insert cloned parent into the parent of original parent immediately4416 // before original parent."4417 originalParent.parentNode.insertBefore(clonedParent, originalParent);4418 // "While the previousSibling of the first member of node list is not4419 // null, append the first child of original parent as the last child of4420 // cloned parent, preserving ranges."4421 while (nodeList[0].previousSibling) {4422 movePreservingRanges(originalParent.firstChild, clonedParent, clonedParent.childNodes.length);4423 }4424 }4425 // "For each node in node list, insert node into the parent of original4426 // parent immediately before original parent, preserving ranges."4427 for (var i = 0; i < nodeList.length; i++) {4428 movePreservingRanges(nodeList[i], originalParent.parentNode, getNodeIndex(originalParent));4429 }4430 // "If follows line break is true, and the first member of node list does4431 // not follow a line break, call createElement("br") on the context object4432 // and insert the result immediately before the first member of node list."4433 if (followsLineBreak_4434 && !followsLineBreak(nodeList[0])) {4435 nodeList[0].parentNode.insertBefore(document.createElement("br"), nodeList[0]);4436 }4437 // "If the last member of node list is an inline node other than a br, and4438 // the first child of original parent is a br, and original parent is not4439 // an inline node, remove the first child of original parent from original4440 // parent."4441 if (isInlineNode(nodeList[nodeList.length - 1])4442 && !isHtmlElement(nodeList[nodeList.length - 1], "br")4443 && isHtmlElement(originalParent.firstChild, "br")4444 && !isInlineNode(originalParent)) {4445 originalParent.removeChild(originalParent.firstChild);4446 }4447 // "If original parent has no children:"4448 if (!originalParent.hasChildNodes()) {4449 // "Remove original parent from its parent."4450 originalParent.parentNode.removeChild(originalParent);4451 // "If precedes line break is true, and the last member of node list4452 // does not precede a line break, call createElement("br") on the4453 // context object and insert the result immediately after the last4454 // member of node list."4455 if (precedesLineBreak_4456 && !precedesLineBreak(nodeList[nodeList.length - 1])) {4457 nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);4458 }4459 // "Otherwise, remove extraneous line breaks before original parent."4460 } else {4461 removeExtraneousLineBreaksBefore(originalParent);4462 }4463 // "If node list's last member's nextSibling is null, but its parent is not4464 // null, remove extraneous line breaks at the end of node list's last4465 // member's parent."4466 if (!nodeList[nodeList.length - 1].nextSibling4467 && nodeList[nodeList.length - 1].parentNode) {4468 removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);4469 }4470}4471// "To remove a node node while preserving its descendants, split the parent of4472// node's children if it has any. If it has no children, instead remove it from4473// its parent."4474function removePreservingDescendants(node) {4475 if (node.hasChildNodes()) {4476 splitParent([].slice.call(node.childNodes));4477 } else {4478 node.parentNode.removeChild(node);4479 }4480}4481//@}4482///// Canonical space sequences /////4483//@{4484function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {4485 // "If n is zero, return the empty string."4486 if (n == 0) {4487 return "";4488 }4489 // "If n is one and both non-breaking start and non-breaking end are false,4490 // return a single space (U+0020)."4491 if (n == 1 && !nonBreakingStart && !nonBreakingEnd) {4492 return " ";4493 }4494 // "If n is one, return a single non-breaking space (U+00A0)."4495 if (n == 1) {4496 return "\xa0";4497 }4498 // "Let buffer be the empty string."4499 var buffer = "";4500 // "If non-breaking start is true, let repeated pair be U+00A0 U+0020.4501 // Otherwise, let it be U+0020 U+00A0."4502 var repeatedPair;4503 if (nonBreakingStart) {4504 repeatedPair = "\xa0 ";4505 } else {4506 repeatedPair = " \xa0";4507 }4508 // "While n is greater than three, append repeated pair to buffer and4509 // subtract two from n."4510 while (n > 3) {4511 buffer += repeatedPair;4512 n -= 2;4513 }4514 // "If n is three, append a three-element string to buffer depending on4515 // non-breaking start and non-breaking end:"4516 if (n == 3) {4517 buffer +=4518 !nonBreakingStart && !nonBreakingEnd ? " \xa0 "4519 : nonBreakingStart && !nonBreakingEnd ? "\xa0\xa0 "4520 : !nonBreakingStart && nonBreakingEnd ? " \xa0\xa0"4521 : nonBreakingStart && nonBreakingEnd ? "\xa0 \xa0"4522 : "impossible";4523 // "Otherwise, append a two-element string to buffer depending on4524 // non-breaking start and non-breaking end:"4525 } else {4526 buffer +=4527 !nonBreakingStart && !nonBreakingEnd ? "\xa0 "4528 : nonBreakingStart && !nonBreakingEnd ? "\xa0 "4529 : !nonBreakingStart && nonBreakingEnd ? " \xa0"4530 : nonBreakingStart && nonBreakingEnd ? "\xa0\xa0"4531 : "impossible";4532 }4533 // "Return buffer."4534 return buffer;4535}4536function canonicalizeWhitespace(node, offset, fixCollapsedSpace) {4537 if (fixCollapsedSpace === undefined) {4538 // "an optional boolean argument fix collapsed space that defaults to4539 // true"4540 fixCollapsedSpace = true;4541 }4542 // "If node is neither editable nor an editing host, abort these steps."4543 if (!isEditable(node) && !isEditingHost(node)) {4544 return;4545 }4546 // "Let start node equal node and let start offset equal offset."4547 var startNode = node;4548 var startOffset = offset;4549 // "Repeat the following steps:"4550 while (true) {4551 // "If start node has a child in the same editing host with index start4552 // offset minus one, set start node to that child, then set start4553 // offset to start node's length."4554 if (0 <= startOffset - 14555 && inSameEditingHost(startNode, startNode.childNodes[startOffset - 1])) {4556 startNode = startNode.childNodes[startOffset - 1];4557 startOffset = getNodeLength(startNode);4558 // "Otherwise, if start offset is zero and start node does not follow a4559 // line break and start node's parent is in the same editing host, set4560 // start offset to start node's index, then set start node to its4561 // parent."4562 } else if (startOffset == 04563 && !followsLineBreak(startNode)4564 && inSameEditingHost(startNode, startNode.parentNode)) {4565 startOffset = getNodeIndex(startNode);4566 startNode = startNode.parentNode;4567 // "Otherwise, if start node is a Text node and its parent's resolved4568 // value for "white-space" is neither "pre" nor "pre-wrap" and start4569 // offset is not zero and the (start offset − 1)st element of start4570 // node's data is a space (0x0020) or non-breaking space (0x00A0),4571 // subtract one from start offset."4572 } else if (startNode.nodeType == Node.TEXT_NODE4573 && ["pre", "pre-wrap"].indexOf(getComputedStyle(startNode.parentNode).whiteSpace) == -14574 && startOffset != 04575 && /[ \xa0]/.test(startNode.data[startOffset - 1])) {4576 startOffset--;4577 // "Otherwise, break from this loop."4578 } else {4579 break;4580 }4581 }4582 // "Let end node equal start node and end offset equal start offset."4583 var endNode = startNode;4584 var endOffset = startOffset;4585 // "Let length equal zero."4586 var length = 0;4587 // "Let collapse spaces be true if start offset is zero and start node4588 // follows a line break, otherwise false."4589 var collapseSpaces = startOffset == 0 && followsLineBreak(startNode);4590 // "Repeat the following steps:"4591 while (true) {4592 // "If end node has a child in the same editing host with index end4593 // offset, set end node to that child, then set end offset to zero."4594 if (endOffset < endNode.childNodes.length4595 && inSameEditingHost(endNode, endNode.childNodes[endOffset])) {4596 endNode = endNode.childNodes[endOffset];4597 endOffset = 0;4598 // "Otherwise, if end offset is end node's length and end node does not4599 // precede a line break and end node's parent is in the same editing4600 // host, set end offset to one plus end node's index, then set end node4601 // to its parent."4602 } else if (endOffset == getNodeLength(endNode)4603 && !precedesLineBreak(endNode)4604 && inSameEditingHost(endNode, endNode.parentNode)) {4605 endOffset = 1 + getNodeIndex(endNode);4606 endNode = endNode.parentNode;4607 // "Otherwise, if end node is a Text node and its parent's resolved4608 // value for "white-space" is neither "pre" nor "pre-wrap" and end4609 // offset is not end node's length and the end offsetth element of4610 // end node's data is a space (0x0020) or non-breaking space (0x00A0):"4611 } else if (endNode.nodeType == Node.TEXT_NODE4612 && ["pre", "pre-wrap"].indexOf(getComputedStyle(endNode.parentNode).whiteSpace) == -14613 && endOffset != getNodeLength(endNode)4614 && /[ \xa0]/.test(endNode.data[endOffset])) {4615 // "If fix collapsed space is true, and collapse spaces is true,4616 // and the end offsetth code unit of end node's data is a space4617 // (0x0020): call deleteData(end offset, 1) on end node, then4618 // continue this loop from the beginning."4619 if (fixCollapsedSpace4620 && collapseSpaces4621 && " " == endNode.data[endOffset]) {4622 endNode.deleteData(endOffset, 1);4623 continue;4624 }4625 // "Set collapse spaces to true if the end offsetth element of end4626 // node's data is a space (0x0020), false otherwise."4627 collapseSpaces = " " == endNode.data[endOffset];4628 // "Add one to end offset."4629 endOffset++;4630 // "Add one to length."4631 length++;4632 // "Otherwise, break from this loop."4633 } else {4634 break;4635 }4636 }4637 // "If fix collapsed space is true, then while (start node, start offset)4638 // is before (end node, end offset):"4639 if (fixCollapsedSpace) {4640 while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {4641 // "If end node has a child in the same editing host with index end4642 // offset − 1, set end node to that child, then set end offset to end4643 // node's length."4644 if (0 <= endOffset - 14645 && endOffset - 1 < endNode.childNodes.length4646 && inSameEditingHost(endNode, endNode.childNodes[endOffset - 1])) {4647 endNode = endNode.childNodes[endOffset - 1];4648 endOffset = getNodeLength(endNode);4649 // "Otherwise, if end offset is zero and end node's parent is in the4650 // same editing host, set end offset to end node's index, then set end4651 // node to its parent."4652 } else if (endOffset == 04653 && inSameEditingHost(endNode, endNode.parentNode)) {4654 endOffset = getNodeIndex(endNode);4655 endNode = endNode.parentNode;4656 // "Otherwise, if end node is a Text node and its parent's resolved4657 // value for "white-space" is neither "pre" nor "pre-wrap" and end4658 // offset is end node's length and the last code unit of end node's4659 // data is a space (0x0020) and end node precedes a line break:"4660 } else if (endNode.nodeType == Node.TEXT_NODE4661 && ["pre", "pre-wrap"].indexOf(getComputedStyle(endNode.parentNode).whiteSpace) == -14662 && endOffset == getNodeLength(endNode)4663 && endNode.data[endNode.data.length - 1] == " "4664 && precedesLineBreak(endNode)) {4665 // "Subtract one from end offset."4666 endOffset--;4667 // "Subtract one from length."4668 length--;4669 // "Call deleteData(end offset, 1) on end node."4670 endNode.deleteData(endOffset, 1);4671 // "Otherwise, break from this loop."4672 } else {4673 break;4674 }4675 }4676 }4677 // "Let replacement whitespace be the canonical space sequence of length4678 // length. non-breaking start is true if start offset is zero and start4679 // node follows a line break, and false otherwise. non-breaking end is true4680 // if end offset is end node's length and end node precedes a line break,4681 // and false otherwise."4682 var replacementWhitespace = canonicalSpaceSequence(length,4683 startOffset == 0 && followsLineBreak(startNode),4684 endOffset == getNodeLength(endNode) && precedesLineBreak(endNode));4685 // "While (start node, start offset) is before (end node, end offset):"4686 while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {4687 // "If start node has a child with index start offset, set start node4688 // to that child, then set start offset to zero."4689 if (startOffset < startNode.childNodes.length) {4690 startNode = startNode.childNodes[startOffset];4691 startOffset = 0;4692 // "Otherwise, if start node is not a Text node or if start offset is4693 // start node's length, set start offset to one plus start node's4694 // index, then set start node to its parent."4695 } else if (startNode.nodeType != Node.TEXT_NODE4696 || startOffset == getNodeLength(startNode)) {4697 startOffset = 1 + getNodeIndex(startNode);4698 startNode = startNode.parentNode;4699 // "Otherwise:"4700 } else {4701 // "Remove the first element from replacement whitespace, and let4702 // element be that element."4703 var element = replacementWhitespace[0];4704 replacementWhitespace = replacementWhitespace.slice(1);4705 // "If element is not the same as the start offsetth element of4706 // start node's data:"4707 if (element != startNode.data[startOffset]) {4708 // "Call insertData(start offset, element) on start node."4709 startNode.insertData(startOffset, element);4710 // "Call deleteData(start offset + 1, 1) on start node."4711 startNode.deleteData(startOffset + 1, 1);4712 }4713 // "Add one to start offset."4714 startOffset++;4715 }4716 }4717}4718//@}4719///// Indenting and outdenting /////4720//@{4721function indentNodes(nodeList) {4722 // "If node list is empty, do nothing and abort these steps."4723 if (!nodeList.length) {4724 return;4725 }4726 // "Let first node be the first member of node list."4727 var firstNode = nodeList[0];4728 // "If first node's parent is an ol or ul:"4729 if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {4730 // "Let tag be the local name of the parent of first node."4731 var tag = firstNode.parentNode.tagName;4732 // "Wrap node list, with sibling criteria returning true for an HTML4733 // element with local name tag and false otherwise, and new parent4734 // instructions returning the result of calling createElement(tag) on4735 // the ownerDocument of first node."4736 wrap(nodeList,4737 function(node) { return isHtmlElement(node, tag) },4738 function() { return firstNode.ownerDocument.createElement(tag) });4739 // "Abort these steps."4740 return;4741 }4742 // "Wrap node list, with sibling criteria returning true for a simple4743 // indentation element and false otherwise, and new parent instructions4744 // returning the result of calling createElement("blockquote") on the4745 // ownerDocument of first node. Let new parent be the result."4746 var newParent = wrap(nodeList,4747 function(node) { return isSimpleIndentationElement(node) },4748 function() { return firstNode.ownerDocument.createElement("blockquote") });4749 // "Fix disallowed ancestors of new parent."4750 fixDisallowedAncestors(newParent);4751}4752function outdentNode(node) {4753 // "If node is not editable, abort these steps."4754 if (!isEditable(node)) {4755 return;4756 }4757 // "If node is a simple indentation element, remove node, preserving its4758 // descendants. Then abort these steps."4759 if (isSimpleIndentationElement(node)) {4760 removePreservingDescendants(node);4761 return;4762 }4763 // "If node is an indentation element:"4764 if (isIndentationElement(node)) {4765 // "Unset the dir attribute of node, if any."4766 node.removeAttribute("dir");4767 // "Unset the margin, padding, and border CSS properties of node."4768 node.style.margin = "";4769 node.style.padding = "";4770 node.style.border = "";4771 if (node.getAttribute("style") == ""4772 // Crazy WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=685514773 || node.getAttribute("style") == "border-width: initial; border-color: initial; ") {4774 node.removeAttribute("style");4775 }4776 // "Set the tag name of node to "div"."4777 setTagName(node, "div");4778 // "Abort these steps."4779 return;4780 }4781 // "Let current ancestor be node's parent."4782 var currentAncestor = node.parentNode;4783 // "Let ancestor list be a list of nodes, initially empty."4784 var ancestorList = [];4785 // "While current ancestor is an editable Element that is neither a simple4786 // indentation element nor an ol nor a ul, append current ancestor to4787 // ancestor list and then set current ancestor to its parent."4788 while (isEditable(currentAncestor)4789 && currentAncestor.nodeType == Node.ELEMENT_NODE4790 && !isSimpleIndentationElement(currentAncestor)4791 && !isHtmlElement(currentAncestor, ["ol", "ul"])) {4792 ancestorList.push(currentAncestor);4793 currentAncestor = currentAncestor.parentNode;4794 }4795 // "If current ancestor is not an editable simple indentation element:"4796 if (!isEditable(currentAncestor)4797 || !isSimpleIndentationElement(currentAncestor)) {4798 // "Let current ancestor be node's parent."4799 currentAncestor = node.parentNode;4800 // "Let ancestor list be the empty list."4801 ancestorList = [];4802 // "While current ancestor is an editable Element that is neither an4803 // indentation element nor an ol nor a ul, append current ancestor to4804 // ancestor list and then set current ancestor to its parent."4805 while (isEditable(currentAncestor)4806 && currentAncestor.nodeType == Node.ELEMENT_NODE4807 && !isIndentationElement(currentAncestor)4808 && !isHtmlElement(currentAncestor, ["ol", "ul"])) {4809 ancestorList.push(currentAncestor);4810 currentAncestor = currentAncestor.parentNode;4811 }4812 }4813 // "If node is an ol or ul and current ancestor is not an editable4814 // indentation element:"4815 if (isHtmlElement(node, ["OL", "UL"])4816 && (!isEditable(currentAncestor)4817 || !isIndentationElement(currentAncestor))) {4818 // "Unset the reversed, start, and type attributes of node, if any are4819 // set."4820 node.removeAttribute("reversed");4821 node.removeAttribute("start");4822 node.removeAttribute("type");4823 // "Let children be the children of node."4824 var children = [].slice.call(node.childNodes);4825 // "If node has attributes, and its parent is not an ol or ul, set the4826 // tag name of node to "div"."4827 if (node.attributes.length4828 && !isHtmlElement(node.parentNode, ["OL", "UL"])) {4829 setTagName(node, "div");4830 // "Otherwise:"4831 } else {4832 // "Record the values of node's children, and let values be the4833 // result."4834 var values = recordValues([].slice.call(node.childNodes));4835 // "Remove node, preserving its descendants."4836 removePreservingDescendants(node);4837 // "Restore the values from values."4838 restoreValues(values);4839 }4840 // "Fix disallowed ancestors of each member of children."4841 for (var i = 0; i < children.length; i++) {4842 fixDisallowedAncestors(children[i]);4843 }4844 // "Abort these steps."4845 return;4846 }4847 // "If current ancestor is not an editable indentation element, abort these4848 // steps."4849 if (!isEditable(currentAncestor)4850 || !isIndentationElement(currentAncestor)) {4851 return;4852 }4853 // "Append current ancestor to ancestor list."4854 ancestorList.push(currentAncestor);4855 // "Let original ancestor be current ancestor."4856 var originalAncestor = currentAncestor;4857 // "While ancestor list is not empty:"4858 while (ancestorList.length) {4859 // "Let current ancestor be the last member of ancestor list."4860 //4861 // "Remove the last member of ancestor list."4862 currentAncestor = ancestorList.pop();4863 // "Let target be the child of current ancestor that is equal to either4864 // node or the last member of ancestor list."4865 var target = node.parentNode == currentAncestor4866 ? node4867 : ancestorList[ancestorList.length - 1];4868 // "If target is an inline node that is not a br, and its nextSibling4869 // is a br, remove target's nextSibling from its parent."4870 if (isInlineNode(target)4871 && !isHtmlElement(target, "BR")4872 && isHtmlElement(target.nextSibling, "BR")) {4873 target.parentNode.removeChild(target.nextSibling);4874 }4875 // "Let preceding siblings be the preceding siblings of target, and let4876 // following siblings be the following siblings of target."4877 var precedingSiblings = [].slice.call(currentAncestor.childNodes, 0, getNodeIndex(target));4878 var followingSiblings = [].slice.call(currentAncestor.childNodes, 1 + getNodeIndex(target));4879 // "Indent preceding siblings."4880 indentNodes(precedingSiblings);4881 // "Indent following siblings."4882 indentNodes(followingSiblings);4883 }4884 // "Outdent original ancestor."4885 outdentNode(originalAncestor);4886}4887//@}4888///// Toggling lists /////4889//@{4890function toggleLists(tagName) {4891 // "Let mode be "disable" if the selection's list state is tag name, and4892 // "enable" otherwise."4893 var mode = getSelectionListState() == tagName ? "disable" : "enable";4894 var range = getActiveRange();4895 tagName = tagName.toUpperCase();4896 // "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is4897 // "ol"."4898 var otherTagName = tagName == "OL" ? "UL" : "OL";4899 // "Let items be a list of all lis that are ancestor containers of the4900 // range's start and/or end node."4901 //4902 // It's annoying to get this in tree order using functional stuff without4903 // doing getDescendants(document), which is slow, so I do it imperatively.4904 var items = [];4905 (function(){4906 for (4907 var ancestorContainer = range.endContainer;4908 ancestorContainer != range.commonAncestorContainer;4909 ancestorContainer = ancestorContainer.parentNode4910 ) {4911 if (isHtmlElement(ancestorContainer, "li")) {4912 items.unshift(ancestorContainer);4913 }4914 }4915 for (4916 var ancestorContainer = range.startContainer;4917 ancestorContainer;4918 ancestorContainer = ancestorContainer.parentNode4919 ) {4920 if (isHtmlElement(ancestorContainer, "li")) {4921 items.unshift(ancestorContainer);4922 }4923 }4924 })();4925 // "For each item in items, normalize sublists of item."4926 items.forEach(normalizeSublists);4927 // "Block-extend the range, and let new range be the result."4928 var newRange = blockExtend(range);4929 // "If mode is "enable", then let lists to convert consist of every4930 // editable HTML element with local name other tag name that is contained4931 // in new range, and for every list in lists to convert:"4932 if (mode == "enable") {4933 getAllContainedNodes(newRange, function(node) {4934 return isEditable(node)4935 && isHtmlElement(node, otherTagName);4936 }).forEach(function(list) {4937 // "If list's previousSibling or nextSibling is an editable HTML4938 // element with local name tag name:"4939 if ((isEditable(list.previousSibling) && isHtmlElement(list.previousSibling, tagName))4940 || (isEditable(list.nextSibling) && isHtmlElement(list.nextSibling, tagName))) {4941 // "Let children be list's children."4942 var children = [].slice.call(list.childNodes);4943 // "Record the values of children, and let values be the4944 // result."4945 var values = recordValues(children);4946 // "Split the parent of children."4947 splitParent(children);4948 // "Wrap children, with sibling criteria returning true for an4949 // HTML element with local name tag name and false otherwise."4950 wrap(children, function(node) { return isHtmlElement(node, tagName) });4951 // "Restore the values from values."4952 restoreValues(values);4953 // "Otherwise, set the tag name of list to tag name."4954 } else {4955 setTagName(list, tagName);4956 }4957 });4958 }4959 // "Let node list be a list of nodes, initially empty."4960 //4961 // "For each node node contained in new range, if node is editable; the4962 // last member of node list (if any) is not an ancestor of node; node4963 // is not an indentation element; and either node is an ol or ul, or its4964 // parent is an ol or ul, or it is an allowed child of "li"; then append4965 // node to node list."4966 var nodeList = getContainedNodes(newRange, function(node) {4967 return isEditable(node)4968 && !isIndentationElement(node)4969 && (isHtmlElement(node, ["OL", "UL"])4970 || isHtmlElement(node.parentNode, ["OL", "UL"])4971 || isAllowedChild(node, "li"));4972 });4973 // "If mode is "enable", remove from node list any ol or ul whose parent is4974 // not also an ol or ul."4975 if (mode == "enable") {4976 nodeList = nodeList.filter(function(node) {4977 return !isHtmlElement(node, ["ol", "ul"])4978 || isHtmlElement(node.parentNode, ["ol", "ul"]);4979 });4980 }4981 // "If mode is "disable", then while node list is not empty:"4982 if (mode == "disable") {4983 while (nodeList.length) {4984 // "Let sublist be an empty list of nodes."4985 var sublist = [];4986 // "Remove the first member from node list and append it to4987 // sublist."4988 sublist.push(nodeList.shift());4989 // "If the first member of sublist is an HTML element with local4990 // name tag name, outdent it and continue this loop from the4991 // beginning."4992 if (isHtmlElement(sublist[0], tagName)) {4993 outdentNode(sublist[0]);4994 continue;4995 }4996 // "While node list is not empty, and the first member of node list4997 // is the nextSibling of the last member of sublist and is not an4998 // HTML element with local name tag name, remove the first member4999 // from node list and append it to sublist."5000 while (nodeList.length5001 && nodeList[0] == sublist[sublist.length - 1].nextSibling5002 && !isHtmlElement(nodeList[0], tagName)) {5003 sublist.push(nodeList.shift());5004 }5005 // "Record the values of sublist, and let values be the result."5006 var values = recordValues(sublist);5007 // "Split the parent of sublist."5008 splitParent(sublist);5009 // "Fix disallowed ancestors of each member of sublist."5010 for (var i = 0; i < sublist.length; i++) {5011 fixDisallowedAncestors(sublist[i]);5012 }5013 // "Restore the values from values."5014 restoreValues(values);5015 }5016 // "Otherwise, while node list is not empty:"5017 } else {5018 while (nodeList.length) {5019 // "Let sublist be an empty list of nodes."5020 var sublist = [];5021 // "While either sublist is empty, or node list is not empty and5022 // its first member is the nextSibling of sublist's last member:"5023 while (!sublist.length5024 || (nodeList.length5025 && nodeList[0] == sublist[sublist.length - 1].nextSibling)) {5026 // "If node list's first member is a p or div, set the tag name5027 // of node list's first member to "li", and append the result5028 // to sublist. Remove the first member from node list."5029 if (isHtmlElement(nodeList[0], ["p", "div"])) {5030 sublist.push(setTagName(nodeList[0], "li"));5031 nodeList.shift();5032 // "Otherwise, if the first member of node list is an li or ol5033 // or ul, remove it from node list and append it to sublist."5034 } else if (isHtmlElement(nodeList[0], ["li", "ol", "ul"])) {5035 sublist.push(nodeList.shift());5036 // "Otherwise:"5037 } else {5038 // "Let nodes to wrap be a list of nodes, initially empty."5039 var nodesToWrap = [];5040 // "While nodes to wrap is empty, or node list is not empty5041 // and its first member is the nextSibling of nodes to5042 // wrap's last member and the first member of node list is5043 // an inline node and the last member of nodes to wrap is5044 // an inline node other than a br, remove the first member5045 // from node list and append it to nodes to wrap."5046 while (!nodesToWrap.length5047 || (nodeList.length5048 && nodeList[0] == nodesToWrap[nodesToWrap.length - 1].nextSibling5049 && isInlineNode(nodeList[0])5050 && isInlineNode(nodesToWrap[nodesToWrap.length - 1])5051 && !isHtmlElement(nodesToWrap[nodesToWrap.length - 1], "br"))) {5052 nodesToWrap.push(nodeList.shift());5053 }5054 // "Wrap nodes to wrap, with new parent instructions5055 // returning the result of calling createElement("li") on5056 // the context object. Append the result to sublist."5057 sublist.push(wrap(nodesToWrap,5058 undefined,5059 function() { return document.createElement("li") }));5060 }5061 }5062 // "If sublist's first member's parent is an HTML element with5063 // local name tag name, or if every member of sublist is an ol or5064 // ul, continue this loop from the beginning."5065 if (isHtmlElement(sublist[0].parentNode, tagName)5066 || sublist.every(function(node) { return isHtmlElement(node, ["ol", "ul"]) })) {5067 continue;5068 }5069 // "If sublist's first member's parent is an HTML element with5070 // local name other tag name:"5071 if (isHtmlElement(sublist[0].parentNode, otherTagName)) {5072 // "Record the values of sublist, and let values be the5073 // result."5074 var values = recordValues(sublist);5075 // "Split the parent of sublist."5076 splitParent(sublist);5077 // "Wrap sublist, with sibling criteria returning true for an5078 // HTML element with local name tag name and false otherwise,5079 // and new parent instructions returning the result of calling5080 // createElement(tag name) on the context object."5081 wrap(sublist,5082 function(node) { return isHtmlElement(node, tagName) },5083 function() { return document.createElement(tagName) });5084 // "Restore the values from values."5085 restoreValues(values);5086 // "Continue this loop from the beginning."5087 continue;5088 }5089 // "Wrap sublist, with sibling criteria returning true for an HTML5090 // element with local name tag name and false otherwise, and new5091 // parent instructions being the following:"5092 // . . .5093 // "Fix disallowed ancestors of the previous step's result."5094 fixDisallowedAncestors(wrap(sublist,5095 function(node) { return isHtmlElement(node, tagName) },5096 function() {5097 // "If sublist's first member's parent is not an editable5098 // simple indentation element, or sublist's first member's5099 // parent's previousSibling is not an editable HTML element5100 // with local name tag name, call createElement(tag name)5101 // on the context object and return the result."5102 if (!isEditable(sublist[0].parentNode)5103 || !isSimpleIndentationElement(sublist[0].parentNode)5104 || !isEditable(sublist[0].parentNode.previousSibling)5105 || !isHtmlElement(sublist[0].parentNode.previousSibling, tagName)) {5106 return document.createElement(tagName);5107 }5108 // "Let list be sublist's first member's parent's5109 // previousSibling."5110 var list = sublist[0].parentNode.previousSibling;5111 // "Normalize sublists of list's lastChild."5112 normalizeSublists(list.lastChild);5113 // "If list's lastChild is not an editable HTML element5114 // with local name tag name, call createElement(tag name)5115 // on the context object, and append the result as the last5116 // child of list."5117 if (!isEditable(list.lastChild)5118 || !isHtmlElement(list.lastChild, tagName)) {5119 list.appendChild(document.createElement(tagName));5120 }5121 // "Return the last child of list."5122 return list.lastChild;5123 }5124 ));5125 }5126 }5127}5128//@}5129///// Justifying the selection /////5130//@{5131function justifySelection(alignment) {5132 // "Block-extend the active range, and let new range be the result."5133 var newRange = blockExtend(globalRange);5134 // "Let element list be a list of all editable Elements contained in new5135 // range that either has an attribute in the HTML namespace whose local5136 // name is "align", or has a style attribute that sets "text-align", or is5137 // a center."5138 var elementList = getAllContainedNodes(newRange, function(node) {5139 return node.nodeType == Node.ELEMENT_NODE5140 && isEditable(node)5141 // Ignoring namespaces here5142 && (5143 node.hasAttribute("align")5144 || node.style.textAlign != ""5145 || isHtmlElement(node, "center")5146 );5147 });5148 // "For each element in element list:"5149 for (var i = 0; i < elementList.length; i++) {5150 var element = elementList[i];5151 // "If element has an attribute in the HTML namespace whose local name5152 // is "align", remove that attribute."5153 element.removeAttribute("align");5154 // "Unset the CSS property "text-align" on element, if it's set by a5155 // style attribute."5156 element.style.textAlign = "";5157 if (element.getAttribute("style") == "") {5158 element.removeAttribute("style");5159 }5160 // "If element is a div or span or center with no attributes, remove5161 // it, preserving its descendants."5162 if (isHtmlElement(element, ["div", "span", "center"])5163 && !element.attributes.length) {5164 removePreservingDescendants(element);5165 }5166 // "If element is a center with one or more attributes, set the tag5167 // name of element to "div"."5168 if (isHtmlElement(element, "center")5169 && element.attributes.length) {5170 setTagName(element, "div");5171 }5172 }5173 // "Block-extend the active range, and let new range be the result."5174 newRange = blockExtend(globalRange);5175 // "Let node list be a list of nodes, initially empty."5176 var nodeList = [];5177 // "For each node node contained in new range, append node to node list if5178 // the last member of node list (if any) is not an ancestor of node; node5179 // is editable; node is an allowed child of "div"; and node's alignment5180 // value is not alignment."5181 nodeList = getContainedNodes(newRange, function(node) {5182 return isEditable(node)5183 && isAllowedChild(node, "div")5184 && getAlignmentValue(node) != alignment;5185 });5186 // "While node list is not empty:"5187 while (nodeList.length) {5188 // "Let sublist be a list of nodes, initially empty."5189 var sublist = [];5190 // "Remove the first member of node list and append it to sublist."5191 sublist.push(nodeList.shift());5192 // "While node list is not empty, and the first member of node list is5193 // the nextSibling of the last member of sublist, remove the first5194 // member of node list and append it to sublist."5195 while (nodeList.length5196 && nodeList[0] == sublist[sublist.length - 1].nextSibling) {5197 sublist.push(nodeList.shift());5198 }5199 // "Wrap sublist. Sibling criteria returns true for any div that has5200 // one or both of the following two attributes and no other attributes,5201 // and false otherwise:"5202 //5203 // * "An align attribute whose value is an ASCII case-insensitive5204 // match for alignment.5205 // * "A style attribute which sets exactly one CSS property5206 // (including unrecognized or invalid attributes), which is5207 // "text-align", which is set to alignment.5208 //5209 // "New parent instructions are to call createElement("div") on the5210 // context object, then set its CSS property "text-align" to alignment5211 // and return the result."5212 wrap(sublist,5213 function(node) {5214 return isHtmlElement(node, "div")5215 && [].every.call(node.attributes, function(attr) {5216 return (attr.name == "align" && attr.value.toLowerCase() == alignment)5217 || (attr.name == "style" && node.style.length == 1 && node.style.textAlign == alignment);5218 });5219 },5220 function() {5221 var newParent = document.createElement("div");5222 newParent.setAttribute("style", "text-align: " + alignment);5223 return newParent;5224 }5225 );5226 }5227}5228//@}5229///// Automatic linking /////5230//@{5231// "An autolinkable URL is a string of the following form:"5232var autolinkableUrlRegexp =5233 // "Either a string matching the scheme pattern from RFC 3986 section 3.15234 // followed by the literal string ://, or the literal string mailto:;5235 // followed by"5236 //5237 // From the RFC: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )5238 "([a-zA-Z][a-zA-Z0-9+.-]*://|mailto:)"5239 // "Zero or more characters other than space characters; followed by"5240 + "[^ \t\n\f\r]*"5241 // "A character that is not one of the ASCII characters !"'(),-.:;<>[]`{}."5242 + "[^!\"'(),\\-.:;<>[\\]`{}]";5243// "A valid e-mail address is a string that matches the ABNF production 1*(5244// atext / "." ) "@" ldh-str *( "." ldh-str ) where atext is defined in RFC5245// 5322 section 3.2.3, and ldh-str is defined in RFC 1034 section 3.5."5246//5247// atext: ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" /5248// "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"5249//5250//<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>5251//<let-dig-hyp> ::= <let-dig> | "-"5252//<let-dig> ::= <letter> | <digit>5253var validEmailRegexp =5254 "[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~.]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*";5255function autolink(node, endOffset) {5256 // "While (node, end offset)'s previous equivalent point is not null, set5257 // it to its previous equivalent point."5258 while (getPreviousEquivalentPoint(node, endOffset)) {5259 var prev = getPreviousEquivalentPoint(node, endOffset);5260 node = prev[0];5261 endOffset = prev[1];5262 }5263 // "If node is not a Text node, or has an a ancestor, do nothing and abort5264 // these steps."5265 if (node.nodeType != Node.TEXT_NODE5266 || getAncestors(node).some(function(ancestor) { return isHtmlElement(ancestor, "a") })) {5267 return;5268 }5269 // "Let search be the largest substring of node's data whose end is end5270 // offset and that contains no space characters."5271 var search = /[^ \t\n\f\r]*$/.exec(node.substringData(0, endOffset))[0];5272 // "If some substring of search is an autolinkable URL:"5273 if (new RegExp(autolinkableUrlRegexp).test(search)) {5274 // "While there is no substring of node's data ending at end offset5275 // that is an autolinkable URL, decrement end offset."5276 while (!(new RegExp(autolinkableUrlRegexp + "$").test(node.substringData(0, endOffset)))) {5277 endOffset--;5278 }5279 // "Let start offset be the start index of the longest substring of5280 // node's data that is an autolinkable URL ending at end offset."5281 var startOffset = new RegExp(autolinkableUrlRegexp + "$").exec(node.substringData(0, endOffset)).index;5282 // "Let href be the substring of node's data starting at start offset5283 // and ending at end offset."5284 var href = node.substringData(startOffset, endOffset - startOffset);5285 // "Otherwise, if some substring of search is a valid e-mail address:"5286 } else if (new RegExp(validEmailRegexp).test(search)) {5287 // "While there is no substring of node's data ending at end offset5288 // that is a valid e-mail address, decrement end offset."5289 while (!(new RegExp(validEmailRegexp + "$").test(node.substringData(0, endOffset)))) {5290 endOffset--;5291 }5292 // "Let start offset be the start index of the longest substring of5293 // node's data that is a valid e-mail address ending at end offset."5294 var startOffset = new RegExp(validEmailRegexp + "$").exec(node.substringData(0, endOffset)).index;5295 // "Let href be "mailto:" concatenated with the substring of node's5296 // data starting at start offset and ending at end offset."5297 var href = "mailto:" + node.substringData(startOffset, endOffset - startOffset);5298 // "Otherwise, do nothing and abort these steps."5299 } else {5300 return;5301 }5302 // "Let original range be the active range."5303 var originalRange = getActiveRange();5304 // "Create a new range with start (node, start offset) and end (node, end5305 // offset), and set the context object's selection's range to it."5306 var newRange = document.createRange();5307 newRange.setStart(node, startOffset);5308 newRange.setEnd(node, endOffset);5309 getSelection().removeAllRanges();5310 getSelection().addRange(newRange);5311 globalRange = newRange;5312 // "Take the action for "createLink", with value equal to href."5313 commands.createlink.action(href);5314 // "Set the context object's selection's range to original range."5315 getSelection().removeAllRanges();5316 getSelection().addRange(originalRange);5317 globalRange = originalRange;5318}5319//@}5320///// The delete command /////5321//@{5322commands["delete"] = {5323 preservesOverrides: true,5324 action: function() {5325 // "If the active range is not collapsed, delete the selection and5326 // return true."5327 if (!getActiveRange().collapsed) {5328 deleteSelection();5329 return true;5330 }5331 // "Canonicalize whitespace at the active range's start."5332 canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);5333 // "Let node and offset be the active range's start node and offset."5334 var node = getActiveRange().startContainer;5335 var offset = getActiveRange().startOffset;5336 // "Repeat the following steps:"5337 while (true) {5338 // "If offset is zero and node's previousSibling is an editable5339 // invisible node, remove node's previousSibling from its parent."5340 if (offset == 05341 && isEditable(node.previousSibling)5342 && isInvisible(node.previousSibling)) {5343 node.parentNode.removeChild(node.previousSibling);5344 // "Otherwise, if node has a child with index offset − 1 and that5345 // child is an editable invisible node, remove that child from5346 // node, then subtract one from offset."5347 } else if (0 <= offset - 15348 && offset - 1 < node.childNodes.length5349 && isEditable(node.childNodes[offset - 1])5350 && isInvisible(node.childNodes[offset - 1])) {5351 node.removeChild(node.childNodes[offset - 1]);5352 offset--;5353 // "Otherwise, if offset is zero and node is an inline node, or if5354 // node is an invisible node, set offset to the index of node, then5355 // set node to its parent."5356 } else if ((offset == 05357 && isInlineNode(node))5358 || isInvisible(node)) {5359 offset = getNodeIndex(node);5360 node = node.parentNode;5361 // "Otherwise, if node has a child with index offset − 1 and that5362 // child is an editable a, remove that child from node, preserving5363 // its descendants. Then return true."5364 } else if (0 <= offset - 15365 && offset - 1 < node.childNodes.length5366 && isEditable(node.childNodes[offset - 1])5367 && isHtmlElement(node.childNodes[offset - 1], "a")) {5368 removePreservingDescendants(node.childNodes[offset - 1]);5369 return true;5370 // "Otherwise, if node has a child with index offset − 1 and that5371 // child is not a block node or a br or an img, set node to that5372 // child, then set offset to the length of node."5373 } else if (0 <= offset - 15374 && offset - 1 < node.childNodes.length5375 && !isBlockNode(node.childNodes[offset - 1])5376 && !isHtmlElement(node.childNodes[offset - 1], ["br", "img"])) {5377 node = node.childNodes[offset - 1];5378 offset = getNodeLength(node);5379 // "Otherwise, break from this loop."5380 } else {5381 break;5382 }5383 }5384 // "If node is a Text node and offset is not zero, or if node is a5385 // block node that has a child with index offset − 1 and that child is5386 // a br or hr or img:"5387 if ((node.nodeType == Node.TEXT_NODE5388 && offset != 0)5389 || (isBlockNode(node)5390 && 0 <= offset - 15391 && offset - 1 < node.childNodes.length5392 && isHtmlElement(node.childNodes[offset - 1], ["br", "hr", "img"]))) {5393 // "Call collapse(node, offset) on the context object's Selection."5394 getSelection().collapse(node, offset);5395 getActiveRange().setEnd(node, offset);5396 // "Call extend(node, offset − 1) on the context object's5397 // Selection."5398 getSelection().extend(node, offset - 1);5399 getActiveRange().setStart(node, offset - 1);5400 // "Delete the selection."5401 deleteSelection();5402 // "Return true."5403 return true;5404 }5405 // "If node is an inline node, return true."5406 if (isInlineNode(node)) {5407 return true;5408 }5409 // "If node is an li or dt or dd and is the first child of its parent,5410 // and offset is zero:"5411 if (isHtmlElement(node, ["li", "dt", "dd"])5412 && node == node.parentNode.firstChild5413 && offset == 0) {5414 // "Let items be a list of all lis that are ancestors of node."5415 //5416 // Remember, must be in tree order.5417 var items = [];5418 for (var ancestor = node.parentNode; ancestor; ancestor = ancestor.parentNode) {5419 if (isHtmlElement(ancestor, "li")) {5420 items.unshift(ancestor);5421 }5422 }5423 // "Normalize sublists of each item in items."5424 for (var i = 0; i < items.length; i++) {5425 normalizeSublists(items[i]);5426 }5427 // "Record the values of the one-node list consisting of node, and5428 // let values be the result."5429 var values = recordValues([node]);5430 // "Split the parent of the one-node list consisting of node."5431 splitParent([node]);5432 // "Restore the values from values."5433 restoreValues(values);5434 // "If node is a dd or dt, and it is not an allowed child of any of5435 // its ancestors in the same editing host, set the tag name of node5436 // to the default single-line container name and let node be the5437 // result."5438 if (isHtmlElement(node, ["dd", "dt"])5439 && getAncestors(node).every(function(ancestor) {5440 return !inSameEditingHost(node, ancestor)5441 || !isAllowedChild(node, ancestor)5442 })) {5443 node = setTagName(node, defaultSingleLineContainerName);5444 }5445 // "Fix disallowed ancestors of node."5446 fixDisallowedAncestors(node);5447 // "Return true."5448 return true;5449 }5450 // "Let start node equal node and let start offset equal offset."5451 var startNode = node;5452 var startOffset = offset;5453 // "Repeat the following steps:"5454 while (true) {5455 // "If start offset is zero, set start offset to the index of start5456 // node and then set start node to its parent."5457 if (startOffset == 0) {5458 startOffset = getNodeIndex(startNode);5459 startNode = startNode.parentNode;5460 // "Otherwise, if start node has an editable invisible child with5461 // index start offset minus one, remove it from start node and5462 // subtract one from start offset."5463 } else if (0 <= startOffset - 15464 && startOffset - 1 < startNode.childNodes.length5465 && isEditable(startNode.childNodes[startOffset - 1])5466 && isInvisible(startNode.childNodes[startOffset - 1])) {5467 startNode.removeChild(startNode.childNodes[startOffset - 1]);5468 startOffset--;5469 // "Otherwise, break from this loop."5470 } else {5471 break;5472 }5473 }5474 // "If offset is zero, and node has an editable ancestor container in5475 // the same editing host that's an indentation element:"5476 if (offset == 05477 && getAncestors(node).concat(node).filter(function(ancestor) {5478 return isEditable(ancestor)5479 && inSameEditingHost(ancestor, node)5480 && isIndentationElement(ancestor);5481 }).length) {5482 // "Block-extend the range whose start and end are both (node, 0),5483 // and let new range be the result."5484 var newRange = document.createRange();5485 newRange.setStart(node, 0);5486 newRange = blockExtend(newRange);5487 // "Let node list be a list of nodes, initially empty."5488 //5489 // "For each node current node contained in new range, append5490 // current node to node list if the last member of node list (if5491 // any) is not an ancestor of current node, and current node is5492 // editable but has no editable descendants."5493 var nodeList = getContainedNodes(newRange, function(currentNode) {5494 return isEditable(currentNode)5495 && !hasEditableDescendants(currentNode);5496 });5497 // "Outdent each node in node list."5498 for (var i = 0; i < nodeList.length; i++) {5499 outdentNode(nodeList[i]);5500 }5501 // "Return true."5502 return true;5503 }5504 // "If the child of start node with index start offset is a table,5505 // return true."5506 if (isHtmlElement(startNode.childNodes[startOffset], "table")) {5507 return true;5508 }5509 // "If start node has a child with index start offset − 1, and that5510 // child is a table:"5511 if (0 <= startOffset - 15512 && startOffset - 1 < startNode.childNodes.length5513 && isHtmlElement(startNode.childNodes[startOffset - 1], "table")) {5514 // "Call collapse(start node, start offset − 1) on the context5515 // object's Selection."5516 getSelection().collapse(startNode, startOffset - 1);5517 getActiveRange().setStart(startNode, startOffset - 1);5518 // "Call extend(start node, start offset) on the context object's5519 // Selection."5520 getSelection().extend(startNode, startOffset);5521 getActiveRange().setEnd(startNode, startOffset);5522 // "Return true."5523 return true;5524 }5525 // "If offset is zero; and either the child of start node with index5526 // start offset minus one is an hr, or the child is a br whose5527 // previousSibling is either a br or not an inline node:"5528 if (offset == 05529 && (isHtmlElement(startNode.childNodes[startOffset - 1], "hr")5530 || (5531 isHtmlElement(startNode.childNodes[startOffset - 1], "br")5532 && (5533 isHtmlElement(startNode.childNodes[startOffset - 1].previousSibling, "br")5534 || !isInlineNode(startNode.childNodes[startOffset - 1].previousSibling)5535 )5536 )5537 )) {5538 // "Call collapse(start node, start offset − 1) on the context5539 // object's Selection."5540 getSelection().collapse(startNode, startOffset - 1);5541 getActiveRange().setStart(startNode, startOffset - 1);5542 // "Call extend(start node, start offset) on the context object's5543 // Selection."5544 getSelection().extend(startNode, startOffset);5545 getActiveRange().setEnd(startNode, startOffset);5546 // "Delete the selection."5547 deleteSelection();5548 // "Call collapse(node, offset) on the Selection."5549 getSelection().collapse(node, offset);5550 getActiveRange().setStart(node, offset);5551 getActiveRange().collapse(true);5552 // "Return true."5553 return true;5554 }5555 // "If the child of start node with index start offset is an li or dt5556 // or dd, and that child's firstChild is an inline node, and start5557 // offset is not zero:"5558 if (isHtmlElement(startNode.childNodes[startOffset], ["li", "dt", "dd"])5559 && isInlineNode(startNode.childNodes[startOffset].firstChild)5560 && startOffset != 0) {5561 // "Let previous item be the child of start node with index start5562 // offset minus one."5563 var previousItem = startNode.childNodes[startOffset - 1];5564 // "If previous item's lastChild is an inline node other than a br,5565 // call createElement("br") on the context object and append the5566 // result as the last child of previous item."5567 if (isInlineNode(previousItem.lastChild)5568 && !isHtmlElement(previousItem.lastChild, "br")) {5569 previousItem.appendChild(document.createElement("br"));5570 }5571 // "If previous item's lastChild is an inline node, call5572 // createElement("br") on the context object and append the result5573 // as the last child of previous item."5574 if (isInlineNode(previousItem.lastChild)) {5575 previousItem.appendChild(document.createElement("br"));5576 }5577 }5578 // "If start node's child with index start offset is an li or dt or dd,5579 // and that child's previousSibling is also an li or dt or dd:"5580 if (isHtmlElement(startNode.childNodes[startOffset], ["li", "dt", "dd"])5581 && isHtmlElement(startNode.childNodes[startOffset].previousSibling, ["li", "dt", "dd"])) {5582 // "Call cloneRange() on the active range, and let original range5583 // be the result."5584 //5585 // We need to add it to extraRanges so it will actually get updated5586 // when moving preserving ranges.5587 var originalRange = getActiveRange().cloneRange();5588 extraRanges.push(originalRange);5589 // "Set start node to its child with index start offset − 1."5590 startNode = startNode.childNodes[startOffset - 1];5591 // "Set start offset to start node's length."5592 startOffset = getNodeLength(startNode);5593 // "Set node to start node's nextSibling."5594 node = startNode.nextSibling;5595 // "Call collapse(start node, start offset) on the context object's5596 // Selection."5597 getSelection().collapse(startNode, startOffset);5598 getActiveRange().setStart(startNode, startOffset);5599 // "Call extend(node, 0) on the context object's Selection."5600 getSelection().extend(node, 0);5601 getActiveRange().setEnd(node, 0);5602 // "Delete the selection."5603 deleteSelection();5604 // "Call removeAllRanges() on the context object's Selection."5605 getSelection().removeAllRanges();5606 // "Call addRange(original range) on the context object's5607 // Selection."5608 getSelection().addRange(originalRange);5609 getActiveRange().setStart(originalRange.startContainer, originalRange.startOffset);5610 getActiveRange().setEnd(originalRange.endContainer, originalRange.endOffset);5611 // "Return true."5612 extraRanges.pop();5613 return true;5614 }5615 // "While start node has a child with index start offset minus one:"5616 while (0 <= startOffset - 15617 && startOffset - 1 < startNode.childNodes.length) {5618 // "If start node's child with index start offset minus one is5619 // editable and invisible, remove it from start node, then subtract5620 // one from start offset."5621 if (isEditable(startNode.childNodes[startOffset - 1])5622 && isInvisible(startNode.childNodes[startOffset - 1])) {5623 startNode.removeChild(startNode.childNodes[startOffset - 1]);5624 startOffset--;5625 // "Otherwise, set start node to its child with index start offset5626 // minus one, then set start offset to the length of start node."5627 } else {5628 startNode = startNode.childNodes[startOffset - 1];5629 startOffset = getNodeLength(startNode);5630 }5631 }5632 // "Call collapse(start node, start offset) on the context object's5633 // Selection."5634 getSelection().collapse(startNode, startOffset);5635 getActiveRange().setStart(startNode, startOffset);5636 // "Call extend(node, offset) on the context object's Selection."5637 getSelection().extend(node, offset);5638 getActiveRange().setEnd(node, offset);5639 // "Delete the selection, with direction "backward"."5640 deleteSelection({direction: "backward"});5641 // "Return true."5642 return true;5643 }5644};5645//@}5646///// The formatBlock command /////5647//@{5648// "A formattable block name is "address", "dd", "div", "dt", "h1", "h2", "h3",5649// "h4", "h5", "h6", "p", or "pre"."5650var formattableBlockNames = ["address", "dd", "div", "dt", "h1", "h2", "h3",5651 "h4", "h5", "h6", "p", "pre"];5652commands.formatblock = {5653 preservesOverrides: true,5654 action: function(value) {5655 // "If value begins with a "<" character and ends with a ">" character,5656 // remove the first and last characters from it."5657 if (/^<.*>$/.test(value)) {5658 value = value.slice(1, -1);5659 }5660 // "Let value be converted to ASCII lowercase."5661 value = value.toLowerCase();5662 // "If value is not a formattable block name, return false."5663 if (formattableBlockNames.indexOf(value) == -1) {5664 return false;5665 }5666 // "Block-extend the active range, and let new range be the result."5667 var newRange = blockExtend(getActiveRange());5668 // "Let node list be an empty list of nodes."5669 //5670 // "For each node node contained in new range, append node to node list5671 // if it is editable, the last member of original node list (if any) is5672 // not an ancestor of node, node is either a non-list single-line5673 // container or an allowed child of "p" or a dd or dt, and node is not5674 // the ancestor of a prohibited paragraph child."5675 var nodeList = getContainedNodes(newRange, function(node) {5676 return isEditable(node)5677 && (isNonListSingleLineContainer(node)5678 || isAllowedChild(node, "p")5679 || isHtmlElement(node, ["dd", "dt"]))5680 && !getDescendants(node).some(isProhibitedParagraphChild);5681 });5682 // "Record the values of node list, and let values be the result."5683 var values = recordValues(nodeList);5684 // "For each node in node list, while node is the descendant of an5685 // editable HTML element in the same editing host, whose local name is5686 // a formattable block name, and which is not the ancestor of a5687 // prohibited paragraph child, split the parent of the one-node list5688 // consisting of node."5689 for (var i = 0; i < nodeList.length; i++) {5690 var node = nodeList[i];5691 while (getAncestors(node).some(function(ancestor) {5692 return isEditable(ancestor)5693 && inSameEditingHost(ancestor, node)5694 && isHtmlElement(ancestor, formattableBlockNames)5695 && !getDescendants(ancestor).some(isProhibitedParagraphChild);5696 })) {5697 splitParent([node]);5698 }5699 }5700 // "Restore the values from values."5701 restoreValues(values);5702 // "While node list is not empty:"5703 while (nodeList.length) {5704 var sublist;5705 // "If the first member of node list is a single-line5706 // container:"5707 if (isSingleLineContainer(nodeList[0])) {5708 // "Let sublist be the children of the first member of node5709 // list."5710 sublist = [].slice.call(nodeList[0].childNodes);5711 // "Record the values of sublist, and let values be the5712 // result."5713 var values = recordValues(sublist);5714 // "Remove the first member of node list from its parent,5715 // preserving its descendants."5716 removePreservingDescendants(nodeList[0]);5717 // "Restore the values from values."5718 restoreValues(values);5719 // "Remove the first member from node list."5720 nodeList.shift();5721 // "Otherwise:"5722 } else {5723 // "Let sublist be an empty list of nodes."5724 sublist = [];5725 // "Remove the first member of node list and append it to5726 // sublist."5727 sublist.push(nodeList.shift());5728 // "While node list is not empty, and the first member of5729 // node list is the nextSibling of the last member of5730 // sublist, and the first member of node list is not a5731 // single-line container, and the last member of sublist is5732 // not a br, remove the first member of node list and5733 // append it to sublist."5734 while (nodeList.length5735 && nodeList[0] == sublist[sublist.length - 1].nextSibling5736 && !isSingleLineContainer(nodeList[0])5737 && !isHtmlElement(sublist[sublist.length - 1], "BR")) {5738 sublist.push(nodeList.shift());5739 }5740 }5741 // "Wrap sublist. If value is "div" or "p", sibling criteria5742 // returns false; otherwise it returns true for an HTML element5743 // with local name value and no attributes, and false otherwise.5744 // New parent instructions return the result of running5745 // createElement(value) on the context object. Then fix disallowed5746 // ancestors of the result."5747 fixDisallowedAncestors(wrap(sublist,5748 ["div", "p"].indexOf(value) == - 15749 ? function(node) { return isHtmlElement(node, value) && !node.attributes.length }5750 : function() { return false },5751 function() { return document.createElement(value) }));5752 }5753 // "Return true."5754 return true;5755 }, indeterm: function() {5756 // "If the active range is null, return false."5757 if (!getActiveRange()) {5758 return false;5759 }5760 // "Block-extend the active range, and let new range be the result."5761 var newRange = blockExtend(getActiveRange());5762 // "Let node list be all visible editable nodes that are contained in5763 // new range and have no children."5764 var nodeList = getAllContainedNodes(newRange, function(node) {5765 return isVisible(node)5766 && isEditable(node)5767 && !node.hasChildNodes();5768 });5769 // "If node list is empty, return false."5770 if (!nodeList.length) {5771 return false;5772 }5773 // "Let type be null."5774 var type = null;5775 // "For each node in node list:"5776 for (var i = 0; i < nodeList.length; i++) {5777 var node = nodeList[i];5778 // "While node's parent is editable and in the same editing host as5779 // node, and node is not an HTML element whose local name is a5780 // formattable block name, set node to its parent."5781 while (isEditable(node.parentNode)5782 && inSameEditingHost(node, node.parentNode)5783 && !isHtmlElement(node, formattableBlockNames)) {5784 node = node.parentNode;5785 }5786 // "Let current type be the empty string."5787 var currentType = "";5788 // "If node is an editable HTML element whose local name is a5789 // formattable block name, and node is not the ancestor of a5790 // prohibited paragraph child, set current type to node's local5791 // name."5792 if (isEditable(node)5793 && isHtmlElement(node, formattableBlockNames)5794 && !getDescendants(node).some(isProhibitedParagraphChild)) {5795 currentType = node.tagName;5796 }5797 // "If type is null, set type to current type."5798 if (type === null) {5799 type = currentType;5800 // "Otherwise, if type does not equal current type, return true."5801 } else if (type != currentType) {5802 return true;5803 }5804 }5805 // "Return false."5806 return false;5807 }, value: function() {5808 // "If the active range is null, return the empty string."5809 if (!getActiveRange()) {5810 return "";5811 }5812 // "Block-extend the active range, and let new range be the result."5813 var newRange = blockExtend(getActiveRange());5814 // "Let node be the first visible editable node that is contained in5815 // new range and has no children. If there is no such node, return the5816 // empty string."5817 var nodes = getAllContainedNodes(newRange, function(node) {5818 return isVisible(node)5819 && isEditable(node)5820 && !node.hasChildNodes();5821 });5822 if (!nodes.length) {5823 return "";5824 }5825 var node = nodes[0];5826 // "While node's parent is editable and in the same editing host as5827 // node, and node is not an HTML element whose local name is a5828 // formattable block name, set node to its parent."5829 while (isEditable(node.parentNode)5830 && inSameEditingHost(node, node.parentNode)5831 && !isHtmlElement(node, formattableBlockNames)) {5832 node = node.parentNode;5833 }5834 // "If node is an editable HTML element whose local name is a5835 // formattable block name, and node is not the ancestor of a prohibited5836 // paragraph child, return node's local name, converted to ASCII5837 // lowercase."5838 if (isEditable(node)5839 && isHtmlElement(node, formattableBlockNames)5840 && !getDescendants(node).some(isProhibitedParagraphChild)) {5841 return node.tagName.toLowerCase();5842 }5843 // "Return the empty string."5844 return "";5845 }5846};5847//@}5848///// The forwardDelete command /////5849//@{5850commands.forwarddelete = {5851 preservesOverrides: true,5852 action: function() {5853 // "If the active range is not collapsed, delete the selection and5854 // return true."5855 if (!getActiveRange().collapsed) {5856 deleteSelection();5857 return true;5858 }5859 // "Canonicalize whitespace at the active range's start."5860 canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);5861 // "Let node and offset be the active range's start node and offset."5862 var node = getActiveRange().startContainer;5863 var offset = getActiveRange().startOffset;5864 // "Repeat the following steps:"5865 while (true) {5866 // "If offset is the length of node and node's nextSibling is an5867 // editable invisible node, remove node's nextSibling from its5868 // parent."5869 if (offset == getNodeLength(node)5870 && isEditable(node.nextSibling)5871 && isInvisible(node.nextSibling)) {5872 node.parentNode.removeChild(node.nextSibling);5873 // "Otherwise, if node has a child with index offset and that child5874 // is an editable invisible node, remove that child from node."5875 } else if (offset < node.childNodes.length5876 && isEditable(node.childNodes[offset])5877 && isInvisible(node.childNodes[offset])) {5878 node.removeChild(node.childNodes[offset]);5879 // "Otherwise, if offset is the length of node and node is an5880 // inline node, or if node is invisible, set offset to one plus the5881 // index of node, then set node to its parent."5882 } else if ((offset == getNodeLength(node)5883 && isInlineNode(node))5884 || isInvisible(node)) {5885 offset = 1 + getNodeIndex(node);5886 node = node.parentNode;5887 // "Otherwise, if node has a child with index offset and that child5888 // is neither a block node nor a br nor an img nor a collapsed5889 // block prop, set node to that child, then set offset to zero."5890 } else if (offset < node.childNodes.length5891 && !isBlockNode(node.childNodes[offset])5892 && !isHtmlElement(node.childNodes[offset], ["br", "img"])5893 && !isCollapsedBlockProp(node.childNodes[offset])) {5894 node = node.childNodes[offset];5895 offset = 0;5896 // "Otherwise, break from this loop."5897 } else {5898 break;5899 }5900 }5901 // "If node is a Text node and offset is not node's length:"5902 if (node.nodeType == Node.TEXT_NODE5903 && offset != getNodeLength(node)) {5904 // "Let end offset be offset plus one."5905 var endOffset = offset + 1;5906 // "While end offset is not node's length and the end offsetth5907 // element of node's data has general category M when interpreted5908 // as a Unicode code point, add one to end offset."5909 //5910 // TODO: Not even going to try handling anything beyond the most5911 // basic combining marks, since I couldn't find a good list. I5912 // special-case a few Hebrew diacritics too to test basic coverage5913 // of non-Latin stuff.5914 while (endOffset != node.length5915 && /^[\u0300-\u036f\u0591-\u05bd\u05c1\u05c2]$/.test(node.data[endOffset])) {5916 endOffset++;5917 }5918 // "Call collapse(node, offset) on the context object's Selection."5919 getSelection().collapse(node, offset);5920 getActiveRange().setStart(node, offset);5921 // "Call extend(node, end offset) on the context object's5922 // Selection."5923 getSelection().extend(node, endOffset);5924 getActiveRange().setEnd(node, endOffset);5925 // "Delete the selection."5926 deleteSelection();5927 // "Return true."5928 return true;5929 }5930 // "If node is an inline node, return true."5931 if (isInlineNode(node)) {5932 return true;5933 }5934 // "If node has a child with index offset and that child is a br or hr5935 // or img, but is not a collapsed block prop:"5936 if (offset < node.childNodes.length5937 && isHtmlElement(node.childNodes[offset], ["br", "hr", "img"])5938 && !isCollapsedBlockProp(node.childNodes[offset])) {5939 // "Call collapse(node, offset) on the context object's Selection."5940 getSelection().collapse(node, offset);5941 getActiveRange().setStart(node, offset);5942 // "Call extend(node, offset + 1) on the context object's5943 // Selection."5944 getSelection().extend(node, offset + 1);5945 getActiveRange().setEnd(node, offset + 1);5946 // "Delete the selection."5947 deleteSelection();5948 // "Return true."5949 return true;5950 }5951 // "Let end node equal node and let end offset equal offset."5952 var endNode = node;5953 var endOffset = offset;5954 // "If end node has a child with index end offset, and that child is a5955 // collapsed block prop, add one to end offset."5956 if (endOffset < endNode.childNodes.length5957 && isCollapsedBlockProp(endNode.childNodes[endOffset])) {5958 endOffset++;5959 }5960 // "Repeat the following steps:"5961 while (true) {5962 // "If end offset is the length of end node, set end offset to one5963 // plus the index of end node and then set end node to its parent."5964 if (endOffset == getNodeLength(endNode)) {5965 endOffset = 1 + getNodeIndex(endNode);5966 endNode = endNode.parentNode;5967 // "Otherwise, if end node has a an editable invisible child with5968 // index end offset, remove it from end node."5969 } else if (endOffset < endNode.childNodes.length5970 && isEditable(endNode.childNodes[endOffset])5971 && isInvisible(endNode.childNodes[endOffset])) {5972 endNode.removeChild(endNode.childNodes[endOffset]);5973 // "Otherwise, break from this loop."5974 } else {5975 break;5976 }5977 }5978 // "If the child of end node with index end offset minus one is a5979 // table, return true."5980 if (isHtmlElement(endNode.childNodes[endOffset - 1], "table")) {5981 return true;5982 }5983 // "If the child of end node with index end offset is a table:"5984 if (isHtmlElement(endNode.childNodes[endOffset], "table")) {5985 // "Call collapse(end node, end offset) on the context object's5986 // Selection."5987 getSelection().collapse(endNode, endOffset);5988 getActiveRange().setStart(endNode, endOffset);5989 // "Call extend(end node, end offset + 1) on the context object's5990 // Selection."5991 getSelection().extend(endNode, endOffset + 1);5992 getActiveRange().setEnd(endNode, endOffset + 1);5993 // "Return true."5994 return true;5995 }5996 // "If offset is the length of node, and the child of end node with5997 // index end offset is an hr or br:"5998 if (offset == getNodeLength(node)5999 && isHtmlElement(endNode.childNodes[endOffset], ["br", "hr"])) {6000 // "Call collapse(end node, end offset) on the context object's6001 // Selection."6002 getSelection().collapse(endNode, endOffset);6003 getActiveRange().setStart(endNode, endOffset);6004 // "Call extend(end node, end offset + 1) on the context object's6005 // Selection."6006 getSelection().extend(endNode, endOffset + 1);6007 getActiveRange().setEnd(endNode, endOffset + 1);6008 // "Delete the selection."6009 deleteSelection();6010 // "Call collapse(node, offset) on the Selection."6011 getSelection().collapse(node, offset);6012 getActiveRange().setStart(node, offset);6013 getActiveRange().collapse(true);6014 // "Return true."6015 return true;6016 }6017 // "While end node has a child with index end offset:"6018 while (endOffset < endNode.childNodes.length) {6019 // "If end node's child with index end offset is editable and6020 // invisible, remove it from end node."6021 if (isEditable(endNode.childNodes[endOffset])6022 && isInvisible(endNode.childNodes[endOffset])) {6023 endNode.removeChild(endNode.childNodes[endOffset]);6024 // "Otherwise, set end node to its child with index end offset and6025 // set end offset to zero."6026 } else {6027 endNode = endNode.childNodes[endOffset];6028 endOffset = 0;6029 }6030 }6031 // "Call collapse(node, offset) on the context object's Selection."6032 getSelection().collapse(node, offset);6033 getActiveRange().setStart(node, offset);6034 // "Call extend(end node, end offset) on the context object's6035 // Selection."6036 getSelection().extend(endNode, endOffset);6037 getActiveRange().setEnd(endNode, endOffset);6038 // "Delete the selection."6039 deleteSelection();6040 // "Return true."6041 return true;6042 }6043};6044//@}6045///// The indent command /////6046//@{6047commands.indent = {6048 preservesOverrides: true,6049 action: function() {6050 // "Let items be a list of all lis that are ancestor containers of the6051 // active range's start and/or end node."6052 //6053 // Has to be in tree order, remember!6054 var items = [];6055 for (var node = getActiveRange().endContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {6056 if (isHtmlElement(node, "LI")) {6057 items.unshift(node);6058 }6059 }6060 for (var node = getActiveRange().startContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {6061 if (isHtmlElement(node, "LI")) {6062 items.unshift(node);6063 }6064 }6065 for (var node = getActiveRange().commonAncestorContainer; node; node = node.parentNode) {6066 if (isHtmlElement(node, "LI")) {6067 items.unshift(node);6068 }6069 }6070 // "For each item in items, normalize sublists of item."6071 for (var i = 0; i < items.length; i++) {6072 normalizeSublists(items[i]);6073 }6074 // "Block-extend the active range, and let new range be the result."6075 var newRange = blockExtend(getActiveRange());6076 // "Let node list be a list of nodes, initially empty."6077 var nodeList = [];6078 // "For each node node contained in new range, if node is editable and6079 // is an allowed child of "div" or "ol" and if the last member of node6080 // list (if any) is not an ancestor of node, append node to node list."6081 nodeList = getContainedNodes(newRange, function(node) {6082 return isEditable(node)6083 && (isAllowedChild(node, "div")6084 || isAllowedChild(node, "ol"));6085 });6086 // "If the first visible member of node list is an li whose parent is6087 // an ol or ul:"6088 if (isHtmlElement(nodeList.filter(isVisible)[0], "li")6089 && isHtmlElement(nodeList.filter(isVisible)[0].parentNode, ["ol", "ul"])) {6090 // "Let sibling be node list's first visible member's6091 // previousSibling."6092 var sibling = nodeList.filter(isVisible)[0].previousSibling;6093 // "While sibling is invisible, set sibling to its6094 // previousSibling."6095 while (isInvisible(sibling)) {6096 sibling = sibling.previousSibling;6097 }6098 // "If sibling is an li, normalize sublists of sibling."6099 if (isHtmlElement(sibling, "li")) {6100 normalizeSublists(sibling);6101 }6102 }6103 // "While node list is not empty:"6104 while (nodeList.length) {6105 // "Let sublist be a list of nodes, initially empty."6106 var sublist = [];6107 // "Remove the first member of node list and append it to sublist."6108 sublist.push(nodeList.shift());6109 // "While the first member of node list is the nextSibling of the6110 // last member of sublist, remove the first member of node list and6111 // append it to sublist."6112 while (nodeList.length6113 && nodeList[0] == sublist[sublist.length - 1].nextSibling) {6114 sublist.push(nodeList.shift());6115 }6116 // "Indent sublist."6117 indentNodes(sublist);6118 }6119 // "Return true."6120 return true;6121 }6122};6123//@}6124///// The insertHorizontalRule command /////6125//@{6126commands.inserthorizontalrule = {6127 preservesOverrides: true,6128 action: function() {6129 // "Let start node, start offset, end node, and end offset be the6130 // active range's start and end nodes and offsets."6131 var startNode = getActiveRange().startContainer;6132 var startOffset = getActiveRange().startOffset;6133 var endNode = getActiveRange().endContainer;6134 var endOffset = getActiveRange().endOffset;6135 // "While start offset is 0 and start node's parent is not null, set6136 // start offset to start node's index, then set start node to its6137 // parent."6138 while (startOffset == 06139 && startNode.parentNode) {6140 startOffset = getNodeIndex(startNode);6141 startNode = startNode.parentNode;6142 }6143 // "While end offset is end node's length, and end node's parent is not6144 // null, set end offset to one plus end node's index, then set end node6145 // to its parent."6146 while (endOffset == getNodeLength(endNode)6147 && endNode.parentNode) {6148 endOffset = 1 + getNodeIndex(endNode);6149 endNode = endNode.parentNode;6150 }6151 // "Call collapse(start node, start offset) on the context object's6152 // Selection."6153 getSelection().collapse(startNode, startOffset);6154 getActiveRange().setStart(startNode, startOffset);6155 // "Call extend(end node, end offset) on the context object's6156 // Selection."6157 getSelection().extend(endNode, endOffset);6158 getActiveRange().setEnd(endNode, endOffset);6159 // "Delete the selection, with block merging false."6160 deleteSelection({blockMerging: false});6161 // "If the active range's start node is neither editable nor an editing6162 // host, return true."6163 if (!isEditable(getActiveRange().startContainer)6164 && !isEditingHost(getActiveRange().startContainer)) {6165 return true;6166 }6167 // "If the active range's start node is a Text node and its start6168 // offset is zero, call collapse() on the context object's Selection,6169 // with first argument the active range's start node's parent and6170 // second argument the active range's start node's index."6171 if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE6172 && getActiveRange().startOffset == 0) {6173 var newNode = getActiveRange().startContainer.parentNode;6174 var newOffset = getNodeIndex(getActiveRange().startContainer);6175 getSelection().collapse(newNode, newOffset);6176 getActiveRange().setStart(newNode, newOffset);6177 getActiveRange().collapse(true);6178 }6179 // "If the active range's start node is a Text node and its start6180 // offset is the length of its start node, call collapse() on the6181 // context object's Selection, with first argument the active range's6182 // start node's parent, and the second argument one plus the active6183 // range's start node's index."6184 if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE6185 && getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {6186 var newNode = getActiveRange().startContainer.parentNode;6187 var newOffset = 1 + getNodeIndex(getActiveRange().startContainer);6188 getSelection().collapse(newNode, newOffset);6189 getActiveRange().setStart(newNode, newOffset);6190 getActiveRange().collapse(true);6191 }6192 // "Let hr be the result of calling createElement("hr") on the6193 // context object."6194 var hr = document.createElement("hr");6195 // "Run insertNode(hr) on the active range."6196 getActiveRange().insertNode(hr);6197 // "Fix disallowed ancestors of hr."6198 fixDisallowedAncestors(hr);6199 // "Run collapse() on the context object's Selection, with first6200 // argument hr's parent and the second argument equal to one plus hr's6201 // index."6202 getSelection().collapse(hr.parentNode, 1 + getNodeIndex(hr));6203 getActiveRange().setStart(hr.parentNode, 1 + getNodeIndex(hr));6204 getActiveRange().collapse(true);6205 // "Return true."6206 return true;6207 }6208};6209//@}6210///// The insertHTML command /////6211//@{6212commands.inserthtml = {6213 preservesOverrides: true,6214 action: function(value) {6215 // "Delete the selection."6216 deleteSelection();6217 // "If the active range's start node is neither editable nor an editing6218 // host, return true."6219 if (!isEditable(getActiveRange().startContainer)6220 && !isEditingHost(getActiveRange().startContainer)) {6221 return true;6222 }6223 // "Let frag be the result of calling createContextualFragment(value)6224 // on the active range."6225 var frag = getActiveRange().createContextualFragment(value);6226 // "Let last child be the lastChild of frag."6227 var lastChild = frag.lastChild;6228 // "If last child is null, return true."6229 if (!lastChild) {6230 return true;6231 }6232 // "Let descendants be all descendants of frag."6233 var descendants = getDescendants(frag);6234 // "If the active range's start node is a block node:"6235 if (isBlockNode(getActiveRange().startContainer)) {6236 // "Let collapsed block props be all editable collapsed block prop6237 // children of the active range's start node that have index6238 // greater than or equal to the active range's start offset."6239 //6240 // "For each node in collapsed block props, remove node from its6241 // parent."6242 [].filter.call(getActiveRange().startContainer.childNodes, function(node) {6243 return isEditable(node)6244 && isCollapsedBlockProp(node)6245 && getNodeIndex(node) >= getActiveRange().startOffset;6246 }).forEach(function(node) {6247 node.parentNode.removeChild(node);6248 });6249 }6250 // "Call insertNode(frag) on the active range."6251 getActiveRange().insertNode(frag);6252 // "If the active range's start node is a block node with no visible6253 // children, call createElement("br") on the context object and append6254 // the result as the last child of the active range's start node."6255 if (isBlockNode(getActiveRange().startContainer)6256 && ![].some.call(getActiveRange().startContainer.childNodes, isVisible)) {6257 getActiveRange().startContainer.appendChild(document.createElement("br"));6258 }6259 // "Call collapse() on the context object's Selection, with last6260 // child's parent as the first argument and one plus its index as the6261 // second."6262 getActiveRange().setStart(lastChild.parentNode, 1 + getNodeIndex(lastChild));6263 getActiveRange().setEnd(lastChild.parentNode, 1 + getNodeIndex(lastChild));6264 // "Fix disallowed ancestors of each member of descendants."6265 for (var i = 0; i < descendants.length; i++) {6266 fixDisallowedAncestors(descendants[i]);6267 }6268 // "Return true."6269 return true;6270 }6271};6272//@}6273///// The insertImage command /////6274//@{6275commands.insertimage = {6276 preservesOverrides: true,6277 action: function(value) {6278 // "If value is the empty string, return false."6279 if (value === "") {6280 return false;6281 }6282 // "Delete the selection, with strip wrappers false."6283 deleteSelection({stripWrappers: false});6284 // "Let range be the active range."6285 var range = getActiveRange();6286 // "If the active range's start node is neither editable nor an editing6287 // host, return true."6288 if (!isEditable(getActiveRange().startContainer)6289 && !isEditingHost(getActiveRange().startContainer)) {6290 return true;6291 }6292 // "If range's start node is a block node whose sole child is a br, and6293 // its start offset is 0, remove its start node's child from it."6294 if (isBlockNode(range.startContainer)6295 && range.startContainer.childNodes.length == 16296 && isHtmlElement(range.startContainer.firstChild, "br")6297 && range.startOffset == 0) {6298 range.startContainer.removeChild(range.startContainer.firstChild);6299 }6300 // "Let img be the result of calling createElement("img") on the6301 // context object."6302 var img = document.createElement("img");6303 // "Run setAttribute("src", value) on img."6304 img.setAttribute("src", value);6305 // "Run insertNode(img) on the range."6306 range.insertNode(img);6307 // "Run collapse() on the Selection, with first argument equal to the6308 // parent of img and the second argument equal to one plus the index of6309 // img."6310 //6311 // Not everyone actually supports collapse(), so we do it manually6312 // instead. Also, we need to modify the actual range we're given as6313 // well, for the sake of autoimplementation.html's range-filling-in.6314 range.setStart(img.parentNode, 1 + getNodeIndex(img));6315 range.setEnd(img.parentNode, 1 + getNodeIndex(img));6316 getSelection().removeAllRanges();6317 getSelection().addRange(range);6318 // IE adds width and height attributes for some reason, so remove those6319 // to actually do what the spec says.6320 img.removeAttribute("width");6321 img.removeAttribute("height");6322 // "Return true."6323 return true;6324 }6325};6326//@}6327///// The insertLineBreak command /////6328//@{6329commands.insertlinebreak = {6330 preservesOverrides: true,6331 action: function(value) {6332 // "Delete the selection, with strip wrappers false."6333 deleteSelection({stripWrappers: false});6334 // "If the active range's start node is neither editable nor an editing6335 // host, return true."6336 if (!isEditable(getActiveRange().startContainer)6337 && !isEditingHost(getActiveRange().startContainer)) {6338 return true;6339 }6340 // "If the active range's start node is an Element, and "br" is not an6341 // allowed child of it, return true."6342 if (getActiveRange().startContainer.nodeType == Node.ELEMENT_NODE6343 && !isAllowedChild("br", getActiveRange().startContainer)) {6344 return true;6345 }6346 // "If the active range's start node is not an Element, and "br" is not6347 // an allowed child of the active range's start node's parent, return6348 // true."6349 if (getActiveRange().startContainer.nodeType != Node.ELEMENT_NODE6350 && !isAllowedChild("br", getActiveRange().startContainer.parentNode)) {6351 return true;6352 }6353 // "If the active range's start node is a Text node and its start6354 // offset is zero, call collapse() on the context object's Selection,6355 // with first argument equal to the active range's start node's parent6356 // and second argument equal to the active range's start node's index."6357 if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE6358 && getActiveRange().startOffset == 0) {6359 var newNode = getActiveRange().startContainer.parentNode;6360 var newOffset = getNodeIndex(getActiveRange().startContainer);6361 getSelection().collapse(newNode, newOffset);6362 getActiveRange().setStart(newNode, newOffset);6363 getActiveRange().setEnd(newNode, newOffset);6364 }6365 // "If the active range's start node is a Text node and its start6366 // offset is the length of its start node, call collapse() on the6367 // context object's Selection, with first argument equal to the active6368 // range's start node's parent and second argument equal to one plus6369 // the active range's start node's index."6370 if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE6371 && getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {6372 var newNode = getActiveRange().startContainer.parentNode;6373 var newOffset = 1 + getNodeIndex(getActiveRange().startContainer);6374 getSelection().collapse(newNode, newOffset);6375 getActiveRange().setStart(newNode, newOffset);6376 getActiveRange().setEnd(newNode, newOffset);6377 }6378 // "Let br be the result of calling createElement("br") on the context6379 // object."6380 var br = document.createElement("br");6381 // "Call insertNode(br) on the active range."6382 getActiveRange().insertNode(br);6383 // "Call collapse() on the context object's Selection, with br's parent6384 // as the first argument and one plus br's index as the second6385 // argument."6386 getSelection().collapse(br.parentNode, 1 + getNodeIndex(br));6387 getActiveRange().setStart(br.parentNode, 1 + getNodeIndex(br));6388 getActiveRange().setEnd(br.parentNode, 1 + getNodeIndex(br));6389 // "If br is a collapsed line break, call createElement("br") on the6390 // context object and let extra br be the result, then call6391 // insertNode(extra br) on the active range."6392 if (isCollapsedLineBreak(br)) {6393 getActiveRange().insertNode(document.createElement("br"));6394 // Compensate for nonstandard implementations of insertNode6395 getSelection().collapse(br.parentNode, 1 + getNodeIndex(br));6396 getActiveRange().setStart(br.parentNode, 1 + getNodeIndex(br));6397 getActiveRange().setEnd(br.parentNode, 1 + getNodeIndex(br));6398 }6399 // "Return true."6400 return true;6401 }6402};6403//@}6404///// The insertOrderedList command /////6405//@{6406commands.insertorderedlist = {6407 preservesOverrides: true,6408 // "Toggle lists with tag name "ol", then return true."6409 action: function() { toggleLists("ol"); return true },6410 // "True if the selection's list state is "mixed" or "mixed ol", false6411 // otherwise."6412 indeterm: function() { return /^mixed( ol)?$/.test(getSelectionListState()) },6413 // "True if the selection's list state is "ol", false otherwise."6414 state: function() { return getSelectionListState() == "ol" },6415};6416//@}6417///// The insertParagraph command /////6418//@{6419commands.insertparagraph = {6420 preservesOverrides: true,6421 action: function() {6422 // "Delete the selection."6423 deleteSelection();6424 // "If the active range's start node is neither editable nor an editing6425 // host, return true."6426 if (!isEditable(getActiveRange().startContainer)6427 && !isEditingHost(getActiveRange().startContainer)) {6428 return true;6429 }6430 // "Let node and offset be the active range's start node and offset."6431 var node = getActiveRange().startContainer;6432 var offset = getActiveRange().startOffset;6433 // "If node is a Text node, and offset is neither 0 nor the length of6434 // node, call splitText(offset) on node."6435 if (node.nodeType == Node.TEXT_NODE6436 && offset != 06437 && offset != getNodeLength(node)) {6438 node.splitText(offset);6439 }6440 // "If node is a Text node and offset is its length, set offset to one6441 // plus the index of node, then set node to its parent."6442 if (node.nodeType == Node.TEXT_NODE6443 && offset == getNodeLength(node)) {6444 offset = 1 + getNodeIndex(node);6445 node = node.parentNode;6446 }6447 // "If node is a Text or Comment node, set offset to the index of node,6448 // then set node to its parent."6449 if (node.nodeType == Node.TEXT_NODE6450 || node.nodeType == Node.COMMENT_NODE) {6451 offset = getNodeIndex(node);6452 node = node.parentNode;6453 }6454 // "Call collapse(node, offset) on the context object's Selection."6455 getSelection().collapse(node, offset);6456 getActiveRange().setStart(node, offset);6457 getActiveRange().setEnd(node, offset);6458 // "Let container equal node."6459 var container = node;6460 // "While container is not a single-line container, and container's6461 // parent is editable and in the same editing host as node, set6462 // container to its parent."6463 while (!isSingleLineContainer(container)6464 && isEditable(container.parentNode)6465 && inSameEditingHost(node, container.parentNode)) {6466 container = container.parentNode;6467 }6468 // "If container is an editable single-line container in the same6469 // editing host as node, and its local name is "p" or "div":"6470 if (isEditable(container)6471 && isSingleLineContainer(container)6472 && inSameEditingHost(node, container.parentNode)6473 && (container.tagName == "P" || container.tagName == "DIV")) {6474 // "Let outer container equal container."6475 var outerContainer = container;6476 // "While outer container is not a dd or dt or li, and outer6477 // container's parent is editable, set outer container to its6478 // parent."6479 while (!isHtmlElement(outerContainer, ["dd", "dt", "li"])6480 && isEditable(outerContainer.parentNode)) {6481 outerContainer = outerContainer.parentNode;6482 }6483 // "If outer container is a dd or dt or li, set container to outer6484 // container."6485 if (isHtmlElement(outerContainer, ["dd", "dt", "li"])) {6486 container = outerContainer;6487 }6488 }6489 // "If container is not editable or not in the same editing host as6490 // node or is not a single-line container:"6491 if (!isEditable(container)6492 || !inSameEditingHost(container, node)6493 || !isSingleLineContainer(container)) {6494 // "Let tag be the default single-line container name."6495 var tag = defaultSingleLineContainerName;6496 // "Block-extend the active range, and let new range be the6497 // result."6498 var newRange = blockExtend(getActiveRange());6499 // "Let node list be a list of nodes, initially empty."6500 //6501 // "Append to node list the first node in tree order that is6502 // contained in new range and is an allowed child of "p", if any."6503 var nodeList = getContainedNodes(newRange, function(node) { return isAllowedChild(node, "p") })6504 .slice(0, 1);6505 // "If node list is empty:"6506 if (!nodeList.length) {6507 // "If tag is not an allowed child of the active range's start6508 // node, return true."6509 if (!isAllowedChild(tag, getActiveRange().startContainer)) {6510 return true;6511 }6512 // "Set container to the result of calling createElement(tag)6513 // on the context object."6514 container = document.createElement(tag);6515 // "Call insertNode(container) on the active range."6516 getActiveRange().insertNode(container);6517 // "Call createElement("br") on the context object, and append6518 // the result as the last child of container."6519 container.appendChild(document.createElement("br"));6520 // "Call collapse(container, 0) on the context object's6521 // Selection."6522 getSelection().collapse(container, 0);6523 getActiveRange().setStart(container, 0);6524 getActiveRange().setEnd(container, 0);6525 // "Return true."6526 return true;6527 }6528 // "While the nextSibling of the last member of node list is not6529 // null and is an allowed child of "p", append it to node list."6530 while (nodeList[nodeList.length - 1].nextSibling6531 && isAllowedChild(nodeList[nodeList.length - 1].nextSibling, "p")) {6532 nodeList.push(nodeList[nodeList.length - 1].nextSibling);6533 }6534 // "Wrap node list, with sibling criteria returning false and new6535 // parent instructions returning the result of calling6536 // createElement(tag) on the context object. Set container to the6537 // result."6538 container = wrap(nodeList,6539 function() { return false },6540 function() { return document.createElement(tag) }6541 );6542 }6543 // "If container's local name is "address", "listing", or "pre":"6544 if (container.tagName == "ADDRESS"6545 || container.tagName == "LISTING"6546 || container.tagName == "PRE") {6547 // "Let br be the result of calling createElement("br") on the6548 // context object."6549 var br = document.createElement("br");6550 // "Call insertNode(br) on the active range."6551 getActiveRange().insertNode(br);6552 // "Call collapse(node, offset + 1) on the context object's6553 // Selection."6554 getSelection().collapse(node, offset + 1);6555 getActiveRange().setStart(node, offset + 1);6556 getActiveRange().setEnd(node, offset + 1);6557 // "If br is the last descendant of container, let br be the result6558 // of calling createElement("br") on the context object, then call6559 // insertNode(br) on the active range."6560 //6561 // Work around browser bugs: some browsers select the6562 // newly-inserted node, not per spec.6563 if (!isDescendant(nextNode(br), container)) {6564 getActiveRange().insertNode(document.createElement("br"));6565 getSelection().collapse(node, offset + 1);6566 getActiveRange().setEnd(node, offset + 1);6567 }6568 // "Return true."6569 return true;6570 }6571 // "If container's local name is "li", "dt", or "dd"; and either it has6572 // no children or it has a single child and that child is a br:"6573 if (["LI", "DT", "DD"].indexOf(container.tagName) != -16574 && (!container.hasChildNodes()6575 || (container.childNodes.length == 16576 && isHtmlElement(container.firstChild, "br")))) {6577 // "Split the parent of the one-node list consisting of container."6578 splitParent([container]);6579 // "If container has no children, call createElement("br") on the6580 // context object and append the result as the last child of6581 // container."6582 if (!container.hasChildNodes()) {6583 container.appendChild(document.createElement("br"));6584 }6585 // "If container is a dd or dt, and it is not an allowed child of6586 // any of its ancestors in the same editing host, set the tag name6587 // of container to the default single-line container name and let6588 // container be the result."6589 if (isHtmlElement(container, ["dd", "dt"])6590 && getAncestors(container).every(function(ancestor) {6591 return !inSameEditingHost(container, ancestor)6592 || !isAllowedChild(container, ancestor)6593 })) {6594 container = setTagName(container, defaultSingleLineContainerName);6595 }6596 // "Fix disallowed ancestors of container."6597 fixDisallowedAncestors(container);6598 // "Return true."6599 return true;6600 }6601 // "Let new line range be a new range whose start is the same as6602 // the active range's, and whose end is (container, length of6603 // container)."6604 var newLineRange = document.createRange();6605 newLineRange.setStart(getActiveRange().startContainer, getActiveRange().startOffset);6606 newLineRange.setEnd(container, getNodeLength(container));6607 // "While new line range's start offset is zero and its start node is6608 // not a prohibited paragraph child, set its start to (parent of start6609 // node, index of start node)."6610 while (newLineRange.startOffset == 06611 && !isProhibitedParagraphChild(newLineRange.startContainer)) {6612 newLineRange.setStart(newLineRange.startContainer.parentNode, getNodeIndex(newLineRange.startContainer));6613 }6614 // "While new line range's start offset is the length of its start node6615 // and its start node is not a prohibited paragraph child, set its6616 // start to (parent of start node, 1 + index of start node)."6617 while (newLineRange.startOffset == getNodeLength(newLineRange.startContainer)6618 && !isProhibitedParagraphChild(newLineRange.startContainer)) {6619 newLineRange.setStart(newLineRange.startContainer.parentNode, 1 + getNodeIndex(newLineRange.startContainer));6620 }6621 // "Let end of line be true if new line range contains either nothing6622 // or a single br, and false otherwise."6623 var containedInNewLineRange = getContainedNodes(newLineRange);6624 var endOfLine = !containedInNewLineRange.length6625 || (containedInNewLineRange.length == 16626 && isHtmlElement(containedInNewLineRange[0], "br"));6627 // "If the local name of container is "h1", "h2", "h3", "h4", "h5", or6628 // "h6", and end of line is true, let new container name be the default6629 // single-line container name."6630 var newContainerName;6631 if (/^H[1-6]$/.test(container.tagName)6632 && endOfLine) {6633 newContainerName = defaultSingleLineContainerName;6634 // "Otherwise, if the local name of container is "dt" and end of line6635 // is true, let new container name be "dd"."6636 } else if (container.tagName == "DT"6637 && endOfLine) {6638 newContainerName = "dd";6639 // "Otherwise, if the local name of container is "dd" and end of line6640 // is true, let new container name be "dt"."6641 } else if (container.tagName == "DD"6642 && endOfLine) {6643 newContainerName = "dt";6644 // "Otherwise, let new container name be the local name of container."6645 } else {6646 newContainerName = container.tagName.toLowerCase();6647 }6648 // "Let new container be the result of calling createElement(new6649 // container name) on the context object."6650 var newContainer = document.createElement(newContainerName);6651 // "Copy all attributes of container to new container."6652 for (var i = 0; i < container.attributes.length; i++) {6653 newContainer.setAttributeNS(container.attributes[i].namespaceURI, container.attributes[i].name, container.attributes[i].value);6654 }6655 // "If new container has an id attribute, unset it."6656 newContainer.removeAttribute("id");6657 // "Insert new container into the parent of container immediately after6658 // container."6659 container.parentNode.insertBefore(newContainer, container.nextSibling);6660 // "Let contained nodes be all nodes contained in new line range."6661 var containedNodes = getAllContainedNodes(newLineRange);6662 // "Let frag be the result of calling extractContents() on new line6663 // range."6664 var frag = newLineRange.extractContents();6665 // "Unset the id attribute (if any) of each Element descendant of frag6666 // that is not in contained nodes."6667 var descendants = getDescendants(frag);6668 for (var i = 0; i < descendants.length; i++) {6669 if (descendants[i].nodeType == Node.ELEMENT_NODE6670 && containedNodes.indexOf(descendants[i]) == -1) {6671 descendants[i].removeAttribute("id");6672 }6673 }6674 // "Call appendChild(frag) on new container."6675 newContainer.appendChild(frag);6676 // "While container's lastChild is a prohibited paragraph child, set6677 // container to its lastChild."6678 while (isProhibitedParagraphChild(container.lastChild)) {6679 container = container.lastChild;6680 }6681 // "While new container's lastChild is a prohibited paragraph child,6682 // set new container to its lastChild."6683 while (isProhibitedParagraphChild(newContainer.lastChild)) {6684 newContainer = newContainer.lastChild;6685 }6686 // "If container has no visible children, call createElement("br") on6687 // the context object, and append the result as the last child of6688 // container."6689 if (![].some.call(container.childNodes, isVisible)) {6690 container.appendChild(document.createElement("br"));6691 }6692 // "If new container has no visible children, call createElement("br")6693 // on the context object, and append the result as the last child of6694 // new container."6695 if (![].some.call(newContainer.childNodes, isVisible)) {6696 newContainer.appendChild(document.createElement("br"));6697 }6698 // "Call collapse(new container, 0) on the context object's Selection."6699 getSelection().collapse(newContainer, 0);6700 getActiveRange().setStart(newContainer, 0);6701 getActiveRange().setEnd(newContainer, 0);6702 // "Return true."6703 return true;6704 }6705};6706//@}6707///// The insertText command /////6708//@{6709commands.inserttext = {6710 action: function(value) {6711 // "Delete the selection, with strip wrappers false."6712 deleteSelection({stripWrappers: false});6713 // "If the active range's start node is neither editable nor an editing6714 // host, return true."6715 if (!isEditable(getActiveRange().startContainer)6716 && !isEditingHost(getActiveRange().startContainer)) {6717 return true;6718 }6719 // "If value's length is greater than one:"6720 if (value.length > 1) {6721 // "For each element el in value, take the action for the6722 // insertText command, with value equal to el."6723 for (var i = 0; i < value.length; i++) {6724 commands.inserttext.action(value[i]);6725 }6726 // "Return true."6727 return true;6728 }6729 // "If value is the empty string, return true."6730 if (value == "") {6731 return true;6732 }6733 // "If value is a newline (U+00A0), take the action for the6734 // insertParagraph command and return true."6735 if (value == "\n") {6736 commands.insertparagraph.action();6737 return true;6738 }6739 // "Let node and offset be the active range's start node and offset."6740 var node = getActiveRange().startContainer;6741 var offset = getActiveRange().startOffset;6742 // "If node has a child whose index is offset − 1, and that child is a6743 // Text node, set node to that child, then set offset to node's6744 // length."6745 if (0 <= offset - 16746 && offset - 1 < node.childNodes.length6747 && node.childNodes[offset - 1].nodeType == Node.TEXT_NODE) {6748 node = node.childNodes[offset - 1];6749 offset = getNodeLength(node);6750 }6751 // "If node has a child whose index is offset, and that child is a Text6752 // node, set node to that child, then set offset to zero."6753 if (0 <= offset6754 && offset < node.childNodes.length6755 && node.childNodes[offset].nodeType == Node.TEXT_NODE) {6756 node = node.childNodes[offset];6757 offset = 0;6758 }6759 // "Record current overrides, and let overrides be the result."6760 var overrides = recordCurrentOverrides();6761 // "Call collapse(node, offset) on the context object's Selection."6762 getSelection().collapse(node, offset);6763 getActiveRange().setStart(node, offset);6764 getActiveRange().setEnd(node, offset);6765 // "Canonicalize whitespace at (node, offset)."6766 canonicalizeWhitespace(node, offset);6767 // "Let (node, offset) be the active range's start."6768 node = getActiveRange().startContainer;6769 offset = getActiveRange().startOffset;6770 // "If node is a Text node:"6771 if (node.nodeType == Node.TEXT_NODE) {6772 // "Call insertData(offset, value) on node."6773 node.insertData(offset, value);6774 // "Call collapse(node, offset) on the context object's Selection."6775 getSelection().collapse(node, offset);6776 getActiveRange().setStart(node, offset);6777 // "Call extend(node, offset + 1) on the context object's6778 // Selection."6779 //6780 // Work around WebKit bug: the extend() can throw if the text we're6781 // adding is trailing whitespace.6782 try { getSelection().extend(node, offset + 1); } catch(e) {}6783 getActiveRange().setEnd(node, offset + 1);6784 // "Otherwise:"6785 } else {6786 // "If node has only one child, which is a collapsed line break,6787 // remove its child from it."6788 //6789 // FIXME: IE incorrectly returns false here instead of true6790 // sometimes?6791 if (node.childNodes.length == 16792 && isCollapsedLineBreak(node.firstChild)) {6793 node.removeChild(node.firstChild);6794 }6795 // "Let text be the result of calling createTextNode(value) on the6796 // context object."6797 var text = document.createTextNode(value);6798 // "Call insertNode(text) on the active range."6799 getActiveRange().insertNode(text);6800 // "Call collapse(text, 0) on the context object's Selection."6801 getSelection().collapse(text, 0);6802 getActiveRange().setStart(text, 0);6803 // "Call extend(text, 1) on the context object's Selection."6804 getSelection().extend(text, 1);6805 getActiveRange().setEnd(text, 1);6806 }6807 // "Restore states and values from overrides."6808 restoreStatesAndValues(overrides);6809 // "Canonicalize whitespace at the active range's start, with fix6810 // collapsed space false."6811 canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset, false);6812 // "Canonicalize whitespace at the active range's end, with fix6813 // collapsed space false."6814 canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset, false);6815 // "If value is a space character, autolink the active range's start."6816 if (/^[ \t\n\f\r]$/.test(value)) {6817 autolink(getActiveRange().startContainer, getActiveRange().startOffset);6818 }6819 // "Call collapseToEnd() on the context object's Selection."6820 //6821 // Work around WebKit bug: sometimes it blows up the selection and6822 // throws, which we don't want.6823 try { getSelection().collapseToEnd(); } catch(e) {}6824 getActiveRange().collapse(false);6825 // "Return true."6826 return true;6827 }6828};6829//@}6830///// The insertUnorderedList command /////6831//@{6832commands.insertunorderedlist = {6833 preservesOverrides: true,6834 // "Toggle lists with tag name "ul", then return true."6835 action: function() { toggleLists("ul"); return true },6836 // "True if the selection's list state is "mixed" or "mixed ul", false6837 // otherwise."6838 indeterm: function() { return /^mixed( ul)?$/.test(getSelectionListState()) },6839 // "True if the selection's list state is "ul", false otherwise."6840 state: function() { return getSelectionListState() == "ul" },6841};6842//@}6843///// The justifyCenter command /////6844//@{6845commands.justifycenter = {6846 preservesOverrides: true,6847 // "Justify the selection with alignment "center", then return true."6848 action: function() { justifySelection("center"); return true },6849 indeterm: function() {6850 // "Return false if the active range is null. Otherwise, block-extend6851 // the active range. Return true if among visible editable nodes that6852 // are contained in the result and have no children, at least one has6853 // alignment value "center" and at least one does not. Otherwise return6854 // false."6855 if (!getActiveRange()) {6856 return false;6857 }6858 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6859 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6860 });6861 return nodes.some(function(node) { return getAlignmentValue(node) == "center" })6862 && nodes.some(function(node) { return getAlignmentValue(node) != "center" });6863 }, state: function() {6864 // "Return false if the active range is null. Otherwise, block-extend6865 // the active range. Return true if there is at least one visible6866 // editable node that is contained in the result and has no children,6867 // and all such nodes have alignment value "center". Otherwise return6868 // false."6869 if (!getActiveRange()) {6870 return false;6871 }6872 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6873 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6874 });6875 return nodes.length6876 && nodes.every(function(node) { return getAlignmentValue(node) == "center" });6877 }, value: function() {6878 // "Return the empty string if the active range is null. Otherwise,6879 // block-extend the active range, and return the alignment value of the6880 // first visible editable node that is contained in the result and has6881 // no children. If there is no such node, return "left"."6882 if (!getActiveRange()) {6883 return "";6884 }6885 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6886 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6887 });6888 if (nodes.length) {6889 return getAlignmentValue(nodes[0]);6890 } else {6891 return "left";6892 }6893 },6894};6895//@}6896///// The justifyFull command /////6897//@{6898commands.justifyfull = {6899 preservesOverrides: true,6900 // "Justify the selection with alignment "justify", then return true."6901 action: function() { justifySelection("justify"); return true },6902 indeterm: function() {6903 // "Return false if the active range is null. Otherwise, block-extend6904 // the active range. Return true if among visible editable nodes that6905 // are contained in the result and have no children, at least one has6906 // alignment value "justify" and at least one does not. Otherwise6907 // return false."6908 if (!getActiveRange()) {6909 return false;6910 }6911 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6912 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6913 });6914 return nodes.some(function(node) { return getAlignmentValue(node) == "justify" })6915 && nodes.some(function(node) { return getAlignmentValue(node) != "justify" });6916 }, state: function() {6917 // "Return false if the active range is null. Otherwise, block-extend6918 // the active range. Return true if there is at least one visible6919 // editable node that is contained in the result and has no children,6920 // and all such nodes have alignment value "justify". Otherwise return6921 // false."6922 if (!getActiveRange()) {6923 return false;6924 }6925 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6926 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6927 });6928 return nodes.length6929 && nodes.every(function(node) { return getAlignmentValue(node) == "justify" });6930 }, value: function() {6931 // "Return the empty string if the active range is null. Otherwise,6932 // block-extend the active range, and return the alignment value of the6933 // first visible editable node that is contained in the result and has6934 // no children. If there is no such node, return "left"."6935 if (!getActiveRange()) {6936 return "";6937 }6938 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6939 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6940 });6941 if (nodes.length) {6942 return getAlignmentValue(nodes[0]);6943 } else {6944 return "left";6945 }6946 },6947};6948//@}6949///// The justifyLeft command /////6950//@{6951commands.justifyleft = {6952 preservesOverrides: true,6953 // "Justify the selection with alignment "left", then return true."6954 action: function() { justifySelection("left"); return true },6955 indeterm: function() {6956 // "Return false if the active range is null. Otherwise, block-extend6957 // the active range. Return true if among visible editable nodes that6958 // are contained in the result and have no children, at least one has6959 // alignment value "left" and at least one does not. Otherwise return6960 // false."6961 if (!getActiveRange()) {6962 return false;6963 }6964 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6965 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6966 });6967 return nodes.some(function(node) { return getAlignmentValue(node) == "left" })6968 && nodes.some(function(node) { return getAlignmentValue(node) != "left" });6969 }, state: function() {6970 // "Return false if the active range is null. Otherwise, block-extend6971 // the active range. Return true if there is at least one visible6972 // editable node that is contained in the result and has no children,6973 // and all such nodes have alignment value "left". Otherwise return6974 // false."6975 if (!getActiveRange()) {6976 return false;6977 }6978 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6979 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6980 });6981 return nodes.length6982 && nodes.every(function(node) { return getAlignmentValue(node) == "left" });6983 }, value: function() {6984 // "Return the empty string if the active range is null. Otherwise,6985 // block-extend the active range, and return the alignment value of the6986 // first visible editable node that is contained in the result and has6987 // no children. If there is no such node, return "left"."6988 if (!getActiveRange()) {6989 return "";6990 }6991 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {6992 return isEditable(node) && isVisible(node) && !node.hasChildNodes();6993 });6994 if (nodes.length) {6995 return getAlignmentValue(nodes[0]);6996 } else {6997 return "left";6998 }6999 },7000};7001//@}7002///// The justifyRight command /////7003//@{7004commands.justifyright = {7005 preservesOverrides: true,7006 // "Justify the selection with alignment "right", then return true."7007 action: function() { justifySelection("right"); return true },7008 indeterm: function() {7009 // "Return false if the active range is null. Otherwise, block-extend7010 // the active range. Return true if among visible editable nodes that7011 // are contained in the result and have no children, at least one has7012 // alignment value "right" and at least one does not. Otherwise return7013 // false."7014 if (!getActiveRange()) {7015 return false;7016 }7017 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {7018 return isEditable(node) && isVisible(node) && !node.hasChildNodes();7019 });7020 return nodes.some(function(node) { return getAlignmentValue(node) == "right" })7021 && nodes.some(function(node) { return getAlignmentValue(node) != "right" });7022 }, state: function() {7023 // "Return false if the active range is null. Otherwise, block-extend7024 // the active range. Return true if there is at least one visible7025 // editable node that is contained in the result and has no children,7026 // and all such nodes have alignment value "right". Otherwise return7027 // false."7028 if (!getActiveRange()) {7029 return false;7030 }7031 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {7032 return isEditable(node) && isVisible(node) && !node.hasChildNodes();7033 });7034 return nodes.length7035 && nodes.every(function(node) { return getAlignmentValue(node) == "right" });7036 }, value: function() {7037 // "Return the empty string if the active range is null. Otherwise,7038 // block-extend the active range, and return the alignment value of the7039 // first visible editable node that is contained in the result and has7040 // no children. If there is no such node, return "left"."7041 if (!getActiveRange()) {7042 return "";7043 }7044 var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {7045 return isEditable(node) && isVisible(node) && !node.hasChildNodes();7046 });7047 if (nodes.length) {7048 return getAlignmentValue(nodes[0]);7049 } else {7050 return "left";7051 }7052 },7053};7054//@}7055///// The outdent command /////7056//@{7057commands.outdent = {7058 preservesOverrides: true,7059 action: function() {7060 // "Let items be a list of all lis that are ancestor containers of the7061 // range's start and/or end node."7062 //7063 // It's annoying to get this in tree order using functional stuff7064 // without doing getDescendants(document), which is slow, so I do it7065 // imperatively.7066 var items = [];7067 (function(){7068 for (7069 var ancestorContainer = getActiveRange().endContainer;7070 ancestorContainer != getActiveRange().commonAncestorContainer;7071 ancestorContainer = ancestorContainer.parentNode7072 ) {7073 if (isHtmlElement(ancestorContainer, "li")) {7074 items.unshift(ancestorContainer);7075 }7076 }7077 for (7078 var ancestorContainer = getActiveRange().startContainer;7079 ancestorContainer;7080 ancestorContainer = ancestorContainer.parentNode7081 ) {7082 if (isHtmlElement(ancestorContainer, "li")) {7083 items.unshift(ancestorContainer);7084 }7085 }7086 })();7087 // "For each item in items, normalize sublists of item."7088 items.forEach(normalizeSublists);7089 // "Block-extend the active range, and let new range be the result."7090 var newRange = blockExtend(getActiveRange());7091 // "Let node list be a list of nodes, initially empty."7092 //7093 // "For each node node contained in new range, append node to node list7094 // if the last member of node list (if any) is not an ancestor of node;7095 // node is editable; and either node has no editable descendants, or is7096 // an ol or ul, or is an li whose parent is an ol or ul."7097 var nodeList = getContainedNodes(newRange, function(node) {7098 return isEditable(node)7099 && (!getDescendants(node).some(isEditable)7100 || isHtmlElement(node, ["ol", "ul"])7101 || (isHtmlElement(node, "li") && isHtmlElement(node.parentNode, ["ol", "ul"])));7102 });7103 // "While node list is not empty:"7104 while (nodeList.length) {7105 // "While the first member of node list is an ol or ul or is not7106 // the child of an ol or ul, outdent it and remove it from node7107 // list."7108 while (nodeList.length7109 && (isHtmlElement(nodeList[0], ["OL", "UL"])7110 || !isHtmlElement(nodeList[0].parentNode, ["OL", "UL"]))) {7111 outdentNode(nodeList.shift());7112 }7113 // "If node list is empty, break from these substeps."7114 if (!nodeList.length) {7115 break;7116 }7117 // "Let sublist be a list of nodes, initially empty."7118 var sublist = [];7119 // "Remove the first member of node list and append it to sublist."7120 sublist.push(nodeList.shift());7121 // "While the first member of node list is the nextSibling of the7122 // last member of sublist, and the first member of node list is not7123 // an ol or ul, remove the first member of node list and append it7124 // to sublist."7125 while (nodeList.length7126 && nodeList[0] == sublist[sublist.length - 1].nextSibling7127 && !isHtmlElement(nodeList[0], ["OL", "UL"])) {7128 sublist.push(nodeList.shift());7129 }7130 // "Record the values of sublist, and let values be the result."7131 var values = recordValues(sublist);7132 // "Split the parent of sublist, with new parent null."7133 splitParent(sublist);7134 // "Fix disallowed ancestors of each member of sublist."7135 sublist.forEach(fixDisallowedAncestors);7136 // "Restore the values from values."7137 restoreValues(values);7138 }7139 // "Return true."7140 return true;7141 }7142};7143//@}7144//////////////////////////////////7145///// Miscellaneous commands /////7146//////////////////////////////////7147///// The defaultParagraphSeparator command /////7148//@{7149commands.defaultparagraphseparator = {7150 action: function(value) {7151 // "Let value be converted to ASCII lowercase. If value is then equal7152 // to "p" or "div", set the context object's default single-line7153 // container name to value and return true. Otherwise, return false."7154 value = value.toLowerCase();7155 if (value == "p" || value == "div") {7156 defaultSingleLineContainerName = value;7157 return true;7158 }7159 return false;7160 }, value: function() {7161 // "Return the context object's default single-line container name."7162 return defaultSingleLineContainerName;7163 },7164};7165//@}7166///// The selectAll command /////7167//@{7168commands.selectall = {7169 // Note, this ignores the whole globalRange/getActiveRange() thing and7170 // works with actual selections. Not suitable for autoimplementation.html.7171 action: function() {7172 // "Let target be the body element of the context object."7173 var target = document.body;7174 // "If target is null, let target be the context object's7175 // documentElement."7176 if (!target) {7177 target = document.documentElement;7178 }7179 // "If target is null, call getSelection() on the context object, and7180 // call removeAllRanges() on the result."7181 if (!target) {7182 getSelection().removeAllRanges();7183 // "Otherwise, call getSelection() on the context object, and call7184 // selectAllChildren(target) on the result."7185 } else {7186 getSelection().selectAllChildren(target);7187 }7188 // "Return true."7189 return true;7190 }7191};7192//@}7193///// The styleWithCSS command /////7194//@{7195commands.stylewithcss = {7196 action: function(value) {7197 // "If value is an ASCII case-insensitive match for the string7198 // "false", set the CSS styling flag to false. Otherwise, set the7199 // CSS styling flag to true. Either way, return true."7200 cssStylingFlag = String(value).toLowerCase() != "false";7201 return true;7202 }, state: function() { return cssStylingFlag }7203};7204//@}7205///// The useCSS command /////7206//@{7207commands.usecss = {7208 action: function(value) {7209 // "If value is an ASCII case-insensitive match for the string "false",7210 // set the CSS styling flag to true. Otherwise, set the CSS styling7211 // flag to false. Either way, return true."7212 cssStylingFlag = String(value).toLowerCase() == "false";7213 return true;7214 }7215};7216//@}7217// Some final setup7218//@{7219(function() {7220// Opera 11.50 doesn't implement Object.keys, so I have to make an explicit7221// temporary, which means I need an extra closure to not leak the temporaries7222// into the global namespace. >:(7223var commandNames = [];7224for (var command in commands) {7225 commandNames.push(command);7226}7227commandNames.forEach(function(command) {7228 // "If a command does not have a relevant CSS property specified, it7229 // defaults to null."7230 if (!("relevantCssProperty" in commands[command])) {7231 commands[command].relevantCssProperty = null;7232 }7233 // "If a command has inline command activated values defined but nothing7234 // else defines when it is indeterminate, it is indeterminate if among7235 // formattable nodes effectively contained in the active range, there is at7236 // least one whose effective command value is one of the given values and7237 // at least one whose effective command value is not one of the given7238 // values."7239 if ("inlineCommandActivatedValues" in commands[command]7240 && !("indeterm" in commands[command])) {7241 commands[command].indeterm = function() {7242 if (!getActiveRange()) {7243 return false;7244 }7245 var values = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode)7246 .map(function(node) { return getEffectiveCommandValue(node, command) });7247 var matchingValues = values.filter(function(value) {7248 return commands[command].inlineCommandActivatedValues.indexOf(value) != -1;7249 });7250 return matchingValues.length >= 17251 && values.length - matchingValues.length >= 1;7252 };7253 }7254 // "If a command has inline command activated values defined, its state is7255 // true if either no formattable node is effectively contained in the7256 // active range, and the active range's start node's effective command7257 // value is one of the given values; or if there is at least one7258 // formattable node effectively contained in the active range, and all of7259 // them have an effective command value equal to one of the given values."7260 if ("inlineCommandActivatedValues" in commands[command]) {7261 commands[command].state = function() {7262 if (!getActiveRange()) {7263 return false;7264 }7265 var nodes = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode);7266 if (nodes.length == 0) {7267 return commands[command].inlineCommandActivatedValues7268 .indexOf(getEffectiveCommandValue(getActiveRange().startContainer, command)) != -1;7269 } else {7270 return nodes.every(function(node) {7271 return commands[command].inlineCommandActivatedValues7272 .indexOf(getEffectiveCommandValue(node, command)) != -1;7273 });7274 }7275 };7276 }7277 // "If a command is a standard inline value command, it is indeterminate if7278 // among formattable nodes that are effectively contained in the active7279 // range, there are two that have distinct effective command values. Its7280 // value is the effective command value of the first formattable node that7281 // is effectively contained in the active range; or if there is no such7282 // node, the effective command value of the active range's start node; or7283 // if that is null, the empty string."7284 if ("standardInlineValueCommand" in commands[command]) {7285 commands[command].indeterm = function() {7286 if (!getActiveRange()) {7287 return false;7288 }7289 var values = getAllEffectivelyContainedNodes(getActiveRange())7290 .filter(isFormattableNode)7291 .map(function(node) { return getEffectiveCommandValue(node, command) });7292 for (var i = 1; i < values.length; i++) {7293 if (values[i] != values[i - 1]) {7294 return true;7295 }7296 }7297 return false;7298 };7299 commands[command].value = function() {7300 if (!getActiveRange()) {7301 return "";7302 }7303 var refNode = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode)[0];7304 if (typeof refNode == "undefined") {7305 refNode = getActiveRange().startContainer;7306 }7307 var ret = getEffectiveCommandValue(refNode, command);7308 if (ret === null) {7309 return "";7310 }7311 return ret;7312 };7313 }7314 // "If a command preserves overrides, then before taking its action, the7315 // user agent must record current overrides. After taking the action, if7316 // the active range is collapsed, it must restore states and values from7317 // the recorded list."7318 if ("preservesOverrides" in commands[command]) {7319 var oldAction = commands[command].action;7320 commands[command].action = function(value) {7321 var overrides = recordCurrentOverrides();7322 var ret = oldAction(value);7323 if (getActiveRange().collapsed) {7324 restoreStatesAndValues(overrides);7325 }7326 return ret;7327 };7328 }7329});7330})();7331//@}...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1var activeRange = wptbTableSetupObj.getActiveRange();2var activeTable = wptbTableSetupObj.getActiveTable();3var activeTableId = wptbTableSetupObj.getActiveTableId();4var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();5var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();6var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();7var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();8var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();9var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();10var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();11var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();12var activeTableSettings = wptbTableSetupObj.getActiveTableSettings();

Full Screen

Using AI Code Generation

copy

Full Screen

1var table = document.getElementById('table');2var range = table.getActiveRange();3### getSelectedCells()4var table = document.getElementById('table');5var selectedCells = table.getSelectedCells();6### getCellByCoords()7var table = document.getElementById('table');8var cell = table.getCellByCoords(0, 0);9### setCellByCoords()10var table = document.getElementById('table');11table.setCellByCoords(0, 0, 'new cell content');12### getCellByRowCol()13var table = document.getElementById('table');14var cell = table.getCellByRowCol(0, 0);15### setCellByRowCol()16var table = document.getElementById('table');17table.setCellByRowCol(0, 0, 'new cell content');18### getCellByIndex()19var table = document.getElementById('table');20var cell = table.getCellByIndex(0);21### setCellByIndex()22var table = document.getElementById('table');23table.setCellByIndex(0, 'new cell content');24### getCellContent()

Full Screen

Using AI Code Generation

copy

Full Screen

1var wpt = require('webpage').create();2var range = wpt.getActiveRange();3var range2 = wpt.getRange();4var range3 = wpt.getRange();5var range4 = wpt.getRange();6var range5 = wpt.getRange();7var range6 = wpt.getRange();8var range7 = wpt.getRange();9var range8 = wpt.getRange();10var range9 = wpt.getRange();11var range10 = wpt.getRange();12var range11 = wpt.getRange();13var range12 = wpt.getRange();14var range13 = wpt.getRange();15var range14 = wpt.getRange();16var range15 = wpt.getRange();17var range16 = wpt.getRange();18var range17 = wpt.getRange();19var range18 = wpt.getRange();20var range19 = wpt.getRange();21var range20 = wpt.getRange();22var range21 = wpt.getRange();23var range22 = wpt.getRange();24var range23 = wpt.getRange();25var range24 = wpt.getRange();26var range25 = wpt.getRange();27var range26 = wpt.getRange();28var range27 = wpt.getRange();29var range28 = wpt.getRange();

Full Screen

Using AI Code Generation

copy

Full Screen

1let tableState = new wptbTableState();2let range = tableState.getActiveRange();3console.log(range);4### wptbTableState.getActiveCell()5let tableState = new wptbTableState();6let cell = tableState.getActiveCell();7console.log(cell);8### wptbTableState.getCellsCount()9let tableState = new wptbTableState();10let cellsCount = tableState.getCellsCount();11console.log(cellsCount);12### wptbTableState.getCells()13let tableState = new wptbTableState();14let cells = tableState.getCells();15console.log(cells);16### wptbTableState.getRows()17let tableState = new wptbTableState();18let rows = tableState.getRows();19console.log(rows);20### wptbTableState.getColumns()21let tableState = new wptbTableState();22let columns = tableState.getColumns();23console.log(columns);

Full Screen

Using AI Code Generation

copy

Full Screen

1var table = document.getElementById('table');2var tableState = new wptbTableState(table);3var range = tableState.getActiveRange();4var startCell = range.startCell;5var endCell = range.endCell;6var tableElement = range.tableElement;7console.log(startCell);8console.log(endCell);9console.log(tableElement);10var table = document.getElementById('table');11var tableState = new wptbTableState(table);12var range = tableState.getActiveRange();13var startCell = range.startCell;14var endCell = range.endCell;15var tableElement = range.tableElement;16console.log(startCell);17console.log(endCell);18console.log(tableElement);19var table = document.getElementById('table');20var tableState = new wptbTableState(table);21var range = tableState.getActiveRange();22var startCell = range.startCell;23var endCell = range.endCell;24var tableElement = range.tableElement;25console.log(startCell);26console.log(endCell);27console.log(tableElement);28var table = document.getElementById('table');29var tableState = new wptbTableState(table);30var range = tableState.getActiveRange();

Full Screen

Using AI Code Generation

copy

Full Screen

1var wpt = require('webpagetest');2var test = wpt('A.3b6a1a6c2e6f0b7c1f1f9f9c8c8d3e3', 'www.webpagetest.org');3test.getActiveRange(function(err, data) {4 if (err) {5 console.log(err);6 } else {7 console.log(data);8 }9});10### webpagetest(apiKey, server, options)11Default: `{}`12### webpagetest#runTest(url, options, callback)13Default: `{}`14### webpagetest#getLocations(callback)15### webpagetest#getTesters(callback)16### webpagetest#getStatus(callback)17### webpagetest#getTesters(callback)18### webpagetest#getActiveRange(callback)19### webpagetest#getTestResults(testId, callback)20### webpagetest#getTestStatus(testId, callback)

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run wpt automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful