forgejo/templates/repo/issue/list.tmpl

339 lines
21 KiB
Handlebars
Raw Normal View History

2015-07-23 22:50:05 +02:00
{{template "base/head" .}}
<div role="main" aria-label="{{.Title}}" class="page-content repository">
2015-07-23 22:50:05 +02:00
{{template "repo/header" .}}
2015-08-09 16:45:38 +02:00
<div class="ui container">
Improve UI on mobile (#19546) Start making the mobile experience not painful and be actually usable. This contains a few smaller changes to enhance this experience. - Submit buttons on the review forms aren't columns anymore and are now allowed to be displayed on one row. - The label/milestone & New Issue buttons were given each own row even tough, there's enough place to do it one the same row. This commit fixes that. - The issues+Pull tab on repo's has a third item besides the label/milestone & New Issue buttons, the search bar. On desktop there's enough place to do this on one row, for mobile it isn't, currently it was using for each item a new row. This commits fixes that by only giving the searchbar a new row and have the other two buttons on the same row. - The notification table will now be show a scrollbar instead of overflow. - The repo buttons(Watch, Star, Fork) on mobile were showing quite big and the SVG wasn't even displayed on the same line, if the count of those numbers were too high it would even overflow. This commit removes the SVG, as there isn't any place to show them on the same row and allows them to have a new row if the counts of those buttons are high. - The admin page can show you a lot of interesting information, on mobile the System Status + Configuration weren't properly displayed as the margin's were too high. This commit fixes that by reducing the margin to a number that makes sense on mobile. - Fixes to not overflow the tables but instead force them to be scrollable. - When viewing a issue or pull request, the comments aren't full-width but instead 80% and aligned to right, on mobile this is a annoyance as there isn't much width to begin with. This commits fixes that by forcing full-width and removing the avatars on the left side and instead including them inline in the comment header.
2022-05-01 18:11:21 +02:00
<div class="ui three column grid issue-list-headers">
<div class="column">
{{template "repo/issue/navbar" .}}
</div>
<div class="column center aligned">
{{template "repo/issue/search" .}}
</div>
2019-01-23 19:58:38 +01:00
{{if not .Repository.IsArchived}}
<div class="column right aligned">
{{if .PageIsIssueList}}
<a class="ui green button" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{.locale.Tr "repo.issues.new"}}</a>
2019-01-23 19:58:38 +01:00
{{else}}
<a class="ui green button {{if not .PullRequestCtx.Allowed}}disabled{{end}}" href="{{if .PullRequestCtx.Allowed}}{{.Repository.Link}}/compare/{{.Repository.DefaultBranch | PathEscapeSegments}}...{{if ne .Repository.Owner.Name .PullRequestCtx.BaseRepo.Owner.Name}}{{PathEscape .Repository.Owner.Name}}:{{end}}{{.Repository.DefaultBranch | PathEscapeSegments}}{{end}}">{{.locale.Tr "repo.pulls.new"}}</a>
2019-01-23 19:58:38 +01:00
{{end}}
</div>
{{else}}
{{if not .PageIsIssueList}}
<div class="column right aligned">
<a class="ui green button {{if not .PullRequestCtx.Allowed}}disabled{{end}}" href="{{if .PullRequestCtx.Allowed}}{{.PullRequestCtx.BaseRepo.Link}}/compare/{{.PullRequestCtx.BaseRepo.DefaultBranch | PathEscapeSegments}}...{{if ne .Repository.Owner.Name .PullRequestCtx.BaseRepo.Owner.Name}}{{PathEscape .Repository.Owner.Name}}:{{end}}{{.Repository.DefaultBranch | PathEscapeSegments}}{{end}}">{{$.locale.Tr "action.compare_commits_general"}}</a>
</div>
{{end}}
2019-01-23 19:58:38 +01:00
{{end}}
2015-07-23 22:50:05 +02:00
</div>
<div class="ui divider"></div>
<div id="issue-filters" class="ui stackable grid">
<div class="six wide column">
{{if $.CanWriteIssuesOrPulls}}
<div class="ui checkbox issue-checkbox-all gt-vm">
<input type="checkbox" title="{{.locale.Tr "repo.issues.action_check_all"}}">
</div>
{{end}}
{{template "repo/issue/openclose" .}}
2015-07-23 22:50:05 +02:00
</div>
<div class="ten wide right aligned column">
<div class="ui secondary filter stackable menu labels">
<!-- Label -->
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item label-filter" style="margin-left: auto">
<span class="text">
{{.locale.Tr "repo.issues.filter_label"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<div class="ui icon search input">
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_label"}}">
</div>
<span class="info">{{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_label_no_select"}}</a>
Scoped labels (#22585) Add a new "exclusive" option per label. This makes it so that when the label is named `scope/name`, no other label with the same `scope/` prefix can be set on an issue. The scope is determined by the last occurence of `/`, so for example `scope/alpha/name` and `scope/beta/name` are considered to be in different scopes and can coexist. Exclusive scopes are not enforced by any database rules, however they are enforced when editing labels at the models level, automatically removing any existing labels in the same scope when either attaching a new label or replacing all labels. In menus use a circle instead of checkbox to indicate they function as radio buttons per scope. Issue filtering by label ensures that only a single scoped label is selected at a time. Clicking with alt key can be used to remove a scoped label, both when editing individual issues and batch editing. Label rendering refactor for consistency and code simplification: * Labels now consistently have the same shape, emojis and tooltips everywhere. This includes the label list and label assignment menus. * In label list, show description below label same as label menus. * Don't use exactly black/white text colors to look a bit nicer. * Simplify text color computation. There is no point computing luminance in linear color space, as this is a perceptual problem and sRGB is closer to perceptually linear. * Increase height of label assignment menus to show more labels. Showing only 3-4 labels at a time leads to a lot of scrolling. * Render all labels with a new RenderLabel template helper function. Label creation and editing in multiline modal menu: * Change label creation to open a modal menu like label editing. * Change menu layout to place name, description and colors on separate lines. * Don't color cancel button red in label editing modal menu. * Align text to the left in model menu for better readability and consistent with settings layout elsewhere. Custom exclusive scoped label rendering: * Display scoped label prefix and suffix with slightly darker and lighter background color respectively, and a slanted edge between them similar to the `/` symbol. * In menus exclusive labels are grouped with a divider line. --------- Co-authored-by: Yarden Shoham <hrsi88@gmail.com> Co-authored-by: Lauris BH <lauris@nix.lv>
2023-02-18 20:17:39 +01:00
{{$previousExclusiveScope := "_no_scope"}}
{{range .Labels}}
Scoped labels (#22585) Add a new "exclusive" option per label. This makes it so that when the label is named `scope/name`, no other label with the same `scope/` prefix can be set on an issue. The scope is determined by the last occurence of `/`, so for example `scope/alpha/name` and `scope/beta/name` are considered to be in different scopes and can coexist. Exclusive scopes are not enforced by any database rules, however they are enforced when editing labels at the models level, automatically removing any existing labels in the same scope when either attaching a new label or replacing all labels. In menus use a circle instead of checkbox to indicate they function as radio buttons per scope. Issue filtering by label ensures that only a single scoped label is selected at a time. Clicking with alt key can be used to remove a scoped label, both when editing individual issues and batch editing. Label rendering refactor for consistency and code simplification: * Labels now consistently have the same shape, emojis and tooltips everywhere. This includes the label list and label assignment menus. * In label list, show description below label same as label menus. * Don't use exactly black/white text colors to look a bit nicer. * Simplify text color computation. There is no point computing luminance in linear color space, as this is a perceptual problem and sRGB is closer to perceptually linear. * Increase height of label assignment menus to show more labels. Showing only 3-4 labels at a time leads to a lot of scrolling. * Render all labels with a new RenderLabel template helper function. Label creation and editing in multiline modal menu: * Change label creation to open a modal menu like label editing. * Change menu layout to place name, description and colors on separate lines. * Don't color cancel button red in label editing modal menu. * Align text to the left in model menu for better readability and consistent with settings layout elsewhere. Custom exclusive scoped label rendering: * Display scoped label prefix and suffix with slightly darker and lighter background color respectively, and a slanted edge between them similar to the `/` symbol. * In menus exclusive labels are grouped with a divider line. --------- Co-authored-by: Yarden Shoham <hrsi88@gmail.com> Co-authored-by: Lauris BH <lauris@nix.lv>
2023-02-18 20:17:39 +01:00
{{$exclusiveScope := .ExclusiveScope}}
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
<div class="ui divider"></div>
{{end}}
{{$previousExclusiveScope = $exclusiveScope}}
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context .}}</a>
{{end}}
</div>
2017-03-15 02:10:35 +01:00
</div>
2015-08-15 05:24:41 +02:00
<!-- Milestone -->
<div class="ui {{if not (or .OpenMilestones .ClosedMilestones)}}disabled{{end}} dropdown jump item">
<span class="text">
{{.locale.Tr "repo.issues.filter_milestone"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<div class="ui icon search input">
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_milestone"}}">
</div>
<div class="divider"></div>
<a class="{{if not $.MilestoneID}}active selected {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=0&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_milestone_all"}}</a>
<a class="{{if $.MilestoneID}}{{if eq $.MilestoneID -1}}active selected {{end}}{{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone=-1&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_milestone_none"}}</a>
{{if .OpenMilestones}}
<div class="divider"></div>
<div class="header">{{.locale.Tr "repo.issues.filter_milestone_open"}}</div>
{{range .OpenMilestones}}
<a class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">
{{svg "octicon-milestone" 16 "mr-2"}}
{{.Name}}
</a>
{{end}}
{{end}}
{{if .ClosedMilestones}}
<div class="divider"></div>
<div class="header">{{.locale.Tr "repo.issues.filter_milestone_closed"}}</div>
{{range .ClosedMilestones}}
<a class="{{if $.MilestoneID}}{{if eq $.MilestoneID .ID}}active selected {{end}}{{end}}item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.ID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">
{{svg "octicon-milestone" 16 "mr-2"}}
{{.Name}}
</a>
{{end}}
{{end}}
</div>
</div>
<!-- Project -->
<div class="ui{{if not (or .OpenProjects .ClosedProjects)}} disabled{{end}} dropdown jump item">
<span class="text">
{{.locale.Tr "repo.issues.filter_project"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<div class="ui icon search input">
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_project"}}">
</div>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_all"}}</a>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&project=-1&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_project_none"}}</a>
{{if .OpenProjects}}
<div class="divider"></div>
<div class="header">
{{.locale.Tr "repo.issues.new.open_projects"}}
</div>
{{range .OpenProjects}}
<a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">
{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "gt-mr-3"}}{{else}}{{svg "octicon-project" 18 "gt-mr-3"}}{{end}}
{{.Title}}
</a>
{{end}}
{{end}}
{{if .ClosedProjects}}
<div class="divider"></div>
<div class="header">
{{.locale.Tr "repo.issues.new.closed_projects"}}
</div>
{{range .ClosedProjects}}
<a class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">
{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "gt-mr-3"}}{{else}}{{svg "octicon-project" 18 "gt-mr-3"}}{{end}}
{{.Title}}
</a>
{{end}}
{{end}}
</div>
</div>
<!-- Author -->
<div class="ui {{if not .Posters}}disabled{{end}} dropdown jump item">
<span class="text">
{{.locale.Tr "repo.issues.filter_poster"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<div class="ui icon search input">
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_poster"}}">
</div>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}">{{.locale.Tr "repo.issues.filter_poster_no_select"}}</a>
{{range .Posters}}
<a class="{{if eq $.PosterID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{.ID}}">
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
2023-02-15 14:37:34 +01:00
{{avatar $.Context .}} {{.GetDisplayName}}
</a>
{{end}}
</div>
2017-03-15 02:10:35 +01:00
</div>
2015-08-15 06:07:08 +02:00
<!-- Assignee -->
<div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item">
<span class="text">
{{.locale.Tr "repo.issues.filter_assignee"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<div class="ui icon search input">
<i class="icon gt-df gt-ac gt-jc">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{.locale.Tr "repo.issues.filter_assignee"}}">
</div>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_assginee_no_select"}}</a>
{{range .Assignees}}
<a class="{{if eq $.AssigneeID .ID}}active selected{{end}} item" href="{{$.Link}}?type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{.ID}}&poster={{$.PosterID}}">
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
2023-02-15 14:37:34 +01:00
{{avatar $.Context .}} {{.GetDisplayName}}
</a>
{{end}}
</div>
2017-03-15 02:10:35 +01:00
</div>
2015-08-15 06:07:08 +02:00
{{if .IsSigned}}
<!-- Type -->
<div class="ui dropdown type jump item">
<span class="text">
{{.locale.Tr "repo.issues.filter_type"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<a class="{{if eq .ViewType "all"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=all&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.all_issues"}}</a>
<a class="{{if eq .ViewType "assigned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=assigned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.assigned_to_you"}}</a>
<a class="{{if eq .ViewType "created_by"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=created_by&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.created_by_you"}}</a>
<a class="{{if eq .ViewType "mentioned"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=mentioned&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.mentioning_you"}}</a>
{{if .PageIsPullList}}
<a class="{{if eq .ViewType "review_requested"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type=review_requested&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_type.review_requested"}}</a>
{{end}}
</div>
</div>
{{end}}
<!-- Sort -->
<div class="ui dropdown type jump item">
<span class="text">
{{.locale.Tr "repo.issues.filter_sort"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<a class="{{if or (eq .SortType "latest") (not .SortType)}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=latest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "oldest"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=oldest&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class="{{if eq .SortType "recentupdate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=recentupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
<a class="{{if eq .SortType "leastupdate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastupdate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
<a class="{{if eq .SortType "mostcomment"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=mostcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.mostcomment"}}</a>
<a class="{{if eq .SortType "leastcomment"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=leastcomment&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.leastcomment"}}</a>
<a class="{{if eq .SortType "nearduedate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=nearduedate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.nearduedate"}}</a>
<a class="{{if eq .SortType "farduedate"}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort=farduedate&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}&project={{$.ProjectID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}">{{.locale.Tr "repo.issues.filter_sort.farduedate"}}</a>
</div>
2017-03-15 02:10:35 +01:00
</div>
</div>
</div>
</div>
<div id="issue-actions" class="ui stackable grid gt-hidden">
<div class="six wide column">
{{template "repo/issue/openclose" .}}
2015-08-15 06:07:08 +02:00
</div>
{{/* Ten wide does not cope well and makes the columns stack.
This seems to be related to jQuery's hide/show: in fact, switching
issue-actions and issue-filters and having this ten wide will show
this one correctly, but not the other one. */}}
<div class="nine wide right aligned right floated column">
<div class="ui secondary filter stackable menu">
{{if not .Repository.IsArchived}}
<!-- Action Button -->
{{if .IsShowClosed}}
<div class="ui green active basic button issue-action" data-action="open" data-url="{{$.RepoLink}}/issues/status" style="margin-left: auto">{{.locale.Tr "repo.issues.action_open"}}</div>
{{else}}
<div class="ui red active basic button issue-action" data-action="close" data-url="{{$.RepoLink}}/issues/status" style="margin-left: auto">{{.locale.Tr "repo.issues.action_close"}}</div>
{{end}}
<!-- Labels -->
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item">
<span class="text">
{{.locale.Tr "repo.issues.action_label"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
Scoped labels (#22585) Add a new "exclusive" option per label. This makes it so that when the label is named `scope/name`, no other label with the same `scope/` prefix can be set on an issue. The scope is determined by the last occurence of `/`, so for example `scope/alpha/name` and `scope/beta/name` are considered to be in different scopes and can coexist. Exclusive scopes are not enforced by any database rules, however they are enforced when editing labels at the models level, automatically removing any existing labels in the same scope when either attaching a new label or replacing all labels. In menus use a circle instead of checkbox to indicate they function as radio buttons per scope. Issue filtering by label ensures that only a single scoped label is selected at a time. Clicking with alt key can be used to remove a scoped label, both when editing individual issues and batch editing. Label rendering refactor for consistency and code simplification: * Labels now consistently have the same shape, emojis and tooltips everywhere. This includes the label list and label assignment menus. * In label list, show description below label same as label menus. * Don't use exactly black/white text colors to look a bit nicer. * Simplify text color computation. There is no point computing luminance in linear color space, as this is a perceptual problem and sRGB is closer to perceptually linear. * Increase height of label assignment menus to show more labels. Showing only 3-4 labels at a time leads to a lot of scrolling. * Render all labels with a new RenderLabel template helper function. Label creation and editing in multiline modal menu: * Change label creation to open a modal menu like label editing. * Change menu layout to place name, description and colors on separate lines. * Don't color cancel button red in label editing modal menu. * Align text to the left in model menu for better readability and consistent with settings layout elsewhere. Custom exclusive scoped label rendering: * Display scoped label prefix and suffix with slightly darker and lighter background color respectively, and a slanted edge between them similar to the `/` symbol. * In menus exclusive labels are grouped with a divider line. --------- Co-authored-by: Yarden Shoham <hrsi88@gmail.com> Co-authored-by: Lauris BH <lauris@nix.lv>
2023-02-18 20:17:39 +01:00
{{$previousExclusiveScope := "_no_scope"}}
{{range .Labels}}
Scoped labels (#22585) Add a new "exclusive" option per label. This makes it so that when the label is named `scope/name`, no other label with the same `scope/` prefix can be set on an issue. The scope is determined by the last occurence of `/`, so for example `scope/alpha/name` and `scope/beta/name` are considered to be in different scopes and can coexist. Exclusive scopes are not enforced by any database rules, however they are enforced when editing labels at the models level, automatically removing any existing labels in the same scope when either attaching a new label or replacing all labels. In menus use a circle instead of checkbox to indicate they function as radio buttons per scope. Issue filtering by label ensures that only a single scoped label is selected at a time. Clicking with alt key can be used to remove a scoped label, both when editing individual issues and batch editing. Label rendering refactor for consistency and code simplification: * Labels now consistently have the same shape, emojis and tooltips everywhere. This includes the label list and label assignment menus. * In label list, show description below label same as label menus. * Don't use exactly black/white text colors to look a bit nicer. * Simplify text color computation. There is no point computing luminance in linear color space, as this is a perceptual problem and sRGB is closer to perceptually linear. * Increase height of label assignment menus to show more labels. Showing only 3-4 labels at a time leads to a lot of scrolling. * Render all labels with a new RenderLabel template helper function. Label creation and editing in multiline modal menu: * Change label creation to open a modal menu like label editing. * Change menu layout to place name, description and colors on separate lines. * Don't color cancel button red in label editing modal menu. * Align text to the left in model menu for better readability and consistent with settings layout elsewhere. Custom exclusive scoped label rendering: * Display scoped label prefix and suffix with slightly darker and lighter background color respectively, and a slanted edge between them similar to the `/` symbol. * In menus exclusive labels are grouped with a divider line. --------- Co-authored-by: Yarden Shoham <hrsi88@gmail.com> Co-authored-by: Lauris BH <lauris@nix.lv>
2023-02-18 20:17:39 +01:00
{{$exclusiveScope := .ExclusiveScope}}
{{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}}
<div class="ui divider"></div>
{{end}}
{{$previousExclusiveScope = $exclusiveScope}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
{{if contain $.SelLabelIDs .ID}}{{if $exclusiveScope}}{{svg "octicon-dot-fill"}}{{else}}{{svg "octicon-check"}}{{end}}{{end}} {{RenderLabel $.Context .}}
</div>
{{end}}
</div>
2017-03-15 02:10:35 +01:00
</div>
<!-- Milestone -->
<div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item">
<span class="text">
{{.locale.Tr "repo.issues.action_milestone"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/milestone">
{{.locale.Tr "repo.issues.action_milestone_no_select"}}
2017-03-15 02:10:35 +01:00
</div>
{{range .Milestones}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/milestone">
{{.Name}}
</div>
{{end}}
</div>
2017-03-15 02:10:35 +01:00
</div>
<!-- Projects -->
<div class="ui{{if not (or .OpenProjects .ClosedProjects)}} disabled{{end}} dropdown jump item">
<span class="text">
{{.locale.Tr "repo.project_board"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/projects">
{{.locale.Tr "repo.issues.new.clear_projects"}}
</div>
{{if .OpenProjects}}
<div class="divider"></div>
<div class="header">
{{.locale.Tr "repo.issues.new.open_projects"}}
</div>
{{range .OpenProjects}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "gt-mr-3"}}{{else}}{{svg "octicon-project" 18 "gt-mr-3"}}{{end}}
{{.Title}}
</div>
{{end}}
{{end}}
{{if .ClosedProjects}}
<div class="divider"></div>
<div class="header">
{{.locale.Tr "repo.issues.new.closed_projects"}}
</div>
{{range .ClosedProjects}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
{{if .IsOrganizationProject}}{{svg "octicon-project-symlink" 18 "gt-mr-3"}}{{else}}{{svg "octicon-project" 18 "gt-mr-3"}}{{end}}
{{.Title}}
</div>
{{end}}
{{end}}
</div>
</div>
2018-05-09 18:29:04 +02:00
<!-- Assignees -->
<div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item">
<span class="text">
{{.locale.Tr "repo.issues.action_assignee"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<div class="item issue-action" data-element-id="0" data-url="{{$.Link}}/assignee">
{{.locale.Tr "repo.issues.action_assignee_no_select"}}
2017-03-15 02:10:35 +01:00
</div>
{{range .Assignees}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/assignee">
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
2023-02-15 14:37:34 +01:00
{{avatar $.Context .}} {{.GetDisplayName}}
</div>
{{end}}
</div>
2017-03-15 02:10:35 +01:00
</div>
{{end}}
2015-07-23 22:50:05 +02:00
</div>
</div>
</div>
{{template "shared/issuelist" mergeinto . "listType" "repo"}}
2015-07-23 22:50:05 +02:00
</div>
2014-03-25 16:00:26 +01:00
</div>
{{template "base/footer" .}}