feat(userVignette): show by preference givenname/familyname

This commit is contained in:
Edouard Vanbelle
2026-06-05 10:05:51 +02:00
parent 8cc21f17c5
commit 3eb74f83b0
3 changed files with 45 additions and 9 deletions
+12 -1
View File
@@ -155,14 +155,25 @@ export function createUserVignette(userId, size = 'sm', { showName = true, showE
// longer existed in the DOM, leaving the badge invisible until
// the next render. Creating-then-appending keeps the icon system
// and our reveal step in agreement.
//
// We always fetch the email — when `showEmail` is false (the common
// case) it's still used as the hover-tooltip on the vignette so the
// recipient identifier stays discoverable without visual clutter.
Promise.all([
systemUsers.getDisplayName(userId),
systemUsers.getPhoto(userId),
emailEl ? systemUsers.getEmail(userId) : Promise.resolve(null),
systemUsers.getEmail(userId),
showOrigin ? systemUsers.getIsExternal(userId) : Promise.resolve(false)
]).then(([name, photo, email, isExternal]) => {
if (nameEl) nameEl.textContent = name;
if (emailEl) emailEl.textContent = email ?? '';
// Tooltip: surface the email on hover when it's not already
// rendered as the visible label (showEmail mode) and isn't
// already the displayed name (the fallback case where the user
// has no given/family/username and the label IS the email).
if (email && !showEmail && email !== name) {
wrapper.title = email;
}
if (photo) {
_applyPhoto(avatar, photo, name);
} else {
+31 -6
View File
@@ -53,6 +53,31 @@ function _nameFor(c) {
return `${c.id.slice(0, 8)}…`;
}
/**
* Derive the best display name from a `User` shape (i.e. the
* `/api/users/{id}` payload OR the `oxicloud_user` localStorage blob).
* Priority — matches the server-side `User::display_full()` rule sans
* the email decoration; the `<email>` part is added in the vignette
* layer as a tooltip when the email isn't already in the displayed
* label:
*
* 1. `"Given Family"` — both names set
* 2. `username` — handle (the typical case for password / OIDC
* users with no profile claims)
* 3. `email` — last resort but unambiguous
* 4. shortened UUID — failure mode (e.g. /api/users/{id} returned
* nothing usable)
*
* @param {{id?: string, given_name?: string|null, family_name?: string|null, username?: string|null, email?: string|null}} u
* @returns {string}
*/
function _displayNameFromUser(u) {
if (u.given_name && u.family_name) return `${u.given_name} ${u.family_name}`;
if (u.username) return u.username;
if (u.email) return u.email;
return u.id ? `${u.id.slice(0, 8)}…` : '?';
}
/**
* Ensure both indexes are built (idempotent).
* After loading contacts from the system address book, the current user
@@ -93,13 +118,13 @@ async function _ensureIndex() {
try {
const raw = localStorage.getItem('oxicloud_user');
if (raw) {
const u = /** @type {{id?:string, display_name?:string, username?:string, email?:string, image?:string|null, is_external?:boolean}} */ (
JSON.parse(raw)
);
const u =
/** @type {{id?:string, given_name?:string|null, family_name?:string|null, username?:string|null, email?:string|null, image?:string|null, is_external?:boolean}} */ (
JSON.parse(raw)
);
if (u?.id) {
if (!_index.has(u.id)) {
const name = u.display_name || u.username || u.email || `${u.id.slice(0, 8)}…`;
_index.set(u.id, name);
_index.set(u.id, _displayNameFromUser(u));
}
if (!_photoIndex.has(u.id)) {
_photoIndex.set(u.id, u.image ?? null);
@@ -143,7 +168,7 @@ async function _resolveMissing(userId) {
if (!resp.ok) return;
/** @type {User} */
const u = await resp.json();
_index?.set(u.id, u.username || u.email || `${u.id.slice(0, 8)}…`);
_index?.set(u.id, _displayNameFromUser(u));
_photoIndex?.set(u.id, u.image ?? null);
_emailIndex?.set(u.id, u.email ?? null);
_externalIndex?.set(u.id, !!u.is_external);
+2 -2
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} shared a {{kind}} with you on OxiCloud",
"body": "{{inviter}} shared a {{kind}} with you on OxiCloud.\n\nOpen it by clicking the link below:\n{{link}}\n\nThe link works once and expires in {{ttl_hours}} hours.\nIf you didn't expect this invitation, you can safely ignore this message.\n\n— OxiCloud"
"body": "{{inviter_full}} shared a {{kind}} with you on OxiCloud.\n\nOpen it by clicking the link below:\n{{link}}\n\nThe link works once and expires in {{ttl_hours}} hours.\nIf you didn't expect this invitation, you can safely ignore this message.\n\n— OxiCloud"
},
"login": {
"subject": "Sign in to OxiCloud",
@@ -34,7 +34,7 @@
"notification": {
"share": {
"subject": "{{inviter}} shared a {{kind}} with you on OxiCloud",
"body": "{{inviter}} shared a {{kind}} with you on OxiCloud.\n\nOpen OxiCloud to see your new share:\n{{login_link}}\n\nYou may have additional new shares from {{inviter}} — sign in to see all your shared items.\n\n— OxiCloud\n\nYou're receiving this message because you have an OxiCloud account and your share-notification preference is on. You can turn it off in your profile (Email me when someone shares with me)."
"body": "{{inviter_full}} shared a {{kind}} with you on OxiCloud.\n\nOpen OxiCloud to see your new share:\n{{login_link}}\n\nYou may have additional new shares from {{inviter}} — sign in to see all your shared items.\n\n— OxiCloud\n\nYou're receiving this message because you have an OxiCloud account and your share-notification preference is on. You can turn it off in your profile (Email me when someone shares with me)."
}
}
},