diff --git a/frontend/deno.json b/frontend/deno.json
index 1a2fec1cb..bffefd6c9 100644
--- a/frontend/deno.json
+++ b/frontend/deno.json
@@ -26,7 +26,6 @@
"@std/front-matter": "jsr:@std/front-matter@1",
"@std/semver": "jsr:@std/semver@1",
- "twas": "npm:twas@^2.1.3",
"$imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts",
"@deno/gfm": "jsr:@deno/gfm@0.10",
diff --git a/frontend/deno.lock b/frontend/deno.lock
index f587c6963..4afabcef5 100644
--- a/frontend/deno.lock
+++ b/frontend/deno.lock
@@ -67,8 +67,7 @@
"npm:prismjs@^1.29.0": "1.29.0",
"npm:sanitize-html@^2.13.0": "2.13.1",
"npm:tailwindcss@3.4": "3.4.14_postcss@8.4.47",
- "npm:tailwindcss@^3.4.1": "3.4.14_postcss@8.4.47",
- "npm:twas@^2.1.3": "2.1.3"
+ "npm:tailwindcss@^3.4.1": "3.4.14_postcss@8.4.47"
},
"jsr": {
"@deno/gfm@0.10.0": {
@@ -1672,9 +1671,6 @@
"ts-interface-checker@0.1.13": {
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
},
- "twas@2.1.3": {
- "integrity": "sha512-4Spnweu5OEBG9ZZIfabEh0js2x1p+34QsLLz+vHjER/nQX0L/+b7H6gelZbT+Ewi2KVNHcBy5gGhib9DeVAqpA=="
- },
"undici-types@5.26.5": {
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
@@ -1890,8 +1886,7 @@
"npm:preact-render-to-string@6.3.1",
"npm:preact@10",
"npm:prismjs@^1.29.0",
- "npm:tailwindcss@3.4",
- "npm:twas@^2.1.3"
+ "npm:tailwindcss@3.4"
]
}
}
diff --git a/frontend/islands/admin/ScopeEdit.tsx b/frontend/islands/admin/ScopeEdit.tsx
index b7c239658..2cb963eda 100644
--- a/frontend/islands/admin/ScopeEdit.tsx
+++ b/frontend/islands/admin/ScopeEdit.tsx
@@ -1,7 +1,7 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
import type { FullScope } from "../../utils/api_types.ts";
import { useState } from "preact/hooks";
-import twas from "twas";
+import { timeAgo } from "../../utils/timeAgo.ts";
import { api, path } from "../../utils/api.ts";
import { TableData, TableRow } from "../../components/Table.tsx";
@@ -71,7 +71,7 @@ export default function AdminScopeEdit({ scope }: { scope: FullScope }) {
: publishAttemptsPerWeekLimit}
- {twas(new Date(scope.createdAt).getTime())}
+ {timeAgo(new Date(scope.createdAt))}
{edit
diff --git a/frontend/islands/admin/UserEdit.tsx b/frontend/islands/admin/UserEdit.tsx
index 7dc8ec841..cd1447a8c 100644
--- a/frontend/islands/admin/UserEdit.tsx
+++ b/frontend/islands/admin/UserEdit.tsx
@@ -1,6 +1,6 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
import { useState } from "preact/hooks";
-import twas from "twas";
+import { timeAgo } from "../../utils/timeAgo.ts";
import { FullUser } from "../../utils/api_types.ts";
import { api, path } from "../../utils/api.ts";
import { TableData, TableRow } from "../../components/Table.tsx";
@@ -68,7 +68,7 @@ export default function UserEdit({ user }: { user: FullUser }) {
: String(isBlocked)}
- {twas(new Date(user.createdAt).getTime())}
+ {timeAgo(new Date(user.createdAt))}
{edit
diff --git a/frontend/islands/new.tsx b/frontend/islands/new.tsx
index cc80f1f37..7f45fabb2 100644
--- a/frontend/islands/new.tsx
+++ b/frontend/islands/new.tsx
@@ -8,7 +8,7 @@ import {
import { Package, Scope } from "../utils/api_types.ts";
import { api, path } from "../utils/api.ts";
import { ComponentChildren } from "preact";
-import twas from "twas";
+import { timeAgo } from "../utils/timeAgo.ts";
interface IconColorProps {
done: Signal;
@@ -419,7 +419,7 @@ export function CreatePackage({ scope, name, pkg, fromCli }: {
{pkg.value.description || No description}
- Created {twas(new Date(pkg.value.createdAt).getTime())}.
+ Created {timeAgo(pkg.value.createdAt)}.
{fromCli && (
diff --git a/frontend/routes/account/(_components)/AccountLayout.tsx b/frontend/routes/account/(_components)/AccountLayout.tsx
index 3248c2fe8..b7f01c213 100644
--- a/frontend/routes/account/(_components)/AccountLayout.tsx
+++ b/frontend/routes/account/(_components)/AccountLayout.tsx
@@ -1,6 +1,6 @@
// Copyright 2024 the JSR authors. All rights reserved. MIT license.
import { ComponentChildren } from "preact";
-import twas from "twas";
+import { timeAgo } from "../../../utils/timeAgo.ts";
import { AccountNav, AccountNavTab } from "./AccountNav.tsx";
import { FullUser, User } from "../../../utils/api_types.ts";
import { GitHubUserLink } from "../../../islands/GithubUserLink.tsx";
@@ -25,7 +25,7 @@ export function AccountLayout({ user, active, children }: AccountLayoutProps) {
{user.name}
- Created account {twas(new Date(user.createdAt).getTime())}
+ Created account {timeAgo(new Date(user.createdAt))}
diff --git a/frontend/routes/account/tokens/index.tsx b/frontend/routes/account/tokens/index.tsx
index 0da983a7b..28c3dd0e6 100644
--- a/frontend/routes/account/tokens/index.tsx
+++ b/frontend/routes/account/tokens/index.tsx
@@ -5,7 +5,7 @@ import { define } from "../../../util.ts";
import { path } from "../../../utils/api.ts";
import { Token } from "../../../utils/api_types.ts";
import { AccountLayout } from "../(_components)/AccountLayout.tsx";
-import twas from "twas";
+import { timeAgo } from "../../../utils/timeAgo.ts";
import { RevokeToken } from "./(_islands)/RevokeToken.tsx";
import TbPlus from "@preact-icons/tb/TbPlus";
@@ -105,7 +105,7 @@ function PersonalTokenRow({ token }: { token: Token }) {
Active {expiresAt === null
? "forever"
: `– expires ${
- twas(new Date().getTime(), expiresAt.getTime()).replace(
+ timeAgo(expiresAt).replace(
"ago",
"from now",
)
@@ -114,12 +114,12 @@ function PersonalTokenRow({ token }: { token: Token }) {
)
: (
- Inactive - expired {twas(expiresAt.getTime())}
+ Inactive - expired {timeAgo(expiresAt)}
)}
- Created {twas(new Date(token.createdAt).getTime())}
+ Created {timeAgo(new Date(token.createdAt))}
@@ -160,7 +160,7 @@ function SessionRow({ token }: { token: Token }) {
Active {expiresAt === null
? "forever"
: `– expires ${
- twas(new Date().getTime(), expiresAt.getTime()).replace(
+ timeAgo(expiresAt).replace(
"ago",
"from now",
)
@@ -169,7 +169,7 @@ function SessionRow({ token }: { token: Token }) {
)
: (
- Inactive - expired {twas(expiresAt.getTime())}
+ Inactive - expired {timeAgo(expiresAt)}
)}
@@ -178,7 +178,7 @@ function SessionRow({ token }: { token: Token }) {
- Created {twas(new Date(token.createdAt).getTime())}
+ Created {timeAgo(new Date(token.createdAt))}
diff --git a/frontend/routes/admin/publishingTasks.tsx b/frontend/routes/admin/publishingTasks.tsx
index 1356cf8c7..66d3f3528 100644
--- a/frontend/routes/admin/publishingTasks.tsx
+++ b/frontend/routes/admin/publishingTasks.tsx
@@ -5,7 +5,7 @@ import { AdminNav } from "./(_components)/AdminNav.tsx";
import { path } from "../../utils/api.ts";
import { List, PublishingTask } from "../../utils/api_types.ts";
import { URLQuerySearch } from "../../components/URLQuerySearch.tsx";
-import twas from "twas";
+import { timeAgo } from "../../utils/timeAgo.ts";
import PublishingTaskRequeue from "../../islands/PublishingTaskRequeue.tsx";
export default define.page(function PublishingTasks({
@@ -77,7 +77,7 @@ export default define.page(function PublishingTasks({
10,
)}
>
- {twas(new Date(publishingTask.createdAt).getTime())}
+ {timeAgo(new Date(publishingTask.createdAt))}
(function PublishingTasks({
10,
)}
>
- {twas(new Date(publishingTask.updatedAt).getTime())}
+ {timeAgo(new Date(publishingTask.updatedAt))}
diff --git a/frontend/routes/package/(_components)/PackageHeader.tsx b/frontend/routes/package/(_components)/PackageHeader.tsx
index cd179228f..9909521f3 100644
--- a/frontend/routes/package/(_components)/PackageHeader.tsx
+++ b/frontend/routes/package/(_components)/PackageHeader.tsx
@@ -9,7 +9,7 @@ import {
TbRosetteDiscountCheck,
} from "@preact-icons/tb";
import { Tooltip } from "../../../components/Tooltip.tsx";
-import twas from "twas";
+import { timeAgo } from "../../../utils/timeAgo.ts";
import { greaterThan, parse } from "@std/semver";
interface PackageHeaderProps {
@@ -207,7 +207,7 @@ export function PackageHeader({
)}
>
{`${
- twas(new Date(selectedVersion.createdAt).getTime())
+ timeAgo(new Date(selectedVersion.createdAt))
} (${selectedVersion.version})`}
diff --git a/frontend/routes/package/og.ts b/frontend/routes/package/og.ts
index ce84177f0..bdc61a548 100644
--- a/frontend/routes/package/og.ts
+++ b/frontend/routes/package/og.ts
@@ -2,7 +2,7 @@
import { HttpError, RouteConfig } from "fresh";
import { Image } from "$imagescript";
-import twas from "twas";
+import { timeAgo } from "../../utils/timeAgo.ts";
import { packageDataWithVersion } from "../../utils/data.ts";
import { define } from "../../util.ts";
@@ -263,7 +263,7 @@ export const handler = define.handlers({
const publishDateText = Image.renderText(
dmmonoFont,
25,
- twas(new Date(selectedVersion.createdAt).getTime()),
+ timeAgo(new Date(selectedVersion.createdAt)),
COLOR_GRAY,
);
const result = new Image(
diff --git a/frontend/routes/package/versions.tsx b/frontend/routes/package/versions.tsx
index d58475c2e..d521df901 100644
--- a/frontend/routes/package/versions.tsx
+++ b/frontend/routes/package/versions.tsx
@@ -7,7 +7,7 @@ import type {
} from "../../utils/api_types.ts";
import { define } from "../../util.ts";
import { compare, equals, format, lessThan, parse, SemVer } from "@std/semver";
-import twas from "twas";
+import { timeAgo } from "../../utils/timeAgo.ts";
import { packageData } from "../../utils/data.ts";
import { PackageHeader } from "./(_components)/PackageHeader.tsx";
import { PackageNav, Params } from "./(_components)/PackageNav.tsx";
@@ -239,7 +239,7 @@ function Version({
{" "}
>
)}
- {twas(new Date(version.createdAt).getTime())}
+ {timeAgo(new Date(version.createdAt))}
)}
@@ -268,8 +268,7 @@ function Version({
: }
{ordinalNumber(tasks.length - i)} publishing attempt{" "}
- {statusVerb[task.status]}{" "}
- {twas(new Date(task.updatedAt).getTime())}
+ {statusVerb[task.status]} {timeAgo(new Date(task.updatedAt))}
Details
diff --git a/frontend/routes/status.tsx b/frontend/routes/status.tsx
index 5be14136f..1c3640734 100644
--- a/frontend/routes/status.tsx
+++ b/frontend/routes/status.tsx
@@ -9,7 +9,7 @@ import { path } from "../utils/api.ts";
import { packageData } from "../utils/data.ts";
import { PackageHeader } from "./package/(_components)/PackageHeader.tsx";
import { PackageNav } from "./package/(_components)/PackageNav.tsx";
-import twas from "twas";
+import { timeAgo } from "../utils/timeAgo.ts";
import PublishingTaskRequeue from "../islands/PublishingTaskRequeue.tsx";
import { TbAlertCircle, TbCheck, TbClockHour3 } from "@preact-icons/tb";
import { scopeIAM } from "../utils/iam.ts";
@@ -48,7 +48,7 @@ export default define.page(function PackageListPage({
Created:{" "}
- {twas(new Date(data.publishingTask.createdAt).getTime())}
+ {timeAgo(new Date(data.publishingTask.createdAt))}
{data.publishingTask.userId && (
diff --git a/frontend/utils/timeAgo.ts b/frontend/utils/timeAgo.ts
new file mode 100644
index 000000000..f61ffe77c
--- /dev/null
+++ b/frontend/utils/timeAgo.ts
@@ -0,0 +1,24 @@
+// Copyright 2024 the JSR authors. All rights reserved. MIT license.
+export function timeAgo(date: Date | string): string {
+ const now = new Date();
+ const past = new Date(date);
+ const diff = Math.abs(now.getTime() - past.getTime());
+
+ const duration = {
+ years: Math.floor(diff / (1000 * 60 * 60 * 24 * 365)),
+ months: Math.floor(
+ (diff % (1000 * 60 * 60 * 24 * 365)) / (1000 * 60 * 60 * 24 * 30),
+ ),
+ days: Math.floor(
+ (diff % (1000 * 60 * 60 * 24 * 30)) / (1000 * 60 * 60 * 24),
+ ),
+ hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
+ minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
+ seconds: Math.floor((diff % (1000 * 60)) / 1000),
+ };
+
+ // Force english because JSR is an English-only project
+ // @ts-ignore - TS doesn't know about this API yet
+ const formatter = new Intl.DurationFormat("en", { style: "long" });
+ return formatter.format(duration) + " ago";
+}