Browse Source

1、实现标签功能
2、实现删除用户,当删除一个用户时,该用户的所有信息将转义到超级管理员账户上

Minho 8 years ago
parent
commit
47687cc5b1
85 changed files with 13999 additions and 187 deletions
  1. 1 0
      commands/command.go
  2. 1 1
      controllers/book.go
  3. 2 1
      controllers/document.go
  4. 7 0
      controllers/home.go
  5. 60 0
      controllers/label.go
  6. 34 1
      controllers/manager.go
  7. 0 1
      docs/.gitignore
  8. 166 166
      models/book.go
  9. 1 1
      models/document.go
  10. 92 0
      models/label.go
  11. 65 0
      models/member.go
  12. 12 1
      routers/filter.go
  13. 3 0
      routers/router.go
  14. 55 0
      static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.css
  15. 646 0
      static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.js
  16. 50 0
      static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.less
  17. 5 0
      static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.min.js
  18. 0 0
      static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.min.js.map
  19. 44 2
      static/css/main.css
  20. 17 6
      vendor/github.com/PuerkitoBio/goquery/README.md
  21. 8 0
      vendor/github.com/russross/blackfriday/.gitignore
  22. 30 0
      vendor/github.com/russross/blackfriday/.travis.yml
  23. 29 0
      vendor/github.com/russross/blackfriday/LICENSE.txt
  24. 292 0
      vendor/github.com/russross/blackfriday/README.md
  25. 1450 0
      vendor/github.com/russross/blackfriday/block.go
  26. 1781 0
      vendor/github.com/russross/blackfriday/block_test.go
  27. 32 0
      vendor/github.com/russross/blackfriday/doc.go
  28. 949 0
      vendor/github.com/russross/blackfriday/html.go
  29. 1154 0
      vendor/github.com/russross/blackfriday/inline.go
  30. 1242 0
      vendor/github.com/russross/blackfriday/inline_test.go
  31. 332 0
      vendor/github.com/russross/blackfriday/latex.go
  32. 931 0
      vendor/github.com/russross/blackfriday/markdown.go
  33. 75 0
      vendor/github.com/russross/blackfriday/markdown_test.go
  34. 128 0
      vendor/github.com/russross/blackfriday/ref_test.go
  35. 400 0
      vendor/github.com/russross/blackfriday/smartypants.go
  36. 17 0
      vendor/github.com/russross/blackfriday/testdata/Amps and angle encoding.html
  37. 21 0
      vendor/github.com/russross/blackfriday/testdata/Amps and angle encoding.text
  38. 18 0
      vendor/github.com/russross/blackfriday/testdata/Auto links.html
  39. 13 0
      vendor/github.com/russross/blackfriday/testdata/Auto links.text
  40. 123 0
      vendor/github.com/russross/blackfriday/testdata/Backslash escapes.html
  41. 126 0
      vendor/github.com/russross/blackfriday/testdata/Backslash escapes.text
  42. 15 0
      vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.html
  43. 11 0
      vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.text
  44. 18 0
      vendor/github.com/russross/blackfriday/testdata/Code Blocks.html
  45. 14 0
      vendor/github.com/russross/blackfriday/testdata/Code Blocks.text
  46. 5 0
      vendor/github.com/russross/blackfriday/testdata/Code Spans.html
  47. 6 0
      vendor/github.com/russross/blackfriday/testdata/Code Spans.text
  48. 14 0
      vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.html
  49. 8 0
      vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.text
  50. 8 0
      vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines.html
  51. 8 0
      vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines.text
  52. 71 0
      vendor/github.com/russross/blackfriday/testdata/Horizontal rules.html
  53. 67 0
      vendor/github.com/russross/blackfriday/testdata/Horizontal rules.text
  54. 15 0
      vendor/github.com/russross/blackfriday/testdata/Inline HTML (Advanced).html
  55. 15 0
      vendor/github.com/russross/blackfriday/testdata/Inline HTML (Advanced).text
  56. 72 0
      vendor/github.com/russross/blackfriday/testdata/Inline HTML (Simple).html
  57. 69 0
      vendor/github.com/russross/blackfriday/testdata/Inline HTML (Simple).text
  58. 13 0
      vendor/github.com/russross/blackfriday/testdata/Inline HTML comments.html
  59. 13 0
      vendor/github.com/russross/blackfriday/testdata/Inline HTML comments.text
  60. 11 0
      vendor/github.com/russross/blackfriday/testdata/Links, inline style.html
  61. 12 0
      vendor/github.com/russross/blackfriday/testdata/Links, inline style.text
  62. 52 0
      vendor/github.com/russross/blackfriday/testdata/Links, reference style.html
  63. 71 0
      vendor/github.com/russross/blackfriday/testdata/Links, reference style.text
  64. 9 0
      vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.html
  65. 20 0
      vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.text
  66. 3 0
      vendor/github.com/russross/blackfriday/testdata/Literal quotes in titles.html
  67. 7 0
      vendor/github.com/russross/blackfriday/testdata/Literal quotes in titles.text
  68. 314 0
      vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.html
  69. 306 0
      vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.text
  70. 946 0
      vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.html
  71. 888 0
      vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.text
  72. 9 0
      vendor/github.com/russross/blackfriday/testdata/Nested blockquotes.html
  73. 5 0
      vendor/github.com/russross/blackfriday/testdata/Nested blockquotes.text
  74. 166 0
      vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.html
  75. 131 0
      vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.text
  76. 7 0
      vendor/github.com/russross/blackfriday/testdata/Strong and em together.html
  77. 7 0
      vendor/github.com/russross/blackfriday/testdata/Strong and em together.text
  78. 26 0
      vendor/github.com/russross/blackfriday/testdata/Tabs.html
  79. 21 0
      vendor/github.com/russross/blackfriday/testdata/Tabs.text
  80. 9 0
      vendor/github.com/russross/blackfriday/testdata/Tidyness.html
  81. 5 0
      vendor/github.com/russross/blackfriday/testdata/Tidyness.text
  82. 16 7
      views/book/setting.tpl
  83. 13 0
      views/home/index.tpl
  84. 65 0
      views/label/index.tpl
  85. 26 0
      views/manager/users.tpl

+ 1 - 0
commands/command.go

@@ -78,6 +78,7 @@ func RegisterModel() {
 		new(models.MemberToken),
 		new(models.DocumentHistory),
 		new(models.Migration),
+		new(models.Label),
 	)
 	migrate.RegisterMigration()
 }

+ 1 - 1
controllers/book.go

@@ -137,7 +137,7 @@ func (c *BookController) SaveBook()  {
 		comment_status = "closed"
 	}
 	if tag != ""{
-		tags := strings.Split(tag,";")
+		tags := strings.Split(tag,",")
 		if len(tags) > 10 {
 			c.JsonResult(6005,"最多允许添加10个标签")
 		}

+ 2 - 1
controllers/document.go

@@ -26,6 +26,7 @@ import (
 	"github.com/lifei6671/mindoc/models"
 	"github.com/lifei6671/mindoc/utils"
 	"github.com/lifei6671/mindoc/utils/wkhtmltopdf"
+	"github.com/russross/blackfriday"
 )
 
 //DocumentController struct.
@@ -126,7 +127,7 @@ func (c *DocumentController) Index() {
 	c.Data["Model"] = bookResult
 	c.Data["Result"] = template.HTML(tree)
 	c.Data["Title"] = "概要"
-	c.Data["Content"] = bookResult.Description
+	c.Data["Content"] = template.HTML( blackfriday.MarkdownBasic([]byte(bookResult.Description)))
 }
 
 //阅读文档.

+ 7 - 0
controllers/home.go

@@ -43,4 +43,11 @@ func (c *HomeController) Index() {
 
 	c.Data["Lists"] = books
 
+	labels ,totalCount,err := models.NewLabel().FindToPager(1,10)
+
+	if err != nil {
+		c.Data["Labels"] = make([]*models.Label,0)
+	}else{
+		c.Data["Labels"] = labels
+	}
 }

+ 60 - 0
controllers/label.go

@@ -0,0 +1,60 @@
+package controllers
+
+import (
+	"github.com/lifei6671/mindoc/models"
+	"github.com/astaxie/beego/orm"
+	"github.com/astaxie/beego"
+	"github.com/lifei6671/mindoc/conf"
+	"github.com/lifei6671/mindoc/utils"
+)
+
+type LabelController struct {
+	BaseController
+}
+
+func (c *LabelController) Index() {
+	c.Prepare()
+	c.TplName = "label/index.tpl"
+
+	//如果没有开启你们访问则跳转到登录
+	if !c.EnableAnonymous && c.Member == nil {
+		c.Redirect(beego.URLFor("AccountController.Login"),302)
+		return
+	}
+
+	labelName := c.Ctx.Input.Param(":key")
+	pageIndex,_ := c.GetInt("page",1)
+	if labelName == "" {
+		c.Abort("404")
+	}
+	_,err := models.NewLabel().FindFirst("label_name",labelName)
+
+	if err != nil {
+		if err == orm.ErrNoRows {
+			c.Abort("404")
+		}else{
+			beego.Error(err)
+			c.Abort("500")
+		}
+	}
+	member_id := 0
+	if c.Member != nil {
+		member_id = c.Member.MemberId
+	}
+	search_result,totalCount,err := models.NewBook().FindForLabelToPager(labelName,pageIndex,conf.PageSize,member_id)
+
+	if err != nil {
+		beego.Error(err)
+		return
+	}
+	if totalCount > 0 {
+		html := utils.GetPagerHtml(c.Ctx.Request.RequestURI, pageIndex, conf.PageSize, totalCount)
+
+		c.Data["PageHtml"] = html
+	}else {
+		c.Data["PageHtml"] = ""
+	}
+	c.Data["Lists"] = search_result
+
+	c.Data["LabelName"] = labelName
+}

+ 34 - 1
controllers/manager.go

@@ -186,6 +186,7 @@ func (c *ManagerController) ChangeMemberRole() {
 	c.JsonResult(0, "ok", member)
 }
 
+//编辑用户信息.
 func (c *ManagerController) EditMember() {
 	c.Prepare()
 	c.TplName = "manager/edit_users.tpl"
@@ -238,6 +239,38 @@ func (c *ManagerController) EditMember() {
 	c.Data["Model"] = member
 }
 
+//删除一个用户,并将该用户的所有信息转移到超级管理员上.
+func (c *ManagerController) DeleteMember()  {
+	c.Prepare()
+	member_id,_ := c.GetInt("id",0)
+
+	if member_id <= 0 {
+		c.JsonResult(404,"参数错误")
+	}
+
+	_ ,err := models.NewMember().Find(member_id)
+
+	if err != nil {
+		beego.Error(err)
+		c.JsonResult(500,"用户不存在")
+	}
+	superMember,err := models.NewMember().FindByFieldFirst("role",0)
+
+	if err != nil {
+		beego.Error(err)
+		c.JsonResult(5001,"未能找到超级管理员")
+	}
+
+	err = models.NewMember().Delete(member_id,superMember.MemberId)
+
+	if err != nil {
+		beego.Error(err)
+		c.JsonResult(5002,"删除失败")
+	}
+	c.JsonResult(0,"ok")
+}
+
+//项目列表.
 func (c *ManagerController) Books() {
 	c.Prepare()
 	c.TplName = "manager/books.tpl"
@@ -261,7 +294,7 @@ func (c *ManagerController) Books() {
 	c.Data["Lists"] = books
 }
 
-//编辑项目
+//编辑项目.
 func (c *ManagerController) EditBook() {
 	c.Prepare()
 

+ 0 - 1
docs/.gitignore

@@ -1 +0,0 @@
-# Created by .ignore support plugin (hsz.mobi)

+ 166 - 166
models/book.go

@@ -3,53 +3,55 @@ package models
 import (
 	"time"
 
-	"github.com/astaxie/beego/orm"
-	"github.com/lifei6671/mindoc/conf"
-	"github.com/astaxie/beego/logs"
 	"strings"
+
 	"github.com/astaxie/beego"
+	"github.com/astaxie/beego/logs"
+	"github.com/astaxie/beego/orm"
+	"github.com/lifei6671/mindoc/conf"
+	"fmt"
 )
 
 // Book struct .
 type Book struct {
-	BookId int		`orm:"pk;auto;unique;column(book_id)" json:"book_id"`
+	BookId int `orm:"pk;auto;unique;column(book_id)" json:"book_id"`
 	// BookName 项目名称.
-	BookName string		`orm:"column(book_name);size(500)" json:"book_name"`
+	BookName string `orm:"column(book_name);size(500)" json:"book_name"`
 	// Identify 项目唯一标识.
-	Identify string		`orm:"column(identify);size(100);unique" json:"identify"`
-	OrderIndex int		`orm:"column(order_index);type(int);default(0)" json:"order_index"`
+	Identify   string `orm:"column(identify);size(100);unique" json:"identify"`
+	OrderIndex int    `orm:"column(order_index);type(int);default(0)" json:"order_index"`
 	// Description 项目描述.
-	Description string	`orm:"column(description);size(2000)" json:"description"`
-	Label string		`orm:"column(label);size(500)" json:"label"`
+	Description string `orm:"column(description);size(2000)" json:"description"`
+	Label       string `orm:"column(label);size(500)" json:"label"`
 	// PrivatelyOwned 项目私有: 0 公开/ 1 私有
-	PrivatelyOwned int	`orm:"column(privately_owned);type(int);default(0)" json:"privately_owned"`
+	PrivatelyOwned int `orm:"column(privately_owned);type(int);default(0)" json:"privately_owned"`
 	// 当项目是私有时的访问Token.
-	PrivateToken string 	`orm:"column(private_token);size(500);null" json:"private_token"`
+	PrivateToken string `orm:"column(private_token);size(500);null" json:"private_token"`
 	//状态:0 正常/1 已删除
-	Status int 		`orm:"column(status);type(int);default(0)" json:"status"`
+	Status int `orm:"column(status);type(int);default(0)" json:"status"`
 	//默认的编辑器.
-	Editor string		`orm:"column(editor);size(50)" json:"editor"`
+	Editor string `orm:"column(editor);size(50)" json:"editor"`
 	// DocCount 包含文档数量.
-	DocCount int		`orm:"column(doc_count);type(int)" json:"doc_count"`
+	DocCount int `orm:"column(doc_count);type(int)" json:"doc_count"`
 	// CommentStatus 评论设置的状态:open 为允许所有人评论,closed 为不允许评论, group_only 仅允许参与者评论 ,registered_only 仅允许注册者评论.
-	CommentStatus string	`orm:"column(comment_status);size(20);default(open)" json:"comment_status"`
-	CommentCount int	`orm:"column(comment_count);type(int)" json:"comment_count"`
+	CommentStatus string `orm:"column(comment_status);size(20);default(open)" json:"comment_status"`
+	CommentCount  int    `orm:"column(comment_count);type(int)" json:"comment_count"`
 	//封面地址
-	Cover string 		`orm:"column(cover);size(1000)" json:"cover"`
+	Cover string `orm:"column(cover);size(1000)" json:"cover"`
 	//主题风格
-	Theme string 		`orm:"column(theme);size(255);default(default)" json:"theme"`
+	Theme string `orm:"column(theme);size(255);default(default)" json:"theme"`
 	// CreateTime 创建时间 .
-	CreateTime time.Time	`orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
-	MemberId int		`orm:"column(member_id);size(100)" json:"member_id"`
-	ModifyTime time.Time	`orm:"type(datetime);column(modify_time);null;auto_now" json:"modify_time"`
-	Version int64		`orm:"type(bigint);column(version)" json:"version"`
+	CreateTime time.Time `orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
+	MemberId   int       `orm:"column(member_id);size(100)" json:"member_id"`
+	ModifyTime time.Time `orm:"type(datetime);column(modify_time);null;auto_now" json:"modify_time"`
+	Version    int64     `orm:"type(bigint);column(version)" json:"version"`
 }
 
-
 // TableName 获取对应数据库表名.
 func (m *Book) TableName() string {
 	return "books"
 }
+
 // TableEngine 获取数据使用的引擎.
 func (m *Book) TableEngine() string {
 	return "INNODB"
@@ -64,18 +66,22 @@ func NewBook() *Book {
 
 func (m *Book) Insert() error {
 	o := orm.NewOrm()
-//	o.Begin()
+	//	o.Begin()
 
-	_,err := o.Insert(m)
+	_, err := o.Insert(m)
 
 	if err == nil {
+		if m.Label != "" {
+			NewLabel().InsertOrUpdateMulti(m.Label)
+		}
+
 		relationship := NewRelationship()
 		relationship.BookId = m.BookId
 		relationship.RoleId = 0
 		relationship.MemberId = m.MemberId
 		err = relationship.Insert()
 		if err != nil {
-			logs.Error("插入项目与用户关联 => ",err)
+			logs.Error("插入项目与用户关联 => ", err)
 			//o.Rollback()
 			return err
 		}
@@ -84,7 +90,7 @@ func (m *Book) Insert() error {
 		document.DocumentName = "空白文档"
 		document.MemberId = m.MemberId
 		err = document.InsertOrUpdate()
-		if err != nil{
+		if err != nil {
 			//o.Rollback()
 			return err
 		}
@@ -95,113 +101,113 @@ func (m *Book) Insert() error {
 	return err
 }
 
-func (m *Book) Find(id int) (*Book,error) {
+func (m *Book) Find(id int) (*Book, error) {
 	if id <= 0 {
-		return m,ErrInvalidParameter
+		return m, ErrInvalidParameter
 	}
 	o := orm.NewOrm()
 
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", id).One(m)
 
-	err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id",id).One(m)
-
-	return m,err
+	return m, err
 }
 
-func (m *Book) Update(cols... string) error  {
+func (m *Book) Update(cols ...string) error {
 	o := orm.NewOrm()
 
-	_,err := o.Update(m,cols...)
+	temp := NewBook()
+	temp.BookId = m.BookId
+
+	if err := o.Read(temp);err != nil {
+		return err
+	}
+
+	if (m.Label + temp.Label) != "" {
+
+		go NewLabel().InsertOrUpdateMulti(m.Label + "," + temp.Label)
+	}
+
+	_, err := o.Update(m, cols...)
 	return err
 }
 
 //根据指定字段查询结果集.
-func (m *Book) FindByField(field string,value interface{}) ([]*Book,error)  {
+func (m *Book) FindByField(field string, value interface{}) ([]*Book, error) {
 	o := orm.NewOrm()
 
 	var books []*Book
-	_,err := o.QueryTable(m.TableNameWithPrefix()).Filter(field,value).All(&books)
+	_, err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, value).All(&books)
 
-	return books,err
+	return books, err
 }
 
 //根据指定字段查询一个结果.
-func (m *Book) FindByFieldFirst(field string,value interface{})(*Book,error) {
+func (m *Book) FindByFieldFirst(field string, value interface{}) (*Book, error) {
 	o := orm.NewOrm()
 
-	err := o.QueryTable(m.TableNameWithPrefix()).Filter(field,value).One(m)
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, value).One(m)
 
-	return m,err
+	return m, err
 
 }
 
-func (m *Book) FindByIdentify(identify string) (*Book,error) {
+func (m *Book) FindByIdentify(identify string) (*Book, error) {
 	o := orm.NewOrm()
 
-	err := o.QueryTable(m.TableNameWithPrefix()).Filter("identify",identify).One(m)
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("identify", identify).One(m)
 
-	return m,err
+	return m, err
 }
 
 //分页查询指定用户的项目
-func (m *Book) FindToPager(pageIndex, pageSize ,memberId int) (books []*BookResult,totalCount int,err error){
+func (m *Book) FindToPager(pageIndex, pageSize, memberId int) (books []*BookResult, totalCount int, err error) {
 
 	relationship := NewRelationship()
 
 	o := orm.NewOrm()
 
-	qb, _ := orm.NewQueryBuilder("mysql")
-
-	qb.Select("COUNT(book.book_id) AS total_count").
-		From(m.TableNameWithPrefix() + " AS book").
-		LeftJoin(relationship.TableNameWithPrefix() + " AS rel").
-		On("book.book_id=rel.book_id AND rel.member_id = ?").
-		Where("rel.relationship_id > 0")
+	sql1 := "SELECT COUNT(book.book_id) AS total_count FROM " + m.TableNameWithPrefix() + " AS book LEFT JOIN " +
+		relationship.TableNameWithPrefix() + " AS rel ON book.book_id=rel.book_id AND rel.member_id = ? WHERE rel.relationship_id > 0 "
 
-	err = o.Raw(qb.String(),memberId).QueryRow(&totalCount)
+	err = o.Raw(sql1, memberId).QueryRow(&totalCount)
 
 	if err != nil {
 		return
 	}
 
 	offset := (pageIndex - 1) * pageSize
-	qb2,_ := orm.NewQueryBuilder("mysql")
-
-	qb2.Select("book.*,rel.member_id","rel.role_id","m.account as create_name").
-		From(m.TableNameWithPrefix() + " AS book").
-		LeftJoin(relationship.TableNameWithPrefix() + " AS rel").On("book.book_id=rel.book_id AND rel.member_id = ?").
-		LeftJoin(relationship.TableNameWithPrefix() + " AS rel1").On("book.book_id=rel1.book_id  AND rel1.role_id=0").
-		LeftJoin(NewMember().TableNameWithPrefix() + " AS m").On("rel1.member_id=m.member_id").
-		Where("rel.relationship_id > 0").
-		OrderBy("book.order_index DESC ","book.book_id").Desc().
-		Limit(pageSize).
-		Offset(offset)
-
-	_,err = o.Raw(qb2.String(),memberId).QueryRows(&books)
+
+	sql2 := "SELECT book.*,rel.member_id,rel.role_id,m.account as create_name FROM " + m.TableNameWithPrefix() + " AS book" +
+		" LEFT JOIN " + relationship.TableNameWithPrefix() + " AS rel ON book.book_id=rel.book_id AND rel.member_id = ?" +
+		" LEFT JOIN " + relationship.TableNameWithPrefix() + " AS rel1 ON book.book_id=rel1.book_id  AND rel1.role_id=0" +
+		" LEFT JOIN " + NewMember().TableNameWithPrefix() + " AS m ON rel1.member_id=m.member_id " +
+		" WHERE rel.relationship_id > 0 ORDER BY book.order_index DESC,book.book_id DESC LIMIT " + fmt.Sprintf("%d,%d",offset,pageSize)
+
+	_, err = o.Raw(sql2, memberId).QueryRows(&books)
 	if err != nil {
-		logs.Error("分页查询项目列表 => ",err)
+		logs.Error("分页查询项目列表 => ", err)
 		return
 	}
 	sql := "SELECT m.account,doc.modify_time FROM md_documents AS doc LEFT JOIN md_members AS m ON doc.modify_at=m.member_id WHERE book_id = ? LIMIT 1 ORDER BY doc.modify_time DESC"
 
-	if err == nil && len(books) > 0{
-		for index,book := range books  {
-			var text struct{
-				Account string
+	if err == nil && len(books) > 0 {
+		for index, book := range books {
+			var text struct {
+				Account    string
 				ModifyTime time.Time
 			}
 
-
-			err1 := o.Raw(sql,book.BookId).QueryRow(&text)
+			err1 := o.Raw(sql, book.BookId).QueryRow(&text)
 			if err1 == nil {
 				books[index].LastModifyText = text.Account + " 于 " + text.ModifyTime.Format("2006-01-02 15:04:05")
 			}
-			if book.RoleId == 0{
+			if book.RoleId == 0 {
 				book.RoleName = "创始人"
-			}else if book.RoleId == 1 {
+			} else if book.RoleId == 1 {
 				book.RoleName = "管理员"
-			}else if book.RoleId == 2 {
+			} else if book.RoleId == 2 {
 				book.RoleName = "编辑者"
-			}else if book.RoleId == 3 {
+			} else if book.RoleId == 3 {
 				book.RoleName = "观察者"
 			}
 		}
@@ -211,7 +217,7 @@ func (m *Book) FindToPager(pageIndex, pageSize ,memberId int) (books []*BookResu
 
 // 彻底删除项目.
 func (m *Book) ThoroughDeleteBook(id int) error {
-	if id <= 0{
+	if id <= 0 {
 		return ErrInvalidParameter
 	}
 	o := orm.NewOrm()
@@ -221,17 +227,10 @@ func (m *Book) ThoroughDeleteBook(id int) error {
 		return err
 	}
 	o.Begin()
-	//sql1 := "DELETE FROM " + NewComment().TableNameWithPrefix() + " WHERE book_id = ?"
-	//
-	//_,err := o.Raw(sql1,m.BookId).Exec()
-	//
-	//if err != nil {
-	//	o.Rollback()
-	//	return err
-	//}
+
 	sql2 := "DELETE FROM " + NewDocument().TableNameWithPrefix() + " WHERE book_id = ?"
 
-	_,err := o.Raw(sql2,m.BookId).Exec()
+	_, err := o.Raw(sql2, m.BookId).Exec()
 
 	if err != nil {
 		o.Rollback()
@@ -239,7 +238,7 @@ func (m *Book) ThoroughDeleteBook(id int) error {
 	}
 	sql3 := "DELETE FROM " + m.TableNameWithPrefix() + " WHERE book_id = ?"
 
-	_,err = o.Raw(sql3,m.BookId).Exec()
+	_, err = o.Raw(sql3, m.BookId).Exec()
 
 	if err != nil {
 		o.Rollback()
@@ -247,19 +246,23 @@ func (m *Book) ThoroughDeleteBook(id int) error {
 	}
 	sql4 := "DELETE FROM " + NewRelationship().TableNameWithPrefix() + " WHERE book_id = ?"
 
-	_,err = o.Raw(sql4,m.BookId).Exec()
+	_, err = o.Raw(sql4, m.BookId).Exec()
 
 	if err != nil {
 		o.Rollback()
 		return err
 	}
 
+	if m.Label != "" {
+		NewLabel().InsertOrUpdateMulti(m.Label)
+	}
+
 	return o.Commit()
 
 }
 
 //分页查找系统首页数据.
-func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*BookResult,totalCount int,err error) {
+func (m *Book) FindForHomeToPager(pageIndex, pageSize, member_id int) (books []*BookResult, totalCount int, err error) {
 	o := orm.NewOrm()
 
 	offset := (pageIndex - 1) * pageSize
@@ -267,7 +270,7 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
 	if member_id > 0 {
 		sql1 := "SELECT COUNT(*) FROM md_books AS book LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.member_id = ? WHERE relationship_id > 0 OR book.privately_owned = 0"
 
-		err = o.Raw(sql1,member_id).QueryRow(&totalCount)
+		err = o.Raw(sql1, member_id).QueryRow(&totalCount)
 		if err != nil {
 			return
 		}
@@ -277,12 +280,12 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
 			LEFT JOIN md_members AS member ON rel1.member_id = member.member_id
 			WHERE rel.relationship_id > 0 OR book.privately_owned = 0 ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
 
-		_,err = o.Raw(sql2,member_id,offset,pageSize).QueryRows(&books)
+		_, err = o.Raw(sql2, member_id, offset, pageSize).QueryRows(&books)
 
 		return
 
-	}else{
-		count,err1 := o.QueryTable(m.TableNameWithPrefix()).Filter("privately_owned",0).Count()
+	} else {
+		count, err1 := o.QueryTable(m.TableNameWithPrefix()).Filter("privately_owned", 0).Count()
 
 		if err1 != nil {
 			err = err1
@@ -295,7 +298,7 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
 			LEFT JOIN md_members AS member ON rel.member_id = member.member_id
 			WHERE book.privately_owned = 0 ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
 
-		_,err = o.Raw(sql,offset,pageSize).QueryRows(&books)
+		_, err = o.Raw(sql, offset, pageSize).QueryRows(&books)
 
 		return
 
@@ -303,30 +306,75 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
 
 }
 
+//分页全局搜索.
+func (m *Book) FindForLabelToPager(keyword string, pageIndex, pageSize, member_id int) (books []*BookResult, totalCount int, err error) {
+	o := orm.NewOrm()
+
+	keyword = "%" + keyword + "%"
+	offset := (pageIndex - 1) * pageSize
+	//如果是登录用户
+	if member_id > 0 {
+		sql1 := "SELECT COUNT(*) FROM md_books AS book LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.member_id = ? WHERE (relationship_id > 0 OR book.privately_owned = 0) AND book.label LIKE ?"
+
+		err = o.Raw(sql1, member_id,keyword).QueryRow(&totalCount)
+		if err != nil {
+			return
+		}
+		sql2 := `SELECT book.*,rel1.*,member.account AS create_name FROM md_books AS book
+			LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.member_id = ?
+			LEFT JOIN md_relationship AS rel1 ON rel1.book_id = book.book_id AND rel1.role_id = 0
+			LEFT JOIN md_members AS member ON rel1.member_id = member.member_id
+			WHERE (rel.relationship_id > 0 OR book.privately_owned = 0) AND book.label LIKE ? ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
+
+		_, err = o.Raw(sql2, member_id,keyword, offset, pageSize).QueryRows(&books)
+
+		return
+
+	} else {
+		count, err1 := o.QueryTable(NewBook().TableNameWithPrefix()).Filter("privately_owned", 0).Filter("label__icontains",keyword).Count()
+
+		if err1 != nil {
+			err = err1
+			return
+		}
+		totalCount = int(count)
+
+		sql := `SELECT book.*,rel.*,member.account AS create_name FROM md_books AS book
+			LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.role_id = 0
+			LEFT JOIN md_members AS member ON rel.member_id = member.member_id
+			WHERE book.privately_owned = 0 AND book.label LIKE ? ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
+
+		_, err = o.Raw(sql,keyword, offset, pageSize).QueryRows(&books)
+
+		return
+
+	}
+}
+
+
 func (book *Book) ToBookResult() *BookResult {
 
 	m := NewBookResult()
 
-	m.BookId 	 	= book.BookId
-	m.BookName 	 	= book.BookName
-	m.Identify 	 	= book.Identify
-	m.OrderIndex 	 	= book.OrderIndex
-	m.Description 	 	= strings.Replace(book.Description, "\r\n", "<br/>", -1)
-	m.PrivatelyOwned 	= book.PrivatelyOwned
-	m.PrivateToken 		= book.PrivateToken
-	m.DocCount 		= book.DocCount
-	m.CommentStatus 	= book.CommentStatus
-	m.CommentCount 		= book.CommentCount
-	m.CreateTime 		= book.CreateTime
-	m.ModifyTime 		= book.ModifyTime
-	m.Cover 		= book.Cover
-	m.Label 		= book.Label
-	m.Status 		= book.Status
-	m.Editor 		= book.Editor
-	m.Theme			= book.Theme
-
-
-	if book.Theme == ""{
+	m.BookId = book.BookId
+	m.BookName = book.BookName
+	m.Identify = book.Identify
+	m.OrderIndex = book.OrderIndex
+	m.Description = strings.Replace(book.Description, "\r\n", "<br/>", -1)
+	m.PrivatelyOwned = book.PrivatelyOwned
+	m.PrivateToken = book.PrivateToken
+	m.DocCount = book.DocCount
+	m.CommentStatus = book.CommentStatus
+	m.CommentCount = book.CommentCount
+	m.CreateTime = book.CreateTime
+	m.ModifyTime = book.ModifyTime
+	m.Cover = book.Cover
+	m.Label = book.Label
+	m.Status = book.Status
+	m.Editor = book.Editor
+	m.Theme = book.Theme
+
+	if book.Theme == "" {
 		m.Theme = "default"
 	}
 	if book.Editor == "" {
@@ -336,61 +384,13 @@ func (book *Book) ToBookResult() *BookResult {
 }
 
 //重置文档数量
-func (m *Book) ResetDocumentNumber(book_id int)  {
+func (m *Book) ResetDocumentNumber(book_id int) {
 	o := orm.NewOrm()
 
-	totalCount,err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id",book_id).Count()
+	totalCount, err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id", book_id).Count()
 	if err == nil {
-		o.Raw("UPDATE md_books SET doc_count = ? WHERE book_id = ?",int(totalCount),book_id).Exec()
-	}else{
+		o.Raw("UPDATE md_books SET doc_count = ? WHERE book_id = ?", int(totalCount), book_id).Exec()
+	} else {
 		beego.Error(err)
 	}
 }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

+ 1 - 1
models/document.go

@@ -155,7 +155,7 @@ func (m *Document) ReleaseContent(book_id int) {
 func (m *Document) FindListByBookId(book_id int) (docs []*Document, err error) {
 	o := orm.NewOrm()
 
-	_, err = o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", book_id).All(&docs)
+	_, err = o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", book_id).OrderBy("order_sort").All(&docs)
 
 	return
 }

+ 92 - 0
models/label.go

@@ -0,0 +1,92 @@
+package models
+
+import (
+	"github.com/lifei6671/mindoc/conf"
+	"github.com/astaxie/beego/orm"
+	"strings"
+)
+
+type Label struct {
+	LabelId int		`orm:"column(label_id);pk;auto;unique;" json:"label_id"`
+	LabelName string	`orm:"column(label_name);size(50);unique" json:"label_name"`
+	BookNumber int		`orm:"column(book_number)" json:"book_number"`
+}
+
+// TableName 获取对应数据库表名.
+func (m *Label) TableName() string {
+	return "label"
+}
+// TableEngine 获取数据使用的引擎.
+func (m *Label) TableEngine() string {
+	return "INNODB"
+}
+
+func (m *Label)TableNameWithPrefix() string {
+	return conf.GetDatabasePrefix() +  m.TableName()
+}
+
+func NewLabel() *Label {
+	return &Label{}
+}
+
+func (m *Label) FindFirst(field string, value interface{}) (*Label,error){
+	o := orm.NewOrm()
+
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, value).One(m)
+
+	return m, err
+}
+
+//插入或更新标签.
+func (m *Label) InsertOrUpdate(labelName string) error {
+	o := orm.NewOrm()
+
+	err := o.QueryTable(m.TableNameWithPrefix()).Filter("label_name",labelName).One(m)
+	if err != nil && err != orm.ErrNoRows {
+		return err
+	}
+	count,_ := o.QueryTable(NewBook().TableNameWithPrefix()).Filter("label__icontains",labelName).Count()
+	m.BookNumber = int(count)
+	m.LabelName = labelName
+
+	if err == orm.ErrNoRows {
+		err = nil
+		m.LabelName = labelName
+		_,err = o.Insert(m)
+	}else{
+		_,err = o.Update(m)
+	}
+	return err
+}
+
+//批量插入或更新标签.
+func (m *Label) InsertOrUpdateMulti(labels string)  {
+	if  labels != "" {
+		labelArray := strings.Split(labels, ",")
+
+		for _, label := range labelArray {
+			if label != "" {
+				NewLabel().InsertOrUpdate(label)
+			}
+		}
+	}
+}
+
+//分页查找标签.
+func (m *Label) FindToPager(pageIndex, pageSize int) (labels []*Label,totalCount int,err error) {
+	o := orm.NewOrm()
+
+	count,err := o.QueryTable(m.TableNameWithPrefix()).Count()
+
+	if err != nil {
+		return
+	}
+	totalCount = int(count)
+
+	offset := (pageIndex - 1) * pageSize
+
+	_,err = o.QueryTable(m.TableNameWithPrefix()).OrderBy("-book_number").Offset(offset).Limit(pageSize).All(&labels)
+
+	return
+}
+

+ 65 - 0
models/member.go

@@ -217,6 +217,7 @@ func (m *Member) ResolveRoleName() {
 	}
 }
 
+//根据账号查找用户.
 func (m *Member) FindByAccount(account string) (*Member, error) {
 	o := orm.NewOrm()
 
@@ -228,6 +229,7 @@ func (m *Member) FindByAccount(account string) (*Member, error) {
 	return m, err
 }
 
+//分页查找用户.
 func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member, int64, error) {
 	o := orm.NewOrm()
 
@@ -260,6 +262,7 @@ func (c *Member) IsAdministrator() bool {
 	return c.Role == 0 || c.Role == 1
 }
 
+//根据指定字段查找用户.
 func (m *Member) FindByFieldFirst(field string, value interface{}) (*Member, error) {
 	o := orm.NewOrm()
 
@@ -268,6 +271,7 @@ func (m *Member) FindByFieldFirst(field string, value interface{}) (*Member, err
 	return m, err
 }
 
+//校验用户.
 func (m *Member) Valid(is_hash_password bool) error {
 
 	//邮箱不能为空
@@ -324,6 +328,67 @@ func (m *Member) Valid(is_hash_password bool) error {
 	return nil
 }
 
+//删除一个用户.
+
+func (m *Member) Delete(oldId int,newId int) error {
+	o := orm.NewOrm()
+
+	err := o.Begin()
+
+	if err != nil {
+		return err
+	}
+
+	_,err = o.Raw("DELETE FROM md_members WHERE member_id = ?",oldId).Exec()
+	if err != nil {
+		o.Rollback()
+		return err
+	}
+	_,err = o.Raw("UPDATE md_attachment SET `create_at` = ? WHERE `create_at` = ?",newId,oldId).Exec()
+
+	if err != nil {
+		o.Rollback()
+		return err
+	}
+
+	_,err = o.Raw("UPDATE md_books SET member_id = ? WHERE member_id = ?",newId,oldId).Exec()
+	if err != nil {
+		o.Rollback()
+		return err
+	}
+	_,err = o.Raw("UPDATE md_document_history SET member_id=? WHERE member_id = ?",newId,oldId).Exec()
+	if err != nil {
+		o.Rollback()
+		return err
+	}
+	_,err = o.Raw("UPDATE md_document_history SET modify_at=? WHERE modify_at = ?",newId,oldId).Exec()
+	if err != nil {
+		o.Rollback()
+		return err
+	}
+	_,err = o.Raw("UPDATE md_documents SET member_id = ? WHERE member_id = ?;",newId,oldId).Exec()
+	if err != nil {
+		o.Rollback()
+		return err
+	}
+	_,err = o.Raw("UPDATE md_documents SET modify_at = ? WHERE modify_at = ?",newId,oldId).Exec()
+	if err != nil {
+		o.Rollback()
+		return err
+	}
+	_,err = o.Raw("UPDATE md_relationship SET member_id = ? WHERE member_id = ?",newId,oldId).Exec()
+	if err != nil {
+		o.Rollback()
+		return err
+	}
+	if err = o.Commit();err != nil {
+		o.Rollback()
+		return err
+	}
+	return nil
+}
+
+
 
 
 

+ 12 - 1
routers/filter.go

@@ -5,6 +5,7 @@ import (
 	"github.com/astaxie/beego/context"
 	"github.com/lifei6671/mindoc/conf"
 	"github.com/lifei6671/mindoc/models"
+	"encoding/json"
 )
 
 func init()  {
@@ -12,7 +13,17 @@ func init()  {
 		_, ok := ctx.Input.Session(conf.LoginSessionName).(models.Member)
 
 		if !ok {
-			ctx.Redirect(302, beego.URLFor("AccountController.Login"))
+			if ctx.Input.IsAjax() {
+				jsonData := make(map[string]interface{},3)
+
+				jsonData["errcode"] = 403
+				jsonData["message"] = "请登录后再操作"
+				returnJSON, _ := json.Marshal(jsonData)
+
+				ctx.ResponseWriter.Write(returnJSON)
+			}else{
+				ctx.Redirect(302, beego.URLFor("AccountController.Login"))
+			}
 		}
 	}
 	beego.InsertFilter("/manager",beego.BeforeRouter,FilterUser)

+ 3 - 0
routers/router.go

@@ -19,6 +19,7 @@ func init()  {
 	beego.Router("/manager/users", &controllers.ManagerController{},"*:Users")
 	beego.Router("/manager/users/edit/:id", &controllers.ManagerController{},"*:EditMember")
 	beego.Router("/manager/member/create", &controllers.ManagerController{},"post:CreateMember")
+	beego.Router("/manager/member/delete", &controllers.ManagerController{},"post:DeleteMember")
 	beego.Router("/manager/member/update-member-status",&controllers.ManagerController{},"post:UpdateMemberStatus")
 	beego.Router("/manager/member/change-member-role", &controllers.ManagerController{},"post:ChangeMemberRole")
 	beego.Router("/manager/books", &controllers.ManagerController{},"*:Books")
@@ -82,5 +83,7 @@ func init()  {
 	beego.Router("/comment/index", &controllers.CommentController{},"*:Index")
 
 	beego.Router("/search",&controllers.SearchController{},"get:Index")
+
+	beego.Router("/tag/:key", &controllers.LabelController{},"get:Index")
 }
 

+ 55 - 0
static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.css

@@ -0,0 +1,55 @@
+.bootstrap-tagsinput {
+  background-color: #fff;
+  border: 1px solid #ccc;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  display: inline-block;
+  padding: 4px 6px;
+  color: #555;
+  vertical-align: middle;
+  border-radius: 4px;
+  max-width: 100%;
+  line-height: 22px;
+  cursor: text;
+}
+.bootstrap-tagsinput input {
+  border: none;
+  box-shadow: none;
+  outline: none;
+  background-color: transparent;
+  padding: 0 6px;
+  margin: 0;
+  width: auto;
+  max-width: inherit;
+}
+.bootstrap-tagsinput.form-control input::-moz-placeholder {
+  color: #777;
+  opacity: 1;
+}
+.bootstrap-tagsinput.form-control input:-ms-input-placeholder {
+  color: #777;
+}
+.bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
+  color: #777;
+}
+.bootstrap-tagsinput input:focus {
+  border: none;
+  box-shadow: none;
+}
+.bootstrap-tagsinput .tag {
+  margin-right: 2px;
+  color: white;
+}
+.bootstrap-tagsinput .tag [data-role="remove"] {
+  margin-left: 8px;
+  cursor: pointer;
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:after {
+  content: "x";
+  padding: 0px 2px;
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:hover {
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
+  box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}

+ 646 - 0
static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.js

@@ -0,0 +1,646 @@
+(function ($) {
+  "use strict";
+
+  var defaultOptions = {
+    tagClass: function(item) {
+      return 'label label-info';
+    },
+    itemValue: function(item) {
+      return item ? item.toString() : item;
+    },
+    itemText: function(item) {
+      return this.itemValue(item);
+    },
+    itemTitle: function(item) {
+      return null;
+    },
+    freeInput: true,
+    addOnBlur: true,
+    maxTags: undefined,
+    maxChars: undefined,
+    confirmKeys: [13, 44],
+    delimiter: ',',
+    delimiterRegex: null,
+    cancelConfirmKeysOnEmpty: true,
+    onTagExists: function(item, $tag) {
+      $tag.hide().fadeIn();
+    },
+    trimValue: false,
+    allowDuplicates: false
+  };
+
+  /**
+   * Constructor function
+   */
+  function TagsInput(element, options) {
+    this.itemsArray = [];
+
+    this.$element = $(element);
+    this.$element.hide();
+
+    this.isSelect = (element.tagName === 'SELECT');
+    this.multiple = (this.isSelect && element.hasAttribute('multiple'));
+    this.objectItems = options && options.itemValue;
+    this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
+    this.inputSize = Math.max(1, this.placeholderText.length);
+
+    this.$container = $('<div class="bootstrap-tagsinput"></div>');
+    this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);
+
+    this.$element.before(this.$container);
+
+    this.build(options);
+  }
+
+  TagsInput.prototype = {
+    constructor: TagsInput,
+
+    /**
+     * Adds the given item as a new tag. Pass true to dontPushVal to prevent
+     * updating the elements val()
+     */
+    add: function(item, dontPushVal, options) {
+      var self = this;
+
+      if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
+        return;
+
+      // Ignore falsey values, except false
+      if (item !== false && !item)
+        return;
+
+      // Trim value
+      if (typeof item === "string" && self.options.trimValue) {
+        item = $.trim(item);
+      }
+
+      // Throw an error when trying to add an object while the itemValue option was not set
+      if (typeof item === "object" && !self.objectItems)
+        throw("Can't add objects when itemValue option is not set");
+
+      // Ignore strings only containg whitespace
+      if (item.toString().match(/^\s*$/))
+        return;
+
+      // If SELECT but not multiple, remove current tag
+      if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
+        self.remove(self.itemsArray[0]);
+
+      if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
+        var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter;
+        var items = item.split(delimiter);
+        if (items.length > 1) {
+          for (var i = 0; i < items.length; i++) {
+            this.add(items[i], true);
+          }
+
+          if (!dontPushVal)
+            self.pushVal();
+          return;
+        }
+      }
+
+      var itemValue = self.options.itemValue(item),
+          itemText = self.options.itemText(item),
+          tagClass = self.options.tagClass(item),
+          itemTitle = self.options.itemTitle(item);
+
+      // Ignore items allready added
+      var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
+      if (existing && !self.options.allowDuplicates) {
+        // Invoke onTagExists
+        if (self.options.onTagExists) {
+          var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
+          self.options.onTagExists(item, $existingTag);
+        }
+        return;
+      }
+
+      // if length greater than limit
+      if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
+        return;
+
+      // raise beforeItemAdd arg
+      var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options});
+      self.$element.trigger(beforeItemAddEvent);
+      if (beforeItemAddEvent.cancel)
+        return;
+
+      // register item in internal array and map
+      self.itemsArray.push(item);
+
+      // add a tag element
+
+      var $tag = $('<span class="tag ' + htmlEncode(tagClass) + (itemTitle !== null ? ('" title="' + itemTitle) : '') + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>');
+      $tag.data('item', item);
+      self.findInputWrapper().before($tag);
+      $tag.after(' ');
+
+      // add <option /> if item represents a value not present in one of the <select />'s options
+      if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) {
+        var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
+        $option.data('item', item);
+        $option.attr('value', itemValue);
+        self.$element.append($option);
+      }
+
+      if (!dontPushVal)
+        self.pushVal();
+
+      // Add class when reached maxTags
+      if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
+        self.$container.addClass('bootstrap-tagsinput-max');
+
+      self.$element.trigger($.Event('itemAdded', { item: item, options: options }));
+    },
+
+    /**
+     * Removes the given item. Pass true to dontPushVal to prevent updating the
+     * elements val()
+     */
+    remove: function(item, dontPushVal, options) {
+      var self = this;
+
+      if (self.objectItems) {
+        if (typeof item === "object")
+          item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) ==  self.options.itemValue(item); } );
+        else
+          item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) ==  item; } );
+
+        item = item[item.length-1];
+      }
+
+      if (item) {
+        var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options });
+        self.$element.trigger(beforeItemRemoveEvent);
+        if (beforeItemRemoveEvent.cancel)
+          return;
+
+        $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
+        $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
+        if($.inArray(item, self.itemsArray) !== -1)
+          self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
+      }
+
+      if (!dontPushVal)
+        self.pushVal();
+
+      // Remove class when reached maxTags
+      if (self.options.maxTags > self.itemsArray.length)
+        self.$container.removeClass('bootstrap-tagsinput-max');
+
+      self.$element.trigger($.Event('itemRemoved',  { item: item, options: options }));
+    },
+
+    /**
+     * Removes all items
+     */
+    removeAll: function() {
+      var self = this;
+
+      $('.tag', self.$container).remove();
+      $('option', self.$element).remove();
+
+      while(self.itemsArray.length > 0)
+        self.itemsArray.pop();
+
+      self.pushVal();
+    },
+
+    /**
+     * Refreshes the tags so they match the text/value of their corresponding
+     * item.
+     */
+    refresh: function() {
+      var self = this;
+      $('.tag', self.$container).each(function() {
+        var $tag = $(this),
+            item = $tag.data('item'),
+            itemValue = self.options.itemValue(item),
+            itemText = self.options.itemText(item),
+            tagClass = self.options.tagClass(item);
+
+          // Update tag's class and inner text
+          $tag.attr('class', null);
+          $tag.addClass('tag ' + htmlEncode(tagClass));
+          $tag.contents().filter(function() {
+            return this.nodeType == 3;
+          })[0].nodeValue = htmlEncode(itemText);
+
+          if (self.isSelect) {
+            var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
+            option.attr('value', itemValue);
+          }
+      });
+    },
+
+    /**
+     * Returns the items added as tags
+     */
+    items: function() {
+      return this.itemsArray;
+    },
+
+    /**
+     * Assembly value by retrieving the value of each item, and set it on the
+     * element.
+     */
+    pushVal: function() {
+      var self = this,
+          val = $.map(self.items(), function(item) {
+            return self.options.itemValue(item).toString();
+          });
+
+      self.$element.val(val, true).trigger('change');
+    },
+
+    /**
+     * Initializes the tags input behaviour on the element
+     */
+    build: function(options) {
+      var self = this;
+
+      self.options = $.extend({}, defaultOptions, options);
+      // When itemValue is set, freeInput should always be false
+      if (self.objectItems)
+        self.options.freeInput = false;
+
+      makeOptionItemFunction(self.options, 'itemValue');
+      makeOptionItemFunction(self.options, 'itemText');
+      makeOptionFunction(self.options, 'tagClass');
+
+      // Typeahead Bootstrap version 2.3.2
+      if (self.options.typeahead) {
+        var typeahead = self.options.typeahead || {};
+
+        makeOptionFunction(typeahead, 'source');
+
+        self.$input.typeahead($.extend({}, typeahead, {
+          source: function (query, process) {
+            function processItems(items) {
+              var texts = [];
+
+              for (var i = 0; i < items.length; i++) {
+                var text = self.options.itemText(items[i]);
+                map[text] = items[i];
+                texts.push(text);
+              }
+              process(texts);
+            }
+
+            this.map = {};
+            var map = this.map,
+                data = typeahead.source(query);
+
+            if ($.isFunction(data.success)) {
+              // support for Angular callbacks
+              data.success(processItems);
+            } else if ($.isFunction(data.then)) {
+              // support for Angular promises
+              data.then(processItems);
+            } else {
+              // support for functions and jquery promises
+              $.when(data)
+               .then(processItems);
+            }
+          },
+          updater: function (text) {
+            self.add(this.map[text]);
+            return this.map[text];
+          },
+          matcher: function (text) {
+            return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
+          },
+          sorter: function (texts) {
+            return texts.sort();
+          },
+          highlighter: function (text) {
+            var regex = new RegExp( '(' + this.query + ')', 'gi' );
+            return text.replace( regex, "<strong>$1</strong>" );
+          }
+        }));
+      }
+
+      // typeahead.js
+      if (self.options.typeaheadjs) {
+          var typeaheadConfig = null;
+          var typeaheadDatasets = {};
+
+          // Determine if main configurations were passed or simply a dataset
+          var typeaheadjs = self.options.typeaheadjs;
+          if ($.isArray(typeaheadjs)) {
+            typeaheadConfig = typeaheadjs[0];
+            typeaheadDatasets = typeaheadjs[1];
+          } else {
+            typeaheadDatasets = typeaheadjs;
+          }
+
+          self.$input.typeahead(typeaheadConfig, typeaheadDatasets).on('typeahead:selected', $.proxy(function (obj, datum) {
+            if (typeaheadDatasets.valueKey)
+              self.add(datum[typeaheadDatasets.valueKey]);
+            else
+              self.add(datum);
+            self.$input.typeahead('val', '');
+          }, self));
+      }
+
+      self.$container.on('click', $.proxy(function(event) {
+        if (! self.$element.attr('disabled')) {
+          self.$input.removeAttr('disabled');
+        }
+        self.$input.focus();
+      }, self));
+
+        if (self.options.addOnBlur && self.options.freeInput) {
+          self.$input.on('focusout', $.proxy(function(event) {
+              // HACK: only process on focusout when no typeahead opened, to
+              //       avoid adding the typeahead text as tag
+              if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
+                self.add(self.$input.val());
+                self.$input.val('');
+              }
+          }, self));
+        }
+
+
+      self.$container.on('keydown', 'input', $.proxy(function(event) {
+        var $input = $(event.target),
+            $inputWrapper = self.findInputWrapper();
+
+        if (self.$element.attr('disabled')) {
+          self.$input.attr('disabled', 'disabled');
+          return;
+        }
+
+        switch (event.which) {
+          // BACKSPACE
+          case 8:
+            if (doGetCaretPosition($input[0]) === 0) {
+              var prev = $inputWrapper.prev();
+              if (prev.length) {
+                self.remove(prev.data('item'));
+              }
+            }
+            break;
+
+          // DELETE
+          case 46:
+            if (doGetCaretPosition($input[0]) === 0) {
+              var next = $inputWrapper.next();
+              if (next.length) {
+                self.remove(next.data('item'));
+              }
+            }
+            break;
+
+          // LEFT ARROW
+          case 37:
+            // Try to move the input before the previous tag
+            var $prevTag = $inputWrapper.prev();
+            if ($input.val().length === 0 && $prevTag[0]) {
+              $prevTag.before($inputWrapper);
+              $input.focus();
+            }
+            break;
+          // RIGHT ARROW
+          case 39:
+            // Try to move the input after the next tag
+            var $nextTag = $inputWrapper.next();
+            if ($input.val().length === 0 && $nextTag[0]) {
+              $nextTag.after($inputWrapper);
+              $input.focus();
+            }
+            break;
+         default:
+             // ignore
+         }
+
+        // Reset internal input's size
+        var textLength = $input.val().length,
+            wordSpace = Math.ceil(textLength / 5),
+            size = textLength + wordSpace + 1;
+        $input.attr('size', Math.max(this.inputSize, $input.val().length));
+      }, self));
+
+      self.$container.on('keypress', 'input', $.proxy(function(event) {
+         var $input = $(event.target);
+
+         if (self.$element.attr('disabled')) {
+            self.$input.attr('disabled', 'disabled');
+            return;
+         }
+
+         var text = $input.val(),
+         maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
+         if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
+            // Only attempt to add a tag if there is data in the field
+            if (text.length !== 0) {
+               self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
+               $input.val('');
+            }
+
+            // If the field is empty, let the event triggered fire as usual
+            if (self.options.cancelConfirmKeysOnEmpty === false) {
+               event.preventDefault();
+            }
+         }
+
+         // Reset internal input's size
+         var textLength = $input.val().length,
+            wordSpace = Math.ceil(textLength / 5),
+            size = textLength + wordSpace + 1;
+         $input.attr('size', Math.max(this.inputSize, $input.val().length));
+      }, self));
+
+      // Remove icon clicked
+      self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
+        if (self.$element.attr('disabled')) {
+          return;
+        }
+        self.remove($(event.target).closest('.tag').data('item'));
+      }, self));
+
+      // Only add existing value as tags when using strings as tags
+      if (self.options.itemValue === defaultOptions.itemValue) {
+        if (self.$element[0].tagName === 'INPUT') {
+            self.add(self.$element.val());
+        } else {
+          $('option', self.$element).each(function() {
+            self.add($(this).attr('value'), true);
+          });
+        }
+      }
+    },
+
+    /**
+     * Removes all tagsinput behaviour and unregsiter all event handlers
+     */
+    destroy: function() {
+      var self = this;
+
+      // Unbind events
+      self.$container.off('keypress', 'input');
+      self.$container.off('click', '[role=remove]');
+
+      self.$container.remove();
+      self.$element.removeData('tagsinput');
+      self.$element.show();
+    },
+
+    /**
+     * Sets focus on the tagsinput
+     */
+    focus: function() {
+      this.$input.focus();
+    },
+
+    /**
+     * Returns the internal input element
+     */
+    input: function() {
+      return this.$input;
+    },
+
+    /**
+     * Returns the element which is wrapped around the internal input. This
+     * is normally the $container, but typeahead.js moves the $input element.
+     */
+    findInputWrapper: function() {
+      var elt = this.$input[0],
+          container = this.$container[0];
+      while(elt && elt.parentNode !== container)
+        elt = elt.parentNode;
+
+      return $(elt);
+    }
+  };
+
+  /**
+   * Register JQuery plugin
+   */
+  $.fn.tagsinput = function(arg1, arg2, arg3) {
+    var results = [];
+
+    this.each(function() {
+      var tagsinput = $(this).data('tagsinput');
+      // Initialize a new tags input
+      if (!tagsinput) {
+          tagsinput = new TagsInput(this, arg1);
+          $(this).data('tagsinput', tagsinput);
+          results.push(tagsinput);
+
+          if (this.tagName === 'SELECT') {
+              $('option', $(this)).attr('selected', 'selected');
+          }
+
+          // Init tags from $(this).val()
+          $(this).val($(this).val());
+      } else if (!arg1 && !arg2) {
+          // tagsinput already exists
+          // no function, trying to init
+          results.push(tagsinput);
+      } else if(tagsinput[arg1] !== undefined) {
+          // Invoke function on existing tags input
+            if(tagsinput[arg1].length === 3 && arg3 !== undefined){
+               var retVal = tagsinput[arg1](arg2, null, arg3);
+            }else{
+               var retVal = tagsinput[arg1](arg2);
+            }
+          if (retVal !== undefined)
+              results.push(retVal);
+      }
+    });
+
+    if ( typeof arg1 == 'string') {
+      // Return the results from the invoked function calls
+      return results.length > 1 ? results : results[0];
+    } else {
+      return results;
+    }
+  };
+
+  $.fn.tagsinput.Constructor = TagsInput;
+
+  /**
+   * Most options support both a string or number as well as a function as
+   * option value. This function makes sure that the option with the given
+   * key in the given options is wrapped in a function
+   */
+  function makeOptionItemFunction(options, key) {
+    if (typeof options[key] !== 'function') {
+      var propertyName = options[key];
+      options[key] = function(item) { return item[propertyName]; };
+    }
+  }
+  function makeOptionFunction(options, key) {
+    if (typeof options[key] !== 'function') {
+      var value = options[key];
+      options[key] = function() { return value; };
+    }
+  }
+  /**
+   * HtmlEncodes the given value
+   */
+  var htmlEncodeContainer = $('<div />');
+  function htmlEncode(value) {
+    if (value) {
+      return htmlEncodeContainer.text(value).html();
+    } else {
+      return '';
+    }
+  }
+
+  /**
+   * Returns the position of the caret in the given input field
+   * http://flightschool.acylt.com/devnotes/caret-position-woes/
+   */
+  function doGetCaretPosition(oField) {
+    var iCaretPos = 0;
+    if (document.selection) {
+      oField.focus ();
+      var oSel = document.selection.createRange();
+      oSel.moveStart ('character', -oField.value.length);
+      iCaretPos = oSel.text.length;
+    } else if (oField.selectionStart || oField.selectionStart == '0') {
+      iCaretPos = oField.selectionStart;
+    }
+    return (iCaretPos);
+  }
+
+  /**
+    * Returns boolean indicates whether user has pressed an expected key combination.
+    * @param object keyPressEvent: JavaScript event object, refer
+    *     http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+    * @param object lookupList: expected key combinations, as in:
+    *     [13, {which: 188, shiftKey: true}]
+    */
+  function keyCombinationInList(keyPressEvent, lookupList) {
+      var found = false;
+      $.each(lookupList, function (index, keyCombination) {
+          if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
+              found = true;
+              return false;
+          }
+
+          if (keyPressEvent.which === keyCombination.which) {
+              var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
+                  shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
+                  ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
+              if (alt && shift && ctrl) {
+                  found = true;
+                  return false;
+              }
+          }
+      });
+
+      return found;
+  }
+
+  /**
+   * Initialize tagsinput behaviour on inputs and selects which have
+   * data-role=tagsinput
+   */
+  $(function() {
+    $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
+  });
+})(window.jQuery);

+ 50 - 0
static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.less

@@ -0,0 +1,50 @@
+.bootstrap-tagsinput {
+  background-color: #fff;
+  border: 1px solid #ccc;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  display: inline-block;
+  padding: 4px 6px;
+  margin-bottom: 10px;
+  color: #555;
+  vertical-align: middle;
+  border-radius: 4px;
+  max-width: 100%;
+  line-height: 22px;
+  cursor: text;
+
+  input {
+    border: none;
+    box-shadow: none;
+    outline: none;
+    background-color: transparent;
+    padding: 0;
+    margin: 0;
+    width: auto !important;
+    max-width: inherit;
+
+    &:focus {
+      border: none;
+      box-shadow: none;
+    }
+  }
+
+  .tag {
+    margin-right: 2px;
+    color: white;
+
+    [data-role="remove"] {
+      margin-left:8px;
+      cursor:pointer;
+      &:after{
+        content: "x";
+        padding:0px 2px;
+      }
+      &:hover {
+        box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+        &:active {
+          box-shadow: inset 0 3px 5px rgba(0,0,0,0.125);
+        }
+      }
+    }
+  }
+}

File diff suppressed because it is too large
+ 5 - 0
static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.min.js


File diff suppressed because it is too large
+ 0 - 0
static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.min.js.map


+ 44 - 2
static/css/main.css

@@ -48,12 +48,15 @@ textarea{
     -o-border-radius: 6px;
     border-radius: 6px;
 }
-
+.bootstrap-tagsinput{
+    display: block !important;
+}
 .manual-header{
     margin-bottom: 0;
     background-color: #fff;
     border-bottom: 0;
     box-shadow: rgba(0,0,0,.1)0 1px 5px;
+    border-top: 3px solid #009a61
 }
 .manual-header  .navbar-brand,.manual-header a {
     font-weight: 500;
@@ -106,9 +109,48 @@ textarea{
     line-height: 1;
     color: #9ba3af;
 }
+/**************首页标签*******************/
+.tag-container-outer{
+    border-bottom: 1px solid #eee;
+    padding-top: 5px;
+    padding-bottom: 5px;
+    line-height: 30px;
+    position: relative;
+    display: block !important;
+    margin-top: 15px;
+}
+.tag-container-outer .title{
+    font-weight: 400;
+    font-size: 14px;
+}
+
+.tag-container-outer .tags a{
+    display: inline-block;
+    vertical-align: baseline;
+    line-height: 1;
+    border-radius: 0.3rem;
+    transition: background .1s ease;
+    cursor: pointer;
+    background-color: #e0e0e0;
+    margin: 0 .5em .5em 0!important;
+    padding: 0.5rem 0.8rem;
+    color: #999999;
+    opacity: 0.8;
+}
+.tag-container-outer .tags a:hover{
+    color: #666666;
+    opacity: 1;
+}
+.tag-container-outer .tags a>.detail{
+    display: inline-block;
+    vertical-align: top;
+    font-weight: 400;
+    margin-left: 1em;
+    opacity: .8
+}
 /*********************************/
 .manual-body{
-    margin-top: 50px;
+    margin-top: 53px;
 }
 .manual-body .page-left{
     position: fixed;

+ 17 - 6
vendor/github.com/PuerkitoBio/goquery/README.md

@@ -1,4 +1,4 @@
-# goquery - a little like that j-thing, only in Go 
+# goquery - a little like that j-thing, only in Go
 [![build status](https://secure.travis-ci.org/PuerkitoBio/goquery.png)](http://travis-ci.org/PuerkitoBio/goquery) [![GoDoc](https://godoc.org/github.com/PuerkitoBio/goquery?status.png)](http://godoc.org/github.com/PuerkitoBio/goquery) [![Sourcegraph Badge](https://sourcegraph.com/github.com/PuerkitoBio/goquery/-/badge.svg)](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge)
 
 
@@ -8,6 +8,15 @@ Also, because the net/html parser requires UTF-8 encoding, so does goquery: it i
 
 Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...).
 
+## Table of Contents
+
+* [Installation](#installation)
+* [Changelog](#changelog)
+* [API](#api)
+* [Examples](#examples)
+* [Related Projects](#related-projects)
+* [License](#license)
+
 ## Installation
 
 Please note that because of the net/html dependency, goquery requires Go1.1+.
@@ -107,18 +116,20 @@ func main() {
 }
 ```
 
-## License
+## Related Projects
 
-The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic].
+- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags.
+- [andybalholm/cascadia][cascadia], the CSS selector library used by goquery.
+- [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors.
 
-## Downstream Projects
+## License
 
-- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct
-    tags.
+The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic].
 
 [jquery]: http://jquery.com/
 [go]: http://golang.org/
 [cascadia]: https://github.com/andybalholm/cascadia
+[cascadiacli]: https://github.com/suntong/cascadia
 [bsd]: http://opensource.org/licenses/BSD-3-Clause
 [golic]: http://golang.org/LICENSE
 [caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE

+ 8 - 0
vendor/github.com/russross/blackfriday/.gitignore

@@ -0,0 +1,8 @@
+*.out
+*.swp
+*.8
+*.6
+_obj
+_test*
+markdown
+tags

+ 30 - 0
vendor/github.com/russross/blackfriday/.travis.yml

@@ -0,0 +1,30 @@
+sudo: false
+language: go
+go:
+  - 1.5.4
+  - 1.6.2
+  - tip
+matrix:
+  include:
+    - go: 1.2.2
+      script:
+        - go get -t -v ./...
+        - go test -v -race ./...
+    - go: 1.3.3
+      script:
+        - go get -t -v ./...
+        - go test -v -race ./...
+    - go: 1.4.3
+      script:
+        - go get -t -v ./...
+        - go test -v -race ./...
+  allow_failures:
+    - go: tip
+  fast_finish: true
+install:
+  - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
+script:
+  - go get -t -v ./...
+  - diff -u <(echo -n) <(gofmt -d -s .)
+  - go tool vet .
+  - go test -v -race ./...

+ 29 - 0
vendor/github.com/russross/blackfriday/LICENSE.txt

@@ -0,0 +1,29 @@
+Blackfriday is distributed under the Simplified BSD License:
+
+> Copyright © 2011 Russ Ross
+> All rights reserved.
+>
+> Redistribution and use in source and binary forms, with or without
+> modification, are permitted provided that the following conditions
+> are met:
+>
+> 1.  Redistributions of source code must retain the above copyright
+>     notice, this list of conditions and the following disclaimer.
+>
+> 2.  Redistributions in binary form must reproduce the above
+>     copyright notice, this list of conditions and the following
+>     disclaimer in the documentation and/or other materials provided with
+>     the distribution.
+>
+> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+> POSSIBILITY OF SUCH DAMAGE.

+ 292 - 0
vendor/github.com/russross/blackfriday/README.md

@@ -0,0 +1,292 @@
+Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) [![GoDoc](https://godoc.org/github.com/russross/blackfriday?status.svg)](https://godoc.org/github.com/russross/blackfriday)
+===========
+
+Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
+is paranoid about its input (so you can safely feed it user-supplied
+data), it is fast, it supports common extensions (tables, smart
+punctuation substitutions, etc.), and it is safe for all utf-8
+(unicode) input.
+
+HTML output is currently supported, along with Smartypants
+extensions. An experimental LaTeX output engine is also included.
+
+It started as a translation from C of [Sundown][3].
+
+
+Installation
+------------
+
+Blackfriday is compatible with Go 1. If you are using an older
+release of Go, consider using v1.1 of blackfriday, which was based
+on the last stable release of Go prior to Go 1. You can find it as a
+tagged commit on github.
+
+With Go 1 and git installed:
+
+    go get github.com/russross/blackfriday
+
+will download, compile, and install the package into your `$GOPATH`
+directory hierarchy. Alternatively, you can achieve the same if you
+import it into a project:
+
+    import "github.com/russross/blackfriday"
+
+and `go get` without parameters.
+
+Usage
+-----
+
+For basic usage, it is as simple as getting your input into a byte
+slice and calling:
+
+    output := blackfriday.MarkdownBasic(input)
+
+This renders it with no extensions enabled. To get a more useful
+feature set, use this instead:
+
+    output := blackfriday.MarkdownCommon(input)
+
+### Sanitize untrusted content
+
+Blackfriday itself does nothing to protect against malicious content. If you are
+dealing with user-supplied markdown, we recommend running blackfriday's output
+through HTML sanitizer such as
+[Bluemonday](https://github.com/microcosm-cc/bluemonday).
+
+Here's an example of simple usage of blackfriday together with bluemonday:
+
+``` go
+import (
+    "github.com/microcosm-cc/bluemonday"
+    "github.com/russross/blackfriday"
+)
+
+// ...
+unsafe := blackfriday.MarkdownCommon(input)
+html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
+```
+
+### Custom options
+
+If you want to customize the set of options, first get a renderer
+(currently either the HTML or LaTeX output engines), then use it to
+call the more general `Markdown` function. For examples, see the
+implementations of `MarkdownBasic` and `MarkdownCommon` in
+`markdown.go`.
+
+You can also check out `blackfriday-tool` for a more complete example
+of how to use it. Download and install it using:
+
+    go get github.com/russross/blackfriday-tool
+
+This is a simple command-line tool that allows you to process a
+markdown file using a standalone program.  You can also browse the
+source directly on github if you are just looking for some example
+code:
+
+* <http://github.com/russross/blackfriday-tool>
+
+Note that if you have not already done so, installing
+`blackfriday-tool` will be sufficient to download and install
+blackfriday in addition to the tool itself. The tool binary will be
+installed in `$GOPATH/bin`.  This is a statically-linked binary that
+can be copied to wherever you need it without worrying about
+dependencies and library versions.
+
+### Sanitized anchor names
+
+Blackfriday includes an algorithm for creating sanitized anchor names
+corresponding to a given input text. This algorithm is used to create
+anchors for headings when `EXTENSION_AUTO_HEADER_IDS` is enabled. The
+algorithm has a specification, so that other packages can create
+compatible anchor names and links to those anchors.
+
+The specification is located at https://godoc.org/github.com/russross/blackfriday#hdr-Sanitized_Anchor_Names.
+
+[`SanitizedAnchorName`](https://godoc.org/github.com/russross/blackfriday#SanitizedAnchorName) exposes this functionality, and can be used to
+create compatible links to the anchor names generated by blackfriday.
+This algorithm is also implemented in a small standalone package at
+[`github.com/shurcooL/sanitized_anchor_name`](https://godoc.org/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
+that want a small package and don't need full functionality of blackfriday.
+
+
+Features
+--------
+
+All features of Sundown are supported, including:
+
+*   **Compatibility**. The Markdown v1.0.3 test suite passes with
+    the `--tidy` option.  Without `--tidy`, the differences are
+    mostly in whitespace and entity escaping, where blackfriday is
+    more consistent and cleaner.
+
+*   **Common extensions**, including table support, fenced code
+    blocks, autolinks, strikethroughs, non-strict emphasis, etc.
+
+*   **Safety**. Blackfriday is paranoid when parsing, making it safe
+    to feed untrusted user input without fear of bad things
+    happening. The test suite stress tests this and there are no
+    known inputs that make it crash.  If you find one, please let me
+    know and send me the input that does it.
+
+    NOTE: "safety" in this context means *runtime safety only*. In order to
+    protect yourself against JavaScript injection in untrusted content, see
+    [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
+
+*   **Fast processing**. It is fast enough to render on-demand in
+    most web applications without having to cache the output.
+
+*   **Thread safety**. You can run multiple parsers in different
+    goroutines without ill effect. There is no dependence on global
+    shared state.
+
+*   **Minimal dependencies**. Blackfriday only depends on standard
+    library packages in Go. The source code is pretty
+    self-contained, so it is easy to add to any project, including
+    Google App Engine projects.
+
+*   **Standards compliant**. Output successfully validates using the
+    W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
+
+
+Extensions
+----------
+
+In addition to the standard markdown syntax, this package
+implements the following extensions:
+
+*   **Intra-word emphasis supression**. The `_` character is
+    commonly used inside words when discussing code, so having
+    markdown interpret it as an emphasis command is usually the
+    wrong thing. Blackfriday lets you treat all emphasis markers as
+    normal characters when they occur inside a word.
+
+*   **Tables**. Tables can be created by drawing them in the input
+    using a simple syntax:
+
+    ```
+    Name    | Age
+    --------|------
+    Bob     | 27
+    Alice   | 23
+    ```
+
+*   **Fenced code blocks**. In addition to the normal 4-space
+    indentation to mark code blocks, you can explicitly mark them
+    and supply a language (to make syntax highlighting simple). Just
+    mark it like this:
+
+        ``` go
+        func getTrue() bool {
+            return true
+        }
+        ```
+
+    You can use 3 or more backticks to mark the beginning of the
+    block, and the same number to mark the end of the block.
+
+    To preserve classes of fenced code blocks while using the bluemonday
+    HTML sanitizer, use the following policy:
+
+    ``` go
+    p := bluemonday.UGCPolicy()
+    p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
+    html := p.SanitizeBytes(unsafe)
+    ```
+
+*   **Definition lists**. A simple definition list is made of a single-line
+    term followed by a colon and the definition for that term.
+
+        Cat
+        : Fluffy animal everyone likes
+        
+        Internet
+        : Vector of transmission for pictures of cats
+
+    Terms must be separated from the previous definition by a blank line.
+
+*   **Footnotes**. A marker in the text that will become a superscript number;
+    a footnote definition that will be placed in a list of footnotes at the
+    end of the document. A footnote looks like this:
+
+        This is a footnote.[^1]
+        
+        [^1]: the footnote text.
+
+*   **Autolinking**. Blackfriday can find URLs that have not been
+    explicitly marked as links and turn them into links.
+
+*   **Strikethrough**. Use two tildes (`~~`) to mark text that
+    should be crossed out.
+
+*   **Hard line breaks**. With this extension enabled (it is off by
+    default in the `MarkdownBasic` and `MarkdownCommon` convenience
+    functions), newlines in the input translate into line breaks in
+    the output.
+
+*   **Smart quotes**. Smartypants-style punctuation substitution is
+    supported, turning normal double- and single-quote marks into
+    curly quotes, etc.
+
+*   **LaTeX-style dash parsing** is an additional option, where `--`
+    is translated into `&ndash;`, and `---` is translated into
+    `&mdash;`. This differs from most smartypants processors, which
+    turn a single hyphen into an ndash and a double hyphen into an
+    mdash.
+
+*   **Smart fractions**, where anything that looks like a fraction
+    is translated into suitable HTML (instead of just a few special
+    cases like most smartypant processors). For example, `4/5`
+    becomes `<sup>4</sup>&frasl;<sub>5</sub>`, which renders as
+    <sup>4</sup>&frasl;<sub>5</sub>.
+
+
+Other renderers
+---------------
+
+Blackfriday is structured to allow alternative rendering engines. Here
+are a few of note:
+
+*   [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
+    provides a GitHub Flavored Markdown renderer with fenced code block
+    highlighting, clickable header anchor links.
+
+    It's not customizable, and its goal is to produce HTML output
+    equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
+    except the rendering is performed locally.
+
+*   [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
+    but for markdown.
+
+*   LaTeX output: renders output as LaTeX. This is currently part of the
+    main Blackfriday repository, but may be split into its own project
+    in the future. If you are interested in owning and maintaining the
+    LaTeX output component, please be in touch.
+
+    It renders some basic documents, but is only experimental at this
+    point. In particular, it does not do any inline escaping, so input
+    that happens to look like LaTeX code will be passed through without
+    modification.
+    
+*   [Md2Vim](https://github.com/FooSoft/md2vim): transforms markdown files into vimdoc format.
+
+
+Todo
+----
+
+*   More unit testing
+*   Improve unicode support. It does not understand all unicode
+    rules (about what constitutes a letter, a punctuation symbol,
+    etc.), so it may fail to detect word boundaries correctly in
+    some instances. It is safe on all utf-8 input.
+
+
+License
+-------
+
+[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
+
+
+   [1]: http://daringfireball.net/projects/markdown/ "Markdown"
+   [2]: http://golang.org/ "Go Language"
+   [3]: https://github.com/vmg/sundown "Sundown"

+ 1450 - 0
vendor/github.com/russross/blackfriday/block.go

@@ -0,0 +1,1450 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+// Functions to parse block-level elements.
+//
+
+package blackfriday
+
+import (
+	"bytes"
+	"unicode"
+)
+
+// Parse block-level data.
+// Note: this function and many that it calls assume that
+// the input buffer ends with a newline.
+func (p *parser) block(out *bytes.Buffer, data []byte) {
+	if len(data) == 0 || data[len(data)-1] != '\n' {
+		panic("block input is missing terminating newline")
+	}
+
+	// this is called recursively: enforce a maximum depth
+	if p.nesting >= p.maxNesting {
+		return
+	}
+	p.nesting++
+
+	// parse out one block-level construct at a time
+	for len(data) > 0 {
+		// prefixed header:
+		//
+		// # Header 1
+		// ## Header 2
+		// ...
+		// ###### Header 6
+		if p.isPrefixHeader(data) {
+			data = data[p.prefixHeader(out, data):]
+			continue
+		}
+
+		// block of preformatted HTML:
+		//
+		// <div>
+		//     ...
+		// </div>
+		if data[0] == '<' {
+			if i := p.html(out, data, true); i > 0 {
+				data = data[i:]
+				continue
+			}
+		}
+
+		// title block
+		//
+		// % stuff
+		// % more stuff
+		// % even more stuff
+		if p.flags&EXTENSION_TITLEBLOCK != 0 {
+			if data[0] == '%' {
+				if i := p.titleBlock(out, data, true); i > 0 {
+					data = data[i:]
+					continue
+				}
+			}
+		}
+
+		// blank lines.  note: returns the # of bytes to skip
+		if i := p.isEmpty(data); i > 0 {
+			data = data[i:]
+			continue
+		}
+
+		// indented code block:
+		//
+		//     func max(a, b int) int {
+		//         if a > b {
+		//             return a
+		//         }
+		//         return b
+		//      }
+		if p.codePrefix(data) > 0 {
+			data = data[p.code(out, data):]
+			continue
+		}
+
+		// fenced code block:
+		//
+		// ``` go
+		// func fact(n int) int {
+		//     if n <= 1 {
+		//         return n
+		//     }
+		//     return n * fact(n-1)
+		// }
+		// ```
+		if p.flags&EXTENSION_FENCED_CODE != 0 {
+			if i := p.fencedCodeBlock(out, data, true); i > 0 {
+				data = data[i:]
+				continue
+			}
+		}
+
+		// horizontal rule:
+		//
+		// ------
+		// or
+		// ******
+		// or
+		// ______
+		if p.isHRule(data) {
+			p.r.HRule(out)
+			var i int
+			for i = 0; data[i] != '\n'; i++ {
+			}
+			data = data[i:]
+			continue
+		}
+
+		// block quote:
+		//
+		// > A big quote I found somewhere
+		// > on the web
+		if p.quotePrefix(data) > 0 {
+			data = data[p.quote(out, data):]
+			continue
+		}
+
+		// table:
+		//
+		// Name  | Age | Phone
+		// ------|-----|---------
+		// Bob   | 31  | 555-1234
+		// Alice | 27  | 555-4321
+		if p.flags&EXTENSION_TABLES != 0 {
+			if i := p.table(out, data); i > 0 {
+				data = data[i:]
+				continue
+			}
+		}
+
+		// an itemized/unordered list:
+		//
+		// * Item 1
+		// * Item 2
+		//
+		// also works with + or -
+		if p.uliPrefix(data) > 0 {
+			data = data[p.list(out, data, 0):]
+			continue
+		}
+
+		// a numbered/ordered list:
+		//
+		// 1. Item 1
+		// 2. Item 2
+		if p.oliPrefix(data) > 0 {
+			data = data[p.list(out, data, LIST_TYPE_ORDERED):]
+			continue
+		}
+
+		// definition lists:
+		//
+		// Term 1
+		// :   Definition a
+		// :   Definition b
+		//
+		// Term 2
+		// :   Definition c
+		if p.flags&EXTENSION_DEFINITION_LISTS != 0 {
+			if p.dliPrefix(data) > 0 {
+				data = data[p.list(out, data, LIST_TYPE_DEFINITION):]
+				continue
+			}
+		}
+
+		// anything else must look like a normal paragraph
+		// note: this finds underlined headers, too
+		data = data[p.paragraph(out, data):]
+	}
+
+	p.nesting--
+}
+
+func (p *parser) isPrefixHeader(data []byte) bool {
+	if data[0] != '#' {
+		return false
+	}
+
+	if p.flags&EXTENSION_SPACE_HEADERS != 0 {
+		level := 0
+		for level < 6 && data[level] == '#' {
+			level++
+		}
+		if data[level] != ' ' {
+			return false
+		}
+	}
+	return true
+}
+
+func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int {
+	level := 0
+	for level < 6 && data[level] == '#' {
+		level++
+	}
+	i := skipChar(data, level, ' ')
+	end := skipUntilChar(data, i, '\n')
+	skip := end
+	id := ""
+	if p.flags&EXTENSION_HEADER_IDS != 0 {
+		j, k := 0, 0
+		// find start/end of header id
+		for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
+		}
+		for k = j + 1; k < end && data[k] != '}'; k++ {
+		}
+		// extract header id iff found
+		if j < end && k < end {
+			id = string(data[j+2 : k])
+			end = j
+			skip = k + 1
+			for end > 0 && data[end-1] == ' ' {
+				end--
+			}
+		}
+	}
+	for end > 0 && data[end-1] == '#' {
+		if isBackslashEscaped(data, end-1) {
+			break
+		}
+		end--
+	}
+	for end > 0 && data[end-1] == ' ' {
+		end--
+	}
+	if end > i {
+		if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
+			id = SanitizedAnchorName(string(data[i:end]))
+		}
+		work := func() bool {
+			p.inline(out, data[i:end])
+			return true
+		}
+		p.r.Header(out, work, level, id)
+	}
+	return skip
+}
+
+func (p *parser) isUnderlinedHeader(data []byte) int {
+	// test of level 1 header
+	if data[0] == '=' {
+		i := skipChar(data, 1, '=')
+		i = skipChar(data, i, ' ')
+		if data[i] == '\n' {
+			return 1
+		} else {
+			return 0
+		}
+	}
+
+	// test of level 2 header
+	if data[0] == '-' {
+		i := skipChar(data, 1, '-')
+		i = skipChar(data, i, ' ')
+		if data[i] == '\n' {
+			return 2
+		} else {
+			return 0
+		}
+	}
+
+	return 0
+}
+
+func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int {
+	if data[0] != '%' {
+		return 0
+	}
+	splitData := bytes.Split(data, []byte("\n"))
+	var i int
+	for idx, b := range splitData {
+		if !bytes.HasPrefix(b, []byte("%")) {
+			i = idx // - 1
+			break
+		}
+	}
+
+	data = bytes.Join(splitData[0:i], []byte("\n"))
+	p.r.TitleBlock(out, data)
+
+	return len(data)
+}
+
+func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int {
+	var i, j int
+
+	// identify the opening tag
+	if data[0] != '<' {
+		return 0
+	}
+	curtag, tagfound := p.htmlFindTag(data[1:])
+
+	// handle special cases
+	if !tagfound {
+		// check for an HTML comment
+		if size := p.htmlComment(out, data, doRender); size > 0 {
+			return size
+		}
+
+		// check for an <hr> tag
+		if size := p.htmlHr(out, data, doRender); size > 0 {
+			return size
+		}
+
+		// check for HTML CDATA
+		if size := p.htmlCDATA(out, data, doRender); size > 0 {
+			return size
+		}
+
+		// no special case recognized
+		return 0
+	}
+
+	// look for an unindented matching closing tag
+	// followed by a blank line
+	found := false
+	/*
+		closetag := []byte("\n</" + curtag + ">")
+		j = len(curtag) + 1
+		for !found {
+			// scan for a closing tag at the beginning of a line
+			if skip := bytes.Index(data[j:], closetag); skip >= 0 {
+				j += skip + len(closetag)
+			} else {
+				break
+			}
+
+			// see if it is the only thing on the line
+			if skip := p.isEmpty(data[j:]); skip > 0 {
+				// see if it is followed by a blank line/eof
+				j += skip
+				if j >= len(data) {
+					found = true
+					i = j
+				} else {
+					if skip := p.isEmpty(data[j:]); skip > 0 {
+						j += skip
+						found = true
+						i = j
+					}
+				}
+			}
+		}
+	*/
+
+	// if not found, try a second pass looking for indented match
+	// but not if tag is "ins" or "del" (following original Markdown.pl)
+	if !found && curtag != "ins" && curtag != "del" {
+		i = 1
+		for i < len(data) {
+			i++
+			for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
+				i++
+			}
+
+			if i+2+len(curtag) >= len(data) {
+				break
+			}
+
+			j = p.htmlFindEnd(curtag, data[i-1:])
+
+			if j > 0 {
+				i += j - 1
+				found = true
+				break
+			}
+		}
+	}
+
+	if !found {
+		return 0
+	}
+
+	// the end of the block has been found
+	if doRender {
+		// trim newlines
+		end := i
+		for end > 0 && data[end-1] == '\n' {
+			end--
+		}
+		p.r.BlockHtml(out, data[:end])
+	}
+
+	return i
+}
+
+func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRender bool) int {
+	// html block needs to end with a blank line
+	if i := p.isEmpty(data[start:]); i > 0 {
+		size := start + i
+		if doRender {
+			// trim trailing newlines
+			end := size
+			for end > 0 && data[end-1] == '\n' {
+				end--
+			}
+			p.r.BlockHtml(out, data[:end])
+		}
+		return size
+	}
+	return 0
+}
+
+// HTML comment, lax form
+func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int {
+	i := p.inlineHTMLComment(out, data)
+	return p.renderHTMLBlock(out, data, i, doRender)
+}
+
+// HTML CDATA section
+func (p *parser) htmlCDATA(out *bytes.Buffer, data []byte, doRender bool) int {
+	const cdataTag = "<![cdata["
+	const cdataTagLen = len(cdataTag)
+	if len(data) < cdataTagLen+1 {
+		return 0
+	}
+	if !bytes.Equal(bytes.ToLower(data[:cdataTagLen]), []byte(cdataTag)) {
+		return 0
+	}
+	i := cdataTagLen
+	// scan for an end-of-comment marker, across lines if necessary
+	for i < len(data) && !(data[i-2] == ']' && data[i-1] == ']' && data[i] == '>') {
+		i++
+	}
+	i++
+	// no end-of-comment marker
+	if i >= len(data) {
+		return 0
+	}
+	return p.renderHTMLBlock(out, data, i, doRender)
+}
+
+// HR, which is the only self-closing block tag considered
+func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int {
+	if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
+		return 0
+	}
+	if data[3] != ' ' && data[3] != '/' && data[3] != '>' {
+		// not an <hr> tag after all; at least not a valid one
+		return 0
+	}
+
+	i := 3
+	for data[i] != '>' && data[i] != '\n' {
+		i++
+	}
+
+	if data[i] == '>' {
+		return p.renderHTMLBlock(out, data, i+1, doRender)
+	}
+
+	return 0
+}
+
+func (p *parser) htmlFindTag(data []byte) (string, bool) {
+	i := 0
+	for isalnum(data[i]) {
+		i++
+	}
+	key := string(data[:i])
+	if _, ok := blockTags[key]; ok {
+		return key, true
+	}
+	return "", false
+}
+
+func (p *parser) htmlFindEnd(tag string, data []byte) int {
+	// assume data[0] == '<' && data[1] == '/' already tested
+
+	// check if tag is a match
+	closetag := []byte("</" + tag + ">")
+	if !bytes.HasPrefix(data, closetag) {
+		return 0
+	}
+	i := len(closetag)
+
+	// check that the rest of the line is blank
+	skip := 0
+	if skip = p.isEmpty(data[i:]); skip == 0 {
+		return 0
+	}
+	i += skip
+	skip = 0
+
+	if i >= len(data) {
+		return i
+	}
+
+	if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
+		return i
+	}
+	if skip = p.isEmpty(data[i:]); skip == 0 {
+		// following line must be blank
+		return 0
+	}
+
+	return i + skip
+}
+
+func (*parser) isEmpty(data []byte) int {
+	// it is okay to call isEmpty on an empty buffer
+	if len(data) == 0 {
+		return 0
+	}
+
+	var i int
+	for i = 0; i < len(data) && data[i] != '\n'; i++ {
+		if data[i] != ' ' && data[i] != '\t' {
+			return 0
+		}
+	}
+	return i + 1
+}
+
+func (*parser) isHRule(data []byte) bool {
+	i := 0
+
+	// skip up to three spaces
+	for i < 3 && data[i] == ' ' {
+		i++
+	}
+
+	// look at the hrule char
+	if data[i] != '*' && data[i] != '-' && data[i] != '_' {
+		return false
+	}
+	c := data[i]
+
+	// the whole line must be the char or whitespace
+	n := 0
+	for data[i] != '\n' {
+		switch {
+		case data[i] == c:
+			n++
+		case data[i] != ' ':
+			return false
+		}
+		i++
+	}
+
+	return n >= 3
+}
+
+// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
+// and returns the end index if so, or 0 otherwise. It also returns the marker found.
+// If syntax is not nil, it gets set to the syntax specified in the fence line.
+// A final newline is mandatory to recognize the fence line, unless newlineOptional is true.
+func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional bool) (end int, marker string) {
+	i, size := 0, 0
+
+	// skip up to three spaces
+	for i < len(data) && i < 3 && data[i] == ' ' {
+		i++
+	}
+
+	// check for the marker characters: ~ or `
+	if i >= len(data) {
+		return 0, ""
+	}
+	if data[i] != '~' && data[i] != '`' {
+		return 0, ""
+	}
+
+	c := data[i]
+
+	// the whole line must be the same char or whitespace
+	for i < len(data) && data[i] == c {
+		size++
+		i++
+	}
+
+	// the marker char must occur at least 3 times
+	if size < 3 {
+		return 0, ""
+	}
+	marker = string(data[i-size : i])
+
+	// if this is the end marker, it must match the beginning marker
+	if oldmarker != "" && marker != oldmarker {
+		return 0, ""
+	}
+
+	// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
+	// into one, always get the syntax, and discard it if the caller doesn't care.
+	if syntax != nil {
+		syn := 0
+		i = skipChar(data, i, ' ')
+
+		if i >= len(data) {
+			if newlineOptional && i == len(data) {
+				return i, marker
+			}
+			return 0, ""
+		}
+
+		syntaxStart := i
+
+		if data[i] == '{' {
+			i++
+			syntaxStart++
+
+			for i < len(data) && data[i] != '}' && data[i] != '\n' {
+				syn++
+				i++
+			}
+
+			if i >= len(data) || data[i] != '}' {
+				return 0, ""
+			}
+
+			// strip all whitespace at the beginning and the end
+			// of the {} block
+			for syn > 0 && isspace(data[syntaxStart]) {
+				syntaxStart++
+				syn--
+			}
+
+			for syn > 0 && isspace(data[syntaxStart+syn-1]) {
+				syn--
+			}
+
+			i++
+		} else {
+			for i < len(data) && !isspace(data[i]) {
+				syn++
+				i++
+			}
+		}
+
+		*syntax = string(data[syntaxStart : syntaxStart+syn])
+	}
+
+	i = skipChar(data, i, ' ')
+	if i >= len(data) || data[i] != '\n' {
+		if newlineOptional && i == len(data) {
+			return i, marker
+		}
+		return 0, ""
+	}
+
+	return i + 1, marker // Take newline into account.
+}
+
+// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning,
+// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
+// If doRender is true, a final newline is mandatory to recognize the fenced code block.
+func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) int {
+	var syntax string
+	beg, marker := isFenceLine(data, &syntax, "", false)
+	if beg == 0 || beg >= len(data) {
+		return 0
+	}
+
+	var work bytes.Buffer
+
+	for {
+		// safe to assume beg < len(data)
+
+		// check for the end of the code block
+		newlineOptional := !doRender
+		fenceEnd, _ := isFenceLine(data[beg:], nil, marker, newlineOptional)
+		if fenceEnd != 0 {
+			beg += fenceEnd
+			break
+		}
+
+		// copy the current line
+		end := skipUntilChar(data, beg, '\n') + 1
+
+		// did we reach the end of the buffer without a closing marker?
+		if end >= len(data) {
+			return 0
+		}
+
+		// verbatim copy to the working buffer
+		if doRender {
+			work.Write(data[beg:end])
+		}
+		beg = end
+	}
+
+	if doRender {
+		p.r.BlockCode(out, work.Bytes(), syntax)
+	}
+
+	return beg
+}
+
+func (p *parser) table(out *bytes.Buffer, data []byte) int {
+	var header bytes.Buffer
+	i, columns := p.tableHeader(&header, data)
+	if i == 0 {
+		return 0
+	}
+
+	var body bytes.Buffer
+
+	for i < len(data) {
+		pipes, rowStart := 0, i
+		for ; data[i] != '\n'; i++ {
+			if data[i] == '|' {
+				pipes++
+			}
+		}
+
+		if pipes == 0 {
+			i = rowStart
+			break
+		}
+
+		// include the newline in data sent to tableRow
+		i++
+		p.tableRow(&body, data[rowStart:i], columns, false)
+	}
+
+	p.r.Table(out, header.Bytes(), body.Bytes(), columns)
+
+	return i
+}
+
+// check if the specified position is preceded by an odd number of backslashes
+func isBackslashEscaped(data []byte, i int) bool {
+	backslashes := 0
+	for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
+		backslashes++
+	}
+	return backslashes&1 == 1
+}
+
+func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns []int) {
+	i := 0
+	colCount := 1
+	for i = 0; data[i] != '\n'; i++ {
+		if data[i] == '|' && !isBackslashEscaped(data, i) {
+			colCount++
+		}
+	}
+
+	// doesn't look like a table header
+	if colCount == 1 {
+		return
+	}
+
+	// include the newline in the data sent to tableRow
+	header := data[:i+1]
+
+	// column count ignores pipes at beginning or end of line
+	if data[0] == '|' {
+		colCount--
+	}
+	if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) {
+		colCount--
+	}
+
+	columns = make([]int, colCount)
+
+	// move on to the header underline
+	i++
+	if i >= len(data) {
+		return
+	}
+
+	if data[i] == '|' && !isBackslashEscaped(data, i) {
+		i++
+	}
+	i = skipChar(data, i, ' ')
+
+	// each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
+	// and trailing | optional on last column
+	col := 0
+	for data[i] != '\n' {
+		dashes := 0
+
+		if data[i] == ':' {
+			i++
+			columns[col] |= TABLE_ALIGNMENT_LEFT
+			dashes++
+		}
+		for data[i] == '-' {
+			i++
+			dashes++
+		}
+		if data[i] == ':' {
+			i++
+			columns[col] |= TABLE_ALIGNMENT_RIGHT
+			dashes++
+		}
+		for data[i] == ' ' {
+			i++
+		}
+
+		// end of column test is messy
+		switch {
+		case dashes < 3:
+			// not a valid column
+			return
+
+		case data[i] == '|' && !isBackslashEscaped(data, i):
+			// marker found, now skip past trailing whitespace
+			col++
+			i++
+			for data[i] == ' ' {
+				i++
+			}
+
+			// trailing junk found after last column
+			if col >= colCount && data[i] != '\n' {
+				return
+			}
+
+		case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
+			// something else found where marker was required
+			return
+
+		case data[i] == '\n':
+			// marker is optional for the last column
+			col++
+
+		default:
+			// trailing junk found after last column
+			return
+		}
+	}
+	if col != colCount {
+		return
+	}
+
+	p.tableRow(out, header, columns, true)
+	size = i + 1
+	return
+}
+
+func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header bool) {
+	i, col := 0, 0
+	var rowWork bytes.Buffer
+
+	if data[i] == '|' && !isBackslashEscaped(data, i) {
+		i++
+	}
+
+	for col = 0; col < len(columns) && i < len(data); col++ {
+		for data[i] == ' ' {
+			i++
+		}
+
+		cellStart := i
+
+		for (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
+			i++
+		}
+
+		cellEnd := i
+
+		// skip the end-of-cell marker, possibly taking us past end of buffer
+		i++
+
+		for cellEnd > cellStart && data[cellEnd-1] == ' ' {
+			cellEnd--
+		}
+
+		var cellWork bytes.Buffer
+		p.inline(&cellWork, data[cellStart:cellEnd])
+
+		if header {
+			p.r.TableHeaderCell(&rowWork, cellWork.Bytes(), columns[col])
+		} else {
+			p.r.TableCell(&rowWork, cellWork.Bytes(), columns[col])
+		}
+	}
+
+	// pad it out with empty columns to get the right number
+	for ; col < len(columns); col++ {
+		if header {
+			p.r.TableHeaderCell(&rowWork, nil, columns[col])
+		} else {
+			p.r.TableCell(&rowWork, nil, columns[col])
+		}
+	}
+
+	// silently ignore rows with too many cells
+
+	p.r.TableRow(out, rowWork.Bytes())
+}
+
+// returns blockquote prefix length
+func (p *parser) quotePrefix(data []byte) int {
+	i := 0
+	for i < 3 && data[i] == ' ' {
+		i++
+	}
+	if data[i] == '>' {
+		if data[i+1] == ' ' {
+			return i + 2
+		}
+		return i + 1
+	}
+	return 0
+}
+
+// blockquote ends with at least one blank line
+// followed by something without a blockquote prefix
+func (p *parser) terminateBlockquote(data []byte, beg, end int) bool {
+	if p.isEmpty(data[beg:]) <= 0 {
+		return false
+	}
+	if end >= len(data) {
+		return true
+	}
+	return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0
+}
+
+// parse a blockquote fragment
+func (p *parser) quote(out *bytes.Buffer, data []byte) int {
+	var raw bytes.Buffer
+	beg, end := 0, 0
+	for beg < len(data) {
+		end = beg
+		// Step over whole lines, collecting them. While doing that, check for
+		// fenced code and if one's found, incorporate it altogether,
+		// irregardless of any contents inside it
+		for data[end] != '\n' {
+			if p.flags&EXTENSION_FENCED_CODE != 0 {
+				if i := p.fencedCodeBlock(out, data[end:], false); i > 0 {
+					// -1 to compensate for the extra end++ after the loop:
+					end += i - 1
+					break
+				}
+			}
+			end++
+		}
+		end++
+
+		if pre := p.quotePrefix(data[beg:]); pre > 0 {
+			// skip the prefix
+			beg += pre
+		} else if p.terminateBlockquote(data, beg, end) {
+			break
+		}
+
+		// this line is part of the blockquote
+		raw.Write(data[beg:end])
+		beg = end
+	}
+
+	var cooked bytes.Buffer
+	p.block(&cooked, raw.Bytes())
+	p.r.BlockQuote(out, cooked.Bytes())
+	return end
+}
+
+// returns prefix length for block code
+func (p *parser) codePrefix(data []byte) int {
+	if data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
+		return 4
+	}
+	return 0
+}
+
+func (p *parser) code(out *bytes.Buffer, data []byte) int {
+	var work bytes.Buffer
+
+	i := 0
+	for i < len(data) {
+		beg := i
+		for data[i] != '\n' {
+			i++
+		}
+		i++
+
+		blankline := p.isEmpty(data[beg:i]) > 0
+		if pre := p.codePrefix(data[beg:i]); pre > 0 {
+			beg += pre
+		} else if !blankline {
+			// non-empty, non-prefixed line breaks the pre
+			i = beg
+			break
+		}
+
+		// verbatim copy to the working buffeu
+		if blankline {
+			work.WriteByte('\n')
+		} else {
+			work.Write(data[beg:i])
+		}
+	}
+
+	// trim all the \n off the end of work
+	workbytes := work.Bytes()
+	eol := len(workbytes)
+	for eol > 0 && workbytes[eol-1] == '\n' {
+		eol--
+	}
+	if eol != len(workbytes) {
+		work.Truncate(eol)
+	}
+
+	work.WriteByte('\n')
+
+	p.r.BlockCode(out, work.Bytes(), "")
+
+	return i
+}
+
+// returns unordered list item prefix
+func (p *parser) uliPrefix(data []byte) int {
+	i := 0
+
+	// start with up to 3 spaces
+	for i < 3 && data[i] == ' ' {
+		i++
+	}
+
+	// need a *, +, or - followed by a space
+	if (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
+		data[i+1] != ' ' {
+		return 0
+	}
+	return i + 2
+}
+
+// returns ordered list item prefix
+func (p *parser) oliPrefix(data []byte) int {
+	i := 0
+
+	// start with up to 3 spaces
+	for i < 3 && data[i] == ' ' {
+		i++
+	}
+
+	// count the digits
+	start := i
+	for data[i] >= '0' && data[i] <= '9' {
+		i++
+	}
+
+	// we need >= 1 digits followed by a dot and a space
+	if start == i || data[i] != '.' || data[i+1] != ' ' {
+		return 0
+	}
+	return i + 2
+}
+
+// returns definition list item prefix
+func (p *parser) dliPrefix(data []byte) int {
+	i := 0
+
+	// need a : followed by a spaces
+	if data[i] != ':' || data[i+1] != ' ' {
+		return 0
+	}
+	for data[i] == ' ' {
+		i++
+	}
+	return i + 2
+}
+
+// parse ordered or unordered list block
+func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int {
+	i := 0
+	flags |= LIST_ITEM_BEGINNING_OF_LIST
+	work := func() bool {
+		for i < len(data) {
+			skip := p.listItem(out, data[i:], &flags)
+			i += skip
+
+			if skip == 0 || flags&LIST_ITEM_END_OF_LIST != 0 {
+				break
+			}
+			flags &= ^LIST_ITEM_BEGINNING_OF_LIST
+		}
+		return true
+	}
+
+	p.r.List(out, work, flags)
+	return i
+}
+
+// Parse a single list item.
+// Assumes initial prefix is already removed if this is a sublist.
+func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int {
+	// keep track of the indentation of the first line
+	itemIndent := 0
+	for itemIndent < 3 && data[itemIndent] == ' ' {
+		itemIndent++
+	}
+
+	i := p.uliPrefix(data)
+	if i == 0 {
+		i = p.oliPrefix(data)
+	}
+	if i == 0 {
+		i = p.dliPrefix(data)
+		// reset definition term flag
+		if i > 0 {
+			*flags &= ^LIST_TYPE_TERM
+		}
+	}
+	if i == 0 {
+		// if in defnition list, set term flag and continue
+		if *flags&LIST_TYPE_DEFINITION != 0 {
+			*flags |= LIST_TYPE_TERM
+		} else {
+			return 0
+		}
+	}
+
+	// skip leading whitespace on first line
+	for data[i] == ' ' {
+		i++
+	}
+
+	// find the end of the line
+	line := i
+	for i > 0 && data[i-1] != '\n' {
+		i++
+	}
+
+	// get working buffer
+	var raw bytes.Buffer
+
+	// put the first line into the working buffer
+	raw.Write(data[line:i])
+	line = i
+
+	// process the following lines
+	containsBlankLine := false
+	sublist := 0
+
+gatherlines:
+	for line < len(data) {
+		i++
+
+		// find the end of this line
+		for data[i-1] != '\n' {
+			i++
+		}
+
+		// if it is an empty line, guess that it is part of this item
+		// and move on to the next line
+		if p.isEmpty(data[line:i]) > 0 {
+			containsBlankLine = true
+			raw.Write(data[line:i])
+			line = i
+			continue
+		}
+
+		// calculate the indentation
+		indent := 0
+		for indent < 4 && line+indent < i && data[line+indent] == ' ' {
+			indent++
+		}
+
+		chunk := data[line+indent : i]
+
+		// evaluate how this line fits in
+		switch {
+		// is this a nested list item?
+		case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) ||
+			p.oliPrefix(chunk) > 0 ||
+			p.dliPrefix(chunk) > 0:
+
+			if containsBlankLine {
+				// end the list if the type changed after a blank line
+				if indent <= itemIndent &&
+					((*flags&LIST_TYPE_ORDERED != 0 && p.uliPrefix(chunk) > 0) ||
+						(*flags&LIST_TYPE_ORDERED == 0 && p.oliPrefix(chunk) > 0)) {
+
+					*flags |= LIST_ITEM_END_OF_LIST
+					break gatherlines
+				}
+				*flags |= LIST_ITEM_CONTAINS_BLOCK
+			}
+
+			// to be a nested list, it must be indented more
+			// if not, it is the next item in the same list
+			if indent <= itemIndent {
+				break gatherlines
+			}
+
+			// is this the first item in the nested list?
+			if sublist == 0 {
+				sublist = raw.Len()
+			}
+
+		// is this a nested prefix header?
+		case p.isPrefixHeader(chunk):
+			// if the header is not indented, it is not nested in the list
+			// and thus ends the list
+			if containsBlankLine && indent < 4 {
+				*flags |= LIST_ITEM_END_OF_LIST
+				break gatherlines
+			}
+			*flags |= LIST_ITEM_CONTAINS_BLOCK
+
+		// anything following an empty line is only part
+		// of this item if it is indented 4 spaces
+		// (regardless of the indentation of the beginning of the item)
+		case containsBlankLine && indent < 4:
+			if *flags&LIST_TYPE_DEFINITION != 0 && i < len(data)-1 {
+				// is the next item still a part of this list?
+				next := i
+				for data[next] != '\n' {
+					next++
+				}
+				for next < len(data)-1 && data[next] == '\n' {
+					next++
+				}
+				if i < len(data)-1 && data[i] != ':' && data[next] != ':' {
+					*flags |= LIST_ITEM_END_OF_LIST
+				}
+			} else {
+				*flags |= LIST_ITEM_END_OF_LIST
+			}
+			break gatherlines
+
+		// a blank line means this should be parsed as a block
+		case containsBlankLine:
+			*flags |= LIST_ITEM_CONTAINS_BLOCK
+		}
+
+		containsBlankLine = false
+
+		// add the line into the working buffer without prefix
+		raw.Write(data[line+indent : i])
+
+		line = i
+	}
+
+	// If reached end of data, the Renderer.ListItem call we're going to make below
+	// is definitely the last in the list.
+	if line >= len(data) {
+		*flags |= LIST_ITEM_END_OF_LIST
+	}
+
+	rawBytes := raw.Bytes()
+
+	// render the contents of the list item
+	var cooked bytes.Buffer
+	if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 {
+		// intermediate render of block item, except for definition term
+		if sublist > 0 {
+			p.block(&cooked, rawBytes[:sublist])
+			p.block(&cooked, rawBytes[sublist:])
+		} else {
+			p.block(&cooked, rawBytes)
+		}
+	} else {
+		// intermediate render of inline item
+		if sublist > 0 {
+			p.inline(&cooked, rawBytes[:sublist])
+			p.block(&cooked, rawBytes[sublist:])
+		} else {
+			p.inline(&cooked, rawBytes)
+		}
+	}
+
+	// render the actual list item
+	cookedBytes := cooked.Bytes()
+	parsedEnd := len(cookedBytes)
+
+	// strip trailing newlines
+	for parsedEnd > 0 && cookedBytes[parsedEnd-1] == '\n' {
+		parsedEnd--
+	}
+	p.r.ListItem(out, cookedBytes[:parsedEnd], *flags)
+
+	return line
+}
+
+// render a single paragraph that has already been parsed out
+func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) {
+	if len(data) == 0 {
+		return
+	}
+
+	// trim leading spaces
+	beg := 0
+	for data[beg] == ' ' {
+		beg++
+	}
+
+	// trim trailing newline
+	end := len(data) - 1
+
+	// trim trailing spaces
+	for end > beg && data[end-1] == ' ' {
+		end--
+	}
+
+	work := func() bool {
+		p.inline(out, data[beg:end])
+		return true
+	}
+	p.r.Paragraph(out, work)
+}
+
+func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
+	// prev: index of 1st char of previous line
+	// line: index of 1st char of current line
+	// i: index of cursor/end of current line
+	var prev, line, i int
+
+	// keep going until we find something to mark the end of the paragraph
+	for i < len(data) {
+		// mark the beginning of the current line
+		prev = line
+		current := data[i:]
+		line = i
+
+		// did we find a blank line marking the end of the paragraph?
+		if n := p.isEmpty(current); n > 0 {
+			// did this blank line followed by a definition list item?
+			if p.flags&EXTENSION_DEFINITION_LISTS != 0 {
+				if i < len(data)-1 && data[i+1] == ':' {
+					return p.list(out, data[prev:], LIST_TYPE_DEFINITION)
+				}
+			}
+
+			p.renderParagraph(out, data[:i])
+			return i + n
+		}
+
+		// an underline under some text marks a header, so our paragraph ended on prev line
+		if i > 0 {
+			if level := p.isUnderlinedHeader(current); level > 0 {
+				// render the paragraph
+				p.renderParagraph(out, data[:prev])
+
+				// ignore leading and trailing whitespace
+				eol := i - 1
+				for prev < eol && data[prev] == ' ' {
+					prev++
+				}
+				for eol > prev && data[eol-1] == ' ' {
+					eol--
+				}
+
+				// render the header
+				// this ugly double closure avoids forcing variables onto the heap
+				work := func(o *bytes.Buffer, pp *parser, d []byte) func() bool {
+					return func() bool {
+						pp.inline(o, d)
+						return true
+					}
+				}(out, p, data[prev:eol])
+
+				id := ""
+				if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
+					id = SanitizedAnchorName(string(data[prev:eol]))
+				}
+
+				p.r.Header(out, work, level, id)
+
+				// find the end of the underline
+				for data[i] != '\n' {
+					i++
+				}
+				return i
+			}
+		}
+
+		// if the next line starts a block of HTML, then the paragraph ends here
+		if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 {
+			if data[i] == '<' && p.html(out, current, false) > 0 {
+				// rewind to before the HTML block
+				p.renderParagraph(out, data[:i])
+				return i
+			}
+		}
+
+		// if there's a prefixed header or a horizontal rule after this, paragraph is over
+		if p.isPrefixHeader(current) || p.isHRule(current) {
+			p.renderParagraph(out, data[:i])
+			return i
+		}
+
+		// if there's a fenced code block, paragraph is over
+		if p.flags&EXTENSION_FENCED_CODE != 0 {
+			if p.fencedCodeBlock(out, current, false) > 0 {
+				p.renderParagraph(out, data[:i])
+				return i
+			}
+		}
+
+		// if there's a definition list item, prev line is a definition term
+		if p.flags&EXTENSION_DEFINITION_LISTS != 0 {
+			if p.dliPrefix(current) != 0 {
+				return p.list(out, data[prev:], LIST_TYPE_DEFINITION)
+			}
+		}
+
+		// if there's a list after this, paragraph is over
+		if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 {
+			if p.uliPrefix(current) != 0 ||
+				p.oliPrefix(current) != 0 ||
+				p.quotePrefix(current) != 0 ||
+				p.codePrefix(current) != 0 {
+				p.renderParagraph(out, data[:i])
+				return i
+			}
+		}
+
+		// otherwise, scan to the beginning of the next line
+		for data[i] != '\n' {
+			i++
+		}
+		i++
+	}
+
+	p.renderParagraph(out, data[:i])
+	return i
+}
+
+// SanitizedAnchorName returns a sanitized anchor name for the given text.
+//
+// It implements the algorithm specified in the package comment.
+func SanitizedAnchorName(text string) string {
+	var anchorName []rune
+	futureDash := false
+	for _, r := range text {
+		switch {
+		case unicode.IsLetter(r) || unicode.IsNumber(r):
+			if futureDash && len(anchorName) > 0 {
+				anchorName = append(anchorName, '-')
+			}
+			futureDash = false
+			anchorName = append(anchorName, unicode.ToLower(r))
+		default:
+			futureDash = true
+		}
+	}
+	return string(anchorName)
+}

+ 1781 - 0
vendor/github.com/russross/blackfriday/block_test.go

@@ -0,0 +1,1781 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+// Unit tests for block parsing
+//
+
+package blackfriday
+
+import (
+	"strings"
+	"testing"
+)
+
+func runMarkdownBlockWithRenderer(input string, extensions int, renderer Renderer) string {
+	return string(Markdown([]byte(input), renderer, extensions))
+}
+
+func runMarkdownBlock(input string, extensions int) string {
+	htmlFlags := 0
+	htmlFlags |= HTML_USE_XHTML
+
+	renderer := HtmlRenderer(htmlFlags, "", "")
+
+	return runMarkdownBlockWithRenderer(input, extensions, renderer)
+}
+
+func runnerWithRendererParameters(parameters HtmlRendererParameters) func(string, int) string {
+	return func(input string, extensions int) string {
+		htmlFlags := 0
+		htmlFlags |= HTML_USE_XHTML
+
+		renderer := HtmlRendererWithParameters(htmlFlags, "", "", parameters)
+
+		return runMarkdownBlockWithRenderer(input, extensions, renderer)
+	}
+}
+
+func doTestsBlock(t *testing.T, tests []string, extensions int) {
+	doTestsBlockWithRunner(t, tests, extensions, runMarkdownBlock)
+}
+
+func doTestsBlockWithRunner(t *testing.T, tests []string, extensions int, runner func(string, int) string) {
+	// catch and report panics
+	var candidate string
+	defer func() {
+		if err := recover(); err != nil {
+			t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
+		}
+	}()
+
+	for i := 0; i+1 < len(tests); i += 2 {
+		input := tests[i]
+		candidate = input
+		expected := tests[i+1]
+		actual := runner(candidate, extensions)
+		if actual != expected {
+			t.Errorf("\nInput   [%#v]\nExpected[%#v]\nActual  [%#v]",
+				candidate, expected, actual)
+		}
+
+		// now test every substring to stress test bounds checking
+		if !testing.Short() {
+			for start := 0; start < len(input); start++ {
+				for end := start + 1; end <= len(input); end++ {
+					candidate = input[start:end]
+					_ = runMarkdownBlock(candidate, extensions)
+				}
+			}
+		}
+	}
+}
+
+func TestPrefixHeaderNoExtensions(t *testing.T) {
+	var tests = []string{
+		"# Header 1\n",
+		"<h1>Header 1</h1>\n",
+
+		"## Header 2\n",
+		"<h2>Header 2</h2>\n",
+
+		"### Header 3\n",
+		"<h3>Header 3</h3>\n",
+
+		"#### Header 4\n",
+		"<h4>Header 4</h4>\n",
+
+		"##### Header 5\n",
+		"<h5>Header 5</h5>\n",
+
+		"###### Header 6\n",
+		"<h6>Header 6</h6>\n",
+
+		"####### Header 7\n",
+		"<h6># Header 7</h6>\n",
+
+		"#Header 1\n",
+		"<h1>Header 1</h1>\n",
+
+		"##Header 2\n",
+		"<h2>Header 2</h2>\n",
+
+		"###Header 3\n",
+		"<h3>Header 3</h3>\n",
+
+		"####Header 4\n",
+		"<h4>Header 4</h4>\n",
+
+		"#####Header 5\n",
+		"<h5>Header 5</h5>\n",
+
+		"######Header 6\n",
+		"<h6>Header 6</h6>\n",
+
+		"#######Header 7\n",
+		"<h6>#Header 7</h6>\n",
+
+		"Hello\n# Header 1\nGoodbye\n",
+		"<p>Hello</p>\n\n<h1>Header 1</h1>\n\n<p>Goodbye</p>\n",
+
+		"* List\n# Header\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1>Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"* List\n#Header\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1>Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"*   List\n    * Nested list\n    # Nested header\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
+			"<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n",
+
+		"#Header 1 \\#\n",
+		"<h1>Header 1 #</h1>\n",
+
+		"#Header 1 \\# foo\n",
+		"<h1>Header 1 # foo</h1>\n",
+
+		"#Header 1 #\\##\n",
+		"<h1>Header 1 ##</h1>\n",
+	}
+	doTestsBlock(t, tests, 0)
+}
+
+func TestPrefixHeaderSpaceExtension(t *testing.T) {
+	var tests = []string{
+		"# Header 1\n",
+		"<h1>Header 1</h1>\n",
+
+		"## Header 2\n",
+		"<h2>Header 2</h2>\n",
+
+		"### Header 3\n",
+		"<h3>Header 3</h3>\n",
+
+		"#### Header 4\n",
+		"<h4>Header 4</h4>\n",
+
+		"##### Header 5\n",
+		"<h5>Header 5</h5>\n",
+
+		"###### Header 6\n",
+		"<h6>Header 6</h6>\n",
+
+		"####### Header 7\n",
+		"<p>####### Header 7</p>\n",
+
+		"#Header 1\n",
+		"<p>#Header 1</p>\n",
+
+		"##Header 2\n",
+		"<p>##Header 2</p>\n",
+
+		"###Header 3\n",
+		"<p>###Header 3</p>\n",
+
+		"####Header 4\n",
+		"<p>####Header 4</p>\n",
+
+		"#####Header 5\n",
+		"<p>#####Header 5</p>\n",
+
+		"######Header 6\n",
+		"<p>######Header 6</p>\n",
+
+		"#######Header 7\n",
+		"<p>#######Header 7</p>\n",
+
+		"Hello\n# Header 1\nGoodbye\n",
+		"<p>Hello</p>\n\n<h1>Header 1</h1>\n\n<p>Goodbye</p>\n",
+
+		"* List\n# Header\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1>Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"* List\n#Header\n* List\n",
+		"<ul>\n<li>List\n#Header</li>\n<li>List</li>\n</ul>\n",
+
+		"*   List\n    * Nested list\n    # Nested header\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
+			"<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_SPACE_HEADERS)
+}
+
+func TestPrefixHeaderIdExtension(t *testing.T) {
+	var tests = []string{
+		"# Header 1 {#someid}\n",
+		"<h1 id=\"someid\">Header 1</h1>\n",
+
+		"# Header 1 {#someid}   \n",
+		"<h1 id=\"someid\">Header 1</h1>\n",
+
+		"# Header 1         {#someid}\n",
+		"<h1 id=\"someid\">Header 1</h1>\n",
+
+		"# Header 1 {#someid\n",
+		"<h1>Header 1 {#someid</h1>\n",
+
+		"# Header 1 {#someid\n",
+		"<h1>Header 1 {#someid</h1>\n",
+
+		"# Header 1 {#someid}}\n",
+		"<h1 id=\"someid\">Header 1</h1>\n\n<p>}</p>\n",
+
+		"## Header 2 {#someid}\n",
+		"<h2 id=\"someid\">Header 2</h2>\n",
+
+		"### Header 3 {#someid}\n",
+		"<h3 id=\"someid\">Header 3</h3>\n",
+
+		"#### Header 4 {#someid}\n",
+		"<h4 id=\"someid\">Header 4</h4>\n",
+
+		"##### Header 5 {#someid}\n",
+		"<h5 id=\"someid\">Header 5</h5>\n",
+
+		"###### Header 6 {#someid}\n",
+		"<h6 id=\"someid\">Header 6</h6>\n",
+
+		"####### Header 7 {#someid}\n",
+		"<h6 id=\"someid\"># Header 7</h6>\n",
+
+		"# Header 1 # {#someid}\n",
+		"<h1 id=\"someid\">Header 1</h1>\n",
+
+		"## Header 2 ## {#someid}\n",
+		"<h2 id=\"someid\">Header 2</h2>\n",
+
+		"Hello\n# Header 1\nGoodbye\n",
+		"<p>Hello</p>\n\n<h1>Header 1</h1>\n\n<p>Goodbye</p>\n",
+
+		"* List\n# Header {#someid}\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1 id=\"someid\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"* List\n#Header {#someid}\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1 id=\"someid\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"*   List\n    * Nested list\n    # Nested header {#someid}\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
+			"<h1 id=\"someid\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_HEADER_IDS)
+}
+
+func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
+	var tests = []string{
+		"# header 1 {#someid}\n",
+		"<h1 id=\"PRE:someid:POST\">header 1</h1>\n",
+
+		"## header 2 {#someid}\n",
+		"<h2 id=\"PRE:someid:POST\">header 2</h2>\n",
+
+		"### header 3 {#someid}\n",
+		"<h3 id=\"PRE:someid:POST\">header 3</h3>\n",
+
+		"#### header 4 {#someid}\n",
+		"<h4 id=\"PRE:someid:POST\">header 4</h4>\n",
+
+		"##### header 5 {#someid}\n",
+		"<h5 id=\"PRE:someid:POST\">header 5</h5>\n",
+
+		"###### header 6 {#someid}\n",
+		"<h6 id=\"PRE:someid:POST\">header 6</h6>\n",
+
+		"####### header 7 {#someid}\n",
+		"<h6 id=\"PRE:someid:POST\"># header 7</h6>\n",
+
+		"# header 1 # {#someid}\n",
+		"<h1 id=\"PRE:someid:POST\">header 1</h1>\n",
+
+		"## header 2 ## {#someid}\n",
+		"<h2 id=\"PRE:someid:POST\">header 2</h2>\n",
+
+		"* List\n# Header {#someid}\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1 id=\"PRE:someid:POST\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"* List\n#Header {#someid}\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1 id=\"PRE:someid:POST\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"*   List\n    * Nested list\n    # Nested header {#someid}\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
+			"<h1 id=\"PRE:someid:POST\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
+	}
+
+	parameters := HtmlRendererParameters{
+		HeaderIDPrefix: "PRE:",
+		HeaderIDSuffix: ":POST",
+	}
+
+	doTestsBlockWithRunner(t, tests, EXTENSION_HEADER_IDS, runnerWithRendererParameters(parameters))
+}
+
+func TestPrefixAutoHeaderIdExtension(t *testing.T) {
+	var tests = []string{
+		"# Header 1\n",
+		"<h1 id=\"header-1\">Header 1</h1>\n",
+
+		"# Header 1   \n",
+		"<h1 id=\"header-1\">Header 1</h1>\n",
+
+		"## Header 2\n",
+		"<h2 id=\"header-2\">Header 2</h2>\n",
+
+		"### Header 3\n",
+		"<h3 id=\"header-3\">Header 3</h3>\n",
+
+		"#### Header 4\n",
+		"<h4 id=\"header-4\">Header 4</h4>\n",
+
+		"##### Header 5\n",
+		"<h5 id=\"header-5\">Header 5</h5>\n",
+
+		"###### Header 6\n",
+		"<h6 id=\"header-6\">Header 6</h6>\n",
+
+		"####### Header 7\n",
+		"<h6 id=\"header-7\"># Header 7</h6>\n",
+
+		"Hello\n# Header 1\nGoodbye\n",
+		"<p>Hello</p>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<p>Goodbye</p>\n",
+
+		"* List\n# Header\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1 id=\"header\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"* List\n#Header\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1 id=\"header\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"*   List\n    * Nested list\n    # Nested header\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
+			"<h1 id=\"nested-header\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
+
+		"# Header\n\n# Header\n",
+		"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n",
+
+		"# Header 1\n\n# Header 1",
+		"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
+
+		"# Header\n\n# Header 1\n\n# Header\n\n# Header",
+		"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header</h1>\n\n<h1 id=\"header-1-2\">Header</h1>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS)
+}
+
+func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
+	var tests = []string{
+		"# Header 1\n",
+		"<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n",
+
+		"# Header 1   \n",
+		"<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n",
+
+		"## Header 2\n",
+		"<h2 id=\"PRE:header-2:POST\">Header 2</h2>\n",
+
+		"### Header 3\n",
+		"<h3 id=\"PRE:header-3:POST\">Header 3</h3>\n",
+
+		"#### Header 4\n",
+		"<h4 id=\"PRE:header-4:POST\">Header 4</h4>\n",
+
+		"##### Header 5\n",
+		"<h5 id=\"PRE:header-5:POST\">Header 5</h5>\n",
+
+		"###### Header 6\n",
+		"<h6 id=\"PRE:header-6:POST\">Header 6</h6>\n",
+
+		"####### Header 7\n",
+		"<h6 id=\"PRE:header-7:POST\"># Header 7</h6>\n",
+
+		"Hello\n# Header 1\nGoodbye\n",
+		"<p>Hello</p>\n\n<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<p>Goodbye</p>\n",
+
+		"* List\n# Header\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1 id=\"PRE:header:POST\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"* List\n#Header\n* List\n",
+		"<ul>\n<li><p>List</p>\n\n<h1 id=\"PRE:header:POST\">Header</h1></li>\n\n<li><p>List</p></li>\n</ul>\n",
+
+		"*   List\n    * Nested list\n    # Nested header\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
+			"<h1 id=\"PRE:nested-header:POST\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
+
+		"# Header\n\n# Header\n",
+		"<h1 id=\"PRE:header:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1:POST\">Header</h1>\n",
+
+		"# Header 1\n\n# Header 1",
+		"<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<h1 id=\"PRE:header-1-1:POST\">Header 1</h1>\n",
+
+		"# Header\n\n# Header 1\n\n# Header\n\n# Header",
+		"<h1 id=\"PRE:header:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<h1 id=\"PRE:header-1-1:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1-2:POST\">Header</h1>\n",
+	}
+
+	parameters := HtmlRendererParameters{
+		HeaderIDPrefix: "PRE:",
+		HeaderIDSuffix: ":POST",
+	}
+
+	doTestsBlockWithRunner(t, tests, EXTENSION_AUTO_HEADER_IDS, runnerWithRendererParameters(parameters))
+}
+
+func TestPrefixMultipleHeaderExtensions(t *testing.T) {
+	var tests = []string{
+		"# Header\n\n# Header {#header}\n\n# Header 1",
+		"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS|EXTENSION_HEADER_IDS)
+}
+
+func TestUnderlineHeaders(t *testing.T) {
+	var tests = []string{
+		"Header 1\n========\n",
+		"<h1>Header 1</h1>\n",
+
+		"Header 2\n--------\n",
+		"<h2>Header 2</h2>\n",
+
+		"A\n=\n",
+		"<h1>A</h1>\n",
+
+		"B\n-\n",
+		"<h2>B</h2>\n",
+
+		"Paragraph\nHeader\n=\n",
+		"<p>Paragraph</p>\n\n<h1>Header</h1>\n",
+
+		"Header\n===\nParagraph\n",
+		"<h1>Header</h1>\n\n<p>Paragraph</p>\n",
+
+		"Header\n===\nAnother header\n---\n",
+		"<h1>Header</h1>\n\n<h2>Another header</h2>\n",
+
+		"   Header\n======\n",
+		"<h1>Header</h1>\n",
+
+		"    Code\n========\n",
+		"<pre><code>Code\n</code></pre>\n\n<p>========</p>\n",
+
+		"Header with *inline*\n=====\n",
+		"<h1>Header with <em>inline</em></h1>\n",
+
+		"*   List\n    * Sublist\n    Not a header\n    ------\n",
+		"<ul>\n<li>List\n\n<ul>\n<li>Sublist\nNot a header\n------</li>\n</ul></li>\n</ul>\n",
+
+		"Paragraph\n\n\n\n\nHeader\n===\n",
+		"<p>Paragraph</p>\n\n<h1>Header</h1>\n",
+
+		"Trailing space \n====        \n\n",
+		"<h1>Trailing space</h1>\n",
+
+		"Trailing spaces\n====        \n\n",
+		"<h1>Trailing spaces</h1>\n",
+
+		"Double underline\n=====\n=====\n",
+		"<h1>Double underline</h1>\n\n<p>=====</p>\n",
+	}
+	doTestsBlock(t, tests, 0)
+}
+
+func TestUnderlineHeadersAutoIDs(t *testing.T) {
+	var tests = []string{
+		"Header 1\n========\n",
+		"<h1 id=\"header-1\">Header 1</h1>\n",
+
+		"Header 2\n--------\n",
+		"<h2 id=\"header-2\">Header 2</h2>\n",
+
+		"A\n=\n",
+		"<h1 id=\"a\">A</h1>\n",
+
+		"B\n-\n",
+		"<h2 id=\"b\">B</h2>\n",
+
+		"Paragraph\nHeader\n=\n",
+		"<p>Paragraph</p>\n\n<h1 id=\"header\">Header</h1>\n",
+
+		"Header\n===\nParagraph\n",
+		"<h1 id=\"header\">Header</h1>\n\n<p>Paragraph</p>\n",
+
+		"Header\n===\nAnother header\n---\n",
+		"<h1 id=\"header\">Header</h1>\n\n<h2 id=\"another-header\">Another header</h2>\n",
+
+		"   Header\n======\n",
+		"<h1 id=\"header\">Header</h1>\n",
+
+		"Header with *inline*\n=====\n",
+		"<h1 id=\"header-with-inline\">Header with <em>inline</em></h1>\n",
+
+		"Paragraph\n\n\n\n\nHeader\n===\n",
+		"<p>Paragraph</p>\n\n<h1 id=\"header\">Header</h1>\n",
+
+		"Trailing space \n====        \n\n",
+		"<h1 id=\"trailing-space\">Trailing space</h1>\n",
+
+		"Trailing spaces\n====        \n\n",
+		"<h1 id=\"trailing-spaces\">Trailing spaces</h1>\n",
+
+		"Double underline\n=====\n=====\n",
+		"<h1 id=\"double-underline\">Double underline</h1>\n\n<p>=====</p>\n",
+
+		"Header\n======\n\nHeader\n======\n",
+		"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n",
+
+		"Header 1\n========\n\nHeader 1\n========\n",
+		"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS)
+}
+
+func TestHorizontalRule(t *testing.T) {
+	var tests = []string{
+		"-\n",
+		"<p>-</p>\n",
+
+		"--\n",
+		"<p>--</p>\n",
+
+		"---\n",
+		"<hr />\n",
+
+		"----\n",
+		"<hr />\n",
+
+		"*\n",
+		"<p>*</p>\n",
+
+		"**\n",
+		"<p>**</p>\n",
+
+		"***\n",
+		"<hr />\n",
+
+		"****\n",
+		"<hr />\n",
+
+		"_\n",
+		"<p>_</p>\n",
+
+		"__\n",
+		"<p>__</p>\n",
+
+		"___\n",
+		"<hr />\n",
+
+		"____\n",
+		"<hr />\n",
+
+		"-*-\n",
+		"<p>-*-</p>\n",
+
+		"- - -\n",
+		"<hr />\n",
+
+		"* * *\n",
+		"<hr />\n",
+
+		"_ _ _\n",
+		"<hr />\n",
+
+		"-----*\n",
+		"<p>-----*</p>\n",
+
+		"   ------   \n",
+		"<hr />\n",
+
+		"Hello\n***\n",
+		"<p>Hello</p>\n\n<hr />\n",
+
+		"---\n***\n___\n",
+		"<hr />\n\n<hr />\n\n<hr />\n",
+	}
+	doTestsBlock(t, tests, 0)
+}
+
+func TestUnorderedList(t *testing.T) {
+	var tests = []string{
+		"* Hello\n",
+		"<ul>\n<li>Hello</li>\n</ul>\n",
+
+		"* Yin\n* Yang\n",
+		"<ul>\n<li>Yin</li>\n<li>Yang</li>\n</ul>\n",
+
+		"* Ting\n* Bong\n* Goo\n",
+		"<ul>\n<li>Ting</li>\n<li>Bong</li>\n<li>Goo</li>\n</ul>\n",
+
+		"* Yin\n\n* Yang\n",
+		"<ul>\n<li><p>Yin</p></li>\n\n<li><p>Yang</p></li>\n</ul>\n",
+
+		"* Ting\n\n* Bong\n* Goo\n",
+		"<ul>\n<li><p>Ting</p></li>\n\n<li><p>Bong</p></li>\n\n<li><p>Goo</p></li>\n</ul>\n",
+
+		"+ Hello\n",
+		"<ul>\n<li>Hello</li>\n</ul>\n",
+
+		"+ Yin\n+ Yang\n",
+		"<ul>\n<li>Yin</li>\n<li>Yang</li>\n</ul>\n",
+
+		"+ Ting\n+ Bong\n+ Goo\n",
+		"<ul>\n<li>Ting</li>\n<li>Bong</li>\n<li>Goo</li>\n</ul>\n",
+
+		"+ Yin\n\n+ Yang\n",
+		"<ul>\n<li><p>Yin</p></li>\n\n<li><p>Yang</p></li>\n</ul>\n",
+
+		"+ Ting\n\n+ Bong\n+ Goo\n",
+		"<ul>\n<li><p>Ting</p></li>\n\n<li><p>Bong</p></li>\n\n<li><p>Goo</p></li>\n</ul>\n",
+
+		"- Hello\n",
+		"<ul>\n<li>Hello</li>\n</ul>\n",
+
+		"- Yin\n- Yang\n",
+		"<ul>\n<li>Yin</li>\n<li>Yang</li>\n</ul>\n",
+
+		"- Ting\n- Bong\n- Goo\n",
+		"<ul>\n<li>Ting</li>\n<li>Bong</li>\n<li>Goo</li>\n</ul>\n",
+
+		"- Yin\n\n- Yang\n",
+		"<ul>\n<li><p>Yin</p></li>\n\n<li><p>Yang</p></li>\n</ul>\n",
+
+		"- Ting\n\n- Bong\n- Goo\n",
+		"<ul>\n<li><p>Ting</p></li>\n\n<li><p>Bong</p></li>\n\n<li><p>Goo</p></li>\n</ul>\n",
+
+		"*Hello\n",
+		"<p>*Hello</p>\n",
+
+		"*   Hello \n",
+		"<ul>\n<li>Hello</li>\n</ul>\n",
+
+		"*   Hello \n    Next line \n",
+		"<ul>\n<li>Hello\nNext line</li>\n</ul>\n",
+
+		"Paragraph\n* No linebreak\n",
+		"<p>Paragraph\n* No linebreak</p>\n",
+
+		"Paragraph\n\n* Linebreak\n",
+		"<p>Paragraph</p>\n\n<ul>\n<li>Linebreak</li>\n</ul>\n",
+
+		"*   List\n\n1. Spacer Mixed listing\n",
+		"<ul>\n<li>List</li>\n</ul>\n\n<ol>\n<li>Spacer Mixed listing</li>\n</ol>\n",
+
+		"*   List\n    * Nested list\n",
+		"<ul>\n<li>List\n\n<ul>\n<li>Nested list</li>\n</ul></li>\n</ul>\n",
+
+		"*   List\n\n    * Nested list\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li>Nested list</li>\n</ul></li>\n</ul>\n",
+
+		"*   List\n    Second line\n\n    + Nested\n",
+		"<ul>\n<li><p>List\nSecond line</p>\n\n<ul>\n<li>Nested</li>\n</ul></li>\n</ul>\n",
+
+		"*   List\n    + Nested\n\n    Continued\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li>Nested</li>\n</ul>\n\n<p>Continued</p></li>\n</ul>\n",
+
+		"*   List\n   * shallow indent\n",
+		"<ul>\n<li>List\n\n<ul>\n<li>shallow indent</li>\n</ul></li>\n</ul>\n",
+
+		"* List\n" +
+			" * shallow indent\n" +
+			"  * part of second list\n" +
+			"   * still second\n" +
+			"    * almost there\n" +
+			"     * third level\n",
+		"<ul>\n" +
+			"<li>List\n\n" +
+			"<ul>\n" +
+			"<li>shallow indent</li>\n" +
+			"<li>part of second list</li>\n" +
+			"<li>still second</li>\n" +
+			"<li>almost there\n\n" +
+			"<ul>\n" +
+			"<li>third level</li>\n" +
+			"</ul></li>\n" +
+			"</ul></li>\n" +
+			"</ul>\n",
+
+		"* List\n        extra indent, same paragraph\n",
+		"<ul>\n<li>List\n    extra indent, same paragraph</li>\n</ul>\n",
+
+		"* List\n\n        code block\n",
+		"<ul>\n<li><p>List</p>\n\n<pre><code>code block\n</code></pre></li>\n</ul>\n",
+
+		"* List\n\n          code block with spaces\n",
+		"<ul>\n<li><p>List</p>\n\n<pre><code>  code block with spaces\n</code></pre></li>\n</ul>\n",
+
+		"* List\n\n    * sublist\n\n    normal text\n\n    * another sublist\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li>sublist</li>\n</ul>\n\n<p>normal text</p>\n\n<ul>\n<li>another sublist</li>\n</ul></li>\n</ul>\n",
+
+		`* Foo
+
+        bar
+
+        qux
+`,
+		`<ul>
+<li><p>Foo</p>
+
+<pre><code>bar
+
+qux
+</code></pre></li>
+</ul>
+`,
+	}
+	doTestsBlock(t, tests, 0)
+}
+
+func TestFencedCodeBlockWithinList(t *testing.T) {
+	doTestsBlock(t, []string{
+		"* Foo\n\n    ```\n    bar\n\n    qux\n    ```\n",
+		`<ul>
+<li><p>Foo</p>
+
+<pre><code>bar
+
+qux
+</code></pre></li>
+</ul>
+`,
+	}, EXTENSION_FENCED_CODE)
+}
+
+func TestOrderedList(t *testing.T) {
+	var tests = []string{
+		"1. Hello\n",
+		"<ol>\n<li>Hello</li>\n</ol>\n",
+
+		"1. Yin\n2. Yang\n",
+		"<ol>\n<li>Yin</li>\n<li>Yang</li>\n</ol>\n",
+
+		"1. Ting\n2. Bong\n3. Goo\n",
+		"<ol>\n<li>Ting</li>\n<li>Bong</li>\n<li>Goo</li>\n</ol>\n",
+
+		"1. Yin\n\n2. Yang\n",
+		"<ol>\n<li><p>Yin</p></li>\n\n<li><p>Yang</p></li>\n</ol>\n",
+
+		"1. Ting\n\n2. Bong\n3. Goo\n",
+		"<ol>\n<li><p>Ting</p></li>\n\n<li><p>Bong</p></li>\n\n<li><p>Goo</p></li>\n</ol>\n",
+
+		"1 Hello\n",
+		"<p>1 Hello</p>\n",
+
+		"1.Hello\n",
+		"<p>1.Hello</p>\n",
+
+		"1.  Hello \n",
+		"<ol>\n<li>Hello</li>\n</ol>\n",
+
+		"1.  Hello \n    Next line \n",
+		"<ol>\n<li>Hello\nNext line</li>\n</ol>\n",
+
+		"Paragraph\n1. No linebreak\n",
+		"<p>Paragraph\n1. No linebreak</p>\n",
+
+		"Paragraph\n\n1. Linebreak\n",
+		"<p>Paragraph</p>\n\n<ol>\n<li>Linebreak</li>\n</ol>\n",
+
+		"1.  List\n    1. Nested list\n",
+		"<ol>\n<li>List\n\n<ol>\n<li>Nested list</li>\n</ol></li>\n</ol>\n",
+
+		"1.  List\n\n    1. Nested list\n",
+		"<ol>\n<li><p>List</p>\n\n<ol>\n<li>Nested list</li>\n</ol></li>\n</ol>\n",
+
+		"1.  List\n    Second line\n\n    1. Nested\n",
+		"<ol>\n<li><p>List\nSecond line</p>\n\n<ol>\n<li>Nested</li>\n</ol></li>\n</ol>\n",
+
+		"1.  List\n    1. Nested\n\n    Continued\n",
+		"<ol>\n<li><p>List</p>\n\n<ol>\n<li>Nested</li>\n</ol>\n\n<p>Continued</p></li>\n</ol>\n",
+
+		"1.  List\n   1. shallow indent\n",
+		"<ol>\n<li>List\n\n<ol>\n<li>shallow indent</li>\n</ol></li>\n</ol>\n",
+
+		"1. List\n" +
+			" 1. shallow indent\n" +
+			"  2. part of second list\n" +
+			"   3. still second\n" +
+			"    4. almost there\n" +
+			"     1. third level\n",
+		"<ol>\n" +
+			"<li>List\n\n" +
+			"<ol>\n" +
+			"<li>shallow indent</li>\n" +
+			"<li>part of second list</li>\n" +
+			"<li>still second</li>\n" +
+			"<li>almost there\n\n" +
+			"<ol>\n" +
+			"<li>third level</li>\n" +
+			"</ol></li>\n" +
+			"</ol></li>\n" +
+			"</ol>\n",
+
+		"1. List\n        extra indent, same paragraph\n",
+		"<ol>\n<li>List\n    extra indent, same paragraph</li>\n</ol>\n",
+
+		"1. List\n\n        code block\n",
+		"<ol>\n<li><p>List</p>\n\n<pre><code>code block\n</code></pre></li>\n</ol>\n",
+
+		"1. List\n\n          code block with spaces\n",
+		"<ol>\n<li><p>List</p>\n\n<pre><code>  code block with spaces\n</code></pre></li>\n</ol>\n",
+
+		"1. List\n\n* Spacer Mixed listing\n",
+		"<ol>\n<li>List</li>\n</ol>\n\n<ul>\n<li>Spacer Mixed listing</li>\n</ul>\n",
+
+		"1. List\n* Mixed listing\n",
+		"<ol>\n<li>List</li>\n<li>Mixed listing</li>\n</ol>\n",
+
+		"1. List\n    * Mixted list\n",
+		"<ol>\n<li>List\n\n<ul>\n<li>Mixted list</li>\n</ul></li>\n</ol>\n",
+
+		"1. List\n * Mixed list\n",
+		"<ol>\n<li>List\n\n<ul>\n<li>Mixed list</li>\n</ul></li>\n</ol>\n",
+
+		"* Start with unordered\n 1. Ordered\n",
+		"<ul>\n<li>Start with unordered\n\n<ol>\n<li>Ordered</li>\n</ol></li>\n</ul>\n",
+
+		"* Start with unordered\n    1. Ordered\n",
+		"<ul>\n<li>Start with unordered\n\n<ol>\n<li>Ordered</li>\n</ol></li>\n</ul>\n",
+
+		"1. numbers\n1. are ignored\n",
+		"<ol>\n<li>numbers</li>\n<li>are ignored</li>\n</ol>\n",
+
+		`1. Foo
+
+        bar
+
+
+
+        qux
+`,
+		`<ol>
+<li><p>Foo</p>
+
+<pre><code>bar
+
+
+
+qux
+</code></pre></li>
+</ol>
+`,
+	}
+	doTestsBlock(t, tests, 0)
+}
+
+func TestDefinitionList(t *testing.T) {
+	var tests = []string{
+		"Term 1\n:   Definition a\n",
+		"<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n",
+
+		"Term 1\n:   Definition a \n",
+		"<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n",
+
+		"Term 1\n:   Definition a\n:   Definition b\n",
+		"<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n<dd>Definition b</dd>\n</dl>\n",
+
+		"Term 1\n:   Definition a\n\nTerm 2\n:   Definition b\n",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd>Definition a</dd>\n" +
+			"<dt>Term 2</dt>\n" +
+			"<dd>Definition b</dd>\n" +
+			"</dl>\n",
+
+		"Term 1\n:   Definition a\n\nTerm 2\n:   Definition b\n\nTerm 3\n:   Definition c\n",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd>Definition a</dd>\n" +
+			"<dt>Term 2</dt>\n" +
+			"<dd>Definition b</dd>\n" +
+			"<dt>Term 3</dt>\n" +
+			"<dd>Definition c</dd>\n" +
+			"</dl>\n",
+
+		"Term 1\n:   Definition a\n:   Definition b\n\nTerm 2\n:   Definition c\n",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd>Definition a</dd>\n" +
+			"<dd>Definition b</dd>\n" +
+			"<dt>Term 2</dt>\n" +
+			"<dd>Definition c</dd>\n" +
+			"</dl>\n",
+
+		"Term 1\n\n:   Definition a\n\nTerm 2\n\n:   Definition b\n",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd><p>Definition a</p></dd>\n" +
+			"<dt>Term 2</dt>\n" +
+			"<dd><p>Definition b</p></dd>\n" +
+			"</dl>\n",
+
+		"Term 1\n\n:   Definition a\n\n:   Definition b\n\nTerm 2\n\n:   Definition c\n",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd><p>Definition a</p></dd>\n" +
+			"<dd><p>Definition b</p></dd>\n" +
+			"<dt>Term 2</dt>\n" +
+			"<dd><p>Definition c</p></dd>\n" +
+			"</dl>\n",
+
+		"Term 1\n:   Definition a\nNext line\n",
+		"<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n",
+
+		"Term 1\n:   Definition a\n  Next line\n",
+		"<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n",
+
+		"Term 1\n:   Definition a \n  Next line \n",
+		"<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n",
+
+		"Term 1\n:   Definition a\nNext line\n\nTerm 2\n:   Definition b",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd>Definition a\nNext line</dd>\n" +
+			"<dt>Term 2</dt>\n" +
+			"<dd>Definition b</dd>\n" +
+			"</dl>\n",
+
+		"Term 1\n: Definition a\n",
+		"<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n",
+
+		"Term 1\n:Definition a\n",
+		"<p>Term 1\n:Definition a</p>\n",
+
+		"Term 1\n\n:   Definition a\n\nTerm 2\n\n:   Definition b\n\nText 1",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd><p>Definition a</p></dd>\n" +
+			"<dt>Term 2</dt>\n" +
+			"<dd><p>Definition b</p></dd>\n" +
+			"</dl>\n" +
+			"\n<p>Text 1</p>\n",
+
+		"Term 1\n\n:   Definition a\n\nText 1\n\nTerm 2\n\n:   Definition b\n\nText 2",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd><p>Definition a</p></dd>\n" +
+			"</dl>\n" +
+			"\n<p>Text 1</p>\n" +
+			"\n<dl>\n" +
+			"<dt>Term 2</dt>\n" +
+			"<dd><p>Definition b</p></dd>\n" +
+			"</dl>\n" +
+			"\n<p>Text 2</p>\n",
+
+		"Term 1\n:   Definition a\n\n    Text 1\n\n    1. First\n    2. Second",
+		"<dl>\n" +
+			"<dt>Term 1</dt>\n" +
+			"<dd><p>Definition a</p>\n\n" +
+			"<p>Text 1</p>\n\n" +
+			"<ol>\n<li>First</li>\n<li>Second</li>\n</ol></dd>\n" +
+			"</dl>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_DEFINITION_LISTS)
+}
+
+func TestPreformattedHtml(t *testing.T) {
+	var tests = []string{
+		"<div></div>\n",
+		"<div></div>\n",
+
+		"<div>\n</div>\n",
+		"<div>\n</div>\n",
+
+		"<div>\n</div>\nParagraph\n",
+		"<p><div>\n</div>\nParagraph</p>\n",
+
+		"<div class=\"foo\">\n</div>\n",
+		"<div class=\"foo\">\n</div>\n",
+
+		"<div>\nAnything here\n</div>\n",
+		"<div>\nAnything here\n</div>\n",
+
+		"<div>\n  Anything here\n</div>\n",
+		"<div>\n  Anything here\n</div>\n",
+
+		"<div>\nAnything here\n  </div>\n",
+		"<div>\nAnything here\n  </div>\n",
+
+		"<div>\nThis is *not* &proceessed\n</div>\n",
+		"<div>\nThis is *not* &proceessed\n</div>\n",
+
+		"<faketag>\n  Something\n</faketag>\n",
+		"<p><faketag>\n  Something\n</faketag></p>\n",
+
+		"<div>\n  Something here\n</divv>\n",
+		"<p><div>\n  Something here\n</divv></p>\n",
+
+		"Paragraph\n<div>\nHere? >&<\n</div>\n",
+		"<p>Paragraph\n<div>\nHere? &gt;&amp;&lt;\n</div></p>\n",
+
+		"Paragraph\n\n<div>\nHow about here? >&<\n</div>\n",
+		"<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n",
+
+		"Paragraph\n<div>\nHere? >&<\n</div>\nAnd here?\n",
+		"<p>Paragraph\n<div>\nHere? &gt;&amp;&lt;\n</div>\nAnd here?</p>\n",
+
+		"Paragraph\n\n<div>\nHow about here? >&<\n</div>\nAnd here?\n",
+		"<p>Paragraph</p>\n\n<p><div>\nHow about here? &gt;&amp;&lt;\n</div>\nAnd here?</p>\n",
+
+		"Paragraph\n<div>\nHere? >&<\n</div>\n\nAnd here?\n",
+		"<p>Paragraph\n<div>\nHere? &gt;&amp;&lt;\n</div></p>\n\n<p>And here?</p>\n",
+
+		"Paragraph\n\n<div>\nHow about here? >&<\n</div>\n\nAnd here?\n",
+		"<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n\n<p>And here?</p>\n",
+	}
+	doTestsBlock(t, tests, 0)
+}
+
+func TestPreformattedHtmlLax(t *testing.T) {
+	var tests = []string{
+		"Paragraph\n<div>\nHere? >&<\n</div>\n",
+		"<p>Paragraph</p>\n\n<div>\nHere? >&<\n</div>\n",
+
+		"Paragraph\n\n<div>\nHow about here? >&<\n</div>\n",
+		"<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n",
+
+		"Paragraph\n<div>\nHere? >&<\n</div>\nAnd here?\n",
+		"<p>Paragraph</p>\n\n<div>\nHere? >&<\n</div>\n\n<p>And here?</p>\n",
+
+		"Paragraph\n\n<div>\nHow about here? >&<\n</div>\nAnd here?\n",
+		"<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n\n<p>And here?</p>\n",
+
+		"Paragraph\n<div>\nHere? >&<\n</div>\n\nAnd here?\n",
+		"<p>Paragraph</p>\n\n<div>\nHere? >&<\n</div>\n\n<p>And here?</p>\n",
+
+		"Paragraph\n\n<div>\nHow about here? >&<\n</div>\n\nAnd here?\n",
+		"<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n\n<p>And here?</p>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_LAX_HTML_BLOCKS)
+}
+
+func TestFencedCodeBlock(t *testing.T) {
+	var tests = []string{
+		"``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n",
+		"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",
+
+		"``` c\n/* special & char < > \" escaping */\n```\n",
+		"<pre><code class=\"language-c\">/* special &amp; char &lt; &gt; &quot; escaping */\n</code></pre>\n",
+
+		"``` c\nno *inline* processing ~~of text~~\n```\n",
+		"<pre><code class=\"language-c\">no *inline* processing ~~of text~~\n</code></pre>\n",
+
+		"```\nNo language\n```\n",
+		"<pre><code>No language\n</code></pre>\n",
+
+		"``` {ocaml}\nlanguage in braces\n```\n",
+		"<pre><code class=\"language-ocaml\">language in braces\n</code></pre>\n",
+
+		"```    {ocaml}      \nwith extra whitespace\n```\n",
+		"<pre><code class=\"language-ocaml\">with extra whitespace\n</code></pre>\n",
+
+		"```{   ocaml   }\nwith extra whitespace\n```\n",
+		"<pre><code class=\"language-ocaml\">with extra whitespace\n</code></pre>\n",
+
+		"~ ~~ java\nWith whitespace\n~~~\n",
+		"<p>~ ~~ java\nWith whitespace\n~~~</p>\n",
+
+		"~~\nonly two\n~~\n",
+		"<p>~~\nonly two\n~~</p>\n",
+
+		"```` python\nextra\n````\n",
+		"<pre><code class=\"language-python\">extra\n</code></pre>\n",
+
+		"~~~ perl\nthree to start, four to end\n~~~~\n",
+		"<p>~~~ perl\nthree to start, four to end\n~~~~</p>\n",
+
+		"~~~~ perl\nfour to start, three to end\n~~~\n",
+		"<p>~~~~ perl\nfour to start, three to end\n~~~</p>\n",
+
+		"~~~ bash\ntildes\n~~~\n",
+		"<pre><code class=\"language-bash\">tildes\n</code></pre>\n",
+
+		"``` lisp\nno ending\n",
+		"<p>``` lisp\nno ending</p>\n",
+
+		"~~~ lisp\nend with language\n~~~ lisp\n",
+		"<p>~~~ lisp\nend with language\n~~~ lisp</p>\n",
+
+		"```\nmismatched begin and end\n~~~\n",
+		"<p>```\nmismatched begin and end\n~~~</p>\n",
+
+		"~~~\nmismatched begin and end\n```\n",
+		"<p>~~~\nmismatched begin and end\n```</p>\n",
+
+		"   ``` oz\nleading spaces\n```\n",
+		"<pre><code class=\"language-oz\">leading spaces\n</code></pre>\n",
+
+		"  ``` oz\nleading spaces\n ```\n",
+		"<pre><code class=\"language-oz\">leading spaces\n</code></pre>\n",
+
+		" ``` oz\nleading spaces\n  ```\n",
+		"<pre><code class=\"language-oz\">leading spaces\n</code></pre>\n",
+
+		"``` oz\nleading spaces\n   ```\n",
+		"<pre><code class=\"language-oz\">leading spaces\n</code></pre>\n",
+
+		"    ``` oz\nleading spaces\n    ```\n",
+		"<pre><code>``` oz\n</code></pre>\n\n<p>leading spaces\n    ```</p>\n",
+
+		"Bla bla\n\n``` oz\ncode blocks breakup paragraphs\n```\n\nBla Bla\n",
+		"<p>Bla bla</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>Bla Bla</p>\n",
+
+		"Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nAnd some text after a fenced code block",
+		"<p>Some text before a fenced code block</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>And some text after a fenced code block</p>\n",
+
+		"`",
+		"<p>`</p>\n",
+
+		"Bla bla\n\n``` oz\ncode blocks breakup paragraphs\n```\n\nBla Bla\n\n``` oz\nmultiple code blocks work okay\n```\n\nBla Bla\n",
+		"<p>Bla bla</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>Bla Bla</p>\n\n<pre><code class=\"language-oz\">multiple code blocks work okay\n</code></pre>\n\n<p>Bla Bla</p>\n",
+
+		"Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nSome text in between\n``` oz\nmultiple code blocks work okay\n```\nAnd some text after a fenced code block",
+		"<p>Some text before a fenced code block</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>Some text in between</p>\n\n<pre><code class=\"language-oz\">multiple code blocks work okay\n</code></pre>\n\n<p>And some text after a fenced code block</p>\n",
+
+		"```\n[]:()\n```\n",
+		"<pre><code>[]:()\n</code></pre>\n",
+
+		"```\n[]:()\n[]:)\n[]:(\n[]:x\n[]:testing\n[:testing\n\n[]:\nlinebreak\n[]()\n\n[]:\n[]()\n```",
+		"<pre><code>[]:()\n[]:)\n[]:(\n[]:x\n[]:testing\n[:testing\n\n[]:\nlinebreak\n[]()\n\n[]:\n[]()\n</code></pre>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_FENCED_CODE)
+}
+
+func TestFencedCodeInsideBlockquotes(t *testing.T) {
+	cat := func(s ...string) string { return strings.Join(s, "\n") }
+	var tests = []string{
+		cat("> ```go",
+			"package moo",
+			"",
+			"```",
+			""),
+		`<blockquote>
+<pre><code class="language-go">package moo
+
+</code></pre>
+</blockquote>
+`,
+		// -------------------------------------------
+		cat("> foo",
+			"> ",
+			"> ```go",
+			"package moo",
+			"```",
+			"> ",
+			"> goo.",
+			""),
+		`<blockquote>
+<p>foo</p>
+
+<pre><code class="language-go">package moo
+</code></pre>
+
+<p>goo.</p>
+</blockquote>
+`,
+		// -------------------------------------------
+		cat("> foo",
+			"> ",
+			"> quote",
+			"continues",
+			"```",
+			""),
+		`<blockquote>
+<p>foo</p>
+
+<p>quote
+continues
+` + "```" + `</p>
+</blockquote>
+`,
+		// -------------------------------------------
+		cat("> foo",
+			"> ",
+			"> ```go",
+			"package moo",
+			"```",
+			"> ",
+			"> goo.",
+			"> ",
+			"> ```go",
+			"package zoo",
+			"```",
+			"> ",
+			"> woo.",
+			""),
+		`<blockquote>
+<p>foo</p>
+
+<pre><code class="language-go">package moo
+</code></pre>
+
+<p>goo.</p>
+
+<pre><code class="language-go">package zoo
+</code></pre>
+
+<p>woo.</p>
+</blockquote>
+`,
+	}
+
+	// These 2 alternative forms of blockquoted fenced code blocks should produce same output.
+	forms := [2]string{
+		cat("> plain quoted text",
+			"> ```fenced",
+			"code",
+			" with leading single space correctly preserved",
+			"okay",
+			"```",
+			"> rest of quoted text"),
+		cat("> plain quoted text",
+			"> ```fenced",
+			"> code",
+			">  with leading single space correctly preserved",
+			"> okay",
+			"> ```",
+			"> rest of quoted text"),
+	}
+	want := `<blockquote>
+<p>plain quoted text</p>
+
+<pre><code class="language-fenced">code
+ with leading single space correctly preserved
+okay
+</code></pre>
+
+<p>rest of quoted text</p>
+</blockquote>
+`
+	tests = append(tests, forms[0], want)
+	tests = append(tests, forms[1], want)
+
+	doTestsBlock(t, tests, EXTENSION_FENCED_CODE)
+}
+
+func TestTable(t *testing.T) {
+	var tests = []string{
+		"a | b\n---|---\nc | d\n",
+		"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n\n" +
+			"<tbody>\n<tr>\n<td>c</td>\n<td>d</td>\n</tr>\n</tbody>\n</table>\n",
+
+		"a | b\n---|--\nc | d\n",
+		"<p>a | b\n---|--\nc | d</p>\n",
+
+		"|a|b|c|d|\n|----|----|----|---|\n|e|f|g|h|\n",
+		"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n<th>d</th>\n</tr>\n</thead>\n\n" +
+			"<tbody>\n<tr>\n<td>e</td>\n<td>f</td>\n<td>g</td>\n<td>h</td>\n</tr>\n</tbody>\n</table>\n",
+
+		"*a*|__b__|[c](C)|d\n---|---|---|---\ne|f|g|h\n",
+		"<table>\n<thead>\n<tr>\n<th><em>a</em></th>\n<th><strong>b</strong></th>\n<th><a href=\"C\">c</a></th>\n<th>d</th>\n</tr>\n</thead>\n\n" +
+			"<tbody>\n<tr>\n<td>e</td>\n<td>f</td>\n<td>g</td>\n<td>h</td>\n</tr>\n</tbody>\n</table>\n",
+
+		"a|b|c\n---|---|---\nd|e|f\ng|h\ni|j|k|l|m\nn|o|p\n",
+		"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n</tr>\n</thead>\n\n" +
+			"<tbody>\n<tr>\n<td>d</td>\n<td>e</td>\n<td>f</td>\n</tr>\n\n" +
+			"<tr>\n<td>g</td>\n<td>h</td>\n<td></td>\n</tr>\n\n" +
+			"<tr>\n<td>i</td>\n<td>j</td>\n<td>k</td>\n</tr>\n\n" +
+			"<tr>\n<td>n</td>\n<td>o</td>\n<td>p</td>\n</tr>\n</tbody>\n</table>\n",
+
+		"a|b|c\n---|---|---\n*d*|__e__|f\n",
+		"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n</tr>\n</thead>\n\n" +
+			"<tbody>\n<tr>\n<td><em>d</em></td>\n<td><strong>e</strong></td>\n<td>f</td>\n</tr>\n</tbody>\n</table>\n",
+
+		"a|b|c|d\n:--|--:|:-:|---\ne|f|g|h\n",
+		"<table>\n<thead>\n<tr>\n<th align=\"left\">a</th>\n<th align=\"right\">b</th>\n" +
+			"<th align=\"center\">c</th>\n<th>d</th>\n</tr>\n</thead>\n\n" +
+			"<tbody>\n<tr>\n<td align=\"left\">e</td>\n<td align=\"right\">f</td>\n" +
+			"<td align=\"center\">g</td>\n<td>h</td>\n</tr>\n</tbody>\n</table>\n",
+
+		"a|b|c\n---|---|---\n",
+		"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n</tr>\n</thead>\n\n<tbody>\n</tbody>\n</table>\n",
+
+		"a| b|c | d | e\n---|---|---|---|---\nf| g|h | i |j\n",
+		"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n<th>d</th>\n<th>e</th>\n</tr>\n</thead>\n\n" +
+			"<tbody>\n<tr>\n<td>f</td>\n<td>g</td>\n<td>h</td>\n<td>i</td>\n<td>j</td>\n</tr>\n</tbody>\n</table>\n",
+
+		"a|b\\|c|d\n---|---|---\nf|g\\|h|i\n",
+		"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b|c</th>\n<th>d</th>\n</tr>\n</thead>\n\n<tbody>\n<tr>\n<td>f</td>\n<td>g|h</td>\n<td>i</td>\n</tr>\n</tbody>\n</table>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_TABLES)
+}
+
+func TestUnorderedListWith_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
+	var tests = []string{
+		"* Hello\n",
+		"<ul>\n<li>Hello</li>\n</ul>\n",
+
+		"* Yin\n* Yang\n",
+		"<ul>\n<li>Yin</li>\n<li>Yang</li>\n</ul>\n",
+
+		"* Ting\n* Bong\n* Goo\n",
+		"<ul>\n<li>Ting</li>\n<li>Bong</li>\n<li>Goo</li>\n</ul>\n",
+
+		"* Yin\n\n* Yang\n",
+		"<ul>\n<li><p>Yin</p></li>\n\n<li><p>Yang</p></li>\n</ul>\n",
+
+		"* Ting\n\n* Bong\n* Goo\n",
+		"<ul>\n<li><p>Ting</p></li>\n\n<li><p>Bong</p></li>\n\n<li><p>Goo</p></li>\n</ul>\n",
+
+		"+ Hello\n",
+		"<ul>\n<li>Hello</li>\n</ul>\n",
+
+		"+ Yin\n+ Yang\n",
+		"<ul>\n<li>Yin</li>\n<li>Yang</li>\n</ul>\n",
+
+		"+ Ting\n+ Bong\n+ Goo\n",
+		"<ul>\n<li>Ting</li>\n<li>Bong</li>\n<li>Goo</li>\n</ul>\n",
+
+		"+ Yin\n\n+ Yang\n",
+		"<ul>\n<li><p>Yin</p></li>\n\n<li><p>Yang</p></li>\n</ul>\n",
+
+		"+ Ting\n\n+ Bong\n+ Goo\n",
+		"<ul>\n<li><p>Ting</p></li>\n\n<li><p>Bong</p></li>\n\n<li><p>Goo</p></li>\n</ul>\n",
+
+		"- Hello\n",
+		"<ul>\n<li>Hello</li>\n</ul>\n",
+
+		"- Yin\n- Yang\n",
+		"<ul>\n<li>Yin</li>\n<li>Yang</li>\n</ul>\n",
+
+		"- Ting\n- Bong\n- Goo\n",
+		"<ul>\n<li>Ting</li>\n<li>Bong</li>\n<li>Goo</li>\n</ul>\n",
+
+		"- Yin\n\n- Yang\n",
+		"<ul>\n<li><p>Yin</p></li>\n\n<li><p>Yang</p></li>\n</ul>\n",
+
+		"- Ting\n\n- Bong\n- Goo\n",
+		"<ul>\n<li><p>Ting</p></li>\n\n<li><p>Bong</p></li>\n\n<li><p>Goo</p></li>\n</ul>\n",
+
+		"*Hello\n",
+		"<p>*Hello</p>\n",
+
+		"*   Hello \n",
+		"<ul>\n<li>Hello</li>\n</ul>\n",
+
+		"*   Hello \n    Next line \n",
+		"<ul>\n<li>Hello\nNext line</li>\n</ul>\n",
+
+		"Paragraph\n* No linebreak\n",
+		"<p>Paragraph</p>\n\n<ul>\n<li>No linebreak</li>\n</ul>\n",
+
+		"Paragraph\n\n* Linebreak\n",
+		"<p>Paragraph</p>\n\n<ul>\n<li>Linebreak</li>\n</ul>\n",
+
+		"*   List\n    * Nested list\n",
+		"<ul>\n<li>List\n\n<ul>\n<li>Nested list</li>\n</ul></li>\n</ul>\n",
+
+		"*   List\n\n    * Nested list\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li>Nested list</li>\n</ul></li>\n</ul>\n",
+
+		"*   List\n    Second line\n\n    + Nested\n",
+		"<ul>\n<li><p>List\nSecond line</p>\n\n<ul>\n<li>Nested</li>\n</ul></li>\n</ul>\n",
+
+		"*   List\n    + Nested\n\n    Continued\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li>Nested</li>\n</ul>\n\n<p>Continued</p></li>\n</ul>\n",
+
+		"*   List\n   * shallow indent\n",
+		"<ul>\n<li>List\n\n<ul>\n<li>shallow indent</li>\n</ul></li>\n</ul>\n",
+
+		"* List\n" +
+			" * shallow indent\n" +
+			"  * part of second list\n" +
+			"   * still second\n" +
+			"    * almost there\n" +
+			"     * third level\n",
+		"<ul>\n" +
+			"<li>List\n\n" +
+			"<ul>\n" +
+			"<li>shallow indent</li>\n" +
+			"<li>part of second list</li>\n" +
+			"<li>still second</li>\n" +
+			"<li>almost there\n\n" +
+			"<ul>\n" +
+			"<li>third level</li>\n" +
+			"</ul></li>\n" +
+			"</ul></li>\n" +
+			"</ul>\n",
+
+		"* List\n        extra indent, same paragraph\n",
+		"<ul>\n<li>List\n    extra indent, same paragraph</li>\n</ul>\n",
+
+		"* List\n\n        code block\n",
+		"<ul>\n<li><p>List</p>\n\n<pre><code>code block\n</code></pre></li>\n</ul>\n",
+
+		"* List\n\n          code block with spaces\n",
+		"<ul>\n<li><p>List</p>\n\n<pre><code>  code block with spaces\n</code></pre></li>\n</ul>\n",
+
+		"* List\n\n    * sublist\n\n    normal text\n\n    * another sublist\n",
+		"<ul>\n<li><p>List</p>\n\n<ul>\n<li>sublist</li>\n</ul>\n\n<p>normal text</p>\n\n<ul>\n<li>another sublist</li>\n</ul></li>\n</ul>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
+}
+
+func TestOrderedList_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
+	var tests = []string{
+		"1. Hello\n",
+		"<ol>\n<li>Hello</li>\n</ol>\n",
+
+		"1. Yin\n2. Yang\n",
+		"<ol>\n<li>Yin</li>\n<li>Yang</li>\n</ol>\n",
+
+		"1. Ting\n2. Bong\n3. Goo\n",
+		"<ol>\n<li>Ting</li>\n<li>Bong</li>\n<li>Goo</li>\n</ol>\n",
+
+		"1. Yin\n\n2. Yang\n",
+		"<ol>\n<li><p>Yin</p></li>\n\n<li><p>Yang</p></li>\n</ol>\n",
+
+		"1. Ting\n\n2. Bong\n3. Goo\n",
+		"<ol>\n<li><p>Ting</p></li>\n\n<li><p>Bong</p></li>\n\n<li><p>Goo</p></li>\n</ol>\n",
+
+		"1 Hello\n",
+		"<p>1 Hello</p>\n",
+
+		"1.Hello\n",
+		"<p>1.Hello</p>\n",
+
+		"1.  Hello \n",
+		"<ol>\n<li>Hello</li>\n</ol>\n",
+
+		"1.  Hello \n    Next line \n",
+		"<ol>\n<li>Hello\nNext line</li>\n</ol>\n",
+
+		"Paragraph\n1. No linebreak\n",
+		"<p>Paragraph</p>\n\n<ol>\n<li>No linebreak</li>\n</ol>\n",
+
+		"Paragraph\n\n1. Linebreak\n",
+		"<p>Paragraph</p>\n\n<ol>\n<li>Linebreak</li>\n</ol>\n",
+
+		"1.  List\n    1. Nested list\n",
+		"<ol>\n<li>List\n\n<ol>\n<li>Nested list</li>\n</ol></li>\n</ol>\n",
+
+		"1.  List\n\n    1. Nested list\n",
+		"<ol>\n<li><p>List</p>\n\n<ol>\n<li>Nested list</li>\n</ol></li>\n</ol>\n",
+
+		"1.  List\n    Second line\n\n    1. Nested\n",
+		"<ol>\n<li><p>List\nSecond line</p>\n\n<ol>\n<li>Nested</li>\n</ol></li>\n</ol>\n",
+
+		"1.  List\n    1. Nested\n\n    Continued\n",
+		"<ol>\n<li><p>List</p>\n\n<ol>\n<li>Nested</li>\n</ol>\n\n<p>Continued</p></li>\n</ol>\n",
+
+		"1.  List\n   1. shallow indent\n",
+		"<ol>\n<li>List\n\n<ol>\n<li>shallow indent</li>\n</ol></li>\n</ol>\n",
+
+		"1. List\n" +
+			" 1. shallow indent\n" +
+			"  2. part of second list\n" +
+			"   3. still second\n" +
+			"    4. almost there\n" +
+			"     1. third level\n",
+		"<ol>\n" +
+			"<li>List\n\n" +
+			"<ol>\n" +
+			"<li>shallow indent</li>\n" +
+			"<li>part of second list</li>\n" +
+			"<li>still second</li>\n" +
+			"<li>almost there\n\n" +
+			"<ol>\n" +
+			"<li>third level</li>\n" +
+			"</ol></li>\n" +
+			"</ol></li>\n" +
+			"</ol>\n",
+
+		"1. List\n        extra indent, same paragraph\n",
+		"<ol>\n<li>List\n    extra indent, same paragraph</li>\n</ol>\n",
+
+		"1. List\n\n        code block\n",
+		"<ol>\n<li><p>List</p>\n\n<pre><code>code block\n</code></pre></li>\n</ol>\n",
+
+		"1. List\n\n          code block with spaces\n",
+		"<ol>\n<li><p>List</p>\n\n<pre><code>  code block with spaces\n</code></pre></li>\n</ol>\n",
+
+		"1. List\n    * Mixted list\n",
+		"<ol>\n<li>List\n\n<ul>\n<li>Mixted list</li>\n</ul></li>\n</ol>\n",
+
+		"1. List\n * Mixed list\n",
+		"<ol>\n<li>List\n\n<ul>\n<li>Mixed list</li>\n</ul></li>\n</ol>\n",
+
+		"* Start with unordered\n 1. Ordered\n",
+		"<ul>\n<li>Start with unordered\n\n<ol>\n<li>Ordered</li>\n</ol></li>\n</ul>\n",
+
+		"* Start with unordered\n    1. Ordered\n",
+		"<ul>\n<li>Start with unordered\n\n<ol>\n<li>Ordered</li>\n</ol></li>\n</ul>\n",
+
+		"1. numbers\n1. are ignored\n",
+		"<ol>\n<li>numbers</li>\n<li>are ignored</li>\n</ol>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
+}
+
+func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
+	var tests = []string{
+		"``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n",
+		"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",
+
+		"``` c\n/* special & char < > \" escaping */\n```\n",
+		"<pre><code class=\"language-c\">/* special &amp; char &lt; &gt; &quot; escaping */\n</code></pre>\n",
+
+		"``` c\nno *inline* processing ~~of text~~\n```\n",
+		"<pre><code class=\"language-c\">no *inline* processing ~~of text~~\n</code></pre>\n",
+
+		"```\nNo language\n```\n",
+		"<pre><code>No language\n</code></pre>\n",
+
+		"``` {ocaml}\nlanguage in braces\n```\n",
+		"<pre><code class=\"language-ocaml\">language in braces\n</code></pre>\n",
+
+		"```    {ocaml}      \nwith extra whitespace\n```\n",
+		"<pre><code class=\"language-ocaml\">with extra whitespace\n</code></pre>\n",
+
+		"```{   ocaml   }\nwith extra whitespace\n```\n",
+		"<pre><code class=\"language-ocaml\">with extra whitespace\n</code></pre>\n",
+
+		"~ ~~ java\nWith whitespace\n~~~\n",
+		"<p>~ ~~ java\nWith whitespace\n~~~</p>\n",
+
+		"~~\nonly two\n~~\n",
+		"<p>~~\nonly two\n~~</p>\n",
+
+		"```` python\nextra\n````\n",
+		"<pre><code class=\"language-python\">extra\n</code></pre>\n",
+
+		"~~~ perl\nthree to start, four to end\n~~~~\n",
+		"<p>~~~ perl\nthree to start, four to end\n~~~~</p>\n",
+
+		"~~~~ perl\nfour to start, three to end\n~~~\n",
+		"<p>~~~~ perl\nfour to start, three to end\n~~~</p>\n",
+
+		"~~~ bash\ntildes\n~~~\n",
+		"<pre><code class=\"language-bash\">tildes\n</code></pre>\n",
+
+		"``` lisp\nno ending\n",
+		"<p>``` lisp\nno ending</p>\n",
+
+		"~~~ lisp\nend with language\n~~~ lisp\n",
+		"<p>~~~ lisp\nend with language\n~~~ lisp</p>\n",
+
+		"```\nmismatched begin and end\n~~~\n",
+		"<p>```\nmismatched begin and end\n~~~</p>\n",
+
+		"~~~\nmismatched begin and end\n```\n",
+		"<p>~~~\nmismatched begin and end\n```</p>\n",
+
+		"   ``` oz\nleading spaces\n```\n",
+		"<pre><code class=\"language-oz\">leading spaces\n</code></pre>\n",
+
+		"  ``` oz\nleading spaces\n ```\n",
+		"<pre><code class=\"language-oz\">leading spaces\n</code></pre>\n",
+
+		" ``` oz\nleading spaces\n  ```\n",
+		"<pre><code class=\"language-oz\">leading spaces\n</code></pre>\n",
+
+		"``` oz\nleading spaces\n   ```\n",
+		"<pre><code class=\"language-oz\">leading spaces\n</code></pre>\n",
+
+		"    ``` oz\nleading spaces\n    ```\n",
+		"<pre><code>``` oz\n</code></pre>\n\n<p>leading spaces</p>\n\n<pre><code>```\n</code></pre>\n",
+	}
+	doTestsBlock(t, tests, EXTENSION_FENCED_CODE|EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
+}
+
+func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) {
+	var tests = []string{
+		"% Some title\n" +
+			"% Another title line\n" +
+			"% Yep, more here too\n",
+		"<h1 class=\"title\">" +
+			"Some title\n" +
+			"Another title line\n" +
+			"Yep, more here too\n" +
+			"</h1>",
+	}
+	doTestsBlock(t, tests, EXTENSION_TITLEBLOCK)
+}
+
+func TestBlockComments(t *testing.T) {
+	var tests = []string{
+		"Some text\n\n<!-- comment -->\n",
+		"<p>Some text</p>\n\n<!-- comment -->\n",
+
+		"Some text\n\n<!--\n\nmultiline\ncomment\n-->\n",
+		"<p>Some text</p>\n\n<!--\n\nmultiline\ncomment\n-->\n",
+
+		"Some text\n\n<!--\n\n<div><p>Commented</p>\n<span>html</span></div>\n-->\n",
+		"<p>Some text</p>\n\n<!--\n\n<div><p>Commented</p>\n<span>html</span></div>\n-->\n",
+	}
+	doTestsBlock(t, tests, 0)
+}
+
+func TestCDATA(t *testing.T) {
+	var tests = []string{
+		"Some text\n\n<![CDATA[foo]]>\n",
+		"<p>Some text</p>\n\n<![CDATA[foo]]>\n",
+
+		"CDATA ]]\n\n<![CDATA[]]]]>\n",
+		"<p>CDATA ]]</p>\n\n<![CDATA[]]]]>\n",
+
+		"CDATA >\n\n<![CDATA[>]]>\n",
+		"<p>CDATA &gt;</p>\n\n<![CDATA[>]]>\n",
+
+		"Lots of text\n\n<![CDATA[lots of te><t\non\nseveral\nlines]]>\n",
+		"<p>Lots of text</p>\n\n<![CDATA[lots of te><t\non\nseveral\nlines]]>\n",
+
+		"<![CDATA[>]]>\n",
+		"<![CDATA[>]]>\n",
+	}
+	doTestsBlock(t, tests, 0)
+	doTestsBlock(t, []string{
+		"``` html\n<![CDATA[foo]]>\n```\n",
+		"<pre><code class=\"language-html\">&lt;![CDATA[foo]]&gt;\n</code></pre>\n",
+
+		"<![CDATA[\n``` python\ndef func():\n    pass\n```\n]]>\n",
+		"<![CDATA[\n``` python\ndef func():\n    pass\n```\n]]>\n",
+
+		`<![CDATA[
+> def func():
+>     pass
+]]>
+`,
+		`<![CDATA[
+> def func():
+>     pass
+]]>
+`,
+	}, EXTENSION_FENCED_CODE)
+}
+
+func TestIsFenceLine(t *testing.T) {
+	tests := []struct {
+		data            []byte
+		syntaxRequested bool
+		newlineOptional bool
+		wantEnd         int
+		wantMarker      string
+		wantSyntax      string
+	}{
+		{
+			data:    []byte("```"),
+			wantEnd: 0,
+		},
+		{
+			data:       []byte("```\nstuff here\n"),
+			wantEnd:    4,
+			wantMarker: "```",
+		},
+		{
+			data:            []byte("```\nstuff here\n"),
+			syntaxRequested: true,
+			wantEnd:         4,
+			wantMarker:      "```",
+		},
+		{
+			data:    []byte("stuff here\n```\n"),
+			wantEnd: 0,
+		},
+		{
+			data:            []byte("```"),
+			newlineOptional: true,
+			wantEnd:         3,
+			wantMarker:      "```",
+		},
+		{
+			data:            []byte("```"),
+			syntaxRequested: true,
+			newlineOptional: true,
+			wantEnd:         3,
+			wantMarker:      "```",
+		},
+		{
+			data:            []byte("``` go"),
+			syntaxRequested: true,
+			newlineOptional: true,
+			wantEnd:         6,
+			wantMarker:      "```",
+			wantSyntax:      "go",
+		},
+	}
+
+	for _, test := range tests {
+		var syntax *string
+		if test.syntaxRequested {
+			syntax = new(string)
+		}
+		end, marker := isFenceLine(test.data, syntax, "```", test.newlineOptional)
+		if got, want := end, test.wantEnd; got != want {
+			t.Errorf("got end %v, want %v", got, want)
+		}
+		if got, want := marker, test.wantMarker; got != want {
+			t.Errorf("got marker %q, want %q", got, want)
+		}
+		if test.syntaxRequested {
+			if got, want := *syntax, test.wantSyntax; got != want {
+				t.Errorf("got syntax %q, want %q", got, want)
+			}
+		}
+	}
+}
+
+func TestJoinLines(t *testing.T) {
+	input := `# 标题
+
+第一
+行文字。
+
+第
+二
+行文字。
+`
+	result := `<h1>标题</h1>
+
+<p>第一行文字。</p>
+
+<p>第二行文字。</p>
+`
+	opt := Options{Extensions: commonExtensions | EXTENSION_JOIN_LINES}
+	renderer := HtmlRenderer(commonHtmlFlags, "", "")
+	output := MarkdownOptions([]byte(input), renderer, opt)
+
+	if string(output) != result {
+		t.Error("output dose not match.")
+	}
+}
+
+func TestSanitizedAnchorName(t *testing.T) {
+	tests := []struct {
+		text string
+		want string
+	}{
+		{
+			text: "This is a header",
+			want: "this-is-a-header",
+		},
+		{
+			text: "This is also          a header",
+			want: "this-is-also-a-header",
+		},
+		{
+			text: "main.go",
+			want: "main-go",
+		},
+		{
+			text: "Article 123",
+			want: "article-123",
+		},
+		{
+			text: "<- Let's try this, shall we?",
+			want: "let-s-try-this-shall-we",
+		},
+		{
+			text: "        ",
+			want: "",
+		},
+		{
+			text: "Hello, 世界",
+			want: "hello-世界",
+		},
+	}
+	for _, test := range tests {
+		if got := SanitizedAnchorName(test.text); got != test.want {
+			t.Errorf("SanitizedAnchorName(%q):\ngot %q\nwant %q", test.text, got, test.want)
+		}
+	}
+}

+ 32 - 0
vendor/github.com/russross/blackfriday/doc.go

@@ -0,0 +1,32 @@
+// Package blackfriday is a Markdown processor.
+//
+// It translates plain text with simple formatting rules into HTML or LaTeX.
+//
+// Sanitized Anchor Names
+//
+// Blackfriday includes an algorithm for creating sanitized anchor names
+// corresponding to a given input text. This algorithm is used to create
+// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The
+// algorithm is specified below, so that other packages can create
+// compatible anchor names and links to those anchors.
+//
+// The algorithm iterates over the input text, interpreted as UTF-8,
+// one Unicode code point (rune) at a time. All runes that are letters (category L)
+// or numbers (category N) are considered valid characters. They are mapped to
+// lower case, and included in the output. All other runes are considered
+// invalid characters. Invalid characters that preceed the first valid character,
+// as well as invalid character that follow the last valid character
+// are dropped completely. All other sequences of invalid characters
+// between two valid characters are replaced with a single dash character '-'.
+//
+// SanitizedAnchorName exposes this functionality, and can be used to
+// create compatible links to the anchor names generated by blackfriday.
+// This algorithm is also implemented in a small standalone package at
+// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
+// that want a small package and don't need full functionality of blackfriday.
+package blackfriday
+
+// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
+//       github.com/shurcooL/sanitized_anchor_name.
+//       Otherwise, users of sanitized_anchor_name will get anchor names
+//       that are incompatible with those generated by blackfriday.

+ 949 - 0
vendor/github.com/russross/blackfriday/html.go

@@ -0,0 +1,949 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+//
+// HTML rendering backend
+//
+//
+
+package blackfriday
+
+import (
+	"bytes"
+	"fmt"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+// Html renderer configuration options.
+const (
+	HTML_SKIP_HTML                 = 1 << iota // skip preformatted HTML blocks
+	HTML_SKIP_STYLE                            // skip embedded <style> elements
+	HTML_SKIP_IMAGES                           // skip embedded images
+	HTML_SKIP_LINKS                            // skip all links
+	HTML_SAFELINK                              // only link to trusted protocols
+	HTML_NOFOLLOW_LINKS                        // only link with rel="nofollow"
+	HTML_NOREFERRER_LINKS                      // only link with rel="noreferrer"
+	HTML_HREF_TARGET_BLANK                     // add a blank target
+	HTML_TOC                                   // generate a table of contents
+	HTML_OMIT_CONTENTS                         // skip the main contents (for a standalone table of contents)
+	HTML_COMPLETE_PAGE                         // generate a complete HTML page
+	HTML_USE_XHTML                             // generate XHTML output instead of HTML
+	HTML_USE_SMARTYPANTS                       // enable smart punctuation substitutions
+	HTML_SMARTYPANTS_FRACTIONS                 // enable smart fractions (with HTML_USE_SMARTYPANTS)
+	HTML_SMARTYPANTS_DASHES                    // enable smart dashes (with HTML_USE_SMARTYPANTS)
+	HTML_SMARTYPANTS_LATEX_DASHES              // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
+	HTML_SMARTYPANTS_ANGLED_QUOTES             // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
+	HTML_FOOTNOTE_RETURN_LINKS                 // generate a link at the end of a footnote to return to the source
+)
+
+var (
+	alignments = []string{
+		"left",
+		"right",
+		"center",
+	}
+
+	// TODO: improve this regexp to catch all possible entities:
+	htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`)
+)
+
+type HtmlRendererParameters struct {
+	// Prepend this text to each relative URL.
+	AbsolutePrefix string
+	// Add this text to each footnote anchor, to ensure uniqueness.
+	FootnoteAnchorPrefix string
+	// Show this text inside the <a> tag for a footnote return link, if the
+	// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
+	// <sup>[return]</sup> is used.
+	FootnoteReturnLinkContents string
+	// If set, add this text to the front of each Header ID, to ensure
+	// uniqueness.
+	HeaderIDPrefix string
+	// If set, add this text to the back of each Header ID, to ensure uniqueness.
+	HeaderIDSuffix string
+}
+
+// Html is a type that implements the Renderer interface for HTML output.
+//
+// Do not create this directly, instead use the HtmlRenderer function.
+type Html struct {
+	flags    int    // HTML_* options
+	closeTag string // how to end singleton tags: either " />" or ">"
+	title    string // document title
+	css      string // optional css file url (used with HTML_COMPLETE_PAGE)
+
+	parameters HtmlRendererParameters
+
+	// table of contents data
+	tocMarker    int
+	headerCount  int
+	currentLevel int
+	toc          *bytes.Buffer
+
+	// Track header IDs to prevent ID collision in a single generation.
+	headerIDs map[string]int
+
+	smartypants *smartypantsRenderer
+}
+
+const (
+	xhtmlClose = " />"
+	htmlClose  = ">"
+)
+
+// HtmlRenderer creates and configures an Html object, which
+// satisfies the Renderer interface.
+//
+// flags is a set of HTML_* options ORed together.
+// title is the title of the document, and css is a URL for the document's
+// stylesheet.
+// title and css are only used when HTML_COMPLETE_PAGE is selected.
+func HtmlRenderer(flags int, title string, css string) Renderer {
+	return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{})
+}
+
+func HtmlRendererWithParameters(flags int, title string,
+	css string, renderParameters HtmlRendererParameters) Renderer {
+	// configure the rendering engine
+	closeTag := htmlClose
+	if flags&HTML_USE_XHTML != 0 {
+		closeTag = xhtmlClose
+	}
+
+	if renderParameters.FootnoteReturnLinkContents == "" {
+		renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>`
+	}
+
+	return &Html{
+		flags:      flags,
+		closeTag:   closeTag,
+		title:      title,
+		css:        css,
+		parameters: renderParameters,
+
+		headerCount:  0,
+		currentLevel: 0,
+		toc:          new(bytes.Buffer),
+
+		headerIDs: make(map[string]int),
+
+		smartypants: smartypants(flags),
+	}
+}
+
+// Using if statements is a bit faster than a switch statement. As the compiler
+// improves, this should be unnecessary this is only worthwhile because
+// attrEscape is the single largest CPU user in normal use.
+// Also tried using map, but that gave a ~3x slowdown.
+func escapeSingleChar(char byte) (string, bool) {
+	if char == '"' {
+		return "&quot;", true
+	}
+	if char == '&' {
+		return "&amp;", true
+	}
+	if char == '<' {
+		return "&lt;", true
+	}
+	if char == '>' {
+		return "&gt;", true
+	}
+	return "", false
+}
+
+func attrEscape(out *bytes.Buffer, src []byte) {
+	org := 0
+	for i, ch := range src {
+		if entity, ok := escapeSingleChar(ch); ok {
+			if i > org {
+				// copy all the normal characters since the last escape
+				out.Write(src[org:i])
+			}
+			org = i + 1
+			out.WriteString(entity)
+		}
+	}
+	if org < len(src) {
+		out.Write(src[org:])
+	}
+}
+
+func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) {
+	end := 0
+	for _, rang := range skipRanges {
+		attrEscape(out, src[end:rang[0]])
+		out.Write(src[rang[0]:rang[1]])
+		end = rang[1]
+	}
+	attrEscape(out, src[end:])
+}
+
+func (options *Html) GetFlags() int {
+	return options.flags
+}
+
+func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) {
+	text = bytes.TrimPrefix(text, []byte("% "))
+	text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
+	out.WriteString("<h1 class=\"title\">")
+	out.Write(text)
+	out.WriteString("\n</h1>")
+}
+
+func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) {
+	marker := out.Len()
+	doubleSpace(out)
+
+	if id == "" && options.flags&HTML_TOC != 0 {
+		id = fmt.Sprintf("toc_%d", options.headerCount)
+	}
+
+	if id != "" {
+		id = options.ensureUniqueHeaderID(id)
+
+		if options.parameters.HeaderIDPrefix != "" {
+			id = options.parameters.HeaderIDPrefix + id
+		}
+
+		if options.parameters.HeaderIDSuffix != "" {
+			id = id + options.parameters.HeaderIDSuffix
+		}
+
+		out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
+	} else {
+		out.WriteString(fmt.Sprintf("<h%d>", level))
+	}
+
+	tocMarker := out.Len()
+	if !text() {
+		out.Truncate(marker)
+		return
+	}
+
+	// are we building a table of contents?
+	if options.flags&HTML_TOC != 0 {
+		options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id)
+	}
+
+	out.WriteString(fmt.Sprintf("</h%d>\n", level))
+}
+
+func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) {
+	if options.flags&HTML_SKIP_HTML != 0 {
+		return
+	}
+
+	doubleSpace(out)
+	out.Write(text)
+	out.WriteByte('\n')
+}
+
+func (options *Html) HRule(out *bytes.Buffer) {
+	doubleSpace(out)
+	out.WriteString("<hr")
+	out.WriteString(options.closeTag)
+	out.WriteByte('\n')
+}
+
+func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) {
+	doubleSpace(out)
+
+	// parse out the language names/classes
+	count := 0
+	for _, elt := range strings.Fields(lang) {
+		if elt[0] == '.' {
+			elt = elt[1:]
+		}
+		if len(elt) == 0 {
+			continue
+		}
+		if count == 0 {
+			out.WriteString("<pre><code class=\"language-")
+		} else {
+			out.WriteByte(' ')
+		}
+		attrEscape(out, []byte(elt))
+		count++
+	}
+
+	if count == 0 {
+		out.WriteString("<pre><code>")
+	} else {
+		out.WriteString("\">")
+	}
+
+	attrEscape(out, text)
+	out.WriteString("</code></pre>\n")
+}
+
+func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) {
+	doubleSpace(out)
+	out.WriteString("<blockquote>\n")
+	out.Write(text)
+	out.WriteString("</blockquote>\n")
+}
+
+func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
+	doubleSpace(out)
+	out.WriteString("<table>\n<thead>\n")
+	out.Write(header)
+	out.WriteString("</thead>\n\n<tbody>\n")
+	out.Write(body)
+	out.WriteString("</tbody>\n</table>\n")
+}
+
+func (options *Html) TableRow(out *bytes.Buffer, text []byte) {
+	doubleSpace(out)
+	out.WriteString("<tr>\n")
+	out.Write(text)
+	out.WriteString("\n</tr>\n")
+}
+
+func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
+	doubleSpace(out)
+	switch align {
+	case TABLE_ALIGNMENT_LEFT:
+		out.WriteString("<th align=\"left\">")
+	case TABLE_ALIGNMENT_RIGHT:
+		out.WriteString("<th align=\"right\">")
+	case TABLE_ALIGNMENT_CENTER:
+		out.WriteString("<th align=\"center\">")
+	default:
+		out.WriteString("<th>")
+	}
+
+	out.Write(text)
+	out.WriteString("</th>")
+}
+
+func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) {
+	doubleSpace(out)
+	switch align {
+	case TABLE_ALIGNMENT_LEFT:
+		out.WriteString("<td align=\"left\">")
+	case TABLE_ALIGNMENT_RIGHT:
+		out.WriteString("<td align=\"right\">")
+	case TABLE_ALIGNMENT_CENTER:
+		out.WriteString("<td align=\"center\">")
+	default:
+		out.WriteString("<td>")
+	}
+
+	out.Write(text)
+	out.WriteString("</td>")
+}
+
+func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) {
+	out.WriteString("<div class=\"footnotes\">\n")
+	options.HRule(out)
+	options.List(out, text, LIST_TYPE_ORDERED)
+	out.WriteString("</div>\n")
+}
+
+func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
+	if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
+		doubleSpace(out)
+	}
+	slug := slugify(name)
+	out.WriteString(`<li id="`)
+	out.WriteString(`fn:`)
+	out.WriteString(options.parameters.FootnoteAnchorPrefix)
+	out.Write(slug)
+	out.WriteString(`">`)
+	out.Write(text)
+	if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 {
+		out.WriteString(` <a class="footnote-return" href="#`)
+		out.WriteString(`fnref:`)
+		out.WriteString(options.parameters.FootnoteAnchorPrefix)
+		out.Write(slug)
+		out.WriteString(`">`)
+		out.WriteString(options.parameters.FootnoteReturnLinkContents)
+		out.WriteString(`</a>`)
+	}
+	out.WriteString("</li>\n")
+}
+
+func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
+	marker := out.Len()
+	doubleSpace(out)
+
+	if flags&LIST_TYPE_DEFINITION != 0 {
+		out.WriteString("<dl>")
+	} else if flags&LIST_TYPE_ORDERED != 0 {
+		out.WriteString("<ol>")
+	} else {
+		out.WriteString("<ul>")
+	}
+	if !text() {
+		out.Truncate(marker)
+		return
+	}
+	if flags&LIST_TYPE_DEFINITION != 0 {
+		out.WriteString("</dl>\n")
+	} else if flags&LIST_TYPE_ORDERED != 0 {
+		out.WriteString("</ol>\n")
+	} else {
+		out.WriteString("</ul>\n")
+	}
+}
+
+func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) {
+	if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) ||
+		flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
+		doubleSpace(out)
+	}
+	if flags&LIST_TYPE_TERM != 0 {
+		out.WriteString("<dt>")
+	} else if flags&LIST_TYPE_DEFINITION != 0 {
+		out.WriteString("<dd>")
+	} else {
+		out.WriteString("<li>")
+	}
+	out.Write(text)
+	if flags&LIST_TYPE_TERM != 0 {
+		out.WriteString("</dt>\n")
+	} else if flags&LIST_TYPE_DEFINITION != 0 {
+		out.WriteString("</dd>\n")
+	} else {
+		out.WriteString("</li>\n")
+	}
+}
+
+func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {
+	marker := out.Len()
+	doubleSpace(out)
+
+	out.WriteString("<p>")
+	if !text() {
+		out.Truncate(marker)
+		return
+	}
+	out.WriteString("</p>\n")
+}
+
+func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) {
+	skipRanges := htmlEntity.FindAllIndex(link, -1)
+	if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL {
+		// mark it but don't link it if it is not a safe link: no smartypants
+		out.WriteString("<tt>")
+		entityEscapeWithSkip(out, link, skipRanges)
+		out.WriteString("</tt>")
+		return
+	}
+
+	out.WriteString("<a href=\"")
+	if kind == LINK_TYPE_EMAIL {
+		out.WriteString("mailto:")
+	} else {
+		options.maybeWriteAbsolutePrefix(out, link)
+	}
+
+	entityEscapeWithSkip(out, link, skipRanges)
+
+	var relAttrs []string
+	if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
+		relAttrs = append(relAttrs, "nofollow")
+	}
+	if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
+		relAttrs = append(relAttrs, "noreferrer")
+	}
+	if len(relAttrs) > 0 {
+		out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
+	}
+
+	// blank target only add to external link
+	if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
+		out.WriteString("\" target=\"_blank")
+	}
+
+	out.WriteString("\">")
+
+	// Pretty print: if we get an email address as
+	// an actual URI, e.g. `mailto:[email protected]`, we don't
+	// want to print the `mailto:` prefix
+	switch {
+	case bytes.HasPrefix(link, []byte("mailto://")):
+		attrEscape(out, link[len("mailto://"):])
+	case bytes.HasPrefix(link, []byte("mailto:")):
+		attrEscape(out, link[len("mailto:"):])
+	default:
+		entityEscapeWithSkip(out, link, skipRanges)
+	}
+
+	out.WriteString("</a>")
+}
+
+func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) {
+	out.WriteString("<code>")
+	attrEscape(out, text)
+	out.WriteString("</code>")
+}
+
+func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) {
+	out.WriteString("<strong>")
+	out.Write(text)
+	out.WriteString("</strong>")
+}
+
+func (options *Html) Emphasis(out *bytes.Buffer, text []byte) {
+	if len(text) == 0 {
+		return
+	}
+	out.WriteString("<em>")
+	out.Write(text)
+	out.WriteString("</em>")
+}
+
+func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) {
+	if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
+		out.WriteString(options.parameters.AbsolutePrefix)
+		if link[0] != '/' {
+			out.WriteByte('/')
+		}
+	}
+}
+
+func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
+	if options.flags&HTML_SKIP_IMAGES != 0 {
+		return
+	}
+
+	out.WriteString("<img src=\"")
+	options.maybeWriteAbsolutePrefix(out, link)
+	attrEscape(out, link)
+	out.WriteString("\" alt=\"")
+	if len(alt) > 0 {
+		attrEscape(out, alt)
+	}
+	if len(title) > 0 {
+		out.WriteString("\" title=\"")
+		attrEscape(out, title)
+	}
+
+	out.WriteByte('"')
+	out.WriteString(options.closeTag)
+}
+
+func (options *Html) LineBreak(out *bytes.Buffer) {
+	out.WriteString("<br")
+	out.WriteString(options.closeTag)
+	out.WriteByte('\n')
+}
+
+func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
+	if options.flags&HTML_SKIP_LINKS != 0 {
+		// write the link text out but don't link it, just mark it with typewriter font
+		out.WriteString("<tt>")
+		attrEscape(out, content)
+		out.WriteString("</tt>")
+		return
+	}
+
+	if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) {
+		// write the link text out but don't link it, just mark it with typewriter font
+		out.WriteString("<tt>")
+		attrEscape(out, content)
+		out.WriteString("</tt>")
+		return
+	}
+
+	out.WriteString("<a href=\"")
+	options.maybeWriteAbsolutePrefix(out, link)
+	attrEscape(out, link)
+	if len(title) > 0 {
+		out.WriteString("\" title=\"")
+		attrEscape(out, title)
+	}
+	var relAttrs []string
+	if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
+		relAttrs = append(relAttrs, "nofollow")
+	}
+	if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
+		relAttrs = append(relAttrs, "noreferrer")
+	}
+	if len(relAttrs) > 0 {
+		out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
+	}
+
+	// blank target only add to external link
+	if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
+		out.WriteString("\" target=\"_blank")
+	}
+
+	out.WriteString("\">")
+	out.Write(content)
+	out.WriteString("</a>")
+	return
+}
+
+func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) {
+	if options.flags&HTML_SKIP_HTML != 0 {
+		return
+	}
+	if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") {
+		return
+	}
+	if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") {
+		return
+	}
+	if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") {
+		return
+	}
+	out.Write(text)
+}
+
+func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) {
+	out.WriteString("<strong><em>")
+	out.Write(text)
+	out.WriteString("</em></strong>")
+}
+
+func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) {
+	out.WriteString("<del>")
+	out.Write(text)
+	out.WriteString("</del>")
+}
+
+func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
+	slug := slugify(ref)
+	out.WriteString(`<sup class="footnote-ref" id="`)
+	out.WriteString(`fnref:`)
+	out.WriteString(options.parameters.FootnoteAnchorPrefix)
+	out.Write(slug)
+	out.WriteString(`"><a rel="footnote" href="#`)
+	out.WriteString(`fn:`)
+	out.WriteString(options.parameters.FootnoteAnchorPrefix)
+	out.Write(slug)
+	out.WriteString(`">`)
+	out.WriteString(strconv.Itoa(id))
+	out.WriteString(`</a></sup>`)
+}
+
+func (options *Html) Entity(out *bytes.Buffer, entity []byte) {
+	out.Write(entity)
+}
+
+func (options *Html) NormalText(out *bytes.Buffer, text []byte) {
+	if options.flags&HTML_USE_SMARTYPANTS != 0 {
+		options.Smartypants(out, text)
+	} else {
+		attrEscape(out, text)
+	}
+}
+
+func (options *Html) Smartypants(out *bytes.Buffer, text []byte) {
+	smrt := smartypantsData{false, false}
+
+	// first do normal entity escaping
+	var escaped bytes.Buffer
+	attrEscape(&escaped, text)
+	text = escaped.Bytes()
+
+	mark := 0
+	for i := 0; i < len(text); i++ {
+		if action := options.smartypants[text[i]]; action != nil {
+			if i > mark {
+				out.Write(text[mark:i])
+			}
+
+			previousChar := byte(0)
+			if i > 0 {
+				previousChar = text[i-1]
+			}
+			i += action(out, &smrt, previousChar, text[i:])
+			mark = i + 1
+		}
+	}
+
+	if mark < len(text) {
+		out.Write(text[mark:])
+	}
+}
+
+func (options *Html) DocumentHeader(out *bytes.Buffer) {
+	if options.flags&HTML_COMPLETE_PAGE == 0 {
+		return
+	}
+
+	ending := ""
+	if options.flags&HTML_USE_XHTML != 0 {
+		out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
+		out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
+		out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
+		ending = " /"
+	} else {
+		out.WriteString("<!DOCTYPE html>\n")
+		out.WriteString("<html>\n")
+	}
+	out.WriteString("<head>\n")
+	out.WriteString("  <title>")
+	options.NormalText(out, []byte(options.title))
+	out.WriteString("</title>\n")
+	out.WriteString("  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
+	out.WriteString(VERSION)
+	out.WriteString("\"")
+	out.WriteString(ending)
+	out.WriteString(">\n")
+	out.WriteString("  <meta charset=\"utf-8\"")
+	out.WriteString(ending)
+	out.WriteString(">\n")
+	if options.css != "" {
+		out.WriteString("  <link rel=\"stylesheet\" type=\"text/css\" href=\"")
+		attrEscape(out, []byte(options.css))
+		out.WriteString("\"")
+		out.WriteString(ending)
+		out.WriteString(">\n")
+	}
+	out.WriteString("</head>\n")
+	out.WriteString("<body>\n")
+
+	options.tocMarker = out.Len()
+}
+
+func (options *Html) DocumentFooter(out *bytes.Buffer) {
+	// finalize and insert the table of contents
+	if options.flags&HTML_TOC != 0 {
+		options.TocFinalize()
+
+		// now we have to insert the table of contents into the document
+		var temp bytes.Buffer
+
+		// start by making a copy of everything after the document header
+		temp.Write(out.Bytes()[options.tocMarker:])
+
+		// now clear the copied material from the main output buffer
+		out.Truncate(options.tocMarker)
+
+		// corner case spacing issue
+		if options.flags&HTML_COMPLETE_PAGE != 0 {
+			out.WriteByte('\n')
+		}
+
+		// insert the table of contents
+		out.WriteString("<nav>\n")
+		out.Write(options.toc.Bytes())
+		out.WriteString("</nav>\n")
+
+		// corner case spacing issue
+		if options.flags&HTML_COMPLETE_PAGE == 0 && options.flags&HTML_OMIT_CONTENTS == 0 {
+			out.WriteByte('\n')
+		}
+
+		// write out everything that came after it
+		if options.flags&HTML_OMIT_CONTENTS == 0 {
+			out.Write(temp.Bytes())
+		}
+	}
+
+	if options.flags&HTML_COMPLETE_PAGE != 0 {
+		out.WriteString("\n</body>\n")
+		out.WriteString("</html>\n")
+	}
+
+}
+
+func (options *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) {
+	for level > options.currentLevel {
+		switch {
+		case bytes.HasSuffix(options.toc.Bytes(), []byte("</li>\n")):
+			// this sublist can nest underneath a header
+			size := options.toc.Len()
+			options.toc.Truncate(size - len("</li>\n"))
+
+		case options.currentLevel > 0:
+			options.toc.WriteString("<li>")
+		}
+		if options.toc.Len() > 0 {
+			options.toc.WriteByte('\n')
+		}
+		options.toc.WriteString("<ul>\n")
+		options.currentLevel++
+	}
+
+	for level < options.currentLevel {
+		options.toc.WriteString("</ul>")
+		if options.currentLevel > 1 {
+			options.toc.WriteString("</li>\n")
+		}
+		options.currentLevel--
+	}
+
+	options.toc.WriteString("<li><a href=\"#")
+	if anchor != "" {
+		options.toc.WriteString(anchor)
+	} else {
+		options.toc.WriteString("toc_")
+		options.toc.WriteString(strconv.Itoa(options.headerCount))
+	}
+	options.toc.WriteString("\">")
+	options.headerCount++
+
+	options.toc.Write(text)
+
+	options.toc.WriteString("</a></li>\n")
+}
+
+func (options *Html) TocHeader(text []byte, level int) {
+	options.TocHeaderWithAnchor(text, level, "")
+}
+
+func (options *Html) TocFinalize() {
+	for options.currentLevel > 1 {
+		options.toc.WriteString("</ul></li>\n")
+		options.currentLevel--
+	}
+
+	if options.currentLevel > 0 {
+		options.toc.WriteString("</ul>\n")
+	}
+}
+
+func isHtmlTag(tag []byte, tagname string) bool {
+	found, _ := findHtmlTagPos(tag, tagname)
+	return found
+}
+
+// Look for a character, but ignore it when it's in any kind of quotes, it
+// might be JavaScript
+func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
+	inSingleQuote := false
+	inDoubleQuote := false
+	inGraveQuote := false
+	i := start
+	for i < len(html) {
+		switch {
+		case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
+			return i
+		case html[i] == '\'':
+			inSingleQuote = !inSingleQuote
+		case html[i] == '"':
+			inDoubleQuote = !inDoubleQuote
+		case html[i] == '`':
+			inGraveQuote = !inGraveQuote
+		}
+		i++
+	}
+	return start
+}
+
+func findHtmlTagPos(tag []byte, tagname string) (bool, int) {
+	i := 0
+	if i < len(tag) && tag[0] != '<' {
+		return false, -1
+	}
+	i++
+	i = skipSpace(tag, i)
+
+	if i < len(tag) && tag[i] == '/' {
+		i++
+	}
+
+	i = skipSpace(tag, i)
+	j := 0
+	for ; i < len(tag); i, j = i+1, j+1 {
+		if j >= len(tagname) {
+			break
+		}
+
+		if strings.ToLower(string(tag[i]))[0] != tagname[j] {
+			return false, -1
+		}
+	}
+
+	if i == len(tag) {
+		return false, -1
+	}
+
+	rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
+	if rightAngle > i {
+		return true, rightAngle
+	}
+
+	return false, -1
+}
+
+func skipUntilChar(text []byte, start int, char byte) int {
+	i := start
+	for i < len(text) && text[i] != char {
+		i++
+	}
+	return i
+}
+
+func skipSpace(tag []byte, i int) int {
+	for i < len(tag) && isspace(tag[i]) {
+		i++
+	}
+	return i
+}
+
+func skipChar(data []byte, start int, char byte) int {
+	i := start
+	for i < len(data) && data[i] == char {
+		i++
+	}
+	return i
+}
+
+func doubleSpace(out *bytes.Buffer) {
+	if out.Len() > 0 {
+		out.WriteByte('\n')
+	}
+}
+
+func isRelativeLink(link []byte) (yes bool) {
+	// a tag begin with '#'
+	if link[0] == '#' {
+		return true
+	}
+
+	// link begin with '/' but not '//', the second maybe a protocol relative link
+	if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
+		return true
+	}
+
+	// only the root '/'
+	if len(link) == 1 && link[0] == '/' {
+		return true
+	}
+
+	// current directory : begin with "./"
+	if bytes.HasPrefix(link, []byte("./")) {
+		return true
+	}
+
+	// parent directory : begin with "../"
+	if bytes.HasPrefix(link, []byte("../")) {
+		return true
+	}
+
+	return false
+}
+
+func (options *Html) ensureUniqueHeaderID(id string) string {
+	for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] {
+		tmp := fmt.Sprintf("%s-%d", id, count+1)
+
+		if _, tmpFound := options.headerIDs[tmp]; !tmpFound {
+			options.headerIDs[id] = count + 1
+			id = tmp
+		} else {
+			id = id + "-1"
+		}
+	}
+
+	if _, found := options.headerIDs[id]; !found {
+		options.headerIDs[id] = 0
+	}
+
+	return id
+}

+ 1154 - 0
vendor/github.com/russross/blackfriday/inline.go

@@ -0,0 +1,1154 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+// Functions to parse inline elements.
+//
+
+package blackfriday
+
+import (
+	"bytes"
+	"regexp"
+	"strconv"
+)
+
+var (
+	urlRe    = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
+	anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
+)
+
+// Functions to parse text within a block
+// Each function returns the number of chars taken care of
+// data is the complete block being rendered
+// offset is the number of valid chars before the current cursor
+
+func (p *parser) inline(out *bytes.Buffer, data []byte) {
+	// this is called recursively: enforce a maximum depth
+	if p.nesting >= p.maxNesting {
+		return
+	}
+	p.nesting++
+
+	i, end := 0, 0
+	for i < len(data) {
+		// copy inactive chars into the output
+		for end < len(data) && p.inlineCallback[data[end]] == nil {
+			end++
+		}
+
+		p.r.NormalText(out, data[i:end])
+
+		if end >= len(data) {
+			break
+		}
+		i = end
+
+		// call the trigger
+		handler := p.inlineCallback[data[end]]
+		if consumed := handler(p, out, data, i); consumed == 0 {
+			// no action from the callback; buffer the byte for later
+			end = i + 1
+		} else {
+			// skip past whatever the callback used
+			i += consumed
+			end = i
+		}
+	}
+
+	p.nesting--
+}
+
+// single and double emphasis parsing
+func emphasis(p *parser, out *bytes.Buffer, data []byte, offset int) int {
+	data = data[offset:]
+	c := data[0]
+	ret := 0
+
+	if len(data) > 2 && data[1] != c {
+		// whitespace cannot follow an opening emphasis;
+		// strikethrough only takes two characters '~~'
+		if c == '~' || isspace(data[1]) {
+			return 0
+		}
+		if ret = helperEmphasis(p, out, data[1:], c); ret == 0 {
+			return 0
+		}
+
+		return ret + 1
+	}
+
+	if len(data) > 3 && data[1] == c && data[2] != c {
+		if isspace(data[2]) {
+			return 0
+		}
+		if ret = helperDoubleEmphasis(p, out, data[2:], c); ret == 0 {
+			return 0
+		}
+
+		return ret + 2
+	}
+
+	if len(data) > 4 && data[1] == c && data[2] == c && data[3] != c {
+		if c == '~' || isspace(data[3]) {
+			return 0
+		}
+		if ret = helperTripleEmphasis(p, out, data, 3, c); ret == 0 {
+			return 0
+		}
+
+		return ret + 3
+	}
+
+	return 0
+}
+
+func codeSpan(p *parser, out *bytes.Buffer, data []byte, offset int) int {
+	data = data[offset:]
+
+	nb := 0
+
+	// count the number of backticks in the delimiter
+	for nb < len(data) && data[nb] == '`' {
+		nb++
+	}
+
+	// find the next delimiter
+	i, end := 0, 0
+	for end = nb; end < len(data) && i < nb; end++ {
+		if data[end] == '`' {
+			i++
+		} else {
+			i = 0
+		}
+	}
+
+	// no matching delimiter?
+	if i < nb && end >= len(data) {
+		return 0
+	}
+
+	// trim outside whitespace
+	fBegin := nb
+	for fBegin < end && data[fBegin] == ' ' {
+		fBegin++
+	}
+
+	fEnd := end - nb
+	for fEnd > fBegin && data[fEnd-1] == ' ' {
+		fEnd--
+	}
+
+	// render the code span
+	if fBegin != fEnd {
+		p.r.CodeSpan(out, data[fBegin:fEnd])
+	}
+
+	return end
+
+}
+
+// newline preceded by two spaces becomes <br>
+// newline without two spaces works when EXTENSION_HARD_LINE_BREAK is enabled
+func lineBreak(p *parser, out *bytes.Buffer, data []byte, offset int) int {
+	// remove trailing spaces from out
+	outBytes := out.Bytes()
+	end := len(outBytes)
+	eol := end
+	for eol > 0 && outBytes[eol-1] == ' ' {
+		eol--
+	}
+	out.Truncate(eol)
+
+	precededByTwoSpaces := offset >= 2 && data[offset-2] == ' ' && data[offset-1] == ' '
+	precededByBackslash := offset >= 1 && data[offset-1] == '\\' // see http://spec.commonmark.org/0.18/#example-527
+	precededByBackslash = precededByBackslash && p.flags&EXTENSION_BACKSLASH_LINE_BREAK != 0
+
+	if p.flags&EXTENSION_JOIN_LINES != 0 {
+		return 1
+	}
+
+	// should there be a hard line break here?
+	if p.flags&EXTENSION_HARD_LINE_BREAK == 0 && !precededByTwoSpaces && !precededByBackslash {
+		return 0
+	}
+
+	if precededByBackslash && eol > 0 {
+		out.Truncate(eol - 1)
+	}
+	p.r.LineBreak(out)
+	return 1
+}
+
+type linkType int
+
+const (
+	linkNormal linkType = iota
+	linkImg
+	linkDeferredFootnote
+	linkInlineFootnote
+)
+
+func isReferenceStyleLink(data []byte, pos int, t linkType) bool {
+	if t == linkDeferredFootnote {
+		return false
+	}
+	return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^'
+}
+
+// '[': parse a link or an image or a footnote
+func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
+	// no links allowed inside regular links, footnote, and deferred footnotes
+	if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
+		return 0
+	}
+
+	var t linkType
+	switch {
+	// special case: ![^text] == deferred footnote (that follows something with
+	// an exclamation point)
+	case p.flags&EXTENSION_FOOTNOTES != 0 && len(data)-1 > offset && data[offset+1] == '^':
+		t = linkDeferredFootnote
+	// ![alt] == image
+	case offset > 0 && data[offset-1] == '!':
+		t = linkImg
+	// ^[text] == inline footnote
+	// [^refId] == deferred footnote
+	case p.flags&EXTENSION_FOOTNOTES != 0:
+		if offset > 0 && data[offset-1] == '^' {
+			t = linkInlineFootnote
+		} else if len(data)-1 > offset && data[offset+1] == '^' {
+			t = linkDeferredFootnote
+		}
+	// [text] == regular link
+	default:
+		t = linkNormal
+	}
+
+	data = data[offset:]
+
+	var (
+		i                       = 1
+		noteId                  int
+		title, link, altContent []byte
+		textHasNl               = false
+	)
+
+	if t == linkDeferredFootnote {
+		i++
+	}
+
+	brace := 0
+
+	// look for the matching closing bracket
+	for level := 1; level > 0 && i < len(data); i++ {
+		switch {
+		case data[i] == '\n':
+			textHasNl = true
+
+		case data[i-1] == '\\':
+			continue
+
+		case data[i] == '[':
+			level++
+
+		case data[i] == ']':
+			level--
+			if level <= 0 {
+				i-- // compensate for extra i++ in for loop
+			}
+		}
+	}
+
+	if i >= len(data) {
+		return 0
+	}
+
+	txtE := i
+	i++
+
+	// skip any amount of whitespace or newline
+	// (this is much more lax than original markdown syntax)
+	for i < len(data) && isspace(data[i]) {
+		i++
+	}
+
+	switch {
+	// inline style link
+	case i < len(data) && data[i] == '(':
+		// skip initial whitespace
+		i++
+
+		for i < len(data) && isspace(data[i]) {
+			i++
+		}
+
+		linkB := i
+
+		// look for link end: ' " ), check for new opening braces and take this
+		// into account, this may lead for overshooting and probably will require
+		// some fine-tuning.
+	findlinkend:
+		for i < len(data) {
+			switch {
+			case data[i] == '\\':
+				i += 2
+
+			case data[i] == '(':
+				brace++
+				i++
+
+			case data[i] == ')':
+				if brace <= 0 {
+					break findlinkend
+				}
+				brace--
+				i++
+
+			case data[i] == '\'' || data[i] == '"':
+				break findlinkend
+
+			default:
+				i++
+			}
+		}
+
+		if i >= len(data) {
+			return 0
+		}
+		linkE := i
+
+		// look for title end if present
+		titleB, titleE := 0, 0
+		if data[i] == '\'' || data[i] == '"' {
+			i++
+			titleB = i
+
+		findtitleend:
+			for i < len(data) {
+				switch {
+				case data[i] == '\\':
+					i += 2
+
+				case data[i] == ')':
+					break findtitleend
+
+				default:
+					i++
+				}
+			}
+
+			if i >= len(data) {
+				return 0
+			}
+
+			// skip whitespace after title
+			titleE = i - 1
+			for titleE > titleB && isspace(data[titleE]) {
+				titleE--
+			}
+
+			// check for closing quote presence
+			if data[titleE] != '\'' && data[titleE] != '"' {
+				titleB, titleE = 0, 0
+				linkE = i
+			}
+		}
+
+		// remove whitespace at the end of the link
+		for linkE > linkB && isspace(data[linkE-1]) {
+			linkE--
+		}
+
+		// remove optional angle brackets around the link
+		if data[linkB] == '<' {
+			linkB++
+		}
+		if data[linkE-1] == '>' {
+			linkE--
+		}
+
+		// build escaped link and title
+		if linkE > linkB {
+			link = data[linkB:linkE]
+		}
+
+		if titleE > titleB {
+			title = data[titleB:titleE]
+		}
+
+		i++
+
+	// reference style link
+	case isReferenceStyleLink(data, i, t):
+		var id []byte
+		altContentConsidered := false
+
+		// look for the id
+		i++
+		linkB := i
+		for i < len(data) && data[i] != ']' {
+			i++
+		}
+		if i >= len(data) {
+			return 0
+		}
+		linkE := i
+
+		// find the reference
+		if linkB == linkE {
+			if textHasNl {
+				var b bytes.Buffer
+
+				for j := 1; j < txtE; j++ {
+					switch {
+					case data[j] != '\n':
+						b.WriteByte(data[j])
+					case data[j-1] != ' ':
+						b.WriteByte(' ')
+					}
+				}
+
+				id = b.Bytes()
+			} else {
+				id = data[1:txtE]
+				altContentConsidered = true
+			}
+		} else {
+			id = data[linkB:linkE]
+		}
+
+		// find the reference with matching id
+		lr, ok := p.getRef(string(id))
+		if !ok {
+			return 0
+		}
+
+		// keep link and title from reference
+		link = lr.link
+		title = lr.title
+		if altContentConsidered {
+			altContent = lr.text
+		}
+		i++
+
+	// shortcut reference style link or reference or inline footnote
+	default:
+		var id []byte
+
+		// craft the id
+		if textHasNl {
+			var b bytes.Buffer
+
+			for j := 1; j < txtE; j++ {
+				switch {
+				case data[j] != '\n':
+					b.WriteByte(data[j])
+				case data[j-1] != ' ':
+					b.WriteByte(' ')
+				}
+			}
+
+			id = b.Bytes()
+		} else {
+			if t == linkDeferredFootnote {
+				id = data[2:txtE] // get rid of the ^
+			} else {
+				id = data[1:txtE]
+			}
+		}
+
+		if t == linkInlineFootnote {
+			// create a new reference
+			noteId = len(p.notes) + 1
+
+			var fragment []byte
+			if len(id) > 0 {
+				if len(id) < 16 {
+					fragment = make([]byte, len(id))
+				} else {
+					fragment = make([]byte, 16)
+				}
+				copy(fragment, slugify(id))
+			} else {
+				fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteId))...)
+			}
+
+			ref := &reference{
+				noteId:   noteId,
+				hasBlock: false,
+				link:     fragment,
+				title:    id,
+			}
+
+			p.notes = append(p.notes, ref)
+			p.notesRecord[string(ref.link)] = struct{}{}
+
+			link = ref.link
+			title = ref.title
+		} else {
+			// find the reference with matching id
+			lr, ok := p.getRef(string(id))
+			if !ok {
+				return 0
+			}
+
+			if t == linkDeferredFootnote && !p.isFootnote(lr) {
+				lr.noteId = len(p.notes) + 1
+				p.notes = append(p.notes, lr)
+				p.notesRecord[string(lr.link)] = struct{}{}
+			}
+
+			// keep link and title from reference
+			link = lr.link
+			// if inline footnote, title == footnote contents
+			title = lr.title
+			noteId = lr.noteId
+		}
+
+		// rewind the whitespace
+		i = txtE + 1
+	}
+
+	// build content: img alt is escaped, link content is parsed
+	var content bytes.Buffer
+	if txtE > 1 {
+		if t == linkImg {
+			content.Write(data[1:txtE])
+		} else {
+			// links cannot contain other links, so turn off link parsing temporarily
+			insideLink := p.insideLink
+			p.insideLink = true
+			p.inline(&content, data[1:txtE])
+			p.insideLink = insideLink
+		}
+	}
+
+	var uLink []byte
+	if t == linkNormal || t == linkImg {
+		if len(link) > 0 {
+			var uLinkBuf bytes.Buffer
+			unescapeText(&uLinkBuf, link)
+			uLink = uLinkBuf.Bytes()
+		}
+
+		// links need something to click on and somewhere to go
+		if len(uLink) == 0 || (t == linkNormal && content.Len() == 0) {
+			return 0
+		}
+	}
+
+	// call the relevant rendering function
+	switch t {
+	case linkNormal:
+		if len(altContent) > 0 {
+			p.r.Link(out, uLink, title, altContent)
+		} else {
+			p.r.Link(out, uLink, title, content.Bytes())
+		}
+
+	case linkImg:
+		outSize := out.Len()
+		outBytes := out.Bytes()
+		if outSize > 0 && outBytes[outSize-1] == '!' {
+			out.Truncate(outSize - 1)
+		}
+
+		p.r.Image(out, uLink, title, content.Bytes())
+
+	case linkInlineFootnote:
+		outSize := out.Len()
+		outBytes := out.Bytes()
+		if outSize > 0 && outBytes[outSize-1] == '^' {
+			out.Truncate(outSize - 1)
+		}
+
+		p.r.FootnoteRef(out, link, noteId)
+
+	case linkDeferredFootnote:
+		p.r.FootnoteRef(out, link, noteId)
+
+	default:
+		return 0
+	}
+
+	return i
+}
+
+func (p *parser) inlineHTMLComment(out *bytes.Buffer, data []byte) int {
+	if len(data) < 5 {
+		return 0
+	}
+	if data[0] != '<' || data[1] != '!' || data[2] != '-' || data[3] != '-' {
+		return 0
+	}
+	i := 5
+	// scan for an end-of-comment marker, across lines if necessary
+	for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') {
+		i++
+	}
+	// no end-of-comment marker
+	if i >= len(data) {
+		return 0
+	}
+	return i + 1
+}
+
+// '<' when tags or autolinks are allowed
+func leftAngle(p *parser, out *bytes.Buffer, data []byte, offset int) int {
+	data = data[offset:]
+	altype := LINK_TYPE_NOT_AUTOLINK
+	end := tagLength(data, &altype)
+	if size := p.inlineHTMLComment(out, data); size > 0 {
+		end = size
+	}
+	if end > 2 {
+		if altype != LINK_TYPE_NOT_AUTOLINK {
+			var uLink bytes.Buffer
+			unescapeText(&uLink, data[1:end+1-2])
+			if uLink.Len() > 0 {
+				p.r.AutoLink(out, uLink.Bytes(), altype)
+			}
+		} else {
+			p.r.RawHtmlTag(out, data[:end])
+		}
+	}
+
+	return end
+}
+
+// '\\' backslash escape
+var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
+
+func escape(p *parser, out *bytes.Buffer, data []byte, offset int) int {
+	data = data[offset:]
+
+	if len(data) > 1 {
+		if bytes.IndexByte(escapeChars, data[1]) < 0 {
+			return 0
+		}
+
+		p.r.NormalText(out, data[1:2])
+	}
+
+	return 2
+}
+
+func unescapeText(ob *bytes.Buffer, src []byte) {
+	i := 0
+	for i < len(src) {
+		org := i
+		for i < len(src) && src[i] != '\\' {
+			i++
+		}
+
+		if i > org {
+			ob.Write(src[org:i])
+		}
+
+		if i+1 >= len(src) {
+			break
+		}
+
+		ob.WriteByte(src[i+1])
+		i += 2
+	}
+}
+
+// '&' escaped when it doesn't belong to an entity
+// valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
+func entity(p *parser, out *bytes.Buffer, data []byte, offset int) int {
+	data = data[offset:]
+
+	end := 1
+
+	if end < len(data) && data[end] == '#' {
+		end++
+	}
+
+	for end < len(data) && isalnum(data[end]) {
+		end++
+	}
+
+	if end < len(data) && data[end] == ';' {
+		end++ // real entity
+	} else {
+		return 0 // lone '&'
+	}
+
+	p.r.Entity(out, data[:end])
+
+	return end
+}
+
+func linkEndsWithEntity(data []byte, linkEnd int) bool {
+	entityRanges := htmlEntity.FindAllIndex(data[:linkEnd], -1)
+	return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd
+}
+
+func autoLink(p *parser, out *bytes.Buffer, data []byte, offset int) int {
+	// quick check to rule out most false hits on ':'
+	if p.insideLink || len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' {
+		return 0
+	}
+
+	// Now a more expensive check to see if we're not inside an anchor element
+	anchorStart := offset
+	offsetFromAnchor := 0
+	for anchorStart > 0 && data[anchorStart] != '<' {
+		anchorStart--
+		offsetFromAnchor++
+	}
+
+	anchorStr := anchorRe.Find(data[anchorStart:])
+	if anchorStr != nil {
+		out.Write(anchorStr[offsetFromAnchor:])
+		return len(anchorStr) - offsetFromAnchor
+	}
+
+	// scan backward for a word boundary
+	rewind := 0
+	for offset-rewind > 0 && rewind <= 7 && isletter(data[offset-rewind-1]) {
+		rewind++
+	}
+	if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters
+		return 0
+	}
+
+	origData := data
+	data = data[offset-rewind:]
+
+	if !isSafeLink(data) {
+		return 0
+	}
+
+	linkEnd := 0
+	for linkEnd < len(data) && !isEndOfLink(data[linkEnd]) {
+		linkEnd++
+	}
+
+	// Skip punctuation at the end of the link
+	if (data[linkEnd-1] == '.' || data[linkEnd-1] == ',') && data[linkEnd-2] != '\\' {
+		linkEnd--
+	}
+
+	// But don't skip semicolon if it's a part of escaped entity:
+	if data[linkEnd-1] == ';' && data[linkEnd-2] != '\\' && !linkEndsWithEntity(data, linkEnd) {
+		linkEnd--
+	}
+
+	// See if the link finishes with a punctuation sign that can be closed.
+	var copen byte
+	switch data[linkEnd-1] {
+	case '"':
+		copen = '"'
+	case '\'':
+		copen = '\''
+	case ')':
+		copen = '('
+	case ']':
+		copen = '['
+	case '}':
+		copen = '{'
+	default:
+		copen = 0
+	}
+
+	if copen != 0 {
+		bufEnd := offset - rewind + linkEnd - 2
+
+		openDelim := 1
+
+		/* Try to close the final punctuation sign in this same line;
+		 * if we managed to close it outside of the URL, that means that it's
+		 * not part of the URL. If it closes inside the URL, that means it
+		 * is part of the URL.
+		 *
+		 * Examples:
+		 *
+		 *      foo http://www.pokemon.com/Pikachu_(Electric) bar
+		 *              => http://www.pokemon.com/Pikachu_(Electric)
+		 *
+		 *      foo (http://www.pokemon.com/Pikachu_(Electric)) bar
+		 *              => http://www.pokemon.com/Pikachu_(Electric)
+		 *
+		 *      foo http://www.pokemon.com/Pikachu_(Electric)) bar
+		 *              => http://www.pokemon.com/Pikachu_(Electric))
+		 *
+		 *      (foo http://www.pokemon.com/Pikachu_(Electric)) bar
+		 *              => foo http://www.pokemon.com/Pikachu_(Electric)
+		 */
+
+		for bufEnd >= 0 && origData[bufEnd] != '\n' && openDelim != 0 {
+			if origData[bufEnd] == data[linkEnd-1] {
+				openDelim++
+			}
+
+			if origData[bufEnd] == copen {
+				openDelim--
+			}
+
+			bufEnd--
+		}
+
+		if openDelim == 0 {
+			linkEnd--
+		}
+	}
+
+	// we were triggered on the ':', so we need to rewind the output a bit
+	if out.Len() >= rewind {
+		out.Truncate(len(out.Bytes()) - rewind)
+	}
+
+	var uLink bytes.Buffer
+	unescapeText(&uLink, data[:linkEnd])
+
+	if uLink.Len() > 0 {
+		p.r.AutoLink(out, uLink.Bytes(), LINK_TYPE_NORMAL)
+	}
+
+	return linkEnd - rewind
+}
+
+func isEndOfLink(char byte) bool {
+	return isspace(char) || char == '<'
+}
+
+var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
+var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
+
+func isSafeLink(link []byte) bool {
+	for _, path := range validPaths {
+		if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) {
+			if len(link) == len(path) {
+				return true
+			} else if isalnum(link[len(path)]) {
+				return true
+			}
+		}
+	}
+
+	for _, prefix := range validUris {
+		// TODO: handle unicode here
+		// case-insensitive prefix test
+		if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// return the length of the given tag, or 0 is it's not valid
+func tagLength(data []byte, autolink *int) int {
+	var i, j int
+
+	// a valid tag can't be shorter than 3 chars
+	if len(data) < 3 {
+		return 0
+	}
+
+	// begins with a '<' optionally followed by '/', followed by letter or number
+	if data[0] != '<' {
+		return 0
+	}
+	if data[1] == '/' {
+		i = 2
+	} else {
+		i = 1
+	}
+
+	if !isalnum(data[i]) {
+		return 0
+	}
+
+	// scheme test
+	*autolink = LINK_TYPE_NOT_AUTOLINK
+
+	// try to find the beginning of an URI
+	for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
+		i++
+	}
+
+	if i > 1 && i < len(data) && data[i] == '@' {
+		if j = isMailtoAutoLink(data[i:]); j != 0 {
+			*autolink = LINK_TYPE_EMAIL
+			return i + j
+		}
+	}
+
+	if i > 2 && i < len(data) && data[i] == ':' {
+		*autolink = LINK_TYPE_NORMAL
+		i++
+	}
+
+	// complete autolink test: no whitespace or ' or "
+	switch {
+	case i >= len(data):
+		*autolink = LINK_TYPE_NOT_AUTOLINK
+	case *autolink != 0:
+		j = i
+
+		for i < len(data) {
+			if data[i] == '\\' {
+				i += 2
+			} else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) {
+				break
+			} else {
+				i++
+			}
+
+		}
+
+		if i >= len(data) {
+			return 0
+		}
+		if i > j && data[i] == '>' {
+			return i + 1
+		}
+
+		// one of the forbidden chars has been found
+		*autolink = LINK_TYPE_NOT_AUTOLINK
+	}
+
+	// look for something looking like a tag end
+	for i < len(data) && data[i] != '>' {
+		i++
+	}
+	if i >= len(data) {
+		return 0
+	}
+	return i + 1
+}
+
+// look for the address part of a mail autolink and '>'
+// this is less strict than the original markdown e-mail address matching
+func isMailtoAutoLink(data []byte) int {
+	nb := 0
+
+	// address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@'
+	for i := 0; i < len(data); i++ {
+		if isalnum(data[i]) {
+			continue
+		}
+
+		switch data[i] {
+		case '@':
+			nb++
+
+		case '-', '.', '_':
+			// Do nothing.
+
+		case '>':
+			if nb == 1 {
+				return i + 1
+			} else {
+				return 0
+			}
+		default:
+			return 0
+		}
+	}
+
+	return 0
+}
+
+// look for the next emph char, skipping other constructs
+func helperFindEmphChar(data []byte, c byte) int {
+	i := 0
+
+	for i < len(data) {
+		for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' {
+			i++
+		}
+		if i >= len(data) {
+			return 0
+		}
+		// do not count escaped chars
+		if i != 0 && data[i-1] == '\\' {
+			i++
+			continue
+		}
+		if data[i] == c {
+			return i
+		}
+
+		if data[i] == '`' {
+			// skip a code span
+			tmpI := 0
+			i++
+			for i < len(data) && data[i] != '`' {
+				if tmpI == 0 && data[i] == c {
+					tmpI = i
+				}
+				i++
+			}
+			if i >= len(data) {
+				return tmpI
+			}
+			i++
+		} else if data[i] == '[' {
+			// skip a link
+			tmpI := 0
+			i++
+			for i < len(data) && data[i] != ']' {
+				if tmpI == 0 && data[i] == c {
+					tmpI = i
+				}
+				i++
+			}
+			i++
+			for i < len(data) && (data[i] == ' ' || data[i] == '\n') {
+				i++
+			}
+			if i >= len(data) {
+				return tmpI
+			}
+			if data[i] != '[' && data[i] != '(' { // not a link
+				if tmpI > 0 {
+					return tmpI
+				} else {
+					continue
+				}
+			}
+			cc := data[i]
+			i++
+			for i < len(data) && data[i] != cc {
+				if tmpI == 0 && data[i] == c {
+					return i
+				}
+				i++
+			}
+			if i >= len(data) {
+				return tmpI
+			}
+			i++
+		}
+	}
+	return 0
+}
+
+func helperEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int {
+	i := 0
+
+	// skip one symbol if coming from emph3
+	if len(data) > 1 && data[0] == c && data[1] == c {
+		i = 1
+	}
+
+	for i < len(data) {
+		length := helperFindEmphChar(data[i:], c)
+		if length == 0 {
+			return 0
+		}
+		i += length
+		if i >= len(data) {
+			return 0
+		}
+
+		if i+1 < len(data) && data[i+1] == c {
+			i++
+			continue
+		}
+
+		if data[i] == c && !isspace(data[i-1]) {
+
+			if p.flags&EXTENSION_NO_INTRA_EMPHASIS != 0 {
+				if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) {
+					continue
+				}
+			}
+
+			var work bytes.Buffer
+			p.inline(&work, data[:i])
+			p.r.Emphasis(out, work.Bytes())
+			return i + 1
+		}
+	}
+
+	return 0
+}
+
+func helperDoubleEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int {
+	i := 0
+
+	for i < len(data) {
+		length := helperFindEmphChar(data[i:], c)
+		if length == 0 {
+			return 0
+		}
+		i += length
+
+		if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) {
+			var work bytes.Buffer
+			p.inline(&work, data[:i])
+
+			if work.Len() > 0 {
+				// pick the right renderer
+				if c == '~' {
+					p.r.StrikeThrough(out, work.Bytes())
+				} else {
+					p.r.DoubleEmphasis(out, work.Bytes())
+				}
+			}
+			return i + 2
+		}
+		i++
+	}
+	return 0
+}
+
+func helperTripleEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int, c byte) int {
+	i := 0
+	origData := data
+	data = data[offset:]
+
+	for i < len(data) {
+		length := helperFindEmphChar(data[i:], c)
+		if length == 0 {
+			return 0
+		}
+		i += length
+
+		// skip whitespace preceded symbols
+		if data[i] != c || isspace(data[i-1]) {
+			continue
+		}
+
+		switch {
+		case i+2 < len(data) && data[i+1] == c && data[i+2] == c:
+			// triple symbol found
+			var work bytes.Buffer
+
+			p.inline(&work, data[:i])
+			if work.Len() > 0 {
+				p.r.TripleEmphasis(out, work.Bytes())
+			}
+			return i + 3
+		case (i+1 < len(data) && data[i+1] == c):
+			// double symbol found, hand over to emph1
+			length = helperEmphasis(p, out, origData[offset-2:], c)
+			if length == 0 {
+				return 0
+			} else {
+				return length - 2
+			}
+		default:
+			// single symbol found, hand over to emph2
+			length = helperDoubleEmphasis(p, out, origData[offset-1:], c)
+			if length == 0 {
+				return 0
+			} else {
+				return length - 1
+			}
+		}
+	}
+	return 0
+}

+ 1242 - 0
vendor/github.com/russross/blackfriday/inline_test.go

@@ -0,0 +1,1242 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+// Unit tests for inline parsing
+//
+
+package blackfriday
+
+import (
+	"regexp"
+	"strings"
+	"testing"
+)
+
+func runMarkdownInline(input string, opts Options, htmlFlags int, params HtmlRendererParameters) string {
+	opts.Extensions |= EXTENSION_AUTOLINK
+	opts.Extensions |= EXTENSION_STRIKETHROUGH
+
+	htmlFlags |= HTML_USE_XHTML
+
+	renderer := HtmlRendererWithParameters(htmlFlags, "", "", params)
+
+	return string(MarkdownOptions([]byte(input), renderer, opts))
+}
+
+func doTestsInline(t *testing.T, tests []string) {
+	doTestsInlineParam(t, tests, Options{}, 0, HtmlRendererParameters{})
+}
+
+func doLinkTestsInline(t *testing.T, tests []string) {
+	doTestsInline(t, tests)
+
+	prefix := "http://localhost"
+	params := HtmlRendererParameters{AbsolutePrefix: prefix}
+	transformTests := transformLinks(tests, prefix)
+	doTestsInlineParam(t, transformTests, Options{}, 0, params)
+	doTestsInlineParam(t, transformTests, Options{}, commonHtmlFlags, params)
+}
+
+func doSafeTestsInline(t *testing.T, tests []string) {
+	doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK, HtmlRendererParameters{})
+
+	// All the links in this test should not have the prefix appended, so
+	// just rerun it with different parameters and the same expectations.
+	prefix := "http://localhost"
+	params := HtmlRendererParameters{AbsolutePrefix: prefix}
+	transformTests := transformLinks(tests, prefix)
+	doTestsInlineParam(t, transformTests, Options{}, HTML_SAFELINK, params)
+}
+
+func doTestsInlineParam(t *testing.T, tests []string, opts Options, htmlFlags int,
+	params HtmlRendererParameters) {
+	// catch and report panics
+	var candidate string
+	defer func() {
+		if err := recover(); err != nil {
+			t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
+		}
+	}()
+
+	for i := 0; i+1 < len(tests); i += 2 {
+		input := tests[i]
+		candidate = input
+		expected := tests[i+1]
+		actual := runMarkdownInline(candidate, opts, htmlFlags, params)
+		if actual != expected {
+			t.Errorf("\nInput   [%#v]\nExpected[%#v]\nActual  [%#v]",
+				candidate, expected, actual)
+		}
+
+		// now test every substring to stress test bounds checking
+		if !testing.Short() {
+			for start := 0; start < len(input); start++ {
+				for end := start + 1; end <= len(input); end++ {
+					candidate = input[start:end]
+					_ = runMarkdownInline(candidate, opts, htmlFlags, params)
+				}
+			}
+		}
+	}
+}
+
+func transformLinks(tests []string, prefix string) []string {
+	newTests := make([]string, len(tests))
+	anchorRe := regexp.MustCompile(`<a href="/(.*?)"`)
+	imgRe := regexp.MustCompile(`<img src="/(.*?)"`)
+	for i, test := range tests {
+		if i%2 == 1 {
+			test = anchorRe.ReplaceAllString(test, `<a href="`+prefix+`/$1"`)
+			test = imgRe.ReplaceAllString(test, `<img src="`+prefix+`/$1"`)
+		}
+		newTests[i] = test
+	}
+	return newTests
+}
+
+func TestEmphasis(t *testing.T) {
+	var tests = []string{
+		"nothing inline\n",
+		"<p>nothing inline</p>\n",
+
+		"simple *inline* test\n",
+		"<p>simple <em>inline</em> test</p>\n",
+
+		"*at the* beginning\n",
+		"<p><em>at the</em> beginning</p>\n",
+
+		"at the *end*\n",
+		"<p>at the <em>end</em></p>\n",
+
+		"*try two* in *one line*\n",
+		"<p><em>try two</em> in <em>one line</em></p>\n",
+
+		"over *two\nlines* test\n",
+		"<p>over <em>two\nlines</em> test</p>\n",
+
+		"odd *number of* markers* here\n",
+		"<p>odd <em>number of</em> markers* here</p>\n",
+
+		"odd *number\nof* markers* here\n",
+		"<p>odd <em>number\nof</em> markers* here</p>\n",
+
+		"simple _inline_ test\n",
+		"<p>simple <em>inline</em> test</p>\n",
+
+		"_at the_ beginning\n",
+		"<p><em>at the</em> beginning</p>\n",
+
+		"at the _end_\n",
+		"<p>at the <em>end</em></p>\n",
+
+		"_try two_ in _one line_\n",
+		"<p><em>try two</em> in <em>one line</em></p>\n",
+
+		"over _two\nlines_ test\n",
+		"<p>over <em>two\nlines</em> test</p>\n",
+
+		"odd _number of_ markers_ here\n",
+		"<p>odd <em>number of</em> markers_ here</p>\n",
+
+		"odd _number\nof_ markers_ here\n",
+		"<p>odd <em>number\nof</em> markers_ here</p>\n",
+
+		"mix of *markers_\n",
+		"<p>mix of *markers_</p>\n",
+
+		"*What is A\\* algorithm?*\n",
+		"<p><em>What is A* algorithm?</em></p>\n",
+
+		"some para_graph with _emphasised_ text.\n",
+		"<p>some para_graph with <em>emphasised</em> text.</p>\n",
+
+		"some paragraph with _emphasised_ te_xt.\n",
+		"<p>some paragraph with <em>emphasised</em> te_xt.</p>\n",
+
+		"some paragraph with t_wo bi_ts of _emphasised_ text.\n",
+		"<p>some paragraph with t<em>wo bi</em>ts of <em>emphasised</em> text.</p>\n",
+
+		"un*frigging*believable\n",
+		"<p>un<em>frigging</em>believable</p>\n",
+	}
+	doTestsInline(t, tests)
+}
+
+func TestNoIntraEmphasis(t *testing.T) {
+	tests := []string{
+		"some para_graph with _emphasised_ text.\n",
+		"<p>some para_graph with <em>emphasised</em> text.</p>\n",
+
+		"un*frigging*believable\n",
+		"<p>un*frigging*believable</p>\n",
+	}
+	doTestsInlineParam(t, tests, Options{
+		Extensions: EXTENSION_NO_INTRA_EMPHASIS},
+		0, HtmlRendererParameters{})
+}
+
+func TestReferenceOverride(t *testing.T) {
+	var tests = []string{
+		"test [ref1][]\n",
+		"<p>test <a href=\"http://www.ref1.com/\" title=\"Reference 1\">ref1</a></p>\n",
+
+		"test [my ref][ref1]\n",
+		"<p>test <a href=\"http://www.ref1.com/\" title=\"Reference 1\">my ref</a></p>\n",
+
+		"test [ref2][]\n\n[ref2]: http://www.leftalone.com/ (Ref left alone)\n",
+		"<p>test <a href=\"http://www.overridden.com/\" title=\"Reference Overridden\">ref2</a></p>\n",
+
+		"test [ref3][]\n\n[ref3]: http://www.leftalone.com/ (Ref left alone)\n",
+		"<p>test <a href=\"http://www.leftalone.com/\" title=\"Ref left alone\">ref3</a></p>\n",
+
+		"test [ref4][]\n\n[ref4]: http://zombo.com/ (You can do anything)\n",
+		"<p>test [ref4][]</p>\n",
+
+		"test [!(*http.ServeMux).ServeHTTP][] complicated ref\n",
+		"<p>test <a href=\"http://localhost:6060/pkg/net/http/#ServeMux.ServeHTTP\" title=\"ServeHTTP docs\">!(*http.ServeMux).ServeHTTP</a> complicated ref</p>\n",
+
+		"test [ref5][]\n",
+		"<p>test <a href=\"http://www.ref5.com/\" title=\"Reference 5\">Moo</a></p>\n",
+	}
+	doTestsInlineParam(t, tests, Options{
+		ReferenceOverride: func(reference string) (rv *Reference, overridden bool) {
+			switch reference {
+			case "ref1":
+				// just an overriden reference exists without definition
+				return &Reference{
+					Link:  "http://www.ref1.com/",
+					Title: "Reference 1"}, true
+			case "ref2":
+				// overridden exists and reference defined
+				return &Reference{
+					Link:  "http://www.overridden.com/",
+					Title: "Reference Overridden"}, true
+			case "ref3":
+				// not overridden and reference defined
+				return nil, false
+			case "ref4":
+				// overridden missing and defined
+				return nil, true
+			case "!(*http.ServeMux).ServeHTTP":
+				return &Reference{
+					Link:  "http://localhost:6060/pkg/net/http/#ServeMux.ServeHTTP",
+					Title: "ServeHTTP docs"}, true
+			case "ref5":
+				return &Reference{
+					Link:  "http://www.ref5.com/",
+					Title: "Reference 5",
+					Text:  "Moo",
+				}, true
+			}
+			return nil, false
+		}}, 0, HtmlRendererParameters{})
+}
+
+func TestStrong(t *testing.T) {
+	var tests = []string{
+		"nothing inline\n",
+		"<p>nothing inline</p>\n",
+
+		"simple **inline** test\n",
+		"<p>simple <strong>inline</strong> test</p>\n",
+
+		"**at the** beginning\n",
+		"<p><strong>at the</strong> beginning</p>\n",
+
+		"at the **end**\n",
+		"<p>at the <strong>end</strong></p>\n",
+
+		"**try two** in **one line**\n",
+		"<p><strong>try two</strong> in <strong>one line</strong></p>\n",
+
+		"over **two\nlines** test\n",
+		"<p>over <strong>two\nlines</strong> test</p>\n",
+
+		"odd **number of** markers** here\n",
+		"<p>odd <strong>number of</strong> markers** here</p>\n",
+
+		"odd **number\nof** markers** here\n",
+		"<p>odd <strong>number\nof</strong> markers** here</p>\n",
+
+		"simple __inline__ test\n",
+		"<p>simple <strong>inline</strong> test</p>\n",
+
+		"__at the__ beginning\n",
+		"<p><strong>at the</strong> beginning</p>\n",
+
+		"at the __end__\n",
+		"<p>at the <strong>end</strong></p>\n",
+
+		"__try two__ in __one line__\n",
+		"<p><strong>try two</strong> in <strong>one line</strong></p>\n",
+
+		"over __two\nlines__ test\n",
+		"<p>over <strong>two\nlines</strong> test</p>\n",
+
+		"odd __number of__ markers__ here\n",
+		"<p>odd <strong>number of</strong> markers__ here</p>\n",
+
+		"odd __number\nof__ markers__ here\n",
+		"<p>odd <strong>number\nof</strong> markers__ here</p>\n",
+
+		"mix of **markers__\n",
+		"<p>mix of **markers__</p>\n",
+
+		"**`/usr`** : this folder is named `usr`\n",
+		"<p><strong><code>/usr</code></strong> : this folder is named <code>usr</code></p>\n",
+
+		"**`/usr`** :\n\n this folder is named `usr`\n",
+		"<p><strong><code>/usr</code></strong> :</p>\n\n<p>this folder is named <code>usr</code></p>\n",
+	}
+	doTestsInline(t, tests)
+}
+
+func TestEmphasisMix(t *testing.T) {
+	var tests = []string{
+		"***triple emphasis***\n",
+		"<p><strong><em>triple emphasis</em></strong></p>\n",
+
+		"***triple\nemphasis***\n",
+		"<p><strong><em>triple\nemphasis</em></strong></p>\n",
+
+		"___triple emphasis___\n",
+		"<p><strong><em>triple emphasis</em></strong></p>\n",
+
+		"***triple emphasis___\n",
+		"<p>***triple emphasis___</p>\n",
+
+		"*__triple emphasis__*\n",
+		"<p><em><strong>triple emphasis</strong></em></p>\n",
+
+		"__*triple emphasis*__\n",
+		"<p><strong><em>triple emphasis</em></strong></p>\n",
+
+		"**improper *nesting** is* bad\n",
+		"<p><strong>improper *nesting</strong> is* bad</p>\n",
+
+		"*improper **nesting* is** bad\n",
+		"<p>*improper <strong>nesting* is</strong> bad</p>\n",
+	}
+	doTestsInline(t, tests)
+}
+
+func TestEmphasisLink(t *testing.T) {
+	var tests = []string{
+		"[first](before) *text[second] (inside)text* [third](after)\n",
+		"<p><a href=\"before\">first</a> <em>text<a href=\"inside\">second</a>text</em> <a href=\"after\">third</a></p>\n",
+
+		"*incomplete [link] definition*\n",
+		"<p><em>incomplete [link] definition</em></p>\n",
+
+		"*it's [emphasis*] (not link)\n",
+		"<p><em>it's [emphasis</em>] (not link)</p>\n",
+
+		"*it's [emphasis*] and *[asterisk]\n",
+		"<p><em>it's [emphasis</em>] and *[asterisk]</p>\n",
+	}
+	doTestsInline(t, tests)
+}
+
+func TestStrikeThrough(t *testing.T) {
+	var tests = []string{
+		"nothing inline\n",
+		"<p>nothing inline</p>\n",
+
+		"simple ~~inline~~ test\n",
+		"<p>simple <del>inline</del> test</p>\n",
+
+		"~~at the~~ beginning\n",
+		"<p><del>at the</del> beginning</p>\n",
+
+		"at the ~~end~~\n",
+		"<p>at the <del>end</del></p>\n",
+
+		"~~try two~~ in ~~one line~~\n",
+		"<p><del>try two</del> in <del>one line</del></p>\n",
+
+		"over ~~two\nlines~~ test\n",
+		"<p>over <del>two\nlines</del> test</p>\n",
+
+		"odd ~~number of~~ markers~~ here\n",
+		"<p>odd <del>number of</del> markers~~ here</p>\n",
+
+		"odd ~~number\nof~~ markers~~ here\n",
+		"<p>odd <del>number\nof</del> markers~~ here</p>\n",
+	}
+	doTestsInline(t, tests)
+}
+
+func TestCodeSpan(t *testing.T) {
+	var tests = []string{
+		"`source code`\n",
+		"<p><code>source code</code></p>\n",
+
+		"` source code with spaces `\n",
+		"<p><code>source code with spaces</code></p>\n",
+
+		"` source code with spaces `not here\n",
+		"<p><code>source code with spaces</code>not here</p>\n",
+
+		"a `single marker\n",
+		"<p>a `single marker</p>\n",
+
+		"a single multi-tick marker with ``` no text\n",
+		"<p>a single multi-tick marker with ``` no text</p>\n",
+
+		"markers with ` ` a space\n",
+		"<p>markers with  a space</p>\n",
+
+		"`source code` and a `stray\n",
+		"<p><code>source code</code> and a `stray</p>\n",
+
+		"`source *with* _awkward characters_ in it`\n",
+		"<p><code>source *with* _awkward characters_ in it</code></p>\n",
+
+		"`split over\ntwo lines`\n",
+		"<p><code>split over\ntwo lines</code></p>\n",
+
+		"```multiple ticks``` for the marker\n",
+		"<p><code>multiple ticks</code> for the marker</p>\n",
+
+		"```multiple ticks `with` ticks inside```\n",
+		"<p><code>multiple ticks `with` ticks inside</code></p>\n",
+	}
+	doTestsInline(t, tests)
+}
+
+func TestLineBreak(t *testing.T) {
+	var tests = []string{
+		"this line  \nhas a break\n",
+		"<p>this line<br />\nhas a break</p>\n",
+
+		"this line \ndoes not\n",
+		"<p>this line\ndoes not</p>\n",
+
+		"this line\\\ndoes not\n",
+		"<p>this line\\\ndoes not</p>\n",
+
+		"this line\\ \ndoes not\n",
+		"<p>this line\\\ndoes not</p>\n",
+
+		"this has an   \nextra space\n",
+		"<p>this has an<br />\nextra space</p>\n",
+	}
+	doTestsInline(t, tests)
+
+	tests = []string{
+		"this line  \nhas a break\n",
+		"<p>this line<br />\nhas a break</p>\n",
+
+		"this line \ndoes not\n",
+		"<p>this line\ndoes not</p>\n",
+
+		"this line\\\nhas a break\n",
+		"<p>this line<br />\nhas a break</p>\n",
+
+		"this line\\ \ndoes not\n",
+		"<p>this line\\\ndoes not</p>\n",
+
+		"this has an   \nextra space\n",
+		"<p>this has an<br />\nextra space</p>\n",
+	}
+	doTestsInlineParam(t, tests, Options{
+		Extensions: EXTENSION_BACKSLASH_LINE_BREAK},
+		0, HtmlRendererParameters{})
+}
+
+func TestInlineLink(t *testing.T) {
+	var tests = []string{
+		"[foo](/bar/)\n",
+		"<p><a href=\"/bar/\">foo</a></p>\n",
+
+		"[foo with a title](/bar/ \"title\")\n",
+		"<p><a href=\"/bar/\" title=\"title\">foo with a title</a></p>\n",
+
+		"[foo with a title](/bar/\t\"title\")\n",
+		"<p><a href=\"/bar/\" title=\"title\">foo with a title</a></p>\n",
+
+		"[foo with a title](/bar/ \"title\"  )\n",
+		"<p><a href=\"/bar/\" title=\"title\">foo with a title</a></p>\n",
+
+		"[foo with a title](/bar/ title with no quotes)\n",
+		"<p><a href=\"/bar/ title with no quotes\">foo with a title</a></p>\n",
+
+		"[foo]()\n",
+		"<p>[foo]()</p>\n",
+
+		"![foo](/bar/)\n",
+		"<p><img src=\"/bar/\" alt=\"foo\" /></p>\n",
+
+		"![foo with a title](/bar/ \"title\")\n",
+		"<p><img src=\"/bar/\" alt=\"foo with a title\" title=\"title\" /></p>\n",
+
+		"![foo with a title](/bar/\t\"title\")\n",
+		"<p><img src=\"/bar/\" alt=\"foo with a title\" title=\"title\" /></p>\n",
+
+		"![foo with a title](/bar/ \"title\"  )\n",
+		"<p><img src=\"/bar/\" alt=\"foo with a title\" title=\"title\" /></p>\n",
+
+		"![foo with a title](/bar/ title with no quotes)\n",
+		"<p><img src=\"/bar/ title with no quotes\" alt=\"foo with a title\" /></p>\n",
+
+		"![](img.jpg)\n",
+		"<p><img src=\"img.jpg\" alt=\"\" /></p>\n",
+
+		"[link](url)\n",
+		"<p><a href=\"url\">link</a></p>\n",
+
+		"![foo]()\n",
+		"<p>![foo]()</p>\n",
+
+		"[a link]\t(/with_a_tab/)\n",
+		"<p><a href=\"/with_a_tab/\">a link</a></p>\n",
+
+		"[a link]  (/with_spaces/)\n",
+		"<p><a href=\"/with_spaces/\">a link</a></p>\n",
+
+		"[text (with) [[nested] (brackets)]](/url/)\n",
+		"<p><a href=\"/url/\">text (with) [[nested] (brackets)]</a></p>\n",
+
+		"[text (with) [broken nested] (brackets)]](/url/)\n",
+		"<p>[text (with) <a href=\"brackets\">broken nested</a>]](/url/)</p>\n",
+
+		"[text\nwith a newline](/link/)\n",
+		"<p><a href=\"/link/\">text\nwith a newline</a></p>\n",
+
+		"[text in brackets] [followed](/by a link/)\n",
+		"<p>[text in brackets] <a href=\"/by a link/\">followed</a></p>\n",
+
+		"[link with\\] a closing bracket](/url/)\n",
+		"<p><a href=\"/url/\">link with] a closing bracket</a></p>\n",
+
+		"[link with\\[ an opening bracket](/url/)\n",
+		"<p><a href=\"/url/\">link with[ an opening bracket</a></p>\n",
+
+		"[link with\\) a closing paren](/url/)\n",
+		"<p><a href=\"/url/\">link with) a closing paren</a></p>\n",
+
+		"[link with\\( an opening paren](/url/)\n",
+		"<p><a href=\"/url/\">link with( an opening paren</a></p>\n",
+
+		"[link](  with whitespace)\n",
+		"<p><a href=\"with whitespace\">link</a></p>\n",
+
+		"[link](  with whitespace   )\n",
+		"<p><a href=\"with whitespace\">link</a></p>\n",
+
+		"[![image](someimage)](with image)\n",
+		"<p><a href=\"with image\"><img src=\"someimage\" alt=\"image\" /></a></p>\n",
+
+		"[link](url \"one quote)\n",
+		"<p><a href=\"url &quot;one quote\">link</a></p>\n",
+
+		"[link](url 'one quote)\n",
+		"<p><a href=\"url 'one quote\">link</a></p>\n",
+
+		"[link](<url>)\n",
+		"<p><a href=\"url\">link</a></p>\n",
+
+		"[link & ampersand](/url/)\n",
+		"<p><a href=\"/url/\">link &amp; ampersand</a></p>\n",
+
+		"[link &amp; ampersand](/url/)\n",
+		"<p><a href=\"/url/\">link &amp; ampersand</a></p>\n",
+
+		"[link](/url/&query)\n",
+		"<p><a href=\"/url/&amp;query\">link</a></p>\n",
+
+		"[[t]](/t)\n",
+		"<p><a href=\"/t\">[t]</a></p>\n",
+
+		"[link](</>)\n",
+		"<p><a href=\"/\">link</a></p>\n",
+
+		"[link](<./>)\n",
+		"<p><a href=\"./\">link</a></p>\n",
+
+		"[link](<../>)\n",
+		"<p><a href=\"../\">link</a></p>\n",
+
+		// Issue 116 in blackfriday
+		"![](http://www.broadgate.co.uk/Content/Upload/DetailImages/Cyclus700(1).jpg)",
+		"<p><img src=\"http://www.broadgate.co.uk/Content/Upload/DetailImages/Cyclus700(1).jpg\" alt=\"\" /></p>\n",
+
+		// no closing ), autolinking detects the url next
+		"[disambiguation](http://en.wikipedia.org/wiki/Disambiguation_(disambiguation) is the",
+		"<p>[disambiguation](<a href=\"http://en.wikipedia.org/wiki/Disambiguation_(disambiguation\">http://en.wikipedia.org/wiki/Disambiguation_(disambiguation</a>) is the</p>\n",
+
+		"[disambiguation](http://en.wikipedia.org/wiki/Disambiguation_(disambiguation)) is the",
+		"<p><a href=\"http://en.wikipedia.org/wiki/Disambiguation_(disambiguation)\">disambiguation</a> is the</p>\n",
+
+		"[disambiguation](http://en.wikipedia.org/wiki/Disambiguation_(disambiguation))",
+		"<p><a href=\"http://en.wikipedia.org/wiki/Disambiguation_(disambiguation)\">disambiguation</a></p>\n",
+	}
+	doLinkTestsInline(t, tests)
+
+}
+
+func TestRelAttrLink(t *testing.T) {
+	var nofollowTests = []string{
+		"[foo](http://bar.com/foo/)\n",
+		"<p><a href=\"http://bar.com/foo/\" rel=\"nofollow\">foo</a></p>\n",
+
+		"[foo](/bar/)\n",
+		"<p><a href=\"/bar/\">foo</a></p>\n",
+
+		"[foo](/)\n",
+		"<p><a href=\"/\">foo</a></p>\n",
+
+		"[foo](./)\n",
+		"<p><a href=\"./\">foo</a></p>\n",
+
+		"[foo](../)\n",
+		"<p><a href=\"../\">foo</a></p>\n",
+
+		"[foo](../bar)\n",
+		"<p><a href=\"../bar\">foo</a></p>\n",
+	}
+	doTestsInlineParam(t, nofollowTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS,
+		HtmlRendererParameters{})
+
+	var noreferrerTests = []string{
+		"[foo](http://bar.com/foo/)\n",
+		"<p><a href=\"http://bar.com/foo/\" rel=\"noreferrer\">foo</a></p>\n",
+
+		"[foo](/bar/)\n",
+		"<p><a href=\"/bar/\">foo</a></p>\n",
+	}
+	doTestsInlineParam(t, noreferrerTests, Options{}, HTML_SAFELINK|HTML_NOREFERRER_LINKS,
+		HtmlRendererParameters{})
+
+	var nofollownoreferrerTests = []string{
+		"[foo](http://bar.com/foo/)\n",
+		"<p><a href=\"http://bar.com/foo/\" rel=\"nofollow noreferrer\">foo</a></p>\n",
+
+		"[foo](/bar/)\n",
+		"<p><a href=\"/bar/\">foo</a></p>\n",
+	}
+	doTestsInlineParam(t, nofollownoreferrerTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS,
+		HtmlRendererParameters{})
+}
+
+func TestHrefTargetBlank(t *testing.T) {
+	var tests = []string{
+		// internal link
+		"[foo](/bar/)\n",
+		"<p><a href=\"/bar/\">foo</a></p>\n",
+
+		"[foo](/)\n",
+		"<p><a href=\"/\">foo</a></p>\n",
+
+		"[foo](./)\n",
+		"<p><a href=\"./\">foo</a></p>\n",
+
+		"[foo](./bar)\n",
+		"<p><a href=\"./bar\">foo</a></p>\n",
+
+		"[foo](../)\n",
+		"<p><a href=\"../\">foo</a></p>\n",
+
+		"[foo](../bar)\n",
+		"<p><a href=\"../bar\">foo</a></p>\n",
+
+		"[foo](http://example.com)\n",
+		"<p><a href=\"http://example.com\" target=\"_blank\">foo</a></p>\n",
+	}
+	doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK|HTML_HREF_TARGET_BLANK, HtmlRendererParameters{})
+}
+
+func TestSafeInlineLink(t *testing.T) {
+	var tests = []string{
+		"[foo](/bar/)\n",
+		"<p><a href=\"/bar/\">foo</a></p>\n",
+
+		"[foo](/)\n",
+		"<p><a href=\"/\">foo</a></p>\n",
+
+		"[foo](./)\n",
+		"<p><a href=\"./\">foo</a></p>\n",
+
+		"[foo](../)\n",
+		"<p><a href=\"../\">foo</a></p>\n",
+
+		"[foo](http://bar/)\n",
+		"<p><a href=\"http://bar/\">foo</a></p>\n",
+
+		"[foo](https://bar/)\n",
+		"<p><a href=\"https://bar/\">foo</a></p>\n",
+
+		"[foo](ftp://bar/)\n",
+		"<p><a href=\"ftp://bar/\">foo</a></p>\n",
+
+		"[foo](mailto://bar/)\n",
+		"<p><a href=\"mailto://bar/\">foo</a></p>\n",
+
+		// Not considered safe
+		"[foo](baz://bar/)\n",
+		"<p><tt>foo</tt></p>\n",
+	}
+	doSafeTestsInline(t, tests)
+}
+
+func TestReferenceLink(t *testing.T) {
+	var tests = []string{
+		"[link][ref]\n",
+		"<p>[link][ref]</p>\n",
+
+		"[link][ref]\n   [ref]: /url/ \"title\"\n",
+		"<p><a href=\"/url/\" title=\"title\">link</a></p>\n",
+
+		"[link][ref]\n   [ref]: /url/\n",
+		"<p><a href=\"/url/\">link</a></p>\n",
+
+		"   [ref]: /url/\n",
+		"",
+
+		"   [ref]: /url/\n[ref2]: /url/\n [ref3]: /url/\n",
+		"",
+
+		"   [ref]: /url/\n[ref2]: /url/\n [ref3]: /url/\n    [4spaces]: /url/\n",
+		"<pre><code>[4spaces]: /url/\n</code></pre>\n",
+
+		"[hmm](ref2)\n   [ref]: /url/\n[ref2]: /url/\n [ref3]: /url/\n",
+		"<p><a href=\"ref2\">hmm</a></p>\n",
+
+		"[ref]\n",
+		"<p>[ref]</p>\n",
+
+		"[ref]\n   [ref]: /url/ \"title\"\n",
+		"<p><a href=\"/url/\" title=\"title\">ref</a></p>\n",
+
+		"[ref]\n   [ref]: ../url/ \"title\"\n",
+		"<p><a href=\"../url/\" title=\"title\">ref</a></p>\n",
+
+		"[link][ref]\n   [ref]: /url/",
+		"<p><a href=\"/url/\">link</a></p>\n",
+	}
+	doLinkTestsInline(t, tests)
+}
+
+func TestTags(t *testing.T) {
+	var tests = []string{
+		"a <span>tag</span>\n",
+		"<p>a <span>tag</span></p>\n",
+
+		"<span>tag</span>\n",
+		"<p><span>tag</span></p>\n",
+
+		"<span>mismatch</spandex>\n",
+		"<p><span>mismatch</spandex></p>\n",
+
+		"a <singleton /> tag\n",
+		"<p>a <singleton /> tag</p>\n",
+	}
+	doTestsInline(t, tests)
+}
+
+func TestAutoLink(t *testing.T) {
+	var tests = []string{
+		"http://foo.com/\n",
+		"<p><a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"1 http://foo.com/\n",
+		"<p>1 <a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"1http://foo.com/\n",
+		"<p>1<a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"1.http://foo.com/\n",
+		"<p>1.<a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"1. http://foo.com/\n",
+		"<ol>\n<li><a href=\"http://foo.com/\">http://foo.com/</a></li>\n</ol>\n",
+
+		"-http://foo.com/\n",
+		"<p>-<a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"- http://foo.com/\n",
+		"<ul>\n<li><a href=\"http://foo.com/\">http://foo.com/</a></li>\n</ul>\n",
+
+		"_http://foo.com/\n",
+		"<p>_<a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"令狐http://foo.com/\n",
+		"<p>令狐<a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"令狐 http://foo.com/\n",
+		"<p>令狐 <a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"ahttp://foo.com/\n",
+		"<p>ahttp://foo.com/</p>\n",
+
+		">http://foo.com/\n",
+		"<blockquote>\n<p><a href=\"http://foo.com/\">http://foo.com/</a></p>\n</blockquote>\n",
+
+		"> http://foo.com/\n",
+		"<blockquote>\n<p><a href=\"http://foo.com/\">http://foo.com/</a></p>\n</blockquote>\n",
+
+		"go to <http://foo.com/>\n",
+		"<p>go to <a href=\"http://foo.com/\">http://foo.com/</a></p>\n",
+
+		"a secure <https://link.org>\n",
+		"<p>a secure <a href=\"https://link.org\">https://link.org</a></p>\n",
+
+		"an email <mailto:[email protected]>\n",
+		"<p>an email <a href=\"mailto:[email protected]\">[email protected]</a></p>\n",
+
+		"an email <mailto://[email protected]>\n",
+		"<p>an email <a href=\"mailto://[email protected]\">[email protected]</a></p>\n",
+
+		"an email <[email protected]>\n",
+		"<p>an email <a href=\"mailto:[email protected]\">[email protected]</a></p>\n",
+
+		"an ftp <ftp://old.com>\n",
+		"<p>an ftp <a href=\"ftp://old.com\">ftp://old.com</a></p>\n",
+
+		"an ftp <ftp:old.com>\n",
+		"<p>an ftp <a href=\"ftp:old.com\">ftp:old.com</a></p>\n",
+
+		"a link with <http://new.com?query=foo&bar>\n",
+		"<p>a link with <a href=\"http://new.com?query=foo&amp;bar\">" +
+			"http://new.com?query=foo&amp;bar</a></p>\n",
+
+		"quotes mean a tag <http://new.com?query=\"foo\"&bar>\n",
+		"<p>quotes mean a tag <http://new.com?query=\"foo\"&bar></p>\n",
+
+		"quotes mean a tag <http://new.com?query='foo'&bar>\n",
+		"<p>quotes mean a tag <http://new.com?query='foo'&bar></p>\n",
+
+		"unless escaped <http://new.com?query=\\\"foo\\\"&bar>\n",
+		"<p>unless escaped <a href=\"http://new.com?query=&quot;foo&quot;&amp;bar\">" +
+			"http://new.com?query=&quot;foo&quot;&amp;bar</a></p>\n",
+
+		"even a > can be escaped <http://new.com?q=\\>&etc>\n",
+		"<p>even a &gt; can be escaped <a href=\"http://new.com?q=&gt;&amp;etc\">" +
+			"http://new.com?q=&gt;&amp;etc</a></p>\n",
+
+		"<a href=\"http://fancy.com\">http://fancy.com</a>\n",
+		"<p><a href=\"http://fancy.com\">http://fancy.com</a></p>\n",
+
+		"<a href=\"http://fancy.com\">This is a link</a>\n",
+		"<p><a href=\"http://fancy.com\">This is a link</a></p>\n",
+
+		"<a href=\"http://www.fancy.com/A_B.pdf\">http://www.fancy.com/A_B.pdf</a>\n",
+		"<p><a href=\"http://www.fancy.com/A_B.pdf\">http://www.fancy.com/A_B.pdf</a></p>\n",
+
+		"(<a href=\"http://www.fancy.com/A_B\">http://www.fancy.com/A_B</a> (\n",
+		"<p>(<a href=\"http://www.fancy.com/A_B\">http://www.fancy.com/A_B</a> (</p>\n",
+
+		"(<a href=\"http://www.fancy.com/A_B\">http://www.fancy.com/A_B</a> (part two: <a href=\"http://www.fancy.com/A_B\">http://www.fancy.com/A_B</a>)).\n",
+		"<p>(<a href=\"http://www.fancy.com/A_B\">http://www.fancy.com/A_B</a> (part two: <a href=\"http://www.fancy.com/A_B\">http://www.fancy.com/A_B</a>)).</p>\n",
+
+		"http://www.foo.com<br />\n",
+		"<p><a href=\"http://www.foo.com\">http://www.foo.com</a><br /></p>\n",
+
+		"http://foo.com/viewtopic.php?f=18&amp;t=297",
+		"<p><a href=\"http://foo.com/viewtopic.php?f=18&amp;t=297\">http://foo.com/viewtopic.php?f=18&amp;t=297</a></p>\n",
+
+		"http://foo.com/viewtopic.php?param=&quot;18&quot;zz",
+		"<p><a href=\"http://foo.com/viewtopic.php?param=&quot;18&quot;zz\">http://foo.com/viewtopic.php?param=&quot;18&quot;zz</a></p>\n",
+
+		"http://foo.com/viewtopic.php?param=&quot;18&quot;",
+		"<p><a href=\"http://foo.com/viewtopic.php?param=&quot;18&quot;\">http://foo.com/viewtopic.php?param=&quot;18&quot;</a></p>\n",
+	}
+	doLinkTestsInline(t, tests)
+}
+
+var footnoteTests = []string{
+	"testing footnotes.[^a]\n\n[^a]: This is the note\n",
+	`<p>testing footnotes.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup></p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:a">This is the note
+</li>
+</ol>
+</div>
+`,
+
+	`testing long[^b] notes.
+
+[^b]: Paragraph 1
+
+	Paragraph 2
+
+	` + "```\n\tsome code\n\t```" + `
+
+	Paragraph 3
+
+No longer in the footnote
+`,
+	`<p>testing long<sup class="footnote-ref" id="fnref:b"><a rel="footnote" href="#fn:b">1</a></sup> notes.</p>
+
+<p>No longer in the footnote</p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:b"><p>Paragraph 1</p>
+
+<p>Paragraph 2</p>
+
+<p><code>
+some code
+</code></p>
+
+<p>Paragraph 3</p>
+</li>
+</ol>
+</div>
+`,
+
+	`testing[^c] multiple[^d] notes.
+
+[^c]: this is [note] c
+
+
+omg
+
+[^d]: this is note d
+
+what happens here
+
+[note]: /link/c
+
+`,
+	`<p>testing<sup class="footnote-ref" id="fnref:c"><a rel="footnote" href="#fn:c">1</a></sup> multiple<sup class="footnote-ref" id="fnref:d"><a rel="footnote" href="#fn:d">2</a></sup> notes.</p>
+
+<p>omg</p>
+
+<p>what happens here</p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:c">this is <a href="/link/c">note</a> c
+</li>
+<li id="fn:d">this is note d
+</li>
+</ol>
+</div>
+`,
+
+	"testing inline^[this is the note] notes.\n",
+	`<p>testing inline<sup class="footnote-ref" id="fnref:this-is-the-note"><a rel="footnote" href="#fn:this-is-the-note">1</a></sup> notes.</p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:this-is-the-note">this is the note</li>
+</ol>
+</div>
+`,
+
+	"testing multiple[^1] types^[inline note] of notes[^2]\n\n[^2]: the second deferred note\n[^1]: the first deferred note\n\n\twhich happens to be a block\n",
+	`<p>testing multiple<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup> types<sup class="footnote-ref" id="fnref:inline-note"><a rel="footnote" href="#fn:inline-note">2</a></sup> of notes<sup class="footnote-ref" id="fnref:2"><a rel="footnote" href="#fn:2">3</a></sup></p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:1"><p>the first deferred note</p>
+
+<p>which happens to be a block</p>
+</li>
+<li id="fn:inline-note">inline note</li>
+<li id="fn:2">the second deferred note
+</li>
+</ol>
+</div>
+`,
+
+	`This is a footnote[^1]^[and this is an inline footnote]
+
+[^1]: the footnote text.
+
+    may be multiple paragraphs.
+`,
+	`<p>This is a footnote<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup><sup class="footnote-ref" id="fnref:and-this-is-an-i"><a rel="footnote" href="#fn:and-this-is-an-i">2</a></sup></p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:1"><p>the footnote text.</p>
+
+<p>may be multiple paragraphs.</p>
+</li>
+<li id="fn:and-this-is-an-i">and this is an inline footnote</li>
+</ol>
+</div>
+`,
+
+	"empty footnote[^]\n\n[^]: fn text",
+	"<p>empty footnote<sup class=\"footnote-ref\" id=\"fnref:\"><a rel=\"footnote\" href=\"#fn:\">1</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:\">fn text\n</li>\n</ol>\n</div>\n",
+
+	"Some text.[^note1]\n\n[^note1]: fn1",
+	"<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1\n</li>\n</ol>\n</div>\n",
+
+	"Some text.[^note1][^note2]\n\n[^note1]: fn1\n[^note2]: fn2\n",
+	"<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup><sup class=\"footnote-ref\" id=\"fnref:note2\"><a rel=\"footnote\" href=\"#fn:note2\">2</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1\n</li>\n<li id=\"fn:note2\">fn2\n</li>\n</ol>\n</div>\n",
+
+	`Bla bla [^1] [WWW][w3]
+
+[^1]: This is a footnote
+
+[w3]: http://www.w3.org/
+`,
+	`<p>Bla bla <sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup> <a href="http://www.w3.org/">WWW</a></p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:1">This is a footnote
+</li>
+</ol>
+</div>
+`,
+
+	`This is exciting![^fn1]
+
+[^fn1]: Fine print
+`,
+	`<p>This is exciting!<sup class="footnote-ref" id="fnref:fn1"><a rel="footnote" href="#fn:fn1">1</a></sup></p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:fn1">Fine print
+</li>
+</ol>
+</div>
+`,
+	`testing footnotes.[^a]
+
+test footnotes the second.[^b]
+
+[^a]: This is the first note[^a].
+[^b]: this is the second note.[^a]
+`,
+	`<p>testing footnotes.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup></p>
+
+<p>test footnotes the second.<sup class="footnote-ref" id="fnref:b"><a rel="footnote" href="#fn:b">2</a></sup></p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:a">This is the first note<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup>.
+</li>
+<li id="fn:b">this is the second note.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup>
+</li>
+</ol>
+</div>
+`,
+}
+
+func TestFootnotes(t *testing.T) {
+	doTestsInlineParam(t, footnoteTests, Options{Extensions: EXTENSION_FOOTNOTES}, 0, HtmlRendererParameters{})
+}
+
+func TestFootnotesWithParameters(t *testing.T) {
+	tests := make([]string, len(footnoteTests))
+
+	prefix := "testPrefix"
+	returnText := "ret"
+	re := regexp.MustCompile(`(?ms)<li id="fn:(\S+?)">(.*?)</li>`)
+
+	// Transform the test expectations to match the parameters we're using.
+	for i, test := range footnoteTests {
+		if i%2 == 1 {
+			test = strings.Replace(test, "fn:", "fn:"+prefix, -1)
+			test = strings.Replace(test, "fnref:", "fnref:"+prefix, -1)
+			test = re.ReplaceAllString(test, `<li id="fn:$1">$2 <a class="footnote-return" href="#fnref:$1">ret</a></li>`)
+		}
+		tests[i] = test
+	}
+
+	params := HtmlRendererParameters{
+		FootnoteAnchorPrefix:       prefix,
+		FootnoteReturnLinkContents: returnText,
+	}
+
+	doTestsInlineParam(t, tests, Options{Extensions: EXTENSION_FOOTNOTES}, HTML_FOOTNOTE_RETURN_LINKS, params)
+}
+
+func TestNestedFootnotes(t *testing.T) {
+	var tests = []string{
+		`Paragraph.[^fn1]
+
+[^fn1]:
+  Asterisk[^fn2]
+
+[^fn2]:
+  Obelisk`,
+		`<p>Paragraph.<sup class="footnote-ref" id="fnref:fn1"><a rel="footnote" href="#fn:fn1">1</a></sup></p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:fn1">Asterisk<sup class="footnote-ref" id="fnref:fn2"><a rel="footnote" href="#fn:fn2">2</a></sup>
+</li>
+<li id="fn:fn2">Obelisk
+</li>
+</ol>
+</div>
+`,
+		`This uses footnote A.[^A]
+
+This uses footnote C.[^C]
+
+[^A]:
+  A note. use itself.[^A]
+[^B]:
+  B note, uses A to test duplicate.[^A]
+[^C]:
+  C note, uses B.[^B]
+`,
+		`<p>This uses footnote A.<sup class="footnote-ref" id="fnref:A"><a rel="footnote" href="#fn:A">1</a></sup></p>
+
+<p>This uses footnote C.<sup class="footnote-ref" id="fnref:C"><a rel="footnote" href="#fn:C">2</a></sup></p>
+<div class="footnotes">
+
+<hr />
+
+<ol>
+<li id="fn:A">A note. use itself.<sup class="footnote-ref" id="fnref:A"><a rel="footnote" href="#fn:A">1</a></sup>
+</li>
+<li id="fn:C">C note, uses B.<sup class="footnote-ref" id="fnref:B"><a rel="footnote" href="#fn:B">3</a></sup>
+</li>
+<li id="fn:B">B note, uses A to test duplicate.<sup class="footnote-ref" id="fnref:A"><a rel="footnote" href="#fn:A">1</a></sup>
+</li>
+</ol>
+</div>
+`,
+	}
+	doTestsInlineParam(t, tests, Options{Extensions: EXTENSION_FOOTNOTES}, 0,
+		HtmlRendererParameters{})
+}
+
+func TestInlineComments(t *testing.T) {
+	var tests = []string{
+		"Hello <!-- there ->\n",
+		"<p>Hello &lt;!&mdash; there &ndash;&gt;</p>\n",
+
+		"Hello <!-- there -->\n",
+		"<p>Hello <!-- there --></p>\n",
+
+		"Hello <!-- there -->",
+		"<p>Hello <!-- there --></p>\n",
+
+		"Hello <!---->\n",
+		"<p>Hello <!----></p>\n",
+
+		"Hello <!-- there -->\na",
+		"<p>Hello <!-- there -->\na</p>\n",
+
+		"* list <!-- item -->\n",
+		"<ul>\n<li>list <!-- item --></li>\n</ul>\n",
+
+		"<!-- Front --> comment\n",
+		"<p><!-- Front --> comment</p>\n",
+
+		"blahblah\n<!--- foo -->\nrhubarb\n",
+		"<p>blahblah\n<!--- foo -->\nrhubarb</p>\n",
+	}
+	doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_DASHES, HtmlRendererParameters{})
+}
+
+func TestSmartDoubleQuotes(t *testing.T) {
+	var tests = []string{
+		"this should be normal \"quoted\" text.\n",
+		"<p>this should be normal &ldquo;quoted&rdquo; text.</p>\n",
+		"this \" single double\n",
+		"<p>this &ldquo; single double</p>\n",
+		"two pair of \"some\" quoted \"text\".\n",
+		"<p>two pair of &ldquo;some&rdquo; quoted &ldquo;text&rdquo;.</p>\n"}
+
+	doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
+}
+
+func TestSmartAngledDoubleQuotes(t *testing.T) {
+	var tests = []string{
+		"this should be angled \"quoted\" text.\n",
+		"<p>this should be angled &laquo;quoted&raquo; text.</p>\n",
+		"this \" single double\n",
+		"<p>this &laquo; single double</p>\n",
+		"two pair of \"some\" quoted \"text\".\n",
+		"<p>two pair of &laquo;some&raquo; quoted &laquo;text&raquo;.</p>\n"}
+
+	doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{})
+}
+
+func TestSmartFractions(t *testing.T) {
+	var tests = []string{
+		"1/2, 1/4 and 3/4; 1/4th and 3/4ths\n",
+		"<p>&frac12;, &frac14; and &frac34;; &frac14;th and &frac34;ths</p>\n",
+		"1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\n",
+		"<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"}
+
+	doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
+
+	tests = []string{
+		"1/2, 2/3, 81/100 and 1000000/1048576.\n",
+		"<p><sup>1</sup>&frasl;<sub>2</sub>, <sup>2</sup>&frasl;<sub>3</sub>, <sup>81</sup>&frasl;<sub>100</sub> and <sup>1000000</sup>&frasl;<sub>1048576</sub>.</p>\n",
+		"1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\n",
+		"<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"}
+
+	doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_FRACTIONS, HtmlRendererParameters{})
+}
+
+func TestDisableSmartDashes(t *testing.T) {
+	doTestsInlineParam(t, []string{
+		"foo - bar\n",
+		"<p>foo - bar</p>\n",
+		"foo -- bar\n",
+		"<p>foo -- bar</p>\n",
+		"foo --- bar\n",
+		"<p>foo --- bar</p>\n",
+	}, Options{}, 0, HtmlRendererParameters{})
+	doTestsInlineParam(t, []string{
+		"foo - bar\n",
+		"<p>foo &ndash; bar</p>\n",
+		"foo -- bar\n",
+		"<p>foo &mdash; bar</p>\n",
+		"foo --- bar\n",
+		"<p>foo &mdash;&ndash; bar</p>\n",
+	}, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_DASHES, HtmlRendererParameters{})
+	doTestsInlineParam(t, []string{
+		"foo - bar\n",
+		"<p>foo - bar</p>\n",
+		"foo -- bar\n",
+		"<p>foo &ndash; bar</p>\n",
+		"foo --- bar\n",
+		"<p>foo &mdash; bar</p>\n",
+	}, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_LATEX_DASHES|HTML_SMARTYPANTS_DASHES,
+		HtmlRendererParameters{})
+	doTestsInlineParam(t, []string{
+		"foo - bar\n",
+		"<p>foo - bar</p>\n",
+		"foo -- bar\n",
+		"<p>foo -- bar</p>\n",
+		"foo --- bar\n",
+		"<p>foo --- bar</p>\n",
+	}, Options{},
+		HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_LATEX_DASHES,
+		HtmlRendererParameters{})
+}

+ 332 - 0
vendor/github.com/russross/blackfriday/latex.go

@@ -0,0 +1,332 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+//
+// LaTeX rendering backend
+//
+//
+
+package blackfriday
+
+import (
+	"bytes"
+)
+
+// Latex is a type that implements the Renderer interface for LaTeX output.
+//
+// Do not create this directly, instead use the LatexRenderer function.
+type Latex struct {
+}
+
+// LatexRenderer creates and configures a Latex object, which
+// satisfies the Renderer interface.
+//
+// flags is a set of LATEX_* options ORed together (currently no such options
+// are defined).
+func LatexRenderer(flags int) Renderer {
+	return &Latex{}
+}
+
+func (options *Latex) GetFlags() int {
+	return 0
+}
+
+// render code chunks using verbatim, or listings if we have a language
+func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
+	if lang == "" {
+		out.WriteString("\n\\begin{verbatim}\n")
+	} else {
+		out.WriteString("\n\\begin{lstlisting}[language=")
+		out.WriteString(lang)
+		out.WriteString("]\n")
+	}
+	out.Write(text)
+	if lang == "" {
+		out.WriteString("\n\\end{verbatim}\n")
+	} else {
+		out.WriteString("\n\\end{lstlisting}\n")
+	}
+}
+
+func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
+
+}
+
+func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
+	out.WriteString("\n\\begin{quotation}\n")
+	out.Write(text)
+	out.WriteString("\n\\end{quotation}\n")
+}
+
+func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
+	// a pretty lame thing to do...
+	out.WriteString("\n\\begin{verbatim}\n")
+	out.Write(text)
+	out.WriteString("\n\\end{verbatim}\n")
+}
+
+func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
+	marker := out.Len()
+
+	switch level {
+	case 1:
+		out.WriteString("\n\\section{")
+	case 2:
+		out.WriteString("\n\\subsection{")
+	case 3:
+		out.WriteString("\n\\subsubsection{")
+	case 4:
+		out.WriteString("\n\\paragraph{")
+	case 5:
+		out.WriteString("\n\\subparagraph{")
+	case 6:
+		out.WriteString("\n\\textbf{")
+	}
+	if !text() {
+		out.Truncate(marker)
+		return
+	}
+	out.WriteString("}\n")
+}
+
+func (options *Latex) HRule(out *bytes.Buffer) {
+	out.WriteString("\n\\HRule\n")
+}
+
+func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
+	marker := out.Len()
+	if flags&LIST_TYPE_ORDERED != 0 {
+		out.WriteString("\n\\begin{enumerate}\n")
+	} else {
+		out.WriteString("\n\\begin{itemize}\n")
+	}
+	if !text() {
+		out.Truncate(marker)
+		return
+	}
+	if flags&LIST_TYPE_ORDERED != 0 {
+		out.WriteString("\n\\end{enumerate}\n")
+	} else {
+		out.WriteString("\n\\end{itemize}\n")
+	}
+}
+
+func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
+	out.WriteString("\n\\item ")
+	out.Write(text)
+}
+
+func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
+	marker := out.Len()
+	out.WriteString("\n")
+	if !text() {
+		out.Truncate(marker)
+		return
+	}
+	out.WriteString("\n")
+}
+
+func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
+	out.WriteString("\n\\begin{tabular}{")
+	for _, elt := range columnData {
+		switch elt {
+		case TABLE_ALIGNMENT_LEFT:
+			out.WriteByte('l')
+		case TABLE_ALIGNMENT_RIGHT:
+			out.WriteByte('r')
+		default:
+			out.WriteByte('c')
+		}
+	}
+	out.WriteString("}\n")
+	out.Write(header)
+	out.WriteString(" \\\\\n\\hline\n")
+	out.Write(body)
+	out.WriteString("\n\\end{tabular}\n")
+}
+
+func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
+	if out.Len() > 0 {
+		out.WriteString(" \\\\\n")
+	}
+	out.Write(text)
+}
+
+func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
+	if out.Len() > 0 {
+		out.WriteString(" & ")
+	}
+	out.Write(text)
+}
+
+func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
+	if out.Len() > 0 {
+		out.WriteString(" & ")
+	}
+	out.Write(text)
+}
+
+// TODO: this
+func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
+
+}
+
+func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
+
+}
+
+func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
+	out.WriteString("\\href{")
+	if kind == LINK_TYPE_EMAIL {
+		out.WriteString("mailto:")
+	}
+	out.Write(link)
+	out.WriteString("}{")
+	out.Write(link)
+	out.WriteString("}")
+}
+
+func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
+	out.WriteString("\\texttt{")
+	escapeSpecialChars(out, text)
+	out.WriteString("}")
+}
+
+func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
+	out.WriteString("\\textbf{")
+	out.Write(text)
+	out.WriteString("}")
+}
+
+func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
+	out.WriteString("\\textit{")
+	out.Write(text)
+	out.WriteString("}")
+}
+
+func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
+	if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
+		// treat it like a link
+		out.WriteString("\\href{")
+		out.Write(link)
+		out.WriteString("}{")
+		out.Write(alt)
+		out.WriteString("}")
+	} else {
+		out.WriteString("\\includegraphics{")
+		out.Write(link)
+		out.WriteString("}")
+	}
+}
+
+func (options *Latex) LineBreak(out *bytes.Buffer) {
+	out.WriteString(" \\\\\n")
+}
+
+func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
+	out.WriteString("\\href{")
+	out.Write(link)
+	out.WriteString("}{")
+	out.Write(content)
+	out.WriteString("}")
+}
+
+func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
+}
+
+func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
+	out.WriteString("\\textbf{\\textit{")
+	out.Write(text)
+	out.WriteString("}}")
+}
+
+func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
+	out.WriteString("\\sout{")
+	out.Write(text)
+	out.WriteString("}")
+}
+
+// TODO: this
+func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
+
+}
+
+func needsBackslash(c byte) bool {
+	for _, r := range []byte("_{}%$&\\~#") {
+		if c == r {
+			return true
+		}
+	}
+	return false
+}
+
+func escapeSpecialChars(out *bytes.Buffer, text []byte) {
+	for i := 0; i < len(text); i++ {
+		// directly copy normal characters
+		org := i
+
+		for i < len(text) && !needsBackslash(text[i]) {
+			i++
+		}
+		if i > org {
+			out.Write(text[org:i])
+		}
+
+		// escape a character
+		if i >= len(text) {
+			break
+		}
+		out.WriteByte('\\')
+		out.WriteByte(text[i])
+	}
+}
+
+func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
+	// TODO: convert this into a unicode character or something
+	out.Write(entity)
+}
+
+func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
+	escapeSpecialChars(out, text)
+}
+
+// header and footer
+func (options *Latex) DocumentHeader(out *bytes.Buffer) {
+	out.WriteString("\\documentclass{article}\n")
+	out.WriteString("\n")
+	out.WriteString("\\usepackage{graphicx}\n")
+	out.WriteString("\\usepackage{listings}\n")
+	out.WriteString("\\usepackage[margin=1in]{geometry}\n")
+	out.WriteString("\\usepackage[utf8]{inputenc}\n")
+	out.WriteString("\\usepackage{verbatim}\n")
+	out.WriteString("\\usepackage[normalem]{ulem}\n")
+	out.WriteString("\\usepackage{hyperref}\n")
+	out.WriteString("\n")
+	out.WriteString("\\hypersetup{colorlinks,%\n")
+	out.WriteString("  citecolor=black,%\n")
+	out.WriteString("  filecolor=black,%\n")
+	out.WriteString("  linkcolor=black,%\n")
+	out.WriteString("  urlcolor=black,%\n")
+	out.WriteString("  pdfstartview=FitH,%\n")
+	out.WriteString("  breaklinks=true,%\n")
+	out.WriteString("  pdfauthor={Blackfriday Markdown Processor v")
+	out.WriteString(VERSION)
+	out.WriteString("}}\n")
+	out.WriteString("\n")
+	out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
+	out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
+	out.WriteString("\\parindent=0pt\n")
+	out.WriteString("\n")
+	out.WriteString("\\begin{document}\n")
+}
+
+func (options *Latex) DocumentFooter(out *bytes.Buffer) {
+	out.WriteString("\n\\end{document}\n")
+}

+ 931 - 0
vendor/github.com/russross/blackfriday/markdown.go

@@ -0,0 +1,931 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+//
+// Markdown parsing and processing
+//
+//
+
+package blackfriday
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+	"unicode/utf8"
+)
+
+const VERSION = "1.5"
+
+// These are the supported markdown parsing extensions.
+// OR these values together to select multiple extensions.
+const (
+	EXTENSION_NO_INTRA_EMPHASIS          = 1 << iota // ignore emphasis markers inside words
+	EXTENSION_TABLES                                 // render tables
+	EXTENSION_FENCED_CODE                            // render fenced code blocks
+	EXTENSION_AUTOLINK                               // detect embedded URLs that are not explicitly marked
+	EXTENSION_STRIKETHROUGH                          // strikethrough text using ~~test~~
+	EXTENSION_LAX_HTML_BLOCKS                        // loosen up HTML block parsing rules
+	EXTENSION_SPACE_HEADERS                          // be strict about prefix header rules
+	EXTENSION_HARD_LINE_BREAK                        // translate newlines into line breaks
+	EXTENSION_TAB_SIZE_EIGHT                         // expand tabs to eight spaces instead of four
+	EXTENSION_FOOTNOTES                              // Pandoc-style footnotes
+	EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK             // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
+	EXTENSION_HEADER_IDS                             // specify header IDs  with {#id}
+	EXTENSION_TITLEBLOCK                             // Titleblock ala pandoc
+	EXTENSION_AUTO_HEADER_IDS                        // Create the header ID from the text
+	EXTENSION_BACKSLASH_LINE_BREAK                   // translate trailing backslashes into line breaks
+	EXTENSION_DEFINITION_LISTS                       // render definition lists
+	EXTENSION_JOIN_LINES                             // delete newline and join lines
+
+	commonHtmlFlags = 0 |
+		HTML_USE_XHTML |
+		HTML_USE_SMARTYPANTS |
+		HTML_SMARTYPANTS_FRACTIONS |
+		HTML_SMARTYPANTS_DASHES |
+		HTML_SMARTYPANTS_LATEX_DASHES
+
+	commonExtensions = 0 |
+		EXTENSION_NO_INTRA_EMPHASIS |
+		EXTENSION_TABLES |
+		EXTENSION_FENCED_CODE |
+		EXTENSION_AUTOLINK |
+		EXTENSION_STRIKETHROUGH |
+		EXTENSION_SPACE_HEADERS |
+		EXTENSION_HEADER_IDS |
+		EXTENSION_BACKSLASH_LINE_BREAK |
+		EXTENSION_DEFINITION_LISTS
+)
+
+// These are the possible flag values for the link renderer.
+// Only a single one of these values will be used; they are not ORed together.
+// These are mostly of interest if you are writing a new output format.
+const (
+	LINK_TYPE_NOT_AUTOLINK = iota
+	LINK_TYPE_NORMAL
+	LINK_TYPE_EMAIL
+)
+
+// These are the possible flag values for the ListItem renderer.
+// Multiple flag values may be ORed together.
+// These are mostly of interest if you are writing a new output format.
+const (
+	LIST_TYPE_ORDERED = 1 << iota
+	LIST_TYPE_DEFINITION
+	LIST_TYPE_TERM
+	LIST_ITEM_CONTAINS_BLOCK
+	LIST_ITEM_BEGINNING_OF_LIST
+	LIST_ITEM_END_OF_LIST
+)
+
+// These are the possible flag values for the table cell renderer.
+// Only a single one of these values will be used; they are not ORed together.
+// These are mostly of interest if you are writing a new output format.
+const (
+	TABLE_ALIGNMENT_LEFT = 1 << iota
+	TABLE_ALIGNMENT_RIGHT
+	TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT)
+)
+
+// The size of a tab stop.
+const (
+	TAB_SIZE_DEFAULT = 4
+	TAB_SIZE_EIGHT   = 8
+)
+
+// blockTags is a set of tags that are recognized as HTML block tags.
+// Any of these can be included in markdown text without special escaping.
+var blockTags = map[string]struct{}{
+	"blockquote": {},
+	"del":        {},
+	"div":        {},
+	"dl":         {},
+	"fieldset":   {},
+	"form":       {},
+	"h1":         {},
+	"h2":         {},
+	"h3":         {},
+	"h4":         {},
+	"h5":         {},
+	"h6":         {},
+	"iframe":     {},
+	"ins":        {},
+	"math":       {},
+	"noscript":   {},
+	"ol":         {},
+	"pre":        {},
+	"p":          {},
+	"script":     {},
+	"style":      {},
+	"table":      {},
+	"ul":         {},
+
+	// HTML5
+	"address":    {},
+	"article":    {},
+	"aside":      {},
+	"canvas":     {},
+	"figcaption": {},
+	"figure":     {},
+	"footer":     {},
+	"header":     {},
+	"hgroup":     {},
+	"main":       {},
+	"nav":        {},
+	"output":     {},
+	"progress":   {},
+	"section":    {},
+	"video":      {},
+}
+
+// Renderer is the rendering interface.
+// This is mostly of interest if you are implementing a new rendering format.
+//
+// When a byte slice is provided, it contains the (rendered) contents of the
+// element.
+//
+// When a callback is provided instead, it will write the contents of the
+// respective element directly to the output buffer and return true on success.
+// If the callback returns false, the rendering function should reset the
+// output buffer as though it had never been called.
+//
+// Currently Html and Latex implementations are provided
+type Renderer interface {
+	// block-level callbacks
+	BlockCode(out *bytes.Buffer, text []byte, lang string)
+	BlockQuote(out *bytes.Buffer, text []byte)
+	BlockHtml(out *bytes.Buffer, text []byte)
+	Header(out *bytes.Buffer, text func() bool, level int, id string)
+	HRule(out *bytes.Buffer)
+	List(out *bytes.Buffer, text func() bool, flags int)
+	ListItem(out *bytes.Buffer, text []byte, flags int)
+	Paragraph(out *bytes.Buffer, text func() bool)
+	Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
+	TableRow(out *bytes.Buffer, text []byte)
+	TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
+	TableCell(out *bytes.Buffer, text []byte, flags int)
+	Footnotes(out *bytes.Buffer, text func() bool)
+	FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
+	TitleBlock(out *bytes.Buffer, text []byte)
+
+	// Span-level callbacks
+	AutoLink(out *bytes.Buffer, link []byte, kind int)
+	CodeSpan(out *bytes.Buffer, text []byte)
+	DoubleEmphasis(out *bytes.Buffer, text []byte)
+	Emphasis(out *bytes.Buffer, text []byte)
+	Image(out *bytes.Buffer, link []byte, title []byte, alt []byte)
+	LineBreak(out *bytes.Buffer)
+	Link(out *bytes.Buffer, link []byte, title []byte, content []byte)
+	RawHtmlTag(out *bytes.Buffer, tag []byte)
+	TripleEmphasis(out *bytes.Buffer, text []byte)
+	StrikeThrough(out *bytes.Buffer, text []byte)
+	FootnoteRef(out *bytes.Buffer, ref []byte, id int)
+
+	// Low-level callbacks
+	Entity(out *bytes.Buffer, entity []byte)
+	NormalText(out *bytes.Buffer, text []byte)
+
+	// Header and footer
+	DocumentHeader(out *bytes.Buffer)
+	DocumentFooter(out *bytes.Buffer)
+
+	GetFlags() int
+}
+
+// Callback functions for inline parsing. One such function is defined
+// for each character that triggers a response when parsing inline data.
+type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int
+
+// Parser holds runtime state used by the parser.
+// This is constructed by the Markdown function.
+type parser struct {
+	r              Renderer
+	refOverride    ReferenceOverrideFunc
+	refs           map[string]*reference
+	inlineCallback [256]inlineParser
+	flags          int
+	nesting        int
+	maxNesting     int
+	insideLink     bool
+
+	// Footnotes need to be ordered as well as available to quickly check for
+	// presence. If a ref is also a footnote, it's stored both in refs and here
+	// in notes. Slice is nil if footnotes not enabled.
+	notes       []*reference
+	notesRecord map[string]struct{}
+}
+
+func (p *parser) getRef(refid string) (ref *reference, found bool) {
+	if p.refOverride != nil {
+		r, overridden := p.refOverride(refid)
+		if overridden {
+			if r == nil {
+				return nil, false
+			}
+			return &reference{
+				link:     []byte(r.Link),
+				title:    []byte(r.Title),
+				noteId:   0,
+				hasBlock: false,
+				text:     []byte(r.Text)}, true
+		}
+	}
+	// refs are case insensitive
+	ref, found = p.refs[strings.ToLower(refid)]
+	return ref, found
+}
+
+func (p *parser) isFootnote(ref *reference) bool {
+	_, ok := p.notesRecord[string(ref.link)]
+	return ok
+}
+
+//
+//
+// Public interface
+//
+//
+
+// Reference represents the details of a link.
+// See the documentation in Options for more details on use-case.
+type Reference struct {
+	// Link is usually the URL the reference points to.
+	Link string
+	// Title is the alternate text describing the link in more detail.
+	Title string
+	// Text is the optional text to override the ref with if the syntax used was
+	// [refid][]
+	Text string
+}
+
+// ReferenceOverrideFunc is expected to be called with a reference string and
+// return either a valid Reference type that the reference string maps to or
+// nil. If overridden is false, the default reference logic will be executed.
+// See the documentation in Options for more details on use-case.
+type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
+
+// Options represents configurable overrides and callbacks (in addition to the
+// extension flag set) for configuring a Markdown parse.
+type Options struct {
+	// Extensions is a flag set of bit-wise ORed extension bits. See the
+	// EXTENSION_* flags defined in this package.
+	Extensions int
+
+	// ReferenceOverride is an optional function callback that is called every
+	// time a reference is resolved.
+	//
+	// In Markdown, the link reference syntax can be made to resolve a link to
+	// a reference instead of an inline URL, in one of the following ways:
+	//
+	//  * [link text][refid]
+	//  * [refid][]
+	//
+	// Usually, the refid is defined at the bottom of the Markdown document. If
+	// this override function is provided, the refid is passed to the override
+	// function first, before consulting the defined refids at the bottom. If
+	// the override function indicates an override did not occur, the refids at
+	// the bottom will be used to fill in the link details.
+	ReferenceOverride ReferenceOverrideFunc
+}
+
+// MarkdownBasic is a convenience function for simple rendering.
+// It processes markdown input with no extensions enabled.
+func MarkdownBasic(input []byte) []byte {
+	// set up the HTML renderer
+	htmlFlags := HTML_USE_XHTML
+	renderer := HtmlRenderer(htmlFlags, "", "")
+
+	// set up the parser
+	return MarkdownOptions(input, renderer, Options{Extensions: 0})
+}
+
+// Call Markdown with most useful extensions enabled
+// MarkdownCommon is a convenience function for simple rendering.
+// It processes markdown input with common extensions enabled, including:
+//
+// * Smartypants processing with smart fractions and LaTeX dashes
+//
+// * Intra-word emphasis suppression
+//
+// * Tables
+//
+// * Fenced code blocks
+//
+// * Autolinking
+//
+// * Strikethrough support
+//
+// * Strict header parsing
+//
+// * Custom Header IDs
+func MarkdownCommon(input []byte) []byte {
+	// set up the HTML renderer
+	renderer := HtmlRenderer(commonHtmlFlags, "", "")
+	return MarkdownOptions(input, renderer, Options{
+		Extensions: commonExtensions})
+}
+
+// Markdown is the main rendering function.
+// It parses and renders a block of markdown-encoded text.
+// The supplied Renderer is used to format the output, and extensions dictates
+// which non-standard extensions are enabled.
+//
+// To use the supplied Html or LaTeX renderers, see HtmlRenderer and
+// LatexRenderer, respectively.
+func Markdown(input []byte, renderer Renderer, extensions int) []byte {
+	return MarkdownOptions(input, renderer, Options{
+		Extensions: extensions})
+}
+
+// MarkdownOptions is just like Markdown but takes additional options through
+// the Options struct.
+func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
+	// no point in parsing if we can't render
+	if renderer == nil {
+		return nil
+	}
+
+	extensions := opts.Extensions
+
+	// fill in the render structure
+	p := new(parser)
+	p.r = renderer
+	p.flags = extensions
+	p.refOverride = opts.ReferenceOverride
+	p.refs = make(map[string]*reference)
+	p.maxNesting = 16
+	p.insideLink = false
+
+	// register inline parsers
+	p.inlineCallback['*'] = emphasis
+	p.inlineCallback['_'] = emphasis
+	if extensions&EXTENSION_STRIKETHROUGH != 0 {
+		p.inlineCallback['~'] = emphasis
+	}
+	p.inlineCallback['`'] = codeSpan
+	p.inlineCallback['\n'] = lineBreak
+	p.inlineCallback['['] = link
+	p.inlineCallback['<'] = leftAngle
+	p.inlineCallback['\\'] = escape
+	p.inlineCallback['&'] = entity
+
+	if extensions&EXTENSION_AUTOLINK != 0 {
+		p.inlineCallback[':'] = autoLink
+	}
+
+	if extensions&EXTENSION_FOOTNOTES != 0 {
+		p.notes = make([]*reference, 0)
+		p.notesRecord = make(map[string]struct{})
+	}
+
+	first := firstPass(p, input)
+	second := secondPass(p, first)
+	return second
+}
+
+// first pass:
+// - normalize newlines
+// - extract references (outside of fenced code blocks)
+// - expand tabs (outside of fenced code blocks)
+// - copy everything else
+func firstPass(p *parser, input []byte) []byte {
+	var out bytes.Buffer
+	tabSize := TAB_SIZE_DEFAULT
+	if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
+		tabSize = TAB_SIZE_EIGHT
+	}
+	beg := 0
+	lastFencedCodeBlockEnd := 0
+	for beg < len(input) {
+		// Find end of this line, then process the line.
+		end := beg
+		for end < len(input) && input[end] != '\n' && input[end] != '\r' {
+			end++
+		}
+
+		if p.flags&EXTENSION_FENCED_CODE != 0 {
+			// track fenced code block boundaries to suppress tab expansion
+			// and reference extraction inside them:
+			if beg >= lastFencedCodeBlockEnd {
+				if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 {
+					lastFencedCodeBlockEnd = beg + i
+				}
+			}
+		}
+
+		// add the line body if present
+		if end > beg {
+			if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
+				out.Write(input[beg:end])
+			} else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
+				beg += refEnd
+				continue
+			} else {
+				expandTabs(&out, input[beg:end], tabSize)
+			}
+		}
+
+		if end < len(input) && input[end] == '\r' {
+			end++
+		}
+		if end < len(input) && input[end] == '\n' {
+			end++
+		}
+		out.WriteByte('\n')
+
+		beg = end
+	}
+
+	// empty input?
+	if out.Len() == 0 {
+		out.WriteByte('\n')
+	}
+
+	return out.Bytes()
+}
+
+// second pass: actual rendering
+func secondPass(p *parser, input []byte) []byte {
+	var output bytes.Buffer
+
+	p.r.DocumentHeader(&output)
+	p.block(&output, input)
+
+	if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 {
+		p.r.Footnotes(&output, func() bool {
+			flags := LIST_ITEM_BEGINNING_OF_LIST
+			for i := 0; i < len(p.notes); i += 1 {
+				ref := p.notes[i]
+				var buf bytes.Buffer
+				if ref.hasBlock {
+					flags |= LIST_ITEM_CONTAINS_BLOCK
+					p.block(&buf, ref.title)
+				} else {
+					p.inline(&buf, ref.title)
+				}
+				p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
+				flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
+			}
+
+			return true
+		})
+	}
+
+	p.r.DocumentFooter(&output)
+
+	if p.nesting != 0 {
+		panic("Nesting level did not end at zero")
+	}
+
+	return output.Bytes()
+}
+
+//
+// Link references
+//
+// This section implements support for references that (usually) appear
+// as footnotes in a document, and can be referenced anywhere in the document.
+// The basic format is:
+//
+//    [1]: http://www.google.com/ "Google"
+//    [2]: http://www.github.com/ "Github"
+//
+// Anywhere in the document, the reference can be linked by referring to its
+// label, i.e., 1 and 2 in this example, as in:
+//
+//    This library is hosted on [Github][2], a git hosting site.
+//
+// Actual footnotes as specified in Pandoc and supported by some other Markdown
+// libraries such as php-markdown are also taken care of. They look like this:
+//
+//    This sentence needs a bit of further explanation.[^note]
+//
+//    [^note]: This is the explanation.
+//
+// Footnotes should be placed at the end of the document in an ordered list.
+// Inline footnotes such as:
+//
+//    Inline footnotes^[Not supported.] also exist.
+//
+// are not yet supported.
+
+// References are parsed and stored in this struct.
+type reference struct {
+	link     []byte
+	title    []byte
+	noteId   int // 0 if not a footnote ref
+	hasBlock bool
+	text     []byte
+}
+
+func (r *reference) String() string {
+	return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}",
+		r.link, r.title, r.text, r.noteId, r.hasBlock)
+}
+
+// Check whether or not data starts with a reference link.
+// If so, it is parsed and stored in the list of references
+// (in the render struct).
+// Returns the number of bytes to skip to move past it,
+// or zero if the first line is not a reference.
+func isReference(p *parser, data []byte, tabSize int) int {
+	// up to 3 optional leading spaces
+	if len(data) < 4 {
+		return 0
+	}
+	i := 0
+	for i < 3 && data[i] == ' ' {
+		i++
+	}
+
+	noteId := 0
+
+	// id part: anything but a newline between brackets
+	if data[i] != '[' {
+		return 0
+	}
+	i++
+	if p.flags&EXTENSION_FOOTNOTES != 0 {
+		if i < len(data) && data[i] == '^' {
+			// we can set it to anything here because the proper noteIds will
+			// be assigned later during the second pass. It just has to be != 0
+			noteId = 1
+			i++
+		}
+	}
+	idOffset := i
+	for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
+		i++
+	}
+	if i >= len(data) || data[i] != ']' {
+		return 0
+	}
+	idEnd := i
+
+	// spacer: colon (space | tab)* newline? (space | tab)*
+	i++
+	if i >= len(data) || data[i] != ':' {
+		return 0
+	}
+	i++
+	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
+		i++
+	}
+	if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
+		i++
+		if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
+			i++
+		}
+	}
+	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
+		i++
+	}
+	if i >= len(data) {
+		return 0
+	}
+
+	var (
+		linkOffset, linkEnd   int
+		titleOffset, titleEnd int
+		lineEnd               int
+		raw                   []byte
+		hasBlock              bool
+	)
+
+	if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 {
+		linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
+		lineEnd = linkEnd
+	} else {
+		linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
+	}
+	if lineEnd == 0 {
+		return 0
+	}
+
+	// a valid ref has been found
+
+	ref := &reference{
+		noteId:   noteId,
+		hasBlock: hasBlock,
+	}
+
+	if noteId > 0 {
+		// reusing the link field for the id since footnotes don't have links
+		ref.link = data[idOffset:idEnd]
+		// if footnote, it's not really a title, it's the contained text
+		ref.title = raw
+	} else {
+		ref.link = data[linkOffset:linkEnd]
+		ref.title = data[titleOffset:titleEnd]
+	}
+
+	// id matches are case-insensitive
+	id := string(bytes.ToLower(data[idOffset:idEnd]))
+
+	p.refs[id] = ref
+
+	return lineEnd
+}
+
+func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
+	// link: whitespace-free sequence, optionally between angle brackets
+	if data[i] == '<' {
+		i++
+	}
+	linkOffset = i
+	if i == len(data) {
+		return
+	}
+	for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
+		i++
+	}
+	linkEnd = i
+	if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
+		linkOffset++
+		linkEnd--
+	}
+
+	// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
+	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
+		i++
+	}
+	if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
+		return
+	}
+
+	// compute end-of-line
+	if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
+		lineEnd = i
+	}
+	if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
+		lineEnd++
+	}
+
+	// optional (space|tab)* spacer after a newline
+	if lineEnd > 0 {
+		i = lineEnd + 1
+		for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
+			i++
+		}
+	}
+
+	// optional title: any non-newline sequence enclosed in '"() alone on its line
+	if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
+		i++
+		titleOffset = i
+
+		// look for EOL
+		for i < len(data) && data[i] != '\n' && data[i] != '\r' {
+			i++
+		}
+		if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
+			titleEnd = i + 1
+		} else {
+			titleEnd = i
+		}
+
+		// step back
+		i--
+		for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
+			i--
+		}
+		if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
+			lineEnd = titleEnd
+			titleEnd = i
+		}
+	}
+
+	return
+}
+
+// The first bit of this logic is the same as (*parser).listItem, but the rest
+// is much simpler. This function simply finds the entire block and shifts it
+// over by one tab if it is indeed a block (just returns the line if it's not).
+// blockEnd is the end of the section in the input buffer, and contents is the
+// extracted text that was shifted over one tab. It will need to be rendered at
+// the end of the document.
+func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
+	if i == 0 || len(data) == 0 {
+		return
+	}
+
+	// skip leading whitespace on first line
+	for i < len(data) && data[i] == ' ' {
+		i++
+	}
+
+	blockStart = i
+
+	// find the end of the line
+	blockEnd = i
+	for i < len(data) && data[i-1] != '\n' {
+		i++
+	}
+
+	// get working buffer
+	var raw bytes.Buffer
+
+	// put the first line into the working buffer
+	raw.Write(data[blockEnd:i])
+	blockEnd = i
+
+	// process the following lines
+	containsBlankLine := false
+
+gatherLines:
+	for blockEnd < len(data) {
+		i++
+
+		// find the end of this line
+		for i < len(data) && data[i-1] != '\n' {
+			i++
+		}
+
+		// if it is an empty line, guess that it is part of this item
+		// and move on to the next line
+		if p.isEmpty(data[blockEnd:i]) > 0 {
+			containsBlankLine = true
+			blockEnd = i
+			continue
+		}
+
+		n := 0
+		if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
+			// this is the end of the block.
+			// we don't want to include this last line in the index.
+			break gatherLines
+		}
+
+		// if there were blank lines before this one, insert a new one now
+		if containsBlankLine {
+			raw.WriteByte('\n')
+			containsBlankLine = false
+		}
+
+		// get rid of that first tab, write to buffer
+		raw.Write(data[blockEnd+n : i])
+		hasBlock = true
+
+		blockEnd = i
+	}
+
+	if data[blockEnd-1] != '\n' {
+		raw.WriteByte('\n')
+	}
+
+	contents = raw.Bytes()
+
+	return
+}
+
+//
+//
+// Miscellaneous helper functions
+//
+//
+
+// Test if a character is a punctuation symbol.
+// Taken from a private function in regexp in the stdlib.
+func ispunct(c byte) bool {
+	for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
+		if c == r {
+			return true
+		}
+	}
+	return false
+}
+
+// Test if a character is a whitespace character.
+func isspace(c byte) bool {
+	return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
+}
+
+// Test if a character is letter.
+func isletter(c byte) bool {
+	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+}
+
+// Test if a character is a letter or a digit.
+// TODO: check when this is looking for ASCII alnum and when it should use unicode
+func isalnum(c byte) bool {
+	return (c >= '0' && c <= '9') || isletter(c)
+}
+
+// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
+// always ends output with a newline
+func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
+	// first, check for common cases: no tabs, or only tabs at beginning of line
+	i, prefix := 0, 0
+	slowcase := false
+	for i = 0; i < len(line); i++ {
+		if line[i] == '\t' {
+			if prefix == i {
+				prefix++
+			} else {
+				slowcase = true
+				break
+			}
+		}
+	}
+
+	// no need to decode runes if all tabs are at the beginning of the line
+	if !slowcase {
+		for i = 0; i < prefix*tabSize; i++ {
+			out.WriteByte(' ')
+		}
+		out.Write(line[prefix:])
+		return
+	}
+
+	// the slow case: we need to count runes to figure out how
+	// many spaces to insert for each tab
+	column := 0
+	i = 0
+	for i < len(line) {
+		start := i
+		for i < len(line) && line[i] != '\t' {
+			_, size := utf8.DecodeRune(line[i:])
+			i += size
+			column++
+		}
+
+		if i > start {
+			out.Write(line[start:i])
+		}
+
+		if i >= len(line) {
+			break
+		}
+
+		for {
+			out.WriteByte(' ')
+			column++
+			if column%tabSize == 0 {
+				break
+			}
+		}
+
+		i++
+	}
+}
+
+// Find if a line counts as indented or not.
+// Returns number of characters the indent is (0 = not indented).
+func isIndented(data []byte, indentSize int) int {
+	if len(data) == 0 {
+		return 0
+	}
+	if data[0] == '\t' {
+		return 1
+	}
+	if len(data) < indentSize {
+		return 0
+	}
+	for i := 0; i < indentSize; i++ {
+		if data[i] != ' ' {
+			return 0
+		}
+	}
+	return indentSize
+}
+
+// Create a url-safe slug for fragments
+func slugify(in []byte) []byte {
+	if len(in) == 0 {
+		return in
+	}
+	out := make([]byte, 0, len(in))
+	sym := false
+
+	for _, ch := range in {
+		if isalnum(ch) {
+			sym = false
+			out = append(out, ch)
+		} else if sym {
+			continue
+		} else {
+			out = append(out, '-')
+			sym = true
+		}
+	}
+	var a, b int
+	var ch byte
+	for a, ch = range out {
+		if ch != '-' {
+			break
+		}
+	}
+	for b = len(out) - 1; b > 0; b-- {
+		if out[b] != '-' {
+			break
+		}
+	}
+	return out[a : b+1]
+}

+ 75 - 0
vendor/github.com/russross/blackfriday/markdown_test.go

@@ -0,0 +1,75 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+// Unit tests for full document parsing and rendering
+//
+
+package blackfriday
+
+import (
+	"testing"
+)
+
+func runMarkdown(input string) string {
+	return string(MarkdownCommon([]byte(input)))
+}
+
+func doTests(t *testing.T, tests []string) {
+	// catch and report panics
+	var candidate string
+	defer func() {
+		if err := recover(); err != nil {
+			t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
+		}
+	}()
+
+	for i := 0; i+1 < len(tests); i += 2 {
+		input := tests[i]
+		candidate = input
+		expected := tests[i+1]
+		actual := runMarkdown(candidate)
+		if actual != expected {
+			t.Errorf("\nInput   [%#v]\nExpected[%#v]\nActual  [%#v]",
+				candidate, expected, actual)
+		}
+
+		// now test every substring to stress test bounds checking
+		if !testing.Short() {
+			for start := 0; start < len(input); start++ {
+				for end := start + 1; end <= len(input); end++ {
+					candidate = input[start:end]
+					_ = runMarkdown(candidate)
+				}
+			}
+		}
+	}
+}
+
+func TestDocument(t *testing.T) {
+	var tests = []string{
+		// Empty document.
+		"",
+		"",
+
+		" ",
+		"",
+
+		// This shouldn't panic.
+		// https://github.com/russross/blackfriday/issues/172
+		"[]:<",
+		"<p>[]:&lt;</p>\n",
+
+		// This shouldn't panic.
+		// https://github.com/russross/blackfriday/issues/173
+		"   [",
+		"<p>[</p>\n",
+	}
+	doTests(t, tests)
+}

+ 128 - 0
vendor/github.com/russross/blackfriday/ref_test.go

@@ -0,0 +1,128 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+// Markdown 1.0.3 reference tests
+//
+
+package blackfriday
+
+import (
+	"io/ioutil"
+	"path/filepath"
+	"testing"
+)
+
+func runMarkdownReference(input string, flag int) string {
+	renderer := HtmlRenderer(0, "", "")
+	return string(Markdown([]byte(input), renderer, flag))
+}
+
+func doTestsReference(t *testing.T, files []string, flag int) {
+	// catch and report panics
+	var candidate string
+	defer func() {
+		if err := recover(); err != nil {
+			t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
+		}
+	}()
+
+	for _, basename := range files {
+		filename := filepath.Join("testdata", basename+".text")
+		inputBytes, err := ioutil.ReadFile(filename)
+		if err != nil {
+			t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
+			continue
+		}
+		input := string(inputBytes)
+
+		filename = filepath.Join("testdata", basename+".html")
+		expectedBytes, err := ioutil.ReadFile(filename)
+		if err != nil {
+			t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
+			continue
+		}
+		expected := string(expectedBytes)
+
+		// fmt.Fprintf(os.Stderr, "processing %s ...", filename)
+		actual := string(runMarkdownReference(input, flag))
+		if actual != expected {
+			t.Errorf("\n    [%#v]\nExpected[%#v]\nActual  [%#v]",
+				basename+".text", expected, actual)
+		}
+		// fmt.Fprintf(os.Stderr, " ok\n")
+
+		// now test every prefix of every input to check for
+		// bounds checking
+		if !testing.Short() {
+			start, max := 0, len(input)
+			for end := start + 1; end <= max; end++ {
+				candidate = input[start:end]
+				// fmt.Fprintf(os.Stderr, "  %s %d:%d/%d\n", filename, start, end, max)
+				_ = runMarkdownReference(candidate, flag)
+			}
+		}
+	}
+}
+
+func TestReference(t *testing.T) {
+	files := []string{
+		"Amps and angle encoding",
+		"Auto links",
+		"Backslash escapes",
+		"Blockquotes with code blocks",
+		"Code Blocks",
+		"Code Spans",
+		"Hard-wrapped paragraphs with list-like lines",
+		"Horizontal rules",
+		"Inline HTML (Advanced)",
+		"Inline HTML (Simple)",
+		"Inline HTML comments",
+		"Links, inline style",
+		"Links, reference style",
+		"Links, shortcut references",
+		"Literal quotes in titles",
+		"Markdown Documentation - Basics",
+		"Markdown Documentation - Syntax",
+		"Nested blockquotes",
+		"Ordered and unordered lists",
+		"Strong and em together",
+		"Tabs",
+		"Tidyness",
+	}
+	doTestsReference(t, files, 0)
+}
+
+func TestReference_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
+	files := []string{
+		"Amps and angle encoding",
+		"Auto links",
+		"Backslash escapes",
+		"Blockquotes with code blocks",
+		"Code Blocks",
+		"Code Spans",
+		"Hard-wrapped paragraphs with list-like lines no empty line before block",
+		"Horizontal rules",
+		"Inline HTML (Advanced)",
+		"Inline HTML (Simple)",
+		"Inline HTML comments",
+		"Links, inline style",
+		"Links, reference style",
+		"Links, shortcut references",
+		"Literal quotes in titles",
+		"Markdown Documentation - Basics",
+		"Markdown Documentation - Syntax",
+		"Nested blockquotes",
+		"Ordered and unordered lists",
+		"Strong and em together",
+		"Tabs",
+		"Tidyness",
+	}
+	doTestsReference(t, files, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
+}

+ 400 - 0
vendor/github.com/russross/blackfriday/smartypants.go

@@ -0,0 +1,400 @@
+//
+// Blackfriday Markdown Processor
+// Available at http://github.com/russross/blackfriday
+//
+// Copyright © 2011 Russ Ross <[email protected]>.
+// Distributed under the Simplified BSD License.
+// See README.md for details.
+//
+
+//
+//
+// SmartyPants rendering
+//
+//
+
+package blackfriday
+
+import (
+	"bytes"
+)
+
+type smartypantsData struct {
+	inSingleQuote bool
+	inDoubleQuote bool
+}
+
+func wordBoundary(c byte) bool {
+	return c == 0 || isspace(c) || ispunct(c)
+}
+
+func tolower(c byte) byte {
+	if c >= 'A' && c <= 'Z' {
+		return c - 'A' + 'a'
+	}
+	return c
+}
+
+func isdigit(c byte) bool {
+	return c >= '0' && c <= '9'
+}
+
+func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
+	// edge of the buffer is likely to be a tag that we don't get to see,
+	// so we treat it like text sometimes
+
+	// enumerate all sixteen possibilities for (previousChar, nextChar)
+	// each can be one of {0, space, punct, other}
+	switch {
+	case previousChar == 0 && nextChar == 0:
+		// context is not any help here, so toggle
+		*isOpen = !*isOpen
+	case isspace(previousChar) && nextChar == 0:
+		// [ "] might be [ "<code>foo...]
+		*isOpen = true
+	case ispunct(previousChar) && nextChar == 0:
+		// [!"] hmm... could be [Run!"] or [("<code>...]
+		*isOpen = false
+	case /* isnormal(previousChar) && */ nextChar == 0:
+		// [a"] is probably a close
+		*isOpen = false
+	case previousChar == 0 && isspace(nextChar):
+		// [" ] might be [...foo</code>" ]
+		*isOpen = false
+	case isspace(previousChar) && isspace(nextChar):
+		// [ " ] context is not any help here, so toggle
+		*isOpen = !*isOpen
+	case ispunct(previousChar) && isspace(nextChar):
+		// [!" ] is probably a close
+		*isOpen = false
+	case /* isnormal(previousChar) && */ isspace(nextChar):
+		// [a" ] this is one of the easy cases
+		*isOpen = false
+	case previousChar == 0 && ispunct(nextChar):
+		// ["!] hmm... could be ["$1.95] or [</code>"!...]
+		*isOpen = false
+	case isspace(previousChar) && ispunct(nextChar):
+		// [ "!] looks more like [ "$1.95]
+		*isOpen = true
+	case ispunct(previousChar) && ispunct(nextChar):
+		// [!"!] context is not any help here, so toggle
+		*isOpen = !*isOpen
+	case /* isnormal(previousChar) && */ ispunct(nextChar):
+		// [a"!] is probably a close
+		*isOpen = false
+	case previousChar == 0 /* && isnormal(nextChar) */ :
+		// ["a] is probably an open
+		*isOpen = true
+	case isspace(previousChar) /* && isnormal(nextChar) */ :
+		// [ "a] this is one of the easy cases
+		*isOpen = true
+	case ispunct(previousChar) /* && isnormal(nextChar) */ :
+		// [!"a] is probably an open
+		*isOpen = true
+	default:
+		// [a'b] maybe a contraction?
+		*isOpen = false
+	}
+
+	out.WriteByte('&')
+	if *isOpen {
+		out.WriteByte('l')
+	} else {
+		out.WriteByte('r')
+	}
+	out.WriteByte(quote)
+	out.WriteString("quo;")
+	return true
+}
+
+func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	if len(text) >= 2 {
+		t1 := tolower(text[1])
+
+		if t1 == '\'' {
+			nextChar := byte(0)
+			if len(text) >= 3 {
+				nextChar = text[2]
+			}
+			if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
+				return 1
+			}
+		}
+
+		if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
+			out.WriteString("&rsquo;")
+			return 0
+		}
+
+		if len(text) >= 3 {
+			t2 := tolower(text[2])
+
+			if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
+				(len(text) < 4 || wordBoundary(text[3])) {
+				out.WriteString("&rsquo;")
+				return 0
+			}
+		}
+	}
+
+	nextChar := byte(0)
+	if len(text) > 1 {
+		nextChar = text[1]
+	}
+	if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
+		return 0
+	}
+
+	out.WriteByte(text[0])
+	return 0
+}
+
+func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	if len(text) >= 3 {
+		t1 := tolower(text[1])
+		t2 := tolower(text[2])
+
+		if t1 == 'c' && t2 == ')' {
+			out.WriteString("&copy;")
+			return 2
+		}
+
+		if t1 == 'r' && t2 == ')' {
+			out.WriteString("&reg;")
+			return 2
+		}
+
+		if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
+			out.WriteString("&trade;")
+			return 3
+		}
+	}
+
+	out.WriteByte(text[0])
+	return 0
+}
+
+func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	if len(text) >= 2 {
+		if text[1] == '-' {
+			out.WriteString("&mdash;")
+			return 1
+		}
+
+		if wordBoundary(previousChar) && wordBoundary(text[1]) {
+			out.WriteString("&ndash;")
+			return 0
+		}
+	}
+
+	out.WriteByte(text[0])
+	return 0
+}
+
+func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
+		out.WriteString("&mdash;")
+		return 2
+	}
+	if len(text) >= 2 && text[1] == '-' {
+		out.WriteString("&ndash;")
+		return 1
+	}
+
+	out.WriteByte(text[0])
+	return 0
+}
+
+func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
+	if bytes.HasPrefix(text, []byte("&quot;")) {
+		nextChar := byte(0)
+		if len(text) >= 7 {
+			nextChar = text[6]
+		}
+		if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
+			return 5
+		}
+	}
+
+	if bytes.HasPrefix(text, []byte("&#0;")) {
+		return 3
+	}
+
+	out.WriteByte('&')
+	return 0
+}
+
+func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	return smartAmpVariant(out, smrt, previousChar, text, 'd')
+}
+
+func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	return smartAmpVariant(out, smrt, previousChar, text, 'a')
+}
+
+func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
+		out.WriteString("&hellip;")
+		return 2
+	}
+
+	if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
+		out.WriteString("&hellip;")
+		return 4
+	}
+
+	out.WriteByte(text[0])
+	return 0
+}
+
+func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	if len(text) >= 2 && text[1] == '`' {
+		nextChar := byte(0)
+		if len(text) >= 3 {
+			nextChar = text[2]
+		}
+		if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
+			return 1
+		}
+	}
+
+	out.WriteByte(text[0])
+	return 0
+}
+
+func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
+		// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
+		// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
+		//       and avoid changing dates like 1/23/2005 into fractions.
+		numEnd := 0
+		for len(text) > numEnd && isdigit(text[numEnd]) {
+			numEnd++
+		}
+		if numEnd == 0 {
+			out.WriteByte(text[0])
+			return 0
+		}
+		denStart := numEnd + 1
+		if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
+			denStart = numEnd + 3
+		} else if len(text) < numEnd+2 || text[numEnd] != '/' {
+			out.WriteByte(text[0])
+			return 0
+		}
+		denEnd := denStart
+		for len(text) > denEnd && isdigit(text[denEnd]) {
+			denEnd++
+		}
+		if denEnd == denStart {
+			out.WriteByte(text[0])
+			return 0
+		}
+		if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
+			out.WriteString("<sup>")
+			out.Write(text[:numEnd])
+			out.WriteString("</sup>&frasl;<sub>")
+			out.Write(text[denStart:denEnd])
+			out.WriteString("</sub>")
+			return denEnd - 1
+		}
+	}
+
+	out.WriteByte(text[0])
+	return 0
+}
+
+func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
+		if text[0] == '1' && text[1] == '/' && text[2] == '2' {
+			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
+				out.WriteString("&frac12;")
+				return 2
+			}
+		}
+
+		if text[0] == '1' && text[1] == '/' && text[2] == '4' {
+			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
+				out.WriteString("&frac14;")
+				return 2
+			}
+		}
+
+		if text[0] == '3' && text[1] == '/' && text[2] == '4' {
+			if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
+				out.WriteString("&frac34;")
+				return 2
+			}
+		}
+	}
+
+	out.WriteByte(text[0])
+	return 0
+}
+
+func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
+	nextChar := byte(0)
+	if len(text) > 1 {
+		nextChar = text[1]
+	}
+	if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
+		out.WriteString("&quot;")
+	}
+
+	return 0
+}
+
+func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd')
+}
+
+func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a')
+}
+
+func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
+	i := 0
+
+	for i < len(text) && text[i] != '>' {
+		i++
+	}
+
+	out.Write(text[:i+1])
+	return i
+}
+
+type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int
+
+type smartypantsRenderer [256]smartCallback
+
+func smartypants(flags int) *smartypantsRenderer {
+	r := new(smartypantsRenderer)
+	if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
+		r['"'] = smartDoubleQuote
+		r['&'] = smartAmp
+	} else {
+		r['"'] = smartAngledDoubleQuote
+		r['&'] = smartAmpAngledQuote
+	}
+	r['\''] = smartSingleQuote
+	r['('] = smartParens
+	if flags&HTML_SMARTYPANTS_DASHES != 0 {
+		if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
+			r['-'] = smartDash
+		} else {
+			r['-'] = smartDashLatex
+		}
+	}
+	r['.'] = smartPeriod
+	if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
+		r['1'] = smartNumber
+		r['3'] = smartNumber
+	} else {
+		for ch := '1'; ch <= '9'; ch++ {
+			r[ch] = smartNumberGeneric
+		}
+	}
+	r['<'] = smartLeftAngle
+	r['`'] = smartBacktick
+	return r
+}

+ 17 - 0
vendor/github.com/russross/blackfriday/testdata/Amps and angle encoding.html

@@ -0,0 +1,17 @@
+<p>AT&amp;T has an ampersand in their name.</p>
+
+<p>AT&amp;T is another way to write it.</p>
+
+<p>This &amp; that.</p>
+
+<p>4 &lt; 5.</p>
+
+<p>6 &gt; 5.</p>
+
+<p>Here's a <a href="http://example.com/?foo=1&amp;bar=2">link</a> with an ampersand in the URL.</p>
+
+<p>Here's a link with an amersand in the link text: <a href="http://att.com/" title="AT&amp;T">AT&amp;T</a>.</p>
+
+<p>Here's an inline <a href="/script?foo=1&amp;bar=2">link</a>.</p>
+
+<p>Here's an inline <a href="/script?foo=1&amp;bar=2">link</a>.</p>

+ 21 - 0
vendor/github.com/russross/blackfriday/testdata/Amps and angle encoding.text

@@ -0,0 +1,21 @@
+AT&T has an ampersand in their name.
+
+AT&amp;T is another way to write it.
+
+This & that.
+
+4 < 5.
+
+6 > 5.
+
+Here's a [link] [1] with an ampersand in the URL.
+
+Here's a link with an amersand in the link text: [AT&T] [2].
+
+Here's an inline [link](/script?foo=1&bar=2).
+
+Here's an inline [link](</script?foo=1&bar=2>).
+
+
+[1]: http://example.com/?foo=1&bar=2
+[2]: http://att.com/  "AT&T"

+ 18 - 0
vendor/github.com/russross/blackfriday/testdata/Auto links.html

@@ -0,0 +1,18 @@
+<p>Link: <a href="http://example.com/">http://example.com/</a>.</p>
+
+<p>With an ampersand: <a href="http://example.com/?foo=1&amp;bar=2">http://example.com/?foo=1&amp;bar=2</a></p>
+
+<ul>
+<li>In a list?</li>
+<li><a href="http://example.com/">http://example.com/</a></li>
+<li>It should.</li>
+</ul>
+
+<blockquote>
+<p>Blockquoted: <a href="http://example.com/">http://example.com/</a></p>
+</blockquote>
+
+<p>Auto-links should not occur here: <code>&lt;http://example.com/&gt;</code></p>
+
+<pre><code>or here: &lt;http://example.com/&gt;
+</code></pre>

+ 13 - 0
vendor/github.com/russross/blackfriday/testdata/Auto links.text

@@ -0,0 +1,13 @@
+Link: <http://example.com/>.
+
+With an ampersand: <http://example.com/?foo=1&bar=2>
+
+* In a list?
+* <http://example.com/>
+* It should.
+
+> Blockquoted: <http://example.com/>
+
+Auto-links should not occur here: `<http://example.com/>`
+
+	or here: <http://example.com/>

+ 123 - 0
vendor/github.com/russross/blackfriday/testdata/Backslash escapes.html

@@ -0,0 +1,123 @@
+<p>These should all get escaped:</p>
+
+<p>Backslash: \</p>
+
+<p>Backtick: `</p>
+
+<p>Asterisk: *</p>
+
+<p>Underscore: _</p>
+
+<p>Left brace: {</p>
+
+<p>Right brace: }</p>
+
+<p>Left bracket: [</p>
+
+<p>Right bracket: ]</p>
+
+<p>Left paren: (</p>
+
+<p>Right paren: )</p>
+
+<p>Greater-than: &gt;</p>
+
+<p>Hash: #</p>
+
+<p>Period: .</p>
+
+<p>Bang: !</p>
+
+<p>Plus: +</p>
+
+<p>Minus: -</p>
+
+<p>Tilde: ~</p>
+
+<p>These should not, because they occur within a code block:</p>
+
+<pre><code>Backslash: \\
+
+Backtick: \`
+
+Asterisk: \*
+
+Underscore: \_
+
+Left brace: \{
+
+Right brace: \}
+
+Left bracket: \[
+
+Right bracket: \]
+
+Left paren: \(
+
+Right paren: \)
+
+Greater-than: \&gt;
+
+Hash: \#
+
+Period: \.
+
+Bang: \!
+
+Plus: \+
+
+Minus: \-
+
+Tilde: \~
+</code></pre>
+
+<p>Nor should these, which occur in code spans:</p>
+
+<p>Backslash: <code>\\</code></p>
+
+<p>Backtick: <code>\`</code></p>
+
+<p>Asterisk: <code>\*</code></p>
+
+<p>Underscore: <code>\_</code></p>
+
+<p>Left brace: <code>\{</code></p>
+
+<p>Right brace: <code>\}</code></p>
+
+<p>Left bracket: <code>\[</code></p>
+
+<p>Right bracket: <code>\]</code></p>
+
+<p>Left paren: <code>\(</code></p>
+
+<p>Right paren: <code>\)</code></p>
+
+<p>Greater-than: <code>\&gt;</code></p>
+
+<p>Hash: <code>\#</code></p>
+
+<p>Period: <code>\.</code></p>
+
+<p>Bang: <code>\!</code></p>
+
+<p>Plus: <code>\+</code></p>
+
+<p>Minus: <code>\-</code></p>
+
+<p>Tilde: <code>\~</code></p>
+
+<p>These should get escaped, even though they're matching pairs for
+other Markdown constructs:</p>
+
+<p>*asterisks*</p>
+
+<p>_underscores_</p>
+
+<p>`backticks`</p>
+
+<p>This is a code span with a literal backslash-backtick sequence: <code>\`</code></p>
+
+<p>This is a tag with unescaped backticks <span attr='`ticks`'>bar</span>.</p>
+
+<p>This is a tag with backslashes <span attr='\\backslashes\\'>bar</span>.</p>

+ 126 - 0
vendor/github.com/russross/blackfriday/testdata/Backslash escapes.text

@@ -0,0 +1,126 @@
+These should all get escaped:
+
+Backslash: \\
+
+Backtick: \`
+
+Asterisk: \*
+
+Underscore: \_
+
+Left brace: \{
+
+Right brace: \}
+
+Left bracket: \[
+
+Right bracket: \]
+
+Left paren: \(
+
+Right paren: \)
+
+Greater-than: \>
+
+Hash: \#
+
+Period: \.
+
+Bang: \!
+
+Plus: \+
+
+Minus: \-
+
+Tilde: \~
+
+
+
+These should not, because they occur within a code block:
+
+	Backslash: \\
+
+	Backtick: \`
+
+	Asterisk: \*
+
+	Underscore: \_
+
+	Left brace: \{
+
+	Right brace: \}
+
+	Left bracket: \[
+
+	Right bracket: \]
+
+	Left paren: \(
+
+	Right paren: \)
+
+	Greater-than: \>
+
+	Hash: \#
+
+	Period: \.
+
+	Bang: \!
+
+	Plus: \+
+
+	Minus: \-
+
+	Tilde: \~
+
+
+Nor should these, which occur in code spans:
+
+Backslash: `\\`
+
+Backtick: `` \` ``
+
+Asterisk: `\*`
+
+Underscore: `\_`
+
+Left brace: `\{`
+
+Right brace: `\}`
+
+Left bracket: `\[`
+
+Right bracket: `\]`
+
+Left paren: `\(`
+
+Right paren: `\)`
+
+Greater-than: `\>`
+
+Hash: `\#`
+
+Period: `\.`
+
+Bang: `\!`
+
+Plus: `\+`
+
+Minus: `\-`
+
+Tilde: `\~`
+
+
+These should get escaped, even though they're matching pairs for
+other Markdown constructs:
+
+\*asterisks\*
+
+\_underscores\_
+
+\`backticks\`
+
+This is a code span with a literal backslash-backtick sequence: `` \` ``
+
+This is a tag with unescaped backticks <span attr='`ticks`'>bar</span>.
+
+This is a tag with backslashes <span attr='\\backslashes\\'>bar</span>.

+ 15 - 0
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.html

@@ -0,0 +1,15 @@
+<blockquote>
+<p>Example:</p>
+
+<pre><code>sub status {
+    print &quot;working&quot;;
+}
+</code></pre>
+
+<p>Or:</p>
+
+<pre><code>sub status {
+    return &quot;working&quot;;
+}
+</code></pre>
+</blockquote>

+ 11 - 0
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.text

@@ -0,0 +1,11 @@
+> Example:
+> 
+>     sub status {
+>         print "working";
+>     }
+> 
+> Or:
+> 
+>     sub status {
+>         return "working";
+>     }

+ 18 - 0
vendor/github.com/russross/blackfriday/testdata/Code Blocks.html

@@ -0,0 +1,18 @@
+<pre><code>code block on the first line
+</code></pre>
+
+<p>Regular text.</p>
+
+<pre><code>code block indented by spaces
+</code></pre>
+
+<p>Regular text.</p>
+
+<pre><code>the lines in this block  
+all contain trailing spaces  
+</code></pre>
+
+<p>Regular Text.</p>
+
+<pre><code>code block on the last line
+</code></pre>

+ 14 - 0
vendor/github.com/russross/blackfriday/testdata/Code Blocks.text

@@ -0,0 +1,14 @@
+	code block on the first line
+	
+Regular text.
+
+    code block indented by spaces
+
+Regular text.
+
+	the lines in this block  
+	all contain trailing spaces  
+
+Regular Text.
+
+	code block on the last line

+ 5 - 0
vendor/github.com/russross/blackfriday/testdata/Code Spans.html

@@ -0,0 +1,5 @@
+<p><code>&lt;test a=&quot;</code> content of attribute <code>&quot;&gt;</code></p>
+
+<p>Fix for backticks within HTML tag: <span attr='`ticks`'>like this</span></p>
+
+<p>Here's how you put <code>`backticks`</code> in a code span.</p>

+ 6 - 0
vendor/github.com/russross/blackfriday/testdata/Code Spans.text

@@ -0,0 +1,6 @@
+`<test a="` content of attribute `">`
+
+Fix for backticks within HTML tag: <span attr='`ticks`'>like this</span>
+
+Here's how you put `` `backticks` `` in a code span.
+

+ 14 - 0
vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.html

@@ -0,0 +1,14 @@
+<p>In Markdown 1.0.0 and earlier. Version</p>
+
+<ol>
+<li>This line turns into a list item.
+Because a hard-wrapped line in the
+middle of a paragraph looked like a
+list item.</li>
+</ol>
+
+<p>Here's one with a bullet.</p>
+
+<ul>
+<li>criminey.</li>
+</ul>

+ 8 - 0
vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines no empty line before block.text

@@ -0,0 +1,8 @@
+In Markdown 1.0.0 and earlier. Version
+8. This line turns into a list item.
+Because a hard-wrapped line in the
+middle of a paragraph looked like a
+list item.
+
+Here's one with a bullet.
+* criminey.

+ 8 - 0
vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines.html

@@ -0,0 +1,8 @@
+<p>In Markdown 1.0.0 and earlier. Version
+8. This line turns into a list item.
+Because a hard-wrapped line in the
+middle of a paragraph looked like a
+list item.</p>
+
+<p>Here's one with a bullet.
+* criminey.</p>

+ 8 - 0
vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines.text

@@ -0,0 +1,8 @@
+In Markdown 1.0.0 and earlier. Version
+8. This line turns into a list item.
+Because a hard-wrapped line in the
+middle of a paragraph looked like a
+list item.
+
+Here's one with a bullet.
+* criminey.

+ 71 - 0
vendor/github.com/russross/blackfriday/testdata/Horizontal rules.html

@@ -0,0 +1,71 @@
+<p>Dashes:</p>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<pre><code>---
+</code></pre>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<pre><code>- - -
+</code></pre>
+
+<p>Asterisks:</p>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<pre><code>***
+</code></pre>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<pre><code>* * *
+</code></pre>
+
+<p>Underscores:</p>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<pre><code>___
+</code></pre>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<hr>
+
+<pre><code>_ _ _
+</code></pre>

+ 67 - 0
vendor/github.com/russross/blackfriday/testdata/Horizontal rules.text

@@ -0,0 +1,67 @@
+Dashes:
+
+---
+
+ ---
+ 
+  ---
+
+   ---
+
+	---
+
+- - -
+
+ - - -
+ 
+  - - -
+
+   - - -
+
+	- - -
+
+
+Asterisks:
+
+***
+
+ ***
+ 
+  ***
+
+   ***
+
+	***
+
+* * *
+
+ * * *
+ 
+  * * *
+
+   * * *
+
+	* * *
+
+
+Underscores:
+
+___
+
+ ___
+ 
+  ___
+
+   ___
+
+    ___
+
+_ _ _
+
+ _ _ _
+ 
+  _ _ _
+
+   _ _ _
+
+    _ _ _

+ 15 - 0
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Advanced).html

@@ -0,0 +1,15 @@
+<p>Simple block on one line:</p>
+
+<div>foo</div>
+
+<p>And nested without indentation:</p>
+
+<div>
+<div>
+<div>
+foo
+</div>
+<div style=">"/>
+</div>
+<div>bar</div>
+</div>

+ 15 - 0
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Advanced).text

@@ -0,0 +1,15 @@
+Simple block on one line:
+
+<div>foo</div>
+
+And nested without indentation:
+
+<div>
+<div>
+<div>
+foo
+</div>
+<div style=">"/>
+</div>
+<div>bar</div>
+</div>

+ 72 - 0
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Simple).html

@@ -0,0 +1,72 @@
+<p>Here's a simple block:</p>
+
+<div>
+    foo
+</div>
+
+<p>This should be a code block, though:</p>
+
+<pre><code>&lt;div&gt;
+    foo
+&lt;/div&gt;
+</code></pre>
+
+<p>As should this:</p>
+
+<pre><code>&lt;div&gt;foo&lt;/div&gt;
+</code></pre>
+
+<p>Now, nested:</p>
+
+<div>
+    <div>
+        <div>
+            foo
+        </div>
+    </div>
+</div>
+
+<p>This should just be an HTML comment:</p>
+
+<!-- Comment -->
+
+<p>Multiline:</p>
+
+<!--
+Blah
+Blah
+-->
+
+<p>Code block:</p>
+
+<pre><code>&lt;!-- Comment --&gt;
+</code></pre>
+
+<p>Just plain comment, with trailing spaces on the line:</p>
+
+<!-- foo -->   
+
+<p>Code:</p>
+
+<pre><code>&lt;hr /&gt;
+</code></pre>
+
+<p>Hr's:</p>
+
+<hr>
+
+<hr/>
+
+<hr />
+
+<hr>   
+
+<hr/>  
+
+<hr /> 
+
+<hr class="foo" id="bar" />
+
+<hr class="foo" id="bar"/>
+
+<hr class="foo" id="bar" >

+ 69 - 0
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Simple).text

@@ -0,0 +1,69 @@
+Here's a simple block:
+
+<div>
+	foo
+</div>
+
+This should be a code block, though:
+
+	<div>
+		foo
+	</div>
+
+As should this:
+
+	<div>foo</div>
+
+Now, nested:
+
+<div>
+	<div>
+		<div>
+			foo
+		</div>
+	</div>
+</div>
+
+This should just be an HTML comment:
+
+<!-- Comment -->
+
+Multiline:
+
+<!--
+Blah
+Blah
+-->
+
+Code block:
+
+	<!-- Comment -->
+
+Just plain comment, with trailing spaces on the line:
+
+<!-- foo -->   
+
+Code:
+
+	<hr />
+	
+Hr's:
+
+<hr>
+
+<hr/>
+
+<hr />
+
+<hr>   
+
+<hr/>  
+
+<hr /> 
+
+<hr class="foo" id="bar" />
+
+<hr class="foo" id="bar"/>
+
+<hr class="foo" id="bar" >
+

+ 13 - 0
vendor/github.com/russross/blackfriday/testdata/Inline HTML comments.html

@@ -0,0 +1,13 @@
+<p>Paragraph one.</p>
+
+<!-- This is a simple comment -->
+
+<!--
+    This is another comment.
+-->
+
+<p>Paragraph two.</p>
+
+<!-- one comment block -- -- with two comments -->
+
+<p>The end.</p>

+ 13 - 0
vendor/github.com/russross/blackfriday/testdata/Inline HTML comments.text

@@ -0,0 +1,13 @@
+Paragraph one.
+
+<!-- This is a simple comment -->
+
+<!--
+	This is another comment.
+-->
+
+Paragraph two.
+
+<!-- one comment block -- -- with two comments -->
+
+The end.

+ 11 - 0
vendor/github.com/russross/blackfriday/testdata/Links, inline style.html

@@ -0,0 +1,11 @@
+<p>Just a <a href="/url/">URL</a>.</p>
+
+<p><a href="/url/" title="title">URL and title</a>.</p>
+
+<p><a href="/url/" title="title preceded by two spaces">URL and title</a>.</p>
+
+<p><a href="/url/" title="title preceded by a tab">URL and title</a>.</p>
+
+<p><a href="/url/" title="title has spaces afterward">URL and title</a>.</p>
+
+<p>[Empty]().</p>

+ 12 - 0
vendor/github.com/russross/blackfriday/testdata/Links, inline style.text

@@ -0,0 +1,12 @@
+Just a [URL](/url/).
+
+[URL and title](/url/ "title").
+
+[URL and title](/url/  "title preceded by two spaces").
+
+[URL and title](/url/	"title preceded by a tab").
+
+[URL and title](/url/ "title has spaces afterward"  ).
+
+
+[Empty]().

+ 52 - 0
vendor/github.com/russross/blackfriday/testdata/Links, reference style.html

@@ -0,0 +1,52 @@
+<p>Foo <a href="/url/" title="Title">bar</a>.</p>
+
+<p>Foo <a href="/url/" title="Title">bar</a>.</p>
+
+<p>Foo <a href="/url/" title="Title">bar</a>.</p>
+
+<p>With <a href="/url/">embedded [brackets]</a>.</p>
+
+<p>Indented <a href="/url">once</a>.</p>
+
+<p>Indented <a href="/url">twice</a>.</p>
+
+<p>Indented <a href="/url">thrice</a>.</p>
+
+<p>Indented [four][] times.</p>
+
+<pre><code>[four]: /url
+</code></pre>
+
+<hr>
+
+<p><a href="foo">this</a> should work</p>
+
+<p>So should <a href="foo">this</a>.</p>
+
+<p>And <a href="foo">this</a>.</p>
+
+<p>And <a href="foo">this</a>.</p>
+
+<p>And <a href="foo">this</a>.</p>
+
+<p>But not [that] [].</p>
+
+<p>Nor [that][].</p>
+
+<p>Nor [that].</p>
+
+<p>[Something in brackets like <a href="foo">this</a> should work]</p>
+
+<p>[Same with <a href="foo">this</a>.]</p>
+
+<p>In this case, <a href="/somethingelse/">this</a> points to something else.</p>
+
+<p>Backslashing should suppress [this] and [this].</p>
+
+<hr>
+
+<p>Here's one where the <a href="/url/">link
+breaks</a> across lines.</p>
+
+<p>Here's another where the <a href="/url/">link
+breaks</a> across lines, but with a line-ending space.</p>

+ 71 - 0
vendor/github.com/russross/blackfriday/testdata/Links, reference style.text

@@ -0,0 +1,71 @@
+Foo [bar] [1].
+
+Foo [bar][1].
+
+Foo [bar]
+[1].
+
+[1]: /url/  "Title"
+
+
+With [embedded [brackets]] [b].
+
+
+Indented [once][].
+
+Indented [twice][].
+
+Indented [thrice][].
+
+Indented [four][] times.
+
+ [once]: /url
+
+  [twice]: /url
+
+   [thrice]: /url
+
+    [four]: /url
+
+
+[b]: /url/
+
+* * *
+
+[this] [this] should work
+
+So should [this][this].
+
+And [this] [].
+
+And [this][].
+
+And [this].
+
+But not [that] [].
+
+Nor [that][].
+
+Nor [that].
+
+[Something in brackets like [this][] should work]
+
+[Same with [this].]
+
+In this case, [this](/somethingelse/) points to something else.
+
+Backslashing should suppress \[this] and [this\].
+
+[this]: foo
+
+
+* * *
+
+Here's one where the [link
+breaks] across lines.
+
+Here's another where the [link 
+breaks] across lines, but with a line-ending space.
+
+
+[link breaks]: /url/

+ 9 - 0
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.html

@@ -0,0 +1,9 @@
+<p>This is the <a href="/simple">simple case</a>.</p>
+
+<p>This one has a <a href="/foo">line
+break</a>.</p>
+
+<p>This one has a <a href="/foo">line
+break</a> with a line-ending space.</p>
+
+<p><a href="/that">this</a> and the <a href="/other">other</a></p>

+ 20 - 0
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.text

@@ -0,0 +1,20 @@
+This is the [simple case].
+
+[simple case]: /simple
+
+
+
+This one has a [line
+break].
+
+This one has a [line 
+break] with a line-ending space.
+
+[line break]: /foo
+
+
+[this] [that] and the [other]
+
+[this]: /this
+[that]: /that
+[other]: /other

+ 3 - 0
vendor/github.com/russross/blackfriday/testdata/Literal quotes in titles.html

@@ -0,0 +1,3 @@
+<p>Foo <a href="/url/" title="Title with &quot;quotes&quot; inside">bar</a>.</p>
+
+<p>Foo <a href="/url/" title="Title with &quot;quotes&quot; inside">bar</a>.</p>

+ 7 - 0
vendor/github.com/russross/blackfriday/testdata/Literal quotes in titles.text

@@ -0,0 +1,7 @@
+Foo [bar][].
+
+Foo [bar](/url/ "Title with "quotes" inside").
+
+
+  [bar]: /url/ "Title with "quotes" inside"
+

+ 314 - 0
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.html

@@ -0,0 +1,314 @@
+<h1>Markdown: Basics</h1>
+
+<ul id="ProjectSubmenu">
+    <li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
+    <li><a class="selected" title="Markdown Basics">Basics</a></li>
+    <li><a href="/projects/markdown/syntax" title="Markdown Syntax Documentation">Syntax</a></li>
+    <li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
+    <li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
+</ul>
+
+<h2>Getting the Gist of Markdown's Formatting Syntax</h2>
+
+<p>This page offers a brief overview of what it's like to use Markdown.
+The <a href="/projects/markdown/syntax" title="Markdown Syntax">syntax page</a> provides complete, detailed documentation for
+every feature, but Markdown should be very easy to pick up simply by
+looking at a few examples of it in action. The examples on this page
+are written in a before/after style, showing example syntax and the
+HTML output produced by Markdown.</p>
+
+<p>It's also helpful to simply try Markdown out; the <a href="/projects/markdown/dingus" title="Markdown Dingus">Dingus</a> is a
+web application that allows you type your own Markdown-formatted text
+and translate it to XHTML.</p>
+
+<p><strong>Note:</strong> This document is itself written using Markdown; you
+can <a href="/projects/markdown/basics.text">see the source for it by adding '.text' to the URL</a>.</p>
+
+<h2>Paragraphs, Headers, Blockquotes</h2>
+
+<p>A paragraph is simply one or more consecutive lines of text, separated
+by one or more blank lines. (A blank line is any line that looks like a
+blank line -- a line containing nothing spaces or tabs is considered
+blank.) Normal paragraphs should not be intended with spaces or tabs.</p>
+
+<p>Markdown offers two styles of headers: <em>Setext</em> and <em>atx</em>.
+Setext-style headers for <code>&lt;h1&gt;</code> and <code>&lt;h2&gt;</code> are created by
+&quot;underlining&quot; with equal signs (<code>=</code>) and hyphens (<code>-</code>), respectively.
+To create an atx-style header, you put 1-6 hash marks (<code>#</code>) at the
+beginning of the line -- the number of hashes equals the resulting
+HTML header level.</p>
+
+<p>Blockquotes are indicated using email-style '<code>&gt;</code>' angle brackets.</p>
+
+<p>Markdown:</p>
+
+<pre><code>A First Level Header
+====================
+
+A Second Level Header
+---------------------
+
+Now is the time for all good men to come to
+the aid of their country. This is just a
+regular paragraph.
+
+The quick brown fox jumped over the lazy
+dog's back.
+
+### Header 3
+
+&gt; This is a blockquote.
+&gt; 
+&gt; This is the second paragraph in the blockquote.
+&gt;
+&gt; ## This is an H2 in a blockquote
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;h1&gt;A First Level Header&lt;/h1&gt;
+
+&lt;h2&gt;A Second Level Header&lt;/h2&gt;
+
+&lt;p&gt;Now is the time for all good men to come to
+the aid of their country. This is just a
+regular paragraph.&lt;/p&gt;
+
+&lt;p&gt;The quick brown fox jumped over the lazy
+dog's back.&lt;/p&gt;
+
+&lt;h3&gt;Header 3&lt;/h3&gt;
+
+&lt;blockquote&gt;
+    &lt;p&gt;This is a blockquote.&lt;/p&gt;
+
+    &lt;p&gt;This is the second paragraph in the blockquote.&lt;/p&gt;
+
+    &lt;h2&gt;This is an H2 in a blockquote&lt;/h2&gt;
+&lt;/blockquote&gt;
+</code></pre>
+
+<h3>Phrase Emphasis</h3>
+
+<p>Markdown uses asterisks and underscores to indicate spans of emphasis.</p>
+
+<p>Markdown:</p>
+
+<pre><code>Some of these words *are emphasized*.
+Some of these words _are emphasized also_.
+
+Use two asterisks for **strong emphasis**.
+Or, if you prefer, __use two underscores instead__.
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;p&gt;Some of these words &lt;em&gt;are emphasized&lt;/em&gt;.
+Some of these words &lt;em&gt;are emphasized also&lt;/em&gt;.&lt;/p&gt;
+
+&lt;p&gt;Use two asterisks for &lt;strong&gt;strong emphasis&lt;/strong&gt;.
+Or, if you prefer, &lt;strong&gt;use two underscores instead&lt;/strong&gt;.&lt;/p&gt;
+</code></pre>
+
+<h2>Lists</h2>
+
+<p>Unordered (bulleted) lists use asterisks, pluses, and hyphens (<code>*</code>,
+<code>+</code>, and <code>-</code>) as list markers. These three markers are
+interchangable; this:</p>
+
+<pre><code>*   Candy.
+*   Gum.
+*   Booze.
+</code></pre>
+
+<p>this:</p>
+
+<pre><code>+   Candy.
++   Gum.
++   Booze.
+</code></pre>
+
+<p>and this:</p>
+
+<pre><code>-   Candy.
+-   Gum.
+-   Booze.
+</code></pre>
+
+<p>all produce the same output:</p>
+
+<pre><code>&lt;ul&gt;
+&lt;li&gt;Candy.&lt;/li&gt;
+&lt;li&gt;Gum.&lt;/li&gt;
+&lt;li&gt;Booze.&lt;/li&gt;
+&lt;/ul&gt;
+</code></pre>
+
+<p>Ordered (numbered) lists use regular numbers, followed by periods, as
+list markers:</p>
+
+<pre><code>1.  Red
+2.  Green
+3.  Blue
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;ol&gt;
+&lt;li&gt;Red&lt;/li&gt;
+&lt;li&gt;Green&lt;/li&gt;
+&lt;li&gt;Blue&lt;/li&gt;
+&lt;/ol&gt;
+</code></pre>
+
+<p>If you put blank lines between items, you'll get <code>&lt;p&gt;</code> tags for the
+list item text. You can create multi-paragraph list items by indenting
+the paragraphs by 4 spaces or 1 tab:</p>
+
+<pre><code>*   A list item.
+
+    With multiple paragraphs.
+
+*   Another item in the list.
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;ul&gt;
+&lt;li&gt;&lt;p&gt;A list item.&lt;/p&gt;
+&lt;p&gt;With multiple paragraphs.&lt;/p&gt;&lt;/li&gt;
+&lt;li&gt;&lt;p&gt;Another item in the list.&lt;/p&gt;&lt;/li&gt;
+&lt;/ul&gt;
+</code></pre>
+
+<h3>Links</h3>
+
+<p>Markdown supports two styles for creating links: <em>inline</em> and
+<em>reference</em>. With both styles, you use square brackets to delimit the
+text you want to turn into a link.</p>
+
+<p>Inline-style links use parentheses immediately after the link text.
+For example:</p>
+
+<pre><code>This is an [example link](http://example.com/).
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;p&gt;This is an &lt;a href=&quot;http://example.com/&quot;&gt;
+example link&lt;/a&gt;.&lt;/p&gt;
+</code></pre>
+
+<p>Optionally, you may include a title attribute in the parentheses:</p>
+
+<pre><code>This is an [example link](http://example.com/ &quot;With a Title&quot;).
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;p&gt;This is an &lt;a href=&quot;http://example.com/&quot; title=&quot;With a Title&quot;&gt;
+example link&lt;/a&gt;.&lt;/p&gt;
+</code></pre>
+
+<p>Reference-style links allow you to refer to your links by names, which
+you define elsewhere in your document:</p>
+
+<pre><code>I get 10 times more traffic from [Google][1] than from
+[Yahoo][2] or [MSN][3].
+
+[1]: http://google.com/        &quot;Google&quot;
+[2]: http://search.yahoo.com/  &quot;Yahoo Search&quot;
+[3]: http://search.msn.com/    &quot;MSN Search&quot;
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;p&gt;I get 10 times more traffic from &lt;a href=&quot;http://google.com/&quot;
+title=&quot;Google&quot;&gt;Google&lt;/a&gt; than from &lt;a href=&quot;http://search.yahoo.com/&quot;
+title=&quot;Yahoo Search&quot;&gt;Yahoo&lt;/a&gt; or &lt;a href=&quot;http://search.msn.com/&quot;
+title=&quot;MSN Search&quot;&gt;MSN&lt;/a&gt;.&lt;/p&gt;
+</code></pre>
+
+<p>The title attribute is optional. Link names may contain letters,
+numbers and spaces, but are <em>not</em> case sensitive:</p>
+
+<pre><code>I start my morning with a cup of coffee and
+[The New York Times][NY Times].
+
+[ny times]: http://www.nytimes.com/
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;p&gt;I start my morning with a cup of coffee and
+&lt;a href=&quot;http://www.nytimes.com/&quot;&gt;The New York Times&lt;/a&gt;.&lt;/p&gt;
+</code></pre>
+
+<h3>Images</h3>
+
+<p>Image syntax is very much like link syntax.</p>
+
+<p>Inline (titles are optional):</p>
+
+<pre><code>![alt text](/path/to/img.jpg &quot;Title&quot;)
+</code></pre>
+
+<p>Reference-style:</p>
+
+<pre><code>![alt text][id]
+
+[id]: /path/to/img.jpg &quot;Title&quot;
+</code></pre>
+
+<p>Both of the above examples produce the same output:</p>
+
+<pre><code>&lt;img src=&quot;/path/to/img.jpg&quot; alt=&quot;alt text&quot; title=&quot;Title&quot; /&gt;
+</code></pre>
+
+<h3>Code</h3>
+
+<p>In a regular paragraph, you can create code span by wrapping text in
+backtick quotes. Any ampersands (<code>&amp;</code>) and angle brackets (<code>&lt;</code> or
+<code>&gt;</code>) will automatically be translated into HTML entities. This makes
+it easy to use Markdown to write about HTML example code:</p>
+
+<pre><code>I strongly recommend against using any `&lt;blink&gt;` tags.
+
+I wish SmartyPants used named entities like `&amp;mdash;`
+instead of decimal-encoded entites like `&amp;#8212;`.
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;p&gt;I strongly recommend against using any
+&lt;code&gt;&amp;lt;blink&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
+
+&lt;p&gt;I wish SmartyPants used named entities like
+&lt;code&gt;&amp;amp;mdash;&lt;/code&gt; instead of decimal-encoded
+entites like &lt;code&gt;&amp;amp;#8212;&lt;/code&gt;.&lt;/p&gt;
+</code></pre>
+
+<p>To specify an entire block of pre-formatted code, indent every line of
+the block by 4 spaces or 1 tab. Just like with code spans, <code>&amp;</code>, <code>&lt;</code>,
+and <code>&gt;</code> characters will be escaped automatically.</p>
+
+<p>Markdown:</p>
+
+<pre><code>If you want your page to validate under XHTML 1.0 Strict,
+you've got to put paragraph tags in your blockquotes:
+
+    &lt;blockquote&gt;
+        &lt;p&gt;For example.&lt;/p&gt;
+    &lt;/blockquote&gt;
+</code></pre>
+
+<p>Output:</p>
+
+<pre><code>&lt;p&gt;If you want your page to validate under XHTML 1.0 Strict,
+you've got to put paragraph tags in your blockquotes:&lt;/p&gt;
+
+&lt;pre&gt;&lt;code&gt;&amp;lt;blockquote&amp;gt;
+    &amp;lt;p&amp;gt;For example.&amp;lt;/p&amp;gt;
+&amp;lt;/blockquote&amp;gt;
+&lt;/code&gt;&lt;/pre&gt;
+</code></pre>

+ 306 - 0
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.text

@@ -0,0 +1,306 @@
+Markdown: Basics
+================
+
+<ul id="ProjectSubmenu">
+    <li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
+    <li><a class="selected" title="Markdown Basics">Basics</a></li>
+    <li><a href="/projects/markdown/syntax" title="Markdown Syntax Documentation">Syntax</a></li>
+    <li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
+    <li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
+</ul>
+
+
+Getting the Gist of Markdown's Formatting Syntax
+------------------------------------------------
+
+This page offers a brief overview of what it's like to use Markdown.
+The [syntax page] [s] provides complete, detailed documentation for
+every feature, but Markdown should be very easy to pick up simply by
+looking at a few examples of it in action. The examples on this page
+are written in a before/after style, showing example syntax and the
+HTML output produced by Markdown.
+
+It's also helpful to simply try Markdown out; the [Dingus] [d] is a
+web application that allows you type your own Markdown-formatted text
+and translate it to XHTML.
+
+**Note:** This document is itself written using Markdown; you
+can [see the source for it by adding '.text' to the URL] [src].
+
+  [s]: /projects/markdown/syntax  "Markdown Syntax"
+  [d]: /projects/markdown/dingus  "Markdown Dingus"
+  [src]: /projects/markdown/basics.text
+
+
+## Paragraphs, Headers, Blockquotes ##
+
+A paragraph is simply one or more consecutive lines of text, separated
+by one or more blank lines. (A blank line is any line that looks like a
+blank line -- a line containing nothing spaces or tabs is considered
+blank.) Normal paragraphs should not be intended with spaces or tabs.
+
+Markdown offers two styles of headers: *Setext* and *atx*.
+Setext-style headers for `<h1>` and `<h2>` are created by
+"underlining" with equal signs (`=`) and hyphens (`-`), respectively.
+To create an atx-style header, you put 1-6 hash marks (`#`) at the
+beginning of the line -- the number of hashes equals the resulting
+HTML header level.
+
+Blockquotes are indicated using email-style '`>`' angle brackets.
+
+Markdown:
+
+    A First Level Header
+    ====================
+    
+    A Second Level Header
+    ---------------------
+
+    Now is the time for all good men to come to
+    the aid of their country. This is just a
+    regular paragraph.
+
+    The quick brown fox jumped over the lazy
+    dog's back.
+    
+    ### Header 3
+
+    > This is a blockquote.
+    > 
+    > This is the second paragraph in the blockquote.
+    >
+    > ## This is an H2 in a blockquote
+
+
+Output:
+
+    <h1>A First Level Header</h1>
+    
+    <h2>A Second Level Header</h2>
+    
+    <p>Now is the time for all good men to come to
+    the aid of their country. This is just a
+    regular paragraph.</p>
+    
+    <p>The quick brown fox jumped over the lazy
+    dog's back.</p>
+    
+    <h3>Header 3</h3>
+    
+    <blockquote>
+        <p>This is a blockquote.</p>
+        
+        <p>This is the second paragraph in the blockquote.</p>
+        
+        <h2>This is an H2 in a blockquote</h2>
+    </blockquote>
+
+
+
+### Phrase Emphasis ###
+
+Markdown uses asterisks and underscores to indicate spans of emphasis.
+
+Markdown:
+
+    Some of these words *are emphasized*.
+    Some of these words _are emphasized also_.
+    
+    Use two asterisks for **strong emphasis**.
+    Or, if you prefer, __use two underscores instead__.
+
+Output:
+
+    <p>Some of these words <em>are emphasized</em>.
+    Some of these words <em>are emphasized also</em>.</p>
+    
+    <p>Use two asterisks for <strong>strong emphasis</strong>.
+    Or, if you prefer, <strong>use two underscores instead</strong>.</p>
+   
+
+
+## Lists ##
+
+Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`,
+`+`, and `-`) as list markers. These three markers are
+interchangable; this:
+
+    *   Candy.
+    *   Gum.
+    *   Booze.
+
+this:
+
+    +   Candy.
+    +   Gum.
+    +   Booze.
+
+and this:
+
+    -   Candy.
+    -   Gum.
+    -   Booze.
+
+all produce the same output:
+
+    <ul>
+    <li>Candy.</li>
+    <li>Gum.</li>
+    <li>Booze.</li>
+    </ul>
+
+Ordered (numbered) lists use regular numbers, followed by periods, as
+list markers:
+
+    1.  Red
+    2.  Green
+    3.  Blue
+
+Output:
+
+    <ol>
+    <li>Red</li>
+    <li>Green</li>
+    <li>Blue</li>
+    </ol>
+
+If you put blank lines between items, you'll get `<p>` tags for the
+list item text. You can create multi-paragraph list items by indenting
+the paragraphs by 4 spaces or 1 tab:
+
+    *   A list item.
+    
+        With multiple paragraphs.
+
+    *   Another item in the list.
+
+Output:
+
+    <ul>
+    <li><p>A list item.</p>
+    <p>With multiple paragraphs.</p></li>
+    <li><p>Another item in the list.</p></li>
+    </ul>
+    
+
+
+### Links ###
+
+Markdown supports two styles for creating links: *inline* and
+*reference*. With both styles, you use square brackets to delimit the
+text you want to turn into a link.
+
+Inline-style links use parentheses immediately after the link text.
+For example:
+
+    This is an [example link](http://example.com/).
+
+Output:
+
+    <p>This is an <a href="http://example.com/">
+    example link</a>.</p>
+
+Optionally, you may include a title attribute in the parentheses:
+
+    This is an [example link](http://example.com/ "With a Title").
+
+Output:
+
+    <p>This is an <a href="http://example.com/" title="With a Title">
+    example link</a>.</p>
+
+Reference-style links allow you to refer to your links by names, which
+you define elsewhere in your document:
+
+    I get 10 times more traffic from [Google][1] than from
+    [Yahoo][2] or [MSN][3].
+
+    [1]: http://google.com/        "Google"
+    [2]: http://search.yahoo.com/  "Yahoo Search"
+    [3]: http://search.msn.com/    "MSN Search"
+
+Output:
+
+    <p>I get 10 times more traffic from <a href="http://google.com/"
+    title="Google">Google</a> than from <a href="http://search.yahoo.com/"
+    title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/"
+    title="MSN Search">MSN</a>.</p>
+
+The title attribute is optional. Link names may contain letters,
+numbers and spaces, but are *not* case sensitive:
+
+    I start my morning with a cup of coffee and
+    [The New York Times][NY Times].
+
+    [ny times]: http://www.nytimes.com/
+
+Output:
+
+    <p>I start my morning with a cup of coffee and
+    <a href="http://www.nytimes.com/">The New York Times</a>.</p>
+
+
+### Images ###
+
+Image syntax is very much like link syntax.
+
+Inline (titles are optional):
+
+    ![alt text](/path/to/img.jpg "Title")
+
+Reference-style:
+
+    ![alt text][id]
+
+    [id]: /path/to/img.jpg "Title"
+
+Both of the above examples produce the same output:
+
+    <img src="/path/to/img.jpg" alt="alt text" title="Title" />
+
+
+
+### Code ###
+
+In a regular paragraph, you can create code span by wrapping text in
+backtick quotes. Any ampersands (`&`) and angle brackets (`<` or
+`>`) will automatically be translated into HTML entities. This makes
+it easy to use Markdown to write about HTML example code:
+
+    I strongly recommend against using any `<blink>` tags.
+
+    I wish SmartyPants used named entities like `&mdash;`
+    instead of decimal-encoded entites like `&#8212;`.
+
+Output:
+
+    <p>I strongly recommend against using any
+    <code>&lt;blink&gt;</code> tags.</p>
+    
+    <p>I wish SmartyPants used named entities like
+    <code>&amp;mdash;</code> instead of decimal-encoded
+    entites like <code>&amp;#8212;</code>.</p>
+
+
+To specify an entire block of pre-formatted code, indent every line of
+the block by 4 spaces or 1 tab. Just like with code spans, `&`, `<`,
+and `>` characters will be escaped automatically.
+
+Markdown:
+
+    If you want your page to validate under XHTML 1.0 Strict,
+    you've got to put paragraph tags in your blockquotes:
+
+        <blockquote>
+            <p>For example.</p>
+        </blockquote>
+
+Output:
+
+    <p>If you want your page to validate under XHTML 1.0 Strict,
+    you've got to put paragraph tags in your blockquotes:</p>
+    
+    <pre><code>&lt;blockquote&gt;
+        &lt;p&gt;For example.&lt;/p&gt;
+    &lt;/blockquote&gt;
+    </code></pre>

+ 946 - 0
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.html

@@ -0,0 +1,946 @@
+<h1>Markdown: Syntax</h1>
+
+<ul id="ProjectSubmenu">
+    <li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
+    <li><a href="/projects/markdown/basics" title="Markdown Basics">Basics</a></li>
+    <li><a class="selected" title="Markdown Syntax Documentation">Syntax</a></li>
+    <li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
+    <li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
+</ul>
+
+<ul>
+<li><a href="#overview">Overview</a>
+
+<ul>
+<li><a href="#philosophy">Philosophy</a></li>
+<li><a href="#html">Inline HTML</a></li>
+<li><a href="#autoescape">Automatic Escaping for Special Characters</a></li>
+</ul></li>
+<li><a href="#block">Block Elements</a>
+
+<ul>
+<li><a href="#p">Paragraphs and Line Breaks</a></li>
+<li><a href="#header">Headers</a></li>
+<li><a href="#blockquote">Blockquotes</a></li>
+<li><a href="#list">Lists</a></li>
+<li><a href="#precode">Code Blocks</a></li>
+<li><a href="#hr">Horizontal Rules</a></li>
+</ul></li>
+<li><a href="#span">Span Elements</a>
+
+<ul>
+<li><a href="#link">Links</a></li>
+<li><a href="#em">Emphasis</a></li>
+<li><a href="#code">Code</a></li>
+<li><a href="#img">Images</a></li>
+</ul></li>
+<li><a href="#misc">Miscellaneous</a>
+
+<ul>
+<li><a href="#backslash">Backslash Escapes</a></li>
+<li><a href="#autolink">Automatic Links</a></li>
+</ul></li>
+</ul>
+
+<p><strong>Note:</strong> This document is itself written using Markdown; you
+can <a href="/projects/markdown/syntax.text">see the source for it by adding '.text' to the URL</a>.</p>
+
+<hr>
+
+<h2 id="overview">Overview</h2>
+
+<h3 id="philosophy">Philosophy</h3>
+
+<p>Markdown is intended to be as easy-to-read and easy-to-write as is feasible.</p>
+
+<p>Readability, however, is emphasized above all else. A Markdown-formatted
+document should be publishable as-is, as plain text, without looking
+like it's been marked up with tags or formatting instructions. While
+Markdown's syntax has been influenced by several existing text-to-HTML
+filters -- including <a href="http://docutils.sourceforge.net/mirror/setext.html">Setext</a>, <a href="http://www.aaronsw.com/2002/atx/">atx</a>, <a href="http://textism.com/tools/textile/">Textile</a>, <a href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>,
+<a href="http://www.triptico.com/software/grutatxt.html">Grutatext</a>, and <a href="http://ettext.taint.org/doc/">EtText</a> -- the single biggest source of
+inspiration for Markdown's syntax is the format of plain text email.</p>
+
+<p>To this end, Markdown's syntax is comprised entirely of punctuation
+characters, which punctuation characters have been carefully chosen so
+as to look like what they mean. E.g., asterisks around a word actually
+look like *emphasis*. Markdown lists look like, well, lists. Even
+blockquotes look like quoted passages of text, assuming you've ever
+used email.</p>
+
+<h3 id="html">Inline HTML</h3>
+
+<p>Markdown's syntax is intended for one purpose: to be used as a
+format for <em>writing</em> for the web.</p>
+
+<p>Markdown is not a replacement for HTML, or even close to it. Its
+syntax is very small, corresponding only to a very small subset of
+HTML tags. The idea is <em>not</em> to create a syntax that makes it easier
+to insert HTML tags. In my opinion, HTML tags are already easy to
+insert. The idea for Markdown is to make it easy to read, write, and
+edit prose. HTML is a <em>publishing</em> format; Markdown is a <em>writing</em>
+format. Thus, Markdown's formatting syntax only addresses issues that
+can be conveyed in plain text.</p>
+
+<p>For any markup that is not covered by Markdown's syntax, you simply
+use HTML itself. There's no need to preface it or delimit it to
+indicate that you're switching from Markdown to HTML; you just use
+the tags.</p>
+
+<p>The only restrictions are that block-level HTML elements -- e.g. <code>&lt;div&gt;</code>,
+<code>&lt;table&gt;</code>, <code>&lt;pre&gt;</code>, <code>&lt;p&gt;</code>, etc. -- must be separated from surrounding
+content by blank lines, and the start and end tags of the block should
+not be indented with tabs or spaces. Markdown is smart enough not
+to add extra (unwanted) <code>&lt;p&gt;</code> tags around HTML block-level tags.</p>
+
+<p>For example, to add an HTML table to a Markdown article:</p>
+
+<pre><code>This is a regular paragraph.
+
+&lt;table&gt;
+    &lt;tr&gt;
+        &lt;td&gt;Foo&lt;/td&gt;
+    &lt;/tr&gt;
+&lt;/table&gt;
+
+This is another regular paragraph.
+</code></pre>
+
+<p>Note that Markdown formatting syntax is not processed within block-level
+HTML tags. E.g., you can't use Markdown-style <code>*emphasis*</code> inside an
+HTML block.</p>
+
+<p>Span-level HTML tags -- e.g. <code>&lt;span&gt;</code>, <code>&lt;cite&gt;</code>, or <code>&lt;del&gt;</code> -- can be
+used anywhere in a Markdown paragraph, list item, or header. If you
+want, you can even use HTML tags instead of Markdown formatting; e.g. if
+you'd prefer to use HTML <code>&lt;a&gt;</code> or <code>&lt;img&gt;</code> tags instead of Markdown's
+link or image syntax, go right ahead.</p>
+
+<p>Unlike block-level HTML tags, Markdown syntax <em>is</em> processed within
+span-level tags.</p>
+
+<h3 id="autoescape">Automatic Escaping for Special Characters</h3>
+
+<p>In HTML, there are two characters that demand special treatment: <code>&lt;</code>
+and <code>&amp;</code>. Left angle brackets are used to start tags; ampersands are
+used to denote HTML entities. If you want to use them as literal
+characters, you must escape them as entities, e.g. <code>&amp;lt;</code>, and
+<code>&amp;amp;</code>.</p>
+
+<p>Ampersands in particular are bedeviling for web writers. If you want to
+write about 'AT&amp;T', you need to write '<code>AT&amp;amp;T</code>'. You even need to
+escape ampersands within URLs. Thus, if you want to link to:</p>
+
+<pre><code>http://images.google.com/images?num=30&amp;q=larry+bird
+</code></pre>
+
+<p>you need to encode the URL as:</p>
+
+<pre><code>http://images.google.com/images?num=30&amp;amp;q=larry+bird
+</code></pre>
+
+<p>in your anchor tag <code>href</code> attribute. Needless to say, this is easy to
+forget, and is probably the single most common source of HTML validation
+errors in otherwise well-marked-up web sites.</p>
+
+<p>Markdown allows you to use these characters naturally, taking care of
+all the necessary escaping for you. If you use an ampersand as part of
+an HTML entity, it remains unchanged; otherwise it will be translated
+into <code>&amp;amp;</code>.</p>
+
+<p>So, if you want to include a copyright symbol in your article, you can write:</p>
+
+<pre><code>&amp;copy;
+</code></pre>
+
+<p>and Markdown will leave it alone. But if you write:</p>
+
+<pre><code>AT&amp;T
+</code></pre>
+
+<p>Markdown will translate it to:</p>
+
+<pre><code>AT&amp;amp;T
+</code></pre>
+
+<p>Similarly, because Markdown supports <a href="#html">inline HTML</a>, if you use
+angle brackets as delimiters for HTML tags, Markdown will treat them as
+such. But if you write:</p>
+
+<pre><code>4 &lt; 5
+</code></pre>
+
+<p>Markdown will translate it to:</p>
+
+<pre><code>4 &amp;lt; 5
+</code></pre>
+
+<p>However, inside Markdown code spans and blocks, angle brackets and
+ampersands are <em>always</em> encoded automatically. This makes it easy to use
+Markdown to write about HTML code. (As opposed to raw HTML, which is a
+terrible format for writing about HTML syntax, because every single <code>&lt;</code>
+and <code>&amp;</code> in your example code needs to be escaped.)</p>
+
+<hr>
+
+<h2 id="block">Block Elements</h2>
+
+<h3 id="p">Paragraphs and Line Breaks</h3>
+
+<p>A paragraph is simply one or more consecutive lines of text, separated
+by one or more blank lines. (A blank line is any line that looks like a
+blank line -- a line containing nothing but spaces or tabs is considered
+blank.) Normal paragraphs should not be intended with spaces or tabs.</p>
+
+<p>The implication of the &quot;one or more consecutive lines of text&quot; rule is
+that Markdown supports &quot;hard-wrapped&quot; text paragraphs. This differs
+significantly from most other text-to-HTML formatters (including Movable
+Type's &quot;Convert Line Breaks&quot; option) which translate every line break
+character in a paragraph into a <code>&lt;br /&gt;</code> tag.</p>
+
+<p>When you <em>do</em> want to insert a <code>&lt;br /&gt;</code> break tag using Markdown, you
+end a line with two or more spaces, then type return.</p>
+
+<p>Yes, this takes a tad more effort to create a <code>&lt;br /&gt;</code>, but a simplistic
+&quot;every line break is a <code>&lt;br /&gt;</code>&quot; rule wouldn't work for Markdown.
+Markdown's email-style <a href="#blockquote">blockquoting</a> and multi-paragraph <a href="#list">list items</a>
+work best -- and look better -- when you format them with hard breaks.</p>
+
+<h3 id="header">Headers</h3>
+
+<p>Markdown supports two styles of headers, <a href="http://docutils.sourceforge.net/mirror/setext.html">Setext</a> and <a href="http://www.aaronsw.com/2002/atx/">atx</a>.</p>
+
+<p>Setext-style headers are &quot;underlined&quot; using equal signs (for first-level
+headers) and dashes (for second-level headers). For example:</p>
+
+<pre><code>This is an H1
+=============
+
+This is an H2
+-------------
+</code></pre>
+
+<p>Any number of underlining <code>=</code>'s or <code>-</code>'s will work.</p>
+
+<p>Atx-style headers use 1-6 hash characters at the start of the line,
+corresponding to header levels 1-6. For example:</p>
+
+<pre><code># This is an H1
+
+## This is an H2
+
+###### This is an H6
+</code></pre>
+
+<p>Optionally, you may &quot;close&quot; atx-style headers. This is purely
+cosmetic -- you can use this if you think it looks better. The
+closing hashes don't even need to match the number of hashes
+used to open the header. (The number of opening hashes
+determines the header level.) :</p>
+
+<pre><code># This is an H1 #
+
+## This is an H2 ##
+
+### This is an H3 ######
+</code></pre>
+
+<h3 id="blockquote">Blockquotes</h3>
+
+<p>Markdown uses email-style <code>&gt;</code> characters for blockquoting. If you're
+familiar with quoting passages of text in an email message, then you
+know how to create a blockquote in Markdown. It looks best if you hard
+wrap the text and put a <code>&gt;</code> before every line:</p>
+
+<pre><code>&gt; This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+&gt; consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+&gt; Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+&gt; 
+&gt; Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+&gt; id sem consectetuer libero luctus adipiscing.
+</code></pre>
+
+<p>Markdown allows you to be lazy and only put the <code>&gt;</code> before the first
+line of a hard-wrapped paragraph:</p>
+
+<pre><code>&gt; This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+
+&gt; Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+id sem consectetuer libero luctus adipiscing.
+</code></pre>
+
+<p>Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
+adding additional levels of <code>&gt;</code>:</p>
+
+<pre><code>&gt; This is the first level of quoting.
+&gt;
+&gt; &gt; This is nested blockquote.
+&gt;
+&gt; Back to the first level.
+</code></pre>
+
+<p>Blockquotes can contain other Markdown elements, including headers, lists,
+and code blocks:</p>
+
+<pre><code>&gt; ## This is a header.
+&gt; 
+&gt; 1.   This is the first list item.
+&gt; 2.   This is the second list item.
+&gt; 
+&gt; Here's some example code:
+&gt; 
+&gt;     return shell_exec(&quot;echo $input | $markdown_script&quot;);
+</code></pre>
+
+<p>Any decent text editor should make email-style quoting easy. For
+example, with BBEdit, you can make a selection and choose Increase
+Quote Level from the Text menu.</p>
+
+<h3 id="list">Lists</h3>
+
+<p>Markdown supports ordered (numbered) and unordered (bulleted) lists.</p>
+
+<p>Unordered lists use asterisks, pluses, and hyphens -- interchangably
+-- as list markers:</p>
+
+<pre><code>*   Red
+*   Green
+*   Blue
+</code></pre>
+
+<p>is equivalent to:</p>
+
+<pre><code>+   Red
++   Green
++   Blue
+</code></pre>
+
+<p>and:</p>
+
+<pre><code>-   Red
+-   Green
+-   Blue
+</code></pre>
+
+<p>Ordered lists use numbers followed by periods:</p>
+
+<pre><code>1.  Bird
+2.  McHale
+3.  Parish
+</code></pre>
+
+<p>It's important to note that the actual numbers you use to mark the
+list have no effect on the HTML output Markdown produces. The HTML
+Markdown produces from the above list is:</p>
+
+<pre><code>&lt;ol&gt;
+&lt;li&gt;Bird&lt;/li&gt;
+&lt;li&gt;McHale&lt;/li&gt;
+&lt;li&gt;Parish&lt;/li&gt;
+&lt;/ol&gt;
+</code></pre>
+
+<p>If you instead wrote the list in Markdown like this:</p>
+
+<pre><code>1.  Bird
+1.  McHale
+1.  Parish
+</code></pre>
+
+<p>or even:</p>
+
+<pre><code>3. Bird
+1. McHale
+8. Parish
+</code></pre>
+
+<p>you'd get the exact same HTML output. The point is, if you want to,
+you can use ordinal numbers in your ordered Markdown lists, so that
+the numbers in your source match the numbers in your published HTML.
+But if you want to be lazy, you don't have to.</p>
+
+<p>If you do use lazy list numbering, however, you should still start the
+list with the number 1. At some point in the future, Markdown may support
+starting ordered lists at an arbitrary number.</p>
+
+<p>List markers typically start at the left margin, but may be indented by
+up to three spaces. List markers must be followed by one or more spaces
+or a tab.</p>
+
+<p>To make lists look nice, you can wrap items with hanging indents:</p>
+
+<pre><code>*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+    Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+    viverra nec, fringilla in, laoreet vitae, risus.
+*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+    Suspendisse id sem consectetuer libero luctus adipiscing.
+</code></pre>
+
+<p>But if you want to be lazy, you don't have to:</p>
+
+<pre><code>*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+viverra nec, fringilla in, laoreet vitae, risus.
+*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+Suspendisse id sem consectetuer libero luctus adipiscing.
+</code></pre>
+
+<p>If list items are separated by blank lines, Markdown will wrap the
+items in <code>&lt;p&gt;</code> tags in the HTML output. For example, this input:</p>
+
+<pre><code>*   Bird
+*   Magic
+</code></pre>
+
+<p>will turn into:</p>
+
+<pre><code>&lt;ul&gt;
+&lt;li&gt;Bird&lt;/li&gt;
+&lt;li&gt;Magic&lt;/li&gt;
+&lt;/ul&gt;
+</code></pre>
+
+<p>But this:</p>
+
+<pre><code>*   Bird
+
+*   Magic
+</code></pre>
+
+<p>will turn into:</p>
+
+<pre><code>&lt;ul&gt;
+&lt;li&gt;&lt;p&gt;Bird&lt;/p&gt;&lt;/li&gt;
+&lt;li&gt;&lt;p&gt;Magic&lt;/p&gt;&lt;/li&gt;
+&lt;/ul&gt;
+</code></pre>
+
+<p>List items may consist of multiple paragraphs. Each subsequent
+paragraph in a list item must be intended by either 4 spaces
+or one tab:</p>
+
+<pre><code>1.  This is a list item with two paragraphs. Lorem ipsum dolor
+    sit amet, consectetuer adipiscing elit. Aliquam hendrerit
+    mi posuere lectus.
+
+    Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+    vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
+    sit amet velit.
+
+2.  Suspendisse id sem consectetuer libero luctus adipiscing.
+</code></pre>
+
+<p>It looks nice if you indent every line of the subsequent
+paragraphs, but here again, Markdown will allow you to be
+lazy:</p>
+
+<pre><code>*   This is a list item with two paragraphs.
+
+    This is the second paragraph in the list item. You're
+only required to indent the first line. Lorem ipsum dolor
+sit amet, consectetuer adipiscing elit.
+
+*   Another item in the same list.
+</code></pre>
+
+<p>To put a blockquote within a list item, the blockquote's <code>&gt;</code>
+delimiters need to be indented:</p>
+
+<pre><code>*   A list item with a blockquote:
+
+    &gt; This is a blockquote
+    &gt; inside a list item.
+</code></pre>
+
+<p>To put a code block within a list item, the code block needs
+to be indented <em>twice</em> -- 8 spaces or two tabs:</p>
+
+<pre><code>*   A list item with a code block:
+
+        &lt;code goes here&gt;
+</code></pre>
+
+<p>It's worth noting that it's possible to trigger an ordered list by
+accident, by writing something like this:</p>
+
+<pre><code>1986. What a great season.
+</code></pre>
+
+<p>In other words, a <em>number-period-space</em> sequence at the beginning of a
+line. To avoid this, you can backslash-escape the period:</p>
+
+<pre><code>1986\. What a great season.
+</code></pre>
+
+<h3 id="precode">Code Blocks</h3>
+
+<p>Pre-formatted code blocks are used for writing about programming or
+markup source code. Rather than forming normal paragraphs, the lines
+of a code block are interpreted literally. Markdown wraps a code block
+in both <code>&lt;pre&gt;</code> and <code>&lt;code&gt;</code> tags.</p>
+
+<p>To produce a code block in Markdown, simply indent every line of the
+block by at least 4 spaces or 1 tab. For example, given this input:</p>
+
+<pre><code>This is a normal paragraph:
+
+    This is a code block.
+</code></pre>
+
+<p>Markdown will generate:</p>
+
+<pre><code>&lt;p&gt;This is a normal paragraph:&lt;/p&gt;
+
+&lt;pre&gt;&lt;code&gt;This is a code block.
+&lt;/code&gt;&lt;/pre&gt;
+</code></pre>
+
+<p>One level of indentation -- 4 spaces or 1 tab -- is removed from each
+line of the code block. For example, this:</p>
+
+<pre><code>Here is an example of AppleScript:
+
+    tell application &quot;Foo&quot;
+        beep
+    end tell
+</code></pre>
+
+<p>will turn into:</p>
+
+<pre><code>&lt;p&gt;Here is an example of AppleScript:&lt;/p&gt;
+
+&lt;pre&gt;&lt;code&gt;tell application &quot;Foo&quot;
+    beep
+end tell
+&lt;/code&gt;&lt;/pre&gt;
+</code></pre>
+
+<p>A code block continues until it reaches a line that is not indented
+(or the end of the article).</p>
+
+<p>Within a code block, ampersands (<code>&amp;</code>) and angle brackets (<code>&lt;</code> and <code>&gt;</code>)
+are automatically converted into HTML entities. This makes it very
+easy to include example HTML source code using Markdown -- just paste
+it and indent it, and Markdown will handle the hassle of encoding the
+ampersands and angle brackets. For example, this:</p>
+
+<pre><code>    &lt;div class=&quot;footer&quot;&gt;
+        &amp;copy; 2004 Foo Corporation
+    &lt;/div&gt;
+</code></pre>
+
+<p>will turn into:</p>
+
+<pre><code>&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;footer&quot;&amp;gt;
+    &amp;amp;copy; 2004 Foo Corporation
+&amp;lt;/div&amp;gt;
+&lt;/code&gt;&lt;/pre&gt;
+</code></pre>
+
+<p>Regular Markdown syntax is not processed within code blocks. E.g.,
+asterisks are just literal asterisks within a code block. This means
+it's also easy to use Markdown to write about Markdown's own syntax.</p>
+
+<h3 id="hr">Horizontal Rules</h3>
+
+<p>You can produce a horizontal rule tag (<code>&lt;hr /&gt;</code>) by placing three or
+more hyphens, asterisks, or underscores on a line by themselves. If you
+wish, you may use spaces between the hyphens or asterisks. Each of the
+following lines will produce a horizontal rule:</p>
+
+<pre><code>* * *
+
+***
+
+*****
+
+- - -
+
+---------------------------------------
+
+_ _ _
+</code></pre>
+
+<hr>
+
+<h2 id="span">Span Elements</h2>
+
+<h3 id="link">Links</h3>
+
+<p>Markdown supports two style of links: <em>inline</em> and <em>reference</em>.</p>
+
+<p>In both styles, the link text is delimited by [square brackets].</p>
+
+<p>To create an inline link, use a set of regular parentheses immediately
+after the link text's closing square bracket. Inside the parentheses,
+put the URL where you want the link to point, along with an <em>optional</em>
+title for the link, surrounded in quotes. For example:</p>
+
+<pre><code>This is [an example](http://example.com/ &quot;Title&quot;) inline link.
+
+[This link](http://example.net/) has no title attribute.
+</code></pre>
+
+<p>Will produce:</p>
+
+<pre><code>&lt;p&gt;This is &lt;a href=&quot;http://example.com/&quot; title=&quot;Title&quot;&gt;
+an example&lt;/a&gt; inline link.&lt;/p&gt;
+
+&lt;p&gt;&lt;a href=&quot;http://example.net/&quot;&gt;This link&lt;/a&gt; has no
+title attribute.&lt;/p&gt;
+</code></pre>
+
+<p>If you're referring to a local resource on the same server, you can
+use relative paths:</p>
+
+<pre><code>See my [About](/about/) page for details.
+</code></pre>
+
+<p>Reference-style links use a second set of square brackets, inside
+which you place a label of your choosing to identify the link:</p>
+
+<pre><code>This is [an example][id] reference-style link.
+</code></pre>
+
+<p>You can optionally use a space to separate the sets of brackets:</p>
+
+<pre><code>This is [an example] [id] reference-style link.
+</code></pre>
+
+<p>Then, anywhere in the document, you define your link label like this,
+on a line by itself:</p>
+
+<pre><code>[id]: http://example.com/  &quot;Optional Title Here&quot;
+</code></pre>
+
+<p>That is:</p>
+
+<ul>
+<li>Square brackets containing the link identifier (optionally
+indented from the left margin using up to three spaces);</li>
+<li>followed by a colon;</li>
+<li>followed by one or more spaces (or tabs);</li>
+<li>followed by the URL for the link;</li>
+<li>optionally followed by a title attribute for the link, enclosed
+in double or single quotes.</li>
+</ul>
+
+<p>The link URL may, optionally, be surrounded by angle brackets:</p>
+
+<pre><code>[id]: &lt;http://example.com/&gt;  &quot;Optional Title Here&quot;
+</code></pre>
+
+<p>You can put the title attribute on the next line and use extra spaces
+or tabs for padding, which tends to look better with longer URLs:</p>
+
+<pre><code>[id]: http://example.com/longish/path/to/resource/here
+    &quot;Optional Title Here&quot;
+</code></pre>
+
+<p>Link definitions are only used for creating links during Markdown
+processing, and are stripped from your document in the HTML output.</p>
+
+<p>Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are <em>not</em> case sensitive. E.g. these two links:</p>
+
+<pre><code>[link text][a]
+[link text][A]
+</code></pre>
+
+<p>are equivalent.</p>
+
+<p>The <em>implicit link name</em> shortcut allows you to omit the name of the
+link, in which case the link text itself is used as the name.
+Just use an empty set of square brackets -- e.g., to link the word
+&quot;Google&quot; to the google.com web site, you could simply write:</p>
+
+<pre><code>[Google][]
+</code></pre>
+
+<p>And then define the link:</p>
+
+<pre><code>[Google]: http://google.com/
+</code></pre>
+
+<p>Because link names may contain spaces, this shortcut even works for
+multiple words in the link text:</p>
+
+<pre><code>Visit [Daring Fireball][] for more information.
+</code></pre>
+
+<p>And then define the link:</p>
+
+<pre><code>[Daring Fireball]: http://daringfireball.net/
+</code></pre>
+
+<p>Link definitions can be placed anywhere in your Markdown document. I
+tend to put them immediately after each paragraph in which they're
+used, but if you want, you can put them all at the end of your
+document, sort of like footnotes.</p>
+
+<p>Here's an example of reference links in action:</p>
+
+<pre><code>I get 10 times more traffic from [Google] [1] than from
+[Yahoo] [2] or [MSN] [3].
+
+  [1]: http://google.com/        &quot;Google&quot;
+  [2]: http://search.yahoo.com/  &quot;Yahoo Search&quot;
+  [3]: http://search.msn.com/    &quot;MSN Search&quot;
+</code></pre>
+
+<p>Using the implicit link name shortcut, you could instead write:</p>
+
+<pre><code>I get 10 times more traffic from [Google][] than from
+[Yahoo][] or [MSN][].
+
+  [google]: http://google.com/        &quot;Google&quot;
+  [yahoo]:  http://search.yahoo.com/  &quot;Yahoo Search&quot;
+  [msn]:    http://search.msn.com/    &quot;MSN Search&quot;
+</code></pre>
+
+<p>Both of the above examples will produce the following HTML output:</p>
+
+<pre><code>&lt;p&gt;I get 10 times more traffic from &lt;a href=&quot;http://google.com/&quot;
+title=&quot;Google&quot;&gt;Google&lt;/a&gt; than from
+&lt;a href=&quot;http://search.yahoo.com/&quot; title=&quot;Yahoo Search&quot;&gt;Yahoo&lt;/a&gt;
+or &lt;a href=&quot;http://search.msn.com/&quot; title=&quot;MSN Search&quot;&gt;MSN&lt;/a&gt;.&lt;/p&gt;
+</code></pre>
+
+<p>For comparison, here is the same paragraph written using
+Markdown's inline link style:</p>
+
+<pre><code>I get 10 times more traffic from [Google](http://google.com/ &quot;Google&quot;)
+than from [Yahoo](http://search.yahoo.com/ &quot;Yahoo Search&quot;) or
+[MSN](http://search.msn.com/ &quot;MSN Search&quot;).
+</code></pre>
+
+<p>The point of reference-style links is not that they're easier to
+write. The point is that with reference-style links, your document
+source is vastly more readable. Compare the above examples: using
+reference-style links, the paragraph itself is only 81 characters
+long; with inline-style links, it's 176 characters; and as raw HTML,
+it's 234 characters. In the raw HTML, there's more markup than there
+is text.</p>
+
+<p>With Markdown's reference-style links, a source document much more
+closely resembles the final output, as rendered in a browser. By
+allowing you to move the markup-related metadata out of the paragraph,
+you can add links without interrupting the narrative flow of your
+prose.</p>
+
+<h3 id="em">Emphasis</h3>
+
+<p>Markdown treats asterisks (<code>*</code>) and underscores (<code>_</code>) as indicators of
+emphasis. Text wrapped with one <code>*</code> or <code>_</code> will be wrapped with an
+HTML <code>&lt;em&gt;</code> tag; double <code>*</code>'s or <code>_</code>'s will be wrapped with an HTML
+<code>&lt;strong&gt;</code> tag. E.g., this input:</p>
+
+<pre><code>*single asterisks*
+
+_single underscores_
+
+**double asterisks**
+
+__double underscores__
+</code></pre>
+
+<p>will produce:</p>
+
+<pre><code>&lt;em&gt;single asterisks&lt;/em&gt;
+
+&lt;em&gt;single underscores&lt;/em&gt;
+
+&lt;strong&gt;double asterisks&lt;/strong&gt;
+
+&lt;strong&gt;double underscores&lt;/strong&gt;
+</code></pre>
+
+<p>You can use whichever style you prefer; the lone restriction is that
+the same character must be used to open and close an emphasis span.</p>
+
+<p>Emphasis can be used in the middle of a word:</p>
+
+<pre><code>un*fucking*believable
+</code></pre>
+
+<p>But if you surround an <code>*</code> or <code>_</code> with spaces, it'll be treated as a
+literal asterisk or underscore.</p>
+
+<p>To produce a literal asterisk or underscore at a position where it
+would otherwise be used as an emphasis delimiter, you can backslash
+escape it:</p>
+
+<pre><code>\*this text is surrounded by literal asterisks\*
+</code></pre>
+
+<h3 id="code">Code</h3>
+
+<p>To indicate a span of code, wrap it with backtick quotes (<code>`</code>).
+Unlike a pre-formatted code block, a code span indicates code within a
+normal paragraph. For example:</p>
+
+<pre><code>Use the `printf()` function.
+</code></pre>
+
+<p>will produce:</p>
+
+<pre><code>&lt;p&gt;Use the &lt;code&gt;printf()&lt;/code&gt; function.&lt;/p&gt;
+</code></pre>
+
+<p>To include a literal backtick character within a code span, you can use
+multiple backticks as the opening and closing delimiters:</p>
+
+<pre><code>``There is a literal backtick (`) here.``
+</code></pre>
+
+<p>which will produce this:</p>
+
+<pre><code>&lt;p&gt;&lt;code&gt;There is a literal backtick (`) here.&lt;/code&gt;&lt;/p&gt;
+</code></pre>
+
+<p>The backtick delimiters surrounding a code span may include spaces --
+one after the opening, one before the closing. This allows you to place
+literal backtick characters at the beginning or end of a code span:</p>
+
+<pre><code>A single backtick in a code span: `` ` ``
+
+A backtick-delimited string in a code span: `` `foo` ``
+</code></pre>
+
+<p>will produce:</p>
+
+<pre><code>&lt;p&gt;A single backtick in a code span: &lt;code&gt;`&lt;/code&gt;&lt;/p&gt;
+
+&lt;p&gt;A backtick-delimited string in a code span: &lt;code&gt;`foo`&lt;/code&gt;&lt;/p&gt;
+</code></pre>
+
+<p>With a code span, ampersands and angle brackets are encoded as HTML
+entities automatically, which makes it easy to include example HTML
+tags. Markdown will turn this:</p>
+
+<pre><code>Please don't use any `&lt;blink&gt;` tags.
+</code></pre>
+
+<p>into:</p>
+
+<pre><code>&lt;p&gt;Please don't use any &lt;code&gt;&amp;lt;blink&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
+</code></pre>
+
+<p>You can write this:</p>
+
+<pre><code>`&amp;#8212;` is the decimal-encoded equivalent of `&amp;mdash;`.
+</code></pre>
+
+<p>to produce:</p>
+
+<pre><code>&lt;p&gt;&lt;code&gt;&amp;amp;#8212;&lt;/code&gt; is the decimal-encoded
+equivalent of &lt;code&gt;&amp;amp;mdash;&lt;/code&gt;.&lt;/p&gt;
+</code></pre>
+
+<h3 id="img">Images</h3>
+
+<p>Admittedly, it's fairly difficult to devise a &quot;natural&quot; syntax for
+placing images into a plain text document format.</p>
+
+<p>Markdown uses an image syntax that is intended to resemble the syntax
+for links, allowing for two styles: <em>inline</em> and <em>reference</em>.</p>
+
+<p>Inline image syntax looks like this:</p>
+
+<pre><code>![Alt text](/path/to/img.jpg)
+
+![Alt text](/path/to/img.jpg &quot;Optional title&quot;)
+</code></pre>
+
+<p>That is:</p>
+
+<ul>
+<li>An exclamation mark: <code>!</code>;</li>
+<li>followed by a set of square brackets, containing the <code>alt</code>
+attribute text for the image;</li>
+<li>followed by a set of parentheses, containing the URL or path to
+the image, and an optional <code>title</code> attribute enclosed in double
+or single quotes.</li>
+</ul>
+
+<p>Reference-style image syntax looks like this:</p>
+
+<pre><code>![Alt text][id]
+</code></pre>
+
+<p>Where &quot;id&quot; is the name of a defined image reference. Image references
+are defined using syntax identical to link references:</p>
+
+<pre><code>[id]: url/to/image  &quot;Optional title attribute&quot;
+</code></pre>
+
+<p>As of this writing, Markdown has no syntax for specifying the
+dimensions of an image; if this is important to you, you can simply
+use regular HTML <code>&lt;img&gt;</code> tags.</p>
+
+<hr>
+
+<h2 id="misc">Miscellaneous</h2>
+
+<h3 id="autolink">Automatic Links</h3>
+
+<p>Markdown supports a shortcut style for creating &quot;automatic&quot; links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:</p>
+
+<pre><code>&lt;http://example.com/&gt;
+</code></pre>
+
+<p>Markdown will turn this into:</p>
+
+<pre><code>&lt;a href=&quot;http://example.com/&quot;&gt;http://example.com/&lt;/a&gt;
+</code></pre>
+
+<p>Automatic links for email addresses work similarly, except that
+Markdown will also perform a bit of randomized decimal and hex
+entity-encoding to help obscure your address from address-harvesting
+spambots. For example, Markdown will turn this:</p>
+
+<pre><code>&lt;[email protected]&gt;
+</code></pre>
+
+<p>into something like this:</p>
+
+<pre><code>&lt;a href=&quot;&amp;#x6D;&amp;#x61;i&amp;#x6C;&amp;#x74;&amp;#x6F;:&amp;#x61;&amp;#x64;&amp;#x64;&amp;#x72;&amp;#x65;
+&amp;#115;&amp;#115;&amp;#64;&amp;#101;&amp;#120;&amp;#x61;&amp;#109;&amp;#x70;&amp;#x6C;e&amp;#x2E;&amp;#99;&amp;#111;
+&amp;#109;&quot;&gt;&amp;#x61;&amp;#x64;&amp;#x64;&amp;#x72;&amp;#x65;&amp;#115;&amp;#115;&amp;#64;&amp;#101;&amp;#120;&amp;#x61;
+&amp;#109;&amp;#x70;&amp;#x6C;e&amp;#x2E;&amp;#99;&amp;#111;&amp;#109;&lt;/a&gt;
+</code></pre>
+
+<p>which will render in a browser as a clickable link to &quot;[email protected]&quot;.</p>
+
+<p>(This sort of entity-encoding trick will indeed fool many, if not
+most, address-harvesting bots, but it definitely won't fool all of
+them. It's better than nothing, but an address published in this way
+will probably eventually start receiving spam.)</p>
+
+<h3 id="backslash">Backslash Escapes</h3>
+
+<p>Markdown allows you to use backslash escapes to generate literal
+characters which would otherwise have special meaning in Markdown's
+formatting syntax. For example, if you wanted to surround a word with
+literal asterisks (instead of an HTML <code>&lt;em&gt;</code> tag), you can backslashes
+before the asterisks, like this:</p>
+
+<pre><code>\*literal asterisks\*
+</code></pre>
+
+<p>Markdown provides backslash escapes for the following characters:</p>
+
+<pre><code>\   backslash
+`   backtick
+*   asterisk
+_   underscore
+{}  curly braces
+[]  square brackets
+()  parentheses
+#   hash mark
++   plus sign
+-   minus sign (hyphen)
+.   dot
+!   exclamation mark
+</code></pre>

+ 888 - 0
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.text

@@ -0,0 +1,888 @@
+Markdown: Syntax
+================
+
+<ul id="ProjectSubmenu">
+    <li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
+    <li><a href="/projects/markdown/basics" title="Markdown Basics">Basics</a></li>
+    <li><a class="selected" title="Markdown Syntax Documentation">Syntax</a></li>
+    <li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
+    <li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
+</ul>
+
+
+*   [Overview](#overview)
+    *   [Philosophy](#philosophy)
+    *   [Inline HTML](#html)
+    *   [Automatic Escaping for Special Characters](#autoescape)
+*   [Block Elements](#block)
+    *   [Paragraphs and Line Breaks](#p)
+    *   [Headers](#header)
+    *   [Blockquotes](#blockquote)
+    *   [Lists](#list)
+    *   [Code Blocks](#precode)
+    *   [Horizontal Rules](#hr)
+*   [Span Elements](#span)
+    *   [Links](#link)
+    *   [Emphasis](#em)
+    *   [Code](#code)
+    *   [Images](#img)
+*   [Miscellaneous](#misc)
+    *   [Backslash Escapes](#backslash)
+    *   [Automatic Links](#autolink)
+
+
+**Note:** This document is itself written using Markdown; you
+can [see the source for it by adding '.text' to the URL][src].
+
+  [src]: /projects/markdown/syntax.text
+
+* * *
+
+<h2 id="overview">Overview</h2>
+
+<h3 id="philosophy">Philosophy</h3>
+
+Markdown is intended to be as easy-to-read and easy-to-write as is feasible.
+
+Readability, however, is emphasized above all else. A Markdown-formatted
+document should be publishable as-is, as plain text, without looking
+like it's been marked up with tags or formatting instructions. While
+Markdown's syntax has been influenced by several existing text-to-HTML
+filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4],
+[Grutatext] [5], and [EtText] [6] -- the single biggest source of
+inspiration for Markdown's syntax is the format of plain text email.
+
+  [1]: http://docutils.sourceforge.net/mirror/setext.html
+  [2]: http://www.aaronsw.com/2002/atx/
+  [3]: http://textism.com/tools/textile/
+  [4]: http://docutils.sourceforge.net/rst.html
+  [5]: http://www.triptico.com/software/grutatxt.html
+  [6]: http://ettext.taint.org/doc/
+
+To this end, Markdown's syntax is comprised entirely of punctuation
+characters, which punctuation characters have been carefully chosen so
+as to look like what they mean. E.g., asterisks around a word actually
+look like \*emphasis\*. Markdown lists look like, well, lists. Even
+blockquotes look like quoted passages of text, assuming you've ever
+used email.
+
+
+
+<h3 id="html">Inline HTML</h3>
+
+Markdown's syntax is intended for one purpose: to be used as a
+format for *writing* for the web.
+
+Markdown is not a replacement for HTML, or even close to it. Its
+syntax is very small, corresponding only to a very small subset of
+HTML tags. The idea is *not* to create a syntax that makes it easier
+to insert HTML tags. In my opinion, HTML tags are already easy to
+insert. The idea for Markdown is to make it easy to read, write, and
+edit prose. HTML is a *publishing* format; Markdown is a *writing*
+format. Thus, Markdown's formatting syntax only addresses issues that
+can be conveyed in plain text.
+
+For any markup that is not covered by Markdown's syntax, you simply
+use HTML itself. There's no need to preface it or delimit it to
+indicate that you're switching from Markdown to HTML; you just use
+the tags.
+
+The only restrictions are that block-level HTML elements -- e.g. `<div>`,
+`<table>`, `<pre>`, `<p>`, etc. -- must be separated from surrounding
+content by blank lines, and the start and end tags of the block should
+not be indented with tabs or spaces. Markdown is smart enough not
+to add extra (unwanted) `<p>` tags around HTML block-level tags.
+
+For example, to add an HTML table to a Markdown article:
+
+    This is a regular paragraph.
+
+    <table>
+        <tr>
+            <td>Foo</td>
+        </tr>
+    </table>
+
+    This is another regular paragraph.
+
+Note that Markdown formatting syntax is not processed within block-level
+HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an
+HTML block.
+
+Span-level HTML tags -- e.g. `<span>`, `<cite>`, or `<del>` -- can be
+used anywhere in a Markdown paragraph, list item, or header. If you
+want, you can even use HTML tags instead of Markdown formatting; e.g. if
+you'd prefer to use HTML `<a>` or `<img>` tags instead of Markdown's
+link or image syntax, go right ahead.
+
+Unlike block-level HTML tags, Markdown syntax *is* processed within
+span-level tags.
+
+
+<h3 id="autoescape">Automatic Escaping for Special Characters</h3>
+
+In HTML, there are two characters that demand special treatment: `<`
+and `&`. Left angle brackets are used to start tags; ampersands are
+used to denote HTML entities. If you want to use them as literal
+characters, you must escape them as entities, e.g. `&lt;`, and
+`&amp;`.
+
+Ampersands in particular are bedeviling for web writers. If you want to
+write about 'AT&T', you need to write '`AT&amp;T`'. You even need to
+escape ampersands within URLs. Thus, if you want to link to:
+
+    http://images.google.com/images?num=30&q=larry+bird
+
+you need to encode the URL as:
+
+    http://images.google.com/images?num=30&amp;q=larry+bird
+
+in your anchor tag `href` attribute. Needless to say, this is easy to
+forget, and is probably the single most common source of HTML validation
+errors in otherwise well-marked-up web sites.
+
+Markdown allows you to use these characters naturally, taking care of
+all the necessary escaping for you. If you use an ampersand as part of
+an HTML entity, it remains unchanged; otherwise it will be translated
+into `&amp;`.
+
+So, if you want to include a copyright symbol in your article, you can write:
+
+    &copy;
+
+and Markdown will leave it alone. But if you write:
+
+    AT&T
+
+Markdown will translate it to:
+
+    AT&amp;T
+
+Similarly, because Markdown supports [inline HTML](#html), if you use
+angle brackets as delimiters for HTML tags, Markdown will treat them as
+such. But if you write:
+
+    4 < 5
+
+Markdown will translate it to:
+
+    4 &lt; 5
+
+However, inside Markdown code spans and blocks, angle brackets and
+ampersands are *always* encoded automatically. This makes it easy to use
+Markdown to write about HTML code. (As opposed to raw HTML, which is a
+terrible format for writing about HTML syntax, because every single `<`
+and `&` in your example code needs to be escaped.)
+
+
+* * *
+
+
+<h2 id="block">Block Elements</h2>
+
+
+<h3 id="p">Paragraphs and Line Breaks</h3>
+
+A paragraph is simply one or more consecutive lines of text, separated
+by one or more blank lines. (A blank line is any line that looks like a
+blank line -- a line containing nothing but spaces or tabs is considered
+blank.) Normal paragraphs should not be intended with spaces or tabs.
+
+The implication of the "one or more consecutive lines of text" rule is
+that Markdown supports "hard-wrapped" text paragraphs. This differs
+significantly from most other text-to-HTML formatters (including Movable
+Type's "Convert Line Breaks" option) which translate every line break
+character in a paragraph into a `<br />` tag.
+
+When you *do* want to insert a `<br />` break tag using Markdown, you
+end a line with two or more spaces, then type return.
+
+Yes, this takes a tad more effort to create a `<br />`, but a simplistic
+"every line break is a `<br />`" rule wouldn't work for Markdown.
+Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l]
+work best -- and look better -- when you format them with hard breaks.
+
+  [bq]: #blockquote
+  [l]:  #list
+
+
+
+<h3 id="header">Headers</h3>
+
+Markdown supports two styles of headers, [Setext] [1] and [atx] [2].
+
+Setext-style headers are "underlined" using equal signs (for first-level
+headers) and dashes (for second-level headers). For example:
+
+    This is an H1
+    =============
+
+    This is an H2
+    -------------
+
+Any number of underlining `=`'s or `-`'s will work.
+
+Atx-style headers use 1-6 hash characters at the start of the line,
+corresponding to header levels 1-6. For example:
+
+    # This is an H1
+
+    ## This is an H2
+
+    ###### This is an H6
+
+Optionally, you may "close" atx-style headers. This is purely
+cosmetic -- you can use this if you think it looks better. The
+closing hashes don't even need to match the number of hashes
+used to open the header. (The number of opening hashes
+determines the header level.) :
+
+    # This is an H1 #
+
+    ## This is an H2 ##
+
+    ### This is an H3 ######
+
+
+<h3 id="blockquote">Blockquotes</h3>
+
+Markdown uses email-style `>` characters for blockquoting. If you're
+familiar with quoting passages of text in an email message, then you
+know how to create a blockquote in Markdown. It looks best if you hard
+wrap the text and put a `>` before every line:
+
+    > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+    > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+    > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+    > 
+    > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+    > id sem consectetuer libero luctus adipiscing.
+
+Markdown allows you to be lazy and only put the `>` before the first
+line of a hard-wrapped paragraph:
+
+    > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
+    consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
+    Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
+
+    > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
+    id sem consectetuer libero luctus adipiscing.
+
+Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
+adding additional levels of `>`:
+
+    > This is the first level of quoting.
+    >
+    > > This is nested blockquote.
+    >
+    > Back to the first level.
+
+Blockquotes can contain other Markdown elements, including headers, lists,
+and code blocks:
+
+	> ## This is a header.
+	> 
+	> 1.   This is the first list item.
+	> 2.   This is the second list item.
+	> 
+	> Here's some example code:
+	> 
+	>     return shell_exec("echo $input | $markdown_script");
+
+Any decent text editor should make email-style quoting easy. For
+example, with BBEdit, you can make a selection and choose Increase
+Quote Level from the Text menu.
+
+
+<h3 id="list">Lists</h3>
+
+Markdown supports ordered (numbered) and unordered (bulleted) lists.
+
+Unordered lists use asterisks, pluses, and hyphens -- interchangably
+-- as list markers:
+
+    *   Red
+    *   Green
+    *   Blue
+
+is equivalent to:
+
+    +   Red
+    +   Green
+    +   Blue
+
+and:
+
+    -   Red
+    -   Green
+    -   Blue
+
+Ordered lists use numbers followed by periods:
+
+    1.  Bird
+    2.  McHale
+    3.  Parish
+
+It's important to note that the actual numbers you use to mark the
+list have no effect on the HTML output Markdown produces. The HTML
+Markdown produces from the above list is:
+
+    <ol>
+    <li>Bird</li>
+    <li>McHale</li>
+    <li>Parish</li>
+    </ol>
+
+If you instead wrote the list in Markdown like this:
+
+    1.  Bird
+    1.  McHale
+    1.  Parish
+
+or even:
+
+    3. Bird
+    1. McHale
+    8. Parish
+
+you'd get the exact same HTML output. The point is, if you want to,
+you can use ordinal numbers in your ordered Markdown lists, so that
+the numbers in your source match the numbers in your published HTML.
+But if you want to be lazy, you don't have to.
+
+If you do use lazy list numbering, however, you should still start the
+list with the number 1. At some point in the future, Markdown may support
+starting ordered lists at an arbitrary number.
+
+List markers typically start at the left margin, but may be indented by
+up to three spaces. List markers must be followed by one or more spaces
+or a tab.
+
+To make lists look nice, you can wrap items with hanging indents:
+
+    *   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+        Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+        viverra nec, fringilla in, laoreet vitae, risus.
+    *   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+        Suspendisse id sem consectetuer libero luctus adipiscing.
+
+But if you want to be lazy, you don't have to:
+
+    *   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+    Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
+    viverra nec, fringilla in, laoreet vitae, risus.
+    *   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
+    Suspendisse id sem consectetuer libero luctus adipiscing.
+
+If list items are separated by blank lines, Markdown will wrap the
+items in `<p>` tags in the HTML output. For example, this input:
+
+    *   Bird
+    *   Magic
+
+will turn into:
+
+    <ul>
+    <li>Bird</li>
+    <li>Magic</li>
+    </ul>
+
+But this:
+
+    *   Bird
+
+    *   Magic
+
+will turn into:
+
+    <ul>
+    <li><p>Bird</p></li>
+    <li><p>Magic</p></li>
+    </ul>
+
+List items may consist of multiple paragraphs. Each subsequent
+paragraph in a list item must be intended by either 4 spaces
+or one tab:
+
+    1.  This is a list item with two paragraphs. Lorem ipsum dolor
+        sit amet, consectetuer adipiscing elit. Aliquam hendrerit
+        mi posuere lectus.
+
+        Vestibulum enim wisi, viverra nec, fringilla in, laoreet
+        vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
+        sit amet velit.
+
+    2.  Suspendisse id sem consectetuer libero luctus adipiscing.
+
+It looks nice if you indent every line of the subsequent
+paragraphs, but here again, Markdown will allow you to be
+lazy:
+
+    *   This is a list item with two paragraphs.
+
+        This is the second paragraph in the list item. You're
+    only required to indent the first line. Lorem ipsum dolor
+    sit amet, consectetuer adipiscing elit.
+
+    *   Another item in the same list.
+
+To put a blockquote within a list item, the blockquote's `>`
+delimiters need to be indented:
+
+    *   A list item with a blockquote:
+
+        > This is a blockquote
+        > inside a list item.
+
+To put a code block within a list item, the code block needs
+to be indented *twice* -- 8 spaces or two tabs:
+
+    *   A list item with a code block:
+
+            <code goes here>
+
+
+It's worth noting that it's possible to trigger an ordered list by
+accident, by writing something like this:
+
+    1986. What a great season.
+
+In other words, a *number-period-space* sequence at the beginning of a
+line. To avoid this, you can backslash-escape the period:
+
+    1986\. What a great season.
+
+
+
+<h3 id="precode">Code Blocks</h3>
+
+Pre-formatted code blocks are used for writing about programming or
+markup source code. Rather than forming normal paragraphs, the lines
+of a code block are interpreted literally. Markdown wraps a code block
+in both `<pre>` and `<code>` tags.
+
+To produce a code block in Markdown, simply indent every line of the
+block by at least 4 spaces or 1 tab. For example, given this input:
+
+    This is a normal paragraph:
+
+        This is a code block.
+
+Markdown will generate:
+
+    <p>This is a normal paragraph:</p>
+
+    <pre><code>This is a code block.
+    </code></pre>
+
+One level of indentation -- 4 spaces or 1 tab -- is removed from each
+line of the code block. For example, this:
+
+    Here is an example of AppleScript:
+
+        tell application "Foo"
+            beep
+        end tell
+
+will turn into:
+
+    <p>Here is an example of AppleScript:</p>
+
+    <pre><code>tell application "Foo"
+        beep
+    end tell
+    </code></pre>
+
+A code block continues until it reaches a line that is not indented
+(or the end of the article).
+
+Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
+are automatically converted into HTML entities. This makes it very
+easy to include example HTML source code using Markdown -- just paste
+it and indent it, and Markdown will handle the hassle of encoding the
+ampersands and angle brackets. For example, this:
+
+        <div class="footer">
+            &copy; 2004 Foo Corporation
+        </div>
+
+will turn into:
+
+    <pre><code>&lt;div class="footer"&gt;
+        &amp;copy; 2004 Foo Corporation
+    &lt;/div&gt;
+    </code></pre>
+
+Regular Markdown syntax is not processed within code blocks. E.g.,
+asterisks are just literal asterisks within a code block. This means
+it's also easy to use Markdown to write about Markdown's own syntax.
+
+
+
+<h3 id="hr">Horizontal Rules</h3>
+
+You can produce a horizontal rule tag (`<hr />`) by placing three or
+more hyphens, asterisks, or underscores on a line by themselves. If you
+wish, you may use spaces between the hyphens or asterisks. Each of the
+following lines will produce a horizontal rule:
+
+    * * *
+
+    ***
+
+    *****
+	
+    - - -
+
+    ---------------------------------------
+
+	_ _ _
+
+
+* * *
+
+<h2 id="span">Span Elements</h2>
+
+<h3 id="link">Links</h3>
+
+Markdown supports two style of links: *inline* and *reference*.
+
+In both styles, the link text is delimited by [square brackets].
+
+To create an inline link, use a set of regular parentheses immediately
+after the link text's closing square bracket. Inside the parentheses,
+put the URL where you want the link to point, along with an *optional*
+title for the link, surrounded in quotes. For example:
+
+    This is [an example](http://example.com/ "Title") inline link.
+
+    [This link](http://example.net/) has no title attribute.
+
+Will produce:
+
+    <p>This is <a href="http://example.com/" title="Title">
+    an example</a> inline link.</p>
+
+    <p><a href="http://example.net/">This link</a> has no
+    title attribute.</p>
+
+If you're referring to a local resource on the same server, you can
+use relative paths:
+
+    See my [About](/about/) page for details.
+
+Reference-style links use a second set of square brackets, inside
+which you place a label of your choosing to identify the link:
+
+    This is [an example][id] reference-style link.
+
+You can optionally use a space to separate the sets of brackets:
+
+    This is [an example] [id] reference-style link.
+
+Then, anywhere in the document, you define your link label like this,
+on a line by itself:
+
+    [id]: http://example.com/  "Optional Title Here"
+
+That is:
+
+*   Square brackets containing the link identifier (optionally
+    indented from the left margin using up to three spaces);
+*   followed by a colon;
+*   followed by one or more spaces (or tabs);
+*   followed by the URL for the link;
+*   optionally followed by a title attribute for the link, enclosed
+    in double or single quotes.
+
+The link URL may, optionally, be surrounded by angle brackets:
+
+    [id]: <http://example.com/>  "Optional Title Here"
+
+You can put the title attribute on the next line and use extra spaces
+or tabs for padding, which tends to look better with longer URLs:
+
+    [id]: http://example.com/longish/path/to/resource/here
+        "Optional Title Here"
+
+Link definitions are only used for creating links during Markdown
+processing, and are stripped from your document in the HTML output.
+
+Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are *not* case sensitive. E.g. these two links:
+
+	[link text][a]
+	[link text][A]
+
+are equivalent.
+
+The *implicit link name* shortcut allows you to omit the name of the
+link, in which case the link text itself is used as the name.
+Just use an empty set of square brackets -- e.g., to link the word
+"Google" to the google.com web site, you could simply write:
+
+	[Google][]
+
+And then define the link:
+
+	[Google]: http://google.com/
+
+Because link names may contain spaces, this shortcut even works for
+multiple words in the link text:
+
+	Visit [Daring Fireball][] for more information.
+
+And then define the link:
+	
+	[Daring Fireball]: http://daringfireball.net/
+
+Link definitions can be placed anywhere in your Markdown document. I
+tend to put them immediately after each paragraph in which they're
+used, but if you want, you can put them all at the end of your
+document, sort of like footnotes.
+
+Here's an example of reference links in action:
+
+    I get 10 times more traffic from [Google] [1] than from
+    [Yahoo] [2] or [MSN] [3].
+
+      [1]: http://google.com/        "Google"
+      [2]: http://search.yahoo.com/  "Yahoo Search"
+      [3]: http://search.msn.com/    "MSN Search"
+
+Using the implicit link name shortcut, you could instead write:
+
+    I get 10 times more traffic from [Google][] than from
+    [Yahoo][] or [MSN][].
+
+      [google]: http://google.com/        "Google"
+      [yahoo]:  http://search.yahoo.com/  "Yahoo Search"
+      [msn]:    http://search.msn.com/    "MSN Search"
+
+Both of the above examples will produce the following HTML output:
+
+    <p>I get 10 times more traffic from <a href="http://google.com/"
+    title="Google">Google</a> than from
+    <a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
+    or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
+
+For comparison, here is the same paragraph written using
+Markdown's inline link style:
+
+    I get 10 times more traffic from [Google](http://google.com/ "Google")
+    than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
+    [MSN](http://search.msn.com/ "MSN Search").
+
+The point of reference-style links is not that they're easier to
+write. The point is that with reference-style links, your document
+source is vastly more readable. Compare the above examples: using
+reference-style links, the paragraph itself is only 81 characters
+long; with inline-style links, it's 176 characters; and as raw HTML,
+it's 234 characters. In the raw HTML, there's more markup than there
+is text.
+
+With Markdown's reference-style links, a source document much more
+closely resembles the final output, as rendered in a browser. By
+allowing you to move the markup-related metadata out of the paragraph,
+you can add links without interrupting the narrative flow of your
+prose.
+
+
+<h3 id="em">Emphasis</h3>
+
+Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
+emphasis. Text wrapped with one `*` or `_` will be wrapped with an
+HTML `<em>` tag; double `*`'s or `_`'s will be wrapped with an HTML
+`<strong>` tag. E.g., this input:
+
+    *single asterisks*
+
+    _single underscores_
+
+    **double asterisks**
+
+    __double underscores__
+
+will produce:
+
+    <em>single asterisks</em>
+
+    <em>single underscores</em>
+
+    <strong>double asterisks</strong>
+
+    <strong>double underscores</strong>
+
+You can use whichever style you prefer; the lone restriction is that
+the same character must be used to open and close an emphasis span.
+
+Emphasis can be used in the middle of a word:
+
+    un*fucking*believable
+
+But if you surround an `*` or `_` with spaces, it'll be treated as a
+literal asterisk or underscore.
+
+To produce a literal asterisk or underscore at a position where it
+would otherwise be used as an emphasis delimiter, you can backslash
+escape it:
+
+    \*this text is surrounded by literal asterisks\*
+
+
+
+<h3 id="code">Code</h3>
+
+To indicate a span of code, wrap it with backtick quotes (`` ` ``).
+Unlike a pre-formatted code block, a code span indicates code within a
+normal paragraph. For example:
+
+    Use the `printf()` function.
+
+will produce:
+
+    <p>Use the <code>printf()</code> function.</p>
+
+To include a literal backtick character within a code span, you can use
+multiple backticks as the opening and closing delimiters:
+
+    ``There is a literal backtick (`) here.``
+
+which will produce this:
+
+    <p><code>There is a literal backtick (`) here.</code></p>
+
+The backtick delimiters surrounding a code span may include spaces --
+one after the opening, one before the closing. This allows you to place
+literal backtick characters at the beginning or end of a code span:
+
+	A single backtick in a code span: `` ` ``
+	
+	A backtick-delimited string in a code span: `` `foo` ``
+
+will produce:
+
+	<p>A single backtick in a code span: <code>`</code></p>
+	
+	<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
+
+With a code span, ampersands and angle brackets are encoded as HTML
+entities automatically, which makes it easy to include example HTML
+tags. Markdown will turn this:
+
+    Please don't use any `<blink>` tags.
+
+into:
+
+    <p>Please don't use any <code>&lt;blink&gt;</code> tags.</p>
+
+You can write this:
+
+    `&#8212;` is the decimal-encoded equivalent of `&mdash;`.
+
+to produce:
+
+    <p><code>&amp;#8212;</code> is the decimal-encoded
+    equivalent of <code>&amp;mdash;</code>.</p>
+
+
+
+<h3 id="img">Images</h3>
+
+Admittedly, it's fairly difficult to devise a "natural" syntax for
+placing images into a plain text document format.
+
+Markdown uses an image syntax that is intended to resemble the syntax
+for links, allowing for two styles: *inline* and *reference*.
+
+Inline image syntax looks like this:
+
+    ![Alt text](/path/to/img.jpg)
+
+    ![Alt text](/path/to/img.jpg "Optional title")
+
+That is:
+
+*   An exclamation mark: `!`;
+*   followed by a set of square brackets, containing the `alt`
+    attribute text for the image;
+*   followed by a set of parentheses, containing the URL or path to
+    the image, and an optional `title` attribute enclosed in double
+    or single quotes.
+
+Reference-style image syntax looks like this:
+
+    ![Alt text][id]
+
+Where "id" is the name of a defined image reference. Image references
+are defined using syntax identical to link references:
+
+    [id]: url/to/image  "Optional title attribute"
+
+As of this writing, Markdown has no syntax for specifying the
+dimensions of an image; if this is important to you, you can simply
+use regular HTML `<img>` tags.
+
+
+* * *
+
+
+<h2 id="misc">Miscellaneous</h2>
+
+<h3 id="autolink">Automatic Links</h3>
+
+Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:
+
+    <http://example.com/>
+    
+Markdown will turn this into:
+
+    <a href="http://example.com/">http://example.com/</a>
+
+Automatic links for email addresses work similarly, except that
+Markdown will also perform a bit of randomized decimal and hex
+entity-encoding to help obscure your address from address-harvesting
+spambots. For example, Markdown will turn this:
+
+    <[email protected]>
+
+into something like this:
+
+    <a href="&#x6D;&#x61;i&#x6C;&#x74;&#x6F;:&#x61;&#x64;&#x64;&#x72;&#x65;
+    &#115;&#115;&#64;&#101;&#120;&#x61;&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;
+    &#109;">&#x61;&#x64;&#x64;&#x72;&#x65;&#115;&#115;&#64;&#101;&#120;&#x61;
+    &#109;&#x70;&#x6C;e&#x2E;&#99;&#111;&#109;</a>
+
+which will render in a browser as a clickable link to "[email protected]".
+
+(This sort of entity-encoding trick will indeed fool many, if not
+most, address-harvesting bots, but it definitely won't fool all of
+them. It's better than nothing, but an address published in this way
+will probably eventually start receiving spam.)
+
+
+
+<h3 id="backslash">Backslash Escapes</h3>
+
+Markdown allows you to use backslash escapes to generate literal
+characters which would otherwise have special meaning in Markdown's
+formatting syntax. For example, if you wanted to surround a word with
+literal asterisks (instead of an HTML `<em>` tag), you can backslashes
+before the asterisks, like this:
+
+    \*literal asterisks\*
+
+Markdown provides backslash escapes for the following characters:
+
+    \   backslash
+    `   backtick
+    *   asterisk
+    _   underscore
+    {}  curly braces
+    []  square brackets
+    ()  parentheses
+    #   hash mark
+	+	plus sign
+	-	minus sign (hyphen)
+    .   dot
+    !   exclamation mark
+

+ 9 - 0
vendor/github.com/russross/blackfriday/testdata/Nested blockquotes.html

@@ -0,0 +1,9 @@
+<blockquote>
+<p>foo</p>
+
+<blockquote>
+<p>bar</p>
+</blockquote>
+
+<p>foo</p>
+</blockquote>

+ 5 - 0
vendor/github.com/russross/blackfriday/testdata/Nested blockquotes.text

@@ -0,0 +1,5 @@
+> foo
+>
+> > bar
+>
+> foo

+ 166 - 0
vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.html

@@ -0,0 +1,166 @@
+<h2>Unordered</h2>
+
+<p>Asterisks tight:</p>
+
+<ul>
+<li>asterisk 1</li>
+<li>asterisk 2</li>
+<li>asterisk 3</li>
+</ul>
+
+<p>Asterisks loose:</p>
+
+<ul>
+<li><p>asterisk 1</p></li>
+
+<li><p>asterisk 2</p></li>
+
+<li><p>asterisk 3</p></li>
+</ul>
+
+<hr>
+
+<p>Pluses tight:</p>
+
+<ul>
+<li>Plus 1</li>
+<li>Plus 2</li>
+<li>Plus 3</li>
+</ul>
+
+<p>Pluses loose:</p>
+
+<ul>
+<li><p>Plus 1</p></li>
+
+<li><p>Plus 2</p></li>
+
+<li><p>Plus 3</p></li>
+</ul>
+
+<hr>
+
+<p>Minuses tight:</p>
+
+<ul>
+<li>Minus 1</li>
+<li>Minus 2</li>
+<li>Minus 3</li>
+</ul>
+
+<p>Minuses loose:</p>
+
+<ul>
+<li><p>Minus 1</p></li>
+
+<li><p>Minus 2</p></li>
+
+<li><p>Minus 3</p></li>
+</ul>
+
+<h2>Ordered</h2>
+
+<p>Tight:</p>
+
+<ol>
+<li>First</li>
+<li>Second</li>
+<li>Third</li>
+</ol>
+
+<p>and:</p>
+
+<ol>
+<li>One</li>
+<li>Two</li>
+<li>Three</li>
+</ol>
+
+<p>Loose using tabs:</p>
+
+<ol>
+<li><p>First</p></li>
+
+<li><p>Second</p></li>
+
+<li><p>Third</p></li>
+</ol>
+
+<p>and using spaces:</p>
+
+<ol>
+<li><p>One</p></li>
+
+<li><p>Two</p></li>
+
+<li><p>Three</p></li>
+</ol>
+
+<p>Multiple paragraphs:</p>
+
+<ol>
+<li><p>Item 1, graf one.</p>
+
+<p>Item 2. graf two. The quick brown fox jumped over the lazy dog's
+back.</p></li>
+
+<li><p>Item 2.</p></li>
+
+<li><p>Item 3.</p></li>
+</ol>
+
+<h2>Nested</h2>
+
+<ul>
+<li>Tab
+
+<ul>
+<li>Tab
+
+<ul>
+<li>Tab</li>
+</ul></li>
+</ul></li>
+</ul>
+
+<p>Here's another:</p>
+
+<ol>
+<li>First</li>
+<li>Second:
+
+<ul>
+<li>Fee</li>
+<li>Fie</li>
+<li>Foe</li>
+</ul></li>
+<li>Third</li>
+</ol>
+
+<p>Same thing but with paragraphs:</p>
+
+<ol>
+<li><p>First</p></li>
+
+<li><p>Second:</p>
+
+<ul>
+<li>Fee</li>
+<li>Fie</li>
+<li>Foe</li>
+</ul></li>
+
+<li><p>Third</p></li>
+</ol>
+
+<p>This was an error in Markdown 1.0.1:</p>
+
+<ul>
+<li><p>this</p>
+
+<ul>
+<li>sub</li>
+</ul>
+
+<p>that</p></li>
+</ul>

+ 131 - 0
vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.text

@@ -0,0 +1,131 @@
+## Unordered
+
+Asterisks tight:
+
+*	asterisk 1
+*	asterisk 2
+*	asterisk 3
+
+
+Asterisks loose:
+
+*	asterisk 1
+
+*	asterisk 2
+
+*	asterisk 3
+
+* * *
+
+Pluses tight:
+
++	Plus 1
++	Plus 2
++	Plus 3
+
+
+Pluses loose:
+
++	Plus 1
+
++	Plus 2
+
++	Plus 3
+
+* * *
+
+
+Minuses tight:
+
+-	Minus 1
+-	Minus 2
+-	Minus 3
+
+
+Minuses loose:
+
+-	Minus 1
+
+-	Minus 2
+
+-	Minus 3
+
+
+## Ordered
+
+Tight:
+
+1.	First
+2.	Second
+3.	Third
+
+and:
+
+1. One
+2. Two
+3. Three
+
+
+Loose using tabs:
+
+1.	First
+
+2.	Second
+
+3.	Third
+
+and using spaces:
+
+1. One
+
+2. Two
+
+3. Three
+
+Multiple paragraphs:
+
+1.	Item 1, graf one.
+
+	Item 2. graf two. The quick brown fox jumped over the lazy dog's
+	back.
+	
+2.	Item 2.
+
+3.	Item 3.
+
+
+
+## Nested
+
+*	Tab
+	*	Tab
+		*	Tab
+
+Here's another:
+
+1. First
+2. Second:
+	* Fee
+	* Fie
+	* Foe
+3. Third
+
+Same thing but with paragraphs:
+
+1. First
+
+2. Second:
+	* Fee
+	* Fie
+	* Foe
+
+3. Third
+
+
+This was an error in Markdown 1.0.1:
+
+*	this
+
+	*	sub
+
+	that

+ 7 - 0
vendor/github.com/russross/blackfriday/testdata/Strong and em together.html

@@ -0,0 +1,7 @@
+<p><strong><em>This is strong and em.</em></strong></p>
+
+<p>So is <strong><em>this</em></strong> word.</p>
+
+<p><strong><em>This is strong and em.</em></strong></p>
+
+<p>So is <strong><em>this</em></strong> word.</p>

+ 7 - 0
vendor/github.com/russross/blackfriday/testdata/Strong and em together.text

@@ -0,0 +1,7 @@
+***This is strong and em.***
+
+So is ***this*** word.
+
+___This is strong and em.___
+
+So is ___this___ word.

+ 26 - 0
vendor/github.com/russross/blackfriday/testdata/Tabs.html

@@ -0,0 +1,26 @@
+<ul>
+<li><p>this is a list item
+indented with tabs</p></li>
+
+<li><p>this is a list item
+indented with spaces</p></li>
+</ul>
+
+<p>Code:</p>
+
+<pre><code>this code block is indented by one tab
+</code></pre>
+
+<p>And:</p>
+
+<pre><code>    this code block is indented by two tabs
+</code></pre>
+
+<p>And:</p>
+
+<pre><code>+   this is an example list item
+    indented with tabs
+
++   this is an example list item
+    indented with spaces
+</code></pre>

+ 21 - 0
vendor/github.com/russross/blackfriday/testdata/Tabs.text

@@ -0,0 +1,21 @@
++	this is a list item
+	indented with tabs
+
++   this is a list item
+    indented with spaces
+
+Code:
+
+	this code block is indented by one tab
+
+And:
+
+		this code block is indented by two tabs
+
+And:
+
+	+	this is an example list item
+		indented with tabs
+	
+	+   this is an example list item
+	    indented with spaces

+ 9 - 0
vendor/github.com/russross/blackfriday/testdata/Tidyness.html

@@ -0,0 +1,9 @@
+<blockquote>
+<p>A list within a blockquote:</p>
+
+<ul>
+<li>asterisk 1</li>
+<li>asterisk 2</li>
+<li>asterisk 3</li>
+</ul>
+</blockquote>

+ 5 - 0
vendor/github.com/russross/blackfriday/testdata/Tidyness.text

@@ -0,0 +1,5 @@
+> A list within a blockquote:
+> 
+> *	asterisk 1
+> *	asterisk 2
+> *	asterisk 3

+ 16 - 7
views/book/setting.tpl

@@ -5,13 +5,14 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1">
 
-    <title>设置 - Powered by MinDoc</title>
+    <title>设置 - {{.Model.BookName}} - Powered by MinDoc</title>
 
     <!-- Bootstrap -->
     <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
     <link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
     <link href="{{cdncss "/static/webuploader/webuploader.css"}}" rel="stylesheet">
     <link href="{{cdncss "/static/cropper/2.3.4/cropper.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.css"}}" rel="stylesheet">
     <link href="/static/css/main.css" rel="stylesheet">
     <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
     <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
@@ -60,17 +61,17 @@
                             </div>
                             <div class="form-group">
                                 <label>标识</label>
-                                <input type="text" class="form-control" value=" {{.BaseUrl}}{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" disabled>
+                                <input type="text" class="form-control" value="{{.BaseUrl}}{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" placeholder="项目唯一标识" disabled>
                             </div>
                             <div class="form-group">
                                 <label>描述</label>
-                                <textarea rows="3" class="form-control" name="description" style="height: 90px">{{.Model.Description}}</textarea>
-                                <p class="text">描述信息不超过500个字符</p>
+                                <textarea rows="3" class="form-control" name="description" style="height: 90px" placeholder="项目描述">{{.Model.Description}}</textarea>
+                                <p class="text">描述信息不超过500个字符,支持Markdown语法</p>
                             </div>
                             <div class="form-group">
                                 <label>标签</label>
                                 <input type="text" class="form-control" name="label" placeholder="项目标签" value="{{.Model.Label}}">
-                                <p class="text">最多允许添加10个标签,多个标签请用“;”分割</p>
+                                <p class="text">最多允许添加10个标签,多个标签请用“,”分割</p>
                             </div>
                             <div class="form-group">
                                 <label>编辑器</label>
@@ -177,12 +178,12 @@
     </div>
 </div>
 <!-- Start Modal -->
-<div class="modal fade" id="upload-logo-panel" tabindex="-1" role="dialog" aria-labelledby="修改头像" aria-hidden="true">
+<div class="modal fade" id="upload-logo-panel" tabindex="-1" role="dialog" aria-labelledby="修改封面" aria-hidden="true">
     <div class="modal-dialog">
         <div class="modal-content">
             <div class="modal-header">
                 <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
-                <h4 class="modal-title">修改头像</h4>
+                <h4 class="modal-title">修改封面</h4>
             </div>
             <div class="modal-body">
                 <div class="wraper">
@@ -271,6 +272,7 @@
 <script src="{{cdnjs "/static/webuploader/webuploader.min.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/cropper/2.3.4/cropper.min.js"}}" type="text/javascript"></script>
 <script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
+<script src="{{cdnjs "/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.min.js"}}" type="text/javascript"></script>
 <script src="/static/js/main.js" type="text/javascript"></script>
 <script type="text/javascript">
     $(function () {
@@ -280,6 +282,13 @@
             window.modalHtml = $("#upload-logo-panel").find(".modal-body").html();
         });
 
+        $('input[name="label"]').tagsinput({
+            confirmKeys: [13,44],
+            maxTags: 10,
+            trimValue: true,
+            cancelConfirmKeysOnEmpty : false
+        });
+
         $("#changePrivatelyOwnedForm").ajaxForm({
             beforeSubmit :function () {
                 $("#btnChangePrivatelyOwned").button("loading");

+ 13 - 0
views/home/index.tpl

@@ -25,6 +25,19 @@
     {{template "widgets/header.tpl" .}}
     <div class="container manual-body">
         <div class="row">
+            {{if gt (.Labels|len) 0}}
+            <div class="hide tag-container-outer">
+                <span class="title">热门标签:</span>
+                <span class="tags">
+                    {{range  $index,$item := .Labels}}
+                    <a href="{{urlfor "LabelController.Index" ":key" $item.LabelName}}">{{$item.LabelName}}<span class="detail">{{$item.BookNumber}}</span></a>
+                    {{end}}
+
+                </span>
+            </div>
+
+            {{end}}
+
             <div class="manual-list">
                 {{range $index,$item := .Lists}}
                 <div class="list-item">

+ 65 - 0
views/label/index.tpl

@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    <title>{{.LabelName}} - Powered by MinDoc</title>
+
+    <!-- Bootstrap -->
+    <link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
+    <link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
+
+    <link href="/static/css/main.css" rel="stylesheet">
+    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
+    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+    <!--[if lt IE 9]>
+    <script src="/static/html5shiv/3.7.3/html5shiv.min.js"></script>
+    <script src="/static/respond.js/1.4.2/respond.min.js"></script>
+    <![endif]-->
+</head>
+<body>
+<div class="manual-reader manual-container manual-search-reader">
+    {{template "widgets/header.tpl" .}}
+    <div class="container manual-body">
+        <div class="search-head">
+            <strong class="search-title">显示标签为"{{.LabelName}}"的项目</strong>
+        </div>
+        <div class="row">
+            <div class="manual-list">
+                {{range $index,$item := .Lists}}
+                <div class="list-item">
+                    <dl class="manual-item-standard">
+                        <dt>
+                            <a href="{{urlfor "DocumentController.Index" ":key" $item.Identify}}" title="{{$item.BookName}}-{{$item.CreateName}}" target="_blank">
+                                <img src="{{$item.Cover}}" class="cover" alt="{{$item.BookName}}-{{$item.CreateName}}">
+                            </a>
+                        </dt>
+                        <dd>
+                            <a href="{{urlfor "DocumentController.Index" ":key" $item.Identify}}" class="name" title="{{$item.BookName}}-{{$item.CreateName}}" target="_blank">{{$item.BookName}}</a>
+                        </dd>
+                        <dd>
+                            <span class="author">
+                                <b class="text">作者</b>
+                                <b class="text">-</b>
+                                <b class="text">{{$item.CreateName}}</b>
+                            </span>
+                        </dd>
+                    </dl>
+                </div>
+                {{end}}
+
+                <div class="clearfix"></div>
+            </div>
+            <nav>
+                {{.PageHtml}}
+            </nav>
+        </div>
+    </div>
+    {{template "widgets/footer.tpl" .}}
+</div>
+<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
+<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
+</body>
+</html>

+ 26 - 0
views/manager/users.tpl

@@ -112,6 +112,7 @@
                                             <template v-else>
                                                 <button type="button" class="btn btn-success btn-sm" @click="setMemberStatus(item.member_id,0,$event)" data-loading-text="禁用中...">启用</button>
                                             </template>
+                                            <button type="button" class="btn btn-danger btn-sm" @click="deleteMember(item.member_id,$event)" data-loading-text="删除中">删除</button>
                                         </template>
                                     </td>
                                 </tr>
@@ -297,6 +298,31 @@
                             }
                         }
                     })
+                },
+                deleteMember : function (id, e) {
+                    var $this = this;
+                    $.ajax({
+                        url : "{{urlfor "ManagerController.DeleteMember"}}",
+                        type : "post",
+                        data : { "id":id },
+                        dataType : "json",
+                        success : function (res) {
+                            if (res.errcode === 0) {
+
+                                for (var index in $this.lists) {
+                                    var item = $this.lists[index];
+                                    if (item.member_id == id) {
+                                        console.log(item);
+                                        $this.lists.splice(index,1);
+                                        break;
+                                    }
+                                }
+                            } else {
+                                alert("操作失败:" + res.message);
+                            }
+                        }
+                    });
+
                 }
             }
         });

Some files were not shown because too many files changed in this diff