chore: updates

This commit is contained in:
Tristan Yang
2024-09-14 18:26:27 +08:00
parent be4ef8585d
commit 51dd4c1739
9 changed files with 791 additions and 697 deletions
+19 -19
View File
@@ -1,6 +1,6 @@
{ {
"name": "vocechat-web", "name": "vocechat-web",
"version": "0.7.36", "version": "0.7.38",
"homepage": "https://voce.chat", "homepage": "https://voce.chat",
"dependencies": { "dependencies": {
"@metamask/onboarding": "^1.0.1", "@metamask/onboarding": "^1.0.1",
@@ -31,16 +31,16 @@
"copy-image-clipboard": "^2.1.2", "copy-image-clipboard": "^2.1.2",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0", "css-minimizer-webpack-plugin": "^7.0.0",
"dayjs": "^1.11.12", "dayjs": "^1.11.13",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"firebase": "^10.13.0", "firebase": "^10.13.1",
"framer-motion": "11.3.28", "framer-motion": "11.5.4",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"heic2any": "^0.0.4", "heic2any": "^0.0.4",
"html-webpack-plugin": "^5.6.0", "html-webpack-plugin": "^5.6.0",
"i18next": "^23.13.0", "i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2", "i18next-http-backend": "^2.6.1",
"linkify-plugin-mention": "^4.1.3", "linkify-plugin-mention": "^4.1.3",
"linkify-react": "^4.1.3", "linkify-react": "^4.1.3",
"linkifyjs": "^4.1.3", "linkifyjs": "^4.1.3",
@@ -48,9 +48,9 @@
"localforage-setitems": "^1.4.0", "localforage-setitems": "^1.4.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"masonry-layout": "^4.2.2", "masonry-layout": "^4.2.2",
"mini-css-extract-plugin": "^2.9.0", "mini-css-extract-plugin": "^2.9.1",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"qrcode.react": "^3.1.0", "qrcode.react": "^4.0.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dev-utils": "^12.0.1", "react-dev-utils": "^12.0.1",
"react-dnd": "16.0.1", "react-dnd": "16.0.1",
@@ -62,12 +62,12 @@
"react-i18next": "^15.0.1", "react-i18next": "^15.0.1",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-refresh": "0.14.2", "react-refresh": "0.14.2",
"react-router-dom": "^6.26.1", "react-router-dom": "^6.26.2",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
"react-use-wizard": "^2.3.0", "react-use-wizard": "^2.3.0",
"react-viewport-list": "^7.1.2", "react-viewport-list": "^7.1.2",
"react-virtuoso": "^4.10.1", "react-virtuoso": "^4.10.4",
"rooks": "^7.14.1", "rooks": "^7.14.1",
"slate": "^0.103.0", "slate": "^0.103.0",
"slate-history": "^0.100.0", "slate-history": "^0.100.0",
@@ -78,9 +78,9 @@
"tailwind-merge": "2.5.2", "tailwind-merge": "2.5.2",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"typescript": "^5.5.4", "typescript": "^5.6.2",
"wavesurfer.js": "^7.8.4", "wavesurfer.js": "^7.8.6",
"webpack": "^5.93.0", "webpack": "^5.94.0",
"webpack-dev-server": "^4.15.1", "webpack-dev-server": "^4.15.1",
"webpack-manifest-plugin": "^5.0.0", "webpack-manifest-plugin": "^5.0.0",
"workbox-core": "^7.1.0", "workbox-core": "^7.1.0",
@@ -119,8 +119,8 @@
"@ianvs/prettier-plugin-sort-imports": "^4.3.1", "@ianvs/prettier-plugin-sort-imports": "^4.3.1",
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.7",
"@types/masonry-layout": "^4.2.8", "@types/masonry-layout": "^4.2.8",
"@types/node": "^22.4.0", "@types/node": "^22.5.5",
"@types/react": "18.3.3", "@types/react": "18.3.5",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
@@ -131,18 +131,18 @@
"babel-plugin-named-asset-import": "^0.3.8", "babel-plugin-named-asset-import": "^0.3.8",
"babel-preset-react-app": "^10.0.1", "babel-preset-react-app": "^10.0.1",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.30.0",
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.35.0", "eslint-plugin-react": "^7.36.1",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"gh-pages": "^6.1.1", "gh-pages": "^6.1.1",
"md5-file": "^5.0.0", "md5-file": "^5.0.0",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"postcss": "^8.4.41", "postcss": "^8.4.45",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"postinstall-postinstall": "^2.1.0", "postinstall-postinstall": "^2.1.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.11",
"webpack-bundle-analyzer": "^4.10.2" "webpack-bundle-analyzer": "^4.10.2"
} }
} }
+659 -661
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -67,6 +67,12 @@
"enable": "Enable", "enable": "Enable",
"disable": "Disable" "disable": "Disable"
}, },
"admin_see_group_members": {
"title": "Only Admins Can See Group Members",
"desc": "If enabled, only admins can see group members.",
"enable": "Enable",
"disable": "Disable"
},
"guest_mode": { "guest_mode": {
"title": "Guest Mode", "title": "Guest Mode",
"desc": "If enabled, visitors will see public channels on this server.", "desc": "If enabled, visitors will see public channels on this server.",
+1 -1
View File
@@ -185,5 +185,5 @@ export const getInviteLinkTimesList = () => [
value: 100 value: 100
} }
]; ];
export const KEY_ADMIN_SEE_CHANNEL_MEMBERS = `only_admin_can_see_channel_members`;
export default BASE_URL; export default BASE_URL;
+3 -3
View File
@@ -1,4 +1,4 @@
import QR from "qrcode.react"; import { QRCodeSVG } from "qrcode.react";
import { useAppSelector } from "@/app/store"; import { useAppSelector } from "@/app/store";
import { shallowEqual } from "react-redux"; import { shallowEqual } from "react-redux";
@@ -13,14 +13,14 @@ const QRCode = ({ link, size = 512, level = "L" }: Props) => {
const logo = useAppSelector((store) => store.server.logo, shallowEqual); const logo = useAppSelector((store) => store.server.logo, shallowEqual);
return ( return (
<div className="p-2 bg-white dark:bg-slate-200 rounded"> <div className="p-2 bg-white dark:bg-slate-200 rounded">
<QR <QRCodeSVG
renderAs="svg"
value={link} value={link}
className="rounded border border-solid border-gray-200 dark:border-none !w-full !h-full" className="rounded border border-solid border-gray-200 dark:border-none !w-full !h-full"
size={size} size={size}
bgColor={"#fff"} bgColor={"#fff"}
fgColor={"#000"} fgColor={"#000"}
level={level} level={level}
marginSize={2}
// includeMargin={true} // includeMargin={true}
imageSettings={{ imageSettings={{
src: logo, src: logo,
+26 -13
View File
@@ -19,6 +19,8 @@ import VoiceChat from "../VoiceChat";
import Dashboard from "../VoiceChat/Dashboard"; import Dashboard from "../VoiceChat/Dashboard";
import Members from "./Members"; import Members from "./Members";
import PinList from "./PinList"; import PinList from "./PinList";
import { getJSONField } from "@/utils";
import { KEY_ADMIN_SEE_CHANNEL_MEMBERS } from "@/app/config";
type Props = { type Props = {
cid?: number; cid?: number;
@@ -29,13 +31,17 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
const { pathname } = useLocation(); const { pathname } = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const serverExtSetting = useAppSelector(
(store) => store.server.ext_setting ?? "{}",
shallowEqual
);
const loginUser = useAppSelector((store) => store.authData.user, shallowEqual); const loginUser = useAppSelector((store) => store.authData.user, shallowEqual);
const visibleAside = useAppSelector((store) => store.footprint.channelAsides[cid], shallowEqual); const visibleAside = useAppSelector((store) => store.footprint.channelAsides[cid], shallowEqual);
const userIds = useAppSelector((store) => store.users.ids, shallowEqual); const userIds = useAppSelector((store) => store.users.ids, shallowEqual);
const data = useAppSelector((store) => store.channels.byId[cid], shallowEqual); const data = useAppSelector((store) => store.channels.byId[cid], shallowEqual);
useEffect(() => { useEffect(() => {
if (!data) { if (!data) {
// channel不存在了 回首页 // channel 不存在了 回首页
navigate("/chat"); navigate("/chat");
} }
}, [data]); }, [data]);
@@ -61,6 +67,9 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
const addVisible = loginUser?.is_admin || owner == loginUser?.uid; const addVisible = loginUser?.is_admin || owner == loginUser?.uid;
const pinCount = data?.pinned_messages?.length || 0; const pinCount = data?.pinned_messages?.length || 0;
const toolClass = `relative cursor-pointer hidden md:block`; const toolClass = `relative cursor-pointer hidden md:block`;
const onlyAdminCanSeeMembers =
getJSONField(serverExtSetting, KEY_ADMIN_SEE_CHANNEL_MEMBERS) ?? false;
const canViewMembers = loginUser?.is_admin ? true : onlyAdminCanSeeMembers;
return ( return (
<Layout <Layout
to={cid} to={cid}
@@ -102,11 +111,13 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
</li> </li>
</Tippy> </Tippy>
</Tooltip> </Tooltip>
<li className={`${toolClass}`} onClick={toggleMembersVisible}> {canViewMembers && (
<Tooltip tip={t("channel_members")} placement="left"> <li className={`${toolClass}`} onClick={toggleMembersVisible}>
<IconPeople className={visibleAside == "members" ? "fill-gray-600" : ""} /> <Tooltip tip={t("channel_members")} placement="left">
</Tooltip> <IconPeople className={visibleAside == "members" ? "fill-gray-600" : ""} />
</li> </Tooltip>
</li>
)}
</ul> </ul>
} }
header={ header={
@@ -125,13 +136,15 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
</header> </header>
} }
users={ users={
<Members canViewMembers ? (
uids={memberIds} <Members
addVisible={addVisible} uids={memberIds}
cid={cid} addVisible={addVisible}
ownerId={owner} cid={cid}
membersVisible={visibleAside == "members"} ownerId={owner}
/> membersVisible={visibleAside == "members"}
/>
) : null
} }
voice={<Dashboard visible={visibleAside == "voice"} id={cid} context="channel" />} voice={<Dashboard visible={visibleAside == "voice"} id={cid} context="channel" />}
/> />
@@ -0,0 +1,58 @@
import { useAppSelector } from "@/app/store";
import SettingBlock from "@/components/SettingBlock";
import { useTranslation } from "react-i18next";
import StyledRadio from "@/components/styled/Radio";
import { useGetSystemCommonQuery, useUpdateSystemCommonMutation } from "@/app/services/server";
import { useEffect } from "react";
import toast from "react-hot-toast";
import { shallowEqual } from "react-redux";
import ServerVersionChecker from "@/components/ServerVersionChecker";
import { getJSONField, upsertJSON } from "@/utils";
import { KEY_ADMIN_SEE_CHANNEL_MEMBERS } from "@/app/config";
type Props = {};
const OnlyAdminCanSeeChannelMembers = ({}: Props) => {
const { t } = useTranslation("setting");
const { t: ct } = useTranslation();
const { refetch } = useGetSystemCommonQuery();
const serverExtSetting = useAppSelector(
(store) => store.server.ext_setting ?? "{}",
shallowEqual
);
const [updateSetting, { isSuccess }] = useUpdateSystemCommonMutation();
useEffect(() => {
if (isSuccess) {
refetch();
toast.success(ct("tip.update"));
}
}, [isSuccess]);
const handleChange = (newVal: boolean) => {
const json = upsertJSON(serverExtSetting, { only_admin_can_see_channel_members: newVal });
updateSetting({ ext_setting: json });
};
const onlyAdminSeeChannelMembers =
getJSONField(serverExtSetting, KEY_ADMIN_SEE_CHANNEL_MEMBERS) ?? false;
return (
<ServerVersionChecker empty version="0.3.50">
<SettingBlock
title={t("overview.admin_see_group_members.title")}
desc={t("overview.admin_see_group_members.desc")}
>
<StyledRadio
options={[
t("overview.admin_see_group_members.enable"),
t("overview.admin_see_group_members.disable")
]}
values={["true", "false"]}
value={`${onlyAdminSeeChannelMembers}`}
onChange={(v) => {
handleChange(v == "true");
}}
/>
</SettingBlock>
</ServerVersionChecker>
);
};
export default OnlyAdminCanSeeChannelMembers;
+2
View File
@@ -16,6 +16,7 @@ import MessageSound from "./MessageSound";
import Server from "./Server"; import Server from "./Server";
import { shallowEqual } from "react-redux"; import { shallowEqual } from "react-redux";
import OnlyAdminCreateGroup from "./OnlyAdminCreateGroup"; import OnlyAdminCreateGroup from "./OnlyAdminCreateGroup";
import OnlyAdminCanSeeChannelMembers from "./OnlyAdminSeeChannelMembers";
export default function Overview() { export default function Overview() {
const { t } = useTranslation("setting"); const { t } = useTranslation("setting");
@@ -57,6 +58,7 @@ export default function Overview() {
{/* 只有 admin 能创建群组 */} {/* 只有 admin 能创建群组 */}
<OnlyAdminCreateGroup /> <OnlyAdminCreateGroup />
<OnlyAdminCanSeeChannelMembers />
{/* 访客模式 */} {/* 访客模式 */}
<SettingBlock title={t("overview.guest_mode.title")} desc={t("overview.guest_mode.desc")}> <SettingBlock title={t("overview.guest_mode.title")} desc={t("overview.guest_mode.desc")}>
<StyledRadio <StyledRadio
+17
View File
@@ -507,6 +507,23 @@ export const playMessageSound = async () => {
console.warn("play join sound failed!", error); console.warn("play join sound failed!", error);
} }
}; };
export const getJSONField = (json: string | null, field: string) => {
try {
const obj = json ? JSON.parse(json) : {};
return obj[field];
} catch (error) {
return undefined;
}
};
export const upsertJSON = (json: string | null, obj: object) => {
try {
const jsonObj = json ? JSON.parse(json) : {};
const result = Object.assign({}, jsonObj, obj);
return JSON.stringify(result);
} catch (error) {
return JSON.stringify(obj);
}
};
export const isInIframe = () => window.location !== window.parent.location; export const isInIframe = () => window.location !== window.parent.location;
export const encodeBase64 = (str = "") => btoa(unescape(encodeURIComponent(str))); export const encodeBase64 = (str = "") => btoa(unescape(encodeURIComponent(str)));
export const shouldPreviewImage = (type: string) => { export const shouldPreviewImage = (type: string) => {