Browse Source

#1253 : List, create and update repository labels from user setting

pull/5260/head
isundil 7 years ago
parent
commit
cb48edb6d6
  1. 1
      cmd/web.go
  2. 2
      models/models.go
  3. 141
      models/repo.go
  4. 7
      public/css/gogs.css
  5. 45
      public/js/gogs.js
  6. 1
      public/less/_repository.less
  7. 37
      routes/user/setting.go
  8. 57
      templates/user/settings/repositories.tmpl

1
cmd/web.go

@ -230,6 +230,7 @@ func runWeb(c *cli.Context) error {
m.Group("/repositories", func() { m.Group("/repositories", func() {
m.Get("", user.SettingsRepos) m.Get("", user.SettingsRepos)
m.Post("/leave", user.SettingsLeaveRepo) m.Post("/leave", user.SettingsLeaveRepo)
m.Post("/labels", user.SettingsAddOrEditRepoLabel)
}) })
m.Group("/organizations", func() { m.Group("/organizations", func() {
m.Get("", user.SettingsOrganizations) m.Get("", user.SettingsOrganizations)

2
models/models.go

@ -65,7 +65,7 @@ func init() {
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(HookTask), new(Mirror), new(Release), new(LoginSource), new(Webhook), new(HookTask),
new(ProtectBranch), new(ProtectBranchWhitelist), new(ProtectBranch), new(ProtectBranchWhitelist),
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo), new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
new(Notice), new(EmailAddress)) new(Notice), new(EmailAddress), new (RepositoryLabel))
gonicNames := []string{"SSL"} gonicNames := []string{"SSL"}
for _, name := range gonicNames { for _, name := range gonicNames {

141
models/repo.go

@ -8,6 +8,8 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"html/template"
"strconv"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@ -139,6 +141,23 @@ func NewRepoContext() {
RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp")) RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(setting.AppDataPath, "tmp"))
} }
// Repository label
type RepositoryLabel struct {
ID int64
OwnerID int64
Owner *User `xorm:"-"`
Name string `xorm:"INDEX NOT NULL"`
Color string `xorm:"VARCHAR(7)"`
IsPrivate bool
NumRepo int
Created time.Time `xorm:"-"`
CreatedUnix int64
Updated time.Time `xorm:"-"`
UpdatedUnix int64
}
// Repository contains information of a repository. // Repository contains information of a repository.
type Repository struct { type Repository struct {
ID int64 ID int64
@ -1619,6 +1638,128 @@ func GetUserAndCollaborativeRepositories(userID int64) ([]*Repository, error) {
return append(repos, ownRepos...), nil return append(repos, ownRepos...), nil
} }
// ForegroundColor calculates the text color for labels based
// on their background color.
func (l *RepositoryLabel) ForegroundColor() template.CSS {
if strings.HasPrefix(l.Color, "#") {
if color, err := strconv.ParseUint(l.Color[1:], 16, 64); err == nil {
r := float32(0xFF & (color >> 16))
g := float32(0xFF & (color >> 8))
b := float32(0xFF & color)
luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255
if luminance < 0.66 {
return template.CSS("#fff")
}
}
}
// default to black
return template.CSS("#000")
}
func CreateRepositoryLabel(owner *User, opts *CreateRepoLabelOptions) (_ *RepositoryLabel, err error) {
if !owner.CanCreateRepo() {
return nil, errors.ReachLimitOfRepo{owner.RepoCreationNum()}
}
// FIXME check color is /#[0-9A-Fa-F]{6}/
repoLabel := &RepositoryLabel{
OwnerID: owner.ID,
Owner: owner,
Name: opts.Name,
Color: opts.Color,
IsPrivate: opts.IsPrivate,
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return nil, err
}
if err = createRepositoryLabel(sess, repoLabel); err != nil {
return nil, err
}
return repoLabel, sess.Commit()
}
func UpdateRepositoryLabel(id int64, owner *User, opts *CreateRepoLabelOptions) (_ *RepositoryLabel, err error) {
if !owner.CanCreateRepo() {
return nil, errors.ReachLimitOfRepo{owner.RepoCreationNum()}
}
// FIXME check color is /#[0-9A-Fa-F]{6}/
repoLabel, err := GetRepositoryLabel(id)
if err != nil {
return nil, err
}
// FIXME check user.ID is repoLabel.OwnerID
repoLabel.Name = opts.Name
repoLabel.Color = opts.Color
repoLabel.IsPrivate = opts.IsPrivate
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return nil, err
}
// Use same rules for label name than for repository names
if err = IsUsableRepoName(repoLabel.Name); err != nil {
return nil, err
}
if _, err = sess.ID(repoLabel.ID).Update(repoLabel); err != nil {
return nil, err
}
return repoLabel, sess.Commit()
}
func GetRepositoryLabels(userID int64) ([]*RepositoryLabel, error) {
labels := make([]*RepositoryLabel, 0, 5)
if err := x.Where("owner_id = ?", userID).Find(&labels); err != nil {
return nil, fmt.Errorf("select repository labels: %v", err)
}
return labels, nil
}
func GetRepositoryLabel(labelId int64) (*RepositoryLabel, error) {
label := new(RepositoryLabel)
if has, err := x.Where("ID = ?", labelId).Get(label); err != nil {
return nil, fmt.Errorf("select repository labels: %v", err)
} else if !has {
return nil, fmt.Errorf("Label %d not found", labelId)
}
return label, nil
}
type CreateRepoLabelOptions struct {
Name string
IsPrivate bool
Color string
}
func createRepositoryLabel(e *xorm.Session, repoLabel *RepositoryLabel) (err error) {
// Use same rules for label name than for repository names
if err = IsUsableRepoName(repoLabel.Name); err != nil {
return err
}
if _, err = e.Insert(repoLabel); err != nil {
return err
}
return nil
}
func getRepositoryCount(e Engine, u *User) (int64, error) { func getRepositoryCount(e Engine, u *User) (int64, error) {
return x.Count(&Repository{OwnerID: u.ID}) return x.Count(&Repository{OwnerID: u.ID})
} }

7
public/css/gogs.css

@ -2613,25 +2613,30 @@ footer .ui.language .menu {
background: #f0f0f0; background: #f0f0f0;
} }
.edit-label.modal .form .column, .edit-label.modal .form .column,
.add-edit-label.modal .form .column,
.new-label.segment .form .column { .new-label.segment .form .column {
padding-right: 0; padding-right: 0;
} }
.edit-label.modal .form .buttons, .edit-label.modal .form .buttons,
.add-edit-label.modal .form .buttons,
.new-label.segment .form .buttons { .new-label.segment .form .buttons {
margin-left: auto; margin-left: auto;
padding-top: 15px; padding-top: 15px;
} }
.edit-label.modal .form .color.picker.column, .edit-label.modal .form .color.picker.column,
.add-edit-label.modal .form .color.picker.column,
.new-label.segment .form .color.picker.column { .new-label.segment .form .color.picker.column {
width: auto; width: auto;
} }
.edit-label.modal .form .color.picker.column .color-picker, .edit-label.modal .form .color.picker.column .color-picker,
.add-edit-label.modal .form .color.picker.column .color-picker,
.new-label.segment .form .color.picker.column .color-picker { .new-label.segment .form .color.picker.column .color-picker {
height: 35px; height: 35px;
width: auto; width: auto;
padding-left: 30px; padding-left: 30px;
} }
.edit-label.modal .form .minicolors-swatch.minicolors-sprite, .edit-label.modal .form .minicolors-swatch.minicolors-sprite,
.add-edit-label.modal .form .minicolors-swatch.minicolors-sprite,
.new-label.segment .form .minicolors-swatch.minicolors-sprite { .new-label.segment .form .minicolors-swatch.minicolors-sprite {
top: 10px; top: 10px;
left: 10px; left: 10px;
@ -2639,6 +2644,7 @@ footer .ui.language .menu {
height: 15px; height: 15px;
} }
.edit-label.modal .form .precolors, .edit-label.modal .form .precolors,
.add-edit-label.modal .form .precolors,
.new-label.segment .form .precolors { .new-label.segment .form .precolors {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
@ -2646,6 +2652,7 @@ footer .ui.language .menu {
width: 120px; width: 120px;
} }
.edit-label.modal .form .precolors .color, .edit-label.modal .form .precolors .color,
.add-edit-label.modal .form .precolors .color,
.new-label.segment .form .precolors .color { .new-label.segment .form .precolors .color {
float: left; float: left;
width: 15px; width: 15px;

45
public/js/gogs.js

@ -1089,6 +1089,51 @@ function initUserSettings() {
} }
}); });
} }
// Repository labels
if ($('.repo-labels').length > 0) {
var $newLabelPanel = $('.add-edit-label.modal'),
$header = $newLabelPanel.find('.header'),
$submitButton = $newLabelPanel.find('.actions .positive');
$newLabelPanel.find('.color-picker').first().minicolors();
$('.new-label.button').click(function () {
$('.label-name').val('');
$('.label-id').val('');
$header.html($header.data('createtext'));
$submitButton.html($submitButton.data('createtext'));
var defaultColor = $newLabelPanel.find('.precolors .color').first().data('color-hex');
$('.color-picker').val(defaultColor);
$('.minicolors-swatch-color').css("background-color", defaultColor);
$newLabelPanel.modal('show');
});
$('.edit-label-button').click(function() {
var $this = $(this);
$('.label-name').val($this.data('name'));
$('.label-id').val($this.data('id'));
$header.html($header.data('edittext'));
$submitButton.html($submitButton.data('edittext'));
$('.color-picker').val($this.data('color'));
$('.minicolors-swatch-color').css("background-color", $this.data('color'));
$newLabelPanel.modal('show');
});
$('.add-edit-label .submit').click(function (e) {
e.stopPropagation();
$('.add-edit-label .form').submit();
});
$('.precolors .color').click(function () {
var color = $(this).data('color-hex');
$('.color-picker').val(color);
$('.minicolors-swatch-color').css("background-color", color);
});
}
} }
function initRepositoryCollaboration() { function initRepositoryCollaboration() {

1
public/less/_repository.less

@ -1643,6 +1643,7 @@
} }
.edit-label.modal, .edit-label.modal,
.add-edit-label.modal,
.new-label.segment { .new-label.segment {
.form { .form {
.column { .column {

37
routes/user/setting.go

@ -8,6 +8,7 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strconv"
"html/template" "html/template"
"image/png" "image/png"
"io/ioutil" "io/ioutil"
@ -521,10 +522,46 @@ func SettingsRepos(c *context.Context) {
return return
} }
c.Data["Repos"] = repos c.Data["Repos"] = repos
c.Data["RequireMinicolors"] = true
repoLabels, err := models.GetRepositoryLabels(c.User.ID)
if err != nil {
c.ServerError("GetUserAndCollaborativeRepositories", err)
return
}
if err = models.RepositoryList(repos).LoadAttributes(); err != nil {
c.ServerError("LoadAttributes", err)
return
}
c.Data["RepoLabels"] = repoLabels
c.Success(SETTINGS_REPOSITORIES) c.Success(SETTINGS_REPOSITORIES)
} }
func SettingsAddOrEditRepoLabel(c *context.Context) {
id, _ := strconv.ParseInt(c.Query("id"), 10, 64)
repoLabelOptions := &models.CreateRepoLabelOptions{
Name: c.Query("name"),
Color: c.Query("color"),
IsPrivate: false,
}
if id != 0 {
// update
if _, err := models.UpdateRepositoryLabel(id, c.User, repoLabelOptions); err != nil {
c.ServerError("UpdateRepositoryLabel", err)
return
}
c.Redirect(setting.AppSubURL + "/user/settings/repositories")
} else {
// create
if _, err := models.CreateRepositoryLabel(c.User, repoLabelOptions); err != nil {
c.ServerError("AddRepositoryLabel", err)
return
}
c.Redirect(setting.AppSubURL + "/user/settings/repositories")
}
}
func SettingsLeaveRepo(c *context.Context) { func SettingsLeaveRepo(c *context.Context) {
repo, err := models.GetRepositoryByID(c.QueryInt64("id")) repo, err := models.GetRepositoryByID(c.QueryInt64("id"))
if err != nil { if err != nil {

57
templates/user/settings/repositories.tmpl

@ -37,7 +37,64 @@
{{end}} {{end}}
</div> </div>
</div> </div>
<h4 class="ui top attached header">
{{.i18n.Tr "settings.repoLabels"}}
<div class="ui right">
<div class="ui blue tiny new-label button">{{.i18n.Tr "settings.add_repo_label"}}</div>
</div>
</h4>
<div class="ui segment attached repo-labels">
<div class="ui list">
{{range .RepoLabels}}
<div class="item">
<span class="column">
<span class="ui label" style="background-color: {{.Color}}; color: {{.ForegroundColor}}">
{{.Name}}
</span>
<div class="ui right">
<a class="ui right delete-button" href="#" data-url="{{$.Link}}/labels/delete" data-id="{{.ID}}"><i class="octicon octicon-trashcan"></i> {{$.i18n.Tr "repo.label_delete"}}</a>
<a class="ui right edit-label-button" href="#" data-id={{.ID}} data-name={{.Name}} data-color={{.Color}}><i class="octicon octicon-pencil"></i> {{$.i18n.Tr "repo.issues.label_edit"}}</a>
<a class="ui right repositories" href="/issues?labels={{.ID}}"><i class="octicon octicon-repo"></i> {{$.i18n.Tr "repo.repositories" .NumRepo}}</a>
</div>
</span>
</div>
{{end}}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="ui small add-edit-label modal">
<div class="header" data-createText="{{.i18n.Tr "repo.issues.label_create"}}" data-editText="{{.i18n.Tr "repo.issues.label_modify"}}">
</div>
<div class="content">
<form class="ui form" action="{{$.Link}}/labels" method="post">
{{.CSRFTokenHTML}}
<input name="id" type="hidden" class="label-id">
<div class="ui grid">
<div class="five wide column">
<div class="ui small input">
<input class="label-name" name="name" placeholder="{{.i18n.Tr "repo.issues.new_label_placeholder"}}" autofocus required>
</div>
</div>
<div class="color picker column">
<input class="color-picker" name="color" value="#70c24a" required>
</div>
<div class="column precolors">
{{template "repo/issue/label_precolors"}}
</div>
</div> </div>
</form>
</div>
<div class="actions">
<div class="ui negative button">{{.i18n.Tr "modal.no"}}</div>
<div class="ui positive right submit labeled icon button">
{{.i18n.Tr "modal.send"}}
<i class="checkmark icon"></i>
</div> </div>
</div> </div>
</div> </div>

Loading…
Cancel
Save