mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	custom avatar upload
This commit is contained in:
		| @@ -71,7 +71,6 @@ There are 5 ways to install Gogs: | |||||||
| - Router and middleware mechanism of [Macaron](https://github.com/Unknwon/macaron). | - Router and middleware mechanism of [Macaron](https://github.com/Unknwon/macaron). | ||||||
| - Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk). | - Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk). | ||||||
| - System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog). | - System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog). | ||||||
| - Usage and modification from [beego](http://beego.me) modules. |  | ||||||
| - Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo. | - Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo. | ||||||
| - Thanks [gobuild.io](http://gobuild.io) for providing binary compile and download service. | - Thanks [gobuild.io](http://gobuild.io) for providing binary compile and download service. | ||||||
| - Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan. | - Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan. | ||||||
|   | |||||||
| @@ -59,8 +59,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自 | |||||||
|  |  | ||||||
| ## 特别鸣谢 | ## 特别鸣谢 | ||||||
|  |  | ||||||
| - [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。 | - 基于 [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。 | ||||||
| - [beego](http://beego.me) 模块的使用与修改。 |  | ||||||
| - 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。 | - 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。 | ||||||
| - 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。 | - 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。 | ||||||
| - 感谢 [gobuild.io](http://gobuild.io) 提供二进制编译与下载服务。 | - 感谢 [gobuild.io](http://gobuild.io) 提供二进制编译与下载服务。 | ||||||
|   | |||||||
| @@ -94,6 +94,13 @@ func newMacaron() *macaron.Macaron { | |||||||
| 			SkipLogging: !setting.DisableRouterLog, | 			SkipLogging: !setting.DisableRouterLog, | ||||||
| 		}, | 		}, | ||||||
| 	)) | 	)) | ||||||
|  | 	m.Use(macaron.Static( | ||||||
|  | 		setting.AvatarUploadPath, | ||||||
|  | 		macaron.StaticOptions{ | ||||||
|  | 			Prefix:      "avatars", | ||||||
|  | 			SkipLogging: !setting.DisableRouterLog, | ||||||
|  | 		}, | ||||||
|  | 	)) | ||||||
| 	m.Use(macaron.Renderer(macaron.RenderOptions{ | 	m.Use(macaron.Renderer(macaron.RenderOptions{ | ||||||
| 		Directory:  path.Join(setting.StaticRootPath, "templates"), | 		Directory:  path.Join(setting.StaticRootPath, "templates"), | ||||||
| 		Funcs:      []template.FuncMap{base.TemplateFuncs}, | 		Funcs:      []template.FuncMap{base.TemplateFuncs}, | ||||||
| @@ -214,6 +221,7 @@ func runWeb(*cli.Context) { | |||||||
| 	m.Group("/user/settings", func() { | 	m.Group("/user/settings", func() { | ||||||
| 		m.Get("", user.Settings) | 		m.Get("", user.Settings) | ||||||
| 		m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost) | 		m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost) | ||||||
|  | 		m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar) | ||||||
| 		m.Get("/password", user.SettingsPassword) | 		m.Get("/password", user.SettingsPassword) | ||||||
| 		m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost) | 		m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost) | ||||||
| 		m.Get("/ssh", user.SettingsSSHKeys) | 		m.Get("/ssh", user.SettingsSSHKeys) | ||||||
|   | |||||||
| @@ -167,6 +167,7 @@ SESSION_LIFE_TIME = 86400 | |||||||
| [picture] | [picture] | ||||||
| ; The place to picture data, either "server" or "qiniu", default is "server" | ; The place to picture data, either "server" or "qiniu", default is "server" | ||||||
| SERVICE = server | SERVICE = server | ||||||
|  | AVATAR_UPLOAD_PATH = data/avatars | ||||||
| ; Chinese users can choose "duoshuo" | ; Chinese users can choose "duoshuo" | ||||||
| GRAVATAR_SOURCE = gravatar | GRAVATAR_SOURCE = gravatar | ||||||
| DISABLE_GRAVATAR = false | DISABLE_GRAVATAR = false | ||||||
|   | |||||||
| @@ -173,6 +173,7 @@ target_branch_not_exist = Target branch does not exist | |||||||
|  |  | ||||||
| [user] | [user] | ||||||
| change_avatar = Change your avatar at gravatar.com | change_avatar = Change your avatar at gravatar.com | ||||||
|  | change_custom_avatar = Change your avatar in settings | ||||||
| join_on = Joined on | join_on = Joined on | ||||||
| repositories = Repositories | repositories = Repositories | ||||||
| activity = Public Activity | activity = Public Activity | ||||||
| @@ -201,6 +202,10 @@ change_username = Username Changed | |||||||
| change_username_desc = Username has been changed, do you want to continue? This will affect all links relate to your account. | change_username_desc = Username has been changed, do you want to continue? This will affect all links relate to your account. | ||||||
| continue = Continue | continue = Continue | ||||||
| cancel = Cancel | cancel = Cancel | ||||||
|  | choose_new_avatar = Choose new avatar | ||||||
|  | upload_avatar = Upload Avatar | ||||||
|  | uploaded_avatar_not_a_image = Uploaded file is not a image | ||||||
|  | upload_avatar_success = Your new avatar has been uploaded successfully. | ||||||
|  |  | ||||||
| change_password = Change Password | change_password = Change Password | ||||||
| old_password = Current Password | old_password = Current Password | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							| @@ -17,7 +17,7 @@ import ( | |||||||
| 	"github.com/gogits/gogs/modules/setting" | 	"github.com/gogits/gogs/modules/setting" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const APP_VER = "0.5.8.1119 Beta" | const APP_VER = "0.5.8.1121 Beta" | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| 	runtime.GOMAXPROCS(runtime.NumCPU()) | 	runtime.GOMAXPROCS(runtime.NumCPU()) | ||||||
|   | |||||||
| @@ -58,6 +58,7 @@ type Action struct { | |||||||
| 	ActUserId    int64  // Action user id. | 	ActUserId    int64  // Action user id. | ||||||
| 	ActUserName  string // Action user name. | 	ActUserName  string // Action user name. | ||||||
| 	ActEmail     string | 	ActEmail     string | ||||||
|  | 	ActAvatar    string `xorm:"-"` | ||||||
| 	RepoId       int64 | 	RepoId       int64 | ||||||
| 	RepoUserName string | 	RepoUserName string | ||||||
| 	RepoName     string | 	RepoName     string | ||||||
|   | |||||||
| @@ -5,17 +5,21 @@ | |||||||
| package models | package models | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"container/list" | 	"container/list" | ||||||
| 	"crypto/sha256" | 	"crypto/sha256" | ||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"image" | ||||||
|  | 	"image/jpeg" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/Unknwon/com" | 	"github.com/Unknwon/com" | ||||||
|  | 	"github.com/nfnt/resize" | ||||||
|  |  | ||||||
| 	"github.com/gogits/gogs/modules/base" | 	"github.com/gogits/gogs/modules/base" | ||||||
| 	"github.com/gogits/gogs/modules/git" | 	"github.com/gogits/gogs/modules/git" | ||||||
| @@ -57,22 +61,29 @@ type User struct { | |||||||
| 	Type        UserType | 	Type        UserType | ||||||
| 	Orgs        []*User       `xorm:"-"` | 	Orgs        []*User       `xorm:"-"` | ||||||
| 	Repos       []*Repository `xorm:"-"` | 	Repos       []*Repository `xorm:"-"` | ||||||
| 	NumFollowers  int |  | ||||||
| 	NumFollowings int |  | ||||||
| 	NumStars      int |  | ||||||
| 	NumRepos      int |  | ||||||
| 	Avatar        string `xorm:"VARCHAR(2048) NOT NULL"` |  | ||||||
| 	AvatarEmail   string `xorm:"NOT NULL"` |  | ||||||
| 	Location    string | 	Location    string | ||||||
| 	Website     string | 	Website     string | ||||||
| 	IsActive      bool |  | ||||||
| 	IsAdmin       bool |  | ||||||
| 	AllowGitHook  bool |  | ||||||
| 	Rands       string    `xorm:"VARCHAR(10)"` | 	Rands       string    `xorm:"VARCHAR(10)"` | ||||||
| 	Salt        string    `xorm:"VARCHAR(10)"` | 	Salt        string    `xorm:"VARCHAR(10)"` | ||||||
| 	Created     time.Time `xorm:"CREATED"` | 	Created     time.Time `xorm:"CREATED"` | ||||||
| 	Updated     time.Time `xorm:"UPDATED"` | 	Updated     time.Time `xorm:"UPDATED"` | ||||||
|  |  | ||||||
|  | 	// Permissions. | ||||||
|  | 	IsActive     bool | ||||||
|  | 	IsAdmin      bool | ||||||
|  | 	AllowGitHook bool | ||||||
|  |  | ||||||
|  | 	// Avatar. | ||||||
|  | 	Avatar          string `xorm:"VARCHAR(2048) NOT NULL"` | ||||||
|  | 	AvatarEmail     string `xorm:"NOT NULL"` | ||||||
|  | 	UseCustomAvatar bool | ||||||
|  |  | ||||||
|  | 	// Counters. | ||||||
|  | 	NumFollowers  int | ||||||
|  | 	NumFollowings int | ||||||
|  | 	NumStars      int | ||||||
|  | 	NumRepos      int | ||||||
|  |  | ||||||
| 	// For organization. | 	// For organization. | ||||||
| 	Description string | 	Description string | ||||||
| 	NumTeams    int | 	NumTeams    int | ||||||
| @@ -96,9 +107,12 @@ func (u *User) HomeLink() string { | |||||||
|  |  | ||||||
| // AvatarLink returns user gravatar link. | // AvatarLink returns user gravatar link. | ||||||
| func (u *User) AvatarLink() string { | func (u *User) AvatarLink() string { | ||||||
| 	if setting.DisableGravatar { | 	switch { | ||||||
|  | 	case u.UseCustomAvatar: | ||||||
|  | 		return setting.AppSubUrl + "/avatars/" + com.ToStr(u.Id) | ||||||
|  | 	case setting.DisableGravatar: | ||||||
| 		return setting.AppSubUrl + "/img/avatar_default.jpg" | 		return setting.AppSubUrl + "/img/avatar_default.jpg" | ||||||
| 	} else if setting.Service.EnableCacheAvatar { | 	case setting.Service.EnableCacheAvatar: | ||||||
| 		return setting.AppSubUrl + "/avatar/" + u.Avatar | 		return setting.AppSubUrl + "/avatar/" + u.Avatar | ||||||
| 	} | 	} | ||||||
| 	return setting.GravatarSource + u.Avatar | 	return setting.GravatarSource + u.Avatar | ||||||
| @@ -126,6 +140,43 @@ func (u *User) ValidtePassword(passwd string) bool { | |||||||
| 	return u.Passwd == newUser.Passwd | 	return u.Passwd == newUser.Passwd | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // UploadAvatar saves custom avatar for user. | ||||||
|  | // FIXME: splite uploads to different subdirs in case we have massive users. | ||||||
|  | func (u *User) UploadAvatar(data []byte) error { | ||||||
|  | 	savePath := filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id)) | ||||||
|  | 	u.UseCustomAvatar = true | ||||||
|  |  | ||||||
|  | 	img, _, err := image.Decode(bytes.NewReader(data)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	m := resize.Resize(200, 200, img, resize.NearestNeighbor) | ||||||
|  |  | ||||||
|  | 	sess := x.NewSession() | ||||||
|  | 	defer sess.Close() | ||||||
|  | 	if err = sess.Begin(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if _, err = sess.Id(u.Id).AllCols().Update(u); err != nil { | ||||||
|  | 		sess.Rollback() | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fw, err := os.Create(savePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		sess.Rollback() | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer fw.Close() | ||||||
|  | 	if err = jpeg.Encode(fw, m, nil); err != nil { | ||||||
|  | 		sess.Rollback() | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return sess.Commit() | ||||||
|  | } | ||||||
|  |  | ||||||
| // IsOrganization returns true if user is actually a organization. | // IsOrganization returns true if user is actually a organization. | ||||||
| func (u *User) IsOrganization() bool { | func (u *User) IsOrganization() bool { | ||||||
| 	return u.Type == ORGANIZATION | 	return u.Type == ORGANIZATION | ||||||
| @@ -517,40 +568,37 @@ func GetUserIdsByNames(names []string) []int64 { | |||||||
|  |  | ||||||
| // UserCommit represtns a commit with validation of user. | // UserCommit represtns a commit with validation of user. | ||||||
| type UserCommit struct { | type UserCommit struct { | ||||||
| 	UserName string | 	User *User | ||||||
| 	*git.Commit | 	*git.Commit | ||||||
| } | } | ||||||
|  |  | ||||||
| // ValidateCommitWithEmail chceck if author's e-mail of commit is corresponsind to a user. | // ValidateCommitWithEmail chceck if author's e-mail of commit is corresponsind to a user. | ||||||
| func ValidateCommitWithEmail(c *git.Commit) (uname string) { | func ValidateCommitWithEmail(c *git.Commit) *User { | ||||||
| 	u, err := GetUserByEmail(c.Author.Email) | 	u, err := GetUserByEmail(c.Author.Email) | ||||||
| 	if err == nil { | 	if err != nil { | ||||||
| 		uname = u.Name | 		return nil | ||||||
| 	} | 	} | ||||||
| 	return uname | 	return u | ||||||
| } | } | ||||||
|  |  | ||||||
| // ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users. | // ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users. | ||||||
| func ValidateCommitsWithEmails(oldCommits *list.List) *list.List { | func ValidateCommitsWithEmails(oldCommits *list.List) *list.List { | ||||||
| 	emails := map[string]string{} | 	emails := map[string]*User{} | ||||||
| 	newCommits := list.New() | 	newCommits := list.New() | ||||||
| 	e := oldCommits.Front() | 	e := oldCommits.Front() | ||||||
| 	for e != nil { | 	for e != nil { | ||||||
| 		c := e.Value.(*git.Commit) | 		c := e.Value.(*git.Commit) | ||||||
|  |  | ||||||
| 		uname := "" | 		var u *User | ||||||
| 		if v, ok := emails[c.Author.Email]; !ok { | 		if v, ok := emails[c.Author.Email]; !ok { | ||||||
| 			u, err := GetUserByEmail(c.Author.Email) | 			u, _ = GetUserByEmail(c.Author.Email) | ||||||
| 			if err == nil { | 			emails[c.Author.Email] = u | ||||||
| 				uname = u.Name |  | ||||||
| 			} |  | ||||||
| 			emails[c.Author.Email] = uname |  | ||||||
| 		} else { | 		} else { | ||||||
| 			uname = v | 			u = v | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		newCommits.PushBack(UserCommit{ | 		newCommits.PushBack(UserCommit{ | ||||||
| 			UserName: uname, | 			User:   u, | ||||||
| 			Commit: c, | 			Commit: c, | ||||||
| 		}) | 		}) | ||||||
| 		e = e.Next() | 		e = e.Next() | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ | |||||||
| package auth | package auth | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"mime/multipart" | ||||||
|  |  | ||||||
| 	"github.com/Unknwon/macaron" | 	"github.com/Unknwon/macaron" | ||||||
| 	"github.com/macaron-contrib/binding" | 	"github.com/macaron-contrib/binding" | ||||||
| ) | ) | ||||||
| @@ -86,6 +88,14 @@ func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) | |||||||
| 	return validate(errs, ctx.Data, f, ctx.Locale) | 	return validate(errs, ctx.Data, f, ctx.Locale) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type UploadAvatarForm struct { | ||||||
|  | 	Avatar *multipart.FileHeader `form:"avatar" binding:"Required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | ||||||
|  | 	return validate(errs, ctx.Data, f, ctx.Locale) | ||||||
|  | } | ||||||
|  |  | ||||||
| type ChangePasswordForm struct { | type ChangePasswordForm struct { | ||||||
| 	OldPassword string `form:"old_password" binding:"Required;MinSize(6);MaxSize(255)"` | 	OldPassword string `form:"old_password" binding:"Required;MinSize(6);MaxSize(255)"` | ||||||
| 	Password    string `form:"password" binding:"Required;MinSize(6);MaxSize(255)"` | 	Password    string `form:"password" binding:"Required;MinSize(6);MaxSize(255)"` | ||||||
|   | |||||||
| @@ -121,7 +121,7 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) { | |||||||
| 	if img, err = decodeImageFile(imgPath); err != nil { | 	if img, err = decodeImageFile(imgPath); err != nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	m := resize.Resize(uint(size), 0, img, resize.Lanczos3) | 	m := resize.Resize(uint(size), 0, img, resize.NearestNeighbor) | ||||||
| 	return jpeg.Encode(wr, m, nil) | 	return jpeg.Encode(wr, m, nil) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -67,6 +67,7 @@ var ( | |||||||
|  |  | ||||||
| 	// Picture settings. | 	// Picture settings. | ||||||
| 	PictureService   string | 	PictureService   string | ||||||
|  | 	AvatarUploadPath string | ||||||
| 	GravatarSource   string | 	GravatarSource   string | ||||||
| 	DisableGravatar  bool | 	DisableGravatar  bool | ||||||
|  |  | ||||||
| @@ -259,6 +260,9 @@ func NewConfigContext() { | |||||||
| 	ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash") | 	ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash") | ||||||
|  |  | ||||||
| 	PictureService = Cfg.MustValueRange("picture", "SERVICE", "server", []string{"server"}) | 	PictureService = Cfg.MustValueRange("picture", "SERVICE", "server", []string{"server"}) | ||||||
|  | 	AvatarUploadPath = Cfg.MustValue("picture", "AVATAR_UPLOAD_PATH", "data/avatars") | ||||||
|  | 	os.MkdirAll(AvatarUploadPath, os.ModePerm) | ||||||
|  |  | ||||||
| 	switch Cfg.MustValue("picture", "GRAVATAR_SOURCE", "gravatar") { | 	switch Cfg.MustValue("picture", "GRAVATAR_SOURCE", "gravatar") { | ||||||
| 	case "duoshuo": | 	case "duoshuo": | ||||||
| 		GravatarSource = "http://gravatar.duoshuo.com/avatar/" | 		GravatarSource = "http://gravatar.duoshuo.com/avatar/" | ||||||
|   | |||||||
| @@ -100,6 +100,13 @@ func Dashboard(ctx *middleware.Context) { | |||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 		// FIXME: cache results? | ||||||
|  | 		u, err := models.GetUserByName(act.ActUserName) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.Handle(500, "GetUserByName", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		act.ActAvatar = u.AvatarLink() | ||||||
| 		feeds = append(feeds, act) | 		feeds = append(feeds, act) | ||||||
| 	} | 	} | ||||||
| 	ctx.Data["Feeds"] = feeds | 	ctx.Data["Feeds"] = feeds | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| package user | package user | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"io/ioutil" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/Unknwon/com" | 	"github.com/Unknwon/com" | ||||||
| @@ -83,6 +84,34 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateProfileForm) { | |||||||
| 	ctx.Redirect(setting.AppSubUrl + "/user/settings") | 	ctx.Redirect(setting.AppSubUrl + "/user/settings") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // FIXME: limit size. | ||||||
|  | func SettingsAvatar(ctx *middleware.Context, form auth.UploadAvatarForm) { | ||||||
|  | 	defer ctx.Redirect(setting.AppSubUrl + "/user/settings") | ||||||
|  |  | ||||||
|  | 	if form.Avatar != nil { | ||||||
|  | 		fr, err := form.Avatar.Open() | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.Flash.Error(err.Error()) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		data, err := ioutil.ReadAll(fr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.Flash.Error(err.Error()) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if _, ok := base.IsImageFile(data); !ok { | ||||||
|  | 			ctx.Flash.Error(ctx.Tr("settings.uploaded_avatar_not_a_image")) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		if err = ctx.User.UploadAvatar(data); err != nil { | ||||||
|  | 			ctx.Flash.Error(err.Error()) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		ctx.Flash.Success(ctx.Tr("settings.upload_avatar_success")) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func SettingsPassword(ctx *middleware.Context) { | func SettingsPassword(ctx *middleware.Context) { | ||||||
| 	ctx.Data["Title"] = ctx.Tr("settings") | 	ctx.Data["Title"] = ctx.Tr("settings") | ||||||
| 	ctx.Data["PageIsUserSettings"] = true | 	ctx.Data["PageIsUserSettings"] = true | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| 0.5.8.1119 Beta | 0.5.8.1121 Beta | ||||||
| @@ -24,7 +24,13 @@ | |||||||
|             {{$r := List .Commits}} |             {{$r := List .Commits}} | ||||||
|             {{range $r}} |             {{range $r}} | ||||||
|             <tr> |             <tr> | ||||||
|                 <td class="author"><img class="avatar-20" src="{{AvatarLink .Author.Email}}" alt=""/>   {{if .UserName}}<a href="{{AppSubUrl}}/{{.UserName}}">{{.Author.Name}}</a>{{else}}{{.Author.Name}}{{end}}</td> |                 <td class="author"> | ||||||
|  |                     {{if .User}} | ||||||
|  |                     <img class="avatar-20" src="{{.User.AvatarLink}}" alt=""/>   <a href="{{AppSubUrl}}/{{.User.Name}}">{{.Author.Name}}</a> | ||||||
|  |                     {{else}} | ||||||
|  |                     <img class="avatar-20" src="{{AvatarLink .Author.Email}}" alt=""/>   {{.Author.Name}} | ||||||
|  |                     {{end}} | ||||||
|  |                 </td> | ||||||
|                 <td class="sha"><a rel="nofollow" class="label label-green" href="{{AppSubUrl}}/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td> |                 <td class="sha"><a rel="nofollow" class="label label-green" href="{{AppSubUrl}}/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td> | ||||||
|                 <td class="message"><span class="text-truncate">{{.Summary}}</span></td> |                 <td class="message"><span class="text-truncate">{{.Summary}}</span></td> | ||||||
|                 <td class="date">{{TimeSince .Author.When $.Lang}}</td> |                 <td class="date">{{TimeSince .Author.When $.Lang}}</td> | ||||||
|   | |||||||
| @@ -30,10 +30,11 @@ | |||||||
|                     </ul> |                     </ul> | ||||||
|                 </span> |                 </span> | ||||||
|                 <p class="author"> |                 <p class="author"> | ||||||
|                     <img class="avatar-30" src="{{AvatarLink .Commit.Author.Email}}" /> |  | ||||||
|                     {{if .Author}} |                     {{if .Author}} | ||||||
|                     <a href="{{AppSubUrl}}/{{.Author}}"><strong>{{.Commit.Author.Name}}</strong></a> |                     <img class="avatar-30" src="{{.Author.AvatarLink}}" /> | ||||||
|  |                     <a href="{{AppSubUrl}}/{{.Author.Name}}"><strong>{{.Commit.Author.Name}}</strong></a> | ||||||
|                     {{else}} |                     {{else}} | ||||||
|  |                     <img class="avatar-30" src="{{AvatarLink .Commit.Author.Email}}" /> | ||||||
|                     <strong>{{.Commit.Author.Name}}</strong> |                     <strong>{{.Commit.Author.Name}}</strong> | ||||||
|                     {{end}} |                     {{end}} | ||||||
|                     <span class="text-grey" id="authored-time">{{TimeSince .Commit.Author.When $.Lang}}</span>  |                     <span class="text-grey" id="authored-time">{{TimeSince .Commit.Author.When $.Lang}}</span>  | ||||||
|   | |||||||
| @@ -3,8 +3,14 @@ | |||||||
|     <tr> |     <tr> | ||||||
|         <th colspan="4" class="clear"> |         <th colspan="4" class="clear"> | ||||||
|             <span class="author left"> |             <span class="author left"> | ||||||
|  |                 {{if .LastCommitUser}} | ||||||
|  |                 <img class="avatar-24 radius" src="{{.LastCommitUser.AvatarLink}}" /> | ||||||
|  |                 <a href="{{AppSubUrl}}/{{.LastCommitUser.Name}}"><strong>{{.LastCommit.Author.Name}}</strong></a>: | ||||||
|  |                 {{else}} | ||||||
|                 <img class="avatar-24 radius" src="{{AvatarLink .LastCommit.Author.Email}}" /> |                 <img class="avatar-24 radius" src="{{AvatarLink .LastCommit.Author.Email}}" /> | ||||||
|                 {{if .LastCommitUser}}<a href="{{AppSubUrl}}/{{.LastCommitUser}}">{{end}}<strong>{{.LastCommit.Author.Name}}</strong>:{{if .LastCommitUser}}</a>{{end}} |                 <strong>{{.LastCommit.Author.Name}}</strong>: | ||||||
|  |                 {{end}} | ||||||
|  |                   | ||||||
|             </span> |             </span> | ||||||
|             <span class="last-commit"><a href="{{.RepoLink}}/commit/{{.LastCommit.Id}}" rel="nofollow"> |             <span class="last-commit"><a href="{{.RepoLink}}/commit/{{.LastCommit.Id}}" rel="nofollow"> | ||||||
|                 <strong>{{ShortSha .LastCommit.Id.String}}</strong></a> |                 <strong>{{ShortSha .LastCommit.Id.String}}</strong></a> | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| {{range .Feeds}} | {{range .Feeds}} | ||||||
| <div class="news clear"> | <div class="news clear"> | ||||||
|     <div class="avatar left"> |     <div class="avatar left"> | ||||||
|         <img class="avatar-30" src="{{AvatarLink .GetActEmail}}" alt=""> |         <img class="avatar-30" src="{{.ActAvatar}}" alt=""> | ||||||
|     </div> |     </div> | ||||||
|     <div class="content left {{if eq .GetOpType 5}}push-news{{end}} grid-4-5"> |     <div class="content left {{if eq .GetOpType 5}}push-news{{end}} grid-4-5"> | ||||||
|         <p class="text-bold"> |         <p class="text-bold"> | ||||||
|   | |||||||
| @@ -4,7 +4,11 @@ | |||||||
|     <div id="user-profile-page" class="container clear"> |     <div id="user-profile-page" class="container clear"> | ||||||
|         <div class="grid-1-5 left"> |         <div class="grid-1-5 left"> | ||||||
|             <div> |             <div> | ||||||
|  |                 {{if .Owner.UseCustomAvatar}} | ||||||
|  |                 <a href="{{AppSubUrl}}/user/settings" id="profile-avatar" original-title="{{.i18n.Tr "user.change_custom_avatar"}}"> | ||||||
|  |                 {{else}} | ||||||
|                 <a href="http://gravatar.com/emails/" id="profile-avatar" original-title="{{.i18n.Tr "user.change_avatar"}}"> |                 <a href="http://gravatar.com/emails/" id="profile-avatar" original-title="{{.i18n.Tr "user.change_avatar"}}"> | ||||||
|  |                 {{end}} | ||||||
|                     <img class="profile-avatar" src="{{.Owner.AvatarLink}}?s=200"title="{{.Owner.Name}}"/> |                     <img class="profile-avatar" src="{{.Owner.AvatarLink}}?s=200"title="{{.Owner.Name}}"/> | ||||||
|                 </a> |                 </a> | ||||||
|                 <div class="text-center" id="profile-name"> |                 <div class="text-center" id="profile-name"> | ||||||
|   | |||||||
| @@ -11,7 +11,8 @@ | |||||||
|                         <div class="panel-header"> |                         <div class="panel-header"> | ||||||
|                             <strong>{{.i18n.Tr "settings.public_profile"}}</strong> |                             <strong>{{.i18n.Tr "settings.public_profile"}}</strong> | ||||||
|                         </div> |                         </div> | ||||||
|                         <form class="form form-align panel-body" id="user-profile-form" action="{{AppSubUrl}}/user/settings" method="post"> |                         <div class="panel-body"> | ||||||
|  |                             <form class="form form-align" id="user-profile-form" action="{{AppSubUrl}}/user/settings" method="post"> | ||||||
|                                 {{.CsrfTokenHtml}} |                                 {{.CsrfTokenHtml}} | ||||||
|                                 <div class="text-center panel-desc">{{.i18n.Tr "settings.profile_desc"}}</div> |                                 <div class="text-center panel-desc">{{.i18n.Tr "settings.profile_desc"}}</div> | ||||||
|                                 <div class="field"> |                                 <div class="field"> | ||||||
| @@ -54,6 +55,19 @@ | |||||||
|                                     <button class="btn btn-green btn-large btn-radius" id="change-username-btn" href="#change-username-modal">{{.i18n.Tr "settings.update_profile"}}</button> |                                     <button class="btn btn-green btn-large btn-radius" id="change-username-btn" href="#change-username-modal">{{.i18n.Tr "settings.update_profile"}}</button> | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </form> |                             </form> | ||||||
|  |                             <hr> | ||||||
|  |                             <form class="form form-align" id="user-profile-form" action="{{AppSubUrl}}/user/settings/avatar" method="post" enctype="multipart/form-data"> | ||||||
|  |                                 {{.CsrfTokenHtml}} | ||||||
|  |                                 <div class="field"> | ||||||
|  |                                     <label>{{.i18n.Tr "settings.choose_new_avatar"}}</label> | ||||||
|  |                                     <input name="avatar" type="file" required /> | ||||||
|  |                                 </div> | ||||||
|  |                                 <div class="field"> | ||||||
|  |                                     <label></label> | ||||||
|  |                                     <button class="btn btn-green btn-large btn-radius">{{.i18n.Tr "settings.upload_avatar"}}</button> | ||||||
|  |                                 </div> | ||||||
|  |                             </form> | ||||||
|  |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user