diff options
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | README_ZH.md | 3 | ||||
-rw-r--r-- | cmd/web.go | 8 | ||||
-rw-r--r-- | conf/app.ini | 1 | ||||
-rw-r--r-- | conf/locale/locale_en-US.ini | 5 | ||||
-rw-r--r-- | gogs.go | 2 | ||||
-rw-r--r-- | models/action.go | 1 | ||||
-rw-r--r-- | models/user.go | 128 | ||||
-rw-r--r-- | modules/auth/user_form.go | 10 | ||||
-rw-r--r-- | modules/avatar/avatar.go | 2 | ||||
-rw-r--r-- | modules/setting/setting.go | 10 | ||||
-rw-r--r-- | routers/user/home.go | 7 | ||||
-rw-r--r-- | routers/user/setting.go | 29 | ||||
-rw-r--r-- | templates/.VERSION | 2 | ||||
-rw-r--r-- | templates/repo/commits_table.tmpl | 8 | ||||
-rw-r--r-- | templates/repo/diff.tmpl | 5 | ||||
-rw-r--r-- | templates/repo/view_list.tmpl | 8 | ||||
-rw-r--r-- | templates/user/dashboard/feeds.tmpl | 2 | ||||
-rw-r--r-- | templates/user/profile.tmpl | 4 | ||||
-rw-r--r-- | templates/user/settings/profile.tmpl | 100 |
20 files changed, 239 insertions, 97 deletions
@@ -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. diff --git a/README_ZH.md b/README_ZH.md index ae60c36b34..7adc5dc566 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -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) 提供二进制编译与下载服务。 diff --git a/cmd/web.go b/cmd/web.go index de222d6fae..c2017a2850 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -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) diff --git a/conf/app.ini b/conf/app.ini index dbae8a4fd0..6374c2423f 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -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 diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 3cfc967158..6334966602 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -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 @@ -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()) diff --git a/models/action.go b/models/action.go index 334d143d2c..269fd753e8 100644 --- a/models/action.go +++ b/models/action.go @@ -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 diff --git a/models/user.go b/models/user.go index 31f4a289ff..1337ca2350 100644 --- a/models/user.go +++ b/models/user.go @@ -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" @@ -45,33 +49,40 @@ var ( // User represents the object of individual and member of organization. type User struct { - Id int64 - LowerName string `xorm:"UNIQUE NOT NULL"` - Name string `xorm:"UNIQUE NOT NULL"` - FullName string - Email string `xorm:"UNIQUE NOT NULL"` - Passwd string `xorm:"NOT NULL"` - LoginType LoginType - LoginSource int64 `xorm:"NOT NULL DEFAULT 0"` - LoginName string - Type UserType - Orgs []*User `xorm:"-"` - Repos []*Repository `xorm:"-"` + Id int64 + LowerName string `xorm:"UNIQUE NOT NULL"` + Name string `xorm:"UNIQUE NOT NULL"` + FullName string + Email string `xorm:"UNIQUE NOT NULL"` + Passwd string `xorm:"NOT NULL"` + LoginType LoginType + LoginSource int64 `xorm:"NOT NULL DEFAULT 0"` + LoginName string + Type UserType + Orgs []*User `xorm:"-"` + Repos []*Repository `xorm:"-"` + Location string + Website string + 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 - 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"` // For organization. Description string @@ -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,41 +568,38 @@ 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, - Commit: c, + User: u, + Commit: c, }) e = e.Next() } diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 6046a8d1ee..afdd8be0c9 100644 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -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)"` diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index fb198da149..144fda387e 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -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) } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 0f293b6905..49bd11c4c2 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -66,9 +66,10 @@ var ( ScriptType string // Picture settings. - PictureService string - GravatarSource string - DisableGravatar bool + PictureService string + AvatarUploadPath string + GravatarSource string + DisableGravatar bool // Log settings. LogRootPath string @@ -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/" diff --git a/routers/user/home.go b/routers/user/home.go index 031872fca9..1bb9701104 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -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 diff --git a/routers/user/setting.go b/routers/user/setting.go index bb0fa9103e..559e10fd89 100644 --- a/routers/user/setting.go +++ b/routers/user/setting.go @@ -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 diff --git a/templates/.VERSION b/templates/.VERSION index 92a14ffc74..5de719a000 100644 --- a/templates/.VERSION +++ b/templates/.VERSION @@ -1 +1 @@ -0.5.8.1119 Beta
\ No newline at end of file +0.5.8.1121 Beta
\ No newline at end of file diff --git a/templates/repo/commits_table.tmpl b/templates/repo/commits_table.tmpl index d48d61a5a7..eb819e387f 100644 --- a/templates/repo/commits_table.tmpl +++ b/templates/repo/commits_table.tmpl @@ -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> diff --git a/templates/repo/diff.tmpl b/templates/repo/diff.tmpl index 7c0d1b6bb8..8e6b1b0796 100644 --- a/templates/repo/diff.tmpl +++ b/templates/repo/diff.tmpl @@ -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> diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index e65d7c1134..d516eac94c 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -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> diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index 57c97def2e..834e5f0a56 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -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"> diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 46bc99bda4..44c2212383 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -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"> diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl index 5338d295f6..85db6f8951 100644 --- a/templates/user/settings/profile.tmpl +++ b/templates/user/settings/profile.tmpl @@ -11,49 +11,63 @@ <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"> - {{.CsrfTokenHtml}} - <div class="text-center panel-desc">{{.i18n.Tr "settings.profile_desc"}}</div> - <div class="field"> - <label>{{.i18n.Tr "settings.uid"}}</label> - <label class="text-left">{{.SignedUser.Id}}</label> - </div> - <div class="field"> - <label class="req" for="username">{{.i18n.Tr "username"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="username" name="uname" type="text" value="{{.SignedUser.Name}}" data-uname="{{.SignedUser.Name}}" required /> - </div> - <div class="white-popup-block mfp-hide" id="change-username-modal"> - <h1 class="text-red">{{.i18n.Tr "settings.change_username"}}</h1> - <p>{{.i18n.Tr "settings.change_username_desc"}}</p> - <br> - <button class="btn btn-red btn-large btn-radius" id="change-username-submit">{{.i18n.Tr "settings.continue"}}</button> - <button class="btn btn-large btn-radius popup-modal-dismiss">{{.i18n.Tr "settings.cancel"}}</button> - </div> - <div class="field"> - <label for="full-name">{{.i18n.Tr "settings.full_name"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_FullName}}ipt-error{{end}}" id="full-name" name="fullname" type="text" value="{{.SignedUser.FullName}}" /> - </div> - <div class="field"> - <label class="req" for="email">{{.i18n.Tr "email"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Email}}ipt-error{{end}}" id="email" name="email" type="email" value="{{.SignedUser.Email}}" required /> - </div> - <div class="field"> - <label for="website">{{.i18n.Tr "settings.website"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Website}}ipt-error{{end}}" id="website" name="website" type="url" value="{{.SignedUser.Website}}" /> - </div> - <div class="field"> - <label for="location">{{.i18n.Tr "settings.location"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Location}}ipt-error{{end}}" id="location" name="location" type="text" value="{{.SignedUser.Location}}" /> - </div> - <div class="field"> - <label for="gravatar-email">Gravatar {{.i18n.Tr "email"}}</label> - <input class="ipt ipt-large ipt-radius {{if .Err_Avatar}}ipt-error{{end}}" id="gravatar-email" name="avatar" type="text" value="{{.SignedUser.AvatarEmail}}" /> - </div> - <div class="field"> - <label></label> - <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> + <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"> + <label>{{.i18n.Tr "settings.uid"}}</label> + <label class="text-left">{{.SignedUser.Id}}</label> + </div> + <div class="field"> + <label class="req" for="username">{{.i18n.Tr "username"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="username" name="uname" type="text" value="{{.SignedUser.Name}}" data-uname="{{.SignedUser.Name}}" required /> + </div> + <div class="white-popup-block mfp-hide" id="change-username-modal"> + <h1 class="text-red">{{.i18n.Tr "settings.change_username"}}</h1> + <p>{{.i18n.Tr "settings.change_username_desc"}}</p> + <br> + <button class="btn btn-red btn-large btn-radius" id="change-username-submit">{{.i18n.Tr "settings.continue"}}</button> + <button class="btn btn-large btn-radius popup-modal-dismiss">{{.i18n.Tr "settings.cancel"}}</button> + </div> + <div class="field"> + <label for="full-name">{{.i18n.Tr "settings.full_name"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_FullName}}ipt-error{{end}}" id="full-name" name="fullname" type="text" value="{{.SignedUser.FullName}}" /> + </div> + <div class="field"> + <label class="req" for="email">{{.i18n.Tr "email"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Email}}ipt-error{{end}}" id="email" name="email" type="email" value="{{.SignedUser.Email}}" required /> + </div> + <div class="field"> + <label for="website">{{.i18n.Tr "settings.website"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Website}}ipt-error{{end}}" id="website" name="website" type="url" value="{{.SignedUser.Website}}" /> + </div> + <div class="field"> + <label for="location">{{.i18n.Tr "settings.location"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Location}}ipt-error{{end}}" id="location" name="location" type="text" value="{{.SignedUser.Location}}" /> + </div> + <div class="field"> + <label for="gravatar-email">Gravatar {{.i18n.Tr "email"}}</label> + <input class="ipt ipt-large ipt-radius {{if .Err_Avatar}}ipt-error{{end}}" id="gravatar-email" name="avatar" type="text" value="{{.SignedUser.AvatarEmail}}" /> + </div> + <div class="field"> + <label></label> + <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> |