mirror of
https://github.com/Xevion/banner.git
synced 2026-01-31 00:23:31 -06:00
feat: implement smart name abbreviation for instructor display
This commit is contained in:
@@ -551,20 +551,31 @@ const table = createSvelteTable({
|
||||
{@const primary = getPrimaryInstructor(
|
||||
course.instructors,
|
||||
)}
|
||||
{@const display = primaryInstructorDisplay(course)}
|
||||
{@const commaIdx = display.indexOf(", ")}
|
||||
<td class="py-2 px-2 whitespace-nowrap">
|
||||
<SimpleTooltip
|
||||
text={primary?.displayName ??
|
||||
"Staff"}
|
||||
delay={200}
|
||||
side="bottom"
|
||||
passthrough
|
||||
>
|
||||
{#if display === "Staff"}
|
||||
<span
|
||||
>{primaryInstructorDisplay(
|
||||
course,
|
||||
)}</span
|
||||
class="text-xs text-muted-foreground/60 uppercase"
|
||||
>Staff</span
|
||||
>
|
||||
</SimpleTooltip>
|
||||
{:else}
|
||||
<SimpleTooltip
|
||||
text={primary?.displayName ??
|
||||
"Staff"}
|
||||
delay={200}
|
||||
side="bottom"
|
||||
passthrough
|
||||
>
|
||||
{#if commaIdx !== -1}
|
||||
<span>{display.slice(0, commaIdx)},
|
||||
<span class="text-muted-foreground">{display.slice(commaIdx + 1)}</span
|
||||
></span>
|
||||
{:else}
|
||||
<span>{display}</span>
|
||||
{/if}
|
||||
</SimpleTooltip>
|
||||
{/if}
|
||||
{#if primaryRating(course)}
|
||||
{@const r =
|
||||
primaryRating(course)!}
|
||||
|
||||
@@ -101,11 +101,29 @@ describe("formatMeetingTime", () => {
|
||||
});
|
||||
|
||||
describe("abbreviateInstructor", () => {
|
||||
it("abbreviates standard name", () =>
|
||||
expect(abbreviateInstructor("Heaps, John")).toBe("Heaps, J."));
|
||||
it("returns short names unabbreviated", () =>
|
||||
expect(abbreviateInstructor("Li, Bo")).toBe("Li, Bo"));
|
||||
it("returns names within budget unabbreviated", () =>
|
||||
expect(abbreviateInstructor("Heaps, John")).toBe("Heaps, John"));
|
||||
it("handles no comma", () => expect(abbreviateInstructor("Staff")).toBe("Staff"));
|
||||
it("handles multiple first names", () =>
|
||||
expect(abbreviateInstructor("Smith, Mary Jane")).toBe("Smith, M."));
|
||||
|
||||
// Progressive abbreviation with multiple given names
|
||||
it("abbreviates trailing given names first", () =>
|
||||
expect(abbreviateInstructor("Ramirez, Maria Elena")).toBe("Ramirez, Maria E."));
|
||||
it("abbreviates all given names when needed", () =>
|
||||
expect(abbreviateInstructor("Ramirez, Maria Elena", 16)).toBe("Ramirez, M. E."));
|
||||
it("falls back to first initial only", () =>
|
||||
expect(abbreviateInstructor("Ramirez, Maria Elena", 12)).toBe("Ramirez, M."));
|
||||
|
||||
// Single given name that exceeds budget
|
||||
it("abbreviates single given name when over budget", () =>
|
||||
expect(abbreviateInstructor("Bartholomew, Christopher", 18)).toBe("Bartholomew, C."));
|
||||
|
||||
// Respects custom maxLen
|
||||
it("keeps full name when within custom budget", () =>
|
||||
expect(abbreviateInstructor("Ramirez, Maria Elena", 30)).toBe("Ramirez, Maria Elena"));
|
||||
it("always abbreviates when budget is tiny", () =>
|
||||
expect(abbreviateInstructor("Heaps, John", 5)).toBe("Heaps, J."));
|
||||
});
|
||||
|
||||
describe("getPrimaryInstructor", () => {
|
||||
|
||||
+36
-4
@@ -54,13 +54,45 @@ export function formatMeetingTime(mt: DbMeetingTime): string {
|
||||
return `${days} ${begin}–${end}`;
|
||||
}
|
||||
|
||||
/** Abbreviate instructor name: "Heaps, John" → "Heaps, J." */
|
||||
export function abbreviateInstructor(name: string): string {
|
||||
/**
|
||||
* Progressively abbreviate an instructor name to fit within a character budget.
|
||||
*
|
||||
* Tries each level until the result fits `maxLen`:
|
||||
* 1. Full name: "Ramirez, Maria Elena"
|
||||
* 2. Abbreviate trailing given names: "Ramirez, Maria E."
|
||||
* 3. Abbreviate all given names: "Ramirez, M. E."
|
||||
* 4. First initial only: "Ramirez, M."
|
||||
*
|
||||
* Names without a comma (e.g. "Staff") are returned as-is.
|
||||
*/
|
||||
export function abbreviateInstructor(name: string, maxLen: number = 18): string {
|
||||
if (name.length <= maxLen) return name;
|
||||
|
||||
const commaIdx = name.indexOf(", ");
|
||||
if (commaIdx === -1) return name;
|
||||
|
||||
const last = name.slice(0, commaIdx);
|
||||
const first = name.slice(commaIdx + 2);
|
||||
return `${last}, ${first.charAt(0)}.`;
|
||||
const parts = name.slice(commaIdx + 2).split(" ");
|
||||
|
||||
// Level 2: abbreviate trailing given names, keep first given name intact
|
||||
// "Maria Elena" → "Maria E."
|
||||
if (parts.length > 1) {
|
||||
const abbreviated = [parts[0], ...parts.slice(1).map((p) => `${p[0]}.`)].join(" ");
|
||||
const result = `${last}, ${abbreviated}`;
|
||||
if (result.length <= maxLen) return result;
|
||||
}
|
||||
|
||||
// Level 3: abbreviate all given names
|
||||
// "Maria Elena" → "M. E."
|
||||
if (parts.length > 1) {
|
||||
const allInitials = parts.map((p) => `${p[0]}.`).join(" ");
|
||||
const result = `${last}, ${allInitials}`;
|
||||
if (result.length <= maxLen) return result;
|
||||
}
|
||||
|
||||
// Level 4: first initial only
|
||||
// "Maria Elena" → "M." or "John" → "J."
|
||||
return `${last}, ${parts[0][0]}.`;
|
||||
}
|
||||
|
||||
/** Get primary instructor from a course, or first instructor */
|
||||
|
||||
Reference in New Issue
Block a user