.time ago
This commit is contained in:
@@ -28,7 +28,7 @@ export function AboutPage() {
|
|||||||
const [copiedUsdt, setCopiedUsdt] = useState(false);
|
const [copiedUsdt, setCopiedUsdt] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRepoStats = async () => {
|
const fetchRepoStats = async () => {
|
||||||
const CACHE_KEY = "github_repo_stats_v3";
|
const CACHE_KEY = "github_repo_stats_v4";
|
||||||
const CACHE_DURATION = 1000 * 60 * 60;
|
const CACHE_DURATION = 1000 * 60 * 60;
|
||||||
const cached = localStorage.getItem(CACHE_KEY);
|
const cached = localStorage.getItem(CACHE_KEY);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@@ -70,8 +70,10 @@ export function AboutPage() {
|
|||||||
let totalDownloads = 0;
|
let totalDownloads = 0;
|
||||||
let latestDownloads = 0;
|
let latestDownloads = 0;
|
||||||
let latestVersion = "";
|
let latestVersion = "";
|
||||||
|
let latestReleaseAt = "";
|
||||||
if (releases.length > 0) {
|
if (releases.length > 0) {
|
||||||
latestVersion = releases[0].tag_name || "";
|
latestVersion = releases[0].tag_name || "";
|
||||||
|
latestReleaseAt = releases[0].published_at || releases[0].created_at || "";
|
||||||
latestDownloads =
|
latestDownloads =
|
||||||
releases[0].assets?.reduce((sum: number, asset: any) => sum + (asset.download_count || 0), 0) || 0;
|
releases[0].assets?.reduce((sum: number, asset: any) => sum + (asset.download_count || 0), 0) || 0;
|
||||||
totalDownloads = releases.reduce((sum: number, release: any) => {
|
totalDownloads = releases.reduce((sum: number, release: any) => {
|
||||||
@@ -91,6 +93,7 @@ export function AboutPage() {
|
|||||||
totalDownloads,
|
totalDownloads,
|
||||||
latestDownloads,
|
latestDownloads,
|
||||||
latestVersion,
|
latestVersion,
|
||||||
|
latestReleaseAt,
|
||||||
languages: topLangs,
|
languages: topLangs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -128,6 +131,39 @@ export function AboutPage() {
|
|||||||
const diffYears = Math.floor(diffMonths / 12);
|
const diffYears = Math.floor(diffMonths / 12);
|
||||||
return `${diffYears}y`;
|
return `${diffYears}y`;
|
||||||
};
|
};
|
||||||
|
const formatReleaseTimeAgo = (dateString: string): string => {
|
||||||
|
if (!dateString) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
const releasedAt = new Date(dateString).getTime();
|
||||||
|
if (Number.isNaN(releasedAt)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const diffMs = Math.max(0, now - releasedAt);
|
||||||
|
const totalMinutes = Math.floor(diffMs / (1000 * 60));
|
||||||
|
const totalHours = Math.floor(totalMinutes / 60);
|
||||||
|
const totalDays = Math.floor(totalHours / 24);
|
||||||
|
const totalMonths = Math.floor(totalDays / 30);
|
||||||
|
const totalYears = Math.floor(totalMonths / 12);
|
||||||
|
if (totalYears > 0) {
|
||||||
|
const remainingMonths = totalMonths % 12;
|
||||||
|
return remainingMonths > 0 ? `${totalYears}y ${remainingMonths}m ago` : `${totalYears}y ago`;
|
||||||
|
}
|
||||||
|
if (totalMonths > 0) {
|
||||||
|
const remainingDays = totalDays % 30;
|
||||||
|
return remainingDays > 0 ? `${totalMonths}m ${remainingDays}d ago` : `${totalMonths}m ago`;
|
||||||
|
}
|
||||||
|
if (totalDays > 0) {
|
||||||
|
const remainingHours = totalHours % 24;
|
||||||
|
return remainingHours > 0 ? `${totalDays}d ${remainingHours}h ago` : `${totalDays}d ago`;
|
||||||
|
}
|
||||||
|
if (totalHours > 0) {
|
||||||
|
const remainingMinutes = totalMinutes % 60;
|
||||||
|
return `${totalHours}h ${remainingMinutes}m ago`;
|
||||||
|
}
|
||||||
|
return `${totalMinutes}m ago`;
|
||||||
|
};
|
||||||
const formatNumber = (num: number): string => {
|
const formatNumber = (num: number): string => {
|
||||||
if (num >= 1000) {
|
if (num >= 1000) {
|
||||||
return num.toLocaleString();
|
return num.toLocaleString();
|
||||||
@@ -165,10 +201,15 @@ export function AboutPage() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex justify-between items-start mb-2">
|
<div className="flex justify-between items-start mb-2">
|
||||||
<img src={SpotiFLACNextIcon} className="h-6 w-6 shrink-0" alt="SpotiFLAC Next"/>
|
<img src={SpotiFLACNextIcon} className="h-6 w-6 shrink-0" alt="SpotiFLAC Next"/>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{repoStats["SpotiFLAC-Next"]?.latestReleaseAt && (<span className="text-[10px] text-muted-foreground whitespace-nowrap">
|
||||||
|
{formatReleaseTimeAgo(repoStats["SpotiFLAC-Next"].latestReleaseAt)}
|
||||||
|
</span>)}
|
||||||
{repoStats["SpotiFLAC-Next"]?.latestVersion && (<span className="text-[10px] bg-primary text-primary-foreground px-1.5 py-0.5 rounded-sm font-mono font-semibold max-w-[80px] truncate">
|
{repoStats["SpotiFLAC-Next"]?.latestVersion && (<span className="text-[10px] bg-primary text-primary-foreground px-1.5 py-0.5 rounded-sm font-mono font-semibold max-w-[80px] truncate">
|
||||||
{repoStats["SpotiFLAC-Next"].latestVersion}
|
{repoStats["SpotiFLAC-Next"].latestVersion}
|
||||||
</span>)}
|
</span>)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<CardTitle className="leading-tight">
|
<CardTitle className="leading-tight">
|
||||||
SpotiFLAC Next
|
SpotiFLAC Next
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -214,10 +255,15 @@ export function AboutPage() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex justify-between items-start mb-2">
|
<div className="flex justify-between items-start mb-2">
|
||||||
<img src={SpotiDownloaderIcon} className="h-6 w-6 shrink-0" alt="SpotiDownloader"/>
|
<img src={SpotiDownloaderIcon} className="h-6 w-6 shrink-0" alt="SpotiDownloader"/>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{repoStats["SpotiDownloader"]?.latestReleaseAt && (<span className="text-[10px] text-muted-foreground whitespace-nowrap">
|
||||||
|
{formatReleaseTimeAgo(repoStats["SpotiDownloader"].latestReleaseAt)}
|
||||||
|
</span>)}
|
||||||
{repoStats["SpotiDownloader"]?.latestVersion && (<span className="text-[10px] bg-primary text-primary-foreground px-1.5 py-0.5 rounded-sm font-mono font-semibold max-w-[80px] truncate">
|
{repoStats["SpotiDownloader"]?.latestVersion && (<span className="text-[10px] bg-primary text-primary-foreground px-1.5 py-0.5 rounded-sm font-mono font-semibold max-w-[80px] truncate">
|
||||||
{repoStats["SpotiDownloader"].latestVersion}
|
{repoStats["SpotiDownloader"].latestVersion}
|
||||||
</span>)}
|
</span>)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<CardTitle className="leading-tight">
|
<CardTitle className="leading-tight">
|
||||||
SpotiDownloader
|
SpotiDownloader
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -264,10 +310,15 @@ export function AboutPage() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex justify-between items-start mb-2">
|
<div className="flex justify-between items-start mb-2">
|
||||||
<img src={XBatchDLIcon} className="h-6 w-6 shrink-0" alt="Twitter/X Media Batch Downloader"/>
|
<img src={XBatchDLIcon} className="h-6 w-6 shrink-0" alt="Twitter/X Media Batch Downloader"/>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{repoStats["Twitter-X-Media-Batch-Downloader"]?.latestReleaseAt && (<span className="text-[10px] text-muted-foreground whitespace-nowrap">
|
||||||
|
{formatReleaseTimeAgo(repoStats["Twitter-X-Media-Batch-Downloader"].latestReleaseAt)}
|
||||||
|
</span>)}
|
||||||
{repoStats["Twitter-X-Media-Batch-Downloader"]?.latestVersion && (<span className="text-[10px] bg-primary text-primary-foreground px-1.5 py-0.5 rounded-sm font-mono font-semibold max-w-[80px] truncate">
|
{repoStats["Twitter-X-Media-Batch-Downloader"]?.latestVersion && (<span className="text-[10px] bg-primary text-primary-foreground px-1.5 py-0.5 rounded-sm font-mono font-semibold max-w-[80px] truncate">
|
||||||
{repoStats["Twitter-X-Media-Batch-Downloader"].latestVersion}
|
{repoStats["Twitter-X-Media-Batch-Downloader"].latestVersion}
|
||||||
</span>)}
|
</span>)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<CardTitle className="leading-tight">
|
<CardTitle className="leading-tight">
|
||||||
Twitter/X Media Batch Downloader
|
Twitter/X Media Batch Downloader
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
|||||||
Reference in New Issue
Block a user