merge: branch 'quark/permissions'

This commit is contained in:
Paul Makles
2022-04-29 13:48:38 +01:00
117 changed files with 10609 additions and 6253 deletions

View File

@@ -1,6 +1,6 @@
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Channel } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";

View File

@@ -1,5 +1,4 @@
import { Channel } from "revolt.js/dist/maps/Channels";
import { User } from "revolt.js/dist/maps/Users";
import { Channel, User } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { StateUpdater, useState } from "preact/hooks";

View File

@@ -1,14 +1,15 @@
import { Hash, VolumeFull } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Channel } from "revolt.js";
import { useContext } from "preact/hooks";
import { AppContext } from "../../context/revoltjs/RevoltClient";
import { ImageIconBase, IconBaseProps } from "./IconBase";
import fallback from "./assets/group.png";
import { ImageIconBase, IconBaseProps } from "./IconBase";
interface Props extends IconBaseProps<Channel> {
isServerChannel?: boolean;
}
@@ -32,7 +33,7 @@ export default observer(
...imgProps
} = props;
const iconURL = client.generateFileURL(
target?.icon ?? attachment,
target?.icon ?? attachment ?? undefined,
{ max_side: 256 },
animate,
);

View File

@@ -1,4 +1,5 @@
import { Attachment } from "revolt-api/types/Autumn";
import { API } from "revolt.js";
import { Nullable } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Ref } from "preact";
@@ -6,7 +7,7 @@ import { Ref } from "preact";
export interface IconBaseProps<T> {
target?: T;
url?: string;
attachment?: Attachment;
attachment?: Nullable<API.File>;
size: number;
hover?: boolean;

View File

@@ -2,14 +2,11 @@ import { Check } from "@styled-icons/boxicons-regular";
import { Cog } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Link } from "react-router-dom";
import { ServerPermission } from "revolt.js/dist/api/permissions";
import { Server } from "revolt.js/dist/maps/Servers";
import { Server } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Text } from "preact-i18n";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
import IconButton from "../ui/IconButton";
import Tooltip from "./Tooltip";
@@ -125,7 +122,7 @@ export default observer(({ server }: Props) => {
</Tooltip>
) : undefined}
<div className="title">{server.name}</div>
{(server.permission & ServerPermission.ManageServer) > 0 && (
{server.havePermission("ManageServer") && (
<Link to={`/server/${server._id}/settings`}>
<IconButton>
<Cog size={20} />

View File

@@ -1,5 +1,5 @@
import { observer } from "mobx-react-lite";
import { Server } from "revolt.js/dist/maps/Servers";
import { Server } from "revolt.js";
import styled from "styled-components/macro";
import { useContext } from "preact/hooks";
@@ -39,7 +39,7 @@ export default observer(
const { target, attachment, size, animate, server_name, ...imgProps } =
props;
const iconURL = client.generateFileURL(
target?.icon ?? attachment,
target?.icon ?? attachment ?? undefined,
{ max_side: 256 },
animate,
);

View File

@@ -1,5 +1,5 @@
import { observer } from "mobx-react-lite";
import { Message as MessageObject } from "revolt.js/dist/maps/Messages";
import { Message as MessageObject } from "revolt.js";
import { useTriggerEvents } from "preact-context-menu";
import { memo } from "preact/compat";

View File

@@ -1,5 +1,5 @@
import { observer } from "mobx-react-lite";
import { Message } from "revolt.js/dist/maps/Messages";
import { Message } from "revolt.js";
import styled, { css, keyframes } from "styled-components/macro";
import { decodeTime } from "ulid";

View File

@@ -1,8 +1,16 @@
import { Send, ShieldX, HappyBeaming, Box } from "@styled-icons/boxicons-solid";
import { Send, ShieldX } from "@styled-icons/boxicons-solid";
import Axios, { CancelTokenSource } from "axios";
import Long from "long";
import { observer } from "mobx-react-lite";
import { ChannelPermission } from "revolt.js/dist/api/permissions";
import { Channel } from "revolt.js/dist/maps/Channels";
import {
Channel,
DEFAULT_PERMISSION_DIRECT_MESSAGE,
DEFAULT_PERMISSION_VIEW_ONLY,
Permission,
Server,
U32_MAX,
UserPermission,
} from "revolt.js";
import styled, { css } from "styled-components/macro";
import { ulid } from "ulid";
@@ -125,6 +133,11 @@ const FileAction = styled.div`
display: flex;
align-items: center;
justify-content: center;
}
`;
const ThisCodeWillBeReplacedAnywaysSoIMightAsWellJustDoItThisWay__Padding = styled.div`
width: 16px;
`;
// For sed replacement
@@ -150,7 +163,7 @@ export default observer(({ channel }: Props) => {
const renderer = getRenderer(channel);
if (!(channel.permission & ChannelPermission.SendMessage)) {
if (!channel.havePermission("SendMessage")) {
return (
<Base>
<Blocked>
@@ -231,7 +244,7 @@ export default observer(({ channel }: Props) => {
);
renderer.messages.reverse();
if (msg) {
if (msg?.content) {
// eslint-disable-next-line prefer-const
let [_, toReplace, newText, flags] = content.split(/\//);
@@ -493,7 +506,7 @@ export default observer(({ channel }: Props) => {
setReplies={setReplies}
/>
<Base>
{channel.permission & ChannelPermission.UploadFiles ? (
{channel.havePermission("UploadFiles") ? (
<FileAction>
<FileUploader
size={24}
@@ -530,7 +543,9 @@ export default observer(({ channel }: Props) => {
}}
/>
</FileAction>
) : undefined}
) : (
<ThisCodeWillBeReplacedAnywaysSoIMightAsWellJustDoItThisWay__Padding />
)}
<TextAreaAutoSize
autoFocus
hideBorder

View File

@@ -11,8 +11,7 @@ import {
MessageSquareEdit,
} from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { SystemMessage as SystemMessageI } from "revolt-api/types/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import { Message, API } from "revolt.js";
import styled from "styled-components/macro";
import { useTriggerEvents } from "preact-context-menu";
@@ -75,13 +74,11 @@ export const SystemMessage = observer(
({ attachContext, message, highlight, hideInfo }: Props) => {
const data = message.asSystemMessage;
const SystemMessageIcon =
iconDictionary[data.type as SystemMessageI["type"]] ?? InfoCircle;
iconDictionary[data.type as API.SystemMessage["type"]] ??
InfoCircle;
let children;
let children = null;
switch (data.type) {
case "text":
children = <span>{data.content}</span>;
break;
case "user_added":
case "user_remove":
children = (

View File

@@ -1,4 +1,4 @@
import { Attachment as AttachmentI } from "revolt-api/types/Autumn";
import { API } from "revolt.js";
import styles from "./Attachment.module.scss";
import classNames from "classnames";
@@ -14,7 +14,7 @@ import Spoiler from "./Spoiler";
import TextFile from "./TextFile";
interface Props {
attachment: AttachmentI;
attachment: API.File;
hasContent?: boolean;
}

View File

@@ -4,7 +4,7 @@ import {
Download,
} from "@styled-icons/boxicons-regular";
import { File, Video } from "@styled-icons/boxicons-solid";
import { Attachment } from "revolt-api/types/Autumn";
import { API } from "revolt.js";
import styles from "./AttachmentActions.module.scss";
import classNames from "classnames";
@@ -17,7 +17,7 @@ import { AppContext } from "../../../../context/revoltjs/RevoltClient";
import IconButton from "../../../ui/IconButton";
interface Props {
attachment: Attachment;
attachment: API.File;
}
export default function AttachmentActions({ attachment }: Props) {

View File

@@ -1,4 +1,4 @@
import { Attachment } from "revolt-api/types/Autumn";
import { API } from "revolt.js";
import styles from "./Attachment.module.scss";
import classNames from "classnames";
@@ -10,12 +10,12 @@ import { AppContext } from "../../../../context/revoltjs/RevoltClient";
enum ImageLoadingState {
Loading,
Loaded,
Error
Error,
}
type Props = JSX.HTMLAttributes<HTMLImageElement> & {
attachment: Attachment;
}
attachment: API.File;
};
export default function ImageFile({ attachment, ...props }: Props) {
const [loading, setLoading] = useState(ImageLoadingState.Loading);
@@ -23,25 +23,19 @@ export default function ImageFile({ attachment, ...props }: Props) {
const { openScreen } = useIntermediate();
const url = client.generateFileURL(attachment)!;
return <img
{...props}
src={url}
alt={attachment.filename}
loading="lazy"
className={classNames(styles.image, {
[styles.loading]: loading !== ImageLoadingState.Loaded
})}
onClick={() =>
openScreen({ id: "image_viewer", attachment })
}
onMouseDown={(ev) =>
ev.button === 1 && window.open(url, "_blank")
}
onLoad={() =>
setLoading(ImageLoadingState.Loaded)
}
onError={() =>
setLoading(ImageLoadingState.Error)
}
/>
return (
<img
{...props}
src={url}
alt={attachment.filename}
loading="lazy"
className={classNames(styles.image, {
[styles.loading]: loading !== ImageLoadingState.Loaded,
})}
onClick={() => openScreen({ id: "image_viewer", attachment })}
onMouseDown={(ev) => ev.button === 1 && window.open(url, "_blank")}
onLoad={() => setLoading(ImageLoadingState.Loaded)}
onError={() => setLoading(ImageLoadingState.Error)}
/>
);
}

View File

@@ -2,9 +2,7 @@ import { Reply } from "@styled-icons/boxicons-regular";
import { File } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { RelationshipStatus } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import { Channel, Message, API } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Text } from "preact-i18n";
@@ -174,7 +172,7 @@ export const MessageReply = observer(
<ReplyBase head={index === 0}>
{/*<Reply size={16} />*/}
{message.author?.relationship === RelationshipStatus.Blocked ? (
{message.author?.relationship === "Blocked" ? (
<Text id="app.main.channel.misc.blocked_user" />
) : (
<>

View File

@@ -1,5 +1,5 @@
import axios from "axios";
import { Attachment } from "revolt-api/types/Autumn";
import { API } from "revolt.js";
import styles from "./Attachment.module.scss";
import { useContext, useEffect, useState } from "preact/hooks";
@@ -13,7 +13,7 @@ import {
import Preloader from "../../../ui/Preloader";
interface Props {
attachment: Attachment;
attachment: API.File;
}
const fileCache: { [key: string]: string } = {};

View File

@@ -1,6 +1,6 @@
import { DownArrowAlt } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Channel } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Text } from "preact-i18n";

View File

@@ -7,8 +7,8 @@ import {
Notification,
} from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { ChannelPermission } from "revolt.js";
import { Message as MessageObject } from "revolt.js/dist/maps/Messages";
import { Permission } from "revolt.js";
import { Message as MessageObject } from "revolt.js";
import styled from "styled-components";
import { openContextMenu } from "preact-context-menu";
@@ -131,8 +131,7 @@ export const MessageOverlayBar = observer(({ message, queued }: Props) => {
)}
{isAuthor ||
(message.channel &&
message.channel.permission &
ChannelPermission.ManageMessages) ? (
message.channel.havePermission("ManageMessages")) ? (
<Tooltip content="Delete">
<Entry
onClick={(e) =>

View File

@@ -1,7 +1,7 @@
import { UpArrowAlt } from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Channel } from "revolt.js";
import { decodeTime } from "ulid";
import { Text } from "preact-i18n";

View File

@@ -1,8 +1,7 @@
import { At, Reply as ReplyIcon } from "@styled-icons/boxicons-regular";
import { At } from "@styled-icons/boxicons-regular";
import { File, XCircle } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Message } from "revolt.js/dist/maps/Messages";
import { Channel, Message } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";

View File

@@ -1,6 +1,5 @@
import { observer } from "mobx-react-lite";
import { RelationshipStatus } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Channel } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
@@ -65,7 +64,7 @@ export default observer(({ channel }: Props) => {
(x) =>
typeof x !== "undefined" &&
x._id !== x.client.user!._id &&
x.relationship !== RelationshipStatus.Blocked,
x.relationship !== "Blocked",
);
if (users.length > 0) {

View File

@@ -1,4 +1,4 @@
import { Embed as EmbedI } from "revolt-api/types/Channels";
import { API } from "revolt.js";
import styles from "./Embed.module.scss";
import classNames from "classnames";
@@ -13,7 +13,7 @@ import Attachment from "../attachments/Attachment";
import EmbedMedia from "./EmbedMedia";
interface Props {
embed: EmbedI;
embed: API.Embed;
}
const MAX_EMBED_WIDTH = 480;
@@ -128,7 +128,7 @@ export default function Embed({ embed }: Props) {
<a
onMouseDown={(ev) =>
(ev.button === 0 || ev.button === 1) &&
openLink(embed.url)
openLink(embed.url!)
}
className={styles.title}>
{embed.title}

View File

@@ -1,14 +1,12 @@
import { Group } from "@styled-icons/boxicons-solid";
import { autorun, reaction } from "mobx";
import { reaction } from "mobx";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
import { RetrievedInvite } from "revolt-api/types/Invites";
import { Message } from "revolt.js/dist/maps/Messages";
import { Message, API } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { useContext, useEffect, useState } from "preact/hooks";
import { defer } from "../../../../lib/defer";
import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice";
import {
@@ -85,9 +83,9 @@ export function EmbedInvite({ code }: Props) {
const [processing, setProcessing] = useState(false);
const [error, setError] = useState<string | undefined>(undefined);
const [joinError, setJoinError] = useState<string | undefined>(undefined);
const [invite, setInvite] = useState<RetrievedInvite | undefined>(
undefined,
);
const [invite, setInvite] = useState<
(API.InviteResponse & { type: "Server" }) | undefined
>(undefined);
useEffect(() => {
if (
@@ -96,7 +94,9 @@ export function EmbedInvite({ code }: Props) {
) {
client
.fetchInvite(code)
.then((data) => setInvite(data))
.then((data) =>
setInvite(data as API.InviteResponse & { type: "Server" }),
)
.catch((err) => setError(takeError(err)));
}
}, [client, code, invite, status]);
@@ -139,42 +139,17 @@ export function EmbedInvite({ code }: Props) {
) : (
<Button
onClick={async () => {
setProcessing(true);
try {
setProcessing(true);
await client.joinInvite(invite);
if (invite.type === "Server") {
if (client.servers.get(invite.server_id)) {
history.push(
`/server/${invite.server_id}/channel/${invite.channel_id}`,
);
return;
}
const dispose = reaction(
() =>
client.servers.get(
invite.server_id,
),
(server) => {
if (server) {
client.unreads!.markMultipleRead(
server.channel_ids,
);
history.push(
`/server/${server._id}/channel/${invite.channel_id}`,
);
dispose();
}
},
);
}
await client.joinInvite(code);
history.push(
`/server/${invite.server_id}/channel/${invite.channel_id}`,
);
} catch (err) {
setJoinError(takeError(err));
} finally {
setProcessing(false);
}
}}>

View File

@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { JanuaryEmbed } from "revolt-api/types/January";
import { API } from "revolt.js";
import styles from "./Embed.module.scss";
@@ -7,7 +7,7 @@ import { useIntermediate } from "../../../../context/intermediate/Intermediate";
import { useClient } from "../../../../context/revoltjs/RevoltClient";
interface Props {
embed: JanuaryEmbed;
embed: API.Embed;
width?: number;
height: number;
}
@@ -94,7 +94,7 @@ export default function EmbedMedia({ embed, width, height }: Props) {
onClick={() =>
openScreen({
id: "image_viewer",
embed: embed.image,
embed: embed.image!,
})
}
onMouseDown={(ev) =>

View File

@@ -1,12 +1,12 @@
import { LinkExternal } from "@styled-icons/boxicons-regular";
import { EmbedImage } from "revolt-api/types/January";
import { API } from "revolt.js";
import styles from "./Embed.module.scss";
import IconButton from "../../../ui/IconButton";
interface Props {
embed: EmbedImage;
embed: API.Image;
}
export default function EmbedMediaActions({ embed }: Props) {

View File

@@ -1,11 +1,23 @@
import { Shield } from "@styled-icons/boxicons-regular";
import { Badges } from "revolt-api/types/Users";
import styled from "styled-components/macro";
import { Localizer, Text } from "preact-i18n";
import Tooltip from "../Tooltip";
enum Badges {
Developer = 1,
Translator = 2,
Supporter = 4,
ResponsibleDisclosure = 8,
Founder = 16,
PlatformModeration = 32,
ActiveSupporter = 64,
Paw = 128,
EarlyAdopter = 256,
ReservedRelevantJokeBadge1 = 512,
}
const BadgesBase = styled.div`
gap: 8px;
display: flex;

View File

@@ -1,4 +1,4 @@
import { User } from "revolt.js/dist/maps/Users";
import { User } from "revolt.js";
import Checkbox, { CheckboxProps } from "../../ui/Checkbox";

View File

@@ -1,7 +1,7 @@
import { Cog } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Link } from "react-router-dom";
import { User } from "revolt.js/dist/maps/Users";
import { User } from "revolt.js";
import styled from "styled-components/macro";
import { openContextMenu } from "preact-context-menu";

View File

@@ -1,4 +1,4 @@
import { User } from "revolt.js/dist/maps/Users";
import { User } from "revolt.js";
import styled from "styled-components/macro";
import { Children } from "../../../types/Preact";

View File

@@ -1,9 +1,7 @@
import { VolumeMute, MicrophoneOff } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { useParams } from "react-router-dom";
import { Masquerade } from "revolt-api/types/Channels";
import { Presence } from "revolt-api/types/Users";
import { User } from "revolt.js/dist/maps/Users";
import { User, API } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { useApplicationState } from "../../../mobx/State";
@@ -18,17 +16,17 @@ type VoiceStatus = "muted" | "deaf";
interface Props extends IconBaseProps<User> {
status?: boolean;
voice?: VoiceStatus;
masquerade?: Masquerade;
masquerade?: API.Masquerade;
showServerIdentity?: boolean;
}
export function useStatusColour(user?: User) {
const theme = useApplicationState().settings.theme;
return user?.online && user?.status?.presence !== Presence.Invisible
? user?.status?.presence === Presence.Idle
return user?.online && user?.status?.presence !== "Invisible"
? user?.status?.presence === "Idle"
? theme.getVariable("status-away")
: user?.status?.presence === Presence.Busy
: user?.status?.presence === "Busy"
? theme.getVariable("status-busy")
: theme.getVariable("status-online")
: theme.getVariable("status-invisible");
@@ -95,7 +93,7 @@ export default observer(
url =
client.generateFileURL(
override ?? target?.avatar ?? attachment,
override ?? target?.avatar ?? attachment ?? undefined,
{ max_side: 256 },
animate,
) ?? (target ? target.defaultAvatarURL : fallback);

View File

@@ -1,8 +1,6 @@
import { observer } from "mobx-react-lite";
import { useParams } from "react-router-dom";
import { Masquerade } from "revolt-api/types/Channels";
import { User } from "revolt.js/dist/maps/Users";
import { Nullable } from "revolt.js/dist/util/null";
import { User, API } from "revolt.js";
import styled from "styled-components/macro";
import { Ref } from "preact";
@@ -32,7 +30,7 @@ const BotBadge = styled.div`
type UsernameProps = JSX.HTMLAttributes<HTMLElement> & {
user?: User;
prefixAt?: boolean;
masquerade?: Masquerade;
masquerade?: API.Masquerade;
showServerIdentity?: boolean | "both";
innerRef?: Ref<any>;
@@ -120,7 +118,7 @@ export default function UserShort({
user?: User;
size?: number;
prefixAt?: boolean;
masquerade?: Masquerade;
masquerade?: API.Masquerade;
showServerIdentity?: boolean;
}) {
const { openScreen } = useIntermediate();

View File

@@ -1,6 +1,5 @@
import { observer } from "mobx-react-lite";
import { Presence } from "revolt-api/types/Users";
import { User } from "revolt.js/dist/maps/Users";
import { User, API } from "revolt.js";
import { Text } from "preact-i18n";
@@ -25,15 +24,15 @@ export default observer(({ user, tooltip }: Props) => {
return <>{user.status.text}</>;
}
if (user.status?.presence === Presence.Busy) {
if (user.status?.presence === "Busy") {
return <Text id="app.status.busy" />;
}
if (user.status?.presence === Presence.Idle) {
if (user.status?.presence === "Idle") {
return <Text id="app.status.idle" />;
}
if (user.status?.presence === Presence.Invisible) {
if (user.status?.presence === "Invisible") {
return <Text id="app.status.offline" />;
}

View File

@@ -3,7 +3,7 @@ import { Suspense, lazy } from "preact/compat";
const Renderer = lazy(() => import("./Renderer"));
export interface MarkdownProps {
content?: string;
content?: string | null;
disallowBigEmoji?: boolean;
}

View File

@@ -129,7 +129,7 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
const { openLink } = useIntermediate();
if (typeof content === "undefined") return null;
if (content.length === 0) return null;
if (!content || content.length === 0) return null;
// We replace the message with the mention at the time of render.
// We don't care if the mention changes.

View File

@@ -1,9 +1,7 @@
import { X } from "@styled-icons/boxicons-regular";
import { Crown } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Presence } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import { User } from "revolt.js/dist/maps/Users";
import { User, Channel } from "revolt.js";
import styles from "./Item.module.scss";
import classNames from "classnames";
@@ -65,7 +63,7 @@ export const UserButton = observer((props: UserProps) => {
data-alert={typeof alert === "string"}
data-online={
typeof channel !== "undefined" ||
(user.online && user.status?.presence !== Presence.Invisible)
(user.online && user.status?.presence !== "Invisible")
}
{...useTriggerEvents("Menu", {
user: user._id,

View File

@@ -4,12 +4,14 @@ import { useContext } from "preact/hooks";
import {
ClientStatus,
StatusContext,
useClient,
} from "../../../context/revoltjs/RevoltClient";
import Banner from "../../ui/Banner";
export default function ConnectionStatus() {
const status = useContext(StatusContext);
const client = useClient();
if (status === ClientStatus.OFFLINE) {
return (
@@ -20,7 +22,10 @@ export default function ConnectionStatus() {
} else if (status === ClientStatus.DISCONNECTED) {
return (
<Banner>
<Text id="app.special.status.disconnected" />
<Text id="app.special.status.disconnected" /> <br />
<a onClick={() => client.websocket.connect()}>
<Text id="app.special.status.reconnect" />
</a>
</Banner>
);
} else if (status === ClientStatus.CONNECTING) {

View File

@@ -6,7 +6,6 @@ import {
} from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Link, useLocation, useParams } from "react-router-dom";
import { RelationshipStatus } from "revolt-api/types/Users";
import styled, { css } from "styled-components/macro";
import { Text } from "preact-i18n";
@@ -47,14 +46,16 @@ export default observer(() => {
const { pathname } = useLocation();
const client = useContext(AppContext);
const state = useApplicationState();
const { channel: currentChannel } = useParams<{ channel: string }>();
const { channel: channel_id } = useParams<{ channel: string }>();
const { openScreen } = useIntermediate();
const channels = [...client.channels.values()].filter(
(x) => x.channel_type === "DirectMessage" || x.channel_type === "Group",
(x) =>
(x.channel_type === "DirectMessage" && x.active) ||
x.channel_type === "Group",
);
const obj = client.channels.get(currentChannel);
const channel = client.channels.get(channel_id);
// ! FIXME: move this globally
// Track what page the user was last on (in home page).
@@ -66,7 +67,7 @@ export default observer(() => {
// ! FIXME: must be a better way
const incoming = [...client.users.values()].filter(
(user) => user?.relationship === RelationshipStatus.Incoming,
(user) => user?.relationship === "Incoming",
);
return (
@@ -104,9 +105,10 @@ export default observer(() => {
</>
)}
<ConditionalLink
active={obj?.channel_type === "SavedMessages"}
active={channel?.channel_type === "SavedMessages"}
to="/open/saved">
<ButtonItem active={obj?.channel_type === "SavedMessages"}>
<ButtonItem
active={channel?.channel_type === "SavedMessages"}>
<Notepad size={20} />
<span>
<Text id="app.navigation.tabs.saved" />
@@ -152,7 +154,7 @@ export default observer(() => {
return (
<ConditionalLink
key={channel._id}
active={channel._id === currentChannel}
active={channel._id === channel_id}
to={`/channel/${channel._id}`}>
<ChannelButton
user={user}
@@ -165,7 +167,7 @@ export default observer(() => {
: undefined
}
alertCount={mentionCount}
active={channel._id === currentChannel}
active={channel._id === channel_id}
/>
</ConditionalLink>
);

View File

@@ -2,10 +2,8 @@ import { Plus } from "@styled-icons/boxicons-regular";
import { Cog, Compass } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Link, useHistory, useLocation, useParams } from "react-router-dom";
import { RelationshipStatus } from "revolt-api/types/Users";
import styled, { css } from "styled-components/macro";
import { Ref } from "preact";
import { useTriggerEvents } from "preact-context-menu";
import ConditionalLink from "../../../lib/ConditionalLink";
@@ -248,7 +246,7 @@ export default observer(() => {
const { openScreen } = useIntermediate();
let alertCount = [...client.users.values()].filter(
(x) => x.relationship === RelationshipStatus.Incoming,
(x) => x.relationship === "Incoming",
).length;
const homeActive =
@@ -290,7 +288,7 @@ export default observer(() => {
{channels
.filter(
(x) =>
(x.channel_type === "DirectMessage" ||
((x.channel_type === "DirectMessage" && x.active) ||
x.channel_type === "Group") &&
x.unread,
)

View File

@@ -1,6 +1,6 @@
import { observer } from "mobx-react-lite";
import { Redirect, useParams } from "react-router";
import { Server } from "revolt.js/dist/maps/Servers";
import { Server } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Ref } from "preact";

View File

@@ -1,6 +1,6 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Channel } from "revolt.js";
import { getRenderer } from "../../../lib/renderer/Singleton";

View File

@@ -1,7 +1,6 @@
import { Link } from "react-router-dom";
import { GroupedVirtuoso } from "react-virtuoso";
import { Channel } from "revolt.js/dist/maps/Channels";
import { User } from "revolt.js/dist/maps/Users";
import { Channel, User } from "revolt.js";
import styled, { css } from "styled-components/macro";
import { Text } from "preact-i18n";

View File

@@ -2,11 +2,7 @@
import { autorun } from "mobx";
import { observer } from "mobx-react-lite";
import { useParams } from "react-router-dom";
import { Role } from "revolt-api/types/Servers";
import { Presence } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Server } from "revolt.js/dist/maps/Servers";
import { User } from "revolt.js/dist/maps/Users";
import { Channel, Server, User, API } from "revolt.js";
import { useContext, useEffect, useState } from "preact/hooks";
@@ -62,7 +58,7 @@ function useEntries(
.map((id) => {
return [id, roles![id], roles![id].rank ?? 0] as [
string,
Role,
API.Role,
number,
];
})
@@ -96,7 +92,7 @@ function useEntries(
const sort = member?.nickname ?? u.username;
const entry = [u, sort] as [User, string];
if (!u.online || u.status?.presence === Presence.Invisible) {
if (!u.online || u.status?.presence === "Invisible") {
categories.offline.push(entry);
} else {
if (isServer) {

View File

@@ -1,5 +1,5 @@
import { Link, useParams } from "react-router-dom";
import { Message as MessageI } from "revolt.js/dist/maps/Messages";
import { Message as MessageI } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";

View File

@@ -0,0 +1,42 @@
import { API } from "revolt.js";
import { Permission } from "revolt.js";
import { PermissionSelect } from "./PermissionSelect";
interface Props {
value: API.OverrideField | number;
onChange: (v: API.OverrideField | number) => void;
filter?: (keyof typeof Permission)[];
}
export function PermissionList({ value, onChange, filter }: Props) {
return (
<>
{(Object.keys(Permission) as (keyof typeof Permission)[])
.filter(
(key) =>
![
"GrantAllSafe",
"TimeoutMembers",
"ReadMessageHistory",
"Speak",
"Video",
"MuteMembers",
"DeafenMembers",
"MoveMembers",
].includes(key) &&
(!filter || filter.includes(key)),
)
.map((x) => (
<PermissionSelect
id={x}
key={x}
permission={Permission[x]}
value={value}
onChange={onChange}
/>
))}
</>
);
}

View File

@@ -0,0 +1,124 @@
import Long from "long";
import { API } from "revolt.js";
import { Permission } from "revolt.js";
import styled from "styled-components";
import { Text } from "preact-i18n";
import { useMemo } from "preact/hooks";
import Checkbox from "../../ui/Checkbox";
import { OverrideSwitch } from "@revoltchat/ui";
interface PermissionSelectProps {
id: keyof typeof Permission;
permission: number;
value: API.OverrideField | number;
onChange: (value: API.OverrideField | number) => void;
}
type State = "Allow" | "Neutral" | "Deny";
const PermissionEntry = styled.label`
gap: 8px;
width: 100%;
margin: 8px 0;
display: flex;
font-size: 1.1em;
align-items: center;
.title {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.description {
font-size: 0.8em;
color: var(--secondary-foreground);
}
`;
export function PermissionSelect({
id,
permission,
value,
onChange,
}: PermissionSelectProps) {
const state: State = useMemo(() => {
if (typeof value === "object") {
if (Long.fromNumber(value.d).and(permission).eq(permission)) {
return "Deny";
}
if (Long.fromNumber(value.a).and(permission).eq(permission)) {
return "Allow";
}
return "Neutral";
} else {
if (Long.fromNumber(value).and(permission).eq(permission)) {
return "Allow";
}
return "Neutral";
}
}, [value]);
function onSwitch(state: State) {
if (typeof value !== "object") throw "!";
// Convert to Long so we can do bitwise ops.
let allow = Long.fromNumber(value.a);
let deny = Long.fromNumber(value.d);
// Clear the current permission value.
if (allow.and(permission).eq(permission)) {
allow = allow.xor(permission);
}
if (deny.and(permission).eq(permission)) {
deny = deny.xor(permission);
}
// Apply the current permission state.
if (state === "Allow") {
allow = allow.or(permission);
}
if (state === "Deny") {
deny = deny.or(permission);
}
// Invoke state change.
onChange({
a: allow.toNumber(),
d: deny.toNumber(),
});
}
return (
<PermissionEntry>
<span class="title">
<Text id={`permissions.${id}.t`}>{id}</Text>
<span class="description">
<Text id={`permissions.${id}.d`} />
</span>
</span>
{typeof value === "object" ? (
<OverrideSwitch state={state} onChange={onSwitch} />
) : (
<Checkbox
checked={state === "Allow"}
onChange={() =>
onChange(
Long.fromNumber(value, false)
.xor(permission)
.toNumber(),
)
}
/>
)}
</PermissionEntry>
);
}

View File

@@ -0,0 +1,12 @@
import { API } from "revolt.js";
export type RoleOrDefault = (
| API.Role
| {
name: string;
permissions: number;
colour?: string;
hoist?: boolean;
rank?: number;
}
) & { id: string };

View File

@@ -89,7 +89,7 @@ export interface CheckboxProps {
disabled?: boolean;
contrast?: boolean;
className?: string;
children: Children;
children?: Children;
description?: Children;
onChange: (state: boolean) => void;
}