diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index 70ee0633..c38ed11f 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -55,11 +55,11 @@ export type UploadState =
| { type: "none" }
| { type: "attached"; files: File[] }
| {
- type: "uploading";
- files: File[];
- percent: number;
- cancel: CancelTokenSource;
- }
+ type: "uploading";
+ files: File[];
+ percent: number;
+ cancel: CancelTokenSource;
+ }
| { type: "sending"; files: File[] }
| { type: "failed"; files: File[]; error: string };
@@ -257,25 +257,27 @@ export default observer(({ channel }: Props) => {
);
}
- console.log(channel)
- if (!channel.havePermission("SendMessage") && channel.recipient?.relationship == "Blocked" || channel.recipient?.relationship == "BlockedOther"){
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
+ console.log(channel) //|| channel.channel_type != "DirectMessage"
+ if (channel.channel_type != "SavedMessages")
+ if (!channel.havePermission("SendMessage") && channel.channel_type == "TextChannel" || channel.recipient?.relationship == "Blocked" || channel.recipient?.relationship == "BlockedOther") {
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
// Push message content to draft.
const setMessage = useCallback(
(content?: string) => {
@@ -297,9 +299,9 @@ export default observer(({ channel }: Props) => {
const text =
action === "quote"
? `${content
- .split("\n")
- .map((x) => `> ${x}`)
- .join("\n")}\n\n`
+ .split("\n")
+ .map((x) => `> ${x}`)
+ .join("\n")}\n\n`
: `${content} `;
if (!state.draft.has(channel._id)) {
@@ -353,8 +355,8 @@ export default observer(({ channel }: Props) => {
toReplace == ""
? msg.content.toString() + newText
: msg.content
- .toString()
- .replace(new RegExp(toReplace, flags), newText);
+ .toString()
+ .replace(new RegExp(toReplace, flags), newText);
if (newContent != msg.content) {
if (newContent.length == 0) {
@@ -432,10 +434,10 @@ export default observer(({ channel }: Props) => {
files,
percent: Math.round(
(i * 100 + (100 * e.loaded) / e.total) /
- Math.min(
- files.length,
- CAN_UPLOAD_AT_ONCE,
- ),
+ Math.min(
+ files.length,
+ CAN_UPLOAD_AT_ONCE,
+ ),
),
cancel,
}),
@@ -628,42 +630,42 @@ export default observer(({ channel }: Props) => {
{/* {channel.havePermission("UploadFiles") ? ( */}
-
-
- setUploadState({ type: "none" })
- }
- onChange={(files) =>
- setUploadState({ type: "attached", files })
- }
- cancel={() =>
- uploadState.type === "uploading" &&
- uploadState.cancel.cancel("cancel")
- }
- append={(files) => {
- if (files.length === 0) return;
+
+
+ setUploadState({ type: "none" })
+ }
+ onChange={(files) =>
+ setUploadState({ type: "attached", files })
+ }
+ cancel={() =>
+ uploadState.type === "uploading" &&
+ uploadState.cancel.cancel("cancel")
+ }
+ append={(files) => {
+ if (files.length === 0) return;
- if (uploadState.type === "none") {
- setUploadState({ type: "attached", files });
- } else if (uploadState.type === "attached") {
- setUploadState({
- type: "attached",
- files: [...uploadState.files, ...files],
- });
- }
- }}
- />
-
+ if (uploadState.type === "none") {
+ setUploadState({ type: "attached", files });
+ } else if (uploadState.type === "attached") {
+ setUploadState({
+ type: "attached",
+ files: [...uploadState.files, ...files],
+ });
+ }
+ }}
+ />
+
{/* ) : (
)} */}
@@ -726,13 +728,13 @@ export default observer(({ channel }: Props) => {
placeholder={
channel.channel_type === "DirectMessage"
? translate("app.main.channel.message_who", {
- person: channel.recipient?.username,
- })
+ person: channel.recipient?.username,
+ })
: channel.channel_type === "SavedMessages"
- ? translate("app.main.channel.message_saved")
- : translate("app.main.channel.message_where", {
- channel_name: channel.name ?? undefined,
- })
+ ? translate("app.main.channel.message_saved")
+ : translate("app.main.channel.message_where", {
+ channel_name: channel.name ?? undefined,
+ })
}
disabled={
uploadState.type === "uploading" ||
diff --git a/src/components/common/messaging/PinMessageBox.tsx b/src/components/common/messaging/PinMessageBox.tsx
new file mode 100644
index 00000000..4fa9ba28
--- /dev/null
+++ b/src/components/common/messaging/PinMessageBox.tsx
@@ -0,0 +1,162 @@
+import {
+ InfoCircle,
+ UserPlus,
+ UserMinus,
+ ArrowToRight,
+ ArrowToLeft,
+ UserX,
+ ShieldX,
+ EditAlt,
+ Edit,
+ MessageSquareEdit,
+ Key,
+} from "@styled-icons/boxicons-solid";
+import { observer } from "mobx-react-lite";
+import { Message, Channel, 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";
+import { Pin } from "@styled-icons/boxicons-regular";
+import { useHistory } from "react-router-dom";
+
+const SystemContent = styled.div`
+ gap: 4px;
+ display: flex;
+ padding: 2px 0;
+ flex-wrap: wrap;
+ align-items: center;
+ flex-direction: row;
+ font-size: 14px;
+ color: var(--secondary-foreground);
+
+ span {
+ font-weight: 600;
+ color: var(--foreground);
+ }
+
+ svg {
+ margin-inline-end: 4px;
+ }
+
+ svg,
+ span {
+ cursor: pointer;
+ }
+
+ span:hover {
+ text-decoration: underline;
+ }
+`;
+
+interface Props {
+ attachContext?: boolean;
+ message: Message;
+ highlight?: boolean;
+ hideInfo?: boolean;
+ channel: Channel
+}
+
+const iconDictionary = {
+ user_added: UserPlus,
+ user_remove: UserMinus,
+ user_joined: ArrowToRight,
+ user_left: ArrowToLeft,
+ user_kicked: UserX,
+ user_banned: ShieldX,
+ channel_renamed: EditAlt,
+ channel_description_changed: Edit,
+ channel_icon_changed: MessageSquareEdit,
+ channel_ownership_changed: Key,
+ text: InfoCircle,
+};
+
+export const PinMessageBox = observer(
+ ({ attachContext, message, channel, highlight, hideInfo }: Props) => {
+ const data: any = message.system
+ if (!data) return null;
+ const history = useHistory();
+
+
+ let children = null;
+ let userName = message.client ? message.client.user?.username : ""
+
+
+ if (data.type as string == "message_pinned") {
+ children = children = (
+
{
+ if (channel.channel_type === "TextChannel") {
+ history.push(
+ `/server/${channel.server_id}/channel/${channel._id}/${data.id}`,
+ );
+ } else {
+ history.push(`/channel/${channel._id}/${data.id}`);
+ }
+ }}
+ >
+
+
+ );
+ }
+ if (data.type as string == "message_unpinned") {
+ children = children = (
+ {
+ if (channel.channel_type === "TextChannel") {
+ history.push(
+ `/server/${channel.server_id}/channel/${channel._id}/${data.id}`,
+ );
+ } else {
+ history.push(`/channel/${channel._id}/${data.id}`);
+ }
+ }}
+ >
+
+
+ );
+ }
+
+
+
+ return (
+
+
+
+ {!hideInfo && (
+
+
+ {/* */}
+
+ )}
+
+
+ {children}
+
+ );
+ },
+);
diff --git a/src/components/common/messaging/bars/PinnedMessage.tsx b/src/components/common/messaging/bars/PinnedMessage.tsx
new file mode 100644
index 00000000..793ac0ae
--- /dev/null
+++ b/src/components/common/messaging/bars/PinnedMessage.tsx
@@ -0,0 +1,423 @@
+import { LeftArrow, LeftArrowAlt, Pin, UpArrowAlt } from "@styled-icons/boxicons-regular";
+import { observer } from "mobx-react-lite";
+import { useHistory } from "react-router-dom";
+import { Channel } from "revolt.js";
+import { decodeTime } from "ulid";
+
+import { Text } from "preact-i18n";
+import { useEffect, useState } from "preact/hooks";
+
+import { internalSubscribe } from "../../../../lib/eventEmitter";
+import { getRenderer } from "../../../../lib/renderer/Singleton";
+
+import { dayjs } from "../../../../context/Locale";
+import styled, { css } from "styled-components/macro";
+
+import classNames from "classnames";
+import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice";
+import { useClient } from "../../../../controllers/client/ClientController";
+import { Message } from "revolt.js/esm";
+export const PinBar = styled.div<{ position: "top" | "bottom"; accent?: boolean }>`
+ z-index: 2;
+ position: relative;
+
+ @keyframes bottomBounce {
+ 0% {
+ transform: translateY(33px);
+ }
+ 100% {
+ transform: translateY(0px);
+ }
+ }
+
+ @keyframes topBounce {
+ 0% {
+ transform: translateY(-33px);
+ }
+ 100% {
+ transform: translateY(0px);
+ }
+ }
+
+ ${(props) =>
+ props.position === "top" &&
+ css`
+ top: 0;
+
+
+ animation: topBounce 1s cubic-bezier(0.2, 0.9, 0.5, 1.16)
+ forwards;
+ `}
+
+ ${(props) =>
+ props.position === "bottom" &&
+ css`
+ top: -28px;
+ animation: bottomBounce 340ms cubic-bezier(0.2, 0.9, 0.5, 1.16)
+ forwards;
+
+ ${() =>
+ isTouchscreenDevice &&
+ css`
+ top: -90px;
+ `}
+ `}
+
+ > div {
+ min-height: 120px;
+ max-height: 200px;
+
+ height: auto;
+ width: 40%;
+ right : 0px !important;
+ position: absolute;
+ display: block;
+ align-items: center;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 600;
+ padding: 0 8px;
+ user-select: none;
+ justify-content: space-between;
+ transition: color ease-in-out 0.08s;
+
+ white-space: nowrap;
+ overflow: scroll;
+ text-overflow: ellipsis;
+
+ ${(props) =>
+ props.accent
+ ? css`
+ color: var(--accent-contrast);
+ background-color: var(--hover)!important;
+ backdrop-filter: blur(20px);
+ `
+ : css`
+ color: var(--secondary-foreground);
+ background-color: rgba(
+ var(--secondary-background-rgb),
+ max(var(--min-opacity), 0.9)
+ );
+ backdrop-filter: blur(20px);
+ `}
+
+ ${(props) =>
+ props.position === "top"
+ ? css`
+ top: 48px;
+ border-radius: 0 0 var(--border-radius)
+ var(--border-radius);
+ `
+ : css`
+ border-radius: var(--border-radius) var(--border-radius) 0
+ 0;
+ `}
+
+ ${() =>
+ isTouchscreenDevice &&
+ css`
+ top: 56px;
+ `}
+
+ > div {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ &:hover {
+ color: var(--primary-text);
+ }
+
+ &:active {
+ transform: translateY(1px);
+ }
+
+ ${() =>
+ isTouchscreenDevice &&
+ css`
+ height: 34px;
+ padding: 0 12px;
+ `}
+ }
+
+ @media only screen and (max-width: 800px) {
+ .right > span {
+ display: none;
+ }
+ }
+`;
+
+
+
+
+
+export const PinIcon = styled.div<{ position: "top" | "bottom", accent?: boolean }>`
+ z-index: 2;
+ position: relative;
+
+ @keyframes bottomBounce {
+ 0% {
+ transform: translateY(33px);
+ }
+ 100% {
+ transform: translateY(0px);
+ }
+ }
+
+ @keyframes topBounce {
+ 0% {
+ transform: translateY(-33px);
+ }
+ 100% {
+ transform: translateY(0px);
+ }
+ }
+ ${(props) =>
+ props.accent
+ ? css`
+ color: var(--accent-contrast);
+ background-color: var(--hover)!important;
+ backdrop-filter: blur(20px);
+ `
+ : css`
+ color: var(--secondary-foreground);
+ background-color: rgba(
+ var(--secondary-background-rgb),
+ max(var(--min-opacity), 0.9)
+ );
+ backdrop-filter: blur(20px);
+ `}
+
+ ${(props) =>
+ props.position === "top" &&
+ css`
+ top: 5;
+ animation: topBounce 1s cubic-bezier(0.2, 0.9, 0.5, 1.16)
+ forwards;
+ `}
+
+
+ > div {
+ height: auto;
+ width: auto;
+ right : 5px !important;
+ position: absolute;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 600;
+ padding: 8px 8px;
+ user-select: none;
+ justify-content: space-between;
+ transition: color ease-in-out 0.08s;
+
+ white-space: nowrap;
+
+ ${(props) =>
+ props.accent
+ ? css`
+ color: var(--accent-contrast);
+ background-color: var(--hover)!important;
+ backdrop-filter: blur(20px);
+ `
+ : css`
+ color: var(--secondary-foreground);
+ background-color: rgba(
+ var(--secondary-background-rgb),
+ max(var(--min-opacity), 0.9)
+ );
+ backdrop-filter: blur(20px);
+ `}
+
+ ${(props) =>
+ props.position === "top"
+ ? css`
+ top: 52px;
+ border-radius: 0 0 var(--border-radius)
+ var(--border-radius);
+ `
+ : css`
+ border-radius: var(--border-radius) var(--border-radius) 0
+ 0;
+ `}
+
+ ${() =>
+ isTouchscreenDevice &&
+ css`
+ top: 56px;
+ `}
+
+
+ }
+
+ @media only screen and (max-width: 800px) {
+ .right > span {
+ display: none;
+ }
+ }
+`;
+
+
+
+
+export default observer(
+ ({ channel }: { channel: Channel; }) => {
+ const [hidden, setHidden] = useState(true);
+ const unhide = () => setHidden(false);
+
+ // useEffect(() => setHidden(false), [last_id]);
+ // useEffect(() => internalSubscribe("NewMessages", "hide", hide), []);
+ // useEffect(() => {
+ // const onKeyDown = (e: KeyboardEvent) =>
+ // e.key === "Escape" && hide();
+
+ // document.addEventListener("keydown", onKeyDown);
+ // return () => document.removeEventListener("keydown", onKeyDown);
+ // }, []);
+
+ // const extendedMessage = new MessageExtendedClass(client);
+
+
+ // useEffect(() => {
+ // if (last_id) {
+ // try {
+ // setTimeAgo(dayjs(decodeTime(last_id)).fromNow());
+ // } catch (err) { }
+ // }
+ // }, [last_id]);
+
+ const renderer = getRenderer(channel);
+ const history = useHistory();
+ if (renderer.state !== "RENDER") return null;
+ // if (!last_id) return null;
+ // if (hidden) return null;
+
+
+ // renderer.messages.slice().reverse().map((res, i) => {
+ // console.log(res, 8989)
+ // })
+ function truncateText(text: string, chars: number) {
+ if (text.length > chars) {
+ return text.slice(0, chars) + "..";
+ }
+ return text;
+ }
+
+
+
+ let pinFound = false
+ return (
+ <>
+
+
+
+
+
+ {!hidden &&
+
+
setHidden(true)}
+ style={{
+ backgroundColor: "var(--block)",
+ width: "100%",
+ position: "sticky",
+ top: "0px",
+ display: "flex",
+
+ justifyContent: "space-between",
+ borderRadius: "5px",
+ padding: "8px 8px"
+
+ }}>
+
+
setHidden(true)} />
+
+
+
+
+
+
+
+
+ {
+
+ renderer.messages.slice().reverse().map((msg, i) => {
+ if (msg.is_pinned) {
+ // console.log(msg, 8989)
+ let content = msg.content ? truncateText(msg.content, 20) : ""
+ pinFound = true
+ return (
+
+
{
+ // setHidden(true);
+ if (channel.channel_type === "TextChannel") {
+ history.push(
+ `/server/${channel.server_id}/channel/${channel._id}/${msg._id}`,
+ );
+ } else {
+ history.push(`/channel/${channel._id}/${msg._id}`);
+ }
+ }}
+
+
+ style={{ display: 'flex', paddingTop: "5px" }}>
+ <>. {" "}>
+
+
+ )
+ }
+
+ })
+
+
+
+ }
+
+ {!renderer.atTop &&
{
+ // setHidden(true);
+ renderer.loadTop()
+ }}
+
+
+ style={{ display: 'flex', paddingTop: "5px", justifyContent: "center" }}>
+
+
+
}
+
+
+
+
+
+
+
+ }
+ >
+ );
+ },
+);
diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx
index b51fd8cc..e3344df5 100644
--- a/src/lib/ContextMenus.tsx
+++ b/src/lib/ContextMenus.tsx
@@ -58,6 +58,7 @@ type Action =
| { action: "mark_as_read"; channel: Channel }
| { action: "mark_server_as_read"; server: Server }
| { action: "mark_unread"; message: Message }
+ | { action: "pin_message"; channel: any; message: any }
| { action: "retry_message"; message: QueuedMessage }
| { action: "cancel_message"; message: QueuedMessage }
| { action: "mention"; user: string }
@@ -87,32 +88,32 @@ type Action =
| { action: "create_channel"; target: Server }
| { action: "create_category"; target: Server }
| {
- action: "create_invite";
- target: Channel;
- }
+ action: "create_invite";
+ target: Channel;
+ }
| { action: "leave_group"; target: Channel }
| {
- action: "delete_channel";
- target: Channel;
- }
+ action: "delete_channel";
+ target: Channel;
+ }
| { action: "close_dm"; target: Channel }
| { action: "leave_server"; target: Server }
| { action: "delete_server"; target: Server }
| { action: "edit_identity"; target: Member }
| {
- action: "open_notification_options";
- channel?: Channel;
- server?: Server;
- }
+ action: "open_notification_options";
+ channel?: Channel;
+ server?: Server;
+ }
| { action: "open_settings" }
| { action: "open_channel_settings"; id: string }
| { action: "open_server_settings"; id: string }
| { action: "open_server_channel_settings"; server: string; id: string }
| {
- action: "set_notification_state";
- key: string;
- state?: NotificationState;
- }
+ action: "set_notification_state";
+ key: string;
+ state?: NotificationState;
+ }
| { action: "report"; target: User | Server | Message; messageId?: string };
// ! FIXME: I dare someone to re-write this
@@ -202,8 +203,50 @@ export default function ContextMenus() {
internalEmit("NewMessages", "mark", unread_id);
data.message.channel?.ack(unread_id, true);
}
+ case "pin_message":
+ {
+
+
+ const messages = getRenderer(
+ data.message.channel!,
+ ).messages;
+ const index = messages.findIndex(
+ (x) => x._id === data.message._id,
+ );
+ let message
+
+ if (index > 0) {
+ message = messages[index];
+ }
+
+ internalEmit("MessageBox", "pin", message);
+
+ // data.message.channel?.ack(pin_id, true);
+ }
break;
+
+ case "unpin_message":
+ {
+
+
+ const messages = getRenderer(
+ data.message.channel!,
+ ).messages;
+ const index = messages.findIndex(
+ (x) => x._id === data.message._id,
+ );
+ let message
+
+ if (index > 0) {
+ message = messages[index];
+ }
+
+ internalEmit("MessageBox", "unpin", message);
+
+ // data.message.channel?.ack(pin_id, true);
+ }
+ break;
case "retry_message":
{
const nonce = data.message.id;
@@ -513,9 +556,8 @@ export default function ContextMenus() {
"Open User in Admin Panel"
) : (
)}
@@ -573,7 +615,7 @@ export default function ContextMenus() {
const user = uid ? client.users.get(uid) : undefined;
const serverChannel =
targetChannel &&
- (targetChannel.channel_type === "TextChannel")
+ (targetChannel.channel_type === "TextChannel")
? targetChannel
: undefined;
@@ -585,8 +627,8 @@ export default function ContextMenus() {
(server
? server.permission
: serverChannel
- ? serverChannel.server?.permission
- : 0) || 0;
+ ? serverChannel.server?.permission
+ : 0) || 0;
const userPermissions = (user ? user.permission : 0) || 0;
if (unread) {
@@ -810,6 +852,24 @@ export default function ContextMenus() {
action: "mark_unread",
message,
});
+ if (sendPermission) {
+
+
+ if (message.is_pinned) {
+ generateAction({
+ action: "unpin_message",
+ channel,
+ message
+ });
+ } else {
+ generateAction({
+ action: "pin_message",
+ channel,
+ message
+ });
+ }
+
+ }
if (
typeof message.content === "string" &&
@@ -880,8 +940,8 @@ export default function ContextMenus() {
type === "Image"
? "open_image"
: type === "Video"
- ? "open_video"
- : "open_file",
+ ? "open_video"
+ : "open_file",
);
generateAction(
@@ -892,8 +952,8 @@ export default function ContextMenus() {
type === "Image"
? "save_image"
: type === "Video"
- ? "save_video"
- : "save_file",
+ ? "save_video"
+ : "save_file",
);
generateAction(
@@ -929,8 +989,8 @@ export default function ContextMenus() {
type === "Image"
? "open_image"
: type === "Video"
- ? "open_video"
- : "open_file",
+ ? "open_video"
+ : "open_file",
);
generateAction(
@@ -941,8 +1001,8 @@ export default function ContextMenus() {
type === "Image"
? "save_image"
: type === "Video"
- ? "save_video"
- : "save_file",
+ ? "save_video"
+ : "save_file",
);
generateAction(
@@ -1130,8 +1190,8 @@ export default function ContextMenus() {
type: cid
? "channel"
: message
- ? "message"
- : "user",
+ ? "message"
+ : "user",
},
"admin",
);
@@ -1158,8 +1218,8 @@ export default function ContextMenus() {
cid
? "copy_cid"
: message
- ? "copy_mid"
- : "copy_uid",
+ ? "copy_mid"
+ : "copy_uid",
);
}
}
diff --git a/src/lib/eventEmitter.ts b/src/lib/eventEmitter.ts
index c54460a9..85cebc7c 100644
--- a/src/lib/eventEmitter.ts
+++ b/src/lib/eventEmitter.ts
@@ -25,6 +25,8 @@ export function internalEmit(ns: string, event: string, ...args: unknown[]) {
// - Intermediate/open_profile
// - Intermediate/navigate
// - MessageBox/append
+// - MessageBox/pin
+// - MessageBox/unpin
// - TextArea/focus
// - ReplyBar/add
// - Modal/close
diff --git a/src/lib/renderer/simple/SimpleRenderer.ts b/src/lib/renderer/simple/SimpleRenderer.ts
index 98db77b6..19222589 100644
--- a/src/lib/renderer/simple/SimpleRenderer.ts
+++ b/src/lib/renderer/simple/SimpleRenderer.ts
@@ -28,6 +28,7 @@ export const SimpleRenderer: RendererRoutines = {
renderer.channel
.fetchMessagesWithUsers({})
.then(({ messages }) => {
+ console.log(messages, 9090);
messages.reverse();
runInAction(() => {
diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx
index ae219cb2..32418e39 100644
--- a/src/pages/channels/Channel.tsx
+++ b/src/pages/channels/Channel.tsx
@@ -26,6 +26,7 @@ import { PageHeader } from "../../components/ui/Header";
import { useClient } from "../../controllers/client/ClientController";
import ChannelHeader from "./ChannelHeader";
import { MessageArea } from "./messaging/MessageArea";
+import PinnedMessage from "../../components/common/messaging/bars/PinnedMessage";
const ChannelMain = styled.div.attrs({ "data-component": "channel" })`
flex-grow: 1;
@@ -99,7 +100,7 @@ export const Channel = observer(
const client = useClient();
const state = useApplicationState();
- if (!client.channels.exists(id) && client.servers.get(server_id)) {
+ if (!client.channels.exists(id) && server_id) {
if (server_id) {
const server = client.servers.get(server_id);
if (server && server.channel_ids.length > 0) {
@@ -110,7 +111,7 @@ export const Channel = observer(
target_id = last_id;
}
}
-
+
return (
{
+
diff --git a/src/pages/channels/messaging/MessageArea.tsx b/src/pages/channels/messaging/MessageArea.tsx
index d0ae8a5f..375d1a39 100644
--- a/src/pages/channels/messaging/MessageArea.tsx
+++ b/src/pages/channels/messaging/MessageArea.tsx
@@ -23,11 +23,12 @@ import { internalEmit, internalSubscribe } from "../../../lib/eventEmitter";
import { getRenderer } from "../../../lib/renderer/Singleton";
import { ScrollState } from "../../../lib/renderer/types";
-import { useSession } from "../../../controllers/client/ClientController";
+import { useClient, useSession } from "../../../controllers/client/ClientController";
import RequiresOnline from "../../../controllers/client/jsx/RequiresOnline";
import { modalController } from "../../../controllers/modals/ModalController";
import ConversationStart from "./ConversationStart";
import MessageRenderer from "./MessageRenderer";
+import { Message } from "revolt.js/esm";
const Area = styled.div.attrs({ "data-scroll-offset": "with-padding" })`
height: 100%;
@@ -115,8 +116,8 @@ export const MessageArea = observer(({ last_id, channel }: Props) => {
101,
ref.current
? ref.current.scrollTop +
- (ref.current.scrollHeight -
- scrollState.current.previousHeight)
+ (ref.current.scrollHeight -
+ scrollState.current.previousHeight)
: 101,
),
{
@@ -148,20 +149,48 @@ export const MessageArea = observer(({ last_id, channel }: Props) => {
const atBottom = (offset = 0) =>
ref.current
? Math.floor(ref.current?.scrollHeight - ref.current?.scrollTop) -
- offset <=
- ref.current?.clientHeight
+ offset <=
+ ref.current?.clientHeight
: true;
const atTop = (offset = 0) =>
ref.current ? ref.current.scrollTop <= offset : false;
+ const client = useClient()
+ function pin(message: Message) {
+ client.api.post(`/channels/${message.channel_id}/messages/${message._id}/pin` as any)
+ message.is_pinned = true
+ }
+ function unpin(message: Message) {
+ client.api.delete(`/channels/${message.channel_id}/messages/${message._id}/pin` as any)
+ message.is_pinned = false
+ }
// ? Handle global jump to bottom, e.g. when editing last message in chat.
useEffect(() => {
+
return internalSubscribe("MessageArea", "jump_to_bottom", () =>
setScrollState({ type: "ScrollToBottom" }),
);
}, [setScrollState]);
+ useEffect(() => {
+
+
+ return internalSubscribe(
+ "MessageBox",
+ "pin",
+ pin as (...args: unknown[]) => void,
+ );
+ }, []);
+ useEffect(() => {
+
+
+ return internalSubscribe(
+ "MessageBox",
+ "unpin",
+ unpin as (...args: unknown[]) => void,
+ );
+ }, []);
// ? Handle events from renderer.
useLayoutEffect(
() => setScrollState(renderer.scrollState),
diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx
index 950c56d5..c9ad66a3 100644
--- a/src/pages/channels/messaging/MessageRenderer.tsx
+++ b/src/pages/channels/messaging/MessageRenderer.tsx
@@ -23,6 +23,7 @@ import { useClient } from "../../../controllers/client/ClientController";
import RequiresOnline from "../../../controllers/client/jsx/RequiresOnline";
import ConversationStart from "./ConversationStart";
import MessageEditor from "./MessageEditor";
+import { PinMessageBox } from "../../../components/common/messaging/PinMessageBox";
interface Props {
last_id?: string;
@@ -150,8 +151,9 @@ export default observer(({ last_id, renderer, highlight }: Props) => {
);
blocked = 0;
}
+ let lastPinned = null
- for (const message of renderer.messages) {
+ for (const [i, message] of renderer.messages.entries()) {
if (previous) {
compare(
message._id,
@@ -162,8 +164,21 @@ export default observer(({ last_id, renderer, highlight }: Props) => {
previous.masquerade,
);
}
+ // console.log(renderer.messages[i].content, 7979)
- if (message.author_id === "00000000000000000000000000") {
+
+ if (message.system?.type as any == "message_pinned" || message.system?.type as any == "message_unpinned") {
+ render.push(
+
+ ,
+ );
+ } else if (message.author_id === "00000000000000000000000000") {
render.push(
{
attachContext
highlight={highlight === message._id}
/>,
- );
+ )
} else if (message.author?.relationship === "Blocked") {
blocked++;
} else {
@@ -204,6 +219,7 @@ export default observer(({ last_id, renderer, highlight }: Props) => {
const nonces = renderer.messages.map((x) => x.nonce);
if (renderer.atBottom) {
for (const msg of queue.get(renderer.channel._id)) {
+
if (nonces.includes(msg.id)) continue;
if (previous) {
@@ -222,6 +238,7 @@ export default observer(({ last_id, renderer, highlight }: Props) => {
} as MessageI;
}
+
render.push(
= 0.14.0"
+ react-dom: ">= 0.14.0"
+ checksum: 42d9b3182b9d2495bf0d7914c9f370da51d8bdb853a3eba2acaf433894ae760386a075ba103185be825b33d42f50d85ef462087f261656d433f4c74dab23861f
+ languageName: node
+ linkType: hard
+
"react-fast-compare@npm:^3.1.1":
version: 3.2.0
resolution: "react-fast-compare@npm:3.2.0"
@@ -9068,12 +9092,12 @@ __metadata:
linkType: hard
"typescript@npm:^4.6.2":
- version: 4.6.4
- resolution: "typescript@npm:4.6.4"
+ version: 4.9.5
+ resolution: "typescript@npm:4.9.5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: e7bfcc39cd4571a63a54e5ea21f16b8445268b9900bf55aee0e02ad981be576acc140eba24f1af5e3c1457767c96cea6d12861768fb386cf3ffb34013718631a
+ checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db
languageName: node
linkType: hard
@@ -9088,12 +9112,12 @@ __metadata:
linkType: hard
"typescript@patch:typescript@^4.6.2#~builtin":
- version: 4.6.4
- resolution: "typescript@patch:typescript@npm%3A4.6.4#~builtin::version=4.6.4&hash=bda367"
+ version: 4.9.5
+ resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=bda367"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: 1cb434fbc637d347be90e3a0c6cd05e33c38f941713c8786d3031faf1842c2c148ba91d2fac01e7276b0ae3249b8633f1660e32686cc7a8c6a8fd5361dc52c66
+ checksum: 2eee5c37cad4390385db5db5a8e81470e42e8f1401b0358d7390095d6f681b410f2c4a0c496c6ff9ebd775423c7785cdace7bcdad76c7bee283df3d9718c0f20
languageName: node
linkType: hard
@@ -9104,6 +9128,15 @@ __metadata:
languageName: node
linkType: hard
+"ua-parser-js@npm:^1.0.33":
+ version: 1.0.40
+ resolution: "ua-parser-js@npm:1.0.40"
+ bin:
+ ua-parser-js: script/cli.js
+ checksum: ae555a33dc9395dd877e295d6adbf5634e047aad7c3358328830218f3ca3a6233e35848cd355465a7612f269860e8029984389282940c7a27c9af4dfcdbba8c3
+ languageName: node
+ linkType: hard
+
"ulid@npm:^2.3.0":
version: 2.3.0
resolution: "ulid@npm:2.3.0"
@@ -9125,10 +9158,12 @@ __metadata:
languageName: node
linkType: hard
-"undici@npm:^4.14.1":
- version: 4.16.0
- resolution: "undici@npm:4.16.0"
- checksum: 5e88c2b3381085e25ed1d1a308610ac7ee985f478ac705af7a8e03213536e10f73ef8dd8d85e6ed38948d1883fa0ae935e04357c317b0f5d3d3c0211d0c8c393
+"undici@npm:^5.4.0":
+ version: 5.28.4
+ resolution: "undici@npm:5.28.4"
+ dependencies:
+ "@fastify/busboy": ^2.0.0
+ checksum: a8193132d84540e4dc1895ecc8dbaa176e8a49d26084d6fbe48a292e28397cd19ec5d13bc13e604484e76f94f6e334b2bdc740d5f06a6e50c44072818d0c19f9
languageName: node
linkType: hard
@@ -9794,17 +9829,17 @@ __metadata:
linkType: hard
"ws@npm:^8.2.2":
- version: 8.2.2
- resolution: "ws@npm:8.2.2"
+ version: 8.18.0
+ resolution: "ws@npm:8.18.0"
peerDependencies:
bufferutil: ^4.0.1
- utf-8-validate: ^5.0.2
+ utf-8-validate: ">=5.0.2"
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
- checksum: 25e764c631141bdca45badc86e69437b8791e57e461f9a16c0f7cd779baf70c3fbba07ecdd9e0d34fea1155ddcf62ef165cd7f81b68ed545bc7d455c15a85fb0
+ checksum: 91d4d35bc99ff6df483bdf029b9ea4bfd7af1f16fc91231a96777a63d263e1eabf486e13a2353970efc534f9faa43bdbf9ee76525af22f4752cbc5ebda333975
languageName: node
linkType: hard
@@ -9822,10 +9857,10 @@ __metadata:
languageName: node
linkType: hard
-"yargs-parser@npm:^21.0.0":
- version: 21.0.1
- resolution: "yargs-parser@npm:21.0.1"
- checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a
+"yargs-parser@npm:^21.0.1":
+ version: 21.1.1
+ resolution: "yargs-parser@npm:21.1.1"
+ checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c
languageName: node
linkType: hard