Skip to content

Commit 81dadef

Browse files
stabldevstabldev
andauthored
feat: repo content navigation (#45)
* feat: add content forward navigation * feat: navigation functions with proper rendering --------- Co-authored-by: stabldev <stabldev@gmail.com>
1 parent 4db4af8 commit 81dadef

File tree

4 files changed

+100
-24
lines changed

4 files changed

+100
-24
lines changed

src/app/(app)/[repo]/_components/content-item.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@ interface Props {
77
username: string | undefined;
88
repo: string;
99
content: Content;
10+
navigateTo: (path: string) => void;
1011
}
1112

12-
export default function ContentItem({ username, repo, content }: Props) {
13+
export default function ContentItem({ username, repo, content, navigateTo }: Props) {
1314
const Icon = content.type === 'dir' ? Folder : File;
1415

16+
function handleClick() {
17+
if (content.type === 'dir') {
18+
navigateTo(content.path);
19+
} else {
20+
// handle file click
21+
}
22+
}
23+
1524
return (
1625
<div key={content.path} className="hover:bg-secondary/50 grid grid-cols-5 gap-2 p-3">
1726
<div className="col-span-2 flex items-center gap-2">
@@ -21,7 +30,9 @@ export default function ContentItem({ username, repo, content }: Props) {
2130
content.type === 'dir' && 'fill-muted-foreground',
2231
)}
2332
/>
24-
<button className="text-sm hover:underline">{content.name}</button>
33+
<button className="text-sm hover:underline" onClick={handleClick}>
34+
{content.name}
35+
</button>
2536
</div>
2637
<a
2738
href={`https://github.com/${username}/${repo}/commit/${content.lastCommit.sha}`}

src/app/(app)/[repo]/_components/repo-contents.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import useRepoContents from '@/hooks/use-repo-contents';
55
import { useStableSession } from '@/hooks/use-stable-session';
66
import { getRepoConfig } from '@/lib/api/github';
77
import { useRepoStore } from '@/stores/repo.store';
8-
import { Plus, Search } from 'lucide-react';
8+
import { Folder, Plus, Search } from 'lucide-react';
99
import { useCallback, useEffect } from 'react';
1010
import { toast } from 'sonner';
1111
import ContentItem from './content-item';
@@ -19,7 +19,7 @@ interface Props {
1919
export default function RepoContents({ repo, setIsConfigDialogOpen }: Props) {
2020
const { session, status } = useStableSession();
2121
const { setConfig, setIsValid } = useRepoStore((state) => state);
22-
const { contents, isLoading } = useRepoContents(repo);
22+
const { contents, isLoading, navigateTo, navigateBack, canGoBack } = useRepoContents(repo);
2323

2424
const loadConfigFilePromise = useCallback(
2525
() =>
@@ -99,6 +99,15 @@ export default function RepoContents({ repo, setIsConfigDialogOpen }: Props) {
9999
<span className="col-span-2">Last commit message</span>
100100
<span className="ml-auto">Last commit date</span>
101101
</div>
102+
{canGoBack && (
103+
<button
104+
className="hover:bg-secondary/50 flex w-full items-center gap-2 p-3"
105+
onClick={navigateBack}
106+
>
107+
<Folder className="text-muted-foreground fill-muted-foreground size-4" />
108+
<span className="text-muted-foreground text-sm">..</span>
109+
</button>
110+
)}
102111
{isLoading || status === 'loading' || !contents ? (
103112
<ContentsSkeleton />
104113
) : (
@@ -108,6 +117,7 @@ export default function RepoContents({ repo, setIsConfigDialogOpen }: Props) {
108117
username={session?.user?.username}
109118
repo={repo}
110119
content={content}
120+
navigateTo={navigateTo}
111121
/>
112122
))
113123
)}

src/hooks/use-repo-contents.ts

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,94 @@
1-
import { getLastCommit } from '@/lib/api/github';
1+
import { getLastCommit, getPathContents } from '@/lib/api/github';
22
import { useRepoStore } from '@/stores/repo.store';
33
import { Content } from '@/types/github';
44
import { useQuery } from '@tanstack/react-query';
5+
import { useCallback, useMemo, useState } from 'react';
56
import { useStableSession } from './use-stable-session';
67

78
export default function useRepoContents(repo: string) {
89
const { session } = useStableSession();
910
const { config } = useRepoStore((state) => state);
1011

11-
async function getRootContent(path: string): Promise<Content> {
12-
const lastCommit = await getLastCommit({
12+
const [history, setHistory] = useState<string[]>(['<root>']);
13+
const currentPath = history[history.length - 1];
14+
const canGoBack = history.length > 1;
15+
16+
const fetchParams = useMemo(
17+
() => ({
1318
accessToken: session?.accessToken,
1419
username: session?.user?.username,
1520
repo,
16-
path,
17-
});
21+
}),
22+
[session, repo],
23+
);
1824

19-
return {
20-
lastCommit,
21-
name: path,
22-
path,
23-
type: 'dir',
24-
};
25-
}
25+
const _getContentDetails = useCallback(
26+
async (path: string, type: Content['type']): Promise<Content> => {
27+
const lastCommit = await getLastCommit({
28+
...fetchParams,
29+
path,
30+
});
2631

27-
const { data: contents, isLoading } = useQuery<Content[]>({
28-
queryKey: ['repoContents', repo],
29-
queryFn: async () => {
30-
if (!config) return [];
32+
return {
33+
lastCommit,
34+
name: path.split('/').pop() ?? path,
35+
path,
36+
type,
37+
};
38+
},
39+
[fetchParams],
40+
);
41+
42+
const _getRootContents = useCallback(async (): Promise<Content[]> => {
43+
if (!config) return [];
44+
45+
const paths = [...Object.values(config.contentTypes).map((item) => item.path), '.gitloom'];
46+
return Promise.all(paths.map((path) => _getContentDetails(path, 'dir')));
47+
}, [config, _getContentDetails]);
48+
49+
const _getPathContents = useCallback(
50+
async (path: string): Promise<Content[]> => {
51+
const contents = await getPathContents({
52+
...fetchParams,
53+
path,
54+
});
3155

32-
const paths = [...Object.values(config.contentTypes).map((item) => item.path), '.gitloom'];
33-
return Promise.all(paths.map((path) => getRootContent(path)));
56+
return Promise.all(contents.map((item) => _getContentDetails(item.path, item.type)));
3457
},
58+
[fetchParams, _getContentDetails],
59+
);
60+
61+
const { data: contents, isLoading } = useQuery<Content[]>({
62+
queryKey: ['repoContents', repo, currentPath],
63+
queryFn: async () =>
64+
currentPath === '<root>' ? await _getRootContents() : await _getPathContents(currentPath),
3565
enabled: !!config,
3666
});
3767

38-
return { contents, isLoading };
68+
const navigateTo = useCallback((path: string) => {
69+
setHistory((prev) => [...prev, path]);
70+
}, []);
71+
72+
const navigateBack = useCallback(() => {
73+
setHistory((prev) => prev.slice(0, prev.length - 1));
74+
}, []);
75+
76+
const navigateBackTo = useCallback((path: string) => {
77+
setHistory((prev) => {
78+
const targetIdx = prev.findIndex((p) => p === path);
79+
if (targetIdx === -1) return prev;
80+
81+
return prev.slice(0, targetIdx + 1);
82+
});
83+
}, []);
84+
85+
return {
86+
contents,
87+
isLoading,
88+
currentPath,
89+
navigateTo,
90+
navigateBack,
91+
navigateBackTo,
92+
canGoBack,
93+
};
3994
}

src/lib/api/github.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export async function createContent({
129129
* Returns the contents inside a specific path.
130130
* Includes both files and directories.
131131
*/
132-
export async function getFolderContents({
132+
export async function getPathContents({
133133
accessToken,
134134
username,
135135
repo,

0 commit comments

Comments
 (0)