Compare commits

...

8 commits

Author SHA1 Message Date
Declan Chidlow
b2985ae155
Merge cd4d8eb96d into 5de18192b2 2024-10-27 15:52:13 -07:00
Paul Makles
5de18192b2
chore: delete .env.production 2024-10-27 22:31:42 +00:00
Paul Makles
ef4218d12b
merge branch: 'insert/member-subscriptions' 2024-10-27 22:00:22 +00:00
Paul Makles
df4f6578f7
feat: add session token to file uploads 2024-10-27 21:59:16 +00:00
Declan Chidlow
cd4d8eb96d
Merge branch 'revoltchat:master' into master 2024-08-28 10:00:32 +08:00
Paul Makles
c972e6813f
fix: should always call subscribe 2024-06-19 18:16:30 +01:00
Paul Makles
61304f18c2
feat: implement support for Subscribe event 2024-06-19 17:40:59 +01:00
Declan Chidlow
ffee147aed Fix ability to set appearence of Focus status and remove unused references to streaming status 2024-06-05 15:33:48 +08:00
16 changed files with 139 additions and 58 deletions

4
.env
View file

@ -1,5 +1,5 @@
# VITE_API_URL=https://api.revolt.chat # VITE_API_URL=https://api.revolt.chat
VITE_API_URL=https://app.revolt.chat/api # VITE_API_URL=https://app.revolt.chat/api
# VITE_API_URL=http://local.revolt.chat:8000 # VITE_API_URL=http://local.revolt.chat:8000
# VITE_API_URL=https://revolt.chat/api VITE_API_URL=https://revolt.chat/api
VITE_THEMES_URL=https://themes.revolt.chat VITE_THEMES_URL=https://themes.revolt.chat

View file

@ -1,10 +1,10 @@
import { Plugin } from "unified"; import { Plugin } from "unified";
import { visit } from "unist-util-visit"; import { visit } from "unist-util-visit";
export const remarkHtmlToText: Plugin = () => { export const remarkHtmlToText: Plugin = () => {
return (tree) => { return (tree) => {
visit(tree, "html", (node: { type: string; value: string }) => { visit(tree, "html", (node: { type: string; value: string }) => {
node.type = "text"; node.type = "text";
}); });
}; };
}; };

View file

@ -106,9 +106,9 @@ export default observer(() => {
"scrollbar-thumb", "scrollbar-thumb",
"scrollbar-track", "scrollbar-track",
"status-online", "status-online",
"status-away", "status-idle",
"status-focus",
"status-busy", "status-busy",
"status-streaming",
"status-invisible", "status-invisible",
"success", "success",
"warning", "warning",

View file

@ -67,13 +67,12 @@ export function PermissionSelect({
} }
return "Neutral"; return "Neutral";
} }
if (Long.fromNumber(value).and(permission).eq(permission)) { if (Long.fromNumber(value).and(permission).eq(permission)) {
return "Allow"; return "Allow";
} }
return "Neutral"; return "Neutral";
}, [value]); }, [value]);
function onSwitch(state: State) { function onSwitch(state: State) {

View file

@ -30,10 +30,9 @@ export type Variables =
| "tertiary-foreground" | "tertiary-foreground"
| "tooltip" | "tooltip"
| "status-online" | "status-online"
| "status-away" | "status-idle"
| "status-focus" | "status-focus"
| "status-busy" | "status-busy"
| "status-streaming"
| "status-invisible"; | "status-invisible";
// While this isn't used, it'd be good to keep this up to date as a reference or for future use // While this isn't used, it'd be good to keep this up to date as a reference or for future use
@ -283,10 +282,9 @@ export const PRESETS: Record<string, Theme> = {
"tertiary-background": "#4D4D4D", "tertiary-background": "#4D4D4D",
"tertiary-foreground": "#3a3a3a", "tertiary-foreground": "#3a3a3a",
"status-online": "#3ABF7E", "status-online": "#3ABF7E",
"status-away": "#F39F00", "status-idle": "#F39F00",
"status-focus": "#4799F0", "status-focus": "#4799F0",
"status-busy": "#F84848", "status-busy": "#F84848",
"status-streaming": "#977EFF",
"status-invisible": "#A5A5A5", "status-invisible": "#A5A5A5",
}, },
dark: { dark: {
@ -311,10 +309,9 @@ export const PRESETS: Record<string, Theme> = {
"tertiary-background": "#4D4D4D", "tertiary-background": "#4D4D4D",
"tertiary-foreground": "#848484", "tertiary-foreground": "#848484",
"status-online": "#3ABF7E", "status-online": "#3ABF7E",
"status-away": "#F39F00", "status-idle": "#F39F00",
"status-focus": "#4799F0", "status-focus": "#4799F0",
"status-busy": "#F84848", "status-busy": "#F84848",
"status-streaming": "#977EFF",
"status-invisible": "#A5A5A5", "status-invisible": "#A5A5A5",
}, },
}; };

