Make outdent work again, maybe add list support
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 08 May 2011 13:30:40 -0600
changeset 96 bfd5189f4d1c
parent 95 4670ae4eec26
child 97 32d1bf688781
Make outdent work again, maybe add list support

List support is untested. I'm committing right now because it passes
all the old outdent tests, so I know it's less broken than what I had
before this commit.
autoimplementation.html
editcommands.html
implementation.js
preprocess
source.html
--- a/autoimplementation.html	Sun May 08 11:52:58 2011 -0600
+++ b/autoimplementation.html	Sun May 08 13:30:40 2011 -0600
@@ -1333,6 +1333,10 @@
 		if ("stack" in e) {
 			specCell.lastChild.textContent += " (stack: " + e.stack + ")";
 		}
+
+		// Don't bother comparing to localStorage, this is always wrong no
+		// matter what.
+		return;
 	}
 
 	var key = "execcommand-" + command
@@ -1340,13 +1344,23 @@
 		+ "-" + tr.firstChild.lastChild.textContent;
 
 	var oldValue = localStorage[key];
-	localStorage[key] = specCell.lastChild.textContent;
+	var newValue = specCell.lastChild.textContent;
 
-	if (oldValue !== null && oldValue !== undefined && oldValue != specCell.lastChild.textContent) {
+	if (oldValue !== null && oldValue !== undefined && oldValue !== newValue) {
 		specCell.lastChild.appendChild(document.createElement("div"));
 		specCell.lastChild.lastChild.style.color = "red";
 		specCell.lastChild.lastChild.style.fontWeight = "bold";
 		specCell.lastChild.lastChild.textContent = "Note, last run produced different markup: " + oldValue;
+
+		var button = document.createElement("button");
+		specCell.lastChild.lastChild.appendChild(button);
+		button.textContent = "Store new result";
+		button.onclick = (function(key, val, button) { return function() {
+			localStorage[key] = val;
+			button.parentNode.removeChild(button);
+		} })(key, newValue, button);
+	} else {
+		localStorage[key] = newValue;
 	}
 }
 
--- a/editcommands.html	Sun May 08 11:52:58 2011 -0600
+++ b/editcommands.html	Sun May 08 13:30:40 2011 -0600
@@ -175,13 +175,20 @@
 
   <li>I'm sloppy about handling things like nodes that don't descend from a
   Document, comments that are children of a Document, that sort of thing.  Not
-  essential for prototyping, but needs to be cleaned up eventually.
+  essential for prototyping, but needs to be cleaned up eventually.  Mostly we
+  should be able to avoid the problems by requiring that everything be
+  editable, since that immediately means it has to descend from an element or
+  Document (and cannot be parentless itself).
 
   <li>I haven't paid much attention to performance.  The algorithms here aren't
   performance-critical in most cases, but I might have accidentally included
   some algorithms that are too slow anyway on large pages.  Generally I haven't
   worried about throwing nodes away and recreating them multiple times or
   things like that, as long as it produces the correct result.
+
+  <li>I need to pay more attention to whitespace-only nodes.  In most cases
+  these will have no visual effect, but they'll make many algorithms behave
+  differently: decomposing a range, block-extending, etc.
 </ul>
 
 <p class=XXX>A variety of other issues are also noted in the text, formatted
@@ -1956,8 +1963,6 @@
 
 <dd><strong>Action</strong>:
 
-<p class=XXX>List support is preliminary &ndash; I'm working on it right now.
-
 <p class=XXX>Handle corner cases: endpoints are detached, documents, document
 fragments, html/body, head or things in head . . .
 
@@ -2052,6 +2057,8 @@
 <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>:
 
 <ol>
+  <li>If <var title="">node list</var> is empty, do nothing and abort these steps.
+
   <li>Let <var title="">first node</var> be the first member of <var title="">node list</var>.
 
   <li>If <var title="">first node</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>:
@@ -2548,9 +2555,16 @@
 
 <dd><strong>Action</strong>:
 
