import { isDid } from "@atcute/lexicons/syntax"; import type { Component } from "svelte"; import type { SvelteHTMLElements } from "svelte/elements"; import AtSign from "$icon/at-sign"; import Ban from "$icon/ban"; import CircleDot from "$icon/circle-dot"; import GitMerge from "$icon/git-merge"; import GitPullRequestClosed from "$icon/git-pull-request-closed"; import GitPullRequestCreate from "$icon/git-pull-request-create"; import MessageSquare from "$icon/message-square"; import Star from "$icon/star"; import UserPlus from "$icon/user-plus"; import UserRoundMinus from "$icon/user-round-minus"; import UserRoundArrowForward from "#lib/components/icons/UserRoundArrowForward.svelte"; import { profileIdentifier } from "#lib/api/identity.ts"; import type { NotificationCategory, NotificationSummary, NotificationType } from "#lib/components/notifications/types.ts"; type IconComponent = Component; export const SOCIAL_TYPES: NotificationType[] = ["repo_starred", "followed"]; const CATEGORIES: Record = { repo_starred: "social", followed: "social", issue_created: "work", issue_commented: "work", issue_closed: "work", issue_reopen: "work", issue_assigned: "work", issue_unassigned: "work", pull_created: "work", pull_commented: "work", pull_merged: "work", pull_closed: "work", pull_reopen: "work", pull_assigned: "work", pull_unassigned: "work", user_mentioned: "work" }; const ICONS: Record = { repo_starred: Star, followed: UserPlus, issue_created: CircleDot, issue_commented: MessageSquare, issue_closed: Ban, issue_reopen: CircleDot, issue_assigned: UserRoundArrowForward, issue_unassigned: UserRoundMinus, pull_created: GitPullRequestCreate, pull_commented: MessageSquare, pull_merged: GitMerge, pull_closed: GitPullRequestClosed, pull_reopen: GitPullRequestCreate, pull_assigned: UserRoundArrowForward, pull_unassigned: UserRoundMinus, user_mentioned: AtSign }; // the design system has no purple, and a merged pull already reads as info on // TicketStatePill, so merges land on the same token here. const TONES: Record = { repo_starred: "text-foreground-warning", followed: "text-foreground-muted", issue_created: "text-foreground-success", issue_commented: "text-foreground-muted", issue_closed: "text-foreground-muted", issue_reopen: "text-foreground-success", issue_assigned: "text-foreground-info", issue_unassigned: "text-foreground-muted", pull_created: "text-foreground-success", pull_commented: "text-foreground-muted", pull_merged: "text-foreground-info", pull_closed: "text-foreground-danger", pull_reopen: "text-foreground-success", pull_assigned: "text-foreground-info", pull_unassigned: "text-foreground-muted", user_mentioned: "text-foreground-info" }; const PHRASES: Record = { repo_starred: "starred", followed: "followed you", issue_created: "opened an issue", issue_commented: "commented on an issue", issue_closed: "closed an issue", issue_reopen: "reopened an issue", issue_assigned: "assigned you to an issue", issue_unassigned: "unassigned you from an issue", pull_created: "created a PR", pull_commented: "commented on a PR", pull_merged: "merged a PR", pull_closed: "closed a PR", pull_reopen: "reopened a PR", pull_assigned: "assigned you to a PR", pull_unassigned: "unassigned you from a PR", user_mentioned: "mentioned you" }; export const notificationCategory = (type: NotificationType): NotificationCategory => CATEGORIES[type]; export const notificationIcon = (type: NotificationType): IconComponent => ICONS[type]; export const notificationTone = (type: NotificationType): string => TONES[type]; const basePhrase = (notification: NotificationSummary): string => { if (notification.type === "user_mentioned" && notification.subject) { return notification.subject.kind === "issue" ? "mentioned you on an issue" : "mentioned you on a pull request"; } return PHRASES[notification.type]; }; const preposition = (type: NotificationType): string => { if (type === "repo_starred") return ""; return type === "user_mentioned" ? " in" : " on"; }; export const notificationPhrase = ( notification: NotificationSummary, leadingRepo = false ): string => { const phrase = basePhrase(notification); return leadingRepo && notification.repo ? phrase + preposition(notification.type) : phrase; }; export const notificationTarget = ( notification: NotificationSummary, includingOwner = true ): string => { const repo = notification.repo; if (!repo) return ""; return includingOwner ? `${repo.ownerHandle}/${repo.name}` : repo.name; }; export const notificationHref = (notification: NotificationSummary): string => { if (notification.type === "followed") return `/${profileIdentifier(notification.actor.handle, notification.actor.did)}`; const repo = notification.repo; if (!repo) return `/${profileIdentifier(notification.actor.handle, notification.actor.did)}`; // did-addressed repos have no name to route on; the did is their address, and // issues/pulls under them can't be addressed either if (isDid(repo.name)) return `/${repo.name}`; const base = `/${repo.ownerHandle}/${repo.name}`; const subject = notification.subject; if (!subject) return base; return `${base}/${subject.kind === "issue" ? "issues" : "pulls"}/${subject.number}`; };