Compare commits

...

9 Commits

Author SHA1 Message Date
wxiaoguang
b0f18726a3
Fix milestone title font problem (#22863)
Replace #22853 since it's closed, and actually there are 2 places need
to be fixed.

~~Follow @fsologureng 's suggestion to keep the `<hX>` tags.~~ 

Update: from fsologureng: this doesn't change anything from a11y's point
of view. So I think this PR could be fine to fix the UI looking problems
as a quick patch, then defer the a11y problems to new PRs together.

Before: the font-size is too large.

After: it seems better.

![image](https://user-images.githubusercontent.com/2114189/218266257-fc2d5872-9e96-4c6a-87ea-f27531ac15c0.png)

![image](https://user-images.githubusercontent.com/2114189/218266247-efc09d83-405f-4495-967a-30d9744134ce.png)

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-12 11:19:09 +08:00
gempir
e460b6ef4d
Fix PR file tree folders no longer collapsing (#22864)
Collapsing folders currently just throws a console error

```
index.js?v=1.19.0~dev-403-gb6b8feb3d:10 TypeError: this.$set is not a function
    at Proxy.handleClick (index.js?v=1.19.0~dev-403-gb6b8feb3d:58:7159)
    at index.js?v=1.19.0~dev-403-gb6b8feb3d:58:6466
    at index.js?v=1.19.0~dev-403-gb6b8feb3d:10:93922
    at ce (index.js?v=1.19.0~dev-403-gb6b8feb3d:10:1472)
    at Q (index.js?v=1.19.0~dev-403-gb6b8feb3d:10:1567)
    at HTMLDivElement.$e (index.js?v=1.19.0~dev-403-gb6b8feb3d:10:79198)
```

This PR fixes this and allows folders to be collapsed again.

Also:
- better cursor interaction with folders
- added some color to the diff detail stats
- remove green link color from all the file names

Screenshots:

![image](https://user-images.githubusercontent.com/9765622/218269712-2f3dda55-6d70-407f-8d34-2a5d9c8df548.png)

![image](https://user-images.githubusercontent.com/9765622/218269714-6ce8a954-daea-4ed6-9eea-8b2323db4d8f.png)

---------

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-12 10:34:46 +08:00
sillyguodong
51ab495198
escape filename when assemble URL (#22850)
Fixes: #22843 

### Cause:

affdd40296/services/repository/files/content.go (L161)

Previously, we did not escape the **"%"** that might be in "treePath"
when call "url.parse()".


![image](https://user-images.githubusercontent.com/33891828/218066318-5a909e50-2a17-46e6-b32f-684b2aa4b91f.png)

This function will check whether "%" is the beginning of an escape
character. Obviously, the "%" in the example (hello%mother.txt) is not
that. So, the function will return a error.

### Solution:
We can escape "treePath" by call "url.PathEscape()" function firstly.

### Screenshot:

![image](https://user-images.githubusercontent.com/33891828/218069781-1a030f8b-18d0-4804-b0f8-73997849ef43.png)

---------

Signed-off-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: Andrew Thornton <art27@cantab.net>
2023-02-12 09:31:14 +08:00
silverwind
8fa54d0fda
Fix notification and stopwatch empty states (#22845)
Previous solution was relying on fomantic selector `.ui.label.hidden` to
hide the elements in their empty state, but this doesn't work any more
with the removal of the `label` class. Instead, introduce a standalone
CSS rule for the `hidden` class, which is universally usable as a single
class.

We can unfortunately not use the existing `hide` class because without
the `!important`, it does not have enough specificity to win against
fomantic's `.ui.menu:not(.vertical) .item {display: flex}` rule.

Followup and fixes regression from
https://github.com/go-gitea/gitea/pull/22169.

Before:

<img width="98" alt="image"
src="https://user-images.githubusercontent.com/115237/217959380-d3279ff3-526a-4ac4-9a18-3ab7c9ae91dd.png">

After:

<img width="77" alt="image"
src="https://user-images.githubusercontent.com/115237/217959463-44852716-cb25-4110-8481-668842ad4454.png">
2023-02-11 21:45:58 +00:00
zeripath
2152c4e98f
Fix .golangci.yml (#22868)
When we updated the .golangci.yml for 1.20 we should have used a string
as 1.20 is not a valid number.

In doing so we need to restore the nolint markings within the pq driver.

Signed-off-by: Andrew Thornton <art27@cantab.net>
2023-02-11 21:44:53 +00:00
Nathaniel Sabanski
d4a9b35c4b
Fix migration issue. (#22867)
See:
https://github.com/go-gitea/gitea/pull/22112#issuecomment-1426872992
2023-02-11 21:28:41 +00:00
KN4CK3R
9057a008a1
Add /$count endpoints for NuGet v2 (#22855)
Fixes #22838

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-11 19:30:44 +08:00
Nathaniel Sabanski
fb1a2a13f0
Preview images for Issue cards in Project Board view (#22112)
Original Issue: https://github.com/go-gitea/gitea/issues/22102

This addition would be a big benefit for design and art teams using the
issue tracking.

The preview will be the latest "image type" attachments on an issue-
simple, and allows for automatic updates of the cover image as issue
progress is made!

This would make Gitea competitive with Trello... wouldn't it be amazing
to say goodbye to Atlassian products? Ha.

First image is the most recent, the SQL will fetch up to 5 latest images
(URL string).

All images supported by browsers plus upcoming formats: *.avif *.bmp
*.gif *.jpg *.jpeg *.jxl *.png *.svg *.webp

The CSS will try to center-align images until it cannot, then it will
left align with overflow hidden. Single images get to be slightly
larger!

Tested so far on: Chrome, Firefox, Android Chrome, Android Firefox.

Current revision with light and dark themes:

![image](https://user-images.githubusercontent.com/24665/207066878-58e6bf73-0c93-4caa-8d40-38f4432b3578.png)


![image](https://user-images.githubusercontent.com/24665/207066555-293f65c3-e706-4888-8516-de8ec632d638.png)

---------

Co-authored-by: Jason Song <i@wolfogre.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: delvh <dev.lh@web.de>
2023-02-11 16:12:41 +08:00
wxiaoguang
e9288c2477
Fix improper HTMLURL usages in Go code (#22839)
In Go code, HTMLURL should be only used for external systems, like
API/webhook/mail/notification, etc.

If a URL is used by `Redirect` or rendered in a template, it should be a
relative URL (aka `Link()` in Gitea)

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-11 14:34:11 +08:00
45 changed files with 326 additions and 86 deletions

View File

@ -28,7 +28,7 @@ linters:
fast: false fast: false
run: run:
go: 1.20 go: "1.20"
timeout: 10m timeout: 10m
skip-dirs: skip-dirs:
- node_modules - node_modules

View File

@ -37,7 +37,9 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
} }
schemaValue, _ := driver.String.ConvertValue(setting.Database.Schema) schemaValue, _ := driver.String.ConvertValue(setting.Database.Schema)
if execer, ok := conn.(driver.Execer); ok { // golangci lint is incorrect here - there is no benefit to using driver.ExecerContext here
// and in any case pq does not implement it
if execer, ok := conn.(driver.Execer); ok { //nolint
_, err := execer.Exec(`SELECT set_config( _, err := execer.Exec(`SELECT set_config(
'search_path', 'search_path',
$1 || ',' || current_setting('search_path'), $1 || ',' || current_setting('search_path'),
@ -61,7 +63,8 @@ func (d *postgresSchemaDriver) Open(name string) (driver.Conn, error) {
// driver.String.ConvertValue will never return err for string // driver.String.ConvertValue will never return err for string
_, err = stmt.Exec([]driver.Value{schemaValue}) // golangci lint is incorrect here - there is no benefit to using stmt.ExecWithContext here
_, err = stmt.Exec([]driver.Value{schemaValue}) //nolint
if err != nil { if err != nil {
_ = conn.Close() _ = conn.Close()
return nil, err return nil, err

View File

@ -455,6 +455,8 @@ var migrations = []Migration{
NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens), NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens),
// v240 -> v241 // v240 -> v241
NewMigration("Add actions tables", v1_19.AddActionsTables), 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 // GetCurrentDBVersion returns the current db version

View File

@ -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 DEFAULT 0"`
}
return x.Sync(new(Project))
}

View File

@ -19,6 +19,9 @@ type (
// BoardType is used to represent a project board type // BoardType is used to represent a project board type
BoardType uint8 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 is a list of all project boards in a repository
BoardList []*Board BoardList []*Board
) )
@ -34,6 +37,14 @@ const (
BoardTypeBugTriage 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 // BoardColorPattern is a regexp witch can validate BoardColor
var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$") 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 { func createBoardsForProjectsType(ctx context.Context, project *Project) error {
var items []string var items []string

View File

@ -19,12 +19,18 @@ import (
) )
type ( type (
// ProjectsConfig is used to identify the type of board that is being created // BoardConfig is used to identify the type of board that is being created
ProjectsConfig struct { BoardConfig struct {
BoardType BoardType BoardType BoardType
Translation string 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 is used to identify the type of project in question and ownership
Type uint8 Type uint8
) )
@ -91,6 +97,7 @@ type Project struct {
CreatorID int64 `xorm:"NOT NULL"` CreatorID int64 `xorm:"NOT NULL"`
IsClosed bool `xorm:"INDEX"` IsClosed bool `xorm:"INDEX"`
BoardType BoardType BoardType BoardType
CardType CardType
Type Type Type Type
RenderedContent string `xorm:"-"` RenderedContent string `xorm:"-"`
@ -145,15 +152,23 @@ func init() {
db.RegisterModel(new(Project)) db.RegisterModel(new(Project))
} }
// GetProjectsConfig retrieves the types of configurations projects could have // GetBoardConfig retrieves the types of configurations project boards could have
func GetProjectsConfig() []ProjectsConfig { func GetBoardConfig() []BoardConfig {
return []ProjectsConfig{ return []BoardConfig{
{BoardTypeNone, "repo.projects.type.none"}, {BoardTypeNone, "repo.projects.type.none"},
{BoardTypeBasicKanban, "repo.projects.type.basic_kanban"}, {BoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
{BoardTypeBugTriage, "repo.projects.type.bug_triage"}, {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 // IsTypeValid checks if a project type is valid
func IsTypeValid(p Type) bool { func IsTypeValid(p Type) bool {
switch p { switch p {
@ -237,6 +252,10 @@ func NewProject(p *Project) error {
p.BoardType = BoardTypeNone p.BoardType = BoardTypeNone
} }
if !IsCardTypeValid(p.CardType) {
p.CardType = CardTypeTextOnly
}
if !IsTypeValid(p.Type) { if !IsTypeValid(p.Type) {
return util.NewInvalidArgumentErrorf("project type is not valid") 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 // UpdateProject updates project properties
func UpdateProject(ctx context.Context, p *Project) error { func UpdateProject(ctx context.Context, p *Project) error {
if !IsCardTypeValid(p.CardType) {
p.CardType = CardTypeTextOnly
}
_, err := db.GetEngine(ctx).ID(p.ID).Cols( _, err := db.GetEngine(ctx).ID(p.ID).Cols(
"title", "title",
"description", "description",
"card_type",
).Update(p) ).Update(p)
return err return err
} }

View File

@ -53,6 +53,7 @@ func TestProject(t *testing.T) {
project := &Project{ project := &Project{
Type: TypeRepository, Type: TypeRepository,
BoardType: BoardTypeBasicKanban, BoardType: BoardTypeBasicKanban,
CardType: CardTypeTextOnly,
Title: "New Project", Title: "New Project",
RepoID: 1, RepoID: 1,
CreatedUnix: timeutil.TimeStampNow(), CreatedUnix: timeutil.TimeStampNow(),

View File

@ -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) 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. // GetAttachmentsByCommentID returns all attachments if comment by given ID.
func GetAttachmentsByCommentID(ctx context.Context, commentID int64) ([]*Attachment, error) { func GetAttachmentsByCommentID(ctx context.Context, commentID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10) attachments := make([]*Attachment, 0, 10)

View File

@ -274,7 +274,7 @@ func (repo *Repository) CommitLink(commitID string) (result string) {
if commitID == "" || commitID == "0000000000000000000000000000000000000000" { if commitID == "" || commitID == "0000000000000000000000000000000000000000" {
result = "" result = ""
} else { } else {
result = repo.HTMLURL() + "/commit/" + url.PathEscape(commitID) result = repo.Link() + "/commit/" + url.PathEscape(commitID)
} }
return result return result
} }

View File

@ -743,9 +743,9 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
if ctx.FormString("go-get") == "1" { if ctx.FormString("go-get") == "1" {
ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name) ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
prefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName) fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
ctx.Data["GoDocDirectory"] = prefix + "{/dir}" ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}" ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
} }
return cancel return cancel
} }

View File

@ -4,7 +4,6 @@
package lfs package lfs
import ( import (
"fmt"
"net/url" "net/url"
"os" "os"
"path" "path"
@ -12,6 +11,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
) )
// DetermineEndpoint determines an endpoint from the clone url or uses the specified LFS url. // DetermineEndpoint determines an endpoint from the clone url or uses the specified LFS url.
@ -95,7 +95,7 @@ func endpointFromLocalPath(path string) *url.URL {
return nil return nil
} }
path = fmt.Sprintf("file://%s%s", slash, filepath.ToSlash(path)) path = "file://" + slash + util.PathEscapeSegments(filepath.ToSlash(path))
u, _ := url.Parse(path) u, _ := url.Parse(path)

View File

@ -1231,6 +1231,9 @@ projects.board.color = "Color"
projects.open = Open projects.open = Open
projects.close = Close projects.close = Close
projects.board.assigned_to = Assigned to 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.desc = Organize bug reports, tasks and milestones.
issues.filter_assignees = Filter Assignee issues.filter_assignees = Filter Assignee

View File

@ -286,9 +286,18 @@ func CommonRoutes(ctx gocontext.Context) *web.Route {
}, reqPackageAccess(perm.AccessModeWrite)) }, reqPackageAccess(perm.AccessModeWrite))
r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile) r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
r.Get("/Packages(Id='{id:[^']+}',Version='{version:[^']+}')", nuget.RegistrationLeafV2) r.Get("/Packages(Id='{id:[^']+}',Version='{version:[^']+}')", nuget.RegistrationLeafV2)
r.Get("/Packages()", nuget.SearchServiceV2) r.Group("/Packages()", func() {
r.Get("/FindPackagesById()", nuget.EnumeratePackageVersionsV2) r.Get("", nuget.SearchServiceV2)
r.Get("/Search()", nuget.SearchServiceV2) r.Get("/$count", nuget.SearchServiceV2Count)
})
r.Group("/FindPackagesById()", func() {
r.Get("", nuget.EnumeratePackageVersionsV2)
r.Get("/$count", nuget.EnumeratePackageVersionsV2Count)
})
r.Group("/Search()", func() {
r.Get("", nuget.SearchServiceV2)
r.Get("/$count", nuget.SearchServiceV2Count)
})
}, reqPackageAccess(perm.AccessModeRead)) }, reqPackageAccess(perm.AccessModeRead))
}) })
r.Group("/npm", func() { r.Group("/npm", func() {

View File

@ -10,6 +10,7 @@ import (
"io" "io"
"net/http" "net/http"
"regexp" "regexp"
"strconv"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -94,8 +95,7 @@ func FeedCapabilityResource(ctx *context.Context) {
var searchTermExtract = regexp.MustCompile(`'([^']+)'`) var searchTermExtract = regexp.MustCompile(`'([^']+)'`)
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs func getSearchTerm(ctx *context.Context) string {
func SearchServiceV2(ctx *context.Context) {
searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'") searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
if searchTerm == "" { if searchTerm == "" {
// $filter contains a query like: // $filter contains a query like:
@ -106,7 +106,11 @@ func SearchServiceV2(ctx *context.Context) {
searchTerm = strings.TrimSpace(match[1]) searchTerm = strings.TrimSpace(match[1])
} }
} }
return searchTerm
}
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
func SearchServiceV2(ctx *context.Context) {
skip, take := ctx.FormInt("skip"), ctx.FormInt("take") skip, take := ctx.FormInt("skip"), ctx.FormInt("take")
if skip == 0 { if skip == 0 {
skip = ctx.FormInt("$skip") skip = ctx.FormInt("$skip")
@ -116,9 +120,11 @@ func SearchServiceV2(ctx *context.Context) {
} }
pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID, OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet, Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{Value: searchTerm}, Name: packages_model.SearchValue{
Value: getSearchTerm(ctx),
},
IsInternal: util.OptionalBoolFalse, IsInternal: util.OptionalBoolFalse,
Paginator: db.NewAbsoluteListOptions( Paginator: db.NewAbsoluteListOptions(
skip, skip,
@ -145,6 +151,24 @@ func SearchServiceV2(ctx *context.Context) {
xmlResponse(ctx, http.StatusOK, resp) xmlResponse(ctx, http.StatusOK, resp)
} }
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
func SearchServiceV2Count(ctx *context.Context) {
count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{
Value: getSearchTerm(ctx),
},
IsInternal: util.OptionalBoolFalse,
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10))
}
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages // https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
func SearchServiceV3(ctx *context.Context) { func SearchServiceV3(ctx *context.Context) {
pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
@ -288,6 +312,25 @@ func EnumeratePackageVersionsV2(ctx *context.Context) {
xmlResponse(ctx, http.StatusOK, resp) xmlResponse(ctx, http.StatusOK, resp)
} }
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
func EnumeratePackageVersionsV2Count(ctx *context.Context) {
count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{
ExactMatch: true,
Value: strings.Trim(ctx.FormTrim("id"), "'"),
},
IsInternal: util.OptionalBoolFalse,
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10))
}
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#enumerate-package-versions // https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#enumerate-package-versions
func EnumeratePackageVersionsV3(ctx *context.Context) { func EnumeratePackageVersionsV3(ctx *context.Context) {
packageName := ctx.Params("id") packageName := ctx.Params("id")

View File

@ -121,7 +121,7 @@ func canWriteUnit(ctx *context.Context) bool {
// NewProject render creating a project page // NewProject render creating a project page
func NewProject(ctx *context.Context) { func NewProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.new") 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["CanWriteProjects"] = canWriteUnit(ctx)
ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink() ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink()
shared_user.RenderUserHeader(ctx) shared_user.RenderUserHeader(ctx)
@ -137,7 +137,7 @@ func NewProjectPost(ctx *context.Context) {
if ctx.HasError() { if ctx.HasError() {
ctx.Data["CanWriteProjects"] = canWriteUnit(ctx) ctx.Data["CanWriteProjects"] = canWriteUnit(ctx)
ctx.Data["PageIsViewProjects"] = true ctx.Data["PageIsViewProjects"] = true
ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig() ctx.Data["BoardTypes"] = project_model.GetBoardConfig()
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)
return return
} }

View File

@ -70,7 +70,7 @@ func List(ctx *context.Context) {
} }
ctx.Data["workflows"] = workflows ctx.Data["workflows"] = workflows
ctx.Data["RepoLink"] = ctx.Repo.Repository.HTMLURL() ctx.Data["RepoLink"] = ctx.Repo.Repository.Link()
page := ctx.FormInt("page") page := ctx.FormInt("page")
if page <= 0 { if page <= 0 {

View File

@ -100,7 +100,7 @@ func MustAllowUserComment(ctx *context.Context) {
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin { if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked")) ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
return return
} }
} }
@ -927,7 +927,7 @@ func NewIssueChooseTemplate(ctx *context.Context) {
if len(issueTemplates) == 0 { if len(issueTemplates) == 0 {
// The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if no template here, just redirect to the "issues/new" page with these parameters. // The "issues/new" and "issues/new/choose" share the same query parameters "project" and "milestone", if no template here, just redirect to the "issues/new" page with these parameters.
ctx.Redirect(fmt.Sprintf("%s/issues/new?%s", ctx.Repo.Repository.HTMLURL(), ctx.Req.URL.RawQuery), http.StatusSeeOther) ctx.Redirect(fmt.Sprintf("%s/issues/new?%s", ctx.Repo.Repository.Link(), ctx.Req.URL.RawQuery), http.StatusSeeOther)
return return
} }
@ -950,11 +950,11 @@ func DeleteIssue(ctx *context.Context) {
} }
if issue.IsPull { if issue.IsPull {
ctx.Redirect(fmt.Sprintf("%s/pulls", ctx.Repo.Repository.HTMLURL()), http.StatusSeeOther) ctx.Redirect(fmt.Sprintf("%s/pulls", ctx.Repo.Repository.Link()), http.StatusSeeOther)
return return
} }
ctx.Redirect(fmt.Sprintf("%s/issues", ctx.Repo.Repository.HTMLURL()), http.StatusSeeOther) ctx.Redirect(fmt.Sprintf("%s/issues", ctx.Repo.Repository.Link()), http.StatusSeeOther)
} }
// ValidateRepoMetas check and returns repository's meta information // ValidateRepoMetas check and returns repository's meta information
@ -1425,7 +1425,7 @@ func ViewIssue(ctx *context.Context) {
return return
} }
// Add link to the issue of the already running stopwatch // Add link to the issue of the already running stopwatch
ctx.Data["OtherStopwatchURL"] = otherIssue.HTMLURL() ctx.Data["OtherStopwatchURL"] = otherIssue.Link()
} }
} }
ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(issue, ctx.Doer) ctx.Data["CanUseTimetracker"] = ctx.Repo.CanUseTimetracker(issue, ctx.Doer)
@ -2658,7 +2658,7 @@ func NewComment(ctx *context.Context) {
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin { if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked")) ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
return return
} }
@ -2669,7 +2669,7 @@ func NewComment(ctx *context.Context) {
if ctx.HasError() { if ctx.HasError() {
ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
return return
} }

View File

@ -34,7 +34,7 @@ func AddDependency(ctx *context.Context) {
} }
// Redirect // Redirect
defer ctx.Redirect(issue.HTMLURL()) defer ctx.Redirect(issue.Link())
// Dependency // Dependency
dep, err := issues_model.GetIssueByID(ctx, depID) dep, err := issues_model.GetIssueByID(ctx, depID)
@ -124,5 +124,5 @@ func RemoveDependency(ctx *context.Context) {
} }
// Redirect // Redirect
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
} }

View File

@ -21,13 +21,13 @@ func LockIssue(ctx *context.Context) {
if issue.IsLocked { if issue.IsLocked {
ctx.Flash.Error(ctx.Tr("repo.issues.lock_duplicate")) ctx.Flash.Error(ctx.Tr("repo.issues.lock_duplicate"))
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
return return
} }
if !form.HasValidReason() { if !form.HasValidReason() {
ctx.Flash.Error(ctx.Tr("repo.issues.lock.unknown_reason")) ctx.Flash.Error(ctx.Tr("repo.issues.lock.unknown_reason"))
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
return return
} }
@ -40,7 +40,7 @@ func LockIssue(ctx *context.Context) {
return return
} }
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
} }
// UnlockIssue unlocks a previously locked issue. // UnlockIssue unlocks a previously locked issue.
@ -52,7 +52,7 @@ func UnlockIssue(ctx *context.Context) {
if !issue.IsLocked { if !issue.IsLocked {
ctx.Flash.Error(ctx.Tr("repo.issues.unlock_error")) ctx.Flash.Error(ctx.Tr("repo.issues.unlock_error"))
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
return return
} }
@ -64,5 +64,5 @@ func UnlockIssue(ctx *context.Context) {
return return
} }
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
} }

View File

@ -40,7 +40,7 @@ func IssueStopwatch(c *context.Context) {
c.Flash.Success(c.Tr("repo.issues.tracker_auto_close")) c.Flash.Success(c.Tr("repo.issues.tracker_auto_close"))
} }
url := issue.HTMLURL() url := issue.Link()
c.Redirect(url, http.StatusSeeOther) c.Redirect(url, http.StatusSeeOther)
} }
@ -72,7 +72,7 @@ func CancelStopwatch(c *context.Context) {
}) })
} }
url := issue.HTMLURL() url := issue.Link()
c.Redirect(url, http.StatusSeeOther) c.Redirect(url, http.StatusSeeOther)
} }

