Кодревью изменений до 29 июня 2022 #43

Closed
Bezborodov wants to merge 54 commits from dev_mirocod into release/v1.16
  1. 130
      README.md
  2. 7
      custom/conf/app.example.ini
  3. 2
      integrations/api_repo_edit_test.go
  4. 85
      models/error.go
  5. 52
      models/issue.go
  6. 52
      models/issue_comment.go
  7. 67
      models/issue_comment_list.go
  8. 134
      models/issue_parent.go
  9. 61
      models/issue_parent_test.go
  10. 3
      models/migrations/v70.go
  11. 4
      models/org_team.go
  12. 5
      models/repo.go
  13. 16
      models/repo/issue.go
  14. 1
      models/repo/repo_unit.go
  15. 6
      models/user/user.go
  16. 5
      modules/context/repo.go
  17. 1
      modules/convert/convert.go
  18. 10
      modules/convert/issue_comment.go
  19. 1
      modules/convert/repository.go
  20. 2
      modules/convert/user.go
  21. 4
      modules/doctor/fix16961.go
  22. 4
      modules/doctor/fix16961_test.go
  23. 4
      modules/setting/service.go
  24. 1
      modules/structs/admin_user.go
  25. 2
      modules/structs/issue_comment.go
  26. 7
      modules/structs/org.go
  27. 4
      modules/structs/org_team.go
  28. 10
      modules/structs/repo.go
  29. 6
      modules/structs/user.go
  30. 571
      options/locale/locale_ru-RU.ini
  31. 39
      package-lock.json
  32. BIN
      public/img/logo.png
  33. 108
      public/img/logo.svg
  34. 102
      public/img/logo1.svg
  35. 51
      public/img/logo2.svg
  36. 1
      routers/api/v1/admin/org.go
  37. 3
      routers/api/v1/admin/user.go
  38. 2
      routers/api/v1/org/org.go
  39. 2
      routers/api/v1/repo/repo.go
  40. 3
      routers/api/v1/user/settings.go
  41. 2
      routers/web/admin/users.go
  42. 127
      routers/web/map/umap.go
  43. 1
      routers/web/org/setting.go
  44. 78
      routers/web/repo/issue.go
  45. 129
      routers/web/repo/issue_parent.go
  46. 2
      routers/web/repo/milestone.go
  47. 1
      routers/web/repo/setting.go
  48. 10
      routers/web/repo/view.go
  49. 1
      routers/web/user/setting/profile.go
  50. 18
      routers/web/web.go
  51. 2
      services/forms/admin.go
  52. 3
      services/forms/org.go
  53. 3
      services/forms/repo_form.go
  54. 5
      services/forms/user_form.go
  55. 2
      templates/admin/config.tmpl
  56. 8
      templates/admin/user/edit.tmpl
  57. 1
      templates/base/head_navbar.tmpl
  58. 2
      templates/explore/organizations.tmpl
  59. 2
      templates/explore/users.tmpl
  60. 18
      templates/map/navbar.tmpl
  61. 2862
      templates/map/umap.tmpl
  62. 2
      templates/org/home.tmpl
  63. 5
      templates/org/settings/options.tmpl
  64. 39
      templates/repo/header.tmpl
  65. 89
      templates/repo/issue/issue_actions.tmpl
  66. 90
      templates/repo/issue/issue_filters.tmpl
  67. 204
      templates/repo/issue/list.tmpl
  68. 23
      templates/repo/issue/nav_search_and_new.tmpl
  69. 1
      templates/repo/issue/navbar.tmpl
  70. 20
      templates/repo/issue/treant_config.tmpl
  71. 84
      templates/repo/issue/tree.tmpl
  72. 50
      templates/repo/issue/view_content/comments.tmpl
  73. 125
      templates/repo/issue/view_content/sidebar.tmpl
  74. 6
      templates/repo/settings/options.tmpl
  75. 2
      templates/repo/user_cards.tmpl
  76. 144
      templates/shared/issuelistfortree.tmpl
  77. 5
      templates/swagger/v1_json.tmpl
  78. 2
      templates/user/profile.tmpl
  79. 4
      templates/user/settings/profile.tmpl
  80. 46
      web_src/js/features/repo-issue.js
  81. 3
      web_src/js/features/repo-legacy.js
  82. 15
      web_src/less/_repository.less

130
README.md

