@ -7,12 +7,12 @@ package issues
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@ -78,9 +78,6 @@ func (err ErrLabelNotExist) Unwrap() error {
return util . ErrNotExist
}
// LabelColorPattern is a regexp witch can validate LabelColor
var LabelColorPattern = regexp . MustCompile ( "^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$" )
// Label represents a label of repository for issues.
type Label struct {
ID int64 ` xorm:"pk autoincr" `
@ -109,12 +106,12 @@ func init() {
}
// CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues.
func ( l abel * Label ) CalOpenIssues ( ) {
l abel . NumOpenIssues = l abel . NumIssues - labe l. NumClosedIssues
func ( l * Label ) CalOpenIssues ( ) {
l . NumOpenIssues = l . NumIssues - l. NumClosedIssues
}
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
func ( l abel * Label ) CalOpenOrgIssues ( ctx context . Context , repoID , labelID int64 ) {
func ( l * Label ) CalOpenOrgIssues ( ctx context . Context , repoID , labelID int64 ) {
counts , _ := CountIssuesByRepo ( ctx , & IssuesOptions {
RepoID : repoID ,
LabelIDs : [ ] int64 { labelID } ,
@ -122,22 +119,22 @@ func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64)
} )
for _ , count := range counts {
l abel . NumOpenRepoIssues += count
l . NumOpenRepoIssues += count
}
}
// LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked
func ( l abel * Label ) LoadSelectedLabelsAfterClick ( currentSelectedLabels [ ] int64 , currentSelectedExclusiveScopes [ ] string ) {
func ( l * Label ) LoadSelectedLabelsAfterClick ( currentSelectedLabels [ ] int64 , currentSelectedExclusiveScopes [ ] string ) {
var labelQuerySlice [ ] string
labelSelected := false
labelID := strconv . FormatInt ( l abel . ID , 10 )
labelScope := l abel . ExclusiveScope ( )
labelID := strconv . FormatInt ( l . ID , 10 )
labelScope := l . ExclusiveScope ( )
for i , s := range currentSelectedLabels {
if s == l abel . ID {
if s == l . ID {
labelSelected = true
} else if - s == l abel . ID {
} else if - s == l . ID {
labelSelected = true
l abel . IsExcluded = true
l . IsExcluded = true
} else if s != 0 {
// Exclude other labels in the same scope from selection
if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes [ i ] {
@ -148,23 +145,23 @@ func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64,
if ! labelSelected {
labelQuerySlice = append ( labelQuerySlice , labelID )
}
l abel . IsSelected = labelSelected
l abel . QueryString = strings . Join ( labelQuerySlice , "," )
l . IsSelected = labelSelected
l . QueryString = strings . Join ( labelQuerySlice , "," )
}
// BelongsToOrg returns true if label is an organization label
func ( l abel * Label ) BelongsToOrg ( ) bool {
return l abel . OrgID > 0
func ( l * Label ) BelongsToOrg ( ) bool {
return l . OrgID > 0
}
// BelongsToRepo returns true if label is a repository label
func ( l abel * Label ) BelongsToRepo ( ) bool {
return l abel . RepoID > 0
func ( l * Label ) BelongsToRepo ( ) bool {
return l . RepoID > 0
}
// Get color as RGB values in 0..255 range
func ( l abel * Label ) ColorRGB ( ) ( float64 , float64 , float64 , error ) {
color , err := strconv . ParseUint ( l abel . Color [ 1 : ] , 16 , 64 )
func ( l * Label ) ColorRGB ( ) ( float64 , float64 , float64 , error ) {
color , err := strconv . ParseUint ( l . Color [ 1 : ] , 16 , 64 )
if err != nil {
return 0 , 0 , 0 , err
}
@ -176,9 +173,9 @@ func (label *Label) ColorRGB() (float64, float64, float64, error) {
}
// Determine if label text should be light or dark to be readable on background color
func ( l abel * Label ) UseLightTextColor ( ) bool {
if strings . HasPrefix ( l abel . Color , "#" ) {
if r , g , b , err := l abel . ColorRGB ( ) ; err == nil {
func ( l * Label ) UseLightTextColor ( ) bool {
if strings . HasPrefix ( l . Color , "#" ) {
if r , g , b , err := l . ColorRGB ( ) ; err == nil {
// Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast
// In the future WCAG 3 APCA may be a better solution
brightness := ( 0.299 * r + 0.587 * g + 0.114 * b ) / 255
@ -190,40 +187,26 @@ func (label *Label) UseLightTextColor() bool {
}
// Return scope substring of label name, or empty string if none exists
func ( l abel * Label ) ExclusiveScope ( ) string {
if ! l abel . Exclusive {
func ( l * Label ) ExclusiveScope ( ) string {
if ! l . Exclusive {
return ""
}
lastIndex := strings . LastIndex ( l abel . Name , "/" )
if lastIndex == - 1 || lastIndex == 0 || lastIndex == len ( l abel . Name ) - 1 {
lastIndex := strings . LastIndex ( l . Name , "/" )
if lastIndex == - 1 || lastIndex == 0 || lastIndex == len ( l . Name ) - 1 {
return ""
}
return l abel . Name [ : lastIndex ]
return l . Name [ : lastIndex ]
}
// NewLabel creates a new label
func NewLabel ( ctx context . Context , label * Label ) error {
if ! LabelColorPattern . MatchString ( label . Color ) {
return fmt . Errorf ( "bad color code: %s" , label . Color )
}
// normalize case
label . Color = strings . ToLower ( label . Color )
// add leading hash
if label . Color [ 0 ] != '#' {
label . Color = "#" + label . Color
}
// convert 3-character shorthand into 6-character version
if len ( label . Color ) == 4 {
r := label . Color [ 1 ]
g := label . Color [ 2 ]
b := label . Color [ 3 ]
label . Color = fmt . Sprintf ( "#%c%c%c%c%c%c" , r , r , g , g , b , b )
func NewLabel ( ctx context . Context , l * Label ) error {
color , err := label . NormalizeColor ( l . Color )
if err != nil {
return err
}
l . Color = color
return db . Insert ( ctx , l abel )
return db . Insert ( ctx , l )
}
// NewLabels creates new labels
@ -234,11 +217,14 @@ func NewLabels(labels ...*Label) error {
}
defer committer . Close ( )
for _ , label := range labels {
if ! LabelColorPattern . MatchString ( label . Color ) {
return fmt . Errorf ( "bad color code: %s" , label . Color )
for _ , l := range labels {
color , err := label . NormalizeColor ( l . Color )
if err != nil {
return err
}
if err := db . Insert ( ctx , label ) ; err != nil {
l . Color = color
if err := db . Insert ( ctx , l ) ; err != nil {
return err
}
}
@ -247,15 +233,18 @@ func NewLabels(labels ...*Label) error {
// UpdateLabel updates label information.
func UpdateLabel ( l * Label ) error {
if ! LabelColorPattern . MatchString ( l . Color ) {
return fmt . Errorf ( "bad color code: %s" , l . Color )
color , err := label . NormalizeColor ( l . Color )
if err != nil {
return err
}
l . Color = color
return updateLabelCols ( db . DefaultContext , l , "name" , "description" , "color" , "exclusive" )
}
// DeleteLabel delete a label
func DeleteLabel ( id , labelID int64 ) error {
l abel , err := GetLabelByID ( db . DefaultContext , labelID )
l , err := GetLabelByID ( db . DefaultContext , labelID )
if err != nil {
if IsErrLabelNotExist ( err ) {
return nil
@ -271,10 +260,10 @@ func DeleteLabel(id, labelID int64) error {
sess := db . GetEngine ( ctx )
if l abel . BelongsToOrg ( ) && labe l. OrgID != id {
if l . BelongsToOrg ( ) && l. OrgID != id {
return nil
}
if l abel . BelongsToRepo ( ) && labe l. RepoID != id {
if l . BelongsToRepo ( ) && l. RepoID != id {
return nil
}
@ -682,14 +671,14 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
if err = issue . LoadRepo ( ctx ) ; err != nil {
return err
}
for _ , l abel := range labels {
for _ , l := range labels {
// Don't add already present labels and invalid labels
if HasIssueLabel ( ctx , issue . ID , l abel . ID ) ||
( l abel . RepoID != issue . RepoID && labe l. OrgID != issue . Repo . OwnerID ) {
if HasIssueLabel ( ctx , issue . ID , l . ID ) ||
( l . RepoID != issue . RepoID && l. OrgID != issue . Repo . OwnerID ) {
continue
}
if err = newIssueLabel ( ctx , issue , l abel , doer ) ; err != nil {
if err = newIssueLabel ( ctx , issue , l , doer ) ; err != nil {
return fmt . Errorf ( "newIssueLabel: %w" , err )
}
}