Improve indentation of lists
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Thu, 05 May 2011 13:09:04 -0600
changeset 87 b5cb0161423b
parent 86 667696ef2073
child 88 381a4d2e48ad
Improve indentation of lists

This moves <ol>/<ul> children of potentially-to-be-indented <li>s to be
their siblings, so they don't get indented along with them.
editcommands.html
implementation.js
source.html
--- a/editcommands.html	Thu May 05 12:00:19 2011 -0600
+++ b/editcommands.html	Thu May 05 13:09:04 2011 -0600
@@ -59,12 +59,13 @@
  <li><a href=#issues><span class=secno>2 </span>Issues</a></li>
  <li><a href=#definitions><span class=secno>3 </span>Definitions</a></li>
  <li><a href=#decomposing-a-range-into-nodes><span class=secno>4 </span>Decomposing a range into nodes</a></li>
- <li><a href=#block-extending-a-range><span class=secno>5 </span>Block-extending a range</a></li>
- <li><a href="#clearing-an-element's-value"><span class=secno>6 </span>Clearing an element's value</a></li>
- <li><a href=#pushing-down-values><span class=secno>7 </span>Pushing down values</a></li>
- <li><a href=#forcing-the-value-of-a-node><span class=secno>8 </span>Forcing the value of a node</a></li>
- <li><a href=#setting-the-value-of-a-node><span class=secno>9 </span>Setting the value of a node</a></li>
- <li><a href=#commands><span class=secno>10 </span>Commands</a></li>
+ <li><a href=#normalizing-sublists-in-a-range><span class=secno>5 </span>Normalizing sublists in a range</a></li>
+ <li><a href=#block-extending-a-range><span class=secno>6 </span>Block-extending a range</a></li>
+ <li><a href="#clearing-an-element's-value"><span class=secno>7 </span>Clearing an element's value</a></li>
+ <li><a href=#pushing-down-values><span class=secno>8 </span>Pushing down values</a></li>
+ <li><a href=#forcing-the-value-of-a-node><span class=secno>9 </span>Forcing the value of a node</a></li>
+ <li><a href=#setting-the-value-of-a-node><span class=secno>10 </span>Setting the value of a node</a></li>
+ <li><a href=#commands><span class=secno>11 </span>Commands</a></li>
  <li><a class=no-num href=#references>References</a></li>
  <li><a class=no-num href=#acknowledgements>Acknowledgements</a></ol>
 <!--end-toc-->
@@ -599,7 +600,60 @@
 </ol>
 
 
-<h2 id=block-extending-a-range><span class=secno>5 </span>Block-extending a range</h2>
+<h2 id=normalizing-sublists-in-a-range><span class=secno>5 </span>Normalizing sublists in a range</h2>
+<p>When a user agent is to <dfn id=normalize-sublists>normalize sublists</dfn> in a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>
+<var title="">range</var>, it must run the following steps:
+
+<ol>
+  <li>Let <var title="">items</var> be a list of <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>, initially empty.
+
+  <li>If there is some <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> element that is an <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#ancestor-container>ancestor container</a> of <var title="">range</var>'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 <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>nodes</a>, append the last such element in
+  <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#tree-order>tree order</a> to <var title="">items</var>.
+  <!-- We don't want to add more than that, because it would disturb upper
+  parts of the DOM that we don't actually need to change. -->
+
+  <li>Append to <var title="">items</var> every <code class=external data-anolis-spec=html title="the li element"><a href=http://www.whatwg.org/html/#the-li-element>li</a></code> element that is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> or
+  <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#partially-contained>partially contained</a> in <var title="">range</var>.
+
+  <li>For each <var title="">item</var> in <var title="">items</var>:
+
+  <ol>
+    <li>Let <var title="">new item</var> be null.
+
+    <li>While <var title="">item</var> has 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 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>:
+
+    <ol>
+      <li>Let <var title="">child</var> be 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="">item</var>.
+
+      <li>If <var title="">child</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>, or <var title="">new item</var> is
+      null and <var title="">child</var> is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#text>Text</a></code> node whose <code class=external data-anolis-spec=domcore title=dom-CharacterData-data><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-characterdata-data>data</a></code>
+      consists of zero of more <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#space-character title="space
+      character">space characters</a>:
+
+      <ol>
+        <li>Set <var title="">new item</var> to null.
+
+        <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="">item</var>
+        immediately following <var title="">item</var>, <a href=#preserving-ranges>preserving ranges</a>.
+      </ol>
+
+      <li>Otherwise:
+
+      <ol>
+        <li>If <var title="">new item</var> is null, let <var title="">new item</var> be the
+        result of calling <code class=external data-anolis-spec=domcore title=dom-Document-createElement><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-document-createelement>createElement("li")</a></code> on the
+        <code class=external data-anolis-spec=domcore title=dom-Node-ownerDocument><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-ownerdocument>ownerDocument</a></code> of <var title="">item</var>, then insert <var title="">new item</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="">item</var> immediately after
+        <var title="">item</var>.
+
+        <li>Insert <var title="">child</var> into <var title="">new item</var> as its 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>, <a href=#preserving-ranges>preserving ranges</a>.
+    </ol>
+  </ol>
+</ol>
+
+<h2 id=block-extending-a-range><span class=secno>6 </span>Block-extending a range</h2>
 <p>When a user agent is to <dfn id=block-extend>block-extend</dfn> a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>
 <var title="">range</var>, it must run the following steps:
 
