feat: extract link from server description

This commit is contained in:
Tristan Yang
2023-03-20 21:36:34 +08:00
parent abbe5fbfea
commit 797ae0ff91
3 changed files with 142 additions and 6 deletions
+28 -5
View File
@@ -1,13 +1,17 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { useTranslation } from "react-i18next";
import { NavLink } from "react-router-dom";
import Linkify from "linkify-react";
import ChannelModal from "./ChannelModal"; import ChannelModal from "./ChannelModal";
import InviteModal from "./InviteModal"; import InviteModal from "./InviteModal";
import IconEdit from "../../assets/icons/edit.svg";
import IconChat from "../../assets/icons/placeholder.chat.svg"; import IconChat from "../../assets/icons/placeholder.chat.svg";
import IconAsk from "../../assets/icons/placeholder.question.svg"; import IconAsk from "../../assets/icons/placeholder.question.svg";
import IconInvite from "../../assets/icons/placeholder.invite.svg"; import IconInvite from "../../assets/icons/placeholder.invite.svg";
import IconDownload from "../../assets/icons/placeholder.download.svg"; import IconDownload from "../../assets/icons/placeholder.download.svg";
import UsersModal from "./UsersModal"; import UsersModal from "./UsersModal";
import { useAppSelector } from "../../app/store"; import { useAppSelector } from "../../app/store";
import { useTranslation } from "react-i18next";
interface Props { interface Props {
@@ -20,7 +24,7 @@ const classes = {
}; };
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => { const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
const { t } = useTranslation("welcome"); const { t } = useTranslation("welcome");
const server = useAppSelector((store) => store.server); const { server, isAdmin } = useAppSelector((store) => { return { server: store.server, isAdmin: store.authData.user?.is_admin }; });
const [inviteModalVisible, setInviteModalVisible] = useState(false); const [inviteModalVisible, setInviteModalVisible] = useState(false);
const [createChannelVisible, setCreateChannelVisible] = useState(false); const [createChannelVisible, setCreateChannelVisible] = useState(false);
const [userListVisible, setUserListVisible] = useState(false); const [userListVisible, setUserListVisible] = useState(false);
@@ -39,10 +43,29 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
return ( return (
<> <>
<div className="flex flex-col gap-8 -mt-[50px] dark:bg-gray-700"> <div className="flex flex-col gap-8 -mt-[50px] dark:bg-gray-700">
<div className="flex flex-col gap-2 items-center"> <div className="flex flex-col gap-2 items-center group">
<h2 className="text-center text-3xl text-slate-700 dark:text-white font-bold">{t("title", { name: server.name })}</h2> <h2 className="text-center text-3xl text-slate-700 dark:text-white font-bold">{t("title", { name: server.name })}</h2>
<p className="text-sm text-gray-400 max-w-[424px] text-center"> <p className="text-sm text-gray-400 max-w-md text-center relative break-all">
{t("desc")} <Linkify options={
{
render: {
url: ({ content, attributes: { href: link } }) => {
// console.log("attr", link);
// if (!url) return <>{content}</>;
return <>
<a className="text-primary-400" target="_blank" href={link} rel="noreferrer">
{content}
</a>
</>;
},
}
}
}>
{server.description ?? t("desc")}
</Linkify>
{isAdmin && <NavLink to={"/setting/overview"} className="absolute -top-6 -right-6 invisible group-hover:visible">
<IconEdit />
</NavLink>}
</p> </p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 grid-rows-2 gap-2 md:gap-6 m-auto"> <div className="grid grid-cols-1 md:grid-cols-2 grid-rows-2 gap-2 md:gap-6 m-auto">
+113
View File
@@ -0,0 +1,113 @@
import { ChangeEvent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import toast from "react-hot-toast";
import { useUpdateLogoMutation, useUpdateServerMutation } from '../../../app/services/server';
import LogoUploader from "../../../common/component/AvatarUploader";
import Input from "../../../common/component/styled/Input";
import Label from "../../../common/component/styled/Label";
import Textarea from "../../../common/component/styled/Textarea";
import SaveTip from '../../../common/component/SaveTip';
import { useAppSelector } from '../../../app/store';
const Index = () => {
const { t } = useTranslation("setting");
const { t: ct } = useTranslation();
const { loginUser, server } = useAppSelector((store) => {
return { loginUser: store.authData.user, server: store.server };
});
const [uploadLogo, { isSuccess: uploadSuccess }] = useUpdateLogoMutation();
const [updateServer] = useUpdateServerMutation();
const [changed, setChanged] = useState(false);
const [serverValues, setServerValues] = useState<typeof server>(server);
const handleChange = (evt: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const newValue = evt.target.value;
const { type = "" } = evt.target.dataset;
setServerValues((prev) => {
return { ...prev, [type]: newValue };
});
};
const handleUpdateServer = () => {
const { name, description } = serverValues;
updateServer({ name, description });
};
const handleReset = () => {
setServerValues(server);
};
useEffect(() => {
if (server) {
setServerValues(server);
}
}, [server]);
useEffect(() => {
if (uploadSuccess) {
toast.success(ct("tip.update"));
}
}, [uploadSuccess]);
useEffect(() => {
if (server && serverValues) {
const { name, description } = serverValues;
const { name: oName, description: oDescription } = server;
if (oName !== name || oDescription !== description) {
setChanged(true);
} else {
setChanged(false);
}
}
}, [server, serverValues]);
const { name, description, logo } = serverValues;
const isAdmin = loginUser?.is_admin;
if (!loginUser || !serverValues) return null;
return (
<>
<div className="flex gap-4">
<div className="w-24 h-24">
<LogoUploader
disabled={!isAdmin}
url={uploadSuccess ? `${logo}?t=${+new Date()}` : logo}
name={name}
uploadImage={uploadLogo}
/>
</div>
{isAdmin && (
<div className="flex flex-col justify-between items-start">
<div className="text-sm text-gray-600 dark:text-gray-100">
{t("overview.upload_desc")}
</div>
</div>
)}
</div>
<div className="flex flex-col items-start gap-6 mb-16">
<div className="w-full flex flex-col items-start gap-2">
<Label className='dark:text-gray-100' htmlFor="name">{t("overview.name")}</Label>
<Input
disabled={!isAdmin}
data-type="name"
onChange={handleChange}
value={name}
name="name"
id="name"
placeholder="Server Name"
/>
</div>
<div className="w-full flex flex-col items-start gap-2">
<Label className='dark:text-gray-100' htmlFor="desc">{t("overview.desc")}</Label>
<Textarea
disabled={!isAdmin}
data-type="description"
onChange={handleChange}
value={description ?? ""}
rows={4}
name="name"
id="name"
placeholder="Tell the world a bit about this server"
/>
</div>
</div>
{changed && <SaveTip saveHandler={handleUpdateServer} resetHandler={handleReset} />}
</>
);
};
export default Index;
+1 -1
View File
@@ -4,7 +4,7 @@ import StyledRadio from "../../../common/component/styled/Radio";
import { useAppSelector } from "../../../app/store"; import { useAppSelector } from "../../../app/store";
import { LoginConfig, WhoCanSignUp } from "../../../types/server"; import { LoginConfig, WhoCanSignUp } from "../../../types/server";
import useConfig from "../../../common/hook/useConfig"; import useConfig from "../../../common/hook/useConfig";
import Server from './server'; import Server from './Server';
import Language from './Language'; import Language from './Language';
import FrontendURL from "./FrontendURL"; import FrontendURL from "./FrontendURL";
import DarkMode from "./DarkMode"; import DarkMode from "./DarkMode";