View File

@ -26,7 +26,7 @@ func AddTimeManually(c *context.Context) {
c.NotFound("CanUseTimetracker", nil) c.NotFound("CanUseTimetracker", nil)
return return
} }
url := issue.HTMLURL() url := issue.Link()
if c.HasError() { if c.HasError() {
c.Flash.Error(c.GetErrMsg()) c.Flash.Error(c.GetErrMsg())
@ -83,5 +83,5 @@ func DeleteTime(c *context.Context) {
} }
c.Flash.Success(c.Tr("repo.issues.del_time_history", util.SecToTime(t.Time))) c.Flash.Success(c.Tr("repo.issues.del_time_history", util.SecToTime(t.Time)))
c.Redirect(issue.HTMLURL()) c.Redirect(issue.Link())
} }

View File

@ -52,5 +52,5 @@ func IssueWatch(ctx *context.Context) {
return return
} }
ctx.Redirect(issue.HTMLURL()) ctx.Redirect(issue.Link())
} }

View File

@ -13,6 +13,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
project_model "code.gitea.io/gitea/models/project" 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/models/unit"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@ -123,7 +124,8 @@ func Projects(ctx *context.Context) {
// NewProject render creating a project page // NewProject render creating a project page
func NewProject(ctx *context.Context) { func NewProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.new") 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.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)
} }
@ -135,7 +137,8 @@ func NewProjectPost(ctx *context.Context) {
if ctx.HasError() { if ctx.HasError() {
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) 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) ctx.HTML(http.StatusOK, tplProjectsNew)
return return
} }
@ -146,6 +149,7 @@ func NewProjectPost(ctx *context.Context) {
Description: form.Content, Description: form.Content,
CreatorID: ctx.Doer.ID, CreatorID: ctx.Doer.ID,
BoardType: form.BoardType, BoardType: form.BoardType,
CardType: form.CardType,
Type: project_model.TypeRepository, Type: project_model.TypeRepository,
}); err != nil { }); err != nil {
ctx.ServerError("NewProject", err) ctx.ServerError("NewProject", err)
@ -212,6 +216,7 @@ func EditProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.edit") ctx.Data["Title"] = ctx.Tr("repo.projects.edit")
ctx.Data["PageIsEditProjects"] = true ctx.Data["PageIsEditProjects"] = true
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["CardTypes"] = project_model.GetCardConfig()
p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id")) p, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
@ -229,6 +234,7 @@ func EditProject(ctx *context.Context) {
ctx.Data["title"] = p.Title ctx.Data["title"] = p.Title
ctx.Data["content"] = p.Description ctx.Data["content"] = p.Description
ctx.Data["card_type"] = p.CardType
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)
} }
@ -239,6 +245,7 @@ func EditProjectPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.edit") ctx.Data["Title"] = ctx.Tr("repo.projects.edit")
ctx.Data["PageIsEditProjects"] = true ctx.Data["PageIsEditProjects"] = true
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["CardTypes"] = project_model.GetCardConfig()
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)
@ -261,6 +268,7 @@ func EditProjectPost(ctx *context.Context) {
p.Title = form.Title p.Title = form.Title
p.Description = form.Content p.Description = form.Content
p.CardType = form.CardType
if err = project_model.UpdateProject(ctx, p); err != nil { if err = project_model.UpdateProject(ctx, p); err != nil {
ctx.ServerError("UpdateProjects", err) ctx.ServerError("UpdateProjects", err)
return return
@ -302,6 +310,18 @@ func ViewProject(ctx *context.Context) {
return 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) linkedPrsMap := make(map[int64][]*issues_model.Issue)
for _, issuesList := range issuesMap { for _, issuesList := range issuesMap {
for _, issue := range issuesList { for _, issue := range issuesList {

View File

@ -98,7 +98,7 @@ func CreateCodeComment(ctx *context.Context) {
renderConversation(ctx, comment) renderConversation(ctx, comment)
return return
} }
ctx.Redirect(comment.HTMLURL()) ctx.Redirect(comment.Link())
} }
// UpdateResolveConversation add or remove an Conversation resolved mark // UpdateResolveConversation add or remove an Conversation resolved mark

View File

@ -295,7 +295,7 @@ func LatestRelease(ctx *context.Context) {
return return
} }
ctx.Redirect(release.HTMLURL()) ctx.Redirect(release.Link())
} }
// NewRelease render creating or edit release page // NewRelease render creating or edit release page

View File

@ -344,7 +344,7 @@ func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error {
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
} }
ctx.Redirect(ctx.Repo.Repository.HTMLURL()) ctx.Redirect(ctx.Repo.Repository.Link())
return nil return nil
} }

