| @@ -217,6 +217,7 @@ func runWeb(ctx *cli.Context) { | |||||
| 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.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar) | ||||
| m.Post("/avatar/delete", user.SettingsDeleteAvatar) | |||||
| m.Combo("/email").Get(user.SettingsEmails). | m.Combo("/email").Get(user.SettingsEmails). | ||||
| Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost) | Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost) | ||||
| m.Post("/email/delete", user.DeleteEmail) | m.Post("/email/delete", user.DeleteEmail) | ||||
| @@ -264,11 +264,10 @@ continue = Continue | |||||
| cancel = Cancel | cancel = Cancel | ||||
| enable_custom_avatar = Enable Custom Avatar | enable_custom_avatar = Enable Custom Avatar | ||||
| enable_custom_avatar_helper = Disable fetch from Gravatar | |||||
| choose_new_avatar = Choose new avatar | choose_new_avatar = Choose new avatar | ||||
| update_avatar = Update Avatar Setting | update_avatar = Update Avatar Setting | ||||
| delete_current_avatar = Delete Current Avatar | |||||
| uploaded_avatar_not_a_image = Uploaded file is not a image. | uploaded_avatar_not_a_image = Uploaded file is not a image. | ||||
| no_custom_avatar_available = No custom avatar available, cannot enable it. | |||||
| update_avatar_success = Your avatar setting has been updated successfully. | update_avatar_success = Your avatar setting has been updated successfully. | ||||
| change_password = Change Password | change_password = Change Password | ||||
| @@ -17,7 +17,7 @@ import ( | |||||
| "github.com/gogits/gogs/modules/setting" | "github.com/gogits/gogs/modules/setting" | ||||
| ) | ) | ||||
| const APP_VER = "0.8.57.0304" | |||||
| const APP_VER = "0.8.57.0305" | |||||
| func init() { | func init() { | ||||
| runtime.GOMAXPROCS(runtime.NumCPU()) | runtime.GOMAXPROCS(runtime.NumCPU()) | ||||
| @@ -617,7 +617,7 @@ func ChangeUserName(u *User, newUserName string) (err error) { | |||||
| } | } | ||||
| func updateUser(e Engine, u *User) error { | func updateUser(e Engine, u *User) error { | ||||
| // Organization does not need e-mail. | |||||
| // Organization does not need email | |||||
| if !u.IsOrganization() { | if !u.IsOrganization() { | ||||
| u.Email = strings.ToLower(u.Email) | u.Email = strings.ToLower(u.Email) | ||||
| has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User)) | has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User)) | ||||
| @@ -634,16 +634,9 @@ func updateUser(e Engine, u *User) error { | |||||
| } | } | ||||
| u.LowerName = strings.ToLower(u.Name) | u.LowerName = strings.ToLower(u.Name) | ||||
| if len(u.Location) > 255 { | |||||
| u.Location = u.Location[:255] | |||||
| } | |||||
| if len(u.Website) > 255 { | |||||
| u.Website = u.Website[:255] | |||||
| } | |||||
| if len(u.Description) > 255 { | |||||
| u.Description = u.Description[:255] | |||||
| } | |||||
| u.Location = base.TruncateString(u.Location, 255) | |||||
| u.Website = base.TruncateString(u.Website, 255) | |||||
| u.Description = base.TruncateString(u.Description, 255) | |||||
| u.FullName = markdown.Sanitizer.Sanitize(u.FullName) | u.FullName = markdown.Sanitizer.Sanitize(u.FullName) | ||||
| _, err := e.Id(u.Id).AllCols().Update(u) | _, err := e.Id(u.Id).AllCols().Update(u) | ||||
| @@ -464,6 +464,15 @@ func EllipsisString(str string, length int) string { | |||||
| return str[:length-3] + "..." | return str[:length-3] + "..." | ||||
| } | } | ||||
| // TruncateString returns a truncated string with given limit, | |||||
| // it returns input string if length is not reached limit. | |||||
| func TruncateString(str string, limit int) string { | |||||
| if len(str) < limit { | |||||
| return str | |||||
| } | |||||
| return str[:limit] | |||||
| } | |||||
| // StringsToInt64s converts a slice of string to a slice of int64. | // StringsToInt64s converts a slice of string to a slice of int64. | ||||
| func StringsToInt64s(strs []string) []int64 { | func StringsToInt64s(strs []string) []int64 { | ||||
| ints := make([]int64, len(strs)) | ints := make([]int64, len(strs)) | ||||
| @@ -18,7 +18,7 @@ function initCommentPreviewTab($form) { | |||||
| var $preview_tab = $form.find('.tab.segment[data-tab="' + $tab_menu.data('preview') + '"]'); | var $preview_tab = $form.find('.tab.segment[data-tab="' + $tab_menu.data('preview') + '"]'); | ||||
| $preview_tab.html(data); | $preview_tab.html(data); | ||||
| emojify.run($preview_tab[0]); | emojify.run($preview_tab[0]); | ||||
| $('pre code', $preview_tab[0]).each(function(i, block) { | |||||
| $('pre code', $preview_tab[0]).each(function (i, block) { | |||||
| hljs.highlightBlock(block); | hljs.highlightBlock(block); | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -322,8 +322,7 @@ function initRepository() { | |||||
| }; | }; | ||||
| $('#edit-title').click(editTitleToggle); | $('#edit-title').click(editTitleToggle); | ||||
| $('#cancel-edit-title').click(editTitleToggle); | $('#cancel-edit-title').click(editTitleToggle); | ||||
| $('#save-edit-title').click(editTitleToggle). | |||||
| click(function () { | |||||
| $('#save-edit-title').click(editTitleToggle).click(function () { | |||||
| if ($edit_input.val().length == 0 || | if ($edit_input.val().length == 0 || | ||||
| $edit_input.val() == $issue_title.text()) { | $edit_input.val() == $issue_title.text()) { | ||||
| $edit_input.val($issue_title.text()); | $edit_input.val($issue_title.text()); | ||||
| @@ -385,7 +384,7 @@ function initRepository() { | |||||
| } else { | } else { | ||||
| $render_content.html(data.content); | $render_content.html(data.content); | ||||
| emojify.run($render_content[0]); | emojify.run($render_content[0]); | ||||
| $('pre code', $render_content[0]).each(function(i, block) { | |||||
| $('pre code', $render_content[0]).each(function (i, block) { | |||||
| hljs.highlightBlock(block); | hljs.highlightBlock(block); | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -521,10 +520,8 @@ function initOrganization() { | |||||
| } | } | ||||
| } | } | ||||
| function initUser() { | |||||
| if ($('.user').length == 0) { | |||||
| return; | |||||
| } | |||||
| function initUserSettings() { | |||||
| console.log('initUserSettings'); | |||||
| // Options | // Options | ||||
| if ($('.user.settings.profile').length > 0) { | if ($('.user.settings.profile').length > 0) { | ||||
| @@ -796,10 +793,7 @@ $(document).ready(function () { | |||||
| // Show exact time | // Show exact time | ||||
| $('.time-since').each(function () { | $('.time-since').each(function () { | ||||
| $(this).addClass('poping up'). | |||||
| attr('data-content', $(this).attr('title')). | |||||
| attr('data-variation', 'inverted tiny'). | |||||
| attr('title', ''); | |||||
| $(this).addClass('poping up').attr('data-content', $(this).attr('title')).attr('data-variation', 'inverted tiny').attr('title', ''); | |||||
| }); | }); | ||||
| // Semantic UI modules. | // Semantic UI modules. | ||||
| @@ -879,8 +873,8 @@ $(document).ready(function () { | |||||
| ignore_emoticons: true | ignore_emoticons: true | ||||
| }); | }); | ||||
| var hasEmoji = document.getElementsByClassName('has-emoji'); | var hasEmoji = document.getElementsByClassName('has-emoji'); | ||||
| for (var i = 0; i < hasEmoji.length; i++) { | |||||
| emojify.run(hasEmoji[i]); | |||||
| for (var i = 0; i < hasEmoji.length; i++) { | |||||
| emojify.run(hasEmoji[i]); | |||||
| } | } | ||||
| // Clipboard JS | // Clipboard JS | ||||
| @@ -928,6 +922,14 @@ $(document).ready(function () { | |||||
| $('.show-modal.button').click(function () { | $('.show-modal.button').click(function () { | ||||
| $($(this).data('modal')).modal('show'); | $($(this).data('modal')).modal('show'); | ||||
| }); | }); | ||||
| $('.delete-post.button').click(function(){ | |||||
| var $this = $(this); | |||||
| $.post($this.data('request-url'),{ | |||||
| "_csrf": csrf | |||||
| }).done(function(){ | |||||
| window.location.href = $this.data('done-url'); | |||||
| }); | |||||
| }); | |||||
| // Set anchor. | // Set anchor. | ||||
| $('.markdown').each(function () { | $('.markdown').each(function () { | ||||
| @@ -953,15 +955,25 @@ $(document).ready(function () { | |||||
| searchUsers(); | searchUsers(); | ||||
| searchRepositories(); | searchRepositories(); | ||||
| initCommentForm(); | initCommentForm(); | ||||
| initInstall(); | initInstall(); | ||||
| initRepository(); | initRepository(); | ||||
| initWiki(); | initWiki(); | ||||
| initOrganization(); | initOrganization(); | ||||
| initUser(); | |||||
| initWebhook(); | initWebhook(); | ||||
| initAdmin(); | initAdmin(); | ||||
| var routes = { | |||||
| 'div.user.settings': initUserSettings | |||||
| }; | |||||
| var selector; | |||||
| for (selector in routes) { | |||||
| if ($(selector).length > 0) { | |||||
| routes[selector](); | |||||
| break; | |||||
| } | |||||
| } | |||||
| }); | }); | ||||
| $(window).load(function () { | $(window).load(function () { | ||||
| @@ -1053,11 +1065,12 @@ $(window).load(function () { | |||||
| case 'ssh': | case 'ssh': | ||||
| if ($('#repo-clone-ssh').click().length === 0) { | if ($('#repo-clone-ssh').click().length === 0) { | ||||
| $('#repo-clone-https').click(); | $('#repo-clone-https').click(); | ||||
| }; | |||||
| } | |||||
| ; | |||||
| break; | break; | ||||
| default: | default: | ||||
| $('#repo-clone-https').click(); | $('#repo-clone-https').click(); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| }); | |||||
| }); | |||||
| @@ -8,6 +8,7 @@ import ( | |||||
| "errors" | "errors" | ||||
| "fmt" | "fmt" | ||||
| "io/ioutil" | "io/ioutil" | ||||
| "os" | |||||
| "strings" | "strings" | ||||
| "github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
| @@ -39,12 +40,13 @@ func Settings(ctx *middleware.Context) { | |||||
| ctx.HTML(200, SETTINGS_PROFILE) | ctx.HTML(200, SETTINGS_PROFILE) | ||||
| } | } | ||||
| func handlerUsernameChange(ctx *middleware.Context, newName string) { | |||||
| func handleUsernameChange(ctx *middleware.Context, newName string) { | |||||
| // Non-local users are not allowed to change their username. | |||||
| if len(newName) == 0 || !ctx.User.IsLocal() { | if len(newName) == 0 || !ctx.User.IsLocal() { | ||||
| return | return | ||||
| } | } | ||||
| // Check if user name has been changed. | |||||
| // Check if user name has been changed | |||||
| if ctx.User.LowerName != strings.ToLower(newName) { | if ctx.User.LowerName != strings.ToLower(newName) { | ||||
| if err := models.ChangeUserName(ctx.User, newName); err != nil { | if err := models.ChangeUserName(ctx.User, newName); err != nil { | ||||
| switch { | switch { | ||||
| @@ -67,7 +69,8 @@ func handlerUsernameChange(ctx *middleware.Context, newName string) { | |||||
| } | } | ||||
| log.Trace("User name changed: %s -> %s", ctx.User.Name, newName) | log.Trace("User name changed: %s -> %s", ctx.User.Name, newName) | ||||
| } | } | ||||
| // In case it's just a case change. | |||||
| // In case it's just a case change | |||||
| ctx.User.Name = newName | ctx.User.Name = newName | ||||
| ctx.User.LowerName = strings.ToLower(newName) | ctx.User.LowerName = strings.ToLower(newName) | ||||
| } | } | ||||
| @@ -81,7 +84,7 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateProfileForm) { | |||||
| return | return | ||||
| } | } | ||||
| handlerUsernameChange(ctx, form.Name) | |||||
| handleUsernameChange(ctx, form.Name) | |||||
| if ctx.Written() { | if ctx.Written() { | ||||
| return | return | ||||
| } | } | ||||
| @@ -98,7 +101,8 @@ func SettingsPost(ctx *middleware.Context, form auth.UpdateProfileForm) { | |||||
| ctx.Handle(500, "UpdateUser", err) | ctx.Handle(500, "UpdateUser", err) | ||||
| return | return | ||||
| } | } | ||||
| log.Trace("User setting updated: %s", ctx.User.Name) | |||||
| log.Trace("User settings updated: %s", ctx.User.Name) | |||||
| ctx.Flash.Success(ctx.Tr("settings.update_profile_success")) | ctx.Flash.Success(ctx.Tr("settings.update_profile_success")) | ||||
| ctx.Redirect(setting.AppSubUrl + "/user/settings") | ctx.Redirect(setting.AppSubUrl + "/user/settings") | ||||
| } | } | ||||
| @@ -112,10 +116,11 @@ func UpdateAvatarSetting(ctx *middleware.Context, form auth.UploadAvatarForm, ct | |||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("Avatar.Open: %v", err) | return fmt.Errorf("Avatar.Open: %v", err) | ||||
| } | } | ||||
| defer fr.Close() | |||||
| data, err := ioutil.ReadAll(fr) | data, err := ioutil.ReadAll(fr) | ||||
| if err != nil { | if err != nil { | ||||
| return fmt.Errorf("ReadAll: %v", err) | |||||
| return fmt.Errorf("ioutil.ReadAll: %v", err) | |||||
| } | } | ||||
| if _, ok := base.IsImageFile(data); !ok { | if _, ok := base.IsImageFile(data); !ok { | ||||
| return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image")) | return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image")) | ||||
| @@ -124,9 +129,12 @@ func UpdateAvatarSetting(ctx *middleware.Context, form auth.UploadAvatarForm, ct | |||||
| return fmt.Errorf("UploadAvatar: %v", err) | return fmt.Errorf("UploadAvatar: %v", err) | ||||
| } | } | ||||
| } else { | } else { | ||||
| // In case no avatar at all. | |||||
| if form.Enable && !com.IsFile(ctx.User.CustomAvatarPath()) { | |||||
| return errors.New(ctx.Tr("settings.no_custom_avatar_available")) | |||||
| // No avatar is uploaded but setting has been changed to enable, | |||||
| // generate a random one when needed. | |||||
| if form.Enable && !com.IsFile(ctxUser.CustomAvatarPath()) { | |||||
| if err := ctxUser.GenerateRandomAvatar(); err != nil { | |||||
| log.Error(4, "GenerateRandomAvatar[%d]: %v", ctxUser.Id, err) | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -147,6 +155,16 @@ func SettingsAvatar(ctx *middleware.Context, form auth.UploadAvatarForm) { | |||||
| ctx.Redirect(setting.AppSubUrl + "/user/settings") | ctx.Redirect(setting.AppSubUrl + "/user/settings") | ||||
| } | } | ||||
| func SettingsDeleteAvatar(ctx *middleware.Context) { | |||||
| os.Remove(ctx.User.CustomAvatarPath()) | |||||
| ctx.User.UseCustomAvatar = false | |||||
| if err := models.UpdateUser(ctx.User); err != nil { | |||||
| ctx.Flash.Error(fmt.Sprintf("UpdateUser: %v", err)) | |||||
| } | |||||
| ctx.Redirect(setting.AppSubUrl + "/user/settings") | |||||
| } | |||||
| func SettingsPassword(ctx *middleware.Context) { | func SettingsPassword(ctx *middleware.Context) { | ||||
| ctx.Data["Title"] = ctx.Tr("settings") | ctx.Data["Title"] = ctx.Tr("settings") | ||||
| ctx.Data["PageIsSettingsPassword"] = true | ctx.Data["PageIsSettingsPassword"] = true | ||||
| @@ -1 +1 @@ | |||||
| 0.8.57.0304 | |||||
| 0.8.57.0305 | |||||
| @@ -52,10 +52,9 @@ | |||||
| <form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> | <form class="ui form" action="{{.Link}}/avatar" method="post" enctype="multipart/form-data"> | ||||
| {{.CsrfTokenHtml}} | {{.CsrfTokenHtml}} | ||||
| <div class="inline field"> | <div class="inline field"> | ||||
| <label>{{.i18n.Tr "settings.enable_custom_avatar"}}</label> | |||||
| <div class="ui checkbox"> | <div class="ui checkbox"> | ||||
| <input name="enable" type="checkbox" {{if .SignedUser.UseCustomAvatar}}checked{{end}}> | <input name="enable" type="checkbox" {{if .SignedUser.UseCustomAvatar}}checked{{end}}> | ||||
| <label>{{.i18n.Tr "settings.enable_custom_avatar_helper"}}</label> | |||||
| <label>{{.i18n.Tr "settings.enable_custom_avatar"}}</label> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div class="inline field"> | <div class="inline field"> | ||||
| @@ -65,6 +64,7 @@ | |||||
| <div class="field"> | <div class="field"> | ||||
| <button class="ui green button">{{$.i18n.Tr "settings.update_avatar"}}</button> | <button class="ui green button">{{$.i18n.Tr "settings.update_avatar"}}</button> | ||||
| <a class="ui red button delete-post" data-request-url="{{.Link}}/avatar/delete" data-done-url="{{.Link}}">{{$.i18n.Tr "settings.delete_current_avatar"}}</a> | |||||
| </div> | </div> | ||||
| </form> | </form> | ||||
| </div> | </div> | ||||