mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Direct avatar rendering (#13649)
* Direct avatar rendering This adds new template helpers for avatar rendering which output image elements with direct links to avatars which makes them cacheable by the browsers. This should be a major performance improvment for pages with many avatars. * fix avatars of other user's profile pages * fix top border on user avatar name * uncircle avatars * remove old incomplete avatar selector * use title attribute for name and add it back on blame * minor refactor * tweak comments * fix url path join and adjust test to new result * dedupe functions
This commit is contained in:
@@ -168,7 +168,7 @@ func (s *SSPI) newUser(ctx *macaron.Context, username string, cfg *models.SSPICo
|
||||
IsActive: cfg.AutoActivateUsers,
|
||||
Language: cfg.DefaultLanguage,
|
||||
UseCustomAvatar: true,
|
||||
Avatar: base.DefaultAvatarLink(),
|
||||
Avatar: models.DefaultAvatarLink(),
|
||||
EmailNotificationsPreference: models.EmailNotificationsDisabled,
|
||||
}
|
||||
if err := models.CreateUser(user); err != nil {
|
||||
|
@@ -12,9 +12,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -134,93 +132,6 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
|
||||
return code
|
||||
}
|
||||
|
||||
// HashEmail hashes email address to MD5 string.
|
||||
// https://en.gravatar.com/site/implement/hash/
|
||||
func HashEmail(email string) string {
|
||||
return EncodeMD5(strings.ToLower(strings.TrimSpace(email)))
|
||||
}
|
||||
|
||||
// DefaultAvatarLink the default avatar link
|
||||
func DefaultAvatarLink() string {
|
||||
return setting.AppSubURL + "/img/avatar_default.png"
|
||||
}
|
||||
|
||||
// DefaultAvatarSize is a sentinel value for the default avatar size, as
|
||||
// determined by the avatar-hosting service.
|
||||
const DefaultAvatarSize = -1
|
||||
|
||||
// libravatarURL returns the URL for the given email. This function should only
|
||||
// be called if a federated avatar service is enabled.
|
||||
func libravatarURL(email string) (*url.URL, error) {
|
||||
urlStr, err := setting.LibravatarService.FromEmail(email)
|
||||
if err != nil {
|
||||
log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
|
||||
return nil, err
|
||||
}
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
log.Error("Failed to parse libravatar url(%s): error %v", urlStr, err)
|
||||
return nil, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// SizedAvatarLink returns a sized link to the avatar for the given email
|
||||
// address.
|
||||
func SizedAvatarLink(email string, size int) string {
|
||||
var avatarURL *url.URL
|
||||
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
|
||||
var err error
|
||||
avatarURL, err = libravatarURL(email)
|
||||
if err != nil {
|
||||
return DefaultAvatarLink()
|
||||
}
|
||||
} else if !setting.DisableGravatar {
|
||||
// copy GravatarSourceURL, because we will modify its Path.
|
||||
copyOfGravatarSourceURL := *setting.GravatarSourceURL
|
||||
avatarURL = ©OfGravatarSourceURL
|
||||
avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
|
||||
} else {
|
||||
return DefaultAvatarLink()
|
||||
}
|
||||
|
||||
vals := avatarURL.Query()
|
||||
vals.Set("d", "identicon")
|
||||
if size != DefaultAvatarSize {
|
||||
vals.Set("s", strconv.Itoa(size))
|
||||
}
|
||||
avatarURL.RawQuery = vals.Encode()
|
||||
return avatarURL.String()
|
||||
}
|
||||
|
||||
// SizedAvatarLinkWithDomain returns a sized link to the avatar for the given email
|
||||
// address.
|
||||
func SizedAvatarLinkWithDomain(email string, size int) string {
|
||||
var avatarURL *url.URL
|
||||
if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
|
||||
var err error
|
||||
avatarURL, err = libravatarURL(email)
|
||||
if err != nil {
|
||||
return DefaultAvatarLink()
|
||||
}
|
||||
} else if !setting.DisableGravatar {
|
||||
// copy GravatarSourceURL, because we will modify its Path.
|
||||
copyOfGravatarSourceURL := *setting.GravatarSourceURL
|
||||
avatarURL = ©OfGravatarSourceURL
|
||||
avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
|
||||
} else {
|
||||
return DefaultAvatarLink()
|
||||
}
|
||||
|
||||
vals := avatarURL.Query()
|
||||
vals.Set("d", "identicon")
|
||||
if size != DefaultAvatarSize {
|
||||
vals.Set("s", strconv.Itoa(size))
|
||||
}
|
||||
avatarURL.RawQuery = vals.Encode()
|
||||
return avatarURL.String()
|
||||
}
|
||||
|
||||
// FileSize calculates the file size and generate user-friendly string.
|
||||
func FileSize(s int64) string {
|
||||
return humanize.IBytes(uint64(s))
|
||||
|
@@ -5,11 +5,8 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -56,44 +53,6 @@ func TestBasicAuthEncode(t *testing.T) {
|
||||
// TODO: Test VerifyTimeLimitCode()
|
||||
// TODO: Test CreateTimeLimitCode()
|
||||
|
||||
func TestHashEmail(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
"d41d8cd98f00b204e9800998ecf8427e",
|
||||
HashEmail(""),
|
||||
)
|
||||
assert.Equal(t,
|
||||
"353cbad9b58e69c96154ad99f92bedc7",
|
||||
HashEmail("gitea@example.com"),
|
||||
)
|
||||
}
|
||||
|
||||
const gravatarSource = "https://secure.gravatar.com/avatar/"
|
||||
|
||||
func disableGravatar() {
|
||||
setting.EnableFederatedAvatar = false
|
||||
setting.LibravatarService = nil
|
||||
setting.DisableGravatar = true
|
||||
}
|
||||
|
||||
func enableGravatar(t *testing.T) {
|
||||
setting.DisableGravatar = false
|
||||
var err error
|
||||
setting.GravatarSourceURL, err = url.Parse(gravatarSource)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSizedAvatarLink(t *testing.T) {
|
||||
disableGravatar()
|
||||
assert.Equal(t, "/img/avatar_default.png",
|
||||
SizedAvatarLink("gitea@example.com", 100))
|
||||
|
||||
enableGravatar(t)
|
||||
assert.Equal(t,
|
||||
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100",
|
||||
SizedAvatarLink("gitea@example.com", 100),
|
||||
)
|
||||
}
|
||||
|
||||
func TestFileSize(t *testing.T) {
|
||||
var size int64 = 512
|
||||
assert.Equal(t, "512 B", FileSize(size))
|
||||
|
@@ -123,7 +123,7 @@ func (pc *PushCommits) AvatarLink(email string) string {
|
||||
var err error
|
||||
u, err = models.GetUserByEmail(email)
|
||||
if err != nil {
|
||||
pc.avatars[email] = models.AvatarLink(email)
|
||||
pc.avatars[email] = models.HashedAvatarLink(email)
|
||||
if !models.IsErrUserNotExist(err) {
|
||||
log.Error("GetUserByEmail: %v", err)
|
||||
return ""
|
||||
|
@@ -88,7 +88,6 @@ func NewFuncMap() []template.FuncMap {
|
||||
"AllowedReactions": func() []string {
|
||||
return setting.UI.Reactions
|
||||
},
|
||||
"AvatarLink": models.AvatarLink,
|
||||
"Safe": Safe,
|
||||
"SafeJS": SafeJS,
|
||||
"Str2html": Str2html,
|
||||
@@ -339,7 +338,9 @@ func NewFuncMap() []template.FuncMap {
|
||||
}
|
||||
return false
|
||||
},
|
||||
"svg": SVG,
|
||||
"svg": SVG,
|
||||
"avatar": Avatar,
|
||||
"avatarByEmail": AvatarByEmail,
|
||||
"SortArrow": func(normSort, revSort, urlSort string, isDefault bool) template.HTML {
|
||||
// if needed
|
||||
if len(normSort) == 0 || len(urlSort) == 0 {
|
||||
@@ -499,18 +500,38 @@ func NewTextFuncMap() []texttmpl.FuncMap {
|
||||
var widthRe = regexp.MustCompile(`width="[0-9]+?"`)
|
||||
var heightRe = regexp.MustCompile(`height="[0-9]+?"`)
|
||||
|
||||
// SVG render icons - arguments icon name (string), size (int), class (string)
|
||||
func SVG(icon string, others ...interface{}) template.HTML {
|
||||
size := 16
|
||||
func parseOthers(defaultSize int, defaultClass string, others ...interface{}) (int, string) {
|
||||
size := defaultSize
|
||||
if len(others) > 0 && others[0].(int) != 0 {
|
||||
size = others[0].(int)
|
||||
}
|
||||
|
||||
class := ""
|
||||
class := defaultClass
|
||||
if len(others) > 1 && others[1].(string) != "" {
|
||||
class = others[1].(string)
|
||||
if defaultClass == "" {
|
||||
class = others[1].(string)
|
||||
} else {
|
||||
class = defaultClass + " " + others[1].(string)
|
||||
}
|
||||
}
|
||||
|
||||
return size, class
|
||||
}
|
||||
|
||||
func avatarHTML(src string, size int, class string, name string) template.HTML {
|
||||
sizeStr := fmt.Sprintf(`%d`, size)
|
||||
|
||||
if name == "" {
|
||||
name = "avatar"
|
||||
}
|
||||
|
||||
return template.HTML(`<img class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
|
||||
}
|
||||
|
||||
// SVG render icons - arguments icon name (string), size (int), class (string)
|
||||
func SVG(icon string, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(16, "", others...)
|
||||
|
||||
if svgStr, ok := svg.SVGs[icon]; ok {
|
||||
if size != 16 {
|
||||
svgStr = widthRe.ReplaceAllString(svgStr, fmt.Sprintf(`width="%d"`, size))
|
||||
@@ -524,6 +545,38 @@ func SVG(icon string, others ...interface{}) template.HTML {
|
||||
return template.HTML("")
|
||||
}
|
||||
|
||||
// Avatar renders user and repo avatars. args: user/repo, size (int), class (string)
|
||||
func Avatar(item interface{}, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(28, "ui avatar image", others...)
|
||||
if user, ok := item.(*models.User); ok {
|
||||
src := user.RealSizedAvatarLink(size * 2) // request double size for finer rendering
|
||||
if src != "" {
|
||||
return avatarHTML(src, size, class, user.DisplayName())
|
||||
}
|
||||
}
|
||||
|
||||
if repo, ok := item.(*models.Repository); ok {
|
||||
src := repo.RelAvatarLink()
|
||||
if src != "" {
|
||||
return avatarHTML(src, size, class, repo.FullName())
|
||||
}
|
||||
}
|
||||
|
||||
return template.HTML("")
|
||||
}
|
||||
|
||||
// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
|
||||
func AvatarByEmail(email string, name string, others ...interface{}) template.HTML {
|
||||
size, class := parseOthers(28, "ui avatar image", others...)
|
||||
src := models.SizedAvatarLink(email, size*2) // request double size for finer rendering
|
||||
|
||||
if src != "" {
|
||||
return avatarHTML(src, size, class, name)
|
||||
}
|
||||
|
||||
return template.HTML("")
|
||||
}
|
||||
|
||||
// Safe render raw as HTML
|
||||
func Safe(raw string) template.HTML {
|
||||
return template.HTML(raw)
|
||||
|
Reference in New Issue
Block a user