mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00: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).
 | 
			
		||||
- 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).
 | 
			
		||||
- 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 [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.
 | 
			
		||||
 
 | 
			
		||||
@@ -59,8 +59,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
 | 
			
		||||
 | 
			
		||||
## 特别鸣谢
 | 
			
		||||
 | 
			
		||||
- [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。
 | 
			
		||||
- [beego](http://beego.me) 模块的使用与修改。
 | 
			
		||||
- 基于 [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。
 | 
			
		||||
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。
 | 
			
		||||
- 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。
 | 
			
		||||
- 感谢 [gobuild.io](http://gobuild.io) 提供二进制编译与下载服务。
 | 
			
		||||
 
 | 
			
		||||
@@ -94,6 +94,13 @@ func newMacaron() *macaron.Macaron {
 | 
			
		||||
			SkipLogging: !setting.DisableRouterLog,
 | 
			
		||||
		},
 | 
			
		||||
	))
 | 
			
		||||
	m.Use(macaron.Static(
 | 
			
		||||
		setting.AvatarUploadPath,
 | 
			
		||||
		macaron.StaticOptions{
 | 
			
		||||
			Prefix:      "avatars",
 | 
			
		||||
			SkipLogging: !setting.DisableRouterLog,
 | 
			
		||||
		},
 | 
			
		||||
	))
 | 
			
		||||
	m.Use(macaron.Renderer(macaron.RenderOptions{
 | 
			
		||||
		Directory:  path.Join(setting.StaticRootPath, "templates"),
 | 
			
		||||
		Funcs:      []template.FuncMap{base.TemplateFuncs},
 | 
			
		||||
@@ -214,6 +221,7 @@ func runWeb(*cli.Context) {
 | 
			
		||||
	m.Group("/user/settings", func() {
 | 
			
		||||
		m.Get("", user.Settings)
 | 
			
		||||
		m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
 | 
			
		||||
		m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar)
 | 
			
		||||
		m.Get("/password", user.SettingsPassword)
 | 
			
		||||
		m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
 | 
			
		||||
		m.Get("/ssh", user.SettingsSSHKeys)
 | 
			
		||||
 
 | 
			
		||||
@@ -167,6 +167,7 @@ SESSION_LIFE_TIME = 86400
 | 
			
		||||
[picture]
 | 
			
		||||
; The place to picture data, either "server" or "qiniu", default is "server"
 | 
			
		||||
SERVICE = server
 | 
			
		||||
AVATAR_UPLOAD_PATH = data/avatars
 | 
			
		||||
; Chinese users can choose "duoshuo"
 | 
			
		||||
GRAVATAR_SOURCE = gravatar
 | 
			
		||||
DISABLE_GRAVATAR = false
 | 
			
		||||
 
 | 
			
		||||
@@ -173,6 +173,7 @@ target_branch_not_exist = Target branch does not exist
 | 
			
		||||
 | 
			
		||||
[user]
 | 
			
		||||
change_avatar = Change your avatar at gravatar.com
 | 
			
		||||
change_custom_avatar = Change your avatar in settings
 | 
			
		||||
join_on = Joined on
 | 
			
		||||
repositories = Repositories
 | 
			
		||||
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.
 | 
			
		||||
continue = Continue
 | 
			
		||||
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
 | 
			
		||||
old_password = Current Password
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								gogs.go
									
									
									
									
									
								
							@@ -17,7 +17,7 @@ import (
 | 
			
		||||
	"github.com/gogits/gogs/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const APP_VER = "0.5.8.1119 Beta"
 | 
			
		||||
const APP_VER = "0.5.8.1121 Beta"
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	runtime.GOMAXPROCS(runtime.NumCPU())
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ type Action struct {
 | 
			
		||||
	ActUserId    int64  // Action user id.
 | 
			
		||||
	ActUserName  string // Action user name.
 | 
			
		||||
	ActEmail     string
 | 
			
		||||
	ActAvatar    string `xorm:"-"`
 | 
			
		||||
	RepoId       int64
 | 
			
		||||
	RepoUserName string
 | 
			
		||||
	RepoName     string
 | 
			
		||||
 
 | 
			
		||||
@@ -5,17 +5,21 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"container/list"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"image"
 | 
			
		||||
	"image/jpeg"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Unknwon/com"
 | 
			
		||||
	"github.com/nfnt/resize"
 | 
			
		||||
 | 
			
		||||
	"github.com/gogits/gogs/modules/base"
 | 
			
		||||
	"github.com/gogits/gogs/modules/git"
 | 
			
		||||
@@ -57,22 +61,29 @@ type User struct {
 | 
			
		||||
	Type        UserType
 | 
			
		||||
	Orgs        []*User       `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
 | 
			
		||||
	Website     string
 | 
			
		||||
	IsActive      bool
 | 
			
		||||
	IsAdmin       bool
 | 
			
		||||
	AllowGitHook  bool
 | 
			
		||||
	Rands       string    `xorm:"VARCHAR(10)"`
 | 
			
		||||
	Salt        string    `xorm:"VARCHAR(10)"`
 | 
			
		||||
	Created     time.Time `xorm:"CREATED"`
 | 
			
		||||
	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.
 | 
			
		||||
	Description string
 | 
			
		||||
	NumTeams    int
 | 
			
		||||
@@ -96,9 +107,12 @@ func (u *User) HomeLink() string {
 | 
			
		||||
 | 
			
		||||
// AvatarLink returns user gravatar link.
 | 
			
		||||
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"
 | 
			
		||||
	} else if setting.Service.EnableCacheAvatar {
 | 
			
		||||
	case setting.Service.EnableCacheAvatar:
 | 
			
		||||
		return setting.AppSubUrl + "/avatar/" + u.Avatar
 | 
			
		||||
	}
 | 
			
		||||
	return setting.GravatarSource + u.Avatar
 | 
			
		||||
@@ -126,6 +140,43 @@ func (u *User) ValidtePassword(passwd string) bool {
 | 
			
		||||
	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.
 | 
			
		||||
func (u *User) IsOrganization() bool {
 | 
			
		||||
	return u.Type == ORGANIZATION
 | 
			
		||||
@@ -517,40 +568,37 @@ func GetUserIdsByNames(names []string) []int64 {
 | 
			
		||||
 | 
			
		||||
// UserCommit represtns a commit with validation of user.
 | 
			
		||||
type UserCommit struct {
 | 
			
		||||
	UserName string
 | 
			
		||||
	User *User
 | 
			
		||||
	*git.Commit
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		uname = u.Name
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return uname
 | 
			
		||||
	return u
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users.
 | 
			
		||||
func ValidateCommitsWithEmails(oldCommits *list.List) *list.List {
 | 
			
		||||
	emails := map[string]string{}
 | 
			
		||||
	emails := map[string]*User{}
 | 
			
		||||
	newCommits := list.New()
 | 
			
		||||
	e := oldCommits.Front()
 | 
			
		||||
	for e != nil {
 | 
			
		||||
		c := e.Value.(*git.Commit)
 | 
			
		||||
 | 
			
		||||
		uname := ""
 | 
			
		||||
		var u *User
 | 
			
		||||
		if v, ok := emails[c.Author.Email]; !ok {
 | 
			
		||||
			u, err := GetUserByEmail(c.Author.Email)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				uname = u.Name
 | 
			
		||||
			}
 | 
			
		||||
			emails[c.Author.Email] = uname
 | 
			
		||||
			u, _ = GetUserByEmail(c.Author.Email)
 | 
			
		||||
			emails[c.Author.Email] = u
 | 
			
		||||
		} else {
 | 
			
		||||
			uname = v
 | 
			
		||||
			u = v
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newCommits.PushBack(UserCommit{
 | 
			
		||||
			UserName: uname,
 | 
			
		||||
			User:   u,
 | 
			
		||||
			Commit: c,
 | 
			
		||||
		})
 | 
			
		||||
		e = e.Next()
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,8 @@
 | 
			
		||||
package auth
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"mime/multipart"
 | 
			
		||||
 | 
			
		||||
	"github.com/Unknwon/macaron"
 | 
			
		||||
	"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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
	OldPassword string `form:"old_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 {
 | 
			
		||||
		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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,7 @@ var (
 | 
			
		||||
 | 
			
		||||
	// Picture settings.
 | 
			
		||||
	PictureService   string
 | 
			
		||||
	AvatarUploadPath string
 | 
			
		||||
	GravatarSource   string
 | 
			
		||||
	DisableGravatar  bool
 | 
			
		||||
 | 
			
		||||
@@ -259,6 +260,9 @@ func NewConfigContext() {
 | 
			
		||||
	ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash")
 | 
			
		||||
 | 
			
		||||
	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") {
 | 
			
		||||
	case "duoshuo":
 | 
			
		||||
		GravatarSource = "http://gravatar.duoshuo.com/avatar/"
 | 
			
		||||
 
 | 
			
		||||
@@ -100,6 +100,13 @@ func Dashboard(ctx *middleware.Context) {
 | 
			
		||||
				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)
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["Feeds"] = feeds
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
package user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Unknwon/com"
 | 
			
		||||
@@ -83,6 +84,34 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
 | 
			
		||||
	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) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("settings")
 | 
			
		||||
	ctx.Data["PageIsUserSettings"] = true
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
0.5.8.1119 Beta
 | 
			
		||||
0.5.8.1121 Beta
 | 
			
		||||
@@ -24,7 +24,13 @@
 | 
			
		||||
            {{$r := List .Commits}}
 | 
			
		||||
            {{range $r}}
 | 
			
		||||
            <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="message"><span class="text-truncate">{{.Summary}}</span></td>
 | 
			
		||||
                <td class="date">{{TimeSince .Author.When $.Lang}}</td>
 | 
			
		||||
 
 | 
			
		||||
@@ -30,10 +30,11 @@
 | 
			
		||||
                    </ul>
 | 
			
		||||
                </span>
 | 
			
		||||
                <p class="author">
 | 
			
		||||
                    <img class="avatar-30" src="{{AvatarLink .Commit.Author.Email}}" />
 | 
			
		||||
                    {{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}}
 | 
			
		||||
                    <img class="avatar-30" src="{{AvatarLink .Commit.Author.Email}}" />
 | 
			
		||||
                    <strong>{{.Commit.Author.Name}}</strong>
 | 
			
		||||
                    {{end}}
 | 
			
		||||
                    <span class="text-grey" id="authored-time">{{TimeSince .Commit.Author.When $.Lang}}</span> 
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,14 @@
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th colspan="4" class="clear">
 | 
			
		||||
            <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}}" />
 | 
			
		||||
                {{if .LastCommitUser}}<a href="{{AppSubUrl}}/{{.LastCommitUser}}">{{end}}<strong>{{.LastCommit.Author.Name}}</strong>:{{if .LastCommitUser}}</a>{{end}}
 | 
			
		||||
                <strong>{{.LastCommit.Author.Name}}</strong>:
 | 
			
		||||
                {{end}}
 | 
			
		||||
                 
 | 
			
		||||
            </span>
 | 
			
		||||
            <span class="last-commit"><a href="{{.RepoLink}}/commit/{{.LastCommit.Id}}" rel="nofollow">
 | 
			
		||||
                <strong>{{ShortSha .LastCommit.Id.String}}</strong></a>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
{{range .Feeds}}
 | 
			
		||||
<div class="news clear">
 | 
			
		||||
    <div class="avatar left">
 | 
			
		||||
        <img class="avatar-30" src="{{AvatarLink .GetActEmail}}" alt="">
 | 
			
		||||
        <img class="avatar-30" src="{{.ActAvatar}}" alt="">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="content left {{if eq .GetOpType 5}}push-news{{end}} grid-4-5">
 | 
			
		||||
        <p class="text-bold">
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,11 @@
 | 
			
		||||
    <div id="user-profile-page" class="container clear">
 | 
			
		||||
        <div class="grid-1-5 left">
 | 
			
		||||
            <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"}}">
 | 
			
		||||
                {{end}}
 | 
			
		||||
                    <img class="profile-avatar" src="{{.Owner.AvatarLink}}?s=200"title="{{.Owner.Name}}"/>
 | 
			
		||||
                </a>
 | 
			
		||||
                <div class="text-center" id="profile-name">
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,8 @@
 | 
			
		||||
                        <div class="panel-header">
 | 
			
		||||
                            <strong>{{.i18n.Tr "settings.public_profile"}}</strong>
 | 
			
		||||
                        </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}}
 | 
			
		||||
                                <div class="text-center panel-desc">{{.i18n.Tr "settings.profile_desc"}}</div>
 | 
			
		||||
                                <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>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user