From adde69358545596cb0d764dc32604cadf6500559 Mon Sep 17 00:00:00 2001 From: Tilman Date: Wed, 1 Oct 2025 15:16:58 +0200 Subject: [PATCH 1/5] Merge pull request #103 from medallyon/bugfix/101-ignore-case-for-sorting Make file and directory sorting case-insensitive --- lib/utils/files.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/utils/files.ts b/lib/utils/files.ts index d5f542d..e52271a 100644 --- a/lib/utils/files.ts +++ b/lib/utils/files.ts @@ -21,11 +21,14 @@ export function humanFileSize(bytes: number) { } export function sortEntriesByName(entryA: Deno.DirEntry, entryB: Deno.DirEntry) { - if (entryA.name > entryB.name) { + const nameA = entryA.name.toLocaleLowerCase(); + const nameB = entryB.name.toLocaleLowerCase(); + + if (nameA > nameB) { return 1; } - if (entryA.name < entryB.name) { + if (nameA < nameB) { return -1; } @@ -33,11 +36,14 @@ export function sortEntriesByName(entryA: Deno.DirEntry, entryB: Deno.DirEntry) } export function sortDirectoriesByName(directoryA: Directory, directoryB: Directory) { - if (directoryA.directory_name > directoryB.directory_name) { + const nameA = directoryA.directory_name.toLocaleLowerCase(); + const nameB = directoryB.directory_name.toLocaleLowerCase(); + + if (nameA > nameB) { return 1; } - if (directoryA.directory_name < directoryB.directory_name) { + if (nameA < nameB) { return -1; } @@ -45,11 +51,14 @@ export function sortDirectoriesByName(directoryA: Directory, directoryB: Directo } export function sortFilesByName(fileA: DirectoryFile, fileB: DirectoryFile) { - if (fileA.file_name > fileB.file_name) { + const nameA = fileA.file_name.toLocaleLowerCase(); + const nameB = fileB.file_name.toLocaleLowerCase(); + + if (nameA > nameB) { return 1; } - if (fileA.file_name < fileB.file_name) { + if (nameA < nameB) { return -1; } From 1dcbf529a3d6d5ecd5749a32e93bc5867c7b1679 Mon Sep 17 00:00:00 2001 From: Bruno Bernardino Date: Wed, 1 Oct 2025 14:17:39 +0100 Subject: [PATCH 2/5] Make initial News loading faster --- docker-compose.yml | 2 +- islands/news/Articles.tsx | 7 +++++++ lib/models/news.ts | 11 +++++++++++ routes/news.tsx | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index cb4a8cf..24e8c1c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: website: - image: ghcr.io/bewcloud/bewcloud:v2.6.0 + image: ghcr.io/bewcloud/bewcloud:v2.6.1 restart: always ports: - 127.0.0.1:8000:8000 diff --git a/islands/news/Articles.tsx b/islands/news/Articles.tsx index f667a2d..6190cc3 100644 --- a/islands/news/Articles.tsx +++ b/islands/news/Articles.tsx @@ -15,6 +15,8 @@ interface Filter { status: 'all' | 'unread'; } +let hasFetchedAllArticlesOnce = false; + export default function Articles({ initialArticles }: ArticlesProps) { const isRefreshing = useSignal(false); const articles = useSignal(initialArticles); @@ -148,6 +150,11 @@ export default function Articles({ initialArticles }: ArticlesProps) { function setNewFilter(newFilter: Partial) { filter.value = { ...filter.value, ...newFilter }; + if (newFilter.status === 'all' && !hasFetchedAllArticlesOnce) { + refreshArticles(); + hasFetchedAllArticlesOnce = true; + } + isFilterDropdownOpen.value = false; } diff --git a/lib/models/news.ts b/lib/models/news.ts index eeaf703..b780d90 100644 --- a/lib/models/news.ts +++ b/lib/models/news.ts @@ -214,6 +214,17 @@ export class ArticleModel { return articles; } + static async listUnread(userId: string) { + const articles = await db.query( + sql`SELECT * FROM "bewcloud_news_feed_articles" WHERE "user_id" = $1 AND "is_read" = FALSE ORDER BY "article_date" DESC`, + [ + userId, + ], + ); + + return articles; + } + static async listByFeedId(feedId: string) { const articles = await db.query( sql`SELECT * FROM "bewcloud_news_feed_articles" WHERE "feed_id" = $1 ORDER BY "article_date" DESC`, diff --git a/routes/news.tsx b/routes/news.tsx index 222b56e..f373396 100644 --- a/routes/news.tsx +++ b/routes/news.tsx @@ -19,7 +19,7 @@ export const handler: Handlers = { return new Response('Redirect', { status: 303, headers: { 'Location': `/dashboard` } }); } - const userArticles = await ArticleModel.list(context.state.user.id); + const userArticles = await ArticleModel.listUnread(context.state.user.id); return await context.render({ userArticles }); }, From c81ef773706d410ebb181039c1c7704387ef3eb0 Mon Sep 17 00:00:00 2001 From: Bruno Bernardino Date: Wed, 1 Oct 2025 14:20:51 +0100 Subject: [PATCH 3/5] Fix linting --- lib/utils/files.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/utils/files.ts b/lib/utils/files.ts index e52271a..f555901 100644 --- a/lib/utils/files.ts +++ b/lib/utils/files.ts @@ -23,7 +23,7 @@ export function humanFileSize(bytes: number) { export function sortEntriesByName(entryA: Deno.DirEntry, entryB: Deno.DirEntry) { const nameA = entryA.name.toLocaleLowerCase(); const nameB = entryB.name.toLocaleLowerCase(); - + if (nameA > nameB) { return 1; } @@ -38,7 +38,7 @@ export function sortEntriesByName(entryA: Deno.DirEntry, entryB: Deno.DirEntry) export function sortDirectoriesByName(directoryA: Directory, directoryB: Directory) { const nameA = directoryA.directory_name.toLocaleLowerCase(); const nameB = directoryB.directory_name.toLocaleLowerCase(); - + if (nameA > nameB) { return 1; } @@ -53,7 +53,7 @@ export function sortDirectoriesByName(directoryA: Directory, directoryB: Directo export function sortFilesByName(fileA: DirectoryFile, fileB: DirectoryFile) { const nameA = fileA.file_name.toLocaleLowerCase(); const nameB = fileB.file_name.toLocaleLowerCase(); - + if (nameA > nameB) { return 1; } From c4a5166e3bd97e9786210a5356738cfc30bb85ae Mon Sep 17 00:00:00 2001 From: Tilman Date: Wed, 8 Oct 2025 15:32:45 +0200 Subject: [PATCH 4/5] 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 --- Dockerfile | 2 +- bewcloud.config.sample.ts | 1 + components/files/ListFiles.tsx | 25 +++++++-- components/files/MainFiles.tsx | 18 ++++++ fresh.gen.ts | 2 + islands/files/FilesWrapper.tsx | 3 + lib/config.ts | 7 +++ lib/models/files.ts | 6 +- lib/types.ts | 2 + routes/api/files/download-directory.tsx | 74 +++++++++++++++++++++++++ routes/file-share/[fileShareId].tsx | 1 + routes/files.tsx | 4 ++ static/images/download.svg | 14 +++++ 13 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 routes/api/files/download-directory.tsx create mode 100644 static/images/download.svg diff --git a/Dockerfile b/Dockerfile index 40f2e8b..2a0f7ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM denoland/deno:ubuntu-2.5.2 EXPOSE 8000 -RUN apt-get update && apt-get install -y make +RUN apt-get update && apt-get install -y make zip WORKDIR /app diff --git a/bewcloud.config.sample.ts b/bewcloud.config.sample.ts index 84ffba8..a22412e 100644 --- a/bewcloud.config.sample.ts +++ b/bewcloud.config.sample.ts @@ -18,6 +18,7 @@ const config: PartialDeep = { // files: { // rootPath: 'data-files', // allowPublicSharing: false, // If true, public file sharing will be allowed (still requires a user to enable sharing for a given file or directory) + // allowDirectoryDownloads: false, // If true, directories can be downloaded as zip files // }, // core: { // enabledApps: ['news', 'notes', 'photos', 'expenses', 'contacts', 'calendar'], // dashboard and files cannot be disabled diff --git a/components/files/ListFiles.tsx b/components/files/ListFiles.tsx index 371e526..b4077a9 100644 --- a/components/files/ListFiles.tsx +++ b/components/files/ListFiles.tsx @@ -18,6 +18,7 @@ interface ListFilesProps { onClickDeleteFile?: (parentPath: string, name: string) => Promise; 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 : ( -
+
+ {typeof onClickDownloadDirectory === 'undefined' ? null : ( + + )}