MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
Milestone *Milestone `xorm:"-"`
+ TimeID int64
+ Time *TrackedTime `xorm:"-"`
AssigneeID int64
RemovedAssignee bool
Assignee *User `xorm:"-"`
return err
}
+// LoadTime loads the associated time for a CommentTypeAddTimeManual
+func (c *Comment) LoadTime() error {
+ if c.Time != nil || c.TimeID == 0 {
+ return nil
+ }
+ var err error
+ c.Time, err = GetTrackedTimeByID(c.TimeID)
+ return err
+}
+
func (c *Comment) loadReactions(e Engine, repo *Repository) (err error) {
if c.Reactions != nil {
return nil
MilestoneID: opts.MilestoneID,
OldProjectID: opts.OldProjectID,
ProjectID: opts.ProjectID,
+ TimeID: opts.TimeID,
RemovedAssignee: opts.RemovedAssignee,
AssigneeID: opts.AssigneeID,
AssigneeTeamID: opts.AssigneeTeamID,
MilestoneID int64
OldProjectID int64
ProjectID int64
+ TimeID int64
AssigneeID int64
AssigneeTeamID int64
RemovedAssignee bool
Repo: issue.Repo,
Content: SecToTime(timediff),
Type: CommentTypeStopTracking,
+ TimeID: tt.ID,
}); err != nil {
return err
}
Doer: user,
Content: SecToTime(amount),
Type: CommentTypeAddTimeManual,
+ TimeID: t.ID,
}); err != nil {
return nil, err
}
NewMigration("Add Sorting to ProjectBoard table", addSortingColToProjectBoard),
// v172 -> v173
NewMigration("Add sessions table for go-chi/session", addSessionTable),
+ // v173 -> v174
+ NewMigration("Add time_id column to Comment", addTimeIDCommentColumn),
}
// GetCurrentDBVersion returns the current db version
--- /dev/null
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func addTimeIDCommentColumn(x *xorm.Engine) error {
+ type Comment struct {
+ TimeID int64
+ }
+
+ if err := x.Sync2(new(Comment)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+ return nil
+}
issues.cancel_tracking = Discard
issues.cancel_tracking_history = `cancelled time tracking %s`
issues.add_time = Manually Add Time
+issues.del_time = Delete this time log
issues.add_time_short = Add Time
issues.add_time_cancel = Cancel
issues.add_time_history = `added spent time %s`
ctx.ServerError("LoadPushCommits", err)
return
}
+ } else if comment.Type == models.CommentTypeAddTimeManual ||
+ comment.Type == models.CommentTypeStopTracking {
+ // drop error since times could be pruned from DB..
+ _ = comment.LoadTime()
}
}
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
- auth "code.gitea.io/gitea/modules/forms"
+ "code.gitea.io/gitea/modules/forms"
"code.gitea.io/gitea/modules/web"
)
// AddTimeManually tracks time manually
func AddTimeManually(c *context.Context) {
- form := web.GetForm(c).(*auth.AddTimeManuallyForm)
+ form := web.GetForm(c).(*forms.AddTimeManuallyForm)
issue := GetActionIssue(c)
if c.Written() {
return
c.Redirect(url, http.StatusSeeOther)
}
+
+// DeleteTime deletes tracked time
+func DeleteTime(c *context.Context) {
+ issue := GetActionIssue(c)
+ if c.Written() {
+ return
+ }
+ if !c.Repo.CanUseTimetracker(issue, c.User) {
+ c.NotFound("CanUseTimetracker", nil)
+ return
+ }
+
+ t, err := models.GetTrackedTimeByID(c.ParamsInt64(":timeid"))
+ if err != nil {
+ if models.IsErrNotExist(err) {
+ c.NotFound("time not found", err)
+ return
+ }
+ c.Error(http.StatusInternalServerError, "GetTrackedTimeByID", err.Error())
+ return
+ }
+
+ // only OP or admin may delete
+ if !c.IsSigned || (!c.IsUserSiteAdmin() && c.User.ID != t.UserID) {
+ c.Error(http.StatusForbidden, "not allowed")
+ return
+ }
+
+ if err = models.DeleteTime(t); err != nil {
+ c.ServerError("DeleteTime", err)
+ return
+ }
+
+ c.Flash.Success(c.Tr("repo.issues.del_time_history", models.SecToTime(t.Time)))
+ c.Redirect(issue.HTMLURL())
+}
m.Combo("/comments").Post(repo.MustAllowUserComment, bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
m.Group("/times", func() {
m.Post("/add", bindIgnErr(auth.AddTimeManuallyForm{}), repo.AddTimeManually)
+ m.Post("/{timeid}/delete", repo.DeleteTime)
m.Group("/stopwatch", func() {
m.Post("/toggle", repo.IssueStopwatch)
m.Post("/cancel", repo.CancelStopwatch)
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
{{$.i18n.Tr "repo.issues.stop_tracking_history" $createdStr | Safe}}
</span>
+ {{ template "repo/issue/view_content/comments_delete_time" Dict "ctx" $ "comment" . }}
<div class="detail">
{{svg "octicon-clock"}}
<span class="text grey">{{.Content}}</span>
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
{{$.i18n.Tr "repo.issues.add_time_history" $createdStr | Safe}}
</span>
+ {{ template "repo/issue/view_content/comments_delete_time" Dict "ctx" $ "comment" . }}
<div class="detail">
{{svg "octicon-clock"}}
<span class="text grey">{{.Content}}</span>
--- /dev/null
+{{ if .comment.Time }} {{/* compatibility with time comments made before v1.14 */}}
+ {{ if (not .comment.Time.Deleted) }}
+ {{ if (or .ctx.IsAdmin (and .ctx.IsSigned (eq .ctx.SignedUserID .comment.PosterID))) }}
+ <span class="ui float right">
+ <div class="ui mini modal issue-delete-time-modal" data-id="{{.comment.Time.ID}}">
+ <form method="POST" class="delete-time-form" action="{{.ctx.RepoLink}}/issues/{{.ctx.Issue.Index}}/times/{{.comment.TimeID}}/delete">
+ {{.ctx.CsrfTokenHtml}}
+ </form>
+ <div class="header">{{.ctx.i18n.Tr "repo.issues.del_time"}}</div>
+ <div class="actions">
+ <div class="ui red approve button">{{.ctx.i18n.Tr "repo.issues.context.delete"}}</div>
+ <div class="ui cancel button">{{.ctx.i18n.Tr "repo.issues.add_time_cancel"}}</div>
+ </div>
+ </div>
+ <button class="ui icon button compact mini issue-delete-time poping up" data-id="{{.comment.Time.ID}}" data-content="{{.ctx.i18n.Tr "repo.issues.del_time"}}" data-position="top right" data-variation="tiny inverted">
+ {{svg "octicon-trashcan"}}
+ </button>
+ </span>
+ {{end}}
+ {{end}}
+{{end}}
{{end}}
<div class="ui buttons two fluid">
<button class="ui button poping up issue-start-time" data-content='{{.i18n.Tr "repo.issues.start_tracking"}}' data-position="top center" data-variation="small inverted">{{.i18n.Tr "repo.issues.start_tracking_short"}}</button>
- <div class="ui mini modal">
+ <div class="ui mini modal issue-start-time-modal">
<div class="header">{{.i18n.Tr "repo.issues.add_time"}}</div>
<div class="content">
<form method="POST" id="add_time_manual_form" action="{{$.RepoLink}}/issues/{{.Issue.Index}}/times/add" class="ui action input fluid">
function initIssueTimetracking() {
$(document).on('click', '.issue-add-time', () => {
- $('.mini.modal').modal({
+ $('.issue-start-time-modal').modal({
duration: 200,
onApprove() {
$('#add_time_manual_form').trigger('submit');
}
}).modal('show');
+ $('.issue-start-time-modal input').on('keydown', (e) => {
+ if ((e.keyCode || e.key) === 13) {
+ $('#add_time_manual_form').trigger('submit');
+ }
+ });
});
$(document).on('click', '.issue-start-time, .issue-stop-time', () => {
$('#toggle_stopwatch_form').trigger('submit');
$(document).on('click', '.issue-cancel-time', () => {
$('#cancel_stopwatch_form').trigger('submit');
});
+ $(document).on('click', 'button.issue-delete-time', function () {
+ const sel = `.issue-delete-time-modal[data-id="${$(this).data('id')}"]`;
+ $(sel).modal({
+ duration: 200,
+ onApprove() {
+ $(`${sel} form`).trigger('submit');
+ }
+ }).modal('show');
+ });
}
function initFilterBranchTagDropdown(selector) {