mirror of https://github.com/mindoc-org/mindoc.git
parent
c7f32d7cb5
commit
47687cc5b1
|
@ -78,6 +78,7 @@ func RegisterModel() {
|
||||||
new(models.MemberToken),
|
new(models.MemberToken),
|
||||||
new(models.DocumentHistory),
|
new(models.DocumentHistory),
|
||||||
new(models.Migration),
|
new(models.Migration),
|
||||||
|
new(models.Label),
|
||||||
)
|
)
|
||||||
migrate.RegisterMigration()
|
migrate.RegisterMigration()
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@ func (c *BookController) SaveBook() {
|
||||||
comment_status = "closed"
|
comment_status = "closed"
|
||||||
}
|
}
|
||||||
if tag != ""{
|
if tag != ""{
|
||||||
tags := strings.Split(tag,";")
|
tags := strings.Split(tag,",")
|
||||||
if len(tags) > 10 {
|
if len(tags) > 10 {
|
||||||
c.JsonResult(6005,"最多允许添加10个标签")
|
c.JsonResult(6005,"最多允许添加10个标签")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/lifei6671/mindoc/models"
|
"github.com/lifei6671/mindoc/models"
|
||||||
"github.com/lifei6671/mindoc/utils"
|
"github.com/lifei6671/mindoc/utils"
|
||||||
"github.com/lifei6671/mindoc/utils/wkhtmltopdf"
|
"github.com/lifei6671/mindoc/utils/wkhtmltopdf"
|
||||||
|
"github.com/russross/blackfriday"
|
||||||
)
|
)
|
||||||
|
|
||||||
//DocumentController struct.
|
//DocumentController struct.
|
||||||
|
@ -126,7 +127,7 @@ func (c *DocumentController) Index() {
|
||||||
c.Data["Model"] = bookResult
|
c.Data["Model"] = bookResult
|
||||||
c.Data["Result"] = template.HTML(tree)
|
c.Data["Result"] = template.HTML(tree)
|
||||||
c.Data["Title"] = "概要"
|
c.Data["Title"] = "概要"
|
||||||
c.Data["Content"] = bookResult.Description
|
c.Data["Content"] = template.HTML( blackfriday.MarkdownBasic([]byte(bookResult.Description)))
|
||||||
}
|
}
|
||||||
|
|
||||||
//阅读文档.
|
//阅读文档.
|
||||||
|
|
|
@ -43,4 +43,11 @@ func (c *HomeController) Index() {
|
||||||
|
|
||||||
c.Data["Lists"] = books
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -186,6 +186,7 @@ func (c *ManagerController) ChangeMemberRole() {
|
||||||
c.JsonResult(0, "ok", member)
|
c.JsonResult(0, "ok", member)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//编辑用户信息.
|
||||||
func (c *ManagerController) EditMember() {
|
func (c *ManagerController) EditMember() {
|
||||||
c.Prepare()
|
c.Prepare()
|
||||||
c.TplName = "manager/edit_users.tpl"
|
c.TplName = "manager/edit_users.tpl"
|
||||||
|
@ -238,6 +239,38 @@ func (c *ManagerController) EditMember() {
|
||||||
c.Data["Model"] = member
|
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() {
|
func (c *ManagerController) Books() {
|
||||||
c.Prepare()
|
c.Prepare()
|
||||||
c.TplName = "manager/books.tpl"
|
c.TplName = "manager/books.tpl"
|
||||||
|
@ -261,7 +294,7 @@ func (c *ManagerController) Books() {
|
||||||
c.Data["Lists"] = books
|
c.Data["Lists"] = books
|
||||||
}
|
}
|
||||||
|
|
||||||
//编辑项目
|
//编辑项目.
|
||||||
func (c *ManagerController) EditBook() {
|
func (c *ManagerController) EditBook() {
|
||||||
c.Prepare()
|
c.Prepare()
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
# Created by .ignore support plugin (hsz.mobi)
|
|
326
models/book.go
326
models/book.go
|
@ -3,53 +3,55 @@ package models
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/lifei6671/mindoc/conf"
|
"github.com/lifei6671/mindoc/conf"
|
||||||
"github.com/astaxie/beego/logs"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"github.com/astaxie/beego"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Book struct .
|
// Book struct .
|
||||||
type 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 项目名称.
|
||||||
BookName string `orm:"column(book_name);size(500)" json:"book_name"`
|
BookName string `orm:"column(book_name);size(500)" json:"book_name"`
|
||||||
// Identify 项目唯一标识.
|
// Identify 项目唯一标识.
|
||||||
Identify string `orm:"column(identify);size(100);unique" json:"identify"`
|
Identify string `orm:"column(identify);size(100);unique" json:"identify"`
|
||||||
OrderIndex int `orm:"column(order_index);type(int);default(0)" json:"order_index"`
|
OrderIndex int `orm:"column(order_index);type(int);default(0)" json:"order_index"`
|
||||||
// Description 项目描述.
|
// Description 项目描述.
|
||||||
Description string `orm:"column(description);size(2000)" json:"description"`
|
Description string `orm:"column(description);size(2000)" json:"description"`
|
||||||
Label string `orm:"column(label);size(500)" json:"label"`
|
Label string `orm:"column(label);size(500)" json:"label"`
|
||||||
// PrivatelyOwned 项目私有: 0 公开/ 1 私有
|
// 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.
|
// 当项目是私有时的访问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 已删除
|
//状态: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 包含文档数量.
|
||||||
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 评论设置的状态:open 为允许所有人评论,closed 为不允许评论, group_only 仅允许参与者评论 ,registered_only 仅允许注册者评论.
|
||||||
CommentStatus string `orm:"column(comment_status);size(20);default(open)" json:"comment_status"`
|
CommentStatus string `orm:"column(comment_status);size(20);default(open)" json:"comment_status"`
|
||||||
CommentCount int `orm:"column(comment_count);type(int)" json:"comment_count"`
|
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 创建时间 .
|
||||||
CreateTime time.Time `orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
|
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"`
|
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"`
|
ModifyTime time.Time `orm:"type(datetime);column(modify_time);null;auto_now" json:"modify_time"`
|
||||||
Version int64 `orm:"type(bigint);column(version)" json:"version"`
|
Version int64 `orm:"type(bigint);column(version)" json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TableName 获取对应数据库表名.
|
// TableName 获取对应数据库表名.
|
||||||
func (m *Book) TableName() string {
|
func (m *Book) TableName() string {
|
||||||
return "books"
|
return "books"
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableEngine 获取数据使用的引擎.
|
// TableEngine 获取数据使用的引擎.
|
||||||
func (m *Book) TableEngine() string {
|
func (m *Book) TableEngine() string {
|
||||||
return "INNODB"
|
return "INNODB"
|
||||||
|
@ -64,18 +66,22 @@ func NewBook() *Book {
|
||||||
|
|
||||||
func (m *Book) Insert() error {
|
func (m *Book) Insert() error {
|
||||||
o := orm.NewOrm()
|
o := orm.NewOrm()
|
||||||
// o.Begin()
|
// o.Begin()
|
||||||
|
|
||||||
_,err := o.Insert(m)
|
_, err := o.Insert(m)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if m.Label != "" {
|
||||||
|
NewLabel().InsertOrUpdateMulti(m.Label)
|
||||||
|
}
|
||||||
|
|
||||||
relationship := NewRelationship()
|
relationship := NewRelationship()
|
||||||
relationship.BookId = m.BookId
|
relationship.BookId = m.BookId
|
||||||
relationship.RoleId = 0
|
relationship.RoleId = 0
|
||||||
relationship.MemberId = m.MemberId
|
relationship.MemberId = m.MemberId
|
||||||
err = relationship.Insert()
|
err = relationship.Insert()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Error("插入项目与用户关联 => ",err)
|
logs.Error("插入项目与用户关联 => ", err)
|
||||||
//o.Rollback()
|
//o.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -84,7 +90,7 @@ func (m *Book) Insert() error {
|
||||||
document.DocumentName = "空白文档"
|
document.DocumentName = "空白文档"
|
||||||
document.MemberId = m.MemberId
|
document.MemberId = m.MemberId
|
||||||
err = document.InsertOrUpdate()
|
err = document.InsertOrUpdate()
|
||||||
if err != nil{
|
if err != nil {
|
||||||
//o.Rollback()
|
//o.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -95,113 +101,113 @@ func (m *Book) Insert() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Book) Find(id int) (*Book,error) {
|
func (m *Book) Find(id int) (*Book, error) {
|
||||||
if id <= 0 {
|
if id <= 0 {
|
||||||
return m,ErrInvalidParameter
|
return m, ErrInvalidParameter
|
||||||
}
|
}
|
||||||
o := orm.NewOrm()
|
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()
|
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
|
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()
|
o := orm.NewOrm()
|
||||||
|
|
||||||
var books []*Book
|
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()
|
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()
|
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()
|
relationship := NewRelationship()
|
||||||
|
|
||||||
o := orm.NewOrm()
|
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").
|
err = o.Raw(sql1, memberId).QueryRow(&totalCount)
|
||||||
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)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := (pageIndex - 1) * pageSize
|
offset := (pageIndex - 1) * pageSize
|
||||||
qb2,_ := orm.NewQueryBuilder("mysql")
|
|
||||||
|
|
||||||
qb2.Select("book.*,rel.member_id","rel.role_id","m.account as create_name").
|
sql2 := "SELECT book.*,rel.member_id,rel.role_id,m.account as create_name FROM " + m.TableNameWithPrefix() + " AS book" +
|
||||||
From(m.TableNameWithPrefix() + " AS book").
|
" LEFT JOIN " + relationship.TableNameWithPrefix() + " AS rel ON book.book_id=rel.book_id AND rel.member_id = ?" +
|
||||||
LeftJoin(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" +
|
||||||
LeftJoin(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 " +
|
||||||
LeftJoin(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)
|
||||||
Where("rel.relationship_id > 0").
|
|
||||||
OrderBy("book.order_index DESC ","book.book_id").Desc().
|
|
||||||
Limit(pageSize).
|
|
||||||
Offset(offset)
|
|
||||||
|
|
||||||
_,err = o.Raw(qb2.String(),memberId).QueryRows(&books)
|
_, err = o.Raw(sql2, memberId).QueryRows(&books)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logs.Error("分页查询项目列表 => ",err)
|
logs.Error("分页查询项目列表 => ", err)
|
||||||
return
|
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"
|
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{
|
if err == nil && len(books) > 0 {
|
||||||
for index,book := range books {
|
for index, book := range books {
|
||||||
var text struct{
|
var text struct {
|
||||||
Account string
|
Account string
|
||||||
ModifyTime time.Time
|
ModifyTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err1 := o.Raw(sql, book.BookId).QueryRow(&text)
|
||||||
err1 := o.Raw(sql,book.BookId).QueryRow(&text)
|
|
||||||
if err1 == nil {
|
if err1 == nil {
|
||||||
books[index].LastModifyText = text.Account + " 于 " + text.ModifyTime.Format("2006-01-02 15:04:05")
|
books[index].LastModifyText = text.Account + " 于 " + text.ModifyTime.Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
if book.RoleId == 0{
|
if book.RoleId == 0 {
|
||||||
book.RoleName = "创始人"
|
book.RoleName = "创始人"
|
||||||
}else if book.RoleId == 1 {
|
} else if book.RoleId == 1 {
|
||||||
book.RoleName = "管理员"
|
book.RoleName = "管理员"
|
||||||
}else if book.RoleId == 2 {
|
} else if book.RoleId == 2 {
|
||||||
book.RoleName = "编辑者"
|
book.RoleName = "编辑者"
|
||||||
}else if book.RoleId == 3 {
|
} else if book.RoleId == 3 {
|
||||||
book.RoleName = "观察者"
|
book.RoleName = "观察者"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +217,7 @@ func (m *Book) FindToPager(pageIndex, pageSize ,memberId int) (books []*BookResu
|
||||||
|
|
||||||
// 彻底删除项目.
|
// 彻底删除项目.
|
||||||
func (m *Book) ThoroughDeleteBook(id int) error {
|
func (m *Book) ThoroughDeleteBook(id int) error {
|
||||||
if id <= 0{
|
if id <= 0 {
|
||||||
return ErrInvalidParameter
|
return ErrInvalidParameter
|
||||||
}
|
}
|
||||||
o := orm.NewOrm()
|
o := orm.NewOrm()
|
||||||
|
@ -221,17 +227,10 @@ func (m *Book) ThoroughDeleteBook(id int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o.Begin()
|
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 = ?"
|
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 {
|
if err != nil {
|
||||||
o.Rollback()
|
o.Rollback()
|
||||||
|
@ -239,7 +238,7 @@ func (m *Book) ThoroughDeleteBook(id int) error {
|
||||||
}
|
}
|
||||||
sql3 := "DELETE FROM " + m.TableNameWithPrefix() + " WHERE book_id = ?"
|
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 {
|
if err != nil {
|
||||||
o.Rollback()
|
o.Rollback()
|
||||||
|
@ -247,19 +246,23 @@ func (m *Book) ThoroughDeleteBook(id int) error {
|
||||||
}
|
}
|
||||||
sql4 := "DELETE FROM " + NewRelationship().TableNameWithPrefix() + " WHERE book_id = ?"
|
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 {
|
if err != nil {
|
||||||
o.Rollback()
|
o.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.Label != "" {
|
||||||
|
NewLabel().InsertOrUpdateMulti(m.Label)
|
||||||
|
}
|
||||||
|
|
||||||
return o.Commit()
|
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()
|
o := orm.NewOrm()
|
||||||
|
|
||||||
offset := (pageIndex - 1) * pageSize
|
offset := (pageIndex - 1) * pageSize
|
||||||
|
@ -267,7 +270,7 @@ func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*
|
||||||
if member_id > 0 {
|
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"
|
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 {
|
if err != nil {
|
||||||
return
|
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
|
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 ?,?`
|
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
|
return
|
||||||
|
|
||||||
}else{
|
} else {
|
||||||
count,err1 := o.QueryTable(m.TableNameWithPrefix()).Filter("privately_owned",0).Count()
|
count, err1 := o.QueryTable(m.TableNameWithPrefix()).Filter("privately_owned", 0).Count()
|
||||||
|
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
err = err1
|
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
|
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 ?,?`
|
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
|
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 {
|
func (book *Book) ToBookResult() *BookResult {
|
||||||
|
|
||||||
m := NewBookResult()
|
m := NewBookResult()
|
||||||
|
|
||||||
m.BookId = book.BookId
|
m.BookId = book.BookId
|
||||||
m.BookName = book.BookName
|
m.BookName = book.BookName
|
||||||
m.Identify = book.Identify
|
m.Identify = book.Identify
|
||||||
m.OrderIndex = book.OrderIndex
|
m.OrderIndex = book.OrderIndex
|
||||||
m.Description = strings.Replace(book.Description, "\r\n", "<br/>", -1)
|
m.Description = strings.Replace(book.Description, "\r\n", "<br/>", -1)
|
||||||
m.PrivatelyOwned = book.PrivatelyOwned
|
m.PrivatelyOwned = book.PrivatelyOwned
|
||||||
m.PrivateToken = book.PrivateToken
|
m.PrivateToken = book.PrivateToken
|
||||||
m.DocCount = book.DocCount
|
m.DocCount = book.DocCount
|
||||||
m.CommentStatus = book.CommentStatus
|
m.CommentStatus = book.CommentStatus
|
||||||
m.CommentCount = book.CommentCount
|
m.CommentCount = book.CommentCount
|
||||||
m.CreateTime = book.CreateTime
|
m.CreateTime = book.CreateTime
|
||||||
m.ModifyTime = book.ModifyTime
|
m.ModifyTime = book.ModifyTime
|
||||||
m.Cover = book.Cover
|
m.Cover = book.Cover
|
||||||
m.Label = book.Label
|
m.Label = book.Label
|
||||||
m.Status = book.Status
|
m.Status = book.Status
|
||||||
m.Editor = book.Editor
|
m.Editor = book.Editor
|
||||||
m.Theme = book.Theme
|
m.Theme = book.Theme
|
||||||
|
|
||||||
|
if book.Theme == "" {
|
||||||
if book.Theme == ""{
|
|
||||||
m.Theme = "default"
|
m.Theme = "default"
|
||||||
}
|
}
|
||||||
if book.Editor == "" {
|
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()
|
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 {
|
if err == nil {
|
||||||
o.Raw("UPDATE md_books SET doc_count = ? WHERE book_id = ?",int(totalCount),book_id).Exec()
|
o.Raw("UPDATE md_books SET doc_count = ? WHERE book_id = ?", int(totalCount), book_id).Exec()
|
||||||
}else{
|
} else {
|
||||||
beego.Error(err)
|
beego.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ func (m *Document) ReleaseContent(book_id int) {
|
||||||
func (m *Document) FindListByBookId(book_id int) (docs []*Document, err error) {
|
func (m *Document) FindListByBookId(book_id int) (docs []*Document, err error) {
|
||||||
o := orm.NewOrm()
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -217,6 +217,7 @@ func (m *Member) ResolveRoleName() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//根据账号查找用户.
|
||||||
func (m *Member) FindByAccount(account string) (*Member, error) {
|
func (m *Member) FindByAccount(account string) (*Member, error) {
|
||||||
o := orm.NewOrm()
|
o := orm.NewOrm()
|
||||||
|
|
||||||
|
@ -228,6 +229,7 @@ func (m *Member) FindByAccount(account string) (*Member, error) {
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//分页查找用户.
|
||||||
func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member, int64, error) {
|
func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member, int64, error) {
|
||||||
o := orm.NewOrm()
|
o := orm.NewOrm()
|
||||||
|
|
||||||
|
@ -260,6 +262,7 @@ func (c *Member) IsAdministrator() bool {
|
||||||
return c.Role == 0 || c.Role == 1
|
return c.Role == 0 || c.Role == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//根据指定字段查找用户.
|
||||||
func (m *Member) FindByFieldFirst(field string, value interface{}) (*Member, error) {
|
func (m *Member) FindByFieldFirst(field string, value interface{}) (*Member, error) {
|
||||||
o := orm.NewOrm()
|
o := orm.NewOrm()
|
||||||
|
|
||||||
|
@ -268,6 +271,7 @@ func (m *Member) FindByFieldFirst(field string, value interface{}) (*Member, err
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//校验用户.
|
||||||
func (m *Member) Valid(is_hash_password bool) error {
|
func (m *Member) Valid(is_hash_password bool) error {
|
||||||
|
|
||||||
//邮箱不能为空
|
//邮箱不能为空
|
||||||
|
@ -324,6 +328,67 @@ func (m *Member) Valid(is_hash_password bool) error {
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/lifei6671/mindoc/conf"
|
"github.com/lifei6671/mindoc/conf"
|
||||||
"github.com/lifei6671/mindoc/models"
|
"github.com/lifei6671/mindoc/models"
|
||||||
|
"encoding/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -12,7 +13,17 @@ func init() {
|
||||||
_, ok := ctx.Input.Session(conf.LoginSessionName).(models.Member)
|
_, ok := ctx.Input.Session(conf.LoginSessionName).(models.Member)
|
||||||
|
|
||||||
if !ok {
|
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)
|
beego.InsertFilter("/manager",beego.BeforeRouter,FilterUser)
|
||||||
|
|
|
@ -19,6 +19,7 @@ func init() {
|
||||||
beego.Router("/manager/users", &controllers.ManagerController{},"*:Users")
|
beego.Router("/manager/users", &controllers.ManagerController{},"*:Users")
|
||||||
beego.Router("/manager/users/edit/:id", &controllers.ManagerController{},"*:EditMember")
|
beego.Router("/manager/users/edit/:id", &controllers.ManagerController{},"*:EditMember")
|
||||||
beego.Router("/manager/member/create", &controllers.ManagerController{},"post:CreateMember")
|
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/update-member-status",&controllers.ManagerController{},"post:UpdateMemberStatus")
|
||||||
beego.Router("/manager/member/change-member-role", &controllers.ManagerController{},"post:ChangeMemberRole")
|
beego.Router("/manager/member/change-member-role", &controllers.ManagerController{},"post:ChangeMemberRole")
|
||||||
beego.Router("/manager/books", &controllers.ManagerController{},"*:Books")
|
beego.Router("/manager/books", &controllers.ManagerController{},"*:Books")
|
||||||
|
@ -82,5 +83,7 @@ func init() {
|
||||||
beego.Router("/comment/index", &controllers.CommentController{},"*:Index")
|
beego.Router("/comment/index", &controllers.CommentController{},"*:Index")
|
||||||
|
|
||||||
beego.Router("/search",&controllers.SearchController{},"get:Index")
|
beego.Router("/search",&controllers.SearchController{},"get:Index")
|
||||||
|
|
||||||
|
beego.Router("/tag/:key", &controllers.LabelController{},"get:Index")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,646 @@
|
||||||
|
(function ($) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var defaultOptions = {
|
||||||
|
tagClass: function(item) {
|
||||||
|
return 'label label-info';
|
||||||
|
},
|
||||||
|
itemValue: function(item) {
|
||||||
|
return item ? item.toString() : item;
|
||||||
|
},
|
||||||
|
itemText: function(item) {
|
||||||
|
return this.itemValue(item);
|
||||||
|
},
|
||||||
|
itemTitle: function(item) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
freeInput: true,
|
||||||
|
addOnBlur: true,
|
||||||
|
maxTags: undefined,
|
||||||
|
maxChars: undefined,
|
||||||
|
confirmKeys: [13, 44],
|
||||||
|
delimiter: ',',
|
||||||
|
delimiterRegex: null,
|
||||||
|
cancelConfirmKeysOnEmpty: true,
|
||||||
|
onTagExists: function(item, $tag) {
|
||||||
|
$tag.hide().fadeIn();
|
||||||
|
},
|
||||||
|
trimValue: false,
|
||||||
|
allowDuplicates: false
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor function
|
||||||
|
*/
|
||||||
|
function TagsInput(element, options) {
|
||||||
|
this.itemsArray = [];
|
||||||
|
|
||||||
|
this.$element = $(element);
|
||||||
|
this.$element.hide();
|
||||||
|
|
||||||
|
this.isSelect = (element.tagName === 'SELECT');
|
||||||
|
this.multiple = (this.isSelect && element.hasAttribute('multiple'));
|
||||||
|
this.objectItems = options && options.itemValue;
|
||||||
|
this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
|
||||||
|
this.inputSize = Math.max(1, this.placeholderText.length);
|
||||||
|
|
||||||
|
this.$container = $('<div class="bootstrap-tagsinput"></div>');
|
||||||
|
this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);
|
||||||
|
|
||||||
|
this.$element.before(this.$container);
|
||||||
|
|
||||||
|
this.build(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
TagsInput.prototype = {
|
||||||
|
constructor: TagsInput,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given item as a new tag. Pass true to dontPushVal to prevent
|
||||||
|
* updating the elements val()
|
||||||
|
*/
|
||||||
|
add: function(item, dontPushVal, options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Ignore falsey values, except false
|
||||||
|
if (item !== false && !item)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Trim value
|
||||||
|
if (typeof item === "string" && self.options.trimValue) {
|
||||||
|
item = $.trim(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw an error when trying to add an object while the itemValue option was not set
|
||||||
|
if (typeof item === "object" && !self.objectItems)
|
||||||
|
throw("Can't add objects when itemValue option is not set");
|
||||||
|
|
||||||
|
// Ignore strings only containg whitespace
|
||||||
|
if (item.toString().match(/^\s*$/))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If SELECT but not multiple, remove current tag
|
||||||
|
if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
|
||||||
|
self.remove(self.itemsArray[0]);
|
||||||
|
|
||||||
|
if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
|
||||||
|
var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter;
|
||||||
|
var items = item.split(delimiter);
|
||||||
|
if (items.length > 1) {
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
this.add(items[i], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dontPushVal)
|
||||||
|
self.pushVal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemValue = self.options.itemValue(item),
|
||||||
|
itemText = self.options.itemText(item),
|
||||||
|
tagClass = self.options.tagClass(item),
|
||||||
|
itemTitle = self.options.itemTitle(item);
|
||||||
|
|
||||||
|
// Ignore items allready added
|
||||||
|
var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
|
||||||
|
if (existing && !self.options.allowDuplicates) {
|
||||||
|
// Invoke onTagExists
|
||||||
|
if (self.options.onTagExists) {
|
||||||
|
var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
|
||||||
|
self.options.onTagExists(item, $existingTag);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if length greater than limit
|
||||||
|
if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// raise beforeItemAdd arg
|
||||||
|
var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options});
|
||||||
|
self.$element.trigger(beforeItemAddEvent);
|
||||||
|
if (beforeItemAddEvent.cancel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// register item in internal array and map
|
||||||
|
self.itemsArray.push(item);
|
||||||
|
|
||||||
|
// add a tag element
|
||||||
|
|
||||||
|
var $tag = $('<span class="tag ' + htmlEncode(tagClass) + (itemTitle !== null ? ('" title="' + itemTitle) : '') + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>');
|
||||||
|
$tag.data('item', item);
|
||||||
|
self.findInputWrapper().before($tag);
|
||||||
|
$tag.after(' ');
|
||||||
|
|
||||||
|
// add <option /> if item represents a value not present in one of the <select />'s options
|
||||||
|
if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) {
|
||||||
|
var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
|
||||||
|
$option.data('item', item);
|
||||||
|
$option.attr('value', itemValue);
|
||||||
|
self.$element.append($option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dontPushVal)
|
||||||
|
self.pushVal();
|
||||||
|
|
||||||
|
// Add class when reached maxTags
|
||||||
|
if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
|
||||||
|
self.$container.addClass('bootstrap-tagsinput-max');
|
||||||
|
|
||||||
|
self.$element.trigger($.Event('itemAdded', { item: item, options: options }));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given item. Pass true to dontPushVal to prevent updating the
|
||||||
|
* elements val()
|
||||||
|
*/
|
||||||
|
remove: function(item, dontPushVal, options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (self.objectItems) {
|
||||||
|
if (typeof item === "object")
|
||||||
|
item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } );
|
||||||
|
else
|
||||||
|
item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } );
|
||||||
|
|
||||||
|
item = item[item.length-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false, options: options });
|
||||||
|
self.$element.trigger(beforeItemRemoveEvent);
|
||||||
|
if (beforeItemRemoveEvent.cancel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
|
||||||
|
$('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
|
||||||
|
if($.inArray(item, self.itemsArray) !== -1)
|
||||||
|
self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dontPushVal)
|
||||||
|
self.pushVal();
|
||||||
|
|
||||||
|
// Remove class when reached maxTags
|
||||||
|
if (self.options.maxTags > self.itemsArray.length)
|
||||||
|
self.$container.removeClass('bootstrap-tagsinput-max');
|
||||||
|
|
||||||
|
self.$element.trigger($.Event('itemRemoved', { item: item, options: options }));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all items
|
||||||
|
*/
|
||||||
|
removeAll: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$('.tag', self.$container).remove();
|
||||||
|
$('option', self.$element).remove();
|
||||||
|
|
||||||
|
while(self.itemsArray.length > 0)
|
||||||
|
self.itemsArray.pop();
|
||||||
|
|
||||||
|
self.pushVal();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the tags so they match the text/value of their corresponding
|
||||||
|
* item.
|
||||||
|
*/
|
||||||
|
refresh: function() {
|
||||||
|
var self = this;
|
||||||
|
$('.tag', self.$container).each(function() {
|
||||||
|
var $tag = $(this),
|
||||||
|
item = $tag.data('item'),
|
||||||
|
itemValue = self.options.itemValue(item),
|
||||||
|
itemText = self.options.itemText(item),
|
||||||
|
tagClass = self.options.tagClass(item);
|
||||||
|
|
||||||
|
// Update tag's class and inner text
|
||||||
|
$tag.attr('class', null);
|
||||||
|
$tag.addClass('tag ' + htmlEncode(tagClass));
|
||||||
|
$tag.contents().filter(function() {
|
||||||
|
return this.nodeType == 3;
|
||||||
|
})[0].nodeValue = htmlEncode(itemText);
|
||||||
|
|
||||||
|
if (self.isSelect) {
|
||||||
|
var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
|
||||||
|
option.attr('value', itemValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the items added as tags
|
||||||
|
*/
|
||||||
|
items: function() {
|
||||||
|
return this.itemsArray;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assembly value by retrieving the value of each item, and set it on the
|
||||||
|
* element.
|
||||||
|
*/
|
||||||
|
pushVal: function() {
|
||||||
|
var self = this,
|
||||||
|
val = $.map(self.items(), function(item) {
|
||||||
|
return self.options.itemValue(item).toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.$element.val(val, true).trigger('change');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the tags input behaviour on the element
|
||||||
|
*/
|
||||||
|
build: function(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.options = $.extend({}, defaultOptions, options);
|
||||||
|
// When itemValue is set, freeInput should always be false
|
||||||
|
if (self.objectItems)
|
||||||
|
self.options.freeInput = false;
|
||||||
|
|
||||||
|
makeOptionItemFunction(self.options, 'itemValue');
|
||||||
|
makeOptionItemFunction(self.options, 'itemText');
|
||||||
|
makeOptionFunction(self.options, 'tagClass');
|
||||||
|
|
||||||
|
// Typeahead Bootstrap version 2.3.2
|
||||||
|
if (self.options.typeahead) {
|
||||||
|
var typeahead = self.options.typeahead || {};
|
||||||
|
|
||||||
|
makeOptionFunction(typeahead, 'source');
|
||||||
|
|
||||||
|
self.$input.typeahead($.extend({}, typeahead, {
|
||||||
|
source: function (query, process) {
|
||||||
|
function processItems(items) {
|
||||||
|
var texts = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
var text = self.options.itemText(items[i]);
|
||||||
|
map[text] = items[i];
|
||||||
|
texts.push(text);
|
||||||
|
}
|
||||||
|
process(texts);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.map = {};
|
||||||
|
var map = this.map,
|
||||||
|
data = typeahead.source(query);
|
||||||
|
|
||||||
|
if ($.isFunction(data.success)) {
|
||||||
|
// support for Angular callbacks
|
||||||
|
data.success(processItems);
|
||||||
|
} else if ($.isFunction(data.then)) {
|
||||||
|
// support for Angular promises
|
||||||
|
data.then(processItems);
|
||||||
|
} else {
|
||||||
|
// support for functions and jquery promises
|
||||||
|
$.when(data)
|
||||||
|
.then(processItems);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updater: function (text) {
|
||||||
|
self.add(this.map[text]);
|
||||||
|
return this.map[text];
|
||||||
|
},
|
||||||
|
matcher: function (text) {
|
||||||
|
return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
|
||||||
|
},
|
||||||
|
sorter: function (texts) {
|
||||||
|
return texts.sort();
|
||||||
|
},
|
||||||
|
highlighter: function (text) {
|
||||||
|
var regex = new RegExp( '(' + this.query + ')', 'gi' );
|
||||||
|
return text.replace( regex, "<strong>$1</strong>" );
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeahead.js
|
||||||
|
if (self.options.typeaheadjs) {
|
||||||
|
var typeaheadConfig = null;
|
||||||
|
var typeaheadDatasets = {};
|
||||||
|
|
||||||
|
// Determine if main configurations were passed or simply a dataset
|
||||||
|
var typeaheadjs = self.options.typeaheadjs;
|
||||||
|
if ($.isArray(typeaheadjs)) {
|
||||||
|
typeaheadConfig = typeaheadjs[0];
|
||||||
|
typeaheadDatasets = typeaheadjs[1];
|
||||||
|
} else {
|
||||||
|
typeaheadDatasets = typeaheadjs;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.$input.typeahead(typeaheadConfig, typeaheadDatasets).on('typeahead:selected', $.proxy(function (obj, datum) {
|
||||||
|
if (typeaheadDatasets.valueKey)
|
||||||
|
self.add(datum[typeaheadDatasets.valueKey]);
|
||||||
|
else
|
||||||
|
self.add(datum);
|
||||||
|
self.$input.typeahead('val', '');
|
||||||
|
}, self));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.$container.on('click', $.proxy(function(event) {
|
||||||
|
if (! self.$element.attr('disabled')) {
|
||||||
|
self.$input.removeAttr('disabled');
|
||||||
|
}
|
||||||
|
self.$input.focus();
|
||||||
|
}, self));
|
||||||
|
|
||||||
|
if (self.options.addOnBlur && self.options.freeInput) {
|
||||||
|
self.$input.on('focusout', $.proxy(function(event) {
|
||||||
|
// HACK: only process on focusout when no typeahead opened, to
|
||||||
|
// avoid adding the typeahead text as tag
|
||||||
|
if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
|
||||||
|
self.add(self.$input.val());
|
||||||
|
self.$input.val('');
|
||||||
|
}
|
||||||
|
}, self));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.$container.on('keydown', 'input', $.proxy(function(event) {
|
||||||
|
var $input = $(event.target),
|
||||||
|
$inputWrapper = self.findInputWrapper();
|
||||||
|
|
||||||
|
if (self.$element.attr('disabled')) {
|
||||||
|
self.$input.attr('disabled', 'disabled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.which) {
|
||||||
|
// BACKSPACE
|
||||||
|
case 8:
|
||||||
|
if (doGetCaretPosition($input[0]) === 0) {
|
||||||
|
var prev = $inputWrapper.prev();
|
||||||
|
if (prev.length) {
|
||||||
|
self.remove(prev.data('item'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
case 46:
|
||||||
|
if (doGetCaretPosition($input[0]) === 0) {
|
||||||
|
var next = $inputWrapper.next();
|
||||||
|
if (next.length) {
|
||||||
|
self.remove(next.data('item'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LEFT ARROW
|
||||||
|
case 37:
|
||||||
|
// Try to move the input before the previous tag
|
||||||
|
var $prevTag = $inputWrapper.prev();
|
||||||
|
if ($input.val().length === 0 && $prevTag[0]) {
|
||||||
|
$prevTag.before($inputWrapper);
|
||||||
|
$input.focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// RIGHT ARROW
|
||||||
|
case 39:
|
||||||
|
// Try to move the input after the next tag
|
||||||
|
var $nextTag = $inputWrapper.next();
|
||||||
|
if ($input.val().length === 0 && $nextTag[0]) {
|
||||||
|
$nextTag.after($inputWrapper);
|
||||||
|
$input.focus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset internal input's size
|
||||||
|
var textLength = $input.val().length,
|
||||||
|
wordSpace = Math.ceil(textLength / 5),
|
||||||
|
size = textLength + wordSpace + 1;
|
||||||
|
$input.attr('size', Math.max(this.inputSize, $input.val().length));
|
||||||
|
}, self));
|
||||||
|
|
||||||
|
self.$container.on('keypress', 'input', $.proxy(function(event) {
|
||||||
|
var $input = $(event.target);
|
||||||
|
|
||||||
|
if (self.$element.attr('disabled')) {
|
||||||
|
self.$input.attr('disabled', 'disabled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = $input.val(),
|
||||||
|
maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
|
||||||
|
if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
|
||||||
|
// Only attempt to add a tag if there is data in the field
|
||||||
|
if (text.length !== 0) {
|
||||||
|
self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
|
||||||
|
$input.val('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the field is empty, let the event triggered fire as usual
|
||||||
|
if (self.options.cancelConfirmKeysOnEmpty === false) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset internal input's size
|
||||||
|
var textLength = $input.val().length,
|
||||||
|
wordSpace = Math.ceil(textLength / 5),
|
||||||
|
size = textLength + wordSpace + 1;
|
||||||
|
$input.attr('size', Math.max(this.inputSize, $input.val().length));
|
||||||
|
}, self));
|
||||||
|
|
||||||
|
// Remove icon clicked
|
||||||
|
self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
|
||||||
|
if (self.$element.attr('disabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.remove($(event.target).closest('.tag').data('item'));
|
||||||
|
}, self));
|
||||||
|
|
||||||
|
// Only add existing value as tags when using strings as tags
|
||||||
|
if (self.options.itemValue === defaultOptions.itemValue) {
|
||||||
|
if (self.$element[0].tagName === 'INPUT') {
|
||||||
|
self.add(self.$element.val());
|
||||||
|
} else {
|
||||||
|
$('option', self.$element).each(function() {
|
||||||
|
self.add($(this).attr('value'), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all tagsinput behaviour and unregsiter all event handlers
|
||||||
|
*/
|
||||||
|
destroy: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// Unbind events
|
||||||
|
self.$container.off('keypress', 'input');
|
||||||
|
self.$container.off('click', '[role=remove]');
|
||||||
|
|
||||||
|
self.$container.remove();
|
||||||
|
self.$element.removeData('tagsinput');
|
||||||
|
self.$element.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets focus on the tagsinput
|
||||||
|
*/
|
||||||
|
focus: function() {
|
||||||
|
this.$input.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the internal input element
|
||||||
|
*/
|
||||||
|
input: function() {
|
||||||
|
return this.$input;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the element which is wrapped around the internal input. This
|
||||||
|
* is normally the $container, but typeahead.js moves the $input element.
|
||||||
|
*/
|
||||||
|
findInputWrapper: function() {
|
||||||
|
var elt = this.$input[0],
|
||||||
|
container = this.$container[0];
|
||||||
|
while(elt && elt.parentNode !== container)
|
||||||
|
elt = elt.parentNode;
|
||||||
|
|
||||||
|
return $(elt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register JQuery plugin
|
||||||
|
*/
|
||||||
|
$.fn.tagsinput = function(arg1, arg2, arg3) {
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
this.each(function() {
|
||||||
|
var tagsinput = $(this).data('tagsinput');
|
||||||
|
// Initialize a new tags input
|
||||||
|
if (!tagsinput) {
|
||||||
|
tagsinput = new TagsInput(this, arg1);
|
||||||
|
$(this).data('tagsinput', tagsinput);
|
||||||
|
results.push(tagsinput);
|
||||||
|
|
||||||
|
if (this.tagName === 'SELECT') {
|
||||||
|
$('option', $(this)).attr('selected', 'selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init tags from $(this).val()
|
||||||
|
$(this).val($(this).val());
|
||||||
|
} else if (!arg1 && !arg2) {
|
||||||
|
// tagsinput already exists
|
||||||
|
// no function, trying to init
|
||||||
|
results.push(tagsinput);
|
||||||
|
} else if(tagsinput[arg1] !== undefined) {
|
||||||
|
// Invoke function on existing tags input
|
||||||
|
if(tagsinput[arg1].length === 3 && arg3 !== undefined){
|
||||||
|
var retVal = tagsinput[arg1](arg2, null, arg3);
|
||||||
|
}else{
|
||||||
|
var retVal = tagsinput[arg1](arg2);
|
||||||
|
}
|
||||||
|
if (retVal !== undefined)
|
||||||
|
results.push(retVal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( typeof arg1 == 'string') {
|
||||||
|
// Return the results from the invoked function calls
|
||||||
|
return results.length > 1 ? results : results[0];
|
||||||
|
} else {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.tagsinput.Constructor = TagsInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Most options support both a string or number as well as a function as
|
||||||
|
* option value. This function makes sure that the option with the given
|
||||||
|
* key in the given options is wrapped in a function
|
||||||
|
*/
|
||||||
|
function makeOptionItemFunction(options, key) {
|
||||||
|
if (typeof options[key] !== 'function') {
|
||||||
|
var propertyName = options[key];
|
||||||
|
options[key] = function(item) { return item[propertyName]; };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function makeOptionFunction(options, key) {
|
||||||
|
if (typeof options[key] !== 'function') {
|
||||||
|
var value = options[key];
|
||||||
|
options[key] = function() { return value; };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* HtmlEncodes the given value
|
||||||
|
*/
|
||||||
|
var htmlEncodeContainer = $('<div />');
|
||||||
|
function htmlEncode(value) {
|
||||||
|
if (value) {
|
||||||
|
return htmlEncodeContainer.text(value).html();
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position of the caret in the given input field
|
||||||
|
* http://flightschool.acylt.com/devnotes/caret-position-woes/
|
||||||
|
*/
|
||||||
|
function doGetCaretPosition(oField) {
|
||||||
|
var iCaretPos = 0;
|
||||||
|
if (document.selection) {
|
||||||
|
oField.focus ();
|
||||||
|
var oSel = document.selection.createRange();
|
||||||
|
oSel.moveStart ('character', -oField.value.length);
|
||||||
|
iCaretPos = oSel.text.length;
|
||||||
|
} else if (oField.selectionStart || oField.selectionStart == '0') {
|
||||||
|
iCaretPos = oField.selectionStart;
|
||||||
|
}
|
||||||
|
return (iCaretPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns boolean indicates whether user has pressed an expected key combination.
|
||||||
|
* @param object keyPressEvent: JavaScript event object, refer
|
||||||
|
* http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
|
||||||
|
* @param object lookupList: expected key combinations, as in:
|
||||||
|
* [13, {which: 188, shiftKey: true}]
|
||||||
|
*/
|
||||||
|
function keyCombinationInList(keyPressEvent, lookupList) {
|
||||||
|
var found = false;
|
||||||
|
$.each(lookupList, function (index, keyCombination) {
|
||||||
|
if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
|
||||||
|
found = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyPressEvent.which === keyCombination.which) {
|
||||||
|
var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
|
||||||
|
shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
|
||||||
|
ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
|
||||||
|
if (alt && shift && ctrl) {
|
||||||
|
found = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize tagsinput behaviour on inputs and selects which have
|
||||||
|
* data-role=tagsinput
|
||||||
|
*/
|
||||||
|
$(function() {
|
||||||
|
$("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
|
||||||
|
});
|
||||||
|
})(window.jQuery);
|
|
@ -0,0 +1,50 @@
|
||||||
|
.bootstrap-tagsinput {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 6px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #555;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-width: 100%;
|
||||||
|
line-height: 22px;
|
||||||
|
cursor: text;
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: auto !important;
|
||||||
|
max-width: inherit;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
margin-right: 2px;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
[data-role="remove"] {
|
||||||
|
margin-left:8px;
|
||||||
|
cursor:pointer;
|
||||||
|
&:after{
|
||||||
|
content: "x";
|
||||||
|
padding:0px 2px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
&:active {
|
||||||
|
box-shadow: inset 0 3px 5px rgba(0,0,0,0.125);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -48,12 +48,15 @@ textarea{
|
||||||
-o-border-radius: 6px;
|
-o-border-radius: 6px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
.bootstrap-tagsinput{
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
.manual-header{
|
.manual-header{
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
box-shadow: rgba(0,0,0,.1)0 1px 5px;
|
box-shadow: rgba(0,0,0,.1)0 1px 5px;
|
||||||
|
border-top: 3px solid #009a61
|
||||||
}
|
}
|
||||||
.manual-header .navbar-brand,.manual-header a {
|
.manual-header .navbar-brand,.manual-header a {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -106,9 +109,48 @@ textarea{
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: #9ba3af;
|
color: #9ba3af;
|
||||||
}
|
}
|
||||||
|
/**************首页标签*******************/
|
||||||
|
.tag-container-outer{
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
line-height: 30px;
|
||||||
|
position: relative;
|
||||||
|
display: block !important;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.tag-container-outer .title{
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-container-outer .tags a{
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: baseline;
|
||||||
|
line-height: 1;
|
||||||
|
border-radius: 0.3rem;
|
||||||
|
transition: background .1s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
margin: 0 .5em .5em 0!important;
|
||||||
|
padding: 0.5rem 0.8rem;
|
||||||
|
color: #999999;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.tag-container-outer .tags a:hover{
|
||||||
|
color: #666666;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.tag-container-outer .tags a>.detail{
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-left: 1em;
|
||||||
|
opacity: .8
|
||||||
|
}
|
||||||
/*********************************/
|
/*********************************/
|
||||||
.manual-body{
|
.manual-body{
|
||||||
margin-top: 50px;
|
margin-top: 53px;
|
||||||
}
|
}
|
||||||
.manual-body .page-left{
|
.manual-body .page-left{
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# goquery - a little like that j-thing, only in Go
|
# goquery - a little like that j-thing, only in Go
|
||||||
[![build status](https://secure.travis-ci.org/PuerkitoBio/goquery.png)](http://travis-ci.org/PuerkitoBio/goquery) [![GoDoc](https://godoc.org/github.com/PuerkitoBio/goquery?status.png)](http://godoc.org/github.com/PuerkitoBio/goquery) [![Sourcegraph Badge](https://sourcegraph.com/github.com/PuerkitoBio/goquery/-/badge.svg)](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge)
|
[![build status](https://secure.travis-ci.org/PuerkitoBio/goquery.png)](http://travis-ci.org/PuerkitoBio/goquery) [![GoDoc](https://godoc.org/github.com/PuerkitoBio/goquery?status.png)](http://godoc.org/github.com/PuerkitoBio/goquery) [![Sourcegraph Badge](https://sourcegraph.com/github.com/PuerkitoBio/goquery/-/badge.svg)](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge)
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,15 @@ Also, because the net/html parser requires UTF-8 encoding, so does goquery: it i
|
||||||
|
|
||||||
Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...).
|
Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...).
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
* [Installation](#installation)
|
||||||
|
* [Changelog](#changelog)
|
||||||
|
* [API](#api)
|
||||||
|
* [Examples](#examples)
|
||||||
|
* [Related Projects](#related-projects)
|
||||||
|
* [License](#license)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Please note that because of the net/html dependency, goquery requires Go1.1+.
|
Please note that because of the net/html dependency, goquery requires Go1.1+.
|
||||||
|
@ -107,18 +116,20 @@ func main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags.
|
||||||
|
- [andybalholm/cascadia][cascadia], the CSS selector library used by goquery.
|
||||||
|
- [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic].
|
The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic].
|
||||||
|
|
||||||
## Downstream Projects
|
|
||||||
|
|
||||||
- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct
|
|
||||||
tags.
|
|
||||||
|
|
||||||
[jquery]: http://jquery.com/
|
[jquery]: http://jquery.com/
|
||||||
[go]: http://golang.org/
|
[go]: http://golang.org/
|
||||||
[cascadia]: https://github.com/andybalholm/cascadia
|
[cascadia]: https://github.com/andybalholm/cascadia
|
||||||
|
[cascadiacli]: https://github.com/suntong/cascadia
|
||||||
[bsd]: http://opensource.org/licenses/BSD-3-Clause
|
[bsd]: http://opensource.org/licenses/BSD-3-Clause
|
||||||
[golic]: http://golang.org/LICENSE
|
[golic]: http://golang.org/LICENSE
|
||||||
[caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE
|
[caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
*.out
|
||||||
|
*.swp
|
||||||
|
*.8
|
||||||
|
*.6
|
||||||
|
_obj
|
||||||
|
_test*
|
||||||
|
markdown
|
||||||
|
tags
|
|
@ -0,0 +1,30 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.5.4
|
||||||
|
- 1.6.2
|
||||||
|
- tip
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: 1.2.2
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- go test -v -race ./...
|
||||||
|
- go: 1.3.3
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- go test -v -race ./...
|
||||||
|
- go: 1.4.3
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- go test -v -race ./...
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
fast_finish: true
|
||||||
|
install:
|
||||||
|
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||||
|
- go tool vet .
|
||||||
|
- go test -v -race ./...
|
|
@ -0,0 +1,29 @@
|
||||||
|
Blackfriday is distributed under the Simplified BSD License:
|
||||||
|
|
||||||
|
> Copyright © 2011 Russ Ross
|
||||||
|
> All rights reserved.
|
||||||
|
>
|
||||||
|
> Redistribution and use in source and binary forms, with or without
|
||||||
|
> modification, are permitted provided that the following conditions
|
||||||
|
> are met:
|
||||||
|
>
|
||||||
|
> 1. Redistributions of source code must retain the above copyright
|
||||||
|
> notice, this list of conditions and the following disclaimer.
|
||||||
|
>
|
||||||
|
> 2. Redistributions in binary form must reproduce the above
|
||||||
|
> copyright notice, this list of conditions and the following
|
||||||
|
> disclaimer in the documentation and/or other materials provided with
|
||||||
|
> the distribution.
|
||||||
|
>
|
||||||
|
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
> POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,292 @@
|
||||||
|
Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) [![GoDoc](https://godoc.org/github.com/russross/blackfriday?status.svg)](https://godoc.org/github.com/russross/blackfriday)
|
||||||
|
===========
|
||||||
|
|
||||||
|
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
|
||||||
|
is paranoid about its input (so you can safely feed it user-supplied
|
||||||
|
data), it is fast, it supports common extensions (tables, smart
|
||||||
|
punctuation substitutions, etc.), and it is safe for all utf-8
|
||||||
|
(unicode) input.
|
||||||
|
|
||||||
|
HTML output is currently supported, along with Smartypants
|
||||||
|
extensions. An experimental LaTeX output engine is also included.
|
||||||
|
|
||||||
|
It started as a translation from C of [Sundown][3].
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Blackfriday is compatible with Go 1. If you are using an older
|
||||||
|
release of Go, consider using v1.1 of blackfriday, which was based
|
||||||
|
on the last stable release of Go prior to Go 1. You can find it as a
|
||||||
|
tagged commit on github.
|
||||||
|
|
||||||
|
With Go 1 and git installed:
|
||||||
|
|
||||||
|
go get github.com/russross/blackfriday
|
||||||
|
|
||||||
|
will download, compile, and install the package into your `$GOPATH`
|
||||||
|
directory hierarchy. Alternatively, you can achieve the same if you
|
||||||
|
import it into a project:
|
||||||
|
|
||||||
|
import "github.com/russross/blackfriday"
|
||||||
|
|
||||||
|
and `go get` without parameters.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
For basic usage, it is as simple as getting your input into a byte
|
||||||
|
slice and calling:
|
||||||
|
|
||||||
|
output := blackfriday.MarkdownBasic(input)
|
||||||
|
|
||||||
|
This renders it with no extensions enabled. To get a more useful
|
||||||
|
feature set, use this instead:
|
||||||
|
|
||||||
|
output := blackfriday.MarkdownCommon(input)
|
||||||
|
|
||||||
|
### Sanitize untrusted content
|
||||||
|
|
||||||
|
Blackfriday itself does nothing to protect against malicious content. If you are
|
||||||
|
dealing with user-supplied markdown, we recommend running blackfriday's output
|
||||||
|
through HTML sanitizer such as
|
||||||
|
[Bluemonday](https://github.com/microcosm-cc/bluemonday).
|
||||||
|
|
||||||
|
Here's an example of simple usage of blackfriday together with bluemonday:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
import (
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
"github.com/russross/blackfriday"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ...
|
||||||
|
unsafe := blackfriday.MarkdownCommon(input)
|
||||||
|
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom options
|
||||||
|
|
||||||
|
If you want to customize the set of options, first get a renderer
|
||||||
|
(currently either the HTML or LaTeX output engines), then use it to
|
||||||
|
call the more general `Markdown` function. For examples, see the
|
||||||
|
implementations of `MarkdownBasic` and `MarkdownCommon` in
|
||||||
|
`markdown.go`.
|
||||||
|
|
||||||
|
You can also check out `blackfriday-tool` for a more complete example
|
||||||
|
of how to use it. Download and install it using:
|
||||||
|
|
||||||
|
go get github.com/russross/blackfriday-tool
|
||||||
|
|
||||||
|
This is a simple command-line tool that allows you to process a
|
||||||
|
markdown file using a standalone program. You can also browse the
|
||||||
|
source directly on github if you are just looking for some example
|
||||||
|
code:
|
||||||
|
|
||||||
|
* <http://github.com/russross/blackfriday-tool>
|
||||||
|
|
||||||
|
Note that if you have not already done so, installing
|
||||||
|
`blackfriday-tool` will be sufficient to download and install
|
||||||
|
blackfriday in addition to the tool itself. The tool binary will be
|
||||||
|
installed in `$GOPATH/bin`. This is a statically-linked binary that
|
||||||
|
can be copied to wherever you need it without worrying about
|
||||||
|
dependencies and library versions.
|
||||||
|
|
||||||
|
### Sanitized anchor names
|
||||||
|
|
||||||
|
Blackfriday includes an algorithm for creating sanitized anchor names
|
||||||
|
corresponding to a given input text. This algorithm is used to create
|
||||||
|
anchors for headings when `EXTENSION_AUTO_HEADER_IDS` is enabled. The
|
||||||
|
algorithm has a specification, so that other packages can create
|
||||||
|
compatible anchor names and links to those anchors.
|
||||||
|
|
||||||
|
The specification is located at https://godoc.org/github.com/russross/blackfriday#hdr-Sanitized_Anchor_Names.
|
||||||
|
|
||||||
|
[`SanitizedAnchorName`](https://godoc.org/github.com/russross/blackfriday#SanitizedAnchorName) exposes this functionality, and can be used to
|
||||||
|
create compatible links to the anchor names generated by blackfriday.
|
||||||
|
This algorithm is also implemented in a small standalone package at
|
||||||
|
[`github.com/shurcooL/sanitized_anchor_name`](https://godoc.org/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
|
||||||
|
that want a small package and don't need full functionality of blackfriday.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
All features of Sundown are supported, including:
|
||||||
|
|
||||||
|
* **Compatibility**. The Markdown v1.0.3 test suite passes with
|
||||||
|
the `--tidy` option. Without `--tidy`, the differences are
|
||||||
|
mostly in whitespace and entity escaping, where blackfriday is
|
||||||
|
more consistent and cleaner.
|
||||||
|
|
||||||
|
* **Common extensions**, including table support, fenced code
|
||||||
|
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
|
||||||
|
|
||||||
|
* **Safety**. Blackfriday is paranoid when parsing, making it safe
|
||||||
|
to feed untrusted user input without fear of bad things
|
||||||
|
happening. The test suite stress tests this and there are no
|
||||||
|
known inputs that make it crash. If you find one, please let me
|
||||||
|
know and send me the input that does it.
|
||||||
|
|
||||||
|
NOTE: "safety" in this context means *runtime safety only*. In order to
|
||||||
|
protect yourself against JavaScript injection in untrusted content, see
|
||||||
|
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
|
||||||
|
|
||||||
|
* **Fast processing**. It is fast enough to render on-demand in
|
||||||
|
most web applications without having to cache the output.
|
||||||
|
|
||||||
|
* **Thread safety**. You can run multiple parsers in different
|
||||||
|
goroutines without ill effect. There is no dependence on global
|
||||||
|
shared state.
|
||||||
|
|
||||||
|
* **Minimal dependencies**. Blackfriday only depends on standard
|
||||||
|
library packages in Go. The source code is pretty
|
||||||
|
self-contained, so it is easy to add to any project, including
|
||||||
|
Google App Engine projects.
|
||||||
|
|
||||||
|
* **Standards compliant**. Output successfully validates using the
|
||||||
|
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
|
||||||
|
|
||||||
|
|
||||||
|
Extensions
|
||||||
|
----------
|
||||||
|
|
||||||
|
In addition to the standard markdown syntax, this package
|
||||||
|
implements the following extensions:
|
||||||
|
|
||||||
|
* **Intra-word emphasis supression**. The `_` character is
|
||||||
|
commonly used inside words when discussing code, so having
|
||||||
|
markdown interpret it as an emphasis command is usually the
|
||||||
|
wrong thing. Blackfriday lets you treat all emphasis markers as
|
||||||
|
normal characters when they occur inside a word.
|
||||||
|
|
||||||
|
* **Tables**. Tables can be created by drawing them in the input
|
||||||
|
using a simple syntax:
|
||||||
|
|
||||||
|
```
|
||||||
|
Name | Age
|
||||||
|
--------|------
|
||||||
|
Bob | 27
|
||||||
|
Alice | 23
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Fenced code blocks**. In addition to the normal 4-space
|
||||||
|
indentation to mark code blocks, you can explicitly mark them
|
||||||
|
and supply a language (to make syntax highlighting simple). Just
|
||||||
|
mark it like this:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
func getTrue() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use 3 or more backticks to mark the beginning of the
|
||||||
|
block, and the same number to mark the end of the block.
|
||||||
|
|
||||||
|
To preserve classes of fenced code blocks while using the bluemonday
|
||||||
|
HTML sanitizer, use the following policy:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
p := bluemonday.UGCPolicy()
|
||||||
|
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
|
||||||
|
html := p.SanitizeBytes(unsafe)
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Definition lists**. A simple definition list is made of a single-line
|
||||||
|
term followed by a colon and the definition for that term.
|
||||||
|
|
||||||
|
Cat
|
||||||
|
: Fluffy animal everyone likes
|
||||||
|
|
||||||
|
Internet
|
||||||
|
: Vector of transmission for pictures of cats
|
||||||
|
|
||||||
|
Terms must be separated from the previous definition by a blank line.
|
||||||
|
|
||||||
|
* **Footnotes**. A marker in the text that will become a superscript number;
|
||||||
|
a footnote definition that will be placed in a list of footnotes at the
|
||||||
|
end of the document. A footnote looks like this:
|
||||||
|
|
||||||
|
This is a footnote.[^1]
|
||||||
|
|
||||||
|
[^1]: the footnote text.
|
||||||
|
|
||||||
|
* **Autolinking**. Blackfriday can find URLs that have not been
|
||||||
|
explicitly marked as links and turn them into links.
|
||||||
|
|
||||||
|
* **Strikethrough**. Use two tildes (`~~`) to mark text that
|
||||||
|
should be crossed out.
|
||||||
|
|
||||||
|
* **Hard line breaks**. With this extension enabled (it is off by
|
||||||
|
default in the `MarkdownBasic` and `MarkdownCommon` convenience
|
||||||
|
functions), newlines in the input translate into line breaks in
|
||||||
|
the output.
|
||||||
|
|
||||||
|
* **Smart quotes**. Smartypants-style punctuation substitution is
|
||||||
|
supported, turning normal double- and single-quote marks into
|
||||||
|
curly quotes, etc.
|
||||||
|
|
||||||
|
* **LaTeX-style dash parsing** is an additional option, where `--`
|
||||||
|
is translated into `–`, and `---` is translated into
|
||||||
|
`—`. This differs from most smartypants processors, which
|
||||||
|
turn a single hyphen into an ndash and a double hyphen into an
|
||||||
|
mdash.
|
||||||
|
|
||||||
|
* **Smart fractions**, where anything that looks like a fraction
|
||||||
|
is translated into suitable HTML (instead of just a few special
|
||||||
|
cases like most smartypant processors). For example, `4/5`
|
||||||
|
becomes `<sup>4</sup>⁄<sub>5</sub>`, which renders as
|
||||||
|
<sup>4</sup>⁄<sub>5</sub>.
|
||||||
|
|
||||||
|
|
||||||
|
Other renderers
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Blackfriday is structured to allow alternative rendering engines. Here
|
||||||
|
are a few of note:
|
||||||
|
|
||||||
|
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
||||||
|
provides a GitHub Flavored Markdown renderer with fenced code block
|
||||||
|
highlighting, clickable header anchor links.
|
||||||
|
|
||||||
|
It's not customizable, and its goal is to produce HTML output
|
||||||
|
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
|
||||||
|
except the rendering is performed locally.
|
||||||
|
|
||||||
|
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
||||||
|
but for markdown.
|
||||||
|
|
||||||
|
* LaTeX output: renders output as LaTeX. This is currently part of the
|
||||||
|
main Blackfriday repository, but may be split into its own project
|
||||||
|
in the future. If you are interested in owning and maintaining the
|
||||||
|
LaTeX output component, please be in touch.
|
||||||
|
|
||||||
|
It renders some basic documents, but is only experimental at this
|
||||||
|
point. In particular, it does not do any inline escaping, so input
|
||||||
|
that happens to look like LaTeX code will be passed through without
|
||||||
|
modification.
|
||||||
|
|
||||||
|
* [Md2Vim](https://github.com/FooSoft/md2vim): transforms markdown files into vimdoc format.
|
||||||
|
|
||||||
|
|
||||||
|
Todo
|
||||||
|
----
|
||||||
|
|
||||||
|
* More unit testing
|
||||||
|
* Improve unicode support. It does not understand all unicode
|
||||||
|
rules (about what constitutes a letter, a punctuation symbol,
|
||||||
|
etc.), so it may fail to detect word boundaries correctly in
|
||||||
|
some instances. It is safe on all utf-8 input.
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
|
||||||
|
|
||||||
|
|
||||||
|
[1]: http://daringfireball.net/projects/markdown/ "Markdown"
|
||||||
|
[2]: http://golang.org/ "Go Language"
|
||||||
|
[3]: https://github.com/vmg/sundown "Sundown"
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
|
@ -0,0 +1,949 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// HTML rendering backend
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Html renderer configuration options.
|
||||||
|
const (
|
||||||
|
HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks
|
||||||
|
HTML_SKIP_STYLE // skip embedded <style> elements
|
||||||
|
HTML_SKIP_IMAGES // skip embedded images
|
||||||
|
HTML_SKIP_LINKS // skip all links
|
||||||
|
HTML_SAFELINK // only link to trusted protocols
|
||||||
|
HTML_NOFOLLOW_LINKS // only link with rel="nofollow"
|
||||||
|
HTML_NOREFERRER_LINKS // only link with rel="noreferrer"
|
||||||
|
HTML_HREF_TARGET_BLANK // add a blank target
|
||||||
|
HTML_TOC // generate a table of contents
|
||||||
|
HTML_OMIT_CONTENTS // skip the main contents (for a standalone table of contents)
|
||||||
|
HTML_COMPLETE_PAGE // generate a complete HTML page
|
||||||
|
HTML_USE_XHTML // generate XHTML output instead of HTML
|
||||||
|
HTML_USE_SMARTYPANTS // enable smart punctuation substitutions
|
||||||
|
HTML_SMARTYPANTS_FRACTIONS // enable smart fractions (with HTML_USE_SMARTYPANTS)
|
||||||
|
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
|
||||||
|
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
|
||||||
|
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
|
||||||
|
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alignments = []string{
|
||||||
|
"left",
|
||||||
|
"right",
|
||||||
|
"center",
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: improve this regexp to catch all possible entities:
|
||||||
|
htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type HtmlRendererParameters struct {
|
||||||
|
// Prepend this text to each relative URL.
|
||||||
|
AbsolutePrefix string
|
||||||
|
// Add this text to each footnote anchor, to ensure uniqueness.
|
||||||
|
FootnoteAnchorPrefix string
|
||||||
|
// Show this text inside the <a> tag for a footnote return link, if the
|
||||||
|
// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
|
||||||
|
// <sup>[return]</sup> is used.
|
||||||
|
FootnoteReturnLinkContents string
|
||||||
|
// If set, add this text to the front of each Header ID, to ensure
|
||||||
|
// uniqueness.
|
||||||
|
HeaderIDPrefix string
|
||||||
|
// If set, add this text to the back of each Header ID, to ensure uniqueness.
|
||||||
|
HeaderIDSuffix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Html is a type that implements the Renderer interface for HTML output.
|
||||||
|
//
|
||||||
|
// Do not create this directly, instead use the HtmlRenderer function.
|
||||||
|
type Html struct {
|
||||||
|
flags int // HTML_* options
|
||||||
|
closeTag string // how to end singleton tags: either " />" or ">"
|
||||||
|
title string // document title
|
||||||
|
css string // optional css file url (used with HTML_COMPLETE_PAGE)
|
||||||
|
|
||||||
|
parameters HtmlRendererParameters
|
||||||
|
|
||||||
|
// table of contents data
|
||||||
|
tocMarker int
|
||||||
|
headerCount int
|
||||||
|
currentLevel int
|
||||||
|
toc *bytes.Buffer
|
||||||
|
|
||||||
|
// Track header IDs to prevent ID collision in a single generation.
|
||||||
|
headerIDs map[string]int
|
||||||
|
|
||||||
|
smartypants *smartypantsRenderer
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
xhtmlClose = " />"
|
||||||
|
htmlClose = ">"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HtmlRenderer creates and configures an Html object, which
|
||||||
|
// satisfies the Renderer interface.
|
||||||
|
//
|
||||||
|
// flags is a set of HTML_* options ORed together.
|
||||||
|
// title is the title of the document, and css is a URL for the document's
|
||||||
|
// stylesheet.
|
||||||
|
// title and css are only used when HTML_COMPLETE_PAGE is selected.
|
||||||
|
func HtmlRenderer(flags int, title string, css string) Renderer {
|
||||||
|
return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func HtmlRendererWithParameters(flags int, title string,
|
||||||
|
css string, renderParameters HtmlRendererParameters) Renderer {
|
||||||
|
// configure the rendering engine
|
||||||
|
closeTag := htmlClose
|
||||||
|
if flags&HTML_USE_XHTML != 0 {
|
||||||
|
closeTag = xhtmlClose
|
||||||
|
}
|
||||||
|
|
||||||
|
if renderParameters.FootnoteReturnLinkContents == "" {
|
||||||
|
renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Html{
|
||||||
|
flags: flags,
|
||||||
|
closeTag: closeTag,
|
||||||
|
title: title,
|
||||||
|
css: css,
|
||||||
|
parameters: renderParameters,
|
||||||
|
|
||||||
|
headerCount: 0,
|
||||||
|
currentLevel: 0,
|
||||||
|
toc: new(bytes.Buffer),
|
||||||
|
|
||||||
|
headerIDs: make(map[string]int),
|
||||||
|
|
||||||
|
smartypants: smartypants(flags),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using if statements is a bit faster than a switch statement. As the compiler
|
||||||
|
// improves, this should be unnecessary this is only worthwhile because
|
||||||
|
// attrEscape is the single largest CPU user in normal use.
|
||||||
|
// Also tried using map, but that gave a ~3x slowdown.
|
||||||
|
func escapeSingleChar(char byte) (string, bool) {
|
||||||
|
if char == '"' {
|
||||||
|
return """, true
|
||||||
|
}
|
||||||
|
if char == '&' {
|
||||||
|
return "&", true
|
||||||
|
}
|
||||||
|
if char == '<' {
|
||||||
|
return "<", true
|
||||||
|
}
|
||||||
|
if char == '>' {
|
||||||
|
return ">", true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func attrEscape(out *bytes.Buffer, src []byte) {
|
||||||
|
org := 0
|
||||||
|
for i, ch := range src {
|
||||||
|
if entity, ok := escapeSingleChar(ch); ok {
|
||||||
|
if i > org {
|
||||||
|
// copy all the normal characters since the last escape
|
||||||
|
out.Write(src[org:i])
|
||||||
|
}
|
||||||
|
org = i + 1
|
||||||
|
out.WriteString(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if org < len(src) {
|
||||||
|
out.Write(src[org:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) {
|
||||||
|
end := 0
|
||||||
|
for _, rang := range skipRanges {
|
||||||
|
attrEscape(out, src[end:rang[0]])
|
||||||
|
out.Write(src[rang[0]:rang[1]])
|
||||||
|
end = rang[1]
|
||||||
|
}
|
||||||
|
attrEscape(out, src[end:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) GetFlags() int {
|
||||||
|
return options.flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) {
|
||||||
|
text = bytes.TrimPrefix(text, []byte("% "))
|
||||||
|
text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
|
||||||
|
out.WriteString("<h1 class=\"title\">")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("\n</h1>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||||
|
marker := out.Len()
|
||||||
|
doubleSpace(out)
|
||||||
|
|
||||||
|
if id == "" && options.flags&HTML_TOC != 0 {
|
||||||
|
id = fmt.Sprintf("toc_%d", options.headerCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id != "" {
|
||||||
|
id = options.ensureUniqueHeaderID(id)
|
||||||
|
|
||||||
|
if options.parameters.HeaderIDPrefix != "" {
|
||||||
|
id = options.parameters.HeaderIDPrefix + id
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.parameters.HeaderIDSuffix != "" {
|
||||||
|
id = id + options.parameters.HeaderIDSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
|
||||||
|
} else {
|
||||||
|
out.WriteString(fmt.Sprintf("<h%d>", level))
|
||||||
|
}
|
||||||
|
|
||||||
|
tocMarker := out.Len()
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// are we building a table of contents?
|
||||||
|
if options.flags&HTML_TOC != 0 {
|
||||||
|
options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString(fmt.Sprintf("</h%d>\n", level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) {
|
||||||
|
if options.flags&HTML_SKIP_HTML != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
doubleSpace(out)
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) HRule(out *bytes.Buffer) {
|
||||||
|
doubleSpace(out)
|
||||||
|
out.WriteString("<hr")
|
||||||
|
out.WriteString(options.closeTag)
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||||
|
doubleSpace(out)
|
||||||
|
|
||||||
|
// parse out the language names/classes
|
||||||
|
count := 0
|
||||||
|
for _, elt := range strings.Fields(lang) {
|
||||||
|
if elt[0] == '.' {
|
||||||
|
elt = elt[1:]
|
||||||
|
}
|
||||||
|
if len(elt) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
out.WriteString("<pre><code class=\"language-")
|
||||||
|
} else {
|
||||||
|
out.WriteByte(' ')
|
||||||
|
}
|
||||||
|
attrEscape(out, []byte(elt))
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
out.WriteString("<pre><code>")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\">")
|
||||||
|
}
|
||||||
|
|
||||||
|
attrEscape(out, text)
|
||||||
|
out.WriteString("</code></pre>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||||
|
doubleSpace(out)
|
||||||
|
out.WriteString("<blockquote>\n")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</blockquote>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
|
||||||
|
doubleSpace(out)
|
||||||
|
out.WriteString("<table>\n<thead>\n")
|
||||||
|
out.Write(header)
|
||||||
|
out.WriteString("</thead>\n\n<tbody>\n")
|
||||||
|
out.Write(body)
|
||||||
|
out.WriteString("</tbody>\n</table>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TableRow(out *bytes.Buffer, text []byte) {
|
||||||
|
doubleSpace(out)
|
||||||
|
out.WriteString("<tr>\n")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("\n</tr>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
|
||||||
|
doubleSpace(out)
|
||||||
|
switch align {
|
||||||
|
case TABLE_ALIGNMENT_LEFT:
|
||||||
|
out.WriteString("<th align=\"left\">")
|
||||||
|
case TABLE_ALIGNMENT_RIGHT:
|
||||||
|
out.WriteString("<th align=\"right\">")
|
||||||
|
case TABLE_ALIGNMENT_CENTER:
|
||||||
|
out.WriteString("<th align=\"center\">")
|
||||||
|
default:
|
||||||
|
out.WriteString("<th>")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</th>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) {
|
||||||
|
doubleSpace(out)
|
||||||
|
switch align {
|
||||||
|
case TABLE_ALIGNMENT_LEFT:
|
||||||
|
out.WriteString("<td align=\"left\">")
|
||||||
|
case TABLE_ALIGNMENT_RIGHT:
|
||||||
|
out.WriteString("<td align=\"right\">")
|
||||||
|
case TABLE_ALIGNMENT_CENTER:
|
||||||
|
out.WriteString("<td align=\"center\">")
|
||||||
|
default:
|
||||||
|
out.WriteString("<td>")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</td>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) {
|
||||||
|
out.WriteString("<div class=\"footnotes\">\n")
|
||||||
|
options.HRule(out)
|
||||||
|
options.List(out, text, LIST_TYPE_ORDERED)
|
||||||
|
out.WriteString("</div>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
|
||||||
|
if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||||
|
doubleSpace(out)
|
||||||
|
}
|
||||||
|
slug := slugify(name)
|
||||||
|
out.WriteString(`<li id="`)
|
||||||
|
out.WriteString(`fn:`)
|
||||||
|
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||||
|
out.Write(slug)
|
||||||
|
out.WriteString(`">`)
|
||||||
|
out.Write(text)
|
||||||
|
if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 {
|
||||||
|
out.WriteString(` <a class="footnote-return" href="#`)
|
||||||
|
out.WriteString(`fnref:`)
|
||||||
|
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||||
|
out.Write(slug)
|
||||||
|
out.WriteString(`">`)
|
||||||
|
out.WriteString(options.parameters.FootnoteReturnLinkContents)
|
||||||
|
out.WriteString(`</a>`)
|
||||||
|
}
|
||||||
|
out.WriteString("</li>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||||
|
marker := out.Len()
|
||||||
|
doubleSpace(out)
|
||||||
|
|
||||||
|
if flags&LIST_TYPE_DEFINITION != 0 {
|
||||||
|
out.WriteString("<dl>")
|
||||||
|
} else if flags&LIST_TYPE_ORDERED != 0 {
|
||||||
|
out.WriteString("<ol>")
|
||||||
|
} else {
|
||||||
|
out.WriteString("<ul>")
|
||||||
|
}
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if flags&LIST_TYPE_DEFINITION != 0 {
|
||||||
|
out.WriteString("</dl>\n")
|
||||||
|
} else if flags&LIST_TYPE_ORDERED != 0 {
|
||||||
|
out.WriteString("</ol>\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("</ul>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||||
|
if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) ||
|
||||||
|
flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
|
||||||
|
doubleSpace(out)
|
||||||
|
}
|
||||||
|
if flags&LIST_TYPE_TERM != 0 {
|
||||||
|
out.WriteString("<dt>")
|
||||||
|
} else if flags&LIST_TYPE_DEFINITION != 0 {
|
||||||
|
out.WriteString("<dd>")
|
||||||
|
} else {
|
||||||
|
out.WriteString("<li>")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
if flags&LIST_TYPE_TERM != 0 {
|
||||||
|
out.WriteString("</dt>\n")
|
||||||
|
} else if flags&LIST_TYPE_DEFINITION != 0 {
|
||||||
|
out.WriteString("</dd>\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("</li>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||||
|
marker := out.Len()
|
||||||
|
doubleSpace(out)
|
||||||
|
|
||||||
|
out.WriteString("<p>")
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteString("</p>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
||||||
|
skipRanges := htmlEntity.FindAllIndex(link, -1)
|
||||||
|
if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL {
|
||||||
|
// mark it but don't link it if it is not a safe link: no smartypants
|
||||||
|
out.WriteString("<tt>")
|
||||||
|
entityEscapeWithSkip(out, link, skipRanges)
|
||||||
|
out.WriteString("</tt>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("<a href=\"")
|
||||||
|
if kind == LINK_TYPE_EMAIL {
|
||||||
|
out.WriteString("mailto:")
|
||||||
|
} else {
|
||||||
|
options.maybeWriteAbsolutePrefix(out, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
entityEscapeWithSkip(out, link, skipRanges)
|
||||||
|
|
||||||
|
var relAttrs []string
|
||||||
|
if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
|
||||||
|
relAttrs = append(relAttrs, "nofollow")
|
||||||
|
}
|
||||||
|
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
|
||||||
|
relAttrs = append(relAttrs, "noreferrer")
|
||||||
|
}
|
||||||
|
if len(relAttrs) > 0 {
|
||||||
|
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// blank target only add to external link
|
||||||
|
if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
|
||||||
|
out.WriteString("\" target=\"_blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("\">")
|
||||||
|
|
||||||
|
// Pretty print: if we get an email address as
|
||||||
|
// an actual URI, e.g. `mailto:foo@bar.com`, we don't
|
||||||
|
// want to print the `mailto:` prefix
|
||||||
|
switch {
|
||||||
|
case bytes.HasPrefix(link, []byte("mailto://")):
|
||||||
|
attrEscape(out, link[len("mailto://"):])
|
||||||
|
case bytes.HasPrefix(link, []byte("mailto:")):
|
||||||
|
attrEscape(out, link[len("mailto:"):])
|
||||||
|
default:
|
||||||
|
entityEscapeWithSkip(out, link, skipRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("</a>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("<code>")
|
||||||
|
attrEscape(out, text)
|
||||||
|
out.WriteString("</code>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("<strong>")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</strong>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Emphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
if len(text) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteString("<em>")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</em>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) {
|
||||||
|
if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
|
||||||
|
out.WriteString(options.parameters.AbsolutePrefix)
|
||||||
|
if link[0] != '/' {
|
||||||
|
out.WriteByte('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||||
|
if options.flags&HTML_SKIP_IMAGES != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("<img src=\"")
|
||||||
|
options.maybeWriteAbsolutePrefix(out, link)
|
||||||
|
attrEscape(out, link)
|
||||||
|
out.WriteString("\" alt=\"")
|
||||||
|
if len(alt) > 0 {
|
||||||
|
attrEscape(out, alt)
|
||||||
|
}
|
||||||
|
if len(title) > 0 {
|
||||||
|
out.WriteString("\" title=\"")
|
||||||
|
attrEscape(out, title)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte('"')
|
||||||
|
out.WriteString(options.closeTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) LineBreak(out *bytes.Buffer) {
|
||||||
|
out.WriteString("<br")
|
||||||
|
out.WriteString(options.closeTag)
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||||
|
if options.flags&HTML_SKIP_LINKS != 0 {
|
||||||
|
// write the link text out but don't link it, just mark it with typewriter font
|
||||||
|
out.WriteString("<tt>")
|
||||||
|
attrEscape(out, content)
|
||||||
|
out.WriteString("</tt>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) {
|
||||||
|
// write the link text out but don't link it, just mark it with typewriter font
|
||||||
|
out.WriteString("<tt>")
|
||||||
|
attrEscape(out, content)
|
||||||
|
out.WriteString("</tt>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("<a href=\"")
|
||||||
|
options.maybeWriteAbsolutePrefix(out, link)
|
||||||
|
attrEscape(out, link)
|
||||||
|
if len(title) > 0 {
|
||||||
|
out.WriteString("\" title=\"")
|
||||||
|
attrEscape(out, title)
|
||||||
|
}
|
||||||
|
var relAttrs []string
|
||||||
|
if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
|
||||||
|
relAttrs = append(relAttrs, "nofollow")
|
||||||
|
}
|
||||||
|
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
|
||||||
|
relAttrs = append(relAttrs, "noreferrer")
|
||||||
|
}
|
||||||
|
if len(relAttrs) > 0 {
|
||||||
|
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// blank target only add to external link
|
||||||
|
if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
|
||||||
|
out.WriteString("\" target=\"_blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("\">")
|
||||||
|
out.Write(content)
|
||||||
|
out.WriteString("</a>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) {
|
||||||
|
if options.flags&HTML_SKIP_HTML != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("<strong><em>")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</em></strong>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("<del>")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("</del>")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
|
||||||
|
slug := slugify(ref)
|
||||||
|
out.WriteString(`<sup class="footnote-ref" id="`)
|
||||||
|
out.WriteString(`fnref:`)
|
||||||
|
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||||
|
out.Write(slug)
|
||||||
|
out.WriteString(`"><a rel="footnote" href="#`)
|
||||||
|
out.WriteString(`fn:`)
|
||||||
|
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||||
|
out.Write(slug)
|
||||||
|
out.WriteString(`">`)
|
||||||
|
out.WriteString(strconv.Itoa(id))
|
||||||
|
out.WriteString(`</a></sup>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Entity(out *bytes.Buffer, entity []byte) {
|
||||||
|
out.Write(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) NormalText(out *bytes.Buffer, text []byte) {
|
||||||
|
if options.flags&HTML_USE_SMARTYPANTS != 0 {
|
||||||
|
options.Smartypants(out, text)
|
||||||
|
} else {
|
||||||
|
attrEscape(out, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) Smartypants(out *bytes.Buffer, text []byte) {
|
||||||
|
smrt := smartypantsData{false, false}
|
||||||
|
|
||||||
|
// first do normal entity escaping
|
||||||
|
var escaped bytes.Buffer
|
||||||
|
attrEscape(&escaped, text)
|
||||||
|
text = escaped.Bytes()
|
||||||
|
|
||||||
|
mark := 0
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
if action := options.smartypants[text[i]]; action != nil {
|
||||||
|
if i > mark {
|
||||||
|
out.Write(text[mark:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
previousChar := byte(0)
|
||||||
|
if i > 0 {
|
||||||
|
previousChar = text[i-1]
|
||||||
|
}
|
||||||
|
i += action(out, &smrt, previousChar, text[i:])
|
||||||
|
mark = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mark < len(text) {
|
||||||
|
out.Write(text[mark:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) DocumentHeader(out *bytes.Buffer) {
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ending := ""
|
||||||
|
if options.flags&HTML_USE_XHTML != 0 {
|
||||||
|
out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
|
||||||
|
out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
|
||||||
|
out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
|
||||||
|
ending = " /"
|
||||||
|
} else {
|
||||||
|
out.WriteString("<!DOCTYPE html>\n")
|
||||||
|
out.WriteString("<html>\n")
|
||||||
|
}
|
||||||
|
out.WriteString("<head>\n")
|
||||||
|
out.WriteString(" <title>")
|
||||||
|
options.NormalText(out, []byte(options.title))
|
||||||
|
out.WriteString("</title>\n")
|
||||||
|
out.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
|
||||||
|
out.WriteString(VERSION)
|
||||||
|
out.WriteString("\"")
|
||||||
|
out.WriteString(ending)
|
||||||
|
out.WriteString(">\n")
|
||||||
|
out.WriteString(" <meta charset=\"utf-8\"")
|
||||||
|
out.WriteString(ending)
|
||||||
|
out.WriteString(">\n")
|
||||||
|
if options.css != "" {
|
||||||
|
out.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")
|
||||||
|
attrEscape(out, []byte(options.css))
|
||||||
|
out.WriteString("\"")
|
||||||
|
out.WriteString(ending)
|
||||||
|
out.WriteString(">\n")
|
||||||
|
}
|
||||||
|
out.WriteString("</head>\n")
|
||||||
|
out.WriteString("<body>\n")
|
||||||
|
|
||||||
|
options.tocMarker = out.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) DocumentFooter(out *bytes.Buffer) {
|
||||||
|
// finalize and insert the table of contents
|
||||||
|
if options.flags&HTML_TOC != 0 {
|
||||||
|
options.TocFinalize()
|
||||||
|
|
||||||
|
// now we have to insert the table of contents into the document
|
||||||
|
var temp bytes.Buffer
|
||||||
|
|
||||||
|
// start by making a copy of everything after the document header
|
||||||
|
temp.Write(out.Bytes()[options.tocMarker:])
|
||||||
|
|
||||||
|
// now clear the copied material from the main output buffer
|
||||||
|
out.Truncate(options.tocMarker)
|
||||||
|
|
||||||
|
// corner case spacing issue
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE != 0 {
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert the table of contents
|
||||||
|
out.WriteString("<nav>\n")
|
||||||
|
out.Write(options.toc.Bytes())
|
||||||
|
out.WriteString("</nav>\n")
|
||||||
|
|
||||||
|
// corner case spacing issue
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE == 0 && options.flags&HTML_OMIT_CONTENTS == 0 {
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out everything that came after it
|
||||||
|
if options.flags&HTML_OMIT_CONTENTS == 0 {
|
||||||
|
out.Write(temp.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.flags&HTML_COMPLETE_PAGE != 0 {
|
||||||
|
out.WriteString("\n</body>\n")
|
||||||
|
out.WriteString("</html>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) {
|
||||||
|
for level > options.currentLevel {
|
||||||
|
switch {
|
||||||
|
case bytes.HasSuffix(options.toc.Bytes(), []byte("</li>\n")):
|
||||||
|
// this sublist can nest underneath a header
|
||||||
|
size := options.toc.Len()
|
||||||
|
options.toc.Truncate(size - len("</li>\n"))
|
||||||
|
|
||||||
|
case options.currentLevel > 0:
|
||||||
|
options.toc.WriteString("<li>")
|
||||||
|
}
|
||||||
|
if options.toc.Len() > 0 {
|
||||||
|
options.toc.WriteByte('\n')
|
||||||
|
}
|
||||||
|
options.toc.WriteString("<ul>\n")
|
||||||
|
options.currentLevel++
|
||||||
|
}
|
||||||
|
|
||||||
|
for level < options.currentLevel {
|
||||||
|
options.toc.WriteString("</ul>")
|
||||||
|
if options.currentLevel > 1 {
|
||||||
|
options.toc.WriteString("</li>\n")
|
||||||
|
}
|
||||||
|
options.currentLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
options.toc.WriteString("<li><a href=\"#")
|
||||||
|
if anchor != "" {
|
||||||
|
options.toc.WriteString(anchor)
|
||||||
|
} else {
|
||||||
|
options.toc.WriteString("toc_")
|
||||||
|
options.toc.WriteString(strconv.Itoa(options.headerCount))
|
||||||
|
}
|
||||||
|
options.toc.WriteString("\">")
|
||||||
|
options.headerCount++
|
||||||
|
|
||||||
|
options.toc.Write(text)
|
||||||
|
|
||||||
|
options.toc.WriteString("</a></li>\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TocHeader(text []byte, level int) {
|
||||||
|
options.TocHeaderWithAnchor(text, level, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) TocFinalize() {
|
||||||
|
for options.currentLevel > 1 {
|
||||||
|
options.toc.WriteString("</ul></li>\n")
|
||||||
|
options.currentLevel--
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.currentLevel > 0 {
|
||||||
|
options.toc.WriteString("</ul>\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHtmlTag(tag []byte, tagname string) bool {
|
||||||
|
found, _ := findHtmlTagPos(tag, tagname)
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for a character, but ignore it when it's in any kind of quotes, it
|
||||||
|
// might be JavaScript
|
||||||
|
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
|
||||||
|
inSingleQuote := false
|
||||||
|
inDoubleQuote := false
|
||||||
|
inGraveQuote := false
|
||||||
|
i := start
|
||||||
|
for i < len(html) {
|
||||||
|
switch {
|
||||||
|
case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
|
||||||
|
return i
|
||||||
|
case html[i] == '\'':
|
||||||
|
inSingleQuote = !inSingleQuote
|
||||||
|
case html[i] == '"':
|
||||||
|
inDoubleQuote = !inDoubleQuote
|
||||||
|
case html[i] == '`':
|
||||||
|
inGraveQuote = !inGraveQuote
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
func findHtmlTagPos(tag []byte, tagname string) (bool, int) {
|
||||||
|
i := 0
|
||||||
|
if i < len(tag) && tag[0] != '<' {
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
i = skipSpace(tag, i)
|
||||||
|
|
||||||
|
if i < len(tag) && tag[i] == '/' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
i = skipSpace(tag, i)
|
||||||
|
j := 0
|
||||||
|
for ; i < len(tag); i, j = i+1, j+1 {
|
||||||
|
if j >= len(tagname) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(string(tag[i]))[0] != tagname[j] {
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == len(tag) {
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
|
||||||
|
if rightAngle > i {
|
||||||
|
return true, rightAngle
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipUntilChar(text []byte, start int, char byte) int {
|
||||||
|
i := start
|
||||||
|
for i < len(text) && text[i] != char {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipSpace(tag []byte, i int) int {
|
||||||
|
for i < len(tag) && isspace(tag[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipChar(data []byte, start int, char byte) int {
|
||||||
|
i := start
|
||||||
|
for i < len(data) && data[i] == char {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func doubleSpace(out *bytes.Buffer) {
|
||||||
|
if out.Len() > 0 {
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRelativeLink(link []byte) (yes bool) {
|
||||||
|
// a tag begin with '#'
|
||||||
|
if link[0] == '#' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// link begin with '/' but not '//', the second maybe a protocol relative link
|
||||||
|
if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// only the root '/'
|
||||||
|
if len(link) == 1 && link[0] == '/' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// current directory : begin with "./"
|
||||||
|
if bytes.HasPrefix(link, []byte("./")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent directory : begin with "../"
|
||||||
|
if bytes.HasPrefix(link, []byte("../")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Html) ensureUniqueHeaderID(id string) string {
|
||||||
|
for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] {
|
||||||
|
tmp := fmt.Sprintf("%s-%d", id, count+1)
|
||||||
|
|
||||||
|
if _, tmpFound := options.headerIDs[tmp]; !tmpFound {
|
||||||
|
options.headerIDs[id] = count + 1
|
||||||
|
id = tmp
|
||||||
|
} else {
|
||||||
|
id = id + "-1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := options.headerIDs[id]; !found {
|
||||||
|
options.headerIDs[id] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,332 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// LaTeX rendering backend
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Latex is a type that implements the Renderer interface for LaTeX output.
|
||||||
|
//
|
||||||
|
// Do not create this directly, instead use the LatexRenderer function.
|
||||||
|
type Latex struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatexRenderer creates and configures a Latex object, which
|
||||||
|
// satisfies the Renderer interface.
|
||||||
|
//
|
||||||
|
// flags is a set of LATEX_* options ORed together (currently no such options
|
||||||
|
// are defined).
|
||||||
|
func LatexRenderer(flags int) Renderer {
|
||||||
|
return &Latex{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) GetFlags() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// render code chunks using verbatim, or listings if we have a language
|
||||||
|
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||||
|
if lang == "" {
|
||||||
|
out.WriteString("\n\\begin{verbatim}\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\n\\begin{lstlisting}[language=")
|
||||||
|
out.WriteString(lang)
|
||||||
|
out.WriteString("]\n")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
if lang == "" {
|
||||||
|
out.WriteString("\n\\end{verbatim}\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\n\\end{lstlisting}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\n\\begin{quotation}\n")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("\n\\end{quotation}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
|
||||||
|
// a pretty lame thing to do...
|
||||||
|
out.WriteString("\n\\begin{verbatim}\n")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("\n\\end{verbatim}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
||||||
|
marker := out.Len()
|
||||||
|
|
||||||
|
switch level {
|
||||||
|
case 1:
|
||||||
|
out.WriteString("\n\\section{")
|
||||||
|
case 2:
|
||||||
|
out.WriteString("\n\\subsection{")
|
||||||
|
case 3:
|
||||||
|
out.WriteString("\n\\subsubsection{")
|
||||||
|
case 4:
|
||||||
|
out.WriteString("\n\\paragraph{")
|
||||||
|
case 5:
|
||||||
|
out.WriteString("\n\\subparagraph{")
|
||||||
|
case 6:
|
||||||
|
out.WriteString("\n\\textbf{")
|
||||||
|
}
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteString("}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) HRule(out *bytes.Buffer) {
|
||||||
|
out.WriteString("\n\\HRule\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
|
||||||
|
marker := out.Len()
|
||||||
|
if flags&LIST_TYPE_ORDERED != 0 {
|
||||||
|
out.WriteString("\n\\begin{enumerate}\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\n\\begin{itemize}\n")
|
||||||
|
}
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if flags&LIST_TYPE_ORDERED != 0 {
|
||||||
|
out.WriteString("\n\\end{enumerate}\n")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\n\\end{itemize}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||||
|
out.WriteString("\n\\item ")
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
|
||||||
|
marker := out.Len()
|
||||||
|
out.WriteString("\n")
|
||||||
|
if !text() {
|
||||||
|
out.Truncate(marker)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
|
||||||
|
out.WriteString("\n\\begin{tabular}{")
|
||||||
|
for _, elt := range columnData {
|
||||||
|
switch elt {
|
||||||
|
case TABLE_ALIGNMENT_LEFT:
|
||||||
|
out.WriteByte('l')
|
||||||
|
case TABLE_ALIGNMENT_RIGHT:
|
||||||
|
out.WriteByte('r')
|
||||||
|
default:
|
||||||
|
out.WriteByte('c')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.WriteString("}\n")
|
||||||
|
out.Write(header)
|
||||||
|
out.WriteString(" \\\\\n\\hline\n")
|
||||||
|
out.Write(body)
|
||||||
|
out.WriteString("\n\\end{tabular}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
|
||||||
|
if out.Len() > 0 {
|
||||||
|
out.WriteString(" \\\\\n")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
|
||||||
|
if out.Len() > 0 {
|
||||||
|
out.WriteString(" & ")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
|
||||||
|
if out.Len() > 0 {
|
||||||
|
out.WriteString(" & ")
|
||||||
|
}
|
||||||
|
out.Write(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this
|
||||||
|
func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
||||||
|
out.WriteString("\\href{")
|
||||||
|
if kind == LINK_TYPE_EMAIL {
|
||||||
|
out.WriteString("mailto:")
|
||||||
|
}
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}{")
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\texttt{")
|
||||||
|
escapeSpecialChars(out, text)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\textbf{")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\textit{")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||||
|
if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
|
||||||
|
// treat it like a link
|
||||||
|
out.WriteString("\\href{")
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}{")
|
||||||
|
out.Write(alt)
|
||||||
|
out.WriteString("}")
|
||||||
|
} else {
|
||||||
|
out.WriteString("\\includegraphics{")
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) LineBreak(out *bytes.Buffer) {
|
||||||
|
out.WriteString(" \\\\\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||||
|
out.WriteString("\\href{")
|
||||||
|
out.Write(link)
|
||||||
|
out.WriteString("}{")
|
||||||
|
out.Write(content)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\textbf{\\textit{")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("}}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
|
||||||
|
out.WriteString("\\sout{")
|
||||||
|
out.Write(text)
|
||||||
|
out.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this
|
||||||
|
func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func needsBackslash(c byte) bool {
|
||||||
|
for _, r := range []byte("_{}%$&\\~#") {
|
||||||
|
if c == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
// directly copy normal characters
|
||||||
|
org := i
|
||||||
|
|
||||||
|
for i < len(text) && !needsBackslash(text[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i > org {
|
||||||
|
out.Write(text[org:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// escape a character
|
||||||
|
if i >= len(text) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out.WriteByte('\\')
|
||||||
|
out.WriteByte(text[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
|
||||||
|
// TODO: convert this into a unicode character or something
|
||||||
|
out.Write(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
|
||||||
|
escapeSpecialChars(out, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// header and footer
|
||||||
|
func (options *Latex) DocumentHeader(out *bytes.Buffer) {
|
||||||
|
out.WriteString("\\documentclass{article}\n")
|
||||||
|
out.WriteString("\n")
|
||||||
|
out.WriteString("\\usepackage{graphicx}\n")
|
||||||
|
out.WriteString("\\usepackage{listings}\n")
|
||||||
|
out.WriteString("\\usepackage[margin=1in]{geometry}\n")
|
||||||
|
out.WriteString("\\usepackage[utf8]{inputenc}\n")
|
||||||
|
out.WriteString("\\usepackage{verbatim}\n")
|
||||||
|
out.WriteString("\\usepackage[normalem]{ulem}\n")
|
||||||
|
out.WriteString("\\usepackage{hyperref}\n")
|
||||||
|
out.WriteString("\n")
|
||||||
|
out.WriteString("\\hypersetup{colorlinks,%\n")
|
||||||
|
out.WriteString(" citecolor=black,%\n")
|
||||||
|
out.WriteString(" filecolor=black,%\n")
|
||||||
|
out.WriteString(" linkcolor=black,%\n")
|
||||||
|
out.WriteString(" urlcolor=black,%\n")
|
||||||
|
out.WriteString(" pdfstartview=FitH,%\n")
|
||||||
|
out.WriteString(" breaklinks=true,%\n")
|
||||||
|
out.WriteString(" pdfauthor={Blackfriday Markdown Processor v")
|
||||||
|
out.WriteString(VERSION)
|
||||||
|
out.WriteString("}}\n")
|
||||||
|
out.WriteString("\n")
|
||||||
|
out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
|
||||||
|
out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
|
||||||
|
out.WriteString("\\parindent=0pt\n")
|
||||||
|
out.WriteString("\n")
|
||||||
|
out.WriteString("\\begin{document}\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (options *Latex) DocumentFooter(out *bytes.Buffer) {
|
||||||
|
out.WriteString("\n\\end{document}\n")
|
||||||
|
}
|
|
@ -0,0 +1,931 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Markdown parsing and processing
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const VERSION = "1.5"
|
||||||
|
|
||||||
|
// These are the supported markdown parsing extensions.
|
||||||
|
// OR these values together to select multiple extensions.
|
||||||
|
const (
|
||||||
|
EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words
|
||||||
|
EXTENSION_TABLES // render tables
|
||||||
|
EXTENSION_FENCED_CODE // render fenced code blocks
|
||||||
|
EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked
|
||||||
|
EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~
|
||||||
|
EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules
|
||||||
|
EXTENSION_SPACE_HEADERS // be strict about prefix header rules
|
||||||
|
EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks
|
||||||
|
EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four
|
||||||
|
EXTENSION_FOOTNOTES // Pandoc-style footnotes
|
||||||
|
EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
||||||
|
EXTENSION_HEADER_IDS // specify header IDs with {#id}
|
||||||
|
EXTENSION_TITLEBLOCK // Titleblock ala pandoc
|
||||||
|
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
|
||||||
|
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
|
||||||
|
EXTENSION_DEFINITION_LISTS // render definition lists
|
||||||
|
EXTENSION_JOIN_LINES // delete newline and join lines
|
||||||
|
|
||||||
|
commonHtmlFlags = 0 |
|
||||||
|
HTML_USE_XHTML |
|
||||||
|
HTML_USE_SMARTYPANTS |
|
||||||
|
HTML_SMARTYPANTS_FRACTIONS |
|
||||||
|
HTML_SMARTYPANTS_DASHES |
|
||||||
|
HTML_SMARTYPANTS_LATEX_DASHES
|
||||||
|
|
||||||
|
commonExtensions = 0 |
|
||||||
|
EXTENSION_NO_INTRA_EMPHASIS |
|
||||||
|
EXTENSION_TABLES |
|
||||||
|
EXTENSION_FENCED_CODE |
|
||||||
|
EXTENSION_AUTOLINK |
|
||||||
|
EXTENSION_STRIKETHROUGH |
|
||||||
|
EXTENSION_SPACE_HEADERS |
|
||||||
|
EXTENSION_HEADER_IDS |
|
||||||
|
EXTENSION_BACKSLASH_LINE_BREAK |
|
||||||
|
EXTENSION_DEFINITION_LISTS
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are the possible flag values for the link renderer.
|
||||||
|
// Only a single one of these values will be used; they are not ORed together.
|
||||||
|
// These are mostly of interest if you are writing a new output format.
|
||||||
|
const (
|
||||||
|
LINK_TYPE_NOT_AUTOLINK = iota
|
||||||
|
LINK_TYPE_NORMAL
|
||||||
|
LINK_TYPE_EMAIL
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are the possible flag values for the ListItem renderer.
|
||||||
|
// Multiple flag values may be ORed together.
|
||||||
|
// These are mostly of interest if you are writing a new output format.
|
||||||
|
const (
|
||||||
|
LIST_TYPE_ORDERED = 1 << iota
|
||||||
|
LIST_TYPE_DEFINITION
|
||||||
|
LIST_TYPE_TERM
|
||||||
|
LIST_ITEM_CONTAINS_BLOCK
|
||||||
|
LIST_ITEM_BEGINNING_OF_LIST
|
||||||
|
LIST_ITEM_END_OF_LIST
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are the possible flag values for the table cell renderer.
|
||||||
|
// Only a single one of these values will be used; they are not ORed together.
|
||||||
|
// These are mostly of interest if you are writing a new output format.
|
||||||
|
const (
|
||||||
|
TABLE_ALIGNMENT_LEFT = 1 << iota
|
||||||
|
TABLE_ALIGNMENT_RIGHT
|
||||||
|
TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT)
|
||||||
|
)
|
||||||
|
|
||||||
|
// The size of a tab stop.
|
||||||
|
const (
|
||||||
|
TAB_SIZE_DEFAULT = 4
|
||||||
|
TAB_SIZE_EIGHT = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// blockTags is a set of tags that are recognized as HTML block tags.
|
||||||
|
// Any of these can be included in markdown text without special escaping.
|
||||||
|
var blockTags = map[string]struct{}{
|
||||||
|
"blockquote": {},
|
||||||
|
"del": {},
|
||||||
|
"div": {},
|
||||||
|
"dl": {},
|
||||||
|
"fieldset": {},
|
||||||
|
"form": {},
|
||||||
|
"h1": {},
|
||||||
|
"h2": {},
|
||||||
|
"h3": {},
|
||||||
|
"h4": {},
|
||||||
|
"h5": {},
|
||||||
|
"h6": {},
|
||||||
|
"iframe": {},
|
||||||
|
"ins": {},
|
||||||
|
"math": {},
|
||||||
|
"noscript": {},
|
||||||
|
"ol": {},
|
||||||
|
"pre": {},
|
||||||
|
"p": {},
|
||||||
|
"script": {},
|
||||||
|
"style": {},
|
||||||
|
"table": {},
|
||||||
|
"ul": {},
|
||||||
|
|
||||||
|
// HTML5
|
||||||
|
"address": {},
|
||||||
|
"article": {},
|
||||||
|
"aside": {},
|
||||||
|
"canvas": {},
|
||||||
|
"figcaption": {},
|
||||||
|
"figure": {},
|
||||||
|
"footer": {},
|
||||||
|
"header": {},
|
||||||
|
"hgroup": {},
|
||||||
|
"main": {},
|
||||||
|
"nav": {},
|
||||||
|
"output": {},
|
||||||
|
"progress": {},
|
||||||
|
"section": {},
|
||||||
|
"video": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderer is the rendering interface.
|
||||||
|
// This is mostly of interest if you are implementing a new rendering format.
|
||||||
|
//
|
||||||
|
// When a byte slice is provided, it contains the (rendered) contents of the
|
||||||
|
// element.
|
||||||
|
//
|
||||||
|
// When a callback is provided instead, it will write the contents of the
|
||||||
|
// respective element directly to the output buffer and return true on success.
|
||||||
|
// If the callback returns false, the rendering function should reset the
|
||||||
|
// output buffer as though it had never been called.
|
||||||
|
//
|
||||||
|
// Currently Html and Latex implementations are provided
|
||||||
|
type Renderer interface {
|
||||||
|
// block-level callbacks
|
||||||
|
BlockCode(out *bytes.Buffer, text []byte, lang string)
|
||||||
|
BlockQuote(out *bytes.Buffer, text []byte)
|
||||||
|
BlockHtml(out *bytes.Buffer, text []byte)
|
||||||
|
Header(out *bytes.Buffer, text func() bool, level int, id string)
|
||||||
|
HRule(out *bytes.Buffer)
|
||||||
|
List(out *bytes.Buffer, text func() bool, flags int)
|
||||||
|
ListItem(out *bytes.Buffer, text []byte, flags int)
|
||||||
|
Paragraph(out *bytes.Buffer, text func() bool)
|
||||||
|
Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
|
||||||
|
TableRow(out *bytes.Buffer, text []byte)
|
||||||
|
TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
|
||||||
|
TableCell(out *bytes.Buffer, text []byte, flags int)
|
||||||
|
Footnotes(out *bytes.Buffer, text func() bool)
|
||||||
|
FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
|
||||||
|
TitleBlock(out *bytes.Buffer, text []byte)
|
||||||
|
|
||||||
|
// Span-level callbacks
|
||||||
|
AutoLink(out *bytes.Buffer, link []byte, kind int)
|
||||||
|
CodeSpan(out *bytes.Buffer, text []byte)
|
||||||
|
DoubleEmphasis(out *bytes.Buffer, text []byte)
|
||||||
|
Emphasis(out *bytes.Buffer, text []byte)
|
||||||
|
Image(out *bytes.Buffer, link []byte, title []byte, alt []byte)
|
||||||
|
LineBreak(out *bytes.Buffer)
|
||||||
|
Link(out *bytes.Buffer, link []byte, title []byte, content []byte)
|
||||||
|
RawHtmlTag(out *bytes.Buffer, tag []byte)
|
||||||
|
TripleEmphasis(out *bytes.Buffer, text []byte)
|
||||||
|
StrikeThrough(out *bytes.Buffer, text []byte)
|
||||||
|
FootnoteRef(out *bytes.Buffer, ref []byte, id int)
|
||||||
|
|
||||||
|
// Low-level callbacks
|
||||||
|
Entity(out *bytes.Buffer, entity []byte)
|
||||||
|
NormalText(out *bytes.Buffer, text []byte)
|
||||||
|
|
||||||
|
// Header and footer
|
||||||
|
DocumentHeader(out *bytes.Buffer)
|
||||||
|
DocumentFooter(out *bytes.Buffer)
|
||||||
|
|
||||||
|
GetFlags() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback functions for inline parsing. One such function is defined
|
||||||
|
// for each character that triggers a response when parsing inline data.
|
||||||
|
type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int
|
||||||
|
|
||||||
|
// Parser holds runtime state used by the parser.
|
||||||
|
// This is constructed by the Markdown function.
|
||||||
|
type parser struct {
|
||||||
|
r Renderer
|
||||||
|
refOverride ReferenceOverrideFunc
|
||||||
|
refs map[string]*reference
|
||||||
|
inlineCallback [256]inlineParser
|
||||||
|
flags int
|
||||||
|
nesting int
|
||||||
|
maxNesting int
|
||||||
|
insideLink bool
|
||||||
|
|
||||||
|
// Footnotes need to be ordered as well as available to quickly check for
|
||||||
|
// presence. If a ref is also a footnote, it's stored both in refs and here
|
||||||
|
// in notes. Slice is nil if footnotes not enabled.
|
||||||
|
notes []*reference
|
||||||
|
notesRecord map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||||
|
if p.refOverride != nil {
|
||||||
|
r, overridden := p.refOverride(refid)
|
||||||
|
if overridden {
|
||||||
|
if r == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return &reference{
|
||||||
|
link: []byte(r.Link),
|
||||||
|
title: []byte(r.Title),
|
||||||
|
noteId: 0,
|
||||||
|
hasBlock: false,
|
||||||
|
text: []byte(r.Text)}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// refs are case insensitive
|
||||||
|
ref, found = p.refs[strings.ToLower(refid)]
|
||||||
|
return ref, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) isFootnote(ref *reference) bool {
|
||||||
|
_, ok := p.notesRecord[string(ref.link)]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Public interface
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
// Reference represents the details of a link.
|
||||||
|
// See the documentation in Options for more details on use-case.
|
||||||
|
type Reference struct {
|
||||||
|
// Link is usually the URL the reference points to.
|
||||||
|
Link string
|
||||||
|
// Title is the alternate text describing the link in more detail.
|
||||||
|
Title string
|
||||||
|
// Text is the optional text to override the ref with if the syntax used was
|
||||||
|
// [refid][]
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferenceOverrideFunc is expected to be called with a reference string and
|
||||||
|
// return either a valid Reference type that the reference string maps to or
|
||||||
|
// nil. If overridden is false, the default reference logic will be executed.
|
||||||
|
// See the documentation in Options for more details on use-case.
|
||||||
|
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
|
||||||
|
|
||||||
|
// Options represents configurable overrides and callbacks (in addition to the
|
||||||
|
// extension flag set) for configuring a Markdown parse.
|
||||||
|
type Options struct {
|
||||||
|
// Extensions is a flag set of bit-wise ORed extension bits. See the
|
||||||
|
// EXTENSION_* flags defined in this package.
|
||||||
|
Extensions int
|
||||||
|
|
||||||
|
// ReferenceOverride is an optional function callback that is called every
|
||||||
|
// time a reference is resolved.
|
||||||
|
//
|
||||||
|
// In Markdown, the link reference syntax can be made to resolve a link to
|
||||||
|
// a reference instead of an inline URL, in one of the following ways:
|
||||||
|
//
|
||||||
|
// * [link text][refid]
|
||||||
|
// * [refid][]
|
||||||
|
//
|
||||||
|
// Usually, the refid is defined at the bottom of the Markdown document. If
|
||||||
|
// this override function is provided, the refid is passed to the override
|
||||||
|
// function first, before consulting the defined refids at the bottom. If
|
||||||
|
// the override function indicates an override did not occur, the refids at
|
||||||
|
// the bottom will be used to fill in the link details.
|
||||||
|
ReferenceOverride ReferenceOverrideFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkdownBasic is a convenience function for simple rendering.
|
||||||
|
// It processes markdown input with no extensions enabled.
|
||||||
|
func MarkdownBasic(input []byte) []byte {
|
||||||
|
// set up the HTML renderer
|
||||||
|
htmlFlags := HTML_USE_XHTML
|
||||||
|
renderer := HtmlRenderer(htmlFlags, "", "")
|
||||||
|
|
||||||
|
// set up the parser
|
||||||
|
return MarkdownOptions(input, renderer, Options{Extensions: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Markdown with most useful extensions enabled
|
||||||
|
// MarkdownCommon is a convenience function for simple rendering.
|
||||||
|
// It processes markdown input with common extensions enabled, including:
|
||||||
|
//
|
||||||
|
// * Smartypants processing with smart fractions and LaTeX dashes
|
||||||
|
//
|
||||||
|
// * Intra-word emphasis suppression
|
||||||
|
//
|
||||||
|
// * Tables
|
||||||
|
//
|
||||||
|
// * Fenced code blocks
|
||||||
|
//
|
||||||
|
// * Autolinking
|
||||||
|
//
|
||||||
|
// * Strikethrough support
|
||||||
|
//
|
||||||
|
// * Strict header parsing
|
||||||
|
//
|
||||||
|
// * Custom Header IDs
|
||||||
|
func MarkdownCommon(input []byte) []byte {
|
||||||
|
// set up the HTML renderer
|
||||||
|
renderer := HtmlRenderer(commonHtmlFlags, "", "")
|
||||||
|
return MarkdownOptions(input, renderer, Options{
|
||||||
|
Extensions: commonExtensions})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Markdown is the main rendering function.
|
||||||
|
// It parses and renders a block of markdown-encoded text.
|
||||||
|
// The supplied Renderer is used to format the output, and extensions dictates
|
||||||
|
// which non-standard extensions are enabled.
|
||||||
|
//
|
||||||
|
// To use the supplied Html or LaTeX renderers, see HtmlRenderer and
|
||||||
|
// LatexRenderer, respectively.
|
||||||
|
func Markdown(input []byte, renderer Renderer, extensions int) []byte {
|
||||||
|
return MarkdownOptions(input, renderer, Options{
|
||||||
|
Extensions: extensions})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkdownOptions is just like Markdown but takes additional options through
|
||||||
|
// the Options struct.
|
||||||
|
func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
|
||||||
|
// no point in parsing if we can't render
|
||||||
|
if renderer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions := opts.Extensions
|
||||||
|
|
||||||
|
// fill in the render structure
|
||||||
|
p := new(parser)
|
||||||
|
p.r = renderer
|
||||||
|
p.flags = extensions
|
||||||
|
p.refOverride = opts.ReferenceOverride
|
||||||
|
p.refs = make(map[string]*reference)
|
||||||
|
p.maxNesting = 16
|
||||||
|
p.insideLink = false
|
||||||
|
|
||||||
|
// register inline parsers
|
||||||
|
p.inlineCallback['*'] = emphasis
|
||||||
|
p.inlineCallback['_'] = emphasis
|
||||||
|
if extensions&EXTENSION_STRIKETHROUGH != 0 {
|
||||||
|
p.inlineCallback['~'] = emphasis
|
||||||
|
}
|
||||||
|
p.inlineCallback['`'] = codeSpan
|
||||||
|
p.inlineCallback['\n'] = lineBreak
|
||||||
|
p.inlineCallback['['] = link
|
||||||
|
p.inlineCallback['<'] = leftAngle
|
||||||
|
p.inlineCallback['\\'] = escape
|
||||||
|
p.inlineCallback['&'] = entity
|
||||||
|
|
||||||
|
if extensions&EXTENSION_AUTOLINK != 0 {
|
||||||
|
p.inlineCallback[':'] = autoLink
|
||||||
|
}
|
||||||
|
|
||||||
|
if extensions&EXTENSION_FOOTNOTES != 0 {
|
||||||
|
p.notes = make([]*reference, 0)
|
||||||
|
p.notesRecord = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
first := firstPass(p, input)
|
||||||
|
second := secondPass(p, first)
|
||||||
|
return second
|
||||||
|
}
|
||||||
|
|
||||||
|
// first pass:
|
||||||
|
// - normalize newlines
|
||||||
|
// - extract references (outside of fenced code blocks)
|
||||||
|
// - expand tabs (outside of fenced code blocks)
|
||||||
|
// - copy everything else
|
||||||
|
func firstPass(p *parser, input []byte) []byte {
|
||||||
|
var out bytes.Buffer
|
||||||
|
tabSize := TAB_SIZE_DEFAULT
|
||||||
|
if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
|
||||||
|
tabSize = TAB_SIZE_EIGHT
|
||||||
|
}
|
||||||
|
beg := 0
|
||||||
|
lastFencedCodeBlockEnd := 0
|
||||||
|
for beg < len(input) {
|
||||||
|
// Find end of this line, then process the line.
|
||||||
|
end := beg
|
||||||
|
for end < len(input) && input[end] != '\n' && input[end] != '\r' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
||||||
|
// track fenced code block boundaries to suppress tab expansion
|
||||||
|
// and reference extraction inside them:
|
||||||
|
if beg >= lastFencedCodeBlockEnd {
|
||||||
|
if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 {
|
||||||
|
lastFencedCodeBlockEnd = beg + i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the line body if present
|
||||||
|
if end > beg {
|
||||||
|
if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
|
||||||
|
out.Write(input[beg:end])
|
||||||
|
} else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
|
||||||
|
beg += refEnd
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
expandTabs(&out, input[beg:end], tabSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if end < len(input) && input[end] == '\r' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
if end < len(input) && input[end] == '\n' {
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
out.WriteByte('\n')
|
||||||
|
|
||||||
|
beg = end
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty input?
|
||||||
|
if out.Len() == 0 {
|
||||||
|
out.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// second pass: actual rendering
|
||||||
|
func secondPass(p *parser, input []byte) []byte {
|
||||||
|
var output bytes.Buffer
|
||||||
|
|
||||||
|
p.r.DocumentHeader(&output)
|
||||||
|
p.block(&output, input)
|
||||||
|
|
||||||
|
if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 {
|
||||||
|
p.r.Footnotes(&output, func() bool {
|
||||||
|
flags := LIST_ITEM_BEGINNING_OF_LIST
|
||||||
|
for i := 0; i < len(p.notes); i += 1 {
|
||||||
|
ref := p.notes[i]
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if ref.hasBlock {
|
||||||
|
flags |= LIST_ITEM_CONTAINS_BLOCK
|
||||||
|
p.block(&buf, ref.title)
|
||||||
|
} else {
|
||||||
|
p.inline(&buf, ref.title)
|
||||||
|
}
|
||||||
|
p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
|
||||||
|
flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
p.r.DocumentFooter(&output)
|
||||||
|
|
||||||
|
if p.nesting != 0 {
|
||||||
|
panic("Nesting level did not end at zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Link references
|
||||||
|
//
|
||||||
|
// This section implements support for references that (usually) appear
|
||||||
|
// as footnotes in a document, and can be referenced anywhere in the document.
|
||||||
|
// The basic format is:
|
||||||
|
//
|
||||||
|
// [1]: http://www.google.com/ "Google"
|
||||||
|
// [2]: http://www.github.com/ "Github"
|
||||||
|
//
|
||||||
|
// Anywhere in the document, the reference can be linked by referring to its
|
||||||
|
// label, i.e., 1 and 2 in this example, as in:
|
||||||
|
//
|
||||||
|
// This library is hosted on [Github][2], a git hosting site.
|
||||||
|
//
|
||||||
|
// Actual footnotes as specified in Pandoc and supported by some other Markdown
|
||||||
|
// libraries such as php-markdown are also taken care of. They look like this:
|
||||||
|
//
|
||||||
|
// This sentence needs a bit of further explanation.[^note]
|
||||||
|
//
|
||||||
|
// [^note]: This is the explanation.
|
||||||
|
//
|
||||||
|
// Footnotes should be placed at the end of the document in an ordered list.
|
||||||
|
// Inline footnotes such as:
|
||||||
|
//
|
||||||
|
// Inline footnotes^[Not supported.] also exist.
|
||||||
|
//
|
||||||
|
// are not yet supported.
|
||||||
|
|
||||||
|
// References are parsed and stored in this struct.
|
||||||
|
type reference struct {
|
||||||
|
link []byte
|
||||||
|
title []byte
|
||||||
|
noteId int // 0 if not a footnote ref
|
||||||
|
hasBlock bool
|
||||||
|
text []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) String() string {
|
||||||
|
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}",
|
||||||
|
r.link, r.title, r.text, r.noteId, r.hasBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether or not data starts with a reference link.
|
||||||
|
// If so, it is parsed and stored in the list of references
|
||||||
|
// (in the render struct).
|
||||||
|
// Returns the number of bytes to skip to move past it,
|
||||||
|
// or zero if the first line is not a reference.
|
||||||
|
func isReference(p *parser, data []byte, tabSize int) int {
|
||||||
|
// up to 3 optional leading spaces
|
||||||
|
if len(data) < 4 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for i < 3 && data[i] == ' ' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
noteId := 0
|
||||||
|
|
||||||
|
// id part: anything but a newline between brackets
|
||||||
|
if data[i] != '[' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if p.flags&EXTENSION_FOOTNOTES != 0 {
|
||||||
|
if i < len(data) && data[i] == '^' {
|
||||||
|
// we can set it to anything here because the proper noteIds will
|
||||||
|
// be assigned later during the second pass. It just has to be != 0
|
||||||
|
noteId = 1
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idOffset := i
|
||||||
|
for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(data) || data[i] != ']' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
idEnd := i
|
||||||
|
|
||||||
|
// spacer: colon (space | tab)* newline? (space | tab)*
|
||||||
|
i++
|
||||||
|
if i >= len(data) || data[i] != ':' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
|
||||||
|
i++
|
||||||
|
if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(data) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
linkOffset, linkEnd int
|
||||||
|
titleOffset, titleEnd int
|
||||||
|
lineEnd int
|
||||||
|
raw []byte
|
||||||
|
hasBlock bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 {
|
||||||
|
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
|
||||||
|
lineEnd = linkEnd
|
||||||
|
} else {
|
||||||
|
linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
|
||||||
|
}
|
||||||
|
if lineEnd == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// a valid ref has been found
|
||||||
|
|
||||||
|
ref := &reference{
|
||||||
|
noteId: noteId,
|
||||||
|
hasBlock: hasBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
if noteId > 0 {
|
||||||
|
// reusing the link field for the id since footnotes don't have links
|
||||||
|
ref.link = data[idOffset:idEnd]
|
||||||
|
// if footnote, it's not really a title, it's the contained text
|
||||||
|
ref.title = raw
|
||||||
|
} else {
|
||||||
|
ref.link = data[linkOffset:linkEnd]
|
||||||
|
ref.title = data[titleOffset:titleEnd]
|
||||||
|
}
|
||||||
|
|
||||||
|
// id matches are case-insensitive
|
||||||
|
id := string(bytes.ToLower(data[idOffset:idEnd]))
|
||||||
|
|
||||||
|
p.refs[id] = ref
|
||||||
|
|
||||||
|
return lineEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
|
||||||
|
// link: whitespace-free sequence, optionally between angle brackets
|
||||||
|
if data[i] == '<' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
linkOffset = i
|
||||||
|
if i == len(data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
linkEnd = i
|
||||||
|
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
|
||||||
|
linkOffset++
|
||||||
|
linkEnd--
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute end-of-line
|
||||||
|
if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
|
||||||
|
lineEnd = i
|
||||||
|
}
|
||||||
|
if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
|
||||||
|
lineEnd++
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional (space|tab)* spacer after a newline
|
||||||
|
if lineEnd > 0 {
|
||||||
|
i = lineEnd + 1
|
||||||
|
for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional title: any non-newline sequence enclosed in '"() alone on its line
|
||||||
|
if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
|
||||||
|
i++
|
||||||
|
titleOffset = i
|
||||||
|
|
||||||
|
// look for EOL
|
||||||
|
for i < len(data) && data[i] != '\n' && data[i] != '\r' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
|
||||||
|
titleEnd = i + 1
|
||||||
|
} else {
|
||||||
|
titleEnd = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// step back
|
||||||
|
i--
|
||||||
|
for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
|
||||||
|
lineEnd = titleEnd
|
||||||
|
titleEnd = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first bit of this logic is the same as (*parser).listItem, but the rest
|
||||||
|
// is much simpler. This function simply finds the entire block and shifts it
|
||||||
|
// over by one tab if it is indeed a block (just returns the line if it's not).
|
||||||
|
// blockEnd is the end of the section in the input buffer, and contents is the
|
||||||
|
// extracted text that was shifted over one tab. It will need to be rendered at
|
||||||
|
// the end of the document.
|
||||||
|
func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
|
||||||
|
if i == 0 || len(data) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip leading whitespace on first line
|
||||||
|
for i < len(data) && data[i] == ' ' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
blockStart = i
|
||||||
|
|
||||||
|
// find the end of the line
|
||||||
|
blockEnd = i
|
||||||
|
for i < len(data) && data[i-1] != '\n' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// get working buffer
|
||||||
|
var raw bytes.Buffer
|
||||||
|
|
||||||
|
// put the first line into the working buffer
|
||||||
|
raw.Write(data[blockEnd:i])
|
||||||
|
blockEnd = i
|
||||||
|
|
||||||
|
// process the following lines
|
||||||
|
containsBlankLine := false
|
||||||
|
|
||||||
|
gatherLines:
|
||||||
|
for blockEnd < len(data) {
|
||||||
|
i++
|
||||||
|
|
||||||
|
// find the end of this line
|
||||||
|
for i < len(data) && data[i-1] != '\n' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it is an empty line, guess that it is part of this item
|
||||||
|
// and move on to the next line
|
||||||
|
if p.isEmpty(data[blockEnd:i]) > 0 {
|
||||||
|
containsBlankLine = true
|
||||||
|
blockEnd = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n := 0
|
||||||
|
if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
|
||||||
|
// this is the end of the block.
|
||||||
|
// we don't want to include this last line in the index.
|
||||||
|
break gatherLines
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there were blank lines before this one, insert a new one now
|
||||||
|
if containsBlankLine {
|
||||||
|
raw.WriteByte('\n')
|
||||||
|
containsBlankLine = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// get rid of that first tab, write to buffer
|
||||||
|
raw.Write(data[blockEnd+n : i])
|
||||||
|
hasBlock = true
|
||||||
|
|
||||||
|
blockEnd = i
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[blockEnd-1] != '\n' {
|
||||||
|
raw.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = raw.Bytes()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Miscellaneous helper functions
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
// Test if a character is a punctuation symbol.
|
||||||
|
// Taken from a private function in regexp in the stdlib.
|
||||||
|
func ispunct(c byte) bool {
|
||||||
|
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
|
||||||
|
if c == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if a character is a whitespace character.
|
||||||
|
func isspace(c byte) bool {
|
||||||
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if a character is letter.
|
||||||
|
func isletter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if a character is a letter or a digit.
|
||||||
|
// TODO: check when this is looking for ASCII alnum and when it should use unicode
|
||||||
|
func isalnum(c byte) bool {
|
||||||
|
return (c >= '0' && c <= '9') || isletter(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
|
||||||
|
// always ends output with a newline
|
||||||
|
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
|
||||||
|
// first, check for common cases: no tabs, or only tabs at beginning of line
|
||||||
|
i, prefix := 0, 0
|
||||||
|
slowcase := false
|
||||||
|
for i = 0; i < len(line); i++ {
|
||||||
|
if line[i] == '\t' {
|
||||||
|
if prefix == i {
|
||||||
|
prefix++
|
||||||
|
} else {
|
||||||
|
slowcase = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to decode runes if all tabs are at the beginning of the line
|
||||||
|
if !slowcase {
|
||||||
|
for i = 0; i < prefix*tabSize; i++ {
|
||||||
|
out.WriteByte(' ')
|
||||||
|
}
|
||||||
|
out.Write(line[prefix:])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the slow case: we need to count runes to figure out how
|
||||||
|
// many spaces to insert for each tab
|
||||||
|
column := 0
|
||||||
|
i = 0
|
||||||
|
for i < len(line) {
|
||||||
|
start := i
|
||||||
|
for i < len(line) && line[i] != '\t' {
|
||||||
|
_, size := utf8.DecodeRune(line[i:])
|
||||||
|
i += size
|
||||||
|
column++
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > start {
|
||||||
|
out.Write(line[start:i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= len(line) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
out.WriteByte(' ')
|
||||||
|
column++
|
||||||
|
if column%tabSize == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find if a line counts as indented or not.
|
||||||
|
// Returns number of characters the indent is (0 = not indented).
|
||||||
|
func isIndented(data []byte, indentSize int) int {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if data[0] == '\t' {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if len(data) < indentSize {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for i := 0; i < indentSize; i++ {
|
||||||
|
if data[i] != ' ' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indentSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a url-safe slug for fragments
|
||||||
|
func slugify(in []byte) []byte {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
out := make([]byte, 0, len(in))
|
||||||
|
sym := false
|
||||||
|
|
||||||
|
for _, ch := range in {
|
||||||
|
if isalnum(ch) {
|
||||||
|
sym = false
|
||||||
|
out = append(out, ch)
|
||||||
|
} else if sym {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
out = append(out, '-')
|
||||||
|
sym = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var a, b int
|
||||||
|
var ch byte
|
||||||
|
for a, ch = range out {
|
||||||
|
if ch != '-' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for b = len(out) - 1; b > 0; b-- {
|
||||||
|
if out[b] != '-' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out[a : b+1]
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Unit tests for full document parsing and rendering
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runMarkdown(input string) string {
|
||||||
|
return string(MarkdownCommon([]byte(input)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTests(t *testing.T, tests []string) {
|
||||||
|
// catch and report panics
|
||||||
|
var candidate string
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i+1 < len(tests); i += 2 {
|
||||||
|
input := tests[i]
|
||||||
|
candidate = input
|
||||||
|
expected := tests[i+1]
|
||||||
|
actual := runMarkdown(candidate)
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
||||||
|
candidate, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now test every substring to stress test bounds checking
|
||||||
|
if !testing.Short() {
|
||||||
|
for start := 0; start < len(input); start++ {
|
||||||
|
for end := start + 1; end <= len(input); end++ {
|
||||||
|
candidate = input[start:end]
|
||||||
|
_ = runMarkdown(candidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocument(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
// Empty document.
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
|
||||||
|
" ",
|
||||||
|
"",
|
||||||
|
|
||||||
|
// This shouldn't panic.
|
||||||
|
// https://github.com/russross/blackfriday/issues/172
|
||||||
|
"[]:<",
|
||||||
|
"<p>[]:<</p>\n",
|
||||||
|
|
||||||
|
// This shouldn't panic.
|
||||||
|
// https://github.com/russross/blackfriday/issues/173
|
||||||
|
" [",
|
||||||
|
"<p>[</p>\n",
|
||||||
|
}
|
||||||
|
doTests(t, tests)
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Markdown 1.0.3 reference tests
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runMarkdownReference(input string, flag int) string {
|
||||||
|
renderer := HtmlRenderer(0, "", "")
|
||||||
|
return string(Markdown([]byte(input), renderer, flag))
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestsReference(t *testing.T, files []string, flag int) {
|
||||||
|
// catch and report panics
|
||||||
|
var candidate string
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for _, basename := range files {
|
||||||
|
filename := filepath.Join("testdata", basename+".text")
|
||||||
|
inputBytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
input := string(inputBytes)
|
||||||
|
|
||||||
|
filename = filepath.Join("testdata", basename+".html")
|
||||||
|
expectedBytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expected := string(expectedBytes)
|
||||||
|
|
||||||
|
// fmt.Fprintf(os.Stderr, "processing %s ...", filename)
|
||||||
|
actual := string(runMarkdownReference(input, flag))
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("\n [%#v]\nExpected[%#v]\nActual [%#v]",
|
||||||
|
basename+".text", expected, actual)
|
||||||
|
}
|
||||||
|
// fmt.Fprintf(os.Stderr, " ok\n")
|
||||||
|
|
||||||
|
// now test every prefix of every input to check for
|
||||||
|
// bounds checking
|
||||||
|
if !testing.Short() {
|
||||||
|
start, max := 0, len(input)
|
||||||
|
for end := start + 1; end <= max; end++ {
|
||||||
|
candidate = input[start:end]
|
||||||
|
// fmt.Fprintf(os.Stderr, " %s %d:%d/%d\n", filename, start, end, max)
|
||||||
|
_ = runMarkdownReference(candidate, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReference(t *testing.T) {
|
||||||
|
files := []string{
|
||||||
|
"Amps and angle encoding",
|
||||||
|
"Auto links",
|
||||||
|
"Backslash escapes",
|
||||||
|
"Blockquotes with code blocks",
|
||||||
|
"Code Blocks",
|
||||||
|
"Code Spans",
|
||||||
|
"Hard-wrapped paragraphs with list-like lines",
|
||||||
|
"Horizontal rules",
|
||||||
|
"Inline HTML (Advanced)",
|
||||||
|
"Inline HTML (Simple)",
|
||||||
|
"Inline HTML comments",
|
||||||
|
"Links, inline style",
|
||||||
|
"Links, reference style",
|
||||||
|
"Links, shortcut references",
|
||||||
|
"Literal quotes in titles",
|
||||||
|
"Markdown Documentation - Basics",
|
||||||
|
"Markdown Documentation - Syntax",
|
||||||
|
"Nested blockquotes",
|
||||||
|
"Ordered and unordered lists",
|
||||||
|
"Strong and em together",
|
||||||
|
"Tabs",
|
||||||
|
"Tidyness",
|
||||||
|
}
|
||||||
|
doTestsReference(t, files, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReference_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
|
files := []string{
|
||||||
|
"Amps and angle encoding",
|
||||||
|
"Auto links",
|
||||||
|
"Backslash escapes",
|
||||||
|
"Blockquotes with code blocks",
|
||||||
|
"Code Blocks",
|
||||||
|
"Code Spans",
|
||||||
|
"Hard-wrapped paragraphs with list-like lines no empty line before block",
|
||||||
|
"Horizontal rules",
|
||||||
|
"Inline HTML (Advanced)",
|
||||||
|
"Inline HTML (Simple)",
|
||||||
|
"Inline HTML comments",
|
||||||
|
"Links, inline style",
|
||||||
|
"Links, reference style",
|
||||||
|
"Links, shortcut references",
|
||||||
|
"Literal quotes in titles",
|
||||||
|
"Markdown Documentation - Basics",
|
||||||
|
"Markdown Documentation - Syntax",
|
||||||
|
"Nested blockquotes",
|
||||||
|
"Ordered and unordered lists",
|
||||||
|
"Strong and em together",
|
||||||
|
"Tabs",
|
||||||
|
"Tidyness",
|
||||||
|
}
|
||||||
|
doTestsReference(t, files, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
|
||||||
|
}
|
|
@ -0,0 +1,400 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// SmartyPants rendering
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type smartypantsData struct {
|
||||||
|
inSingleQuote bool
|
||||||
|
inDoubleQuote bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func wordBoundary(c byte) bool {
|
||||||
|
return c == 0 || isspace(c) || ispunct(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tolower(c byte) byte {
|
||||||
|
if c >= 'A' && c <= 'Z' {
|
||||||
|
return c - 'A' + 'a'
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func isdigit(c byte) bool {
|
||||||
|
return c >= '0' && c <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
|
||||||
|
// edge of the buffer is likely to be a tag that we don't get to see,
|
||||||
|
// so we treat it like text sometimes
|
||||||
|
|
||||||
|
// enumerate all sixteen possibilities for (previousChar, nextChar)
|
||||||
|
// each can be one of {0, space, punct, other}
|
||||||
|
switch {
|
||||||
|
case previousChar == 0 && nextChar == 0:
|
||||||
|
// context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen
|
||||||
|
case isspace(previousChar) && nextChar == 0:
|
||||||
|
// [ "] might be [ "<code>foo...]
|
||||||
|
*isOpen = true
|
||||||
|
case ispunct(previousChar) && nextChar == 0:
|
||||||
|
// [!"] hmm... could be [Run!"] or [("<code>...]
|
||||||
|
*isOpen = false
|
||||||
|
case /* isnormal(previousChar) && */ nextChar == 0:
|
||||||
|
// [a"] is probably a close
|
||||||
|
*isOpen = false
|
||||||
|
case previousChar == 0 && isspace(nextChar):
|
||||||
|
// [" ] might be [...foo</code>" ]
|
||||||
|
*isOpen = false
|
||||||
|
case isspace(previousChar) && isspace(nextChar):
|
||||||
|
// [ " ] context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen
|
||||||
|
case ispunct(previousChar) && isspace(nextChar):
|
||||||
|
// [!" ] is probably a close
|
||||||
|
*isOpen = false
|
||||||
|
case /* isnormal(previousChar) && */ isspace(nextChar):
|
||||||
|
// [a" ] this is one of the easy cases
|
||||||
|
*isOpen = false
|
||||||
|
case previousChar == 0 && ispunct(nextChar):
|
||||||
|
// ["!] hmm... could be ["$1.95] or [</code>"!...]
|
||||||
|
*isOpen = false
|
||||||
|
case isspace(previousChar) && ispunct(nextChar):
|
||||||
|
// [ "!] looks more like [ "$1.95]
|
||||||
|
*isOpen = true
|
||||||
|
case ispunct(previousChar) && ispunct(nextChar):
|
||||||
|
// [!"!] context is not any help here, so toggle
|
||||||
|
*isOpen = !*isOpen
|
||||||
|
case /* isnormal(previousChar) && */ ispunct(nextChar):
|
||||||
|
// [a"!] is probably a close
|
||||||
|
*isOpen = false
|
||||||
|
case previousChar == 0 /* && isnormal(nextChar) */ :
|
||||||
|
// ["a] is probably an open
|
||||||
|
*isOpen = true
|
||||||
|
case isspace(previousChar) /* && isnormal(nextChar) */ :
|
||||||
|
// [ "a] this is one of the easy cases
|
||||||
|
*isOpen = true
|
||||||
|
case ispunct(previousChar) /* && isnormal(nextChar) */ :
|
||||||
|
// [!"a] is probably an open
|
||||||
|
*isOpen = true
|
||||||
|
default:
|
||||||
|
// [a'b] maybe a contraction?
|
||||||
|
*isOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte('&')
|
||||||
|
if *isOpen {
|
||||||
|
out.WriteByte('l')
|
||||||
|
} else {
|
||||||
|
out.WriteByte('r')
|
||||||
|
}
|
||||||
|
out.WriteByte(quote)
|
||||||
|
out.WriteString("quo;")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 2 {
|
||||||
|
t1 := tolower(text[1])
|
||||||
|
|
||||||
|
if t1 == '\'' {
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) >= 3 {
|
||||||
|
nextChar = text[2]
|
||||||
|
}
|
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
|
||||||
|
out.WriteString("’")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(text) >= 3 {
|
||||||
|
t2 := tolower(text[2])
|
||||||
|
|
||||||
|
if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
|
||||||
|
(len(text) < 4 || wordBoundary(text[3])) {
|
||||||
|
out.WriteString("’")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) > 1 {
|
||||||
|
nextChar = text[1]
|
||||||
|
}
|
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 3 {
|
||||||
|
t1 := tolower(text[1])
|
||||||
|
t2 := tolower(text[2])
|
||||||
|
|
||||||
|
if t1 == 'c' && t2 == ')' {
|
||||||
|
out.WriteString("©")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if t1 == 'r' && t2 == ')' {
|
||||||
|
out.WriteString("®")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
|
||||||
|
out.WriteString("™")
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 2 {
|
||||||
|
if text[1] == '-' {
|
||||||
|
out.WriteString("—")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if wordBoundary(previousChar) && wordBoundary(text[1]) {
|
||||||
|
out.WriteString("–")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
|
||||||
|
out.WriteString("—")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if len(text) >= 2 && text[1] == '-' {
|
||||||
|
out.WriteString("–")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
||||||
|
if bytes.HasPrefix(text, []byte(""")) {
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) >= 7 {
|
||||||
|
nextChar = text[6]
|
||||||
|
}
|
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.HasPrefix(text, []byte("�")) {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte('&')
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
return smartAmpVariant(out, smrt, previousChar, text, 'd')
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
return smartAmpVariant(out, smrt, previousChar, text, 'a')
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
|
||||||
|
out.WriteString("…")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
|
||||||
|
out.WriteString("…")
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if len(text) >= 2 && text[1] == '`' {
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) >= 3 {
|
||||||
|
nextChar = text[2]
|
||||||
|
}
|
||||||
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||||
|
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
|
||||||
|
// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
|
||||||
|
// and avoid changing dates like 1/23/2005 into fractions.
|
||||||
|
numEnd := 0
|
||||||
|
for len(text) > numEnd && isdigit(text[numEnd]) {
|
||||||
|
numEnd++
|
||||||
|
}
|
||||||
|
if numEnd == 0 {
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
denStart := numEnd + 1
|
||||||
|
if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
|
||||||
|
denStart = numEnd + 3
|
||||||
|
} else if len(text) < numEnd+2 || text[numEnd] != '/' {
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
denEnd := denStart
|
||||||
|
for len(text) > denEnd && isdigit(text[denEnd]) {
|
||||||
|
denEnd++
|
||||||
|
}
|
||||||
|
if denEnd == denStart {
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
|
||||||
|
out.WriteString("<sup>")
|
||||||
|
out.Write(text[:numEnd])
|
||||||
|
out.WriteString("</sup>⁄<sub>")
|
||||||
|
out.Write(text[denStart:denEnd])
|
||||||
|
out.WriteString("</sub>")
|
||||||
|
return denEnd - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||||
|
if text[0] == '1' && text[1] == '/' && text[2] == '2' {
|
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
|
||||||
|
out.WriteString("½")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if text[0] == '1' && text[1] == '/' && text[2] == '4' {
|
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
|
||||||
|
out.WriteString("¼")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if text[0] == '3' && text[1] == '/' && text[2] == '4' {
|
||||||
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
|
||||||
|
out.WriteString("¾")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteByte(text[0])
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
||||||
|
nextChar := byte(0)
|
||||||
|
if len(text) > 1 {
|
||||||
|
nextChar = text[1]
|
||||||
|
}
|
||||||
|
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||||
|
out.WriteString(""")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd')
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a')
|
||||||
|
}
|
||||||
|
|
||||||
|
func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for i < len(text) && text[i] != '>' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Write(text[:i+1])
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int
|
||||||
|
|
||||||
|
type smartypantsRenderer [256]smartCallback
|
||||||
|
|
||||||
|
func smartypants(flags int) *smartypantsRenderer {
|
||||||
|
r := new(smartypantsRenderer)
|
||||||
|
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
|
||||||
|
r['"'] = smartDoubleQuote
|
||||||
|
r['&'] = smartAmp
|
||||||
|
} else {
|
||||||
|
r['"'] = smartAngledDoubleQuote
|
||||||
|
r['&'] = smartAmpAngledQuote
|
||||||
|
}
|
||||||
|
r['\''] = smartSingleQuote
|
||||||
|
r['('] = smartParens
|
||||||
|
if flags&HTML_SMARTYPANTS_DASHES != 0 {
|
||||||
|
if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
|
||||||
|
r['-'] = smartDash
|
||||||
|
} else {
|
||||||
|
r['-'] = smartDashLatex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r['.'] = smartPeriod
|
||||||
|
if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
|
||||||
|
r['1'] = smartNumber
|
||||||
|
r['3'] = smartNumber
|
||||||
|
} else {
|
||||||
|
for ch := '1'; ch <= '9'; ch++ {
|
||||||
|
r[ch] = smartNumberGeneric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r['<'] = smartLeftAngle
|
||||||
|
r['`'] = smartBacktick
|
||||||
|
return r
|
||||||
|
}
|
17
vendor/github.com/russross/blackfriday/testdata/Amps and angle encoding.html
generated
vendored
100644
17
vendor/github.com/russross/blackfriday/testdata/Amps and angle encoding.html
generated
vendored
100644
|
@ -0,0 +1,17 @@
|
||||||
|
<p>AT&T has an ampersand in their name.</p>
|
||||||
|
|
||||||
|
<p>AT&T is another way to write it.</p>
|
||||||
|
|
||||||
|
<p>This & that.</p>
|
||||||
|
|
||||||
|
<p>4 < 5.</p>
|
||||||
|
|
||||||
|
<p>6 > 5.</p>
|
||||||
|
|
||||||
|
<p>Here's a <a href="http://example.com/?foo=1&bar=2">link</a> with an ampersand in the URL.</p>
|
||||||
|
|
||||||
|
<p>Here's a link with an amersand in the link text: <a href="http://att.com/" title="AT&T">AT&T</a>.</p>
|
||||||
|
|
||||||
|
<p>Here's an inline <a href="/script?foo=1&bar=2">link</a>.</p>
|
||||||
|
|
||||||
|
<p>Here's an inline <a href="/script?foo=1&bar=2">link</a>.</p>
|
21
vendor/github.com/russross/blackfriday/testdata/Amps and angle encoding.text
generated
vendored
100644
21
vendor/github.com/russross/blackfriday/testdata/Amps and angle encoding.text
generated
vendored
100644
|
@ -0,0 +1,21 @@
|
||||||
|
AT&T has an ampersand in their name.
|
||||||
|
|
||||||
|
AT&T is another way to write it.
|
||||||
|
|
||||||
|
This & that.
|
||||||
|
|
||||||
|
4 < 5.
|
||||||
|
|
||||||
|
6 > 5.
|
||||||
|
|
||||||
|
Here's a [link] [1] with an ampersand in the URL.
|
||||||
|
|
||||||
|
Here's a link with an amersand in the link text: [AT&T] [2].
|
||||||
|
|
||||||
|
Here's an inline [link](/script?foo=1&bar=2).
|
||||||
|
|
||||||
|
Here's an inline [link](</script?foo=1&bar=2>).
|
||||||
|
|
||||||
|
|
||||||
|
[1]: http://example.com/?foo=1&bar=2
|
||||||
|
[2]: http://att.com/ "AT&T"
|
|
@ -0,0 +1,18 @@
|
||||||
|
<p>Link: <a href="http://example.com/">http://example.com/</a>.</p>
|
||||||
|
|
||||||
|
<p>With an ampersand: <a href="http://example.com/?foo=1&bar=2">http://example.com/?foo=1&bar=2</a></p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>In a list?</li>
|
||||||
|
<li><a href="http://example.com/">http://example.com/</a></li>
|
||||||
|
<li>It should.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>Blockquoted: <a href="http://example.com/">http://example.com/</a></p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<p>Auto-links should not occur here: <code><http://example.com/></code></p>
|
||||||
|
|
||||||
|
<pre><code>or here: <http://example.com/>
|
||||||
|
</code></pre>
|
|
@ -0,0 +1,13 @@
|
||||||
|
Link: <http://example.com/>.
|
||||||
|
|
||||||
|
With an ampersand: <http://example.com/?foo=1&bar=2>
|
||||||
|
|
||||||
|
* In a list?
|
||||||
|
* <http://example.com/>
|
||||||
|
* It should.
|
||||||
|
|
||||||
|
> Blockquoted: <http://example.com/>
|
||||||
|
|
||||||
|
Auto-links should not occur here: `<http://example.com/>`
|
||||||
|
|
||||||
|
or here: <http://example.com/>
|
123
vendor/github.com/russross/blackfriday/testdata/Backslash escapes.html
generated
vendored
100644
123
vendor/github.com/russross/blackfriday/testdata/Backslash escapes.html
generated
vendored
100644
|
@ -0,0 +1,123 @@
|
||||||
|
<p>These should all get escaped:</p>
|
||||||
|
|
||||||
|
<p>Backslash: \</p>
|
||||||
|
|
||||||
|
<p>Backtick: `</p>
|
||||||
|
|
||||||
|
<p>Asterisk: *</p>
|
||||||
|
|
||||||
|
<p>Underscore: _</p>
|
||||||
|
|
||||||
|
<p>Left brace: {</p>
|
||||||
|
|
||||||
|
<p>Right brace: }</p>
|
||||||
|
|
||||||
|
<p>Left bracket: [</p>
|
||||||
|
|
||||||
|
<p>Right bracket: ]</p>
|
||||||
|
|
||||||
|
<p>Left paren: (</p>
|
||||||
|
|
||||||
|
<p>Right paren: )</p>
|
||||||
|
|
||||||
|
<p>Greater-than: ></p>
|
||||||
|
|
||||||
|
<p>Hash: #</p>
|
||||||
|
|
||||||
|
<p>Period: .</p>
|
||||||
|
|
||||||
|
<p>Bang: !</p>
|
||||||
|
|
||||||
|
<p>Plus: +</p>
|
||||||
|
|
||||||
|
<p>Minus: -</p>
|
||||||
|
|
||||||
|
<p>Tilde: ~</p>
|
||||||
|
|
||||||
|
<p>These should not, because they occur within a code block:</p>
|
||||||
|
|
||||||
|
<pre><code>Backslash: \\
|
||||||
|
|
||||||
|
Backtick: \`
|
||||||
|
|
||||||
|
Asterisk: \*
|
||||||
|
|
||||||
|
Underscore: \_
|
||||||
|
|
||||||
|
Left brace: \{
|
||||||
|
|
||||||
|
Right brace: \}
|
||||||
|
|
||||||
|
Left bracket: \[
|
||||||
|
|
||||||
|
Right bracket: \]
|
||||||
|
|
||||||
|
Left paren: \(
|
||||||
|
|
||||||
|
Right paren: \)
|
||||||
|
|
||||||
|
Greater-than: \>
|
||||||
|
|
||||||
|
Hash: \#
|
||||||
|
|
||||||
|
Period: \.
|
||||||
|
|
||||||
|
Bang: \!
|
||||||
|
|
||||||
|
Plus: \+
|
||||||
|
|
||||||
|
Minus: \-
|
||||||
|
|
||||||
|
Tilde: \~
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Nor should these, which occur in code spans:</p>
|
||||||
|
|
||||||
|
<p>Backslash: <code>\\</code></p>
|
||||||
|
|
||||||
|
<p>Backtick: <code>\`</code></p>
|
||||||
|
|
||||||
|
<p>Asterisk: <code>\*</code></p>
|
||||||
|
|
||||||
|
<p>Underscore: <code>\_</code></p>
|
||||||
|
|
||||||
|
<p>Left brace: <code>\{</code></p>
|
||||||
|
|
||||||
|
<p>Right brace: <code>\}</code></p>
|
||||||
|
|
||||||
|
<p>Left bracket: <code>\[</code></p>
|
||||||
|
|
||||||
|
<p>Right bracket: <code>\]</code></p>
|
||||||
|
|
||||||
|
<p>Left paren: <code>\(</code></p>
|
||||||
|
|
||||||
|
<p>Right paren: <code>\)</code></p>
|
||||||
|
|
||||||
|
<p>Greater-than: <code>\></code></p>
|
||||||
|
|
||||||
|
<p>Hash: <code>\#</code></p>
|
||||||
|
|
||||||
|
<p>Period: <code>\.</code></p>
|
||||||
|
|
||||||
|
<p>Bang: <code>\!</code></p>
|
||||||
|
|
||||||
|
<p>Plus: <code>\+</code></p>
|
||||||
|
|
||||||
|
<p>Minus: <code>\-</code></p>
|
||||||
|
|
||||||
|
<p>Tilde: <code>\~</code></p>
|
||||||
|
|
||||||
|
<p>These should get escaped, even though they're matching pairs for
|
||||||
|
other Markdown constructs:</p>
|
||||||
|
|
||||||
|
<p>*asterisks*</p>
|
||||||
|
|
||||||
|
<p>_underscores_</p>
|
||||||
|
|
||||||
|
<p>`backticks`</p>
|
||||||
|
|
||||||
|
<p>This is a code span with a literal backslash-backtick sequence: <code>\`</code></p>
|
||||||
|
|
||||||
|
<p>This is a tag with unescaped backticks <span attr='`ticks`'>bar</span>.</p>
|
||||||
|
|
||||||
|
<p>This is a tag with backslashes <span attr='\\backslashes\\'>bar</span>.</p>
|
126
vendor/github.com/russross/blackfriday/testdata/Backslash escapes.text
generated
vendored
100644
126
vendor/github.com/russross/blackfriday/testdata/Backslash escapes.text
generated
vendored
100644
|
@ -0,0 +1,126 @@
|
||||||
|
These should all get escaped:
|
||||||
|
|
||||||
|
Backslash: \\
|
||||||
|
|
||||||
|
Backtick: \`
|
||||||
|
|
||||||
|
Asterisk: \*
|
||||||
|
|
||||||
|
Underscore: \_
|
||||||
|
|
||||||
|
Left brace: \{
|
||||||
|
|
||||||
|
Right brace: \}
|
||||||
|
|
||||||
|
Left bracket: \[
|
||||||
|
|
||||||
|
Right bracket: \]
|
||||||
|
|
||||||
|
Left paren: \(
|
||||||
|
|
||||||
|
Right paren: \)
|
||||||
|
|
||||||
|
Greater-than: \>
|
||||||
|
|
||||||
|
Hash: \#
|
||||||
|
|
||||||
|
Period: \.
|
||||||
|
|
||||||
|
Bang: \!
|
||||||
|
|
||||||
|
Plus: \+
|
||||||
|
|
||||||
|
Minus: \-
|
||||||
|
|
||||||
|
Tilde: \~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
These should not, because they occur within a code block:
|
||||||
|
|
||||||
|
Backslash: \\
|
||||||
|
|
||||||
|
Backtick: \`
|
||||||
|
|
||||||
|
Asterisk: \*
|
||||||
|
|
||||||
|
Underscore: \_
|
||||||
|
|
||||||
|
Left brace: \{
|
||||||
|
|
||||||
|
Right brace: \}
|
||||||
|
|
||||||
|
Left bracket: \[
|
||||||
|
|
||||||
|
Right bracket: \]
|
||||||
|
|
||||||
|
Left paren: \(
|
||||||
|
|
||||||
|
Right paren: \)
|
||||||
|
|
||||||
|
Greater-than: \>
|
||||||
|
|
||||||
|
Hash: \#
|
||||||
|
|
||||||
|
Period: \.
|
||||||
|
|
||||||
|
Bang: \!
|
||||||
|
|
||||||
|
Plus: \+
|
||||||
|
|
||||||
|
Minus: \-
|
||||||
|
|
||||||
|
Tilde: \~
|
||||||
|
|
||||||
|
|
||||||
|
Nor should these, which occur in code spans:
|
||||||
|
|
||||||
|
Backslash: `\\`
|
||||||
|
|
||||||
|
Backtick: `` \` ``
|
||||||
|
|
||||||
|
Asterisk: `\*`
|
||||||
|
|
||||||
|
Underscore: `\_`
|
||||||
|
|
||||||
|
Left brace: `\{`
|
||||||
|
|
||||||
|
Right brace: `\}`
|
||||||
|
|
||||||
|
Left bracket: `\[`
|
||||||
|
|
||||||
|
Right bracket: `\]`
|
||||||
|
|
||||||
|
Left paren: `\(`
|
||||||
|
|
||||||
|
Right paren: `\)`
|
||||||
|
|
||||||
|
Greater-than: `\>`
|
||||||
|
|
||||||
|
Hash: `\#`
|
||||||
|
|
||||||
|
Period: `\.`
|
||||||
|
|
||||||
|
Bang: `\!`
|
||||||
|
|
||||||
|
Plus: `\+`
|
||||||
|
|
||||||
|
Minus: `\-`
|
||||||
|
|
||||||
|
Tilde: `\~`
|
||||||
|
|
||||||
|
|
||||||
|
These should get escaped, even though they're matching pairs for
|
||||||
|
other Markdown constructs:
|
||||||
|
|
||||||
|
\*asterisks\*
|
||||||
|
|
||||||
|
\_underscores\_
|
||||||
|
|
||||||
|
\`backticks\`
|
||||||
|
|
||||||
|
This is a code span with a literal backslash-backtick sequence: `` \` ``
|
||||||
|
|
||||||
|
This is a tag with unescaped backticks <span attr='`ticks`'>bar</span>.
|
||||||
|
|
||||||
|
This is a tag with backslashes <span attr='\\backslashes\\'>bar</span>.
|
15
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.html
generated
vendored
100644
15
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.html
generated
vendored
100644
|
@ -0,0 +1,15 @@
|
||||||
|
<blockquote>
|
||||||
|
<p>Example:</p>
|
||||||
|
|
||||||
|
<pre><code>sub status {
|
||||||
|
print "working";
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Or:</p>
|
||||||
|
|
||||||
|
<pre><code>sub status {
|
||||||
|
return "working";
|
||||||
|
}
|
||||||
|
</code></pre>
|
||||||
|
</blockquote>
|
11
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.text
generated
vendored
100644
11
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.text
generated
vendored
100644
|
@ -0,0 +1,11 @@
|
||||||
|
> Example:
|
||||||
|
>
|
||||||
|
> sub status {
|
||||||
|
> print "working";
|
||||||
|
> }
|
||||||
|
>
|
||||||
|
> Or:
|
||||||
|
>
|
||||||
|
> sub status {
|
||||||
|
> return "working";
|
||||||
|
> }
|
|
@ -0,0 +1,18 @@
|
||||||
|
<pre><code>code block on the first line
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Regular text.</p>
|
||||||
|
|
||||||
|
<pre><code>code block indented by spaces
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Regular text.</p>
|
||||||
|
|
||||||
|
<pre><code>the lines in this block
|
||||||
|
all contain trailing spaces
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Regular Text.</p>
|
||||||
|
|
||||||
|
<pre><code>code block on the last line
|
||||||
|
</code></pre>
|
|
@ -0,0 +1,14 @@
|
||||||
|
code block on the first line
|
||||||
|
|
||||||
|
Regular text.
|
||||||
|
|
||||||
|
code block indented by spaces
|
||||||
|
|
||||||
|
Regular text.
|
||||||
|
|
||||||
|
the lines in this block
|
||||||
|
all contain trailing spaces
|
||||||
|
|
||||||
|
Regular Text.
|
||||||
|
|
||||||
|
code block on the last line
|
|
@ -0,0 +1,5 @@
|
||||||
|
<p><code><test a="</code> content of attribute <code>"></code></p>
|
||||||
|
|
||||||
|
<p>Fix for backticks within HTML tag: <span attr='`ticks`'>like this</span></p>
|
||||||
|
|
||||||
|
<p>Here's how you put <code>`backticks`</code> in a code span.</p>
|
|
@ -0,0 +1,6 @@
|
||||||
|
`<test a="` content of attribute `">`
|
||||||
|
|
||||||
|
Fix for backticks within HTML tag: <span attr='`ticks`'>like this</span>
|
||||||
|
|
||||||
|
Here's how you put `` `backticks` `` in a code span.
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<p>In Markdown 1.0.0 and earlier. Version</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>This line turns into a list item.
|
||||||
|
Because a hard-wrapped line in the
|
||||||
|
middle of a paragraph looked like a
|
||||||
|
list item.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Here's one with a bullet.</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>criminey.</li>
|
||||||
|
</ul>
|
|
@ -0,0 +1,8 @@
|
||||||
|
In Markdown 1.0.0 and earlier. Version
|
||||||
|
8. This line turns into a list item.
|
||||||
|
Because a hard-wrapped line in the
|
||||||
|
middle of a paragraph looked like a
|
||||||
|
list item.
|
||||||
|
|
||||||
|
Here's one with a bullet.
|
||||||
|
* criminey.
|
8
vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines.html
generated
vendored
100644
8
vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines.html
generated
vendored
100644
|
@ -0,0 +1,8 @@
|
||||||
|
<p>In Markdown 1.0.0 and earlier. Version
|
||||||
|
8. This line turns into a list item.
|
||||||
|
Because a hard-wrapped line in the
|
||||||
|
middle of a paragraph looked like a
|
||||||
|
list item.</p>
|
||||||
|
|
||||||
|
<p>Here's one with a bullet.
|
||||||
|
* criminey.</p>
|
8
vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines.text
generated
vendored
100644
8
vendor/github.com/russross/blackfriday/testdata/Hard-wrapped paragraphs with list-like lines.text
generated
vendored
100644
|
@ -0,0 +1,8 @@
|
||||||
|
In Markdown 1.0.0 and earlier. Version
|
||||||
|
8. This line turns into a list item.
|
||||||
|
Because a hard-wrapped line in the
|
||||||
|
middle of a paragraph looked like a
|
||||||
|
list item.
|
||||||
|
|
||||||
|
Here's one with a bullet.
|
||||||
|
* criminey.
|
|
@ -0,0 +1,71 @@
|
||||||
|
<p>Dashes:</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<pre><code>---
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<pre><code>- - -
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Asterisks:</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<pre><code>***
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<pre><code>* * *
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Underscores:</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<pre><code>___
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<pre><code>_ _ _
|
||||||
|
</code></pre>
|
|
@ -0,0 +1,67 @@
|
||||||
|
Dashes:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- - -
|
||||||
|
|
||||||
|
- - -
|
||||||
|
|
||||||
|
- - -
|
||||||
|
|
||||||
|
- - -
|
||||||
|
|
||||||
|
- - -
|
||||||
|
|
||||||
|
|
||||||
|
Asterisks:
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
|
||||||
|
Underscores:
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
_ _ _
|
||||||
|
|
||||||
|
_ _ _
|
||||||
|
|
||||||
|
_ _ _
|
||||||
|
|
||||||
|
_ _ _
|
||||||
|
|
||||||
|
_ _ _
|
15
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Advanced).html
generated
vendored
100644
15
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Advanced).html
generated
vendored
100644
|
@ -0,0 +1,15 @@
|
||||||
|
<p>Simple block on one line:</p>
|
||||||
|
|
||||||
|
<div>foo</div>
|
||||||
|
|
||||||
|
<p>And nested without indentation:</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
<div style=">"/>
|
||||||
|
</div>
|
||||||
|
<div>bar</div>
|
||||||
|
</div>
|
15
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Advanced).text
generated
vendored
100644
15
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Advanced).text
generated
vendored
100644
|
@ -0,0 +1,15 @@
|
||||||
|
Simple block on one line:
|
||||||
|
|
||||||
|
<div>foo</div>
|
||||||
|
|
||||||
|
And nested without indentation:
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
<div style=">"/>
|
||||||
|
</div>
|
||||||
|
<div>bar</div>
|
||||||
|
</div>
|
72
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Simple).html
generated
vendored
100644
72
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Simple).html
generated
vendored
100644
|
@ -0,0 +1,72 @@
|
||||||
|
<p>Here's a simple block:</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>This should be a code block, though:</p>
|
||||||
|
|
||||||
|
<pre><code><div>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>As should this:</p>
|
||||||
|
|
||||||
|
<pre><code><div>foo</div>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Now, nested:</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>This should just be an HTML comment:</p>
|
||||||
|
|
||||||
|
<!-- Comment -->
|
||||||
|
|
||||||
|
<p>Multiline:</p>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Blah
|
||||||
|
Blah
|
||||||
|
-->
|
||||||
|
|
||||||
|
<p>Code block:</p>
|
||||||
|
|
||||||
|
<pre><code><!-- Comment -->
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Just plain comment, with trailing spaces on the line:</p>
|
||||||
|
|
||||||
|
<!-- foo -->
|
||||||
|
|
||||||
|
<p>Code:</p>
|
||||||
|
|
||||||
|
<pre><code><hr />
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Hr's:</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<hr class="foo" id="bar" />
|
||||||
|
|
||||||
|
<hr class="foo" id="bar"/>
|
||||||
|
|
||||||
|
<hr class="foo" id="bar" >
|
69
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Simple).text
generated
vendored
100644
69
vendor/github.com/russross/blackfriday/testdata/Inline HTML (Simple).text
generated
vendored
100644
|
@ -0,0 +1,69 @@
|
||||||
|
Here's a simple block:
|
||||||
|
|
||||||
|
<div>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
|
||||||
|
This should be a code block, though:
|
||||||
|
|
||||||
|
<div>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
|
||||||
|
As should this:
|
||||||
|
|
||||||
|
<div>foo</div>
|
||||||
|
|
||||||
|
Now, nested:
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
This should just be an HTML comment:
|
||||||
|
|
||||||
|
<!-- Comment -->
|
||||||
|
|
||||||
|
Multiline:
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Blah
|
||||||
|
Blah
|
||||||
|
-->
|
||||||
|
|
||||||
|
Code block:
|
||||||
|
|
||||||
|
<!-- Comment -->
|
||||||
|
|
||||||
|
Just plain comment, with trailing spaces on the line:
|
||||||
|
|
||||||
|
<!-- foo -->
|
||||||
|
|
||||||
|
Code:
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
Hr's:
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<hr class="foo" id="bar" />
|
||||||
|
|
||||||
|
<hr class="foo" id="bar"/>
|
||||||
|
|
||||||
|
<hr class="foo" id="bar" >
|
||||||
|
|
13
vendor/github.com/russross/blackfriday/testdata/Inline HTML comments.html
generated
vendored
100644
13
vendor/github.com/russross/blackfriday/testdata/Inline HTML comments.html
generated
vendored
100644
|
@ -0,0 +1,13 @@
|
||||||
|
<p>Paragraph one.</p>
|
||||||
|
|
||||||
|
<!-- This is a simple comment -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is another comment.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<p>Paragraph two.</p>
|
||||||
|
|
||||||
|
<!-- one comment block -- -- with two comments -->
|
||||||
|
|
||||||
|
<p>The end.</p>
|
13
vendor/github.com/russross/blackfriday/testdata/Inline HTML comments.text
generated
vendored
100644
13
vendor/github.com/russross/blackfriday/testdata/Inline HTML comments.text
generated
vendored
100644
|
@ -0,0 +1,13 @@
|
||||||
|
Paragraph one.
|
||||||
|
|
||||||
|
<!-- This is a simple comment -->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is another comment.
|
||||||
|
-->
|
||||||
|
|
||||||
|
Paragraph two.
|
||||||
|
|
||||||
|
<!-- one comment block -- -- with two comments -->
|
||||||
|
|
||||||
|
The end.
|
11
vendor/github.com/russross/blackfriday/testdata/Links, inline style.html
generated
vendored
100644
11
vendor/github.com/russross/blackfriday/testdata/Links, inline style.html
generated
vendored
100644
|
@ -0,0 +1,11 @@
|
||||||
|
<p>Just a <a href="/url/">URL</a>.</p>
|
||||||
|
|
||||||
|
<p><a href="/url/" title="title">URL and title</a>.</p>
|
||||||
|
|
||||||
|
<p><a href="/url/" title="title preceded by two spaces">URL and title</a>.</p>
|
||||||
|
|
||||||
|
<p><a href="/url/" title="title preceded by a tab">URL and title</a>.</p>
|
||||||
|
|
||||||
|
<p><a href="/url/" title="title has spaces afterward">URL and title</a>.</p>
|
||||||
|
|
||||||
|
<p>[Empty]().</p>
|
12
vendor/github.com/russross/blackfriday/testdata/Links, inline style.text
generated
vendored
100644
12
vendor/github.com/russross/blackfriday/testdata/Links, inline style.text
generated
vendored
100644
|
@ -0,0 +1,12 @@
|
||||||
|
Just a [URL](/url/).
|
||||||
|
|
||||||
|
[URL and title](/url/ "title").
|
||||||
|
|
||||||
|
[URL and title](/url/ "title preceded by two spaces").
|
||||||
|
|
||||||
|
[URL and title](/url/ "title preceded by a tab").
|
||||||
|
|
||||||
|
[URL and title](/url/ "title has spaces afterward" ).
|
||||||
|
|
||||||
|
|
||||||
|
[Empty]().
|
52
vendor/github.com/russross/blackfriday/testdata/Links, reference style.html
generated
vendored
100644
52
vendor/github.com/russross/blackfriday/testdata/Links, reference style.html
generated
vendored
100644
|
@ -0,0 +1,52 @@
|
||||||
|
<p>Foo <a href="/url/" title="Title">bar</a>.</p>
|
||||||
|
|
||||||
|
<p>Foo <a href="/url/" title="Title">bar</a>.</p>
|
||||||
|
|
||||||
|
<p>Foo <a href="/url/" title="Title">bar</a>.</p>
|
||||||
|
|
||||||
|
<p>With <a href="/url/">embedded [brackets]</a>.</p>
|
||||||
|
|
||||||
|
<p>Indented <a href="/url">once</a>.</p>
|
||||||
|
|
||||||
|
<p>Indented <a href="/url">twice</a>.</p>
|
||||||
|
|
||||||
|
<p>Indented <a href="/url">thrice</a>.</p>
|
||||||
|
|
||||||
|
<p>Indented [four][] times.</p>
|
||||||
|
|
||||||
|
<pre><code>[four]: /url
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p><a href="foo">this</a> should work</p>
|
||||||
|
|
||||||
|
<p>So should <a href="foo">this</a>.</p>
|
||||||
|
|
||||||
|
<p>And <a href="foo">this</a>.</p>
|
||||||
|
|
||||||
|
<p>And <a href="foo">this</a>.</p>
|
||||||
|
|
||||||
|
<p>And <a href="foo">this</a>.</p>
|
||||||
|
|
||||||
|
<p>But not [that] [].</p>
|
||||||
|
|
||||||
|
<p>Nor [that][].</p>
|
||||||
|
|
||||||
|
<p>Nor [that].</p>
|
||||||
|
|
||||||
|
<p>[Something in brackets like <a href="foo">this</a> should work]</p>
|
||||||
|
|
||||||
|
<p>[Same with <a href="foo">this</a>.]</p>
|
||||||
|
|
||||||
|
<p>In this case, <a href="/somethingelse/">this</a> points to something else.</p>
|
||||||
|
|
||||||
|
<p>Backslashing should suppress [this] and [this].</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p>Here's one where the <a href="/url/">link
|
||||||
|
breaks</a> across lines.</p>
|
||||||
|
|
||||||
|
<p>Here's another where the <a href="/url/">link
|
||||||
|
breaks</a> across lines, but with a line-ending space.</p>
|
71
vendor/github.com/russross/blackfriday/testdata/Links, reference style.text
generated
vendored
100644
71
vendor/github.com/russross/blackfriday/testdata/Links, reference style.text
generated
vendored
100644
|
@ -0,0 +1,71 @@
|
||||||
|
Foo [bar] [1].
|
||||||
|
|
||||||
|
Foo [bar][1].
|
||||||
|
|
||||||
|
Foo [bar]
|
||||||
|
[1].
|
||||||
|
|
||||||
|
[1]: /url/ "Title"
|
||||||
|
|
||||||
|
|
||||||
|
With [embedded [brackets]] [b].
|
||||||
|
|
||||||
|
|
||||||
|
Indented [once][].
|
||||||
|
|
||||||
|
Indented [twice][].
|
||||||
|
|
||||||
|
Indented [thrice][].
|
||||||
|
|
||||||
|
Indented [four][] times.
|
||||||
|
|
||||||
|
[once]: /url
|
||||||
|
|
||||||
|
[twice]: /url
|
||||||
|
|
||||||
|
[thrice]: /url
|
||||||
|
|
||||||
|
[four]: /url
|
||||||
|
|
||||||
|
|
||||||
|
[b]: /url/
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
[this] [this] should work
|
||||||
|
|
||||||
|
So should [this][this].
|
||||||
|
|
||||||
|
And [this] [].
|
||||||
|
|
||||||
|
And [this][].
|
||||||
|
|
||||||
|
And [this].
|
||||||
|
|
||||||
|
But not [that] [].
|
||||||
|
|
||||||
|
Nor [that][].
|
||||||
|
|
||||||
|
Nor [that].
|
||||||
|
|
||||||
|
[Something in brackets like [this][] should work]
|
||||||
|
|
||||||
|
[Same with [this].]
|
||||||
|
|
||||||
|
In this case, [this](/somethingelse/) points to something else.
|
||||||
|
|
||||||
|
Backslashing should suppress \[this] and [this\].
|
||||||
|
|
||||||
|
[this]: foo
|
||||||
|
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
Here's one where the [link
|
||||||
|
breaks] across lines.
|
||||||
|
|
||||||
|
Here's another where the [link
|
||||||
|
breaks] across lines, but with a line-ending space.
|
||||||
|
|
||||||
|
|
||||||
|
[link breaks]: /url/
|
9
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.html
generated
vendored
100644
9
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.html
generated
vendored
100644
|
@ -0,0 +1,9 @@
|
||||||
|
<p>This is the <a href="/simple">simple case</a>.</p>
|
||||||
|
|
||||||
|
<p>This one has a <a href="/foo">line
|
||||||
|
break</a>.</p>
|
||||||
|
|
||||||
|
<p>This one has a <a href="/foo">line
|
||||||
|
break</a> with a line-ending space.</p>
|
||||||
|
|
||||||
|
<p><a href="/that">this</a> and the <a href="/other">other</a></p>
|
20
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.text
generated
vendored
100644
20
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.text
generated
vendored
100644
|
@ -0,0 +1,20 @@
|
||||||
|
This is the [simple case].
|
||||||
|
|
||||||
|
[simple case]: /simple
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This one has a [line
|
||||||
|
break].
|
||||||
|
|
||||||
|
This one has a [line
|
||||||
|
break] with a line-ending space.
|
||||||
|
|
||||||
|
[line break]: /foo
|
||||||
|
|
||||||
|
|
||||||
|
[this] [that] and the [other]
|
||||||
|
|
||||||
|
[this]: /this
|
||||||
|
[that]: /that
|
||||||
|
[other]: /other
|
3
vendor/github.com/russross/blackfriday/testdata/Literal quotes in titles.html
generated
vendored
100644
3
vendor/github.com/russross/blackfriday/testdata/Literal quotes in titles.html
generated
vendored
100644
|
@ -0,0 +1,3 @@
|
||||||
|
<p>Foo <a href="/url/" title="Title with "quotes" inside">bar</a>.</p>
|
||||||
|
|
||||||
|
<p>Foo <a href="/url/" title="Title with "quotes" inside">bar</a>.</p>
|
7
vendor/github.com/russross/blackfriday/testdata/Literal quotes in titles.text
generated
vendored
100644
7
vendor/github.com/russross/blackfriday/testdata/Literal quotes in titles.text
generated
vendored
100644
|
@ -0,0 +1,7 @@
|
||||||
|
Foo [bar][].
|
||||||
|
|
||||||
|
Foo [bar](/url/ "Title with "quotes" inside").
|
||||||
|
|
||||||
|
|
||||||
|
[bar]: /url/ "Title with "quotes" inside"
|
||||||
|
|
314
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.html
generated
vendored
100644
314
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.html
generated
vendored
100644
|
@ -0,0 +1,314 @@
|
||||||
|
<h1>Markdown: Basics</h1>
|
||||||
|
|
||||||
|
<ul id="ProjectSubmenu">
|
||||||
|
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
|
||||||
|
<li><a class="selected" title="Markdown Basics">Basics</a></li>
|
||||||
|
<li><a href="/projects/markdown/syntax" title="Markdown Syntax Documentation">Syntax</a></li>
|
||||||
|
<li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
|
||||||
|
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Getting the Gist of Markdown's Formatting Syntax</h2>
|
||||||
|
|
||||||
|
<p>This page offers a brief overview of what it's like to use Markdown.
|
||||||
|
The <a href="/projects/markdown/syntax" title="Markdown Syntax">syntax page</a> provides complete, detailed documentation for
|
||||||
|
every feature, but Markdown should be very easy to pick up simply by
|
||||||
|
looking at a few examples of it in action. The examples on this page
|
||||||
|
are written in a before/after style, showing example syntax and the
|
||||||
|
HTML output produced by Markdown.</p>
|
||||||
|
|
||||||
|
<p>It's also helpful to simply try Markdown out; the <a href="/projects/markdown/dingus" title="Markdown Dingus">Dingus</a> is a
|
||||||
|
web application that allows you type your own Markdown-formatted text
|
||||||
|
and translate it to XHTML.</p>
|
||||||
|
|
||||||
|
<p><strong>Note:</strong> This document is itself written using Markdown; you
|
||||||
|
can <a href="/projects/markdown/basics.text">see the source for it by adding '.text' to the URL</a>.</p>
|
||||||
|
|
||||||
|
<h2>Paragraphs, Headers, Blockquotes</h2>
|
||||||
|
|
||||||
|
<p>A paragraph is simply one or more consecutive lines of text, separated
|
||||||
|
by one or more blank lines. (A blank line is any line that looks like a
|
||||||
|
blank line -- a line containing nothing spaces or tabs is considered
|
||||||
|
blank.) Normal paragraphs should not be intended with spaces or tabs.</p>
|
||||||
|
|
||||||
|
<p>Markdown offers two styles of headers: <em>Setext</em> and <em>atx</em>.
|
||||||
|
Setext-style headers for <code><h1></code> and <code><h2></code> are created by
|
||||||
|
"underlining" with equal signs (<code>=</code>) and hyphens (<code>-</code>), respectively.
|
||||||
|
To create an atx-style header, you put 1-6 hash marks (<code>#</code>) at the
|
||||||
|
beginning of the line -- the number of hashes equals the resulting
|
||||||
|
HTML header level.</p>
|
||||||
|
|
||||||
|
<p>Blockquotes are indicated using email-style '<code>></code>' angle brackets.</p>
|
||||||
|
|
||||||
|
<p>Markdown:</p>
|
||||||
|
|
||||||
|
<pre><code>A First Level Header
|
||||||
|
====================
|
||||||
|
|
||||||
|
A Second Level Header
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Now is the time for all good men to come to
|
||||||
|
the aid of their country. This is just a
|
||||||
|
regular paragraph.
|
||||||
|
|
||||||
|
The quick brown fox jumped over the lazy
|
||||||
|
dog's back.
|
||||||
|
|
||||||
|
### Header 3
|
||||||
|
|
||||||
|
> This is a blockquote.
|
||||||
|
>
|
||||||
|
> This is the second paragraph in the blockquote.
|
||||||
|
>
|
||||||
|
> ## This is an H2 in a blockquote
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><h1>A First Level Header</h1>
|
||||||
|
|
||||||
|
<h2>A Second Level Header</h2>
|
||||||
|
|
||||||
|
<p>Now is the time for all good men to come to
|
||||||
|
the aid of their country. This is just a
|
||||||
|
regular paragraph.</p>
|
||||||
|
|
||||||
|
<p>The quick brown fox jumped over the lazy
|
||||||
|
dog's back.</p>
|
||||||
|
|
||||||
|
<h3>Header 3</h3>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a blockquote.</p>
|
||||||
|
|
||||||
|
<p>This is the second paragraph in the blockquote.</p>
|
||||||
|
|
||||||
|
<h2>This is an H2 in a blockquote</h2>
|
||||||
|
</blockquote>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3>Phrase Emphasis</h3>
|
||||||
|
|
||||||
|
<p>Markdown uses asterisks and underscores to indicate spans of emphasis.</p>
|
||||||
|
|
||||||
|
<p>Markdown:</p>
|
||||||
|
|
||||||
|
<pre><code>Some of these words *are emphasized*.
|
||||||
|
Some of these words _are emphasized also_.
|
||||||
|
|
||||||
|
Use two asterisks for **strong emphasis**.
|
||||||
|
Or, if you prefer, __use two underscores instead__.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><p>Some of these words <em>are emphasized</em>.
|
||||||
|
Some of these words <em>are emphasized also</em>.</p>
|
||||||
|
|
||||||
|
<p>Use two asterisks for <strong>strong emphasis</strong>.
|
||||||
|
Or, if you prefer, <strong>use two underscores instead</strong>.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h2>Lists</h2>
|
||||||
|
|
||||||
|
<p>Unordered (bulleted) lists use asterisks, pluses, and hyphens (<code>*</code>,
|
||||||
|
<code>+</code>, and <code>-</code>) as list markers. These three markers are
|
||||||
|
interchangable; this:</p>
|
||||||
|
|
||||||
|
<pre><code>* Candy.
|
||||||
|
* Gum.
|
||||||
|
* Booze.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>this:</p>
|
||||||
|
|
||||||
|
<pre><code>+ Candy.
|
||||||
|
+ Gum.
|
||||||
|
+ Booze.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>and this:</p>
|
||||||
|
|
||||||
|
<pre><code>- Candy.
|
||||||
|
- Gum.
|
||||||
|
- Booze.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>all produce the same output:</p>
|
||||||
|
|
||||||
|
<pre><code><ul>
|
||||||
|
<li>Candy.</li>
|
||||||
|
<li>Gum.</li>
|
||||||
|
<li>Booze.</li>
|
||||||
|
</ul>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Ordered (numbered) lists use regular numbers, followed by periods, as
|
||||||
|
list markers:</p>
|
||||||
|
|
||||||
|
<pre><code>1. Red
|
||||||
|
2. Green
|
||||||
|
3. Blue
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><ol>
|
||||||
|
<li>Red</li>
|
||||||
|
<li>Green</li>
|
||||||
|
<li>Blue</li>
|
||||||
|
</ol>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>If you put blank lines between items, you'll get <code><p></code> tags for the
|
||||||
|
list item text. You can create multi-paragraph list items by indenting
|
||||||
|
the paragraphs by 4 spaces or 1 tab:</p>
|
||||||
|
|
||||||
|
<pre><code>* A list item.
|
||||||
|
|
||||||
|
With multiple paragraphs.
|
||||||
|
|
||||||
|
* Another item in the list.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><ul>
|
||||||
|
<li><p>A list item.</p>
|
||||||
|
<p>With multiple paragraphs.</p></li>
|
||||||
|
<li><p>Another item in the list.</p></li>
|
||||||
|
</ul>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3>Links</h3>
|
||||||
|
|
||||||
|
<p>Markdown supports two styles for creating links: <em>inline</em> and
|
||||||
|
<em>reference</em>. With both styles, you use square brackets to delimit the
|
||||||
|
text you want to turn into a link.</p>
|
||||||
|
|
||||||
|
<p>Inline-style links use parentheses immediately after the link text.
|
||||||
|
For example:</p>
|
||||||
|
|
||||||
|
<pre><code>This is an [example link](http://example.com/).
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><p>This is an <a href="http://example.com/">
|
||||||
|
example link</a>.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Optionally, you may include a title attribute in the parentheses:</p>
|
||||||
|
|
||||||
|
<pre><code>This is an [example link](http://example.com/ "With a Title").
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><p>This is an <a href="http://example.com/" title="With a Title">
|
||||||
|
example link</a>.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Reference-style links allow you to refer to your links by names, which
|
||||||
|
you define elsewhere in your document:</p>
|
||||||
|
|
||||||
|
<pre><code>I get 10 times more traffic from [Google][1] than from
|
||||||
|
[Yahoo][2] or [MSN][3].
|
||||||
|
|
||||||
|
[1]: http://google.com/ "Google"
|
||||||
|
[2]: http://search.yahoo.com/ "Yahoo Search"
|
||||||
|
[3]: http://search.msn.com/ "MSN Search"
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><p>I get 10 times more traffic from <a href="http://google.com/"
|
||||||
|
title="Google">Google</a> than from <a href="http://search.yahoo.com/"
|
||||||
|
title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/"
|
||||||
|
title="MSN Search">MSN</a>.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>The title attribute is optional. Link names may contain letters,
|
||||||
|
numbers and spaces, but are <em>not</em> case sensitive:</p>
|
||||||
|
|
||||||
|
<pre><code>I start my morning with a cup of coffee and
|
||||||
|
[The New York Times][NY Times].
|
||||||
|
|
||||||
|
[ny times]: http://www.nytimes.com/
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><p>I start my morning with a cup of coffee and
|
||||||
|
<a href="http://www.nytimes.com/">The New York Times</a>.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3>Images</h3>
|
||||||
|
|
||||||
|
<p>Image syntax is very much like link syntax.</p>
|
||||||
|
|
||||||
|
<p>Inline (titles are optional):</p>
|
||||||
|
|
||||||
|
<pre><code>![alt text](/path/to/img.jpg "Title")
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Reference-style:</p>
|
||||||
|
|
||||||
|
<pre><code>![alt text][id]
|
||||||
|
|
||||||
|
[id]: /path/to/img.jpg "Title"
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Both of the above examples produce the same output:</p>
|
||||||
|
|
||||||
|
<pre><code><img src="/path/to/img.jpg" alt="alt text" title="Title" />
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3>Code</h3>
|
||||||
|
|
||||||
|
<p>In a regular paragraph, you can create code span by wrapping text in
|
||||||
|
backtick quotes. Any ampersands (<code>&</code>) and angle brackets (<code><</code> or
|
||||||
|
<code>></code>) will automatically be translated into HTML entities. This makes
|
||||||
|
it easy to use Markdown to write about HTML example code:</p>
|
||||||
|
|
||||||
|
<pre><code>I strongly recommend against using any `<blink>` tags.
|
||||||
|
|
||||||
|
I wish SmartyPants used named entities like `&mdash;`
|
||||||
|
instead of decimal-encoded entites like `&#8212;`.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><p>I strongly recommend against using any
|
||||||
|
<code>&lt;blink&gt;</code> tags.</p>
|
||||||
|
|
||||||
|
<p>I wish SmartyPants used named entities like
|
||||||
|
<code>&amp;mdash;</code> instead of decimal-encoded
|
||||||
|
entites like <code>&amp;#8212;</code>.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>To specify an entire block of pre-formatted code, indent every line of
|
||||||
|
the block by 4 spaces or 1 tab. Just like with code spans, <code>&</code>, <code><</code>,
|
||||||
|
and <code>></code> characters will be escaped automatically.</p>
|
||||||
|
|
||||||
|
<p>Markdown:</p>
|
||||||
|
|
||||||
|
<pre><code>If you want your page to validate under XHTML 1.0 Strict,
|
||||||
|
you've got to put paragraph tags in your blockquotes:
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>For example.</p>
|
||||||
|
</blockquote>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Output:</p>
|
||||||
|
|
||||||
|
<pre><code><p>If you want your page to validate under XHTML 1.0 Strict,
|
||||||
|
you've got to put paragraph tags in your blockquotes:</p>
|
||||||
|
|
||||||
|
<pre><code>&lt;blockquote&gt;
|
||||||
|
&lt;p&gt;For example.&lt;/p&gt;
|
||||||
|
&lt;/blockquote&gt;
|
||||||
|
</code></pre>
|
||||||
|
</code></pre>
|
306
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.text
generated
vendored
100644
306
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.text
generated
vendored
100644
|
@ -0,0 +1,306 @@
|
||||||
|
Markdown: Basics
|
||||||
|
================
|
||||||
|
|
||||||
|
<ul id="ProjectSubmenu">
|
||||||
|
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
|
||||||
|
<li><a class="selected" title="Markdown Basics">Basics</a></li>
|
||||||
|
<li><a href="/projects/markdown/syntax" title="Markdown Syntax Documentation">Syntax</a></li>
|
||||||
|
<li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
|
||||||
|
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
Getting the Gist of Markdown's Formatting Syntax
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
|
This page offers a brief overview of what it's like to use Markdown.
|
||||||
|
The [syntax page] [s] provides complete, detailed documentation for
|
||||||
|
every feature, but Markdown should be very easy to pick up simply by
|
||||||
|
looking at a few examples of it in action. The examples on this page
|
||||||
|
are written in a before/after style, showing example syntax and the
|
||||||
|
HTML output produced by Markdown.
|
||||||
|
|
||||||
|
It's also helpful to simply try Markdown out; the [Dingus] [d] is a
|
||||||
|
web application that allows you type your own Markdown-formatted text
|
||||||
|
and translate it to XHTML.
|
||||||
|
|
||||||
|
**Note:** This document is itself written using Markdown; you
|
||||||
|
can [see the source for it by adding '.text' to the URL] [src].
|
||||||
|
|
||||||
|
[s]: /projects/markdown/syntax "Markdown Syntax"
|
||||||
|
[d]: /projects/markdown/dingus "Markdown Dingus"
|
||||||
|
[src]: /projects/markdown/basics.text
|
||||||
|
|
||||||
|
|
||||||
|
## Paragraphs, Headers, Blockquotes ##
|
||||||
|
|
||||||
|
A paragraph is simply one or more consecutive lines of text, separated
|
||||||
|
by one or more blank lines. (A blank line is any line that looks like a
|
||||||
|
blank line -- a line containing nothing spaces or tabs is considered
|
||||||
|
blank.) Normal paragraphs should not be intended with spaces or tabs.
|
||||||
|
|
||||||
|
Markdown offers two styles of headers: *Setext* and *atx*.
|
||||||
|
Setext-style headers for `<h1>` and `<h2>` are created by
|
||||||
|
"underlining" with equal signs (`=`) and hyphens (`-`), respectively.
|
||||||
|
To create an atx-style header, you put 1-6 hash marks (`#`) at the
|
||||||
|
beginning of the line -- the number of hashes equals the resulting
|
||||||
|
HTML header level.
|
||||||
|
|
||||||
|
Blockquotes are indicated using email-style '`>`' angle brackets.
|
||||||
|
|
||||||
|
Markdown:
|
||||||
|
|
||||||
|
A First Level Header
|
||||||
|
====================
|
||||||
|
|
||||||
|
A Second Level Header
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Now is the time for all good men to come to
|
||||||
|
the aid of their country. This is just a
|
||||||
|
regular paragraph.
|
||||||
|
|
||||||
|
The quick brown fox jumped over the lazy
|
||||||
|
dog's back.
|
||||||
|
|
||||||
|
### Header 3
|
||||||
|
|
||||||
|
> This is a blockquote.
|
||||||
|
>
|
||||||
|
> This is the second paragraph in the blockquote.
|
||||||
|
>
|
||||||
|
> ## This is an H2 in a blockquote
|
||||||
|
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<h1>A First Level Header</h1>
|
||||||
|
|
||||||
|
<h2>A Second Level Header</h2>
|
||||||
|
|
||||||
|
<p>Now is the time for all good men to come to
|
||||||
|
the aid of their country. This is just a
|
||||||
|
regular paragraph.</p>
|
||||||
|
|
||||||
|
<p>The quick brown fox jumped over the lazy
|
||||||
|
dog's back.</p>
|
||||||
|
|
||||||
|
<h3>Header 3</h3>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a blockquote.</p>
|
||||||
|
|
||||||
|
<p>This is the second paragraph in the blockquote.</p>
|
||||||
|
|
||||||
|
<h2>This is an H2 in a blockquote</h2>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Phrase Emphasis ###
|
||||||
|
|
||||||
|
Markdown uses asterisks and underscores to indicate spans of emphasis.
|
||||||
|
|
||||||
|
Markdown:
|
||||||
|
|
||||||
|
Some of these words *are emphasized*.
|
||||||
|
Some of these words _are emphasized also_.
|
||||||
|
|
||||||
|
Use two asterisks for **strong emphasis**.
|
||||||
|
Or, if you prefer, __use two underscores instead__.
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<p>Some of these words <em>are emphasized</em>.
|
||||||
|
Some of these words <em>are emphasized also</em>.</p>
|
||||||
|
|
||||||
|
<p>Use two asterisks for <strong>strong emphasis</strong>.
|
||||||
|
Or, if you prefer, <strong>use two underscores instead</strong>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Lists ##
|
||||||
|
|
||||||
|
Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`,
|
||||||
|
`+`, and `-`) as list markers. These three markers are
|
||||||
|
interchangable; this:
|
||||||
|
|
||||||
|
* Candy.
|
||||||
|
* Gum.
|
||||||
|
* Booze.
|
||||||
|
|
||||||
|
this:
|
||||||
|
|
||||||
|
+ Candy.
|
||||||
|
+ Gum.
|
||||||
|
+ Booze.
|
||||||
|
|
||||||
|
and this:
|
||||||
|
|
||||||
|
- Candy.
|
||||||
|
- Gum.
|
||||||
|
- Booze.
|
||||||
|
|
||||||
|
all produce the same output:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Candy.</li>
|
||||||
|
<li>Gum.</li>
|
||||||
|
<li>Booze.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Ordered (numbered) lists use regular numbers, followed by periods, as
|
||||||
|
list markers:
|
||||||
|
|
||||||
|
1. Red
|
||||||
|
2. Green
|
||||||
|
3. Blue
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Red</li>
|
||||||
|
<li>Green</li>
|
||||||
|
<li>Blue</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
If you put blank lines between items, you'll get `<p>` tags for the
|
||||||
|
list item text. You can create multi-paragraph list items by indenting
|
||||||
|
the paragraphs by 4 spaces or 1 tab:
|
||||||
|
|
||||||
|
* A list item.
|
||||||
|
|
||||||
|
With multiple paragraphs.
|
||||||
|
|
||||||
|
* Another item in the list.
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><p>A list item.</p>
|
||||||
|
<p>With multiple paragraphs.</p></li>
|
||||||
|
<li><p>Another item in the list.</p></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Links ###
|
||||||
|
|
||||||
|
Markdown supports two styles for creating links: *inline* and
|
||||||
|
*reference*. With both styles, you use square brackets to delimit the
|
||||||
|
text you want to turn into a link.
|
||||||
|
|
||||||
|
Inline-style links use parentheses immediately after the link text.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
This is an [example link](http://example.com/).
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<p>This is an <a href="http://example.com/">
|
||||||
|
example link</a>.</p>
|
||||||
|
|
||||||
|
Optionally, you may include a title attribute in the parentheses:
|
||||||
|
|
||||||
|
This is an [example link](http://example.com/ "With a Title").
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<p>This is an <a href="http://example.com/" title="With a Title">
|
||||||
|
example link</a>.</p>
|
||||||
|
|
||||||
|
Reference-style links allow you to refer to your links by names, which
|
||||||
|
you define elsewhere in your document:
|
||||||
|
|
||||||
|
I get 10 times more traffic from [Google][1] than from
|
||||||
|
[Yahoo][2] or [MSN][3].
|
||||||
|
|
||||||
|
[1]: http://google.com/ "Google"
|
||||||
|
[2]: http://search.yahoo.com/ "Yahoo Search"
|
||||||
|
[3]: http://search.msn.com/ "MSN Search"
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<p>I get 10 times more traffic from <a href="http://google.com/"
|
||||||
|
title="Google">Google</a> than from <a href="http://search.yahoo.com/"
|
||||||
|
title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/"
|
||||||
|
title="MSN Search">MSN</a>.</p>
|
||||||
|
|
||||||
|
The title attribute is optional. Link names may contain letters,
|
||||||
|
numbers and spaces, but are *not* case sensitive:
|
||||||
|
|
||||||
|
I start my morning with a cup of coffee and
|
||||||
|
[The New York Times][NY Times].
|
||||||
|
|
||||||
|
[ny times]: http://www.nytimes.com/
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<p>I start my morning with a cup of coffee and
|
||||||
|
<a href="http://www.nytimes.com/">The New York Times</a>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
### Images ###
|
||||||
|
|
||||||
|
Image syntax is very much like link syntax.
|
||||||
|
|
||||||
|
Inline (titles are optional):
|
||||||
|
|
||||||
|
![alt text](/path/to/img.jpg "Title")
|
||||||
|
|
||||||
|
Reference-style:
|
||||||
|
|
||||||
|
![alt text][id]
|
||||||
|
|
||||||
|
[id]: /path/to/img.jpg "Title"
|
||||||
|
|
||||||
|
Both of the above examples produce the same output:
|
||||||
|
|
||||||
|
<img src="/path/to/img.jpg" alt="alt text" title="Title" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Code ###
|
||||||
|
|
||||||
|
In a regular paragraph, you can create code span by wrapping text in
|
||||||
|
backtick quotes. Any ampersands (`&`) and angle brackets (`<` or
|
||||||
|
`>`) will automatically be translated into HTML entities. This makes
|
||||||
|
it easy to use Markdown to write about HTML example code:
|
||||||
|
|
||||||
|
I strongly recommend against using any `<blink>` tags.
|
||||||
|
|
||||||
|
I wish SmartyPants used named entities like `—`
|
||||||
|
instead of decimal-encoded entites like `—`.
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<p>I strongly recommend against using any
|
||||||
|
<code><blink></code> tags.</p>
|
||||||
|
|
||||||
|
<p>I wish SmartyPants used named entities like
|
||||||
|
<code>&mdash;</code> instead of decimal-encoded
|
||||||
|
entites like <code>&#8212;</code>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
To specify an entire block of pre-formatted code, indent every line of
|
||||||
|
the block by 4 spaces or 1 tab. Just like with code spans, `&`, `<`,
|
||||||
|
and `>` characters will be escaped automatically.
|
||||||
|
|
||||||
|
Markdown:
|
||||||
|
|
||||||
|
If you want your page to validate under XHTML 1.0 Strict,
|
||||||
|
you've got to put paragraph tags in your blockquotes:
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>For example.</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
<p>If you want your page to validate under XHTML 1.0 Strict,
|
||||||
|
you've got to put paragraph tags in your blockquotes:</p>
|
||||||
|
|
||||||
|
<pre><code><blockquote>
|
||||||
|
<p>For example.</p>
|
||||||
|
</blockquote>
|
||||||
|
</code></pre>
|
946
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.html
generated
vendored
100644
946
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.html
generated
vendored
100644
|
@ -0,0 +1,946 @@
|
||||||
|
<h1>Markdown: Syntax</h1>
|
||||||
|
|
||||||
|
<ul id="ProjectSubmenu">
|
||||||
|
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
|
||||||
|
<li><a href="/projects/markdown/basics" title="Markdown Basics">Basics</a></li>
|
||||||
|
<li><a class="selected" title="Markdown Syntax Documentation">Syntax</a></li>
|
||||||
|
<li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
|
||||||
|
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#overview">Overview</a>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#philosophy">Philosophy</a></li>
|
||||||
|
<li><a href="#html">Inline HTML</a></li>
|
||||||
|
<li><a href="#autoescape">Automatic Escaping for Special Characters</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#block">Block Elements</a>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#p">Paragraphs and Line Breaks</a></li>
|
||||||
|
<li><a href="#header">Headers</a></li>
|
||||||
|
<li><a href="#blockquote">Blockquotes</a></li>
|
||||||
|
<li><a href="#list">Lists</a></li>
|
||||||
|
<li><a href="#precode">Code Blocks</a></li>
|
||||||
|
<li><a href="#hr">Horizontal Rules</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#span">Span Elements</a>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#link">Links</a></li>
|
||||||
|
<li><a href="#em">Emphasis</a></li>
|
||||||
|
<li><a href="#code">Code</a></li>
|
||||||
|
<li><a href="#img">Images</a></li>
|
||||||
|
</ul></li>
|
||||||
|
<li><a href="#misc">Miscellaneous</a>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#backslash">Backslash Escapes</a></li>
|
||||||
|
<li><a href="#autolink">Automatic Links</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Note:</strong> This document is itself written using Markdown; you
|
||||||
|
can <a href="/projects/markdown/syntax.text">see the source for it by adding '.text' to the URL</a>.</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2 id="overview">Overview</h2>
|
||||||
|
|
||||||
|
<h3 id="philosophy">Philosophy</h3>
|
||||||
|
|
||||||
|
<p>Markdown is intended to be as easy-to-read and easy-to-write as is feasible.</p>
|
||||||
|
|
||||||
|
<p>Readability, however, is emphasized above all else. A Markdown-formatted
|
||||||
|
document should be publishable as-is, as plain text, without looking
|
||||||
|
like it's been marked up with tags or formatting instructions. While
|
||||||
|
Markdown's syntax has been influenced by several existing text-to-HTML
|
||||||
|
filters -- including <a href="http://docutils.sourceforge.net/mirror/setext.html">Setext</a>, <a href="http://www.aaronsw.com/2002/atx/">atx</a>, <a href="http://textism.com/tools/textile/">Textile</a>, <a href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>,
|
||||||
|
<a href="http://www.triptico.com/software/grutatxt.html">Grutatext</a>, and <a href="http://ettext.taint.org/doc/">EtText</a> -- the single biggest source of
|
||||||
|
inspiration for Markdown's syntax is the format of plain text email.</p>
|
||||||
|
|
||||||
|
<p>To this end, Markdown's syntax is comprised entirely of punctuation
|
||||||
|
characters, which punctuation characters have been carefully chosen so
|
||||||
|
as to look like what they mean. E.g., asterisks around a word actually
|
||||||
|
look like *emphasis*. Markdown lists look like, well, lists. Even
|
||||||
|
blockquotes look like quoted passages of text, assuming you've ever
|
||||||
|
used email.</p>
|
||||||
|
|
||||||
|
<h3 id="html">Inline HTML</h3>
|
||||||
|
|
||||||
|
<p>Markdown's syntax is intended for one purpose: to be used as a
|
||||||
|
format for <em>writing</em> for the web.</p>
|
||||||
|
|
||||||
|
<p>Markdown is not a replacement for HTML, or even close to it. Its
|
||||||
|
syntax is very small, corresponding only to a very small subset of
|
||||||
|
HTML tags. The idea is <em>not</em> to create a syntax that makes it easier
|
||||||
|
to insert HTML tags. In my opinion, HTML tags are already easy to
|
||||||
|
insert. The idea for Markdown is to make it easy to read, write, and
|
||||||
|
edit prose. HTML is a <em>publishing</em> format; Markdown is a <em>writing</em>
|
||||||
|
format. Thus, Markdown's formatting syntax only addresses issues that
|
||||||
|
can be conveyed in plain text.</p>
|
||||||
|
|
||||||
|
<p>For any markup that is not covered by Markdown's syntax, you simply
|
||||||
|
use HTML itself. There's no need to preface it or delimit it to
|
||||||
|
indicate that you're switching from Markdown to HTML; you just use
|
||||||
|
the tags.</p>
|
||||||
|
|
||||||
|
<p>The only restrictions are that block-level HTML elements -- e.g. <code><div></code>,
|
||||||
|
<code><table></code>, <code><pre></code>, <code><p></code>, etc. -- must be separated from surrounding
|
||||||
|
content by blank lines, and the start and end tags of the block should
|
||||||
|
not be indented with tabs or spaces. Markdown is smart enough not
|
||||||
|
to add extra (unwanted) <code><p></code> tags around HTML block-level tags.</p>
|
||||||
|
|
||||||
|
<p>For example, to add an HTML table to a Markdown article:</p>
|
||||||
|
|
||||||
|
<pre><code>This is a regular paragraph.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Foo</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
This is another regular paragraph.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Note that Markdown formatting syntax is not processed within block-level
|
||||||
|
HTML tags. E.g., you can't use Markdown-style <code>*emphasis*</code> inside an
|
||||||
|
HTML block.</p>
|
||||||
|
|
||||||
|
<p>Span-level HTML tags -- e.g. <code><span></code>, <code><cite></code>, or <code><del></code> -- can be
|
||||||
|
used anywhere in a Markdown paragraph, list item, or header. If you
|
||||||
|
want, you can even use HTML tags instead of Markdown formatting; e.g. if
|
||||||
|
you'd prefer to use HTML <code><a></code> or <code><img></code> tags instead of Markdown's
|
||||||
|
link or image syntax, go right ahead.</p>
|
||||||
|
|
||||||
|
<p>Unlike block-level HTML tags, Markdown syntax <em>is</em> processed within
|
||||||
|
span-level tags.</p>
|
||||||
|
|
||||||
|
<h3 id="autoescape">Automatic Escaping for Special Characters</h3>
|
||||||
|
|
||||||
|
<p>In HTML, there are two characters that demand special treatment: <code><</code>
|
||||||
|
and <code>&</code>. Left angle brackets are used to start tags; ampersands are
|
||||||
|
used to denote HTML entities. If you want to use them as literal
|
||||||
|
characters, you must escape them as entities, e.g. <code>&lt;</code>, and
|
||||||
|
<code>&amp;</code>.</p>
|
||||||
|
|
||||||
|
<p>Ampersands in particular are bedeviling for web writers. If you want to
|
||||||
|
write about 'AT&T', you need to write '<code>AT&amp;T</code>'. You even need to
|
||||||
|
escape ampersands within URLs. Thus, if you want to link to:</p>
|
||||||
|
|
||||||
|
<pre><code>http://images.google.com/images?num=30&q=larry+bird
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>you need to encode the URL as:</p>
|
||||||
|
|
||||||
|
<pre><code>http://images.google.com/images?num=30&amp;q=larry+bird
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>in your anchor tag <code>href</code> attribute. Needless to say, this is easy to
|
||||||
|
forget, and is probably the single most common source of HTML validation
|
||||||
|
errors in otherwise well-marked-up web sites.</p>
|
||||||
|
|
||||||
|
<p>Markdown allows you to use these characters naturally, taking care of
|
||||||
|
all the necessary escaping for you. If you use an ampersand as part of
|
||||||
|
an HTML entity, it remains unchanged; otherwise it will be translated
|
||||||
|
into <code>&amp;</code>.</p>
|
||||||
|
|
||||||
|
<p>So, if you want to include a copyright symbol in your article, you can write:</p>
|
||||||
|
|
||||||
|
<pre><code>&copy;
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>and Markdown will leave it alone. But if you write:</p>
|
||||||
|
|
||||||
|
<pre><code>AT&T
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Markdown will translate it to:</p>
|
||||||
|
|
||||||
|
<pre><code>AT&amp;T
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Similarly, because Markdown supports <a href="#html">inline HTML</a>, if you use
|
||||||
|
angle brackets as delimiters for HTML tags, Markdown will treat them as
|
||||||
|
such. But if you write:</p>
|
||||||
|
|
||||||
|
<pre><code>4 < 5
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Markdown will translate it to:</p>
|
||||||
|
|
||||||
|
<pre><code>4 &lt; 5
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>However, inside Markdown code spans and blocks, angle brackets and
|
||||||
|
ampersands are <em>always</em> encoded automatically. This makes it easy to use
|
||||||
|
Markdown to write about HTML code. (As opposed to raw HTML, which is a
|
||||||
|
terrible format for writing about HTML syntax, because every single <code><</code>
|
||||||
|
and <code>&</code> in your example code needs to be escaped.)</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2 id="block">Block Elements</h2>
|
||||||
|
|
||||||
|
<h3 id="p">Paragraphs and Line Breaks</h3>
|
||||||
|
|
||||||
|
<p>A paragraph is simply one or more consecutive lines of text, separated
|
||||||
|
by one or more blank lines. (A blank line is any line that looks like a
|
||||||
|
blank line -- a line containing nothing but spaces or tabs is considered
|
||||||
|
blank.) Normal paragraphs should not be intended with spaces or tabs.</p>
|
||||||
|
|
||||||
|
<p>The implication of the "one or more consecutive lines of text" rule is
|
||||||
|
that Markdown supports "hard-wrapped" text paragraphs. This differs
|
||||||
|
significantly from most other text-to-HTML formatters (including Movable
|
||||||
|
Type's "Convert Line Breaks" option) which translate every line break
|
||||||
|
character in a paragraph into a <code><br /></code> tag.</p>
|
||||||
|
|
||||||
|
<p>When you <em>do</em> want to insert a <code><br /></code> break tag using Markdown, you
|
||||||
|
end a line with two or more spaces, then type return.</p>
|
||||||
|
|
||||||
|
<p>Yes, this takes a tad more effort to create a <code><br /></code>, but a simplistic
|
||||||
|
"every line break is a <code><br /></code>" rule wouldn't work for Markdown.
|
||||||
|
Markdown's email-style <a href="#blockquote">blockquoting</a> and multi-paragraph <a href="#list">list items</a>
|
||||||
|
work best -- and look better -- when you format them with hard breaks.</p>
|
||||||
|
|
||||||
|
<h3 id="header">Headers</h3>
|
||||||
|
|
||||||
|
<p>Markdown supports two styles of headers, <a href="http://docutils.sourceforge.net/mirror/setext.html">Setext</a> and <a href="http://www.aaronsw.com/2002/atx/">atx</a>.</p>
|
||||||
|
|
||||||
|
<p>Setext-style headers are "underlined" using equal signs (for first-level
|
||||||
|
headers) and dashes (for second-level headers). For example:</p>
|
||||||
|
|
||||||
|
<pre><code>This is an H1
|
||||||
|
=============
|
||||||
|
|
||||||
|
This is an H2
|
||||||
|
-------------
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Any number of underlining <code>=</code>'s or <code>-</code>'s will work.</p>
|
||||||
|
|
||||||
|
<p>Atx-style headers use 1-6 hash characters at the start of the line,
|
||||||
|
corresponding to header levels 1-6. For example:</p>
|
||||||
|
|
||||||
|
<pre><code># This is an H1
|
||||||
|
|
||||||
|
## This is an H2
|
||||||
|
|
||||||
|
###### This is an H6
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Optionally, you may "close" atx-style headers. This is purely
|
||||||
|
cosmetic -- you can use this if you think it looks better. The
|
||||||
|
closing hashes don't even need to match the number of hashes
|
||||||
|
used to open the header. (The number of opening hashes
|
||||||
|
determines the header level.) :</p>
|
||||||
|
|
||||||
|
<pre><code># This is an H1 #
|
||||||
|
|
||||||
|
## This is an H2 ##
|
||||||
|
|
||||||
|
### This is an H3 ######
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3 id="blockquote">Blockquotes</h3>
|
||||||
|
|
||||||
|
<p>Markdown uses email-style <code>></code> characters for blockquoting. If you're
|
||||||
|
familiar with quoting passages of text in an email message, then you
|
||||||
|
know how to create a blockquote in Markdown. It looks best if you hard
|
||||||
|
wrap the text and put a <code>></code> before every line:</p>
|
||||||
|
|
||||||
|
<pre><code>> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
||||||
|
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
||||||
|
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
||||||
|
>
|
||||||
|
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
||||||
|
> id sem consectetuer libero luctus adipiscing.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Markdown allows you to be lazy and only put the <code>></code> before the first
|
||||||
|
line of a hard-wrapped paragraph:</p>
|
||||||
|
|
||||||
|
<pre><code>> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
||||||
|
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
||||||
|
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
||||||
|
|
||||||
|
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
||||||
|
id sem consectetuer libero luctus adipiscing.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
|
||||||
|
adding additional levels of <code>></code>:</p>
|
||||||
|
|
||||||
|
<pre><code>> This is the first level of quoting.
|
||||||
|
>
|
||||||
|
> > This is nested blockquote.
|
||||||
|
>
|
||||||
|
> Back to the first level.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Blockquotes can contain other Markdown elements, including headers, lists,
|
||||||
|
and code blocks:</p>
|
||||||
|
|
||||||
|
<pre><code>> ## This is a header.
|
||||||
|
>
|
||||||
|
> 1. This is the first list item.
|
||||||
|
> 2. This is the second list item.
|
||||||
|
>
|
||||||
|
> Here's some example code:
|
||||||
|
>
|
||||||
|
> return shell_exec("echo $input | $markdown_script");
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Any decent text editor should make email-style quoting easy. For
|
||||||
|
example, with BBEdit, you can make a selection and choose Increase
|
||||||
|
Quote Level from the Text menu.</p>
|
||||||
|
|
||||||
|
<h3 id="list">Lists</h3>
|
||||||
|
|
||||||
|
<p>Markdown supports ordered (numbered) and unordered (bulleted) lists.</p>
|
||||||
|
|
||||||
|
<p>Unordered lists use asterisks, pluses, and hyphens -- interchangably
|
||||||
|
-- as list markers:</p>
|
||||||
|
|
||||||
|
<pre><code>* Red
|
||||||
|
* Green
|
||||||
|
* Blue
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>is equivalent to:</p>
|
||||||
|
|
||||||
|
<pre><code>+ Red
|
||||||
|
+ Green
|
||||||
|
+ Blue
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>and:</p>
|
||||||
|
|
||||||
|
<pre><code>- Red
|
||||||
|
- Green
|
||||||
|
- Blue
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Ordered lists use numbers followed by periods:</p>
|
||||||
|
|
||||||
|
<pre><code>1. Bird
|
||||||
|
2. McHale
|
||||||
|
3. Parish
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>It's important to note that the actual numbers you use to mark the
|
||||||
|
list have no effect on the HTML output Markdown produces. The HTML
|
||||||
|
Markdown produces from the above list is:</p>
|
||||||
|
|
||||||
|
<pre><code><ol>
|
||||||
|
<li>Bird</li>
|
||||||
|
<li>McHale</li>
|
||||||
|
<li>Parish</li>
|
||||||
|
</ol>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>If you instead wrote the list in Markdown like this:</p>
|
||||||
|
|
||||||
|
<pre><code>1. Bird
|
||||||
|
1. McHale
|
||||||
|
1. Parish
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>or even:</p>
|
||||||
|
|
||||||
|
<pre><code>3. Bird
|
||||||
|
1. McHale
|
||||||
|
8. Parish
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>you'd get the exact same HTML output. The point is, if you want to,
|
||||||
|
you can use ordinal numbers in your ordered Markdown lists, so that
|
||||||
|
the numbers in your source match the numbers in your published HTML.
|
||||||
|
But if you want to be lazy, you don't have to.</p>
|
||||||
|
|
||||||
|
<p>If you do use lazy list numbering, however, you should still start the
|
||||||
|
list with the number 1. At some point in the future, Markdown may support
|
||||||
|
starting ordered lists at an arbitrary number.</p>
|
||||||
|
|
||||||
|
<p>List markers typically start at the left margin, but may be indented by
|
||||||
|
up to three spaces. List markers must be followed by one or more spaces
|
||||||
|
or a tab.</p>
|
||||||
|
|
||||||
|
<p>To make lists look nice, you can wrap items with hanging indents:</p>
|
||||||
|
|
||||||
|
<pre><code>* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||||
|
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
||||||
|
viverra nec, fringilla in, laoreet vitae, risus.
|
||||||
|
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
||||||
|
Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>But if you want to be lazy, you don't have to:</p>
|
||||||
|
|
||||||
|
<pre><code>* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||||
|
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
||||||
|
viverra nec, fringilla in, laoreet vitae, risus.
|
||||||
|
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
||||||
|
Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>If list items are separated by blank lines, Markdown will wrap the
|
||||||
|
items in <code><p></code> tags in the HTML output. For example, this input:</p>
|
||||||
|
|
||||||
|
<pre><code>* Bird
|
||||||
|
* Magic
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>will turn into:</p>
|
||||||
|
|
||||||
|
<pre><code><ul>
|
||||||
|
<li>Bird</li>
|
||||||
|
<li>Magic</li>
|
||||||
|
</ul>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>But this:</p>
|
||||||
|
|
||||||
|
<pre><code>* Bird
|
||||||
|
|
||||||
|
* Magic
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>will turn into:</p>
|
||||||
|
|
||||||
|
<pre><code><ul>
|
||||||
|
<li><p>Bird</p></li>
|
||||||
|
<li><p>Magic</p></li>
|
||||||
|
</ul>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>List items may consist of multiple paragraphs. Each subsequent
|
||||||
|
paragraph in a list item must be intended by either 4 spaces
|
||||||
|
or one tab:</p>
|
||||||
|
|
||||||
|
<pre><code>1. This is a list item with two paragraphs. Lorem ipsum dolor
|
||||||
|
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
|
||||||
|
mi posuere lectus.
|
||||||
|
|
||||||
|
Vestibulum enim wisi, viverra nec, fringilla in, laoreet
|
||||||
|
vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
|
||||||
|
sit amet velit.
|
||||||
|
|
||||||
|
2. Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>It looks nice if you indent every line of the subsequent
|
||||||
|
paragraphs, but here again, Markdown will allow you to be
|
||||||
|
lazy:</p>
|
||||||
|
|
||||||
|
<pre><code>* This is a list item with two paragraphs.
|
||||||
|
|
||||||
|
This is the second paragraph in the list item. You're
|
||||||
|
only required to indent the first line. Lorem ipsum dolor
|
||||||
|
sit amet, consectetuer adipiscing elit.
|
||||||
|
|
||||||
|
* Another item in the same list.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>To put a blockquote within a list item, the blockquote's <code>></code>
|
||||||
|
delimiters need to be indented:</p>
|
||||||
|
|
||||||
|
<pre><code>* A list item with a blockquote:
|
||||||
|
|
||||||
|
> This is a blockquote
|
||||||
|
> inside a list item.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>To put a code block within a list item, the code block needs
|
||||||
|
to be indented <em>twice</em> -- 8 spaces or two tabs:</p>
|
||||||
|
|
||||||
|
<pre><code>* A list item with a code block:
|
||||||
|
|
||||||
|
<code goes here>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>It's worth noting that it's possible to trigger an ordered list by
|
||||||
|
accident, by writing something like this:</p>
|
||||||
|
|
||||||
|
<pre><code>1986. What a great season.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>In other words, a <em>number-period-space</em> sequence at the beginning of a
|
||||||
|
line. To avoid this, you can backslash-escape the period:</p>
|
||||||
|
|
||||||
|
<pre><code>1986\. What a great season.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3 id="precode">Code Blocks</h3>
|
||||||
|
|
||||||
|
<p>Pre-formatted code blocks are used for writing about programming or
|
||||||
|
markup source code. Rather than forming normal paragraphs, the lines
|
||||||
|
of a code block are interpreted literally. Markdown wraps a code block
|
||||||
|
in both <code><pre></code> and <code><code></code> tags.</p>
|
||||||
|
|
||||||
|
<p>To produce a code block in Markdown, simply indent every line of the
|
||||||
|
block by at least 4 spaces or 1 tab. For example, given this input:</p>
|
||||||
|
|
||||||
|
<pre><code>This is a normal paragraph:
|
||||||
|
|
||||||
|
This is a code block.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Markdown will generate:</p>
|
||||||
|
|
||||||
|
<pre><code><p>This is a normal paragraph:</p>
|
||||||
|
|
||||||
|
<pre><code>This is a code block.
|
||||||
|
</code></pre>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>One level of indentation -- 4 spaces or 1 tab -- is removed from each
|
||||||
|
line of the code block. For example, this:</p>
|
||||||
|
|
||||||
|
<pre><code>Here is an example of AppleScript:
|
||||||
|
|
||||||
|
tell application "Foo"
|
||||||
|
beep
|
||||||
|
end tell
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>will turn into:</p>
|
||||||
|
|
||||||
|
<pre><code><p>Here is an example of AppleScript:</p>
|
||||||
|
|
||||||
|
<pre><code>tell application "Foo"
|
||||||
|
beep
|
||||||
|
end tell
|
||||||
|
</code></pre>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>A code block continues until it reaches a line that is not indented
|
||||||
|
(or the end of the article).</p>
|
||||||
|
|
||||||
|
<p>Within a code block, ampersands (<code>&</code>) and angle brackets (<code><</code> and <code>></code>)
|
||||||
|
are automatically converted into HTML entities. This makes it very
|
||||||
|
easy to include example HTML source code using Markdown -- just paste
|
||||||
|
it and indent it, and Markdown will handle the hassle of encoding the
|
||||||
|
ampersands and angle brackets. For example, this:</p>
|
||||||
|
|
||||||
|
<pre><code> <div class="footer">
|
||||||
|
&copy; 2004 Foo Corporation
|
||||||
|
</div>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>will turn into:</p>
|
||||||
|
|
||||||
|
<pre><code><pre><code>&lt;div class="footer"&gt;
|
||||||
|
&amp;copy; 2004 Foo Corporation
|
||||||
|
&lt;/div&gt;
|
||||||
|
</code></pre>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Regular Markdown syntax is not processed within code blocks. E.g.,
|
||||||
|
asterisks are just literal asterisks within a code block. This means
|
||||||
|
it's also easy to use Markdown to write about Markdown's own syntax.</p>
|
||||||
|
|
||||||
|
<h3 id="hr">Horizontal Rules</h3>
|
||||||
|
|
||||||
|
<p>You can produce a horizontal rule tag (<code><hr /></code>) by placing three or
|
||||||
|
more hyphens, asterisks, or underscores on a line by themselves. If you
|
||||||
|
wish, you may use spaces between the hyphens or asterisks. Each of the
|
||||||
|
following lines will produce a horizontal rule:</p>
|
||||||
|
|
||||||
|
<pre><code>* * *
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
*****
|
||||||
|
|
||||||
|
- - -
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
_ _ _
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2 id="span">Span Elements</h2>
|
||||||
|
|
||||||
|
<h3 id="link">Links</h3>
|
||||||
|
|
||||||
|
<p>Markdown supports two style of links: <em>inline</em> and <em>reference</em>.</p>
|
||||||
|
|
||||||
|
<p>In both styles, the link text is delimited by [square brackets].</p>
|
||||||
|
|
||||||
|
<p>To create an inline link, use a set of regular parentheses immediately
|
||||||
|
after the link text's closing square bracket. Inside the parentheses,
|
||||||
|
put the URL where you want the link to point, along with an <em>optional</em>
|
||||||
|
title for the link, surrounded in quotes. For example:</p>
|
||||||
|
|
||||||
|
<pre><code>This is [an example](http://example.com/ "Title") inline link.
|
||||||
|
|
||||||
|
[This link](http://example.net/) has no title attribute.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Will produce:</p>
|
||||||
|
|
||||||
|
<pre><code><p>This is <a href="http://example.com/" title="Title">
|
||||||
|
an example</a> inline link.</p>
|
||||||
|
|
||||||
|
<p><a href="http://example.net/">This link</a> has no
|
||||||
|
title attribute.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>If you're referring to a local resource on the same server, you can
|
||||||
|
use relative paths:</p>
|
||||||
|
|
||||||
|
<pre><code>See my [About](/about/) page for details.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Reference-style links use a second set of square brackets, inside
|
||||||
|
which you place a label of your choosing to identify the link:</p>
|
||||||
|
|
||||||
|
<pre><code>This is [an example][id] reference-style link.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>You can optionally use a space to separate the sets of brackets:</p>
|
||||||
|
|
||||||
|
<pre><code>This is [an example] [id] reference-style link.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Then, anywhere in the document, you define your link label like this,
|
||||||
|
on a line by itself:</p>
|
||||||
|
|
||||||
|
<pre><code>[id]: http://example.com/ "Optional Title Here"
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>That is:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Square brackets containing the link identifier (optionally
|
||||||
|
indented from the left margin using up to three spaces);</li>
|
||||||
|
<li>followed by a colon;</li>
|
||||||
|
<li>followed by one or more spaces (or tabs);</li>
|
||||||
|
<li>followed by the URL for the link;</li>
|
||||||
|
<li>optionally followed by a title attribute for the link, enclosed
|
||||||
|
in double or single quotes.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>The link URL may, optionally, be surrounded by angle brackets:</p>
|
||||||
|
|
||||||
|
<pre><code>[id]: <http://example.com/> "Optional Title Here"
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>You can put the title attribute on the next line and use extra spaces
|
||||||
|
or tabs for padding, which tends to look better with longer URLs:</p>
|
||||||
|
|
||||||
|
<pre><code>[id]: http://example.com/longish/path/to/resource/here
|
||||||
|
"Optional Title Here"
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Link definitions are only used for creating links during Markdown
|
||||||
|
processing, and are stripped from your document in the HTML output.</p>
|
||||||
|
|
||||||
|
<p>Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are <em>not</em> case sensitive. E.g. these two links:</p>
|
||||||
|
|
||||||
|
<pre><code>[link text][a]
|
||||||
|
[link text][A]
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>are equivalent.</p>
|
||||||
|
|
||||||
|
<p>The <em>implicit link name</em> shortcut allows you to omit the name of the
|
||||||
|
link, in which case the link text itself is used as the name.
|
||||||
|
Just use an empty set of square brackets -- e.g., to link the word
|
||||||
|
"Google" to the google.com web site, you could simply write:</p>
|
||||||
|
|
||||||
|
<pre><code>[Google][]
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>And then define the link:</p>
|
||||||
|
|
||||||
|
<pre><code>[Google]: http://google.com/
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Because link names may contain spaces, this shortcut even works for
|
||||||
|
multiple words in the link text:</p>
|
||||||
|
|
||||||
|
<pre><code>Visit [Daring Fireball][] for more information.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>And then define the link:</p>
|
||||||
|
|
||||||
|
<pre><code>[Daring Fireball]: http://daringfireball.net/
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Link definitions can be placed anywhere in your Markdown document. I
|
||||||
|
tend to put them immediately after each paragraph in which they're
|
||||||
|
used, but if you want, you can put them all at the end of your
|
||||||
|
document, sort of like footnotes.</p>
|
||||||
|
|
||||||
|
<p>Here's an example of reference links in action:</p>
|
||||||
|
|
||||||
|
<pre><code>I get 10 times more traffic from [Google] [1] than from
|
||||||
|
[Yahoo] [2] or [MSN] [3].
|
||||||
|
|
||||||
|
[1]: http://google.com/ "Google"
|
||||||
|
[2]: http://search.yahoo.com/ "Yahoo Search"
|
||||||
|
[3]: http://search.msn.com/ "MSN Search"
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Using the implicit link name shortcut, you could instead write:</p>
|
||||||
|
|
||||||
|
<pre><code>I get 10 times more traffic from [Google][] than from
|
||||||
|
[Yahoo][] or [MSN][].
|
||||||
|
|
||||||
|
[google]: http://google.com/ "Google"
|
||||||
|
[yahoo]: http://search.yahoo.com/ "Yahoo Search"
|
||||||
|
[msn]: http://search.msn.com/ "MSN Search"
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Both of the above examples will produce the following HTML output:</p>
|
||||||
|
|
||||||
|
<pre><code><p>I get 10 times more traffic from <a href="http://google.com/"
|
||||||
|
title="Google">Google</a> than from
|
||||||
|
<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
|
||||||
|
or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>For comparison, here is the same paragraph written using
|
||||||
|
Markdown's inline link style:</p>
|
||||||
|
|
||||||
|
<pre><code>I get 10 times more traffic from [Google](http://google.com/ "Google")
|
||||||
|
than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
|
||||||
|
[MSN](http://search.msn.com/ "MSN Search").
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>The point of reference-style links is not that they're easier to
|
||||||
|
write. The point is that with reference-style links, your document
|
||||||
|
source is vastly more readable. Compare the above examples: using
|
||||||
|
reference-style links, the paragraph itself is only 81 characters
|
||||||
|
long; with inline-style links, it's 176 characters; and as raw HTML,
|
||||||
|
it's 234 characters. In the raw HTML, there's more markup than there
|
||||||
|
is text.</p>
|
||||||
|
|
||||||
|
<p>With Markdown's reference-style links, a source document much more
|
||||||
|
closely resembles the final output, as rendered in a browser. By
|
||||||
|
allowing you to move the markup-related metadata out of the paragraph,
|
||||||
|
you can add links without interrupting the narrative flow of your
|
||||||
|
prose.</p>
|
||||||
|
|
||||||
|
<h3 id="em">Emphasis</h3>
|
||||||
|
|
||||||
|
<p>Markdown treats asterisks (<code>*</code>) and underscores (<code>_</code>) as indicators of
|
||||||
|
emphasis. Text wrapped with one <code>*</code> or <code>_</code> will be wrapped with an
|
||||||
|
HTML <code><em></code> tag; double <code>*</code>'s or <code>_</code>'s will be wrapped with an HTML
|
||||||
|
<code><strong></code> tag. E.g., this input:</p>
|
||||||
|
|
||||||
|
<pre><code>*single asterisks*
|
||||||
|
|
||||||
|
_single underscores_
|
||||||
|
|
||||||
|
**double asterisks**
|
||||||
|
|
||||||
|
__double underscores__
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>will produce:</p>
|
||||||
|
|
||||||
|
<pre><code><em>single asterisks</em>
|
||||||
|
|
||||||
|
<em>single underscores</em>
|
||||||
|
|
||||||
|
<strong>double asterisks</strong>
|
||||||
|
|
||||||
|
<strong>double underscores</strong>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>You can use whichever style you prefer; the lone restriction is that
|
||||||
|
the same character must be used to open and close an emphasis span.</p>
|
||||||
|
|
||||||
|
<p>Emphasis can be used in the middle of a word:</p>
|
||||||
|
|
||||||
|
<pre><code>un*fucking*believable
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>But if you surround an <code>*</code> or <code>_</code> with spaces, it'll be treated as a
|
||||||
|
literal asterisk or underscore.</p>
|
||||||
|
|
||||||
|
<p>To produce a literal asterisk or underscore at a position where it
|
||||||
|
would otherwise be used as an emphasis delimiter, you can backslash
|
||||||
|
escape it:</p>
|
||||||
|
|
||||||
|
<pre><code>\*this text is surrounded by literal asterisks\*
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3 id="code">Code</h3>
|
||||||
|
|
||||||
|
<p>To indicate a span of code, wrap it with backtick quotes (<code>`</code>).
|
||||||
|
Unlike a pre-formatted code block, a code span indicates code within a
|
||||||
|
normal paragraph. For example:</p>
|
||||||
|
|
||||||
|
<pre><code>Use the `printf()` function.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>will produce:</p>
|
||||||
|
|
||||||
|
<pre><code><p>Use the <code>printf()</code> function.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>To include a literal backtick character within a code span, you can use
|
||||||
|
multiple backticks as the opening and closing delimiters:</p>
|
||||||
|
|
||||||
|
<pre><code>``There is a literal backtick (`) here.``
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>which will produce this:</p>
|
||||||
|
|
||||||
|
<pre><code><p><code>There is a literal backtick (`) here.</code></p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>The backtick delimiters surrounding a code span may include spaces --
|
||||||
|
one after the opening, one before the closing. This allows you to place
|
||||||
|
literal backtick characters at the beginning or end of a code span:</p>
|
||||||
|
|
||||||
|
<pre><code>A single backtick in a code span: `` ` ``
|
||||||
|
|
||||||
|
A backtick-delimited string in a code span: `` `foo` ``
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>will produce:</p>
|
||||||
|
|
||||||
|
<pre><code><p>A single backtick in a code span: <code>`</code></p>
|
||||||
|
|
||||||
|
<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>With a code span, ampersands and angle brackets are encoded as HTML
|
||||||
|
entities automatically, which makes it easy to include example HTML
|
||||||
|
tags. Markdown will turn this:</p>
|
||||||
|
|
||||||
|
<pre><code>Please don't use any `<blink>` tags.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>into:</p>
|
||||||
|
|
||||||
|
<pre><code><p>Please don't use any <code>&lt;blink&gt;</code> tags.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>You can write this:</p>
|
||||||
|
|
||||||
|
<pre><code>`&#8212;` is the decimal-encoded equivalent of `&mdash;`.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>to produce:</p>
|
||||||
|
|
||||||
|
<pre><code><p><code>&amp;#8212;</code> is the decimal-encoded
|
||||||
|
equivalent of <code>&amp;mdash;</code>.</p>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3 id="img">Images</h3>
|
||||||
|
|
||||||
|
<p>Admittedly, it's fairly difficult to devise a "natural" syntax for
|
||||||
|
placing images into a plain text document format.</p>
|
||||||
|
|
||||||
|
<p>Markdown uses an image syntax that is intended to resemble the syntax
|
||||||
|
for links, allowing for two styles: <em>inline</em> and <em>reference</em>.</p>
|
||||||
|
|
||||||
|
<p>Inline image syntax looks like this:</p>
|
||||||
|
|
||||||
|
<pre><code>![Alt text](/path/to/img.jpg)
|
||||||
|
|
||||||
|
![Alt text](/path/to/img.jpg "Optional title")
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>That is:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>An exclamation mark: <code>!</code>;</li>
|
||||||
|
<li>followed by a set of square brackets, containing the <code>alt</code>
|
||||||
|
attribute text for the image;</li>
|
||||||
|
<li>followed by a set of parentheses, containing the URL or path to
|
||||||
|
the image, and an optional <code>title</code> attribute enclosed in double
|
||||||
|
or single quotes.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Reference-style image syntax looks like this:</p>
|
||||||
|
|
||||||
|
<pre><code>![Alt text][id]
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Where "id" is the name of a defined image reference. Image references
|
||||||
|
are defined using syntax identical to link references:</p>
|
||||||
|
|
||||||
|
<pre><code>[id]: url/to/image "Optional title attribute"
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>As of this writing, Markdown has no syntax for specifying the
|
||||||
|
dimensions of an image; if this is important to you, you can simply
|
||||||
|
use regular HTML <code><img></code> tags.</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2 id="misc">Miscellaneous</h2>
|
||||||
|
|
||||||
|
<h3 id="autolink">Automatic Links</h3>
|
||||||
|
|
||||||
|
<p>Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:</p>
|
||||||
|
|
||||||
|
<pre><code><http://example.com/>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Markdown will turn this into:</p>
|
||||||
|
|
||||||
|
<pre><code><a href="http://example.com/">http://example.com/</a>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Automatic links for email addresses work similarly, except that
|
||||||
|
Markdown will also perform a bit of randomized decimal and hex
|
||||||
|
entity-encoding to help obscure your address from address-harvesting
|
||||||
|
spambots. For example, Markdown will turn this:</p>
|
||||||
|
|
||||||
|
<pre><code><address@example.com>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>into something like this:</p>
|
||||||
|
|
||||||
|
<pre><code><a href="&#x6D;&#x61;i&#x6C;&#x74;&#x6F;:&#x61;&#x64;&#x64;&#x72;&#x65;
|
||||||
|
&#115;&#115;&#64;&#101;&#120;&#x61;&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;
|
||||||
|
&#109;">&#x61;&#x64;&#x64;&#x72;&#x65;&#115;&#115;&#64;&#101;&#120;&#x61;
|
||||||
|
&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;&#109;</a>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>which will render in a browser as a clickable link to "address@example.com".</p>
|
||||||
|
|
||||||
|
<p>(This sort of entity-encoding trick will indeed fool many, if not
|
||||||
|
most, address-harvesting bots, but it definitely won't fool all of
|
||||||
|
them. It's better than nothing, but an address published in this way
|
||||||
|
will probably eventually start receiving spam.)</p>
|
||||||
|
|
||||||
|
<h3 id="backslash">Backslash Escapes</h3>
|
||||||
|
|
||||||
|
<p>Markdown allows you to use backslash escapes to generate literal
|
||||||
|
characters which would otherwise have special meaning in Markdown's
|
||||||
|
formatting syntax. For example, if you wanted to surround a word with
|
||||||
|
literal asterisks (instead of an HTML <code><em></code> tag), you can backslashes
|
||||||
|
before the asterisks, like this:</p>
|
||||||
|
|
||||||
|
<pre><code>\*literal asterisks\*
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>Markdown provides backslash escapes for the following characters:</p>
|
||||||
|
|
||||||
|
<pre><code>\ backslash
|
||||||
|
` backtick
|
||||||
|
* asterisk
|
||||||
|
_ underscore
|
||||||
|
{} curly braces
|
||||||
|
[] square brackets
|
||||||
|
() parentheses
|
||||||
|
# hash mark
|
||||||
|
+ plus sign
|
||||||
|
- minus sign (hyphen)
|
||||||
|
. dot
|
||||||
|
! exclamation mark
|
||||||
|
</code></pre>
|
888
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.text
generated
vendored
100644
888
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.text
generated
vendored
100644
|
@ -0,0 +1,888 @@
|
||||||
|
Markdown: Syntax
|
||||||
|
================
|
||||||
|
|
||||||
|
<ul id="ProjectSubmenu">
|
||||||
|
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
|
||||||
|
<li><a href="/projects/markdown/basics" title="Markdown Basics">Basics</a></li>
|
||||||
|
<li><a class="selected" title="Markdown Syntax Documentation">Syntax</a></li>
|
||||||
|
<li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
|
||||||
|
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
* [Overview](#overview)
|
||||||
|
* [Philosophy](#philosophy)
|
||||||
|
* [Inline HTML](#html)
|
||||||
|
* [Automatic Escaping for Special Characters](#autoescape)
|
||||||
|
* [Block Elements](#block)
|
||||||
|
* [Paragraphs and Line Breaks](#p)
|
||||||
|
* [Headers](#header)
|
||||||
|
* [Blockquotes](#blockquote)
|
||||||
|
* [Lists](#list)
|
||||||
|
* [Code Blocks](#precode)
|
||||||
|
* [Horizontal Rules](#hr)
|
||||||
|
* [Span Elements](#span)
|
||||||
|
* [Links](#link)
|
||||||
|
* [Emphasis](#em)
|
||||||
|
* [Code](#code)
|
||||||
|
* [Images](#img)
|
||||||
|
* [Miscellaneous](#misc)
|
||||||
|
* [Backslash Escapes](#backslash)
|
||||||
|
* [Automatic Links](#autolink)
|
||||||
|
|
||||||
|
|
||||||
|
**Note:** This document is itself written using Markdown; you
|
||||||
|
can [see the source for it by adding '.text' to the URL][src].
|
||||||
|
|
||||||
|
[src]: /projects/markdown/syntax.text
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
<h2 id="overview">Overview</h2>
|
||||||
|
|
||||||
|
<h3 id="philosophy">Philosophy</h3>
|
||||||
|
|
||||||
|
Markdown is intended to be as easy-to-read and easy-to-write as is feasible.
|
||||||
|
|
||||||
|
Readability, however, is emphasized above all else. A Markdown-formatted
|
||||||
|
document should be publishable as-is, as plain text, without looking
|
||||||
|
like it's been marked up with tags or formatting instructions. While
|
||||||
|
Markdown's syntax has been influenced by several existing text-to-HTML
|
||||||
|
filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4],
|
||||||
|
[Grutatext] [5], and [EtText] [6] -- the single biggest source of
|
||||||
|
inspiration for Markdown's syntax is the format of plain text email.
|
||||||
|
|
||||||
|
[1]: http://docutils.sourceforge.net/mirror/setext.html
|
||||||
|
[2]: http://www.aaronsw.com/2002/atx/
|
||||||
|
[3]: http://textism.com/tools/textile/
|
||||||
|
[4]: http://docutils.sourceforge.net/rst.html
|
||||||
|
[5]: http://www.triptico.com/software/grutatxt.html
|
||||||
|
[6]: http://ettext.taint.org/doc/
|
||||||
|
|
||||||
|
To this end, Markdown's syntax is comprised entirely of punctuation
|
||||||
|
characters, which punctuation characters have been carefully chosen so
|
||||||
|
as to look like what they mean. E.g., asterisks around a word actually
|
||||||
|
look like \*emphasis\*. Markdown lists look like, well, lists. Even
|
||||||
|
blockquotes look like quoted passages of text, assuming you've ever
|
||||||
|
used email.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="html">Inline HTML</h3>
|
||||||
|
|
||||||
|
Markdown's syntax is intended for one purpose: to be used as a
|
||||||
|
format for *writing* for the web.
|
||||||
|
|
||||||
|
Markdown is not a replacement for HTML, or even close to it. Its
|
||||||
|
syntax is very small, corresponding only to a very small subset of
|
||||||
|
HTML tags. The idea is *not* to create a syntax that makes it easier
|
||||||
|
to insert HTML tags. In my opinion, HTML tags are already easy to
|
||||||
|
insert. The idea for Markdown is to make it easy to read, write, and
|
||||||
|
edit prose. HTML is a *publishing* format; Markdown is a *writing*
|
||||||
|
format. Thus, Markdown's formatting syntax only addresses issues that
|
||||||
|
can be conveyed in plain text.
|
||||||
|
|
||||||
|
For any markup that is not covered by Markdown's syntax, you simply
|
||||||
|
use HTML itself. There's no need to preface it or delimit it to
|
||||||
|
indicate that you're switching from Markdown to HTML; you just use
|
||||||
|
the tags.
|
||||||
|
|
||||||
|
The only restrictions are that block-level HTML elements -- e.g. `<div>`,
|
||||||
|
`<table>`, `<pre>`, `<p>`, etc. -- must be separated from surrounding
|
||||||
|
content by blank lines, and the start and end tags of the block should
|
||||||
|
not be indented with tabs or spaces. Markdown is smart enough not
|
||||||
|
to add extra (unwanted) `<p>` tags around HTML block-level tags.
|
||||||
|
|
||||||
|
For example, to add an HTML table to a Markdown article:
|
||||||
|
|
||||||
|
This is a regular paragraph.
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Foo</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
This is another regular paragraph.
|
||||||
|
|
||||||
|
Note that Markdown formatting syntax is not processed within block-level
|
||||||
|
HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an
|
||||||
|
HTML block.
|
||||||
|
|
||||||
|
Span-level HTML tags -- e.g. `<span>`, `<cite>`, or `<del>` -- can be
|
||||||
|
used anywhere in a Markdown paragraph, list item, or header. If you
|
||||||
|
want, you can even use HTML tags instead of Markdown formatting; e.g. if
|
||||||
|
you'd prefer to use HTML `<a>` or `<img>` tags instead of Markdown's
|
||||||
|
link or image syntax, go right ahead.
|
||||||
|
|
||||||
|
Unlike block-level HTML tags, Markdown syntax *is* processed within
|
||||||
|
span-level tags.
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="autoescape">Automatic Escaping for Special Characters</h3>
|
||||||
|
|
||||||
|
In HTML, there are two characters that demand special treatment: `<`
|
||||||
|
and `&`. Left angle brackets are used to start tags; ampersands are
|
||||||
|
used to denote HTML entities. If you want to use them as literal
|
||||||
|
characters, you must escape them as entities, e.g. `<`, and
|
||||||
|
`&`.
|
||||||
|
|
||||||
|
Ampersands in particular are bedeviling for web writers. If you want to
|
||||||
|
write about 'AT&T', you need to write '`AT&T`'. You even need to
|
||||||
|
escape ampersands within URLs. Thus, if you want to link to:
|
||||||
|
|
||||||
|
http://images.google.com/images?num=30&q=larry+bird
|
||||||
|
|
||||||
|
you need to encode the URL as:
|
||||||
|
|
||||||
|
http://images.google.com/images?num=30&q=larry+bird
|
||||||
|
|
||||||
|
in your anchor tag `href` attribute. Needless to say, this is easy to
|
||||||
|
forget, and is probably the single most common source of HTML validation
|
||||||
|
errors in otherwise well-marked-up web sites.
|
||||||
|
|
||||||
|
Markdown allows you to use these characters naturally, taking care of
|
||||||
|
all the necessary escaping for you. If you use an ampersand as part of
|
||||||
|
an HTML entity, it remains unchanged; otherwise it will be translated
|
||||||
|
into `&`.
|
||||||
|
|
||||||
|
So, if you want to include a copyright symbol in your article, you can write:
|
||||||
|
|
||||||
|
©
|
||||||
|
|
||||||
|
and Markdown will leave it alone. But if you write:
|
||||||
|
|
||||||
|
AT&T
|
||||||
|
|
||||||
|
Markdown will translate it to:
|
||||||
|
|
||||||
|
AT&T
|
||||||
|
|
||||||
|
Similarly, because Markdown supports [inline HTML](#html), if you use
|
||||||
|
angle brackets as delimiters for HTML tags, Markdown will treat them as
|
||||||
|
such. But if you write:
|
||||||
|
|
||||||
|
4 < 5
|
||||||
|
|
||||||
|
Markdown will translate it to:
|
||||||
|
|
||||||
|
4 < 5
|
||||||
|
|
||||||
|
However, inside Markdown code spans and blocks, angle brackets and
|
||||||
|
ampersands are *always* encoded automatically. This makes it easy to use
|
||||||
|
Markdown to write about HTML code. (As opposed to raw HTML, which is a
|
||||||
|
terrible format for writing about HTML syntax, because every single `<`
|
||||||
|
and `&` in your example code needs to be escaped.)
|
||||||
|
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id="block">Block Elements</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="p">Paragraphs and Line Breaks</h3>
|
||||||
|
|
||||||
|
A paragraph is simply one or more consecutive lines of text, separated
|
||||||
|
by one or more blank lines. (A blank line is any line that looks like a
|
||||||
|
blank line -- a line containing nothing but spaces or tabs is considered
|
||||||
|
blank.) Normal paragraphs should not be intended with spaces or tabs.
|
||||||
|
|
||||||
|
The implication of the "one or more consecutive lines of text" rule is
|
||||||
|
that Markdown supports "hard-wrapped" text paragraphs. This differs
|
||||||
|
significantly from most other text-to-HTML formatters (including Movable
|
||||||
|
Type's "Convert Line Breaks" option) which translate every line break
|
||||||
|
character in a paragraph into a `<br />` tag.
|
||||||
|
|
||||||
|
When you *do* want to insert a `<br />` break tag using Markdown, you
|
||||||
|
end a line with two or more spaces, then type return.
|
||||||
|
|
||||||
|
Yes, this takes a tad more effort to create a `<br />`, but a simplistic
|
||||||
|
"every line break is a `<br />`" rule wouldn't work for Markdown.
|
||||||
|
Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l]
|
||||||
|
work best -- and look better -- when you format them with hard breaks.
|
||||||
|
|
||||||
|
[bq]: #blockquote
|
||||||
|
[l]: #list
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="header">Headers</h3>
|
||||||
|
|
||||||
|
Markdown supports two styles of headers, [Setext] [1] and [atx] [2].
|
||||||
|
|
||||||
|
Setext-style headers are "underlined" using equal signs (for first-level
|
||||||
|
headers) and dashes (for second-level headers). For example:
|
||||||
|
|
||||||
|
This is an H1
|
||||||
|
=============
|
||||||
|
|
||||||
|
This is an H2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any number of underlining `=`'s or `-`'s will work.
|
||||||
|
|
||||||
|
Atx-style headers use 1-6 hash characters at the start of the line,
|
||||||
|
corresponding to header levels 1-6. For example:
|
||||||
|
|
||||||
|
# This is an H1
|
||||||
|
|
||||||
|
## This is an H2
|
||||||
|
|
||||||
|
###### This is an H6
|
||||||
|
|
||||||
|
Optionally, you may "close" atx-style headers. This is purely
|
||||||
|
cosmetic -- you can use this if you think it looks better. The
|
||||||
|
closing hashes don't even need to match the number of hashes
|
||||||
|
used to open the header. (The number of opening hashes
|
||||||
|
determines the header level.) :
|
||||||
|
|
||||||
|
# This is an H1 #
|
||||||
|
|
||||||
|
## This is an H2 ##
|
||||||
|
|
||||||
|
### This is an H3 ######
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="blockquote">Blockquotes</h3>
|
||||||
|
|
||||||
|
Markdown uses email-style `>` characters for blockquoting. If you're
|
||||||
|
familiar with quoting passages of text in an email message, then you
|
||||||
|
know how to create a blockquote in Markdown. It looks best if you hard
|
||||||
|
wrap the text and put a `>` before every line:
|
||||||
|
|
||||||
|
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
||||||
|
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
||||||
|
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
||||||
|
>
|
||||||
|
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
||||||
|
> id sem consectetuer libero luctus adipiscing.
|
||||||
|
|
||||||
|
Markdown allows you to be lazy and only put the `>` before the first
|
||||||
|
line of a hard-wrapped paragraph:
|
||||||
|
|
||||||
|
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
||||||
|
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
||||||
|
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
||||||
|
|
||||||
|
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
||||||
|
id sem consectetuer libero luctus adipiscing.
|
||||||
|
|
||||||
|
Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
|
||||||
|
adding additional levels of `>`:
|
||||||
|
|
||||||
|
> This is the first level of quoting.
|
||||||
|
>
|
||||||
|
> > This is nested blockquote.
|
||||||
|
>
|
||||||
|
> Back to the first level.
|
||||||
|
|
||||||
|
Blockquotes can contain other Markdown elements, including headers, lists,
|
||||||
|
and code blocks:
|
||||||
|
|
||||||
|
> ## This is a header.
|
||||||
|
>
|
||||||
|
> 1. This is the first list item.
|
||||||
|
> 2. This is the second list item.
|
||||||
|
>
|
||||||
|
> Here's some example code:
|
||||||
|
>
|
||||||
|
> return shell_exec("echo $input | $markdown_script");
|
||||||
|
|
||||||
|
Any decent text editor should make email-style quoting easy. For
|
||||||
|
example, with BBEdit, you can make a selection and choose Increase
|
||||||
|
Quote Level from the Text menu.
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="list">Lists</h3>
|
||||||
|
|
||||||
|
Markdown supports ordered (numbered) and unordered (bulleted) lists.
|
||||||
|
|
||||||
|
Unordered lists use asterisks, pluses, and hyphens -- interchangably
|
||||||
|
-- as list markers:
|
||||||
|
|
||||||
|
* Red
|
||||||
|
* Green
|
||||||
|
* Blue
|
||||||
|
|
||||||
|
is equivalent to:
|
||||||
|
|
||||||
|
+ Red
|
||||||
|
+ Green
|
||||||
|
+ Blue
|
||||||
|
|
||||||
|
and:
|
||||||
|
|
||||||
|
- Red
|
||||||
|
- Green
|
||||||
|
- Blue
|
||||||
|
|
||||||
|
Ordered lists use numbers followed by periods:
|
||||||
|
|
||||||
|
1. Bird
|
||||||
|
2. McHale
|
||||||
|
3. Parish
|
||||||
|
|
||||||
|
It's important to note that the actual numbers you use to mark the
|
||||||
|
list have no effect on the HTML output Markdown produces. The HTML
|
||||||
|
Markdown produces from the above list is:
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Bird</li>
|
||||||
|
<li>McHale</li>
|
||||||
|
<li>Parish</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
If you instead wrote the list in Markdown like this:
|
||||||
|
|
||||||
|
1. Bird
|
||||||
|
1. McHale
|
||||||
|
1. Parish
|
||||||
|
|
||||||
|
or even:
|
||||||
|
|
||||||
|
3. Bird
|
||||||
|
1. McHale
|
||||||
|
8. Parish
|
||||||
|
|
||||||
|
you'd get the exact same HTML output. The point is, if you want to,
|
||||||
|
you can use ordinal numbers in your ordered Markdown lists, so that
|
||||||
|
the numbers in your source match the numbers in your published HTML.
|
||||||
|
But if you want to be lazy, you don't have to.
|
||||||
|
|
||||||
|
If you do use lazy list numbering, however, you should still start the
|
||||||
|
list with the number 1. At some point in the future, Markdown may support
|
||||||
|
starting ordered lists at an arbitrary number.
|
||||||
|
|
||||||
|
List markers typically start at the left margin, but may be indented by
|
||||||
|
up to three spaces. List markers must be followed by one or more spaces
|
||||||
|
or a tab.
|
||||||
|
|
||||||
|
To make lists look nice, you can wrap items with hanging indents:
|
||||||
|
|
||||||
|
* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||||
|
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
||||||
|
viverra nec, fringilla in, laoreet vitae, risus.
|
||||||
|
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
||||||
|
Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||||
|
|
||||||
|
But if you want to be lazy, you don't have to:
|
||||||
|
|
||||||
|
* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
||||||
|
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
||||||
|
viverra nec, fringilla in, laoreet vitae, risus.
|
||||||
|
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
||||||
|
Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||||
|
|
||||||
|
If list items are separated by blank lines, Markdown will wrap the
|
||||||
|
items in `<p>` tags in the HTML output. For example, this input:
|
||||||
|
|
||||||
|
* Bird
|
||||||
|
* Magic
|
||||||
|
|
||||||
|
will turn into:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Bird</li>
|
||||||
|
<li>Magic</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
But this:
|
||||||
|
|
||||||
|
* Bird
|
||||||
|
|
||||||
|
* Magic
|
||||||
|
|
||||||
|
will turn into:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><p>Bird</p></li>
|
||||||
|
<li><p>Magic</p></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
List items may consist of multiple paragraphs. Each subsequent
|
||||||
|
paragraph in a list item must be intended by either 4 spaces
|
||||||
|
or one tab:
|
||||||
|
|
||||||
|
1. This is a list item with two paragraphs. Lorem ipsum dolor
|
||||||
|
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
|
||||||
|
mi posuere lectus.
|
||||||
|
|
||||||
|
Vestibulum enim wisi, viverra nec, fringilla in, laoreet
|
||||||
|
vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
|
||||||
|
sit amet velit.
|
||||||
|
|
||||||
|
2. Suspendisse id sem consectetuer libero luctus adipiscing.
|
||||||
|
|
||||||
|
It looks nice if you indent every line of the subsequent
|
||||||
|
paragraphs, but here again, Markdown will allow you to be
|
||||||
|
lazy:
|
||||||
|
|
||||||
|
* This is a list item with two paragraphs.
|
||||||
|
|
||||||
|
This is the second paragraph in the list item. You're
|
||||||
|
only required to indent the first line. Lorem ipsum dolor
|
||||||
|
sit amet, consectetuer adipiscing elit.
|
||||||
|
|
||||||
|
* Another item in the same list.
|
||||||
|
|
||||||
|
To put a blockquote within a list item, the blockquote's `>`
|
||||||
|
delimiters need to be indented:
|
||||||
|
|
||||||
|
* A list item with a blockquote:
|
||||||
|
|
||||||
|
> This is a blockquote
|
||||||
|
> inside a list item.
|
||||||
|
|
||||||
|
To put a code block within a list item, the code block needs
|
||||||
|
to be indented *twice* -- 8 spaces or two tabs:
|
||||||
|
|
||||||
|
* A list item with a code block:
|
||||||
|
|
||||||
|
<code goes here>
|
||||||
|
|
||||||
|
|
||||||
|
It's worth noting that it's possible to trigger an ordered list by
|
||||||
|
accident, by writing something like this:
|
||||||
|
|
||||||
|
1986. What a great season.
|
||||||
|
|
||||||
|
In other words, a *number-period-space* sequence at the beginning of a
|
||||||
|
line. To avoid this, you can backslash-escape the period:
|
||||||
|
|
||||||
|
1986\. What a great season.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="precode">Code Blocks</h3>
|
||||||
|
|
||||||
|
Pre-formatted code blocks are used for writing about programming or
|
||||||
|
markup source code. Rather than forming normal paragraphs, the lines
|
||||||
|
of a code block are interpreted literally. Markdown wraps a code block
|
||||||
|
in both `<pre>` and `<code>` tags.
|
||||||
|
|
||||||
|
To produce a code block in Markdown, simply indent every line of the
|
||||||
|
block by at least 4 spaces or 1 tab. For example, given this input:
|
||||||
|
|
||||||
|
This is a normal paragraph:
|
||||||
|
|
||||||
|
This is a code block.
|
||||||
|
|
||||||
|
Markdown will generate:
|
||||||
|
|
||||||
|
<p>This is a normal paragraph:</p>
|
||||||
|
|
||||||
|
<pre><code>This is a code block.
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
One level of indentation -- 4 spaces or 1 tab -- is removed from each
|
||||||
|
line of the code block. For example, this:
|
||||||
|
|
||||||
|
Here is an example of AppleScript:
|
||||||
|
|
||||||
|
tell application "Foo"
|
||||||
|
beep
|
||||||
|
end tell
|
||||||
|
|
||||||
|
will turn into:
|
||||||
|
|
||||||
|
<p>Here is an example of AppleScript:</p>
|
||||||
|
|
||||||
|
<pre><code>tell application "Foo"
|
||||||
|
beep
|
||||||
|
end tell
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
A code block continues until it reaches a line that is not indented
|
||||||
|
(or the end of the article).
|
||||||
|
|
||||||
|
Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
|
||||||
|
are automatically converted into HTML entities. This makes it very
|
||||||
|
easy to include example HTML source code using Markdown -- just paste
|
||||||
|
it and indent it, and Markdown will handle the hassle of encoding the
|
||||||
|
ampersands and angle brackets. For example, this:
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
© 2004 Foo Corporation
|
||||||
|
</div>
|
||||||
|
|
||||||
|
will turn into:
|
||||||
|
|
||||||
|
<pre><code><div class="footer">
|
||||||
|
&copy; 2004 Foo Corporation
|
||||||
|
</div>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
Regular Markdown syntax is not processed within code blocks. E.g.,
|
||||||
|
asterisks are just literal asterisks within a code block. This means
|
||||||
|
it's also easy to use Markdown to write about Markdown's own syntax.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="hr">Horizontal Rules</h3>
|
||||||
|
|
||||||
|
You can produce a horizontal rule tag (`<hr />`) by placing three or
|
||||||
|
more hyphens, asterisks, or underscores on a line by themselves. If you
|
||||||
|
wish, you may use spaces between the hyphens or asterisks. Each of the
|
||||||
|
following lines will produce a horizontal rule:
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
*****
|
||||||
|
|
||||||
|
- - -
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
_ _ _
|
||||||
|
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
<h2 id="span">Span Elements</h2>
|
||||||
|
|
||||||
|
<h3 id="link">Links</h3>
|
||||||
|
|
||||||
|
Markdown supports two style of links: *inline* and *reference*.
|
||||||
|
|
||||||
|
In both styles, the link text is delimited by [square brackets].
|
||||||
|
|
||||||
|
To create an inline link, use a set of regular parentheses immediately
|
||||||
|
after the link text's closing square bracket. Inside the parentheses,
|
||||||
|
put the URL where you want the link to point, along with an *optional*
|
||||||
|
title for the link, surrounded in quotes. For example:
|
||||||
|
|
||||||
|
This is [an example](http://example.com/ "Title") inline link.
|
||||||
|
|
||||||
|
[This link](http://example.net/) has no title attribute.
|
||||||
|
|
||||||
|
Will produce:
|
||||||
|
|
||||||
|
<p>This is <a href="http://example.com/" title="Title">
|
||||||
|
an example</a> inline link.</p>
|
||||||
|
|
||||||
|
<p><a href="http://example.net/">This link</a> has no
|
||||||
|
title attribute.</p>
|
||||||
|
|
||||||
|
If you're referring to a local resource on the same server, you can
|
||||||
|
use relative paths:
|
||||||
|
|
||||||
|
See my [About](/about/) page for details.
|
||||||
|
|
||||||
|
Reference-style links use a second set of square brackets, inside
|
||||||
|
which you place a label of your choosing to identify the link:
|
||||||
|
|
||||||
|
This is [an example][id] reference-style link.
|
||||||
|
|
||||||
|
You can optionally use a space to separate the sets of brackets:
|
||||||
|
|
||||||
|
This is [an example] [id] reference-style link.
|
||||||
|
|
||||||
|
Then, anywhere in the document, you define your link label like this,
|
||||||
|
on a line by itself:
|
||||||
|
|
||||||
|
[id]: http://example.com/ "Optional Title Here"
|
||||||
|
|
||||||
|
That is:
|
||||||
|
|
||||||
|
* Square brackets containing the link identifier (optionally
|
||||||
|
indented from the left margin using up to three spaces);
|
||||||
|
* followed by a colon;
|
||||||
|
* followed by one or more spaces (or tabs);
|
||||||
|
* followed by the URL for the link;
|
||||||
|
* optionally followed by a title attribute for the link, enclosed
|
||||||
|
in double or single quotes.
|
||||||
|
|
||||||
|
The link URL may, optionally, be surrounded by angle brackets:
|
||||||
|
|
||||||
|
[id]: <http://example.com/> "Optional Title Here"
|
||||||
|
|
||||||
|
You can put the title attribute on the next line and use extra spaces
|
||||||
|
or tabs for padding, which tends to look better with longer URLs:
|
||||||
|
|
||||||
|
[id]: http://example.com/longish/path/to/resource/here
|
||||||
|
"Optional Title Here"
|
||||||
|
|
||||||
|
Link definitions are only used for creating links during Markdown
|
||||||
|
processing, and are stripped from your document in the HTML output.
|
||||||
|
|
||||||
|
Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are *not* case sensitive. E.g. these two links:
|
||||||
|
|
||||||
|
[link text][a]
|
||||||
|
[link text][A]
|
||||||
|
|
||||||
|
are equivalent.
|
||||||
|
|
||||||
|
The *implicit link name* shortcut allows you to omit the name of the
|
||||||
|
link, in which case the link text itself is used as the name.
|
||||||
|
Just use an empty set of square brackets -- e.g., to link the word
|
||||||
|
"Google" to the google.com web site, you could simply write:
|
||||||
|
|
||||||
|
[Google][]
|
||||||
|
|
||||||
|
And then define the link:
|
||||||
|
|
||||||
|
[Google]: http://google.com/
|
||||||
|
|
||||||
|
Because link names may contain spaces, this shortcut even works for
|
||||||
|
multiple words in the link text:
|
||||||
|
|
||||||
|
Visit [Daring Fireball][] for more information.
|
||||||
|
|
||||||
|
And then define the link:
|
||||||
|
|
||||||
|
[Daring Fireball]: http://daringfireball.net/
|
||||||
|
|
||||||
|
Link definitions can be placed anywhere in your Markdown document. I
|
||||||
|
tend to put them immediately after each paragraph in which they're
|
||||||
|
used, but if you want, you can put them all at the end of your
|
||||||
|
document, sort of like footnotes.
|
||||||
|
|
||||||
|
Here's an example of reference links in action:
|
||||||
|
|
||||||
|
I get 10 times more traffic from [Google] [1] than from
|
||||||
|
[Yahoo] [2] or [MSN] [3].
|
||||||
|
|
||||||
|
[1]: http://google.com/ "Google"
|
||||||
|
[2]: http://search.yahoo.com/ "Yahoo Search"
|
||||||
|
[3]: http://search.msn.com/ "MSN Search"
|
||||||
|
|
||||||
|
Using the implicit link name shortcut, you could instead write:
|
||||||
|
|
||||||
|
I get 10 times more traffic from [Google][] than from
|
||||||
|
[Yahoo][] or [MSN][].
|
||||||
|
|
||||||
|
[google]: http://google.com/ "Google"
|
||||||
|
[yahoo]: http://search.yahoo.com/ "Yahoo Search"
|
||||||
|
[msn]: http://search.msn.com/ "MSN Search"
|
||||||
|
|
||||||
|
Both of the above examples will produce the following HTML output:
|
||||||
|
|
||||||
|
<p>I get 10 times more traffic from <a href="http://google.com/"
|
||||||
|
title="Google">Google</a> than from
|
||||||
|
<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
|
||||||
|
or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
|
||||||
|
|
||||||
|
For comparison, here is the same paragraph written using
|
||||||
|
Markdown's inline link style:
|
||||||
|
|
||||||
|
I get 10 times more traffic from [Google](http://google.com/ "Google")
|
||||||
|
than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
|
||||||
|
[MSN](http://search.msn.com/ "MSN Search").
|
||||||
|
|
||||||
|
The point of reference-style links is not that they're easier to
|
||||||
|
write. The point is that with reference-style links, your document
|
||||||
|
source is vastly more readable. Compare the above examples: using
|
||||||
|
reference-style links, the paragraph itself is only 81 characters
|
||||||
|
long; with inline-style links, it's 176 characters; and as raw HTML,
|
||||||
|
it's 234 characters. In the raw HTML, there's more markup than there
|
||||||
|
is text.
|
||||||
|
|
||||||
|
With Markdown's reference-style links, a source document much more
|
||||||
|
closely resembles the final output, as rendered in a browser. By
|
||||||
|
allowing you to move the markup-related metadata out of the paragraph,
|
||||||
|
you can add links without interrupting the narrative flow of your
|
||||||
|
prose.
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="em">Emphasis</h3>
|
||||||
|
|
||||||
|
Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
|
||||||
|
emphasis. Text wrapped with one `*` or `_` will be wrapped with an
|
||||||
|
HTML `<em>` tag; double `*`'s or `_`'s will be wrapped with an HTML
|
||||||
|
`<strong>` tag. E.g., this input:
|
||||||
|
|
||||||
|
*single asterisks*
|
||||||
|
|
||||||
|
_single underscores_
|
||||||
|
|
||||||
|
**double asterisks**
|
||||||
|
|
||||||
|
__double underscores__
|
||||||
|
|
||||||
|
will produce:
|
||||||
|
|
||||||
|
<em>single asterisks</em>
|
||||||
|
|
||||||
|
<em>single underscores</em>
|
||||||
|
|
||||||
|
<strong>double asterisks</strong>
|
||||||
|
|
||||||
|
<strong>double underscores</strong>
|
||||||
|
|
||||||
|
You can use whichever style you prefer; the lone restriction is that
|
||||||
|
the same character must be used to open and close an emphasis span.
|
||||||
|
|
||||||
|
Emphasis can be used in the middle of a word:
|
||||||
|
|
||||||
|
un*fucking*believable
|
||||||
|
|
||||||
|
But if you surround an `*` or `_` with spaces, it'll be treated as a
|
||||||
|
literal asterisk or underscore.
|
||||||
|
|
||||||
|
To produce a literal asterisk or underscore at a position where it
|
||||||
|
would otherwise be used as an emphasis delimiter, you can backslash
|
||||||
|
escape it:
|
||||||
|
|
||||||
|
\*this text is surrounded by literal asterisks\*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="code">Code</h3>
|
||||||
|
|
||||||
|
To indicate a span of code, wrap it with backtick quotes (`` ` ``).
|
||||||
|
Unlike a pre-formatted code block, a code span indicates code within a
|
||||||
|
normal paragraph. For example:
|
||||||
|
|
||||||
|
Use the `printf()` function.
|
||||||
|
|
||||||
|
will produce:
|
||||||
|
|
||||||
|
<p>Use the <code>printf()</code> function.</p>
|
||||||
|
|
||||||
|
To include a literal backtick character within a code span, you can use
|
||||||
|
multiple backticks as the opening and closing delimiters:
|
||||||
|
|
||||||
|
``There is a literal backtick (`) here.``
|
||||||
|
|
||||||
|
which will produce this:
|
||||||
|
|
||||||
|
<p><code>There is a literal backtick (`) here.</code></p>
|
||||||
|
|
||||||
|
The backtick delimiters surrounding a code span may include spaces --
|
||||||
|
one after the opening, one before the closing. This allows you to place
|
||||||
|
literal backtick characters at the beginning or end of a code span:
|
||||||
|
|
||||||
|
A single backtick in a code span: `` ` ``
|
||||||
|
|
||||||
|
A backtick-delimited string in a code span: `` `foo` ``
|
||||||
|
|
||||||
|
will produce:
|
||||||
|
|
||||||
|
<p>A single backtick in a code span: <code>`</code></p>
|
||||||
|
|
||||||
|
<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
|
||||||
|
|
||||||
|
With a code span, ampersands and angle brackets are encoded as HTML
|
||||||
|
entities automatically, which makes it easy to include example HTML
|
||||||
|
tags. Markdown will turn this:
|
||||||
|
|
||||||
|
Please don't use any `<blink>` tags.
|
||||||
|
|
||||||
|
into:
|
||||||
|
|
||||||
|
<p>Please don't use any <code><blink></code> tags.</p>
|
||||||
|
|
||||||
|
You can write this:
|
||||||
|
|
||||||
|
`—` is the decimal-encoded equivalent of `—`.
|
||||||
|
|
||||||
|
to produce:
|
||||||
|
|
||||||
|
<p><code>&#8212;</code> is the decimal-encoded
|
||||||
|
equivalent of <code>&mdash;</code>.</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="img">Images</h3>
|
||||||
|
|
||||||
|
Admittedly, it's fairly difficult to devise a "natural" syntax for
|
||||||
|
placing images into a plain text document format.
|
||||||
|
|
||||||
|
Markdown uses an image syntax that is intended to resemble the syntax
|
||||||
|
for links, allowing for two styles: *inline* and *reference*.
|
||||||
|
|
||||||
|
Inline image syntax looks like this:
|
||||||
|
|
||||||
|
![Alt text](/path/to/img.jpg)
|
||||||
|
|
||||||
|
![Alt text](/path/to/img.jpg "Optional title")
|
||||||
|
|
||||||
|
That is:
|
||||||
|
|
||||||
|
* An exclamation mark: `!`;
|
||||||
|
* followed by a set of square brackets, containing the `alt`
|
||||||
|
attribute text for the image;
|
||||||
|
* followed by a set of parentheses, containing the URL or path to
|
||||||
|
the image, and an optional `title` attribute enclosed in double
|
||||||
|
or single quotes.
|
||||||
|
|
||||||
|
Reference-style image syntax looks like this:
|
||||||
|
|
||||||
|
![Alt text][id]
|
||||||
|
|
||||||
|
Where "id" is the name of a defined image reference. Image references
|
||||||
|
are defined using syntax identical to link references:
|
||||||
|
|
||||||
|
[id]: url/to/image "Optional title attribute"
|
||||||
|
|
||||||
|
As of this writing, Markdown has no syntax for specifying the
|
||||||
|
dimensions of an image; if this is important to you, you can simply
|
||||||
|
use regular HTML `<img>` tags.
|
||||||
|
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id="misc">Miscellaneous</h2>
|
||||||
|
|
||||||
|
<h3 id="autolink">Automatic Links</h3>
|
||||||
|
|
||||||
|
Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:
|
||||||
|
|
||||||
|
<http://example.com/>
|
||||||
|
|
||||||
|
Markdown will turn this into:
|
||||||
|
|
||||||
|
<a href="http://example.com/">http://example.com/</a>
|
||||||
|
|
||||||
|
Automatic links for email addresses work similarly, except that
|
||||||
|
Markdown will also perform a bit of randomized decimal and hex
|
||||||
|
entity-encoding to help obscure your address from address-harvesting
|
||||||
|
spambots. For example, Markdown will turn this:
|
||||||
|
|
||||||
|
<address@example.com>
|
||||||
|
|
||||||
|
into something like this:
|
||||||
|
|
||||||
|
<a href="mailto:addre
|
||||||
|
ss@example.co
|
||||||
|
m">address@exa
|
||||||
|
mple.com</a>
|
||||||
|
|
||||||
|
which will render in a browser as a clickable link to "address@example.com".
|
||||||
|
|
||||||
|
(This sort of entity-encoding trick will indeed fool many, if not
|
||||||
|
most, address-harvesting bots, but it definitely won't fool all of
|
||||||
|
them. It's better than nothing, but an address published in this way
|
||||||
|
will probably eventually start receiving spam.)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h3 id="backslash">Backslash Escapes</h3>
|
||||||
|
|
||||||
|
Markdown allows you to use backslash escapes to generate literal
|
||||||
|
characters which would otherwise have special meaning in Markdown's
|
||||||
|
formatting syntax. For example, if you wanted to surround a word with
|
||||||
|
literal asterisks (instead of an HTML `<em>` tag), you can backslashes
|
||||||
|
before the asterisks, like this:
|
||||||
|
|
||||||
|
\*literal asterisks\*
|
||||||
|
|
||||||
|
Markdown provides backslash escapes for the following characters:
|
||||||
|
|
||||||
|
\ backslash
|
||||||
|
` backtick
|
||||||
|
* asterisk
|
||||||
|
_ underscore
|
||||||
|
{} curly braces
|
||||||
|
[] square brackets
|
||||||
|
() parentheses
|
||||||
|
# hash mark
|
||||||
|
+ plus sign
|
||||||
|
- minus sign (hyphen)
|
||||||
|
. dot
|
||||||
|
! exclamation mark
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>bar</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<p>foo</p>
|
||||||
|
</blockquote>
|
|
@ -0,0 +1,5 @@
|
||||||
|
> foo
|
||||||
|
>
|
||||||
|
> > bar
|
||||||
|
>
|
||||||
|
> foo
|
166
vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.html
generated
vendored
100644
166
vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.html
generated
vendored
100644
|
@ -0,0 +1,166 @@
|
||||||
|
<h2>Unordered</h2>
|
||||||
|
|
||||||
|
<p>Asterisks tight:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>asterisk 1</li>
|
||||||
|
<li>asterisk 2</li>
|
||||||
|
<li>asterisk 3</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Asterisks loose:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><p>asterisk 1</p></li>
|
||||||
|
|
||||||
|
<li><p>asterisk 2</p></li>
|
||||||
|
|
||||||
|
<li><p>asterisk 3</p></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p>Pluses tight:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Plus 1</li>
|
||||||
|
<li>Plus 2</li>
|
||||||
|
<li>Plus 3</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Pluses loose:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><p>Plus 1</p></li>
|
||||||
|
|
||||||
|
<li><p>Plus 2</p></li>
|
||||||
|
|
||||||
|
<li><p>Plus 3</p></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p>Minuses tight:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Minus 1</li>
|
||||||
|
<li>Minus 2</li>
|
||||||
|
<li>Minus 3</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Minuses loose:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><p>Minus 1</p></li>
|
||||||
|
|
||||||
|
<li><p>Minus 2</p></li>
|
||||||
|
|
||||||
|
<li><p>Minus 3</p></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Ordered</h2>
|
||||||
|
|
||||||
|
<p>Tight:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>First</li>
|
||||||
|
<li>Second</li>
|
||||||
|
<li>Third</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>and:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>One</li>
|
||||||
|
<li>Two</li>
|
||||||
|
<li>Three</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Loose using tabs:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><p>First</p></li>
|
||||||
|
|
||||||
|
<li><p>Second</p></li>
|
||||||
|
|
||||||
|
<li><p>Third</p></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>and using spaces:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><p>One</p></li>
|
||||||
|
|
||||||
|
<li><p>Two</p></li>
|
||||||
|
|
||||||
|
<li><p>Three</p></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Multiple paragraphs:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><p>Item 1, graf one.</p>
|
||||||
|
|
||||||
|
<p>Item 2. graf two. The quick brown fox jumped over the lazy dog's
|
||||||
|
back.</p></li>
|
||||||
|
|
||||||
|
<li><p>Item 2.</p></li>
|
||||||
|
|
||||||
|
<li><p>Item 3.</p></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Nested</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Tab
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Tab
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Tab</li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Here's another:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>First</li>
|
||||||
|
<li>Second:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Fee</li>
|
||||||
|
<li>Fie</li>
|
||||||
|
<li>Foe</li>
|
||||||
|
</ul></li>
|
||||||
|
<li>Third</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Same thing but with paragraphs:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><p>First</p></li>
|
||||||
|
|
||||||
|
<li><p>Second:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Fee</li>
|
||||||
|
<li>Fie</li>
|
||||||
|
<li>Foe</li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
|
<li><p>Third</p></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>This was an error in Markdown 1.0.1:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><p>this</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>sub</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>that</p></li>
|
||||||
|
</ul>
|
131
vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.text
generated
vendored
100644
131
vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.text
generated
vendored
100644
|
@ -0,0 +1,131 @@
|
||||||
|
## Unordered
|
||||||
|
|
||||||
|
Asterisks tight:
|
||||||
|
|
||||||
|
* asterisk 1
|
||||||
|
* asterisk 2
|
||||||
|
* asterisk 3
|
||||||
|
|
||||||
|
|
||||||
|
Asterisks loose:
|
||||||
|
|
||||||
|
* asterisk 1
|
||||||
|
|
||||||
|
* asterisk 2
|
||||||
|
|
||||||
|
* asterisk 3
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
Pluses tight:
|
||||||
|
|
||||||
|
+ Plus 1
|
||||||
|
+ Plus 2
|
||||||
|
+ Plus 3
|
||||||
|
|
||||||
|
|
||||||
|
Pluses loose:
|
||||||
|
|
||||||
|
+ Plus 1
|
||||||
|
|
||||||
|
+ Plus 2
|
||||||
|
|
||||||
|
+ Plus 3
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
|
||||||
|
Minuses tight:
|
||||||
|
|
||||||
|
- Minus 1
|
||||||
|
- Minus 2
|
||||||
|
- Minus 3
|
||||||
|
|
||||||
|
|
||||||
|
Minuses loose:
|
||||||
|
|
||||||
|
- Minus 1
|
||||||
|
|
||||||
|
- Minus 2
|
||||||
|
|
||||||
|
- Minus 3
|
||||||
|
|
||||||
|
|
||||||
|
## Ordered
|
||||||
|
|
||||||
|
Tight:
|
||||||
|
|
||||||
|
1. First
|
||||||
|
2. Second
|
||||||
|
3. Third
|
||||||
|
|
||||||
|
and:
|
||||||
|
|
||||||
|
1. One
|
||||||
|
2. Two
|
||||||
|
3. Three
|
||||||
|
|
||||||
|
|
||||||
|
Loose using tabs:
|
||||||
|
|
||||||
|
1. First
|
||||||
|
|
||||||
|
2. Second
|
||||||
|
|
||||||
|
3. Third
|
||||||
|
|
||||||
|
and using spaces:
|
||||||
|
|
||||||
|
1. One
|
||||||
|
|
||||||
|
2. Two
|
||||||
|
|
||||||
|
3. Three
|
||||||
|
|
||||||
|
Multiple paragraphs:
|
||||||
|
|
||||||
|
1. Item 1, graf one.
|
||||||
|
|
||||||
|
Item 2. graf two. The quick brown fox jumped over the lazy dog's
|
||||||
|
back.
|
||||||
|
|
||||||
|
2. Item 2.
|
||||||
|
|
||||||
|
3. Item 3.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Nested
|
||||||
|
|
||||||
|
* Tab
|
||||||
|
* Tab
|
||||||
|
* Tab
|
||||||
|
|
||||||
|
Here's another:
|
||||||
|
|
||||||
|
1. First
|
||||||
|
2. Second:
|
||||||
|
* Fee
|
||||||
|
* Fie
|
||||||
|
* Foe
|
||||||
|
3. Third
|
||||||
|
|
||||||
|
Same thing but with paragraphs:
|
||||||
|
|
||||||
|
1. First
|
||||||
|
|
||||||
|
2. Second:
|
||||||
|
* Fee
|
||||||
|
* Fie
|
||||||
|
* Foe
|
||||||
|
|
||||||
|
3. Third
|
||||||
|
|
||||||
|
|
||||||
|
This was an error in Markdown 1.0.1:
|
||||||
|
|
||||||
|
* this
|
||||||
|
|
||||||
|
* sub
|
||||||
|
|
||||||
|
that
|
7
vendor/github.com/russross/blackfriday/testdata/Strong and em together.html
generated
vendored
100644
7
vendor/github.com/russross/blackfriday/testdata/Strong and em together.html
generated
vendored
100644
|
@ -0,0 +1,7 @@
|
||||||
|
<p><strong><em>This is strong and em.</em></strong></p>
|
||||||
|
|
||||||
|
<p>So is <strong><em>this</em></strong> word.</p>
|
||||||
|
|
||||||
|
<p><strong><em>This is strong and em.</em></strong></p>
|
||||||
|
|
||||||
|
<p>So is <strong><em>this</em></strong> word.</p>
|
7
vendor/github.com/russross/blackfriday/testdata/Strong and em together.text
generated
vendored
100644
7
vendor/github.com/russross/blackfriday/testdata/Strong and em together.text
generated
vendored
100644
|
@ -0,0 +1,7 @@
|
||||||
|
***This is strong and em.***
|
||||||
|
|
||||||
|
So is ***this*** word.
|
||||||
|
|
||||||
|
___This is strong and em.___
|
||||||
|
|
||||||
|
So is ___this___ word.
|
|
@ -0,0 +1,26 @@
|
||||||
|
<ul>
|
||||||
|
<li><p>this is a list item
|
||||||
|
indented with tabs</p></li>
|
||||||
|
|
||||||
|
<li><p>this is a list item
|
||||||
|
indented with spaces</p></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Code:</p>
|
||||||
|
|
||||||
|
<pre><code>this code block is indented by one tab
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>And:</p>
|
||||||
|
|
||||||
|
<pre><code> this code block is indented by two tabs
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>And:</p>
|
||||||
|
|
||||||
|
<pre><code>+ this is an example list item
|
||||||
|
indented with tabs
|
||||||
|
|
||||||
|
+ this is an example list item
|
||||||
|
indented with spaces
|
||||||
|
</code></pre>
|
|
@ -0,0 +1,21 @@
|
||||||
|
+ this is a list item
|
||||||
|
indented with tabs
|
||||||
|
|
||||||
|
+ this is a list item
|
||||||
|
indented with spaces
|
||||||
|
|
||||||
|
Code:
|
||||||
|
|
||||||
|
this code block is indented by one tab
|
||||||
|
|
||||||
|
And:
|
||||||
|
|
||||||
|
this code block is indented by two tabs
|
||||||
|
|
||||||
|
And:
|
||||||
|
|
||||||
|
+ this is an example list item
|
||||||
|
indented with tabs
|
||||||
|
|
||||||
|
+ this is an example list item
|
||||||
|
indented with spaces
|
|
@ -0,0 +1,9 @@
|
||||||
|
<blockquote>
|
||||||
|
<p>A list within a blockquote:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>asterisk 1</li>
|
||||||
|
<li>asterisk 2</li>
|
||||||
|
<li>asterisk 3</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
|
@ -0,0 +1,5 @@
|
||||||
|
> A list within a blockquote:
|
||||||
|
>
|
||||||
|
> * asterisk 1
|
||||||
|
> * asterisk 2
|
||||||
|
> * asterisk 3
|
|
@ -5,13 +5,14 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<title>设置 - Powered by MinDoc</title>
|
<title>设置 - {{.Model.BookName}} - Powered by MinDoc</title>
|
||||||
|
|
||||||
<!-- Bootstrap -->
|
<!-- Bootstrap -->
|
||||||
<link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/webuploader/webuploader.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/webuploader/webuploader.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/cropper/2.3.4/cropper.min.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/cropper/2.3.4/cropper.min.css"}}" rel="stylesheet">
|
||||||
|
<link href="{{cdncss "/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.css"}}" rel="stylesheet">
|
||||||
<link href="/static/css/main.css" rel="stylesheet">
|
<link href="/static/css/main.css" rel="stylesheet">
|
||||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
@ -60,17 +61,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>标识</label>
|
<label>标识</label>
|
||||||
<input type="text" class="form-control" value=" {{.BaseUrl}}{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" disabled>
|
<input type="text" class="form-control" value="{{.BaseUrl}}{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" placeholder="项目唯一标识" disabled>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>描述</label>
|
<label>描述</label>
|
||||||
<textarea rows="3" class="form-control" name="description" style="height: 90px">{{.Model.Description}}</textarea>
|
<textarea rows="3" class="form-control" name="description" style="height: 90px" placeholder="项目描述">{{.Model.Description}}</textarea>
|
||||||
<p class="text">描述信息不超过500个字符</p>
|
<p class="text">描述信息不超过500个字符,支持Markdown语法</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>标签</label>
|
<label>标签</label>
|
||||||
<input type="text" class="form-control" name="label" placeholder="项目标签" value="{{.Model.Label}}">
|
<input type="text" class="form-control" name="label" placeholder="项目标签" value="{{.Model.Label}}">
|
||||||
<p class="text">最多允许添加10个标签,多个标签请用“;”分割</p>
|
<p class="text">最多允许添加10个标签,多个标签请用“,”分割</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>编辑器</label>
|
<label>编辑器</label>
|
||||||
|
@ -177,12 +178,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Start Modal -->
|
<!-- Start Modal -->
|
||||||
<div class="modal fade" id="upload-logo-panel" tabindex="-1" role="dialog" aria-labelledby="修改头像" aria-hidden="true">
|
<div class="modal fade" id="upload-logo-panel" tabindex="-1" role="dialog" aria-labelledby="修改封面" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||||
<h4 class="modal-title">修改头像</h4>
|
<h4 class="modal-title">修改封面</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="wraper">
|
<div class="wraper">
|
||||||
|
@ -271,6 +272,7 @@
|
||||||
<script src="{{cdnjs "/static/webuploader/webuploader.min.js"}}" type="text/javascript"></script>
|
<script src="{{cdnjs "/static/webuploader/webuploader.min.js"}}" type="text/javascript"></script>
|
||||||
<script src="{{cdnjs "/static/cropper/2.3.4/cropper.min.js"}}" type="text/javascript"></script>
|
<script src="{{cdnjs "/static/cropper/2.3.4/cropper.min.js"}}" type="text/javascript"></script>
|
||||||
<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
|
<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
|
||||||
|
<script src="{{cdnjs "/static/bootstrap/plugins/tagsinput/bootstrap-tagsinput.min.js"}}" type="text/javascript"></script>
|
||||||
<script src="/static/js/main.js" type="text/javascript"></script>
|
<script src="/static/js/main.js" type="text/javascript"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(function () {
|
$(function () {
|
||||||
|
@ -280,6 +282,13 @@
|
||||||
window.modalHtml = $("#upload-logo-panel").find(".modal-body").html();
|
window.modalHtml = $("#upload-logo-panel").find(".modal-body").html();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('input[name="label"]').tagsinput({
|
||||||
|
confirmKeys: [13,44],
|
||||||
|
maxTags: 10,
|
||||||
|
trimValue: true,
|
||||||
|
cancelConfirmKeysOnEmpty : false
|
||||||
|
});
|
||||||
|
|
||||||
$("#changePrivatelyOwnedForm").ajaxForm({
|
$("#changePrivatelyOwnedForm").ajaxForm({
|
||||||
beforeSubmit :function () {
|
beforeSubmit :function () {
|
||||||
$("#btnChangePrivatelyOwned").button("loading");
|
$("#btnChangePrivatelyOwned").button("loading");
|
||||||
|
|
|
@ -25,6 +25,19 @@
|
||||||
{{template "widgets/header.tpl" .}}
|
{{template "widgets/header.tpl" .}}
|
||||||
<div class="container manual-body">
|
<div class="container manual-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
{{if gt (.Labels|len) 0}}
|
||||||
|
<div class="hide tag-container-outer">
|
||||||
|
<span class="title">热门标签:</span>
|
||||||
|
<span class="tags">
|
||||||
|
{{range $index,$item := .Labels}}
|
||||||
|
<a href="{{urlfor "LabelController.Index" ":key" $item.LabelName}}">{{$item.LabelName}}<span class="detail">{{$item.BookNumber}}</span></a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div class="manual-list">
|
<div class="manual-list">
|
||||||
{{range $index,$item := .Lists}}
|
{{range $index,$item := .Lists}}
|
||||||
<div class="list-item">
|
<div class="list-item">
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>{{.LabelName}} - Powered by MinDoc</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<link href="{{cdncss "/static/bootstrap/css/bootstrap.min.css"}}" rel="stylesheet">
|
||||||
|
<link href="{{cdncss "/static/font-awesome/css/font-awesome.min.css"}}" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="/static/css/main.css" rel="stylesheet">
|
||||||
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="/static/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
|
<script src="/static/respond.js/1.4.2/respond.min.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="manual-reader manual-container manual-search-reader">
|
||||||
|
{{template "widgets/header.tpl" .}}
|
||||||
|
<div class="container manual-body">
|
||||||
|
<div class="search-head">
|
||||||
|
<strong class="search-title">显示标签为"{{.LabelName}}"的项目</strong>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="manual-list">
|
||||||
|
{{range $index,$item := .Lists}}
|
||||||
|
<div class="list-item">
|
||||||
|
<dl class="manual-item-standard">
|
||||||
|
<dt>
|
||||||
|
<a href="{{urlfor "DocumentController.Index" ":key" $item.Identify}}" title="{{$item.BookName}}-{{$item.CreateName}}" target="_blank">
|
||||||
|
<img src="{{$item.Cover}}" class="cover" alt="{{$item.BookName}}-{{$item.CreateName}}">
|
||||||
|
</a>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<a href="{{urlfor "DocumentController.Index" ":key" $item.Identify}}" class="name" title="{{$item.BookName}}-{{$item.CreateName}}" target="_blank">{{$item.BookName}}</a>
|
||||||
|
</dd>
|
||||||
|
<dd>
|
||||||
|
<span class="author">
|
||||||
|
<b class="text">作者</b>
|
||||||
|
<b class="text">-</b>
|
||||||
|
<b class="text">{{$item.CreateName}}</b>
|
||||||
|
</span>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
{{.PageHtml}}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "widgets/footer.tpl" .}}
|
||||||
|
</div>
|
||||||
|
<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
|
||||||
|
<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -112,6 +112,7 @@
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<button type="button" class="btn btn-success btn-sm" @click="setMemberStatus(item.member_id,0,$event)" data-loading-text="禁用中...">启用</button>
|
<button type="button" class="btn btn-success btn-sm" @click="setMemberStatus(item.member_id,0,$event)" data-loading-text="禁用中...">启用</button>
|
||||||
</template>
|
</template>
|
||||||
|
<button type="button" class="btn btn-danger btn-sm" @click="deleteMember(item.member_id,$event)" data-loading-text="删除中">删除</button>
|
||||||
</template>
|
</template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -297,6 +298,31 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
deleteMember : function (id, e) {
|
||||||
|
var $this = this;
|
||||||
|
$.ajax({
|
||||||
|
url : "{{urlfor "ManagerController.DeleteMember"}}",
|
||||||
|
type : "post",
|
||||||
|
data : { "id":id },
|
||||||
|
dataType : "json",
|
||||||
|
success : function (res) {
|
||||||
|
if (res.errcode === 0) {
|
||||||
|
|
||||||
|
for (var index in $this.lists) {
|
||||||
|
var item = $this.lists[index];
|
||||||
|
if (item.member_id == id) {
|
||||||
|
console.log(item);
|
||||||
|
$this.lists.splice(index,1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("操作失败:" + res.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue