summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v1_19/v241.go17
-rw-r--r--models/project/board.go21
-rw-r--r--models/project/project.go34
-rw-r--r--models/project/project_test.go1
-rw-r--r--models/repo/attachment.go15
-rw-r--r--options/locale/locale_en-US.ini3
-rw-r--r--routers/web/org/projects.go4
-rw-r--r--routers/web/repo/projects.go24
-rw-r--r--services/forms/repo_form.go2
-rw-r--r--templates/projects/new.tmpl2
-rw-r--r--templates/repo/projects/new.tmpl33
-rw-r--r--templates/repo/projects/view.tmpl7
-rw-r--r--templates/user/project.tmpl2
-rw-r--r--web_src/less/features/projects.less23
15 files changed, 173 insertions, 17 deletions
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 15600f057c..79e8573881 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -455,6 +455,8 @@ var migrations = []Migration{
NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens),
// v240 -> v241
NewMigration("Add actions tables", v1_19.AddActionsTables),
+ // v241 -> v242
+ NewMigration("Add card_type column to project table", v1_19.AddCardTypeToProjectTable),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_19/v241.go b/models/migrations/v1_19/v241.go
new file mode 100644
index 0000000000..332be580fa
--- /dev/null
+++ b/models/migrations/v1_19/v241.go
@@ -0,0 +1,17 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_19 //nolint
+
+import (
+ "xorm.io/xorm"
+)
+
+// AddCardTypeToProjectTable: add CardType column, setting existing rows to CardTypeTextOnly
+func AddCardTypeToProjectTable(x *xorm.Engine) error {
+ type Project struct {
+ CardType int `xorm:"NOT NULL"`
+ }
+
+ return x.Sync(new(Project))
+}
diff --git a/models/project/board.go b/models/project/board.go
index d8468f0cb5..dc4e2e6882 100644
--- a/models/project/board.go
+++ b/models/project/board.go
@@ -19,6 +19,9 @@ type (
// BoardType is used to represent a project board type
BoardType uint8
+ // CardType is used to represent a project board card type
+ CardType uint8
+
// BoardList is a list of all project boards in a repository
BoardList []*Board
)
@@ -34,6 +37,14 @@ const (
BoardTypeBugTriage
)
+const (
+ // CardTypeTextOnly is a project board card type that is text only
+ CardTypeTextOnly CardType = iota
+
+ // CardTypeImagesAndText is a project board card type that has images and text
+ CardTypeImagesAndText
+)
+
// BoardColorPattern is a regexp witch can validate BoardColor
var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
@@ -85,6 +96,16 @@ func IsBoardTypeValid(p BoardType) bool {
}
}
+// IsCardTypeValid checks if the project board card type is valid
+func IsCardTypeValid(p CardType) bool {
+ switch p {
+ case CardTypeTextOnly, CardTypeImagesAndText:
+ return true
+ default:
+ return false
+ }
+}
+
func createBoardsForProjectsType(ctx context.Context, project *Project) error {
var items []string
diff --git a/models/project/project.go b/models/project/project.go
index 9074fd0c15..931ef44675 100644
--- a/models/project/project.go
+++ b/models/project/project.go
@@ -19,12 +19,18 @@ import (
)
type (
- // ProjectsConfig is used to identify the type of board that is being created
- ProjectsConfig struct {
+ // BoardConfig is used to identify the type of board that is being created
+ BoardConfig struct {
BoardType BoardType
Translation string
}
+ // CardConfig is used to identify the type of board card that is being used
+ CardConfig struct {
+ CardType CardType
+ Translation string
+ }
+
// Type is used to identify the type of project in question and ownership
Type uint8
)
@@ -91,6 +97,7 @@ type Project struct {
CreatorID int64 `xorm:"NOT NULL"`
IsClosed bool `xorm:"INDEX"`
BoardType BoardType
+ CardType CardType
Type Type
RenderedContent string `xorm:"-"`
@@ -145,15 +152,23 @@ func init() {
db.RegisterModel(new(Project))
}
-// GetProjectsConfig retrieves the types of configurations projects could have
-func GetProjectsConfig() []ProjectsConfig {
- return []ProjectsConfig{
+// GetBoardConfig retrieves the types of configurations project boards could have
+func GetBoardConfig() []BoardConfig {
+ return []BoardConfig{
{BoardTypeNone, "repo.projects.type.none"},
{BoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
{BoardTypeBugTriage, "repo.projects.type.bug_triage"},
}
}
+// GetCardConfig retrieves the types of configurations project board cards could have
+func GetCardConfig() []CardConfig {
+ return []CardConfig{
+ {CardTypeTextOnly, "repo.projects.card_type.text_only"},
+ {CardTypeImagesAndText, "repo.projects.card_type.images_and_text"},
+ }
+}
+
// IsTypeValid checks if a project type is valid
func IsTypeValid(p Type) bool {
switch p {
@@ -237,6 +252,10 @@ func NewProject(p *Project) error {
p.BoardType = BoardTypeNone
}
+ if !IsCardTypeValid(p.CardType) {
+ p.CardType = CardTypeTextOnly
+ }
+
if !IsTypeValid(p.Type) {
return util.NewInvalidArgumentErrorf("project type is not valid")
}
@@ -280,9 +299,14 @@ func GetProjectByID(ctx context.Context, id int64) (*Project, error) {
// UpdateProject updates project properties
func UpdateProject(ctx context.Context, p *Project) error {
+ if !IsCardTypeValid(p.CardType) {
+ p.CardType = CardTypeTextOnly
+ }
+
_, err := db.GetEngine(ctx).ID(p.ID).Cols(
"title",
"description",
+ "card_type",
).Update(p)
return err
}
diff --git a/models/project/project_test.go b/models/project/project_test.go
index c2d9005c43..6caa244f54 100644
--- a/models/project/project_test.go
+++ b/models/project/project_test.go
@@ -53,6 +53,7 @@ func TestProject(t *testing.T) {
project := &Project{
Type: TypeRepository,
BoardType: BoardTypeBasicKanban,
+ CardType: CardTypeTextOnly,
Title: "New Project",
RepoID: 1,
CreatedUnix: timeutil.TimeStampNow(),
diff --git a/models/repo/attachment.go b/models/repo/attachment.go
index 8fbf79a7a0..cb05386d93 100644
--- a/models/repo/attachment.go
+++ b/models/repo/attachment.go
@@ -132,6 +132,21 @@ func GetAttachmentsByIssueID(ctx context.Context, issueID int64) ([]*Attachment,
return attachments, db.GetEngine(ctx).Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments)
}
+// GetAttachmentsByIssueIDImagesLatest returns the latest image attachments of an issue.
+func GetAttachmentsByIssueIDImagesLatest(ctx context.Context, issueID int64) ([]*Attachment, error) {
+ attachments := make([]*Attachment, 0, 5)
+ return attachments, db.GetEngine(ctx).Where(`issue_id = ? AND (name like '%.apng'
+ OR name like '%.avif'
+ OR name like '%.bmp'
+ OR name like '%.gif'
+ OR name like '%.jpg'
+ OR name like '%.jpeg'
+ OR name like '%.jxl'
+ OR name like '%.png'
+ OR name like '%.svg'
+ OR name like '%.webp')`, issueID).Desc("comment_id").Limit(5).Find(&attachments)
+}
+
// GetAttachmentsByCommentID returns all attachments if comment by given ID.
func GetAttachmentsByCommentID(ctx context.Context, commentID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index f784b10c8d..5d0fd044f4 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1231,6 +1231,9 @@ projects.board.color = "Color"
projects.open = Open
projects.close = Close
projects.board.assigned_to = Assigned to
+projects.card_type.desc = "Card Previews"
+projects.card_type.images_and_text = "Images and Text"
+projects.card_type.text_only = "Text Only"
issues.desc = Organize bug reports, tasks and milestones.
issues.filter_assignees = Filter Assignee
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index 1ce44d4866..6449d12de1 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -121,7 +121,7 @@ func canWriteUnit(ctx *context.Context) bool {
// NewProject render creating a project page
func NewProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.new")
- ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
+ ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
ctx.Data["CanWriteProjects"] = canWriteUnit(ctx)
ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink()
shared_user.RenderUserHeader(ctx)
@@ -137,7 +137,7 @@ func NewProjectPost(ctx *context.Context) {
if ctx.HasError() {
ctx.Data["CanWriteProjects"] = canWriteUnit(ctx)
ctx.Data["PageIsViewProjects"] = true
- ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
+ ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
ctx.HTML(http.StatusOK, tplProjectsNew)
return
}
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 3becf799c5..967b81c608 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -13,6 +13,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/perm"
project_model "code.gitea.io/gitea/models/project"
+ attachment_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@@ -123,7 +124,8 @@ func Projects(ctx *context.Context) {
// NewProject render creating a project page
func NewProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.new")
- ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
+ ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
+ ctx.Data["CardTypes"] = project_model.GetCardConfig()
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.HTML(http.StatusOK, tplProjectsNew)
}
@@ -135,7 +137,8 @@ func NewProjectPost(ctx *context.Context) {
if ctx.HasError() {
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
- ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
+ ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
+ ctx.Data["CardTypes"] = project_model.GetCardConfig()
ctx.HTML(http.StatusOK, tplProjectsNew)
return
}
@@ -146,6 +149,7 @@ func NewProjectPost(ctx *context.Context) {
Description: form.Content,
CreatorID: ctx.Doer.ID,
BoardType: form.BoardType,
+ CardType: form.CardType,
Type: project_model.TypeRepository,
}); err != nil {
ctx.ServerError("NewProject", err)
@@ -212,6 +216,7 @@ func EditProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.edit")
ctx.Data["PageIsEditProjects"] = true
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
+ ctx.Data["CardTypes"] = project_model.GetCardConfig()
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil {
@@ -229,6 +234,7 @@ func EditProject(ctx *context.Context) {
ctx.Data["title"] = p.Title
ctx.Data["content"] = p.Description
+ ctx.Data["card_type"] = p.CardType
ctx.HTML(http.StatusOK, tplProjectsNew)
}
@@ -239,6 +245,7 @@ func EditProjectPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.edit")
ctx.Data["PageIsEditProjects"] = true
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
+ ctx.Data["CardTypes"] = project_model.GetCardConfig()
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplProjectsNew)
@@ -261,6 +268,7 @@ func EditProjectPost(ctx *context.Context) {
p.Title = form.Title
p.Description = form.Content
+ p.CardType = form.CardType
if err = project_model.UpdateProject(ctx, p); err != nil {
ctx.ServerError("UpdateProjects", err)
return
@@ -302,6 +310,18 @@ func ViewProject(ctx *context.Context) {
return
}
+ if project.CardType != project_model.CardTypeTextOnly {
+ issuesAttachmentMap := make(map[int64][]*attachment_model.Attachment)
+ for _, issuesList := range issuesMap {
+ for _, issue := range issuesList {
+ if issueAttachment, err := attachment_model.GetAttachmentsByIssueIDImagesLatest(ctx, issue.ID); err == nil {
+ issuesAttachmentMap[issue.ID] = issueAttachment
+ }
+ }
+ }
+ ctx.Data["issuesAttachmentMap"] = issuesAttachmentMap
+ }
+
linkedPrsMap := make(map[int64][]*issues_model.Issue)
for _, issuesList := range issuesMap {
for _, issue := range issuesList {
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 436d79df68..db336e25e3 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -512,6 +512,7 @@ type CreateProjectForm struct {
Title string `binding:"Required;MaxSize(100)"`
Content string
BoardType project_model.BoardType
+ CardType project_model.CardType
}
// UserCreateProjectForm is a from for creating an individual or organization
@@ -520,6 +521,7 @@ type UserCreateProjectForm struct {
Title string `binding:"Required;MaxSize(100)"`
Content string
BoardType project_model.BoardType
+ CardType project_model.CardType
UID int64 `binding:"Required"`
}
diff --git a/templates/projects/new.tmpl b/templates/projects/new.tmpl
index 04192c64c2..19bf503692 100644
--- a/templates/projects/new.tmpl
+++ b/templates/projects/new.tmpl
@@ -36,7 +36,7 @@
<input type="hidden" name="board_type" value="{{.type}}">
<div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div>
<div class="menu">
- {{range $element := .ProjectTypes}}
+ {{range $element := .BoardTypes}}
<div class="item" data-id="{{$element.BoardType}}" data-value="{{$element.BoardType}}">{{$.locale.Tr $element.Translation}}</div>
{{end}}
</div>
diff --git a/templates/repo/projects/new.tmpl b/templates/repo/projects/new.tmpl
index 79f9380dce..c90fa4369c 100644
--- a/templates/repo/projects/new.tmpl
+++ b/templates/repo/projects/new.tmpl
@@ -34,17 +34,38 @@
</div>
{{if not .PageIsEditProjects}}
- <label>{{.locale.Tr "repo.projects.template.desc"}}</label>
+ <div class="field">
+ <label>{{.locale.Tr "repo.projects.template.desc"}}</label>
+ <div class="ui selection dropdown">
+ <input type="hidden" name="board_type" value="{{.type}}">
+ <div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div>
+ <div class="menu">
+ {{range $element := .BoardTypes}}
+ <div class="item" data-id="{{$element.BoardType}}" data-value="{{$element.BoardType}}">{{$.locale.Tr $element.Translation}}</div>
+ {{end}}
+ </div>
+ </div>
+ </div>
+ {{end}}
+
+ <div class="field">
+ <label>{{.locale.Tr "repo.projects.card_type.desc"}}</label>
<div class="ui selection dropdown">
- <input type="hidden" name="board_type" value="{{.type}}">
- <div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div>
+ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
+ {{range $element := .CardTypes}}
+ {{if or (eq $.card_type $element.CardType) (and (not $.card_type) (eq $element.CardType 2))}}
+ <input type="hidden" name="card_type" value="{{$element.CardType}}">
+ <div class="default text">{{$.locale.Tr $element.Translation}}</div>
+ {{end}}
+ {{end}}
<div class="menu">
- {{range $element := .ProjectTypes}}
- <div class="item" data-id="{{$element.BoardType}}" data-value="{{$element.BoardType}}">{{$.locale.Tr $element.Translation}}</div>
+ {{range $element := .CardTypes}}
+ <div class="item" data-id="{{$element.CardType}}" data-value="{{$element.CardType}}">{{$.locale.Tr $element.Translation}}</div>
{{end}}
</div>
</div>
- {{end}}
+ </div>
+
</div>
<div class="ui container">
<div class="ui divider"></div>
diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl
index 63d2727b63..711b488180 100644
--- a/templates/repo/projects/view.tmpl
+++ b/templates/repo/projects/view.tmpl
@@ -179,6 +179,13 @@
<!-- start issue card -->
<div class="card board-card" data-issue="{{.ID}}">
+ {{if eq $.Project.CardType 1}}{{/* Images and Text*/}}
+ <div class="card-attachment-images">
+ {{range (index $.issuesAttachmentMap .ID)}}
+ <img src="{{.DownloadURL}}" alt="{{.Name}}" />
+ {{end}}
+ </div>
+ {{end}}
<div class="content p-0">
<div class="header">
<span class="dif ac vm {{if .IsClosed}}red{{else}}green{{end}}">
diff --git a/templates/user/project.tmpl b/templates/user/project.tmpl
index 59eff13aa7..7016c4d8b7 100644
--- a/templates/user/project.tmpl
+++ b/templates/user/project.tmpl
@@ -48,7 +48,7 @@
<input type="hidden" name="board_type" value="{{.type}}">
<div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div>
<div class="menu">
- {{range $element := .ProjectTypes}}
+ {{range $element := .BoardTypes}}
<div class="item" data-id="{{$element.BoardType}}" data-value="{{$element.BoardType}}">{{$.locale.Tr $element.Translation}}</div>
{{end}}
</div>
diff --git a/web_src/less/features/projects.less b/web_src/less/features/projects.less
index b0f674060a..cbdb1a3c9f 100644
--- a/web_src/less/features/projects.less
+++ b/web_src/less/features/projects.less
@@ -72,6 +72,10 @@
margin-right: auto !important;
}
+.board-column .ui.cards > .card > .content {
+ border: none;
+}
+
.board-card {
margin: 4px 2px !important;
border-radius: 5px !important;
@@ -90,6 +94,25 @@
font-size: 16px !important;
}
+.board-card .card-attachment-images {
+ display: inline-block;
+ white-space: nowrap;
+ overflow: hidden;
+ text-align: center;
+}
+
+.board-card .card-attachment-images img {
+ display: inline-block;
+ max-height: 50px;
+ border-radius: var(--border-radius);
+ margin-right: 2px;
+}
+
+.board-card .card-attachment-images img:only-child {
+ max-height: 90px;
+ margin: auto;
+}
+
.card-ghost {
border-style: dashed !important;
background: none !important;