| @@ -97,7 +97,6 @@ There are 5 ways to install Gogs: | |||
| - Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk). | |||
| - System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog). | |||
| - Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo. | |||
| - Thanks [gobuild.io](http://gobuild.io) for providing binary compile and download service. | |||
| - Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan. | |||
| ## Contributors | |||
| @@ -67,7 +67,6 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自 | |||
| - 基于 [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。 | |||
| - 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。 | |||
| - 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。 | |||
| - 感谢 [gobuild.io](http://gobuild.io) 提供二进制编译与下载服务。 | |||
| - 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。 | |||
| - 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。 | |||
| @@ -413,6 +413,13 @@ issues.reopen_issue = Reopen | |||
| issues.reopen_comment_issue = Reopen and comment | |||
| issues.create_comment = Comment | |||
| issues.closed_at = `closed at <a id="%[1]s" href="#%[1]s">%[2]s</a>` | |||
| issues.reopened_at = `reopened at <a id="%[1]s" href="#%[1]s">%[2]s</a>` | |||
| issues.poster = Poster | |||
| issues.admin = Admin | |||
| issues.owner = Owner | |||
| issues.not_signed_in = You must sign in to comment | |||
| issues.sign_up_for_free = Sign up for free | |||
| issues.sign_in_require_desc = to join this conversation. Already have an account? <a href="%s">Sign in to comment</a> | |||
| issues.label_title = Label name | |||
| issues.label_color = Label color | |||
| issues.label_count = %d labels | |||
| @@ -17,7 +17,7 @@ import ( | |||
| "github.com/gogits/gogs/modules/setting" | |||
| ) | |||
| const APP_VER = "0.6.4.0813 Beta" | |||
| const APP_VER = "0.6.4.0814 Beta" | |||
| func init() { | |||
| runtime.GOMAXPROCS(runtime.NumCPU()) | |||
| @@ -1231,6 +1231,15 @@ const ( | |||
| COMMENT_TYPE_PULL_REF | |||
| ) | |||
| type CommentTag int | |||
| const ( | |||
| COMMENT_TAG_NONE CommentTag = iota | |||
| COMMENT_TAG_POSTER | |||
| COMMENT_TAG_ADMIN | |||
| COMMENT_TAG_OWNER | |||
| ) | |||
| // Comment represents a comment in commit and issue page. | |||
| type Comment struct { | |||
| ID int64 `xorm:"pk autoincr"` | |||
| @@ -1245,6 +1254,9 @@ type Comment struct { | |||
| Created time.Time `xorm:"CREATED"` | |||
| Attachments []*Attachment `xorm:"-"` | |||
| // For view issue page. | |||
| ShowTag CommentTag `xorm:"-"` | |||
| } | |||
| // HashTag returns unique hash tag for comment. | |||
| @@ -247,8 +247,8 @@ func (repo *Repository) HasAccess(u *User) bool { | |||
| return has | |||
| } | |||
| func (repo *Repository) IsOwnedBy(u *User) bool { | |||
| return repo.OwnerID == u.Id | |||
| func (repo *Repository) IsOwnedBy(userID int64) bool { | |||
| return repo.OwnerID == userID | |||
| } | |||
| // DescriptionHtml does special handles to description and return HTML string. | |||
| @@ -222,6 +222,25 @@ func (u *User) UploadAvatar(data []byte) error { | |||
| return sess.Commit() | |||
| } | |||
| // IsAdminOfRepo returns true if user has admin or higher access of repository. | |||
| func (u *User) IsAdminOfRepo(repo *Repository) bool { | |||
| if err := repo.GetOwner(); err != nil { | |||
| log.Error(3, "GetOwner: %v", err) | |||
| return false | |||
| } | |||
| if repo.Owner.IsOrganization() { | |||
| has, err := HasAccess(u, repo, ACCESS_MODE_ADMIN) | |||
| if err != nil { | |||
| log.Error(3, "HasAccess: %v", err) | |||
| return false | |||
| } | |||
| return has | |||
| } | |||
| return repo.IsOwnedBy(u.Id) | |||
| } | |||
| // IsOrganization returns true if user is actually a organization. | |||
| func (u *User) IsOrganization() bool { | |||
| return u.Type == ORGANIZATION | |||
| @@ -5,12 +5,16 @@ | |||
| package middleware | |||
| import ( | |||
| "fmt" | |||
| "net/url" | |||
| "github.com/Unknwon/macaron" | |||
| "github.com/macaron-contrib/csrf" | |||
| "github.com/gogits/gogs/models" | |||
| "github.com/gogits/gogs/modules/auth" | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/log" | |||
| "github.com/gogits/gogs/modules/setting" | |||
| ) | |||
| @@ -21,6 +25,41 @@ type ToggleOptions struct { | |||
| DisableCsrf bool | |||
| } | |||
| // AutoSignIn reads cookie and try to auto-login. | |||
| func AutoSignIn(ctx *Context) (bool, error) { | |||
| uname := ctx.GetCookie(setting.CookieUserName) | |||
| if len(uname) == 0 { | |||
| return false, nil | |||
| } | |||
| isSucceed := false | |||
| defer func() { | |||
| if !isSucceed { | |||
| log.Trace("auto-login cookie cleared: %s", uname) | |||
| ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl) | |||
| ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl) | |||
| } | |||
| }() | |||
| u, err := models.GetUserByName(uname) | |||
| if err != nil { | |||
| if !models.IsErrUserNotExist(err) { | |||
| return false, fmt.Errorf("GetUserByName: %v", err) | |||
| } | |||
| return false, nil | |||
| } | |||
| if val, _ := ctx.GetSuperSecureCookie( | |||
| base.EncodeMd5(u.Rands+u.Passwd), setting.CookieRememberName); val != u.Name { | |||
| return false, nil | |||
| } | |||
| isSucceed = true | |||
| ctx.Session.Set("uid", u.Id) | |||
| ctx.Session.Set("uname", u.Name) | |||
| return true, nil | |||
| } | |||
| func Toggle(options *ToggleOptions) macaron.Handler { | |||
| return func(ctx *Context) { | |||
| // Cannot view any page before installation. | |||
| @@ -197,6 +197,14 @@ func Contexter() macaron.Handler { | |||
| ctx.Data["PageStartTime"] = time.Now() | |||
| // Check auto-signin. | |||
| if sess.Get("uid") == nil { | |||
| if _, err := AutoSignIn(ctx); err != nil { | |||
| ctx.Handle(500, "AutoSignIn", err) | |||
| return | |||
| } | |||
| } | |||
| // Get user from session if logined. | |||
| ctx.User, ctx.IsBasicAuth = auth.SignedInUser(ctx.Req.Request, ctx.Session) | |||
| @@ -180,6 +180,14 @@ | |||
| .avatar { | |||
| width: @comment-avatar-width; | |||
| } | |||
| .tag { | |||
| color: #767676; | |||
| margin-top: 3px; | |||
| padding: 2px 5px; | |||
| font-size: 12px; | |||
| border: 1px solid rgba(0,0,0,0.1); | |||
| border-radius: 3px; | |||
| } | |||
| .content { | |||
| margin-left: 4em; | |||
| .header { | |||
| @@ -427,7 +427,7 @@ func ViewIssue(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = issue.Name | |||
| if err = issue.GetPoster(); err != nil { | |||
| ctx.Handle(500, "GetPoster: %v", err) | |||
| ctx.Handle(500, "GetPoster", err) | |||
| return | |||
| } | |||
| issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) | |||
| @@ -441,7 +441,7 @@ func ViewIssue(ctx *middleware.Context) { | |||
| if ctx.IsSigned { | |||
| // Update issue-user. | |||
| if err = issue.ReadBy(ctx.User.Id); err != nil { | |||
| ctx.Handle(500, "ReadBy: %v", err) | |||
| ctx.Handle(500, "ReadBy", err) | |||
| return | |||
| } | |||
| @@ -475,10 +475,35 @@ func ViewIssue(ctx *middleware.Context) { | |||
| } | |||
| } | |||
| var ( | |||
| repo = ctx.Repo.Repository | |||
| tag models.CommentTag | |||
| ok bool | |||
| marked = make(map[int64]models.CommentTag) | |||
| comment *models.Comment | |||
| ) | |||
| // Render comments. | |||
| for i := range issue.Comments { | |||
| if issue.Comments[i].Type == models.COMMENT_TYPE_COMMENT { | |||
| issue.Comments[i].RenderedContent = string(base.RenderMarkdown([]byte(issue.Comments[i].Content), ctx.Repo.RepoLink)) | |||
| for _, comment = range issue.Comments { | |||
| if comment.Type == models.COMMENT_TYPE_COMMENT { | |||
| comment.RenderedContent = string(base.RenderMarkdown([]byte(comment.Content), ctx.Repo.RepoLink)) | |||
| // Check tag. | |||
| tag, ok = marked[comment.PosterID] | |||
| if ok { | |||
| comment.ShowTag = tag | |||
| continue | |||
| } | |||
| if repo.IsOwnedBy(comment.PosterID) || | |||
| (repo.Owner.IsOrganization() && repo.Owner.IsOwnedBy(comment.PosterID)) { | |||
| comment.ShowTag = models.COMMENT_TAG_OWNER | |||
| } else if comment.Poster.IsAdminOfRepo(repo) { | |||
| comment.ShowTag = models.COMMENT_TAG_ADMIN | |||
| } else if comment.PosterID == issue.PosterID { | |||
| comment.ShowTag = models.COMMENT_TAG_POSTER | |||
| } | |||
| marked[comment.PosterID] = comment.ShowTag | |||
| } | |||
| } | |||
| @@ -42,49 +42,22 @@ func SignIn(ctx *middleware.Context) { | |||
| } | |||
| // Check auto-login. | |||
| uname := ctx.GetCookie(setting.CookieUserName) | |||
| if len(uname) == 0 { | |||
| ctx.HTML(200, SIGNIN) | |||
| return | |||
| } | |||
| isSucceed := false | |||
| defer func() { | |||
| if !isSucceed { | |||
| log.Trace("auto-login cookie cleared: %s", uname) | |||
| ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl) | |||
| ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl) | |||
| return | |||
| } | |||
| }() | |||
| u, err := models.GetUserByName(uname) | |||
| isSucceed, err := middleware.AutoSignIn(ctx) | |||
| if err != nil { | |||
| if !models.IsErrUserNotExist(err) { | |||
| ctx.Handle(500, "GetUserByName", err) | |||
| } else { | |||
| ctx.HTML(200, SIGNIN) | |||
| } | |||
| return | |||
| } | |||
| if val, _ := ctx.GetSuperSecureCookie( | |||
| base.EncodeMd5(u.Rands+u.Passwd), setting.CookieRememberName); val != u.Name { | |||
| ctx.HTML(200, SIGNIN) | |||
| ctx.Handle(500, "AutoSignIn", err) | |||
| return | |||
| } | |||
| isSucceed = true | |||
| ctx.Session.Set("uid", u.Id) | |||
| ctx.Session.Set("uname", u.Name) | |||
| if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 { | |||
| ctx.SetCookie("redirect_to", "", -1, setting.AppSubUrl) | |||
| ctx.Redirect(redirectTo) | |||
| if isSucceed { | |||
| if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 { | |||
| ctx.SetCookie("redirect_to", "", -1, setting.AppSubUrl) | |||
| ctx.Redirect(redirectTo) | |||
| } | |||
| ctx.Redirect(setting.AppSubUrl + "/") | |||
| return | |||
| } | |||
| ctx.Redirect(setting.AppSubUrl + "/") | |||
| ctx.HTML(200, SIGNIN) | |||
| } | |||
| func SignInPost(ctx *middleware.Context, form auth.SignInForm) { | |||
| @@ -1 +1 @@ | |||
| 0.6.4.0813 Beta | |||
| 0.6.4.0814 Beta | |||
| @@ -22,8 +22,8 @@ | |||
| <div class="twelve wide column comment-list"> | |||
| <ui class="ui comments"> | |||
| <div class="comment"> | |||
| <a class="avatar" href="{{.SignedUser.HomeLink}}"> | |||
| <img src="{{.SignedUser.AvatarLink}}"> | |||
| <a class="avatar" href="{{.Issue.Poster.HomeLink}}"> | |||
| <img src="{{.Issue.Poster.AvatarLink}}"> | |||
| </a> | |||
| <div class="content"> | |||
| <div class="ui top attached header"> | |||
| @@ -63,6 +63,17 @@ | |||
| <div class="ui top attached header"> | |||
| <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}}</span> | |||
| <div class="ui right actions"> | |||
| {{if gt .ShowTag 0}} | |||
| <div class="tag"> | |||
| {{if eq .ShowTag 1}} | |||
| {{$.i18n.Tr "repo.issues.poster"}} | |||
| {{else if eq .ShowTag 2}} | |||
| {{$.i18n.Tr "repo.issues.admin"}} | |||
| {{else if eq .ShowTag 3}} | |||
| {{$.i18n.Tr "repo.issues.owner"}} | |||
| {{end}} | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| <div class="ui attached segment markdown"> | |||
| @@ -89,7 +100,7 @@ | |||
| <a class="ui avatar image" href="{{.Poster.HomeLink}}"> | |||
| <img src="{{.Poster.AvatarLink}}"> | |||
| </a> | |||
| <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.closed_at" .EventTag $createdStr | Safe}}</span> | |||
| <span class="text grey"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.reopened_at" .EventTag $createdStr | Safe}}</span> | |||
| </div> | |||
| {{else if eq .Type 2}} | |||
| <div class="event"> | |||
| @@ -103,6 +114,7 @@ | |||
| {{end}} | |||
| {{if .IsSigned}} | |||
| <div class="comment form"> | |||
| <a class="avatar" href="{{.SignedUser.HomeLink}}"> | |||
| <img src="{{.SignedUser.AvatarLink}}"> | |||
| @@ -129,6 +141,12 @@ | |||
| </form> | |||
| </div> | |||
| </div> | |||
| {{else}} | |||
| <div class="ui warning message"> | |||
| <a href="/user/sign_up" class="ui green button">{{.i18n.Tr "repo.issues.sign_up_for_free"}}</a> | |||
| {{.i18n.Tr "repo.issues.sign_in_require_desc" "/user/login" | Safe}} | |||
| </div> | |||
| {{end}} | |||
| </ui> | |||
| </div> | |||