diff --git a/app/discord/activityTracker.ts b/app/discord/activityTracker.ts index 930744c..61be161 100644 --- a/app/discord/activityTracker.ts +++ b/app/discord/activityTracker.ts @@ -73,8 +73,8 @@ async function getMessageStats(msg: Message | PartialMessage) { } const { content } = await msg.fetch(); return { - char_count: content?.length ?? 0, - word_count: content?.split(/\s+/).length ?? 0, + char_count: getChars(content).length, + word_count: getWords(content).length, react_count: msg.reactions.cache.size, sent_at: msg.createdTimestamp, }; @@ -97,3 +97,19 @@ export async function reportByGuild(guildId: string) { .execute(); return result; } + +// this is better than string.split(/\s+/) because it counts emojis as 1 word +// and we can easily filter them, works much better in other languages too +function getWords(content: string) { + return Array.from( + new Intl.Segmenter("en-us", { granularity: "word" }).segment(content), + ).filter((seg) => seg.isWordLike); +} + +// string.split(/\s+/) will count most emojis as 2+ chars +// this will count them as 1 +function getChars(content: string) { + return Array.from( + new Intl.Segmenter("en-us", { granularity: "grapheme" }).segment(content), + ); +} diff --git a/app/routes.ts b/app/routes.ts index 402d627..034ae24 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -3,7 +3,8 @@ import { route, layout } from "@react-router/dev/routes"; export default [ layout("routes/__auth.tsx", [ - route("dashboard", "routes/__auth/dashboard.tsx"), + route(":guildId/sh", "routes/__auth/dashboard.tsx"), + route(":guildId/sh/:userId", "routes/__auth/sh-user.tsx"), route("login", "routes/__auth/login.tsx"), route("test", "routes/__auth/test.tsx"), ]), diff --git a/app/routes/__auth/dashboard.tsx b/app/routes/__auth/dashboard.tsx index eac9239..e709c37 100644 --- a/app/routes/__auth/dashboard.tsx +++ b/app/routes/__auth/dashboard.tsx @@ -1,26 +1,31 @@ import type { LoaderFunction } from "react-router"; -import { data, useLoaderData, useNavigation } from "react-router"; +import { + data, + useLoaderData, + useNavigation, + useSearchParams, + Link, +} from "react-router"; import type { LabelHTMLAttributes, PropsWithChildren } from "react"; import { getTopParticipants } from "#~/models/activity.server"; export const loader = async ({ request, // context, - // params, + params, }: Parameters[0]) => { // const user = await getUser(request); const url = new URL(request.url); const start = url.searchParams.get("start"); const end = url.searchParams.get("end"); + const guildId = params.guildId; - if (!start || !end) { + if (!(guildId && start && end)) { return data(null, { status: 400 }); } - const REACTIFLUX_GUILD_ID = "102860784329052160"; - const output = await getTopParticipants( - REACTIFLUX_GUILD_ID, + guildId, start, end, [], @@ -41,16 +46,16 @@ const percent = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0, }).format; -function RangeForm() { +function RangeForm({ values }: { values: { start?: string; end?: string } }) { return (
@@ -68,16 +73,20 @@ const DataHeading = ({ children }: PropsWithChildren) => { export default function DashboardPage() { const nav = useNavigation(); const data = useLoaderData(); + const [qs] = useSearchParams(); if (nav.state === "loading") { return "loading…"; } + const start = qs.get("start") ?? undefined; + const end = qs.get("end") ?? undefined; + if (!data) { return (
- +
@@ -87,7 +96,7 @@ export default function DashboardPage() { return (
- +