Using CommonMark's 'tight' and 'loose' lists
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index ee8ada4..6111f55 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -60,6 +60,10 @@
   /// Index of the current line.
   int _pos = 0;
 
+  /// Whether the parser has encountered a blank line between two block-level
+  /// elements.
+  bool encounteredBlankLine = false;
+
   /// The collection of built-in block parsers.
   final List<BlockSyntax> standardBlockSyntaxes = [
     const EmptyBlockSyntax(),
@@ -128,6 +132,21 @@
     if (next == null) return false;
     return regex.firstMatch(next) != null;
   }
+
+  List<Node> parseLines() {
+    var blocks = <Node>[];
+    while (!isDone) {
+      for (var syntax in blockSyntaxes) {
+        if (syntax.canParse(this)) {
+          var block = syntax.parse(this);
+          if (block != null) blocks.add(block);
+          break;
+        }
+      }
+    }
+
+    return blocks;
+  }
 }
 
 abstract class BlockSyntax {
@@ -145,7 +164,7 @@
   Node parse(BlockParser parser);
 
   List<String> parseChildLines(BlockParser parser) {
-    // Grab all of the lines that form the blockquote, stripping off the ">".
+    // Grab all of the lines that form the block element.
     var childLines = <String>[];
 
     while (!parser.isDone) {
@@ -185,6 +204,7 @@
   const EmptyBlockSyntax();
 
   Node parse(BlockParser parser) {
+    parser.encounteredBlankLine = true;
     parser.advance();
 
     // Don't actually emit anything.
@@ -537,91 +557,53 @@
     }
 
     endItem();
-    determineBlockItems(items);
     var itemNodes = <Node>[];
 
+    var anyEmptyLines = removeTrailingEmptyLines(items);
+    var anyEmptyLinesBetweenBlocks = false;
+
     for (var item in items) {
-      if (item.forceBlock) {
-        // Block list item.
-        var children = parser.document.parseLines(item.lines);
-        itemNodes.add(new Element('li', children));
-      } else {
-        // Raw list item.
-        var contents = parser.document.parseInline(item.lines[0]);
-        itemNodes.add(new Element('li', contents));
+      var itemParser = new BlockParser(item.lines, parser.document);
+      var children = itemParser.parseLines();
+      itemNodes.add(new Element('li', children));
+      anyEmptyLinesBetweenBlocks =
+          anyEmptyLinesBetweenBlocks || itemParser.encounteredBlankLine;
+    }
+
+    // Must strip paragraph tags if the list is "tight".
+    // http://spec.commonmark.org/0.25/#lists
+    var listIsTight = !anyEmptyLines && !anyEmptyLinesBetweenBlocks;
+
+    if (listIsTight) {
+      // We must post-process the list items, converting any top-level paragraph
+      // elements to just text elements.
+      for (var item in itemNodes) {
+        for (var i = 0; i < item.children.length; i++) {
+          var child = item.children[i];
+          if (child is Element && child.tag == 'p') {
+            item.children.removeAt(i);
+            item.children.insertAll(i, child.children);
+          }
+        }
       }
     }
 
     return new Element(listTag, itemNodes);
   }
 
-  /// Determines whether each item in [items] is a block item.
-  ///
-  /// Also removes any trailing empty lines and notes which items are separated
-  /// by empty lines.
-  void determineBlockItems(List items) {
-    // Markdown, because it hates us, specifies two kinds of list items. If you
-    // have a list like:
-    //
-    // * one
-    // * two
-    //
-    // Then it will insert the contents of the lines directly in the <li>, like:
-    //
-    // <ul>
-    //   <li>one</li>
-    //   <li>two</li>
-    // <ul>
-    //
-    // If, however, there are blank lines between the items, each is wrapped in
-    // paragraphs:
-    //
-    // * one
-    //
-    // * two
-    //
-    // <ul>
-    //   <li><p>one</p></li>
-    //   <li><p>two</p></li>
-    // <ul>
-    //
-    // In other words, sometimes we parse the contents of a list item like a
-    // block, and sometimes line an inline. The rules our parser implements are:
-    //
-    // - If it has more than one line, it's a block.
-    // - If the line matches any block parser (BLOCKQUOTE, HEADER, HR, INDENT,
-    //   UL, OL) it's a block. (This is for cases like "* > quote".)
-    // - If there was a blank line between this item and the previous one, it's
-    //   a block.
-    // - If there was a blank line between this item and the next one, it's a
-    //   block.
-    // - Otherwise, parse it as an inline.
-
-    // Remove any trailing empty lines and note which items are separated by
-    // empty lines. Do this before seeing which items are single-line so that
-    // trailing empty lines on the last item don't force it into being a block.
+  /// Removes any trailing empty lines and notes whether any items are separated
+  /// by such lines.
+  bool removeTrailingEmptyLines(List items) {
+    var anyEmpty = false;
     for (var i = 0; i < items.length; i++) {
-      for (var j = items[i].lines.length - 1; j > 0; j--) {
-        if (!_emptyPattern.hasMatch(items[i].lines[j])) break;
-
-        // Found an empty line. This item and the one after it are blocks.
+      while (_emptyPattern.hasMatch(items[i].lines.last)) {
         if (i < items.length - 1) {
-          items[i].forceBlock = true;
-          items[i + 1].forceBlock = true;
+          anyEmpty = true;
         }
         items[i].lines.removeLast();
       }
-
-      // Items with more than one line are block items.
-      items[i].forceBlock = items[i].forceBlock || items[i].lines.length > 1;
-
-      if (items[i].forceBlock) continue;
-
-      // Items (even one-lined items) that start with a block syntax are block
-      // items.
-      items[i].forceBlock =
-          blocksInList.any((p) => p.hasMatch(items[i].lines[0]));
     }
+    return anyEmpty;
   }
 }
 
diff --git a/pkgs/markdown/lib/src/document.dart b/pkgs/markdown/lib/src/document.dart
index 8720ff0..270fa80 100644
--- a/pkgs/markdown/lib/src/document.dart
+++ b/pkgs/markdown/lib/src/document.dart
@@ -73,22 +73,8 @@
   }
 
   /// Parses the given [lines] of Markdown to a series of AST nodes.
-  List<Node> parseLines(List<String> lines) {
-    var parser = new BlockParser(lines, this);
-
-    var blocks = <Node>[];
-    while (!parser.isDone) {
-      for (var syntax in parser.blockSyntaxes) {
-        if (syntax.canParse(parser)) {
-          var block = syntax.parse(parser);
-          if (block != null) blocks.add(block);
-          break;
-        }
-      }
-    }
-
-    return blocks;
-  }
+  List<Node> parseLines(List<String> lines) =>
+      new BlockParser(lines, this).parseLines();
 
   /// Parses the given inline Markdown [text] to a series of AST nodes.
   List<Node> parseInline(String text) => new InlineParser(text, this).parse();
diff --git a/pkgs/markdown/test/original/unordered_lists.unit b/pkgs/markdown/test/original/unordered_lists.unit
index 90f305a..db40868 100644
--- a/pkgs/markdown/test/original/unordered_lists.unit
+++ b/pkgs/markdown/test/original/unordered_lists.unit
@@ -35,7 +35,8 @@
 *   three
 
 <<<
-<ul><li>one</li><li>
+<ul><li>
+<p>one</p></li><li>
 <p>two</p></li><li>
 <p>three</p></li></ul>
 >>> do not force paragraph if item is already block
@@ -66,9 +67,8 @@
 *   three
 
 <<<
-<ul><li>
-<p>one
-two</p></li><li>three</li></ul>
+<ul><li>one
+two</li><li>three</li></ul>
 >>> can nest lists
 *   one
     * nested one