浏览代码

1、实现用户注册
2、实现验证码
3、完善文档评论功能

lifei6671 8 年之前
父节点
当前提交
5b5c5b73bc

+ 2 - 0
commands/command.go

@@ -13,6 +13,7 @@ import (
 	"github.com/astaxie/beego/logs"
 	"github.com/lifei6671/godoc/conf"
 
+	"github.com/lifei6671/gocaptcha"
 )
 
 // RegisterDataBase 注册数据库
@@ -105,5 +106,6 @@ func RegisterFunction()  {
 }
 
 func init()  {
+	gocaptcha.ReadFonts("./static/fonts", ".ttf")
 	gob.Register(models.Member{})
 }

+ 3 - 1
conf/enumerate.go

@@ -9,6 +9,8 @@ import (
 // 登录用户的Session名
 const LoginSessionName = "LoginSessionName"
 
+const CaptchaSessionName  = "__captcha__"
+
 const RegexpEmail  = `^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$`
 
 const RegexpAccount = `^[a-zA-Z][a-zA-z0-9]{2,50}$`
@@ -39,7 +41,7 @@ const (
 
 // app_key
 func GetAppKey()  (string) {
-	return beego.AppConfig.DefaultString("app_key","go-git-webhook")
+	return beego.AppConfig.DefaultString("app_key","godoc")
 }
 
 func GetDatabasePrefix() string  {

+ 101 - 5
controllers/account.go

@@ -2,12 +2,16 @@ package controllers
 
 import (
 	"time"
+	"strings"
 
 	"github.com/lifei6671/godoc/conf"
 	"github.com/lifei6671/godoc/models"
 	"github.com/lifei6671/godoc/utils"
 	"github.com/astaxie/beego"
 	"github.com/astaxie/beego/logs"
+	"github.com/lifei6671/gocaptcha"
+
+	"regexp"
 )
 
 // AccountController 用户登录与注册.
@@ -40,14 +44,32 @@ func (c *AccountController) Login()  {
 	if c.Ctx.Input.IsPost() {
 		account := c.GetString("account")
 		password := c.GetString("password")
-
+		captcha := c.GetString("code")
+		is_remember := c.GetString("is_remember")
+
+		//如果开启了验证码
+		if v,ok := c.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v,"true") {
+			v,ok := c.GetSession(conf.CaptchaSessionName).(string);
+			if !ok || !strings.EqualFold(v,captcha){
+				c.JsonResult(6001,"验证码不正确")
+			}
+		}
 		member,err := models.NewMember().Login(account,password)
 
 		//如果没有数据
 		if err == nil {
 			c.SetMember(*member)
+			if strings.EqualFold(is_remember,"yes") {
+				remember.MemberId = member.MemberId
+				remember.Account = member.Account
+				remember.Time = time.Now()
+				v ,err := utils.Encode(remember)
+				if err == nil {
+					c.SetSecureCookie(conf.GetAppKey(),"login",v)
+				}
+
+			}
 			c.JsonResult(0,"ok")
-			c.StopRun()
 		}else{
 			logs.Error("用户登录 =>",err)
 			c.JsonResult(500,"账号或密码错误",nil)
@@ -61,10 +83,61 @@ func (c *AccountController) Login()  {
 	}
 }
 
-func (p *AccountController) Register()  {
-	p.TplName = "account/register.tpl"
+func (c *AccountController) Register()  {
+	c.TplName = "account/register.tpl"
+
+	//如果没有开启用户注册
+	if v,ok := c.Option["ENABLED_REGISTER"]; ok && !strings.EqualFold(v,"true") {
+		c.Abort("404")
+	}
 
-	
+	if c.Ctx.Input.IsPost() {
+		account := c.GetString("account")
+		password1 := c.GetString("password1")
+		password2 := c.GetString("password2")
+		email := c.GetString("email")
+		captcha := c.GetString("code")
+
+		if ok,err := regexp.MatchString(conf.RegexpAccount,account); account == "" || !ok || err != nil {
+			c.JsonResult(6001,"账号只能由英文字母数字组成,且在3-50个字符")
+		}
+		if  l := strings.Count(password1,"") ; password1 == "" || l > 50 || l < 6{
+			c.JsonResult(6002,"密码必须在6-50个字符之间")
+		}
+		if password1 != password2 {
+			c.JsonResult(6003,"确认密码不正确")
+		}
+		if  ok,err := regexp.MatchString(conf.RegexpEmail,email); !ok || err != nil || email == "" {
+			c.JsonResult(6004,"邮箱格式不正确")
+		}
+		//如果开启了验证码
+		if v,ok := c.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v,"true") {
+			v,ok := c.GetSession(conf.CaptchaSessionName).(string);
+			if !ok || !strings.EqualFold(v,captcha){
+				c.JsonResult(6001,"验证码不正确")
+			}
+		}
+
+		member := models.NewMember()
+
+		if _,err := member.FindByAccount(account); err == nil && member.MemberId > 0 {
+			c.JsonResult(6005,"账号已存在")
+		}
+
+		member.Account = account
+		member.Password = password1
+		member.Role = conf.MemberGeneralRole
+		member.Avatar = conf.GetDefaultAvatar()
+		member.CreateAt = 0
+		member.Email = email
+		member.Status = 0
+		if err := member.Add(); err != nil {
+			beego.Error(err)
+			c.JsonResult(6006,"注册失败,请联系系统管理员处理")
+		}
+
+		c.JsonResult(0,"ok",member)
+	}
 }
 
 func (p *AccountController) FindPassword()  {
@@ -78,5 +151,28 @@ func (c *AccountController) Logout(){
 }
 
 func (c *AccountController) Captcha()  {
+	c.Prepare()
+
+	captchaImage, err := gocaptcha.NewCaptchaImage(140, 40, gocaptcha.RandLightColor())
+
+	if err != nil {
+		beego.Error(err)
+		c.Abort("500")
+	}
+
+	captchaImage.DrawNoise(gocaptcha.CaptchaComplexLower)
+
+	//captchaImage.DrawTextNoise(gocaptcha.CaptchaComplexHigh)
+	txt := gocaptcha.RandText(4)
+
+	c.SetSession(conf.CaptchaSessionName,txt)
+
+	captchaImage.DrawText(txt)
+	//captchaImage.Drawline(3);
+	captchaImage.DrawBorder(gocaptcha.ColorToRGB(0x17A7A7A))
+	//captchaImage.DrawHollowLine()
+
 
+	captchaImage.SaveImage(c.Ctx.ResponseWriter, gocaptcha.ImageFormatJpeg)
+	c.StopRun()
 }

+ 6 - 2
controllers/book.go

@@ -414,6 +414,7 @@ func (c *BookController) Create() {
 		book.Version 	= time.Now().Unix()
 		book.Cover 	= conf.GetDefaultCover()
 		book.Editor 	= "markdown"
+		book.Theme	= "default"
 
 		err := book.Insert()
 
@@ -421,8 +422,11 @@ func (c *BookController) Create() {
 			logs.Error("Insert => ",err)
 			c.JsonResult(6005,"保存项目失败")
 		}
-		bookResult := models.NewBookResult()
-		bookResult.FindByIdentify(book.Identify,c.Member.MemberId)
+		bookResult,err := models.NewBookResult().FindByIdentify(book.Identify,c.Member.MemberId)
+
+		if err != nil {
+			beego.Error(err)
+		}
 
 		c.JsonResult(0,"ok",bookResult)
 	}

+ 9 - 0
controllers/comment.go

@@ -0,0 +1,9 @@
+package controllers
+
+type CommentController struct {
+	BaseController
+}
+
+func (c *CommentController) Lists()  {
+	
+}

+ 23 - 8
controllers/document.go

@@ -21,8 +21,9 @@ type DocumentController struct {
 	BaseController
 }
 
+//判断用户是否可以阅读文档
 func isReadable (identify,token string,c *DocumentController) *models.BookResult {
-	book,err := models.NewBook().FindByFieldFirst("identify",identify)
+	book, err := models.NewBook().FindByFieldFirst("identify", identify)
 
 	if err != nil {
 		beego.Error(err)
@@ -33,7 +34,7 @@ func isReadable (identify,token string,c *DocumentController) *models.BookResult
 
 		is_ok := false
 
-		if c.Member != nil{
+		if c.Member != nil {
 			_, err := models.NewRelationship().FindForRoleId(book.BookId, c.Member.MemberId)
 			if err == nil {
 				is_ok = true
@@ -46,10 +47,10 @@ func isReadable (identify,token string,c *DocumentController) *models.BookResult
 			if token != "" && strings.EqualFold(token, book.PrivateToken) {
 				c.SetSession(identify, token)
 
-			}  else if token, ok := c.GetSession(identify).(string); !ok || !strings.EqualFold(token, book.PrivateToken) {
+			} else if token, ok := c.GetSession(identify).(string); !ok || !strings.EqualFold(token, book.PrivateToken) {
 				c.Abort("403")
 			}
-		}else{
+		} else {
 			c.Abort("403")
 		}
 
@@ -57,17 +58,30 @@ func isReadable (identify,token string,c *DocumentController) *models.BookResult
 	bookResult := book.ToBookResult()
 
 	if c.Member != nil {
-		rel ,err := models.NewRelationship().FindByBookIdAndMemberId(bookResult.BookId,c.Member.MemberId)
+		rel, err := models.NewRelationship().FindByBookIdAndMemberId(bookResult.BookId, c.Member.MemberId)
 
 		if err == nil {
-			bookResult.MemberId 		= rel.MemberId
-			bookResult.RoleId		= rel.RoleId
-			bookResult.RelationshipId	= rel.RelationshipId
+			bookResult.MemberId = rel.MemberId
+			bookResult.RoleId = rel.RoleId
+			bookResult.RelationshipId = rel.RelationshipId
 		}
+
+	}
+	//判断是否需要显示评论框
+	if bookResult.CommentStatus == "closed" {
+		bookResult.IsDisplayComment = false
+	} else if bookResult.CommentStatus == "open" {
+		bookResult.IsDisplayComment = true
+	} else if bookResult.CommentStatus == "group_only" {
+		bookResult.IsDisplayComment = bookResult.RelationshipId > 0
+	} else if bookResult.CommentStatus == "registered_only" {
+		bookResult.IsDisplayComment = true
 	}
+
 	return bookResult
 }
 
+
 func (c *DocumentController) Index()  {
 	c.Prepare()
 	identify := c.Ctx.Input.Param(":key")
@@ -78,6 +92,7 @@ func (c *DocumentController) Index()  {
 	}
 	bookResult := isReadable(identify,token,c)
 
+
 	c.TplName = "document/" + bookResult.Theme + "_read.tpl"
 
 	tree,err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId,0)

+ 0 - 1
main.go

@@ -3,7 +3,6 @@ package main
 import (
 	_ "github.com/go-sql-driver/mysql"
 	_ "github.com/lifei6671/godoc/routers"
-	_ "github.com/garyburd/redigo/redis"
 	"github.com/astaxie/beego"
 	"github.com/lifei6671/godoc/commands"
 )

+ 7 - 1
models/book.go

@@ -24,7 +24,7 @@ type Book struct {
 	PrivatelyOwned int	`orm:"column(privately_owned);type(int);default(0)" json:"privately_owned"`
 	// 当项目是私有时的访问Token.
 	PrivateToken string 	`orm:"column(private_token);size(500);null" json:"private_token"`
-	//评论状态:0 正常/1 已删除
+	//状态:0 正常/1 已删除
 	Status int 		`orm:"column(status);type(int);default(0)" json:"status"`
 	//默认的编辑器.
 	Editor string		`orm:"column(editor);size(50)" json:"editor"`
@@ -316,6 +316,12 @@ func (book *Book) ToBookResult() *BookResult {
 	m.Theme			= book.Theme
 
 
+	if book.Theme == ""{
+		m.Theme = "default"
+	}
+	if book.Editor == "" {
+		m.Editor = "markdown"
+	}
 	return m
 }
 

+ 1 - 0
models/book_result.go

@@ -34,6 +34,7 @@ type BookResult struct {
 	Status int
 
 	LastModifyText string 	`json:"last_modify_text"`
+	IsDisplayComment bool	`json:"is_display_comment"`
 }
 
 func NewBookResult() *BookResult {

+ 2 - 0
models/comment.go

@@ -29,6 +29,8 @@ type Comment struct {
 	UserAgent string		`orm:"column(user_agent);size(500)" json:"user_agent"`
 	// Parent 评论所属父级
 	ParentId int			`orm:"column(parent_id);type(int);default(0)" json:"parent_id"`
+	AgreeCount int			`orm:"column(agree_count);type(int);default(0)" json:"agree_count"`
+	AgainstCount int		`orm:"column(against_count);type(int);default(0)" json:"against_count"`
 }
 
 // TableName 获取对应数据库表名.

+ 32 - 0
static/css/kancloud.css

@@ -376,6 +376,38 @@ h6 {
     background-color: #136ec2;
     height: 100%
 }
+.m-comment{
+    margin: 30px auto 70px auto;
+}
+.m-comment .comment-result .title {
+    display: block;
+    font-size: 16px;
+    padding-bottom: 6px;
+    line-height: 1.5em;
+    border-bottom: 1px solid #ddddd9;
+    margin-bottom: 10px;
+}
+.w-textarea.textarea-full {
+    display: block;
+}
+.w-fragment.fragment-tip {
+    color: #999;
+}
+.w-textarea .textarea-input {
+    font-size: 14px;
+    padding: 5px 10px;
+    border-radius: 3px;
+    border: 1px solid #ccc;
+    line-height: 1.7em;
+    font-weight: 200;
+}
+.m-comment .comment-post .form .enter textarea {
+    resize: none;
+    min-height: 72px;
+    overflow: hidden;
+    width: 100%;
+}
+
 .editor-content {
     line-height: 1.7em;
     font-size: 14px

+ 1 - 1
static/css/main.css

@@ -292,7 +292,7 @@ textarea{
 
 /**************用户登录界面样式*******************/
 .login .login-body{
-    width: 370px;
+    width: 400px;
     padding: 5px 30px 25px 30px;
     margin: 0 auto;
     margin-top: 30px;

二进制
static/fonts/3Dumb.ttf


二进制
static/fonts/ApothecaryFont.ttf


二进制
static/fonts/Comismsh.ttf


二进制
static/fonts/D3Parallelism.ttf


二进制
static/fonts/DENNEthree-dee.ttf


二进制
static/fonts/DeborahFancyDress.ttf


二进制
static/fonts/Esquisito.ttf


二进制
static/fonts/Flim-Flam.ttf


二进制
static/fonts/KREMLINGEORGIANI3D.ttf


二进制
static/fonts/actionj.ttf


二进制
static/fonts/chromohv.ttf


+ 6 - 5
views/account/login.tpl

@@ -16,8 +16,8 @@
     <!-- 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/bootstrap/js/html5shiv.min.js"></script>
-    <script src="/static/bootstrap/js/respond.min.js"></script>
+    <script src="/static/html5shiv/3.7.3/html5shiv.min.js"></script>
+    <script src="/static/respond.js/1.4.2/respond.min.js"></script>
     <![endif]-->
     <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
     <script src="/static/jquery/1.12.4/jquery.min.js"></script>
@@ -53,18 +53,19 @@
                 </div>
                 {{if ne .ENABLED_CAPTCHA "false"}}
                 <div class="form-group">
-                    <div class="input-group">
+                    <div class="input-group" style="float: left;width: 195px;">
                         <div class="input-group-addon">
                             <i class="fa fa-check-square"></i>
                         </div>
                         <input type="text" name="code" id="code" class="form-control" style="width: 150px" maxlength="5" placeholder="验证码" autocomplete="off">&nbsp;
-                        <img id="captcha-img" src="" onclick="this.src='/verify?key=login&t='+(new Date()).getTime();" title="点击换一张">
                     </div>
+                    <img id="captcha-img" style="width: 140px;height: 40px;display: inline-block;float: right" src="{{urlfor "AccountController.Captcha"}}" onclick="this.src='{{urlfor "AccountController.Captcha"}}?key=login&t='+(new Date()).getTime();" title="点击换一张">
+                    <div class="clearfix"></div>
                 </div>
                 {{end}}
                 <div class="checkbox">
                     <label>
-                        <input type="checkbox" name="is_remember"> 保持登录
+                        <input type="checkbox" name="is_remember" value="yes"> 保持登录
                     </label>
                     <a href="{{urlfor "AccountController.FindPassword" }}" style="display: inline-block;float: right">忘记密码?</a>
                 </div>

+ 163 - 0
views/account/register.tpl

@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<html lang="zh-cn">
+<head>
+    <meta charset="utf-8">
+    <link rel="shortcut icon" href="/favicon.ico">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="renderer" content="webkit" />
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="author" content="SmartWiki" />
+    <title>用户登录 - Powered by MinDoc</title>
+
+    <!-- Bootstrap -->
+    <link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
+    <link href="/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]-->
+    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
+    <script src="/static/jquery/1.12.4/jquery.min.js"></script>
+</head>
+<body class="manual-container">
+<header class="navbar navbar-static-top smart-nav navbar-fixed-top manual-header" role="banner">
+    <div class="container">
+        <div class="navbar-header col-sm-12 col-md-6 col-lg-5">
+            <a href="/" class="navbar-brand">MinDoc</a>
+        </div>
+    </div>
+</header>
+<div class="container manual-body">
+    <div class="row login">
+        <div class="login-body">
+            <form role="form" method="post" id="registerForm">
+                <h3 class="text-center">用户注册</h3>
+                <div class="form-group">
+                    <div class="input-group">
+                        <div class="input-group-addon">
+                            <i class="fa fa-user"></i>
+                        </div>
+                        <input type="text" class="form-control" placeholder="用户名" name="account" id="account" autocomplete="off">
+                    </div>
+                </div>
+                <div class="form-group">
+                    <div class="input-group">
+                        <div class="input-group-addon">
+                            <i class="fa fa-lock"></i>
+                        </div>
+                        <input type="password" class="form-control" placeholder="密码" name="password1" id="password1" autocomplete="off">
+                    </div>
+                </div>
+                <div class="form-group">
+                    <div class="input-group">
+                        <div class="input-group-addon">
+                            <i class="fa fa-lock"></i>
+                        </div>
+                        <input type="password" class="form-control" placeholder="确认密码" name="password2" id="password2" autocomplete="off">
+                    </div>
+                </div>
+                <div class="form-group">
+                    <div class="input-group">
+                        <div class="input-group-addon" style="padding: 6px 9px;"><i class="fa fa-envelope"></i></div>
+                        <input type="email" class="form-control" placeholder="用户邮箱" name="email" id="email" autocomplete="off">
+                    </div>
+                </div>
+                {{if ne .ENABLED_CAPTCHA "false"}}
+                <div class="form-group">
+                    <div class="input-group" style="float: left;width: 195px;">
+                        <div class="input-group-addon">
+                            <i class="fa fa-check-square"></i>
+                        </div>
+                        <input type="text" name="code" id="code" class="form-control" style="width: 150px" maxlength="5" placeholder="验证码" autocomplete="off">&nbsp;
+                    </div>
+                    <img id="captcha-img" style="width: 140px;height: 40px;display: inline-block;float: right" src="{{urlfor "AccountController.Captcha"}}" onclick="this.src='{{urlfor "AccountController.Captcha"}}?key=login&t='+(new Date()).getTime();" title="点击换一张">
+                    <div class="clearfix"></div>
+                </div>
+                {{end}}
+                <div class="form-group">
+                    <button type="submit" id="btnRegister" class="btn btn-success" style="width: 100%"  data-loading-text="正在注册..." autocomplete="off">立即注册</button>
+                </div>
+                {{if ne .ENABLED_REGISTER "false"}}
+                <div class="form-group">
+                    已有账号?<a href="{{urlfor "AccountController.Register" }}" title="立即登录">立即登录</a>
+                </div>
+                {{end}}
+            </form>
+        </div>
+    </div>
+    <div class="clearfix"></div>
+</div>
+{{template "widgets/footer.tpl" .}}
+<!-- Include all compiled plugins (below), or include individual files as needed -->
+<script src="/static/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
+<script src="/static/layer/layer.js" type="text/javascript"></script>
+<script src="/static/js/jquery.form.js" type="text/javascript"></script>
+<script type="text/javascript">
+    $(function () {
+        $("#account,#password,#confirm_password,#code").on('focus',function () {
+            $(this).tooltip('destroy').parents('.form-group').removeClass('has-error');;
+        });
+
+        $(document).keyup(function (e) {
+            var event = document.all ? window.event : e;
+            if(event.keyCode === 13){
+                $("#btnRegister").trigger("click");
+            }
+        });
+        $("#registerForm").ajaxForm({
+            beforeSubmit : function () {
+                var account = $.trim($("#account").val());
+                var password = $.trim($("#password1").val());
+                var confirmPassword = $.trim($("#password2").val());
+                var code = $.trim($("#code").val());
+                var email = $.trim($("#email").val());
+
+                if(account === ""){
+                    $("#account").focus().tooltip({placement:"auto",title : "账号不能为空",trigger : 'manual'})
+                        .tooltip('show')
+                        .parents('.form-group').addClass('has-error');
+                    return false;
+
+                }else if(password === ""){
+                    $("#password").focus().tooltip({title : '密码不能为空',trigger : 'manual'})
+                        .tooltip('show')
+                        .parents('.form-group').addClass('has-error');
+                    return false;
+                }else if(confirmPassword !== password){
+                    $("#confirm_password").focus().tooltip({title : '确认密码不正确',trigger : 'manual'})
+                        .tooltip('show')
+                        .parents('.form-group').addClass('has-error');
+                    return false;
+                }else if(email === ""){
+                    $("#email").focus().tooltip({title : '邮箱不能为空',trigger : 'manual'})
+                        .tooltip('show')
+                        .parents('.form-group').addClass('has-error');
+                    return false;
+                }else if(code !== undefined && code === ""){
+                    $("#code").focus().tooltip({title : '验证码不能为空',trigger : 'manual'})
+                        .tooltip('show')
+                        .parents('.form-group').addClass('has-error');
+                    return false;
+                }else {
+
+                    $("button[type='submit']").button('loading');
+                }
+            },
+            success : function (res) {
+                $("button[type='submit']").button('reset');
+                if(res.errcode === 0){
+                    window.location = "{{urlfor "AccountController.Login"}}";
+                }else{
+                    $("#captcha-img").click();
+                    $("#code").val('');
+                    layer.msg(res.message);
+                }
+            }
+        });
+    });
+</script>
+</body>
+</html>

+ 25 - 0
views/document/default_read.tpl

@@ -110,7 +110,32 @@
                     <div class="article-body  {{if eq .Model.Editor "markdown"}}markdown-body editormd-preview-container{{else}}editor-content{{end}}"  id="page-content">
                         {{.Content}}
                     </div>
+                    {{if .Model.IsDisplayComment}}
+                    <div id="articleComment" class="m-comment">
+                        <div class="comment-result">
+                            <strong class="title">相关评论(<b class="comment-total">100</b>)</strong>
+                            <div class="comment-post">
+                                <form class="form" action="/comment/create" method="post">
+                                    <label class="enter w-textarea textarea-full">
+                                        <textarea class="textarea-input form-control" name="content" placeholder="文明上网,理性发言" style="height: 72px;"></textarea>
+                                        <input type="hidden" name="doc_id" value="118003"></label>
+                                    <div class="util cf">
+                                        <div class="pull-left"><span style="font-size: 12px;color: #999"> 支持Markdown语法 </span></div>
+                                        <div class="pull-right">
+                                            <span class="form-tip w-fragment fragment-tip">Ctrl + Enter快速发布</span>
+                                            <label class="form-submit w-btn btn-success btn-m">
+                                                <button class="btn btn-success btn-sm" type="submit">发布</button>
+                                            </label>
+                                        </div>
+                                    </div>
+                                </form>
+                            </div>
+                            <div class="clearfix"></div>
+                        </div>
+                    </div>
+                    {{end}}
                 </div>
+
             </div>
         </div>
         <div class="manual-progress"><b class="progress-bar"></b></div>