Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 900ee64

Browse files
authoredJan 21, 2021
Merge branch 'master' into dependabot/npm_and_yarn/gsap-3.6.0
2 parents 35817dc + b5570d3 commit 900ee64

File tree

20 files changed

+329
-28
lines changed

20 files changed

+329
-28
lines changed
 

‎docs/content/doc/installation/with-docker.en-us.md

+11-7
Original file line numberDiff line numberDiff line change
@@ -345,19 +345,23 @@ ports:
345345
- "127.0.0.1:2222:22"
346346
```
347347

348-
In addition, `/home/git/.ssh/authorized_keys` on the host needs to be modified. It needs to act in the same way as `authorized_keys` within the Gitea container. Therefore add
348+
In addition, `/home/git/.ssh/authorized_keys` on the host needs to be modified. It needs to act in the same way as `authorized_keys` within the Gitea container. Therefore add the public key of the key you created above ("Gitea Host Key") to `~/git/.ssh/authorized_keys`.
349+
This can be done via `echo "$(cat /home/git/.ssh/id_rsa.pub)" >> /home/git/.ssh/authorized_keys`.
350+
Important: The pubkey from the `git` user needs to be added "as is" while all other pubkeys added via the Gitea web interface will be prefixed with `command="/app [...]`.
349351

350-
```bash
351-
command="/app/gitea/gitea --config=/data/gitea/conf/app.ini serv key-1",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa <YOUR_SSH_PUBKEY>
352-
```
352+
The file should then look somewhat like
353353

354-
and replace `<YOUR_SSH_PUBKEY>` with a valid SSH public key of yours.
354+
```bash
355+
# SSH pubkey from git user
356+
ssh-rsa <Gitea Host Key>
355357

356-
In addition the public key of the `git` user on the host needs to be added to `/home/git/.ssh/authorized_keys` so authentication against the container can succeed: `echo "$(cat /home/git/.ssh/id_rsa.pub)" >> /home/git/.ssh/authorized_keys`.
358+
# other keys from users
359+
command="/app/gitea/gitea --config=/data/gitea/conf/app.ini serv key-1",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty <user pubkey>
360+
```
357361

358362
Here is a detailed explanation what is happening when a SSH request is made:
359363

360-
1. A SSH request is made against the host using the `git` user, e.g. `git clone git@domain:user/repo.git`.
364+
1. A SSH request is made against the host (usually port 22) using the `git` user, e.g. `git clone git@domain:user/repo.git`.
361365
2. In `/home/git/.ssh/authorized_keys` , the command executes the `/app/gitea/gitea` script.
362366
3. `/app/gitea/gitea` forwards the SSH request to port 2222 which is mapped to the SSH port (22) of the container.
363367
4. Due to the existence of the public key of the `git` user in `/home/git/.ssh/authorized_keys` the authentication host → container succeeds and the SSH request get forwarded to Gitea running in the docker container.

‎integrations/api_issue_stopwatch_test.go

+5-9
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package integrations
77
import (
88
"net/http"
99
"testing"
10-
"time"
1110

1211
"code.gitea.io/gitea/models"
1312
api "code.gitea.io/gitea/modules/structs"
@@ -31,14 +30,11 @@ func TestAPIListStopWatches(t *testing.T) {
3130
issue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: stopwatch.IssueID}).(*models.Issue)
3231
if assert.Len(t, apiWatches, 1) {
3332
assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix())
34-
apiWatches[0].Created = time.Time{}
35-
assert.EqualValues(t, api.StopWatch{
36-
Created: time.Time{},
37-
IssueIndex: issue.Index,
38-
IssueTitle: issue.Title,
39-
RepoName: repo.Name,
40-
RepoOwnerName: repo.OwnerName,
41-
}, *apiWatches[0])
33+
assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex)
34+
assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle)
35+
assert.EqualValues(t, repo.Name, apiWatches[0].RepoName)
36+
assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName)
37+
assert.Greater(t, int64(apiWatches[0].Seconds), int64(0))
4238
}
4339
}
4440