View File

@ -54,7 +54,7 @@ func Search(ctx *context.Context) {
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable() ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable()
} }
ctx.Data["SourcePath"] = ctx.Repo.Repository.HTMLURL() ctx.Data["SourcePath"] = ctx.Repo.Repository.Link()
ctx.Data["SearchResults"] = searchResults ctx.Data["SearchResults"] = searchResults
ctx.Data["SearchResultLanguages"] = searchResultLanguages ctx.Data["SearchResultLanguages"] = searchResultLanguages

View File

@ -318,7 +318,7 @@ func renderReadmeFile(ctx *context.Context, readmeFile *namedBlob, readmeTreelin
if fInfo.isLFSFile { if fInfo.isLFSFile {
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name)) filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name))
ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.HTMLURL(), url.PathEscape(fInfo.lfsMeta.Oid), url.PathEscape(filenameBase64)) ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.Link(), url.PathEscape(fInfo.lfsMeta.Oid), url.PathEscape(filenameBase64))
} }
if !fInfo.isTextFile { if !fInfo.isTextFile {
@ -738,7 +738,7 @@ func Home(ctx *context.Context) {
} }
ctx.Data["EnableFeed"] = true ctx.Data["EnableFeed"] = true
ctx.Data["FeedURL"] = ctx.Repo.Repository.HTMLURL() ctx.Data["FeedURL"] = ctx.Repo.Repository.Link()
} }
checkHomeCodeViewable(ctx) checkHomeCodeViewable(ctx)

