feat: ✨ add support github emojis, page for commits
by Sivritkin Dmitriy
1@@ -19,13 +19,15 @@
2 "typecheck": "tsc --noEmit --pretty"
3 },
4 "dependencies": {
5+ "@radix-ui/react-avatar": "^1.0.4",
6 "@radix-ui/react-dialog": "^1.0.5",
7 "@radix-ui/react-dropdown-menu": "^2.0.6",
8 "@radix-ui/react-icons": "^1.3.0",
9 "@radix-ui/react-slot": "^1.0.2",
10 "class-variance-authority": "^0.7.0",
11 "clsx": "^2.1.1",
12 "dayjs": "^1.11.11",
13+ "emoji-toolkit": "^8.0.0",
14 "lucide-react": "^0.395.0",
15 "next": "14.2.4",
16 "next-themes": "^0.3.0",@@ -0,0 +1,79 @@
17+"use client";
18+
19+import {
20+ Dialog,
21+ DialogContent,
22+ DialogDescription,
23+ DialogHeader,
24+ DialogTitle,
25+} from "~/shared/ui/dialog";
26+import { useRouter } from "next/navigation";
27+import { useEffect, useState } from "react";
28+import { $api } from "~/shared/api";
29+import { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";
30+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
31+import { materialDark as style } from "react-syntax-highlighter/dist/esm/styles/prism";
32+import { convertEmoji } from "~/shared/lib/convert-emoji";
33+
34+export default function CommitModal({
35+ params: { sha },
36+}: {
37+ params: { sha: string };
38+}) {
39+ const [commit, setCommit] = useState<
40+ null | RestEndpointMethodTypes["repos"]["getCommit"]["response"]["data"]
41+ >(null);
42+ const [loading, setLoading] = useState<boolean>(true);
43+
44+ const router = useRouter();
45+
46+ useEffect(() => {
47+ setLoading(true);
48+ const fetchCommit = async () => {
49+ const response = await $api.rest.repos.getCommit({
50+ owner: "velenyx",
51+ repo: "github-commit-explorer",
52+ ref: sha,
53+ });
54+ setCommit(response.data);
55+ setLoading(false);
56+ };
57+
58+ fetchCommit();
59+ }, []);
60+
61+ const aggregatedDiff =
62+ commit?.files?.reduce(
63+ (acc, file) => {
64+ return acc + (file.patch || ""); // Ensure to handle cases where patch might be undefined
65+ },
66+ String.raw``,
67+ ) || "";
68+
69+ return (
70+ <div>
71+ <Dialog
72+ open={true}
73+ onOpenChange={() => {
74+ router.back();
75+ }}
76+ >
77+ <DialogContent className="max-h-[70vh]">
78+ {loading ? (
79+ <h1>Loading...</h1>
80+ ) : (
81+ <DialogHeader>
82+ <DialogTitle>{convertEmoji(commit.commit.message)}</DialogTitle>
83+ <DialogDescription>
84+ by {commit?.commit.author?.name}
85+ </DialogDescription>
86+ <SyntaxHighlighter showLineNumbers language="diff" style={style}>
87+ {aggregatedDiff}
88+ </SyntaxHighlighter>
89+ </DialogHeader>
90+ )}
91+ </DialogContent>
92+ </Dialog>
93+ </div>
94+ );
95+}@@ -0,0 +1,3 @@
96+export default function DefaultModal() {
97+ return null;
98+}@@ -1,13 +0,0 @@
99-import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
100-import { materialDark as style } from "react-syntax-highlighter/dist/esm/styles/prism";
101-
102-export const DiffComp = ({ text }: { text: string }) => {
103- return (
104- <div>
105- <h1>Diff Component</h1>
106- <SyntaxHighlighter language="diff" style={style}>
107- {text}
108- </SyntaxHighlighter>
109- </div>
110- );
111-};@@ -5,7 +5,6 @@ import { cn } from "~/shared/lib/utils";
112 import { Header } from "~/components/header";
113
114 import "./globals.css";
115-import "react-diff-view/style/index.css";
116
117 const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" });
118
119@@ -17,8 +16,10 @@ export const metadata: Metadata = {
120
121 export default function RootLayout({
122 children,
123+ modal,
124 }: Readonly<{
125 children: React.ReactNode;
126+ modal: React.ReactNode;
127 }>) {
128 return (
129 <html lang="en">
130@@ -36,7 +37,8 @@ export default function RootLayout({
131 >
132 <div className="relative flex min-h-screen flex-col">
133 <Header />
134- <main className="mb-10 flex-1">{children}</main>
135+ <main className="mb-10 mt-4 flex-1">{children}</main>
136+ {modal}
137 </div>
138 </ThemeProvider>
139 </body>@@ -2,7 +2,9 @@ import { $api } from "~/shared/api";
140 import { Card } from "~/shared/ui/card";
141 import relativeTime from "dayjs/plugin/relativeTime";
142 import dayjs from "dayjs";
143-import { DiffComp } from "~/app/diffcomp";
144+import Link from "next/link";
145+import { Avatar, AvatarImage } from "~/shared/ui/avatar";
146+import { convertEmoji } from "~/shared/lib/convert-emoji";
147
148 dayjs.extend(relativeTime);
149
150@@ -19,34 +21,41 @@ export default async function Home() {
151 });
152
153 const aggregatedDiff = selCommit.data.files?.reduce(
154- (acc, file) => {
155- return acc + (file.patch || ""); // Ensure to handle cases where patch might be undefined
156- },
157+ (acc, file) => acc + (file.patch || ""),
158 String.raw``,
159 );
160
161 return (
162- <div className="container mt-3">
163+ <div className="container">
164 <h1 className="mt-8 text-center text-6xl font-semibold">
165 <span className="text-blue-700">There are</span> {data.data.length}{" "}
166 commits!
167 </h1>
168 <div className="mt-10">
169 {data.data.map((commit) => (
170- <Card
171- key={commit.sha}
172- className="mt-4 cursor-pointer p-5 transition-all hover:border-blue-400"
173- >
174- <h2 className="text-2xl">{commit.commit.message}</h2>
175- <div className="mt-3 flex items-center justify-between">
176- <p className="text-lg">by {commit.commit.author?.name}</p>
177- <p className="text-lg">
178- {dayjs(commit.commit.committer?.date).fromNow()}
179- </p>
180- </div>
181- </Card>
182+ <Link href={`/sha/${commit.sha}`}>
183+ <Card
184+ key={commit.sha}
185+ className="mt-4 cursor-pointer p-5 transition-all hover:border-blue-400 hover:text-blue-400"
186+ >
187+ <h2 className="text-2xl">
188+ {convertEmoji(commit.commit.message)}
189+ </h2>
190+ <div className="mt-3 flex items-center justify-between">
191+ <div className="flex items-center gap-2">
192+ <span className="text-lg">by</span>
193+ <Avatar>
194+ <AvatarImage src={commit.author?.avatar_url} />
195+ </Avatar>
196+ <span className="text-lg">{commit.commit.author?.name}</span>
197+ </div>
198+ <p className="text-lg">
199+ {dayjs(commit.commit.committer?.date).fromNow()}
200+ </p>
201+ </div>
202+ </Card>
203+ </Link>
204 ))}
205- <DiffComp text={aggregatedDiff} />
206 </div>
207 </div>
208 );@@ -0,0 +1,34 @@
209+import { $api } from "~/shared/api";
210+import { convertEmoji } from "~/shared/lib/convert-emoji";
211+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
212+import { materialDark as style } from "react-syntax-highlighter/dist/esm/styles/prism";
213+
214+export default async function CommitModal({
215+ params: { sha },
216+}: {
217+ params: { sha: string };
218+}) {
219+ const { data: commit } = await $api.rest.repos.getCommit({
220+ owner: "velenyx",
221+ repo: "github-commit-explorer",
222+ ref: sha,
223+ });
224+
225+ const aggregatedDiff =
226+ commit?.files?.reduce(
227+ (acc, file) => {
228+ return acc + (file.patch || ""); // Ensure to handle cases where patch might be undefined
229+ },
230+ String.raw``,
231+ ) || "";
232+
233+ return (
234+ <div className="container">
235+ <h1 className="text-3xl">{convertEmoji(commit.commit.message)}</h1>
236+ <p>by {commit?.commit.author?.name}</p>
237+ <SyntaxHighlighter showLineNumbers language="diff" style={style}>
238+ {aggregatedDiff}
239+ </SyntaxHighlighter>
240+ </div>
241+ );
242+}@@ -0,0 +1,7 @@
243+// @ts-ignore
244+import joypixels from "emoji-toolkit";
245+
246+export const convertEmoji = (text: string) => {
247+ joypixels.ascii = true;
248+ return joypixels.shortnameToUnicode(text);
249+};@@ -0,0 +1,50 @@
250+"use client"
251+
252+import * as React from "react"
253+import * as AvatarPrimitive from "@radix-ui/react-avatar"
254+
255+import { cn } from "~/shared/lib/utils"
256+
257+const Avatar = React.forwardRef<
258+ React.ElementRef<typeof AvatarPrimitive.Root>,
259+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
260+>(({ className, ...props }, ref) => (
261+ <AvatarPrimitive.Root
262+ ref={ref}
263+ className={cn(
264+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
265+ className
266+ )}
267+ {...props}
268+ />
269+))
270+Avatar.displayName = AvatarPrimitive.Root.displayName
271+
272+const AvatarImage = React.forwardRef<
273+ React.ElementRef<typeof AvatarPrimitive.Image>,
274+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
275+>(({ className, ...props }, ref) => (
276+ <AvatarPrimitive.Image
277+ ref={ref}
278+ className={cn("aspect-square h-full w-full", className)}
279+ {...props}
280+ />
281+))
282+AvatarImage.displayName = AvatarPrimitive.Image.displayName
283+
284+const AvatarFallback = React.forwardRef<
285+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
286+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
287+>(({ className, ...props }, ref) => (
288+ <AvatarPrimitive.Fallback
289+ ref={ref}
290+ className={cn(
291+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
292+ className
293+ )}
294+ {...props}
295+ />
296+))
297+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
298+
299+export { Avatar, AvatarImage, AvatarFallback }@@ -622,6 +622,29 @@ __metadata:
300 languageName: node
301 linkType: hard
302
303+"@radix-ui/react-avatar@npm:^1.0.4":
304+ version: 1.0.4
305+ resolution: "@radix-ui/react-avatar@npm:1.0.4"
306+ dependencies:
307+ "@babel/runtime": "npm:^7.13.10"
308+ "@radix-ui/react-context": "npm:1.0.1"
309+ "@radix-ui/react-primitive": "npm:1.0.3"
310+ "@radix-ui/react-use-callback-ref": "npm:1.0.1"
311+ "@radix-ui/react-use-layout-effect": "npm:1.0.1"
312+ peerDependencies:
313+ "@types/react": "*"
314+ "@types/react-dom": "*"
315+ react: ^16.8 || ^17.0 || ^18.0
316+ react-dom: ^16.8 || ^17.0 || ^18.0
317+ peerDependenciesMeta:
318+ "@types/react":
319+ optional: true
320+ "@types/react-dom":
321+ optional: true
322+ checksum: 10c0/608494c53968085bfcf9b987d80c3ec6720bdb65f78591d53e8bba3b360e86366d48a7dee11405dd443f5a3565432184b95bb9d4954bca1922cc9385a942caaf
323+ languageName: node
324+ linkType: hard
325+
326 "@radix-ui/react-collection@npm:1.0.3":
327 version: 1.0.3
328 resolution: "@radix-ui/react-collection@npm:1.0.3"
329@@ -2038,6 +2061,13 @@ __metadata:
330 languageName: node
331 linkType: hard
332
333+"emoji-toolkit@npm:^8.0.0":
334+ version: 8.0.0
335+ resolution: "emoji-toolkit@npm:8.0.0"
336+ checksum: 10c0/be58f02c75acf41a237df26b9dec5bf0b9d1a43f4d0e0224e630f1e634ebee487580cefdb3cc533a88f9753034f26078a7b90548581dd1df19697d1ec8409d05
337+ languageName: node
338+ linkType: hard
339+
340 "encoding@npm:^0.1.13":
341 version: 0.1.13
342 resolution: "encoding@npm:0.1.13"
343@@ -2724,6 +2754,7 @@ __metadata:
344 version: 0.0.0-use.local
345 resolution: "github-commit-explorer@workspace:."
346 dependencies:
347+ "@radix-ui/react-avatar": "npm:^1.0.4"
348 "@radix-ui/react-dialog": "npm:^1.0.5"
349 "@radix-ui/react-dropdown-menu": "npm:^2.0.6"
350 "@radix-ui/react-icons": "npm:^1.3.0"
351@@ -2735,6 +2766,7 @@ __metadata:
352 class-variance-authority: "npm:^0.7.0"
353 clsx: "npm:^2.1.1"
354 dayjs: "npm:^1.11.11"
355+ emoji-toolkit: "npm:^8.0.0"
356 eslint: "npm:^8"
357 eslint-config-next: "npm:14.2.4"
358 lucide-react: "npm:^0.395.0"