-<p class=XXX>Does not handle lists at all (this is item #1 on my to-do list).
-
 <ol>
+  <li>Let <var title="">items</var> be a list of all <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>s that are
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container title="ancestor container">ancestor containers</a> of the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a> and/or <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-end title=concept-range-end>end</a>
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-boundary-point-node title=concept-boundary-point-node>node</a>.
+
+  <li>For each <var title="">item</var> in <var title="">items</var>, <a href=#normalize-sublists>normalize
+  sublists</a> of <var title="">item</var>.
+  <!-- This overnormalizes, but it seems like the simplest solution for now.
+  -->
+
   <li><a href=#block-extend>Block-extend</a> the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>, and let <var title="">new range</var> be
   the result.
 
@@ -2575,12 +2589,115 @@
 * <blockquote style="margin: 0 40px"> and <div style="margin: 0 40px"> (spec)
 * Other random things with display: block whose left or right margin was
   increased by 40px (CSS Firefox 4.0)
+
+For discussion on the list-related stuff, see the comment for
+insertOrderedList.
 -->
 <ol>
   <li>If <var title="">node</var> is not <a href=#editable>editable</a>, abort these steps.
 
   <p class=XXX>Handle this better for nested editable/non-editable.
 
+  <!-- First we handle the list case, because it's simpler. -->
+
+  <li>If <var title="">node</var> is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code> with no attributes except
+  possibly <code class=external data-anolis-spec=html title=attr-ol-reversed><a href=http://www.whatwg.org/html/#attr-ol-reversed>reversed</a></code>,
+  <code class=external data-anolis-spec=html title=attr-ol-start><a href=http://www.whatwg.org/html/#attr-ol-start>start</a></code>, and/or <code class=external data-anolis-spec=html title=attr-ol-type><a href=http://www.whatwg.org/html/#attr-ol-type>type</a></code>; or is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or
+  <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code> whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is also an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>:
+  <!-- If it has reversed/start/type, those can't reasonably be preserved no
+  matter what.  Not clear how it's supposed to get them, anyway. -->
+
+  <div class=XXX>
+  <p>We don't handle a case like
+
+  </p><xmp><ol><ol style="color: red"><li>foo<li>bar</ol><li>baz</xmp>
+
+  <p>If the inner <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> is selected to be outdented, "foo" and "bar" will stop
+  being red.  It seems nontrivial to handle this case in general, since we
+  can't group <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code>s.  If the list we're outdenting is a child of a non-list,
+  then we can just change it to a div.
+  </div>
+
+  <ol>
+    <li>While <var title="">node</var> has <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a>:
+
+    <ol>
+      <li>Let <var title="">child</var> be the first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">node</var>.
+
+      <li>If <var title="">child</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> and the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of
+      <var title="">node</var> is not an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, <a href=#set-the-tag-name>set the tag name</a>
+      of <var title="">child</var> to "div".
+
+      <li>Insert <var title="">child</var> into the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <var title="">node</var>
+      immediately before <var title="">node</var>, <a href=#preserving-ranges>preserving ranges</a>.
+    </ol>
+
+    <li>Remove <var title="">node</var> from its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
+
+    <li>Abort these steps.
+  </ol>
+
+  <li>If <var title="">node</var> is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>:
+  <!-- But it has non-list attributes, and its parent is not an ol/ul, or we'd
+  have handled it in the previous step. -->
+
+  <ol>
+    <li>Unset the <code class=external data-anolis-spec=html title=attr-ol-reversed><a href=http://www.whatwg.org/html/#attr-ol-reversed>reversed</a></code>, <code class=external data-anolis-spec=html title=attr-ol-start><a href=http://www.whatwg.org/html/#attr-ol-start>start</a></code>, and <code class=external data-anolis-spec=html title=attr-ol-type><a href=http://www.whatwg.org/html/#attr-ol-type>type</a></code> attributes of <var title="">node</var>, if any are
+    set.
+
+    <li><a href=#set-the-tag-name>Set the tag name</a> of <var title="">node</var> to "div".
+
+    <li>For each <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> <var title="">child</var> of <var title="">node</var>, unset
+    the <code class=external data-anolis-spec=html title=attr-li-value><a href=http://www.whatwg.org/html/#attr-li-value>value</a></code> attribute
+    of <var title="">child</var> if set, then <a href=#set-the-tag-name>set the tag name</a> of
+    <var title="">child</var> to "div".
+
+    <li>Abort these steps.
+  </ol>
+
+  <li>If <var title="">node</var> is an <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>:
+
+  <ol>
+    <li>Let <var title="">parent</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <var title="">node</var>.
+
+    <li>If <var title="">parent</var>'s <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is not an <code class=external data-anolis-spec=html title="the ol element"><a href=http://www.whatwg.org/html/#the-ol-element>ol</a></code> or <code class=external data-anolis-spec=html title="the ul element"><a href=http://www.whatwg.org/html/#the-ul-element>ul</a></code>, unset the
+    <code class=external data-anolis-spec=html title=attr-li-value><a href=http://www.whatwg.org/html/#attr-li-value>value</a></code> attribute of
+    <var title="">node</var> if set, then <a href=#set-the-tag-name>set the tag name</a> of
+    <var title="">node</var> to "div".
+
+    <li>If <var title="">node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-previousSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-previoussibling>previousSibling</a></code> is null, insert
+    <var title="">node</var> into the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <var title="">parent</var> immediately before
+    <var title="">parent</var>, <a href=#preserving-ranges>preserving ranges</a>, then abort these steps.
+
+    <li>If <var title="">node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code> is null, insert <var title="">node</var>
+    into the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <var title="">parent</var> immediately after
+    <var title="">parent</var>, <a href=#preserving-ranges>preserving ranges</a>, then abort these steps.
+
+    <!-- Otherwise, have to split up the list. -->
+
+    <li>Let <var title="">new parent</var> be the result of calling <code class=external data-anolis-spec=domcore title=dom-Node-cloneNode><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-clonenode>cloneNode(false)</a></code>
+    on <var title="">parent</var>.
+
+    <p class=XXX>This will duplicate id's, as well as other bad things.  Do we
+    care?  We don't want to not copy attributes at all, because that wouldn't
+    copy style attributes, and Firefox in CSS mode will actually add style
+    attributes to any elements it feels like.  If we do exclude id's, even
+    though they'd only occur if someone manually added them, do we want to
+    exclude other things like itemid or accesskey or . . .
+
+    <li>Insert <var title="">new parent</var> into the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <var title="">parent</var>
+    immediately after <var title="">parent</var>.
+
+    <li>While <var title="">node</var>'s <code class=external data-anolis-spec=domcore title=dom-Node-nextSibling><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-nextsibling>nextSibling</a></code> is not null, insert the last
+    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">parent</var> as the first <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">new
+    parent</var>, <a href=#preserving-ranges>preserving ranges</a>.
+
+    <li>Insert <var title="">node</var> into the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of <var title="">parent</var>
+    immediately after <var title="">parent</var>.
+
+    <li>Abort these steps.
+  </ol>
+
   <!-- The easy case is when the whole element is indented.  In this case we
   remove the whole thing indiscriminately.  In the case of blockquotes
   created by IE, this might change the direction of some children, but then
@@ -2698,12 +2815,17 @@
 
     <li>Remove the last member of <var title="">ancestor list</var>.
 
-    <li>Let <var title="">children</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>children</a> of <var title="">current
-    ancestor</var>.
-
-    <li>For each <var title="">child</var> in <var title="">children</var>, if <var title="">child</var>
-    is neither <var title="">node</var> nor the last member of <var title="">ancestor list</var>,
-    <a href=#indent>indent</a> <var title="">child</var>.
+    <li>Let <var title="">target</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-child title=concept-tree-child>child</a> of <var title="">current ancestor</var>
+    that is equal to either <var title="">node</var> or the last member of <var title="">ancestor
+    list</var>.
+
+    <li>Let <var title="">preceding siblings</var> be the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-preceding-sibling title=concept-tree-preceding-sibling>preceding siblings</a> of
+    <var title="">target</var>, and let <var title="">following siblings</var> be the
+    <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-following-sibling title=concept-tree-following-sibling>following siblings</a> of <var title="">target</var>.
+
+    <li><a href=#indent>Indent</a> <var title="">preceding siblings</var>.
+
+    <li><a href=#indent>Indent</a> <var title="">following siblings</var>.
   </ol>
 
   <li><a href=#outdent>Outdent</a> <var title="">original ancestor</var>.
--- a/implementation.js	Sun May 08 11:52:58 2011 -0600
+++ b/implementation.js	Sun May 08 13:30:40 2011 -0600
@@ -2438,6 +2438,32 @@
 		break;
 
 		case "outdent":
+		// "Let items be a list of all lis that are ancestor containers of the
+		// range's start and/or end node."
+		//
+		// Has to be in tree order, remember!
+		var items = [];
+		for (var node = range.endContainer; node != range.commonAncestorContainer; node = node.parentNode) {
+			if (isHtmlElement(node, "LI")) {
+				items.unshift(node);
+			}
+		}
+		for (var node = range.startContainer; node != range.commonAncestorContainer; node = node.parentNode) {
+			if (isHtmlElement(node, "LI")) {
+				items.unshift(node);
+			}
+		}
+		for (var node = range.commonAncestorContainer; node; node = node.parentNode) {
+			if (isHtmlElement(node, "LI")) {
+				items.unshift(node);
+			}
+		}
+
+		// "For each item in items, normalize sublists of item."
+		for (var i = 0; i < items.length; i++) {
+			normalizeSublists(items[i]);
+		}
+
 		// "Block-extend the range, and let new range be the result."
 		var newRange = blockExtendRange(range);
 
@@ -2628,6 +2654,11 @@
 }
 
 function indentNodes(nodeList) {
+	// "If node list is empty, do nothing and abort these steps."
+	if (!nodeList.length) {
+		return;
+	}
+
 	// "Let first node be the first member of node list."
 	var firstNode = nodeList[0];
 
@@ -2744,6 +2775,118 @@
 		return;
 	}
 
