merge: branch 'master' into production

This commit is contained in:
Paul Makles 2022-09-20 18:44:51 +01:00
commit df20ab7407
36 changed files with 369 additions and 192 deletions

2
external/components vendored

@ -1 +1 @@
Subproject commit ab0fc1af739f877c8f416dcd7e47d25aacbb7eea
Subproject commit e79862b5972b57c016b4c08676ac1b90bd52ee83

2
external/lang vendored

@ -1 +1 @@
Subproject commit 843cece2854185651e17765bcb78109d22eba769
Subproject commit f95ec6dc8200adba7925425d4cf2ae0c16f049c3

View file

@ -73,7 +73,7 @@
"@fontsource/space-mono": "^4.4.5",
"@fontsource/ubuntu": "^4.4.5",
"@fontsource/ubuntu-mono": "^4.4.5",
"@hcaptcha/react-hcaptcha": "^0.3.6",
"@hcaptcha/react-hcaptcha": "^1.4.4",
"@insertish/vite-plugin-babel-macros": "^1.0.5",
"@preact/preset-vite": "^2.0.0",
"@revoltchat/ui": "^1.0.77",
@ -81,7 +81,7 @@
"@styled-icons/boxicons-logos": "^10.38.0",
"@styled-icons/boxicons-regular": "^10.38.0",
"@styled-icons/boxicons-solid": "^10.38.0",
"@styled-icons/simple-icons": "^10.33.0",
"@styled-icons/simple-icons": "^10.45.0",
"@tippyjs/react": "4.2.6",
"@traptitech/markdown-it-katex": "^3.4.3",
"@traptitech/markdown-it-spoiler": "^1.1.6",
@ -89,9 +89,9 @@
"@types/lodash": "^4",
"@types/lodash.defaultsdeep": "^4.6.6",
"@types/lodash.isequal": "^4.5.5",
"@types/node": "^15.12.4",
"@types/node": "^15.14.9",
"@types/preact-i18n": "^2.3.0",
"@types/prismjs": "^1.16.5",
"@types/prismjs": "^1.26.0",
"@types/react-beautiful-dnd": "^13",
"@types/react-helmet": "^6.1.1",
"@types/react-router-dom": "^5.1.7",

1
packages/components Submodule

@ -0,0 +1 @@
Subproject commit d314b2d191124f1b487ebd72409e748c1bfccb87

@ -0,0 +1 @@
Subproject commit 7803fa54410a7ef9fc3149c482253e74ca1d7d71

1
packages/revolt.js Submodule

@ -0,0 +1 @@
Subproject commit 39d1f596e280a28278d913e1e60e4d5298d71578

View file

