Browse Source

repo: add changes to repository avatar feature (#5221)

pull/5340/head
Unknwon 6 years ago
parent
commit
376a629c9f
No known key found for this signature in database
GPG Key ID: 7A02C406FAC875A2
  1. 2
      Makefile
  2. 2
      cmd/backup.go
  3. 2
      cmd/restore.go
  4. 6
      cmd/web.go
  5. 2
      gogs.go
  6. 4
      models/access.go
  7. 59
      models/repo.go
  8. 40
      models/user.go
  9. 62
      pkg/bindata/bindata.go
  10. 3
      public/css/gogs.css
  11. 4
      public/less/_base.less
  12. 12
      public/less/_dashboard.less
  13. 114
      routes/repo/setting.go
  14. 14
      routes/user/setting.go
  15. 2
      templates/.VERSION
  16. 38
      templates/explore/repo_list.tmpl
  17. 1
      templates/org/team/repositories.tmpl
  18. 8
      templates/repo/header.tmpl
  19. 1
      templates/repo/settings/options.tmpl
  20. 8
      templates/user/dashboard/dashboard.tmpl

2
Makefile

@ -62,7 +62,7 @@ pkg/bindata/bindata.go: $(DATA_FILES)
less: public/css/gogs.css less: public/css/gogs.css
public/css/gogs.css: $(LESS_FILES) public/css/gogs.css: $(LESS_FILES)
lessc $< >$@ @type lessc >/dev/null 2>&1 && lessc $< >$@ || echo "lessc command not found, skipped."
clean: clean:
go clean -i ./... go clean -i ./...

2
cmd/backup.go

@ -100,7 +100,7 @@ func runBackup(c *cli.Context) error {
// Data files // Data files
if !c.Bool("database-only") { if !c.Bool("database-only") {
for _, dir := range []string{"attachments", "avatars"} { for _, dir := range []string{"attachments", "avatars", "repo-avatars"} {
dirPath := path.Join(setting.AppDataPath, dir) dirPath := path.Join(setting.AppDataPath, dir)
if !com.IsDir(dirPath) { if !com.IsDir(dirPath) {
continue continue

2
cmd/restore.go

@ -115,7 +115,7 @@ func runRestore(c *cli.Context) error {
// Data files // Data files
if !c.Bool("database-only") { if !c.Bool("database-only") {
os.MkdirAll(setting.AppDataPath, os.ModePerm) os.MkdirAll(setting.AppDataPath, os.ModePerm)
for _, dir := range []string{"attachments", "avatars"} { for _, dir := range []string{"attachments", "avatars", "repo-avatars"} {
// Skip if backup archive does not have corresponding data // Skip if backup archive does not have corresponding data
srcPath := path.Join(archivePath, "data", dir) srcPath := path.Join(archivePath, "data", dir)
if !com.IsDir(srcPath) { if !com.IsDir(srcPath) {

6
cmd/web.go

@ -64,7 +64,7 @@ func checkVersion() {
if err != nil { if err != nil {
log.Fatal(2, "Fail to read 'templates/.VERSION': %v", err) log.Fatal(2, "Fail to read 'templates/.VERSION': %v", err)
} }
tplVer := string(data) tplVer := strings.TrimSpace(string(data))
if tplVer != setting.AppVer { if tplVer != setting.AppVer {
if version.Compare(tplVer, setting.AppVer, ">") { if version.Compare(tplVer, setting.AppVer, ">") {
log.Fatal(2, "Binary version is lower than template file version, did you forget to recompile Gogs?") log.Fatal(2, "Binary version is lower than template file version, did you forget to recompile Gogs?")
@ -96,14 +96,14 @@ func newMacaron() *macaron.Macaron {
m.Use(macaron.Static( m.Use(macaron.Static(
setting.AvatarUploadPath, setting.AvatarUploadPath,
macaron.StaticOptions{ macaron.StaticOptions{
Prefix: "avatars", Prefix: models.USER_AVATAR_URL_PREFIX,
SkipLogging: setting.DisableRouterLog, SkipLogging: setting.DisableRouterLog,
}, },
)) ))
m.Use(macaron.Static( m.Use(macaron.Static(
setting.RepositoryAvatarUploadPath, setting.RepositoryAvatarUploadPath,
macaron.StaticOptions{ macaron.StaticOptions{
Prefix: "repo-avatars", Prefix: models.REPO_AVATAR_URL_PREFIX,
SkipLogging: setting.DisableRouterLog, SkipLogging: setting.DisableRouterLog,
}, },
)) ))

2
gogs.go

@ -16,7 +16,7 @@ import (
"github.com/gogs/gogs/pkg/setting" "github.com/gogs/gogs/pkg/setting"
) )
const APP_VER = "0.11.57.0617" const APP_VER = "0.11.58.0617"
func init() { func init() {
setting.AppVer = APP_VER setting.AppVer = APP_VER

4
models/access.go

@ -237,6 +237,6 @@ func (repo *Repository) recalculateAccesses(e Engine) error {
} }
// RecalculateAccesses recalculates all accesses for repository. // RecalculateAccesses recalculates all accesses for repository.
func (r *Repository) RecalculateAccesses() error { func (repo *Repository) RecalculateAccesses() error {
return r.recalculateAccesses(x) return repo.recalculateAccesses(x)
} }

59
models/repo.go

@ -7,6 +7,9 @@ package models
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"image"
_ "image/jpeg"
"image/png"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
@ -15,15 +18,12 @@ import (
"sort" "sort"
"strings" "strings"
"time" "time"
"image"
_ "image/jpeg"
"image/png"
"github.com/Unknwon/cae/zip" "github.com/Unknwon/cae/zip"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/go-xorm/xorm" "github.com/go-xorm/xorm"
"github.com/nfnt/resize"
"github.com/mcuadros/go-version" "github.com/mcuadros/go-version"
"github.com/nfnt/resize"
log "gopkg.in/clog.v1" log "gopkg.in/clog.v1"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
@ -39,6 +39,9 @@ import (
"github.com/gogs/gogs/pkg/sync" "github.com/gogs/gogs/pkg/sync"
) )
// REPO_AVATAR_URL_PREFIX is used to identify a URL is to access repository avatar.
const REPO_AVATAR_URL_PREFIX = "repo-avatars"
var repoWorkingPool = sync.NewExclusivePool() var repoWorkingPool = sync.NewExclusivePool()
var ( var (
@ -146,16 +149,18 @@ func NewRepoContext() {
// Repository contains information of a repository. // Repository contains information of a repository.
type Repository struct { type Repository struct {
ID int64 ID int64
OwnerID int64 `xorm:"UNIQUE(s)"` OwnerID int64 `xorm:"UNIQUE(s)"`
Owner *User `xorm:"-" json:"-"` Owner *User `xorm:"-" json:"-"`
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
Name string `xorm:"INDEX NOT NULL"` Name string `xorm:"INDEX NOT NULL"`
Description string `xorm:"VARCHAR(512)"` Description string `xorm:"VARCHAR(512)"`
Website string Website string
DefaultBranch string DefaultBranch string
Size int64 `xorm:"NOT NULL DEFAULT 0"` Size int64 `xorm:"NOT NULL DEFAULT 0"`
UseCustomAvatar bool
// Counters
NumWatches int NumWatches int
NumStars int NumStars int
NumForks int NumForks int
@ -302,10 +307,10 @@ func (repo *Repository) RelAvatarLink() string {
if !com.IsExist(repo.CustomAvatarPath()) { if !com.IsExist(repo.CustomAvatarPath()) {
return defaultImgUrl return defaultImgUrl
} }
return setting.AppSubURL + "/repo-avatars/" + com.ToStr(repo.ID) return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, REPO_AVATAR_URL_PREFIX, repo.ID)
} }
// AvatarLink returns user avatar absolute link. // AvatarLink returns repository avatar absolute link.
func (repo *Repository) AvatarLink() string { func (repo *Repository) AvatarLink() string {
link := repo.RelAvatarLink() link := repo.RelAvatarLink()
if link[0] == '/' && link[1] != '/' { if link[0] == '/' && link[1] != '/' {
@ -315,24 +320,23 @@ func (repo *Repository) AvatarLink() string {
} }
// UploadAvatar saves custom avatar for repository. // UploadAvatar saves custom avatar for repository.
// FIXME: split uploads to different subdirs // FIXME: split uploads to different subdirs in case we have massive number of repositories.
// in case we have massive number of repositories.
func (repo *Repository) UploadAvatar(data []byte) error { func (repo *Repository) UploadAvatar(data []byte) error {
img, _, err := image.Decode(bytes.NewReader(data)) img, _, err := image.Decode(bytes.NewReader(data))
if err != nil { if err != nil {
return fmt.Errorf("Decode: %v", err) return fmt.Errorf("decode image: %v", err)
} }
m := resize.Resize(avatar.AVATAR_SIZE, avatar.AVATAR_SIZE, img, resize.NearestNeighbor)
os.MkdirAll(setting.RepositoryAvatarUploadPath, os.ModePerm) os.MkdirAll(setting.RepositoryAvatarUploadPath, os.ModePerm)
fw, err := os.Create(repo.CustomAvatarPath()) fw, err := os.Create(repo.CustomAvatarPath())
if err != nil { if err != nil {
return fmt.Errorf("Create: %v", err) return fmt.Errorf("create custom avatar directory: %v", err)
} }
defer fw.Close() defer fw.Close()
m := resize.Resize(avatar.AVATAR_SIZE, avatar.AVATAR_SIZE, img, resize.NearestNeighbor)
if err = png.Encode(fw, m); err != nil { if err = png.Encode(fw, m); err != nil {
return fmt.Errorf("Encode: %v", err) return fmt.Errorf("encode image: %v", err)
} }
return nil return nil
@ -341,7 +345,12 @@ func (repo *Repository) UploadAvatar(data []byte) error {
// DeleteAvatar deletes the repository custom avatar. // DeleteAvatar deletes the repository custom avatar.
func (repo *Repository) DeleteAvatar() error { func (repo *Repository) DeleteAvatar() error {
log.Trace("DeleteAvatar [%d]: %s", repo.ID, repo.CustomAvatarPath()) log.Trace("DeleteAvatar [%d]: %s", repo.ID, repo.CustomAvatarPath())
return os.Remove(repo.CustomAvatarPath()) if err := os.Remove(repo.CustomAvatarPath()); err != nil {
return err
}
repo.UseCustomAvatar = false
return UpdateRepository(repo, false)
} }
// This method assumes following fields have been assigned with valid values: // This method assumes following fields have been assigned with valid values:
@ -372,8 +381,8 @@ func (repo *Repository) APIFormat(permission *api.Permission, user ...*User) *ap
Created: repo.Created, Created: repo.Created,
Updated: repo.Updated, Updated: repo.Updated,
Permissions: permission, Permissions: permission,
// Reserved for go-gogs-client change // Reserved for go-gogs-client change
// AvatarUrl: repo.AvatarLink(), // AvatarUrl: repo.AvatarLink(),
} }
if repo.IsFork { if repo.IsFork {
p := &api.Permission{Pull: true} p := &api.Permission{Pull: true}

40
models/user.go

@ -35,6 +35,9 @@ import (
"github.com/gogs/gogs/pkg/tool" "github.com/gogs/gogs/pkg/tool"
) )
// USER_AVATAR_URL_PREFIX is used to identify a URL is to access user avatar.
const USER_AVATAR_URL_PREFIX = "avatars"
type UserType int type UserType int
const ( const (
@ -257,7 +260,7 @@ func (u *User) RelAvatarLink() string {
if !com.IsExist(u.CustomAvatarPath()) { if !com.IsExist(u.CustomAvatarPath()) {
return defaultImgUrl return defaultImgUrl
} }
return setting.AppSubURL + "/avatars/" + com.ToStr(u.ID) return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, USER_AVATAR_URL_PREFIX, u.ID)
case setting.DisableGravatar, setting.OfflineMode: case setting.DisableGravatar, setting.OfflineMode:
if !com.IsExist(u.CustomAvatarPath()) { if !com.IsExist(u.CustomAvatarPath()) {
if err := u.GenerateRandomAvatar(); err != nil { if err := u.GenerateRandomAvatar(); err != nil {
@ -265,7 +268,7 @@ func (u *User) RelAvatarLink() string {
} }
} }
return setting.AppSubURL + "/avatars/" + com.ToStr(u.ID) return fmt.Sprintf("%s/%s/%d", setting.AppSubURL, USER_AVATAR_URL_PREFIX, u.ID)
} }
return tool.AvatarLink(u.AvatarEmail) return tool.AvatarLink(u.AvatarEmail)
} }
@ -330,50 +333,37 @@ func (u *User) ValidatePassword(passwd string) bool {
} }
// UploadAvatar saves custom avatar for user. // UploadAvatar saves custom avatar for user.
// FIXME: split uploads to different subdirs in case we have massive users. // FIXME: split uploads to different subdirs in case we have massive number of users.
func (u *User) UploadAvatar(data []byte) error { func (u *User) UploadAvatar(data []byte) error {
img, _, err := image.Decode(bytes.NewReader(data)) img, _, err := image.Decode(bytes.NewReader(data))
if err != nil { if err != nil {
return fmt.Errorf("Decode: %v", err) return fmt.Errorf("decode image: %v", err)
}
m := resize.Resize(avatar.AVATAR_SIZE, avatar.AVATAR_SIZE, img, resize.NearestNeighbor)
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
u.UseCustomAvatar = true
if err = updateUser(sess, u); err != nil {
return fmt.Errorf("updateUser: %v", err)
} }
os.MkdirAll(setting.AvatarUploadPath, os.ModePerm) os.MkdirAll(setting.AvatarUploadPath, os.ModePerm)
fw, err := os.Create(u.CustomAvatarPath()) fw, err := os.Create(u.CustomAvatarPath())
if err != nil { if err != nil {
return fmt.Errorf("Create: %v", err) return fmt.Errorf("create custom avatar directory: %v", err)
} }
defer fw.Close() defer fw.Close()
m := resize.Resize(avatar.AVATAR_SIZE, avatar.AVATAR_SIZE, img, resize.NearestNeighbor)
if err = png.Encode(fw, m); err != nil { if err = png.Encode(fw, m); err != nil {
return fmt.Errorf("Encode: %v", err) return fmt.Errorf("encode image: %v", err)
} }
return sess.Commit() return nil
} }
// DeleteAvatar deletes the user's custom avatar. // DeleteAvatar deletes the user's custom avatar.
func (u *User) DeleteAvatar() error { func (u *User) DeleteAvatar() error {
log.Trace("DeleteAvatar [%d]: %s", u.ID, u.CustomAvatarPath()) log.Trace("DeleteAvatar [%d]: %s", u.ID, u.CustomAvatarPath())
os.Remove(u.CustomAvatarPath()) if err := os.Remove(u.CustomAvatarPath()); err != nil {
return err
}
u.UseCustomAvatar = false u.UseCustomAvatar = false
if err := UpdateUser(u); err != nil { return UpdateUser(u)
return fmt.Errorf("UpdateUser: %v", err)
}
return nil
} }
// IsAdminOfRepo returns true if user has admin or higher access of repository. // IsAdminOfRepo returns true if user has admin or higher access of repository.

62
pkg/bindata/bindata.go

File diff suppressed because one or more lines are too long

3
public/css/gogs.css

@ -350,6 +350,9 @@ footer .ui.language .menu {
.center { .center {
text-align: center; text-align: center;
} }
.no-padding-left {
padding-left: 0 !important;
}
.img-1 { .img-1 {
width: 2px !important; width: 2px !important;
height: 2px !important; height: 2px !important;

4
public/less/_base.less

@ -388,6 +388,10 @@ footer {
text-align: center; text-align: center;
} }
.no-padding-left {
padding-left: 0 !important;
}
.generate-img(16); .generate-img(16);
.generate-img(@n, @i: 1) when (@i =< @n) { .generate-img(@n, @i: 1) when (@i =< @n) {
.img-@{i} { .img-@{i} {

12
public/less/_dashboard.less

@ -143,27 +143,17 @@
max-width: 70%; max-width: 70%;
margin-bottom: -4px; margin-bottom: -4px;
} }
.ui.micro.image {
width: 16px;
height: auto;
display: inline-block;
}
} }
#collaborative-repo-list { #collaborative-repo-list {
.owner-and-repo { .owner-and-repo {
max-width: 75%; max-width: 80%;
margin-bottom: -5px; margin-bottom: -5px;
} }
.owner-name { .owner-name {
max-width: 120px; max-width: 120px;
margin-bottom: -5px; margin-bottom: -5px;
} }
.ui.micro.image {
width: 16px;
height: auto;
display: inline-block;
}
} }
} }
} }

114
routes/repo/setting.go

@ -6,13 +6,13 @@ package repo
import ( import (
"fmt" "fmt"
"io/ioutil"
"strings" "strings"
"time" "time"
"io/ioutil"
log "gopkg.in/clog.v1"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/gogs/git-module" "github.com/gogs/git-module"
log "gopkg.in/clog.v1"
"github.com/gogs/gogs/models" "github.com/gogs/gogs/models"
"github.com/gogs/gogs/models/errors" "github.com/gogs/gogs/models/errors"
@ -296,6 +296,63 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
} }
} }
func SettingsAvatar(c *context.Context) {
c.Title("settings.avatar")
c.PageIs("SettingsAvatar")
c.Success(SETTINGS_REPO_AVATAR)
}
func SettingsAvatarPost(c *context.Context, f form.Avatar) {
f.Source = form.AVATAR_LOCAL
if err := UpdateAvatarSetting(c, f, c.Repo.Repository); err != nil {
c.Flash.Error(err.Error())
} else {
c.Flash.Success(c.Tr("settings.update_avatar_success"))
}
c.SubURLRedirect(c.Repo.RepoLink + "/settings")
}
func SettingsDeleteAvatar(c *context.Context) {
if err := c.Repo.Repository.DeleteAvatar(); err != nil {
c.Flash.Error(fmt.Sprintf("Failed to delete avatar: %v", err))
}
c.SubURLRedirect(c.Repo.RepoLink + "/settings")
}
// FIXME: limit upload size
func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxRepo *models.Repository) error {
ctxRepo.UseCustomAvatar = true
if f.Avatar != nil {
r, err := f.Avatar.Open()
if err != nil {
return fmt.Errorf("open avatar reader: %v", err)
}
defer r.Close()
data, err := ioutil.ReadAll(r)
if err != nil {
return fmt.Errorf("read avatar content: %v", err)
}
if !tool.IsImageFile(data) {
return errors.New(c.Tr("settings.uploaded_avatar_not_a_image"))
}
if err = ctxRepo.UploadAvatar(data); err != nil {
return fmt.Errorf("upload avatar: %v", err)
}
} else {
// No avatar is uploaded and reset setting back.
if !com.IsFile(ctxRepo.CustomAvatarPath()) {
ctxRepo.UseCustomAvatar = false
}
}
if err := models.UpdateRepository(ctxRepo, false); err != nil {
return fmt.Errorf("update repository: %v", err)
}
return nil
}
func SettingsCollaboration(c *context.Context) { func SettingsCollaboration(c *context.Context) {
c.Data["Title"] = c.Tr("repo.settings") c.Data["Title"] = c.Tr("repo.settings")
c.Data["PageIsSettingsCollaboration"] = true c.Data["PageIsSettingsCollaboration"] = true
@ -635,56 +692,3 @@ func DeleteDeployKey(c *context.Context) {
"redirect": c.Repo.RepoLink + "/settings/keys", "redirect": c.Repo.RepoLink + "/settings/keys",
}) })
} }
func SettingsAvatar(c *context.Context) {
c.Title("settings.avatar")
c.PageIs("SettingsAvatar")
c.Success(SETTINGS_REPO_AVATAR)
}
func SettingsAvatarPost(c *context.Context, f form.Avatar) {
f.Source = form.AVATAR_LOCAL
if err := UpdateAvatarSetting(c, f); err != nil {
c.Flash.Error(err.Error())
} else {
c.Flash.Success(c.Tr("settings.update_avatar_success"))
}
c.SubURLRedirect(c.Repo.RepoLink + "/settings")
}
func SettingsDeleteAvatar(c *context.Context) {
if err := c.Repo.Repository.DeleteAvatar(); err != nil {
c.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err))
}
c.SubURLRedirect(c.Repo.RepoLink + "/settings")
}
// FIXME: limit size.
func UpdateAvatarSetting(c *context.Context, f form.Avatar) error {
ctxRepo := c.Repo.Repository;
if f.Avatar != nil {
r, err := f.Avatar.Open()
if err != nil {
return fmt.Errorf("Avatar.Open: %v", err)
}
defer r.Close()
data, err := ioutil.ReadAll(r)
if err != nil {
return fmt.Errorf("ioutil.ReadAll: %v", err)
}
if !tool.IsImageFile(data) {
return errors.New(c.Tr("settings.uploaded_avatar_not_a_image"))
}
if err = ctxRepo.UploadAvatar(data); err != nil {
return fmt.Errorf("UploadAvatar: %v", err)
}
} else {
// No avatar is uploaded but setting has been changed to enable
// No random avatar here.
if !com.IsFile(ctxRepo.CustomAvatarPath()) {
log.Trace("No avatar was uploaded for repo: %d. Default icon will appear instead.", ctxRepo.ID)
}
}
return nil
}

14
routes/user/setting.go

@ -111,7 +111,7 @@ func SettingsPost(c *context.Context, f form.UpdateProfile) {
c.SubURLRedirect("/user/settings") c.SubURLRedirect("/user/settings")
} }
// FIXME: limit size. // FIXME: limit upload size
func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxUser *models.User) error { func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxUser *models.User) error {
ctxUser.UseCustomAvatar = f.Source == form.AVATAR_LOCAL ctxUser.UseCustomAvatar = f.Source == form.AVATAR_LOCAL
if len(f.Gravatar) > 0 { if len(f.Gravatar) > 0 {
@ -122,32 +122,32 @@ func UpdateAvatarSetting(c *context.Context, f form.Avatar, ctxUser *models.User
if f.Avatar != nil && f.Avatar.Filename != "" { if f.Avatar != nil && f.Avatar.Filename != "" {
r, err := f.Avatar.Open() r, err := f.Avatar.Open()
if err != nil { if err != nil {
return fmt.Errorf("Avatar.Open: %v", err) return fmt.Errorf("open avatar reader: %v", err)
} }
defer r.Close() defer r.Close()
data, err := ioutil.ReadAll(r) data, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
return fmt.Errorf("ioutil.ReadAll: %v", err) return fmt.Errorf("read avatar content: %v", err)
} }
if !tool.IsImageFile(data) { if !tool.IsImageFile(data) {
return errors.New(c.Tr("settings.uploaded_avatar_not_a_image")) return errors.New(c.Tr("settings.uploaded_avatar_not_a_image"))
} }
if err = ctxUser.UploadAvatar(data); err != nil { if err = ctxUser.UploadAvatar(data); err != nil {
return fmt.Errorf("UploadAvatar: %v", err) return fmt.Errorf("upload avatar: %v", err)
} }
} else { } else {
// No avatar is uploaded but setting has been changed to enable, // No avatar is uploaded but setting has been changed to enable,
// generate a random one when needed. // generate a random one when needed.
if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) { if ctxUser.UseCustomAvatar && !com.IsFile(ctxUser.CustomAvatarPath()) {
if err := ctxUser.GenerateRandomAvatar(); err != nil { if err := ctxUser.GenerateRandomAvatar(); err != nil {
log.Error(4, "GenerateRandomAvatar[%d]: %v", ctxUser.ID, err) log.Error(2, "generate random avatar [%d]: %v", ctxUser.ID, err)
} }
} }
} }
if err := models.UpdateUser(ctxUser); err != nil { if err := models.UpdateUser(ctxUser); err != nil {
return fmt.Errorf("UpdateUser: %v", err) return fmt.Errorf("update user: %v", err)
} }
return nil return nil
@ -171,7 +171,7 @@ func SettingsAvatarPost(c *context.Context, f form.Avatar) {
func SettingsDeleteAvatar(c *context.Context) { func SettingsDeleteAvatar(c *context.Context) {
if err := c.User.DeleteAvatar(); err != nil { if err := c.User.DeleteAvatar(); err != nil {
c.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err)) c.Flash.Error(fmt.Sprintf("Failed to delete avatar: %v", err))
} }
c.SubURLRedirect("/user/settings/avatar") c.SubURLRedirect("/user/settings/avatar")

2
templates/.VERSION

@ -1 +1 @@
0.11.57.0617 0.11.58.0617

38
templates/explore/repo_list.tmpl

@ -2,31 +2,29 @@
{{range .Repos}} {{range .Repos}}
<div class="item"> <div class="item">
<div class="ui grid"> <div class="ui grid">
<div class="ui two wide column middle aligned"> <div class="ui two wide column middle aligned center">
{{if .RelAvatarLink}}<img class="ui tiny image" src="{{.RelAvatarLink}}">{{else}}<i class="mega-octicon octicon-repo"></i>{{end}} {{if .RelAvatarLink}}<img class="ui tiny image" src="{{.RelAvatarLink}}">{{else}}<i class="mega-octicon octicon-repo"></i>{{end}}
</div> </div>
<div class="ui fourteen wide column"> <div class="ui fourteen wide column no-padding-left">
<div class="ui header"> <div class="ui header">
<a class="name" href="{{AppSubURL}}/{{if .Owner}}{{.Owner.Name}}{{else if $.Org}}{{$.Org.Name}}{{else}}{{$.Owner.Name}}{{end}}/{{.Name}}">{{if $.PageIsExplore}}{{.Owner.Name}} / {{end}}{{.Name}}</a> <a class="name" href="{{AppSubURL}}/{{if .Owner}}{{.Owner.Name}}{{else if $.Org}}{{$.Org.Name}}{{else}}{{$.Owner.Name}}{{end}}/{{.Name}}">{{if $.PageIsExplore}}{{.Owner.Name}} / {{end}}{{.Name}}</a>
{{if .IsPrivate}} {{if .IsPrivate}}
<span class="text gold"><i class="octicon octicon-lock"></i></span> <span class="text gold"><i class="octicon octicon-lock"></i></span>
{{else if .IsFork}} {{else if .IsFork}}
<span><i class="octicon octicon-repo-forked"></i></span> <span><i class="octicon octicon-repo-forked"></i></span>
{{else if .IsMirror}} {{else if .IsMirror}}
<span><i class="octicon octicon-repo-clone"></i></span> <span><i class="octicon octicon-repo-clone"></i></span>
{{else}} {{end}}
<span class="text"><i class="octicon octicon-globe"></i></span>
{{end}}
<div class="ui right metas"> <div class="ui right metas">
<span class="text grey"><i class="octicon octicon-star"></i> {{.NumStars}}</span> <span class="text grey"><i class="octicon octicon-star"></i> {{.NumStars}}</span>
<span class="text grey"><i class="octicon octicon-git-branch"></i> {{.NumForks}}</span> <span class="text grey"><i class="octicon octicon-git-branch"></i> {{.NumForks}}</span>
</div>
</div>
{{if .Description}}<p class="has-emoji">{{.Description | Str2html}}</p>{{end}}
<p class="time">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p>
</div> </div>
</div> </div>
{{if .Description}}<p class="has-emoji">{{.Description | Str2html}}</p>{{end}}
<p class="time">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p>
</div>
</div>
</div> </div>
{{end}} {{end}}
</div> </div>

1
templates/org/team/repositories.tmpl

@ -17,7 +17,6 @@
<a class="ui red small button right" href="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/remove?repoid={{.ID}}">{{$.i18n.Tr "org.teams.remove_repo"}}</a> <a class="ui red small button right" href="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/remove?repoid={{.ID}}">{{$.i18n.Tr "org.teams.remove_repo"}}</a>
{{end}} {{end}}
<a class="member" href="{{AppSubURL}}/{{$.Org.Name}}/{{.Name}}"> <a class="member" href="{{AppSubURL}}/{{$.Org.Name}}/{{.Name}}">
<img height="16px" class="octicon" src="{{.RelAvatarLink}}" />
<i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i> <i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
<strong>{{$.Org.Name}}/{{.Name}}</strong> <strong>{{$.Org.Name}}/{{.Name}}</strong>
</a> </a>

8
templates/repo/header.tmpl

@ -5,8 +5,12 @@
<div class="column"><!-- start column --> <div class="column"><!-- start column -->
<div class="ui header"> <div class="ui header">
<div class="ui huge breadcrumb"> <div class="ui huge breadcrumb">
{{if .RelAvatarLink}}<img class="ui mini spaced image" src="{{.RelAvatarLink}}">{{else}}<i class="mega-octicon octicon-repo"></i>{{end}} {{if .UseCustomAvatar}}
<i class="mega-octicon octicon-{{if .IsPrivate}}lock{{else if .IsMirror}}repo-clone{{else if .IsFork}}repo-forked{{else}}globe{{end}}"></i> <img class="ui mini spaced image" src="{{.RelAvatarLink}}">
<i class="{{if .IsPrivate}}mega-octicon octicon-lock{{else if .IsMirror}}mega-octicon octicon-repo-clone{{else if .IsFork}}mega-octicon octicon-repo-forked{{end}}"></i>
{{else}}
<i class="mega-octicon octicon-{{if .IsPrivate}}lock{{else if .IsMirror}}repo-clone{{else if .IsFork}}repo-forked{{else}}repo{{end}}"></i>
{{end}}
<a href="{{AppSubURL}}/{{.Owner.Name}}">{{.Owner.Name}}</a> <a href="{{AppSubURL}}/{{.Owner.Name}}">{{.Owner.Name}}</a>
<div class="divider"> / </div> <div class="divider"> / </div>
<a href="{{$.RepoLink}}">{{.Name}}</a> <a href="{{$.RepoLink}}">{{.Name}}</a>

1
templates/repo/settings/options.tmpl

@ -41,7 +41,6 @@
<div class="field"> <div class="field">
<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button> <button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
</div> </div>
</form> </form>
<div class="ui divider"></div> <div class="ui divider"></div>

8
templates/user/dashboard/dashboard.tmpl

@ -32,8 +32,7 @@
{{range .Repos}} {{range .Repos}}
<li {{if .IsPrivate}}class="private"{{end}}> <li {{if .IsPrivate}}class="private"{{end}}>
<a href="{{AppSubURL}}/{{$.ContextUser.Name}}/{{.Name}}"> <a href="{{AppSubURL}}/{{$.ContextUser.Name}}/{{.Name}}">
{{if .RelAvatarLink}}<img class="ui micro image" src="{{.RelAvatarLink}}" />{{else}}<i class="octicon octicon-repo"></i>{{end}} <i class="octicon octicon-{{if .IsFork}}repo-forked{{else if .IsPrivate}}lock{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
<i class="octicon octicon-{{if .IsFork}}repo-forked{{else if .IsPrivate}}lock{{else if .IsMirror}}repo-clone{{else}}globe{{end}}"></i>
<strong class="text truncate item-name">{{.Name}}</strong> <strong class="text truncate item-name">{{.Name}}</strong>
<span class="ui right text light grey"> <span class="ui right text light grey">
{{.NumStars}} <i class="octicon octicon-star rear"></i> {{.NumStars}} <i class="octicon octicon-star rear"></i>
@ -58,8 +57,7 @@
{{range .CollaborativeRepos}} {{range .CollaborativeRepos}}
<li {{if .IsPrivate}}class="private"{{end}}> <li {{if .IsPrivate}}class="private"{{end}}>
<a href="{{AppSubURL}}/{{.Owner.Name}}/{{.Name}}"> <a href="{{AppSubURL}}/{{.Owner.Name}}/{{.Name}}">
{{if .RelAvatarLink}}<img class="ui micro image" src="{{.RelAvatarLink}}" />{{else}}<i class="octicon octicon-repo"></i>{{end}} <i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
<i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}globe{{end}}"></i>
<span class="text truncate owner-and-repo"> <span class="text truncate owner-and-repo">
<span class="text truncate owner-name">{{.Owner.Name}}</span> / <strong>{{.Name}}</strong> <span class="text truncate owner-name">{{.Owner.Name}}</span> / <strong>{{.Name}}</strong>
</span> </span>
@ -90,7 +88,6 @@
{{range .ContextUser.Orgs}} {{range .ContextUser.Orgs}}
<li> <li>
<a href="{{AppSubURL}}/{{.Name}}"> <a href="{{AppSubURL}}/{{.Name}}">
{{if .RelAvatarLink}}<img class="ui micro image" src="{{.RelAvatarLink}}" />{{else}}<i class="octicon octicon-repo"></i>{{end}}
<i class="octicon octicon-organization"></i> <i class="octicon octicon-organization"></i>
<strong class="text truncate item-name">{{.Name}}</strong> <strong class="text truncate item-name">{{.Name}}</strong>
<span class="ui right text light grey"> <span class="ui right text light grey">
@ -119,7 +116,6 @@
{{range .Mirrors}} {{range .Mirrors}}
<li {{if .IsPrivate}}class="private"{{end}}> <li {{if .IsPrivate}}class="private"{{end}}>
<a href="{{AppSubURL}}/{{$.ContextUser.Name}}/{{.Name}}"> <a href="{{AppSubURL}}/{{$.ContextUser.Name}}/{{.Name}}">
{{if .RelAvatarLink}}<img class="ui micro image" src="{{.RelAvatarLink}}" />{{else}}<i class="octicon octicon-repo"></i>{{end}}
<i class="octicon octicon-repo-clone"></i> <i class="octicon octicon-repo-clone"></i>
<strong class="text truncate item-name">{{.Name}}</strong> <strong class="text truncate item-name">{{.Name}}</strong>
<span class="ui right text light grey"> <span class="ui right text light grey">

Loading…
Cancel
Save