Files
vikunja/pkg/modules/avatar/initials/initials.go
kolaente fb7764d9f1 feat: format user mentions with display names in email notifications (#1930)
Email notifications now display user mentions with inline avatar images for improved visual recognition and easier identification. Mentions gracefully fall back to display names if avatars are unavailable.
2025-12-10 12:39:05 +01:00

99 lines
2.9 KiB
Go

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package initials
import (
"encoding/base64"
"fmt"
"html"
"strconv"
"strings"
"code.vikunja.io/api/pkg/user"
)
// Provider represents the provider implementation of the initials provider
type Provider struct {
}
// FlushCache is a no-op for the initials provider since SVG generation is lightweight
func (p *Provider) FlushCache(_ *user.User) error { return nil }
var avatarBgColors = []string{
"#e0f8d9",
"#e3f5f8",
"#faeefb",
"#f1efff",
"#ffecf0",
"#ffefe4",
}
var avatarTextColors = []string{
"#005f00",
"#00548c",
"#822198",
"#5d26cd",
"#9f0850",
"#9b2200",
}
// GetAvatar returns an initials avatar for a user as SVG
func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType string, err error) {
// Get the text to display
avatarText := u.Name
if avatarText == "" {
avatarText = u.Username
}
if avatarText == "" {
return nil, "", fmt.Errorf("user has no name or username")
}
// Get the first character and convert to uppercase
firstRune := []rune(strings.ToUpper(avatarText))[0]
initial := html.EscapeString(string(firstRune))
// Select background and text colors based on user ID
colorIndex := int(u.ID) % len(avatarBgColors)
bgColor := avatarBgColors[colorIndex]
textColor := avatarTextColors[colorIndex]
// Convert size to string
sizeStr := strconv.FormatInt(size, 10)
// Generate SVG
svg := fmt.Sprintf(`<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" width="%s" height="%s">
<rect width="100" height="100" fill="%s"/>
<text x="50" y="50" font-family="sans-serif" font-size="50" fill="%s" text-anchor="middle" dominant-baseline="central">%s</text>
</svg>`, sizeStr, sizeStr, bgColor, textColor, initial)
return []byte(svg), "image/svg+xml", nil
}
// AsDataURI returns a data URI for the SVG avatar
func (p *Provider) AsDataURI(u *user.User, size int64) (string, error) {
avatarData, mimeType, err := p.GetAvatar(u, size)
if err != nil {
return "", err
}
// Encode the SVG as base64 and create a data URI
base64Data := base64.StdEncoding.EncodeToString(avatarData)
dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data)
return dataURI, nil
}