+	// "If node is an ol or ul with no attributes except possibly reversed,
+	// start, and/or type; or is an ol or ul whose parent is also an ol or ul:"
+	if ((isHtmlElement(node, "OL") || isHtmlElement(node, "UL"))
+	&& (
+		isHtmlElement(node.parentNode, "OL")
+		|| isHtmlElement(node.parentNode, "UL")
+		|| [].every.call(node.attributes, function (attr) { return ["reversed", "start", "type"].indexOf(attr.name) != -1 })
+	)) {
+		// "While node has children:"
+		while (node.hasChildNodes()) {
+			// "Let child be the first child of node."
+			var child = node.firstChild;
+
+			// "If child is an li and the parent of node is not an ol or ul,
+			// set the tag name of child to "div"."
+			if (isHtmlElement(child, "LI")
+			&& !isHtmlElement(node.parentNode, "OL")
+			&& !isHtmlElement(node.parentNode, "UL")) {
+				setTagName(child, "div");
+			}
+
+			// "Insert child into the parent of node immediately before node,
+			// preserving ranges."
+			movePreservingRanges(child, node.parentNode, getNodeIndex(node));
+		}
+
+		// "Remove node from its parent."
+		node.parentNode.removeChild(node);
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "If node is an ol or ul:"
+	if (isHtmlElement(node, "OL")
+	|| isHtmlElement(node, "UL")) {
+		// "Unset the reversed, start, and type attributes of node, if any are
+		// set."
+		node.removeAttribute("reversed");
+		node.removeAttribute("start");
+		node.removeAttribute("type");
+
+		// "Set the tag name of node to "div"."
+		setTagName(node, "div");
+
+		// "For each li child child of node, unset the value attribute of child
+		// if set, then set the tag name of child to "div"."
+		for (var i = 0; i < node.childNodes.length; i++) {
+			var child = node.childNodes[i];
+
+			if (isHtmlElement(child, "LI")) {
+				child.removeAttribute("value");
+				setTagName(child, "div");
+			}
+		}
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "If node is an li whose parent is an ol or ul:"
+	if (isHtmlElement(node, "LI")
+	&& (isHtmlElement(node.parentNode, "OL")
+	|| isHtmlElement(node.parentNode, "UL"))) {
+		// "Let parent be the parent of node."
+		var parent_ = node.parentNode;
+
+		// "If parent's parent is not an ol or ul, unset the value attribute of
+		// node if set, then set the tag name of node to "div"."
+		if (!isHtmlElement(parent_.parentNode, "OL")
+		&& !isHtmlElement(parent_.parentNode, "UL")) {
+			node.removeAttribute("value");
+			setTagName(node, "div");
+		}
+
+		// "If node's previousSibling is null, insert node into the parent of
+		// parent immediately before parent, preserving ranges, then abort
+		// these steps."
+		if (!node.previousSibling) {
+			movePreservingRanges(node, parent_.parentNode, getNodeIndex(parent_));
+			return;
+		}
+
+		// "If node's nextSibling is null, insert node into the parent of
+		// parent immediately after parent, preserving ranges, then abort these
+		// steps."
+		if (!node.nextSibling) {
+			movePreservingRanges(node, parent_.parentNode, 1 + getNodeIndex(parent_));
+			return;
+		}
+
+		// "Let new parent be the result of calling cloneNode(false) on
+		// parent."
+		var newParent = parent_.cloneNode(false);
+
+		// "Insert new parent into the parent of parent immediately after
+		// parent."
+		parent_.parentNode.insertBefore(newParent, parent_.nextSibling);
+
+		// "While node's nextSibling is not null, insert the last child of
+		// parent as the first child of new parent, preserving ranges."
+		while (node.nextSibling) {
+			movePreservingRanges(parent_.lastChild, newParent, 0);
+		}
+
+		// "Insert node into the parent of parent immediately after parent."
+		parent_.parentNode.insertBefore(node, parent_.nextSibling);
+
+		// "Abort these steps."
+		return;
+	}
+
 	// "If node is an indentation element:"
 	if (isIndentationElement(node)) {
 		// "If node's last child and nextSibling are both inline nodes or its
@@ -2843,17 +2986,22 @@
 		// "Remove the last member of ancestor list."
 		currentAncestor = ancestorList.pop();
 
-		// "Let children be the children of current ancestor."
-		var children = [].slice.call(currentAncestor.childNodes);
-
-		// "For each child in children, if child is neither node nor the last
-		// member of ancestor list, indent child."
-		for (var i = 0; i < children.length; i++) {
-			var child = children[i];
-			if (child != node && child != ancestorList[ancestorList.length - 1]) {
-				indentNode(child);
-			}
-		}
+		// "Let target be the child of current ancestor that is equal to either
+		// node or the last member of ancestor list."
+		var target = node.parentNode == currentAncestor
+			? node
+			: ancestorList[ancestorList.length - 1];
+
+		// "Let preceding siblings be the preceding siblings of target, and let
+		// following siblings be the following siblings of target."
+		var precedingSiblings = [].slice.call(currentAncestor.childNodes, 0, getNodeIndex(target));
+		var followingSiblings = [].slice.call(currentAncestor.childNodes, 1 + getNodeIndex(target));
+
+		// "Indent preceding siblings."
+		indentNodes(precedingSiblings);
+
+		// "Indent following siblings."
+		indentNodes(followingSiblings);
 	}
 
 	// "Outdent original ancestor."
--- a/preprocess	Sun May 08 11:52:58 2011 -0600
+++ b/preprocess	Sun May 08 13:30:40 2011 -0600
@@ -18,6 +18,7 @@
     'bpnode': '<span data-anolis-spec=domrange title=concept-boundary-point-node>node</span>',
     'bpoffset': '<span data-anolis-spec=domrange title=concept-boundary-point-offset>offset</span>',
     'bpposition': '<span data-anolis-spec=domrange title=concept-bp-position>position</span>',
+    'cddata': '<code data-anolis-spec=domcore title=dom-CharacterData-data>data</code>',
     'child': '<span data-anolis-spec=domcore title=concept-tree-child>child</span>',
     'children': '<span data-anolis-spec=domcore title=concept-tree-child>children</span>',
     'collection': '<span data-anolis-spec=domcore title=concept-collection>collection</span>',
@@ -31,6 +32,7 @@
     'documentfragment': '<code data-anolis-spec=domcore>DocumentFragment</code>',
     'element': '<code data-anolis-spec=domcore>Element</code>',
     'em': '<code data-anolis-spec=html title="the em element">em</code>',
+    'followingsibling': '<span data-anolis-spec=domcore title="concept-tree-following-sibling">following sibling</span>',
     'font': '<code data-anolis-spec=html title=font>font</code>',
     'fontcolor': '<code data-anolis-spec=html title=dom-font-color>color</code>',
     'fontface': '<code data-anolis-spec=html title=dom-font-face>face</code>',
@@ -51,6 +53,7 @@
     'parent': '<span data-anolis-spec=domcore title=concept-tree-parent>parent</span>',
     'partiallycontained': '<span data-anolis-spec=domrange>partially contained</span>',
     'phrasingcontent': '<span data-anolis-spec=html>phrasing content</span>',
+    'precedingsibling': '<span data-anolis-spec=domcore title="concept-tree-preceding-sibling">preceding sibling</span>',
     'presentationalhint': '<span data-anolis-spec=html title="presentational hints">presentational hint</span>',
     'previoussibling': '<code data-anolis-spec=domcore title=dom-Node-previousSibling>previousSibling</code>',
     'processinginstruction': '<code data-anolis-spec=domcore>ProcessingInstruction</code>',
@@ -62,6 +65,7 @@
     's': '<code data-anolis-spec=html title="the s element">s</code>',
     'selection': '<code data-anolis-spec=domrange>Selection</code>',
     'sibling': '<span data-anolis-spec=domcore title=concept-tree-sibling>sibling</span>',
+    'spacecharacter': '<span data-anolis-spec=domcore title="space character">space character</span>',
     'span': '<code data-anolis-spec=html title="the span element">span</code>',
     'strike': '<code data-anolis-spec=html title="the strike element">strike</code>',
     'strong': '<code data-anolis-spec=html title="the strong element">strong</code>',
--- a/source.html	Sun May 08 11:52:58 2011 -0600
+++ b/source.html	Sun May 08 13:30:40 2011 -0600
@@ -161,13 +161,20 @@
 
   <li>I'm sloppy about handling things like nodes that don't descend from a
   Document, comments that are children of a Document, that sort of thing.  Not
-  essential for prototyping, but needs to be cleaned up eventually.
+  essential for prototyping, but needs to be cleaned up eventually.  Mostly we
+  should be able to avoid the problems by requiring that everything be
+  editable, since that immediately means it has to descend from an element or
+  Document (and cannot be parentless itself).
 
   <li>I haven't paid much attention to performance.  The algorithms here aren't
   performance-critical in most cases, but I might have accidentally included
   some algorithms that are too slow anyway on large pages.  Generally I haven't
   worried about throwing nodes away and recreating them multiple times or
   things like that, as long as it produces the correct result.
+
+  <li>I need to pay more attention to whitespace-only nodes.  In most cases
+  these will have no visual effect, but they'll make many algorithms behave
+  differently: decomposing a range, block-extending, etc.
 </ul>
 
 <p class=XXX>A variety of other issues are also noted in the text, formatted
@@ -1975,8 +1982,6 @@
 
 <dd><strong>Action</strong>:
 
-<p class=XXX>List support is preliminary &ndash; I'm working on it right now.
-
 <p class=XXX>Handle corner cases: endpoints are detached, documents, document
 fragments, html/body, head or things in head . . .
 
@@ -2071,6 +2076,8 @@
 [[nodes]]:
 
 <ol>
+  <li>If <var>node list</var> is empty, do nothing and abort these steps.
+
   <li>Let <var>first node</var> be the first member of <var>node list</var>.
 
   <li>If <var>first node</var>'s [[parent]] is an [[ol]] or [[ul]]:
@@ -2590,9 +2597,16 @@
 
 <dd><strong>Action</strong>:
 
-<p class=XXX>Does not handle lists at all (this is item #1 on my to-do list).
-
 <ol>
+  <li>Let <var>items</var> be a list of all [[li]]s that are
+  [[ancestorcontainers]] of the [[range]]'s [[rangestart]] and/or [[rangeend]]
+  [[bpnode]].
+
+  <li>For each <var>item</var> in <var>items</var>, <span>normalize
+  sublists</span> of <var>item</var>.
+  <!-- This overnormalizes, but it seems like the simplest solution for now.
+  -->
+
   <li><span>Block-extend</span> the [[range]], and let <var>new range</var> be
   the result.
 
@@ -2617,12 +2631,120 @@
 * <blockquote style="margin: 0 40px"> and <div style="margin: 0 40px"> (spec)
 * Other random things with display: block whose left or right margin was
   increased by 40px (CSS Firefox 4.0)
+
+For discussion on the list-related stuff, see the comment for
+insertOrderedList.
 -->
 <ol>
   <li>If <var>node</var> is not <span>editable</span>, abort these steps.
 
   <p class=XXX>Handle this better for nested editable/non-editable.
 
+  <!-- First we handle the list case, because it's simpler. -->
+
+  <li>If <var>node</var> is an [[ol]] or [[ul]] with no attributes except
+  possibly <code data-anolis-spec=html title=attr-ol-reversed>reversed</code>,
+  <code data-anolis-spec=html title=attr-ol-start>start</code>, and/or <code
+  data-anolis-spec=html title=attr-ol-type>type</code>; or is an [[ol]] or
+  [[ul]] whose [[parent]] is also an [[ol]] or [[ul]]:
+  <!-- If it has reversed/start/type, those can't reasonably be preserved no
+  matter what.  Not clear how it's supposed to get them, anyway. -->
+
+  <div class=XXX>
+  <p>We don't handle a case like
+
+  <xmp><ol><ol style="color: red"><li>foo<li>bar</ol><li>baz</xmp>
+
+  <p>If the inner [[ol]] is selected to be outdented, "foo" and "bar" will stop
+  being red.  It seems nontrivial to handle this case in general, since we
+  can't group [[li]]s.  If the list we're outdenting is a child of a non-list,
+  then we can just change it to a div.
+  </div>
+
+  <ol>
+    <li>While <var>node</var> has [[children]]:
+
+    <ol>
+      <li>Let <var>child</var> be the first [[child]] of <var>node</var>.
+
+      <li>If <var>child</var> is an [[li]] and the [[parent]] of
+      <var>node</var> is not an [[ol]] or [[ul]], <span>set the tag name</span>
+      of <var>child</var> to "div".
+
+      <li>Insert <var>child</var> into the [[parent]] of <var>node</var>
+      immediately before <var>node</var>, <span>preserving ranges</span>.
+    </ol>
+
+    <li>Remove <var>node</var> from its [[parent]].
+
+    <li>Abort these steps.
+  </ol>
+
+  <li>If <var>node</var> is an [[ol]] or [[ul]]:
+  <!-- But it has non-list attributes, and its parent is not an ol/ul, or we'd
+  have handled it in the previous step. -->
+
+  <ol>
+    <li>Unset the <code data-anolis-spec=html
+    title=attr-ol-reversed>reversed</code>, <code data-anolis-spec=html
+    title=attr-ol-start>start</code>, and <code data-anolis-spec=html
+    title=attr-ol-type>type</code> attributes of <var>node</var>, if any are
+    set.
+
+    <li><span>Set the tag name</span> of <var>node</var> to "div".
+
+    <li>For each [[li]] [[child]] <var>child</var> of <var>node</var>, unset
+    the <code data-anolis-spec=html title=attr-li-value>value</code> attribute
+    of <var>child</var> if set, then <span>set the tag name</span> of
+    <var>child</var> to "div".
+
+    <li>Abort these steps.
+  </ol>
+
+  <li>If <var>node</var> is an [[li]] whose [[parent]] is an [[ol]] or [[ul]]:
+
+  <ol>
+    <li>Let <var>parent</var> be the [[parent]] of <var>node</var>.
+
+    <li>If <var>parent</var>'s [[parent]] is not an [[ol]] or [[ul]], unset the
+    <code data-anolis-spec=html title=attr-li-value>value</code> attribute of
+    <var>node</var> if set, then <span>set the tag name</span> of
+    <var>node</var> to "div".
+
+    <li>If <var>node</var>'s [[previoussibling]] is null, insert
+    <var>node</var> into the [[parent]] of <var>parent</var> immediately before
+    <var>parent</var>, <span>preserving ranges</span>, then abort these steps.
+
+    <li>If <var>node</var>'s [[nextsibling]] is null, insert <var>node</var>
+    into the [[parent]] of <var>parent</var> immediately after
+    <var>parent</var>, <span>preserving ranges</span>, then abort these steps.
+
+    <!-- Otherwise, have to split up the list. -->
+
+    <li>Let <var>new parent</var> be the result of calling <code
+    data-anolis-spec=domcore title=dom-Node-cloneNode>cloneNode(false)</code>
+    on <var>parent</var>.
+
+    <p class=XXX>This will duplicate id's, as well as other bad things.  Do we
+    care?  We don't want to not copy attributes at all, because that wouldn't
+    copy style attributes, and Firefox in CSS mode will actually add style
+    attributes to any elements it feels like.  If we do exclude id's, even
+    though they'd only occur if someone manually added them, do we want to
+    exclude other things like itemid or accesskey or . . .
+
+    <li>Insert <var>new parent</var> into the [[parent]] of <var>parent</var>
+    immediately after <var>parent</var>.
+
+    <li>While <var>node</var>'s [[nextsibling]] is not null, insert the last
+    [[child]] of <var>parent</var> as the first [[child]] of <var>new
+    parent</var>, <span>preserving ranges</span>.
+
+    <li>Insert <var>node</var> into the [[parent]] of <var>parent</var>
+    immediately after <var>parent</var>.
+
+    <li>Abort these steps.
+  </ol>
+
   <!-- The easy case is when the whole element is indented.  In this case we
   remove the whole thing indiscriminately.  In the case of blockquotes
   created by IE, this might change the direction of some children, but then
@@ -2740,12 +2862,17 @@
 
     <li>Remove the last member of <var>ancestor list</var>.
 
-    <li>Let <var>children</var> be the [[children]] of <var>current
-    ancestor</var>.
-
-    <li>For each <var>child</var> in <var>children</var>, if <var>child</var>
-    is neither <var>node</var> nor the last member of <var>ancestor list</var>,
-    <span>indent</span> <var>child</var>.
+    <li>Let <var>target</var> be the [[child]] of <var>current ancestor</var>
+    that is equal to either <var>node</var> or the last member of <var>ancestor
+    list</var>.
+
+    <li>Let <var>preceding siblings</var> be the [[precedingsiblings]] of
+    <var>target</var>, and let <var>following siblings</var> be the
+    [[followingsiblings]] of <var>target</var>.
+
+    <li><span>Indent</span> <var>preceding siblings</var>.
+
+    <li><span>Indent</span> <var>following siblings</var>.
   </ol>
 
   <li><span>Outdent</span> <var>original ancestor</var>.