/ opencodehub / src / components / repo / FileExplorer.astro
swadhinbiswas ✨ feat(cli): harden auth, TLS, retries, and repo parsing · 1/22/2026
3ac03a2
146 lines · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
---
import { FileText, Folder } from "lucide-react";
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";

interface Props {
  owner: string;
  repo: string;
  branch: string;
  path?: string;
  files: {
    name: string;
    type: "file" | "directory" | "submodule";
    lastCommit?: {
      message: string;
      date: string;
      hash: string;
    };
  }[];
}

const { owner, repo, branch, path = "", files } = Astro.props;

const getUrl = (file: any) => {
  const type = file.type === "directory" ? "tree" : "blob";
  const filePath = path ? `${path}/${file.name}` : file.name;
  return `/${owner}/${repo}/${type}/${branch}/${filePath}`;
};
---

<div class="w-full">
  <table class="w-full text-sm">
    <tbody class="divide-y divide-border">
      {
        path && (
          <tr class="hover:bg-muted/50 transition-colors">
            <td class="p-4 py-2 w-10">
              <span class="text-muted-foreground">..</span>
            </td>
            <td class="p-4 py-2 font-medium">
              <a
                href={`/${owner}/${repo}/tree/${branch}/${path.split("/").slice(0, -1).join("/")}`}
                class="hover:underline hover:text-primary text-blue-500 block"
              >
                ..
              </a>
            </td>
            <td class="p-4 py-2" />
            <td class="p-4 py-2" />
          </tr>
        )
      }
      {
        files.length > 0 ? (
          files.map((file) => (
            <tr class="hover:bg-muted/50 transition-colors group">
              <td class="p-4 py-2.5 w-10">
                {file.type === "directory" ? (
                  <svg
                    class="h-4 w-4 text-blue-500 fill-blue-500"
                    viewBox="0 0 16 16"
                    fill="currentColor"
                  >
                    <path d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2C6.07 1.26 5.55 1 5 1H1.75Z" />
                  </svg>
                ) : (
                  (() => {
                    const ext = file.name.split(".").pop()?.toLowerCase();
                    let iconClass = "text-muted-foreground";
                    if (
                      ["js", "jsx", "ts", "tsx", "cjs", "mjs"].includes(
                        ext || "",
                      )
                    )
                      iconClass = "text-yellow-500";
                    else if (["html", "htm"].includes(ext || ""))
                      iconClass = "text-orange-600";
                    else if (
                      ["css", "scss", "sass", "less"].includes(ext || "")
                    )
                      iconClass = "text-blue-600";
                    else if (
                      ["json", "yml", "yaml", "toml"].includes(ext || "")
                    )
                      iconClass = "text-yellow-600";
                    else if (["md", "txt"].includes(ext || ""))
                      iconClass = "text-gray-400";
                    else if (
                      ["png", "jpg", "jpeg", "gif", "svg", "webp"].includes(
                        ext || "",
                      )
                    )
                      iconClass = "text-purple-500";
                    else if (["py"].includes(ext || ""))
                      iconClass = "text-blue-500";
                    else if (["go"].includes(ext || ""))
                      iconClass = "text-cyan-500";
                    else if (["rs"].includes(ext || ""))
                      iconClass = "text-orange-500";
                    else if (["java"].includes(ext || ""))
                      iconClass = "text-red-500";
                    else if (["c", "cpp", "h", "hpp"].includes(ext || ""))
                      iconClass = "text-blue-700";
                    else if (["sh", "bash", "zsh"].includes(ext || ""))
                      iconClass = "text-green-500";

                    return <FileText className={`h-4 w-4 ${iconClass}`} />;
                  })()
                )}
              </td>
              <td class="p-4 py-2.5">
                <a
                  href={getUrl(file)}
                  class="hover:underline hover:text-primary text-foreground font-medium block"
                >
                  {file.name}
                </a>
              </td>
              <td class="p-4 py-2.5 text-muted-foreground max-w-md truncate">
                {file.lastCommit ? (
                  <a
                    href={`/${owner}/${repo}/commit/${file.lastCommit.hash}`}
                    class="hover:text-primary hover:underline"
                  >
                    {file.lastCommit.message}
                  </a>
                ) : (
                  <span class="animate-pulse bg-muted h-4 w-24 rounded inline-block" />
                )}
              </td>
              <td class="p-4 py-2.5 text-right text-muted-foreground whitespace-nowrap text-xs">
                {file.lastCommit?.date || ""}
              </td>
            </tr>
          ))
        ) : (
          <tr>
            <td colspan={4} class="p-8 text-center text-muted-foreground">
              This directory is empty.
            </td>
          </tr>
        )
      }
    </tbody>
  </table>
</div>