@ -14,12 +14,21 @@ import {
import { observer } from "mobx-react-lite";
import { Message, API } from "revolt.js";
import styled from "styled-components/macro";
import { decodeTime } from "ulid";
import { useTriggerEvents } from "preact-context-menu";
import { Text } from "preact-i18n";
import { Row } from "@revoltchat/ui";
import { TextReact } from "../../../lib/i18n";
import { useApplicationState } from "../../../mobx/State";
import { dayjs } from "../../../context/Locale";
import Markdown from "../../markdown/Markdown";
import Tooltip from "../Tooltip";
import UserShort from "../user/UserShort";
import MessageBase, { MessageDetail, MessageInfo } from "./MessageBase";
@ -78,6 +87,8 @@ export const SystemMessage = observer(
const data = message.asSystemMessage;
if (!data) return null;
const settings = useApplicationState().settings;
const SystemMessageIcon =
iconDictionary[data.type as API.SystemMessage["type"]] ??
InfoCircle;
@ -103,16 +114,39 @@ export const SystemMessage = observer(
case "user_joined":
case "user_left":
case "user_kicked":
case "user_banned":
case "user_banned": {
const createdAt = data.user ? decodeTime(data.user._id) : null;
children = (
<Row centred>
<TextReact
id={`app.main.channel.system.${data.type}`}
fields={{
user: <UserShort user={data.user} />,
}}
/>
{data.type == "user_joined" &&
createdAt &&
(settings.get("appearance:show_account_age") ||
Date.now() - createdAt <
1000 * 60 * 60 * 24 * 7) && (
<Tooltip
content={
<Text
id="app.main.channel.system.registered_at"
fields={{
time: dayjs(
createdAt,
).fromNow(),
}}
/>
}>
<InfoCircle size={16} />
</Tooltip>
)}
</Row>
);
break;
}
case "channel_renamed":
children = (
<TextReact

View file

@ -1,5 +1,5 @@
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js";
import { Channel, Member } from "revolt.js";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
@ -60,6 +60,8 @@ const Base = styled.div`
`;
export default observer(({ channel }: Props) => {
const client = channel.client;
const users = channel.typing.filter(
(x) =>
typeof x !== "undefined" &&
@ -67,24 +69,47 @@ export default observer(({ channel }: Props) => {
x.relationship !== "Blocked",
);
if (users.length > 0) {
users.sort((a, b) =>
a!._id.toUpperCase().localeCompare(b!._id.toUpperCase()),
const members = users.map((user) => {
return client.members.getKey({
server: channel.server_id!,
user: user!._id,
});
});
const getName = (member: Member) => {
return member.nickname === null
? member.user?.username
: member.nickname;
};
const getAvatar = (member: Member) => {
const memberAvatarURL = member.generateAvatarURL({
max_side: 256,
});
return memberAvatarURL === undefined
? member.user?.generateAvatarURL({ max_side: 256 })
: memberAvatarURL;
};
if (members.length > 0) {
members.sort((a, b) =>
a!._id.user.toUpperCase().localeCompare(b!._id.user.toUpperCase()),
);
let text;
if (users.length >= 5) {
if (members.length >= 5) {
text = <Text id="app.main.channel.typing.several" />;
} else if (users.length > 1) {
const userlist = [...users].map((x) => x!.username);
const user = userlist.pop();
} else if (members.length > 1) {
const memberlist = [...members].map((x) => getName(x!));
const member = memberlist.pop();
text = (
<Text
id="app.main.channel.typing.multiple"
fields={{
user,
userlist: userlist.join(", "),
user: member,
userlist: memberlist.join(", "),
}}
/>
);
@ -92,7 +117,7 @@ export default observer(({ channel }: Props) => {
text = (
<Text
id="app.main.channel.typing.single"
fields={{ user: users[0]!.username }}
fields={{ user: getName(members[0]!) }}
/>
);
}
@ -101,11 +126,11 @@ export default observer(({ channel }: Props) => {
<Base>
<div>
<div className="avatars">
{users.map((user) => (
{members.map((member) => (
<img
key={user!._id}
key={member!._id.user}
loading="eager"
src={user!.generateAvatarURL({ max_side: 256 })}
src={getAvatar(member!)}
/>
))}
</div>

View file

@ -49,7 +49,7 @@ export default function EmbedMedia({ embed, width, height }: Props) {
case "Lightspeed":
return (
<iframe
src={`https://next.lightspeed.tv/embed/${embed.special.id}`}
src={`https://new.lightspeed.tv/embed/${embed.special.id}/stream`}
frameBorder="0"
allowFullScreen
scrolling="no"

View file

@ -20,6 +20,7 @@ const Base = styled.pre`
* Copy codeblock contents button styles
*/
const Lang = styled.div`
font-family: var(--monospace-font);
width: fit-content;
padding-bottom: 8px;

View file

@ -118,20 +118,20 @@ export default function MemberList({
return (
<NoOomfie>
<div>
Offline users temporarily disabled for this
server, see issue{" "}
Offline users have temporarily been disabled for
larger servers - see{" "}
<a
href="https://github.com/revoltchat/delta/issues/128"
href="https://github.com/revoltchat/backend/issues/178"
target="_blank"
rel="noreferrer">
#128
issue #178
</a>{" "}
for when this will be resolved.
</div>
<div>
You may re-enable them in{" "}
You may re-enable them{" "}
<Link to="/settings/experiments">
<a>experiments</a>
<a>here</a>
</Link>
.
</div>

View file

@ -26,6 +26,20 @@ export default function AppearanceOptions() {
<Text id="app.settings.pages.appearance.appearance_options.show_send_desc" />
}
/>
{/* Option to always show the account creation age next to join system messages. */}
<ObservedInputElement
type="checkbox"
value={() =>
settings.get("appearance:show_account_age") ?? false
}
onChange={(v) => settings.set("appearance:show_account_age", v)}
title={
<Text id="app.settings.pages.appearance.appearance_options.show_account_age" />
}
description={
<Text id="app.settings.pages.appearance.appearance_options.show_account_age_desc" />
}
/>
<hr />
<h3>
<Text id="app.settings.pages.appearance.theme_options.title" />

View file

@ -10,6 +10,7 @@ import { noopAsync } from "../../../lib/js";
import { takeError } from "../../client/jsx/error";
import { modalController } from "../ModalController";
import { ModalProps } from "../types";
import { IS_REVOLT } from "../../../version";
/**
* Code block which displays invite
@ -78,7 +79,7 @@ export default function CreateInvite({
children: <Text id="app.context_menu.copy_link" />,
onClick: () =>
modalController.writeText(
`${window.location.protocol}//${window.location.host}/invite/${code}`,
IS_REVOLT ? `https://rvlt.gg/${code}` : `${window.location.host}/invite/${code}`
),
},
]}

View file

@ -12,34 +12,19 @@
flex-direction: column;
div {
flex: 1;
&.container {
max-width: 750px;
flex-grow: 1;
align-items: left;
}
&.header {
gap: 8px;
padding: 3em;
padding-top: 5em;
display: flex;
text-align: center;
h1 {
margin: 0;
}
img {
max-height: 80px;
}
}
&.form {
flex-grow: 1;
max-width: 420px;
img {
margin: auto;
display: block;
max-height: 420px;
border-radius: var(--border-radius);
}
input {
width: 100%;
}

View file

@ -6,7 +6,8 @@ import { useState } from "preact/hooks";
import { Button, Preloader } from "@revoltchat/ui";
import wideSVG from "/assets/wide.svg";
// import wideSVG from "/assets/wide.svg";
import background from "./assets/onboarding_background.svg";
import FormField from "../../../../pages/login/FormField";
import { takeError } from "../../../client/jsx/error";
@ -36,12 +37,9 @@ export function OnboardingModal({
return (
<div className={styles.onboarding}>
<div className={styles.container}>
<div className={styles.header}>
<h1>
<Text id="app.special.modals.onboarding.welcome" />
<br />
<img src={wideSVG} loading="eager" />
</h1>
<h1>{"Welcome to Revolt."}</h1>
</div>
<div className={styles.form}>
{loading ? (
@ -49,7 +47,15 @@ export function OnboardingModal({
) : (
<>
<p>
<Text id="app.special.modals.onboarding.pick" />
{"It's time to choose a username."}
<br />
{
"Others will be able to find, recognise and mention you with this name, so choose wisely."
}
<br />
{
"You can change it at any time in your User Settings."
}
</p>
<form
onSubmit={
@ -65,14 +71,15 @@ export function OnboardingModal({
error={error}
/>
</div>
<Button type="submit">
<Text id="app.special.modals.actions.continue" />
<Button palette="accent">
{"Looks good!"}
</Button>
</form>
</>
)}
</div>
<div />
</div>
<img src={background} />
</div>
);
}

View file

@ -41,8 +41,8 @@ import { ModalProps } from "../../types";
export const UserProfile = observer(
({
user_id,
dummy,
dummyProfile,
isPlaceholder,
placeholderProfile,
...props
}: ModalProps<"user_profile">) => {
const [profile, setProfile] = useState<
@ -87,21 +87,21 @@ export const UserProfile = observer(
}, [user_id]);
useEffect(() => {
if (dummy) {
setProfile(dummyProfile);
if (isPlaceholder) {
setProfile(placeholderProfile);
}
}, [dummy, dummyProfile]);
}, [isPlaceholder, placeholderProfile]);
useEffect(() => {
if (dummy) return;
if (isPlaceholder) return;
if (session.state === "Online" && typeof mutual === "undefined") {
setMutual(null);
user.fetchMutual().then(setMutual);
}
}, [mutual, session.state, dummy, user]);
}, [mutual, session.state, isPlaceholder, user]);
useEffect(() => {
if (dummy) return;
if (isPlaceholder) return;
if (session.state === "Online" && typeof profile === "undefined") {
setProfile(null);
@ -109,7 +109,7 @@ export const UserProfile = observer(
user.fetchProfile().then(setProfile).catch(noop);
}
}
}, [profile, session.state, dummy, user]);
}, [profile, session.state, isPlaceholder, user]);
useEffect(() => {
if (
@ -169,7 +169,8 @@ export const UserProfile = observer(
onClick={() =>
modalController.writeText(user.username)
}>
@{user.username}
{"@"}
{user.username}
</span>
</Localizer>
{user.status?.text && (
@ -184,11 +185,11 @@ export const UserProfile = observer(
palette="accent"
compact
onClick={props.onClose}>
Add to server
{"Add to server" /* FIXME: i18n */}
</Button>
</Link>
)}
{user.relationship === "Friend" && (
{(user.relationship === "Friend" || user.bot) && (
<Localizer>
<Tooltip
content={
@ -204,7 +205,7 @@ export const UserProfile = observer(
</Tooltip>
</Localizer>
)}
{user.relationship === "User" && !dummy && (
{user.relationship === "User" && !isPlaceholder && (
<IconButton
onClick={() => {
props.onClose?.();
@ -237,11 +238,13 @@ export const UserProfile = observer(
</div>
{user.relationship !== "User" && (
<>
{!user.bot && (
<div
data-active={tab === "friends"}
onClick={() => setTab("friends")}>
<Text id="app.special.popovers.user_profile.mutual_friends" />
</div>
)}
<div
data-active={tab === "groups"}
onClick={() => setTab("groups")}>
@ -281,8 +284,9 @@ export const UserProfile = observer(
) : undefined}
{user.bot ? (
<>
{/* FIXME: this too */}
<div className={styles.category}>
bot owner
{"bot owner"}
</div>
<div
onClick={() =>
@ -432,12 +436,12 @@ export const UserProfile = observer(
</>
);
if (dummy) return <div>{children}</div>;
if (isPlaceholder) return <div>{children}</div>;
return (
<Modal
{...props}
nonDismissable={dummy}
nonDismissable={isPlaceholder}
transparent
maxWidth="560px">
{children}

View file

@ -0,0 +1,2 @@
<!-- The following background was created by Infi -->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="1921.2435514377348" xmlns="http://www.w3.org/2000/svg" height="421.46138650693956" id="screenshot-f4de8581-11da-11ed-b308-1b06919bb1d2" viewBox="-5 687.5386134930604 1921.2435514377348 421.46138650693956" style="-webkit-print-color-adjust: exact;" fill="none" version="1.1"><g id="shape-f4de8581-11da-11ed-b308-1b06919bb1d2"><g id="fills-f4de8581-11da-11ed-b308-1b06919bb1d2"><path rx="0" ry="0" d="M-5,906C-5,906,339,692,620,741C901,790,1313,868,1482,763C1651,658,1850,681,1899,722C1948,763,1877,1109,1877,1109L-5,1109L-5,906Z" style="fill: rgb(45, 45, 45); fill-opacity: 1;"/></g></g></svg>

After

Width:  |  Height:  |  Size: 702 B

View file

@ -98,8 +98,8 @@ export type Modal = {
| {
type: "user_profile";
user_id: string;
dummy?: boolean;
dummyProfile?: API.UserProfile;
isPlaceholder?: boolean;
placeholderProfile?: API.UserProfile;
}
| {
type: "create_bot";

View file

@ -974,6 +974,10 @@ export default function ContextMenus() {
}
}
// workaround to prevent button duplication
let hideIDButton;
if (sid && server) hideIDButton = true;
if (sid && server) {
generateAction(
{
@ -1015,15 +1019,28 @@ export default function ContextMenus() {
"open_server_settings",
);
// workaround to move this above the delete/leave button
generateAction(
{ action: "copy_id", id },
"copy_sid",
);
pushDivider();
if (userId === server.owner) {
generateAction(
{ action: "delete_server", target: server },
"delete_server",
undefined,
undefined,
"var(--error)",
);
} else {
generateAction(
{ action: "leave_server", target: server },
"leave_server",
undefined,
undefined,
"var(--error)",
);
}
}
@ -1035,17 +1052,17 @@ export default function ContextMenus() {
});
}
if (!hideIDButton) {
generateAction(
{ action: "copy_id", id },
sid
? "copy_sid"
: cid
cid
? "copy_cid"
: message
? "copy_mid"
: "copy_uid",
);
}
}
return elements;
}}

View file

@ -1,6 +1,5 @@
import { action, makeAutoObservable, runInAction } from "mobx";
import { Channel } from "revolt.js";
import { Nullable, toNullable } from "revolt.js";
import { Channel, Nullable, toNullable } from "revolt.js";
import type { ProduceType, VoiceUser } from "./Types";
import type VoiceClient from "./VoiceClient";

View file

@ -5,15 +5,22 @@ import { mapToRecord } from "../../lib/conversion";
import Persistent from "../interfaces/Persistent";
import Store from "../interfaces/Store";
interface DraftObject {
content?: string;
masquerade?: {
avatar: string;
name: string;
};
}
export interface Data {
drafts: Record<string, string>;
drafts: Record<string, DraftObject>;
}
/**
* Handles storing draft (currently being written) messages.
*/
export default class Draft implements Store, Persistent<Data> {
private drafts: ObservableMap<string, string>;
private drafts: ObservableMap<string, DraftObject>;
/**
* Construct new Draft store.
@ -52,7 +59,10 @@ export default class Draft implements Store, Persistent<Data> {
* @param channel Channel ID
*/
@computed has(channel: string) {
return this.drafts.has(channel) && this.drafts.get(channel)!.length > 0;
return (
this.drafts.has(channel) &&
this.drafts.get(channel)!.content!.length > 0
);
}
/**
@ -60,7 +70,7 @@ export default class Draft implements Store, Persistent<Data> {
* @param channel Channel ID
* @param content Draft content
*/
@action set(channel: string, content?: string) {
@action set(channel: string, content?: DraftObject) {
if (typeof content === "undefined") {
return this.clear(channel);
}

View file

@ -29,13 +29,13 @@ export const EXPERIMENTS: {
[key in Experiment]: { title: string; description: string };
} = {
dummy: {
title: "Dummy Experiment",
description: "This is a dummy experiment.",
title: "Placeholder Experiment",
description: "This is a placeholder experiment.",
},
offline_users: {
title: "Re-enable offline users in large servers (>10k members)",
description:
"If you can take the performance hit (for example, you're on desktop), you can re-enable offline users for big servers such as Revolt Lounge.",
"If you can take the performance hit - for example, if you're on desktop - you can re-enable offline users for big servers such as the Revolt Lounge.",
},
plugins: {
title: "Experimental Plugin API",
@ -45,7 +45,7 @@ export const EXPERIMENTS: {
picker: {
title: "Custom Emoji",
description:
"This will enable a work-in-progress emoji picker, custom emoji settings and reaction picker.",
"This will enable a work-in-progress emoji picker, custom emoji settings and a reaction picker.",
},
};

View file

@ -21,6 +21,7 @@ export interface ISettings {
"appearance:seasonal": boolean;
"appearance:transparency": boolean;
"appearance:show_send_button": boolean;
"appearance:show_account_age": boolean;
"appearance:theme:base": "dark" | "light";
"appearance:theme:overrides": Partial<Overrides>;
@ -79,7 +80,7 @@ export default class Settings
*/
@action set<T extends keyof ISettings>(key: T, value: ISettings[T]) {
// Emoji needs to be immediately applied.
if (key === 'appearance:emoji') {
if (key === "appearance:emoji") {
setGlobalEmojiPack(value as EmojiPack);
}

View file

@ -126,7 +126,7 @@ export default function App() {
)}
{alert.dismissable !== false && (
<a onClick={() => setStatusBar(false)}>
<div className="button">Dismiss</div>
<div className="button">{"Dismiss"}</div>
</a>
)}
</div>

View file

@ -2,6 +2,8 @@ import { Wrench } from "@styled-icons/boxicons-solid";
import { useEffect, useState } from "preact/hooks";
import { Button } from "@revoltchat/ui";
import PaintCounter from "../../lib/PaintCounter";
import { TextReact } from "../../lib/i18n";
@ -45,8 +47,15 @@ export default function Developer() {
</div>
<div style={{ padding: "16px" }}>
<a style={"cursor: pointer;"} onClick={() => setCrash(true)}>click to crash app</a>
{crash && (window as any).sus.sus()}
<Button palette="error" onClick={() => setCrash(true)}>
Click to crash app
</Button>
{
crash &&
(
window as any
).sus.sus() /* this runs a function that doesn't exist */
}
{/*<span>
<b>Voice Status:</b> {VoiceStatus[voice.status]}
</span>

View file

@ -51,12 +51,11 @@ export default observer(() => {
state.settings.set("appearance:seasonal", !seasonalTheme);
const isDecember = !isTouchscreenDevice && new Date().getMonth() === 11;
const isOctober = !isTouchscreenDevice && new Date().getMonth() === 9
const snowflakes = useMemo(() => {
const flakes = [];
// Disable outside of December
if (!isDecember) return [];
if (isDecember) {
for (let i = 0; i < 15; i++) {
flakes.push("❄️");
flakes.push("❄");
@ -68,6 +67,23 @@ export default observer(() => {
flakes.push("⛄");
}
return flakes;
}
if (isOctober) {
for (let i = 0; i < 15; i++) {
flakes.push("🎃");
flakes.push("💀");
}
for (let i = 0; i < 2; i++) {
flakes.push("👻");
flakes.push("⚰️");
flakes.push("🕷️");
}
return flakes;
}
return flakes;
}, []);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

View file

@ -1,7 +1,6 @@
import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite";
import { Channel, API } from "revolt.js";
import { DEFAULT_PERMISSION_DIRECT_MESSAGE } from "revolt.js";
import { Channel, API, DEFAULT_PERMISSION_DIRECT_MESSAGE } from "revolt.js";
import { Text } from "preact-i18n";
import { useState } from "preact/hooks";

View file

@ -24,7 +24,7 @@ export function Feedback() {
</CategoryButton>
</a>
<a
href="https://github.com/revoltchat/revite/issues/new"
href="https://github.com/revoltchat/revite/issues/new/choose"
target="_blank"
rel="noreferrer">
<CategoryButton
@ -37,7 +37,7 @@ export function Feedback() {
</CategoryButton>
</a>
<a
href="https://github.com/orgs/revoltchat/projects/1"
href="https://github.com/orgs/revoltchat/projects/3"
target="_blank"
rel="noreferrer">
<CategoryButton
@ -55,7 +55,7 @@ export function Feedback() {
action="chevron"
icon={<Group size={24} />}
description="You can report issues and discuss improvements with us directly here.">
Join Testers server.
{"Join the Revolt Lounge"}
</CategoryButton>
</a>
</Link>

View file

@ -165,18 +165,22 @@ export function Native() {
title="I understand there's no going back."
description={
<>
This will change the app to the 'dev' branch,
instead loading the app from a local server on
your machine.
{
"This will change the app to the 'dev' branch, instead loading the app from a local server on your machine."
}
<br />
<b>
Without a server running,{" "}
{"Without a server running, "}
<span style={{ color: "var(--error)" }}>
the app will not load!
{"the app will not load!"}
</span>
</b>
<br />
<code>yarn dev --port 3001</code>
{
"Make sure the app is available on port 3001 by running "
}
<code>{"yarn dev --port 3001 --host"}</code>
{"."}
</>
}
/>

View file

@ -72,8 +72,8 @@ export const Profile = observer(() => {
<div className={styles.preview}>
<UserProfile
user_id={client.user!._id}
dummy={true}
dummyProfile={profile}
isPlaceholder={true}
placeholderProfile={profile}
{...({} as any)}
/>
</div>

View file

@ -1,4 +1,4 @@
import { Chrome, Android, Apple, Windows } from "@styled-icons/boxicons-logos";
import { Chrome, Android, Windows } from "@styled-icons/boxicons-logos";
import { HelpCircle, Desktop, LogOut } from "@styled-icons/boxicons-regular";
import {
Safari,
@ -6,6 +6,7 @@ import {
Microsoftedge,
Linux,
Macos,
Ios,
Opera,
Samsung,
Windowsxp,
@ -99,7 +100,7 @@ export function Sessions() {
case /mac.*os/i.test(name):
return <Macos size={14} />;
case /i(Pad)?os/i.test(name):
return <Apple size={14} />;
return <Ios size={14} />;
case /windows 7/i.test(name):
return <Windowsxp size={14} />;
case /windows/i.test(name):

View file

@ -96,14 +96,14 @@ export const Overview = observer(({ server }: Props) => {
<div className={styles.markdown}>
<Markdown size="24" />
<h5>
Descriptions support Markdown formatting,{" "}
{"Server descriptions support Markdown formatting. "}
<a
href="https://developers.revolt.chat/markdown"
href="https://support.revolt.chat/kb/interface/messages/formatting-your-messages"
target="_blank"
rel="noreferrer">
learn more here
{"Learn more here"}
</a>
.
{"."}
</h5>
</div>
<hr />

View file

@ -80,7 +80,7 @@
}
code {
font-size: 1.4em;
font-family: var(--monospace-font);
user-select: all;
}

View file

@ -1,6 +1,8 @@
import { HelpCircle } from "@styled-icons/boxicons-solid";
import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite";
import { Server } from "revolt.js";
import styled from "styled-components";
import { Text } from "preact-i18n";
import { useMemo, useState } from "preact/hooks";
@ -16,6 +18,7 @@ import {
Category,
} from "@revoltchat/ui";
import Tooltip from "../../../components/common/Tooltip";
import { PermissionList } from "../../../components/settings/roles/PermissionList";
import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection";
import { modalController } from "../../../controllers/modals/ModalController";
@ -53,6 +56,20 @@ export const Roles = observer(({ server }: Props) => {
// Consolidate all permissions that we can change right now.
const currentRoles = useRoles(server);
const RoleId = styled.div`
gap: 4px;
display: flex;
align-items: center;
font-size: 12px;
font-weight: 600;
color: var(--tertiary-foreground);
a {
color: var(--tertiary-foreground);
}
`;
return (
<PermissionsLayout
server={server}
@ -147,6 +164,30 @@ export const Roles = observer(({ server }: Props) => {
/>
</p>
</section>
<section>
<Category>{"Role ID"}</Category>
<RoleId>
<Tooltip
content={
"This is a unique identifier for this role."
}>
<HelpCircle size={16} />
</Tooltip>
<Tooltip
content={
<Text id="app.special.copy" />
}>
<a
onClick={() =>
modalController.writeText(
currentRole.id,
)
}>
{currentRole.id}
</a>
</Tooltip>
</RoleId>
</section>
<section>
<Category>
<Text id="app.settings.permissions.role_colour" />

View file

@ -1590,7 +1590,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.8.4":
"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.8.4":
version: 7.15.3
resolution: "@babel/runtime@npm:7.15.3"
dependencies:
@ -1617,6 +1617,15 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.17.9":
version: 7.18.9
resolution: "@babel/runtime@npm:7.18.9"
dependencies:
regenerator-runtime: ^0.13.4
checksum: 36dd736baba7164e82b3cc9d43e081f0cb2d05ff867ad39cac515d99546cee75b7f782018b02a3dcf5f2ef3d27f319faa68965fdfec49d4912c60c6002353a2e
languageName: node
linkType: hard
"@babel/standalone@npm:^7.17.2":
version: 7.17.6
resolution: "@babel/standalone@npm:7.17.6"
@ -2020,13 +2029,15 @@ __metadata:
languageName: node
linkType: hard
"@hcaptcha/react-hcaptcha@npm:^0.3.6":
version: 0.3.7
resolution: "@hcaptcha/react-hcaptcha@npm:0.3.7"
"@hcaptcha/react-hcaptcha@npm:^1.4.4":
version: 1.4.4
resolution: "@hcaptcha/react-hcaptcha@npm:1.4.4"
dependencies:
"@babel/runtime": ^7.17.9
peerDependencies:
react: ">= 16.3.0"
react-dom: ">= 16.3.0"
checksum: 4a0ce88dd7a719cae2dc84255466a8636540287c2208f8fe46003a67c72f65509cabf8452cad73982a514cfce2e475d209a3e67cd41a559465f185fb4176a92f
checksum: 16b046702957f4ca5041c37f2a5012e07415469667fee0396b4764baa7ce0fe5a8577cf652b977732f026d745c999f7e1522bca3b3ca6effea5cfff189ddff2f
languageName: node
linkType: hard
@ -2414,16 +2425,16 @@ __metadata:
languageName: node
linkType: hard
"@styled-icons/simple-icons@npm:^10.33.0":
version: 10.37.0
resolution: "@styled-icons/simple-icons@npm:10.37.0"
"@styled-icons/simple-icons@npm:^10.45.0":
version: 10.45.0
resolution: "@styled-icons/simple-icons@npm:10.45.0"
dependencies:
"@babel/runtime": ^7.14.8
"@babel/runtime": ^7.15.4
"@styled-icons/styled-icon": ^10.6.3
peerDependencies:
react: "*"
styled-components: "*"
checksum: d94b94f6ad82aef50c69933f2f99d9ccdcadf3579f1ecd9fc5b5537ff1a2d7c0630579c4f1e0f73d6893a4e1cea4010d4ab927ba848a6bab7b1fba31e59c0f01
checksum: fda28f4ce59282916413ac2d638440a186f4ae2b1b5904c0e9695efdeec3849800a659f1292299d9afa45276e926f4110b6cbe9259dc38d9ffdc41ef0b4c0086
languageName: node
linkType: hard
@ -2692,7 +2703,7 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^15.12.4":
"@types/node@npm:^15.14.9":
version: 15.14.9
resolution: "@types/node@npm:15.14.9"
checksum: 49f7f0522a3af4b8389aee660e88426490cd54b86356672a1fedb49919a8797c00d090ec2dcc4a5df34edc2099d57fc2203d796c4e7fbd382f2022ccd789eee7
@ -2722,14 +2733,7 @@ __metadata:
languageName: node
linkType: hard
"@types/prismjs@npm:^1.16.5":
version: 1.16.6
resolution: "@types/prismjs@npm:1.16.6"
checksum: fcb489d19d21292ceffc661fd9c9aa8ba36bcd2835388fa85812aa60cdf14d4720ceb22be7a088aa753c48462e75bcd8191d178a085e4a8b0d3f34471d30d86a
languageName: node
linkType: hard
"@types/prismjs@npm:^1.16.6":
"@types/prismjs@npm:^1.16.6, @types/prismjs@npm:^1.26.0":
version: 1.26.0
resolution: "@types/prismjs@npm:1.26.0"
checksum: cd5e7a6214c1f4213ec512a5fcf6d8fe37a56b813fc57ac95b5ff5ee074742bfdbd2f2730d9fd985205bf4586728e09baa97023f739e5aa1c9735a7c1ecbd11a
@ -3681,7 +3685,7 @@ __metadata:
"@fontsource/space-mono": ^4.4.5
"@fontsource/ubuntu": ^4.4.5
"@fontsource/ubuntu-mono": ^4.4.5
"@hcaptcha/react-hcaptcha": ^0.3.6
"@hcaptcha/react-hcaptcha": ^1.4.4
"@insertish/vite-plugin-babel-macros": ^1.0.5
"@preact/preset-vite": ^2.0.0
"@revoltchat/ui": ^1.0.77
@ -3689,7 +3693,7 @@ __metadata:
"@styled-icons/boxicons-logos": ^10.38.0
"@styled-icons/boxicons-regular": ^10.38.0
"@styled-icons/boxicons-solid": ^10.38.0
"@styled-icons/simple-icons": ^10.33.0
"@styled-icons/simple-icons": ^10.45.0
"@tippyjs/react": 4.2.6
"@traptitech/markdown-it-katex": ^3.4.3
"@traptitech/markdown-it-spoiler": ^1.1.6
@ -3697,9 +3701,9 @@ __metadata:
"@types/lodash": ^4
"@types/lodash.defaultsdeep": ^4.6.6
"@types/lodash.isequal": ^4.5.5
"@types/node": ^15.12.4
"@types/node": ^15.14.9
"@types/preact-i18n": ^2.3.0
"@types/prismjs": ^1.16.5
"@types/prismjs": ^1.26.0
"@types/react-beautiful-dnd": ^13
"@types/react-helmet": ^6.1.1
"@types/react-router-dom": ^5.1.7