diff --git a/package.json b/package.json
index daf995c6..a7ee8868 100644
--- a/package.json
+++ b/package.json
@@ -145,8 +145,8 @@
"react-scroll": "^1.8.2",
"react-virtualized-auto-sizer": "^1.0.5",
"react-virtuoso": "^1.10.4",
- "revolt-api": "0.5.3-alpha.10",
- "revolt.js": "5.2.5",
+ "revolt-api": "0.5.3-alpha.12",
+ "revolt.js": "5.2.7",
"rimraf": "^3.0.2",
"sass": "^1.35.1",
"shade-blend-color": "^1.0.0",
diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx
index 68517fd3..b3ead6b3 100644
--- a/src/components/common/messaging/Message.tsx
+++ b/src/components/common/messaging/Message.tsx
@@ -3,9 +3,10 @@ import { Message as MessageObject } from "revolt.js/dist/maps/Messages";
import { attachContextMenu } from "preact-context-menu";
import { memo } from "preact/compat";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
import { internalEmit } from "../../../lib/eventEmitter";
+import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
import { QueuedMessage } from "../../../mobx/stores/MessageQueue";
@@ -88,6 +89,7 @@ const Message = observer(
// ! FIXME(?): animate on hover
const [mouseHovering, setAnimate] = useState(false);
+ useEffect(() => setAnimate(false), [replacement]);
return (
@@ -175,12 +177,14 @@ const Message = observer(
{message.embeds?.map((embed, index) => (
))}
- {mouseHovering && !replacement && (
-
- )}
+ {mouseHovering &&
+ !replacement &&
+ !isTouchscreenDevice && (
+
+ )}
diff --git a/src/components/common/messaging/bars/MessageOverlayBar.tsx b/src/components/common/messaging/bars/MessageOverlayBar.tsx
index 4eec2bd2..d47ac6d6 100644
--- a/src/components/common/messaging/bars/MessageOverlayBar.tsx
+++ b/src/components/common/messaging/bars/MessageOverlayBar.tsx
@@ -12,8 +12,11 @@ import { Message as MessageObject } from "revolt.js/dist/maps/Messages";
import styled from "styled-components";
import { openContextMenu } from "preact-context-menu";
+import { useEffect, useState } from "preact/hooks";
import { internalEmit } from "../../../../lib/eventEmitter";
+import { shiftKeyPressed } from "../../../../lib/modifiers";
+import { getRenderer } from "../../../../lib/renderer/Singleton";
import { QueuedMessage } from "../../../../mobx/stores/MessageQueue";
@@ -89,9 +92,24 @@ const Divider = styled.div`
export const MessageOverlayBar = observer(({ message, queued }: Props) => {
const client = useClient();
- const { openScreen } = useIntermediate();
+ const { openScreen, writeClipboard } = useIntermediate();
const isAuthor = message.author_id === client.user!._id;
+ const [copied, setCopied] = useState<"link" | "id">(null!);
+ const [extraActions, setExtra] = useState(shiftKeyPressed);
+
+ useEffect(() => {
+ const handler = (ev: KeyboardEvent) => setExtra(ev.shiftKey);
+
+ document.addEventListener("keyup", handler);
+ document.addEventListener("keydown", handler);
+
+ return () => {
+ document.removeEventListener("keyup", handler);
+ document.removeEventListener("keydown", handler);
+ };
+ });
+
return (
@@ -120,12 +138,14 @@ export const MessageOverlayBar = observer(({ message, queued }: Props) => {
ChannelPermission.ManageMessages) ? (
- openScreen({
- id: "special_prompt",
- type: "delete_message",
- target: message,
- } as unknown as Screen)
+ onClick={(e) =>
+ e.shiftKey
+ ? message.delete()
+ : openScreen({
+ id: "special_prompt",
+ type: "delete_message",
+ target: message,
+ } as unknown as Screen)
}>
@@ -143,43 +163,54 @@ export const MessageOverlayBar = observer(({ message, queued }: Props) => {
-
-
-
- openContextMenu("Menu", {
- message,
- contextualChannel: message.channel_id,
- queued,
- })
- }>
-
-
-
-
-
- openContextMenu("Menu", {
- message,
- contextualChannel: message.channel_id,
- queued,
- })
- }>
-
-
-
-
-
- openContextMenu("Menu", {
- message,
- contextualChannel: message.channel_id,
- queued,
- })
- }>
-
-
-
+ {extraActions && (
+ <>
+
+
+ {
+ const messages = getRenderer(
+ message.channel!,
+ ).messages;
+ const index = messages.findIndex(
+ (x) => x._id === message._id,
+ );
+
+ let unread_id = message._id;
+ if (index > 0) {
+ unread_id = messages[index - 1]._id;
+ }
+
+ internalEmit("NewMessages", "mark", unread_id);
+ message.channel?.ack(unread_id, true);
+ }}>
+
+
+
+
+ {
+ setCopied("link");
+ writeClipboard(message.url);
+ }}>
+
+
+
+
+ {
+ setCopied("id");
+ writeClipboard(message._id);
+ }}>
+
+
+
+ >
+ )}
);
});
diff --git a/src/components/navigation/items/ButtonItem.tsx b/src/components/navigation/items/ButtonItem.tsx
index 1a0cced6..a9a7a895 100644
--- a/src/components/navigation/items/ButtonItem.tsx
+++ b/src/components/navigation/items/ButtonItem.tsx
@@ -153,12 +153,13 @@ export const ChannelButton = observer((props: ChannelProps) => {
}
const { openScreen } = useIntermediate();
+ const alerting = alert && !muted && !active;
return (
{
)}
- {alert && !muted && (
+ {alerting && (
{alertCount}
diff --git a/src/lib/eventEmitter.ts b/src/lib/eventEmitter.ts
index b4f86958..1ad13148 100644
--- a/src/lib/eventEmitter.ts
+++ b/src/lib/eventEmitter.ts
@@ -30,3 +30,4 @@ export function internalEmit(ns: string, event: string, ...args: unknown[]) {
// - Modal/close
// - PWA/update
// - NewMessages/hide
+// - NewMessages/mark
diff --git a/src/lib/modifiers.ts b/src/lib/modifiers.ts
new file mode 100644
index 00000000..105f81c0
--- /dev/null
+++ b/src/lib/modifiers.ts
@@ -0,0 +1,18 @@
+/**
+ * Utility file for detecting whether the
+ * shift key is currently pressed or not.
+ */
+
+export let shiftKeyPressed = false;
+
+if (typeof window !== "undefined") {
+ document.addEventListener("keydown", (ev) => {
+ if (ev.shiftKey) shiftKeyPressed = true;
+ else shiftKeyPressed = false;
+ });
+
+ document.addEventListener("keyup", (ev) => {
+ if (ev.shiftKey) shiftKeyPressed = true;
+ else shiftKeyPressed = false;
+ });
+}
diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx
index ea1f5a5c..cd550257 100644
--- a/src/pages/channels/Channel.tsx
+++ b/src/pages/channels/Channel.tsx
@@ -7,9 +7,10 @@ import { Channel as ChannelI } from "revolt.js/dist/maps/Channels";
import styled from "styled-components/macro";
import { Text } from "preact-i18n";
-import { useEffect, useMemo } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
import ErrorBoundary from "../../lib/ErrorBoundary";
+import { internalSubscribe } from "../../lib/eventEmitter";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
import { useApplicationState } from "../../mobx/State";
@@ -123,17 +124,30 @@ export function Channel({ id, server_id }: { id: string; server_id: string }) {
const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
const layout = useApplicationState().layout;
- // Cache the unread location.
- const last_id = useMemo(
+ // Store unread location.
+ const [lastId, setLastId] = useState
(undefined);
+
+ useEffect(
() =>
- (channel.unread
- ? channel.client.unreads?.getUnread(channel._id)?.last_id
- : undefined) ?? undefined,
- [channel],
+ internalSubscribe("NewMessages", "hide", () =>
+ setLastId(undefined),
+ ),
+ [],
+ );
+
+ useEffect(
+ () => internalSubscribe("NewMessages", "mark", setLastId as any),
+ [],
);
// Mark channel as read.
useEffect(() => {
+ setLastId(
+ channel.unread
+ ? channel.client.unreads?.getUnread(channel._id)?.last_id
+ : undefined ?? undefined,
+ );
+
const checkUnread = () =>
channel.unread &&
channel.client.unreads!.markRead(
@@ -165,8 +179,8 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
-
-
+
+
diff --git a/yarn.lock b/yarn.lock
index 09f7b985..80ba7071 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4203,20 +4203,15 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
-revolt-api@0.5.3-alpha.10:
- version "0.5.3-alpha.10"
- resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.10.tgz#973f7d63dbce5ddb0c5ec17c7e89a0474770d66f"
- integrity sha512-v+eSPLWpiqmfHafPDeCF1nvd4Ff43ktyvVgGHt7aQN2v9Qm1BFrmpWHWoDPeZpXlW6mtpo+1t74nLK6VCsZ+VA==
+revolt-api@0.5.3-alpha.12:
+ version "0.5.3-alpha.12"
+ resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.12.tgz#78f25b567b840c1fd072595526592a422cb01f25"
+ integrity sha512-MM42oI5+5JJMnAs3JiOwSQOy/SUYzYs3M8YRC5QI4G6HU7CfyB2HNWh5jFsyRlcLdSi13dGazHm31FUPHsxOzw==
-revolt-api@^0.5.3-alpha.9:
- version "0.5.3-alpha.9"
- resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.9.tgz#46e75b7d8f9c6702df39039b829dddbb7897f237"
- integrity sha512-L8K9uPV3ME8bLdtWm8L9iPQvFM0GghA+5LzmWFjd6Gbn56u22ZYub2lABi4iHrWgeA2X41dGSsuSBgHSlts9Og==
-
-revolt.js@5.2.5:
- version "5.2.5"
- resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.2.5.tgz#ffd2b759a807f3430f6018459acdf57e95f842a5"
- integrity sha512-rS6FOc7XPfe/BuGnuCvM1N/CEGMtR/bHy4le5154MF9bbpebLw205niq7rq5TMsXT/f7J4qlOI7xX0rvKprn6w==
+revolt.js@5.2.7:
+ version "5.2.7"
+ resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.2.7.tgz#7b887329913494a2caf02c9828685d63551890db"
+ integrity sha512-KNoQqLrdd/B8zryu2fhWim9rO5OEkouhCZj4nU+upwrekz30DjxqWgZCup/apKXE8PSmrhSgWdKT8SHCBXOxFQ==
dependencies:
"@insertish/exponential-backoff" "3.1.0-patch.0"
"@insertish/isomorphic-ws" "^4.0.1"
@@ -4226,7 +4221,7 @@ revolt.js@5.2.5:
lodash.flatten "^4.4.0"
lodash.isequal "^4.5.0"
mobx "^6.3.2"
- revolt-api "^0.5.3-alpha.9"
+ revolt-api "0.5.3-alpha.12"
ulid "^2.3.0"
ws "^8.2.2"