forked from wrenn/wrenn
Block download for non-regular files in file browser
Disable the download button for symlinks and show a dedicated preview pane explaining the symlink target and suggesting to navigate to the target file instead. Guard handleDownload against non-file types as a safety net.
This commit is contained in:
@ -91,6 +91,10 @@
|
|||||||
|
|
||||||
const canGoUp = $derived(currentPath !== '/' && currentPath.startsWith('/'));
|
const canGoUp = $derived(currentPath !== '/' && currentPath.startsWith('/'));
|
||||||
|
|
||||||
|
// Only regular files can be downloaded — symlinks and other non-regular types
|
||||||
|
// may point to devices, sockets, or directories that can't be read as a file.
|
||||||
|
const isDownloadable = $derived(selectedFile?.type === 'file');
|
||||||
|
|
||||||
async function navigateTo(path: string) {
|
async function navigateTo(path: string) {
|
||||||
// Abort any in-flight file read and invalidate stale generation so the
|
// Abort any in-flight file read and invalidate stale generation so the
|
||||||
// abort error isn't surfaced in the UI.
|
// abort error isn't surfaced in the UI.
|
||||||
@ -215,7 +219,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleDownload() {
|
async function handleDownload() {
|
||||||
if (!selectedFile || downloading) return;
|
if (!selectedFile || downloading || selectedFile.type !== 'file') return;
|
||||||
downloading = true;
|
downloading = true;
|
||||||
try {
|
try {
|
||||||
await downloadFile(capsuleId, selectedFile.path, selectedFile.name);
|
await downloadFile(capsuleId, selectedFile.path, selectedFile.name);
|
||||||
@ -678,7 +682,8 @@
|
|||||||
<span class="font-mono text-badge text-[var(--color-text-muted)]">{formatFileSize(selectedFile.size)}</span>
|
<span class="font-mono text-badge text-[var(--color-text-muted)]">{formatFileSize(selectedFile.size)}</span>
|
||||||
<button
|
<button
|
||||||
onclick={handleDownload}
|
onclick={handleDownload}
|
||||||
disabled={downloading}
|
disabled={downloading || !isDownloadable}
|
||||||
|
title={isDownloadable ? undefined : 'Only regular files can be downloaded'}
|
||||||
class="flex items-center gap-1.5 rounded-[var(--radius-button)] border border-[var(--color-border)] bg-[var(--color-bg-3)] px-2.5 py-1 text-badge font-semibold uppercase tracking-[0.05em] text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-bg-4)] hover:text-[var(--color-text-primary)] disabled:opacity-50 disabled:cursor-not-allowed"
|
class="flex items-center gap-1.5 rounded-[var(--radius-button)] border border-[var(--color-border)] bg-[var(--color-bg-3)] px-2.5 py-1 text-badge font-semibold uppercase tracking-[0.05em] text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-bg-4)] hover:text-[var(--color-text-primary)] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{#if downloading}
|
{#if downloading}
|
||||||
@ -715,6 +720,29 @@
|
|||||||
<span class="text-meta text-[var(--color-red)]">{fileError}</span>
|
<span class="text-meta text-[var(--color-red)]">{fileError}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if !isDownloadable}
|
||||||
|
<!-- Non-regular file (symlink, etc.) — no preview or download -->
|
||||||
|
<div class="flex flex-1 items-center justify-center py-20">
|
||||||
|
<div class="flex flex-col items-center gap-5 text-center" style="animation: fadeUp 0.25s ease both">
|
||||||
|
<div class="flex h-14 w-14 items-center justify-center rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-3)]">
|
||||||
|
<svg class="text-[var(--color-blue)]" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
||||||
|
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-1.5">
|
||||||
|
<span class="text-ui font-medium text-[var(--color-text-primary)]">Symlink</span>
|
||||||
|
{#if selectedFile.symlink_target}
|
||||||
|
<span class="text-meta text-[var(--color-text-tertiary)]">
|
||||||
|
Points to <code class="rounded bg-[var(--color-bg-4)] px-1.5 py-0.5 font-mono text-[var(--color-text-secondary)]">{selectedFile.symlink_target}</code>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
<span class="mt-1 text-meta text-[var(--color-text-muted)]">
|
||||||
|
Symlinks can't be downloaded directly. Navigate to the target file instead.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{:else if isBinaryFile(selectedFile.name) || isFileTooLarge(selectedFile.size) || (selectedFile && fileContent === null && !fileLoading)}
|
{:else if isBinaryFile(selectedFile.name) || isFileTooLarge(selectedFile.size) || (selectedFile && fileContent === null && !fileLoading)}
|
||||||
<!-- Binary / too large / unreadable — download prompt -->
|
<!-- Binary / too large / unreadable — download prompt -->
|
||||||
<div class="flex flex-1 items-center justify-center py-20">
|
<div class="flex flex-1 items-center justify-center py-20">
|
||||||
@ -742,7 +770,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<span class="text-ui font-medium text-[var(--color-text-primary)]">Binary file</span>
|
<span class="text-ui font-medium text-[var(--color-text-primary)]">Binary file</span>
|
||||||
<span class="text-meta text-[var(--color-text-tertiary)]">
|
<span class="text-meta text-[var(--color-text-tertiary)]">
|
||||||
Cannot display as text — download to view
|
Can't display as text — download to view
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user