mirror of https://github.com/usememos/memos.git
Compare commits
3 Commits
08a9291964
...
4e54ec38ff
| Author | SHA1 | Date |
|---|---|---|
|
|
4e54ec38ff | |
|
|
0657a1ef5b | |
|
|
48546f05b3 |
|
|
@ -46,6 +46,17 @@ func (s *APIV1Service) GetWorkspaceSetting(ctx context.Context, request *v1pb.Ge
|
|||
return nil, status.Errorf(codes.NotFound, "workspace setting not found")
|
||||
}
|
||||
|
||||
// For storage setting, only host can get it.
|
||||
if workspaceSetting.Key == storepb.WorkspaceSettingKey_STORAGE {
|
||||
user, err := s.GetCurrentUser(ctx)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
|
||||
}
|
||||
if user == nil || user.Role != store.RoleHost {
|
||||
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
|
||||
}
|
||||
}
|
||||
|
||||
return convertWorkspaceSettingFromStore(workspaceSetting), nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -349,6 +349,7 @@ const MemoEditor = (props: Props) => {
|
|||
toast.error(error.details);
|
||||
}
|
||||
|
||||
localStorage.removeItem(contentCacheKey);
|
||||
setState((state) => {
|
||||
return {
|
||||
...state,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
import { Button, Input, Switch } from "@mui/joy";
|
||||
import { isEqual } from "lodash-es";
|
||||
import { useState } from "react";
|
||||
import { WorkspaceSettingPrefix, useWorkspaceSettingStore } from "@/store/v1";
|
||||
import { WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_setting_service";
|
||||
import { WorkspaceSettingKey } from "@/types/proto/store/workspace_setting";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
const MemoRelatedSettings = () => {
|
||||
const t = useTranslate();
|
||||
const workspaceSettingStore = useWorkspaceSettingStore();
|
||||
const originalSetting = WorkspaceMemoRelatedSetting.fromPartial(
|
||||
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.MEMO_RELATED)?.memoRelatedSetting || {},
|
||||
);
|
||||
const [memoRelatedSetting, setMemoRelatedSetting] = useState<WorkspaceMemoRelatedSetting>(originalSetting);
|
||||
|
||||
const updatePartialSetting = (partial: Partial<WorkspaceMemoRelatedSetting>) => {
|
||||
const newWorkspaceMemoRelatedSetting = WorkspaceMemoRelatedSetting.fromPartial({
|
||||
...memoRelatedSetting,
|
||||
...partial,
|
||||
});
|
||||
setMemoRelatedSetting(newWorkspaceMemoRelatedSetting);
|
||||
};
|
||||
|
||||
const updateSetting = async () => {
|
||||
await workspaceSettingStore.setWorkspaceSetting({
|
||||
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.MEMO_RELATED}`,
|
||||
memoRelatedSetting,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">Memo related settings</p>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.system-section.disable-public-memos")}</span>
|
||||
<Switch
|
||||
checked={memoRelatedSetting.disallowPublicVisible}
|
||||
onChange={(event) => updatePartialSetting({ disallowPublicVisible: event.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.system-section.display-with-updated-time")}</span>
|
||||
<Switch
|
||||
checked={memoRelatedSetting.displayWithUpdateTime}
|
||||
onChange={(event) => updatePartialSetting({ displayWithUpdateTime: event.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.system-section.enable-auto-compact")}</span>
|
||||
<Switch
|
||||
checked={memoRelatedSetting.enableAutoCompact}
|
||||
onChange={(event) => updatePartialSetting({ enableAutoCompact: event.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.system-section.enable-double-click-to-edit")}</span>
|
||||
<Switch
|
||||
checked={memoRelatedSetting.enableDoubleClickEdit}
|
||||
onChange={(event) => updatePartialSetting({ enableDoubleClickEdit: event.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>Content length limit(Byte)</span>
|
||||
<Input
|
||||
className="w-24"
|
||||
type="number"
|
||||
defaultValue={memoRelatedSetting.contentLengthLimit}
|
||||
onBlur={(event) => updatePartialSetting({ contentLengthLimit: Number(event.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 w-full flex justify-end">
|
||||
<Button disabled={isEqual(memoRelatedSetting, originalSetting)} onClick={updateSetting}>
|
||||
{t("common.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemoRelatedSettings;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { Button, Divider, Input, Switch, Textarea } from "@mui/joy";
|
||||
import { Button, Switch, Textarea } from "@mui/joy";
|
||||
import { useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { Link } from "react-router-dom";
|
||||
import { workspaceSettingServiceClient } from "@/grpcweb";
|
||||
import { WorkspaceSettingPrefix, useWorkspaceSettingStore } from "@/store/v1";
|
||||
import { WorkspaceGeneralSetting, WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_setting_service";
|
||||
import { WorkspaceGeneralSetting } from "@/types/proto/api/v1/workspace_setting_service";
|
||||
import { WorkspaceSettingKey } from "@/types/proto/store/workspace_setting";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||
|
|
@ -17,11 +17,6 @@ const WorkspaceSection = () => {
|
|||
const [workspaceGeneralSetting, setWorkspaceGeneralSetting] = useState<WorkspaceGeneralSetting>(
|
||||
WorkspaceGeneralSetting.fromPartial(workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.GENERAL)?.generalSetting || {}),
|
||||
);
|
||||
const [workspaceMemoRelatedSetting, setWorkspaceMemoRelatedSetting] = useState<WorkspaceMemoRelatedSetting>(
|
||||
WorkspaceMemoRelatedSetting.fromPartial(
|
||||
workspaceSettingStore.getWorkspaceSettingByKey(WorkspaceSettingKey.MEMO_RELATED)?.memoRelatedSetting || {},
|
||||
),
|
||||
);
|
||||
|
||||
const handleAllowSignUpChanged = async (value: boolean) => {
|
||||
const setting = { ...workspaceGeneralSetting, disallowSignup: !value };
|
||||
|
|
@ -104,56 +99,6 @@ const WorkspaceSection = () => {
|
|||
toast.success(t("message.update-succeed"));
|
||||
};
|
||||
|
||||
const handleDisablePublicMemosChanged = async (value: boolean) => {
|
||||
const update: WorkspaceMemoRelatedSetting = { ...workspaceMemoRelatedSetting, disallowPublicVisible: value };
|
||||
setWorkspaceMemoRelatedSetting(update);
|
||||
await workspaceSettingStore.setWorkspaceSetting({
|
||||
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.MEMO_RELATED}`,
|
||||
memoRelatedSetting: update,
|
||||
});
|
||||
};
|
||||
|
||||
const handleMemoDisplayWithUpdatedTs = async (value: boolean) => {
|
||||
const update: WorkspaceMemoRelatedSetting = { ...workspaceMemoRelatedSetting, displayWithUpdateTime: value };
|
||||
setWorkspaceMemoRelatedSetting(update);
|
||||
await workspaceSettingStore.setWorkspaceSetting({
|
||||
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.MEMO_RELATED}`,
|
||||
memoRelatedSetting: update,
|
||||
});
|
||||
};
|
||||
|
||||
const handleMemoEnableAutoCompact = async (value: boolean) => {
|
||||
const update: WorkspaceMemoRelatedSetting = { ...workspaceMemoRelatedSetting, enableAutoCompact: value };
|
||||
setWorkspaceMemoRelatedSetting(update);
|
||||
await workspaceSettingStore.setWorkspaceSetting({
|
||||
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.MEMO_RELATED}`,
|
||||
memoRelatedSetting: update,
|
||||
});
|
||||
};
|
||||
|
||||
const handleMemoEnableDoubleClickToEdit = async (value: boolean) => {
|
||||
const update: WorkspaceMemoRelatedSetting = { ...workspaceMemoRelatedSetting, enableDoubleClickEdit: value };
|
||||
setWorkspaceMemoRelatedSetting(update);
|
||||
await workspaceSettingStore.setWorkspaceSetting({
|
||||
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.MEMO_RELATED}`,
|
||||
memoRelatedSetting: update,
|
||||
});
|
||||
};
|
||||
|
||||
const handleMemoContentLengthLimitChanges = async (value: number) => {
|
||||
if (value < 8 * 1024) {
|
||||
toast.error("Content length limit should be greater than 8KB");
|
||||
return;
|
||||
}
|
||||
|
||||
const update: WorkspaceMemoRelatedSetting = { ...workspaceMemoRelatedSetting, contentLengthLimit: value };
|
||||
setWorkspaceMemoRelatedSetting(update);
|
||||
await workspaceSettingStore.setWorkspaceSetting({
|
||||
name: `${WorkspaceSettingPrefix}${WorkspaceSettingKey.MEMO_RELATED}`,
|
||||
memoRelatedSetting: update,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col gap-2 pt-2 pb-4">
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">{t("common.basic")}</p>
|
||||
|
|
@ -225,45 +170,6 @@ const WorkspaceSection = () => {
|
|||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<Divider className="!my-3" />
|
||||
<p className="font-medium text-gray-700 dark:text-gray-500">Memo related settings</p>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.system-section.disable-public-memos")}</span>
|
||||
<Switch
|
||||
checked={workspaceMemoRelatedSetting.disallowPublicVisible}
|
||||
onChange={(event) => handleDisablePublicMemosChanged(event.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.system-section.display-with-updated-time")}</span>
|
||||
<Switch
|
||||
checked={workspaceMemoRelatedSetting.displayWithUpdateTime}
|
||||
onChange={(event) => handleMemoDisplayWithUpdatedTs(event.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.system-section.enable-auto-compact")}</span>
|
||||
<Switch
|
||||
checked={workspaceMemoRelatedSetting.enableAutoCompact}
|
||||
onChange={(event) => handleMemoEnableAutoCompact(event.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>{t("setting.system-section.enable-double-click-to-edit")}</span>
|
||||
<Switch
|
||||
checked={workspaceMemoRelatedSetting.enableDoubleClickEdit}
|
||||
onChange={(event) => handleMemoEnableDoubleClickToEdit(event.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<span>Content length limit(Byte)</span>
|
||||
<Input
|
||||
className="w-32"
|
||||
type="number"
|
||||
defaultValue={workspaceMemoRelatedSetting.contentLengthLimit}
|
||||
onBlur={(event) => handleMemoContentLengthLimitChanges(Number(event.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -297,7 +297,8 @@
|
|||
"max-upload-size": "Maximum upload size (MiB)",
|
||||
"max-upload-size-hint": "Recommended value is 32 MiB.",
|
||||
"server-name": "Server Name"
|
||||
}
|
||||
},
|
||||
"memo-related": "Memo"
|
||||
},
|
||||
"tag": {
|
||||
"all-tags": "All Tags",
|
||||
|
|
|
|||
|
|
@ -290,6 +290,8 @@
|
|||
"disable-password-login-warning": "Bu, tüm kullanıcılar için parola girişini devre dışı bırakacaktır. Yapılandırılmış kimlik sağlayıcılarınız başarısız olursa, veritabanındaki bu ayarı geri almadan oturum açmak mümkün değildir. Ayrıca bir kimlik sağlayıcısını kaldırırken ekstra dikkatli olmanız gerekecektir",
|
||||
"disable-public-memos": "Halka açık notları devre dışı bırak",
|
||||
"display-with-updated-time": "Güncellenmiş zaman göstergesi",
|
||||
"enable-auto-compact": "Otomatik sıkıştırmayı etkinleştir",
|
||||
"enable-double-click-to-edit": "Düzenlemek için çift tıklamayı etkinleştir",
|
||||
"enable-password-login": "Parola girişini etkinleştir",
|
||||
"enable-password-login-warning": "Bu, tüm kullanıcılar için parola girişini etkinleştirecektir. Yalnızca kullanıcıların hem SSO hem de parola kullanarak oturum açabilmesini istiyorsanız devam edin",
|
||||
"max-upload-size": "Maksimum yükleme boyutu (MiB)",
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@
|
|||
},
|
||||
"memo": {
|
||||
"archived-at": "存档于",
|
||||
"code": "代码",
|
||||
"comment": {
|
||||
"self": "评论",
|
||||
"write-a-comment": "写评论"
|
||||
|
|
@ -106,19 +107,18 @@
|
|||
"embed": "嵌入",
|
||||
"fetch-more": "点击获取更多",
|
||||
"fetching-data": "正在获取数据…",
|
||||
"links": "链接",
|
||||
"no-archived-memos": "无存档的 Memo。",
|
||||
"search-placeholder": "搜索 Memos",
|
||||
"show-more": "查看更多",
|
||||
"to-do": "待办",
|
||||
"view-detail": "查看详情",
|
||||
"visibility": {
|
||||
"disabled": "公共 Memo 已禁用",
|
||||
"private": "私有",
|
||||
"protected": "工作区",
|
||||
"public": "公开"
|
||||
},
|
||||
"links": "链接",
|
||||
"to-do": "待办",
|
||||
"code": "代码"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"archived-successfully": "归档成功",
|
||||
|
|
@ -290,6 +290,8 @@
|
|||
"disable-password-login-warning": "所有用户将无法使用密码登录。如果配置的身份提供程序失效,不在数据库中恢复此设置将无法登录。删除身份提供程序时也要格外小心",
|
||||
"disable-public-memos": "禁用公共 Memo",
|
||||
"display-with-updated-time": "根据最后修改时间顺序显示",
|
||||
"enable-auto-compact": "启用自动超长折叠显示",
|
||||
"enable-double-click-to-edit": "启用双击编辑",
|
||||
"enable-password-login": "启用密码登录",
|
||||
"enable-password-login-warning": "启用所有用户的密码登录。如果希望用户同时使用单点登录和密码登录,请开启密码登录",
|
||||
"max-upload-size": "最大上传大小 (MiB)",
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ const MemoDetail = () => {
|
|||
placeholder: t("editor.add-your-comment-here"),
|
||||
parentMemoName: memo.name,
|
||||
onConfirm: handleCommentCreated,
|
||||
cacheKey: `${memo.name}-${memo.updateTime}-comment`,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
|||
import Icon from "@/components/Icon";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import MemberSection from "@/components/Settings/MemberSection";
|
||||
import MemoRelatedSettings from "@/components/Settings/MemoRelatedSettings";
|
||||
import MyAccountSection from "@/components/Settings/MyAccountSection";
|
||||
import PreferencesSection from "@/components/Settings/PreferencesSection";
|
||||
import SSOSection from "@/components/Settings/SSOSection";
|
||||
|
|
@ -17,19 +18,20 @@ import { User_Role } from "@/types/proto/api/v1/user_service";
|
|||
import { WorkspaceSettingKey } from "@/types/proto/store/workspace_setting";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
type SettingSection = "my-account" | "preference" | "member" | "system" | "storage" | "sso";
|
||||
type SettingSection = "my-account" | "preference" | "member" | "system" | "memo-related" | "storage" | "sso";
|
||||
|
||||
interface State {
|
||||
selectedSection: SettingSection;
|
||||
}
|
||||
|
||||
const BASIC_SECTIONS: SettingSection[] = ["my-account", "preference"];
|
||||
const ADMIN_SECTIONS: SettingSection[] = ["member", "system", "storage", "sso"];
|
||||
const ADMIN_SECTIONS: SettingSection[] = ["member", "system", "memo-related", "storage", "sso"];
|
||||
const SECTION_ICON_MAP: Record<SettingSection, LucideIcon> = {
|
||||
"my-account": Icon.User,
|
||||
preference: Icon.Cog,
|
||||
member: Icon.Users,
|
||||
system: Icon.Settings2,
|
||||
"memo-related": Icon.Library,
|
||||
storage: Icon.Database,
|
||||
sso: Icon.Key,
|
||||
};
|
||||
|
|
@ -125,6 +127,8 @@ const Setting = () => {
|
|||
<MemberSection />
|
||||
) : state.selectedSection === "system" ? (
|
||||
<WorkspaceSection />
|
||||
) : state.selectedSection === "memo-related" ? (
|
||||
<MemoRelatedSettings />
|
||||
) : state.selectedSection === "storage" ? (
|
||||
<StorageSection />
|
||||
) : state.selectedSection === "sso" ? (
|
||||
|
|
|
|||
Loading…
Reference in New Issue