@@ -660,7 +714,7 @@
 </ol>
 
 
-<h2 id="clearing-an-element's-value"><span class=secno>6 </span>Clearing an element's value</h2>
+<h2 id="clearing-an-element's-value"><span class=secno>7 </span>Clearing an element's value</h2>
 <p>When a user agent is to <dfn id=clear-the-value>clear the value</dfn> of an element
 <var title="">element</var>, it must run the following steps:
 
@@ -750,7 +804,7 @@
 </ol>
 
 
-<h2 id=pushing-down-values><span class=secno>7 </span>Pushing down values</h2>
+<h2 id=pushing-down-values><span class=secno>8 </span>Pushing down values</h2>
 <p>When a user agent is to <dfn id=push-down-values>push down values</dfn> to a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>
 <var title="">node</var>, given a command <var title="">command</var> and a new value
 <var title="">new value</var>, it must run the following steps:
@@ -868,7 +922,7 @@
 </ol>
 
 
-<h2 id=forcing-the-value-of-a-node><span class=secno>8 </span>Forcing the value of a node</h2>
+<h2 id=forcing-the-value-of-a-node><span class=secno>9 </span>Forcing the value of a node</h2>
 <p>When a user agent is to <dfn id=force-the-value>force the value</dfn> of a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>
 <var title="">node</var>, given a command <var title="">command</var> and a new value
 <var title="">new value</var>, it must run the following steps.
@@ -1178,7 +1232,7 @@
 </ol>
 
 
-<h2 id=setting-the-value-of-a-node><span class=secno>9 </span>Setting the value of a node</h2>
+<h2 id=setting-the-value-of-a-node><span class=secno>10 </span>Setting the value of a node</h2>
 <p>When a user agent is to <dfn id=set-the-value>set the value</dfn> of a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a>
 <var title="">node</var>, it must run the following steps.  There are two inputs: a
 command <var title="">command</var> and a new value <var title="">new value</var>.
@@ -1332,7 +1386,7 @@
 </ol>
 
 
-<h2 id=commands><span class=secno>10 </span>Commands</h2>
+<h2 id=commands><span class=secno>11 </span>Commands</h2>
 <p>The <dfn id=execcommand() title=execCommand()><code>execCommand(<var title="">commandId</var>,
 <var title="">showUI</var>, <var title="">value</var>)</code></dfn> method on the
 <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> interface allows scripts to
@@ -1887,6 +1941,8 @@
 fragments, html/body, head or things in head . . .
 
 <ol>
+  <li><a href=#normalize-sublists>Normalize sublists</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>.
+
   <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.
 
@@ -2925,4 +2981,4 @@
 
 <script src=http://www.whatwg.org/specs/web-apps/current-work/dfn.js></script>
 <!-- vim: set expandtab shiftwidth=2 tabstop=2: -->
-</dl>
\ No newline at end of file
+</dl></ol>
\ No newline at end of file
--- a/implementation.js	Thu May 05 12:00:19 2011 -0600
+++ b/implementation.js	Thu May 05 13:09:04 2011 -0600
@@ -321,10 +321,14 @@
 
 
 // "An HTML element is an Element whose namespace is the HTML namespace."
-function isHtmlElement(node) {
+//
+// I allow an extra argument to more easily check whether something is a
+// particular HTML element, like isHtmlElement(node, "OL").
+function isHtmlElement(node, tag) {
 	return node
 		&& node.nodeType == Node.ELEMENT_NODE
-		&& isHtmlNamespace(node.namespaceURI);
+		&& isHtmlNamespace(node.namespaceURI)
+		&& (typeof tag == "undefined" || node.tagName == tag);
 }
 
 // "An inline node is either a Text node, or an Element whose "display"
@@ -1107,6 +1111,83 @@
 	return ret;
 }
 
