From c04e40a1acb8c31b41410c0a777d4eee896a8066 Mon Sep 17 00:00:00 2001 From: Alexey Makhov Date: Thu, 5 Feb 2015 23:19:49 +0300 Subject: [PATCH] update less and revert css to dev branch state strings via locale try to add action ajax comment form loading code review backend new css & comments view little ajax form loading fix fix plus for commit line return pre tag. This is bad? but so on js changes tmpl fixes js fixes new js new html trash for development more trash for development new comments style comments is really works fix plus validation with i18n and line num fixes Migration for commit comments Commit comment markdown support js fixes and new button fix migration and cancel btn locale --- cmd/web.go | 2 + conf/locale/locale_de-DE.ini | 3 + conf/locale/locale_en-US.ini | 3 + conf/locale/locale_fr-CA.ini | 3 + conf/locale/locale_ja-JP.ini | 3 + conf/locale/locale_lv-LV.ini | 3 + conf/locale/locale_nl-NL.ini | 3 + conf/locale/locale_ru-RU.ini | 3 + conf/locale/locale_zh-CN.ini | 3 + models/action.go | 5 +- models/issue.go | 13 ++- models/migrations/migrations.go | 26 ++++- modules/base/template.go | 5 + modules/mailer/mail.go | 57 +++++++++- public/ng/css/gogs.css | 129 +++++++++++++++------ public/ng/js/gogs.js | 74 +++++++++++- public/ng/less/gogs/base.less | 3 + public/ng/less/gogs/issue.less | 11 +- public/ng/less/gogs/repository.less | 59 ++++++++-- routers/repo/commit.go | 139 ++++++++++++++++++++++- routers/repo/issue.go | 4 +- templates/ng/base/head.tmpl | 1 + templates/repo/comment_form.tmpl | 34 ++++++ templates/repo/diff.tmpl | 50 +++++++- templates/repo/issue/create.tmpl | 2 +- templates/repo/issue/milestone_edit.tmpl | 2 +- templates/repo/issue/milestone_new.tmpl | 2 +- templates/repo/issue/view.tmpl | 6 +- 28 files changed, 572 insertions(+), 76 deletions(-) create mode 100644 templates/repo/comment_form.tmpl diff --git a/cmd/web.go b/cmd/web.go index 55b6bf087..91d675245 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -373,6 +373,7 @@ func runWeb(ctx *cli.Context) { m.Group("/:username/:reponame", func() { m.Get("/settings", repo.Settings) m.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost) + m.Post("/commit/comment/:action/:commitId", repo.CreateCommitComment) m.Group("/settings", func() { m.Route("/collaboration", "GET,POST", repo.SettingsCollaboration) m.Get("/hooks", repo.Webhooks) @@ -439,6 +440,7 @@ func runWeb(ctx *cli.Context) { m.Get("/src/*", repo.Home) m.Get("/raw/*", repo.SingleDownload) m.Get("/commits/*", repo.RefCommits) + m.Get("/commit/comment/*", repo.GetCommentForm) m.Get("/commit/*", repo.Diff) }, middleware.RepoRef()) diff --git a/conf/locale/locale_de-DE.ini b/conf/locale/locale_de-DE.ini index dbf31a329..edc321134 100755 --- a/conf/locale/locale_de-DE.ini +++ b/conf/locale/locale_de-DE.ini @@ -323,6 +323,9 @@ commits.message=Mitteilung commits.date=Datum commits.older=Älter commits.newer=Neuer +commits.comment.add = Comment +commits.comment.comment_line = Comment line +commits.comment.required_field = Missing required field settings=Einstellungen settings.options=Optionen diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 660fb253b..474a63c96 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -325,6 +325,9 @@ commits.message = Message commits.date = Date commits.older = Older commits.newer = Newer +commits.comment.add = Comment +commits.comment.comment_line = Comment line +commits.comment.required_field = Missing required field settings = Settings settings.options = Options diff --git a/conf/locale/locale_fr-CA.ini b/conf/locale/locale_fr-CA.ini index 833d8e1dc..cb402b6ca 100755 --- a/conf/locale/locale_fr-CA.ini +++ b/conf/locale/locale_fr-CA.ini @@ -323,6 +323,9 @@ commits.message=Message commits.date=Date commits.older=Précédemment commits.newer=Récemment +commits.comment.add = Comment +commits.comment.comment_line = Comment line +commits.comment.required_field = Missing required field settings=Paramètres settings.options=Options diff --git a/conf/locale/locale_ja-JP.ini b/conf/locale/locale_ja-JP.ini index 1d1d61493..9b16e58d9 100755 --- a/conf/locale/locale_ja-JP.ini +++ b/conf/locale/locale_ja-JP.ini @@ -323,6 +323,9 @@ commits.message=メッセージ commits.date=日付 commits.older=古い commits.newer=新しい +commits.comment.add = Comment +commits.comment.comment_line = Comment line +commits.comment.required_field = Missing required field settings=設定 settings.options=オプション diff --git a/conf/locale/locale_lv-LV.ini b/conf/locale/locale_lv-LV.ini index 19373118f..25b931a59 100755 --- a/conf/locale/locale_lv-LV.ini +++ b/conf/locale/locale_lv-LV.ini @@ -323,6 +323,9 @@ commits.message=Ziņojums commits.date=Datums commits.older=Vecāki commits.newer=Jaunāki +commits.comment.add = Comment +commits.comment.comment_line = Comment line +commits.comment.required_field = Missing required field settings=Iestatījumi settings.options=Opcijas diff --git a/conf/locale/locale_nl-NL.ini b/conf/locale/locale_nl-NL.ini index f0bc7c70a..b8d23c5f1 100755 --- a/conf/locale/locale_nl-NL.ini +++ b/conf/locale/locale_nl-NL.ini @@ -323,6 +323,9 @@ commits.message=Bericht commits.date=Datum commits.older=Ouder commits.newer=Nieuwer +commits.comment.add = Comment +commits.comment.comment_line = Comment line +commits.comment.required_field = Missing required field settings=Instellingen settings.options=Opties diff --git a/conf/locale/locale_ru-RU.ini b/conf/locale/locale_ru-RU.ini index b0da5c550..d1e9bf9f0 100755 --- a/conf/locale/locale_ru-RU.ini +++ b/conf/locale/locale_ru-RU.ini @@ -323,6 +323,9 @@ commits.message=Сообщение commits.date=Дата commits.older=Раньше commits.newer=Новее +commits.comment.add = Отправить комментарий +commits.comment.comment_line = Комментировать строку +commits.comment.required_field = Не заполнено обязательное поле settings=Настройки settings.options=Опции diff --git a/conf/locale/locale_zh-CN.ini b/conf/locale/locale_zh-CN.ini index 49c013a9f..3edfe737d 100755 --- a/conf/locale/locale_zh-CN.ini +++ b/conf/locale/locale_zh-CN.ini @@ -323,6 +323,9 @@ commits.message=备注 commits.date=提交日期 commits.older=更旧的提交 commits.newer=更新的提交 +commits.comment.add = Comment +commits.comment.comment_line = Comment line +commits.comment.required_field = Missing required field settings=仓库设置 settings.options=基本设置 diff --git a/models/action.go b/models/action.go index 318a5f6ad..49e64dc18 100644 --- a/models/action.go +++ b/models/action.go @@ -33,6 +33,7 @@ const ( TRANSFER_REPO // 8 PUSH_TAG // 9 COMMENT_ISSUE // 10 + COMMENT_COMMIT // 11 ) var ( @@ -143,7 +144,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1) message := fmt.Sprintf(`%s`, url, c.Message) - if _, err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMIT, message, nil); err != nil { + if _, err = CreateComment(userId, issue.RepoId, issue.Id, "", "", COMMIT, message, nil); err != nil { return err } } @@ -194,7 +195,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com } // If commit happened in the referenced repository, it means the issue can be closed. - if _, err = CreateComment(userId, repoId, issue.Id, 0, 0, CLOSE, "", nil); err != nil { + if _, err = CreateComment(userId, repoId, issue.Id, "", "", CLOSE, "", nil); err != nil { return err } } diff --git a/models/issue.go b/models/issue.go index c756e4975..b8a6f2724 100644 --- a/models/issue.go +++ b/models/issue.go @@ -884,14 +884,14 @@ type Comment struct { PosterId int64 Poster *User `xorm:"-"` IssueId int64 - CommitId int64 - Line int64 + CommitId string + Line string Content string `xorm:"TEXT"` Created time.Time `xorm:"CREATED"` } // CreateComment creates comment of issue or commit. -func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string, attachments []int64) (*Comment, error) { +func CreateComment(userId, repoId, issueId int64, commitId, line string, cmtType CommentType, content string, attachments []int64) (*Comment, error) { sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { @@ -971,6 +971,13 @@ func (c *Comment) Attachments() []*Attachment { return a } +// GetCommitComments returns list of comment by given commit id. +func GetCommitComments(commitId string) ([]Comment, error) { + comments := make([]Comment, 0, 10) + err := x.Asc("created").Find(&comments, &Comment{CommitId: commitId}) + return comments, err +} + func (c *Comment) AfterDelete() { _, err := DeleteAttachmentsByComment(c.Id, true) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 3586e5d0b..7bc291d2f 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -2,6 +2,7 @@ package migrations import ( "errors" + "time" "github.com/go-xorm/xorm" ) @@ -16,7 +17,9 @@ type Version struct { // This is a sequence of migrations. Add new migrations to the bottom of the list. // If you want to "retire" a migration, replace it with "expiredMigration" -var migrations = []migration{} +var migrations = []migration{ + prepareToCommitComments, +} // Migrate database to current version func Migrate(x *xorm.Engine) error { @@ -51,3 +54,24 @@ func Migrate(x *xorm.Engine) error { func expiredMigration(x *xorm.Engine) error { return errors.New("You are migrating from a too old gogs version") } + +func prepareToCommitComments(x *xorm.Engine) error { + type Comment struct { + Id int64 + Type int64 + PosterId int64 + IssueId int64 + CommitId string + Line string + Content string `xorm:"TEXT"` + Created time.Time `xorm:"CREATED"` + } + x.Sync(new(Comment)) + + sql := `UPDATE comment SET commit_id = '', line = '' WHERE commit_id = '0' AND line = '0'` + _, err := x.Exec(sql) + if err != nil { + return err + } + return nil +} diff --git a/modules/base/template.go b/modules/base/template.go index f3fa13857..a08578528 100644 --- a/modules/base/template.go +++ b/modules/base/template.go @@ -155,6 +155,7 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{ }, "DiffTypeToStr": DiffTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr, + "DiffLinePosToStr": DiffLinePosToStr, "ShortSha": ShortSha, "Md5": EncodeMd5, "ActionContent2Commits": ActionContent2Commits, @@ -234,6 +235,10 @@ func DiffLineTypeToStr(diffType int) string { return "same" } +func DiffLinePosToStr(file int, section int, line int) string { + return fmt.Sprintf("%vL%v%v", file + 1, section, line) +} + func Oauth2Icon(t int) string { switch t { case 1: diff --git a/modules/mailer/mail.go b/modules/mailer/mail.go index ae8ce6531..004feedb8 100644 --- a/modules/mailer/mail.go +++ b/modules/mailer/mail.go @@ -154,8 +154,33 @@ func SendResetPasswdMail(r macaron.Render, u *models.User) { SendAsync(&msg) } -// SendIssueNotifyMail sends mail notification of all watchers of repository. +// SendIssueNotifyMail sends mail notification to all watchers of repository. func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) { + + subject := fmt.Sprintf("[%s] %s(#%d)", repo.Name, issue.Name, issue.Index) + content := fmt.Sprintf("%s
-
View it on Gogs.", + base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name), + setting.AppUrl, owner.Name, repo.Name, issue.Index) + + msgInfo := fmt.Sprintf("Subject: %s, send issue notify emails", subject) + + return SendNotifyMail(u, repo, subject, content, msgInfo) +} + +// SendCommentNotifyMail sends mail notification to all watchers of repository. +func SendCommentNotifyMail(u, owner *models.User, repo *models.Repository, comment *models.Comment) ([]string, error) { + + subject := fmt.Sprintf("[%s] New comment to commit %s", repo.Name, comment.CommitId) + content := fmt.Sprintf("%s
-
View it on Gogs.", + base.RenderSpecialLink([]byte(comment.Content), owner.Name+"/"+repo.Name), + setting.AppUrl, owner.Name, repo.Name, comment.CommitId) + + msgInfo := fmt.Sprintf("Subject: %s, send issue notify emails", subject) + + return SendNotifyMail(u, repo, subject, content, msgInfo) +} + +func SendNotifyMail(u *models.User, repo *models.Repository, subject, content, msgInfo string) ([]string, error) { ws, err := models.GetWatchers(repo.Id) if err != nil { return nil, errors.New("mail.NotifyWatchers(GetWatchers): " + err.Error()) @@ -178,12 +203,8 @@ func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue * return tos, nil } - subject := fmt.Sprintf("[%s] %s(#%d)", repo.Name, issue.Name, issue.Index) - content := fmt.Sprintf("%s
-
View it on Gogs.", - base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name), - setting.AppUrl, owner.Name, repo.Name, issue.Index) msg := NewMailMessageFrom(tos, u.Email, subject, content) - msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject) + msg.Info = msgInfo SendAsync(&msg) return tos, nil } @@ -213,6 +234,30 @@ func SendIssueMentionMail(r macaron.Render, u, owner *models.User, return nil } +func SendCommentMentionMail(r macaron.Render, u, owner *models.User, + repo *models.Repository, comment *models.Comment, tos []string) error { + + if len(tos) == 0 { + return nil + } + + subject := fmt.Sprintf("[%s] Commit %s", repo.Name, comment.CommitId) + + data := GetMailTmplData(nil) + data["IssueLink"] = fmt.Sprintf("%s/%s/commit/%s", owner.Name, repo.Name, comment.CommitId) + data["Subject"] = subject + + body, err := r.HTMLString(string(NOTIFY_MENTION), data) + if err != nil { + return fmt.Errorf("mail.SendCommentMentionMail(fail to render): %v", err) + } + + msg := NewMailMessageFrom(tos, u.Email, subject, body) + msg.Info = fmt.Sprintf("Subject: %s, send comment mention emails", subject) + SendAsync(&msg) + return nil +} + // SendCollaboratorMail sends mail notification to new collaborator. func SendCollaboratorMail(r macaron.Render, u, owner *models.User, repo *models.Repository) error { diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css index 6a6c10ddb..957c6d1ec 100644 --- a/public/ng/css/gogs.css +++ b/public/ng/css/gogs.css @@ -278,6 +278,9 @@ img.avatar-100 { padding-left: 0; list-style: none; } +.text-danger { + color: #a94442; +} .markdown { background-color: white; font-size: 16px; @@ -1449,42 +1452,9 @@ The register and sign-in page style cursor: pointer; display: block; } -.code-view .lines-code b:hover { - width: 22px; - height: 22px; - line-height: 22px; - font-size: 22px; - margin: -1px -12px -2px -11px; -} -.code-view .lines-code b { - position: relative; - z-index: 5; - float: left; - width: 20px; - height: 20px; - margin: 0px -10px -1px -10px; - line-height: 20px; - color: #fff; - text-align: center; - text-indent: 0; - cursor: pointer; - background-color: #4183c4; - background-color: #4183c4; - background-image: -webkit-linear-gradient(#5490ca, #4183c4); - background-image: linear-gradient(#5490ca, #4183c4); - background-repeat: repeat-x; - border-radius: 3px; - box-shadow: 0 1px 4px rgba(0,0,0,0.15); - opacity: 0; - -webkit-transform: scale(0.8, 0.8); - -ms-transform: scale(0.8, 0.8); - transform: scale(0.8, 0.8); - -webkit-transition: -webkit-transform 0.1s ease-in-out; - transition: transform 0.1s ease-in-out; - font-size: 20px; -} -.code-view .lines-code b.ishovered { - opacity:1; + +.code-view .comment { + background-color: #fafafa; } .code-view .lines-code > pre { border: none; @@ -1499,6 +1469,85 @@ The register and sign-in page style .code-view .lines-code > pre > ol.linenums > li.active { background: #ffffdd; } +.code-view .lines-code > b { + position: relative; + float: left; + width: 16px; + height: 16px; + margin: 2px -8px 0px -8px; + line-height: 16px; + color: #fff; + text-align: center; + cursor: pointer; + background-color: #428bca; + border-radius: 2px; + opacity: 0; + font-size: 16px; +} +.code-view .lines-code > b.ishovered { + opacity: 1; +} +.code-view .lines-code > b:hover { + width: 18px; + height: 18px; + line-height: 18px; + font-size: 20px; + margin: 1px -10px 0px -9px; +} +.code-view .lines-code .commit-comment { + margin: 5px; + max-width: 800px; +} +.code-view .lines-code .commit-comment .label-default { + background-color: #999; + font-size: 80%; + padding: 5px; + line-height: 10px; + margin-right: 5px; +} +.code-view .lines-code .commit-comment .commit-content { + border-bottom-width: 1px; +} +.code-view .lines-code .commit-comment .user.pull-left { + margin: 5px; +} +.code-view .lines-code .commit-comment .user .avatar { + width: 26px; + height: 26px; + margin-right: 3px; +} +.code-view .lines-code .commit-comment .panel-heading { + padding-top: 4px; + padding-bottom: 0px; + font-weight: normal; + background-color: #FAFAFA; + border-bottom: 1px solid #DDD; +} +.code-view .lines-code .commit-comment .panel-default .panel-heading { + font-weight: bold; +} +.code-view .lines-code .commit-comment .markdown { + font-size: 14px; + padding: 12px 15px; +} +.code-view .panel-default { + border-color: #d8d8d8; +} +.code-view .alert { + padding: 8px; + margin-bottom: 18px; + border-radius: 2px; + border: 1px solid transparent; + margin: 5px; +} +.code-view .alert-warning { + background-color: #f9edbe; + border-color: #f0c36d; + color: #333; +} +.code-view #commit-conversation { + margin: 10px; +} .repo-setting-zone { padding: 30px; } @@ -1664,6 +1713,10 @@ The register and sign-in page style background-color: #ffe2dd !important; border-color: #e9aeae !important; } +.diff-file-box .code-diff tbody tr.del-code td.selected-line, +.diff-file-box .code-diff tbody tr.del-code td.selected-line pre { + background-color: #ffffdd !important; +} .diff-file-box .code-diff tbody tr.add-code td, .diff-file-box .code-diff tbody tr.add-code pre { background-color: #d1ffd6 !important; @@ -1678,6 +1731,10 @@ The register and sign-in page style background-color: #FFF8D2 !important; border-color: #F0DB88 !important; } +.diff-file-box .code-diff tbody tr.add-comment:hover td, +.diff-file-box .code-diff tbody tr.add-comment:hover pre { + background-color: #FFFFFF !important; +} .compare-head-box { margin-top: 10px; } diff --git a/public/ng/js/gogs.js b/public/ng/js/gogs.js index 92f560ecf..0f87adb49 100644 --- a/public/ng/js/gogs.js +++ b/public/ng/js/gogs.js @@ -238,6 +238,11 @@ var Gogs = {}; $.changeHash('#' + $select.attr('rel')); } + function prepareToForm() { + $('.add-comment').hide('fast', function(){ $(this).remove(); }); + $('button.answer').show(); + } + $(document).on('click', '.code-diff .lines-num span', function (e) { var $select = $(this); var $list = $select.parent().siblings('.lines-code').parents().find('td.lines-num > span'); @@ -249,6 +254,57 @@ var Gogs = {}; $.deSelect(); }); + $('.code-diff .lines-code > b, .code-diff .lines-code > button.answer').click(function () { + prepareToForm(); + var commit = document.location.href.match(/([a-zA-Z0-9:\/\/]+)\/commit\/([a-z0-9]+)/); + var lineNum; + if ($(this).prop("tagName") == "BUTTON") { + lineNum = $(this).attr('rel'); + } else { + lineNum = $(this).parent().prev().find('span').attr('rel'); + } + $('button[rel='+lineNum+']').fadeOut(); + lineNum = lineNum.substr(5); + var commentTr = $(".comment-"+lineNum); + if (commit) { + var elem = (commentTr.length > 0) ? commentTr : $(this).parents('tr'); + var url = commit[1] + '/commit/comment/' + commit[2]; + elem.after( + $('').load(url + '?line=' + lineNum, function () { + $('.menu-line.add-nav').tabs(); + $('#pull-commit-preview').markdown_preview(".commit-add-comment"); + $('body').animate({ + scrollTop: $(this).offset().top - 33 // height of button + }, 1000); + }) + ); + } + }); + + $('.code-diff').on('click', '#cancel-commit-conversation', function () { + prepareToForm(); + return false; + }); + + $('.code-diff').on('submit', '#commit-add-comment-form', function () { + var url = $(this).attr('action'); + $.ajax({ + url: url, + data: $(this).serialize(), + dataType: "json", + method: "post", + success: function (json) { + if (json.ok && json.data.length) { + window.location.href = json.data; + location.reload(); + } else { + $('#submit-error').html(json.error); + } + } + }); + return false; + }); + $('.code-diff .lines-code > pre').each(function () { var $pre = $(this); var $lineCode = $pre.parent(); @@ -261,10 +317,16 @@ var Gogs = {}; } }); - $('.code-diff .lines-code > pre').hover(function () { - var $b = $(this).prev(); + $('.code-diff .add-code .lines-code > pre, \ + .code-diff .del-code .lines-code > pre, \ + .code-diff .add-code .lines-code > b, \ + .code-diff .del-code .lines-code > b, \ + .code-diff .add-code .lines-num, \ + .code-diff .del-code .lines-num').hover(function () { + var $b = $(this).parents('tr').find('b'); $b.addClass('ishovered'); }); + $('.code-diff tr').mouseleave(function () { $('.code-diff .lines-code > b').removeClass('ishovered'); }); @@ -284,6 +346,14 @@ var Gogs = {}; $first = $list.filter('[rel=diff-' + m[1] + m[2] + ']'); selectRange($list, $first); $("html, body").scrollTop($first.offset().top - 200); + return; + } + m = window.location.hash.match(/^#comment-(\d+)$/); + if (m) { + $("html, body").animate({ + scrollTop: $('a[name=comment-'+m[1]+']').offset().top + }, 1000); + return; } }).trigger('hashchange'); }; diff --git a/public/ng/less/gogs/base.less b/public/ng/less/gogs/base.less index ed5f2fee5..83d38e2f6 100644 --- a/public/ng/less/gogs/base.less +++ b/public/ng/less/gogs/base.less @@ -298,4 +298,7 @@ clear: both; .list-unstyled { padding-left: 0; list-style: none; +} +.text-danger { + color: #a94442; } \ No newline at end of file diff --git a/public/ng/less/gogs/issue.less b/public/ng/less/gogs/issue.less index b950869cd..08136a679 100644 --- a/public/ng/less/gogs/issue.less +++ b/public/ng/less/gogs/issue.less @@ -86,6 +86,7 @@ } #pr-commit, #pr-file-diff, +#commit-add-comment-preview, #issue-add-comment-preview { display: none; } @@ -93,14 +94,16 @@ padding-right: 30px; box-sizing: border-box; } +.commit-comment, .issue-comment, .issue-commit, .issue-line, .issue-merge, +.commit-add-comment, .issue-add-comment { margin-bottom: 24px; } -.issue-comment { +.commit-comment, .issue-comment { .author-avatar { img { margin-right: 12px; @@ -184,7 +187,8 @@ height: 4px; background-color: #E6E6E6; } -.issue-add-comment { +.issue-add-comment, +.commit-add-comment { .panel { margin-left: 60px; margin-top: -40px; @@ -215,7 +219,8 @@ } } } -textarea#issue-add-content { +textarea#issue-add-content, +textarea#commit-add-content { width: 100%; box-sizing: border-box; height: 120px; diff --git a/public/ng/less/gogs/repository.less b/public/ng/less/gogs/repository.less index e48f5c871..dfe2f8457 100644 --- a/public/ng/less/gogs/repository.less +++ b/public/ng/less/gogs/repository.less @@ -477,16 +477,51 @@ display: block; } } - .lines-code > pre { - border: none; - border-left: 1px solid #ddd; - > ol.linenums > li { - padding: 0 10px; - line-height: 20px; - &.active { - background: #ffffdd; + .lines-code { + > pre { + border: none; + border-left: 1px solid #ddd; + border-radius: 0; + padding-left: 15px; + > ol.linenums > li { + padding: 0 10px; + line-height: 20px; + &.active { + background: #ffffdd; + } } } + > b { + position: relative; + float: left; + width: 16px; + height: 16px; + margin: 2px -8px 0px -8px; + line-height: 16px; + color: #fff; + text-align: center; + cursor: pointer; + background-color: #428bca; + border-radius: 2px; + opacity: 0; + font-size: 16px; + &.ishovered { + opacity: 1; + } + &:hover { + width: 18px; + height: 18px; + line-height: 18px; + font-size: 20px; + margin: 1px -10px 0px -9px; + } + } + button.answer { + margin: 0 0 10px 70px; + } + } + .commit-comment { + margin: 10px; } } .repo-setting-zone { @@ -659,6 +694,9 @@ background-color: #ffe2dd !important; border-color: #e9aeae !important; } + td.selected-line, td.selected-line pre { + background-color: #ffffdd !important; + } } &.add-code { td, pre { @@ -675,6 +713,11 @@ border-color: #F0DB88 !important; } } + &.add-comment:hover, &.comment:hover { + td, pre { + background-color: #FFFFFF !important; + } + } } } } diff --git a/routers/repo/commit.go b/routers/repo/commit.go index e92ec8c88..9d1f47e0b 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -6,20 +6,27 @@ package repo import ( "container/list" + "errors" + "fmt" "path" + "regexp" + "strings" "github.com/Unknwon/com" "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/git" + "github.com/gogits/gogs/modules/log" + "github.com/gogits/gogs/modules/mailer" "github.com/gogits/gogs/modules/middleware" "github.com/gogits/gogs/modules/setting" ) const ( - COMMITS base.TplName = "repo/commits" - DIFF base.TplName = "repo/diff" + COMMITS base.TplName = "repo/commits" + DIFF base.TplName = "repo/diff" + COMMENT_FORM base.TplName = "repo/comment_form" ) func RefCommits(ctx *middleware.Context) { @@ -243,6 +250,33 @@ func Diff(ctx *middleware.Context) { } } + // Get comments. + comments, err := models.GetCommitComments(commitId) + if err != nil { + ctx.Handle(500, "commit.GetDiffCommit(GetCommitComments): %v", err) + return + } + + // Get posters. + commentsMap := make(map[string]map[int]models.Comment) + for i := range comments { + u, err := models.GetUserById(comments[i].PosterId) + if err != nil { + ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err) + return + } + comments[i].Poster = u + + if comments[i].Type == models.COMMENT { + comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink)) + } + + if _, ok := commentsMap[comments[i].Line]; !ok { + commentsMap[comments[i].Line] = make(map[int]models.Comment) + } + commentsMap[comments[i].Line][i] = comments[i] + } + ctx.Data["Username"] = userName ctx.Data["Reponame"] = repoName ctx.Data["IsImageFile"] = isImageFile @@ -251,6 +285,7 @@ func Diff(ctx *middleware.Context) { ctx.Data["Author"] = models.ValidateCommitWithEmail(commit) ctx.Data["Diff"] = diff ctx.Data["Parents"] = parents + ctx.Data["Comments"] = commentsMap ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0 ctx.Data["SourcePath"] = setting.AppSubUrl + "/" + path.Join(userName, repoName, "src", commitId) ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(userName, repoName, "raw", commitId) @@ -319,3 +354,103 @@ func CompareDiff(ctx *middleware.Context) { ctx.Data["RawPath"] = setting.AppSubUrl + "/" + path.Join(userName, repoName, "raw", afterCommitId) ctx.HTML(200, DIFF) } + +func GetCommentForm(ctx *middleware.Context) { + ctx.Data["Repo"] = ctx.Repo + ctx.Data["Line"] = ctx.Query("line") + ctx.HTML(200, COMMENT_FORM) +} + +func CreateCommitComment(ctx *middleware.Context) { + + send := func(status int, data interface{}, err error) { + if err != nil { + log.Error(4, "commit.Comment(?): %s", err) + + ctx.JSON(status, map[string]interface{}{ + "ok": false, + "status": status, + "error": err.Error(), + }) + } else { + ctx.JSON(status, map[string]interface{}{ + "ok": true, + "status": status, + "data": data, + }) + } + } + var comment *models.Comment + + commitId := ctx.ParamsEscape(":commitId") + content := ctx.Query("content") + line := ctx.Query("line") + lineRe, err := regexp.Compile("[0-9]+L[0-9]+") + fmt.Println(ctx.Locale.Tr("repo.commits.comment.required_field")) + if len(content) > 0 && lineRe.MatchString(line) { + switch ctx.Params(":action") { + case "new": + if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, 0, commitId, line, models.COMMENT, content, nil); err != nil { + send(500, nil, err) + return + } + log.Trace("%s Comment created: %s", ctx.Req.RequestURI, commitId) + default: + ctx.Handle(404, "commit.Comment", err) + return + } + } else { + err := errors.New(ctx.Locale.Tr("repo.commits.comment.required_field")) + send(200, err.Error(), err) + return + } + // Update mentions. + ms := base.MentionPattern.FindAllString(comment.Content, -1) + if len(ms) > 0 { + for i := range ms { + ms[i] = ms[i][1:] + } + } + // Notify watchers. + act := &models.Action{ + ActUserId: ctx.User.Id, + ActUserName: ctx.User.LowerName, + ActEmail: ctx.User.Email, + OpType: models.COMMENT_COMMIT, + Content: fmt.Sprintf("%s|%s", commitId, strings.Split(content, "\n")[0]), + RepoId: ctx.Repo.Repository.Id, + RepoUserName: ctx.Repo.Owner.LowerName, + RepoName: ctx.Repo.Repository.LowerName, + } + if err = models.NotifyWatchers(act); err != nil { + send(500, nil, err) + return + } + + // Mail watchers and mentions. + if setting.Service.EnableNotifyMail { + comment.Content = content + tos, err := mailer.SendCommentNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, comment) + if err != nil { + send(500, nil, err) + return + } + + tos = append(tos, ctx.User.LowerName) + newTos := make([]string, 0, len(ms)) + for _, m := range ms { + if com.IsSliceContainsStr(tos, m) { + continue + } + + newTos = append(newTos, m) + } + if err = mailer.SendCommentMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner, + ctx.Repo.Repository, comment, models.GetUserEmailsByNames(newTos)); err != nil { + send(500, nil, err) + return + } + } + + send(200, fmt.Sprintf("%s/commit/%s#comment-%d", ctx.Repo.RepoLink, commitId, comment.Id), nil) +} diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 999fd0a89..57d254855 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -779,7 +779,7 @@ func Comment(ctx *middleware.Context) { cmtType = models.REOPEN } - if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, "", nil); err != nil { + if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, "", "", cmtType, "", nil); err != nil { send(200, nil, err) return } @@ -795,7 +795,7 @@ func Comment(ctx *middleware.Context) { if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 { switch ctx.Params(":action") { case "new": - if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.COMMENT, content, nil); err != nil { + if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, "", "", models.COMMENT, content, nil); err != nil { send(500, nil, err) return } diff --git a/templates/ng/base/head.tmpl b/templates/ng/base/head.tmpl index 40a7d28ff..d8db64128 100644 --- a/templates/ng/base/head.tmpl +++ b/templates/ng/base/head.tmpl @@ -27,6 +27,7 @@ + diff --git a/templates/repo/comment_form.tmpl b/templates/repo/comment_form.tmpl new file mode 100644 index 000000000..3e82a989b --- /dev/null +++ b/templates/repo/comment_form.tmpl @@ -0,0 +1,34 @@ + +{{if .SignedUser}} +
+
+
+ +
+ +
+
+
+ {{.CsrfTokenHtml}} + + +

+    + +

+
+
+ preview +
+
+
+
+
+
+{{else}}
Sign up for free to join this conversation. Already have an account? Sign in to comment
{{end}} + \ No newline at end of file diff --git a/templates/repo/diff.tmpl b/templates/repo/diff.tmpl index 277aacead..04044d317 100644 --- a/templates/repo/diff.tmpl +++ b/templates/repo/diff.tmpl @@ -37,7 +37,7 @@ {{.Commit.Author.Name}} {{end}} - {{TimeSince .Commit.Author.When $.Lang}} + {{TimeSince .Commit.Author.When $.Lang}}

@@ -102,19 +102,57 @@ {{range $j, $section := $file.Sections}} - {{range $k, $line := $section.Lines}} + {{range $k, $line := $section.Lines}} + {{$lineNum := (DiffLinePosToStr $i $j $k)}} + {{if index $.Comments $lineNum}} + + + + {{end}} {{end}} {{end}} @@ -122,7 +160,7 @@ {{end}} -
+
{{end}} {{end}} diff --git a/templates/repo/issue/create.tmpl b/templates/repo/issue/create.tmpl index a2471a063..595f8026b 100644 --- a/templates/repo/issue/create.tmpl +++ b/templates/repo/issue/create.tmpl @@ -86,7 +86,7 @@ {{end}}
- Content with Markdown + {{.i18n.Tr "repo.release.content_with_md" "https://help.github.com/articles/markdown-basics" | Str2html}}
- Content with Markdown + {{.i18n.Tr "repo.release.content_with_md" "https://help.github.com/articles/markdown-basics" | Str2html}}
- Content with Markdown + {{.i18n.Tr "repo.release.content_with_md" "https://help.github.com/articles/markdown-basics" | Str2html}}
- {{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}} + {{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}} - {{if $line.RightIdx}}{{$line.RightIdx}}{{end}} + {{if $line.RightIdx}}{{$line.RightIdx}}{{end}} - + + {{if $.SignedUser}}{{if $k}}{{end}}{{end}}
{{$line.Content}}
+ {{range (index $.Comments $lineNum)}} + +
+ + + +
+

{{.Poster.Name}} + {{TimeSince .Created $.Lang}} + + {{if $.IsRepositoryOwner}} + {{$.i18n.Tr "repo.owner"}} + {{end}} + + + +

+ +
+ {{if len .Content}} + {{Str2html .Content}} + {{else}} + No comment entered + {{end}} +
+
+
+ {{end}} + +