Support downloading directories as a zip archive (#106)

* Add directory download as zip feature

Implements the ability for users to download directories as zip files if enabled in config. Adds a new API route for directory zipping, updates UI components to show a download button for directories, and introduces related config and type changes. Also includes a new download icon.

* Windows path bugfix

* Include empty directories in zip archive

* Address feedback

- `isDirectoryDownloadsAllowed` -> `areDirectoryDownloadsAllowed`
- send `parentPath` & `name` to API instead of resolving `fullPath` on client
- call `ensureUserPathIsValidAndSecurelyAccessible` before zipping
- set config `allowDirectoryDownloads` default to `false`
- add `zip` to Dockerfile and replace in-house zip algorithm
- replace `download.svg` with heroicon's `arrow-down-tray`
- `replace` with glob -> `replaceAll` with string

* Cleanup apt-get command

* Remove unused zip archive and directory functions
This commit is contained in:
Tilman
2025-10-08 15:32:45 +02:00
committed by GitHub
parent c81ef77370
commit c4a5166e3b
13 changed files with 153 additions and 6 deletions

View File

@@ -18,6 +18,7 @@ interface ListFilesProps {
onClickDeleteFile?: (parentPath: string, name: string) => Promise<void>;
onClickCreateShare?: (filePath: string) => void;
onClickOpenManageShare?: (fileShareId: string) => void;
onClickDownloadDirectory?: (parentPath: string, name: string) => void;
isShowingNotes?: boolean;
isShowingPhotos?: boolean;
fileShareId?: string;
@@ -39,6 +40,7 @@ export default function ListFiles(
onClickDeleteFile,
onClickCreateShare,
onClickOpenManageShare,
onClickDownloadDirectory,
isShowingNotes,
isShowingPhotos,
fileShareId,
@@ -165,10 +167,26 @@ export default function ListFiles(
typeof onClickOpenMoveDirectory === 'undefined')
? null
: (
<section class='flex items-center justify-end w-24'>
<section class='flex items-center justify-end w-32'>
{typeof onClickDownloadDirectory === 'undefined' ? null : (
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickDownloadDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/download.svg'
class='white drop-shadow-md'
width={18}
height={18}
alt='Download directory as zip'
title='Download directory as zip'
/>
</span>
)}
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() => onClickOpenRenameDirectory(directory.parent_path, directory.directory_name)}
onClick={() =>
onClickOpenRenameDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/rename.svg'
@@ -181,8 +199,7 @@ export default function ListFiles(
</span>
<span
class='invisible cursor-pointer group-hover:visible opacity-50 hover:opacity-100 mr-2'
onClick={() =>
onClickOpenMoveDirectory(directory.parent_path, directory.directory_name)}
onClick={() => onClickOpenMoveDirectory(directory.parent_path, directory.directory_name)}
>
<img
src='/images/move.svg'