From 8f9d73fe948fcb572492a3034664ef69d322b3e2 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sun, 20 Sep 2026 09:55:57 +0200 Subject: [PATCH] feat: option to hide README file from share listings (upstream #2729) When a folder share has readme display enabled, a new hide_readme prop filters README.md/README.txt out of share listings while direct-path resolution stays open so the readme viewer keeps working. - ShareProps.HideReadMe persisted only alongside ShowReadMe - shareNavigator.Children post-filters readme names (case-insensitive) - detectReadMe falls back to URI probes unconditionally (the readme may now be filtered out of the listing, not just unpaginated) - hide_readme exposed on share responses for the owner only - Share dialog gains a nested "Hide readme file" checkbox under the readme accordion; en-US + zh-CN locales Authored By: TDvorak Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ROADMAP.md | 1 + .../public/locales/en-US/application.json | 4 +- .../public/locales/zh-CN/application.json | 4 +- frontend/src/api/explorer.ts | 2 + .../FileManager/Dialogs/Share/ShareDialog.tsx | 1 + .../Dialogs/Share/ShareSetting.tsx | 11 +++ frontend/src/redux/thunks/share.ts | 13 +-- inventory/types/types.go | 3 + pkg/filemanager/fs/dbfs/share_navigator.go | 23 ++++- pkg/filemanager/fs/dbfs/share_readme_test.go | 94 +++++++++++++++++++ pkg/filemanager/manager/manager.go | 1 + pkg/filemanager/manager/operation.go | 1 + service/explorer/response.go | 2 + service/share/manage.go | 2 + 14 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 pkg/filemanager/fs/dbfs/share_readme_test.go diff --git a/ROADMAP.md b/ROADMAP.md index 859a1e1a..6f6f187a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -208,6 +208,7 @@ Order = user-visible value first; each ships with backend + UI + tests. - [x] `desloppify` pass — 73 review items dispositioned (46 fixed, 27 honestly skipped), strict score 77.1 (was 18.9); scorecard lives in README. `security-reviewer` pass done incrementally per batch (OAuth secrets, SSRF, process exec, path safety) - [x] Tag management page (upstream #2962) — owner-scoped `tag:` metadata stats/rename/recolor/delete in `inventory.FileClient`, `GET/PATCH/DELETE /file/tag` routes, Settings → Tags tab with merge-on-rename semantics - [x] Private space / vault (upstream #3447) — opt-in root folder flagged `sys:vault`; ancestry-based membership (zero flag maintenance; chain-less search results resolved lazily via `file_children`); `vaultNavigator` decorator gating `To`/`Children`/`Walk`/`ExecuteHook`; separate vault password (`salt:sha256`, sensitive) + 30-min cache-backed unlock session, unlock rate-limited 10/h; vault content never shareable and never direct-linkable; search filtered while locked; `vault_enabled`/`vault_unlocked` in user settings; unlock prompt in `ExplorerError`, Private space section in security settings, lock badge on vault folder (#195) +- [x] Share `hide_readme` option (upstream #2729 item 6) — `ShareProps.HideReadMe` (only meaningful with `ShowReadMe`); share navigator filters `README.md`/`README.txt` (case-insensitive) from listings while direct-path resolution stays open for the readme viewer; `detectReadMe` URI fallback now probes unconditionally; owner-only `hide_readme` on share responses; Share dialog nested checkbox ## 6. Phase D — desktop, all platforms diff --git a/frontend/public/locales/en-US/application.json b/frontend/public/locales/en-US/application.json index f7c897e5..e779c6e3 100644 --- a/frontend/public/locales/en-US/application.json +++ b/frontend/public/locales/en-US/application.json @@ -688,7 +688,9 @@ "deleteViewSetting": "Delete view setting", "shareTargets": "Share targets", "shareTargetsCount_one": "{{count}} file selected", - "shareTargetsCount_other": "{{count}} files selected" + "shareTargetsCount_other": "{{count}} files selected", + "hideReadme": "Hide readme file", + "hideReadmeDes": "Keep the README file itself out of the share listing. Visitors still see the rendered readme." }, "uploader": { "fileCopyName": "Copy of ", diff --git a/frontend/public/locales/zh-CN/application.json b/frontend/public/locales/zh-CN/application.json index 254f8424..95bef40f 100644 --- a/frontend/public/locales/zh-CN/application.json +++ b/frontend/public/locales/zh-CN/application.json @@ -688,7 +688,9 @@ "deleteViewSetting": "删除视图设置", "shareTargets": "分享对象", "shareTargetsCount_one": "已选择 {{count}} 个项目", - "shareTargetsCount_other": "已选择 {{count}} 个项目" + "shareTargetsCount_other": "已选择 {{count}} 个项目", + "hideReadme": "隐藏 README 文件", + "hideReadmeDes": "在分享文件列表中隐藏 README 文件本身,访客仍可看到渲染后的说明内容。" }, "uploader": { "fileCopyName": "副本_", diff --git a/frontend/src/api/explorer.ts b/frontend/src/api/explorer.ts index 19bfa0a6..07dedd6c 100644 --- a/frontend/src/api/explorer.ts +++ b/frontend/src/api/explorer.ts @@ -90,6 +90,7 @@ export interface Share { source_uri?: string; password?: string; show_readme?: boolean; + hide_readme?: boolean; allow_upload?: boolean; allow_edit?: boolean; preview_only?: boolean; @@ -360,6 +361,7 @@ export interface ShareCreateService { expire?: number; share_view?: boolean; show_readme?: boolean; + hide_readme?: boolean; allow_upload?: boolean; allow_edit?: boolean; preview_only?: boolean; diff --git a/frontend/src/component/FileManager/Dialogs/Share/ShareDialog.tsx b/frontend/src/component/FileManager/Dialogs/Share/ShareDialog.tsx index cc48642a..d20fcded 100644 --- a/frontend/src/component/FileManager/Dialogs/Share/ShareDialog.tsx +++ b/frontend/src/component/FileManager/Dialogs/Share/ShareDialog.tsx @@ -61,6 +61,7 @@ const shareToSetting = (share: ShareModel, t: TFunction): ShareSetting => { use_custom_password: true, share_view: share.share_view, show_readme: share.show_readme, + hide_readme: share.hide_readme, allow_upload: share.allow_upload, allow_edit: share.allow_edit, preview_only: share.preview_only, diff --git a/frontend/src/component/FileManager/Dialogs/Share/ShareSetting.tsx b/frontend/src/component/FileManager/Dialogs/Share/ShareSetting.tsx index 3dba2788..2b0ed860 100644 --- a/frontend/src/component/FileManager/Dialogs/Share/ShareSetting.tsx +++ b/frontend/src/component/FileManager/Dialogs/Share/ShareSetting.tsx @@ -85,6 +85,7 @@ export interface ShareSetting { password?: string; share_view?: boolean; show_readme?: boolean; + hide_readme?: boolean; allow_upload?: boolean; allow_edit?: boolean; preview_only?: boolean; @@ -399,6 +400,16 @@ const ShareSettingContent = ({ setting, file, editing, onSettingChange }: ShareS ]} /> + + + + onSettingChange({ ...setting, hide_readme: !setting.hide_readme })} + /> + + diff --git a/frontend/src/redux/thunks/share.ts b/frontend/src/redux/thunks/share.ts index 65a28106..281bb465 100644 --- a/frontend/src/redux/thunks/share.ts +++ b/frontend/src/redux/thunks/share.ts @@ -1,10 +1,9 @@ import i18next from "i18next"; import { closeSnackbar, enqueueSnackbar, SnackbarKey } from "notistack"; -import { getFileInfo, getFileList, getShareInfo, sendCreateShare, sendUpdateShare } from "../../api/api.ts"; +import { getFileInfo, getShareInfo, sendCreateShare, sendUpdateShare } from "../../api/api.ts"; import { FileResponse, Share, ShareCreateService } from "../../api/explorer.ts"; import { DefaultCloseAction, OpenReadMeAction } from "../../component/Common/Snackbar/snackbar.tsx"; import { ShareSetting } from "../../component/FileManager/Dialogs/Share/ShareSetting.tsx"; -import { getPaginationState } from "../../component/FileManager/Pagination/PaginationFooter.tsx"; import CrUri from "../../util/uri.ts"; import { fileUpdated } from "../fileManagerSlice.ts"; import { @@ -32,6 +31,7 @@ export function createOrUpdateShareLink( password: setting.password, share_view: setting.share_view, show_readme: setting.show_readme, + hide_readme: setting.show_readme ? setting.hide_readme : false, allow_upload: setting.allow_upload || setting.allow_edit, allow_edit: setting.allow_edit, preview_only: setting.preview_only, @@ -147,7 +147,7 @@ const supportedReadMeFiles = ["README.md", "README.txt"]; export function detectReadMe(index: number, isTablet: boolean): AppThunk> { return async (dispatch, getState) => { - const { files: list, pagination } = getState().fileManager[index]?.list ?? {}; + const { files: list } = getState().fileManager[index]?.list ?? {}; if (list) { // Find readme file from highest to lowest priority for (const readmeFile of supportedReadMeFiles) { @@ -159,10 +159,11 @@ export function detectReadMe(index: number, isTablet: boolean): AppThunk