feat: more setting of ext
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"linkify-plugin-mention": "^4.2.0",
|
"linkify-plugin-mention": "^4.2.0",
|
||||||
"linkify-react": "^4.2.0",
|
"linkify-react": "^4.2.0",
|
||||||
|
"linkify-string": "^4.2.0",
|
||||||
"linkifyjs": "^4.2.0",
|
"linkifyjs": "^4.2.0",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"localforage-setitems": "^1.4.0",
|
"localforage-setitems": "^1.4.0",
|
||||||
|
|||||||
Generated
+12
@@ -125,6 +125,9 @@ importers:
|
|||||||
linkify-react:
|
linkify-react:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0(linkifyjs@4.2.0)(react@19.0.0)
|
version: 4.2.0(linkifyjs@4.2.0)(react@19.0.0)
|
||||||
|
linkify-string:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0(linkifyjs@4.2.0)
|
||||||
linkifyjs:
|
linkifyjs:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
@@ -3909,6 +3912,11 @@ packages:
|
|||||||
linkifyjs: ^4.0.0
|
linkifyjs: ^4.0.0
|
||||||
react: '>= 15.0.0'
|
react: '>= 15.0.0'
|
||||||
|
|
||||||
|
linkify-string@4.2.0:
|
||||||
|
resolution: {integrity: sha512-LqOKk0+RlqibFkxjPAGOL7Mfssqj6/SdG9QWGvzeVDpoWXhaw9OXxseCtFanjIl7C6UyTTZizhyGr4IWLfijiw==}
|
||||||
|
peerDependencies:
|
||||||
|
linkifyjs: ^4.0.0
|
||||||
|
|
||||||
linkifyjs@4.2.0:
|
linkifyjs@4.2.0:
|
||||||
resolution: {integrity: sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==}
|
resolution: {integrity: sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==}
|
||||||
|
|
||||||
@@ -10109,6 +10117,10 @@ snapshots:
|
|||||||
linkifyjs: 4.2.0
|
linkifyjs: 4.2.0
|
||||||
react: 19.0.0
|
react: 19.0.0
|
||||||
|
|
||||||
|
linkify-string@4.2.0(linkifyjs@4.2.0):
|
||||||
|
dependencies:
|
||||||
|
linkifyjs: 4.2.0
|
||||||
|
|
||||||
linkifyjs@4.2.0: {}
|
linkifyjs@4.2.0: {}
|
||||||
|
|
||||||
loader-runner@4.3.0: {}
|
loader-runner@4.3.0: {}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"id": "Channel ID",
|
"id": "Channel ID",
|
||||||
"name": "Channel Name",
|
"name": "Channel Name",
|
||||||
"topic": "Channel Topic",
|
"topic": "Channel Topic",
|
||||||
"topic_placeholder": "Let everyone know how to use this channel.",
|
"topic_placeholder": "Let everyone know how to use this channel.(markdown supported)",
|
||||||
"visibility": "Channel Visibility",
|
"visibility": "Channel Visibility",
|
||||||
"public": "Public",
|
"public": "Public",
|
||||||
"private": "Private",
|
"private": "Private",
|
||||||
@@ -86,6 +86,12 @@
|
|||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
"disable": "Disable"
|
"disable": "Disable"
|
||||||
},
|
},
|
||||||
|
"admin_only_can_invite": {
|
||||||
|
"title": "Only Admins Can Invite Users",
|
||||||
|
"desc": "If enabled, only admins can invite new members.",
|
||||||
|
"enable": "Enable",
|
||||||
|
"disable": "Disable"
|
||||||
|
},
|
||||||
"enable_msg_url_preview": {
|
"enable_msg_url_preview": {
|
||||||
"title": "Enable URL Preview in Message",
|
"title": "Enable URL Preview in Message",
|
||||||
"desc": "If enabled, url in message will show preview content.",
|
"desc": "If enabled, url in message will show preview content.",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
"delete_desc": "Are you sure want to delete this channel?",
|
"delete_desc": "Are you sure want to delete this channel?",
|
||||||
"name": "Channel Name",
|
"name": "Channel Name",
|
||||||
"topic": "Channel Topic",
|
"topic": "Channel Topic",
|
||||||
"topic_placeholder": "Let everyone know how to use this channel.",
|
"topic_placeholder": "Let everyone know how to use this channel.(markdown supported)",
|
||||||
"visibility": "Channel Visibility",
|
"visibility": "Channel Visibility",
|
||||||
"public": "Public",
|
"public": "Public",
|
||||||
"private": "Private"
|
"private": "Private"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"id": "频道 ID",
|
"id": "频道 ID",
|
||||||
"name": "频道名",
|
"name": "频道名",
|
||||||
"topic": "频道主题",
|
"topic": "频道主题",
|
||||||
"topic_placeholder": "让别人知道这个频道是用来做啥的",
|
"topic_placeholder": "让别人知道这个频道是用来做啥的(支持 markdown)",
|
||||||
"visibility": "可见性",
|
"visibility": "可见性",
|
||||||
"public": "公开",
|
"public": "公开",
|
||||||
"private": "私密",
|
"private": "私密",
|
||||||
@@ -85,6 +85,12 @@
|
|||||||
"enable": "开启",
|
"enable": "开启",
|
||||||
"disable": "关闭"
|
"disable": "关闭"
|
||||||
},
|
},
|
||||||
|
"admin_only_can_invite": {
|
||||||
|
"title": "只有管理员可以邀请新用户",
|
||||||
|
"desc": "如果开启,只有管理员才能邀请新用户",
|
||||||
|
"enable": "开启",
|
||||||
|
"disable": "关闭"
|
||||||
|
},
|
||||||
"enable_msg_url_preview": {
|
"enable_msg_url_preview": {
|
||||||
"title": "开启消息网址预览",
|
"title": "开启消息网址预览",
|
||||||
"desc": "如果开启,消息中的网址会显示预览内容",
|
"desc": "如果开启,消息中的网址会显示预览内容",
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ export const getEmailNotifyDelayList = () => [
|
|||||||
value: 12 * 60 * 60,
|
value: 12 * 60 * 60,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
export const KEY_ADMIN_ONLY_INVITE = `admin_only_can_invite`;
|
||||||
export const KEY_ADMIN_SEE_CHANNEL_MEMBERS = `only_admin_can_see_channel_members`;
|
export const KEY_ADMIN_SEE_CHANNEL_MEMBERS = `only_admin_can_see_channel_members`;
|
||||||
export const KEY_MSG_URL_PREVIEW = `enable_msg_url_preview`;
|
export const KEY_MSG_URL_PREVIEW = `enable_msg_url_preview`;
|
||||||
export default BASE_URL;
|
export default BASE_URL;
|
||||||
|
|||||||
@@ -12,9 +12,13 @@ import InviteModal from "./InviteModal";
|
|||||||
import SearchUser from "./SearchUser";
|
import SearchUser from "./SearchUser";
|
||||||
import UsersModal from "./UsersModal";
|
import UsersModal from "./UsersModal";
|
||||||
import { shallowEqual } from "react-redux";
|
import { shallowEqual } from "react-redux";
|
||||||
|
import useServerExtSetting from "../hooks/useServerExtSetting";
|
||||||
|
import { KEY_ADMIN_ONLY_INVITE } from "../app/config";
|
||||||
|
|
||||||
export default function AddEntriesMenu() {
|
export default function AddEntriesMenu() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { getExtSetting } = useServerExtSetting();
|
||||||
|
const onlyAdminCanInvite = getExtSetting(KEY_ADMIN_ONLY_INVITE);
|
||||||
const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
|
const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
|
||||||
const onlyAdminCreateGroup = useAppSelector(
|
const onlyAdminCreateGroup = useAppSelector(
|
||||||
(store) => store.server.only_admin_can_create_group,
|
(store) => store.server.only_admin_can_create_group,
|
||||||
@@ -62,6 +66,7 @@ export default function AddEntriesMenu() {
|
|||||||
"rounded flex items-center gap-2 text-sm font-semibold cursor-pointer px-2 py-2.5 md:hover:bg-gray-800/20 md:dark:hover:bg-gray-200/20";
|
"rounded flex items-center gap-2 text-sm font-semibold cursor-pointer px-2 py-2.5 md:hover:bg-gray-800/20 md:dark:hover:bg-gray-200/20";
|
||||||
const iconClass = "w-5 h-5 dark:fill-gray-300";
|
const iconClass = "w-5 h-5 dark:fill-gray-300";
|
||||||
const canPrivateGroup = onlyAdminCreateGroup ? isAdmin : true;
|
const canPrivateGroup = onlyAdminCreateGroup ? isAdmin : true;
|
||||||
|
const showInvite = isAdmin || !onlyAdminCanInvite;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ul className="flex flex-col rounded-xl drop-shadow p-1 select-none text-gray-500 dark:text-gray-300 bg-white dark:bg-black">
|
<ul className="flex flex-col rounded-xl drop-shadow p-1 select-none text-gray-500 dark:text-gray-300 bg-white dark:bg-black">
|
||||||
@@ -82,10 +87,12 @@ export default function AddEntriesMenu() {
|
|||||||
<IconMention className={iconClass} />
|
<IconMention className={iconClass} />
|
||||||
{t("action.new_msg")}
|
{t("action.new_msg")}
|
||||||
</li>
|
</li>
|
||||||
<li className={itemClass} onClick={toggleInviteModalVisible}>
|
{showInvite && (
|
||||||
<IconInvite className={iconClass} />
|
<li className={itemClass} onClick={toggleInviteModalVisible}>
|
||||||
{t("action.invite_people")}
|
<IconInvite className={iconClass} />
|
||||||
</li>
|
{t("action.invite_people")}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
<li className={itemClass} onClick={toggleSearchModalVisible}>
|
<li className={itemClass} onClick={toggleSearchModalVisible}>
|
||||||
<IconSearch className={iconClass} />
|
<IconSearch className={iconClass} />
|
||||||
{t("action.search_people")}
|
{t("action.search_people")}
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ import { isDarkMode } from "../utils";
|
|||||||
import ImagePreview from "./ImagePreview";
|
import ImagePreview from "./ImagePreview";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
cleanMode?: boolean;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
const MarkdownRender: FC<IProps> = ({ content }) => {
|
const MarkdownRender: FC<IProps> = ({ content, cleanMode = false }) => {
|
||||||
const mdContainer = useRef<HTMLDivElement | null>(null);
|
const mdContainer = useRef<HTMLDivElement | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mdContainer.current) {
|
if (mdContainer.current) {
|
||||||
@@ -28,7 +29,8 @@ const MarkdownRender: FC<IProps> = ({ content }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [mdContainer]);
|
}, [mdContainer]);
|
||||||
|
if (cleanMode)
|
||||||
|
return <Viewer initialValue={content} theme={isDarkMode() ? "dark" : "light"}></Viewer>;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ImagePreview container={mdContainer.current} context="markdown" />
|
<ImagePreview container={mdContainer.current} context="markdown" />
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { FC } from "react";
|
|
||||||
import { useAppSelector } from "@/app/store";
|
import { useAppSelector } from "@/app/store";
|
||||||
import { useGetServerQuery } from "@/app/services/server";
|
import { useGetServerQuery } from "@/app/services/server";
|
||||||
import { shallowEqual } from "react-redux";
|
import { shallowEqual } from "react-redux";
|
||||||
|
|
||||||
type Props = {};
|
const Meta = () => {
|
||||||
const Meta: FC<Props> = () => {
|
|
||||||
useGetServerQuery();
|
useGetServerQuery();
|
||||||
const { name, logo } = useAppSelector((store) => store.server, shallowEqual);
|
const { name, logo } = useAppSelector((store) => store.server, shallowEqual);
|
||||||
|
console.log({name});
|
||||||
|
if(!name) return null
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{name && <title>{name} Web App</title>}
|
{name && <title>{`${name} Web App`}</title>}
|
||||||
{logo && <link rel="icon" href={`${logo}?ts=${new Date().getTime()}`} />}
|
{logo && <link rel="icon" href={`${logo}?ts=${new Date().getTime()}`} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import clsx from "clsx";
|
|||||||
import { useAppSelector } from "@/app/store";
|
import { useAppSelector } from "@/app/store";
|
||||||
import EditIcon from "@/assets/icons/edit.svg";
|
import EditIcon from "@/assets/icons/edit.svg";
|
||||||
import { shallowEqual } from "react-redux";
|
import { shallowEqual } from "react-redux";
|
||||||
|
import linkifyStr from "linkify-string";
|
||||||
|
|
||||||
type ChannelHeaderProps = {
|
type ChannelHeaderProps = {
|
||||||
cid: number;
|
cid: number;
|
||||||
@@ -15,12 +16,18 @@ const ChannelHeader = ({ cid }: ChannelHeaderProps) => {
|
|||||||
const { t } = useTranslation("chat");
|
const { t } = useTranslation("chat");
|
||||||
const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
|
const isAdmin = useAppSelector((store) => store.authData.user?.is_admin, shallowEqual);
|
||||||
const data = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
|
const data = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
|
||||||
|
const desc = data.description
|
||||||
|
? linkifyStr(data.description, { target: "_blank" })
|
||||||
|
: t("welcome_desc", { name: data?.name });
|
||||||
return (
|
return (
|
||||||
<div className="pt-14 px-1 md:px-0 flex flex-col items-start gap-2">
|
<div className="pt-14 px-1 md:px-0 flex flex-col items-start gap-2">
|
||||||
<h2 className="font-bold text-4xl dark:text-white">
|
<h2 className="font-bold text-4xl dark:text-white">
|
||||||
{t("welcome_channel", { name: data?.name })}
|
{t("welcome_channel", { name: data?.name })}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 dark:text-gray-300">{t("welcome_desc", { name: data?.name })} </p>
|
<p
|
||||||
|
className="text-gray-600 dark:text-gray-300 [&>a]:text-blue-500 [&>a]:hover:underline"
|
||||||
|
dangerouslySetInnerHTML={{ __html: desc }}
|
||||||
|
/>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`/setting/channel/${cid}/overview?f=${pathname}`}
|
to={`/setting/channel/${cid}/overview?f=${pathname}`}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import SettingBlock from "@/components/SettingBlock";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import ServerVersionChecker from "@/components/ServerVersionChecker";
|
||||||
|
import { KEY_ADMIN_ONLY_INVITE } from "@/app/config";
|
||||||
|
import useServerExtSetting from "@/hooks/useServerExtSetting";
|
||||||
|
import Toggle from "@/components/styled/Toggle";
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
const WhoCanInviteUsers = ({}: Props) => {
|
||||||
|
const { updateExtSetting, getExtSetting } = useServerExtSetting();
|
||||||
|
const { t } = useTranslation("setting");
|
||||||
|
const adminOnlyCanInvite = getExtSetting(KEY_ADMIN_ONLY_INVITE);
|
||||||
|
const handleToggle = () => {
|
||||||
|
updateExtSetting(KEY_ADMIN_ONLY_INVITE, !adminOnlyCanInvite);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ServerVersionChecker empty version="0.3.50">
|
||||||
|
<SettingBlock
|
||||||
|
title={t("overview.admin_only_can_invite.title")}
|
||||||
|
desc={t("overview.admin_only_can_invite.desc")}
|
||||||
|
toggler={<Toggle onClick={handleToggle} checked={adminOnlyCanInvite} />}
|
||||||
|
></SettingBlock>
|
||||||
|
</ServerVersionChecker>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WhoCanInviteUsers;
|
||||||
@@ -18,6 +18,7 @@ import ServerMsgNotify from "./ServerMsgEmailNotify";
|
|||||||
import GuestMode from "./GuestMode";
|
import GuestMode from "./GuestMode";
|
||||||
import WhoCanSignUpSetting from "./WhoCanSignUpSetting";
|
import WhoCanSignUpSetting from "./WhoCanSignUpSetting";
|
||||||
import UserMsgEmailNotify from "./UserMsgEmailNotify";
|
import UserMsgEmailNotify from "./UserMsgEmailNotify";
|
||||||
|
import WhoCanInviteUsers from "./WhoCanInviteUsers";
|
||||||
|
|
||||||
export default function Overview() {
|
export default function Overview() {
|
||||||
const { t } = useTranslation("setting");
|
const { t } = useTranslation("setting");
|
||||||
@@ -42,6 +43,7 @@ export default function Overview() {
|
|||||||
{/* 只有 admin 能创建群组 */}
|
{/* 只有 admin 能创建群组 */}
|
||||||
|
|
||||||
<EnableURLPreviewInMsg />
|
<EnableURLPreviewInMsg />
|
||||||
|
<WhoCanInviteUsers />
|
||||||
<OnlyAdminCanSeeChannelMembers />
|
<OnlyAdminCanSeeChannelMembers />
|
||||||
{/* 访客模式 */}
|
{/* 访客模式 */}
|
||||||
<GuestMode />
|
<GuestMode />
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface SystemCommon {
|
|||||||
chat_layout_mode?: ChatLayout;
|
chat_layout_mode?: ChatLayout;
|
||||||
max_file_expiry_mode?: MessageExpireMode;
|
max_file_expiry_mode?: MessageExpireMode;
|
||||||
only_admin_can_create_group: boolean;
|
only_admin_can_create_group: boolean;
|
||||||
|
who_can_invite_users: boolean;
|
||||||
ext_setting: null | string;
|
ext_setting: null | string;
|
||||||
msg_smtp_notify_enable: boolean;
|
msg_smtp_notify_enable: boolean;
|
||||||
msg_smtp_notify_delay_seconds: number;
|
msg_smtp_notify_delay_seconds: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user