‎integrations/attachment_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func TestCreateIssueAttachment(t *testing.T) {
7272
resp := session.MakeRequest(t, req, http.StatusOK)
7373
htmlDoc := NewHTMLParser(t, resp.Body)
7474

75-
link, exists := htmlDoc.doc.Find("form").Attr("action")
75+
link, exists := htmlDoc.doc.Find("form#new-issue").Attr("action")
7676
assert.True(t, exists, "The template has changed")
7777

7878
postData := map[string]string{

‎models/issue_stopwatch.go

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ type Stopwatch struct {
1919
CreatedUnix timeutil.TimeStamp `xorm:"created"`
2020
}
2121

22+
// Seconds returns the amount of time passed since creation, based on local server time
23+
func (s Stopwatch) Seconds() int64 {
24+
return int64(timeutil.TimeStampNow() - s.CreatedUnix)
25+
}
26+
27+
// Duration returns a human-readable duration string based on local server time
28+
func (s Stopwatch) Duration() string {
29+
return SecToTime(s.Seconds())
30+
}
31+
2232
func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
2333
sw = new(Stopwatch)
2434
exists, err = e.

‎modules/convert/issue.go

+2
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ func ToStopWatches(sws []*models.Stopwatch) (api.StopWatches, error) {
147147

148148
result = append(result, api.StopWatch{
149149
Created: sw.CreatedUnix.AsTime(),
150+
Seconds: sw.Seconds(),
151+
Duration: sw.Duration(),
150152
IssueIndex: issue.Index,
151153
IssueTitle: issue.Title,
152154
RepoOwnerName: repo.OwnerName,

‎modules/structs/issue_stopwatch.go

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
type StopWatch struct {
1313
// swagger:strfmt date-time
1414
Created time.Time `json:"created"`
15+
Seconds int64 `json:"seconds"`
16+
Duration string `json:"duration"`
1517
IssueIndex int64 `json:"issue_index"`
1618
IssueTitle string `json:"issue_title"`
1719
RepoOwnerName string `json:"repo_owner_name"`

‎options/locale/locale_en-US.ini

+7-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ page = Page
1515
template = Template
1616
language = Language
1717
notifications = Notifications
18+
active_stopwatch = Active Time Tracker
1819
create_new = Create…
1920
user_profile_and_more = Profile and Settings…
2021
signed_in_as = Signed in as
@@ -1068,6 +1069,7 @@ issues.commented_at = `commented <a href="#%s">%s</a>`
10681069
issues.delete_comment_confirm = Are you sure you want to delete this comment?
10691070
issues.context.copy_link = Copy Link
10701071
issues.context.quote_reply = Quote Reply
1072+
issues.context.reference_issue = Reference in new issue
10711073
issues.context.edit = Edit
10721074
issues.context.delete = Delete
10731075
issues.no_content = There is no content yet.
@@ -1138,13 +1140,15 @@ issues.lock.title = Lock conversation on this issue.
11381140
issues.unlock.title = Unlock conversation on this issue.
11391141
issues.comment_on_locked = You cannot comment on a locked issue.
11401142
issues.tracker = Time Tracker
1141-
issues.start_tracking_short = Start
1143+
issues.start_tracking_short = Start Timer
11421144
issues.start_tracking = Start Time Tracking
11431145
issues.start_tracking_history = `started working %s`
11441146
issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed
11451147
issues.tracking_already_started = `You have already started time tracking on <a href="%s">another issue</a>!`
1146-
issues.stop_tracking = Stop
1148+
issues.stop_tracking = Stop Timer
11471149
issues.stop_tracking_history = `stopped working %s`
1150+
issues.cancel_tracking = Discard
1151+
issues.cancel_tracking_history = `cancelled time tracking %s`
11481152
issues.add_time = Manually Add Time
11491153
issues.add_time_short = Add Time
11501154
issues.add_time_cancel = Cancel
@@ -1153,8 +1157,6 @@ issues.del_time_history= `deleted spent time %s`
11531157
issues.add_time_hours = Hours
11541158
issues.add_time_minutes = Minutes
11551159
issues.add_time_sum_to_small = No time was entered.
1156-
issues.cancel_tracking = Cancel
1157-
issues.cancel_tracking_history = `cancelled time tracking %s`
11581160
issues.time_spent_total = Total Time Spent
11591161
issues.time_spent_from_all_authors = `Total Time Spent: %s`
11601162
issues.due_date = Due Date
@@ -1225,6 +1227,7 @@ issues.review.resolve_conversation = Resolve conversation
12251227
issues.review.un_resolve_conversation = Unresolve conversation
12261228
issues.review.resolved_by = marked this conversation as resolved
12271229
issues.assignee.error = Not all assignees was added due to an unexpected error.
1230+
issues.reference_issue.body = Body
12281231

12291232
pulls.desc = Enable pull requests and code reviews.
12301233
pulls.new = New Pull Request

‎package-lock.json

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"monaco-editor": "0.21.2",
3535
"monaco-editor-webpack-plugin": "2.1.0",
3636
"postcss": "8.2.1",
37+
"pretty-ms": "7.0.1",
3738
"raw-loader": "4.0.2",
3839
"sortablejs": "1.12.0",
3940
"swagger-ui-dist": "3.38.0",

‎routers/repo/issue_stopwatch.go

+45
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package repo
66

77
import (
88
"net/http"
9+
"strings"
910

1011
"code.gitea.io/gitea/models"
1112
"code.gitea.io/gitea/modules/context"
@@ -61,3 +62,47 @@ func CancelStopwatch(c *context.Context) {
6162
url := issue.HTMLURL()
6263
c.Redirect(url, http.StatusSeeOther)
6364
}
65+
66+
// GetActiveStopwatch is the middleware that sets .ActiveStopwatch on context
67+
func GetActiveStopwatch(c *context.Context) {
68+
if strings.HasPrefix(c.Req.URL.Path, "/api") {
69+
return
70+
}
71+
72+
if !c.IsSigned {
73+
return
74+
}
75+
76+
_, sw, err := models.HasUserStopwatch(c.User.ID)
77+
if err != nil {
78+
c.ServerError("HasUserStopwatch", err)
79+
return
80+
}
81+
82+
if sw == nil || sw.ID == 0 {
83+
return
84+
}
85+
86+
issue, err := models.GetIssueByID(sw.IssueID)
87+
if err != nil || issue == nil {
88+
c.ServerError("GetIssueByID", err)
89+
return
90+
}
91+
if err = issue.LoadRepo(); err != nil {
92+
c.ServerError("LoadRepo", err)
93+
return
94+
}
95+
96+
c.Data["ActiveStopwatch"] = StopwatchTmplInfo{
97+
issue.Repo.FullName(),
98+
issue.Index,
99+
sw.Seconds() + 1, // ensure time is never zero in ui
100+
}
101+
}
102+
103+
// StopwatchTmplInfo is a view on a stopwatch specifically for template rendering
104+
type StopwatchTmplInfo struct {
105+
RepoSlug string
106+
IssueIndex int64
107+
Seconds int64
108+
}

‎routers/routes/macaron.go

+1
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ func RegisterMacaronRoutes(m *macaron.Macaron) {
176176
}
177177

178178
m.Use(user.GetNotificationCount)
179+
m.Use(repo.GetActiveStopwatch)
179180
m.Use(func(ctx *context.Context) {
180181
ctx.Data["UnitWikiGlobalDisabled"] = models.UnitTypeWiki.UnitGlobalDisabled()
181182
ctx.Data["UnitIssuesGlobalDisabled"] = models.UnitTypeIssues.UnitGlobalDisabled()

‎templates/base/head_navbar.tmpl

+38
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,44 @@
6767
</div>
6868
{{else if .IsSigned}}
6969
<div class="right stackable menu">
70+
{{$issueURL := Printf "%s/%s/issues/%d" AppSubUrl .ActiveStopwatch.RepoSlug .ActiveStopwatch.IssueIndex}}
71+
<a class="active-stopwatch-trigger item ui label {{if not .ActiveStopwatch}}hidden{{end}}" href="{{$issueURL}}">
72+
<span class="text">
73+
<span class="fitted item">
74+
{{svg "octicon-stopwatch"}}
75+
<span class="red" style="position:absolute; right:-0.6em; top:-0.6em;">{{svg "octicon-dot-fill"}}</span>
76+
</span>
77+
<span class="sr-mobile-only">{{.i18n.Tr "active_stopwatch"}}</span>
78+
</span>
79+
</a>
80+
<div class="ui popup very wide">
81+
<div class="df ac">
82+
<a class="stopwatch-link df ac" href="{{$issueURL}}">
83+
{{svg "octicon-issue-opened"}}
84+
<span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span>
85+
<span class="ui label blue stopwatch-time my-0 mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}">
86+
{{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}}
87+
</span>
88+
</a>
89+
<form class="stopwatch-commit" method="POST" action="{{$issueURL}}/times/stopwatch/toggle">
90+
{{.CsrfTokenHtml}}
91+
<button
92+
class="ui button mini compact basic icon fitted poping up"
93+
data-content="{{.i18n.Tr "repo.issues.stop_tracking"}}"
94+
data-position="top right" data-variation="small inverted"
95+
>{{svg "octicon-square-fill"}}</button>
96+
</form>
97+
<form class="stopwatch-cancel" method="POST" action="{{$issueURL}}/times/stopwatch/cancel">
98+
{{.CsrfTokenHtml}}
99+
<button
100+
class="ui button mini compact basic icon fitted poping up"
101+
data-content="{{.i18n.Tr "repo.issues.cancel_tracking"}}"
102+
data-position="top right" data-variation="small inverted"
103+
>{{svg "octicon-trashcan"}}</button>
104+
</form>
105+
</div>
106+
</div>
107+
70108
<a href="{{AppSubUrl}}/notifications" class="item poping up" data-content='{{.i18n.Tr "notifications"}}' data-variation="tiny inverted">
71109
<span class="text">
72110
<span class="fitted">{{svg "octicon-bell"}}</span>

‎templates/repo/diff/box.tmpl

+2
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@
164164
</div>
165165
{{end}}
166166

167+
{{template "repo/issue/view_content/reference_issue_dialog" .}}
168+
167169
{{if .IsSplitStyle}}
168170
<script>
169171
document.addEventListener('DOMContentLoaded', () => {

‎templates/repo/issue/new_form.tmpl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<form class="ui comment form stackable grid" action="{{.Link}}" method="post">
1+
<form class="ui comment form stackable grid" id="new-issue" action="{{.Link}}" method="post">
22
{{.CsrfTokenHtml}}
33
{{if .Flash}}
44
<div class="sixteen wide column">

‎templates/repo/issue/view_content.tmpl

+3-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
</div>
5353
{{end}}
5454
{{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index)}}
55-
{{template "repo/issue/view_content/context_menu" Dict "ctx" $ "item" .Issue "delete" false "diff" false "IsCommentPoster" $.IsIssuePoster}}
55+
{{template "repo/issue/view_content/context_menu" Dict "ctx" $ "item" .Issue "delete" false "issue" true "diff" false "IsCommentPoster" $.IsIssuePoster}}
5656
{{end}}
5757
</div>
5858
</div>
@@ -210,6 +210,8 @@
210210
</div>
211211
</div>
212212

213+
{{template "repo/issue/view_content/reference_issue_dialog" .}}
214+
213215
<div class="hide" id="no-content">
214216
<span class="no-content">{{.i18n.Tr "repo.issues.no_content"}}</span>
215217
</div>

‎templates/repo/issue/view_content/context_menu.tmpl

+8-5
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
{{svg "octicon-kebab-horizontal"}}
55
</a>
66
<div class="menu">
7-
{{if .issue}}
8-
<div class="item context clipboard" data-clipboard-text="{{Printf "%s%s/issues/%d#%s" AppUrl .ctx.Repository.FullName .ctx.Issue.Index .item.HashTag}}">{{.ctx.i18n.Tr "repo.issues.context.copy_link"}}</div>
9-
{{else}}
10-
<div class="item context clipboard" data-clipboard-text="{{Printf "%s%s/pulls/%d/files#%s" AppUrl .ctx.Repository.FullName .ctx.Issue.Index .item.HashTag}}">{{.ctx.i18n.Tr "repo.issues.context.copy_link"}}</div>
11-
{{end}}
7+
{{ $referenceUrl := "" }}
8+
{{ if .issue }}
9+
{{ $referenceUrl = Printf "%s%s/issues/%d#%s" AppUrl .ctx.Repository.FullName .ctx.Issue.Index .item.HashTag }}
10+
{{ else }}
11+
{{ $referenceUrl = Printf "%s%s/pulls/%d/files#%s" AppUrl .ctx.Repository.FullName .ctx.Issue.Index .item.HashTag }}
12+
{{ end }}
13+
<div class="item context clipboard" data-clipboard-text="{{$referenceUrl}}">{{.ctx.i18n.Tr "repo.issues.context.copy_link"}}</div>
1214
<div class="item context quote-reply {{if .diff}}quote-reply-diff{{end}}" data-target="{{.item.ID}}">{{.ctx.i18n.Tr "repo.issues.context.quote_reply"}}</div>
15+
<div class="item context reference-issue" data-target="{{.item.ID}}" data-modal="#reference-issue-modal" data-poster="{{.item.Poster.GetDisplayName}}" data-reference="{{$referenceUrl}}">{{.ctx.i18n.Tr "repo.issues.context.reference_issue"}}</div>
1316
{{if or .ctx.Permission.IsAdmin .IsCommentPoster .ctx.HasIssuesOrPullsWritePermission}}
1417
<div class="divider"></div>
1518
<div class="item context edit-content">{{.ctx.i18n.Tr "repo.issues.context.edit"}}</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<div class="ui small modal" id="reference-issue-modal">
2+
<div class="header">
3+
{{.i18n.Tr "repo.issues.context.reference_issue"}}
4+
</div>
5+
<div class="content" style="text-align:left">
6+
<form class="ui form" action="{{ Printf "%s/issues/new" .Repository.Link }}" method="post">
7+
{{.CsrfTokenHtml}}
8+
<div class="ui segment content">
9+
<div class="field">
10+
<span class="text"><strong>{{.i18n.Tr "repository"}}</strong></span>
11+
<div class="ui search normal selection dropdown issue_reference_repository_search">
12+
<div class="default text">{{.Repository.FullName}}</div>
13+
<div class="menu"></div>
14+
</div>
15+
</div>
16+
<div class="field">
17+
<span class="text"><strong>{{.i18n.Tr "repo.milestones.title"}}</strong></span>
18+
<input name="title" value="" autofocus required maxlength="255" autocomplete="off">
19+
</div>
20+
<div class="field">
21+
<span class="text"><strong>{{.i18n.Tr "repo.issues.reference_issue.body"}}</strong></span>
22+
<textarea name="content" class="form-control"></textarea>
23+
</div>
24+
<div class="text right">
25+
<button class="ui green button">{{.i18n.Tr "repo.issues.create"}}</button>
26+
</div>
27+
</div>
28+
</form>
29+
</div>
30+
</div>

‎templates/swagger/v1_json.tmpl

+9
Original file line numberDiff line numberDiff line change
@@ -15473,6 +15473,10 @@
1547315473
"format": "date-time",
1547415474
"x-go-name": "Created"
1547515475
},
15476+
"duration": {
15477+
"type": "string",
15478+
"x-go-name": "Duration"
15479+
},
1547615480
"issue_index": {
1547715481
"type": "integer",
1547815482
"format": "int64",
@@ -15489,6 +15493,11 @@
1548915493
"repo_owner_name": {
1549015494
"type": "string",
1549115495
"x-go-name": "RepoOwnerName"
15496+
},
15497+
"seconds": {
15498+
"type": "integer",
15499+
"format": "int64",
15500+
"x-go-name": "Seconds"
1549215501
}
1549315502
},
1549415503
"x-go-package": "code.gitea.io/gitea/modules/structs"

‎web_src/js/features/stopwatch.js

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import prettyMilliseconds from 'pretty-ms';
2+
const {AppSubUrl, csrf, NotificationSettings} = window.config;
3+
4+
let updateTimeInterval = null; // holds setInterval id when active
5+
6+
export async function initStopwatch() {
7+
const stopwatchEl = $('.active-stopwatch-trigger');
8+
9+
stopwatchEl.removeAttr('href'); // intended for noscript mode only
10+
stopwatchEl.popup({
11+
position: 'bottom right',
12+
hoverable: true,
13+
});
14+
15+
// form handlers
16+
$('form > button', stopwatchEl).on('click', function () {
17+
$(this).parent().trigger('submit');
18+
});
19+
20+
if (!stopwatchEl || NotificationSettings.MinTimeout <= 0) {
21+
return;
22+
}
23+
24+
const fn = (timeout) => {
25+
setTimeout(async () => {
26+
await updateStopwatchWithCallback(fn, timeout);
27+
}, timeout);
28+
};
29+
30+
fn(NotificationSettings.MinTimeout);
31+
32+
const currSeconds = $('.stopwatch-time').data('seconds');
33+
if (currSeconds) {
34+
updateTimeInterval = updateStopwatchTime(currSeconds);
35+
}
36+
}
37+
38+
async function updateStopwatchWithCallback(callback, timeout) {
39+
const isSet = await updateStopwatch();
40+
41+
if (!isSet) {
42+
timeout = NotificationSettings.MinTimeout;
43+
} else if (timeout < NotificationSettings.MaxTimeout) {
44+
timeout += NotificationSettings.TimeoutStep;
45+
}
46+
47+
callback(timeout);
48+
}
49+
50+
async function updateStopwatch() {
51+
const data = await $.ajax({
52+
type: 'GET',
53+
url: `${AppSubUrl}/api/v1/user/stopwatches`,
54+
headers: {'X-Csrf-Token': csrf},
55+
});
56+
57+
if (updateTimeInterval) {
58+
clearInterval(updateTimeInterval);
59+
updateTimeInterval = null;
60+
}
61+
62+
const watch = data[0];
63+
const btnEl = $('.active-stopwatch-trigger');
64+
if (!watch) {
65+
btnEl.addClass('hidden');
66+
} else {
67+
const {repo_owner_name, repo_name, issue_index, seconds} = watch;
68+
const issueUrl = `${AppSubUrl}/${repo_owner_name}/${repo_name}/issues/${issue_index}`;
69+
$('.stopwatch-link').attr('href', issueUrl);
70+
$('.stopwatch-commit').attr('action', `${issueUrl}/times/stopwatch/toggle`);
71+
$('.stopwatch-cancel').attr('action', `${issueUrl}/times/stopwatch/cancel`);
72+
$('.stopwatch-issue').text(`${repo_owner_name}/${repo_name}#${issue_index}`);
73+
$('.stopwatch-time').text(prettyMilliseconds(seconds * 1000));
74+
updateStopwatchTime(seconds);
75+
btnEl.removeClass('hidden');
76+
}
77+
78+
return !!data.length;
79+
}
80+
81+
async function updateStopwatchTime(seconds) {
82+
const secs = parseInt(seconds);
83+
if (!Number.isFinite(secs)) return;
84+
85+
const start = Date.now();
86+
updateTimeInterval = setInterval(() => {
87+
const delta = Date.now() - start;
88+
const dur = prettyMilliseconds(secs * 1000 + delta, {compact: true});
89+
$('.stopwatch-time').text(dur);
90+
}, 1000);
91+
}

‎web_src/js/index.js

+49
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import createDropzone from './features/dropzone.js';
2222
import initTableSort from './features/tablesort.js';
2323
import ActivityTopAuthors from './components/ActivityTopAuthors.vue';
2424
import {initNotificationsTable, initNotificationCount} from './features/notification.js';
25+
import {initStopwatch} from './features/stopwatch.js';
2526
import {createCodeEditor, createMonaco} from './features/codeeditor.js';
2627
import {svg, svgs} from './svg.js';
2728
import {stripTags} from './utils.js';
@@ -932,6 +933,27 @@ async function initRepository() {
932933
event.preventDefault();
933934
});
934935

936+
// Reference issue
937+
$(document).on('click', '.reference-issue', function (event) {
938+
const $this = $(this);
939+
940+
$this.closest('.dropdown').find('.menu').toggle('visible');
941+
942+
const content = $(`#comment-${$this.data('target')}`).text();
943+
const subject = content.split('\n', 1)[0].slice(0, 255);
944+
945+
const poster = $this.data('poster');
946+
const reference = $this.data('reference');
947+
948+
const $modal = $($this.data('modal'));
949+
$modal.find('input[name="title"').val(subject);
950+
$modal.find('textarea[name="content"]').val(`${content}\n\n_Originally posted by @${poster} in ${reference}_`);
951+
952+
$modal.modal('show');
953+
954+
event.preventDefault();
955+
});
956+
935957
// Edit issue or comment content
936958
$(document).on('click', '.edit-content', async function (event) {
937959
$(this).closest('.dropdown').find('.menu').toggle('visible');
@@ -2337,6 +2359,31 @@ function initTemplateSearch() {
23372359
changeOwner();
23382360
}
23392361

2362+
function initIssueReferenceRepositorySearch() {
2363+
$('.issue_reference_repository_search')
2364+
.dropdown({
2365+
apiSettings: {
2366+
url: `${AppSubUrl}/api/v1/repos/search?q={query}&limit=20`,
2367+
onResponse(response) {
2368+
const filteredResponse = {success: true, results: []};
2369+
$.each(response.data, (_r, repo) => {
2370+
filteredResponse.results.push({
2371+
name: htmlEscape(repo.full_name),
2372+
value: repo.full_name
2373+
});
2374+
});
2375+
return filteredResponse;
2376+
},
2377+
cache: false,
2378+
},
2379+
onChange(_value, _text, $choice) {
2380+
const $form = $choice.closest('form');
2381+
$form.attr('action', `${AppSubUrl}/${_text}/issues/new`);
2382+
},
2383+
fullTextSearch: true
2384+
});
2385+
}
2386+
23402387
$(document).ready(async () => {
23412388
// Show exact time
23422389
$('.time-since').each(function () {
@@ -2553,6 +2600,7 @@ $(document).ready(async () => {
25532600
initPullRequestReview();
25542601
initRepoStatusChecker();
25552602
initTemplateSearch();
2603+
initIssueReferenceRepositorySearch();
25562604
initContextPopups();
25572605
initTableSort();
25582606
initNotificationsTable();
@@ -2579,6 +2627,7 @@ $(document).ready(async () => {
25792627
initProject(),
25802628
initServiceWorker(),
25812629
initNotificationCount(),
2630+
initStopwatch(),
25822631
renderMarkdownContent(),
25832632
initGithook(),
25842633
]);

0 commit comments

Comments
 (0)
Please sign in to comment.