View file

@ -10,7 +10,7 @@ export function takeError(error: any): string {
case 429: case 429:
return "TooManyRequests"; return "TooManyRequests";
case 401: case 401:
return "Unauthorized" return "Unauthorized";
case 403: case 403:
return "Forbidden"; return "Forbidden";
default: default:

View file

@ -12,7 +12,7 @@ import { IconButton, Preloader } from "@revoltchat/ui";
import { determineFileSize } from "../../../../lib/fileSize"; import { determineFileSize } from "../../../../lib/fileSize";
import { modalController } from "../../../modals/ModalController"; import { modalController } from "../../../modals/ModalController";
import { useClient } from "../../ClientController"; import { clientController, useClient } from "../../ClientController";
import { takeError } from "../error"; import { takeError } from "../error";
type BehaviourType = type BehaviourType =
@ -67,9 +67,16 @@ export async function uploadFile(
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
const client = clientController.getActiveSession()?.client;
const sesToken =
typeof client?.session === "string"
? client.session
: client?.session?.token;
const res = await Axios.post(`${autumnURL}/${tag}`, formData, { const res = await Axios.post(`${autumnURL}/${tag}`, formData, {
headers: { headers: {
"Content-Type": "multipart/form-data", "Content-Type": "multipart/form-data",
"X-Session-Token": sesToken,
}, },
...config, ...config,
}); });

View file

@ -7,10 +7,10 @@ import { ModalForm } from "@revoltchat/ui";
import { noopAsync } from "../../../lib/js"; import { noopAsync } from "../../../lib/js";
import { IS_REVOLT } from "../../../version";
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
@ -79,7 +79,9 @@ 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(
IS_REVOLT ? `https://rvlt.gg/${code}` : `${window.location.host}/invite/${code}` IS_REVOLT
? `https://rvlt.gg/${code}`
: `${window.location.host}/invite/${code}`,
), ),
}, },
]} ]}

View file

@ -2,29 +2,32 @@ import { ChevronRight, Trash } from "@styled-icons/boxicons-regular";
import { Cog, UserVoice } from "@styled-icons/boxicons-solid"; import { Cog, UserVoice } from "@styled-icons/boxicons-solid";
import { isFirefox } from "react-device-detect"; import { isFirefox } from "react-device-detect";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { Channel, Message, Server, User, API, Permission, UserPermission, Member } from "revolt.js"; import {
Channel,
Message,
Server,
User,
API,
Permission,
UserPermission,
Member,
} from "revolt.js";
import {
ContextMenuWithData,
import { ContextMenuWithData, MenuItem, openContextMenu } from "preact-context-menu"; MenuItem,
openContextMenu,
} from "preact-context-menu";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { Column, IconButton, LineDivider } from "@revoltchat/ui"; import { Column, IconButton, LineDivider } from "@revoltchat/ui";
import { useApplicationState } from "../mobx/State"; import { useApplicationState } from "../mobx/State";
import { QueuedMessage } from "../mobx/stores/MessageQueue"; import { QueuedMessage } from "../mobx/stores/MessageQueue";
import { NotificationState } from "../mobx/stores/NotificationOptions"; import { NotificationState } from "../mobx/stores/NotificationOptions";
import CMNotifications from "./contextmenu/CMNotifications"; import CMNotifications from "./contextmenu/CMNotifications";
import Tooltip from "../components/common/Tooltip"; import Tooltip from "../components/common/Tooltip";
import UserStatus from "../components/common/user/UserStatus"; import UserStatus from "../components/common/user/UserStatus";
import { useSession } from "../controllers/client/ClientController"; import { useSession } from "../controllers/client/ClientController";
@ -33,7 +36,6 @@ import { modalController } from "../controllers/modals/ModalController";
import { internalEmit } from "./eventEmitter"; import { internalEmit } from "./eventEmitter";
import { getRenderer } from "./renderer/Singleton"; import { getRenderer } from "./renderer/Singleton";
interface ContextMenuData { interface ContextMenuData {
user?: string; user?: string;
server?: string; server?: string;
@ -1293,4 +1295,4 @@ export default function ContextMenus() {
<CMNotifications /> <CMNotifications />
</> </>
); );
} }

View file

