consolidation
This commit is contained in:
@@ -2,7 +2,7 @@ import { Typewriter } from "./Typewriter";
|
||||
import { useBars } from "~/context/bars";
|
||||
import { onMount, createSignal, Show, For, onCleanup } from "solid-js";
|
||||
import { api } from "~/lib/api";
|
||||
import { insertSoftHyphens } from "~/lib/client-utils";
|
||||
import { insertSoftHyphens, glitchText } from "~/lib/client-utils";
|
||||
import GitHub from "./icons/GitHub";
|
||||
import LinkedIn from "./icons/LinkedIn";
|
||||
import { RecentCommits } from "./RecentCommits";
|
||||
@@ -314,26 +314,7 @@ export function LeftBar() {
|
||||
setGetLostText(originalText);
|
||||
|
||||
// Occasional glitch effect after reveal
|
||||
glitchInterval = setInterval(() => {
|
||||
if (Math.random() > 0.92) {
|
||||
let glitched = "";
|
||||
for (let i = 0; i < originalText.length; i++) {
|
||||
if (Math.random() > 0.75) {
|
||||
glitched +=
|
||||
glitchChars[
|
||||
Math.floor(Math.random() * glitchChars.length)
|
||||
];
|
||||
} else {
|
||||
glitched += originalText[i];
|
||||
}
|
||||
}
|
||||
setGetLostText(glitched);
|
||||
|
||||
setTimeout(() => {
|
||||
setGetLostText(originalText);
|
||||
}, 80);
|
||||
}
|
||||
}, 200);
|
||||
glitchInterval = glitchText(originalText, setGetLostText, 200, 80);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createSignal, createEffect, onCleanup, Show } from "solid-js";
|
||||
import CountdownCircleTimer from "~/components/CountdownCircleTimer";
|
||||
import LoadingSpinner from "~/components/LoadingSpinner";
|
||||
import { Spinner } from "~/components/Spinner";
|
||||
import { getClientCookie } from "~/lib/cookies.client";
|
||||
|
||||
export default function DeletionForm() {
|
||||
@@ -133,7 +133,7 @@ export default function DeletionForm() {
|
||||
} shadow-maroon flex w-36 justify-center rounded py-3 font-light text-white shadow-lg transition-all duration-300 ease-out`}
|
||||
>
|
||||
<Show when={loading()} fallback="Send Deletion Request">
|
||||
<LoadingSpinner height={24} width={24} />
|
||||
<Spinner size={24} />
|
||||
</Show>
|
||||
</button>
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Spinner } from "~/components/Spinner";
|
||||
|
||||
export default function LoadingSpinner(props: {
|
||||
height: number;
|
||||
width: number;
|
||||
}) {
|
||||
return (
|
||||
<div class="flex w-full justify-center">
|
||||
<Spinner size={props.height} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, For, Show } from "solid-js";
|
||||
import { Typewriter } from "./Typewriter";
|
||||
import { SkeletonText, SkeletonBox } from "./SkeletonLoader";
|
||||
import { formatRelativeTime } from "~/lib/date-utils";
|
||||
|
||||
interface Commit {
|
||||
sha: string;
|
||||
@@ -16,28 +17,6 @@ export const RecentCommits: Component<{
|
||||
title: string;
|
||||
loading?: boolean;
|
||||
}> = (props) => {
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMs / 3600000);
|
||||
const diffDays = Math.floor(diffMs / 86400000);
|
||||
|
||||
if (diffMins < 60) {
|
||||
return `${diffMins}m ago`;
|
||||
} else if (diffHours < 24) {
|
||||
return `${diffHours}h ago`;
|
||||
} else if (diffDays < 7) {
|
||||
return `${diffDays}d ago`;
|
||||
} else {
|
||||
return date.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-3">
|
||||
<h3 class="text-subtext0 text-sm font-semibold">{props.title}</h3>
|
||||
@@ -90,7 +69,10 @@ export const RecentCommits: Component<{
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-subtext1 shrink-0 text-[10px]">
|
||||
{formatDate(commit.date)}
|
||||
{formatRelativeTime(commit.date, {
|
||||
style: "short",
|
||||
maxDays: 7
|
||||
})}
|
||||
</span>
|
||||
<div class="flex min-w-0 items-center gap-2 overflow-hidden">
|
||||
<span class="bg-surface1 shrink-0 rounded px-1.5 py-0.5 font-mono text-[10px]">
|
||||
|
||||
@@ -27,15 +27,3 @@ export function SkeletonText(props: SkeletonProps) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkeletonCircle(props: SkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
class={`bg-surface0 flex items-center justify-center rounded-full ${props.class || ""}`}
|
||||
aria-label="Loading..."
|
||||
role="status"
|
||||
>
|
||||
<Spinner size="md" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createSignal, Show } from "solid-js";
|
||||
import { A } from "@solidjs/router";
|
||||
import LoadingSpinner from "~/components/LoadingSpinner";
|
||||
import { Spinner } from "~/components/Spinner";
|
||||
|
||||
export interface CardLinksProps {
|
||||
postTitle: string;
|
||||
@@ -22,7 +22,7 @@ export default function CardLinks(props: CardLinksProps) {
|
||||
} mx-auto mb-1 flex rounded px-4 py-2 text-base font-light shadow transition-all duration-300 ease-out active:scale-90`}
|
||||
>
|
||||
<Show when={readLoading()} fallback="Read">
|
||||
<LoadingSpinner height={24} width={24} />
|
||||
<Spinner size={24} />
|
||||
</Show>
|
||||
</A>
|
||||
<Show when={props.privilegeLevel === "admin"}>
|
||||
@@ -34,7 +34,7 @@ export default function CardLinks(props: CardLinksProps) {
|
||||
} mx-auto flex rounded px-4 py-2 text-base font-light shadow transition-all duration-300 ease-out active:scale-90`}
|
||||
>
|
||||
<Show when={editLoading()} fallback="Edit">
|
||||
<LoadingSpinner height={24} width={24} />
|
||||
<Spinner size={24} />
|
||||
</Show>
|
||||
</A>
|
||||
</Show>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "solid-js";
|
||||
import { useSearchParams, useNavigate } from "@solidjs/router";
|
||||
import { api } from "~/lib/api";
|
||||
import { formatRelativeTime } from "~/lib/date-utils";
|
||||
import { createTiptapEditor } from "solid-tiptap";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Link from "@tiptap/extension-link";
|
||||
@@ -1153,21 +1154,6 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
return new Date(isoString);
|
||||
};
|
||||
|
||||
const formatRelativeTime = (date: Date): string => {
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffSec = Math.floor(diffMs / 1000);
|
||||
const diffMin = Math.floor(diffSec / 60);
|
||||
const diffHour = Math.floor(diffMin / 60);
|
||||
const diffDay = Math.floor(diffHour / 24);
|
||||
|
||||
if (diffSec < 60) return `${diffSec} seconds ago`;
|
||||
if (diffMin < 60) return `${diffMin} minute${diffMin === 1 ? "" : "s"} ago`;
|
||||
if (diffHour < 24)
|
||||
return `${diffHour} hour${diffHour === 1 ? "" : "s"} ago`;
|
||||
return `${diffDay} day${diffDay === 1 ? "" : "s"} ago`;
|
||||
};
|
||||
|
||||
const restoreHistory = (index: number) => {
|
||||
const instance = editor();
|
||||
if (!instance) return;
|
||||
@@ -4237,7 +4223,10 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
{isCurrent ? `>${index() + 1}<` : index() + 1}
|
||||
</span>
|
||||
<span class="text-text text-sm">
|
||||
{formatRelativeTime(node.timestamp)}
|
||||
{formatRelativeTime(node.timestamp, {
|
||||
style: "long",
|
||||
includeSeconds: true
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<Show when={isCurrent}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { JSX, splitProps, Show } from "solid-js";
|
||||
import LoadingSpinner from "~/components/LoadingSpinner";
|
||||
import { Spinner } from "~/components/Spinner";
|
||||
|
||||
export interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: "primary" | "secondary" | "danger" | "ghost";
|
||||
@@ -72,7 +72,7 @@ export default function Button(props: ButtonProps) {
|
||||
class={`${baseClasses} ${variantClasses()} ${sizeClasses()} ${widthClass()} ${local.class || ""}`}
|
||||
>
|
||||
<Show when={local.loading} fallback={local.children}>
|
||||
<LoadingSpinner height={24} width={24} />
|
||||
<Spinner size={24} />
|
||||
</Show>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -16,3 +16,81 @@ export function getSQLFormattedDate(): string {
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
export interface FormatRelativeTimeOptions {
|
||||
/**
|
||||
* Style of formatting:
|
||||
* - "short": "5m ago", "2h ago", "3d ago"
|
||||
* - "long": "5 minutes ago", "2 hours ago", "3 days ago"
|
||||
*/
|
||||
style?: "short" | "long";
|
||||
/**
|
||||
* Include seconds in the output (only for style="long")
|
||||
*/
|
||||
includeSeconds?: boolean;
|
||||
/**
|
||||
* For dates older than this many days, return a formatted date instead
|
||||
* If undefined, always returns relative time
|
||||
*/
|
||||
maxDays?: number;
|
||||
/**
|
||||
* Locale options for fallback date formatting when maxDays is exceeded
|
||||
*/
|
||||
dateFormatOptions?: Intl.DateTimeFormatOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a date as relative time (e.g., "5 minutes ago", "2h ago")
|
||||
* @param date - Date to format (can be Date object or ISO string)
|
||||
* @param options - Formatting options
|
||||
* @returns Formatted relative time string
|
||||
*/
|
||||
export function formatRelativeTime(
|
||||
date: Date | string,
|
||||
options: FormatRelativeTimeOptions = {}
|
||||
): string {
|
||||
const {
|
||||
style = "short",
|
||||
includeSeconds = false,
|
||||
maxDays,
|
||||
dateFormatOptions
|
||||
} = options;
|
||||
|
||||
const dateObj = typeof date === "string" ? new Date(date) : date;
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - dateObj.getTime();
|
||||
const diffSec = Math.floor(diffMs / 1000);
|
||||
const diffMin = Math.floor(diffSec / 60);
|
||||
const diffHour = Math.floor(diffMin / 60);
|
||||
const diffDay = Math.floor(diffHour / 24);
|
||||
|
||||
// If maxDays is specified and exceeded, return formatted date
|
||||
if (maxDays !== undefined && diffDay >= maxDays) {
|
||||
return dateObj.toLocaleDateString(
|
||||
"en-US",
|
||||
dateFormatOptions || { month: "short", day: "numeric" }
|
||||
);
|
||||
}
|
||||
|
||||
if (style === "short") {
|
||||
if (diffMin < 60) {
|
||||
return `${diffMin}m ago`;
|
||||
} else if (diffHour < 24) {
|
||||
return `${diffHour}h ago`;
|
||||
} else {
|
||||
return `${diffDay}d ago`;
|
||||
}
|
||||
} else {
|
||||
// style === "long"
|
||||
if (includeSeconds && diffSec < 60) {
|
||||
return `${diffSec} second${diffSec === 1 ? "" : "s"} ago`;
|
||||
}
|
||||
if (diffMin < 60) {
|
||||
return `${diffMin} minute${diffMin === 1 ? "" : "s"} ago`;
|
||||
}
|
||||
if (diffHour < 24) {
|
||||
return `${diffHour} hour${diffHour === 1 ? "" : "s"} ago`;
|
||||
}
|
||||
return `${diffDay} day${diffDay === 1 ? "" : "s"} ago`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,23 @@
|
||||
import { PageHead } from "~/components/PageHead";
|
||||
import { HttpStatusCode } from "@solidjs/start";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { createEffect, createSignal, For } from "solid-js";
|
||||
import { createEffect, createSignal, For, onCleanup } from "solid-js";
|
||||
import { ERROR_PAGE_CONFIG } from "~/config";
|
||||
import { glitchText } from "~/lib/client-utils";
|
||||
|
||||
export default function Page_401() {
|
||||
const navigate = useNavigate();
|
||||
const [glitchText, setGlitchText] = createSignal("401");
|
||||
|
||||
createEffect(() => {
|
||||
const glitchChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?~`";
|
||||
const originalText = "401";
|
||||
|
||||
const glitchInterval = setInterval(() => {
|
||||
if (Math.random() > 0.85) {
|
||||
let glitched = "";
|
||||
for (let i = 0; i < originalText.length; i++) {
|
||||
if (Math.random() > 0.7) {
|
||||
glitched +=
|
||||
glitchChars[Math.floor(Math.random() * glitchChars.length)];
|
||||
} else {
|
||||
glitched += originalText[i];
|
||||
}
|
||||
}
|
||||
setGlitchText(glitched);
|
||||
|
||||
setTimeout(
|
||||
() => setGlitchText(originalText),
|
||||
const interval = glitchText(
|
||||
"401",
|
||||
setGlitchText,
|
||||
ERROR_PAGE_CONFIG.GLITCH_INTERVAL_MS,
|
||||
ERROR_PAGE_CONFIG.GLITCH_DURATION_MS
|
||||
);
|
||||
}
|
||||
}, ERROR_PAGE_CONFIG.GLITCH_INTERVAL_MS);
|
||||
|
||||
return () => clearInterval(glitchInterval);
|
||||
onCleanup(() => clearInterval(interval));
|
||||
});
|
||||
|
||||
const createParticles = () => {
|
||||
|
||||
@@ -12,7 +12,6 @@ import { PageHead } from "~/components/PageHead";
|
||||
import { api } from "~/lib/api";
|
||||
import { getClientCookie, setClientCookie } from "~/lib/cookies.client";
|
||||
import CountdownCircleTimer from "~/components/CountdownCircleTimer";
|
||||
import LoadingSpinner from "~/components/LoadingSpinner";
|
||||
import RevealDropDown from "~/components/RevealDropDown";
|
||||
import Input from "~/components/ui/Input";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
|
||||
Reference in New Issue
Block a user