@ -1,11 +1,13 @@
<p align="center">
<a href="https://gitea.io/">
<img alt="Gitea" src="https://raw.githubusercontent.com/go-gitea/gitea/main/public/img/gitea.svg" width="220"/>
<a href="http://git.mirocod.ru/MIROCOD/Platform_Mirocod/">
<img alt="Мирокод" src="http://git.mirocod.ru/MIROCOD/Platform_Mirocod/raw/branch/dev_mirocod/public/img/logo.svg" width="417"/>
</a>
</p>
<h1 align="center">Gitea - Git with a cup of tea</h1>
<h1 align="center">Платформа ЦРНП "Мирокод" для разработки проектов</h1>
<!--
<p align="center">
<a href="https://drone.gitea.io/go-gitea/gitea" title="Build Status">
<img src="https://drone.gitea.io/api/badges/go-gitea/gitea/status.svg?ref=refs/heads/main">
</a>
@ -47,114 +49,28 @@
<p align="center">
<a href="README_ZH.md">View the chinese version of this document</a>
</p>
## Purpose
The goal of this project is to make the easiest, fastest, and most
painless way of setting up a self-hosted Git service.
Using Go, this can be done with an independent binary distribution across
**all platforms** which Go supports, including Linux, macOS, and Windows
on x86, amd64, ARM and PowerPC architectures.
Want to try it before doing anything else?
Do it [with the online demo](https://try.gitea.io/)!
This project has been
-->
## Общее описание
Платформа "Мирокод" - необходимый инструмент для разработки народных проектов.
Разрабатывается на основе gitea, в перспективе планируется реализовать:
1. Карта пользователей (их ресурсы, компетенции и интересы), проектов, сообществ.
2. Дерево задач в каждом проекте
3. Дерево проектов
4. Проекты могут быть не только для разработки, но и любые общественные проекты
5. У пользователя можно будет указать ресурсы, компетенции, интересы
6. Объединяются в одном месте, люди, проекты, ресурсы и компетенции, а также инструменты ведения проектов и решения задач.
7. В перспективе самый простой модуль игроподобия, для того чтобы легко подбирать новые задачи из любых проектов
8. Метрика по проектам
Проект создан на основе:
[forked](https://blog.gitea.io/2016/12/welcome-to-gitea/) from
[Gogs](https://gogs.io) since 2016.11 but changed a lot.
## Building
From the root of the source tree, run:
TAGS="bindata" make build
or if SQLite support is required:
TAGS="bindata sqlite sqlite_unlock_notify" make build
The `build` target is split into two sub-targets:
- `make backend` which requires [Go 1.17](https://go.dev/dl/) or greater.
- `make frontend` which requires [Node.js LTS](https://nodejs.org/en/download/) or greater and Internet connectivity to download npm dependencies.
When building from the official source tarballs which include pre-built frontend files, the `frontend` target will not be triggered, making it possible to build without Node.js and Internet connectivity.
Parallelism (`make -j <num>`) is not supported.
More info: https://docs.gitea.io/en-us/install-from-source/
## Using
./gitea web
NOTE: If you're interested in using our APIs, we have experimental
support with [documentation](https://try.gitea.io/api/swagger).
## Contributing
Expected workflow is: Fork -> Patch -> Push -> Pull Request
NOTES:
1. **YOU MUST READ THE [CONTRIBUTORS GUIDE](CONTRIBUTING.md) BEFORE STARTING TO WORK ON A PULL REQUEST.**
2. If you have found a vulnerability in the project, please write privately to **security@gitea.io**. Thanks!
## Translating
Translations are done through Crowdin. If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope fo fill it as questions pop up.
https://docs.gitea.io/en-us/translation-guidelines/
[![Crowdin](https://badges.crowdin.net/gitea/localized.svg)](https://crowdin.com/project/gitea)
## Further information
For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.io/en-us/).
If you have questions that are not covered by the documentation, you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://discourse.gitea.io/).
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea).
The hugo-based documentation theme is hosted at [gitea/theme](https://gitea.com/gitea/theme).
The official Gitea CLI is developed at [gitea/tea](https://gitea.com/gitea/tea).
## Authors
* [Maintainers](https://github.com/orgs/go-gitea/people)
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
* [Translators](options/locale/TRANSLATORS)
## Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/gitea#backer)]
<a href="https://opencollective.com/gitea#backers" target="_blank"><img src="https://opencollective.com/gitea/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/gitea#sponsor)]
<a href="https://opencollective.com/gitea/sponsor/0/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/1/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/2/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/3/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/4/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/5/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/6/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/7/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/8/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/gitea/sponsor/9/website" target="_blank"><img src="https://opencollective.com/gitea/sponsor/9/avatar.svg"></a>
## FAQ
**How do you pronounce Gitea?**
Gitea is pronounced [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea" with a hard g.
**Why is this not hosted on a Gitea instance?**
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
Подробное описание сборки и пр. - http://git.mirocod.ru/MIROCOD/Platform_Mirocod/wiki
## License
## Лицензия
This project is licensed under the MIT License.
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file

7
custom/conf/app.example.ini

@ -701,6 +701,13 @@ PATH =
;; Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting.
;ALLOW_CROSS_REPOSITORY_DEPENDENCIES = true
;;
;; Default value for EnableParents
;; Repositories will use parents by default depending on this setting
;DEFAULT_ENABLE_PARENTS = true
;;
;; Parents can be added from any repository where the user is granted access or only from the current repository depending on this setting.
;ALLOW_CROSS_REPOSITORY_PARENTS = true
;;
;; Enable heatmap on users profiles.
;ENABLE_USER_HEATMAP = true
;;

2
integrations/api_repo_edit_test.go

@ -35,6 +35,7 @@ func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption
EnableTimeTracker: config.EnableTimetracker,
AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime,
EnableIssueDependencies: config.EnableDependencies,
EnableIssueParents: config.EnableParents,
}
} else if unit, err := repo.GetUnit(unit_model.TypeExternalTracker); err == nil {
config := unit.ExternalTrackerConfig()
@ -182,6 +183,7 @@ func TestAPIRepoEdit(t *testing.T) {
EnableTimeTracker: false,
AllowOnlyContributorsToTrackTime: false,
EnableIssueDependencies: false,
EnableIssueParents: false,
}
*repoEditOption.HasWiki = true
repoEditOption.ExternalWiki = nil

85
models/error.go

@ -1340,6 +1340,91 @@ func (err ErrUnknownDependencyType) Error() string {
return fmt.Sprintf("unknown dependency type [type: %d]", err.Type)
}
// .___ ________ .___ .__
// | | ______ ________ __ ____ \______ \ ____ ______ ____ ____ __| _/____ ____ ____ |__| ____ ______
// | |/ ___// ___/ | \_/ __ \ | | \_/ __ \\____ \_/ __ \ / \ / __ |/ __ \ / \_/ ___\| |/ __ \ / ___/
// | |\___ \ \___ \| | /\ ___/ | ` \ ___/| |_> > ___/| | \/ /_/ \ ___/| | \ \___| \ ___/ \___ \
// |___/____ >____ >____/ \___ >_______ /\___ > __/ \___ >___| /\____ |\___ >___| /\___ >__|\___ >____ >
// \/ \/ \/ \/ \/|__| \/ \/ \/ \/ \/ \/ \/ \/
// ErrParentExists represents a "ParentAlreadyExists" kind of error.
type ErrParentExists struct {
IssueID int64
ParentID int64
}
// IsErrParentExists checks if an error is a ErrParentExists.
func IsErrParentExists(err error) bool {
_, ok := err.(ErrParentExists)
return ok
}
func (err ErrParentExists) Error() string {
return fmt.Sprintf("issue parent does already exist [issue id: %d, parent id: %d]", err.IssueID, err.ParentID)
}
// ErrParentNotExists represents a "ParentAlreadyExists" kind of error.
type ErrParentNotExists struct {
IssueID int64
ParentID int64
}
// IsErrParentNotExists checks if an error is a ErrParentExists.
func IsErrParentNotExists(err error) bool {
_, ok := err.(ErrParentNotExists)
return ok
}
func (err ErrParentNotExists) Error() string {
return fmt.Sprintf("issue parent does not exist [issue id: %d, parent id: %d]", err.IssueID, err.ParentID)
}
// ErrCircularParent represents a "ParentCircular" kind of error.
type ErrCircularParent struct {
IssueID int64
ParentID int64
}
// IsErrCircularParent checks if an error is a ErrCircularParent.
func IsErrCircularParent(err error) bool {
_, ok := err.(ErrCircularParent)
return ok
}
func (err ErrCircularParent) Error() string {
return fmt.Sprintf("circular parents exists (two issues blocking each other) [issue id: %d, parent id: %d]", err.IssueID, err.ParentID)
}
// ErrParentsLeft represents an error where the issue you're trying to close still has parents left.
type ErrParentsLeft struct {
IssueID int64
}
// IsErrParentsLeft checks if an error is a ErrParentsLeft.
func IsErrParentsLeft(err error) bool {
_, ok := err.(ErrParentsLeft)
return ok
}
func (err ErrParentsLeft) Error() string {
return fmt.Sprintf("issue has open parents [issue id: %d]", err.IssueID)
}
// ErrUnknownParentType represents an error where an unknown parent type was passed
type ErrUnknownParentType struct {
Type ParentType
}
// IsErrUnknownParentType checks if an error is ErrUnknownParentType
func IsErrUnknownParentType(err error) bool {
_, ok := err.(ErrUnknownParentType)
return ok
}
func (err ErrUnknownParentType) Error() string {
return fmt.Sprintf("unknown parent type [type: %d]", err.Type)
}
// __________ .__
// \______ \ _______ _|__| ______ _ __
// | _// __ \ \/ / |/ __ \ \/ \/ /

52
models/issue.go

@ -1990,6 +1990,12 @@ type DependencyInfo struct {
repo_model.Repository `xorm:"extends"`
}
// ParentInfo represents high level information about an issue which is a parent of another issue.
type ParentInfo struct {
Issue `xorm:"extends"`
repo_model.Repository `xorm:"extends"`
}
// getParticipantIDsByIssue returns all userIDs who are participated in comments of an issue and issue author
func (issue *Issue) getParticipantIDsByIssue(e db.Engine) ([]int64, error) {
if issue == nil {
@ -2048,6 +2054,42 @@ func (issue *Issue) getBlockingDependencies(e db.Engine) (issueDeps []*Dependenc
return issueDeps, err
}
// Get Blocked By Parents, aka all issues this issue is blocked by.
func (issue *Issue) getBlockedByParents(e db.Engine) (issueParents []*ParentInfo, err error) {
err = e.
Table("issue").
Join("INNER", "repository", "repository.id = issue.repo_id").
Join("INNER", "issue_parent", "issue_parent.parent_id = issue.id").
Where("issue_id = ?", issue.ID).
// sort by repo id then created date, with the issues of the same repo at the beginning of the list
OrderBy("CASE WHEN issue.repo_id = " + strconv.FormatInt(issue.RepoID, 10) + " THEN 0 ELSE issue.repo_id END, issue.created_unix DESC").
Find(&issueParents)
for _, parentInfo := range issueParents {
parentInfo.Issue.Repo = &parentInfo.Repository
}
return issueParents, err
}
// Get Blocking Parents, aka all issues this issue blocks.
func (issue *Issue) getBlockingParents(e db.Engine) (issueParents []*ParentInfo, err error) {
err = e.
Table("issue").
Join("INNER", "repository", "repository.id = issue.repo_id").
Join("INNER", "issue_parent", "issue_parent.issue_id = issue.id").
Where("parent_id = ?", issue.ID).
// sort by repo id then created date, with the issues of the same repo at the beginning of the list
OrderBy("CASE WHEN issue.repo_id = " + strconv.FormatInt(issue.RepoID, 10) + " THEN 0 ELSE issue.repo_id END, issue.created_unix DESC").
Find(&issueParents)
for _, parentInfo := range issueParents {
parentInfo.Issue.Repo = &parentInfo.Repository
}
return issueParents, err
}
// BlockedByDependencies finds all Dependencies an issue is blocked by
func (issue *Issue) BlockedByDependencies() ([]*DependencyInfo, error) {
return issue.getBlockedByDependencies(db.GetEngine(db.DefaultContext))
@ -2058,6 +2100,16 @@ func (issue *Issue) BlockingDependencies() ([]*DependencyInfo, error) {
return issue.getBlockingDependencies(db.GetEngine(db.DefaultContext))
}
// BlockedByParents finds all Parents an issue is blocked by
func (issue *Issue) BlockedByParents() ([]*ParentInfo, error) {
return issue.getBlockedByParents(db.GetEngine(db.DefaultContext))
}
// BlockingParents returns all blocking dependencies, aka all other issues a given issue blocks
func (issue *Issue) BlockingParents() ([]*ParentInfo, error) {
return issue.getBlockingParents(db.GetEngine(db.DefaultContext))
}
func (issue *Issue) updateClosedNum(ctx context.Context) (err error) {
if issue.IsPull {
err = repoStatsCorrectNumClosed(ctx, issue.RepoID, true, "num_closed_pulls")

52
models/issue_comment.go

@ -108,6 +108,10 @@ const (
CommentTypeDismissReview
// 33 Change issue ref
CommentTypeChangeIssueRef
// 34 Parent added
CommentTypeAddParent
// 35 Parent removed
CommentTypeRemoveParent
)
var commentStrings = []string{
@ -145,6 +149,8 @@ var commentStrings = []string{
"project_board",
"dismiss_review",
"change_issue_ref",
"add_parent",
"remove_parent",
}
func (t CommentType) String() string {
@ -224,6 +230,8 @@ type Comment struct {
NewRef string
DependentIssueID int64
DependentIssue *Issue `xorm:"-"`
ParentIssueID int64
ParentIssue *Issue `xorm:"-"`
CommitID int64
Line int64 // - previous line / + proposed line
@ -619,6 +627,15 @@ func (c *Comment) LoadDepIssueDetails() (err error) {
return err
}
// LoadParentIssueDetails loads Parent Issue Details
func (c *Comment) LoadParentIssueDetails() (err error) {
if c.ParentIssueID <= 0 || c.ParentIssue != nil {
return nil
}
c.ParentIssue, err = getIssueByID(db.GetEngine(db.DefaultContext), c.ParentIssueID)
return err
}
// LoadTime loads the associated time for a CommentTypeAddTimeManual
func (c *Comment) LoadTime() error {
if c.Time != nil || c.TimeID == 0 {
@ -789,6 +806,7 @@ func createComment(ctx context.Context, opts *CreateCommentOptions) (_ *Comment,
OldRef: opts.OldRef,
NewRef: opts.NewRef,
DependentIssueID: opts.DependentIssueID,
ParentIssueID: opts.ParentIssueID,
TreePath: opts.TreePath,
ReviewID: opts.ReviewID,
Patch: opts.Patch,
@ -933,6 +951,39 @@ func createIssueDependencyComment(ctx context.Context, doer *user_model.User, is
return
}
// Creates issue parent comment
func createIssueParentComment(ctx context.Context, doer *user_model.User, issue, parentIssue *Issue, add bool) (err error) {
cType := CommentTypeAddParent
if !add {
cType = CommentTypeRemoveParent
}
if err = issue.loadRepo(ctx); err != nil {
return
}
// Make two comments, one in each issue
opts := &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
ParentIssueID: parentIssue.ID,
}
if _, err = createComment(ctx, opts); err != nil {
return
}
opts = &CreateCommentOptions{
Type: cType,
Doer: doer,
Repo: issue.Repo,
Issue: parentIssue,
ParentIssueID: issue.ID,
}
_, err = createComment(ctx, opts)
return
}
// CreateCommentOptions defines options for creating comment
type CreateCommentOptions struct {
Type CommentType
@ -942,6 +993,7 @@ type CreateCommentOptions struct {
Label *Label
DependentIssueID int64
ParentIssueID int64
OldMilestoneID int64
MilestoneID int64
OldProjectID int64

67
models/issue_comment_list.go

@ -395,6 +395,69 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
return nil
}
func (comments CommentList) getParentIssueIDs() []int64 {
ids := make(map[int64]struct{}, len(comments))
for _, comment := range comments {
if comment.ParentIssue != nil {
continue
}
if _, ok := ids[comment.ParentIssueID]; !ok {
ids[comment.ParentIssueID] = struct{}{}
}
}
return keysInt64(ids)
}
func (comments CommentList) loadParentIssues(ctx context.Context) error {
if len(comments) == 0 {
return nil
}
e := db.GetEngine(ctx)
issueIDs := comments.getParentIssueIDs()
issues := make(map[int64]*Issue, len(issueIDs))
left := len(issueIDs)
for left > 0 {
limit := defaultMaxInSize
if left < limit {
limit = left
}
rows, err := e.
In("id", issueIDs[:limit]).
Rows(new(Issue))
if err != nil {
return err
}
for rows.Next() {
var issue Issue
err = rows.Scan(&issue)
if err != nil {
_ = rows.Close()
return err
}
issues[issue.ID] = &issue
}
_ = rows.Close()
left -= limit
issueIDs = issueIDs[limit:]
}
for _, comment := range comments {
if comment.ParentIssue == nil {
comment.ParentIssue = issues[comment.ParentIssueID]
if comment.ParentIssue != nil {
if err := comment.ParentIssue.loadRepo(ctx); err != nil {
return err
}
}
}
}
return nil
}
func (comments CommentList) loadAttachments(e db.Engine) (err error) {
if len(comments) == 0 {
return nil
@ -528,6 +591,10 @@ func (comments CommentList) loadAttributes(ctx context.Context) (err error) {
return
}
if err = comments.loadParentIssues(ctx); err != nil {
return
}
return nil
}

134
models/issue_parent.go

@ -0,0 +1,134 @@
// Copyright 2018-2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/timeutil"
)
// IssueParent represents an issue parent
type IssueParent struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"NOT NULL"`
IssueID int64 `xorm:"UNIQUE(issue_parent) NOT NULL"`
ParentID int64 `xorm:"UNIQUE(issue_parent) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}
func init() {
db.RegisterModel(new(IssueParent))
}
// ParentType Defines Parent Type Constants
type ParentType int
// Define Parent Types
const (
ParentTypeFather ParentType = iota
ParentTypeChild
)
// CreateIssueParent creates a new parent for an issue
func CreateIssueParent(user *user_model.User, issue, parent *Issue) error {
ctx, committer, err := db.TxContext()
if err != nil {
return err
}
defer committer.Close()
sess := db.GetEngine(ctx)
// Check if it aleready exists
exists, err := issueParentExists(sess, issue.ID, parent.ID)
if err != nil {
return err
}
if exists {
return ErrParentExists{issue.ID, parent.ID}
}
// And if it would be circular
circular, err := issueParentExists(sess, parent.ID, issue.ID)
if err != nil {
return err
}
if circular {
return ErrCircularParent{issue.ID, parent.ID}
}
if err := db.Insert(ctx, &IssueParent{
UserID: user.ID,
IssueID: issue.ID,
ParentID: parent.ID,
}); err != nil {
return err
}
// Add comment referencing the new parent
if err = createIssueParentComment(ctx, user, issue, parent, true); err != nil {
return err
}
return committer.Commit()
}
// RemoveIssueParent removes a parent from an issue
func RemoveIssueParent(user *user_model.User, issue, parent *Issue, parentType ParentType) (err error) {
ctx, committer, err := db.TxContext()
if err != nil {
return err
}
defer committer.Close()
var issueParentToDelete IssueParent
switch parentType {
case ParentTypeFather:
issueParentToDelete = IssueParent{IssueID: issue.ID, ParentID: parent.ID}
case ParentTypeChild:
issueParentToDelete = IssueParent{IssueID: parent.ID, ParentID: issue.ID}
default:
return ErrUnknownParentType{parentType}
}
affected, err := db.GetEngine(ctx).Delete(&issueParentToDelete)
if err != nil {
return err
}
// If we deleted nothing, the parent did not exist
if affected <= 0 {
return ErrParentNotExists{issue.ID, parent.ID}
}
// Add comment referencing the removed parent
if err = createIssueParentComment(ctx, user, issue, parent, false); err != nil {
return err
}
return committer.Commit()
}
// Check if the parent already exists
func issueParentExists(e db.Engine, issueID, depID int64) (bool, error) {
return e.Where("(issue_id = ? AND parent_id = ?)", issueID, depID).Exist(&IssueParent{})
}
// IssueNoParentsLeft checks if issue can be closed
func IssueNoParentsLeft(issue *Issue) (bool, error) {
return issueNoParentsLeft(db.GetEngine(db.DefaultContext), issue)
}
func issueNoParentsLeft(e db.Engine, issue *Issue) (bool, error) {
exists, err := e.
Table("issue_parent").
Select("issue.*").
Join("INNER", "issue", "issue.id = issue_parent.parent_id").
Where("issue_parent.issue_id = ?", issue.ID).
And("issue.is_closed = ?", "0").
Exist(&Issue{})
return !exists, err
}

61
models/issue_parent_test.go

@ -0,0 +1,61 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"testing"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
)
func TestCreateIssueParent(t *testing.T) {
// Prepare
assert.NoError(t, unittest.PrepareTestDatabase())
user1, err := user_model.GetUserByID(1)
assert.NoError(t, err)
issue1, err := GetIssueByID(1)
assert.NoError(t, err)
issue2, err := GetIssueByID(2)
assert.NoError(t, err)
// Create a dependency and check if it was successful
err = CreateIssueParent(user1, issue1, issue2)
assert.NoError(t, err)
// Do it again to see if it will check if the dependency already exists
err = CreateIssueParent(user1, issue1, issue2)
assert.Error(t, err)
assert.True(t, IsErrParentExists(err))
// Check for circular dependencies
err = CreateIssueParent(user1, issue2, issue1)
assert.Error(t, err)
assert.True(t, IsErrCircularParent(err))
_ = unittest.AssertExistsAndLoadBean(t, &Comment{Type: CommentTypeAddParent, PosterID: user1.ID, IssueID: issue1.ID})
// Check if dependencies left is correct
left, err := IssueNoParentsLeft(issue1)
assert.NoError(t, err)
assert.False(t, left)
// Close #2 and check again
_, err = issue2.ChangeStatus(user1, true)
assert.NoError(t, err)
left, err = IssueNoParentsLeft(issue1)
assert.NoError(t, err)
assert.True(t, left)
// Test removing the dependency
err = RemoveIssueParent(user1, issue1, issue2, ParentTypeFather)
assert.NoError(t, err)
}

3
models/migrations/v70.go

@ -102,6 +102,9 @@ func addIssueDependencies(x *xorm.Engine) (err error) {
if _, ok := unit.Config["EnableDependencies"]; !ok {
unit.Config["EnableDependencies"] = setting.Service.DefaultEnableDependencies
}
if _, ok := unit.Config["EnableParents"]; !ok {
unit.Config["EnableParents"] = setting.Service.DefaultEnableParents
}
if _, err := x.ID(unit.ID).Cols("config").Update(unit); err != nil {
return err
}

4
models/org_team.go

@ -691,8 +691,8 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
return errors.New("empty team name")
}
if len(t.Description) > 255 {
t.Description = t.Description[:255]
if len(t.Description) > 1024 {
t.Description = t.Description[:1024]
}
ctx, committer, err := db.TxContext()

5
models/repo.go

@ -515,6 +515,7 @@ func CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_
EnableTimetracker: setting.Service.DefaultEnableTimetracking,
AllowOnlyContributorsToTrackTime: setting.Service.DefaultAllowOnlyContributorsToTrackTime,
EnableDependencies: setting.Service.DefaultEnableDependencies,
EnableParents: setting.Service.DefaultEnableParents,
},
})
} else if tp == unit.TypePullRequests {
@ -635,8 +636,8 @@ func DecrementRepoForkNum(ctx context.Context, repoID int64) error {
func updateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) {
repo.LowerName = strings.ToLower(repo.Name)
if utf8.RuneCountInString(repo.Description) > 255 {
repo.Description = string([]rune(repo.Description)[:255])
if utf8.RuneCountInString(repo.Description) > 1024 {
repo.Description = string([]rune(repo.Description)[:1024])
}
if utf8.RuneCountInString(repo.Website) > 255 {
repo.Website = string([]rune(repo.Website)[:255])

16
models/repo/issue.go

@ -70,3 +70,19 @@ func (repo *Repository) IsDependenciesEnabledCtx(ctx context.Context) bool {
}
return u.IssuesConfig().EnableDependencies
}
// IsParentsEnabled returns if parents are enabled and returns the default setting if not set.
func (repo *Repository) IsParentsEnabled() bool {
return repo.IsParentsEnabledCtx(db.DefaultContext)
}
// IsParentsEnabledCtx returns if parents are enabled and returns the default setting if not set.
func (repo *Repository) IsParentsEnabledCtx(ctx context.Context) bool {
var u *RepoUnit
var err error
if u, err = repo.GetUnitCtx(ctx, unit.TypeIssues); err != nil {
log.Trace("%s", err)
return setting.Service.DefaultEnableParents
}
return u.IssuesConfig().EnableParents
}

1
models/repo/repo_unit.go

@ -94,6 +94,7 @@ type IssuesConfig struct {
EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableDependencies bool
EnableParents bool
}
// FromDB fills up a IssuesConfig from serialized format.

6
models/user/user.go

@ -94,11 +94,12 @@ type User struct {
LoginName string
Type UserType
Location string
LocationCoordinate string
Website string
Rands string `xorm:"VARCHAR(32)"`
Salt string `xorm:"VARCHAR(32)"`
Language string `xorm:"VARCHAR(5)"`
Description string
Description string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@ -187,8 +188,9 @@ func (u *User) BeforeUpdate() {
u.LowerName = strings.ToLower(u.Name)
u.Location = base.TruncateString(u.Location, 255)
u.LocationCoordinate = base.TruncateString(u.LocationCoordinate, 255)
u.Website = base.TruncateString(u.Website, 255)
u.Description = base.TruncateString(u.Description, 255)
u.Description = base.TruncateString(u.Description, 1024)
}
// AfterLoad is invoked from XORM after filling all the fields of this object.

5
modules/context/repo.go

@ -164,6 +164,11 @@ func (r *Repository) CanCreateIssueDependencies(user *user_model.User, isPull bo
return r.Repository.IsDependenciesEnabled() && r.Permission.CanWriteIssuesOrPulls(isPull)
}
// CanCreateIssueParents returns whether or not a user can create parents.
func (r *Repository) CanCreateIssueParents(user *user_model.User, isPull bool) bool {
return r.Repository.IsParentsEnabled() && r.Permission.CanWriteIssuesOrPulls(isPull)
}
// GetCommitsCount returns cached commit count for current view
func (r *Repository) GetCommitsCount() (int64, error) {
var contextName string

1
modules/convert/convert.go

@ -289,6 +289,7 @@ func ToOrganization(org *models.Organization) *api.Organization {
Description: org.Description,
Website: org.Website,
Location: org.Location,
LocationCoordinate: org.LocationCoordinate,
Visibility: org.Visibility.String(),
RepoAdminChangeTeamAccess: org.RepoAdminChangeTeamAccess,
}

10
modules/convert/issue_comment.go

@ -52,6 +52,12 @@ func ToTimelineComment(c *models.Comment, doer *user_model.User) *api.TimelineCo
return nil
}
err = c.LoadParentIssueDetails()
if err != nil {
log.Error("LoadParentIssueDetails: %v", err)
return nil
}
err = c.LoadTime()
if err != nil {
log.Error("LoadTime: %v", err)
@ -163,5 +169,9 @@ func ToTimelineComment(c *models.Comment, doer *user_model.User) *api.TimelineCo
comment.DependentIssue = ToAPIIssue(c.DependentIssue)
}
if c.ParentIssue != nil {
comment.ParentIssue = ToAPIIssue(c.ParentIssue)
}
return comment
}

1
modules/convert/repository.go

@ -51,6 +51,7 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
EnableTimeTracker: config.EnableTimetracker,
AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime,
EnableIssueDependencies: config.EnableDependencies,
EnableIssueParents: config.EnableParents,
}
} else if unit, err := repo.GetUnit(unit_model.TypeExternalTracker); err == nil {
config := unit.ExternalTrackerConfig()

2
modules/convert/user.go

@ -55,6 +55,7 @@ func toUser(user *user_model.User, signed, authed bool) *api.User {
Created: user.CreatedUnix.AsTime(),
Restricted: user.IsRestricted,
Location: user.Location,
LocationCoordinate: user.LocationCoordinate,
Website: user.Website,
Description: user.Description,
// counter's
@ -87,6 +88,7 @@ func User2UserSettings(user *user_model.User) api.UserSettings {
FullName: user.FullName,
Website: user.Website,
Location: user.Location,
LocationCoordinate: user.LocationCoordinate,
Language: user.Language,
Description: user.Description,
Theme: user.Theme,

4
modules/doctor/fix16961.go

@ -206,6 +206,10 @@ func fixIssuesConfig16961(bs []byte, cfg *repo_model.IssuesConfig) (fixed bool,
if parseErr != nil {
return
}
cfg.EnableParents, parseErr = parseBool16961(parts[3])
if parseErr != nil {
return
}
return true, nil
}

4
modules/doctor/fix16961_test.go

@ -237,11 +237,12 @@ func Test_fixIssuesConfig_16961(t *testing.T) {
}{
{
name: "normal",
bs: `{"EnableTimetracker":true,"AllowOnlyContributorsToTrackTime":true,"EnableDependencies":true}`,
bs: `{"EnableTimetracker":true,"AllowOnlyContributorsToTrackTime":true,"EnableDependencies":true,"EnableParents":true}`,
expected: repo_model.IssuesConfig{
EnableTimetracker: true,
AllowOnlyContributorsToTrackTime: true,
EnableDependencies: true,
EnableParents: true,
},
},
{
@ -251,6 +252,7 @@ func Test_fixIssuesConfig_16961(t *testing.T) {
EnableTimetracker: true,
AllowOnlyContributorsToTrackTime: true,
EnableDependencies: true,
EnableParents: true,
},
wantFixed: true,
},

4
modules/setting/service.go

@ -53,7 +53,9 @@ var Service = struct {
EnableTimetracking bool
DefaultEnableTimetracking bool
DefaultEnableDependencies bool
DefaultEnableParents bool
AllowCrossRepositoryDependencies bool
AllowCrossRepositoryParents bool
DefaultAllowOnlyContributorsToTrackTime bool
NoReplyAddress string
EnableUserHeatmap bool
@ -141,7 +143,9 @@ func newService() {
Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
}
Service.DefaultEnableDependencies = sec.Key("DEFAULT_ENABLE_DEPENDENCIES").MustBool(true)
Service.DefaultEnableParents = sec.Key("DEFAULT_ENABLE_PARENTS").MustBool(true)
Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true)
Service.AllowCrossRepositoryParents = sec.Key("ALLOW_CROSS_REPOSITORY_PARENTS").MustBool(true)
Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)
Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply." + Domain)
Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true)

1
modules/structs/admin_user.go

@ -36,6 +36,7 @@ type EditUserOption struct {
MustChangePassword *bool `json:"must_change_password"`
Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"`
Location *string `json:"location" binding:"MaxSize(50)"`
LocationCoordinate *string `json:"location_coordinate" binding:"MaxSize(255)"`
Description *string `json:"description" binding:"MaxSize(255)"`
Active *bool `json:"active"`
Admin *bool `json:"admin"`

2
modules/structs/issue_comment.go

@ -79,4 +79,6 @@ type TimelineComment struct {
ResolveDoer *User `json:"resolve_doer"`
DependentIssue *Issue `json:"dependent_issue"`
ParentIssue *Issue `json:"parent_issue"`
}

7
modules/structs/org.go

@ -13,6 +13,7 @@ type Organization struct {
Description string `json:"description"`
Website string `json:"website"`
Location string `json:"location"`
LocationCoordinate string `json:"location_coordinate"`
Visibility string `json:"visibility"`
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access"`
}
@ -31,9 +32,10 @@ type CreateOrgOption struct {
// required: true
UserName string `json:"username" binding:"Required"`
FullName string `json:"full_name"`
Description string `json:"description" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(1024)"`
Website string `json:"website" binding:"ValidUrl;MaxSize(255)"`
Location string `json:"location" binding:"MaxSize(50)"`
LocationCoordinate string `json:"location_coordinate" binding:"MaxSize(255)"`
// possible values are `public` (default), `limited` or `private`
// enum: public,limited,private
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
@ -45,9 +47,10 @@ type CreateOrgOption struct {
// EditOrgOption options for editing an organization
type EditOrgOption struct {
FullName string `json:"full_name"`
Description string `json:"description" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(1024)"`
Website string `json:"website" binding:"ValidUrl;MaxSize(255)"`
Location string `json:"location" binding:"MaxSize(50)"`
LocationCoordinate string `json:"location_coordinate" binding:"MaxSize(255)"`
// possible values are `public`, `limited` or `private`
// enum: public,limited,private
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`

4
modules/structs/org_team.go

@ -26,7 +26,7 @@ type Team struct {
type CreateTeamOption struct {
// required: true
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
Description string `json:"description" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(1024)"`
IncludesAllRepositories bool `json:"includes_all_repositories"`
// enum: read,write,admin
Permission string `json:"permission"`
@ -42,7 +42,7 @@ type CreateTeamOption struct {
type EditTeamOption struct {
// required: true
Name string `json:"name" binding:"AlphaDashDot;MaxSize(30)"`
Description *string `json:"description" binding:"MaxSize(255)"`
Description *string `json:"description" binding:"MaxSize(1024)"`
IncludesAllRepositories *bool `json:"includes_all_repositories"`
// enum: read,write,admin
Permission string `json:"permission"`

10
modules/structs/repo.go

@ -25,6 +25,8 @@ type InternalTracker struct {
AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time"`
// Enable dependencies for issues and pull requests (Built-in issue tracker)
EnableIssueDependencies bool `json:"enable_issue_dependencies"`
// Enable parents for issues and pull requests (Built-in issue tracker)
EnableIssueParents bool `json:"enable_issue_parents"`
}
// ExternalTracker represents settings for external tracker
@ -107,7 +109,7 @@ type CreateRepoOption struct {
// unique: true
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"`
// Description of the repository to create
Description string `json:"description" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(1024)"`
// Whether the repository is private
Private bool `json:"private"`
// Label-Set to use
@ -136,7 +138,7 @@ type EditRepoOption struct {
// unique: true
Name *string `json:"name,omitempty" binding:"OmitEmpty;AlphaDashDot;MaxSize(100);"`
// a short description of the repository.
Description *string `json:"description,omitempty" binding:"MaxSize(255)"`
Description *string `json:"description,omitempty" binding:"MaxSize(1024)"`
// a URL with more information about the repository.
Website *string `json:"website,omitempty" binding:"MaxSize(255)"`
// either `true` to make the repository private or `false` to make it public.
@ -200,7 +202,7 @@ type GenerateRepoOption struct {
// unique: true
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"`
// Description of the repository to create
Description string `json:"description" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(1024)"`
// Whether the repository is private
Private bool `json:"private"`
// include git content of default branch in template repo
@ -309,7 +311,7 @@ type MigrateRepoOptions struct {
LFS bool `json:"lfs"`
LFSEndpoint string `json:"lfs_endpoint"`
Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(1024)"`
Wiki bool `json:"wiki"`
Milestones bool `json:"milestones"`
Labels bool `json:"labels"`

6
modules/structs/user.go

@ -39,6 +39,8 @@ type User struct {
ProhibitLogin bool `json:"prohibit_login"`
// the user's location
Location string `json:"location"`
// the user's location coordinate
LocationCoordinate string `json:"location_coordinate"`
// the user's website
Website string `json:"website"`
// the user's description
@ -69,6 +71,7 @@ type UserSettings struct {
Website string `json:"website"`
Description string `json:"description"`
Location string `json:"location"`
LocationCoordinate string `json:"location_coordinate"`
Language string `json:"language"`
Theme string `json:"theme"`
DiffViewStyle string `json:"diff_view_style"`
@ -82,8 +85,9 @@ type UserSettings struct {
type UserSettingsOptions struct {
FullName *string `json:"full_name" binding:"MaxSize(100)"`
Website *string `json:"website" binding:"OmitEmpty;ValidUrl;MaxSize(255)"`
Description *string `json:"description" binding:"MaxSize(255)"`
Description *string `json:"description" binding:"MaxSize(1024)"`
Location *string `json:"location" binding:"MaxSize(50)"`
LocationCoordinate *string `json:"location_coordinate" binding:"MaxSize(255)"`
Language *string `json:"language"`
Theme *string `json:"theme"`
DiffViewStyle *string `json:"diff_view_style"`

571
options/locale/locale_ru-RU.ini

File diff suppressed because it is too large Load Diff

39
package-lock.json generated

@ -1711,6 +1711,8 @@
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
"optional": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -1725,7 +1727,9 @@
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"optional": true,
"peer": true
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
@ -2119,13 +2123,19 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001301",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001301.tgz",
"integrity": "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA==",
"funding": {
"version": "1.0.30001374",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001374.tgz",
"integrity": "sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
}
]
},
"node_modules/chalk": {
"version": "4.1.2",
@ -11189,14 +11199,13 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"requires": {
"ajv": "^8.0.0"
},
"requires": {},
"dependencies": {
"ajv": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
"version": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
"optional": true,
"peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -11207,7 +11216,9 @@
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"optional": true,
"peer": true
}
}
},
@ -11497,9 +11508,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001301",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001301.tgz",
"integrity": "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA=="
"version": "1.0.30001374",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001374.tgz",
"integrity": "sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw=="
},
"chalk": {
"version": "4.1.2",

BIN
public/img/logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

108
public/img/logo.svg

@ -1 +1,107 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640" xml:space="preserve" width="32" height="32"><path style="fill:#fff" d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z"/><path style="fill:#609926" d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"/><path style="fill:#609926" d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z"/></svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 1133.8586 755.90533"
height="755.90533"
width="1133.8586"
xml:space="preserve"
id="svg2"
version="1.1"
sodipodi:docname="Мирокод 3.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><sodipodi:namedview
id="namedview98"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.97983995"
inkscape:cx="566.92932"
inkscape:cy="377.61269"
inkscape:window-width="1280"
inkscape:window-height="981"
inkscape:window-x="-2"
inkscape:window-y="-2"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" /><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6"><clipPath
id="clipPath18"
clipPathUnits="userSpaceOnUse"><path
id="path16"
d="M 0,0 H 726.398 V 178.782 H 0 Z" /></clipPath><clipPath
id="clipPath40"
clipPathUnits="userSpaceOnUse"><path
id="path38"
d="M 0,566.929 H 850.394 V 0 H 0 Z" /></clipPath></defs><g
id="g457"><g
transform="matrix(1.3333333,0,0,-1.3333333,198.4164,535.00667)"
id="g26"><path
id="path28"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 H 14.864 L 16.008,-0.572 39.447,-28.584 62.886,-0.572 64.03,0 h 14.864 l 0.571,-0.572 v -78.894 l -0.571,-0.572 H 64.03 l -0.572,0.572 v 54.883 L 40.019,-52.024 h -1.144 l -23.439,27.441 v -54.883 l -0.572,-0.572 H 0 l -0.572,0.572 v 78.894 z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,321.12346,535.00667)"
id="g30"><path
id="path32"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 h 14.864 l 0.571,-0.572 V -52.596 L 55.454,-0.572 56.598,0 h 15.436 l 0.571,-0.572 v -78.894 l -0.571,-0.572 H 57.169 l -0.571,0.572 v 52.024 L 16.579,-79.466 15.435,-80.038 H 0 l -0.572,0.572 v 78.894 z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,455.26466,556.35053)"
id="g42"><path
id="path44"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 v -19.437 h 8.003 c 8.004,0 12.006,3.238 12.006,9.718 C 20.009,-3.241 16.007,0 8.003,0 Z M -15.436,16.008 H 9.147 c 8.765,0 15.722,-2.498 20.867,-7.49 5.145,-4.993 7.717,-11.073 7.717,-18.237 0,-7.165 -2.572,-13.245 -7.717,-18.237 -5.145,-4.993 -12.102,-7.489 -20.867,-7.489 H 0 v -28.013 l -0.572,-0.572 h -14.864 l -0.572,0.572 v 78.894 z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,592.21265,564.50613)"
id="g46"><path
id="path48"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -4.765,4.839 -10.576,7.26 -17.437,7.26 -6.86,0 -12.673,-2.421 -17.436,-7.26 -4.765,-4.842 -7.147,-10.806 -7.147,-17.895 0,-7.089 2.382,-13.053 7.147,-17.894 4.763,-4.841 10.576,-7.26 17.436,-7.26 6.861,0 12.672,2.419 17.437,7.26 4.763,4.841 7.146,10.805 7.146,17.894 C 7.146,-10.806 4.763,-4.842 0,0 m -47.394,11.72 c 8.233,8.078 18.218,12.119 29.957,12.119 11.738,0 21.724,-4.041 29.957,-12.119 8.232,-8.081 12.348,-17.951 12.348,-29.615 0,-11.662 -4.116,-21.534 -12.348,-29.613 -8.233,-8.081 -18.219,-12.12 -29.957,-12.12 -11.739,0 -21.724,4.039 -29.957,12.12 -8.232,8.079 -12.348,17.951 -12.348,29.613 0,11.664 4.116,21.534 12.348,29.615" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,637.04732,535.00667)"
id="g50"><path
id="path52"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 h 14.863 l 0.573,-0.572 v -27.441 h 6.86 L 47.45,-0.572 48.594,0 h 17.722 l 0.572,-0.572 -31.444,-34.302 33.731,-44.592 -0.571,-0.572 H 50.881 l -1.145,0.572 -27.44,35.445 h -6.86 v -35.445 l -0.573,-0.572 H 0 l -0.572,0.572 v 78.894 z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,803.04985,564.50613)"
id="g54"><path
id="path56"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -4.765,4.839 -10.576,7.26 -17.437,7.26 -6.86,0 -12.672,-2.421 -17.436,-7.26 -4.765,-4.842 -7.147,-10.806 -7.147,-17.895 0,-7.089 2.382,-13.053 7.147,-17.894 4.764,-4.841 10.576,-7.26 17.436,-7.26 6.861,0 12.672,2.419 17.437,7.26 4.764,4.841 7.146,10.805 7.146,17.894 C 7.146,-10.806 4.764,-4.842 0,0 m -47.394,11.72 c 8.234,8.078 18.218,12.119 29.957,12.119 11.739,0 21.725,-4.041 29.958,-12.119 8.232,-8.081 12.348,-17.951 12.348,-29.615 0,-11.662 -4.116,-21.534 -12.348,-29.613 -8.233,-8.081 -18.219,-12.12 -29.958,-12.12 -11.739,0 -21.723,4.039 -29.957,12.12 -8.232,8.079 -12.348,17.951 -12.348,29.613 0,11.664 4.116,21.534 12.348,29.615" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,884.05371,556.35053)"
id="g58"><path
id="path60"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 -4.464,-43.558 c 0,-0.154 -0.077,-0.801 -0.229,-1.944 -0.154,-1.144 -0.343,-2.287 -0.572,-3.43 h 30.3 V 0 Z m -13.72,16.008 h 54.191 l 0.572,-0.572 v -64.368 h 8.575 l 0.572,-0.571 v -36.264 l -0.572,-0.571 H 34.754 l -0.571,0.571 v 20.828 h -51.454 v -20.828 l -0.571,-0.571 h -14.863 l -0.572,0.571 v 36.264 l 0.572,0.571 h 9.832 c 0.686,1.372 1.486,4.039 2.401,8.004 l 6.18,56.364 z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,565.51465,132.72228)"
id="g186"><path
id="path188"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 17.049,-7.257 33.428,-15.985 48.958,-26.035 l 2.688,25.574 C 35.257,9.378 18.003,17.924 0,24.988 -18.068,17.899 -35.381,9.316 -51.823,-0.567 l 2.688,-25.581 C -33.552,-16.052 -17.114,-7.284 0,0" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,662.47692,431.28934)"
id="g190"><path
id="path192"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -18.016,-4.131 -36.423,-6.706 -54.983,-7.711 l 12.847,-22.254 c 19.435,1.7 38.419,4.969 56.803,9.739 C 24.999,-4.102 34,12.95 41.586,30.75 L 24.376,49.864 C 17.707,32.6 9.563,15.903 0,0" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,722.35852,246.79974)"
id="g194"><path
id="path196"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -1.611,-18.497 -4.824,-36.788 -9.554,-54.656 l 25.172,5.35 c 4.228,18.498 6.994,37.552 8.137,57.044 C 11.508,22.622 -1.976,36.444 -16.502,49.1 L -39.975,38.649 C -25.615,26.958 -12.224,14.034 0,0" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,408.66052,246.79708)"
id="g198"><path
id="path200"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 12.203,14.009 25.569,26.915 39.902,38.589 l -23.469,10.45 c -14.5,-12.64 -27.96,-26.441 -40.188,-41.301 1.142,-19.466 3.901,-38.495 8.119,-56.968 l 25.17,-5.35 C 4.814,-36.736 1.609,-18.47 0,0" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,468.54465,431.28734)"
id="g202"><path
id="path204"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C -9.588,15.947 -17.75,32.693 -24.43,50.008 L -41.645,30.889 c 7.6,-17.85 16.619,-34.95 26.979,-51.115 18.382,-4.769 37.363,-8.039 56.795,-9.74 L 54.977,-7.713 C 36.419,-6.707 18.016,-4.131 0,0" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

102
public/img/logo1.svg

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 1700.7867 377.95334"
height="377.95334"
width="1700.7867"
xml:space="preserve"
id="svg2"
version="1.1"
sodipodi:docname="Мирокод 2.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><sodipodi:namedview
id="namedview96"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.6532271"
inkscape:cx="850.39337"
inkscape:cy="188.29592"
inkscape:window-width="1280"
inkscape:window-height="981"
inkscape:window-x="-2"
inkscape:window-y="-2"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" /><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs6"><clipPath
id="clipPath26"
clipPathUnits="userSpaceOnUse"><path
id="path24"
d="M 0,283.465 H 1275.591 V 0 H 0 Z" /></clipPath></defs><g
transform="matrix(1.3333333,0,0,-1.3333333,453.37466,90.731737)"
id="g12"><path
id="path14"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 H 22.79 L 24.544,-0.876 60.481,-43.827 96.421,-0.876 98.175,0 h 22.79 l 0.876,-0.876 v -120.965 l -0.876,-0.877 h -22.79 l -0.878,0.877 v 84.15 L 61.359,-79.767 h -1.754 l -35.939,42.076 v -84.15 l -0.876,-0.877 H 0 l -0.877,0.877 V -0.876 Z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,641.51665,90.731737)"
id="g16"><path
id="path18"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 h 22.79 l 0.876,-0.876 V -80.643 L 85.025,-0.876 86.778,0 h 23.668 l 0.876,-0.876 v -120.965 l -0.876,-0.877 H 87.655 l -0.877,0.877 v 79.766 l -61.358,-79.766 -1.754,-0.877 H 0 l -0.878,0.877 V -0.876 Z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,847.18851,123.45627)"
id="g28"><path
id="path30"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 v -29.803 h 12.271 c 12.273,0 18.409,4.966 18.409,14.902 C 30.68,-4.969 24.544,0 12.271,0 Z m -23.666,24.543 h 37.691 c 13.439,0 24.106,-3.829 31.995,-11.482 7.889,-7.656 11.833,-16.978 11.833,-27.962 0,-10.987 -3.944,-20.309 -11.833,-27.963 C 38.131,-50.52 27.464,-54.347 14.025,-54.347 H 0 v -42.951 l -0.876,-0.876 h -22.79 l -0.878,0.876 V 23.667 Z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,1057.165,135.96147)"
id="g32"><path
id="path34"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -7.305,7.419 -16.216,11.131 -26.735,11.131 -10.518,0 -19.431,-3.712 -26.734,-11.131 -7.305,-7.423 -10.958,-16.568 -10.958,-27.437 0,-10.869 3.653,-20.015 10.958,-27.436 7.303,-7.423 16.216,-11.132 26.734,-11.132 10.519,0 19.43,3.709 26.735,11.132 7.303,7.421 10.958,16.567 10.958,27.436 C 10.958,-16.568 7.303,-7.423 0,0 m -72.667,17.969 c 12.624,12.386 27.934,18.583 45.932,18.583 17.998,0 33.309,-6.197 45.931,-18.583 C 31.818,5.58 38.13,-9.554 38.13,-27.437 c 0,-17.881 -6.312,-33.018 -18.934,-45.405 -12.622,-12.39 -27.933,-18.583 -45.931,-18.583 -17.998,0 -33.308,6.193 -45.932,18.583 -12.622,12.387 -18.933,27.524 -18.933,45.405 0,17.883 6.311,33.017 18.933,45.406" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,1125.9085,90.731737)"
id="g36"><path
id="path38"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 h 22.79 l 0.876,-0.876 v -42.075 h 10.52 L 72.754,-0.876 74.507,0 h 27.174 l 0.876,-0.876 -48.211,-52.594 51.717,-68.371 -0.876,-0.877 H 78.014 l -1.754,0.877 -42.074,54.346 h -10.52 v -54.346 l -0.876,-0.877 H 0 l -0.877,0.877 V -0.876 Z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,1380.4345,135.96147)"
id="g40"><path
id="path42"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C -7.306,7.419 -16.216,11.131 -26.735,11.131 -37.254,11.131 -46.167,7.419 -53.47,0 c -7.305,-7.423 -10.957,-16.568 -10.957,-27.437 0,-10.869 3.652,-20.015 10.957,-27.436 7.303,-7.423 16.216,-11.132 26.735,-11.132 10.519,0 19.429,3.709 26.735,11.132 7.303,7.421 10.957,16.567 10.957,27.436 C 10.957,-16.568 7.303,-7.423 0,0 m -72.667,17.969 c 12.623,12.386 27.933,18.583 45.932,18.583 17.997,0 33.309,-6.197 45.931,-18.583 C 31.818,5.58 38.13,-9.554 38.13,-27.437 c 0,-17.881 -6.312,-33.018 -18.934,-45.405 -12.622,-12.39 -27.934,-18.583 -45.931,-18.583 -17.999,0 -33.309,6.193 -45.932,18.583 -12.622,12.387 -18.934,27.524 -18.934,45.405 0,17.883 6.312,33.017 18.934,45.406" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,1504.6337,123.45627)"
id="g44"><path
id="path46"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 -6.844,-66.786 c 0,-0.236 -0.118,-1.228 -0.352,-2.98 -0.235,-1.754 -0.526,-3.506 -0.876,-5.26 H 38.385 V 0 Z m -21.036,24.543 h 83.089 l 0.876,-0.876 v -98.693 h 13.149 l 0.876,-0.876 v -55.601 l -0.876,-0.876 H 53.287 l -0.877,0.876 v 31.934 h -78.889 v -31.934 l -0.877,-0.876 h -22.79 l -0.877,0.876 v 55.601 l 0.877,0.876 h 15.076 c 1.052,2.104 2.279,6.193 3.682,12.272 l 9.474,86.421 z" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,223.65853,66.988804)"
id="g172"><path
id="path174"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 13.018,-5.541 25.524,-12.206 37.384,-19.879 l 2.052,19.527 C 26.921,7.161 13.747,13.687 0,19.08 -13.796,13.667 -27.015,7.114 -39.569,-0.433 l 2.052,-19.533 C -25.618,-12.256 -13.067,-5.562 0,0" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,297.69626,294.9628)"
id="g176"><path
id="path178"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -13.756,-3.155 -27.812,-5.121 -41.983,-5.889 l 9.809,-16.991 c 14.84,1.298 29.336,3.794 43.373,7.436 7.889,12.311 14.762,25.332 20.554,38.923 L 18.612,38.074 C 13.521,24.892 7.301,12.143 0,0" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,343.41892,154.09427)"
id="g180"><path
id="path182"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -1.23,-14.123 -3.684,-28.089 -7.295,-41.733 l 19.221,4.085 c 3.227,14.124 5.34,28.673 6.213,43.557 C 8.787,17.273 -1.509,27.827 -12.6,37.491 l -17.923,-7.98 C -19.559,20.585 -9.334,10.716 0,0" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,103.89026,154.0924)"
id="g184"><path
id="path186"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C 9.318,10.697 19.524,20.551 30.469,29.466 L 12.548,37.444 C 1.477,27.793 -8.801,17.255 -18.138,5.909 c 0.872,-14.864 2.98,-29.393 6.2,-43.499 L 7.28,-41.675 C 3.677,-28.05 1.229,-14.103 0,0" /></g><g
transform="matrix(1.3333333,0,0,-1.3333333,149.6168,294.96213)"
id="g188"><path
id="path190"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C -7.321,12.177 -13.554,24.964 -18.653,38.184 L -31.799,23.585 c 5.804,-13.628 12.69,-26.685 20.6,-39.028 14.036,-3.642 28.53,-6.139 43.367,-7.438 l 9.81,16.991 C 27.808,-5.121 13.756,-3.154 0,0" /></g></svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

51
public/img/logo2.svg

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 377.95334 377.95334"
height="377.95334"
width="377.95334"
xml:space="preserve"
id="svg2"
version="1.1"><metadata
id="metadata8"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs6"><clipPath
id="clipPath18"
clipPathUnits="userSpaceOnUse"><path
id="path16"
d="M 0,283.465 H 283.465 V 0 H 0 Z" /></clipPath></defs><g
transform="matrix(1.3333333,0,0,-1.3333333,0,377.95333)"
id="g10"><g
id="g12"><g
clip-path="url(#clipPath18)"
id="g14"><g
transform="translate(141.7347,229.1228)"
id="g20"><path
id="path22"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c 13.018,-5.541 25.524,-12.205 37.383,-19.879 l 2.053,19.527 C 26.921,7.161 13.747,13.687 0,19.08 -13.796,13.667 -27.015,7.114 -39.57,-0.433 l 2.053,-19.533 C -25.619,-12.256 -13.067,-5.562 0,0" /></g><g
transform="translate(197.2625,58.1423)"
id="g24"><path
id="path26"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -13.756,-3.155 -27.811,-5.12 -41.983,-5.889 l 9.81,-16.991 c 14.84,1.298 29.335,3.794 43.372,7.436 7.89,12.312 14.762,25.332 20.555,38.923 L 18.613,38.074 C 13.521,24.892 7.301,12.143 0,0" /></g><g
transform="translate(231.5555,163.7942)"
id="g28"><path
id="path30"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 0,0 c -1.231,-14.124 -3.684,-28.09 -7.295,-41.733 l 19.22,4.085 c 3.228,14.124 5.34,28.673 6.213,43.556 -9.351,11.365 -19.647,21.919 -30.739,31.583 l -17.923,-7.98 C -19.559,20.584 -9.334,10.716 0,0" /></g><g
transform="translate(51.909,163.7956)"
id="g32"><path
id="path34"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C 9.318,10.697 19.524,20.551 30.468,29.465 L 12.547,37.444 C 1.476,27.793 -8.802,17.255 -18.138,5.908 c 0.872,-14.863 2.979,-29.393 6.2,-43.498 L 7.28,-41.675 C 3.676,-28.05 1.229,-14.103 0,0" /></g><g
transform="translate(86.2034,58.1433)"
id="g36"><path
id="path38"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="M 0,0 C -7.321,12.177 -13.553,24.963 -18.654,38.184 L -31.798,23.585 c 5.802,-13.629 12.689,-26.686 20.599,-39.029 14.035,-3.641 28.53,-6.139 43.366,-7.437 L 41.978,-5.89 C 27.808,-5.121 13.756,-3.155 0,0" /></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

1
routers/api/v1/admin/org.go

@ -62,6 +62,7 @@ func CreateOrg(ctx *context.APIContext) {
Description: form.Description,
Website: form.Website,
Location: form.Location,
LocationCoordinate: form.LocationCoordinate,
IsActive: true,
Type: user_model.UserTypeOrganization,
Visibility: visibility,

3
routers/api/v1/admin/user.go

@ -240,6 +240,9 @@ func EditUser(ctx *context.APIContext) {
if form.Location != nil {
u.Location = *form.Location
}
if form.LocationCoordinate != nil {
u.LocationCoordinate = *form.LocationCoordinate
}
if form.Description != nil {
u.Description = *form.Description
}

2
routers/api/v1/org/org.go

@ -267,6 +267,7 @@ func Create(ctx *context.APIContext) {
Description: form.Description,
Website: form.Website,
Location: form.Location,
LocationCoordinate: form.LocationCoordinate,
IsActive: true,
Type: user_model.UserTypeOrganization,
Visibility: visibility,
@ -340,6 +341,7 @@ func Edit(ctx *context.APIContext) {
org.Description = form.Description
org.Website = form.Website
org.Location = form.Location
org.LocationCoordinate = form.LocationCoordinate
if form.Visibility != "" {
org.Visibility = api.VisibilityModes[form.Visibility]
}

2
routers/api/v1/repo/repo.go

@ -776,6 +776,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
EnableTimetracker: opts.InternalTracker.EnableTimeTracker,
AllowOnlyContributorsToTrackTime: opts.InternalTracker.AllowOnlyContributorsToTrackTime,
EnableDependencies: opts.InternalTracker.EnableIssueDependencies,
EnableParents: opts.InternalTracker.EnableIssueParents,
}
} else if unit, err := repo.GetUnit(unit_model.TypeIssues); err != nil {
// Unit type doesn't exist so we make a new config file with default values
@ -783,6 +784,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
EnableTimetracker: true,
AllowOnlyContributorsToTrackTime: true,
EnableDependencies: true,
EnableParents: true,
}
} else {
config = unit.IssuesConfig()

3
routers/api/v1/user/settings.go

@ -57,6 +57,9 @@ func UpdateUserSettings(ctx *context.APIContext) {
if form.Location != nil {
ctx.User.Location = *form.Location
}
if form.LocationCoordinate != nil {
ctx.User.LocationCoordinate = *form.LocationCoordinate
}
if form.Language != nil {
ctx.User.Language = *form.Language
}

2
routers/web/admin/users.go

@ -371,7 +371,9 @@ func EditUserPost(ctx *context.Context) {
emailChanged := !strings.EqualFold(u.Email, form.Email)
u.Email = form.Email
u.Website = form.Website
u.Description = form.Description
u.Location = form.Location
u.LocationCoordinate = form.LocationCoordinate
u.MaxRepoCreation = form.MaxRepoCreation
u.IsActive = form.Active
u.IsAdmin = form.Admin

127
routers/web/map/umap.go

@ -0,0 +1,127 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package umap
import (
"bytes"
"net/http"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
)
const (
// tplExploreUsers map page template
tplExploreUsers base.TplName = "map/umap"
)
// UserSearchDefaultSortType is the default sort type for user search
const UserSearchDefaultSortType = "alphabetically"
var (
nullByte = []byte{0x00}
)
func isKeywordValid(keyword string) bool {
return !bytes.Contains([]byte(keyword), nullByte)
}
// RenderUserSearch render user search page
func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName base.TplName) {
opts.PageSize = 8 * 1024 // Get all users
opts.Page = ctx.FormInt("page")
if opts.Page <= 1 {
opts.Page = 1
}
var (
users []*user_model.User
count int64
err error
orderBy db.SearchOrderBy
)
// we can not set orderBy to `models.SearchOrderByXxx`, because there may be a JOIN in the statement, different tables may have the same name columns
ctx.Data["SortType"] = ctx.FormString("sort")
switch ctx.FormString("sort") {
case "newest":
orderBy = "`user`.id DESC"
case "oldest":
orderBy = "`user`.id ASC"
case "recentupdate":
orderBy = "`user`.updated_unix DESC"
case "leastupdate":
orderBy = "`user`.updated_unix ASC"
case "reversealphabetically":
orderBy = "`user`.name DESC"
case UserSearchDefaultSortType: // "alphabetically"
default:
orderBy = "`user`.name ASC"
ctx.Data["SortType"] = UserSearchDefaultSortType
}
opts.Keyword = ctx.FormTrim("q")
opts.OrderBy = orderBy
if len(opts.Keyword) == 0 || isKeywordValid(opts.Keyword) {
users, count, err = user_model.SearchUsers(opts)
if err != nil {
ctx.ServerError("SearchUsers", err)
return
}
}
for i := range users {
if len(users[i].Description) != 0 {
content, err := markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
Metas: map[string]string{"mode": "document"},
GitRepo: ctx.Repo.GitRepo,
Ctx: ctx,
}, users[i].Description)
if err != nil {
ctx.ServerError("RenderString", err)
return
}
users[i].Description = content
}
}
ctx.Data["Keyword"] = opts.Keyword
ctx.Data["Total"] = count
ctx.Data["Users"] = users
ctx.Data["UsersTwoFaStatus"] = user_model.UserList(users).GetTwoFaStatus()
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.HTML(http.StatusOK, tplName)
}
// Users render explore users page
func UsersMap(ctx *context.Context) {
if setting.Service.Explore.DisableUsersPage {
ctx.Redirect(setting.AppSubURL + "/map/umap")
return
}
ctx.Data["Title"] = ctx.Tr("map")
ctx.Data["PageIsMap"] = true
ctx.Data["PageIsMapUsers"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
RenderUserSearch(ctx, &user_model.SearchUserOptions{
Actor: ctx.User,
Type: user_model.UserTypeIndividual,
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
IsActive: util.OptionalBoolTrue,
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
}, tplExploreUsers)
}

1
routers/web/org/setting.go

@ -100,6 +100,7 @@ func SettingsPost(ctx *context.Context) {
org.Description = form.Description
org.Website = form.Website
org.Location = form.Location
org.LocationCoordinate = form.LocationCoordinate
org.RepoAdminChangeTeamAccess = form.RepoAdminChangeTeamAccess
visibilityChanged := form.Visibility != org.Visibility

78
routers/web/repo/issue.go

@ -47,6 +47,7 @@ const (
tplAttachment base.TplName = "repo/issue/view_content/attachments"
tplIssues base.TplName = "repo/issue/list"
tplIssuesTree base.TplName = "repo/issue/tree"
tplIssueNew base.TplName = "repo/issue/new"
tplIssueChoose base.TplName = "repo/issue/choose"
tplIssueView base.TplName = "repo/issue/view"
@ -114,7 +115,7 @@ func MustAllowPulls(ctx *context.Context) {
}
}
func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption util.OptionalBool) {
func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption util.OptionalBool, page_size int) {
var err error
viewType := ctx.FormString("type")
sortType := ctx.FormString("sort")
@ -210,7 +211,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
} else {
total = int(issueStats.ClosedCount)
}
pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5)
pager := context.NewPagination(total, page_size, page, 5)
var mileIDs []int64
if milestoneID > 0 {
@ -224,7 +225,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
issues, err = models.Issues(&models.IssuesOptions{
ListOptions: db.ListOptions{
Page: pager.Paginater.Current(),
PageSize: setting.UI.IssuePagingNum,
PageSize: page_size,
},
RepoID: repo.ID,
AssigneeID: assigneeID,
@ -391,7 +392,7 @@ func Issues(ctx *context.Context) {
ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0
}
issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), util.OptionalBoolOf(isPullList))
issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), util.OptionalBoolOf(isPullList), setting.UI.IssuePagingNum)
if ctx.Written() {
return
}
@ -412,6 +413,47 @@ func Issues(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplIssues)
}
// Issues Tree render issues page
func IssuesTree(ctx *context.Context) {
isPullList := ctx.Params(":type") == "pulls"
if isPullList {
MustAllowPulls(ctx)
if ctx.Written() {
return
}
ctx.Data["Title"] = ctx.Tr("repo.pulls")
ctx.Data["PageIsPullList"] = true
} else {
MustEnableIssues(ctx)
if ctx.Written() {
return
}
ctx.Data["Title"] = ctx.Tr("repo.issues_tree")
ctx.Data["PageIsIssueList"] = true
ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0
}
issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), util.OptionalBoolOf(isPullList), 32*1024)
if ctx.Written() {
return
}
var err error
// Get milestones
ctx.Data["Milestones"], _, err = models.GetMilestones(models.GetMilestonesOption{
RepoID: ctx.Repo.Repository.ID,
State: api.StateType(ctx.FormString("state")),
})
if err != nil {
ctx.ServerError("GetAllRepoMilestones", err)
return
}
ctx.Data["CanWriteIssuesOrPulls"] = ctx.Repo.CanWriteIssuesOrPulls(isPullList)
ctx.HTML(http.StatusOK, tplIssuesTree)
}
// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.Repository) {
var err error
@ -701,6 +743,9 @@ func RetrieveRepoMetas(ctx *context.Context, repo *repo_model.Repository, isPull
// Contains true if the user can create issue dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, isPull)
// Contains true if the user can create issue parents
ctx.Data["CanCreateIssueParents"] = ctx.Repo.CanCreateIssueParents(ctx.User, isPull)
return labels
}
@ -1324,6 +1369,12 @@ func ViewIssue(ctx *context.Context) {
// check if dependencies can be created across repositories
ctx.Data["AllowCrossRepositoryDependencies"] = setting.Service.AllowCrossRepositoryDependencies
// Check if the user can use the parents
ctx.Data["CanCreateIssueParents"] = ctx.Repo.CanCreateIssueParents(ctx.User, issue.IsPull)
// check if parents can be created across repositories
ctx.Data["AllowCrossRepositoryParents"] = setting.Service.AllowCrossRepositoryParents
if issue.ShowRole, err = roleDescriptor(repo, issue.Poster, issue); err != nil {
ctx.ServerError("roleDescriptor", err)
return
@ -1422,6 +1473,13 @@ func ViewIssue(ctx *context.Context) {
return
}
}
} else if comment.Type == models.CommentTypeRemoveParent || comment.Type == models.CommentTypeAddParent {
if err = comment.LoadParentIssueDetails(); err != nil {
if !models.IsErrIssueNotExist(err) {
ctx.ServerError("LoadParentIssueDetails", err)
return
}
}
} else if comment.Type == models.CommentTypeCode || comment.Type == models.CommentTypeReview || comment.Type == models.CommentTypeDismissReview {
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.Repo.RepoLink,
@ -1650,6 +1708,18 @@ func ViewIssue(ctx *context.Context) {
return
}
// Get Parents
ctx.Data["BlockedByParents"], err = issue.BlockedByParents()
if err != nil {
ctx.ServerError("BlockedByParents", err)
return
}
ctx.Data["BlockingParents"], err = issue.BlockingParents()
if err != nil {
ctx.ServerError("BlockingParents", err)
return
}
ctx.Data["Participants"] = participants
ctx.Data["NumParticipants"] = len(participants)
ctx.Data["Issue"] = issue

129
routers/web/repo/issue_parent.go

@ -0,0 +1,129 @@
// Copyright 2018-2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
)
// AddParent adds new parents
func AddParent(ctx *context.Context) {
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
ctx.ServerError("GetIssueByIndex", err)
return
}
// Check if the Repo is allowed to have parents
if !ctx.Repo.CanCreateIssueParents(ctx.User, issue.IsPull) {
ctx.Error(http.StatusForbidden, "CanCreateIssueParents")
return
}
parentID := ctx.FormInt64("newParent")
if err = issue.LoadRepo(); err != nil {
ctx.ServerError("LoadRepo", err)
return
}
// Redirect
defer ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther)
// Parent
parent, err := models.GetIssueByID(parentID)
if err != nil {
ctx.Flash.Error(ctx.Tr("repo.issues.parent.add_error_dep_issue_not_exist"))
return
}
// Check if both issues are in the same repo if cross repository parents is not enabled
if issue.RepoID != parent.RepoID && !setting.Service.AllowCrossRepositoryParents {
ctx.Flash.Error(ctx.Tr("repo.issues.parent.add_error_dep_not_same_repo"))
return
}
// Check if issue and parent is the same
if parent.ID == issue.ID {
ctx.Flash.Error(ctx.Tr("repo.issues.parent.add_error_same_issue"))
return
}
err = models.CreateIssueParent(ctx.User, issue, parent)
if err != nil {
if models.IsErrParentExists(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.parent.add_error_dep_exists"))
return
} else if models.IsErrCircularParent(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.parent.add_error_cannot_create_circular"))
return
} else {
ctx.ServerError("CreateOrUpdateIssueParent", err)
return
}
}
}
// RemoveParent removes the parent
func RemoveParent(ctx *context.Context) {
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
ctx.ServerError("GetIssueByIndex", err)
return
}
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueParents(ctx.User, issue.IsPull) {
ctx.Error(http.StatusForbidden, "CanCreateIssueParents")
return
}
parentID := ctx.FormInt64("removeParentID")
if err = issue.LoadRepo(); err != nil {
ctx.ServerError("LoadRepo", err)
return
}
// Parent Type
parentTypeStr := ctx.Req.PostForm.Get("parentType")
var parentType models.ParentType
switch parentTypeStr {
case "father":
parentType = models.ParentTypeFather
case "child":
parentType = models.ParentTypeChild
default:
ctx.Error(http.StatusBadRequest, "GetParentType")
return
}
// Parent
parent, err := models.GetIssueByID(parentID)
if err != nil {
ctx.ServerError("GetIssueByID", err)
return
}
if err = models.RemoveIssueParent(ctx.User, issue, parent, parentType); err != nil {
if models.IsErrParentNotExists(err) {
ctx.Flash.Error(ctx.Tr("repo.issues.parent.add_error_dep_not_exist"))
return
}
ctx.ServerError("RemoveIssueParent", err)
return
}
// Redirect
ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther)
}

2
routers/web/repo/milestone.go

@ -289,7 +289,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
ctx.Data["Title"] = milestone.Name
ctx.Data["Milestone"] = milestone
issues(ctx, milestoneID, 0, util.OptionalBoolNone)
issues(ctx, milestoneID, 0, util.OptionalBoolNone, setting.UI.IssuePagingNum)
ctx.Data["NewIssueChooseTemplate"] = len(ctx.IssueTemplatesFromDefaultBranch()) > 0
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWriteIssuesOrPulls(false)

1
routers/web/repo/setting.go

@ -434,6 +434,7 @@ func SettingsPost(ctx *context.Context) {
EnableTimetracker: form.EnableTimetracker,
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
EnableDependencies: form.EnableIssueDependencies,
EnableParents: form.EnableIssueParents,
},
})
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)

10
routers/web/repo/view.go

@ -697,6 +697,16 @@ func Home(ctx *context.Context) {
return
}
Wiki(ctx)
}
// Code render repository page
func Code(ctx *context.Context) {
checkHomeCodeViewable(ctx)
if ctx.Written() {
return
}
renderCode(ctx)
}

1
routers/web/user/setting/profile.go

@ -119,6 +119,7 @@ func ProfilePost(ctx *context.Context) {
ctx.User.KeepEmailPrivate = form.KeepEmailPrivate
ctx.User.Website = form.Website
ctx.User.Location = form.Location
ctx.User.LocationCoordinate = form.LocationCoordinate
ctx.User.Description = form.Description
ctx.User.KeepActivityPrivate = form.KeepActivityPrivate
ctx.User.Visibility = form.Visibility

18
routers/web/web.go

@ -28,6 +28,7 @@ import (
"code.gitea.io/gitea/routers/web/dev"
"code.gitea.io/gitea/routers/web/events"
"code.gitea.io/gitea/routers/web/explore"
"code.gitea.io/gitea/routers/web/map"
"code.gitea.io/gitea/routers/web/org"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/routers/web/user"
@ -254,6 +255,9 @@ func RegisterRoutes(m *web.Route) {
m.Get("/organizations", explore.Organizations)
m.Get("/code", explore.Code)
}, ignExploreSignIn)
m.Group("/map", func() {
m.Get("/umap", umap.UsersMap)
}, ignExploreSignIn)
m.Get("/issues", reqSignIn, user.Issues)
m.Get("/pulls", reqSignIn, user.Pulls)
m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones)
@ -737,6 +741,10 @@ func RegisterRoutes(m *web.Route) {
m.Post("/add", repo.AddDependency)
m.Post("/delete", repo.RemoveDependency)
})
m.Group("/parent", func() {
m.Post("/add", repo.AddParent)
m.Post("/delete", repo.RemoveParent)
})
m.Combo("/comments").Post(repo.MustAllowUserComment, bindIgnErr(forms.CreateCommentForm{}), repo.NewComment)
m.Group("/times", func() {
m.Post("/add", bindIgnErr(forms.AddTimeManuallyForm{}), repo.AddTimeManually)
@ -877,6 +885,7 @@ func RegisterRoutes(m *web.Route) {
m.Group("/{username}/{reponame}", func() {
m.Group("", func() {
m.Get("/{type:issues|pulls}", repo.Issues)
m.Get("/issues_tree", repo.IssuesTree)
m.Get("/{type:issues|pulls}/{index}", repo.ViewIssue)
m.Group("/{type:issues|pulls}/{index}/content-history", func() {
m.Get("/overview", repo.GetContentHistoryOverview)
@ -1033,11 +1042,11 @@ func RegisterRoutes(m *web.Route) {
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
m.Group("/src", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home)
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Code)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Code)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Code)
// "/*" route is deprecated, and kept for backward compatibility
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Home)
m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Code)
}, repo.SetEditorconfigIfExists)
m.Group("", func() {
@ -1058,6 +1067,7 @@ func RegisterRoutes(m *web.Route) {
m.Group("/{username}", func() {
m.Group("/{reponame}", func() {
m.Get("", repo.SetEditorconfigIfExists, repo.Home)
m.Get("/code", repo.SetEditorconfigIfExists, repo.Code)
}, ignSignIn, context.RepoAssignment, context.RepoRef(), context.UnitTypes())
m.Group("/{reponame}", func() {

2
services/forms/admin.go

@ -41,7 +41,9 @@ type AdminEditUserForm struct {
Email string `binding:"Required;Email;MaxSize(254)"`
Password string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Description string `binding:"MaxSize(1024)"`
Location string `binding:"MaxSize(50)"`
LocationCoordinate string `binding:"MaxSize(255)"`
MaxRepoCreation int
Active bool
Admin bool

3
services/forms/org.go

@ -39,9 +39,10 @@ func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding
type UpdateOrgSettingForm struct {
Name string `binding:"Required;AlphaDashDot;MaxSize(40)" locale:"org.org_name_holder"`
FullName string `binding:"MaxSize(100)"`
Description string `binding:"MaxSize(255)"`
Description string `binding:"MaxSize(1024)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
LocationCoordinate string `binding:"MaxSize(255)"`
Visibility structs.VisibleType
MaxRepoCreation int
RepoAdminChangeTeamAccess bool

3
services/forms/repo_form.go

@ -74,7 +74,7 @@ type MigrateRepoForm struct {
LFS bool `json:"lfs"`
LFSEndpoint string `json:"lfs_endpoint"`
Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"`
Description string `json:"description" binding:"MaxSize(1024)"`
Wiki bool `json:"wiki"`
Milestones bool `json:"milestones"`
Labels bool `json:"labels"`
@ -155,6 +155,7 @@ type RepoSettingForm struct {
EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableIssueDependencies bool
EnableIssueParents bool
IsArchived bool
// Signing Settings

5
services/forms/user_form.go

@ -245,7 +245,8 @@ type UpdateProfileForm struct {
KeepEmailPrivate bool
Website string `binding:"ValidSiteUrl;MaxSize(255)"`
Location string `binding:"MaxSize(50)"`
Description string `binding:"MaxSize(255)"`
LocationCoordinate string `binding:"MaxSize(255)"`
Description string `binding:"MaxSize(1024)"`
Visibility structs.VisibleType
KeepActivityPrivate bool
}
@ -273,7 +274,7 @@ const (
AvatarByMail string = "bymail"
)
// AvatarForm form for changing avatar
// AvatarForm form for changing avDescriptionatar
type AvatarForm struct {
Source string
Avatar *multipart.FileHeader

2
templates/admin/config.tmpl

@ -186,6 +186,8 @@
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_enable_dependencies"}}</dt>
<dd>{{if .Service.DefaultEnableDependencies}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_enable_parents"}}</dt>
<dd>{{if .Service.DefaultEnableParents}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.active_code_lives"}}</dt>
<dd>{{.Service.ActiveCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}</dd>

8
templates/admin/user/edit.tmpl

@ -73,6 +73,10 @@
<input id="password" name="password" type="password" autocomplete="new-password">
<p class="help">{{.i18n.Tr "admin.users.password_helper"}}</p>
</div>
<div class="field {{if .Err_Description}}error{{end}}">
<label for="description">{{$.i18n.Tr "user.user_bio"}}</label>
<textarea id="description" name="description" rows="4" placeholder="{{.i18n.Tr "settings.biography_placeholder"}}">{{.User.Description}}</textarea>
</div>
<div class="field {{if .Err_Website}}error{{end}}">
<label for="website">{{.i18n.Tr "settings.website"}}</label>
<input id="website" name="website" type="url" value="{{.User.Website}}" placeholder="e.g. http://mydomain.com or https://mydomain.com">
@ -81,6 +85,10 @@
<label for="location">{{.i18n.Tr "settings.location"}}</label>
<input id="location" name="location" value="{{.User.Location}}">
</div>
<div class="field {{if .LocationCoordinate}}error{{end}}">
<label for="location_coordinate">{{.i18n.Tr "settings.locationcoordinate"}}</label>
<input id="location_coordinate" name="location_coordinate" value="{{.User.LocationCoordinate}}" placeholder="55.4, 37.3">
</div>
<div class="ui divider"></div>

1
templates/base/head_navbar.tmpl

@ -21,6 +21,7 @@
{{if .ShowMilestonesDashboardPage}}<a class="item {{if .PageIsMilestonesDashboard}}active{{end}}" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>{{end}}
{{end}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a>
<a class="item {{if .PageIsMap}}active{{end}}" href="{{AppSubUrl}}/map/umap">{{.i18n.Tr "map"}}</a>
{{else if .IsLandingPageOrganizations}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore"}}</a>
{{else}}

2
templates/explore/organizations.tmpl

@ -17,7 +17,7 @@
</span>
<div class="description">
{{if .Location}}
{{svg "octicon-location"}} {{.Location}}
{{svg "octicon-location"}} {{.Location}} ({{.LocationCoordinate}})
{{end}}
{{if and .Website}}
{{svg "octicon-link"}}

2
templates/explore/users.tmpl

@ -12,7 +12,7 @@
<span class="header"><a href="{{.HomeLink}}">{{.Name}}</a> {{.FullName}}</span>
<div class="description">
{{if .Location}}
{{svg "octicon-location"}} {{.Location}}
{{svg "octicon-location"}} {{.Location}} ({{.LocationCoordinate}})
{{end}}
{{if and $.ShowUserEmail .Email $.IsSigned (not .KeepEmailPrivate)}}
{{svg "octicon-mail"}}

18
templates/map/navbar.tmpl

@ -0,0 +1,18 @@
<div class="ui secondary pointing tabular top attached borderless stackable menu new-menu navbar">
<a class="{{if .PageIsMapRepositories}}active{{end}} item" href="{{AppSubUrl}}/map/rmap">
{{svg "octicon-repo"}} {{.i18n.Tr "explore.repos"}}
</a>
{{if not .UsersIsDisabled}}
<a class="{{if .PageIsMapUsers}}active{{end}} item" href="{{AppSubUrl}}/map/umap">
{{svg "octicon-person"}} {{.i18n.Tr "explore.users"}}
</a>
{{end}}
<a class="{{if .PageIsMapOrganizations}}active{{end}} item" href="{{AppSubUrl}}/map/orgmap">
{{svg "octicon-organization"}} {{.i18n.Tr "explore.organizations"}}
</a>
{{if .IsRepoIndexerEnabled}}
<a class="{{if .PageIsMapCode}}active{{end}} item" href="{{AppSubUrl}}/map/codemap">
{{svg "octicon-code"}} {{.i18n.Tr "explore.code"}}
</a>
{{end}}
</div>

2862
templates/map/umap.tmpl

File diff suppressed because it is too large Load Diff

2
templates/org/home.tmpl

@ -12,7 +12,7 @@
</div>
{{if $.RenderedDescription}}<p class="render-content markup">{{$.RenderedDescription|Str2html}}</p>{{end}}
<div class="text grey meta">
{{if .Org.Location}}<div class="item">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>{{end}}
{{if .Org.Location}}<div class="item">{{svg "octicon-location"}} <span>{{.Org.Location}}</span> <span>({{.Org.LocationCoordinate}})</span></div>{{end}}
{{if .Org.Website}}<div class="item">{{svg "octicon-link"}} <a target="_blank" rel="noopener noreferrer" href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}}
</div>
</div>

5
templates/org/settings/options.tmpl

@ -36,6 +36,11 @@
<input id="location" name="location" value="{{.Org.Location}}">
</div>
<div class="field">
<label for="location_coordinate">{{.i18n.Tr "org.settings.locationcoordinate"}}</label>
<input id="location_coordinate" name="location_coordinate" value="{{.Org.LocationCoordinate}}" placeholder="55.4, 37.3">
</div>
<div class="ui divider"></div>
<div class="field" id="visibility_box">
<label for="visibility">{{.i18n.Tr "org.settings.visibility"}}</label>

39
templates/repo/header.tmpl

@ -2,7 +2,7 @@
{{with .Repository}}
<div class="ui container">
<div class="repo-header">
<div class="repo-title-wrap df fc">
<div id="repo-title-in-header" class="repo-title-wrap df fc">
<div class="repo-title">
{{$avatar := (repoAvatar . 32 "mr-3")}}
{{if $avatar}}
@ -147,9 +147,9 @@
<div class="ui tabs container">
{{if not (or .Repository.IsBeingCreated .Repository.IsBroken)}}
<div class="ui tabular stackable menu navbar">
{{if .Permission.CanRead $.UnitTypeCode}}
<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL}}{{end}}">
{{svg "octicon-code"}} {{.i18n.Tr "repo.code"}}
{{if or (.Permission.CanRead $.UnitTypeWiki) (.Permission.CanRead $.UnitTypeExternalWiki)}}
<a class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki" {{if (.Permission.CanRead $.UnitTypeExternalWiki)}} target="_blank" rel="noopener noreferrer" {{end}}>
{{svg "octicon-book"}} {{.i18n.Tr "repo.wiki"}}
</a>
{{end}}
@ -168,15 +168,6 @@
</a>
{{end}}
{{if and .Repository.CanEnablePulls (.Permission.CanRead $.UnitTypePullRequests)}}
<a class="{{if .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls">
{{svg "octicon-git-pull-request"}} {{.i18n.Tr "repo.pulls"}}
{{if .Repository.NumOpenPulls}}
<span class="ui blue small label">{{CountFmt .Repository.NumOpenPulls}}</span>
{{end}}
</a>
{{end}}
{{ if and (not .UnitProjectsGlobalDisabled) (.Permission.CanRead $.UnitTypeProjects)}}
<a href="{{.RepoLink}}/projects" class="{{ if .IsProjectsPage }}active{{end}} item">
{{svg "octicon-project"}} {{.i18n.Tr "repo.project_board"}}
@ -186,6 +177,22 @@
</a>
{{ end }}
{{if .Permission.CanRead $.UnitTypeCode}}
<a class="{{if .PageIsViewCode}}active{{end}} item" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL}}{{else}}/code{{end}}">
{{svg "octicon-code"}} {{.i18n.Tr "repo.code"}}
</a>
{{end}}
{{if and .Repository.CanEnablePulls (.Permission.CanRead $.UnitTypePullRequests)}}
<a class="{{if .PageIsPullList}}active{{end}} item" href="{{.RepoLink}}/pulls">
{{svg "octicon-git-pull-request"}} {{.i18n.Tr "repo.pulls"}}
{{if .Repository.NumOpenPulls}}
<span class="ui blue small label">{{CountFmt .Repository.NumOpenPulls}}</span>
{{end}}
</a>
{{end}}
{{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsEmptyRepo) }}
<a class="{{if .PageIsReleaseList}}active{{end}} item" href="{{.RepoLink}}/releases">
{{svg "octicon-tag"}} {{.i18n.Tr "repo.releases"}}
@ -195,12 +202,6 @@
</a>
{{end}}
{{if or (.Permission.CanRead $.UnitTypeWiki) (.Permission.CanRead $.UnitTypeExternalWiki)}}
<a class="{{if .PageIsWiki}}active{{end}} item" href="{{.RepoLink}}/wiki" {{if (.Permission.CanRead $.UnitTypeExternalWiki)}} target="_blank" rel="noopener noreferrer" {{end}}>
{{svg "octicon-book"}} {{.i18n.Tr "repo.wiki"}}
</a>
{{end}}
{{if and (.Permission.CanReadAny $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsEmptyRepo)}}
<a class="{{if .PageIsActivity}}active{{end}} item" href="{{.RepoLink}}/activity">
{{svg "octicon-pulse"}} {{.i18n.Tr "repo.activity"}}

89
templates/repo/issue/issue_actions.tmpl

@ -0,0 +1,89 @@
<div id="issue-actions" class="ui stackable grid hide">
<div class="six wide column">
{{template "repo/issue/openclose" .}}
</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">{{.i18n.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">{{.i18n.Tr "repo.issues.action_close"}}</div>
{{end}}
<!-- Labels -->
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.Tr "repo.issues.action_label"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
{{range .Labels}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
{{if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
</div>
{{end}}
</div>
</div>
<!-- Milestone -->
<div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.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">
{{.i18n.Tr "repo.issues.action_milestone_no_select"}}
</div>
{{range .Milestones}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/milestone">
{{.Name}}
</div>
{{end}}
</div>
</div>
<!-- Projects -->
<div class="ui {{if not .Projects}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.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">
{{.i18n.Tr "repo.issues.new.no_projects"}}
</div>
{{range .Projects}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
{{.Title}}
</div>
{{end}}
</div>
</div>
<!-- Assignees -->
<div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.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">
{{.i18n.Tr "repo.issues.action_assignee_no_select"}}
</div>
{{range .Assignees}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/assignee">
{{avatar .}} {{.GetDisplayName}}
</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
</div>

90
templates/repo/issue/issue_filters.tmpl

@ -0,0 +1,90 @@
<div id="issue-filters" class="ui stackable grid">
<div class="six wide column">
{{template "repo/issue/openclose" .}}
</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">
{{.i18n.Tr "repo.issues.filter_label"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<span class="info">{{.i18n.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a>
{{range .Labels}}
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}</a>
{{end}}
</div>
</div>
<!-- Milestone -->
<div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_milestone"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_milestone_no_select"}}</a>
{{range .Milestones}}
<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}}&assignee={{$.AssigneeID}}">{{.Name}}</a>
{{end}}
</div>
</div>
<!-- Assignee -->
<div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_assignee"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}">{{.i18n.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}}&assignee={{.ID}}">
{{avatar .}} {{.GetDisplayName}}
</a>
{{end}}
</div>
</div>
{{if .IsSigned}}
<!-- Type -->
<div class="ui dropdown type jump item">
<span class="text">
{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.review_requested"}}</a>
{{end}}
</div>
</div>
{{end}}
<!-- Sort -->
<div class="ui dropdown type jump item">
<span class="text">
{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.farduedate"}}</a>
</div>
</div>
</div>
</div>
</div>

204
templates/repo/issue/list.tmpl

@ -2,209 +2,11 @@
<div class="page-content repository">
{{template "repo/header" .}}
<div class="ui container">
<div class="ui three column stackable grid">
<div class="column">
{{template "repo/issue/navbar" .}}
</div>
<div class="column center aligned">
{{template "repo/issue/search" .}}
</div>
{{if not .Repository.IsArchived}}
<div class="column right aligned">
{{if .PageIsIssueList}}
<a class="ui green button" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{.i18n.Tr "repo.issues.new"}}</a>
{{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}}">{{.i18n.Tr "repo.pulls.new"}}</a>
{{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}}">{{$.i18n.Tr "action.compare_commits_general"}}</a>
</div>
{{end}}
{{end}}
</div>
{{template "repo/issue/nav_search_and_new" .}}
<div class="ui divider"></div>
<div id="issue-filters" class="ui stackable grid">
<div class="six wide column">
{{template "repo/issue/openclose" .}}
</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">
{{.i18n.Tr "repo.issues.filter_label"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<span class="info">{{.i18n.Tr "repo.issues.filter_label_exclude" | Safe}}</span>
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_label_no_select"}}</a>
{{range .Labels}}
<a class="item label-filter-item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.QueryString}}&milestone={{$.MilestoneID}}&assignee={{$.AssigneeID}}" data-label-id="{{.ID}}">{{if .IsExcluded}}{{svg "octicon-circle-slash"}}{{else if .IsSelected}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}</a>
{{end}}
</div>
</div>
<!-- Milestone -->
<div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_milestone"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_milestone_no_select"}}</a>
{{range .Milestones}}
<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}}&assignee={{$.AssigneeID}}">{{.Name}}</a>
{{end}}
</div>
</div>
<!-- Assignee -->
<div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.Tr "repo.issues.filter_assignee"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
<a class="item" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{.SelectLabels}}&milestone={{$.MilestoneID}}">{{.i18n.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}}&assignee={{.ID}}">
{{avatar .}} {{.GetDisplayName}}
</a>
{{end}}
</div>
</div>
{{if .IsSigned}}
<!-- Type -->
<div class="ui dropdown type jump item">
<span class="text">
{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_type.review_requested"}}</a>
{{end}}
</div>
</div>
{{end}}
<!-- Sort -->
<div class="ui dropdown type jump item">
<span class="text">
{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.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}}&assignee={{$.AssigneeID}}">{{.i18n.Tr "repo.issues.filter_sort.farduedate"}}</a>
</div>
</div>
</div>
</div>
</div>
<div id="issue-actions" class="ui stackable grid hide">
<div class="six wide column">
{{template "repo/issue/openclose" .}}
</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">{{.i18n.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">{{.i18n.Tr "repo.issues.action_close"}}</div>
{{end}}
<!-- Labels -->
<div class="ui {{if not .Labels}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.Tr "repo.issues.action_label"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span>
<div class="menu">
{{range .Labels}}
<div class="item issue-action" data-action="toggle" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/labels">
{{if contain $.SelLabelIDs .ID}}{{svg "octicon-check"}}{{end}}<span class="label color" style="background-color: {{.Color}}"></span> {{.Name | RenderEmoji}}
</div>
{{end}}
</div>
</div>
<!-- Milestone -->
<div class="ui {{if not .Milestones}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.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">
{{.i18n.Tr "repo.issues.action_milestone_no_select"}}
</div>
{{range .Milestones}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/milestone">
{{.Name}}
</div>
{{end}}
</div>
</div>
<!-- Projects -->
<div class="ui {{if not .Projects}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.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">
{{.i18n.Tr "repo.issues.new.no_projects"}}
</div>
{{range .Projects}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
{{.Title}}
</div>
{{end}}
</div>
</div>
{{template "repo/issue/issue_filters" .}}
{{template "repo/issue/issue_actions" .}}
<!-- Assignees -->
<div class="ui {{if not .Assignees}}disabled{{end}} dropdown jump item">
<span class="text">
{{.i18n.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">
{{.i18n.Tr "repo.issues.action_assignee_no_select"}}
</div>
{{range .Assignees}}
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/assignee">
{{avatar .}} {{.GetDisplayName}}
</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
</div>
{{template "shared/issuelist" mergeinto . "listType" "repo"}}
</div>
</div>

23
templates/repo/issue/nav_search_and_new.tmpl

@ -0,0 +1,23 @@
<div class="ui three column stackable grid">
<div class="column">
{{template "repo/issue/navbar" .}}
</div>
<div class="column center aligned">
{{template "repo/issue/search" .}}
</div>
{{if not .Repository.IsArchived}}
<div class="column right aligned">
{{if .PageIsIssueList}}
<a class="ui green button" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{.i18n.Tr "repo.issues.new"}}</a>
{{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}}">{{.i18n.Tr "repo.pulls.new"}}</a>
{{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}}">{{$.i18n.Tr "action.compare_commits_general"}}</a>
</div>
{{end}}
{{end}}
</div>

1
templates/repo/issue/navbar.tmpl

@ -1,4 +1,5 @@
<div class="ui compact left small menu">
<a class="{{if .PageIsLabels}}active{{end}} item" href="{{.RepoLink}}/labels">{{.i18n.Tr "repo.labels"}}</a>
<a class="{{if .PageIsMilestones}}active{{end}} item" href="{{.RepoLink}}/milestones">{{.i18n.Tr "repo.milestones"}}</a>
<a class="{{if .PageIsIssuesTree}}active{{end}} item" href="{{.RepoLink}}/issues_tree">{{.i18n.Tr "repo.issues_tree"}}</a>
</div>

20
templates/repo/issue/treant_config.tmpl

@ -0,0 +1,20 @@
<style>
.chart {
height: 600px;
width: 100%;
border: 3px solid #DDD;
border-radius: 3px;
}
.comment {
display: inline-block;
width: 360px;
}
.comment { padding: 3px; border: 1px solid #ddd; border-radius: 3px; }
</style>
<link rel="stylesheet" href="https://fperucic.github.io/treant-js/Treant.css">
<script src="https://fperucic.github.io/treant-js/vendor/raphael.js"></script>
<script src="https://fperucic.github.io/treant-js/Treant.js"></script>

84
templates/repo/issue/tree.tmpl

@ -0,0 +1,84 @@
{{template "base/head" .}}
<div class="page-content repository">
{{template "repo/header" .}}
<div class="ui container">
{{template "repo/issue/nav_search_and_new" .}}
<div class="ui divider"></div>
{{template "repo/issue/issue_filters" .}}
{{template "repo/issue/issue_actions" .}}
<div hidden>
{{template "shared/issuelistfortree" mergeinto . "listType" "repo"}}
<div id="repo_title" class="comment"></div>
</div>
<script>
var source = document.getElementById("repo-title-in-header"),
destination = document.getElementById("repo_title");
destination.innerHTML = source.innerHTML
var task_post = [];
var config = {
container: "#TaskTreeChart",
levelSeparation: 60,
siblingSeparation: 60,
nodeAlign: "BOTTOM",
connectors: {
type: "step",
style: {
"stroke-width": 4,
"stroke": "#ccc",
"stroke-dasharray": "-", //"", "-", ".", "-.", "-..", ". ", "- ", "--", "- .", "--.", "--.."
"arrow-end": "classic-wide-long"
}
},
},
task_post0 = {
innerHTML: "#repo_title"
};
{{range .Issues}}
task_post[{{.ID}}] = {
parent: task_post0,
innerHTML: "#task{{.ID}}"
};
{{end}}
{{range .Issues}}
{{if .BlockedByParents}}
{{ $cur_id := .ID}}
{{ $cur_repo_id := .RepoID}}
{{range .BlockedByParents}}
{{if eq $cur_repo_id .Issue.RepoID}}
if( task_post[{{.Issue.ID}}] )
{
task_post[{{$cur_id}}].parent = task_post[{{.Issue.ID}}];
}
{{end}}
{{end}}
{{end}}
{{end}}
tree_structure = [
config, task_post0,
{{range .Issues}}
task_post[{{.ID}}],
{{end}}
];
</script>
<div class="chart Treant loaded" id="TaskTreeChart"></div>
{{template "repo/issue/treant_config" .}}
<script>
new Treant( tree_structure );
</script>
</div>
</div>
{{template "base/footer" .}}

50
templates/repo/issue/view_content/comments.tmpl

@ -831,5 +831,55 @@
{{end}}
</span>
</div>
{{else if eq .Type 34}}
<div class="timeline-item event" id="{{.HashTag}}">
<span class="badge">{{svg "octicon-package-parents"}}</span>
<a href="{{.Poster.HomeLink}}">
{{avatar .Poster}}
</a>
<span class="text grey">
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
{{$.i18n.Tr "repo.issues.parent.added_parent" $createdStr | Safe}}
</span>
{{if .ParentIssue}}
<div class="detail">
{{svg "octicon-plus"}}
<span class="text grey">
<a href="{{.ParentIssue.HTMLURL}}">
{{if eq .ParentIssue.RepoID .Issue.RepoID}}
#{{.ParentIssue.Index}} {{.ParentIssue.Title}}
{{else}}
{{.ParentIssue.Repo.FullName}}#{{.ParentIssue.Index}} - {{.ParentIssue.Title}}
{{end}}
</a>
</span>
</div>
{{end}}
</div>
{{else if eq .Type 35}}
<div class="timeline-item event" id="{{.HashTag}}">
<span class="badge">{{svg "octicon-package-parents"}}</span>
<a href="{{.Poster.HomeLink}}">
{{avatar .Poster}}
</a>
<span class="text grey">
<a class="author" href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a>
{{$.i18n.Tr "repo.issues.parent.removed_parent" $createdStr | Safe}}
</span>
{{if .ParentIssue}}
<div class="detail">
<span class="text grey">{{svg "octicon-trash"}}</span>
<span class="text grey">
<a href="{{.ParentIssue.HTMLURL}}">
{{if eq .ParentIssue.RepoID .Issue.RepoID}}
#{{.ParentIssue.Index}} {{.ParentIssue.Title}}
{{else}}
{{.ParentIssue.Repo.FullName}}#{{.ParentIssue.Index}} - {{.ParentIssue.Title}}
{{end}}
</a>
</span>
</div>
{{end}}
</div>
{{end}}
{{end}}

125
templates/repo/issue/view_content/sidebar.tmpl

@ -444,6 +444,131 @@
{{end}}
</div>
{{if .Repository.IsParentsEnabled}}
<div class="ui divider"></div>
<div class="ui depending">
{{if (and (not .BlockedByParents) (not .BlockingParents))}}
<span class="text"><strong>{{.i18n.Tr "repo.issues.parent.title"}}</strong></span>
<br>
<p>
{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.parent.pr_no_parents"}}
{{else}}
{{.i18n.Tr "repo.issues.parent.issue_no_parents"}}
{{end}}
</p>
{{end}}
{{if .BlockingParents}}
<span class="text tooltip" data-content="{{if .Issue.IsPull}}{{.i18n.Tr "repo.issues.parent.pr_close_blocks"}}{{else}}{{.i18n.Tr "repo.issues.parent.issue_close_blocks"}}{{end}}">
<strong>{{.i18n.Tr "repo.issues.parent.blocks_short"}}</strong>
</span>
<div class="ui relaxed divided list">
{{range .BlockingParents}}
<div class="item parent{{if .Issue.IsClosed}} is-closed{{end}} df ac sb">
<div class="item-left df jc fc f1">
<a class="title" href="{{.Issue.Link}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji}}
</a>
<div class="text small">
{{.Repository.OwnerName}}/{{.Repository.Name}}
</div>
</div>
<div class="item-right df ac">
{{if and $.CanCreateIssueParents (not $.Repository.IsArchived)}}
<a class="delete-parent-button tooltip ci muted" data-id="{{.Issue.ID}}" data-type="child" data-content="{{$.i18n.Tr "repo.issues.parent.remove_info"}}" data-inverted="">
{{svg "octicon-trash" 16}}
</a>
{{end}}
</div>
</div>
{{end}}
</div>
{{end}}
{{if .BlockedByParents}}
<span class="text tooltip" data-content="{{if .Issue.IsPull}}{{.i18n.Tr "repo.issues.parent.pr_closing_blockedby"}}{{else}}{{.i18n.Tr "repo.issues.parent.issue_closing_blockedby"}}{{end}}">
<strong>{{.i18n.Tr "repo.issues.parent.blocked_by_short"}}</strong>
</span>
<div class="ui relaxed divided list">
{{range .BlockedByParents}}
<div class="item parent{{if .Issue.IsClosed}} is-closed{{end}} df ac sb">
<div class="item-left df jc fc f1">
<a class="title" href="{{.Issue.Link}}">
#{{.Issue.Index}} {{.Issue.Title | RenderEmoji}}
</a>
<div class="text small">
{{.Repository.OwnerName}}/{{.Repository.Name}}
</div>
</div>
<div class="item-right df ac">
{{if and $.CanCreateIssueParents (not $.Repository.IsArchived)}}
<a class="delete-parent-button tooltip ci muted" data-id="{{.Issue.ID}}" data-type="father" data-content="{{$.i18n.Tr "repo.issues.parent.remove_info"}}" data-inverted="">
{{svg "octicon-trash" 16}}
</a>
{{end}}
</div>
</div>
{{end}}
</div>
{{end}}
{{if and .CanCreateIssueParents (not .Repository.IsArchived)}}
<div>
<form method="POST" action="{{.Issue.Link}}/parent/add" id="addParentForm">
{{$.CsrfTokenHtml}}
<div class="ui fluid action input">
<div class="ui search selection dropdown" id="new-parent-drop-list" data-issue-id="{{.Issue.ID}}">
<input name="newParent" type="hidden">
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<input type="text" class="search">
<div class="default text">{{.i18n.Tr "repo.issues.parent.add"}}</div>
</div>
<button class="ui green icon button">
{{svg "octicon-plus"}}
</button>
</div>
</form>
</div>
{{end}}
</div>
{{if and .CanCreateIssueParents (not .Repository.IsArchived)}}
<input type="hidden" id="crossRepoSearch" value="{{.AllowCrossRepositoryParents}}">
<div class="ui basic modal remove-parent">
<div class="ui icon header">
{{svg "octicon-trash"}}
{{.i18n.Tr "repo.issues.parent.remove_header"}}
</div>
<div class="content">
<form method="POST" action="{{.Issue.Link}}/parent/delete" id="removeParentForm">
{{$.CsrfTokenHtml}}
<input type="hidden" value="" name="removeParentID" id="removeParentID"/>
<input type="hidden" value="" name="parentType" id="parentType"/>
</form>
<p>{{if .Issue.IsPull}}
{{.i18n.Tr "repo.issues.parent.pr_remove_text"}}
{{else}}
{{.i18n.Tr "repo.issues.parent.issue_remove_text"}}
{{end}}</p>
</div>
<div class="actions">
<div class="ui red cancel inverted button">
{{svg "octicon-x"}}
{{.i18n.Tr "repo.issues.parent.cancel"}}
</div>
<div class="ui green ok inverted button">
{{svg "octicon-check"}}
{{.i18n.Tr "repo.issues.parent.remove"}}
</div>
</div>
</div>
{{end}}
{{end}}
{{if .Repository.IsDependenciesEnabled}}
<div class="ui divider"></div>

6
templates/repo/settings/options.tmpl

@ -329,6 +329,12 @@
<label>{{.i18n.Tr "repo.issues.dependency.setting"}}</label>
</div>
</div>
<div class="field">
<div class="ui checkbox">
<input name="enable_issue_parents" type="checkbox" {{if (.Repository.IsParentsEnabled)}}checked{{end}}>
<label>{{.i18n.Tr "repo.issues.parent.setting"}}</label>
</div>
</div>
<div class="ui checkbox">
<input name="enable_close_issues_via_commit_in_any_branch" type="checkbox" {{ if .Repository.CloseIssuesViaCommitInAnyBranch }}checked{{end}}>
<label>{{.i18n.Tr "repo.settings.admin_enable_close_issues_via_commit_in_any_branch"}}</label>

2
templates/repo/user_cards.tmpl

@ -16,7 +16,7 @@
{{if .Website}}
{{svg "octicon-link"}} <a href="{{.Website}}" target="_blank" rel="noopener noreferrer">{{.Website}}</a>
{{else if .Location}}
{{svg "octicon-location"}} {{.Location}}
{{svg "octicon-location"}} {{.Location}} ({{.LocationCoordinate}})
{{else}}
{{svg "octicon-clock"}} {{$.i18n.Tr "user.join_on"}} {{.CreatedUnix.FormatShort}}
{{end}}

144
templates/shared/issuelistfortree.tmpl

@ -0,0 +1,144 @@
<div class="issue list">
{{ $approvalCounts := .ApprovalCounts}}
{{range .Issues}}
<div id="task{{.ID}}" class="issue list comment">
<li class="item df py-3">
<div class="issue-item-left df">
{{if $.CanWriteIssuesOrPulls}}
<div class="ui checkbox issue-checkbox">
<input type="checkbox" data-issue-id={{.ID}}></input>
<label></label>
</div>
{{end}}
<div class="issue-item-icon">
{{if .IsPull}}
{{if .PullRequest.HasMerged}}
{{svg "octicon-git-merge" 16 "text purple"}}
{{else}}
{{if .IsClosed}}
{{svg "octicon-git-pull-request" 16 "text red"}}
{{else}}
{{svg "octicon-git-pull-request" 16 "text green"}}
{{end}}
{{end}}
{{else}}
{{if .IsClosed}}
{{svg "octicon-issue-closed" 16 "text red"}}
{{else}}
{{svg "octicon-issue-opened" 16 "text green"}}
{{end}}
{{end}}
</div>
</div>
<div class="issue-item-main f1 fc df">
<div class="issue-item-top-row">
<a class="title tdn" href="{{if .HTMLURL}}{{.HTMLURL}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji .Title}}</a>
{{if .IsPull}}
{{if (index $.CommitStatus .PullRequest.ID)}}
{{template "repo/commit_status" (index $.CommitStatus .PullRequest.ID)}}
{{end}}
{{end}}
<span class="labels-list ml-2">
{{range .Labels}}
<a class="ui label" href="{{$.Link}}?q={{$.Keyword}}&type={{$.ViewType}}&state={{$.State}}&labels={{.ID}}{{if ne $.listType "milestone"}}&milestone={{$.MilestoneID}}{{end}}&assignee={{$.AssigneeID}}" style="color: {{.ForegroundColor}}; background-color: {{.Color}}" title="{{.Description | RenderEmojiPlain}}">{{.Name | RenderEmoji}}</a>
{{end}}
</span>
</div>
<div class="desc issue-item-bottom-row df ac fw my-1">
<a class="index ml-0 mr-2" href="{{if .HTMLURL}}{{.HTMLURL}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{if eq $.listType "dashboard"}}
{{.Repo.FullName}}#{{.Index}}
{{else}}
#{{.Index}}
{{end}}
</a>
{{ $timeStr := TimeSinceUnix .GetLastEventTimestamp $.Lang }}
{{if .OriginalAuthor }}
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.OriginalAuthor|Escape) | Safe}}
{{else if gt .Poster.ID 0}}
{{$.i18n.Tr .GetLastEventLabel $timeStr (.Poster.HomeLink|Escape) (.Poster.GetDisplayName | Escape) | Safe}}
{{else}}
{{$.i18n.Tr .GetLastEventLabelFake $timeStr (.Poster.GetDisplayName | Escape) | Safe}}
{{end}}
{{if and .Milestone (ne $.listType "milestone")}}
<a class="milestone" {{if $.RepoLink}}href="{{$.RepoLink}}/milestone/{{.Milestone.ID}}"{{else}}href="{{.Repo.Link}}/milestone/{{.Milestone.ID}}"{{end}}>
{{svg "octicon-milestone" 14 "mr-2"}}{{.Milestone.Name}}
</a>
{{end}}
{{if .Ref}}
<a class="ref" {{if $.RepoLink}}href="{{index $.IssueRefURLs .ID}}"{{else}}href="{{.Repo.Link}}{{index $.IssueRefURLs .ID}}"{{end}}>
{{svg "octicon-git-branch" 14 "mr-2"}}{{index $.IssueRefEndNames .ID}}
</a>
{{end}}
{{$tasks := .GetTasks}}
{{if gt $tasks 0}}
{{$tasksDone := .GetTasksDone}}
<span class="checklist">
{{svg "octicon-checklist" 14 "mr-2"}}{{$tasksDone}} / {{$tasks}} <span class="progress-bar"><span class="progress" style="width:calc(100% * {{$tasksDone}} / {{$tasks}});"></span></span>
</span>
{{end}}
{{if ne .DeadlineUnix 0}}
<span class="due-date tooltip" data-content="{{$.i18n.Tr "repo.issues.due_date"}}" data-position="right center">
<span{{if .IsOverdue}} class="overdue"{{end}}>
{{svg "octicon-calendar" 14 "mr-2"}}
{{.DeadlineUnix.FormatShort}}
</span>
</span>
{{end}}
{{if .IsPull}}
{{$approveOfficial := call $approvalCounts .ID "approve"}}
{{$rejectOfficial := call $approvalCounts .ID "reject"}}
{{$waitingOfficial := call $approvalCounts .ID "waiting"}}
{{if gt $approveOfficial 0}}
<span class="approvals df ac">
{{svg "octicon-check" 14 "mr-1"}}
{{$.i18n.TrN $approveOfficial "repo.pulls.approve_count_1" "repo.pulls.approve_count_n" $approveOfficial}}
</span>
{{end}}
{{if gt $rejectOfficial 0}}
<span class="rejects df ac">
{{svg "octicon-diff" 14 "mr-2"}}
{{$.i18n.TrN $rejectOfficial "repo.pulls.reject_count_1" "repo.pulls.reject_count_n" $rejectOfficial}}
</span>
{{end}}
{{if gt $waitingOfficial 0}}
<span class="waiting df ac">
{{svg "octicon-eye" 14 "mr-2"}}
{{$.i18n.TrN $waitingOfficial "repo.pulls.waiting_count_1" "repo.pulls.waiting_count_n" $waitingOfficial}}
</span>
{{end}}
{{if and (not .PullRequest.HasMerged) (gt (len .PullRequest.ConflictedFiles) 0)}}
<span class="conflicting df ac">
{{svg "octicon-x" 14}}
{{$.i18n.TrN (len .PullRequest.ConflictedFiles) "repo.pulls.num_conflicting_files_1" "repo.pulls.num_conflicting_files_n" (len .PullRequest.ConflictedFiles)}}
</span>
{{end}}
{{end}}
</div>
</div>
<div class="issue-item-icons-right df p-2">
<div class="issue-item-icon-right text grey">
{{if .TotalTrackedTime}}
{{svg "octicon-clock" 16 "mr-2"}}
{{.TotalTrackedTime | Sec2Time}}
{{end}}
</div>
<div class="issue-item-icon-right text grey">
{{range .Assignees}}
<a class="ui assignee tooltip tdn" href="{{.HomeLink}}" data-content="{{.GetDisplayName}}" data-position="left center">
{{avatar .}}
</a>
{{end}}
</div>
<div class="issue-item-icon-right text grey">
{{if .NumComments}}
<a class="tdn" href="{{if .HTMLURL}}{{.HTMLURL}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
{{svg "octicon-comment" 16 "mr-2"}}{{.NumComments}}
</a>
{{end}}
</div>
</div>
</li>
</div>
{{end}}
</div>

5
templates/swagger/v1_json.tmpl

@ -15592,6 +15592,11 @@
"type": "boolean",
"x-go-name": "EnableIssueDependencies"
},
"enable_issue_parents": {
"description": "Enable parents for issues and pull requests (Built-in issue tracker)",
"type": "boolean",
"x-go-name": "EnableIssueParents"
},
"enable_time_tracker": {
"description": "Enable time tracking (Built-in issue tracker)",
"type": "boolean",

2
templates/user/profile.tmpl

@ -20,7 +20,7 @@
<div class="extra content word-break">
<ul>
{{if .Owner.Location}}
<li>{{svg "octicon-location"}} {{.Owner.Location}}</li>
<li>{{svg "octicon-location"}} {{.Owner.Location}} ({{.Owner.LocationCoordinate}})</li>
{{end}}
{{if .ShowUserEmail }}
<li>

4
templates/user/settings/profile.tmpl

@ -46,6 +46,10 @@
<label for="location">{{.i18n.Tr "settings.location"}}</label>
<input id="location" name="location" value="{{.SignedUser.Location}}">
</div>
<div class="field">
<label for="location_coordinate">{{.i18n.Tr "settings.locationcoordinate"}}</label>
<input id="location_coordinate" name="location_coordinate" value="{{.SignedUser.LocationCoordinate}}" placeholder="55.4, 37.3">
</div>
<div class="ui divider"></div>
<!-- private block -->

46
web_src/js/features/repo-issue.js

@ -121,6 +121,34 @@ export function initRepoIssueList() {
fullTextSearch: true,
});
$('#new-parent-drop-list')
.dropdown({
apiSettings: {
url: issueSearchUrl,
onResponse(response) {
const filteredResponse = {success: true, results: []};
const currIssueId = $('#new-parent-drop-list').data('issue-id');
// Parse the response from the api to work with our dropdown
$.each(response, (_i, issue) => {
// Don't list current issue in the parent list.
if (issue.id === currIssueId) {
return;
}
filteredResponse.results.push({
name: `#${issue.number} ${htmlEscape(issue.title)
}<div class="text small dont-break-out">${htmlEscape(issue.repository.full_name)}</div>`,
value: issue.id,
});
});
return filteredResponse;
},
cache: false,
},
fullTextSearch: true,
});
function excludeLabel(item) {
const href = $(item).attr('href');
const id = $(item).data('label-id');
@ -196,6 +224,24 @@ export function initRepoIssueDependencyDelete() {
});
}
export function initRepoIssueParentDelete() {
// Delete Issue parent
$(document).on('click', '.delete-parent-button', (e) => {
const id = e.currentTarget.getAttribute('data-id');
const type = e.currentTarget.getAttribute('data-type');
$('.remove-parent').modal({
closable: false,
duration: 200,
onApprove: () => {
$('#removeParentID').val(id);
$('#parentType').val(type);
$('#removeParentForm').trigger('submit');
},
}).modal('show');
});
}
export function initRepoIssueCodeCommentCancel() {
// Cancel inline code comment
$(document).on('click', '.cancel-code-comment', (e) => {

3
web_src/js/features/repo-legacy.js

@ -4,7 +4,7 @@ import {initCompImagePaste, initEasyMDEImagePaste} from './comp/ImagePaste.js';
import {
initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel,
initRepoIssueCommentDelete,
initRepoIssueComments, initRepoIssueDependencyDelete,
initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueParentDelete,
initRepoIssueReferenceIssue, initRepoIssueStatusButton,
initRepoIssueTitleEdit,
initRepoIssueWipToggle, initRepoPullRequestMerge, initRepoPullRequestUpdate,
@ -516,6 +516,7 @@ export function initRepository() {
initRepoIssueCommentDelete();
initRepoIssueDependencyDelete();
initRepoIssueParentDelete();
initRepoIssueCodeCommentCancel();
initRepoIssueStatusButton();
initRepoPullRequestMerge();

15
web_src/less/_repository.less

@ -2929,6 +2929,21 @@ tbody.commit-list {
}
}
#new-parent-drop-list {
&.ui.selection.dropdown {
min-width: 0;
width: 100%;
border-radius: 4px 0 0 4px;
border-right: 0;
white-space: nowrap;
}
.text {
width: 100%;
overflow: hidden;
}
}
#manage_topic {
font-size: 12px;
}

Loading…
Cancel
Save