View File

@ -376,7 +376,7 @@ func PackageSettingsPost(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success")) ctx.Flash.Success(ctx.Tr("packages.settings.delete.success"))
} }
ctx.Redirect(ctx.Package.Owner.HTMLURL() + "/-/packages") ctx.Redirect(ctx.Package.Owner.HomeLink() + "/-/packages")
return return
} }
} }

View File

@ -47,7 +47,7 @@ func Profile(ctx *context.Context) {
} }
// advertise feed via meta tag // advertise feed via meta tag
ctx.Data["FeedURL"] = ctx.ContextUser.HTMLURL() ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink()
// Show OpenID URIs // Show OpenID URIs
openIDs, err := user_model.GetUserOpenIDs(ctx.ContextUser.ID) openIDs, err := user_model.GetUserOpenIDs(ctx.ContextUser.ID)

View File

@ -59,7 +59,7 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
Creator: creator, Creator: creator,
CommitStatus: &git_model.CommitStatus{ CommitStatus: &git_model.CommitStatus{
SHA: sha, SHA: sha,
TargetURL: run.HTMLURL(), TargetURL: run.Link(),
Description: "", Description: "",
Context: ctxname, Context: ctxname,
CreatorID: payload.Pusher.ID, CreatorID: payload.Pusher.ID,

View File

@ -512,6 +512,7 @@ type CreateProjectForm struct {
Title string `binding:"Required;MaxSize(100)"` Title string `binding:"Required;MaxSize(100)"`
Content string Content string
BoardType project_model.BoardType BoardType project_model.BoardType
CardType project_model.CardType
} }
// UserCreateProjectForm is a from for creating an individual or organization // UserCreateProjectForm is a from for creating an individual or organization
@ -520,6 +521,7 @@ type UserCreateProjectForm struct {
Title string `binding:"Required;MaxSize(100)"` Title string `binding:"Required;MaxSize(100)"`
Content string Content string
BoardType project_model.BoardType BoardType project_model.BoardType
CardType project_model.CardType
UID int64 `binding:"Required"` UID int64 `binding:"Required"`
} }

