diff --git a/frontend/public/locales/en-US/application.json b/frontend/public/locales/en-US/application.json
index a4ba1473..7813e9c6 100644
--- a/frontend/public/locales/en-US/application.json
+++ b/frontend/public/locales/en-US/application.json
@@ -606,6 +606,14 @@
"unlinkOnlyDes": "Delete file records only, physical files will not be deleted.",
"shareView": "Share view setting",
"shareViewDes": "If selected, other users can see your view setting (layout, sorting, etc.) saved on the server server when accessing this shared folder.",
+ "allowUpload": "Allow uploads",
+ "allowUploadDes": "Visitors can upload new files and create folders in this shared folder. Storage is counted towards your quota.",
+ "allowEdit": "Allow editing",
+ "allowEditDes": "Visitors can upload, rename, move and delete files in this shared folder. Implies upload permission.",
+ "uploadOnly": "Upload only (drop box)",
+ "uploadOnlyDes": "Visitors can upload files but cannot see or download existing content in this shared folder.",
+ "previewOnly": "Preview only",
+ "previewOnlyDes": "Visitors can preview files inline but cannot download them.",
"showReadme": "Show README file",
"showReadmeDes": "If selected, the <0>README.md0> file (case-sensitive) in the directory will be automatically displayed for visitors.",
"viewSetting": "View setting",
diff --git a/frontend/public/locales/zh-CN/application.json b/frontend/public/locales/zh-CN/application.json
index 9264f674..789b5167 100644
--- a/frontend/public/locales/zh-CN/application.json
+++ b/frontend/public/locales/zh-CN/application.json
@@ -606,6 +606,14 @@
"unlinkOnlyDes": "仅删除文件记录,物理文件不会被删除",
"shareView": "分享视图设置",
"shareViewDes": "勾选后,其他用户访问此共享文件夹时可以看到你保存在服务器的视图设置 (布局、排序等)。",
+ "allowUpload": "允许上传",
+ "allowUploadDes": "访客可以在此共享文件夹中上传新文件和创建文件夹,存储计入你的配额。",
+ "allowEdit": "允许编辑",
+ "allowEditDes": "访客可以上传、重命名、移动和删除此共享文件夹中的文件,包含上传权限。",
+ "uploadOnly": "仅上传(收集箱)",
+ "uploadOnlyDes": "访客可以上传文件,但无法查看或下载此共享文件夹中的现有内容。",
+ "previewOnly": "仅预览",
+ "previewOnlyDes": "访客可以在线预览文件,但不能下载。",
"showReadme": "显示 README 文件",
"showReadmeDes": "勾选后,会自动为访问者展示目录下的 <0>README.md0> (区分大小写) 文件。",
"viewSetting": "视图设置",
diff --git a/frontend/src/api/explorer.ts b/frontend/src/api/explorer.ts
index af6093ee..5bf62ebc 100644
--- a/frontend/src/api/explorer.ts
+++ b/frontend/src/api/explorer.ts
@@ -90,6 +90,10 @@ export interface Share {
source_uri?: string;
password?: string;
show_readme?: boolean;
+ allow_upload?: boolean;
+ allow_edit?: boolean;
+ preview_only?: boolean;
+ upload_only?: boolean;
}
export enum PolicyType {
@@ -308,6 +312,10 @@ export interface ShareCreateService {
expire?: number;
share_view?: boolean;
show_readme?: boolean;
+ allow_upload?: boolean;
+ allow_edit?: boolean;
+ preview_only?: boolean;
+ upload_only?: boolean;
}
export interface CreateFileService {
diff --git a/frontend/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts b/frontend/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts
index a2e1e622..3a37a4ab 100644
--- a/frontend/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts
+++ b/frontend/src/component/FileManager/ContextMenu/useActionDisplayOpt.ts
@@ -26,11 +26,12 @@ export const canShowInfo = (cap: Boolset) => {
};
export const canUpdate = (opt: DisplayOption) => {
+ // Overwriting an existing file requires the rename capability server-side.
return !!(
opt.allUpdatable &&
opt.hasFile &&
opt.orCapability?.enabled(NavigatorCapability.upload_file) &&
- opt.allUpdatable
+ opt.orCapability?.enabled(NavigatorCapability.rename_file)
);
};
@@ -117,7 +118,9 @@ export const getActionOpt = (
}
const parentCap = new Boolset(parent.capability);
- display.showCreateFolder = parentCap.enabled(NavigatorCapability.create_file) && parent.owned;
+ const parentInShare = new CrUri(parent.path).fs() == Filesystem.share;
+ display.showCreateFolder =
+ parentCap.enabled(NavigatorCapability.create_file) && (parent.owned || parentInShare);
display.showCreateFile = display.showCreateFolder && fmIndex == FileManagerIndex.main;
display.showUpload = display.showCreateFile;
if (display.showCreateFile) {
@@ -138,9 +141,12 @@ export const getActionOpt = (
}
const parentUrl = new CrUri(targets?.[0]?.path ?? defaultPath);
+ const inShare = parentUrl.fs() == Filesystem.share;
targets.forEach((target) => {
- let readable = true;
- let updatable = target.owned && parentUrl.fs() != Filesystem.share;
+ const readable = true;
+ // Share visitors are never owners; their writable actions are gated by
+ // the per-file capability boolset stamped from the share's props.
+ const updatable = target.owned || inShare;
if (display.allReadable && !readable) {
display.allReadable = false;
@@ -210,7 +216,11 @@ export const getActionOpt = (
display.allUpdatable &&
display.orCapability &&
display.orCapability.enabled(NavigatorCapability.rename_file);
- display.showCopy = display.hasUpdatable && !!display.orCapability;
+ display.showCopy =
+ display.hasUpdatable &&
+ display.orCapability &&
+ display.orCapability.enabled(NavigatorCapability.download_file) &&
+ (!inShare || display.orCapability.enabled(NavigatorCapability.create_file));
display.showShare =
targets.length == 1 &&
!!currentUser &&
@@ -221,7 +231,11 @@ export const getActionOpt = (
display.orCapability.enabled(NavigatorCapability.share) &&
(!targets[0].metadata ||
(!targets[0].metadata[Metadata.share_redirect] && !targets[0].metadata[Metadata.restore_uri]));
- display.showMove = display.hasUpdatable && !!display.orCapability;
+ display.showMove =
+ display.hasUpdatable &&
+ display.orCapability &&
+ display.orCapability.enabled(NavigatorCapability.delete_file) &&
+ (!inShare || display.orCapability.enabled(NavigatorCapability.create_file));
display.showTags =
display.hasUpdatable && display.orCapability && display.orCapability.enabled(NavigatorCapability.update_metadata);
display.showChangeFolderColor =
diff --git a/frontend/src/component/FileManager/Dialogs/Share/ShareDialog.tsx b/frontend/src/component/FileManager/Dialogs/Share/ShareDialog.tsx
index 6a82ef23..43676403 100644
--- a/frontend/src/component/FileManager/Dialogs/Share/ShareDialog.tsx
+++ b/frontend/src/component/FileManager/Dialogs/Share/ShareDialog.tsx
@@ -34,6 +34,10 @@ const shareToSetting = (share: ShareModel, t: TFunction): ShareSetting => {
use_custom_password: true,
share_view: share.share_view,
show_readme: share.show_readme,
+ allow_upload: share.allow_upload,
+ allow_edit: share.allow_edit,
+ preview_only: share.preview_only,
+ upload_only: share.upload_only,
downloads: share.remain_downloads != undefined && share.remain_downloads > 0,
expires_val: expireOptions[2],
diff --git a/frontend/src/component/FileManager/Dialogs/Share/ShareSetting.tsx b/frontend/src/component/FileManager/Dialogs/Share/ShareSetting.tsx
index 2ac95262..bd5837e7 100644
--- a/frontend/src/component/FileManager/Dialogs/Share/ShareSetting.tsx
+++ b/frontend/src/component/FileManager/Dialogs/Share/ShareSetting.tsx
@@ -24,9 +24,13 @@ import { Code } from "../../../Common/Code.tsx";
import { FilledTextField, SmallFormControlLabel } from "../../../Common/StyledComponents.tsx";
import BookInformation from "../../../Icons/BookInformation.tsx";
import ClockArrowDownload from "../../../Icons/ClockArrowDownload.tsx";
+import Edit from "../../../Icons/Edit.tsx";
import Eye from "../../../Icons/Eye.tsx";
+import EyeOff from "../../../Icons/EyeOff.tsx";
+import FolderAdd from "../../../Icons/FolderAdd.tsx";
import TableSettingsOutlined from "../../../Icons/TableSettings.tsx";
import Timer from "../../../Icons/Timer.tsx";
+import Upload from "../../../Icons/Upload.tsx";
const Accordion = styled(MuiAccordion)(() => ({
border: "0px solid rgba(0, 0, 0, .125)",
@@ -79,6 +83,10 @@ export interface ShareSetting {
password?: string;
share_view?: boolean;
show_readme?: boolean;
+ allow_upload?: boolean;
+ allow_edit?: boolean;
+ preview_only?: boolean;
+ upload_only?: boolean;
downloads?: boolean;
expires?: boolean;
@@ -132,7 +140,9 @@ const ShareSettingContent = ({ setting, file, editing, onSettingChange }: ShareS
setExpanded(isExpanded ? panel : undefined);
};
- const handleCheck = (prop: "is_private" | "share_view" | "show_readme" | "expires" | "downloads") => () => {
+ const handleCheck = (
+ prop: "is_private" | "share_view" | "show_readme" | "preview_only" | "expires" | "downloads",
+ ) => () => {
if (!setting[prop]) {
handleExpand(prop)(null, true);
}
@@ -200,8 +210,98 @@ const ShareSettingContent = ({ setting, file, editing, onSettingChange }: ShareS
)}
+
+
+ {t("application:modals.previewOnlyDes")}
+
{file?.type == FileType.folder && (
<>
+
+
+ {t("application:modals.allowUploadDes")}
+
+
+
+ {t("application:modals.allowEditDes")}
+
+
+
+ {t("application:modals.uploadOnlyDes")}
+