diff --git a/public/locales/en-US/application.json b/public/locales/en-US/application.json
index cf3a679..9264bd9 100644
--- a/public/locales/en-US/application.json
+++ b/public/locales/en-US/application.json
@@ -486,7 +486,7 @@
"shareLinkPasswordInfo": "Password: {{password}}",
"createShareLink": "Create share link",
"privateShare": "Protect with password",
- "privateShareDes": "If selected, password is required to access the share link, others cannot see this share link on your homepage.",
+ "privateShareDes": "If selected, password is required to access the share link.",
"useCustomPassword": "Custom share link password",
"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.",
@@ -787,6 +787,13 @@
"regTime": "Sign up date",
"security": "Password and security",
"profilePage": "Public profile",
+ "publicShareOnly": "Public share only",
+ "publicShareOnlyDes": "Only show shares without password on the profile page.",
+ "allShare": "All shares",
+ "allShareDes": "Show all shares on the profile page (including password-protected shares). Users still need to enter a password to access them.",
+ "hideShare": "Hide all shares",
+ "hideShareDes": "Hide all shares on the profile page.",
+ "userHideShare": "User has hidden the share list",
"accountPassword": "Password",
"2fa": "2FA authentication",
"enabled": "Enabled",
diff --git a/public/locales/ja-JP/application.json b/public/locales/ja-JP/application.json
index ee8eee1..a4a83b8 100644
--- a/public/locales/ja-JP/application.json
+++ b/public/locales/ja-JP/application.json
@@ -486,7 +486,7 @@
"shareLinkPasswordInfo": "パスワード:{{password}}",
"createShareLink": "共有リンク作成",
"privateShare": "パスワードで保護",
- "privateShareDes": "チェックを入れると、パスワードが必要です。他の人はあなたのプロフィールページでこの共有リンクを見ることができません。",
+ "privateShareDes": "チェックを入れると、パスワードが必要です。",
"useCustomPassword": "カスタムパスワードを使用",
"expireAfterDownload": "ダウンロード後に自動的に期限切れ",
"sharePassword": "共有パスワード",
@@ -791,6 +791,13 @@
"regTime": "登録日時",
"security": "パスワードとセキュリティ",
"profilePage": "マイページ",
+ "publicShareOnly": "パスワードなし共有のみ表示",
+ "publicShareOnlyDes": "マイページにパスワードなし共有のみ表示します。",
+ "allShare": "すべての共有",
+ "allShareDes": "マイページにすべての共有を表示します。パスワードが設定された共有には、パスワードを入力してアクセスする必要があります。",
+ "hideShare": "すべての共有を非表示",
+ "hideShareDes": "マイページにすべての共有を非表示にします。",
+ "userHideShare": "ユーザーが共有リストを非表示にしました",
"accountPassword": "ログインパスワード",
"2fa": "2段階認証",
"enabled": "有効",
diff --git a/public/locales/zh-CN/application.json b/public/locales/zh-CN/application.json
index bd7c731..712041f 100644
--- a/public/locales/zh-CN/application.json
+++ b/public/locales/zh-CN/application.json
@@ -486,7 +486,7 @@
"shareLinkPasswordInfo": " 密码: {{password}}",
"createShareLink": "创建分享链接",
"privateShare": "使用密码保护链接",
- "privateShareDes": "勾选后,需要使用密码访问分享链接,其他人无法在你的个人主页看到此分享链接。",
+ "privateShareDes": "勾选后,需要使用密码访问分享链接。",
"useCustomPassword": "自定义分享密码",
"expireAfterDownload": "下载后自动过期",
"sharePassword": "分享密码",
@@ -791,6 +791,13 @@
"regTime": "注册时间",
"security": "密码和安全",
"profilePage": "个人主页",
+ "publicShareOnly": "仅展示无密码分享链接",
+ "publicShareOnlyDes": "仅在个人主页展示没有设置密码的分享链接。",
+ "allShare": "所有分享",
+ "allShareDes": "在个人主页展示所有分享链接(包括有密码的分享)。对于有密码的分享,用户还需要输入密码才能访问。",
+ "hideShare": "隐藏所有分享链接",
+ "hideShareDes": "在个人主页隐藏所有分享链接。",
+ "userHideShare": "用户隐藏了分享链接列表",
"accountPassword": "登录密码",
"2fa": "二步验证",
"enabled": "已开启",
diff --git a/public/locales/zh-TW/application.json b/public/locales/zh-TW/application.json
index 359ddc6..21b38a4 100644
--- a/public/locales/zh-TW/application.json
+++ b/public/locales/zh-TW/application.json
@@ -482,7 +482,7 @@
"shareLinkPasswordInfo": " 密碼: {{password}}",
"createShareLink": "建立分享連結",
"privateShare": "使用密碼保護連結",
- "privateShareDes": "勾選後,需要使用密碼訪問分享連結,其他人無法在你的個人主頁看到此分享連結。",
+ "privateShareDes": "勾選後,需要使用密碼訪問分享連結。",
"useCustomPassword": "使用自定義密碼",
"expireAfterDownload": "下載後自動過期",
"sharePassword": "分享密碼",
@@ -787,6 +787,13 @@
"regTime": "注冊時間",
"security": "密碼和安全",
"profilePage": "個人主頁",
+ "publicShareOnly": "僅展示無密碼分享連結",
+ "publicShareOnlyDes": "僅在個人主頁展示沒有設置密碼的分享連結。",
+ "allShare": "所有分享",
+ "allShareDes": "在個人主頁展示所有分享連結(包括有密碼的分享)。對於有密碼的分享,用戶還需要輸入密碼才能訪問。",
+ "hideShare": "隱藏所有分享連結",
+ "hideShareDes": "在個人主頁隱藏所有分享連結。",
+ "userHideShare": "用戶隱藏了分享連結列表",
"accountPassword": "登入密碼",
"2fa": "二步驗證",
"enabled": "已開啟",
diff --git a/src/api/explorer.ts b/src/api/explorer.ts
index 7cb9711..65a6fb6 100644
--- a/src/api/explorer.ts
+++ b/src/api/explorer.ts
@@ -83,6 +83,7 @@ export interface Share {
downloaded: number;
expired?: boolean;
unlocked: boolean;
+ password_protected: boolean;
source_type?: number;
owner: User;
source_uri?: string;
diff --git a/src/api/user.ts b/src/api/user.ts
index faa1c2b..ffcaabb 100644
--- a/src/api/user.ts
+++ b/src/api/user.ts
@@ -23,6 +23,7 @@ export interface User {
pined?: PinedFile[];
language?: string;
disable_view_sync?: boolean;
+ share_links_in_profile?: ShareLinksInProfileLevel;
}
export interface Group {
id: string;
@@ -102,6 +103,7 @@ export interface UserSettings {
two_fa_enabled: boolean;
passkeys?: Passkey[];
disable_view_sync: boolean;
+ share_links_in_profile: ShareLinksInProfileLevel;
}
export interface PatchUserSetting {
@@ -116,6 +118,7 @@ export interface PatchUserSetting {
two_fa_enabled?: boolean;
two_fa_code?: string;
disable_view_sync?: boolean;
+ share_links_in_profile?: ShareLinksInProfileLevel;
}
export interface PasskeyCredentialOption {
@@ -191,3 +194,9 @@ export interface ResetPasswordService {
password: string;
secret: string;
}
+
+export enum ShareLinksInProfileLevel {
+ public_share_only = "",
+ all_share = "all_share",
+ hide_share = "hide_share",
+}
diff --git a/src/component/Icons/EyeOff.tsx b/src/component/Icons/EyeOff.tsx
new file mode 100644
index 0000000..3269008
--- /dev/null
+++ b/src/component/Icons/EyeOff.tsx
@@ -0,0 +1,9 @@
+import { SvgIcon, SvgIconProps } from "@mui/material";
+
+export default function EyeOff(props: SvgIconProps) {
+ return (
+
+
+
+ );
+}
diff --git a/src/component/Pages/Profile/Profile.tsx b/src/component/Pages/Profile/Profile.tsx
index 7527bbd..bcf75bd 100644
--- a/src/component/Pages/Profile/Profile.tsx
+++ b/src/component/Pages/Profile/Profile.tsx
@@ -12,7 +12,7 @@ import { DenseSelect } from "../../Common/StyledComponents.tsx";
import { SquareMenuItem } from "../../FileManager/ContextMenu/ContextMenu.tsx";
import ShareCard from "../Shares/ShareCard.tsx";
import { useParams } from "react-router-dom";
-import { User } from "../../../api/user.ts";
+import { ShareLinksInProfileLevel, User } from "../../../api/user.ts";
import { loadUserInfo } from "../../../redux/thunks/session.ts";
import { UserProfile } from "../../Common/User/UserPopover.tsx";
import PageContainer from "../PageContainer.tsx";
@@ -110,31 +110,36 @@ const Profile = () => {
-
-
-
- {t("application:share.createdAtDesc")}
-
-
-
-
- {t("application:share.createdAtAsc")}
-
-
-
-
+ user &&
+ user.share_links_in_profile !== ShareLinksInProfileLevel.hide_share && (
+
+
+
+
+ {t("application:share.createdAtDesc")}
+
+
+
+
+ {t("application:share.createdAtAsc")}
+
+
+
+
+ )
}
skipChangingDocumentTitle
- onRefresh={() => refresh()}
+ onRefresh={
+ user && user.share_links_in_profile !== ShareLinksInProfileLevel.hide_share ? () => refresh() : undefined
+ }
loading={loading}
title={t("application:share.somebodyShare", {
name: user?.nickname ?? "-",
@@ -142,19 +147,28 @@ const Profile = () => {
/>
- {shares.map((share) => (
-
- ))}
- {nextPageToken != undefined && (
+ {user &&
+ user.share_links_in_profile !== ShareLinksInProfileLevel.hide_share &&
+ shares.map((share) => )}
+ {nextPageToken != undefined &&
+ user &&
+ user?.share_links_in_profile !== ShareLinksInProfileLevel.hide_share && (
+ <>
+ {[...Array(4)].map((_, i) => (
+
+ ))}
+ >
+ )}
+ {user && user.share_links_in_profile === ShareLinksInProfileLevel.hide_share && (
<>
- {[...Array(4)].map((_, i) => (
-
- ))}
+
+
+
>
)}
diff --git a/src/component/Pages/Setting/ProfileSetting.tsx b/src/component/Pages/Setting/ProfileSetting.tsx
index 013a0c8..62d646f 100644
--- a/src/component/Pages/Setting/ProfileSetting.tsx
+++ b/src/component/Pages/Setting/ProfileSetting.tsx
@@ -1,14 +1,17 @@
import { LoadingButton } from "@mui/lab";
-import { Collapse, Grid2, Stack, Typography, useMediaQuery, useTheme } from "@mui/material";
+import { Collapse, Grid2, Stack, Typography, useMediaQuery, useTheme, styled } from "@mui/material";
+import { bindPopover, bindTrigger, usePopupState } from "material-ui-popup-state/hooks";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { sendUpdateUserSetting } from "../../../api/api.ts";
-import { UserSettings } from "../../../api/user.ts";
+import { UserSettings, ShareLinksInProfileLevel } from "../../../api/user.ts";
import { useAppDispatch } from "../../../redux/hooks.ts";
import SessionManager from "../../../session";
-import { DenseFilledTextField } from "../../Common/StyledComponents.tsx";
+import { DefaultButton, DenseFilledTextField } from "../../Common/StyledComponents.tsx";
import TimeBadge from "../../Common/TimeBadge.tsx";
+import CaretDown from "../../Icons/CaretDown.tsx";
import AvatarSetting from "./AvatarSetting.tsx";
+import ProfileSettingPopover, { useProfileSettingSummary } from "./ProfileSettingPopover.tsx";
import SettingForm from "./SettingForm.tsx";
export interface ProfileSettingProps {
@@ -16,6 +19,18 @@ export interface ProfileSettingProps {
setSetting: (setting: UserSettings) => void;
}
+const ProfileDropButton = styled(DefaultButton)(({ theme }) => ({
+ color: theme.palette.text.secondary,
+ minWidth: 0,
+ minHeight: 0,
+ fontSize: theme.typography.body2.fontSize,
+ fontWeight: theme.typography.body2.fontWeight,
+ borderRadius: "4px",
+ padding: "0px 4px",
+ position: "relative",
+ left: "-4px",
+}));
+
const ProfileSetting = ({ setting, setSetting }: ProfileSettingProps) => {
const { t } = useTranslation();
const theme = useTheme();
@@ -26,6 +41,14 @@ const ProfileSetting = ({ setting, setSetting }: ProfileSettingProps) => {
const user = SessionManager.currentLoginOrNull();
const [nick, setNick] = useState(user?.user.nickname);
const [nickLoading, setNickLoading] = useState(false);
+ const [profileSettingLoading, setProfileSettingLoading] = useState(false);
+
+ const profileSettingPopup = usePopupState({
+ variant: "popover",
+ popupId: "profileSetting",
+ });
+
+ const profileSettingSummary = useProfileSettingSummary(setting.share_links_in_profile);
const onClick = () => {
// Validate input length
@@ -49,6 +72,21 @@ const ProfileSetting = ({ setting, setSetting }: ProfileSettingProps) => {
}
};
+ const onProfileSettingChange = (value: ShareLinksInProfileLevel) => {
+ setProfileSettingLoading(true);
+ dispatch(sendUpdateUserSetting({ share_links_in_profile: value }))
+ .then(() => {
+ setSetting({
+ ...setting,
+ share_links_in_profile: value,
+ });
+ profileSettingPopup.close();
+ })
+ .finally(() => {
+ setProfileSettingLoading(false);
+ });
+ };
+
return (
{
-
-
- {user?.user.group?.name}
-
-
+
+
+
+ {user?.user.group?.name}
+
+
+
+
+ }
+ disabled={profileSettingLoading}
+ >
+ {profileSettingSummary}
+
+
+
+
diff --git a/src/component/Pages/Setting/ProfileSettingPopover.tsx b/src/component/Pages/Setting/ProfileSettingPopover.tsx
new file mode 100644
index 0000000..7f31542
--- /dev/null
+++ b/src/component/Pages/Setting/ProfileSettingPopover.tsx
@@ -0,0 +1,127 @@
+import {
+ Divider,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ Popover,
+ PopoverProps,
+ SvgIconProps,
+} from "@mui/material";
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
+import { ShareLinksInProfileLevel } from "../../../api/user.ts";
+import Eye from "../../Icons/Eye.tsx";
+import EyeOff from "../../Icons/EyeOff.tsx";
+import Globe from "../../Icons/Globe.tsx";
+
+export interface ProfileSettingPopoverProps extends PopoverProps {
+ currentValue: ShareLinksInProfileLevel;
+ onValueChange?: (value: ShareLinksInProfileLevel) => void;
+ readOnly?: boolean;
+}
+
+const profileSettingOptions: {
+ value: ShareLinksInProfileLevel;
+ label: string;
+ description: string;
+ icon?: ((props: SvgIconProps) => JSX.Element) | typeof Eye;
+}[] = [
+ {
+ value: ShareLinksInProfileLevel.public_share_only,
+ label: "application:setting.publicShareOnly",
+ description: "application:setting.publicShareOnlyDes",
+ icon: Eye,
+ },
+ {
+ value: ShareLinksInProfileLevel.all_share,
+ label: "application:setting.allShare",
+ description: "application:setting.allShareDes",
+ icon: Globe,
+ },
+ {
+ value: ShareLinksInProfileLevel.hide_share,
+ label: "application:setting.hideShare",
+ description: "application:setting.hideShareDes",
+ icon: EyeOff,
+ },
+];
+
+export const useProfileSettingSummary = (value: ShareLinksInProfileLevel) => {
+ const { t } = useTranslation();
+
+ const summary = useMemo(() => {
+ const option = profileSettingOptions.find((opt) => opt.value === (value ?? ""));
+ return option ? t(option.label) : t("application:setting.publicShareOnly");
+ }, [value, t]);
+
+ return summary;
+};
+
+const ProfileSettingPopover = ({ currentValue, onValueChange, readOnly, ...rest }: ProfileSettingPopoverProps) => {
+ const { t } = useTranslation();
+
+ const handleChange = (value: ShareLinksInProfileLevel) => () => {
+ if (onValueChange && !readOnly) {
+ onValueChange(value);
+ }
+ };
+
+ return (
+
+
+ {profileSettingOptions.map((option, index) => (
+ <>
+
+
+ {option.icon && (
+
+ (currentValue === option.value ? theme.palette.primary.main : "inherit"),
+ }}
+ />
+
+ )}
+
+
+
+ {index < profileSettingOptions.length - 1 && }
+ >
+ ))}
+
+
+
+ );
+};
+
+export default ProfileSettingPopover;
diff --git a/src/component/Pages/Shares/ShareCard.tsx b/src/component/Pages/Shares/ShareCard.tsx
index dc09b9c..0853e70 100644
--- a/src/component/Pages/Shares/ShareCard.tsx
+++ b/src/component/Pages/Shares/ShareCard.tsx
@@ -32,6 +32,7 @@ import Clipboard from "../../Icons/Clipboard.tsx";
import DeleteOutlined from "../../Icons/DeleteOutlined.tsx";
import Eye from "../../Icons/Eye.tsx";
import LinkEdit from "../../Icons/LinkEdit.tsx";
+import LockClosedOutlined from "../../Icons/LockClosedOutlined.tsx";
import Open from "../../Icons/Open.tsx";
import { SummaryButton } from "../Tasks/TaskCard.tsx";
@@ -209,7 +210,8 @@ const ShareCard = ({ share, onShareDeleted, onLoad, loading }: ShareCardProps) =
alignItems: "center",
}}
>
-
+
+ {share && share?.password_protected && }
{loading ? : share?.name}