mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat: user avatars
This commit is contained in:
parent
76a7cf7e80
commit
5df0f48303
@ -145,6 +145,7 @@ You can specify configuration options either via a config file (default: `config
|
||||
|---------------------------|---------------------------|--------------|---------------------------------------------------------------------|
|
||||
| `env` | `ENVIRONMENT` | `dev` | Whether to use development- or production settings |
|
||||
| `app.custom_languages` | - | - | Map from file endings to language names |
|
||||
| `app.avatar_url_template` | - | (see [`config.default.yml`](config.default.yml)) | URL template for external user avatar images (e.g. from [Dicebear](https://dicebear.com) or [Gravatar](https://gravatar.com)) |
|
||||
| `server.port` | `WAKAPI_PORT` | `3000` | Port to listen on |
|
||||
| `server.listen_ipv4` | `WAKAPI_LISTEN_IPV4` | `127.0.0.1` | IPv4 network address to listen on (leave blank to disable IPv4) |
|
||||
| `server.listen_ipv6` | `WAKAPI_LISTEN_IPV6` | `::1` | IPv6 network address to listen on (leave blank to disable IPv6) |
|
||||
|
@ -21,6 +21,10 @@ app:
|
||||
jsx: JSX
|
||||
svelte: Svelte
|
||||
|
||||
# url template for user avatar images (to be used with services like gravatar or dicebear)
|
||||
# available variable placeholders are: username, username_hash, email, email_hash
|
||||
avatar_url_template: https://avatars.dicebear.com/api/pixel-art-neutral/{username_hash}.svg
|
||||
|
||||
db:
|
||||
host: # leave blank when using sqlite3
|
||||
port: # leave blank when using sqlite3
|
||||
|
@ -68,6 +68,7 @@ type appConfig struct {
|
||||
ImportBatchSize int `yaml:"import_batch_size" default:"50" env:"WAKAPI_IMPORT_BATCH_SIZE"`
|
||||
InactiveDays int `yaml:"inactive_days" default:"7" env:"WAKAPI_INACTIVE_DAYS"`
|
||||
CountCacheTTLMin int `yaml:"count_cache_ttl_min" default:"30" env:"WAKAPI_COUNT_CACHE_TTL_MIN"`
|
||||
AvatarURLTemplate string `yaml:"avatar_url_template" default:"https://avatars.dicebear.com/api/pixel-art-neutral/{username_hash}.svg"`
|
||||
CustomLanguages map[string]string `yaml:"custom_languages"`
|
||||
Colors map[string]map[string]string `yaml:"-"`
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ type SummaryViewModel struct {
|
||||
*Summary
|
||||
*SummaryParams
|
||||
User *User
|
||||
AvatarURL string
|
||||
LanguageColors map[string]string
|
||||
EditorColors map[string]string
|
||||
OSColors map[string]string
|
||||
|
@ -1,7 +1,10 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -92,6 +95,18 @@ func (u *User) TZOffset() time.Duration {
|
||||
return time.Duration(offset * int(time.Second))
|
||||
}
|
||||
|
||||
func (u *User) AvatarURL(urlTemplate string) string {
|
||||
urlTemplate = strings.ReplaceAll(urlTemplate, "{username}", u.ID)
|
||||
urlTemplate = strings.ReplaceAll(urlTemplate, "{email}", u.Email)
|
||||
if strings.Contains(urlTemplate, "{username_hash}") {
|
||||
urlTemplate = strings.ReplaceAll(urlTemplate, "{username_hash}", fmt.Sprintf("%x", md5.Sum([]byte(u.ID))))
|
||||
}
|
||||
if strings.Contains(urlTemplate, "{email_hash}") {
|
||||
urlTemplate = strings.ReplaceAll(urlTemplate, "{email_hash}", fmt.Sprintf("%x", md5.Sum([]byte(u.Email))))
|
||||
}
|
||||
return urlTemplate
|
||||
}
|
||||
|
||||
func (c *CredentialsReset) IsValid() bool {
|
||||
return ValidatePassword(c.PasswordNew) &&
|
||||
c.PasswordNew == c.PasswordRepeat
|
||||
|
@ -52,6 +52,9 @@ func DefaultTemplateFuncs() template.FuncMap {
|
||||
"htmlSafe": func(html string) template.HTML {
|
||||
return template.HTML(html)
|
||||
},
|
||||
"avatarUrlTemplate": func() string {
|
||||
return config.Get().App.AvatarURLTemplate
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,8 @@ let icons = [
|
||||
'twemoji:gear',
|
||||
'eva:corner-right-down-fill',
|
||||
'bi:heart-fill',
|
||||
'fxemoji:running',
|
||||
'ic:round-person'
|
||||
]
|
||||
|
||||
const output = path.normalize(path.join(__dirname, '../static/assets/icons.js'))
|
||||
|
@ -420,6 +420,13 @@ function hexToRgb(hex) {
|
||||
} : null;
|
||||
}
|
||||
|
||||
function showUserMenuPopup(event) {
|
||||
const el = document.getElementById('user-menu-popup')
|
||||
el.classList.remove('hidden')
|
||||
el.classList.add('block')
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
function showApiKeyPopup(event) {
|
||||
const el = document.getElementById('api-key-popup')
|
||||
el.classList.remove('hidden')
|
||||
|
File diff suppressed because one or more lines are too long
102
static/assets/vendor/tailwind.dist.css
vendored
102
static/assets/vendor/tailwind.dist.css
vendored
@ -9,8 +9,10 @@
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
line-height: 1.15;
|
||||
/* 1 */
|
||||
-webkit-text-size-adjust: 100%;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
@ -51,9 +53,12 @@ h1 {
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
box-sizing: content-box;
|
||||
/* 1 */
|
||||
height: 0;
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,8 +67,10 @@ hr {
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
font-family: monospace, monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
@ -83,10 +90,13 @@ a {
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
border-bottom: none;
|
||||
/* 1 */
|
||||
text-decoration: underline;
|
||||
/* 2 */
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
text-decoration: underline dotted;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,8 +116,10 @@ strong {
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
font-family: monospace, monospace;
|
||||
/* 1 */
|
||||
font-size: 1em;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,10 +175,14 @@ input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: inherit; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
font-family: inherit;
|
||||
/* 1 */
|
||||
font-size: 100%;
|
||||
/* 1 */
|
||||
line-height: 1.15;
|
||||
/* 1 */
|
||||
margin: 0;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,7 +191,8 @@ textarea {
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
input {
|
||||
/* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@ -185,7 +202,8 @@ input { /* 1 */
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
select {
|
||||
/* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
@ -239,12 +257,18 @@ fieldset {
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
color: inherit;
|
||||
/* 2 */
|
||||
display: table;
|
||||
/* 1 */
|
||||
max-width: 100%;
|
||||
/* 1 */
|
||||
padding: 0;
|
||||
/* 3 */
|
||||
white-space: normal;
|
||||
/* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,8 +418,10 @@ ul {
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 1 */
|
||||
line-height: 1.5; /* 2 */
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
/* 1 */
|
||||
line-height: 1.5;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
@ -427,10 +453,14 @@ html {
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box; /* 1 */
|
||||
border-width: 0; /* 2 */
|
||||
border-style: solid; /* 2 */
|
||||
border-color: #e2e8f0; /* 2 */
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
border-width: 0;
|
||||
/* 2 */
|
||||
border-style: solid;
|
||||
/* 2 */
|
||||
border-color: #e2e8f0;
|
||||
/* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
@ -723,6 +753,10 @@ video {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-width: 1px;
|
||||
}
|
||||
@ -831,6 +865,10 @@ video {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.font-semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -999,6 +1037,10 @@ video {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
.mt-24 {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
|
||||
.-ml-1 {
|
||||
margin-left: -0.25rem;
|
||||
}
|
||||
|
@ -19,6 +19,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden flex bg-gray-800 shadow-md z-10 p-2 absolute top-0 right-0 mt-10 mr-8 border border-green-700 rounded popup mt-24"
|
||||
id="user-menu-popup" style="min-width: 200px;">
|
||||
<div class="flex-grow flex flex-col px-2">
|
||||
<div class="flex flex-col text-xs text-gray-300 mx-1 mb-4 items-center">
|
||||
<span class="font-semibold">{{ .User.ID }}</span>
|
||||
{{ if .User.Email }}
|
||||
<span>({{ .User.Email }})</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<form action="logout" method="post" class="flex-grow">
|
||||
<button type="submit" class="py-1 px-3 h-8 rounded bg-green-700 text-white text-sm w-full">
|
||||
<span>Logout</span>
|
||||
<span class="iconify inline" data-icon="fxemoji:running"></span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute flex top-0 right-0 mr-8 mt-10 py-2">
|
||||
<div class="mx-1">
|
||||
<button type="button" class="py-1 px-3 h-8 rounded border border-green-700 text-white text-sm"
|
||||
@ -30,10 +48,12 @@
|
||||
<span class="iconify inline" data-icon="twemoji:gear"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mx-1">
|
||||
<form action="logout" method="post">
|
||||
<button type="submit" class="py-1 px-3 h-8 rounded border border-green-700 text-white text-sm">Logout</button>
|
||||
</form>
|
||||
<div class="mx-1 flex items-center">
|
||||
{{ if avatarUrlTemplate }}
|
||||
<img src="{{ .User.AvatarURL avatarUrlTemplate }}" width="32px" class="rounded-full border-2 border-green-700 cursor-pointer" onclick="showUserMenuPopup(event)" alt="User Profile Avatar" title="Looks like you, doesn't it?"/>
|
||||
{{ else }}
|
||||
<span class="iconify inline cursor-pointer text-gray-500 rounded-full border-2 border-green-700" style="width: 32px; height: 32px" data-icon="ic:round-person" onclick="showUserMenuPopup(event)"></span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user