+function normalizeSublists(range) {
+	// "Let items be a list of nodes, initially empty."
+	var items = [];
+
+	// "If there is some li element that is an ancestor container of range's
+	// start and end nodes, append the last such element in tree order to
+	// items."
+	for (
+		var node = range.commonAncestorContainer;
+		node;
+		node = node.parentNode
+	) {
+		if (isHtmlElement(node, "LI")) {
+			items.push(node);
+			break;
+		}
+	}
+
+	// "Append to items every li element that is contained or partially
+	// contained in range."
+	for (
+		var node = range.commonAncestorContainer;
+		isDescendant(node, range.commonAncestorContainer);
+		node = nextNode(node)
+	) {
+		if (!isHtmlElement(node, "LI")) {
+			continue;
+		}
+
+		if (isContained(node, range) || isPartiallyContained(node, range)) {
+			items.push(node);
+		}
+	}
+
+	// "For each item in items:"
+	for (var i = 0; i < items.length; i++) {
+		var item = items[i];
+
+		// "Let new item be null."
+		var newItem = null;
+
+		// "While item has an ol or ul child:"
+		while ([].some.call(item.childNodes, function (node) { return isHtmlElement(node, "OL") || isHtmlElement(node, "UL") })) {
+			// "Let child be the last child of item."
+			var child = item.lastChild;
+
+			// "If child is an ol or ul, or new item is null and child is a
+			// Text node whose data consists of zero of more space characters:"
+			if (isHtmlElement(child, "OL")
+			|| isHtmlElement(child, "UL")
+			|| (!newItem && child.nodeType == Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
+				// "Set new item to null."
+				newItem = null;
+
+				// "Insert child into the parent of item immediately following
+				// item, preserving ranges."
+				movePreservingRanges(child, item.parentNode, 1 + getNodeIndex(item));
+
+			// "Otherwise:"
+			} else {
+				// "If new item is null, let new item be the result of calling
+				// createElement("li") on the ownerDocument of item, then
+				// insert new item into the parent of item immediately after
+				// item."
+				if (!newItem) {
+					newItem = item.ownerDocument.createElement("li");
+					item.parentNode.insertBefore(newItem, item.nextSibling);
+				}
+
+				// "Insert child into new item as its first child, preserving
+				// ranges."
+				movePreservingRanges(child, newItem, 0);
+			}
+		}
+	}
+}
+
 function blockExtendRange(range) {
 	// "Let start node, start offset, end node, and end offset be the start
 	// and end nodes and offsets of the range."
@@ -2105,6 +2186,9 @@
 		break;
 
 		case "indent":
+		// "Normalize sublists in the range."
+		normalizeSublists(range);
+
 		// "Block-extend the range, and let new range be the result."
 		var newRange = blockExtendRange(range);
 
--- a/source.html	Thu May 05 12:00:19 2011 -0600
+++ b/source.html	Thu May 05 13:09:04 2011 -0600
@@ -594,6 +594,62 @@
 </ol>
 
 
+<h2>Normalizing sublists in a range</h2>
+<p>When a user agent is to <dfn>normalize sublists</dfn> in a [[range]]
+<var>range</var>, it must run the following steps:
+
+<ol>
+  <li>Let <var>items</var> be a list of [[nodes]], initially empty.
+
+  <li>If there is some [[li]] element that is an <span
+  data-anolis-spec=domrange>ancestor container</span> of <var>range</var>'s
+  [[rangestart]] and [[rangeend]] [[bpnodes]], append the last such element in
+  [[treeorder]] to <var>items</var>.
+  <!-- We don't want to add more than that, because it would disturb upper
+  parts of the DOM that we don't actually need to change. -->
+
+  <li>Append to <var>items</var> every [[li]] element that is [[contained]] or
+  [[partiallycontained]] in <var>range</var>.
+
+  <li>For each <var>item</var> in <var>items</var>:
+
+  <ol>
+    <li>Let <var>new item</var> be null.
+
+    <li>While <var>item</var> has an [[ol]] or [[ul]] [[child]]:
+
+    <ol>
+      <li>Let <var>child</var> be the last [[child]] of <var>item</var>.
+
+      <li>If <var>child</var> is an [[ol]] or [[ul]], or <var>new item</var> is
+      null and <var>child</var> is a [[text]] node whose <code
+      data-anolis-spec=domcore title=dom-CharacterData-data>data</code>
+      consists of zero of more <span data-anolis-spec=domcore title="space
+      character">space characters</span>:
+
+      <ol>
+        <li>Set <var>new item</var> to null.
+
+        <li>Insert <var>child</var> into the [[parent]] of <var>item</var>
+        immediately following <var>item</var>, <span>preserving ranges</span>.
+      </ol>
+
+      <li>Otherwise:
+
+      <ol>
+        <li>If <var>new item</var> is null, let <var>new item</var> be the
+        result of calling <code data-anolis-spec=domcore
+        title=dom-Document-createElement>createElement("li")</code> on the
+        [[ownerdocument]] of <var>item</var>, then insert <var>new item</var>
+        into the [[parent]] of <var>item</var> immediately after
+        <var>item</var>.
+
+        <li>Insert <var>child</var> into <var>new item</var> as its first
+        [[child]], <span>preserving ranges</span>.
+    </ol>
+  </ol>
+</ol>
+
 <h2>Block-extending a range</h2>
 <p>When a user agent is to <dfn>block-extend</dfn> a [[range]]
 <var>range</var>, it must run the following steps:
@@ -1905,6 +1961,8 @@
 fragments, html/body, head or things in head . . .
 
 <ol>
+  <li><span>Normalize sublists</span> in the [[range]].
+
   <li><span>Block-extend</span> the [[range]], and let <var>new range</var> be
   the result.