mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-25 16:40:58 -05:00
Use loading="lazy" in more places.
i18n invites page. Polish the bans page.
This commit is contained in:
parent
ca975aae7b
commit
dbaf246c27
15 changed files with 142 additions and 41 deletions
2
external/lang
vendored
2
external/lang
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit b70b4f395caf4dc4911c5ccbf7188de198875173
|
Subproject commit b40f8ce53831a590c0ffdd02f8da9fd35b7a3701
|
|
@ -67,6 +67,7 @@ export default function AgeGate(props: Props) {
|
||||||
return (
|
return (
|
||||||
<Base>
|
<Base>
|
||||||
<img
|
<img
|
||||||
|
loading="eager"
|
||||||
src={"https://static.revolt.chat/emoji/mutant/26a0.svg"}
|
src={"https://static.revolt.chat/emoji/mutant/26a0.svg"}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -55,6 +55,7 @@ export default function Emoji({
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
alt={emoji}
|
alt={emoji}
|
||||||
|
loading="lazy"
|
||||||
className="emoji"
|
className="emoji"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
src={parseEmoji(emoji)}
|
src={parseEmoji(emoji)}
|
||||||
|
@ -66,7 +67,7 @@ export default function Emoji({
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateEmoji(emoji: string) {
|
export function generateEmoji(emoji: string) {
|
||||||
return `<img class="emoji" draggable="false" alt="${emoji}" src="${parseEmoji(
|
return `<img loading="lazy" class="emoji" draggable="false" alt="${emoji}" src="${parseEmoji(
|
||||||
emoji,
|
emoji,
|
||||||
)}" />`;
|
)}" />`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ function FileEntry({
|
||||||
return (
|
return (
|
||||||
<Entry className={index >= CAN_UPLOAD_AT_ONCE ? "fade" : ""}>
|
<Entry className={index >= CAN_UPLOAD_AT_ONCE ? "fade" : ""}>
|
||||||
<PreviewBox onClick={remove}>
|
<PreviewBox onClick={remove}>
|
||||||
<img class="icon" src={url} alt={file.name} />
|
<img class="icon" src={url} alt={file.name} loading="eager" />
|
||||||
<div class="overlay">
|
<div class="overlay">
|
||||||
<XCircle size={36} />
|
<XCircle size={36} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -97,6 +97,7 @@ export function TypingIndicator({ typing }: Props) {
|
||||||
<div className="avatars">
|
<div className="avatars">
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<img
|
<img
|
||||||
|
loading="eager"
|
||||||
src={client.users.getAvatarURL(
|
src={client.users.getAvatarURL(
|
||||||
user._id,
|
user._id,
|
||||||
{ max_side: 256 },
|
{ max_side: 256 },
|
||||||
|
|
|
@ -91,6 +91,7 @@ export default function Embed({ embed }: Props) {
|
||||||
<div className={styles.siteinfo}>
|
<div className={styles.siteinfo}>
|
||||||
{embed.icon_url && (
|
{embed.icon_url && (
|
||||||
<img
|
<img
|
||||||
|
loading="lazy"
|
||||||
className={styles.favicon}
|
className={styles.favicon}
|
||||||
src={client.proxyFile(embed.icon_url)}
|
src={client.proxyFile(embed.icon_url)}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
|
|
|
@ -143,7 +143,9 @@ function HomeSidebar(props: Props) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{channelsArr.length === 0 && <img src={placeholderSVG} />}
|
{channelsArr.length === 0 && (
|
||||||
|
<img src={placeholderSVG} loading="eager" />
|
||||||
|
)}
|
||||||
{channelsArr.map((x) => {
|
{channelsArr.map((x) => {
|
||||||
let user;
|
let user;
|
||||||
if (x.channel_type === "DirectMessage") {
|
if (x.channel_type === "DirectMessage") {
|
||||||
|
|
|
@ -144,7 +144,9 @@ export function GroupMemberSidebar({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}>
|
}>
|
||||||
{members.length === 0 && <img src={placeholderSVG} />}
|
{members.length === 0 && (
|
||||||
|
<img src={placeholderSVG} loading="eager" />
|
||||||
|
)}
|
||||||
{members.map(
|
{members.map(
|
||||||
(user) =>
|
(user) =>
|
||||||
user && (
|
user && (
|
||||||
|
@ -257,7 +259,9 @@ export function ServerMemberSidebar({
|
||||||
{users.length}
|
{users.length}
|
||||||
</span>
|
</span>
|
||||||
}>
|
}>
|
||||||
{users.length === 0 && <img src={placeholderSVG} />}
|
{users.length === 0 && (
|
||||||
|
<img src={placeholderSVG} loading="eager" />
|
||||||
|
)}
|
||||||
{users.map(
|
{users.map(
|
||||||
(user) =>
|
(user) =>
|
||||||
user && (
|
user && (
|
||||||
|
|
|
@ -40,7 +40,7 @@ export function OnboardingModal({ onClose, callback }: Props) {
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<h1>
|
<h1>
|
||||||
<Text id="app.special.modals.onboarding.welcome" />
|
<Text id="app.special.modals.onboarding.welcome" />
|
||||||
<img src={wideSVG} />
|
<img src={wideSVG} loading="eager" />
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.form}>
|
<div className={styles.form}>
|
||||||
|
|
|
@ -37,6 +37,7 @@ export function ImageViewer({ attachment, embed, onClose }: Props) {
|
||||||
{attachment && (
|
{attachment && (
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
|
loading="eager"
|
||||||
src={client.generateFileURL(attachment)}
|
src={client.generateFileURL(attachment)}
|
||||||
width={(attachment.metadata as ImageMetadata).width}
|
width={(attachment.metadata as ImageMetadata).width}
|
||||||
height={
|
height={
|
||||||
|
@ -49,6 +50,7 @@ export function ImageViewer({ attachment, embed, onClose }: Props) {
|
||||||
{embed && (
|
{embed && (
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
|
loading="eager"
|
||||||
src={client.proxyFile(embed.url)}
|
src={client.proxyFile(embed.url)}
|
||||||
width={embed.width}
|
width={embed.width}
|
||||||
height={embed.height}
|
height={embed.height}
|
||||||
|
|
|
@ -3,10 +3,9 @@ import { Client, PermissionCalculator } from "revolt.js";
|
||||||
import { Channels, Servers, Users } from "revolt.js/dist/api/objects";
|
import { Channels, Servers, Users } from "revolt.js/dist/api/objects";
|
||||||
import Collection from "revolt.js/dist/maps/Collection";
|
import Collection from "revolt.js/dist/maps/Collection";
|
||||||
|
|
||||||
import { useCallback, useContext, useEffect, useState } from "preact/hooks";
|
import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
//#region Hooks v1
|
//#region Hooks v1
|
||||||
// ! Hooks v1 will be deprecated soon.
|
|
||||||
import { AppContext } from "./RevoltClient";
|
import { AppContext } from "./RevoltClient";
|
||||||
|
|
||||||
export interface HookContext {
|
export interface HookContext {
|
||||||
|
|
|
@ -97,6 +97,7 @@ export function Component(props: Props) {
|
||||||
<div className={styles.themes}>
|
<div className={styles.themes}>
|
||||||
<div className={styles.theme}>
|
<div className={styles.theme}>
|
||||||
<img
|
<img
|
||||||
|
loading="eager"
|
||||||
src={lightSVG}
|
src={lightSVG}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
data-active={selected === "light"}
|
data-active={selected === "light"}
|
||||||
|
@ -104,7 +105,7 @@ export function Component(props: Props) {
|
||||||
selected !== "light" &&
|
selected !== "light" &&
|
||||||
setTheme({ preset: "light" })
|
setTheme({ preset: "light" })
|
||||||
}
|
}
|
||||||
onContextMenu={e => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
/>
|
/>
|
||||||
<h4>
|
<h4>
|
||||||
<Text id="app.settings.pages.appearance.color.light" />
|
<Text id="app.settings.pages.appearance.color.light" />
|
||||||
|
@ -112,13 +113,14 @@ export function Component(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.theme}>
|
<div className={styles.theme}>
|
||||||
<img
|
<img
|
||||||
|
loading="eager"
|
||||||
src={darkSVG}
|
src={darkSVG}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
data-active={selected === "dark"}
|
data-active={selected === "dark"}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
selected !== "dark" && setTheme({ preset: "dark" })
|
selected !== "dark" && setTheme({ preset: "dark" })
|
||||||
}
|
}
|
||||||
onContextMenu={e => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
/>
|
/>
|
||||||
<h4>
|
<h4>
|
||||||
<Text id="app.settings.pages.appearance.color.dark" />
|
<Text id="app.settings.pages.appearance.color.dark" />
|
||||||
|
@ -202,7 +204,12 @@ export function Component(props: Props) {
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={() => setEmojiPack("mutant")}
|
onClick={() => setEmojiPack("mutant")}
|
||||||
data-active={emojiPack === "mutant"}>
|
data-active={emojiPack === "mutant"}>
|
||||||
<img src={mutantSVG} draggable={false} onContextMenu={e => e.preventDefault()} />
|
<img
|
||||||
|
loading="eager"
|
||||||
|
src={mutantSVG}
|
||||||
|
draggable={false}
|
||||||
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h4>
|
<h4>
|
||||||
Mutant Remix{" "}
|
Mutant Remix{" "}
|
||||||
|
@ -219,7 +226,12 @@ export function Component(props: Props) {
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={() => setEmojiPack("twemoji")}
|
onClick={() => setEmojiPack("twemoji")}
|
||||||
data-active={emojiPack === "twemoji"}>
|
data-active={emojiPack === "twemoji"}>
|
||||||
<img src={twemojiSVG} draggable={false} onContextMenu={e => e.preventDefault()} />
|
<img
|
||||||
|
loading="eager"
|
||||||
|
src={twemojiSVG}
|
||||||
|
draggable={false}
|
||||||
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h4>Twemoji</h4>
|
<h4>Twemoji</h4>
|
||||||
</div>
|
</div>
|
||||||
|
@ -230,7 +242,12 @@ export function Component(props: Props) {
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={() => setEmojiPack("openmoji")}
|
onClick={() => setEmojiPack("openmoji")}
|
||||||
data-active={emojiPack === "openmoji"}>
|
data-active={emojiPack === "openmoji"}>
|
||||||
<img src={openmojiSVG} draggable={false} onContextMenu={e => e.preventDefault()} />
|
<img
|
||||||
|
loading="eager"
|
||||||
|
src={openmojiSVG}
|
||||||
|
draggable={false}
|
||||||
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h4>Openmoji</h4>
|
<h4>Openmoji</h4>
|
||||||
</div>
|
</div>
|
||||||
|
@ -239,7 +256,12 @@ export function Component(props: Props) {
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={() => setEmojiPack("noto")}
|
onClick={() => setEmojiPack("noto")}
|
||||||
data-active={emojiPack === "noto"}>
|
data-active={emojiPack === "noto"}>
|
||||||
<img src={notoSVG} draggable={false} onContextMenu={e => e.preventDefault()} />
|
<img
|
||||||
|
loading="eager"
|
||||||
|
src={notoSVG}
|
||||||
|
draggable={false}
|
||||||
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h4>Noto Emoji</h4>
|
<h4>Noto Emoji</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import { Servers } from "revolt.js/dist/api/objects";
|
import { XCircle } from "@styled-icons/boxicons-regular";
|
||||||
|
import { Servers, Users } from "revolt.js/dist/api/objects";
|
||||||
|
|
||||||
|
import styles from "./Panes.module.scss";
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
import { useContext, useEffect, useState } from "preact/hooks";
|
import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import Tip from "../../../components/ui/Tip";
|
import UserIcon from "../../../components/common/user/UserIcon";
|
||||||
|
import IconButton from "../../../components/ui/IconButton";
|
||||||
|
import Preloader from "../../../components/ui/Preloader";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
server: Servers.Server;
|
server: Servers.Server;
|
||||||
|
@ -12,26 +17,71 @@ interface Props {
|
||||||
|
|
||||||
export function Bans({ server }: Props) {
|
export function Bans({ server }: Props) {
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const [bans, setBans] = useState<Servers.Ban[] | undefined>(undefined);
|
const [deleting, setDelete] = useState<string[]>([]);
|
||||||
|
const [data, setData] = useState<
|
||||||
|
| {
|
||||||
|
users: Pick<Users.User, "_id" | "username" | "avatar">[];
|
||||||
|
bans: Servers.Ban[];
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
client.servers.fetchBans(server._id).then((bans) => setBans(bans));
|
client.servers.fetchBans(server._id).then(setData as any);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={styles.userList}>
|
||||||
<Tip warning>This section is under construction.</Tip>
|
<div className={styles.subtitle}>
|
||||||
{bans?.map((x) => (
|
<span>
|
||||||
<div>
|
<Text id="app.settings.server_pages.bans.user" />
|
||||||
{x._id.user}: {x.reason ?? "no reason"}{" "}
|
</span>
|
||||||
<button
|
<span class={styles.reason}>
|
||||||
onClick={() =>
|
<Text id="app.settings.server_pages.bans.reason" />
|
||||||
client.servers.unbanUser(server._id, x._id.user)
|
</span>
|
||||||
}>
|
<span>
|
||||||
unban
|
<Text id="app.settings.server_pages.bans.revoke" />
|
||||||
</button>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
{typeof data === "undefined" && <Preloader type="ring" />}
|
||||||
|
{data?.bans.map((x) => {
|
||||||
|
let user = data.users.find((y) => y._id === x._id.user);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.ban}
|
||||||
|
data-deleting={deleting.indexOf(x._id.user) > -1}>
|
||||||
|
<span>
|
||||||
|
<UserIcon attachment={user?.avatar} size={24} />
|
||||||
|
{user?.username}
|
||||||
|
</span>
|
||||||
|
<div className={styles.reason}>
|
||||||
|
{x.reason ?? (
|
||||||
|
<Text id="app.settings.server_pages.bans.no_reason" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<IconButton
|
||||||
|
onClick={async () => {
|
||||||
|
setDelete([...deleting, x._id.user]);
|
||||||
|
|
||||||
|
await client.servers.unbanUser(
|
||||||
|
server._id,
|
||||||
|
x._id.user,
|
||||||
|
);
|
||||||
|
|
||||||
|
setData({
|
||||||
|
...data,
|
||||||
|
bans: data.bans.filter(
|
||||||
|
(y) => y._id.user !== x._id.user,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={deleting.indexOf(x._id.user) > -1}>
|
||||||
|
<XCircle size={24} />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,12 +37,20 @@ export function Invites({ server }: Props) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.invites}>
|
<div className={styles.userList}>
|
||||||
<div className={styles.subtitle}>
|
<div className={styles.subtitle}>
|
||||||
<span>Invite Code</span>
|
<span>
|
||||||
<span>Invitor</span>
|
<Text id="app.settings.server_pages.invites.code" />
|
||||||
<span>Channel</span>
|
</span>
|
||||||
<span>Revoke</span>
|
<span>
|
||||||
|
<Text id="app.settings.server_pages.invites.invitor" />
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<Text id="app.settings.server_pages.invites.channel" />
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<Text id="app.settings.server_pages.invites.revoke" />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{typeof invites === "undefined" && <Preloader type="ring" />}
|
{typeof invites === "undefined" && <Preloader type="ring" />}
|
||||||
{invites?.map((invite) => {
|
{invites?.map((invite) => {
|
||||||
|
|
|
@ -17,21 +17,31 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.invites {
|
.userList {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
|
gap: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--secondary-foreground);
|
color: var(--secondary-foreground);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
||||||
|
.reason {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.invite {
|
.reason {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite,
|
||||||
|
.ban {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -39,8 +49,8 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
background: var(--secondary-background);
|
background: var(--secondary-background);
|
||||||
|
|
||||||
code,
|
span,
|
||||||
span {
|
code {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue