import { coAuthorsOf } from '@pdsjs/git/authors';
import { Avatar } from '#/components/atoms/avatar.jsx';
import { describeAuthor, initial } from '#/lib/authors.js';
import { identicon } from '#/lib/avatars.js';
import { cn } from '#/lib/utils.js';
/** How many faces a pile shows before it counts the rest instead. */
const FACES = 3;
/**
* The author stands in front and everyone credited beside them tucks in
* behind, so the face a reader looks for first is the one that is whole.
* These are classes rather than a style, because the avatar takes a class
* name and nothing else. The pile isolates them, so a face never rises
* above a menu that opens over it.
*/
const DEPTH = ['z-30', 'z-20', 'z-10'];
/**
* Everyone a commit credits, in the order a reader meets them: the author it
* names first, then anyone its message credits in a `Co-authored-by` trailer.
* @param {{ident: unknown, message: unknown}} commit
*/
function creditedBy(ident, message) {
const idents = [ident, ...coAuthorsOf(message)].filter(Boolean);
const people = [];
const seen = new Set();
for (const one of idents) {
const author = describeAuthor(one);
const key = author.name.toLowerCase();
if (seen.has(key)) continue;
seen.add(key);
people.push(author);
}
return people;
}
/**
* The faces alone, for a row that has no width for the names: a commit on a
* timeline, where the message is what the reader came for and the face says
* whose it is.
*
* An author with no atproto account keeps whatever the account calls them,
* which is how a tool that writes commits gets a face without having a repo
* of its own.
*/
export function AuthorFaces({ ident, message, className }) {
const people = creditedBy(ident, message);
if (people.length === 0) return null;
return (
person.title).join('\n')}
>
{people.slice(0, FACES).map((person, index) => (
))}
);
}
/**
* One face in a pile, drawn over the one behind it.
*
* An author the account has claimed draws the pattern their DID draws
* everywhere else, so one person wears one face. An author claimed by nobody
* has no identity to draw from and keeps their initial.
*/
function Face({ person, index }) {
return (
{initial(person.name)}
}
// The face in front already carries a border, and that is the edge
// between it and the one behind. A ring drawn here would have to guess
// the colour behind the pile, which differs from one band to the next.
className={cn(
'relative size-4 shrink-0',
DEPTH[index],
index > 0 && '-ml-1.5',
)}
/>
);
}
/**
* Everyone a commit credits, faces and names.
*
* The faces overlap, and the names read as a sentence, because a commit two
* people wrote is one piece of work rather than two rows.
*/
export function AuthorFacepile({ ident, message, className }) {
const people = creditedBy(ident, message);
if (people.length === 0) return null;
const shown = people.slice(0, FACES);
const named = people.map((person) => person.name);
// "Ada", "Ada and Bo", "Ada, Bo and Cy": the last two join with "and", the
// rest with commas, which is how the names would be read aloud.
const sentence =
named.length > 1
? `${named.slice(0, -1).join(', ')} and ${named[named.length - 1]}`
: named[0];
return (
person.title).join('\n')}
>
{shown.map((person, index) => (
))}
{sentence}
{people.length > shown.length && (
{' '}
and {people.length - shown.length} more
)}
);
}