View File

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
) )
// ContentType repo content type // ContentType repo content type
@ -158,7 +159,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref
return nil, fmt.Errorf("no commit found for the ref [ref: %s]", ref) return nil, fmt.Errorf("no commit found for the ref [ref: %s]", ref)
} }
selfURL, err := url.Parse(fmt.Sprintf("%s/contents/%s?ref=%s", repo.APIURL(), treePath, origRef)) selfURL, err := url.Parse(repo.APIURL() + "/contents/" + util.PathEscapeSegments(treePath) + "?ref=" + url.QueryEscape(origRef))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -217,7 +218,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref
} }
// Handle links // Handle links
if entry.IsRegular() || entry.IsLink() { if entry.IsRegular() || entry.IsLink() {
downloadURL, err := url.Parse(fmt.Sprintf("%s/raw/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath)) downloadURL, err := url.Parse(repo.HTMLURL() + "/raw/" + url.PathEscape(string(refType)) + "/" + util.PathEscapeSegments(ref) + "/" + util.PathEscapeSegments(treePath))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -225,7 +226,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref
contentsResponse.DownloadURL = &downloadURLString contentsResponse.DownloadURL = &downloadURLString
} }
if !entry.IsSubModule() { if !entry.IsSubModule() {
htmlURL, err := url.Parse(fmt.Sprintf("%s/src/%s/%s/%s", repo.HTMLURL(), refType, ref, treePath)) htmlURL, err := url.Parse(repo.HTMLURL() + "/src/" + url.PathEscape(string(refType)) + "/" + util.PathEscapeSegments(ref) + "/" + util.PathEscapeSegments(treePath))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -233,7 +234,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref
contentsResponse.HTMLURL = &htmlURLString contentsResponse.HTMLURL = &htmlURLString
contentsResponse.Links.HTMLURL = &htmlURLString contentsResponse.Links.HTMLURL = &htmlURLString
gitURL, err := url.Parse(fmt.Sprintf("%s/git/blobs/%s", repo.APIURL(), entry.ID.String())) gitURL, err := url.Parse(repo.APIURL() + "/git/blobs/" + url.PathEscape(entry.ID.String()))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -36,7 +36,7 @@
<input type="hidden" name="board_type" value="{{.type}}"> <input type="hidden" name="board_type" value="{{.type}}">
<div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div> <div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div>
<div class="menu"> <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> <div class="item" data-id="{{$element.BoardType}}" data-value="{{$element.BoardType}}">{{$.locale.Tr $element.Translation}}</div>
{{end}} {{end}}
</div> </div>

View File

@ -62,9 +62,9 @@
{{range .Milestones}} {{range .Milestones}}
<li class="item"> <li class="item">
<div class="df ac sb"> <div class="df ac sb">
<h2 class="df ac m-0 fw"> <h3 class="df ac m-0 fw">
{{svg "octicon-milestone" 16 "mr-3"}}<a class="muted" href="{{$.RepoLink}}/milestone/{{.ID}}">{{.Name}}</a> {{svg "octicon-milestone" 16 "mr-3"}}<a class="muted" href="{{$.RepoLink}}/milestone/{{.ID}}">{{.Name}}</a>
</h2> </h3>
<div class="df ac"> <div class="df ac">
<span class="mr-3">{{.Completeness}}%</span> <span class="mr-3">{{.Completeness}}%</span>
<progress value="{{.Completeness}}" max="100"></progress> <progress value="{{.Completeness}}" max="100"></progress>

View File

@ -34,17 +34,38 @@
</div> </div>
{{if not .PageIsEditProjects}} {{if not .PageIsEditProjects}}
<label>{{.locale.Tr "repo.projects.template.desc"}}</label> <div class="field">
<div class="ui selection dropdown"> <label>{{.locale.Tr "repo.projects.template.desc"}}</label>
<input type="hidden" name="board_type" value="{{.type}}"> <div class="ui selection dropdown">
<div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div> <input type="hidden" name="board_type" value="{{.type}}">
<div class="menu"> <div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div>
{{range $element := .ProjectTypes}} <div class="menu">
<div class="item" data-id="{{$element.BoardType}}" data-value="{{$element.BoardType}}">{{$.locale.Tr $element.Translation}}</div> {{range $element := .BoardTypes}}
{{end}} <div class="item" data-id="{{$element.BoardType}}" data-value="{{$element.BoardType}}">{{$.locale.Tr $element.Translation}}</div>
{{end}}
</div>
</div> </div>
</div> </div>
{{end}} {{end}}
<div class="field">
<label>{{.locale.Tr "repo.projects.card_type.desc"}}</label>
<div class="ui selection dropdown">
{{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 := .CardTypes}}
<div class="item" data-id="{{$element.CardType}}" data-value="{{$element.CardType}}">{{$.locale.Tr $element.Translation}}</div>
{{end}}
</div>
</div>
</div>
</div> </div>
<div class="ui container"> <div class="ui container">
<div class="ui divider"></div> <div class="ui divider"></div>

View File

@ -179,6 +179,13 @@
<!-- start issue card --> <!-- start issue card -->
<div class="card board-card" data-issue="{{.ID}}"> <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="content p-0">
<div class="header"> <div class="header">
<span class="dif ac vm {{if .IsClosed}}red{{else}}green{{end}}"> <span class="dif ac vm {{if .IsClosed}}red{{else}}green{{end}}">

View File

@ -81,10 +81,10 @@
{{range .Milestones}} {{range .Milestones}}
<li class="item"> <li class="item">
<div class="df ac sb"> <div class="df ac sb">
<h2 class="df ac m-0 fw"> <h3 class="df ac m-0 fw">
<span class="ui large label">{{.Repo.FullName}}</span> <span class="ui large label">{{.Repo.FullName}}</span>
{{svg "octicon-milestone" 16 "mr-3"}}<a class="muted" href="{{.Repo.Link}}/milestone/{{.ID}}">{{.Name}}</a> {{svg "octicon-milestone" 16 "mr-3"}}<a class="muted" href="{{.Repo.Link}}/milestone/{{.ID}}">{{.Name}}</a>
</h2> </h3>
<div class="df ac"> <div class="df ac">
<span class="mr-3">{{.Completeness}}%</span> <span class="mr-3">{{.Completeness}}%</span>
<progress value="{{.Completeness}}" max="100"></progress> <progress value="{{.Completeness}}" max="100"></progress>

View File

@ -48,7 +48,7 @@
<input type="hidden" name="board_type" value="{{.type}}"> <input type="hidden" name="board_type" value="{{.type}}">
<div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div> <div class="default text">{{.locale.Tr "repo.projects.template.desc_helper"}}</div>
<div class="menu"> <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> <div class="item" data-id="{{$element.BoardType}}" data-value="{{$element.BoardType}}">{{$.locale.Tr $element.Translation}}</div>
{{end}} {{end}}
</div> </div>

View File

@ -12,6 +12,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strconv"
"testing" "testing"
"time" "time"
@ -109,8 +110,6 @@ func TestPackageNuGet(t *testing.T) {
url := fmt.Sprintf("/api/packages/%s/nuget", user.Name) url := fmt.Sprintf("/api/packages/%s/nuget", user.Name)
t.Run("ServiceIndex", func(t *testing.T) { t.Run("ServiceIndex", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
t.Run("v2", func(t *testing.T) { t.Run("v2", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@ -374,8 +373,6 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
}) })
t.Run("SearchService", func(t *testing.T) { t.Run("SearchService", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
cases := []struct { cases := []struct {
Query string Query string
Skip int Skip int
@ -391,8 +388,6 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
} }
t.Run("v2", func(t *testing.T) { t.Run("v2", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
t.Run("Search()", func(t *testing.T) { t.Run("Search()", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@ -406,6 +401,12 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
req = NewRequest(t, "GET", fmt.Sprintf("%s/Search()/$count?searchTerm='%s'&skip=%d&take=%d", url, c.Query, c.Skip, c.Take))
req = AddBasicAuthHeader(req, user.Name)
resp = MakeRequest(t, req, http.StatusOK)
assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i)
} }
}) })
@ -413,7 +414,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
for i, c := range cases { for i, c := range cases {
req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)) req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
req = AddBasicAuthHeader(req, user.Name) req = AddBasicAuthHeader(req, user.Name)
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
@ -422,6 +423,12 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
req = AddBasicAuthHeader(req, user.Name)
resp = MakeRequest(t, req, http.StatusOK)
assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i)
} }
}) })
}) })
@ -512,8 +519,6 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
}) })
t.Run("RegistrationLeaf", func(t *testing.T) { t.Run("RegistrationLeaf", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
t.Run("v2", func(t *testing.T) { t.Run("v2", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@ -549,8 +554,6 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
}) })
t.Run("PackageService", func(t *testing.T) { t.Run("PackageService", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
t.Run("v2", func(t *testing.T) { t.Run("v2", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@ -563,6 +566,12 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
assert.Len(t, result.Entries, 1) assert.Len(t, result.Entries, 1)
assert.Equal(t, packageVersion, result.Entries[0].Properties.Version) assert.Equal(t, packageVersion, result.Entries[0].Properties.Version)
req = NewRequest(t, "GET", fmt.Sprintf("%s/FindPackagesById()/$count?id='%s'", url, packageName))
req = AddBasicAuthHeader(req, user.Name)
resp = MakeRequest(t, req, http.StatusOK)
assert.Equal(t, "1", resp.Body.String())
}) })
t.Run("v3", func(t *testing.T) { t.Run("v3", func(t *testing.T) {

View File

@ -10,7 +10,7 @@
/> />
<a <a
v-if="item.isFile" v-if="item.isFile"
class="file ellipsis" class="file ellipsis muted"
:href="item.isFile ? '#diff-' + item.file.NameHash : ''" :href="item.isFile ? '#diff-' + item.file.NameHash : ''"
>{{ item.name }}</a> >{{ item.name }}</a>
<SvgIcon <SvgIcon
@ -63,7 +63,7 @@ export default {
if (itemIsFile) { if (itemIsFile) {
return; return;
} }
this.$set(this, 'collapsed', !this.collapsed); this.collapsed = !this.collapsed;
}, },
getIconForDiffType(pType) { getIconForDiffType(pType) {
const diffTypes = { const diffTypes = {
@ -83,6 +83,7 @@ export default {
span.svg-icon.status { span.svg-icon.status {
float: right; float: right;
} }
span.svg-icon.file { span.svg-icon.file {
color: var(--color-secondary-dark-7); color: var(--color-secondary-dark-7);
} }
@ -122,6 +123,8 @@ span.svg-icon.octicon-diff-renamed {
div.directory { div.directory {
display: grid; display: grid;
grid-template-columns: 18px 20px auto; grid-template-columns: 18px 20px auto;
user-select: none;
cursor: pointer;
} }
div.directory:hover { div.directory:hover {

View File

@ -1803,6 +1803,7 @@ footer {
} }
} }
/* TODO: remove in favor of .hidden helper */
.hide { .hide {
display: none; display: none;

View File

@ -1614,6 +1614,20 @@
margin-right: .25rem; margin-right: .25rem;
} }
// Because the translations contain the <strong> we need to style with nth-of-type
.diff-detail-stats strong:nth-of-type(1) {
color: var(--color-yellow);
}
.diff-detail-stats strong:nth-of-type(2) {
color: var(--color-green);
}
.diff-detail-stats strong:nth-of-type(3) {
color: var(--color-red);
}
.diff-detail-stats { .diff-detail-stats {
@media (max-width: 480px) { @media (max-width: 480px) {
font-size: 0; font-size: 0;

View File

@ -72,6 +72,10 @@
margin-right: auto !important; margin-right: auto !important;
} }
.board-column .ui.cards > .card > .content {
border: none;
}
.board-card { .board-card {
margin: 4px 2px !important; margin: 4px 2px !important;
border-radius: 5px !important; border-radius: 5px !important;
@ -90,6 +94,25 @@
font-size: 16px !important; 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 { .card-ghost {
border-style: dashed !important; border-style: dashed !important;
background: none !important; background: none !important;

View File

@ -22,6 +22,7 @@
/* below class names match Tailwind CSS */ /* below class names match Tailwind CSS */
.pointer-events-none { pointer-events: none !important; } .pointer-events-none { pointer-events: none !important; }
.relative { position: relative !important; } .relative { position: relative !important; }
.hidden { display: none !important; }
.mono { .mono {
font-family: var(--fonts-monospace) !important; font-family: var(--fonts-monospace) !important;