75 lines
2.8 KiB
TypeScript
75 lines
2.8 KiB
TypeScript
import { parsePublishedDocumentForCommunityRuleDisplay } from "./publishedDocumentToDisplaySections";
|
||
import { formatInviteCountWord } from "./parseInviteEmails";
|
||
import { RULE_SECTION_CATEGORY } from "./ruleSectionsFromMethodSelections";
|
||
import sharedTerms from "../../messages/en/create/sharedTerms.json";
|
||
|
||
const DESCRIPTION_MAX = 140;
|
||
|
||
/** Initials for the invite email disc when the group has no photo. */
|
||
export function communityInviteInitials(name: string): string {
|
||
const words = name
|
||
.trim()
|
||
.split(/\s+/)
|
||
.filter((word) => word.length > 0);
|
||
if (words.length === 0) return "·";
|
||
if (words.length === 1) {
|
||
const word = words[0] ?? "";
|
||
return word.slice(0, 2).toUpperCase();
|
||
}
|
||
const first = words[0]?.charAt(0) ?? "";
|
||
const second = words[1]?.charAt(0) ?? "";
|
||
return `${first}${second}`.toUpperCase();
|
||
}
|
||
|
||
/** Possessive for footer copy (`Share’s` / `Members’`). */
|
||
export function communityPossessive(name: string): string {
|
||
const trimmed = name.trim();
|
||
if (trimmed.length === 0) return "this community’s";
|
||
if (/s$/i.test(trimmed)) return `${trimmed}’`;
|
||
return `${trimmed}’s`;
|
||
}
|
||
|
||
/** Community description on the invite card, cut at 140 characters. */
|
||
export function clipCommunityInviteDescription(
|
||
raw: string | null | undefined,
|
||
): string {
|
||
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
||
if (trimmed.length <= DESCRIPTION_MAX) return trimmed;
|
||
return trimmed.slice(0, DESCRIPTION_MAX).trimEnd();
|
||
}
|
||
|
||
/** Values vs method cards on a published Rule. Shared terms do not count. */
|
||
export function inviteRuleValueAndCardCounts(document: unknown): {
|
||
values: number;
|
||
cards: number;
|
||
} {
|
||
const sections = parsePublishedDocumentForCommunityRuleDisplay(document);
|
||
let values = 0;
|
||
let cards = 0;
|
||
for (const section of sections) {
|
||
const n = section.entries.length;
|
||
if (section.categoryName === RULE_SECTION_CATEGORY.values) {
|
||
values += n;
|
||
} else if (section.categoryName !== sharedTerms.sectionTitle) {
|
||
cards += n;
|
||
}
|
||
}
|
||
return { values, cards };
|
||
}
|
||
|
||
/** Tag on the invite Rule card (`3 VALUES AND 6 CARDS`). Shared terms do not count. */
|
||
export function inviteRuleCountsLabel(document: unknown): string {
|
||
const { values, cards } = inviteRuleValueAndCardCounts(document);
|
||
const valuePart = values === 1 ? "1 VALUE" : `${values} VALUES`;
|
||
const cardPart = cards === 1 ? "1 CARD" : `${cards} CARDS`;
|
||
return `${valuePart} AND ${cardPart}`;
|
||
}
|
||
|
||
/** Spoken counts for Make a copy (`three values and six cards`). */
|
||
export function inviteRuleCountsSpoken(document: unknown): string {
|
||
const { values, cards } = inviteRuleValueAndCardCounts(document);
|
||
const valueNoun = values === 1 ? "value" : "values";
|
||
const cardNoun = cards === 1 ? "card" : "cards";
|
||
return `${formatInviteCountWord(values)} ${valueNoun} and ${formatInviteCountWord(cards)} ${cardNoun}`;
|
||
}
|