mirror of
https://github.com/go-gitea/gitea
synced 2025-01-03 10:15:59 +01:00
Merge branch 'main' into lunny/automerge_support_delete_branch
This commit is contained in:
commit
c879891fe1
@ -1443,11 +1443,11 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if issue.IsPull && !ctx.Repo.CanRead(unit.TypeIssues) {
|
||||
ctx.Data["IssueType"] = "pulls"
|
||||
ctx.Data["IssueDependencySearchType"] = "pulls"
|
||||
} else if !issue.IsPull && !ctx.Repo.CanRead(unit.TypePullRequests) {
|
||||
ctx.Data["IssueType"] = "issues"
|
||||
ctx.Data["IssueDependencySearchType"] = "issues"
|
||||
} else {
|
||||
ctx.Data["IssueType"] = "all"
|
||||
ctx.Data["IssueDependencySearchType"] = "all"
|
||||
}
|
||||
|
||||
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(unit.TypeProjects)
|
||||
|
@ -318,9 +318,10 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
||||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeTags: true,
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeDrafts: true,
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
|
||||
@ -407,13 +408,17 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
||||
|
||||
newReleases = append(newReleases, rel)
|
||||
} else {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
|
||||
rel.NumCommits = commitsCount
|
||||
if rel.IsTag && author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
if rel.IsTag {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
if author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
} else {
|
||||
rel.IsDraft = false
|
||||
}
|
||||
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
|
||||
return fmt.Errorf("Update: %w", err)
|
||||
|
@ -156,7 +156,7 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) {
|
||||
// for each commit, generate attachment text
|
||||
for i, commit := range p.Commits {
|
||||
// limit the commit message display to just the summary, otherwise it would be hard to read
|
||||
message := strings.TrimRight(strings.SplitN(commit.Message, "\n", 1)[0], "\r")
|
||||
message := strings.TrimRight(strings.SplitN(commit.Message, "\n", 2)[0], "\r")
|
||||
|
||||
// a limit of 50 is set because GitHub does the same
|
||||
if utf8.RuneCountInString(message) > 50 {
|
||||
|
@ -80,12 +80,26 @@ func TestDiscordPayload(t *testing.T) {
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("PushWithLongCommitMessage", func(t *testing.T) {
|
||||
t.Run("PushWithMultilineCommitMessage", func(t *testing.T) {
|
||||
p := pushTestMultilineCommitMessagePayload()
|
||||
|
||||
pl, err := dc.Push(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, pl.Embeds, 1)
|
||||
assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title)
|
||||
assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) chore: This is a commit summary - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) chore: This is a commit summary - user1", pl.Embeds[0].Description)
|
||||
assert.Equal(t, p.Sender.UserName, pl.Embeds[0].Author.Name)
|
||||
assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.Embeds[0].Author.URL)
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("PushWithLongCommitSummary", func(t *testing.T) {
|
||||
p := pushTestPayloadWithCommitMessage("This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好 ⚠️⚠️️\n\nThis is the message body")
|
||||
|
||||
pl, err := dc.Push(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, pl.Embeds, 1)
|
||||
assert.Equal(t, "[test/repo:test] 2 new commits", pl.Embeds[0].Title)
|
||||
assert.Equal(t, "[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1\n[2020558](http://localhost:3000/test/repo/commit/2020558fe2e34debb818a514715839cabd25e778) This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好... - user1", pl.Embeds[0].Description)
|
||||
|
@ -68,7 +68,7 @@ func pushTestPayload() *api.PushPayload {
|
||||
}
|
||||
|
||||
func pushTestMultilineCommitMessagePayload() *api.PushPayload {
|
||||
return pushTestPayloadWithCommitMessage("This is a commit summary ⚠️⚠️⚠️⚠️ containing 你好 ⚠️⚠️️\n\nThis is the message body.")
|
||||
return pushTestPayloadWithCommitMessage("chore: This is a commit summary\n\nThis is a commit description.")
|
||||
}
|
||||
|
||||
func pushTestPayloadWithCommitMessage(message string) *api.PushPayload {
|
||||
|
@ -17,12 +17,12 @@
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="right-links" role="group" aria-label="{{ctx.Locale.Tr "aria.footer.links"}}">
|
||||
<div class="ui dropdown upward language">
|
||||
<div class="ui dropdown upward">
|
||||
<span class="flex-text-inline">{{svg "octicon-globe" 14}} {{ctx.Locale.LangName}}</span>
|
||||
<div class="menu language-menu">
|
||||
{{range .AllLangs}}
|
||||
<a lang="{{.Lang}}" data-url="{{AppSubUrl}}/?lang={{.Lang}}" class="item {{if eq ctx.Locale.Lang .Lang}}active selected{{end}}">{{.Name}}</a>
|
||||
{{end}}
|
||||
{{range .AllLangs -}}
|
||||
<a lang="{{.Lang}}" data-url="{{AppSubUrl}}/?lang={{.Lang}}" class="item {{if eq ctx.Locale.Lang .Lang}}selected{{end}}">{{.Name}}</a>
|
||||
{{end -}}
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{AssetUrlPrefix}}/licenses.txt">{{ctx.Locale.Tr "licenses"}}</a>
|
||||
|
@ -44,6 +44,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
{{end}}
|
||||
|
13
templates/repo/issue/sidebar/allow_maintainer_edit.tmpl
Normal file
13
templates/repo/issue/sidebar/allow_maintainer_edit.tmpl
Normal file
@ -0,0 +1,13 @@
|
||||
{{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}}
|
||||
{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
|
||||
<div class="divider"></div>
|
||||
<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
|
||||
data-url="{{.Issue.Link}}"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
|
||||
data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
|
||||
>
|
||||
<label><strong>{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label>
|
||||
<input type="checkbox" {{if .Issue.PullRequest.AllowMaintainerEdit}}checked{{end}}>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
46
templates/repo/issue/sidebar/assignee_list.tmpl
Normal file
46
templates/repo/issue/sidebar/assignee_list.tmpl
Normal file
@ -0,0 +1,46 @@
|
||||
<div class="divider"></div>
|
||||
<input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}">
|
||||
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-assignees-modify dropdown">
|
||||
<a class="text muted flex-text-block">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.new.assignees"}}</strong>
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
{{svg "octicon-gear" 16 "tw-ml-1"}}
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/assignee">
|
||||
<div class="ui icon search input">
|
||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignees"}}">
|
||||
</div>
|
||||
<div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_assignees"}}</div>
|
||||
{{range .Assignees}}
|
||||
|
||||
{{$AssigneeID := .ID}}
|
||||
<a class="item{{range $.Issue.Assignees}}{{if eq .ID $AssigneeID}} checked{{end}}{{end}}" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}">
|
||||
{{$checked := false}}
|
||||
{{range $.Issue.Assignees}}
|
||||
{{if eq .ID $AssigneeID}}
|
||||
{{$checked = true}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
<span class="octicon-check {{if not $checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
|
||||
<span class="text">
|
||||
{{ctx.AvatarUtils.Avatar . 20 "tw-mr-2"}}{{template "repo/search_name" .}}
|
||||
</span>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui assignees list">
|
||||
<span class="no-select item {{if .Issue.Assignees}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}</span>
|
||||
<div class="selected">
|
||||
{{range .Issue.Assignees}}
|
||||
<div class="item">
|
||||
<a class="muted sidebar-item-link" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?assignee={{.ID}}">
|
||||
{{ctx.AvatarUtils.Avatar . 28 "tw-mr-2"}}
|
||||
{{.GetDisplayName}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
29
templates/repo/issue/sidebar/due_date.tmpl
Normal file
29
templates/repo/issue/sidebar/due_date.tmpl
Normal file
@ -0,0 +1,29 @@
|
||||
<div class="divider"></div>
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.due_date"}}</strong></span>
|
||||
<div class="ui form tw-mt-2">
|
||||
{{if .Issue.DeadlineUnix}}
|
||||
<div class="tw-flex tw-justify-between tw-items-center tw-gap-2">
|
||||
<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
|
||||
{{svg "octicon-calendar"}} {{DateUtils.AbsoluteLong .Issue.DeadlineUnix}}
|
||||
</div>
|
||||
<div class="flex-text-block">
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
<a class="issue-due-edit muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_edit"}}">{{svg "octicon-pencil"}}</a>
|
||||
<a class="issue-due-remove muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_remove"}}">{{svg "octicon-trash"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.due_date_not_set"}}
|
||||
{{end}}
|
||||
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
<form class="ui fluid action input issue-due-form form-fetch-action tw-mt-2 {{if .Issue.DeadlineUnix}}tw-hidden{{end}}"
|
||||
method="post" action="{{AppSubUrl}}/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}/deadline"
|
||||
>
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input required type="date" name="deadline" placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}" {{if .Issue.DeadlineUnix}}value="{{.Issue.DeadlineUnix.FormatDate}}"{{end}}>
|
||||
<button class="ui icon button">{{Iif .Issue.DeadlineUnix (svg "octicon-pencil") (svg "octicon-plus")}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
149
templates/repo/issue/sidebar/issue_dependencies.tmpl
Normal file
149
templates/repo/issue/sidebar/issue_dependencies.tmpl
Normal file
@ -0,0 +1,149 @@
|
||||
{{if .Repository.IsDependenciesEnabled ctx}}
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="ui depending">
|
||||
{{if (and (not .BlockedByDependencies) (not .BlockedByDependenciesNotPermitted) (not .BlockingDependencies) (not .BlockingDependenciesNotPermitted))}}
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.dependency.title"}}</strong></span>
|
||||
<br>
|
||||
<p>
|
||||
{{if .Issue.IsPull}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.pr_no_dependencies"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.issue_no_dependencies"}}
|
||||
{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
{{if or .BlockingDependencies .BlockingDependenciesNotPermitted}}
|
||||
<span class="text" data-tooltip-content="{{if .Issue.IsPull}}{{ctx.Locale.Tr "repo.issues.dependency.pr_close_blocks"}}{{else}}{{ctx.Locale.Tr "repo.issues.dependency.issue_close_blocks"}}{{end}}">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.dependency.blocks_short"}}</strong>
|
||||
</span>
|
||||
<div class="ui divided list">
|
||||
{{range .BlockingDependencies}}
|
||||
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
|
||||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<a class="muted gt-ellipsis" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}
|
||||
</a>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
{{.Repository.OwnerName}}/{{.Repository.Name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right tw-flex tw-items-center tw-m-1">
|
||||
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
|
||||
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
|
||||
{{svg "octicon-trash" 16}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .BlockingDependenciesNotPermitted}}
|
||||
<div class="item tw-flex tw-items-center tw-justify-between gt-ellipsis">
|
||||
<span>{{ctx.Locale.TrN (len .BlockingDependenciesNotPermitted) "repo.issues.dependency.no_permission_1" "repo.issues.dependency.no_permission_n" (len .BlockingDependenciesNotPermitted)}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if or .BlockedByDependencies .BlockedByDependenciesNotPermitted}}
|
||||
<span class="text" data-tooltip-content="{{if .Issue.IsPull}}{{ctx.Locale.Tr "repo.issues.dependency.pr_closing_blockedby"}}{{else}}{{ctx.Locale.Tr "repo.issues.dependency.issue_closing_blockedby"}}{{end}}">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.dependency.blocked_by_short"}}</strong>
|
||||
</span>
|
||||
<div class="ui divided list">
|
||||
{{range .BlockedByDependencies}}
|
||||
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
|
||||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<a class="muted gt-ellipsis" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}
|
||||
</a>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
{{.Repository.OwnerName}}/{{.Repository.Name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right tw-flex tw-items-center tw-m-1">
|
||||
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
|
||||
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blockedBy" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
|
||||
{{svg "octicon-trash" 16}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if $.CanCreateIssueDependencies}}
|
||||
{{range .BlockedByDependenciesNotPermitted}}
|
||||
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
|
||||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<div class="gt-ellipsis">
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.no_permission.can_remove"}}">{{svg "octicon-lock" 16}}</span>
|
||||
<span class="gt-ellipsis" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
{{.Repository.OwnerName}}/{{.Repository.Name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right tw-flex tw-items-center tw-m-1">
|
||||
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
|
||||
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
|
||||
{{svg "octicon-trash" 16}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else if .BlockedByDependenciesNotPermitted}}
|
||||
<div class="item tw-flex tw-items-center tw-justify-between gt-ellipsis">
|
||||
<span>{{ctx.Locale.TrN (len .BlockedByDependenciesNotPermitted) "repo.issues.dependency.no_permission_1" "repo.issues.dependency.no_permission_n" (len .BlockedByDependenciesNotPermitted)}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
|
||||
<div>
|
||||
<form method="post" action="{{.Issue.Link}}/dependency/add" id="addDependencyForm">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<div class="ui fluid action input">
|
||||
<div class="ui search selection dropdown" id="new-dependency-drop-list" data-issue-id="{{.Issue.ID}}">
|
||||
<input name="newDependency" type="hidden">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<input type="text" class="search">
|
||||
<div class="default text">{{ctx.Locale.Tr "repo.issues.dependency.add"}}</div>
|
||||
</div>
|
||||
<button class="ui icon button">
|
||||
{{svg "octicon-plus"}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
|
||||
<input type="hidden" id="crossRepoSearch" value="{{.AllowCrossRepositoryDependencies}}">
|
||||
|
||||
<div class="ui g-modal-confirm modal remove-dependency">
|
||||
<div class="header">
|
||||
{{svg "octicon-trash"}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.remove_header"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<form method="post" action="{{.Issue.Link}}/dependency/delete" id="removeDependencyForm">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input type="hidden" value="" name="removeDependencyID" id="removeDependencyID">
|
||||
<input type="hidden" value="" name="dependencyType" id="dependencyType">
|
||||
</form>
|
||||
<p>{{if .Issue.IsPull}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.pr_remove_text"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.issue_remove_text"}}
|
||||
{{end}}</p>
|
||||
</div>
|
||||
{{$ModalButtonCancelText := ctx.Locale.Tr "repo.issues.dependency.cancel"}}
|
||||
{{$ModalButtonOkText := ctx.Locale.Tr "repo.issues.dependency.remove"}}
|
||||
{{template "base/modal_actions_confirm" (dict "." . "ModalButtonCancelText" $ModalButtonCancelText "ModalButtonOkText" $ModalButtonOkText)}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
118
templates/repo/issue/sidebar/issue_management.tmpl
Normal file
118
templates/repo/issue/sidebar/issue_management.tmpl
Normal file
@ -0,0 +1,118 @@
|
||||
{{if and .IsRepoAdmin (not .Repository.IsArchived)}}
|
||||
<div class="divider"></div>
|
||||
|
||||
{{if or .PinEnabled .Issue.IsPinned}}
|
||||
<form class="tw-mt-1 form-fetch-action single-button-form" method="post" {{if $.NewPinAllowed}}action="{{.Issue.Link}}/pin"{{else}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.max_pinned"}}"{{end}}>
|
||||
{{$.CsrfTokenHtml}}
|
||||
<button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}">
|
||||
{{if not .Issue.IsPinned}}
|
||||
{{svg "octicon-pin" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "pin"}}
|
||||
{{else}}
|
||||
{{svg "octicon-pin-slash" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "unpin"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
<button class="tw-mt-1 fluid ui show-modal button{{if .Issue.IsLocked}} red{{end}}" data-modal="#lock">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{svg "octicon-key"}}
|
||||
{{ctx.Locale.Tr "repo.issues.unlock"}}
|
||||
{{else}}
|
||||
{{svg "octicon-lock"}}
|
||||
{{ctx.Locale.Tr "repo.issues.lock"}}
|
||||
{{end}}
|
||||
</button>
|
||||
<div class="ui tiny modal" id="lock">
|
||||
<div class="header">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{ctx.Locale.Tr "repo.issues.unlock.title"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.lock.title"}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui warning message">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{ctx.Locale.Tr "repo.issues.unlock.notice_1"}}<br>
|
||||
{{ctx.Locale.Tr "repo.issues.unlock.notice_2"}}<br>
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.lock.notice_1"}}<br>
|
||||
{{ctx.Locale.Tr "repo.issues.lock.notice_2"}}<br>
|
||||
{{ctx.Locale.Tr "repo.issues.lock.notice_3"}}<br>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<form class="ui form form-fetch-action" action="{{.Issue.Link}}{{if .Issue.IsLocked}}/unlock{{else}}/lock{{end}}"
|
||||
method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
|
||||
{{if not .Issue.IsLocked}}
|
||||
<div class="field">
|
||||
<strong> {{ctx.Locale.Tr "repo.issues.lock.reason"}} </strong>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="ui fluid dropdown selection">
|
||||
|
||||
<select name="reason">
|
||||
<option value=""> </option>
|
||||
{{range .LockReasons}}
|
||||
<option value="{{.}}">{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
|
||||
<div class="default text"> </div>
|
||||
|
||||
<div class="menu">
|
||||
{{range .LockReasons}}
|
||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="text right actions">
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||
<button class="ui red button">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{ctx.Locale.Tr "repo.issues.unlock_confirm"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.lock_confirm"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<button class="tw-mt-1 fluid ui show-modal button" data-modal="#sidebar-delete-issue">
|
||||
{{svg "octicon-trash"}}
|
||||
{{ctx.Locale.Tr "repo.issues.delete"}}
|
||||
</button>
|
||||
<div class="ui g-modal-confirm modal" id="sidebar-delete-issue">
|
||||
<div class="header">
|
||||
{{if .Issue.IsPull}}
|
||||
{{ctx.Locale.Tr "repo.pulls.delete.title"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.delete.title"}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>
|
||||
{{if .Issue.IsPull}}
|
||||
{{ctx.Locale.Tr "repo.pulls.delete.text"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.delete.text"}}
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
<form action="{{.Issue.Link}}/delete" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
23
templates/repo/issue/sidebar/milestone_list.tmpl
Normal file
23
templates/repo/issue/sidebar/milestone_list.tmpl
Normal file
@ -0,0 +1,23 @@
|
||||
<div class="divider"></div>
|
||||
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-milestone dropdown">
|
||||
<a class="text muted flex-text-block">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.new.milestone"}}</strong>
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
{{svg "octicon-gear" 16 "tw-ml-1"}}
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/milestone">
|
||||
{{template "repo/issue/milestone/select_menu" .}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui select-milestone list">
|
||||
<span class="no-select item {{if .Issue.Milestone}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_milestone"}}</span>
|
||||
<div class="selected">
|
||||
{{if .Issue.Milestone}}
|
||||
<a class="item muted sidebar-item-link" href="{{.RepoLink}}/milestone/{{.Issue.Milestone.ID}}">
|
||||
{{svg "octicon-milestone" 18 "tw-mr-2"}}
|
||||
{{.Issue.Milestone.Name}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
11
templates/repo/issue/sidebar/participant_list.tmpl
Normal file
11
templates/repo/issue/sidebar/participant_list.tmpl
Normal file
@ -0,0 +1,11 @@
|
||||
{{if .Participants}}
|
||||
<div class="divider"></div>
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.num_participants" .NumParticipants}}</strong></span>
|
||||
<div class="ui list tw-flex tw-flex-wrap">
|
||||
{{range .Participants}}
|
||||
<a {{if gt .ID 0}}href="{{.HomeLink}}"{{end}} data-tooltip-content="{{.GetDisplayName}}">
|
||||
{{ctx.AvatarUtils.Avatar . 28 "tw-my-0.5 tw-mr-1"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
53
templates/repo/issue/sidebar/project_list.tmpl
Normal file
53
templates/repo/issue/sidebar/project_list.tmpl
Normal file
@ -0,0 +1,53 @@
|
||||
{{if .IsProjectsEnabled}}
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-project dropdown">
|
||||
<a class="text muted flex-text-block">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.new.projects"}}</strong>
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
{{svg "octicon-gear" 16 "tw-ml-1"}}
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/projects">
|
||||
{{if or .OpenProjects .ClosedProjects}}
|
||||
<div class="ui icon search input">
|
||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_projects"}}">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_projects"}}</div>
|
||||
{{if .OpenProjects}}
|
||||
<div class="divider"></div>
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.issues.new.open_projects"}}
|
||||
</div>
|
||||
{{range .OpenProjects}}
|
||||
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if .ClosedProjects}}
|
||||
<div class="divider"></div>
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.issues.new.closed_projects"}}
|
||||
</div>
|
||||
{{range .ClosedProjects}}
|
||||
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui select-project list">
|
||||
<span class="no-select item {{if .Issue.Project}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_projects"}}</span>
|
||||
<div class="selected">
|
||||
{{if .Issue.Project}}
|
||||
<a class="item muted sidebar-item-link" href="{{.Issue.Project.Link ctx}}">
|
||||
{{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
8
templates/repo/issue/sidebar/reference_link.tmpl
Normal file
8
templates/repo/issue/sidebar/reference_link.tmpl
Normal file
@ -0,0 +1,8 @@
|
||||
<div class="divider"></div>
|
||||
<div class="ui equal width compact grid">
|
||||
{{$issueReferenceLink := printf "%s#%d" .Issue.Repo.FullName .Issue.Index}}
|
||||
<div class="row tw-items-center" data-tooltip-content="{{$issueReferenceLink}}">
|
||||
<span class="text column truncate">{{ctx.Locale.Tr "repo.issues.reference_link" $issueReferenceLink}}</span>
|
||||
<button class="ui two wide button column tw-p-2" data-clipboard-text="{{$issueReferenceLink}}">{{svg "octicon-copy" 14}}</button>
|
||||
</div>
|
||||
</div>
|
116
templates/repo/issue/sidebar/reviewer_list.tmpl
Normal file
116
templates/repo/issue/sidebar/reviewer_list.tmpl
Normal file
@ -0,0 +1,116 @@
|
||||
<input id="reviewer_id" name="reviewer_id" type="hidden" value="{{.reviewer_id}}">
|
||||
<div class="ui {{if or (and (not .Reviewers) (not .TeamReviewers)) (not .CanChooseReviewer) .Repository.IsArchived}}disabled{{end}} floating jump select-reviewers-modify dropdown">
|
||||
<a class="text tw-flex tw-items-center muted">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong>
|
||||
{{if and .CanChooseReviewer (not .Repository.IsArchived)}}
|
||||
{{svg "octicon-gear" 16 "tw-ml-1"}}
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/request_review">
|
||||
{{if .Reviewers}}
|
||||
<div class="ui icon search input">
|
||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_reviewers"}}">
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Reviewers}}
|
||||
{{range .Reviewers}}
|
||||
{{if .User}}
|
||||
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_{{.ItemID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
|
||||
<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
|
||||
<span class="text">
|
||||
{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}{{template "repo/search_name" .User}}
|
||||
</span>
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if .TeamReviewers}}
|
||||
{{if .Reviewers}}
|
||||
<div class="divider"></div>
|
||||
{{end}}
|
||||
{{range .TeamReviewers}}
|
||||
{{if .Team}}
|
||||
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
|
||||
<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check" 16}}</span>
|
||||
<span class="text">
|
||||
{{svg "octicon-people" 16 "tw-ml-4 tw-mr-1"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}
|
||||
</span>
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui assignees list">
|
||||
<span class="no-select item {{if or .OriginalReviews .PullReviewers}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}</span>
|
||||
<div class="selected">
|
||||
{{range .PullReviewers}}
|
||||
<div class="item tw-flex tw-items-center tw-py-2">
|
||||
<div class="tw-flex tw-items-center tw-flex-1">
|
||||
{{if .User}}
|
||||
<a class="muted sidebar-item-link" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20 "tw-mr-2"}}{{.User.GetDisplayName}}</a>
|
||||
{{else if .Team}}
|
||||
<span class="text">{{svg "octicon-people" 20 "tw-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
{{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged))}}
|
||||
<a href="#" class="ui muted icon tw-flex tw-items-center show-modal" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dismiss_review"}}" data-modal="#dismiss-review-modal-{{.Review.ID}}">
|
||||
{{svg "octicon-x" 20}}
|
||||
</a>
|
||||
<div class="ui small modal" id="dismiss-review-modal-{{.Review.ID}}">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.issues.dismiss_review"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui warning message">
|
||||
{{ctx.Locale.Tr "repo.issues.dismiss_review_warning"}}
|
||||
</div>
|
||||
<form class="ui form dismiss-review-form" id="dismiss-review-{{.Review.ID}}" action="{{$.RepoLink}}/issues/dismiss_review" method="post">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input type="hidden" name="review_id" value="{{.Review.ID}}">
|
||||
<div class="field">
|
||||
<label for="message">{{ctx.Locale.Tr "action.review_dismissed_reason"}}</label>
|
||||
<input id="message" name="message">
|
||||
</div>
|
||||
<div class="text right actions">
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||
<button class="ui red button" type="submit">{{ctx.Locale.Tr "ok"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Review.Stale}}
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.is_stale"}}">
|
||||
{{svg "octicon-hourglass" 16}}
|
||||
</span>
|
||||
{{end}}
|
||||
{{if and .CanChange (or .Checked (and (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged)))}}
|
||||
<a href="#" class="ui muted icon re-request-review{{if .Checked}} checked{{end}}" data-tooltip-content="{{if .Checked}}{{ctx.Locale.Tr "repo.issues.remove_request_review"}}{{else}}{{ctx.Locale.Tr "repo.issues.re_request_review"}}{{end}}" data-issue-id="{{$.Issue.ID}}" data-id="{{.ItemID}}" data-update-url="{{$.RepoLink}}/issues/request_review">{{svg (Iif .Checked "octicon-trash" "octicon-sync")}}</a>
|
||||
{{end}}
|
||||
<span {{if .Review.TooltipContent}}data-tooltip-content="{{ctx.Locale.Tr .Review.TooltipContent}}"{{end}}>
|
||||
{{svg (printf "octicon-%s" .Review.Type.Icon) 16 (printf "text %s" (.Review.HTMLTypeColorName))}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{range .OriginalReviews}}
|
||||
<div class="item tw-flex tw-items-center tw-py-2">
|
||||
<div class="tw-flex tw-items-center tw-flex-1">
|
||||
<a class="muted" href="{{$.Repository.OriginalURL}}" data-tooltip-content="{{ctx.Locale.Tr "repo.migrated_from_fake" $.Repository.GetOriginalURLHostname}}">
|
||||
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname) 20 "tw-mr-2"}}
|
||||
{{.OriginalAuthor}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
<span {{if .TooltipContent}}data-tooltip-content="{{ctx.Locale.Tr .TooltipContent}}"{{end}}>
|
||||
{{svg (printf "octicon-%s" .Type.Icon) 16 (printf "text %s" (.HTMLTypeColorName))}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
75
templates/repo/issue/sidebar/stopwatch_timetracker.tmpl
Normal file
75
templates/repo/issue/sidebar/stopwatch_timetracker.tmpl
Normal file
@ -0,0 +1,75 @@
|
||||
{{if .Repository.IsTimetrackerEnabled ctx}}
|
||||
{{if and .CanUseTimetracker (not .Repository.IsArchived)}}
|
||||
<div class="divider"></div>
|
||||
<div class="ui timetrack">
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.tracker"}}</strong></span>
|
||||
<div class="tw-mt-2">
|
||||
<form method="post" action="{{.Issue.Link}}/times/stopwatch/toggle" id="toggle_stopwatch_form">
|
||||
{{$.CsrfTokenHtml}}
|
||||
</form>
|
||||
<form method="post" action="{{.Issue.Link}}/times/stopwatch/cancel" id="cancel_stopwatch_form">
|
||||
{{$.CsrfTokenHtml}}
|
||||
</form>
|
||||
{{if $.IsStopwatchRunning}}
|
||||
<button class="ui fluid button issue-stop-time">
|
||||
{{svg "octicon-stopwatch" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "repo.issues.stop_tracking"}}
|
||||
</button>
|
||||
<button class="ui fluid button issue-cancel-time tw-mt-2">
|
||||
{{svg "octicon-trash" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "repo.issues.cancel_tracking"}}
|
||||
</button>
|
||||
{{else}}
|
||||
{{if .HasUserStopwatch}}
|
||||
<div class="ui warning message">
|
||||
{{ctx.Locale.Tr "repo.issues.tracking_already_started" .OtherStopwatchURL}}
|
||||
</div>
|
||||
{{end}}
|
||||
<button class="ui fluid button issue-start-time" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.start_tracking"}}'>
|
||||
{{svg "octicon-stopwatch" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "repo.issues.start_tracking_short"}}
|
||||
</button>
|
||||
<div class="ui mini modal issue-start-time-modal">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.issues.add_time"}}</div>
|
||||
<div class="content">
|
||||
<form method="post" id="add_time_manual_form" action="{{.Issue.Link}}/times/add" class="ui input fluid tw-gap-2">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input placeholder='{{ctx.Locale.Tr "repo.issues.add_time_hours"}}' type="number" name="hours">
|
||||
<input placeholder='{{ctx.Locale.Tr "repo.issues.add_time_minutes"}}' type="number" name="minutes" class="ui compact">
|
||||
</form>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui primary approve button">{{ctx.Locale.Tr "repo.issues.add_time_short"}}</button>
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.add_time_cancel"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui fluid button issue-add-time tw-mt-2" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.add_time"}}'>
|
||||
{{svg "octicon-plus" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "repo.issues.add_time_short"}}
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .WorkingUsers}}
|
||||
<div class="divider"></div>
|
||||
<div class="ui comments">
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time)}}</strong></span>
|
||||
<div>
|
||||
{{range $user, $trackedtime := .WorkingUsers}}
|
||||
<div class="comment tw-mt-2">
|
||||
<a class="avatar">
|
||||
{{ctx.AvatarUtils.Avatar $user}}
|
||||
</a>
|
||||
<div class="content">
|
||||
{{template "shared/user/authorlink" $user}}
|
||||
<div class="text">
|
||||
{{$trackedtime|Sec2Time}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
9
templates/repo/issue/sidebar/watch_notification.tmpl
Normal file
9
templates/repo/issue/sidebar/watch_notification.tmpl
Normal file
@ -0,0 +1,9 @@
|
||||
{{if and $.IssueWatch (not .Repository.IsArchived)}}
|
||||
<div class="divider"></div>
|
||||
<div class="ui watching">
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
|
||||
<div class="tw-mt-2">
|
||||
{{template "repo/issue/view_content/watching" .}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
7
templates/repo/issue/sidebar/wip_switch.tmpl
Normal file
7
templates/repo/issue/sidebar/wip_switch.tmpl
Normal file
@ -0,0 +1,7 @@
|
||||
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .HasMerged) (not .Issue.IsClosed) (not .IsPullWorkInProgress)}}
|
||||
<div class="toggle-wip" data-title="{{.Issue.Title}}" data-wip-prefix="{{index .PullRequestWorkInProgressPrefixes 0}}" data-update-url="{{.Issue.Link}}/title">
|
||||
<a class="muted">
|
||||
{{ctx.Locale.Tr "repo.pulls.still_in_progress"}} {{ctx.Locale.Tr "repo.pulls.add_prefix" (index .PullRequestWorkInProgressPrefixes 0)}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
@ -1,11 +1,4 @@
|
||||
<div class="issue-content">
|
||||
<!-- I know, there is probably a better way to do this (moved from sidebar.tmpl, original author: 6543 @ 2021-02-28) -->
|
||||
<!-- Agree, there should be a better way, eg: introduce window.config.pageData (original author: wxiaoguang @ 2021-09-05) -->
|
||||
<input type="hidden" id="repolink" value="{{$.RepoRelPath}}">
|
||||
<input type="hidden" id="repoId" value="{{.Repository.ID}}">
|
||||
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}">
|
||||
<input type="hidden" id="type" value="{{.IssueType}}">
|
||||
|
||||
{{$createdStr:= DateUtils.TimeSince .Issue.CreatedUnix}}
|
||||
<div class="issue-content-left comment-list prevent-before-timeline">
|
||||
<div class="ui timeline">
|
||||
|
@ -1,682 +1,24 @@
|
||||
<div class="issue-content-right ui segment">
|
||||
{{template "repo/issue/branch_selector_field" .}}
|
||||
{{if .Issue.IsPull}}
|
||||
<input id="reviewer_id" name="reviewer_id" type="hidden" value="{{.reviewer_id}}">
|
||||
<div class="ui {{if or (and (not .Reviewers) (not .TeamReviewers)) (not .CanChooseReviewer) .Repository.IsArchived}}disabled{{end}} floating jump select-reviewers-modify dropdown">
|
||||
<a class="text tw-flex tw-items-center muted">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.review.reviewers"}}</strong>
|
||||
{{if and .CanChooseReviewer (not .Repository.IsArchived)}}
|
||||
{{svg "octicon-gear" 16 "tw-ml-1"}}
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/request_review">
|
||||
{{if .Reviewers}}
|
||||
<div class="ui icon search input">
|
||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_reviewers"}}">
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Reviewers}}
|
||||
{{range .Reviewers}}
|
||||
{{if .User}}
|
||||
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_{{.ItemID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
|
||||
<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
|
||||
<span class="text">
|
||||
{{ctx.AvatarUtils.Avatar .User 28 "tw-mr-2"}}{{template "repo/search_name" .User}}
|
||||
</span>
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if .TeamReviewers}}
|
||||
{{if .Reviewers}}
|
||||
<div class="divider"></div>
|
||||
{{end}}
|
||||
{{range .TeamReviewers}}
|
||||
{{if .Team}}
|
||||
<a class="{{if not .CanChange}}ui{{end}} item {{if .Checked}}checked{{end}} {{if not .CanChange}}ban-change{{end}}" href="#" data-id="{{.ItemID}}" data-id-selector="#review_request_team_{{.Team.ID}}" {{if not .CanChange}} data-tooltip-content="{{ctx.Locale.Tr "repo.issues.remove_request_review_block"}}"{{end}}>
|
||||
<span class="octicon-check {{if not .Checked}}tw-invisible{{end}}">{{svg "octicon-check" 16}}</span>
|
||||
<span class="text">
|
||||
{{svg "octicon-people" 16 "tw-ml-4 tw-mr-1"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}
|
||||
</span>
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "repo/issue/branch_selector_field" $}}
|
||||
|
||||
<div class="ui assignees list">
|
||||
<span class="no-select item {{if or .OriginalReviews .PullReviewers}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_reviewers"}}</span>
|
||||
<div class="selected">
|
||||
{{range .PullReviewers}}
|
||||
<div class="item tw-flex tw-items-center tw-py-2">
|
||||
<div class="tw-flex tw-items-center tw-flex-1">
|
||||
{{if .User}}
|
||||
<a class="muted sidebar-item-link" href="{{.User.HomeLink}}">{{ctx.AvatarUtils.Avatar .User 20 "tw-mr-2"}}{{.User.GetDisplayName}}</a>
|
||||
{{else if .Team}}
|
||||
<span class="text">{{svg "octicon-people" 20 "tw-mr-2"}}{{$.Issue.Repo.OwnerName}}/{{.Team.Name}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
{{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged))}}
|
||||
<a href="#" class="ui muted icon tw-flex tw-items-center show-modal" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dismiss_review"}}" data-modal="#dismiss-review-modal-{{.Review.ID}}">
|
||||
{{svg "octicon-x" 20}}
|
||||
</a>
|
||||
<div class="ui small modal" id="dismiss-review-modal-{{.Review.ID}}">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.issues.dismiss_review"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui warning message">
|
||||
{{ctx.Locale.Tr "repo.issues.dismiss_review_warning"}}
|
||||
</div>
|
||||
<form class="ui form dismiss-review-form" id="dismiss-review-{{.Review.ID}}" action="{{$.RepoLink}}/issues/dismiss_review" method="post">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input type="hidden" name="review_id" value="{{.Review.ID}}">
|
||||
<div class="field">
|
||||
<label for="message">{{ctx.Locale.Tr "action.review_dismissed_reason"}}</label>
|
||||
<input id="message" name="message">
|
||||
</div>
|
||||
<div class="text right actions">
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||
<button class="ui red button" type="submit">{{ctx.Locale.Tr "ok"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Review.Stale}}
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.is_stale"}}">
|
||||
{{svg "octicon-hourglass" 16}}
|
||||
</span>
|
||||
{{end}}
|
||||
{{if and .CanChange (or .Checked (and (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged)))}}
|
||||
<a href="#" class="ui muted icon re-request-review{{if .Checked}} checked{{end}}" data-tooltip-content="{{if .Checked}}{{ctx.Locale.Tr "repo.issues.remove_request_review"}}{{else}}{{ctx.Locale.Tr "repo.issues.re_request_review"}}{{end}}" data-issue-id="{{$.Issue.ID}}" data-id="{{.ItemID}}" data-update-url="{{$.RepoLink}}/issues/request_review">{{svg (Iif .Checked "octicon-trash" "octicon-sync")}}</a>
|
||||
{{end}}
|
||||
<span {{if .Review.TooltipContent}}data-tooltip-content="{{ctx.Locale.Tr .Review.TooltipContent}}"{{end}}>
|
||||
{{svg (printf "octicon-%s" .Review.Type.Icon) 16 (printf "text %s" (.Review.HTMLTypeColorName))}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{range .OriginalReviews}}
|
||||
<div class="item tw-flex tw-items-center tw-py-2">
|
||||
<div class="tw-flex tw-items-center tw-flex-1">
|
||||
<a class="muted" href="{{$.Repository.OriginalURL}}" data-tooltip-content="{{ctx.Locale.Tr "repo.migrated_from_fake" $.Repository.GetOriginalURLHostname}}">
|
||||
{{svg (MigrationIcon $.Repository.GetOriginalURLHostname) 20 "tw-mr-2"}}
|
||||
{{.OriginalAuthor}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="tw-flex tw-items-center tw-gap-2">
|
||||
<span {{if .TooltipContent}}data-tooltip-content="{{ctx.Locale.Tr .TooltipContent}}"{{end}}>
|
||||
{{svg (printf "octicon-%s" .Type.Icon) 16 (printf "text %s" (.HTMLTypeColorName))}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .HasMerged) (not .Issue.IsClosed) (not .IsPullWorkInProgress)}}
|
||||
<div class="toggle-wip" data-title="{{.Issue.Title}}" data-wip-prefix="{{index .PullRequestWorkInProgressPrefixes 0}}" data-update-url="{{.Issue.Link}}/title">
|
||||
<a class="muted">
|
||||
{{ctx.Locale.Tr "repo.pulls.still_in_progress"}} {{ctx.Locale.Tr "repo.pulls.add_prefix" (index .PullRequestWorkInProgressPrefixes 0)}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Issue.IsPull}}
|
||||
{{template "repo/issue/sidebar/reviewer_list" $}}
|
||||
{{template "repo/issue/sidebar/wip_switch" $}}
|
||||
<div class="divider"></div>
|
||||
{{end}}
|
||||
|
||||
{{template "repo/issue/labels/labels_selector_field" .}}
|
||||
{{template "repo/issue/labels/labels_selector_field" $}}
|
||||
{{template "repo/issue/labels/labels_sidebar" dict "root" $}}
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-milestone dropdown">
|
||||
<a class="text muted flex-text-block">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.new.milestone"}}</strong>
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
{{svg "octicon-gear" 16 "tw-ml-1"}}
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/milestone">
|
||||
{{template "repo/issue/milestone/select_menu" .}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui select-milestone list">
|
||||
<span class="no-select item {{if .Issue.Milestone}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_milestone"}}</span>
|
||||
<div class="selected">
|
||||
{{if .Issue.Milestone}}
|
||||
<a class="item muted sidebar-item-link" href="{{.RepoLink}}/milestone/{{.Issue.Milestone.ID}}">
|
||||
{{svg "octicon-milestone" 18 "tw-mr-2"}}
|
||||
{{.Issue.Milestone.Name}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .IsProjectsEnabled}}
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-project dropdown">
|
||||
<a class="text muted flex-text-block">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.new.projects"}}</strong>
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
{{svg "octicon-gear" 16 "tw-ml-1"}}
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/projects">
|
||||
{{if or .OpenProjects .ClosedProjects}}
|
||||
<div class="ui icon search input">
|
||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_projects"}}">
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_projects"}}</div>
|
||||
{{if .OpenProjects}}
|
||||
<div class="divider"></div>
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.issues.new.open_projects"}}
|
||||
</div>
|
||||
{{range .OpenProjects}}
|
||||
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if .ClosedProjects}}
|
||||
<div class="divider"></div>
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "repo.issues.new.closed_projects"}}
|
||||
</div>
|
||||
{{range .ClosedProjects}}
|
||||
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui select-project list">
|
||||
<span class="no-select item {{if .Issue.Project}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_projects"}}</span>
|
||||
<div class="selected">
|
||||
{{if .Issue.Project}}
|
||||
<a class="item muted sidebar-item-link" href="{{.Issue.Project.Link ctx}}">
|
||||
{{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<input id="assignee_id" name="assignee_id" type="hidden" value="{{.assignee_id}}">
|
||||
<div class="ui {{if or (not .HasIssuesOrPullsWritePermission) .Repository.IsArchived}}disabled{{end}} floating jump select-assignees-modify dropdown">
|
||||
<a class="text muted flex-text-block">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.new.assignees"}}</strong>
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
{{svg "octicon-gear" 16 "tw-ml-1"}}
|
||||
{{end}}
|
||||
</a>
|
||||
<div class="filter menu" data-action="update" data-issue-id="{{$.Issue.ID}}" data-update-url="{{$.RepoLink}}/issues/assignee">
|
||||
<div class="ui icon search input">
|
||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||
<input type="text" placeholder="{{ctx.Locale.Tr "repo.issues.filter_assignees"}}">
|
||||
</div>
|
||||
<div class="no-select item">{{ctx.Locale.Tr "repo.issues.new.clear_assignees"}}</div>
|
||||
{{range .Assignees}}
|
||||
|
||||
{{$AssigneeID := .ID}}
|
||||
<a class="item{{range $.Issue.Assignees}}{{if eq .ID $AssigneeID}} checked{{end}}{{end}}" href="#" data-id="{{.ID}}" data-id-selector="#assignee_{{.ID}}">
|
||||
{{$checked := false}}
|
||||
{{range $.Issue.Assignees}}
|
||||
{{if eq .ID $AssigneeID}}
|
||||
{{$checked = true}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
<span class="octicon-check {{if not $checked}}tw-invisible{{end}}">{{svg "octicon-check"}}</span>
|
||||
<span class="text">
|
||||
{{ctx.AvatarUtils.Avatar . 20 "tw-mr-2"}}{{template "repo/search_name" .}}
|
||||
</span>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui assignees list">
|
||||
<span class="no-select item {{if .Issue.Assignees}}tw-hidden{{end}}">{{ctx.Locale.Tr "repo.issues.new.no_assignees"}}</span>
|
||||
<div class="selected">
|
||||
{{range .Issue.Assignees}}
|
||||
<div class="item">
|
||||
<a class="muted sidebar-item-link" href="{{$.RepoLink}}/{{if $.Issue.IsPull}}pulls{{else}}issues{{end}}?assignee={{.ID}}">
|
||||
{{ctx.AvatarUtils.Avatar . 28 "tw-mr-2"}}
|
||||
{{.GetDisplayName}}
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
{{if .Participants}}
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.num_participants" .NumParticipants}}</strong></span>
|
||||
<div class="ui list tw-flex tw-flex-wrap">
|
||||
{{range .Participants}}
|
||||
<a {{if gt .ID 0}}href="{{.HomeLink}}"{{end}} data-tooltip-content="{{.GetDisplayName}}">
|
||||
{{ctx.AvatarUtils.Avatar . 28 "tw-my-0.5 tw-mr-1"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if and $.IssueWatch (not .Repository.IsArchived)}}
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="ui watching">
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "notification.notifications"}}</strong></span>
|
||||
<div class="tw-mt-2">
|
||||
{{template "repo/issue/view_content/watching" .}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Repository.IsTimetrackerEnabled ctx}}
|
||||
{{if and .CanUseTimetracker (not .Repository.IsArchived)}}
|
||||
<div class="divider"></div>
|
||||
<div class="ui timetrack">
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.tracker"}}</strong></span>
|
||||
<div class="tw-mt-2">
|
||||
<form method="post" action="{{.Issue.Link}}/times/stopwatch/toggle" id="toggle_stopwatch_form">
|
||||
{{$.CsrfTokenHtml}}
|
||||
</form>
|
||||
<form method="post" action="{{.Issue.Link}}/times/stopwatch/cancel" id="cancel_stopwatch_form">
|
||||
{{$.CsrfTokenHtml}}
|
||||
</form>
|
||||
{{if $.IsStopwatchRunning}}
|
||||
<button class="ui fluid button issue-stop-time">
|
||||
{{svg "octicon-stopwatch" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "repo.issues.stop_tracking"}}
|
||||
</button>
|
||||
<button class="ui fluid button issue-cancel-time tw-mt-2">
|
||||
{{svg "octicon-trash" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "repo.issues.cancel_tracking"}}
|
||||
</button>
|
||||
{{else}}
|
||||
{{if .HasUserStopwatch}}
|
||||
<div class="ui warning message">
|
||||
{{ctx.Locale.Tr "repo.issues.tracking_already_started" .OtherStopwatchURL}}
|
||||
</div>
|
||||
{{end}}
|
||||
<button class="ui fluid button issue-start-time" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.start_tracking"}}'>
|
||||
{{svg "octicon-stopwatch" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "repo.issues.start_tracking_short"}}
|
||||
</button>
|
||||
<div class="ui mini modal issue-start-time-modal">
|
||||
<div class="header">{{ctx.Locale.Tr "repo.issues.add_time"}}</div>
|
||||
<div class="content">
|
||||
<form method="post" id="add_time_manual_form" action="{{.Issue.Link}}/times/add" class="ui input fluid tw-gap-2">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input placeholder='{{ctx.Locale.Tr "repo.issues.add_time_hours"}}' type="number" name="hours">
|
||||
<input placeholder='{{ctx.Locale.Tr "repo.issues.add_time_minutes"}}' type="number" name="minutes" class="ui compact">
|
||||
</form>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui primary approve button">{{ctx.Locale.Tr "repo.issues.add_time_short"}}</button>
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.add_time_cancel"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui fluid button issue-add-time tw-mt-2" data-tooltip-content='{{ctx.Locale.Tr "repo.issues.add_time"}}'>
|
||||
{{svg "octicon-plus" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "repo.issues.add_time_short"}}
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .WorkingUsers}}
|
||||
<div class="divider"></div>
|
||||
<div class="ui comments">
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time)}}</strong></span>
|
||||
<div>
|
||||
{{range $user, $trackedtime := .WorkingUsers}}
|
||||
<div class="comment tw-mt-2">
|
||||
<a class="avatar">
|
||||
{{ctx.AvatarUtils.Avatar $user}}
|
||||
</a>
|
||||
<div class="content">
|
||||
{{template "shared/user/authorlink" $user}}
|
||||
<div class="text">
|
||||
{{$trackedtime|Sec2Time}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
<div class="divider"></div>
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.due_date"}}</strong></span>
|
||||
<div class="ui form tw-mt-2">
|
||||
{{if .Issue.DeadlineUnix}}
|
||||
<div class="tw-flex tw-justify-between tw-items-center tw-gap-2">
|
||||
<div class="due-date {{if .Issue.IsOverdue}}text red{{end}}" {{if .Issue.IsOverdue}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_overdue"}}"{{end}}>
|
||||
{{svg "octicon-calendar"}} {{DateUtils.AbsoluteLong .Issue.DeadlineUnix}}
|
||||
</div>
|
||||
<div class="flex-text-block">
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
<a class="issue-due-edit muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_edit"}}">{{svg "octicon-pencil"}}</a>
|
||||
<a class="issue-due-remove muted" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.due_date_form_remove"}}">{{svg "octicon-trash"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.due_date_not_set"}}
|
||||
{{end}}
|
||||
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
<form class="ui fluid action input issue-due-form form-fetch-action tw-mt-2 {{if .Issue.DeadlineUnix}}tw-hidden{{end}}"
|
||||
method="post" action="{{AppSubUrl}}/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}/deadline"
|
||||
>
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input required type="date" name="deadline" placeholder="{{ctx.Locale.Tr "repo.issues.due_date_form"}}" {{if .Issue.DeadlineUnix}}value="{{.Issue.DeadlineUnix.FormatDate}}"{{end}}>
|
||||
<button class="ui icon button">{{Iif .Issue.DeadlineUnix (svg "octicon-pencil") (svg "octicon-plus")}}</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Repository.IsDependenciesEnabled ctx}}
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="ui depending">
|
||||
{{if (and (not .BlockedByDependencies) (not .BlockedByDependenciesNotPermitted) (not .BlockingDependencies) (not .BlockingDependenciesNotPermitted))}}
|
||||
<span class="text"><strong>{{ctx.Locale.Tr "repo.issues.dependency.title"}}</strong></span>
|
||||
<br>
|
||||
<p>
|
||||
{{if .Issue.IsPull}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.pr_no_dependencies"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.issue_no_dependencies"}}
|
||||
{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
{{if or .BlockingDependencies .BlockingDependenciesNotPermitted}}
|
||||
<span class="text" data-tooltip-content="{{if .Issue.IsPull}}{{ctx.Locale.Tr "repo.issues.dependency.pr_close_blocks"}}{{else}}{{ctx.Locale.Tr "repo.issues.dependency.issue_close_blocks"}}{{end}}">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.dependency.blocks_short"}}</strong>
|
||||
</span>
|
||||
<div class="ui relaxed divided list">
|
||||
{{range .BlockingDependencies}}
|
||||
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
|
||||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}
|
||||
</a>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
{{.Repository.OwnerName}}/{{.Repository.Name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right tw-flex tw-items-center tw-m-1">
|
||||
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
|
||||
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
|
||||
{{svg "octicon-trash" 16}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .BlockingDependenciesNotPermitted}}
|
||||
<div class="item tw-flex tw-items-center tw-justify-between gt-ellipsis">
|
||||
<span>{{ctx.Locale.TrN (len .BlockingDependenciesNotPermitted) "repo.issues.dependency.no_permission_1" "repo.issues.dependency.no_permission_n" (len .BlockingDependenciesNotPermitted)}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if or .BlockedByDependencies .BlockedByDependenciesNotPermitted}}
|
||||
<span class="text" data-tooltip-content="{{if .Issue.IsPull}}{{ctx.Locale.Tr "repo.issues.dependency.pr_closing_blockedby"}}{{else}}{{ctx.Locale.Tr "repo.issues.dependency.issue_closing_blockedby"}}{{end}}">
|
||||
<strong>{{ctx.Locale.Tr "repo.issues.dependency.blocked_by_short"}}</strong>
|
||||
</span>
|
||||
<div class="ui relaxed divided list">
|
||||
{{range .BlockedByDependencies}}
|
||||
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
|
||||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<a class="title muted" href="{{.Issue.Link}}" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}
|
||||
</a>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
{{.Repository.OwnerName}}/{{.Repository.Name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right tw-flex tw-items-center tw-m-1">
|
||||
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
|
||||
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blockedBy" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
|
||||
{{svg "octicon-trash" 16}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if $.CanCreateIssueDependencies}}
|
||||
{{range .BlockedByDependenciesNotPermitted}}
|
||||
<div class="item dependency{{if .Issue.IsClosed}} is-closed{{end}} tw-flex tw-items-center tw-justify-between">
|
||||
<div class="item-left tw-flex tw-justify-center tw-flex-col tw-flex-1 gt-ellipsis">
|
||||
<div class="gt-ellipsis">
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.no_permission.can_remove"}}">{{svg "octicon-lock" 16}}</span>
|
||||
<span class="title" data-tooltip-content="#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}">
|
||||
#{{.Issue.Index}} {{.Issue.Title | ctx.RenderUtils.RenderEmoji}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text small gt-ellipsis" data-tooltip-content="{{.Repository.OwnerName}}/{{.Repository.Name}}">
|
||||
{{.Repository.OwnerName}}/{{.Repository.Name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right tw-flex tw-items-center tw-m-1">
|
||||
{{if and $.CanCreateIssueDependencies (not $.Repository.IsArchived)}}
|
||||
<a class="delete-dependency-button ci muted" data-id="{{.Issue.ID}}" data-type="blocking" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dependency.remove_info"}}">
|
||||
{{svg "octicon-trash" 16}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else if .BlockedByDependenciesNotPermitted}}
|
||||
<div class="item tw-flex tw-items-center tw-justify-between gt-ellipsis">
|
||||
<span>{{ctx.Locale.TrN (len .BlockedByDependenciesNotPermitted) "repo.issues.dependency.no_permission_1" "repo.issues.dependency.no_permission_n" (len .BlockedByDependenciesNotPermitted)}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
|
||||
<div>
|
||||
<form method="post" action="{{.Issue.Link}}/dependency/add" id="addDependencyForm">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<div class="ui fluid action input">
|
||||
<div class="ui search selection dropdown" id="new-dependency-drop-list" data-issue-id="{{.Issue.ID}}">
|
||||
<input name="newDependency" type="hidden">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<input type="text" class="search">
|
||||
<div class="default text">{{ctx.Locale.Tr "repo.issues.dependency.add"}}</div>
|
||||
</div>
|
||||
<button class="ui icon button">
|
||||
{{svg "octicon-plus"}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}}
|
||||
<input type="hidden" id="crossRepoSearch" value="{{.AllowCrossRepositoryDependencies}}">
|
||||
|
||||
<div class="ui g-modal-confirm modal remove-dependency">
|
||||
<div class="header">
|
||||
{{svg "octicon-trash"}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.remove_header"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<form method="post" action="{{.Issue.Link}}/dependency/delete" id="removeDependencyForm">
|
||||
{{$.CsrfTokenHtml}}
|
||||
<input type="hidden" value="" name="removeDependencyID" id="removeDependencyID">
|
||||
<input type="hidden" value="" name="dependencyType" id="dependencyType">
|
||||
</form>
|
||||
<p>{{if .Issue.IsPull}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.pr_remove_text"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.dependency.issue_remove_text"}}
|
||||
{{end}}</p>
|
||||
</div>
|
||||
{{$ModalButtonCancelText := ctx.Locale.Tr "repo.issues.dependency.cancel"}}
|
||||
{{$ModalButtonOkText := ctx.Locale.Tr "repo.issues.dependency.remove"}}
|
||||
{{template "base/modal_actions_confirm" (dict "." . "ModalButtonCancelText" $ModalButtonCancelText "ModalButtonOkText" $ModalButtonOkText)}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
<div class="divider"></div>
|
||||
<div class="ui equal width compact grid">
|
||||
{{$issueReferenceLink := printf "%s#%d" .Issue.Repo.FullName .Issue.Index}}
|
||||
<div class="row tw-items-center" data-tooltip-content="{{$issueReferenceLink}}">
|
||||
<span class="text column truncate">{{ctx.Locale.Tr "repo.issues.reference_link" $issueReferenceLink}}</span>
|
||||
<button class="ui two wide button column tw-p-2" data-clipboard-text="{{$issueReferenceLink}}">{{svg "octicon-copy" 14}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if and .IsRepoAdmin (not .Repository.IsArchived)}}
|
||||
<div class="divider"></div>
|
||||
|
||||
{{if or .PinEnabled .Issue.IsPinned}}
|
||||
<form class="tw-mt-1 form-fetch-action single-button-form" method="post" {{if $.NewPinAllowed}}action="{{.Issue.Link}}/pin"{{else}}data-tooltip-content="{{ctx.Locale.Tr "repo.issues.max_pinned"}}"{{end}}>
|
||||
{{$.CsrfTokenHtml}}
|
||||
<button class="fluid ui button {{if not $.NewPinAllowed}}disabled{{end}}">
|
||||
{{if not .Issue.IsPinned}}
|
||||
{{svg "octicon-pin" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "pin"}}
|
||||
{{else}}
|
||||
{{svg "octicon-pin-slash" 16 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "unpin"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
<button class="tw-mt-1 fluid ui show-modal button{{if .Issue.IsLocked}} red{{end}}" data-modal="#lock">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{svg "octicon-key"}}
|
||||
{{ctx.Locale.Tr "repo.issues.unlock"}}
|
||||
{{else}}
|
||||
{{svg "octicon-lock"}}
|
||||
{{ctx.Locale.Tr "repo.issues.lock"}}
|
||||
{{end}}
|
||||
</button>
|
||||
<div class="ui tiny modal" id="lock">
|
||||
<div class="header">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{ctx.Locale.Tr "repo.issues.unlock.title"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.lock.title"}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui warning message">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{ctx.Locale.Tr "repo.issues.unlock.notice_1"}}<br>
|
||||
{{ctx.Locale.Tr "repo.issues.unlock.notice_2"}}<br>
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.lock.notice_1"}}<br>
|
||||
{{ctx.Locale.Tr "repo.issues.lock.notice_2"}}<br>
|
||||
{{ctx.Locale.Tr "repo.issues.lock.notice_3"}}<br>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<form class="ui form form-fetch-action" action="{{.Issue.Link}}{{if .Issue.IsLocked}}/unlock{{else}}/lock{{end}}"
|
||||
method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
|
||||
{{if not .Issue.IsLocked}}
|
||||
<div class="field">
|
||||
<strong> {{ctx.Locale.Tr "repo.issues.lock.reason"}} </strong>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="ui fluid dropdown selection">
|
||||
|
||||
<select name="reason">
|
||||
<option value=""> </option>
|
||||
{{range .LockReasons}}
|
||||
<option value="{{.}}">{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
|
||||
<div class="default text"> </div>
|
||||
|
||||
<div class="menu">
|
||||
{{range .LockReasons}}
|
||||
<div class="item" data-value="{{.}}">{{.}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="text right actions">
|
||||
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
|
||||
<button class="ui red button">
|
||||
{{if .Issue.IsLocked}}
|
||||
{{ctx.Locale.Tr "repo.issues.unlock_confirm"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.lock_confirm"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<button class="tw-mt-1 fluid ui show-modal button" data-modal="#sidebar-delete-issue">
|
||||
{{svg "octicon-trash"}}
|
||||
{{ctx.Locale.Tr "repo.issues.delete"}}
|
||||
</button>
|
||||
<div class="ui g-modal-confirm modal" id="sidebar-delete-issue">
|
||||
<div class="header">
|
||||
{{if .Issue.IsPull}}
|
||||
{{ctx.Locale.Tr "repo.pulls.delete.title"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.delete.title"}}
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>
|
||||
{{if .Issue.IsPull}}
|
||||
{{ctx.Locale.Tr "repo.pulls.delete.text"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.issues.delete.text"}}
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
<form action="{{.Issue.Link}}/delete" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
{{template "base/modal_actions_confirm" .}}
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed) .Issue.PullRequest.HeadRepo}}
|
||||
{{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
|
||||
<div class="divider"></div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox loading-icon-2px" id="allow-edits-from-maintainers"
|
||||
data-url="{{.Issue.Link}}"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_desc"}}"
|
||||
data-prompt-error="{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers_err"}}"
|
||||
>
|
||||
<label><strong>{{ctx.Locale.Tr "repo.pulls.allow_edits_from_maintainers"}}</strong></label>
|
||||
<input type="checkbox" {{if .Issue.PullRequest.AllowMaintainerEdit}}checked{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{template "repo/issue/sidebar/milestone_list" $}}
|
||||
{{template "repo/issue/sidebar/project_list" $}}
|
||||
{{template "repo/issue/sidebar/assignee_list" $}}
|
||||
{{template "repo/issue/sidebar/participant_list" $}}
|
||||
{{template "repo/issue/sidebar/watch_notification" $}}
|
||||
{{template "repo/issue/sidebar/stopwatch_timetracker" $}}
|
||||
{{template "repo/issue/sidebar/due_date" $}}
|
||||
{{template "repo/issue/sidebar/issue_dependencies" $}}
|
||||
{{template "repo/issue/sidebar/reference_link" $}}
|
||||
{{template "repo/issue/sidebar/issue_management" $}}
|
||||
{{template "repo/issue/sidebar/allow_maintainer_edit" $}}
|
||||
</div>
|
||||
|
@ -3,6 +3,12 @@
|
||||
{{template "base/alert" .}}
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="tw-hidden" id="issue-page-info"
|
||||
data-issue-index="{{$.Issue.Index}}"
|
||||
data-issue-dependency-search-type="{{$.IssueDependencySearchType}}"
|
||||
data-issue-repo-link="{{$.RepoLink}}"
|
||||
data-issue-repo-id="{{$.Repository.ID}}"
|
||||
></div>
|
||||
<div class="issue-title-header">
|
||||
{{$canEditIssueTitle := and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
|
||||
<div class="issue-title" id="issue-title-display">
|
||||
|
@ -1,8 +1,4 @@
|
||||
{{template "base/head" .}}
|
||||
|
||||
<input type="hidden" id="repolink" value="{{$.RepoRelPath}}">
|
||||
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}">
|
||||
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content repository view issue pull files diff">
|
||||
{{template "repo/header" .}}
|
||||
<div class="ui container fluid padded">
|
||||
|
@ -4,17 +4,20 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/release"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
@ -117,3 +120,47 @@ func TestCreateNewTagProtected(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepushTag(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
session := loginUser(t, owner.LowerName)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
httpContext := NewAPITestContext(t, owner.Name, repo.Name)
|
||||
|
||||
dstPath := t.TempDir()
|
||||
|
||||
u.Path = httpContext.GitPath()
|
||||
u.User = url.UserPassword(owner.Name, userPassword)
|
||||
|
||||
doGitClone(dstPath, u)(t)
|
||||
|
||||
// create and push a tag
|
||||
_, _, err := git.NewCommand(git.DefaultContext, "tag", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
assert.NoError(t, err)
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
assert.NoError(t, err)
|
||||
// create a release for the tag
|
||||
createdRelease := createNewReleaseUsingAPI(t, token, owner, repo, "v2.0", "", "Release of v2.0", "desc")
|
||||
assert.False(t, createdRelease.IsDraft)
|
||||
// delete the tag
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--delete", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
assert.NoError(t, err)
|
||||
// query the release by API and it should be a draft
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0"))
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var respRelease *api.Release
|
||||
DecodeJSON(t, resp, &respRelease)
|
||||
assert.True(t, respRelease.IsDraft)
|
||||
// re-push the tag
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "origin", "--tags", "v2.0").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
assert.NoError(t, err)
|
||||
// query the release by API and it should not be a draft
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, "v2.0"))
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &respRelease)
|
||||
assert.False(t, respRelease.IsDraft)
|
||||
})
|
||||
}
|
||||
|
@ -73,7 +73,7 @@
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.page-footer .ui.dropdown.language .menu {
|
||||
.page-footer .ui.dropdown .menu.language-menu {
|
||||
max-height: min(500px, calc(100vh - 60px));
|
||||
overflow-y: auto;
|
||||
margin-bottom: 10px;
|
||||
|
@ -62,23 +62,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.repository .issue-content-right .ui.list .dependency {
|
||||
padding: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.repository .issue-content-right .ui.list .title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.repository .issue-content-right #deadlineForm input {
|
||||
width: 12.8rem;
|
||||
border-radius: var(--border-radius) 0 0 var(--border-radius);
|
||||
border-right: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.repository .issue-content-right .filter.menu {
|
||||
max-height: 500px;
|
||||
overflow-x: auto;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import $ from 'jquery';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {showGlobalErrorMessage} from '../bootstrap.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {queryElems} from '../utils/dom.ts';
|
||||
|
||||
const {appUrl} = window.config;
|
||||
|
||||
@ -17,18 +18,18 @@ export function initHeadNavbarContentToggle() {
|
||||
}
|
||||
|
||||
export function initFootLanguageMenu() {
|
||||
async function linkLanguageAction() {
|
||||
const $this = $(this);
|
||||
await GET($this.data('url'));
|
||||
document.querySelector('.ui.dropdown .menu.language-menu')?.addEventListener('click', async (e) => {
|
||||
const item = (e.target as HTMLElement).closest('.item');
|
||||
if (!item) return;
|
||||
e.preventDefault();
|
||||
await GET(item.getAttribute('data-url'));
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
$('.language-menu a[lang]').on('click', linkLanguageAction);
|
||||
});
|
||||
}
|
||||
|
||||
export function initGlobalDropdown() {
|
||||
// Semantic UI modules.
|
||||
const $uiDropdowns = $('.ui.dropdown');
|
||||
const $uiDropdowns = fomanticQuery('.ui.dropdown');
|
||||
|
||||
// do not init "custom" dropdowns, "custom" dropdowns are managed by their own code.
|
||||
$uiDropdowns.filter(':not(.custom)').dropdown();
|
||||
@ -46,14 +47,14 @@ export function initGlobalDropdown() {
|
||||
},
|
||||
onHide() {
|
||||
this._tippy?.enable();
|
||||
// eslint-disable-next-line unicorn/no-this-assignment
|
||||
const elDropdown = this;
|
||||
|
||||
// hide all tippy elements of items after a while. eg: use Enter to click "Copy Link" in the Issue Context Menu
|
||||
setTimeout(() => {
|
||||
const $dropdown = $(this);
|
||||
const $dropdown = fomanticQuery(elDropdown);
|
||||
if ($dropdown.dropdown('is hidden')) {
|
||||
$(this).find('.menu > .item').each((_, item) => {
|
||||
item._tippy?.hide();
|
||||
});
|
||||
queryElems(elDropdown, '.menu > .item', (el) => el._tippy?.hide());
|
||||
}
|
||||
}, 2000);
|
||||
},
|
||||
@ -71,7 +72,7 @@ export function initGlobalDropdown() {
|
||||
}
|
||||
|
||||
export function initGlobalTabularMenu() {
|
||||
$('.ui.menu.tabular:not(.custom) .item').tab({autoTabActivation: false});
|
||||
fomanticQuery('.ui.menu.tabular:not(.custom) .item').tab({autoTabActivation: false});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,14 +1,14 @@
|
||||
import $ from 'jquery';
|
||||
import {GET} from '../modules/fetch.ts';
|
||||
import {hideElem, loadElem, queryElemChildren, queryElems} from '../utils/dom.ts';
|
||||
import {parseDom} from '../utils.ts';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
|
||||
function getDefaultSvgBoundsIfUndefined(text, src) {
|
||||
const defaultSize = 300;
|
||||
const maxSize = 99999;
|
||||
|
||||
const svgDoc = parseDom(text, 'image/svg+xml');
|
||||
const svg = svgDoc.documentElement;
|
||||
const svg = (svgDoc.documentElement as unknown) as SVGSVGElement;
|
||||
const width = svg?.width?.baseVal;
|
||||
const height = svg?.height?.baseVal;
|
||||
if (width === undefined || height === undefined) {
|
||||
@ -68,12 +68,14 @@ function createContext(imageAfter, imageBefore) {
|
||||
}
|
||||
|
||||
class ImageDiff {
|
||||
async init(containerEl) {
|
||||
containerEl: HTMLElement;
|
||||
diffContainerWidth: number;
|
||||
|
||||
async init(containerEl: HTMLElement) {
|
||||
this.containerEl = containerEl;
|
||||
containerEl.setAttribute('data-image-diff-loaded', 'true');
|
||||
|
||||
// the only jQuery usage in this file
|
||||
$(containerEl).find('.ui.menu.tabular .item').tab({autoTabActivation: false});
|
||||
fomanticQuery(containerEl).find('.ui.menu.tabular .item').tab({autoTabActivation: false});
|
||||
|
||||
// the container may be hidden by "viewed" checkbox, so use the parent's width for reference
|
||||
this.diffContainerWidth = Math.max(containerEl.closest('.diff-file-box').clientWidth - 300, 100);
|
||||
@ -81,12 +83,12 @@ class ImageDiff {
|
||||
const imageInfos = [{
|
||||
path: containerEl.getAttribute('data-path-after'),
|
||||
mime: containerEl.getAttribute('data-mime-after'),
|
||||
images: containerEl.querySelectorAll('img.image-after'), // matches 3 <img>
|
||||
images: containerEl.querySelectorAll<HTMLImageElement>('img.image-after'), // matches 3 <img>
|
||||
boundsInfo: containerEl.querySelector('.bounds-info-after'),
|
||||
}, {
|
||||
path: containerEl.getAttribute('data-path-before'),
|
||||
mime: containerEl.getAttribute('data-mime-before'),
|
||||
images: containerEl.querySelectorAll('img.image-before'), // matches 3 <img>
|
||||
images: containerEl.querySelectorAll<HTMLImageElement>('img.image-before'), // matches 3 <img>
|
||||
boundsInfo: containerEl.querySelector('.bounds-info-before'),
|
||||
}];
|
||||
|
||||
@ -102,8 +104,8 @@ class ImageDiff {
|
||||
const bounds = getDefaultSvgBoundsIfUndefined(text, info.path);
|
||||
if (bounds) {
|
||||
for (const el of info.images) {
|
||||
el.setAttribute('width', bounds.width);
|
||||
el.setAttribute('height', bounds.height);
|
||||
el.setAttribute('width', String(bounds.width));
|
||||
el.setAttribute('height', String(bounds.height));
|
||||
}
|
||||
hideElem(info.boundsInfo);
|
||||
}
|
||||
@ -151,7 +153,7 @@ class ImageDiff {
|
||||
const boundsInfoBeforeHeight = this.containerEl.querySelector('.bounds-info-before .bounds-info-height');
|
||||
if (boundsInfoBeforeHeight) {
|
||||
boundsInfoBeforeHeight.textContent = `${sizes.imageBefore.naturalHeight}px`;
|
||||
boundsInfoBeforeHeight.classList.add('red', heightChanged);
|
||||
boundsInfoBeforeHeight.classList.toggle('red', heightChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +207,7 @@ class ImageDiff {
|
||||
}
|
||||
|
||||
// extra height for inner "position: absolute" elements
|
||||
const swipe = this.containerEl.querySelector('.diff-swipe');
|
||||
const swipe = this.containerEl.querySelector<HTMLElement>('.diff-swipe');
|
||||
if (swipe) {
|
||||
swipe.style.width = `${sizes.maxSize.width * factor + 2}px`;
|
||||
swipe.style.height = `${sizes.maxSize.height * factor + 30}px`;
|
||||
@ -225,7 +227,7 @@ class ImageDiff {
|
||||
const rect = swipeFrame.getBoundingClientRect();
|
||||
const value = Math.max(0, Math.min(e.clientX - rect.left, width));
|
||||
swipeBar.style.left = `${value}px`;
|
||||
this.containerEl.querySelector('.swipe-container').style.width = `${swipeFrame.clientWidth - value}px`;
|
||||
this.containerEl.querySelector<HTMLElement>('.swipe-container').style.width = `${swipeFrame.clientWidth - value}px`;
|
||||
};
|
||||
const removeEventListeners = () => {
|
||||
document.removeEventListener('mousemove', onSwipeMouseMove);
|
||||
@ -264,11 +266,11 @@ class ImageDiff {
|
||||
overlayFrame.style.height = `${sizes.maxSize.height * factor + 2}px`;
|
||||
}
|
||||
|
||||
const rangeInput = this.containerEl.querySelector('input[type="range"]');
|
||||
const rangeInput = this.containerEl.querySelector<HTMLInputElement>('input[type="range"]');
|
||||
|
||||
function updateOpacity() {
|
||||
if (sizes.imageAfter) {
|
||||
sizes.imageAfter.parentNode.style.opacity = `${rangeInput.value / 100}`;
|
||||
sizes.imageAfter.parentNode.style.opacity = `${Number(rangeInput.value) / 100}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,7 +280,7 @@ class ImageDiff {
|
||||
}
|
||||
|
||||
export function initImageDiff() {
|
||||
for (const el of queryElems('.image-diff:not([data-image-diff-loaded])')) {
|
||||
for (const el of queryElems<HTMLImageElement>(document, '.image-diff:not([data-image-diff-loaded])')) {
|
||||
(new ImageDiff()).init(el); // it is async, but we don't need to await for it
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ async function onDownloadArchive(e) {
|
||||
}
|
||||
|
||||
export function initRepoArchiveLinks() {
|
||||
queryElems('a.archive-link[href]', (el) => el.addEventListener('click', onDownloadArchive));
|
||||
queryElems(document, 'a.archive-link[href]', (el) => el.addEventListener('click', onDownloadArchive));
|
||||
}
|
||||
|
||||
export function initRepoActivityTopAuthorsChart() {
|
||||
|
@ -45,17 +45,17 @@ export function initRepoEditor() {
|
||||
const dropzoneUpload = document.querySelector('.page-content.repository.editor.upload .dropzone');
|
||||
if (dropzoneUpload) initDropzone(dropzoneUpload);
|
||||
|
||||
const editArea = document.querySelector('.page-content.repository.editor textarea#edit_area');
|
||||
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
|
||||
if (!editArea) return;
|
||||
|
||||
for (const el of queryElems('.js-quick-pull-choice-option')) {
|
||||
for (const el of queryElems<HTMLInputElement>(document, '.js-quick-pull-choice-option')) {
|
||||
el.addEventListener('input', () => {
|
||||
if (el.value === 'commit-to-new-branch') {
|
||||
showElem('.quick-pull-branch-name');
|
||||
document.querySelector('.quick-pull-branch-name input').required = true;
|
||||
document.querySelector<HTMLInputElement>('.quick-pull-branch-name input').required = true;
|
||||
} else {
|
||||
hideElem('.quick-pull-branch-name');
|
||||
document.querySelector('.quick-pull-branch-name input').required = false;
|
||||
document.querySelector<HTMLInputElement>('.quick-pull-branch-name input').required = false;
|
||||
}
|
||||
document.querySelector('#commit-button').textContent = el.getAttribute('data-button-text');
|
||||
});
|
||||
@ -71,13 +71,13 @@ export function initRepoEditor() {
|
||||
if (filenameInput.value) {
|
||||
parts.push(filenameInput.value);
|
||||
}
|
||||
document.querySelector('#tree_path').value = parts.join('/');
|
||||
document.querySelector<HTMLInputElement>('#tree_path').value = parts.join('/');
|
||||
}
|
||||
filenameInput.addEventListener('input', function () {
|
||||
const parts = filenameInput.value.split('/');
|
||||
const links = Array.from(document.querySelectorAll('.breadcrumb span.section'));
|
||||
const dividers = Array.from(document.querySelectorAll('.breadcrumb .breadcrumb-divider'));
|
||||
let warningDiv = document.querySelector('.ui.warning.message.flash-message.flash-warning.space-related');
|
||||
let warningDiv = document.querySelector<HTMLDivElement>('.ui.warning.message.flash-message.flash-warning.space-related');
|
||||
let containSpace = false;
|
||||
if (parts.length > 1) {
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
@ -110,14 +110,14 @@ export function initRepoEditor() {
|
||||
filenameInput.value = value;
|
||||
}
|
||||
this.setSelectionRange(0, 0);
|
||||
containSpace |= (trimValue !== value && trimValue !== '');
|
||||
containSpace = containSpace || (trimValue !== value && trimValue !== '');
|
||||
}
|
||||
}
|
||||
containSpace |= Array.from(links).some((link) => {
|
||||
containSpace = containSpace || Array.from(links).some((link) => {
|
||||
const value = link.querySelector('a').textContent;
|
||||
return value.trim() !== value;
|
||||
});
|
||||
containSpace |= parts[parts.length - 1].trim() !== parts[parts.length - 1];
|
||||
containSpace = containSpace || parts[parts.length - 1].trim() !== parts[parts.length - 1];
|
||||
if (containSpace) {
|
||||
if (!warningDiv) {
|
||||
warningDiv = document.createElement('div');
|
||||
@ -135,8 +135,8 @@ export function initRepoEditor() {
|
||||
joinTreePath();
|
||||
});
|
||||
filenameInput.addEventListener('keydown', function (e) {
|
||||
const sections = queryElems('.breadcrumb span.section');
|
||||
const dividers = queryElems('.breadcrumb .breadcrumb-divider');
|
||||
const sections = queryElems(document, '.breadcrumb span.section');
|
||||
const dividers = queryElems(document, '.breadcrumb .breadcrumb-divider');
|
||||
// Jump back to last directory once the filename is empty
|
||||
if (e.code === 'Backspace' && filenameInput.selectionStart === 0 && sections.length > 0) {
|
||||
e.preventDefault();
|
||||
@ -159,7 +159,7 @@ export function initRepoEditor() {
|
||||
|
||||
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
|
||||
// to enable or disable the commit button
|
||||
const commitButton = document.querySelector('#commit-button');
|
||||
const commitButton = document.querySelector<HTMLButtonElement>('#commit-button');
|
||||
const $editForm = $('.ui.edit.form');
|
||||
const dirtyFileClass = 'dirty-file';
|
||||
|
||||
|
@ -3,8 +3,8 @@ import {svg} from '../svg.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {GET, POST} from '../modules/fetch.ts';
|
||||
import {showElem} from '../utils/dom.ts';
|
||||
import {parseIssuePageInfo} from '../utils.ts';
|
||||
|
||||
const {appSubUrl} = window.config;
|
||||
let i18nTextEdited;
|
||||
let i18nTextOptions;
|
||||
let i18nTextDeleteFromHistory;
|
||||
@ -121,15 +121,14 @@ function showContentHistoryMenu(issueBaseUrl, $item, commentId) {
|
||||
}
|
||||
|
||||
export async function initRepoIssueContentHistory() {
|
||||
const issueIndex = $('#issueIndex').val();
|
||||
if (!issueIndex) return;
|
||||
const issuePageInfo = parseIssuePageInfo();
|
||||
if (!issuePageInfo.issueNumber) return;
|
||||
|
||||
const $itemIssue = $('.repository.issue .timeline-item.comment.first'); // issue(PR) main content
|
||||
const $comments = $('.repository.issue .comment-list .comment'); // includes: issue(PR) comments, review comments, code comments
|
||||
if (!$itemIssue.length && !$comments.length) return;
|
||||
|
||||
const repoLink = $('#repolink').val();
|
||||
const issueBaseUrl = `${appSubUrl}/${repoLink}/issues/${issueIndex}`;
|
||||
const issueBaseUrl = `${issuePageInfo.repoLink}/issues/${issuePageInfo.issueNumber}`;
|
||||
|
||||
try {
|
||||
const response = await GET(`${issueBaseUrl}/content-history/overview`);
|
||||
|
@ -4,7 +4,7 @@ import {createTippy, showTemporaryTooltip} from '../modules/tippy.ts';
|
||||
import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
|
||||
import {setFileFolding} from './file-fold.ts';
|
||||
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||
import {toAbsoluteUrl} from '../utils.ts';
|
||||
import {parseIssuePageInfo, toAbsoluteUrl} from '../utils.ts';
|
||||
import {GET, POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {initRepoIssueSidebar} from './repo-issue-sidebar.ts';
|
||||
@ -57,13 +57,11 @@ function excludeLabel(item) {
|
||||
}
|
||||
|
||||
export function initRepoIssueSidebarList() {
|
||||
const repolink = $('#repolink').val();
|
||||
const repoId = $('#repoId').val();
|
||||
const issuePageInfo = parseIssuePageInfo();
|
||||
const crossRepoSearch = $('#crossRepoSearch').val();
|
||||
const tp = $('#type').val();
|
||||
let issueSearchUrl = `${appSubUrl}/${repolink}/issues/search?q={query}&type=${tp}`;
|
||||
let issueSearchUrl = `${issuePageInfo.repoLink}/issues/search?q={query}&type=${issuePageInfo.issueDependencySearchType}`;
|
||||
if (crossRepoSearch === 'true') {
|
||||
issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
|
||||
issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${issuePageInfo.repoId}&type=${issuePageInfo.issueDependencySearchType}`;
|
||||
}
|
||||
$('#new-dependency-drop-list')
|
||||
.dropdown({
|
||||
|
@ -8,7 +8,7 @@ const {appSubUrl, csrfToken} = window.config;
|
||||
|
||||
function initRepoSettingsCollaboration() {
|
||||
// Change collaborator access mode
|
||||
for (const dropdownEl of queryElems('.page-content.repository .ui.dropdown.access-mode')) {
|
||||
for (const dropdownEl of queryElems(document, '.page-content.repository .ui.dropdown.access-mode')) {
|
||||
const textEl = dropdownEl.querySelector(':scope > .text');
|
||||
$(dropdownEl).dropdown({
|
||||
async action(text, value) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import $ from 'jquery';
|
||||
let ariaIdCounter = 0;
|
||||
|
||||
export function generateAriaId() {
|
||||
@ -16,3 +17,6 @@ export function linkLabelAndInput(label, input) {
|
||||
label.setAttribute('for', id);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-jquery/variable-pattern
|
||||
export const fomanticQuery = $;
|
||||
|
@ -179,11 +179,9 @@ export function initGlobalTooltips() {
|
||||
}
|
||||
|
||||
export function showTemporaryTooltip(target: Element, content: Content) {
|
||||
// if the target is inside a dropdown, don't show the tooltip because when the dropdown
|
||||
// closes, the tippy would be pushed unsightly to the top-left of the screen like seen
|
||||
// on the issue comment menu.
|
||||
if (target.closest('.ui.dropdown > .menu')) return;
|
||||
|
||||
// if the target is inside a dropdown, the menu will be hidden soon
|
||||
// so display the tooltip on the dropdown instead
|
||||
target = target.closest('.ui.dropdown') || target;
|
||||
const tippy = target._tippy ?? attachTooltip(target, content);
|
||||
tippy.setContent(content);
|
||||
if (!tippy.state.isShown) tippy.show();
|
||||
|
@ -37,6 +37,13 @@ export type IssuePathInfo = {
|
||||
indexString?: string,
|
||||
}
|
||||
|
||||
export type IssuePageInfo = {
|
||||
repoLink: string,
|
||||
repoId: number,
|
||||
issueNumber: number,
|
||||
issueDependencySearchType: string,
|
||||
}
|
||||
|
||||
export type Issue = {
|
||||
id: number;
|
||||
number: number;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {encode, decode} from 'uint8-to-base64';
|
||||
import type {IssuePathInfo} from './types.ts';
|
||||
import {decode, encode} from 'uint8-to-base64';
|
||||
import type {IssuePageInfo, IssuePathInfo} from './types.ts';
|
||||
|
||||
// transform /path/to/file.ext to file.ext
|
||||
export function basename(path: string): string {
|
||||
@ -43,6 +43,16 @@ export function parseIssueNewHref(href: string): IssuePathInfo {
|
||||
return {ownerName, repoName, pathType, indexString};
|
||||
}
|
||||
|
||||
export function parseIssuePageInfo(): IssuePageInfo {
|
||||
const el = document.querySelector('#issue-page-info');
|
||||
return {
|
||||
issueNumber: parseInt(el?.getAttribute('data-issue-index')),
|
||||
issueDependencySearchType: el?.getAttribute('data-issue-dependency-search-type') || '',
|
||||
repoId: parseInt(el?.getAttribute('data-issue-repo-id')),
|
||||
repoLink: el?.getAttribute('data-issue-repo-link') || '',
|
||||
};
|
||||
}
|
||||
|
||||
// parse a URL, either relative '/path' or absolute 'https://localhost/path'
|
||||
export function parseUrl(str: string): URL {
|
||||
return new URL(str, str.startsWith('http') ? undefined : window.location.origin);
|
||||
|
@ -5,7 +5,7 @@ import type $ from 'jquery';
|
||||
type ElementArg = Element | string | NodeListOf<Element> | Array<Element> | ReturnType<typeof $>;
|
||||
type ElementsCallback = (el: Element) => Promisable<any>;
|
||||
type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>;
|
||||
type IterableElements = NodeListOf<Element> | Array<Element>;
|
||||
type ArrayLikeIterable<T> = ArrayLike<T> & Iterable<T>; // for NodeListOf and Array
|
||||
|
||||
function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) {
|
||||
if (typeof el === 'string' || el instanceof String) {
|
||||
@ -15,7 +15,7 @@ function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: a
|
||||
func(el, ...args);
|
||||
} else if (el.length !== undefined) {
|
||||
// this works for: NodeList, HTMLCollection, Array, jQuery
|
||||
for (const e of (el as IterableElements)) {
|
||||
for (const e of (el as ArrayLikeIterable<Element>)) {
|
||||
func(e, ...args);
|
||||
}
|
||||
} else {
|
||||
@ -58,7 +58,7 @@ export function isElemHidden(el: ElementArg) {
|
||||
return res[0];
|
||||
}
|
||||
|
||||
function applyElemsCallback(elems: IterableElements, fn?: ElementsCallback) {
|
||||
function applyElemsCallback<T extends Element>(elems: ArrayLikeIterable<T>, fn?: ElementsCallback): ArrayLikeIterable<T> {
|
||||
if (fn) {
|
||||
for (const el of elems) {
|
||||
fn(el);
|
||||
@ -67,19 +67,22 @@ function applyElemsCallback(elems: IterableElements, fn?: ElementsCallback) {
|
||||
return elems;
|
||||
}
|
||||
|
||||
export function queryElemSiblings(el: Element, selector = '*', fn?: ElementsCallback) {
|
||||
return applyElemsCallback(Array.from(el.parentNode.children).filter((child: Element) => {
|
||||
export function queryElemSiblings<T extends Element>(el: Element, selector = '*', fn?: ElementsCallback): ArrayLikeIterable<T> {
|
||||
const elems = Array.from(el.parentNode.children) as T[];
|
||||
return applyElemsCallback<T>(elems.filter((child: Element) => {
|
||||
return child !== el && child.matches(selector);
|
||||
}), fn);
|
||||
}
|
||||
|
||||
// it works like jQuery.children: only the direct children are selected
|
||||
export function queryElemChildren(parent: Element | ParentNode, selector = '*', fn?: ElementsCallback) {
|
||||
return applyElemsCallback(parent.querySelectorAll(`:scope > ${selector}`), fn);
|
||||
export function queryElemChildren<T extends Element>(parent: Element | ParentNode, selector = '*', fn?: ElementsCallback): ArrayLikeIterable<T> {
|
||||
return applyElemsCallback<T>(parent.querySelectorAll(`:scope > ${selector}`), fn);
|
||||
}
|
||||
|
||||
export function queryElems(selector: string, fn?: ElementsCallback) {
|
||||
return applyElemsCallback(document.querySelectorAll(selector), fn);
|
||||
// it works like parent.querySelectorAll: all descendants are selected
|
||||
// in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent
|
||||
export function queryElems<T extends Element>(parent: Element | ParentNode, selector: string, fn?: ElementsCallback): ArrayLikeIterable<T> {
|
||||
return applyElemsCallback<T>(parent.querySelectorAll(selector), fn);
|
||||
}
|
||||
|
||||
export function onDomReady(cb: () => Promisable<void>) {
|
||||
@ -92,7 +95,7 @@ export function onDomReady(cb: () => Promisable<void>) {
|
||||
|
||||
// checks whether an element is owned by the current document, and whether it is a document fragment or element node
|
||||
// if it is, it means it is a "normal" element managed by us, which can be modified safely.
|
||||
export function isDocumentFragmentOrElementNode(el: Element | Node) {
|
||||
export function isDocumentFragmentOrElementNode(el: Node) {
|
||||
try {
|
||||
return el.ownerDocument === document && el.nodeType === Node.ELEMENT_NODE || el.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
|
||||
} catch {
|
||||
|
Loading…
Reference in New Issue
Block a user