@ -82,11 +82,10 @@ export default class SAudio {
getAudio(path: string) { getAudio(path: string) {
if (this.cache.has(path)) { if (this.cache.has(path)) {
return this.cache.get(path)!; return this.cache.get(path)!;
} }
const el = new Audio(path); const el = new Audio(path);
this.cache.set(path, el); this.cache.set(path, el);
return el; return el;
} }
loadCache() { loadCache() {
@ -100,7 +99,7 @@ export default class SAudio {
try { try {
audio.play(); audio.play();
} catch (err) { } catch (err) {
console.error("Hit error while playing", `${sound }:`, err); console.error("Hit error while playing", `${sound}:`, err);
} }
} }
} }

View file

@ -110,7 +110,7 @@ export default class STheme {
for (const key of Object.keys(variables)) { for (const key of Object.keys(variables)) {
const value = variables[key]; const value = variables[key];
if (typeof value === "string") { if (typeof value === "string") {
variables[`${key }-contrast`] = getContrastingColour(value); variables[`${key}-contrast`] = getContrastingColour(value);
} }
} }

View file

@ -1,5 +1,6 @@
import { Hash } from "@styled-icons/boxicons-regular"; import { Hash } from "@styled-icons/boxicons-regular";
import { Ghost } from "@styled-icons/boxicons-solid"; import { Ghost } from "@styled-icons/boxicons-solid";
import dayjs from "dayjs";
import { reaction } from "mobx"; import { reaction } from "mobx";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Redirect, useParams } from "react-router-dom"; import { Redirect, useParams } from "react-router-dom";
@ -163,6 +164,7 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
const checkUnread = () => const checkUnread = () =>
channel.unread && channel.unread &&
document.hasFocus() &&
channel.client.unreads!.markRead( channel.client.unreads!.markRead(
channel._id, channel._id,
channel.last_message_id!, channel.last_message_id!,
@ -176,6 +178,46 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
); );
}, [channel]); }, [channel]);
useEffect(() => {
let lastSubscribed: number | undefined;
function subscribe() {
if (document.hasFocus()) {
if (
!lastSubscribed ||
dayjs().subtract(10, "minutes").isAfter(lastSubscribed)
) {
lastSubscribed = +new Date();
channel.server?.subscribe();
}
}
}
// Trigger logic every minute
const subTimer = setInterval(subscribe, 60e3);
subscribe();
function onFocus() {
// Mark channel as read if it's unread
if (channel.unread) {
channel.client.unreads!.markRead(
channel._id,
channel.last_message_id!,
true,
);
}
// Subscribe to channel if expired
subscribe();
}
addEventListener("focus", onFocus);
return () => {
removeEventListener("focus", onFocus);
clearInterval(subTimer);
};
}, [channel]);
return ( return (
<AgeGate <AgeGate
type="channel" type="channel"

View file

@ -51,7 +51,7 @@ 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 isOctober = !isTouchscreenDevice && new Date().getMonth() === 9;
const snowflakes = useMemo(() => { const snowflakes = useMemo(() => {
const flakes: string[] = []; const flakes: string[] = [];

View file

@ -103,10 +103,43 @@ export const Members = ({ server }: Props) => {
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
useEffect(() => { useEffect(() => {
server function fetch() {
.fetchMembers() server
.then((data) => data.members) .fetchMembers()
.then(setData); .then((data) => data.members)
.then(setData);
}
fetch();
// Members may be invalidated if we stop receiving events
// This is not very accurate, this should be tracked within
// revolt.js so we know the true validity.
let valid = true,
invalidationTimer: number;
function waitToInvalidate() {
invalidationTimer = setTimeout(() => {
valid = false;
}, 15 * 60e3) as never; // 15 minutes
}
function cancelInvalidation() {
if (!valid) {
fetch();
valid = true;
}
clearTimeout(invalidationTimer);
}
addEventListener("blur", waitToInvalidate);
addEventListener("focus", cancelInvalidation);
return () => {
removeEventListener("blur", waitToInvalidate);
removeEventListener("focus", cancelInvalidation);
};
}, [server, setData]); }, [server, setData]);
const members = useMemo( const members = useMemo(

View file

@ -89,7 +89,7 @@
} }
&.idle { &.idle {
background: var(--status-away); background: var(--status-idle);
} }
&.focus { &.focus {

View file

@ -29,7 +29,7 @@ precacheAndRoute(
} }
for (const key of locale_keys) { for (const key of locale_keys) {
if (fn.startsWith(`${key }.`)) { if (fn.startsWith(`${key}.`)) {
return false; return false;
} }
} }