lukeonline - commit 203b42a
feat: fix file path concatenation during traversal203b42a4d0dd0bba801ddae7f69c72f45b46b070LukeJ<luke.jones.2567@gmail.com>2026-09-29T22:31:04+01:00diff --git a/src/lib/repos.ts b/src/lib/repos.ts
index acbdd70..2ab410e 100644
--- a/src/lib/repos.ts
+++ b/src/lib/repos.ts
@@ -60,3 +60,30 @@ export function getFileByPath(name: string, filePath: string) {
return getFile(name, encodedPath)
}
+
+export function getDirectoryChildren(tree: RepoFile[], directory: string) {
+ const prefix = directory ? `${directory}/` : ''
+
+ const directories = new Set<string>()
+ const files = new Set<string>()
+
+ for (const file of tree) {
+ if (!file.path.startsWith(prefix)) {
+ continue
+ }
+
+ const remaining = file.path.slice(prefix.length)
+ const child = remaining.split('/')[0]
+
+ if (remaining.includes('/')) {
+ directories.add(child)
+ } else {
+ files.add(child)
+ }
+ }
+
+ return {
+ directories: [...directories].sort(),
+ files: [...files].sort(),
+ }
+}
diff --git a/src/pages/projects/[name]/file/[...path].astro b/src/pages/projects/[name]/file/[...path].astro
new file mode 100644
index 0000000..6fd18a2
--- /dev/null
+++ b/src/pages/projects/[name]/file/[...path].astro
@@ -0,0 +1,78 @@
+---
+import Base from '../../../../layouts/Base.astro'
+
+import { getCollection } from 'astro:content'
+import { getFileByPath, getTree } from '../../../../lib/repos'
+import CodeContainer from '../../../../components/CodeContainer.astro'
+import { BUTTON_STYLING_SHARED } from '../../../../consts.ts'
+
+export async function getStaticPaths() {
+ const repos = await getCollection('repos')
+
+ return repos.flatMap((repo) => {
+ const tree = getTree(repo.data.name)
+
+ return tree.map((file) => ({
+ params: {
+ name: repo.data.name,
+ path: file.path,
+ },
+ props: {
+ repo,
+ file,
+ },
+ }))
+ })
+}
+
+const { repo, file } = Astro.props
+
+const tree = getTree(repo.data.name)
+const fileData = getFileByPath(repo.data.name, file.path)
+---
+
+<Base title={`${repo.data.name} - ${file.path}`}>
+ <div class="flex flex-row items-baseline gap-2">
+ <code class="text-sm">
+ <a href={`/projects/${repo.data.name}`}>
+ {repo.data.defaultBranch}
+ </a>
+ </code>
+
+ <a
+ class={BUTTON_STYLING_SHARED + ' h-8 p-1 pl-2 items-center gap-3'}
+ href={`/projects/${repo.data.name}/commits`}
+ >
+ {repo.data.commitCount} commits
+ </a>
+
+ <a
+ class={BUTTON_STYLING_SHARED + ' h-8 p-1 pl-2 items-center gap-3'}
+ href={`/projects/${repo.data.name}/files`}
+ >
+ {tree.length} files
+ </a>
+
+ <a
+ class={BUTTON_STYLING_SHARED + ' h-8 p-1 pl-2 items-center gap-3'}
+ href={`/projects/${repo.data.name}/file/README.md`}
+ >
+ README
+ </a>
+
+ <a
+ class={BUTTON_STYLING_SHARED + ' h-8 p-1 pl-2 items-center gap-3'}
+ href={`/projects/${repo.data.name}/file/LICENSE`}
+ >
+ LICENSE
+ </a>
+ </div>
+
+ {
+ fileData.binary ? (
+ <CodeContainer code={'Binary File'} />
+ ) : (
+ <CodeContainer code={fileData.content} />
+ )
+ }
+</Base>
diff --git a/src/pages/projects/[name]/files/[...path].astro b/src/pages/projects/[name]/files/[...path].astro
index c8db89a..669111a 100644
--- a/src/pages/projects/[name]/files/[...path].astro
+++ b/src/pages/projects/[name]/files/[...path].astro
@@ -1,21 +1,19 @@
---
import { getCollection } from 'astro:content'
-import { getTree } from '../../../../lib/repos'
+import { getTree, getDirectoryChildren } from '../../../../lib/repos'
import Base from '../../../../layouts/Base.astro'
-import { BUTTON_STYLING_SHARED, DISABLED_BUTTON_STYLING_SHARED } from '../../../../consts.ts'
+import { BUTTON_STYLING_SHARED } from '../../../../consts.ts'
export async function getStaticPaths() {
const repos = await getCollection('repos')
return repos.flatMap((repo) => {
const tree = getTree(repo.data.name)
-
const directories = new Set<string>()
for (const file of tree) {
const parts = file.path.split('/')
- // Add every directory that exists in the tree.
for (let i = 1; i < parts.length; i++) {
directories.add(parts.slice(0, i).join('/'))
}
@@ -35,90 +33,92 @@ export async function getStaticPaths() {
}
const { repo, tree } = Astro.props
-const { path } = Astro.params
-
-const directory = path ?? ''
-const prefix = `${directory}/`
+const directory = Astro.params.path ?? ''
-const files = tree
- .filter((file) => {
- if (!file.path.startsWith(prefix)) {
- return false
- }
+const { directories, files } = getDirectoryChildren(tree, directory)
- const remaining = file.path.slice(prefix.length)
-
- return remaining.length > 0 && !remaining.includes('/')
- })
- .sort((a, b) => a.path.localeCompare(b.path))
-
-const directories = [
- ...new Set(
- tree
- .filter((file) => file.path.startsWith(prefix))
- .map((file) => {
- const remaining = file.path.slice(prefix.length)
- return remaining.split('/')[0]
- })
- .filter(Boolean)
- ),
-].sort((a, b) => a.localeCompare(b))
+const parent = directory.includes('/')
+ ? directory.slice(0, directory.lastIndexOf('/'))
+ : ''
---
<Base title={`${repo.data.name} - ${directory}`}>
<div class="flex flex-row items-baseline gap-2">
- <code class="text-sm"><a href={`/projects/${repo.data.name}`}>{repo.data.defaultBranch}</a></code>
+ <code class="text-sm">
+ <a href={`/projects/${repo.data.name}`}>
+ {repo.data.defaultBranch}
+ </a>
+ </code>
+
<a
class={BUTTON_STYLING_SHARED + ' h-8 p-1 pl-2 items-center gap-3'}
href={`/projects/${repo.data.name}/commits`}
- >{repo.data.commitCount} commits</a
>
+ {repo.data.commitCount} commits
+ </a>
+
<a
class={BUTTON_STYLING_SHARED + ' h-8 p-1 pl-2 items-center gap-3'}
href={`/projects/${repo.data.name}/files`}
- >{tree.length} files</a
>
+ {tree.length} files
+ </a>
+
<a
class={BUTTON_STYLING_SHARED + ' h-8 p-1 pl-2 items-center gap-3'}
href={`/projects/${repo.data.name}/file/README.md`}
- >README</a
>
+ README
+ </a>
+
<a
class={BUTTON_STYLING_SHARED + ' h-8 p-1 pl-2 items-center gap-3'}
href={`/projects/${repo.data.name}/file/LICENSE`}
- >LICENSE</a
>
+ LICENSE
+ </a>
</div>
+
<div
class="flex flex-col rounded-sm p-2 font-mono text-sm whitespace-pre-line ring inset-shadow-sm ring-gray-600 inset-shadow-gray-400"
>
<a
class="p-0.5 hover:text-blue-800 focus:underline"
- href={`/projects/${repo.data.name}/files`}
+ href={parent
+ ? `/projects/${repo.data.name}/files/${parent}`
+ : `/projects/${repo.data.name}/files`}
>
../
</a>
{
- directories.map((child) => (
- <a
- class="p-0.5 hover:text-blue-800 focus:underline"
- href={`/projects/${repo.data.name}/files/${directory}/${child}`}
- >
- {child}/
- </a>
- ))
+ directories.map((child) => {
+ const childPath = directory ? `${directory}/${child}` : child
+
+ return (
+ <a
+ class="p-0.5 hover:text-blue-800 focus:underline"
+ href={`/projects/${repo.data.name}/files/${childPath}`}
+ >
+ {child}/
+ </a>
+ )
+ })
}
{
- files.map((file) => (
- <a
- class="p-0.5 hover:text-blue-800 focus:underline"
- href={`/projects/${repo.data.name}/file/${file.path}`}
- >
- {file.path.slice(prefix.length)}
- </a>
- ))
+ files.map((file) => {
+ const filePath = directory ? `${directory}/${file}` : file
+
+ return (
+ <a
+ class="p-0.5 hover:text-blue-800 focus:underline"
+ href={`/projects/${repo.data.name}/file/${filePath}`}
+ >
+ {file}
+ </a>
+ )
+ })
}
</div>
</Base>