Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -659,9 +659,10 @@ for these names, and holds each module allowed to name them to exactly the file

## Profile pictures (Gravatar)

People show as their initials on their own color (`ui/Avatar`, `avatarColorOf`) until the user chooses to see their
Gravatar pictures. Nothing goes to gravatar.com before that choice: it's a hash of each email address shown, and the
user decides whether it leaves the machine.
People show as their initials (`initials`: the first letters of the first two words of the name, or the first two
letters of a one-word name, as the other UVCS clients show them) on their own color (`ui/Avatar`, `avatarColorOf`)
until the user chooses to see their Gravatar pictures. Nothing goes to gravatar.com before that choice: it's a hash of
each email address shown, and the user decides whether it leaves the machine.

- **Off until chosen**: `showGravatar` and `gravatarChoiceAsked` both start false (`shared/domain/gravatarChoice.ts`).
Pictures show only when both are true (`showsGravatar`), checked in one place, the main process's `system.gravatar`
Expand Down
41 changes: 40 additions & 1 deletion src/renderer/src/lib/userName.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,51 @@
import { describe, expect, it } from 'vitest';
import { displayName, userFilterTexts } from './userName';
import { displayName, initials, userFilterTexts } from './userName';

describe('displayName', () => {
it('reads an email as a name', () => {
expect(displayName('jane.doe@unity3d.com')).toBe('Jane Doe');
});
});

describe('initials', () => {
it('takes the first letter of the first two words of a name', () => {
expect(initials('jane.doe@unity3d.com')).toBe('JD');
expect(initials('jane-doe')).toBe('JD');
expect(initials('jane_doe')).toBe('JD');
expect(initials('jane.mary.doe')).toBe('JM');
});

it('takes the first two letters of a one-word name, as people know it from the other clients', () => {
expect(initials('danipen@unity3d.com')).toBe('DA');
expect(initials('jd')).toBe('JD');
});

it('shows a one-letter name whole', () => {
expect(initials('j@unity3d.com')).toBe('J');
});

it('reads letters beyond ASCII', () => {
expect(initials('ñandú')).toBe('ÑA');
expect(initials('\u{1D49C}bc')).toBe('\u{1D49C}B');
expect(initials('\u{1D49C}x.bob')).toBe('\u{1D49C}B');
});

it('keeps an accent written as a separate mark with its letter', () => {
expect(initials('n\u0303andu\u0301')).toBe('ÑA');
expect(initials('n\u0303o.bob')).toBe('ÑB');
});

it('reads a name with extra spaces as its words', () => {
expect(initials('Jane Doe')).toBe('JD');
expect(initials(' jane')).toBe('JA');
});

it('shows a question mark for an empty name', () => {
expect(initials('')).toBe('?');
expect(initials('@unity3d.com')).toBe('?');
});
});

describe('userFilterTexts', () => {
it('matches the name shown before the user as stored', () => {
expect(userFilterTexts('jane.doe@unity3d.com')).toEqual(['Jane Doe', 'jane.doe@unity3d.com']);
Expand Down
9 changes: 7 additions & 2 deletions src/renderer/src/lib/userName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ export function displayName(user: string): string {
.join(' ');
}

/** `jane.doe` → `JD`; a one-word name gives its first two letters (`danipen` → `DA`), as the other UVCS clients show it. */
export function initials(user: string): string {
const words = displayName(user).split(' ');
return ((words[0]?.[0] ?? '') + (words[1]?.[0] ?? '')).toUpperCase() || '?';
// NFC joins a letter written with a separate accent mark (`n` and a combining tilde) into one, so the two letters
// shown are letters.
const words = displayName(user).normalize('NFC').split(/\s+/).filter(Boolean);
const [first = [], second] = words.map((word) => Array.from(word));
const letters = second ? [first[0], second[0]] : first.slice(0, 2);
return letters.join('').toUpperCase() || '?';
}

/** What a filter matches a user by: the name shown (which `UserLabel` highlights), and the user as stored. */
Expand Down
Loading