custom avatar upload
This commit is contained in:
parent
3c3f7c2a56
commit
55dfe2c978
20 changed files with 239 additions and 97 deletions
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue