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/space-mono": "^4.4.5",
"@fontsource/ubuntu": "^4.4.5", "@fontsource/ubuntu": "^4.4.5",
"@fontsource/ubuntu-mono": "^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", "@insertish/vite-plugin-babel-macros": "^1.0.5",
"@preact/preset-vite": "^2.0.0", "@preact/preset-vite": "^2.0.0",
"@revoltchat/ui": "^1.0.77", "@revoltchat/ui": "^1.0.77",
@ -81,7 +81,7 @@
"@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-logos": "^10.38.0",
"@styled-icons/boxicons-regular": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0",
"@styled-icons/boxicons-solid": "^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", "@tippyjs/react": "4.2.6",
"@traptitech/markdown-it-katex": "^3.4.3", "@traptitech/markdown-it-katex": "^3.4.3",
"@traptitech/markdown-it-spoiler": "^1.1.6", "@traptitech/markdown-it-spoiler": "^1.1.6",
@ -89,9 +89,9 @@
"@types/lodash": "^4", "@types/lodash": "^4",
"@types/lodash.defaultsdeep": "^4.6.6", "@types/lodash.defaultsdeep": "^4.6.6",
"@types/lodash.isequal": "^4.5.5", "@types/lodash.isequal": "^4.5.5",
"@types/node": "^15.12.4", "@types/node": "^15.14.9",
"@types/preact-i18n": "^2.3.0", "@types/preact-i18n": "^2.3.0",
"@types/prismjs": "^1.16.5", "@types/prismjs": "^1.26.0",
"@types/react-beautiful-dnd": "^13", "@types/react-beautiful-dnd": "^13",
"@types/react-helmet": "^6.1.1", "@types/react-helmet": "^6.1.1",
"@types/react-router-dom": "^5.1.7", "@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 { observer } from "mobx-react-lite";
import { Message, API } from "revolt.js"; import { Message, API } from "revolt.js";
import styled from "styled-components/macro"; import styled from "styled-components/macro";
import { decodeTime } from "ulid";
import { useTriggerEvents } from "preact-context-menu"; import { useTriggerEvents } from "preact-context-menu";
import { Text } from "preact-i18n";
import { Row } from "@revoltchat/ui";
import { TextReact } from "../../../lib/i18n"; import { TextReact } from "../../../lib/i18n";
import { useApplicationState } from "../../../mobx/State";
import { dayjs } from "../../../context/Locale";
import Markdown from "../../markdown/Markdown"; import Markdown from "../../markdown/Markdown";
import Tooltip from "../Tooltip";
import UserShort from "../user/UserShort"; import UserShort from "../user/UserShort";
import MessageBase, { MessageDetail, MessageInfo } from "./MessageBase"; import MessageBase, { MessageDetail, MessageInfo } from "./MessageBase";
@ -78,6 +87,8 @@ export const SystemMessage = observer(
const data = message.asSystemMessage; const data = message.asSystemMessage;
if (!data) return null; if (!data) return null;
const settings = useApplicationState().settings;
const SystemMessageIcon = const SystemMessageIcon =
iconDictionary[data.type as API.SystemMessage["type"]] ?? iconDictionary[data.type as API.SystemMessage["type"]] ??
InfoCircle; InfoCircle;
@ -103,16 +114,39 @@ export const SystemMessage = observer(
case "user_joined": case "user_joined":
case "user_left": case "user_left":
case "user_kicked": case "user_kicked":
case "user_banned": case "user_banned": {
const createdAt = data.user ? decodeTime(data.user._id) : null;
children = ( children = (
<Row centred>
<TextReact <TextReact
id={`app.main.channel.system.${data.type}`} id={`app.main.channel.system.${data.type}`}
fields={{ fields={{
user: <UserShort user={data.user} />, 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; break;
}
case "channel_renamed": case "channel_renamed":
children = ( children = (
<TextReact <TextReact

View file

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

View file

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

View file

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

View file

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

View file

@ -26,6 +26,20 @@ export default function AppearanceOptions() {
<Text id="app.settings.pages.appearance.appearance_options.show_send_desc" /> <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 /> <hr />
<h3> <h3>
<Text id="app.settings.pages.appearance.theme_options.title" /> <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 { takeError } from "../../client/jsx/error";
import { modalController } from "../ModalController"; import { modalController } from "../ModalController";
import { ModalProps } from "../types"; import { ModalProps } from "../types";
import { IS_REVOLT } from "../../../version";
/** /**
* Code block which displays invite * Code block which displays invite
@ -78,7 +79,7 @@ export default function CreateInvite({
children: <Text id="app.context_menu.copy_link" />, children: <Text id="app.context_menu.copy_link" />,
onClick: () => onClick: () =>
modalController.writeText( 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; flex-direction: column;
div { div {
flex: 1; &.container {
max-width: 750px;
flex-grow: 1;
align-items: left;
}
&.header { &.header {
gap: 8px; gap: 8px;
padding: 3em; padding-top: 5em;
display: flex; display: flex;
text-align: center;
h1 {
margin: 0;
}
img {
max-height: 80px;
}
} }
&.form { &.form {
flex-grow: 1;
max-width: 420px;
img {
margin: auto;
display: block;
max-height: 420px;
border-radius: var(--border-radius);
}
input { input {
width: 100%; width: 100%;
} }

View file

@ -6,7 +6,8 @@ import { useState } from "preact/hooks";
import { Button, Preloader } from "@revoltchat/ui"; 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 FormField from "../../../../pages/login/FormField";
import { takeError } from "../../../client/jsx/error"; import { takeError } from "../../../client/jsx/error";
@ -36,12 +37,9 @@ export function OnboardingModal({
return ( return (
<div className={styles.onboarding}> <div className={styles.onboarding}>
<div className={styles.container}>
<div className={styles.header}> <div className={styles.header}>
<h1> <h1>{"Welcome to Revolt."}</h1>
<Text id="app.special.modals.onboarding.welcome" />
<br />
<img src={wideSVG} loading="eager" />
</h1>
</div> </div>
<div className={styles.form}> <div className={styles.form}>
{loading ? ( {loading ? (
@ -49,7 +47,15 @@ export function OnboardingModal({
) : ( ) : (
<> <>
<p> <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> </p>
<form <form
onSubmit={ onSubmit={
@ -65,14 +71,15 @@ export function OnboardingModal({
error={error} error={error}
/> />
</div> </div>
<Button type="submit"> <Button palette="accent">
<Text id="app.special.modals.actions.continue" /> {"Looks good!"}
</Button> </Button>
</form> </form>
</> </>
)} )}
</div> </div>
<div /> </div>
<img src={background} />
</div> </div>
); );
} }

View file

@ -41,8 +41,8 @@ import { ModalProps } from "../../types";
export const UserProfile = observer( export const UserProfile = observer(
({ ({
user_id, user_id,
dummy, isPlaceholder,
dummyProfile, placeholderProfile,
...props ...props
}: ModalProps<"user_profile">) => { }: ModalProps<"user_profile">) => {
const [profile, setProfile] = useState< const [profile, setProfile] = useState<
@ -87,21 +87,21 @@ export const UserProfile = observer(
}, [user_id]); }, [user_id]);
useEffect(() => { useEffect(() => {
if (dummy) { if (isPlaceholder) {
setProfile(dummyProfile); setProfile(placeholderProfile);
} }
}, [dummy, dummyProfile]); }, [isPlaceholder, placeholderProfile]);
useEffect(() => { useEffect(() => {
if (dummy) return; if (isPlaceholder) return;
if (session.state === "Online" && typeof mutual === "undefined") { if (session.state === "Online" && typeof mutual === "undefined") {
setMutual(null); setMutual(null);
user.fetchMutual().then(setMutual); user.fetchMutual().then(setMutual);
} }
}, [mutual, session.state, dummy, user]); }, [mutual, session.state, isPlaceholder, user]);
useEffect(() => { useEffect(() => {
if (dummy) return; if (isPlaceholder) return;
if (session.state === "Online" && typeof profile === "undefined") { if (session.state === "Online" && typeof profile === "undefined") {
setProfile(null); setProfile(null);
@ -109,7 +109,7 @@ export const UserProfile = observer(
user.fetchProfile().then(setProfile).catch(noop); user.fetchProfile().then(setProfile).catch(noop);
} }
} }
}, [profile, session.state, dummy, user]); }, [profile, session.state, isPlaceholder, user]);
useEffect(() => { useEffect(() => {
if ( if (
@ -169,7 +169,8 @@ export const UserProfile = observer(
onClick={() => onClick={() =>
modalController.writeText(user.username) modalController.writeText(user.username)
}> }>
@{user.username} {"@"}
{user.username}
</span> </span>
</Localizer> </Localizer>
{user.status?.text && ( {user.status?.text && (
@ -184,11 +185,11 @@ export const UserProfile = observer(
palette="accent" palette="accent"
compact compact
onClick={props.onClose}> onClick={props.onClose}>
Add to server {"Add to server" /* FIXME: i18n */}
</Button> </Button>
</Link> </Link>
)} )}
{user.relationship === "Friend" && ( {(user.relationship === "Friend" || user.bot) && (
<Localizer> <Localizer>
<Tooltip <Tooltip
content={ content={
@ -204,7 +205,7 @@ export const UserProfile = observer(
</Tooltip> </Tooltip>
</Localizer> </Localizer>
)} )}
{user.relationship === "User" && !dummy && ( {user.relationship === "User" && !isPlaceholder && (
<IconButton <IconButton
onClick={() => { onClick={() => {
props.onClose?.(); props.onClose?.();
@ -237,11 +238,13 @@ export const UserProfile = observer(
</div> </div>
{user.relationship !== "User" && ( {user.relationship !== "User" && (
<> <>
{!user.bot && (
<div <div
data-active={tab === "friends"} data-active={tab === "friends"}
onClick={() => setTab("friends")}> onClick={() => setTab("friends")}>
<Text id="app.special.popovers.user_profile.mutual_friends" /> <Text id="app.special.popovers.user_profile.mutual_friends" />
</div> </div>
)}
<div <div
data-active={tab === "groups"} data-active={tab === "groups"}
onClick={() => setTab("groups")}> onClick={() => setTab("groups")}>
@ -281,8 +284,9 @@ export const UserProfile = observer(
) : undefined} ) : undefined}
{user.bot ? ( {user.bot ? (
<> <>
{/* FIXME: this too */}
<div className={styles.category}> <div className={styles.category}>
bot owner {"bot owner"}
</div> </div>
<div <div
onClick={() => onClick={() =>
@ -432,12 +436,12 @@ export const UserProfile = observer(
</> </>
); );
if (dummy) return <div>{children}</div>; if (isPlaceholder) return <div>{children}</div>;
return ( return (
<Modal <Modal
{...props} {...props}
nonDismissable={dummy} nonDismissable={isPlaceholder}
transparent transparent
maxWidth="560px"> maxWidth="560px">
{children} {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"; type: "user_profile";
user_id: string; user_id: string;
dummy?: boolean; isPlaceholder?: boolean;
dummyProfile?: API.UserProfile; placeholderProfile?: API.UserProfile;
} }
| { | {
type: "create_bot"; 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) { if (sid && server) {
generateAction( generateAction(
{ {
@ -1015,15 +1019,28 @@ export default function ContextMenus() {
"open_server_settings", "open_server_settings",
); );
// workaround to move this above the delete/leave button
generateAction(
{ action: "copy_id", id },
"copy_sid",
);
pushDivider();
if (userId === server.owner) { if (userId === server.owner) {
generateAction( generateAction(
{ action: "delete_server", target: server }, { action: "delete_server", target: server },
"delete_server", "delete_server",
undefined,
undefined,
"var(--error)",
); );
} else { } else {
generateAction( generateAction(
{ action: "leave_server", target: server }, { action: "leave_server", target: server },
"leave_server", "leave_server",
undefined,
undefined,
"var(--error)",
); );
} }
} }
@ -1035,17 +1052,17 @@ export default function ContextMenus() {
}); });
} }
if (!hideIDButton) {
generateAction( generateAction(
{ action: "copy_id", id }, { action: "copy_id", id },
sid cid
? "copy_sid"
: cid
? "copy_cid" ? "copy_cid"
: message : message
? "copy_mid" ? "copy_mid"
: "copy_uid", : "copy_uid",
); );
} }
}
return elements; return elements;
}} }}

View file

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

View file

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

View file

@ -29,13 +29,13 @@ export const EXPERIMENTS: {
[key in Experiment]: { title: string; description: string }; [key in Experiment]: { title: string; description: string };
} = { } = {
dummy: { dummy: {
title: "Dummy Experiment", title: "Placeholder Experiment",
description: "This is a dummy experiment.", description: "This is a placeholder experiment.",
}, },
offline_users: { offline_users: {
title: "Re-enable offline users in large servers (>10k members)", title: "Re-enable offline users in large servers (>10k members)",
description: 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: { plugins: {
title: "Experimental Plugin API", title: "Experimental Plugin API",
@ -45,7 +45,7 @@ export const EXPERIMENTS: {
picker: { picker: {
title: "Custom Emoji", title: "Custom Emoji",
description: 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:seasonal": boolean;
"appearance:transparency": boolean; "appearance:transparency": boolean;
"appearance:show_send_button": boolean; "appearance:show_send_button": boolean;
"appearance:show_account_age": boolean;
"appearance:theme:base": "dark" | "light"; "appearance:theme:base": "dark" | "light";
"appearance:theme:overrides": Partial<Overrides>; "appearance:theme:overrides": Partial<Overrides>;
@ -79,7 +80,7 @@ export default class Settings
*/ */
@action set<T extends keyof ISettings>(key: T, value: ISettings[T]) { @action set<T extends keyof ISettings>(key: T, value: ISettings[T]) {
// Emoji needs to be immediately applied. // Emoji needs to be immediately applied.
if (key === 'appearance:emoji') { if (key === "appearance:emoji") {
setGlobalEmojiPack(value as EmojiPack); setGlobalEmojiPack(value as EmojiPack);
} }

View file

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

View file

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

View file

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

View file

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

View file

@ -165,18 +165,22 @@ export function Native() {
title="I understand there's no going back." title="I understand there's no going back."
description={ description={
<> <>
This will change the app to the 'dev' branch, {
instead loading the app from a local server on "This will change the app to the 'dev' branch, instead loading the app from a local server on your machine."
your machine. }
<br /> <br />
<b> <b>
Without a server running,{" "} {"Without a server running, "}
<span style={{ color: "var(--error)" }}> <span style={{ color: "var(--error)" }}>
the app will not load! {"the app will not load!"}
</span> </span>
</b> </b>
<br /> <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}> <div className={styles.preview}>
<UserProfile <UserProfile
user_id={client.user!._id} user_id={client.user!._id}
dummy={true} isPlaceholder={true}
dummyProfile={profile} placeholderProfile={profile}
{...({} as any)} {...({} as any)}
/> />
</div> </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 { HelpCircle, Desktop, LogOut } from "@styled-icons/boxicons-regular";
import { import {
Safari, Safari,
@ -6,6 +6,7 @@ import {
Microsoftedge, Microsoftedge,
Linux, Linux,
Macos, Macos,
Ios,
Opera, Opera,
Samsung, Samsung,
Windowsxp, Windowsxp,
@ -99,7 +100,7 @@ export function Sessions() {
case /mac.*os/i.test(name): case /mac.*os/i.test(name):
return <Macos size={14} />; return <Macos size={14} />;
case /i(Pad)?os/i.test(name): case /i(Pad)?os/i.test(name):
return <Apple size={14} />; return <Ios size={14} />;
case /windows 7/i.test(name): case /windows 7/i.test(name):
return <Windowsxp size={14} />; return <Windowsxp size={14} />;
case /windows/i.test(name): case /windows/i.test(name):

View file

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

View file

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

View file

@ -1,6 +1,8 @@
import { HelpCircle } from "@styled-icons/boxicons-solid";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Server } from "revolt.js"; import { Server } from "revolt.js";
import styled from "styled-components";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useMemo, useState } from "preact/hooks"; import { useMemo, useState } from "preact/hooks";
@ -16,6 +18,7 @@ import {
Category, Category,
} from "@revoltchat/ui"; } from "@revoltchat/ui";
import Tooltip from "../../../components/common/Tooltip";
import { PermissionList } from "../../../components/settings/roles/PermissionList"; import { PermissionList } from "../../../components/settings/roles/PermissionList";
import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection"; import { RoleOrDefault } from "../../../components/settings/roles/RoleSelection";
import { modalController } from "../../../controllers/modals/ModalController"; 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. // Consolidate all permissions that we can change right now.
const currentRoles = useRoles(server); 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 ( return (
<PermissionsLayout <PermissionsLayout
server={server} server={server}
@ -147,6 +164,30 @@ export const Roles = observer(({ server }: Props) => {
/> />
</p> </p>
</section> </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> <section>
<Category> <Category>
<Text id="app.settings.permissions.role_colour" /> <Text id="app.settings.permissions.role_colour" />

View file

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