diff --git a/commands/command.go b/commands/command.go
index 0ab127ee..ee4c9bc6 100644
--- a/commands/command.go
+++ b/commands/command.go
@@ -78,6 +78,7 @@ func RegisterModel() {
new(models.MemberToken),
new(models.DocumentHistory),
new(models.Migration),
+ new(models.Label),
)
migrate.RegisterMigration()
}
diff --git a/controllers/book.go b/controllers/book.go
index 01a591f6..62f4240a 100644
--- a/controllers/book.go
+++ b/controllers/book.go
@@ -137,7 +137,7 @@ func (c *BookController) SaveBook() {
comment_status = "closed"
}
if tag != ""{
- tags := strings.Split(tag,";")
+ tags := strings.Split(tag,",")
if len(tags) > 10 {
c.JsonResult(6005,"最多允许添加10个标签")
}
diff --git a/controllers/document.go b/controllers/document.go
index c4180511..2760bacd 100644
--- a/controllers/document.go
+++ b/controllers/document.go
@@ -26,6 +26,7 @@ import (
"github.com/lifei6671/mindoc/models"
"github.com/lifei6671/mindoc/utils"
"github.com/lifei6671/mindoc/utils/wkhtmltopdf"
+ "github.com/russross/blackfriday"
)
//DocumentController struct.
@@ -126,7 +127,7 @@ func (c *DocumentController) Index() {
c.Data["Model"] = bookResult
c.Data["Result"] = template.HTML(tree)
c.Data["Title"] = "概要"
- c.Data["Content"] = bookResult.Description
+ c.Data["Content"] = template.HTML( blackfriday.MarkdownBasic([]byte(bookResult.Description)))
}
//阅读文档.
diff --git a/controllers/home.go b/controllers/home.go
index dca97ee8..51f9a99f 100644
--- a/controllers/home.go
+++ b/controllers/home.go
@@ -43,4 +43,11 @@ func (c *HomeController) Index() {
c.Data["Lists"] = books
+ labels ,totalCount,err := models.NewLabel().FindToPager(1,10)
+
+ if err != nil {
+ c.Data["Labels"] = make([]*models.Label,0)
+ }else{
+ c.Data["Labels"] = labels
+ }
}
diff --git a/controllers/label.go b/controllers/label.go
new file mode 100644
index 00000000..c2fe653f
--- /dev/null
+++ b/controllers/label.go
@@ -0,0 +1,60 @@
+package controllers
+
+import (
+ "github.com/lifei6671/mindoc/models"
+ "github.com/astaxie/beego/orm"
+ "github.com/astaxie/beego"
+ "github.com/lifei6671/mindoc/conf"
+ "github.com/lifei6671/mindoc/utils"
+)
+
+type LabelController struct {
+ BaseController
+}
+
+func (c *LabelController) Index() {
+ c.Prepare()
+ c.TplName = "label/index.tpl"
+
+ //如果没有开启你们访问则跳转到登录
+ if !c.EnableAnonymous && c.Member == nil {
+ c.Redirect(beego.URLFor("AccountController.Login"),302)
+ return
+ }
+
+ labelName := c.Ctx.Input.Param(":key")
+ pageIndex,_ := c.GetInt("page",1)
+ if labelName == "" {
+ c.Abort("404")
+ }
+ _,err := models.NewLabel().FindFirst("label_name",labelName)
+
+ if err != nil {
+ if err == orm.ErrNoRows {
+ c.Abort("404")
+ }else{
+ beego.Error(err)
+ c.Abort("500")
+ }
+ }
+ member_id := 0
+ if c.Member != nil {
+ member_id = c.Member.MemberId
+ }
+ search_result,totalCount,err := models.NewBook().FindForLabelToPager(labelName,pageIndex,conf.PageSize,member_id)
+
+ if err != nil {
+ beego.Error(err)
+ return
+ }
+ if totalCount > 0 {
+ html := utils.GetPagerHtml(c.Ctx.Request.RequestURI, pageIndex, conf.PageSize, totalCount)
+
+ c.Data["PageHtml"] = html
+ }else {
+ c.Data["PageHtml"] = ""
+ }
+ c.Data["Lists"] = search_result
+
+ c.Data["LabelName"] = labelName
+}
diff --git a/controllers/manager.go b/controllers/manager.go
index 63548c12..6809ba7c 100644
--- a/controllers/manager.go
+++ b/controllers/manager.go
@@ -186,6 +186,7 @@ func (c *ManagerController) ChangeMemberRole() {
c.JsonResult(0, "ok", member)
}
+//编辑用户信息.
func (c *ManagerController) EditMember() {
c.Prepare()
c.TplName = "manager/edit_users.tpl"
@@ -238,6 +239,38 @@ func (c *ManagerController) EditMember() {
c.Data["Model"] = member
}
+//删除一个用户,并将该用户的所有信息转移到超级管理员上.
+func (c *ManagerController) DeleteMember() {
+ c.Prepare()
+ member_id,_ := c.GetInt("id",0)
+
+ if member_id <= 0 {
+ c.JsonResult(404,"参数错误")
+ }
+
+ _ ,err := models.NewMember().Find(member_id)
+
+ if err != nil {
+ beego.Error(err)
+ c.JsonResult(500,"用户不存在")
+ }
+ superMember,err := models.NewMember().FindByFieldFirst("role",0)
+
+ if err != nil {
+ beego.Error(err)
+ c.JsonResult(5001,"未能找到超级管理员")
+ }
+
+ err = models.NewMember().Delete(member_id,superMember.MemberId)
+
+ if err != nil {
+ beego.Error(err)
+ c.JsonResult(5002,"删除失败")
+ }
+ c.JsonResult(0,"ok")
+}
+
+//项目列表.
func (c *ManagerController) Books() {
c.Prepare()
c.TplName = "manager/books.tpl"
@@ -261,7 +294,7 @@ func (c *ManagerController) Books() {
c.Data["Lists"] = books
}
-//编辑项目
+//编辑项目.
func (c *ManagerController) EditBook() {
c.Prepare()
diff --git a/docs/.gitignore b/docs/.gitignore
deleted file mode 100644
index bfa6a22a..00000000
--- a/docs/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-# Created by .ignore support plugin (hsz.mobi)
diff --git a/models/book.go b/models/book.go
index 0e6b2bb5..59d9c3ef 100644
--- a/models/book.go
+++ b/models/book.go
@@ -3,53 +3,55 @@ package models
import (
"time"
+ "strings"
+
+ "github.com/astaxie/beego"
+ "github.com/astaxie/beego/logs"
"github.com/astaxie/beego/orm"
"github.com/lifei6671/mindoc/conf"
- "github.com/astaxie/beego/logs"
- "strings"
- "github.com/astaxie/beego"
+ "fmt"
)
// Book struct .
type Book struct {
- BookId int `orm:"pk;auto;unique;column(book_id)" json:"book_id"`
+ BookId int `orm:"pk;auto;unique;column(book_id)" json:"book_id"`
// BookName 项目名称.
- BookName string `orm:"column(book_name);size(500)" json:"book_name"`
+ BookName string `orm:"column(book_name);size(500)" json:"book_name"`
// Identify 项目唯一标识.
- Identify string `orm:"column(identify);size(100);unique" json:"identify"`
- OrderIndex int `orm:"column(order_index);type(int);default(0)" json:"order_index"`
+ Identify string `orm:"column(identify);size(100);unique" json:"identify"`
+ OrderIndex int `orm:"column(order_index);type(int);default(0)" json:"order_index"`
// Description 项目描述.
- Description string `orm:"column(description);size(2000)" json:"description"`
- Label string `orm:"column(label);size(500)" json:"label"`
+ Description string `orm:"column(description);size(2000)" json:"description"`
+ Label string `orm:"column(label);size(500)" json:"label"`
// PrivatelyOwned 项目私有: 0 公开/ 1 私有
- PrivatelyOwned int `orm:"column(privately_owned);type(int);default(0)" json:"privately_owned"`
+ PrivatelyOwned int `orm:"column(privately_owned);type(int);default(0)" json:"privately_owned"`
// 当项目是私有时的访问Token.
- PrivateToken string `orm:"column(private_token);size(500);null" json:"private_token"`
+ PrivateToken string `orm:"column(private_token);size(500);null" json:"private_token"`
//状态:0 正常/1 已删除
- Status int `orm:"column(status);type(int);default(0)" json:"status"`
+ Status int `orm:"column(status);type(int);default(0)" json:"status"`
//默认的编辑器.
- Editor string `orm:"column(editor);size(50)" json:"editor"`
+ Editor string `orm:"column(editor);size(50)" json:"editor"`
// DocCount 包含文档数量.
- DocCount int `orm:"column(doc_count);type(int)" json:"doc_count"`
+ DocCount int `orm:"column(doc_count);type(int)" json:"doc_count"`
// CommentStatus 评论设置的状态:open 为允许所有人评论,closed 为不允许评论, group_only 仅允许参与者评论 ,registered_only 仅允许注册者评论.
- CommentStatus string `orm:"column(comment_status);size(20);default(open)" json:"comment_status"`
- CommentCount int `orm:"column(comment_count);type(int)" json:"comment_count"`
+ CommentStatus string `orm:"column(comment_status);size(20);default(open)" json:"comment_status"`
+ CommentCount int `orm:"column(comment_count);type(int)" json:"comment_count"`
//封面地址
- Cover string `orm:"column(cover);size(1000)" json:"cover"`
+ Cover string `orm:"column(cover);size(1000)" json:"cover"`
//主题风格
- Theme string `orm:"column(theme);size(255);default(default)" json:"theme"`
+ Theme string `orm:"column(theme);size(255);default(default)" json:"theme"`
// CreateTime 创建时间 .
- CreateTime time.Time `orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
- MemberId int `orm:"column(member_id);size(100)" json:"member_id"`
- ModifyTime time.Time `orm:"type(datetime);column(modify_time);null;auto_now" json:"modify_time"`
- Version int64 `orm:"type(bigint);column(version)" json:"version"`
+ CreateTime time.Time `orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
+ MemberId int `orm:"column(member_id);size(100)" json:"member_id"`
+ ModifyTime time.Time `orm:"type(datetime);column(modify_time);null;auto_now" json:"modify_time"`
+ Version int64 `orm:"type(bigint);column(version)" json:"version"`
}
-
// TableName 获取对应数据库表名.
func (m *Book) TableName() string {
return "books"
}
+
// TableEngine 获取数据使用的引擎.
func (m *Book) TableEngine() string {
return "INNODB"
@@ -64,18 +66,22 @@ func NewBook() *Book {
func (m *Book) Insert() error {
o := orm.NewOrm()
-// o.Begin()
+ // o.Begin()
- _,err := o.Insert(m)
+ _, err := o.Insert(m)
if err == nil {
+ if m.Label != "" {
+ NewLabel().InsertOrUpdateMulti(m.Label)
+ }
+
relationship := NewRelationship()
relationship.BookId = m.BookId
relationship.RoleId = 0
relationship.MemberId = m.MemberId
err = relationship.Insert()
if err != nil {
- logs.Error("插入项目与用户关联 => ",err)
+ logs.Error("插入项目与用户关联 => ", err)
//o.Rollback()
return err
}
@@ -84,7 +90,7 @@ func (m *Book) Insert() error {
document.DocumentName = "空白文档"
document.MemberId = m.MemberId
err = document.InsertOrUpdate()
- if err != nil{
+ if err != nil {
//o.Rollback()
return err
}
@@ -95,113 +101,113 @@ func (m *Book) Insert() error {
return err
}
-func (m *Book) Find(id int) (*Book,error) {
+func (m *Book) Find(id int) (*Book, error) {
if id <= 0 {
- return m,ErrInvalidParameter
+ return m, ErrInvalidParameter
}
o := orm.NewOrm()
+ err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", id).One(m)
- err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id",id).One(m)
-
- return m,err
+ return m, err
}
-func (m *Book) Update(cols... string) error {
+func (m *Book) Update(cols ...string) error {
o := orm.NewOrm()
- _,err := o.Update(m,cols...)
+ temp := NewBook()
+ temp.BookId = m.BookId
+
+ if err := o.Read(temp);err != nil {
+ return err
+ }
+
+ if (m.Label + temp.Label) != "" {
+
+ go NewLabel().InsertOrUpdateMulti(m.Label + "," + temp.Label)
+ }
+
+ _, err := o.Update(m, cols...)
return err
}
//根据指定字段查询结果集.
-func (m *Book) FindByField(field string,value interface{}) ([]*Book,error) {
+func (m *Book) FindByField(field string, value interface{}) ([]*Book, error) {
o := orm.NewOrm()
var books []*Book
- _,err := o.QueryTable(m.TableNameWithPrefix()).Filter(field,value).All(&books)
+ _, err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, value).All(&books)
- return books,err
+ return books, err
}
//根据指定字段查询一个结果.
-func (m *Book) FindByFieldFirst(field string,value interface{})(*Book,error) {
+func (m *Book) FindByFieldFirst(field string, value interface{}) (*Book, error) {
o := orm.NewOrm()
- err := o.QueryTable(m.TableNameWithPrefix()).Filter(field,value).One(m)
+ err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, value).One(m)
- return m,err
+ return m, err
}
-func (m *Book) FindByIdentify(identify string) (*Book,error) {
+func (m *Book) FindByIdentify(identify string) (*Book, error) {
o := orm.NewOrm()
- err := o.QueryTable(m.TableNameWithPrefix()).Filter("identify",identify).One(m)
+ err := o.QueryTable(m.TableNameWithPrefix()).Filter("identify", identify).One(m)
- return m,err
+ return m, err
}
//分页查询指定用户的项目
-func (m *Book) FindToPager(pageIndex, pageSize ,memberId int) (books []*BookResult,totalCount int,err error){
+func (m *Book) FindToPager(pageIndex, pageSize, memberId int) (books []*BookResult, totalCount int, err error) {
relationship := NewRelationship()
o := orm.NewOrm()
- qb, _ := orm.NewQueryBuilder("mysql")
+ sql1 := "SELECT COUNT(book.book_id) AS total_count FROM " + m.TableNameWithPrefix() + " AS book LEFT JOIN " +
+ relationship.TableNameWithPrefix() + " AS rel ON book.book_id=rel.book_id AND rel.member_id = ? WHERE rel.relationship_id > 0 "
- qb.Select("COUNT(book.book_id) AS total_count").
- From(m.TableNameWithPrefix() + " AS book").
- LeftJoin(relationship.TableNameWithPrefix() + " AS rel").
- On("book.book_id=rel.book_id AND rel.member_id = ?").
- Where("rel.relationship_id > 0")
-
- err = o.Raw(qb.String(),memberId).QueryRow(&totalCount)
+ err = o.Raw(sql1, memberId).QueryRow(&totalCount)
if err != nil {
return
}
offset := (pageIndex - 1) * pageSize
- qb2,_ := orm.NewQueryBuilder("mysql")
- qb2.Select("book.*,rel.member_id","rel.role_id","m.account as create_name").
- From(m.TableNameWithPrefix() + " AS book").
- LeftJoin(relationship.TableNameWithPrefix() + " AS rel").On("book.book_id=rel.book_id AND rel.member_id = ?").
- LeftJoin(relationship.TableNameWithPrefix() + " AS rel1").On("book.book_id=rel1.book_id AND rel1.role_id=0").
- LeftJoin(NewMember().TableNameWithPrefix() + " AS m").On("rel1.member_id=m.member_id").
- Where("rel.relationship_id > 0").
- OrderBy("book.order_index DESC ","book.book_id").Desc().
- Limit(pageSize).
- Offset(offset)
+ sql2 := "SELECT book.*,rel.member_id,rel.role_id,m.account as create_name FROM " + m.TableNameWithPrefix() + " AS book" +
+ " LEFT JOIN " + relationship.TableNameWithPrefix() + " AS rel ON book.book_id=rel.book_id AND rel.member_id = ?" +
+ " LEFT JOIN " + relationship.TableNameWithPrefix() + " AS rel1 ON book.book_id=rel1.book_id AND rel1.role_id=0" +
+ " LEFT JOIN " + NewMember().TableNameWithPrefix() + " AS m ON rel1.member_id=m.member_id " +
+ " WHERE rel.relationship_id > 0 ORDER BY book.order_index DESC,book.book_id DESC LIMIT " + fmt.Sprintf("%d,%d",offset,pageSize)
- _,err = o.Raw(qb2.String(),memberId).QueryRows(&books)
+ _, err = o.Raw(sql2, memberId).QueryRows(&books)
if err != nil {
- logs.Error("分页查询项目列表 => ",err)
+ logs.Error("分页查询项目列表 => ", err)
return
}
sql := "SELECT m.account,doc.modify_time FROM md_documents AS doc LEFT JOIN md_members AS m ON doc.modify_at=m.member_id WHERE book_id = ? LIMIT 1 ORDER BY doc.modify_time DESC"
- if err == nil && len(books) > 0{
- for index,book := range books {
- var text struct{
- Account string
+ if err == nil && len(books) > 0 {
+ for index, book := range books {
+ var text struct {
+ Account string
ModifyTime time.Time
}
-
- err1 := o.Raw(sql,book.BookId).QueryRow(&text)
+ err1 := o.Raw(sql, book.BookId).QueryRow(&text)
if err1 == nil {
books[index].LastModifyText = text.Account + " 于 " + text.ModifyTime.Format("2006-01-02 15:04:05")
}
- if book.RoleId == 0{
+ if book.RoleId == 0 {
book.RoleName = "创始人"
- }else if book.RoleId == 1 {
+ } else if book.RoleId == 1 {
book.RoleName = "管理员"
- }else if book.RoleId == 2 {
+ } else if book.RoleId == 2 {
book.RoleName = "编辑者"
- }else if book.RoleId == 3 {
+ } else if book.RoleId == 3 {
book.RoleName = "观察者"
}
}
@@ -211,7 +217,7 @@ func (m *Book) FindToPager(pageIndex, pageSize ,memberId int) (books []*BookResu
// 彻底删除项目.
func (m *Book) ThoroughDeleteBook(id int) error {
- if id <= 0{
+ if id <= 0 {
return ErrInvalidParameter
}
o := orm.NewOrm()
@@ -221,17 +227,10 @@ func (m *Book) ThoroughDeleteBook(id int) error {
return err
}
o.Begin()
- //sql1 := "DELETE FROM " + NewComment().TableNameWithPrefix() + " WHERE book_id = ?"
- //
- //_,err := o.Raw(sql1,m.BookId).Exec()
- //
- //if err != nil {
- // o.Rollback()
- // return err
- //}
+
sql2 := "DELETE FROM " + NewDocument().TableNameWithPrefix() + " WHERE book_id = ?"
- _,err := o.Raw(sql2,m.BookId).Exec()
+ _, err := o.Raw(sql2, m.BookId).Exec()
if err != nil {
o.Rollback()
@@ -239,7 +238,7 @@ func (m *Book) ThoroughDeleteBook(id int) error {
}
sql3 := "DELETE FROM " + m.TableNameWithPrefix() + " WHERE book_id = ?"
- _,err = o.Raw(sql3,m.BookId).Exec()
+ _, err = o.Raw(sql3, m.BookId).Exec()
if err != nil {
o.Rollback()
@@ -247,19 +246,23 @@ func (m *Book) ThoroughDeleteBook(id int) error {
}
sql4 := "DELETE FROM " + NewRelationship().TableNameWithPrefix() + " WHERE book_id = ?"
- _,err = o.Raw(sql4,m.BookId).Exec()
+ _, err = o.Raw(sql4, m.BookId).Exec()
if err != nil {
o.Rollback()
return err
}
+ if m.Label != "" {
+ NewLabel().InsertOrUpdateMulti(m.Label)
+ }
+
return o.Commit()
}
//分页查找系统首页数据.
-func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*BookResult,totalCount int,err error) {
+func (m *Book) FindForHomeToPager(pageIndex, pageSize, member_id int) (books []*BookResult, totalCount int, err error) {
o := orm.NewOrm()
offset := (pageIndex - 1) * pageSize
@@ -267,7 +270,7 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
if member_id > 0 {
sql1 := "SELECT COUNT(*) FROM md_books AS book LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.member_id = ? WHERE relationship_id > 0 OR book.privately_owned = 0"
- err = o.Raw(sql1,member_id).QueryRow(&totalCount)
+ err = o.Raw(sql1, member_id).QueryRow(&totalCount)
if err != nil {
return
}
@@ -277,12 +280,12 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
LEFT JOIN md_members AS member ON rel1.member_id = member.member_id
WHERE rel.relationship_id > 0 OR book.privately_owned = 0 ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
- _,err = o.Raw(sql2,member_id,offset,pageSize).QueryRows(&books)
+ _, err = o.Raw(sql2, member_id, offset, pageSize).QueryRows(&books)
return
- }else{
- count,err1 := o.QueryTable(m.TableNameWithPrefix()).Filter("privately_owned",0).Count()
+ } else {
+ count, err1 := o.QueryTable(m.TableNameWithPrefix()).Filter("privately_owned", 0).Count()
if err1 != nil {
err = err1
@@ -295,7 +298,7 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
LEFT JOIN md_members AS member ON rel.member_id = member.member_id
WHERE book.privately_owned = 0 ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
- _,err = o.Raw(sql,offset,pageSize).QueryRows(&books)
+ _, err = o.Raw(sql, offset, pageSize).QueryRows(&books)
return
@@ -303,30 +306,75 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
}
+//分页全局搜索.
+func (m *Book) FindForLabelToPager(keyword string, pageIndex, pageSize, member_id int) (books []*BookResult, totalCount int, err error) {
+ o := orm.NewOrm()
+
+ keyword = "%" + keyword + "%"
+ offset := (pageIndex - 1) * pageSize
+ //如果是登录用户
+ if member_id > 0 {
+ sql1 := "SELECT COUNT(*) FROM md_books AS book LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.member_id = ? WHERE (relationship_id > 0 OR book.privately_owned = 0) AND book.label LIKE ?"
+
+ err = o.Raw(sql1, member_id,keyword).QueryRow(&totalCount)
+ if err != nil {
+ return
+ }
+ sql2 := `SELECT book.*,rel1.*,member.account AS create_name FROM md_books AS book
+ LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.member_id = ?
+ LEFT JOIN md_relationship AS rel1 ON rel1.book_id = book.book_id AND rel1.role_id = 0
+ LEFT JOIN md_members AS member ON rel1.member_id = member.member_id
+ WHERE (rel.relationship_id > 0 OR book.privately_owned = 0) AND book.label LIKE ? ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
+
+ _, err = o.Raw(sql2, member_id,keyword, offset, pageSize).QueryRows(&books)
+
+ return
+
+ } else {
+ count, err1 := o.QueryTable(NewBook().TableNameWithPrefix()).Filter("privately_owned", 0).Filter("label__icontains",keyword).Count()
+
+ if err1 != nil {
+ err = err1
+ return
+ }
+ totalCount = int(count)
+
+ sql := `SELECT book.*,rel.*,member.account AS create_name FROM md_books AS book
+ LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.role_id = 0
+ LEFT JOIN md_members AS member ON rel.member_id = member.member_id
+ WHERE book.privately_owned = 0 AND book.label LIKE ? ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
+
+ _, err = o.Raw(sql,keyword, offset, pageSize).QueryRows(&books)
+
+ return
+
+ }
+}
+
+
func (book *Book) ToBookResult() *BookResult {
m := NewBookResult()
- m.BookId = book.BookId
- m.BookName = book.BookName
- m.Identify = book.Identify
- m.OrderIndex = book.OrderIndex
- m.Description = strings.Replace(book.Description, "\r\n", "
", -1)
- m.PrivatelyOwned = book.PrivatelyOwned
- m.PrivateToken = book.PrivateToken
- m.DocCount = book.DocCount
- m.CommentStatus = book.CommentStatus
- m.CommentCount = book.CommentCount
- m.CreateTime = book.CreateTime
- m.ModifyTime = book.ModifyTime
- m.Cover = book.Cover
- m.Label = book.Label
- m.Status = book.Status
- m.Editor = book.Editor
- m.Theme = book.Theme
+ m.BookId = book.BookId
+ m.BookName = book.BookName
+ m.Identify = book.Identify
+ m.OrderIndex = book.OrderIndex
+ m.Description = strings.Replace(book.Description, "\r\n", "
", -1)
+ m.PrivatelyOwned = book.PrivatelyOwned
+ m.PrivateToken = book.PrivateToken
+ m.DocCount = book.DocCount
+ m.CommentStatus = book.CommentStatus
+ m.CommentCount = book.CommentCount
+ m.CreateTime = book.CreateTime
+ m.ModifyTime = book.ModifyTime
+ m.Cover = book.Cover
+ m.Label = book.Label
+ m.Status = book.Status
+ m.Editor = book.Editor
+ m.Theme = book.Theme
-
- if book.Theme == ""{
+ if book.Theme == "" {
m.Theme = "default"
}
if book.Editor == "" {
@@ -336,61 +384,13 @@ func (book *Book) ToBookResult() *BookResult {
}
//重置文档数量
-func (m *Book) ResetDocumentNumber(book_id int) {
+func (m *Book) ResetDocumentNumber(book_id int) {
o := orm.NewOrm()
- totalCount,err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id",book_id).Count()
+ totalCount, err := o.QueryTable(NewDocument().TableNameWithPrefix()).Filter("book_id", book_id).Count()
if err == nil {
- o.Raw("UPDATE md_books SET doc_count = ? WHERE book_id = ?",int(totalCount),book_id).Exec()
- }else{
+ o.Raw("UPDATE md_books SET doc_count = ? WHERE book_id = ?", int(totalCount), book_id).Exec()
+ } else {
beego.Error(err)
}
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/models/document.go b/models/document.go
index e4a149e3..f20767a3 100644
--- a/models/document.go
+++ b/models/document.go
@@ -155,7 +155,7 @@ func (m *Document) ReleaseContent(book_id int) {
func (m *Document) FindListByBookId(book_id int) (docs []*Document, err error) {
o := orm.NewOrm()
- _, err = o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", book_id).All(&docs)
+ _, err = o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", book_id).OrderBy("order_sort").All(&docs)
return
}
diff --git a/models/label.go b/models/label.go
new file mode 100644
index 00000000..7f1aa8ae
--- /dev/null
+++ b/models/label.go
@@ -0,0 +1,92 @@
+package models
+
+import (
+ "github.com/lifei6671/mindoc/conf"
+ "github.com/astaxie/beego/orm"
+ "strings"
+)
+
+type Label struct {
+ LabelId int `orm:"column(label_id);pk;auto;unique;" json:"label_id"`
+ LabelName string `orm:"column(label_name);size(50);unique" json:"label_name"`
+ BookNumber int `orm:"column(book_number)" json:"book_number"`
+}
+
+// TableName 获取对应数据库表名.
+func (m *Label) TableName() string {
+ return "label"
+}
+// TableEngine 获取数据使用的引擎.
+func (m *Label) TableEngine() string {
+ return "INNODB"
+}
+
+func (m *Label)TableNameWithPrefix() string {
+ return conf.GetDatabasePrefix() + m.TableName()
+}
+
+func NewLabel() *Label {
+ return &Label{}
+}
+
+func (m *Label) FindFirst(field string, value interface{}) (*Label,error){
+ o := orm.NewOrm()
+
+ err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, value).One(m)
+
+ return m, err
+}
+
+//插入或更新标签.
+func (m *Label) InsertOrUpdate(labelName string) error {
+ o := orm.NewOrm()
+
+ err := o.QueryTable(m.TableNameWithPrefix()).Filter("label_name",labelName).One(m)
+ if err != nil && err != orm.ErrNoRows {
+ return err
+ }
+ count,_ := o.QueryTable(NewBook().TableNameWithPrefix()).Filter("label__icontains",labelName).Count()
+ m.BookNumber = int(count)
+ m.LabelName = labelName
+
+ if err == orm.ErrNoRows {
+ err = nil
+ m.LabelName = labelName
+ _,err = o.Insert(m)
+ }else{
+ _,err = o.Update(m)
+ }
+ return err
+}
+
+//批量插入或更新标签.
+func (m *Label) InsertOrUpdateMulti(labels string) {
+ if labels != "" {
+ labelArray := strings.Split(labels, ",")
+
+ for _, label := range labelArray {
+ if label != "" {
+ NewLabel().InsertOrUpdate(label)
+ }
+ }
+ }
+}
+
+//分页查找标签.
+func (m *Label) FindToPager(pageIndex, pageSize int) (labels []*Label,totalCount int,err error) {
+ o := orm.NewOrm()
+
+ count,err := o.QueryTable(m.TableNameWithPrefix()).Count()
+
+ if err != nil {
+ return
+ }
+ totalCount = int(count)
+
+ offset := (pageIndex - 1) * pageSize
+
+ _,err = o.QueryTable(m.TableNameWithPrefix()).OrderBy("-book_number").Offset(offset).Limit(pageSize).All(&labels)
+
+ return
+}
+
diff --git a/models/member.go b/models/member.go
index e9f79f5d..6bffe8f2 100644
--- a/models/member.go
+++ b/models/member.go
@@ -217,6 +217,7 @@ func (m *Member) ResolveRoleName() {
}
}
+//根据账号查找用户.
func (m *Member) FindByAccount(account string) (*Member, error) {
o := orm.NewOrm()
@@ -228,6 +229,7 @@ func (m *Member) FindByAccount(account string) (*Member, error) {
return m, err
}
+//分页查找用户.
func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member, int64, error) {
o := orm.NewOrm()
@@ -260,6 +262,7 @@ func (c *Member) IsAdministrator() bool {
return c.Role == 0 || c.Role == 1
}
+//根据指定字段查找用户.
func (m *Member) FindByFieldFirst(field string, value interface{}) (*Member, error) {
o := orm.NewOrm()
@@ -268,6 +271,7 @@ func (m *Member) FindByFieldFirst(field string, value interface{}) (*Member, err
return m, err
}
+//校验用户.
func (m *Member) Valid(is_hash_password bool) error {
//邮箱不能为空
@@ -324,6 +328,67 @@ func (m *Member) Valid(is_hash_password bool) error {
return nil
}
+//删除一个用户.
+
+func (m *Member) Delete(oldId int,newId int) error {
+ o := orm.NewOrm()
+
+ err := o.Begin()
+
+ if err != nil {
+ return err
+ }
+
+ _,err = o.Raw("DELETE FROM md_members WHERE member_id = ?",oldId).Exec()
+ if err != nil {
+ o.Rollback()
+ return err
+ }
+ _,err = o.Raw("UPDATE md_attachment SET `create_at` = ? WHERE `create_at` = ?",newId,oldId).Exec()
+
+ if err != nil {
+ o.Rollback()
+ return err
+ }
+
+ _,err = o.Raw("UPDATE md_books SET member_id = ? WHERE member_id = ?",newId,oldId).Exec()
+ if err != nil {
+ o.Rollback()
+ return err
+ }
+ _,err = o.Raw("UPDATE md_document_history SET member_id=? WHERE member_id = ?",newId,oldId).Exec()
+ if err != nil {
+ o.Rollback()
+ return err
+ }
+ _,err = o.Raw("UPDATE md_document_history SET modify_at=? WHERE modify_at = ?",newId,oldId).Exec()
+ if err != nil {
+ o.Rollback()
+ return err
+ }
+ _,err = o.Raw("UPDATE md_documents SET member_id = ? WHERE member_id = ?;",newId,oldId).Exec()
+ if err != nil {
+ o.Rollback()
+ return err
+ }
+ _,err = o.Raw("UPDATE md_documents SET modify_at = ? WHERE modify_at = ?",newId,oldId).Exec()
+ if err != nil {
+ o.Rollback()
+ return err
+ }
+ _,err = o.Raw("UPDATE md_relationship SET member_id = ? WHERE member_id = ?",newId,oldId).Exec()
+ if err != nil {
+ o.Rollback()
+ return err
+ }
+ if err = o.Commit();err != nil {
+ o.Rollback()
+ return err
+ }
+ return nil
+}
+
+
diff --git a/routers/filter.go b/routers/filter.go
index da34de98..b9811682 100644
--- a/routers/filter.go
+++ b/routers/filter.go
@@ -5,6 +5,7 @@ import (
"github.com/astaxie/beego/context"
"github.com/lifei6671/mindoc/conf"
"github.com/lifei6671/mindoc/models"
+ "encoding/json"
)
func init() {
@@ -12,7 +13,17 @@ func init() {
_, ok := ctx.Input.Session(conf.LoginSessionName).(models.Member)
if !ok {
- ctx.Redirect(302, beego.URLFor("AccountController.Login"))
+ if ctx.Input.IsAjax() {
+ jsonData := make(map[string]interface{},3)
+
+ jsonData["errcode"] = 403
+ jsonData["message"] = "请登录后再操作"
+ returnJSON, _ := json.Marshal(jsonData)
+
+ ctx.ResponseWriter.Write(returnJSON)
+ }else{
+ ctx.Redirect(302, beego.URLFor("AccountController.Login"))
+ }
}
}
beego.InsertFilter("/manager",beego.BeforeRouter,FilterUser)
diff --git a/routers/router.go b/routers/router.go
index a951dadb..880ec872 100644
--- a/routers/router.go
+++ b/routers/router.go
@@ -19,6 +19,7 @@ func init() {
beego.Router("/manager/users", &controllers.ManagerController{},"*:Users")
beego.Router("/manager/users/edit/:id", &controllers.ManagerController{},"*:EditMember")
beego.Router("/manager/member/create", &controllers.ManagerController{},"post:CreateMember")
+ beego.Router("/manager/member/delete", &controllers.ManagerController{},"post:DeleteMember")
beego.Router("/manager/member/update-member-status",&controllers.ManagerController{},"post:UpdateMemberStatus")
beego.Router("/manager/member/change-member-role", &controllers.ManagerController{},"post:ChangeMemberRole")
beego.Router("/manager/books", &controllers.ManagerController{},"*:Books")
@@ -82,5 +83,7 @@ func init() {
beego.Router("/comment/index", &controllers.CommentController{},"*:Index")
beego.Router("/search",&controllers.SearchController{},"get:Index")
+
+ beego.Router("/tag/:key", &controllers.LabelController{},"get:Index")
}
diff --git a/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.css b/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.css
new file mode 100644
index 00000000..b31f01c7
--- /dev/null
+++ b/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.css
@@ -0,0 +1,55 @@
+.bootstrap-tagsinput {
+ background-color: #fff;
+ border: 1px solid #ccc;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ display: inline-block;
+ padding: 4px 6px;
+ color: #555;
+ vertical-align: middle;
+ border-radius: 4px;
+ max-width: 100%;
+ line-height: 22px;
+ cursor: text;
+}
+.bootstrap-tagsinput input {
+ border: none;
+ box-shadow: none;
+ outline: none;
+ background-color: transparent;
+ padding: 0 6px;
+ margin: 0;
+ width: auto;
+ max-width: inherit;
+}
+.bootstrap-tagsinput.form-control input::-moz-placeholder {
+ color: #777;
+ opacity: 1;
+}
+.bootstrap-tagsinput.form-control input:-ms-input-placeholder {
+ color: #777;
+}
+.bootstrap-tagsinput.form-control input::-webkit-input-placeholder {
+ color: #777;
+}
+.bootstrap-tagsinput input:focus {
+ border: none;
+ box-shadow: none;
+}
+.bootstrap-tagsinput .tag {
+ margin-right: 2px;
+ color: white;
+}
+.bootstrap-tagsinput .tag [data-role="remove"] {
+ margin-left: 8px;
+ cursor: pointer;
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:after {
+ content: "x";
+ padding: 0px 2px;
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:hover {
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
diff --git a/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.js b/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.js
new file mode 100644
index 00000000..2b403f77
--- /dev/null
+++ b/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.js
@@ -0,0 +1,646 @@
+(function ($) {
+ "use strict";
+
+ var defaultOptions = {
+ tagClass: function(item) {
+ return 'label label-info';
+ },
+ itemValue: function(item) {
+ return item ? item.toString() : item;
+ },
+ itemText: function(item) {
+ return this.itemValue(item);
+ },
+ itemTitle: function(item) {
+ return null;
+ },
+ freeInput: true,
+ addOnBlur: true,
+ maxTags: undefined,
+ maxChars: undefined,
+ confirmKeys: [13, 44],
+ delimiter: ',',
+ delimiterRegex: null,
+ cancelConfirmKeysOnEmpty: true,
+ onTagExists: function(item, $tag) {
+ $tag.hide().fadeIn();
+ },
+ trimValue: false,
+ allowDuplicates: false
+ };
+
+ /**
+ * Constructor function
+ */
+ function TagsInput(element, options) {
+ this.itemsArray = [];
+
+ this.$element = $(element);
+ this.$element.hide();
+
+ this.isSelect = (element.tagName === 'SELECT');
+ this.multiple = (this.isSelect && element.hasAttribute('multiple'));
+ this.objectItems = options && options.itemValue;
+ this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
+ this.inputSize = Math.max(1, this.placeholderText.length);
+
+ this.$container = $('
Hello
\n\nGoodbye
\n", + + "* List\n# Header\n* List\n", + "List
\n\nList
List
\n\nList
List
\n\nNested list
\n\n" + + "####### Header 7
\n", + + "#Header 1\n", + "#Header 1
\n", + + "##Header 2\n", + "##Header 2
\n", + + "###Header 3\n", + "###Header 3
\n", + + "####Header 4\n", + "####Header 4
\n", + + "#####Header 5\n", + "#####Header 5
\n", + + "######Header 6\n", + "######Header 6
\n", + + "#######Header 7\n", + "#######Header 7
\n", + + "Hello\n# Header 1\nGoodbye\n", + "Hello
\n\nGoodbye
\n", + + "* List\n# Header\n* List\n", + "List
\n\nList
List
\n\nNested list
\n\n" + + "}
\n", + + "## Header 2 {#someid}\n", + "Hello
\n\nGoodbye
\n", + + "* List\n# Header {#someid}\n* List\n", + "List
\n\nList
List
\n\nList
List
\n\nNested list
\n\n" + + "List
\n\nList
List
\n\nList
List
\n\nNested list
\n\n" + + "Hello
\n\nGoodbye
\n", + + "* List\n# Header\n* List\n", + "List
\n\nList
List
\n\nList
List
\n\nNested list
\n\n" + + "Hello
\n\nGoodbye
\n", + + "* List\n# Header\n* List\n", + "List
\n\nList
List
\n\nList
List
\n\nNested list
\n\n" + + "Paragraph
\n\nParagraph
\n", + + "Header\n===\nAnother header\n---\n", + "Code\n
\n\n========
\n", + + "Header with *inline*\n=====\n", + "Paragraph
\n\n=====
\n", + } + doTestsBlock(t, tests, 0) +} + +func TestUnderlineHeadersAutoIDs(t *testing.T) { + var tests = []string{ + "Header 1\n========\n", + "Paragraph
\n\nParagraph
\n", + + "Header\n===\nAnother header\n---\n", + "Paragraph
\n\n=====
\n", + + "Header\n======\n\nHeader\n======\n", + "-
\n", + + "--\n", + "--
\n", + + "---\n", + "*
\n", + + "**\n", + "**
\n", + + "***\n", + "_
\n", + + "__\n", + "__
\n", + + "___\n", + "-*-
\n", + + "- - -\n", + "-----*
\n", + + " ------ \n", + "Hello
\n\nYin
Yang
Ting
Bong
Goo
Yin
Yang
Ting
Bong
Goo
Yin
Yang
Ting
Bong
Goo
*Hello
\n", + + "* Hello \n", + "Paragraph\n* No linebreak
\n", + + "Paragraph\n\n* Linebreak\n", + "Paragraph
\n\nList
\n\nList\nSecond line
\n\nList
\n\nContinued
List
\n\ncode block\n
List
\n\n code block with spaces\n
List
\n\nnormal text
\n\nFoo
+ +bar
+
+qux
+
Foo
+ +bar
+
+qux
+
Yin
Yang
Ting
Bong
Goo
1 Hello
\n", + + "1.Hello\n", + "1.Hello
\n", + + "1. Hello \n", + "Paragraph\n1. No linebreak
\n", + + "Paragraph\n\n1. Linebreak\n", + "Paragraph
\n\nList
\n\nList\nSecond line
\n\nList
\n\nContinued
List
\n\ncode block\n
List
\n\n code block with spaces\n
Foo
+ +bar
+
+
+
+qux
+
Definition a
Definition b
Definition a
Definition b
Definition c
Term 1\n:Definition a
\n", + + "Term 1\n\n: Definition a\n\nTerm 2\n\n: Definition b\n\nText 1", + "Definition a
Definition b
Text 1
\n", + + "Term 1\n\n: Definition a\n\nText 1\n\nTerm 2\n\n: Definition b\n\nText 2", + "Definition a
Text 1
\n" + + "\nDefinition b
Text 2
\n", + + "Term 1\n: Definition a\n\n Text 1\n\n 1. First\n 2. Second", + "Definition a
\n\n" + + "Text 1
\n\n" + + "Paragraph\n
Paragraph
\n\nParagraph\n
Paragraph
\n\nParagraph\n
And here?
\n", + + "Paragraph\n\nParagraph
\n\nAnd here?
\n", + } + doTestsBlock(t, tests, 0) +} + +func TestPreformattedHtmlLax(t *testing.T) { + var tests = []string{ + "Paragraph\nParagraph
\n\nParagraph
\n\nParagraph
\n\nAnd here?
\n", + + "Paragraph\n\nParagraph
\n\nAnd here?
\n", + + "Paragraph\nParagraph
\n\nAnd here?
\n", + + "Paragraph\n\nParagraph
\n\nAnd here?
\n", + } + doTestsBlock(t, tests, EXTENSION_LAX_HTML_BLOCKS) +} + +func TestFencedCodeBlock(t *testing.T) { + var tests = []string{ + "``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n", + "func foo() bool {\n\treturn true;\n}\n
\n",
+
+ "``` c\n/* special & char < > \" escaping */\n```\n",
+ "/* special & char < > " escaping */\n
\n",
+
+ "``` c\nno *inline* processing ~~of text~~\n```\n",
+ "no *inline* processing ~~of text~~\n
\n",
+
+ "```\nNo language\n```\n",
+ "No language\n
\n",
+
+ "``` {ocaml}\nlanguage in braces\n```\n",
+ "language in braces\n
\n",
+
+ "``` {ocaml} \nwith extra whitespace\n```\n",
+ "with extra whitespace\n
\n",
+
+ "```{ ocaml }\nwith extra whitespace\n```\n",
+ "with extra whitespace\n
\n",
+
+ "~ ~~ java\nWith whitespace\n~~~\n",
+ "~ ~~ java\nWith whitespace\n~~~
\n", + + "~~\nonly two\n~~\n", + "~~\nonly two\n~~
\n", + + "```` python\nextra\n````\n", + "extra\n
\n",
+
+ "~~~ perl\nthree to start, four to end\n~~~~\n",
+ "~~~ perl\nthree to start, four to end\n~~~~
\n", + + "~~~~ perl\nfour to start, three to end\n~~~\n", + "~~~~ perl\nfour to start, three to end\n~~~
\n", + + "~~~ bash\ntildes\n~~~\n", + "tildes\n
\n",
+
+ "``` lisp\nno ending\n",
+ "``` lisp\nno ending
\n", + + "~~~ lisp\nend with language\n~~~ lisp\n", + "~~~ lisp\nend with language\n~~~ lisp
\n", + + "```\nmismatched begin and end\n~~~\n", + "```\nmismatched begin and end\n~~~
\n", + + "~~~\nmismatched begin and end\n```\n", + "~~~\nmismatched begin and end\n```
\n", + + " ``` oz\nleading spaces\n```\n", + "leading spaces\n
\n",
+
+ " ``` oz\nleading spaces\n ```\n",
+ "leading spaces\n
\n",
+
+ " ``` oz\nleading spaces\n ```\n",
+ "leading spaces\n
\n",
+
+ "``` oz\nleading spaces\n ```\n",
+ "leading spaces\n
\n",
+
+ " ``` oz\nleading spaces\n ```\n",
+ "``` oz\n
\n\nleading spaces\n ```
\n", + + "Bla bla\n\n``` oz\ncode blocks breakup paragraphs\n```\n\nBla Bla\n", + "Bla bla
\n\ncode blocks breakup paragraphs\n
\n\nBla Bla
\n", + + "Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nAnd some text after a fenced code block", + "Some text before a fenced code block
\n\ncode blocks breakup paragraphs\n
\n\nAnd some text after a fenced code block
\n", + + "`", + "`
\n", + + "Bla bla\n\n``` oz\ncode blocks breakup paragraphs\n```\n\nBla Bla\n\n``` oz\nmultiple code blocks work okay\n```\n\nBla Bla\n", + "Bla bla
\n\ncode blocks breakup paragraphs\n
\n\nBla Bla
\n\nmultiple code blocks work okay\n
\n\nBla Bla
\n", + + "Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nSome text in between\n``` oz\nmultiple code blocks work okay\n```\nAnd some text after a fenced code block", + "Some text before a fenced code block
\n\ncode blocks breakup paragraphs\n
\n\nSome text in between
\n\nmultiple code blocks work okay\n
\n\nAnd some text after a fenced code block
\n", + + "```\n[]:()\n```\n", + "[]:()\n
\n",
+
+ "```\n[]:()\n[]:)\n[]:(\n[]:x\n[]:testing\n[:testing\n\n[]:\nlinebreak\n[]()\n\n[]:\n[]()\n```",
+ "[]:()\n[]:)\n[]:(\n[]:x\n[]:testing\n[:testing\n\n[]:\nlinebreak\n[]()\n\n[]:\n[]()\n
\n",
+ }
+ doTestsBlock(t, tests, EXTENSION_FENCED_CODE)
+}
+
+func TestFencedCodeInsideBlockquotes(t *testing.T) {
+ cat := func(s ...string) string { return strings.Join(s, "\n") }
+ var tests = []string{
+ cat("> ```go",
+ "package moo",
+ "",
+ "```",
+ ""),
+ `++`, + // ------------------------------------------- + cat("> foo", + "> ", + "> ```go", + "package moo", + "```", + "> ", + "> goo.", + ""), + `+package moo + +
++`, + // ------------------------------------------- + cat("> foo", + "> ", + "> quote", + "continues", + "```", + ""), + `foo
+ ++ +package moo +
goo.
+
++`, + // ------------------------------------------- + cat("> foo", + "> ", + "> ```go", + "package moo", + "```", + "> ", + "> goo.", + "> ", + "> ```go", + "package zoo", + "```", + "> ", + "> woo.", + ""), + `foo
+ +quote +continues +` + "```" + `
+
++`, + } + + // These 2 alternative forms of blockquoted fenced code blocks should produce same output. + forms := [2]string{ + cat("> plain quoted text", + "> ```fenced", + "code", + " with leading single space correctly preserved", + "okay", + "```", + "> rest of quoted text"), + cat("> plain quoted text", + "> ```fenced", + "> code", + "> with leading single space correctly preserved", + "> okay", + "> ```", + "> rest of quoted text"), + } + want := `foo
+ ++ +package moo +
goo.
+ ++ +package zoo +
woo.
+
++` + tests = append(tests, forms[0], want) + tests = append(tests, forms[1], want) + + doTestsBlock(t, tests, EXTENSION_FENCED_CODE) +} + +func TestTable(t *testing.T) { + var tests = []string{ + "a | b\n---|---\nc | d\n", + "plain quoted text
+ ++ +code + with leading single space correctly preserved +okay +
rest of quoted text
+
a | \nb | \n
---|---|
c | \nd | \n
a | b\n---|--\nc | d
\n", + + "|a|b|c|d|\n|----|----|----|---|\n|e|f|g|h|\n", + "a | \nb | \nc | \nd | \n
---|---|---|---|
e | \nf | \ng | \nh | \n
a | \nb | \nc | \nd | \n
---|---|---|---|
e | \nf | \ng | \nh | \n
a | \nb | \nc | \n
---|---|---|
d | \ne | \nf | \n
g | \nh | \n\n |
i | \nj | \nk | \n
n | \no | \np | \n
a | \nb | \nc | \n
---|---|---|
d | \ne | \nf | \n
a | \nb | \n" + + "c | \nd | \n
---|---|---|---|
e | \nf | \n" + + "g | \nh | \n
a | \nb | \nc | \n
---|
a | \nb | \nc | \nd | \ne | \n
---|---|---|---|---|
f | \ng | \nh | \ni | \nj | \n
a | \nb|c | \nd | \n
---|---|---|
f | \ng|h | \ni | \n
Yin
Yang
Ting
Bong
Goo
Yin
Yang
Ting
Bong
Goo
Yin
Yang
Ting
Bong
Goo
*Hello
\n", + + "* Hello \n", + "Paragraph
\n\nParagraph
\n\nList
\n\nList\nSecond line
\n\nList
\n\nContinued
List
\n\ncode block\n
List
\n\n code block with spaces\n
List
\n\nnormal text
\n\nYin
Yang
Ting
Bong
Goo
1 Hello
\n", + + "1.Hello\n", + "1.Hello
\n", + + "1. Hello \n", + "Paragraph
\n\nParagraph
\n\nList
\n\nList\nSecond line
\n\nList
\n\nContinued
List
\n\ncode block\n
List
\n\n code block with spaces\n
func foo() bool {\n\treturn true;\n}\n
\n",
+
+ "``` c\n/* special & char < > \" escaping */\n```\n",
+ "/* special & char < > " escaping */\n
\n",
+
+ "``` c\nno *inline* processing ~~of text~~\n```\n",
+ "no *inline* processing ~~of text~~\n
\n",
+
+ "```\nNo language\n```\n",
+ "No language\n
\n",
+
+ "``` {ocaml}\nlanguage in braces\n```\n",
+ "language in braces\n
\n",
+
+ "``` {ocaml} \nwith extra whitespace\n```\n",
+ "with extra whitespace\n
\n",
+
+ "```{ ocaml }\nwith extra whitespace\n```\n",
+ "with extra whitespace\n
\n",
+
+ "~ ~~ java\nWith whitespace\n~~~\n",
+ "~ ~~ java\nWith whitespace\n~~~
\n", + + "~~\nonly two\n~~\n", + "~~\nonly two\n~~
\n", + + "```` python\nextra\n````\n", + "extra\n
\n",
+
+ "~~~ perl\nthree to start, four to end\n~~~~\n",
+ "~~~ perl\nthree to start, four to end\n~~~~
\n", + + "~~~~ perl\nfour to start, three to end\n~~~\n", + "~~~~ perl\nfour to start, three to end\n~~~
\n", + + "~~~ bash\ntildes\n~~~\n", + "tildes\n
\n",
+
+ "``` lisp\nno ending\n",
+ "``` lisp\nno ending
\n", + + "~~~ lisp\nend with language\n~~~ lisp\n", + "~~~ lisp\nend with language\n~~~ lisp
\n", + + "```\nmismatched begin and end\n~~~\n", + "```\nmismatched begin and end\n~~~
\n", + + "~~~\nmismatched begin and end\n```\n", + "~~~\nmismatched begin and end\n```
\n", + + " ``` oz\nleading spaces\n```\n", + "leading spaces\n
\n",
+
+ " ``` oz\nleading spaces\n ```\n",
+ "leading spaces\n
\n",
+
+ " ``` oz\nleading spaces\n ```\n",
+ "leading spaces\n
\n",
+
+ "``` oz\nleading spaces\n ```\n",
+ "leading spaces\n
\n",
+
+ " ``` oz\nleading spaces\n ```\n",
+ "``` oz\n
\n\nleading spaces
\n\n```\n
\n",
+ }
+ doTestsBlock(t, tests, EXTENSION_FENCED_CODE|EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
+}
+
+func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) {
+ var tests = []string{
+ "% Some title\n" +
+ "% Another title line\n" +
+ "% Yep, more here too\n",
+ "Some text
\n\n\n", + + "Some text\n\n\n", + "Some text
\n\n\n", + + "Some text\n\n\n", + "Some text
\n\n\n", + } + doTestsBlock(t, tests, 0) +} + +func TestCDATA(t *testing.T) { + var tests = []string{ + "Some text\n\n\n", + "Some text
\n\n\n", + + "CDATA ]]\n\n\n", + "CDATA ]]
\n\n\n", + + "CDATA >\n\n]]>\n", + "CDATA >
\n\n]]>\n", + + "Lots of text\n\nLots of text
\n\n<![CDATA[foo]]>\n
\n",
+
+ "\n",
+ "\n",
+
+ ` def func():
+> pass
+]]>
+`,
+ ` def func():
+> pass
+]]>
+`,
+ }, EXTENSION_FENCED_CODE)
+}
+
+func TestIsFenceLine(t *testing.T) {
+ tests := []struct {
+ data []byte
+ syntaxRequested bool
+ newlineOptional bool
+ wantEnd int
+ wantMarker string
+ wantSyntax string
+ }{
+ {
+ data: []byte("```"),
+ wantEnd: 0,
+ },
+ {
+ data: []byte("```\nstuff here\n"),
+ wantEnd: 4,
+ wantMarker: "```",
+ },
+ {
+ data: []byte("```\nstuff here\n"),
+ syntaxRequested: true,
+ wantEnd: 4,
+ wantMarker: "```",
+ },
+ {
+ data: []byte("stuff here\n```\n"),
+ wantEnd: 0,
+ },
+ {
+ data: []byte("```"),
+ newlineOptional: true,
+ wantEnd: 3,
+ wantMarker: "```",
+ },
+ {
+ data: []byte("```"),
+ syntaxRequested: true,
+ newlineOptional: true,
+ wantEnd: 3,
+ wantMarker: "```",
+ },
+ {
+ data: []byte("``` go"),
+ syntaxRequested: true,
+ newlineOptional: true,
+ wantEnd: 6,
+ wantMarker: "```",
+ wantSyntax: "go",
+ },
+ }
+
+ for _, test := range tests {
+ var syntax *string
+ if test.syntaxRequested {
+ syntax = new(string)
+ }
+ end, marker := isFenceLine(test.data, syntax, "```", test.newlineOptional)
+ if got, want := end, test.wantEnd; got != want {
+ t.Errorf("got end %v, want %v", got, want)
+ }
+ if got, want := marker, test.wantMarker; got != want {
+ t.Errorf("got marker %q, want %q", got, want)
+ }
+ if test.syntaxRequested {
+ if got, want := *syntax, test.wantSyntax; got != want {
+ t.Errorf("got syntax %q, want %q", got, want)
+ }
+ }
+ }
+}
+
+func TestJoinLines(t *testing.T) {
+ input := `# 标题
+
+第一
+行文字。
+
+第
+二
+行文字。
+`
+ result := `第一行文字。
+ +第二行文字。
+` + opt := Options{Extensions: commonExtensions | EXTENSION_JOIN_LINES} + renderer := HtmlRenderer(commonHtmlFlags, "", "") + output := MarkdownOptions([]byte(input), renderer, opt) + + if string(output) != result { + t.Error("output dose not match.") + } +} + +func TestSanitizedAnchorName(t *testing.T) { + tests := []struct { + text string + want string + }{ + { + text: "This is a header", + want: "this-is-a-header", + }, + { + text: "This is also a header", + want: "this-is-also-a-header", + }, + { + text: "main.go", + want: "main-go", + }, + { + text: "Article 123", + want: "article-123", + }, + { + text: "<- Let's try this, shall we?", + want: "let-s-try-this-shall-we", + }, + { + text: " ", + want: "", + }, + { + text: "Hello, 世界", + want: "hello-世界", + }, + } + for _, test := range tests { + if got := SanitizedAnchorName(test.text); got != test.want { + t.Errorf("SanitizedAnchorName(%q):\ngot %q\nwant %q", test.text, got, test.want) + } + } +} diff --git a/vendor/github.com/russross/blackfriday/doc.go b/vendor/github.com/russross/blackfriday/doc.go new file mode 100644 index 00000000..9656c42a --- /dev/null +++ b/vendor/github.com/russross/blackfriday/doc.go @@ -0,0 +1,32 @@ +// Package blackfriday is a Markdown processor. +// +// It translates plain text with simple formatting rules into HTML or LaTeX. +// +// Sanitized Anchor Names +// +// Blackfriday includes an algorithm for creating sanitized anchor names +// corresponding to a given input text. This algorithm is used to create +// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The +// algorithm is specified below, so that other packages can create +// compatible anchor names and links to those anchors. +// +// The algorithm iterates over the input text, interpreted as UTF-8, +// one Unicode code point (rune) at a time. All runes that are letters (category L) +// or numbers (category N) are considered valid characters. They are mapped to +// lower case, and included in the output. All other runes are considered +// invalid characters. Invalid characters that preceed the first valid character, +// as well as invalid character that follow the last valid character +// are dropped completely. All other sequences of invalid characters +// between two valid characters are replaced with a single dash character '-'. +// +// SanitizedAnchorName exposes this functionality, and can be used to +// create compatible links to the anchor names generated by blackfriday. +// This algorithm is also implemented in a small standalone package at +// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients +// that want a small package and don't need full functionality of blackfriday. +package blackfriday + +// NOTE: Keep Sanitized Anchor Name algorithm in sync with package +// github.com/shurcooL/sanitized_anchor_name. +// Otherwise, users of sanitized_anchor_name will get anchor names +// that are incompatible with those generated by blackfriday. diff --git a/vendor/github.com/russross/blackfriday/html.go b/vendor/github.com/russross/blackfriday/html.go new file mode 100644 index 00000000..74e67ee8 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/html.go @@ -0,0 +1,949 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross