瀏覽代碼

allow edit issue and comment

Unknwon 9 年之前
父節點
當前提交
371572cf5f

+ 6 - 1
cmd/web.go

@@ -467,12 +467,17 @@ func runWeb(ctx *cli.Context) {
 
 			m.Combo("/:index/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
 			m.Group("/:index", func() {
-				m.Post("/title", repo.UpdateIssueTitle)
 				m.Post("/label", repo.UpdateIssueLabel)
 				m.Post("/milestone", repo.UpdateIssueMilestone)
 				m.Post("/assignee", repo.UpdateIssueAssignee)
 			}, reqRepoAdmin)
+
+			m.Group("/:index", func() {
+				m.Post("/title", repo.UpdateIssueTitle)
+				m.Post("/content", repo.UpdateIssueContent)
+			})
 		})
+		m.Post("/comments/:id", repo.UpdateCommentContent)
 		m.Group("/labels", func() {
 			m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
 			m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)

+ 1 - 1
gogs.go

@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.6.5.0819 Beta"
+const APP_VER = "0.6.5.0820 Beta"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 21 - 1
models/error.go

@@ -258,7 +258,27 @@ func IsErrIssueNotExist(err error) bool {
 }
 
 func (err ErrIssueNotExist) Error() string {
-	return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %4]", err.ID, err.RepoID, err.Index)
+	return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
+}
+
+// _________                                       __
+// \_   ___ \  ____   _____   _____   ____   _____/  |_
+// /    \  \/ /  _ \ /     \ /     \_/ __ \ /    \   __\
+// \     \___(  <_> )  Y Y  \  Y Y  \  ___/|   |  \  |
+//  \______  /\____/|__|_|  /__|_|  /\___  >___|  /__|
+//         \/             \/      \/     \/     \/
+
+type ErrCommentNotExist struct {
+	ID int64
+}
+
+func IsErrCommentNotExist(err error) bool {
+	_, ok := err.(ErrCommentNotExist)
+	return ok
+}
+
+func (err ErrCommentNotExist) Error() string {
+	return fmt.Sprintf("comment does not exist [id: %d]", err.ID)
 }
 
 // .____          ___.          .__

+ 21 - 10
models/issue.go

@@ -1363,6 +1363,14 @@ func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
 	}
 }
 
+func (c *Comment) AfterDelete() {
+	_, err := DeleteAttachmentsByComment(c.ID, true)
+
+	if err != nil {
+		log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
+	}
+}
+
 // HashTag returns unique hash tag for comment.
 func (c *Comment) HashTag() string {
 	return "issuecomment-" + com.ToStr(c.ID)
@@ -1473,11 +1481,16 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
 	return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, attachments)
 }
 
-// GetCommentById returns the comment with the given id
-func GetCommentById(id int64) (*Comment, error) {
+// GetCommentByID returns the comment by given ID.
+func GetCommentByID(id int64) (*Comment, error) {
 	c := new(Comment)
-	_, err := x.Id(id).Get(c)
-	return c, err
+	has, err := x.Id(id).Get(c)
+	if err != nil {
+		return nil, err
+	} else if !has {
+		return nil, ErrCommentNotExist{id}
+	}
+	return c, nil
 }
 
 // GetCommentsByIssueID returns all comments of issue by given ID.
@@ -1486,12 +1499,10 @@ func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
 	return comments, x.Where("issue_id=?", issueID).Asc("created").Find(&comments)
 }
 
