Explorar el Código

1、实现富文本编辑器
2、实现项目排序

lifei6671 hace 8 años
padre
commit
ae8f4532f6
Se han modificado 100 ficheros con 22025 adiciones y 10269 borrados
  1. 2 88
      Godeps/Godeps.json
  2. 15 5
      commands/command.go
  3. 76 1
      controllers/book.go
  4. 51 29
      controllers/document.go
  5. 1 0
      models/attachment.go
  6. 18 1
      models/book.go
  7. 14 10
      models/document.go
  8. 1 1
      models/document_tree.go
  9. 2 0
      routers/router.go
  10. 41 2
      static/css/markdown.css
  11. 193 0
      static/js/edirot.js
  12. 254 0
      static/js/html-editor.js
  13. 47 163
      static/js/markdown.js
  14. 789 0
      static/to-markdown/dist/to-markdown.js
  15. 110 0
      static/to-markdown/lib/gfm-converters.js
  16. 76 0
      static/to-markdown/lib/html-parser.js
  17. 151 0
      static/to-markdown/lib/md-converters.js
  18. 831 0
      static/wangEditor/css/wangEditor.css
  19. 810 0
      static/wangEditor/css/wangEditor.less
  20. 0 0
      static/wangEditor/css/wangEditor.min.css
  21. BIN
      static/wangEditor/fonts/icomoon.eot
  22. 76 0
      static/wangEditor/fonts/icomoon.svg
  23. BIN
      static/wangEditor/fonts/icomoon.ttf
  24. BIN
      static/wangEditor/fonts/icomoon.woff
  25. 3 0
      static/wangEditor/js/lib/jquery-1.10.2.min.js
  26. 9831 0
      static/wangEditor/js/lib/jquery-2.2.1.js
  27. 8585 0
      static/wangEditor/js/wangEditor.js
  28. 1 0
      static/wangEditor/js/wangEditor.min.js
  29. 47 0
      static/wangEditor/plugins/save-menu.js
  30. 0 339
      vendor/github.com/adamzy/cedar-go/LICENSE.md
  31. 0 83
      vendor/github.com/adamzy/cedar-go/README.md
  32. 0 231
      vendor/github.com/adamzy/cedar-go/api.go
  33. 0 407
      vendor/github.com/adamzy/cedar-go/cedar.go
  34. 0 12
      vendor/github.com/adamzy/cedar-go/doc.go
  35. 0 11
      vendor/github.com/adamzy/cedar-go/errors.go
  36. 0 63
      vendor/github.com/adamzy/cedar-go/io.go
  37. 0 4
      vendor/github.com/boltdb/bolt/.gitignore
  38. 0 20
      vendor/github.com/boltdb/bolt/LICENSE
  39. 0 18
      vendor/github.com/boltdb/bolt/Makefile
  40. 0 915
      vendor/github.com/boltdb/bolt/README.md
  41. 0 18
      vendor/github.com/boltdb/bolt/appveyor.yml
  42. 0 10
      vendor/github.com/boltdb/bolt/bolt_386.go
  43. 0 10
      vendor/github.com/boltdb/bolt/bolt_amd64.go
  44. 0 28
      vendor/github.com/boltdb/bolt/bolt_arm.go
  45. 0 12
      vendor/github.com/boltdb/bolt/bolt_arm64.go
  46. 0 10
      vendor/github.com/boltdb/bolt/bolt_linux.go
  47. 0 27
      vendor/github.com/boltdb/bolt/bolt_openbsd.go
  48. 0 9
      vendor/github.com/boltdb/bolt/bolt_ppc.go
  49. 0 12
      vendor/github.com/boltdb/bolt/bolt_ppc64.go
  50. 0 12
      vendor/github.com/boltdb/bolt/bolt_ppc64le.go
  51. 0 12
      vendor/github.com/boltdb/bolt/bolt_s390x.go
  52. 0 89
      vendor/github.com/boltdb/bolt/bolt_unix.go
  53. 0 90
      vendor/github.com/boltdb/bolt/bolt_unix_solaris.go
  54. 0 144
      vendor/github.com/boltdb/bolt/bolt_windows.go
  55. 0 8
      vendor/github.com/boltdb/bolt/boltsync_unix.go
  56. 0 777
      vendor/github.com/boltdb/bolt/bucket.go
  57. 0 400
      vendor/github.com/boltdb/bolt/cursor.go
  58. 0 1039
      vendor/github.com/boltdb/bolt/db.go
  59. 0 44
      vendor/github.com/boltdb/bolt/doc.go
  60. 0 71
      vendor/github.com/boltdb/bolt/errors.go
  61. 0 252
      vendor/github.com/boltdb/bolt/freelist.go
  62. 0 604
      vendor/github.com/boltdb/bolt/node.go
  63. 0 197
      vendor/github.com/boltdb/bolt/page.go
  64. 0 684
      vendor/github.com/boltdb/bolt/tx.go
  65. 0 15
      vendor/github.com/cznic/fileutil/AUTHORS
  66. 0 15
      vendor/github.com/cznic/fileutil/CONTRIBUTORS
  67. 0 27
      vendor/github.com/cznic/fileutil/LICENSE
  68. 0 27
      vendor/github.com/cznic/fileutil/Makefile
  69. 0 16
      vendor/github.com/cznic/fileutil/README
  70. 0 223
      vendor/github.com/cznic/fileutil/fileutil.go
  71. 0 27
      vendor/github.com/cznic/fileutil/fileutil_arm.go
  72. 0 29
      vendor/github.com/cznic/fileutil/fileutil_darwin.go
  73. 0 29
      vendor/github.com/cznic/fileutil/fileutil_freebsd.go
  74. 0 98
      vendor/github.com/cznic/fileutil/fileutil_linux.go
  75. 0 29
      vendor/github.com/cznic/fileutil/fileutil_netbsd.go
  76. 0 27
      vendor/github.com/cznic/fileutil/fileutil_openbsd.go
  77. 0 27
      vendor/github.com/cznic/fileutil/fileutil_plan9.go
  78. 0 29
      vendor/github.com/cznic/fileutil/fileutil_solaris.go
  79. 0 185
      vendor/github.com/cznic/fileutil/fileutil_windows.go
  80. 0 13
      vendor/github.com/cznic/fileutil/test_deps.go
  81. 0 60
      vendor/github.com/cznic/internal/buffer/Makefile
  82. 0 146
      vendor/github.com/cznic/internal/buffer/buffer.go
  83. 0 55
      vendor/github.com/cznic/internal/file/Makefile
  84. 0 434
      vendor/github.com/cznic/internal/file/file.go
  85. 0 55
      vendor/github.com/cznic/internal/slice/Makefile
  86. 0 173
      vendor/github.com/cznic/internal/slice/pool.go
  87. 0 11
      vendor/github.com/cznic/kv/AUTHORS
  88. 0 13
      vendor/github.com/cznic/kv/CONTRIBUTORS
  89. 0 27
      vendor/github.com/cznic/kv/LICENSE
  90. 0 55
      vendor/github.com/cznic/kv/Makefile
  91. 0 10
      vendor/github.com/cznic/kv/README.md
  92. 0 86
      vendor/github.com/cznic/kv/doc.go
  93. 0 31
      vendor/github.com/cznic/kv/etc.go
  94. 0 851
      vendor/github.com/cznic/kv/kv.go
  95. 0 58
      vendor/github.com/cznic/kv/lock.go
  96. 0 246
      vendor/github.com/cznic/kv/options.go
  97. 0 35
      vendor/github.com/cznic/kv/perf-4670.log
  98. 0 35
      vendor/github.com/cznic/kv/perf-r550.log
  99. 0 21
      vendor/github.com/cznic/kv/v0.go
  100. 0 89
      vendor/github.com/cznic/kv/verify.go

+ 2 - 88
Godeps/Godeps.json

@@ -3,10 +3,6 @@
 	"GoVersion": "go1.8",
 	"GodepVersion": "v79",
 	"Deps": [
-		{
-			"ImportPath": "github.com/adamzy/cedar-go",
-			"Rev": "d348c21f72432c2b6d5f05f68759fde94f64b227"
-		},
 		{
 			"ImportPath": "github.com/astaxie/beego",
 			"Comment": "v1.8.0",
@@ -52,96 +48,14 @@
 			"Comment": "v1.8.0",
 			"Rev": "323a1c4214101331a4b71922c23d19b7409ac71f"
 		},
-		{
-			"ImportPath": "github.com/boltdb/bolt",
-			"Comment": "v1.3.0-58-ge9cf4fa",
-			"Rev": "e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd"
-		},
-		{
-			"ImportPath": "github.com/cznic/fileutil",
-			"Rev": "90cf820aafe8f7df39416fdbb932029ff99bd1ab"
-		},
-		{
-			"ImportPath": "github.com/cznic/internal/buffer",
-			"Comment": "1.0.0-1-ge5e1c3e",
-			"Rev": "e5e1c3e9165d0a72507c2bbb0ffac1c02b8d3f7c"
-		},
-		{
-			"ImportPath": "github.com/cznic/internal/file",
-			"Comment": "1.0.0-1-ge5e1c3e",
-			"Rev": "e5e1c3e9165d0a72507c2bbb0ffac1c02b8d3f7c"
-		},
-		{
-			"ImportPath": "github.com/cznic/internal/slice",
-			"Comment": "1.0.0-1-ge5e1c3e",
-			"Rev": "e5e1c3e9165d0a72507c2bbb0ffac1c02b8d3f7c"
-		},
-		{
-			"ImportPath": "github.com/cznic/kv",
-			"Rev": "c5de474a2ccdaed5ba5ff8b5d2d213dbf48a8b5e"
-		},
-		{
-			"ImportPath": "github.com/cznic/lldb",
-			"Comment": "v1.1.0",
-			"Rev": "bea8611dd5c407f3c5eab9f9c68e887a27dc6f0e"
-		},
-		{
-			"ImportPath": "github.com/cznic/mathutil",
-			"Rev": "1447ad269d64ca91aa8d7079baa40b6fc8b965e7"
-		},
-		{
-			"ImportPath": "github.com/cznic/sortutil",
-			"Rev": "4c7342852e65c2088c981288f2c5610d10b9f7f4"
-		},
-		{
-			"ImportPath": "github.com/cznic/zappy",
-			"Rev": "2533cb5b45cc6c07421468ce262899ddc9d53fb7"
-		},
-		{
-			"ImportPath": "github.com/edsrzf/mmap-go",
-			"Rev": "0bce6a6887123b67a60366d2c9fe2dfb74289d2e"
-		},
 		{
 			"ImportPath": "github.com/go-sql-driver/mysql",
 			"Comment": "v1.2-191-g1421caf",
 			"Rev": "1421caf44f6464fd2ee8de694c7508ee13f92964"
 		},
 		{
-			"ImportPath": "github.com/huichen/murmur",
-			"Rev": "e0489551cf5116e27d7cc69d97a53cdbdd028acf"
-		},
-		{
-			"ImportPath": "github.com/huichen/sego",
-			"Rev": "d06fe1b3abe3877ab593b57e5e43daf6c4c25add"
-		},
-		{
-			"ImportPath": "github.com/huichen/wukong/core",
-			"Comment": "v0.1-94-gd014a1f",
-			"Rev": "d014a1f19dae3664677c11bd25549cfc820cd890"
-		},
-		{
-			"ImportPath": "github.com/huichen/wukong/engine",
-			"Comment": "v0.1-94-gd014a1f",
-			"Rev": "d014a1f19dae3664677c11bd25549cfc820cd890"
-		},
-		{
-			"ImportPath": "github.com/huichen/wukong/storage",
-			"Comment": "v0.1-94-gd014a1f",
-			"Rev": "d014a1f19dae3664677c11bd25549cfc820cd890"
-		},
-		{
-			"ImportPath": "github.com/huichen/wukong/types",
-			"Comment": "v0.1-94-gd014a1f",
-			"Rev": "d014a1f19dae3664677c11bd25549cfc820cd890"
-		},
-		{
-			"ImportPath": "github.com/huichen/wukong/utils",
-			"Comment": "v0.1-94-gd014a1f",
-			"Rev": "d014a1f19dae3664677c11bd25549cfc820cd890"
-		},
-		{
-			"ImportPath": "golang.org/x/sys/unix",
-			"Rev": "a408501be4d17ee978c04a618e7a1b22af058c0e"
+			"ImportPath": "github.com/nfnt/resize",
+			"Rev": "891127d8d1b52734debe1b3c3d7e747502b6c366"
 		}
 	]
 }

+ 15 - 5
commands/command.go

@@ -70,20 +70,30 @@ func Initialization()  {
 // RegisterLogger 注册日志
 func RegisterLogger()  {
 
+	logs.SetLogFuncCall(true)
 	logs.SetLogger("console")
-	logs.SetLogger("file",`{"filename":"logs/log.log"}`)
 	logs.EnableFuncCallDepth(true)
 	logs.Async()
+
+	beego.BeeLogger.DelLogger("console")
+	beego.SetLogger("file",`{"filename":"logs/log.log"}`)
+	beego.SetLogFuncCall(true)
+	beego.BeeLogger.Async()
 }
 
 // RunCommand 注册orm命令行工具
 func RegisterCommand() {
 
 	if _,err := os.Stat("install.lock"); os.IsNotExist(err){
-		orm.RunSyncdb("default",true,false)
-		Initialization()
-		f,_ := os.Create("install.lock")
-		defer f.Close()
+		err = orm.RunSyncdb("default",true,false)
+		if err == nil {
+			Initialization()
+			f, _ := os.Create("install.lock")
+			defer f.Close()
+		}else{
+			logs.Info("初始化数据库失败 =>",err)
+			os.Exit(2)
+		}
 	}
 
 }

+ 76 - 1
controllers/book.go

@@ -418,7 +418,8 @@ func (c *BookController) Create() {
 		err := book.Insert()
 
 		if err != nil {
-			c.JsonResult(6005,err.Error())
+			logs.Error("Insert => ",err)
+			c.JsonResult(6005,"保存项目失败")
 		}
 		bookResult := models.NewBookResult()
 		bookResult.FindByIdentify(book.Identify,c.Member.MemberId)
@@ -496,6 +497,80 @@ func (c *BookController) Delete() {
 	c.JsonResult(0,"ok")
 }
 
+//发布项目
+func (c *BookController) Release() {
+	c.JsonResult(0,"ok")
+}
+
+func (c *BookController) SaveSort() {
+	c.Prepare()
+
+	identify := c.Ctx.Input.Param(":key")
+	if identify == "" {
+		c.Abort("404")
+	}
+
+	bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
+
+	if err != nil {
+		beego.Error("DocumentController.Edit => ",err)
+
+		c.Abort("403")
+	}
+	if bookResult.RoleId == conf.BookObserver {
+		c.JsonResult(6002,"项目不存在或权限不足")
+	}
+	content := c.Ctx.Input.RequestBody
+
+	var docs []map[string]interface{}
+
+	err = json.Unmarshal(content,&docs)
+
+	if err != nil {
+		beego.Error(err)
+		c.JsonResult(6003,"数据错误")
+	}
+	fmt.Printf("%+v",docs)
+	for _,item := range docs {
+		if doc_id,ok := item["id"].(float64);ok {
+			doc,err := models.NewDocument().Find(int(doc_id));
+			if err != nil {
+				beego.Error(err)
+				continue;
+			}
+			if doc.BookId != bookResult.BookId {
+				logs.Info("%s","权限错误")
+				continue;
+			}
+			sort,ok := item["sort"].(float64);
+			if !ok {
+				beego.Info("排序数字转换失败 => ",item)
+				continue
+			}
+			parent_id,ok := item["parent"].(float64)
+			if !ok {
+				beego.Info("父分类转换失败 => ",item)
+				continue
+			}
+			if parent_id > 0 {
+				if parent,err := models.NewDocument().Find(int(parent_id)); err != nil || parent.BookId != bookResult.BookId {
+					continue
+				}
+			}
+			doc.OrderSort = int(sort)
+			doc.ParentId = int(parent_id)
+			if err := doc.InsertOrUpdate(); err != nil {
+				fmt.Printf("%s",err.Error())
+				beego.Error(err)
+			}
+		}else{
+			fmt.Printf("文档ID转换失败 => %+v",item)
+		}
+
+	}
+	c.JsonResult(0,"ok")
+}
+
 func (c *BookController) IsPermission() (*models.BookResult,error) {
 	identify := c.GetString("identify")
 	book ,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)

+ 51 - 29
controllers/document.go

@@ -12,7 +12,6 @@ import (
 	"html/template"
 
 	"github.com/lifei6671/godoc/models"
-	"github.com/astaxie/beego/logs"
 	"github.com/lifei6671/godoc/conf"
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego/orm"
@@ -38,31 +37,39 @@ func (c *DocumentController) Edit()  {
 		c.Abort("404")
 	}
 
-	book,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
+	bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
 
 	if err != nil {
-		logs.Error("DocumentController.Edit => ",err)
+		beego.Error("DocumentController.Edit => ",err)
 
 		c.Abort("403")
 	}
-	if book.Editor == "markdown" {
+	if bookResult.RoleId == conf.BookObserver {
+
+		c.JsonResult(6002,"项目不存在或权限不足")
+	}
+
+	//根据不同编辑器类型加载编辑器
+	if bookResult.Editor == "markdown" {
 		c.TplName = "document/markdown_edit_template.tpl"
-	}else{
+	}else if bookResult.Editor == "html"{
 		c.TplName = "document/html_edit_template.tpl"
+	}else{
+		c.TplName = "document/" + bookResult.Editor + "_edit_template.tpl"
 	}
 
-	c.Data["Model"] = book
+	c.Data["Model"] = bookResult
 
-	r,_ := json.Marshal(book)
+	r,_ := json.Marshal(bookResult)
 
 	c.Data["ModelResult"] = template.JS(string(r))
 
 	c.Data["Result"] = template.JS("[]")
 
-	trees ,err := models.NewDocument().FindDocumentTree(book.BookId)
-	logs.Info("",trees)
+	trees ,err := models.NewDocument().FindDocumentTree(bookResult.BookId)
+	beego.Info("",trees)
 	if err != nil {
-		logs.Error("FindDocumentTree => ", err)
+		beego.Error("FindDocumentTree => ", err)
 	}else{
 		if len(trees) > 0 {
 			if jtree, err := json.Marshal(trees); err == nil {
@@ -75,7 +82,7 @@ func (c *DocumentController) Edit()  {
 
 }
 
-//创建一个文档
+//创建一个文档.
 func (c *DocumentController) Create() {
 	identify := c.GetString("identify")
 	doc_identify := c.GetString("doc_identify")
@@ -103,7 +110,7 @@ func (c *DocumentController) Create() {
 	bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
 
 	if err != nil || bookResult.RoleId == conf.BookObserver {
-		logs.Error("FindByIdentify => ",err)
+		beego.Error("FindByIdentify => ",err)
 		c.JsonResult(6002,"项目不存在或权限不足")
 	}
 	if parent_id > 0 {
@@ -125,18 +132,19 @@ func (c *DocumentController) Create() {
 	document.ParentId = parent_id
 
 	if err := document.InsertOrUpdate();err != nil {
-		logs.Error("InsertOrUpdate => ",err)
+		beego.Error("InsertOrUpdate => ",err)
 		c.JsonResult(6005,"保存失败")
 	}else{
-		logs.Info("",document)
+		beego.Info("",document)
 		c.JsonResult(0,"ok",document)
 	}
 }
 
-//上传附件或图片
+//上传附件或图片.
 func (c *DocumentController) Upload()  {
 
 	identify := c.GetString("identify")
+	doc_id,_ := c.GetInt("doc_id")
 
 	if identify == "" {
 		c.JsonResult(6001,"参数错误")
@@ -172,7 +180,7 @@ func (c *DocumentController) Upload()  {
 	book,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
 
 	if err != nil {
-		logs.Error("DocumentController.Edit => ",err)
+		beego.Error("DocumentController.Edit => ",err)
 		if err == orm.ErrNoRows {
 			c.JsonResult(6006,"权限不足")
 		}
@@ -182,15 +190,28 @@ func (c *DocumentController) Upload()  {
 	if book.RoleId != conf.BookEditor && book.RoleId != conf.BookAdmin && book.RoleId != conf.BookFounder {
 		c.JsonResult(6006,"权限不足")
 	}
+	if doc_id > 0 {
+		doc,err := models.NewDocument().Find(doc_id);
+		if err != nil {
+			c.JsonResult(6007,"文档不存在")
+		}
+		if doc.BookId != book.BookId {
+			c.JsonResult(6008,"文档不属于指定的项目")
+		}
+	}
 
 	fileName := "attachment_" +  strconv.FormatInt(time.Now().UnixNano(), 16)
 
 	filePath := "uploads/" + time.Now().Format("200601") + "/" + fileName + ext
 
+	path := filepath.Dir(filePath)
+
+	os.MkdirAll(path, os.ModePerm)
+
 	err = c.SaveToFile(name,filePath)
 
 	if err != nil {
-		logs.Error("SaveToFile => ",err)
+		beego.Error("SaveToFile => ",err)
 		c.JsonResult(6005,"保存文件失败")
 	}
 	attachment := models.NewAttachment()
@@ -199,6 +220,9 @@ func (c *DocumentController) Upload()  {
 	attachment.CreateAt = c.Member.MemberId
 	attachment.FileExt = ext
 	attachment.FilePath = filePath
+	if doc_id > 0{
+		attachment.DocumentId = doc_id
+	}
 
 	if strings.EqualFold(ext,".jpg") || strings.EqualFold(ext,".jpeg") || strings.EqualFold(ext,"png") || strings.EqualFold(ext,"gif") {
 		attachment.HttpPath = c.BaseUrl() + "/" + filePath
@@ -208,18 +232,20 @@ func (c *DocumentController) Upload()  {
 
 	if err != nil {
 		os.Remove(filePath)
-		logs.Error("Attachment Insert => ",err)
+		beego.Error("Attachment Insert => ",err)
 		c.JsonResult(6006,"文件保存失败")
 	}
 	if attachment.HttpPath == "" {
 		attachment.HttpPath = c.BaseUrl() + beego.URLFor("DocumentController.DownloadAttachment",":key", identify, ":attach_id", attachment.AttachmentId)
 
 		if err := attachment.Update();err != nil {
-			logs.Error("SaveToFile => ",err)
+			beego.Error("SaveToFile => ",err)
 			c.JsonResult(6005,"保存文件失败")
 		}
 	}
+
 	result := map[string]interface{}{
+		"errcode" : 0,
 		"success" : 1,
 		"message" :"ok",
 		"url" : attachment.HttpPath,
@@ -267,7 +293,7 @@ func (c *DocumentController) DownloadAttachment()  {
 	attachment,err := models.NewAttachment().Find(attach_id)
 
 	if err != nil {
-		logs.Error("DownloadAttachment => ", err)
+		beego.Error("DownloadAttachment => ", err)
 		if err == orm.ErrNoRows {
 			c.Abort("404")
 		} else {
@@ -291,7 +317,7 @@ func (c *DocumentController) Delete() {
 	bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
 
 	if err != nil || bookResult.RoleId == conf.BookObserver {
-		logs.Error("FindByIdentify => ",err)
+		beego.Error("FindByIdentify => ",err)
 		c.JsonResult(6002,"项目不存在或权限不足")
 	}
 
@@ -302,7 +328,7 @@ func (c *DocumentController) Delete() {
 	doc,err := models.NewDocument().Find(doc_id)
 
 	if err != nil {
-		logs.Error("Delete => ",err)
+		beego.Error("Delete => ",err)
 		c.JsonResult(6003,"删除失败")
 	}
 	if doc.BookId != bookResult.BookId {
@@ -329,7 +355,7 @@ func (c *DocumentController) Content()  {
 	bookResult,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
 
 	if err != nil || bookResult.RoleId == conf.BookObserver {
-		logs.Error("FindByIdentify => ",err)
+		beego.Error("FindByIdentify => ",err)
 		c.JsonResult(6002,"项目不存在或权限不足")
 	}
 
@@ -352,7 +378,7 @@ func (c *DocumentController) Content()  {
 			c.JsonResult(6004,"保存的文档不属于指定项目")
 		}
 		if doc.Version != version && !strings.EqualFold(is_cover,"yes"){
-			logs.Info("%d|",version,doc.Version)
+			beego.Info("%d|",version,doc.Version)
 			c.JsonResult(6005,"文档已被修改确定要覆盖吗?")
 		}
 		if markdown == "" && content != ""{
@@ -363,7 +389,7 @@ func (c *DocumentController) Content()  {
 		doc.Version = time.Now().Unix()
 		doc.Content = content
 		if err := doc.InsertOrUpdate();err != nil {
-			logs.Error("InsertOrUpdate => ",err)
+			beego.Error("InsertOrUpdate => ",err)
 			c.JsonResult(6006,"保存失败")
 		}
 
@@ -390,7 +416,3 @@ func (c *DocumentController) Content()  {
 
 
 
-
-
-
-

+ 1 - 0
models/attachment.go

@@ -10,6 +10,7 @@ import (
 type Attachment struct {
 	AttachmentId int	`orm:"column(attachment_id);pk;auto;unique" json:"attachment_id"`
 	BookId int		`orm:"column(book_id);type(int)" json:"book_id"`
+	DocumentId int		`orm:"column(document_id);type(int);null" json:"doc_id"`
 	FileName string 	`orm:"column(file_name);size(255)" json:"file_name"`
 	FilePath string		`orm:"column(file_path);size(2000)" json:"file_path"`
 	FileSize float64	`orm:"column(file_size);type(float)" json:"file_size"`

+ 18 - 1
models/book.go

@@ -60,6 +60,8 @@ func NewBook() *Book {
 
 func (m *Book) Insert() error {
 	o := orm.NewOrm()
+	o.Begin()
+
 	_,err := o.Insert(m)
 
 	if err == nil {
@@ -68,8 +70,23 @@ func (m *Book) Insert() error {
 		relationship.RoleId = 0
 		relationship.MemberId = m.MemberId
 		err = relationship.Insert()
+		if err != nil {
+			logs.Error("插入项目与用户关联 => ",err)
+			o.Rollback()
+			return err
+		}
+		document := NewDocument()
+		document.BookId = m.BookId
+		document.DocumentName = "空白文档"
+		document.MemberId = m.MemberId
+		err = document.InsertOrUpdate()
+		if err != nil{
+			o.Rollback()
+			return err
+		}
+		o.Commit()
 	}
-
+	o.Rollback()
 	return err
 }
 

+ 14 - 10
models/document.go

@@ -5,7 +5,7 @@ import (
 
 	"github.com/lifei6671/godoc/conf"
 	"github.com/astaxie/beego/orm"
-	"github.com/astaxie/beego/logs"
+	"github.com/astaxie/beego"
 )
 
 // Document struct.
@@ -15,14 +15,14 @@ type Document struct {
 	// Identify 文档唯一标识
 	Identify string		`orm:"column(identify);size(100);index;null;default(null)" json:"identify"`
 	BookId int		`orm:"column(book_id);type(int);index" json:"book_id"`
-	ParentId int 		`orm:"column(parent_id);type(int);index" json:"parent_id"`
+	ParentId int 		`orm:"column(parent_id);type(int);index;default(0)" json:"parent_id"`
 	OrderSort int		`orm:"column(order_sort);default(0);type(int);index" json:"order_sort"`
 	// Markdown markdown格式文档.
-	Markdown string		`orm:"column(markdown);type(longtext)" json:"markdown"`
+	Markdown string		`orm:"column(markdown);type(text);null" json:"markdown"`
 	// Release 发布后的Html格式内容.
-	Release string		`orm:"column(release);type(longtext)" json:"release"`
+	Release string		`orm:"column(release);type(text);null" json:"release"`
 	// Content 未发布的 Html 格式内容.
-	Content string		`orm:"column(content);type(longtext)" json:"content"`
+	Content string		`orm:"column(content);type(text);null" json:"content"`
 	CreateTime time.Time	`orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
 	MemberId int		`orm:"column(member_id);type(int)" json:"member_id"`
 	ModifyTime time.Time	`orm:"column(modify_time);type(datetime);auto_now" json:"modify_time"`
@@ -45,7 +45,9 @@ func (m *Document) TableNameWithPrefix()  string {
 }
 
 func NewDocument() *Document  {
-	return &Document{}
+	return &Document{
+		Version: time.Now().Unix(),
+	}
 }
 
 func (m *Document) Find(id int) (*Document,error) {
@@ -88,12 +90,16 @@ func (m *Document) RecursiveDocument(doc_id int) error {
 
 	o := orm.NewOrm()
 
+	if doc,err := m.Find(doc_id); err == nil {
+		o.Delete(doc)
+	}
+
 	var docs []*Document
 
 	_,err := o.QueryTable(m.TableNameWithPrefix()).Filter("parent_id",doc_id).All(&docs)
 
 	if err != nil {
-		logs.Error("",err)
+		beego.Error("RecursiveDocument => ",err)
 		return err
 	}
 
@@ -102,9 +108,7 @@ func (m *Document) RecursiveDocument(doc_id int) error {
 		o.QueryTable(m.TableNameWithPrefix()).Filter("document_id",doc_id).Delete()
 		m.RecursiveDocument(doc_id)
 	}
-	if doc,err := m.Find(doc_id); err != nil {
-		o.Delete(doc)
-	}
+
 	return nil
 }
 

+ 1 - 1
models/document_tree.go

@@ -25,7 +25,7 @@ func (m *Document) FindDocumentTree(book_id int) ([]*DocumentTree,error){
 
 	var docs []*Document
 
-	count ,err := o.QueryTable(m).Filter("book_id",book_id).OrderBy("-order_sort","document_id").All(&docs,"document_id","version","document_name","parent_id","identify")
+	count ,err := o.QueryTable(m).Filter("book_id",book_id).OrderBy("order_sort","document_id").All(&docs,"document_id","version","document_name","parent_id","identify")
 
 	if err != nil {
 		return trees,err

+ 2 - 0
routers/router.go

@@ -32,6 +32,8 @@ func init()  {
 	beego.Router("/book/:key/dashboard", &controllers.BookController{},"*:Dashboard")
 	beego.Router("/book/:key/setting", &controllers.BookController{},"*:Setting")
 	beego.Router("/book/:key/users", &controllers.BookController{},"*:Users")
+	beego.Router("/book/:key/release", &controllers.BookController{},"post:Release")
+	beego.Router("/book/:key/sort", &controllers.BookController{},"post:SaveSort")
 
 	beego.Router("/book/create", &controllers.BookController{},"*:Create")
 	beego.Router("/book/users/create", &controllers.BookMemberController{},"post:AddMember")

+ 41 - 2
static/css/markdown.css

@@ -5,6 +5,22 @@ body{
     right: 0;
     bottom: 0;
 }
+::-webkit-scrollbar , body  .scrollbar-track-color{
+    height: 9px;
+    width: 7px;
+    background: #E6E6E6;
+}
+::-webkit-scrollbar:hover {
+    background: #CCCCCC;
+}
+::-webkit-scrollbar-thumb {
+    background: #A2A2A2;
+    -webkit-border-radius: 6px;
+    -moz-border-radius: 6px;
+    -ms-border-radius: 6px;
+    -o-border-radius: 6px;
+    border-radius: 6px;
+}
 .error-message{
     color: red;
 }
@@ -76,9 +92,30 @@ body{
     left: 0;
     right: 0;
 }
-.manual-editor-container .manual-editormd .manual-editormd-active{
+.manual-editor-container .manual-editormd .manual-editormd-active,
+.manual-wangEditor,.manual-wangEditor .wangEditor-container,
+.manual-wangEditor .wangEditor-container .wangEditor-txt{
     position: absolute;
-    top: 0;
+    top:0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+}
+.manual-wangEditor,.manual-wangEditor .wangEditor-container{
+    bottom: 15px;
+    border-top: 0;
+    overflow: hidden;
+}
+.manual-wangEditor .wangEditor-container .wangEditor-txt{
+    top: 32px;
+}
+.manual-wangEditor .wangEditor-container .wangEditor-menu-container{
+    position: fixed;
+    z-index: 10000;
+}
+.manual-wangEditor .wangEditor-container .code-textarea{
+    position: absolute;
+    top: 32px;
     bottom: 0;
     left: 0;
     right: 0;
@@ -148,6 +185,8 @@ body{
     font-style: normal;
 }
 
+
+
 .manual-editor-status{
     position: absolute;
     left: 0;

+ 193 - 0
static/js/edirot.js

@@ -0,0 +1,193 @@
+/**
+ * Created by lifei6671 on 2017/4/29 0029.
+ */
+/**
+ * 保存排序
+ * @param node
+ * @param parent
+ */
+function jstree_save(node, parent) {
+
+    var parentNode = window.treeCatalog.get_node(parent.parent);
+
+    var nodeData = window.getSiblingSort(parentNode);
+
+    if (parent.parent !== parent.old_parent) {
+        parentNode = window.treeCatalog.get_node(parent.old_parent);
+        var newNodeData = window.getSiblingSort(parentNode);
+        if (newNodeData.length > 0) {
+            nodeData = nodeData.concat(newNodeData);
+        }
+    }
+
+    var index = layer.load(1, {
+        shade: [0.1, '#fff'] //0.1透明度的白色背景
+    });
+
+    console.log(JSON.stringify(nodeData));
+
+    $.ajax({
+        url : window.sortURL,
+        type :"post",
+        data : JSON.stringify(nodeData),
+        success : function (res) {
+            layer.close(index);
+            if (res.errcode === 0){
+                layer.msg("保存排序成功");
+            }else{
+                layer.msg(res.message);
+            }
+        }
+    })
+}
+
+/**
+ * 创建文档
+ */
+function openCreateCatalogDialog($node) {
+    var $then =  $("#addDocumentModal");
+
+    var doc_id = $node ? $node.id : 0;
+
+    $then.find("input[name='parent_id']").val(doc_id);
+
+    $then.modal("show");
+}
+
+/**
+ * 处理排序
+ * @param node
+ * @returns {Array}
+ */
+function getSiblingSort (node) {
+    var data = [];
+
+    for(var key in node.children){
+        var index = data.length;
+
+        data[index] = {
+            "id" : parseInt(node.children[key]),
+            "sort" : parseInt(key),
+            "parent" : Number(node.id) ? Number(node.id) : 0
+        };
+    }
+    return data;
+};
+
+/**
+ * 删除一个文档
+ * @param $node
+ */
+function openDeleteDocumentDialog($node) {
+    var index = layer.confirm('你确定要删除该文档吗?', {
+        btn: ['确定','取消'] //按钮
+    }, function(){
+
+        $.post(window.deleteURL,{"identify" : window.book.identify,"doc_id" : $node.id}).done(function (res) {
+            layer.close(index);
+            if(res.errcode === 0){
+                window.treeCatalog.delete_node($node);
+                resetEditor($node);
+            }else{
+                layer.msg("删除失败",{icon : 2})
+            }
+        }).fail(function () {
+            layer.close(index);
+            layer.msg("删除失败",{icon : 2})
+        });
+
+    });
+}
+
+/**
+ * 打开文档编辑界面
+ * @param $node
+ */
+function openEditCatalogDialog($node) {
+    var $then =  $("#addDocumentModal");
+    var doc_id = parseInt($node ? $node.id : 0);
+    var text = $node ? $node.text : '';
+    var parentId = $node && $node.parent !== '#' ? $node.parent : 0;
+
+    $then.find("input[name='doc_id']").val(doc_id);
+    $then.find("input[name='parent_id']").val(parentId);
+    $then.find("input[name='doc_name']").val(text);
+
+    for (var index in window.documentCategory){
+        var item = window.documentCategory[index];
+        if(item.id === doc_id){
+            $then.find("input[name='doc_identify']").val(item.identify);
+            break;
+        }
+    }
+
+    $then.modal({ show : true });
+}
+
+/**
+ * 将一个节点推送到现有数组中
+ * @param $node
+ */
+function pushDocumentCategory($node) {
+    for (var index in window.documentCategory){
+        var item = window.documentCategory[index];
+        if(item.id === $node.id){
+
+            window.documentCategory[index] = $node;
+            console.log( window.documentCategory[index]);
+            return;
+        }
+    }
+    window.documentCategory.push($node);
+}
+
+//实现小提示
+$("[data-toggle='tooltip']").hover(function () {
+    var title = $(this).attr('data-title');
+    var direction = $(this).attr("data-direction");
+    var tips = 3;
+    if(direction === "top"){
+        tips = 1;
+    }else if(direction === "right"){
+        tips = 2;
+    }else if(direction === "bottom"){
+        tips = 3;
+    }else if(direction === "left"){
+        tips = 4;
+    }
+    index = layer.tips(title, this, {
+        tips: tips
+    });
+}, function () {
+    layer.close(index);
+});
+//弹出创建文档的遮罩层
+$("#btnAddDocument").on("click",function () {
+    $("#addDocumentModal").modal("show");
+});
+//用于还原创建文档的遮罩层
+$("#addDocumentModal").on("hidden.bs.modal",function () {
+    $(this).find("form").html(window.addDocumentModalFormHtml);
+}).on("shown.bs.modal",function () {
+    $(this).find("input[name='doc_name']").focus();
+});
+
+function showError($msg,$id) {
+    if(!$id){
+        $id = "#form-error-message"
+    }
+    $($id).addClass("error-message").removeClass("success-message").text($msg);
+    return false;
+}
+
+function showSuccess($msg,$id) {
+    if(!$id){
+        $id = "#form-error-message"
+    }
+    $($id).addClass("success-message").removeClass("error-message").text($msg);
+    return true;
+}
+
+
+
+

+ 254 - 0
static/js/html-editor.js

@@ -0,0 +1,254 @@
+$(function () {
+
+    window.addDocumentModalFormHtml = $(this).find("form").html();
+    wangEditor.config.printLog = false;
+    window.editor = new wangEditor('htmlEditor');
+    editor.config.uploadImgUrl = window.imageUploadURL;
+    editor.config.uploadImgFileName = "editormd-file-file";
+    editor.config.uploadParams = {
+        "editor" : "wangEditor"
+    };
+    wangEditor.config.menus.splice(0,0,"|");
+    wangEditor.config.menus.splice(0,0,"save");
+
+    window.editor.ready(function () {
+        if(window.documentCategory.length > 0){
+            var item =  window.documentCategory[0];
+            var $select_node = { node : {id : item.id}};
+            loadDocument($select_node);
+        }
+
+    });
+
+    window.editor.config.uploadImgFns.onload = function (resultText, xhr) {
+        // resultText 服务器端返回的text
+        // xhr 是 xmlHttpRequest 对象,IE8、9中不支持
+
+        // 上传图片时,已经将图片的名字存在 editor.uploadImgOriginalName
+        var originalName = editor.uploadImgOriginalName || '';
+
+        var res = jQuery.parseJSON(resultText);
+        if (res.errcode === 0){
+            editor.command(null, 'insertHtml', '<img src="' + res.url + '" alt="' + res.alt + '" style="max-width:100%;"/>');
+        }else{
+            layer.msg(res.message);
+        }
+    };
+
+
+    window.editor.create();
+
+
+    $("#htmlEditor").css("height","100%");
+
+
+
+    /***
+     * 加载指定的文档到编辑器中
+     * @param $node
+     */
+    function loadDocument($node) {
+        var index = layer.load(1, {
+            shade: [0.1,'#fff'] //0.1透明度的白色背景
+        });
+
+        $.get(window.editURL + $node.node.id ).done(function (res) {
+            layer.close(index);
+
+            if(res.errcode === 0){
+                window.isLoad = true;
+                window.editor.clear();
+                window.editor.$txt.html(res.data.content);
+                var node = { "id" : res.data.doc_id,'parent' : res.data.parent_id === 0 ? '#' : res.data.parent_id ,"text" : res.data.doc_name,"identify" : res.data.identify,"version" : res.data.version};
+                pushDocumentCategory(node);
+                window.selectNode = node;
+
+            }else{
+                layer.msg("文档加载失败");
+            }
+        }).fail(function () {
+            layer.close(index);
+            layer.msg("文档加载失败");
+        });
+    }
+
+    /**
+     * 保存文档到服务器
+     * @param $is_cover 是否强制覆盖
+     */
+    function saveDocument($is_cover,callback) {
+        var index = null;
+        var node = window.selectNode;
+
+        var html = window.editor.$txt.html() ;
+
+        var content = "";
+        if($.trim(html) !== ""){
+            content = toMarkdown(html, { gfm: true });
+        }
+        var version = "";
+
+        if(!node){
+            layer.msg("获取当前文档信息失败");
+            return;
+        }
+        var doc_id = parseInt(node.id);
+
+        for(var i in window.documentCategory){
+            var item = window.documentCategory[i];
+
+            if(item.id === doc_id){
+                version = item.version;
+                break;
+            }
+        }
+        $.ajax({
+            beforeSend  : function () {
+                index = layer.load(1, {shade: [0.1,'#fff'] });
+            },
+            url :  window.editURL,
+            data : {"identify" : window.book.identify,"doc_id" : doc_id,"markdown" : content,"html" : html,"cover" : $is_cover ? "yes":"no","version": version},
+            type :"post",
+            dataType :"json",
+            success : function (res) {
+                layer.close(index);
+                if(res.errcode === 0){
+                    for(var i in window.documentCategory){
+                        var item = window.documentCategory[i];
+
+                        if(item.id === doc_id){
+                            window.documentCategory[i].version = res.data.version;
+                            break;
+                        }
+                    }
+                    if(typeof callback === "function"){
+                        callback();
+                    }
+                }else if(res.errcode === 6005){
+                    var confirmIndex = layer.confirm('文档已被其他人修改确定覆盖已存在的文档吗?', {
+                        btn: ['确定','取消'] //按钮
+                    }, function(){
+                        layer.close(confirmIndex);
+                        saveDocument(true,callback);
+                    });
+                }else{
+                    layer.msg(res.message);
+                }
+            }
+        });
+    }
+
+
+    /**
+     * 添加顶级文档
+     */
+    $("#addDocumentForm").ajaxForm({
+        beforeSubmit : function () {
+            var doc_name = $.trim($("#documentName").val());
+            if (doc_name === ""){
+                return showError("目录名称不能为空","#add-error-message")
+            }
+            window.addDocumentFormIndex = layer.load(1, { shade: [0.1,'#fff']  });
+            return true;
+        },
+        success : function (res) {
+            if(res.errcode === 0){
+
+                var data = { "id" : res.data.doc_id,'parent' : res.data.parent_id === 0 ? '#' : res.data.parent_id ,"text" : res.data.doc_name,"identify" : res.data.identify,"version" : res.data.version};
+
+                var node = window.treeCatalog.get_node(data.id);
+                if(node){
+                    window.treeCatalog.rename_node({"id":data.id},data.text);
+
+                }else {
+                    window.treeCatalog.create_node(data.parent, data);
+                    window.treeCatalog.deselect_all();
+                    window.treeCatalog.select_node(data);
+                }
+                pushDocumentCategory(data);
+                $("#markdown-save").removeClass('change').addClass('disabled');
+                $("#addDocumentModal").modal('hide');
+            }else{
+                showError(res.message,"#add-error-message")
+            }
+            layer.close(window.addDocumentFormIndex);
+        }
+    });
+
+    /**
+     * 文档目录树
+     */
+    $("#sidebar").jstree({
+        'plugins': ["wholerow", "types", 'dnd', 'contextmenu'],
+        "types": {
+            "default": {
+                "icon": false  // 删除默认图标
+            }
+        },
+        'core': {
+            'check_callback': true,
+            "multiple": false,
+            'animation': 0,
+            "data": window.documentCategory
+        },
+        "contextmenu": {
+            show_at_node: false,
+            select_node: false,
+            "items": {
+                "添加文档": {
+                    "separator_before": false,
+                    "separator_after": true,
+                    "_disabled": false,
+                    "label": "添加文档",
+                    "icon": "fa fa-plus",
+                    "action": function (data) {
+
+                        var inst = $.jstree.reference(data.reference),
+                            node = inst.get_node(data.reference);
+
+                        openCreateCatalogDialog(node);
+                    }
+                },
+                "编辑": {
+                    "separator_before": false,
+                    "separator_after": true,
+                    "_disabled": false,
+                    "label": "编辑",
+                    "icon": "fa fa-edit",
+                    "action": function (data) {
+                        var inst = $.jstree.reference(data.reference);
+                        var node = inst.get_node(data.reference);
+                        openEditCatalogDialog(node);
+                    }
+                },
+                "删除": {
+                    "separator_before": false,
+                    "separator_after": true,
+                    "_disabled": false,
+                    "label": "删除",
+                    "icon": "fa fa-trash-o",
+                    "action": function (data) {
+                        var inst = $.jstree.reference(data.reference);
+                        var node = inst.get_node(data.reference);
+                        openDeleteDocumentDialog(node);
+                    }
+                }
+            }
+        }
+    }).on('loaded.jstree', function () {
+        window.treeCatalog = $(this).jstree();
+    }).on('select_node.jstree', function (node, selected, event) {
+        if($("#markdown-save").hasClass('change')) {
+            if(confirm("编辑内容未保存,需要保存吗?")){
+                saveDocument(false,function () {
+                    loadDocument(selected);
+                });
+                return true;
+            }
+        }
+        loadDocument(selected);
+
+    }).on("move_node.jstree", jstree_save);
+
+    window.saveDocument = saveDocument;
+});

+ 47 - 163
static/js/markdown.js

@@ -1,18 +1,4 @@
-function showError($msg,$id) {
-    if(!$id){
-        $id = "#form-error-message"
-    }
-    $($id).addClass("error-message").removeClass("success-message").text($msg);
-    return false;
-}
 
-function showSuccess($msg,$id) {
-    if(!$id){
-        $id = "#form-error-message"
-    }
-    $($id).addClass("success-message").removeClass("error-message").text($msg);
-    return true;
-}
 
 $(function () {
     window.addDocumentModalFormHtml = $(this).find("form").html();
@@ -32,12 +18,24 @@ $(function () {
         flowChart : true,
         htmlDecode : "style,script,iframe,title,onmouseover,onmouseout,style",
         lineNumbers : false,
-
         tocStartLevel : 1,
         tocm : true,
         saveHTMLToTextarea : true,
         onload : function() {
             this.hideToolbar();
+            var keyMap = {
+                "Ctrl-S": function(cm) {
+                    saveDocument(false);
+                },
+                "Cmd-S" : function(cm){
+                    saveDocument(false);
+                },
+                "Ctrl-A": function(cm) {
+                    cm.execCommand("selectAll");
+                }
+            };
+            this.addKeyMap(keyMap);
+
             var $select_node_id = window.treeCatalog.get_selected();
             if($select_node_id) {
                 var $select_node = window.treeCatalog.get_node($select_node_id[0])
@@ -67,7 +65,6 @@ $(function () {
        }else if(name === "history"){
 
        }else if(name === "save"){
-
             saveDocument(false);
 
        }else if(name === "sidebar"){
@@ -84,7 +81,22 @@ $(function () {
                 window.editor.resize();
             });
        }else if(name === "release"){
-
+            if(Object.prototype.toString.call(window.documentCategory) === '[object Array]' && window.documentCategory.length > 0){
+                $.ajax({
+                    url : window.releaseURL,
+                    type : "post",
+                    dataType : "json",
+                    success : function (res) {
+                        if(res.errcode === 0){
+                            layer.msg("发布任务已推送到任务队列,稍后将在后台执行。");
+                        }else{
+                            layer.msg(res.message);
+                        }
+                    }
+                });
+            }else{
+                layer.msg("没有需要发布的文档")
+            }
        }else if(name === "tasks") {
            //插入GFM任务列表
            var cm = window.editor.cm;
@@ -111,36 +123,6 @@ $(function () {
        }
    }) ;
 
-    //实现小提示
-    $("[data-toggle='tooltip']").hover(function () {
-        var title = $(this).attr('data-title');
-        var direction = $(this).attr("data-direction");
-        var tips = 3;
-        if(direction === "top"){
-            tips = 1;
-        }else if(direction === "right"){
-            tips = 2;
-        }else if(direction === "bottom"){
-            tips = 3;
-        }else if(direction === "left"){
-            tips = 4;
-        }
-        index = layer.tips(title, this, {
-            tips: tips
-        });
-    }, function () {
-        layer.close(index);
-    });
-
-    $("#btnAddDocument").on("click",function () {
-        $("#addDocumentModal").modal("show");
-    });
-    $("#addDocumentModal").on("hidden.bs.modal",function () {
-       $(this).find("form").html(window.addDocumentModalFormHtml);
-    }).on("shown.bs.modal",function () {
-        $(this).find("input[name='doc_name']").focus();
-    });
-
     /***
      * 加载指定的文档到编辑器中
      * @param $node
@@ -173,95 +155,16 @@ $(function () {
     }
 
     /**
-     * 创建文档
-     */
-    function openCreateCatalogDialog($node) {
-        var $then =  $("#addDocumentModal");
-
-        var doc_id = $node ? $node.id : 0;
-
-        $then.find("input[name='parent_id']").val(doc_id);
-
-        $then.modal("show");
-    }
-
-    /**
-     * 将一个节点推送到现有数组中
-     * @param $node
-     */
-    function pushDocumentCategory($node) {
-        for (var index in window.documentCategory){
-            var item = window.documentCategory[index];
-            if(item.id === $node.id){
-
-               window.documentCategory[index] = $node;
-                console.log( window.documentCategory[index]);
-               return;
-            }
-        }
-        window.documentCategory.push($node);
-    }
-
-    /**
-     * 打开文档编辑界面
-     * @param $node
-     */
-    function openEditCatalogDialog($node) {
-        var $then =  $("#addDocumentModal");
-        var doc_id = parseInt($node ? $node.id : 0);
-        var text = $node ? $node.text : '';
-        var parentId = $node && $node.parent !== '#' ? $node.parent : 0;
-
-        $then.find("input[name='doc_id']").val(doc_id);
-        $then.find("input[name='parent_id']").val(parentId);
-        $then.find("input[name='doc_name']").val(text);
-
-        for (var index in window.documentCategory){
-            var item = window.documentCategory[index];
-            if(item.id === doc_id){
-                $then.find("input[name='doc_identify']").val(item.identify);
-                break;
-            }
-        }
-
-        $then.modal({ show : true });
-    }
-
-    /**
-     * 删除一个文档
-     * @param $node
+     * 保存文档到服务器
+     * @param $is_cover 是否强制覆盖
      */
-    function openDeleteDocumentDialog($node) {
-        var index = layer.confirm('你确定要删除该文档吗?', {
-            btn: ['确定','取消'] //按钮
-        }, function(){
-
-            $.post(window.deleteURL,{"identify" : window.book.identify,"doc_id" : $node.id}).done(function (res) {
-                layer.close(index);
-                if(res.errcode === 0){
-                    window.treeCatalog.delete_node($node);
-                    resetEditor($node);
-                }else{
-                    layer.msg("删除失败",{icon : 2})
-                }
-            }).fail(function () {
-                layer.close(index);
-                layer.msg("删除失败",{icon : 2})
-            });
-
-        });
-    }
-
-    function saveDocument($is_cover) {
+    function saveDocument($is_cover,callback) {
         var index = null;
         var node = window.selectNode;
         var content = window.editor.getMarkdown();
         var html = window.editor.getPreviewedHTML();
         var version = "";
-        if(content === ""){
-            resetEditorChanged(false);
-            return;
-        }
+
         if(!node){
             layer.msg("获取当前文档信息失败");
             return;
@@ -297,12 +200,15 @@ $(function () {
                             break;
                         }
                     }
+                    if(typeof callback === "function"){
+                        callback();
+                    }
                 }else if(res.errcode === 6005){
-                    var confirmIndex = layer.confirm('你确定要删除该文档吗?', {
+                    var confirmIndex = layer.confirm('文档已被其他人修改确定覆盖已存在的文档吗?', {
                         btn: ['确定','取消'] //按钮
                     }, function(){
                         layer.close(confirmIndex);
-                        saveDocument(true);
+                        saveDocument(true,callback);
                     });
                 }else{
                     layer.msg(res.message);
@@ -315,6 +221,10 @@ $(function () {
 
     }
 
+    /**
+     * 设置编辑器变更状态
+     * @param $is_change
+     */
     function resetEditorChanged($is_change) {
         if($is_change && !window.isLoad ){
             $("#markdown-save").removeClass('disabled').addClass('change');
@@ -424,39 +334,13 @@ $(function () {
     }).on('select_node.jstree', function (node, selected, event) {
         if($("#markdown-save").hasClass('change')) {
             if(confirm("编辑内容未保存,需要保存吗?")){
-                saveDocument();
+                saveDocument(false,function () {
+                    loadDocument(selected);
+                });
+                return true;
             }
         }
         loadDocument(selected);
 
-    }).on("move_node.jstree", function (node, parent) {
-
-        var parentNode = window.treeCatalog.get_node(parent.parent);
-
-        var nodeData = window.getSiblingSort(parentNode);
-
-        if (parent.parent != parent.old_parent) {
-            parentNode = window.treeCatalog.get_node(parent.old_parent);
-            var newNodeData = window.getSiblingSort(parentNode);
-            if (newNodeData.length > 0) {
-                nodeData = nodeData.concat(newNodeData);
-            }
-        }
-
-        var index = layer.load(1, {
-            shade: [0.1, '#fff'] //0.1透明度的白色背景
-        });
-
-        $.post("https://wiki.iminho.me/docs/sort/2", JSON.stringify(nodeData)).done(function (res) {
-            layer.close(index);
-            if (res.errcode != 0) {
-                layer.msg(res.message);
-            } else {
-                layer.msg("保存排序成功");
-            }
-        }).fail(function () {
-            layer.close(index);
-            layer.msg("保存排序失败");
-        });
-    });
+    }).on("move_node.jstree",jstree_save);
 });

+ 789 - 0
static/to-markdown/dist/to-markdown.js

@@ -0,0 +1,789 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.toMarkdown = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*
+ * to-markdown - an HTML to Markdown converter
+ *
+ * Copyright 2011+, Dom Christie
+ * Licenced under the MIT licence
+ *
+ */
+
+'use strict'
+
+var toMarkdown
+var converters
+var mdConverters = require('./lib/md-converters')
+var gfmConverters = require('./lib/gfm-converters')
+var HtmlParser = require('./lib/html-parser')
+var collapse = require('collapse-whitespace')
+
+/*
+ * Utilities
+ */
+
+var blocks = ['address', 'article', 'aside', 'audio', 'blockquote', 'body',
+  'canvas', 'center', 'dd', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption',
+  'figure', 'footer', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
+  'header', 'hgroup', 'hr', 'html', 'isindex', 'li', 'main', 'menu', 'nav',
+  'noframes', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table',
+  'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul'
+]
+
+function isBlock (node) {
+  return blocks.indexOf(node.nodeName.toLowerCase()) !== -1
+}
+
+var voids = [
+  'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
+  'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
+]
+
+function isVoid (node) {
+  return voids.indexOf(node.nodeName.toLowerCase()) !== -1
+}
+
+function htmlToDom (string) {
+  var tree = new HtmlParser().parseFromString(string, 'text/html')
+  collapse(tree.documentElement, isBlock)
+  return tree
+}
+
+/*
+ * Flattens DOM tree into single array
+ */
+
+function bfsOrder (node) {
+  var inqueue = [node]
+  var outqueue = []
+  var elem
+  var children
+  var i
+
+  while (inqueue.length > 0) {
+    elem = inqueue.shift()
+    outqueue.push(elem)
+    children = elem.childNodes
+    for (i = 0; i < children.length; i++) {
+      if (children[i].nodeType === 1) inqueue.push(children[i])
+    }
+  }
+  outqueue.shift()
+  return outqueue
+}
+
+/*
+ * Contructs a Markdown string of replacement text for a given node
+ */
+
+function getContent (node) {
+  var text = ''
+  for (var i = 0; i < node.childNodes.length; i++) {
+    if (node.childNodes[i].nodeType === 1) {
+      text += node.childNodes[i]._replacement
+    } else if (node.childNodes[i].nodeType === 3) {
+      text += node.childNodes[i].data
+    } else continue
+  }
+  return text
+}
+
+/*
+ * Returns the HTML string of an element with its contents converted
+ */
+
+function outer (node, content) {
+  return node.cloneNode(false).outerHTML.replace('><', '>' + content + '<')
+}
+
+function canConvert (node, filter) {
+  if (typeof filter === 'string') {
+    return filter === node.nodeName.toLowerCase()
+  }
+  if (Array.isArray(filter)) {
+    return filter.indexOf(node.nodeName.toLowerCase()) !== -1
+  } else if (typeof filter === 'function') {
+    return filter.call(toMarkdown, node)
+  } else {
+    throw new TypeError('`filter` needs to be a string, array, or function')
+  }
+}
+
+function isFlankedByWhitespace (side, node) {
+  var sibling
+  var regExp
+  var isFlanked
+
+  if (side === 'left') {
+    sibling = node.previousSibling
+    regExp = / $/
+  } else {
+    sibling = node.nextSibling
+    regExp = /^ /
+  }
+
+  if (sibling) {
+    if (sibling.nodeType === 3) {
+      isFlanked = regExp.test(sibling.nodeValue)
+    } else if (sibling.nodeType === 1 && !isBlock(sibling)) {
+      isFlanked = regExp.test(sibling.textContent)
+    }
+  }
+  return isFlanked
+}
+
+function flankingWhitespace (node, content) {
+  var leading = ''
+  var trailing = ''
+
+  if (!isBlock(node)) {
+    var hasLeading = /^[ \r\n\t]/.test(content)
+    var hasTrailing = /[ \r\n\t]$/.test(content)
+
+    if (hasLeading && !isFlankedByWhitespace('left', node)) {
+      leading = ' '
+    }
+    if (hasTrailing && !isFlankedByWhitespace('right', node)) {
+      trailing = ' '
+    }
+  }
+
+  return { leading: leading, trailing: trailing }
+}
+
+/*
+ * Finds a Markdown converter, gets the replacement, and sets it on
+ * `_replacement`
+ */
+
+function process (node) {
+  var replacement
+  var content = getContent(node)
+
+  // Remove blank nodes
+  if (!isVoid(node) && !/A|TH|TD/.test(node.nodeName) && /^\s*$/i.test(content)) {
+    node._replacement = ''
+    return
+  }
+
+  for (var i = 0; i < converters.length; i++) {
+    var converter = converters[i]
+
+    if (canConvert(node, converter.filter)) {
+      if (typeof converter.replacement !== 'function') {
+        throw new TypeError(
+          '`replacement` needs to be a function that returns a string'
+        )
+      }
+
+      var whitespace = flankingWhitespace(node, content)
+
+      if (whitespace.leading || whitespace.trailing) {
+        content = content.trim()
+      }
+      replacement = whitespace.leading +
+        converter.replacement.call(toMarkdown, content, node) +
+        whitespace.trailing
+      break
+    }
+  }
+
+  node._replacement = replacement
+}
+
+toMarkdown = function (input, options) {
+  options = options || {}
+
+  if (typeof input !== 'string') {
+    throw new TypeError(input + ' is not a string')
+  }
+
+  if (input === '') {
+    return ''
+  }
+
+  // Escape potential ol triggers
+  input = input.replace(/(\d+)\. /g, '$1\\. ')
+
+  var clone = htmlToDom(input).body
+  var nodes = bfsOrder(clone)
+  var output
+
+  converters = mdConverters.slice(0)
+  if (options.gfm) {
+    converters = gfmConverters.concat(converters)
+  }
+
+  if (options.converters) {
+    converters = options.converters.concat(converters)
+  }
+
+  // Process through nodes in reverse (so deepest child elements are first).
+  for (var i = nodes.length - 1; i >= 0; i--) {
+    process(nodes[i])
+  }
+  output = getContent(clone)
+
+  return output.replace(/^[\t\r\n]+|[\t\r\n\s]+$/g, '')
+    .replace(/\n\s+\n/g, '\n\n')
+    .replace(/\n{3,}/g, '\n\n')
+}
+
+toMarkdown.isBlock = isBlock
+toMarkdown.isVoid = isVoid
+toMarkdown.outer = outer
+
+module.exports = toMarkdown
+
+},{"./lib/gfm-converters":2,"./lib/html-parser":3,"./lib/md-converters":4,"collapse-whitespace":7}],2:[function(require,module,exports){
+'use strict'
+
+function cell (content, node) {
+  var index = Array.prototype.indexOf.call(node.parentNode.childNodes, node)
+  var prefix = ' '
+  if (index === 0) prefix = '| '
+  return prefix + content + ' |'
+}
+
+var highlightRegEx = /highlight highlight-(\S+)/
+
+module.exports = [
+  {
+    filter: 'br',
+    replacement: function () {
+      return '\n'
+    }
+  },
+  {
+    filter: ['del', 's', 'strike'],
+    replacement: function (content) {
+      return '~~' + content + '~~'
+    }
+  },
+
+  {
+    filter: function (node) {
+      return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'
+    },
+    replacement: function (content, node) {
+      return (node.checked ? '[x]' : '[ ]') + ' '
+    }
+  },
+
+  {
+    filter: ['th', 'td'],
+    replacement: function (content, node) {
+      return cell(content, node)
+    }
+  },
+
+  {
+    filter: 'tr',
+    replacement: function (content, node) {
+      var borderCells = ''
+      var alignMap = { left: ':--', right: '--:', center: ':-:' }
+
+      if (node.parentNode.nodeName === 'THEAD') {
+        for (var i = 0; i < node.childNodes.length; i++) {
+          var align = node.childNodes[i].attributes.align
+          var border = '---'
+
+          if (align) border = alignMap[align.value] || border
+
+          borderCells += cell(border, node.childNodes[i])
+        }
+      }
+      return '\n' + content + (borderCells ? '\n' + borderCells : '')
+    }
+  },
+
+  {
+    filter: 'table',
+    replacement: function (content) {
+      return '\n\n' + content + '\n\n'
+    }
+  },
+
+  {
+    filter: ['thead', 'tbody', 'tfoot'],
+    replacement: function (content) {
+      return content
+    }
+  },
+
+  // Fenced code blocks
+  {
+    filter: function (node) {
+      return node.nodeName === 'PRE' &&
+      node.firstChild &&
+      node.firstChild.nodeName === 'CODE'
+    },
+    replacement: function (content, node) {
+      return '\n\n```\n' + node.firstChild.textContent + '\n```\n\n'
+    }
+  },
+
+  // Syntax-highlighted code blocks
+  {
+    filter: function (node) {
+      return node.nodeName === 'PRE' &&
+      node.parentNode.nodeName === 'DIV' &&
+      highlightRegEx.test(node.parentNode.className)
+    },
+    replacement: function (content, node) {
+      var language = node.parentNode.className.match(highlightRegEx)[1]
+      return '\n\n```' + language + '\n' + node.textContent + '\n```\n\n'
+    }
+  },
+
+  {
+    filter: function (node) {
+      return node.nodeName === 'DIV' &&
+      highlightRegEx.test(node.className)
+    },
+    replacement: function (content) {
+      return '\n\n' + content + '\n\n'
+    }
+  }
+]
+
+},{}],3:[function(require,module,exports){
+/*
+ * Set up window for Node.js
+ */
+
+var _window = (typeof window !== 'undefined' ? window : this)
+
+/*
+ * Parsing HTML strings
+ */
+
+function canParseHtmlNatively () {
+  var Parser = _window.DOMParser
+  var canParse = false
+
+  // Adapted from https://gist.github.com/1129031
+  // Firefox/Opera/IE throw errors on unsupported types
+  try {
+    // WebKit returns null on unsupported types
+    if (new Parser().parseFromString('', 'text/html')) {
+      canParse = true
+    }
+  } catch (e) {}
+
+  return canParse
+}
+
+function createHtmlParser () {
+  var Parser = function () {}
+
+  // For Node.js environments
+  if (typeof document === 'undefined') {
+    var jsdom = require('jsdom')
+    Parser.prototype.parseFromString = function (string) {
+      return jsdom.jsdom(string, {
+        features: {
+          FetchExternalResources: [],
+          ProcessExternalResources: false
+        }
+      })
+    }
+  } else {
+    if (!shouldUseActiveX()) {
+      Parser.prototype.parseFromString = function (string) {
+        var doc = document.implementation.createHTMLDocument('')
+        doc.open()
+        doc.write(string)
+        doc.close()
+        return doc
+      }
+    } else {
+      Parser.prototype.parseFromString = function (string) {
+        var doc = new window.ActiveXObject('htmlfile')
+        doc.designMode = 'on' // disable on-page scripts
+        doc.open()
+        doc.write(string)
+        doc.close()
+        return doc
+      }
+    }
+  }
+  return Parser
+}
+
+function shouldUseActiveX () {
+  var useActiveX = false
+
+  try {
+    document.implementation.createHTMLDocument('').open()
+  } catch (e) {
+    if (window.ActiveXObject) useActiveX = true
+  }
+
+  return useActiveX
+}
+
+module.exports = canParseHtmlNatively() ? _window.DOMParser : createHtmlParser()
+
+},{"jsdom":6}],4:[function(require,module,exports){
+'use strict'
+
+module.exports = [
+  {
+    filter: 'p',
+    replacement: function (content) {
+      return '\n\n' + content + '\n\n'
+    }
+  },
+
+  {
+    filter: 'br',
+    replacement: function () {
+      return '  \n'
+    }
+  },
+
+  {
+    filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+    replacement: function (content, node) {
+      var hLevel = node.nodeName.charAt(1)
+      var hPrefix = ''
+      for (var i = 0; i < hLevel; i++) {
+        hPrefix += '#'
+      }
+      return '\n\n' + hPrefix + ' ' + content + '\n\n'
+    }
+  },
+
+  {
+    filter: 'hr',
+    replacement: function () {
+      return '\n\n* * *\n\n'
+    }
+  },
+
+  {
+    filter: ['em', 'i'],
+    replacement: function (content) {
+      return '_' + content + '_'
+    }
+  },
+
+  {
+    filter: ['strong', 'b'],
+    replacement: function (content) {
+      return '**' + content + '**'
+    }
+  },
+
+  // Inline code
+  {
+    filter: function (node) {
+      var hasSiblings = node.previousSibling || node.nextSibling
+      var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings
+
+      return node.nodeName === 'CODE' && !isCodeBlock
+    },
+    replacement: function (content) {
+      return '`' + content + '`'
+    }
+  },
+
+  {
+    filter: function (node) {
+      return node.nodeName === 'A' && node.getAttribute('href')
+    },
+    replacement: function (content, node) {
+      var titlePart = node.title ? ' "' + node.title + '"' : ''
+      return '[' + content + '](' + node.getAttribute('href') + titlePart + ')'
+    }
+  },
+
+  {
+    filter: 'img',
+    replacement: function (content, node) {
+      var alt = node.alt || ''
+      var src = node.getAttribute('src') || ''
+      var title = node.title || ''
+      var titlePart = title ? ' "' + title + '"' : ''
+      return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
+    }
+  },
+
+  // Code blocks
+  {
+    filter: function (node) {
+      return node.nodeName === 'PRE' && node.firstChild.nodeName === 'CODE'
+    },
+    replacement: function (content, node) {
+      return '\n\n    ' + node.firstChild.textContent.replace(/\n/g, '\n    ') + '\n\n'
+    }
+  },
+
+  {
+    filter: 'blockquote',
+    replacement: function (content) {
+      content = content.trim()
+      content = content.replace(/\n{3,}/g, '\n\n')
+      content = content.replace(/^/gm, '> ')
+      return '\n\n' + content + '\n\n'
+    }
+  },
+
+  {
+    filter: 'li',
+    replacement: function (content, node) {
+      content = content.replace(/^\s+/, '').replace(/\n/gm, '\n    ')
+      var prefix = '*   '
+      var parent = node.parentNode
+      var index = Array.prototype.indexOf.call(parent.children, node) + 1
+
+      prefix = /ol/i.test(parent.nodeName) ? index + '.  ' : '*   '
+      return prefix + content
+    }
+  },
+
+  {
+    filter: ['ul', 'ol'],
+    replacement: function (content, node) {
+      var strings = []
+      for (var i = 0; i < node.childNodes.length; i++) {
+        strings.push(node.childNodes[i]._replacement)
+      }
+
+      if (/li/i.test(node.parentNode.nodeName)) {
+        return '\n' + strings.join('\n')
+      }
+      return '\n\n' + strings.join('\n') + '\n\n'
+    }
+  },
+
+  {
+    filter: function (node) {
+      return this.isBlock(node)
+    },
+    replacement: function (content, node) {
+      return '\n\n' + this.outer(node, content) + '\n\n'
+    }
+  },
+
+  // Anything else!
+  {
+    filter: function () {
+      return true
+    },
+    replacement: function (content, node) {
+      return this.outer(node, content)
+    }
+  }
+]
+
+},{}],5:[function(require,module,exports){
+/**
+ * This file automatically generated from `build.js`.
+ * Do not manually edit.
+ */
+
+module.exports = [
+  "address",
+  "article",
+  "aside",
+  "audio",
+  "blockquote",
+  "canvas",
+  "dd",
+  "div",
+  "dl",
+  "fieldset",
+  "figcaption",
+  "figure",
+  "footer",
+  "form",
+  "h1",
+  "h2",
+  "h3",
+  "h4",
+  "h5",
+  "h6",
+  "header",
+  "hgroup",
+  "hr",
+  "main",
+  "nav",
+  "noscript",
+  "ol",
+  "output",
+  "p",
+  "pre",
+  "section",
+  "table",
+  "tfoot",
+  "ul",
+  "video"
+];
+
+},{}],6:[function(require,module,exports){
+
+},{}],7:[function(require,module,exports){
+'use strict';
+
+var voidElements = require('void-elements');
+Object.keys(voidElements).forEach(function (name) {
+  voidElements[name.toUpperCase()] = 1;
+});
+
+var blockElements = {};
+require('block-elements').forEach(function (name) {
+  blockElements[name.toUpperCase()] = 1;
+});
+
+/**
+ * isBlockElem(node) determines if the given node is a block element.
+ *
+ * @param {Node} node
+ * @return {Boolean}
+ */
+function isBlockElem(node) {
+  return !!(node && blockElements[node.nodeName]);
+}
+
+/**
+ * isVoid(node) determines if the given node is a void element.
+ *
+ * @param {Node} node
+ * @return {Boolean}
+ */
+function isVoid(node) {
+  return !!(node && voidElements[node.nodeName]);
+}
+
+/**
+ * whitespace(elem [, isBlock]) removes extraneous whitespace from an
+ * the given element. The function isBlock may optionally be passed in
+ * to determine whether or not an element is a block element; if none
+ * is provided, defaults to using the list of block elements provided
+ * by the `block-elements` module.
+ *
+ * @param {Node} elem
+ * @param {Function} blockTest
+ */
+function collapseWhitespace(elem, isBlock) {
+  if (!elem.firstChild || elem.nodeName === 'PRE') return;
+
+  if (typeof isBlock !== 'function') {
+    isBlock = isBlockElem;
+  }
+
+  var prevText = null;
+  var prevVoid = false;
+
+  var prev = null;
+  var node = next(prev, elem);
+
+  while (node !== elem) {
+    if (node.nodeType === 3) {
+      // Node.TEXT_NODE
+      var text = node.data.replace(/[ \r\n\t]+/g, ' ');
+
+      if ((!prevText || / $/.test(prevText.data)) && !prevVoid && text[0] === ' ') {
+        text = text.substr(1);
+      }
+
+      // `text` might be empty at this point.
+      if (!text) {
+        node = remove(node);
+        continue;
+      }
+
+      node.data = text;
+      prevText = node;
+    } else if (node.nodeType === 1) {
+      // Node.ELEMENT_NODE
+      if (isBlock(node) || node.nodeName === 'BR') {
+        if (prevText) {
+          prevText.data = prevText.data.replace(/ $/, '');
+        }
+
+        prevText = null;
+        prevVoid = false;
+      } else if (isVoid(node)) {
+        // Avoid trimming space around non-block, non-BR void elements.
+        prevText = null;
+        prevVoid = true;
+      }
+    } else {
+      node = remove(node);
+      continue;
+    }
+
+    var nextNode = next(prev, node);
+    prev = node;
+    node = nextNode;
+  }
+
+  if (prevText) {
+    prevText.data = prevText.data.replace(/ $/, '');
+    if (!prevText.data) {
+      remove(prevText);
+    }
+  }
+}
+
+/**
+ * remove(node) removes the given node from the DOM and returns the
+ * next node in the sequence.
+ *
+ * @param {Node} node
+ * @return {Node} node
+ */
+function remove(node) {
+  var next = node.nextSibling || node.parentNode;
+
+  node.parentNode.removeChild(node);
+
+  return next;
+}
+
+/**
+ * next(prev, current) returns the next node in the sequence, given the
+ * current and previous nodes.
+ *
+ * @param {Node} prev
+ * @param {Node} current
+ * @return {Node}
+ */
+function next(prev, current) {
+  if (prev && prev.parentNode === current || current.nodeName === 'PRE') {
+    return current.nextSibling || current.parentNode;
+  }
+
+  return current.firstChild || current.nextSibling || current.parentNode;
+}
+
+module.exports = collapseWhitespace;
+
+},{"block-elements":5,"void-elements":8}],8:[function(require,module,exports){
+/**
+ * This file automatically generated from `pre-publish.js`.
+ * Do not manually edit.
+ */
+
+module.exports = {
+  "area": true,
+  "base": true,
+  "br": true,
+  "col": true,
+  "embed": true,
+  "hr": true,
+  "img": true,
+  "input": true,
+  "keygen": true,
+  "link": true,
+  "menuitem": true,
+  "meta": true,
+  "param": true,
+  "source": true,
+  "track": true,
+  "wbr": true
+};
+
+},{}]},{},[1])(1)
+});

+ 110 - 0
static/to-markdown/lib/gfm-converters.js

@@ -0,0 +1,110 @@
+'use strict'
+
+function cell (content, node) {
+  var index = Array.prototype.indexOf.call(node.parentNode.childNodes, node)
+  var prefix = ' '
+  if (index === 0) prefix = '| '
+  return prefix + content + ' |'
+}
+
+var highlightRegEx = /highlight highlight-(\S+)/
+
+module.exports = [
+  {
+    filter: 'br',
+    replacement: function () {
+      return '\n'
+    }
+  },
+  {
+    filter: ['del', 's', 'strike'],
+    replacement: function (content) {
+      return '~~' + content + '~~'
+    }
+  },
+
+  {
+    filter: function (node) {
+      return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'
+    },
+    replacement: function (content, node) {
+      return (node.checked ? '[x]' : '[ ]') + ' '
+    }
+  },
+
+  {
+    filter: ['th', 'td'],
+    replacement: function (content, node) {
+      return cell(content, node)
+    }
+  },
+
+  {
+    filter: 'tr',
+    replacement: function (content, node) {
+      var borderCells = ''
+      var alignMap = { left: ':--', right: '--:', center: ':-:' }
+
+      if (node.parentNode.nodeName === 'THEAD') {
+        for (var i = 0; i < node.childNodes.length; i++) {
+          var align = node.childNodes[i].attributes.align
+          var border = '---'
+
+          if (align) border = alignMap[align.value] || border
+
+          borderCells += cell(border, node.childNodes[i])
+        }
+      }
+      return '\n' + content + (borderCells ? '\n' + borderCells : '')
+    }
+  },
+
+  {
+    filter: 'table',
+    replacement: function (content) {
+      return '\n\n' + content + '\n\n'
+    }
+  },
+
+  {
+    filter: ['thead', 'tbody', 'tfoot'],
+    replacement: function (content) {
+      return content
+    }
+  },
+
+  // Fenced code blocks
+  {
+    filter: function (node) {
+      return node.nodeName === 'PRE' &&
+      node.firstChild &&
+      node.firstChild.nodeName === 'CODE'
+    },
+    replacement: function (content, node) {
+      return '\n\n```\n' + node.firstChild.textContent + '\n```\n\n'
+    }
+  },
+
+  // Syntax-highlighted code blocks
+  {
+    filter: function (node) {
+      return node.nodeName === 'PRE' &&
+      node.parentNode.nodeName === 'DIV' &&
+      highlightRegEx.test(node.parentNode.className)
+    },
+    replacement: function (content, node) {
+      var language = node.parentNode.className.match(highlightRegEx)[1]
+      return '\n\n```' + language + '\n' + node.textContent + '\n```\n\n'
+    }
+  },
+
+  {
+    filter: function (node) {
+      return node.nodeName === 'DIV' &&
+      highlightRegEx.test(node.className)
+    },
+    replacement: function (content) {
+      return '\n\n' + content + '\n\n'
+    }
+  }
+]

+ 76 - 0
static/to-markdown/lib/html-parser.js

@@ -0,0 +1,76 @@
+/*
+ * Set up window for Node.js
+ */
+
+var _window = (typeof window !== 'undefined' ? window : this)
+
+/*
+ * Parsing HTML strings
+ */
+
+function canParseHtmlNatively () {
+  var Parser = _window.DOMParser
+  var canParse = false
+
+  // Adapted from https://gist.github.com/1129031
+  // Firefox/Opera/IE throw errors on unsupported types
+  try {
+    // WebKit returns null on unsupported types
+    if (new Parser().parseFromString('', 'text/html')) {
+      canParse = true
+    }
+  } catch (e) {}
+
+  return canParse
+}
+
+function createHtmlParser () {
+  var Parser = function () {}
+
+  // For Node.js environments
+  if (typeof document === 'undefined') {
+    var jsdom = require('jsdom')
+    Parser.prototype.parseFromString = function (string) {
+      return jsdom.jsdom(string, {
+        features: {
+          FetchExternalResources: [],
+          ProcessExternalResources: false
+        }
+      })
+    }
+  } else {
+    if (!shouldUseActiveX()) {
+      Parser.prototype.parseFromString = function (string) {
+        var doc = document.implementation.createHTMLDocument('')
+        doc.open()
+        doc.write(string)
+        doc.close()
+        return doc
+      }
+    } else {
+      Parser.prototype.parseFromString = function (string) {
+        var doc = new window.ActiveXObject('htmlfile')
+        doc.designMode = 'on' // disable on-page scripts
+        doc.open()
+        doc.write(string)
+        doc.close()
+        return doc
+      }
+    }
+  }
+  return Parser
+}
+
+function shouldUseActiveX () {
+  var useActiveX = false
+
+  try {
+    document.implementation.createHTMLDocument('').open()
+  } catch (e) {
+    if (window.ActiveXObject) useActiveX = true
+  }
+
+  return useActiveX
+}
+
+module.exports = canParseHtmlNatively() ? _window.DOMParser : createHtmlParser()

+ 151 - 0
static/to-markdown/lib/md-converters.js

@@ -0,0 +1,151 @@
+'use strict'
+
+module.exports = [
+  {
+    filter: 'p',
+    replacement: function (content) {
+      return '\n\n' + content + '\n\n'
+    }
+  },
+
+  {
+    filter: 'br',
+    replacement: function () {
+      return '  \n'
+    }
+  },
+
+  {
+    filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+    replacement: function (content, node) {
+      var hLevel = node.nodeName.charAt(1)
+      var hPrefix = ''
+      for (var i = 0; i < hLevel; i++) {
+        hPrefix += '#'
+      }
+      return '\n\n' + hPrefix + ' ' + content + '\n\n'
+    }
+  },
+
+  {
+    filter: 'hr',
+    replacement: function () {
+      return '\n\n* * *\n\n'
+    }
+  },
+
+  {
+    filter: ['em', 'i'],
+    replacement: function (content) {
+      return '_' + content + '_'
+    }
+  },
+
+  {
+    filter: ['strong', 'b'],
+    replacement: function (content) {
+      return '**' + content + '**'
+    }
+  },
+
+  // Inline code
+  {
+    filter: function (node) {
+      var hasSiblings = node.previousSibling || node.nextSibling
+      var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings
+
+      return node.nodeName === 'CODE' && !isCodeBlock
+    },
+    replacement: function (content) {
+      return '`' + content + '`'
+    }
+  },
+
+  {
+    filter: function (node) {
+      return node.nodeName === 'A' && node.getAttribute('href')
+    },
+    replacement: function (content, node) {
+      var titlePart = node.title ? ' "' + node.title + '"' : ''
+      return '[' + content + '](' + node.getAttribute('href') + titlePart + ')'
+    }
+  },
+
+  {
+    filter: 'img',
+    replacement: function (content, node) {
+      var alt = node.alt || ''
+      var src = node.getAttribute('src') || ''
+      var title = node.title || ''
+      var titlePart = title ? ' "' + title + '"' : ''
+      return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
+    }
+  },
+
+  // Code blocks
+  {
+    filter: function (node) {
+      return node.nodeName === 'PRE' && node.firstChild.nodeName === 'CODE'
+    },
+    replacement: function (content, node) {
+      return '\n\n    ' + node.firstChild.textContent.replace(/\n/g, '\n    ') + '\n\n'
+    }
+  },
+
+  {
+    filter: 'blockquote',
+    replacement: function (content) {
+      content = content.trim()
+      content = content.replace(/\n{3,}/g, '\n\n')
+      content = content.replace(/^/gm, '> ')
+      return '\n\n' + content + '\n\n'
+    }
+  },
+
+  {
+    filter: 'li',
+    replacement: function (content, node) {
+      content = content.replace(/^\s+/, '').replace(/\n/gm, '\n    ')
+      var prefix = '*   '
+      var parent = node.parentNode
+      var index = Array.prototype.indexOf.call(parent.children, node) + 1
+
+      prefix = /ol/i.test(parent.nodeName) ? index + '.  ' : '*   '
+      return prefix + content
+    }
+  },
+
+  {
+    filter: ['ul', 'ol'],
+    replacement: function (content, node) {
+      var strings = []
+      for (var i = 0; i < node.childNodes.length; i++) {
+        strings.push(node.childNodes[i]._replacement)
+      }
+
+      if (/li/i.test(node.parentNode.nodeName)) {
+        return '\n' + strings.join('\n')
+      }
+      return '\n\n' + strings.join('\n') + '\n\n'
+    }
+  },
+
+  {
+    filter: function (node) {
+      return this.isBlock(node)
+    },
+    replacement: function (content, node) {
+      return '\n\n' + this.outer(node, content) + '\n\n'
+    }
+  },
+
+  // Anything else!
+  {
+    filter: function () {
+      return true
+    },
+    replacement: function (content, node) {
+      return this.outer(node, content)
+    }
+  }
+]

+ 831 - 0
static/wangEditor/css/wangEditor.css

@@ -0,0 +1,831 @@
+/* 编辑器边框颜色 */
+/* 菜单颜色、上边框颜色 */
+/* 菜单选中状态的颜色 */
+/* input focus 时的颜色 */
+/* 按钮颜色 */
+/* tab selected 状态下的颜色 */
+.wangEditor-container {
+  position: relative;
+  background-color: #fff;
+  border: 1px solid #ccc;
+  z-index: 1;
+  width: 100%;
+}
+.wangEditor-container a:focus,
+.wangEditor-container button:focus {
+  outline: none;
+}
+.wangEditor-container,
+.wangEditor-container * {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  line-height: 1;
+}
+.wangEditor-container img {
+  border: none;
+}
+.wangEditor-container .clearfix:after {
+  content: '';
+  display: table;
+  clear: both;
+}
+.wangEditor-container .clearfix {
+  *zoom: 1;
+}
+.wangEditor-container textarea {
+  border: none;
+}
+.wangEditor-container textarea:focus {
+  outline: none;
+}
+.wangEditor-container .height-tip {
+  position: absolute;
+  width: 3px;
+  background-color: #ccc;
+  left: 0;
+  transition: top .2s;
+}
+.wangEditor-container .txt-toolbar {
+  position: absolute;
+  background-color: #fff;
+  padding: 3px 5px;
+  border-top: 2px solid #666;
+  box-shadow: 1px 3px 3px #999;
+  border-left: 1px\9 solid\9 #ccc\9;
+  border-bottom: 1px\9 solid\9 #999\9;
+  border-right: 1px\9 solid\9 #999\9;
+}
+.wangEditor-container .txt-toolbar .tip-triangle {
+  display: block;
+  position: absolute;
+  width: 0;
+  height: 0;
+  border: 5px solid;
+  border-color: transparent transparent #666 transparent;
+  top: -12px;
+  left: 50%;
+  margin-left: -5px;
+}
+.wangEditor-container .txt-toolbar a {
+  color: #666;
+  display: inline-block;
+  margin: 0 3px;
+  padding: 5px;
+  text-decoration: none;
+  border-radius: 3px;
+}
+.wangEditor-container .txt-toolbar a:hover {
+  background-color: #f1f1f1;
+}
+.wangEditor-container .img-drag-point {
+  display: block;
+  position: absolute;
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  cursor: se-resize;
+  background-color: #666;
+  margin-left: -6px;
+  margin-top: -6px;
+  box-shadow: 1px 1px 5px #999;
+}
+.wangEditor-container .wangEditor-upload-progress {
+  position: absolute;
+  height: 1px;
+  background: #1e88e5;
+  width: 0;
+  display: none;
+  -webkit-transition: width .5s;
+  -o-transition: width .5s;
+  transition: width .5s;
+}
+.wangEditor-fullscreen {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+}
+.wangEditor-container .code-textarea {
+  resize: none;
+  width: 100%;
+  font-size: 14px;
+  line-height: 1.5;
+  font-family: 'Verdana';
+  color: #333;
+  padding: 0 15px 0 15px;
+}
+.wangEditor-menu-container {
+  width: 100%;
+  border-bottom: 1px solid #f1f1f1;
+  background-color: #fff;
+}
+.wangEditor-menu-container a {
+  text-decoration: none;
+}
+.wangEditor-menu-container .menu-group {
+  float: left;
+  padding: 0 8px;
+  border-right: 1px solid #f1f1f1;
+}
+.wangEditor-menu-container .menu-item {
+  float: left;
+  position: relative;
+  text-align: center;
+  height: 31px;
+  width: 35px;
+}
+.wangEditor-menu-container .menu-item:hover {
+  background-color: #f1f1f1;
+}
+.wangEditor-menu-container .menu-item a {
+  display: block;
+  text-align: center;
+  color: #666;
+  width: 100%;
+  padding: 8px 0;
+  font-size: 0.9em;
+}
+.wangEditor-menu-container .menu-item .selected {
+  color: #1e88e5;
+}
+.wangEditor-menu-container .menu-item .active {
+  background-color: #f1f1f1;
+}
+.wangEditor-menu-container .menu-item .disable {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+.wangEditor-menu-container .menu-tip {
+  display: block;
+  position: absolute;
+  z-index: 20;
+  width: 60px;
+  text-align: center;
+  background-color: #666;
+  color: #fff;
+  padding: 7px 0;
+  font-size: 12px;
+  top: 100%;
+  left: 50%;
+  margin-left: -30px;
+  border-radius: 2px;
+  box-shadow: 1px 1px 5px #999;
+  display: none;
+  /*// 小三角
+        .tip-triangle {
+            display: block;
+            position: absolute;
+            width: 0;
+            height: 0;
+            border:5px solid;
+            border-color: transparent transparent @fore-color transparent;
+            top: -10px;
+            left: 50%;
+            margin-left: -5px;
+        }*/
+}
+.wangEditor-menu-container .menu-tip-40 {
+  width: 40px;
+  margin-left: -20px;
+}
+.wangEditor-menu-container .menu-tip-50 {
+  width: 50px;
+  margin-left: -25px;
+}
+.wangEditor-menu-shadow {
+  /*border-bottom-width: 0;*/
+  border-bottom: 1px\9 solid\9 #f1f1f1\9;
+  box-shadow: 0 1px 3px #999;
+}
+.wangEditor-container .wangEditor-txt {
+  width: 100%;
+  text-align: left;
+  padding: 15px;
+  padding-top: 0;
+  margin-top: 5px;
+  overflow-y: auto;
+}
+.wangEditor-container .wangEditor-txt p,
+.wangEditor-container .wangEditor-txt h1,
+.wangEditor-container .wangEditor-txt h2,
+.wangEditor-container .wangEditor-txt h3,
+.wangEditor-container .wangEditor-txt h4,
+.wangEditor-container .wangEditor-txt h5 {
+  margin: 10px 0;
+  line-height: 1.8;
+}
+.wangEditor-container .wangEditor-txt p *,
+.wangEditor-container .wangEditor-txt h1 *,
+.wangEditor-container .wangEditor-txt h2 *,
+.wangEditor-container .wangEditor-txt h3 *,
+.wangEditor-container .wangEditor-txt h4 *,
+.wangEditor-container .wangEditor-txt h5 * {
+  line-height: 1.8;
+}
+.wangEditor-container .wangEditor-txt ul,
+.wangEditor-container .wangEditor-txt ol {
+  padding-left: 20px;
+}
+.wangEditor-container .wangEditor-txt img {
+  cursor: pointer;
+}
+.wangEditor-container .wangEditor-txt img.clicked {
+  box-shadow: 1px 1px 10px #999;
+}
+.wangEditor-container .wangEditor-txt table.clicked {
+  box-shadow: 1px 1px 10px #999;
+}
+.wangEditor-container .wangEditor-txt pre code {
+  line-height: 1.5;
+}
+.wangEditor-container .wangEditor-txt:focus {
+  outline: none;
+}
+.wangEditor-container .wangEditor-txt blockquote {
+  display: block;
+  border-left: 8px solid #d0e5f2;
+  padding: 5px 10px;
+  margin: 10px 0;
+  line-height: 1.4;
+  font-size: 100%;
+  background-color: #f1f1f1;
+}
+.wangEditor-container .wangEditor-txt table {
+  border: none;
+  border-collapse: collapse;
+}
+.wangEditor-container .wangEditor-txt table td,
+.wangEditor-container .wangEditor-txt table th {
+  border: 1px solid #999;
+  padding: 3px 5px;
+  min-width: 50px;
+  height: 20px;
+}
+.wangEditor-container .wangEditor-txt pre {
+  border: 1px solid #ccc;
+  background-color: #f8f8f8;
+  padding: 10px;
+  margin: 5px 0px;
+  font-size: 0.8em;
+  border-radius: 3px;
+}
+.wangEditor-drop-list {
+  display: none;
+  position: absolute;
+  background-color: #fff;
+  overflow: hidden;
+  z-index: 10;
+  transition: height .7s;
+  border-top: 1px solid #f1f1f1;
+  box-shadow: 1px 3px 3px #999;
+  border-left: 1px\9 solid\9 #ccc\9;
+  border-bottom: 1px\9 solid\9 #999\9;
+  border-right: 1px\9 solid\9 #999\9;
+}
+.wangEditor-drop-list a {
+  text-decoration: none;
+  display: block;
+  color: #666;
+  padding: 3px 5px;
+}
+.wangEditor-drop-list a:hover {
+  background-color: #f1f1f1;
+}
+.wangEditor-drop-panel,
+.txt-toolbar {
+  display: none;
+  position: absolute;
+  padding: 10px;
+  font-size: 14px;
+  /*border: 1px\9 solid\9 #cccccc\9;*/
+  background-color: #fff;
+  z-index: 10;
+  border-top: 2px solid #666;
+  box-shadow: 1px 3px 3px #999;
+  border-left: 1px\9 solid\9 #ccc\9;
+  border-bottom: 1px\9 solid\9 #999\9;
+  border-right: 1px\9 solid\9 #999\9;
+}
+.wangEditor-drop-panel .tip-triangle,
+.txt-toolbar .tip-triangle {
+  display: block;
+  position: absolute;
+  width: 0;
+  height: 0;
+  border: 5px solid;
+  border-color: transparent transparent #666 transparent;
+  top: -12px;
+  left: 50%;
+  margin-left: -5px;
+}
+.wangEditor-drop-panel a,
+.txt-toolbar a {
+  text-decoration: none;
+}
+.wangEditor-drop-panel input[type=text],
+.txt-toolbar input[type=text] {
+  border: none;
+  border-bottom: 1px solid #ccc;
+  font-size: 14px;
+  height: 20px;
+  color: #333;
+  padding: 3px 0;
+}
+.wangEditor-drop-panel input[type=text]:focus,
+.txt-toolbar input[type=text]:focus {
+  outline: none;
+  border-bottom: 2px solid #1e88e5;
+}
+.wangEditor-drop-panel input[type=text].block,
+.txt-toolbar input[type=text].block {
+  display: block;
+  width: 100%;
+}
+.wangEditor-drop-panel textarea,
+.txt-toolbar textarea {
+  border: 1px solid #ccc;
+}
+.wangEditor-drop-panel textarea:focus,
+.txt-toolbar textarea:focus {
+  outline: none;
+  border-color: #1e88e5;
+}
+.wangEditor-drop-panel button,
+.txt-toolbar button {
+  font-size: 14px;
+  color: #1e88e5;
+  border: none;
+  padding: 10px;
+  background-color: #fff;
+  cursor: pointer;
+  border-radius: 3px;
+}
+.wangEditor-drop-panel button:hover,
+.txt-toolbar button:hover {
+  background-color: #f1f1f1;
+}
+.wangEditor-drop-panel button:focus,
+.txt-toolbar button:focus {
+  outline: none;
+}
+.wangEditor-drop-panel button.right,
+.txt-toolbar button.right {
+  float: right;
+  margin-left: 10px;
+}
+.wangEditor-drop-panel button.gray,
+.txt-toolbar button.gray {
+  color: #999;
+}
+.wangEditor-drop-panel button.link,
+.txt-toolbar button.link {
+  padding: 5px 10px;
+}
+.wangEditor-drop-panel button.link:hover,
+.txt-toolbar button.link:hover {
+  background-color: #fff;
+  text-decoration: underline;
+}
+.wangEditor-drop-panel .color-item,
+.txt-toolbar .color-item {
+  display: block;
+  float: left;
+  width: 25px;
+  height: 25px;
+  text-align: center;
+  padding: 2px;
+  border-radius: 2px;
+  text-decoration: underline;
+}
+.wangEditor-drop-panel .color-item:hover,
+.txt-toolbar .color-item:hover {
+  background-color: #f1f1f1;
+}
+.wangEditor-drop-panel .list-menu-item,
+.txt-toolbar .list-menu-item {
+  display: block;
+  float: left;
+  color: #333;
+  padding: 5px 5px;
+  border-radius: 2px;
+}
+.wangEditor-drop-panel .list-menu-item:hover,
+.txt-toolbar .list-menu-item:hover {
+  background-color: #f1f1f1;
+}
+.wangEditor-drop-panel table.choose-table,
+.txt-toolbar table.choose-table {
+  border: none;
+  border-collapse: collapse;
+}
+.wangEditor-drop-panel table.choose-table td,
+.txt-toolbar table.choose-table td {
+  border: 1px solid #ccc;
+  width: 16px;
+  height: 12px;
+}
+.wangEditor-drop-panel table.choose-table td.active,
+.txt-toolbar table.choose-table td.active {
+  background-color: #ccc;
+  opacity: .5;
+  filter: alpha(opacity=50);
+}
+.wangEditor-drop-panel .panel-tab .tab-container,
+.txt-toolbar .panel-tab .tab-container {
+  margin-bottom: 5px;
+}
+.wangEditor-drop-panel .panel-tab .tab-container a,
+.txt-toolbar .panel-tab .tab-container a {
+  display: inline-block;
+  color: #999;
+  text-align: center;
+  margin: 0 5px;
+  padding: 5px 5px;
+}
+.wangEditor-drop-panel .panel-tab .tab-container a.selected,
+.txt-toolbar .panel-tab .tab-container a.selected {
+  color: #1e88e5;
+  border-bottom: 2px solid #1e88e5;
+}
+.wangEditor-drop-panel .panel-tab .content-container .content,
+.txt-toolbar .panel-tab .content-container .content {
+  display: none;
+}
+.wangEditor-drop-panel .panel-tab .content-container .content a,
+.txt-toolbar .panel-tab .content-container .content a {
+  display: inline-block;
+  margin: 2px;
+  padding: 2px;
+  border-radius: 2px;
+}
+.wangEditor-drop-panel .panel-tab .content-container .content a:hover,
+.txt-toolbar .panel-tab .content-container .content a:hover {
+  background-color: #f1f1f1;
+}
+.wangEditor-drop-panel .panel-tab .content-container .selected,
+.txt-toolbar .panel-tab .content-container .selected {
+  display: block;
+}
+.wangEditor-drop-panel .panel-tab .emotion-content-container,
+.txt-toolbar .panel-tab .emotion-content-container {
+  height: 200px;
+  overflow-y: auto;
+}
+.wangEditor-drop-panel .upload-icon-container,
+.txt-toolbar .upload-icon-container {
+  color: #ccc;
+  text-align: center;
+  margin: 20px 20px 15px 20px !important;
+  padding: 5px !important;
+  font-size: 65px;
+  cursor: pointer;
+  border: 2px dotted #f1f1f1;
+  display: block !important;
+}
+.wangEditor-drop-panel .upload-icon-container:hover,
+.txt-toolbar .upload-icon-container:hover {
+  color: #666;
+  border-color: #ccc;
+}
+.wangEditor-modal {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  background-color: #fff;
+  border-top: 1px solid #f1f1f1;
+  box-shadow: 1px 3px 3px #999;
+  border-top: 1px\9 solid\9 #ccc\9;
+  border-left: 1px\9 solid\9 #ccc\9;
+  border-bottom: 1px\9 solid\9 #999\9;
+  border-right: 1px\9 solid\9 #999\9;
+}
+.wangEditor-modal .wangEditor-modal-close {
+  position: absolute;
+  top: 0;
+  right: 0;
+  margin-top: -25px;
+  margin-right: -25px;
+  font-size: 1.5em;
+  color: #666;
+  cursor: pointer;
+}
+@font-face {
+  font-family: 'icomoon';
+  src: url('../fonts/icomoon.eot?-qdfu1s');
+  src: url('../fonts/icomoon.eot?#iefix-qdfu1s') format('embedded-opentype'), url('../fonts/icomoon.ttf?-qdfu1s') format('truetype'), url('../fonts/icomoon.woff?-qdfu1s') format('woff'), url('../fonts/icomoon.svg?-qdfu1s#icomoon') format('svg');
+  font-weight: normal;
+  font-style: normal;
+}
+[class^="wangeditor-menu-img-"],
+[class*=" wangeditor-menu-img-"] {
+  font-family: 'icomoon';
+  speak: none;
+  font-style: normal;
+  font-weight: normal;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  /* Better Font Rendering =========== */
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+.wangeditor-menu-img-link:before {
+  content: "\e800";
+}
+.wangeditor-menu-img-unlink:before {
+  content: "\e801";
+}
+.wangeditor-menu-img-code:before {
+  content: "\e802";
+}
+.wangeditor-menu-img-cancel:before {
+  content: "\e803";
+}
+.wangeditor-menu-img-terminal:before {
+  content: "\e804";
+}
+.wangeditor-menu-img-angle-down:before {
+  content: "\e805";
+}
+.wangeditor-menu-img-font:before {
+  content: "\e806";
+}
+.wangeditor-menu-img-bold:before {
+  content: "\e807";
+}
+.wangeditor-menu-img-italic:before {
+  content: "\e808";
+}
+.wangeditor-menu-img-header:before {
+  content: "\e809";
+}
+.wangeditor-menu-img-align-left:before {
+  content: "\e80a";
+}
+.wangeditor-menu-img-align-center:before {
+  content: "\e80b";
+}
+.wangeditor-menu-img-align-right:before {
+  content: "\e80c";
+}
+.wangeditor-menu-img-list-bullet:before {
+  content: "\e80d";
+}
+.wangeditor-menu-img-indent-left:before {
+  content: "\e80e";
+}
+.wangeditor-menu-img-indent-right:before {
+  content: "\e80f";
+}
+.wangeditor-menu-img-list-numbered:before {
+  content: "\e810";
+}
+.wangeditor-menu-img-underline:before {
+  content: "\e811";
+}
+.wangeditor-menu-img-table:before {
+  content: "\e812";
+}
+.wangeditor-menu-img-eraser:before {
+  content: "\e813";
+}
+.wangeditor-menu-img-text-height:before {
+  content: "\e814";
+}
+.wangeditor-menu-img-brush:before {
+  content: "\e815";
+}
+.wangeditor-menu-img-pencil:before {
+  content: "\e816";
+}
+.wangeditor-menu-img-minus:before {
+  content: "\e817";
+}
+.wangeditor-menu-img-picture:before {
+  content: "\e818";
+}
+.wangeditor-menu-img-file-image:before {
+  content: "\e819";
+}
+.wangeditor-menu-img-cw:before {
+  content: "\e81a";
+}
+.wangeditor-menu-img-ccw:before {
+  content: "\e81b";
+}
+.wangeditor-menu-img-music:before {
+  content: "\e911";
+}
+.wangeditor-menu-img-play:before {
+  content: "\e912";
+}
+.wangeditor-menu-img-location:before {
+  content: "\e947";
+}
+.wangeditor-menu-img-happy:before {
+  content: "\e9df";
+}
+.wangeditor-menu-img-sigma:before {
+  content: "\ea67";
+}
+.wangeditor-menu-img-enlarge2:before {
+  content: "\e98b";
+}
+.wangeditor-menu-img-shrink2:before {
+  content: "\e98c";
+}
+.wangeditor-menu-img-newspaper:before {
+  content: "\e904";
+}
+.wangeditor-menu-img-camera:before {
+  content: "\e90f";
+}
+.wangeditor-menu-img-video-camera:before {
+  content: "\e914";
+}
+.wangeditor-menu-img-file-zip:before {
+  content: "\e92b";
+}
+.wangeditor-menu-img-stack:before {
+  content: "\e92e";
+}
+.wangeditor-menu-img-credit-card:before {
+  content: "\e93f";
+}
+.wangeditor-menu-img-address-book:before {
+  content: "\e944";
+}
+.wangeditor-menu-img-envelop:before {
+  content: "\e945";
+}
+.wangeditor-menu-img-drawer:before {
+  content: "\e95c";
+}
+.wangeditor-menu-img-download:before {
+  content: "\e960";
+}
+.wangeditor-menu-img-upload:before {
+  content: "\e961";
+}
+.wangeditor-menu-img-lock:before {
+  content: "\e98f";
+}
+.wangeditor-menu-img-unlocked:before {
+  content: "\e990";
+}
+.wangeditor-menu-img-wrench:before {
+  content: "\e991";
+}
+.wangeditor-menu-img-eye:before {
+  content: "\e9ce";
+}
+.wangeditor-menu-img-eye-blocked:before {
+  content: "\e9d1";
+}
+.wangeditor-menu-img-command:before {
+  content: "\ea4e";
+}
+.wangeditor-menu-img-font2:before {
+  content: "\ea5c";
+}
+.wangeditor-menu-img-libreoffice:before {
+  content: "\eade";
+}
+.wangeditor-menu-img-quotes-left:before {
+  content: "\e977";
+}
+.wangeditor-menu-img-strikethrough:before {
+  content: "\ea65";
+}
+.wangeditor-menu-img-desktop:before {
+  content: "\f108";
+}
+.wangeditor-menu-img-tablet:before {
+  content: "\f10a";
+}
+.wangeditor-menu-img-search-plus:before {
+  content: "\f00e";
+}
+.wangeditor-menu-img-search-minus:before {
+  content: "\f010";
+}
+.wangeditor-menu-img-trash-o:before {
+  content: "\f014";
+}
+.wangeditor-menu-img-align-justify:before {
+  content: "\f039";
+}
+.wangeditor-menu-img-arrows-v:before {
+  content: "\f07d";
+}
+.wangeditor-menu-img-sigma2:before {
+  content: "\ea68";
+}
+.wangeditor-menu-img-omega:before {
+  content: "\e900";
+}
+.wangeditor-menu-img-cancel-circle:before {
+  content: "\e901";
+}
+.hljs {
+  display: block;
+  overflow-x: auto;
+  padding: 0.5em;
+  color: #333;
+  background: #f8f8f8;
+  -webkit-text-size-adjust: none;
+}
+.hljs-comment,
+.diff .hljs-header {
+  color: #998;
+  font-style: italic;
+}
+.hljs-keyword,
+.css .rule .hljs-keyword,
+.hljs-winutils,
+.nginx .hljs-title,
+.hljs-subst,
+.hljs-request,
+.hljs-status {
+  color: #333;
+  font-weight: bold;
+}
+.hljs-number,
+.hljs-hexcolor,
+.ruby .hljs-constant {
+  color: #008080;
+}
+.hljs-string,
+.hljs-tag .hljs-value,
+.hljs-doctag,
+.tex .hljs-formula {
+  color: #d14;
+}
+.hljs-title,
+.hljs-id,
+.scss .hljs-preprocessor {
+  color: #900;
+  font-weight: bold;
+}
+.hljs-list .hljs-keyword,
+.hljs-subst {
+  font-weight: normal;
+}
+.hljs-class .hljs-title,
+.hljs-type,
+.vhdl .hljs-literal,
+.tex .hljs-command {
+  color: #458;
+  font-weight: bold;
+}
+.hljs-tag,
+.hljs-tag .hljs-title,
+.hljs-rule .hljs-property,
+.django .hljs-tag .hljs-keyword {
+  color: #000080;
+  font-weight: normal;
+}
+.hljs-attribute,
+.hljs-variable,
+.lisp .hljs-body,
+.hljs-name {
+  color: #008080;
+}
+.hljs-regexp {
+  color: #009926;
+}
+.hljs-symbol,
+.ruby .hljs-symbol .hljs-string,
+.lisp .hljs-keyword,
+.clojure .hljs-keyword,
+.scheme .hljs-keyword,
+.tex .hljs-special,
+.hljs-prompt {
+  color: #990073;
+}
+.hljs-built_in {
+  color: #0086b3;
+}
+.hljs-preprocessor,
+.hljs-pragma,
+.hljs-pi,
+.hljs-doctype,
+.hljs-shebang,
+.hljs-cdata {
+  color: #999;
+  font-weight: bold;
+}
+.hljs-deletion {
+  background: #fdd;
+}
+.hljs-addition {
+  background: #dfd;
+}
+.diff .hljs-change {
+  background: #0086b3;
+}
+.hljs-chunk {
+  color: #aaa;
+}

+ 810 - 0
static/wangEditor/css/wangEditor.less

@@ -0,0 +1,810 @@
+
+// ---------- begin 全局颜色配置 ------------
+
+/* 编辑器边框颜色 */
+@border-color: #ccc;
+
+/* 菜单颜色、上边框颜色 */
+@fore-color: #666;
+
+/* 菜单选中状态的颜色 */
+@selected-color: #1e88e5;
+
+/* input focus 时的颜色 */
+@focus-input-color: #1e88e5;
+
+/* 按钮颜色 */
+@button-color: #1e88e5;
+
+/* tab selected 状态下的颜色 */
+@selected-tab-color: #1e88e5;
+
+// ---------- end 全局颜色配置 ------------
+
+
+.wangEditor-container {
+    position: relative;
+    background-color: #fff;
+    border: 1px solid @border-color;
+    z-index: 1;
+    width: 100%;
+
+    a:focus,
+    button:focus{
+        outline:none;
+    }
+
+    &,* {
+        margin: 0;
+        padding: 0;
+        box-sizing: border-box;
+        line-height: 1;
+    }
+
+    img {
+        border: none;
+    }
+
+    .clearfix:after {
+        content: '';
+        display: table;
+        clear: both;
+    }
+    .clearfix {
+        *zoom: 1;
+    }
+
+    textarea {
+        border: none;
+        &:focus{
+            outline: none; 
+        }
+    }
+
+    // 显示p head 高度的 tip
+    .height-tip {
+        position: absolute;
+        width: 3px;
+        background-color: #ccc;
+        left: 0;
+        transition: top .2s;
+    }
+
+    // 设置 img table 的 toolbar
+    .txt-toolbar {
+        position: absolute;
+        background-color: #fff;
+        padding: 3px 5px;
+        border-top: 2px solid @fore-color;
+        box-shadow: 1px 3px 3px #999;
+
+        // for IE8
+        border-left: 1px\9 solid\9 #ccc\9;
+        border-bottom: 1px\9 solid\9 #999\9;
+        border-right: 1px\9 solid\9 #999\9;
+
+        // 小三角
+        .tip-triangle {
+            display: block;
+            position: absolute;
+            width: 0;
+            height: 0;
+            border: 5px solid;
+            border-color: transparent transparent @fore-color transparent;
+            top: -12px;
+            left: 50%;
+            margin-left: -5px;
+        }
+
+        a {
+            color: @fore-color;
+            display: inline-block;
+            margin: 0 3px;
+            padding: 5px;
+            text-decoration: none;
+            border-radius: 3px;
+
+            &:hover {
+                background-color: #f1f1f1;
+            }
+        }
+    }
+    // 图品拖拽大小
+    .img-drag-point {
+        display: block;
+        position: absolute;
+        width: 12px;
+        height: 12px;
+        border-radius: 50%;
+        cursor: se-resize;
+        background-color: @fore-color;
+        margin-left: -6px;
+        margin-top: -6px;
+        box-shadow: 1px 1px 5px #999;
+    }
+
+    // 进度条
+    .wangEditor-upload-progress {
+        position: absolute;
+        height: 1px;
+        background: #1e88e5;
+        width: 0;
+        display: none;
+        -webkit-transition: width .5s;
+        -o-transition: width .5s;
+        transition: width .5s;
+    }
+}
+.wangEditor-fullscreen {
+    position: fixed;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+}
+.wangEditor-container {
+    .code-textarea {
+        resize: none;
+        width: 100%;
+        font-size: 14px;
+        line-height: 1.5;
+        font-family: 'Verdana';
+        color: #333;
+        padding: 0 15px 0 15px;
+    }
+}
+.wangEditor-menu-container {
+
+    width: 100%;
+    border-bottom: 1px solid #f1f1f1;
+    background-color: #fff;
+    
+    a {
+        text-decoration: none;
+    }
+
+    // 菜单组
+    .menu-group {
+        float: left;
+        padding: 0 8px;
+        border-right: 1px solid #f1f1f1;
+    }
+
+    // 单个菜单容器
+    .menu-item {
+        float: left;
+        position: relative;
+        text-align: center;
+        height: 31px;
+        width: 35px;
+
+        &:hover {
+            background-color: #f1f1f1;
+        }
+
+        // 菜单
+        a {
+            display: block;
+            text-align: center;
+            color: @fore-color;
+            width: 100%;
+            padding: 8px 0;
+            font-size: 0.9em;
+        }
+
+         // 菜单选中状态
+        .selected {
+            color: @selected-color;
+        }
+
+        // 激活状态
+        .active {
+            background-color: #f1f1f1;
+        }
+
+        // 禁用状态
+        .disable {
+            opacity: 0.5;
+            filter: Alpha(opacity=50);
+        }
+    }
+
+    // tip提示
+    .menu-tip {
+        display: block;
+        position: absolute;
+        z-index: 20;
+        width: 60px;
+        text-align: center;
+        background-color: @fore-color;
+        color: #fff;
+        padding: 7px 0;
+        font-size: 12px;
+        top: 100%;
+        left: 50%;
+        margin-left: -30px;
+        border-radius: 2px;
+        box-shadow: 1px 1px 5px #999;
+
+        display: none;
+
+        /*// 小三角
+        .tip-triangle {
+            display: block;
+            position: absolute;
+            width: 0;
+            height: 0;
+            border:5px solid;
+            border-color: transparent transparent @fore-color transparent;
+            top: -10px;
+            left: 50%;
+            margin-left: -5px;
+        }*/
+    }
+    .menu-tip-40 {
+        width: 40px;
+        margin-left: -20px;
+    }
+    .menu-tip-50 {
+        width: 50px;
+        margin-left: -25px;
+    }
+}
+.wangEditor-menu-shadow {
+    /*border-bottom-width: 0;*/
+    border-bottom: 1px\9 solid\9 #f1f1f1\9;
+    box-shadow: 0 1px 3px #999;
+}
+.wangEditor-container {
+    .wangEditor-txt{
+        width: 100%;
+        text-align: left;
+        padding: 15px;
+        padding-top: 0;
+        margin-top: 5px;
+        overflow-y: auto;
+
+        p,h1,h2,h3,h4,h5 {
+            margin: 10px 0;
+            line-height: 1.8;
+
+            * {
+                line-height: 1.8;
+            }
+        }
+
+        ul, ol {
+            padding-left: 20px;
+        }
+
+        img {
+            cursor: pointer;
+        }
+        img.clicked {
+            box-shadow: 1px 1px 10px #999;
+        }
+
+        table.clicked {
+            box-shadow: 1px 1px 10px #999;
+        }
+
+        pre code {
+            line-height: 1.5;
+        }
+
+        &:focus{
+            outline: none; 
+        }
+    }
+}
+.wangEditor-container {
+    .wangEditor-txt {
+        blockquote {
+            display: block; 
+            border-left: 8px solid #d0e5f2;
+            padding: 5px 10px;
+            margin: 10px 0; 
+            line-height: 1.4; 
+            font-size: 100%;
+            background-color: #f1f1f1;
+        }
+        table {
+            border: none;
+            border-collapse: collapse;
+        }
+        table td,
+        table th {
+            border: 1px solid #999;
+            padding: 3px 5px;
+            min-width: 50px;
+            height: 20px;
+        }
+        pre {
+            border: 1px solid #ccc; 
+            background-color: #f8f8f8; 
+            padding: 10px; 
+            margin: 5px 0px;
+            font-size: 0.8em;
+            border-radius: 3px;
+        }
+    }
+}
+.wangEditor-drop-list {
+    display: none;
+    position: absolute;
+    background-color: #fff;
+    overflow: hidden;
+    z-index: 10;
+
+    transition: height .7s;
+    
+    border-top: 1px solid #f1f1f1;
+    box-shadow: 1px 3px 3px #999;
+
+    // for IE8
+    border-left: 1px\9 solid\9 #ccc\9;
+    border-bottom: 1px\9 solid\9 #999\9;
+    border-right: 1px\9 solid\9 #999\9;
+
+    a {
+        text-decoration: none;
+        display: block;
+        color: @fore-color;
+        padding: 3px 5px;
+
+        &:hover {
+            background-color: #f1f1f1;
+        }
+    }
+}
+.wangEditor-drop-panel,
+.txt-toolbar {
+    display: none;
+    position: absolute;
+    padding: 10px;
+    font-size: 14px;
+    /*border: 1px\9 solid\9 #cccccc\9;*/
+
+    background-color: #fff;
+    z-index: 10;
+    
+    border-top: 2px solid @fore-color;
+    box-shadow: 1px 3px 3px #999;
+
+    // for IE8
+    border-left: 1px\9 solid\9 #ccc\9;
+    border-bottom: 1px\9 solid\9 #999\9;
+    border-right: 1px\9 solid\9 #999\9;
+
+    // 小三角
+    .tip-triangle {
+        display: block;
+        position: absolute;
+        width: 0;
+        height: 0;
+        border: 5px solid;
+        border-color: transparent transparent @fore-color transparent;
+        top: -12px;
+        left: 50%;
+        margin-left: -5px;
+    }
+
+    a {
+        text-decoration: none;
+    }
+
+    // 输入框
+    input[type=text] {
+        border: none;
+        border-bottom: 1px solid #ccc;
+        font-size: 14px;
+        height: 20px;
+        color: #333;
+        padding: 3px 0;
+
+        &:focus{
+            outline: none;
+            border-bottom: 2px solid @focus-input-color;
+        }
+    }
+    input[type=text].block {
+        display: block;
+        width: 100%;
+    }
+    textarea {
+        border: 1px solid #ccc;
+        &:focus {
+            outline: none;
+            border-color: @focus-input-color;
+        }
+    }
+
+    // 按钮
+    button {
+        font-size: 14px;
+        color: @button-color;
+        border: none;
+        padding: 10px;
+        background-color: #fff;
+        cursor: pointer;
+        border-radius: 3px;
+        
+        &:hover {
+            background-color: #f1f1f1;
+        }
+        &:focus{
+            outline: none; 
+        }
+    }
+    button.right {
+        float: right;
+        margin-left: 10px;
+    }
+    button.gray {
+        color: #999;
+    }
+    button.link {
+        padding: 5px 10px;
+        &:hover {
+            background-color: #fff;
+            text-decoration: underline;
+        }
+    }
+    
+    // 颜色块
+    .color-item {
+        display: block;
+        float: left;
+        width: 25px;
+        height: 25px;
+        text-align: center;
+        padding: 2px;
+        border-radius: 2px;
+        text-decoration: underline;
+
+        &:hover {
+            background-color: #f1f1f1;
+        }
+    }
+
+    // 列表
+    .list-menu-item {
+        display: block;
+        float: left;
+        color: #333;
+        padding: 5px 5px;
+        border-radius: 2px;
+
+        &:hover {
+            background-color: #f1f1f1;
+        }
+    }
+
+    // 表格
+    table.choose-table {
+        border: none;
+        border-collapse: collapse;
+
+        td {
+            border: 1px solid #ccc;
+            width: 16px;
+            height: 12px;
+        }
+        td.active {
+            background-color: #ccc;
+            opacity: .5;
+            filter: Alpha(opacity=50);
+        }
+    }
+
+    // tab
+    .panel-tab {
+        .tab-container {
+            margin-bottom: 5px;
+
+            a {
+                display: inline-block;
+                color: #999;
+                text-align: center;
+                margin: 0 5px;
+                padding: 5px 5px;
+            }
+
+            a.selected {
+                color: @selected-tab-color;
+                border-bottom: 2px solid @selected-tab-color;
+            }
+        }
+        .content-container {
+            .content {
+                display: none;
+
+                a {
+                    display: inline-block;
+                    margin: 2px;
+                    padding: 2px;
+                    border-radius: 2px;
+
+                    &:hover {
+                        background-color: #f1f1f1;
+                    }
+                }
+            }
+            .selected {
+                display: block;
+            }
+        }
+        .emotion-content-container {
+            height: 200px;
+            overflow-y: auto;
+        }
+    }
+
+    // 上传图片
+    .upload-icon-container {
+        color: #ccc;
+        text-align: center;
+        margin: 20px 20px 15px 20px !important;
+        padding: 5px !important;
+        font-size: 65px;
+        cursor: pointer;
+        border: 2px dotted #f1f1f1;
+        display: block !important;
+
+        &:hover {
+            color: #666;
+            border-color: #ccc;
+        }
+    }
+}
+.wangEditor-modal {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    background-color: #fff;
+    
+    border-top: 1px solid #f1f1f1;
+    box-shadow: 1px 3px 3px #999;
+
+    // for IE8
+    border-top: 1px\9 solid\9 #ccc\9;
+    border-left: 1px\9 solid\9 #ccc\9;
+    border-bottom: 1px\9 solid\9 #999\9;
+    border-right: 1px\9 solid\9 #999\9;
+
+    // 关闭按钮
+    .wangEditor-modal-close {
+        position: absolute;
+        top: 0;
+        right: 0;
+        margin-top: -25px;
+        margin-right: -25px;
+        font-size: 1.5em;
+        color: #666;
+        cursor: pointer;
+    }
+    
+}
+@font-face {
+    font-family: 'icomoon';
+    src:url('../fonts/icomoon.eot?-qdfu1s');
+    src:url('../fonts/icomoon.eot?#iefix-qdfu1s') format('embedded-opentype'),
+        url('../fonts/icomoon.ttf?-qdfu1s') format('truetype'),
+        url('../fonts/icomoon.woff?-qdfu1s') format('woff'),
+        url('../fonts/icomoon.svg?-qdfu1s#icomoon') format('svg');
+    font-weight: normal;
+    font-style: normal;
+}
+
+[class^="wangeditor-menu-img-"], [class*=" wangeditor-menu-img-"] {
+    font-family: 'icomoon';
+    speak: none;
+    font-style: normal;
+    font-weight: normal;
+    font-variant: normal;
+    text-transform: none;
+    line-height: 1;
+
+    /* Better Font Rendering =========== */
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+}
+.wangeditor-menu-img-link:before {content: "\e800";}
+.wangeditor-menu-img-unlink:before {content: "\e801";}
+.wangeditor-menu-img-code:before {content: "\e802";}
+.wangeditor-menu-img-cancel:before {content: "\e803";}
+.wangeditor-menu-img-terminal:before {content: "\e804";}
+.wangeditor-menu-img-angle-down:before {content: "\e805";}
+.wangeditor-menu-img-font:before {content: "\e806";}
+.wangeditor-menu-img-bold:before {content: "\e807";}
+.wangeditor-menu-img-italic:before {content: "\e808";}
+.wangeditor-menu-img-header:before {content: "\e809";}
+.wangeditor-menu-img-align-left:before {content: "\e80a";}
+.wangeditor-menu-img-align-center:before {content: "\e80b";}
+.wangeditor-menu-img-align-right:before {content: "\e80c";}
+.wangeditor-menu-img-list-bullet:before {content: "\e80d";}
+.wangeditor-menu-img-indent-left:before {content: "\e80e";}
+.wangeditor-menu-img-indent-right:before {content: "\e80f";}
+.wangeditor-menu-img-list-numbered:before {content: "\e810";}
+.wangeditor-menu-img-underline:before {content: "\e811";}
+.wangeditor-menu-img-table:before {content: "\e812";}
+.wangeditor-menu-img-eraser:before {content: "\e813";}
+.wangeditor-menu-img-text-height:before {content: "\e814";}
+.wangeditor-menu-img-brush:before {content: "\e815";}
+.wangeditor-menu-img-pencil:before {content: "\e816";}
+.wangeditor-menu-img-minus:before {content: "\e817";}
+.wangeditor-menu-img-picture:before {content: "\e818";}
+.wangeditor-menu-img-file-image:before {content: "\e819";}
+.wangeditor-menu-img-cw:before {content: "\e81a";}
+.wangeditor-menu-img-ccw:before {content: "\e81b";}
+.wangeditor-menu-img-music:before {content: "\e911";}
+.wangeditor-menu-img-play:before {content: "\e912";}
+.wangeditor-menu-img-location:before {content: "\e947";}
+.wangeditor-menu-img-happy:before {content: "\e9df";}
+.wangeditor-menu-img-sigma:before {content: "\ea67";}
+.wangeditor-menu-img-enlarge2:before {content: "\e98b";}
+.wangeditor-menu-img-shrink2:before {content: "\e98c";}
+.wangeditor-menu-img-newspaper:before{content: "\e904";}
+.wangeditor-menu-img-camera:before{content: "\e90f";}
+.wangeditor-menu-img-video-camera:before{content: "\e914";}
+.wangeditor-menu-img-file-zip:before{content: "\e92b";}
+.wangeditor-menu-img-stack:before{content: "\e92e";}
+.wangeditor-menu-img-credit-card:before{content: "\e93f";}
+.wangeditor-menu-img-address-book:before{content: "\e944";}
+.wangeditor-menu-img-envelop:before{content: "\e945";}
+.wangeditor-menu-img-drawer:before{content: "\e95c";}
+.wangeditor-menu-img-download:before{content: "\e960";}
+.wangeditor-menu-img-upload:before{content: "\e961";}
+.wangeditor-menu-img-lock:before{content: "\e98f";}
+.wangeditor-menu-img-unlocked:before{content: "\e990";}
+.wangeditor-menu-img-wrench:before{content: "\e991";}
+.wangeditor-menu-img-eye:before{content: "\e9ce";}
+.wangeditor-menu-img-eye-blocked:before{content: "\e9d1";}
+.wangeditor-menu-img-command:before{content: "\ea4e";}
+.wangeditor-menu-img-font2:before{content: "\ea5c";}
+.wangeditor-menu-img-libreoffice:before{content: "\eade";}
+.wangeditor-menu-img-quotes-left:before{content: "\e977";}
+.wangeditor-menu-img-strikethrough:before{content: "\ea65";}
+.wangeditor-menu-img-desktop:before{content: "\f108";}
+.wangeditor-menu-img-tablet:before{content: "\f10a";}
+.wangeditor-menu-img-search-plus:before {
+    content: "\f00e";
+}
+.wangeditor-menu-img-search-minus:before {
+    content: "\f010";
+}
+.wangeditor-menu-img-trash-o:before {
+    content: "\f014";
+}
+.wangeditor-menu-img-align-justify:before {
+    content: "\f039";
+}
+.wangeditor-menu-img-arrows-v:before {
+    content: "\f07d";
+}
+.wangeditor-menu-img-sigma2:before {
+    content: "\ea68";
+}
+.wangeditor-menu-img-omega:before {
+    content: "\e900";
+}
+.wangeditor-menu-img-cancel-circle:before {
+    content: "\e901";
+}
+.hljs {
+  display: block;
+  overflow-x: auto;
+  padding: 0.5em;
+  color: #333;
+  background: #f8f8f8;
+  -webkit-text-size-adjust: none;
+}
+
+.hljs-comment,
+.diff .hljs-header {
+  color: #998;
+  font-style: italic;
+}
+
+.hljs-keyword,
+.css .rule .hljs-keyword,
+.hljs-winutils,
+.nginx .hljs-title,
+.hljs-subst,
+.hljs-request,
+.hljs-status {
+  color: #333;
+  font-weight: bold;
+}
+
+.hljs-number,
+.hljs-hexcolor,
+.ruby .hljs-constant {
+  color: #008080;
+}
+
+.hljs-string,
+.hljs-tag .hljs-value,
+.hljs-doctag,
+.tex .hljs-formula {
+  color: #d14;
+}
+
+.hljs-title,
+.hljs-id,
+.scss .hljs-preprocessor {
+  color: #900;
+  font-weight: bold;
+}
+
+.hljs-list .hljs-keyword,
+.hljs-subst {
+  font-weight: normal;
+}
+
+.hljs-class .hljs-title,
+.hljs-type,
+.vhdl .hljs-literal,
+.tex .hljs-command {
+  color: #458;
+  font-weight: bold;
+}
+
+.hljs-tag,
+.hljs-tag .hljs-title,
+.hljs-rule .hljs-property,
+.django .hljs-tag .hljs-keyword {
+  color: #000080;
+  font-weight: normal;
+}
+
+.hljs-attribute,
+.hljs-variable,
+.lisp .hljs-body,
+.hljs-name {
+  color: #008080;
+}
+
+.hljs-regexp {
+  color: #009926;
+}
+
+.hljs-symbol,
+.ruby .hljs-symbol .hljs-string,
+.lisp .hljs-keyword,
+.clojure .hljs-keyword,
+.scheme .hljs-keyword,
+.tex .hljs-special,
+.hljs-prompt {
+  color: #990073;
+}
+
+.hljs-built_in {
+  color: #0086b3;
+}
+
+.hljs-preprocessor,
+.hljs-pragma,
+.hljs-pi,
+.hljs-doctype,
+.hljs-shebang,
+.hljs-cdata {
+  color: #999;
+  font-weight: bold;
+}
+
+.hljs-deletion {
+  background: #fdd;
+}
+
+.hljs-addition {
+  background: #dfd;
+}
+
+.diff .hljs-change {
+  background: #0086b3;
+}
+
+.hljs-chunk {
+  color: #aaa;
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
static/wangEditor/css/wangEditor.min.css


BIN
static/wangEditor/fonts/icomoon.eot


+ 76 - 0
static/wangEditor/fonts/icomoon.svg

@@ -0,0 +1,76 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata>Generated by IcoMoon</metadata>
+<defs>
+<font id="icomoon" horiz-adv-x="1024">
+<font-face units-per-em="1024" ascent="960" descent="-64" />
+<missing-glyph horiz-adv-x="1024" />
+<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
+<glyph unicode="&#xe800;" glyph-name="link" horiz-adv-x="950" d="M831.488 264.704q0 23.552-15.36 38.912l-118.784 118.784q-16.384 16.384-38.912 16.384-24.576 0-40.96-18.432 1.024-1.024 10.24-10.24t12.288-12.288 9.216-11.264 7.168-14.336 2.048-15.36q0-23.552-16.384-38.912t-38.912-16.384q-8.192 0-15.36 2.048t-14.336 7.168-11.264 9.216-12.288 12.288-10.24 10.24q-19.456-17.408-19.456-40.96t16.384-38.912l117.76-118.784q15.36-15.36 38.912-15.36 22.528 0 38.912 15.36l83.968 82.944q15.36 16.384 15.36 37.888zM430.080 668.16q0 22.528-15.36 38.912l-117.76 117.76q-16.384 16.384-38.912 16.384t-38.912-15.36l-83.968-83.968q-16.384-15.36-16.384-37.888t16.384-38.912l118.784-118.784q15.36-15.36 38.912-15.36t40.96 17.408q-2.048 2.048-11.264 11.264t-12.288 12.288-8.192 10.24-7.168 14.336-2.048 16.384q0 22.528 15.36 38.912t38.912 15.36q9.216 0 16.384-2.048t14.336-7.168 10.24-8.192 12.288-12.288 11.264-11.264q18.432 17.408 18.432 41.984zM942.080 264.704q0-68.608-49.152-115.712l-83.968-82.944q-47.104-48.128-115.712-48.128-69.632 0-116.736 49.152l-117.76 117.76q-47.104 48.128-47.104 116.736 0 69.632 50.176 118.784l-50.176 50.176q-49.152-50.176-118.784-50.176-68.608 0-116.736 48.128l-118.784 118.784q-48.128 48.128-48.128 116.736t48.128 115.712l83.968 83.968q48.128 47.104 116.736 47.104t116.736-48.128l116.736-118.784q48.128-47.104 48.128-115.712 0-70.656-50.176-119.808l50.176-50.176q49.152 50.176 118.784 50.176 68.608 0 116.736-48.128l118.784-118.784q48.128-48.128 48.128-116.736z" />
+<glyph unicode="&#xe801;" glyph-name="unlink" horiz-adv-x="950" d="M250.88 233.984l-146.432-146.432q-5.12-5.12-13.312-5.12-6.144 0-13.312 5.12-5.12 5.12-5.12 13.312t5.12 13.312l146.432 145.408q6.144 5.12 13.312 5.12t13.312-5.12q5.12-5.12 5.12-12.288t-5.12-13.312zM347.136 210.432v-183.296q0-8.192-5.12-13.312t-13.312-5.12-12.288 5.12-5.12 13.312v183.296q0 8.192 5.12 13.312t12.288 5.12 13.312-5.12 5.12-13.312zM219.136 338.432q0-8.192-5.12-13.312t-13.312-5.12h-182.272q-8.192 0-13.312 5.12t-5.12 13.312 5.12 13.312 13.312 5.12h182.272q8.192 0 13.312-5.12t5.12-13.312zM942.080 264.704q0-68.608-49.152-115.712l-83.968-82.944q-47.104-48.128-115.712-48.128-69.632 0-116.736 49.152l-190.464 191.488q-12.288 11.264-24.576 31.744l137.216 10.24 155.648-156.672q15.36-15.36 38.912-15.36t38.912 15.36l83.968 82.944q15.36 16.384 15.36 37.888 0 23.552-15.36 38.912l-156.672 157.696 10.24 136.192q20.48-12.288 31.744-23.552l192.512-192.512q48.128-49.152 48.128-116.736zM588.8 678.4l-136.192-10.24-155.648 156.672q-16.384 16.384-38.912 16.384t-38.912-15.36l-83.968-83.968q-16.384-15.36-16.384-37.888t16.384-38.912l156.672-156.672-10.24-137.216q-20.48 12.288-32.768 24.576l-191.488 191.488q-48.128 49.152-48.128 116.736 0 68.608 48.128 115.712l83.968 83.968q48.128 47.104 116.736 47.104t116.736-48.128l190.464-191.488q12.288-12.288 23.552-32.768zM951.296 631.296q0-8.192-5.12-13.312t-13.312-5.12h-183.296q-8.192 0-13.312 5.12t-5.12 13.312 5.12 12.288 13.312 5.12h183.296q8.192 0 13.312-5.12t5.12-12.288zM640 941.568v-182.272q0-8.192-5.12-13.312t-13.312-5.12-13.312 5.12-5.12 13.312v182.272q0 8.192 5.12 13.312t13.312 5.12 13.312-5.12 5.12-13.312zM872.448 855.552l-146.432-146.432q-6.144-5.12-13.312-5.12t-12.288 5.12q-5.12 6.144-5.12 13.312t5.12 13.312l145.408 145.408q6.144 5.12 13.312 5.12t13.312-5.12q5.12-5.12 5.12-12.288t-5.12-13.312z" />
+<glyph unicode="&#xe802;" glyph-name="code" horiz-adv-x="1097" d="M352.256 160.256l-28.672-28.672q-5.12-5.12-12.288-5.12t-13.312 5.12l-266.24 266.24q-6.144 6.144-6.144 13.312t6.144 13.312l266.24 266.24q5.12 6.144 13.312 6.144t12.288-6.144l28.672-28.672q6.144-5.12 6.144-13.312t-6.144-12.288l-224.256-225.28 224.256-224.256q6.144-6.144 6.144-13.312t-6.144-13.312zM690.176 770.56l-212.992-738.304q-2.048-7.168-9.216-11.264t-13.312-1.024l-34.816 9.216q-8.192 3.072-11.264 9.216t-2.048 14.336l212.992 737.28q3.072 8.192 9.216 11.264t13.312 2.048l35.84-10.24q7.168-2.048 11.264-9.216t1.024-13.312zM1065.984 397.824l-266.24-266.24q-6.144-5.12-13.312-5.12t-13.312 5.12l-28.672 28.672q-5.12 6.144-5.12 13.312t5.12 13.312l224.256 224.256-224.256 225.28q-5.12 5.12-5.12 12.288t5.12 13.312l28.672 28.672q6.144 6.144 13.312 6.144t13.312-6.144l266.24-266.24q5.12-5.12 5.12-13.312t-5.12-13.312z" />
+<glyph unicode="&#xe803;" glyph-name="cancel" horiz-adv-x="804" d="M741.376 204.288q0-22.528-15.36-38.912l-77.824-77.824q-16.384-15.36-38.912-15.36t-38.912 15.36l-167.936 168.96-167.936-168.96q-16.384-15.36-38.912-15.36t-38.912 15.36l-77.824 77.824q-16.384 16.384-16.384 38.912t16.384 38.912l167.936 167.936-167.936 167.936q-16.384 16.384-16.384 38.912t16.384 38.912l77.824 77.824q16.384 16.384 38.912 16.384t38.912-16.384l167.936-167.936 167.936 167.936q16.384 16.384 38.912 16.384t38.912-16.384l77.824-77.824q15.36-15.36 15.36-38.912t-15.36-38.912l-167.936-167.936 167.936-167.936q15.36-15.36 15.36-38.912z" />
+<glyph unicode="&#xe804;" glyph-name="terminal" horiz-adv-x="950" d="M333.824 397.824l-266.24-266.24q-5.12-5.12-12.288-5.12t-13.312 5.12l-28.672 28.672q-6.144 6.144-6.144 13.312t6.144 13.312l224.256 224.256-224.256 225.28q-6.144 5.12-6.144 12.288t6.144 13.312l28.672 28.672q5.12 6.144 13.312 6.144t12.288-6.144l266.24-266.24q6.144-5.12 6.144-13.312t-6.144-13.312zM951.296 136.704v-35.84q0-8.192-5.12-13.312t-13.312-5.12h-548.864q-8.192 0-13.312 5.12t-5.12 13.312v35.84q0 8.192 5.12 13.312t13.312 5.12h548.864q8.192 0 13.312-5.12t5.12-13.312z" />
+<glyph unicode="&#xe805;" glyph-name="angle-down" horiz-adv-x="657" d="M614.4 539.136q0-7.168-6.144-13.312l-266.24-266.24q-5.12-5.12-13.312-5.12t-12.288 5.12l-266.24 266.24q-6.144 6.144-6.144 13.312t6.144 13.312l27.648 28.672q6.144 6.144 13.312 6.144t13.312-6.144l224.256-224.256 225.28 224.256q5.12 6.144 13.312 6.144t12.288-6.144l28.672-28.672q6.144-5.12 6.144-13.312z" />
+<glyph unicode="&#xe806;" glyph-name="font" horiz-adv-x="950" d="M414.72 640.512l-97.28-257.024q18.432 0 77.824-1.024t91.136-1.024q11.264 0 32.768 1.024-49.152 144.384-104.448 258.048zM0 8.704l1.024 45.056q13.312 4.096 31.744 7.168t32.768 6.144 28.672 8.192 25.6 17.408 17.408 28.672l135.168 352.256 159.744 413.696h73.728q4.096-8.192 6.144-12.288l116.736-274.432q19.456-45.056 61.44-147.456t64.512-156.672q9.216-19.456 33.792-82.944t40.96-96.256q11.264-25.6 19.456-31.744 11.264-9.216 50.176-17.408t48.128-11.264q4.096-22.528 4.096-32.768 0-2.048-1.024-7.168t0-8.192q-35.84 0-108.544 5.12t-109.568 4.096q-43.008 0-122.88-4.096t-101.376-4.096q0 24.576 2.048 44.032l74.752 16.384q1.024 0 7.168 1.024t9.216 2.048 8.192 3.072 9.216 4.096 6.144 4.096 5.12 6.144 1.024 8.192q0 9.216-17.408 55.296t-40.96 101.376-24.576 57.344l-257.024 1.024q-14.336-33.792-44.032-111.616t-28.672-93.184q0-12.288 8.192-21.504t24.576-14.336 27.648-7.168 32.768-5.12 23.552-2.048q1.024-11.264 1.024-32.768 0-5.12-2.048-16.384-32.768 0-99.328 6.144t-99.328 6.144q-5.12 0-15.36-3.072t-12.288-2.048q-46.080-8.192-107.52-8.192z" />
+<glyph unicode="&#xe807;" glyph-name="bold" horiz-adv-x="804" d="M317.44 90.624q41.984-18.432 79.872-18.432 215.040 0 215.040 191.488 0 65.536-23.552 103.424-15.36 24.576-35.84 41.984t-37.888 26.624-46.080 14.336-48.128 6.144-54.272 1.024q-40.96 0-57.344-6.144 0-29.696 0-90.112t-1.024-91.136q0-4.096 0-37.888t0-55.296 2.048-48.128 7.168-37.888zM309.248 517.632q23.552-4.096 62.464-4.096 47.104 0 81.92 7.168t62.464 25.6 41.984 51.2 15.36 80.896q0 39.936-16.384 69.632t-46.080 47.104-61.44 24.576-70.656 8.192q-28.672 0-74.752-7.168 0-28.672 3.072-86.016t2.048-87.040q0-15.36 0-46.080t-1.024-45.056q0-26.624 1.024-38.912zM0 8.704l1.024 54.272q9.216 2.048 49.152 9.216t60.416 15.36q4.096 7.168 7.168 15.36t4.096 19.456 4.096 18.432 1.024 21.504 0 19.456v36.864q0 561.152-12.288 585.728-2.048 5.12-12.288 8.192t-25.6 6.144-28.672 4.096-27.648 3.072-17.408 2.048l-2.048 47.104q56.32 1.024 194.56 6.144t212.992 5.12q13.312 0 38.912 0t38.912 0q39.936 0 77.824-7.168t73.728-24.576 61.44-39.936 41.984-60.416 16.384-77.824q0-29.696-9.216-55.296t-22.528-40.96-36.864-32.768-41.984-25.6-48.128-22.528q88.064-20.48 147.456-76.8t58.368-142.336q0-56.32-20.48-102.4t-53.248-74.752-78.848-48.128-93.184-27.648-100.352-8.192q-25.6 0-75.776 2.048t-75.776 1.024q-60.416 0-175.104-6.144t-132.096-7.168z" />
+<glyph unicode="&#xe808;" glyph-name="italic" horiz-adv-x="585" d="M0 9.728l10.24 49.152q3.072 1.024 46.080 12.288t63.488 21.504q16.384 19.456 23.552 57.344 1.024 4.096 35.84 165.888t64.512 310.272 29.696 168.96v14.336q-13.312 7.168-30.72 11.264t-39.936 4.096-32.768 3.072l10.24 59.392q19.456-2.048 68.608-4.096t86.016-4.096 68.608-1.024q27.648 0 56.32 1.024t68.608 4.096 56.32 4.096q-2.048-22.528-10.24-51.2-17.408-6.144-58.368-16.384t-61.44-19.456q-5.12-10.24-8.192-23.552t-5.12-23.552-4.096-25.6-4.096-24.576q-15.36-83.968-50.176-239.616t-44.032-202.752q-1.024-5.12-7.168-32.768t-11.264-52.224-9.216-47.104-4.096-32.768l1.024-10.24q9.216-3.072 105.472-18.432-2.048-24.576-9.216-56.32-6.144 0-18.432-1.024t-18.432-1.024q-16.384 0-50.176 6.144t-49.152 6.144q-78.848 1.024-117.76 1.024-28.672 0-80.896-5.12t-69.632-6.144z" />
+<glyph unicode="&#xe809;" glyph-name="header" d="M961.536 8.704q-25.6 0-75.776 2.048t-76.8 2.048q-24.576 0-74.752-2.048t-75.776-2.048q-14.336 0-21.504 12.288t-7.168 25.6q0 17.408 9.216 26.624t22.528 9.216 29.696 4.096 25.6 9.216q18.432 11.264 18.432 79.872v223.232q0 12.288-1.024 17.408-7.168 3.072-28.672 3.072h-385.024q-22.528 0-29.696-3.072 0-5.12 0-17.408l-1.024-211.968q0-80.896 21.504-94.208 9.216-5.12 26.624-7.168t32.768-2.048 25.6-8.192 11.264-26.624q0-14.336-7.168-26.624t-20.48-13.312q-26.624 0-79.872 2.048t-78.848 2.048q-24.576 0-72.704-2.048t-72.704-2.048q-13.312 0-20.48 12.288t-7.168 25.6q0 17.408 9.216 25.6t20.48 10.24 26.624 4.096 24.576 9.216q18.432 13.312 18.432 81.92l-1.024 31.744v464.896q0 2.048 1.024 14.336t0 21.504-1.024 21.504-2.048 24.576-4.096 20.48-6.144 18.432-9.216 10.24q-8.192 5.12-25.6 6.144t-29.696 2.048-23.552 7.168-10.24 26.624q0 14.336 6.144 26.624t20.48 13.312q26.624 0 79.872-2.048t78.848-2.048q23.552 0 72.704 2.048t71.68 2.048q14.336 0 21.504-13.312t7.168-26.624q0-17.408-9.216-25.6t-22.528-8.192-28.672-2.048-24.576-7.168q-19.456-12.288-19.456-92.16l1.024-182.272q0-12.288 0-18.432 7.168-2.048 22.528-2.048h399.36q14.336 0 21.504 2.048 1.024 6.144 1.024 18.432v182.272q0 79.872-19.456 92.16-10.24 6.144-33.792 7.168t-37.888 7.168-14.336 28.672q0 14.336 7.168 26.624t21.504 13.312q24.576 0 75.776-2.048t74.752-2.048q24.576 0 73.728 2.048t73.728 2.048q14.336 0 21.504-13.312t7.168-26.624q0-17.408-10.24-25.6t-22.528-8.192-29.696-2.048-24.576-7.168q-20.48-13.312-20.48-92.16l1.024-538.624q0-67.584 19.456-79.872 9.216-6.144 25.6-7.168t30.72-3.072 23.552-9.216 10.24-24.576q0-15.36-6.144-27.648t-20.48-13.312z" />
+<glyph unicode="&#xe80a;" glyph-name="align-left" d="M1024 192v-72.704q0-15.36-11.264-25.6t-25.6-11.264h-950.272q-15.36 0-25.6 11.264t-11.264 25.6v72.704q0 15.36 11.264 25.6t25.6 11.264h950.272q15.36 0 25.6-11.264t11.264-25.6zM804.864 411.136v-72.704q0-15.36-11.264-25.6t-25.6-11.264h-731.136q-15.36 0-25.6 11.264t-11.264 25.6v72.704q0 15.36 11.264 25.6t25.6 11.264h731.136q15.36 0 25.6-11.264t11.264-25.6zM951.296 631.296v-73.728q0-14.336-11.264-25.6t-25.6-11.264h-877.568q-15.36 0-25.6 11.264t-11.264 25.6v73.728q0 14.336 11.264 25.6t25.6 10.24h877.568q14.336 0 25.6-10.24t11.264-25.6zM731.136 850.432v-73.728q0-14.336-10.24-25.6t-25.6-10.24h-658.432q-15.36 0-25.6 10.24t-11.264 25.6v73.728q0 14.336 11.264 25.6t25.6 11.264h658.432q14.336 0 25.6-11.264t10.24-25.6z" />
+<glyph unicode="&#xe80b;" glyph-name="align-center" d="M1024 192v-72.704q0-15.36-11.264-25.6t-25.6-11.264h-950.272q-15.36 0-25.6 11.264t-11.264 25.6v72.704q0 15.36 11.264 25.6t25.6 11.264h950.272q15.36 0 25.6-11.264t11.264-25.6zM804.864 411.136v-72.704q0-15.36-11.264-25.6t-25.6-11.264h-512q-14.336 0-25.6 11.264t-11.264 25.6v72.704q0 15.36 11.264 25.6t25.6 11.264h512q15.36 0 25.6-11.264t11.264-25.6zM951.296 631.296v-73.728q0-14.336-11.264-25.6t-25.6-11.264h-804.864q-14.336 0-25.6 11.264t-11.264 25.6v73.728q0 14.336 11.264 25.6t25.6 10.24h804.864q14.336 0 25.6-10.24t11.264-25.6zM731.136 850.432v-73.728q0-14.336-10.24-25.6t-25.6-10.24h-366.592q-14.336 0-25.6 10.24t-10.24 25.6v73.728q0 14.336 10.24 25.6t25.6 11.264h366.592q14.336 0 25.6-11.264t10.24-25.6z" />
+<glyph unicode="&#xe80c;" glyph-name="align-right" d="M1024 192v-72.704q0-15.36-11.264-25.6t-25.6-11.264h-950.272q-15.36 0-25.6 11.264t-11.264 25.6v72.704q0 15.36 11.264 25.6t25.6 11.264h950.272q15.36 0 25.6-11.264t11.264-25.6zM1024 411.136v-72.704q0-15.36-11.264-25.6t-25.6-11.264h-731.136q-14.336 0-25.6 11.264t-11.264 25.6v72.704q0 15.36 11.264 25.6t25.6 11.264h731.136q15.36 0 25.6-11.264t11.264-25.6zM1024 631.296v-73.728q0-14.336-11.264-25.6t-25.6-11.264h-877.568q-14.336 0-25.6 11.264t-11.264 25.6v73.728q0 14.336 11.264 25.6t25.6 10.24h877.568q15.36 0 25.6-10.24t11.264-25.6zM1024 850.432v-73.728q0-14.336-11.264-25.6t-25.6-10.24h-658.432q-14.336 0-25.6 10.24t-10.24 25.6v73.728q0 14.336 10.24 25.6t25.6 11.264h658.432q15.36 0 25.6-11.264t11.264-25.6z" />
+<glyph unicode="&#xe80d;" glyph-name="list-bullet" d="M219.136 155.136q0-45.056-31.744-77.824t-77.824-31.744-77.824 31.744-31.744 77.824 31.744 77.824 77.824 31.744 77.824-31.744 31.744-77.824zM219.136 448q0-46.080-31.744-77.824t-77.824-31.744-77.824 31.744-31.744 77.824 31.744 77.824 77.824 31.744 77.824-31.744 31.744-77.824zM1024 210.432v-109.568q0-7.168-5.12-13.312t-13.312-5.12h-694.272q-8.192 0-13.312 5.12t-5.12 13.312v109.568q0 7.168 5.12 12.288t13.312 6.144h694.272q7.168 0 13.312-6.144t5.12-12.288zM219.136 740.864q0-46.080-31.744-77.824t-77.824-31.744-77.824 31.744-31.744 77.824 31.744 77.824 77.824 31.744 77.824-31.744 31.744-77.824zM1024 503.296v-110.592q0-7.168-5.12-12.288t-13.312-5.12h-694.272q-8.192 0-13.312 5.12t-5.12 12.288v110.592q0 7.168 5.12 12.288t13.312 5.12h694.272q7.168 0 13.312-5.12t5.12-12.288zM1024 795.136v-109.568q0-7.168-5.12-12.288t-13.312-6.144h-694.272q-8.192 0-13.312 6.144t-5.12 12.288v109.568q0 8.192 5.12 13.312t13.312 5.12h694.272q7.168 0 13.312-5.12t5.12-13.312z" />
+<glyph unicode="&#xe80e;" glyph-name="indent-left" d="M219.136 648.704v-328.704q0-7.168-5.12-13.312t-13.312-5.12q-7.168 0-12.288 5.12l-164.864 164.864q-5.12 5.12-5.12 13.312t5.12 13.312l164.864 163.84q5.12 5.12 12.288 5.12 8.192 0 13.312-5.12t5.12-13.312zM1024 210.432v-109.568q0-7.168-5.12-13.312t-13.312-5.12h-987.136q-7.168 0-13.312 5.12t-5.12 13.312v109.568q0 7.168 5.12 12.288t13.312 6.144h987.136q7.168 0 13.312-6.144t5.12-12.288zM1024 429.568v-109.568q0-7.168-5.12-13.312t-13.312-5.12h-621.568q-7.168 0-13.312 5.12t-5.12 13.312v109.568q0 7.168 5.12 13.312t13.312 5.12h621.568q7.168 0 13.312-5.12t5.12-13.312zM1024 648.704v-109.568q0-7.168-5.12-12.288t-13.312-6.144h-621.568q-7.168 0-13.312 6.144t-5.12 12.288v109.568q0 8.192 5.12 13.312t13.312 5.12h621.568q7.168 0 13.312-5.12t5.12-13.312zM1024 868.864v-109.568q0-8.192-5.12-13.312t-13.312-5.12h-987.136q-7.168 0-13.312 5.12t-5.12 13.312v109.568q0 7.168 5.12 12.288t13.312 6.144h987.136q7.168 0 13.312-6.144t5.12-12.288z" />
+<glyph unicode="&#xe80f;" glyph-name="indent-right" d="M200.704 484.864q0-8.192-5.12-13.312l-163.84-164.864q-5.12-5.12-13.312-5.12-7.168 0-13.312 5.12t-5.12 13.312v328.704q0 8.192 5.12 13.312t13.312 5.12 13.312-5.12l163.84-163.84q5.12-5.12 5.12-13.312zM1024 210.432v-109.568q0-7.168-5.12-13.312t-13.312-5.12h-987.136q-7.168 0-13.312 5.12t-5.12 13.312v109.568q0 7.168 5.12 12.288t13.312 6.144h987.136q7.168 0 13.312-6.144t5.12-12.288zM1024 429.568v-109.568q0-7.168-5.12-13.312t-13.312-5.12h-621.568q-7.168 0-13.312 5.12t-5.12 13.312v109.568q0 7.168 5.12 13.312t13.312 5.12h621.568q7.168 0 13.312-5.12t5.12-13.312zM1024 648.704v-109.568q0-7.168-5.12-12.288t-13.312-6.144h-621.568q-7.168 0-13.312 6.144t-5.12 12.288v109.568q0 8.192 5.12 13.312t13.312 5.12h621.568q7.168 0 13.312-5.12t5.12-13.312zM1024 868.864v-109.568q0-8.192-5.12-13.312t-13.312-5.12h-987.136q-7.168 0-13.312 5.12t-5.12 13.312v109.568q0 7.168 5.12 12.288t13.312 6.144h987.136q7.168 0 13.312-6.144t5.12-12.288z" />
+<glyph unicode="&#xe810;" glyph-name="list-numbered" d="M218.112 34.304q0-46.080-31.744-71.68t-76.8-26.624q-61.44 0-98.304 37.888l31.744 50.176q28.672-25.6 61.44-25.6 16.384 0 28.672 8.192t12.288 24.576q0 35.84-60.416 31.744l-14.336 31.744q4.096 6.144 18.432 24.576t24.576 31.744 20.48 21.504v1.024q-9.216 0-27.648-1.024t-27.648 0v-30.72h-60.416v87.040h190.464v-50.176l-54.272-66.56q28.672-6.144 46.080-27.648t17.408-50.176zM219.136 392.704v-91.136h-206.848q-4.096 20.48-4.096 30.72 0 29.696 14.336 53.248t31.744 38.912 37.888 27.648 31.744 24.576 14.336 25.6q0 14.336-9.216 22.528t-22.528 7.168q-25.6 0-46.080-32.768l-48.128 33.792q13.312 28.672 40.96 45.056t60.416 16.384q40.96 0 69.632-23.552t28.672-64.512q0-28.672-19.456-52.224t-43.008-36.864-43.008-28.672-20.48-30.72h72.704v34.816h60.416zM1024 210.432v-109.568q0-8.192-5.12-13.312t-13.312-5.12h-694.272q-8.192 0-13.312 5.12t-5.12 13.312v109.568q0 8.192 5.12 13.312t13.312 5.12h694.272q7.168 0 13.312-6.144t5.12-12.288zM219.136 724.48v-57.344h-191.488v57.344h61.44q0 22.528 0 69.632t1.024 68.608v7.168h-1.024q-5.12-10.24-28.672-30.72l-40.96 43.008 77.824 72.704h60.416v-230.4h61.44zM1024 503.296v-110.592q0-7.168-5.12-12.288t-13.312-5.12h-694.272q-8.192 0-13.312 5.12t-5.12 12.288v110.592q0 7.168 5.12 12.288t13.312 5.12h694.272q7.168 0 13.312-5.12t5.12-12.288zM1024 795.136v-109.568q0-7.168-5.12-12.288t-13.312-6.144h-694.272q-8.192 0-13.312 6.144t-5.12 12.288v109.568q0 8.192 5.12 13.312t13.312 5.12h694.272q7.168 0 13.312-5.12t5.12-13.312z" />
+<glyph unicode="&#xe811;" glyph-name="underline" horiz-adv-x="878" d="M27.648 833.024q-21.504 1.024-25.6 2.048l-2.048 50.176q7.168 0 22.528 0 34.816 0 64.512-2.048 75.776-4.096 94.208-4.096 49.152 0 96.256 2.048 66.56 2.048 83.968 3.072 31.744 0 49.152 1.024l-1.024-8.192 1.024-36.864v-5.12q-33.792-5.12-70.656-5.12-33.792 0-45.056-14.336-7.168-7.168-7.168-74.752 0-8.192 0-18.432t0-15.36l1.024-131.072 8.192-159.744q3.072-70.656 28.672-114.688 20.48-33.792 55.296-53.248 50.176-26.624 100.352-26.624 59.392 0 109.568 16.384 31.744 10.24 56.32 28.672 27.648 20.48 37.888 36.864 20.48 31.744 29.696 64.512 12.288 41.984 12.288 131.072 0 45.056-2.048 73.728t-6.144 69.632-8.192 91.136l-2.048 33.792q-3.072 37.888-13.312 50.176-19.456 20.48-44.032 19.456l-57.344-1.024-8.192 2.048 1.024 49.152h48.128l116.736-6.144q44.032-2.048 112.64 6.144l10.24-2.048q3.072-21.504 3.072-28.672 0-4.096-2.048-17.408-25.6-7.168-48.128-8.192-41.984-6.144-45.056-9.216-8.192-9.216-8.192-23.552 0-4.096 0-15.36t1.024-17.408q5.12-11.264 13.312-226.304 3.072-111.616-9.216-174.080-8.192-43.008-23.552-69.632-21.504-36.864-63.488-70.656-43.008-32.768-104.448-50.176-62.464-19.456-145.408-19.456-95.232 0-162.816 26.624t-101.376 69.632q-34.816 43.008-48.128 111.616-9.216 45.056-9.216 135.168v190.464q0 107.52-9.216 121.856-14.336 20.48-83.968 21.504zM877.568 27.136v36.864q0 8.192-5.12 13.312t-13.312 5.12h-840.704q-8.192 0-13.312-5.12t-5.12-13.312v-36.864q0-8.192 5.12-13.312t13.312-5.12h840.704q8.192 0 13.312 5.12t5.12 13.312z" />
+<glyph unicode="&#xe812;" glyph-name="table" horiz-adv-x="950" d="M292.864 173.568v109.568q0 8.192-5.12 13.312t-13.312 5.12h-183.296q-7.168 0-13.312-5.12t-5.12-13.312v-109.568q0-8.192 5.12-13.312t13.312-5.12h183.296q8.192 0 13.312 5.12t5.12 13.312zM292.864 392.704v110.592q0 7.168-5.12 12.288t-13.312 5.12h-183.296q-7.168 0-13.312-5.12t-5.12-12.288v-110.592q0-7.168 5.12-12.288t13.312-5.12h183.296q8.192 0 13.312 5.12t5.12 12.288zM584.704 173.568v109.568q0 8.192-5.12 13.312t-12.288 5.12h-183.296q-8.192 0-13.312-5.12t-5.12-13.312v-109.568q0-8.192 5.12-13.312t13.312-5.12h183.296q7.168 0 12.288 5.12t5.12 13.312zM292.864 612.864v109.568q0 8.192-5.12 13.312t-13.312 5.12h-183.296q-7.168 0-13.312-5.12t-5.12-13.312v-109.568q0-8.192 5.12-13.312t13.312-5.12h183.296q8.192 0 13.312 5.12t5.12 13.312zM584.704 392.704v110.592q0 7.168-5.12 12.288t-12.288 5.12h-183.296q-8.192 0-13.312-5.12t-5.12-12.288v-110.592q0-7.168 5.12-12.288t13.312-5.12h183.296q7.168 0 12.288 5.12t5.12 12.288zM877.568 173.568v109.568q0 8.192-5.12 13.312t-13.312 5.12h-182.272q-8.192 0-13.312-5.12t-5.12-13.312v-109.568q0-8.192 5.12-13.312t13.312-5.12h182.272q8.192 0 13.312 5.12t5.12 13.312zM584.704 612.864v109.568q0 8.192-5.12 13.312t-12.288 5.12h-183.296q-8.192 0-13.312-5.12t-5.12-13.312v-109.568q0-8.192 5.12-13.312t13.312-5.12h183.296q7.168 0 12.288 5.12t5.12 13.312zM877.568 392.704v110.592q0 7.168-5.12 12.288t-13.312 5.12h-182.272q-8.192 0-13.312-5.12t-5.12-12.288v-110.592q0-7.168 5.12-12.288t13.312-5.12h182.272q8.192 0 13.312 5.12t5.12 12.288zM877.568 612.864v109.568q0 8.192-5.12 13.312t-13.312 5.12h-182.272q-8.192 0-13.312-5.12t-5.12-13.312v-109.568q0-8.192 5.12-13.312t13.312-5.12h182.272q8.192 0 13.312 5.12t5.12 13.312zM951.296 795.136v-621.568q0-37.888-27.648-64.512t-64.512-26.624h-768q-36.864 0-64.512 26.624t-26.624 64.512v621.568q0 37.888 26.624 64.512t64.512 27.648h768q37.888 0 64.512-27.648t27.648-64.512z" />
+<glyph unicode="&#xe813;" glyph-name="eraser" horiz-adv-x="1097" d="M512 155.136l192.512 220.16h-439.296l-192.512-220.16h439.296zM1090.56 770.56q9.216-19.456 6.144-40.96t-17.408-36.864l-512-585.728q-22.528-24.576-55.296-24.576h-439.296q-21.504 0-38.912 11.264t-27.648 31.744q-8.192 19.456-5.12 40.96t17.408 36.864l512 585.728q21.504 24.576 54.272 24.576h439.296q21.504 0 39.936-11.264t26.624-31.744z" />
+<glyph unicode="&#xe814;" glyph-name="text-height" d="M996.352 155.136q19.456 0 24.576-10.24t-6.144-25.6l-72.704-92.16q-11.264-15.36-27.648-15.36t-27.648 15.36l-72.704 92.16q-11.264 15.36-6.144 25.6t23.552 10.24h46.080v585.728h-46.080q-18.432 0-23.552 10.24t6.144 25.6l72.704 92.16q11.264 15.36 27.648 15.36t27.648-15.36l72.704-92.16q11.264-15.36 6.144-25.6t-24.576-10.24h-45.056v-585.728h45.056zM46.080 886.272l30.72-15.36q7.168-3.072 120.832-3.072 25.6 0 75.776 1.024t74.752 1.024q21.504 0 61.44 0t61.44 0h167.936q3.072 0 12.288 0t11.264 0 9.216 1.024 10.24 5.12 8.192 10.24l24.576 1.024q2.048 0 7.168-1.024t8.192 0q1.024-63.488 1.024-191.488 0-46.080-2.048-62.464-22.528-8.192-38.912-10.24-14.336 24.576-31.744 72.704-1.024 5.12-6.144 27.648t-8.192 41.984-4.096 20.48q-3.072 4.096-7.168 7.168t-8.192 3.072-8.192 1.024-10.24 1.024-9.216-1.024q-9.216 0-37.888 1.024t-43.008 0-35.84-1.024-40.96-4.096q-5.12-46.080-4.096-76.8 0-54.272 1.024-222.208t1.024-260.096q0-9.216-1.024-40.96t0-52.224 7.168-38.912q22.528-12.288 70.656-24.576t68.608-21.504q2.048-22.528 2.048-28.672 0-8.192-1.024-16.384l-19.456-1.024q-44.032-1.024-124.928 5.12t-117.76 5.12q-28.672 0-87.040-5.12t-86.016-5.12q-2.048 29.696-2.048 29.696v5.12q9.216 15.36 34.816 24.576t56.32 17.408 45.056 15.36q10.24 23.552 10.24 218.112 0 58.368-1.024 173.056t-2.048 174.080v66.56q0 1.024 0 8.192t1.024 14.336-1.024 15.36-2.048 13.312-3.072 8.192q-6.144 7.168-92.16 7.168-18.432 0-53.248-7.168t-45.056-15.36q-11.264-7.168-19.456-40.96t-18.432-63.488-24.576-30.72q-23.552 15.36-31.744 25.6v218.112z" />
+<glyph unicode="&#xe815;" glyph-name="brush" d="M922.624 960q39.936 0 70.656-26.624t29.696-66.56q0-35.84-25.6-86.016-189.44-359.424-266.24-430.080-55.296-52.224-123.904-52.224-72.704 0-123.904 53.248t-52.224 124.928q0 73.728 53.248 121.856l364.544 330.752q33.792 30.72 73.728 30.72zM403.456 369.152q22.528-43.008 60.416-74.752t86.016-43.008l1.024-40.96q2.048-121.856-73.728-197.632t-199.68-76.8q-69.632 0-123.904 26.624t-88.064 72.704-49.152 104.448-16.384 125.952q4.096-3.072 23.552-17.408t35.84-25.6 32.768-20.48 26.624-9.216q23.552 0 31.744 20.48 14.336 37.888 32.768 64.512t39.936 43.008 50.176 27.648 58.368 14.336 71.68 6.144z" />
+<glyph unicode="&#xe816;" glyph-name="pencil" horiz-adv-x="878" d="M207.872 82.432l51.2 52.224-134.144 134.144-52.224-52.224v-61.44h73.728v-72.704h61.44zM505.856 612.864q0 12.288-12.288 12.288-5.12 0-9.216-4.096l-310.272-309.248q-4.096-4.096-4.096-10.24 0-12.288 13.312-12.288 5.12 0 9.216 4.096l310.272 309.248q3.072 4.096 3.072 10.24zM475.136 722.432l237.568-237.568-475.136-476.16h-237.568v238.592zM865.28 667.136q0-29.696-20.48-51.2l-95.232-95.232-237.568 238.592 95.232 94.208q20.48 21.504 51.2 21.504 29.696 0 52.224-21.504l134.144-134.144q20.48-22.528 20.48-52.224z" />
+<glyph unicode="&#xe817;" glyph-name="minus" horiz-adv-x="804" d="M804.864 539.136v-109.568q0-22.528-16.384-38.912t-38.912-15.36h-694.272q-23.552 0-38.912 15.36t-16.384 38.912v109.568q0 23.552 16.384 38.912t38.912 16.384h694.272q22.528 0 38.912-16.384t16.384-38.912z" />
+<glyph unicode="&#xe818;" glyph-name="picture" horiz-adv-x="1097" d="M365.568 631.296q0-46.080-31.744-77.824t-77.824-32.768-77.824 32.768-31.744 77.824 31.744 76.8 77.824 32.768 77.824-32.768 31.744-76.8zM951.296 411.136v-256h-804.864v109.568l182.272 183.296 92.16-91.136 291.84 291.84zM1005.568 813.568h-914.432q-7.168 0-12.288-5.12t-6.144-13.312v-694.272q0-8.192 6.144-13.312t12.288-5.12h914.432q7.168 0 13.312 5.12t5.12 13.312v694.272q0 7.168-5.12 13.312t-13.312 5.12zM1096.704 795.136v-694.272q0-37.888-26.624-64.512t-64.512-27.648h-914.432q-36.864 0-64.512 27.648t-26.624 64.512v694.272q0 37.888 26.624 64.512t64.512 27.648h914.432q37.888 0 64.512-27.648t26.624-64.512z" />
+<glyph unicode="&#xe819;" glyph-name="file-image" horiz-adv-x="878" d="M838.656 742.912q16.384-16.384 27.648-43.008t11.264-51.2v-657.408q0-23.552-15.36-38.912t-38.912-16.384h-768q-23.552 0-38.912 16.384t-16.384 38.912v913.408q0 23.552 16.384 38.912t38.912 16.384h512q22.528 0 50.176-11.264t43.008-27.648zM584.704 882.176v-215.040h215.040q-5.12 16.384-12.288 23.552l-179.2 179.2q-6.144 7.168-23.552 12.288zM804.864 8.704v585.728h-237.568q-23.552 0-38.912 15.36t-16.384 38.912v238.592h-439.296v-878.592h732.16zM731.136 264.704v-182.272h-584.704v109.568l109.568 109.568 72.704-72.704 220.16 219.136zM256 375.296q-46.080 0-77.824 31.744t-31.744 77.824 31.744 77.824 77.824 31.744 77.824-31.744 31.744-77.824-31.744-77.824-77.824-31.744z" />
+<glyph unicode="&#xe81a;" glyph-name="cw" horiz-adv-x="878" d="M877.568 813.568v-256q0-14.336-10.24-25.6t-26.624-11.264h-256q-23.552 0-32.768 23.552-10.24 22.528 7.168 38.912l78.848 78.848q-83.968 78.848-198.656 78.848-59.392 0-113.664-23.552t-93.184-62.464-63.488-93.184-22.528-113.664 22.528-113.664 63.488-93.184 93.184-62.464 113.664-23.552q67.584 0 128 29.696t102.4 83.968q4.096 6.144 13.312 7.168 8.192 0 14.336-5.12l77.824-78.848q5.12-4.096 6.144-11.264t-5.12-13.312q-61.44-75.776-150.528-116.736t-186.368-41.984q-89.088 0-171.008 34.816t-139.264 94.208-94.208 140.288-34.816 169.984 34.816 169.984 94.208 140.288 139.264 94.208 171.008 34.816q83.968 0 161.792-31.744t140.288-90.112l73.728 73.728q16.384 18.432 39.936 8.192 22.528-9.216 22.528-33.792z" />
+<glyph unicode="&#xe81b;" glyph-name="ccw" horiz-adv-x="878" d="M877.568 448q0-89.088-34.816-169.984t-93.184-140.288-140.288-94.208-169.984-34.816q-98.304 0-187.392 41.984t-150.528 116.736q-4.096 6.144-4.096 13.312t5.12 11.264l77.824 78.848q6.144 5.12 14.336 5.12 9.216-1.024 13.312-7.168 41.984-54.272 102.4-83.968t129.024-29.696q59.392 0 112.64 23.552t94.208 62.464 62.464 93.184 22.528 113.664-22.528 113.664-62.464 93.184-94.208 62.464-112.64 23.552q-56.32 0-107.52-20.48t-92.16-58.368l78.848-78.848q17.408-16.384 8.192-38.912-10.24-23.552-33.792-23.552h-256q-15.36 0-25.6 11.264t-11.264 25.6v256q0 24.576 22.528 33.792 22.528 10.24 39.936-8.192l73.728-73.728q61.44 58.368 140.288 90.112t162.816 31.744q89.088 0 169.984-34.816t140.288-94.208 93.184-140.288 34.816-169.984z" />
+<glyph unicode="&#xe900;" glyph-name="omega" d="M704 64h256l64 128v-256h-384v214.214c131.112 56.484 224 197.162 224 361.786 0 214.432-157.598 382.266-352 382.266-194.406 0-352-167.832-352-382.266 0-164.624 92.886-305.302 224-361.786v-214.214h-384v256l64-128h256v32.59c-187.63 66.46-320 227.402-320 415.41 0 247.424 229.23 448 512 448s512-200.576 512-448c0-188.008-132.37-348.95-320-415.41v-32.59z" />
+<glyph unicode="&#xe901;" glyph-name="cancel-circle" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512 32c-229.75 0-416 186.25-416 416s186.25 416 416 416 416-186.25 416-416-186.25-416-416-416zM672 704l-160-160-160 160-96-96 160-160-160-160 96-96 160 160 160-160 96 96-160 160 160 160z" />
+<glyph unicode="&#xe904;" glyph-name="newspaper" d="M896 704v128h-896v-704c0-35.346 28.654-64 64-64h864c53.022 0 96 42.978 96 96v544h-128zM832 128h-768v640h768v-640zM128 640h640v-64h-640zM512 512h256v-64h-256zM512 384h256v-64h-256zM512 256h192v-64h-192zM128 512h320v-320h-320z" />
+<glyph unicode="&#xe90f;" glyph-name="camera" d="M304 352c0-114.876 93.124-208 208-208s208 93.124 208 208-93.124 208-208 208-208-93.124-208-208zM960 704h-224c-16 64-32 128-96 128h-256c-64 0-80-64-96-128h-224c-35.2 0-64-28.8-64-64v-576c0-35.2 28.8-64 64-64h896c35.2 0 64 28.8 64 64v576c0 35.2-28.8 64-64 64zM512 68c-156.85 0-284 127.148-284 284 0 156.85 127.15 284 284 284 156.852 0 284-127.15 284-284 0-156.852-127.146-284-284-284zM960 512h-128v64h128v-64z" />
+<glyph unicode="&#xe911;" glyph-name="music" d="M960 960h64v-736c0-88.366-100.29-160-224-160s-224 71.634-224 160c0 88.368 100.29 160 224 160 62.684 0 119.342-18.4 160-48.040v368.040l-512-113.778v-494.222c0-88.366-100.288-160-224-160s-224 71.634-224 160c0 88.368 100.288 160 224 160 62.684 0 119.342-18.4 160-48.040v624.040l576 128z" />
+<glyph unicode="&#xe912;" glyph-name="play" d="M981.188 799.892c-143.632 20.65-302.332 32.108-469.186 32.108-166.86 0-325.556-11.458-469.194-32.108-27.53-107.726-42.808-226.75-42.808-351.892 0-125.14 15.278-244.166 42.808-351.89 143.638-20.652 302.336-32.11 469.194-32.11 166.854 0 325.552 11.458 469.186 32.11 27.532 107.724 42.812 226.75 42.812 351.89 0 125.142-15.28 244.166-42.812 351.892zM384.002 256v384l320-192-320-192z" />
+<glyph unicode="&#xe914;" glyph-name="video-camera" d="M384 672c0 88.366 71.634 160 160 160s160-71.634 160-160c0-88.366-71.634-160-160-160s-160 71.634-160 160zM0 672c0 88.366 71.634 160 160 160s160-71.634 160-160c0-88.366-71.634-160-160-160s-160 71.634-160 160zM768 352v96c0 35.2-28.8 64-64 64h-640c-35.2 0-64-28.8-64-64v-320c0-35.2 28.8-64 64-64h640c35.2 0 64 28.8 64 64v96l256-160v448l-256-160zM640 192h-512v192h512v-192z" />
+<glyph unicode="&#xe92b;" glyph-name="file-zip" d="M917.806 730.924c-22.208 30.292-53.174 65.7-87.178 99.704s-69.412 64.964-99.704 87.178c-51.574 37.82-76.592 42.194-90.924 42.194h-496c-44.112 0-80-35.888-80-80v-864c0-44.112 35.884-80 80-80h736c44.112 0 80 35.888 80 80v624c0 14.332-4.372 39.35-42.194 90.924v0 0zM785.374 785.374c30.7-30.7 54.8-58.398 72.58-81.374h-153.954v153.946c22.98-17.78 50.678-41.878 81.374-72.572v0 0zM896 16c0-8.672-7.328-16-16-16h-736c-8.672 0-16 7.328-16 16v864c0 8.672 7.328 16 16 16 0 0 495.956 0.002 496 0v-224c0-17.672 14.322-32 32-32h224v-624zM256 896h128v-64h-128v64zM384 832h128v-64h-128v64zM256 768h128v-64h-128v64zM384 704h128v-64h-128v64zM256 640h128v-64h-128v64zM384 576h128v-64h-128v64zM256 512h128v-64h-128v64zM384 448h128v-64h-128v64zM256 112c0-26.4 21.6-48 48-48h160c26.4 0 48 21.6 48 48v160c0 26.4-21.6 48-48 48h-80v64h-128v-272zM448 192v-64h-128v64h128z" />
+<glyph unicode="&#xe92e;" glyph-name="stack" d="M1024 640l-512 256-512-256 512-256 512 256zM512 811.030l342.058-171.030-342.058-171.030-342.058 171.030 342.058 171.030zM921.444 499.278l102.556-51.278-512-256-512 256 102.556 51.278 409.444-204.722zM921.444 307.278l102.556-51.278-512-256-512 256 102.556 51.278 409.444-204.722z" />
+<glyph unicode="&#xe93f;" glyph-name="credit-card" d="M928 832h-832c-52.8 0-96-43.2-96-96v-576c0-52.8 43.2-96 96-96h832c52.8 0 96 43.2 96 96v576c0 52.8-43.2 96-96 96zM96 768h832c17.346 0 32-14.654 32-32v-96h-896v96c0 17.346 14.654 32 32 32zM928 128h-832c-17.346 0-32 14.654-32 32v288h896v-288c0-17.346-14.654-32-32-32zM128 320h64v-128h-64zM256 320h64v-128h-64zM384 320h64v-128h-64z" />
+<glyph unicode="&#xe944;" glyph-name="address-book" d="M192 960v-1024h768v1024h-768zM576 703.67c70.51 0 127.67-57.16 127.67-127.67s-57.16-127.67-127.67-127.67-127.67 57.16-127.67 127.67 57.16 127.67 127.67 127.67v0zM768 192h-384v64c0 70.696 57.306 128 128 128v0h128c70.696 0 128-57.304 128-128v-64zM64 896h96v-192h-96v192zM64 640h96v-192h-96v192zM64 384h96v-192h-96v192zM64 128h96v-192h-96v192z" />
+<glyph unicode="&#xe945;" glyph-name="envelop" d="M928 832h-832c-52.8 0-96-43.2-96-96v-640c0-52.8 43.2-96 96-96h832c52.8 0 96 43.2 96 96v640c0 52.8-43.2 96-96 96zM398.74 409.628l-270.74-210.892v501.642l270.74-290.75zM176.38 704h671.24l-335.62-252-335.62 252zM409.288 398.302l102.712-110.302 102.71 110.302 210.554-270.302h-626.528l210.552 270.302zM625.26 409.628l270.74 290.75v-501.642l-270.74 210.892z" />
+<glyph unicode="&#xe947;" glyph-name="location" d="M512 960c-176.732 0-320-143.268-320-320 0-320 320-704 320-704s320 384 320 704c0 176.732-143.27 320-320 320zM512 448c-106.040 0-192 85.96-192 192s85.96 192 192 192 192-85.96 192-192-85.96-192-192-192z" />
+<glyph unicode="&#xe95c;" glyph-name="drawer" d="M1016.988 307.99l-256 320c-6.074 7.592-15.266 12.010-24.988 12.010h-448c-9.72 0-18.916-4.418-24.988-12.010l-256-320c-4.538-5.674-7.012-12.724-7.012-19.99v-288c0-35.346 28.654-64 64-64h896c35.348 0 64 28.654 64 64v288c0 7.266-2.472 14.316-7.012 19.99zM960 256h-224l-128-128h-192l-128 128h-224v20.776l239.38 299.224h417.24l239.38-299.224v-20.776zM736 448h-448c-17.672 0-32 14.328-32 32s14.328 32 32 32h448c17.674 0 32-14.328 32-32s-14.326-32-32-32zM800 320h-576c-17.672 0-32 14.326-32 32s14.328 32 32 32h576c17.674 0 32-14.326 32-32s-14.326-32-32-32z" />
+<glyph unicode="&#xe960;" glyph-name="download" d="M512 384l256 256h-192v256h-128v-256h-192zM744.726 488.728l-71.74-71.742 260.080-96.986-421.066-157.018-421.066 157.018 260.080 96.986-71.742 71.742-279.272-104.728v-256l512-192 512 192v256z" />
+<glyph unicode="&#xe961;" glyph-name="upload" d="M448 384h128v256h192l-256 256-256-256h192zM640 528v-98.712l293.066-109.288-421.066-157.018-421.066 157.018 293.066 109.288v98.712l-384-144v-256l512-192 512 192v256z" />
+<glyph unicode="&#xe977;" glyph-name="quotes-left" d="M225 512c123.712 0 224-100.29 224-224 0-123.712-100.288-224-224-224s-224 100.288-224 224l-1 32c0 247.424 200.576 448 448 448v-128c-85.474 0-165.834-33.286-226.274-93.726-11.634-11.636-22.252-24.016-31.83-37.020 11.438 1.8 23.16 2.746 35.104 2.746zM801 512c123.71 0 224-100.29 224-224 0-123.712-100.29-224-224-224s-224 100.288-224 224l-1 32c0 247.424 200.576 448 448 448v-128c-85.474 0-165.834-33.286-226.274-93.726-11.636-11.636-22.254-24.016-31.832-37.020 11.44 1.8 23.16 2.746 35.106 2.746z" />
+<glyph unicode="&#xe98b;" glyph-name="enlarge2" d="M1024 960v-416l-160 160-192-192-96 96 192 192-160 160zM448 288l-192-192 160-160h-416v416l160-160 192 192z" />
+<glyph unicode="&#xe98c;" glyph-name="shrink2" d="M448 384v-416l-160 160-192-192-96 96 192 192-160 160zM1024 864l-192-192 160-160h-416v416l160-160 192 192z" />
+<glyph unicode="&#xe98f;" glyph-name="lock" d="M592 512h-16v192c0 105.87-86.13 192-192 192h-128c-105.87 0-192-86.13-192-192v-192h-16c-26.4 0-48-21.6-48-48v-480c0-26.4 21.6-48 48-48h544c26.4 0 48 21.6 48 48v480c0 26.4-21.6 48-48 48zM192 704c0 35.29 28.71 64 64 64h128c35.29 0 64-28.71 64-64v-192h-256v192z" />
+<glyph unicode="&#xe990;" glyph-name="unlocked" d="M768 896c105.87 0 192-86.13 192-192v-192h-128v192c0 35.29-28.71 64-64 64h-128c-35.29 0-64-28.71-64-64v-192h16c26.4 0 48-21.6 48-48v-480c0-26.4-21.6-48-48-48h-544c-26.4 0-48 21.6-48 48v480c0 26.4 21.6 48 48 48h400v192c0 105.87 86.13 192 192 192h128z" />
+<glyph unicode="&#xe991;" glyph-name="wrench" d="M1002.934 142.124l-460.552 394.76c21.448 40.298 33.618 86.282 33.618 135.116 0 159.058-128.942 288-288 288-29.094 0-57.172-4.332-83.646-12.354l166.39-166.39c24.89-24.89 24.89-65.62 0-90.51l-101.49-101.49c-24.89-24.89-65.62-24.89-90.51 0l-166.39 166.39c-8.022-26.474-12.354-54.552-12.354-83.646 0-159.058 128.942-288 288-288 48.834 0 94.818 12.17 135.116 33.62l394.76-460.552c22.908-26.724 62.016-28.226 86.904-3.338l101.492 101.492c24.888 24.888 23.386 63.994-3.338 86.902z" />
+<glyph unicode="&#xe9ce;" glyph-name="eye" d="M512 768c-223.318 0-416.882-130.042-512-320 95.118-189.958 288.682-320 512-320 223.312 0 416.876 130.042 512 320-95.116 189.958-288.688 320-512 320zM764.45 598.296c60.162-38.374 111.142-89.774 149.434-150.296-38.292-60.522-89.274-111.922-149.436-150.296-75.594-48.218-162.89-73.704-252.448-73.704-89.56 0-176.858 25.486-252.452 73.704-60.158 38.372-111.138 89.772-149.432 150.296 38.292 60.524 89.274 111.924 149.434 150.296 3.918 2.5 7.876 4.922 11.86 7.3-9.96-27.328-15.41-56.822-15.41-87.596 0-141.382 114.616-256 256-256 141.382 0 256 114.618 256 256 0 30.774-5.452 60.268-15.408 87.598 3.978-2.378 7.938-4.802 11.858-7.302v0zM512 544c0-53.020-42.98-96-96-96s-96 42.98-96 96 42.98 96 96 96 96-42.982 96-96z" />
+<glyph unicode="&#xe9d1;" glyph-name="eye-blocked" d="M945.942 945.942c-18.746 18.744-49.136 18.744-67.882 0l-202.164-202.164c-51.938 15.754-106.948 24.222-163.896 24.222-223.318 0-416.882-130.042-512-320 41.122-82.124 100.648-153.040 173.022-207.096l-158.962-158.962c-18.746-18.746-18.746-49.136 0-67.882 9.372-9.374 21.656-14.060 33.94-14.060s24.568 4.686 33.942 14.058l864 864c18.744 18.746 18.744 49.138 0 67.884zM416 640c42.24 0 78.082-27.294 90.92-65.196l-121.724-121.724c-37.902 12.838-65.196 48.68-65.196 90.92 0 53.020 42.98 96 96 96zM110.116 448c38.292 60.524 89.274 111.924 149.434 150.296 3.918 2.5 7.876 4.922 11.862 7.3-9.962-27.328-15.412-56.822-15.412-87.596 0-54.89 17.286-105.738 46.7-147.418l-60.924-60.924c-52.446 36.842-97.202 83.882-131.66 138.342zM768 518c0 27.166-4.256 53.334-12.102 77.898l-321.808-321.808c24.568-7.842 50.742-12.090 77.91-12.090 141.382 0 256 114.618 256 256zM830.026 670.026l-69.362-69.362c1.264-0.786 2.53-1.568 3.786-2.368 60.162-38.374 111.142-89.774 149.434-150.296-38.292-60.522-89.274-111.922-149.436-150.296-75.594-48.218-162.89-73.704-252.448-73.704-38.664 0-76.902 4.76-113.962 14.040l-76.894-76.894c59.718-21.462 123.95-33.146 190.856-33.146 223.31 0 416.876 130.042 512 320-45.022 89.916-112.118 166.396-193.974 222.026z" />
+<glyph unicode="&#xe9df;" glyph-name="happy" d="M512-64c282.77 0 512 229.23 512 512s-229.23 512-512 512-512-229.23-512-512 229.23-512 512-512zM512 864c229.75 0 416-186.25 416-416s-186.25-416-416-416-416 186.25-416 416 186.25 416 416 416zM512 361.24c115.95 0 226.23 30.806 320 84.92-14.574-178.438-153.128-318.16-320-318.16-166.868 0-305.422 139.872-320 318.304 93.77-54.112 204.050-85.064 320-85.064zM256 608c0 53.019 28.654 96 64 96s64-42.981 64-96c0-53.019-28.654-96-64-96s-64 42.981-64 96zM640 608c0 53.019 28.654 96 64 96s64-42.981 64-96c0-53.019-28.654-96-64-96s-64 42.981-64 96z" />
+<glyph unicode="&#xea4e;" glyph-name="command" d="M736 64c-88.224 0-160 71.776-160 160v96h-128v-96c0-88.224-71.776-160-160-160s-160 71.776-160 160 71.776 160 160 160h96v128h-96c-88.224 0-160 71.776-160 160s71.776 160 160 160 160-71.776 160-160v-96h128v96c0 88.224 71.776 160 160 160s160-71.776 160-160-71.776-160-160-160h-96v-128h96c88.224 0 160-71.776 160-160s-71.774-160-160-160zM640 320v-96c0-52.934 43.066-96 96-96s96 43.066 96 96-43.066 96-96 96h-96zM288 320c-52.934 0-96-43.066-96-96s43.066-96 96-96 96 43.066 96 96v96h-96zM448 384h128v128h-128v-128zM640 576h96c52.934 0 96 43.066 96 96s-43.066 96-96 96-96-43.066-96-96v-96zM288 768c-52.934 0-96-43.066-96-96s43.066-96 96-96h96v96c0 52.934-43.064 96-96 96z" />
+<glyph unicode="&#xea5c;" glyph-name="font2" d="M799.596 943.792c-90.526 0-148.62 16.208-241.848 16.208-301.284 0-441.792-171.584-441.792-345.872 0-102.678 48.64-136.458 144.564-136.458-6.758 14.864-18.914 31.080-18.914 104.034 0 204.010 77.006 263.458 175.636 267.51 0 0-80.918-793.374-315.778-888.542v-24.672h316.594l108.026 512h197.844l44.072 128h-214.908l51.944 246.19c59.446-12.156 117.542-24.316 167.532-24.316 62.148 0 118.894 18.914 149.968 162.126-37.826-12.16-78.362-16.208-122.94-16.208z" />
+<glyph unicode="&#xea65;" glyph-name="strikethrough" d="M1024 448v-64h-234.506c27.504-38.51 42.506-82.692 42.506-128 0-70.878-36.66-139.026-100.58-186.964-59.358-44.518-137.284-69.036-219.42-69.036-82.138 0-160.062 24.518-219.42 69.036-63.92 47.938-100.58 116.086-100.58 186.964h128c0-69.382 87.926-128 192-128s192 58.618 192 128c0 69.382-87.926 128-192 128h-512v64h299.518c-2.338 1.654-4.656 3.324-6.938 5.036-63.92 47.94-100.58 116.086-100.58 186.964s36.66 139.024 100.58 186.964c59.358 44.518 137.282 69.036 219.42 69.036 82.136 0 160.062-24.518 219.42-69.036 63.92-47.94 100.58-116.086 100.58-186.964h-128c0 69.382-87.926 128-192 128s-192-58.618-192-128c0-69.382 87.926-128 192-128 78.978 0 154.054-22.678 212.482-64h299.518z" />
+<glyph unicode="&#xea67;" glyph-name="sigma" d="M941.606 225.292l44.394 94.708h38l-64-384h-960v74.242l331.546 391.212-331.546 331.546v227h980l44-256h-34.376l-18.72 38.88c-35.318 73.364-61.904 89.12-138.904 89.12h-662l353.056-353.056-297.42-350.944h542.364c116.008 0 146.648 41.578 173.606 97.292z" />
+<glyph unicode="&#xea68;" glyph-name="sigma2" d="M941.606 225.292l44.394 94.708h38l-64-384h-960v74.242l331.546 391.212-331.546 331.546v227h980l44-256h-34.376l-18.72 38.88c-35.318 73.364-61.904 89.12-138.904 89.12h-662l353.056-353.056-297.42-350.944h542.364c116.008 0 146.648 41.578 173.606 97.292z" />
+<glyph unicode="&#xeade;" glyph-name="libreoffice" d="M534.626 937.372c-12.444 12.444-37.026 22.628-54.626 22.628h-384c-17.6 0-32-14.4-32-32v-960c0-17.6 14.4-32 32-32h768c17.6 0 32 14.4 32 32v576c0 17.6-10.182 42.182-22.626 54.626l-338.748 338.746zM832 0h-704v896h351.158c2.916-0.48 8.408-2.754 10.81-4.478l337.556-337.554c1.722-2.402 3.996-7.894 4.476-10.81v-543.158zM864 960h-192c-17.6 0-21.818-10.182-9.374-22.626l210.746-210.746c12.446-12.446 22.628-8.228 22.628 9.372v192c0 17.6-14.4 32-32 32z" />
+<glyph unicode="&#xf00e;" glyph-name="search-plus" horiz-adv-x="951" d="M585.143 493.714v-36.571q0-7.429-5.429-12.857t-12.857-5.429h-128v-128q0-7.429-5.429-12.857t-12.857-5.429h-36.571q-7.429 0-12.857 5.429t-5.429 12.857v128h-128q-7.429 0-12.857 5.429t-5.429 12.857v36.571q0 7.429 5.429 12.857t12.857 5.429h128v128q0 7.429 5.429 12.857t12.857 5.429h36.571q7.429 0 12.857-5.429t5.429-12.857v-128h128q7.429 0 12.857-5.429t5.429-12.857zM658.286 475.428q0 105.714-75.143 180.857t-180.857 75.143-180.857-75.143-75.143-180.857 75.143-180.857 180.857-75.143 180.857 75.143 75.143 180.857zM950.857 0q0-30.286-21.429-51.714t-51.714-21.429q-30.857 0-51.429 21.714l-196 195.429q-102.286-70.857-228-70.857-81.714 0-156.286 31.714t-128.571 85.714-85.714 128.571-31.714 156.286 31.714 156.286 85.714 128.571 128.571 85.714 156.286 31.714 156.286-31.714 128.571-85.714 85.714-128.571 31.714-156.286q0-125.714-70.857-228l196-196q21.143-21.143 21.143-51.429z" />
+<glyph unicode="&#xf010;" glyph-name="search-minus" horiz-adv-x="951" d="M585.143 493.714v-36.571q0-7.429-5.429-12.857t-12.857-5.429h-329.143q-7.429 0-12.857 5.429t-5.429 12.857v36.571q0 7.429 5.429 12.857t12.857 5.429h329.143q7.429 0 12.857-5.429t5.429-12.857zM658.286 475.428q0 105.714-75.143 180.857t-180.857 75.143-180.857-75.143-75.143-180.857 75.143-180.857 180.857-75.143 180.857 75.143 75.143 180.857zM950.857 0q0-30.286-21.429-51.714t-51.714-21.429q-30.857 0-51.429 21.714l-196 195.429q-102.286-70.857-228-70.857-81.714 0-156.286 31.714t-128.571 85.714-85.714 128.571-31.714 156.286 31.714 156.286 85.714 128.571 128.571 85.714 156.286 31.714 156.286-31.714 128.571-85.714 85.714-128.571 31.714-156.286q0-125.714-70.857-228l196-196q21.143-21.143 21.143-51.429z" />
+<glyph unicode="&#xf014;" glyph-name="trash-o" horiz-adv-x="805" d="M292.571 530.286v-329.143q0-8-5.143-13.143t-13.143-5.143h-36.571q-8 0-13.143 5.143t-5.143 13.143v329.143q0 8 5.143 13.143t13.143 5.143h36.571q8 0 13.143-5.143t5.143-13.143zM438.857 530.286v-329.143q0-8-5.143-13.143t-13.143-5.143h-36.571q-8 0-13.143 5.143t-5.143 13.143v329.143q0 8 5.143 13.143t13.143 5.143h36.571q8 0 13.143-5.143t5.143-13.143zM585.143 530.286v-329.143q0-8-5.143-13.143t-13.143-5.143h-36.571q-8 0-13.143 5.143t-5.143 13.143v329.143q0 8 5.143 13.143t13.143 5.143h36.571q8 0 13.143-5.143t5.143-13.143zM658.286 116.571v541.714h-512v-541.714q0-12.571 4-23.143t8.286-15.429 6-4.857h475.429q1.714 0 6 4.857t8.286 15.429 4 23.143zM274.286 731.428h256l-27.429 66.857q-4 5.143-9.714 6.286h-181.143q-5.714-1.143-9.714-6.286zM804.571 713.143v-36.571q0-8-5.143-13.143t-13.143-5.143h-54.857v-541.714q0-47.429-26.857-82t-64.571-34.571h-475.429q-37.714 0-64.571 33.429t-26.857 80.857v544h-54.857q-8 0-13.143 5.143t-5.143 13.143v36.571q0 8 5.143 13.143t13.143 5.143h176.571l40 95.429q8.571 21.143 30.857 36t45.143 14.857h182.857q22.857 0 45.143-14.857t30.857-36l40-95.429h176.571q8 0 13.143-5.143t5.143-13.143z" />
+<glyph unicode="&#xf039;" glyph-name="align-justify" d="M1024 182.857v-73.143q0-14.857-10.857-25.714t-25.714-10.857h-950.857q-14.857 0-25.714 10.857t-10.857 25.714v73.143q0 14.857 10.857 25.714t25.714 10.857h950.857q14.857 0 25.714-10.857t10.857-25.714zM1024 402.286v-73.143q0-14.857-10.857-25.714t-25.714-10.857h-950.857q-14.857 0-25.714 10.857t-10.857 25.714v73.143q0 14.857 10.857 25.714t25.714 10.857h950.857q14.857 0 25.714-10.857t10.857-25.714zM1024 621.714v-73.143q0-14.857-10.857-25.714t-25.714-10.857h-950.857q-14.857 0-25.714 10.857t-10.857 25.714v73.143q0 14.857 10.857 25.714t25.714 10.857h950.857q14.857 0 25.714-10.857t10.857-25.714zM1024 841.143v-73.143q0-14.857-10.857-25.714t-25.714-10.857h-950.857q-14.857 0-25.714 10.857t-10.857 25.714v73.143q0 14.857 10.857 25.714t25.714 10.857h950.857q14.857 0 25.714-10.857t10.857-25.714z" />
+<glyph unicode="&#xf07d;" glyph-name="arrows-v" horiz-adv-x="439" d="M402.286 768q0-14.857-10.857-25.714t-25.714-10.857h-73.143v-585.143h73.143q14.857 0 25.714-10.857t10.857-25.714-10.857-25.714l-146.286-146.286q-10.857-10.857-25.714-10.857t-25.714 10.857l-146.286 146.286q-10.857 10.857-10.857 25.714t10.857 25.714 25.714 10.857h73.143v585.143h-73.143q-14.857 0-25.714 10.857t-10.857 25.714 10.857 25.714l146.286 146.286q10.857 10.857 25.714 10.857t25.714-10.857l146.286-146.286q10.857-10.857 10.857-25.714z" />
+<glyph unicode="&#xf108;" glyph-name="desktop" horiz-adv-x="1097" d="M1024 384v475.429q0 7.429-5.429 12.857t-12.857 5.429h-914.286q-7.429 0-12.857-5.429t-5.429-12.857v-475.429q0-7.429 5.429-12.857t12.857-5.429h914.286q7.429 0 12.857 5.429t5.429 12.857zM1097.143 859.428v-621.714q0-37.714-26.857-64.571t-64.571-26.857h-310.857q0-21.143 9.143-44.286t18.286-40.571 9.143-24.857q0-14.857-10.857-25.714t-25.714-10.857h-292.571q-14.857 0-25.714 10.857t-10.857 25.714q0 8 9.143 25.143t18.286 40 9.143 44.571h-310.857q-37.714 0-64.571 26.857t-26.857 64.571v621.714q0 37.714 26.857 64.571t64.571 26.857h914.286q37.714 0 64.571-26.857t26.857-64.571z" />
+<glyph unicode="&#xf10a;" glyph-name="tablet" horiz-adv-x="658" d="M365.714 146.286q0 14.857-10.857 25.714t-25.714 10.857-25.714-10.857-10.857-25.714 10.857-25.714 25.714-10.857 25.714 10.857 10.857 25.714zM585.143 237.714v548.571q0 7.429-5.429 12.857t-12.857 5.429h-475.429q-7.429 0-12.857-5.429t-5.429-12.857v-548.571q0-7.429 5.429-12.857t12.857-5.429h475.429q7.429 0 12.857 5.429t5.429 12.857zM658.286 786.286v-621.714q0-37.714-26.857-64.571t-64.571-26.857h-475.429q-37.714 0-64.571 26.857t-26.857 64.571v621.714q0 37.714 26.857 64.571t64.571 26.857h475.429q37.714 0 64.571-26.857t26.857-64.571z" />
+</font></defs></svg>

BIN
static/wangEditor/fonts/icomoon.ttf


BIN
static/wangEditor/fonts/icomoon.woff


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 3 - 0
static/wangEditor/js/lib/jquery-1.10.2.min.js


+ 9831 - 0
static/wangEditor/js/lib/jquery-2.2.1.js

@@ -0,0 +1,9831 @@
+/*!
+ * jQuery JavaScript Library v2.2.1
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2016-02-22T19:11Z
+ */
+
+(function( global, factory ) {
+
+	if ( typeof module === "object" && typeof module.exports === "object" ) {
+		// For CommonJS and CommonJS-like environments where a proper `window`
+		// is present, execute the factory and get jQuery.
+		// For environments that do not have a `window` with a `document`
+		// (such as Node.js), expose a factory as module.exports.
+		// This accentuates the need for the creation of a real `window`.
+		// e.g. var jQuery = require("jquery")(window);
+		// See ticket #14549 for more info.
+		module.exports = global.document ?
+			factory( global, true ) :
+			function( w ) {
+				if ( !w.document ) {
+					throw new Error( "jQuery requires a window with a document" );
+				}
+				return factory( w );
+			};
+	} else {
+		factory( global );
+	}
+
+// Pass this if window is not defined yet
+}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Support: Firefox 18+
+// Can't be in strict mode, several libs including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+//"use strict";
+var arr = [];
+
+var document = window.document;
+
+var slice = arr.slice;
+
+var concat = arr.concat;
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var support = {};
+
+
+
+var
+	version = "2.2.1",
+
+	// Define a local copy of jQuery
+	jQuery = function( selector, context ) {
+
+		// The jQuery object is actually just the init constructor 'enhanced'
+		// Need init if jQuery is called (just allow error to be thrown if not included)
+		return new jQuery.fn.init( selector, context );
+	},
+
+	// Support: Android<4.1
+	// Make sure we trim BOM and NBSP
+	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+	// Matches dashed string for camelizing
+	rmsPrefix = /^-ms-/,
+	rdashAlpha = /-([\da-z])/gi,
+
+	// Used by jQuery.camelCase as callback to replace()
+	fcamelCase = function( all, letter ) {
+		return letter.toUpperCase();
+	};
+
+jQuery.fn = jQuery.prototype = {
+
+	// The current version of jQuery being used
+	jquery: version,
+
+	constructor: jQuery,
+
+	// Start with an empty selector
+	selector: "",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	toArray: function() {
+		return slice.call( this );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num != null ?
+
+			// Return just the one element from the set
+			( num < 0 ? this[ num + this.length ] : this[ num ] ) :
+
+			// Return all the elements in a clean array
+			slice.call( this );
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems ) {
+
+		// Build a new jQuery matched element set
+		var ret = jQuery.merge( this.constructor(), elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+		ret.context = this.context;
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	each: function( callback ) {
+		return jQuery.each( this, callback );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map( this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		} ) );
+	},
+
+	slice: function() {
+		return this.pushStack( slice.apply( this, arguments ) );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	eq: function( i ) {
+		var len = this.length,
+			j = +i + ( i < 0 ? len : 0 );
+		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor();
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: push,
+	sort: arr.sort,
+	splice: arr.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[ 0 ] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+
+		// Skip the boolean and the target
+		target = arguments[ i ] || {};
+		i++;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
+		target = {};
+	}
+
+	// Extend jQuery itself if only one argument is passed
+	if ( i === length ) {
+		target = this;
+		i--;
+	}
+
+	for ( ; i < length; i++ ) {
+
+		// Only deal with non-null/undefined values
+		if ( ( options = arguments[ i ] ) != null ) {
+
+			// Extend the base object
+			for ( name in options ) {
+				src = target[ name ];
+				copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
+					( copyIsArray = jQuery.isArray( copy ) ) ) ) {
+
+					if ( copyIsArray ) {
+						copyIsArray = false;
+						clone = src && jQuery.isArray( src ) ? src : [];
+
+					} else {
+						clone = src && jQuery.isPlainObject( src ) ? src : {};
+					}
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend( {
+
+	// Unique for each copy of jQuery on the page
+	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+	// Assume jQuery is ready without the ready module
+	isReady: true,
+
+	error: function( msg ) {
+		throw new Error( msg );
+	},
+
+	noop: function() {},
+
+	isFunction: function( obj ) {
+		return jQuery.type( obj ) === "function";
+	},
+
+	isArray: Array.isArray,
+
+	isWindow: function( obj ) {
+		return obj != null && obj === obj.window;
+	},
+
+	isNumeric: function( obj ) {
+
+		// parseFloat NaNs numeric-cast false positives (null|true|false|"")
+		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+		// subtraction forces infinities to NaN
+		// adding 1 corrects loss of precision from parseFloat (#15100)
+		var realStringObj = obj && obj.toString();
+		return !jQuery.isArray( obj ) && ( realStringObj - parseFloat( realStringObj ) + 1 ) >= 0;
+	},
+
+	isPlainObject: function( obj ) {
+
+		// Not plain objects:
+		// - Any object or value whose internal [[Class]] property is not "[object Object]"
+		// - DOM nodes
+		// - window
+		if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		if ( obj.constructor &&
+				!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+			return false;
+		}
+
+		// If the function hasn't returned already, we're confident that
+		// |obj| is a plain object, created by {} or constructed with new Object
+		return true;
+	},
+
+	isEmptyObject: function( obj ) {
+		var name;
+		for ( name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	type: function( obj ) {
+		if ( obj == null ) {
+			return obj + "";
+		}
+
+		// Support: Android<4.0, iOS<6 (functionish RegExp)
+		return typeof obj === "object" || typeof obj === "function" ?
+			class2type[ toString.call( obj ) ] || "object" :
+			typeof obj;
+	},
+
+	// Evaluates a script in a global context
+	globalEval: function( code ) {
+		var script,
+			indirect = eval;
+
+		code = jQuery.trim( code );
+
+		if ( code ) {
+
+			// If the code includes a valid, prologue position
+			// strict mode pragma, execute code by injecting a
+			// script tag into the document.
+			if ( code.indexOf( "use strict" ) === 1 ) {
+				script = document.createElement( "script" );
+				script.text = code;
+				document.head.appendChild( script ).parentNode.removeChild( script );
+			} else {
+
+				// Otherwise, avoid the DOM node creation, insertion
+				// and removal by using an indirect global eval
+
+				indirect( code );
+			}
+		}
+	},
+
+	// Convert dashed to camelCase; used by the css and data modules
+	// Support: IE9-11+
+	// Microsoft forgot to hump their vendor prefix (#9572)
+	camelCase: function( string ) {
+		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+	},
+
+	each: function( obj, callback ) {
+		var length, i = 0;
+
+		if ( isArrayLike( obj ) ) {
+			length = obj.length;
+			for ( ; i < length; i++ ) {
+				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+					break;
+				}
+			}
+		} else {
+			for ( i in obj ) {
+				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
+					break;
+				}
+			}
+		}
+
+		return obj;
+	},
+
+	// Support: Android<4.1
+	trim: function( text ) {
+		return text == null ?
+			"" :
+			( text + "" ).replace( rtrim, "" );
+	},
+
+	// results is for internal usage only
+	makeArray: function( arr, results ) {
+		var ret = results || [];
+
+		if ( arr != null ) {
+			if ( isArrayLike( Object( arr ) ) ) {
+				jQuery.merge( ret,
+					typeof arr === "string" ?
+					[ arr ] : arr
+				);
+			} else {
+				push.call( ret, arr );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, arr, i ) {
+		return arr == null ? -1 : indexOf.call( arr, elem, i );
+	},
+
+	merge: function( first, second ) {
+		var len = +second.length,
+			j = 0,
+			i = first.length;
+
+		for ( ; j < len; j++ ) {
+			first[ i++ ] = second[ j ];
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, invert ) {
+		var callbackInverse,
+			matches = [],
+			i = 0,
+			length = elems.length,
+			callbackExpect = !invert;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( ; i < length; i++ ) {
+			callbackInverse = !callback( elems[ i ], i );
+			if ( callbackInverse !== callbackExpect ) {
+				matches.push( elems[ i ] );
+			}
+		}
+
+		return matches;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var length, value,
+			i = 0,
+			ret = [];
+
+		// Go through the array, translating each of the items to their new values
+		if ( isArrayLike( elems ) ) {
+			length = elems.length;
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret.push( value );
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( i in elems ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret.push( value );
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// Bind a function to a context, optionally partially applying any
+	// arguments.
+	proxy: function( fn, context ) {
+		var tmp, args, proxy;
+
+		if ( typeof context === "string" ) {
+			tmp = fn[ context ];
+			context = fn;
+			fn = tmp;
+		}
+
+		// Quick check to determine if target is callable, in the spec
+		// this throws a TypeError, but we will just return undefined.
+		if ( !jQuery.isFunction( fn ) ) {
+			return undefined;
+		}
+
+		// Simulated bind
+		args = slice.call( arguments, 2 );
+		proxy = function() {
+			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+		};
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+		return proxy;
+	},
+
+	now: Date.now,
+
+	// jQuery.support is not used in Core but other projects attach their
+	// properties to it so it needs to exist.
+	support: support
+} );
+
+// JSHint would error on this code due to the Symbol not being defined in ES5.
+// Defining this global in .jshintrc would create a danger of using the global
+// unguarded in another place, it seems safer to just disable JSHint for these
+// three lines.
+/* jshint ignore: start */
+if ( typeof Symbol === "function" ) {
+	jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
+}
+/* jshint ignore: end */
+
+// Populate the class2type map
+jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
+function( i, name ) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+} );
+
+function isArrayLike( obj ) {
+
+	// Support: iOS 8.2 (not reproducible in simulator)
+	// `in` check used to prevent JIT error (gh-2145)
+	// hasOwn isn't used here due to false negatives
+	// regarding Nodelist length in IE
+	var length = !!obj && "length" in obj && obj.length,
+		type = jQuery.type( obj );
+
+	if ( type === "function" || jQuery.isWindow( obj ) ) {
+		return false;
+	}
+
+	return type === "array" || length === 0 ||
+		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v2.2.1
+ * http://sizzlejs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-10-17
+ */
+(function( window ) {
+
+var i,
+	support,
+	Expr,
+	getText,
+	isXML,
+	tokenize,
+	compile,
+	select,
+	outermostContext,
+	sortInput,
+	hasDuplicate,
+
+	// Local document vars
+	setDocument,
+	document,
+	docElem,
+	documentIsHTML,
+	rbuggyQSA,
+	rbuggyMatches,
+	matches,
+	contains,
+
+	// Instance-specific data
+	expando = "sizzle" + 1 * new Date(),
+	preferredDoc = window.document,
+	dirruns = 0,
+	done = 0,
+	classCache = createCache(),
+	tokenCache = createCache(),
+	compilerCache = createCache(),
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+		}
+		return 0;
+	},
+
+	// General-purpose constants
+	MAX_NEGATIVE = 1 << 31,
+
+	// Instance methods
+	hasOwn = ({}).hasOwnProperty,
+	arr = [],
+	pop = arr.pop,
+	push_native = arr.push,
+	push = arr.push,
+	slice = arr.slice,
+	// Use a stripped-down indexOf as it's faster than native
+	// http://jsperf.com/thor-indexof-vs-for/5
+	indexOf = function( list, elem ) {
+		var i = 0,
+			len = list.length;
+		for ( ; i < len; i++ ) {
+			if ( list[i] === elem ) {
+				return i;
+			}
+		}
+		return -1;
+	},
+
+	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+	// Regular expressions
+
+	// http://www.w3.org/TR/css3-selectors/#whitespace
+	whitespace = "[\\x20\\t\\r\\n\\f]",
+
+	// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+	identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
+		// Operator (capture 2)
+		"*([*^$|!~]?=)" + whitespace +
+		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
+		"*\\]",
+
+	pseudos = ":(" + identifier + ")(?:\\((" +
+		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+		// 1. quoted (capture 3; capture 4 or capture 5)
+		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+		// 2. simple (capture 6)
+		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+		// 3. anything else (capture 2)
+		".*" +
+		")\\)|)",
+
+	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+	rwhitespace = new RegExp( whitespace + "+", "g" ),
+	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
+
+	rpseudo = new RegExp( pseudos ),
+	ridentifier = new RegExp( "^" + identifier + "$" ),
+
+	matchExpr = {
+		"ID": new RegExp( "^#(" + identifier + ")" ),
+		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
+		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
+		"ATTR": new RegExp( "^" + attributes ),
+		"PSEUDO": new RegExp( "^" + pseudos ),
+		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+		// For use in libraries implementing .is()
+		// We use this for POS matching in `select`
+		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+	},
+
+	rinputs = /^(?:input|select|textarea|button)$/i,
+	rheader = /^h\d$/i,
+
+	rnative = /^[^{]+\{\s*\[native \w/,
+
+	// Easily-parseable/retrievable ID or TAG or CLASS selectors
+	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+	rsibling = /[+~]/,
+	rescape = /'|\\/g,
+
+	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+	funescape = function( _, escaped, escapedWhitespace ) {
+		var high = "0x" + escaped - 0x10000;
+		// NaN means non-codepoint
+		// Support: Firefox<24
+		// Workaround erroneous numeric interpretation of +"0x"
+		return high !== high || escapedWhitespace ?
+			escaped :
+			high < 0 ?
+				// BMP codepoint
+				String.fromCharCode( high + 0x10000 ) :
+				// Supplemental Plane codepoint (surrogate pair)
+				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+	},
+
+	// Used for iframes
+	// See setDocument()
+	// Removing the function wrapper causes a "Permission Denied"
+	// error in IE
+	unloadHandler = function() {
+		setDocument();
+	};
+
+// Optimize for push.apply( _, NodeList )
+try {
+	push.apply(
+		(arr = slice.call( preferredDoc.childNodes )),
+		preferredDoc.childNodes
+	);
+	// Support: Android<4.0
+	// Detect silently failing push.apply
+	arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+	push = { apply: arr.length ?
+
+		// Leverage slice if possible
+		function( target, els ) {
+			push_native.apply( target, slice.call(els) );
+		} :
+
+		// Support: IE<9
+		// Otherwise append directly
+		function( target, els ) {
+			var j = target.length,
+				i = 0;
+			// Can't trust NodeList.length
+			while ( (target[j++] = els[i++]) ) {}
+			target.length = j - 1;
+		}
+	};
+}
+
+function Sizzle( selector, context, results, seed ) {
+	var m, i, elem, nid, nidselect, match, groups, newSelector,
+		newContext = context && context.ownerDocument,
+
+		// nodeType defaults to 9, since context defaults to document
+		nodeType = context ? context.nodeType : 9;
+
+	results = results || [];
+
+	// Return early from calls with invalid selector or context
+	if ( typeof selector !== "string" || !selector ||
+		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+		return results;
+	}
+
+	// Try to shortcut find operations (as opposed to filters) in HTML documents
+	if ( !seed ) {
+
+		if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+			setDocument( context );
+		}
+		context = context || document;
+
+		if ( documentIsHTML ) {
+
+			// If the selector is sufficiently simple, try using a "get*By*" DOM method
+			// (excepting DocumentFragment context, where the methods don't exist)
+			if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
+
+				// ID selector
+				if ( (m = match[1]) ) {
+
+					// Document context
+					if ( nodeType === 9 ) {
+						if ( (elem = context.getElementById( m )) ) {
+
+							// Support: IE, Opera, Webkit
+							// TODO: identify versions
+							// getElementById can match elements by name instead of ID
+							if ( elem.id === m ) {
+								results.push( elem );
+								return results;
+							}
+						} else {
+							return results;
+						}
+
+					// Element context
+					} else {
+
+						// Support: IE, Opera, Webkit
+						// TODO: identify versions
+						// getElementById can match elements by name instead of ID
+						if ( newContext && (elem = newContext.getElementById( m )) &&
+							contains( context, elem ) &&
+							elem.id === m ) {
+
+							results.push( elem );
+							return results;
+						}
+					}
+
+				// Type selector
+				} else if ( match[2] ) {
+					push.apply( results, context.getElementsByTagName( selector ) );
+					return results;
+
+				// Class selector
+				} else if ( (m = match[3]) && support.getElementsByClassName &&
+					context.getElementsByClassName ) {
+
+					push.apply( results, context.getElementsByClassName( m ) );
+					return results;
+				}
+			}
+
+			// Take advantage of querySelectorAll
+			if ( support.qsa &&
+				!compilerCache[ selector + " " ] &&
+				(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+
+				if ( nodeType !== 1 ) {
+					newContext = context;
+					newSelector = selector;
+
+				// qSA looks outside Element context, which is not what we want
+				// Thanks to Andrew Dupont for this workaround technique
+				// Support: IE <=8
+				// Exclude object elements
+				} else if ( context.nodeName.toLowerCase() !== "object" ) {
+
+					// Capture the context ID, setting it first if necessary
+					if ( (nid = context.getAttribute( "id" )) ) {
+						nid = nid.replace( rescape, "\\$&" );
+					} else {
+						context.setAttribute( "id", (nid = expando) );
+					}
+
+					// Prefix every selector in the list
+					groups = tokenize( selector );
+					i = groups.length;
+					nidselect = ridentifier.test( nid ) ? "#" + nid : "[id='" + nid + "']";
+					while ( i-- ) {
+						groups[i] = nidselect + " " + toSelector( groups[i] );
+					}
+					newSelector = groups.join( "," );
+
+					// Expand context for sibling selectors
+					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
+						context;
+				}
+
+				if ( newSelector ) {
+					try {
+						push.apply( results,
+							newContext.querySelectorAll( newSelector )
+						);
+						return results;
+					} catch ( qsaError ) {
+					} finally {
+						if ( nid === expando ) {
+							context.removeAttribute( "id" );
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// All others
+	return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {function(string, object)} Returns the Object data after storing it on itself with
+ *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ *	deleting the oldest entry
+ */
+function createCache() {
+	var keys = [];
+
+	function cache( key, value ) {
+		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+		if ( keys.push( key + " " ) > Expr.cacheLength ) {
+			// Only keep the most recent entries
+			delete cache[ keys.shift() ];
+		}
+		return (cache[ key + " " ] = value);
+	}
+	return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+	fn[ expando ] = true;
+	return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+function assert( fn ) {
+	var div = document.createElement("div");
+
+	try {
+		return !!fn( div );
+	} catch (e) {
+		return false;
+	} finally {
+		// Remove from its parent by default
+		if ( div.parentNode ) {
+			div.parentNode.removeChild( div );
+		}
+		// release memory in IE
+		div = null;
+	}
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+	var arr = attrs.split("|"),
+		i = arr.length;
+
+	while ( i-- ) {
+		Expr.attrHandle[ arr[i] ] = handler;
+	}
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+	var cur = b && a,
+		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+			( ~b.sourceIndex || MAX_NEGATIVE ) -
+			( ~a.sourceIndex || MAX_NEGATIVE );
+
+	// Use IE sourceIndex if available on both nodes
+	if ( diff ) {
+		return diff;
+	}
+
+	// Check if b follows a
+	if ( cur ) {
+		while ( (cur = cur.nextSibling) ) {
+			if ( cur === b ) {
+				return -1;
+			}
+		}
+	}
+
+	return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+	return function( elem ) {
+		var name = elem.nodeName.toLowerCase();
+		return name === "input" && elem.type === type;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+	return function( elem ) {
+		var name = elem.nodeName.toLowerCase();
+		return (name === "input" || name === "button") && elem.type === type;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+	return markFunction(function( argument ) {
+		argument = +argument;
+		return markFunction(function( seed, matches ) {
+			var j,
+				matchIndexes = fn( [], seed.length, argument ),
+				i = matchIndexes.length;
+
+			// Match elements found at the specified indexes
+			while ( i-- ) {
+				if ( seed[ (j = matchIndexes[i]) ] ) {
+					seed[j] = !(matches[j] = seed[j]);
+				}
+			}
+		});
+	});
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+	return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+	// documentElement is verified for cases where it doesn't yet exist
+	// (such as loading iframes in IE - #4833)
+	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+	return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+	var hasCompare, parent,
+		doc = node ? node.ownerDocument || node : preferredDoc;
+
+	// Return early if doc is invalid or already selected
+	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+		return document;
+	}
+
+	// Update global variables
+	document = doc;
+	docElem = document.documentElement;
+	documentIsHTML = !isXML( document );
+
+	// Support: IE 9-11, Edge
+	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
+	if ( (parent = document.defaultView) && parent.top !== parent ) {
+		// Support: IE 11
+		if ( parent.addEventListener ) {
+			parent.addEventListener( "unload", unloadHandler, false );
+
+		// Support: IE 9 - 10 only
+		} else if ( parent.attachEvent ) {
+			parent.attachEvent( "onunload", unloadHandler );
+		}
+	}
+
+	/* Attributes
+	---------------------------------------------------------------------- */
+
+	// Support: IE<8
+	// Verify that getAttribute really returns attributes and not properties
+	// (excepting IE8 booleans)
+	support.attributes = assert(function( div ) {
+		div.className = "i";
+		return !div.getAttribute("className");
+	});
+
+	/* getElement(s)By*
+	---------------------------------------------------------------------- */
+
+	// Check if getElementsByTagName("*") returns only elements
+	support.getElementsByTagName = assert(function( div ) {
+		div.appendChild( document.createComment("") );
+		return !div.getElementsByTagName("*").length;
+	});
+
+	// Support: IE<9
+	support.getElementsByClassName = rnative.test( document.getElementsByClassName );
+
+	// Support: IE<10
+	// Check if getElementById returns elements by name
+	// The broken getElementById methods don't pick up programatically-set names,
+	// so use a roundabout getElementsByName test
+	support.getById = assert(function( div ) {
+		docElem.appendChild( div ).id = expando;
+		return !document.getElementsByName || !document.getElementsByName( expando ).length;
+	});
+
+	// ID find and filter
+	if ( support.getById ) {
+		Expr.find["ID"] = function( id, context ) {
+			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+				var m = context.getElementById( id );
+				return m ? [ m ] : [];
+			}
+		};
+		Expr.filter["ID"] = function( id ) {
+			var attrId = id.replace( runescape, funescape );
+			return function( elem ) {
+				return elem.getAttribute("id") === attrId;
+			};
+		};
+	} else {
+		// Support: IE6/7
+		// getElementById is not reliable as a find shortcut
+		delete Expr.find["ID"];
+
+		Expr.filter["ID"] =  function( id ) {
+			var attrId = id.replace( runescape, funescape );
+			return function( elem ) {
+				var node = typeof elem.getAttributeNode !== "undefined" &&
+					elem.getAttributeNode("id");
+				return node && node.value === attrId;
+			};
+		};
+	}
+
+	// Tag
+	Expr.find["TAG"] = support.getElementsByTagName ?
+		function( tag, context ) {
+			if ( typeof context.getElementsByTagName !== "undefined" ) {
+				return context.getElementsByTagName( tag );
+
+			// DocumentFragment nodes don't have gEBTN
+			} else if ( support.qsa ) {
+				return context.querySelectorAll( tag );
+			}
+		} :
+
+		function( tag, context ) {
+			var elem,
+				tmp = [],
+				i = 0,
+				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+				results = context.getElementsByTagName( tag );
+
+			// Filter out possible comments
+			if ( tag === "*" ) {
+				while ( (elem = results[i++]) ) {
+					if ( elem.nodeType === 1 ) {
+						tmp.push( elem );
+					}
+				}
+
+				return tmp;
+			}
+			return results;
+		};
+
+	// Class
+	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
+			return context.getElementsByClassName( className );
+		}
+	};
+
+	/* QSA/matchesSelector
+	---------------------------------------------------------------------- */
+
+	// QSA and matchesSelector support
+
+	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+	rbuggyMatches = [];
+
+	// qSa(:focus) reports false when true (Chrome 21)
+	// We allow this because of a bug in IE8/9 that throws an error
+	// whenever `document.activeElement` is accessed on an iframe
+	// So, we allow :focus to pass through QSA all the time to avoid the IE error
+	// See http://bugs.jquery.com/ticket/13378
+	rbuggyQSA = [];
+
+	if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
+		// Build QSA regex
+		// Regex strategy adopted from Diego Perini
+		assert(function( div ) {
+			// Select is set to empty string on purpose
+			// This is to test IE's treatment of not explicitly
+			// setting a boolean content attribute,
+			// since its presence should be enough
+			// http://bugs.jquery.com/ticket/12359
+			docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" +
+				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
+				"<option selected=''></option></select>";
+
+			// Support: IE8, Opera 11-12.16
+			// Nothing should be selected when empty strings follow ^= or $= or *=
+			// The test attribute must be unknown in Opera but "safe" for WinRT
+			// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+			if ( div.querySelectorAll("[msallowcapture^='']").length ) {
+				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+			}
+
+			// Support: IE8
+			// Boolean attributes and "value" are not treated correctly
+			if ( !div.querySelectorAll("[selected]").length ) {
+				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+			}
+
+			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
+			if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+				rbuggyQSA.push("~=");
+			}
+
+			// Webkit/Opera - :checked should return selected option elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			// IE8 throws error here and will not see later tests
+			if ( !div.querySelectorAll(":checked").length ) {
+				rbuggyQSA.push(":checked");
+			}
+
+			// Support: Safari 8+, iOS 8+
+			// https://bugs.webkit.org/show_bug.cgi?id=136851
+			// In-page `selector#id sibing-combinator selector` fails
+			if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
+				rbuggyQSA.push(".#.+[+~]");
+			}
+		});
+
+		assert(function( div ) {
+			// Support: Windows 8 Native Apps
+			// The type and name attributes are restricted during .innerHTML assignment
+			var input = document.createElement("input");
+			input.setAttribute( "type", "hidden" );
+			div.appendChild( input ).setAttribute( "name", "D" );
+
+			// Support: IE8
+			// Enforce case-sensitivity of name attribute
+			if ( div.querySelectorAll("[name=d]").length ) {
+				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+			}
+
+			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+			// IE8 throws error here and will not see later tests
+			if ( !div.querySelectorAll(":enabled").length ) {
+				rbuggyQSA.push( ":enabled", ":disabled" );
+			}
+
+			// Opera 10-11 does not throw on post-comma invalid pseudos
+			div.querySelectorAll("*,:x");
+			rbuggyQSA.push(",.*:");
+		});
+	}
+
+	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
+		docElem.webkitMatchesSelector ||
+		docElem.mozMatchesSelector ||
+		docElem.oMatchesSelector ||
+		docElem.msMatchesSelector) )) ) {
+
+		assert(function( div ) {
+			// Check to see if it's possible to do matchesSelector
+			// on a disconnected node (IE 9)
+			support.disconnectedMatch = matches.call( div, "div" );
+
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( div, "[s!='']:x" );
+			rbuggyMatches.push( "!=", pseudos );
+		});
+	}
+
+	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+	/* Contains
+	---------------------------------------------------------------------- */
+	hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+	// Element contains another
+	// Purposefully self-exclusive
+	// As in, an element does not contain itself
+	contains = hasCompare || rnative.test( docElem.contains ) ?
+		function( a, b ) {
+			var adown = a.nodeType === 9 ? a.documentElement : a,
+				bup = b && b.parentNode;
+			return a === bup || !!( bup && bup.nodeType === 1 && (
+				adown.contains ?
+					adown.contains( bup ) :
+					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+			));
+		} :
+		function( a, b ) {
+			if ( b ) {
+				while ( (b = b.parentNode) ) {
+					if ( b === a ) {
+						return true;
+					}
+				}
+			}
+			return false;
+		};
+
+	/* Sorting
+	---------------------------------------------------------------------- */
+
+	// Document order sorting
+	sortOrder = hasCompare ?
+	function( a, b ) {
+
+		// Flag for duplicate removal
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		// Sort on method existence if only one input has compareDocumentPosition
+		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+		if ( compare ) {
+			return compare;
+		}
+
+		// Calculate position if both inputs belong to the same document
+		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+			a.compareDocumentPosition( b ) :
+
+			// Otherwise we know they are disconnected
+			1;
+
+		// Disconnected nodes
+		if ( compare & 1 ||
+			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+			// Choose the first element that is related to our preferred document
+			if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+				return -1;
+			}
+			if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+				return 1;
+			}
+
+			// Maintain original order
+			return sortInput ?
+				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+				0;
+		}
+
+		return compare & 4 ? -1 : 1;
+	} :
+	function( a, b ) {
+		// Exit early if the nodes are identical
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		var cur,
+			i = 0,
+			aup = a.parentNode,
+			bup = b.parentNode,
+			ap = [ a ],
+			bp = [ b ];
+
+		// Parentless nodes are either documents or disconnected
+		if ( !aup || !bup ) {
+			return a === document ? -1 :
+				b === document ? 1 :
+				aup ? -1 :
+				bup ? 1 :
+				sortInput ?
+				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+				0;
+
+		// If the nodes are siblings, we can do a quick check
+		} else if ( aup === bup ) {
+			return siblingCheck( a, b );
+		}
+
+		// Otherwise we need full lists of their ancestors for comparison
+		cur = a;
+		while ( (cur = cur.parentNode) ) {
+			ap.unshift( cur );
+		}
+		cur = b;
+		while ( (cur = cur.parentNode) ) {
+			bp.unshift( cur );
+		}
+
+		// Walk down the tree looking for a discrepancy
+		while ( ap[i] === bp[i] ) {
+			i++;
+		}
+
+		return i ?
+			// Do a sibling check if the nodes have a common ancestor
+			siblingCheck( ap[i], bp[i] ) :
+
+			// Otherwise nodes in our document sort first
+			ap[i] === preferredDoc ? -1 :
+			bp[i] === preferredDoc ? 1 :
+			0;
+	};
+
+	return document;
+};
+
+Sizzle.matches = function( expr, elements ) {
+	return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+	// Set document vars if needed
+	if ( ( elem.ownerDocument || elem ) !== document ) {
+		setDocument( elem );
+	}
+
+	// Make sure that attribute selectors are quoted
+	expr = expr.replace( rattributeQuotes, "='$1']" );
+
+	if ( support.matchesSelector && documentIsHTML &&
+		!compilerCache[ expr + " " ] &&
+		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
+
+		try {
+			var ret = matches.call( elem, expr );
+
+			// IE 9's matchesSelector returns false on disconnected nodes
+			if ( ret || support.disconnectedMatch ||
+					// As well, disconnected nodes are said to be in a document
+					// fragment in IE 9
+					elem.document && elem.document.nodeType !== 11 ) {
+				return ret;
+			}
+		} catch (e) {}
+	}
+
+	return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+	// Set document vars if needed
+	if ( ( context.ownerDocument || context ) !== document ) {
+		setDocument( context );
+	}
+	return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+	// Set document vars if needed
+	if ( ( elem.ownerDocument || elem ) !== document ) {
+		setDocument( elem );
+	}
+
+	var fn = Expr.attrHandle[ name.toLowerCase() ],
+		// Don't get fooled by Object.prototype properties (jQuery #13807)
+		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+			fn( elem, name, !documentIsHTML ) :
+			undefined;
+
+	return val !== undefined ?
+		val :
+		support.attributes || !documentIsHTML ?
+			elem.getAttribute( name ) :
+			(val = elem.getAttributeNode(name)) && val.specified ?
+				val.value :
+				null;
+};
+
+Sizzle.error = function( msg ) {
+	throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+	var elem,
+		duplicates = [],
+		j = 0,
+		i = 0;
+
+	// Unless we *know* we can detect duplicates, assume their presence
+	hasDuplicate = !support.detectDuplicates;
+	sortInput = !support.sortStable && results.slice( 0 );
+	results.sort( sortOrder );
+
+	if ( hasDuplicate ) {
+		while ( (elem = results[i++]) ) {
+			if ( elem === results[ i ] ) {
+				j = duplicates.push( i );
+			}
+		}
+		while ( j-- ) {
+			results.splice( duplicates[ j ], 1 );
+		}
+	}
+
+	// Clear input after sorting to release objects
+	// See https://github.com/jquery/sizzle/pull/225
+	sortInput = null;
+
+	return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+	var node,
+		ret = "",
+		i = 0,
+		nodeType = elem.nodeType;
+
+	if ( !nodeType ) {
+		// If no nodeType, this is expected to be an array
+		while ( (node = elem[i++]) ) {
+			// Do not traverse comment nodes
+			ret += getText( node );
+		}
+	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+		// Use textContent for elements
+		// innerText usage removed for consistency of new lines (jQuery #11153)
+		if ( typeof elem.textContent === "string" ) {
+			return elem.textContent;
+		} else {
+			// Traverse its children
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				ret += getText( elem );
+			}
+		}
+	} else if ( nodeType === 3 || nodeType === 4 ) {
+		return elem.nodeValue;
+	}
+	// Do not include comment or processing instruction nodes
+
+	return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+	// Can be adjusted by the user
+	cacheLength: 50,
+
+	createPseudo: markFunction,
+
+	match: matchExpr,
+
+	attrHandle: {},
+
+	find: {},
+
+	relative: {
+		">": { dir: "parentNode", first: true },
+		" ": { dir: "parentNode" },
+		"+": { dir: "previousSibling", first: true },
+		"~": { dir: "previousSibling" }
+	},
+
+	preFilter: {
+		"ATTR": function( match ) {
+			match[1] = match[1].replace( runescape, funescape );
+
+			// Move the given value to match[3] whether quoted or unquoted
+			match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
+
+			if ( match[2] === "~=" ) {
+				match[3] = " " + match[3] + " ";
+			}
+
+			return match.slice( 0, 4 );
+		},
+
+		"CHILD": function( match ) {
+			/* matches from matchExpr["CHILD"]
+				1 type (only|nth|...)
+				2 what (child|of-type)
+				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+				4 xn-component of xn+y argument ([+-]?\d*n|)
+				5 sign of xn-component
+				6 x of xn-component
+				7 sign of y-component
+				8 y of y-component
+			*/
+			match[1] = match[1].toLowerCase();
+
+			if ( match[1].slice( 0, 3 ) === "nth" ) {
+				// nth-* requires argument
+				if ( !match[3] ) {
+					Sizzle.error( match[0] );
+				}
+
+				// numeric x and y parameters for Expr.filter.CHILD
+				// remember that false/true cast respectively to 0/1
+				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+			// other types prohibit arguments
+			} else if ( match[3] ) {
+				Sizzle.error( match[0] );
+			}
+
+			return match;
+		},
+
+		"PSEUDO": function( match ) {
+			var excess,
+				unquoted = !match[6] && match[2];
+
+			if ( matchExpr["CHILD"].test( match[0] ) ) {
+				return null;
+			}
+
+			// Accept quoted arguments as-is
+			if ( match[3] ) {
+				match[2] = match[4] || match[5] || "";
+
+			// Strip excess characters from unquoted arguments
+			} else if ( unquoted && rpseudo.test( unquoted ) &&
+				// Get excess from tokenize (recursively)
+				(excess = tokenize( unquoted, true )) &&
+				// advance to the next closing parenthesis
+				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+				// excess is a negative index
+				match[0] = match[0].slice( 0, excess );
+				match[2] = unquoted.slice( 0, excess );
+			}
+
+			// Return only captures needed by the pseudo filter method (type and argument)
+			return match.slice( 0, 3 );
+		}
+	},
+
+	filter: {
+
+		"TAG": function( nodeNameSelector ) {
+			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+			return nodeNameSelector === "*" ?
+				function() { return true; } :
+				function( elem ) {
+					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+				};
+		},
+
+		"CLASS": function( className ) {
+			var pattern = classCache[ className + " " ];
+
+			return pattern ||
+				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+				classCache( className, function( elem ) {
+					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
+				});
+		},
+
+		"ATTR": function( name, operator, check ) {
+			return function( elem ) {
+				var result = Sizzle.attr( elem, name );
+
+				if ( result == null ) {
+					return operator === "!=";
+				}
+				if ( !operator ) {
+					return true;
+				}
+
+				result += "";
+
+				return operator === "=" ? result === check :
+					operator === "!=" ? result !== check :
+					operator === "^=" ? check && result.indexOf( check ) === 0 :
+					operator === "*=" ? check && result.indexOf( check ) > -1 :
+					operator === "$=" ? check && result.slice( -check.length ) === check :
+					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+					false;
+			};
+		},
+
+		"CHILD": function( type, what, argument, first, last ) {
+			var simple = type.slice( 0, 3 ) !== "nth",
+				forward = type.slice( -4 ) !== "last",
+				ofType = what === "of-type";
+
+			return first === 1 && last === 0 ?
+
+				// Shortcut for :nth-*(n)
+				function( elem ) {
+					return !!elem.parentNode;
+				} :
+
+				function( elem, context, xml ) {
+					var cache, uniqueCache, outerCache, node, nodeIndex, start,
+						dir = simple !== forward ? "nextSibling" : "previousSibling",
+						parent = elem.parentNode,
+						name = ofType && elem.nodeName.toLowerCase(),
+						useCache = !xml && !ofType,
+						diff = false;
+
+					if ( parent ) {
+
+						// :(first|last|only)-(child|of-type)
+						if ( simple ) {
+							while ( dir ) {
+								node = elem;
+								while ( (node = node[ dir ]) ) {
+									if ( ofType ?
+										node.nodeName.toLowerCase() === name :
+										node.nodeType === 1 ) {
+
+										return false;
+									}
+								}
+								// Reverse direction for :only-* (if we haven't yet done so)
+								start = dir = type === "only" && !start && "nextSibling";
+							}
+							return true;
+						}
+
+						start = [ forward ? parent.firstChild : parent.lastChild ];
+
+						// non-xml :nth-child(...) stores cache data on `parent`
+						if ( forward && useCache ) {
+
+							// Seek `elem` from a previously-cached index
+
+							// ...in a gzip-friendly way
+							node = parent;
+							outerCache = node[ expando ] || (node[ expando ] = {});
+
+							// Support: IE <9 only
+							// Defend against cloned attroperties (jQuery gh-1709)
+							uniqueCache = outerCache[ node.uniqueID ] ||
+								(outerCache[ node.uniqueID ] = {});
+
+							cache = uniqueCache[ type ] || [];
+							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+							diff = nodeIndex && cache[ 2 ];
+							node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+							while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+								// Fallback to seeking `elem` from the start
+								(diff = nodeIndex = 0) || start.pop()) ) {
+
+								// When found, cache indexes on `parent` and break
+								if ( node.nodeType === 1 && ++diff && node === elem ) {
+									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
+									break;
+								}
+							}
+
+						} else {
+							// Use previously-cached element index if available
+							if ( useCache ) {
+								// ...in a gzip-friendly way
+								node = elem;
+								outerCache = node[ expando ] || (node[ expando ] = {});
+
+								// Support: IE <9 only
+								// Defend against cloned attroperties (jQuery gh-1709)
+								uniqueCache = outerCache[ node.uniqueID ] ||
+									(outerCache[ node.uniqueID ] = {});
+
+								cache = uniqueCache[ type ] || [];
+								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
+								diff = nodeIndex;
+							}
+
+							// xml :nth-child(...)
+							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
+							if ( diff === false ) {
+								// Use the same loop as above to seek `elem` from the start
+								while ( (node = ++nodeIndex && node && node[ dir ] ||
+									(diff = nodeIndex = 0) || start.pop()) ) {
+
+									if ( ( ofType ?
+										node.nodeName.toLowerCase() === name :
+										node.nodeType === 1 ) &&
+										++diff ) {
+
+										// Cache the index of each encountered element
+										if ( useCache ) {
+											outerCache = node[ expando ] || (node[ expando ] = {});
+
+											// Support: IE <9 only
+											// Defend against cloned attroperties (jQuery gh-1709)
+											uniqueCache = outerCache[ node.uniqueID ] ||
+												(outerCache[ node.uniqueID ] = {});
+
+											uniqueCache[ type ] = [ dirruns, diff ];
+										}
+
+										if ( node === elem ) {
+											break;
+										}
+									}
+								}
+							}
+						}
+
+						// Incorporate the offset, then check against cycle size
+						diff -= last;
+						return diff === first || ( diff % first === 0 && diff / first >= 0 );
+					}
+				};
+		},
+
+		"PSEUDO": function( pseudo, argument ) {
+			// pseudo-class names are case-insensitive
+			// http://www.w3.org/TR/selectors/#pseudo-classes
+			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+			// Remember that setFilters inherits from pseudos
+			var args,
+				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+					Sizzle.error( "unsupported pseudo: " + pseudo );
+
+			// The user may use createPseudo to indicate that
+			// arguments are needed to create the filter function
+			// just as Sizzle does
+			if ( fn[ expando ] ) {
+				return fn( argument );
+			}
+
+			// But maintain support for old signatures
+			if ( fn.length > 1 ) {
+				args = [ pseudo, pseudo, "", argument ];
+				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+					markFunction(function( seed, matches ) {
+						var idx,
+							matched = fn( seed, argument ),
+							i = matched.length;
+						while ( i-- ) {
+							idx = indexOf( seed, matched[i] );
+							seed[ idx ] = !( matches[ idx ] = matched[i] );
+						}
+					}) :
+					function( elem ) {
+						return fn( elem, 0, args );
+					};
+			}
+
+			return fn;
+		}
+	},
+
+	pseudos: {
+		// Potentially complex pseudos
+		"not": markFunction(function( selector ) {
+			// Trim the selector passed to compile
+			// to avoid treating leading and trailing
+			// spaces as combinators
+			var input = [],
+				results = [],
+				matcher = compile( selector.replace( rtrim, "$1" ) );
+
+			return matcher[ expando ] ?
+				markFunction(function( seed, matches, context, xml ) {
+					var elem,
+						unmatched = matcher( seed, null, xml, [] ),
+						i = seed.length;
+
+					// Match elements unmatched by `matcher`
+					while ( i-- ) {
+						if ( (elem = unmatched[i]) ) {
+							seed[i] = !(matches[i] = elem);
+						}
+					}
+				}) :
+				function( elem, context, xml ) {
+					input[0] = elem;
+					matcher( input, null, xml, results );
+					// Don't keep the element (issue #299)
+					input[0] = null;
+					return !results.pop();
+				};
+		}),
+
+		"has": markFunction(function( selector ) {
+			return function( elem ) {
+				return Sizzle( selector, elem ).length > 0;
+			};
+		}),
+
+		"contains": markFunction(function( text ) {
+			text = text.replace( runescape, funescape );
+			return function( elem ) {
+				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+			};
+		}),
+
+		// "Whether an element is represented by a :lang() selector
+		// is based solely on the element's language value
+		// being equal to the identifier C,
+		// or beginning with the identifier C immediately followed by "-".
+		// The matching of C against the element's language value is performed case-insensitively.
+		// The identifier C does not have to be a valid language name."
+		// http://www.w3.org/TR/selectors/#lang-pseudo
+		"lang": markFunction( function( lang ) {
+			// lang value must be a valid identifier
+			if ( !ridentifier.test(lang || "") ) {
+				Sizzle.error( "unsupported lang: " + lang );
+			}
+			lang = lang.replace( runescape, funescape ).toLowerCase();
+			return function( elem ) {
+				var elemLang;
+				do {
+					if ( (elemLang = documentIsHTML ?
+						elem.lang :
+						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+						elemLang = elemLang.toLowerCase();
+						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+					}
+				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+				return false;
+			};
+		}),
+
+		// Miscellaneous
+		"target": function( elem ) {
+			var hash = window.location && window.location.hash;
+			return hash && hash.slice( 1 ) === elem.id;
+		},
+
+		"root": function( elem ) {
+			return elem === docElem;
+		},
+
+		"focus": function( elem ) {
+			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+		},
+
+		// Boolean properties
+		"enabled": function( elem ) {
+			return elem.disabled === false;
+		},
+
+		"disabled": function( elem ) {
+			return elem.disabled === true;
+		},
+
+		"checked": function( elem ) {
+			// In CSS3, :checked should return both checked and selected elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			var nodeName = elem.nodeName.toLowerCase();
+			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+		},
+
+		"selected": function( elem ) {
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			if ( elem.parentNode ) {
+				elem.parentNode.selectedIndex;
+			}
+
+			return elem.selected === true;
+		},
+
+		// Contents
+		"empty": function( elem ) {
+			// http://www.w3.org/TR/selectors/#empty-pseudo
+			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+			//   but not by others (comment: 8; processing instruction: 7; etc.)
+			// nodeType < 6 works because attributes (2) do not appear as children
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				if ( elem.nodeType < 6 ) {
+					return false;
+				}
+			}
+			return true;
+		},
+
+		"parent": function( elem ) {
+			return !Expr.pseudos["empty"]( elem );
+		},
+
+		// Element/input types
+		"header": function( elem ) {
+			return rheader.test( elem.nodeName );
+		},
+
+		"input": function( elem ) {
+			return rinputs.test( elem.nodeName );
+		},
+
+		"button": function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && elem.type === "button" || name === "button";
+		},
+
+		"text": function( elem ) {
+			var attr;
+			return elem.nodeName.toLowerCase() === "input" &&
+				elem.type === "text" &&
+
+				// Support: IE<8
+				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+		},
+
+		// Position-in-collection
+		"first": createPositionalPseudo(function() {
+			return [ 0 ];
+		}),
+
+		"last": createPositionalPseudo(function( matchIndexes, length ) {
+			return [ length - 1 ];
+		}),
+
+		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			return [ argument < 0 ? argument + length : argument ];
+		}),
+
+		"even": createPositionalPseudo(function( matchIndexes, length ) {
+			var i = 0;
+			for ( ; i < length; i += 2 ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"odd": createPositionalPseudo(function( matchIndexes, length ) {
+			var i = 1;
+			for ( ; i < length; i += 2 ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			var i = argument < 0 ? argument + length : argument;
+			for ( ; --i >= 0; ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			var i = argument < 0 ? argument + length : argument;
+			for ( ; ++i < length; ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		})
+	}
+};
+
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+	Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+	Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+	var matched, match, tokens, type,
+		soFar, groups, preFilters,
+		cached = tokenCache[ selector + " " ];
+
+	if ( cached ) {
+		return parseOnly ? 0 : cached.slice( 0 );
+	}
+
+	soFar = selector;
+	groups = [];
+	preFilters = Expr.preFilter;
+
+	while ( soFar ) {
+
+		// Comma and first run
+		if ( !matched || (match = rcomma.exec( soFar )) ) {
+			if ( match ) {
+				// Don't consume trailing commas as valid
+				soFar = soFar.slice( match[0].length ) || soFar;
+			}
+			groups.push( (tokens = []) );
+		}
+
+		matched = false;
+
+		// Combinators
+		if ( (match = rcombinators.exec( soFar )) ) {
+			matched = match.shift();
+			tokens.push({
+				value: matched,
+				// Cast descendant combinators to space
+				type: match[0].replace( rtrim, " " )
+			});
+			soFar = soFar.slice( matched.length );
+		}
+
+		// Filters
+		for ( type in Expr.filter ) {
+			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+				(match = preFilters[ type ]( match ))) ) {
+				matched = match.shift();
+				tokens.push({
+					value: matched,
+					type: type,
+					matches: match
+				});
+				soFar = soFar.slice( matched.length );
+			}
+		}
+
+		if ( !matched ) {
+			break;
+		}
+	}
+
+	// Return the length of the invalid excess
+	// if we're just parsing
+	// Otherwise, throw an error or return tokens
+	return parseOnly ?
+		soFar.length :
+		soFar ?
+			Sizzle.error( selector ) :
+			// Cache the tokens
+			tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+	var i = 0,
+		len = tokens.length,
+		selector = "";
+	for ( ; i < len; i++ ) {
+		selector += tokens[i].value;
+	}
+	return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+	var dir = combinator.dir,
+		checkNonElements = base && dir === "parentNode",
+		doneName = done++;
+
+	return combinator.first ?
+		// Check against closest ancestor/preceding element
+		function( elem, context, xml ) {
+			while ( (elem = elem[ dir ]) ) {
+				if ( elem.nodeType === 1 || checkNonElements ) {
+					return matcher( elem, context, xml );
+				}
+			}
+		} :
+
+		// Check against all ancestor/preceding elements
+		function( elem, context, xml ) {
+			var oldCache, uniqueCache, outerCache,
+				newCache = [ dirruns, doneName ];
+
+			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
+			if ( xml ) {
+				while ( (elem = elem[ dir ]) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						if ( matcher( elem, context, xml ) ) {
+							return true;
+						}
+					}
+				}
+			} else {
+				while ( (elem = elem[ dir ]) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						outerCache = elem[ expando ] || (elem[ expando ] = {});
+
+						// Support: IE <9 only
+						// Defend against cloned attroperties (jQuery gh-1709)
+						uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
+
+						if ( (oldCache = uniqueCache[ dir ]) &&
+							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+							// Assign to newCache so results back-propagate to previous elements
+							return (newCache[ 2 ] = oldCache[ 2 ]);
+						} else {
+							// Reuse newcache so results back-propagate to previous elements
+							uniqueCache[ dir ] = newCache;
+
+							// A match means we're done; a fail means we have to keep checking
+							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+								return true;
+							}
+						}
+					}
+				}
+			}
+		};
+}
+
+function elementMatcher( matchers ) {
+	return matchers.length > 1 ?
+		function( elem, context, xml ) {
+			var i = matchers.length;
+			while ( i-- ) {
+				if ( !matchers[i]( elem, context, xml ) ) {
+					return false;
+				}
+			}
+			return true;
+		} :
+		matchers[0];
+}
+
+function multipleContexts( selector, contexts, results ) {
+	var i = 0,
+		len = contexts.length;
+	for ( ; i < len; i++ ) {
+		Sizzle( selector, contexts[i], results );
+	}
+	return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+	var elem,
+		newUnmatched = [],
+		i = 0,
+		len = unmatched.length,
+		mapped = map != null;
+
+	for ( ; i < len; i++ ) {
+		if ( (elem = unmatched[i]) ) {
+			if ( !filter || filter( elem, context, xml ) ) {
+				newUnmatched.push( elem );
+				if ( mapped ) {
+					map.push( i );
+				}
+			}
+		}
+	}
+
+	return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+	if ( postFilter && !postFilter[ expando ] ) {
+		postFilter = setMatcher( postFilter );
+	}
+	if ( postFinder && !postFinder[ expando ] ) {
+		postFinder = setMatcher( postFinder, postSelector );
+	}
+	return markFunction(function( seed, results, context, xml ) {
+		var temp, i, elem,
+			preMap = [],
+			postMap = [],
+			preexisting = results.length,
+
+			// Get initial elements from seed or context
+			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+			// Prefilter to get matcher input, preserving a map for seed-results synchronization
+			matcherIn = preFilter && ( seed || !selector ) ?
+				condense( elems, preMap, preFilter, context, xml ) :
+				elems,
+
+			matcherOut = matcher ?
+				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+					// ...intermediate processing is necessary
+					[] :
+
+					// ...otherwise use results directly
+					results :
+				matcherIn;
+
+		// Find primary matches
+		if ( matcher ) {
+			matcher( matcherIn, matcherOut, context, xml );
+		}
+
+		// Apply postFilter
+		if ( postFilter ) {
+			temp = condense( matcherOut, postMap );
+			postFilter( temp, [], context, xml );
+
+			// Un-match failing elements by moving them back to matcherIn
+			i = temp.length;
+			while ( i-- ) {
+				if ( (elem = temp[i]) ) {
+					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+				}
+			}
+		}
+
+		if ( seed ) {
+			if ( postFinder || preFilter ) {
+				if ( postFinder ) {
+					// Get the final matcherOut by condensing this intermediate into postFinder contexts
+					temp = [];
+					i = matcherOut.length;
+					while ( i-- ) {
+						if ( (elem = matcherOut[i]) ) {
+							// Restore matcherIn since elem is not yet a final match
+							temp.push( (matcherIn[i] = elem) );
+						}
+					}
+					postFinder( null, (matcherOut = []), temp, xml );
+				}
+
+				// Move matched elements from seed to results to keep them synchronized
+				i = matcherOut.length;
+				while ( i-- ) {
+					if ( (elem = matcherOut[i]) &&
+						(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
+
+						seed[temp] = !(results[temp] = elem);
+					}
+				}
+			}
+
+		// Add elements to results, through postFinder if defined
+		} else {
+			matcherOut = condense(
+				matcherOut === results ?
+					matcherOut.splice( preexisting, matcherOut.length ) :
+					matcherOut
+			);
+			if ( postFinder ) {
+				postFinder( null, results, matcherOut, xml );
+			} else {
+				push.apply( results, matcherOut );
+			}
+		}
+	});
+}
+
+function matcherFromTokens( tokens ) {
+	var checkContext, matcher, j,
+		len = tokens.length,
+		leadingRelative = Expr.relative[ tokens[0].type ],
+		implicitRelative = leadingRelative || Expr.relative[" "],
+		i = leadingRelative ? 1 : 0,
+
+		// The foundational matcher ensures that elements are reachable from top-level context(s)
+		matchContext = addCombinator( function( elem ) {
+			return elem === checkContext;
+		}, implicitRelative, true ),
+		matchAnyContext = addCombinator( function( elem ) {
+			return indexOf( checkContext, elem ) > -1;
+		}, implicitRelative, true ),
+		matchers = [ function( elem, context, xml ) {
+			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+				(checkContext = context).nodeType ?
+					matchContext( elem, context, xml ) :
+					matchAnyContext( elem, context, xml ) );
+			// Avoid hanging onto element (issue #299)
+			checkContext = null;
+			return ret;
+		} ];
+
+	for ( ; i < len; i++ ) {
+		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+		} else {
+			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+			// Return special upon seeing a positional matcher
+			if ( matcher[ expando ] ) {
+				// Find the next relative operator (if any) for proper handling
+				j = ++i;
+				for ( ; j < len; j++ ) {
+					if ( Expr.relative[ tokens[j].type ] ) {
+						break;
+					}
+				}
+				return setMatcher(
+					i > 1 && elementMatcher( matchers ),
+					i > 1 && toSelector(
+						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
+						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+					).replace( rtrim, "$1" ),
+					matcher,
+					i < j && matcherFromTokens( tokens.slice( i, j ) ),
+					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+					j < len && toSelector( tokens )
+				);
+			}
+			matchers.push( matcher );
+		}
+	}
+
+	return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+	var bySet = setMatchers.length > 0,
+		byElement = elementMatchers.length > 0,
+		superMatcher = function( seed, context, xml, results, outermost ) {
+			var elem, j, matcher,
+				matchedCount = 0,
+				i = "0",
+				unmatched = seed && [],
+				setMatched = [],
+				contextBackup = outermostContext,
+				// We must always have either seed elements or outermost context
+				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+				// Use integer dirruns iff this is the outermost matcher
+				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+				len = elems.length;
+
+			if ( outermost ) {
+				outermostContext = context === document || context || outermost;
+			}
+
+			// Add elements passing elementMatchers directly to results
+			// Support: IE<9, Safari
+			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
+			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+				if ( byElement && elem ) {
+					j = 0;
+					if ( !context && elem.ownerDocument !== document ) {
+						setDocument( elem );
+						xml = !documentIsHTML;
+					}
+					while ( (matcher = elementMatchers[j++]) ) {
+						if ( matcher( elem, context || document, xml) ) {
+							results.push( elem );
+							break;
+						}
+					}
+					if ( outermost ) {
+						dirruns = dirrunsUnique;
+					}
+				}
+
+				// Track unmatched elements for set filters
+				if ( bySet ) {
+					// They will have gone through all possible matchers
+					if ( (elem = !matcher && elem) ) {
+						matchedCount--;
+					}
+
+					// Lengthen the array for every element, matched or not
+					if ( seed ) {
+						unmatched.push( elem );
+					}
+				}
+			}
+
+			// `i` is now the count of elements visited above, and adding it to `matchedCount`
+			// makes the latter nonnegative.
+			matchedCount += i;
+
+			// Apply set filters to unmatched elements
+			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
+			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
+			// no element matchers and no seed.
+			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
+			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
+			// numerically zero.
+			if ( bySet && i !== matchedCount ) {
+				j = 0;
+				while ( (matcher = setMatchers[j++]) ) {
+					matcher( unmatched, setMatched, context, xml );
+				}
+
+				if ( seed ) {
+					// Reintegrate element matches to eliminate the need for sorting
+					if ( matchedCount > 0 ) {
+						while ( i-- ) {
+							if ( !(unmatched[i] || setMatched[i]) ) {
+								setMatched[i] = pop.call( results );
+							}
+						}
+					}
+
+					// Discard index placeholder values to get only actual matches
+					setMatched = condense( setMatched );
+				}
+
+				// Add matches to results
+				push.apply( results, setMatched );
+
+				// Seedless set matches succeeding multiple successful matchers stipulate sorting
+				if ( outermost && !seed && setMatched.length > 0 &&
+					( matchedCount + setMatchers.length ) > 1 ) {
+
+					Sizzle.uniqueSort( results );
+				}
+			}
+
+			// Override manipulation of globals by nested matchers
+			if ( outermost ) {
+				dirruns = dirrunsUnique;
+				outermostContext = contextBackup;
+			}
+
+			return unmatched;
+		};
+
+	return bySet ?
+		markFunction( superMatcher ) :
+		superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+	var i,
+		setMatchers = [],
+		elementMatchers = [],
+		cached = compilerCache[ selector + " " ];
+
+	if ( !cached ) {
+		// Generate a function of recursive functions that can be used to check each element
+		if ( !match ) {
+			match = tokenize( selector );
+		}
+		i = match.length;
+		while ( i-- ) {
+			cached = matcherFromTokens( match[i] );
+			if ( cached[ expando ] ) {
+				setMatchers.push( cached );
+			} else {
+				elementMatchers.push( cached );
+			}
+		}
+
+		// Cache the compiled function
+		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+		// Save selector and tokenization
+		cached.selector = selector;
+	}
+	return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ *  selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ *  selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+	var i, tokens, token, type, find,
+		compiled = typeof selector === "function" && selector,
+		match = !seed && tokenize( (selector = compiled.selector || selector) );
+
+	results = results || [];
+
+	// Try to minimize operations if there is only one selector in the list and no seed
+	// (the latter of which guarantees us context)
+	if ( match.length === 1 ) {
+
+		// Reduce context if the leading compound selector is an ID
+		tokens = match[0] = match[0].slice( 0 );
+		if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+				support.getById && context.nodeType === 9 && documentIsHTML &&
+				Expr.relative[ tokens[1].type ] ) {
+
+			context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+			if ( !context ) {
+				return results;
+
+			// Precompiled matchers will still verify ancestry, so step up a level
+			} else if ( compiled ) {
+				context = context.parentNode;
+			}
+
+			selector = selector.slice( tokens.shift().value.length );
+		}
+
+		// Fetch a seed set for right-to-left matching
+		i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+		while ( i-- ) {
+			token = tokens[i];
+
+			// Abort if we hit a combinator
+			if ( Expr.relative[ (type = token.type) ] ) {
+				break;
+			}
+			if ( (find = Expr.find[ type ]) ) {
+				// Search, expanding context for leading sibling combinators
+				if ( (seed = find(
+					token.matches[0].replace( runescape, funescape ),
+					rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+				)) ) {
+
+					// If seed is empty or no tokens remain, we can return early
+					tokens.splice( i, 1 );
+					selector = seed.length && toSelector( tokens );
+					if ( !selector ) {
+						push.apply( results, seed );
+						return results;
+					}
+
+					break;
+				}
+			}
+		}
+	}
+
+	// Compile and execute a filtering function if one is not provided
+	// Provide `match` to avoid retokenization if we modified the selector above
+	( compiled || compile( selector, match ) )(
+		seed,
+		context,
+		!documentIsHTML,
+		results,
+		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
+	);
+	return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Support: Chrome 14-35+
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert(function( div1 ) {
+	// Should return 1, but returns 4 (following)
+	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+});
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert(function( div ) {
+	div.innerHTML = "<a href='#'></a>";
+	return div.firstChild.getAttribute("href") === "#" ;
+}) ) {
+	addHandle( "type|href|height|width", function( elem, name, isXML ) {
+		if ( !isXML ) {
+			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+		}
+	});
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert(function( div ) {
+	div.innerHTML = "<input/>";
+	div.firstChild.setAttribute( "value", "" );
+	return div.firstChild.getAttribute( "value" ) === "";
+}) ) {
+	addHandle( "value", function( elem, name, isXML ) {
+		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+			return elem.defaultValue;
+		}
+	});
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert(function( div ) {
+	return div.getAttribute("disabled") == null;
+}) ) {
+	addHandle( booleans, function( elem, name, isXML ) {
+		var val;
+		if ( !isXML ) {
+			return elem[ name ] === true ? name.toLowerCase() :
+					(val = elem.getAttributeNode( name )) && val.specified ?
+					val.value :
+				null;
+		}
+	});
+}
+
+return Sizzle;
+
+})( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[ ":" ] = jQuery.expr.pseudos;
+jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+
+var dir = function( elem, dir, until ) {
+	var matched = [],
+		truncate = until !== undefined;
+
+	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
+		if ( elem.nodeType === 1 ) {
+			if ( truncate && jQuery( elem ).is( until ) ) {
+				break;
+			}
+			matched.push( elem );
+		}
+	}
+	return matched;
+};
+
+
+var siblings = function( n, elem ) {
+	var matched = [];
+
+	for ( ; n; n = n.nextSibling ) {
+		if ( n.nodeType === 1 && n !== elem ) {
+			matched.push( n );
+		}
+	}
+
+	return matched;
+};
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+var rsingleTag = ( /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/ );
+
+
+
+var risSimple = /^.[^:#\[\.,]*$/;
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+	if ( jQuery.isFunction( qualifier ) ) {
+		return jQuery.grep( elements, function( elem, i ) {
+			/* jshint -W018 */
+			return !!qualifier.call( elem, i, elem ) !== not;
+		} );
+
+	}
+
+	if ( qualifier.nodeType ) {
+		return jQuery.grep( elements, function( elem ) {
+			return ( elem === qualifier ) !== not;
+		} );
+
+	}
+
+	if ( typeof qualifier === "string" ) {
+		if ( risSimple.test( qualifier ) ) {
+			return jQuery.filter( qualifier, elements, not );
+		}
+
+		qualifier = jQuery.filter( qualifier, elements );
+	}
+
+	return jQuery.grep( elements, function( elem ) {
+		return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
+	} );
+}
+
+jQuery.filter = function( expr, elems, not ) {
+	var elem = elems[ 0 ];
+
+	if ( not ) {
+		expr = ":not(" + expr + ")";
+	}
+
+	return elems.length === 1 && elem.nodeType === 1 ?
+		jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+		jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+			return elem.nodeType === 1;
+		} ) );
+};
+
+jQuery.fn.extend( {
+	find: function( selector ) {
+		var i,
+			len = this.length,
+			ret = [],
+			self = this;
+
+		if ( typeof selector !== "string" ) {
+			return this.pushStack( jQuery( selector ).filter( function() {
+				for ( i = 0; i < len; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			} ) );
+		}
+
+		for ( i = 0; i < len; i++ ) {
+			jQuery.find( selector, self[ i ], ret );
+		}
+
+		// Needed because $( selector, context ) becomes $( context ).find( selector )
+		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+		ret.selector = this.selector ? this.selector + " " + selector : selector;
+		return ret;
+	},
+	filter: function( selector ) {
+		return this.pushStack( winnow( this, selector || [], false ) );
+	},
+	not: function( selector ) {
+		return this.pushStack( winnow( this, selector || [], true ) );
+	},
+	is: function( selector ) {
+		return !!winnow(
+			this,
+
+			// If this is a positional/relative selector, check membership in the returned set
+			// so $("p:first").is("p:last") won't return true for a doc with two "p".
+			typeof selector === "string" && rneedsContext.test( selector ) ?
+				jQuery( selector ) :
+				selector || [],
+			false
+		).length;
+	}
+} );
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+	// A simple way to check for HTML strings
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	// Strict HTML recognition (#11290: must start with <)
+	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+	init = jQuery.fn.init = function( selector, context, root ) {
+		var match, elem;
+
+		// HANDLE: $(""), $(null), $(undefined), $(false)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Method init() accepts an alternate rootjQuery
+		// so migrate can support jQuery.sub (gh-2101)
+		root = root || rootjQuery;
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			if ( selector[ 0 ] === "<" &&
+				selector[ selector.length - 1 ] === ">" &&
+				selector.length >= 3 ) {
+
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = rquickExpr.exec( selector );
+			}
+
+			// Match html or make sure no context is specified for #id
+			if ( match && ( match[ 1 ] || !context ) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[ 1 ] ) {
+					context = context instanceof jQuery ? context[ 0 ] : context;
+
+					// Option to run scripts is true for back-compat
+					// Intentionally let the error be thrown if parseHTML is not present
+					jQuery.merge( this, jQuery.parseHTML(
+						match[ 1 ],
+						context && context.nodeType ? context.ownerDocument || context : document,
+						true
+					) );
+
+					// HANDLE: $(html, props)
+					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
+						for ( match in context ) {
+
+							// Properties of context are called as methods if possible
+							if ( jQuery.isFunction( this[ match ] ) ) {
+								this[ match ]( context[ match ] );
+
+							// ...and otherwise set as attributes
+							} else {
+								this.attr( match, context[ match ] );
+							}
+						}
+					}
+
+					return this;
+
+				// HANDLE: $(#id)
+				} else {
+					elem = document.getElementById( match[ 2 ] );
+
+					// Support: Blackberry 4.6
+					// gEBID returns nodes no longer in the document (#6963)
+					if ( elem && elem.parentNode ) {
+
+						// Inject the element directly into the jQuery object
+						this.length = 1;
+						this[ 0 ] = elem;
+					}
+
+					this.context = document;
+					this.selector = selector;
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return ( context || root ).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(DOMElement)
+		} else if ( selector.nodeType ) {
+			this.context = this[ 0 ] = selector;
+			this.length = 1;
+			return this;
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) ) {
+			return root.ready !== undefined ?
+				root.ready( selector ) :
+
+				// Execute immediately if ready is not present
+				selector( jQuery );
+		}
+
+		if ( selector.selector !== undefined ) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	};
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+
+	// Methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend( {
+	has: function( target ) {
+		var targets = jQuery( target, this ),
+			l = targets.length;
+
+		return this.filter( function() {
+			var i = 0;
+			for ( ; i < l; i++ ) {
+				if ( jQuery.contains( this, targets[ i ] ) ) {
+					return true;
+				}
+			}
+		} );
+	},
+
+	closest: function( selectors, context ) {
+		var cur,
+			i = 0,
+			l = this.length,
+			matched = [],
+			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+				jQuery( selectors, context || this.context ) :
+				0;
+
+		for ( ; i < l; i++ ) {
+			for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
+
+				// Always skip document fragments
+				if ( cur.nodeType < 11 && ( pos ?
+					pos.index( cur ) > -1 :
+
+					// Don't pass non-elements to Sizzle
+					cur.nodeType === 1 &&
+						jQuery.find.matchesSelector( cur, selectors ) ) ) {
+
+					matched.push( cur );
+					break;
+				}
+			}
+		}
+
+		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
+	},
+
+	// Determine the position of an element within the set
+	index: function( elem ) {
+
+		// No argument, return index in parent
+		if ( !elem ) {
+			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+		}
+
+		// Index in selector
+		if ( typeof elem === "string" ) {
+			return indexOf.call( jQuery( elem ), this[ 0 ] );
+		}
+
+		// Locate the position of the desired element
+		return indexOf.call( this,
+
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[ 0 ] : elem
+		);
+	},
+
+	add: function( selector, context ) {
+		return this.pushStack(
+			jQuery.uniqueSort(
+				jQuery.merge( this.get(), jQuery( selector, context ) )
+			)
+		);
+	},
+
+	addBack: function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter( selector )
+		);
+	}
+} );
+
+function sibling( cur, dir ) {
+	while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
+	return cur;
+}
+
+jQuery.each( {
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return sibling( elem, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return sibling( elem, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return siblings( ( elem.parentNode || {} ).firstChild, elem );
+	},
+	children: function( elem ) {
+		return siblings( elem.firstChild );
+	},
+	contents: function( elem ) {
+		return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var matched = jQuery.map( this, fn, until );
+
+		if ( name.slice( -5 ) !== "Until" ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			matched = jQuery.filter( selector, matched );
+		}
+
+		if ( this.length > 1 ) {
+
+			// Remove duplicates
+			if ( !guaranteedUnique[ name ] ) {
+				jQuery.uniqueSort( matched );
+			}
+
+			// Reverse order for parents* and prev-derivatives
+			if ( rparentsprev.test( name ) ) {
+				matched.reverse();
+			}
+		}
+
+		return this.pushStack( matched );
+	};
+} );
+var rnotwhite = ( /\S+/g );
+
+
+
+// Convert String-formatted options into Object-formatted ones
+function createOptions( options ) {
+	var object = {};
+	jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+		object[ flag ] = true;
+	} );
+	return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *	options: an optional list of space-separated options that will change how
+ *			the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ *	once:			will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *	memory:			will keep track of previous values and will call any callback added
+ *					after the list has been fired right away with the latest "memorized"
+ *					values (like a Deferred)
+ *
+ *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *	stopOnFalse:	interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+	// Convert options from String-formatted to Object-formatted if needed
+	// (we check in cache first)
+	options = typeof options === "string" ?
+		createOptions( options ) :
+		jQuery.extend( {}, options );
+
+	var // Flag to know if list is currently firing
+		firing,
+
+		// Last fire value for non-forgettable lists
+		memory,
+
+		// Flag to know if list was already fired
+		fired,
+
+		// Flag to prevent firing
+		locked,
+
+		// Actual callback list
+		list = [],
+
+		// Queue of execution data for repeatable lists
+		queue = [],
+
+		// Index of currently firing callback (modified by add/remove as needed)
+		firingIndex = -1,
+
+		// Fire callbacks
+		fire = function() {
+
+			// Enforce single-firing
+			locked = options.once;
+
+			// Execute callbacks for all pending executions,
+			// respecting firingIndex overrides and runtime changes
+			fired = firing = true;
+			for ( ; queue.length; firingIndex = -1 ) {
+				memory = queue.shift();
+				while ( ++firingIndex < list.length ) {
+
+					// Run callback and check for early termination
+					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
+						options.stopOnFalse ) {
+
+						// Jump to end and forget the data so .add doesn't re-fire
+						firingIndex = list.length;
+						memory = false;
+					}
+				}
+			}
+
+			// Forget the data if we're done with it
+			if ( !options.memory ) {
+				memory = false;
+			}
+
+			firing = false;
+
+			// Clean up if we're done firing for good
+			if ( locked ) {
+
+				// Keep an empty list if we have data for future add calls
+				if ( memory ) {
+					list = [];
+
+				// Otherwise, this object is spent
+				} else {
+					list = "";
+				}
+			}
+		},
+
+		// Actual Callbacks object
+		self = {
+
+			// Add a callback or a collection of callbacks to the list
+			add: function() {
+				if ( list ) {
+
+					// If we have memory from a past run, we should fire after adding
+					if ( memory && !firing ) {
+						firingIndex = list.length - 1;
+						queue.push( memory );
+					}
+
+					( function add( args ) {
+						jQuery.each( args, function( _, arg ) {
+							if ( jQuery.isFunction( arg ) ) {
+								if ( !options.unique || !self.has( arg ) ) {
+									list.push( arg );
+								}
+							} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {
+
+								// Inspect recursively
+								add( arg );
+							}
+						} );
+					} )( arguments );
+
+					if ( memory && !firing ) {
+						fire();
+					}
+				}
+				return this;
+			},
+
+			// Remove a callback from the list
+			remove: function() {
+				jQuery.each( arguments, function( _, arg ) {
+					var index;
+					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+						list.splice( index, 1 );
+
+						// Handle firing indexes
+						if ( index <= firingIndex ) {
+							firingIndex--;
+						}
+					}
+				} );
+				return this;
+			},
+
+			// Check if a given callback is in the list.
+			// If no argument is given, return whether or not list has callbacks attached.
+			has: function( fn ) {
+				return fn ?
+					jQuery.inArray( fn, list ) > -1 :
+					list.length > 0;
+			},
+
+			// Remove all callbacks from the list
+			empty: function() {
+				if ( list ) {
+					list = [];
+				}
+				return this;
+			},
+
+			// Disable .fire and .add
+			// Abort any current/pending executions
+			// Clear all callbacks and values
+			disable: function() {
+				locked = queue = [];
+				list = memory = "";
+				return this;
+			},
+			disabled: function() {
+				return !list;
+			},
+
+			// Disable .fire
+			// Also disable .add unless we have memory (since it would have no effect)
+			// Abort any pending executions
+			lock: function() {
+				locked = queue = [];
+				if ( !memory ) {
+					list = memory = "";
+				}
+				return this;
+			},
+			locked: function() {
+				return !!locked;
+			},
+
+			// Call all callbacks with the given context and arguments
+			fireWith: function( context, args ) {
+				if ( !locked ) {
+					args = args || [];
+					args = [ context, args.slice ? args.slice() : args ];
+					queue.push( args );
+					if ( !firing ) {
+						fire();
+					}
+				}
+				return this;
+			},
+
+			// Call all the callbacks with the given arguments
+			fire: function() {
+				self.fireWith( this, arguments );
+				return this;
+			},
+
+			// To know if the callbacks have already been called at least once
+			fired: function() {
+				return !!fired;
+			}
+		};
+
+	return self;
+};
+
+
+jQuery.extend( {
+
+	Deferred: function( func ) {
+		var tuples = [
+
+				// action, add listener, listener list, final state
+				[ "resolve", "done", jQuery.Callbacks( "once memory" ), "resolved" ],
+				[ "reject", "fail", jQuery.Callbacks( "once memory" ), "rejected" ],
+				[ "notify", "progress", jQuery.Callbacks( "memory" ) ]
+			],
+			state = "pending",
+			promise = {
+				state: function() {
+					return state;
+				},
+				always: function() {
+					deferred.done( arguments ).fail( arguments );
+					return this;
+				},
+				then: function( /* fnDone, fnFail, fnProgress */ ) {
+					var fns = arguments;
+					return jQuery.Deferred( function( newDefer ) {
+						jQuery.each( tuples, function( i, tuple ) {
+							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+
+							// deferred[ done | fail | progress ] for forwarding actions to newDefer
+							deferred[ tuple[ 1 ] ]( function() {
+								var returned = fn && fn.apply( this, arguments );
+								if ( returned && jQuery.isFunction( returned.promise ) ) {
+									returned.promise()
+										.progress( newDefer.notify )
+										.done( newDefer.resolve )
+										.fail( newDefer.reject );
+								} else {
+									newDefer[ tuple[ 0 ] + "With" ](
+										this === promise ? newDefer.promise() : this,
+										fn ? [ returned ] : arguments
+									);
+								}
+							} );
+						} );
+						fns = null;
+					} ).promise();
+				},
+
+				// Get a promise for this deferred
+				// If obj is provided, the promise aspect is added to the object
+				promise: function( obj ) {
+					return obj != null ? jQuery.extend( obj, promise ) : promise;
+				}
+			},
+			deferred = {};
+
+		// Keep pipe for back-compat
+		promise.pipe = promise.then;
+
+		// Add list-specific methods
+		jQuery.each( tuples, function( i, tuple ) {
+			var list = tuple[ 2 ],
+				stateString = tuple[ 3 ];
+
+			// promise[ done | fail | progress ] = list.add
+			promise[ tuple[ 1 ] ] = list.add;
+
+			// Handle state
+			if ( stateString ) {
+				list.add( function() {
+
+					// state = [ resolved | rejected ]
+					state = stateString;
+
+				// [ reject_list | resolve_list ].disable; progress_list.lock
+				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+			}
+
+			// deferred[ resolve | reject | notify ]
+			deferred[ tuple[ 0 ] ] = function() {
+				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? promise : this, arguments );
+				return this;
+			};
+			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
+		} );
+
+		// Make the deferred a promise
+		promise.promise( deferred );
+
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+
+		// All done!
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( subordinate /* , ..., subordinateN */ ) {
+		var i = 0,
+			resolveValues = slice.call( arguments ),
+			length = resolveValues.length,
+
+			// the count of uncompleted subordinates
+			remaining = length !== 1 ||
+				( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+			// the master Deferred.
+			// If resolveValues consist of only a single Deferred, just use that.
+			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+			// Update function for both resolve and progress values
+			updateFunc = function( i, contexts, values ) {
+				return function( value ) {
+					contexts[ i ] = this;
+					values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+					if ( values === progressValues ) {
+						deferred.notifyWith( contexts, values );
+					} else if ( !( --remaining ) ) {
+						deferred.resolveWith( contexts, values );
+					}
+				};
+			},
+
+			progressValues, progressContexts, resolveContexts;
+
+		// Add listeners to Deferred subordinates; treat others as resolved
+		if ( length > 1 ) {
+			progressValues = new Array( length );
+			progressContexts = new Array( length );
+			resolveContexts = new Array( length );
+			for ( ; i < length; i++ ) {
+				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+					resolveValues[ i ].promise()
+						.progress( updateFunc( i, progressContexts, progressValues ) )
+						.done( updateFunc( i, resolveContexts, resolveValues ) )
+						.fail( deferred.reject );
+				} else {
+					--remaining;
+				}
+			}
+		}
+
+		// If we're not waiting on anything, resolve the master
+		if ( !remaining ) {
+			deferred.resolveWith( resolveContexts, resolveValues );
+		}
+
+		return deferred.promise();
+	}
+} );
+
+
+// The deferred used on DOM ready
+var readyList;
+
+jQuery.fn.ready = function( fn ) {
+
+	// Add the callback
+	jQuery.ready.promise().done( fn );
+
+	return this;
+};
+
+jQuery.extend( {
+
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Hold (or release) the ready event
+	holdReady: function( hold ) {
+		if ( hold ) {
+			jQuery.readyWait++;
+		} else {
+			jQuery.ready( true );
+		}
+	},
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+
+		// Abort if there are pending holds or we're already ready
+		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+			return;
+		}
+
+		// Remember that the DOM is ready
+		jQuery.isReady = true;
+
+		// If a normal DOM Ready event fired, decrement, and wait if need be
+		if ( wait !== true && --jQuery.readyWait > 0 ) {
+			return;
+		}
+
+		// If there are functions bound, to execute
+		readyList.resolveWith( document, [ jQuery ] );
+
+		// Trigger any bound ready events
+		if ( jQuery.fn.triggerHandler ) {
+			jQuery( document ).triggerHandler( "ready" );
+			jQuery( document ).off( "ready" );
+		}
+	}
+} );
+
+/**
+ * The ready event handler and self cleanup method
+ */
+function completed() {
+	document.removeEventListener( "DOMContentLoaded", completed );
+	window.removeEventListener( "load", completed );
+	jQuery.ready();
+}
+
+jQuery.ready.promise = function( obj ) {
+	if ( !readyList ) {
+
+		readyList = jQuery.Deferred();
+
+		// Catch cases where $(document).ready() is called
+		// after the browser event has already occurred.
+		// Support: IE9-10 only
+		// Older IE sometimes signals "interactive" too soon
+		if ( document.readyState === "complete" ||
+			( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
+
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			window.setTimeout( jQuery.ready );
+
+		} else {
+
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", completed );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", completed );
+		}
+	}
+	return readyList.promise( obj );
+};
+
+// Kick off the DOM ready check even if the user does not
+jQuery.ready.promise();
+
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+	var i = 0,
+		len = elems.length,
+		bulk = key == null;
+
+	// Sets many values
+	if ( jQuery.type( key ) === "object" ) {
+		chainable = true;
+		for ( i in key ) {
+			access( elems, fn, i, key[ i ], true, emptyGet, raw );
+		}
+
+	// Sets one value
+	} else if ( value !== undefined ) {
+		chainable = true;
+
+		if ( !jQuery.isFunction( value ) ) {
+			raw = true;
+		}
+
+		if ( bulk ) {
+
+			// Bulk operations run against the entire set
+			if ( raw ) {
+				fn.call( elems, value );
+				fn = null;
+
+			// ...except when executing function values
+			} else {
+				bulk = fn;
+				fn = function( elem, key, value ) {
+					return bulk.call( jQuery( elem ), value );
+				};
+			}
+		}
+
+		if ( fn ) {
+			for ( ; i < len; i++ ) {
+				fn(
+					elems[ i ], key, raw ?
+					value :
+					value.call( elems[ i ], i, fn( elems[ i ], key ) )
+				);
+			}
+		}
+	}
+
+	return chainable ?
+		elems :
+
+		// Gets
+		bulk ?
+			fn.call( elems ) :
+			len ? fn( elems[ 0 ], key ) : emptyGet;
+};
+var acceptData = function( owner ) {
+
+	// Accepts only:
+	//  - Node
+	//    - Node.ELEMENT_NODE
+	//    - Node.DOCUMENT_NODE
+	//  - Object
+	//    - Any
+	/* jshint -W018 */
+	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+};
+
+
+
+
+function Data() {
+	this.expando = jQuery.expando + Data.uid++;
+}
+
+Data.uid = 1;
+
+Data.prototype = {
+
+	register: function( owner, initial ) {
+		var value = initial || {};
+
+		// If it is a node unlikely to be stringify-ed or looped over
+		// use plain assignment
+		if ( owner.nodeType ) {
+			owner[ this.expando ] = value;
+
+		// Otherwise secure it in a non-enumerable, non-writable property
+		// configurability must be true to allow the property to be
+		// deleted with the delete operator
+		} else {
+			Object.defineProperty( owner, this.expando, {
+				value: value,
+				writable: true,
+				configurable: true
+			} );
+		}
+		return owner[ this.expando ];
+	},
+	cache: function( owner ) {
+
+		// We can accept data for non-element nodes in modern browsers,
+		// but we should not, see #8335.
+		// Always return an empty object.
+		if ( !acceptData( owner ) ) {
+			return {};
+		}
+
+		// Check if the owner object already has a cache
+		var value = owner[ this.expando ];
+
+		// If not, create one
+		if ( !value ) {
+			value = {};
+
+			// We can accept data for non-element nodes in modern browsers,
+			// but we should not, see #8335.
+			// Always return an empty object.
+			if ( acceptData( owner ) ) {
+
+				// If it is a node unlikely to be stringify-ed or looped over
+				// use plain assignment
+				if ( owner.nodeType ) {
+					owner[ this.expando ] = value;
+
+				// Otherwise secure it in a non-enumerable property
+				// configurable must be true to allow the property to be
+				// deleted when data is removed
+				} else {
+					Object.defineProperty( owner, this.expando, {
+						value: value,
+						configurable: true
+					} );
+				}
+			}
+		}
+
+		return value;
+	},
+	set: function( owner, data, value ) {
+		var prop,
+			cache = this.cache( owner );
+
+		// Handle: [ owner, key, value ] args
+		if ( typeof data === "string" ) {
+			cache[ data ] = value;
+
+		// Handle: [ owner, { properties } ] args
+		} else {
+
+			// Copy the properties one-by-one to the cache object
+			for ( prop in data ) {
+				cache[ prop ] = data[ prop ];
+			}
+		}
+		return cache;
+	},
+	get: function( owner, key ) {
+		return key === undefined ?
+			this.cache( owner ) :
+			owner[ this.expando ] && owner[ this.expando ][ key ];
+	},
+	access: function( owner, key, value ) {
+		var stored;
+
+		// In cases where either:
+		//
+		//   1. No key was specified
+		//   2. A string key was specified, but no value provided
+		//
+		// Take the "read" path and allow the get method to determine
+		// which value to return, respectively either:
+		//
+		//   1. The entire cache object
+		//   2. The data stored at the key
+		//
+		if ( key === undefined ||
+				( ( key && typeof key === "string" ) && value === undefined ) ) {
+
+			stored = this.get( owner, key );
+
+			return stored !== undefined ?
+				stored : this.get( owner, jQuery.camelCase( key ) );
+		}
+
+		// When the key is not a string, or both a key and value
+		// are specified, set or extend (existing objects) with either:
+		//
+		//   1. An object of properties
+		//   2. A key and value
+		//
+		this.set( owner, key, value );
+
+		// Since the "set" path can have two possible entry points
+		// return the expected data based on which path was taken[*]
+		return value !== undefined ? value : key;
+	},
+	remove: function( owner, key ) {
+		var i, name, camel,
+			cache = owner[ this.expando ];
+
+		if ( cache === undefined ) {
+			return;
+		}
+
+		if ( key === undefined ) {
+			this.register( owner );
+
+		} else {
+
+			// Support array or space separated string of keys
+			if ( jQuery.isArray( key ) ) {
+
+				// If "name" is an array of keys...
+				// When data is initially created, via ("key", "val") signature,
+				// keys will be converted to camelCase.
+				// Since there is no way to tell _how_ a key was added, remove
+				// both plain key and camelCase key. #12786
+				// This will only penalize the array argument path.
+				name = key.concat( key.map( jQuery.camelCase ) );
+			} else {
+				camel = jQuery.camelCase( key );
+
+				// Try the string as a key before any manipulation
+				if ( key in cache ) {
+					name = [ key, camel ];
+				} else {
+
+					// If a key with the spaces exists, use it.
+					// Otherwise, create an array by matching non-whitespace
+					name = camel;
+					name = name in cache ?
+						[ name ] : ( name.match( rnotwhite ) || [] );
+				}
+			}
+
+			i = name.length;
+
+			while ( i-- ) {
+				delete cache[ name[ i ] ];
+			}
+		}
+
+		// Remove the expando if there's no more data
+		if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
+
+			// Support: Chrome <= 35-45+
+			// Webkit & Blink performance suffers when deleting properties
+			// from DOM nodes, so set to undefined instead
+			// https://code.google.com/p/chromium/issues/detail?id=378607
+			if ( owner.nodeType ) {
+				owner[ this.expando ] = undefined;
+			} else {
+				delete owner[ this.expando ];
+			}
+		}
+	},
+	hasData: function( owner ) {
+		var cache = owner[ this.expando ];
+		return cache !== undefined && !jQuery.isEmptyObject( cache );
+	}
+};
+var dataPriv = new Data();
+
+var dataUser = new Data();
+
+
+
+//	Implementation Summary
+//
+//	1. Enforce API surface and semantic compatibility with 1.9.x branch
+//	2. Improve the module's maintainability by reducing the storage
+//		paths to a single mechanism.
+//	3. Use the same single mechanism to support "private" and "user" data.
+//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+//	5. Avoid exposing implementation details on user objects (eg. expando properties)
+//	6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+	rmultiDash = /[A-Z]/g;
+
+function dataAttr( elem, key, data ) {
+	var name;
+
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+		name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
+		data = elem.getAttribute( name );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = data === "true" ? true :
+					data === "false" ? false :
+					data === "null" ? null :
+
+					// Only convert to a number if it doesn't change the string
+					+data + "" === data ? +data :
+					rbrace.test( data ) ? jQuery.parseJSON( data ) :
+					data;
+			} catch ( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			dataUser.set( elem, key, data );
+		} else {
+			data = undefined;
+		}
+	}
+	return data;
+}
+
+jQuery.extend( {
+	hasData: function( elem ) {
+		return dataUser.hasData( elem ) || dataPriv.hasData( elem );
+	},
+
+	data: function( elem, name, data ) {
+		return dataUser.access( elem, name, data );
+	},
+
+	removeData: function( elem, name ) {
+		dataUser.remove( elem, name );
+	},
+
+	// TODO: Now that all calls to _data and _removeData have been replaced
+	// with direct calls to dataPriv methods, these can be deprecated.
+	_data: function( elem, name, data ) {
+		return dataPriv.access( elem, name, data );
+	},
+
+	_removeData: function( elem, name ) {
+		dataPriv.remove( elem, name );
+	}
+} );
+
+jQuery.fn.extend( {
+	data: function( key, value ) {
+		var i, name, data,
+			elem = this[ 0 ],
+			attrs = elem && elem.attributes;
+
+		// Gets all values
+		if ( key === undefined ) {
+			if ( this.length ) {
+				data = dataUser.get( elem );
+
+				if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
+					i = attrs.length;
+					while ( i-- ) {
+
+						// Support: IE11+
+						// The attrs elements can be null (#14894)
+						if ( attrs[ i ] ) {
+							name = attrs[ i ].name;
+							if ( name.indexOf( "data-" ) === 0 ) {
+								name = jQuery.camelCase( name.slice( 5 ) );
+								dataAttr( elem, name, data[ name ] );
+							}
+						}
+					}
+					dataPriv.set( elem, "hasDataAttrs", true );
+				}
+			}
+
+			return data;
+		}
+
+		// Sets multiple values
+		if ( typeof key === "object" ) {
+			return this.each( function() {
+				dataUser.set( this, key );
+			} );
+		}
+
+		return access( this, function( value ) {
+			var data, camelKey;
+
+			// The calling jQuery object (element matches) is not empty
+			// (and therefore has an element appears at this[ 0 ]) and the
+			// `value` parameter was not undefined. An empty jQuery object
+			// will result in `undefined` for elem = this[ 0 ] which will
+			// throw an exception if an attempt to read a data cache is made.
+			if ( elem && value === undefined ) {
+
+				// Attempt to get data from the cache
+				// with the key as-is
+				data = dataUser.get( elem, key ) ||
+
+					// Try to find dashed key if it exists (gh-2779)
+					// This is for 2.2.x only
+					dataUser.get( elem, key.replace( rmultiDash, "-$&" ).toLowerCase() );
+
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				camelKey = jQuery.camelCase( key );
+
+				// Attempt to get data from the cache
+				// with the key camelized
+				data = dataUser.get( elem, camelKey );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// Attempt to "discover" the data in
+				// HTML5 custom data-* attrs
+				data = dataAttr( elem, camelKey, undefined );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// We tried really hard, but the data doesn't exist.
+				return;
+			}
+
+			// Set the data...
+			camelKey = jQuery.camelCase( key );
+			this.each( function() {
+
+				// First, attempt to store a copy or reference of any
+				// data that might've been store with a camelCased key.
+				var data = dataUser.get( this, camelKey );
+
+				// For HTML5 data-* attribute interop, we have to
+				// store property names with dashes in a camelCase form.
+				// This might not apply to all properties...*
+				dataUser.set( this, camelKey, value );
+
+				// *... In the case of properties that might _actually_
+				// have dashes, we need to also store a copy of that
+				// unchanged property.
+				if ( key.indexOf( "-" ) > -1 && data !== undefined ) {
+					dataUser.set( this, key, value );
+				}
+			} );
+		}, null, value, arguments.length > 1, null, true );
+	},
+
+	removeData: function( key ) {
+		return this.each( function() {
+			dataUser.remove( this, key );
+		} );
+	}
+} );
+
+
+jQuery.extend( {
+	queue: function( elem, type, data ) {
+		var queue;
+
+		if ( elem ) {
+			type = ( type || "fx" ) + "queue";
+			queue = dataPriv.get( elem, type );
+
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !queue || jQuery.isArray( data ) ) {
+					queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
+				} else {
+					queue.push( data );
+				}
+			}
+			return queue || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			startLength = queue.length,
+			fn = queue.shift(),
+			hooks = jQuery._queueHooks( elem, type ),
+			next = function() {
+				jQuery.dequeue( elem, type );
+			};
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+			startLength--;
+		}
+
+		if ( fn ) {
+
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift( "inprogress" );
+			}
+
+			// Clear up the last queue stop function
+			delete hooks.stop;
+			fn.call( elem, next, hooks );
+		}
+
+		if ( !startLength && hooks ) {
+			hooks.empty.fire();
+		}
+	},
+
+	// Not public - generate a queueHooks object, or return the current one
+	_queueHooks: function( elem, type ) {
+		var key = type + "queueHooks";
+		return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
+			empty: jQuery.Callbacks( "once memory" ).add( function() {
+				dataPriv.remove( elem, [ type + "queue", key ] );
+			} )
+		} );
+	}
+} );
+
+jQuery.fn.extend( {
+	queue: function( type, data ) {
+		var setter = 2;
+
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+			setter--;
+		}
+
+		if ( arguments.length < setter ) {
+			return jQuery.queue( this[ 0 ], type );
+		}
+
+		return data === undefined ?
+			this :
+			this.each( function() {
+				var queue = jQuery.queue( this, type, data );
+
+				// Ensure a hooks for this queue
+				jQuery._queueHooks( this, type );
+
+				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
+					jQuery.dequeue( this, type );
+				}
+			} );
+	},
+	dequeue: function( type ) {
+		return this.each( function() {
+			jQuery.dequeue( this, type );
+		} );
+	},
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	},
+
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, obj ) {
+		var tmp,
+			count = 1,
+			defer = jQuery.Deferred(),
+			elements = this,
+			i = this.length,
+			resolve = function() {
+				if ( !( --count ) ) {
+					defer.resolveWith( elements, [ elements ] );
+				}
+			};
+
+		if ( typeof type !== "string" ) {
+			obj = type;
+			type = undefined;
+		}
+		type = type || "fx";
+
+		while ( i-- ) {
+			tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
+			if ( tmp && tmp.empty ) {
+				count++;
+				tmp.empty.add( resolve );
+			}
+		}
+		resolve();
+		return defer.promise( obj );
+	}
+} );
+var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
+
+var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
+
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var isHidden = function( elem, el ) {
+
+		// isHidden might be called from jQuery#filter function;
+		// in that case, element will be second argument
+		elem = el || elem;
+		return jQuery.css( elem, "display" ) === "none" ||
+			!jQuery.contains( elem.ownerDocument, elem );
+	};
+
+
+
+function adjustCSS( elem, prop, valueParts, tween ) {
+	var adjusted,
+		scale = 1,
+		maxIterations = 20,
+		currentValue = tween ?
+			function() { return tween.cur(); } :
+			function() { return jQuery.css( elem, prop, "" ); },
+		initial = currentValue(),
+		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+		// Starting value computation is required for potential unit mismatches
+		initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
+			rcssNum.exec( jQuery.css( elem, prop ) );
+
+	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
+
+		// Trust units reported by jQuery.css
+		unit = unit || initialInUnit[ 3 ];
+
+		// Make sure we update the tween properties later on
+		valueParts = valueParts || [];
+
+		// Iteratively approximate from a nonzero starting point
+		initialInUnit = +initial || 1;
+
+		do {
+
+			// If previous iteration zeroed out, double until we get *something*.
+			// Use string for doubling so we don't accidentally see scale as unchanged below
+			scale = scale || ".5";
+
+			// Adjust and apply
+			initialInUnit = initialInUnit / scale;
+			jQuery.style( elem, prop, initialInUnit + unit );
+
+		// Update scale, tolerating zero or NaN from tween.cur()
+		// Break the loop if scale is unchanged or perfect, or if we've just had enough.
+		} while (
+			scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
+		);
+	}
+
+	if ( valueParts ) {
+		initialInUnit = +initialInUnit || +initial || 0;
+
+		// Apply relative offset (+=/-=) if specified
+		adjusted = valueParts[ 1 ] ?
+			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
+			+valueParts[ 2 ];
+		if ( tween ) {
+			tween.unit = unit;
+			tween.start = initialInUnit;
+			tween.end = adjusted;
+		}
+	}
+	return adjusted;
+}
+var rcheckableType = ( /^(?:checkbox|radio)$/i );
+
+var rtagName = ( /<([\w:-]+)/ );
+
+var rscriptType = ( /^$|\/(?:java|ecma)script/i );
+
+
+
+// We have to close these tags to support XHTML (#13200)
+var wrapMap = {
+
+	// Support: IE9
+	option: [ 1, "<select multiple='multiple'>", "</select>" ],
+
+	// XHTML parsers do not magically insert elements in the
+	// same way that tag soup parsers do. So we cannot shorten
+	// this by omitting <tbody> or other required elements.
+	thead: [ 1, "<table>", "</table>" ],
+	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+	_default: [ 0, "", "" ]
+};
+
+// Support: IE9
+wrapMap.optgroup = wrapMap.option;
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+
+function getAll( context, tag ) {
+
+	// Support: IE9-11+
+	// Use typeof to avoid zero-argument method invocation on host objects (#15151)
+	var ret = typeof context.getElementsByTagName !== "undefined" ?
+			context.getElementsByTagName( tag || "*" ) :
+			typeof context.querySelectorAll !== "undefined" ?
+				context.querySelectorAll( tag || "*" ) :
+			[];
+
+	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+		jQuery.merge( [ context ], ret ) :
+		ret;
+}
+
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+	var i = 0,
+		l = elems.length;
+
+	for ( ; i < l; i++ ) {
+		dataPriv.set(
+			elems[ i ],
+			"globalEval",
+			!refElements || dataPriv.get( refElements[ i ], "globalEval" )
+		);
+	}
+}
+
+
+var rhtml = /<|&#?\w+;/;
+
+function buildFragment( elems, context, scripts, selection, ignored ) {
+	var elem, tmp, tag, wrap, contains, j,
+		fragment = context.createDocumentFragment(),
+		nodes = [],
+		i = 0,
+		l = elems.length;
+
+	for ( ; i < l; i++ ) {
+		elem = elems[ i ];
+
+		if ( elem || elem === 0 ) {
+
+			// Add nodes directly
+			if ( jQuery.type( elem ) === "object" ) {
+
+				// Support: Android<4.1, PhantomJS<2
+				// push.apply(_, arraylike) throws on ancient WebKit
+				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+			// Convert non-html into a text node
+			} else if ( !rhtml.test( elem ) ) {
+				nodes.push( context.createTextNode( elem ) );
+
+			// Convert html into DOM nodes
+			} else {
+				tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
+
+				// Deserialize a standard representation
+				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+				wrap = wrapMap[ tag ] || wrapMap._default;
+				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
+
+				// Descend through wrappers to the right content
+				j = wrap[ 0 ];
+				while ( j-- ) {
+					tmp = tmp.lastChild;
+				}
+
+				// Support: Android<4.1, PhantomJS<2
+				// push.apply(_, arraylike) throws on ancient WebKit
+				jQuery.merge( nodes, tmp.childNodes );
+
+				// Remember the top-level container
+				tmp = fragment.firstChild;
+
+				// Ensure the created nodes are orphaned (#12392)
+				tmp.textContent = "";
+			}
+		}
+	}
+
+	// Remove wrapper from fragment
+	fragment.textContent = "";
+
+	i = 0;
+	while ( ( elem = nodes[ i++ ] ) ) {
+
+		// Skip elements already in the context collection (trac-4087)
+		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
+			if ( ignored ) {
+				ignored.push( elem );
+			}
+			continue;
+		}
+
+		contains = jQuery.contains( elem.ownerDocument, elem );
+
+		// Append to fragment
+		tmp = getAll( fragment.appendChild( elem ), "script" );
+
+		// Preserve script evaluation history
+		if ( contains ) {
+			setGlobalEval( tmp );
+		}
+
+		// Capture executables
+		if ( scripts ) {
+			j = 0;
+			while ( ( elem = tmp[ j++ ] ) ) {
+				if ( rscriptType.test( elem.type || "" ) ) {
+					scripts.push( elem );
+				}
+			}
+		}
+	}
+
+	return fragment;
+}
+
+
+( function() {
+	var fragment = document.createDocumentFragment(),
+		div = fragment.appendChild( document.createElement( "div" ) ),
+		input = document.createElement( "input" );
+
+	// Support: Android 4.0-4.3, Safari<=5.1
+	// Check state lost if the name is set (#11217)
+	// Support: Windows Web Apps (WWA)
+	// `name` and `type` must use .setAttribute for WWA (#14901)
+	input.setAttribute( "type", "radio" );
+	input.setAttribute( "checked", "checked" );
+	input.setAttribute( "name", "t" );
+
+	div.appendChild( input );
+
+	// Support: Safari<=5.1, Android<4.2
+	// Older WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	// Support: IE<=11+
+	// Make sure textarea (and checkbox) defaultValue is properly cloned
+	div.innerHTML = "<textarea>x</textarea>";
+	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+} )();
+
+
+var
+	rkeyEvent = /^key/,
+	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
+	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
+
+function returnTrue() {
+	return true;
+}
+
+function returnFalse() {
+	return false;
+}
+
+// Support: IE9
+// See #13393 for more info
+function safeActiveElement() {
+	try {
+		return document.activeElement;
+	} catch ( err ) { }
+}
+
+function on( elem, types, selector, data, fn, one ) {
+	var origFn, type;
+
+	// Types can be a map of types/handlers
+	if ( typeof types === "object" ) {
+
+		// ( types-Object, selector, data )
+		if ( typeof selector !== "string" ) {
+
+			// ( types-Object, data )
+			data = data || selector;
+			selector = undefined;
+		}
+		for ( type in types ) {
+			on( elem, type, selector, data, types[ type ], one );
+		}
+		return elem;
+	}
+
+	if ( data == null && fn == null ) {
+
+		// ( types, fn )
+		fn = selector;
+		data = selector = undefined;
+	} else if ( fn == null ) {
+		if ( typeof selector === "string" ) {
+
+			// ( types, selector, fn )
+			fn = data;
+			data = undefined;
+		} else {
+
+			// ( types, data, fn )
+			fn = data;
+			data = selector;
+			selector = undefined;
+		}
+	}
+	if ( fn === false ) {
+		fn = returnFalse;
+	} else if ( !fn ) {
+		return elem;
+	}
+
+	if ( one === 1 ) {
+		origFn = fn;
+		fn = function( event ) {
+
+			// Can use an empty set, since event contains the info
+			jQuery().off( event );
+			return origFn.apply( this, arguments );
+		};
+
+		// Use same guid so caller can remove using origFn
+		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+	}
+	return elem.each( function() {
+		jQuery.event.add( this, types, fn, data, selector );
+	} );
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+	global: {},
+
+	add: function( elem, types, handler, data, selector ) {
+
+		var handleObjIn, eventHandle, tmp,
+			events, t, handleObj,
+			special, handlers, type, namespaces, origType,
+			elemData = dataPriv.get( elem );
+
+		// Don't attach events to noData or text/comment nodes (but allow plain objects)
+		if ( !elemData ) {
+			return;
+		}
+
+		// Caller can pass in an object of custom data in lieu of the handler
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+			selector = handleObjIn.selector;
+		}
+
+		// Make sure that the handler has a unique ID, used to find/remove it later
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure and main handler, if this is the first
+		if ( !( events = elemData.events ) ) {
+			events = elemData.events = {};
+		}
+		if ( !( eventHandle = elemData.handle ) ) {
+			eventHandle = elemData.handle = function( e ) {
+
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
+					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+			};
+		}
+
+		// Handle multiple events separated by a space
+		types = ( types || "" ).match( rnotwhite ) || [ "" ];
+		t = types.length;
+		while ( t-- ) {
+			tmp = rtypenamespace.exec( types[ t ] ) || [];
+			type = origType = tmp[ 1 ];
+			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+			// There *must* be a type, no attaching namespace-only handlers
+			if ( !type ) {
+				continue;
+			}
+
+			// If event changes its type, use the special event handlers for the changed type
+			special = jQuery.event.special[ type ] || {};
+
+			// If selector defined, determine special event api type, otherwise given type
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+
+			// Update special based on newly reset type
+			special = jQuery.event.special[ type ] || {};
+
+			// handleObj is passed to all event handlers
+			handleObj = jQuery.extend( {
+				type: type,
+				origType: origType,
+				data: data,
+				handler: handler,
+				guid: handler.guid,
+				selector: selector,
+				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+				namespace: namespaces.join( "." )
+			}, handleObjIn );
+
+			// Init the event handler queue if we're the first
+			if ( !( handlers = events[ type ] ) ) {
+				handlers = events[ type ] = [];
+				handlers.delegateCount = 0;
+
+				// Only use addEventListener if the special events handler returns false
+				if ( !special.setup ||
+					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add to the element's handler list, delegates in front
+			if ( selector ) {
+				handlers.splice( handlers.delegateCount++, 0, handleObj );
+			} else {
+				handlers.push( handleObj );
+			}
+
+			// Keep track of which events have ever been used, for event optimization
+			jQuery.event.global[ type ] = true;
+		}
+
+	},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, selector, mappedTypes ) {
+
+		var j, origCount, tmp,
+			events, t, handleObj,
+			special, handlers, type, namespaces, origType,
+			elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
+
+		if ( !elemData || !( events = elemData.events ) ) {
+			return;
+		}
+
+		// Once for each type.namespace in types; type may be omitted
+		types = ( types || "" ).match( rnotwhite ) || [ "" ];
+		t = types.length;
+		while ( t-- ) {
+			tmp = rtypenamespace.exec( types[ t ] ) || [];
+			type = origType = tmp[ 1 ];
+			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
+
+			// Unbind all events (on this namespace, if provided) for the element
+			if ( !type ) {
+				for ( type in events ) {
+					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+				}
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+			handlers = events[ type ] || [];
+			tmp = tmp[ 2 ] &&
+				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
+
+			// Remove matching events
+			origCount = j = handlers.length;
+			while ( j-- ) {
+				handleObj = handlers[ j ];
+
+				if ( ( mappedTypes || origType === handleObj.origType ) &&
+					( !handler || handler.guid === handleObj.guid ) &&
+					( !tmp || tmp.test( handleObj.namespace ) ) &&
+					( !selector || selector === handleObj.selector ||
+						selector === "**" && handleObj.selector ) ) {
+					handlers.splice( j, 1 );
+
+					if ( handleObj.selector ) {
+						handlers.delegateCount--;
+					}
+					if ( special.remove ) {
+						special.remove.call( elem, handleObj );
+					}
+				}
+			}
+
+			// Remove generic event handler if we removed something and no more handlers exist
+			// (avoids potential for endless recursion during removal of special event handlers)
+			if ( origCount && !handlers.length ) {
+				if ( !special.teardown ||
+					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				delete events[ type ];
+			}
+		}
+
+		// Remove data and the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			dataPriv.remove( elem, "handle events" );
+		}
+	},
+
+	dispatch: function( event ) {
+
+		// Make a writable jQuery.Event from the native event object
+		event = jQuery.event.fix( event );
+
+		var i, j, ret, matched, handleObj,
+			handlerQueue = [],
+			args = slice.call( arguments ),
+			handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
+			special = jQuery.event.special[ event.type ] || {};
+
+		// Use the fix-ed jQuery.Event rather than the (read-only) native event
+		args[ 0 ] = event;
+		event.delegateTarget = this;
+
+		// Call the preDispatch hook for the mapped type, and let it bail if desired
+		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+			return;
+		}
+
+		// Determine handlers
+		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+		// Run delegates first; they may want to stop propagation beneath us
+		i = 0;
+		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
+			event.currentTarget = matched.elem;
+
+			j = 0;
+			while ( ( handleObj = matched.handlers[ j++ ] ) &&
+				!event.isImmediatePropagationStopped() ) {
+
+				// Triggered event must either 1) have no namespace, or 2) have namespace(s)
+				// a subset or equal to those in the bound event (both can have no namespace).
+				if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {
+
+					event.handleObj = handleObj;
+					event.data = handleObj.data;
+
+					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
+						handleObj.handler ).apply( matched.elem, args );
+
+					if ( ret !== undefined ) {
+						if ( ( event.result = ret ) === false ) {
+							event.preventDefault();
+							event.stopPropagation();
+						}
+					}
+				}
+			}
+		}
+
+		// Call the postDispatch hook for the mapped type
+		if ( special.postDispatch ) {
+			special.postDispatch.call( this, event );
+		}
+
+		return event.result;
+	},
+
+	handlers: function( event, handlers ) {
+		var i, matches, sel, handleObj,
+			handlerQueue = [],
+			delegateCount = handlers.delegateCount,
+			cur = event.target;
+
+		// Support (at least): Chrome, IE9
+		// Find delegate handlers
+		// Black-hole SVG <use> instance trees (#13180)
+		//
+		// Support: Firefox<=42+
+		// Avoid non-left-click in FF but don't block IE radio events (#3861, gh-2343)
+		if ( delegateCount && cur.nodeType &&
+			( event.type !== "click" || isNaN( event.button ) || event.button < 1 ) ) {
+
+			for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+				// Don't check non-elements (#13208)
+				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+				if ( cur.nodeType === 1 && ( cur.disabled !== true || event.type !== "click" ) ) {
+					matches = [];
+					for ( i = 0; i < delegateCount; i++ ) {
+						handleObj = handlers[ i ];
+
+						// Don't conflict with Object.prototype properties (#13203)
+						sel = handleObj.selector + " ";
+
+						if ( matches[ sel ] === undefined ) {
+							matches[ sel ] = handleObj.needsContext ?
+								jQuery( sel, this ).index( cur ) > -1 :
+								jQuery.find( sel, this, null, [ cur ] ).length;
+						}
+						if ( matches[ sel ] ) {
+							matches.push( handleObj );
+						}
+					}
+					if ( matches.length ) {
+						handlerQueue.push( { elem: cur, handlers: matches } );
+					}
+				}
+			}
+		}
+
+		// Add the remaining (directly-bound) handlers
+		if ( delegateCount < handlers.length ) {
+			handlerQueue.push( { elem: this, handlers: handlers.slice( delegateCount ) } );
+		}
+
+		return handlerQueue;
+	},
+
+	// Includes some event props shared by KeyEvent and MouseEvent
+	props: ( "altKey bubbles cancelable ctrlKey currentTarget detail eventPhase " +
+		"metaKey relatedTarget shiftKey target timeStamp view which" ).split( " " ),
+
+	fixHooks: {},
+
+	keyHooks: {
+		props: "char charCode key keyCode".split( " " ),
+		filter: function( event, original ) {
+
+			// Add which for key events
+			if ( event.which == null ) {
+				event.which = original.charCode != null ? original.charCode : original.keyCode;
+			}
+
+			return event;
+		}
+	},
+
+	mouseHooks: {
+		props: ( "button buttons clientX clientY offsetX offsetY pageX pageY " +
+			"screenX screenY toElement" ).split( " " ),
+		filter: function( event, original ) {
+			var eventDoc, doc, body,
+				button = original.button;
+
+			// Calculate pageX/Y if missing and clientX/Y available
+			if ( event.pageX == null && original.clientX != null ) {
+				eventDoc = event.target.ownerDocument || document;
+				doc = eventDoc.documentElement;
+				body = eventDoc.body;
+
+				event.pageX = original.clientX +
+					( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
+					( doc && doc.clientLeft || body && body.clientLeft || 0 );
+				event.pageY = original.clientY +
+					( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) -
+					( doc && doc.clientTop  || body && body.clientTop  || 0 );
+			}
+
+			// Add which for click: 1 === left; 2 === middle; 3 === right
+			// Note: button is not normalized, so don't use it
+			if ( !event.which && button !== undefined ) {
+				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+			}
+
+			return event;
+		}
+	},
+
+	fix: function( event ) {
+		if ( event[ jQuery.expando ] ) {
+			return event;
+		}
+
+		// Create a writable copy of the event object and normalize some properties
+		var i, prop, copy,
+			type = event.type,
+			originalEvent = event,
+			fixHook = this.fixHooks[ type ];
+
+		if ( !fixHook ) {
+			this.fixHooks[ type ] = fixHook =
+				rmouseEvent.test( type ) ? this.mouseHooks :
+				rkeyEvent.test( type ) ? this.keyHooks :
+				{};
+		}
+		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+		event = new jQuery.Event( originalEvent );
+
+		i = copy.length;
+		while ( i-- ) {
+			prop = copy[ i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Support: Cordova 2.5 (WebKit) (#13255)
+		// All events should have a target; Cordova deviceready doesn't
+		if ( !event.target ) {
+			event.target = document;
+		}
+
+		// Support: Safari 6.0+, Chrome<28
+		// Target should not be a text node (#504, #13143)
+		if ( event.target.nodeType === 3 ) {
+			event.target = event.target.parentNode;
+		}
+
+		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
+	},
+
+	special: {
+		load: {
+
+			// Prevent triggered image.load events from bubbling to window.load
+			noBubble: true
+		},
+		focus: {
+
+			// Fire native event if possible so blur/focus sequence is correct
+			trigger: function() {
+				if ( this !== safeActiveElement() && this.focus ) {
+					this.focus();
+					return false;
+				}
+			},
+			delegateType: "focusin"
+		},
+		blur: {
+			trigger: function() {
+				if ( this === safeActiveElement() && this.blur ) {
+					this.blur();
+					return false;
+				}
+			},
+			delegateType: "focusout"
+		},
+		click: {
+
+			// For checkbox, fire native event so checked state will be right
+			trigger: function() {
+				if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+					this.click();
+					return false;
+				}
+			},
+
+			// For cross-browser consistency, don't fire native .click() on links
+			_default: function( event ) {
+				return jQuery.nodeName( event.target, "a" );
+			}
+		},
+
+		beforeunload: {
+			postDispatch: function( event ) {
+
+				// Support: Firefox 20+
+				// Firefox doesn't alert if the returnValue field is not set.
+				if ( event.result !== undefined && event.originalEvent ) {
+					event.originalEvent.returnValue = event.result;
+				}
+			}
+		}
+	}
+};
+
+jQuery.removeEvent = function( elem, type, handle ) {
+
+	// This "if" is needed for plain objects
+	if ( elem.removeEventListener ) {
+		elem.removeEventListener( type, handle );
+	}
+};
+
+jQuery.Event = function( src, props ) {
+
+	// Allow instantiation without the 'new' keyword
+	if ( !( this instanceof jQuery.Event ) ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = src.defaultPrevented ||
+				src.defaultPrevented === undefined &&
+
+				// Support: Android<4.0
+				src.returnValue === false ?
+			returnTrue :
+			returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// Create a timestamp if incoming event doesn't have one
+	this.timeStamp = src && src.timeStamp || jQuery.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	constructor: jQuery.Event,
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse,
+
+	preventDefault: function() {
+		var e = this.originalEvent;
+
+		this.isDefaultPrevented = returnTrue;
+
+		if ( e ) {
+			e.preventDefault();
+		}
+	},
+	stopPropagation: function() {
+		var e = this.originalEvent;
+
+		this.isPropagationStopped = returnTrue;
+
+		if ( e ) {
+			e.stopPropagation();
+		}
+	},
+	stopImmediatePropagation: function() {
+		var e = this.originalEvent;
+
+		this.isImmediatePropagationStopped = returnTrue;
+
+		if ( e ) {
+			e.stopImmediatePropagation();
+		}
+
+		this.stopPropagation();
+	}
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// so that event delegation works in jQuery.
+// Do the same for pointerenter/pointerleave and pointerover/pointerout
+//
+// Support: Safari 7 only
+// Safari sends mouseenter too often; see:
+// https://code.google.com/p/chromium/issues/detail?id=470258
+// for the description of the bug (it existed in older Chrome versions as well).
+jQuery.each( {
+	mouseenter: "mouseover",
+	mouseleave: "mouseout",
+	pointerenter: "pointerover",
+	pointerleave: "pointerout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		delegateType: fix,
+		bindType: fix,
+
+		handle: function( event ) {
+			var ret,
+				target = this,
+				related = event.relatedTarget,
+				handleObj = event.handleObj;
+
+			// For mouseenter/leave call the handler if related is outside the target.
+			// NB: No relatedTarget if the mouse left/entered the browser window
+			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
+				event.type = handleObj.origType;
+				ret = handleObj.handler.apply( this, arguments );
+				event.type = fix;
+			}
+			return ret;
+		}
+	};
+} );
+
+jQuery.fn.extend( {
+	on: function( types, selector, data, fn ) {
+		return on( this, types, selector, data, fn );
+	},
+	one: function( types, selector, data, fn ) {
+		return on( this, types, selector, data, fn, 1 );
+	},
+	off: function( types, selector, fn ) {
+		var handleObj, type;
+		if ( types && types.preventDefault && types.handleObj ) {
+
+			// ( event )  dispatched jQuery.Event
+			handleObj = types.handleObj;
+			jQuery( types.delegateTarget ).off(
+				handleObj.namespace ?
+					handleObj.origType + "." + handleObj.namespace :
+					handleObj.origType,
+				handleObj.selector,
+				handleObj.handler
+			);
+			return this;
+		}
+		if ( typeof types === "object" ) {
+
+			// ( types-object [, selector] )
+			for ( type in types ) {
+				this.off( type, selector, types[ type ] );
+			}
+			return this;
+		}
+		if ( selector === false || typeof selector === "function" ) {
+
+			// ( types [, fn] )
+			fn = selector;
+			selector = undefined;
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		}
+		return this.each( function() {
+			jQuery.event.remove( this, types, fn, selector );
+		} );
+	}
+} );
+
+
+var
+	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
+
+	// Support: IE 10-11, Edge 10240+
+	// In IE/Edge using regex groups here causes severe slowdowns.
+	// See https://connect.microsoft.com/IE/feedback/details/1736512/
+	rnoInnerhtml = /<script|<style|<link/i,
+
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptTypeMasked = /^true\/(.*)/,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
+
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+	return jQuery.nodeName( elem, "table" ) &&
+		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
+
+		elem.getElementsByTagName( "tbody" )[ 0 ] ||
+			elem.appendChild( elem.ownerDocument.createElement( "tbody" ) ) :
+		elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+	elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
+	return elem;
+}
+function restoreScript( elem ) {
+	var match = rscriptTypeMasked.exec( elem.type );
+
+	if ( match ) {
+		elem.type = match[ 1 ];
+	} else {
+		elem.removeAttribute( "type" );
+	}
+
+	return elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+	var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
+
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// 1. Copy private data: events, handlers, etc.
+	if ( dataPriv.hasData( src ) ) {
+		pdataOld = dataPriv.access( src );
+		pdataCur = dataPriv.set( dest, pdataOld );
+		events = pdataOld.events;
+
+		if ( events ) {
+			delete pdataCur.handle;
+			pdataCur.events = {};
+
+			for ( type in events ) {
+				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+					jQuery.event.add( dest, type, events[ type ][ i ] );
+				}
+			}
+		}
+	}
+
+	// 2. Copy user data
+	if ( dataUser.hasData( src ) ) {
+		udataOld = dataUser.access( src );
+		udataCur = jQuery.extend( {}, udataOld );
+
+		dataUser.set( dest, udataCur );
+	}
+}
+
+// Fix IE bugs, see support tests
+function fixInput( src, dest ) {
+	var nodeName = dest.nodeName.toLowerCase();
+
+	// Fails to persist the checked state of a cloned checkbox or radio button.
+	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+		dest.checked = src.checked;
+
+	// Fails to return the selected option to the default selected state when cloning options
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+}
+
+function domManip( collection, args, callback, ignored ) {
+
+	// Flatten any nested arrays
+	args = concat.apply( [], args );
+
+	var fragment, first, scripts, hasScripts, node, doc,
+		i = 0,
+		l = collection.length,
+		iNoClone = l - 1,
+		value = args[ 0 ],
+		isFunction = jQuery.isFunction( value );
+
+	// We can't cloneNode fragments that contain checked, in WebKit
+	if ( isFunction ||
+			( l > 1 && typeof value === "string" &&
+				!support.checkClone && rchecked.test( value ) ) ) {
+		return collection.each( function( index ) {
+			var self = collection.eq( index );
+			if ( isFunction ) {
+				args[ 0 ] = value.call( this, index, self.html() );
+			}
+			domManip( self, args, callback, ignored );
+		} );
+	}
+
+	if ( l ) {
+		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
+		first = fragment.firstChild;
+
+		if ( fragment.childNodes.length === 1 ) {
+			fragment = first;
+		}
+
+		// Require either new content or an interest in ignored elements to invoke the callback
+		if ( first || ignored ) {
+			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+			hasScripts = scripts.length;
+
+			// Use the original fragment for the last item
+			// instead of the first because it can end up
+			// being emptied incorrectly in certain situations (#8070).
+			for ( ; i < l; i++ ) {
+				node = fragment;
+
+				if ( i !== iNoClone ) {
+					node = jQuery.clone( node, true, true );
+
+					// Keep references to cloned scripts for later restoration
+					if ( hasScripts ) {
+
+						// Support: Android<4.1, PhantomJS<2
+						// push.apply(_, arraylike) throws on ancient WebKit
+						jQuery.merge( scripts, getAll( node, "script" ) );
+					}
+				}
+
+				callback.call( collection[ i ], node, i );
+			}
+
+			if ( hasScripts ) {
+				doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+				// Reenable scripts
+				jQuery.map( scripts, restoreScript );
+
+				// Evaluate executable scripts on first document insertion
+				for ( i = 0; i < hasScripts; i++ ) {
+					node = scripts[ i ];
+					if ( rscriptType.test( node.type || "" ) &&
+						!dataPriv.access( node, "globalEval" ) &&
+						jQuery.contains( doc, node ) ) {
+
+						if ( node.src ) {
+
+							// Optional AJAX dependency, but won't run scripts if not present
+							if ( jQuery._evalUrl ) {
+								jQuery._evalUrl( node.src );
+							}
+						} else {
+							jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return collection;
+}
+
+function remove( elem, selector, keepData ) {
+	var node,
+		nodes = selector ? jQuery.filter( selector, elem ) : elem,
+		i = 0;
+
+	for ( ; ( node = nodes[ i ] ) != null; i++ ) {
+		if ( !keepData && node.nodeType === 1 ) {
+			jQuery.cleanData( getAll( node ) );
+		}
+
+		if ( node.parentNode ) {
+			if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
+				setGlobalEval( getAll( node, "script" ) );
+			}
+			node.parentNode.removeChild( node );
+		}
+	}
+
+	return elem;
+}
+
+jQuery.extend( {
+	htmlPrefilter: function( html ) {
+		return html.replace( rxhtmlTag, "<$1></$2>" );
+	},
+
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var i, l, srcElements, destElements,
+			clone = elem.cloneNode( true ),
+			inPage = jQuery.contains( elem.ownerDocument, elem );
+
+		// Fix IE cloning issues
+		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+				!jQuery.isXMLDoc( elem ) ) {
+
+			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+			destElements = getAll( clone );
+			srcElements = getAll( elem );
+
+			for ( i = 0, l = srcElements.length; i < l; i++ ) {
+				fixInput( srcElements[ i ], destElements[ i ] );
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			if ( deepDataAndEvents ) {
+				srcElements = srcElements || getAll( elem );
+				destElements = destElements || getAll( clone );
+
+				for ( i = 0, l = srcElements.length; i < l; i++ ) {
+					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+				}
+			} else {
+				cloneCopyEvent( elem, clone );
+			}
+		}
+
+		// Preserve script evaluation history
+		destElements = getAll( clone, "script" );
+		if ( destElements.length > 0 ) {
+			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+		}
+
+		// Return the cloned set
+		return clone;
+	},
+
+	cleanData: function( elems ) {
+		var data, elem, type,
+			special = jQuery.event.special,
+			i = 0;
+
+		for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
+			if ( acceptData( elem ) ) {
+				if ( ( data = elem[ dataPriv.expando ] ) ) {
+					if ( data.events ) {
+						for ( type in data.events ) {
+							if ( special[ type ] ) {
+								jQuery.event.remove( elem, type );
+
+							// This is a shortcut to avoid jQuery.event.remove's overhead
+							} else {
+								jQuery.removeEvent( elem, type, data.handle );
+							}
+						}
+					}
+
+					// Support: Chrome <= 35-45+
+					// Assign undefined instead of using delete, see Data#remove
+					elem[ dataPriv.expando ] = undefined;
+				}
+				if ( elem[ dataUser.expando ] ) {
+
+					// Support: Chrome <= 35-45+
+					// Assign undefined instead of using delete, see Data#remove
+					elem[ dataUser.expando ] = undefined;
+				}
+			}
+		}
+	}
+} );
+
+jQuery.fn.extend( {
+
+	// Keep domManip exposed until 3.0 (gh-2225)
+	domManip: domManip,
+
+	detach: function( selector ) {
+		return remove( this, selector, true );
+	},
+
+	remove: function( selector ) {
+		return remove( this, selector );
+	},
+
+	text: function( value ) {
+		return access( this, function( value ) {
+			return value === undefined ?
+				jQuery.text( this ) :
+				this.empty().each( function() {
+					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+						this.textContent = value;
+					}
+				} );
+		}, null, value, arguments.length );
+	},
+
+	append: function() {
+		return domManip( this, arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.appendChild( elem );
+			}
+		} );
+	},
+
+	prepend: function() {
+		return domManip( this, arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.insertBefore( elem, target.firstChild );
+			}
+		} );
+	},
+
+	before: function() {
+		return domManip( this, arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this );
+			}
+		} );
+	},
+
+	after: function() {
+		return domManip( this, arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			}
+		} );
+	},
+
+	empty: function() {
+		var elem,
+			i = 0;
+
+		for ( ; ( elem = this[ i ] ) != null; i++ ) {
+			if ( elem.nodeType === 1 ) {
+
+				// Prevent memory leaks
+				jQuery.cleanData( getAll( elem, false ) );
+
+				// Remove any remaining nodes
+				elem.textContent = "";
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function() {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		} );
+	},
+
+	html: function( value ) {
+		return access( this, function( value ) {
+			var elem = this[ 0 ] || {},
+				i = 0,
+				l = this.length;
+
+			if ( value === undefined && elem.nodeType === 1 ) {
+				return elem.innerHTML;
+			}
+
+			// See if we can take a shortcut and just use innerHTML
+			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+				value = jQuery.htmlPrefilter( value );
+
+				try {
+					for ( ; i < l; i++ ) {
+						elem = this[ i ] || {};
+
+						// Remove element nodes and prevent memory leaks
+						if ( elem.nodeType === 1 ) {
+							jQuery.cleanData( getAll( elem, false ) );
+							elem.innerHTML = value;
+						}
+					}
+
+					elem = 0;
+
+				// If using innerHTML throws an exception, use the fallback method
+				} catch ( e ) {}
+			}
+
+			if ( elem ) {
+				this.empty().append( value );
+			}
+		}, null, value, arguments.length );
+	},
+
+	replaceWith: function() {
+		var ignored = [];
+
+		// Make the changes, replacing each non-ignored context element with the new content
+		return domManip( this, arguments, function( elem ) {
+			var parent = this.parentNode;
+
+			if ( jQuery.inArray( this, ignored ) < 0 ) {
+				jQuery.cleanData( getAll( this ) );
+				if ( parent ) {
+					parent.replaceChild( elem, this );
+				}
+			}
+
+		// Force callback invocation
+		}, ignored );
+	}
+} );
+
+jQuery.each( {
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var elems,
+			ret = [],
+			insert = jQuery( selector ),
+			last = insert.length - 1,
+			i = 0;
+
+		for ( ; i <= last; i++ ) {
+			elems = i === last ? this : this.clone( true );
+			jQuery( insert[ i ] )[ original ]( elems );
+
+			// Support: QtWebKit
+			// .get() because push.apply(_, arraylike) throws
+			push.apply( ret, elems.get() );
+		}
+
+		return this.pushStack( ret );
+	};
+} );
+
+
+var iframe,
+	elemdisplay = {
+
+		// Support: Firefox
+		// We have to pre-define these values for FF (#10227)
+		HTML: "block",
+		BODY: "block"
+	};
+
+/**
+ * Retrieve the actual display of a element
+ * @param {String} name nodeName of the element
+ * @param {Object} doc Document object
+ */
+
+// Called only from within defaultDisplay
+function actualDisplay( name, doc ) {
+	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+
+		display = jQuery.css( elem[ 0 ], "display" );
+
+	// We don't have any data stored on the element,
+	// so use "detach" method as fast way to get rid of the element
+	elem.detach();
+
+	return display;
+}
+
+/**
+ * Try to determine the default display value of an element
+ * @param {String} nodeName
+ */
+function defaultDisplay( nodeName ) {
+	var doc = document,
+		display = elemdisplay[ nodeName ];
+
+	if ( !display ) {
+		display = actualDisplay( nodeName, doc );
+
+		// If the simple way fails, read from inside an iframe
+		if ( display === "none" || !display ) {
+
+			// Use the already-created iframe if possible
+			iframe = ( iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" ) )
+				.appendTo( doc.documentElement );
+
+			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+			doc = iframe[ 0 ].contentDocument;
+
+			// Support: IE
+			doc.write();
+			doc.close();
+
+			display = actualDisplay( nodeName, doc );
+			iframe.detach();
+		}
+
+		// Store the correct default display
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return display;
+}
+var rmargin = ( /^margin/ );
+
+var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+var getStyles = function( elem ) {
+
+		// Support: IE<=11+, Firefox<=30+ (#15098, #14150)
+		// IE throws on elements created in popups
+		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+		var view = elem.ownerDocument.defaultView;
+
+		if ( !view || !view.opener ) {
+			view = window;
+		}
+
+		return view.getComputedStyle( elem );
+	};
+
+var swap = function( elem, options, callback, args ) {
+	var ret, name,
+		old = {};
+
+	// Remember the old values, and insert the new ones
+	for ( name in options ) {
+		old[ name ] = elem.style[ name ];
+		elem.style[ name ] = options[ name ];
+	}
+
+	ret = callback.apply( elem, args || [] );
+
+	// Revert the old values
+	for ( name in options ) {
+		elem.style[ name ] = old[ name ];
+	}
+
+	return ret;
+};
+
+
+var documentElement = document.documentElement;
+
+
+
+( function() {
+	var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal,
+		container = document.createElement( "div" ),
+		div = document.createElement( "div" );
+
+	// Finish early in limited (non-browser) environments
+	if ( !div.style ) {
+		return;
+	}
+
+	// Support: IE9-11+
+	// Style of cloned element affects source element cloned (#8908)
+	div.style.backgroundClip = "content-box";
+	div.cloneNode( true ).style.backgroundClip = "";
+	support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+	container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" +
+		"padding:0;margin-top:1px;position:absolute";
+	container.appendChild( div );
+
+	// Executing both pixelPosition & boxSizingReliable tests require only one layout
+	// so they're executed at the same time to save the second computation.
+	function computeStyleTests() {
+		div.style.cssText =
+
+			// Support: Firefox<29, Android 2.3
+			// Vendor-prefix box-sizing
+			"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;" +
+			"position:relative;display:block;" +
+			"margin:auto;border:1px;padding:1px;" +
+			"top:1%;width:50%";
+		div.innerHTML = "";
+		documentElement.appendChild( container );
+
+		var divStyle = window.getComputedStyle( div );
+		pixelPositionVal = divStyle.top !== "1%";
+		reliableMarginLeftVal = divStyle.marginLeft === "2px";
+		boxSizingReliableVal = divStyle.width === "4px";
+
+		// Support: Android 4.0 - 4.3 only
+		// Some styles come back with percentage values, even though they shouldn't
+		div.style.marginRight = "50%";
+		pixelMarginRightVal = divStyle.marginRight === "4px";
+
+		documentElement.removeChild( container );
+	}
+
+	jQuery.extend( support, {
+		pixelPosition: function() {
+
+			// This test is executed only once but we still do memoizing
+			// since we can use the boxSizingReliable pre-computing.
+			// No need to check if the test was already performed, though.
+			computeStyleTests();
+			return pixelPositionVal;
+		},
+		boxSizingReliable: function() {
+			if ( boxSizingReliableVal == null ) {
+				computeStyleTests();
+			}
+			return boxSizingReliableVal;
+		},
+		pixelMarginRight: function() {
+
+			// Support: Android 4.0-4.3
+			// We're checking for boxSizingReliableVal here instead of pixelMarginRightVal
+			// since that compresses better and they're computed together anyway.
+			if ( boxSizingReliableVal == null ) {
+				computeStyleTests();
+			}
+			return pixelMarginRightVal;
+		},
+		reliableMarginLeft: function() {
+
+			// Support: IE <=8 only, Android 4.0 - 4.3 only, Firefox <=3 - 37
+			if ( boxSizingReliableVal == null ) {
+				computeStyleTests();
+			}
+			return reliableMarginLeftVal;
+		},
+		reliableMarginRight: function() {
+
+			// Support: Android 2.3
+			// Check if div with explicit width and no margin-right incorrectly
+			// gets computed margin-right based on width of container. (#3333)
+			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+			// This support function is only executed once so no memoizing is needed.
+			var ret,
+				marginDiv = div.appendChild( document.createElement( "div" ) );
+
+			// Reset CSS: box-sizing; display; margin; border; padding
+			marginDiv.style.cssText = div.style.cssText =
+
+				// Support: Android 2.3
+				// Vendor-prefix box-sizing
+				"-webkit-box-sizing:content-box;box-sizing:content-box;" +
+				"display:block;margin:0;border:0;padding:0";
+			marginDiv.style.marginRight = marginDiv.style.width = "0";
+			div.style.width = "1px";
+			documentElement.appendChild( container );
+
+			ret = !parseFloat( window.getComputedStyle( marginDiv ).marginRight );
+
+			documentElement.removeChild( container );
+			div.removeChild( marginDiv );
+
+			return ret;
+		}
+	} );
+} )();
+
+
+function curCSS( elem, name, computed ) {
+	var width, minWidth, maxWidth, ret,
+		style = elem.style;
+
+	computed = computed || getStyles( elem );
+	ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined;
+
+	// Support: Opera 12.1x only
+	// Fall back to style even without computed
+	// computed is undefined for elems on document fragments
+	if ( ( ret === "" || ret === undefined ) && !jQuery.contains( elem.ownerDocument, elem ) ) {
+		ret = jQuery.style( elem, name );
+	}
+
+	// Support: IE9
+	// getPropertyValue is only needed for .css('filter') (#12537)
+	if ( computed ) {
+
+		// A tribute to the "awesome hack by Dean Edwards"
+		// Android Browser returns percentage for some values,
+		// but width seems to be reliably pixels.
+		// This is against the CSSOM draft spec:
+		// http://dev.w3.org/csswg/cssom/#resolved-values
+		if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+			// Remember the original values
+			width = style.width;
+			minWidth = style.minWidth;
+			maxWidth = style.maxWidth;
+
+			// Put in the new values to get a computed value out
+			style.minWidth = style.maxWidth = style.width = ret;
+			ret = computed.width;
+
+			// Revert the changed values
+			style.width = width;
+			style.minWidth = minWidth;
+			style.maxWidth = maxWidth;
+		}
+	}
+
+	return ret !== undefined ?
+
+		// Support: IE9-11+
+		// IE returns zIndex value as an integer.
+		ret + "" :
+		ret;
+}
+
+
+function addGetHookIf( conditionFn, hookFn ) {
+
+	// Define the hook, we'll check on the first run if it's really needed.
+	return {
+		get: function() {
+			if ( conditionFn() ) {
+
+				// Hook not needed (or it's not possible to use it due
+				// to missing dependency), remove it.
+				delete this.get;
+				return;
+			}
+
+			// Hook needed; redefine it so that the support test is not executed again.
+			return ( this.get = hookFn ).apply( this, arguments );
+		}
+	};
+}
+
+
+var
+
+	// Swappable if display is none or starts with table
+	// except "table", "table-cell", or "table-caption"
+	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssNormalTransform = {
+		letterSpacing: "0",
+		fontWeight: "400"
+	},
+
+	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
+	emptyStyle = document.createElement( "div" ).style;
+
+// Return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( name ) {
+
+	// Shortcut for names that are not vendor prefixed
+	if ( name in emptyStyle ) {
+		return name;
+	}
+
+	// Check for vendor prefixed names
+	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
+		i = cssPrefixes.length;
+
+	while ( i-- ) {
+		name = cssPrefixes[ i ] + capName;
+		if ( name in emptyStyle ) {
+			return name;
+		}
+	}
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+
+	// Any relative (+/-) values have already been
+	// normalized at this point
+	var matches = rcssNum.exec( value );
+	return matches ?
+
+		// Guard against undefined "subtract", e.g., when used as in cssHooks
+		Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
+		value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+	var i = extra === ( isBorderBox ? "border" : "content" ) ?
+
+		// If we already have the right measurement, avoid augmentation
+		4 :
+
+		// Otherwise initialize for horizontal or vertical properties
+		name === "width" ? 1 : 0,
+
+		val = 0;
+
+	for ( ; i < 4; i += 2 ) {
+
+		// Both box models exclude margin, so add it if we want it
+		if ( extra === "margin" ) {
+			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+		}
+
+		if ( isBorderBox ) {
+
+			// border-box includes padding, so remove it if we want content
+			if ( extra === "content" ) {
+				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+			}
+
+			// At this point, extra isn't border nor margin, so remove border
+			if ( extra !== "margin" ) {
+				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		} else {
+
+			// At this point, extra isn't content, so add padding
+			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+			// At this point, extra isn't content nor padding, so add border
+			if ( extra !== "padding" ) {
+				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		}
+	}
+
+	return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+	// Start with offset property, which is equivalent to the border-box value
+	var valueIsBorderBox = true,
+		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		styles = getStyles( elem ),
+		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+	// Support: IE11 only
+	// In IE 11 fullscreen elements inside of an iframe have
+	// 100x too small dimensions (gh-1764).
+	if ( document.msFullscreenElement && window.top !== window ) {
+
+		// Support: IE11 only
+		// Running getBoundingClientRect on a disconnected node
+		// in IE throws an error.
+		if ( elem.getClientRects().length ) {
+			val = Math.round( elem.getBoundingClientRect()[ name ] * 100 );
+		}
+	}
+
+	// Some non-html elements return undefined for offsetWidth, so check for null/undefined
+	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+	if ( val <= 0 || val == null ) {
+
+		// Fall back to computed then uncomputed css if necessary
+		val = curCSS( elem, name, styles );
+		if ( val < 0 || val == null ) {
+			val = elem.style[ name ];
+		}
+
+		// Computed unit is not pixels. Stop here and return.
+		if ( rnumnonpx.test( val ) ) {
+			return val;
+		}
+
+		// Check for style in case a browser which returns unreliable values
+		// for getComputedStyle silently falls back to the reliable elem.style
+		valueIsBorderBox = isBorderBox &&
+			( support.boxSizingReliable() || val === elem.style[ name ] );
+
+		// Normalize "", auto, and prepare for extra
+		val = parseFloat( val ) || 0;
+	}
+
+	// Use the active box-sizing model to add/subtract irrelevant styles
+	return ( val +
+		augmentWidthOrHeight(
+			elem,
+			name,
+			extra || ( isBorderBox ? "border" : "content" ),
+			valueIsBorderBox,
+			styles
+		)
+	) + "px";
+}
+
+function showHide( elements, show ) {
+	var display, elem, hidden,
+		values = [],
+		index = 0,
+		length = elements.length;
+
+	for ( ; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+
+		values[ index ] = dataPriv.get( elem, "olddisplay" );
+		display = elem.style.display;
+		if ( show ) {
+
+			// Reset the inline display of this element to learn if it is
+			// being hidden by cascaded rules or not
+			if ( !values[ index ] && display === "none" ) {
+				elem.style.display = "";
+			}
+
+			// Set elements which have been overridden with display: none
+			// in a stylesheet to whatever the default browser style is
+			// for such an element
+			if ( elem.style.display === "" && isHidden( elem ) ) {
+				values[ index ] = dataPriv.access(
+					elem,
+					"olddisplay",
+					defaultDisplay( elem.nodeName )
+				);
+			}
+		} else {
+			hidden = isHidden( elem );
+
+			if ( display !== "none" || !hidden ) {
+				dataPriv.set(
+					elem,
+					"olddisplay",
+					hidden ? display : jQuery.css( elem, "display" )
+				);
+			}
+		}
+	}
+
+	// Set the display of most of the elements in a second loop
+	// to avoid the constant reflow
+	for ( index = 0; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+			elem.style.display = show ? values[ index ] || "" : "none";
+		}
+	}
+
+	return elements;
+}
+
+jQuery.extend( {
+
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity" );
+					return ret === "" ? "1" : ret;
+				}
+			}
+		}
+	},
+
+	// Don't automatically add "px" to these possibly-unitless properties
+	cssNumber: {
+		"animationIterationCount": true,
+		"columnCount": true,
+		"fillOpacity": true,
+		"flexGrow": true,
+		"flexShrink": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"order": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		"float": "cssFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, hooks,
+			origName = jQuery.camelCase( name ),
+			style = elem.style;
+
+		name = jQuery.cssProps[ origName ] ||
+			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
+
+		// Gets hook for the prefixed version, then unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// Convert "+=" or "-=" to relative numbers (#7345)
+			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
+				value = adjustCSS( elem, name, ret );
+
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that null and NaN values aren't set (#7116)
+			if ( value == null || value !== value ) {
+				return;
+			}
+
+			// If a number was passed in, add the unit (except for certain CSS properties)
+			if ( type === "number" ) {
+				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
+			}
+
+			// Support: IE9-11+
+			// background-* props affect original clone's values
+			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+				style[ name ] = "inherit";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !( "set" in hooks ) ||
+				( value = hooks.set( elem, value, extra ) ) !== undefined ) {
+
+				style[ name ] = value;
+			}
+
+		} else {
+
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks &&
+				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
+
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra, styles ) {
+		var val, num, hooks,
+			origName = jQuery.camelCase( name );
+
+		// Make sure that we're working with the right name
+		name = jQuery.cssProps[ origName ] ||
+			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );
+
+		// Try prefixed name followed by the unprefixed name
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks ) {
+			val = hooks.get( elem, true, extra );
+		}
+
+		// Otherwise, if a way to get the computed value exists, use that
+		if ( val === undefined ) {
+			val = curCSS( elem, name, styles );
+		}
+
+		// Convert "normal" to computed value
+		if ( val === "normal" && name in cssNormalTransform ) {
+			val = cssNormalTransform[ name ];
+		}
+
+		// Make numeric if forced or a qualifier was provided and val looks numeric
+		if ( extra === "" || extra ) {
+			num = parseFloat( val );
+			return extra === true || isFinite( num ) ? num || 0 : val;
+		}
+		return val;
+	}
+} );
+
+jQuery.each( [ "height", "width" ], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			if ( computed ) {
+
+				// Certain elements can have dimension info if we invisibly show them
+				// but it must have a current display style that would benefit
+				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
+					elem.offsetWidth === 0 ?
+						swap( elem, cssShow, function() {
+							return getWidthOrHeight( elem, name, extra );
+						} ) :
+						getWidthOrHeight( elem, name, extra );
+			}
+		},
+
+		set: function( elem, value, extra ) {
+			var matches,
+				styles = extra && getStyles( elem ),
+				subtract = extra && augmentWidthOrHeight(
+					elem,
+					name,
+					extra,
+					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+					styles
+				);
+
+			// Convert to pixels if value adjustment is needed
+			if ( subtract && ( matches = rcssNum.exec( value ) ) &&
+				( matches[ 3 ] || "px" ) !== "px" ) {
+
+				elem.style[ name ] = value;
+				value = jQuery.css( elem, name );
+			}
+
+			return setPositiveNumber( elem, value, subtract );
+		}
+	};
+} );
+
+jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
+	function( elem, computed ) {
+		if ( computed ) {
+			return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
+				elem.getBoundingClientRect().left -
+					swap( elem, { marginLeft: 0 }, function() {
+						return elem.getBoundingClientRect().left;
+					} )
+				) + "px";
+		}
+	}
+);
+
+// Support: Android 2.3
+jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
+	function( elem, computed ) {
+		if ( computed ) {
+			return swap( elem, { "display": "inline-block" },
+				curCSS, [ elem, "marginRight" ] );
+		}
+	}
+);
+
+// These hooks are used by animate to expand properties
+jQuery.each( {
+	margin: "",
+	padding: "",
+	border: "Width"
+}, function( prefix, suffix ) {
+	jQuery.cssHooks[ prefix + suffix ] = {
+		expand: function( value ) {
+			var i = 0,
+				expanded = {},
+
+				// Assumes a single number if not a string
+				parts = typeof value === "string" ? value.split( " " ) : [ value ];
+
+			for ( ; i < 4; i++ ) {
+				expanded[ prefix + cssExpand[ i ] + suffix ] =
+					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+			}
+
+			return expanded;
+		}
+	};
+
+	if ( !rmargin.test( prefix ) ) {
+		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+	}
+} );
+
+jQuery.fn.extend( {
+	css: function( name, value ) {
+		return access( this, function( elem, name, value ) {
+			var styles, len,
+				map = {},
+				i = 0;
+
+			if ( jQuery.isArray( name ) ) {
+				styles = getStyles( elem );
+				len = name.length;
+
+				for ( ; i < len; i++ ) {
+					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+				}
+
+				return map;
+			}
+
+			return value !== undefined ?
+				jQuery.style( elem, name, value ) :
+				jQuery.css( elem, name );
+		}, name, value, arguments.length > 1 );
+	},
+	show: function() {
+		return showHide( this, true );
+	},
+	hide: function() {
+		return showHide( this );
+	},
+	toggle: function( state ) {
+		if ( typeof state === "boolean" ) {
+			return state ? this.show() : this.hide();
+		}
+
+		return this.each( function() {
+			if ( isHidden( this ) ) {
+				jQuery( this ).show();
+			} else {
+				jQuery( this ).hide();
+			}
+		} );
+	}
+} );
+
+
+function Tween( elem, options, prop, end, easing ) {
+	return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+	constructor: Tween,
+	init: function( elem, options, prop, end, easing, unit ) {
+		this.elem = elem;
+		this.prop = prop;
+		this.easing = easing || jQuery.easing._default;
+		this.options = options;
+		this.start = this.now = this.cur();
+		this.end = end;
+		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+	},
+	cur: function() {
+		var hooks = Tween.propHooks[ this.prop ];
+
+		return hooks && hooks.get ?
+			hooks.get( this ) :
+			Tween.propHooks._default.get( this );
+	},
+	run: function( percent ) {
+		var eased,
+			hooks = Tween.propHooks[ this.prop ];
+
+		if ( this.options.duration ) {
+			this.pos = eased = jQuery.easing[ this.easing ](
+				percent, this.options.duration * percent, 0, 1, this.options.duration
+			);
+		} else {
+			this.pos = eased = percent;
+		}
+		this.now = ( this.end - this.start ) * eased + this.start;
+
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		if ( hooks && hooks.set ) {
+			hooks.set( this );
+		} else {
+			Tween.propHooks._default.set( this );
+		}
+		return this;
+	}
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+	_default: {
+		get: function( tween ) {
+			var result;
+
+			// Use a property on the element directly when it is not a DOM element,
+			// or when there is no matching style property that exists.
+			if ( tween.elem.nodeType !== 1 ||
+				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
+				return tween.elem[ tween.prop ];
+			}
+
+			// Passing an empty string as a 3rd parameter to .css will automatically
+			// attempt a parseFloat and fallback to a string if the parse fails.
+			// Simple values such as "10px" are parsed to Float;
+			// complex values such as "rotate(1rad)" are returned as-is.
+			result = jQuery.css( tween.elem, tween.prop, "" );
+
+			// Empty strings, null, undefined and "auto" are converted to 0.
+			return !result || result === "auto" ? 0 : result;
+		},
+		set: function( tween ) {
+
+			// Use step hook for back compat.
+			// Use cssHook if its there.
+			// Use .style if available and use plain properties where available.
+			if ( jQuery.fx.step[ tween.prop ] ) {
+				jQuery.fx.step[ tween.prop ]( tween );
+			} else if ( tween.elem.nodeType === 1 &&
+				( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
+					jQuery.cssHooks[ tween.prop ] ) ) {
+				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+			} else {
+				tween.elem[ tween.prop ] = tween.now;
+			}
+		}
+	}
+};
+
+// Support: IE9
+// Panic based approach to setting things on disconnected nodes
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+	set: function( tween ) {
+		if ( tween.elem.nodeType && tween.elem.parentNode ) {
+			tween.elem[ tween.prop ] = tween.now;
+		}
+	}
+};
+
+jQuery.easing = {
+	linear: function( p ) {
+		return p;
+	},
+	swing: function( p ) {
+		return 0.5 - Math.cos( p * Math.PI ) / 2;
+	},
+	_default: "swing"
+};
+
+jQuery.fx = Tween.prototype.init;
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+
+
+
+var
+	fxNow, timerId,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rrun = /queueHooks$/;
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	window.setTimeout( function() {
+		fxNow = undefined;
+	} );
+	return ( fxNow = jQuery.now() );
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+	var which,
+		i = 0,
+		attrs = { height: type };
+
+	// If we include width, step value is 1 to do all cssExpand values,
+	// otherwise step value is 2 to skip over Left and Right
+	includeWidth = includeWidth ? 1 : 0;
+	for ( ; i < 4 ; i += 2 - includeWidth ) {
+		which = cssExpand[ i ];
+		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+	}
+
+	if ( includeWidth ) {
+		attrs.opacity = attrs.width = type;
+	}
+
+	return attrs;
+}
+
+function createTween( value, prop, animation ) {
+	var tween,
+		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
+		index = 0,
+		length = collection.length;
+	for ( ; index < length; index++ ) {
+		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
+
+			// We're done with this property
+			return tween;
+		}
+	}
+}
+
+function defaultPrefilter( elem, props, opts ) {
+	/* jshint validthis: true */
+	var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
+		anim = this,
+		orig = {},
+		style = elem.style,
+		hidden = elem.nodeType && isHidden( elem ),
+		dataShow = dataPriv.get( elem, "fxshow" );
+
+	// Handle queue: false promises
+	if ( !opts.queue ) {
+		hooks = jQuery._queueHooks( elem, "fx" );
+		if ( hooks.unqueued == null ) {
+			hooks.unqueued = 0;
+			oldfire = hooks.empty.fire;
+			hooks.empty.fire = function() {
+				if ( !hooks.unqueued ) {
+					oldfire();
+				}
+			};
+		}
+		hooks.unqueued++;
+
+		anim.always( function() {
+
+			// Ensure the complete handler is called before this completes
+			anim.always( function() {
+				hooks.unqueued--;
+				if ( !jQuery.queue( elem, "fx" ).length ) {
+					hooks.empty.fire();
+				}
+			} );
+		} );
+	}
+
+	// Height/width overflow pass
+	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+
+		// Make sure that nothing sneaks out
+		// Record all 3 overflow attributes because IE9-10 do not
+		// change the overflow attribute when overflowX and
+		// overflowY are set to the same value
+		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+		// Set display property to inline-block for height/width
+		// animations on inline elements that are having width/height animated
+		display = jQuery.css( elem, "display" );
+
+		// Test default display if display is currently "none"
+		checkDisplay = display === "none" ?
+			dataPriv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;
+
+		if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
+			style.display = "inline-block";
+		}
+	}
+
+	if ( opts.overflow ) {
+		style.overflow = "hidden";
+		anim.always( function() {
+			style.overflow = opts.overflow[ 0 ];
+			style.overflowX = opts.overflow[ 1 ];
+			style.overflowY = opts.overflow[ 2 ];
+		} );
+	}
+
+	// show/hide pass
+	for ( prop in props ) {
+		value = props[ prop ];
+		if ( rfxtypes.exec( value ) ) {
+			delete props[ prop ];
+			toggle = toggle || value === "toggle";
+			if ( value === ( hidden ? "hide" : "show" ) ) {
+
+				// If there is dataShow left over from a stopped hide or show
+				// and we are going to proceed with show, we should pretend to be hidden
+				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+					hidden = true;
+				} else {
+					continue;
+				}
+			}
+			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+
+		// Any non-fx value stops us from restoring the original display value
+		} else {
+			display = undefined;
+		}
+	}
+
+	if ( !jQuery.isEmptyObject( orig ) ) {
+		if ( dataShow ) {
+			if ( "hidden" in dataShow ) {
+				hidden = dataShow.hidden;
+			}
+		} else {
+			dataShow = dataPriv.access( elem, "fxshow", {} );
+		}
+
+		// Store state if its toggle - enables .stop().toggle() to "reverse"
+		if ( toggle ) {
+			dataShow.hidden = !hidden;
+		}
+		if ( hidden ) {
+			jQuery( elem ).show();
+		} else {
+			anim.done( function() {
+				jQuery( elem ).hide();
+			} );
+		}
+		anim.done( function() {
+			var prop;
+
+			dataPriv.remove( elem, "fxshow" );
+			for ( prop in orig ) {
+				jQuery.style( elem, prop, orig[ prop ] );
+			}
+		} );
+		for ( prop in orig ) {
+			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+			if ( !( prop in dataShow ) ) {
+				dataShow[ prop ] = tween.start;
+				if ( hidden ) {
+					tween.end = tween.start;
+					tween.start = prop === "width" || prop === "height" ? 1 : 0;
+				}
+			}
+		}
+
+	// If this is a noop like .hide().hide(), restore an overwritten display value
+	} else if ( ( display === "none" ? defaultDisplay( elem.nodeName ) : display ) === "inline" ) {
+		style.display = display;
+	}
+}
+
+function propFilter( props, specialEasing ) {
+	var index, name, easing, value, hooks;
+
+	// camelCase, specialEasing and expand cssHook pass
+	for ( index in props ) {
+		name = jQuery.camelCase( index );
+		easing = specialEasing[ name ];
+		value = props[ index ];
+		if ( jQuery.isArray( value ) ) {
+			easing = value[ 1 ];
+			value = props[ index ] = value[ 0 ];
+		}
+
+		if ( index !== name ) {
+			props[ name ] = value;
+			delete props[ index ];
+		}
+
+		hooks = jQuery.cssHooks[ name ];
+		if ( hooks && "expand" in hooks ) {
+			value = hooks.expand( value );
+			delete props[ name ];
+
+			// Not quite $.extend, this won't overwrite existing keys.
+			// Reusing 'index' because we have the correct "name"
+			for ( index in value ) {
+				if ( !( index in props ) ) {
+					props[ index ] = value[ index ];
+					specialEasing[ index ] = easing;
+				}
+			}
+		} else {
+			specialEasing[ name ] = easing;
+		}
+	}
+}
+
+function Animation( elem, properties, options ) {
+	var result,
+		stopped,
+		index = 0,
+		length = Animation.prefilters.length,
+		deferred = jQuery.Deferred().always( function() {
+
+			// Don't match elem in the :animated selector
+			delete tick.elem;
+		} ),
+		tick = function() {
+			if ( stopped ) {
+				return false;
+			}
+			var currentTime = fxNow || createFxNow(),
+				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+
+				// Support: Android 2.3
+				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
+				temp = remaining / animation.duration || 0,
+				percent = 1 - temp,
+				index = 0,
+				length = animation.tweens.length;
+
+			for ( ; index < length ; index++ ) {
+				animation.tweens[ index ].run( percent );
+			}
+
+			deferred.notifyWith( elem, [ animation, percent, remaining ] );
+
+			if ( percent < 1 && length ) {
+				return remaining;
+			} else {
+				deferred.resolveWith( elem, [ animation ] );
+				return false;
+			}
+		},
+		animation = deferred.promise( {
+			elem: elem,
+			props: jQuery.extend( {}, properties ),
+			opts: jQuery.extend( true, {
+				specialEasing: {},
+				easing: jQuery.easing._default
+			}, options ),
+			originalProperties: properties,
+			originalOptions: options,
+			startTime: fxNow || createFxNow(),
+			duration: options.duration,
+			tweens: [],
+			createTween: function( prop, end ) {
+				var tween = jQuery.Tween( elem, animation.opts, prop, end,
+						animation.opts.specialEasing[ prop ] || animation.opts.easing );
+				animation.tweens.push( tween );
+				return tween;
+			},
+			stop: function( gotoEnd ) {
+				var index = 0,
+
+					// If we are going to the end, we want to run all the tweens
+					// otherwise we skip this part
+					length = gotoEnd ? animation.tweens.length : 0;
+				if ( stopped ) {
+					return this;
+				}
+				stopped = true;
+				for ( ; index < length ; index++ ) {
+					animation.tweens[ index ].run( 1 );
+				}
+
+				// Resolve when we played the last frame; otherwise, reject
+				if ( gotoEnd ) {
+					deferred.notifyWith( elem, [ animation, 1, 0 ] );
+					deferred.resolveWith( elem, [ animation, gotoEnd ] );
+				} else {
+					deferred.rejectWith( elem, [ animation, gotoEnd ] );
+				}
+				return this;
+			}
+		} ),
+		props = animation.props;
+
+	propFilter( props, animation.opts.specialEasing );
+
+	for ( ; index < length ; index++ ) {
+		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
+		if ( result ) {
+			if ( jQuery.isFunction( result.stop ) ) {
+				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
+					jQuery.proxy( result.stop, result );
+			}
+			return result;
+		}
+	}
+
+	jQuery.map( props, createTween, animation );
+
+	if ( jQuery.isFunction( animation.opts.start ) ) {
+		animation.opts.start.call( elem, animation );
+	}
+
+	jQuery.fx.timer(
+		jQuery.extend( tick, {
+			elem: elem,
+			anim: animation,
+			queue: animation.opts.queue
+		} )
+	);
+
+	// attach callbacks from options
+	return animation.progress( animation.opts.progress )
+		.done( animation.opts.done, animation.opts.complete )
+		.fail( animation.opts.fail )
+		.always( animation.opts.always );
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+	tweeners: {
+		"*": [ function( prop, value ) {
+			var tween = this.createTween( prop, value );
+			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
+			return tween;
+		} ]
+	},
+
+	tweener: function( props, callback ) {
+		if ( jQuery.isFunction( props ) ) {
+			callback = props;
+			props = [ "*" ];
+		} else {
+			props = props.match( rnotwhite );
+		}
+
+		var prop,
+			index = 0,
+			length = props.length;
+
+		for ( ; index < length ; index++ ) {
+			prop = props[ index ];
+			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
+			Animation.tweeners[ prop ].unshift( callback );
+		}
+	},
+
+	prefilters: [ defaultPrefilter ],
+
+	prefilter: function( callback, prepend ) {
+		if ( prepend ) {
+			Animation.prefilters.unshift( callback );
+		} else {
+			Animation.prefilters.push( callback );
+		}
+	}
+} );
+
+jQuery.speed = function( speed, easing, fn ) {
+	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+		complete: fn || !fn && easing ||
+			jQuery.isFunction( speed ) && speed,
+		duration: speed,
+		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+	};
+
+	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ?
+		opt.duration : opt.duration in jQuery.fx.speeds ?
+			jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+	// Normalize opt.queue - true/undefined/null -> "fx"
+	if ( opt.queue == null || opt.queue === true ) {
+		opt.queue = "fx";
+	}
+
+	// Queueing
+	opt.old = opt.complete;
+
+	opt.complete = function() {
+		if ( jQuery.isFunction( opt.old ) ) {
+			opt.old.call( this );
+		}
+
+		if ( opt.queue ) {
+			jQuery.dequeue( this, opt.queue );
+		}
+	};
+
+	return opt;
+};
+
+jQuery.fn.extend( {
+	fadeTo: function( speed, to, easing, callback ) {
+
+		// Show any hidden elements after setting opacity to 0
+		return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+			// Animate to the value specified
+			.end().animate( { opacity: to }, speed, easing, callback );
+	},
+	animate: function( prop, speed, easing, callback ) {
+		var empty = jQuery.isEmptyObject( prop ),
+			optall = jQuery.speed( speed, easing, callback ),
+			doAnimation = function() {
+
+				// Operate on a copy of prop so per-property easing won't be lost
+				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+				// Empty animations, or finishing resolves immediately
+				if ( empty || dataPriv.get( this, "finish" ) ) {
+					anim.stop( true );
+				}
+			};
+			doAnimation.finish = doAnimation;
+
+		return empty || optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+	stop: function( type, clearQueue, gotoEnd ) {
+		var stopQueue = function( hooks ) {
+			var stop = hooks.stop;
+			delete hooks.stop;
+			stop( gotoEnd );
+		};
+
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue && type !== false ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each( function() {
+			var dequeue = true,
+				index = type != null && type + "queueHooks",
+				timers = jQuery.timers,
+				data = dataPriv.get( this );
+
+			if ( index ) {
+				if ( data[ index ] && data[ index ].stop ) {
+					stopQueue( data[ index ] );
+				}
+			} else {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+						stopQueue( data[ index ] );
+					}
+				}
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this &&
+					( type == null || timers[ index ].queue === type ) ) {
+
+					timers[ index ].anim.stop( gotoEnd );
+					dequeue = false;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// Start the next in the queue if the last step wasn't forced.
+			// Timers currently will call their complete callbacks, which
+			// will dequeue but only if they were gotoEnd.
+			if ( dequeue || !gotoEnd ) {
+				jQuery.dequeue( this, type );
+			}
+		} );
+	},
+	finish: function( type ) {
+		if ( type !== false ) {
+			type = type || "fx";
+		}
+		return this.each( function() {
+			var index,
+				data = dataPriv.get( this ),
+				queue = data[ type + "queue" ],
+				hooks = data[ type + "queueHooks" ],
+				timers = jQuery.timers,
+				length = queue ? queue.length : 0;
+
+			// Enable finishing flag on private data
+			data.finish = true;
+
+			// Empty the queue first
+			jQuery.queue( this, type, [] );
+
+			if ( hooks && hooks.stop ) {
+				hooks.stop.call( this, true );
+			}
+
+			// Look for any active animations, and finish them
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+					timers[ index ].anim.stop( true );
+					timers.splice( index, 1 );
+				}
+			}
+
+			// Look for any animations in the old queue and finish them
+			for ( index = 0; index < length; index++ ) {
+				if ( queue[ index ] && queue[ index ].finish ) {
+					queue[ index ].finish.call( this );
+				}
+			}
+
+			// Turn off finishing flag
+			delete data.finish;
+		} );
+	}
+} );
+
+jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
+	var cssFn = jQuery.fn[ name ];
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return speed == null || typeof speed === "boolean" ?
+			cssFn.apply( this, arguments ) :
+			this.animate( genFx( name, true ), speed, easing, callback );
+	};
+} );
+
+// Generate shortcuts for custom animations
+jQuery.each( {
+	slideDown: genFx( "show" ),
+	slideUp: genFx( "hide" ),
+	slideToggle: genFx( "toggle" ),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+} );
+
+jQuery.timers = [];
+jQuery.fx.tick = function() {
+	var timer,
+		i = 0,
+		timers = jQuery.timers;
+
+	fxNow = jQuery.now();
+
+	for ( ; i < timers.length; i++ ) {
+		timer = timers[ i ];
+
+		// Checks the timer has not already been removed
+		if ( !timer() && timers[ i ] === timer ) {
+			timers.splice( i--, 1 );
+		}
+	}
+
+	if ( !timers.length ) {
+		jQuery.fx.stop();
+	}
+	fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+	jQuery.timers.push( timer );
+	if ( timer() ) {
+		jQuery.fx.start();
+	} else {
+		jQuery.timers.pop();
+	}
+};
+
+jQuery.fx.interval = 13;
+jQuery.fx.start = function() {
+	if ( !timerId ) {
+		timerId = window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
+	}
+};
+
+jQuery.fx.stop = function() {
+	window.clearInterval( timerId );
+
+	timerId = null;
+};
+
+jQuery.fx.speeds = {
+	slow: 600,
+	fast: 200,
+
+	// Default speed
+	_default: 400
+};
+
+
+// Based off of the plugin by Clint Helfers, with permission.
+// http://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
+jQuery.fn.delay = function( time, type ) {
+	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+	type = type || "fx";
+
+	return this.queue( type, function( next, hooks ) {
+		var timeout = window.setTimeout( next, time );
+		hooks.stop = function() {
+			window.clearTimeout( timeout );
+		};
+	} );
+};
+
+
+( function() {
+	var input = document.createElement( "input" ),
+		select = document.createElement( "select" ),
+		opt = select.appendChild( document.createElement( "option" ) );
+
+	input.type = "checkbox";
+
+	// Support: iOS<=5.1, Android<=4.2+
+	// Default value for a checkbox should be "on"
+	support.checkOn = input.value !== "";
+
+	// Support: IE<=11+
+	// Must access selectedIndex to make default options select
+	support.optSelected = opt.selected;
+
+	// Support: Android<=2.3
+	// Options inside disabled selects are incorrectly marked as disabled
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Support: IE<=11+
+	// An input loses its value after becoming a radio
+	input = document.createElement( "input" );
+	input.value = "t";
+	input.type = "radio";
+	support.radioValue = input.value === "t";
+} )();
+
+
+var boolHook,
+	attrHandle = jQuery.expr.attrHandle;
+
+jQuery.fn.extend( {
+	attr: function( name, value ) {
+		return access( this, jQuery.attr, name, value, arguments.length > 1 );
+	},
+
+	removeAttr: function( name ) {
+		return this.each( function() {
+			jQuery.removeAttr( this, name );
+		} );
+	}
+} );
+
+jQuery.extend( {
+	attr: function( elem, name, value ) {
+		var ret, hooks,
+			nType = elem.nodeType;
+
+		// Don't get/set attributes on text, comment and attribute nodes
+		if ( nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( typeof elem.getAttribute === "undefined" ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		// All attributes are lowercase
+		// Grab necessary hook if one is defined
+		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+			name = name.toLowerCase();
+			hooks = jQuery.attrHooks[ name ] ||
+				( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
+		}
+
+		if ( value !== undefined ) {
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+				return;
+			}
+
+			if ( hooks && "set" in hooks &&
+				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+				return ret;
+			}
+
+			elem.setAttribute( name, value + "" );
+			return value;
+		}
+
+		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+			return ret;
+		}
+
+		ret = jQuery.find.attr( elem, name );
+
+		// Non-existent attributes return null, we normalize to undefined
+		return ret == null ? undefined : ret;
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				if ( !support.radioValue && value === "radio" &&
+					jQuery.nodeName( elem, "input" ) ) {
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		}
+	},
+
+	removeAttr: function( elem, value ) {
+		var name, propName,
+			i = 0,
+			attrNames = value && value.match( rnotwhite );
+
+		if ( attrNames && elem.nodeType === 1 ) {
+			while ( ( name = attrNames[ i++ ] ) ) {
+				propName = jQuery.propFix[ name ] || name;
+
+				// Boolean attributes get special treatment (#10870)
+				if ( jQuery.expr.match.bool.test( name ) ) {
+
+					// Set corresponding property to false
+					elem[ propName ] = false;
+				}
+
+				elem.removeAttribute( name );
+			}
+		}
+	}
+} );
+
+// Hooks for boolean attributes
+boolHook = {
+	set: function( elem, value, name ) {
+		if ( value === false ) {
+
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			elem.setAttribute( name, name );
+		}
+		return name;
+	}
+};
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+	var getter = attrHandle[ name ] || jQuery.find.attr;
+
+	attrHandle[ name ] = function( elem, name, isXML ) {
+		var ret, handle;
+		if ( !isXML ) {
+
+			// Avoid an infinite loop by temporarily removing this function from the getter
+			handle = attrHandle[ name ];
+			attrHandle[ name ] = ret;
+			ret = getter( elem, name, isXML ) != null ?
+				name.toLowerCase() :
+				null;
+			attrHandle[ name ] = handle;
+		}
+		return ret;
+	};
+} );
+
+
+
+
+var rfocusable = /^(?:input|select|textarea|button)$/i,
+	rclickable = /^(?:a|area)$/i;
+
+jQuery.fn.extend( {
+	prop: function( name, value ) {
+		return access( this, jQuery.prop, name, value, arguments.length > 1 );
+	},
+
+	removeProp: function( name ) {
+		return this.each( function() {
+			delete this[ jQuery.propFix[ name ] || name ];
+		} );
+	}
+} );
+
+jQuery.extend( {
+	prop: function( elem, name, value ) {
+		var ret, hooks,
+			nType = elem.nodeType;
+
+		// Don't get/set properties on text, comment and attribute nodes
+		if ( nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			if ( hooks && "set" in hooks &&
+				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
+				return ret;
+			}
+
+			return ( elem[ name ] = value );
+		}
+
+		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
+			return ret;
+		}
+
+		return elem[ name ];
+	},
+
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+
+				// elem.tabIndex doesn't always return the
+				// correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				// Use proper attribute retrieval(#12072)
+				var tabindex = jQuery.find.attr( elem, "tabindex" );
+
+				return tabindex ?
+					parseInt( tabindex, 10 ) :
+					rfocusable.test( elem.nodeName ) ||
+						rclickable.test( elem.nodeName ) && elem.href ?
+							0 :
+							-1;
+			}
+		}
+	},
+
+	propFix: {
+		"for": "htmlFor",
+		"class": "className"
+	}
+} );
+
+if ( !support.optSelected ) {
+	jQuery.propHooks.selected = {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+			if ( parent && parent.parentNode ) {
+				parent.parentNode.selectedIndex;
+			}
+			return null;
+		}
+	};
+}
+
+jQuery.each( [
+	"tabIndex",
+	"readOnly",
+	"maxLength",
+	"cellSpacing",
+	"cellPadding",
+	"rowSpan",
+	"colSpan",
+	"useMap",
+	"frameBorder",
+	"contentEditable"
+], function() {
+	jQuery.propFix[ this.toLowerCase() ] = this;
+} );
+
+
+
+
+var rclass = /[\t\r\n\f]/g;
+
+function getClass( elem ) {
+	return elem.getAttribute && elem.getAttribute( "class" ) || "";
+}
+
+jQuery.fn.extend( {
+	addClass: function( value ) {
+		var classes, elem, cur, curValue, clazz, j, finalValue,
+			i = 0;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each( function( j ) {
+				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
+			} );
+		}
+
+		if ( typeof value === "string" && value ) {
+			classes = value.match( rnotwhite ) || [];
+
+			while ( ( elem = this[ i++ ] ) ) {
+				curValue = getClass( elem );
+				cur = elem.nodeType === 1 &&
+					( " " + curValue + " " ).replace( rclass, " " );
+
+				if ( cur ) {
+					j = 0;
+					while ( ( clazz = classes[ j++ ] ) ) {
+						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+							cur += clazz + " ";
+						}
+					}
+
+					// Only assign if different to avoid unneeded rendering.
+					finalValue = jQuery.trim( cur );
+					if ( curValue !== finalValue ) {
+						elem.setAttribute( "class", finalValue );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classes, elem, cur, curValue, clazz, j, finalValue,
+			i = 0;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each( function( j ) {
+				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
+			} );
+		}
+
+		if ( !arguments.length ) {
+			return this.attr( "class", "" );
+		}
+
+		if ( typeof value === "string" && value ) {
+			classes = value.match( rnotwhite ) || [];
+
+			while ( ( elem = this[ i++ ] ) ) {
+				curValue = getClass( elem );
+
+				// This expression is here for better compressibility (see addClass)
+				cur = elem.nodeType === 1 &&
+					( " " + curValue + " " ).replace( rclass, " " );
+
+				if ( cur ) {
+					j = 0;
+					while ( ( clazz = classes[ j++ ] ) ) {
+
+						// Remove *all* instances
+						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
+							cur = cur.replace( " " + clazz + " ", " " );
+						}
+					}
+
+					// Only assign if different to avoid unneeded rendering.
+					finalValue = jQuery.trim( cur );
+					if ( curValue !== finalValue ) {
+						elem.setAttribute( "class", finalValue );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value;
+
+		if ( typeof stateVal === "boolean" && type === "string" ) {
+			return stateVal ? this.addClass( value ) : this.removeClass( value );
+		}
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each( function( i ) {
+				jQuery( this ).toggleClass(
+					value.call( this, i, getClass( this ), stateVal ),
+					stateVal
+				);
+			} );
+		}
+
+		return this.each( function() {
+			var className, i, self, classNames;
+
+			if ( type === "string" ) {
+
+				// Toggle individual class names
+				i = 0;
+				self = jQuery( this );
+				classNames = value.match( rnotwhite ) || [];
+
+				while ( ( className = classNames[ i++ ] ) ) {
+
+					// Check each className given, space separated list
+					if ( self.hasClass( className ) ) {
+						self.removeClass( className );
+					} else {
+						self.addClass( className );
+					}
+				}
+
+			// Toggle whole class name
+			} else if ( value === undefined || type === "boolean" ) {
+				className = getClass( this );
+				if ( className ) {
+
+					// Store className if set
+					dataPriv.set( this, "__className__", className );
+				}
+
+				// If the element has a class name or if we're passed `false`,
+				// then remove the whole classname (if there was one, the above saved it).
+				// Otherwise bring back whatever was previously saved (if anything),
+				// falling back to the empty string if nothing was stored.
+				if ( this.setAttribute ) {
+					this.setAttribute( "class",
+						className || value === false ?
+						"" :
+						dataPriv.get( this, "__className__" ) || ""
+					);
+				}
+			}
+		} );
+	},
+
+	hasClass: function( selector ) {
+		var className, elem,
+			i = 0;
+
+		className = " " + selector + " ";
+		while ( ( elem = this[ i++ ] ) ) {
+			if ( elem.nodeType === 1 &&
+				( " " + getClass( elem ) + " " ).replace( rclass, " " )
+					.indexOf( className ) > -1
+			) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+} );
+
+
+
+
+var rreturn = /\r/g;
+
+jQuery.fn.extend( {
+	val: function( value ) {
+		var hooks, ret, isFunction,
+			elem = this[ 0 ];
+
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.type ] ||
+					jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+				if ( hooks &&
+					"get" in hooks &&
+					( ret = hooks.get( elem, "value" ) ) !== undefined
+				) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ?
+
+					// Handle most common string cases
+					ret.replace( rreturn, "" ) :
+
+					// Handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return;
+		}
+
+		isFunction = jQuery.isFunction( value );
+
+		return this.each( function( i ) {
+			var val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, jQuery( this ).val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+
+			} else if ( typeof val === "number" ) {
+				val += "";
+
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map( val, function( value ) {
+					return value == null ? "" : value + "";
+				} );
+			}
+
+			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		} );
+	}
+} );
+
+jQuery.extend( {
+	valHooks: {
+		option: {
+			get: function( elem ) {
+
+				// Support: IE<11
+				// option.value not trimmed (#14858)
+				return jQuery.trim( elem.value );
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value, option,
+					options = elem.options,
+					index = elem.selectedIndex,
+					one = elem.type === "select-one" || index < 0,
+					values = one ? null : [],
+					max = one ? index + 1 : options.length,
+					i = index < 0 ?
+						max :
+						one ? index : 0;
+
+				// Loop through all the selected options
+				for ( ; i < max; i++ ) {
+					option = options[ i ];
+
+					// IE8-9 doesn't update selected after form reset (#2551)
+					if ( ( option.selected || i === index ) &&
+
+							// Don't return options that are disabled or in a disabled optgroup
+							( support.optDisabled ?
+								!option.disabled : option.getAttribute( "disabled" ) === null ) &&
+							( !option.parentNode.disabled ||
+								!jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var optionSet, option,
+					options = elem.options,
+					values = jQuery.makeArray( value ),
+					i = options.length;
+
+				while ( i-- ) {
+					option = options[ i ];
+					if ( option.selected =
+							jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
+					) {
+						optionSet = true;
+					}
+				}
+
+				// Force browsers to behave consistently when non-matching value is set
+				if ( !optionSet ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	}
+} );
+
+// Radios and checkboxes getter/setter
+jQuery.each( [ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
+			}
+		}
+	};
+	if ( !support.checkOn ) {
+		jQuery.valHooks[ this ].get = function( elem ) {
+			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
+		};
+	}
+} );
+
+
+
+
+// Return jQuery for attributes-only inclusion
+
+
+var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/;
+
+jQuery.extend( jQuery.event, {
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+
+		var i, cur, tmp, bubbleType, ontype, handle, special,
+			eventPath = [ elem || document ],
+			type = hasOwn.call( event, "type" ) ? event.type : event,
+			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
+
+		cur = tmp = elem = elem || document;
+
+		// Don't do events on text and comment nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		// focus/blur morphs to focusin/out; ensure we're not firing them right now
+		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+			return;
+		}
+
+		if ( type.indexOf( "." ) > -1 ) {
+
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split( "." );
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+		ontype = type.indexOf( ":" ) < 0 && "on" + type;
+
+		// Caller can pass in a jQuery.Event object, Object, or just an event type string
+		event = event[ jQuery.expando ] ?
+			event :
+			new jQuery.Event( type, typeof event === "object" && event );
+
+		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+		event.isTrigger = onlyHandlers ? 2 : 3;
+		event.namespace = namespaces.join( "." );
+		event.rnamespace = event.namespace ?
+			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
+			null;
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		if ( !event.target ) {
+			event.target = elem;
+		}
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data == null ?
+			[ event ] :
+			jQuery.makeArray( data, [ event ] );
+
+		// Allow special events to draw outside the lines
+		special = jQuery.event.special[ type ] || {};
+		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+			return;
+		}
+
+		// Determine event propagation path in advance, per W3C events spec (#9951)
+		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+			bubbleType = special.delegateType || type;
+			if ( !rfocusMorph.test( bubbleType + type ) ) {
+				cur = cur.parentNode;
+			}
+			for ( ; cur; cur = cur.parentNode ) {
+				eventPath.push( cur );
+				tmp = cur;
+			}
+
+			// Only add window if we got to document (e.g., not plain obj or detached DOM)
+			if ( tmp === ( elem.ownerDocument || document ) ) {
+				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+			}
+		}
+
+		// Fire handlers on the event path
+		i = 0;
+		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
+
+			event.type = i > 1 ?
+				bubbleType :
+				special.bindType || type;
+
+			// jQuery handler
+			handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
+				dataPriv.get( cur, "handle" );
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+
+			// Native handler
+			handle = ontype && cur[ ontype ];
+			if ( handle && handle.apply && acceptData( cur ) ) {
+				event.result = handle.apply( cur, data );
+				if ( event.result === false ) {
+					event.preventDefault();
+				}
+			}
+		}
+		event.type = type;
+
+		// If nobody prevented the default action, do it now
+		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+			if ( ( !special._default ||
+				special._default.apply( eventPath.pop(), data ) === false ) &&
+				acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name name as the event.
+				// Don't do default actions on window, that's where global variables be (#6170)
+				if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+					// Don't re-trigger an onFOO event when we call its FOO() method
+					tmp = elem[ ontype ];
+
+					if ( tmp ) {
+						elem[ ontype ] = null;
+					}
+
+					// Prevent re-triggering of the same event, since we already bubbled it above
+					jQuery.event.triggered = type;
+					elem[ type ]();
+					jQuery.event.triggered = undefined;
+
+					if ( tmp ) {
+						elem[ ontype ] = tmp;
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	// Piggyback on a donor event to simulate a different one
+	simulate: function( type, elem, event ) {
+		var e = jQuery.extend(
+			new jQuery.Event(),
+			event,
+			{
+				type: type,
+				isSimulated: true
+
+				// Previously, `originalEvent: {}` was set here, so stopPropagation call
+				// would not be triggered on donor event, since in our own
+				// jQuery.event.stopPropagation function we had a check for existence of
+				// originalEvent.stopPropagation method, so, consequently it would be a noop.
+				//
+				// But now, this "simulate" function is used only for events
+				// for which stopPropagation() is noop, so there is no need for that anymore.
+				//
+				// For the 1.x branch though, guard for "click" and "submit"
+				// events is still used, but was moved to jQuery.event.stopPropagation function
+				// because `originalEvent` should point to the original event for the constancy
+				// with other events and for more focused logic
+			}
+		);
+
+		jQuery.event.trigger( e, null, elem );
+
+		if ( e.isDefaultPrevented() ) {
+			event.preventDefault();
+		}
+	}
+
+} );
+
+jQuery.fn.extend( {
+
+	trigger: function( type, data ) {
+		return this.each( function() {
+			jQuery.event.trigger( type, data, this );
+		} );
+	},
+	triggerHandler: function( type, data ) {
+		var elem = this[ 0 ];
+		if ( elem ) {
+			return jQuery.event.trigger( type, data, elem, true );
+		}
+	}
+} );
+
+
+jQuery.each( ( "blur focus focusin focusout load resize scroll unload click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup error contextmenu" ).split( " " ),
+	function( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		return arguments.length > 0 ?
+			this.on( name, null, data, fn ) :
+			this.trigger( name );
+	};
+} );
+
+jQuery.fn.extend( {
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	}
+} );
+
+
+
+
+support.focusin = "onfocusin" in window;
+
+
+// Support: Firefox
+// Firefox doesn't have focus(in | out) events
+// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
+//
+// Support: Chrome, Safari
+// focus(in | out) events fire after focus & blur events,
+// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
+// Related ticket - https://code.google.com/p/chromium/issues/detail?id=449857
+if ( !support.focusin ) {
+	jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler on the document while someone wants focusin/focusout
+		var handler = function( event ) {
+			jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
+		};
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				var doc = this.ownerDocument || this,
+					attaches = dataPriv.access( doc, fix );
+
+				if ( !attaches ) {
+					doc.addEventListener( orig, handler, true );
+				}
+				dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
+			},
+			teardown: function() {
+				var doc = this.ownerDocument || this,
+					attaches = dataPriv.access( doc, fix ) - 1;
+
+				if ( !attaches ) {
+					doc.removeEventListener( orig, handler, true );
+					dataPriv.remove( doc, fix );
+
+				} else {
+					dataPriv.access( doc, fix, attaches );
+				}
+			}
+		};
+	} );
+}
+var location = window.location;
+
+var nonce = jQuery.now();
+
+var rquery = ( /\?/ );
+
+
+
+// Support: Android 2.3
+// Workaround failure to string-cast null input
+jQuery.parseJSON = function( data ) {
+	return JSON.parse( data + "" );
+};
+
+
+// Cross-browser xml parsing
+jQuery.parseXML = function( data ) {
+	var xml;
+	if ( !data || typeof data !== "string" ) {
+		return null;
+	}
+
+	// Support: IE9
+	try {
+		xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
+	} catch ( e ) {
+		xml = undefined;
+	}
+
+	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+		jQuery.error( "Invalid XML: " + data );
+	}
+	return xml;
+};
+
+
+var
+	rhash = /#.*$/,
+	rts = /([?&])_=[^&]*/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = "*/".concat( "*" ),
+
+	// Anchor tag for parsing the document origin
+	originAnchor = document.createElement( "a" );
+	originAnchor.href = location.href;
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		var dataType,
+			i = 0,
+			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
+
+		if ( jQuery.isFunction( func ) ) {
+
+			// For each dataType in the dataTypeExpression
+			while ( ( dataType = dataTypes[ i++ ] ) ) {
+
+				// Prepend if requested
+				if ( dataType[ 0 ] === "+" ) {
+					dataType = dataType.slice( 1 ) || "*";
+					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
+
+				// Otherwise append
+				} else {
+					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
+				}
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+	var inspected = {},
+		seekingTransport = ( structure === transports );
+
+	function inspect( dataType ) {
+		var selected;
+		inspected[ dataType ] = true;
+		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+			if ( typeof dataTypeOrTransport === "string" &&
+				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+
+				options.dataTypes.unshift( dataTypeOrTransport );
+				inspect( dataTypeOrTransport );
+				return false;
+			} else if ( seekingTransport ) {
+				return !( selected = dataTypeOrTransport );
+			}
+		} );
+		return selected;
+	}
+
+	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+
+	return target;
+}
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var ct, type, finalDataType, firstDataType,
+		contents = s.contents,
+		dataTypes = s.dataTypes;
+
+	// Remove auto dataType and get content-type in the process
+	while ( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+	var conv2, current, conv, tmp, prev,
+		converters = {},
+
+		// Work with a copy of dataTypes in case we need to modify it for conversion
+		dataTypes = s.dataTypes.slice();
+
+	// Create converters map with lowercased keys
+	if ( dataTypes[ 1 ] ) {
+		for ( conv in s.converters ) {
+			converters[ conv.toLowerCase() ] = s.converters[ conv ];
+		}
+	}
+
+	current = dataTypes.shift();
+
+	// Convert to each sequential dataType
+	while ( current ) {
+
+		if ( s.responseFields[ current ] ) {
+			jqXHR[ s.responseFields[ current ] ] = response;
+		}
+
+		// Apply the dataFilter if provided
+		if ( !prev && isSuccess && s.dataFilter ) {
+			response = s.dataFilter( response, s.dataType );
+		}
+
+		prev = current;
+		current = dataTypes.shift();
+
+		if ( current ) {
+
+		// There's only work to do if current dataType is non-auto
+			if ( current === "*" ) {
+
+				current = prev;
+
+			// Convert response if prev dataType is non-auto and differs from current
+			} else if ( prev !== "*" && prev !== current ) {
+
+				// Seek a direct converter
+				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+				// If none found, seek a pair
+				if ( !conv ) {
+					for ( conv2 in converters ) {
+
+						// If conv2 outputs current
+						tmp = conv2.split( " " );
+						if ( tmp[ 1 ] === current ) {
+
+							// If prev can be converted to accepted input
+							conv = converters[ prev + " " + tmp[ 0 ] ] ||
+								converters[ "* " + tmp[ 0 ] ];
+							if ( conv ) {
+
+								// Condense equivalence converters
+								if ( conv === true ) {
+									conv = converters[ conv2 ];
+
+								// Otherwise, insert the intermediate dataType
+								} else if ( converters[ conv2 ] !== true ) {
+									current = tmp[ 0 ];
+									dataTypes.unshift( tmp[ 1 ] );
+								}
+								break;
+							}
+						}
+					}
+				}
+
+				// Apply converter (if not an equivalence)
+				if ( conv !== true ) {
+
+					// Unless errors are allowed to bubble, catch and return them
+					if ( conv && s.throws ) {
+						response = conv( response );
+					} else {
+						try {
+							response = conv( response );
+						} catch ( e ) {
+							return {
+								state: "parsererror",
+								error: conv ? e : "No conversion from " + prev + " to " + current
+							};
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return { state: "success", data: response };
+}
+
+jQuery.extend( {
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {},
+
+	ajaxSettings: {
+		url: location.href,
+		type: "GET",
+		isLocal: rlocalProtocol.test( location.protocol ),
+		global: true,
+		processData: true,
+		async: true,
+		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		throws: false,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			"*": allTypes,
+			text: "text/plain",
+			html: "text/html",
+			xml: "application/xml, text/xml",
+			json: "application/json, text/javascript"
+		},
+
+		contents: {
+			xml: /\bxml\b/,
+			html: /\bhtml/,
+			json: /\bjson\b/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText",
+			json: "responseJSON"
+		},
+
+		// Data converters
+		// Keys separate source (or catchall "*") and destination types with a single space
+		converters: {
+
+			// Convert anything to text
+			"* text": String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			url: true,
+			context: true
+		}
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		return settings ?
+
+			// Building a settings object
+			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+			// Extending ajaxSettings
+			ajaxExtend( jQuery.ajaxSettings, target );
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var transport,
+
+			// URL without anti-cache param
+			cacheURL,
+
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+
+			// timeout handle
+			timeoutTimer,
+
+			// Url cleanup var
+			urlAnchor,
+
+			// To know if global events are to be dispatched
+			fireGlobals,
+
+			// Loop variable
+			i,
+
+			// Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+
+			// Callbacks context
+			callbackContext = s.context || s,
+
+			// Context for global events is callbackContext if it is a DOM node or jQuery collection
+			globalEventContext = s.context &&
+				( callbackContext.nodeType || callbackContext.jquery ) ?
+					jQuery( callbackContext ) :
+					jQuery.event,
+
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks( "once memory" ),
+
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+
+			// The jqXHR state
+			state = 0,
+
+			// Default abort message
+			strAbort = "canceled",
+
+			// Fake xhr
+			jqXHR = {
+				readyState: 0,
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match == null ? null : match;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					var lname = name.toLowerCase();
+					if ( !state ) {
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Status-dependent callbacks
+				statusCode: function( map ) {
+					var code;
+					if ( map ) {
+						if ( state < 2 ) {
+							for ( code in map ) {
+
+								// Lazy-add the new callback in a way that preserves old ones
+								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+							}
+						} else {
+
+							// Execute the appropriate callbacks
+							jqXHR.always( map[ jqXHR.status ] );
+						}
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					var finalText = statusText || strAbort;
+					if ( transport ) {
+						transport.abort( finalText );
+					}
+					done( 0, finalText );
+					return this;
+				}
+			};
+
+		// Attach deferreds
+		deferred.promise( jqXHR ).complete = completeDeferred.add;
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (prefilters might expect it)
+		// Handle falsy url in the settings object (#10093: consistency with old signature)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url || location.href ) + "" ).replace( rhash, "" )
+			.replace( rprotocol, location.protocol + "//" );
+
+		// Alias method option to type as per ticket #12004
+		s.type = options.method || options.type || s.method || s.type;
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
+
+		// A cross-domain request is in order when the origin doesn't match the current origin.
+		if ( s.crossDomain == null ) {
+			urlAnchor = document.createElement( "a" );
+
+			// Support: IE8-11+
+			// IE throws exception if url is malformed, e.g. http://example.com:80x/
+			try {
+				urlAnchor.href = s.url;
+
+				// Support: IE8-11+
+				// Anchor's host property isn't correctly set when s.url is relative
+				urlAnchor.href = urlAnchor.href;
+				s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
+					urlAnchor.protocol + "//" + urlAnchor.host;
+			} catch ( e ) {
+
+				// If there is an error parsing the URL, assume it is crossDomain,
+				// it can be rejected by the transport if it is invalid
+				s.crossDomain = true;
+			}
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefilter, stop there
+		if ( state === 2 ) {
+			return jqXHR;
+		}
+
+		// We can fire global events as of now if asked to
+		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
+		fireGlobals = jQuery.event && s.global;
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Save the URL in case we're toying with the If-Modified-Since
+		// and/or If-None-Match header later on
+		cacheURL = s.url;
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+				s.url = rts.test( cacheURL ) ?
+
+					// If there is already a '_' parameter, set its value
+					cacheURL.replace( rts, "$1_=" + nonce++ ) :
+
+					// Otherwise add one to the end
+					cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
+			}
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			if ( jQuery.lastModified[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+			}
+			if ( jQuery.etag[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
+				s.accepts[ s.dataTypes[ 0 ] ] +
+					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend &&
+			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+
+			// Abort if not done already and return
+			return jqXHR.abort();
+		}
+
+		// Aborting is no longer a cancellation
+		strAbort = "abort";
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+
+			// If request was aborted inside ajaxSend, stop there
+			if ( state === 2 ) {
+				return jqXHR;
+			}
+
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = window.setTimeout( function() {
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch ( e ) {
+
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+
+				// Simply rethrow otherwise
+				} else {
+					throw e;
+				}
+			}
+		}
+
+		// Callback for when everything is done
+		function done( status, nativeStatusText, responses, headers ) {
+			var isSuccess, success, error, response, modified,
+				statusText = nativeStatusText;
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				window.clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			// Determine if successful
+			isSuccess = status >= 200 && status < 300 || status === 304;
+
+			// Get response data
+			if ( responses ) {
+				response = ajaxHandleResponses( s, jqXHR, responses );
+			}
+
+			// Convert no matter what (that way responseXXX fields are always set)
+			response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+			// If successful, handle type chaining
+			if ( isSuccess ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+					modified = jqXHR.getResponseHeader( "Last-Modified" );
+					if ( modified ) {
+						jQuery.lastModified[ cacheURL ] = modified;
+					}
+					modified = jqXHR.getResponseHeader( "etag" );
+					if ( modified ) {
+						jQuery.etag[ cacheURL ] = modified;
+					}
+				}
+
+				// if no content
+				if ( status === 204 || s.type === "HEAD" ) {
+					statusText = "nocontent";
+
+				// if not modified
+				} else if ( status === 304 ) {
+					statusText = "notmodified";
+
+				// If we have data, let's convert it
+				} else {
+					statusText = response.state;
+					success = response.data;
+					error = response.error;
+					isSuccess = !error;
+				}
+			} else {
+
+				// Extract error from statusText and normalize for non-aborts
+				error = statusText;
+				if ( status || !statusText ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+					[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	}
+} );
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+
+		// Shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		// The url can be an options object (which then must have .url)
+		return jQuery.ajax( jQuery.extend( {
+			url: url,
+			type: method,
+			dataType: type,
+			data: data,
+			success: callback
+		}, jQuery.isPlainObject( url ) && url ) );
+	};
+} );
+
+
+jQuery._evalUrl = function( url ) {
+	return jQuery.ajax( {
+		url: url,
+
+		// Make this explicit, since user can override this through ajaxSetup (#11264)
+		type: "GET",
+		dataType: "script",
+		async: false,
+		global: false,
+		"throws": true
+	} );
+};
+
+
+jQuery.fn.extend( {
+	wrapAll: function( html ) {
+		var wrap;
+
+		if ( jQuery.isFunction( html ) ) {
+			return this.each( function( i ) {
+				jQuery( this ).wrapAll( html.call( this, i ) );
+			} );
+		}
+
+		if ( this[ 0 ] ) {
+
+			// The elements to wrap the target around
+			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+			if ( this[ 0 ].parentNode ) {
+				wrap.insertBefore( this[ 0 ] );
+			}
+
+			wrap.map( function() {
+				var elem = this;
+
+				while ( elem.firstElementChild ) {
+					elem = elem.firstElementChild;
+				}
+
+				return elem;
+			} ).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each( function( i ) {
+				jQuery( this ).wrapInner( html.call( this, i ) );
+			} );
+		}
+
+		return this.each( function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		} );
+	},
+
+	wrap: function( html ) {
+		var isFunction = jQuery.isFunction( html );
+
+		return this.each( function( i ) {
+			jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html );
+		} );
+	},
+
+	unwrap: function() {
+		return this.parent().each( function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		} ).end();
+	}
+} );
+
+
+jQuery.expr.filters.hidden = function( elem ) {
+	return !jQuery.expr.filters.visible( elem );
+};
+jQuery.expr.filters.visible = function( elem ) {
+
+	// Support: Opera <= 12.12
+	// Opera reports offsetWidths and offsetHeights less than zero on some elements
+	// Use OR instead of AND as the element is not visible if either is true
+	// See tickets #10406 and #13132
+	return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem.getClientRects().length > 0;
+};
+
+
+
+
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+	rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+function buildParams( prefix, obj, traditional, add ) {
+	var name;
+
+	if ( jQuery.isArray( obj ) ) {
+
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+
+				// Item is non-scalar (array or object), encode its numeric index.
+				buildParams(
+					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
+					v,
+					traditional,
+					add
+				);
+			}
+		} );
+
+	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
+
+		// Serialize object item.
+		for ( name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+
+// Serialize an array of form elements or a set of
+// key/values into a query string
+jQuery.param = function( a, traditional ) {
+	var prefix,
+		s = [],
+		add = function( key, value ) {
+
+			// If value is a function, invoke it and return its value
+			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+		};
+
+	// Set traditional to true for jQuery <= 1.3.2 behavior.
+	if ( traditional === undefined ) {
+		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+	}
+
+	// If an array was passed in, assume that it is an array of form elements.
+	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+
+		// Serialize the form elements
+		jQuery.each( a, function() {
+			add( this.name, this.value );
+		} );
+
+	} else {
+
+		// If traditional, encode the "old" way (the way 1.3.2 or older
+		// did it), otherwise encode params recursively.
+		for ( prefix in a ) {
+			buildParams( prefix, a[ prefix ], traditional, add );
+		}
+	}
+
+	// Return the resulting serialization
+	return s.join( "&" ).replace( r20, "+" );
+};
+
+jQuery.fn.extend( {
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+	serializeArray: function() {
+		return this.map( function() {
+
+			// Can add propHook for "elements" to filter or add form elements
+			var elements = jQuery.prop( this, "elements" );
+			return elements ? jQuery.makeArray( elements ) : this;
+		} )
+		.filter( function() {
+			var type = this.type;
+
+			// Use .is( ":disabled" ) so that fieldset[disabled] works
+			return this.name && !jQuery( this ).is( ":disabled" ) &&
+				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+				( this.checked || !rcheckableType.test( type ) );
+		} )
+		.map( function( i, elem ) {
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val ) {
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					} ) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		} ).get();
+	}
+} );
+
+
+jQuery.ajaxSettings.xhr = function() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch ( e ) {}
+};
+
+var xhrSuccessStatus = {
+
+		// File protocol always yields status code 0, assume 200
+		0: 200,
+
+		// Support: IE9
+		// #1450: sometimes IE returns 1223 when it should be 204
+		1223: 204
+	},
+	xhrSupported = jQuery.ajaxSettings.xhr();
+
+support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+support.ajax = xhrSupported = !!xhrSupported;
+
+jQuery.ajaxTransport( function( options ) {
+	var callback, errorCallback;
+
+	// Cross domain only allowed if supported through XMLHttpRequest
+	if ( support.cors || xhrSupported && !options.crossDomain ) {
+		return {
+			send: function( headers, complete ) {
+				var i,
+					xhr = options.xhr();
+
+				xhr.open(
+					options.type,
+					options.url,
+					options.async,
+					options.username,
+					options.password
+				);
+
+				// Apply custom fields if provided
+				if ( options.xhrFields ) {
+					for ( i in options.xhrFields ) {
+						xhr[ i ] = options.xhrFields[ i ];
+					}
+				}
+
+				// Override mime type if needed
+				if ( options.mimeType && xhr.overrideMimeType ) {
+					xhr.overrideMimeType( options.mimeType );
+				}
+
+				// X-Requested-With header
+				// For cross-domain requests, seeing as conditions for a preflight are
+				// akin to a jigsaw puzzle, we simply never set it to be sure.
+				// (it can always be set on a per-request basis or even using ajaxSetup)
+				// For same-domain requests, won't change header if already provided.
+				if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
+					headers[ "X-Requested-With" ] = "XMLHttpRequest";
+				}
+
+				// Set headers
+				for ( i in headers ) {
+					xhr.setRequestHeader( i, headers[ i ] );
+				}
+
+				// Callback
+				callback = function( type ) {
+					return function() {
+						if ( callback ) {
+							callback = errorCallback = xhr.onload =
+								xhr.onerror = xhr.onabort = xhr.onreadystatechange = null;
+
+							if ( type === "abort" ) {
+								xhr.abort();
+							} else if ( type === "error" ) {
+
+								// Support: IE9
+								// On a manual native abort, IE9 throws
+								// errors on any property access that is not readyState
+								if ( typeof xhr.status !== "number" ) {
+									complete( 0, "error" );
+								} else {
+									complete(
+
+										// File: protocol always yields status 0; see #8605, #14207
+										xhr.status,
+										xhr.statusText
+									);
+								}
+							} else {
+								complete(
+									xhrSuccessStatus[ xhr.status ] || xhr.status,
+									xhr.statusText,
+
+									// Support: IE9 only
+									// IE9 has no XHR2 but throws on binary (trac-11426)
+									// For XHR2 non-text, let the caller handle it (gh-2498)
+									( xhr.responseType || "text" ) !== "text"  ||
+									typeof xhr.responseText !== "string" ?
+										{ binary: xhr.response } :
+										{ text: xhr.responseText },
+									xhr.getAllResponseHeaders()
+								);
+							}
+						}
+					};
+				};
+
+				// Listen to events
+				xhr.onload = callback();
+				errorCallback = xhr.onerror = callback( "error" );
+
+				// Support: IE9
+				// Use onreadystatechange to replace onabort
+				// to handle uncaught aborts
+				if ( xhr.onabort !== undefined ) {
+					xhr.onabort = errorCallback;
+				} else {
+					xhr.onreadystatechange = function() {
+
+						// Check readyState before timeout as it changes
+						if ( xhr.readyState === 4 ) {
+
+							// Allow onerror to be called first,
+							// but that will not handle a native abort
+							// Also, save errorCallback to a variable
+							// as xhr.onerror cannot be accessed
+							window.setTimeout( function() {
+								if ( callback ) {
+									errorCallback();
+								}
+							} );
+						}
+					};
+				}
+
+				// Create the abort callback
+				callback = callback( "abort" );
+
+				try {
+
+					// Do send the request (this may raise an exception)
+					xhr.send( options.hasContent && options.data || null );
+				} catch ( e ) {
+
+					// #14683: Only rethrow if this hasn't been notified as an error yet
+					if ( callback ) {
+						throw e;
+					}
+				}
+			},
+
+			abort: function() {
+				if ( callback ) {
+					callback();
+				}
+			}
+		};
+	}
+} );
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup( {
+	accepts: {
+		script: "text/javascript, application/javascript, " +
+			"application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /\b(?:java|ecma)script\b/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+} );
+
+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+	}
+} );
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+		var script, callback;
+		return {
+			send: function( _, complete ) {
+				script = jQuery( "<script>" ).prop( {
+					charset: s.scriptCharset,
+					src: s.url
+				} ).on(
+					"load error",
+					callback = function( evt ) {
+						script.remove();
+						callback = null;
+						if ( evt ) {
+							complete( evt.type === "error" ? 404 : 200, evt.type );
+						}
+					}
+				);
+
+				// Use native DOM manipulation to avoid our domManip AJAX trickery
+				document.head.appendChild( script[ 0 ] );
+			},
+			abort: function() {
+				if ( callback ) {
+					callback();
+				}
+			}
+		};
+	}
+} );
+
+
+
+
+var oldCallbacks = [],
+	rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup( {
+	jsonp: "callback",
+	jsonpCallback: function() {
+		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+		this[ callback ] = true;
+		return callback;
+	}
+} );
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var callbackName, overwritten, responseContainer,
+		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+			"url" :
+			typeof s.data === "string" &&
+				( s.contentType || "" )
+					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
+				rjsonp.test( s.data ) && "data"
+		);
+
+	// Handle iff the expected data type is "jsonp" or we have a parameter to set
+	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+		// Get callback name, remembering preexisting value associated with it
+		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+			s.jsonpCallback() :
+			s.jsonpCallback;
+
+		// Insert callback into url or form data
+		if ( jsonProp ) {
+			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+		} else if ( s.jsonp !== false ) {
+			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+		}
+
+		// Use data converter to retrieve json after script execution
+		s.converters[ "script json" ] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( callbackName + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// Force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Install callback
+		overwritten = window[ callbackName ];
+		window[ callbackName ] = function() {
+			responseContainer = arguments;
+		};
+
+		// Clean-up function (fires after converters)
+		jqXHR.always( function() {
+
+			// If previous value didn't exist - remove it
+			if ( overwritten === undefined ) {
+				jQuery( window ).removeProp( callbackName );
+
+			// Otherwise restore preexisting value
+			} else {
+				window[ callbackName ] = overwritten;
+			}
+
+			// Save back as free
+			if ( s[ callbackName ] ) {
+
+				// Make sure that re-using the options doesn't screw things around
+				s.jsonpCallback = originalSettings.jsonpCallback;
+
+				// Save the callback name for future use
+				oldCallbacks.push( callbackName );
+			}
+
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+				overwritten( responseContainer[ 0 ] );
+			}
+
+			responseContainer = overwritten = undefined;
+		} );
+
+		// Delegate to script
+		return "script";
+	}
+} );
+
+
+
+
+// Support: Safari 8+
+// In Safari 8 documents created via document.implementation.createHTMLDocument
+// collapse sibling forms: the second one becomes a child of the first one.
+// Because of that, this security measure has to be disabled in Safari 8.
+// https://bugs.webkit.org/show_bug.cgi?id=137337
+support.createHTMLDocument = ( function() {
+	var body = document.implementation.createHTMLDocument( "" ).body;
+	body.innerHTML = "<form></form><form></form>";
+	return body.childNodes.length === 2;
+} )();
+
+
+// Argument "data" should be string of html
+// context (optional): If specified, the fragment will be created in this context,
+// defaults to document
+// keepScripts (optional): If true, will include scripts passed in the html string
+jQuery.parseHTML = function( data, context, keepScripts ) {
+	if ( !data || typeof data !== "string" ) {
+		return null;
+	}
+	if ( typeof context === "boolean" ) {
+		keepScripts = context;
+		context = false;
+	}
+
+	// Stop scripts or inline event handlers from being executed immediately
+	// by using document.implementation
+	context = context || ( support.createHTMLDocument ?
+		document.implementation.createHTMLDocument( "" ) :
+		document );
+
+	var parsed = rsingleTag.exec( data ),
+		scripts = !keepScripts && [];
+
+	// Single tag
+	if ( parsed ) {
+		return [ context.createElement( parsed[ 1 ] ) ];
+	}
+
+	parsed = buildFragment( [ data ], context, scripts );
+
+	if ( scripts && scripts.length ) {
+		jQuery( scripts ).remove();
+	}
+
+	return jQuery.merge( [], parsed.childNodes );
+};
+
+
+// Keep a copy of the old load method
+var _load = jQuery.fn.load;
+
+/**
+ * Load a url into a page
+ */
+jQuery.fn.load = function( url, params, callback ) {
+	if ( typeof url !== "string" && _load ) {
+		return _load.apply( this, arguments );
+	}
+
+	var selector, type, response,
+		self = this,
+		off = url.indexOf( " " );
+
+	if ( off > -1 ) {
+		selector = jQuery.trim( url.slice( off ) );
+		url = url.slice( 0, off );
+	}
+
+	// If it's a function
+	if ( jQuery.isFunction( params ) ) {
+
+		// We assume that it's the callback
+		callback = params;
+		params = undefined;
+
+	// Otherwise, build a param string
+	} else if ( params && typeof params === "object" ) {
+		type = "POST";
+	}
+
+	// If we have elements to modify, make the request
+	if ( self.length > 0 ) {
+		jQuery.ajax( {
+			url: url,
+
+			// If "type" variable is undefined, then "GET" method will be used.
+			// Make value of this field explicit since
+			// user can override it through ajaxSetup method
+			type: type || "GET",
+			dataType: "html",
+			data: params
+		} ).done( function( responseText ) {
+
+			// Save response for use in complete callback
+			response = arguments;
+
+			self.html( selector ?
+
+				// If a selector was specified, locate the right elements in a dummy div
+				// Exclude scripts to avoid IE 'Permission Denied' errors
+				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+				// Otherwise use the full result
+				responseText );
+
+		// If the request succeeds, this function gets "data", "status", "jqXHR"
+		// but they are ignored because response was set above.
+		// If it fails, this function gets "jqXHR", "status", "error"
+		} ).always( callback && function( jqXHR, status ) {
+			self.each( function() {
+				callback.apply( self, response || [ jqXHR.responseText, status, jqXHR ] );
+			} );
+		} );
+	}
+
+	return this;
+};
+
+
+
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [
+	"ajaxStart",
+	"ajaxStop",
+	"ajaxComplete",
+	"ajaxError",
+	"ajaxSuccess",
+	"ajaxSend"
+], function( i, type ) {
+	jQuery.fn[ type ] = function( fn ) {
+		return this.on( type, fn );
+	};
+} );
+
+
+
+
+jQuery.expr.filters.animated = function( elem ) {
+	return jQuery.grep( jQuery.timers, function( fn ) {
+		return elem === fn.elem;
+	} ).length;
+};
+
+
+
+
+/**
+ * Gets a window from an element
+ */
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
+}
+
+jQuery.offset = {
+	setOffset: function( elem, options, i ) {
+		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+			position = jQuery.css( elem, "position" ),
+			curElem = jQuery( elem ),
+			props = {};
+
+		// Set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		curOffset = curElem.offset();
+		curCSSTop = jQuery.css( elem, "top" );
+		curCSSLeft = jQuery.css( elem, "left" );
+		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+			( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
+
+		// Need to be able to calculate position if either
+		// top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+
+			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
+			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+jQuery.fn.extend( {
+	offset: function( options ) {
+		if ( arguments.length ) {
+			return options === undefined ?
+				this :
+				this.each( function( i ) {
+					jQuery.offset.setOffset( this, options, i );
+				} );
+		}
+
+		var docElem, win,
+			elem = this[ 0 ],
+			box = { top: 0, left: 0 },
+			doc = elem && elem.ownerDocument;
+
+		if ( !doc ) {
+			return;
+		}
+
+		docElem = doc.documentElement;
+
+		// Make sure it's not a disconnected DOM node
+		if ( !jQuery.contains( docElem, elem ) ) {
+			return box;
+		}
+
+		box = elem.getBoundingClientRect();
+		win = getWindow( doc );
+		return {
+			top: box.top + win.pageYOffset - docElem.clientTop,
+			left: box.left + win.pageXOffset - docElem.clientLeft
+		};
+	},
+
+	position: function() {
+		if ( !this[ 0 ] ) {
+			return;
+		}
+
+		var offsetParent, offset,
+			elem = this[ 0 ],
+			parentOffset = { top: 0, left: 0 };
+
+		// Fixed elements are offset from window (parentOffset = {top:0, left: 0},
+		// because it is its only offset parent
+		if ( jQuery.css( elem, "position" ) === "fixed" ) {
+
+			// Assume getBoundingClientRect is there when computed position is fixed
+			offset = elem.getBoundingClientRect();
+
+		} else {
+
+			// Get *real* offsetParent
+			offsetParent = this.offsetParent();
+
+			// Get correct offsets
+			offset = this.offset();
+			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+				parentOffset = offsetParent.offset();
+			}
+
+			// Add offsetParent borders
+			parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+		}
+
+		// Subtract parent offsets and element margins
+		return {
+			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+		};
+	},
+
+	// This method will return documentElement in the following cases:
+	// 1) For the element inside the iframe without offsetParent, this method will return
+	//    documentElement of the parent window
+	// 2) For the hidden or detached element
+	// 3) For body or html element, i.e. in case of the html node - it will return itself
+	//
+	// but those exceptions were never presented as a real life use-cases
+	// and might be considered as more preferable results.
+	//
+	// This logic, however, is not guaranteed and can change at any point in the future
+	offsetParent: function() {
+		return this.map( function() {
+			var offsetParent = this.offsetParent;
+
+			while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+
+			return offsetParent || documentElement;
+		} );
+	}
+} );
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+	var top = "pageYOffset" === prop;
+
+	jQuery.fn[ method ] = function( val ) {
+		return access( this, function( elem, method, val ) {
+			var win = getWindow( elem );
+
+			if ( val === undefined ) {
+				return win ? win[ prop ] : elem[ method ];
+			}
+
+			if ( win ) {
+				win.scrollTo(
+					!top ? val : win.pageXOffset,
+					top ? val : win.pageYOffset
+				);
+
+			} else {
+				elem[ method ] = val;
+			}
+		}, method, val, arguments.length );
+	};
+} );
+
+// Support: Safari<7-8+, Chrome<37-44+
+// Add the top/left cssHooks using jQuery.fn.position
+// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+// Blink bug: https://code.google.com/p/chromium/issues/detail?id=229280
+// getComputedStyle returns percent when specified for top/left/bottom/right;
+// rather than make the css module depend on the offset module, just check for it here
+jQuery.each( [ "top", "left" ], function( i, prop ) {
+	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+		function( elem, computed ) {
+			if ( computed ) {
+				computed = curCSS( elem, prop );
+
+				// If curCSS returns percentage, fallback to offset
+				return rnumnonpx.test( computed ) ?
+					jQuery( elem ).position()[ prop ] + "px" :
+					computed;
+			}
+		}
+	);
+} );
+
+
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
+		function( defaultExtra, funcName ) {
+
+		// Margin is only for outerHeight, outerWidth
+		jQuery.fn[ funcName ] = function( margin, value ) {
+			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+			return access( this, function( elem, type, value ) {
+				var doc;
+
+				if ( jQuery.isWindow( elem ) ) {
+
+					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+					// isn't a whole lot we can do. See pull request at this URL for discussion:
+					// https://github.com/jquery/jquery/pull/764
+					return elem.document.documentElement[ "client" + name ];
+				}
+
+				// Get document width or height
+				if ( elem.nodeType === 9 ) {
+					doc = elem.documentElement;
+
+					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+					// whichever is greatest
+					return Math.max(
+						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+						elem.body[ "offset" + name ], doc[ "offset" + name ],
+						doc[ "client" + name ]
+					);
+				}
+
+				return value === undefined ?
+
+					// Get width or height on the element, requesting but not forcing parseFloat
+					jQuery.css( elem, type, extra ) :
+
+					// Set width or height on the element
+					jQuery.style( elem, type, value, extra );
+			}, type, chainable ? margin : undefined, chainable, null );
+		};
+	} );
+} );
+
+
+jQuery.fn.extend( {
+
+	bind: function( types, data, fn ) {
+		return this.on( types, null, data, fn );
+	},
+	unbind: function( types, fn ) {
+		return this.off( types, null, fn );
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.on( types, selector, data, fn );
+	},
+	undelegate: function( selector, types, fn ) {
+
+		// ( namespace ) or ( selector, types [, fn] )
+		return arguments.length === 1 ?
+			this.off( selector, "**" ) :
+			this.off( types, selector || "**", fn );
+	},
+	size: function() {
+		return this.length;
+	}
+} );
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+
+
+
+// Register as a named AMD module, since jQuery can be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Lowercase jquery is used because AMD module names are
+// derived from file names, and jQuery is normally delivered in a lowercase
+// file name. Do this after creating the global so that if an AMD module wants
+// to call noConflict to hide this version of jQuery, it will work.
+
+// Note that for maximum portability, libraries that are not jQuery should
+// declare themselves as anonymous modules, and avoid setting a global if an
+// AMD loader is present. jQuery is a special case. For more information, see
+// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+if ( typeof define === "function" && define.amd ) {
+	define( "jquery", [], function() {
+		return jQuery;
+	} );
+}
+
+
+
+var
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$;
+
+jQuery.noConflict = function( deep ) {
+	if ( window.$ === jQuery ) {
+		window.$ = _$;
+	}
+
+	if ( deep && window.jQuery === jQuery ) {
+		window.jQuery = _jQuery;
+	}
+
+	return jQuery;
+};
+
+// Expose jQuery and $ identifiers, even in AMD
+// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (#13566)
+if ( !noGlobal ) {
+	window.jQuery = window.$ = jQuery;
+}
+
+return jQuery;
+}));

+ 8585 - 0
static/wangEditor/js/wangEditor.js

@@ -0,0 +1,8585 @@
+(function (factory) {
+    if (typeof window.define === 'function') {
+        if (window.define.amd) {
+            // AMD模式
+            window.define('wangEditor', ["jquery"], factory);
+        } else if (window.define.cmd) {
+            // CMD模式
+            window.define(function (require, exports, module) {
+                return factory;
+            });
+        } else {
+            // 全局模式
+            factory(window.jQuery);
+        }
+    } else if (typeof module === "object" && typeof module.exports === "object") {
+        // commonjs
+
+        // 引用 css —— webapck
+        require('../css/wangEditor.css');
+        module.exports = factory(
+            // 传入 jquery ,支持使用 npm 方式或者自己定义jquery的路径
+            require('jquery')
+        );
+    } else {
+        // 全局模式
+        factory(window.jQuery);
+    }
+})(function($){
+    
+    // 验证是否引用jquery
+    if (!$ || !$.fn || !$.fn.jquery) {
+        alert('在引用wangEditor.js之前,先引用jQuery,否则无法使用 wangEditor');
+        return;
+    }
+
+    // 定义扩展函数
+    var _e = function (fn) {
+        var E = window.wangEditor;
+        if (E) {
+            // 执行传入的函数
+            fn(E, $);
+        }
+    };
+// 定义构造函数
+(function (window, $) {
+    if (window.wangEditor) {
+        // 重复引用
+        alert('一个页面不能重复引用 wangEditor.js 或 wangEditor.min.js !!!');
+        return;
+    }
+
+    // 编辑器(整体)构造函数
+    var E = function (elem) {
+        // 支持 id 和 element 两种形式
+        if (typeof elem === 'string') {
+            elem = '#' + elem;
+        }
+
+        // ---------------获取基本节点------------------
+        var $elem = $(elem);
+        if ($elem.length !== 1) {
+            return;
+        }
+        var nodeName = $elem[0].nodeName;
+        if (nodeName !== 'TEXTAREA' && nodeName !== 'DIV') {
+            // 只能是 textarea 和 div ,其他类型的元素不行
+            return;   
+        }
+        this.valueNodeName = nodeName.toLowerCase();
+        this.$valueContainer = $elem;
+
+        // 记录 elem 的 prev 和 parent(最后渲染 editor 要用到)
+        this.$prev = $elem.prev();
+        this.$parent = $elem.parent();
+
+        // ------------------初始化------------------
+        this.init();
+    };
+
+    E.fn = E.prototype;
+
+    E.$body = $('body');
+    E.$document = $(document);
+    E.$window = $(window);
+    E.userAgent = navigator.userAgent;
+    E.getComputedStyle = window.getComputedStyle;
+    E.w3cRange = typeof document.createRange === 'function';
+    E.hostname = location.hostname.toLowerCase();
+    E.websiteHost = 'wangeditor.github.io|www.wangeditor.com|wangeditor.coding.me';
+    E.isOnWebsite = E.websiteHost.indexOf(E.hostname) >= 0;
+    E.docsite = 'http://www.kancloud.cn/wangfupeng/wangeditor2/113961';
+
+    // 暴露给全局对象
+    window.wangEditor = E;
+
+    // 注册 plugin 事件,用于用户自定义插件
+    // 用户在引用 wangEditor.js 之后,还可以通过 E.plugin() 注入自定义函数,
+    // 该函数将会在 editor.create() 方法的最后一步执行
+    E.plugin = function (fn) {
+        if (!E._plugins) {
+            E._plugins = [];
+        }
+
+        if (typeof fn === 'function') {
+            E._plugins.push(fn);
+        }
+    };
+
+})(window, $);
+// editor 绑定事件
+_e(function (E, $) {
+
+    E.fn.init = function () {
+
+        // 初始化 editor 默认配置
+        this.initDefaultConfig();
+
+        // 增加container
+        this.addEditorContainer();
+
+        // 增加编辑区域
+        this.addTxt();
+
+        // 增加menuContainer
+        this.addMenuContainer();
+
+        // 初始化菜单集合
+        this.menus = {};
+
+        // 初始化commandHooks
+        this.commandHooks();
+
+    };
+
+});
+// editor api
+_e(function (E, $) {
+
+    // 预定义 ready 事件
+    E.fn.ready = function (fn) {
+
+        if (!this.readyFns) {
+            this.readyFns = [];
+        }
+
+        this.readyFns.push(fn);
+    };
+
+    // 处理ready事件
+    E.fn.readyHeadler = function () {
+        var fns = this.readyFns;
+
+        while (fns.length) {
+            fns.shift().call(this);
+        }
+    };
+
+    // 更新内容到 $valueContainer
+    E.fn.updateValue = function () {
+        var editor = this;
+        var $valueContainer = editor.$valueContainer;
+        var $txt = editor.txt.$txt;
+
+        if ($valueContainer === $txt) {
+            // 传入生成编辑器的div,即是编辑区域
+            return;
+        }
+
+        var value = $txt.html();
+        $valueContainer.val(value);
+    };
+
+    // 获取初始化的内容
+    E.fn.getInitValue = function () {
+        var editor = this;
+        var $valueContainer = editor.$valueContainer;
+        var currentValue = '';
+        var nodeName = editor.valueNodeName;
+        if (nodeName === 'div') {
+            currentValue = $valueContainer.html();
+        } else if (nodeName === 'textarea') {
+            currentValue = $valueContainer.val();
+        }
+
+        return currentValue;
+    };
+
+    // 触发菜单updatestyle
+    E.fn.updateMenuStyle = function () {
+        var menus = this.menus;
+
+        $.each(menus, function (k, menu) {
+            menu.updateSelected();
+        });
+    };
+
+    // 除了传入的 menuIds,其他全部启用
+    E.fn.enableMenusExcept = function (menuIds) {
+        if (this._disabled) {
+            // 编辑器处于禁用状态,则不执行改操作
+            return;
+        }
+        // menuIds参数:支持数组和字符串
+        menuIds = menuIds || [];
+        if (typeof menuIds === 'string') {
+            menuIds = [menuIds];
+        }
+
+        $.each(this.menus, function (k, menu) {
+            if (menuIds.indexOf(k) >= 0) {
+                return;
+            }
+            menu.disabled(false);
+        });
+    };
+
+    // 除了传入的 menuIds,其他全部禁用
+    E.fn.disableMenusExcept = function (menuIds) {
+        if (this._disabled) {
+            // 编辑器处于禁用状态,则不执行改操作
+            return;
+        }
+        // menuIds参数:支持数组和字符串
+        menuIds = menuIds || [];
+        if (typeof menuIds === 'string') {
+            menuIds = [menuIds];
+        }
+
+        $.each(this.menus, function (k, menu) {
+            if (menuIds.indexOf(k) >= 0) {
+                return;
+            }
+            menu.disabled(true);
+        });
+    };
+
+    // 隐藏所有 dropPanel droplist modal
+    E.fn.hideDropPanelAndModal = function () {
+        var menus = this.menus;
+
+        $.each(menus, function (k, menu) {
+            var m = menu.dropPanel || menu.dropList || menu.modal;
+            if (m && m.hide) {
+                m.hide();
+            }
+        });
+    };
+
+});
+// selection range API
+_e(function (E, $) {
+
+    // 用到 w3c range 的函数,如果检测到浏览器不支持 w3c range,则赋值为空函数
+    var ieRange = !E.w3cRange;
+    function emptyFn() {}
+
+    // 设置或读取当前的range
+    E.fn.currentRange = function (cr){
+        if (cr) {
+            this._rangeData = cr;
+        } else {
+            return this._rangeData;
+        }
+    };
+
+    // 将当前选区折叠
+    E.fn.collapseRange = function (range, opt) {
+        // opt 参数说明:'start'-折叠到开始; 'end'-折叠到结束
+        opt = opt || 'end';
+        opt = opt === 'start' ? true : false;
+
+        range = range || this.currentRange();
+        
+        if (range) {
+            // 合并,保存
+            range.collapse(opt);
+            this.currentRange(range);
+        }
+    };
+
+    // 获取选区的文字
+    E.fn.getRangeText = ieRange ? emptyFn : function (range) {
+        range = range || this.currentRange();
+        if (!range) {
+            return;
+        }
+        return range.toString();
+    };
+
+    // 获取选区对应的DOM对象
+    E.fn.getRangeElem = ieRange ? emptyFn : function (range) {
+        range = range || this.currentRange();
+        var dom = range.commonAncestorContainer;
+
+        if (dom.nodeType === 1) {
+            return dom;
+        } else {
+            return dom.parentNode;
+        }
+    };
+
+    // 选区内容是否为空?
+    E.fn.isRangeEmpty = ieRange ? emptyFn : function (range) {
+        range = range || this.currentRange();
+
+        if (range && range.startContainer) {
+            if (range.startContainer === range.endContainer) {
+                if (range.startOffset === range.endOffset) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    };
+
+    // 保存选区数据
+    E.fn.saveSelection = ieRange ? emptyFn : function (range) {
+        var self = this,
+            _parentElem,
+            selection,
+            txt = self.txt.$txt.get(0);
+
+        if (range) {
+            _parentElem = range.commonAncestorContainer;
+        } else {
+            selection = document.getSelection();
+            if (selection.getRangeAt && selection.rangeCount) {
+                range = document.getSelection().getRangeAt(0);
+                _parentElem = range.commonAncestorContainer;
+            }
+        }
+        // 确定父元素一定要包含在编辑器区域内
+        if (_parentElem && ($.contains(txt, _parentElem) || txt === _parentElem) ) {
+            // 保存选择区域
+            self.currentRange(range);
+        }
+    };
+
+    // 恢复选中区域
+    E.fn.restoreSelection = ieRange ? emptyFn : function (range) {
+        var selection;
+
+        range = range || this.currentRange();
+
+        if (!range) {
+            return;
+        }
+
+        // 使用 try catch 来防止 IE 某些情况报错
+        try {
+            selection = document.getSelection();
+            selection.removeAllRanges();
+            selection.addRange(range);
+        } catch (ex) {
+            E.error('执行 editor.restoreSelection 时,IE可能会有异常,不影响使用');
+        }
+    };
+
+    // 根据elem恢复选区
+    E.fn.restoreSelectionByElem = ieRange ? emptyFn : function (elem, opt) {
+        // opt参数说明:'start'-折叠到开始,'end'-折叠到结束,'all'-全部选中
+        if (!elem) {
+            return;
+        }
+        opt = opt || 'end'; // 默认为折叠到结束
+
+        // 根据elem获取选区
+        this.setRangeByElem(elem);
+
+        // 根据 opt 折叠选区
+        if (opt === 'start') {
+            this.collapseRange(this.currentRange(), 'start');
+        }
+        if (opt === 'end') {
+            this.collapseRange(this.currentRange(), 'end');
+        }
+        
+        // 恢复选区
+        this.restoreSelection();
+    };
+
+    // 初始化选区
+    E.fn.initSelection = ieRange ? emptyFn : function () {
+        var editor = this;
+        if( editor.currentRange() ){
+            //如果currentRange有值,则不用再初始化
+            return;
+        }
+
+        var range;
+        var $txt = editor.txt.$txt;
+        var $firstChild = $txt.children().first();
+        
+        if ($firstChild.length) {
+            editor.restoreSelectionByElem($firstChild.get(0));
+        }
+    };
+
+    // 根据元素创建选区
+    E.fn.setRangeByElem = ieRange ? emptyFn : function (elem) {
+        var editor = this;
+        var txtElem = editor.txt.$txt.get(0);
+        if (!elem || !$.contains(txtElem, elem)) {
+            return;
+        }
+
+        // 找到elem的第一个 textNode 和 最后一个 textNode
+        var firstTextNode = elem.firstChild;
+        while (firstTextNode) {
+            if (firstTextNode.nodeType === 3) {
+                break;
+            }
+            // 继续向下
+            firstTextNode = firstTextNode.firstChild;
+        }
+        var lastTextNode = elem.lastChild;
+        while (lastTextNode) {
+            if (lastTextNode.nodeType === 3) {
+                break;
+            }
+            // 继续向下
+            lastTextNode = lastTextNode.lastChild;
+        }
+        
+        var range = document.createRange();
+        if (firstTextNode && lastTextNode) {
+            // 说明 elem 有内容,能取到子元素
+            range.setStart(firstTextNode, 0);
+            range.setEnd(lastTextNode, lastTextNode.textContent.length);
+        } else {
+            // 说明 elem 无内容
+            range.setStart(elem, 0);
+            range.setEnd(elem, 0);
+        }
+
+        // 保存选区
+        editor.saveSelection(range);
+    };
+
+});
+// selection range API - IE8及以下
+_e(function (E, $) {
+
+    if (E.w3cRange) {
+        // 说明支持 W3C 的range方法
+        return;
+    }
+
+    // -----------------IE8时,需要重写以下方法-------------------
+
+    // 获取选区的文字
+    E.fn.getRangeText = function (range) {
+        range = range || this.currentRange();
+        if (!range) {
+            return;
+        }
+        return range.text;
+    };
+
+    // 获取选区对应的DOM对象
+    E.fn.getRangeElem = function (range) {
+        range = range || this.currentRange();
+        if (!range) {
+            return;
+        }
+        var dom = range.parentElement();
+
+        if (dom.nodeType === 1) {
+            return dom;
+        } else {
+            return dom.parentNode;
+        }
+    };
+
+    // 选区内容是否为空?
+    E.fn.isRangeEmpty = function (range) {
+        range = range || this.currentRange();
+
+        if (range && range.text) {
+            return false;
+        }
+
+        return true;
+    };
+
+    // 保存选区数据
+    E.fn.saveSelection = function (range) {
+        var self = this,
+            _parentElem,
+            selection,
+            txt = self.txt.$txt.get(0);
+
+        if (range) {
+            _parentElem = range.parentElement();
+        } else {
+            range = document.selection.createRange();
+            if(typeof range.parentElement === 'undefined'){
+                //IE6、7中,insertImage后会执行此处
+                //由于找不到range.parentElement,所以干脆将_parentElem赋值为null
+                _parentElem = null;
+            }else{
+                _parentElem = range.parentElement();
+            }
+        }
+
+        // 确定父元素一定要包含在编辑器区域内
+        if (_parentElem && ($.contains(txt, _parentElem) || txt === _parentElem) ) {
+            // 保存选择区域
+            self.currentRange(range);
+        }
+    };
+
+    // 恢复选中区域
+    E.fn.restoreSelection = function (currentRange){
+        var editor = this,
+            selection,
+            range;
+
+        currentRange = currentRange || editor.currentRange();
+        if(!currentRange){
+            return;
+        }
+
+        range = document.selection.createRange();
+        try {
+            // 此处,plupload上传上传图片时,IE8-会报一个『参数无效』的错误
+            range.setEndPoint('EndToEnd', currentRange);
+        } catch (ex) {
+
+        }
+        
+        if(currentRange.text.length === 0){
+            try {
+                // IE8 插入表情会报错
+                range.collapse(false);
+            } catch (ex) {
+                
+            }
+            
+        }else{
+            range.setEndPoint('StartToStart', currentRange);
+        }
+        range.select();
+    };
+
+});
+// editor command hooks
+_e(function (E, $) {
+    
+    E.fn.commandHooks = function () {
+        var editor = this;
+        var commandHooks = {};
+        
+        // insertHtml
+        commandHooks.insertHtml = function (html) {
+            var $elem = $(html);
+            var rangeElem = editor.getRangeElem();
+            var targetElem;
+            
+            targetElem = editor.getLegalTags(rangeElem);
+            if (!targetElem) {
+                return;
+            }
+
+            $(targetElem).after($elem);
+        };
+
+        // 保存到对象
+        editor.commandHooks = commandHooks;
+    };
+
+});
+// editor command API
+_e(function (E, $) {
+
+    // 基本命令
+    E.fn.command = function (e, commandName, commandValue, callback) {
+        var editor = this;
+        var hooks;
+        
+        function commandFn() {
+            if (!commandName) {
+                return;
+            }
+            if (editor.queryCommandSupported(commandName)) {
+                // 默认命令
+                document.execCommand(commandName, false, commandValue);
+            } else {
+                // hooks 命令
+                hooks = editor.commandHooks;
+                if (commandName in hooks) {
+                    hooks[commandName](commandValue);
+                }
+            }
+        }
+
+        this.customCommand(e, commandFn, callback);
+    };
+
+    // 针对一个elem对象执行基础命令
+    E.fn.commandForElem = function (elemOpt, e, commandName, commandValue, callback) {
+        // 取得查询elem的查询条件和验证函数
+        var selector;
+        var check;
+        if (typeof elemOpt === 'string') {
+            selector = elemOpt;
+        } else {
+            selector = elemOpt.selector;
+            check = elemOpt.check;
+        }
+
+        // 查询elem
+        var rangeElem = this.getRangeElem();
+        rangeElem = this.getSelfOrParentByName(rangeElem, selector, check);
+
+        // 根据elem设置range
+        if (rangeElem) {
+            this.setRangeByElem(rangeElem);
+        }
+
+        // 然后执行基础命令
+        this.command(e, commandName, commandValue, callback);
+    };
+
+    // 自定义命令
+    E.fn.customCommand = function (e, commandFn, callback) {
+        var editor = this;
+        var range = editor.currentRange();
+
+        if (!range) {
+            // 目前没有选区,则无法执行命令
+            e && e.preventDefault();
+            return;
+        }
+        // 记录内容,以便撤销(执行命令之前就要记录)
+        editor.undoRecord();
+
+        // 恢复选区(有 range 参数)
+        this.restoreSelection(range);
+
+        // 执行命令事件
+        commandFn.call(editor);
+
+        // 保存选区(无参数,要从浏览器直接获取range信息)
+        this.saveSelection();
+        // 重新恢复选区(无参数,要取得刚刚从浏览器得到的range信息)
+        this.restoreSelection();
+
+        // 执行 callback
+        if (callback && typeof callback === 'function') {
+            callback.call(editor);
+        }
+
+        // 最后插入空行
+        editor.txt.insertEmptyP();
+
+        // 包裹暴露的img和text
+        editor.txt.wrapImgAndText();
+
+        // 更新内容
+        editor.updateValue();
+
+        // 更新菜单样式
+        editor.updateMenuStyle();
+
+        // 隐藏 dropPanel dropList modal  设置 200ms 间隔
+        function hidePanelAndModal() {
+            editor.hideDropPanelAndModal();
+        } 
+        setTimeout(hidePanelAndModal, 200);
+
+        if (e) {
+            e.preventDefault();
+        }
+    };
+
+    // 封装 document.queryCommandValue 函数
+    // IE8 直接执行偶尔会报错,因此直接用 try catch 封装一下
+    E.fn.queryCommandValue = function (commandName) {
+        var result = '';
+        try {
+            result = document.queryCommandValue(commandName);
+        } catch (ex) {
+
+        }
+        return result;
+    };
+
+    // 封装 document.queryCommandState 函数
+    // IE8 直接执行偶尔会报错,因此直接用 try catch 封装一下
+    E.fn.queryCommandState = function (commandName) {
+        var result = false;
+        try {
+            result = document.queryCommandState(commandName);
+        } catch (ex) {
+
+        }
+        return result;
+    };
+
+    // 封装 document.queryCommandSupported 函数
+    E.fn.queryCommandSupported = function (commandName) {
+        var result = false;
+        try {
+            result = document.queryCommandSupported(commandName);
+        } catch (ex) {
+
+        }
+        return result;
+    };
+
+});
+// dom selector
+_e(function (E, $) {
+
+    var matchesSelector;
+
+    // matchesSelector hook
+    function _matchesSelectorForIE(selector) {
+        var elem = this;
+        var $elems = $(selector);
+        var result = false;
+
+        // 用jquery查找 selector 所有对象,如果其中有一个和传入 elem 相同,则证明 elem 符合 selector
+        $elems.each(function () {
+            if (this === elem) {
+                result = true;
+                return false;
+            }
+        });
+
+        return result;
+    }
+
+    // 从当前的elem,往上去查找合法标签 如 p head table blockquote ul ol 等
+    E.fn.getLegalTags = function (elem) {
+        var legalTags = this.config.legalTags;
+        if (!legalTags) {
+            E.error('配置项中缺少 legalTags 的配置');
+            return;
+        }
+        return this.getSelfOrParentByName(elem, legalTags);
+    };
+
+    // 根据条件,查询自身或者父元素,符合即返回
+    E.fn.getSelfOrParentByName = function (elem, selector, check) {
+
+        if (!elem || !selector) {
+            return;
+        }
+
+        if (!matchesSelector) {
+            // 定义 matchesSelector 函数
+            matchesSelector = elem.webkitMatchesSelector || 
+                              elem.mozMatchesSelector ||
+                              elem.oMatchesSelector || 
+                              elem.matchesSelector;
+        }
+        if (!matchesSelector) {
+            // 如果浏览器本身不支持 matchesSelector 则使用自定义的hook
+            matchesSelector = _matchesSelectorForIE;
+        }
+
+        var txt = this.txt.$txt.get(0);
+
+        while (elem && txt !== elem && $.contains(txt, elem)) {
+            if (matchesSelector.call(elem, selector)) {
+                // 符合 selector 查询条件
+
+                if (!check) {
+                    // 没有 check 验证函数,直接返回即可
+                    return elem;
+                }
+
+                if (check(elem)) {
+                    // 如果有 check 验证函数,还需 check 函数的确认
+                    return elem;
+                }
+            }
+
+            // 如果上一步没经过验证,则将跳转到父元素
+            elem = elem.parentNode;
+        }
+
+        return;
+    };
+
+});
+// undo redo
+_e(function (E, $) {
+
+    var length = 20;  // 缓存的最大长度
+    function _getRedoList(editor) {
+        if (editor._redoList == null) {
+            editor._redoList = [];
+        }
+        return editor._redoList;
+    }
+    function _getUndoList(editor) {
+        if (editor._undoList == null) {
+            editor._undoList = [];
+        }
+        return editor._undoList;
+    }
+
+    // 数据处理
+    function _handle(editor, data, type) {
+        // var range = data.range;
+        // var range2 = range.cloneRange && range.cloneRange();
+        var val = data.val;
+        var html = editor.txt.$txt.html();
+
+        if(val == null) {
+            return;
+        }
+
+        if (val === html) {
+            if (type === 'redo') { 
+                editor.redo();
+                return;
+            } else if (type === 'undo') {
+                editor.undo();
+                return;
+            } else {
+                return;
+            }
+        }
+
+        // 保存数据
+        editor.txt.$txt.html(val);
+        // 更新数据到textarea(有必要的话)
+        editor.updateValue();
+
+        // onchange 事件
+        if (editor.onchange && typeof editor.onchange === 'function') {
+            editor.onchange.call(editor);
+        }
+
+        // ?????
+        // 注释:$txt 被重新赋值之后,range会被重置,cloneRange() 也不好使
+        // // 重置选区
+        // if (range2) {
+        //     editor.restoreSelection(range2);
+        // }
+    }
+
+    // 记录
+    E.fn.undoRecord = function () {
+        var editor = this;
+        var $txt = editor.txt.$txt;
+        var val = $txt.html();
+        var undoList = _getUndoList(editor);
+        var redoList = _getRedoList(editor);
+        var currentVal = undoList.length ? undoList[0] : '';
+
+        if (val === currentVal.val) {
+            return;
+        }
+
+        // 清空 redolist
+        if (redoList.length) {
+            redoList = [];
+        }
+
+        // 添加数据到 undoList
+        undoList.unshift({
+            range: editor.currentRange(),  // 将当前的range也记录下
+            val: val
+        });
+
+        // 限制 undoList 长度
+        if (undoList.length > length) {
+            undoList.pop();
+        }
+    };
+
+    // undo 操作
+    E.fn.undo = function () {
+        var editor = this;
+        var undoList = _getUndoList(editor);
+        var redoList = _getRedoList(editor);
+
+        if (!undoList.length) {
+            return;
+        }
+
+        // 取出 undolist 第一个值,加入 redolist
+        var data = undoList.shift();
+        redoList.unshift(data);
+
+        // 并修改编辑器的内容
+        _handle(this, data, 'undo');
+    };
+
+    // redo 操作
+    E.fn.redo = function () {
+        var editor = this;
+        var undoList = _getUndoList(editor);
+        var redoList = _getRedoList(editor);
+        if (!redoList.length) {
+            return;
+        }
+
+        // 取出 redolist 第一个值,加入 undolist
+        var data = redoList.shift();
+        undoList.unshift(data);
+
+        // 并修改编辑器的内容
+        _handle(this, data, 'redo');
+    };
+});
+// 暴露给用户的 API
+_e(function (E, $) {
+
+    // 创建编辑器
+    E.fn.create = function () {
+        var editor = this;
+
+        // 检查 E.$body 是否有值
+        // 如果在 body 之前引用了 js 文件,body 尚未加载,可能没有值
+        if (!E.$body || E.$body.length === 0) {
+            E.$body = $('body');
+            E.$document = $(document);
+            E.$window = $(window);
+        }
+
+        // 执行 addMenus 之前:
+        // 1. 允许用户修改 editor.UI 自定义配置UI
+        // 2. 允许用户通过修改 editor.menus 来自定义配置菜单
+        // 因此要在 create 时执行,而不是 init           
+        editor.addMenus();
+
+        // 渲染
+        editor.renderMenus();
+        editor.renderMenuContainer();
+        editor.renderTxt();
+        editor.renderEditorContainer();
+
+        // 绑定事件
+        editor.eventMenus();
+        editor.eventMenuContainer();
+        editor.eventTxt();
+
+        // 处理ready事件
+        editor.readyHeadler();
+
+        // 初始化选区
+        editor.initSelection();
+
+        // $txt 快捷方式
+        editor.$txt = editor.txt.$txt;
+
+        // 执行用户自定义事件,通过 E.ready() 添加
+        var _plugins = E._plugins;
+        if (_plugins && _plugins.length) {
+            $.each(_plugins, function (k, val) {
+                val.call(editor);
+            });
+        }
+    };
+
+    // 禁用编辑器
+    E.fn.disable = function () {
+        this.txt.$txt.removeAttr('contenteditable');
+        this.disableMenusExcept();
+
+        // 先禁用,再记录状态
+        this._disabled = true;
+    };
+    // 启用编辑器
+    E.fn.enable = function () {
+        // 先解除状态记录,再启用
+        this._disabled = false;
+        this.txt.$txt.attr('contenteditable', 'true');
+        this.enableMenusExcept();
+    };
+
+    // 销毁编辑器
+    E.fn.destroy = function () {
+        var self = this;
+        var $valueContainer = self.$valueContainer;
+        var $editorContainer = self.$editorContainer;
+        var valueNodeName = self.valueNodeName;
+
+        if (valueNodeName === 'div') {
+            // div 生成的编辑器
+            $valueContainer.removeAttr('contenteditable');
+            $editorContainer.after($valueContainer);
+            $editorContainer.hide();
+        } else {
+            // textarea 生成的编辑器
+            $valueContainer.show();
+            $editorContainer.hide();
+        }
+    };
+
+    // 撤销 销毁编辑器
+    E.fn.undestroy = function () {
+        var self = this;
+        var $valueContainer = self.$valueContainer;
+        var $editorContainer = self.$editorContainer;
+        var $menuContainer = self.menuContainer.$menuContainer;
+        var valueNodeName = self.valueNodeName;
+
+        if (valueNodeName === 'div') {
+            // div 生成的编辑器
+            $valueContainer.attr('contenteditable', 'true');
+            $menuContainer.after($valueContainer);
+            $editorContainer.show();
+        } else {
+            // textarea 生成的编辑器
+            $valueContainer.hide();
+            $editorContainer.show();
+        }
+    };
+
+    // 清空内容的快捷方式
+    E.fn.clear = function () {
+        var editor = this;
+        var $txt = editor.txt.$txt;
+        $txt.html('<p><br></p>');
+        editor.restoreSelectionByElem($txt.find('p').get(0));
+    };
+
+});
+// menuContainer 构造函数
+_e(function (E, $) {
+
+    // 定义构造函数
+    var MenuContainer = function (editor) {
+        this.editor = editor;
+        this.init();
+    };
+
+    MenuContainer.fn = MenuContainer.prototype;
+
+    // 暴露给 E 即 window.wangEditor
+    E.MenuContainer = MenuContainer;
+
+});
+// MenuContainer.fn bind fn
+_e(function (E, $) {
+
+    var MenuContainer = E.MenuContainer;
+
+    // 初始化
+    MenuContainer.fn.init = function () {
+        var self = this;
+        var $menuContainer = $('<div class="wangEditor-menu-container clearfix"></div>');
+
+        self.$menuContainer = $menuContainer;
+
+        // change shadow
+        self.changeShadow();
+    };
+
+    // 编辑区域滚动时,增加shadow
+    MenuContainer.fn.changeShadow = function () {
+        var $menuContainer = this.$menuContainer;
+        var editor = this.editor;
+        var $txt = editor.txt.$txt;
+
+        $txt.on('scroll', function () {
+            if ($txt.scrollTop() > 10) {
+                $menuContainer.addClass('wangEditor-menu-shadow');
+            } else {
+                $menuContainer.removeClass('wangEditor-menu-shadow');
+            }
+        });
+    };
+
+});
+// MenuContainer.fn API
+_e(function (E, $) {
+
+    var MenuContainer = E.MenuContainer;
+
+    MenuContainer.fn.render = function () {
+        var $menuContainer = this.$menuContainer;
+        var $editorContainer = this.editor.$editorContainer;
+
+        $editorContainer.append($menuContainer);
+    };
+    
+    // 获取菜单栏的高度
+    MenuContainer.fn.height = function () {
+        var $menuContainer = this.$menuContainer;
+        return $menuContainer.height();
+    };
+
+    // 添加菜单
+    MenuContainer.fn.appendMenu = function (groupIdx, menu) {
+        // 判断是否需要新增一个菜单组
+        this._addGroup(groupIdx);
+        // 增加菜单(返回 $menuItem)
+        return this._addOneMenu(menu);
+    };
+    MenuContainer.fn._addGroup = function (groupIdx) {
+        var $menuContainer = this.$menuContainer;
+        var $menuGroup;
+        if (!this.$currentGroup || this.currentGroupIdx !== groupIdx) {
+            $menuGroup = $('<div class="menu-group clearfix"></div>');
+            $menuContainer.append($menuGroup);
+
+            this.$currentGroup = $menuGroup;
+            this.currentGroupIdx = groupIdx;
+        }
+    };
+    MenuContainer.fn._addOneMenu = function (menu) {
+        var $menuNormal = menu.$domNormal;
+        var $menuSelected = menu.$domSelected;
+
+        var $menuGroup = this.$currentGroup;
+        var $item = $('<div class="menu-item clearfix"></div>');
+        $menuSelected.hide();
+        $item.append($menuNormal).append($menuSelected);
+        $menuGroup.append($item);
+
+        return $item;
+    };
+
+});
+// menu 构造函数
+_e(function (E, $) {
+
+    // 定义构造函数
+    var Menu = function (opt) {
+        this.editor = opt.editor;
+        this.id = opt.id;
+        this.title = opt.title;
+        this.$domNormal = opt.$domNormal;
+        this.$domSelected = opt.$domSelected || opt.$domNormal;
+
+        // document.execCommand 的参数
+        this.commandName = opt.commandName;
+        this.commandValue = opt.commandValue;
+        this.commandNameSelected = opt.commandNameSelected || opt.commandName;
+        this.commandValueSelected = opt.commandValueSelected || opt.commandValue;
+    };
+
+    Menu.fn = Menu.prototype;
+
+    // 暴露给 E 即 window.wangEditor
+    E.Menu = Menu;
+});
+// Menu.fn 初始化绑定的事件
+_e(function (E, $) {
+
+    var Menu = E.Menu;
+
+    // 初始化UI
+    Menu.fn.initUI = function () {
+        var editor = this.editor;
+        var uiConfig = editor.UI.menus;
+        var menuId = this.id;
+        var menuUI = uiConfig[menuId];
+
+        if (this.$domNormal && this.$domSelected) {
+            // 自定义的菜单中,已经传入了 $dom 无需从配置文件中查找生成
+            return;
+        }
+
+        if (menuUI == null) {
+            E.warn('editor.UI配置中,没有菜单 "' + menuId + '" 的UI配置,只能取默认值');
+            
+            // 必须写成 uiConfig['default'];
+            // 写成 uiConfig.default IE8会报错
+            menuUI = uiConfig['default'];
+        }
+
+        // 正常状态
+        this.$domNormal = $(menuUI.normal);
+
+        // 选中状态
+        if (/^\./.test(menuUI.selected)) {
+            // 增加一个样式
+            this.$domSelected = this.$domNormal.clone().addClass(menuUI.selected.slice(1));
+        } else {
+            // 一个新的dom对象
+            this.$domSelected = $(menuUI.selected);
+        }
+    };
+
+});
+// Menu.fn API
+_e(function (E, $) {
+
+    var Menu = E.Menu;
+
+    // 渲染菜单
+    Menu.fn.render = function (groupIdx) {
+        // 渲染UI
+        this.initUI();
+        
+        var editor = this.editor;
+        var menuContainer = editor.menuContainer;
+        var $menuItem = menuContainer.appendMenu(groupIdx, this);
+        var onRender = this.onRender;
+
+        // 渲染tip
+        this._renderTip($menuItem);
+
+        // 执行 onRender 函数
+        if (onRender && typeof onRender === 'function') {
+            onRender.call(this);
+        }
+    };
+    Menu.fn._renderTip = function ($menuItem) {
+        var self = this;
+        var editor = self.editor;
+        var title = self.title;
+        var $tip = $('<div class="menu-tip"></div>');
+        // var $triangle = $('<i class="tip-triangle"></i>'); // 小三角
+
+        // 计算 tip 宽度
+        var $tempDiv;
+        if (!self.tipWidth) {
+            // 设置一个纯透明的 p(absolute;top:-10000px;不会显示在内容区域)
+            // 内容赋值为 title ,为了计算tip宽度
+            $tempDiv = $('<p style="opacity:0; filter:Alpha(opacity=0); position:absolute;top:-10000px;">' + title + '</p>');
+            // 先添加到body,计算完再 remove
+            E.$body.append($tempDiv);
+            editor.ready(function () {
+                var editor = this;
+                var titleWidth = $tempDiv.outerWidth() + 5; // 多出 5px 的冗余
+                var currentWidth = $tip.outerWidth();
+                var currentMarginLeft = parseFloat($tip.css('margin-left'), 10);
+                // 计算完,拿到数据,则弃用
+                $tempDiv.remove();
+                $tempDiv = null;
+
+                // 重新设置样式
+                $tip.css({
+                    width: titleWidth,
+                    'margin-left': currentMarginLeft + (currentWidth - titleWidth)/2
+                });
+
+                // 存储
+                self.tipWidth = titleWidth;
+            });
+        }
+
+        // $tip.append($triangle);
+        $tip.append(title);
+        $menuItem.append($tip);
+
+        function show() {
+            $tip.show();
+        }
+        function hide() {
+            $tip.hide();
+        }
+
+        var timeoutId;
+        $menuItem.find('a').on('mouseenter', function (e) {
+            if (!self.active() && !self.disabled()) {
+                timeoutId = setTimeout(show, 200);
+            }
+        }).on('mouseleave', function (e) {
+            timeoutId && clearTimeout(timeoutId);
+            hide();
+        }).on('click', hide);
+    };
+
+    // 绑定事件
+    Menu.fn.bindEvent = function () {
+        var self = this;
+
+        var $domNormal = self.$domNormal;
+        var $domSelected = self.$domSelected;
+
+        // 试图获取该菜单定义的事件(未selected),没有则自己定义
+        var clickEvent = self.clickEvent;
+        if (!clickEvent) {
+            clickEvent = function (e) {
+                // -----------dropPanel dropList modal-----------
+                var dropObj = self.dropPanel || self.dropList || self.modal;
+                if (dropObj && dropObj.show) {
+                    if (dropObj.isShowing) {
+                        dropObj.hide();
+                    } else {
+                        dropObj.show();
+                    }
+                    return;
+                }
+
+                // -----------command-----------
+                var editor = self.editor;
+                var commandName;
+                var commandValue;
+
+                var selected = self.selected;
+                if (selected) {
+                    commandName = self.commandNameSelected;
+                    commandValue = self.commandValueSelected;
+                } else {
+                    commandName = self.commandName;
+                    commandValue = self.commandValue;
+                }
+
+                if (commandName) {
+                    // 执行命令
+                    editor.command(e, commandName, commandValue);
+                } else {
+                    // 提示
+                    E.warn('菜单 "' + self.id + '" 未定义click事件');
+                    e.preventDefault();
+                }
+            };
+        }
+        // 获取菜单定义的selected情况下的点击事件
+        var clickEventSelected = self.clickEventSelected || clickEvent;
+
+        // 将事件绑定到菜单dom上
+        $domNormal.click(function (e) {
+            if (!self.disabled()) {
+                clickEvent.call(self, e);
+                self.updateSelected();
+            }
+            e.preventDefault();
+        });
+        $domSelected.click(function (e) {
+            if (!self.disabled()) {
+                clickEventSelected.call(self, e);
+                self.updateSelected();
+            }
+            e.preventDefault();
+        });
+    };
+
+    // 更新选中状态
+    Menu.fn.updateSelected = function () {
+        var self = this;
+        var editor = self.editor;
+
+        // 试图获取用户自定义的判断事件
+        var updateSelectedEvent = self.updateSelectedEvent;
+        if (!updateSelectedEvent) {
+            // 用户未自定义,则设置默认值
+            updateSelectedEvent = function () {
+                var self = this;
+                var editor = self.editor;
+                var commandName = self.commandName;
+                var commandValue = self.commandValue;
+
+                if (commandValue) {
+                    if (editor.queryCommandValue(commandName).toLowerCase() === commandValue.toLowerCase()) {
+                        return true;
+                    }
+                } else if (editor.queryCommandState(commandName)) {
+                    return true;
+                }
+
+                return false;
+            };
+        }
+
+        // 获取结果
+        var result = updateSelectedEvent.call(self);
+        result = !!result;
+
+        // 存储结果、显示效果
+        self.changeSelectedState(result);
+    };
+
+    // 切换选中状态、显示效果
+    Menu.fn.changeSelectedState = function (state) {
+        var self = this;
+        var selected = self.selected;
+
+        if (state != null && typeof state === 'boolean') {
+            if (selected === state) {
+                // 计算结果和当前的状态一样
+                return;
+            }
+            // 存储结果
+            self.selected = state;
+
+            // 切换菜单的显示
+            if (state) {
+                // 选中
+                self.$domNormal.hide();
+                self.$domSelected.show();
+            } else {
+                // 未选中
+                self.$domNormal.show();
+                self.$domSelected.hide();
+            }
+        } // if
+    };
+
+    // 点击菜单,显示了 dropPanel modal 时,菜单的状态 
+    Menu.fn.active = function (active) {
+        if (active == null) {
+            return this._activeState;
+        }
+        this._activeState = active;
+    };
+    Menu.fn.activeStyle = function (active) {
+        var selected = this.selected;
+        var $dom = this.$domNormal;
+        var $domSelected = this.$domSelected;
+
+        if (active) {
+            $dom.addClass('active');
+            $domSelected.addClass('active');
+        } else {
+            $dom.removeClass('active');
+            $domSelected.removeClass('active');
+        }
+
+        // 记录状态 ( menu hover 时会取状态用 )
+        this.active(active);
+    };
+
+    // 菜单的启用和禁用
+    Menu.fn.disabled = function (opt) {
+        // 参数为空,取值
+        if (opt == null) {
+            return !!this._disabled;
+        }
+
+        if (this._disabled === opt) {
+            // 要设置的参数值和当前参数只一样,无需再次设置
+            return;
+        }
+
+        var $dom = this.$domNormal;
+        var $domSelected = this.$domSelected;
+
+        // 设置样式
+        if (opt) {
+            $dom.addClass('disable');
+            $domSelected.addClass('disable');
+        } else {
+            $dom.removeClass('disable');
+            $domSelected.removeClass('disable');
+        }
+
+        // 存储
+        this._disabled = opt;
+    };
+
+});
+// dropList 构造函数
+_e(function (E, $) {
+
+    // 定义构造函数
+    var DropList = function (editor, menu, opt) {
+        this.editor = editor;
+        this.menu = menu;
+
+        // list 的数据源,格式 {'commandValue': 'title', ...}
+        this.data = opt.data;
+        // 要为每个item自定义的模板
+        this.tpl = opt.tpl;
+        // 为了执行 editor.commandForElem 而传入的elem查询方式
+        this.selectorForELemCommand = opt.selectorForELemCommand;
+
+        // 执行事件前后的钩子
+        this.beforeEvent = opt.beforeEvent;
+        this.afterEvent = opt.afterEvent;
+
+        // 初始化
+        this.init();
+    };
+
+    DropList.fn = DropList.prototype;
+
+    // 暴露给 E 即 window.wangEditor
+    E.DropList = DropList;
+});
+// dropList fn bind
+_e(function (E, $) {
+
+    var DropList = E.DropList;
+
+    // init
+    DropList.fn.init = function () {
+        var self = this;
+
+        // 生成dom对象
+        self.initDOM();
+
+        // 绑定command事件
+        self.bindEvent();
+
+        // 声明隐藏的事件
+        self.initHideEvent();
+    };
+
+    // 初始化dom结构
+    DropList.fn.initDOM = function () {
+        var self = this;
+        var data = self.data;
+        var tpl = self.tpl || '<span>{#title}</span>';
+        var $list = $('<div class="wangEditor-drop-list clearfix"></div>');
+
+        var itemContent;
+        var $item;
+        $.each(data, function (commandValue, title) {
+            itemContent = tpl.replace(/{#commandValue}/ig, commandValue).replace(/{#title}/ig, title);
+            $item = $('<a href="#" commandValue="' + commandValue + '"></a>');
+            $item.append(itemContent);
+            $list.append($item);
+        });
+
+        self.$list = $list;
+    };
+
+    // 绑定事件
+    DropList.fn.bindEvent = function () {
+        var self = this;
+        var editor = self.editor;
+        var menu = self.menu;
+        var commandName = menu.commandName;
+        var selectorForELemCommand = self.selectorForELemCommand;
+        var $list = self.$list;
+
+        // 执行事件前后的钩子函数
+        var beforeEvent = self.beforeEvent;
+        var afterEvent = self.afterEvent;
+
+        $list.on('click', 'a[commandValue]', function (e) {
+            // 正式命令执行之前
+            if (beforeEvent && typeof beforeEvent === 'function') {
+                beforeEvent.call(e);
+            }
+
+            // 执行命令
+            var commandValue = $(e.currentTarget).attr('commandValue');
+            if (menu.selected && editor.isRangeEmpty() && selectorForELemCommand) {
+                // 当前处于选中状态,并且选中内容为空
+                editor.commandForElem(selectorForELemCommand, e, commandName, commandValue);
+            } else {
+                // 当前未处于选中状态,或者有选中内容。则执行默认命令
+                editor.command(e, commandName, commandValue);
+            }
+
+            // 正式命令之后的钩子
+            if (afterEvent && typeof afterEvent === 'function') {
+                afterEvent.call(e);
+            }
+        });
+    };
+
+    // 点击其他地方,立即隐藏 droplist
+    DropList.fn.initHideEvent = function () {
+        var self = this;
+
+        // 获取 list elem
+        var thisList = self.$list.get(0);
+
+        E.$body.on('click', function (e) {
+            if (!self.isShowing) {
+                return;
+            }
+            var trigger = e.target;
+
+            // 获取菜单elem
+            var menu = self.menu;
+            var menuDom;
+            if (menu.selected) {
+                menuDom = menu.$domSelected.get(0);
+            } else {
+                menuDom = menu.$domNormal.get(0);
+            }
+
+            if (menuDom === trigger || $.contains(menuDom, trigger)) {
+                // 说明由本菜单点击触发的
+                return;
+            }
+
+            if (thisList === trigger || $.contains(thisList, trigger)) {
+                // 说明由本list点击触发的
+                return;
+            }
+
+            // 其他情况,隐藏 list
+            self.hide();
+        });
+
+        E.$window.scroll(function () {
+            self.hide();
+        });
+
+        E.$window.on('resize', function () {
+            self.hide();
+        });
+    };
+
+});
+// dropListfn api
+_e(function (E, $) {
+    
+    var DropList = E.DropList;
+
+    // 渲染
+    DropList.fn._render = function () {
+        var self = this;
+        var editor = self.editor;
+        var $list = self.$list;
+
+        // 渲染到页面
+        editor.$editorContainer.append($list);
+
+        // 记录状态
+        self.rendered = true;
+    };
+
+    // 定位
+    DropList.fn._position = function () {
+        var self = this;
+        var $list = self.$list;
+        var editor = self.editor;
+        var menu = self.menu;
+        var $menuContainer = editor.menuContainer.$menuContainer;
+        var $menuDom = menu.selected ? menu.$domSelected : menu.$domNormal;
+        // 注意这里的 offsetParent() 要返回 .menu-item 的 position
+        // 因为 .menu-item 是 position:relative
+        var menuPosition = $menuDom.offsetParent().position();
+
+        // 取得 menu 的位置、尺寸属性
+        var menuTop = menuPosition.top;
+        var menuLeft = menuPosition.left;
+        var menuHeight = $menuDom.offsetParent().height();
+        var menuWidth = $menuDom.offsetParent().width();
+
+        // 取得 list 的尺寸属性
+        var listWidth = $list.outerWidth();
+        // var listHeight = $list.outerHeight();
+
+        // 取得 $txt 的尺寸
+        var txtWidth = editor.txt.$txt.outerWidth();
+
+        // ------------开始计算-------------
+
+        // 初步计算 list 位置属性
+        var top = menuTop + menuHeight;
+        var left = menuLeft + menuWidth/2;
+        var marginLeft = 0 - menuWidth/2;
+
+        // 如果超出了有边界,则要左移(且和右侧有间隙)
+        var valWithTxt = (left + listWidth) - txtWidth;
+        if (valWithTxt > -10) {
+            marginLeft = marginLeft - valWithTxt - 10;
+        }
+        // 设置样式
+        $list.css({
+            top: top,
+            left: left,
+            'margin-left': marginLeft
+        });
+
+        // 如果因为向下滚动而导致菜单fixed,则再加一步处理
+        if (editor._isMenufixed) {
+            top = top + (($menuContainer.offset().top + $menuContainer.outerHeight()) - $list.offset().top);
+
+            // 重新设置top
+            $list.css({
+                top: top
+            });
+        }
+    };
+
+    // 显示
+    DropList.fn.show = function () {
+        var self = this;
+        var menu = self.menu;
+        if (!self.rendered) {
+            // 第一次show之前,先渲染
+            self._render();
+        }
+
+        if (self.isShowing) {
+            return;
+        }
+
+        var $list = self.$list;
+        $list.show();
+
+        // 定位
+        self._position();
+
+        // 记录状态
+        self.isShowing = true;
+
+        // 菜单状态
+        menu.activeStyle(true);
+    };
+
+    // 隐藏
+    DropList.fn.hide = function () {
+        var self = this;
+        var menu = self.menu;
+        if (!self.isShowing) {
+            return;
+        }
+
+        var $list = self.$list;
+        $list.hide();
+
+        // 记录状态
+        self.isShowing = false;
+
+        // 菜单状态
+        menu.activeStyle(false);
+    };
+});
+// dropPanel 构造函数
+_e(function (E, $) {
+
+    // 定义构造函数
+    var DropPanel = function (editor, menu, opt) {
+        this.editor = editor;
+        this.menu = menu;
+        this.$content = opt.$content;
+        this.width = opt.width || 200;
+        this.height = opt.height;
+        this.onRender = opt.onRender;
+
+        // init
+        this.init();
+    };
+
+    DropPanel.fn = DropPanel.prototype;
+
+    // 暴露给 E 即 window.wangEditor
+    E.DropPanel = DropPanel;
+});
+// dropPanel fn bind
+_e(function (E, $) {
+
+    var DropPanel = E.DropPanel;
+
+    // init
+    DropPanel.fn.init = function () {
+        var self = this;
+
+        // 生成dom对象
+        self.initDOM();
+
+        // 声明隐藏的事件
+        self.initHideEvent();
+    };
+
+    // init DOM
+    DropPanel.fn.initDOM = function () {
+        var self = this;
+        var $content = self.$content;
+        var width = self.width;
+        var height = self.height;
+        var $panel = $('<div class="wangEditor-drop-panel clearfix"></div>');
+        var $triangle = $('<div class="tip-triangle"></div>');
+
+        $panel.css({
+            width: width,
+            height: height ? height : 'auto'
+        });
+        $panel.append($triangle);
+        $panel.append($content);
+
+        // 添加对象数据
+        self.$panel = $panel;
+        self.$triangle = $triangle;
+    };
+
+    // 点击其他地方,立即隐藏 dropPanel
+    DropPanel.fn.initHideEvent = function () {
+        var self = this;
+
+        // 获取 panel elem
+        var thisPanle = self.$panel.get(0);
+
+        E.$body.on('click', function (e) {
+            if (!self.isShowing) {
+                return;
+            }
+            var trigger = e.target;
+
+            // 获取菜单elem
+            var menu = self.menu;
+            var menuDom;
+            if (menu.selected) {
+                menuDom = menu.$domSelected.get(0);
+            } else {
+                menuDom = menu.$domNormal.get(0);
+            }
+
+            if (menuDom === trigger || $.contains(menuDom, trigger)) {
+                // 说明由本菜单点击触发的
+                return;
+            }
+
+            if (thisPanle === trigger || $.contains(thisPanle, trigger)) {
+                // 说明由本panel点击触发的
+                return;
+            }
+
+            // 其他情况,隐藏 panel
+            self.hide();
+        });
+
+        E.$window.scroll(function (e) {
+            self.hide();
+        });
+
+        E.$window.on('resize', function () {
+            self.hide();
+        });
+    };
+
+});
+// dropPanel fn api
+_e(function (E, $) {
+   
+    var DropPanel = E.DropPanel;
+
+    // 渲染
+    DropPanel.fn._render = function () {
+        var self = this;
+        var onRender = self.onRender;
+        var editor = self.editor;
+        var $panel = self.$panel;
+
+        // 渲染到页面
+        editor.$editorContainer.append($panel);
+
+        // 渲染后的回调事件
+        onRender && onRender.call(self);
+
+        // 记录状态
+        self.rendered = true;
+    };
+
+    // 定位
+    DropPanel.fn._position = function () {
+        var self = this;
+        var $panel = self.$panel;
+        var $triangle = self.$triangle;
+        var editor = self.editor;
+        var $menuContainer = editor.menuContainer.$menuContainer;
+        var menu = self.menu;
+        var $menuDom = menu.selected ? menu.$domSelected : menu.$domNormal;
+        // 注意这里的 offsetParent() 要返回 .menu-item 的 position
+        // 因为 .menu-item 是 position:relative
+        var menuPosition = $menuDom.offsetParent().position();
+
+        // 取得 menu 的位置、尺寸属性
+        var menuTop = menuPosition.top;
+        var menuLeft = menuPosition.left;
+        var menuHeight = $menuDom.offsetParent().height();
+        var menuWidth = $menuDom.offsetParent().width();
+
+        // 取得 panel 的尺寸属性
+        var panelWidth = $panel.outerWidth();
+        // var panelHeight = $panel.outerHeight();
+
+        // 取得 $txt 的尺寸
+        var txtWidth = editor.txt.$txt.outerWidth();
+
+        // ------------开始计算-------------
+
+        // 初步计算 panel 位置属性
+        var top = menuTop + menuHeight;
+        var left = menuLeft + menuWidth/2;
+        var marginLeft = 0 - panelWidth/2;
+        var marginLeft2 = marginLeft;  // 下文用于和 marginLeft 比较,来设置三角形tip的位置
+
+        // 如果超出了左边界,则移动回来(要和左侧有10px间隙)
+        if ((0 - marginLeft) > (left - 10)) {
+            marginLeft = 0 - (left - 10);
+        }
+
+        // 如果超出了有边界,则要左移(且和右侧有10px间隙)
+        var valWithTxt = (left + panelWidth + marginLeft) - txtWidth;
+        if (valWithTxt > -10) {
+            marginLeft = marginLeft - valWithTxt - 10;
+        }
+
+        // 设置样式
+        $panel.css({
+            top: top,
+            left: left,
+            'margin-left': marginLeft
+        });
+
+        // 如果因为向下滚动而导致菜单fixed,则再加一步处理
+        if (editor._isMenufixed) {
+            top = top + (($menuContainer.offset().top + $menuContainer.outerHeight()) - $panel.offset().top);
+
+            // 重新设置top
+            $panel.css({
+                top: top
+            });
+        }
+
+        // 设置三角形 tip 的位置
+        $triangle.css({
+            'margin-left': marginLeft2 - marginLeft - 5
+        });
+    };
+
+    // focus 第一个 input
+    DropPanel.fn.focusFirstInput = function () {
+        var self = this;
+        var $panel = self.$panel;
+        $panel.find('input[type=text],textarea').each(function () {
+            var $input = $(this);
+            if ($input.attr('disabled') == null) {
+                $input.focus();
+                return false;
+            }
+        });
+    };
+
+    // 显示
+    DropPanel.fn.show = function () {
+        var self = this;
+        var menu = self.menu;
+        if (!self.rendered) {
+            // 第一次show之前,先渲染
+            self._render();
+        }
+
+        if (self.isShowing) {
+            return;
+        }
+
+        var $panel = self.$panel;
+        $panel.show();
+
+        // 定位
+        self._position();
+
+        // 记录状态
+        self.isShowing = true;
+
+        // 菜单状态
+        menu.activeStyle(true);
+
+        if (E.w3cRange) {
+            // 高级浏览器
+            self.focusFirstInput();
+        } else {
+            // 兼容 IE8 input placeholder
+            E.placeholderForIE8($panel);
+        }
+    };
+
+    // 隐藏
+    DropPanel.fn.hide = function () {
+        var self = this;
+        var menu = self.menu;
+        if (!self.isShowing) {
+            return;
+        }
+
+        var $panel = self.$panel;
+        $panel.hide();
+
+        // 记录状态
+        self.isShowing = false;
+
+        // 菜单状态
+        menu.activeStyle(false);
+    };
+
+});
+// modal 构造函数
+_e(function (E, $) {
+
+    // 定义构造函数
+    var Modal = function (editor, menu, opt) {
+        this.editor = editor;
+        this.menu = menu;
+        this.$content = opt.$content;
+
+        this.init();
+    };
+
+    Modal.fn = Modal.prototype;
+
+    // 暴露给 E 即 window.wangEditor
+    E.Modal = Modal;
+});
+// modal fn bind
+_e(function (E, $) {
+
+    var Modal = E.Modal;
+
+    Modal.fn.init = function () {
+        var self = this;
+
+        // 初始化dom
+        self.initDom();
+
+        // 初始化隐藏事件
+        self.initHideEvent();
+    };
+
+    // 初始化dom
+    Modal.fn.initDom = function () {
+        var self = this;
+        var $content = self.$content;
+        var $modal = $('<div class="wangEditor-modal"></div>');
+        var $close = $('<div class="wangEditor-modal-close"><i class="wangeditor-menu-img-cancel-circle"></i></div>');
+
+        $modal.append($close);
+        $modal.append($content);
+
+        // 记录数据
+        self.$modal = $modal;
+        self.$close = $close;
+    };
+
+    // 初始化隐藏事件
+    Modal.fn.initHideEvent = function () {
+        var self = this;
+        var $close = self.$close;
+        var modal = self.$modal.get(0);
+
+        // 点击 $close 按钮,隐藏
+        $close.click(function () {
+            self.hide();
+        });
+
+        // 点击其他部分,隐藏
+        E.$body.on('click', function (e) {
+            if (!self.isShowing) {
+                return;
+            }
+            var trigger = e.target;
+
+            // 获取菜单elem
+            var menu = self.menu;
+            var menuDom;
+            if (menu) {
+                if (menu.selected) {
+                    menuDom = menu.$domSelected.get(0);
+                } else {
+                    menuDom = menu.$domNormal.get(0);
+                }
+
+                if (menuDom === trigger || $.contains(menuDom, trigger)) {
+                    // 说明由本菜单点击触发的
+                    return;
+                }
+            }
+
+            if (modal === trigger || $.contains(modal, trigger)) {
+                // 说明由本panel点击触发的
+                return;
+            }
+
+            // 其他情况,隐藏 panel
+            self.hide();
+        });
+    };
+});
+// modal fn api
+_e(function (E, $) {
+
+    var Modal = E.Modal;
+
+    // 渲染
+    Modal.fn._render = function () {
+        var self = this;
+        var editor = self.editor;
+        var $modal = self.$modal;
+
+        // $modal的z-index,在配置的z-index基础上再 +10
+        $modal.css('z-index', editor.config.zindex + 10 + '');
+
+        // 渲染到body最后面
+        E.$body.append($modal);
+
+        // 记录状态
+        self.rendered = true;
+    };
+
+    // 定位
+    Modal.fn._position = function () {
+        var self = this;
+        var $modal = self.$modal;
+        var top = $modal.offset().top;
+        var width = $modal.outerWidth();
+        var height = $modal.outerHeight();
+        var marginLeft = 0 - (width / 2);
+        var marginTop = 0 - (height / 2);
+        var sTop = E.$window.scrollTop();
+
+        // 保证modal最顶部,不超过浏览器上边框
+        if ((height / 2) > top) {
+            marginTop = 0 - top;
+        }
+
+        $modal.css({
+            'margin-left': marginLeft + 'px',
+            'margin-top': (marginTop + sTop) + 'px'
+        });
+    };
+
+    // 显示
+    Modal.fn.show = function () {
+        var self = this;
+        var menu = self.menu;
+        if (!self.rendered) {
+            // 第一次show之前,先渲染
+            self._render();
+        }
+
+        if (self.isShowing) {
+            return;
+        }
+        // 记录状态
+        self.isShowing = true;
+
+        var $modal = self.$modal;
+        $modal.show();
+
+        // 定位
+        self._position();
+
+        // 激活菜单状态
+        menu && menu.activeStyle(true);
+    };
+
+    // 隐藏
+    Modal.fn.hide = function () {
+        var self = this;
+        var menu = self.menu;
+        if (!self.isShowing) {
+            return;
+        }
+        // 记录状态
+        self.isShowing = false;
+
+        // 隐藏
+        var $modal = self.$modal;
+        $modal.hide();
+
+        // 菜单状态
+        menu && menu.activeStyle(false);
+    };
+});
+// txt 构造函数
+_e(function (E, $) {
+
+    // 定义构造函数
+    var Txt = function (editor) {
+        this.editor = editor;
+
+        // 初始化
+        this.init();
+    };
+
+    Txt.fn = Txt.prototype;
+
+    // 暴露给 E 即 window.wangEditor
+    E.Txt = Txt;
+});
+// Txt.fn bind fn
+_e(function (E, $) {
+
+    var Txt = E.Txt;
+
+    // 初始化
+    Txt.fn.init = function () {
+        var self = this;
+        var editor = self.editor;
+        var $valueContainer = editor.$valueContainer;
+        var currentValue = editor.getInitValue();
+        var $txt;
+
+        if ($valueContainer.get(0).nodeName === 'DIV') {
+            // 如果传入生成编辑器的元素就是div,则直接使用
+            $txt = $valueContainer;
+            $txt.addClass("wangEditor-txt");
+            $txt.attr('contentEditable', 'true');
+        } else {
+            // 如果不是div(是textarea),则创建一个div
+            $txt = $(
+                '<div class="wangEditor-txt" contentEditable="true">' +
+                    currentValue +
+                '</div>'
+            );
+        }
+
+        // 试图最后插入一个空行,ready之后才行
+        editor.ready(function () {
+            self.insertEmptyP();
+        });
+
+        self.$txt = $txt;
+
+        // 删除时,如果没有内容了,就添加一个 <p><br></p>
+        self.contentEmptyHandle();
+
+        // enter时,不能使用 div 换行
+        self.bindEnterForDiv();
+
+        // enter时,用 p 包裹 text
+        self.bindEnterForText();
+
+        // tab 插入4个空格
+        self.bindTabEvent();
+
+        // 处理粘贴内容
+        self.bindPasteFilter();
+
+        // $txt.formatText() 方法
+        self.bindFormatText();
+
+        // 定义 $txt.html() 方法
+        self.bindHtml();
+    };
+
+    // 删除时,如果没有内容了,就添加一个 <p><br></p>
+    Txt.fn.contentEmptyHandle = function () {
+        var self = this;
+        var editor = self.editor;
+        var $txt = self.$txt;
+        var $p;
+
+        $txt.on('keydown', function (e) {
+            if (e.keyCode !== 8) {
+                return;
+            }
+            var txtHtml = $.trim($txt.html().toLowerCase());
+            if (txtHtml === '<p><br></p>') {
+                // 如果最后还剩余一个空行,就不再继续删除了
+                e.preventDefault();
+                return;
+            }
+        });
+
+        $txt.on('keyup', function (e) {
+            if (e.keyCode !== 8) {
+                return;
+            }
+            var txtHtml = $.trim($txt.html().toLowerCase());
+            // ff时用 txtHtml === '<br>' 判断,其他用 !txtHtml 判断
+            if (!txtHtml || txtHtml === '<br>') {
+                // 内容空了
+                $p = $('<p><br/></p>');
+                $txt.html(''); // 一定要先清空,否则在 ff 下有问题
+                $txt.append($p);
+                editor.restoreSelectionByElem($p.get(0));
+            }
+        });
+    };
+
+    // enter时,不能使用 div 换行
+    Txt.fn.bindEnterForDiv = function () {
+        var tags = E.config.legalTags; // 配置中编辑器要求的合法标签,如 p head table blockquote ul ol 等
+        var self = this;
+        var editor = self.editor;
+        var $txt = self.$txt;
+
+        var $keydownDivElem;
+        function divHandler() {
+            if (!$keydownDivElem) {
+                return;
+            }
+
+            var $pElem = $('<p>' + $keydownDivElem.html() + '</p>');
+            $keydownDivElem.after($pElem);
+            $keydownDivElem.remove();
+        }
+
+        $txt.on('keydown keyup', function (e) {
+            if (e.keyCode !== 13) {
+                return;
+            }
+            // 查找合法标签
+            var rangeElem = editor.getRangeElem();
+            var targetElem = editor.getLegalTags(rangeElem);
+            var $targetElem;
+            var $pElem;
+
+            if (!targetElem) {
+                // 没找到合法标签,就去查找 div
+                targetElem = editor.getSelfOrParentByName(rangeElem, 'div');
+                if (!targetElem) {
+                    return;
+                }
+                $targetElem = $(targetElem);
+
+                if (e.type === 'keydown') {
+                    // 异步执行(同步执行会出现问题)
+                    $keydownDivElem = $targetElem;
+                    setTimeout(divHandler, 0);
+                }
+
+                if (e.type === 'keyup') {
+                    // 将 div 的内容移动到 p 里面,并移除 div
+                    $pElem = $('<p>' + $targetElem.html() + '</p>');
+                    $targetElem.after($pElem);
+                    $targetElem.remove();
+
+                    // 如果是回车结束,将选区定位到行首
+                    editor.restoreSelectionByElem($pElem.get(0), 'start');
+                }
+            }
+        });
+    };
+
+    // enter时,用 p 包裹 text
+    Txt.fn.bindEnterForText = function () {
+        var self = this;
+        var $txt = self.$txt;
+        var handle;
+        $txt.on('keyup', function (e) {
+            if (e.keyCode !== 13) {
+                return;
+            }
+            if (!handle) {
+                handle = function() {
+                    self.wrapImgAndText();
+                };
+            }
+            setTimeout(handle);
+        });
+    };
+
+    // tab 时,插入4个空格
+    Txt.fn.bindTabEvent = function () {
+        var self = this;
+        var editor = self.editor;
+        var $txt = self.$txt;
+
+        $txt.on('keydown', function (e) {
+            if (e.keyCode !== 9) {
+                // 只监听 tab 按钮
+                return;
+            }
+            // 如果浏览器支持 insertHtml 则插入4个空格。如果不支持,就不管了
+            if (editor.queryCommandSupported('insertHtml')) {
+                editor.command(e, 'insertHtml', '&nbsp;&nbsp;&nbsp;&nbsp;');
+            }
+        });
+    };
+
+    // 处理粘贴内容
+    Txt.fn.bindPasteFilter = function () {
+        var self = this;
+        var editor = self.editor;
+        var resultHtml = '';  //存储最终的结果
+        var $txt = self.$txt;
+        var legalTags = editor.config.legalTags;
+        var legalTagArr = legalTags.split(',');
+
+        $txt.on('paste', function (e) {
+            if (!editor.config.pasteFilter) {
+                // 配置中取消了粘贴过滤
+                return;
+            }
+
+            var currentNodeName = editor.getRangeElem().nodeName;
+            if (currentNodeName === 'TD' || currentNodeName === 'TH') {
+                // 在表格的单元格中粘贴,忽略所有内容。否则会出现异常情况
+                return;
+            }
+
+            resultHtml = ''; // 先清空 resultHtml
+
+            var pasteHtml, $paste, docSplitHtml;
+            var data = e.clipboardData || e.originalEvent.clipboardData;
+            var ieData = window.clipboardData;
+
+            if (editor.config.pasteText) {
+                // 只粘贴纯文本
+
+                if (data && data.getData) {
+                    // w3c
+                    pasteHtml = data.getData('text/plain');
+                } else if (ieData && ieData.getData) {
+                    // IE
+                    pasteHtml = ieData.getData('text');
+                } else {
+                    // 其他情况
+                    return;
+                }
+
+                // 拼接为 <p> 标签
+                if (pasteHtml) {
+                    resultHtml = '<p>' + pasteHtml + '</p>';
+                }
+
+            } else {
+                // 粘贴过滤了样式的、只有标签的 html
+
+                if (data && data.getData) {
+                    // w3c
+
+                    // 获取粘贴过来的html
+                    pasteHtml = data.getData('text/html');
+
+                    // 过滤从 word excel 粘贴过来的乱码
+                    docSplitHtml = pasteHtml.split('</html>');
+                    if (docSplitHtml.length === 2) {
+                        pasteHtml = docSplitHtml[0];
+                    }
+
+                    if (pasteHtml) {
+                        // 创建dom
+                        $paste = $('<div>' + pasteHtml + '</div>');
+                        // 处理,并将结果存储到 resultHtml 『全局』变量
+                        handle($paste.get(0));
+                    } else {
+                        // 得不到html,试图获取text
+                        pasteHtml = data.getData('text/plain');
+                        if (pasteHtml) {
+                            // 替换特殊字符
+                            pasteHtml = pasteHtml.replace(/[ ]/g, '&nbsp;')
+                                                 .replace(/</g, '&lt;')
+                                                 .replace(/>/g, '&gt;')
+                                                 .replace(/\n/g, '</p><p>');
+                            // 拼接
+                            resultHtml = '<p>' + pasteHtml + '</p>';
+
+                            // 查询链接
+                            resultHtml = resultHtml.replace(/<p>(https?:\/\/.*?)<\/p>/ig, function (match, link) {
+                                return '<p><a href="' + link + '" target="_blank">' + link + '</p>';
+                            });
+                        }
+                    }
+                    
+                } else if (ieData && ieData.getData) {
+                    // IE 直接从剪切板中取出纯文本格式
+                    resultHtml = ieData.getData('text');
+                    if (!resultHtml) {
+                        return;
+                    }
+                    // 拼接为 <p> 标签
+                    resultHtml = '<p>' + resultHtml + '</p>';
+                    resultHtml = resultHtml.replace(new RegExp('\n', 'g'), '</p><p>');
+                } else {
+                    // 其他情况
+                    return;
+                }
+            }
+
+            // 执行命令
+            if (resultHtml) {
+                editor.command(e, 'insertHtml', resultHtml);
+
+                // 删除内容为空的 p 和嵌套的 p
+                self.clearEmptyOrNestP();
+            }
+        });
+
+        // 处理粘贴的内容
+        function handle(elem) {
+            if (!elem || !elem.nodeType || !elem.nodeName) {
+                return;
+            }
+            var $elem;
+            var nodeName = elem.nodeName.toLowerCase();
+            var nodeType = elem.nodeType;
+            var childNodesClone;
+
+            // 只处理文本和普通node标签
+            if (nodeType !== 3 && nodeType !== 1) {
+                return;
+            }
+
+            $elem = $(elem);
+
+            // 如果是容器,则继续深度遍历
+            if (nodeName === 'div') {
+                childNodesClone = [];
+                $.each(elem.childNodes, function (index, item) {
+                    // elem.childNodes 可获取TEXT节点,而 $elem.children() 就获取不到
+                    // 先将 elem.childNodes 拷贝一份,一面在循环递归过程中 elem 发生变化
+                    childNodesClone.push(item);
+                });
+                // 遍历子元素,执行操作
+                $.each(childNodesClone, function () {
+                    handle(this);
+                });
+                return;
+            }
+            
+            if (legalTagArr.indexOf(nodeName) >= 0) {
+                // 如果是合法标签之内的,则根据元素类型,获取值
+                resultHtml += getResult(elem);
+            } else if (nodeType === 3) {
+                // 如果是文本,则直接插入 p 标签
+                resultHtml += '<p>' + elem.textContent + '</p>';
+            } else if (nodeName === 'br') {
+                // <br>保留
+                resultHtml += '<br/>';
+            }
+            else {
+                // 忽略的标签
+                if (['meta', 'style', 'script', 'object', 'form', 'iframe', 'hr'].indexOf(nodeName) >= 0) {
+                    return;
+                }
+                // 其他标签,移除属性,插入 p 标签
+                $elem = $(removeAttrs(elem));
+                // 注意,这里的 clone() 是必须的,否则会出错
+                resultHtml += $('<div>').append($elem.clone()).html();
+            }
+        }
+
+        // 获取元素的结果
+        function getResult(elem) {
+            var nodeName = elem.nodeName.toLowerCase();
+            var $elem;
+            var htmlForP = '';
+            var htmlForLi = '';
+
+            if (['blockquote'].indexOf(nodeName) >= 0) {
+
+                // 直接取出元素text即可
+                $elem = $(elem);
+                return '<' + nodeName + '>' + $elem.text() + '</' + nodeName + '>';
+
+            } else if (['p', 'h1', 'h2', 'h3', 'h4', 'h5'].indexOf(nodeName) >= 0) {
+
+                //p head 取出 text 和链接
+                elem = removeAttrs(elem);
+                $elem = $(elem);
+                htmlForP = $elem.html();
+
+                // 剔除 a img 之外的元素
+                htmlForP = htmlForP.replace(/<.*?>/ig, function (tag) {
+                    if (tag === '</a>' || tag.indexOf('<a ') === 0 || tag.indexOf('<img ') === 0) {
+                        return tag;
+                    } else {
+                        return '';
+                    }
+                });
+
+                return '<' + nodeName + '>' + htmlForP + '</' + nodeName + '>';
+
+            } else if (['ul', 'ol'].indexOf(nodeName) >= 0) {
+                
+                // ul ol元素,获取子元素(li元素)的text link img,再拼接
+                $elem = $(elem);
+                $elem.children().each(function () {
+                    var $li = $(removeAttrs(this));
+                    var html = $li.html();
+
+                    html = html.replace(/<.*?>/ig, function (tag) {
+                        if (tag === '</a>' || tag.indexOf('<a ') === 0 || tag.indexOf('<img ') === 0) {
+                            return tag;
+                        } else {
+                            return '';
+                        }
+                    });
+
+                    htmlForLi += '<li>' + html + '</li>';
+                });
+                return '<' + nodeName + '>' + htmlForLi + '</' + nodeName + '>';
+            
+            } else {
+                
+                // 其他元素,移除元素属性
+                $elem = $(removeAttrs(elem));
+                return $('<div>').append($elem).html();
+            }
+        }
+
+        // 移除一个元素(子元素)的attr
+        function removeAttrs(elem) {
+            var attrs = elem.attributes || [];
+            var attrNames = [];
+            var exception = ['href', 'target', 'src', 'alt', 'rowspan', 'colspan']; //例外情况
+
+            // 先存储下elem中所有 attr 的名称
+            $.each(attrs, function (key, attr) {
+                if (attr && attr.nodeType === 2) {
+                    attrNames.push(attr.nodeName);
+                }
+            });
+            // 再根据名称删除所有attr
+            $.each(attrNames, function (key, attr) {
+                if (exception.indexOf(attr) < 0) {
+                    // 除了 exception 规定的例外情况,删除其他属性
+                    elem.removeAttribute(attr);
+                }
+            });
+
+
+            // 递归子节点
+            var children = elem.childNodes;
+            if (children.length) {
+                $.each(children, function (key, value) {
+                    removeAttrs(value);
+                });
+            }
+
+            return elem;
+        }
+    };
+
+    // 绑定 $txt.formatText() 方法
+    Txt.fn.bindFormatText = function () {
+        var self = this;
+        var editor = self.editor;
+        var $txt = self.$txt;
+        var legalTags = E.config.legalTags;
+        var legalTagArr = legalTags.split(',');
+        var length = legalTagArr.length;
+        var regArr = [];
+
+        // 将 E.config.legalTags 配置的有效字符,生成正则表达式
+        $.each(legalTagArr, function (k, tag) {
+            var reg = '\>\\s*\<(' + tag + ')\>';
+            regArr.push(new RegExp(reg, 'ig'));
+        });
+
+        // 增加 li 
+        regArr.push(new RegExp('\>\\s*\<(li)\>', 'ig'));
+
+        // 增加 tr
+        regArr.push(new RegExp('\>\\s*\<(tr)\>', 'ig'));
+
+        // 增加 code
+        regArr.push(new RegExp('\>\\s*\<(code)\>', 'ig'));
+
+        // 生成 formatText 方法
+        $txt.formatText = function () {
+            var $temp = $('<div>');
+            var html = $txt.html();
+
+            // 去除空格
+            html = html.replace(/\s*</ig, '<');
+
+            // 段落、表格之间换行
+            $.each(regArr, function (k, reg) {
+                if (!reg.test(html)) {
+                    return;
+                }
+                html = html.replace(reg, function (matchStr, tag) {
+                    return '>\n<' + tag + '>';
+                });
+            });
+
+            $temp.html(html);
+            return $temp.text();
+        };
+    };
+
+    // 定制 $txt.html 方法
+    Txt.fn.bindHtml = function () {
+        var self = this;
+        var editor = self.editor;
+        var $txt = self.$txt;
+        var $valueContainer = editor.$valueContainer;
+        var valueNodeName = editor.valueNodeName;
+
+        $txt.html = function (html) {
+            var result;
+
+            if (valueNodeName === 'div') {
+                // div 生成的编辑器,取值、赋值,都直接触发jquery的html方法
+                result = $.fn.html.call($txt, html);
+            }
+
+            // textarea 生成的编辑器,则需要考虑赋值时,也给textarea赋值
+
+            if (html === undefined) {
+                // 取值,直接触发jquery原生html方法
+                result = $.fn.html.call($txt);
+
+                // 替换 html 中,src和href属性中的 & 字符。
+                // 因为 .html() 或者 .innerHTML 会把所有的 & 字符都改成 &amp; 但是 src 和 href 中的要保持 &
+                result = result.replace(/(href|src)\=\"(.*)\"/igm, function (a, b, c) {
+                    return b + '="' + c.replace('&amp;', '&') + '"';
+                });
+            } else {
+                // 赋值,需要同时给 textarea 赋值
+                result = $.fn.html.call($txt, html);
+                $valueContainer.val(html);
+            }
+
+            if (html === undefined) {
+                return result;
+            } else {
+                // 手动触发 change 事件,因为 $txt 监控了 change 事件来判断是否需要执行 editor.onchange 
+                $txt.change();
+            }
+        };
+    };
+});
+// Txt.fn api
+_e(function (E, $) {
+
+    var Txt = E.Txt;
+
+    var txtChangeEventNames = 'propertychange change click keyup input paste';
+
+    // 渲染
+    Txt.fn.render = function () {
+        var $txt = this.$txt;
+        var $editorContainer = this.editor.$editorContainer;
+        $editorContainer.append($txt);
+    };
+
+    // 计算高度
+    Txt.fn.initHeight = function () {
+        var editor = this.editor;
+        var $txt = this.$txt;
+        var valueContainerHeight = editor.$valueContainer.height();
+        var menuHeight = editor.menuContainer.height();
+        var txtHeight = valueContainerHeight - menuHeight;
+
+        // 限制最小为 50px
+        txtHeight = txtHeight < 50 ? 50 : txtHeight;
+
+        $txt.height(txtHeight);
+
+        // 记录原始高度
+        editor.valueContainerHeight = valueContainerHeight;
+
+        // 设置 max-height
+        this.initMaxHeight(txtHeight, menuHeight);
+    };
+
+    // 计算最大高度
+    Txt.fn.initMaxHeight = function (txtHeight, menuHeight) {
+        var editor = this.editor;
+        var $menuContainer = editor.menuContainer.$menuContainer;
+        var $txt = this.$txt;
+        var $wrap = $('<div>');
+
+        // 需要浏览器支持 max-height,否则不管
+        if (window.getComputedStyle && 'max-height'in window.getComputedStyle($txt.get(0))) {
+            // 获取 max-height 并判断是否有值
+            var maxHeight = parseInt(editor.$valueContainer.css('max-height'));
+            if (isNaN(maxHeight)) {
+                return;
+            }
+
+            // max-height 和『全屏』暂时有冲突
+            if (editor.menus.fullscreen) {
+                E.warn('max-height和『全屏』菜单一起使用时,会有一些问题尚未解决,请暂时不要两个同时使用');
+                return;
+            }
+
+            // 标记
+            editor.useMaxHeight = true;
+
+            // 设置maxheight
+            $wrap.css({
+                'max-height': (maxHeight - menuHeight) + 'px',
+                'overflow-y': 'auto'
+            });
+            $txt.css({
+                'height': 'auto',
+                'overflow-y': 'visible',
+                'min-height': txtHeight + 'px'
+            });
+
+            // 滚动式,菜单阴影
+            $wrap.on('scroll', function () {
+                if ($txt.parent().scrollTop() > 10) {
+                    $menuContainer.addClass('wangEditor-menu-shadow');
+                } else {
+                    $menuContainer.removeClass('wangEditor-menu-shadow');
+                }
+            });
+
+            // 需在编辑器区域外面再包裹一层
+            $txt.wrap($wrap);
+        }
+    };
+
+    // 保存选区
+    Txt.fn.saveSelectionEvent = function () {
+        var $txt = this.$txt;
+        var editor = this.editor;
+        var timeoutId;
+        var dt = Date.now();
+
+        function save() {
+            editor.saveSelection();
+        }
+
+        // 同步保存选区
+        function saveSync() {
+            // 100ms之内,不重复保存
+            if (Date.now() - dt < 100) {
+                return;
+            }
+
+            dt = Date.now();
+            save();
+        }
+
+        // 异步保存选区
+        function saveAync() {
+            // 节流,防止高频率重复操作
+            if (timeoutId) {
+                clearTimeout(timeoutId);
+            }
+            timeoutId = setTimeout(save, 300);
+        }
+
+        // txt change 、focus、blur 时随时保存选区
+        $txt.on(txtChangeEventNames + ' focus blur', function (e) {
+            // 先同步保存选区,为了让接下来就马上要执行 editor.getRangeElem() 的程序
+            // 能够获取到正确的 rangeElem
+            saveSync();
+
+            // 再异步保存选区,为了确定更加准确的选区,为后续的操作做准备
+            saveAync();
+        });
+
+        // 鼠标拖拽选择时,可能会拖拽到编辑器区域外面再松手,此时 $txt 就监听不到 click事件了
+        $txt.on('mousedown', function () {
+            $txt.on('mouseleave.saveSelection', function (e) {
+                // 先同步后异步,如上述注释
+                saveSync();
+                saveAync();
+
+                // 顺道吧菜单状态也更新了
+                editor.updateMenuStyle();
+            });
+        }).on('mouseup', function () {
+            $txt.off('mouseleave.saveSelection');
+        });
+        
+    };
+
+    // 随时更新 value
+    Txt.fn.updateValueEvent = function () {
+        var $txt = this.$txt;
+        var editor = this.editor;
+        var timeoutId, oldValue;
+
+        // 触发 onchange 事件
+        function doOnchange() {
+            var val = $txt.html();
+            if (oldValue === val) {
+                // 无变化
+                return;
+            }
+
+            // 触发 onchange 事件
+            if (editor.onchange && typeof editor.onchange === 'function') {
+                editor.onchange.call(editor);
+            }
+
+            // 更新内容
+            editor.updateValue();
+
+            // 记录最新内容
+            oldValue = val;
+        }
+
+        // txt change 时随时更新内容
+        $txt.on(txtChangeEventNames, function (e) {
+            // 初始化
+            if (oldValue == null) {
+                oldValue = $txt.html();
+            }
+
+            // 监控内容变化(停止操作 100ms 之后立即执行)
+            if (timeoutId) {
+                clearTimeout(timeoutId);
+            }
+            timeoutId = setTimeout(doOnchange, 100);
+        });
+    };
+
+    // 随时更新 menustyle
+    Txt.fn.updateMenuStyleEvent = function () {
+        var $txt = this.$txt;
+        var editor = this.editor;
+
+        // txt change 时随时更新内容
+        $txt.on(txtChangeEventNames, function (e) {
+            editor.updateMenuStyle();
+        });
+    };
+
+    // 最后插入试图插入 <p><br><p>
+    Txt.fn.insertEmptyP = function () {
+        var $txt = this.$txt;
+        var $children = $txt.children();
+
+        if ($children.length === 0) {
+            $txt.append($('<p><br></p>'));
+            return;
+        }
+
+        if ($.trim($children.last().html()).toLowerCase() !== '<br>') {
+            $txt.append($('<p><br></p>'));
+        }
+    };
+
+    // 将编辑器暴露出来的文字和图片,都用 p 来包裹
+    Txt.fn.wrapImgAndText = function () {
+        var $txt = this.$txt;
+        var $imgs = $txt.children('img');
+        var txt = $txt[0];
+        var childNodes = txt.childNodes;
+        var childrenLength = childNodes.length;
+        var i, childNode, p;
+
+        // 处理图片
+        $imgs.length && $imgs.each(function () {
+            $(this).wrap('<p>');
+        });
+
+        // 处理文字
+        for (i = 0; i < childrenLength; i++) {
+            childNode = childNodes[i];
+            if (childNode.nodeType === 3 && childNode.textContent && $.trim(childNode.textContent)) {
+                $(childNode).wrap('<p>');
+            }
+        }
+    };
+
+    // 清空内容为空的<p>,以及重复包裹的<p>(在windows下的chrome粘贴文字之后,会出现上述情况)
+    Txt.fn.clearEmptyOrNestP = function () {
+        var $txt = this.$txt;
+        var $pList = $txt.find('p');
+
+        $pList.each(function () {
+            var $p = $(this);
+            var $children = $p.children();
+            var childrenLength = $children.length;
+            var $firstChild;
+            var content = $.trim($p.html());
+
+            // 内容为空的p
+            if (!content) {
+                $p.remove();
+                return;
+            }
+
+            // 嵌套的p
+            if (childrenLength === 1) {
+                $firstChild = $children.first();
+                if ($firstChild.get(0) && $firstChild.get(0).nodeName === 'P') {
+                    $p.html( $firstChild.html() );
+                }
+            }
+        });
+    };
+
+    // 获取 scrollTop
+    Txt.fn.scrollTop = function (val) {
+        var self = this;
+        var editor = self.editor;
+        var $txt = self.$txt;
+
+        if (editor.useMaxHeight) {
+            return $txt.parent().scrollTop(val);
+        } else {
+            return $txt.scrollTop(val);
+        }
+    };
+
+    // 鼠标hover时候,显示p、head的高度
+    Txt.fn.showHeightOnHover = function () {
+        var editor = this.editor;
+        var $editorContainer = editor.$editorContainer;
+        var menuContainer = editor.menuContainer;
+        var $txt = this.$txt;
+        var $tip = $('<i class="height-tip"><i>');
+        var isTipInTxt = false;
+
+        function addAndShowTip($target) {
+            if (!isTipInTxt) {
+                $editorContainer.append($tip);
+                isTipInTxt = true;
+            }
+
+            var txtTop = $txt.position().top;
+            var txtHeight = $txt.outerHeight();
+
+            var height = $target.height();
+            var top = $target.position().top;
+            var marginTop = parseInt($target.css('margin-top'), 10);
+            var paddingTop = parseInt($target.css('padding-top'), 10);
+            var marginBottom = parseInt($target.css('margin-bottom'), 10);
+            var paddingBottom = parseInt($target.css('padding-bottom'), 10);
+
+            // 计算初步的结果
+            var resultHeight = height + paddingTop + marginTop + paddingBottom + marginBottom;
+            var resultTop = top + menuContainer.height();
+            
+            // var spaceValue;
+
+            // // 判断是否超出下边界
+            // spaceValue = (resultTop + resultHeight) - (txtTop + txtHeight);
+            // if (spaceValue > 0) {
+            //     resultHeight = resultHeight - spaceValue;
+            // }
+
+            // // 判断是否超出了下边界
+            // spaceValue = txtTop > resultTop;
+            // if (spaceValue) {
+            //     resultHeight = resultHeight - spaceValue;
+            //     top = top + spaceValue;
+            // }
+
+            // 按照最终结果渲染
+            $tip.css({
+                height: height + paddingTop + marginTop + paddingBottom + marginBottom,
+                top: top + menuContainer.height()
+            });
+        }
+        function removeTip() {
+            if (!isTipInTxt) {
+                return;
+            }
+            $tip.remove();
+            isTipInTxt = false;
+        }
+
+        $txt.on('mouseenter', 'ul,ol,blockquote,p,h1,h2,h3,h4,h5,table,pre', function (e) {
+            addAndShowTip($(e.currentTarget));
+        }).on('mouseleave', function () {
+            removeTip();
+        });
+    };
+
+});
+// 工具函数
+_e(function (E, $) {
+
+    // IE8 [].indexOf()
+    if(!Array.prototype.indexOf){
+        //IE低版本不支持 arr.indexOf 
+        Array.prototype.indexOf = function(elem){
+            var i = 0,
+                length = this.length;
+            for(; i<length; i++){
+                if(this[i] === elem){
+                    return i;
+                }
+            }
+            return -1;
+        };
+        //IE低版本不支持 arr.lastIndexOf
+        Array.prototype.lastIndexOf = function(elem){
+            var length = this.length;
+            for(length = length - 1; length >= 0; length--){
+                if(this[length] === elem){
+                    return length;
+                }
+            }
+            return -1;
+        };
+    }
+
+    // IE8 Date.now()
+    if (!Date.now) {
+        Date.now = function () {
+            return new Date().valueOf(); 
+        };
+    }
+
+    // console.log && console.warn && console.error
+    var console = window.console;
+    var emptyFn = function () {};
+    $.each(['info', 'log', 'warn', 'error'], function (key, value) {
+        if (console == null) {
+            E[value] = emptyFn;
+        } else {
+            E[value] = function (info) {
+                // 通过配置来控制打印输出
+                if (E.config && E.config.printLog) {
+                    console[value]('wangEditor提示: ' + info);
+                }
+            };
+        }
+    });
+
+    // 获取随机数
+    E.random = function () {
+        return Math.random().toString().slice(2);
+    };
+
+    // 浏览器是否支持 placeholder
+    E.placeholder = 'placeholder' in document.createElement('input');
+
+    // 兼容IE8的 input placeholder
+    E.placeholderForIE8 = function ($container) {
+        if (E.placeholder) {
+            return;
+        }
+        $container.find('input[placeholder]').each(function () {
+            var $input = $(this);
+            var placeholder = $input.attr('placeholder');
+
+            if ($input.val() === '') {
+                $input.css('color', '#666');
+                $input.val(placeholder);
+
+                $input.on('focus.placeholder click.placeholder', function () {
+                    $input.val('');
+                    $input.css('color', '#333');
+                    $input.off('focus.placeholder click.placeholder');
+                });
+            }
+        });
+    };
+});
+// 语言包
+_e(function (E, $) {
+    E.langs = {};
+    
+    // 中文
+    E.langs['zh-cn'] = {
+        bold: '粗体',
+        underline: '下划线',
+        italic: '斜体',
+        forecolor: '文字颜色',
+        bgcolor: '背景色',
+        strikethrough: '删除线',
+        eraser: '清空格式',
+        source: '源码',
+        quote: '引用',
+        fontfamily: '字体',
+        fontsize: '字号',
+        head: '标题',
+        orderlist: '有序列表',
+        unorderlist: '无序列表',
+        alignleft: '左对齐',
+        aligncenter: '居中',
+        alignright: '右对齐',
+        link: '链接',
+        text: '文本',
+        submit: '提交',
+        cancel: '取消',
+        unlink: '取消链接',
+        table: '表格',
+        emotion: '表情',
+        img: '图片',
+        uploadImg: '上传图片',
+        linkImg: '网络图片',
+        video: '视频',
+        'width': '宽',
+        'height': '高',
+        location: '位置',
+        loading: '加载中',
+        searchlocation: '搜索位置',
+        dynamicMap: '动态地图',
+        clearLocation: '清除位置',
+        langDynamicOneLocation: '动态地图只能显示一个位置',
+        insertcode: '插入代码',
+        undo: '撤销',
+        redo: '重复',
+        fullscreen: '全屏',
+        openLink: '打开链接'
+    };
+
+    // 英文
+    E.langs.en = {
+        bold: 'Bold',
+        underline: 'Underline',
+        italic: 'Italic',
+        forecolor: 'Color',
+        bgcolor: 'Backcolor',
+        strikethrough: 'Strikethrough',
+        eraser: 'Eraser',
+        source: 'Codeview',
+        quote: 'Quote',
+        fontfamily: 'Font family',
+        fontsize: 'Font size',
+        head: 'Head',
+        orderlist: 'Ordered list',
+        unorderlist: 'Unordered list',
+        alignleft: 'Align left',
+        aligncenter: 'Align center',
+        alignright: 'Align right',
+        link: 'Insert link',
+        text: 'Text',
+        submit: 'Submit',
+        cancel: 'Cancel',
+        unlink: 'Unlink',
+        table: 'Table',
+        emotion: 'Emotions',
+        img: 'Image',
+        uploadImg: 'Upload',
+        linkImg: 'Link',
+        video: 'Video',
+        'width': 'width',
+        'height': 'height',
+        location: 'Location',
+        loading: 'Loading',
+        searchlocation: 'search',
+        dynamicMap: 'Dynamic',
+        clearLocation: 'Clear',
+        langDynamicOneLocation: 'Only one location in dynamic map',
+        insertcode: 'Insert Code',
+        undo: 'Undo',
+        redo: 'Redo',
+        fullscreen: 'Full screnn',
+        openLink: 'open link'
+    };
+});
+// 全局配置
+_e(function (E, $) {
+
+    E.config = {};
+
+    // 全屏时的 z-index
+    E.config.zindex = 10000;
+
+    // 是否打印log
+    E.config.printLog = true;
+
+    // 菜单吸顶:false - 不吸顶;number - 吸顶,值为top值
+    E.config.menuFixed = 0;
+
+    // 编辑源码时,过滤 javascript
+    E.config.jsFilter = true;
+
+    // 编辑器允许的标签
+    E.config.legalTags = 'p,h1,h2,h3,h4,h5,h6,blockquote,table,ul,ol,pre';
+
+    // 语言包
+    E.config.lang = E.langs['zh-cn'];
+
+    // 菜单配置
+    E.config.menus = [
+        'source',
+        '|',
+        'bold',
+        'underline',
+        'italic',
+        'strikethrough',
+        'eraser',
+        'forecolor',
+        'bgcolor',
+        '|',
+        'quote',
+        'fontfamily',
+        'fontsize',
+        'head',
+        'unorderlist',
+        'orderlist',
+        'alignleft',
+        'aligncenter',
+        'alignright',
+        '|',
+        'link',
+        'unlink',
+        'table',
+        'emotion',
+        '|',
+        'img',
+        'video',
+        'location',
+        'insertcode',
+        '|',
+        'undo',
+        'redo',
+        'fullscreen'
+    ];
+
+    // 颜色配置
+    E.config.colors = {
+        // 'value': 'title'
+        '#880000': '暗红色',
+        '#800080': '紫色',
+        '#ff0000': '红色',
+        '#ff00ff': '鲜粉色',
+        '#000080': '深蓝色',
+        '#0000ff': '蓝色',
+        '#00ffff': '湖蓝色',
+        '#008080': '蓝绿色',
+        '#008000': '绿色',
+        '#808000': '橄榄色',
+        '#00ff00': '浅绿色',
+        '#ffcc00': '橙黄色',
+        '#808080': '灰色',
+        '#c0c0c0': '银色',
+        '#000000': '黑色',
+        '#ffffff': '白色'
+    };
+
+    // 字体
+    E.config.familys = [
+        '宋体', '黑体', '楷体', '微软雅黑',
+        'Arial', 'Verdana', 'Georgia',
+        'Times New Roman', 'Microsoft JhengHei',
+        'Trebuchet MS', 'Courier New', 'Impact', 'Comic Sans MS', 'Consolas'
+    ];
+
+    // 字号
+    E.config.fontsizes = {
+        // 格式:'value': 'title'
+        1: '12px',
+        2: '13px',
+        3: '16px',
+        4: '18px',
+        5: '24px',
+        6: '32px',
+        7: '48px'
+    };
+
+    // 表情包
+    E.config.emotionsShow = 'icon'; // 显示项,默认为'icon',也可以配置成'value'
+    E.config.emotions = {
+        // 'default': {
+        //     title: '默认',
+        //     data: './emotions.data'
+        // },
+        'weibo': {
+            title: '微博表情',
+            data: [
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/7a/shenshou_thumb.gif',
+                    value: '[草泥马]'    
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/60/horse2_thumb.gif',
+                    value: '[神马]'    
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/fuyun_thumb.gif',
+                    value: '[浮云]'    
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c9/geili_thumb.gif',
+                    value: '[给力]'    
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/f2/wg_thumb.gif',
+                    value: '[围观]'    
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/70/vw_thumb.gif',
+                    value: '[威武]'
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/6e/panda_thumb.gif',
+                    value: '[熊猫]'
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/81/rabbit_thumb.gif',
+                    value: '[兔子]'
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/otm_thumb.gif',
+                    value: '[奥特曼]'
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/15/j_thumb.gif',
+                    value: '[囧]'
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/89/hufen_thumb.gif',
+                    value: '[互粉]'
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c4/liwu_thumb.gif',
+                    value: '[礼物]'
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/ac/smilea_thumb.gif',
+                    value: '[呵呵]'
+                },
+                {
+                    icon: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/0b/tootha_thumb.gif',
+                    value: '[哈哈]'
+                }
+            ]
+        }
+    };
+
+    // 百度地图的key
+    E.config.mapAk = 'TVhjYjq1ICT2qqL5LdS8mwas';
+
+    // 上传图片的配置
+    // server地址
+    E.config.uploadImgUrl = '';
+    // 超时时间
+    E.config.uploadTimeout = 20 * 1000;
+    // 用于存储上传回调事件
+    E.config.uploadImgFns = {};
+    // 自定义上传图片的filename
+    // E.config.uploadImgFileName = 'customFileName';
+
+    // 自定义上传,设置为 true 之后,显示上传图标
+    E.config.customUpload = false;
+    // 自定义上传的init事件
+    // E.config.customUploadInit = function () {....};
+
+    // 自定义上传时传递的参数(如 token)
+    E.config.uploadParams = {
+        /* token: 'abcdef12345' */
+    };
+
+    // 自定义上传是的header参数
+    E.config.uploadHeaders = {
+         /* 'Accept' : 'text/x-json' */
+    };
+
+    // 跨域上传时传递 cookie,默认为 true
+    E.config.withCredentials = true;
+
+    // 隐藏网络图片,默认为 false
+    E.config.hideLinkImg = false;
+
+    // 是否过滤粘贴内容
+    E.config.pasteFilter = true;
+
+    // 是否粘贴纯文本,当 editor.config.pasteFilter === false 时候,此配置将失效
+    E.config.pasteText = false;
+
+    // 插入代码时,默认的语言
+    E.config.codeDefaultLang = 'javascript';
+
+});
+// 全局UI
+_e(function (E, $) {
+
+     E.UI = {};
+
+     // 为菜单自定义配置的UI
+     E.UI.menus = {
+        // 这个 default 不加引号,在 IE8 会报错
+        'default': {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-command"></i></a>',
+            selected: '.selected'
+        },
+        bold: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-bold"></i></a>',
+            selected: '.selected'
+        },
+        underline: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-underline"></i></a>',
+            selected: '.selected'
+        },
+        italic: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-italic"></i></a>',
+            selected: '.selected'
+        },
+        forecolor: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-pencil"></i></a>',
+            selected: '.selected'
+        },
+        bgcolor: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-brush"></i></a>',
+            selected: '.selected'
+        },
+        strikethrough: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-strikethrough"></i></a>',
+            selected: '.selected'
+        },
+        eraser: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-eraser"></i></a>',
+            selected: '.selected'
+        },
+        quote: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-quotes-left"></i></a>',
+            selected: '.selected'
+        },
+        source: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-code"></i></a>',
+            selected: '.selected'
+        },
+        fontfamily: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-font2"></i></a>',
+            selected: '.selected'
+        },
+        fontsize: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-text-height"></i></a>',
+            selected: '.selected'
+        },
+        head: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-header"></i></a>',
+            selected: '.selected'
+        },
+        orderlist: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-list-numbered"></i></a>',
+            selected: '.selected'
+        },
+        unorderlist: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-list-bullet"></i></a>',
+            selected: '.selected'
+        },
+        alignleft: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-left"></i></a>',
+            selected: '.selected'
+        },
+        aligncenter: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-center"></i></a>',
+            selected: '.selected'
+        },
+        alignright: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-right"></i></a>',
+            selected: '.selected'
+        },
+        link: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-link"></i></a>',
+            selected: '.selected'
+        },
+        unlink: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-unlink"></i></a>',
+            selected: '.selected'
+        },
+        table: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-table"></i></a>',
+            selected: '.selected'
+        },
+        emotion: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-happy"></i></a>',
+            selected: '.selected'
+        },
+        img: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-picture"></i></a>',
+            selected: '.selected'
+        },
+        video: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-play"></i></a>',
+            selected: '.selected'
+        },
+        location: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-location"></i></a>',
+            selected: '.selected'
+        },
+        insertcode: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-terminal"></i></a>',
+            selected: '.selected'
+        },
+        undo: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-ccw"></i></a>',
+            selected: '.selected'
+        },
+        redo: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-cw"></i></a>',
+            selected: '.selected'
+        },
+        fullscreen: {
+            normal: '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-enlarge2"></i></a>',
+            selected: '<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-shrink2"></i></a>'
+        }
+     };
+     
+});
+// 对象配置
+_e(function (E, $) {
+
+    E.fn.initDefaultConfig = function () {
+        var editor = this;
+        editor.config = $.extend({}, E.config);
+        editor.UI = $.extend({}, E.UI);
+    };
+
+});
+// 增加 container
+_e(function (E, $) {
+
+    E.fn.addEditorContainer = function () {
+        this.$editorContainer = $('<div class="wangEditor-container"></div>');
+    };
+
+});
+// 增加编辑区域对象
+_e(function (E, $) {
+
+    E.fn.addTxt = function () {
+        var editor = this;
+        var txt = new E.Txt(editor);
+
+        editor.txt = txt;
+    };
+
+});
+// 增加menuContainer对象
+_e(function (E, $) {
+
+    E.fn.addMenuContainer = function () {
+        var editor = this;
+        editor.menuContainer = new E.MenuContainer(editor);
+    };
+
+});
+// 增加menus
+_e(function (E, $) {
+
+    // 存储创建菜单的函数
+    E.createMenuFns = [];
+    E.createMenu = function (fn) {
+        E.createMenuFns.push(fn);
+    };
+
+    // 创建所有菜单
+    E.fn.addMenus = function () {
+        var editor = this;
+        var menuIds = editor.config.menus;
+
+        // 检验 menuId 是否在配置中存在
+        function check(menuId) {
+            if (menuIds.indexOf(menuId) >= 0) {
+                return true;
+            }
+            return false;
+        }
+
+        // 遍历所有的菜单创建函数,并执行
+        $.each(E.createMenuFns, function (k, createMenuFn) {
+            createMenuFn.call(editor, check);
+        });
+    };
+
+});
+// bold菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'bold';
+        if (!check(menuId)) {
+            return;
+        }
+
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.bold,
+            commandName: 'Bold'
+        });
+
+        // 定义选中状态下的click事件
+        menu.clickEventSelected = function (e) {
+            var isRangeEmpty = editor.isRangeEmpty();
+            if (!isRangeEmpty) {
+                // 如果选区有内容,则执行基础命令
+                editor.command(e, 'Bold');
+            } else {
+                // 如果选区没有内容
+                editor.commandForElem('b,strong,h1,h2,h3,h4,h5', e, 'Bold');
+            }
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// underline菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'underline';
+        if (!check(menuId)) {
+            return;
+        }
+
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.underline,
+            commandName: 'Underline'
+        });
+
+        // 定义选中状态下的click事件
+        menu.clickEventSelected = function (e) {
+            var isRangeEmpty = editor.isRangeEmpty();
+            if (!isRangeEmpty) {
+                // 如果选区有内容,则执行基础命令
+                editor.command(e, 'Underline');
+            } else {
+                // 如果选区没有内容
+                editor.commandForElem('u,a', e, 'Underline');
+            }
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// italic 菜单
+_e(function (E, $) {
+    
+    E.createMenu(function (check) {
+        var menuId = 'italic';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.italic,
+            commandName: 'Italic'
+        });
+
+        // 定义选中状态下的click事件
+        menu.clickEventSelected = function (e) {
+            var isRangeEmpty = editor.isRangeEmpty();
+            if (!isRangeEmpty) {
+                // 如果选区有内容,则执行基础命令
+                editor.command(e, 'Italic');
+            } else {
+                // 如果选区没有内容
+                editor.commandForElem('i', e, 'Italic');
+            }
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// forecolor 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'forecolor';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+        var configColors = editor.config.colors;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.forecolor
+        });
+
+        // 创建 dropPanel
+        var $content = $('<div></div>');
+        $.each(configColors, function (k, v) {
+            $content.append(
+                [
+                    '<a href="#" class="color-item"',
+                    '    title="' + v + '" commandValue="' + k + '" ',
+                    '    style="color: ' + k + '" ',
+                    '><i class="wangeditor-menu-img-pencil"></i></a>'
+                ].join('')
+            );
+        });
+        $content.on('click', 'a[commandValue]', function (e) {
+            // 执行命令
+            var $elem = $(this);
+            var commandValue = $elem.attr('commandValue');
+
+            if (menu.selected && editor.isRangeEmpty()) {
+                // 当前处于选中状态,并且选中内容为空
+                editor.commandForElem('font[color]', e, 'forecolor', commandValue);
+            } else {
+                // 当前未处于选中状态,或者有选中内容。则执行默认命令
+                editor.command(e, 'forecolor', commandValue);
+            }
+        });
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $content,
+            width: 125
+        });
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[color]');
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// bgcolor 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'bgcolor';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+        var configColors = editor.config.colors;
+
+        // 检查元素是否有 background-color: 内联样式
+        function checkElemFn(elem) {
+            var cssText;
+            if (elem && elem.style && elem.style.cssText != null) {
+                cssText = elem.style.cssText;
+                if (cssText && cssText.indexOf('background-color:') >= 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.bgcolor
+        });
+
+        // 创建 dropPanel
+        var $content = $('<div></div>');
+        $.each(configColors, function (k, v) {
+            $content.append(
+                [
+                    '<a href="#" class="color-item"',
+                    '    title="' + v + '" commandValue="' + k + '" ',
+                    '    style="color: ' + k + '" ',
+                    '><i class="wangeditor-menu-img-brush"></i></a>'
+                ].join('')
+            );
+        });
+        $content.on('click', 'a[commandValue]', function (e) {
+            // 执行命令
+
+            var $elem = $(this);
+            var commandValue = $elem.attr('commandValue');
+
+            if (menu.selected && editor.isRangeEmpty()) {
+                // 当前处于选中状态,并且选中内容为空。使用 commandForElem 执行命令
+                editor.commandForElem({
+                    selector: 'span,font',
+                    check: checkElemFn
+                }, e, 'BackColor', commandValue);
+            } else {
+                // 当前未处于选中状态,或者有选中内容。则执行默认命令
+                editor.command(e, 'BackColor', commandValue);
+            }
+        });
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $content,
+            width: 125
+        });
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'span,font', checkElemFn);
+            
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// strikethrough 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'strikethrough';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.strikethrough,
+            commandName: 'StrikeThrough'
+        });
+
+        // 定义选中状态下的click事件
+        menu.clickEventSelected = function (e) {
+            var isRangeEmpty = editor.isRangeEmpty();
+            if (!isRangeEmpty) {
+                // 如果选区有内容,则执行基础命令
+                editor.command(e, 'StrikeThrough');
+            } else {
+                // 如果选区没有内容
+                editor.commandForElem('strike', e, 'StrikeThrough');
+            }
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// eraser 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'eraser';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.eraser,
+            commandName: 'RemoveFormat'
+        });
+
+        // 定义点击事件
+        menu.clickEvent = function (e) {
+            var isRangeEmpty = editor.isRangeEmpty();
+
+            if (!isRangeEmpty) {
+                // 选区不是空的,则执行默认命令
+                editor.command(e, 'RemoveFormat');
+                return;
+            }
+
+            var $clearElem;
+
+            // 自定义的命令函数
+            function commandFn() {
+                var editor = this;
+                var rangeElem;
+                var pElem, $pElem;
+                var quoteElem, $quoteElem;
+                var listElem, $listElem;
+
+                // 获取选区 elem
+                rangeElem = editor.getRangeElem();
+                // 第一步,获取 quote 父元素
+                quoteElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
+                if (quoteElem) {
+                    $quoteElem = $(quoteElem);
+                    $clearElem = $('<p>' + $quoteElem.text() + '</p>');
+                    $quoteElem.after($clearElem).remove();
+                }
+                // 第二步,获取 p h 父元素
+                pElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5');
+                if (pElem) {
+                    $pElem = $(pElem);
+                    $clearElem = $('<p>' + $pElem.text() + '</p>');
+                    $pElem.after($clearElem).remove();
+                }
+                // 第三步,获取list
+                listElem = editor.getSelfOrParentByName(rangeElem, 'ul,ol');
+                if (listElem) {
+                    $listElem = $(listElem);
+                    $clearElem = $('<p>' + $listElem.text() + '</p>');
+                    $listElem.after($clearElem).remove();
+                }
+            }
+
+            // 自定义 callback 事件
+            function callback() {
+                // callback中,设置range为clearElem
+                var editor = this;
+                if ($clearElem) {
+                    editor.restoreSelectionByElem($clearElem.get(0));
+                }
+            }
+
+            // 执行自定义命令
+            editor.customCommand(e, commandFn, callback);
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// source 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'source';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+        var txtHtml;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.source
+        });
+
+        menu.isShowCode = false;
+
+        // 更新内容
+        function updateValue() {
+            var $code = menu.$codeTextarea;
+            var $txt = editor.txt.$txt;
+            var value = $.trim($code.val()); // 取值
+
+            if (!value) {
+                value = '<p><br></p>';
+            }
+            
+            // 过滤js代码
+            if (editor.config.jsFilter) {
+                
+                value = value.replace(/<script[\s\S]*?<\/script>/ig, '');
+            }
+            // 赋值
+            try {
+                $txt.html(value);
+            } catch (ex) {
+                // 更新 html 源码出错,一般都是取消了 js 过滤之后,js报错导致的
+            }
+        }
+
+        // 定义click事件
+        menu.clickEvent = function (e) {
+            var self = this;
+            var editor = self.editor;
+            var $txt = editor.txt.$txt;
+            var txtOuterHeight = $txt.outerHeight();
+            var txtHeight = $txt.height();
+
+            if (!self.$codeTextarea) {
+                self.$codeTextarea = $('<textarea class="code-textarea"></textarea>');
+            }
+            var $code = self.$codeTextarea;
+            $code.css({
+                height: txtHeight,
+                'margin-top': txtOuterHeight - txtHeight
+            });
+
+            // 赋值
+            $code.val($txt.html());
+
+            // 监控变化
+            $code.on('change', function (e) {
+                updateValue();
+            });
+
+            // 渲染
+            $txt.after($code).hide();
+            $code.show();
+
+            // 更新状态
+            menu.isShowCode = true;
+
+            // 执行 updateSelected 事件
+            this.updateSelected();
+
+            // 禁用其他菜单
+            editor.disableMenusExcept('source');
+
+            // 记录当前html值
+            txtHtml = $txt.html();
+        };
+
+        // 定义选中状态下的click事件
+        menu.clickEventSelected = function (e) {
+            var self = this;
+            var editor = self.editor;
+            var $txt = editor.txt.$txt;
+            var $code = self.$codeTextarea;
+            var value;
+
+            if (!$code) {
+                return;
+            }
+
+            // 更新内容
+            updateValue();
+
+            // 渲染
+            $code.after($txt).hide();
+            $txt.show();
+
+            // 更新状态
+            menu.isShowCode = false;
+
+            // 执行 updateSelected 事件
+            this.updateSelected();
+
+            // 启用其他菜单
+            editor.enableMenusExcept('source');
+
+            // 判断是否执行 onchange 事件
+            if ($txt.html() !== txtHtml) {
+                if (editor.onchange && typeof editor.onchange === 'function') {
+                    editor.onchange.call(editor);
+                }
+            }
+        };
+
+        // 定义切换选中状态事件
+        menu.updateSelectedEvent = function () {
+            return this.isShowCode;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// quote 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'quote';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.quote,
+            commandName: 'formatBlock',
+            commandValue: 'blockquote'
+        });
+
+        // 定义click事件
+        menu.clickEvent = function (e) {
+            var rangeElem = editor.getRangeElem();
+            var $rangeElem;
+            if (!rangeElem) {
+                e.preventDefault();
+                return;
+            }
+            var currentQuote = editor.getSelfOrParentByName(rangeElem, 'blockquote');
+            var $quote;
+
+            if (currentQuote) {
+                // 说明当前在quote之内,不做任何处理
+                e.preventDefault();
+                return;
+            }
+
+            rangeElem = editor.getLegalTags(rangeElem);
+            $rangeElem = $(rangeElem);
+
+            // 无文字,则不允许执行引用
+            if (!$rangeElem.text()) {
+                return;
+            }
+
+
+            if (!rangeElem) {
+                // 执行默认命令
+                // IE8 下执行此处(不过,经测试代码无效,也不报错)
+                editor.command(e, 'formatBlock', 'blockquote');
+                return;
+            }
+
+            // 自定义command事件
+            function commandFn() {
+                $quote = $('<p>' + $rangeElem.text() + '</p>');
+                $rangeElem.after($quote).remove();
+                $quote.wrap('<blockquote>');
+            }
+
+            // 自定义 callback 事件
+            function callback() {
+                // callback中,设置range为quote
+                var editor = this;
+                if ($quote) {
+                    editor.restoreSelectionByElem($quote.get(0));
+                }
+            }
+
+            // 执行自定义命令
+            editor.customCommand(e, commandFn, callback);
+        };
+
+        // 定义选中状态下的click事件
+        menu.clickEventSelected = function (e) {
+            var rangeElem;
+            var quoteElem;
+            var $lastChild;
+
+            // 获取当前选区的elem,并试图往上找 quote 元素
+            rangeElem = editor.getRangeElem();
+            quoteElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
+            if (!quoteElem) {
+                // 没找到,则返回
+                e.preventDefault();
+                return;
+            }
+
+            // 自定义的command事件
+            function commandFn() {
+                var $quoteElem;
+                var $children;
+
+                $quoteElem = $(quoteElem);
+                $children = $quoteElem.children();
+                if ($children.length) {
+                    $children.each(function (k) {
+                        var $item = $(this);
+                        if ($item.get(0).nodeName === 'P') {
+                            $quoteElem.after($item);
+                        } else {
+                            $quoteElem.after('<p>' + $item.text() + '</p>');
+                        }
+                        $lastChild = $item;  // 记录最后一个子元素,用于callback中的range定位
+                    });
+                    $quoteElem.remove();
+                    return;
+                }
+            }
+
+            // 自定义的callback函数
+            function callback() {
+                // callback中,设置range为lastChild
+                var editor = this;
+                if ($lastChild) {
+                    editor.restoreSelectionByElem($lastChild.get(0));
+                }
+            }
+
+            // 执行自定义命令
+            editor.customCommand(e, commandFn, callback);
+        };
+
+        // 定义更新选中状态的事件
+        menu.updateSelectedEvent = function () {
+            var self = this; //菜单对象
+            var editor = self.editor;
+            var rangeElem;
+
+            rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
+
+            if (rangeElem) {
+                return true;
+            }
+
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+
+        // --------------- 两次点击 enter 跳出引用 ---------------
+        editor.ready(function () {
+            var editor = this;
+            var $txt = editor.txt.$txt;
+            var isPrevEnter = false;  // 是不是刚刚在quote中按了 enter 键
+            $txt.on('keydown', function (e) {
+                if (e.keyCode !== 13) {
+                    // 不是 enter 键
+                    isPrevEnter = false;
+                    return;
+                }
+
+                var rangeElem = editor.getRangeElem();
+                rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
+                if (!rangeElem) {
+                    // 选区不是 quote
+                    isPrevEnter = false;
+                    return;
+                }
+
+                if (!isPrevEnter) {
+                    // 最近没有在qote中按enter键
+                    isPrevEnter = true;
+                    return;
+                }
+
+                var currentRangeElem = editor.getRangeElem();
+                var $currentRangeElem = $(currentRangeElem);
+                if ($currentRangeElem.length) {
+                    $currentRangeElem.parent().after($currentRangeElem);
+                }
+
+                // 设置选区
+                editor.restoreSelectionByElem(currentRangeElem, 'start');
+
+                isPrevEnter = false;
+                // 阻止默认行文
+                e.preventDefault();
+
+            });
+        }); // editor.ready(
+
+        // --------------- 处理quote中无内容时不能删除的问题 ---------------
+        editor.ready(function () {
+            var editor = this;
+            var $txt = editor.txt.$txt;
+            var $rangeElem;
+
+            function commandFn() {
+                $rangeElem && $rangeElem.remove();
+            }
+            function callback() {
+                if (!$rangeElem) {
+                    return;
+                }
+                var $prev = $rangeElem.prev();
+                if ($prev.length) {
+                    // 有 prev 则定位到 prev 最后
+                    editor.restoreSelectionByElem($prev.get(0));
+                } else {
+                    // 无 prev 则初始化选区
+                    editor.initSelection();
+                }
+            }
+
+            $txt.on('keydown', function (e) {
+                if (e.keyCode !== 8) {
+                    // 不是 backspace 键
+                    return;
+                }
+
+                var rangeElem = editor.getRangeElem();
+                rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote');
+                if (!rangeElem) {
+                    // 选区不是 quote
+                    return;
+                }
+                $rangeElem = $(rangeElem);
+
+                var text = $rangeElem.text();
+                if (text) {
+                    // quote 中还有内容
+                    return;
+                }
+                editor.customCommand(e, commandFn, callback);
+
+            }); // $txt.on
+        }); // editor.ready(
+    });
+
+});
+// 字体 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'fontfamily';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+        var configFamilys = editor.config.familys;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.fontfamily,
+            commandName: 'fontName'
+        });
+
+        // 初始化数据
+        var data  = {};
+        /*
+            data 需要的结构
+            {
+                'commandValue': 'title'
+                ...
+            }
+        */
+        $.each(configFamilys, function (k, v) {
+            // configFamilys 是数组,data 是对象
+            data[v] = v;
+        });
+
+        // 创建droplist
+        var tpl = '<span style="font-family:{#commandValue};">{#title}</span>';
+        menu.dropList = new E.DropList(editor, menu, {
+            data: data,
+            tpl: tpl,
+            selectorForELemCommand: 'font[face]'  // 为了执行 editor.commandForElem 而传入的elem查询方式
+        });
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[face]');
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+});
+// 字号 菜单
+_e(function (E, $) {
+    E.createMenu(function (check) {
+        var menuId = 'fontsize';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+        var configSize = editor.config.fontsizes;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.fontsize,
+            commandName: 'fontSize'
+        });
+
+        // 初始化数据
+        var data  = configSize;
+        /*
+            data 需要的结构
+            {
+                'commandValue': 'title'
+                ...
+            }
+        */
+
+        // 创建droplist
+        var tpl = '<span style="font-size:{#title};">{#title}</span>';
+        menu.dropList = new E.DropList(editor, menu, {
+            data: data,
+            tpl: tpl,
+            selectorForELemCommand: 'font[size]'  // 为了执行 editor.commandForElem 而传入的elem查询方式
+        });
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[size]');
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+});
+// head 菜单
+_e(function (E, $) {
+    E.createMenu(function (check) {
+        var menuId = 'head';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.head,
+            commandName: 'formatBlock'
+        });
+
+        // 初始化数据
+        var data  = {
+            '<h1>': '标题1',
+            '<h2>': '标题2',
+            '<h3>': '标题3',
+            '<h4>': '标题4',
+            '<h5>': '标题5'
+        };
+        /*
+            data 需要的结构
+            {
+                'commandValue': 'title'
+                ...
+            }
+        */
+
+        var isOrderedList;
+        function beforeEvent(e) {
+            if (editor.queryCommandState('InsertOrderedList')) {
+                isOrderedList = true;
+
+                // 先取消有序列表
+                editor.command(e, 'InsertOrderedList');
+            } else {
+                isOrderedList = false;
+            }
+        }
+
+        function afterEvent(e) {
+            if (isOrderedList) {
+                // 再设置有序列表
+                editor.command(e, 'InsertOrderedList');
+            }
+        }
+
+        // 创建droplist
+        var tpl = '{#commandValue}{#title}';
+        menu.dropList = new E.DropList(editor, menu, {
+            data: data,
+            tpl: tpl,
+            // 对 ol 直接设置 head,会出现每个 li 的 index 都变成 1 的问题,因此要先取消 ol,然后设置 head,最后再增加上 ol
+            beforeEvent: beforeEvent,
+            afterEvent: afterEvent
+        });
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'h1,h2,h3,h4,h5');
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+});
+// unorderlist 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'unorderlist';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.unorderlist,
+            commandName: 'InsertUnorderedList'
+        });
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// orderlist 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'orderlist';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.orderlist,
+            commandName: 'InsertOrderedList'
+        });
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// alignleft 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'alignleft';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.alignleft,
+            commandName: 'JustifyLeft'
+        });
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5,li', function (elem) {
+                var cssText;
+                if (elem && elem.style && elem.style.cssText != null) {
+                    cssText = elem.style.cssText;
+                    if (cssText && /text-align:\s*left;/.test(cssText)) {
+                        return true;
+                    }
+                }
+                if ($(elem).attr('align') === 'left') {
+                    // ff 中,设置align-left之后,会是 <p align="left">xxx</p>
+                    return true;
+                }
+                return false;
+            });
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// aligncenter 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'aligncenter';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.aligncenter,
+            commandName: 'JustifyCenter'
+        });
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5,li', function (elem) {
+                var cssText;
+                if (elem && elem.style && elem.style.cssText != null) {
+                    cssText = elem.style.cssText;
+                    if (cssText && /text-align:\s*center;/.test(cssText)) {
+                        return true;
+                    }
+                }
+                if ($(elem).attr('align') === 'center') {
+                    // ff 中,设置align-center之后,会是 <p align="center">xxx</p>
+                    return true;
+                }
+                return false;
+            });
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// alignright 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'alignright';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.alignright,
+            commandName: 'JustifyRight'
+        });
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5,li', function (elem) {
+                var cssText;
+                if (elem && elem.style && elem.style.cssText != null) {
+                    cssText = elem.style.cssText;
+                    if (cssText && /text-align:\s*right;/.test(cssText)) {
+                        return true;
+                    }
+                }
+                if ($(elem).attr('align') === 'right') {
+                    // ff 中,设置align-right之后,会是 <p align="right">xxx</p>
+                    return true;
+                }
+                return false;
+            });
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// link 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'link';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.link
+        });
+
+        // 创建 dropPanel
+        var $content = $('<div></div>');
+        var $div1 = $('<div style="margin:20px 10px;" class="clearfix"></div>');
+        var $div2 = $div1.clone();
+        var $div3 = $div1.clone().css('margin', '0 10px');
+        var $textInput = $('<input type="text" class="block" placeholder="' + lang.text + '"/>');
+        var $urlInput = $('<input type="text" class="block" placeholder="' + lang.link + '"/>');
+        var $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
+        var $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');
+
+        $div1.append($textInput);
+        $div2.append($urlInput);
+        $div3.append($btnSubmit).append($btnCancel);
+        $content.append($div1).append($div2).append($div3);
+        
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $content,
+            width: 300
+        });
+
+        // 定义click事件
+        menu.clickEvent = function (e) {
+            var menu = this;
+            var dropPanel = menu.dropPanel;
+
+            // -------------隐藏----------------
+            if (dropPanel.isShowing) {
+                dropPanel.hide();
+                return;
+            }
+
+            // -------------显示----------------
+
+            // 重置 input
+            $textInput.val('');
+            $urlInput.val('http://');
+
+            // 获取url
+            var url = '';
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'a');
+            if (rangeElem) {
+                url = rangeElem.href || '';
+            }
+
+            // 获取 text
+            var text = '';
+            var isRangeEmpty = editor.isRangeEmpty();
+            if (!isRangeEmpty) {
+                // 选区不是空
+                text = editor.getRangeText() || '';
+            } else if (rangeElem) {
+                // 如果选区空,并且在 a 标签之内
+                text = rangeElem.textContent || rangeElem.innerHTML;
+            }
+
+            // 设置 url 和 text
+            url && $urlInput.val(url);
+            text && $textInput.val(text);
+
+            // 如果有选区内容,textinput 不能修改
+            if (!isRangeEmpty) {
+                $textInput.attr('disabled', true);
+            } else {
+                $textInput.removeAttr('disabled');
+            }
+
+            // 显示(要设置好了所有input的值和属性之后再显示)
+            dropPanel.show();
+        };
+
+        // 定义 update selected 事件
+        menu.updateSelectedEvent = function () {
+            var rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'a');
+            if (rangeElem) {
+                return true;
+            }
+            return false;
+        };
+
+        // 『取消』 按钮
+        $btnCancel.click(function (e) {
+            e.preventDefault();
+            menu.dropPanel.hide();
+        });
+
+        // 『确定』按钮
+        $btnSubmit.click(function (e) {
+            e.preventDefault();
+            var rangeElem = editor.getRangeElem();
+            var targetElem = editor.getSelfOrParentByName(rangeElem, 'a');
+            var isRangeEmpty = editor.isRangeEmpty();
+
+            var $linkElem, linkHtml;
+            var commandFn, callback;
+            var $txt = editor.txt.$txt;
+            var $oldLinks, $newLinks;
+            var uniqId = 'link' + E.random();
+
+            // 获取数据
+            var url = $.trim($urlInput.val());
+            var text = $.trim($textInput.val());
+
+            if (!url) {
+                menu.dropPanel.focusFirstInput();
+                return;
+            }
+            if (!text) {
+                text = url;
+            }
+
+            if (!isRangeEmpty) {
+                // 选中区域有内容,则执行默认命令
+
+                // 获取目前 txt 内所有链接,并为当前链接做一个标记
+                $oldLinks = $txt.find('a');
+                $oldLinks.attr(uniqId, '1');
+
+                // 执行命令 
+                editor.command(e, 'createLink', url);
+
+                // 去的没有标记的链接,即刚刚插入的链接
+                $newLinks = $txt.find('a').not('[' + uniqId + ']');
+                $newLinks.attr('target', '_blank'); // 增加 _blank
+
+                // 去掉之前做的标记
+                $oldLinks.removeAttr(uniqId);
+
+            } else if (targetElem) {
+                // 无选中区域,在 a 标签之内,修改该 a 标签的内容和链接
+                $linkElem = $(targetElem);
+                commandFn = function () {
+                    $linkElem.attr('href', url);
+                    $linkElem.text(text);
+                };
+                callback = function () {
+                    var editor = this;
+                    editor.restoreSelectionByElem(targetElem);
+                };
+                // 执行命令
+                editor.customCommand(e, commandFn, callback);
+            } else {
+                // 无选中区域,不在 a 标签之内,插入新的链接
+
+                linkHtml = '<a href="' + url + '" target="_blank">' + text + '</a>';
+                if (E.userAgent.indexOf('Firefox') > 0) {
+                    linkHtml += '<span>&nbsp;</span>';
+                }
+                editor.command(e, 'insertHtml', linkHtml);
+            }
+
+        });
+        
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// unlink 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'unlink';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.unlink,
+            commandName: 'unLink'
+        });
+
+        // click 事件
+        menu.clickEvent = function  (e) {
+            var isRangeEmpty = editor.isRangeEmpty();
+            if (!isRangeEmpty) {
+                // 有选中区域,或者IE8,执行默认命令
+                editor.command(e, 'unLink');
+                return;
+            }
+
+            // 无选中区域...
+
+            var rangeElem = editor.getRangeElem();
+            var aElem = editor.getSelfOrParentByName(rangeElem, 'a');
+            if (!aElem) {
+                // 不在 a 之内,返回
+                e.preventDefault();
+                return;
+            }
+
+            // 在 a 之内
+            var $a = $(aElem);
+            var $span = $('<span>' + $a.text() + '</span>');
+            function commandFn() {
+                $a.after($span).remove();
+            }
+            function callback() {
+                editor.restoreSelectionByElem($span.get(0));
+            }
+            editor.customCommand(e, commandFn, callback);
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// table 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'table';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.table
+        });
+
+        // dropPanel 内容
+        var $content = $('<div style="font-size: 14px; color: #666; text-align:right;"></div>');
+        var $table = $('<table class="choose-table" style="margin-bottom:10px;margin-top:5px;">');
+        var $row = $('<span>0</span>');
+        var $rowspan = $('<span> 行 </span>');
+        var $col = $('<span>0</span>');
+        var $colspan = $('<span> 列</span>');
+        var $tr;
+        var i, j;
+
+        // 创建一个n行n列的表格
+        for (i = 0; i < 15; i++) {
+            $tr = $('<tr index="' + (i + 1) + '">');
+            for (j = 0; j < 20; j++) {
+                $tr.append($('<td index="' + (j + 1) + '">'));
+            }
+            $table.append($tr);
+        }
+        $content.append($table);
+        $content.append($row).append($rowspan).append($col).append($colspan);
+
+        // 定义table事件
+        $table.on('mouseenter', 'td', function (e) {
+            var $currentTd = $(e.currentTarget);
+            var currentTdIndex = $currentTd.attr('index');
+            var $currentTr = $currentTd.parent();
+            var currentTrIndex = $currentTr.attr('index');
+
+            // 显示
+            $row.text(currentTrIndex);
+            $col.text(currentTdIndex);
+
+            // 遍历设置背景颜色
+            $table.find('tr').each(function () {
+                var $tr = $(this);
+                var trIndex = $tr.attr('index');
+                if (parseInt(trIndex, 10) <= parseInt(currentTrIndex, 10)) {
+                    // 该行需要可能需要设置背景色
+                    $tr.find('td').each(function () {
+                        var $td = $(this);
+                        var tdIndex = $td.attr('index');
+                        if (parseInt(tdIndex, 10) <= parseInt(currentTdIndex, 10)) {
+                            // 需要设置背景色
+                            $td.addClass('active');
+                        } else {
+                            // 需要移除背景色
+                            $td.removeClass('active');
+                        }
+                    });
+                } else {
+                    // 改行不需要设置背景色
+                    $tr.find('td').removeClass('active');
+                }
+            });
+        }).on('mouseleave', function (e) {
+            // mouseleave 删除背景色
+            $table.find('td').removeClass('active');
+
+            $row.text(0);
+            $col.text(0);
+        });
+
+        // 插入表格
+        $table.on('click', 'td', function (e) {
+            var $currentTd = $(e.currentTarget);
+            var currentTdIndex = $currentTd.attr('index');
+            var $currentTr = $currentTd.parent();
+            var currentTrIndex = $currentTr.attr('index');
+
+            var rownum = parseInt(currentTrIndex, 10);
+            var colnum = parseInt(currentTdIndex, 10);
+
+            // -------- 拼接tabel html --------
+
+            var i, j;
+            var tableHtml = '<table>';
+            for (i = 0; i < rownum; i++) {
+                tableHtml += '<tr>';
+
+                for (j = 0; j < colnum; j++) {
+                    tableHtml += '<td><span>&nbsp;</span></td>';
+                }
+                tableHtml += '</tr>';
+            }
+            tableHtml += '</table>';
+
+            // -------- 执行命令 --------
+            editor.command(e, 'insertHtml', tableHtml);
+        });
+
+        // 创建 panel
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $content,
+            width: 262
+        });
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// emotion 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'emotion';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var config = editor.config;
+        var lang = config.lang;
+        var configEmotions = config.emotions;
+        var emotionsShow = config.emotionsShow;
+
+        // 记录每一个表情图片的地址
+        editor.emotionUrls = [];
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.emotion
+        });
+
+        // 添加表情图片的函数
+        function insertEmotionImgs(data, $tabContent) {
+            // 添加表情图片
+            $.each(data, function (k, emotion) {
+                var src = emotion.icon || emotion.url;
+                var value = emotion.value || emotion.title;
+                // 通过配置 editor.config.emotionsShow 的值来修改插入到编辑器的内容(图片/value)
+                var commandValue = emotionsShow === 'icon' ? src : value;
+                var $command = $('<a href="#" commandValue="' + commandValue + '"></a>');
+                var $img = $('<img>');
+                $img.attr('_src', src);  // 先将 src 复制到 '_src' 属性,先不加载
+
+                $command.append($img);
+                $tabContent.append($command);
+
+                // 记录下每一个表情图片的地址
+                editor.emotionUrls.push(src);
+            });
+        }
+
+        // 拼接 dropPanel 内容
+        var $panelContent = $('<div class="panel-tab"></div>');
+        var $tabContainer = $('<div class="tab-container"></div>');
+        var $contentContainer = $('<div class="content-container emotion-content-container"></div>');
+        $.each(configEmotions, function (k, emotion) {
+            var title = emotion.title;
+            var data = emotion.data;
+
+            E.log('正在处理 ' + title + ' 表情的数据...');
+
+            // 增加该组表情的tab和content
+            var $tab = $('<a href="#">' + title +' </a>');
+            $tabContainer.append($tab);
+            var $tabContent = $('<div class="content"></div>');
+            $contentContainer.append($tabContent);
+
+            // tab 切换事件
+            $tab.click(function (e) {
+                $tabContainer.children().removeClass('selected');
+                $contentContainer.children().removeClass('selected');
+                $tabContent.addClass('selected');
+                $tab.addClass('selected');
+                e.preventDefault();
+            });
+
+            // 处理data
+            if (typeof data === 'string') {
+                // url 形式,需要通过ajax从该url获取数据
+                E.log('将通过 ' + data + ' 地址ajax下载表情包');
+                $.get(data, function (result) {
+                    result = $.parseJSON(result);
+                    E.log('下载完毕,得到 ' + result.length + ' 个表情');
+                    insertEmotionImgs(result, $tabContent);
+                });
+                
+            } else if ( Object.prototype.toString.call(data).toLowerCase().indexOf('array') > 0 ) {
+                // 数组,即 data 直接就是表情包数据
+                insertEmotionImgs(data, $tabContent);
+            } else {
+                // 其他情况,data格式不对
+                E.error('data 数据格式错误,请修改为正确格式,参考文档:' + E.docsite);
+                return;
+            }
+        });
+        $panelContent.append($tabContainer).append($contentContainer);
+
+        // 默认显示第一个tab
+        $tabContainer.children().first().addClass('selected');
+        $contentContainer.children().first().addClass('selected');
+
+        // 插入表情command事件
+        $contentContainer.on('click', 'a[commandValue]', function (e) {
+            var $a = $(e.currentTarget);
+            var commandValue = $a.attr('commandValue');
+            var img;
+
+            // commandValue 有可能是图片url,也有可能是表情的 value,需要区别对待
+
+            if (emotionsShow === 'icon') {
+                // 插入图片
+                editor.command(e, 'InsertImage', commandValue);
+            } else {
+                // 插入value
+                editor.command(e, 'insertHtml', '<span>' + commandValue + '</span>');
+            }
+
+            e.preventDefault();
+        });
+
+        // 添加panel
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $panelContent,
+            width: 350
+        });
+
+        // 定义click事件(异步加载表情图片)
+        menu.clickEvent = function (e) {
+            var menu = this;
+            var dropPanel = menu.dropPanel;
+
+            // -------------隐藏-------------
+            if (dropPanel.isShowing) {
+                dropPanel.hide();
+                return;
+            }
+
+            // -------------显示-------------
+            dropPanel.show();
+
+            // 异步加载图片
+            if (menu.imgLoaded) {
+                return;
+            }
+            $contentContainer.find('img').each(function () {
+                var $img = $(this);
+                var _src = $img.attr('_src');
+                $img.on('error', function () {
+                    E.error('加载不出表情图片 ' + _src);
+                });
+                $img.attr('src', _src);
+                $img.removeAttr('_src');
+            });
+            menu.imgLoaded = true;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// img 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'img';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.img
+        });
+
+        // 创建 panel content
+        var $panelContent = $('<div class="panel-tab"></div>');
+        var $tabContainer = $('<div class="tab-container"></div>');
+        var $contentContainer = $('<div class="content-container"></div>');
+        $panelContent.append($tabContainer).append($contentContainer);
+
+        // tab
+        var $uploadTab = $('<a href="#">' + lang.uploadImg + '</a>');
+        var $linkTab = $('<a href="#">' + lang.linkImg + '</a>');
+        $tabContainer.append($uploadTab).append($linkTab);
+
+        // 上传图片 content
+        var $uploadContent = $('<div class="content"></div>');
+        $contentContainer.append($uploadContent);
+
+        // 网络图片 content
+        var $linkContent = $('<div class="content"></div>');
+        $contentContainer.append($linkContent);
+        linkContentHandler(editor, menu, $linkContent);
+
+        // 添加panel
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $panelContent,
+            width: 400,
+            onRender: function () {
+                // 渲染后的回调事件,用于执行自定义上传的init
+                // 因为渲染之后,上传面板的dom才会被渲染到页面,才能让第三方空间获取到
+                var init = editor.config.customUploadInit;
+                init && init.call(editor);
+            }
+        });
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+
+        // tab 切换事件
+        function tabToggle() {
+            $uploadTab.click(function (e) {
+                $tabContainer.children().removeClass('selected');
+                $contentContainer.children().removeClass('selected');
+                $uploadContent.addClass('selected');
+                $uploadTab.addClass('selected');
+                e.preventDefault();
+            });
+            $linkTab.click(function (e) {
+                $tabContainer.children().removeClass('selected');
+                $contentContainer.children().removeClass('selected');
+                $linkContent.addClass('selected');
+                $linkTab.addClass('selected');
+                e.preventDefault();
+
+                // focus input
+                if (E.placeholder) {
+                    $linkContent.find('input[type=text]').focus();
+                }
+            });
+
+            // 默认情况
+            // $uploadTab.addClass('selected');
+            // $uploadContent.addClass('selected');
+            $uploadTab.click();
+        }
+
+        // 隐藏上传图片
+        function hideUploadImg() {
+            $tabContainer.remove();
+            $uploadContent.remove();
+            $linkContent.addClass('selected');
+        }
+
+        // 隐藏网络图片
+        function hideLinkImg() {
+            $tabContainer.remove();
+            $linkContent.remove();
+            $uploadContent.addClass('selected');
+        }
+
+        // 判断用户是否配置了上传图片
+        editor.ready(function () {
+            var editor = this;
+            var config = editor.config;
+            var uploadImgUrl = config.uploadImgUrl;
+            var customUpload = config.customUpload;
+            var linkImg = config.hideLinkImg;
+            var $uploadImgPanel;
+
+            if (uploadImgUrl || customUpload) {
+                // 第一,暴露出 $uploadContent 以便用户自定义 !!!重要
+                editor.$uploadContent = $uploadContent;
+
+                // 第二,绑定tab切换事件
+                tabToggle();
+
+                if (linkImg) {
+                    // 隐藏网络图片
+                    hideLinkImg();
+                }
+            } else {
+                // 未配置上传图片功能
+                hideUploadImg();
+            }
+
+            // 点击 $uploadContent 立即隐藏 dropPanel
+            // 为了兼容IE8、9的上传,因为IE8、9使用 modal 上传
+            // 这里使用异步,为了不妨碍高级浏览器通过点击 $uploadContent 选择文件
+            function hidePanel() {
+                menu.dropPanel.hide();
+            }
+            $uploadContent.click(function () {
+                setTimeout(hidePanel);
+            });
+        });
+    });
+
+    // --------------- 处理网络图片content ---------------
+    function linkContentHandler (editor, menu, $linkContent) {
+        var lang = editor.config.lang;
+        var $urlContainer = $('<div style="margin:20px 10px 10px 10px;"></div>');
+        var $urlInput = $('<input type="text" class="block" placeholder="http://"/>');
+        $urlContainer.append($urlInput);
+        var $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
+        var $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');
+
+        $linkContent.append($urlContainer).append($btnSubmit).append($btnCancel);
+
+        // 取消
+        $btnCancel.click(function (e) {
+            e.preventDefault();
+            menu.dropPanel.hide();
+        });
+
+        // callback 
+        function callback() {
+            $urlInput.val('');
+        }
+
+        // 确定
+        $btnSubmit.click(function (e) {
+            e.preventDefault();
+            var url = $.trim($urlInput.val());
+            if (!url) {
+                // 无内容
+                $urlInput.focus();
+                return;
+            }
+
+            var imgHtml = '<img style="max-width:100%;" src="' + url + '"/>';
+            editor.command(e, 'insertHtml', imgHtml, callback);
+        });
+    }
+
+});
+// video 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'video';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+        var reg = /^<(iframe)|(embed)/i;  // <iframe... 或者 <embed... 格式
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.video
+        });
+
+        // 创建 panel 内容
+        var $content = $('<div></div>');
+        var $linkInputContainer = $('<div style="margin:20px 10px;"></div>');
+        var $linkInput = $('<input type="text" class="block" placeholder=\'格式如:<iframe src="..." frameborder=0 allowfullscreen></iframe>\'/>');
+        $linkInputContainer.append($linkInput);
+        var $sizeContainer = $('<div style="margin:20px 10px;"></div>');
+        var $widthInput = $('<input type="text" value="640" style="width:50px;text-align:center;"/>');
+        var $heightInput = $('<input type="text" value="498" style="width:50px;text-align:center;"/>');
+        $sizeContainer.append('<span> ' + lang.width + ' </span>')
+                      .append($widthInput)
+                      .append('<span> px &nbsp;&nbsp;&nbsp;</span>')
+                      .append('<span> ' + lang.height + ' </span>')
+                      .append($heightInput)
+                      .append('<span> px </span>');
+        var $btnContainer = $('<div></div>');
+        var $howToCopy = $('<a href="http://www.kancloud.cn/wangfupeng/wangeditor2/134973" target="_blank" style="display:inline-block;margin-top:10px;margin-left:10px;color:#999;">如何复制视频链接?</a>');
+        var $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
+        var $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');
+        $btnContainer.append($howToCopy).append($btnSubmit).append($btnCancel);
+        $content.append($linkInputContainer).append($sizeContainer).append($btnContainer);
+
+        // 取消按钮
+        $btnCancel.click(function (e) {
+            e.preventDefault();
+            $linkInput.val('');
+            menu.dropPanel.hide();
+        });
+
+        // 确定按钮
+        $btnSubmit.click(function (e) {
+            e.preventDefault();
+            var link = $.trim($linkInput.val());
+            var $link;
+            var width = parseInt($widthInput.val());
+            var height = parseInt($heightInput.val());
+            var $div = $('<div>');
+            var html = '<p>{content}</p>';
+
+            // 验证数据
+            if (!link) {
+                menu.dropPanel.focusFirstInput();
+                return;
+            }
+
+            if (!reg.test(link)) {
+                alert('视频链接格式错误!');
+                menu.dropPanel.focusFirstInput();
+                return;
+            }
+
+            if (isNaN(width) || isNaN(height)) {
+                alert('宽度或高度不是数字!');
+                return;
+            }
+
+            $link = $(link);
+
+            // 设置高度和宽度
+            $link.attr('width', width)
+                 .attr('height', height);
+
+            // 拼接字符串
+            html = html.replace('{content}', $div.append($link).html());
+
+            // 执行命令
+            editor.command(e, 'insertHtml', html);
+            $linkInput.val('');
+        });
+
+        // 创建panel
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $content,
+            width: 400
+        });
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// location 菜单
+_e(function (E, $) {
+
+    // 判断浏览器的 input 是否支持 keyup
+    var inputKeyup = (function (input) {
+        return 'onkeyup' in input;
+    })(document.createElement('input'));
+
+    // 百度地图的key
+    E.baiduMapAk = 'TVhjYjq1ICT2qqL5LdS8mwas';
+
+    // 一个页面中,如果有多个编辑器,地图会出现问题。这个参数记录一下,如果超过 1 就提示
+    E.numberOfLocation = 0;
+
+    E.createMenu(function (check) {
+        var menuId = 'location';
+        if (!check(menuId)) {
+            return;
+        }
+
+        if (++E.numberOfLocation > 1) {
+            E.error('目前不支持在一个页面多个编辑器上同时使用地图,可通过自定义菜单配置去掉地图菜单');
+            return;
+        }
+
+        var editor = this;
+        var config = editor.config;
+        var lang = config.lang;
+        var ak = config.mapAk;
+
+        // 地图的变量存储到这个地方
+        editor.mapData = {};
+        var mapData = editor.mapData;
+
+        // ---------- 地图事件 ----------
+        mapData.markers = [];
+        mapData.mapContainerId = 'map' + E.random();
+
+        mapData.clearLocations = function () {
+            var map = mapData.map;
+            if (!map) {
+                return;
+            }
+            map.clearOverlays();
+
+            //同时,清空marker数组
+            mapData.markers = [];
+        };
+
+        mapData.searchMap = function () {
+            var map = mapData.map;
+            if (!map) {
+                return;
+            }
+
+            var BMap = window.BMap;
+            var cityName = $cityInput.val();
+            var locationName = $searchInput.val();
+            var myGeo, marker;
+
+            if(cityName !== ''){
+                if(!locationName || locationName === ''){
+                    map.centerAndZoom(cityName, 11);
+                }
+
+                //地址解析
+                if(locationName && locationName !== ''){
+                    myGeo = new BMap.Geocoder();
+                    // 将地址解析结果显示在地图上,并调整地图视野
+                    myGeo.getPoint(locationName, function(point){
+                        if (point) {
+                            map.centerAndZoom(point, 13);
+                            marker = new BMap.Marker(point);
+                            map.addOverlay(marker);
+                            marker.enableDragging();  //允许拖拽
+                            mapData.markers.push(marker);  //将marker加入到数组中
+                        }else{
+                            // alert('未找到');
+                            map.centerAndZoom(cityName, 11);  //找不到则重新定位到城市
+                        }
+                    }, cityName);
+                }
+            } // if(cityName !== '')
+        };
+
+        // load script 之后的 callback
+        var hasCallback = false;
+        window.baiduMapCallBack = function(){
+            // 避免重复加载
+            if (hasCallback) {
+                return;
+            } else {
+                hasCallback = true;
+            }
+
+            var BMap = window.BMap;
+            if (!mapData.map) {
+                // 创建Map实例
+                mapData.map = new BMap.Map(mapData.mapContainerId);
+            }
+            var map = mapData.map;
+
+            map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);  // 初始化地图,设置中心点坐标和地图级别
+            map.addControl(new BMap.MapTypeControl());   //添加地图类型控件
+            map.setCurrentCity("北京");          // 设置地图显示的城市 此项是必须设置的
+            map.enableScrollWheelZoom(true);     //开启鼠标滚轮缩放
+
+            //根据IP定位
+            function locationFun(result){
+                var cityName = result.name;
+                map.setCenter(cityName);
+
+                // 设置城市名称
+                $cityInput.val(cityName);
+                if (E.placeholder) {
+                    $searchInput.focus();
+                }
+                var timeoutId, searchFn;
+                if (inputKeyup) {
+                   // 并绑定搜索事件 - input 支持 keyup
+                   searchFn = function (e) {
+                       if (e.type === 'keyup' && e.keyCode === 13) {
+                           e.preventDefault();
+                       }
+                       if (timeoutId) {
+                           clearTimeout(timeoutId);
+                       }
+                       timeoutId = setTimeout(mapData.searchMap, 500);
+                   };
+                   $cityInput.on('keyup change paste', searchFn);
+                   $searchInput.on('keyup change paste', searchFn); 
+                } else {
+                    // 并绑定搜索事件 - input 不支持 keyup
+                    searchFn = function () {
+                        if (!$content.is(':visible')) {
+                            // panel 不显示了,就不用再监控了
+                            clearTimeout(timeoutId);
+                            return;
+                        }
+
+                        var currentCity = '';
+                        var currentSearch = '';
+                        var city = $cityInput.val();
+                        var search = $searchInput.val();
+
+                        if (city !== currentCity || search !== currentSearch) {
+                            // 刚获取的数据和之前的数据不一致,执行查询
+                            mapData.searchMap();
+                            // 更新数据
+                            currentCity = city;
+                            currentSearch = search;
+                        }
+
+                        // 继续监控
+                        if (timeoutId) {
+                            clearTimeout(timeoutId);
+                        }
+                        timeoutId = setTimeout(searchFn, 1000);
+                    };
+                    // 开始监控
+                    timeoutId = setTimeout(searchFn, 1000);
+                }
+            }
+            var myCity = new BMap.LocalCity();
+            myCity.get(locationFun);
+
+            //鼠标点击,创建位置
+            map.addEventListener("click", function(e){
+                var marker = new BMap.Marker(new BMap.Point(e.point.lng, e.point.lat)); 
+                map.addOverlay(marker);  
+                marker.enableDragging();
+                mapData.markers.push(marker);  //加入到数组中
+            }, false);
+        };
+
+        mapData.loadMapScript = function () {
+            var script = document.createElement("script");
+            script.type = "text/javascript";
+            script.src = "https://api.map.baidu.com/api?v=2.0&ak=" + ak + "&s=1&callback=baiduMapCallBack";  // baiduMapCallBack是一个本地函数
+            try {
+                // IE10- 报错
+                document.body.appendChild(script);
+            } catch (ex) {
+                E.error('加载地图过程中发生错误');
+            }
+        };
+
+        // 初始化地图
+        mapData.initMap = function () {
+            if (window.BMap) {
+                // 不是第一次,直接处理地图即可
+                window.baiduMapCallBack();
+            } else {
+                // 第一次,先加载地图 script,再处理地图(script加载完自动执行处理)
+                mapData.loadMapScript();
+            }
+        };
+
+        // ---------- 创建 menu 对象 ----------
+
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.location
+        });
+
+        editor.menus[menuId] = menu;
+
+        // ---------- 构建UI ----------
+
+        // panel content 
+        var $content = $('<div></div>');
+
+        // 搜索框
+        var $inputContainer = $('<div style="margin:10px 0;"></div>');
+        var $cityInput = $('<input type="text"/>');
+        $cityInput.css({
+            width: '80px',
+            'text-align': 'center'
+        });
+        var $searchInput = $('<input type="text"/>');
+        $searchInput.css({
+            width: '300px',
+            'margin-left': '10px'
+        }).attr('placeholder', lang.searchlocation);
+        var $clearBtn = $('<button class="right link">' + lang.clearLocation + '</button>');
+        $inputContainer.append($clearBtn)
+                       .append($cityInput)
+                       .append($searchInput);
+        $content.append($inputContainer);
+
+        // 清除位置按钮
+        $clearBtn.click(function (e) {
+            $searchInput.val('');
+            $searchInput.focus();
+            mapData.clearLocations();
+            e.preventDefault();
+        });
+
+        // 地图
+        var $map = $('<div id="' + mapData.mapContainerId + '"></div>');
+        $map.css({
+            height: '260px',
+            width: '100%',
+            position: 'relative',
+            'margin-top': '10px',
+            border: '1px solid #f1f1f1'
+        });
+        var $mapLoading = $('<span>' + lang.loading + '</span>');
+        $mapLoading.css({
+            position: 'absolute',
+            width: '100px',
+            'text-align': 'center',
+            top: '45%',
+            left: '50%',
+            'margin-left': '-50px'
+        });
+        $map.append($mapLoading);
+        $content.append($map);
+
+        // 按钮
+        var $btnContainer = $('<div style="margin:10px 0;"></div>');
+        var $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
+        var $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');
+        var $checkLabel = $('<label style="display:inline-block;margin-top:10px;color:#666;"></label>');
+        var $check = $('<input type="checkbox">');
+        $checkLabel.append($check).append('<span style="display:inline-block;margin-left:5px;">  ' + lang.dynamicMap + '</span>');
+        $btnContainer.append($checkLabel)
+                     .append($btnSubmit)
+                     .append($btnCancel);
+        $content.append($btnContainer);
+
+        function callback() {
+            $searchInput.val('');
+        }
+
+        // 『取消』按钮事件
+        $btnCancel.click(function (e) {
+            e.preventDefault();
+            callback();
+            menu.dropPanel.hide();
+        });
+
+        // 『确定』按钮事件
+        $btnSubmit.click(function (e) {
+            e.preventDefault();
+            var map = mapData.map,
+                isDynamic = $check.is(':checked'),
+                markers =  mapData.markers,
+
+                center = map.getCenter(),
+                centerLng = center.lng,
+                centerLat = center.lat,
+
+                zoom = map.getZoom(),
+
+                size = map.getSize(),
+                sizeWidth = size.width,
+                sizeHeight = size.height,
+
+                position,
+                src,
+                iframe;
+
+            if(isDynamic){
+                //动态地址
+                src = 'http://ueditor.baidu.com/ueditor/dialogs/map/show.html#';
+            }else{
+                //静态地址
+                src = 'http://api.map.baidu.com/staticimage?';
+            }
+
+            //src参数
+            src = src +'center=' + centerLng + ',' + centerLat +
+                '&zoom=' + zoom +
+                '&width=' + sizeWidth +
+                '&height=' + sizeHeight;
+            if(markers.length > 0){
+                src = src + '&markers=';
+
+                //添加所有的marker
+                $.each(markers, function(key, value){
+                    position = value.getPosition();
+                    if(key > 0){
+                        src = src + '|';
+                    }
+                    src = src + position.lng + ',' + position.lat;
+                });
+            }
+
+            if(isDynamic){
+                if(markers.length > 1){
+                    alert( lang.langDynamicOneLocation );
+                    return;
+                }
+
+                src += '&markerStyles=l,A';
+
+                //插入iframe
+                iframe = '<iframe class="ueditor_baidumap" src="{src}" frameborder="0" width="' + sizeWidth + '" height="' + sizeHeight + '"></iframe>';
+                iframe = iframe.replace('{src}', src);
+                editor.command(e, 'insertHtml', iframe, callback);
+            }else{
+                //插入图片
+                editor.command(e, 'insertHtml', '<img style="max-width:100%;" src="' + src + '"/>', callback);
+            }
+        });
+
+        // 根据 UI 创建菜单 panel
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $content,
+            width: 500
+        });
+
+        // ---------- 事件 ----------
+
+        // render 时执行事件
+        menu.onRender = function () {
+            if (ak === E.baiduMapAk) {
+                E.warn('建议在配置中自定义百度地图的mapAk,否则可能影响地图功能,文档:' + E.docsite);
+            }
+        };
+
+        // click 事件
+        menu.clickEvent = function (e) {
+            var menu = this;
+            var dropPanel = menu.dropPanel;
+            var firstTime = false;
+
+            // -------------隐藏-------------
+            if (dropPanel.isShowing) {
+                dropPanel.hide();
+                return;
+            }
+
+            // -------------显示-------------
+            if (!mapData.map) {
+                // 第一次,先加载地图
+                firstTime = true;
+            }
+            
+            dropPanel.show();
+            mapData.initMap();
+
+            if (!firstTime) {
+                $searchInput.focus();
+            }
+        };
+
+    });
+
+});
+// insertcode 菜单
+_e(function (E, $) {
+
+    // 加载 highlightjs 代码
+    function loadHljs() {
+        if (E.userAgent.indexOf('MSIE 8') > 0) {
+            // 不支持 IE8
+            return;
+        }
+        if (window.hljs) {
+            // 不要重复加载
+            return;
+        }
+        var script = document.createElement("script");
+        script.type = "text/javascript";
+        script.src = "//cdn.bootcss.com/highlight.js/9.2.0/highlight.min.js";
+        document.body.appendChild(script);
+    }
+    
+
+    E.createMenu(function (check) {
+        var menuId = 'insertcode';
+        if (!check(menuId)) {
+            return;
+        }
+
+        // 加载 highlightjs 代码
+        setTimeout(loadHljs, 0);
+
+        var editor = this;
+        var config = editor.config;
+        var lang = config.lang;
+        var $txt = editor.txt.$txt;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.insertcode
+        });
+
+        // click 事件
+        menu.clickEvent = function (e) {
+            var menu = this;
+            var dropPanel = menu.dropPanel;
+
+            // 隐藏
+            if (dropPanel.isShowing) {
+                dropPanel.hide();
+                return;
+            }
+
+            // 显示
+            $textarea.val('');
+            dropPanel.show();
+
+            // highlightjs 语言列表
+            var hljs = window.hljs;
+            if (hljs && hljs.listLanguages) {
+                if ($langSelect.children().length !== 0) {
+                    return;
+                }
+                $langSelect.css({
+                    'margin-top': '9px',
+                    'margin-left': '5px'
+                });
+                $.each(hljs.listLanguages(), function (key, lang) {
+                    if (lang === 'xml') {
+                        lang = 'html';
+                    }
+                    if (lang === config.codeDefaultLang) {
+                        $langSelect.append('<option value="' + lang + '" selected="selected">' + lang + '</option>');
+                    } else {
+                        $langSelect.append('<option value="' + lang + '">' + lang + '</option>');
+                    }
+                });
+            } else {
+                $langSelect.hide();
+            }
+        };
+
+        // 选中状态下的 click 事件
+        menu.clickEventSelected = function (e) {
+            var menu = this;
+            var dropPanel = menu.dropPanel;
+
+            // 隐藏
+            if (dropPanel.isShowing) {
+                dropPanel.hide();
+                return;
+            }
+
+            // 显示
+            dropPanel.show();
+
+            var rangeElem = editor.getRangeElem();
+            var targetElem = editor.getSelfOrParentByName(rangeElem, 'pre');
+            var $targetElem;
+            var className;
+            if (targetElem) {
+                // 确定找到 pre 之后,再找 code
+                targetElem = editor.getSelfOrParentByName(rangeElem, 'code');
+            }
+            if (!targetElem) {
+                return;
+            }
+            $targetElem = $(targetElem);
+
+            // 赋值内容
+            $textarea.val($targetElem.text());
+            if ($langSelect) {
+                // 赋值语言
+                className = $targetElem.attr('class');
+                if (className) {
+                    $langSelect.val(className.split(' ')[0]);
+                }
+            }
+        };
+
+        // 定义更新选中状态的事件
+        menu.updateSelectedEvent = function () {
+            var self = this; //菜单对象
+            var editor = self.editor;
+            var rangeElem;
+
+            rangeElem = editor.getRangeElem();
+            rangeElem = editor.getSelfOrParentByName(rangeElem, 'pre');
+
+            if (rangeElem) {
+                return true;
+            }
+
+            return false;
+        };
+
+        // 创建 panel
+        var $content = $('<div></div>');
+        var $textarea = $('<textarea></textarea>');
+        var $langSelect = $('<select></select>');
+        contentHandle($content);
+        menu.dropPanel = new E.DropPanel(editor, menu, {
+            $content: $content,
+            width: 500
+        });
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+
+        // ------ 增加 content 内容 ------
+        function contentHandle($content) {
+            // textarea 区域
+            var $textareaContainer = $('<div></div>');
+            $textareaContainer.css({
+                margin: '15px 5px 5px 5px',
+                height: '160px',
+                'text-align': 'center'
+            });
+            $textarea.css({
+                width: '100%',
+                height: '100%',
+                padding: '10px'
+            });
+            $textarea.on('keydown', function (e) {
+                // 取消 tab 键默认行为
+                if (e.keyCode === 9) {
+                    e.preventDefault();
+                }
+            });
+            $textareaContainer.append($textarea);
+            $content.append($textareaContainer);
+
+            // 按钮区域
+            var $btnContainer = $('<div></div>');
+            var $btnSubmit = $('<button class="right">' + lang.submit + '</button>');
+            var $btnCancel = $('<button class="right gray">' + lang.cancel + '</button>');
+
+            $btnContainer.append($btnSubmit).append($btnCancel).append($langSelect);
+            $content.append($btnContainer);
+
+            // 取消按钮
+            $btnCancel.click(function (e) {
+                e.preventDefault();
+                menu.dropPanel.hide();
+            });
+
+            // 确定按钮
+            var codeTpl = '<pre style="max-width:100%;overflow-x:auto;"><code{#langClass}>{#content}</code></pre>';
+            $btnSubmit.click(function (e) {
+                e.preventDefault();
+                var val = $textarea.val();
+                if (!val) {
+                    // 无内容
+                    $textarea.focus();
+                    return;
+                }
+
+                var rangeElem = editor.getRangeElem();
+                if ($.trim($(rangeElem).text()) && codeTpl.indexOf('<p><br></p>') !== 0) {
+                    codeTpl = '<p><br></p>' + codeTpl;
+                }
+
+                var lang = $langSelect ? $langSelect.val() : ''; // 获取高亮语言
+                var langClass = '';
+                var doHightlight = function () {
+                    $txt.find('pre code').each(function (i, block) {
+                        var $block = $(block);
+                        if ($block.attr('codemark')) {
+                            // 有 codemark 标记的代码块,就不再重新格式化了
+                            return;
+                        } else if (window.hljs) {
+                            // 新代码块,格式化之后,立即标记 codemark
+                            window.hljs.highlightBlock(block);
+                            $block.attr('codemark', '1');
+                        }
+                    });
+                };
+
+                // 语言高亮样式
+                if (lang) {
+                    langClass = ' class="' + lang + ' hljs"';
+                }
+
+                // 替换标签
+                val = val.replace(/&/gm, '&amp;')
+                         .replace(/</gm, '&lt;')
+                         .replace(/>/gm, '&gt;');
+
+                // ---- menu 未选中状态 ----
+                if (!menu.selected) {
+                    // 拼接html
+                    var html = codeTpl.replace('{#langClass}', langClass).replace('{#content}', val);
+                    editor.command(e, 'insertHtml', html, doHightlight);
+                    return;
+                }
+
+                // ---- menu 选中状态 ----
+                var targetElem = editor.getSelfOrParentByName(rangeElem, 'pre');
+                var $targetElem;
+                if (targetElem) {
+                    // 确定找到 pre 之后,再找 code
+                    targetElem = editor.getSelfOrParentByName(rangeElem, 'code');
+                }
+                if (!targetElem) {
+                    return;
+                }
+                $targetElem = $(targetElem);
+
+                function commandFn() {
+                    var className;
+                    if (lang) {
+                        className = $targetElem.attr('class');
+                        if (className !== lang + ' hljs') {
+                            $targetElem.attr('class', lang + ' hljs');
+                        }
+                    }
+                    $targetElem.html(val);
+                }
+                function callback() {
+                    editor.restoreSelectionByElem(targetElem);
+                    doHightlight();
+                }
+                editor.customCommand(e, commandFn, callback);
+            });
+        }
+
+        // ------ enter 时,不另起标签,只换行 ------
+        $txt.on('keydown', function (e) {
+            if (e.keyCode !== 13) {
+                return;
+            }
+            var rangeElem = editor.getRangeElem();
+            var targetElem = editor.getSelfOrParentByName(rangeElem, 'code');
+            if (!targetElem) {
+                return;
+            }
+
+            editor.command(e, 'insertHtml', '\n');
+        });
+
+        // ------ 点击时,禁用其他标签 ------
+        function updateMenu() {
+            var rangeElem = editor.getRangeElem();
+            var targetElem = editor.getSelfOrParentByName(rangeElem, 'code');
+            if (targetElem) {
+                // 在 code 之内,禁用其他菜单
+                editor.disableMenusExcept('insertcode');
+            } else {
+                // 不是在 code 之内,启用其他菜单
+                editor.enableMenusExcept('insertcode');
+            }
+        }
+        $txt.on('keydown click', function (e) {
+            // 此处必须使用 setTimeout 异步处理,否则不对
+            setTimeout(updateMenu);
+        });
+    });
+
+});
+// undo 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'undo';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.undo
+        });
+
+        // click 事件
+        menu.clickEvent = function (e) {
+            editor.undo();
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+
+
+        // ------------ 初始化时、enter 时、打字中断时,做记录 ------------
+        // ------------ ctrl + z 是调用记录撤销,而不是使用浏览器默认的撤销 ------------
+        editor.ready(function () {
+            var editor = this;
+            var $txt = editor.txt.$txt;
+            var timeoutId;
+
+            // 执行undo记录
+            function undo() {
+                editor.undoRecord();
+            }
+
+            $txt.on('keydown', function (e) {
+                var keyCode = e.keyCode;
+
+                // 撤销 ctrl + z
+                if (e.ctrlKey && keyCode === 90) {
+                    editor.undo();
+                    return;
+                }
+
+                if (keyCode === 13) {
+                    // enter 做记录
+                    undo();
+                } else {
+                    // keyup 之后 1s 之内不操作,则做一次记录
+                    if (timeoutId) {
+                        clearTimeout(timeoutId);
+                    }
+                    timeoutId = setTimeout(undo, 1000);
+                }
+            });
+
+            // 初始化做记录
+            editor.undoRecord();
+        });
+    });
+
+});
+// redo 菜单
+_e(function (E, $) {
+
+    E.createMenu(function (check) {
+        var menuId = 'redo';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var lang = editor.config.lang;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.redo
+        });
+
+        // click 事件
+        menu.clickEvent = function (e) {
+            editor.redo();
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// 全屏 菜单
+_e(function (E, $) {
+
+    // 记录全屏时的scrollTop
+    var scrollTopWhenFullScreen;
+
+    E.createMenu(function (check) {
+        var menuId = 'fullscreen';
+        if (!check(menuId)) {
+            return;
+        }
+        var editor = this;
+        var $txt = editor.txt.$txt;
+        var config = editor.config;
+        var zIndexConfig = config.zindex || 10000;
+        var lang = config.lang;
+
+        var isSelected = false;
+        var zIndex;
+
+        var maxHeight;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,
+            id: menuId,
+            title: lang.fullscreen
+        });
+
+        // 定义click事件
+        menu.clickEvent = function (e) {
+            // 增加样式
+            var $editorContainer = editor.$editorContainer;
+            $editorContainer.addClass('wangEditor-fullscreen');
+
+            // (先保存当前的)再设置z-index
+            zIndex = $editorContainer.css('z-index');
+            $editorContainer.css('z-index', zIndexConfig);
+
+            var $wrapper;
+            var txtHeight = $txt.height();
+            var txtOuterHeight = $txt.outerHeight();
+
+            if (editor.useMaxHeight) {
+                // 记录 max-height,并暂时去掉maxheight
+                maxHeight = $txt.css('max-height');
+                $txt.css('max-height', 'none');
+
+                // 如果使用了maxHeight, 将$txt从它的父元素中移出来
+                $wrapper = $txt.parent();
+                $wrapper.after($txt);
+                $wrapper.remove();
+                $txt.css('overflow-y', 'auto');
+            }
+
+            // 设置高度到全屏
+            var menuContainer = editor.menuContainer;
+            $txt.height(
+                E.$window.height() - 
+                menuContainer.height() - 
+                (txtOuterHeight - txtHeight)  // 去掉内边距和外边距
+            );
+
+            // 取消menuContainer的内联样式(menu吸顶时,会为 menuContainer 设置一些内联样式)
+            editor.menuContainer.$menuContainer.attr('style', '');
+
+            // 保存状态
+            isSelected = true;
+
+            // 记录编辑器是否全屏
+            editor.isFullScreen = true;
+
+            // 记录设置全屏时的高度
+            scrollTopWhenFullScreen = E.$window.scrollTop();
+        };
+
+        // 定义选中状态的 click 事件
+        menu.clickEventSelected = function (e) {
+            // 取消样式
+            var $editorContainer = editor.$editorContainer;
+            $editorContainer.removeClass('wangEditor-fullscreen');
+            $editorContainer.css('z-index', zIndex);
+
+            // 还原height
+            if (editor.useMaxHeight) {
+                $txt.css('max-height', maxHeight);
+            } else {
+                // editor.valueContainerHeight 在 editor.txt.initHeight() 中事先保存了
+                editor.$valueContainer.css('height', editor.valueContainerHeight);
+            }
+
+            // 重新计算高度
+            editor.txt.initHeight();
+
+            // 保存状态
+            isSelected = false;
+
+            // 记录编辑器是否全屏
+            editor.isFullScreen = false;
+
+            // 还原scrollTop
+            if (scrollTopWhenFullScreen != null) {
+                E.$window.scrollTop(scrollTopWhenFullScreen);
+            }
+        };
+
+        // 定义选中事件
+        menu.updateSelectedEvent = function (e) {
+            return isSelected;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// 渲染menus
+_e(function (E, $) {
+
+    E.fn.renderMenus = function () {
+
+        var editor = this;
+        var menus = editor.menus;
+        var menuIds = editor.config.menus;
+        var menuContainer = editor.menuContainer;
+
+        var menu;
+        var groupIdx = 0;
+        $.each(menuIds, function (k, v) {
+            if (v === '|') {
+                groupIdx++;
+                return;
+            }
+
+            menu = menus[v];
+            if (menu) {
+                menu.render(groupIdx);
+            }
+        });
+    };
+
+});
+// 渲染menus
+_e(function (E, $) {
+
+    E.fn.renderMenuContainer = function () {
+
+        var editor = this;
+        var menuContainer = editor.menuContainer;
+        var $editorContainer = editor.$editorContainer;
+
+        menuContainer.render();
+
+    };
+
+});
+// 渲染 txt
+_e(function (E, $) {
+
+    E.fn.renderTxt = function () {
+
+        var editor = this;
+        var txt = editor.txt;
+
+        txt.render();
+
+        // ready 时候,计算txt的高度
+        editor.ready(function () {
+            txt.initHeight();
+        });
+    };
+
+});
+// 渲染 container
+_e(function (E, $) {
+
+    E.fn.renderEditorContainer = function () {
+
+        var editor = this;
+        var $valueContainer = editor.$valueContainer;
+        var $editorContainer = editor.$editorContainer;
+        var $txt = editor.txt.$txt;
+        var $prev, $parent;
+
+        // 将编辑器渲染到页面中
+        if ($valueContainer === $txt) {
+            $prev = editor.$prev;
+            $parent = editor.$parent;
+
+            if ($prev && $prev.length) {
+                // 有前置节点,就插入到前置节点的后面
+                $prev.after($editorContainer);
+            } else {
+                // 没有前置节点,就直接插入到父元素
+                $parent.prepend($editorContainer);
+            }
+
+        } else {
+            $valueContainer.after($editorContainer);
+            $valueContainer.hide();
+        }
+
+        // 设置宽度(这样设置宽度有问题)
+        // $editorContainer.css('width', $valueContainer.css('width'));
+    };
+
+});
+// 菜单事件
+_e(function (E, $) {
+
+    // 绑定每个菜单的click事件
+    E.fn.eventMenus = function () {
+
+        var menus = this.menus;
+
+        // 绑定菜单的点击事件
+        $.each(menus, function (k, v) {
+            v.bindEvent();
+        });
+
+    };
+
+});
+// 菜单container事件
+_e(function (E, $) {
+
+    E.fn.eventMenuContainer = function () {
+
+    };
+
+});
+// 编辑区域事件
+_e(function (E, $) {
+
+    E.fn.eventTxt = function () {
+
+        var txt = this.txt;
+
+        // txt内容变化时,保存选区
+        txt.saveSelectionEvent();
+
+        // txt内容变化时,随时更新 value
+        txt.updateValueEvent();
+
+        // txt内容变化时,随时更新 menu style
+        txt.updateMenuStyleEvent();
+
+        // // 鼠标hover时,显示 p head 高度(暂时关闭这个功能)
+        // if (!/ie/i.test(E.userAgent)) {
+        //     // 暂时不支持IE
+        //     txt.showHeightOnHover();
+        // }
+    };
+
+});
+// 上传图片事件
+_e(function (E, $) {
+
+    E.plugin(function () {
+        var editor = this;
+        var fns = editor.config.uploadImgFns; // editor.config.uploadImgFns = {} 在config文件中定义了
+
+        // -------- 定义load函数 --------
+        fns.onload || (fns.onload = function (resultText, xhr) {
+            E.log('上传结束,返回结果为 ' + resultText);
+
+            var editor = this;
+            var originalName = editor.uploadImgOriginalName || '';  // 上传图片时,已经将图片的名字存在 editor.uploadImgOriginalName
+            var img;
+            if (resultText.indexOf('error|') === 0) {
+                // 提示错误
+                E.warn('上传失败:' + resultText.split('|')[1]);
+                alert(resultText.split('|')[1]);
+            } else {
+                E.log('上传成功,即将插入编辑区域,结果为:' + resultText);
+
+                // 将结果插入编辑器
+                img = document.createElement('img');
+                img.onload = function () {
+                    var html = '<img src="' + resultText + '" alt="' + originalName + '" style="max-width:100%;"/>';
+                    editor.command(null, 'insertHtml', html);
+
+                    E.log('已插入图片,地址 ' + resultText);
+                    img = null;
+                };
+                img.onerror = function () {
+                    E.error('使用返回的结果获取图片,发生错误。请确认以下结果是否正确:' + resultText);
+                    img = null;
+                };
+                img.src = resultText;
+            }
+
+        });
+
+        // -------- 定义tiemout函数 --------
+        fns.ontimeout || (fns.ontimeout = function (xhr) {
+            E.error('上传图片超时');
+            alert('上传图片超时');
+        });
+
+        // -------- 定义error函数 --------
+        fns.onerror || (fns.onerror = function (xhr) {
+            E.error('上传上图片发生错误');
+            alert('上传上图片发生错误');
+        });
+
+    });
+});
+// xhr 上传图片
+_e(function (E, $) {
+
+    if (!window.FileReader || !window.FormData) {
+        // 如果不支持html5的文档操作,直接返回
+        return;
+    }
+
+    E.plugin(function () {
+
+        var editor = this;
+        var config = editor.config;
+        var uploadImgUrl = config.uploadImgUrl;
+        var uploadTimeout = config.uploadTimeout;
+
+        // 获取配置中的上传事件
+        var uploadImgFns = config.uploadImgFns;
+        var onload = uploadImgFns.onload;
+        var ontimeout = uploadImgFns.ontimeout;
+        var onerror = uploadImgFns.onerror;
+
+        if (!uploadImgUrl) {
+            return;
+        }
+
+        // -------- 将以base64的图片url数据转换为Blob --------
+        function convertBase64UrlToBlob(urlData, filetype){
+            //去掉url的头,并转换为byte
+            var bytes = window.atob(urlData.split(',')[1]);
+            
+            //处理异常,将ascii码小于0的转换为大于0
+            var ab = new ArrayBuffer(bytes.length);
+            var ia = new Uint8Array(ab);
+            var i;
+            for (i = 0; i < bytes.length; i++) {
+                ia[i] = bytes.charCodeAt(i);
+            }
+
+            return new Blob([ab], {type : filetype});
+        }
+
+        // -------- 插入图片的方法 --------
+        function insertImg(src, event) {
+            var img = document.createElement('img');
+            img.onload = function () {
+                var html = '<img src="' + src + '" style="max-width:100%;"/>';
+                editor.command(event, 'insertHtml', html);
+
+                E.log('已插入图片,地址 ' + src);
+                img = null;
+            };
+            img.onerror = function () {
+                E.error('使用返回的结果获取图片,发生错误。请确认以下结果是否正确:' + src);
+                img = null;
+            };
+            img.src = src;
+        }
+
+        // -------- onprogress 事件 --------
+        function updateProgress(e) {
+            if (e.lengthComputable) {
+                var percentComplete = e.loaded / e.total;
+                editor.showUploadProgress(percentComplete * 100);
+            }
+        }
+
+        // -------- xhr 上传图片 --------
+        editor.xhrUploadImg = function (opt) {
+            // opt 数据
+            var event = opt.event;
+            var fileName = opt.filename || '';
+            var base64 = opt.base64;
+            var fileType = opt.fileType || 'image/png'; // 无扩展名则默认使用 png
+            var name = opt.name || 'wangEditor_upload_file';
+            var loadfn = opt.loadfn || onload;
+            var errorfn = opt.errorfn || onerror;
+            var timeoutfn = opt.timeoutfn || ontimeout;
+
+            // 上传参数(如 token)
+            var params = editor.config.uploadParams || {};
+
+            // headers
+            var headers = editor.config.uploadHeaders || {};
+
+            // 获取文件扩展名
+            var fileExt = 'png';  // 默认为 png
+            if (fileName.indexOf('.') > 0) {
+                // 原来的文件名有扩展名
+                fileExt = fileName.slice(fileName.lastIndexOf('.') - fileName.length + 1);
+            } else if (fileType.indexOf('/') > 0 && fileType.split('/')[1]) {
+                // 文件名没有扩展名,通过类型获取,如从 'image/png' 取 'png'
+                fileExt = fileType.split('/')[1];
+            }
+
+            // ------------ begin 预览模拟上传 ------------
+            if (E.isOnWebsite) {
+                E.log('预览模拟上传');
+                insertImg(base64, event);
+                return;
+            }
+            // ------------ end 预览模拟上传 ------------
+
+            // 变量声明
+            var xhr = new XMLHttpRequest();
+            var timeoutId;
+            var src;
+            var formData = new FormData();
+
+            // 超时处理
+            function timeoutCallback() {
+                if (timeoutId) {
+                    clearTimeout(timeoutId);
+                }
+                if (xhr && xhr.abort) {
+                    xhr.abort();
+                }
+
+                // 超时了就阻止默认行为
+                event.preventDefault();
+
+                // 执行回调函数,提示什么内容,都应该在回调函数中定义
+                timeoutfn && timeoutfn.call(editor, xhr);
+
+                // 隐藏进度条
+                editor.hideUploadProgress();
+            }
+
+            xhr.onload = function () {
+                if (timeoutId) {
+                    clearTimeout(timeoutId);
+                }
+
+                // 记录文件名到 editor.uploadImgOriginalName ,插入图片时,可做 alt 属性用
+                editor.uploadImgOriginalName = fileName;
+                if (fileName.indexOf('.') > 0) {
+                    editor.uploadImgOriginalName = fileName.split('.')[0];
+                }
+
+                // 执行load函数,任何操作,都应该在load函数中定义
+                loadfn && loadfn.call(editor, xhr.responseText, xhr);
+
+                // 隐藏进度条
+                editor.hideUploadProgress();
+            };
+            xhr.onerror = function () {
+                if (timeoutId) {
+                    clearTimeout(timeoutId);
+                }
+
+                // 超时了就阻止默认行为
+                event.preventDefault();
+
+                // 执行error函数,错误提示,应该在error函数中定义
+                errorfn && errorfn.call(editor, xhr);
+
+                // 隐藏进度条
+                editor.hideUploadProgress();
+            };
+            // xhr.onprogress = updateProgress;
+            xhr.upload.onprogress = updateProgress;
+
+            // 填充数据
+            formData.append(name, convertBase64UrlToBlob(base64, fileType), E.random() + '.' + fileExt);
+
+            // 添加参数
+            $.each(params, function (key, value) {
+                formData.append(key, value);
+            });
+
+            // 开始上传
+            xhr.open('POST', uploadImgUrl, true);
+            // xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");  // 将参数解析成传统form的方式上传
+
+            // 修改自定义配置的headers
+            $.each(headers, function (key, value) {
+                xhr.setRequestHeader(key, value);
+            });
+
+            // 跨域上传时,传cookie
+            xhr.withCredentials = editor.config.withCredentials || true;
+
+            // 发送数据
+            xhr.send(formData);
+            timeoutId = setTimeout(timeoutCallback, uploadTimeout);
+
+            E.log('开始上传...并开始超时计算');
+        };
+    });
+});
+// 进度条
+_e(function (E, $) {
+
+    E.plugin(function () {
+
+        var editor = this;
+        var menuContainer = editor.menuContainer;
+        var menuHeight = menuContainer.height();
+        var $editorContainer = editor.$editorContainer;
+        var width = $editorContainer.width();
+        var $progress = $('<div class="wangEditor-upload-progress"></div>');
+
+        // 渲染事件
+        var isRender = false;
+        function render() {
+            if (isRender) {
+                return;
+            }
+            isRender = true;
+
+            $progress.css({
+                top: menuHeight + 'px'
+            });
+            $editorContainer.append($progress);
+        }
+
+        // ------ 显示进度 ------
+        editor.showUploadProgress = function (progress) {
+            if (timeoutId) {
+                clearTimeout(timeoutId);
+            }
+
+            // 显示之前,先判断是否渲染
+            render();
+
+            $progress.show();
+            $progress.width(progress * width / 100);
+        };
+
+        // ------ 隐藏进度条 ------
+        var timeoutId;
+        function hideProgress() {
+            $progress.hide();
+            timeoutId = null;
+        }
+        editor.hideUploadProgress = function (time) {
+            if (timeoutId) {
+                clearTimeout(timeoutId);
+            }
+            time = time || 750;
+            timeoutId = setTimeout(hideProgress, time);
+        };
+    });
+});
+// upload img 插件
+_e(function (E, $) {
+
+    E.plugin(function () {
+        var editor = this;
+        var config = editor.config;
+        var uploadImgUrl = config.uploadImgUrl;
+        var uploadTimeout = config.uploadTimeout;
+        var event;
+
+        if (!uploadImgUrl) {
+            return;
+        }
+
+        // 获取editor的上传dom
+        var $uploadContent = editor.$uploadContent;
+        if (!$uploadContent) {
+            return;
+        }
+
+        // 自定义UI,并添加到上传dom节点上
+        var $uploadIcon = $('<div class="upload-icon-container"><i class="wangeditor-menu-img-upload"></i></div>');
+        $uploadContent.append($uploadIcon);
+
+        // ---------- 构建上传对象 ----------
+        var upfile = new E.UploadFile({
+            editor: editor,
+            uploadUrl: uploadImgUrl,
+            timeout: uploadTimeout,
+            fileAccept: 'image/jpg,image/jpeg,image/png,image/gif,image/bmp'    // 只允许选择图片 
+        });
+
+        // 选择本地文件,上传
+        $uploadIcon.click(function (e) {
+            event = e;
+            upfile.selectFiles();
+        });
+    });
+});
+// h5 方式上传图片
+_e(function (E, $) {
+
+    if (!window.FileReader || !window.FormData) {
+        // 如果不支持html5的文档操作,直接返回
+        return;
+    }
+
+    // 构造函数
+    var UploadFile = function (opt) {
+        this.editor = opt.editor;
+        this.uploadUrl = opt.uploadUrl;
+        this.timeout = opt.timeout;
+        this.fileAccept = opt.fileAccept;
+        this.multiple = true;
+    };
+
+    UploadFile.fn = UploadFile.prototype;
+
+    // clear
+    UploadFile.fn.clear = function () {
+        this.$input.val('');
+        E.log('input value 已清空');
+    };
+
+    // 渲染
+    UploadFile.fn.render = function () {
+        var self = this;
+        if (self._hasRender) {
+            // 不要重复渲染
+            return;
+        }
+
+        E.log('渲染dom');
+
+        var fileAccept = self.fileAccept;
+        var acceptTpl = fileAccept ? 'accept="' + fileAccept + '"' : '';
+        var multiple = self.multiple;
+        var multipleTpl = multiple ? 'multiple="multiple"' : '';
+        var $input = $('<input type="file" ' + acceptTpl + ' ' + multipleTpl + '/>');
+        var $container = $('<div style="display:none;"></div>');
+
+        $container.append($input);
+        E.$body.append($container);
+
+        // onchange 事件
+        $input.on('change', function (e) {
+            self.selected(e, $input.get(0));
+        });
+
+        // 记录对象数据
+        self.$input = $input;
+
+        // 记录
+        self._hasRender = true;
+    };
+
+    // 选择
+    UploadFile.fn.selectFiles = function () {
+        var self = this;
+
+        E.log('使用 html5 方式上传');
+
+        // 先渲染
+        self.render();
+
+        // 选择
+        E.log('选择文件');
+        self.$input.click();
+    };
+
+    // 选中文件之后
+    UploadFile.fn.selected = function (e, input) {
+        var self = this;
+        var files = input.files || [];
+        if (files.length === 0) {
+            return;
+        }
+
+        E.log('选中 ' + files.length + ' 个文件');
+
+        // 遍历选中的文件,预览、上传
+        $.each(files, function (key, value) {
+            self.upload(value);
+        });
+    };
+
+    // 上传单个文件
+    UploadFile.fn.upload = function (file) {
+        var self = this;
+        var editor = self.editor;
+        var filename = file.name || '';
+        var fileType = file.type || '';
+        var uploadImgFns = editor.config.uploadImgFns;
+        var uploadFileName = editor.config.uploadImgFileName || 'wangEditorH5File';
+        var onload = uploadImgFns.onload;
+        var ontimeout = uploadImgFns.ontimeout;
+        var onerror = uploadImgFns.onerror;
+        var reader = new FileReader();
+
+        if (!onload || !ontimeout || !onerror) {
+            E.error('请为编辑器配置上传图片的 onload ontimeout onerror 回调事件');
+            return;
+        }
+
+
+        E.log('开始执行 ' + filename + ' 文件的上传');
+
+        // 清空 input 数据
+        function clearInput() {
+            self.clear();
+        }
+
+        // onload事件
+        reader.onload = function (e) {
+            E.log('已读取' + filename + '文件');
+
+            var base64 = e.target.result || this.result;
+            editor.xhrUploadImg({
+                event: e,
+                filename: filename,
+                base64: base64,
+                fileType: fileType,
+                name: uploadFileName,
+                loadfn: function (resultText, xhr) {
+                    clearInput();
+                    // 执行配置中的方法
+                    var editor = this;
+                    onload.call(editor, resultText, xhr);
+                },
+                errorfn: function (xhr) {
+                    clearInput();
+                    if (E.isOnWebsite) {
+                        alert('wangEditor官网暂时没有服务端,因此报错。实际项目中不会发生');
+                    }
+                    // 执行配置中的方法
+                    var editor = this;
+                    onerror.call(editor, xhr);
+                },
+                timeoutfn: function (xhr) {
+                    clearInput();
+                    if (E.isOnWebsite) {
+                        alert('wangEditor官网暂时没有服务端,因此超时。实际项目中不会发生');
+                    }
+                    // 执行配置中的方法
+                    var editor = this;
+                    ontimeout(editor, xhr);
+                }
+            });
+        };
+
+        // 开始取文件
+        reader.readAsDataURL(file);
+    };
+
+    // 暴露给 E
+    E.UploadFile = UploadFile;
+
+});
+// form方式上传图片
+_e(function (E, $) {
+
+    if (window.FileReader && window.FormData) {
+        // 如果支持 html5 上传,则返回
+        return;
+    }
+    
+    // 构造函数
+    var UploadFile = function (opt) {
+        this.editor = opt.editor;
+        this.uploadUrl = opt.uploadUrl;
+        this.timeout = opt.timeout;
+        this.fileAccept = opt.fileAccept;
+        this.multiple = false;
+    };
+
+    UploadFile.fn = UploadFile.prototype;
+
+    // clear
+    UploadFile.fn.clear = function () {
+        this.$input.val('');
+        E.log('input value 已清空');
+    };
+
+    // 隐藏modal
+    UploadFile.fn.hideModal = function () {
+        this.modal.hide();
+    };
+
+    // 渲染
+    UploadFile.fn.render = function () {
+        var self = this;
+        var editor = self.editor;
+        var uploadFileName = editor.config.uploadImgFileName || 'wangEditorFormFile';
+        if (self._hasRender) {
+            // 不要重复渲染
+            return;
+        }
+
+        // 服务器端路径
+        var uploadUrl = self.uploadUrl;
+
+        E.log('渲染dom');
+
+        // 创建 form 和 iframe
+        var iframeId = 'iframe' + E.random();
+        var $iframe = $('<iframe name="' + iframeId + '" id="' + iframeId + '" frameborder="0" width="0" height="0"></iframe>');
+        var multiple = self.multiple;
+        var multipleTpl = multiple ? 'multiple="multiple"' : '';
+        var $p = $('<p>选择图片并上传</p>');
+        var $input = $('<input type="file" ' + multipleTpl + ' name="' + uploadFileName + '"/>');
+        var $btn = $('<input type="submit" value="上传"/>');
+        var $form = $('<form enctype="multipart/form-data" method="post" action="' + uploadUrl + '" target="' + iframeId + '"></form>');
+        var $container = $('<div style="margin:10px 20px;"></div>');
+
+        $form.append($p).append($input).append($btn);
+
+        // 增加用户配置的参数,如 token
+        $.each(editor.config.uploadParams, function (key, value) {
+            $form.append( $('<input type="hidden" name="' + key + '" value="' + value + '"/>') );
+        });
+
+        $container.append($form);
+        $container.append($iframe);
+
+        self.$input = $input;
+        self.$iframe = $iframe;
+
+        // 生成 modal
+        var modal = new E.Modal(editor, undefined, {
+            $content: $container
+        });
+        self.modal = modal;
+
+        // 记录
+        self._hasRender = true;
+    };
+
+    // 绑定 iframe load 事件
+    UploadFile.fn.bindLoadEvent = function () {
+        var self = this;
+        if (self._hasBindLoad) {
+            // 不要重复绑定
+            return;
+        }
+
+        var editor = self.editor;
+        var $iframe = self.$iframe;
+        var iframe = $iframe.get(0);
+        var iframeWindow = iframe.contentWindow;
+        var onload = editor.config.uploadImgFns.onload;
+
+        // 定义load事件
+        function onloadFn() {
+            var resultText = $.trim(iframeWindow.document.body.innerHTML);
+            if (!resultText) {
+                return;
+            }
+
+            // 获取文件名
+            var fileFullName = self.$input.val();  // 结果如 C:\folder\abc.png 格式
+            var fileOriginalName = fileFullName;
+            if (fileFullName.lastIndexOf('\\') >= 0) {
+                // 获取 abc.png 格式
+                fileOriginalName = fileFullName.slice(fileFullName.lastIndexOf('\\') + 1);
+                if (fileOriginalName.indexOf('.') > 0) {
+                    // 获取 abc (即不带扩展名的文件名)
+                    fileOriginalName = fileOriginalName.split('.')[0];
+                }
+            }
+
+            // 将文件名暂存到 editor.uploadImgOriginalName ,插入图片时,可作为 alt 属性来用
+            editor.uploadImgOriginalName = fileOriginalName;
+
+            // 执行load函数,插入图片的操作,应该在load函数中执行
+            onload.call(editor, resultText);
+
+            // 清空 input 数据
+            self.clear();
+
+            // 隐藏modal
+            self.hideModal();
+        }
+
+        // 绑定 load 事件
+        if (iframe.attachEvent) {
+            iframe.attachEvent('onload', onloadFn);
+        } else {
+            iframe.onload = onloadFn;
+        }
+
+        // 记录
+        self._hasBindLoad = true;
+    };
+
+    UploadFile.fn.show = function () {
+        var self = this;
+        var modal = self.modal;
+
+        function show() {
+            modal.show();
+            self.bindLoadEvent();
+        }
+        setTimeout(show);
+    };
+
+    // 选择
+    UploadFile.fn.selectFiles = function () {
+        var self = this;
+
+        E.log('使用 form 方式上传');
+
+        // 先渲染
+        self.render();
+
+        // 先清空
+        self.clear();
+
+        // 显示
+        self.show();
+    };
+
+    // 暴露给 E
+    E.UploadFile = UploadFile;
+
+});
+// upload img 插件 粘贴图片
+_e(function (E, $) {
+    
+    E.plugin(function () {
+        var editor = this;
+        var txt = editor.txt;
+        var $txt = txt.$txt;
+        var config = editor.config;
+        var uploadImgUrl = config.uploadImgUrl;
+        var uploadFileName = config.uploadImgFileName || 'wangEditorPasteFile';
+        var pasteEvent;
+        var $imgsBeforePaste;
+
+        // 未配置上传图片url,则忽略
+        if (!uploadImgUrl) {
+            return;
+        }
+
+        // -------- 非 chrome 下,通过查找粘贴的图片的方式上传 --------
+        function findPasteImgAndUpload() {
+            var reg = /^data:(image\/\w+);base64/;
+            var $imgs = $txt.find('img');
+
+            E.log('粘贴后,检查到编辑器有' + $imgs.length + '个图片。开始遍历图片,试图找到刚刚粘贴过来的图片');
+
+            $.each($imgs, function () {
+                var img = this;
+                var $img = $(img);
+                var flag;
+                var base64 = $img.attr('src');
+                var type;
+
+                // 判断当前图片是否是粘贴之前的
+                $imgsBeforePaste.each(function () {
+                    if (img === this) {
+                        // 当前图片是粘贴之前的
+                        flag = true;
+                        return false;
+                    }
+                });
+
+                // 当前图片是粘贴之前的,则忽略
+                if (flag) {
+                    return;
+                }
+
+                E.log('找到一个粘贴过来的图片');
+
+                if (reg.test(base64)) {
+                    // 得到的粘贴的图片是 base64 格式,符合要求
+                    E.log('src 是 base64 格式,可以上传');
+                    type = base64.match(reg)[1];
+                    editor.xhrUploadImg({
+                        event: pasteEvent,
+                        base64: base64,
+                        fileType: type,
+                        name: uploadFileName
+                    });
+                } else {
+                    E.log('src 为 ' + base64 + ' ,不是 base64 格式,暂时不支持上传');
+                }
+
+                // 最终移除原图片
+                $img.remove();
+            });
+
+            E.log('遍历结束');
+        }
+
+        // 开始监控粘贴事件
+        $txt.on('paste', function (e) {
+            pasteEvent = e;
+            var data = pasteEvent.clipboardData || pasteEvent.originalEvent.clipboardData;
+            var text;
+            var items;
+
+            // -------- 试图获取剪切板中的文字,有文字的情况下,就不处理图片粘贴 --------
+            if (data == null) {
+                text = window.clipboardData && window.clipboardData.getData('text');
+            } else {
+                text = data.getData('text/plain') || data.getData('text/html');
+            }
+            if (text) {
+                return;
+            }
+
+            items = data && data.items;
+            if (items) {
+                // -------- chrome 可以用 data.items 取出图片 -----
+                E.log('通过 data.items 得到了数据');
+
+                $.each(items, function (key, value) {
+                    var fileType = value.type || '';
+                    if(fileType.indexOf('image') < 0) {
+                        // 不是图片
+                        return;
+                    }
+
+                    var file = value.getAsFile();
+                    var reader = new FileReader();
+
+                    E.log('得到一个粘贴图片');
+
+                    reader.onload = function (e) {
+                        E.log('读取到粘贴的图片');
+
+                        // 执行上传
+                        var base64 = e.target.result || this.result;
+                        editor.xhrUploadImg({
+                            event: pasteEvent,
+                            base64: base64,
+                            fileType: fileType,
+                            name: uploadFileName
+                        });
+                    };
+
+                    //读取粘贴的文件
+                    reader.readAsDataURL(file);
+                });
+            } else {
+                // -------- 非 chrome 不能用 data.items 取图片 -----
+
+                E.log('未从 data.items 得到数据,使用检测粘贴图片的方式');
+
+                // 获取
+                $imgsBeforePaste = $txt.find('img');
+                E.log('粘贴前,检查到编辑器有' + $imgsBeforePaste.length + '个图片');
+
+                // 异步上传找到的图片
+                setTimeout(findPasteImgAndUpload, 0);
+            }
+        });
+
+    });
+});
+// 拖拽上传图片 插件 
+_e(function (E, $) {
+
+    E.plugin(function () {
+
+        var editor = this;
+        var txt = editor.txt;
+        var $txt = txt.$txt;
+        var config = editor.config;
+        var uploadImgUrl = config.uploadImgUrl;
+        var uploadFileName = config.uploadImgFileName || 'wangEditorDragFile';
+
+        // 未配置上传图片url,则忽略
+        if (!uploadImgUrl) {
+            return;
+        }
+
+        // 阻止浏览器默认行为
+        E.$document.on('dragleave drop dragenter dragover', function (e) {
+            e.preventDefault();
+        });
+
+        // 监控 $txt drop 事件
+        $txt.on('drop', function (dragEvent) {
+            dragEvent.preventDefault();
+
+            var originalEvent = dragEvent.originalEvent;
+            var files = originalEvent.dataTransfer && originalEvent.dataTransfer.files;
+
+            if (!files || !files.length) {
+                return;
+            }
+
+            $.each(files, function (k, file) {
+                var type = file.type;
+                var name = file.name;
+
+                if (type.indexOf('image/') < 0) {
+                    // 只接收图片
+                    return;
+                }
+
+                E.log('得到图片 ' + name);
+
+                var reader = new FileReader();
+                reader.onload = function (e) {
+                    E.log('读取到图片 ' + name);
+
+                    // 执行上传
+                    var base64 = e.target.result || this.result;
+                    editor.xhrUploadImg({
+                        event: dragEvent,
+                        base64: base64,
+                        fileType: type,
+                        name: uploadFileName
+                    });
+                };
+
+                //读取粘贴的文件
+                reader.readAsDataURL(file);
+
+            });
+        });
+    });
+
+});
+// 编辑器区域 table toolbar
+_e(function (E, $) {
+
+    E.plugin(function () {
+        var editor = this;
+        var txt = editor.txt;
+        var $txt = txt.$txt;
+        var html = '';
+        // 说明:设置了 max-height 之后,$txt.parent() 负责滚动处理
+        var $currentTxt = editor.useMaxHeight ? $txt.parent() : $txt;
+        var $currentTable;
+
+        // 用到的dom节点
+        var isRendered = false;
+        var $toolbar = $('<div class="txt-toolbar"></div>');
+        var $triangle = $('<div class="tip-triangle"></div>');
+        var $delete = $('<a href="#"><i class="wangeditor-menu-img-trash-o"></i></a>');
+        var $zoomSmall = $('<a href="#"><i class="wangeditor-menu-img-search-minus"></i></a>');
+        var $zoomBig = $('<a href="#"><i class="wangeditor-menu-img-search-plus"></i></a>');
+
+        // 渲染到页面
+        function render() {
+            if (isRendered) {
+                return;
+            }
+            
+            // 绑定事件
+            bindEvent();
+
+            // 拼接 渲染到页面上
+            $toolbar.append($triangle)
+                    .append($delete)
+                    .append($zoomSmall)
+                    .append($zoomBig);
+            editor.$editorContainer.append($toolbar);
+            isRendered = true;
+        }
+
+        // 绑定事件
+        function bindEvent() {
+            // 统一执行命令的方法
+            var commandFn;
+            function command(e, callback) {
+                // 执行命令之前,先存储html内容
+                html = $txt.html();
+                // 监控内容变化
+                var cb = function  () {
+                    if (callback) {
+                        callback();
+                    }
+                    if (html !== $txt.html()) {
+                        $txt.change();
+                    }
+                };
+                // 执行命令
+                if (commandFn) {
+                    editor.customCommand(e, commandFn, cb);
+                }
+            }
+
+            // 删除
+            $delete.click(function (e) {
+                commandFn = function () {
+                    $currentTable.remove();
+                };
+                command(e, function () {
+                    setTimeout(hide, 100);
+                });
+            });
+
+            // 放大
+            $zoomBig.click(function (e) {
+                commandFn = function () {
+                    $currentTable.css({
+                        width: '100%'
+                    });
+                };
+                command(e, function () {
+                    setTimeout(show);
+                });
+            });
+
+            // 缩小
+            $zoomSmall.click(function (e) {
+                commandFn = function () {
+                    $currentTable.css({
+                        width: 'auto'
+                    });
+                };
+                command(e, function () {
+                    setTimeout(show);
+                });
+            });
+        }
+
+        // 显示 toolbar
+        function show() {
+            if (editor._disabled) {
+                // 编辑器已经被禁用,则不让显示
+                return;
+            }
+            if ($currentTable == null) {
+                return;
+            }
+            $currentTable.addClass('clicked');
+            var tablePosition = $currentTable.position();
+            var tableTop = tablePosition.top;
+            var tableLeft = tablePosition.left;
+            var tableHeight = $currentTable.outerHeight();
+            var tableWidth = $currentTable.outerWidth();
+
+            // --- 定位 toolbar ---
+
+            // 计算初步结果
+            var top = tableTop + tableHeight;
+            var left = tableLeft;
+            var marginLeft = 0;
+
+            var txtTop = $currentTxt.position().top;
+            var txtHeight = $currentTxt.outerHeight();
+            if (top > (txtTop + txtHeight)) {
+                // top 不得超出编辑范围
+                top = txtTop + txtHeight;
+            }
+
+            // 显示(方便计算 margin)
+            $toolbar.show();
+
+            // 计算 margin
+            var width = $toolbar.outerWidth();
+            marginLeft = tableWidth / 2 - width / 2;
+
+            // 定位
+            $toolbar.css({
+                top: top + 5,
+                left: left,
+                'margin-left': marginLeft
+            });
+            // 如果定位太靠左了
+            if (marginLeft < 0) {
+                // 得到三角形的margin-left
+                $toolbar.css('margin-left', '0');
+                $triangle.hide();
+            } else {
+                $triangle.show();
+            }
+        }
+        
+        // 隐藏 toolbar
+        function hide() {
+            if ($currentTable == null) {
+                return;
+            }
+            $currentTable.removeClass('clicked');
+            $currentTable = null;
+            $toolbar.hide();
+        }
+
+        // click table 事件
+        $currentTxt.on('click', 'table', function (e) {
+            var $table = $(e.currentTarget);
+
+            // 渲染
+            render();
+
+            if ($currentTable && ($currentTable.get(0) === $table.get(0))) {
+                setTimeout(hide, 100);
+                return;
+            }
+
+            // 显示 toolbar
+            $currentTable = $table;
+            show();
+
+            // 阻止冒泡
+            e.preventDefault();
+            e.stopPropagation();
+            
+        }).on('click keydown scroll', function (e) {
+            setTimeout(hide, 100);
+        });
+        E.$body.on('click keydown scroll', function (e) {
+            setTimeout(hide, 100);
+        });
+    });
+
+});
+// 编辑器区域 img toolbar
+_e(function (E, $) {
+
+    if (E.userAgent.indexOf('MSIE 8') > 0) {
+        return;
+    }
+    
+    E.plugin(function () {
+        var editor = this;
+        var lang = editor.config.lang;
+        var txt = editor.txt;
+        var $txt = txt.$txt;
+        var html = '';
+        // 说明:设置了 max-height 之后,$txt.parent() 负责滚动处理
+        var $currentTxt = editor.useMaxHeight ? $txt.parent() : $txt;
+        var $editorContainer = editor.$editorContainer;
+        var $currentImg;
+        var currentLink = '';
+
+        // 用到的dom节点
+        var isRendered = false;
+        var $dragPoint = $('<div class="img-drag-point"></div>');
+
+        var $toolbar = $('<div class="txt-toolbar"></div>');
+        var $triangle = $('<div class="tip-triangle"></div>');
+
+        var $menuContainer = $('<div></div>');
+        var $delete = $('<a href="#"><i class="wangeditor-menu-img-trash-o"></i></a>');
+        var $zoomSmall = $('<a href="#"><i class="wangeditor-menu-img-search-minus"></i></a>');
+        var $zoomBig = $('<a href="#"><i class="wangeditor-menu-img-search-plus"></i></a>');
+        // var $floatLeft = $('<a href="#"><i class="wangeditor-menu-img-align-left"></i></a>');
+        // var $noFloat = $('<a href="#"><i class="wangeditor-menu-img-align-justify"></i></a>');
+        // var $floatRight = $('<a href="#"><i class="wangeditor-menu-img-align-right"></i></a>');
+        var $alignLeft = $('<a href="#"><i class="wangeditor-menu-img-align-left"></i></a>');
+        var $alignCenter = $('<a href="#"><i class="wangeditor-menu-img-align-center"></i></a>');
+        var $alignRight = $('<a href="#"><i class="wangeditor-menu-img-align-right"></i></a>');
+        var $link = $('<a href="#"><i class="wangeditor-menu-img-link"></i></a>');
+        var $unLink = $('<a href="#"><i class="wangeditor-menu-img-unlink"></i></a>');
+
+        var $linkInputContainer = $('<div style="display:none;"></div>');
+        var $linkInput = $('<input type="text" style="height:26px; margin-left:10px; width:200px;"/>');
+        var $linkBtnSubmit = $('<button class="right">' + lang.submit + '</button>');
+        var $linkBtnCancel = $('<button class="right gray">' + lang.cancel + '</button>');
+
+        // 记录是否正在拖拽
+        var isOnDrag = false;
+
+        // 获取 / 设置 链接
+        function imgLink(e, url) {
+            if (!$currentImg) {
+                return;
+            }
+            var commandFn;
+            var callback = function () {
+                // 及时保存currentLink
+                if (url != null) {
+                    currentLink = url;
+                }
+                if (html !== $txt.html()) {
+                    $txt.change();
+                }
+            };
+            var $link;
+            var inLink = false;
+            var $parent = $currentImg.parent();
+            if ($parent.get(0).nodeName.toLowerCase() === 'a') {
+                // 父元素就是图片链接
+                $link = $parent;
+                inLink = true;
+            } else {
+                // 父元素不是图片链接,则重新创建一个链接
+                $link = $('<a target="_blank"></a>');
+            }
+
+            if (url == null) {
+                // url 无值,是获取链接
+                return $link.attr('href') || '';
+            } else if (url === '') {
+                // url 是空字符串,是取消链接
+                if (inLink) {
+                    commandFn = function () {
+                        $currentImg.unwrap();
+                    };
+                }
+            } else {
+                // url 有值,是设置链接
+                if (url === currentLink) {
+                    return;
+                }
+                commandFn = function () {
+                    $link.attr('href', url);
+
+                    if (!inLink) {
+                        // 当前图片未包含在链接中,则包含进来
+                        $currentImg.wrap($link);
+                    }
+                };
+            }
+
+            // 执行命令
+            if (commandFn) {
+                // 记录下执行命令之前的html内容
+                html = $txt.html();
+                // 执行命令
+                editor.customCommand(e, commandFn, callback);
+            }
+        }
+
+        // 渲染到页面
+        function render() {
+            if (isRendered) {
+                return;
+            }
+            
+            // 绑定事件
+            bindToolbarEvent();
+            bindDragEvent();
+
+            // 菜单放入 container
+            $menuContainer.append($delete)
+                            .append($zoomSmall)
+                            .append($zoomBig)
+                            // .append($floatLeft)
+                            // .append($noFloat)
+                            // .append($floatRight);
+                            .append($alignLeft)
+                            .append($alignCenter)
+                            .append($alignRight)
+                            .append($link)
+                            .append($unLink);
+
+            // 链接input放入container
+            $linkInputContainer.append($linkInput)
+                               .append($linkBtnCancel)
+                               .append($linkBtnSubmit);
+
+            // 拼接 渲染到页面上
+            $toolbar.append($triangle)
+                    .append($menuContainer)
+                    .append($linkInputContainer);
+                    
+            editor.$editorContainer.append($toolbar).append($dragPoint);
+            isRendered = true;
+        }
+
+        // 绑定toolbar事件
+        function bindToolbarEvent() {
+            // 统一执行命令的方法
+            var commandFn;
+            function customCommand(e, callback) {
+                var cb;
+                // 记录下执行命令之前的html内容
+                html = $txt.html();
+                cb = function () {
+                    if (callback) {
+                        callback();
+                    }
+                    if (html !== $txt.html()) {
+                        $txt.change();
+                    }
+                };
+                // 执行命令
+                if (commandFn) {
+                    editor.customCommand(e, commandFn, cb);
+                }
+            }
+
+            // 删除
+            $delete.click(function (e) {
+                // 删除之前先unlink
+                imgLink(e, '');
+
+                // 删除图片
+                commandFn = function () {
+                    $currentImg.remove();
+                };
+                customCommand(e, function () {
+                    setTimeout(hide, 100);
+                });
+            });
+
+            // 放大
+            $zoomBig.click(function (e) {
+                commandFn = function () {
+                    var img = $currentImg.get(0);
+                    var width = img.width;
+                    var height = img.height;
+                    width = width * 1.1;
+                    height = height * 1.1;
+
+                    $currentImg.css({
+                        width: width + 'px',
+                        height: height + 'px'
+                    });
+                };
+                customCommand(e, function () {
+                    setTimeout(show);
+                });
+            });
+
+            // 缩小
+            $zoomSmall.click(function (e) {
+                commandFn = function () {
+                    var img = $currentImg.get(0);
+                    var width = img.width;
+                    var height = img.height;
+                    width = width * 0.9;
+                    height = height * 0.9;
+
+                    $currentImg.css({
+                        width: width + 'px',
+                        height: height + 'px'
+                    });
+                };
+                customCommand(e, function () {
+                    setTimeout(show);
+                });
+            });
+
+            // // 左浮动
+            // $floatLeft.click(function (e) {
+            //     commandFn = function () {
+            //         $currentImg.css({
+            //             float: 'left'
+            //         });
+            //     };
+            //     customCommand(e, function () {
+            //         setTimeout(hide, 100);
+            //     });
+            // });
+
+            // alignLeft
+            $alignLeft.click(function (e) {
+                commandFn = function () {
+                    // 如果 img 增加了链接,那么 img.parent() 就是 a 标签,设置 align 没用的,因此必须找到 P 父节点来设置 align
+                    $currentImg.parents('p').css({
+                        'text-align': 'left'
+                    }).attr('align', 'left');
+                };
+                customCommand(e, function () {
+                    setTimeout(hide, 100);
+                });
+            });
+
+            // // 右浮动
+            // $floatRight.click(function (e) {
+            //     commandFn = function () {
+            //         $currentImg.css({
+            //             float: 'right'
+            //         });
+            //     };
+            //     customCommand(e, function () {
+            //         setTimeout(hide, 100);
+            //     });
+            // });
+
+            // alignRight
+            $alignRight.click(function (e) {
+                commandFn = function () {
+                    // 如果 img 增加了链接,那么 img.parent() 就是 a 标签,设置 align 没用的,因此必须找到 P 父节点来设置 align
+                    $currentImg.parents('p').css({
+                        'text-align': 'right'
+                    }).attr('align', 'right');
+                };
+                customCommand(e, function () {
+                    setTimeout(hide, 100);
+                });
+            });
+
+            // // 无浮动
+            // $noFloat.click(function (e) {
+            //     commandFn = function () {
+            //         $currentImg.css({
+            //             float: 'none'
+            //         });
+            //     };
+            //     customCommand(e, function () {
+            //         setTimeout(hide, 100);
+            //     });
+            // });
+
+            // alignCenter
+            $alignCenter.click(function (e) {
+                commandFn = function () {
+                    // 如果 img 增加了链接,那么 img.parent() 就是 a 标签,设置 align 没用的,因此必须找到 P 父节点来设置 align
+                    $currentImg.parents('p').css({
+                        'text-align': 'center'
+                    }).attr('align', 'center');
+                };
+                customCommand(e, function () {
+                    setTimeout(hide, 100);
+                });
+            });
+
+            // link
+            // 显示链接input
+            $link.click(function (e) {
+                e.preventDefault();
+
+                // 获取当前链接,并显示
+                currentLink = imgLink(e);
+                $linkInput.val(currentLink);
+
+                $menuContainer.hide();
+                $linkInputContainer.show();
+            });
+            // 设置链接
+            $linkBtnSubmit.click(function (e) {
+                e.preventDefault();
+
+                var url = $.trim($linkInput.val());
+                if (url) {
+                    // 设置链接,同时会自动更新 currentLink 的值
+                    imgLink(e, url);
+                }
+
+                // 隐藏 toolbar
+                setTimeout(hide);
+            });
+            // 取消设置链接
+            $linkBtnCancel.click(function (e) {
+                e.preventDefault();
+
+                // 重置链接 input
+                $linkInput.val(currentLink);
+
+                $menuContainer.show();
+                $linkInputContainer.hide();
+            });
+
+            // unlink
+            $unLink.click(function (e) {
+                e.preventDefault();
+
+                // 执行 unlink
+                imgLink(e, '');
+
+                // 隐藏 toolbar
+                setTimeout(hide);
+            });
+        }
+
+        // 绑定drag事件
+        function bindDragEvent() {
+            var _x, _y;
+            var dragMarginLeft, dragMarginTop;
+            var imgWidth, imgHeight;
+
+            function mousemove (e) {
+                var diffX, diffY;
+
+                // 计算差额
+                diffX = e.pageX - _x;
+                diffY = e.pageY - _y;
+
+                // --------- 计算拖拽点的位置 ---------
+                var currentDragMarginLeft = dragMarginLeft + diffX;
+                var currentDragMarginTop = dragMarginTop + diffY;
+                $dragPoint.css({
+                    'margin-left': currentDragMarginLeft,
+                    'margin-top': currentDragMarginTop
+                });
+
+                // --------- 计算图片的大小 ---------
+                var currentImgWidth = imgWidth + diffX;
+                var currentImggHeight = imgHeight + diffY;
+                $currentImg && $currentImg.css({
+                    width: currentImgWidth,
+                    height: currentImggHeight
+                });
+            }
+
+            $dragPoint.on('mousedown', function(e){
+                if (!$currentImg) {
+                    return;
+                }
+                // 当前鼠标位置
+                _x = e.pageX;
+                _y = e.pageY;
+
+                // 当前拖拽点的位置
+                dragMarginLeft = parseFloat($dragPoint.css('margin-left'), 10);
+                dragMarginTop = parseFloat($dragPoint.css('margin-top'), 10);
+
+                // 当前图片的大小
+                imgWidth = $currentImg.width();
+                imgHeight = $currentImg.height();
+
+                // 隐藏 $toolbar
+                $toolbar.hide();
+
+                // 绑定计算事件
+                E.$document.on('mousemove._dragResizeImg', mousemove);
+                E.$document.on('mouseup._dragResizeImg', function (e) {
+                    // 取消绑定
+                    E.$document.off('mousemove._dragResizeImg');
+                    E.$document.off('mouseup._dragResizeImg');
+
+                    // 隐藏,并还原拖拽点的位置
+                    hide();
+                    $dragPoint.css({
+                        'margin-left': dragMarginLeft,
+                        'margin-top': dragMarginTop
+                    });
+
+                    // 记录
+                    isOnDrag = false;
+                });
+
+                // 记录
+                isOnDrag = true;
+            });
+        }
+
+        // 显示 toolbar
+        function show() {
+            if (editor._disabled) {
+                // 编辑器已经被禁用,则不让显示
+                return;
+            }
+            if ($currentImg == null) {
+                return;
+            }
+            $currentImg.addClass('clicked');
+            var imgPosition = $currentImg.position();
+            var imgTop = imgPosition.top;
+            var imgLeft = imgPosition.left;
+            var imgHeight = $currentImg.outerHeight();
+            var imgWidth = $currentImg.outerWidth();
+
+
+            // --- 定位 dragpoint ---
+            $dragPoint.css({
+                top: imgTop + imgHeight,
+                left: imgLeft + imgWidth
+            });
+
+            // --- 定位 toolbar ---
+
+            // 计算初步结果
+            var top = imgTop + imgHeight;
+            var left = imgLeft;
+            var marginLeft = 0;
+
+            var txtTop = $currentTxt.position().top;
+            var txtHeight = $currentTxt.outerHeight();
+            if (top > (txtTop + txtHeight)) {
+                // top 不得超出编辑范围
+                top = txtTop + txtHeight;
+            } else {
+                // top 超出编辑范围,dragPoint就不显示了
+                $dragPoint.show();
+            }
+
+            // 显示(方便计算 margin)
+            $toolbar.show();
+
+            // 计算 margin
+            var width = $toolbar.outerWidth();
+            marginLeft = imgWidth / 2 - width / 2;
+
+            // 定位
+            $toolbar.css({
+                top: top + 5,
+                left: left,
+                'margin-left': marginLeft
+            });
+            // 如果定位太靠左了
+            if (marginLeft < 0) {
+                // 得到三角形的margin-left
+                $toolbar.css('margin-left', '0');
+                $triangle.hide();
+            } else {
+                $triangle.show();
+            }
+
+            // disable 菜单
+            editor.disableMenusExcept();
+        }
+        
+        // 隐藏 toolbar
+        function hide() {
+            if ($currentImg == null) {
+                return;
+            }
+            $currentImg.removeClass('clicked');
+            $currentImg = null;
+
+            $toolbar.hide();
+            $dragPoint.hide();
+
+            // enable 菜单
+            editor.enableMenusExcept();
+        }
+
+        // 判断img是否是一个表情
+        function isEmotion(imgSrc) {
+            var result = false;
+            if (!editor.emotionUrls) {
+                return result;
+            }
+            $.each(editor.emotionUrls, function (index, url) {
+                var flag = false;
+                if (imgSrc === url) {
+                    result = true;
+                    flag = true;
+                }
+                if (flag) {
+                    return false;  // break 循环
+                }
+            });
+            return result;
+        }
+
+        // click img 事件
+        $currentTxt.on('mousedown', 'img', function (e) {
+            e.preventDefault();
+        }).on('click', 'img', function (e) {
+            var $img = $(e.currentTarget);
+            var src = $img.attr('src');
+
+            if (!src || isEmotion(src)) {
+                // 是一个表情图标
+                return;
+            }
+
+            // ---------- 不是表情图标 ---------- 
+
+            // 渲染
+            render();
+
+            if ($currentImg && ($currentImg.get(0) === $img.get(0))) {
+                setTimeout(hide, 100);
+                return;
+            }
+
+            // 显示 toolbar
+            $currentImg = $img;
+            show();
+
+            // 默认显示menuContainer,其他默认隐藏
+            $menuContainer.show();
+            $linkInputContainer.hide();
+
+            // 阻止冒泡
+            e.preventDefault();
+            e.stopPropagation();
+            
+        }).on('click keydown scroll', function (e) {
+            if (!isOnDrag) {
+                setTimeout(hide, 100);
+            }
+        });
+
+    });
+
+});
+// 编辑区域 link toolbar
+_e(function (E, $) {
+    E.plugin(function () {
+        var editor = this;
+        var lang = editor.config.lang;
+        var $txt = editor.txt.$txt;
+
+        // 当前命中的链接
+        var $currentLink;
+
+        var $toolbar = $('<div class="txt-toolbar"></div>');
+        var $triangle = $('<div class="tip-triangle"></div>');
+        var $triggerLink = $('<a href="#" target="_blank"><i class="wangeditor-menu-img-link"></i> ' + lang.openLink + '</a>');
+        var isRendered;
+
+        // 记录当前的显示/隐藏状态
+        var isShow = false;
+
+        var showTimeoutId, hideTimeoutId;
+        var showTimeoutIdByToolbar, hideTimeoutIdByToolbar;
+
+        // 渲染 dom
+        function render() {
+            if (isRendered) {
+                return;
+            }
+
+            $toolbar.append($triangle)
+                    .append($triggerLink);
+
+            editor.$editorContainer.append($toolbar);
+
+            isRendered = true;
+        }
+
+        // 定位
+        function setPosition() {
+            if (!$currentLink) {
+                return;
+            }
+
+            var position = $currentLink.position();
+            var left = position.left;
+            var top = position.top;
+            var height = $currentLink.height();
+
+            // 初步计算top值
+            var topResult = top + height + 5;
+
+            // 判断 toolbar 是否超过了编辑器区域的下边界
+            var menuHeight = editor.menuContainer.height();
+            var txtHeight = editor.txt.$txt.outerHeight();
+            if (topResult > menuHeight + txtHeight) {
+                topResult = menuHeight + txtHeight + 5;
+            }
+
+            // 最终设置
+            $toolbar.css({
+                top: topResult,
+                left: left
+            });
+        }
+
+        // 显示 toolbar
+        function show() {
+            if (isShow) {
+                return;
+            }
+
+            if (!$currentLink) {
+                return;
+            }
+
+            render();
+
+            $toolbar.show();
+
+            // 设置链接
+            var href = $currentLink.attr('href');
+            $triggerLink.attr('href', href);
+
+            // 定位
+            setPosition();
+
+            isShow = true;
+        }
+
+        // 隐藏 toolbar
+        function hide() {
+            if (!isShow) {
+                return;
+            }
+
+            if (!$currentLink) {
+                return;
+            }
+
+            $toolbar.hide();
+            isShow = false;
+        }
+
+        // $txt 绑定事件
+        $txt.on('mouseenter', 'a', function (e) {
+            // 延时 500ms 显示toolbar
+            if (showTimeoutId) {
+                clearTimeout(showTimeoutId);
+            }
+            showTimeoutId = setTimeout(function () {
+                var a = e.currentTarget;
+                var $a = $(a);
+                $currentLink = $a;
+
+                var $img = $a.children('img');
+                if ($img.length) {
+                    // 该链接下包含一个图片
+
+                    // 图片点击时,隐藏toolbar
+                    $img.click(function (e) {
+                        hide();
+                    });
+
+                    if ($img.hasClass('clicked')) {
+                        // 图片还处于clicked状态,则不显示toolbar
+                        return;
+                    }
+                }
+
+                // 显示toolbar
+                show();
+            }, 500);
+        }).on('mouseleave', 'a', function (e) {
+            // 延时 500ms 隐藏toolbar
+            if (hideTimeoutId) {
+                clearTimeout(hideTimeoutId);
+            }
+            hideTimeoutId = setTimeout(hide, 500);
+        }).on('click keydown scroll', function (e) {
+            setTimeout(hide, 100);
+        });
+        // $toolbar 绑定事件
+        $toolbar.on('mouseenter', function (e) {
+            // 先中断掉 $txt.mouseleave 导致的隐藏
+            if (hideTimeoutId) {
+                clearTimeout(hideTimeoutId);
+            }
+        }).on('mouseleave', function (e) {
+            // 延时 500ms 显示toolbar
+            if (showTimeoutIdByToolbar) {
+                clearTimeout(showTimeoutIdByToolbar);
+            }
+            showTimeoutIdByToolbar = setTimeout(hide, 500);
+        });
+    });
+});
+// menu吸顶
+_e(function (E, $) {
+
+    E.plugin(function () {
+        var editor = this;
+        var menuFixed = editor.config.menuFixed;
+        if (menuFixed === false || typeof menuFixed !== 'number') {
+            // 没有配置菜单吸顶
+            return;
+        }
+        var bodyMarginTop = parseFloat(E.$body.css('margin-top'), 10);
+        if (isNaN(bodyMarginTop)) {
+            bodyMarginTop = 0;
+        }
+
+        var $editorContainer = editor.$editorContainer;
+        var editorTop = $editorContainer.offset().top;
+        var editorHeight = $editorContainer.outerHeight();
+        
+        var $menuContainer = editor.menuContainer.$menuContainer;
+        var menuCssPosition = $menuContainer.css('position');
+        var menuCssTop = $menuContainer.css('top');
+        var menuTop = $menuContainer.offset().top;
+        var menuHeight = $menuContainer.outerHeight();
+        
+        var $txt = editor.txt.$txt;
+
+        E.$window.scroll(function () {
+            //全屏模式不支持
+            if (editor.isFullScreen) {
+                return;
+            }
+
+            var sTop = E.$window.scrollTop();
+
+            // 需要重新计算宽度,因为浏览器可能此时出现滚动条
+            var menuWidth = $menuContainer.width();
+
+            // 如果 menuTop === 0 说明此前编辑器一直隐藏,后来显示出来了,要重新计算相关数据
+            if (menuTop === 0) {
+                menuTop = $menuContainer.offset().top;
+                editorTop = $editorContainer.offset().top;
+                editorHeight = $editorContainer.outerHeight();
+                menuHeight = $menuContainer.outerHeight();
+            }
+
+            if (sTop >= menuTop && sTop + menuFixed + menuHeight + 30 < editorTop + editorHeight) {
+                // 吸顶
+                $menuContainer.css({
+                    position: 'fixed',
+                    top: menuFixed
+                });
+
+                // 固定宽度
+                $menuContainer.width(menuWidth);
+
+                // 增加body margin-top
+                E.$body.css({
+                    'margin-top': bodyMarginTop + menuHeight
+                });
+
+                // 记录
+                if (!editor._isMenufixed) {
+                    editor._isMenufixed = true;
+                }
+            } else {
+                // 取消吸顶
+                $menuContainer.css({
+                    position: menuCssPosition,
+                    top: menuCssTop
+                });
+
+                // 取消宽度固定
+                $menuContainer.css('width', '100%');
+
+                // 还原 body margin-top
+                E.$body.css({
+                    'margin-top': bodyMarginTop
+                });
+
+                // 撤销记录
+                if (editor._isMenufixed) {
+                    editor._isMenufixed = false;
+                }
+            }
+        });
+    });
+
+});
+// 缩进 菜单插件
+_e(function (E, $) {
+
+    // 用 createMenu 方法创建菜单
+    E.createMenu(function (check) {
+
+        // 定义菜单id,不要和其他菜单id重复。编辑器自带的所有菜单id,可通过『参数配置-自定义菜单』一节查看
+        var menuId = 'indent';
+
+        // check将检查菜单配置(『参数配置-自定义菜单』一节描述)中是否该菜单id,如果没有,则忽略下面的代码。
+        if (!check(menuId)) {
+            return;
+        }
+
+        // this 指向 editor 对象自身
+        var editor = this;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,  // 编辑器对象
+            id: menuId,  // 菜单id
+            title: '缩进', // 菜单标题
+
+            // 正常状态和选中装下的dom对象,样式需要自定义
+            $domNormal: $('<a href="#" tabindex="-1"><i class="wangeditor-menu-img-indent-left"></i></a>'),
+            $domSelected: $('<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-indent-left"></i></a>')
+        });
+
+        // 菜单正常状态下,点击将触发该事件
+        menu.clickEvent = function (e) {
+            var elem = editor.getRangeElem();
+            var p = editor.getSelfOrParentByName(elem, 'p');
+            var $p;
+
+            if (!p) {
+                // 未找到 p 元素,则忽略
+                return e.preventDefault();
+            }
+            $p = $(p);
+
+            // 使用自定义命令
+            function commandFn() {
+                $p.css('text-indent', '2em');
+            }
+            editor.customCommand(e, commandFn);
+        };
+
+        // 菜单选中状态下,点击将触发该事件
+        menu.clickEventSelected = function (e) {
+            var elem = editor.getRangeElem();
+            var p = editor.getSelfOrParentByName(elem, 'p');
+            var $p;
+
+            if (!p) {
+                // 未找到 p 元素,则忽略
+                return e.preventDefault();
+            }
+            $p = $(p);
+
+            // 使用自定义命令
+            function commandFn() {
+                $p.css('text-indent', '0');
+            }
+            editor.customCommand(e, commandFn);
+        };
+
+        // 根据当前选区,自定义更新菜单的选中状态或者正常状态
+        menu.updateSelectedEvent = function () {
+            // 获取当前选区所在的父元素
+            var elem = editor.getRangeElem();
+            var p = editor.getSelfOrParentByName(elem, 'p');
+            var $p;
+            var indent;
+
+            if (!p) {
+                // 未找到 p 元素,则标记为未处于选中状态
+                return false;
+            }
+            $p = $(p);
+            indent = $p.css('text-indent');
+
+            if (!indent || indent === '0px') {
+                // 得到的p,text-indent 属性是 0,则标记为未处于选中状态
+                return false;
+            }
+
+            // 找到 p 元素,并且 text-indent 不是 0,则标记为选中状态
+            return true;
+        };
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+});
+// 行高 菜单插件
+_e(function (E, $) {
+
+    // 用 createMenu 方法创建菜单
+    E.createMenu(function (check) {
+
+        // 定义菜单id,不要和其他菜单id重复。编辑器自带的所有菜单id,可通过『参数配置-自定义菜单』一节查看
+        var menuId = 'lineheight';
+
+        // check将检查菜单配置(『参数配置-自定义菜单』一节描述)中是否该菜单id,如果没有,则忽略下面的代码。
+        if (!check(menuId)) {
+            return;
+        }
+
+        // this 指向 editor 对象自身
+        var editor = this;
+
+        // 由于浏览器自身不支持 lineHeight 命令,因此要做一个hook
+        editor.commandHooks.lineHeight = function (value) {
+            var rangeElem = editor.getRangeElem();
+            var targetElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5,pre');
+            if (!targetElem) {
+                return;
+            }
+            $(targetElem).css('line-height', value + '');
+        };
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,  // 编辑器对象
+            id: menuId,  // 菜单id
+            title: '行高', // 菜单标题
+            commandName: 'lineHeight', // 命令名称
+
+            // 正常状态和选中装下的dom对象,样式需要自定义
+            $domNormal: $('<a href="#" tabindex="-1"><i class="wangeditor-menu-img-arrows-v"></i></a>'),
+            $domSelected: $('<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-arrows-v"></i></a>')
+        });
+
+        // 数据源
+        var data  = {
+            // 格式: 'value' : 'title'
+            '1.0': '1.0倍',
+            '1.5': '1.5倍',
+            '1.8': '1.8倍',
+            '2.0': '2.0倍',
+            '2.5': '2.5倍',
+            '3.0': '3.0倍'
+        };
+
+        // 为menu创建droplist对象
+        var tpl = '<span style="line-height:{#commandValue}">{#title}</span>';
+        menu.dropList = new E.DropList(editor, menu, {
+            data: data,  // 传入数据源
+            tpl: tpl  // 传入模板
+        });
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+
+    });
+
+});
+// 自定义上传
+_e(function (E, $) {
+
+    E.plugin(function () {
+
+        var editor = this;
+        var customUpload = editor.config.customUpload;
+        if (!customUpload) {
+            return;
+        } else if (editor.config.uploadImgUrl) {
+            alert('自定义上传无效,详看浏览器日志console.log');
+            E.error('已经配置了 uploadImgUrl ,就不能再配置 customUpload ,两者冲突。将导致自定义上传无效。');
+            return;
+        }
+
+        var $uploadContent = editor.$uploadContent;
+        if (!$uploadContent) {
+            E.error('自定义上传,无法获取 editor.$uploadContent');
+        }
+
+        // UI
+        var $uploadIcon = $('<div class="upload-icon-container"><i class="wangeditor-menu-img-upload"></i></div>');
+        $uploadContent.append($uploadIcon);
+
+        // 设置id,并暴露
+        var btnId = 'upload' + E.random();
+        var containerId = 'upload' + E.random();
+        $uploadIcon.attr('id', btnId);
+        $uploadContent.attr('id', containerId);
+
+        editor.customUploadBtnId = btnId;
+        editor.customUploadContainerId = containerId;
+    });
+
+});
+// 版权提示
+_e(function (E, $) {
+    E.info('本页面富文本编辑器由 wangEditor 提供 http://wangeditor.github.io/ ');
+});
+    
+    // 最终返回wangEditor构造函数
+    return window.wangEditor;
+});

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
static/wangEditor/js/wangEditor.min.js


+ 47 - 0
static/wangEditor/plugins/save-menu.js

@@ -0,0 +1,47 @@
+(function () {
+
+    // 获取 wangEditor 构造函数和 jquery
+    var E = window.wangEditor;
+    var $ = window.jQuery;
+
+    // 用 createMenu 方法创建菜单
+    E.createMenu(function (check) {
+
+        // 定义菜单id,不要和其他菜单id重复。编辑器自带的所有菜单id,可通过『参数配置-自定义菜单』一节查看
+        var menuId = 'save';
+
+        // check将检查菜单配置(『参数配置-自定义菜单』一节描述)中是否该菜单id,如果没有,则忽略下面的代码。
+        if (!check(menuId)) {
+            return;
+        }
+
+        // this 指向 editor 对象自身
+        var editor = this;
+
+        // 创建 menu 对象
+        var menu = new E.Menu({
+            editor: editor,  // 编辑器对象
+            id: menuId,  // 菜单id
+            title: '保存', // 菜单标题
+
+            // 正常状态和选中状态下的dom对象,样式需要自定义
+            $domNormal: $('<a href="#" tabindex="-1"><i class="fa fa-save" aria-hidden="true" name="save"></i></a>'),
+            $domSelected: $('<a href="#" tabindex="-1" class="selected"><i class="fa fa-save" aria-hidden="true" name="save"></i></a>')
+        });
+
+        // 菜单正常状态下,点击将触发该事件
+        menu.clickEvent = function (e) {
+            window.saveDocument();
+        };
+
+        // 菜单选中状态下,点击将触发该事件
+        menu.clickEventSelected = function (e) {
+
+        };
+
+
+        // 增加到editor对象中
+        editor.menus[menuId] = menu;
+    });
+
+})();

+ 0 - 339
vendor/github.com/adamzy/cedar-go/LICENSE.md

@@ -1,339 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-                            NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    {description}
-    Copyright (C) {year}  {fullname}
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License along
-    with this program; if not, write to the Free Software Foundation, Inc.,
-    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
-    Gnomovision version 69, Copyright (C) year name of author
-    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
-  `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
-  {signature of Ty Coon}, 1 April 1989
-  Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs.  If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.

+ 0 - 83
vendor/github.com/adamzy/cedar-go/README.md

@@ -1,83 +0,0 @@
-# cedar-go [![GoDoc](https://godoc.org/github.com/adamzy/cedar-go?status.svg)](https://godoc.org/github.com/adamzy/cedar-go)
-
-Package `cedar-go` implementes double-array trie.
-
-It is a [Golang](https://golang.org/) port of [cedar](http://www.tkl.iis.u-tokyo.ac.jp/~ynaga/cedar) which is written in C++ by Naoki Yoshinaga. `cedar-go` currently implements the `reduced` verion of cedar. 
-This package is not thread safe if there is one goroutine doing insertions or deletions. 
-
-## Install
-```
-go get github.com/adamzy/cedar-go
-```
-
-## Usage
-```go
-package main
-
-import (
-	"fmt"
-
-	"github.com/adamzy/cedar-go"
-)
-
-func main() {
-	// create a new cedar trie.
-	trie := cedar.New()
-
-	// a helper function to print the id-key-value triple given trie node id
-	printIdKeyValue := func(id int) {
-		// the key of node `id`.
-		key, _ := trie.Key(id)
-		// the value of node `id`.
-		value, _ := trie.Value(id)
-		fmt.Printf("%d\t%s:%v\n", id, key, value)
-	}
-
-	// Insert key-value pairs.
-    // The order of insertion is not important.
-	trie.Insert([]byte("How many"), 0)
-	trie.Insert([]byte("How many loved"), 1)
-	trie.Insert([]byte("How many loved your moments"), 2)
-	trie.Insert([]byte("How many loved your moments of glad grace"), 3)
-	trie.Insert([]byte("姑苏"), 4)
-	trie.Insert([]byte("姑苏城外"), 5)
-	trie.Insert([]byte("姑苏城外寒山寺"), 6)
-
-	// Get the associated value of a key directly.
-	value, _ := trie.Get([]byte("How many loved your moments of glad grace"))
-	fmt.Println(value)
-
-	// Or, jump to the node first,
-	id, _ := trie.Jump([]byte("How many loved your moments"), 0)
-	// then get the key and the value
-	printIdKeyValue(id)
-
-	fmt.Println("\nPrefixMatch\nid\tkey:value")
-	for _, id := range trie.PrefixMatch([]byte("How many loved your moments of glad grace"), 0) {
-		printIdKeyValue(id)
-	}
-
-	fmt.Println("\nPrefixPredict\nid\tkey:value")
-	for _, id := range trie.PrefixPredict([]byte("姑苏"), 0) {
-		printIdKeyValue(id)
-	}
-}
-```
-will produce
-```
-3
-281	How many loved your moments:2
-
-PrefixMatch
-id	key:value
-262	How many:0
-268	How many loved:1
-281	How many loved your moments:2
-296	How many loved your moments of glad grace:3
-
-PrefixPredict
-id	key:value
-303	姑苏:4
-309	姑苏城外:5
-318	姑苏城外寒山寺:6
-```

+ 0 - 231
vendor/github.com/adamzy/cedar-go/api.go

@@ -1,231 +0,0 @@
-package cedar
-
-// Status reports the following statistics of the cedar:
-//	keys:		number of keys that are in the cedar,
-//	nodes:		number of trie nodes (slots in the base array) has been taken,
-//	size:			the size of the base array used by the cedar,
-//	capacity:		the capicity of the base array used by the cedar.
-func (da *Cedar) Status() (keys, nodes, size, capacity int) {
-	for i := 0; i < da.Size; i++ {
-		n := da.Array[i]
-		if n.Check >= 0 {
-			nodes++
-			if n.Value >= 0 {
-				keys++
-			}
-		}
-	}
-	return keys, nodes, da.Size, da.Capacity
-}
-
-// Jump travels from a node `from` to another node `to` by following the path `path`.
-// For example, if the following keys were inserted:
-//	id	key
-//	19	abc
-//	23	ab
-//	37	abcd
-// then
-//	Jump([]byte("ab"), 0) = 23, nil		// reach "ab" from root
-//	Jump([]byte("c"), 23) = 19, nil			// reach "abc" from "ab"
-//	Jump([]byte("cd"), 23) = 37, nil		// reach "abcd" from "ab"
-func (da *Cedar) Jump(path []byte, from int) (to int, err error) {
-	for _, b := range path {
-		if da.Array[from].Value >= 0 {
-			return from, ErrNoPath
-		}
-		to = da.Array[from].base() ^ int(b)
-		if da.Array[to].Check != from {
-			return from, ErrNoPath
-		}
-		from = to
-	}
-	return to, nil
-}
-
-// Key returns the key of the node with the given `id`.
-// It will return ErrNoPath, if the node does not exist.
-func (da *Cedar) Key(id int) (key []byte, err error) {
-	for id > 0 {
-		from := da.Array[id].Check
-		if from < 0 {
-			return nil, ErrNoPath
-		}
-		if char := byte(da.Array[from].base() ^ id); char != 0 {
-			key = append(key, char)
-		}
-		id = from
-	}
-	if id != 0 || len(key) == 0 {
-		return nil, ErrInvalidKey
-	}
-	for i := 0; i < len(key)/2; i++ {
-		key[i], key[len(key)-i-1] = key[len(key)-i-1], key[i]
-	}
-	return key, nil
-}
-
-// Value returns the value of the node with the given `id`.
-// It will return ErrNoValue, if the node does not have a value.
-func (da *Cedar) Value(id int) (value int, err error) {
-	value = da.Array[id].Value
-	if value >= 0 {
-		return value, nil
-	}
-	to := da.Array[id].base()
-	if da.Array[to].Check == id && da.Array[to].Value >= 0 {
-		return da.Array[to].Value, nil
-	}
-	return 0, ErrNoValue
-}
-
-// Insert adds a key-value pair into the cedar.
-// It will return ErrInvalidValue, if value < 0 or >= ValueLimit.
-func (da *Cedar) Insert(key []byte, value int) error {
-	if value < 0 || value >= ValueLimit {
-		return ErrInvalidValue
-	}
-	p := da.get(key, 0, 0)
-	*p = value
-	return nil
-}
-
-// Update increases the value associated with the `key`.
-// The `key` will be inserted if it is not in the cedar.
-// It will return ErrInvalidValue, if the updated value < 0 or >= ValueLimit.
-func (da *Cedar) Update(key []byte, value int) error {
-	p := da.get(key, 0, 0)
-	if *p+value < 0 || *p+value >= ValueLimit {
-		return ErrInvalidValue
-	}
-	*p += value
-	return nil
-}
-
-// Delete removes a key-value pair from the cedar.
-// It will return ErrNoPath, if the key has not been added.
-func (da *Cedar) Delete(key []byte) error {
-	// if the path does not exist, or the end is not a leaf, nothing to delete
-	to, err := da.Jump(key, 0)
-	if err != nil {
-		return ErrNoPath
-	}
-
-	if da.Array[to].Value < 0 {
-		base := da.Array[to].base()
-		if da.Array[base].Check == to {
-			to = base
-		}
-	}
-
-	for {
-		from := da.Array[to].Check
-		base := da.Array[from].base()
-		label := byte(to ^ base)
-
-		// if `to` has sibling, remove `to` from the sibling list, then stop
-		if da.Ninfos[to].Sibling != 0 || da.Ninfos[from].Child != label {
-			// delete the label from the child ring first
-			da.popSibling(from, base, label)
-			// then release the current node `to` to the empty node ring
-			da.pushEnode(to)
-			break
-		}
-		// otherwise, just release the current node `to` to the empty node ring
-		da.pushEnode(to)
-		// then check its parent node
-		to = from
-	}
-	return nil
-}
-
-// Get returns the value associated with the given `key`.
-// It is equivalent to
-//		id, err1 = Jump(key)
-//		value, err2 = Value(id)
-// Thus, it may return ErrNoPath or ErrNoValue,
-func (da *Cedar) Get(key []byte) (value int, err error) {
-	to, err := da.Jump(key, 0)
-	if err != nil {
-		return 0, err
-	}
-	return da.Value(to)
-}
-
-// PrefixMatch returns a list of at most `num` nodes which match the prefix of the key.
-// If `num` is 0, it returns all matches.
-// For example, if the following keys were inserted:
-//	id	key
-//	19	abc
-//	23	ab
-//	37	abcd
-// then
-//	PrefixMatch([]byte("abc"), 1) = [ 23 ]				// match ["ab"]
-//	PrefixMatch([]byte("abcd"), 0) = [ 23, 19, 37]		// match ["ab", "abc", "abcd"]
-func (da *Cedar) PrefixMatch(key []byte, num int) (ids []int) {
-	for from, i := 0, 0; i < len(key); i++ {
-		to, err := da.Jump(key[i:i+1], from)
-		if err != nil {
-			break
-		}
-		if _, err := da.Value(to); err == nil {
-			ids = append(ids, to)
-			num--
-			if num == 0 {
-				return
-			}
-		}
-		from = to
-	}
-	return
-}
-
-// PrefixPredict returns a list of at most `num` nodes which has the key as their prefix.
-// These nodes are ordered by their keys.
-// If `num` is 0, it returns all matches.
-// For example, if the following keys were inserted:
-//	id	key
-//	19	abc
-//	23	ab
-//	37	abcd
-// then
-//	PrefixPredict([]byte("ab"), 2) = [ 23, 19 ]			// predict ["ab", "abc"]
-//	PrefixPredict([]byte("ab"), 0) = [ 23, 19, 37 ]		// predict ["ab", "abc", "abcd"]
-func (da *Cedar) PrefixPredict(key []byte, num int) (ids []int) {
-	root, err := da.Jump(key, 0)
-	if err != nil {
-		return
-	}
-	for from, err := da.begin(root); err == nil; from, err = da.next(from, root) {
-		ids = append(ids, from)
-		num--
-		if num == 0 {
-			return
-		}
-	}
-	return
-}
-
-func (da *Cedar) begin(from int) (to int, err error) {
-	for c := da.Ninfos[from].Child; c != 0; {
-		to = da.Array[from].base() ^ int(c)
-		c = da.Ninfos[to].Child
-		from = to
-	}
-	if da.Array[from].base() > 0 {
-		return da.Array[from].base(), nil
-	}
-	return from, nil
-}
-
-func (da *Cedar) next(from int, root int) (to int, err error) {
-	c := da.Ninfos[from].Sibling
-	for c == 0 && from != root && da.Array[from].Check >= 0 {
-		from = da.Array[from].Check
-		c = da.Ninfos[from].Sibling
-	}
-	if from == root {
-		return 0, ErrNoPath
-	}
-	from = da.Array[da.Array[from].Check].base() ^ int(c)
-	return da.begin(from)
-}

+ 0 - 407
vendor/github.com/adamzy/cedar-go/cedar.go

@@ -1,407 +0,0 @@
-package cedar
-
-const ValueLimit = int(^uint(0) >> 1)
-
-type node struct {
-	Value int
-	Check int
-}
-
-func (n *node) base() int { return -(n.Value + 1) }
-
-type ninfo struct {
-	Sibling, Child byte
-}
-
-type block struct {
-	Prev, Next, Num, Reject, Trial, Ehead int
-}
-
-func (b *block) init() {
-	b.Num = 256
-	b.Reject = 257
-}
-
-type Cedar struct {
-	*cedar
-}
-
-type cedar struct {
-	Array    []node
-	Ninfos   []ninfo
-	Blocks   []block
-	Reject   [257]int
-	BheadF   int
-	BheadC   int
-	BheadO   int
-	Capacity int
-	Size     int
-	Ordered  bool
-	MaxTrial int
-}
-
-func New() *Cedar {
-	da := cedar{
-		Array:    make([]node, 256),
-		Ninfos:   make([]ninfo, 256),
-		Blocks:   make([]block, 1),
-		Capacity: 256,
-		Size:     256,
-		Ordered:  true,
-		MaxTrial: 1,
-	}
-
-	da.Array[0] = node{-2, 0}
-	for i := 1; i < 256; i++ {
-		da.Array[i] = node{-(i - 1), -(i + 1)}
-	}
-	da.Array[1].Value = -255
-	da.Array[255].Check = -1
-
-	da.Blocks[0].Ehead = 1
-	da.Blocks[0].init()
-
-	for i := 0; i <= 256; i++ {
-		da.Reject[i] = i + 1
-	}
-
-	return &Cedar{&da}
-}
-
-// Get value by key, insert the key if not exist
-func (da *cedar) get(key []byte, from, pos int) *int {
-	for ; pos < len(key); pos++ {
-		if value := da.Array[from].Value; value >= 0 && value != ValueLimit {
-			to := da.follow(from, 0)
-			da.Array[to].Value = value
-		}
-		from = da.follow(from, key[pos])
-	}
-	to := from
-	if da.Array[from].Value < 0 {
-		to = da.follow(from, 0)
-	}
-	return &da.Array[to].Value
-}
-
-func (da *cedar) follow(from int, label byte) int {
-	base := da.Array[from].base()
-	to := base ^ int(label)
-	if base < 0 || da.Array[to].Check < 0 {
-		hasChild := false
-		if base >= 0 {
-			hasChild = (da.Array[base^int(da.Ninfos[from].Child)].Check == from)
-		}
-		to = da.popEnode(base, label, from)
-		da.pushSibling(from, to^int(label), label, hasChild)
-	} else if da.Array[to].Check != from {
-		to = da.resolve(from, base, label)
-	} else if da.Array[to].Check == from {
-	} else {
-		panic("cedar: internal error, should not be here")
-	}
-	return to
-}
-
-func (da *cedar) popBlock(bi int, head_in *int, last bool) {
-	if last {
-		*head_in = 0
-	} else {
-		b := &da.Blocks[bi]
-		da.Blocks[b.Prev].Next = b.Next
-		da.Blocks[b.Next].Prev = b.Prev
-		if bi == *head_in {
-			*head_in = b.Next
-		}
-	}
-}
-
-func (da *cedar) pushBlock(bi int, head_out *int, empty bool) {
-	b := &da.Blocks[bi]
-	if empty {
-		*head_out, b.Prev, b.Next = bi, bi, bi
-	} else {
-		tail_out := &da.Blocks[*head_out].Prev
-		b.Prev = *tail_out
-		b.Next = *head_out
-		*head_out, *tail_out, da.Blocks[*tail_out].Next = bi, bi, bi
-	}
-}
-
-func (da *cedar) addBlock() int {
-	if da.Size == da.Capacity {
-		da.Capacity *= 2
-
-		oldArray := da.Array
-		da.Array = make([]node, da.Capacity)
-		copy(da.Array, oldArray)
-
-		oldNinfo := da.Ninfos
-		da.Ninfos = make([]ninfo, da.Capacity)
-		copy(da.Ninfos, oldNinfo)
-
-		oldBlock := da.Blocks
-		da.Blocks = make([]block, da.Capacity>>8)
-		copy(da.Blocks, oldBlock)
-	}
-
-	da.Blocks[da.Size>>8].init()
-	da.Blocks[da.Size>>8].Ehead = da.Size
-
-	da.Array[da.Size] = node{-(da.Size + 255), -(da.Size + 1)}
-	for i := da.Size + 1; i < da.Size+255; i++ {
-		da.Array[i] = node{-(i - 1), -(i + 1)}
-	}
-	da.Array[da.Size+255] = node{-(da.Size + 254), -da.Size}
-
-	da.pushBlock(da.Size>>8, &da.BheadO, da.BheadO == 0)
-	da.Size += 256
-	return da.Size>>8 - 1
-}
-
-func (da *cedar) transferBlock(bi int, head_in, head_out *int) {
-	da.popBlock(bi, head_in, bi == da.Blocks[bi].Next)
-	da.pushBlock(bi, head_out, *head_out == 0 && da.Blocks[bi].Num != 0)
-}
-
-func (da *cedar) popEnode(base int, label byte, from int) int {
-	e := base ^ int(label)
-	if base < 0 {
-		e = da.findPlace()
-	}
-	bi := e >> 8
-	n := &da.Array[e]
-	b := &da.Blocks[bi]
-	b.Num--
-	if b.Num == 0 {
-		if bi != 0 {
-			da.transferBlock(bi, &da.BheadC, &da.BheadF)
-		}
-	} else {
-		da.Array[-n.Value].Check = n.Check
-		da.Array[-n.Check].Value = n.Value
-		if e == b.Ehead {
-			b.Ehead = -n.Check
-		}
-		if bi != 0 && b.Num == 1 && b.Trial != da.MaxTrial {
-			da.transferBlock(bi, &da.BheadO, &da.BheadC)
-		}
-	}
-	n.Value = ValueLimit
-	n.Check = from
-	if base < 0 {
-		da.Array[from].Value = -(e ^ int(label)) - 1
-	}
-	return e
-}
-
-func (da *cedar) pushEnode(e int) {
-	bi := e >> 8
-	b := &da.Blocks[bi]
-	b.Num++
-	if b.Num == 1 {
-		b.Ehead = e
-		da.Array[e] = node{-e, -e}
-		if bi != 0 {
-			da.transferBlock(bi, &da.BheadF, &da.BheadC)
-		}
-	} else {
-		prev := b.Ehead
-		next := -da.Array[prev].Check
-		da.Array[e] = node{-prev, -next}
-		da.Array[prev].Check = -e
-		da.Array[next].Value = -e
-		if b.Num == 2 || b.Trial == da.MaxTrial {
-			if bi != 0 {
-				da.transferBlock(bi, &da.BheadC, &da.BheadO)
-			}
-		}
-		b.Trial = 0
-	}
-	if b.Reject < da.Reject[b.Num] {
-		b.Reject = da.Reject[b.Num]
-	}
-	da.Ninfos[e] = ninfo{}
-}
-
-// hasChild: wherether the `from` node has children
-func (da *cedar) pushSibling(from, base int, label byte, hasChild bool) {
-	c := &da.Ninfos[from].Child
-	keepOrder := *c == 0
-	if da.Ordered {
-		keepOrder = label > *c
-	}
-	if hasChild && keepOrder {
-		c = &da.Ninfos[base^int(*c)].Sibling
-		for da.Ordered && *c != 0 && *c < label {
-			c = &da.Ninfos[base^int(*c)].Sibling
-		}
-	}
-	da.Ninfos[base^int(label)].Sibling = *c
-	*c = label
-}
-
-func (da *cedar) popSibling(from, base int, label byte) {
-	c := &da.Ninfos[from].Child
-	for *c != label {
-		c = &da.Ninfos[base^int(*c)].Sibling
-	}
-	*c = da.Ninfos[base^int(*c)].Sibling
-}
-
-func (da *cedar) consult(base_n, base_p int, c_n, c_p byte) bool {
-	c_n = da.Ninfos[base_n^int(c_n)].Sibling
-	c_p = da.Ninfos[base_p^int(c_p)].Sibling
-	for c_n != 0 && c_p != 0 {
-		c_n = da.Ninfos[base_n^int(c_n)].Sibling
-		c_p = da.Ninfos[base_p^int(c_p)].Sibling
-	}
-	return c_p != 0
-}
-
-func (da *cedar) setChild(base int, c byte, label byte, flag bool) []byte {
-	child := make([]byte, 0, 257)
-	if c == 0 {
-		child = append(child, c)
-		c = da.Ninfos[base^int(c)].Sibling
-	}
-	if da.Ordered {
-		for c != 0 && c <= label {
-			child = append(child, c)
-			c = da.Ninfos[base^int(c)].Sibling
-		}
-	}
-	if flag {
-		child = append(child, label)
-	}
-	for c != 0 {
-		child = append(child, c)
-		c = da.Ninfos[base^int(c)].Sibling
-	}
-	return child
-}
-
-func (da *cedar) findPlace() int {
-	if da.BheadC != 0 {
-		return da.Blocks[da.BheadC].Ehead
-	}
-	if da.BheadO != 0 {
-		return da.Blocks[da.BheadO].Ehead
-	}
-	return da.addBlock() << 8
-}
-
-func (da *cedar) findPlaces(child []byte) int {
-	bi := da.BheadO
-	if bi != 0 {
-		bz := da.Blocks[da.BheadO].Prev
-		nc := len(child)
-		for {
-			b := &da.Blocks[bi]
-			if b.Num >= nc && nc < b.Reject {
-				for e := b.Ehead; ; {
-					base := e ^ int(child[0])
-					for i := 0; da.Array[base^int(child[i])].Check < 0; i++ {
-						if i == len(child)-1 {
-							b.Ehead = e
-							return e
-						}
-					}
-					e = -da.Array[e].Check
-					if e == b.Ehead {
-						break
-					}
-				}
-			}
-			b.Reject = nc
-			if b.Reject < da.Reject[b.Num] {
-				da.Reject[b.Num] = b.Reject
-			}
-			bi_ := b.Next
-			b.Trial++
-			if b.Trial == da.MaxTrial {
-				da.transferBlock(bi, &da.BheadO, &da.BheadC)
-			}
-			if bi == bz {
-				break
-			}
-			bi = bi_
-		}
-	}
-	return da.addBlock() << 8
-}
-
-func (da *cedar) resolve(from_n, base_n int, label_n byte) int {
-	to_pn := base_n ^ int(label_n)
-	from_p := da.Array[to_pn].Check
-	base_p := da.Array[from_p].base()
-
-	flag := da.consult(base_n, base_p, da.Ninfos[from_n].Child, da.Ninfos[from_p].Child)
-	var children []byte
-	if flag {
-		children = da.setChild(base_n, da.Ninfos[from_n].Child, label_n, true)
-	} else {
-		children = da.setChild(base_p, da.Ninfos[from_p].Child, 255, false)
-	}
-	var base int
-	if len(children) == 1 {
-		base = da.findPlace()
-	} else {
-		base = da.findPlaces(children)
-	}
-	base ^= int(children[0])
-	var from int
-	var base_ int
-	if flag {
-		from = from_n
-		base_ = base_n
-	} else {
-		from = from_p
-		base_ = base_p
-	}
-	if flag && children[0] == label_n {
-		da.Ninfos[from].Child = label_n
-	}
-	da.Array[from].Value = -base - 1
-	for i := 0; i < len(children); i++ {
-		to := da.popEnode(base, children[i], from)
-		to_ := base_ ^ int(children[i])
-		if i == len(children)-1 {
-			da.Ninfos[to].Sibling = 0
-		} else {
-			da.Ninfos[to].Sibling = children[i+1]
-		}
-		if flag && to_ == to_pn { // new node has no child
-			continue
-		}
-		n := &da.Array[to]
-		n_ := &da.Array[to_]
-		n.Value = n_.Value
-		if n.Value < 0 && children[i] != 0 {
-			// this node has children, fix their check
-			c := da.Ninfos[to_].Child
-			da.Ninfos[to].Child = c
-			da.Array[n.base()^int(c)].Check = to
-			c = da.Ninfos[n.base()^int(c)].Sibling
-			for c != 0 {
-				da.Array[n.base()^int(c)].Check = to
-				c = da.Ninfos[n.base()^int(c)].Sibling
-			}
-		}
-		if !flag && to_ == from_n { // parent node moved
-			from_n = to
-		}
-		if !flag && to_ == to_pn {
-			da.pushSibling(from_n, to_pn^int(label_n), label_n, true)
-			da.Ninfos[to_].Child = 0
-			n_.Value = ValueLimit
-			n_.Check = from_n
-		} else {
-			da.pushEnode(to_)
-		}
-	}
-	if flag {
-		return base ^ int(label_n)
-	}
-	return to_pn
-}

+ 0 - 12
vendor/github.com/adamzy/cedar-go/doc.go

@@ -1,12 +0,0 @@
-// Package cedar-go implements double-array trie.
-//
-// It is a golang port of cedar (http://www.tkl.iis.u-tokyo.ac.jp/~ynaga/cedar) which is written in C++ by Naoki Yoshinaga.
-// Currently cedar-go implements the `reduced` verion of cedar.
-// This package is not thread safe if there is one goroutine doing
-// insertions or deletions.
-//
-// Note
-//
-// key must be `[]byte` without zero items,
-// while value must be integer in the range [0, 2<<63-2] or [0, 2<<31-2] depends on the platform.
-package cedar

+ 0 - 11
vendor/github.com/adamzy/cedar-go/errors.go

@@ -1,11 +0,0 @@
-package cedar
-
-import "errors"
-
-var (
-	ErrInvalidDataType = errors.New("cedar: invalid datatype")
-	ErrInvalidValue    = errors.New("cedar: invalid value")
-	ErrInvalidKey      = errors.New("cedar: invalid key")
-	ErrNoPath          = errors.New("cedar: no path")
-	ErrNoValue         = errors.New("cedar: no value")
-)

+ 0 - 63
vendor/github.com/adamzy/cedar-go/io.go

@@ -1,63 +0,0 @@
-package cedar
-
-import (
-	"bufio"
-	"encoding/gob"
-	"encoding/json"
-	"io"
-	"os"
-)
-
-// Save saves the cedar to an io.Writer,
-// where dataType is either "json" or "gob".
-func (da *Cedar) Save(out io.Writer, dataType string) error {
-	switch dataType {
-	case "gob", "GOB":
-		dataEecoder := gob.NewEncoder(out)
-		return dataEecoder.Encode(da.cedar)
-	case "json", "JSON":
-		dataEecoder := json.NewEncoder(out)
-		return dataEecoder.Encode(da.cedar)
-	}
-	return ErrInvalidDataType
-}
-
-// SaveToFile saves the cedar to a file,
-// where dataType is either "json" or "gob".
-func (da *Cedar) SaveToFile(fileName string, dataType string) error {
-	file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, 0666)
-	if err != nil {
-		return err
-	}
-	defer file.Close()
-	out := bufio.NewWriter(file)
-	defer out.Flush()
-	da.Save(out, dataType)
-	return nil
-}
-
-// Load loads the cedar from an io.Writer,
-// where dataType is either "json" or "gob".
-func (da *Cedar) Load(in io.Reader, dataType string) error {
-	switch dataType {
-	case "gob", "GOB":
-		dataDecoder := gob.NewDecoder(in)
-		return dataDecoder.Decode(da.cedar)
-	case "json", "JSON":
-		dataDecoder := json.NewDecoder(in)
-		return dataDecoder.Decode(da.cedar)
-	}
-	return ErrInvalidDataType
-}
-
-// LoadFromFile loads the cedar from a file,
-// where dataType is either "json" or "gob".
-func (da *Cedar) LoadFromFile(fileName string, dataType string) error {
-	file, err := os.OpenFile(fileName, os.O_RDONLY, 0600)
-	defer file.Close()
-	if err != nil {
-		return err
-	}
-	in := bufio.NewReader(file)
-	return da.Load(in, dataType)
-}

+ 0 - 4
vendor/github.com/boltdb/bolt/.gitignore

@@ -1,4 +0,0 @@
-*.prof
-*.test
-*.swp
-/bin/

+ 0 - 20
vendor/github.com/boltdb/bolt/LICENSE

@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2013 Ben Johnson
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 0 - 18
vendor/github.com/boltdb/bolt/Makefile

@@ -1,18 +0,0 @@
-BRANCH=`git rev-parse --abbrev-ref HEAD`
-COMMIT=`git rev-parse --short HEAD`
-GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
-
-default: build
-
-race:
-	@go test -v -race -test.run="TestSimulate_(100op|1000op)"
-
-# go get github.com/kisielk/errcheck
-errcheck:
-	@errcheck -ignorepkg=bytes -ignore=os:Remove github.com/boltdb/bolt
-
-test: 
-	@go test -v -cover .
-	@go test -v ./cmd/bolt
-
-.PHONY: fmt test

+ 0 - 915
vendor/github.com/boltdb/bolt/README.md

@@ -1,915 +0,0 @@
-Bolt [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.2.1-green.svg)
-====
-
-Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas]
-[LMDB project][lmdb]. The goal of the project is to provide a simple,
-fast, and reliable database for projects that don't require a full database
-server such as Postgres or MySQL.
-
-Since Bolt is meant to be used as such a low-level piece of functionality,
-simplicity is key. The API will be small and only focus on getting values
-and setting values. That's it.
-
-[hyc_symas]: https://twitter.com/hyc_symas
-[lmdb]: http://symas.com/mdb/
-
-## Project Status
-
-Bolt is stable, the API is fixed, and the file format is fixed. Full unit
-test coverage and randomized black box testing are used to ensure database
-consistency and thread safety. Bolt is currently used in high-load production
-environments serving databases as large as 1TB. Many companies such as
-Shopify and Heroku use Bolt-backed services every day.
-
-## Table of Contents
-
-- [Getting Started](#getting-started)
-  - [Installing](#installing)
-  - [Opening a database](#opening-a-database)
-  - [Transactions](#transactions)
-    - [Read-write transactions](#read-write-transactions)
-    - [Read-only transactions](#read-only-transactions)
-    - [Batch read-write transactions](#batch-read-write-transactions)
-    - [Managing transactions manually](#managing-transactions-manually)
-  - [Using buckets](#using-buckets)
-  - [Using key/value pairs](#using-keyvalue-pairs)
-  - [Autoincrementing integer for the bucket](#autoincrementing-integer-for-the-bucket)
-  - [Iterating over keys](#iterating-over-keys)
-    - [Prefix scans](#prefix-scans)
-    - [Range scans](#range-scans)
-    - [ForEach()](#foreach)
-  - [Nested buckets](#nested-buckets)
-  - [Database backups](#database-backups)
-  - [Statistics](#statistics)
-  - [Read-Only Mode](#read-only-mode)
-  - [Mobile Use (iOS/Android)](#mobile-use-iosandroid)
-- [Resources](#resources)
-- [Comparison with other databases](#comparison-with-other-databases)
-  - [Postgres, MySQL, & other relational databases](#postgres-mysql--other-relational-databases)
-  - [LevelDB, RocksDB](#leveldb-rocksdb)
-  - [LMDB](#lmdb)
-- [Caveats & Limitations](#caveats--limitations)
-- [Reading the Source](#reading-the-source)
-- [Other Projects Using Bolt](#other-projects-using-bolt)
-
-## Getting Started
-
-### Installing
-
-To start using Bolt, install Go and run `go get`:
-
-```sh
-$ go get github.com/boltdb/bolt/...
-```
-
-This will retrieve the library and install the `bolt` command line utility into
-your `$GOBIN` path.
-
-
-### Opening a database
-
-The top-level object in Bolt is a `DB`. It is represented as a single file on
-your disk and represents a consistent snapshot of your data.
-
-To open your database, simply use the `bolt.Open()` function:
-
-```go
-package main
-
-import (
-	"log"
-
-	"github.com/boltdb/bolt"
-)
-
-func main() {
-	// Open the my.db data file in your current directory.
-	// It will be created if it doesn't exist.
-	db, err := bolt.Open("my.db", 0600, nil)
-	if err != nil {
-		log.Fatal(err)
-	}
-	defer db.Close()
-
-	...
-}
-```
-
-Please note that Bolt obtains a file lock on the data file so multiple processes
-cannot open the same database at the same time. Opening an already open Bolt
-database will cause it to hang until the other process closes it. To prevent
-an indefinite wait you can pass a timeout option to the `Open()` function:
-
-```go
-db, err := bolt.Open("my.db", 0600, &bolt.Options{Timeout: 1 * time.Second})
-```
-
-
-### Transactions
-
-Bolt allows only one read-write transaction at a time but allows as many
-read-only transactions as you want at a time. Each transaction has a consistent
-view of the data as it existed when the transaction started.
-
-Individual transactions and all objects created from them (e.g. buckets, keys)
-are not thread safe. To work with data in multiple goroutines you must start
-a transaction for each one or use locking to ensure only one goroutine accesses
-a transaction at a time. Creating transaction from the `DB` is thread safe.
-
-Read-only transactions and read-write transactions should not depend on one
-another and generally shouldn't be opened simultaneously in the same goroutine.
-This can cause a deadlock as the read-write transaction needs to periodically
-re-map the data file but it cannot do so while a read-only transaction is open.
-
-
-#### Read-write transactions
-
-To start a read-write transaction, you can use the `DB.Update()` function:
-
-```go
-err := db.Update(func(tx *bolt.Tx) error {
-	...
-	return nil
-})
-```
-
-Inside the closure, you have a consistent view of the database. You commit the
-transaction by returning `nil` at the end. You can also rollback the transaction
-at any point by returning an error. All database operations are allowed inside
-a read-write transaction.
-
-Always check the return error as it will report any disk failures that can cause
-your transaction to not complete. If you return an error within your closure
-it will be passed through.
-
-
-#### Read-only transactions
-
-To start a read-only transaction, you can use the `DB.View()` function:
-
-```go
-err := db.View(func(tx *bolt.Tx) error {
-	...
-	return nil
-})
-```
-
-You also get a consistent view of the database within this closure, however,
-no mutating operations are allowed within a read-only transaction. You can only
-retrieve buckets, retrieve values, and copy the database within a read-only
-transaction.
-
-
-#### Batch read-write transactions
-
-Each `DB.Update()` waits for disk to commit the writes. This overhead
-can be minimized by combining multiple updates with the `DB.Batch()`
-function:
-
-```go
-err := db.Batch(func(tx *bolt.Tx) error {
-	...
-	return nil
-})
-```
-
-Concurrent Batch calls are opportunistically combined into larger
-transactions. Batch is only useful when there are multiple goroutines
-calling it.
-
-The trade-off is that `Batch` can call the given
-function multiple times, if parts of the transaction fail. The
-function must be idempotent and side effects must take effect only
-after a successful return from `DB.Batch()`.
-
-For example: don't display messages from inside the function, instead
-set variables in the enclosing scope:
-
-```go
-var id uint64
-err := db.Batch(func(tx *bolt.Tx) error {
-	// Find last key in bucket, decode as bigendian uint64, increment
-	// by one, encode back to []byte, and add new key.
-	...
-	id = newValue
-	return nil
-})
-if err != nil {
-	return ...
-}
-fmt.Println("Allocated ID %d", id)
-```
-
-
-#### Managing transactions manually
-
-The `DB.View()` and `DB.Update()` functions are wrappers around the `DB.Begin()`
-function. These helper functions will start the transaction, execute a function,
-and then safely close your transaction if an error is returned. This is the
-recommended way to use Bolt transactions.
-
-However, sometimes you may want to manually start and end your transactions.
-You can use the `DB.Begin()` function directly but **please** be sure to close
-the transaction.
-
-```go
-// Start a writable transaction.
-tx, err := db.Begin(true)
-if err != nil {
-    return err
-}
-defer tx.Rollback()
-
-// Use the transaction...
-_, err := tx.CreateBucket([]byte("MyBucket"))
-if err != nil {
-    return err
-}
-
-// Commit the transaction and check for error.
-if err := tx.Commit(); err != nil {
-    return err
-}
-```
-
-The first argument to `DB.Begin()` is a boolean stating if the transaction
-should be writable.
-
-
-### Using buckets
-
-Buckets are collections of key/value pairs within the database. All keys in a
-bucket must be unique. You can create a bucket using the `DB.CreateBucket()`
-function:
-
-```go
-db.Update(func(tx *bolt.Tx) error {
-	b, err := tx.CreateBucket([]byte("MyBucket"))
-	if err != nil {
-		return fmt.Errorf("create bucket: %s", err)
-	}
-	return nil
-})
-```
-
-You can also create a bucket only if it doesn't exist by using the
-`Tx.CreateBucketIfNotExists()` function. It's a common pattern to call this
-function for all your top-level buckets after you open your database so you can
-guarantee that they exist for future transactions.
-
-To delete a bucket, simply call the `Tx.DeleteBucket()` function.
-
-
-### Using key/value pairs
-
-To save a key/value pair to a bucket, use the `Bucket.Put()` function:
-
-```go
-db.Update(func(tx *bolt.Tx) error {
-	b := tx.Bucket([]byte("MyBucket"))
-	err := b.Put([]byte("answer"), []byte("42"))
-	return err
-})
-```
-
-This will set the value of the `"answer"` key to `"42"` in the `MyBucket`
-bucket. To retrieve this value, we can use the `Bucket.Get()` function:
-
-```go
-db.View(func(tx *bolt.Tx) error {
-	b := tx.Bucket([]byte("MyBucket"))
-	v := b.Get([]byte("answer"))
-	fmt.Printf("The answer is: %s\n", v)
-	return nil
-})
-```
-
-The `Get()` function does not return an error because its operation is
-guaranteed to work (unless there is some kind of system failure). If the key
-exists then it will return its byte slice value. If it doesn't exist then it
-will return `nil`. It's important to note that you can have a zero-length value
-set to a key which is different than the key not existing.
-
-Use the `Bucket.Delete()` function to delete a key from the bucket.
-
-Please note that values returned from `Get()` are only valid while the
-transaction is open. If you need to use a value outside of the transaction
-then you must use `copy()` to copy it to another byte slice.
-
-
-### Autoincrementing integer for the bucket
-By using the `NextSequence()` function, you can let Bolt determine a sequence
-which can be used as the unique identifier for your key/value pairs. See the
-example below.
-
-```go
-// CreateUser saves u to the store. The new user ID is set on u once the data is persisted.
-func (s *Store) CreateUser(u *User) error {
-    return s.db.Update(func(tx *bolt.Tx) error {
-        // Retrieve the users bucket.
-        // This should be created when the DB is first opened.
-        b := tx.Bucket([]byte("users"))
-
-        // Generate ID for the user.
-        // This returns an error only if the Tx is closed or not writeable.
-        // That can't happen in an Update() call so I ignore the error check.
-        id, _ := b.NextSequence()
-        u.ID = int(id)
-
-        // Marshal user data into bytes.
-        buf, err := json.Marshal(u)
-        if err != nil {
-            return err
-        }
-
-        // Persist bytes to users bucket.
-        return b.Put(itob(u.ID), buf)
-    })
-}
-
-// itob returns an 8-byte big endian representation of v.
-func itob(v int) []byte {
-    b := make([]byte, 8)
-    binary.BigEndian.PutUint64(b, uint64(v))
-    return b
-}
-
-type User struct {
-    ID int
-    ...
-}
-```
-
-### Iterating over keys
-
-Bolt stores its keys in byte-sorted order within a bucket. This makes sequential
-iteration over these keys extremely fast. To iterate over keys we'll use a
-`Cursor`:
-
-```go
-db.View(func(tx *bolt.Tx) error {
-	// Assume bucket exists and has keys
-	b := tx.Bucket([]byte("MyBucket"))
-
-	c := b.Cursor()
-
-	for k, v := c.First(); k != nil; k, v = c.Next() {
-		fmt.Printf("key=%s, value=%s\n", k, v)
-	}
-
-	return nil
-})
-```
-
-The cursor allows you to move to a specific point in the list of keys and move
-forward or backward through the keys one at a time.
-
-The following functions are available on the cursor:
-
-```
-First()  Move to the first key.
-Last()   Move to the last key.
-Seek()   Move to a specific key.
-Next()   Move to the next key.
-Prev()   Move to the previous key.
-```
-
-Each of those functions has a return signature of `(key []byte, value []byte)`.
-When you have iterated to the end of the cursor then `Next()` will return a
-`nil` key.  You must seek to a position using `First()`, `Last()`, or `Seek()`
-before calling `Next()` or `Prev()`. If you do not seek to a position then
-these functions will return a `nil` key.
-
-During iteration, if the key is non-`nil` but the value is `nil`, that means
-the key refers to a bucket rather than a value.  Use `Bucket.Bucket()` to
-access the sub-bucket.
-
-
-#### Prefix scans
-
-To iterate over a key prefix, you can combine `Seek()` and `bytes.HasPrefix()`:
-
-```go
-db.View(func(tx *bolt.Tx) error {
-	// Assume bucket exists and has keys
-	c := tx.Bucket([]byte("MyBucket")).Cursor()
-
-	prefix := []byte("1234")
-	for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() {
-		fmt.Printf("key=%s, value=%s\n", k, v)
-	}
-
-	return nil
-})
-```
-
-#### Range scans
-
-Another common use case is scanning over a range such as a time range. If you
-use a sortable time encoding such as RFC3339 then you can query a specific
-date range like this:
-
-```go
-db.View(func(tx *bolt.Tx) error {
-	// Assume our events bucket exists and has RFC3339 encoded time keys.
-	c := tx.Bucket([]byte("Events")).Cursor()
-
-	// Our time range spans the 90's decade.
-	min := []byte("1990-01-01T00:00:00Z")
-	max := []byte("2000-01-01T00:00:00Z")
-
-	// Iterate over the 90's.
-	for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() {
-		fmt.Printf("%s: %s\n", k, v)
-	}
-
-	return nil
-})
-```
-
-Note that, while RFC3339 is sortable, the Golang implementation of RFC3339Nano does not use a fixed number of digits after the decimal point and is therefore not sortable.
-
-
-#### ForEach()
-
-You can also use the function `ForEach()` if you know you'll be iterating over
-all the keys in a bucket:
-
-```go
-db.View(func(tx *bolt.Tx) error {
-	// Assume bucket exists and has keys
-	b := tx.Bucket([]byte("MyBucket"))
-
-	b.ForEach(func(k, v []byte) error {
-		fmt.Printf("key=%s, value=%s\n", k, v)
-		return nil
-	})
-	return nil
-})
-```
-
-Please note that keys and values in `ForEach()` are only valid while
-the transaction is open. If you need to use a key or value outside of
-the transaction, you must use `copy()` to copy it to another byte
-slice.
-
-### Nested buckets
-
-You can also store a bucket in a key to create nested buckets. The API is the
-same as the bucket management API on the `DB` object:
-
-```go
-func (*Bucket) CreateBucket(key []byte) (*Bucket, error)
-func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error)
-func (*Bucket) DeleteBucket(key []byte) error
-```
-
-Say you had a multi-tenant application where the root level bucket was the account bucket. Inside of this bucket was a sequence of accounts which themselves are buckets. And inside the sequence bucket you could have many buckets pertaining to the Account itself (Users, Notes, etc) isolating the information into logical groupings.
-
-```go
-
-// createUser creates a new user in the given account.
-func createUser(accountID int, u *User) error {
-    // Start the transaction.
-    tx, err := db.Begin(true)
-    if err != nil {
-        return err
-    }
-    defer tx.Rollback()
-
-    // Retrieve the root bucket for the account.
-    // Assume this has already been created when the account was set up.
-    root := tx.Bucket([]byte(strconv.FormatUint(accountID, 10)))
-
-    // Setup the users bucket.
-    bkt, err := root.CreateBucketIfNotExists([]byte("USERS"))
-    if err != nil {
-        return err
-    }
-
-    // Generate an ID for the new user.
-    userID, err := bkt.NextSequence()
-    if err != nil {
-        return err
-    }
-    u.ID = userID
-
-    // Marshal and save the encoded user.
-    if buf, err := json.Marshal(u); err != nil {
-        return err
-    } else if err := bkt.Put([]byte(strconv.FormatUint(u.ID, 10)), buf); err != nil {
-        return err
-    }
-
-    // Commit the transaction.
-    if err := tx.Commit(); err != nil {
-        return err
-    }
-
-    return nil
-}
-
-```
-
-
-
-
-### Database backups
-
-Bolt is a single file so it's easy to backup. You can use the `Tx.WriteTo()`
-function to write a consistent view of the database to a writer. If you call
-this from a read-only transaction, it will perform a hot backup and not block
-your other database reads and writes.
-
-By default, it will use a regular file handle which will utilize the operating
-system's page cache. See the [`Tx`](https://godoc.org/github.com/boltdb/bolt#Tx)
-documentation for information about optimizing for larger-than-RAM datasets.
-
-One common use case is to backup over HTTP so you can use tools like `cURL` to
-do database backups:
-
-```go
-func BackupHandleFunc(w http.ResponseWriter, req *http.Request) {
-	err := db.View(func(tx *bolt.Tx) error {
-		w.Header().Set("Content-Type", "application/octet-stream")
-		w.Header().Set("Content-Disposition", `attachment; filename="my.db"`)
-		w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
-		_, err := tx.WriteTo(w)
-		return err
-	})
-	if err != nil {
-		http.Error(w, err.Error(), http.StatusInternalServerError)
-	}
-}
-```
-
-Then you can backup using this command:
-
-```sh
-$ curl http://localhost/backup > my.db
-```
-
-Or you can open your browser to `http://localhost/backup` and it will download
-automatically.
-
-If you want to backup to another file you can use the `Tx.CopyFile()` helper
-function.
-
-
-### Statistics
-
-The database keeps a running count of many of the internal operations it
-performs so you can better understand what's going on. By grabbing a snapshot
-of these stats at two points in time we can see what operations were performed
-in that time range.
-
-For example, we could start a goroutine to log stats every 10 seconds:
-
-```go
-go func() {
-	// Grab the initial stats.
-	prev := db.Stats()
-
-	for {
-		// Wait for 10s.
-		time.Sleep(10 * time.Second)
-
-		// Grab the current stats and diff them.
-		stats := db.Stats()
-		diff := stats.Sub(&prev)
-
-		// Encode stats to JSON and print to STDERR.
-		json.NewEncoder(os.Stderr).Encode(diff)
-
-		// Save stats for the next loop.
-		prev = stats
-	}
-}()
-```
-
-It's also useful to pipe these stats to a service such as statsd for monitoring
-or to provide an HTTP endpoint that will perform a fixed-length sample.
-
-
-### Read-Only Mode
-
-Sometimes it is useful to create a shared, read-only Bolt database. To this,
-set the `Options.ReadOnly` flag when opening your database. Read-only mode
-uses a shared lock to allow multiple processes to read from the database but
-it will block any processes from opening the database in read-write mode.
-
-```go
-db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true})
-if err != nil {
-	log.Fatal(err)
-}
-```
-
-### Mobile Use (iOS/Android)
-
-Bolt is able to run on mobile devices by leveraging the binding feature of the
-[gomobile](https://github.com/golang/mobile) tool. Create a struct that will
-contain your database logic and a reference to a `*bolt.DB` with a initializing
-constructor that takes in a filepath where the database file will be stored.
-Neither Android nor iOS require extra permissions or cleanup from using this method.
-
-```go
-func NewBoltDB(filepath string) *BoltDB {
-	db, err := bolt.Open(filepath+"/demo.db", 0600, nil)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	return &BoltDB{db}
-}
-
-type BoltDB struct {
-	db *bolt.DB
-	...
-}
-
-func (b *BoltDB) Path() string {
-	return b.db.Path()
-}
-
-func (b *BoltDB) Close() {
-	b.db.Close()
-}
-```
-
-Database logic should be defined as methods on this wrapper struct.
-
-To initialize this struct from the native language (both platforms now sync
-their local storage to the cloud. These snippets disable that functionality for the
-database file):
-
-#### Android
-
-```java
-String path;
-if (android.os.Build.VERSION.SDK_INT >=android.os.Build.VERSION_CODES.LOLLIPOP){
-    path = getNoBackupFilesDir().getAbsolutePath();
-} else{
-    path = getFilesDir().getAbsolutePath();
-}
-Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)
-```
-
-#### iOS
-
-```objc
-- (void)demo {
-    NSString* path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
-                                                          NSUserDomainMask,
-                                                          YES) objectAtIndex:0];
-	GoBoltmobiledemoBoltDB * demo = GoBoltmobiledemoNewBoltDB(path);
-	[self addSkipBackupAttributeToItemAtPath:demo.path];
-	//Some DB Logic would go here
-	[demo close];
-}
-
-- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
-{
-    NSURL* URL= [NSURL fileURLWithPath: filePathString];
-    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
-
-    NSError *error = nil;
-    BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
-                                  forKey: NSURLIsExcludedFromBackupKey error: &error];
-    if(!success){
-        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
-    }
-    return success;
-}
-
-```
-
-## Resources
-
-For more information on getting started with Bolt, check out the following articles:
-
-* [Intro to BoltDB: Painless Performant Persistence](http://npf.io/2014/07/intro-to-boltdb-painless-performant-persistence/) by [Nate Finch](https://github.com/natefinch).
-* [Bolt -- an embedded key/value database for Go](https://www.progville.com/go/bolt-embedded-db-golang/) by Progville
-
-
-## Comparison with other databases
-
-### Postgres, MySQL, & other relational databases
-
-Relational databases structure data into rows and are only accessible through
-the use of SQL. This approach provides flexibility in how you store and query
-your data but also incurs overhead in parsing and planning SQL statements. Bolt
-accesses all data by a byte slice key. This makes Bolt fast to read and write
-data by key but provides no built-in support for joining values together.
-
-Most relational databases (with the exception of SQLite) are standalone servers
-that run separately from your application. This gives your systems
-flexibility to connect multiple application servers to a single database
-server but also adds overhead in serializing and transporting data over the
-network. Bolt runs as a library included in your application so all data access
-has to go through your application's process. This brings data closer to your
-application but limits multi-process access to the data.
-
-
-### LevelDB, RocksDB
-
-LevelDB and its derivatives (RocksDB, HyperLevelDB) are similar to Bolt in that
-they are libraries bundled into the application, however, their underlying
-structure is a log-structured merge-tree (LSM tree). An LSM tree optimizes
-random writes by using a write ahead log and multi-tiered, sorted files called
-SSTables. Bolt uses a B+tree internally and only a single file. Both approaches
-have trade-offs.
-
-If you require a high random write throughput (>10,000 w/sec) or you need to use
-spinning disks then LevelDB could be a good choice. If your application is
-read-heavy or does a lot of range scans then Bolt could be a good choice.
-
-One other important consideration is that LevelDB does not have transactions.
-It supports batch writing of key/values pairs and it supports read snapshots
-but it will not give you the ability to do a compare-and-swap operation safely.
-Bolt supports fully serializable ACID transactions.
-
-
-### LMDB
-
-Bolt was originally a port of LMDB so it is architecturally similar. Both use
-a B+tree, have ACID semantics with fully serializable transactions, and support
-lock-free MVCC using a single writer and multiple readers.
-
-The two projects have somewhat diverged. LMDB heavily focuses on raw performance
-while Bolt has focused on simplicity and ease of use. For example, LMDB allows
-several unsafe actions such as direct writes for the sake of performance. Bolt
-opts to disallow actions which can leave the database in a corrupted state. The
-only exception to this in Bolt is `DB.NoSync`.
-
-There are also a few differences in API. LMDB requires a maximum mmap size when
-opening an `mdb_env` whereas Bolt will handle incremental mmap resizing
-automatically. LMDB overloads the getter and setter functions with multiple
-flags whereas Bolt splits these specialized cases into their own functions.
-
-
-## Caveats & Limitations
-
-It's important to pick the right tool for the job and Bolt is no exception.
-Here are a few things to note when evaluating and using Bolt:
-
-* Bolt is good for read intensive workloads. Sequential write performance is
-  also fast but random writes can be slow. You can use `DB.Batch()` or add a
-  write-ahead log to help mitigate this issue.
-
-* Bolt uses a B+tree internally so there can be a lot of random page access.
-  SSDs provide a significant performance boost over spinning disks.
-
-* Try to avoid long running read transactions. Bolt uses copy-on-write so
-  old pages cannot be reclaimed while an old transaction is using them.
-
-* Byte slices returned from Bolt are only valid during a transaction. Once the
-  transaction has been committed or rolled back then the memory they point to
-  can be reused by a new page or can be unmapped from virtual memory and you'll
-  see an `unexpected fault address` panic when accessing it.
-
-* Bolt uses an exclusive write lock on the database file so it cannot be
-  shared by multiple processes.
-
-* Be careful when using `Bucket.FillPercent`. Setting a high fill percent for
-  buckets that have random inserts will cause your database to have very poor
-  page utilization.
-
-* Use larger buckets in general. Smaller buckets causes poor page utilization
-  once they become larger than the page size (typically 4KB).
-
-* Bulk loading a lot of random writes into a new bucket can be slow as the
-  page will not split until the transaction is committed. Randomly inserting
-  more than 100,000 key/value pairs into a single new bucket in a single
-  transaction is not advised.
-
-* Bolt uses a memory-mapped file so the underlying operating system handles the
-  caching of the data. Typically, the OS will cache as much of the file as it
-  can in memory and will release memory as needed to other processes. This means
-  that Bolt can show very high memory usage when working with large databases.
-  However, this is expected and the OS will release memory as needed. Bolt can
-  handle databases much larger than the available physical RAM, provided its
-  memory-map fits in the process virtual address space. It may be problematic
-  on 32-bits systems.
-
-* The data structures in the Bolt database are memory mapped so the data file
-  will be endian specific. This means that you cannot copy a Bolt file from a
-  little endian machine to a big endian machine and have it work. For most
-  users this is not a concern since most modern CPUs are little endian.
-
-* Because of the way pages are laid out on disk, Bolt cannot truncate data files
-  and return free pages back to the disk. Instead, Bolt maintains a free list
-  of unused pages within its data file. These free pages can be reused by later
-  transactions. This works well for many use cases as databases generally tend
-  to grow. However, it's important to note that deleting large chunks of data
-  will not allow you to reclaim that space on disk.
-
-  For more information on page allocation, [see this comment][page-allocation].
-
-[page-allocation]: https://github.com/boltdb/bolt/issues/308#issuecomment-74811638
-
-
-## Reading the Source
-
-Bolt is a relatively small code base (<3KLOC) for an embedded, serializable,
-transactional key/value database so it can be a good starting point for people
-interested in how databases work.
-
-The best places to start are the main entry points into Bolt:
-
-- `Open()` - Initializes the reference to the database. It's responsible for
-  creating the database if it doesn't exist, obtaining an exclusive lock on the
-  file, reading the meta pages, & memory-mapping the file.
-
-- `DB.Begin()` - Starts a read-only or read-write transaction depending on the
-  value of the `writable` argument. This requires briefly obtaining the "meta"
-  lock to keep track of open transactions. Only one read-write transaction can
-  exist at a time so the "rwlock" is acquired during the life of a read-write
-  transaction.
-
-- `Bucket.Put()` - Writes a key/value pair into a bucket. After validating the
-  arguments, a cursor is used to traverse the B+tree to the page and position
-  where they key & value will be written. Once the position is found, the bucket
-  materializes the underlying page and the page's parent pages into memory as
-  "nodes". These nodes are where mutations occur during read-write transactions.
-  These changes get flushed to disk during commit.
-
-- `Bucket.Get()` - Retrieves a key/value pair from a bucket. This uses a cursor
-  to move to the page & position of a key/value pair. During a read-only
-  transaction, the key and value data is returned as a direct reference to the
-  underlying mmap file so there's no allocation overhead. For read-write
-  transactions, this data may reference the mmap file or one of the in-memory
-  node values.
-
-- `Cursor` - This object is simply for traversing the B+tree of on-disk pages
-  or in-memory nodes. It can seek to a specific key, move to the first or last
-  value, or it can move forward or backward. The cursor handles the movement up
-  and down the B+tree transparently to the end user.
-
-- `Tx.Commit()` - Converts the in-memory dirty nodes and the list of free pages
-  into pages to be written to disk. Writing to disk then occurs in two phases.
-  First, the dirty pages are written to disk and an `fsync()` occurs. Second, a
-  new meta page with an incremented transaction ID is written and another
-  `fsync()` occurs. This two phase write ensures that partially written data
-  pages are ignored in the event of a crash since the meta page pointing to them
-  is never written. Partially written meta pages are invalidated because they
-  are written with a checksum.
-
-If you have additional notes that could be helpful for others, please submit
-them via pull request.
-
-
-## Other Projects Using Bolt
-
-Below is a list of public, open source projects that use Bolt:
-
-* [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files.
-* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard.
-* [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside.
-* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
-* [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics.
-* [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects.
-* [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday.
-* [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
-* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
-* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin".
-* [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka.
-* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
-* [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt.
-* [photosite/session](https://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site.
-* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage.
-* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters.
-* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
-* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
-* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server.
-* [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read.
-* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics.
-* [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data.
-* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
-* [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
-* [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs.
-* [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems.
-* [stow](https://github.com/djherbis/stow) -  a persistence manager for objects
-  backed by boltdb.
-* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
-  simple tx and key scans.
-* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets.
-* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service
-* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service.
-* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
-* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
-* [Storm](https://github.com/asdine/storm) - Simple and powerful ORM for BoltDB.
-* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB.
-* [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings.
-* [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend.
-* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files.
-* [GoShort](https://github.com/pankajkhairnar/goShort) - GoShort is a URL shortener written in Golang and BoltDB for persistent key/value storage and for routing it's using high performent HTTPRouter.
-* [torrent](https://github.com/anacrolix/torrent) - Full-featured BitTorrent client package and utilities in Go. BoltDB is a storage backend in development.
-* [gopherpit](https://github.com/gopherpit/gopherpit) - A web service to manage Go remote import paths with custom domains
-* [bolter](https://github.com/hasit/bolter) - Command-line app for viewing BoltDB file in your terminal.
-* [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet.
-* [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency.
-* [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies
-* [BoltHold](https://github.com/timshannon/bolthold) - An embeddable NoSQL store for Go types built on BoltDB
-
-If you are using Bolt in a project please send a pull request to add it to the list.

+ 0 - 18
vendor/github.com/boltdb/bolt/appveyor.yml

@@ -1,18 +0,0 @@
-version: "{build}"
-
-os: Windows Server 2012 R2
-
-clone_folder: c:\gopath\src\github.com\boltdb\bolt
-
-environment:
-  GOPATH: c:\gopath
-
-install:
-  - echo %PATH%
-  - echo %GOPATH%
-  - go version
-  - go env
-  - go get -v -t ./...
-
-build_script:
-  - go test -v ./...

+ 0 - 10
vendor/github.com/boltdb/bolt/bolt_386.go

@@ -1,10 +0,0 @@
-package bolt
-
-// maxMapSize represents the largest mmap size supported by Bolt.
-const maxMapSize = 0x7FFFFFFF // 2GB
-
-// maxAllocSize is the size used when creating array pointers.
-const maxAllocSize = 0xFFFFFFF
-
-// Are unaligned load/stores broken on this arch?
-var brokenUnaligned = false

+ 0 - 10
vendor/github.com/boltdb/bolt/bolt_amd64.go

@@ -1,10 +0,0 @@
-package bolt
-
-// maxMapSize represents the largest mmap size supported by Bolt.
-const maxMapSize = 0xFFFFFFFFFFFF // 256TB
-
-// maxAllocSize is the size used when creating array pointers.
-const maxAllocSize = 0x7FFFFFFF
-
-// Are unaligned load/stores broken on this arch?
-var brokenUnaligned = false

+ 0 - 28
vendor/github.com/boltdb/bolt/bolt_arm.go

@@ -1,28 +0,0 @@
-package bolt
-
-import "unsafe"
-
-// maxMapSize represents the largest mmap size supported by Bolt.
-const maxMapSize = 0x7FFFFFFF // 2GB
-
-// maxAllocSize is the size used when creating array pointers.
-const maxAllocSize = 0xFFFFFFF
-
-// Are unaligned load/stores broken on this arch?
-var brokenUnaligned bool
-
-func init() {
-	// Simple check to see whether this arch handles unaligned load/stores
-	// correctly.
-
-	// ARM9 and older devices require load/stores to be from/to aligned
-	// addresses. If not, the lower 2 bits are cleared and that address is
-	// read in a jumbled up order.
-
-	// See http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka15414.html
-
-	raw := [6]byte{0xfe, 0xef, 0x11, 0x22, 0x22, 0x11}
-	val := *(*uint32)(unsafe.Pointer(uintptr(unsafe.Pointer(&raw)) + 2))
-
-	brokenUnaligned = val != 0x11222211
-}

+ 0 - 12
vendor/github.com/boltdb/bolt/bolt_arm64.go

@@ -1,12 +0,0 @@
-// +build arm64
-
-package bolt
-
-// maxMapSize represents the largest mmap size supported by Bolt.
-const maxMapSize = 0xFFFFFFFFFFFF // 256TB
-
-// maxAllocSize is the size used when creating array pointers.
-const maxAllocSize = 0x7FFFFFFF
-
-// Are unaligned load/stores broken on this arch?
-var brokenUnaligned = false

+ 0 - 10
vendor/github.com/boltdb/bolt/bolt_linux.go

@@ -1,10 +0,0 @@
-package bolt
-
-import (
-	"syscall"
-)
-
-// fdatasync flushes written data to a file descriptor.
-func fdatasync(db *DB) error {
-	return syscall.Fdatasync(int(db.file.Fd()))
-}

+ 0 - 27
vendor/github.com/boltdb/bolt/bolt_openbsd.go

@@ -1,27 +0,0 @@
-package bolt
-
-import (
-	"syscall"
-	"unsafe"
-)
-
-const (
-	msAsync      = 1 << iota // perform asynchronous writes
-	msSync                   // perform synchronous writes
-	msInvalidate             // invalidate cached data
-)
-
-func msync(db *DB) error {
-	_, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(db.data)), uintptr(db.datasz), msInvalidate)
-	if errno != 0 {
-		return errno
-	}
-	return nil
-}
-
-func fdatasync(db *DB) error {
-	if db.data != nil {
-		return msync(db)
-	}
-	return db.file.Sync()
-}

+ 0 - 9
vendor/github.com/boltdb/bolt/bolt_ppc.go

@@ -1,9 +0,0 @@
-// +build ppc
-
-package bolt
-
-// maxMapSize represents the largest mmap size supported by Bolt.
-const maxMapSize = 0x7FFFFFFF // 2GB
-
-// maxAllocSize is the size used when creating array pointers.
-const maxAllocSize = 0xFFFFFFF

+ 0 - 12
vendor/github.com/boltdb/bolt/bolt_ppc64.go

@@ -1,12 +0,0 @@
-// +build ppc64
-
-package bolt
-
-// maxMapSize represents the largest mmap size supported by Bolt.
-const maxMapSize = 0xFFFFFFFFFFFF // 256TB
-
-// maxAllocSize is the size used when creating array pointers.
-const maxAllocSize = 0x7FFFFFFF
-
-// Are unaligned load/stores broken on this arch?
-var brokenUnaligned = false

+ 0 - 12
vendor/github.com/boltdb/bolt/bolt_ppc64le.go

@@ -1,12 +0,0 @@
-// +build ppc64le
-
-package bolt
-
-// maxMapSize represents the largest mmap size supported by Bolt.
-const maxMapSize = 0xFFFFFFFFFFFF // 256TB
-
-// maxAllocSize is the size used when creating array pointers.
-const maxAllocSize = 0x7FFFFFFF
-
-// Are unaligned load/stores broken on this arch?
-var brokenUnaligned = false

+ 0 - 12
vendor/github.com/boltdb/bolt/bolt_s390x.go

@@ -1,12 +0,0 @@
-// +build s390x
-
-package bolt
-
-// maxMapSize represents the largest mmap size supported by Bolt.
-const maxMapSize = 0xFFFFFFFFFFFF // 256TB
-
-// maxAllocSize is the size used when creating array pointers.
-const maxAllocSize = 0x7FFFFFFF
-
-// Are unaligned load/stores broken on this arch?
-var brokenUnaligned = false

+ 0 - 89
vendor/github.com/boltdb/bolt/bolt_unix.go

@@ -1,89 +0,0 @@
-// +build !windows,!plan9,!solaris
-
-package bolt
-
-import (
-	"fmt"
-	"os"
-	"syscall"
-	"time"
-	"unsafe"
-)
-
-// flock acquires an advisory lock on a file descriptor.
-func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
-	var t time.Time
-	for {
-		// If we're beyond our timeout then return an error.
-		// This can only occur after we've attempted a flock once.
-		if t.IsZero() {
-			t = time.Now()
-		} else if timeout > 0 && time.Since(t) > timeout {
-			return ErrTimeout
-		}
-		flag := syscall.LOCK_SH
-		if exclusive {
-			flag = syscall.LOCK_EX
-		}
-
-		// Otherwise attempt to obtain an exclusive lock.
-		err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB)
-		if err == nil {
-			return nil
-		} else if err != syscall.EWOULDBLOCK {
-			return err
-		}
-
-		// Wait for a bit and try again.
-		time.Sleep(50 * time.Millisecond)
-	}
-}
-
-// funlock releases an advisory lock on a file descriptor.
-func funlock(db *DB) error {
-	return syscall.Flock(int(db.file.Fd()), syscall.LOCK_UN)
-}
-
-// mmap memory maps a DB's data file.
-func mmap(db *DB, sz int) error {
-	// Map the data file to memory.
-	b, err := syscall.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
-	if err != nil {
-		return err
-	}
-
-	// Advise the kernel that the mmap is accessed randomly.
-	if err := madvise(b, syscall.MADV_RANDOM); err != nil {
-		return fmt.Errorf("madvise: %s", err)
-	}
-
-	// Save the original byte slice and convert to a byte array pointer.
-	db.dataref = b
-	db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
-	db.datasz = sz
-	return nil
-}
-
-// munmap unmaps a DB's data file from memory.
-func munmap(db *DB) error {
-	// Ignore the unmap if we have no mapped data.
-	if db.dataref == nil {
-		return nil
-	}
-
-	// Unmap using the original byte slice.
-	err := syscall.Munmap(db.dataref)
-	db.dataref = nil
-	db.data = nil
-	db.datasz = 0
-	return err
-}
-
-// NOTE: This function is copied from stdlib because it is not available on darwin.
-func madvise(b []byte, advice int) (err error) {
-	_, _, e1 := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), uintptr(advice))
-	if e1 != 0 {
-		err = e1
-	}
-	return
-}

+ 0 - 90
vendor/github.com/boltdb/bolt/bolt_unix_solaris.go

@@ -1,90 +0,0 @@
-package bolt
-
-import (
-	"fmt"
-	"os"
-	"syscall"
-	"time"
-	"unsafe"
-
-	"golang.org/x/sys/unix"
-)
-
-// flock acquires an advisory lock on a file descriptor.
-func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
-	var t time.Time
-	for {
-		// If we're beyond our timeout then return an error.
-		// This can only occur after we've attempted a flock once.
-		if t.IsZero() {
-			t = time.Now()
-		} else if timeout > 0 && time.Since(t) > timeout {
-			return ErrTimeout
-		}
-		var lock syscall.Flock_t
-		lock.Start = 0
-		lock.Len = 0
-		lock.Pid = 0
-		lock.Whence = 0
-		lock.Pid = 0
-		if exclusive {
-			lock.Type = syscall.F_WRLCK
-		} else {
-			lock.Type = syscall.F_RDLCK
-		}
-		err := syscall.FcntlFlock(db.file.Fd(), syscall.F_SETLK, &lock)
-		if err == nil {
-			return nil
-		} else if err != syscall.EAGAIN {
-			return err
-		}
-
-		// Wait for a bit and try again.
-		time.Sleep(50 * time.Millisecond)
-	}
-}
-
-// funlock releases an advisory lock on a file descriptor.
-func funlock(db *DB) error {
-	var lock syscall.Flock_t
-	lock.Start = 0
-	lock.Len = 0
-	lock.Type = syscall.F_UNLCK
-	lock.Whence = 0
-	return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
-}
-
-// mmap memory maps a DB's data file.
-func mmap(db *DB, sz int) error {
-	// Map the data file to memory.
-	b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
-	if err != nil {
-		return err
-	}
-
-	// Advise the kernel that the mmap is accessed randomly.
-	if err := unix.Madvise(b, syscall.MADV_RANDOM); err != nil {
-		return fmt.Errorf("madvise: %s", err)
-	}
-
-	// Save the original byte slice and convert to a byte array pointer.
-	db.dataref = b
-	db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
-	db.datasz = sz
-	return nil
-}
-
-// munmap unmaps a DB's data file from memory.
-func munmap(db *DB) error {
-	// Ignore the unmap if we have no mapped data.
-	if db.dataref == nil {
-		return nil
-	}
-
-	// Unmap using the original byte slice.
-	err := unix.Munmap(db.dataref)
-	db.dataref = nil
-	db.data = nil
-	db.datasz = 0
-	return err
-}

+ 0 - 144
vendor/github.com/boltdb/bolt/bolt_windows.go

@@ -1,144 +0,0 @@
-package bolt
-
-import (
-	"fmt"
-	"os"
-	"syscall"
-	"time"
-	"unsafe"
-)
-
-// LockFileEx code derived from golang build filemutex_windows.go @ v1.5.1
-var (
-	modkernel32      = syscall.NewLazyDLL("kernel32.dll")
-	procLockFileEx   = modkernel32.NewProc("LockFileEx")
-	procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
-)
-
-const (
-	lockExt = ".lock"
-
-	// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
-	flagLockExclusive       = 2
-	flagLockFailImmediately = 1
-
-	// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
-	errLockViolation syscall.Errno = 0x21
-)
-
-func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
-	r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
-	if r == 0 {
-		return err
-	}
-	return nil
-}
-
-func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
-	r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0)
-	if r == 0 {
-		return err
-	}
-	return nil
-}
-
-// fdatasync flushes written data to a file descriptor.
-func fdatasync(db *DB) error {
-	return db.file.Sync()
-}
-
-// flock acquires an advisory lock on a file descriptor.
-func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
-	// Create a separate lock file on windows because a process
-	// cannot share an exclusive lock on the same file. This is
-	// needed during Tx.WriteTo().
-	f, err := os.OpenFile(db.path+lockExt, os.O_CREATE, mode)
-	if err != nil {
-		return err
-	}
-	db.lockfile = f
-
-	var t time.Time
-	for {
-		// If we're beyond our timeout then return an error.
-		// This can only occur after we've attempted a flock once.
-		if t.IsZero() {
-			t = time.Now()
-		} else if timeout > 0 && time.Since(t) > timeout {
-			return ErrTimeout
-		}
-
-		var flag uint32 = flagLockFailImmediately
-		if exclusive {
-			flag |= flagLockExclusive
-		}
-
-		err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
-		if err == nil {
-			return nil
-		} else if err != errLockViolation {
-			return err
-		}
-
-		// Wait for a bit and try again.
-		time.Sleep(50 * time.Millisecond)
-	}
-}
-
-// funlock releases an advisory lock on a file descriptor.
-func funlock(db *DB) error {
-	err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{})
-	db.lockfile.Close()
-	os.Remove(db.path + lockExt)
-	return err
-}
-
-// mmap memory maps a DB's data file.
-// Based on: https://github.com/edsrzf/mmap-go
-func mmap(db *DB, sz int) error {
-	if !db.readOnly {
-		// Truncate the database to the size of the mmap.
-		if err := db.file.Truncate(int64(sz)); err != nil {
-			return fmt.Errorf("truncate: %s", err)
-		}
-	}
-
-	// Open a file mapping handle.
-	sizelo := uint32(sz >> 32)
-	sizehi := uint32(sz) & 0xffffffff
-	h, errno := syscall.CreateFileMapping(syscall.Handle(db.file.Fd()), nil, syscall.PAGE_READONLY, sizelo, sizehi, nil)
-	if h == 0 {
-		return os.NewSyscallError("CreateFileMapping", errno)
-	}
-
-	// Create the memory map.
-	addr, errno := syscall.MapViewOfFile(h, syscall.FILE_MAP_READ, 0, 0, uintptr(sz))
-	if addr == 0 {
-		return os.NewSyscallError("MapViewOfFile", errno)
-	}
-
-	// Close mapping handle.
-	if err := syscall.CloseHandle(syscall.Handle(h)); err != nil {
-		return os.NewSyscallError("CloseHandle", err)
-	}
-
-	// Convert to a byte array.
-	db.data = ((*[maxMapSize]byte)(unsafe.Pointer(addr)))
-	db.datasz = sz
-
-	return nil
-}
-
-// munmap unmaps a pointer from a file.
-// Based on: https://github.com/edsrzf/mmap-go
-func munmap(db *DB) error {
-	if db.data == nil {
-		return nil
-	}
-
-	addr := (uintptr)(unsafe.Pointer(&db.data[0]))
-	if err := syscall.UnmapViewOfFile(addr); err != nil {
-		return os.NewSyscallError("UnmapViewOfFile", err)
-	}
-	return nil
-}

+ 0 - 8
vendor/github.com/boltdb/bolt/boltsync_unix.go

@@ -1,8 +0,0 @@
-// +build !windows,!plan9,!linux,!openbsd
-
-package bolt
-
-// fdatasync flushes written data to a file descriptor.
-func fdatasync(db *DB) error {
-	return db.file.Sync()
-}

+ 0 - 777
vendor/github.com/boltdb/bolt/bucket.go

@@ -1,777 +0,0 @@
-package bolt
-
-import (
-	"bytes"
-	"fmt"
-	"unsafe"
-)
-
-const (
-	// MaxKeySize is the maximum length of a key, in bytes.
-	MaxKeySize = 32768
-
-	// MaxValueSize is the maximum length of a value, in bytes.
-	MaxValueSize = (1 << 31) - 2
-)
-
-const (
-	maxUint = ^uint(0)
-	minUint = 0
-	maxInt  = int(^uint(0) >> 1)
-	minInt  = -maxInt - 1
-)
-
-const bucketHeaderSize = int(unsafe.Sizeof(bucket{}))
-
-const (
-	minFillPercent = 0.1
-	maxFillPercent = 1.0
-)
-
-// DefaultFillPercent is the percentage that split pages are filled.
-// This value can be changed by setting Bucket.FillPercent.
-const DefaultFillPercent = 0.5
-
-// Bucket represents a collection of key/value pairs inside the database.
-type Bucket struct {
-	*bucket
-	tx       *Tx                // the associated transaction
-	buckets  map[string]*Bucket // subbucket cache
-	page     *page              // inline page reference
-	rootNode *node              // materialized node for the root page.
-	nodes    map[pgid]*node     // node cache
-
-	// Sets the threshold for filling nodes when they split. By default,
-	// the bucket will fill to 50% but it can be useful to increase this
-	// amount if you know that your write workloads are mostly append-only.
-	//
-	// This is non-persisted across transactions so it must be set in every Tx.
-	FillPercent float64
-}
-
-// bucket represents the on-file representation of a bucket.
-// This is stored as the "value" of a bucket key. If the bucket is small enough,
-// then its root page can be stored inline in the "value", after the bucket
-// header. In the case of inline buckets, the "root" will be 0.
-type bucket struct {
-	root     pgid   // page id of the bucket's root-level page
-	sequence uint64 // monotonically incrementing, used by NextSequence()
-}
-
-// newBucket returns a new bucket associated with a transaction.
-func newBucket(tx *Tx) Bucket {
-	var b = Bucket{tx: tx, FillPercent: DefaultFillPercent}
-	if tx.writable {
-		b.buckets = make(map[string]*Bucket)
-		b.nodes = make(map[pgid]*node)
-	}
-	return b
-}
-
-// Tx returns the tx of the bucket.
-func (b *Bucket) Tx() *Tx {
-	return b.tx
-}
-
-// Root returns the root of the bucket.
-func (b *Bucket) Root() pgid {
-	return b.root
-}
-
-// Writable returns whether the bucket is writable.
-func (b *Bucket) Writable() bool {
-	return b.tx.writable
-}
-
-// Cursor creates a cursor associated with the bucket.
-// The cursor is only valid as long as the transaction is open.
-// Do not use a cursor after the transaction is closed.
-func (b *Bucket) Cursor() *Cursor {
-	// Update transaction statistics.
-	b.tx.stats.CursorCount++
-
-	// Allocate and return a cursor.
-	return &Cursor{
-		bucket: b,
-		stack:  make([]elemRef, 0),
-	}
-}
-
-// Bucket retrieves a nested bucket by name.
-// Returns nil if the bucket does not exist.
-// The bucket instance is only valid for the lifetime of the transaction.
-func (b *Bucket) Bucket(name []byte) *Bucket {
-	if b.buckets != nil {
-		if child := b.buckets[string(name)]; child != nil {
-			return child
-		}
-	}
-
-	// Move cursor to key.
-	c := b.Cursor()
-	k, v, flags := c.seek(name)
-
-	// Return nil if the key doesn't exist or it is not a bucket.
-	if !bytes.Equal(name, k) || (flags&bucketLeafFlag) == 0 {
-		return nil
-	}
-
-	// Otherwise create a bucket and cache it.
-	var child = b.openBucket(v)
-	if b.buckets != nil {
-		b.buckets[string(name)] = child
-	}
-
-	return child
-}
-
-// Helper method that re-interprets a sub-bucket value
-// from a parent into a Bucket
-func (b *Bucket) openBucket(value []byte) *Bucket {
-	var child = newBucket(b.tx)
-
-	// If unaligned load/stores are broken on this arch and value is
-	// unaligned simply clone to an aligned byte array.
-	unaligned := brokenUnaligned && uintptr(unsafe.Pointer(&value[0]))&3 != 0
-
-	if unaligned {
-		value = cloneBytes(value)
-	}
-
-	// If this is a writable transaction then we need to copy the bucket entry.
-	// Read-only transactions can point directly at the mmap entry.
-	if b.tx.writable && !unaligned {
-		child.bucket = &bucket{}
-		*child.bucket = *(*bucket)(unsafe.Pointer(&value[0]))
-	} else {
-		child.bucket = (*bucket)(unsafe.Pointer(&value[0]))
-	}
-
-	// Save a reference to the inline page if the bucket is inline.
-	if child.root == 0 {
-		child.page = (*page)(unsafe.Pointer(&value[bucketHeaderSize]))
-	}
-
-	return &child
-}
-
-// CreateBucket creates a new bucket at the given key and returns the new bucket.
-// Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long.
-// The bucket instance is only valid for the lifetime of the transaction.
-func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
-	if b.tx.db == nil {
-		return nil, ErrTxClosed
-	} else if !b.tx.writable {
-		return nil, ErrTxNotWritable
-	} else if len(key) == 0 {
-		return nil, ErrBucketNameRequired
-	}
-
-	// Move cursor to correct position.
-	c := b.Cursor()
-	k, _, flags := c.seek(key)
-
-	// Return an error if there is an existing key.
-	if bytes.Equal(key, k) {
-		if (flags & bucketLeafFlag) != 0 {
-			return nil, ErrBucketExists
-		}
-		return nil, ErrIncompatibleValue
-	}
-
-	// Create empty, inline bucket.
-	var bucket = Bucket{
-		bucket:      &bucket{},
-		rootNode:    &node{isLeaf: true},
-		FillPercent: DefaultFillPercent,
-	}
-	var value = bucket.write()
-
-	// Insert into node.
-	key = cloneBytes(key)
-	c.node().put(key, key, value, 0, bucketLeafFlag)
-
-	// Since subbuckets are not allowed on inline buckets, we need to
-	// dereference the inline page, if it exists. This will cause the bucket
-	// to be treated as a regular, non-inline bucket for the rest of the tx.
-	b.page = nil
-
-	return b.Bucket(key), nil
-}
-
-// CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it.
-// Returns an error if the bucket name is blank, or if the bucket name is too long.
-// The bucket instance is only valid for the lifetime of the transaction.
-func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) {
-	child, err := b.CreateBucket(key)
-	if err == ErrBucketExists {
-		return b.Bucket(key), nil
-	} else if err != nil {
-		return nil, err
-	}
-	return child, nil
-}
-
-// DeleteBucket deletes a bucket at the given key.
-// Returns an error if the bucket does not exists, or if the key represents a non-bucket value.
-func (b *Bucket) DeleteBucket(key []byte) error {
-	if b.tx.db == nil {
-		return ErrTxClosed
-	} else if !b.Writable() {
-		return ErrTxNotWritable
-	}
-
-	// Move cursor to correct position.
-	c := b.Cursor()
-	k, _, flags := c.seek(key)
-
-	// Return an error if bucket doesn't exist or is not a bucket.
-	if !bytes.Equal(key, k) {
-		return ErrBucketNotFound
-	} else if (flags & bucketLeafFlag) == 0 {
-		return ErrIncompatibleValue
-	}
-
-	// Recursively delete all child buckets.
-	child := b.Bucket(key)
-	err := child.ForEach(func(k, v []byte) error {
-		if v == nil {
-			if err := child.DeleteBucket(k); err != nil {
-				return fmt.Errorf("delete bucket: %s", err)
-			}
-		}
-		return nil
-	})
-	if err != nil {
-		return err
-	}
-
-	// Remove cached copy.
-	delete(b.buckets, string(key))
-
-	// Release all bucket pages to freelist.
-	child.nodes = nil
-	child.rootNode = nil
-	child.free()
-
-	// Delete the node if we have a matching key.
-	c.node().del(key)
-
-	return nil
-}
-
-// Get retrieves the value for a key in the bucket.
-// Returns a nil value if the key does not exist or if the key is a nested bucket.
-// The returned value is only valid for the life of the transaction.
-func (b *Bucket) Get(key []byte) []byte {
-	k, v, flags := b.Cursor().seek(key)
-
-	// Return nil if this is a bucket.
-	if (flags & bucketLeafFlag) != 0 {
-		return nil
-	}
-
-	// If our target node isn't the same key as what's passed in then return nil.
-	if !bytes.Equal(key, k) {
-		return nil
-	}
-	return v
-}
-
-// Put sets the value for a key in the bucket.
-// If the key exist then its previous value will be overwritten.
-// Supplied value must remain valid for the life of the transaction.
-// Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large.
-func (b *Bucket) Put(key []byte, value []byte) error {
-	if b.tx.db == nil {
-		return ErrTxClosed
-	} else if !b.Writable() {
-		return ErrTxNotWritable
-	} else if len(key) == 0 {
-		return ErrKeyRequired
-	} else if len(key) > MaxKeySize {
-		return ErrKeyTooLarge
-	} else if int64(len(value)) > MaxValueSize {
-		return ErrValueTooLarge
-	}
-
-	// Move cursor to correct position.
-	c := b.Cursor()
-	k, _, flags := c.seek(key)
-
-	// Return an error if there is an existing key with a bucket value.
-	if bytes.Equal(key, k) && (flags&bucketLeafFlag) != 0 {
-		return ErrIncompatibleValue
-	}
-
-	// Insert into node.
-	key = cloneBytes(key)
-	c.node().put(key, key, value, 0, 0)
-
-	return nil
-}
-
-// Delete removes a key from the bucket.
-// If the key does not exist then nothing is done and a nil error is returned.
-// Returns an error if the bucket was created from a read-only transaction.
-func (b *Bucket) Delete(key []byte) error {
-	if b.tx.db == nil {
-		return ErrTxClosed
-	} else if !b.Writable() {
-		return ErrTxNotWritable
-	}
-
-	// Move cursor to correct position.
-	c := b.Cursor()
-	_, _, flags := c.seek(key)
-
-	// Return an error if there is already existing bucket value.
-	if (flags & bucketLeafFlag) != 0 {
-		return ErrIncompatibleValue
-	}
-
-	// Delete the node if we have a matching key.
-	c.node().del(key)
-
-	return nil
-}
-
-// Sequence returns the current integer for the bucket without incrementing it.
-func (b *Bucket) Sequence() uint64 { return b.bucket.sequence }
-
-// SetSequence updates the sequence number for the bucket.
-func (b *Bucket) SetSequence(v uint64) error {
-	if b.tx.db == nil {
-		return ErrTxClosed
-	} else if !b.Writable() {
-		return ErrTxNotWritable
-	}
-
-	// Materialize the root node if it hasn't been already so that the
-	// bucket will be saved during commit.
-	if b.rootNode == nil {
-		_ = b.node(b.root, nil)
-	}
-
-	// Increment and return the sequence.
-	b.bucket.sequence = v
-	return nil
-}
-
-// NextSequence returns an autoincrementing integer for the bucket.
-func (b *Bucket) NextSequence() (uint64, error) {
-	if b.tx.db == nil {
-		return 0, ErrTxClosed
-	} else if !b.Writable() {
-		return 0, ErrTxNotWritable
-	}
-
-	// Materialize the root node if it hasn't been already so that the
-	// bucket will be saved during commit.
-	if b.rootNode == nil {
-		_ = b.node(b.root, nil)
-	}
-
-	// Increment and return the sequence.
-	b.bucket.sequence++
-	return b.bucket.sequence, nil
-}
-
-// ForEach executes a function for each key/value pair in a bucket.
-// If the provided function returns an error then the iteration is stopped and
-// the error is returned to the caller. The provided function must not modify
-// the bucket; this will result in undefined behavior.
-func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
-	if b.tx.db == nil {
-		return ErrTxClosed
-	}
-	c := b.Cursor()
-	for k, v := c.First(); k != nil; k, v = c.Next() {
-		if err := fn(k, v); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// Stat returns stats on a bucket.
-func (b *Bucket) Stats() BucketStats {
-	var s, subStats BucketStats
-	pageSize := b.tx.db.pageSize
-	s.BucketN += 1
-	if b.root == 0 {
-		s.InlineBucketN += 1
-	}
-	b.forEachPage(func(p *page, depth int) {
-		if (p.flags & leafPageFlag) != 0 {
-			s.KeyN += int(p.count)
-
-			// used totals the used bytes for the page
-			used := pageHeaderSize
-
-			if p.count != 0 {
-				// If page has any elements, add all element headers.
-				used += leafPageElementSize * int(p.count-1)
-
-				// Add all element key, value sizes.
-				// The computation takes advantage of the fact that the position
-				// of the last element's key/value equals to the total of the sizes
-				// of all previous elements' keys and values.
-				// It also includes the last element's header.
-				lastElement := p.leafPageElement(p.count - 1)
-				used += int(lastElement.pos + lastElement.ksize + lastElement.vsize)
-			}
-
-			if b.root == 0 {
-				// For inlined bucket just update the inline stats
-				s.InlineBucketInuse += used
-			} else {
-				// For non-inlined bucket update all the leaf stats
-				s.LeafPageN++
-				s.LeafInuse += used
-				s.LeafOverflowN += int(p.overflow)
-
-				// Collect stats from sub-buckets.
-				// Do that by iterating over all element headers
-				// looking for the ones with the bucketLeafFlag.
-				for i := uint16(0); i < p.count; i++ {
-					e := p.leafPageElement(i)
-					if (e.flags & bucketLeafFlag) != 0 {
-						// For any bucket element, open the element value
-						// and recursively call Stats on the contained bucket.
-						subStats.Add(b.openBucket(e.value()).Stats())
-					}
-				}
-			}
-		} else if (p.flags & branchPageFlag) != 0 {
-			s.BranchPageN++
-			lastElement := p.branchPageElement(p.count - 1)
-
-			// used totals the used bytes for the page
-			// Add header and all element headers.
-			used := pageHeaderSize + (branchPageElementSize * int(p.count-1))
-
-			// Add size of all keys and values.
-			// Again, use the fact that last element's position equals to
-			// the total of key, value sizes of all previous elements.
-			used += int(lastElement.pos + lastElement.ksize)
-			s.BranchInuse += used
-			s.BranchOverflowN += int(p.overflow)
-		}
-
-		// Keep track of maximum page depth.
-		if depth+1 > s.Depth {
-			s.Depth = (depth + 1)
-		}
-	})
-
-	// Alloc stats can be computed from page counts and pageSize.
-	s.BranchAlloc = (s.BranchPageN + s.BranchOverflowN) * pageSize
-	s.LeafAlloc = (s.LeafPageN + s.LeafOverflowN) * pageSize
-
-	// Add the max depth of sub-buckets to get total nested depth.
-	s.Depth += subStats.Depth
-	// Add the stats for all sub-buckets
-	s.Add(subStats)
-	return s
-}
-
-// forEachPage iterates over every page in a bucket, including inline pages.
-func (b *Bucket) forEachPage(fn func(*page, int)) {
-	// If we have an inline page then just use that.
-	if b.page != nil {
-		fn(b.page, 0)
-		return
-	}
-
-	// Otherwise traverse the page hierarchy.
-	b.tx.forEachPage(b.root, 0, fn)
-}
-
-// forEachPageNode iterates over every page (or node) in a bucket.
-// This also includes inline pages.
-func (b *Bucket) forEachPageNode(fn func(*page, *node, int)) {
-	// If we have an inline page or root node then just use that.
-	if b.page != nil {
-		fn(b.page, nil, 0)
-		return
-	}
-	b._forEachPageNode(b.root, 0, fn)
-}
-
-func (b *Bucket) _forEachPageNode(pgid pgid, depth int, fn func(*page, *node, int)) {
-	var p, n = b.pageNode(pgid)
-
-	// Execute function.
-	fn(p, n, depth)
-
-	// Recursively loop over children.
-	if p != nil {
-		if (p.flags & branchPageFlag) != 0 {
-			for i := 0; i < int(p.count); i++ {
-				elem := p.branchPageElement(uint16(i))
-				b._forEachPageNode(elem.pgid, depth+1, fn)
-			}
-		}
-	} else {
-		if !n.isLeaf {
-			for _, inode := range n.inodes {
-				b._forEachPageNode(inode.pgid, depth+1, fn)
-			}
-		}
-	}
-}
-
-// spill writes all the nodes for this bucket to dirty pages.
-func (b *Bucket) spill() error {
-	// Spill all child buckets first.
-	for name, child := range b.buckets {
-		// If the child bucket is small enough and it has no child buckets then
-		// write it inline into the parent bucket's page. Otherwise spill it
-		// like a normal bucket and make the parent value a pointer to the page.
-		var value []byte
-		if child.inlineable() {
-			child.free()
-			value = child.write()
-		} else {
-			if err := child.spill(); err != nil {
-				return err
-			}
-
-			// Update the child bucket header in this bucket.
-			value = make([]byte, unsafe.Sizeof(bucket{}))
-			var bucket = (*bucket)(unsafe.Pointer(&value[0]))
-			*bucket = *child.bucket
-		}
-
-		// Skip writing the bucket if there are no materialized nodes.
-		if child.rootNode == nil {
-			continue
-		}
-
-		// Update parent node.
-		var c = b.Cursor()
-		k, _, flags := c.seek([]byte(name))
-		if !bytes.Equal([]byte(name), k) {
-			panic(fmt.Sprintf("misplaced bucket header: %x -> %x", []byte(name), k))
-		}
-		if flags&bucketLeafFlag == 0 {
-			panic(fmt.Sprintf("unexpected bucket header flag: %x", flags))
-		}
-		c.node().put([]byte(name), []byte(name), value, 0, bucketLeafFlag)
-	}
-
-	// Ignore if there's not a materialized root node.
-	if b.rootNode == nil {
-		return nil
-	}
-
-	// Spill nodes.
-	if err := b.rootNode.spill(); err != nil {
-		return err
-	}
-	b.rootNode = b.rootNode.root()
-
-	// Update the root node for this bucket.
-	if b.rootNode.pgid >= b.tx.meta.pgid {
-		panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", b.rootNode.pgid, b.tx.meta.pgid))
-	}
-	b.root = b.rootNode.pgid
-
-	return nil
-}
-
-// inlineable returns true if a bucket is small enough to be written inline
-// and if it contains no subbuckets. Otherwise returns false.
-func (b *Bucket) inlineable() bool {
-	var n = b.rootNode
-
-	// Bucket must only contain a single leaf node.
-	if n == nil || !n.isLeaf {
-		return false
-	}
-
-	// Bucket is not inlineable if it contains subbuckets or if it goes beyond
-	// our threshold for inline bucket size.
-	var size = pageHeaderSize
-	for _, inode := range n.inodes {
-		size += leafPageElementSize + len(inode.key) + len(inode.value)
-
-		if inode.flags&bucketLeafFlag != 0 {
-			return false
-		} else if size > b.maxInlineBucketSize() {
-			return false
-		}
-	}
-
-	return true
-}
-
-// Returns the maximum total size of a bucket to make it a candidate for inlining.
-func (b *Bucket) maxInlineBucketSize() int {
-	return b.tx.db.pageSize / 4
-}
-
-// write allocates and writes a bucket to a byte slice.
-func (b *Bucket) write() []byte {
-	// Allocate the appropriate size.
-	var n = b.rootNode
-	var value = make([]byte, bucketHeaderSize+n.size())
-
-	// Write a bucket header.
-	var bucket = (*bucket)(unsafe.Pointer(&value[0]))
-	*bucket = *b.bucket
-
-	// Convert byte slice to a fake page and write the root node.
-	var p = (*page)(unsafe.Pointer(&value[bucketHeaderSize]))
-	n.write(p)
-
-	return value
-}
-
-// rebalance attempts to balance all nodes.
-func (b *Bucket) rebalance() {
-	for _, n := range b.nodes {
-		n.rebalance()
-	}
-	for _, child := range b.buckets {
-		child.rebalance()
-	}
-}
-
-// node creates a node from a page and associates it with a given parent.
-func (b *Bucket) node(pgid pgid, parent *node) *node {
-	_assert(b.nodes != nil, "nodes map expected")
-
-	// Retrieve node if it's already been created.
-	if n := b.nodes[pgid]; n != nil {
-		return n
-	}
-
-	// Otherwise create a node and cache it.
-	n := &node{bucket: b, parent: parent}
-	if parent == nil {
-		b.rootNode = n
-	} else {
-		parent.children = append(parent.children, n)
-	}
-
-	// Use the inline page if this is an inline bucket.
-	var p = b.page
-	if p == nil {
-		p = b.tx.page(pgid)
-	}
-
-	// Read the page into the node and cache it.
-	n.read(p)
-	b.nodes[pgid] = n
-
-	// Update statistics.
-	b.tx.stats.NodeCount++
-
-	return n
-}
-
-// free recursively frees all pages in the bucket.
-func (b *Bucket) free() {
-	if b.root == 0 {
-		return
-	}
-
-	var tx = b.tx
-	b.forEachPageNode(func(p *page, n *node, _ int) {
-		if p != nil {
-			tx.db.freelist.free(tx.meta.txid, p)
-		} else {
-			n.free()
-		}
-	})
-	b.root = 0
-}
-
-// dereference removes all references to the old mmap.
-func (b *Bucket) dereference() {
-	if b.rootNode != nil {
-		b.rootNode.root().dereference()
-	}
-
-	for _, child := range b.buckets {
-		child.dereference()
-	}
-}
-
-// pageNode returns the in-memory node, if it exists.
-// Otherwise returns the underlying page.
-func (b *Bucket) pageNode(id pgid) (*page, *node) {
-	// Inline buckets have a fake page embedded in their value so treat them
-	// differently. We'll return the rootNode (if available) or the fake page.
-	if b.root == 0 {
-		if id != 0 {
-			panic(fmt.Sprintf("inline bucket non-zero page access(2): %d != 0", id))
-		}
-		if b.rootNode != nil {
-			return nil, b.rootNode
-		}
-		return b.page, nil
-	}
-
-	// Check the node cache for non-inline buckets.
-	if b.nodes != nil {
-		if n := b.nodes[id]; n != nil {
-			return nil, n
-		}
-	}
-
-	// Finally lookup the page from the transaction if no node is materialized.
-	return b.tx.page(id), nil
-}
-
-// BucketStats records statistics about resources used by a bucket.
-type BucketStats struct {
-	// Page count statistics.
-	BranchPageN     int // number of logical branch pages
-	BranchOverflowN int // number of physical branch overflow pages
-	LeafPageN       int // number of logical leaf pages
-	LeafOverflowN   int // number of physical leaf overflow pages
-
-	// Tree statistics.
-	KeyN  int // number of keys/value pairs
-	Depth int // number of levels in B+tree
-
-	// Page size utilization.
-	BranchAlloc int // bytes allocated for physical branch pages
-	BranchInuse int // bytes actually used for branch data
-	LeafAlloc   int // bytes allocated for physical leaf pages
-	LeafInuse   int // bytes actually used for leaf data
-
-	// Bucket statistics
-	BucketN           int // total number of buckets including the top bucket
-	InlineBucketN     int // total number on inlined buckets
-	InlineBucketInuse int // bytes used for inlined buckets (also accounted for in LeafInuse)
-}
-
-func (s *BucketStats) Add(other BucketStats) {
-	s.BranchPageN += other.BranchPageN
-	s.BranchOverflowN += other.BranchOverflowN
-	s.LeafPageN += other.LeafPageN
-	s.LeafOverflowN += other.LeafOverflowN
-	s.KeyN += other.KeyN
-	if s.Depth < other.Depth {
-		s.Depth = other.Depth
-	}
-	s.BranchAlloc += other.BranchAlloc
-	s.BranchInuse += other.BranchInuse
-	s.LeafAlloc += other.LeafAlloc
-	s.LeafInuse += other.LeafInuse
-
-	s.BucketN += other.BucketN
-	s.InlineBucketN += other.InlineBucketN
-	s.InlineBucketInuse += other.InlineBucketInuse
-}
-
-// cloneBytes returns a copy of a given slice.
-func cloneBytes(v []byte) []byte {
-	var clone = make([]byte, len(v))
-	copy(clone, v)
-	return clone
-}

+ 0 - 400
vendor/github.com/boltdb/bolt/cursor.go

@@ -1,400 +0,0 @@
-package bolt
-
-import (
-	"bytes"
-	"fmt"
-	"sort"
-)
-
-// Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order.
-// Cursors see nested buckets with value == nil.
-// Cursors can be obtained from a transaction and are valid as long as the transaction is open.
-//
-// Keys and values returned from the cursor are only valid for the life of the transaction.
-//
-// Changing data while traversing with a cursor may cause it to be invalidated
-// and return unexpected keys and/or values. You must reposition your cursor
-// after mutating data.
-type Cursor struct {
-	bucket *Bucket
-	stack  []elemRef
-}
-
-// Bucket returns the bucket that this cursor was created from.
-func (c *Cursor) Bucket() *Bucket {
-	return c.bucket
-}
-
-// First moves the cursor to the first item in the bucket and returns its key and value.
-// If the bucket is empty then a nil key and value are returned.
-// The returned key and value are only valid for the life of the transaction.
-func (c *Cursor) First() (key []byte, value []byte) {
-	_assert(c.bucket.tx.db != nil, "tx closed")
-	c.stack = c.stack[:0]
-	p, n := c.bucket.pageNode(c.bucket.root)
-	c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
-	c.first()
-
-	// If we land on an empty page then move to the next value.
-	// https://github.com/boltdb/bolt/issues/450
-	if c.stack[len(c.stack)-1].count() == 0 {
-		c.next()
-	}
-
-	k, v, flags := c.keyValue()
-	if (flags & uint32(bucketLeafFlag)) != 0 {
-		return k, nil
-	}
-	return k, v
-
-}
-
-// Last moves the cursor to the last item in the bucket and returns its key and value.
-// If the bucket is empty then a nil key and value are returned.
-// The returned key and value are only valid for the life of the transaction.
-func (c *Cursor) Last() (key []byte, value []byte) {
-	_assert(c.bucket.tx.db != nil, "tx closed")
-	c.stack = c.stack[:0]
-	p, n := c.bucket.pageNode(c.bucket.root)
-	ref := elemRef{page: p, node: n}
-	ref.index = ref.count() - 1
-	c.stack = append(c.stack, ref)
-	c.last()
-	k, v, flags := c.keyValue()
-	if (flags & uint32(bucketLeafFlag)) != 0 {
-		return k, nil
-	}
-	return k, v
-}
-
-// Next moves the cursor to the next item in the bucket and returns its key and value.
-// If the cursor is at the end of the bucket then a nil key and value are returned.
-// The returned key and value are only valid for the life of the transaction.
-func (c *Cursor) Next() (key []byte, value []byte) {
-	_assert(c.bucket.tx.db != nil, "tx closed")
-	k, v, flags := c.next()
-	if (flags & uint32(bucketLeafFlag)) != 0 {
-		return k, nil
-	}
-	return k, v
-}
-
-// Prev moves the cursor to the previous item in the bucket and returns its key and value.
-// If the cursor is at the beginning of the bucket then a nil key and value are returned.
-// The returned key and value are only valid for the life of the transaction.
-func (c *Cursor) Prev() (key []byte, value []byte) {
-	_assert(c.bucket.tx.db != nil, "tx closed")
-
-	// Attempt to move back one element until we're successful.
-	// Move up the stack as we hit the beginning of each page in our stack.
-	for i := len(c.stack) - 1; i >= 0; i-- {
-		elem := &c.stack[i]
-		if elem.index > 0 {
-			elem.index--
-			break
-		}
-		c.stack = c.stack[:i]
-	}
-
-	// If we've hit the end then return nil.
-	if len(c.stack) == 0 {
-		return nil, nil
-	}
-
-	// Move down the stack to find the last element of the last leaf under this branch.
-	c.last()
-	k, v, flags := c.keyValue()
-	if (flags & uint32(bucketLeafFlag)) != 0 {
-		return k, nil
-	}
-	return k, v
-}
-
-// Seek moves the cursor to a given key and returns it.
-// If the key does not exist then the next key is used. If no keys
-// follow, a nil key is returned.
-// The returned key and value are only valid for the life of the transaction.
-func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
-	k, v, flags := c.seek(seek)
-
-	// If we ended up after the last element of a page then move to the next one.
-	if ref := &c.stack[len(c.stack)-1]; ref.index >= ref.count() {
-		k, v, flags = c.next()
-	}
-
-	if k == nil {
-		return nil, nil
-	} else if (flags & uint32(bucketLeafFlag)) != 0 {
-		return k, nil
-	}
-	return k, v
-}
-
-// Delete removes the current key/value under the cursor from the bucket.
-// Delete fails if current key/value is a bucket or if the transaction is not writable.
-func (c *Cursor) Delete() error {
-	if c.bucket.tx.db == nil {
-		return ErrTxClosed
-	} else if !c.bucket.Writable() {
-		return ErrTxNotWritable
-	}
-
-	key, _, flags := c.keyValue()
-	// Return an error if current value is a bucket.
-	if (flags & bucketLeafFlag) != 0 {
-		return ErrIncompatibleValue
-	}
-	c.node().del(key)
-
-	return nil
-}
-
-// seek moves the cursor to a given key and returns it.
-// If the key does not exist then the next key is used.
-func (c *Cursor) seek(seek []byte) (key []byte, value []byte, flags uint32) {
-	_assert(c.bucket.tx.db != nil, "tx closed")
-
-	// Start from root page/node and traverse to correct page.
-	c.stack = c.stack[:0]
-	c.search(seek, c.bucket.root)
-	ref := &c.stack[len(c.stack)-1]
-
-	// If the cursor is pointing to the end of page/node then return nil.
-	if ref.index >= ref.count() {
-		return nil, nil, 0
-	}
-
-	// If this is a bucket then return a nil value.
-	return c.keyValue()
-}
-
-// first moves the cursor to the first leaf element under the last page in the stack.
-func (c *Cursor) first() {
-	for {
-		// Exit when we hit a leaf page.
-		var ref = &c.stack[len(c.stack)-1]
-		if ref.isLeaf() {
-			break
-		}
-
-		// Keep adding pages pointing to the first element to the stack.
-		var pgid pgid
-		if ref.node != nil {
-			pgid = ref.node.inodes[ref.index].pgid
-		} else {
-			pgid = ref.page.branchPageElement(uint16(ref.index)).pgid
-		}
-		p, n := c.bucket.pageNode(pgid)
-		c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
-	}
-}
-
-// last moves the cursor to the last leaf element under the last page in the stack.
-func (c *Cursor) last() {
-	for {
-		// Exit when we hit a leaf page.
-		ref := &c.stack[len(c.stack)-1]
-		if ref.isLeaf() {
-			break
-		}
-
-		// Keep adding pages pointing to the last element in the stack.
-		var pgid pgid
-		if ref.node != nil {
-			pgid = ref.node.inodes[ref.index].pgid
-		} else {
-			pgid = ref.page.branchPageElement(uint16(ref.index)).pgid
-		}
-		p, n := c.bucket.pageNode(pgid)
-
-		var nextRef = elemRef{page: p, node: n}
-		nextRef.index = nextRef.count() - 1
-		c.stack = append(c.stack, nextRef)
-	}
-}
-
-// next moves to the next leaf element and returns the key and value.
-// If the cursor is at the last leaf element then it stays there and returns nil.
-func (c *Cursor) next() (key []byte, value []byte, flags uint32) {
-	for {
-		// Attempt to move over one element until we're successful.
-		// Move up the stack as we hit the end of each page in our stack.
-		var i int
-		for i = len(c.stack) - 1; i >= 0; i-- {
-			elem := &c.stack[i]
-			if elem.index < elem.count()-1 {
-				elem.index++
-				break
-			}
-		}
-
-		// If we've hit the root page then stop and return. This will leave the
-		// cursor on the last element of the last page.
-		if i == -1 {
-			return nil, nil, 0
-		}
-
-		// Otherwise start from where we left off in the stack and find the
-		// first element of the first leaf page.
-		c.stack = c.stack[:i+1]
-		c.first()
-
-		// If this is an empty page then restart and move back up the stack.
-		// https://github.com/boltdb/bolt/issues/450
-		if c.stack[len(c.stack)-1].count() == 0 {
-			continue
-		}
-
-		return c.keyValue()
-	}
-}
-
-// search recursively performs a binary search against a given page/node until it finds a given key.
-func (c *Cursor) search(key []byte, pgid pgid) {
-	p, n := c.bucket.pageNode(pgid)
-	if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 {
-		panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags))
-	}
-	e := elemRef{page: p, node: n}
-	c.stack = append(c.stack, e)
-
-	// If we're on a leaf page/node then find the specific node.
-	if e.isLeaf() {
-		c.nsearch(key)
-		return
-	}
-
-	if n != nil {
-		c.searchNode(key, n)
-		return
-	}
-	c.searchPage(key, p)
-}
-
-func (c *Cursor) searchNode(key []byte, n *node) {
-	var exact bool
-	index := sort.Search(len(n.inodes), func(i int) bool {
-		// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
-		// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
-		ret := bytes.Compare(n.inodes[i].key, key)
-		if ret == 0 {
-			exact = true
-		}
-		return ret != -1
-	})
-	if !exact && index > 0 {
-		index--
-	}
-	c.stack[len(c.stack)-1].index = index
-
-	// Recursively search to the next page.
-	c.search(key, n.inodes[index].pgid)
-}
-
-func (c *Cursor) searchPage(key []byte, p *page) {
-	// Binary search for the correct range.
-	inodes := p.branchPageElements()
-
-	var exact bool
-	index := sort.Search(int(p.count), func(i int) bool {
-		// TODO(benbjohnson): Optimize this range search. It's a bit hacky right now.
-		// sort.Search() finds the lowest index where f() != -1 but we need the highest index.
-		ret := bytes.Compare(inodes[i].key(), key)
-		if ret == 0 {
-			exact = true
-		}
-		return ret != -1
-	})
-	if !exact && index > 0 {
-		index--
-	}
-	c.stack[len(c.stack)-1].index = index
-
-	// Recursively search to the next page.
-	c.search(key, inodes[index].pgid)
-}
-
-// nsearch searches the leaf node on the top of the stack for a key.
-func (c *Cursor) nsearch(key []byte) {
-	e := &c.stack[len(c.stack)-1]
-	p, n := e.page, e.node
-
-	// If we have a node then search its inodes.
-	if n != nil {
-		index := sort.Search(len(n.inodes), func(i int) bool {
-			return bytes.Compare(n.inodes[i].key, key) != -1
-		})
-		e.index = index
-		return
-	}
-
-	// If we have a page then search its leaf elements.
-	inodes := p.leafPageElements()
-	index := sort.Search(int(p.count), func(i int) bool {
-		return bytes.Compare(inodes[i].key(), key) != -1
-	})
-	e.index = index
-}
-
-// keyValue returns the key and value of the current leaf element.
-func (c *Cursor) keyValue() ([]byte, []byte, uint32) {
-	ref := &c.stack[len(c.stack)-1]
-	if ref.count() == 0 || ref.index >= ref.count() {
-		return nil, nil, 0
-	}
-
-	// Retrieve value from node.
-	if ref.node != nil {
-		inode := &ref.node.inodes[ref.index]
-		return inode.key, inode.value, inode.flags
-	}
-
-	// Or retrieve value from page.
-	elem := ref.page.leafPageElement(uint16(ref.index))
-	return elem.key(), elem.value(), elem.flags
-}
-
-// node returns the node that the cursor is currently positioned on.
-func (c *Cursor) node() *node {
-	_assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
-
-	// If the top of the stack is a leaf node then just return it.
-	if ref := &c.stack[len(c.stack)-1]; ref.node != nil && ref.isLeaf() {
-		return ref.node
-	}
-
-	// Start from root and traverse down the hierarchy.
-	var n = c.stack[0].node
-	if n == nil {
-		n = c.bucket.node(c.stack[0].page.id, nil)
-	}
-	for _, ref := range c.stack[:len(c.stack)-1] {
-		_assert(!n.isLeaf, "expected branch node")
-		n = n.childAt(int(ref.index))
-	}
-	_assert(n.isLeaf, "expected leaf node")
-	return n
-}
-
-// elemRef represents a reference to an element on a given page/node.
-type elemRef struct {
-	page  *page
-	node  *node
-	index int
-}
-
-// isLeaf returns whether the ref is pointing at a leaf page/node.
-func (r *elemRef) isLeaf() bool {
-	if r.node != nil {
-		return r.node.isLeaf
-	}
-	return (r.page.flags & leafPageFlag) != 0
-}
-
-// count returns the number of inodes or page elements.
-func (r *elemRef) count() int {
-	if r.node != nil {
-		return len(r.node.inodes)
-	}
-	return int(r.page.count)
-}

+ 0 - 1039
vendor/github.com/boltdb/bolt/db.go

@@ -1,1039 +0,0 @@
-package bolt
-
-import (
-	"errors"
-	"fmt"
-	"hash/fnv"
-	"log"
-	"os"
-	"runtime"
-	"runtime/debug"
-	"strings"
-	"sync"
-	"time"
-	"unsafe"
-)
-
-// The largest step that can be taken when remapping the mmap.
-const maxMmapStep = 1 << 30 // 1GB
-
-// The data file format version.
-const version = 2
-
-// Represents a marker value to indicate that a file is a Bolt DB.
-const magic uint32 = 0xED0CDAED
-
-// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
-// syncing changes to a file.  This is required as some operating systems,
-// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
-// must be synchronized using the msync(2) syscall.
-const IgnoreNoSync = runtime.GOOS == "openbsd"
-
-// Default values if not set in a DB instance.
-const (
-	DefaultMaxBatchSize  int = 1000
-	DefaultMaxBatchDelay     = 10 * time.Millisecond
-	DefaultAllocSize         = 16 * 1024 * 1024
-)
-
-// default page size for db is set to the OS page size.
-var defaultPageSize = os.Getpagesize()
-
-// DB represents a collection of buckets persisted to a file on disk.
-// All data access is performed through transactions which can be obtained through the DB.
-// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
-type DB struct {
-	// When enabled, the database will perform a Check() after every commit.
-	// A panic is issued if the database is in an inconsistent state. This
-	// flag has a large performance impact so it should only be used for
-	// debugging purposes.
-	StrictMode bool
-
-	// Setting the NoSync flag will cause the database to skip fsync()
-	// calls after each commit. This can be useful when bulk loading data
-	// into a database and you can restart the bulk load in the event of
-	// a system failure or database corruption. Do not set this flag for
-	// normal use.
-	//
-	// If the package global IgnoreNoSync constant is true, this value is
-	// ignored.  See the comment on that constant for more details.
-	//
-	// THIS IS UNSAFE. PLEASE USE WITH CAUTION.
-	NoSync bool
-
-	// When true, skips the truncate call when growing the database.
-	// Setting this to true is only safe on non-ext3/ext4 systems.
-	// Skipping truncation avoids preallocation of hard drive space and
-	// bypasses a truncate() and fsync() syscall on remapping.
-	//
-	// https://github.com/boltdb/bolt/issues/284
-	NoGrowSync bool
-
-	// If you want to read the entire database fast, you can set MmapFlag to
-	// syscall.MAP_POPULATE on Linux 2.6.23+ for sequential read-ahead.
-	MmapFlags int
-
-	// MaxBatchSize is the maximum size of a batch. Default value is
-	// copied from DefaultMaxBatchSize in Open.
-	//
-	// If <=0, disables batching.
-	//
-	// Do not change concurrently with calls to Batch.
-	MaxBatchSize int
-
-	// MaxBatchDelay is the maximum delay before a batch starts.
-	// Default value is copied from DefaultMaxBatchDelay in Open.
-	//
-	// If <=0, effectively disables batching.
-	//
-	// Do not change concurrently with calls to Batch.
-	MaxBatchDelay time.Duration
-
-	// AllocSize is the amount of space allocated when the database
-	// needs to create new pages. This is done to amortize the cost
-	// of truncate() and fsync() when growing the data file.
-	AllocSize int
-
-	path     string
-	file     *os.File
-	lockfile *os.File // windows only
-	dataref  []byte   // mmap'ed readonly, write throws SEGV
-	data     *[maxMapSize]byte
-	datasz   int
-	filesz   int // current on disk file size
-	meta0    *meta
-	meta1    *meta
-	pageSize int
-	opened   bool
-	rwtx     *Tx
-	txs      []*Tx
-	freelist *freelist
-	stats    Stats
-
-	pagePool sync.Pool
-
-	batchMu sync.Mutex
-	batch   *batch
-
-	rwlock   sync.Mutex   // Allows only one writer at a time.
-	metalock sync.Mutex   // Protects meta page access.
-	mmaplock sync.RWMutex // Protects mmap access during remapping.
-	statlock sync.RWMutex // Protects stats access.
-
-	ops struct {
-		writeAt func(b []byte, off int64) (n int, err error)
-	}
-
-	// Read only mode.
-	// When true, Update() and Begin(true) return ErrDatabaseReadOnly immediately.
-	readOnly bool
-}
-
-// Path returns the path to currently open database file.
-func (db *DB) Path() string {
-	return db.path
-}
-
-// GoString returns the Go string representation of the database.
-func (db *DB) GoString() string {
-	return fmt.Sprintf("bolt.DB{path:%q}", db.path)
-}
-
-// String returns the string representation of the database.
-func (db *DB) String() string {
-	return fmt.Sprintf("DB<%q>", db.path)
-}
-
-// Open creates and opens a database at the given path.
-// If the file does not exist then it will be created automatically.
-// Passing in nil options will cause Bolt to open the database with the default options.
-func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
-	var db = &DB{opened: true}
-
-	// Set default options if no options are provided.
-	if options == nil {
-		options = DefaultOptions
-	}
-	db.NoGrowSync = options.NoGrowSync
-	db.MmapFlags = options.MmapFlags
-
-	// Set default values for later DB operations.
-	db.MaxBatchSize = DefaultMaxBatchSize
-	db.MaxBatchDelay = DefaultMaxBatchDelay
-	db.AllocSize = DefaultAllocSize
-
-	flag := os.O_RDWR
-	if options.ReadOnly {
-		flag = os.O_RDONLY
-		db.readOnly = true
-	}
-
-	// Open data file and separate sync handler for metadata writes.
-	db.path = path
-	var err error
-	if db.file, err = os.OpenFile(db.path, flag|os.O_CREATE, mode); err != nil {
-		_ = db.close()
-		return nil, err
-	}
-
-	// Lock file so that other processes using Bolt in read-write mode cannot
-	// use the database  at the same time. This would cause corruption since
-	// the two processes would write meta pages and free pages separately.
-	// The database file is locked exclusively (only one process can grab the lock)
-	// if !options.ReadOnly.
-	// The database file is locked using the shared lock (more than one process may
-	// hold a lock at the same time) otherwise (options.ReadOnly is set).
-	if err := flock(db, mode, !db.readOnly, options.Timeout); err != nil {
-		_ = db.close()
-		return nil, err
-	}
-
-	// Default values for test hooks
-	db.ops.writeAt = db.file.WriteAt
-
-	// Initialize the database if it doesn't exist.
-	if info, err := db.file.Stat(); err != nil {
-		return nil, err
-	} else if info.Size() == 0 {
-		// Initialize new files with meta pages.
-		if err := db.init(); err != nil {
-			return nil, err
-		}
-	} else {
-		// Read the first meta page to determine the page size.
-		var buf [0x1000]byte
-		if _, err := db.file.ReadAt(buf[:], 0); err == nil {
-			m := db.pageInBuffer(buf[:], 0).meta()
-			if err := m.validate(); err != nil {
-				// If we can't read the page size, we can assume it's the same
-				// as the OS -- since that's how the page size was chosen in the
-				// first place.
-				//
-				// If the first page is invalid and this OS uses a different
-				// page size than what the database was created with then we
-				// are out of luck and cannot access the database.
-				db.pageSize = os.Getpagesize()
-			} else {
-				db.pageSize = int(m.pageSize)
-			}
-		}
-	}
-
-	// Initialize page pool.
-	db.pagePool = sync.Pool{
-		New: func() interface{} {
-			return make([]byte, db.pageSize)
-		},
-	}
-
-	// Memory map the data file.
-	if err := db.mmap(options.InitialMmapSize); err != nil {
-		_ = db.close()
-		return nil, err
-	}
-
-	// Read in the freelist.
-	db.freelist = newFreelist()
-	db.freelist.read(db.page(db.meta().freelist))
-
-	// Mark the database as opened and return.
-	return db, nil
-}
-
-// mmap opens the underlying memory-mapped file and initializes the meta references.
-// minsz is the minimum size that the new mmap can be.
-func (db *DB) mmap(minsz int) error {
-	db.mmaplock.Lock()
-	defer db.mmaplock.Unlock()
-
-	info, err := db.file.Stat()
-	if err != nil {
-		return fmt.Errorf("mmap stat error: %s", err)
-	} else if int(info.Size()) < db.pageSize*2 {
-		return fmt.Errorf("file size too small")
-	}
-
-	// Ensure the size is at least the minimum size.
-	var size = int(info.Size())
-	if size < minsz {
-		size = minsz
-	}
-	size, err = db.mmapSize(size)
-	if err != nil {
-		return err
-	}
-
-	// Dereference all mmap references before unmapping.
-	if db.rwtx != nil {
-		db.rwtx.root.dereference()
-	}
-
-	// Unmap existing data before continuing.
-	if err := db.munmap(); err != nil {
-		return err
-	}
-
-	// Memory-map the data file as a byte slice.
-	if err := mmap(db, size); err != nil {
-		return err
-	}
-
-	// Save references to the meta pages.
-	db.meta0 = db.page(0).meta()
-	db.meta1 = db.page(1).meta()
-
-	// Validate the meta pages. We only return an error if both meta pages fail
-	// validation, since meta0 failing validation means that it wasn't saved
-	// properly -- but we can recover using meta1. And vice-versa.
-	err0 := db.meta0.validate()
-	err1 := db.meta1.validate()
-	if err0 != nil && err1 != nil {
-		return err0
-	}
-
-	return nil
-}
-
-// munmap unmaps the data file from memory.
-func (db *DB) munmap() error {
-	if err := munmap(db); err != nil {
-		return fmt.Errorf("unmap error: " + err.Error())
-	}
-	return nil
-}
-
-// mmapSize determines the appropriate size for the mmap given the current size
-// of the database. The minimum size is 32KB and doubles until it reaches 1GB.
-// Returns an error if the new mmap size is greater than the max allowed.
-func (db *DB) mmapSize(size int) (int, error) {
-	// Double the size from 32KB until 1GB.
-	for i := uint(15); i <= 30; i++ {
-		if size <= 1<<i {
-			return 1 << i, nil
-		}
-	}
-
-	// Verify the requested size is not above the maximum allowed.
-	if size > maxMapSize {
-		return 0, fmt.Errorf("mmap too large")
-	}
-
-	// If larger than 1GB then grow by 1GB at a time.
-	sz := int64(size)
-	if remainder := sz % int64(maxMmapStep); remainder > 0 {
-		sz += int64(maxMmapStep) - remainder
-	}
-
-	// Ensure that the mmap size is a multiple of the page size.
-	// This should always be true since we're incrementing in MBs.
-	pageSize := int64(db.pageSize)
-	if (sz % pageSize) != 0 {
-		sz = ((sz / pageSize) + 1) * pageSize
-	}
-
-	// If we've exceeded the max size then only grow up to the max size.
-	if sz > maxMapSize {
-		sz = maxMapSize
-	}
-
-	return int(sz), nil
-}
-
-// init creates a new database file and initializes its meta pages.
-func (db *DB) init() error {
-	// Set the page size to the OS page size.
-	db.pageSize = os.Getpagesize()
-
-	// Create two meta pages on a buffer.
-	buf := make([]byte, db.pageSize*4)
-	for i := 0; i < 2; i++ {
-		p := db.pageInBuffer(buf[:], pgid(i))
-		p.id = pgid(i)
-		p.flags = metaPageFlag
-
-		// Initialize the meta page.
-		m := p.meta()
-		m.magic = magic
-		m.version = version
-		m.pageSize = uint32(db.pageSize)
-		m.freelist = 2
-		m.root = bucket{root: 3}
-		m.pgid = 4
-		m.txid = txid(i)
-		m.checksum = m.sum64()
-	}
-
-	// Write an empty freelist at page 3.
-	p := db.pageInBuffer(buf[:], pgid(2))
-	p.id = pgid(2)
-	p.flags = freelistPageFlag
-	p.count = 0
-
-	// Write an empty leaf page at page 4.
-	p = db.pageInBuffer(buf[:], pgid(3))
-	p.id = pgid(3)
-	p.flags = leafPageFlag
-	p.count = 0
-
-	// Write the buffer to our data file.
-	if _, err := db.ops.writeAt(buf, 0); err != nil {
-		return err
-	}
-	if err := fdatasync(db); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// Close releases all database resources.
-// All transactions must be closed before closing the database.
-func (db *DB) Close() error {
-	db.rwlock.Lock()
-	defer db.rwlock.Unlock()
-
-	db.metalock.Lock()
-	defer db.metalock.Unlock()
-
-	db.mmaplock.RLock()
-	defer db.mmaplock.RUnlock()
-
-	return db.close()
-}
-
-func (db *DB) close() error {
-	if !db.opened {
-		return nil
-	}
-
-	db.opened = false
-
-	db.freelist = nil
-
-	// Clear ops.
-	db.ops.writeAt = nil
-
-	// Close the mmap.
-	if err := db.munmap(); err != nil {
-		return err
-	}
-
-	// Close file handles.
-	if db.file != nil {
-		// No need to unlock read-only file.
-		if !db.readOnly {
-			// Unlock the file.
-			if err := funlock(db); err != nil {
-				log.Printf("bolt.Close(): funlock error: %s", err)
-			}
-		}
-
-		// Close the file descriptor.
-		if err := db.file.Close(); err != nil {
-			return fmt.Errorf("db file close: %s", err)
-		}
-		db.file = nil
-	}
-
-	db.path = ""
-	return nil
-}
-
-// Begin starts a new transaction.
-// Multiple read-only transactions can be used concurrently but only one
-// write transaction can be used at a time. Starting multiple write transactions
-// will cause the calls to block and be serialized until the current write
-// transaction finishes.
-//
-// Transactions should not be dependent on one another. Opening a read
-// transaction and a write transaction in the same goroutine can cause the
-// writer to deadlock because the database periodically needs to re-mmap itself
-// as it grows and it cannot do that while a read transaction is open.
-//
-// If a long running read transaction (for example, a snapshot transaction) is
-// needed, you might want to set DB.InitialMmapSize to a large enough value
-// to avoid potential blocking of write transaction.
-//
-// IMPORTANT: You must close read-only transactions after you are finished or
-// else the database will not reclaim old pages.
-func (db *DB) Begin(writable bool) (*Tx, error) {
-	if writable {
-		return db.beginRWTx()
-	}
-	return db.beginTx()
-}
-
-func (db *DB) beginTx() (*Tx, error) {
-	// Lock the meta pages while we initialize the transaction. We obtain
-	// the meta lock before the mmap lock because that's the order that the
-	// write transaction will obtain them.
-	db.metalock.Lock()
-
-	// Obtain a read-only lock on the mmap. When the mmap is remapped it will
-	// obtain a write lock so all transactions must finish before it can be
-	// remapped.
-	db.mmaplock.RLock()
-
-	// Exit if the database is not open yet.
-	if !db.opened {
-		db.mmaplock.RUnlock()
-		db.metalock.Unlock()
-		return nil, ErrDatabaseNotOpen
-	}
-
-	// Create a transaction associated with the database.
-	t := &Tx{}
-	t.init(db)
-
-	// Keep track of transaction until it closes.
-	db.txs = append(db.txs, t)
-	n := len(db.txs)
-
-	// Unlock the meta pages.
-	db.metalock.Unlock()
-
-	// Update the transaction stats.
-	db.statlock.Lock()
-	db.stats.TxN++
-	db.stats.OpenTxN = n
-	db.statlock.Unlock()
-
-	return t, nil
-}
-
-func (db *DB) beginRWTx() (*Tx, error) {
-	// If the database was opened with Options.ReadOnly, return an error.
-	if db.readOnly {
-		return nil, ErrDatabaseReadOnly
-	}
-
-	// Obtain writer lock. This is released by the transaction when it closes.
-	// This enforces only one writer transaction at a time.
-	db.rwlock.Lock()
-
-	// Once we have the writer lock then we can lock the meta pages so that
-	// we can set up the transaction.
-	db.metalock.Lock()
-	defer db.metalock.Unlock()
-
-	// Exit if the database is not open yet.
-	if !db.opened {
-		db.rwlock.Unlock()
-		return nil, ErrDatabaseNotOpen
-	}
-
-	// Create a transaction associated with the database.
-	t := &Tx{writable: true}
-	t.init(db)
-	db.rwtx = t
-
-	// Free any pages associated with closed read-only transactions.
-	var minid txid = 0xFFFFFFFFFFFFFFFF
-	for _, t := range db.txs {
-		if t.meta.txid < minid {
-			minid = t.meta.txid
-		}
-	}
-	if minid > 0 {
-		db.freelist.release(minid - 1)
-	}
-
-	return t, nil
-}
-
-// removeTx removes a transaction from the database.
-func (db *DB) removeTx(tx *Tx) {
-	// Release the read lock on the mmap.
-	db.mmaplock.RUnlock()
-
-	// Use the meta lock to restrict access to the DB object.
-	db.metalock.Lock()
-
-	// Remove the transaction.
-	for i, t := range db.txs {
-		if t == tx {
-			last := len(db.txs) - 1
-			db.txs[i] = db.txs[last]
-			db.txs[last] = nil
-			db.txs = db.txs[:last]
-			break
-		}
-	}
-	n := len(db.txs)
-
-	// Unlock the meta pages.
-	db.metalock.Unlock()
-
-	// Merge statistics.
-	db.statlock.Lock()
-	db.stats.OpenTxN = n
-	db.stats.TxStats.add(&tx.stats)
-	db.statlock.Unlock()
-}
-
-// Update executes a function within the context of a read-write managed transaction.
-// If no error is returned from the function then the transaction is committed.
-// If an error is returned then the entire transaction is rolled back.
-// Any error that is returned from the function or returned from the commit is
-// returned from the Update() method.
-//
-// Attempting to manually commit or rollback within the function will cause a panic.
-func (db *DB) Update(fn func(*Tx) error) error {
-	t, err := db.Begin(true)
-	if err != nil {
-		return err
-	}
-
-	// Make sure the transaction rolls back in the event of a panic.
-	defer func() {
-		if t.db != nil {
-			t.rollback()
-		}
-	}()
-
-	// Mark as a managed tx so that the inner function cannot manually commit.
-	t.managed = true
-
-	// If an error is returned from the function then rollback and return error.
-	err = fn(t)
-	t.managed = false
-	if err != nil {
-		_ = t.Rollback()
-		return err
-	}
-
-	return t.Commit()
-}
-
-// View executes a function within the context of a managed read-only transaction.
-// Any error that is returned from the function is returned from the View() method.
-//
-// Attempting to manually rollback within the function will cause a panic.
-func (db *DB) View(fn func(*Tx) error) error {
-	t, err := db.Begin(false)
-	if err != nil {
-		return err
-	}
-
-	// Make sure the transaction rolls back in the event of a panic.
-	defer func() {
-		if t.db != nil {
-			t.rollback()
-		}
-	}()
-
-	// Mark as a managed tx so that the inner function cannot manually rollback.
-	t.managed = true
-
-	// If an error is returned from the function then pass it through.
-	err = fn(t)
-	t.managed = false
-	if err != nil {
-		_ = t.Rollback()
-		return err
-	}
-
-	if err := t.Rollback(); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// Batch calls fn as part of a batch. It behaves similar to Update,
-// except:
-//
-// 1. concurrent Batch calls can be combined into a single Bolt
-// transaction.
-//
-// 2. the function passed to Batch may be called multiple times,
-// regardless of whether it returns error or not.
-//
-// This means that Batch function side effects must be idempotent and
-// take permanent effect only after a successful return is seen in
-// caller.
-//
-// The maximum batch size and delay can be adjusted with DB.MaxBatchSize
-// and DB.MaxBatchDelay, respectively.
-//
-// Batch is only useful when there are multiple goroutines calling it.
-func (db *DB) Batch(fn func(*Tx) error) error {
-	errCh := make(chan error, 1)
-
-	db.batchMu.Lock()
-	if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) {
-		// There is no existing batch, or the existing batch is full; start a new one.
-		db.batch = &batch{
-			db: db,
-		}
-		db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger)
-	}
-	db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh})
-	if len(db.batch.calls) >= db.MaxBatchSize {
-		// wake up batch, it's ready to run
-		go db.batch.trigger()
-	}
-	db.batchMu.Unlock()
-
-	err := <-errCh
-	if err == trySolo {
-		err = db.Update(fn)
-	}
-	return err
-}
-
-type call struct {
-	fn  func(*Tx) error
-	err chan<- error
-}
-
-type batch struct {
-	db    *DB
-	timer *time.Timer
-	start sync.Once
-	calls []call
-}
-
-// trigger runs the batch if it hasn't already been run.
-func (b *batch) trigger() {
-	b.start.Do(b.run)
-}
-
-// run performs the transactions in the batch and communicates results
-// back to DB.Batch.
-func (b *batch) run() {
-	b.db.batchMu.Lock()
-	b.timer.Stop()
-	// Make sure no new work is added to this batch, but don't break
-	// other batches.
-	if b.db.batch == b {
-		b.db.batch = nil
-	}
-	b.db.batchMu.Unlock()
-
-retry:
-	for len(b.calls) > 0 {
-		var failIdx = -1
-		err := b.db.Update(func(tx *Tx) error {
-			for i, c := range b.calls {
-				if err := safelyCall(c.fn, tx); err != nil {
-					failIdx = i
-					return err
-				}
-			}
-			return nil
-		})
-
-		if failIdx >= 0 {
-			// take the failing transaction out of the batch. it's
-			// safe to shorten b.calls here because db.batch no longer
-			// points to us, and we hold the mutex anyway.
-			c := b.calls[failIdx]
-			b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1]
-			// tell the submitter re-run it solo, continue with the rest of the batch
-			c.err <- trySolo
-			continue retry
-		}
-
-		// pass success, or bolt internal errors, to all callers
-		for _, c := range b.calls {
-			if c.err != nil {
-				c.err <- err
-			}
-		}
-		break retry
-	}
-}
-
-// trySolo is a special sentinel error value used for signaling that a
-// transaction function should be re-run. It should never be seen by
-// callers.
-var trySolo = errors.New("batch function returned an error and should be re-run solo")
-
-type panicked struct {
-	reason interface{}
-}
-
-func (p panicked) Error() string {
-	if err, ok := p.reason.(error); ok {
-		return err.Error()
-	}
-	return fmt.Sprintf("panic: %v", p.reason)
-}
-
-func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
-	defer func() {
-		if p := recover(); p != nil {
-			err = panicked{p}
-		}
-	}()
-	return fn(tx)
-}
-
-// Sync executes fdatasync() against the database file handle.
-//
-// This is not necessary under normal operation, however, if you use NoSync
-// then it allows you to force the database file to sync against the disk.
-func (db *DB) Sync() error { return fdatasync(db) }
-
-// Stats retrieves ongoing performance stats for the database.
-// This is only updated when a transaction closes.
-func (db *DB) Stats() Stats {
-	db.statlock.RLock()
-	defer db.statlock.RUnlock()
-	return db.stats
-}
-
-// This is for internal access to the raw data bytes from the C cursor, use
-// carefully, or not at all.
-func (db *DB) Info() *Info {
-	return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize}
-}
-
-// page retrieves a page reference from the mmap based on the current page size.
-func (db *DB) page(id pgid) *page {
-	pos := id * pgid(db.pageSize)
-	return (*page)(unsafe.Pointer(&db.data[pos]))
-}
-
-// pageInBuffer retrieves a page reference from a given byte array based on the current page size.
-func (db *DB) pageInBuffer(b []byte, id pgid) *page {
-	return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)]))
-}
-
-// meta retrieves the current meta page reference.
-func (db *DB) meta() *meta {
-	// We have to return the meta with the highest txid which doesn't fail
-	// validation. Otherwise, we can cause errors when in fact the database is
-	// in a consistent state. metaA is the one with the higher txid.
-	metaA := db.meta0
-	metaB := db.meta1
-	if db.meta1.txid > db.meta0.txid {
-		metaA = db.meta1
-		metaB = db.meta0
-	}
-
-	// Use higher meta page if valid. Otherwise fallback to previous, if valid.
-	if err := metaA.validate(); err == nil {
-		return metaA
-	} else if err := metaB.validate(); err == nil {
-		return metaB
-	}
-
-	// This should never be reached, because both meta1 and meta0 were validated
-	// on mmap() and we do fsync() on every write.
-	panic("bolt.DB.meta(): invalid meta pages")
-}
-
-// allocate returns a contiguous block of memory starting at a given page.
-func (db *DB) allocate(count int) (*page, error) {
-	// Allocate a temporary buffer for the page.
-	var buf []byte
-	if count == 1 {
-		buf = db.pagePool.Get().([]byte)
-	} else {
-		buf = make([]byte, count*db.pageSize)
-	}
-	p := (*page)(unsafe.Pointer(&buf[0]))
-	p.overflow = uint32(count - 1)
-
-	// Use pages from the freelist if they are available.
-	if p.id = db.freelist.allocate(count); p.id != 0 {
-		return p, nil
-	}
-
-	// Resize mmap() if we're at the end.
-	p.id = db.rwtx.meta.pgid
-	var minsz = int((p.id+pgid(count))+1) * db.pageSize
-	if minsz >= db.datasz {
-		if err := db.mmap(minsz); err != nil {
-			return nil, fmt.Errorf("mmap allocate error: %s", err)
-		}
-	}
-
-	// Move the page id high water mark.
-	db.rwtx.meta.pgid += pgid(count)
-
-	return p, nil
-}
-
-// grow grows the size of the database to the given sz.
-func (db *DB) grow(sz int) error {
-	// Ignore if the new size is less than available file size.
-	if sz <= db.filesz {
-		return nil
-	}
-
-	// If the data is smaller than the alloc size then only allocate what's needed.
-	// Once it goes over the allocation size then allocate in chunks.
-	if db.datasz < db.AllocSize {
-		sz = db.datasz
-	} else {
-		sz += db.AllocSize
-	}
-
-	// Truncate and fsync to ensure file size metadata is flushed.
-	// https://github.com/boltdb/bolt/issues/284
-	if !db.NoGrowSync && !db.readOnly {
-		if runtime.GOOS != "windows" {
-			if err := db.file.Truncate(int64(sz)); err != nil {
-				return fmt.Errorf("file resize error: %s", err)
-			}
-		}
-		if err := db.file.Sync(); err != nil {
-			return fmt.Errorf("file sync error: %s", err)
-		}
-	}
-
-	db.filesz = sz
-	return nil
-}
-
-func (db *DB) IsReadOnly() bool {
-	return db.readOnly
-}
-
-// Options represents the options that can be set when opening a database.
-type Options struct {
-	// Timeout is the amount of time to wait to obtain a file lock.
-	// When set to zero it will wait indefinitely. This option is only
-	// available on Darwin and Linux.
-	Timeout time.Duration
-
-	// Sets the DB.NoGrowSync flag before memory mapping the file.
-	NoGrowSync bool
-
-	// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
-	// grab a shared lock (UNIX).
-	ReadOnly bool
-
-	// Sets the DB.MmapFlags flag before memory mapping the file.
-	MmapFlags int
-
-	// InitialMmapSize is the initial mmap size of the database
-	// in bytes. Read transactions won't block write transaction
-	// if the InitialMmapSize is large enough to hold database mmap
-	// size. (See DB.Begin for more information)
-	//
-	// If <=0, the initial map size is 0.
-	// If initialMmapSize is smaller than the previous database size,
-	// it takes no effect.
-	InitialMmapSize int
-}
-
-// DefaultOptions represent the options used if nil options are passed into Open().
-// No timeout is used which will cause Bolt to wait indefinitely for a lock.
-var DefaultOptions = &Options{
-	Timeout:    0,
-	NoGrowSync: false,
-}
-
-// Stats represents statistics about the database.
-type Stats struct {
-	// Freelist stats
-	FreePageN     int // total number of free pages on the freelist
-	PendingPageN  int // total number of pending pages on the freelist
-	FreeAlloc     int // total bytes allocated in free pages
-	FreelistInuse int // total bytes used by the freelist
-
-	// Transaction stats
-	TxN     int // total number of started read transactions
-	OpenTxN int // number of currently open read transactions
-
-	TxStats TxStats // global, ongoing stats.
-}
-
-// Sub calculates and returns the difference between two sets of database stats.
-// This is useful when obtaining stats at two different points and time and
-// you need the performance counters that occurred within that time span.
-func (s *Stats) Sub(other *Stats) Stats {
-	if other == nil {
-		return *s
-	}
-	var diff Stats
-	diff.FreePageN = s.FreePageN
-	diff.PendingPageN = s.PendingPageN
-	diff.FreeAlloc = s.FreeAlloc
-	diff.FreelistInuse = s.FreelistInuse
-	diff.TxN = s.TxN - other.TxN
-	diff.TxStats = s.TxStats.Sub(&other.TxStats)
-	return diff
-}
-
-func (s *Stats) add(other *Stats) {
-	s.TxStats.add(&other.TxStats)
-}
-
-type Info struct {
-	Data     uintptr
-	PageSize int
-}
-
-type meta struct {
-	magic    uint32
-	version  uint32
-	pageSize uint32
-	flags    uint32
-	root     bucket
-	freelist pgid
-	pgid     pgid
-	txid     txid
-	checksum uint64
-}
-
-// validate checks the marker bytes and version of the meta page to ensure it matches this binary.
-func (m *meta) validate() error {
-	if m.magic != magic {
-		return ErrInvalid
-	} else if m.version != version {
-		return ErrVersionMismatch
-	} else if m.checksum != 0 && m.checksum != m.sum64() {
-		return ErrChecksum
-	}
-	return nil
-}
-
-// copy copies one meta object to another.
-func (m *meta) copy(dest *meta) {
-	*dest = *m
-}
-
-// write writes the meta onto a page.
-func (m *meta) write(p *page) {
-	if m.root.root >= m.pgid {
-		panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
-	} else if m.freelist >= m.pgid {
-		panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
-	}
-
-	// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
-	p.id = pgid(m.txid % 2)
-	p.flags |= metaPageFlag
-
-	// Calculate the checksum.
-	m.checksum = m.sum64()
-
-	m.copy(p.meta())
-}
-
-// generates the checksum for the meta.
-func (m *meta) sum64() uint64 {
-	var h = fnv.New64a()
-	_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
-	return h.Sum64()
-}
-
-// _assert will panic with a given formatted message if the given condition is false.
-func _assert(condition bool, msg string, v ...interface{}) {
-	if !condition {
-		panic(fmt.Sprintf("assertion failed: "+msg, v...))
-	}
-}
-
-func warn(v ...interface{})              { fmt.Fprintln(os.Stderr, v...) }
-func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) }
-
-func printstack() {
-	stack := strings.Join(strings.Split(string(debug.Stack()), "\n")[2:], "\n")
-	fmt.Fprintln(os.Stderr, stack)
-}

+ 0 - 44
vendor/github.com/boltdb/bolt/doc.go

@@ -1,44 +0,0 @@
-/*
-Package bolt implements a low-level key/value store in pure Go. It supports
-fully serializable transactions, ACID semantics, and lock-free MVCC with
-multiple readers and a single writer. Bolt can be used for projects that
-want a simple data store without the need to add large dependencies such as
-Postgres or MySQL.
-
-Bolt is a single-level, zero-copy, B+tree data store. This means that Bolt is
-optimized for fast read access and does not require recovery in the event of a
-system crash. Transactions which have not finished committing will simply be
-rolled back in the event of a crash.
-
-The design of Bolt is based on Howard Chu's LMDB database project.
-
-Bolt currently works on Windows, Mac OS X, and Linux.
-
-
-Basics
-
-There are only a few types in Bolt: DB, Bucket, Tx, and Cursor. The DB is
-a collection of buckets and is represented by a single file on disk. A bucket is
-a collection of unique keys that are associated with values.
-
-Transactions provide either read-only or read-write access to the database.
-Read-only transactions can retrieve key/value pairs and can use Cursors to
-iterate over the dataset sequentially. Read-write transactions can create and
-delete buckets and can insert and remove keys. Only one read-write transaction
-is allowed at a time.
-
-
-Caveats
-
-The database uses a read-only, memory-mapped data file to ensure that
-applications cannot corrupt the database, however, this means that keys and
-values returned from Bolt cannot be changed. Writing to a read-only byte slice
-will cause Go to panic.
-
-Keys and values retrieved from the database are only valid for the life of
-the transaction. When used outside the transaction, these byte slices can
-point to different data or can point to invalid memory which will cause a panic.
-
-
-*/
-package bolt

+ 0 - 71
vendor/github.com/boltdb/bolt/errors.go

@@ -1,71 +0,0 @@
-package bolt
-
-import "errors"
-
-// These errors can be returned when opening or calling methods on a DB.
-var (
-	// ErrDatabaseNotOpen is returned when a DB instance is accessed before it
-	// is opened or after it is closed.
-	ErrDatabaseNotOpen = errors.New("database not open")
-
-	// ErrDatabaseOpen is returned when opening a database that is
-	// already open.
-	ErrDatabaseOpen = errors.New("database already open")
-
-	// ErrInvalid is returned when both meta pages on a database are invalid.
-	// This typically occurs when a file is not a bolt database.
-	ErrInvalid = errors.New("invalid database")
-
-	// ErrVersionMismatch is returned when the data file was created with a
-	// different version of Bolt.
-	ErrVersionMismatch = errors.New("version mismatch")
-
-	// ErrChecksum is returned when either meta page checksum does not match.
-	ErrChecksum = errors.New("checksum error")
-
-	// ErrTimeout is returned when a database cannot obtain an exclusive lock
-	// on the data file after the timeout passed to Open().
-	ErrTimeout = errors.New("timeout")
-)
-
-// These errors can occur when beginning or committing a Tx.
-var (
-	// ErrTxNotWritable is returned when performing a write operation on a
-	// read-only transaction.
-	ErrTxNotWritable = errors.New("tx not writable")
-
-	// ErrTxClosed is returned when committing or rolling back a transaction
-	// that has already been committed or rolled back.
-	ErrTxClosed = errors.New("tx closed")
-
-	// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
-	// read-only database.
-	ErrDatabaseReadOnly = errors.New("database is in read-only mode")
-)
-
-// These errors can occur when putting or deleting a value or a bucket.
-var (
-	// ErrBucketNotFound is returned when trying to access a bucket that has
-	// not been created yet.
-	ErrBucketNotFound = errors.New("bucket not found")
-
-	// ErrBucketExists is returned when creating a bucket that already exists.
-	ErrBucketExists = errors.New("bucket already exists")
-
-	// ErrBucketNameRequired is returned when creating a bucket with a blank name.
-	ErrBucketNameRequired = errors.New("bucket name required")
-
-	// ErrKeyRequired is returned when inserting a zero-length key.
-	ErrKeyRequired = errors.New("key required")
-
-	// ErrKeyTooLarge is returned when inserting a key that is larger than MaxKeySize.
-	ErrKeyTooLarge = errors.New("key too large")
-
-	// ErrValueTooLarge is returned when inserting a value that is larger than MaxValueSize.
-	ErrValueTooLarge = errors.New("value too large")
-
-	// ErrIncompatibleValue is returned when trying create or delete a bucket
-	// on an existing non-bucket key or when trying to create or delete a
-	// non-bucket key on an existing bucket key.
-	ErrIncompatibleValue = errors.New("incompatible value")
-)

+ 0 - 252
vendor/github.com/boltdb/bolt/freelist.go

@@ -1,252 +0,0 @@
-package bolt
-
-import (
-	"fmt"
-	"sort"
-	"unsafe"
-)
-
-// freelist represents a list of all pages that are available for allocation.
-// It also tracks pages that have been freed but are still in use by open transactions.
-type freelist struct {
-	ids     []pgid          // all free and available free page ids.
-	pending map[txid][]pgid // mapping of soon-to-be free page ids by tx.
-	cache   map[pgid]bool   // fast lookup of all free and pending page ids.
-}
-
-// newFreelist returns an empty, initialized freelist.
-func newFreelist() *freelist {
-	return &freelist{
-		pending: make(map[txid][]pgid),
-		cache:   make(map[pgid]bool),
-	}
-}
-
-// size returns the size of the page after serialization.
-func (f *freelist) size() int {
-	n := f.count()
-	if n >= 0xFFFF {
-		// The first element will be used to store the count. See freelist.write.
-		n++
-	}
-	return pageHeaderSize + (int(unsafe.Sizeof(pgid(0))) * n)
-}
-
-// count returns count of pages on the freelist
-func (f *freelist) count() int {
-	return f.free_count() + f.pending_count()
-}
-
-// free_count returns count of free pages
-func (f *freelist) free_count() int {
-	return len(f.ids)
-}
-
-// pending_count returns count of pending pages
-func (f *freelist) pending_count() int {
-	var count int
-	for _, list := range f.pending {
-		count += len(list)
-	}
-	return count
-}
-
-// copyall copies into dst a list of all free ids and all pending ids in one sorted list.
-// f.count returns the minimum length required for dst.
-func (f *freelist) copyall(dst []pgid) {
-	m := make(pgids, 0, f.pending_count())
-	for _, list := range f.pending {
-		m = append(m, list...)
-	}
-	sort.Sort(m)
-	mergepgids(dst, f.ids, m)
-}
-
-// allocate returns the starting page id of a contiguous list of pages of a given size.
-// If a contiguous block cannot be found then 0 is returned.
-func (f *freelist) allocate(n int) pgid {
-	if len(f.ids) == 0 {
-		return 0
-	}
-
-	var initial, previd pgid
-	for i, id := range f.ids {
-		if id <= 1 {
-			panic(fmt.Sprintf("invalid page allocation: %d", id))
-		}
-
-		// Reset initial page if this is not contiguous.
-		if previd == 0 || id-previd != 1 {
-			initial = id
-		}
-
-		// If we found a contiguous block then remove it and return it.
-		if (id-initial)+1 == pgid(n) {
-			// If we're allocating off the beginning then take the fast path
-			// and just adjust the existing slice. This will use extra memory
-			// temporarily but the append() in free() will realloc the slice
-			// as is necessary.
-			if (i + 1) == n {
-				f.ids = f.ids[i+1:]
-			} else {
-				copy(f.ids[i-n+1:], f.ids[i+1:])
-				f.ids = f.ids[:len(f.ids)-n]
-			}
-
-			// Remove from the free cache.
-			for i := pgid(0); i < pgid(n); i++ {
-				delete(f.cache, initial+i)
-			}
-
-			return initial
-		}
-
-		previd = id
-	}
-	return 0
-}
-
-// free releases a page and its overflow for a given transaction id.
-// If the page is already free then a panic will occur.
-func (f *freelist) free(txid txid, p *page) {
-	if p.id <= 1 {
-		panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id))
-	}
-
-	// Free page and all its overflow pages.
-	var ids = f.pending[txid]
-	for id := p.id; id <= p.id+pgid(p.overflow); id++ {
-		// Verify that page is not already free.
-		if f.cache[id] {
-			panic(fmt.Sprintf("page %d already freed", id))
-		}
-
-		// Add to the freelist and cache.
-		ids = append(ids, id)
-		f.cache[id] = true
-	}
-	f.pending[txid] = ids
-}
-
-// release moves all page ids for a transaction id (or older) to the freelist.
-func (f *freelist) release(txid txid) {
-	m := make(pgids, 0)
-	for tid, ids := range f.pending {
-		if tid <= txid {
-			// Move transaction's pending pages to the available freelist.
-			// Don't remove from the cache since the page is still free.
-			m = append(m, ids...)
-			delete(f.pending, tid)
-		}
-	}
-	sort.Sort(m)
-	f.ids = pgids(f.ids).merge(m)
-}
-
-// rollback removes the pages from a given pending tx.
-func (f *freelist) rollback(txid txid) {
-	// Remove page ids from cache.
-	for _, id := range f.pending[txid] {
-		delete(f.cache, id)
-	}
-
-	// Remove pages from pending list.
-	delete(f.pending, txid)
-}
-
-// freed returns whether a given page is in the free list.
-func (f *freelist) freed(pgid pgid) bool {
-	return f.cache[pgid]
-}
-
-// read initializes the freelist from a freelist page.
-func (f *freelist) read(p *page) {
-	// If the page.count is at the max uint16 value (64k) then it's considered
-	// an overflow and the size of the freelist is stored as the first element.
-	idx, count := 0, int(p.count)
-	if count == 0xFFFF {
-		idx = 1
-		count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0])
-	}
-
-	// Copy the list of page ids from the freelist.
-	if count == 0 {
-		f.ids = nil
-	} else {
-		ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count]
-		f.ids = make([]pgid, len(ids))
-		copy(f.ids, ids)
-
-		// Make sure they're sorted.
-		sort.Sort(pgids(f.ids))
-	}
-
-	// Rebuild the page cache.
-	f.reindex()
-}
-
-// write writes the page ids onto a freelist page. All free and pending ids are
-// saved to disk since in the event of a program crash, all pending ids will
-// become free.
-func (f *freelist) write(p *page) error {
-	// Combine the old free pgids and pgids waiting on an open transaction.
-
-	// Update the header flag.
-	p.flags |= freelistPageFlag
-
-	// The page.count can only hold up to 64k elements so if we overflow that
-	// number then we handle it by putting the size in the first element.
-	lenids := f.count()
-	if lenids == 0 {
-		p.count = uint16(lenids)
-	} else if lenids < 0xFFFF {
-		p.count = uint16(lenids)
-		f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:])
-	} else {
-		p.count = 0xFFFF
-		((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0] = pgid(lenids)
-		f.copyall(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[1:])
-	}
-
-	return nil
-}
-
-// reload reads the freelist from a page and filters out pending items.
-func (f *freelist) reload(p *page) {
-	f.read(p)
-
-	// Build a cache of only pending pages.
-	pcache := make(map[pgid]bool)
-	for _, pendingIDs := range f.pending {
-		for _, pendingID := range pendingIDs {
-			pcache[pendingID] = true
-		}
-	}
-
-	// Check each page in the freelist and build a new available freelist
-	// with any pages not in the pending lists.
-	var a []pgid
-	for _, id := range f.ids {
-		if !pcache[id] {
-			a = append(a, id)
-		}
-	}
-	f.ids = a
-
-	// Once the available list is rebuilt then rebuild the free cache so that
-	// it includes the available and pending free pages.
-	f.reindex()
-}
-
-// reindex rebuilds the free cache based on available and pending free lists.
-func (f *freelist) reindex() {
-	f.cache = make(map[pgid]bool, len(f.ids))
-	for _, id := range f.ids {
-		f.cache[id] = true
-	}
-	for _, pendingIDs := range f.pending {
-		for _, pendingID := range pendingIDs {
-			f.cache[pendingID] = true
-		}
-	}
-}

+ 0 - 604
vendor/github.com/boltdb/bolt/node.go

@@ -1,604 +0,0 @@
-package bolt
-
-import (
-	"bytes"
-	"fmt"
-	"sort"
-	"unsafe"
-)
-
-// node represents an in-memory, deserialized page.
-type node struct {
-	bucket     *Bucket
-	isLeaf     bool
-	unbalanced bool
-	spilled    bool
-	key        []byte
-	pgid       pgid
-	parent     *node
-	children   nodes
-	inodes     inodes
-}
-
-// root returns the top-level node this node is attached to.
-func (n *node) root() *node {
-	if n.parent == nil {
-		return n
-	}
-	return n.parent.root()
-}
-
-// minKeys returns the minimum number of inodes this node should have.
-func (n *node) minKeys() int {
-	if n.isLeaf {
-		return 1
-	}
-	return 2
-}
-
-// size returns the size of the node after serialization.
-func (n *node) size() int {
-	sz, elsz := pageHeaderSize, n.pageElementSize()
-	for i := 0; i < len(n.inodes); i++ {
-		item := &n.inodes[i]
-		sz += elsz + len(item.key) + len(item.value)
-	}
-	return sz
-}
-
-// sizeLessThan returns true if the node is less than a given size.
-// This is an optimization to avoid calculating a large node when we only need
-// to know if it fits inside a certain page size.
-func (n *node) sizeLessThan(v int) bool {
-	sz, elsz := pageHeaderSize, n.pageElementSize()
-	for i := 0; i < len(n.inodes); i++ {
-		item := &n.inodes[i]
-		sz += elsz + len(item.key) + len(item.value)
-		if sz >= v {
-			return false
-		}
-	}
-	return true
-}
-
-// pageElementSize returns the size of each page element based on the type of node.
-func (n *node) pageElementSize() int {
-	if n.isLeaf {
-		return leafPageElementSize
-	}
-	return branchPageElementSize
-}
-
-// childAt returns the child node at a given index.
-func (n *node) childAt(index int) *node {
-	if n.isLeaf {
-		panic(fmt.Sprintf("invalid childAt(%d) on a leaf node", index))
-	}
-	return n.bucket.node(n.inodes[index].pgid, n)
-}
-
-// childIndex returns the index of a given child node.
-func (n *node) childIndex(child *node) int {
-	index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, child.key) != -1 })
-	return index
-}
-
-// numChildren returns the number of children.
-func (n *node) numChildren() int {
-	return len(n.inodes)
-}
-
-// nextSibling returns the next node with the same parent.
-func (n *node) nextSibling() *node {
-	if n.parent == nil {
-		return nil
-	}
-	index := n.parent.childIndex(n)
-	if index >= n.parent.numChildren()-1 {
-		return nil
-	}
-	return n.parent.childAt(index + 1)
-}
-
-// prevSibling returns the previous node with the same parent.
-func (n *node) prevSibling() *node {
-	if n.parent == nil {
-		return nil
-	}
-	index := n.parent.childIndex(n)
-	if index == 0 {
-		return nil
-	}
-	return n.parent.childAt(index - 1)
-}
-
-// put inserts a key/value.
-func (n *node) put(oldKey, newKey, value []byte, pgid pgid, flags uint32) {
-	if pgid >= n.bucket.tx.meta.pgid {
-		panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", pgid, n.bucket.tx.meta.pgid))
-	} else if len(oldKey) <= 0 {
-		panic("put: zero-length old key")
-	} else if len(newKey) <= 0 {
-		panic("put: zero-length new key")
-	}
-
-	// Find insertion index.
-	index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, oldKey) != -1 })
-
-	// Add capacity and shift nodes if we don't have an exact match and need to insert.
-	exact := (len(n.inodes) > 0 && index < len(n.inodes) && bytes.Equal(n.inodes[index].key, oldKey))
-	if !exact {
-		n.inodes = append(n.inodes, inode{})
-		copy(n.inodes[index+1:], n.inodes[index:])
-	}
-
-	inode := &n.inodes[index]
-	inode.flags = flags
-	inode.key = newKey
-	inode.value = value
-	inode.pgid = pgid
-	_assert(len(inode.key) > 0, "put: zero-length inode key")
-}
-
-// del removes a key from the node.
-func (n *node) del(key []byte) {
-	// Find index of key.
-	index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 })
-
-	// Exit if the key isn't found.
-	if index >= len(n.inodes) || !bytes.Equal(n.inodes[index].key, key) {
-		return
-	}
-
-	// Delete inode from the node.
-	n.inodes = append(n.inodes[:index], n.inodes[index+1:]...)
-
-	// Mark the node as needing rebalancing.
-	n.unbalanced = true
-}
-
-// read initializes the node from a page.
-func (n *node) read(p *page) {
-	n.pgid = p.id
-	n.isLeaf = ((p.flags & leafPageFlag) != 0)
-	n.inodes = make(inodes, int(p.count))
-
-	for i := 0; i < int(p.count); i++ {
-		inode := &n.inodes[i]
-		if n.isLeaf {
-			elem := p.leafPageElement(uint16(i))
-			inode.flags = elem.flags
-			inode.key = elem.key()
-			inode.value = elem.value()
-		} else {
-			elem := p.branchPageElement(uint16(i))
-			inode.pgid = elem.pgid
-			inode.key = elem.key()
-		}
-		_assert(len(inode.key) > 0, "read: zero-length inode key")
-	}
-
-	// Save first key so we can find the node in the parent when we spill.
-	if len(n.inodes) > 0 {
-		n.key = n.inodes[0].key
-		_assert(len(n.key) > 0, "read: zero-length node key")
-	} else {
-		n.key = nil
-	}
-}
-
-// write writes the items onto one or more pages.
-func (n *node) write(p *page) {
-	// Initialize page.
-	if n.isLeaf {
-		p.flags |= leafPageFlag
-	} else {
-		p.flags |= branchPageFlag
-	}
-
-	if len(n.inodes) >= 0xFFFF {
-		panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.id))
-	}
-	p.count = uint16(len(n.inodes))
-
-	// Stop here if there are no items to write.
-	if p.count == 0 {
-		return
-	}
-
-	// Loop over each item and write it to the page.
-	b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):]
-	for i, item := range n.inodes {
-		_assert(len(item.key) > 0, "write: zero-length inode key")
-
-		// Write the page element.
-		if n.isLeaf {
-			elem := p.leafPageElement(uint16(i))
-			elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
-			elem.flags = item.flags
-			elem.ksize = uint32(len(item.key))
-			elem.vsize = uint32(len(item.value))
-		} else {
-			elem := p.branchPageElement(uint16(i))
-			elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
-			elem.ksize = uint32(len(item.key))
-			elem.pgid = item.pgid
-			_assert(elem.pgid != p.id, "write: circular dependency occurred")
-		}
-
-		// If the length of key+value is larger than the max allocation size
-		// then we need to reallocate the byte array pointer.
-		//
-		// See: https://github.com/boltdb/bolt/pull/335
-		klen, vlen := len(item.key), len(item.value)
-		if len(b) < klen+vlen {
-			b = (*[maxAllocSize]byte)(unsafe.Pointer(&b[0]))[:]
-		}
-
-		// Write data for the element to the end of the page.
-		copy(b[0:], item.key)
-		b = b[klen:]
-		copy(b[0:], item.value)
-		b = b[vlen:]
-	}
-
-	// DEBUG ONLY: n.dump()
-}
-
-// split breaks up a node into multiple smaller nodes, if appropriate.
-// This should only be called from the spill() function.
-func (n *node) split(pageSize int) []*node {
-	var nodes []*node
-
-	node := n
-	for {
-		// Split node into two.
-		a, b := node.splitTwo(pageSize)
-		nodes = append(nodes, a)
-
-		// If we can't split then exit the loop.
-		if b == nil {
-			break
-		}
-
-		// Set node to b so it gets split on the next iteration.
-		node = b
-	}
-
-	return nodes
-}
-
-// splitTwo breaks up a node into two smaller nodes, if appropriate.
-// This should only be called from the split() function.
-func (n *node) splitTwo(pageSize int) (*node, *node) {
-	// Ignore the split if the page doesn't have at least enough nodes for
-	// two pages or if the nodes can fit in a single page.
-	if len(n.inodes) <= (minKeysPerPage*2) || n.sizeLessThan(pageSize) {
-		return n, nil
-	}
-
-	// Determine the threshold before starting a new node.
-	var fillPercent = n.bucket.FillPercent
-	if fillPercent < minFillPercent {
-		fillPercent = minFillPercent
-	} else if fillPercent > maxFillPercent {
-		fillPercent = maxFillPercent
-	}
-	threshold := int(float64(pageSize) * fillPercent)
-
-	// Determine split position and sizes of the two pages.
-	splitIndex, _ := n.splitIndex(threshold)
-
-	// Split node into two separate nodes.
-	// If there's no parent then we'll need to create one.
-	if n.parent == nil {
-		n.parent = &node{bucket: n.bucket, children: []*node{n}}
-	}
-
-	// Create a new node and add it to the parent.
-	next := &node{bucket: n.bucket, isLeaf: n.isLeaf, parent: n.parent}
-	n.parent.children = append(n.parent.children, next)
-
-	// Split inodes across two nodes.
-	next.inodes = n.inodes[splitIndex:]
-	n.inodes = n.inodes[:splitIndex]
-
-	// Update the statistics.
-	n.bucket.tx.stats.Split++
-
-	return n, next
-}
-
-// splitIndex finds the position where a page will fill a given threshold.
-// It returns the index as well as the size of the first page.
-// This is only be called from split().
-func (n *node) splitIndex(threshold int) (index, sz int) {
-	sz = pageHeaderSize
-
-	// Loop until we only have the minimum number of keys required for the second page.
-	for i := 0; i < len(n.inodes)-minKeysPerPage; i++ {
-		index = i
-		inode := n.inodes[i]
-		elsize := n.pageElementSize() + len(inode.key) + len(inode.value)
-
-		// If we have at least the minimum number of keys and adding another
-		// node would put us over the threshold then exit and return.
-		if i >= minKeysPerPage && sz+elsize > threshold {
-			break
-		}
-
-		// Add the element size to the total size.
-		sz += elsize
-	}
-
-	return
-}
-
-// spill writes the nodes to dirty pages and splits nodes as it goes.
-// Returns an error if dirty pages cannot be allocated.
-func (n *node) spill() error {
-	var tx = n.bucket.tx
-	if n.spilled {
-		return nil
-	}
-
-	// Spill child nodes first. Child nodes can materialize sibling nodes in
-	// the case of split-merge so we cannot use a range loop. We have to check
-	// the children size on every loop iteration.
-	sort.Sort(n.children)
-	for i := 0; i < len(n.children); i++ {
-		if err := n.children[i].spill(); err != nil {
-			return err
-		}
-	}
-
-	// We no longer need the child list because it's only used for spill tracking.
-	n.children = nil
-
-	// Split nodes into appropriate sizes. The first node will always be n.
-	var nodes = n.split(tx.db.pageSize)
-	for _, node := range nodes {
-		// Add node's page to the freelist if it's not new.
-		if node.pgid > 0 {
-			tx.db.freelist.free(tx.meta.txid, tx.page(node.pgid))
-			node.pgid = 0
-		}
-
-		// Allocate contiguous space for the node.
-		p, err := tx.allocate((node.size() / tx.db.pageSize) + 1)
-		if err != nil {
-			return err
-		}
-
-		// Write the node.
-		if p.id >= tx.meta.pgid {
-			panic(fmt.Sprintf("pgid (%d) above high water mark (%d)", p.id, tx.meta.pgid))
-		}
-		node.pgid = p.id
-		node.write(p)
-		node.spilled = true
-
-		// Insert into parent inodes.
-		if node.parent != nil {
-			var key = node.key
-			if key == nil {
-				key = node.inodes[0].key
-			}
-
-			node.parent.put(key, node.inodes[0].key, nil, node.pgid, 0)
-			node.key = node.inodes[0].key
-			_assert(len(node.key) > 0, "spill: zero-length node key")
-		}
-
-		// Update the statistics.
-		tx.stats.Spill++
-	}
-
-	// If the root node split and created a new root then we need to spill that
-	// as well. We'll clear out the children to make sure it doesn't try to respill.
-	if n.parent != nil && n.parent.pgid == 0 {
-		n.children = nil
-		return n.parent.spill()
-	}
-
-	return nil
-}
-
-// rebalance attempts to combine the node with sibling nodes if the node fill
-// size is below a threshold or if there are not enough keys.
-func (n *node) rebalance() {
-	if !n.unbalanced {
-		return
-	}
-	n.unbalanced = false
-
-	// Update statistics.
-	n.bucket.tx.stats.Rebalance++
-
-	// Ignore if node is above threshold (25%) and has enough keys.
-	var threshold = n.bucket.tx.db.pageSize / 4
-	if n.size() > threshold && len(n.inodes) > n.minKeys() {
-		return
-	}
-
-	// Root node has special handling.
-	if n.parent == nil {
-		// If root node is a branch and only has one node then collapse it.
-		if !n.isLeaf && len(n.inodes) == 1 {
-			// Move root's child up.
-			child := n.bucket.node(n.inodes[0].pgid, n)
-			n.isLeaf = child.isLeaf
-			n.inodes = child.inodes[:]
-			n.children = child.children
-
-			// Reparent all child nodes being moved.
-			for _, inode := range n.inodes {
-				if child, ok := n.bucket.nodes[inode.pgid]; ok {
-					child.parent = n
-				}
-			}
-
-			// Remove old child.
-			child.parent = nil
-			delete(n.bucket.nodes, child.pgid)
-			child.free()
-		}
-
-		return
-	}
-
-	// If node has no keys then just remove it.
-	if n.numChildren() == 0 {
-		n.parent.del(n.key)
-		n.parent.removeChild(n)
-		delete(n.bucket.nodes, n.pgid)
-		n.free()
-		n.parent.rebalance()
-		return
-	}
-
-	_assert(n.parent.numChildren() > 1, "parent must have at least 2 children")
-
-	// Destination node is right sibling if idx == 0, otherwise left sibling.
-	var target *node
-	var useNextSibling = (n.parent.childIndex(n) == 0)
-	if useNextSibling {
-		target = n.nextSibling()
-	} else {
-		target = n.prevSibling()
-	}
-
-	// If both this node and the target node are too small then merge them.
-	if useNextSibling {
-		// Reparent all child nodes being moved.
-		for _, inode := range target.inodes {
-			if child, ok := n.bucket.nodes[inode.pgid]; ok {
-				child.parent.removeChild(child)
-				child.parent = n
-				child.parent.children = append(child.parent.children, child)
-			}
-		}
-
-		// Copy over inodes from target and remove target.
-		n.inodes = append(n.inodes, target.inodes...)
-		n.parent.del(target.key)
-		n.parent.removeChild(target)
-		delete(n.bucket.nodes, target.pgid)
-		target.free()
-	} else {
-		// Reparent all child nodes being moved.
-		for _, inode := range n.inodes {
-			if child, ok := n.bucket.nodes[inode.pgid]; ok {
-				child.parent.removeChild(child)
-				child.parent = target
-				child.parent.children = append(child.parent.children, child)
-			}
-		}
-
-		// Copy over inodes to target and remove node.
-		target.inodes = append(target.inodes, n.inodes...)
-		n.parent.del(n.key)
-		n.parent.removeChild(n)
-		delete(n.bucket.nodes, n.pgid)
-		n.free()
-	}
-
-	// Either this node or the target node was deleted from the parent so rebalance it.
-	n.parent.rebalance()
-}
-
-// removes a node from the list of in-memory children.
-// This does not affect the inodes.
-func (n *node) removeChild(target *node) {
-	for i, child := range n.children {
-		if child == target {
-			n.children = append(n.children[:i], n.children[i+1:]...)
-			return
-		}
-	}
-}
-
-// dereference causes the node to copy all its inode key/value references to heap memory.
-// This is required when the mmap is reallocated so inodes are not pointing to stale data.
-func (n *node) dereference() {
-	if n.key != nil {
-		key := make([]byte, len(n.key))
-		copy(key, n.key)
-		n.key = key
-		_assert(n.pgid == 0 || len(n.key) > 0, "dereference: zero-length node key on existing node")
-	}
-
-	for i := range n.inodes {
-		inode := &n.inodes[i]
-
-		key := make([]byte, len(inode.key))
-		copy(key, inode.key)
-		inode.key = key
-		_assert(len(inode.key) > 0, "dereference: zero-length inode key")
-
-		value := make([]byte, len(inode.value))
-		copy(value, inode.value)
-		inode.value = value
-	}
-
-	// Recursively dereference children.
-	for _, child := range n.children {
-		child.dereference()
-	}
-
-	// Update statistics.
-	n.bucket.tx.stats.NodeDeref++
-}
-
-// free adds the node's underlying page to the freelist.
-func (n *node) free() {
-	if n.pgid != 0 {
-		n.bucket.tx.db.freelist.free(n.bucket.tx.meta.txid, n.bucket.tx.page(n.pgid))
-		n.pgid = 0
-	}
-}
-
-// dump writes the contents of the node to STDERR for debugging purposes.
-/*
-func (n *node) dump() {
-	// Write node header.
-	var typ = "branch"
-	if n.isLeaf {
-		typ = "leaf"
-	}
-	warnf("[NODE %d {type=%s count=%d}]", n.pgid, typ, len(n.inodes))
-
-	// Write out abbreviated version of each item.
-	for _, item := range n.inodes {
-		if n.isLeaf {
-			if item.flags&bucketLeafFlag != 0 {
-				bucket := (*bucket)(unsafe.Pointer(&item.value[0]))
-				warnf("+L %08x -> (bucket root=%d)", trunc(item.key, 4), bucket.root)
-			} else {
-				warnf("+L %08x -> %08x", trunc(item.key, 4), trunc(item.value, 4))
-			}
-		} else {
-			warnf("+B %08x -> pgid=%d", trunc(item.key, 4), item.pgid)
-		}
-	}
-	warn("")
-}
-*/
-
-type nodes []*node
-
-func (s nodes) Len() int           { return len(s) }
-func (s nodes) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
-func (s nodes) Less(i, j int) bool { return bytes.Compare(s[i].inodes[0].key, s[j].inodes[0].key) == -1 }
-
-// inode represents an internal node inside of a node.
-// It can be used to point to elements in a page or point
-// to an element which hasn't been added to a page yet.
-type inode struct {
-	flags uint32
-	pgid  pgid
-	key   []byte
-	value []byte
-}
-
-type inodes []inode

+ 0 - 197
vendor/github.com/boltdb/bolt/page.go

@@ -1,197 +0,0 @@
-package bolt
-
-import (
-	"fmt"
-	"os"
-	"sort"
-	"unsafe"
-)
-
-const pageHeaderSize = int(unsafe.Offsetof(((*page)(nil)).ptr))
-
-const minKeysPerPage = 2
-
-const branchPageElementSize = int(unsafe.Sizeof(branchPageElement{}))
-const leafPageElementSize = int(unsafe.Sizeof(leafPageElement{}))
-
-const (
-	branchPageFlag   = 0x01
-	leafPageFlag     = 0x02
-	metaPageFlag     = 0x04
-	freelistPageFlag = 0x10
-)
-
-const (
-	bucketLeafFlag = 0x01
-)
-
-type pgid uint64
-
-type page struct {
-	id       pgid
-	flags    uint16
-	count    uint16
-	overflow uint32
-	ptr      uintptr
-}
-
-// typ returns a human readable page type string used for debugging.
-func (p *page) typ() string {
-	if (p.flags & branchPageFlag) != 0 {
-		return "branch"
-	} else if (p.flags & leafPageFlag) != 0 {
-		return "leaf"
-	} else if (p.flags & metaPageFlag) != 0 {
-		return "meta"
-	} else if (p.flags & freelistPageFlag) != 0 {
-		return "freelist"
-	}
-	return fmt.Sprintf("unknown<%02x>", p.flags)
-}
-
-// meta returns a pointer to the metadata section of the page.
-func (p *page) meta() *meta {
-	return (*meta)(unsafe.Pointer(&p.ptr))
-}
-
-// leafPageElement retrieves the leaf node by index
-func (p *page) leafPageElement(index uint16) *leafPageElement {
-	n := &((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[index]
-	return n
-}
-
-// leafPageElements retrieves a list of leaf nodes.
-func (p *page) leafPageElements() []leafPageElement {
-	if p.count == 0 {
-		return nil
-	}
-	return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:]
-}
-
-// branchPageElement retrieves the branch node by index
-func (p *page) branchPageElement(index uint16) *branchPageElement {
-	return &((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[index]
-}
-
-// branchPageElements retrieves a list of branch nodes.
-func (p *page) branchPageElements() []branchPageElement {
-	if p.count == 0 {
-		return nil
-	}
-	return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:]
-}
-
-// dump writes n bytes of the page to STDERR as hex output.
-func (p *page) hexdump(n int) {
-	buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:n]
-	fmt.Fprintf(os.Stderr, "%x\n", buf)
-}
-
-type pages []*page
-
-func (s pages) Len() int           { return len(s) }
-func (s pages) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
-func (s pages) Less(i, j int) bool { return s[i].id < s[j].id }
-
-// branchPageElement represents a node on a branch page.
-type branchPageElement struct {
-	pos   uint32
-	ksize uint32
-	pgid  pgid
-}
-
-// key returns a byte slice of the node key.
-func (n *branchPageElement) key() []byte {
-	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
-	return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize]
-}
-
-// leafPageElement represents a node on a leaf page.
-type leafPageElement struct {
-	flags uint32
-	pos   uint32
-	ksize uint32
-	vsize uint32
-}
-
-// key returns a byte slice of the node key.
-func (n *leafPageElement) key() []byte {
-	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
-	return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize:n.ksize]
-}
-
-// value returns a byte slice of the node value.
-func (n *leafPageElement) value() []byte {
-	buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
-	return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize:n.vsize]
-}
-
-// PageInfo represents human readable information about a page.
-type PageInfo struct {
-	ID            int
-	Type          string
-	Count         int
-	OverflowCount int
-}
-
-type pgids []pgid
-
-func (s pgids) Len() int           { return len(s) }
-func (s pgids) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
-func (s pgids) Less(i, j int) bool { return s[i] < s[j] }
-
-// merge returns the sorted union of a and b.
-func (a pgids) merge(b pgids) pgids {
-	// Return the opposite slice if one is nil.
-	if len(a) == 0 {
-		return b
-	}
-	if len(b) == 0 {
-		return a
-	}
-	merged := make(pgids, len(a)+len(b))
-	mergepgids(merged, a, b)
-	return merged
-}
-
-// mergepgids copies the sorted union of a and b into dst.
-// If dst is too small, it panics.
-func mergepgids(dst, a, b pgids) {
-	if len(dst) < len(a)+len(b) {
-		panic(fmt.Errorf("mergepgids bad len %d < %d + %d", len(dst), len(a), len(b)))
-	}
-	// Copy in the opposite slice if one is nil.
-	if len(a) == 0 {
-		copy(dst, b)
-		return
-	}
-	if len(b) == 0 {
-		copy(dst, a)
-		return
-	}
-
-	// Merged will hold all elements from both lists.
-	merged := dst[:0]
-
-	// Assign lead to the slice with a lower starting value, follow to the higher value.
-	lead, follow := a, b
-	if b[0] < a[0] {
-		lead, follow = b, a
-	}
-
-	// Continue while there are elements in the lead.
-	for len(lead) > 0 {
-		// Merge largest prefix of lead that is ahead of follow[0].
-		n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] })
-		merged = append(merged, lead[:n]...)
-		if n >= len(lead) {
-			break
-		}
-
-		// Swap lead and follow.
-		lead, follow = follow, lead[n:]
-	}
-
-	// Append what's left in follow.
-	_ = append(merged, follow...)
-}

+ 0 - 684
vendor/github.com/boltdb/bolt/tx.go

@@ -1,684 +0,0 @@
-package bolt
-
-import (
-	"fmt"
-	"io"
-	"os"
-	"sort"
-	"strings"
-	"time"
-	"unsafe"
-)
-
-// txid represents the internal transaction identifier.
-type txid uint64
-
-// Tx represents a read-only or read/write transaction on the database.
-// Read-only transactions can be used for retrieving values for keys and creating cursors.
-// Read/write transactions can create and remove buckets and create and remove keys.
-//
-// IMPORTANT: You must commit or rollback transactions when you are done with
-// them. Pages can not be reclaimed by the writer until no more transactions
-// are using them. A long running read transaction can cause the database to
-// quickly grow.
-type Tx struct {
-	writable       bool
-	managed        bool
-	db             *DB
-	meta           *meta
-	root           Bucket
-	pages          map[pgid]*page
-	stats          TxStats
-	commitHandlers []func()
-
-	// WriteFlag specifies the flag for write-related methods like WriteTo().
-	// Tx opens the database file with the specified flag to copy the data.
-	//
-	// By default, the flag is unset, which works well for mostly in-memory
-	// workloads. For databases that are much larger than available RAM,
-	// set the flag to syscall.O_DIRECT to avoid trashing the page cache.
-	WriteFlag int
-}
-
-// init initializes the transaction.
-func (tx *Tx) init(db *DB) {
-	tx.db = db
-	tx.pages = nil
-
-	// Copy the meta page since it can be changed by the writer.
-	tx.meta = &meta{}
-	db.meta().copy(tx.meta)
-
-	// Copy over the root bucket.
-	tx.root = newBucket(tx)
-	tx.root.bucket = &bucket{}
-	*tx.root.bucket = tx.meta.root
-
-	// Increment the transaction id and add a page cache for writable transactions.
-	if tx.writable {
-		tx.pages = make(map[pgid]*page)
-		tx.meta.txid += txid(1)
-	}
-}
-
-// ID returns the transaction id.
-func (tx *Tx) ID() int {
-	return int(tx.meta.txid)
-}
-
-// DB returns a reference to the database that created the transaction.
-func (tx *Tx) DB() *DB {
-	return tx.db
-}
-
-// Size returns current database size in bytes as seen by this transaction.
-func (tx *Tx) Size() int64 {
-	return int64(tx.meta.pgid) * int64(tx.db.pageSize)
-}
-
-// Writable returns whether the transaction can perform write operations.
-func (tx *Tx) Writable() bool {
-	return tx.writable
-}
-
-// Cursor creates a cursor associated with the root bucket.
-// All items in the cursor will return a nil value because all root bucket keys point to buckets.
-// The cursor is only valid as long as the transaction is open.
-// Do not use a cursor after the transaction is closed.
-func (tx *Tx) Cursor() *Cursor {
-	return tx.root.Cursor()
-}
-
-// Stats retrieves a copy of the current transaction statistics.
-func (tx *Tx) Stats() TxStats {
-	return tx.stats
-}
-
-// Bucket retrieves a bucket by name.
-// Returns nil if the bucket does not exist.
-// The bucket instance is only valid for the lifetime of the transaction.
-func (tx *Tx) Bucket(name []byte) *Bucket {
-	return tx.root.Bucket(name)
-}
-
-// CreateBucket creates a new bucket.
-// Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long.
-// The bucket instance is only valid for the lifetime of the transaction.
-func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) {
-	return tx.root.CreateBucket(name)
-}
-
-// CreateBucketIfNotExists creates a new bucket if it doesn't already exist.
-// Returns an error if the bucket name is blank, or if the bucket name is too long.
-// The bucket instance is only valid for the lifetime of the transaction.
-func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) {
-	return tx.root.CreateBucketIfNotExists(name)
-}
-
-// DeleteBucket deletes a bucket.
-// Returns an error if the bucket cannot be found or if the key represents a non-bucket value.
-func (tx *Tx) DeleteBucket(name []byte) error {
-	return tx.root.DeleteBucket(name)
-}
-
-// ForEach executes a function for each bucket in the root.
-// If the provided function returns an error then the iteration is stopped and
-// the error is returned to the caller.
-func (tx *Tx) ForEach(fn func(name []byte, b *Bucket) error) error {
-	return tx.root.ForEach(func(k, v []byte) error {
-		if err := fn(k, tx.root.Bucket(k)); err != nil {
-			return err
-		}
-		return nil
-	})
-}
-
-// OnCommit adds a handler function to be executed after the transaction successfully commits.
-func (tx *Tx) OnCommit(fn func()) {
-	tx.commitHandlers = append(tx.commitHandlers, fn)
-}
-
-// Commit writes all changes to disk and updates the meta page.
-// Returns an error if a disk write error occurs, or if Commit is
-// called on a read-only transaction.
-func (tx *Tx) Commit() error {
-	_assert(!tx.managed, "managed tx commit not allowed")
-	if tx.db == nil {
-		return ErrTxClosed
-	} else if !tx.writable {
-		return ErrTxNotWritable
-	}
-
-	// TODO(benbjohnson): Use vectorized I/O to write out dirty pages.
-
-	// Rebalance nodes which have had deletions.
-	var startTime = time.Now()
-	tx.root.rebalance()
-	if tx.stats.Rebalance > 0 {
-		tx.stats.RebalanceTime += time.Since(startTime)
-	}
-
-	// spill data onto dirty pages.
-	startTime = time.Now()
-	if err := tx.root.spill(); err != nil {
-		tx.rollback()
-		return err
-	}
-	tx.stats.SpillTime += time.Since(startTime)
-
-	// Free the old root bucket.
-	tx.meta.root.root = tx.root.root
-
-	opgid := tx.meta.pgid
-
-	// Free the freelist and allocate new pages for it. This will overestimate
-	// the size of the freelist but not underestimate the size (which would be bad).
-	tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist))
-	p, err := tx.allocate((tx.db.freelist.size() / tx.db.pageSize) + 1)
-	if err != nil {
-		tx.rollback()
-		return err
-	}
-	if err := tx.db.freelist.write(p); err != nil {
-		tx.rollback()
-		return err
-	}
-	tx.meta.freelist = p.id
-
-	// If the high water mark has moved up then attempt to grow the database.
-	if tx.meta.pgid > opgid {
-		if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil {
-			tx.rollback()
-			return err
-		}
-	}
-
-	// Write dirty pages to disk.
-	startTime = time.Now()
-	if err := tx.write(); err != nil {
-		tx.rollback()
-		return err
-	}
-
-	// If strict mode is enabled then perform a consistency check.
-	// Only the first consistency error is reported in the panic.
-	if tx.db.StrictMode {
-		ch := tx.Check()
-		var errs []string
-		for {
-			err, ok := <-ch
-			if !ok {
-				break
-			}
-			errs = append(errs, err.Error())
-		}
-		if len(errs) > 0 {
-			panic("check fail: " + strings.Join(errs, "\n"))
-		}
-	}
-
-	// Write meta to disk.
-	if err := tx.writeMeta(); err != nil {
-		tx.rollback()
-		return err
-	}
-	tx.stats.WriteTime += time.Since(startTime)
-
-	// Finalize the transaction.
-	tx.close()
-
-	// Execute commit handlers now that the locks have been removed.
-	for _, fn := range tx.commitHandlers {
-		fn()
-	}
-
-	return nil
-}
-
-// Rollback closes the transaction and ignores all previous updates. Read-only
-// transactions must be rolled back and not committed.
-func (tx *Tx) Rollback() error {
-	_assert(!tx.managed, "managed tx rollback not allowed")
-	if tx.db == nil {
-		return ErrTxClosed
-	}
-	tx.rollback()
-	return nil
-}
-
-func (tx *Tx) rollback() {
-	if tx.db == nil {
-		return
-	}
-	if tx.writable {
-		tx.db.freelist.rollback(tx.meta.txid)
-		tx.db.freelist.reload(tx.db.page(tx.db.meta().freelist))
-	}
-	tx.close()
-}
-
-func (tx *Tx) close() {
-	if tx.db == nil {
-		return
-	}
-	if tx.writable {
-		// Grab freelist stats.
-		var freelistFreeN = tx.db.freelist.free_count()
-		var freelistPendingN = tx.db.freelist.pending_count()
-		var freelistAlloc = tx.db.freelist.size()
-
-		// Remove transaction ref & writer lock.
-		tx.db.rwtx = nil
-		tx.db.rwlock.Unlock()
-
-		// Merge statistics.
-		tx.db.statlock.Lock()
-		tx.db.stats.FreePageN = freelistFreeN
-		tx.db.stats.PendingPageN = freelistPendingN
-		tx.db.stats.FreeAlloc = (freelistFreeN + freelistPendingN) * tx.db.pageSize
-		tx.db.stats.FreelistInuse = freelistAlloc
-		tx.db.stats.TxStats.add(&tx.stats)
-		tx.db.statlock.Unlock()
-	} else {
-		tx.db.removeTx(tx)
-	}
-
-	// Clear all references.
-	tx.db = nil
-	tx.meta = nil
-	tx.root = Bucket{tx: tx}
-	tx.pages = nil
-}
-
-// Copy writes the entire database to a writer.
-// This function exists for backwards compatibility. Use WriteTo() instead.
-func (tx *Tx) Copy(w io.Writer) error {
-	_, err := tx.WriteTo(w)
-	return err
-}
-
-// WriteTo writes the entire database to a writer.
-// If err == nil then exactly tx.Size() bytes will be written into the writer.
-func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
-	// Attempt to open reader with WriteFlag
-	f, err := os.OpenFile(tx.db.path, os.O_RDONLY|tx.WriteFlag, 0)
-	if err != nil {
-		return 0, err
-	}
-	defer func() { _ = f.Close() }()
-
-	// Generate a meta page. We use the same page data for both meta pages.
-	buf := make([]byte, tx.db.pageSize)
-	page := (*page)(unsafe.Pointer(&buf[0]))
-	page.flags = metaPageFlag
-	*page.meta() = *tx.meta
-
-	// Write meta 0.
-	page.id = 0
-	page.meta().checksum = page.meta().sum64()
-	nn, err := w.Write(buf)
-	n += int64(nn)
-	if err != nil {
-		return n, fmt.Errorf("meta 0 copy: %s", err)
-	}
-
-	// Write meta 1 with a lower transaction id.
-	page.id = 1
-	page.meta().txid -= 1
-	page.meta().checksum = page.meta().sum64()
-	nn, err = w.Write(buf)
-	n += int64(nn)
-	if err != nil {
-		return n, fmt.Errorf("meta 1 copy: %s", err)
-	}
-
-	// Move past the meta pages in the file.
-	if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil {
-		return n, fmt.Errorf("seek: %s", err)
-	}
-
-	// Copy data pages.
-	wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2))
-	n += wn
-	if err != nil {
-		return n, err
-	}
-
-	return n, f.Close()
-}
-
-// CopyFile copies the entire database to file at the given path.
-// A reader transaction is maintained during the copy so it is safe to continue
-// using the database while a copy is in progress.
-func (tx *Tx) CopyFile(path string, mode os.FileMode) error {
-	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
-	if err != nil {
-		return err
-	}
-
-	err = tx.Copy(f)
-	if err != nil {
-		_ = f.Close()
-		return err
-	}
-	return f.Close()
-}
-
-// Check performs several consistency checks on the database for this transaction.
-// An error is returned if any inconsistency is found.
-//
-// It can be safely run concurrently on a writable transaction. However, this
-// incurs a high cost for large databases and databases with a lot of subbuckets
-// because of caching. This overhead can be removed if running on a read-only
-// transaction, however, it is not safe to execute other writer transactions at
-// the same time.
-func (tx *Tx) Check() <-chan error {
-	ch := make(chan error)
-	go tx.check(ch)
-	return ch
-}
-
-func (tx *Tx) check(ch chan error) {
-	// Check if any pages are double freed.
-	freed := make(map[pgid]bool)
-	all := make([]pgid, tx.db.freelist.count())
-	tx.db.freelist.copyall(all)
-	for _, id := range all {
-		if freed[id] {
-			ch <- fmt.Errorf("page %d: already freed", id)
-		}
-		freed[id] = true
-	}
-
-	// Track every reachable page.
-	reachable := make(map[pgid]*page)
-	reachable[0] = tx.page(0) // meta0
-	reachable[1] = tx.page(1) // meta1
-	for i := uint32(0); i <= tx.page(tx.meta.freelist).overflow; i++ {
-		reachable[tx.meta.freelist+pgid(i)] = tx.page(tx.meta.freelist)
-	}
-
-	// Recursively check buckets.
-	tx.checkBucket(&tx.root, reachable, freed, ch)
-
-	// Ensure all pages below high water mark are either reachable or freed.
-	for i := pgid(0); i < tx.meta.pgid; i++ {
-		_, isReachable := reachable[i]
-		if !isReachable && !freed[i] {
-			ch <- fmt.Errorf("page %d: unreachable unfreed", int(i))
-		}
-	}
-
-	// Close the channel to signal completion.
-	close(ch)
-}
-
-func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bool, ch chan error) {
-	// Ignore inline buckets.
-	if b.root == 0 {
-		return
-	}
-
-	// Check every page used by this bucket.
-	b.tx.forEachPage(b.root, 0, func(p *page, _ int) {
-		if p.id > tx.meta.pgid {
-			ch <- fmt.Errorf("page %d: out of bounds: %d", int(p.id), int(b.tx.meta.pgid))
-		}
-
-		// Ensure each page is only referenced once.
-		for i := pgid(0); i <= pgid(p.overflow); i++ {
-			var id = p.id + i
-			if _, ok := reachable[id]; ok {
-				ch <- fmt.Errorf("page %d: multiple references", int(id))
-			}
-			reachable[id] = p
-		}
-
-		// We should only encounter un-freed leaf and branch pages.
-		if freed[p.id] {
-			ch <- fmt.Errorf("page %d: reachable freed", int(p.id))
-		} else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 {
-			ch <- fmt.Errorf("page %d: invalid type: %s", int(p.id), p.typ())
-		}
-	})
-
-	// Check each bucket within this bucket.
-	_ = b.ForEach(func(k, v []byte) error {
-		if child := b.Bucket(k); child != nil {
-			tx.checkBucket(child, reachable, freed, ch)
-		}
-		return nil
-	})
-}
-
-// allocate returns a contiguous block of memory starting at a given page.
-func (tx *Tx) allocate(count int) (*page, error) {
-	p, err := tx.db.allocate(count)
-	if err != nil {
-		return nil, err
-	}
-
-	// Save to our page cache.
-	tx.pages[p.id] = p
-
-	// Update statistics.
-	tx.stats.PageCount++
-	tx.stats.PageAlloc += count * tx.db.pageSize
-
-	return p, nil
-}
-
-// write writes any dirty pages to disk.
-func (tx *Tx) write() error {
-	// Sort pages by id.
-	pages := make(pages, 0, len(tx.pages))
-	for _, p := range tx.pages {
-		pages = append(pages, p)
-	}
-	// Clear out page cache early.
-	tx.pages = make(map[pgid]*page)
-	sort.Sort(pages)
-
-	// Write pages to disk in order.
-	for _, p := range pages {
-		size := (int(p.overflow) + 1) * tx.db.pageSize
-		offset := int64(p.id) * int64(tx.db.pageSize)
-
-		// Write out page in "max allocation" sized chunks.
-		ptr := (*[maxAllocSize]byte)(unsafe.Pointer(p))
-		for {
-			// Limit our write to our max allocation size.
-			sz := size
-			if sz > maxAllocSize-1 {
-				sz = maxAllocSize - 1
-			}
-
-			// Write chunk to disk.
-			buf := ptr[:sz]
-			if _, err := tx.db.ops.writeAt(buf, offset); err != nil {
-				return err
-			}
-
-			// Update statistics.
-			tx.stats.Write++
-
-			// Exit inner for loop if we've written all the chunks.
-			size -= sz
-			if size == 0 {
-				break
-			}
-
-			// Otherwise move offset forward and move pointer to next chunk.
-			offset += int64(sz)
-			ptr = (*[maxAllocSize]byte)(unsafe.Pointer(&ptr[sz]))
-		}
-	}
-
-	// Ignore file sync if flag is set on DB.
-	if !tx.db.NoSync || IgnoreNoSync {
-		if err := fdatasync(tx.db); err != nil {
-			return err
-		}
-	}
-
-	// Put small pages back to page pool.
-	for _, p := range pages {
-		// Ignore page sizes over 1 page.
-		// These are allocated using make() instead of the page pool.
-		if int(p.overflow) != 0 {
-			continue
-		}
-
-		buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:tx.db.pageSize]
-
-		// See https://go.googlesource.com/go/+/f03c9202c43e0abb130669852082117ca50aa9b1
-		for i := range buf {
-			buf[i] = 0
-		}
-		tx.db.pagePool.Put(buf)
-	}
-
-	return nil
-}
-
-// writeMeta writes the meta to the disk.
-func (tx *Tx) writeMeta() error {
-	// Create a temporary buffer for the meta page.
-	buf := make([]byte, tx.db.pageSize)
-	p := tx.db.pageInBuffer(buf, 0)
-	tx.meta.write(p)
-
-	// Write the meta page to file.
-	if _, err := tx.db.ops.writeAt(buf, int64(p.id)*int64(tx.db.pageSize)); err != nil {
-		return err
-	}
-	if !tx.db.NoSync || IgnoreNoSync {
-		if err := fdatasync(tx.db); err != nil {
-			return err
-		}
-	}
-
-	// Update statistics.
-	tx.stats.Write++
-
-	return nil
-}
-
-// page returns a reference to the page with a given id.
-// If page has been written to then a temporary buffered page is returned.
-func (tx *Tx) page(id pgid) *page {
-	// Check the dirty pages first.
-	if tx.pages != nil {
-		if p, ok := tx.pages[id]; ok {
-			return p
-		}
-	}
-
-	// Otherwise return directly from the mmap.
-	return tx.db.page(id)
-}
-
-// forEachPage iterates over every page within a given page and executes a function.
-func (tx *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) {
-	p := tx.page(pgid)
-
-	// Execute function.
-	fn(p, depth)
-
-	// Recursively loop over children.
-	if (p.flags & branchPageFlag) != 0 {
-		for i := 0; i < int(p.count); i++ {
-			elem := p.branchPageElement(uint16(i))
-			tx.forEachPage(elem.pgid, depth+1, fn)
-		}
-	}
-}
-
-// Page returns page information for a given page number.
-// This is only safe for concurrent use when used by a writable transaction.
-func (tx *Tx) Page(id int) (*PageInfo, error) {
-	if tx.db == nil {
-		return nil, ErrTxClosed
-	} else if pgid(id) >= tx.meta.pgid {
-		return nil, nil
-	}
-
-	// Build the page info.
-	p := tx.db.page(pgid(id))
-	info := &PageInfo{
-		ID:            id,
-		Count:         int(p.count),
-		OverflowCount: int(p.overflow),
-	}
-
-	// Determine the type (or if it's free).
-	if tx.db.freelist.freed(pgid(id)) {
-		info.Type = "free"
-	} else {
-		info.Type = p.typ()
-	}
-
-	return info, nil
-}
-
-// TxStats represents statistics about the actions performed by the transaction.
-type TxStats struct {
-	// Page statistics.
-	PageCount int // number of page allocations
-	PageAlloc int // total bytes allocated
-
-	// Cursor statistics.
-	CursorCount int // number of cursors created
-
-	// Node statistics
-	NodeCount int // number of node allocations
-	NodeDeref int // number of node dereferences
-
-	// Rebalance statistics.
-	Rebalance     int           // number of node rebalances
-	RebalanceTime time.Duration // total time spent rebalancing
-
-	// Split/Spill statistics.
-	Split     int           // number of nodes split
-	Spill     int           // number of nodes spilled
-	SpillTime time.Duration // total time spent spilling
-
-	// Write statistics.
-	Write     int           // number of writes performed
-	WriteTime time.Duration // total time spent writing to disk
-}
-
-func (s *TxStats) add(other *TxStats) {
-	s.PageCount += other.PageCount
-	s.PageAlloc += other.PageAlloc
-	s.CursorCount += other.CursorCount
-	s.NodeCount += other.NodeCount
-	s.NodeDeref += other.NodeDeref
-	s.Rebalance += other.Rebalance
-	s.RebalanceTime += other.RebalanceTime
-	s.Split += other.Split
-	s.Spill += other.Spill
-	s.SpillTime += other.SpillTime
-	s.Write += other.Write
-	s.WriteTime += other.WriteTime
-}
-
-// Sub calculates and returns the difference between two sets of transaction stats.
-// This is useful when obtaining stats at two different points and time and
-// you need the performance counters that occurred within that time span.
-func (s *TxStats) Sub(other *TxStats) TxStats {
-	var diff TxStats
-	diff.PageCount = s.PageCount - other.PageCount
-	diff.PageAlloc = s.PageAlloc - other.PageAlloc
-	diff.CursorCount = s.CursorCount - other.CursorCount
-	diff.NodeCount = s.NodeCount - other.NodeCount
-	diff.NodeDeref = s.NodeDeref - other.NodeDeref
-	diff.Rebalance = s.Rebalance - other.Rebalance
-	diff.RebalanceTime = s.RebalanceTime - other.RebalanceTime
-	diff.Split = s.Split - other.Split
-	diff.Spill = s.Spill - other.Spill
-	diff.SpillTime = s.SpillTime - other.SpillTime
-	diff.Write = s.Write - other.Write
-	diff.WriteTime = s.WriteTime - other.WriteTime
-	return diff
-}

+ 0 - 15
vendor/github.com/cznic/fileutil/AUTHORS

@@ -1,15 +0,0 @@
-# This file lists authors for copyright purposes.  This file is distinct from
-# the CONTRIBUTORS files.  See the latter for an explanation.
-#
-# Names should be added to this file as:
-#     Name or Organization <email address>
-#
-# The email address is not required for organizations.
-#
-# Please keep the list sorted.
-
-CZ.NIC z.s.p.o. <[email protected]>
-Jan Mercl <[email protected]>
-Linelane GmbH <[email protected]>
-Aaron Bieber <[email protected]>
-

+ 0 - 15
vendor/github.com/cznic/fileutil/CONTRIBUTORS

@@ -1,15 +0,0 @@
-# This file lists people who contributed code to this repository.  The AUTHORS
-# file lists the copyright holders; this file lists people.
-#
-# Names should be added to this file like so:
-#     Name <email address>
-#
-# Please keep the list sorted.
-
-Andris Valums <[email protected]>
-Bill Thiede <xinu.tv>
-Gary Burd <[email protected]>
-Jan Mercl <[email protected]>
-Nick Owens <[email protected]>
-Tamás Gulácsi <[email protected]>
-Aaron Bieber <[email protected]>

+ 0 - 27
vendor/github.com/cznic/fileutil/LICENSE

@@ -1,27 +0,0 @@
-Copyright (c) 2014 The fileutil Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-   * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-   * 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.
-   * Neither the names of the authors nor the names of the
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-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
-OWNER 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.

+ 0 - 27
vendor/github.com/cznic/fileutil/Makefile

@@ -1,27 +0,0 @@
-# Copyright (c) 2014 The fileutil authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-.PHONY: all clean editor todo
-
-all: editor
-	go vet
-	golint .
-	go install
-	make todo
-
-editor:
-	go fmt
-	go test -i
-	go test
-	go build
-
-todo:
-	@grep -n ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* *.go || true
-	@grep -n TODO *.go || true
-	@grep -n BUG *.go || true
-	@grep -n println *.go || true
-
-clean:
-	@go clean
-	rm -f y.output

+ 0 - 16
vendor/github.com/cznic/fileutil/README

@@ -1,16 +0,0 @@
-This is a goinstall-able mirror of modified code already published at:
-http://git.nic.cz/redmine/projects/gofileutil/repository
-
-Packages in this repository:
-
-Install: $go get github.com/cznic/fileutil
-Godocs: http://godoc.org/github.com/cznic/fileutil
-
-Install: $go get github.com/cznic/fileutil/storage
-Godocs: http://godoc.org/github.com/cznic/fileutil/storage
-
-Install: $go get github.com/cznic/fileutil/falloc
-Godocs: http://godoc.org/github.com/cznic/fileutil/falloc
-
-Install: $go get github.com/cznic/fileutil/hdb
-Godocs: http://godoc.org/github.com/cznic/fileutil/hdb

+ 0 - 223
vendor/github.com/cznic/fileutil/fileutil.go

@@ -1,223 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package fileutil collects some file utility functions.
-package fileutil
-
-import (
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"runtime"
-	"strconv"
-	"sync"
-	"time"
-)
-
-// GoMFile is a concurrent access safe version of MFile.
-type GoMFile struct {
-	mfile *MFile
-	mutex sync.Mutex
-}
-
-// NewGoMFile return a newly created GoMFile.
-func NewGoMFile(fname string, flag int, perm os.FileMode, delta_ns int64) (m *GoMFile, err error) {
-	m = &GoMFile{}
-	if m.mfile, err = NewMFile(fname, flag, perm, delta_ns); err != nil {
-		m = nil
-	}
-	return
-}
-
-func (m *GoMFile) File() (file *os.File, err error) {
-	m.mutex.Lock()
-	defer m.mutex.Unlock()
-	return m.mfile.File()
-}
-
-func (m *GoMFile) SetChanged() {
-	m.mutex.Lock()
-	defer m.mutex.Unlock()
-	m.mfile.SetChanged()
-}
-
-func (m *GoMFile) SetHandler(h MFileHandler) {
-	m.mutex.Lock()
-	defer m.mutex.Unlock()
-	m.mfile.SetHandler(h)
-}
-
-// MFileHandler resolves modifications of File.
-// Possible File context is expected to be a part of the handler's closure.
-type MFileHandler func(*os.File) error
-
-// MFile represents an os.File with a guard/handler on change/modification.
-// Example use case is an app with a configuration file which can be modified at any time
-// and have to be reloaded in such event prior to performing something configurable by that
-// file. The checks are made only on access to the MFile file by
-// File() and a time threshold/hysteresis value can be chosen on creating a new MFile.
-type MFile struct {
-	file    *os.File
-	handler MFileHandler
-	t0      int64
-	delta   int64
-	ctime   int64
-}
-
-// NewMFile returns a newly created MFile or Error if any.
-// The fname, flag and perm parameters have the same meaning as in os.Open.
-// For meaning of the delta_ns parameter please see the (m *MFile) File() docs.
-func NewMFile(fname string, flag int, perm os.FileMode, delta_ns int64) (m *MFile, err error) {
-	m = &MFile{}
-	m.t0 = time.Now().UnixNano()
-	if m.file, err = os.OpenFile(fname, flag, perm); err != nil {
-		return
-	}
-
-	var fi os.FileInfo
-	if fi, err = m.file.Stat(); err != nil {
-		return
-	}
-
-	m.ctime = fi.ModTime().UnixNano()
-	m.delta = delta_ns
-	runtime.SetFinalizer(m, func(m *MFile) {
-		m.file.Close()
-	})
-	return
-}
-
-// SetChanged forces next File() to unconditionally handle modification of the wrapped os.File.
-func (m *MFile) SetChanged() {
-	m.ctime = -1
-}
-
-// SetHandler sets a function to be invoked when modification of MFile is to be processed.
-func (m *MFile) SetHandler(h MFileHandler) {
-	m.handler = h
-}
-
-// File returns an os.File from MFile. If time elapsed between the last invocation of this function
-// and now is at least delta_ns ns (a parameter of NewMFile) then the file is checked for
-// change/modification. For delta_ns == 0 the modification is checked w/o getting os.Time().
-// If a change is detected a handler is invoked on the MFile file.
-// Any of these steps can produce an Error. If that happens the function returns nil, Error.
-func (m *MFile) File() (file *os.File, err error) {
-	var now int64
-
-	mustCheck := m.delta == 0
-	if !mustCheck {
-		now = time.Now().UnixNano()
-		mustCheck = now-m.t0 > m.delta
-	}
-
-	if mustCheck { // check interval reached
-		var fi os.FileInfo
-		if fi, err = m.file.Stat(); err != nil {
-			return
-		}
-
-		if fi.ModTime().UnixNano() != m.ctime { // modification detected
-			if m.handler == nil {
-				return nil, fmt.Errorf("no handler set for modified file %q", m.file.Name())
-			}
-			if err = m.handler(m.file); err != nil {
-				return
-			}
-
-			m.ctime = fi.ModTime().UnixNano()
-		}
-		m.t0 = now
-	}
-
-	return m.file, nil
-}
-
-// Read reads buf from r. It will either fill the full buf or fail.
-// It wraps the functionality of an io.Reader which may return less bytes than requested,
-// but may block if not all data are ready for the io.Reader.
-func Read(r io.Reader, buf []byte) (err error) {
-	have := 0
-	remain := len(buf)
-	got := 0
-	for remain > 0 {
-		if got, err = r.Read(buf[have:]); err != nil {
-			return
-		}
-
-		remain -= got
-		have += got
-	}
-	return
-}
-
-// "os" and/or "syscall" extensions
-
-// FadviseAdvice is used by Fadvise.
-type FadviseAdvice int
-
-// FAdviseAdvice values.
-const (
-	// $ grep FADV /usr/include/bits/fcntl.h
-	POSIX_FADV_NORMAL     FadviseAdvice = iota // No further special treatment.
-	POSIX_FADV_RANDOM                          // Expect random page references.
-	POSIX_FADV_SEQUENTIAL                      // Expect sequential page references.
-	POSIX_FADV_WILLNEED                        // Will need these pages.
-	POSIX_FADV_DONTNEED                        // Don't need these pages.
-	POSIX_FADV_NOREUSE                         // Data will be accessed once.
-)
-
-// TempFile creates a new temporary file in the directory dir with a name
-// ending with suffix, basename starting with prefix, opens the file for
-// reading and writing, and returns the resulting *os.File.  If dir is the
-// empty string, TempFile uses the default directory for temporary files (see
-// os.TempDir).  Multiple programs calling TempFile simultaneously will not
-// choose the same file.  The caller can use f.Name() to find the pathname of
-// the file.  It is the caller's responsibility to remove the file when no
-// longer needed.
-//
-// NOTE: This function differs from ioutil.TempFile.
-func TempFile(dir, prefix, suffix string) (f *os.File, err error) {
-	if dir == "" {
-		dir = os.TempDir()
-	}
-
-	nconflict := 0
-	for i := 0; i < 10000; i++ {
-		name := filepath.Join(dir, prefix+nextInfix()+suffix)
-		f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
-		if os.IsExist(err) {
-			if nconflict++; nconflict > 10 {
-				rand = reseed()
-			}
-			continue
-		}
-		break
-	}
-	return
-}
-
-// Random number state.
-// We generate random temporary file names so that there's a good
-// chance the file doesn't exist yet - keeps the number of tries in
-// TempFile to a minimum.
-var rand uint32
-var randmu sync.Mutex
-
-func reseed() uint32 {
-	return uint32(time.Now().UnixNano() + int64(os.Getpid()))
-}
-
-func nextInfix() string {
-	randmu.Lock()
-	r := rand
-	if r == 0 {
-		r = reseed()
-	}
-	r = r*1664525 + 1013904223 // constants from Numerical Recipes
-	rand = r
-	randmu.Unlock()
-	return strconv.Itoa(int(1e9 + r%1e9))[1:]
-}

+ 0 - 27
vendor/github.com/cznic/fileutil/fileutil_arm.go

@@ -1,27 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package fileutil
-
-import (
-	"io"
-	"os"
-)
-
-const hasPunchHole = false
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. Not supported on ARM.
-func PunchHole(f *os.File, off, len int64) error {
-	return nil
-}
-
-// Fadvise predeclares an access pattern for file data.  See also 'man 2
-// posix_fadvise'. Not supported on ARM.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	return nil
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool { return err == io.EOF }

+ 0 - 29
vendor/github.com/cznic/fileutil/fileutil_darwin.go

@@ -1,29 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !arm
-
-package fileutil
-
-import (
-	"io"
-	"os"
-)
-
-const hasPunchHole = false
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. Not supported on OSX.
-func PunchHole(f *os.File, off, len int64) error {
-	return nil
-}
-
-// Fadvise predeclares an access pattern for file data.  See also 'man 2
-// posix_fadvise'. Not supported on OSX.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	return nil
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool { return err == io.EOF }

+ 0 - 29
vendor/github.com/cznic/fileutil/fileutil_freebsd.go

@@ -1,29 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !arm
-
-package fileutil
-
-import (
-	"io"
-	"os"
-)
-
-const hasPunchHole = false
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. Unimplemented on FreeBSD.
-func PunchHole(f *os.File, off, len int64) error {
-	return nil
-}
-
-// Fadvise predeclares an access pattern for file data.  See also 'man 2
-// posix_fadvise'. Unimplemented on FreeBSD.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	return nil
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool { return err == io.EOF }

+ 0 - 98
vendor/github.com/cznic/fileutil/fileutil_linux.go

@@ -1,98 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !arm
-
-package fileutil
-
-import (
-	"bytes"
-	"io"
-	"io/ioutil"
-	"os"
-	"strconv"
-	"syscall"
-)
-
-const hasPunchHole = true
-
-func n(s []byte) byte {
-	for i, c := range s {
-		if c < '0' || c > '9' {
-			s = s[:i]
-			break
-		}
-	}
-	v, _ := strconv.Atoi(string(s))
-	return byte(v)
-}
-
-func init() {
-	b, err := ioutil.ReadFile("/proc/sys/kernel/osrelease")
-	if err != nil {
-		panic(err)
-	}
-
-	tokens := bytes.Split(b, []byte("."))
-	if len(tokens) > 3 {
-		tokens = tokens[:3]
-	}
-	switch len(tokens) {
-	case 3:
-		// Supported since kernel 2.6.38
-		if bytes.Compare([]byte{n(tokens[0]), n(tokens[1]), n(tokens[2])}, []byte{2, 6, 38}) < 0 {
-			puncher = func(*os.File, int64, int64) error { return nil }
-		}
-	case 2:
-		if bytes.Compare([]byte{n(tokens[0]), n(tokens[1])}, []byte{2, 7}) < 0 {
-			puncher = func(*os.File, int64, int64) error { return nil }
-		}
-	default:
-		puncher = func(*os.File, int64, int64) error { return nil }
-	}
-}
-
-var puncher = func(f *os.File, off, len int64) error {
-	const (
-		/*
-			/usr/include/linux$ grep FL_ falloc.h
-		*/
-		_FALLOC_FL_KEEP_SIZE  = 0x01 // default is extend size
-		_FALLOC_FL_PUNCH_HOLE = 0x02 // de-allocates range
-	)
-
-	_, _, errno := syscall.Syscall6(
-		syscall.SYS_FALLOCATE,
-		uintptr(f.Fd()),
-		uintptr(_FALLOC_FL_KEEP_SIZE|_FALLOC_FL_PUNCH_HOLE),
-		uintptr(off),
-		uintptr(len),
-		0, 0)
-	if errno != 0 {
-		return os.NewSyscallError("SYS_FALLOCATE", errno)
-	}
-	return nil
-}
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. No-op for kernels < 2.6.38 (or < 2.7).
-func PunchHole(f *os.File, off, len int64) error {
-	return puncher(f, off, len)
-}
-
-// Fadvise predeclares an access pattern for file data.  See also 'man 2
-// posix_fadvise'.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	_, _, errno := syscall.Syscall6(
-		syscall.SYS_FADVISE64,
-		uintptr(f.Fd()),
-		uintptr(off),
-		uintptr(len),
-		uintptr(advice),
-		0, 0)
-	return os.NewSyscallError("SYS_FADVISE64", errno)
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool { return err == io.EOF }

+ 0 - 29
vendor/github.com/cznic/fileutil/fileutil_netbsd.go

@@ -1,29 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build !arm
-
-package fileutil
-
-import (
-	"io"
-	"os"
-)
-
-const hasPunchHole = false
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. Similar to FreeBSD, this is
-// unimplemented.
-func PunchHole(f *os.File, off, len int64) error {
-	return nil
-}
-
-// Unimplemented on NetBSD.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	return nil
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool { return err == io.EOF }

+ 0 - 27
vendor/github.com/cznic/fileutil/fileutil_openbsd.go

@@ -1,27 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package fileutil
-
-import (
-	"io"
-	"os"
-)
-
-const hasPunchHole = false
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. Similar to FreeBSD, this is
-// unimplemented.
-func PunchHole(f *os.File, off, len int64) error {
-	return nil
-}
-
-// Unimplemented on OpenBSD.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	return nil
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool { return err == io.EOF }

+ 0 - 27
vendor/github.com/cznic/fileutil/fileutil_plan9.go

@@ -1,27 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package fileutil
-
-import (
-	"io"
-	"os"
-)
-
-const hasPunchHole = false
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. Unimplemented on Plan 9.
-func PunchHole(f *os.File, off, len int64) error {
-	return nil
-}
-
-// Fadvise predeclares an access pattern for file data.  See also 'man 2
-// posix_fadvise'. Unimplemented on Plan 9.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	return nil
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool { return err == io.EOF }

+ 0 - 29
vendor/github.com/cznic/fileutil/fileutil_solaris.go

@@ -1,29 +0,0 @@
-// Copyright (c) 2013 jnml. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build go1.3
-
-package fileutil
-
-import (
-	"io"
-	"os"
-)
-
-const hasPunchHole = false
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. Not supported on Solaris.
-func PunchHole(f *os.File, off, len int64) error {
-	return nil
-}
-
-// Fadvise predeclares an access pattern for file data.  See also 'man 2
-// posix_fadvise'. Not supported on Solaris.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	return nil
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool { return err == io.EOF }

+ 0 - 185
vendor/github.com/cznic/fileutil/fileutil_windows.go

@@ -1,185 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package fileutil
-
-import (
-	"io"
-	"os"
-	"sync"
-	"syscall"
-	"unsafe"
-)
-
-const hasPunchHole = true
-
-// PunchHole deallocates space inside a file in the byte range starting at
-// offset and continuing for len bytes. Not supported on Windows.
-func PunchHole(f *os.File, off, len int64) error {
-	return puncher(f, off, len)
-}
-
-// Fadvise predeclares an access pattern for file data.  See also 'man 2
-// posix_fadvise'. Not supported on Windows.
-func Fadvise(f *os.File, off, len int64, advice FadviseAdvice) error {
-	return nil
-}
-
-// IsEOF reports whether err is an EOF condition.
-func IsEOF(err error) bool {
-	if err == io.EOF {
-		return true
-	}
-
-	// http://social.technet.microsoft.com/Forums/windowsserver/en-US/1a16311b-c625-46cf-830b-6a26af488435/how-to-solve-error-38-0x26-errorhandleeof-using-fsctlgetretrievalpointers
-	x, ok := err.(*os.PathError)
-	return ok && x.Op == "read" && x.Err.(syscall.Errno) == 0x26
-}
-
-var (
-	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
-
-	procDeviceIOControl = modkernel32.NewProc("DeviceIoControl")
-
-	sparseFilesMu sync.Mutex
-	sparseFiles   map[uintptr]struct{}
-)
-
-func init() {
-	// sparseFiles is an fd set for already "sparsed" files - according to
-	// msdn.microsoft.com/en-us/library/windows/desktop/aa364225(v=vs.85).aspx
-	// the file handles are unique per process.
-	sparseFiles = make(map[uintptr]struct{})
-}
-
-// puncHoleWindows punches a hole into the given file starting at offset,
-// measuring "size" bytes
-// (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364597%28v=vs.85%29.aspx)
-func puncher(file *os.File, offset, size int64) error {
-	if err := ensureFileSparse(file); err != nil {
-		return err
-	}
-
-	// http://msdn.microsoft.com/en-us/library/windows/desktop/aa364411%28v=vs.85%29.aspx
-	// typedef struct _FILE_ZERO_DATA_INFORMATION {
-	//  LARGE_INTEGER FileOffset;
-	//  LARGE_INTEGER BeyondFinalZero;
-	//} FILE_ZERO_DATA_INFORMATION, *PFILE_ZERO_DATA_INFORMATION;
-	type fileZeroDataInformation struct {
-		FileOffset, BeyondFinalZero int64
-	}
-
-	lpInBuffer := fileZeroDataInformation{
-		FileOffset:      offset,
-		BeyondFinalZero: offset + size}
-	return deviceIOControl(false, file.Fd(), uintptr(unsafe.Pointer(&lpInBuffer)), 16)
-}
-
-// // http://msdn.microsoft.com/en-us/library/windows/desktop/cc948908%28v=vs.85%29.aspx
-// type fileSetSparseBuffer struct {
-//	 SetSparse bool
-// }
-
-func ensureFileSparse(file *os.File) (err error) {
-	fd := file.Fd()
-	sparseFilesMu.Lock()
-	if _, ok := sparseFiles[fd]; ok {
-		sparseFilesMu.Unlock()
-		return nil
-	}
-
-	if err = deviceIOControl(true, fd, 0, 0); err == nil {
-		sparseFiles[fd] = struct{}{}
-	}
-	sparseFilesMu.Unlock()
-	return err
-}
-
-func deviceIOControl(setSparse bool, fd, inBuf, inBufLen uintptr) (err error) {
-	const (
-		//http://source.winehq.org/source/include/winnt.h#L4605
-		file_read_data  = 1
-		file_write_data = 2
-
-		// METHOD_BUFFERED	0
-		method_buffered = 0
-		// FILE_ANY_ACCESS   0
-		file_any_access = 0
-		// FILE_DEVICE_FILE_SYSTEM   0x00000009
-		file_device_file_system = 0x00000009
-		// FILE_SPECIAL_ACCESS   (FILE_ANY_ACCESS)
-		file_special_access = file_any_access
-		file_read_access    = file_read_data
-		file_write_access   = file_write_data
-
-		// http://source.winehq.org/source/include/winioctl.h
-		// #define CTL_CODE 	(  	DeviceType,
-		//		Function,
-		//		Method,
-		//		Access  		 )
-		//    ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)
-
-		// FSCTL_SET_COMPRESSION   CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 16, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
-		fsctl_set_compression = (file_device_file_system << 16) | ((file_read_access | file_write_access) << 14) | (16 << 2) | method_buffered
-		// FSCTL_SET_SPARSE   CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 49, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
-		fsctl_set_sparse = (file_device_file_system << 16) | (file_special_access << 14) | (49 << 2) | method_buffered
-		// FSCTL_SET_ZERO_DATA   CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 50, METHOD_BUFFERED, FILE_WRITE_DATA)
-		fsctl_set_zero_data = (file_device_file_system << 16) | (file_write_data << 14) | (50 << 2) | method_buffered
-	)
-	retPtr := uintptr(unsafe.Pointer(&(make([]byte, 8)[0])))
-	var r1 uintptr
-	var e1 syscall.Errno
-	if setSparse {
-		// BOOL
-		// WINAPI
-		// DeviceIoControl( (HANDLE) hDevice,                      // handle to a file
-		//                  FSCTL_SET_SPARSE,                      // dwIoControlCode
-		//                  (PFILE_SET_SPARSE_BUFFER) lpInBuffer,  // input buffer
-		//                  (DWORD) nInBufferSize,                 // size of input buffer
-		//                  NULL,                                  // lpOutBuffer
-		//                  0,                                     // nOutBufferSize
-		//                  (LPDWORD) lpBytesReturned,             // number of bytes returned
-		//                  (LPOVERLAPPED) lpOverlapped );         // OVERLAPPED structure
-		r1, _, e1 = syscall.Syscall9(procDeviceIOControl.Addr(), 8,
-			fd,
-			uintptr(fsctl_set_sparse),
-			// If the lpInBuffer parameter is NULL, the operation will behave the same as if the SetSparse member of the FILE_SET_SPARSE_BUFFER structure were TRUE. In other words, the operation sets the file to a sparse file.
-			0, // uintptr(unsafe.Pointer(&lpInBuffer)),
-			0, // 1,
-			0,
-			0,
-			retPtr,
-			0,
-			0)
-	} else {
-		// BOOL
-		// WINAPI
-		// DeviceIoControl( (HANDLE) hDevice,              // handle to a file
-		//                  FSCTL_SET_ZERO_DATA,           // dwIoControlCode
-		//                  (LPVOID) lpInBuffer,           // input buffer
-		//                  (DWORD) nInBufferSize,         // size of input buffer
-		//                  NULL,                          // lpOutBuffer
-		//                  0,                             // nOutBufferSize
-		//                  (LPDWORD) lpBytesReturned,     // number of bytes returned
-		//                  (LPOVERLAPPED) lpOverlapped ); // OVERLAPPED structure
-		r1, _, e1 = syscall.Syscall9(procDeviceIOControl.Addr(), 8,
-			fd,
-			uintptr(fsctl_set_zero_data),
-			inBuf,
-			inBufLen,
-			0,
-			0,
-			retPtr,
-			0,
-			0)
-	}
-	if r1 == 0 {
-		if e1 != 0 {
-			err = error(e1)
-		} else {
-			err = syscall.EINVAL
-		}
-	}
-	return err
-}

+ 0 - 13
vendor/github.com/cznic/fileutil/test_deps.go

@@ -1,13 +0,0 @@
-// Copyright (c) 2014 The fileutil Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// blame: jnml, labs.nic.cz
-
-package fileutil
-
-// Pull test dependencies too.
-// Enables easy 'go test X' after 'go get X'
-import (
-// nothing yet
-)

+ 0 - 60
vendor/github.com/cznic/internal/buffer/Makefile

@@ -1,60 +0,0 @@
-# Copyright 2016 The Internal Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-.PHONY:	all clean cover cpu editor internalError later mem nuke todo edit
-
-grep=--include=*.go --include=*.l --include=*.y --include=*.yy
-ngrep='TODOOK\|parser\.go\|scanner\.go\|.*_string\.go'
-
-all: editor
-	go vet 2>&1 | grep -v $(ngrep) || true
-	go vet 2>&1 | grep -v $(ngrep) || true
-	golint 2>&1 | grep -v $(ngrep) || true
-	make todo
-	unused . || true
-	misspell *.go
-	gosimple || true
-	codesweep || true
-	maligned || true
-	unconvert -apply
-
-clean:
-	go clean
-	rm -f *~ *.test *.out
-
-cover:
-	t=$(shell tempfile) ; go test -coverprofile $$t && go tool cover -html $$t && unlink $$t
-
-cpu: clean
-	go test -run @ -bench . -cpuprofile cpu.out
-	go tool pprof -lines *.test cpu.out
-
-edit:
-	@ 1>/dev/null 2>/dev/null gvim -p Makefile log *.go
-
-editor:
-	gofmt -l -s -w *.go
-	go test -i
-	go test 2>&1 | tee log
-	go install
-
-internalError:
-	egrep -ho '"internal error.*"' *.go | sort | cat -n
-
-later:
-	@grep -n $(grep) LATER * || true
-	@grep -n $(grep) MAYBE * || true
-
-mem: clean
-	go test -run @ -bench . -memprofile mem.out -memprofilerate 1 -timeout 24h
-	go tool pprof -lines -web -alloc_space *.test mem.out
-
-nuke: clean
-	go clean -i
-
-todo:
-	@grep -nr $(grep) ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* * | grep -v $(ngrep) || true
-	@grep -nr $(grep) TODO * | grep -v $(ngrep) || true
-	@grep -nr $(grep) BUG * | grep -v $(ngrep) || true
-	@grep -nr $(grep) [^[:alpha:]]println * | grep -v $(ngrep) || true

+ 0 - 146
vendor/github.com/cznic/internal/buffer/buffer.go

@@ -1,146 +0,0 @@
-// Copyright 2016 The Internal Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package buffer implements a pool of pointers to byte slices.
-//
-// Example usage pattern
-//
-//	p := buffer.Get(size)
-//	b := *p	// Now you can use b in any way you need.
-//	...
-//	// When b will not be used anymore
-//	buffer.Put(p)
-//	...
-//	// If b or p are not going out of scope soon, optionally
-//	b = nil
-//	p = nil
-//
-// Otherwise the pool cannot release the buffer on garbage collection.
-//
-// Do not do
-//
-//	p := buffer.Get(size)
-//	b := *p
-//	...
-//	buffer.Put(&b)
-//
-// or
-//
-//	b := *buffer.Get(size)
-//	...
-//	buffer.Put(&b)
-package buffer
-
-import (
-	"github.com/cznic/internal/slice"
-	"io"
-)
-
-// CGet returns a pointer to a byte slice of len size. The pointed to byte
-// slice is zeroed up to its cap. CGet panics for size < 0.
-//
-// CGet is safe for concurrent use by multiple goroutines.
-func CGet(size int) *[]byte { return slice.Bytes.CGet(size).(*[]byte) }
-
-// Get returns a pointer to a byte slice of len size. The pointed to byte slice
-// is not zeroed. Get panics for size < 0.
-//
-// Get is safe for concurrent use by multiple goroutines.
-func Get(size int) *[]byte { return slice.Bytes.Get(size).(*[]byte) }
-
-// Put puts a pointer to a byte slice into a pool for possible later reuse by
-// CGet or Get.
-//
-// Put is safe for concurrent use by multiple goroutines.
-func Put(p *[]byte) { slice.Bytes.Put(p) }
-
-// Bytes is similar to bytes.Buffer but may generate less garbage when properly
-// Closed. Zero value is ready to use.
-type Bytes struct {
-	p *[]byte
-}
-
-// Bytes return the content of b. The result is R/O.
-func (b *Bytes) Bytes() []byte {
-	if b.p != nil {
-		return *b.p
-	}
-
-	return nil
-}
-
-// Close will recycle the underlying storage, if any. After Close, b is again
-// the zero value.
-func (b *Bytes) Close() error {
-	if b.p != nil {
-		Put(b.p)
-		b.p = nil
-	}
-	return nil
-}
-
-// Len returns the size of content in b.
-func (b *Bytes) Len() int {
-	if b.p != nil {
-		return len(*b.p)
-	}
-
-	return 0
-}
-
-// Reset discard the content of Bytes while keeping the internal storage, if any.
-func (b *Bytes) Reset() {
-	if b.p != nil {
-		*b.p = (*b.p)[:0]
-	}
-}
-
-// Write writes p into b and returns (len(p), nil).
-func (b *Bytes) Write(p []byte) (int, error) {
-	n := b.Len()
-	b.grow(n + len(p))
-	copy((*b.p)[n:], p)
-	return len(p), nil
-}
-
-// WriteByte writes p into b and returns nil.
-func (b *Bytes) WriteByte(p byte) error {
-	n := b.Len()
-	b.grow(n + 1)
-	(*b.p)[n] = p
-	return nil
-}
-
-// WriteTo writes b's content to w and returns the number of bytes written to w
-// and an error, if any.
-func (b *Bytes) WriteTo(w io.Writer) (int64, error) {
-	n, err := w.Write(b.Bytes())
-	return int64(n), err
-}
-
-// WriteString writes s to b and returns (len(s), nil).
-func (b *Bytes) WriteString(s string) (int, error) {
-	n := b.Len()
-	b.grow(n + len(s))
-	copy((*b.p)[n:], s)
-	return len(s), nil
-}
-
-func (b *Bytes) grow(n int) {
-	if b.p != nil {
-		if n <= cap(*b.p) {
-			*b.p = (*b.p)[:n]
-			return
-		}
-
-		np := Get(2 * n)
-		*np = (*np)[:n]
-		copy(*np, *b.p)
-		Put(b.p)
-		b.p = np
-		return
-	}
-
-	b.p = Get(n)
-}

+ 0 - 55
vendor/github.com/cznic/internal/file/Makefile

@@ -1,55 +0,0 @@
-# Copyright 2016 The Internal Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-.PHONY:	all clean cover cpu editor internalError later mem nuke todo edit
-
-grep=--include=*.go --include=*.l --include=*.y --include=*.yy
-ngrep='TODOOK\|parser\.go\|scanner\.go\|.*_string\.go'
-
-all: editor
-	go vet 2>&1 | grep -v $(ngrep) || true
-	golint 2>&1 | grep -v $(ngrep) || true
-	make todo
-	unused . || true
-	misspell *.go
-	gosimple || true
-
-clean:
-	go clean
-	rm -f *~ *.test *.out
-
-cover:
-	t=$(shell tempfile) ; go test -coverprofile $$t && go tool cover -html $$t && unlink $$t
-
-cpu: clean
-	go test -run @ -bench . -cpuprofile cpu.out
-	go tool pprof -lines *.test cpu.out
-
-edit:
-	@ 1>/dev/null 2>/dev/null gvim -p Makefile log *.go
-
-editor:
-	gofmt -l -s -w *.go
-	go test 2>&1 | tee log
-	go build
-
-internalError:
-	egrep -ho '"internal error.*"' *.go | sort | cat -n
-
-later:
-	@grep -n $(grep) LATER * || true
-	@grep -n $(grep) MAYBE * || true
-
-mem: clean
-	go test -run @ -bench BenchmarkReadWrite -benchmem -memprofile mem.out -memprofilerate 1 -timeout 24h
-	go tool pprof -lines -web -alloc_space *.test mem.out
-
-nuke: clean
-	go clean -i
-
-todo:
-	@grep -nr $(grep) ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* * | grep -v $(ngrep) || true
-	@grep -nr $(grep) TODO * | grep -v $(ngrep) || true
-	@grep -nr $(grep) BUG * | grep -v $(ngrep) || true
-	@grep -nr $(grep) [^[:alpha:]]println * | grep -v $(ngrep) || true

+ 0 - 434
vendor/github.com/cznic/internal/file/file.go

@@ -1,434 +0,0 @@
-// Copyright 2016 The Internal Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package file provides an os.File-like interface of a memory mapped file.
-package file
-
-import (
-	"fmt"
-	"io"
-	"os"
-	"time"
-
-	"github.com/cznic/fileutil"
-	"github.com/cznic/internal/buffer"
-	"github.com/cznic/mathutil"
-	"github.com/edsrzf/mmap-go"
-)
-
-const copyBufSize = 1 << 20 // 1 MB.
-
-var (
-	_ Interface = (*mem)(nil)
-	_ Interface = (*file)(nil)
-
-	_ os.FileInfo = stat{}
-
-	sysPage = os.Getpagesize()
-)
-
-// Interface is a os.File-like entity.
-type Interface interface {
-	io.ReaderAt
-	io.ReaderFrom
-	io.WriterAt
-	io.WriterTo
-
-	Close() error
-	Stat() (os.FileInfo, error)
-	Sync() error
-	Truncate(int64) error
-}
-
-// Open returns a new Interface backed by f, or an error, if any.
-func Open(f *os.File) (Interface, error) { return newFile(f, 1<<30, 20) }
-
-// OpenMem returns a new Interface, or an error, if any. The Interface content
-// is volatile, it's backed only by process' memory.
-func OpenMem(name string) (Interface, error) { return newMem(name, 18), nil }
-
-type memMap map[int64]*[]byte
-
-type mem struct {
-	m       memMap
-	modTime time.Time
-	name    string
-	pgBits  uint
-	pgMask  int
-	pgSize  int
-	size    int64
-}
-
-func newMem(name string, pgBits uint) *mem {
-	pgSize := 1 << pgBits
-	return &mem{
-		m:       memMap{},
-		modTime: time.Now(),
-		name:    name,
-		pgBits:  pgBits,
-		pgMask:  pgSize - 1,
-		pgSize:  pgSize,
-	}
-}
-
-func (f *mem) IsDir() bool                               { return false }
-func (f *mem) Mode() os.FileMode                         { return os.ModeTemporary + 0600 }
-func (f *mem) ModTime() time.Time                        { return f.modTime }
-func (f *mem) Name() string                              { return f.name }
-func (f *mem) ReadFrom(r io.Reader) (n int64, err error) { return readFrom(f, r) }
-func (f *mem) Size() (n int64)                           { return f.size }
-func (f *mem) Stat() (os.FileInfo, error)                { return f, nil }
-func (f *mem) Sync() error                               { return nil }
-func (f *mem) Sys() interface{}                          { return nil }
-func (f *mem) WriteTo(w io.Writer) (n int64, err error)  { return writeTo(f, w) }
-
-func (f *mem) Close() error {
-	f.Truncate(0)
-	f.m = nil
-	return nil
-}
-
-func (f *mem) ReadAt(b []byte, off int64) (n int, err error) {
-	avail := f.size - off
-	pi := off >> f.pgBits
-	po := int(off) & f.pgMask
-	rem := len(b)
-	if int64(rem) >= avail {
-		rem = int(avail)
-		err = io.EOF
-	}
-	var zeroPage *[]byte
-	for rem != 0 && avail > 0 {
-		pg := f.m[pi]
-		if pg == nil {
-			if zeroPage == nil {
-				zeroPage = buffer.CGet(f.pgSize)
-				defer buffer.Put(zeroPage)
-			}
-			pg = zeroPage
-		}
-		nc := copy(b[:mathutil.Min(rem, f.pgSize)], (*pg)[po:])
-		pi++
-		po = 0
-		rem -= nc
-		n += nc
-		b = b[nc:]
-	}
-	return n, err
-}
-
-func (f *mem) Truncate(size int64) (err error) {
-	if size < 0 {
-		return fmt.Errorf("invalid truncate size: %d", size)
-	}
-
-	first := size >> f.pgBits
-	if size&int64(f.pgMask) != 0 {
-		first++
-	}
-	last := f.size >> f.pgBits
-	if f.size&int64(f.pgMask) != 0 {
-		last++
-	}
-	for ; first <= last; first++ {
-		if p := f.m[first]; p != nil {
-			buffer.Put(p)
-		}
-		delete(f.m, first)
-	}
-
-	f.size = size
-	return nil
-}
-
-func (f *mem) WriteAt(b []byte, off int64) (n int, err error) {
-	pi := off >> f.pgBits
-	po := int(off) & f.pgMask
-	n = len(b)
-	rem := n
-	var nc int
-	for rem != 0 {
-		pg := f.m[pi]
-		if pg == nil {
-			pg = buffer.CGet(f.pgSize)
-			f.m[pi] = pg
-		}
-		nc = copy((*pg)[po:], b)
-		pi++
-		po = 0
-		rem -= nc
-		b = b[nc:]
-	}
-	f.size = mathutil.MaxInt64(f.size, off+int64(n))
-	return n, nil
-}
-
-type stat struct {
-	os.FileInfo
-	size int64
-}
-
-func (s stat) Size() int64 { return s.size }
-
-type fileMap map[int64]mmap.MMap
-
-type file struct {
-	f        *os.File
-	m        fileMap
-	maxPages int
-	pgBits   uint
-	pgMask   int
-	pgSize   int
-	size     int64
-	fsize    int64
-}
-
-func newFile(f *os.File, maxSize int64, pgBits uint) (*file, error) {
-	if maxSize < 0 {
-		panic("internal error")
-	}
-
-	pgSize := 1 << pgBits
-	switch {
-	case sysPage > pgSize:
-		pgBits = uint(mathutil.Log2Uint64(uint64(sysPage)))
-	default:
-		pgBits = uint(mathutil.Log2Uint64(uint64(pgSize / sysPage * sysPage)))
-	}
-	pgSize = 1 << pgBits
-	fi := &file{
-		f: f,
-		m: fileMap{},
-		maxPages: int(mathutil.MinInt64(
-			1024,
-			mathutil.MaxInt64(maxSize/int64(pgSize), 1)),
-		),
-		pgBits: pgBits,
-		pgMask: pgSize - 1,
-		pgSize: pgSize,
-	}
-	info, err := f.Stat()
-	if err != nil {
-		return nil, err
-	}
-
-	if err = fi.Truncate(info.Size()); err != nil {
-		return nil, err
-	}
-
-	return fi, nil
-}
-
-func (f *file) ReadFrom(r io.Reader) (n int64, err error) { return readFrom(f, r) }
-func (f *file) Sync() (err error)                         { return f.f.Sync() }
-func (f *file) WriteTo(w io.Writer) (n int64, err error)  { return writeTo(f, w) }
-
-func (f *file) Close() (err error) {
-	for _, p := range f.m {
-		if err = p.Unmap(); err != nil {
-			return err
-		}
-	}
-
-	if err = f.f.Truncate(f.size); err != nil {
-		return err
-	}
-
-	if err = f.f.Sync(); err != nil {
-		return err
-	}
-
-	if err = f.f.Close(); err != nil {
-		return err
-	}
-
-	f.m = nil
-	f.f = nil
-	return nil
-}
-
-func (f *file) page(index int64) (mmap.MMap, error) {
-	if len(f.m) == f.maxPages {
-		for i, p := range f.m {
-			if err := p.Unmap(); err != nil {
-				return nil, err
-			}
-
-			delete(f.m, i)
-			break
-		}
-	}
-
-	off := index << f.pgBits
-	fsize := off + int64(f.pgSize)
-	if fsize > f.fsize {
-		if err := f.f.Truncate(fsize); err != nil {
-			return nil, err
-		}
-
-		f.fsize = fsize
-	}
-	p, err := mmap.MapRegion(f.f, f.pgSize, mmap.RDWR, 0, off)
-	if err != nil {
-		return nil, err
-	}
-
-	f.m[index] = p
-	return p, nil
-}
-
-func (f *file) ReadAt(b []byte, off int64) (n int, err error) {
-	avail := f.size - off
-	pi := off >> f.pgBits
-	po := int(off) & f.pgMask
-	rem := len(b)
-	if int64(rem) >= avail {
-		rem = int(avail)
-		err = io.EOF
-	}
-	for rem != 0 && avail > 0 {
-		pg := f.m[pi]
-		if pg == nil {
-			if pg, err = f.page(pi); err != nil {
-				return n, err
-			}
-		}
-		nc := copy(b[:mathutil.Min(rem, f.pgSize)], pg[po:])
-		pi++
-		po = 0
-		rem -= nc
-		n += nc
-		b = b[nc:]
-	}
-	return n, err
-}
-
-func (f *file) Stat() (os.FileInfo, error) {
-	fi, err := f.f.Stat()
-	if err != nil {
-		return nil, err
-	}
-
-	return stat{fi, f.size}, nil
-}
-
-func (f *file) Truncate(size int64) (err error) {
-	if size < 0 {
-		return fmt.Errorf("invalid truncate size: %d", size)
-	}
-
-	first := size >> f.pgBits
-	if size&int64(f.pgMask) != 0 {
-		first++
-	}
-	last := f.size >> f.pgBits
-	if f.size&int64(f.pgMask) != 0 {
-		last++
-	}
-	for ; first <= last; first++ {
-		if p := f.m[first]; p != nil {
-			if err := p.Unmap(); err != nil {
-				return err
-			}
-		}
-
-		delete(f.m, first)
-	}
-
-	f.size = size
-	fsize := (size + int64(f.pgSize) - 1) &^ int64(f.pgMask)
-	if fsize != f.fsize {
-		if err := f.f.Truncate(fsize); err != nil {
-			return err
-		}
-
-	}
-	f.fsize = fsize
-	return nil
-}
-
-func (f *file) WriteAt(b []byte, off int64) (n int, err error) {
-	pi := off >> f.pgBits
-	po := int(off) & f.pgMask
-	n = len(b)
-	rem := n
-	var nc int
-	for rem != 0 {
-		pg := f.m[pi]
-		if pg == nil {
-			pg, err = f.page(pi)
-			if err != nil {
-				return n, err
-			}
-		}
-		nc = copy(pg[po:], b)
-		pi++
-		po = 0
-		rem -= nc
-		b = b[nc:]
-	}
-	f.size = mathutil.MaxInt64(f.size, off+int64(n))
-	return n, nil
-}
-
-// ----------------------------------------------------------------------------
-
-func readFrom(f Interface, r io.Reader) (n int64, err error) {
-	f.Truncate(0)
-	p := buffer.Get(copyBufSize)
-	b := *p
-	defer buffer.Put(p)
-
-	var off int64
-	var werr error
-	for {
-		rn, rerr := r.Read(b)
-		if rn != 0 {
-			_, werr = f.WriteAt(b[:rn], off)
-			n += int64(rn)
-			off += int64(rn)
-		}
-		if rerr != nil {
-			if !fileutil.IsEOF(rerr) {
-				err = rerr
-			}
-			break
-		}
-
-		if werr != nil {
-			err = werr
-			break
-		}
-	}
-	return n, err
-}
-
-func writeTo(f Interface, w io.Writer) (n int64, err error) {
-	p := buffer.Get(copyBufSize)
-	b := *p
-	defer buffer.Put(p)
-
-	var off int64
-	var werr error
-	for {
-		rn, rerr := f.ReadAt(b, off)
-		if rn != 0 {
-			_, werr = w.Write(b[:rn])
-			n += int64(rn)
-			off += int64(rn)
-		}
-		if rerr != nil {
-			if !fileutil.IsEOF(rerr) {
-				err = rerr
-			}
-			break
-		}
-
-		if werr != nil {
-			err = werr
-			break
-		}
-	}
-	return n, err
-}

+ 0 - 55
vendor/github.com/cznic/internal/slice/Makefile

@@ -1,55 +0,0 @@
-# Copyright 2016 The Internal Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-.PHONY:	all clean cover cpu editor internalError later mem nuke todo edit
-
-grep=--include=*.go --include=*.l --include=*.y --include=*.yy
-ngrep='TODOOK\|parser\.go\|scanner\.go\|.*_string\.go'
-
-all: editor
-	go vet 2>&1 | grep -v $(ngrep) || true
-	golint 2>&1 | grep -v $(ngrep) || true
-	make todo
-	unused . || true
-	misspell *.go
-	gosimple || true
-
-clean:
-	go clean
-	rm -f *~ *.test *.out
-
-cover:
-	t=$(shell tempfile) ; go test -coverprofile $$t && go tool cover -html $$t && unlink $$t
-
-cpu: clean
-	go test -run @ -bench . -cpuprofile cpu.out
-	go tool pprof -lines *.test cpu.out
-
-edit:
-	@ 1>/dev/null 2>/dev/null gvim -p Makefile log *.go
-
-editor:
-	gofmt -l -s -w *.go
-	go test 2>&1 | tee log
-	go build
-
-internalError:
-	egrep -ho '"internal error.*"' *.go | sort | cat -n
-
-later:
-	@grep -n $(grep) LATER * || true
-	@grep -n $(grep) MAYBE * || true
-
-mem: clean
-	go test -run @ -bench . -memprofile mem.out -memprofilerate 1 -timeout 24h
-	go tool pprof -lines -web -alloc_space *.test mem.out
-
-nuke: clean
-	go clean -i
-
-todo:
-	@grep -nr $(grep) ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* * | grep -v $(ngrep) || true
-	@grep -nr $(grep) TODO * | grep -v $(ngrep) || true
-	@grep -nr $(grep) BUG * | grep -v $(ngrep) || true
-	@grep -nr $(grep) [^[:alpha:]]println * | grep -v $(ngrep) || true

+ 0 - 173
vendor/github.com/cznic/internal/slice/pool.go

@@ -1,173 +0,0 @@
-// Copyright 2016 The Internal Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package slice implements pools of pointers to slices.
-package slice
-
-import (
-	"sync"
-
-	"github.com/cznic/mathutil"
-)
-
-var (
-	// Bytes is a ready to use *[]byte Pool.
-	Bytes *Pool
-	// Ints is a ready to use *[]int Pool.
-	Ints *Pool
-)
-
-func init() {
-	Bytes = newBytes()
-	Ints = NewPool(
-		func(size int) interface{} { // create
-			b := make([]int, size)
-			return &b
-		},
-		func(s interface{}) { // clear
-			b := *s.(*[]int)
-			b = b[:cap(b)]
-			for i := range b {
-				b[i] = 0
-			}
-		},
-		func(s interface{}, size int) { // setSize
-			p := s.(*[]int)
-			*p = (*p)[:size]
-		},
-		func(s interface{}) int { return cap(*s.(*[]int)) }, // cap
-	)
-}
-
-func newBytes() *Pool {
-	return NewPool(
-		func(size int) interface{} { // create
-			b := make([]byte, size)
-			return &b
-		},
-		func(s interface{}) { // clear
-			b := *s.(*[]byte)
-			b = b[:cap(b)]
-			for i := range b {
-				b[i] = 0
-			}
-		},
-		func(s interface{}, size int) { // setSize
-			p := s.(*[]byte)
-			*p = (*p)[:size]
-		},
-		func(s interface{}) int { return cap(*s.(*[]byte)) }, // cap
-	)
-}
-
-// Pool implements a pool of pointers to slices.
-//
-// Example usage pattern (assuming pool is, for example, a *[]byte Pool)
-//
-//	p := pool.Get(size).(*[]byte)
-//	b := *p	// Now you can use b in any way you need.
-//	...
-//	// When b will not be used anymore
-//	pool.Put(p)
-//	...
-//	// If b or p are not going out of scope soon, optionally
-//	b = nil
-//	p = nil
-//
-// Otherwise the pool cannot release the slice on garbage collection.
-//
-// Do not do
-//
-//	p := pool.Get(size).(*[]byte)
-//	b := *p
-//	...
-//	pool.Put(&b)
-//
-// or
-//
-//	b := *pool.Get(size).(*[]byte)
-//	...
-//	pool.Put(&b)
-type Pool struct {
-	cap     func(interface{}) int
-	clear   func(interface{})
-	m       [63]sync.Pool
-	null    interface{}
-	setSize func(interface{}, int)
-}
-
-// NewPool returns a newly created Pool. Assuming the desired slice type is
-// []T:
-//
-// The create function returns a *[]T of len == cap == size.
-//
-// The argument of clear is *[]T and the function sets all the slice elements
-// to the respective zero value.
-//
-// The setSize function gets a *[]T and sets its len to size.
-//
-// The cap function gets a *[]T and returns its capacity.
-func NewPool(
-	create func(size int) interface{},
-	clear func(interface{}),
-	setSize func(p interface{}, size int),
-	cap func(p interface{}) int,
-) *Pool {
-	p := &Pool{clear: clear, setSize: setSize, cap: cap, null: create(0)}
-	for i := range p.m {
-		size := 1 << uint(i)
-		p.m[i] = sync.Pool{New: func() interface{} {
-			// 0:     1 -      1
-			// 1:    10 -     10
-			// 2:    11 -    100
-			// 3:   101 -   1000
-			// 4:  1001 -  10000
-			// 5: 10001 - 100000
-			return create(size)
-		}}
-	}
-	return p
-}
-
-// CGet returns a *[]T of len size. The pointed to slice is zeroed up to its
-// cap. CGet panics for size < 0.
-//
-// CGet is safe for concurrent use by multiple goroutines.
-func (p *Pool) CGet(size int) interface{} {
-	s := p.Get(size)
-	p.clear(s)
-	return s
-}
-
-// Get returns a *[]T of len size. The pointed to slice is not zeroed. Get
-// panics for size < 0.
-//
-// Get is safe for concurrent use by multiple goroutines.
-func (p *Pool) Get(size int) interface{} {
-	var index int
-	switch {
-	case size < 0:
-		panic("Pool.Get: negative size")
-	case size == 0:
-		return p.null
-	case size > 1:
-		index = mathutil.Log2Uint64(uint64(size-1)) + 1
-	}
-	s := p.m[index].Get()
-	p.setSize(s, size)
-	return s
-}
-
-// Put puts a *[]T into a pool for possible later reuse by CGet or Get. Put
-// panics is its argument is not of type *[]T.
-//
-// Put is safe for concurrent use by multiple goroutines.
-func (p *Pool) Put(b interface{}) {
-	size := p.cap(b)
-	if size == 0 {
-		return
-	}
-
-	p.m[mathutil.Log2Uint64(uint64(size))].Put(b)
-}

+ 0 - 11
vendor/github.com/cznic/kv/AUTHORS

@@ -1,11 +0,0 @@
-# This file lists authors for copyright purposes.  This file is distinct from
-# the CONTRIBUTORS files.  See the latter for an explanation.
-#
-# Names should be added to this file as:
-#     Name or Organization <email address>
-#
-# The email address is not required for organizations.
-#
-# Please keep the list sorted.
-
-Jan Mercl <[email protected]>

+ 0 - 13
vendor/github.com/cznic/kv/CONTRIBUTORS

@@ -1,13 +0,0 @@
-# This file lists people who contributed code to this repository.  The AUTHORS
-# file lists the copyright holders; this file lists people.
-#
-# Names should be added to this file like so:
-#     Name <email address>
-#
-# Please keep the list sorted.
-
-Brad Fitzpatrick <[email protected]>
-Jan Mercl <[email protected]>
-Patrick Mézard <[email protected]>
-Salmān Aljammāz <[email protected]>
-Tamás Gulácsi <[email protected]>

+ 0 - 27
vendor/github.com/cznic/kv/LICENSE

@@ -1,27 +0,0 @@
-Copyright (c) 2014 The kv Authors. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-   * Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-   * 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.
-   * Neither the names of the authors nor the names of the
-contributors may be used to endorse or promote products derived from
-this software without specific prior written permission.
-
-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
-OWNER 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.

+ 0 - 55
vendor/github.com/cznic/kv/Makefile

@@ -1,55 +0,0 @@
-# Copyright 2014 The kv Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-.PHONY:	all clean cover cpu editor internalError later mem nuke todo edit
-
-grep=--include=*.go --include=*.l --include=*.y --include=*.yy
-ngrep='TODOOK\|parser\.go\|scanner\.go\|.*_string\.go'
-
-all: editor
-	go vet 2>&1 | grep -v $(ngrep) || true
-	golint 2>&1 | grep -v $(ngrep) || true
-	make todo
-	unused . || true
-	misspell *.go
-	gosimple || true
-
-clean:
-	go clean
-	rm -f *~ *.test *.out _testdata/temp*
-
-cover:
-	t=$(shell tempfile) ; go test -coverprofile $$t && go tool cover -html $$t && unlink $$t
-
-cpu: clean
-	go test -run @ -bench . -cpuprofile cpu.out
-	go tool pprof -lines *.test cpu.out
-
-edit:
-	gvim -p Makefile *.go
-
-editor:
-	gofmt -l -s -w *.go
-	go test 2>&1 | tee log
-	go build
-
-internalError:
-	egrep -ho '"internal error.*"' *.go | sort | cat -n
-
-later:
-	@grep -n $(grep) LATER * || true
-	@grep -n $(grep) MAYBE * || true
-
-mem: clean
-	go test -run @ -bench . -memprofile mem.out -memprofilerate 1 -timeout 24h
-	go tool pprof -lines -web -alloc_space *.test mem.out
-
-nuke: clean
-	go clean -i
-
-todo:
-	@grep -nr $(grep) ^[[:space:]]*_[[:space:]]*=[[:space:]][[:alpha:]][[:alnum:]]* * || true
-	@grep -nr $(grep) TODO * || true
-	@grep -nr $(grep) BUG * || true
-	@grep -nr $(grep) [^[:alpha:]]println * || true

+ 0 - 10
vendor/github.com/cznic/kv/README.md

@@ -1,10 +0,0 @@
-kv
-==
-
-Package kv implements a simple and easy to use persistent key/value (KV) store.
-
-Installation
-
-    $ go get github.com/cznic/kv
-
-Documentation: [godoc.org/github.com/cznic/kv](http://godoc.org/github.com/cznic/kv)

+ 0 - 86
vendor/github.com/cznic/kv/doc.go

@@ -1,86 +0,0 @@
-// Copyright 2014 The kv Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-/*
-
-Package kv implements a simple and easy to use persistent key/value (KV) store.
-
-Changelog
-
-2016-07-11: KV now uses the stable version of lldb. (github.com/cznic/lldb).
-
-The stored KV pairs are sorted in the key collation order defined by an user
-supplied 'compare' function (passed as a field in Options).
-
-Keys and Values Limits
-
-Keys, as well as the values associated with them, are opaque []bytes. Maximum
-size of a "native" key or value is 65787 bytes. Larger keys or values have to
-be composed of the "native" ones in client code.
-
-Database limits
-
-The maximum DB size kv can handle is 2^60 bytes (1 exabyte). See also [4]:
-"Block handles".
-
-ACID and transactional properties
-
-Transactions are resource limited. All changes made by a transaction are held
-in memory until the top level transaction is committed. ACID[1] implementation
-notes/details follows.
-
-Atomicity
-
-A successfully committed transaction appears (by its effects on the database)
-to be indivisible ("atomic") iff the transaction is performed in isolation. An
-aborted (via RollBack) transaction appears like it never happened under the
-same limitation.
-
-Atomic updates to the DB, via functions like Set, Inc, etc., are performed in
-their own automatic transaction. If the partial progress of any such function
-fails at any point, the automatic transaction is canceled via Rollback before
-returning from the function. A non nil error is returned in that case.
-
-Consistency
-
-All reads, including those made from any other concurrent non isolated
-transaction(s), performed during a not yet committed transaction, are dirty
-reads, i.e.  the data returned are consistent with the in-progress state of the
-open transaction, or all of the open transactions. Obviously, conflicts, data
-races and inconsistent states can happen, but iff non isolated transactions are
-performed.
-
-Performing a Rollback at a nested transaction level properly returns the
-transaction state (and data read from the DB) to what it was before the
-respective BeginTransaction.
-
-Isolation
-
-Transactions of the atomic updating functions (Set, Put, Delete ...) are always
-isolated. Transactions controlled by BeginTransaction/Commit/RollBack, are
-isolated iff their execution is serialized.
-
-Durability
-
-Transactions are committed using the two phase commit protocol(2PC)[2] and a
-write ahead log(WAL)[3]. DB recovery after a crash is performed automatically
-using data from the WAL. Last transaction data, either of an in progress
-transaction or a transaction being committed at the moment of the crash, can get
-lost.
-
-No protection from non readable files, files corrupted by other processes or by
-memory faults or other HW problems, is provided. Always properly backup your DB
-data file(s).
-
-Links
-
-Referenced from above:
-
-  [1]: http://en.wikipedia.org/wiki/ACID
-  [2]: http://en.wikipedia.org/wiki/2PC
-  [3]: http://en.wikipedia.org/wiki/Write_ahead_logging
-  [4]: http://godoc.org/github.com/cznic/lldb#Allocator
-
-*/
-package kv

+ 0 - 31
vendor/github.com/cznic/kv/etc.go

@@ -1,31 +0,0 @@
-// Copyright 2014 The kv Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package kv
-
-import (
-	"bytes"
-	"fmt"
-)
-
-type header struct {
-	magic    []byte
-	ver      byte
-	reserved []byte
-}
-
-func (h *header) rd(b []byte) error {
-	if len(b) != 16 {
-		panic("internal error")
-	}
-
-	if h.magic = b[:4]; !bytes.Equal(h.magic, []byte(magic)) {
-		return fmt.Errorf("Unknown file format")
-	}
-
-	b = b[4:]
-	h.ver = b[0]
-	h.reserved = b[1:]
-	return nil
-}

+ 0 - 851
vendor/github.com/cznic/kv/kv.go

@@ -1,851 +0,0 @@
-// Copyright 2014 The kv Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package kv
-
-import (
-	"encoding/binary"
-	"fmt"
-	"io"
-	"os"
-	"sync"
-	"time"
-
-	"github.com/cznic/fileutil"
-	"github.com/cznic/internal/buffer"
-	"github.com/cznic/lldb"
-)
-
-const (
-	magic = "\x60\xdbKV"
-)
-
-const (
-	stDisabled = iota // stDisabled must be zero
-	stIdle
-	stCollecting
-	stIdleArmed
-	stCollectingArmed
-	stCollectingTriggered
-	stEndUpdateFailed
-)
-
-func init() {
-	if stDisabled != 0 {
-		panic("stDisabled != 0")
-	}
-}
-
-// DB represents the database (the KV store).
-type DB struct {
-	acidNest      int             // Grace period nesting level
-	acidState     int             // Grace period FSM state.
-	acidTimer     *time.Timer     // Grace period timer
-	alloc         *lldb.Allocator // The machinery. Wraps filer
-	bkl           sync.Mutex      // Big Kernel Lock
-	closeMu       sync.Mutex      // Close() coordination
-	closed        bool            // it was
-	filer         lldb.Filer      // Wraps f
-	gracePeriod   time.Duration   // WAL grace period
-	isMem         bool            // No signal capture
-	lastCommitErr error           // from failed EndUpdate
-	lock          io.Closer       // The DB file lock
-	opts          *Options
-	root          *lldb.BTree // The KV layer
-	wal           *os.File    // WAL if any
-}
-
-// CreateFromFiler is like Create but accepts an arbitrary backing storage
-// provided by filer.
-//
-// For the meaning of opts please see documentation of Options.
-func CreateFromFiler(filer lldb.Filer, opts *Options) (db *DB, err error) {
-	opts = opts.clone()
-	opts._ACID = _ACIDFull
-	return create(filer, opts, false)
-}
-
-// Create creates the named DB file mode 0666 (before umask). The file must not
-// already exist. If successful, methods on the returned DB can be used for
-// I/O; the associated file descriptor has mode os.O_RDWR. If there is an
-// error, it will be of type *os.PathError.
-//
-// For the meaning of opts please see documentation of Options.
-func Create(name string, opts *Options) (db *DB, err error) {
-	opts = opts.clone()
-	opts._ACID = _ACIDFull
-	f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
-	if err != nil {
-		return
-	}
-
-	return CreateFromFiler(lldb.NewSimpleFileFiler(f), opts)
-}
-
-func create(filer lldb.Filer, opts *Options, isMem bool) (db *DB, err error) {
-	defer func() {
-		if db != nil {
-			db.opts = opts
-		}
-	}()
-	defer func() {
-		lock := opts.lock
-		if err != nil && lock != nil {
-			lock.Close()
-			db = nil
-		}
-	}()
-
-	if err = opts.check(filer.Name(), true, !isMem); err != nil {
-		return
-	}
-
-	b := [16]byte{byte(magic[0]), byte(magic[1]), byte(magic[2]), byte(magic[3]), 0x00} // ver 0x00
-	if n, err := filer.WriteAt(b[:], 0); n != 16 {
-		return nil, &os.PathError{Op: "kv.create.WriteAt", Path: filer.Name(), Err: err}
-	}
-
-	db = &DB{lock: opts.lock}
-
-	filer = lldb.NewInnerFiler(filer, 16)
-	if filer, err = opts.acidFiler(db, filer); err != nil {
-		return nil, err
-	}
-
-	db.filer = filer
-	if err = filer.BeginUpdate(); err != nil {
-		return
-	}
-
-	defer func() {
-		if e := filer.EndUpdate(); e != nil {
-			if err == nil {
-				err = e
-			}
-		}
-	}()
-
-	if db.alloc, err = lldb.NewAllocator(filer, &lldb.Options{}); err != nil {
-		return nil, &os.PathError{Op: "kv.create", Path: filer.Name(), Err: err}
-	}
-
-	db.alloc.Compress = true
-	db.isMem = isMem
-	var h int64
-	if db.root, h, err = lldb.CreateBTree(db.alloc, opts.Compare); err != nil {
-		return
-	}
-
-	if h != 1 {
-		panic("internal error")
-	}
-
-	db.wal = opts.wal
-	return
-}
-
-// CreateMem creates a new instance of an in-memory DB not backed by a disk
-// file. Memory DBs are resource limited as they are completely held in memory
-// and are not automatically persisted.
-//
-// For the meaning of opts please see documentation of Options.
-func CreateMem(opts *Options) (db *DB, err error) {
-	opts = opts.clone()
-	opts._ACID = _ACIDTransactions
-	f := lldb.NewMemFiler()
-	return create(f, opts, true)
-}
-
-// CreateTemp creates a new temporary DB in the directory dir with a basename
-// beginning with prefix and name ending in suffix. If dir is the empty string,
-// CreateTemp uses the default directory for temporary files (see os.TempDir).
-// Multiple programs calling CreateTemp simultaneously will not choose the same
-// file name for the DB. The caller can use Name() to find the pathname of the
-// DB file. It is the caller's responsibility to remove the file when no longer
-// needed.
-//
-// For the meaning of opts please see documentation of Options.
-func CreateTemp(dir, prefix, suffix string, opts *Options) (db *DB, err error) {
-	opts = opts.clone()
-	opts._ACID = _ACIDFull
-	f, err := fileutil.TempFile(dir, prefix, suffix)
-	if err != nil {
-		return
-	}
-
-	return create(lldb.NewSimpleFileFiler(f), opts, false)
-}
-
-// Open opens the named DB file for reading/writing. If successful, methods on
-// the returned DB can be used for I/O; the associated file descriptor has mode
-// os.O_RDWR. If there is an error, it will be of type *os.PathError.
-//
-// Note: While a DB is opened, it is locked and cannot be simultaneously opened
-// again.
-//
-// For the meaning of opts please see documentation of Options.
-func Open(name string, opts *Options) (db *DB, err error) {
-	f, err := os.OpenFile(name, os.O_RDWR, 0666)
-	if err != nil {
-		return nil, err
-	}
-
-	return OpenFromFiler(lldb.NewSimpleFileFiler(f), opts)
-}
-
-// OpenFromFiler is like Open but it accepts an arbitrary backing storage
-// provided by filer.
-func OpenFromFiler(filer lldb.Filer, opts *Options) (db *DB, err error) {
-	opts = opts.clone()
-	opts._ACID = _ACIDFull
-	defer func() {
-		if db != nil {
-			db.opts = opts
-		}
-	}()
-	defer func() {
-		lock := opts.lock
-		if err != nil && lock != nil {
-			lock.Close()
-			db = nil
-		}
-		if err != nil {
-			if db != nil {
-				db.Close()
-				db = nil
-			}
-		}
-	}()
-
-	name := filer.Name()
-	if err = opts.check(name, false, true); err != nil {
-		return
-	}
-
-	sz, err := filer.Size()
-	if err != nil {
-		return
-	}
-
-	if sz%16 != 0 {
-		return nil, &os.PathError{Op: "kv.Open:", Path: name, Err: fmt.Errorf("file size %d(%#x) is not 0 (mod 16)", sz, sz)}
-	}
-
-	var b [16]byte
-	if n, err := filer.ReadAt(b[:], 0); n != 16 || err != nil {
-		return nil, &os.PathError{Op: "kv.Open.ReadAt", Path: name, Err: err}
-	}
-
-	var h header
-	if err = h.rd(b[:]); err != nil {
-		return nil, &os.PathError{Op: "kv.Open:validate header", Path: name, Err: err}
-	}
-
-	db = &DB{lock: opts.lock}
-	if filer, err = opts.acidFiler(db, filer); err != nil {
-		return nil, err
-	}
-
-	db.filer = filer
-	switch h.ver {
-	default:
-		return nil, &os.PathError{Op: "kv.Open", Path: name, Err: fmt.Errorf("unknown/unsupported kv file format version %#x", h.ver)}
-	case 0x00:
-		if _, err = open00(name, db); err != nil {
-			return nil, err
-		}
-	}
-
-	db.root, err = lldb.OpenBTree(db.alloc, opts.Compare, 1)
-	db.wal = opts.wal
-	if opts.VerifyDbAfterOpen {
-		err = verifyAllocator(db.alloc)
-	}
-	return
-}
-
-// Close closes the DB, rendering it unusable for I/O. It returns an error, if
-// any. Failing to call Close before exiting a program can lose the last open
-// or being committed transaction.
-//
-// Successful Close is idempotent.
-func (db *DB) Close() (err error) {
-	db.closeMu.Lock()
-	defer db.closeMu.Unlock()
-	if db.closed {
-		return
-	}
-
-	db.closed = true
-
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	doLeave := true
-	defer func() {
-		db.wal = nil
-		if e := recover(); e != nil {
-			err = fmt.Errorf("%v", e)
-		}
-		if doLeave {
-			db.leave(&err)
-		}
-	}()
-
-	if db.acidTimer != nil {
-		db.acidTimer.Stop()
-	}
-
-	var e error
-	for db.acidNest > 0 {
-		db.acidNest--
-		if e = db.filer.EndUpdate(); err == nil {
-			err = e
-		}
-	}
-
-	doLeave = false
-	if e = db.leave(&err); err == nil {
-		err = e
-	}
-	if db.opts.VerifyDbBeforeClose {
-		if e = verifyAllocator(db.alloc); err == nil {
-			err = e
-		}
-	}
-	if e = db.close(); err == nil {
-		err = e
-	}
-	if lock := db.lock; lock != nil {
-		if e = lock.Close(); err == nil {
-			err = e
-		}
-	}
-	if wal := db.wal; wal != nil {
-		e = wal.Close()
-		db.wal = nil
-		if err == nil {
-			err = e
-		}
-	}
-	return
-}
-
-func (db *DB) close() (err error) {
-	// We are safe to close due to locked db.closeMu, but not safe against
-	// any other goroutine concurrently calling other exported db methods,
-	// causing a race[0] in the db.enter() mechanism. So we must lock
-	// db.bkl.
-	//
-	//  [0]: https://github.com/cznic/kv/issues/17#issuecomment-31960658
-	db.bkl.Lock()
-	defer db.bkl.Unlock()
-
-	if db.isMem { // lldb.MemFiler
-		return
-	}
-
-	err = db.filer.Sync()
-	if e := db.filer.Close(); err == nil {
-		err = e
-	}
-	if db.opts.VerifyDbAfterClose {
-		if e := verifyDbFile(db.Name()); err == nil {
-			err = e
-		}
-	}
-	return
-}
-
-// Name returns the name of the DB file.
-func (db *DB) Name() string {
-	return db.filer.Name()
-}
-
-// Size returns the size of the DB file.
-func (db *DB) Size() (sz int64, err error) {
-	db.bkl.Lock()
-	defer func() {
-		if e := recover(); e != nil {
-			err = fmt.Errorf("%v", e)
-		}
-		db.bkl.Unlock()
-	}()
-
-	return db.filer.Size()
-}
-
-func (db *DB) enter() (err error) {
-	db.bkl.Lock()
-	switch db.acidState {
-	default:
-		panic("internal error")
-	case stDisabled:
-		db.acidNest++
-		if db.acidNest == 1 {
-			if err = db.filer.BeginUpdate(); err != nil {
-				return err
-			}
-		}
-	case stIdle:
-		if err = db.filer.BeginUpdate(); err != nil {
-			return err
-		}
-
-		db.acidNest = 1
-		db.acidTimer = time.AfterFunc(db.gracePeriod, db.timeout)
-		db.acidState = stCollecting
-	case stCollecting:
-		db.acidNest++
-	case stIdleArmed:
-		db.acidNest = 1
-		db.acidState = stCollectingArmed
-	case stCollectingArmed:
-		db.acidNest++
-	case stCollectingTriggered:
-		db.acidNest++
-	case stEndUpdateFailed:
-		return db.leave(&err)
-	}
-
-	return nil
-}
-
-func (db *DB) leave(err *error) error {
-	switch db.acidState {
-	default:
-		panic("internal error")
-	case stDisabled:
-		db.acidNest--
-		if db.acidNest == 0 {
-			if e := db.filer.EndUpdate(); e != nil && *err == nil {
-				*err = e
-			}
-		}
-	case stCollecting:
-		db.acidNest--
-		if db.acidNest == 0 {
-			db.acidState = stIdleArmed
-		}
-	case stCollectingArmed:
-		db.acidNest--
-		if db.acidNest == 0 {
-			db.acidState = stIdleArmed
-		}
-	case stCollectingTriggered:
-		db.acidNest--
-		if db.acidNest == 0 {
-			if e := db.filer.EndUpdate(); e != nil && *err == nil {
-				*err = e
-			}
-			db.acidState = stIdle
-		}
-	case stEndUpdateFailed:
-		db.bkl.Unlock()
-		return fmt.Errorf("Last transaction commit failed: %v", db.lastCommitErr)
-	}
-
-	if *err != nil {
-		db.filer.Rollback() // return the original, input error
-	}
-	db.bkl.Unlock()
-	return *err
-}
-
-func (db *DB) timeout() {
-	db.closeMu.Lock()
-	defer db.closeMu.Unlock()
-	if db.closed {
-		return
-	}
-
-	db.bkl.Lock()
-	defer db.bkl.Unlock()
-
-	switch db.acidState {
-	default:
-		panic("internal error")
-	case stIdle:
-		panic("internal error")
-	case stCollecting:
-		db.acidState = stCollectingTriggered
-	case stIdleArmed:
-		if err := db.filer.EndUpdate(); err != nil { // If EndUpdate fails, no WAL was written (automatic Rollback)
-			db.acidState = stEndUpdateFailed
-			db.lastCommitErr = err
-			return
-		}
-
-		db.acidState = stIdle
-	case stCollectingArmed:
-		db.acidState = stCollectingTriggered
-	case stCollectingTriggered:
-		panic("internal error")
-	}
-}
-
-// BeginTransaction starts a new transaction. Every call to BeginTransaction
-// must be eventually "balanced" by exactly one call to Commit or Rollback (but
-// not both). Calls to BeginTransaction may nest.
-//
-// BeginTransaction is atomic and it is safe for concurrent use by multiple
-// goroutines (if/when that makes sense).
-func (db *DB) BeginTransaction() (err error) {
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	defer func() {
-		if e := recover(); e != nil {
-			err = fmt.Errorf("%v", e)
-		}
-		db.leave(&err)
-	}()
-
-	db.acidNest++
-	return db.filer.BeginUpdate()
-}
-
-// Commit commits the current transaction. If the transaction is the top level
-// one, then all of the changes made within the transaction are atomically made
-// persistent in the DB.  Invocation of an unbalanced Commit is an error.
-//
-// Commit is atomic and it is safe for concurrent use by multiple goroutines
-// (if/when that makes sense).
-func (db *DB) Commit() (err error) {
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	defer func() {
-		if e := recover(); e != nil {
-			err = fmt.Errorf("%v", e)
-		}
-		db.leave(&err)
-	}()
-
-	db.acidNest--
-	return db.filer.EndUpdate()
-}
-
-// Rollback cancels and undoes the innermost transaction level. If the
-// transaction is the top level one, then no of the changes made within the
-// transactions are persisted. Invocation of an unbalanced Rollback is an
-// error.
-//
-// Rollback is atomic and it is safe for concurrent use by multiple goroutines
-// (if/when that makes sense).
-func (db *DB) Rollback() (err error) {
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	defer func() {
-		if e := recover(); e != nil {
-			err = fmt.Errorf("%v", e)
-		}
-		db.leave(&err)
-	}()
-
-	db.acidNest--
-	return db.filer.Rollback()
-}
-
-// Verify attempts to find any structural errors in DB wrt the organization of
-// it as defined by lldb.Allocator. Any problems found are reported to 'log'
-// except non verify related errors like disk read fails etc. If 'log' returns
-// false or the error doesn't allow to (reliably) continue, the verification
-// process is stopped and an error is returned from the Verify function.
-// Passing a nil log works like providing a log function always returning
-// false. Any non-structural errors, like for instance Filer read errors, are
-// NOT reported to 'log', but returned as the Verify's return value, because
-// Verify cannot proceed in such cases. Verify returns nil only if it fully
-// completed verifying DB without detecting any error.
-//
-// It is recommended to limit the number reported problems by returning false
-// from 'log' after reaching some limit. Huge and corrupted DB can produce an
-// overwhelming error report dataset.
-//
-// The verifying process will scan the whole DB at least 3 times (a trade
-// between processing space and time consumed). It doesn't read the content of
-// free blocks above the head/tail info bytes. If the 3rd phase detects lost
-// free space, then a 4th scan (a faster one) is performed to precisely report
-// all of them.
-//
-// Statistics are returned via 'stats' if non nil. The statistics are valid
-// only if Verify succeeded, ie. it didn't reported anything to log and it
-// returned a nil error.
-func (db *DB) Verify(log func(error) bool, stats *lldb.AllocStats) (err error) {
-	bitmapf, err := fileutil.TempFile("", "verifier", ".tmp")
-	if err != nil {
-		return
-	}
-
-	defer func() {
-		tn := bitmapf.Name()
-		bitmapf.Close()
-		os.Remove(tn)
-	}()
-
-	bitmap := lldb.NewSimpleFileFiler(bitmapf)
-
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	defer func() {
-		if e := recover(); e != nil {
-			err = fmt.Errorf("%v", e)
-		}
-		db.leave(&err)
-	}()
-
-	return db.alloc.Verify(bitmap, log, stats)
-}
-
-// Delete deletes key and its associated value from the DB.
-//
-// Delete is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) Delete(key []byte) (err error) {
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	err = db.root.Delete(key)
-	return db.leave(&err)
-}
-
-// Extract is a combination of Get and Delete. If the key exists in the DB, it
-// is returned (like Get) and also deleted from the DB in a more efficient way
-// which doesn't search for the key twice. The returned slice may be a
-// sub-slice of buf if buf was large enough to hold the entire content.
-// Otherwise, a newly allocated slice will be returned. It is valid to pass a
-// nil buf.
-//
-// Extract is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) Extract(buf, key []byte) (value []byte, err error) {
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	value, err = db.root.Extract(buf, key)
-	db.leave(&err)
-	return
-}
-
-// First returns the first KV pair in the DB, if it exists. Otherwise key ==
-// nil and value == nil.
-//
-// First is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) First() (key, value []byte, err error) {
-	db.bkl.Lock()
-	defer db.bkl.Unlock()
-	return db.root.First()
-}
-
-// Get returns the value associated with key, or nil if no such value exists.
-// The returned slice may be a sub-slice of buf if buf was large enough to hold
-// the entire content. Otherwise, a newly allocated slice will be returned. It
-// is valid to pass a nil buf.
-//
-// Get is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) Get(buf, key []byte) (value []byte, err error) {
-	db.bkl.Lock()
-	defer db.bkl.Unlock()
-	return db.root.Get(buf, key)
-}
-
-// Last returns the last KV pair of the DB, if it exists. Otherwise key ==
-// nil and value == nil.
-//
-// Last is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) Last() (key, value []byte, err error) {
-	db.bkl.Lock()
-	defer db.bkl.Unlock()
-	return db.root.Last()
-}
-
-// Put combines Get and Set in a more efficient way where the DB is searched
-// for the key only once. The upd(ater) receives the current (key, old-value),
-// if that exists or (key, nil) otherwise. It can then return a (new-value,
-// true, nil) to create or overwrite the existing value in the KV pair, or
-// (whatever, false, nil) if it decides not to create or not to update the
-// value of the KV pair.
-//
-//	db.Set(k, v)
-//
-// conceptually equals
-//
-//	db.Put(k, func(k, v []byte){ return v, true }([]byte, bool))
-//
-// modulo the differing return values.
-//
-// The returned slice may be a sub-slice of buf if buf was large enough to hold
-// the entire content. Otherwise, a newly allocated slice will be returned. It
-// is valid to pass a nil buf.
-//
-// Put is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) Put(buf, key []byte, upd func(key, old []byte) (new []byte, write bool, err error)) (old []byte, written bool, err error) {
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	old, written, err = db.root.Put(buf, key, upd)
-	db.leave(&err)
-	return
-}
-
-// Seek returns an enumerator positioned on the first key/value pair whose key
-// is 'greater than or equal to' the given key. There may be no such pair, in
-// which case the Next,Prev methods of the returned enumerator will always
-// return io.EOF.
-//
-// Seek is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) Seek(key []byte) (enum *Enumerator, hit bool, err error) {
-	db.bkl.Lock()
-	defer db.bkl.Unlock()
-	enum0, hit, err := db.root.Seek(key)
-	if err != nil {
-		return
-	}
-
-	enum = &Enumerator{
-		db:   db,
-		enum: enum0,
-	}
-	return
-}
-
-// SeekFirst returns an enumerator positioned on the first KV pair in the DB,
-// if any. For an empty DB, err == io.EOF is returned.
-//
-// SeekFirst is atomic and it is safe for concurrent use by multiple
-// goroutines.
-func (db *DB) SeekFirst() (enum *Enumerator, err error) {
-	db.bkl.Lock()
-	defer db.bkl.Unlock()
-	enum0, err := db.root.SeekFirst()
-	if err != nil {
-		return
-	}
-
-	enum = &Enumerator{
-		db:   db,
-		enum: enum0,
-	}
-	return
-}
-
-// SeekLast returns an enumerator positioned on the last KV pair in the DB,
-// if any. For an empty DB, err == io.EOF is returned.
-//
-// SeekLast is atomic and it is safe for concurrent use by multiple
-// goroutines.
-func (db *DB) SeekLast() (enum *Enumerator, err error) {
-	db.bkl.Lock()
-	defer db.bkl.Unlock()
-	enum0, err := db.root.SeekLast()
-	if err != nil {
-		return
-	}
-
-	enum = &Enumerator{
-		db:   db,
-		enum: enum0,
-	}
-	return
-}
-
-// Set sets the value associated with key. Any previous value, if existed, is
-// overwritten by the new one.
-//
-// Set is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) Set(key, value []byte) (err error) {
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	err = db.root.Set(key, value)
-	db.leave(&err)
-	return
-}
-
-// Enumerator captures the state of enumerating a DB. It is returned from the
-// Seek* methods. Multiple enumerations may be in progress simultaneously.  The
-// enumerator is aware of any mutations made to the tree in the process of
-// enumerating it and automatically resumes the enumeration.
-//
-// Multiple concurrently executing enumerations may be in progress.
-type Enumerator struct {
-	db   *DB
-	enum *lldb.BTreeEnumerator
-}
-
-// Next returns the currently enumerated KV pair, if it exists and moves to the
-// next KV in the key collation order. If there is no KV pair to return, err ==
-// io.EOF is returned.
-//
-// Next is atomic and it is safe for concurrent use by multiple goroutines.
-func (e *Enumerator) Next() (key, value []byte, err error) {
-	e.db.bkl.Lock()
-	defer e.db.bkl.Unlock()
-	return e.enum.Next()
-}
-
-// Prev returns the currently enumerated KV pair, if it exists and moves to the
-// previous KV in the key collation order. If there is no KV pair to return,
-// err == io.EOF is returned.
-//
-// Prev is atomic and it is safe for concurrent use by multiple goroutines.
-func (e *Enumerator) Prev() (key, value []byte, err error) {
-	e.db.bkl.Lock()
-	defer e.db.bkl.Unlock()
-	return e.enum.Prev()
-}
-
-// Inc atomically increments the value associated with key by delta and
-// returns the new value. If the value doesn't exists before calling Inc or if
-// the value is not an [8]byte, the value is considered to be zero before peforming Inc.
-//
-// Inc is atomic and it is safe for concurrent use by multiple goroutines.
-func (db *DB) Inc(key []byte, delta int64) (val int64, err error) {
-	if err = db.enter(); err != nil {
-		return
-	}
-
-	defer db.leave(&err)
-
-	pbuf := buffer.Get(8)
-	defer buffer.Put(pbuf)
-	_, _, err = db.root.Put(
-		*pbuf,
-		key,
-		func(key []byte, old []byte) (new []byte, write bool, err error) {
-			write = true
-			if len(old) == 8 {
-				val = int64(binary.BigEndian.Uint64(old))
-			} else {
-				old = make([]byte, 8)
-				val = 0
-			}
-			val += delta
-			binary.BigEndian.PutUint64(old, uint64(val))
-			new = old
-			return
-		},
-	)
-
-	return
-}
-
-// WALName returns the name of the WAL file in use or an empty string for memory
-// or closed databases.
-func (db *DB) WALName() string {
-	if f := db.wal; f != nil {
-		return f.Name()
-	}
-
-	return ""
-}

+ 0 - 58
vendor/github.com/cznic/kv/lock.go

@@ -1,58 +0,0 @@
-// Copyright 2014 The kv Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package kv
-
-import (
-	"crypto/sha1"
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"sync"
-)
-
-func lockName(dbname string) string {
-	base := filepath.Base(filepath.Clean(dbname)) + "lockfile"
-	h := sha1.New()
-	io.WriteString(h, base)
-	return filepath.Join(filepath.Dir(dbname), fmt.Sprintf(".%x", h.Sum(nil)))
-}
-
-func defaultLocker(dbname string) (io.Closer, error) {
-	lname := lockName(dbname)
-	abs, err := filepath.Abs(lname)
-	if err != nil {
-		return nil, err
-	}
-	f, err := os.OpenFile(abs, os.O_CREATE|os.O_EXCL|os.O_RDONLY, 0666)
-	if os.IsExist(err) {
-		return nil, fmt.Errorf("cannot access DB %q: lock file %q exists", dbname, abs)
-	}
-	if err != nil {
-		return nil, err
-	}
-	return &lockCloser{f: f, abs: abs}, nil
-}
-
-type lockCloser struct {
-	f    *os.File
-	abs  string
-	once sync.Once
-	err  error
-}
-
-func (lc *lockCloser) Close() error {
-	lc.once.Do(lc.close)
-	return lc.err
-}
-
-func (lc *lockCloser) close() {
-	if err := lc.f.Close(); err != nil {
-		lc.err = err
-	}
-	if err := os.Remove(lc.abs); err != nil {
-		lc.err = err
-	}
-}

+ 0 - 246
vendor/github.com/cznic/kv/options.go

@@ -1,246 +0,0 @@
-// Copyright 2014 The kv Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package kv
-
-import (
-	"crypto/sha1"
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"time"
-
-	"github.com/cznic/lldb"
-)
-
-const (
-	// BeginUpdate/EndUpdate/Rollback will be no-ops. All operations
-	// updating a DB will be written immediately including partial updates
-	// during operation's progress. If any update fails, the DB can become
-	// unusable. The same applies to DB crashes and/or any other non clean
-	// DB shutdown.
-	_ACIDNone = iota
-
-	// Enable transactions. BeginUpdate/EndUpdate/Rollback will be
-	// effective. All operations on the DB will be automatically performed
-	// within a transaction. Operations will thus either succeed completely
-	// or have no effect at all - they will be rollbacked in case of any
-	// error. If any update fails the DB will not be corrupted. DB crashes
-	// and/or any other non clean DB shutdown may still render the DB
-	// unusable.
-	_ACIDTransactions
-
-	// Enable durability. Same as ACIDTransactions plus enables 2PC and
-	// WAL.  Updates to the DB will be first made permanent in a WAL and
-	// only after that reflected in the DB. A DB will automatically recover
-	// from crashes and/or any other non clean DB shutdown. Only last
-	// uncommitted transaction (transaction in progress ATM of a crash) can
-	// get lost.
-	//
-	// NOTE: Options.GracePeriod may extend the span of a single
-	// transaction to a batch of multiple transactions.
-	//
-	// NOTE2: Non zero GracePeriod requires GOMAXPROCS > 1 to work. Dbm
-	// checks GOMAXPROCS in such case and if the value is 1 it
-	// automatically sets GOMAXPROCS = 2.
-	_ACIDFull
-)
-
-// Options are passed to the DB create/open functions to amend the behavior of
-// those functions. The compatibility promise is the same as of struct types in
-// the Go standard library - introducing changes can be made only by adding new
-// exported fields, which is backward compatible as long as client code uses
-// field names to assign values of imported struct types literals.
-type Options struct {
-	// Compare compares x and y. Compare may be nil, then bytes.Compare is
-	// used instead.
-	//
-	// Compare returns:
-	//
-	//	-1 if x <  y
-	//	 0 if x == y
-	//	+1 if x >  y
-	Compare func(x, y []byte) int
-
-	// Locker specifies a function to lock a named file.
-	// On success it returns an io.Closer to release the lock.
-	// If nil, a default implementation is used.
-	Locker func(name string) (io.Closer, error)
-
-	// See the ACID* constants documentation.
-	_ACID int
-
-	// The write ahead log pathname. Applicable iff ACID == ACIDFull. May
-	// be left empty in which case an unspecified pathname will be chosen,
-	// which is computed from the DB name and which will be in the same
-	// directory as the DB. Moving or renaming the DB while it is shut down
-	// will break it's connection to the automatically computed name.
-	// Moving both the files (the DB and the WAL) into another directory
-	// with no renaming is safe.
-	//
-	// On creating a new DB the WAL file must not exist or it must be
-	// empty. It's not safe to write to a non empty WAL file as it may
-	// contain unprocessed DB recovery data.
-	WAL string
-
-	// Time to collect transactions before committing them into the WAL.
-	// Applicable iff ACID == ACIDFull. All updates are held in memory
-	// during the grace period so it should not be more than few seconds at
-	// most.
-	//
-	// Recommended value for GracePeriod is 1 second.
-	//
-	// NOTE: Using small GracePeriod values will make DB updates very slow.
-	// Zero GracePeriod will make every single update a separate 2PC/WAL
-	// transaction.  Values smaller than about 100-200 milliseconds
-	// (particularly for mechanical, rotational HDs) are not recommended
-	// and they may not be always honored.
-	_GracePeriod time.Duration
-	wal          *os.File
-	lock         io.Closer
-
-	noClone bool // test hook
-
-	// VerifyDbBeforeOpen turns on structural verification of the DB before
-	// it is opened. This verification may legitimately fail if the DB
-	// crashed and a yet-to-be-processed non empty WAL file exists.
-	VerifyDbBeforeOpen bool
-
-	// VerifyDbAfterOpen turns on structural verification of the DB after
-	// it is opened and possibly recovered from WAL.
-	VerifyDbAfterOpen bool
-
-	// VerifyDbBeforeClose turns on structural verification of the DB
-	// before it is closed.
-	VerifyDbBeforeClose bool
-
-	// VerifyDbAfterClose turns on structural verification of the DB after
-	// it is closed.
-	VerifyDbAfterClose bool
-
-	// Turns on verification of every single mutation of the DB. Before any
-	// such mutation a snapshot of the DB is created and the specific
-	// mutation operation and parameters are recorded. After the mutation
-	// the whole DB is verified. If the verification fails the last known
-	// good state (the snapshot discussed above) and the corrupted state
-	// are "core" dumped to a well known location (TBD).
-	//
-	//MAYBE ParanoidUpdates bool
-}
-
-func (o *Options) locker(dbname string) (io.Closer, error) {
-	if o == nil || o.Locker == nil {
-		return defaultLocker(dbname)
-	}
-	return o.Locker(dbname)
-}
-
-func (o *Options) clone() *Options {
-	if o.noClone {
-		return o
-	}
-
-	r := &Options{}
-	*r = *o
-	return r
-}
-
-func (o *Options) check(dbname string, new, lock bool) (err error) {
-	if lock {
-		if o.lock, err = o.locker(dbname); err != nil {
-			return
-		}
-	}
-
-	if o.VerifyDbBeforeOpen && !new {
-		if err = verifyDbFile(dbname); err != nil {
-			return
-		}
-	}
-
-	switch o._ACID {
-	default:
-		panic("internal error")
-	case _ACIDTransactions:
-	case _ACIDFull:
-		o._GracePeriod = time.Second
-		if o.WAL == "" {
-			o.WAL = o.walName(dbname, o.WAL)
-		}
-
-		switch new {
-		case true:
-			if o.wal, err = os.OpenFile(o.WAL, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err != nil {
-				if os.IsExist(err) {
-					fi, e := os.Stat(o.WAL)
-					if e != nil {
-						return e
-					}
-
-					if sz := fi.Size(); sz != 0 {
-						return fmt.Errorf("cannot create DB %q: non empty WAL file %q (size %d) exists", dbname, o.WAL, sz)
-					}
-
-					o.wal, err = os.OpenFile(o.WAL, os.O_RDWR, 0666)
-				}
-				return
-			}
-		case false:
-			if o.wal, err = os.OpenFile(o.WAL, os.O_RDWR, 0666); err != nil {
-				if os.IsNotExist(err) {
-					if o.wal, err = os.OpenFile(o.WAL, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err != nil {
-						return fmt.Errorf("cannot open DB %q: failed to create  WAL file %q: %v", dbname, o.WAL, err)
-					}
-
-					err = nil
-				}
-				return err
-			}
-		}
-	}
-
-	return err
-}
-
-func (o *Options) walName(dbname, wal string) (r string) {
-	if wal != "" {
-		return filepath.Clean(wal)
-	}
-
-	base := filepath.Base(filepath.Clean(dbname))
-	h := sha1.New()
-	io.WriteString(h, base)
-	return filepath.Join(filepath.Dir(dbname), fmt.Sprintf(".%x", h.Sum(nil)))
-}
-
-func (o *Options) acidFiler(db *DB, f lldb.Filer) (r lldb.Filer, err error) {
-	switch o._ACID {
-	default:
-		panic("internal error")
-	case _ACIDTransactions:
-		if r, err = lldb.NewRollbackFiler(
-			f,
-			func(sz int64) error {
-				return f.Truncate(sz)
-			},
-			f,
-		); err != nil {
-			return nil, err
-		}
-
-		return r, nil
-	case _ACIDFull:
-		if r, err = lldb.NewACIDFiler(f, o.wal); err != nil {
-			return nil, err
-		}
-
-		db.acidState = stIdle
-		db.gracePeriod = o._GracePeriod
-		if o._GracePeriod == 0 {
-			panic("internal error")
-		}
-		return r, nil
-	}
-}

+ 0 - 35
vendor/github.com/cznic/kv/perf-4670.log

@@ -1,35 +0,0 @@
-$ benchcmp -mag -changed log-bench-2016-02-08-2109-120f703e log-bench-2016-07-19-1813-32e56c29-lldb-2016-07-24-1458-74c3b196
-benchmark                  old ns/op     new ns/op     delta
-BenchmarkFirst16-4         4715          3680          -21.95%
-BenchmarkSet16-4           105255        88470         -15.95%
-BenchmarkPut16-4           121708        104760        -13.93%
-BenchmarkGet16-4           108211        93534         -13.56%
-BenchmarkDelete16-4        111052        96004         -13.55%
-BenchmarkExtract16-4       107820        93991         -12.83%
-BenchmarkSeek-4            104365        93677         -10.24%
-BenchmarkLast16-4          4075          3671          -9.91%
-BenchmarkInc-4             2207          2046          -7.29%
-BenchmarkEnumerateDB-4     231182        227247        -1.70%
-BenchmarkNext1e3-4         222782        222161        -0.28%
-
-benchmark                  old allocs     new allocs     delta
-BenchmarkFirst16-4         4              6              +50.00%
-BenchmarkLast16-4          4              6              +50.00%
-BenchmarkSeek-4            19             22             +15.79%
-BenchmarkPut16-4           15             14             -6.67%
-BenchmarkDelete16-4        18             19             +5.56%
-BenchmarkGet16-4           18             19             +5.56%
-BenchmarkEnumerateDB-4     3008           3010           +0.07%
-
-benchmark                  old bytes     new bytes     delta
-BenchmarkDelete16-4        10193         502           -95.08%
-BenchmarkGet16-4           147867        31569         -78.65%
-BenchmarkSeek-4            133654        31436         -76.48%
-BenchmarkPut16-4           1127          360           -68.06%
-BenchmarkExtract16-4       1042          360           -65.45%
-BenchmarkSet16-4           874           350           -59.95%
-BenchmarkFirst16-4         24944         16730         -32.93%
-BenchmarkLast16-4          20848         16730         -19.75%
-BenchmarkEnumerateDB-4     65159         57065         -12.42%
-BenchmarkInc-4             32            36            +12.50%
-BenchmarkNext1e3-4         24002         24000         -0.01%

+ 0 - 35
vendor/github.com/cznic/kv/perf-r550.log

@@ -1,35 +0,0 @@
-$ benchcmp -mag -changed log-bench-2016-02-08-2109-120f703e log-bench-2016-07-19-1813-32e56c29-lldb-2016-07-24-1458-74c3b196
-benchmark                  old ns/op     new ns/op     delta
-BenchmarkGet16-4           263400        180228        -31.58%
-BenchmarkSeek-4            256307        181534        -29.17%
-BenchmarkDelete16-4        237945        181734        -23.62%
-BenchmarkFirst16-4         16199         12430         -23.27%
-BenchmarkExtract16-4       231676        189059        -18.40%
-BenchmarkLast16-4          13875         11783         -15.08%
-BenchmarkPut16-4           206700        177933        -13.92%
-BenchmarkSet16-4           174694        154715        -11.44%
-BenchmarkInc-4             5907          5565          -5.79%
-BenchmarkEnumerateDB-4     432438        427689        -1.10%
-BenchmarkNext1e3-4         421038        421171        +0.03%
-
-benchmark                  old allocs     new allocs     delta
-BenchmarkFirst16-4         4              6              +50.00%
-BenchmarkLast16-4          4              6              +50.00%
-BenchmarkPut16-4           15             13             -13.33%
-BenchmarkSeek-4            19             21             +10.53%
-BenchmarkSet16-4           14             13             -7.14%
-BenchmarkExtract16-4       17             16             -5.88%
-BenchmarkEnumerateDB-4     3007           3010           +0.10%
-
-benchmark                  old bytes     new bytes     delta
-BenchmarkDelete16-4        10118         391           -96.14%
-BenchmarkGet16-4           147861        30234         -79.55%
-BenchmarkPut16-4           1631          334           -79.52%
-BenchmarkExtract16-4       1329          300           -77.43%
-BenchmarkSeek-4            133640        30385         -77.26%
-BenchmarkSet16-4           1293          399           -69.14%
-BenchmarkFirst16-4         24945         16730         -32.93%
-BenchmarkLast16-4          20849         16730         -19.76%
-BenchmarkEnumerateDB-4     65121         57066         -12.37%
-BenchmarkInc-4             32            36            +12.50%
-BenchmarkNext1e3-4         24008         24006         -0.01%

+ 0 - 21
vendor/github.com/cznic/kv/v0.go

@@ -1,21 +0,0 @@
-// Copyright 2014 The kv Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package kv
-
-import (
-	"os"
-
-	"github.com/cznic/lldb"
-)
-
-func open00(name string, in *DB) (db *DB, err error) {
-	db = in
-	if db.alloc, err = lldb.NewAllocator(lldb.NewInnerFiler(db.filer, 16), &lldb.Options{}); err != nil {
-		return nil, &os.PathError{Op: "kv.open00", Path: name, Err: err}
-	}
-
-	db.alloc.Compress = true
-	return
-}

+ 0 - 89
vendor/github.com/cznic/kv/verify.go

@@ -1,89 +0,0 @@
-// Copyright 2014 The kv Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package kv
-
-import (
-	"fmt"
-	"io"
-	"io/ioutil"
-	"os"
-
-	"github.com/cznic/lldb"
-)
-
-func verifyAllocator(a *lldb.Allocator) error {
-	bits, err := ioutil.TempFile("", "kv-verify-")
-	if err != nil {
-		return err
-	}
-
-	sf := lldb.NewSimpleFileFiler(bits)
-
-	defer func() {
-		nm := bits.Name()
-		sf.Close()
-		os.Remove(nm)
-	}()
-
-	var lerr error
-	if err = a.Verify(
-		sf,
-		func(err error) bool {
-			lerr = err
-			return false
-		},
-		nil,
-	); err != nil {
-		return err
-	}
-
-	if lerr != nil {
-		return lerr
-	}
-
-	t, err := lldb.OpenBTree(a, nil, 1)
-	if err != nil {
-		return err
-	}
-
-	e, err := t.SeekFirst()
-	if err != nil {
-		if err == io.EOF {
-			err = nil
-		}
-		return err
-	}
-
-	for {
-		_, _, err := e.Next()
-		if err != nil {
-			if err == io.EOF {
-				err = nil
-			}
-			return err
-		}
-	}
-}
-
-func verifyDbFile(fn string) error {
-	f, err := os.OpenFile(fn, os.O_RDWR, 0666)
-	if err != nil {
-		return err
-	}
-
-	sf := lldb.NewSimpleFileFiler(f)
-	if f == nil {
-		return fmt.Errorf("cannot create %s", fn)
-	}
-
-	defer sf.Close()
-
-	a, err := lldb.NewAllocator(lldb.NewInnerFiler(sf, 16), &lldb.Options{})
-	if err != nil {
-		return err
-	}
-
-	return verifyAllocator(a)
-}

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio