Luke Jones

lukeonline - commit 203b42a

feat: fix file path concatenation during traversal203b42a4d0dd0bba801ddae7f69c72f45b46b070LukeJ<luke.jones.2567@gmail.com>2026-09-29T22:31:04+01:00
diff --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>