-func (c *Comment) AfterDelete() {
-	_, err := DeleteAttachmentsByComment(c.ID, true)
-
-	if err != nil {
-		log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
-	}
+// UpdateComment updates information of comment.
+func UpdateComment(c *Comment) error {
+	_, err := x.Id(c.ID).AllCols().Update(c)
+	return err
 }
 
 // Attachment represent a attachment of issue/comment/release.

File diff suppressed because it is too large
+ 0 - 0
public/css/gogs.min.css


+ 74 - 10
public/js/gogs.js

@@ -2,26 +2,30 @@
 
 var csrf;
 
-function initCommentForm() {
-    if ($('.comment.form').length == 0) {
-        return
-    }
-
-    var $form = $('.comment.form');
-    $form.find('.tabular.menu .item').tab();
-    $form.find('.tabular.menu .item[data-tab="preview"]').click(function () {
+function initCommentPreviewTab($form) {
+    var $tab_menu = $form.find('.tabular.menu');
+    $tab_menu.find('.item').tab();
+    $tab_menu.find('.item[data-tab="' + $tab_menu.data('preview') + '"]').click(function () {
         var $this = $(this);
         $.post($this.data('url'), {
                 "_csrf": csrf,
                 "mode": "gfm",
                 "context": $this.data('context'),
-                "text": $form.find('.tab.segment[data-tab="write"] textarea').val()
+                "text": $form.find('.tab.segment[data-tab="' + $tab_menu.data('write') + '"] textarea').val()
             },
             function (data) {
-                $form.find('.tab.segment[data-tab="preview"]').html(data);
+                $form.find('.tab.segment[data-tab="' + $tab_menu.data('preview') + '"]').html(data);
             }
         );
     });
+}
+
+function initCommentForm() {
+    if ($('.comment.form').length == 0) {
+        return
+    }
+
+    initCommentPreviewTab($('.comment.form'));
 
     // Labels
     var $list = $('.ui.labels.list');
@@ -260,6 +264,66 @@ function initRepository() {
                 return false;
             });
 
+        // Edit issue or comment content
+        $('.edit-content').click(function () {
+            var $segment = $(this).parent().parent().next();
+            var $edit_content_zone = $segment.find('.edit-content-zone');
+            var $render_content = $segment.find('.render-content');
+            var $raw_content = $segment.find('.raw-content');
+            var $textarea;
+
+            // Setup new form
+            if ($edit_content_zone.html().length == 0) {
+                $edit_content_zone.html($('#edit-content-form').html());
+                $textarea = $segment.find('textarea');
+
+                // Give new write/preview data-tab name to distinguish from others
+                var $edit_content_form = $edit_content_zone.find('.ui.comment.form');
+                var $tabular_menu = $edit_content_form.find('.tabular.menu');
+                $tabular_menu.attr('data-write', $edit_content_zone.data('write'));
+                $tabular_menu.attr('data-preview', $edit_content_zone.data('preview'));
+                $tabular_menu.find('.write.item').attr('data-tab', $edit_content_zone.data('write'));
+                $tabular_menu.find('.preview.item').attr('data-tab', $edit_content_zone.data('preview'));
+                $edit_content_form.find('.write.segment').attr('data-tab', $edit_content_zone.data('write'));
+                $edit_content_form.find('.preview.segment').attr('data-tab', $edit_content_zone.data('preview'));
+
+                initCommentPreviewTab($edit_content_form);
+
+                $edit_content_zone.find('.cancel.button').click(function () {
+                    $render_content.show();
+                    $edit_content_zone.hide();
+                });
+                $edit_content_zone.find('.save.button').click(function () {
+                    $render_content.show();
+                    $edit_content_zone.hide();
+
+                    $.post($edit_content_zone.data('update-url'), {
+                            "_csrf": csrf,
+                            "content": $textarea.val(),
+                            "context": $edit_content_zone.data('context')
+                        },
+                        function (data) {
+                            if (data.length == 0) {
+                                $render_content.html($('#no-content').html());
+                            } else {
+                                $render_content.html(data.content);
+                            }
+                        });
+                });
+            } else {
+                $textarea = $segment.find('textarea');
+            }
+
+            // Show write/preview tab and copy raw content as needed
+            $edit_content_zone.show();
+            $render_content.hide();
+            if ($textarea.val().length == 0) {
+                $textarea.val($raw_content.text());
+            }
+            $textarea.focus();
+            return false;
+        });
+
         // Change status
         var $status_btn = $('#status-button');
         $('#content').keyup(function () {

+ 4 - 0
public/less/_repository.less

@@ -253,6 +253,10 @@
 						height: 200px;
 					}
 				}
+
+				.edit.buttons {
+					margin-top: 10px;
+				}
 			}
 			.event {
 				position: relative;

+ 62 - 7
routers/repo/issue.go

@@ -543,19 +543,16 @@ func UpdateIssueTitle(ctx *middleware.Context) {
 		return
 	}
 
-	if !ctx.IsSigned || ctx.User.Id != issue.PosterID || !ctx.Repo.IsAdmin() {
+	if !ctx.IsSigned || (ctx.User.Id != issue.PosterID && !ctx.Repo.IsAdmin()) {
 		ctx.Error(403)
 		return
 	}
 
-	title := ctx.Query("title")
-	if len(title) == 0 {
-		ctx.JSON(200, map[string]interface{}{
-			"title": issue.Name,
-		})
+	issue.Name = ctx.Query("title")
+	if len(issue.Name) == 0 {
+		ctx.Error(204)
 		return
 	}
-	issue.Name = title
 
 	if err := models.UpdateIssue(issue); err != nil {
 		ctx.Handle(500, "UpdateIssue", err)
@@ -567,6 +564,28 @@ func UpdateIssueTitle(ctx *middleware.Context) {
 	})
 }
 
+func UpdateIssueContent(ctx *middleware.Context) {
+	issue := getActionIssue(ctx)
+	if ctx.Written() {
+		return
+	}
+
+	if !ctx.IsSigned || (ctx.User.Id != issue.PosterID && !ctx.Repo.IsAdmin()) {
+		ctx.Error(403)
+		return
+	}
+
+	issue.Content = ctx.Query("content")
+	if err := models.UpdateIssue(issue); err != nil {
+		ctx.Handle(500, "UpdateIssue", err)
+		return
+	}
+
+	ctx.JSON(200, map[string]interface{}{
+		"content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Query("context"))),
+	})
+}
+
 func UpdateIssueLabel(ctx *middleware.Context) {
 	issue := getActionIssue(ctx)
 	if ctx.Written() {
@@ -748,6 +767,42 @@ func NewComment(ctx *middleware.Context, form auth.CreateCommentForm) {
 	ctx.Redirect(fmt.Sprintf("%s/issues/%d#%s", ctx.Repo.RepoLink, issue.Index, comment.HashTag()))
 }
 
+func UpdateCommentContent(ctx *middleware.Context) {
+	comment, err := models.GetCommentByID(ctx.ParamsInt64(":id"))
+	if err != nil {
+		if models.IsErrCommentNotExist(err) {
+			ctx.Error(404, "GetCommentByID")
+		} else {
+			ctx.Handle(500, "GetCommentByID", err)
+		}
+		return
+	}
+
+	if !ctx.IsSigned || (ctx.User.Id != comment.PosterID && !ctx.Repo.IsAdmin()) {
+		ctx.Error(403)
+		return
+	} else if comment.Type != models.COMMENT_TYPE_COMMENT {
+		ctx.Error(204)
+		return
+	}
+
+	comment.Content = ctx.Query("content")
+	if len(comment.Content) == 0 {
+		ctx.JSON(200, map[string]interface{}{
+			"content": "",
+		})
+		return
+	}
+	if err := models.UpdateComment(comment); err != nil {
+		ctx.Handle(500, "UpdateComment", err)
+		return
+	}
+
+	ctx.JSON(200, map[string]interface{}{
+		"content": string(base.RenderMarkdown([]byte(comment.Content), ctx.Query("context"))),
+	})
+}
+
 func Labels(ctx *middleware.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.labels")
 	ctx.Data["PageIsLabels"] = true

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.6.5.0819 Beta
+0.6.5.0820 Beta

+ 2 - 2
templates/repo/issue/comment_tab.tmpl

@@ -1,7 +1,7 @@
 <div class="field">
-  <div class="ui top attached tabular menu">
+  <div class="ui top attached tabular menu" data-write="write" data-preview="preview">
     <a class="active item" data-tab="write">{{.i18n.Tr "repo.release.write"}}</a>
-    <a class="item" data-tab="preview" data-url="/api/v1/markdown" data-context="{{.RepoLink}}">{{.i18n.Tr "repo.release.preview"}}</a>
+    <a class="item" data-tab="preview"  data-url="/api/v1/markdown" data-context="{{.RepoLink}}">{{.i18n.Tr "repo.release.preview"}}</a>
   </div>
   <div class="ui bottom attached active tab segment" data-tab="write">
     <textarea id="content" name="content"></textarea>

+ 45 - 14
templates/repo/issue/view_content.tmpl

@@ -50,16 +50,20 @@
 						<span class="text grey"><a {{if gt .Issue.Poster.Id 0}}href="{{.Issue.Poster.HomeLink}}"{{end}}>{{.Issue.Poster.Name}}</a> {{.i18n.Tr "repo.issues.commented_at" .Issue.HashTag $createdStr | Safe}}</span>
 					  <div class="ui right actions">
 					  	{{if .IsIssueOwner}}
-							<a class="item" href="#"><i class="octicon octicon-pencil"></i></a>
+							<a class="edit-content item" href="#"><i class="octicon octicon-pencil"></i></a>
 					  	{{end}}
 					  </div>
 					</div>
-			    <div class="ui attached segment markdown">
-			    	{{if .Issue.RenderedContent}}
-						{{.Issue.RenderedContent|Str2html}}
-			    	{{else}}
-						<span class="no-content">{{.i18n.Tr "repo.issues.no_content"}}</span>
-			    	{{end}}
+			    <div class="ui attached segment">
+			    	<div class="render-content markdown">
+				    	{{if .Issue.RenderedContent}}
+							{{.Issue.RenderedContent|Str2html}}
+				    	{{else}}
+							<span class="no-content">{{.i18n.Tr "repo.issues.no_content"}}</span>
+				    	{{end}}
+			    	</div>
+			    	<div class="raw-content hide">{{.Issue.Content}}</div>
+			    	<div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{.Link}}/content" data-context="{{.RepoLink}}"></div>
 	  			</div>
 	  			{{if .Issue.Attachments}}
 					<div class="ui bottom attached segment">
@@ -98,16 +102,20 @@
 					  	</div>
 					  	{{end}}
 					  	{{if or $.IsRepositoryAdmin (eq .Poster.Id $.SignedUserID)}}
-							<a class="item" href="#"><i class="octicon octicon-pencil"></i></a>
+							<a class="edit-content item" href="#" data-type="comment"><i class="octicon octicon-pencil"></i></a>
 					  	{{end}}
 					  </div>
 					</div>
-			    <div class="ui attached segment markdown">
-			    	{{if .RenderedContent}}
-						{{.RenderedContent|Str2html}}
-			    	{{else}}
-						<span class="no-content">{{$.i18n.Tr "repo.issues.no_content"}}</span>
-			    	{{end}}
+			    <div class="ui attached segment">
+			    	<div class="render-content markdown">
+				    	{{if .RenderedContent}}
+							{{.RenderedContent|Str2html}}
+				    	{{else}}
+							<span class="no-content">{{$.i18n.Tr "repo.issues.no_content"}}</span>
+				    	{{end}}
+			    	</div>
+			    	<div class="raw-content hide">{{.Content}}</div>
+			    	<div class="edit-content-zone hide" data-write="issuecomment-{{.ID}}-write" data-preview="issuecomment-{{.ID}}-preview" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}"></div>
 	  			</div>
 	  			{{if .Attachments}}
 					<div class="ui bottom attached segment">
@@ -264,4 +272,27 @@
 			</div>
 		</div>
 	</div>
+</div>
+
+<div class="hide" id="edit-content-form">
+	<div class="ui comment form">
+		<div class="ui top attached tabular menu">
+		  <a class="active write item">{{$.i18n.Tr "repo.release.write"}}</a>
+		  <a class="preview item" data-url="/api/v1/markdown" data-context="{{$.RepoLink}}">{{$.i18n.Tr "repo.release.preview"}}</a>
+		</div>
+		<div class="ui bottom attached active write tab segment">
+		  <textarea id="content" name="content"></textarea>
+		</div>
+		<div class="ui bottom attached tab preview segment markdown">
+		  {{$.i18n.Tr "repo.release.loading"}}
+		</div>
+    <div class="text right edit buttons">
+			<div class="ui basic blue cancel button">{{.i18n.Tr "repo.issues.cancel"}}</div>
+			<div class="ui green save button">{{.i18n.Tr "repo.issues.save"}}</div>
+    </div>
+	</div>
+</div>
+
+<div class="hide" id="no-content">
+	<span class="no-content">{{.i18n.Tr "repo.issues.no_content"}}</span>
 </div>

Some files were not shown because too many files changed in this diff