From b8c89c23eb5bcf05236ea594e77ad5ddf14a6370 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Fri, 21 Jun 2019 18:58:01 +0100 Subject: [PATCH 001/216] start working on Kanban feature --- custom/conf/app.ini.sample | 2 ++ modules/setting/setting.go | 1 + 2 files changed, 3 insertions(+) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 991a2a3e6b4b1..a78661ffef0c9 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -300,6 +300,8 @@ MAX_FILE_SIZE = 1048576 [admin] ; Disallow regular (non-admin) users from creating organizations. DISABLE_REGULAR_ORG_CREATION = false +; Enable the kanban board feature system wide. +ENABLE_KANBAN_BOARD = true [security] ; Whether the installer is disabled diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 97bdc03cc9a56..cd07fc1febfcc 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -240,6 +240,7 @@ var ( // Admin settings Admin struct { DisableRegularOrgCreation bool + EnableKanbanBoard bool } // Picture settings From d5ef33762bfdd990abc347e194d0faa1c2b7ca71 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Fri, 21 Jun 2019 19:46:08 +0100 Subject: [PATCH 002/216] initial projects page and support projects unit --- models/migrations/migrations.go | 4 ++++ models/migrations/v90.go | 18 ++++++++++++++ models/migrations/v91.go | 27 +++++++++++++++++++++ models/projects.go | 39 +++++++++++++++++++++++++++++++ models/repo.go | 4 ++++ models/repo_unit.go | 2 +- models/unit.go | 14 +++++++++++ modules/context/repo.go | 1 + options/locale/locale_en-US.ini | 2 ++ routers/repo/projects.go | 37 +++++++++++++++++++++++++++++ routers/routes/routes.go | 8 +++++++ templates/repo/header.tmpl | 9 +++++++ templates/repo/projects/list.tmpl | 21 +++++++++++++++++ 13 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 models/migrations/v90.go create mode 100644 models/migrations/v91.go create mode 100644 models/projects.go create mode 100644 routers/repo/projects.go create mode 100644 templates/repo/projects/list.tmpl diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 62fadf5f361d0..3b7ebed2187ca 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -234,6 +234,10 @@ var migrations = []Migration{ NewMigration("add commit status context field to commit_status", addCommitStatusContext), // v89 -> v90 NewMigration("add original author/url migration info to issues, comments, and repo ", addOriginalMigrationInfo), + // v90 -> v91 + NewMigration("add projects info to database", addProjectsInfo), + // v91 -> v92 + NewMigration("add projects database table", addProjectsTable), } // Migrate database to current version diff --git a/models/migrations/v90.go b/models/migrations/v90.go new file mode 100644 index 0000000000000..f205ba59ef996 --- /dev/null +++ b/models/migrations/v90.go @@ -0,0 +1,18 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import "github.com/go-xorm/xorm" + +func addProjectsInfo(x *xorm.Engine) error { + + type Repository struct { + NumProjects int `xorm:"NOT NULL DEFAULT 0"` + NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"` + NumOpenProjects int `xorm:"-"` + } + + return x.Sync2(new(Repository)) +} diff --git a/models/migrations/v91.go b/models/migrations/v91.go new file mode 100644 index 0000000000000..8b2c02472f8b9 --- /dev/null +++ b/models/migrations/v91.go @@ -0,0 +1,27 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "code.gitea.io/gitea/modules/util" + + "github.com/go-xorm/xorm" +) + +func addProjectsTable(x *xorm.Engine) error { + + type Project struct { + ID int64 `xorm:"pk autoincr"` + Title string `xorm:"INDEX NOT NULL"` + Description string `xorm:"NOT NULL"` + RepoID string `xorm:"NOT NULL"` + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + } + + return x.Sync(new(Project)) +} diff --git a/models/projects.go b/models/projects.go new file mode 100644 index 0000000000000..ad7495968cff9 --- /dev/null +++ b/models/projects.go @@ -0,0 +1,39 @@ +// Copyright 2019 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/modules/util" + +// Project is a kanban board +type Project struct { + ID int64 `xorm:"pk autoincr"` + Title string `xorm:"INDEX NOT NULL"` + Description string `xorm:"NOT NULL"` + RepoID string `xorm:"NOT NULL"` + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` +} + +// CreateProject adds a new project entry to the database +func CreateProject(p *Project, creator *User) error { + + return nil +} + +// ProjectExists checks if a given project exists +func ProjectExists(p *Project) bool { + exists, _ := x.Exist(p) + return exists +} + +func GetProjects(repoID int64) ([]Project, error) { + + var projects []Project + + err := x.Where("repo_id", repoID).Find(&projects) + return projects, err +} diff --git a/models/repo.go b/models/repo.go index c60488844c8ab..62327b8c127d3 100644 --- a/models/repo.go +++ b/models/repo.go @@ -152,6 +152,9 @@ type Repository struct { NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` NumOpenMilestones int `xorm:"-"` NumReleases int `xorm:"-"` + NumProjects int `xorm:"NOT NULL DEFAULT 0"` + NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"` + NumOpenProjects int `xorm:"-"` IsPrivate bool `xorm:"INDEX"` IsEmpty bool `xorm:"INDEX"` @@ -207,6 +210,7 @@ func (repo *Repository) AfterLoad() { repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones + repo.NumOpenProjects = repo.NumProjects - repo.NumClosedProjects } // MustOwner always returns a valid *User object to avoid diff --git a/models/repo_unit.go b/models/repo_unit.go index 80126270deea7..060d9e630ebd8 100644 --- a/models/repo_unit.go +++ b/models/repo_unit.go @@ -118,7 +118,7 @@ func (r *RepoUnit) BeforeSet(colName string, val xorm.Cell) { switch colName { case "type": switch UnitType(Cell2Int64(val)) { - case UnitTypeCode, UnitTypeReleases, UnitTypeWiki: + case UnitTypeCode, UnitTypeReleases, UnitTypeWiki, UnitTypeProjects: r.Config = new(UnitConfig) case UnitTypeExternalWiki: r.Config = new(ExternalWikiConfig) diff --git a/models/unit.go b/models/unit.go index 9f5c8d3cbbf60..0efc4380a060a 100644 --- a/models/unit.go +++ b/models/unit.go @@ -23,6 +23,7 @@ const ( UnitTypeWiki // 5 Wiki UnitTypeExternalWiki // 6 ExternalWiki UnitTypeExternalTracker // 7 ExternalTracker + UnitTypeProjects ) // Value returns integer value for unit type @@ -46,6 +47,8 @@ func (u UnitType) String() string { return "UnitTypeExternalWiki" case UnitTypeExternalTracker: return "UnitTypeExternalTracker" + case UnitTypeProjects: + return "UnitTypeProjects" } return fmt.Sprintf("Unknown UnitType %d", u) } @@ -67,6 +70,7 @@ var ( UnitTypeWiki, UnitTypeExternalWiki, UnitTypeExternalTracker, + UnitTypeProjects, } // DefaultRepoUnits contains the default unit types @@ -76,6 +80,7 @@ var ( UnitTypePullRequests, UnitTypeReleases, UnitTypeWiki, + UnitTypeProjects, } // MustRepoUnits contains the units could not be disabled currently @@ -165,6 +170,14 @@ var ( 4, } + UnitProjects = Unit{ + UnitTypeProjects, + "repo.projects", + "/projects", + "repo.projects.desc", + 5, + } + // Units contains all the units Units = map[UnitType]Unit{ UnitTypeCode: UnitCode, @@ -174,6 +187,7 @@ var ( UnitTypeReleases: UnitReleases, UnitTypeWiki: UnitWiki, UnitTypeExternalWiki: UnitExternalWiki, + UnitTypeProjects: UnitProjects, } ) diff --git a/modules/context/repo.go b/modules/context/repo.go index 096f3c0a5d216..3eecc7d9823be 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -394,6 +394,7 @@ func RepoAssignment() macaron.Handler { ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode) ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues) ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests) + ctx.Data["IsKanbanEnabled"] = setting.Admin.EnableKanbanBoard if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil { ctx.ServerError("CanUserFork", err) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 969a0953a2894..8a6c0e8f06029 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -655,6 +655,7 @@ branches = Branches tags = Tags issues = Issues pulls = Pull Requests +kanban_board = Projects labels = Labels milestones = Milestones commits = Commits @@ -735,6 +736,7 @@ commits.gpg_key_id = GPG Key ID ext_issues = Ext. Issues ext_issues.desc = Link to an external issue tracker. +projects.new = New project issues.desc = Organize bug reports, tasks and milestones. issues.new = New Issue issues.new.title_empty = Title cannot be empty diff --git a/routers/repo/projects.go b/routers/repo/projects.go new file mode 100644 index 0000000000000..6706cccb0a513 --- /dev/null +++ b/routers/repo/projects.go @@ -0,0 +1,37 @@ +// Copyright 2019 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 ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" +) + +const ( + tplProjects base.TplName = "repo/projects/list" + + projectTemplateKey = "ProjectTemplate" +) + +func MustEnableProjects(ctx *context.Context) { + + if !setting.Admin.EnableKanbanBoard { + ctx.NotFound("EnableKanbanBoard", nil) + return + } + + if !ctx.Repo.CanRead(models.UnitTypeProjects) { + ctx.NotFound("MustEnableProjects", nil) + return + } +} + +// Projects renders the home page +func Projects(ctx *context.Context) { + + ctx.HTML(200, tplProjects) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 6169aa563c099..8738535fd3b83 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -531,6 +531,7 @@ func RegisterRoutes(m *macaron.Macaron) { reqRepoPullsReader := context.RequireRepoReader(models.UnitTypePullRequests) reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(models.UnitTypeIssues, models.UnitTypePullRequests) reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(models.UnitTypeIssues, models.UnitTypePullRequests) + reqRepoProjectsReader := context.RequireRepoReader(models.UnitTypeProjects) reqRepoIssueWriter := func(ctx *context.Context) { if !ctx.Repo.CanWrite(models.UnitTypeIssues) { @@ -798,6 +799,9 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/topics", repo.TopicsPost) }, context.RepoAssignment(), context.RepoMustNotBeArchived(), reqRepoAdmin) + m.Group("/:username/:reponame/projects", func() { + }, context.RepoAssignment(), repo.MustEnableProjects, reqRepoProjectsReader) + m.Group("/:username/:reponame", func() { m.Group("", func() { m.Get("/^:type(issues|pulls)$", repo.Issues) @@ -806,6 +810,10 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/milestones", reqRepoIssuesOrPullsReader, repo.Milestones) }, context.RepoRef()) + m.Group("/projects", func() { + m.Get("", repo.Projects) + }, reqRepoProjectsReader, repo.MustEnableProjects) + m.Group("/wiki", func() { m.Get("/?:page", repo.Wiki) m.Get("/_pages", repo.WikiPages) diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index f264a9b55956c..e0d3a69597ad8 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -73,6 +73,15 @@ {{end}} + {{ if .IsKanbanEnabled }} + + {{.i18n.Tr "repo.kanban_board"}} + + {{.Repository.NumOpenProjects}} + + + {{ end }} + {{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsEmptyRepo) }} {{.i18n.Tr "repo.releases"}} {{.Repository.NumReleases}} diff --git a/templates/repo/projects/list.tmpl b/templates/repo/projects/list.tmpl new file mode 100644 index 0000000000000..c3b3263655c04 --- /dev/null +++ b/templates/repo/projects/list.tmpl @@ -0,0 +1,21 @@ +{{template "base/head" .}} + + From 3097077bfa56d37322cbe98449e7fd6adbd85534 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 14 Jul 2019 15:56:21 +0100 Subject: [PATCH 003/216] initial UI build for listing projects --- models/migrations/v91.go | 3 +- models/projects.go | 38 +++++++--- routers/repo/projects.go | 45 ++++++++++++ templates/repo/header.tmpl | 2 +- templates/repo/projects/list.tmpl | 113 +++++++++++++++++++++++++++--- 5 files changed, 180 insertions(+), 21 deletions(-) diff --git a/models/migrations/v91.go b/models/migrations/v91.go index 8b2c02472f8b9..20edbb6eecde7 100644 --- a/models/migrations/v91.go +++ b/models/migrations/v91.go @@ -15,9 +15,10 @@ func addProjectsTable(x *xorm.Engine) error { type Project struct { ID int64 `xorm:"pk autoincr"` Title string `xorm:"INDEX NOT NULL"` - Description string `xorm:"NOT NULL"` + Description string `xorm:"TEXT"` RepoID string `xorm:"NOT NULL"` CreatorID int64 `xorm:"NOT NULL"` + IsClosed bool `xorm:"INDEX"` CreatedUnix util.TimeStamp `xorm:"INDEX created"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` diff --git a/models/projects.go b/models/projects.go index ad7495968cff9..8f2623aad23dc 100644 --- a/models/projects.go +++ b/models/projects.go @@ -4,15 +4,21 @@ package models -import "code.gitea.io/gitea/modules/util" +import ( + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) // Project is a kanban board type Project struct { ID int64 `xorm:"pk autoincr"` Title string `xorm:"INDEX NOT NULL"` - Description string `xorm:"NOT NULL"` + Description string `xorm:"TEXT"` RepoID string `xorm:"NOT NULL"` CreatorID int64 `xorm:"NOT NULL"` + IsClosed bool `xorm:"INDEX"` + + RenderedContent string `xorm:"-"` CreatedUnix util.TimeStamp `xorm:"INDEX created"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` @@ -30,10 +36,26 @@ func ProjectExists(p *Project) bool { return exists } -func GetProjects(repoID int64) ([]Project, error) { - - var projects []Project - - err := x.Where("repo_id", repoID).Find(&projects) - return projects, err +// GetProjects returns a list of all projects that have been created in the +// repository +func GetProjects(repoID int64, page int, isClosed bool, sortType string) ([]*Project, error) { + + projects := make([]*Project, 0, setting.UI.IssuePagingNum) + sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) + if page > 0 { + sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) + } + + switch sortType { + case "oldest": + sess.Desc("created_unix") + case "recentupdate": + sess.Desc("updated_unix") + case "leastupdate": + sess.Asc("updated_unix") + default: + sess.Asc("created_unix") + } + + return projects, sess.Find(&projects) } diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 6706cccb0a513..5b5104882fc47 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -8,6 +8,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" ) @@ -32,6 +33,50 @@ func MustEnableProjects(ctx *context.Context) { // Projects renders the home page func Projects(ctx *context.Context) { + sortType := ctx.Query("sort") + + isShowClosed := ctx.Query("state") == "closed" + repo := ctx.Repo.Repository + page := ctx.QueryInt("page") + if page <= 1 { + page = 1 + } + + ctx.Data["OpenCount"] = repo.NumOpenProjects + ctx.Data["ClosedCount"] = repo.NumClosedProjects + + var total int + if !isShowClosed { + total = int(repo.NumOpenProjects) + } else { + total = int(repo.NumClosedProjects) + } + + projects, err := models.GetProjects(repo.ID, page, isShowClosed, sortType) + if err != nil { + ctx.ServerError("GetProjects", err) + return + } + + for i := range projects { + projects[i].RenderedContent = string(markdown.Render([]byte(projects[i].Description), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())) + } + + ctx.Data["Projects"] = projects + + if isShowClosed { + ctx.Data["State"] = "closed" + } else { + ctx.Data["State"] = "open" + } + + pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) + pager.AddParam(ctx, "state", "State") + ctx.Data["Page"] = pager + + ctx.Data["IsShowClosed"] = isShowClosed + ctx.Data["IsProjectsPage"] = true + ctx.Data["SortType"] = sortType ctx.HTML(200, tplProjects) } diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index e0d3a69597ad8..595aff9bf80eb 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -74,7 +74,7 @@ {{end}} {{ if .IsKanbanEnabled }} - + {{.i18n.Tr "repo.kanban_board"}} {{.Repository.NumOpenProjects}} diff --git a/templates/repo/projects/list.tmpl b/templates/repo/projects/list.tmpl index c3b3263655c04..d1d070f479273 100644 --- a/templates/repo/projects/list.tmpl +++ b/templates/repo/projects/list.tmpl @@ -1,21 +1,112 @@ {{template "base/head" .}} -
+
{{template "repo/header" .}}
-
-
- {{template "repo/issue/navbar" .}} -
-
- {{template "repo/issue/search" .}} -
- {{if not .Repository.IsArchived}} -
- {{.i18n.Tr "repo.projects.new"}} +
+ {{template "base/alert" .}} + + + +
+ {{range .Milestones}} +
  • + {{.Name}} +
    +
    +
    +
    +
    +
    + {{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.Lang }} + {{if .IsClosed}} + {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} + {{else}} + + {{if .DeadlineString}} + {{.DeadlineString}} + {{else}} + {{$.i18n.Tr "repo.milestones.no_due_date"}} + {{end}} + {{end}} + + {{$.i18n.Tr "repo.issues.open_tab" .NumOpenIssues}} + {{$.i18n.Tr "repo.issues.close_tab" .NumClosedIssues}} + {{if .TotalTrackedTime}} {{.TotalTrackedTime|Sec2Time}}{{end}} + +
    + {{if and (or $.CanWriteIssues $.CanWritePulls) (not $.Repository.IsArchived)}} + + {{end}} + {{if .Content}} +
    + {{.RenderedContent|Str2html}} +
    + {{end}} +
  • + {{end}} + + {{template "base/paginate" .}}
    + +{{if or .CanWriteIssues .CanWritePulls}} + +{{end}} +{{template "base/footer" .}} From b8e6318c0f933837ea5f6b15795c6e78a19efd11 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Mon, 15 Jul 2019 18:08:59 +0100 Subject: [PATCH 004/216] projects can now be created --- models/migrations/v91.go | 2 +- models/projects.go | 22 +++++++++++- modules/auth/repo_form.go | 13 +++++++ options/locale/locale_en-US.ini | 6 ++++ routers/repo/projects.go | 38 +++++++++++++++++++-- routers/routes/routes.go | 7 ++-- templates/repo/projects/list.tmpl | 8 +---- templates/repo/projects/new.tmpl | 56 +++++++++++++++++++++++++++++++ 8 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 templates/repo/projects/new.tmpl diff --git a/models/migrations/v91.go b/models/migrations/v91.go index 20edbb6eecde7..2b4cb1187ddd1 100644 --- a/models/migrations/v91.go +++ b/models/migrations/v91.go @@ -16,7 +16,7 @@ func addProjectsTable(x *xorm.Engine) error { ID int64 `xorm:"pk autoincr"` Title string `xorm:"INDEX NOT NULL"` Description string `xorm:"TEXT"` - RepoID string `xorm:"NOT NULL"` + RepoID int64 `xorm:"NOT NULL"` CreatorID int64 `xorm:"NOT NULL"` IsClosed bool `xorm:"INDEX"` diff --git a/models/projects.go b/models/projects.go index 8f2623aad23dc..c46b5acf8c2b7 100644 --- a/models/projects.go +++ b/models/projects.go @@ -14,7 +14,7 @@ type Project struct { ID int64 `xorm:"pk autoincr"` Title string `xorm:"INDEX NOT NULL"` Description string `xorm:"TEXT"` - RepoID string `xorm:"NOT NULL"` + RepoID int64 `xorm:"NOT NULL"` CreatorID int64 `xorm:"NOT NULL"` IsClosed bool `xorm:"INDEX"` @@ -59,3 +59,23 @@ func GetProjects(repoID int64, page int, isClosed bool, sortType string) ([]*Pro return projects, sess.Find(&projects) } + +// NewProject creates a new Project +func NewProject(p *Project) error { + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + if _, err := sess.Insert(p); err != nil { + return err + } + + if _, err := sess.Exec("UPDATE `repository` SET num_projects = num_projects + 1 WHERE id = ?", p.RepoID); err != nil { + return err + } + return sess.Commit() +} diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 0333c3c92614a..72042ee54b2b8 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -365,6 +365,19 @@ func (i IssueLockForm) HasValidReason() bool { return false } +// __________ __ __ +// \______ \_______ ____ |__| ____ _____/ |_ ______ +// | ___/\_ __ \/ _ \ | |/ __ \_/ ___\ __\/ ___/ +// | | | | \( <_> ) | \ ___/\ \___| | \___ \ +// |____| |__| \____/\__| |\___ >\___ >__| /____ > +// \______| \/ \/ \/ + +// CreateProjectForm form for creating a project +type CreateProjectForm struct { + Title string `binding:"Required;MaxSize(50)"` + Content string +} + // _____ .__.__ __ // / \ |__| | ____ _______/ |_ ____ ____ ____ // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8a6c0e8f06029..57163ec33d83d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -736,7 +736,13 @@ commits.gpg_key_id = GPG Key ID ext_issues = Ext. Issues ext_issues.desc = Link to an external issue tracker. +projects.create = Create Project +projects.title = Title projects.new = New project +projects.new_subheader = Coordinate, track, and update your work in one place, so projects stay transparent and on schedule. +projects.desc = Description +projects.create_success = The project '%s' has been created. + issues.desc = Organize bug reports, tasks and milestones. issues.new = New Issue issues.new.title_empty = Title cannot be empty diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 5b5104882fc47..dd05ded8cb32c 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -6,6 +6,7 @@ package repo import ( "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup/markdown" @@ -13,7 +14,8 @@ import ( ) const ( - tplProjects base.TplName = "repo/projects/list" + tplProjects base.TplName = "repo/projects/list" + tplProjectsNew base.TplName = "repo/projects/new" projectTemplateKey = "ProjectTemplate" ) @@ -31,8 +33,10 @@ func MustEnableProjects(ctx *context.Context) { } } -// Projects renders the home page +// Projects renders the home page of projects func Projects(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.kanban_board") + sortType := ctx.Query("sort") isShowClosed := ctx.Query("state") == "closed" @@ -80,3 +84,33 @@ func Projects(ctx *context.Context) { ctx.HTML(200, tplProjects) } + +// NewProject render creating a project page +func NewProject(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.projects.new") + ctx.HTML(200, tplProjectsNew) +} + +// NewProjectPost creates a new project +func NewProjectPost(ctx *context.Context, form auth.CreateProjectForm) { + + ctx.Data["Title"] = ctx.Tr("repo.projects.new") + + if ctx.HasError() { + ctx.HTML(200, tplProjectsNew) + return + } + + if err := models.NewProject(&models.Project{ + RepoID: ctx.Repo.Repository.ID, + Title: form.Title, + Description: form.Content, + CreatorID: ctx.User.ID, + }); err != nil { + ctx.ServerError("NewProject", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.projects.create_success", form.Title)) + ctx.Redirect(ctx.Repo.RepoLink + "/projects") +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 8738535fd3b83..6f90499a3e8b9 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -799,9 +799,6 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/topics", repo.TopicsPost) }, context.RepoAssignment(), context.RepoMustNotBeArchived(), reqRepoAdmin) - m.Group("/:username/:reponame/projects", func() { - }, context.RepoAssignment(), repo.MustEnableProjects, reqRepoProjectsReader) - m.Group("/:username/:reponame", func() { m.Group("", func() { m.Get("/^:type(issues|pulls)$", repo.Issues) @@ -811,7 +808,11 @@ func RegisterRoutes(m *macaron.Macaron) { }, context.RepoRef()) m.Group("/projects", func() { + m.Get("", repo.Projects) + m.Get("/new", repo.NewProject) + m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost) + }, reqRepoProjectsReader, repo.MustEnableProjects) m.Group("/wiki", func() { diff --git a/templates/repo/projects/list.tmpl b/templates/repo/projects/list.tmpl index d1d070f479273..8256718d3e024 100644 --- a/templates/repo/projects/list.tmpl +++ b/templates/repo/projects/list.tmpl @@ -38,14 +38,8 @@
    - {{range .Milestones}} + {{range .Projects}}
  • - {{.Name}} -
    -
    -
    -
    -
    {{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.Lang }} {{if .IsClosed}} diff --git a/templates/repo/projects/new.tmpl b/templates/repo/projects/new.tmpl new file mode 100644 index 0000000000000..be573302c2589 --- /dev/null +++ b/templates/repo/projects/new.tmpl @@ -0,0 +1,56 @@ +{{template "base/head" .}} +
    + {{template "repo/header" .}} +
    + +
    +

    + {{if .PageIsEditProject}} + {{.i18n.Tr "repo.projects.edit"}} +
    {{.i18n.Tr "repo.projects.edit_subheader"}}
    + {{else}} + {{.i18n.Tr "repo.projects.new"}} +
    {{.i18n.Tr "repo.projects.new_subheader"}}
    + {{end}} +

    + {{template "base/alert" .}} +
    + {{.CsrfTokenHtml}} +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    + {{if .PageIsEditMilestone}} + + {{.i18n.Tr "repo.milestones.cancel"}} + + + {{else}} + + {{end}} +
    +
    +
    +
    +
    +{{template "base/footer" .}} From 33791e8eccdc4c1784d0f6d76ba81538e282e334 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Mon, 15 Jul 2019 18:46:17 +0100 Subject: [PATCH 005/216] projects can now be listed --- models/migrations/v91.go | 19 +++++++++++-------- models/projects.go | 26 ++++++++++++++++++-------- templates/repo/projects/list.tmpl | 19 ++++++------------- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/models/migrations/v91.go b/models/migrations/v91.go index 2b4cb1187ddd1..173fb0b20294f 100644 --- a/models/migrations/v91.go +++ b/models/migrations/v91.go @@ -13,15 +13,18 @@ import ( func addProjectsTable(x *xorm.Engine) error { type Project struct { - ID int64 `xorm:"pk autoincr"` - Title string `xorm:"INDEX NOT NULL"` - Description string `xorm:"TEXT"` - RepoID int64 `xorm:"NOT NULL"` - CreatorID int64 `xorm:"NOT NULL"` - IsClosed bool `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + Title string `xorm:"INDEX NOT NULL"` + Description string `xorm:"TEXT"` + RepoID int64 `xorm:"NOT NULL"` + CreatorID int64 `xorm:"NOT NULL"` + IsClosed bool `xorm:"INDEX"` + NumIssues int + NumClosedIssues int - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + ClosedDateUnix util.TimeStamp + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` } return x.Sync(new(Project)) diff --git a/models/projects.go b/models/projects.go index c46b5acf8c2b7..df06da3f58707 100644 --- a/models/projects.go +++ b/models/projects.go @@ -11,17 +11,27 @@ import ( // Project is a kanban board type Project struct { - ID int64 `xorm:"pk autoincr"` - Title string `xorm:"INDEX NOT NULL"` - Description string `xorm:"TEXT"` - RepoID int64 `xorm:"NOT NULL"` - CreatorID int64 `xorm:"NOT NULL"` - IsClosed bool `xorm:"INDEX"` + ID int64 `xorm:"pk autoincr"` + Title string `xorm:"INDEX NOT NULL"` + Description string `xorm:"TEXT"` + RepoID int64 `xorm:"NOT NULL"` + CreatorID int64 `xorm:"NOT NULL"` + IsClosed bool `xorm:"INDEX"` + NumIssues int + NumClosedIssues int + NumOpenIssues int `xorm:"-"` RenderedContent string `xorm:"-"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + ClosedDateUnix util.TimeStamp +} + +// AfterLoad is invoked from XORM after setting the value of a field of +// this object. +func (p *Project) AfterLoad() { + p.NumOpenIssues = p.NumIssues - p.NumClosedIssues } // CreateProject adds a new project entry to the database diff --git a/templates/repo/projects/list.tmpl b/templates/repo/projects/list.tmpl index 8256718d3e024..ab8562814d62f 100644 --- a/templates/repo/projects/list.tmpl +++ b/templates/repo/projects/list.tmpl @@ -40,36 +40,29 @@
    {{range .Projects}}
  • + {{.Title}}
    {{ $closedDate:= TimeSinceUnix .ClosedDateUnix $.Lang }} - {{if .IsClosed}} + {{if .IsClosed }} {{$.i18n.Tr "repo.milestones.closed" $closedDate|Str2html}} - {{else}} - - {{if .DeadlineString}} - {{.DeadlineString}} - {{else}} - {{$.i18n.Tr "repo.milestones.no_due_date"}} - {{end}} {{end}} {{$.i18n.Tr "repo.issues.open_tab" .NumOpenIssues}} {{$.i18n.Tr "repo.issues.close_tab" .NumClosedIssues}} - {{if .TotalTrackedTime}} {{.TotalTrackedTime|Sec2Time}}{{end}}
    {{if and (or $.CanWriteIssues $.CanWritePulls) (not $.Repository.IsArchived)}} {{end}} - {{if .Content}} + {{if .Description}}
    {{.RenderedContent|Str2html}}
    From b531a24f0dd7edc033110226f146a09da0acd80e Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Tue, 16 Jul 2019 18:38:34 +0100 Subject: [PATCH 006/216] projects can be closed and opened --- models/error.go | 23 +++++++++ models/projects.go | 86 ++++++++++++++++++++++++++----- options/locale/locale_en-US.ini | 3 ++ routers/repo/projects.go | 36 +++++++++++++ routers/routes/routes.go | 1 + templates/repo/projects/list.tmpl | 4 +- 6 files changed, 139 insertions(+), 14 deletions(-) diff --git a/models/error.go b/models/error.go index 11ca6e6863f77..a89f1265ae1c3 100644 --- a/models/error.go +++ b/models/error.go @@ -1233,6 +1233,29 @@ func (err ErrLabelNotExist) Error() string { return fmt.Sprintf("label does not exist [label_id: %d, repo_id: %d]", err.LabelID, err.RepoID) } +// __________ __ __ +// \______ \_______ ____ |__| ____ _____/ |_ ______ +// | ___/\_ __ \/ _ \ | |/ __ \_/ ___\ __\/ ___/ +// | | | | \( <_> ) | \ ___/\ \___| | \___ \ +// |____| |__| \____/\__| |\___ >\___ >__| /____ > +// \______| \/ \/ \/ + +// ErrProjectNotExist represents a "ProjectNotExist" kind of error. +type ErrProjectNotExist struct { + ID int64 + RepoID int64 +} + +// IsErrProjetcNotExist checks if an error is a ErrProjectNotExist +func IsErrProjectNotExist(err error) bool { + _, ok := err.(ErrProjectNotExist) + return ok +} + +func (err ErrProjectNotExist) Error() string { + return fmt.Sprintf("projects does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID) +} + // _____ .__.__ __ // / \ |__| | ____ _______/ |_ ____ ____ ____ // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ diff --git a/models/projects.go b/models/projects.go index df06da3f58707..94f449ed45f98 100644 --- a/models/projects.go +++ b/models/projects.go @@ -34,18 +34,6 @@ func (p *Project) AfterLoad() { p.NumOpenIssues = p.NumIssues - p.NumClosedIssues } -// CreateProject adds a new project entry to the database -func CreateProject(p *Project, creator *User) error { - - return nil -} - -// ProjectExists checks if a given project exists -func ProjectExists(p *Project) bool { - exists, _ := x.Exist(p) - return exists -} - // GetProjects returns a list of all projects that have been created in the // repository func GetProjects(repoID int64, page int, isClosed bool, sortType string) ([]*Project, error) { @@ -89,3 +77,77 @@ func NewProject(p *Project) error { } return sess.Commit() } + +// GetProjectByRepoID returns the projects in a repository. +func GetProjectByRepoID(repoID, id int64) (*Project, error) { + + p := &Project{ + ID: id, + RepoID: repoID, + } + + has, err := x.Get(p) + if err != nil { + return nil, err + } else if !has { + return nil, ErrProjectNotExist{id, repoID} + } + + return p, nil +} + +func updateProject(e Engine, p *Project) error { + _, err := e.ID(p.ID).AllCols().Update(p) + return err +} + +func countRepoProjects(e Engine, repoID int64) (int64, error) { + return e. + Where("repo_id=?", repoID). + Count(new(Project)) +} + +func countRepoClosedProjects(e Engine, repoID int64) (int64, error) { + return e. + Where("repo_id=? AND is_closed=?", repoID, true). + Count(new(Project)) +} + +// ChangeProjectStatus togggles a project between opened and closed +func ChangeProjectStatus(p *Project, isClosed bool) error { + + repo, err := GetRepositoryByID(p.RepoID) + if err != nil { + return err + } + + sess := x.NewSession() + defer sess.Close() + if err = sess.Begin(); err != nil { + return err + } + + p.IsClosed = isClosed + if err = updateProject(sess, p); err != nil { + return err + } + + numProjects, err := countRepoProjects(sess, repo.ID) + if err != nil { + return err + } + + numClosedProjects, err := countRepoClosedProjects(sess, repo.ID) + if err != nil { + return err + } + + repo.NumProjects = int(numProjects) + repo.NumClosedProjects = int(numClosedProjects) + + if _, err = sess.ID(repo.ID).Cols("num_projects, num_closed_projects").Update(repo); err != nil { + return err + } + + return sess.Commit() +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 57163ec33d83d..2b37017319cc2 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -742,6 +742,9 @@ projects.new = New project projects.new_subheader = Coordinate, track, and update your work in one place, so projects stay transparent and on schedule. projects.desc = Description projects.create_success = The project '%s' has been created. +projects.deletion = Delete Project +projects.deletion_desc = Deleting a project removes it from all related issues. Continue? +projects.deletion_success = The project has been deleted. issues.desc = Organize bug reports, tasks and milestones. issues.new = New Issue diff --git a/routers/repo/projects.go b/routers/repo/projects.go index dd05ded8cb32c..01991fe7477c2 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) const ( @@ -114,3 +115,38 @@ func NewProjectPost(ctx *context.Context, form auth.CreateProjectForm) { ctx.Flash.Success(ctx.Tr("repo.projects.create_success", form.Title)) ctx.Redirect(ctx.Repo.RepoLink + "/projects") } + +// ChangeProjectStatus updates the status of a project between "open" and "close" +func ChangeProjectStatus(ctx *context.Context) { + p, err := models.GetProjectByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) + if err != nil { + if models.IsErrProjectNotExist(err) { + ctx.NotFound("", err) + } else { + ctx.ServerError("GetProjectByRepoID", err) + } + return + } + + switch ctx.Params(":action") { + case "open": + if p.IsClosed { + if err = models.ChangeProjectStatus(p, false); err != nil { + ctx.ServerError("ChangeProjectStatus", err) + return + } + } + ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open") + case "close": + if !p.IsClosed { + p.ClosedDateUnix = util.TimeStampNow() + if err = models.ChangeProjectStatus(p, true); err != nil { + ctx.ServerError("ChangeProjectStatus", err) + return + } + } + ctx.Redirect(ctx.Repo.RepoLink + "/projects?state=closed") + default: + ctx.Redirect(ctx.Repo.RepoLink + "/projects") + } +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 6f90499a3e8b9..08c5ff05068d1 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -812,6 +812,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("", repo.Projects) m.Get("/new", repo.NewProject) m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost) + m.Get("/:id/:action", repo.ChangeProjectStatus) }, reqRepoProjectsReader, repo.MustEnableProjects) diff --git a/templates/repo/projects/list.tmpl b/templates/repo/projects/list.tmpl index ab8562814d62f..c062715c700c4 100644 --- a/templates/repo/projects/list.tmpl +++ b/templates/repo/projects/list.tmpl @@ -79,10 +79,10 @@ + + +
  • @@ -44,6 +56,7 @@ {{.i18n.Tr "repo.projects.modify"}} {{else}} + @@ -51,21 +64,6 @@
    - - -
    From 155531e398ca5e55b5bd58c5f300abad873f59b6 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 11 Aug 2019 15:35:20 +0100 Subject: [PATCH 013/216] allow selection of project type --- models/migrations/v93.go | 4 ++++ models/projects.go | 7 ++++++- modules/auth/repo_form.go | 1 + routers/repo/projects.go | 1 + templates/repo/projects/new.tmpl | 4 ++-- 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/models/migrations/v93.go b/models/migrations/v93.go index 91ce1186d264b..b2ddc9355b03f 100644 --- a/models/migrations/v93.go +++ b/models/migrations/v93.go @@ -12,6 +12,8 @@ import ( func addProjectsTable(x *xorm.Engine) error { + type ProjectType uint8 + sess := x.NewSession() defer sess.Close() @@ -29,6 +31,8 @@ func addProjectsTable(x *xorm.Engine) error { NumIssues int NumClosedIssues int + Type ProjectType + ClosedDateUnix util.TimeStamp CreatedUnix util.TimeStamp `xorm:"INDEX created"` UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` diff --git a/models/projects.go b/models/projects.go index eb37841ce173f..905de42703a38 100644 --- a/models/projects.go +++ b/models/projects.go @@ -22,12 +22,13 @@ const ( BugTriage ) -/// ProjectsConfig is used to identify the type of board that is being created +// ProjectsConfig is used to identify the type of board that is being created type ProjectsConfig struct { Type ProjectType Translation string } +// GetProjectsConfig retrieves the types of configurations projects could have func GetProjectsConfig() []ProjectsConfig { return []ProjectsConfig{ {None, "repo.projects.type.none"}, @@ -57,6 +58,7 @@ type Project struct { NumIssues int NumClosedIssues int NumOpenIssues int `xorm:"-"` + Type ProjectType RenderedContent string `xorm:"-"` @@ -97,6 +99,9 @@ func GetProjects(repoID int64, page int, isClosed bool, sortType string) ([]*Pro // NewProject creates a new Project func NewProject(p *Project) error { + if !IsProjectTypeValid(p.Type) { + p.Type = None + } sess := x.NewSession() defer sess.Close() diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 72042ee54b2b8..80ee5facdc4f2 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -376,6 +376,7 @@ func (i IssueLockForm) HasValidReason() bool { type CreateProjectForm struct { Title string `binding:"Required;MaxSize(50)"` Content string + Type models.ProjectType } // _____ .__.__ __ diff --git a/routers/repo/projects.go b/routers/repo/projects.go index f8098fd724ae2..22bd304ee9ec3 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -109,6 +109,7 @@ func NewProjectPost(ctx *context.Context, form auth.CreateProjectForm) { Title: form.Title, Description: form.Content, CreatorID: ctx.User.ID, + Type: form.Type, }); err != nil { ctx.ServerError("NewProject", err) return diff --git a/templates/repo/projects/new.tmpl b/templates/repo/projects/new.tmpl index 644e4a2ed7f71..9cb54e97bbf1b 100644 --- a/templates/repo/projects/new.tmpl +++ b/templates/repo/projects/new.tmpl @@ -35,12 +35,12 @@ From ddd0ab0d340a26a02976baf1213faf83612ecc75 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 11 Aug 2019 21:49:04 +0100 Subject: [PATCH 014/216] improve migrations --- models/migrations/v93.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/models/migrations/v93.go b/models/migrations/v93.go index b2ddc9355b03f..ad3a9c773125a 100644 --- a/models/migrations/v93.go +++ b/models/migrations/v93.go @@ -42,10 +42,26 @@ func addProjectsTable(x *xorm.Engine) error { ProjectID int64 `xorm:"INDEX"` } + type ProjectBoard struct { + ID int64 `xorm:"pk autoincr"` + ProjectID int64 `xorm:"INDEX NOT NULL"` + Title string + + // Not really needed but helpful + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + } + if err := x.Sync(new(Project)); err != nil { return err } + if err := x.Sync(new(ProjectBoard)); err != nil { + return err + } + if err := x.Sync2(new(Issue)); err != nil { return err } From d263a7ad9283a980e95200ba5336a04b4a069033 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Thu, 15 Aug 2019 00:37:32 +0100 Subject: [PATCH 015/216] create project boards --- models/projects.go | 57 +++++++++++++++++++++++++++++++++++ modules/context/repo.go | 2 +- modules/setting/repository.go | 6 ++++ modules/setting/setting.go | 1 - routers/repo/projects.go | 2 +- 5 files changed, 65 insertions(+), 3 deletions(-) diff --git a/models/projects.go b/models/projects.go index 905de42703a38..e7b454913db4e 100644 --- a/models/projects.go +++ b/models/projects.go @@ -7,11 +7,25 @@ package models import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "github.com/go-xorm/xorm" ) // ProjectType is used to represent a project board type type ProjectType uint8 +// ProjectBoard is used to represent boards on a kanban project +type ProjectBoard struct { + ID int64 `xorm:"pk autoincr"` + ProjectID int64 `xorm:"INDEX NOT NULL"` + Title string + + // Not really needed but helpful + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix util.TimeStamp `xorm:"INDEX created"` + UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` +} + const ( // None is a project board type that has no predefined columns None ProjectType = iota @@ -117,9 +131,52 @@ func NewProject(p *Project) error { if _, err := sess.Exec("UPDATE `repository` SET num_projects = num_projects + 1 WHERE id = ?", p.RepoID); err != nil { return err } + + if err := createBoardsForProjectsType(sess, p); err != nil { + return err + } + return sess.Commit() } +func createBoardsForProjectsType(sess *xorm.Session, project *Project) error { + + var items []string + + switch project.Type { + + case BugTriage: + items = setting.Repository.ProjectBoardBugTriageType + + case BasicKanban: + items = setting.Repository.ProjectBoardBasicKanbanType + + case None: + fallthrough + default: + return nil + } + + if len(items) == 0 { + return nil + } + + var boards = make([]ProjectBoard, 0) + + for _, v := range items { + boards = append(boards, ProjectBoard{ + CreatedUnix: util.TimeStampNow(), + UpdatedUnix: util.TimeStampNow(), + CreatorID: project.CreatorID, + Title: v, + ProjectID: project.ID, + }) + } + + _, err := sess.Insert(boards) + return err +} + // GetProjectByRepoID returns the projects in a repository. func GetProjectByRepoID(repoID, id int64) (*Project, error) { diff --git a/modules/context/repo.go b/modules/context/repo.go index 3eecc7d9823be..2ad8176d1f67c 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -394,7 +394,7 @@ func RepoAssignment() macaron.Handler { ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(models.UnitTypeCode) ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(models.UnitTypeIssues) ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(models.UnitTypePullRequests) - ctx.Data["IsKanbanEnabled"] = setting.Admin.EnableKanbanBoard + ctx.Data["IsKanbanEnabled"] = setting.Repository.EnableKanbanBoard if ctx.Data["CanSignedUserFork"], err = ctx.Repo.Repository.CanUserFork(ctx.User); err != nil { ctx.ServerError("CanUserFork", err) diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 98e3d6e82624d..bd8df9de942b4 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -34,6 +34,9 @@ var ( AccessControlAllowOrigin string UseCompatSSHURI bool DefaultCloseIssuesViaCommitsInAnyBranch bool + EnableKanbanBoard bool + ProjectBoardBasicKanbanType []string + ProjectBoardBugTriageType []string // Repository editor settings Editor struct { @@ -76,6 +79,9 @@ var ( AccessControlAllowOrigin: "", UseCompatSSHURI: false, DefaultCloseIssuesViaCommitsInAnyBranch: false, + EnableKanbanBoard: true, + ProjectBoardBasicKanbanType: strings.Split("Todo, In progress, Done", ","), + ProjectBoardBugTriageType: strings.Split("Needs Triage, High priority, Low priority, Closed", ","), // Repository editor settings Editor: struct { diff --git a/modules/setting/setting.go b/modules/setting/setting.go index cd07fc1febfcc..97bdc03cc9a56 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -240,7 +240,6 @@ var ( // Admin settings Admin struct { DisableRegularOrgCreation bool - EnableKanbanBoard bool } // Picture settings diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 22bd304ee9ec3..fb837439f250c 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -23,7 +23,7 @@ const ( func MustEnableProjects(ctx *context.Context) { - if !setting.Admin.EnableKanbanBoard { + if !setting.Repository.EnableKanbanBoard { ctx.NotFound("EnableKanbanBoard", nil) return } From 76a10f87c0e9252f170840329505f126c8468fde Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 25 Aug 2019 19:05:21 +0100 Subject: [PATCH 016/216] merge origin/master --- models/migrations/v93.go | 13 ++++++------- models/projects.go | 16 ++++++++-------- routers/repo/projects.go | 4 ++-- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/models/migrations/v93.go b/models/migrations/v93.go index ad3a9c773125a..19c9cf211bebe 100644 --- a/models/migrations/v93.go +++ b/models/migrations/v93.go @@ -5,8 +5,7 @@ package migrations import ( - "code.gitea.io/gitea/modules/util" - + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) @@ -33,9 +32,9 @@ func addProjectsTable(x *xorm.Engine) error { Type ProjectType - ClosedDateUnix util.TimeStamp - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + ClosedDateUnix timeutil.TimeStamp + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } type Issue struct { @@ -50,8 +49,8 @@ func addProjectsTable(x *xorm.Engine) error { // Not really needed but helpful CreatorID int64 `xorm:"NOT NULL"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } if err := x.Sync(new(Project)); err != nil { diff --git a/models/projects.go b/models/projects.go index e7b454913db4e..0d1ebcdb3b135 100644 --- a/models/projects.go +++ b/models/projects.go @@ -6,7 +6,7 @@ package models import ( "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) @@ -22,8 +22,8 @@ type ProjectBoard struct { // Not really needed but helpful CreatorID int64 `xorm:"NOT NULL"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } const ( @@ -76,9 +76,9 @@ type Project struct { RenderedContent string `xorm:"-"` - CreatedUnix util.TimeStamp `xorm:"INDEX created"` - UpdatedUnix util.TimeStamp `xorm:"INDEX updated"` - ClosedDateUnix util.TimeStamp + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + ClosedDateUnix timeutil.TimeStamp } // AfterLoad is invoked from XORM after setting the value of a field of @@ -165,8 +165,8 @@ func createBoardsForProjectsType(sess *xorm.Session, project *Project) error { for _, v := range items { boards = append(boards, ProjectBoard{ - CreatedUnix: util.TimeStampNow(), - UpdatedUnix: util.TimeStampNow(), + CreatedUnix: timeutil.TimeStampNow(), + UpdatedUnix: timeutil.TimeStampNow(), CreatorID: project.CreatorID, Title: v, ProjectID: project.ID, diff --git a/routers/repo/projects.go b/routers/repo/projects.go index fb837439f250c..1057dbc9c8e2f 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/timeutil" ) const ( @@ -142,7 +142,7 @@ func ChangeProjectStatus(ctx *context.Context) { ctx.Redirect(ctx.Repo.RepoLink + "/projects?state=open") case "close": if !p.IsClosed { - p.ClosedDateUnix = util.TimeStampNow() + p.ClosedDateUnix = timeutil.TimeStampNow() if err = models.ChangeProjectStatus(p, true); err != nil { ctx.ServerError("ChangeProjectStatus", err) return From e7cf2b77afe50b5818c52405364faf3c914b9e63 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Wed, 4 Sep 2019 18:00:13 +0100 Subject: [PATCH 017/216] fix compilation --- go.mod | 2 -- models/migrations/v93.go | 63 ---------------------------------------- 2 files changed, 65 deletions(-) diff --git a/go.mod b/go.mod index 66a65973b6089..c095cc6661232 100644 --- a/go.mod +++ b/go.mod @@ -70,8 +70,6 @@ require ( github.com/mattn/go-sqlite3 v1.11.0 github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 diff --git a/models/migrations/v93.go b/models/migrations/v93.go index 9142b7746cdc0..0381ee35be270 100644 --- a/models/migrations/v93.go +++ b/models/migrations/v93.go @@ -4,72 +4,10 @@ package migrations -<<<<<<< HEAD import ( - "code.gitea.io/gitea/modules/timeutil" "github.com/go-xorm/xorm" ) -func addProjectsTable(x *xorm.Engine) error { - - type ProjectType uint8 - - sess := x.NewSession() - defer sess.Close() - - if err := sess.Begin(); err != nil { - return err - } - - type Project struct { - ID int64 `xorm:"pk autoincr"` - Title string `xorm:"INDEX NOT NULL"` - Description string `xorm:"TEXT"` - RepoID int64 `xorm:"NOT NULL"` - CreatorID int64 `xorm:"NOT NULL"` - IsClosed bool `xorm:"INDEX"` - NumIssues int - NumClosedIssues int - - Type ProjectType - - ClosedDateUnix timeutil.TimeStamp - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - } - - type Issue struct { - ProjectID int64 `xorm:"INDEX"` - } - - type ProjectBoard struct { - ID int64 `xorm:"pk autoincr"` - ProjectID int64 `xorm:"INDEX NOT NULL"` - Title string - - // Not really needed but helpful - CreatorID int64 `xorm:"NOT NULL"` - - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - } - - if err := x.Sync(new(Project)); err != nil { - return err - } - - if err := x.Sync(new(ProjectBoard)); err != nil { - return err - } - - if err := x.Sync2(new(Issue)); err != nil { - return err - } - - return sess.Commit() -======= -import "github.com/go-xorm/xorm" - func addEmailNotificationEnabledToUser(x *xorm.Engine) error { // User see models/user.go type User struct { @@ -77,5 +15,4 @@ func addEmailNotificationEnabledToUser(x *xorm.Engine) error { } return x.Sync2(new(User)) ->>>>>>> origin } From 82f752c89263e086a6410a6e909b1a001efac51e Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 14 Sep 2019 17:01:50 +0100 Subject: [PATCH 018/216] Allow issues to be assigned to a project --- models/issue.go | 42 ++++++++++++------- models/issue_comment.go | 15 +++++++ models/projects.go | 74 +++++++++++++++++++++++++++++++-- options/locale/locale_en-US.ini | 5 +++ routers/repo/issue.go | 21 ++++++++++ routers/repo/projects.go | 51 ++++++++++++++++++++++- routers/routes/routes.go | 2 + 7 files changed, 188 insertions(+), 22 deletions(-) diff --git a/models/issue.go b/models/issue.go index 0abd3787782a5..7a2b9663b4bc7 100644 --- a/models/issue.go +++ b/models/issue.go @@ -33,22 +33,24 @@ type Issue struct { Poster *User `xorm:"-"` OriginalAuthor string OriginalAuthorID int64 - Title string `xorm:"name"` - Content string `xorm:"TEXT"` - RenderedContent string `xorm:"-"` - Labels []*Label `xorm:"-"` - MilestoneID int64 `xorm:"INDEX"` - ProjectID int64 `xorm:"INDEX"` - Milestone *Milestone `xorm:"-"` - Priority int - AssigneeID int64 `xorm:"-"` - Assignee *User `xorm:"-"` - IsClosed bool `xorm:"INDEX"` - IsRead bool `xorm:"-"` - IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. - PullRequest *PullRequest `xorm:"-"` - NumComments int - Ref string + Title string `xorm:"name"` + Content string `xorm:"TEXT"` + RenderedContent string `xorm:"-"` + Labels []*Label `xorm:"-"` + MilestoneID int64 `xorm:"INDEX"` + ProjectID int64 `xorm:"INDEX"` + // If 0, then it has not been added to a specific board in the project + ProjectBoardID int64 `xorm:"INDEX"` + Milestone *Milestone `xorm:"-"` + Priority int + AssigneeID int64 `xorm:"-"` + Assignee *User `xorm:"-"` + IsClosed bool `xorm:"INDEX"` + IsRead bool `xorm:"-"` + IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. + PullRequest *PullRequest `xorm:"-"` + NumComments int + Ref string DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` @@ -61,6 +63,7 @@ type Issue struct { Reactions ReactionList `xorm:"-"` TotalTrackedTime int64 `xorm:"-"` Assignees []*User `xorm:"-"` + Project *Project `xorm:"-"` // IsLocked limits commenting abilities to users on an issue // with write access @@ -258,6 +261,13 @@ func (issue *Issue) loadAttributes(e Engine) (err error) { } } + if issue.ProjectID > 0 && issue.Project == nil { + issue.Project, err = getProjectByRepoID(e, issue.RepoID, issue.ProjectID) + if err != nil && !IsErrProjectNotExist(err) { + return fmt.Errorf("getProjectByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.ProjectID, err) + } + } + if err = issue.loadAssignees(e); err != nil { return } diff --git a/models/issue_comment.go b/models/issue_comment.go index 2a9e8596cb202..830487c56359f 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -81,6 +81,10 @@ const ( CommentTypeLock // Unlocks a previously locked issue CommentTypeUnlock + // Project changed + CommentTypeProject + // Project board changed + CommentTypeProjectBoard ) // CommentTag defines comment tag type @@ -660,6 +664,17 @@ func createLabelComment(e *xorm.Session, doer *User, repo *Repository, issue *Is }) } +func createProjectComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldProjectID, projectID int64) (*Comment, error) { + return createComment(e, &CreateCommentOptions{ + Type: CommentTypeProject, + Doer: doer, + Repo: repo, + Issue: issue, + OldMilestoneID: oldProjectID, + MilestoneID: projectID, + }) +} + func createMilestoneComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldMilestoneID, milestoneID int64) (*Comment, error) { return createComment(e, &CreateCommentOptions{ Type: CommentTypeMilestone, diff --git a/models/projects.go b/models/projects.go index 0d1ebcdb3b135..18f48749aef6a 100644 --- a/models/projects.go +++ b/models/projects.go @@ -177,15 +177,13 @@ func createBoardsForProjectsType(sess *xorm.Session, project *Project) error { return err } -// GetProjectByRepoID returns the projects in a repository. -func GetProjectByRepoID(repoID, id int64) (*Project, error) { - +func getProjectByRepoID(e Engine, repoID, id int64) (*Project, error) { p := &Project{ ID: id, RepoID: repoID, } - has, err := x.Get(p) + has, err := e.Get(p) if err != nil { return nil, err } else if !has { @@ -195,6 +193,11 @@ func GetProjectByRepoID(repoID, id int64) (*Project, error) { return p, nil } +// GetProjectByRepoID returns the projects in a repository. +func GetProjectByRepoID(repoID, id int64) (*Project, error) { + return getProjectByRepoID(x, repoID, id) +} + func updateProject(e Engine, p *Project) error { _, err := e.ID(p.ID).AllCols().Update(p) return err @@ -305,3 +308,66 @@ func UpdateProject(p *Project) error { _, err := x.ID(p.ID).AllCols().Update(p) return err } + +// ChangeProjectAssign changes the project associated with an issue +func ChangeProjectAssign(issue *Issue, doer *User, oldProjectID int64) error { + + sess := x.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + if err := changeProjectAssign(sess, doer, issue, oldProjectID); err != nil { + return err + } + + return sess.Commit() +} + +func changeProjectAssign(sess *xorm.Session, doer *User, issue *Issue, oldProjectID int64) error { + + if oldProjectID > 0 { + p, err := getProjectByRepoID(sess, issue.RepoID, oldProjectID) + if err != nil { + return err + } + + p.NumIssues-- + if issue.IsClosed { + p.NumClosedIssues-- + } + + if err := updateProject(sess, p); err != nil { + return err + } + } + + if issue.ProjectID > 0 { + p, err := getProjectByRepoID(sess, issue.RepoID, issue.ProjectID) + if err != nil { + return err + } + + p.NumIssues++ + if issue.IsClosed { + p.NumClosedIssues++ + } + + if err := updateProject(sess, p); err != nil { + return err + } + } + + if err := issue.loadRepo(sess); err != nil { + return err + } + + if oldProjectID > 0 || issue.ProjectID > 0 { + if _, err := createProjectComment(sess, doer, issue.Repo, issue, oldProjectID, issue.ProjectID); err != nil { + return err + } + } + + return updateIssueCols(sess, issue, "project_id") +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 266d6e58debdb..b425a7a9f8073 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -770,6 +770,11 @@ issues.new.title_empty = Title cannot be empty issues.new.labels = Labels issues.new.no_label = No Label issues.new.clear_labels = Clear labels +issues.new.projects = Projects +issues.new.clear_projects = Clear projects +issues.new.no_projects = No project +issues.new.open_projects = Open Projects +issues.new.closed_projects = Closed Projects issues.new.milestone = Milestone issues.new.no_milestone = No Milestone issues.new.clear_milestone = Clear milestone diff --git a/routers/repo/issue.go b/routers/repo/issue.go index dc09a650fed7f..190b4ec3b312f 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -341,6 +341,23 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repos } } +func retrieveProjects(ctx *context.Context, repo *models.Repository) { + + var err error + + ctx.Data["OpenProjects"], err = models.GetProjects(repo.ID, -1, false, "") + if err != nil { + ctx.ServerError("GetProjects", err) + return + } + + ctx.Data["ClosedProjects"], err = models.GetProjects(repo.ID, -1, true, "") + if err != nil { + ctx.ServerError("GetProjects", err) + return + } +} + // RetrieveRepoMetas find all the meta information of a repository func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label { if !ctx.Repo.CanWrite(models.UnitTypeIssues) { @@ -640,6 +657,8 @@ func ViewIssue(ctx *context.Context) { ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireDropzone"] = true ctx.Data["RequireTribute"] = true + // ctx.Data["IsProjectsEnabled"] = settings. + renderAttachmentSettings(ctx) err = issue.LoadAttributes() @@ -711,6 +730,8 @@ func ViewIssue(ctx *context.Context) { // Check milestone and assignee. if ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { RetrieveRepoMilestonesAndAssignees(ctx, repo) + retrieveProjects(ctx, repo) + if ctx.Written() { return } diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 1057dbc9c8e2f..de33e70669693 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -15,8 +15,9 @@ import ( ) const ( - tplProjects base.TplName = "repo/projects/list" - tplProjectsNew base.TplName = "repo/projects/new" + tplProjects base.TplName = "repo/projects/list" + tplProjectsNew base.TplName = "repo/projects/new" + tplProjectsView base.TplName = "repo/projects/view" projectTemplateKey = "ProjectTemplate" ) @@ -221,3 +222,49 @@ func EditProjectPost(ctx *context.Context, form auth.CreateProjectForm) { ctx.Flash.Success(ctx.Tr("repo.projects.edit_success", p.Title)) ctx.Redirect(ctx.Repo.RepoLink + "/projects") } + +// ViewProject renders the kanban board for a project +func ViewProject(ctx *context.Context) { + + p, err := models.GetProjectByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) + if err != nil { + if models.IsErrProjectNotExist(err) { + ctx.NotFound("", nil) + } else { + ctx.ServerError("GetProjectByRepoID", err) + } + return + } + + ctx.Data["Title"] = p.Title + ctx.Data["PageIsProjects"] = true + ctx.Data["RequiresDraggable"] = true + + ctx.HTML(200, tplProjectsView) +} + +// UpdateIssueProject change an issue's project +func UpdateIssueProject(ctx *context.Context) { + issues := getActionIssues(ctx) + if ctx.Written() { + return + } + + projectID := ctx.QueryInt64("id") + for _, issue := range issues { + oldProjectID := issue.ProjectID + if oldProjectID == projectID { + continue + } + + issue.ProjectID = projectID + if err := models.ChangeProjectAssign(issue, ctx.User, oldProjectID); err != nil { + ctx.ServerError("ChangeProjectAssign", err) + return + } + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index f8342a6daa4c0..864d2383e8496 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -709,6 +709,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) + m.Post("/projects", reqRepoIssuesOrPullsWriter, repo.UpdateIssueProject) m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus) }, context.RepoMustNotBeArchived()) @@ -815,6 +816,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("", repo.Projects) m.Get("/new", repo.NewProject) m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost) + m.Get("/:id", repo.ViewProject) m.Get("/:id/:action", repo.ChangeProjectStatus) m.Post("/:id/edit", bindIgnErr(auth.CreateProjectForm{}), repo.EditProjectPost) m.Get("/:id/edit", repo.EditProject) From 1c3c5fb52e090c7fcee288b9edc3f45dd718bc85 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 14 Sep 2019 19:36:36 +0100 Subject: [PATCH 019/216] add comments when you change a project --- models/issue_comment.go | 46 ++++++++++++++++--- models/migrations/migrations.go | 2 + models/migrations/v96.go | 24 ++++++++++ routers/repo/issue.go | 20 ++++++++ .../repo/issue/view_content/comments.tmpl | 21 ++++++++- .../repo/issue/view_content/sidebar.tmpl | 44 ++++++++++++++++++ 6 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 models/migrations/v96.go diff --git a/models/issue_comment.go b/models/issue_comment.go index 830487c56359f..b2a53d6ce6204 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -110,6 +110,10 @@ type Comment struct { Issue *Issue `xorm:"-"` LabelID int64 Label *Label `xorm:"-"` + OldProjectID int64 + ProjectID int64 + OldProject *Project `xorm:"-"` + Project *Project `xorm:"-"` OldMilestoneID int64 MilestoneID int64 OldMilestone *Milestone `xorm:"-"` @@ -301,6 +305,32 @@ func (c *Comment) LoadLabel() error { return nil } +// LoadProject if comment.Type is CommentTypeProject, then load project. +func (c *Comment) LoadProject() error { + + if c.OldProjectID > 0 { + var oldProject Project + has, err := x.ID(c.OldProjectID).Get(&oldProject) + if err != nil { + return err + } else if has { + c.OldProject = &oldProject + } + } + + if c.ProjectID > 0 { + var project Project + has, err := x.ID(c.ProjectID).Get(&project) + if err != nil { + return err + } else if has { + c.Project = &project + } + } + + return nil +} + // LoadMilestone if comment.Type is CommentTypeMilestone, then load milestone func (c *Comment) LoadMilestone() error { if c.OldMilestoneID > 0 { @@ -519,6 +549,8 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err LabelID: LabelID, OldMilestoneID: opts.OldMilestoneID, MilestoneID: opts.MilestoneID, + OldProjectID: opts.OldProjectID, + ProjectID: opts.ProjectID, RemovedAssignee: opts.RemovedAssignee, AssigneeID: opts.AssigneeID, CommitID: opts.CommitID, @@ -666,12 +698,12 @@ func createLabelComment(e *xorm.Session, doer *User, repo *Repository, issue *Is func createProjectComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue, oldProjectID, projectID int64) (*Comment, error) { return createComment(e, &CreateCommentOptions{ - Type: CommentTypeProject, - Doer: doer, - Repo: repo, - Issue: issue, - OldMilestoneID: oldProjectID, - MilestoneID: projectID, + Type: CommentTypeProject, + Doer: doer, + Repo: repo, + Issue: issue, + OldProjectID: oldProjectID, + ProjectID: projectID, }) } @@ -797,6 +829,8 @@ type CreateCommentOptions struct { DependentIssueID int64 OldMilestoneID int64 MilestoneID int64 + OldProjectID int64 + ProjectID int64 AssigneeID int64 RemovedAssignee bool OldTitle string diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 2e4424431bf9b..896ceda6059ec 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -246,6 +246,8 @@ var migrations = []Migration{ NewMigration("add projects info to database", addProjectsInfo), // v94 -> v95 NewMigration("add projects database table", addProjectsTable), + // v95 -> v96 + NewMigration("add project ID to comments table", addProjectIDToCommentsTable), } // Migrate database to current version diff --git a/models/migrations/v96.go b/models/migrations/v96.go new file mode 100644 index 0000000000000..ff4d8b6031d50 --- /dev/null +++ b/models/migrations/v96.go @@ -0,0 +1,24 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import "github.com/go-xorm/xorm" + +func addProjectIDToCommentsTable(x *xorm.Engine) error { + + sess := x.NewSession() + defer sess.Close() + + type Comment struct { + OldProjectID int64 + ProjectID int64 + } + + if err := sess.Sync2(new(Comment)); err != nil { + return err + } + + return sess.Commit() +} diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 190b4ec3b312f..bb509cf8a4146 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -845,6 +845,26 @@ func ViewIssue(ctx *context.Context) { if comment.MilestoneID > 0 && comment.Milestone == nil { comment.Milestone = ghostMilestone } + } else if comment.Type == models.CommentTypeProject { + + if err = comment.LoadProject(); err != nil { + ctx.ServerError("LoadProject", err) + return + } + + ghostProject := &models.Project{ + ID: -1, + Title: ctx.Tr("repo.issues.deleted_project"), + } + + if comment.OldProjectID > 0 && comment.Project == nil { + comment.OldProject = ghostProject + } + + if comment.ProjectID > 0 && comment.Project == nil { + comment.Project = ghostProject + } + } else if comment.Type == models.CommentTypeAssignees { if err = comment.LoadAssigneeUser(); err != nil { ctx.ServerError("LoadAssigneeUser", err) diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 70de314c918dc..4e6ed30e27647 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -6,7 +6,7 @@ 5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE, 19 = ADD_DEPENDENCY, 20 = REMOVE_DEPENDENCY, 21 = CODE, - 22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED --> + 22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED 25 = PROJECT BOARD CHANGED --> {{if eq .Type 0}}
    {{if .OriginalAuthor }} @@ -395,5 +395,24 @@ {{$.i18n.Tr "repo.issues.unlock_comment" $createdStr | Safe}}
    + {{else if eq .Type 25}} +
    + + + + + {{.Poster.GetDisplayName}} + {{if gt .OldProjectID 0}} + {{if gt .ProjectID 0}} + {{$.i18n.Tr "repo.issues.change_project_at" (.OldProject.Title|Escape) (.Project.Title|Escape) $createdStr | Safe}} + + {{else}} + {{$.i18n.Tr "repo.issues.remove_milestone_at" (.OldProject.Title|Escape) $createdStr | Safe}} + {{end}} + {{else if gt .ProjectID 0}} + {{$.i18n.Tr "repo.issues.add_milestone_at" (.Project.Title|Escape) $createdStr | Safe}} + {{end}} + +
    {{end}} {{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index e50ac0a4b126b..2a78def702ee7 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -25,6 +25,48 @@ {{end}} + +
    + + +
    + {{.i18n.Tr "repo.issues.new.no_projects"}} +
    + {{if .Issue.ProjectID}} + {{.Issue.Project.Title}} + {{end}} +
    +
    + +
    +
    @@ -420,6 +463,7 @@ {{ end }} + {{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}} From e2e9feaf2a087d78dcc1b23bc81e6e9944f09a65 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 14 Sep 2019 20:08:36 +0100 Subject: [PATCH 020/216] allow filtering issues by projectID --- models/issue.go | 5 +++++ options/locale/locale_en-US.ini | 3 +++ routers/repo/issue.go | 5 +++-- routers/repo/milestone.go | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/models/issue.go b/models/issue.go index 7a2b9663b4bc7..5f80c302114f2 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1316,6 +1316,7 @@ type IssuesOptions struct { PosterID int64 MentionedID int64 MilestoneID int64 + ProjectID int64 Page int PageSize int IsClosed util.OptionalBool @@ -1396,6 +1397,10 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) { sess.And("issue.milestone_id=?", opts.MilestoneID) } + if opts.ProjectID > 0 { + sess.And("issue.project_id=?", opts.ProjectID) + } + switch opts.IsPull { case util.OptionalBoolTrue: sess.And("issue.is_pull=?", true) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b425a7a9f8073..e98e0c05b9dae 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -798,8 +798,11 @@ issues.add_label_at = added the
    %s
    label %s issues.add_milestone_at = `added this to the %s milestone %s` issues.change_milestone_at = `modified the milestone from %s to %s %s` +issues.change_project_at = `modified the project from %s to %s %s` issues.remove_milestone_at = `removed this from the %s milestone %s` +issues.remove_project_at = `removed this from the %s project %s` issues.deleted_milestone = `(deleted)` +issues.deleted_project = `(deleted)` issues.self_assign_at = `self-assigned this %s` issues.add_assignee_at = `was assigned by %s %s` issues.remove_assignee_at = `was unassigned by %s %s` diff --git a/routers/repo/issue.go b/routers/repo/issue.go index bb509cf8a4146..731b7f195bf7f 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -100,7 +100,7 @@ func MustAllowPulls(ctx *context.Context) { } } -func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalBool) { +func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption util.OptionalBool) { var err error viewType := ctx.Query("type") sortType := ctx.Query("sort") @@ -196,6 +196,7 @@ func issues(ctx *context.Context, milestoneID int64, isPullOption util.OptionalB PosterID: posterID, MentionedID: mentionedID, MilestoneID: milestoneID, + ProjectID: projectID, Page: pager.Paginater.Current(), PageSize: setting.UI.IssuePagingNum, IsClosed: util.OptionalBoolOf(isShowClosed), @@ -300,7 +301,7 @@ func Issues(ctx *context.Context) { ctx.Data["PageIsIssueList"] = true } - issues(ctx, ctx.QueryInt64("milestone"), util.OptionalBoolOf(isPullList)) + issues(ctx, ctx.QueryInt64("milestone"), ctx.QueryInt64("project"), util.OptionalBoolOf(isPullList)) var err error // Get milestones. diff --git a/routers/repo/milestone.go b/routers/repo/milestone.go index b4056cc6d1ab6..1af7b64dee860 100644 --- a/routers/repo/milestone.go +++ b/routers/repo/milestone.go @@ -266,7 +266,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) { ctx.Data["Title"] = milestone.Name ctx.Data["Milestone"] = milestone - issues(ctx, milestoneID, util.OptionalBoolNone) + issues(ctx, milestoneID, 0, util.OptionalBoolNone) perm, err := models.GetUserRepoPermission(ctx.Repo.Repository, ctx.User) if err != nil { From f6f60e3d0597bf8b6c625158a6aded91c7b55ccb Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Thu, 19 Sep 2019 00:01:43 +0100 Subject: [PATCH 021/216] fix build --- models/migrations/v94.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/models/migrations/v94.go b/models/migrations/v94.go index ab9bf29218ecc..5fe8c3fa12d26 100644 --- a/models/migrations/v94.go +++ b/models/migrations/v94.go @@ -6,17 +6,6 @@ package migrations import "github.com/go-xorm/xorm" -<<<<<<< HEAD -func addProjectsInfo(x *xorm.Engine) error { - - type Repository struct { - NumProjects int `xorm:"NOT NULL DEFAULT 0"` - NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"` - NumOpenProjects int `xorm:"-"` - } - - return x.Sync2(new(Repository)) -======= func addStatusCheckColumnsForProtectedBranches(x *xorm.Engine) error { type ProtectedBranch struct { EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` @@ -32,5 +21,4 @@ func addStatusCheckColumnsForProtectedBranches(x *xorm.Engine) error { StatusCheckContexts: []string{}, }) return err ->>>>>>> origin } From f7a8af5195220c43947988ca2e2b834a3c1c84f7 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 22 Sep 2019 11:33:34 +0100 Subject: [PATCH 022/216] fix compilation after merge conflict --- models/migrations/v96.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/migrations/v96.go b/models/migrations/v96.go index f4dfcd77e5c41..7f042d5cb48d2 100644 --- a/models/migrations/v96.go +++ b/models/migrations/v96.go @@ -14,6 +14,9 @@ import ( func deleteOrphanedAttachments(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + type Attachment struct { ID int64 `xorm:"pk autoincr"` UUID string `xorm:"uuid UNIQUE"` From 947bb7a21e47e30def0dc50de1576f568895096a Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 22 Sep 2019 11:39:11 +0100 Subject: [PATCH 023/216] fix migration's arrangement --- models/migrations/migrations.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index a74c2005aea6b..eb6f0d2b10147 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -243,17 +243,17 @@ var migrations = []Migration{ // v93 -> v94 NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), // v93 -> v94 - NewMigration("add projects database table", addProjectsTable), - // v94 -> v95 - NewMigration("add project ID to comments table", addProjectIDToCommentsTable), - // v95 -> v96 NewMigration("add enable_status_check, status_check_contexts to protected_branch", addStatusCheckColumnsForProtectedBranches), // v95 -> v96 NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns), // v96 -> v97 NewMigration("delete orphaned attachments", deleteOrphanedAttachments), // v97 -> v98 + NewMigration("add projects database table", addProjectsTable), + // v98 -> v99 NewMigration("add projects info to database", addProjectsInfo), + // v99 -> v100 + NewMigration("add project ID to comments table", addProjectIDToCommentsTable), } // Migrate database to current version From 16d79502bbbad4fba472ae31d6677310cc8ffe5c Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 22 Sep 2019 12:38:54 +0100 Subject: [PATCH 024/216] save repo ID too --- models/projects.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/models/projects.go b/models/projects.go index 18f48749aef6a..364818f5e6eca 100644 --- a/models/projects.go +++ b/models/projects.go @@ -18,6 +18,7 @@ type ProjectBoard struct { ID int64 `xorm:"pk autoincr"` ProjectID int64 `xorm:"INDEX NOT NULL"` Title string + RepoID int64 `xorm:"INDEX NOT NULL"` // Not really needed but helpful CreatorID int64 `xorm:"NOT NULL"` @@ -161,7 +162,7 @@ func createBoardsForProjectsType(sess *xorm.Session, project *Project) error { return nil } - var boards = make([]ProjectBoard, 0) + var boards = make([]ProjectBoard, 0, len(items)) for _, v := range items { boards = append(boards, ProjectBoard{ @@ -170,6 +171,7 @@ func createBoardsForProjectsType(sess *xorm.Session, project *Project) error { CreatorID: project.CreatorID, Title: v, ProjectID: project.ID, + RepoID: project.RepoID, }) } From 74b92ab0f65e2f0c2ee0bcdc7bfd3d87202dda88 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 22 Sep 2019 12:39:17 +0100 Subject: [PATCH 025/216] save repo ID too --- models/migrations/migrations.go | 2 +- models/migrations/v98.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index eb6f0d2b10147..e5144b1075b86 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -242,7 +242,7 @@ var migrations = []Migration{ NewMigration("add index on owner_id of repository and type, review_id of comment", addIndexOnRepositoryAndComment), // v93 -> v94 NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), - // v93 -> v94 + // v94 -> v95 NewMigration("add enable_status_check, status_check_contexts to protected_branch", addStatusCheckColumnsForProtectedBranches), // v95 -> v96 NewMigration("add table columns for cross referencing issues", addCrossReferenceColumns), diff --git a/models/migrations/v98.go b/models/migrations/v98.go index 3822ac3599444..90020efae897e 100644 --- a/models/migrations/v98.go +++ b/models/migrations/v98.go @@ -46,6 +46,7 @@ func addProjectsTable(x *xorm.Engine) error { ID int64 `xorm:"pk autoincr"` ProjectID int64 `xorm:"INDEX NOT NULL"` Title string + RepoID int64 `xorm:"INDEX NOT NULL"` // Not really needed but helpful CreatorID int64 `xorm:"NOT NULL"` From 1832878e8bf77beb60a98e02ea2c47cc838011a8 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 28 Sep 2019 00:50:14 +0100 Subject: [PATCH 026/216] Initial view for kanban --- models/projects.go | 20 +++++++++ options/locale/locale_en-US.ini | 1 + public/css/index.css | 12 ++++++ public/js/index.js | 1 + routers/repo/projects.go | 60 ++++++++++++++++++++++++++ templates/repo/projects/view.tmpl | 70 +++++++++++++++++++++++++++++++ 6 files changed, 164 insertions(+) create mode 100644 templates/repo/projects/view.tmpl diff --git a/models/projects.go b/models/projects.go index 364818f5e6eca..9e2b7611b8376 100644 --- a/models/projects.go +++ b/models/projects.go @@ -13,6 +13,9 @@ import ( // ProjectType is used to represent a project board type type ProjectType uint8 +// ProjectBoards is a list of all project boards in a repository. +type ProjectBoards []ProjectBoard + // ProjectBoard is used to represent boards on a kanban project type ProjectBoard struct { ID int64 `xorm:"pk autoincr"` @@ -373,3 +376,20 @@ func changeProjectAssign(sess *xorm.Session, doer *User, issue *Issue, oldProjec return updateIssueCols(sess, issue, "project_id") } + +// GetProjectBoards fetches all boards related to a project +func GetProjectBoards(repoID, projectID int64) ([]ProjectBoard, error) { + + var boards = make([]ProjectBoard, 0) + + sess := x.Where("repo_id=? AND project_id=?", repoID, projectID) + return boards, sess.Find(&boards) +} + +// GetProjectIssues fetches issues for a specific project +func GetProjectIssues(repoID, projectID int64) ([]*Issue, error) { + return Issues(&IssuesOptions{ + RepoIDs: []int64{repoID}, + ProjectID: projectID, + }) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index d53a06b779ab6..14eed3f7ba753 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -764,6 +764,7 @@ projects.type.basic_kanban = "Basic Kanban" projects.type.bug_triage = "Bug Triage" projects.template.desc = "Project template" projects.template.desc_helper = "Select a project template to get started" +projects.type.uncategorized = Uncategorized issues.desc = Organize bug reports, tasks and milestones. issues.new = New Issue diff --git a/public/css/index.css b/public/css/index.css index 1da2399c461b8..063282bfa3153 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -878,6 +878,18 @@ tbody.commit-list{vertical-align:baseline} .repo-buttons .disabled-repo-button a.button:hover{background:0 0!important;color:rgba(0,0,0,.6)!important;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset!important} .repo-buttons .ui.labeled.button>.label{border-left:0!important;margin:0!important} .tag-code,.tag-code td{background-color:#f0f0f0!important;border-color:#d3cfcf!important;padding-top:8px;padding-bottom:8px} +.board{display:flex;flex-direction:row;flex-wrap:nowrap;overflow-x:auto;margin:0 .5em} +.board-column{background-color:#eff1f3!important;border:1px solid rgba(34,36,38,.15)!important;margin:0 .5em!important;padding:5px!important;width:320px;height:60vh;overflow-y:scroll;flex:0 0 auto} +.board-column>.cards{margin:0!important} +.board-column>.board-label{margin-left:5px!important;margin-top:3px!important} +.board-column>.divider{margin:5px 0} +.board-column:first-child{margin-left:auto!important} +.board-column:last-child{margin-right:auto!important} +.board-card{margin:3px!important} +.board-card .header{font-size:1.1em!important} +.board-card .content{padding:5px 8px!important} +.board-card .extra.content{padding:5px 8px!important} +.board-label{padding:.4em .6em!important;margin-right:.4em!important;margin-bottom:.4em!important} .CodeMirror{font:14px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} .CodeMirror.cm-s-default{border-radius:3px;padding:0!important} .CodeMirror .cm-comment{background:inherit!important} diff --git a/public/js/index.js b/public/js/index.js index ad5e3912de15c..3ee7de8230ebe 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -572,6 +572,7 @@ function initCommentForm() { // Milestone and assignee selectItem('.select-milestone', '#milestone_id'); selectItem('.select-assignee', '#assignee_id'); + selectItem('.select-project', '#projects_id'); } function initInstall() { diff --git a/routers/repo/projects.go b/routers/repo/projects.go index de33e70669693..5612b75d3f5f9 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -236,6 +236,66 @@ func ViewProject(ctx *context.Context) { return } + boards, err := models.GetProjectBoards(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) + if err != nil { + ctx.ServerError("GetProjectBoards", err) + return + } + + issues, err := models.GetProjectIssues(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) + if err != nil { + ctx.ServerError("GetProjectIssues", err) + return + } + + type data struct { + Board models.ProjectBoard + Issues []*models.Issue + idx int + } + + var seen map[int64]data = make(map[int64]data, 0) + var tmplData = make([]data, 0, len(boards)) + + uncategorizedBoard := data{ + Board: models.ProjectBoard{ + ID: 0, + ProjectID: 0, + Title: ctx.Tr("repo.projects.type.uncategorized"), + }, + } + + tmplData = append(tmplData, uncategorizedBoard) + + for i := range boards { + d := data{Board: boards[i], idx: i} + + seen[boards[i].ID] = d + + tmplData = append(tmplData, d) + } + + for i := range issues { + var currentData data + var ok bool + + currentData, ok = seen[issues[i].ProjectBoardID] + if !ok { + currentData = tmplData[0] + } + + currentData.Issues = append(currentData.Issues, issues[i]) + + if ok { + tmplData[currentData.idx] = currentData + } else { + tmplData[0] = currentData + } + } + + ctx.Data["Boards"] = boards + ctx.Data["Issues"] = issues + ctx.Data["ProjectBoards"] = tmplData ctx.Data["Title"] = p.Title ctx.Data["PageIsProjects"] = true ctx.Data["RequiresDraggable"] = true diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl new file mode 100644 index 0000000000000..f682b4f3285bd --- /dev/null +++ b/templates/repo/projects/view.tmpl @@ -0,0 +1,70 @@ +{{template "base/head" .}} +
    + {{template "repo/header" .}} +
    +
    +
    + {{template "repo/issue/navbar" .}} +
    +
    + {{template "repo/issue/search" .}} +
    +
    + {{if .PageIsProjects}} + {{.i18n.Tr "repo.projects.new"}} + {{end}} +
    +
    +
    +
    + + +
    + +
    + {{ range .ProjectBoards }} + +
    +
    {{.Board.Title}}
    +
    + +
    + + {{ $board := .Board }} + {{ range .Issues }} +
    +
    + +
    + {{ if $board.ID }} + {{ $board.ID }} + {{ end }} + + {{ if .MilestoneID }} + + {{ .Milestone.Name }} + + {{ end }} +
    +
    +
    + {{ range .Labels }} + {{.Name}} + {{ end }} +
    +
    + {{ end }} +
    +
    + {{ end }} +
    + +
    + +
    + + +{{template "base/footer" .}} From 4440cbaf28d717107016ca4929836dbbb06adf11 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 28 Sep 2019 00:52:14 +0100 Subject: [PATCH 027/216] fix board url --- templates/repo/projects/view.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl index f682b4f3285bd..2c8906c40a5a7 100644 --- a/templates/repo/projects/view.tmpl +++ b/templates/repo/projects/view.tmpl @@ -37,7 +37,7 @@
    {{ if $board.ID }} - {{ $board.ID }} + {{ $board.ID }} {{ end }} {{ if .MilestoneID }} From 21699b65a74decf232317b98e8425ee139befc43 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 28 Sep 2019 00:56:15 +0100 Subject: [PATCH 028/216] Commit less changes --- public/less/_repository.less | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/public/less/_repository.less b/public/less/_repository.less index fde11f7a4d619..49b09ef4308f0 100644 --- a/public/less/_repository.less +++ b/public/less/_repository.less @@ -2384,3 +2384,65 @@ tbody.commit-list { padding-top: 8px; padding-bottom: 8px; } + +.board { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + overflow-x: auto; + margin: 0 0.5em; +} + +.board-column { + background-color: #eff1f3 !important; + border: 1px solid rgba(34, 36, 38, 0.15) !important; + margin: 0 0.5em !important; + padding: 5px !important; + width: 320px; + height: 60vh; + overflow-y: scroll; + flex: 0 0 auto; +} + +.board-column > .cards { + margin: 0 !important; +} + +.board-column > .board-label { + margin-left: 5px !important; + margin-top: 3px !important; +} + +.board-column > .divider { + margin: 5px 0; +} + +.board-column:first-child { + margin-left: auto !important; +} + +.board-column:last-child { + margin-right: auto !important; +} + +.board-card { + margin: 3px !important; +} + +.board-card .header { + font-size: 1.1em !important; +} + +.board-card .content { + padding: 5px 8px !important; +} + +.board-card .extra.content { + padding: 5px 8px !important; +} + +.board-label { + padding: 0.4em 0.6em !important; + margin-right: 0.4em !important; + margin-bottom: 0.4em !important; +} From 91b0b774da4f5e4b096ef70d091040cdc066698a Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 28 Sep 2019 17:14:04 +0100 Subject: [PATCH 029/216] Implement sortable library --- public/js/index.js | 16 ++++++++++++++++ public/vendor/plugins/sortable/sortable.min.js | 2 ++ templates/base/footer.tmpl | 9 +++++++++ templates/repo/projects/view.tmpl | 2 +- 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 public/vendor/plugins/sortable/sortable.min.js diff --git a/public/js/index.js b/public/js/index.js index 3ee7de8230ebe..45abd31660bdd 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -2555,6 +2555,22 @@ function cancelStopwatch() { $("#cancel_stopwatch_form").submit(); } +function initKanbanBoard(appElementID) { + + const el = document.getElementById(appElementID) + if (!el) { + return + } + + new Sortable(el, { + group: "shared", + animation: 150, + onAdd: function(e) { + console.log(e) + }, + }) +} + function initHeatmap(appElementId, heatmapUser, locale) { const el = document.getElementById(appElementId); if (!el) { diff --git a/public/vendor/plugins/sortable/sortable.min.js b/public/vendor/plugins/sortable/sortable.min.js new file mode 100644 index 0000000000000..693054f6255b0 --- /dev/null +++ b/public/vendor/plugins/sortable/sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.10.0 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function o(t){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function a(){return(a=Object.assign||function(t){for(var e=1;e"===e[0]&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return!1}return!1}}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"===e[0]?t.parentNode===n&&h(t,e):h(t,e))||o&&t===n)return t;if(t===n)break}while(t=(i=t).host&&i!==document&&i.host.nodeType?i.host:i.parentNode)}var i;return null}var f,p=/\s+/g;function k(t,e,n){if(t&&e)if(t.classList)t.classList[n?"add":"remove"](e);else{var o=(" "+t.className+" ").replace(p," ").replace(" "+e+" "," ");t.className=(o+(n?" "+e:"")).replace(p," ")}}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in o||-1!==e.indexOf("webkit")||(e="-webkit-"+e),o[e]=n+("string"==typeof n?"":"px")}}function v(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform");o&&"none"!==o&&(n=o+" "+n)}while(!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix;return i&&new i(n)}function g(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=e.left-n&&r<=e.right+n,i=a>=e.top-n&&a<=e.bottom+n;return n&&o&&i?l=t:void 0}}),l}((t=t.touches?t.touches[0]:t).clientX,t.clientY);if(e){var n={};for(var o in t)t.hasOwnProperty(o)&&(n[o]=t[o]);n.target=n.rootEl=e,n.preventDefault=void 0,n.stopPropagation=void 0,e[j]._onDragOver(n)}}}function Pt(t){z&&z.parentNode[j]._isOutsideThisEl(t.target)}function kt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[j]=this;var n={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Mt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==kt.supportPointer&&"PointerEvent"in window,emptyInsertThreshold:5};for(var o in O.initializePlugins(this,t,n),n)o in e||(e[o]=n[o]);for(var i in Ot(e),this)"_"===i.charAt(0)&&"function"==typeof this[i]&&(this[i]=this[i].bind(this));this.nativeDraggable=!e.forceFallback&&Tt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?u(t,"pointerdown",this._onTapStart):(u(t,"mousedown",this._onTapStart),u(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(u(t,"dragover",this),u(t,"dragenter",this)),bt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,T())}function Rt(t,e,n,o,i,r,a,l){var s,c,u=t[j],d=u.options.onMove;return!window.CustomEvent||w||E?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),d&&(c=d.call(u,s,a)),c}function Xt(t){t.draggable=!1}function Yt(){Dt=!1}function Bt(t){for(var e=t.tagName+t.className+t.src+t.href+t.textContent,n=e.length,o=0;n--;)o+=e.charCodeAt(n);return o.toString(36)}function Ft(t){return setTimeout(t,0)}function Ht(t){return clearTimeout(t)}kt.prototype={constructor:kt,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(ht=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,z):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(function(t){St.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&St.push(o)}}(o),!z&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled||s.isContentEditable||(l=P(l,t.draggable,o,!1))&&l.animated||Z===l)){if(J=F(l),et=F(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return W({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),K("filter",n,{evt:e}),void(i&&e.cancelable&&e.preventDefault())}else if(c&&(c=c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return W({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),K("filter",n,{evt:e}),!0})))return void(i&&e.cancelable&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;if(n&&!z&&n.parentNode===r){var s=X(n);if(q=r,G=(z=n).parentNode,V=z.nextSibling,Z=n,ot=a.group,rt={target:kt.dragged=z,clientX:(e||t).clientX,clientY:(e||t).clientY},ct=rt.clientX-s.left,ut=rt.clientY-s.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,z.style["will-change"]="all",o=function(){K("delayEnded",i,{evt:t}),kt.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!c&&i.nativeDraggable&&(z.draggable=!0),i._triggerDragStart(t,e),W({sortable:i,name:"choose",originalEvent:t}),k(z,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){g(z,t.trim(),Xt)}),u(l,"dragover",It),u(l,"mousemove",It),u(l,"touchmove",It),u(l,"mouseup",i._onDrop),u(l,"touchend",i._onDrop),u(l,"touchcancel",i._onDrop),c&&this.nativeDraggable&&(this.options.touchStartThreshold=4,z.draggable=!0),K("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(E||w))o();else{if(kt.eventCanceled)return void this._onDrop();u(l,"mouseup",i._disableDelayedDrag),u(l,"touchend",i._disableDelayedDrag),u(l,"touchcancel",i._disableDelayedDrag),u(l,"mousemove",i._delayedDragTouchMoveHandler),u(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&u(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)}}},_delayedDragTouchMoveHandler:function(t){var e=t.touches?t.touches[0]:t;Math.max(Math.abs(e.clientX-this._lastX),Math.abs(e.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){z&&Xt(z),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;d(t,"mouseup",this._disableDelayedDrag),d(t,"touchend",this._disableDelayedDrag),d(t,"touchcancel",this._disableDelayedDrag),d(t,"mousemove",this._delayedDragTouchMoveHandler),d(t,"touchmove",this._delayedDragTouchMoveHandler),d(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?u(document,"pointermove",this._onTouchMove):u(document,e?"touchmove":"mousemove",this._onTouchMove):(u(z,"dragend",this),u(q,"dragstart",this._onDragStart));try{document.selection?Ft(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){if(vt=!1,q&&z){K("dragStarted",this,{evt:e}),this.nativeDraggable&&u(document,"dragover",Pt);var n=this.options;t||k(z,n.dragClass,!1),k(z,n.ghostClass,!0),kt.active=this,t&&this._appendGhost(),W({sortable:this,name:"start",originalEvent:e})}else this._nulling()},_emulateDragOver:function(){if(at){this._lastX=at.clientX,this._lastY=at.clientY,At();for(var t=document.elementFromPoint(at.clientX,at.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(at.clientX,at.clientY))!==e;)e=t;if(z.parentNode[j]._isOutsideThisEl(t),e)do{if(e[j]){if(e[j]._onDragOver({clientX:at.clientX,clientY:at.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}t=e}while(e=e.parentNode);Nt()}},_onTouchMove:function(t){if(rt){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=U&&v(U),a=U&&r&&r.a,l=U&&r&&r.d,s=_t&>&&b(gt),c=(i.clientX-rt.clientX+o.x)/(a||1)+(s?s[0]-Et[0]:0)/(a||1),u=(i.clientY-rt.clientY+o.y)/(l||1)+(s?s[1]-Et[1]:0)/(l||1);if(!kt.active&&!vt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))o.right+10||t.clientX<=o.right&&t.clientY>o.bottom&&t.clientX>=o.left:t.clientX>o.right&&t.clientY>o.top||t.clientX<=o.right&&t.clientY>o.bottom+10}(n,a,this)&&!g.animated){if(g===z)return A(!1);if(g&&l===n.target&&(s=g),s&&(i=X(s)),!1!==Rt(q,l,z,o,s,i,n,!!s))return O(),l.appendChild(z),G=l,N(),A(!0)}else if(s.parentNode===l){i=X(s);var v,m,b,y=z.parentNode!==l,w=!function(t,e,n){var o=n?t.left:t.top,i=n?t.right:t.bottom,r=n?t.width:t.height,a=n?e.left:e.top,l=n?e.right:e.bottom,s=n?e.width:e.height;return o===a||i===l||o+r/2===a+s/2}(z.animated&&z.toRect||o,s.animated&&s.toRect||i,a),E=a?"top":"left",D=Y(s,"top","top")||Y(z,"top","top"),S=D?D.scrollTop:void 0;if(ht!==s&&(m=i[E],yt=!1,wt=!w&&e.invertSwap||y),0!==(v=function(t,e,n,o,i,r,a,l){var s=o?t.clientY:t.clientX,c=o?n.height:n.width,u=o?n.top:n.left,d=o?n.bottom:n.right,h=!1;if(!a)if(l&&pt {{end}} +{{ if .PageIsProjects }} + + +{{ end}} {{template "custom/footer" .}} diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl index 2c8906c40a5a7..e6fed521fe92e 100644 --- a/templates/repo/projects/view.tmpl +++ b/templates/repo/projects/view.tmpl @@ -28,7 +28,7 @@
    {{.Board.Title}}
    -
    +
    {{ $board := .Board }} {{ range .Issues }} From 9641f3837fd016f5cc9b34cf8e4c28bb70a01f80 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 28 Sep 2019 18:36:56 +0100 Subject: [PATCH 030/216] move card across all boards --- templates/base/footer.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 96387fbd914f8..5f9d3bb30ce41 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -132,9 +132,8 @@ {{ if .PageIsProjects }} {{ end}} From e3e4bdbcf318c510bbbebf033fea730efee8b498 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Mon, 30 Sep 2019 21:04:38 +0100 Subject: [PATCH 031/216] cards can be moved from board to board --- models/error.go | 18 ++++++++++- models/projects.go | 33 ++++++++++++++++++++ public/js/index.js | 14 +++++++-- routers/repo/projects.go | 51 +++++++++++++++++++++++++++++-- routers/routes/routes.go | 1 + templates/base/footer.tmpl | 4 +-- templates/repo/projects/view.tmpl | 9 ++---- 7 files changed, 117 insertions(+), 13 deletions(-) diff --git a/models/error.go b/models/error.go index d3bebea5f2521..a8ac59c843396 100644 --- a/models/error.go +++ b/models/error.go @@ -1277,7 +1277,7 @@ type ErrProjectNotExist struct { RepoID int64 } -// IsErrProjetcNotExist checks if an error is a ErrProjectNotExist +// IsErrProjectNotExist checks if an error is a ErrProjectNotExist func IsErrProjectNotExist(err error) bool { _, ok := err.(ErrProjectNotExist) return ok @@ -1287,6 +1287,22 @@ func (err ErrProjectNotExist) Error() string { return fmt.Sprintf("projects does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID) } +type ErrProjectBoardNotExist struct { + BoardID int64 + RepoID int64 + ProjectID int64 +} + +// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist +func IsErrProjectBoardNotExist(err error) bool { + _, ok := err.(ErrProjectBoardNotExist) + return ok +} + +func (err ErrProjectBoardNotExist) Error() string { + return fmt.Sprintf("project board does not exist [board_id: %d, repo_id: %d, project_id: %d]", err.BoardID, err.RepoID, err.ProjectID) +} + // _____ .__.__ __ // / \ |__| | ____ _______/ |_ ____ ____ ____ // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ diff --git a/models/projects.go b/models/projects.go index 9e2b7611b8376..a501b866d522e 100644 --- a/models/projects.go +++ b/models/projects.go @@ -377,6 +377,39 @@ func changeProjectAssign(sess *xorm.Session, doer *User, issue *Issue, oldProjec return updateIssueCols(sess, issue, "project_id") } +// MoveIsssueAcrossProjectBoards move a card from one board to another +func MoveIssueAcrossProjectBoards(issue *Issue, board *ProjectBoard) error { + + sess := x.NewSession() + defer sess.Close() + + issue.ProjectBoardID = board.ID + + if err := updateIssueCols(sess, issue, "project_board_id"); err != nil { + return err + } + + return sess.Commit() +} + +// GetProjectBoard fetches the current board of a project +func GetProjectBoard(repoID, projectID, boardID int64) (*ProjectBoard, error) { + board := &ProjectBoard{ + ID: boardID, + RepoID: repoID, + ProjectID: projectID, + } + + has, err := x.Get(board) + if err != nil { + return nil, err + } else if !has { + return nil, ErrProjectBoardNotExist{RepoID: repoID, BoardID: boardID, ProjectID: projectID} + } + + return board, nil +} + // GetProjectBoards fetches all boards related to a project func GetProjectBoards(repoID, projectID int64) ([]ProjectBoard, error) { diff --git a/public/js/index.js b/public/js/index.js index 45abd31660bdd..2adc56f98f17e 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -2566,8 +2566,18 @@ function initKanbanBoard(appElementID) { group: "shared", animation: 150, onAdd: function(e) { - console.log(e) - }, + $.ajax(e.to.dataset.url + "/" + e.item.dataset.issue, { + headers: { + 'X-Csrf-Token': csrf, + 'X-Remote': true, + }, + contentType: 'application/json', + type: 'POST', + success: function () { + // setTimeout(reload(),3000) + }, + }) + } }) } diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 5612b75d3f5f9..fce5f3a47335a 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -5,6 +5,8 @@ package repo import ( + "fmt" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" @@ -255,7 +257,7 @@ func ViewProject(ctx *context.Context) { } var seen map[int64]data = make(map[int64]data, 0) - var tmplData = make([]data, 0, len(boards)) + var tmplData = make([]data, 0, len(boards)+1) uncategorizedBoard := data{ Board: models.ProjectBoard{ @@ -263,12 +265,13 @@ func ViewProject(ctx *context.Context) { ProjectID: 0, Title: ctx.Tr("repo.projects.type.uncategorized"), }, + Issues: []*models.Issue{}, } tmplData = append(tmplData, uncategorizedBoard) for i := range boards { - d := data{Board: boards[i], idx: i} + d := data{Board: boards[i], idx: i + 1} seen[boards[i].ID] = d @@ -328,3 +331,47 @@ func UpdateIssueProject(ctx *context.Context) { "ok": true, }) } + +func MoveIssueAcrossBoards(ctx *context.Context) { + + p, err := models.GetProjectByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) + if err != nil { + if models.IsErrProjectNotExist(err) { + ctx.NotFound("", nil) + } else { + ctx.ServerError("GetProjectByRepoID", err) + } + return + } + + board, err := models.GetProjectBoard(ctx.Repo.Repository.ID, p.ID, ctx.ParamsInt64(":boardID")) + if err != nil { + if models.IsErrProjectBoardNotExist(err) { + ctx.NotFound("", nil) + } else { + ctx.ServerError("GetProjectBoard", err) + } + return + } + + issue, err := models.GetIssueByID(ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrIssueNotExist(err) { + fmt.Println(err) + ctx.NotFound("", nil) + } else { + ctx.ServerError("GetIssueByID", err) + } + + return + } + + if err := models.MoveIssueAcrossProjectBoards(issue, board); err != nil { + ctx.ServerError("MoveIssueAcrossProjectBoards", err) + return + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 040e325d3823e..fcfd96bc3d0a4 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -827,6 +827,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/:id/edit", bindIgnErr(auth.CreateProjectForm{}), repo.EditProjectPost) m.Get("/:id/edit", repo.EditProject) m.Post("/delete", repo.DeleteProject) + m.Post("/:id/:boardID/:index", repo.MoveIssueAcrossBoards) }, reqRepoProjectsReader, repo.MustEnableProjects) diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 5f9d3bb30ce41..7d84e48103ab0 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -121,7 +121,7 @@ - + {{if .EnableHeatmap}} @@ -134,7 +134,7 @@ {{ end}} {{template "custom/footer" .}} diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl index e6fed521fe92e..11e6286ce96b8 100644 --- a/templates/repo/projects/view.tmpl +++ b/templates/repo/projects/view.tmpl @@ -28,13 +28,13 @@
    {{.Board.Title}}
    -
    +
    {{ $board := .Board }} {{ range .Issues }} -
    +
    - +
    {{ if $board.ID }} {{ $board.ID }} @@ -64,7 +64,4 @@
    - - {{template "base/footer" .}} From 812f256cdeed312877787b383279c30c5cda9a4f Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Tue, 1 Oct 2019 22:09:05 +0100 Subject: [PATCH 032/216] fix migrations --- models/migrations/migrations.go | 6 +++++- models/migrations/v101.go | 28 ++++++++++++++++++++++++ models/migrations/v102.go | 19 +++++++++++++++++ models/migrations/v98.go | 38 +-------------------------------- models/models.go | 2 ++ 5 files changed, 55 insertions(+), 38 deletions(-) create mode 100644 models/migrations/v101.go create mode 100644 models/migrations/v102.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 68c94944bad49..a612b7ab20391 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -253,9 +253,13 @@ var migrations = []Migration{ // v98 -> v99 NewMigration("add projects database table", addProjectsTable), // v99 -> v100 - NewMigration("add projects info to database", addProjectsInfo), + NewMigration("add projects info to repository table", addProjectsInfo), // v100 -> v101 NewMigration("add project ID to comments table", addProjectIDToCommentsTable), + // v101 -> v102 + NewMigration("add project ID to issue table", addProjectIDToIssueTable), + // v102 -> v103 + NewMigration("add project board table", addProjectBoardTable), } // Migrate database to current version diff --git a/models/migrations/v101.go b/models/migrations/v101.go new file mode 100644 index 0000000000000..149d728a56445 --- /dev/null +++ b/models/migrations/v101.go @@ -0,0 +1,28 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "github.com/go-xorm/xorm" +) + +func addProjectBoardTable(x *xorm.Engine) error { + type ProjectBoard struct { + ID int64 `xorm:"pk autoincr"` + ProjectID int64 `xorm:"INDEX NOT NULL"` + Title string + RepoID int64 `xorm:"INDEX NOT NULL"` + + // Not really needed but helpful + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + return x.Sync(new(ProjectBoard)) +} diff --git a/models/migrations/v102.go b/models/migrations/v102.go new file mode 100644 index 0000000000000..176038117b6e2 --- /dev/null +++ b/models/migrations/v102.go @@ -0,0 +1,19 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "github.com/go-xorm/xorm" +) + +func addProjectIDToIssueTable(x *xorm.Engine) error { + + type Issue struct { + ProjectID int64 `xorm:"INDEX"` + ProjectBoardID int64 `xorm:"INDEX"` + } + + return x.Sync2(new(Issue)) +} diff --git a/models/migrations/v98.go b/models/migrations/v98.go index 90020efae897e..d55dbd21b0447 100644 --- a/models/migrations/v98.go +++ b/models/migrations/v98.go @@ -14,13 +14,6 @@ func addProjectsTable(x *xorm.Engine) error { type ProjectType uint8 - sess := x.NewSession() - defer sess.Close() - - if err := sess.Begin(); err != nil { - return err - } - type Project struct { ID int64 `xorm:"pk autoincr"` Title string `xorm:"INDEX NOT NULL"` @@ -38,34 +31,5 @@ func addProjectsTable(x *xorm.Engine) error { UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` } - type Issue struct { - ProjectID int64 `xorm:"INDEX"` - } - - type ProjectBoard struct { - ID int64 `xorm:"pk autoincr"` - ProjectID int64 `xorm:"INDEX NOT NULL"` - Title string - RepoID int64 `xorm:"INDEX NOT NULL"` - - // Not really needed but helpful - CreatorID int64 `xorm:"NOT NULL"` - - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - } - - if err := x.Sync(new(Project)); err != nil { - return err - } - - if err := x.Sync(new(ProjectBoard)); err != nil { - return err - } - - if err := x.Sync2(new(Issue)); err != nil { - return err - } - - return sess.Commit() + return x.Sync(new(Project)) } diff --git a/models/models.go b/models/models.go index e802a35a77709..3097e88e01850 100644 --- a/models/models.go +++ b/models/models.go @@ -112,6 +112,8 @@ func init() { new(OAuth2Application), new(OAuth2AuthorizationCode), new(OAuth2Grant), + new(Project), + new(ProjectBoard), ) gonicNames := []string{"SSL", "UID"} From 9c665120632ded522b9d0afeb383c373714b57ff Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Tue, 1 Oct 2019 22:13:54 +0100 Subject: [PATCH 033/216] fix broken template from merge --- templates/repo/header.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 5efa3bbdad4a3..363dc7b2da060 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -13,7 +13,7 @@ {{.Name}} {{if and .RelAvatarLink .IsPrivate}}{{end}} {{if .IsArchived}}{{end}} - {{if .IsMirror}}
    {{$.i18n.Tr "repo.mirror_from"}} {{MirrorAddress $.Mirror}}
    {{end}} + {{if .IsMirror}}
    {{$.i18n.Tr "repo.mirror_from"}} {{$.Mirror.Address}}
    {{end}} {{if .IsFork}}
    {{$.i18n.Tr "repo.forked_from"}} {{SubStr .BaseRepo.RelLink 1 -1}}
    {{end}}
    From c70b9a81751d3ede9888982843841daa57d404c0 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Tue, 1 Oct 2019 22:21:13 +0100 Subject: [PATCH 034/216] make sure only signed in users can make this api call --- routers/repo/projects.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/routers/repo/projects.go b/routers/repo/projects.go index fce5f3a47335a..313fd17a5d0b3 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -334,6 +334,13 @@ func UpdateIssueProject(ctx *context.Context) { func MoveIssueAcrossBoards(ctx *context.Context) { + if ctx.User == nil { + ctx.JSON(403, map[string]string{ + "message": "Only signed in users are allowed to call make this action.", + }) + return + } + p, err := models.GetProjectByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) if err != nil { if models.IsErrProjectNotExist(err) { From 306ec7c452d6624be006213dcbe0999cb7053c2c Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Tue, 1 Oct 2019 22:43:32 +0100 Subject: [PATCH 035/216] make sure only authorized users can move cards across boards --- routers/repo/projects.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 313fd17a5d0b3..2ce8654a3767e 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -341,6 +341,13 @@ func MoveIssueAcrossBoards(ctx *context.Context) { return } + if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) { + ctx.JSON(403, map[string]string{ + "message": "Only authorized users are allowed to call make this action.", + }) + return + } + p, err := models.GetProjectByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) if err != nil { if models.IsErrProjectNotExist(err) { From 5c334d70326b0a5d08cf755b1a09d0ea1fb92800 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Tue, 1 Oct 2019 22:57:59 +0100 Subject: [PATCH 036/216] support moving back to the special uncategorized folder --- routers/repo/projects.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 2ce8654a3767e..872292453be5c 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -332,6 +332,7 @@ func UpdateIssueProject(ctx *context.Context) { }) } +// MoveIssueAcrossBoards move a card from one board to another in a project func MoveIssueAcrossBoards(ctx *context.Context) { if ctx.User == nil { @@ -358,14 +359,26 @@ func MoveIssueAcrossBoards(ctx *context.Context) { return } - board, err := models.GetProjectBoard(ctx.Repo.Repository.ID, p.ID, ctx.ParamsInt64(":boardID")) - if err != nil { - if models.IsErrProjectBoardNotExist(err) { - ctx.NotFound("", nil) - } else { - ctx.ServerError("GetProjectBoard", err) + var board *models.ProjectBoard + + if ctx.ParamsInt64(":boardID") == 0 { + + board = &models.ProjectBoard{ + ID: 0, + ProjectID: 0, + Title: ctx.Tr("repo.projects.type.uncategorized"), + } + + } else { + board, err = models.GetProjectBoard(ctx.Repo.Repository.ID, p.ID, ctx.ParamsInt64(":boardID")) + if err != nil { + if models.IsErrProjectBoardNotExist(err) { + ctx.NotFound("", nil) + } else { + ctx.ServerError("GetProjectBoard", err) + } + return } - return } issue, err := models.GetIssueByID(ctx.ParamsInt64(":index")) From c55d44e0233f46094fbebd33feac82e5072e1ba7 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Wed, 2 Oct 2019 00:33:04 +0100 Subject: [PATCH 037/216] allow project to be selected when creating an issue --- modules/auth/repo_form.go | 1 + public/js/index.js | 1 + routers/repo/issue.go | 48 +++++++++++++++++++++++------- routers/repo/pull.go | 2 +- templates/repo/issue/new_form.tmpl | 41 +++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 11 deletions(-) diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 9c76e9ed69f9c..5627d79408b20 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -313,6 +313,7 @@ type CreateIssueForm struct { AssigneeIDs string `form:"assignee_ids"` Ref string `form:"ref"` MilestoneID int64 + ProjectID int64 AssigneeID int64 Content string Files []string diff --git a/public/js/index.js b/public/js/index.js index 6e4cabf77e361..085153369b090 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -538,6 +538,7 @@ function initCommentForm() { } switch (input_id) { case '#milestone_id': + case '#projects_id': $list.find('.selected').html('' + htmlEncode($(this).text()) + ''); break; diff --git a/routers/repo/issue.go b/routers/repo/issue.go index ede3595289b23..87691aa407554 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -380,6 +380,11 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models. return nil } + retrieveProjects(ctx, repo) + if ctx.Written() { + return nil + } + brs, err := ctx.Repo.GitRepo.GetBranches() if err != nil { ctx.ServerError("GetBranches", err) @@ -455,6 +460,17 @@ func NewIssue(ctx *context.Context) { } } + projectID := ctx.QueryInt64("project") + if projectID > 0 { + project, err := models.GetProjectByRepoID(ctx.Repo.Repository.ID, projectID) + if err != nil { + log.Error("GetProjectByRepoID: %d: %v", projectID, err) + } else { + ctx.Data["project_id"] = projectID + ctx.Data["Project"] = project + } + } + setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates) renderAttachmentSettings(ctx) @@ -467,7 +483,7 @@ func NewIssue(ctx *context.Context) { } // ValidateRepoMetas check and returns repository's meta informations -func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull bool) ([]int64, []int64, int64) { +func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull bool) ([]int64, []int64, int64, int64) { var ( repo = ctx.Repo.Repository err error @@ -475,7 +491,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository) if ctx.Written() { - return nil, nil, 0 + return nil, nil, 0, 0 } var labelIDs []int64 @@ -484,7 +500,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b if len(form.LabelIDs) > 0 { labelIDs, err = base.StringsToInt64s(strings.Split(form.LabelIDs, ",")) if err != nil { - return nil, nil, 0 + return nil, nil, 0, 0 } labelIDMark := base.Int64sToMap(labelIDs) @@ -506,17 +522,28 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b ctx.Data["Milestone"], err = repo.GetMilestoneByID(milestoneID) if err != nil { ctx.ServerError("GetMilestoneByID", err) - return nil, nil, 0 + return nil, nil, 0, 0 } ctx.Data["milestone_id"] = milestoneID } + projectID := form.ProjectID + if projectID > 0 { + ctx.Data["Project"], err = models.GetProjectByRepoID(ctx.Repo.Repository.ID, projectID) + if err != nil { + ctx.ServerError("GetMilestoneByID", err) + return nil, nil, 0, 0 + } + + ctx.Data["project_id"] = projectID + } + // Check assignees var assigneeIDs []int64 if len(form.AssigneeIDs) > 0 { assigneeIDs, err = base.StringsToInt64s(strings.Split(form.AssigneeIDs, ",")) if err != nil { - return nil, nil, 0 + return nil, nil, 0, 0 } // Check if the passed assignees actually exists and has write access to the repo @@ -524,17 +551,17 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b user, err := models.GetUserByID(aID) if err != nil { ctx.ServerError("GetUserByID", err) - return nil, nil, 0 + return nil, nil, 0, 0 } perm, err := models.GetUserRepoPermission(repo, user) if err != nil { ctx.ServerError("GetUserRepoPermission", err) - return nil, nil, 0 + return nil, nil, 0, 0 } if !perm.CanWriteIssuesOrPulls(isPull) { ctx.ServerError("CanWriteIssuesOrPulls", fmt.Errorf("No permission for %s", user.Name)) - return nil, nil, 0 + return nil, nil, 0, 0 } } } @@ -544,7 +571,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b assigneeIDs = append(assigneeIDs, form.AssigneeID) } - return labelIDs, assigneeIDs, milestoneID + return labelIDs, assigneeIDs, milestoneID, projectID } // NewIssuePost response for creating new issue @@ -562,7 +589,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { attachments []string ) - labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form, false) + labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, form, false) if ctx.Written() { return } @@ -587,6 +614,7 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { PosterID: ctx.User.ID, Poster: ctx.User, MilestoneID: milestoneID, + ProjectID: projectID, Content: form.Content, Ref: form.Ref, } diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 72d2ffcaa7d66..76d30eb766580 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -726,7 +726,7 @@ func CompareAndPullRequestPost(ctx *context.Context, form auth.CreateIssueForm) return } - labelIDs, assigneeIDs, milestoneID := ValidateRepoMetas(ctx, form, true) + labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, form, true) if ctx.Written() { return } diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 99a68bc76eaa9..c3b181ca5004c 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -98,6 +98,47 @@
    +
    + + + +
    + {{.i18n.Tr "repo.issues.new.no_projects"}} +
    + {{if .Project}} + {{.Project.Title}} + {{end}} +
    +
    +
    From a1984a2e770a6115cedcf05ebdef707974b94266 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Wed, 2 Oct 2019 00:49:25 +0100 Subject: [PATCH 038/216] fix npm validation for functions --- public/js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/index.js b/public/js/index.js index 085153369b090..7dc7cbd8e7b72 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1,5 +1,5 @@ /* globals wipPrefixes, issuesTribute, emojiTribute */ -/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ +/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap initKanbanBoard Sortable*/ /* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ 'use strict'; From e123370a8bb8c4ce72b0d4be02df763867e9e909 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Wed, 2 Oct 2019 00:53:15 +0100 Subject: [PATCH 039/216] fix npm validation for functions --- .eslintrc | 1 + public/js/index.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index c7013f278540a..5af1174d9cd6e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -20,6 +20,7 @@ globals: Dropzone: false u2fApi: false hljs: false + Sortable: false rules: no-unused-vars: [error, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, ignoreRestSiblings: true}] diff --git a/public/js/index.js b/public/js/index.js index 7dc7cbd8e7b72..89575881f5711 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1,5 +1,5 @@ /* globals wipPrefixes, issuesTribute, emojiTribute */ -/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap initKanbanBoard Sortable*/ +/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap initKanbanBoard*/ /* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ 'use strict'; From 93862b6da599abc7d1bc04af0d2f1472e8cdc377 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Wed, 2 Oct 2019 12:12:23 +0100 Subject: [PATCH 040/216] fix review --- models/issue.go | 18 +++++++++--------- models/migrations/migrations.go | 4 ++-- models/migrations/v92.go | 1 + models/migrations/v93.go | 4 +--- models/migrations/v96.go | 15 ++++----------- routers/repo/projects.go | 2 +- templates/base/footer.tmpl | 2 +- 7 files changed, 19 insertions(+), 27 deletions(-) diff --git a/models/issue.go b/models/issue.go index 6e4400902ed43..d9809f77f64b8 100644 --- a/models/issue.go +++ b/models/issue.go @@ -33,15 +33,16 @@ type Issue struct { Poster *User `xorm:"-"` OriginalAuthor string OriginalAuthorID int64 - Title string `xorm:"name"` - Content string `xorm:"TEXT"` - RenderedContent string `xorm:"-"` - Labels []*Label `xorm:"-"` - MilestoneID int64 `xorm:"INDEX"` - ProjectID int64 `xorm:"INDEX"` + Title string `xorm:"name"` + Content string `xorm:"TEXT"` + RenderedContent string `xorm:"-"` + Labels []*Label `xorm:"-"` + MilestoneID int64 `xorm:"INDEX"` + Milestone *Milestone `xorm:"-"` + ProjectID int64 `xorm:"INDEX"` // If 0, then it has not been added to a specific board in the project - ProjectBoardID int64 `xorm:"INDEX"` - Milestone *Milestone `xorm:"-"` + ProjectBoardID int64 `xorm:"INDEX"` + Project *Project `xorm:"-"` Priority int AssigneeID int64 `xorm:"-"` Assignee *User `xorm:"-"` @@ -63,7 +64,6 @@ type Issue struct { Reactions ReactionList `xorm:"-"` TotalTrackedTime int64 `xorm:"-"` Assignees []*User `xorm:"-"` - Project *Project `xorm:"-"` // IsLocked limits commenting abilities to users on an issue // with write access diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index a612b7ab20391..3e09ad198746c 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -237,9 +237,9 @@ var migrations = []Migration{ // v90 -> v91 NewMigration("change length of some repository columns", changeSomeColumnsLengthOfRepo), // v91 -> v92 - NewMigration("remove orphaned repository index statuses", removeLingeringIndexStatus), - // v92 -> v93 NewMigration("add index on owner_id of repository and type, review_id of comment", addIndexOnRepositoryAndComment), + // v92 -> v93 + NewMigration("remove orphaned repository index statuses", removeLingeringIndexStatus), // v93 -> v94 NewMigration("add email notification enabled preference to user", addEmailNotificationEnabledToUser), // v94 -> v95 diff --git a/models/migrations/v92.go b/models/migrations/v92.go index e812dffa6686d..090332f151f0d 100644 --- a/models/migrations/v92.go +++ b/models/migrations/v92.go @@ -1,6 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. + package migrations import ( diff --git a/models/migrations/v93.go b/models/migrations/v93.go index 0381ee35be270..0b0441cd5df6e 100644 --- a/models/migrations/v93.go +++ b/models/migrations/v93.go @@ -4,9 +4,7 @@ package migrations -import ( - "github.com/go-xorm/xorm" -) +import "github.com/go-xorm/xorm" func addEmailNotificationEnabledToUser(x *xorm.Engine) error { // User see models/user.go diff --git a/models/migrations/v96.go b/models/migrations/v96.go index 7f042d5cb48d2..18da81a0add49 100644 --- a/models/migrations/v96.go +++ b/models/migrations/v96.go @@ -9,14 +9,12 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/setting" + "github.com/go-xorm/xorm" ) func deleteOrphanedAttachments(x *xorm.Engine) error { - sess := x.NewSession() - defer sess.Close() - type Attachment struct { ID int64 `xorm:"pk autoincr"` UUID string `xorm:"uuid UNIQUE"` @@ -25,7 +23,7 @@ func deleteOrphanedAttachments(x *xorm.Engine) error { CommentID int64 } - err := sess.BufferSize(setting.Database.IterateBufferSize). + err := x.BufferSize(setting.Database.IterateBufferSize). Where("`comment_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").Cols("uuid"). Iterate(new(Attachment), func(idx int, bean interface{}) error { @@ -35,13 +33,8 @@ func deleteOrphanedAttachments(x *xorm.Engine) error { return err } - _, err := sess.ID(attachment.ID).NoAutoCondition().Delete(attachment) + _, err := x.ID(attachment.ID).NoAutoCondition().Delete(attachment) return err }) - - if err != nil { - return err - } - - return sess.Commit() + return err } diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 872292453be5c..04e4117cbdac0 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -256,7 +256,7 @@ func ViewProject(ctx *context.Context) { idx int } - var seen map[int64]data = make(map[int64]data, 0) + var seen = make(map[int64]data, 0) var tmplData = make([]data, 0, len(boards)+1) uncategorizedBoard := data{ diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 7d84e48103ab0..0ea128b5b2ee5 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -121,7 +121,7 @@ - + {{if .EnableHeatmap}} From a035bee72ac23954ebc907ecc29e9c1bc6443697 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 13 Oct 2019 15:56:49 +0100 Subject: [PATCH 041/216] first step to making sure repos and individuals have project boards too --- models/projects.go | 20 ++++++++++---------- modules/auth/repo_form.go | 6 +++--- routers/repo/projects.go | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/models/projects.go b/models/projects.go index a501b866d522e..95cdd6908c126 100644 --- a/models/projects.go +++ b/models/projects.go @@ -10,8 +10,8 @@ import ( "github.com/go-xorm/xorm" ) -// ProjectType is used to represent a project board type -type ProjectType uint8 +// ProjectBoardType is used to represent a project board type +type ProjectBoardType uint8 // ProjectBoards is a list of all project boards in a repository. type ProjectBoards []ProjectBoard @@ -32,17 +32,17 @@ type ProjectBoard struct { const ( // None is a project board type that has no predefined columns - None ProjectType = iota + None ProjectBoardType = iota // BasicKanban is a project board type that has basic predefined columns BasicKanban - // BugTriage is a project type that has predefined columns suited to + // BugTriage is a project board type that has predefined columns suited to // hunting down bugs BugTriage ) // ProjectsConfig is used to identify the type of board that is being created type ProjectsConfig struct { - Type ProjectType + BoardType ProjectBoardType Translation string } @@ -56,7 +56,7 @@ func GetProjectsConfig() []ProjectsConfig { } // IsProjectTypeValid checks if the project type is valid -func IsProjectTypeValid(p ProjectType) bool { +func IsProjectTypeValid(p ProjectBoardType) bool { switch p { case None, BasicKanban, BugTriage: return true @@ -76,7 +76,7 @@ type Project struct { NumIssues int NumClosedIssues int NumOpenIssues int `xorm:"-"` - Type ProjectType + BoardType ProjectBoardType RenderedContent string `xorm:"-"` @@ -117,8 +117,8 @@ func GetProjects(repoID int64, page int, isClosed bool, sortType string) ([]*Pro // NewProject creates a new Project func NewProject(p *Project) error { - if !IsProjectTypeValid(p.Type) { - p.Type = None + if !IsProjectTypeValid(p.BoardType) { + p.BoardType = None } sess := x.NewSession() @@ -147,7 +147,7 @@ func createBoardsForProjectsType(sess *xorm.Session, project *Project) error { var items []string - switch project.Type { + switch project.BoardType { case BugTriage: items = setting.Repository.ProjectBoardBugTriageType diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 5627d79408b20..f7090b2c3eedd 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -381,9 +381,9 @@ func (i IssueLockForm) HasValidReason() bool { // CreateProjectForm form for creating a project type CreateProjectForm struct { - Title string `binding:"Required;MaxSize(50)"` - Content string - Type models.ProjectType + Title string `binding:"Required;MaxSize(50)"` + Content string + BoardType models.ProjectBoardType } // _____ .__.__ __ diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 04e4117cbdac0..d3d1e4051d8ea 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -112,7 +112,7 @@ func NewProjectPost(ctx *context.Context, form auth.CreateProjectForm) { Title: form.Title, Description: form.Content, CreatorID: ctx.User.ID, - Type: form.Type, + BoardType: form.BoardType, }); err != nil { ctx.ServerError("NewProject", err) return From fbd9ba55247d3b39eca5a5e0c250e3316a2556cb Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 13 Oct 2019 18:01:03 +0100 Subject: [PATCH 042/216] introduce repository projects --- models/migrations/v100.go | 8 +++- models/projects.go | 84 ++++++++++++++++++++++++++++----------- routers/repo/projects.go | 5 ++- routers/routes/routes.go | 2 +- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/models/migrations/v100.go b/models/migrations/v100.go index beac3f4e071c4..7579af919286d 100644 --- a/models/migrations/v100.go +++ b/models/migrations/v100.go @@ -15,7 +15,10 @@ func addProjectsInfo(x *xorm.Engine) error { sess := x.NewSession() defer sess.Close() - type ProjectType uint8 + type ( + ProjectType uint8 + ProjectBoardType uint8 + ) type Project struct { ID int64 `xorm:"pk autoincr"` @@ -27,7 +30,8 @@ func addProjectsInfo(x *xorm.Engine) error { NumIssues int NumClosedIssues int - Type ProjectType + BoardType ProjectBoardType + Type ProjectType ClosedDateUnix timeutil.TimeStamp CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` diff --git a/models/projects.go b/models/projects.go index 95cdd6908c126..cb81607a36626 100644 --- a/models/projects.go +++ b/models/projects.go @@ -5,46 +5,67 @@ package models import ( + "errors" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "github.com/go-xorm/xorm" ) -// ProjectBoardType is used to represent a project board type -type ProjectBoardType uint8 +type ( + // ProjectsConfig is used to identify the type of board that is being created + ProjectsConfig struct { + BoardType ProjectBoardType + Translation string + } + + // ProjectBoardType is used to represent a project board type + ProjectBoardType uint8 -// ProjectBoards is a list of all project boards in a repository. -type ProjectBoards []ProjectBoard + // ProjectBoards is a list of all project boards in a repository. + ProjectBoards []ProjectBoard -// ProjectBoard is used to represent boards on a kanban project -type ProjectBoard struct { - ID int64 `xorm:"pk autoincr"` - ProjectID int64 `xorm:"INDEX NOT NULL"` - Title string - RepoID int64 `xorm:"INDEX NOT NULL"` + // ProjectBoard is used to represent boards on a kanban project + ProjectBoard struct { + ID int64 `xorm:"pk autoincr"` + ProjectID int64 `xorm:"INDEX NOT NULL"` + Title string + RepoID int64 `xorm:"INDEX NOT NULL"` - // Not really needed but helpful - CreatorID int64 `xorm:"NOT NULL"` + // Not really needed but helpful + CreatorID int64 `xorm:"NOT NULL"` - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` -} + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + // ProjectType is used to identify the type of project in question and + // ownership + ProjectType uint8 +) const ( // None is a project board type that has no predefined columns None ProjectBoardType = iota + // BasicKanban is a project board type that has basic predefined columns BasicKanban + // BugTriage is a project board type that has predefined columns suited to // hunting down bugs BugTriage -) -// ProjectsConfig is used to identify the type of board that is being created -type ProjectsConfig struct { - BoardType ProjectBoardType - Translation string -} + // IndividualType is a type of project board that is owned by an + // individual. + IndividualType ProjectType = iota + 1 + + // RepositoryType is a project that is tied to a repository. + RepositoryType + + // OrganizationType is a project that is tied to an organisation. + OrganizationType +) // GetProjectsConfig retrieves the types of configurations projects could have func GetProjectsConfig() []ProjectsConfig { @@ -55,8 +76,8 @@ func GetProjectsConfig() []ProjectsConfig { } } -// IsProjectTypeValid checks if the project type is valid -func IsProjectTypeValid(p ProjectBoardType) bool { +// IsProjectBoardTypeValid checks if the project board type is valid +func IsProjectBoardTypeValid(p ProjectBoardType) bool { switch p { case None, BasicKanban, BugTriage: return true @@ -65,6 +86,16 @@ func IsProjectTypeValid(p ProjectBoardType) bool { } } +// IsProjectTypeValid checks if a project typeis valid +func IsProjectTypeValid(p ProjectType) bool { + switch p { + case IndividualType, RepositoryType, OrganizationType: + return true + default: + return false + } +} + // Project is a kanban board type Project struct { ID int64 `xorm:"pk autoincr"` @@ -77,6 +108,7 @@ type Project struct { NumClosedIssues int NumOpenIssues int `xorm:"-"` BoardType ProjectBoardType + Type ProjectType RenderedContent string `xorm:"-"` @@ -117,10 +149,14 @@ func GetProjects(repoID int64, page int, isClosed bool, sortType string) ([]*Pro // NewProject creates a new Project func NewProject(p *Project) error { - if !IsProjectTypeValid(p.BoardType) { + if !IsProjectBoardTypeValid(p.BoardType) { p.BoardType = None } + if !IsProjectTypeValid(p.Type) { + return errors.New("project type is not valid") + } + sess := x.NewSession() defer sess.Close() diff --git a/routers/repo/projects.go b/routers/repo/projects.go index d3d1e4051d8ea..41c2cd180132f 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -97,8 +97,8 @@ func NewProject(ctx *context.Context) { ctx.HTML(200, tplProjectsNew) } -// NewProjectPost creates a new project -func NewProjectPost(ctx *context.Context, form auth.CreateProjectForm) { +// NewRepoProjectPost creates a new project +func NewRepoProjectPost(ctx *context.Context, form auth.CreateProjectForm) { ctx.Data["Title"] = ctx.Tr("repo.projects.new") @@ -113,6 +113,7 @@ func NewProjectPost(ctx *context.Context, form auth.CreateProjectForm) { Description: form.Content, CreatorID: ctx.User.ID, BoardType: form.BoardType, + Type: models.RepositoryType, }); err != nil { ctx.ServerError("NewProject", err) return diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 8bba81f253830..1a03b257f91c5 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -821,7 +821,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("", repo.Projects) m.Get("/new", repo.NewProject) - m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewProjectPost) + m.Post("/new", bindIgnErr(auth.CreateProjectForm{}), repo.NewRepoProjectPost) m.Get("/:id", repo.ViewProject) m.Get("/:id/:action", repo.ChangeProjectStatus) m.Post("/:id/edit", bindIgnErr(auth.CreateProjectForm{}), repo.EditProjectPost) From cf5fb6e844d739e7e5050f9c496d279e0c8cedad Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 13 Oct 2019 18:58:15 +0100 Subject: [PATCH 043/216] fix form --- models/projects.go | 2 ++ templates/repo/projects/new.tmpl | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/models/projects.go b/models/projects.go index cb81607a36626..61af62b98bcbf 100644 --- a/models/projects.go +++ b/models/projects.go @@ -55,7 +55,9 @@ const ( // BugTriage is a project board type that has predefined columns suited to // hunting down bugs BugTriage +) +const ( // IndividualType is a type of project board that is owned by an // individual. IndividualType ProjectType = iota + 1 diff --git a/templates/repo/projects/new.tmpl b/templates/repo/projects/new.tmpl index 9cb54e97bbf1b..9ae50cff8a90c 100644 --- a/templates/repo/projects/new.tmpl +++ b/templates/repo/projects/new.tmpl @@ -35,12 +35,12 @@ From 3d7f94b3dbc54807c5d190f276fa3bc942a6b143 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Fri, 18 Oct 2019 19:41:41 +0100 Subject: [PATCH 044/216] fix merge conflicts --- go.mod | 1 + go.sum | 8 ++++++++ models/projects.go | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e1bbd9ac89d73..289f38d8906ba 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/go-redis/redis v6.15.2+incompatible github.com/go-sql-driver/mysql v1.4.1 github.com/go-swagger/go-swagger v0.20.1 + github.com/go-xorm/xorm v0.7.9 github.com/gobwas/glob v0.2.3 github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 diff --git a/go.sum b/go.sum index 2eeaa79810c0f..701facd8ad082 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/chaseadamsio/goorgeous v0.0.0-20170901132237-098da33fde5f h1:REH9VH5ubNR0skLaOxK7TRJeRbE2dDfvaouQo8FsRcA= github.com/chaseadamsio/goorgeous v0.0.0-20170901132237-098da33fde5f/go.mod h1:6QaC0vFoKWYDth94dHFNgRT2YkT5FHdQp/Yx15aAAi0= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/corbym/gocrest v1.0.3 h1:gwEdq6RkTmq+09CTuM29DfKOCtZ7G7bcyxs3IZ6EVdU= github.com/corbym/gocrest v1.0.3/go.mod h1:maVFL5lbdS2PgfOQgGRWDYTeunSWQeiEgoNdTABShCs= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -239,8 +240,11 @@ github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= +github.com/go-xorm/xorm v0.7.9 h1:LZze6n1UvRmM5gpL9/U9Gucwqo6aWlFVlfcHKH10qA0= +github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 h1:deE7ritpK04PgtpyVOS2TYcQEld9qLCD5b5EbVNOuLA= github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:YgYOrVn3Nj9Tq0EvjmFbphRytDj7JNRoWSStJZWDJTQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -324,6 +328,8 @@ github.com/issue9/assert v1.3.2 h1:IaTa37u4m1fUuTH9K9ldO5IONKVDXjLiUO1T9vj0OF0= github.com/issue9/assert v1.3.2/go.mod h1:9Ger+iz8X7r1zMYYwEhh++2wMGWcNN2oVI+zIQXxcio= github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c h1:A/PDn117UYld5mlxe58EpMguqpkeTMw5/FCo0ZPS/Ko= github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c/go.mod h1:5mTb/PQNkqmq2x3IxlQZE0aSnTksJg7fg/oWmJ5SKXQ= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d h1:ig/iUfDDg06RVW8OMby+GrmW6K2nPO3AFHlEIdvJSd4= github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -495,6 +501,7 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b h1:4kg1wyftSKxLtnPAvcRWakIPpokB9w780/KwrNLnfPA= github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v0.0.0-20160918041101-1dba4b3954bc h1:3wIrJvFb3Pf6B/2mDBnN1G5IfUVev4X5apadQlWOczE= @@ -801,6 +808,7 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= +xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= xorm.io/xorm v0.8.0 h1:iALxgJrX8O00f8Jk22GbZwPmxJNgssV5Mv4uc2HL9PM= diff --git a/models/projects.go b/models/projects.go index 61af62b98bcbf..84f7065a8a622 100644 --- a/models/projects.go +++ b/models/projects.go @@ -10,7 +10,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" - "github.com/go-xorm/xorm" + "xorm.io/xorm" ) type ( From c15c510b190d72c2c854d14f32f4b27efcb61e6a Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 19 Oct 2019 00:08:52 +0100 Subject: [PATCH 045/216] initial support for individual and organization projects --- modules/auth/repo_form.go | 9 +++++ modules/context/context.go | 2 + options/locale/locale_en-US.ini | 1 + routers/repo/projects.go | 59 +++++++++++++++++++++++++--- routers/routes/routes.go | 13 +++++++ templates/base/head_navbar.tmpl | 5 +++ templates/user/project.tmpl | 68 +++++++++++++++++++++++++++++++++ 7 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 templates/user/project.tmpl diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 948e7fb8b5011..68d73110bdfa7 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -386,6 +386,15 @@ type CreateProjectForm struct { BoardType models.ProjectBoardType } +// UserCreateProjectForm is a from for creating an individual or organization +// form. +type UserCreateProjectForm struct { + Title string `binding:"Required;MaxSize(50)"` + Content string + BoardType models.ProjectBoardType + UID int64 `binding:"Required"` +} + // _____ .__.__ __ // / \ |__| | ____ _______/ |_ ____ ____ ____ // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ diff --git a/modules/context/context.go b/modules/context/context.go index ef6c19ed125c3..49b11a3c901a8 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -340,6 +340,8 @@ func Contexter() macaron.Handler { ctx.Data["EnableSwagger"] = setting.API.EnableSwagger ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn + ctx.Data["IsKanbanEnabled"] = setting.Repository.EnableKanbanBoard + c.Map(ctx) } } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 99f23335fabe2..fc8cc73d700fb 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -51,6 +51,7 @@ new_migrate = New Migration new_mirror = New Mirror new_fork = New Repository Fork new_org = New Organization +new_project = New Project manage_org = Manage Organizations admin_panel = Site Administration account_settings = Account Settings diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 41c2cd180132f..fb5830305055e 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -17,9 +17,10 @@ import ( ) const ( - tplProjects base.TplName = "repo/projects/list" - tplProjectsNew base.TplName = "repo/projects/new" - tplProjectsView base.TplName = "repo/projects/view" + tplProjects base.TplName = "repo/projects/list" + tplProjectsNew base.TplName = "repo/projects/new" + tplProjectsView base.TplName = "repo/projects/view" + tplGenericProjectsNew base.TplName = "user/project" projectTemplateKey = "ProjectTemplate" ) @@ -31,9 +32,11 @@ func MustEnableProjects(ctx *context.Context) { return } - if !ctx.Repo.CanRead(models.UnitTypeProjects) { - ctx.NotFound("MustEnableProjects", nil) - return + if ctx.Repo.Repository != nil { + if !ctx.Repo.CanRead(models.UnitTypeProjects) { + ctx.NotFound("MustEnableProjects", nil) + return + } } } @@ -403,3 +406,47 @@ func MoveIssueAcrossBoards(ctx *context.Context) { "ok": true, }) } + +// CreateProject renders the generic project creation page +func CreateProject(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("repo.projects.new") + ctx.Data["ProjectTypes"] = models.GetProjectsConfig() + + ctx.HTML(200, tplGenericProjectsNew) +} + +// CreateProjectPost creates an individual and/or organization project +func CreateProjectPost(ctx *context.Context, form auth.UserCreateProjectForm) { + + user := checkContextUser(ctx, form.UID) + if ctx.Written() { + return + } + + ctx.Data["ContextUser"] = user + + if ctx.HasError() { + ctx.HTML(200, tplGenericProjectsNew) + return + } + + var projectType = models.IndividualType + fmt.Println(user.IsOrganization()) + if user.IsOrganization() { + projectType = models.OrganizationType + } + + if err := models.NewProject(&models.Project{ + Title: form.Title, + Description: form.Content, + CreatorID: user.ID, + BoardType: form.BoardType, + Type: projectType, + }); err != nil { + ctx.ServerError("NewProject", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.projects.create_success", form.Title)) + ctx.Redirect(setting.AppSubURL + "/") +} diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 47f370d57d34b..4eae8a9a65c6a 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -619,6 +619,19 @@ func RegisterRoutes(m *macaron.Macaron) { }, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader) }, reqSignIn) + m.Group("/projects", func() { + m.Get("/create", repo.CreateProject) + m.Post("/create", bindIgnErr(auth.UserCreateProjectForm{}), repo.CreateProjectPost) + }, repo.MustEnableProjects, func(ctx *context.Context) { + + if err := ctx.User.GetOrganizations(true); err != nil { + ctx.ServerError("GetOrganizations", err) + return + } + + ctx.Data["Orgs"] = ctx.User.Orgs + }) + // ***** Release Attachment Download without Signin m.Get("/:username/:reponame/releases/download/:vTag/:fileName", ignSignIn, context.RepoAssignment(), repo.MustBeNotEmpty, repo.RedirectDownload) diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 390a1fe8044ca..a6d54d62bdcf8 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -66,6 +66,11 @@ {{.i18n.Tr "new_org"}} {{end}} + {{ if .IsKanbanEnabled }} + + {{ .i18n.Tr "new_project" }} + + {{ end }}
    diff --git a/templates/user/project.tmpl b/templates/user/project.tmpl new file mode 100644 index 0000000000000..a2b3b176ffaad --- /dev/null +++ b/templates/user/project.tmpl @@ -0,0 +1,68 @@ +{{template "base/head" .}} +
    +
    +
    +
    + {{.CsrfTokenHtml}} +

    + {{.i18n.Tr "new_project"}} +

    +
    + {{template "base/alert" .}} +
    + + +
    + +
    + + +
    +
    + + +
    + +
    + + +
    + +
    + + + {{.i18n.Tr "cancel"}} +
    +
    +
    +
    +
    +
    +{{template "base/footer" .}} From 617ac2651f222fb8b0d499190bdefcbff6e97b85 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 19 Oct 2019 00:52:50 +0100 Subject: [PATCH 046/216] allow search options for projects --- models/projects.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/models/projects.go b/models/projects.go index 84f7065a8a622..604625fd98831 100644 --- a/models/projects.go +++ b/models/projects.go @@ -125,17 +125,25 @@ func (p *Project) AfterLoad() { p.NumOpenIssues = p.NumIssues - p.NumClosedIssues } +type ProjectSearchOptions struct { + RepoID int64 + Page int + IsClosed bool + SortType string + Type ProjectType +} + // GetProjects returns a list of all projects that have been created in the // repository -func GetProjects(repoID int64, page int, isClosed bool, sortType string) ([]*Project, error) { +func GetProjects(opts ProjectSearchOptions) ([]*Project, error) { projects := make([]*Project, 0, setting.UI.IssuePagingNum) - sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) - if page > 0 { - sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) + sess := x.Where("repo_id = ? AND is_closed = ? AND type = ?", opts.RepoID, opts.IsClosed, opts.Type) + if opts.Page > 0 { + sess = sess.Limit(setting.UI.IssuePagingNum, (opts.Page-1)*setting.UI.IssuePagingNum) } - switch sortType { + switch opts.SortType { case "oldest": sess.Desc("created_unix") case "recentupdate": From 6b8a0b12eb3864129960dbb90ca35dadb93cc5d4 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 19 Oct 2019 15:17:25 +0100 Subject: [PATCH 047/216] fix search options for projects --- go.mod | 1 - go.sum | 8 -------- models/projects.go | 24 ++++++++++++++++++++++-- options/locale/locale_en-US.ini | 1 + routers/repo/issue.go | 14 ++++++++++++-- routers/repo/projects.go | 9 ++++++++- routers/user/home.go | 2 +- routers/user/profile.go | 8 ++++++++ templates/user/profile.tmpl | 5 +++++ 9 files changed, 57 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 289f38d8906ba..e1bbd9ac89d73 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,6 @@ require ( github.com/go-redis/redis v6.15.2+incompatible github.com/go-sql-driver/mysql v1.4.1 github.com/go-swagger/go-swagger v0.20.1 - github.com/go-xorm/xorm v0.7.9 github.com/gobwas/glob v0.2.3 github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 diff --git a/go.sum b/go.sum index 701facd8ad082..2eeaa79810c0f 100644 --- a/go.sum +++ b/go.sum @@ -89,7 +89,6 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/chaseadamsio/goorgeous v0.0.0-20170901132237-098da33fde5f h1:REH9VH5ubNR0skLaOxK7TRJeRbE2dDfvaouQo8FsRcA= github.com/chaseadamsio/goorgeous v0.0.0-20170901132237-098da33fde5f/go.mod h1:6QaC0vFoKWYDth94dHFNgRT2YkT5FHdQp/Yx15aAAi0= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/corbym/gocrest v1.0.3 h1:gwEdq6RkTmq+09CTuM29DfKOCtZ7G7bcyxs3IZ6EVdU= github.com/corbym/gocrest v1.0.3/go.mod h1:maVFL5lbdS2PgfOQgGRWDYTeunSWQeiEgoNdTABShCs= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -240,11 +239,8 @@ github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= -github.com/go-xorm/xorm v0.7.9 h1:LZze6n1UvRmM5gpL9/U9Gucwqo6aWlFVlfcHKH10qA0= -github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561 h1:deE7ritpK04PgtpyVOS2TYcQEld9qLCD5b5EbVNOuLA= github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:YgYOrVn3Nj9Tq0EvjmFbphRytDj7JNRoWSStJZWDJTQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -328,8 +324,6 @@ github.com/issue9/assert v1.3.2 h1:IaTa37u4m1fUuTH9K9ldO5IONKVDXjLiUO1T9vj0OF0= github.com/issue9/assert v1.3.2/go.mod h1:9Ger+iz8X7r1zMYYwEhh++2wMGWcNN2oVI+zIQXxcio= github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c h1:A/PDn117UYld5mlxe58EpMguqpkeTMw5/FCo0ZPS/Ko= github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c/go.mod h1:5mTb/PQNkqmq2x3IxlQZE0aSnTksJg7fg/oWmJ5SKXQ= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d h1:ig/iUfDDg06RVW8OMby+GrmW6K2nPO3AFHlEIdvJSd4= github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -501,7 +495,6 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b h1:4kg1wyftSKxLtnPAvcRWakIPpokB9w780/KwrNLnfPA= github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v0.0.0-20160918041101-1dba4b3954bc h1:3wIrJvFb3Pf6B/2mDBnN1G5IfUVev4X5apadQlWOczE= @@ -808,7 +801,6 @@ strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= -xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= xorm.io/xorm v0.8.0 h1:iALxgJrX8O00f8Jk22GbZwPmxJNgssV5Mv4uc2HL9PM= diff --git a/models/projects.go b/models/projects.go index 604625fd98831..ab27d93b1c321 100644 --- a/models/projects.go +++ b/models/projects.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" "xorm.io/xorm" ) @@ -128,7 +129,7 @@ func (p *Project) AfterLoad() { type ProjectSearchOptions struct { RepoID int64 Page int - IsClosed bool + IsClosed util.OptionalBool SortType string Type ProjectType } @@ -138,7 +139,26 @@ type ProjectSearchOptions struct { func GetProjects(opts ProjectSearchOptions) ([]*Project, error) { projects := make([]*Project, 0, setting.UI.IssuePagingNum) - sess := x.Where("repo_id = ? AND is_closed = ? AND type = ?", opts.RepoID, opts.IsClosed, opts.Type) + + sess := x.Where("repo_id = ?", opts.RepoID) + switch opts.IsClosed { + case util.OptionalBoolTrue: + sess = sess.Where("is_closed = ?", true) + case util.OptionalBoolFalse: + sess = sess.Where("is_closed = ?", false) + } + + switch opts.Type { + case RepositoryType: + sess = sess.Where("type = ?", opts.Type) + + case IndividualType: + sess = sess.Where("type = ?", opts.Type) + + case OrganizationType: + sess = sess.Where("type = ?", opts.Type) + } + if opts.Page > 0 { sess = sess.Limit(setting.UI.IssuePagingNum, (opts.Page-1)*setting.UI.IssuePagingNum) } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index fc8cc73d700fb..80aa1f11d8d31 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -343,6 +343,7 @@ repositories = Repositories activity = Public Activity followers = Followers starred = Starred Repositories +projects = Projects following = Following follow = Follow unfollow = Unfollow diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 36804bf4f888e..bb0ccda637737 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -351,13 +351,23 @@ func retrieveProjects(ctx *context.Context, repo *models.Repository) { var err error - ctx.Data["OpenProjects"], err = models.GetProjects(repo.ID, -1, false, "") + ctx.Data["OpenProjects"], err = models.GetProjects(models.ProjectSearchOptions{ + RepoID: repo.ID, + Page: -1, + IsClosed: util.OptionalBoolTrue, + Type: models.RepositoryType, + }) if err != nil { ctx.ServerError("GetProjects", err) return } - ctx.Data["ClosedProjects"], err = models.GetProjects(repo.ID, -1, true, "") + ctx.Data["ClosedProjects"], err = models.GetProjects(models.ProjectSearchOptions{ + RepoID: repo.ID, + Page: -1, + IsClosed: util.OptionalBoolFalse, + Type: models.RepositoryType, + }) if err != nil { ctx.ServerError("GetProjects", err) return diff --git a/routers/repo/projects.go b/routers/repo/projects.go index fb5830305055e..f3ea72b773e5f 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" ) const ( @@ -63,7 +64,13 @@ func Projects(ctx *context.Context) { total = int(repo.NumClosedProjects) } - projects, err := models.GetProjects(repo.ID, page, isShowClosed, sortType) + projects, err := models.GetProjects(models.ProjectSearchOptions{ + RepoID: repo.ID, + Page: page, + IsClosed: util.OptionalBoolOf(isShowClosed), + SortType: sortType, + Type: models.RepositoryType, + }) if err != nil { ctx.ServerError("GetProjects", err) return diff --git a/routers/user/home.go b/routers/user/home.go index 40b3bc3fc1b81..11c821ab2c052 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -92,7 +92,7 @@ func retrieveFeeds(ctx *context.Context, options models.GetFeedsOptions) { ctx.Data["Feeds"] = actions } -// Dashboard render the dashborad page +// Dashboard render the dashboard page func Dashboard(ctx *context.Context) { ctxUser := getDashboardContextUser(ctx) if ctx.Written() { diff --git a/routers/user/profile.go b/routers/user/profile.go index 8a62ddeac026c..c5f39b171cb5a 100644 --- a/routers/user/profile.go +++ b/routers/user/profile.go @@ -188,6 +188,14 @@ func Profile(ctx *context.Context) { } total = int(count) + case "projects": + + ctx.Data["ClosedProjects"], err = models.GetProjects(models.ProjectSearchOptions{ + // RepoID: repo.ID, + Page: -1, + // IsClosed: uti, + Type: models.IndividualType, + }) default: repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ Keyword: keyword, diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 52c6e3ec3f589..37da4f8a8387b 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -104,6 +104,11 @@ {{.i18n.Tr "user.starred"}} + {{ if .IsKanbanEnabled }} + + {{.i18n.Tr "user.projects"}} + + {{ end }}
    {{if eq .TabName "activity"}} From ef92f40b24342fd26bccf96e873ac0ef7a7c39e3 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Mon, 21 Oct 2019 21:22:58 +0100 Subject: [PATCH 048/216] fix open projects not showing up --- templates/repo/issue/new_form.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index c3b181ca5004c..9bce017080349 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -108,7 +108,7 @@ {{if and .CanCreateIssueDependencies (not .Repository.IsArchived)}} From 81864382e362981d16f9ad44dec52ca6d0291cdd Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 12 Jan 2020 13:19:34 +0100 Subject: [PATCH 056/216] fix compilation --- routers/repo/issue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 975f32a367f58..358f83f6d1b31 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -730,7 +730,7 @@ func ViewIssue(ctx *context.Context) { ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireDropzone"] = true ctx.Data["RequireTribute"] = true - ctx.Data["IsProjectsEnabled"] = settings.Repository.EnableKanbanBoard + ctx.Data["IsProjectsEnabled"] = setting.Repository.EnableKanbanBoard renderAttachmentSettings(ctx) From 64430ed3f6b0570a57dadfe1b615fe4412aef79b Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 12 Jan 2020 13:45:36 +0100 Subject: [PATCH 057/216] fix review --- models/migrations/v96.go | 6 +- modules/setting/repository.go | 4 +- public/css/index.css | 1093 ------ public/js/index.js | 3375 ----------------- routers/repo/issue.go | 7 +- routers/user/profile.go | 2 +- .../repo/issue/view_content/sidebar.tmpl | 1 - 7 files changed, 12 insertions(+), 4476 deletions(-) delete mode 100644 public/css/index.css delete mode 100644 public/js/index.js diff --git a/models/migrations/v96.go b/models/migrations/v96.go index 59c4b87a725aa..a59c4660e168a 100644 --- a/models/migrations/v96.go +++ b/models/migrations/v96.go @@ -39,5 +39,9 @@ func deleteOrphanedAttachments(x *xorm.Engine) error { _, err := x.ID(attachment.ID).NoAutoCondition().Delete(attachment) return err }) - return err + if err != nil { + return err + } + + return sess.Commit() } diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 069ffd4386f9c..c3789f74f35dc 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -100,11 +100,11 @@ var ( AccessControlAllowOrigin: "", UseCompatSSHURI: false, DefaultCloseIssuesViaCommitsInAnyBranch: false, + EnablePushCreateUser: false, + EnablePushCreateOrg: false, EnableKanbanBoard: true, ProjectBoardBasicKanbanType: strings.Split("Todo, In progress, Done", ","), ProjectBoardBugTriageType: strings.Split("Needs Triage, High priority, Low priority, Closed", ","), - EnablePushCreateUser: false, - EnablePushCreateOrg: false, // Repository editor settings Editor: struct { diff --git a/public/css/index.css b/public/css/index.css deleted file mode 100644 index 8dfc9dfe53ae4..0000000000000 --- a/public/css/index.css +++ /dev/null @@ -1,1093 +0,0 @@ -.tribute-container{box-shadow:0 1px 3px 1px #c7c7c7} -.tribute-container ul{background:#fff} -.tribute-container li{padding:8px 12px;border-bottom:1px solid #dcdcdc} -.tribute-container li img{display:inline-block;vertical-align:middle;width:28px;height:28px;margin-right:5px} -.tribute-container li span.fullname{font-weight:400;font-size:.8rem;margin-left:3px} -.tribute-container li.highlight,.tribute-container li:hover{background:#2185d0;color:#fff} -.emoji{width:1.5em;height:1.5em;display:inline-block;background-size:contain} -.ui.label .emoji{height:1.2em!important} -@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-regular.eot);src:url(../vendor/assets/lato-fonts/lato-regular.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-regular.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-regular.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-regular.ttf) format('truetype');font-weight:400;font-style:normal} -@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-italic.eot);src:url(../vendor/assets/lato-fonts/lato-italic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-italic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-italic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-italic.ttf) format('truetype');font-weight:400;font-style:italic} -@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bold.eot);src:url(../vendor/assets/lato-fonts/lato-bold.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bold.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bold.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bold.ttf) format('truetype');font-weight:700;font-style:normal} -@font-face{font-family:Lato;src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot);src:url(../vendor/assets/lato-fonts/lato-bolditalic.eot?#iefix) format('embedded-opentype'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff2) format('woff2'),url(../vendor/assets/lato-fonts/lato-bolditalic.woff) format('woff'),url(../vendor/assets/lato-fonts/lato-bolditalic.ttf) format('truetype');font-weight:700;font-style:italic} -@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Medium');font-weight:400} -@font-face{font-family:'Yu Gothic';src:local('Yu Gothic Bold');font-weight:700} -textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif} -.markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif} -h1,h2,h3,h4,h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif} -.home .hero h1,.home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif} -.ui.accordion .title:not(.ui),.ui.button,.ui.card>.content>.header.ui.card>.content>.header,.ui.category.search>.results .category>.name,.ui.form input:not([type]),.ui.form input[type=date],.ui.form input[type=datetime-local],.ui.form input[type=email],.ui.form input[type=file],.ui.form input[type=number],.ui.form input[type=password],.ui.form input[type=search],.ui.form input[type=tel],.ui.form input[type=text],.ui.form input[type=time],.ui.form input[type=url],.ui.header,.ui.input input,.ui.input>input,.ui.items>.item>.content>.header,.ui.language>.menu>.item,.ui.list .list>.item .header,.ui.list>.item .header,.ui.menu,.ui.message .header,.ui.modal>.header,.ui.popup>.header,.ui.search>.results .result .title,.ui.search>.results>.message .header,.ui.statistic>.label,.ui.statistic>.value,.ui.statistics .statistic>.label,.ui.statistics .statistic>.value,.ui.steps .step .title,.ui.text.container,body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,sans-serif} -body{background-color:#fff;overflow-y:auto;-webkit-font-smoothing:antialiased;display:flex;flex-direction:column} -:lang(ja) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif} -:lang(ja) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif} -:lang(ja) h1,:lang(ja) h2,:lang(ja) h3,:lang(ja) h4,:lang(ja) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif} -:lang(ja) .home .hero h1,:lang(ja) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif} -.ui.language>.menu>.item:lang(ja),:lang(ja) .ui.accordion .title:not(.ui),:lang(ja) .ui.button,:lang(ja) .ui.card>.content>.header.ui.card>.content>.header,:lang(ja) .ui.category.search>.results .category>.name,:lang(ja) .ui.form input:not([type]),:lang(ja) .ui.form input[type=date],:lang(ja) .ui.form input[type=datetime-local],:lang(ja) .ui.form input[type=email],:lang(ja) .ui.form input[type=file],:lang(ja) .ui.form input[type=number],:lang(ja) .ui.form input[type=password],:lang(ja) .ui.form input[type=search],:lang(ja) .ui.form input[type=tel],:lang(ja) .ui.form input[type=text],:lang(ja) .ui.form input[type=time],:lang(ja) .ui.form input[type=url],:lang(ja) .ui.header,:lang(ja) .ui.input input,:lang(ja) .ui.input>input,:lang(ja) .ui.items>.item>.content>.header,:lang(ja) .ui.list .list>.item .header,:lang(ja) .ui.list>.item .header,:lang(ja) .ui.menu,:lang(ja) .ui.message .header,:lang(ja) .ui.modal>.header,:lang(ja) .ui.popup>.header,:lang(ja) .ui.search>.results .result .title,:lang(ja) .ui.search>.results>.message .header,:lang(ja) .ui.statistic>.label,:lang(ja) .ui.statistic>.value,:lang(ja) .ui.statistics .statistic>.label,:lang(ja) .ui.statistics .statistic>.value,:lang(ja) .ui.steps .step .title,:lang(ja) .ui.text.container,:lang(ja) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Hiragino Kaku Gothic ProN','Yu Gothic','Source Han Sans JP','Noto Sans CJK JP','Droid Sans Japanese',Meiryo,'MS PGothic',sans-serif} -:lang(zh-CN) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif} -:lang(zh-CN) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif} -:lang(zh-CN) h1,:lang(zh-CN) h2,:lang(zh-CN) h3,:lang(zh-CN) h4,:lang(zh-CN) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif} -:lang(zh-CN) .home .hero h1,:lang(zh-CN) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif} -.ui.language>.menu>.item:lang(zh-CN),:lang(zh-CN) .ui.accordion .title:not(.ui),:lang(zh-CN) .ui.button,:lang(zh-CN) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-CN) .ui.category.search>.results .category>.name,:lang(zh-CN) .ui.form input:not([type]),:lang(zh-CN) .ui.form input[type=date],:lang(zh-CN) .ui.form input[type=datetime-local],:lang(zh-CN) .ui.form input[type=email],:lang(zh-CN) .ui.form input[type=file],:lang(zh-CN) .ui.form input[type=number],:lang(zh-CN) .ui.form input[type=password],:lang(zh-CN) .ui.form input[type=search],:lang(zh-CN) .ui.form input[type=tel],:lang(zh-CN) .ui.form input[type=text],:lang(zh-CN) .ui.form input[type=time],:lang(zh-CN) .ui.form input[type=url],:lang(zh-CN) .ui.header,:lang(zh-CN) .ui.input input,:lang(zh-CN) .ui.input>input,:lang(zh-CN) .ui.items>.item>.content>.header,:lang(zh-CN) .ui.list .list>.item .header,:lang(zh-CN) .ui.list>.item .header,:lang(zh-CN) .ui.menu,:lang(zh-CN) .ui.message .header,:lang(zh-CN) .ui.modal>.header,:lang(zh-CN) .ui.popup>.header,:lang(zh-CN) .ui.search>.results .result .title,:lang(zh-CN) .ui.search>.results>.message .header,:lang(zh-CN) .ui.statistic>.label,:lang(zh-CN) .ui.statistic>.value,:lang(zh-CN) .ui.statistics .statistic>.label,:lang(zh-CN) .ui.statistics .statistic>.value,:lang(zh-CN) .ui.steps .step .title,:lang(zh-CN) .ui.text.container,:lang(zh-CN) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang SC','Hiragino Sans GB','Source Han Sans CN','Source Han Sans SC','Noto Sans CJK SC','Microsoft YaHei','Heiti SC',SimHei,sans-serif} -:lang(zh-TW) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif} -:lang(zh-TW) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif} -:lang(zh-TW) h1,:lang(zh-TW) h2,:lang(zh-TW) h3,:lang(zh-TW) h4,:lang(zh-TW) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif} -:lang(zh-TW) .home .hero h1,:lang(zh-TW) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif} -.ui.language>.menu>.item:lang(zh-TW),:lang(zh-TW) .ui.accordion .title:not(.ui),:lang(zh-TW) .ui.button,:lang(zh-TW) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-TW) .ui.category.search>.results .category>.name,:lang(zh-TW) .ui.form input:not([type]),:lang(zh-TW) .ui.form input[type=date],:lang(zh-TW) .ui.form input[type=datetime-local],:lang(zh-TW) .ui.form input[type=email],:lang(zh-TW) .ui.form input[type=file],:lang(zh-TW) .ui.form input[type=number],:lang(zh-TW) .ui.form input[type=password],:lang(zh-TW) .ui.form input[type=search],:lang(zh-TW) .ui.form input[type=tel],:lang(zh-TW) .ui.form input[type=text],:lang(zh-TW) .ui.form input[type=time],:lang(zh-TW) .ui.form input[type=url],:lang(zh-TW) .ui.header,:lang(zh-TW) .ui.input input,:lang(zh-TW) .ui.input>input,:lang(zh-TW) .ui.items>.item>.content>.header,:lang(zh-TW) .ui.list .list>.item .header,:lang(zh-TW) .ui.list>.item .header,:lang(zh-TW) .ui.menu,:lang(zh-TW) .ui.message .header,:lang(zh-TW) .ui.modal>.header,:lang(zh-TW) .ui.popup>.header,:lang(zh-TW) .ui.search>.results .result .title,:lang(zh-TW) .ui.search>.results>.message .header,:lang(zh-TW) .ui.statistic>.label,:lang(zh-TW) .ui.statistic>.value,:lang(zh-TW) .ui.statistics .statistic>.label,:lang(zh-TW) .ui.statistics .statistic>.value,:lang(zh-TW) .ui.steps .step .title,:lang(zh-TW) .ui.text.container,:lang(zh-TW) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang TC','Hiragino Sans TC','Source Han Sans TW','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU,sans-serif} -:lang(zh-HK) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif} -:lang(zh-HK) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif} -:lang(zh-HK) h1,:lang(zh-HK) h2,:lang(zh-HK) h3,:lang(zh-HK) h4,:lang(zh-HK) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif} -:lang(zh-HK) .home .hero h1,:lang(zh-HK) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif} -.ui.language>.menu>.item:lang(zh-HK),:lang(zh-HK) .ui.accordion .title:not(.ui),:lang(zh-HK) .ui.button,:lang(zh-HK) .ui.card>.content>.header.ui.card>.content>.header,:lang(zh-HK) .ui.category.search>.results .category>.name,:lang(zh-HK) .ui.form input:not([type]),:lang(zh-HK) .ui.form input[type=date],:lang(zh-HK) .ui.form input[type=datetime-local],:lang(zh-HK) .ui.form input[type=email],:lang(zh-HK) .ui.form input[type=file],:lang(zh-HK) .ui.form input[type=number],:lang(zh-HK) .ui.form input[type=password],:lang(zh-HK) .ui.form input[type=search],:lang(zh-HK) .ui.form input[type=tel],:lang(zh-HK) .ui.form input[type=text],:lang(zh-HK) .ui.form input[type=time],:lang(zh-HK) .ui.form input[type=url],:lang(zh-HK) .ui.header,:lang(zh-HK) .ui.input input,:lang(zh-HK) .ui.input>input,:lang(zh-HK) .ui.items>.item>.content>.header,:lang(zh-HK) .ui.list .list>.item .header,:lang(zh-HK) .ui.list>.item .header,:lang(zh-HK) .ui.menu,:lang(zh-HK) .ui.message .header,:lang(zh-HK) .ui.modal>.header,:lang(zh-HK) .ui.popup>.header,:lang(zh-HK) .ui.search>.results .result .title,:lang(zh-HK) .ui.search>.results>.message .header,:lang(zh-HK) .ui.statistic>.label,:lang(zh-HK) .ui.statistic>.value,:lang(zh-HK) .ui.statistics .statistic>.label,:lang(zh-HK) .ui.statistics .statistic>.value,:lang(zh-HK) .ui.steps .step .title,:lang(zh-HK) .ui.text.container,:lang(zh-HK) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'PingFang HK','Hiragino Sans TC','Source Han Sans HK','Source Han Sans TC','Noto Sans CJK TC','Microsoft JhengHei','Heiti TC',PMingLiU_HKSCS,PMingLiU,sans-serif} -:lang(ko) textarea{font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif} -:lang(ko) .markdown:not(code){font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif} -:lang(ko) h1,:lang(ko) h2,:lang(ko) h3,:lang(ko) h4,:lang(ko) h5{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif} -:lang(ko) .home .hero h1,:lang(ko) .home .hero h2{font-family:'PT Sans Narrow',Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif} -.ui.language>.menu>.item:lang(ko),:lang(ko) .ui.accordion .title:not(.ui),:lang(ko) .ui.button,:lang(ko) .ui.card>.content>.header.ui.card>.content>.header,:lang(ko) .ui.category.search>.results .category>.name,:lang(ko) .ui.form input:not([type]),:lang(ko) .ui.form input[type=date],:lang(ko) .ui.form input[type=datetime-local],:lang(ko) .ui.form input[type=email],:lang(ko) .ui.form input[type=file],:lang(ko) .ui.form input[type=number],:lang(ko) .ui.form input[type=password],:lang(ko) .ui.form input[type=search],:lang(ko) .ui.form input[type=tel],:lang(ko) .ui.form input[type=text],:lang(ko) .ui.form input[type=time],:lang(ko) .ui.form input[type=url],:lang(ko) .ui.header,:lang(ko) .ui.input input,:lang(ko) .ui.input>input,:lang(ko) .ui.items>.item>.content>.header,:lang(ko) .ui.list .list>.item .header,:lang(ko) .ui.list>.item .header,:lang(ko) .ui.menu,:lang(ko) .ui.message .header,:lang(ko) .ui.modal>.header,:lang(ko) .ui.popup>.header,:lang(ko) .ui.search>.results .result .title,:lang(ko) .ui.search>.results>.message .header,:lang(ko) .ui.statistic>.label,:lang(ko) .ui.statistic>.value,:lang(ko) .ui.statistics .statistic>.label,:lang(ko) .ui.statistics .statistic>.value,:lang(ko) .ui.steps .step .title,:lang(ko) .ui.text.container,:lang(ko) body{font-family:Lato,-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial,'Apple SD Gothic Neo',NanumBarunGothic,'Malgun Gothic',Gulim,Dotum,'Nanum Gothic','Source Han Sans KR','Noto Sans CJK KR',sans-serif} -img{border-radius:3px} -table{border-collapse:collapse} -a{cursor:pointer} -.rounded{border-radius:.28571429rem!important} -.wrap{word-wrap:break-word;word-break:break-all} -.mono,code,pre{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} -.mono.raw,code.raw,pre.raw{padding:7px 12px;margin:10px 0;background-color:#f8f8f8;border:1px solid #ddd;border-radius:3px;font-size:13px;line-height:1.5;overflow:auto} -.mono.wrap,code.wrap,pre.wrap{white-space:pre-wrap;word-break:break-all;overflow-wrap:break-word;word-wrap:break-word} -.dont-break-out{overflow-wrap:break-word;word-wrap:break-word;word-break:break-all;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto} -.full.height{flex-grow:1;padding-bottom:80px} -.following.bar{z-index:900;left:0;margin:0!important} -.following.bar.light{background-color:#fff;border-bottom:1px solid #ddd;box-shadow:0 2px 3px rgba(0,0,0,.04)} -.following.bar .column .menu{margin-top:0} -.following.bar .top.menu a.item.brand{padding-left:0} -.following.bar .brand .ui.mini.image{width:30px} -.following.bar .top.menu .dropdown.item.active,.following.bar .top.menu .dropdown.item:hover,.following.bar .top.menu a.item:hover{background-color:transparent} -.following.bar .top.menu a.item:hover{color:rgba(0,0,0,.45)} -.following.bar .top.menu .menu{z-index:900} -.following.bar .octicon{margin-right:.75em} -.following.bar .octicon.fitted{margin-right:0} -.following.bar .searchbox{background-color:#f4f4f4!important} -.following.bar .searchbox:focus{background-color:#e9e9e9!important} -.following.bar .text .octicon{width:16px;text-align:center} -.following.bar #navbar{width:100vw;min-height:52px;padding:0 .5rem} -.following.bar #navbar .brand{margin:0} -@media only screen and (max-width:767px){.following.bar #navbar:not(.shown)>:not(:first-child){display:none} -} -.right.stackable.menu{margin-left:auto;display:flex;align-items:inherit;flex-direction:inherit} -.ui.left{float:left} -.ui.right{float:right} -.ui.button,.ui.menu .item{-webkit-user-select:auto;-ms-user-select:auto;user-select:auto} -.ui.container.fluid.padded{padding:0 10px 0 10px} -.ui.form .ui.button{font-weight:400} -.ui.floating.label{z-index:10} -.ui.transparent.label{background-color:transparent} -.ui.nopadding{padding:0} -.ui.menu,.ui.segment,.ui.vertical.menu{box-shadow:none} -.ui .menu:not(.vertical) .item>.button.compact{padding:.58928571em 1.125em} -.ui .menu:not(.vertical) .item>.button.small{font-size:.92857143rem} -.ui.menu .ui.dropdown.item .menu .item{width:100%} -.ui.dropdown .menu>.item>.floating.label{z-index:11} -.ui.dropdown .menu .menu>.item>.floating.label{z-index:21} -.ui .text.red{color:#d95c5c!important} -.ui .text.red a{color:#d95c5c!important} -.ui .text.red a:hover{color:#e67777!important} -.ui .text.blue{color:#428bca!important} -.ui .text.blue a{color:#15c!important} -.ui .text.blue a:hover{color:#428bca!important} -.ui .text.black{color:#444} -.ui .text.black:hover{color:#000} -.ui .text.grey{color:#767676!important} -.ui .text.grey a{color:#444!important} -.ui .text.grey a:hover{color:#000!important} -.ui .text.light.grey{color:#888!important} -.ui .text.green{color:#6cc644!important} -.ui .text.purple{color:#6e5494!important} -.ui .text.yellow{color:#fbbd08!important} -.ui .text.gold{color:#a1882b!important} -.ui .text.left{text-align:left!important} -.ui .text.right{text-align:right!important} -.ui .text.small{font-size:.75em} -.ui .text.normal{font-weight:400} -.ui .text.bold{font-weight:700} -.ui .text.italic{font-style:italic} -.ui .text.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block} -.ui .text.thin{font-weight:400} -.ui .text.middle{vertical-align:middle} -.ui .text.nopadding{padding:0} -.ui .text.nomargin{margin:0} -.ui .message{text-align:center} -.ui .message>ul{margin-left:auto;margin-right:auto;display:table;text-align:left} -.ui.bottom.attached.message{font-weight:700;text-align:left;color:#000} -.ui.bottom.attached.message .pull-right{color:#000} -.ui.bottom.attached.message .pull-right>span,.ui.bottom.attached.message>span{color:#21ba45} -.ui .header>i+.content{padding-left:.75rem;vertical-align:middle} -.ui .warning.header{background-color:#f9edbe!important;border-color:#efc16b} -.ui .warning.segment{border-color:#efc16b} -.ui .info.segment{border:1px solid #c5d5dd} -.ui .info.segment.top{background-color:#e6f1f6!important} -.ui .info.segment.top h3,.ui .info.segment.top h4{margin-top:0} -.ui .info.segment.top h3:last-child{margin-top:4px} -.ui .info.segment.top>:last-child{margin-bottom:0} -.ui .normal.header{font-weight:400} -.ui .avatar.image{border-radius:3px} -.ui .form .fake{display:none!important} -.ui .form .sub.field{margin-left:25px} -.ui .sha.label{font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;font-size:13px;padding:6px 10px 4px 10px;font-weight:400;margin:0 6px} -.ui .button.truncate{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;vertical-align:top;white-space:nowrap;margin-right:6px} -.ui.status.buttons .octicon{margin-right:4px} -.ui.inline.delete-button{padding:8px 15px;font-weight:400} -.ui .background.red{background-color:#d95c5c!important} -.ui .background.blue{background-color:#428bca!important} -.ui .background.black{background-color:#444} -.ui .background.grey{background-color:#767676!important} -.ui .background.light.grey{background-color:#888!important} -.ui .background.green{background-color:#6cc644!important} -.ui .background.purple{background-color:#6e5494!important} -.ui .background.yellow{background-color:#fbbf09!important} -.ui .background.gold{background-color:#a1882b!important} -.ui .migrate{color:#888!important;opacity:.5} -.ui .migrate a{color:#444!important} -.ui .migrate a:hover{color:#000!important} -.ui .border{border:1px solid} -.ui .border.red{border-color:#d95c5c!important} -.ui .border.blue{border-color:#428bca!important} -.ui .border.black{border-color:#444} -.ui .border.grey{border-color:#767676!important} -.ui .border.light.grey{border-color:#888!important} -.ui .border.green{border-color:#6cc644!important} -.ui .border.purple{border-color:#6e5494!important} -.ui .border.yellow{border-color:#fbbd08!important} -.ui .border.gold{border-color:#a1882b!important} -.ui .branch-tag-choice{line-height:20px} -@media only screen and (max-width:767px){.ui.pagination.menu .item.navigation span.navigation_label,.ui.pagination.menu .item:not(.active):not(.navigation){display:none} -} -.file-comment{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;color:rgba(0,0,0,.87)} -.ui.floating.dropdown .overflow.menu .scrolling.menu.items{border-radius:0!important;box-shadow:none!important;border-bottom:1px solid rgba(34,36,38,.15)} -.user-menu>.item{width:100%;border-radius:0!important} -.scrolling.menu .item.selected{font-weight:700!important} -footer{background-color:#fff;border-top:1px solid #d6d6d6;width:100%;flex-basis:40px;color:#888} -footer .container{width:100vw!important;padding:0 .5rem} -footer .container .fa{width:16px;text-align:center;color:#428bca} -footer .container .links>*{border-left:1px solid #d6d6d6;padding-left:8px;margin-left:5px} -footer .container .links>:first-child{border-left:0} -footer .ui.language .menu{max-height:500px;overflow-y:auto;margin-bottom:7px} -footer .ui.left,footer .ui.right{line-height:40px} -.hide{display:none} -.hide.show-outdated{display:none!important} -.hide.hide-outdated{display:none!important} -.center{text-align:center} -.img-1{width:2px!important;height:2px!important} -.img-2{width:4px!important;height:4px!important} -.img-3{width:6px!important;height:6px!important} -.img-4{width:8px!important;height:8px!important} -.img-5{width:10px!important;height:10px!important} -.img-6{width:12px!important;height:12px!important} -.img-7{width:14px!important;height:14px!important} -.img-8{width:16px!important;height:16px!important} -.img-9{width:18px!important;height:18px!important} -.img-10{width:20px!important;height:20px!important} -.img-11{width:22px!important;height:22px!important} -.img-12{width:24px!important;height:24px!important} -.img-13{width:26px!important;height:26px!important} -.img-14{width:28px!important;height:28px!important} -.img-15{width:30px!important;height:30px!important} -.img-16{width:32px!important;height:32px!important} -@media only screen and (min-width:768px){.mobile-only,.ui.button.mobile-only{display:none} -.sr-mobile-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0} -} -@media only screen and (max-width:767px){.not-mobile{display:none} -} -.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0} -.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} -@media only screen and (max-width:991px) and (min-width:768px){.ui.container{width:95%} -} -.hljs{background:inherit!important;padding:0!important} -.ui.menu.new-menu{justify-content:center!important;padding-top:15px!important;margin-top:-15px!important;margin-bottom:15px!important;background-color:#fafafa!important;border-width:1px!important} -@media only screen and (max-width:1200px){.ui.menu.new-menu{overflow-x:auto!important;justify-content:left!important;padding-bottom:5px} -.ui.menu.new-menu::-webkit-scrollbar{height:8px;display:none} -.ui.menu.new-menu:hover::-webkit-scrollbar{display:block} -.ui.menu.new-menu::-webkit-scrollbar-track{background:rgba(0,0,0,.01)} -.ui.menu.new-menu::-webkit-scrollbar-thumb{background:rgba(0,0,0,.2)} -.ui.menu.new-menu:after{position:absolute;margin-top:-15px;display:block;background-image:linear-gradient(to right,rgba(255,255,255,0),#fff 100%);content:' ';right:0;height:53px;z-index:1000;width:60px;clear:none;visibility:visible} -.ui.menu.new-menu a.item:last-child{padding-right:30px!important} -} -[v-cloak]{display:none!important} -.repos-search{padding-bottom:0!important} -.repos-filter{margin-top:0!important;border-bottom-width:0!important;margin-bottom:2px!important} -#user-heatmap{width:107%;text-align:center} -#user-heatmap svg:not(:root){overflow:inherit;padding:0!important} -@media only screen and (max-width:1200px){#user-heatmap{display:none} -} -#user-heatmap .total-contributions{text-align:left;font-weight:500;margin-top:0} -.heatmap-color-0{background-color:#f4f4f4} -.heatmap-color-1{background-color:#d8efbf} -.heatmap-color-2{background-color:#9fdb81} -.heatmap-color-3{background-color:#66c74b} -.heatmap-color-4{background-color:#609926} -.heatmap-color-5{background-color:#025900} -.archived-icon{color:#b3b3b3!important} -.oauth2-authorize-application-box{margin-top:3em!important} -.ui.tabular.menu .item{color:rgba(0,0,0,.5)} -.ui.tabular.menu .item:hover{color:rgba(0,0,0,.8)} -.ui.tabular.menu .item.active{color:rgba(0,0,0,.9)} -.inline-grouped-list{display:inline-block;vertical-align:top} -.inline-grouped-list>.ui{display:block;margin-top:5px;margin-bottom:10px} -.inline-grouped-list>.ui:first-child{margin-top:1px} -i.icons .icon:first-child{margin-right:0} -i.icon.centerlock{top:1.5em} -.ui.label>.detail .icons{margin-right:.25em} -.ui.label>.detail .icons .icon{margin-right:0} -.lines-num{vertical-align:top;text-align:right!important;color:#999;background:#f5f5f5;width:1%;-webkit-user-select:none;-ms-user-select:none;user-select:none} -.lines-num span:before{content:attr(data-line-number);line-height:20px!important;padding:0 10px;cursor:pointer;display:block} -.lines-code,.lines-num{padding:0!important} -.lines-code .hljs,.lines-code ol,.lines-code pre,.lines-num .hljs,.lines-num ol,.lines-num pre{background-color:#fff;margin:0;padding:0!important} -.lines-code .hljs li,.lines-code ol li,.lines-code pre li,.lines-num .hljs li,.lines-num ol li,.lines-num pre li{display:block;width:100%} -.lines-code .hljs li:before,.lines-code ol li:before,.lines-code pre li:before,.lines-num .hljs li:before,.lines-num ol li:before,.lines-num pre li:before{content:' '} -.lines-commit{vertical-align:top;color:#999;padding:0!important;background:#f5f5f5;width:1%;-ms-user-select:none;-webkit-user-select:none;user-select:none} -.lines-commit .blame-info{width:350px;max-width:350px;display:block;-webkit-user-select:none;-ms-user-select:none;user-select:none;padding:0 0 0 10px} -.lines-commit .blame-info .blame-data{display:flex;font-family:-apple-system,BlinkMacSystemFont,system-ui,'Segoe UI',Roboto,Helvetica,Arial} -.lines-commit .blame-info .blame-data .blame-message{flex-grow:2;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;line-height:20px} -.lines-commit .blame-info .blame-data .blame-avatar,.lines-commit .blame-info .blame-data .blame-time{flex-shrink:0} -.lines-commit .ui.avatar.image{height:18px;width:18px} -.lines-code .bottom-line,.lines-commit .bottom-line,.lines-num .bottom-line{border-bottom:1px solid #eaecef} -.code-view{overflow:auto;overflow-x:auto;overflow-y:hidden} -.code-view :not(.fa):not(.octicon):not(.icon){font-size:12px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;line-height:20px} -.code-view table{width:100%} -.code-view .active{background:#fff866} -.markdown:not(code){overflow:hidden;font-size:16px;line-height:1.6!important;word-wrap:break-word} -.markdown:not(code).ui.segment{padding:3em} -.markdown:not(code).file-view{padding:2em 2em 2em!important} -.markdown:not(code)>:first-child{margin-top:0!important} -.markdown:not(code)>:last-child{margin-bottom:0!important} -.markdown:not(code) a:not([href]){color:inherit;text-decoration:none} -.markdown:not(code) .absent{color:#c00} -.markdown:not(code) .anchor{position:absolute;top:0;left:0;display:block;padding-right:6px;padding-left:30px;margin-left:-30px} -.markdown:not(code) .anchor:focus{outline:0} -.markdown:not(code) h1,.markdown:not(code) h2,.markdown:not(code) h3,.markdown:not(code) h4,.markdown:not(code) h5,.markdown:not(code) h6{position:relative;margin-top:1em;margin-bottom:16px;font-weight:700;line-height:1.4} -.markdown:not(code) h1:first-of-type,.markdown:not(code) h2:first-of-type,.markdown:not(code) h3:first-of-type,.markdown:not(code) h4:first-of-type,.markdown:not(code) h5:first-of-type,.markdown:not(code) h6:first-of-type{margin-top:0!important} -.markdown:not(code) h1 .octicon-link,.markdown:not(code) h2 .octicon-link,.markdown:not(code) h3 .octicon-link,.markdown:not(code) h4 .octicon-link,.markdown:not(code) h5 .octicon-link,.markdown:not(code) h6 .octicon-link{display:none;color:#000;vertical-align:middle} -.markdown:not(code) h1:hover .anchor,.markdown:not(code) h2:hover .anchor,.markdown:not(code) h3:hover .anchor,.markdown:not(code) h4:hover .anchor,.markdown:not(code) h5:hover .anchor,.markdown:not(code) h6:hover .anchor{padding-left:8px;margin-left:-30px;text-decoration:none} -.markdown:not(code) h1:hover .anchor .octicon-link,.markdown:not(code) h2:hover .anchor .octicon-link,.markdown:not(code) h3:hover .anchor .octicon-link,.markdown:not(code) h4:hover .anchor .octicon-link,.markdown:not(code) h5:hover .anchor .octicon-link,.markdown:not(code) h6:hover .anchor .octicon-link{display:inline-block} -.markdown:not(code) h1 code,.markdown:not(code) h1 tt,.markdown:not(code) h2 code,.markdown:not(code) h2 tt,.markdown:not(code) h3 code,.markdown:not(code) h3 tt,.markdown:not(code) h4 code,.markdown:not(code) h4 tt,.markdown:not(code) h5 code,.markdown:not(code) h5 tt,.markdown:not(code) h6 code,.markdown:not(code) h6 tt{font-size:inherit} -.markdown:not(code) h1{padding-bottom:.3em;font-size:2.25em;line-height:1.2;border-bottom:1px solid #eee} -.markdown:not(code) h1 .anchor{line-height:1} -.markdown:not(code) h2{padding-bottom:.3em;font-size:1.75em;line-height:1.225;border-bottom:1px solid #eee} -.markdown:not(code) h2 .anchor{line-height:1} -.markdown:not(code) h3{font-size:1.5em;line-height:1.43} -.markdown:not(code) h3 .anchor{line-height:1.2} -.markdown:not(code) h4{font-size:1.25em} -.markdown:not(code) h4 .anchor{line-height:1.2} -.markdown:not(code) h5{font-size:1em} -.markdown:not(code) h5 .anchor{line-height:1.1} -.markdown:not(code) h6{font-size:1em;color:#777} -.markdown:not(code) h6 .anchor{line-height:1.1} -.markdown:not(code) blockquote,.markdown:not(code) dl,.markdown:not(code) ol,.markdown:not(code) p,.markdown:not(code) pre,.markdown:not(code) table,.markdown:not(code) ul{margin-top:0;margin-bottom:16px} -.markdown:not(code) hr{height:4px;padding:0;margin:16px 0;background-color:#e7e7e7;border:0} -.markdown:not(code) ol,.markdown:not(code) ul{padding-left:2em} -.markdown:not(code) ol.no-list,.markdown:not(code) ul.no-list{padding:0;list-style-type:none} -.markdown:not(code) ol ol,.markdown:not(code) ol ul,.markdown:not(code) ul ol,.markdown:not(code) ul ul{margin-top:0;margin-bottom:0} -.markdown:not(code) ol ol,.markdown:not(code) ul ol{list-style-type:lower-roman} -.markdown:not(code) li>p{margin-top:0} -.markdown:not(code) dl{padding:0} -.markdown:not(code) dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:700} -.markdown:not(code) dl dd{padding:0 16px;margin-bottom:16px} -.markdown:not(code) blockquote{margin-left:0;padding:0 15px;color:#777;border-left:4px solid #ddd} -.markdown:not(code) blockquote>:first-child{margin-top:0} -.markdown:not(code) blockquote>:last-child{margin-bottom:0} -.markdown:not(code) table{width:auto;overflow:auto;word-break:keep-all;display:block} -.markdown:not(code) table th{font-weight:700} -.markdown:not(code) table td,.markdown:not(code) table th{padding:6px 13px!important;border:1px solid #ddd!important} -.markdown:not(code) table tr{background-color:#fff;border-top:1px solid #ccc} -.markdown:not(code) table tr:nth-child(2n){background-color:#f8f8f8} -.markdown:not(code) img{max-width:100%;box-sizing:border-box} -.markdown:not(code) .emoji{max-width:none} -.markdown:not(code) span.frame{display:block;overflow:hidden} -.markdown:not(code) span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid #ddd} -.markdown:not(code) span.frame span img{display:block;float:left} -.markdown:not(code) span.frame span span{display:block;padding:5px 0 0;clear:both;color:#333} -.markdown:not(code) span.align-center{display:block;overflow:hidden;clear:both} -.markdown:not(code) span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center} -.markdown:not(code) span.align-center span img{margin:0 auto;text-align:center} -.markdown:not(code) span.align-right{display:block;overflow:hidden;clear:both} -.markdown:not(code) span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right} -.markdown:not(code) span.align-right span img{margin:0;text-align:right} -.markdown:not(code) span.float-left{display:block;float:left;margin-right:13px;overflow:hidden} -.markdown:not(code) span.float-left span{margin:13px 0 0} -.markdown:not(code) span.float-right{display:block;float:right;margin-left:13px;overflow:hidden} -.markdown:not(code) span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right} -.markdown:not(code) code,.markdown:not(code) tt{padding:.2em 0;margin:0;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px} -.markdown:not(code) code:after,.markdown:not(code) code:before,.markdown:not(code) tt:after,.markdown:not(code) tt:before{letter-spacing:-.2em;content:"\00a0"} -.markdown:not(code) code br,.markdown:not(code) tt br{display:none} -.markdown:not(code) del code{text-decoration:inherit} -.markdown:not(code) pre>code{padding:0;margin:0;font-size:100%;word-break:normal;white-space:pre;background:0 0;border:0} -.markdown:not(code) .highlight{margin-bottom:16px} -.markdown:not(code) .highlight pre,.markdown:not(code) pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border-radius:3px} -.markdown:not(code) .highlight pre{margin-bottom:0;word-break:normal} -.markdown:not(code) pre{word-wrap:normal} -.markdown:not(code) pre code,.markdown:not(code) pre tt{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background-color:transparent;border:0} -.markdown:not(code) pre code:after,.markdown:not(code) pre code:before,.markdown:not(code) pre tt:after,.markdown:not(code) pre tt:before{content:normal} -.markdown:not(code) kbd{display:inline-block;padding:3px 5px;font-size:11px;line-height:10px;color:#555;vertical-align:middle;background-color:#fcfcfc;border:solid 1px #ccc;border-bottom-color:#bbb;border-radius:3px;box-shadow:inset 0 -1px 0 #bbb} -.markdown:not(code) input[type=checkbox]{vertical-align:middle!important} -.markdown:not(code) .csv-data td,.markdown:not(code) .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap} -.markdown:not(code) .csv-data .blob-num{padding:10px 8px 9px;text-align:right;background:#fff;border:0} -.markdown:not(code) .csv-data tr{border-top:0} -.markdown:not(code) .csv-data th{font-weight:700;background:#f8f8f8;border-top:0} -.markdown:not(code) .ui.list .list,.markdown:not(code) ol.ui.list ol,.markdown:not(code) ul.ui.list ul{padding-left:2em} -.repository.wiki.revisions .ui.container>.ui.stackable.grid{flex-direction:row-reverse} -.repository.wiki.revisions .ui.container>.ui.stackable.grid>.header{margin-top:0} -.repository.wiki.revisions .ui.container>.ui.stackable.grid>.header .sub.header{padding-left:52px;word-break:break-word} -.file-revisions-btn{display:block;float:left;margin-bottom:2px!important;padding:11px!important;margin-right:10px!important} -.file-revisions-btn i{-webkit-touch-callout:none;-webkit-user-select:none;-ms-user-select:none;user-select:none} -.home .logo{max-width:220px} -@media only screen and (max-width:767px){.home .hero h1{font-size:3.5em} -.home .hero h2{font-size:2em} -} -@media only screen and (min-width:768px){.home .hero h1{font-size:5.5em} -.home .hero h2{font-size:3em} -} -.home .hero .octicon{color:#5aa509;font-size:40px;width:50px} -.home .hero.header{font-size:20px} -.home p.large{font-size:16px} -.home .stackable{padding-top:30px} -.home a{color:#5aa509} -.signup{padding-top:15px} -@media only screen and (max-width:880px){footer .ui.container .left,footer .ui.container .right{display:block;text-align:center;float:none} -} -.install{padding-top:45px} -.install form label{text-align:right;width:320px!important} -.install form input{width:35%!important} -.install form .field{text-align:left} -.install form .field .help{margin-left:335px!important} -.install form .field.optional .title{margin-left:38%} -.install .ui .checkbox{margin-left:40%!important} -.install .ui .checkbox label{width:auto!important} -.form .help{color:#999;padding-top:.6em;padding-bottom:.6em;display:inline-block} -.ui.attached.header{background:#f0f0f0} -.ui.attached.header .right{margin-top:-5px} -.ui.attached.header .right .button{padding:8px 10px;font-weight:400} -#create-page-form form{margin:auto} -#create-page-form form .ui.message{text-align:center} -@media only screen and (min-width:768px){#create-page-form form{width:800px!important} -#create-page-form form .header{padding-left:280px!important} -#create-page-form form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word} -#create-page-form form .help{margin-left:265px!important} -#create-page-form form .optional .title{margin-left:250px!important} -#create-page-form form input,#create-page-form form textarea{width:50%!important} -} -@media only screen and (max-width:767px){#create-page-form form .optional .title{margin-left:15px} -#create-page-form form .inline.field>label{display:block} -} -.signin .oauth2 div{display:inline-block} -.signin .oauth2 div p{margin:10px 5px 0 0;float:left} -.signin .oauth2 a{margin-right:3px} -.signin .oauth2 a:last-child{margin-right:0} -.signin .oauth2 img{width:32px;height:32px} -.signin .oauth2 img.openidConnect{width:auto} -@media only screen and (min-width:768px){.g-recaptcha{margin:0 auto!important;width:304px;padding-left:30px} -} -@media screen and (max-height:575px){#rc-imageselect,.g-recaptcha{transform:scale(.77);transform-origin:0 0} -} -.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{margin:auto} -.user.activate form .ui.message,.user.forgot.password form .ui.message,.user.reset.password form .ui.message,.user.signin form .ui.message,.user.signup form .ui.message{text-align:center} -@media only screen and (min-width:768px){.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{width:800px!important} -.user.activate form .header,.user.forgot.password form .header,.user.reset.password form .header,.user.signin form .header,.user.signup form .header{padding-left:280px!important} -.user.activate form .inline.field>label,.user.forgot.password form .inline.field>label,.user.reset.password form .inline.field>label,.user.signin form .inline.field>label,.user.signup form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word} -.user.activate form .help,.user.forgot.password form .help,.user.reset.password form .help,.user.signin form .help,.user.signup form .help{margin-left:265px!important} -.user.activate form .optional .title,.user.forgot.password form .optional .title,.user.reset.password form .optional .title,.user.signin form .optional .title,.user.signup form .optional .title{margin-left:250px!important} -.user.activate form input,.user.activate form textarea,.user.forgot.password form input,.user.forgot.password form textarea,.user.reset.password form input,.user.reset.password form textarea,.user.signin form input,.user.signin form textarea,.user.signup form input,.user.signup form textarea{width:50%!important} -} -@media only screen and (max-width:767px){.user.activate form .optional .title,.user.forgot.password form .optional .title,.user.reset.password form .optional .title,.user.signin form .optional .title,.user.signup form .optional .title{margin-left:15px} -.user.activate form .inline.field>label,.user.forgot.password form .inline.field>label,.user.reset.password form .inline.field>label,.user.signin form .inline.field>label,.user.signup form .inline.field>label{display:block} -} -.user.activate form,.user.forgot.password form,.user.reset.password form,.user.signin form,.user.signup form{width:700px!important} -.user.activate form .header,.user.forgot.password form .header,.user.reset.password form .header,.user.signin form .header,.user.signup form .header{padding-left:0!important;text-align:center} -.user.activate form .inline.field>label,.user.forgot.password form .inline.field>label,.user.reset.password form .inline.field>label,.user.signin form .inline.field>label,.user.signup form .inline.field>label{width:200px} -@media only screen and (max-width:768px){.user.activate form .inline.field>label,.user.activate form input,.user.forgot.password form .inline.field>label,.user.forgot.password form input,.user.reset.password form .inline.field>label,.user.reset.password form input,.user.signin form .inline.field>label,.user.signin form input,.user.signup form .inline.field>label,.user.signup form input{width:100%!important} -} -.user.activate form input[type=number],.user.forgot.password form input[type=number],.user.reset.password form input[type=number],.user.signin form input[type=number],.user.signup form input[type=number]{-moz-appearance:textfield} -.user.activate form input::-webkit-inner-spin-button,.user.activate form input::-webkit-outer-spin-button,.user.forgot.password form input::-webkit-inner-spin-button,.user.forgot.password form input::-webkit-outer-spin-button,.user.reset.password form input::-webkit-inner-spin-button,.user.reset.password form input::-webkit-outer-spin-button,.user.signin form input::-webkit-inner-spin-button,.user.signin form input::-webkit-outer-spin-button,.user.signup form input::-webkit-inner-spin-button,.user.signup form input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0} -.repository.new.fork form,.repository.new.migrate form,.repository.new.repo form{margin:auto} -.repository.new.fork form .ui.message,.repository.new.migrate form .ui.message,.repository.new.repo form .ui.message{text-align:center} -@media only screen and (min-width:768px){.repository.new.fork form,.repository.new.migrate form,.repository.new.repo form{width:800px!important} -.repository.new.fork form .header,.repository.new.migrate form .header,.repository.new.repo form .header{padding-left:280px!important} -.repository.new.fork form .inline.field>label,.repository.new.migrate form .inline.field>label,.repository.new.repo form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word} -.repository.new.fork form .help,.repository.new.migrate form .help,.repository.new.repo form .help{margin-left:265px!important} -.repository.new.fork form .optional .title,.repository.new.migrate form .optional .title,.repository.new.repo form .optional .title{margin-left:250px!important} -.repository.new.fork form input,.repository.new.fork form textarea,.repository.new.migrate form input,.repository.new.migrate form textarea,.repository.new.repo form input,.repository.new.repo form textarea{width:50%!important} -} -@media only screen and (max-width:767px){.repository.new.fork form .optional .title,.repository.new.migrate form .optional .title,.repository.new.repo form .optional .title{margin-left:15px} -.repository.new.fork form .inline.field>label,.repository.new.migrate form .inline.field>label,.repository.new.repo form .inline.field>label{display:block} -} -.repository.new.fork form .dropdown .dropdown.icon,.repository.new.migrate form .dropdown .dropdown.icon,.repository.new.repo form .dropdown .dropdown.icon{margin-top:-7px!important;padding-bottom:5px} -.repository.new.fork form .dropdown .text,.repository.new.migrate form .dropdown .text,.repository.new.repo form .dropdown .text{margin-right:0!important} -.repository.new.fork form .dropdown .text i,.repository.new.migrate form .dropdown .text i,.repository.new.repo form .dropdown .text i{margin-right:0!important} -.repository.new.fork form .header,.repository.new.migrate form .header,.repository.new.repo form .header{padding-left:0!important;text-align:center} -@media only screen and (max-width:768px){.repository.new.fork form .selection.dropdown,.repository.new.fork form input,.repository.new.fork form label,.repository.new.migrate form .selection.dropdown,.repository.new.migrate form input,.repository.new.migrate form label,.repository.new.repo form .selection.dropdown,.repository.new.repo form input,.repository.new.repo form label{width:100%!important} -.repository.new.fork form .field a,.repository.new.fork form .field button,.repository.new.migrate form .field a,.repository.new.migrate form .field button,.repository.new.repo form .field a,.repository.new.repo form .field button{margin-bottom:1em;width:100%} -} -@media only screen and (min-width:768px){.repository.new.repo .ui.form #auto-init{margin-left:265px!important} -} -.repository.new.repo .ui.form .selection.dropdown:not(.owner){width:50%!important} -@media only screen and (max-width:768px){.repository.new.repo .ui.form .selection.dropdown:not(.owner){width:100%!important} -} -.new.webhook form .help{margin-left:25px} -.new.webhook .events.fields .column{padding-left:40px} -.githook textarea{font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} -@media only screen and (max-width:768px){.new.org .ui.form .field a,.new.org .ui.form .field button{margin-bottom:1em;width:100%} -.new.org .ui.form .field input{width:100%!important} -} -.repository{padding-top:15px} -.repository .repo-header .ui.compact.menu{margin-left:1rem} -.repository .repo-header .ui.header{margin-top:0} -.repository .repo-header .mega-octicon{width:30px;font-size:30px} -.repository .repo-header .ui.huge.breadcrumb{font-weight:400;font-size:1.5rem} -.repository .repo-header .ui.huge.breadcrumb i.mega-octicon{position:relative;top:5px} -.repository .repo-header .ui.huge.breadcrumb i.octicon-lock{margin-left:5px} -.repository .repo-header .fork-flag{margin-left:36px;margin-top:3px;display:block;font-size:12px;white-space:nowrap} -.repository .repo-header .octicon.octicon-repo-forked{margin-top:-1px;font-size:15px} -.repository .repo-header .button{margin-top:2px;margin-bottom:2px} -.repository .tabs .navbar{justify-content:initial} -.repository .navbar{display:flex;justify-content:space-between} -.repository .navbar .ui.label{margin-left:7px;padding:3px 5px} -.repository .owner.dropdown{min-width:40%!important} -.repository #file-buttons{margin-left:auto!important;font-weight:400} -.repository #file-buttons .ui.button{padding:8px 10px;font-weight:400} -.repository .metas .menu{overflow-x:auto} -.repository .metas .ui.list .hide{display:none!important} -.repository .metas .ui.list .item{padding:0} -.repository .metas .ui.list .label.color{padding:0 8px;margin-right:5px} -.repository .metas .ui.list a{margin:2px 0} -.repository .metas .ui.list a .text{color:#444} -.repository .metas .ui.list a .text:hover{color:#000} -.repository .metas #deadlineForm input{width:12.8rem;border-radius:4px 0 0 4px;border-right:0;white-space:nowrap} -.repository .header-wrapper{background-color:#fafafa;margin-top:-15px;padding-top:15px} -.repository .header-wrapper .ui.tabs.divider{border-bottom:0} -.repository .header-wrapper .ui.tabular .octicon{margin-right:5px} -.repository .filter.menu .label.color{border-radius:3px;margin-left:15px;padding:0 8px} -.repository .filter.menu .octicon{float:left;margin:5px -7px 0 -5px;width:16px} -.repository .filter.menu.labels .octicon{margin:-2px -7px 0 -5px} -.repository .filter.menu.labels .label-filter .menu .info{display:inline-block;padding:9px 7px 7px 7px;text-align:center;border-bottom:1px solid #ccc;font-size:12px} -.repository .filter.menu.labels .label-filter .menu .info code{border:1px solid #ccc;border-radius:3px;padding:3px 2px 1px 2px;font-size:11px} -.repository .filter.menu .text{margin-left:.9em} -.repository .filter.menu .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important} -.repository .filter.menu .dropdown.item{margin:1px;padding-right:0} -.repository .select-label .item{max-width:250px;overflow:hidden;text-overflow:ellipsis} -.repository .select-label .desc{padding-left:16px} -.repository .ui.tabs.container{margin-top:14px;margin-bottom:0} -.repository .ui.tabs.container .ui.menu{border-bottom:0} -.repository .ui.tabs.divider{margin-top:0;margin-bottom:20px} -.repository #clone-panel{width:350px} -@media only screen and (max-width:768px){.repository #clone-panel{width:100%} -} -.repository #clone-panel input{border-radius:0;padding:5px 10px;width:50%} -.repository #clone-panel .clone.button{font-size:13px;padding:0 5px} -.repository #clone-panel .clone.button:first-child{border-radius:.28571429rem 0 0 .28571429rem} -.repository #clone-panel .icon.button{padding:0 10px} -.repository #clone-panel .dropdown .menu{right:0!important;left:auto!important} -.repository.file.list .repo-description{display:flex;justify-content:space-between;align-items:center} -.repository.file.list #repo-desc{font-size:1.2em} -.repository.file.list .choose.reference .header .icon{font-size:1.4em} -.repository.file.list .repo-path .divider,.repository.file.list .repo-path .section{display:inline} -.repository.file.list #file-buttons{font-weight:400} -.repository.file.list #file-buttons .ui.button{padding:8px 10px;font-weight:400} -@media only screen and (max-width:768px){.repository.file.list #file-buttons .ui.tiny.blue.buttons{width:100%} -} -.repository.file.list #repo-files-table thead th{padding-top:8px;padding-bottom:5px;font-weight:400} -.repository.file.list #repo-files-table thead .ui.avatar{margin-bottom:5px} -.repository.file.list #repo-files-table thead .commit-summary a{text-decoration:underline;-webkit-text-decoration-style:dashed;text-decoration-style:dashed} -.repository.file.list #repo-files-table thead .commit-summary a:hover{-webkit-text-decoration-style:solid;text-decoration-style:solid} -.repository.file.list #repo-files-table thead .commit-summary a.default-link{text-decoration:none} -.repository.file.list #repo-files-table thead .commit-summary a.default-link:hover{text-decoration:underline;-webkit-text-decoration-style:solid;text-decoration-style:solid} -.repository.file.list #repo-files-table tbody .octicon{margin-left:3px;margin-right:5px;color:#777} -.repository.file.list #repo-files-table tbody .octicon.octicon-mail-reply{margin-right:10px} -.repository.file.list #repo-files-table tbody .octicon.octicon-file-directory,.repository.file.list #repo-files-table tbody .octicon.octicon-file-submodule,.repository.file.list #repo-files-table tbody .octicon.octicon-file-symlink-directory{color:#1e70bf} -.repository.file.list #repo-files-table td{padding-top:8px;padding-bottom:8px;overflow:initial} -.repository.file.list #repo-files-table td.name{max-width:150px} -.repository.file.list #repo-files-table td.message{max-width:400px} -.repository.file.list #repo-files-table td.age{width:120px} -.repository.file.list #repo-files-table td .truncate{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;vertical-align:top;white-space:nowrap} -.repository.file.list #repo-files-table td.message .isSigned{cursor:default} -.repository.file.list #repo-files-table tr:hover{background-color:#ffe} -.repository.file.list #repo-files-table .jumpable-path{color:#888} -.repository.file.list .non-diff-file-content .header .icon{font-size:1em} -.repository.file.list .non-diff-file-content .header .file-actions{margin-bottom:-5px} -.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon{display:inline-block;padding:5px;margin-left:5px;line-height:1;color:#767676;vertical-align:middle;background:0 0;border:0;outline:0} -.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon:hover{color:#4078c0} -.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon-danger:hover{color:#bd2c00} -.repository.file.list .non-diff-file-content .header .file-actions .btn-octicon.disabled{color:#bbb;cursor:default} -.repository.file.list .non-diff-file-content .header .file-actions #delete-file-form{display:inline-block} -.repository.file.list .non-diff-file-content .view-raw{padding:5px} -.repository.file.list .non-diff-file-content .view-raw *{max-width:100%} -.repository.file.list .non-diff-file-content .view-raw img{padding:5px 5px 0 5px} -.repository.file.list .non-diff-file-content .plain-text{padding:1em 2em 1em 2em} -.repository.file.list .non-diff-file-content .plain-text pre{word-break:break-word;white-space:pre-wrap} -.repository.file.list .non-diff-file-content .csv{overflow-x:auto;padding:0!important} -.repository.file.list .non-diff-file-content pre{overflow:auto} -.repository.file.list .sidebar{padding-left:0} -.repository.file.list .sidebar .octicon{width:16px} -.repository.file.editor .treepath{width:100%} -.repository.file.editor .treepath input{vertical-align:middle;box-shadow:rgba(0,0,0,.0745098) 0 1px 2px inset;width:inherit;padding:7px 8px;margin-right:5px} -.repository.file.editor .tabular.menu .octicon{margin-right:5px} -.repository.file.editor .commit-form-wrapper{padding-left:64px} -.repository.file.editor .commit-form-wrapper .commit-avatar{float:left;margin-left:-64px;width:3em;height:auto} -.repository.file.editor .commit-form-wrapper .commit-form{position:relative;padding:15px;margin-bottom:10px;border:1px solid #ddd;border-radius:3px} -.repository.file.editor .commit-form-wrapper .commit-form:after,.repository.file.editor .commit-form-wrapper .commit-form:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} -.repository.file.editor .commit-form-wrapper .commit-form:before{border-right-color:#d3d3d4;border-width:9px;margin-top:-9px} -.repository.file.editor .commit-form-wrapper .commit-form:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px} -.repository.file.editor .commit-form-wrapper .commit-form:after{border-right-color:#fff} -.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .branch-name{display:inline-block;padding:3px 6px;font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;color:rgba(0,0,0,.65);background-color:rgba(209,227,237,.45);border-radius:3px} -.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input{position:relative;margin-left:25px} -.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .new-branch-name-input input{width:240px!important;padding-left:26px!important} -.repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .octicon-git-branch{position:absolute;top:9px;left:10px;color:#b0c4ce} -.repository.options #interval{width:100px!important;min-width:100px} -.repository.options .danger .item{padding:20px 15px} -.repository.options .danger .ui.divider{margin:0} -.repository .comment textarea{max-height:none!important} -.repository.new.issue .comment.form .comment .avatar{width:3em} -.repository.new.issue .comment.form .content{margin-left:4em} -.repository.new.issue .comment.form .content:after,.repository.new.issue .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} -.repository.new.issue .comment.form .content:before{border-right-color:#d3d3d4;border-width:9px;margin-top:-9px} -.repository.new.issue .comment.form .content:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px} -.repository.new.issue .comment.form .content:after{border-right-color:#fff} -.repository.new.issue .comment.form .content .markdown{font-size:14px} -.repository.new.issue .comment.form .metas{min-width:220px} -.repository.new.issue .comment.form .metas .filter.menu{max-height:300px;overflow-x:auto} -.repository.view.issue .title{padding-bottom:0!important} -.repository.view.issue .title h1{font-weight:300;font-size:2.3rem;margin-bottom:5px} -.repository.view.issue .title h1 .ui.input{font-size:.5em;vertical-align:top;width:50%;min-width:600px} -.repository.view.issue .title h1 .ui.input input{font-size:1.5em;padding:6px 10px} -.repository.view.issue .title .index{font-weight:300;color:#aaa;letter-spacing:-1px} -.repository.view.issue .title .label{margin-right:10px} -.repository.view.issue .title .edit-zone{margin-top:10px} -.repository.view.issue .pull-desc code{color:#0166e6} -.repository.view.issue .pull.tabular.menu{margin-bottom:10px} -.repository.view.issue .pull.tabular.menu .octicon{margin-right:5px} -.repository.view.issue .pull.tab.segment{border:0;padding:10px 0 0;box-shadow:none;background-color:inherit} -.repository.view.issue .pull .merge.box .avatar{margin-left:10px;margin-top:10px} -.repository.view.issue .pull .review-item .avatar,.repository.view.issue .pull .review-item .type-icon{float:none;display:inline-block;text-align:center;vertical-align:middle} -.repository.view.issue .pull .review-item .avatar .octicon,.repository.view.issue .pull .review-item .type-icon .octicon{width:23px;font-size:23px;margin-top:.45em} -.repository.view.issue .pull .review-item .text{margin:.3em 0 .5em .5em} -.repository.view.issue .pull .review-item .type-icon{float:right;margin-right:1em} -.repository.view.issue .pull .review-item .divider{margin:.5rem 0} -.repository.view.issue .pull .review-item .review-content{padding:1em 0 1em 3.8em} -.repository.view.issue .comment-list:not(.prevent-before-timeline):before{display:block;content:"";position:absolute;margin-top:12px;margin-bottom:14px;top:0;bottom:0;left:96px;width:2px;background-color:#f3f3f3;z-index:-1} -.repository.view.issue .comment-list .timeline-line{position:relative;display:block;width:100%;max-width:100%} -.repository.view.issue .comment-list .timeline-line:before{display:block;content:"";position:absolute;margin-top:12px;margin-bottom:14px;top:0;bottom:0;left:82px;width:2px;background-color:#f3f3f3;z-index:-1} -.repository.view.issue .comment-list .comment .avatar{width:3em} -.repository.view.issue .comment-list .comment .tag{color:#767676;margin-top:3px;padding:2px 5px;font-size:12px;border:1px solid rgba(0,0,0,.1);border-radius:3px} -.repository.view.issue .comment-list .comment .tag.pending{color:#000;background-color:#fffbb2;margin-left:5px} -.repository.view.issue .comment-list .comment .actions .item{float:left} -.repository.view.issue .comment-list .comment .actions .item.context{float:none} -.repository.view.issue .comment-list .comment .actions .item.tag{margin-right:5px} -.repository.view.issue .comment-list .comment .actions .item.action{margin-top:6px;margin-left:10px} -.repository.view.issue .comment-list .comment .content{margin-left:4em} -.repository.view.issue .comment-list .comment .content>.header{font-weight:400;padding:auto 15px;position:relative;color:#767676;background-color:#f7f7f7;border-bottom:1px solid #eee;border-top-left-radius:3px;border-top-right-radius:3px} -.repository.view.issue .comment-list .comment .content>.header:after,.repository.view.issue .comment-list .comment .content>.header:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} -.repository.view.issue .comment-list .comment .content>.header:before{border-right-color:#d3d3d4;border-width:9px;margin-top:-9px} -.repository.view.issue .comment-list .comment .content>.header:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px} -.repository.view.issue .comment-list .comment .content>.header .text{max-width:78%;padding-top:10px;padding-bottom:10px} -.repository.view.issue .comment-list .comment .content>.merge-section{border-top:1px solid #d4d4d5;background-color:#f7f7f7} -.repository.view.issue .comment-list .comment .content .markdown{font-size:14px} -.repository.view.issue .comment-list .comment .content .no-content{color:#767676;font-style:italic} -.repository.view.issue .comment-list .comment .content>.bottom.segment{background:#f3f4f5} -.repository.view.issue .comment-list .comment .content>.bottom.segment .ui.images::after{clear:both;content:' ';display:block} -.repository.view.issue .comment-list .comment .content>.bottom.segment a{display:block;float:left;margin:5px;padding:5px;height:150px;border:solid 1px #eee;border-radius:3px;max-width:150px;background-color:#fff} -.repository.view.issue .comment-list .comment .content>.bottom.segment a:before{content:' ';display:inline-block;height:100%;vertical-align:middle} -.repository.view.issue .comment-list .comment .content>.bottom.segment .ui.image{max-height:100%;width:auto;margin:0;vertical-align:middle} -.repository.view.issue .comment-list .comment .content>.bottom.segment span.ui.image{font-size:128px;color:#000} -.repository.view.issue .comment-list .comment .content>.bottom.segment span.ui.image:hover{color:#000} -.repository.view.issue .comment-list .comment .ui.form .field:first-child{clear:none} -.repository.view.issue .comment-list .comment .ui.form .tab.segment{border:0;padding:10px 0 0} -.repository.view.issue .comment-list .comment .ui.form textarea{height:200px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} -.repository.view.issue .comment-list .comment .edit.buttons{margin-top:10px} -.repository.view.issue .comment-list .event{position:relative;margin:15px 0 15px 79px;padding-left:25px} -.repository.view.issue .comment-list .event>.octicon:not(.issue-symbol){text-shadow:-2px 0 #fff,0 2px #fff,2px 0 #fff,0 -2px #fff} -.repository.view.issue .comment-list .event>.octicon.issue-symbol{font-size:20px;margin-left:-35px;margin-right:-1px;margin-top:0!important;height:28px;width:28px;border-radius:50%;text-align:center;line-height:28px;background:#eee} -.repository.view.issue .comment-list .event>.octicon.issue-symbol::before{width:15px;display:inline-block} -.repository.view.issue .comment-list .event>.octicon.issue-symbol.octicon-key::before{width:18px} -.repository.view.issue .comment-list .event>.octicon.issue-symbol.octicon-circle-slash::before{width:17px} -.repository.view.issue .comment-list .event>.octicon.issue-symbol.octicon-comment{font-size:21px;line-height:33px} -.repository.view.issue .comment-list .event>.octicon.issue-symbol.octicon-comment::before{width:20px} -.repository.view.issue .comment-list .event .octicon{width:30px;float:left;text-align:center} -.repository.view.issue .comment-list .event .octicon.octicon-circle-slash{margin-top:5px;margin-left:-34.5px;font-size:20px;color:#bd2c00} -.repository.view.issue .comment-list .event .octicon.octicon-primitive-dot{margin-top:-1px;margin-left:-28.5px;margin-right:-1px;font-size:30px;color:#6cc644} -.repository.view.issue .comment-list .event .octicon.octicon-bookmark{margin-top:2px;margin-left:-31px;margin-right:-1px;font-size:25px} -.repository.view.issue .comment-list .event .octicon.octicon-eye{margin-top:3px;margin-left:-36px;margin-right:0;font-size:22px} -.repository.view.issue .comment-list .event .octicon.octicon-x{margin-left:-33px;font-size:25px} -.repository.view.issue .comment-list .event .detail{font-size:.9rem;margin-top:5px;margin-left:35px} -.repository.view.issue .comment-list .event .detail .octicon.octicon-git-commit{margin-top:2px} -.repository.view.issue .ui.segment.metas{margin-top:-3px} -.repository.view.issue .ui.participants img{margin-top:5px;margin-right:5px} -.repository.view.issue .ui.depending .item.is-closed .title{text-decoration:line-through} -.repository .comment.form .ui.comments{margin-top:-12px;max-width:100%} -.repository .comment.form .content .field:first-child{clear:none} -.repository .comment.form .content .form:after,.repository .comment.form .content .form:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} -.repository .comment.form .content .form:before{border-right-color:#d3d3d4;border-width:9px;margin-top:-9px} -.repository .comment.form .content .form:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px} -.repository .comment.form .content .form:after{border-right-color:#fff} -.repository .comment.form .content .tab.segment{border:0;padding:10px 0 0} -.repository .comment.form .content textarea{height:200px;font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} -.repository .label.list{list-style:none;padding-top:15px} -.repository .label.list .item{padding-top:10px;padding-bottom:10px;border-bottom:1px dashed #aaa} -.repository .label.list .item a{font-size:15px;padding-top:5px;padding-right:10px;color:#666} -.repository .label.list .item a:hover{color:#000} -.repository .label.list .item a.open-issues{margin-right:30px} -.repository .label.list .item .ui.label{font-size:1em} -.repository .milestone.list{list-style:none;padding-top:15px} -.repository .milestone.list>.item{padding-top:10px;padding-bottom:10px;border-bottom:1px dashed #aaa} -.repository .milestone.list>.item>a{padding-top:5px;padding-right:10px;color:#000} -.repository .milestone.list>.item>a:hover{color:#4078c0} -.repository .milestone.list>.item .ui.progress{width:40%;padding:0;border:0;margin:0} -.repository .milestone.list>.item .ui.progress .bar{height:20px} -.repository .milestone.list>.item .meta{color:#999;padding-top:5px} -.repository .milestone.list>.item .meta .issue-stats .octicon{padding-left:5px} -.repository .milestone.list>.item .meta .overdue{color:red} -.repository .milestone.list>.item .operate{margin-top:-15px} -.repository .milestone.list>.item .operate>a{font-size:15px;padding-top:5px;padding-right:10px;color:#666} -.repository .milestone.list>.item .operate>a:hover{color:#000} -.repository .milestone.list>.item .content{padding-top:10px} -.repository.new.milestone textarea{height:200px} -.repository.new.milestone #deadline{width:150px} -.repository.compare.pull .show-form-container{text-align:left} -.repository.compare.pull .choose.branch .octicon{padding-right:10px} -.repository.compare.pull .comment.form .content:after,.repository.compare.pull .comment.form .content:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} -.repository.compare.pull .comment.form .content:before{border-right-color:#d3d3d4;border-width:9px;margin-top:-9px} -.repository.compare.pull .comment.form .content:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px} -.repository.compare.pull .comment.form .content:after{border-right-color:#fff} -.repository .filter.dropdown .menu{margin-top:1px!important} -.repository.branches .commit-divergence .bar-group{position:relative;float:left;padding-bottom:6px;width:90px} -.repository.branches .commit-divergence .bar-group:last-child{border-left:1px solid #b4b4b4} -.repository.branches .commit-divergence .count{margin:0 3px} -.repository.branches .commit-divergence .count.count-ahead{text-align:left} -.repository.branches .commit-divergence .count.count-behind{text-align:right} -.repository.branches .commit-divergence .bar{height:4px;position:absolute;background-color:#d4d4d5} -.repository.branches .commit-divergence .bar.bar-behind{right:0} -.repository.branches .commit-divergence .bar.bar-ahead{left:0} -.repository.commits .header .search input{font-weight:400;padding:5px 10px} -.repository #commits-table thead th:first-of-type{padding-left:15px} -.repository #commits-table thead .sha{width:140px} -.repository #commits-table thead .shatd{text-align:center} -.repository #commits-table td.sha .sha.label{margin:0} -.repository #commits-table td.message{text-overflow:unset} -.repository #commits-table.ui.basic.striped.table tbody tr:nth-child(2n){background-color:rgba(0,0,0,.02)!important} -.repository #commits-table td.sha .sha.label,.repository #repo-files-table .sha.label{border:1px solid #bbb} -.repository #commits-table td.sha .sha.label .detail.icon,.repository #repo-files-table .sha.label .detail.icon{background:#fafafa;margin:-6px -10px -4px 0;padding:5px 3px 5px 6px;border-left:1px solid #bbb;border-top-left-radius:0;border-bottom-left-radius:0} -.repository #commits-table td.sha .sha.label.isSigned.isWarning,.repository #repo-files-table .sha.label.isSigned.isWarning{border:1px solid #db2828;background:rgba(219,40,40,.1)} -.repository #commits-table td.sha .sha.label.isSigned.isWarning .detail.icon,.repository #repo-files-table .sha.label.isSigned.isWarning .detail.icon{border-left:1px solid rgba(219,40,40,.5)} -.repository #commits-table td.sha .sha.label.isSigned.isVerified,.repository #repo-files-table .sha.label.isSigned.isVerified{border:1px solid #21ba45;background:rgba(33,186,69,.1)} -.repository #commits-table td.sha .sha.label.isSigned.isVerified .detail.icon,.repository #repo-files-table .sha.label.isSigned.isVerified .detail.icon{border-left:1px solid #21ba45} -.repository #commits-table td.sha .sha.label.isSigned.isVerified:hover,.repository #repo-files-table .sha.label.isSigned.isVerified:hover{background:rgba(33,186,69,.3)!important} -.repository .diff-detail-box{padding:7px 0;background:#fff;line-height:30px} -.repository .diff-detail-box>div:after{clear:both;content:"";display:block} -.repository .diff-detail-box span.status{display:inline-block;width:12px;height:12px;margin-right:8px;vertical-align:middle} -.repository .diff-detail-box span.status.modify{background-color:#f0db88} -.repository .diff-detail-box span.status.add{background-color:#b4e2b4} -.repository .diff-detail-box span.status.del{background-color:#e9aeae} -.repository .diff-detail-box span.status.rename{background-color:#dad8ff} -.repository .diff-detail-box .detail-files{background:#fff;margin:0} -.repository .diff-box .header{display:flex;align-items:center} -.repository .diff-box .header .count{margin-right:12px;font-size:13px;flex:0 0 auto} -.repository .diff-box .header .count .bar{background-color:#bd2c00;height:12px;width:40px;display:inline-block;margin:2px 4px 0 4px;vertical-align:text-top} -.repository .diff-box .header .count .bar .add{background-color:#55a532;height:12px} -.repository .diff-box .header .file{flex:1;color:#888;word-break:break-all} -.repository .diff-box .header .button{margin:-5px 0 -5px 12px;padding:8px 10px;flex:0 0 auto} -.repository .diff-file-box .header{background-color:#f7f7f7} -.repository .diff-file-box .file-body.file-code .lines-num{text-align:right;color:#a6a6a6;background:#fafafa;width:1%;min-width:50px;-webkit-user-select:none;-ms-user-select:none;user-select:none;vertical-align:top} -.repository .diff-file-box .file-body.file-code .lines-num span.fold{display:block;text-align:center} -.repository .diff-file-box .file-body.file-code .lines-num-old{border-right:1px solid #ddd} -.repository .diff-file-box .code-diff{font-size:12px} -.repository .diff-file-box .code-diff td{padding:0 0 0 10px!important;border-top:0} -.repository .diff-file-box .code-diff .lines-num{border-color:#d4d4d5;border-right-width:1px;border-right-style:solid;padding:0 5px!important} -.repository .diff-file-box .code-diff tbody tr td.halfwidth{width:49%} -.repository .diff-file-box .code-diff tbody tr td.center{text-align:center} -.repository .diff-file-box .code-diff tbody tr .removed-code{background-color:#f99} -.repository .diff-file-box .code-diff tbody tr .added-code{background-color:#9f9} -.repository .diff-file-box .code-diff tbody tr [data-line-num]::before{content:attr(data-line-num);text-align:right} -.repository .diff-file-box .code-diff tbody tr .lines-type-marker{width:10px;min-width:10px;-webkit-user-select:none;-ms-user-select:none;user-select:none} -.repository .diff-file-box .code-diff tbody tr [data-type-marker]::before{content:attr(data-type-marker);text-align:right;display:inline-block} -.repository .diff-file-box .code-diff-unified tbody tr.del-code td{background-color:#ffe0e0!important;border-color:#f1c0c0!important} -.repository .diff-file-box .code-diff-unified tbody tr.add-code td{background-color:#d6fcd6!important;border-color:#c1e9c1!important} -.repository .diff-file-box .code-diff-split table,.repository .diff-file-box .code-diff-split tbody{width:100%} -.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(2),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(3),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(4),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(5),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(6){background-color:#fafafa} -.repository .diff-file-box .code-diff-split tbody tr td.del-code,.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(1),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(2),.repository .diff-file-box .code-diff-split tbody tr.del-code td:nth-child(3){background-color:#ffe0e0!important;border-color:#f1c0c0!important} -.repository .diff-file-box .code-diff-split tbody tr td.add-code,.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(4),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(5),.repository .diff-file-box .code-diff-split tbody tr.add-code td:nth-child(6){background-color:#d6fcd6!important;border-color:#c1e9c1!important} -.repository .diff-file-box .code-diff-split tbody tr td:nth-child(4){border-left-width:1px;border-left-style:solid} -.repository .diff-file-box.file-content{clear:right} -.repository .diff-file-box.file-content img{max-width:100%;padding:5px 5px 0 5px} -.repository .diff-file-box.file-content img.emoji{padding:0} -.repository .diff-stats{clear:both;margin-bottom:5px;max-height:400px;overflow:auto;padding-left:0} -.repository .diff-stats li{list-style:none;padding-bottom:4px;margin-bottom:4px;border-bottom:1px dashed #ddd;padding-left:6px} -.repository .diff-stats .diff-counter{margin-right:15px} -.repository .diff-stats .diff-counter .del{color:red} -.repository .diff-stats .diff-counter .add{color:green} -.repository .repo-search-result{padding-top:10px;padding-bottom:10px} -.repository .repo-search-result .lines-num a{color:inherit} -.repository.quickstart .guide .item{padding:1em} -.repository.quickstart .guide .item small{font-weight:400} -.repository.quickstart .guide .clone.button:first-child{border-radius:.28571429rem 0 0 .28571429rem} -.repository.quickstart .guide .ui.action.small.input{width:100%} -.repository.quickstart .guide #repo-clone-url{border-radius:0;padding:5px 10px;font-size:1.2em} -.repository.release #release-list{border-top:1px solid #ddd;margin-top:20px;padding-top:15px} -.repository.release #release-list>li{list-style:none} -.repository.release #release-list>li .detail,.repository.release #release-list>li .meta{padding-top:30px;padding-bottom:40px} -.repository.release #release-list>li .meta{text-align:right;position:relative} -.repository.release #release-list>li .meta .tag:not(.icon){display:block;margin-top:15px} -.repository.release #release-list>li .meta .commit{display:block;margin-top:10px} -.repository.release #release-list>li .detail{border-left:1px solid #ddd} -.repository.release #release-list>li .detail .author img{margin-bottom:-3px} -.repository.release #release-list>li .detail .download{margin-top:20px} -.repository.release #release-list>li .detail .download>a .octicon{margin-left:5px;margin-right:5px} -.repository.release #release-list>li .detail .download .list{padding-left:0;border-top:1px solid #eee} -.repository.release #release-list>li .detail .download .list li{list-style:none;display:block;padding-top:8px;padding-bottom:8px;border-bottom:1px solid #eee} -.repository.release #release-list>li .detail .dot{width:9px;height:9px;background-color:#ccc;z-index:999;position:absolute;display:block;left:-5px;top:40px;border-radius:6px;border:1px solid #fff} -.repository.new.release .target{min-width:500px} -.repository.new.release .target #tag-name{margin-top:-4px} -.repository.new.release .target .at{margin-left:-5px;margin-right:5px} -.repository.new.release .target .dropdown.icon{margin:0;padding-top:3px} -.repository.new.release .target .selection.dropdown{padding-top:10px;padding-bottom:10px} -.repository.new.release .prerelease.field{margin-bottom:0} -@media only screen and (max-width:438px){.repository.new.release .field button,.repository.new.release .field input{width:100%} -} -@media only screen and (max-width:768px){.repository.new.release .field button{margin-bottom:1em} -} -.repository.forks .list{margin-top:0} -.repository.forks .list .item{padding-top:10px;padding-bottom:10px;border-bottom:1px solid #ddd} -.repository.forks .list .item .ui.avatar{float:left;margin-right:5px} -.repository.forks .list .item .link{padding-top:5px} -.repository.wiki.start .ui.segment{padding-top:70px;padding-bottom:100px} -.repository.wiki.start .ui.segment .mega-octicon{font-size:48px} -.repository.wiki.new .CodeMirror .CodeMirror-code{font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} -.repository.wiki.new .CodeMirror .CodeMirror-code .cm-comment{background:inherit} -.repository.wiki.new .editor-preview{background-color:#fff} -.repository.wiki.new .ui.attached.tabular.menu.previewtabs{margin-bottom:15px} -.repository.wiki.new .ui.attached.tabular.menu.previewtabs+.field .editor-toolbar:not(.fullscreen) a.fa-eye{display:none} -.repository.wiki.view .choose.page{margin-top:-5px} -.repository.wiki.view .ui.sub.header{text-transform:none} -.repository.wiki.view>.markdown{padding:15px 30px} -.repository.wiki.view>.markdown h1:first-of-type,.repository.wiki.view>.markdown h2:first-of-type,.repository.wiki.view>.markdown h3:first-of-type,.repository.wiki.view>.markdown h4:first-of-type,.repository.wiki.view>.markdown h5:first-of-type,.repository.wiki.view>.markdown h6:first-of-type{margin-top:0} -@media only screen and (max-width:767px){.repository.wiki .dividing.header .stackable.grid .button{margin-top:2px;margin-bottom:2px} -} -.repository.settings.collaboration .collaborator.list{padding:0} -.repository.settings.collaboration .collaborator.list>.item{margin:0;line-height:2em} -.repository.settings.collaboration .collaborator.list>.item:not(:last-child){border-bottom:1px solid #ddd} -.repository.settings.collaboration #repo-collab-form #search-user-box .results{left:7px} -.repository.settings.collaboration #repo-collab-form .ui.button{margin-left:5px;margin-top:-3px} -.repository.settings.collaboration #repo-collab-team-form #search-team-box .results{left:7px} -.repository.settings.collaboration #repo-collab-team-form .ui.button{margin-left:5px;margin-top:-3px} -.repository.settings.branches .protected-branches .selection.dropdown{width:300px} -.repository.settings.branches .protected-branches .item{border:1px solid #eaeaea;padding:10px 15px} -.repository.settings.branches .protected-branches .item:not(:last-child){border-bottom:0} -.repository.settings.branches .branch-protection .help{margin-left:26px;padding-top:0} -.repository.settings.branches .branch-protection .fields{margin-left:20px;display:block} -.repository.settings.branches .branch-protection .whitelist{margin-left:26px} -.repository.settings.branches .branch-protection .whitelist .dropdown img{display:inline-block} -.repository.settings.webhook .events .column{padding-bottom:0} -.repository.settings.webhook .events .help{font-size:13px;margin-left:26px;padding-top:0} -.repository .ui.attached.isSigned.isVerified:not(.positive){border-left:1px solid #a3c293;border-right:1px solid #a3c293} -.repository .ui.attached.isSigned.isVerified.top:not(.positive){border-top:1px solid #a3c293} -.repository .ui.attached.isSigned.isVerified:not(.positive):last-child{border-bottom:1px solid #a3c293} -.repository .ui.segment.sub-menu{padding:7px;line-height:0} -.repository .ui.segment.sub-menu .list{width:100%;display:flex} -.repository .ui.segment.sub-menu .list .item{width:100%;border-radius:3px} -.repository .ui.segment.sub-menu .list .item a{color:#000} -.repository .ui.segment.sub-menu .list .item a:hover{color:#666} -.repository .ui.segment.sub-menu .list .item.active{background:rgba(0,0,0,.05)} -.repository .segment.reactions.dropdown .menu,.repository .select-reaction.dropdown .menu{right:0!important;left:auto!important} -.repository .segment.reactions.dropdown .menu>.header,.repository .select-reaction.dropdown .menu>.header{margin:.75rem 0 .5rem} -.repository .segment.reactions.dropdown .menu>.item,.repository .select-reaction.dropdown .menu>.item{float:left;padding:.5rem .5rem!important} -.repository .segment.reactions.dropdown .menu>.item img.emoji,.repository .select-reaction.dropdown .menu>.item img.emoji{margin-right:0} -.repository .segment.reactions{padding:0;display:flex} -.repository .segment.reactions .ui.label{padding:.4em;padding-right:1em;padding-left:1em;border:0;border-right:1px solid;border-radius:0;margin:0;font-size:14px;border-color:inherit!important} -.repository .segment.reactions .ui.label.disabled{cursor:default;opacity:.5} -.repository .segment.reactions .ui.label>img{height:1.5em!important} -.repository .segment.reactions .ui.label.basic.blue{background-color:#f1f8ff!important;border-color:inherit!important} -.repository .segment.reactions .select-reaction{float:left;padding:.5em;padding-left:1em} -.repository .segment.reactions .select-reaction:not(.active) a{display:none} -.repository .segment.reactions:hover .select-reaction a{display:block} -.repository .ui.fluid.action.input .ui.search.action.input{flex:auto} -.user-cards .list{padding:0;display:flex;flex-wrap:wrap} -.user-cards .list .item{list-style:none;width:32%;margin:10px 10px 10px 0;padding-bottom:14px;float:left} -.user-cards .list .item .avatar{width:48px;height:48px;float:left;display:block;margin-right:10px} -.user-cards .list .item .name{margin-top:0;margin-bottom:0;font-weight:400} -.user-cards .list .item .meta{margin-top:5px} -#search-repo-box .results .result .image,#search-user-box .results .result .image{float:left;margin-right:8px;width:2em;height:2em} -#search-repo-box .results .result .content,#search-user-box .results .result .content{margin:6px 0} -#search-team-box .results .result .content{margin:6px 0} -#issue-filters.hide{display:none} -#issue-actions{margin-top:-1rem!important} -#issue-actions.hide{display:none} -.ui.checkbox.issue-checkbox{vertical-align:middle} -.issue.list{list-style:none} -.issue.list>.item{padding-top:15px;padding-bottom:10px;border-bottom:1px dashed #aaa} -.issue.list>.item .title{color:#444;font-size:15px;font-weight:700;margin:0 6px} -.issue.list>.item .title:hover{color:#000} -.issue.list>.item .comment{padding-right:10px;color:#666} -.issue.list>.item .desc{padding-top:5px;color:#999} -.issue.list>.item .desc .checklist{padding-left:5px} -.issue.list>.item .desc .checklist .progress-bar{margin-left:2px;width:80px;height:6px;display:inline-block;background-color:#eee;overflow:hidden;border-radius:3px;vertical-align:2px!important} -.issue.list>.item .desc .checklist .progress-bar .progress{background-color:#ccc;display:block;height:100%} -.issue.list>.item .desc .due-date{padding-left:5px} -.issue.list>.item .desc a.milestone{margin-left:5px;color:#999!important} -.issue.list>.item .desc a.milestone:hover{color:#000!important} -.issue.list>.item .desc a.ref{margin-left:8px;color:#999!important} -.issue.list>.item .desc a.ref:hover{color:#000!important} -.issue.list>.item .desc a.ref span{margin-right:-4px} -.issue.list>.item .desc .assignee{margin-top:-5px;margin-right:5px} -.issue.list>.item .desc .overdue{color:red} -.page.buttons{padding-top:15px} -.ui.form .dropzone{width:100%;margin-bottom:10px;border:2px dashed #0087f5;box-shadow:none!important} -.ui.form .dropzone .dz-error-message{top:140px} -.settings .content{margin-top:2px} -.settings .content .segment,.settings .content>.header{box-shadow:0 1px 2px 0 rgba(34,36,38,.15)} -.settings .list>.item .green:not(.ui.button){color:#21ba45} -.settings .list>.item:not(:first-child){border-top:1px solid #eaeaea;padding:1rem;margin:15px -1rem -1rem -1rem} -.settings .list>.item>.mega-octicon{display:table-cell} -.settings .list>.item>.mega-octicon+.content{display:table-cell;padding:0 0 0 .5em;vertical-align:top} -.settings .list>.item .info{margin-top:10px} -.settings .list>.item .info .tab.segment{border:0;padding:10px 0 0} -.settings .list.key .meta{padding-top:5px;color:#666} -.settings .list.email>.item:not(:first-child){min-height:60px} -.settings .list.collaborator>.item{padding:0} -.ui.vertical.menu .header.item{font-size:1.1em;background:#f0f0f0} -.edit-label.modal .form .column,.new-label.segment .form .column{padding-right:0} -.edit-label.modal .form .buttons,.new-label.segment .form .buttons{margin-left:auto;padding-top:15px} -.edit-label.modal .form .color.picker.column,.new-label.segment .form .color.picker.column{width:auto} -.edit-label.modal .form .color.picker.column .color-picker,.new-label.segment .form .color.picker.column .color-picker{height:35px;width:auto;padding-left:30px} -.edit-label.modal .form .minicolors-swatch.minicolors-sprite,.new-label.segment .form .minicolors-swatch.minicolors-sprite{top:10px;left:10px;width:15px;height:15px} -.edit-label.modal .form .precolors,.new-label.segment .form .precolors{padding-left:0;padding-right:0;margin:3px 10px auto 10px;width:120px} -.edit-label.modal .form .precolors .color,.new-label.segment .form .precolors .color{float:left;width:15px;height:15px} -#avatar-arrow:after,#avatar-arrow:before{right:100%;top:20px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none} -#avatar-arrow:before{border-right-color:#d3d3d4;border-width:9px;margin-top:-9px} -#avatar-arrow:after{border-right-color:#f7f7f7;border-width:8px;margin-top:-8px} -#delete-repo-modal .ui.message,#transfer-repo-modal .ui.message{width:100%!important} -.tab-size-1{-moz-tab-size:1!important;-o-tab-size:1!important;tab-size:1!important} -.tab-size-2{-moz-tab-size:2!important;-o-tab-size:2!important;tab-size:2!important} -.tab-size-3{-moz-tab-size:3!important;-o-tab-size:3!important;tab-size:3!important} -.tab-size-4{-moz-tab-size:4!important;-o-tab-size:4!important;tab-size:4!important} -.tab-size-5{-moz-tab-size:5!important;-o-tab-size:5!important;tab-size:5!important} -.tab-size-6{-moz-tab-size:6!important;-o-tab-size:6!important;tab-size:6!important} -.tab-size-7{-moz-tab-size:7!important;-o-tab-size:7!important;tab-size:7!important} -.tab-size-8{-moz-tab-size:8!important;-o-tab-size:8!important;tab-size:8!important} -.tab-size-9{-moz-tab-size:9!important;-o-tab-size:9!important;tab-size:9!important} -.tab-size-10{-moz-tab-size:10!important;-o-tab-size:10!important;tab-size:10!important} -.tab-size-11{-moz-tab-size:11!important;-o-tab-size:11!important;tab-size:11!important} -.tab-size-12{-moz-tab-size:12!important;-o-tab-size:12!important;tab-size:12!important} -.tab-size-13{-moz-tab-size:13!important;-o-tab-size:13!important;tab-size:13!important} -.tab-size-14{-moz-tab-size:14!important;-o-tab-size:14!important;tab-size:14!important} -.tab-size-15{-moz-tab-size:15!important;-o-tab-size:15!important;tab-size:15!important} -.tab-size-16{-moz-tab-size:16!important;-o-tab-size:16!important;tab-size:16!important} -.stats-table{display:table;width:100%} -.stats-table .table-cell{display:table-cell} -.stats-table .table-cell.tiny{height:.5em} -tbody.commit-list{vertical-align:baseline} -.commit-list .message-wrapper{overflow:hidden;text-overflow:ellipsis;max-width:calc(100% - 50px);display:inline-block;vertical-align:middle} -.commit-list .commit-summary a{text-decoration:underline;-webkit-text-decoration-style:dashed;text-decoration-style:dashed} -.commit-list .commit-summary a:hover{-webkit-text-decoration-style:solid;text-decoration-style:solid} -.commit-list .commit-summary a.default-link{text-decoration:none} -.commit-list .commit-summary a.default-link:hover{text-decoration:underline;-webkit-text-decoration-style:solid;text-decoration-style:solid} -.commit-list .commit-status-link{display:inline-block;vertical-align:middle} -.commit-body{white-space:pre-wrap} -.git-notes.top{text-align:left} -.git-notes .commit-body{margin:0} -@media only screen and (max-width:767px){.ui.stackable.menu.mobile--margin-between-items>.item{margin-top:5px;margin-bottom:5px} -.ui.stackable.menu.mobile--no-negative-margins{margin-left:0;margin-right:0} -} -#topic_edit{margin-top:5px} -#repo-topics{margin-top:5px} -.repo-topic{cursor:pointer} -#new-dependency-drop-list.ui.selection.dropdown{min-width:0;width:100%;border-radius:4px 0 0 4px;border-right:0;white-space:nowrap} -#new-dependency-drop-list .text{width:100%;overflow:hidden} -#manage_topic{font-size:12px} -.label+#manage_topic{margin-left:5px} -.repo-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap} -.repo-header .repo-buttons{display:flex;align-items:center} -.repo-buttons .disabled-repo-button .label{opacity:.5} -.repo-buttons .disabled-repo-button a.button{opacity:.5;cursor:not-allowed} -.repo-buttons .disabled-repo-button a.button:hover{background:0 0!important;color:rgba(0,0,0,.6)!important;box-shadow:0 0 0 1px rgba(34,36,38,.15) inset!important} -.repo-buttons .ui.labeled.button>.label{border-left:0!important;margin:0!important} -.tag-code,.tag-code td{background-color:#f0f0f0!important;border-color:#d3cfcf!important;padding-top:8px;padding-bottom:8px} -.board{display:flex;flex-direction:row;flex-wrap:nowrap;overflow-x:auto;margin:0 .5em} -.board-column{background-color:#eff1f3!important;border:1px solid rgba(34,36,38,.15)!important;margin:0 .5em!important;padding:5px!important;width:320px;height:60vh;overflow-y:scroll;flex:0 0 auto} -.board-column>.cards{margin:0!important} -.board-column>.board-label{margin-left:5px!important;margin-top:3px!important} -.board-column>.divider{margin:5px 0} -.board-column:first-child{margin-left:auto!important} -.board-column:last-child{margin-right:auto!important} -.board-card{margin:3px!important} -.board-card .header{font-size:1.1em!important} -.board-card .content{padding:5px 8px!important} -.board-card .extra.content{padding:5px 8px!important} -.board-label{padding:.4em .6em!important;margin-right:.4em!important;margin-bottom:.4em!important} -td.blob-excerpt{background-color:#fafafa} -.issue-keyword{border-bottom:1px dotted #959da5;display:inline-block} -.file-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px!important} -.file-info{display:flex;align-items:center} -.file-info-entry+.file-info-entry{border-left:1px solid currentColor;margin-left:8px;padding-left:8px} -.title_wip_desc{margin-top:1em} -.CodeMirror{font:14px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} -.CodeMirror.cm-s-default{border-radius:3px;padding:0!important} -.CodeMirror .cm-comment{background:inherit!important} -.repository.file.editor .tab[data-tab=write]{padding:0!important} -.repository.file.editor .tab[data-tab=write] .editor-toolbar{border:0!important} -.repository.file.editor .tab[data-tab=write] .CodeMirror{border-left:0;border-right:0;border-bottom:0} -.organization{padding-top:15px} -.organization .head .ui.header .text{vertical-align:middle;font-size:1.6rem;margin-left:15px} -.organization .head .ui.header .ui.right{margin-top:5px} -.organization.new.org form{margin:auto} -.organization.new.org form .ui.message{text-align:center} -@media only screen and (min-width:768px){.organization.new.org form{width:800px!important} -.organization.new.org form .header{padding-left:280px!important} -.organization.new.org form .inline.field>label{text-align:right;width:250px!important;word-wrap:break-word} -.organization.new.org form .help{margin-left:265px!important} -.organization.new.org form .optional .title{margin-left:250px!important} -.organization.new.org form input,.organization.new.org form textarea{width:50%!important} -} -@media only screen and (max-width:767px){.organization.new.org form .optional .title{margin-left:15px} -.organization.new.org form .inline.field>label{display:block} -} -.organization.new.org form .header{padding-left:0!important;text-align:center} -.organization.options input{min-width:300px} -.organization.profile #org-avatar{width:100px;height:100px;margin-right:15px} -.organization.profile #org-info .ui.header{font-size:36px;margin-bottom:0} -.organization.profile #org-info .desc{font-size:16px;margin-bottom:10px} -.organization.profile #org-info .meta .item{display:inline-block;margin-right:10px} -.organization.profile #org-info .meta .item .icon{margin-right:5px} -.organization.profile .ui.top.header .ui.right{margin-top:0} -.organization.profile .teams .item{padding:10px 15px} -.organization.profile .members .ui.avatar,.organization.teams .members .ui.avatar{width:48px;height:48px;margin-right:5px} -.organization.invite #invite-box{margin:50px auto auto;width:500px!important} -.organization.invite #invite-box #search-user-box input{margin-left:0;width:300px} -.organization.invite #invite-box .ui.button{margin-left:5px;margin-top:-3px} -.organization.members .list .item{margin-left:0;margin-right:0;border-bottom:1px solid #eee} -.organization.members .list .item .ui.avatar{width:48px;height:48px} -.organization.members .list .item .meta{line-height:24px} -.organization.teams .detail .item{padding:10px 15px} -.organization.teams .detail .item:not(:last-child){border-bottom:1px solid #eee} -.organization.teams .members .item,.organization.teams .repositories .item{padding:10px 20px;line-height:32px} -.organization.teams .members .item:not(:last-child),.organization.teams .repositories .item:not(:last-child){border-bottom:1px solid #ddd} -.organization.teams .members .item .button,.organization.teams .repositories .item .button{padding:9px 10px} -.organization.teams #add-member-form input,.organization.teams #add-repo-form input,.organization.teams #repo-multiple-form input{margin-left:0} -.organization.teams #add-member-form .ui.button,.organization.teams #add-repo-form .ui.button,.organization.teams #repo-multiple-form .ui.button{margin-left:5px;margin-top:-3px} -.organization.teams #repo-top-segment{height:60px} -.user:not(.icon){padding-top:15px} -.user.profile .ui.card .username{display:block} -.user.profile .ui.card .extra.content{padding:0} -.user.profile .ui.card .extra.content ul{margin:0;padding:0} -.user.profile .ui.card .extra.content ul li{padding:10px;list-style:none} -.user.profile .ui.card .extra.content ul li:not(:last-child){border-bottom:1px solid #eaeaea} -.user.profile .ui.card .extra.content ul li .fa,.user.profile .ui.card .extra.content ul li .octicon{margin-left:1px;margin-right:5px} -.user.profile .ui.card .extra.content ul li.follow .ui.button{width:100%} -@media only screen and (max-width:768px){.user.profile .ui.card #profile-avatar{height:250px;overflow:hidden} -.user.profile .ui.card #profile-avatar img{max-height:768px;max-width:768px} -} -@media only screen and (max-width:768px){.user.profile .ui.card{width:100%} -} -.user.profile .ui.repository.list{margin-top:25px} -.user.profile #loading-heatmap{margin-bottom:1em} -.user.followers .header.name{font-size:20px;line-height:24px;vertical-align:middle} -.user.followers .follow .ui.button{padding:8px 15px} -.user.notification .octicon{float:left;font-size:2em} -.user.notification .octicon.green{color:#21ba45} -.user.notification .octicon.red{color:#d01919} -.user.notification .octicon.purple{color:#a333c8} -.user.notification .octicon.blue{color:#2185d0} -.user.notification .content{float:left;margin-left:7px} -.user.notification table form{display:inline-block} -.user.notification table button{padding:3px 3px 3px 5px} -.user.notification table tr{cursor:pointer} -.user.link-account:not(.icon){padding-top:15px;padding-bottom:5px} -.user.settings .iconFloat{float:left} -.user-orgs{display:flex;flex-flow:row wrap;padding:0;margin:-3px!important} -.user-orgs li{display:flex;border-bottom:0!important;padding:3px!important;width:20%;max-width:60px} -.dashboard{padding-top:15px} -.dashboard.feeds .context.user.menu,.dashboard.issues .context.user.menu{z-index:101;min-width:200px} -.dashboard.feeds .context.user.menu .ui.header,.dashboard.issues .context.user.menu .ui.header{font-size:1rem;text-transform:none} -.dashboard.feeds .filter.menu,.dashboard.issues .filter.menu{width:initial} -.dashboard.feeds .filter.menu .item,.dashboard.issues .filter.menu .item{text-align:left} -.dashboard.feeds .filter.menu .item .text,.dashboard.issues .filter.menu .item .text{height:16px;vertical-align:middle} -.dashboard.feeds .filter.menu .item .text.truncate,.dashboard.issues .filter.menu .item .text.truncate{width:85%} -.dashboard.feeds .filter.menu .item .floating.label,.dashboard.issues .filter.menu .item .floating.label{top:7px;left:90%;width:15%} -@media only screen and (max-width:768px){.dashboard.feeds .filter.menu .item .floating.label,.dashboard.issues .filter.menu .item .floating.label{top:10px;left:auto;width:auto;right:13px} -} -.dashboard.feeds .filter.menu .jump.item,.dashboard.issues .filter.menu .jump.item{margin:1px;padding-right:0} -.dashboard.feeds .filter.menu .menu,.dashboard.issues .filter.menu .menu{max-height:300px;overflow-x:auto;right:0!important;left:auto!important} -@media only screen and (max-width:768px){.dashboard.feeds .filter.menu,.dashboard.issues .filter.menu{width:100%} -} -.dashboard.feeds .right.stackable.menu>.item.active,.dashboard.issues .right.stackable.menu>.item.active{color:#d9453d} -.dashboard .dashboard-repos{margin:0 1px} -.dashboard .dashboard-navbar{width:100vw;padding:0 .5rem} -.feeds .news>.ui.grid{margin-left:auto;margin-right:auto} -.feeds .news .ui.avatar{margin-top:13px} -.feeds .news .time-since{font-size:13px} -.feeds .news .issue.title{width:80%} -.feeds .news .push.news .content ul{font-size:13px;list-style:none;padding-left:10px} -.feeds .news .push.news .content ul img{margin-bottom:-2px} -.feeds .news .push.news .content ul .text.truncate{width:80%;margin-bottom:-5px} -.feeds .news .commit-id{font-family:'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace} -.feeds .news code{padding:1px;font-size:85%;background-color:rgba(0,0,0,.04);border-radius:3px;word-break:break-all} -.feeds .list .header .ui.label{margin-top:-4px;padding:4px 5px;font-weight:400} -.feeds .list .header .plus.icon{margin-top:5px} -.feeds .list ul{list-style:none;margin:0;padding-left:0} -.feeds .list ul li:not(:last-child){border-bottom:1px solid #ebebeb} -.feeds .list ul li.private{background-color:#fcf8e9} -.feeds .list ul li a{padding:6px 1.2em;display:block} -.feeds .list ul li a .octicon{color:#888} -.feeds .list ul li a .octicon.rear{font-size:15px} -.feeds .list ul li a .star-num{font-size:12px} -.feeds .list .repo-owner-name-list .item-name{max-width:70%;margin-bottom:-4px} -.feeds .list #collaborative-repo-list .owner-and-repo{max-width:80%;margin-bottom:-5px} -.feeds .list #collaborative-repo-list .owner-name{max-width:120px;margin-bottom:-5px} -.admin{padding-top:15px} -.admin .table.segment{padding:0;font-size:13px} -.admin .table.segment:not(.striped){padding-top:5px} -.admin .table.segment:not(.striped) thead th:last-child{padding-right:5px!important} -.admin .table.segment th{padding-top:5px;padding-bottom:5px} -.admin .table.segment:not(.select) td:first-of-type,.admin .table.segment:not(.select) th:first-of-type{padding-left:15px!important} -.admin .ui.header,.admin .ui.segment{box-shadow:0 1px 2px 0 rgba(34,36,38,.15)} -.admin.user .email{max-width:200px} -.admin dl.admin-dl-horizontal{padding:20px;margin:0} -.admin dl.admin-dl-horizontal dd{margin-left:275px} -.admin dl.admin-dl-horizontal dt{font-weight:bolder;float:left;width:285px;clear:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} -.admin.config #test-mail-btn{margin-left:5px} -.admin code,.admin pre{white-space:pre-wrap;word-wrap:break-word} -.explore{padding-top:15px} -.explore .navbar{justify-content:center;padding-top:15px!important;margin-top:-15px!important;margin-bottom:15px!important;background-color:#fafafa!important;border-width:1px!important} -.explore .navbar .octicon{width:16px;text-align:center;margin-right:5px} -.ui.repository.list .item{padding-bottom:25px} -.ui.repository.list .item:not(:first-child){border-top:1px solid #eee;padding-top:25px} -.ui.repository.list .item .ui.header{font-size:1.5rem;padding-bottom:10px} -.ui.repository.list .item .ui.header .name{word-break:break-all} -.ui.repository.list .item .ui.header .metas{color:#888;font-size:14px;font-weight:400} -.ui.repository.list .item .ui.header .metas span:not(:last-child){margin-right:5px} -.ui.repository.list .item .time{font-size:12px;color:grey} -.ui.repository.list .item .ui.tags{margin-bottom:1em} -.ui.repository.list .item .ui.avatar.image{width:24px;height:24px} -.ui.repository.branches .info{font-size:12px;color:grey;display:flex;white-space:pre} -.ui.repository.branches .info .commit-message{max-width:72em;overflow:hidden;text-overflow:ellipsis} -.ui.repository.branches .overflow-visible{overflow:visible} -.ui.user.list .item{padding-bottom:25px} -.ui.user.list .item:not(:first-child){border-top:1px solid #eee;padding-top:25px} -.ui.user.list .item .ui.avatar.image{width:40px;height:40px} -.ui.user.list .item .description{margin-top:5px} -.ui.user.list .item .description .octicon:not(:first-child){margin-left:5px} -.ui.user.list .item .description a{color:#333} -.ui.user.list .item .description a:hover{text-decoration:underline} -.ui.button.add-code-comment{font-size:14px;height:16px;line-height:16px!important;padding:0;position:relative;width:16px;z-index:5;float:left;margin:2px -10px 2px -20px;opacity:0;transition:transform .1s ease-in-out;transform:scale(1,1)} -.ui.button.add-code-comment:hover{transform:scale(1.2,1.2)} -.focus-lines-new .ui.button.add-code-comment.add-code-comment-right,.focus-lines-old .ui.button.add-code-comment.add-code-comment-left{opacity:1} -.comment-code-cloud{padding:4px;position:relative;border:1px solid #f1f1f1;margin:13px 10px 5px auto} -.comment-code-cloud:before{content:" ";width:0;height:0;border-left:13px solid transparent;border-right:13px solid transparent;border-bottom:13px solid #f1f1f1;left:20px;position:absolute;top:-13px} -.comment-code-cloud .attached.tab{border:0;padding:0;margin:0} -.comment-code-cloud .attached.tab.markdown{padding:1em;min-height:168px} -.comment-code-cloud .attached.header{padding:.1rem 1rem} -.comment-code-cloud .right.menu.options .item{padding:.85714286em .442857em;cursor:pointer} -.comment-code-cloud .ui.form textarea{border:0} -.comment-code-cloud .ui.attached.tabular.menu{background:#f7f7f7;border:1px solid #d4d4d5;padding-top:5px;padding-left:5px;margin-top:0} -.comment-code-cloud .footer{border-top:1px solid #f1f1f1;margin-top:10px} -.comment-code-cloud .footer .markdown-info{display:inline-block;margin:5px 0;font-size:12px;color:rgba(0,0,0,.6)} -.comment-code-cloud .footer .ui.right.floated{padding-top:6px} -.comment-code-cloud .footer:after{clear:both;content:"";display:block} -.comment-code-cloud button.comment-form-reply{margin:.5em .5em .5em 4.5em} -.comment-code-cloud form.comment-form-reply{margin:0 0 0 4em} -.file-comment{font:12px 'SF Mono',Consolas,Menlo,'Liberation Mono',Monaco,'Lucida Console',monospace;color:rgba(0,0,0,.87)} -.ui.fold-code{margin-right:1em;padding-left:5px;cursor:pointer;width:22px;font-size:12px} -.ui.fold-code:hover{color:#428bca} -.ui.blob-excerpt{display:block;line-height:20px;font-size:16px;cursor:pointer} -.ui.blob-excerpt:hover{color:#428bca} \ No newline at end of file diff --git a/public/js/index.js b/public/js/index.js deleted file mode 100644 index 50ad7101ea6a0..0000000000000 --- a/public/js/index.js +++ /dev/null @@ -1,3375 +0,0 @@ -/* globals wipPrefixes, issuesTribute, emojiTribute */ -/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap initKanbanBoard*/ -/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ -'use strict'; - -function htmlEncode(text) { - return jQuery('
    ').text(text).html() -} - -let csrf; -let suburl; -let previewFileModes; -let simpleMDEditor; -let codeMirrorEditor; - -// Disable Dropzone auto-discover because it's manually initialized -if (typeof(Dropzone) !== "undefined") { - Dropzone.autoDiscover = false; -} - -// Polyfill for IE9+ support (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) -if (!Array.from) { - Array.from = (function () { - const toStr = Object.prototype.toString; - const isCallable = function (fn) { - return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; - }; - const toInteger = function (value) { - const number = Number(value); - if (isNaN(number)) { return 0; } - if (number === 0 || !isFinite(number)) { return number; } - return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); - }; - const maxSafeInteger = Math.pow(2, 53) - 1; - const toLength = function (value) { - const len = toInteger(value); - return Math.min(Math.max(len, 0), maxSafeInteger); - }; - - // The length property of the from method is 1. - return function from(arrayLike/*, mapFn, thisArg */) { - // 1. Let C be the this value. - const C = this; - - // 2. Let items be ToObject(arrayLike). - const items = Object(arrayLike); - - // 3. ReturnIfAbrupt(items). - if (arrayLike == null) { - throw new TypeError("Array.from requires an array-like object - not null or undefined"); - } - - // 4. If mapfn is undefined, then let mapping be false. - const mapFn = arguments.length > 1 ? arguments[1] : void undefined; - let T; - if (typeof mapFn !== 'undefined') { - // 5. else - // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. - if (!isCallable(mapFn)) { - throw new TypeError('Array.from: when provided, the second argument must be a function'); - } - - // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. - if (arguments.length > 2) { - T = arguments[2]; - } - } - - // 10. Let lenValue be Get(items, "length"). - // 11. Let len be ToLength(lenValue). - const len = toLength(items.length); - - // 13. If IsConstructor(C) is true, then - // 13. a. Let A be the result of calling the [[Construct]] internal method of C with an argument list containing the single item len. - // 14. a. Else, Let A be ArrayCreate(len). - const A = isCallable(C) ? Object(new C(len)) : new Array(len); - - // 16. Let k be 0. - let k = 0; - // 17. Repeat, while k < len… (also steps a - h) - let kValue; - while (k < len) { - kValue = items[k]; - if (mapFn) { - A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); - } else { - A[k] = kValue; - } - k += 1; - } - // 18. Let putStatus be Put(A, "length", len, true). - A.length = len; - // 20. Return A. - return A; - }; - }()); -} - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign -if (typeof Object.assign != 'function') { - // Must be writable: true, enumerable: false, configurable: true - Object.defineProperty(Object, "assign", { - value: function assign(target, _varArgs) { // .length of function is 2 - 'use strict'; - if (target == null) { // TypeError if undefined or null - throw new TypeError('Cannot convert undefined or null to object'); - } - - const to = Object(target); - - for (let index = 1; index < arguments.length; index++) { - const nextSource = arguments[index]; - - if (nextSource != null) { // Skip over if undefined or null - for (const nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - }, - writable: true, - configurable: true - }); -} - -function initCommentPreviewTab($form) { - const $tabMenu = $form.find('.tabular.menu'); - $tabMenu.find('.item').tab(); - $tabMenu.find('.item[data-tab="' + $tabMenu.data('preview') + '"]').click(function () { - const $this = $(this); - $.post($this.data('url'), { - "_csrf": csrf, - "mode": "gfm", - "context": $this.data('context'), - "text": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() - }, - function (data) { - const $previewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('preview') + '"]'); - $previewPanel.html(data); - emojify.run($previewPanel[0]); - $('pre code', $previewPanel[0]).each(function () { - hljs.highlightBlock(this); - }); - } - ); - }); - - buttonsClickOnEnter(); -} - -function initEditPreviewTab($form) { - const $tabMenu = $form.find('.tabular.menu'); - $tabMenu.find('.item').tab(); - const $previewTab = $tabMenu.find('.item[data-tab="' + $tabMenu.data('preview') + '"]'); - if ($previewTab.length) { - previewFileModes = $previewTab.data('preview-file-modes').split(','); - $previewTab.click(function () { - const $this = $(this); - $.post($this.data('url'), { - "_csrf": csrf, - "mode": "gfm", - "context": $this.data('context'), - "text": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() - }, - function (data) { - const $previewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('preview') + '"]'); - $previewPanel.html(data); - emojify.run($previewPanel[0]); - $('pre code', $previewPanel[0]).each(function () { - hljs.highlightBlock(this); - }); - } - ); - }); - } -} - -function initEditDiffTab($form) { - const $tabMenu = $form.find('.tabular.menu'); - $tabMenu.find('.item').tab(); - $tabMenu.find('.item[data-tab="' + $tabMenu.data('diff') + '"]').click(function () { - const $this = $(this); - $.post($this.data('url'), { - "_csrf": csrf, - "context": $this.data('context'), - "content": $form.find('.tab.segment[data-tab="' + $tabMenu.data('write') + '"] textarea').val() - }, - function (data) { - const $diffPreviewPanel = $form.find('.tab.segment[data-tab="' + $tabMenu.data('diff') + '"]'); - $diffPreviewPanel.html(data); - emojify.run($diffPreviewPanel[0]); - } - ); - }); -} - - -function initEditForm() { - if ($('.edit.form').length == 0) { - return; - } - - initEditPreviewTab($('.edit.form')); - initEditDiffTab($('.edit.form')); -} - -function initBranchSelector() { - const $selectBranch = $('.ui.select-branch') - const $branchMenu = $selectBranch.find('.reference-list-menu'); - $branchMenu.find('.item:not(.no-select)').click(function () { - const selectedValue = $(this).data('id'); - $($(this).data('id-selector')).val(selectedValue); - $selectBranch.find('.ui .branch-name').text(selectedValue); - }); - $selectBranch.find('.reference.column').click(function () { - $selectBranch.find('.scrolling.reference-list-menu').css('display', 'none'); - $selectBranch.find('.reference .text').removeClass('black'); - $($(this).data('target')).css('display', 'block'); - $(this).find('.text').addClass('black'); - return false; - }); -} - -function updateIssuesMeta(url, action, issueIds, elementId) { - return new Promise(function(resolve) { - $.ajax({ - type: "POST", - url: url, - data: { - "_csrf": csrf, - "action": action, - "issue_ids": issueIds, - "id": elementId - }, - success: resolve - }) - }) -} - -function initRepoStatusChecker() { - const migrating = $("#repo_migrating"); - $('#repo_migrating_failed').hide(); - if (migrating) { - const repo_name = migrating.attr('repo'); - if (typeof repo_name === 'undefined') { - return - } - $.ajax({ - type: "GET", - url: suburl +"/"+repo_name+"/status", - data: { - "_csrf": csrf, - }, - complete: function(xhr) { - if (xhr.status == 200) { - if (xhr.responseJSON) { - if (xhr.responseJSON["status"] == 0) { - location.reload(); - return - } - - setTimeout(function () { - initRepoStatusChecker() - }, 2000); - return - } - } - $('#repo_migrating_progress').hide(); - $('#repo_migrating_failed').show(); - } - }) - } -} - -function initReactionSelector(parent) { - let reactions = ''; - if (!parent) { - parent = $(document); - reactions = '.reactions > '; - } - - parent.find(reactions + 'a.label').popup({'position': 'bottom left', 'metadata': {'content': 'title', 'title': 'none'}}); - - parent.find('.select-reaction > .menu > .item, ' + reactions + 'a.label').on('click', function(e){ - const vm = this; - e.preventDefault(); - - if ($(this).hasClass('disabled')) return; - - const actionURL = $(this).hasClass('item') ? - $(this).closest('.select-reaction').data('action-url') : - $(this).data('action-url'); - const url = actionURL + '/' + ($(this).hasClass('blue') ? 'unreact' : 'react'); - $.ajax({ - type: 'POST', - url: url, - data: { - '_csrf': csrf, - 'content': $(this).data('content') - } - }).done(function(resp) { - if (resp && (resp.html || resp.empty)) { - const content = $(vm).closest('.content'); - let react = content.find('.segment.reactions'); - if (!resp.empty && react.length > 0) { - react.remove(); - } - if (!resp.empty) { - react = $('
    '); - const attachments = content.find('.segment.bottom:first'); - if (attachments.length > 0) { - react.insertBefore(attachments); - } else { - react.appendTo(content); - } - react.html(resp.html); - const hasEmoji = react.find('.has-emoji'); - for (let i = 0; i < hasEmoji.length; i++) { - emojify.run(hasEmoji.get(i)); - } - react.find('.dropdown').dropdown(); - initReactionSelector(react); - } - } - }); - }); -} - -function insertAtCursor(field, value) { - if (field.selectionStart || field.selectionStart === 0) { - const startPos = field.selectionStart; - const endPos = field.selectionEnd; - field.value = field.value.substring(0, startPos) - + value - + field.value.substring(endPos, field.value.length); - field.selectionStart = startPos + value.length; - field.selectionEnd = startPos + value.length; - } else { - field.value += value; - } -} - -function replaceAndKeepCursor(field, oldval, newval) { - if (field.selectionStart || field.selectionStart === 0) { - const startPos = field.selectionStart; - const endPos = field.selectionEnd; - field.value = field.value.replace(oldval, newval); - field.selectionStart = startPos + newval.length - oldval.length; - field.selectionEnd = endPos + newval.length - oldval.length; - } else { - field.value = field.value.replace(oldval, newval); - } -} - -function retrieveImageFromClipboardAsBlob(pasteEvent, callback){ - if (!pasteEvent.clipboardData) { - return; - } - - const items = pasteEvent.clipboardData.items; - if (typeof(items) === "undefined") { - return; - } - - for (let i = 0; i < items.length; i++) { - if (items[i].type.indexOf("image") === -1) continue; - const blob = items[i].getAsFile(); - - if (typeof(callback) === "function") { - pasteEvent.preventDefault(); - pasteEvent.stopPropagation(); - callback(blob); - } - } -} - -function uploadFile(file, callback) { - const xhr = new XMLHttpRequest(); - - xhr.onload = function() { - if (xhr.status == 200) { - callback(xhr.responseText); - } - }; - - xhr.open("post", suburl + "/attachments", true); - xhr.setRequestHeader("X-Csrf-Token", csrf); - const formData = new FormData(); - formData.append('file', file, file.name); - xhr.send(formData); -} - -function reload() { - window.location.reload(); -} - -function initImagePaste(target) { - target.each(function() { - const field = this; - field.addEventListener('paste', function(event){ - retrieveImageFromClipboardAsBlob(event, function(img) { - const name = img.name.substr(0, img.name.lastIndexOf('.')); - insertAtCursor(field, '![' + name + ']()'); - uploadFile(img, function(res) { - const data = JSON.parse(res); - replaceAndKeepCursor(field, '![' + name + ']()', '![' + name + '](' + suburl + '/attachments/' + data.uuid + ')'); - const input = $('').val(data.uuid); - $('.files').append(input); - }); - }); - }, false); - }); -} - -function initCommentForm() { - if ($('.comment.form').length == 0) { - return - } - - initBranchSelector(); - initCommentPreviewTab($('.comment.form')); - initImagePaste($('.comment.form textarea')); - - // Listsubmit - function initListSubmits(selector, outerSelector) { - const $list = $('.ui.' + outerSelector + '.list'); - const $noSelect = $list.find('.no-select'); - const $listMenu = $('.' + selector + ' .menu'); - let hasLabelUpdateAction = $listMenu.data('action') == 'update'; - const labels = {}; - - $('.' + selector).dropdown('setting', 'onHide', function(){ - hasLabelUpdateAction = $listMenu.data('action') == 'update'; // Update the var - if (hasLabelUpdateAction) { - const promises = []; - Object.keys(labels).forEach(function(elementId) { - const label = labels[elementId]; - const promise = updateIssuesMeta( - label["update-url"], - label["action"], - label["issue-id"], - elementId - ); - promises.push(promise); - }); - Promise.all(promises).then(reload); - } - }); - - $listMenu.find('.item:not(.no-select)').click(function () { - - // we don't need the action attribute when updating assignees - if (selector == 'select-assignees-modify') { - - // UI magic. We need to do this here, otherwise it would destroy the functionality of - // adding/removing labels - if ($(this).hasClass('checked')) { - $(this).removeClass('checked'); - $(this).find('.octicon').removeClass('octicon-check'); - } else { - $(this).addClass('checked'); - $(this).find('.octicon').addClass('octicon-check'); - } - - updateIssuesMeta( - $listMenu.data('update-url'), - "", - $listMenu.data('issue-id'), - $(this).data('id') - ); - $listMenu.data('action', 'update'); // Update to reload the page when we updated items - return false; - } - - if ($(this).hasClass('checked')) { - $(this).removeClass('checked'); - $(this).find('.octicon').removeClass('octicon-check'); - if (hasLabelUpdateAction) { - if (!($(this).data('id') in labels)) { - labels[$(this).data('id')] = { - "update-url": $listMenu.data('update-url'), - "action": "detach", - "issue-id": $listMenu.data('issue-id'), - }; - } else { - delete labels[$(this).data('id')]; - } - } - } else { - $(this).addClass('checked'); - $(this).find('.octicon').addClass('octicon-check'); - if (hasLabelUpdateAction) { - if (!($(this).data('id') in labels)) { - labels[$(this).data('id')] = { - "update-url": $listMenu.data('update-url'), - "action": "attach", - "issue-id": $listMenu.data('issue-id'), - }; - } else { - delete labels[$(this).data('id')]; - } - } - } - - const listIds = []; - $(this).parent().find('.item').each(function () { - if ($(this).hasClass('checked')) { - listIds.push($(this).data('id')); - $($(this).data('id-selector')).removeClass('hide'); - } else { - $($(this).data('id-selector')).addClass('hide'); - } - }); - if (listIds.length == 0) { - $noSelect.removeClass('hide'); - } else { - $noSelect.addClass('hide'); - } - $($(this).parent().data('id')).val(listIds.join(",")); - return false; - }); - $listMenu.find('.no-select.item').click(function () { - if (hasLabelUpdateAction || selector == 'select-assignees-modify') { - updateIssuesMeta( - $listMenu.data('update-url'), - "clear", - $listMenu.data('issue-id'), - "" - ).then(reload); - } - - $(this).parent().find('.item').each(function () { - $(this).removeClass('checked'); - $(this).find('.octicon').removeClass('octicon-check'); - }); - - $list.find('.item').each(function () { - $(this).addClass('hide'); - }); - $noSelect.removeClass('hide'); - $($(this).parent().data('id')).val(''); - - }); - } - - // Init labels and assignees - initListSubmits('select-label', 'labels'); - initListSubmits('select-assignees', 'assignees'); - initListSubmits('select-assignees-modify', 'assignees'); - - function selectItem(select_id, input_id) { - const $menu = $(select_id + ' .menu'); - const $list = $('.ui' + select_id + '.list'); - const hasUpdateAction = $menu.data('action') == 'update'; - - $menu.find('.item:not(.no-select)').click(function () { - $(this).parent().find('.item').each(function () { - $(this).removeClass('selected active') - }); - - $(this).addClass('selected active'); - if (hasUpdateAction) { - updateIssuesMeta( - $menu.data('update-url'), - "", - $menu.data('issue-id'), - $(this).data('id') - ).then(reload); - } - switch (input_id) { - case '#milestone_id': - case '#projects_id': - $list.find('.selected').html('
    ' + - htmlEncode($(this).text()) + ''); - break; - case '#assignee_id': - $list.find('.selected').html('' + - '' + - htmlEncode($(this).text()) + ''); - } - $('.ui' + select_id + '.list .no-select').addClass('hide'); - $(input_id).val($(this).data('id')); - }); - $menu.find('.no-select.item').click(function () { - $(this).parent().find('.item:not(.no-select)').each(function () { - $(this).removeClass('selected active') - }); - - if (hasUpdateAction) { - updateIssuesMeta( - $menu.data('update-url'), - "", - $menu.data('issue-id'), - $(this).data('id') - ).then(reload); - } - - $list.find('.selected').html(''); - $list.find('.no-select').removeClass('hide'); - $(input_id).val(''); - }); - } - - // Milestone and assignee - selectItem('.select-milestone', '#milestone_id'); - selectItem('.select-assignee', '#assignee_id'); - selectItem('.select-project', '#projects_id'); -} - -function initInstall() { - if ($('.install').length == 0) { - return; - } - - if ($('#db_host').val()=="") { - $('#db_host').val("127.0.0.1:3306"); - $('#db_user').val("gitea"); - $('#db_name').val("gitea"); - } - - // Database type change detection. - $("#db_type").change(function () { - const sqliteDefault = 'data/gitea.db'; - const tidbDefault = 'data/gitea_tidb'; - - const dbType = $(this).val(); - if (dbType === "SQLite3") { - $('#sql_settings').hide(); - $('#pgsql_settings').hide(); - $('#mysql_settings').hide(); - $('#sqlite_settings').show(); - - if (dbType === "SQLite3" && $('#db_path').val() == tidbDefault) { - $('#db_path').val(sqliteDefault); - } - return; - } - - const dbDefaults = { - "MySQL": "127.0.0.1:3306", - "PostgreSQL": "127.0.0.1:5432", - "MSSQL": "127.0.0.1:1433" - }; - - $('#sqlite_settings').hide(); - $('#sql_settings').show(); - - $('#pgsql_settings').toggle(dbType === "PostgreSQL"); - $('#mysql_settings').toggle(dbType === "MySQL"); - $.each(dbDefaults, function(_type, defaultHost) { - if ($('#db_host').val() == defaultHost) { - $('#db_host').val(dbDefaults[dbType]); - return false; - } - }); - }); - - // TODO: better handling of exclusive relations. - $('#offline-mode input').change(function () { - if ($(this).is(':checked')) { - $('#disable-gravatar').checkbox('check'); - $('#federated-avatar-lookup').checkbox('uncheck'); - } - }); - $('#disable-gravatar input').change(function () { - if ($(this).is(':checked')) { - $('#federated-avatar-lookup').checkbox('uncheck'); - } else { - $('#offline-mode').checkbox('uncheck'); - } - }); - $('#federated-avatar-lookup input').change(function () { - if ($(this).is(':checked')) { - $('#disable-gravatar').checkbox('uncheck'); - $('#offline-mode').checkbox('uncheck'); - } - }); - $('#enable-openid-signin input').change(function () { - if ($(this).is(':checked')) { - if (!$('#disable-registration input').is(':checked')) { - $('#enable-openid-signup').checkbox('check'); - } - } else { - $('#enable-openid-signup').checkbox('uncheck'); - } - }); - $('#disable-registration input').change(function () { - if ($(this).is(':checked')) { - $('#enable-captcha').checkbox('uncheck'); - $('#enable-openid-signup').checkbox('uncheck'); - } else { - $('#enable-openid-signup').checkbox('check'); - } - }); - $('#enable-captcha input').change(function () { - if ($(this).is(':checked')) { - $('#disable-registration').checkbox('uncheck'); - } - }); -} - -function initRepository() { - if ($('.repository').length == 0) { - return; - } - - function initFilterSearchDropdown(selector) { - const $dropdown = $(selector); - $dropdown.dropdown({ - fullTextSearch: true, - selectOnKeydown: false, - onChange: function (_text, _value, $choice) { - if ($choice.data('url')) { - window.location.href = $choice.data('url'); - } - }, - message: {noResults: $dropdown.data('no-results')} - }); - } - - // File list and commits - if ($('.repository.file.list').length > 0 || - ('.repository.commits').length > 0) { - initFilterBranchTagDropdown('.choose.reference .dropdown'); - } - - // Wiki - if ($('.repository.wiki.view').length > 0) { - initFilterSearchDropdown('.choose.page .dropdown'); - } - - // Options - if ($('.repository.settings.options').length > 0) { - $('#repo_name').keyup(function () { - const $prompt = $('#repo-name-change-prompt'); - if ($(this).val().toString().toLowerCase() != $(this).data('name').toString().toLowerCase()) { - $prompt.show(); - } else { - $prompt.hide(); - } - }); - - // Enable or select internal/external wiki system and issue tracker. - $('.enable-system').change(function () { - if (this.checked) { - $($(this).data('target')).removeClass('disabled'); - if (!$(this).data('context')) $($(this).data('context')).addClass('disabled'); - } else { - $($(this).data('target')).addClass('disabled'); - if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled'); - } - }); - $('.enable-system-radio').change(function () { - if (this.value == 'false') { - $($(this).data('target')).addClass('disabled'); - if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).removeClass('disabled'); - } else if (this.value == 'true') { - $($(this).data('target')).removeClass('disabled'); - if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled'); - } - }); - } - - // Labels - if ($('.repository.labels').length > 0) { - // Create label - const $newLabelPanel = $('.new-label.segment'); - $('.new-label.button').click(function () { - $newLabelPanel.show(); - }); - $('.new-label.segment .cancel').click(function () { - $newLabelPanel.hide(); - }); - - $('.color-picker').each(function () { - $(this).minicolors(); - }); - $('.precolors .color').click(function () { - const color_hex = $(this).data('color-hex'); - $('.color-picker').val(color_hex); - $('.minicolors-swatch-color').css("background-color", color_hex); - }); - $('.edit-label-button').click(function () { - $('#label-modal-id').val($(this).data('id')); - $('.edit-label .new-label-input').val($(this).data('title')); - $('.edit-label .new-label-desc-input').val($(this).data('description')); - $('.edit-label .color-picker').val($(this).data('color')); - $('.minicolors-swatch-color').css("background-color", $(this).data('color')); - $('.edit-label.modal').modal({ - onApprove: function () { - $('.edit-label.form').submit(); - } - }).modal('show'); - return false; - }); - } - - // Milestones - if ($('.repository.new.milestone').length > 0) { - const $datepicker = $('.milestone.datepicker'); - $datepicker.datetimepicker({ - lang: $datepicker.data('lang'), - inline: true, - timepicker: false, - startDate: $datepicker.data('start-date'), - formatDate: 'Y-m-d', - onSelectDate: function (ct) { - $('#deadline').val(ct.dateFormat('Y-m-d')); - } - }); - $('#clear-date').click(function () { - $('#deadline').val(''); - return false; - }); - } - - // Issues - if ($('.repository.view.issue').length > 0) { - // Edit issue title - const $issueTitle = $('#issue-title'); - const $editInput = $('#edit-title-input input'); - const editTitleToggle = function () { - $issueTitle.toggle(); - $('.not-in-edit').toggle(); - $('#edit-title-input').toggle(); - $('.in-edit').toggle(); - $editInput.focus(); - return false; - }; - $('#edit-title').click(editTitleToggle); - $('#cancel-edit-title').click(editTitleToggle); - $('#save-edit-title').click(editTitleToggle).click(function () { - if ($editInput.val().length == 0 || - $editInput.val() == $issueTitle.text()) { - $editInput.val($issueTitle.text()); - return false; - } - - $.post($(this).data('update-url'), { - "_csrf": csrf, - "title": $editInput.val() - }, - function (data) { - $editInput.val(data.title); - $issueTitle.text(data.title); - reload(); - }); - return false; - }); - - // Edit issue or comment content - $('.edit-content').click(function () { - const $segment = $(this).parent().parent().parent().next(); - const $editContentZone = $segment.find('.edit-content-zone'); - const $renderContent = $segment.find('.render-content'); - const $rawContent = $segment.find('.raw-content'); - let $textarea; - - // Setup new form - if ($editContentZone.html().length == 0) { - $editContentZone.html($('#edit-content-form').html()); - $textarea = $editContentZone.find('textarea'); - issuesTribute.attach($textarea.get()); - emojiTribute.attach($textarea.get()); - - const $dropzone = $editContentZone.find('.dropzone'); - $dropzone.data("saved", false); - const $files = $editContentZone.find('.comment-files'); - if ($dropzone.length > 0) { - const filenameDict = {}; - $dropzone.dropzone({ - url: $dropzone.data('upload-url'), - headers: {"X-Csrf-Token": csrf}, - maxFiles: $dropzone.data('max-file'), - maxFilesize: $dropzone.data('max-size'), - acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'), - addRemoveLinks: true, - dictDefaultMessage: $dropzone.data('default-message'), - dictInvalidFileType: $dropzone.data('invalid-input-type'), - dictFileTooBig: $dropzone.data('file-too-big'), - dictRemoveFile: $dropzone.data('remove-file'), - init: function () { - this.on("success", function (file, data) { - filenameDict[file.name] = { - "uuid": data.uuid, - "submitted": false - } - const input = $('').val(data.uuid); - $files.append(input); - }); - this.on("removedfile", function (file) { - if (!(file.name in filenameDict)) { - return; - } - $('#' + filenameDict[file.name].uuid).remove(); - if ($dropzone.data('remove-url') && $dropzone.data('csrf') && !filenameDict[file.name].submitted) { - $.post($dropzone.data('remove-url'), { - file: filenameDict[file.name].uuid, - _csrf: $dropzone.data('csrf') - }); - } - }); - this.on("submit", function () { - $.each(filenameDict, function(name){ - filenameDict[name].submitted = true; - }); - }); - this.on("reload", function (){ - $.getJSON($editContentZone.data('attachment-url'), function(data){ - const drop = $dropzone.get(0).dropzone; - drop.removeAllFiles(true); - $files.empty(); - $.each(data, function(){ - const imgSrc = $dropzone.data('upload-url') + "/" + this.uuid; - drop.emit("addedfile", this); - drop.emit("thumbnail", this, imgSrc); - drop.emit("complete", this); - drop.files.push(this); - filenameDict[this.name] = { - "submitted": true, - "uuid": this.uuid - } - $dropzone.find("img[src='" + imgSrc + "']").css("max-width", "100%"); - const input = $('').val(this.uuid); - $files.append(input); - }); - }); - }); - } - }); - $dropzone.get(0).dropzone.emit("reload"); - } - // Give new write/preview data-tab name to distinguish from others - const $editContentForm = $editContentZone.find('.ui.comment.form'); - const $tabMenu = $editContentForm.find('.tabular.menu'); - $tabMenu.attr('data-write', $editContentZone.data('write')); - $tabMenu.attr('data-preview', $editContentZone.data('preview')); - $tabMenu.find('.write.item').attr('data-tab', $editContentZone.data('write')); - $tabMenu.find('.preview.item').attr('data-tab', $editContentZone.data('preview')); - $editContentForm.find('.write.segment').attr('data-tab', $editContentZone.data('write')); - $editContentForm.find('.preview.segment').attr('data-tab', $editContentZone.data('preview')); - - initCommentPreviewTab($editContentForm); - - $editContentZone.find('.cancel.button').click(function () { - $renderContent.show(); - $editContentZone.hide(); - $dropzone.get(0).dropzone.emit("reload"); - }); - $editContentZone.find('.save.button').click(function () { - $renderContent.show(); - $editContentZone.hide(); - const $attachments = $files.find("[name=files]").map(function(){ - return $(this).val(); - }).get(); - $.post($editContentZone.data('update-url'), { - "_csrf": csrf, - "content": $textarea.val(), - "context": $editContentZone.data('context'), - "files": $attachments - }, - function (data) { - if (data.length == 0) { - $renderContent.html($('#no-content').html()); - } else { - $renderContent.html(data.content); - emojify.run($renderContent[0]); - $('pre code', $renderContent[0]).each(function () { - hljs.highlightBlock(this); - }); - } - const $content = $segment.parent(); - if(!$content.find(".ui.small.images").length){ - if(data.attachments != ""){ - $content.append( - '
    ' + - '
    ' + - '
    ' + - '
    ' - ); - $content.find(".ui.small.images").html(data.attachments); - } - } else if (data.attachments == "") { - $content.find(".ui.small.images").parent().remove(); - } else { - $content.find(".ui.small.images").html(data.attachments); - } - $dropzone.get(0).dropzone.emit("submit"); - $dropzone.get(0).dropzone.emit("reload"); - }); - }); - } else { - $textarea = $segment.find('textarea'); - } - - // Show write/preview tab and copy raw content as needed - $editContentZone.show(); - $renderContent.hide(); - if ($textarea.val().length == 0) { - $textarea.val($rawContent.text()); - } - $textarea.focus(); - return false; - }); - - // Delete comment - $('.delete-comment').click(function () { - const $this = $(this); - if (confirm($this.data('locale'))) { - $.post($this.data('url'), { - "_csrf": csrf - }).success(function () { - $('#' + $this.data('comment-id')).remove(); - }); - } - return false; - }); - - // Change status - const $statusButton = $('#status-button'); - $('#comment-form .edit_area').keyup(function () { - if ($(this).val().length == 0) { - $statusButton.text($statusButton.data('status')) - } else { - $statusButton.text($statusButton.data('status-and-comment')) - } - }); - $statusButton.click(function () { - $('#status').val($statusButton.data('status-val')); - $('#comment-form').submit(); - }); - - // Pull Request merge button - const $mergeButton = $('.merge-button > button'); - $mergeButton.on('click', function(e) { - e.preventDefault(); - $('.' + $(this).data('do') + '-fields').show(); - $(this).parent().hide(); - }); - $('.merge-button > .dropdown').dropdown({ - onChange: function (_text, _value, $choice) { - if ($choice.data('do')) { - $mergeButton.find('.button-text').text($choice.text()); - $mergeButton.data('do', $choice.data('do')); - } - } - }); - $('.merge-cancel').on('click', function(e) { - e.preventDefault(); - $(this).closest('.form').hide(); - $mergeButton.parent().show(); - }); - - initReactionSelector(); - } - - // Diff - if ($('.repository.diff').length > 0) { - $('.diff-counter').each(function () { - const $item = $(this); - const addLine = $item.find('span[data-line].add').data("line"); - const delLine = $item.find('span[data-line].del').data("line"); - const addPercent = parseFloat(addLine) / (parseFloat(addLine) + parseFloat(delLine)) * 100; - $item.find(".bar .add").css("width", addPercent + "%"); - }); - } - - // Quick start and repository home - $('#repo-clone-ssh').click(function () { - $('.clone-url').text($(this).data('link')); - $('#repo-clone-url').val($(this).data('link')); - $(this).addClass('blue'); - $('#repo-clone-https').removeClass('blue'); - localStorage.setItem('repo-clone-protocol', 'ssh'); - }); - $('#repo-clone-https').click(function () { - $('.clone-url').text($(this).data('link')); - $('#repo-clone-url').val($(this).data('link')); - $(this).addClass('blue'); - $('#repo-clone-ssh').removeClass('blue'); - localStorage.setItem('repo-clone-protocol', 'https'); - }); - $('#repo-clone-url').click(function () { - $(this).select(); - }); - - // Pull request - const $repoComparePull = $('.repository.compare.pull'); - if ($repoComparePull.length > 0) { - initFilterSearchDropdown('.choose.branch .dropdown'); - // show pull request form - $repoComparePull.find('button.show-form').on('click', function(e) { - e.preventDefault(); - $repoComparePull.find('.pullrequest-form').show(); - $(this).parent().hide(); - }); - } - - // Branches - if ($('.repository.settings.branches').length > 0) { - initFilterSearchDropdown('.protected-branches .dropdown'); - $('.enable-protection, .enable-whitelist').change(function () { - if (this.checked) { - $($(this).data('target')).removeClass('disabled'); - } else { - $($(this).data('target')).addClass('disabled'); - } - }); - } -} - -function initMigration() { - const toggleMigrations = function() { - const authUserName = $('#auth_username').val(); - const cloneAddr = $('#clone_addr').val(); - if (!$('#mirror').is(":checked") && (authUserName!=undefined && authUserName.length > 0) - && (cloneAddr!=undefined && (cloneAddr.startsWith("https://github.com") || cloneAddr.startsWith("http://github.com")))) { - $('#migrate_items').show(); - } else { - $('#migrate_items').hide(); - } - } - - toggleMigrations(); - - $('#clone_addr').on('input', toggleMigrations) - $('#auth_username').on('input', toggleMigrations) - $('#mirror').on('change', toggleMigrations) -} - -function initPullRequestReview() { - $('.show-outdated').on('click', function (e) { - e.preventDefault(); - const id = $(this).data('comment'); - $(this).addClass("hide"); - $("#code-comments-" + id).removeClass('hide'); - $("#code-preview-" + id).removeClass('hide'); - $("#hide-outdated-" + id).removeClass('hide'); - }); - - $('.hide-outdated').on('click', function (e) { - e.preventDefault(); - const id = $(this).data('comment'); - $(this).addClass("hide"); - $("#code-comments-" + id).addClass('hide'); - $("#code-preview-" + id).addClass('hide'); - $("#show-outdated-" + id).removeClass('hide'); - }); - - $('button.comment-form-reply').on('click', function (e) { - e.preventDefault(); - $(this).hide(); - const form = $(this).parent().find('.comment-form') - form.removeClass('hide'); - assingMenuAttributes(form.find('.menu')); - }); - // The following part is only for diff views - if ($('.repository.pull.diff').length == 0) { - return; - } - - $('.diff-detail-box.ui.sticky').sticky(); - - $('.btn-review').on('click', function(e) { - e.preventDefault(); - $(this).closest('.dropdown').find('.menu').toggle('visible'); - }).closest('.dropdown').find('.link.close').on('click', function(e) { - e.preventDefault(); - $(this).closest('.menu').toggle('visible'); - }); - - $('.code-view .lines-code,.code-view .lines-num') - .on('mouseenter', function() { - const parent = $(this).closest('td'); - $(this).closest('tr').addClass( - parent.hasClass('lines-num-old') || parent.hasClass('lines-code-old') - ? 'focus-lines-old' : 'focus-lines-new' - ); - }) - .on('mouseleave', function() { - $(this).closest('tr').removeClass('focus-lines-new focus-lines-old'); - }); - $('.add-code-comment').on('click', function(e) { - // https://github.com/go-gitea/gitea/issues/4745 - if ($(e.target).hasClass('btn-add-single')) { - return; - } - e.preventDefault(); - const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split'); - const side = $(this).data('side'); - const idx = $(this).data('idx'); - const path = $(this).data('path'); - const form = $('#pull_review_add_comment').html(); - const tr = $(this).closest('tr'); - let ntr = tr.next(); - if (!ntr.hasClass('add-comment')) { - ntr = $('' - + (isSplit ? '' - : '') - + ''); - tr.after(ntr); - } - const td = ntr.find('.add-comment-' + side); - let commentCloud = td.find('.comment-code-cloud'); - if (commentCloud.length === 0) { - td.html(form); - commentCloud = td.find('.comment-code-cloud'); - assingMenuAttributes(commentCloud.find('.menu')); - - td.find("input[name='line']").val(idx); - td.find("input[name='side']").val(side === "left" ? "previous":"proposed"); - td.find("input[name='path']").val(path); - } - commentCloud.find('textarea').focus(); - }); -} - -function assingMenuAttributes(menu) { - const id = Math.floor(Math.random() * Math.floor(1000000)); - menu.attr('data-write', menu.attr('data-write') + id); - menu.attr('data-preview', menu.attr('data-preview') + id); - menu.find('.item').each(function() { - const tab = $(this).attr('data-tab') + id; - $(this).attr('data-tab', tab); - }); - menu.parent().find("*[data-tab='write']").attr('data-tab', 'write' + id); - menu.parent().find("*[data-tab='preview']").attr('data-tab', 'preview' + id); - initCommentPreviewTab(menu.parent(".form")); - return id; -} - -function initRepositoryCollaboration() { - // Change collaborator access mode - $('.access-mode.menu .item').click(function () { - const $menu = $(this).parent(); - $.post($menu.data('url'), { - "_csrf": csrf, - "uid": $menu.data('uid'), - "mode": $(this).data('value') - }) - }); -} - -function initTeamSettings() { - // Change team access mode - $('.organization.new.team input[name=permission]').change(function () { - const val = $('input[name=permission]:checked', '.organization.new.team').val() - if (val === 'admin') { - $('.organization.new.team .team-units').hide(); - } else { - $('.organization.new.team .team-units').show(); - } - }); -} - -function initWikiForm() { - const $editArea = $('.repository.wiki textarea#edit_area'); - if ($editArea.length > 0) { - const simplemde = new SimpleMDE({ - autoDownloadFontAwesome: false, - element: $editArea[0], - forceSync: true, - previewRender: function (plainText, preview) { // Async method - setTimeout(function () { - // FIXME: still send render request when return back to edit mode - $.post($editArea.data('url'), { - "_csrf": csrf, - "mode": "gfm", - "context": $editArea.data('context'), - "text": plainText - }, - function (data) { - preview.innerHTML = '
    ' + data + '
    '; - emojify.run($('.editor-preview')[0]); - } - ); - }, 0); - - return "Loading..."; - }, - renderingConfig: { - singleLineBreaks: false - }, - indentWithTabs: false, - tabSize: 4, - spellChecker: false, - toolbar: ["bold", "italic", "strikethrough", "|", - "heading-1", "heading-2", "heading-3", "heading-bigger", "heading-smaller", "|", - { - name: "code-inline", - action: function(e){ - const cm = e.codemirror; - const selection = cm.getSelection(); - cm.replaceSelection("`" + selection + "`"); - if (!selection) { - const cursorPos = cm.getCursor(); - cm.setCursor(cursorPos.line, cursorPos.ch - 1); - } - cm.focus(); - }, - className: "fa fa-angle-right", - title: "Add Inline Code", - },"code", "quote", "|", { - name: "checkbox-empty", - action: function(e){ - const cm = e.codemirror; - cm.replaceSelection("\n- [ ] " + cm.getSelection()); - cm.focus(); - }, - className: "fa fa-square-o", - title: "Add Checkbox (empty)", - }, - { - name: "checkbox-checked", - action: function(e){ - const cm = e.codemirror; - cm.replaceSelection("\n- [x] " + cm.getSelection()); - cm.focus(); - }, - className: "fa fa-check-square-o", - title: "Add Checkbox (checked)", - }, "|", - "unordered-list", "ordered-list", "|", - "link", "image", "table", "horizontal-rule", "|", - "clean-block", "preview", "fullscreen"] - }) - $(simplemde.codemirror.getInputField()).addClass("js-quick-submit"); - } -} - -// For IE -String.prototype.endsWith = function (pattern) { - const d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; -}; - -// Adding function to get the cursor position in a text field to jQuery object. -$.fn.getCursorPosition = function () { - const el = $(this).get(0); - let pos = 0; - if ('selectionStart' in el) { - pos = el.selectionStart; - } else if ('selection' in document) { - el.focus(); - const Sel = document.selection.createRange(); - const SelLength = document.selection.createRange().text.length; - Sel.moveStart('character', -el.value.length); - pos = Sel.text.length - SelLength; - } - return pos; -} - -function setSimpleMDE($editArea) { - if (codeMirrorEditor) { - codeMirrorEditor.toTextArea(); - codeMirrorEditor = null; - } - - if (simpleMDEditor) { - return true; - } - - simpleMDEditor = new SimpleMDE({ - autoDownloadFontAwesome: false, - element: $editArea[0], - forceSync: true, - renderingConfig: { - singleLineBreaks: false - }, - indentWithTabs: false, - tabSize: 4, - spellChecker: false, - previewRender: function (plainText, preview) { // Async method - setTimeout(function () { - // FIXME: still send render request when return back to edit mode - $.post($editArea.data('url'), { - "_csrf": csrf, - "mode": "gfm", - "context": $editArea.data('context'), - "text": plainText - }, - function (data) { - preview.innerHTML = '
    ' + data + '
    '; - emojify.run($('.editor-preview')[0]); - } - ); - }, 0); - - return "Loading..."; - }, - toolbar: ["bold", "italic", "strikethrough", "|", - "heading-1", "heading-2", "heading-3", "heading-bigger", "heading-smaller", "|", - "code", "quote", "|", - "unordered-list", "ordered-list", "|", - "link", "image", "table", "horizontal-rule", "|", - "clean-block", "preview", "fullscreen", "side-by-side"] - }); - - return true; -} - -function setCodeMirror($editArea) { - if (simpleMDEditor) { - simpleMDEditor.toTextArea(); - simpleMDEditor = null; - } - - if (codeMirrorEditor) { - return true; - } - - codeMirrorEditor = CodeMirror.fromTextArea($editArea[0], { - lineNumbers: true - }); - codeMirrorEditor.on("change", function (cm, _change) { - $editArea.val(cm.getValue()); - }); - - return true; -} - -function initEditor() { - $('.js-quick-pull-choice-option').change(function () { - if ($(this).val() == 'commit-to-new-branch') { - $('.quick-pull-branch-name').show(); - $('.quick-pull-branch-name input').prop('required',true); - } else { - $('.quick-pull-branch-name').hide(); - $('.quick-pull-branch-name input').prop('required',false); - } - $('#commit-button').text($(this).attr('button_text')); - }); - - const $editFilename = $("#file-name"); - $editFilename.keyup(function (e) { - const $section = $('.breadcrumb span.section'); - const $divider = $('.breadcrumb div.divider'); - let value; - let parts; - - if (e.keyCode == 8) { - if ($(this).getCursorPosition() == 0) { - if ($section.length > 0) { - value = $section.last().find('a').text(); - $(this).val(value + $(this).val()); - $(this)[0].setSelectionRange(value.length, value.length); - $section.last().remove(); - $divider.last().remove(); - } - } - } - if (e.keyCode == 191) { - parts = $(this).val().split('/'); - for (let i = 0; i < parts.length; ++i) { - value = parts[i]; - if (i < parts.length - 1) { - if (value.length) { - $('' + value + '').insertBefore($(this)); - $('
    /
    ').insertBefore($(this)); - } - } - else { - $(this).val(value); - } - $(this)[0].setSelectionRange(0, 0); - } - } - parts = []; - $('.breadcrumb span.section').each(function () { - const element = $(this); - if (element.find('a').length) { - parts.push(element.find('a').text()); - } else { - parts.push(element.text()); - } - }); - if ($(this).val()) - parts.push($(this).val()); - $('#tree_path').val(parts.join('/')); - }).trigger('keyup'); - - const $editArea = $('.repository.editor textarea#edit_area'); - if (!$editArea.length) - return; - - const markdownFileExts = $editArea.data("markdown-file-exts").split(","); - const lineWrapExtensions = $editArea.data("line-wrap-extensions").split(","); - - $editFilename.on("keyup", function () { - const val = $editFilename.val(); - let mode, spec, extension, extWithDot, dataUrl, apiCall; - - extension = extWithDot = ""; - const m = /.+\.([^.]+)$/.exec(val); - if (m) { - extension = m[1]; - extWithDot = "." + extension; - } - - const info = CodeMirror.findModeByExtension(extension); - const previewLink = $('a[data-tab=preview]'); - if (info) { - mode = info.mode; - spec = info.mime; - apiCall = mode; - } - else { - apiCall = extension - } - - if (previewLink.length && apiCall && previewFileModes && previewFileModes.length && previewFileModes.indexOf(apiCall) >= 0) { - dataUrl = previewLink.data('url'); - previewLink.data('url', dataUrl.replace(/(.*)\/.*/i, '$1/' + mode)); - previewLink.show(); - } - else { - previewLink.hide(); - } - - // If this file is a Markdown extensions, we will load that editor and return - if (markdownFileExts.indexOf(extWithDot) >= 0) { - if (setSimpleMDE($editArea)) { - return; - } - } - - // Else we are going to use CodeMirror - if (!codeMirrorEditor && !setCodeMirror($editArea)) { - return; - } - - if (mode) { - codeMirrorEditor.setOption("mode", spec); - CodeMirror.autoLoadMode(codeMirrorEditor, mode); - } - - if (lineWrapExtensions.indexOf(extWithDot) >= 0) { - codeMirrorEditor.setOption("lineWrapping", true); - } - else { - codeMirrorEditor.setOption("lineWrapping", false); - } - - // get the filename without any folder - let value = $editFilename.val(); - if (value.length === 0) { - return; - } - value = value.split('/'); - value = value[value.length - 1]; - - $.getJSON($editFilename.data('ec-url-prefix')+value, function(editorconfig) { - if (editorconfig.indent_style === 'tab') { - codeMirrorEditor.setOption("indentWithTabs", true); - codeMirrorEditor.setOption('extraKeys', {}); - } else { - codeMirrorEditor.setOption("indentWithTabs", false); - // required because CodeMirror doesn't seems to use spaces correctly for {"indentWithTabs": false}: - // - https://github.com/codemirror/CodeMirror/issues/988 - // - https://codemirror.net/doc/manual.html#keymaps - codeMirrorEditor.setOption('extraKeys', { - Tab: function(cm) { - const spaces = Array(parseInt(cm.getOption("indentUnit")) + 1).join(" "); - cm.replaceSelection(spaces); - } - }); - } - codeMirrorEditor.setOption("indentUnit", editorconfig.indent_size || 4); - codeMirrorEditor.setOption("tabSize", editorconfig.tab_width || 4); - }); - }).trigger('keyup'); - - // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage - // to enable or disable the commit button - const $commitButton = $('#commit-button'); - const $editForm = $('.ui.edit.form'); - const dirtyFileClass = 'dirty-file'; - - // Disabling the button at the start - $commitButton.prop('disabled', true); - - // Registering a custom listener for the file path and the file content - $editForm.areYouSure({ - silent: true, - dirtyClass: dirtyFileClass, - fieldSelector: ':input:not(.commit-form-wrapper :input)', - change: function () { - const dirty = $(this).hasClass(dirtyFileClass); - $commitButton.prop('disabled', !dirty); - } - }); - - $commitButton.click(function (event) { - // A modal which asks if an empty file should be committed - if ($editArea.val().length === 0) { - $('#edit-empty-content-modal').modal({ - onApprove: function () { - $('.edit.form').submit(); - } - }).modal('show'); - event.preventDefault(); - } - }); -} - -function initOrganization() { - if ($('.organization').length == 0) { - return; - } - - // Options - if ($('.organization.settings.options').length > 0) { - $('#org_name').keyup(function () { - const $prompt = $('#org-name-change-prompt'); - if ($(this).val().toString().toLowerCase() != $(this).data('org-name').toString().toLowerCase()) { - $prompt.show(); - } else { - $prompt.hide(); - } - }); - } -} - -function initUserSettings() { - // Options - if ($('.user.settings.profile').length > 0) { - $('#username').keyup(function () { - const $prompt = $('#name-change-prompt'); - if ($(this).val().toString().toLowerCase() != $(this).data('name').toString().toLowerCase()) { - $prompt.show(); - } else { - $prompt.hide(); - } - }); - } -} - -function initWebhook() { - if ($('.new.webhook').length == 0) { - return; - } - - $('.events.checkbox input').change(function () { - if ($(this).is(':checked')) { - $('.events.fields').show(); - } - }); - $('.non-events.checkbox input').change(function () { - if ($(this).is(':checked')) { - $('.events.fields').hide(); - } - }); - - const updateContentType = function () { - const visible = $('#http_method').val() === 'POST'; - $('#content_type').parent().parent()[visible ? 'show' : 'hide'](); - }; - updateContentType(); - $('#http_method').change(function () { - updateContentType(); - }); - - // Test delivery - $('#test-delivery').click(function () { - const $this = $(this); - $this.addClass('loading disabled'); - $.post($this.data('link'), { - "_csrf": csrf - }).done( - setTimeout(function () { - window.location.href = $this.data('redirect'); - }, 5000) - ) - }); -} - -function initAdmin() { - if ($('.admin').length == 0) { - return; - } - - // New user - if ($('.admin.new.user').length > 0 || - $('.admin.edit.user').length > 0) { - $('#login_type').change(function () { - if ($(this).val().substring(0, 1) == '0') { - $('#login_name').removeAttr('required'); - $('.non-local').hide(); - $('.local').show(); - $('#user_name').focus(); - - if ($(this).data('password') == "required") { - $('#password').attr('required', 'required'); - } - - } else { - $('#login_name').attr('required', 'required'); - $('.non-local').show(); - $('.local').hide(); - $('#login_name').focus(); - - $('#password').removeAttr('required'); - } - }); - } - - function onSecurityProtocolChange() { - if ($('#security_protocol').val() > 0) { - $('.has-tls').show(); - } else { - $('.has-tls').hide(); - } - } - - function onUsePagedSearchChange() { - if ($('#use_paged_search').prop('checked')) { - $('.search-page-size').show() - .find('input').attr('required', 'required'); - } else { - $('.search-page-size').hide() - .find('input').removeAttr('required'); - } - } - - function onOAuth2Change() { - $('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url').hide(); - $('.open_id_connect_auto_discovery_url input[required]').removeAttr('required'); - - const provider = $('#oauth2_provider').val(); - switch (provider) { - case 'github': - case 'gitlab': - case 'gitea': - $('.oauth2_use_custom_url').show(); - break; - case 'openidConnect': - $('.open_id_connect_auto_discovery_url input').attr('required', 'required'); - $('.open_id_connect_auto_discovery_url').show(); - break; - } - onOAuth2UseCustomURLChange(); - } - - function onOAuth2UseCustomURLChange() { - const provider = $('#oauth2_provider').val(); - $('.oauth2_use_custom_url_field').hide(); - $('.oauth2_use_custom_url_field input[required]').removeAttr('required'); - - if ($('#oauth2_use_custom_url').is(':checked')) { - if (!$('#oauth2_token_url').val()) { - $('#oauth2_token_url').val($('#' + provider + '_token_url').val()); - } - if (!$('#oauth2_auth_url').val()) { - $('#oauth2_auth_url').val($('#' + provider + '_auth_url').val()); - } - if (!$('#oauth2_profile_url').val()) { - $('#oauth2_profile_url').val($('#' + provider + '_profile_url').val()); - } - if (!$('#oauth2_email_url').val()) { - $('#oauth2_email_url').val($('#' + provider + '_email_url').val()); - } - switch (provider) { - case 'github': - $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input, .oauth2_email_url input').attr('required', 'required'); - $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url, .oauth2_email_url').show(); - break; - case 'gitea': - case 'gitlab': - $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input').attr('required', 'required'); - $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url').show(); - $('#oauth2_email_url').val(''); - break; - } - } - } - - // New authentication - if ($('.admin.new.authentication').length > 0) { - $('#auth_type').change(function () { - $('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls .search-page-size').hide(); - - $('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required]').removeAttr('required'); - $('.binddnrequired').removeClass("required"); - - const authType = $(this).val(); - switch (authType) { - case '2': // LDAP - $('.ldap').show(); - $('.binddnrequired input, .ldap div.required:not(.dldap) input').attr('required', 'required'); - $('.binddnrequired').addClass("required"); - break; - case '3': // SMTP - $('.smtp').show(); - $('.has-tls').show(); - $('.smtp div.required input, .has-tls').attr('required', 'required'); - break; - case '4': // PAM - $('.pam').show(); - $('.pam input').attr('required', 'required'); - break; - case '5': // LDAP - $('.dldap').show(); - $('.dldap div.required:not(.ldap) input').attr('required', 'required'); - break; - case '6': // OAuth2 - $('.oauth2').show(); - $('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input').attr('required', 'required'); - onOAuth2Change(); - break; - } - if (authType == '2' || authType == '5') { - onSecurityProtocolChange() - } - if (authType == '2') { - onUsePagedSearchChange(); - } - }); - $('#auth_type').change(); - $('#security_protocol').change(onSecurityProtocolChange); - $('#use_paged_search').change(onUsePagedSearchChange); - $('#oauth2_provider').change(onOAuth2Change); - $('#oauth2_use_custom_url').change(onOAuth2UseCustomURLChange); - } - // Edit authentication - if ($('.admin.edit.authentication').length > 0) { - const authType = $('#auth_type').val(); - if (authType == '2' || authType == '5') { - $('#security_protocol').change(onSecurityProtocolChange); - if (authType == '2') { - $('#use_paged_search').change(onUsePagedSearchChange); - } - } else if (authType == '6') { - $('#oauth2_provider').change(onOAuth2Change); - $('#oauth2_use_custom_url').change(onOAuth2UseCustomURLChange); - onOAuth2Change(); - } - } - - // Notice - if ($('.admin.notice')) { - const $detailModal = $('#detail-modal'); - - // Attach view detail modals - $('.view-detail').click(function () { - $detailModal.find('.content p').text($(this).data('content')); - $detailModal.modal('show'); - return false; - }); - - // Select actions - const $checkboxes = $('.select.table .ui.checkbox'); - $('.select.action').click(function () { - switch ($(this).data('action')) { - case 'select-all': - $checkboxes.checkbox('check'); - break; - case 'deselect-all': - $checkboxes.checkbox('uncheck'); - break; - case 'inverse': - $checkboxes.checkbox('toggle'); - break; - } - }); - $('#delete-selection').click(function () { - const $this = $(this); - $this.addClass("loading disabled"); - const ids = []; - $checkboxes.each(function () { - if ($(this).checkbox('is checked')) { - ids.push($(this).data('id')); - } - }); - $.post($this.data('link'), { - "_csrf": csrf, - "ids": ids - }).done(function () { - window.location.href = $this.data('redirect'); - }); - }); - } -} - -function buttonsClickOnEnter() { - $('.ui.button').keypress(function (e) { - if (e.keyCode == 13 || e.keyCode == 32) // enter key or space bar - $(this).click(); - }); -} - -function searchUsers() { - const $searchUserBox = $('#search-user-box'); - $searchUserBox.search({ - minCharacters: 2, - apiSettings: { - url: suburl + '/api/v1/users/search?q={query}', - onResponse: function(response) { - const items = []; - $.each(response.data, function (_i, item) { - let title = item.login; - if (item.full_name && item.full_name.length > 0) { - title += ' (' + htmlEncode(item.full_name) + ')'; - } - items.push({ - title: title, - image: item.avatar_url - }) - }); - - return { results: items } - } - }, - searchFields: ['login', 'full_name'], - showNoResults: false - }); -} - -function searchTeams() { - const $searchTeamBox = $('#search-team-box'); - $searchTeamBox.search({ - minCharacters: 2, - apiSettings: { - url: suburl + '/api/v1/orgs/' + $searchTeamBox.data('org') + '/teams/search?q={query}', - headers: {"X-Csrf-Token": csrf}, - onResponse: function(response) { - const items = []; - $.each(response.data, function (_i, item) { - const title = item.name + ' (' + item.permission + ' access)'; - items.push({ - title: title, - }) - }); - - return { results: items } - } - }, - searchFields: ['name', 'description'], - showNoResults: false - }); -} - -function searchRepositories() { - const $searchRepoBox = $('#search-repo-box'); - $searchRepoBox.search({ - minCharacters: 2, - apiSettings: { - url: suburl + '/api/v1/repos/search?q={query}&uid=' + $searchRepoBox.data('uid'), - onResponse: function(response) { - const items = []; - $.each(response.data, function (_i, item) { - items.push({ - title: item.full_name.split("/")[1], - description: item.full_name - }) - }); - - return { results: items } - } - }, - searchFields: ['full_name'], - showNoResults: false - }); -} - -function initCodeView() { - if ($('.code-view .linenums').length > 0) { - $(document).on('click', '.lines-num span', function (e) { - const $select = $(this); - const $list = $select.parent().siblings('.lines-code').find('ol.linenums > li'); - selectRange($list, $list.filter('[rel=' + $select.attr('id') + ']'), (e.shiftKey ? $list.filter('.active').eq(0) : null)); - deSelect(); - }); - - $(window).on('hashchange', function () { - let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/); - const $list = $('.code-view ol.linenums > li'); - let $first; - if (m) { - $first = $list.filter('.' + m[1]); - selectRange($list, $first, $list.filter('.' + m[2])); - $("html, body").scrollTop($first.offset().top - 200); - return; - } - m = window.location.hash.match(/^#(L|n)(\d+)$/); - if (m) { - $first = $list.filter('.L' + m[2]); - selectRange($list, $first); - $("html, body").scrollTop($first.offset().top - 200); - } - }).trigger('hashchange'); - } -} - -function initU2FAuth() { - if($('#wait-for-key').length === 0) { - return - } - u2fApi.ensureSupport() - .then(function () { - $.getJSON(suburl + '/user/u2f/challenge').success(function(req) { - u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30) - .then(u2fSigned) - .catch(function (err) { - if(err === undefined) { - u2fError(1); - return - } - u2fError(err.metaData.code); - }); - }); - }).catch(function () { - // Fallback in case browser do not support U2F - window.location.href = suburl + "/user/two_factor" - }) -} -function u2fSigned(resp) { - $.ajax({ - url: suburl + '/user/u2f/sign', - type: "POST", - headers: {"X-Csrf-Token": csrf}, - data: JSON.stringify(resp), - contentType: "application/json; charset=utf-8", - }).done(function(res){ - window.location.replace(res); - }).fail(function () { - u2fError(1); - }); -} - -function u2fRegistered(resp) { - if (checkError(resp)) { - return; - } - $.ajax({ - url: suburl + '/user/settings/security/u2f/register', - type: "POST", - headers: {"X-Csrf-Token": csrf}, - data: JSON.stringify(resp), - contentType: "application/json; charset=utf-8", - success: function(){ - reload(); - }, - fail: function () { - u2fError(1); - } - }); -} - -function checkError(resp) { - if (!('errorCode' in resp)) { - return false; - } - if (resp.errorCode === 0) { - return false; - } - u2fError(resp.errorCode); - return true; -} - - -function u2fError(errorType) { - const u2fErrors = { - 'browser': $('#unsupported-browser'), - 1: $('#u2f-error-1'), - 2: $('#u2f-error-2'), - 3: $('#u2f-error-3'), - 4: $('#u2f-error-4'), - 5: $('.u2f-error-5') - }; - u2fErrors[errorType].removeClass('hide'); - for(const type in u2fErrors){ - if(type != errorType){ - u2fErrors[type].addClass('hide'); - } - } - $('#u2f-error').modal('show'); -} - -function initU2FRegister() { - $('#register-device').modal({allowMultiple: false}); - $('#u2f-error').modal({allowMultiple: false}); - $('#register-security-key').on('click', function(e) { - e.preventDefault(); - u2fApi.ensureSupport() - .then(u2fRegisterRequest) - .catch(function() { - u2fError('browser'); - }) - }) -} - -function u2fRegisterRequest() { - $.post(suburl + "/user/settings/security/u2f/request_register", { - "_csrf": csrf, - "name": $('#nickname').val() - }).success(function(req) { - $("#nickname").closest("div.field").removeClass("error"); - $('#register-device').modal('show'); - if(req.registeredKeys === null) { - req.registeredKeys = [] - } - u2fApi.register(req.appId, req.registerRequests, req.registeredKeys, 30) - .then(u2fRegistered) - .catch(function (reason) { - if(reason === undefined) { - u2fError(1); - return - } - u2fError(reason.metaData.code); - }); - }).fail(function(xhr) { - if(xhr.status === 409) { - $("#nickname").closest("div.field").addClass("error"); - } - }); -} - -function initWipTitle() { - $(".title_wip_desc > a").click(function (e) { - e.preventDefault(); - - const $issueTitle = $("#issue_title"); - $issueTitle.focus(); - const value = $issueTitle.val().trim().toUpperCase(); - - for (const i in wipPrefixes) { - if (value.startsWith(wipPrefixes[i].toUpperCase())) { - return; - } - } - - $issueTitle.val(wipPrefixes[0] + " " + $issueTitle.val()); - }); -} - -$(document).ready(function () { - csrf = $('meta[name=_csrf]').attr("content"); - suburl = $('meta[name=_suburl]').attr("content"); - - // Show exact time - $('.time-since').each(function () { - $(this).addClass('poping up').attr('data-content', $(this).attr('title')).attr('data-variation', 'inverted tiny').attr('title', ''); - }); - - // Semantic UI modules. - $('.dropdown:not(.custom)').dropdown(); - $('.jump.dropdown').dropdown({ - action: 'hide', - onShow: function () { - $('.poping.up').popup('hide'); - } - }); - $('.slide.up.dropdown').dropdown({ - transition: 'slide up' - }); - $('.upward.dropdown').dropdown({ - direction: 'upward' - }); - $('.ui.accordion').accordion(); - $('.ui.checkbox').checkbox(); - $('.ui.progress').progress({ - showActivity: false - }); - $('.poping.up').popup(); - $('.top.menu .poping.up').popup({ - onShow: function () { - if ($('.top.menu .menu.transition').hasClass('visible')) { - return false; - } - } - }); - $('.tabular.menu .item').tab(); - $('.tabable.menu .item').tab(); - - $('.toggle.button').click(function () { - $($(this).data('target')).slideToggle(100); - }); - - // make table element clickable like a link - $('tr[data-href]').click(function() { - window.location = $(this).data('href'); - }); - - // Highlight JS - if (typeof hljs != 'undefined') { - const nodes = [].slice.call(document.querySelectorAll('pre code') || []); - for (let i = 0; i < nodes.length; i++) { - hljs.highlightBlock(nodes[i]); - } - } - - // Dropzone - const $dropzone = $('#dropzone'); - if ($dropzone.length > 0) { - const filenameDict = {}; - - new Dropzone("#dropzone", { - url: $dropzone.data('upload-url'), - headers: {"X-Csrf-Token": csrf}, - maxFiles: $dropzone.data('max-file'), - maxFilesize: $dropzone.data('max-size'), - acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'), - addRemoveLinks: true, - dictDefaultMessage: $dropzone.data('default-message'), - dictInvalidFileType: $dropzone.data('invalid-input-type'), - dictFileTooBig: $dropzone.data('file-too-big'), - dictRemoveFile: $dropzone.data('remove-file'), - init: function () { - this.on("success", function (file, data) { - filenameDict[file.name] = data.uuid; - const input = $('').val(data.uuid); - $('.files').append(input); - }); - this.on("removedfile", function (file) { - if (file.name in filenameDict) { - $('#' + filenameDict[file.name]).remove(); - } - if ($dropzone.data('remove-url') && $dropzone.data('csrf')) { - $.post($dropzone.data('remove-url'), { - file: filenameDict[file.name], - _csrf: $dropzone.data('csrf') - }); - } - }) - }, - }); - } - - // Emojify - emojify.setConfig({ - img_dir: suburl + '/vendor/plugins/emojify/images', - ignore_emoticons: true - }); - const hasEmoji = document.getElementsByClassName('has-emoji'); - for (let i = 0; i < hasEmoji.length; i++) { - emojify.run(hasEmoji[i]); - for (let j = 0; j < hasEmoji[i].childNodes.length; j++) { - if (hasEmoji[i].childNodes[j].nodeName === "A") { - emojify.run(hasEmoji[i].childNodes[j]) - } - } - } - - // Clipboard JS - const clipboard = new Clipboard('.clipboard'); - clipboard.on('success', function (e) { - e.clearSelection(); - - $('#' + e.trigger.getAttribute('id')).popup('destroy'); - e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-success')) - $('#' + e.trigger.getAttribute('id')).popup('show'); - e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-original')) - }); - - clipboard.on('error', function (e) { - $('#' + e.trigger.getAttribute('id')).popup('destroy'); - e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-error')) - $('#' + e.trigger.getAttribute('id')).popup('show'); - e.trigger.setAttribute('data-content', e.trigger.getAttribute('data-original')) - }); - - // Helpers. - $('.delete-button').click(showDeletePopup); - - $('.delete-branch-button').click(showDeletePopup); - - $('.undo-button').click(function() { - const $this = $(this); - $.post($this.data('url'), { - "_csrf": csrf, - "id": $this.data("id") - }).done(function(data) { - window.location.href = data.redirect; - }); - }); - $('.show-panel.button').click(function () { - $($(this).data('panel')).show(); - }); - $('.show-modal.button').click(function () { - $($(this).data('modal')).modal('show'); - }); - $('.delete-post.button').click(function () { - const $this = $(this); - $.post($this.data('request-url'), { - "_csrf": csrf - }).done(function () { - window.location.href = $this.data('done-url'); - }); - }); - - // Set anchor. - $('.markdown').each(function () { - const headers = {}; - $(this).find('h1, h2, h3, h4, h5, h6').each(function () { - let node = $(this); - const val = encodeURIComponent(node.text().toLowerCase().replace(/[^\u00C0-\u1FFF\u2C00-\uD7FF\w\- ]/g, '').replace(/[ ]/g, '-')); - let name = val; - if (headers[val] > 0) { - name = val + '-' + headers[val]; - } - if (headers[val] == undefined) { - headers[val] = 1; - } else { - headers[val] += 1; - } - node = node.wrap('
    '); - node.append(''); - }); - }); - - $('.issue-checkbox').click(function() { - const numChecked = $('.issue-checkbox').children('input:checked').length; - if (numChecked > 0) { - $('#issue-filters').addClass("hide"); - $('#issue-actions').removeClass("hide"); - } else { - $('#issue-filters').removeClass("hide"); - $('#issue-actions').addClass("hide"); - } - }); - - $('.issue-action').click(function () { - let action = this.dataset.action; - let elementId = this.dataset.elementId; - const issueIDs = $('.issue-checkbox').children('input:checked').map(function() { - return this.dataset.issueId; - }).get().join(); - const url = this.dataset.url; - if (elementId === '0' && url.substr(-9) === '/assignee'){ - elementId = ''; - action = 'clear'; - } - updateIssuesMeta(url, action, issueIDs, elementId).then(function() { - // NOTICE: This reset of checkbox state targets Firefox caching behaviour, as the checkboxes stay checked after reload - if (action === "close" || action === "open" ){ - //uncheck all checkboxes - $('.issue-checkbox input[type="checkbox"]').each(function(_,e){ e.checked = false; }); - } - reload(); - }); - }); - - // NOTICE: This event trigger targets Firefox caching behaviour, as the checkboxes stay checked after reload - // trigger ckecked event, if checkboxes are checked on load - $('.issue-checkbox input[type="checkbox"]:checked').first().each(function(_,e) { - e.checked = false; - $(e).click(); - }); - - buttonsClickOnEnter(); - searchUsers(); - searchTeams(); - searchRepositories(); - - initCommentForm(); - initInstall(); - initRepository(); - initMigration(); - initWikiForm(); - initEditForm(); - initEditor(); - initOrganization(); - initWebhook(); - initAdmin(); - initCodeView(); - initVueApp(); - initTeamSettings(); - initCtrlEnterSubmit(); - initNavbarContentToggle(); - initTopicbar(); - initU2FAuth(); - initU2FRegister(); - initIssueList(); - initWipTitle(); - initPullRequestReview(); - initRepoStatusChecker(); - - // Repo clone url. - if ($('#repo-clone-url').length > 0) { - switch (localStorage.getItem('repo-clone-protocol')) { - case 'ssh': - if ($('#repo-clone-ssh').click().length === 0) { - $('#repo-clone-https').click(); - } - break; - default: - $('#repo-clone-https').click(); - break; - } - } - - const routes = { - 'div.user.settings': initUserSettings, - 'div.repository.settings.collaboration': initRepositoryCollaboration - }; - - let selector; - for (selector in routes) { - if ($(selector).length > 0) { - routes[selector](); - break; - } - } - - const $cloneAddr = $('#clone_addr'); - $cloneAddr.change(function() { - const $repoName = $('#repo_name'); - if ($cloneAddr.val().length > 0 && $repoName.val().length === 0) { // Only modify if repo_name input is blank - $repoName.val($cloneAddr.val().match(/^(.*\/)?((.+?)(\.git)?)$/)[3]); - } - }); -}); - -function changeHash(hash) { - if (history.pushState) { - history.pushState(null, null, hash); - } - else { - location.hash = hash; - } -} - -function deSelect() { - if (window.getSelection) { - window.getSelection().removeAllRanges(); - } else { - document.selection.empty(); - } -} - -function selectRange($list, $select, $from) { - $list.removeClass('active'); - if ($from) { - let a = parseInt($select.attr('rel').substr(1)); - let b = parseInt($from.attr('rel').substr(1)); - let c; - if (a != b) { - if (a > b) { - c = a; - a = b; - b = c; - } - const classes = []; - for (let i = a; i <= b; i++) { - classes.push('.L' + i); - } - $list.filter(classes.join(',')).addClass('active'); - changeHash('#L' + a + '-' + 'L' + b); - return - } - } - $select.addClass('active'); - changeHash('#' + $select.attr('rel')); -} - -$(function () { - // Warn users that try to leave a page after entering data into a form. - // Except on sign-in pages, and for forms marked as 'ignore-dirty'. - if ($('.user.signin').length === 0) { - $('form:not(.ignore-dirty)').areYouSure(); - } - - // Parse SSH Key - $("#ssh-key-content").on('change paste keyup',function(){ - const arrays = $(this).val().split(" "); - const $title = $("#ssh-key-title") - if ($title.val() === "" && arrays.length === 3 && arrays[2] !== "") { - $title.val(arrays[2]); - } - }); -}); - -function showDeletePopup() { - const $this = $(this); - let filter = ""; - if ($this.attr("id")) { - filter += "#" + $this.attr("id") - } - - const dialog = $('.delete.modal' + filter); - dialog.find('.name').text($this.data('name')); - - dialog.modal({ - closable: false, - onApprove: function() { - if ($this.data('type') == "form") { - $($this.data('form')).submit(); - return; - } - - $.post($this.data('url'), { - "_csrf": csrf, - "id": $this.data("id") - }).done(function(data) { - window.location.href = data.redirect; - }); - } - }).modal('show'); - return false; -} - -function initVueComponents(){ - const vueDelimeters = ['${', '}']; - - Vue.component('repo-search', { - delimiters: vueDelimeters, - - props: { - searchLimit: { - type: Number, - default: 10 - }, - suburl: { - type: String, - required: true - }, - uid: { - type: Number, - required: true - }, - organizations: { - type: Array, - default: [] - }, - isOrganization: { - type: Boolean, - default: true - }, - canCreateOrganization: { - type: Boolean, - default: false - }, - organizationsTotalCount: { - type: Number, - default: 0 - }, - moreReposLink: { - type: String, - default: '' - } - }, - - data: function() { - return { - tab: 'repos', - repos: [], - reposTotalCount: 0, - reposFilter: 'all', - searchQuery: '', - isLoading: false, - repoTypes: { - 'all': { - count: 0, - searchMode: '', - }, - 'forks': { - count: 0, - searchMode: 'fork', - }, - 'mirrors': { - count: 0, - searchMode: 'mirror', - }, - 'sources': { - count: 0, - searchMode: 'source', - }, - 'collaborative': { - count: 0, - searchMode: 'collaborative', - }, - } - } - }, - - computed: { - showMoreReposLink: function() { - return this.repos.length > 0 && this.repos.length < this.repoTypes[this.reposFilter].count; - }, - searchURL: function() { - return this.suburl + '/api/v1/repos/search?sort=updated&order=desc&uid=' + this.uid + '&q=' + this.searchQuery - + '&limit=' + this.searchLimit + '&mode=' + this.repoTypes[this.reposFilter].searchMode - + (this.reposFilter !== 'all' ? '&exclusive=1' : ''); - }, - repoTypeCount: function() { - return this.repoTypes[this.reposFilter].count; - } - }, - - mounted: function() { - this.searchRepos(this.reposFilter); - - const self = this; - Vue.nextTick(function() { - self.$refs.search.focus(); - }); - }, - - methods: { - changeTab: function(t) { - this.tab = t; - }, - - changeReposFilter: function(filter) { - this.reposFilter = filter; - this.repos = []; - this.repoTypes[filter].count = 0; - this.searchRepos(filter); - }, - - showRepo: function(repo, filter) { - switch (filter) { - case 'sources': - return repo.owner.id == this.uid && !repo.mirror && !repo.fork; - case 'forks': - return repo.owner.id == this.uid && !repo.mirror && repo.fork; - case 'mirrors': - return repo.mirror; - case 'collaborative': - return repo.owner.id != this.uid && !repo.mirror; - default: - return true; - } - }, - - searchRepos: function(reposFilter) { - const self = this; - - this.isLoading = true; - - const searchedMode = this.repoTypes[reposFilter].searchMode; - const searchedURL = this.searchURL; - const searchedQuery = this.searchQuery; - - $.getJSON(searchedURL, function(result, _textStatus, request) { - if (searchedURL == self.searchURL) { - self.repos = result.data; - const count = request.getResponseHeader('X-Total-Count'); - if (searchedQuery === '' && searchedMode === '') { - self.reposTotalCount = count; - } - self.repoTypes[reposFilter].count = count; - } - }).always(function() { - if (searchedURL == self.searchURL) { - self.isLoading = false; - } - }); - }, - - repoClass: function(repo) { - if (repo.fork) { - return 'octicon octicon-repo-forked'; - } else if (repo.mirror) { - return 'octicon octicon-repo-clone'; - } else if (repo.private) { - return 'octicon octicon-lock'; - } else { - return 'octicon octicon-repo'; - } - } - } - }) -} - -function initCtrlEnterSubmit() { - $(".js-quick-submit").keydown(function(e) { - if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.keyCode == 13 || e.keyCode == 10)) { - $(this).closest("form").submit(); - } - }); -} - -function initVueApp() { - const el = document.getElementById('app'); - if (!el) { - return; - } - - initVueComponents(); - - new Vue({ - delimiters: ['${', '}'], - el: el, - - data: { - searchLimit: document.querySelector('meta[name=_search_limit]').content, - suburl: document.querySelector('meta[name=_suburl]').content, - uid: document.querySelector('meta[name=_context_uid]').content, - }, - }); -} - -function timeAddManual() { - $('.mini.modal') - .modal({ - duration: 200, - onApprove: function() { - $('#add_time_manual_form').submit(); - } - }).modal('show') - ; -} - -function toggleStopwatch() { - $("#toggle_stopwatch_form").submit(); -} -function cancelStopwatch() { - $("#cancel_stopwatch_form").submit(); -} - -function initKanbanBoard(appElementID) { - - const el = document.getElementById(appElementID) - if (!el) { - return - } - - new Sortable(el, { - group: "shared", - animation: 150, - onAdd: function(e) { - $.ajax(e.to.dataset.url + "/" + e.item.dataset.issue, { - headers: { - 'X-Csrf-Token': csrf, - 'X-Remote': true, - }, - contentType: 'application/json', - type: 'POST', - success: function () { - // setTimeout(reload(),3000) - }, - }) - } - }) -} - -function initHeatmap(appElementId, heatmapUser, locale) { - const el = document.getElementById(appElementId); - if (!el) { - return; - } - - locale = locale || {}; - - locale.contributions = locale.contributions || 'contributions'; - locale.no_contributions = locale.no_contributions || 'No contributions'; - - const vueDelimeters = ['${', '}']; - - Vue.component('activity-heatmap', { - delimiters: vueDelimeters, - - props: { - user: { - type: String, - required: true - }, - suburl: { - type: String, - required: true - }, - locale: { - type: Object, - required: true - } - }, - - data: function () { - return { - isLoading: true, - colorRange: [], - endDate: null, - values: [], - totalContributions: 0, - }; - }, - - mounted: function() { - this.colorRange = [ - this.getColor(0), - this.getColor(1), - this.getColor(2), - this.getColor(3), - this.getColor(4), - this.getColor(5) - ]; - this.endDate = new Date(); - this.loadHeatmap(this.user); - }, - - methods: { - loadHeatmap: function(userName) { - const self = this; - $.get(this.suburl + '/api/v1/users/' + userName + '/heatmap', function(chartRawData) { - const chartData = []; - for (let i = 0; i < chartRawData.length; i++) { - self.totalContributions += chartRawData[i].contributions; - chartData[i] = { date: new Date(chartRawData[i].timestamp * 1000), count: chartRawData[i].contributions }; - } - self.values = chartData; - self.isLoading = false; - }); - }, - - getColor: function(idx) { - const el = document.createElement('div'); - el.className = 'heatmap-color-' + idx; - document.body.appendChild(el); - - const color = getComputedStyle(el).backgroundColor; - - document.body.removeChild(el); - - return color; - } - }, - - template: '

    total contributions in the last 12 months

    ' - }); - - new Vue({ - delimiters: vueDelimeters, - el: el, - - data: { - suburl: document.querySelector('meta[name=_suburl]').content, - heatmapUser: heatmapUser, - locale: locale - }, - }); -} - -function initFilterBranchTagDropdown(selector) { - $(selector).each(function() { - const $dropdown = $(this); - const $data = $dropdown.find('.data'); - const data = { - items: [], - mode: $data.data('mode'), - searchTerm: '', - noResults: '', - canCreateBranch: false, - menuVisible: false, - active: 0 - }; - $data.find('.item').each(function() { - data.items.push({ - name: $(this).text(), - url: $(this).data('url'), - branch: $(this).hasClass('branch'), - tag: $(this).hasClass('tag'), - selected: $(this).hasClass('selected') - }); - }); - $data.remove(); - new Vue({ - delimiters: ['${', '}'], - el: this, - data: data, - - beforeMount: function () { - const vm = this; - - this.noResults = vm.$el.getAttribute('data-no-results'); - this.canCreateBranch = vm.$el.getAttribute('data-can-create-branch') === 'true'; - - document.body.addEventListener('click', function(event) { - if (vm.$el.contains(event.target)) { - return; - } - if (vm.menuVisible) { - Vue.set(vm, 'menuVisible', false); - } - }); - }, - - watch: { - menuVisible: function(visible) { - if (visible) { - this.focusSearchField(); - } - } - }, - - computed: { - filteredItems: function() { - const vm = this; - - const items = vm.items.filter(function (item) { - return ((vm.mode === 'branches' && item.branch) - || (vm.mode === 'tags' && item.tag)) - && (!vm.searchTerm - || item.name.toLowerCase().indexOf(vm.searchTerm.toLowerCase()) >= 0); - }); - - vm.active = (items.length === 0 && vm.showCreateNewBranch ? 0 : -1); - - return items; - }, - showNoResults: function() { - return this.filteredItems.length === 0 - && !this.showCreateNewBranch; - }, - showCreateNewBranch: function() { - const vm = this; - if (!this.canCreateBranch || !vm.searchTerm || vm.mode === 'tags') { - return false; - } - - return vm.items.filter(function (item) { - return item.name.toLowerCase() === vm.searchTerm.toLowerCase() - }).length === 0; - } - }, - - methods: { - selectItem: function(item) { - const prev = this.getSelected(); - if (prev !== null) { - prev.selected = false; - } - item.selected = true; - window.location.href = item.url; - }, - createNewBranch: function() { - if (!this.showCreateNewBranch) { - return; - } - this.$refs.newBranchForm.submit(); - }, - focusSearchField: function() { - const vm = this; - Vue.nextTick(function() { - vm.$refs.searchField.focus(); - }); - }, - getSelected: function() { - for (let i = 0, j = this.items.length; i < j; ++i) { - if (this.items[i].selected) - return this.items[i]; - } - return null; - }, - getSelectedIndexInFiltered: function() { - for (let i = 0, j = this.filteredItems.length; i < j; ++i) { - if (this.filteredItems[i].selected) - return i; - } - return -1; - }, - scrollToActive: function() { - let el = this.$refs['listItem' + this.active]; - if (!el || el.length === 0) { - return; - } - if (Array.isArray(el)) { - el = el[0]; - } - - const cont = this.$refs.scrollContainer; - - if (el.offsetTop < cont.scrollTop) { - cont.scrollTop = el.offsetTop; - } - else if (el.offsetTop + el.clientHeight > cont.scrollTop + cont.clientHeight) { - cont.scrollTop = el.offsetTop + el.clientHeight - cont.clientHeight; - } - }, - keydown: function(event) { - const vm = this; - if (event.keyCode === 40) { - // arrow down - event.preventDefault(); - - if (vm.active === -1) { - vm.active = vm.getSelectedIndexInFiltered(); - } - - if (vm.active + (vm.showCreateNewBranch ? 0 : 1) >= vm.filteredItems.length) { - return; - } - vm.active++; - vm.scrollToActive(); - } - if (event.keyCode === 38) { - // arrow up - event.preventDefault(); - - if (vm.active === -1) { - vm.active = vm.getSelectedIndexInFiltered(); - } - - if (vm.active <= 0) { - return; - } - vm.active--; - vm.scrollToActive(); - } - if (event.keyCode == 13) { - // enter - event.preventDefault(); - - if (vm.active >= vm.filteredItems.length) { - vm.createNewBranch(); - } else if (vm.active >= 0) { - vm.selectItem(vm.filteredItems[vm.active]); - } - } - if (event.keyCode == 27) { - // escape - event.preventDefault(); - vm.menuVisible = false; - } - } - } - }); - }); -} - -$(".commit-button").click(function(e) { - e.preventDefault(); - $(this).parent().find('.commit-body').toggle(); -}); - -function initNavbarContentToggle() { - const content = $('#navbar'); - const toggle = $('#navbar-expand-toggle'); - let isExpanded = false; - toggle.click(function() { - isExpanded = !isExpanded; - if (isExpanded) { - content.addClass('shown'); - toggle.addClass('active'); - } - else { - content.removeClass('shown'); - toggle.removeClass('active'); - } - }); -} - -function initTopicbar() { - const mgrBtn = $("#manage_topic"); - const editDiv = $("#topic_edit"); - const viewDiv = $("#repo-topics"); - const saveBtn = $("#save_topic"); - const topicDropdown = $('#topic_edit .dropdown'); - const topicForm = $('#topic_edit.ui.form'); - const topicPrompts = getPrompts(); - - mgrBtn.click(function() { - viewDiv.hide(); - editDiv.css('display', ''); // show Semantic UI Grid - }); - - function getPrompts() { - const hidePrompt = $("div.hide#validate_prompt"), - prompts = { - countPrompt: hidePrompt.children('#count_prompt').text(), - formatPrompt: hidePrompt.children('#format_prompt').text() - }; - hidePrompt.remove(); - return prompts; - } - - saveBtn.click(function() { - const topics = $("input[name=topics]").val(); - - $.post(saveBtn.data('link'), { - "_csrf": csrf, - "topics": topics - }, function(_data, _textStatus, xhr){ - if (xhr.responseJSON.status === 'ok') { - viewDiv.children(".topic").remove(); - if (topics.length) { - const topicArray = topics.split(","); - - const last = viewDiv.children("a").last(); - for (let i=0; i < topicArray.length; i++) { - $('
    '+topicArray[i]+'
    ').insertBefore(last) - } - } - editDiv.css('display', 'none'); - viewDiv.show(); - } - }).fail(function(xhr){ - if (xhr.status === 422) { - if (xhr.responseJSON.invalidTopics.length > 0) { - topicPrompts.formatPrompt = xhr.responseJSON.message; - - const invalidTopics = xhr.responseJSON.invalidTopics, - topicLables = topicDropdown.children('a.ui.label'); - - topics.split(',').forEach(function(value, index) { - for (let i=0; i < invalidTopics.length; i++) { - if (invalidTopics[i] === value) { - topicLables.eq(index).removeClass("green").addClass("red"); - } - } - }); - } else { - topicPrompts.countPrompt = xhr.responseJSON.message; - } - } - }).always(function() { - topicForm.form('validate form'); - }); - }); - - topicDropdown.dropdown({ - allowAdditions: true, - forceSelection: false, - fields: { name: "description", value: "data-value" }, - saveRemoteData: false, - label: { - transition : 'horizontal flip', - duration : 200, - variation : false, - blue : true, - basic: true, - }, - className: { - label: 'ui small label' - }, - apiSettings: { - url: suburl + '/api/v1/topics/search?q={query}', - throttle: 500, - cache: false, - onResponse: function(res) { - const formattedResponse = { - success: false, - results: [], - }; - const stripTags = function (text) { - return text.replace(/<[^>]*>?/gm, ""); - }; - - const query = stripTags(this.urlData.query.trim()); - let found_query = false; - const current_topics = []; - topicDropdown.find('div.label.visible.topic,a.label.visible').each(function(_,e){ current_topics.push(e.dataset.value); }); - - if (res.topics) { - let found = false; - for (let i=0;i < res.topics.length;i++) { - // skip currently added tags - if (current_topics.indexOf(res.topics[i].topic_name) != -1){ - continue; - } - - if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()){ - found_query = true; - } - formattedResponse.results.push({"description": res.topics[i].topic_name, "data-value": res.topics[i].topic_name}); - found = true; - } - formattedResponse.success = found; - } - - if (query.length > 0 && !found_query){ - formattedResponse.success = true; - formattedResponse.results.unshift({"description": query, "data-value": query}); - } else if (query.length > 0 && found_query) { - formattedResponse.results.sort(function(a, b){ - if (a.description.toLowerCase() === query.toLowerCase()) return -1; - if (b.description.toLowerCase() === query.toLowerCase()) return 1; - if (a.description > b.description) return -1; - if (a.description < b.description) return 1; - return 0; - }); - } - - - return formattedResponse; - }, - }, - onLabelCreate: function(value) { - value = value.toLowerCase().trim(); - this.attr("data-value", value).contents().first().replaceWith(value); - return $(this); - }, - onAdd: function(addedValue, _addedText, $addedChoice) { - addedValue = addedValue.toLowerCase().trim(); - $($addedChoice).attr('data-value', addedValue); - $($addedChoice).attr('data-text', addedValue); - } - }); - - $.fn.form.settings.rules.validateTopic = function(_values, regExp) { - const topics = topicDropdown.children('a.ui.label'), - status = topics.length === 0 || topics.last().attr("data-value").match(regExp); - if (!status) { - topics.last().removeClass("green").addClass("red"); - } - return status && topicDropdown.children('a.ui.label.red').length === 0; - }; - - topicForm.form({ - on: 'change', - inline : true, - fields: { - topics: { - identifier: 'topics', - rules: [ - { - type: 'validateTopic', - value: /^[a-z0-9][a-z0-9-]{1,35}$/, - prompt: topicPrompts.formatPrompt - }, - { - type: 'maxCount[25]', - prompt: topicPrompts.countPrompt - } - ] - }, - } - }); -} -function toggleDeadlineForm() { - $('#deadlineForm').fadeToggle(150); -} - -function setDeadline() { - const deadline = $('#deadlineDate').val(); - updateDeadline(deadline); -} - -function updateDeadline(deadlineString) { - $('#deadline-err-invalid-date').hide(); - $('#deadline-loader').addClass('loading'); - - let realDeadline = null; - if (deadlineString !== '') { - - const newDate = Date.parse(deadlineString) - - if (isNaN(newDate)) { - $('#deadline-loader').removeClass('loading'); - $('#deadline-err-invalid-date').show(); - return false; - } - realDeadline = new Date(newDate); - } - - $.ajax($('#update-issue-deadline-form').attr('action') + '/deadline', { - data: JSON.stringify({ - 'due_date': realDeadline, - }), - headers: { - 'X-Csrf-Token': csrf, - 'X-Remote': true, - }, - contentType: 'application/json', - type: 'POST', - success: function () { - reload(); - }, - error: function () { - $('#deadline-loader').removeClass('loading'); - $('#deadline-err-invalid-date').show(); - } - }); -} - -function deleteDependencyModal(id, type) { - $('.remove-dependency') - .modal({ - closable: false, - duration: 200, - onApprove: function () { - $('#removeDependencyID').val(id); - $('#dependencyType').val(type); - $('#removeDependencyForm').submit(); - } - }).modal('show') - ; -} - -function initIssueList() { - const repolink = $('#repolink').val(); - const repoId = $('#repoId').val(); - const crossRepoSearch = $('#crossRepoSearch').val(); - let issueSearchUrl = suburl + '/api/v1/repos/' + repolink + '/issues?q={query}'; - if (crossRepoSearch === 'true') { - issueSearchUrl = suburl + '/api/v1/repos/issues/search?q={query}&priority_repo_id=' + repoId; - } - $('#new-dependency-drop-list') - .dropdown({ - apiSettings: { - url: issueSearchUrl, - onResponse: function(response) { - const filteredResponse = {'success': true, 'results': []}; - const currIssueId = $('#new-dependency-drop-list').data('issue-id'); - // Parse the response from the api to work with our dropdown - $.each(response, function(_i, issue) { - // Don't list current issue in the dependency list. - if(issue.id === currIssueId) { - return; - } - filteredResponse.results.push({ - 'name' : '#' + issue.number + ' ' + htmlEncode(issue.title) + - '
    ' + htmlEncode(issue.repository.full_name) + '
    ', - 'value' : issue.id - }); - }); - return filteredResponse; - }, - cache: false, - }, - - fullTextSearch: true - }); - - $(".menu a.label-filter-item").each(function() { - $(this).click(function(e) { - if (e.altKey) { - e.preventDefault(); - - const href = $(this).attr("href"); - const id = $(this).data("label-id"); - - const regStr = "labels=(-?[0-9]+%2c)*(" + id + ")(%2c-?[0-9]+)*&"; - const newStr = "labels=$1-$2$3&"; - - window.location = href.replace(new RegExp(regStr), newStr); - } - }); - }); - - $(".menu .ui.dropdown.label-filter").keydown(function(e) { - if (e.altKey && e.keyCode == 13) { - const selectedItems = $(".menu .ui.dropdown.label-filter .menu .item.selected"); - - if (selectedItems.length > 0) { - const item = $(selectedItems[0]); - - const href = item.attr("href"); - const id = item.data("label-id"); - - const regStr = "labels=(-?[0-9]+%2c)*(" + id + ")(%2c-?[0-9]+)*&"; - const newStr = "labels=$1-$2$3&"; - - window.location = href.replace(new RegExp(regStr), newStr); - } - } - }); -} -function cancelCodeComment(btn) { - const form = $(btn).closest("form"); - if(form.length > 0 && form.hasClass('comment-form')) { - form.addClass('hide'); - form.parent().find('button.comment-form-reply').show(); - } else { - form.closest('.comment-code-cloud').remove() - } -} -function onOAuthLoginClick() { - const oauthLoader = $('#oauth2-login-loader'); - const oauthNav = $('#oauth2-login-navigator'); - - oauthNav.hide(); - oauthLoader.removeClass('disabled'); - - setTimeout(function(){ - // recover previous content to let user try again - // usually redirection will be performed before this action - oauthLoader.addClass('disabled'); - oauthNav.show(); - },5000); -} diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 51a5cceeb9646..4d23f4b9fed10 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -355,7 +355,7 @@ func retrieveProjects(ctx *context.Context, repo *models.Repository) { ctx.Data["OpenProjects"], err = models.GetProjects(models.ProjectSearchOptions{ RepoID: repo.ID, Page: -1, - IsClosed: util.OptionalBoolTrue, + IsClosed: util.OptionalBoolFalse, Type: models.RepositoryType, }) if err != nil { @@ -366,7 +366,7 @@ func retrieveProjects(ctx *context.Context, repo *models.Repository) { ctx.Data["ClosedProjects"], err = models.GetProjects(models.ProjectSearchOptions{ RepoID: repo.ID, Page: -1, - IsClosed: util.OptionalBoolFalse, + IsClosed: util.OptionalBoolTrue, Type: models.RepositoryType, }) if err != nil { @@ -569,7 +569,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b valid, err := models.CanBeAssigned(assignee, repo, isPull) if err != nil { - ctx.ServerError("GetUserRepoPermission", err) + ctx.ServerError("CanBeAssigned", err) return nil, nil, 0, 0 } @@ -672,6 +672,7 @@ func getBranchData(ctx *context.Context, issue *models.Issue) { ctx.Data["BaseBranch"] = pull.BaseBranch ctx.Data["HeadBranch"] = pull.HeadBranch ctx.Data["HeadUserName"] = pull.MustHeadUserName() + ctx.Data["RequireSimpleMDE"] = true } } diff --git a/routers/user/profile.go b/routers/user/profile.go index 5aea7f37c50d6..4e8eb7f50c712 100644 --- a/routers/user/profile.go +++ b/routers/user/profile.go @@ -191,7 +191,7 @@ func Profile(ctx *context.Context) { total = int(count) case "projects": - ctx.Data["ClosedProjects"], err = models.GetProjects(models.ProjectSearchOptions{ + ctx.Data["OpenProjects"], err = models.GetProjects(models.ProjectSearchOptions{ // RepoID: repo.ID, Page: -1, // IsClosed: uti, diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index cec08738bb71b..05fe447585448 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -107,7 +107,6 @@
    -
    From 338e8bb11ecd92659506cc5e83722c812acacb70 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 8 Feb 2020 11:38:40 +0100 Subject: [PATCH 058/216] Kanban board suggestions (#2) * 6543 suggestions * fix --- .eslintrc | 1 - models/issue.go | 23 +++++++++++------------ models/migrations/migrations.go | 2 +- models/migrations/v104.go | 2 +- models/migrations/v106.go | 1 + models/migrations/v112.go | 2 +- models/migrations/v96.go | 3 ++- modules/setting/repository.go | 11 +++++------ routers/repo/issue.go | 2 +- routers/user/profile.go | 7 +++---- 10 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.eslintrc b/.eslintrc index 02abbb87d4433..8af8099ff6b54 100644 --- a/.eslintrc +++ b/.eslintrc @@ -22,7 +22,6 @@ globals: hljs: false SimpleMDE: false u2fApi: false - hljs: false Sortable: false Vue: false diff --git a/models/issue.go b/models/issue.go index 70eff97d920c0..0ba59aa677a2f 100644 --- a/models/issue.go +++ b/models/issue.go @@ -43,18 +43,17 @@ type Issue struct { MilestoneID int64 `xorm:"INDEX"` Milestone *Milestone `xorm:"-"` ProjectID int64 `xorm:"INDEX"` - // If 0, then it has not been added to a specific board in the project - ProjectBoardID int64 `xorm:"INDEX"` - Project *Project `xorm:"-"` - Priority int - AssigneeID int64 `xorm:"-"` - Assignee *User `xorm:"-"` - IsClosed bool `xorm:"INDEX"` - IsRead bool `xorm:"-"` - IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. - PullRequest *PullRequest `xorm:"-"` - NumComments int - Ref string + ProjectBoardID int64 `xorm:"INDEX"` // If 0, then it has not been added to a specific board in the project + Project *Project `xorm:"-"` + Priority int + AssigneeID int64 `xorm:"-"` + Assignee *User `xorm:"-"` + IsClosed bool `xorm:"INDEX"` + IsRead bool `xorm:"-"` + IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not. + PullRequest *PullRequest `xorm:"-"` + NumComments int + Ref string DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 21c47ad446dbc..e0d1bb9a237bc 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -294,7 +294,7 @@ var migrations = []Migration{ NewMigration("Add commit id and stale to reviews", addReviewCommitAndStale), // v119 -> v120 NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType), - // v120 - v121 + // v120 -> v121 NewMigration("Add owner_name on table repository", addOwnerNameOnRepository), // v120 -> v121 NewMigration("add projects info to repository table", addProjectsInfo), diff --git a/models/migrations/v104.go b/models/migrations/v104.go index feab8d422a88f..f3ec3c88c825b 100644 --- a/models/migrations/v104.go +++ b/models/migrations/v104.go @@ -1,6 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. + package migrations import ( @@ -29,6 +30,5 @@ func removeLabelUneededCols(x *xorm.Engine) error { if err := dropTableColumns(sess, "label", "is_selected"); err != nil { return err } - return sess.Commit() } diff --git a/models/migrations/v106.go b/models/migrations/v106.go index c4ae3f907a2a4..201fc10266a00 100644 --- a/models/migrations/v106.go +++ b/models/migrations/v106.go @@ -1,6 +1,7 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. + package migrations import ( diff --git a/models/migrations/v112.go b/models/migrations/v112.go index d64c7d3f9a3c4..2c4f659675c05 100644 --- a/models/migrations/v112.go +++ b/models/migrations/v112.go @@ -1,13 +1,13 @@ // Copyright 2019 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. + package migrations import ( "os" "code.gitea.io/gitea/models" - "xorm.io/builder" "xorm.io/xorm" ) diff --git a/models/migrations/v96.go b/models/migrations/v96.go index a59c4660e168a..b8eb201591399 100644 --- a/models/migrations/v96.go +++ b/models/migrations/v96.go @@ -36,9 +36,10 @@ func deleteOrphanedAttachments(x *xorm.Engine) error { return err } - _, err := x.ID(attachment.ID).NoAutoCondition().Delete(attachment) + _, err := sess.ID(attachment.ID).NoAutoCondition().Delete(attachment) return err }) + if err != nil { return err } diff --git a/modules/setting/repository.go b/modules/setting/repository.go index c3789f74f35dc..e1ae2cf52e8df 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -35,12 +35,11 @@ var ( AccessControlAllowOrigin string UseCompatSSHURI bool DefaultCloseIssuesViaCommitsInAnyBranch bool - - EnableKanbanBoard bool - ProjectBoardBasicKanbanType []string - ProjectBoardBugTriageType []string - EnablePushCreateUser bool - EnablePushCreateOrg bool + EnablePushCreateUser bool + EnablePushCreateOrg bool + EnableKanbanBoard bool + ProjectBoardBasicKanbanType []string + ProjectBoardBugTriageType []string // Repository editor settings Editor struct { diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 4d23f4b9fed10..64cd3a34af083 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -672,7 +672,6 @@ func getBranchData(ctx *context.Context, issue *models.Issue) { ctx.Data["BaseBranch"] = pull.BaseBranch ctx.Data["HeadBranch"] = pull.HeadBranch ctx.Data["HeadUserName"] = pull.MustHeadUserName() - ctx.Data["RequireSimpleMDE"] = true } } @@ -731,6 +730,7 @@ func ViewIssue(ctx *context.Context) { ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireDropzone"] = true ctx.Data["RequireTribute"] = true + ctx.Data["RequireSimpleMDE"] = true ctx.Data["IsProjectsEnabled"] = setting.Repository.EnableKanbanBoard renderAttachmentSettings(ctx) diff --git a/routers/user/profile.go b/routers/user/profile.go index 4e8eb7f50c712..eb45ab9028bab 100644 --- a/routers/user/profile.go +++ b/routers/user/profile.go @@ -192,10 +192,9 @@ func Profile(ctx *context.Context) { case "projects": ctx.Data["OpenProjects"], err = models.GetProjects(models.ProjectSearchOptions{ - // RepoID: repo.ID, - Page: -1, - // IsClosed: uti, - Type: models.IndividualType, + Page: -1, + IsClosed: util.OptionalBoolFalse, + Type: models.IndividualType, }) default: repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ From 1b86d3d4b6d031946ce4d7ef16e45effec7bc7ee Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 8 Feb 2020 23:42:50 +0100 Subject: [PATCH 059/216] fix dorpdown & comments (#3) * fix dorpdown menue * fix project-change-comments --- options/locale/locale_en-US.ini | 1 + templates/repo/issue/view_content/comments.tmpl | 6 +++--- web_src/js/index.js | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 9ca81dd5b572c..1cff5b3c3b50e 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -855,6 +855,7 @@ issues.label_templates.fail_to_load_file = Failed to load label template file '% issues.add_label_at = added the
    %s
    label %s issues.remove_label_at = removed the
    %s
    label %s issues.add_milestone_at = `added this to the %s milestone %s` +issues.add_project_at = `added this to the %s project %s` issues.change_milestone_at = `modified the milestone from %s to %s %s` issues.change_project_at = `modified the project from %s to %s %s` issues.remove_milestone_at = `removed this from the %s milestone %s` diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 13a89c9af0c41..a5356e6ccb2fa 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -7,7 +7,7 @@ 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, 18 = REMOVED_DEADLINE, 19 = ADD_DEPENDENCY, 20 = REMOVE_DEPENDENCY, 21 = CODE, 22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED, 25 = TARGET_BRANCH_CHANGED, - 26 = DELETE_TIME_MANUAL, 27 = PROJECT BOARD CHANGED --> + 26 = DELETE_TIME_MANUAL, 27 = PROJECT_CHANGED, 28 = Project_Board_CHANGED --> {{if eq .Type 0}}
    {{if .OriginalAuthor }} @@ -448,10 +448,10 @@ {{$.i18n.Tr "repo.issues.change_project_at" (.OldProject.Title|Escape) (.Project.Title|Escape) $createdStr | Safe}} {{else}} - {{$.i18n.Tr "repo.issues.remove_milestone_at" (.OldProject.Title|Escape) $createdStr | Safe}} + {{$.i18n.Tr "repo.issues.remove_project_at" (.OldProject.Title|Escape) $createdStr | Safe}} {{end}} {{else if gt .ProjectID 0}} - {{$.i18n.Tr "repo.issues.add_milestone_at" (.Project.Title|Escape) $createdStr | Safe}} + {{$.i18n.Tr "repo.issues.add_project_at" (.Project.Title|Escape) $createdStr | Safe}} {{end}}
    diff --git a/web_src/js/index.js b/web_src/js/index.js index b701da08b1432..ca2d32ece8ef1 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -509,7 +509,8 @@ function initCommentForm() { }); } - // Milestone and assignee + // Milestone, Assignee, Project + selectItem('.select-project', '#project_id'); selectItem('.select-milestone', '#milestone_id'); selectItem('.select-assignee', '#assignee_id'); } From 4cd0b75c9aaef2dfd1914f52d714517a918beb3d Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sat, 8 Feb 2020 23:52:37 +0100 Subject: [PATCH 060/216] fix build --- routers/routes/routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 3d4be2af52316..d76f295f13c84 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -607,7 +607,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/create", bindIgnErr(auth.UserCreateProjectForm{}), repo.CreateProjectPost) }, repo.MustEnableProjects, func(ctx *context.Context) { - if err := ctx.User.GetOrganizations(true); err != nil { + if err := ctx.User.GetOrganizations(&models.SearchOrganizationsOptions{All: true}); err != nil { ctx.ServerError("GetOrganizations", err) return } From 767a31e0f8ca168434f756ae42a7a896eb4f00f1 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 9 Feb 2020 08:25:31 +0100 Subject: [PATCH 061/216] Kanban fix2 (#4) * fix migrations * update settings sample * fix lint * remove merge-conflict relict --- custom/conf/app.ini.sample | 7 +++++-- models/error.go | 1 + models/issue.go | 2 -- models/migrations/migrations.go | 14 +++++++------- models/projects.go | 11 +++++------ routers/repo/issue.go | 2 -- routers/repo/projects.go | 1 + 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 48a5f20c5a981..1f96ce28e995a 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -51,6 +51,11 @@ DISABLED_REPO_UNITS = DEFAULT_REPO_UNITS = repo.code,repo.releases,repo.issues,repo.pulls,repo.wiki ; Prefix archive files by placing them in a directory named after the repository PREFIX_ARCHIVE_FILES = true +; Enable the kanban board feature system wide. +ENABLE_KANBAN_BOARD = true +; Default templates for kanban borards +PROJECT_BOARD_BASIC_KANBAN_TYPE = Todo, In progress, Done +PROJECT_BOARD_BUG_TRIAGE_TYPE = Needs Triage, High priority, Low priority, Closed [repository.editor] ; List of file extensions for which lines should be wrapped in the CodeMirror editor @@ -432,8 +437,6 @@ BOOST_WORKERS = 5 [admin] ; Disallow regular (non-admin) users from creating organizations. DISABLE_REGULAR_ORG_CREATION = false -; Enable the kanban board feature system wide. -ENABLE_KANBAN_BOARD = true ; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled DEFAULT_EMAIL_NOTIFICATIONS = enabled diff --git a/models/error.go b/models/error.go index ce27fc342dbb1..6e51be15a85f1 100644 --- a/models/error.go +++ b/models/error.go @@ -1525,6 +1525,7 @@ func (err ErrProjectNotExist) Error() string { return fmt.Sprintf("projects does not exist [id: %d, repo_id: %d]", err.ID, err.RepoID) } +// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error. type ErrProjectBoardNotExist struct { BoardID int64 RepoID int64 diff --git a/models/issue.go b/models/issue.go index 4fe3f5ad7ee78..c473664df2f32 100644 --- a/models/issue.go +++ b/models/issue.go @@ -1121,8 +1121,6 @@ type IssuesOptions struct { MentionedID int64 MilestoneID int64 ProjectID int64 - Page int - PageSize int IsClosed util.OptionalBool IsPull util.OptionalBool LabelIDs []int64 diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 910067612c161..8ea81ad354f9c 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -174,19 +174,19 @@ var migrations = []Migration{ NewMigration("Fix migrated repositories' git service type", fixMigratedRepositoryServiceType), // v120 -> v121 NewMigration("Add owner_name on table repository", addOwnerNameOnRepository), - // v120 -> v121 - NewMigration("add is_restricted column for users table", addIsRestricted), // v121 -> v122 - NewMigration("Add Require Signed Commits to ProtectedBranch", addRequireSignedCommits), + NewMigration("add is_restricted column for users table", addIsRestricted), // v122 -> v123 - NewMigration("Add original informations for reactions", addReactionOriginals), + NewMigration("Add Require Signed Commits to ProtectedBranch", addRequireSignedCommits), // v123 -> v124 - NewMigration("Add columns to user and repository", addUserRepoMissingColumns), + NewMigration("Add original informations for reactions", addReactionOriginals), // v124 -> v125 - NewMigration("Add some columns on review for migration", addReviewMigrateInfo), + NewMigration("Add columns to user and repository", addUserRepoMissingColumns), // v125 -> v126 - NewMigration("Fix topic repository count", fixTopicRepositoryCount), + NewMigration("Add some columns on review for migration", addReviewMigrateInfo), // v126 -> v127 + NewMigration("Fix topic repository count", fixTopicRepositoryCount), + // v127 -> v128 NewMigration("add projects info to repository table", addProjectsInfo), } diff --git a/models/projects.go b/models/projects.go index ab27d93b1c321..4fb355b9cf00c 100644 --- a/models/projects.go +++ b/models/projects.go @@ -120,12 +120,12 @@ type Project struct { ClosedDateUnix timeutil.TimeStamp } -// AfterLoad is invoked from XORM after setting the value of a field of -// this object. +// AfterLoad is invoked from XORM after setting the value of a field of this object. func (p *Project) AfterLoad() { p.NumOpenIssues = p.NumIssues - p.NumClosedIssues } +// ProjectSearchOptions are options for GetProjects type ProjectSearchOptions struct { RepoID int64 Page int @@ -134,8 +134,7 @@ type ProjectSearchOptions struct { Type ProjectType } -// GetProjects returns a list of all projects that have been created in the -// repository +// GetProjects returns a list of all projects that have been created in the repository func GetProjects(opts ProjectSearchOptions) ([]*Project, error) { projects := make([]*Project, 0, setting.UI.IssuePagingNum) @@ -286,7 +285,7 @@ func countRepoClosedProjects(e Engine, repoID int64) (int64, error) { Count(new(Project)) } -// ChangeProjectStatus togggles a project between opened and closed +// ChangeProjectStatus toggle a project between opened and closed func ChangeProjectStatus(p *Project, isClosed bool) error { repo, err := GetRepositoryByID(p.RepoID) @@ -443,7 +442,7 @@ func changeProjectAssign(sess *xorm.Session, doer *User, issue *Issue, oldProjec return updateIssueCols(sess, issue, "project_id") } -// MoveIsssueAcrossProjectBoards move a card from one board to another +// MoveIssueAcrossProjectBoards move a card from one board to another func MoveIssueAcrossProjectBoards(issue *Issue, board *ProjectBoard) error { sess := x.NewSession() diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 50544de342bb7..83d553890da05 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -205,8 +205,6 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti MentionedID: mentionedID, MilestoneID: milestoneID, ProjectID: projectID, - Page: pager.Paginater.Current(), - PageSize: setting.UI.IssuePagingNum, IsClosed: util.OptionalBoolOf(isShowClosed), IsPull: isPullOption, LabelIDs: labelIDs, diff --git a/routers/repo/projects.go b/routers/repo/projects.go index f3ea72b773e5f..f379b45abe99a 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -26,6 +26,7 @@ const ( projectTemplateKey = "ProjectTemplate" ) +// MustEnableProjects check if projects are enabled in settings func MustEnableProjects(ctx *context.Context) { if !setting.Repository.EnableKanbanBoard { From 2b6f4b66470cfaa38035e9c0e1cfcf8818486577 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 9 Feb 2020 13:41:39 +0100 Subject: [PATCH 062/216] update migration to add projects board unit --- models/migrations/v127.go | 40 +++++++++++++++++++++++++++++++++++++++ models/unit.go | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/models/migrations/v127.go b/models/migrations/v127.go index a154c6ffe44bb..1c84cc18cbc13 100644 --- a/models/migrations/v127.go +++ b/models/migrations/v127.go @@ -7,6 +7,7 @@ package migrations import ( "code.gitea.io/gitea/modules/timeutil" + "xorm.io/core" "xorm.io/xorm" ) @@ -15,6 +16,10 @@ func addProjectsInfo(x *xorm.Engine) error { sess := x.NewSession() defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + type ( ProjectType uint8 ProjectBoardType uint8 @@ -52,6 +57,7 @@ func addProjectsInfo(x *xorm.Engine) error { } type Repository struct { + ID int64 NumProjects int `xorm:"NOT NULL DEFAULT 0"` NumClosedProjects int `xorm:"NOT NULL DEFAULT 0"` NumOpenProjects int `xorm:"-"` @@ -87,5 +93,39 @@ func addProjectsInfo(x *xorm.Engine) error { return err } + type RepoUnit struct { + ID int64 + RepoID int64 `xorm:"INDEX(s)"` + Type int `xorm:"INDEX(s)"` + Config core.Conversion `xorm:"TEXT"` + CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"` + } + + const batchSize = 100 + + const unitTypeProject int = 8 // see UnitTypeProjects in models/units.go + + for start := 0; ; start += batchSize { + repos := make([]*Repository, 0, batchSize) + + if err := sess.Limit(batchSize, start).Find(&repos); err != nil { + return err + } + + if len(repos) == 0 { + break + } + + for _, r := range repos { + if _, err := sess.ID(r.ID).Insert(&RepoUnit{ + RepoID: r.ID, + Type: unitTypeProject, + CreatedUnix: timeutil.TimeStampNow(), + }); err != nil { + return err + } + } + } + return sess.Commit() } diff --git a/models/unit.go b/models/unit.go index 6f9fb68ee5aba..939deba574824 100644 --- a/models/unit.go +++ b/models/unit.go @@ -24,7 +24,7 @@ const ( UnitTypeWiki // 5 Wiki UnitTypeExternalWiki // 6 ExternalWiki UnitTypeExternalTracker // 7 ExternalTracker - UnitTypeProjects + UnitTypeProjects // 8 Kanban board ) // Value returns integer value for unit type From e24f63beb5766ba2232379379f1b32fb16be64a4 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 16 Feb 2020 00:29:32 +0100 Subject: [PATCH 063/216] Add sortablejs package --- package-lock.json | 5 +++++ package.json | 1 + 2 files changed, 6 insertions(+) diff --git a/package-lock.json b/package-lock.json index 3ca1b0629ea77..081bf6437d54d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12862,6 +12862,11 @@ "is-plain-obj": "^1.0.0" } }, + "sortablejs": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz", + "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==" + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", diff --git a/package.json b/package.json index 7f30cb9c240bc..e62c657e5b713 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "jquery": "3.4.1", "jquery-migrate": "3.1.0", "jquery.are-you-sure": "1.9.0", + "sortablejs": "1.10.2", "swagger-ui": "3.25.0", "vue-bar-graph": "1.2.0" }, From 305fb3f8de7a1ff24e39e3c35a593fad112d3989 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 16 Feb 2020 02:05:03 +0100 Subject: [PATCH 064/216] adapt js to new standard --- models/migrations/migrations.go | 6 ++---- templates/base/footer.tmpl | 6 +++--- web_src/js/index.js | 27 ++++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 5d680cd79af5e..b20bd05867139 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -187,11 +187,9 @@ var migrations = []Migration{ // v126 -> v127 NewMigration("Fix topic repository count", fixTopicRepositoryCount), // v127 -> v128 -<<<<<<< HEAD - NewMigration("add projects info to repository table", addProjectsInfo), -======= NewMigration("add repository code language statistics", addLanguageStats), ->>>>>>> a97fe76950bf69ca71c9b790e8d0e76d5e870235 + // v128 -> v129 + NewMigration("add projects info to repository table", addProjectsInfo), } // Migrate database to current version diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index c889a02b4c14c..8c53fce2c9c98 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -110,7 +110,7 @@ - + {{if .EnableHeatmap}} @@ -119,10 +119,10 @@ {{end}} {{ if .PageIsProjects }} - + {{ end}} diff --git a/web_src/js/index.js b/web_src/js/index.js index 3e2dd79a0bca2..78f35e7073fc3 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -1,5 +1,5 @@ /* globals wipPrefixes, issuesTribute, emojiTribute */ -/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ +/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap, initKanbanBoard */ /* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ import 'jquery.are-you-sure'; @@ -2946,6 +2946,31 @@ window.cancelStopwatch = function () { $('#cancel_stopwatch_form').submit(); }; +window.initKanbanBoard = function (appElementId) { + const el = document.getElementById(appElementId); + if (!el) { + return; + } + + new Sortable(el, { + group: 'shared', + animation: 150, + onAdd: (e) => { + $.ajax(`${e.to.dataset.url}/${e.item.dataset.issue}`, { + headers: { + 'X-Csrf-Token': csrf, + 'X-Remote': true, + }, + contentType: 'application/json', + type: 'POST', + success: () => { + // setTimeout(reload(),3000) + }, + }); + }, + }); +}; + window.initHeatmap = function (appElementId, heatmapUser, locale) { const el = document.getElementById(appElementId); if (!el) { From 26695bbaf8be347c228652c7986d00f84f30c28e Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 16 Feb 2020 03:23:26 +0100 Subject: [PATCH 065/216] update board title --- models/projects.go | 6 +++++ modules/auth/repo_form.go | 6 +++++ routers/repo/projects.go | 44 ++++++++++++++++++++++++++++++- routers/routes/routes.go | 1 + templates/repo/projects/view.tmpl | 1 + 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/models/projects.go b/models/projects.go index 4fb355b9cf00c..4921acd6206d7 100644 --- a/models/projects.go +++ b/models/projects.go @@ -475,6 +475,12 @@ func GetProjectBoard(repoID, projectID, boardID int64) (*ProjectBoard, error) { return board, nil } +// UpdateProjectBoard updates the title of a project board +func UpdateProjectBoard(board *ProjectBoard) error { + _, err := x.ID(board.ID).AllCols().Update(board) + return err +} + // GetProjectBoards fetches all boards related to a project func GetProjectBoards(repoID, projectID int64) ([]ProjectBoard, error) { diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index a8e38eb9c665b..c91d65ca18ed4 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -420,6 +420,12 @@ type UserCreateProjectForm struct { UID int64 `binding:"Required"` } +// EditProjectBoardTitleForm is a form for editing the title of a project's +// board +type EditProjectBoardTitleForm struct { + Title string `binding:"Required;MaxSize(50)"` +} + // _____ .__.__ __ // / \ |__| | ____ _______/ |_ ____ ____ ____ // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ diff --git a/routers/repo/projects.go b/routers/repo/projects.go index f379b45abe99a..28bb9ed698f40 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -205,7 +205,7 @@ func EditProject(ctx *context.Context) { ctx.HTML(200, tplProjectsNew) } -// EditProjectPost response for edting a project +// EditProjectPost response for editing a project func EditProjectPost(ctx *context.Context, form auth.CreateProjectForm) { ctx.Data["Title"] = ctx.Tr("repo.projects.edit") ctx.Data["PageIsProjects"] = true @@ -344,6 +344,48 @@ func UpdateIssueProject(ctx *context.Context) { }) } +// EditProjectBoardTitle allows a project board's title to be updated +func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitleForm) { + + if ctx.User == nil { + ctx.JSON(403, map[string]string{ + "message": "Only signed in users are allowed to call make this action.", + }) + return + } + + if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) { + ctx.JSON(403, map[string]string{ + "message": "Only authorized users are allowed to call make this action.", + }) + return + } + + board, err := models.GetProjectBoard(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"), ctx.ParamsInt64(":boardID")) + if err != nil { + if models.IsErrProjectBoardNotExist(err) { + ctx.NotFound("", err) + } else { + ctx.ServerError("GetProjectBoard", err) + } + + return + } + + if form.Title != "" { + board.Title = form.Title + } + + if err := models.UpdateProjectBoard(board); err != nil { + ctx.ServerError("UpdateProjectBoard", err) + return + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + // MoveIssueAcrossBoards move a card from one board to another in a project func MoveIssueAcrossBoards(ctx *context.Context) { diff --git a/routers/routes/routes.go b/routers/routes/routes.go index e008cfb613867..7276e30e5f0b9 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -846,6 +846,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/:id/edit", repo.EditProject) m.Post("/delete", repo.DeleteProject) m.Post("/:id/:boardID/:index", repo.MoveIssueAcrossBoards) + m.Put("/:id/:boardID", bindIgnErr(auth.EditProjectBoardTitleForm{}, repo.EditProjectBoardTitle)) }, reqRepoProjectsReader, repo.MustEnableProjects) diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl index 11e6286ce96b8..838c2beba7285 100644 --- a/templates/repo/projects/view.tmpl +++ b/templates/repo/projects/view.tmpl @@ -26,6 +26,7 @@
    {{.Board.Title}}
    + {{svg "octicon-three-bars" 16}}
    From 6597f193c644c4b0c151366e998246ee5e3345e7 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 16 Feb 2020 03:50:50 +0100 Subject: [PATCH 066/216] delete project board --- models/projects.go | 26 ++++++++++++++++++++++++++ routers/repo/projects.go | 25 +++++++++++++++++++++++++ routers/routes/routes.go | 3 ++- 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/models/projects.go b/models/projects.go index 4921acd6206d7..2ed0661df445d 100644 --- a/models/projects.go +++ b/models/projects.go @@ -324,6 +324,32 @@ func ChangeProjectStatus(p *Project, isClosed bool) error { return sess.Commit() } +// DeleteProjectBoardByID removes all issues references to the project board. +func DeleteProjectBoardByID(repoID, projectID, boardID int64) error { + board, err := GetProjectBoard(repoID, projectID, boardID) + if err != nil { + if IsErrProjectBoardNotExist(err) { + return nil + } + + return err + } + + sess := x.NewSession() + defer sess.Close() + + if _, err := sess.ID(board.ID).Delete(board); err != nil { + return err + } + + if _, err := x.Exec("UPDATE `issue` SET project_board_id = 0 WHERE project_id = ? AND repo_id = ? AND project_board_id = ? ", + board.ProjectID, board.RepoID, board.ID); err != nil { + return err + } + + return sess.Commit() +} + // DeleteProjectByRepoID deletes a project from a repository. func DeleteProjectByRepoID(repoID, id int64) error { p, err := GetProjectByRepoID(repoID, id) diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 28bb9ed698f40..85c2a6f925d18 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -344,6 +344,31 @@ func UpdateIssueProject(ctx *context.Context) { }) } +func DeleteProjectBoard(ctx context.Context) { + if ctx.User == nil { + ctx.JSON(403, map[string]string{ + "message": "Only signed in users are allowed to call make this action.", + }) + return + } + + if !ctx.Repo.IsOwner() && !ctx.Repo.IsAdmin() && !ctx.Repo.CanAccess(models.AccessModeWrite, models.UnitTypeProjects) { + ctx.JSON(403, map[string]string{ + "message": "Only authorized users are allowed to call make this action.", + }) + return + } + + if err := models.DeleteProjectBoardByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"), ctx.ParamsInt64(":boardID")); err != nil { + ctx.ServerError("DeleteProjectBoardByID", err) + return + } + + ctx.JSON(200, map[string]interface{}{ + "ok": true, + }) +} + // EditProjectBoardTitle allows a project board's title to be updated func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitleForm) { diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 7276e30e5f0b9..d4eb9ad01c20f 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -846,7 +846,8 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/:id/edit", repo.EditProject) m.Post("/delete", repo.DeleteProject) m.Post("/:id/:boardID/:index", repo.MoveIssueAcrossBoards) - m.Put("/:id/:boardID", bindIgnErr(auth.EditProjectBoardTitleForm{}, repo.EditProjectBoardTitle)) + m.Combo("/:id/:boardID").Put(bindIgnErr(auth.EditProjectBoardTitleForm{}, repo.EditProjectBoardTitle)). + Delete(repo.DeleteProjectBoard) }, reqRepoProjectsReader, repo.MustEnableProjects) From 46e71fe046665a8a2f60101cc1c12fadc70eb2fd Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 23 Feb 2020 10:55:44 +0100 Subject: [PATCH 067/216] add a new project board via UI --- models/projects.go | 6 +++++ routers/repo/projects.go | 37 +++++++++++++++++++++++++++++++ routers/routes/routes.go | 1 + templates/repo/projects/view.tmpl | 2 +- 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/models/projects.go b/models/projects.go index 2ed0661df445d..b3117bf97b76b 100644 --- a/models/projects.go +++ b/models/projects.go @@ -324,6 +324,12 @@ func ChangeProjectStatus(p *Project, isClosed bool) error { return sess.Commit() } +// NewProjectBoard adds a new project board to a given project +func NewProjectBoard(board *ProjectBoard) error { + _, err := x.Insert(board) + return err +} + // DeleteProjectBoardByID removes all issues references to the project board. func DeleteProjectBoardByID(repoID, projectID, boardID int64) error { board, err := GetProjectBoard(repoID, projectID, boardID) diff --git a/routers/repo/projects.go b/routers/repo/projects.go index 85c2a6f925d18..eb20a7a00a1a6 100644 --- a/routers/repo/projects.go +++ b/routers/repo/projects.go @@ -344,6 +344,7 @@ func UpdateIssueProject(ctx *context.Context) { }) } +// DeleteProjectBoard allows for the deletion of a project board func DeleteProjectBoard(ctx context.Context) { if ctx.User == nil { ctx.JSON(403, map[string]string{ @@ -369,6 +370,42 @@ func DeleteProjectBoard(ctx context.Context) { }) } +// AddBoardToProject allows a new board to be added to a project. +func AddBoardToProject(ctx context.Context, form auth.EditProjectBoardTitleForm) { + + if ctx.HasError() { + ctx.HTML(200, tplProjectsNew) + return + } + + projectID := ctx.ParamsInt64(":id") + + _, err := models.GetProjectByRepoID(ctx.Repo.Repository.RepoID, projectID) + if err != nil { + + if models.IsErrProjectBoardNotExist(err) { + ctx.NotFound("", err) + } else { + ctx.ServerError("GetProjectBoard", err) + } + + return + } + + if err := models.NewProjectBoard(&models.ProjectBoard{ + ProjectID: projectID, + RepoID: ctx.Repo.Repository.RepoID, + Title: form.Title, + CreatorID: ctx.User.ID, + }); err != nil { + ctx.ServerError("NewProjectBoard", err) + return + } + + ctx.Flash.Success(ctx.Tr("repo.projects.create_success", form.Title)) + ctx.Redirect(ctx.Repo.RepoLink + "/projects") +} + // EditProjectBoardTitle allows a project board's title to be updated func EditProjectBoardTitle(ctx *context.Context, form auth.EditProjectBoardTitleForm) { diff --git a/routers/routes/routes.go b/routers/routes/routes.go index d4eb9ad01c20f..64a7e4654e5ab 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -848,6 +848,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/:id/:boardID/:index", repo.MoveIssueAcrossBoards) m.Combo("/:id/:boardID").Put(bindIgnErr(auth.EditProjectBoardTitleForm{}, repo.EditProjectBoardTitle)). Delete(repo.DeleteProjectBoard) + m.Post("/:id/boards", bindIgnErr(auth.EditProjectBoardTitleForm{}, repo.AddBoardToProject)) }, reqRepoProjectsReader, repo.MustEnableProjects) diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl index 838c2beba7285..b9e44e7e68947 100644 --- a/templates/repo/projects/view.tmpl +++ b/templates/repo/projects/view.tmpl @@ -26,7 +26,7 @@
    {{.Board.Title}}
    - {{svg "octicon-three-bars" 16}} + {{svg "octicon-lock" 16}}
    From 6bbb5d37501b8ffa726f26cb927cdb0ba07834d4 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 23 Feb 2020 12:33:27 +0100 Subject: [PATCH 068/216] fix lazy loading --- templates/base/footer.tmpl | 8 -------- templates/base/head.tmpl | 1 + web_src/js/features/projects.js | 36 +++++++++++++++++++++++++++++++++ web_src/js/index.js | 28 +++---------------------- 4 files changed, 40 insertions(+), 33 deletions(-) create mode 100644 web_src/js/features/projects.js diff --git a/templates/base/footer.tmpl b/templates/base/footer.tmpl index 8c53fce2c9c98..7dc16037db396 100644 --- a/templates/base/footer.tmpl +++ b/templates/base/footer.tmpl @@ -118,14 +118,6 @@ window.initHeatmap('user-heatmap', '{{.HeatmapUser}}'); {{end}} -{{ if .PageIsProjects }} - - -{{ end}} {{template "custom/footer" .}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index f1558f9484320..b1e0a8485dd57 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -93,6 +93,7 @@ SimpleMDE: {{if .RequireSimpleMDE}}true{{else}}false{{end}}, Tribute: {{if .RequireTribute}}true{{else}}false{{end}}, U2F: {{if .RequireU2F}}true{{else}}false{{end}}, + KanbanBoard: {{if .PageIsProjects }}true{{ end }}, }; diff --git a/web_src/js/features/projects.js b/web_src/js/features/projects.js new file mode 100644 index 0000000000000..394c8b27ac78b --- /dev/null +++ b/web_src/js/features/projects.js @@ -0,0 +1,36 @@ +export default async function initProject(csrf) { + if (!window.config || !window.config.KanbanBoard) { + return; + } + + const { Sortable } = await import( + /* webpackChunkName: "sortable" */ 'sortablejs' + ); + + const boardColumns = document.getElementsByClassName('board-column'); + + for (let i = 0; i < boardColumns.length; i++) { + new Sortable( + document.getElementById( + boardColumns[i].getElementsByClassName('board')[0].id + ), + { + group: 'shared', + animation: 150, + onAdd: (e) => { + $.ajax(`${e.to.dataset.url}/${e.item.dataset.issue}`, { + headers: { + 'X-Csrf-Token': csrf, + 'X-Remote': true, + }, + contentType: 'application/json', + type: 'POST', + success: () => { + // setTimeout(reload(),3000) + }, + }); + }, + } + ); + } +} diff --git a/web_src/js/index.js b/web_src/js/index.js index 6836e7c34c63f..6c0558340e546 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -1,5 +1,5 @@ /* globals wipPrefixes, issuesTribute, emojiTribute */ -/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap, initKanbanBoard */ +/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ /* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ import 'jquery.are-you-sure'; @@ -12,6 +12,7 @@ import initContextPopups from './features/contextPopup.js'; import initHighlight from './features/highlight.js'; import initGitGraph from './features/gitGraph.js'; import initClipboard from './features/clipboard.js'; +import initProject from './features/projects.js'; import ActivityTopAuthors from './components/ActivityTopAuthors.vue'; @@ -2613,6 +2614,7 @@ $(document).ready(async () => { initHighlight(), initGitGraph(), initClipboard(), + initProject(csrf), ]); }); @@ -2946,30 +2948,6 @@ window.cancelStopwatch = function () { $('#cancel_stopwatch_form').submit(); }; -window.initKanbanBoard = function (appElementId) { - const el = document.getElementById(appElementId); - if (!el) { - return; - } - - new Sortable(el, { - group: 'shared', - animation: 150, - onAdd: (e) => { - $.ajax(`${e.to.dataset.url}/${e.item.dataset.issue}`, { - headers: { - 'X-Csrf-Token': csrf, - 'X-Remote': true, - }, - contentType: 'application/json', - type: 'POST', - success: () => { - // setTimeout(reload(),3000) - }, - }); - }, - }); -}; window.initHeatmap = function (appElementId, heatmapUser, locale) { const el = document.getElementById(appElementId); From 6748e61cb387aec8f35509ca263537777cbe2eee Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 23 Feb 2020 16:34:36 +0100 Subject: [PATCH 069/216] Update templates/base/head.tmpl Co-Authored-By: silverwind --- templates/base/head.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index b1e0a8485dd57..0c93b928ac504 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -93,7 +93,7 @@ SimpleMDE: {{if .RequireSimpleMDE}}true{{else}}false{{end}}, Tribute: {{if .RequireTribute}}true{{else}}false{{end}}, U2F: {{if .RequireU2F}}true{{else}}false{{end}}, - KanbanBoard: {{if .PageIsProjects }}true{{ end }}, + PageIsProjects: {{if .PageIsProjects }}true{{ end }}, }; From de406139c995a8d372c30e2207a312e3b3d372e5 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 23 Feb 2020 16:34:51 +0100 Subject: [PATCH 070/216] Update web_src/js/index.js Co-Authored-By: silverwind --- web_src/js/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/web_src/js/index.js b/web_src/js/index.js index 6c0558340e546..642cdb3270acf 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -2948,7 +2948,6 @@ window.cancelStopwatch = function () { $('#cancel_stopwatch_form').submit(); }; - window.initHeatmap = function (appElementId, heatmapUser, locale) { const el = document.getElementById(appElementId); if (!el) { From 625206c096baf79aacddf9d802d090ccdf6fe246 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 23 Feb 2020 16:35:04 +0100 Subject: [PATCH 071/216] Update web_src/js/features/projects.js Co-Authored-By: silverwind --- web_src/js/features/projects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/features/projects.js b/web_src/js/features/projects.js index 394c8b27ac78b..f601d92a6d4f0 100644 --- a/web_src/js/features/projects.js +++ b/web_src/js/features/projects.js @@ -1,5 +1,5 @@ export default async function initProject(csrf) { - if (!window.config || !window.config.KanbanBoard) { + if (!window.config || !window.config.PageIsProjects) { return; } From 4f3a02ecaa5d5631030dd2981360098c6c8a1a8d Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 23 Feb 2020 17:09:40 +0100 Subject: [PATCH 072/216] upodate eslint config --- .eslintrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 42896ac7b6c9e..e8c63a6827f6f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,7 +23,6 @@ globals: emojify: false SimpleMDE: false u2fApi: false - Sortable: false Vue: false rules: From 5b09686d02d1b15e678d9af5d8f9a753b0efc161 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 15 Mar 2020 23:46:16 +0100 Subject: [PATCH 073/216] add octicon to the projects board --- options/locale/locale_en-US.ini | 2 ++ templates/base/head.tmpl | 2 +- templates/repo/projects/view.tmpl | 17 ++++++++++++++++- web_src/js/components/ActivityTopAuthors.vue | 4 ++-- web_src/js/features/projects.js | 2 +- web_src/js/index.js | 2 +- web_src/less/_repository.less | 12 ++++++------ 7 files changed, 29 insertions(+), 12 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index aa3e0e35c706b..f568a7dc1644c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -842,6 +842,8 @@ projects.type.bug_triage = "Bug Triage" projects.template.desc = "Project template" projects.template.desc_helper = "Select a project template to get started" projects.type.uncategorized = Uncategorized +projects.board.edit = "Edit board" +projects.board.delete = "Delete board" issues.desc = Organize bug reports, tasks and milestones. issues.new = New Issue diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 6d974f28319de..f14d40d857e54 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -95,7 +95,7 @@ U2F: {{if .RequireU2F}}true{{else}}false{{end}}, Heatmap: {{if .EnableHeatmap}}true{{else}}false{{end}}, heatmapUser: {{if .HeatmapUser}}'{{.HeatmapUser}}'{{else}}null{{end}}, - PageIsProjects: {{if .PageIsProjects }}true{{ end }}, + PageIsProjects: {{if .PageIsProjects }}true{{else}}false{{ end }}, }; diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl index b9e44e7e68947..34dc8b8d97b7a 100644 --- a/templates/repo/projects/view.tmpl +++ b/templates/repo/projects/view.tmpl @@ -26,7 +26,22 @@
    {{.Board.Title}}
    - {{svg "octicon-lock" 16}} +
    diff --git a/web_src/js/components/ActivityTopAuthors.vue b/web_src/js/components/ActivityTopAuthors.vue index a5c6e062b249a..d7a3aa077f211 100644 --- a/web_src/js/components/ActivityTopAuthors.vue +++ b/web_src/js/components/ActivityTopAuthors.vue @@ -1,7 +1,7 @@