Run prettier on all files.

This commit is contained in:
Paul
2021-07-05 11:23:23 +01:00
parent 50cd6fc1ee
commit a9ce64c9fe
181 changed files with 18084 additions and 13521 deletions

View File

@@ -1,38 +1,40 @@
import { Text } from "preact-i18n";
import styled from "styled-components";
import { getChannelName } from "../../../context/revoltjs/util";
import { Text } from "preact-i18n";
import { useChannel, useForceUpdate } from "../../../context/revoltjs/hooks";
import { getChannelName } from "../../../context/revoltjs/util";
const StartBase = styled.div`
margin: 18px 16px 10px 16px;
margin: 18px 16px 10px 16px;
h1 {
font-size: 23px;
margin: 0 0 8px 0;
}
h1 {
font-size: 23px;
margin: 0 0 8px 0;
}
h4 {
font-weight: 400;
margin: 0;
font-size: 14px;
}
h4 {
font-weight: 400;
margin: 0;
font-size: 14px;
}
`;
interface Props {
id: string;
id: string;
}
export default function ConversationStart({ id }: Props) {
const ctx = useForceUpdate();
const channel = useChannel(id, ctx);
if (!channel) return null;
const ctx = useForceUpdate();
const channel = useChannel(id, ctx);
if (!channel) return null;
return (
<StartBase>
<h1>{ getChannelName(ctx.client, channel, true) }</h1>
<h4>
<Text id="app.main.channel.start.group" />
</h4>
</StartBase>
);
return (
<StartBase>
<h1>{getChannelName(ctx.client, channel, true)}</h1>
<h4>
<Text id="app.main.channel.start.group" />
</h4>
</StartBase>
);
}

View File

@@ -1,232 +1,259 @@
import styled from "styled-components";
import { createContext } from "preact";
import { animateScroll } from "react-scroll";
import MessageRenderer from "./MessageRenderer";
import ConversationStart from './ConversationStart';
import styled from "styled-components";
import useResizeObserver from "use-resize-observer";
import Preloader from "../../../components/ui/Preloader";
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
import { RenderState, ScrollState } from "../../../lib/renderer/types";
import { SingletonMessageRenderer } from "../../../lib/renderer/Singleton";
import { IntermediateContext } from "../../../context/intermediate/Intermediate";
import { ClientStatus, StatusContext } from "../../../context/revoltjs/RevoltClient";
import { useContext, useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
import { createContext } from "preact";
import {
useContext,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "preact/hooks";
import { defer } from "../../../lib/defer";
import { internalEmit } from "../../../lib/eventEmitter";
import { SingletonMessageRenderer } from "../../../lib/renderer/Singleton";
import { RenderState, ScrollState } from "../../../lib/renderer/types";
import { IntermediateContext } from "../../../context/intermediate/Intermediate";
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
import {
ClientStatus,
StatusContext,
} from "../../../context/revoltjs/RevoltClient";
import Preloader from "../../../components/ui/Preloader";
import ConversationStart from "./ConversationStart";
import MessageRenderer from "./MessageRenderer";
const Area = styled.div`
height: 100%;
flex-grow: 1;
min-height: 0;
overflow-x: hidden;
overflow-y: scroll;
word-break: break-word;
height: 100%;
flex-grow: 1;
min-height: 0;
overflow-x: hidden;
overflow-y: scroll;
word-break: break-word;
> div {
display: flex;
min-height: 100%;
padding-bottom: 20px;
flex-direction: column;
justify-content: flex-end;
}
> div {
display: flex;
min-height: 100%;
padding-bottom: 20px;
flex-direction: column;
justify-content: flex-end;
}
`;
interface Props {
id: string;
id: string;
}
export const MessageAreaWidthContext = createContext(0);
export const MESSAGE_AREA_PADDING = 82;
export function MessageArea({ id }: Props) {
const status = useContext(StatusContext);
const { focusTaken } = useContext(IntermediateContext);
const status = useContext(StatusContext);
const { focusTaken } = useContext(IntermediateContext);
// ? This is the scroll container.
const ref = useRef<HTMLDivElement>(null);
const { width, height } = useResizeObserver<HTMLDivElement>({ ref });
// ? This is the scroll container.
const ref = useRef<HTMLDivElement>(null);
const { width, height } = useResizeObserver<HTMLDivElement>({ ref });
// ? Current channel state.
const [state, setState] = useState<RenderState>({ type: "LOADING" });
// ? Current channel state.
const [state, setState] = useState<RenderState>({ type: "LOADING" });
// ? useRef to avoid re-renders
const scrollState = useRef<ScrollState>({ type: "Free" });
// ? useRef to avoid re-renders
const scrollState = useRef<ScrollState>({ type: "Free" });
const setScrollState = (v: ScrollState) => {
if (v.type === 'StayAtBottom') {
if (scrollState.current.type === 'Bottom' || atBottom()) {
scrollState.current = { type: 'ScrollToBottom', smooth: v.smooth };
} else {
scrollState.current = { type: 'Free' };
}
} else {
scrollState.current = v;
}
const setScrollState = (v: ScrollState) => {
if (v.type === "StayAtBottom") {
if (scrollState.current.type === "Bottom" || atBottom()) {
scrollState.current = {
type: "ScrollToBottom",
smooth: v.smooth,
};
} else {
scrollState.current = { type: "Free" };
}
} else {
scrollState.current = v;
}
defer(() => {
if (scrollState.current.type === "ScrollToBottom") {
setScrollState({ type: "Bottom", scrollingUntil: + new Date() + 150 });
animateScroll.scrollToBottom({
container: ref.current,
duration: scrollState.current.smooth ? 150 : 0
});
} else if (scrollState.current.type === "OffsetTop") {
animateScroll.scrollTo(
Math.max(
101,
ref.current.scrollTop +
(ref.current.scrollHeight - scrollState.current.previousHeight)
),
{
container: ref.current,
duration: 0
}
);
defer(() => {
if (scrollState.current.type === "ScrollToBottom") {
setScrollState({
type: "Bottom",
scrollingUntil: +new Date() + 150,
});
setScrollState({ type: "Free" });
} else if (scrollState.current.type === "ScrollTop") {
animateScroll.scrollTo(scrollState.current.y, {
container: ref.current,
duration: 0
});
animateScroll.scrollToBottom({
container: ref.current,
duration: scrollState.current.smooth ? 150 : 0,
});
} else if (scrollState.current.type === "OffsetTop") {
animateScroll.scrollTo(
Math.max(
101,
ref.current.scrollTop +
(ref.current.scrollHeight -
scrollState.current.previousHeight),
),
{
container: ref.current,
duration: 0,
},
);
setScrollState({ type: "Free" });
}
});
}
setScrollState({ type: "Free" });
} else if (scrollState.current.type === "ScrollTop") {
animateScroll.scrollTo(scrollState.current.y, {
container: ref.current,
duration: 0,
});
// ? Determine if we are at the bottom of the scroll container.
// -> https://stackoverflow.com/a/44893438
// By default, we assume we are at the bottom, i.e. when we first load.
const atBottom = (offset = 0) =>
ref.current
? Math.floor(ref.current.scrollHeight - ref.current.scrollTop) -
offset <=
ref.current.clientHeight
: true;
const atTop = (offset = 0) => ref.current.scrollTop <= offset;
setScrollState({ type: "Free" });
}
});
};
// ? Handle events from renderer.
useEffect(() => {
SingletonMessageRenderer.addListener('state', setState);
return () => SingletonMessageRenderer.removeListener('state', setState);
}, [ ]);
// ? Determine if we are at the bottom of the scroll container.
// -> https://stackoverflow.com/a/44893438
// By default, we assume we are at the bottom, i.e. when we first load.
const atBottom = (offset = 0) =>
ref.current
? Math.floor(ref.current.scrollHeight - ref.current.scrollTop) -
offset <=
ref.current.clientHeight
: true;
useEffect(() => {
SingletonMessageRenderer.addListener('scroll', setScrollState);
return () => SingletonMessageRenderer.removeListener('scroll', setScrollState);
}, [ scrollState ]);
const atTop = (offset = 0) => ref.current.scrollTop <= offset;
// ? Load channel initially.
useEffect(() => {
SingletonMessageRenderer.init(id);
}, [ id ]);
// ? Handle events from renderer.
useEffect(() => {
SingletonMessageRenderer.addListener("state", setState);
return () => SingletonMessageRenderer.removeListener("state", setState);
}, []);
// ? If we are waiting for network, try again.
useEffect(() => {
switch (status) {
case ClientStatus.ONLINE:
if (state.type === 'WAITING_FOR_NETWORK') {
SingletonMessageRenderer.init(id);
} else {
SingletonMessageRenderer.reloadStale(id);
}
useEffect(() => {
SingletonMessageRenderer.addListener("scroll", setScrollState);
return () =>
SingletonMessageRenderer.removeListener("scroll", setScrollState);
}, [scrollState]);
break;
case ClientStatus.OFFLINE:
case ClientStatus.DISCONNECTED:
case ClientStatus.CONNECTING:
SingletonMessageRenderer.markStale();
break;
}
}, [ status, state ]);
// ? Load channel initially.
useEffect(() => {
SingletonMessageRenderer.init(id);
}, [id]);
// ? When the container is scrolled.
// ? Also handle StayAtBottom
useEffect(() => {
async function onScroll() {
if (scrollState.current.type === "Free" && atBottom()) {
setScrollState({ type: "Bottom" });
} else if (scrollState.current.type === "Bottom" && !atBottom()) {
if (scrollState.current.scrollingUntil && scrollState.current.scrollingUntil > + new Date()) return;
setScrollState({ type: "Free" });
}
}
// ? If we are waiting for network, try again.
useEffect(() => {
switch (status) {
case ClientStatus.ONLINE:
if (state.type === "WAITING_FOR_NETWORK") {
SingletonMessageRenderer.init(id);
} else {
SingletonMessageRenderer.reloadStale(id);
}
ref.current.addEventListener("scroll", onScroll);
return () => ref.current.removeEventListener("scroll", onScroll);
}, [ref, scrollState]);
break;
case ClientStatus.OFFLINE:
case ClientStatus.DISCONNECTED:
case ClientStatus.CONNECTING:
SingletonMessageRenderer.markStale();
break;
}
}, [status, state]);
// ? Top and bottom loaders.
useEffect(() => {
async function onScroll() {
if (atTop(100)) {
SingletonMessageRenderer.loadTop(ref.current);
}
// ? When the container is scrolled.
// ? Also handle StayAtBottom
useEffect(() => {
async function onScroll() {
if (scrollState.current.type === "Free" && atBottom()) {
setScrollState({ type: "Bottom" });
} else if (scrollState.current.type === "Bottom" && !atBottom()) {
if (
scrollState.current.scrollingUntil &&
scrollState.current.scrollingUntil > +new Date()
)
return;
setScrollState({ type: "Free" });
}
}
if (atBottom(100)) {
SingletonMessageRenderer.loadBottom(ref.current);
}
}
ref.current.addEventListener("scroll", onScroll);
return () => ref.current.removeEventListener("scroll", onScroll);
}, [ref, scrollState]);
ref.current.addEventListener("scroll", onScroll);
return () => ref.current.removeEventListener("scroll", onScroll);
}, [ref]);
// ? Top and bottom loaders.
useEffect(() => {
async function onScroll() {
if (atTop(100)) {
SingletonMessageRenderer.loadTop(ref.current);
}
// ? Scroll down whenever the message area resizes.
function stbOnResize() {
if (!atBottom() && scrollState.current.type === "Bottom") {
animateScroll.scrollToBottom({
container: ref.current,
duration: 0
});
if (atBottom(100)) {
SingletonMessageRenderer.loadBottom(ref.current);
}
}
setScrollState({ type: "Bottom" });
}
}
ref.current.addEventListener("scroll", onScroll);
return () => ref.current.removeEventListener("scroll", onScroll);
}, [ref]);
// ? Scroll down when container resized.
useLayoutEffect(() => {
stbOnResize();
}, [height]);
// ? Scroll down whenever the message area resizes.
function stbOnResize() {
if (!atBottom() && scrollState.current.type === "Bottom") {
animateScroll.scrollToBottom({
container: ref.current,
duration: 0,
});
// ? Scroll down whenever the window resizes.
useLayoutEffect(() => {
document.addEventListener("resize", stbOnResize);
return () => document.removeEventListener("resize", stbOnResize);
}, [ref, scrollState]);
setScrollState({ type: "Bottom" });
}
}
// ? Scroll to bottom when pressing 'Escape'.
useEffect(() => {
function keyUp(e: KeyboardEvent) {
if (e.key === "Escape" && !focusTaken) {
SingletonMessageRenderer.jumpToBottom(id, true);
internalEmit("TextArea", "focus", "message");
}
}
// ? Scroll down when container resized.
useLayoutEffect(() => {
stbOnResize();
}, [height]);
document.body.addEventListener("keyup", keyUp);
return () => document.body.removeEventListener("keyup", keyUp);
}, [ref, focusTaken]);
// ? Scroll down whenever the window resizes.
useLayoutEffect(() => {
document.addEventListener("resize", stbOnResize);
return () => document.removeEventListener("resize", stbOnResize);
}, [ref, scrollState]);
return (
<MessageAreaWidthContext.Provider value={(width ?? 0) - MESSAGE_AREA_PADDING}>
<Area ref={ref}>
<div>
{state.type === "LOADING" && <Preloader type="ring" />}
{state.type === "WAITING_FOR_NETWORK" && (
<RequiresOnline>
<Preloader type="ring" />
</RequiresOnline>
)}
{state.type === "RENDER" && (
<MessageRenderer id={id} state={state} />
)}
{state.type === "EMPTY" && <ConversationStart id={id} />}
</div>
</Area>
</MessageAreaWidthContext.Provider>
);
// ? Scroll to bottom when pressing 'Escape'.
useEffect(() => {
function keyUp(e: KeyboardEvent) {
if (e.key === "Escape" && !focusTaken) {
SingletonMessageRenderer.jumpToBottom(id, true);
internalEmit("TextArea", "focus", "message");
}
}
document.body.addEventListener("keyup", keyUp);
return () => document.body.removeEventListener("keyup", keyUp);
}, [ref, focusTaken]);
return (
<MessageAreaWidthContext.Provider
value={(width ?? 0) - MESSAGE_AREA_PADDING}>
<Area ref={ref}>
<div>
{state.type === "LOADING" && <Preloader type="ring" />}
{state.type === "WAITING_FOR_NETWORK" && (
<RequiresOnline>
<Preloader type="ring" />
</RequiresOnline>
)}
{state.type === "RENDER" && (
<MessageRenderer id={id} state={state} />
)}
{state.type === "EMPTY" && <ConversationStart id={id} />}
</div>
</Area>
</MessageAreaWidthContext.Provider>
);
}

View File

@@ -1,115 +1,133 @@
import styled from "styled-components";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { MessageObject } from "../../../context/revoltjs/util";
import { useContext, useEffect, useState } from "preact/hooks";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
import { IntermediateContext, useIntermediate } from "../../../context/intermediate/Intermediate";
import AutoComplete, { useAutoComplete } from "../../../components/common/AutoComplete";
import {
IntermediateContext,
useIntermediate,
} from "../../../context/intermediate/Intermediate";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { MessageObject } from "../../../context/revoltjs/util";
import AutoComplete, {
useAutoComplete,
} from "../../../components/common/AutoComplete";
const EditorBase = styled.div`
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
textarea {
resize: none;
padding: 12px;
font-size: .875rem;
border-radius: 3px;
white-space: pre-wrap;
background: var(--secondary-header);
}
textarea {
resize: none;
padding: 12px;
font-size: 0.875rem;
border-radius: 3px;
white-space: pre-wrap;
background: var(--secondary-header);
}
.caption {
padding: 2px;
font-size: 11px;
color: var(--tertiary-foreground);
.caption {
padding: 2px;
font-size: 11px;
color: var(--tertiary-foreground);
a {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
a {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
`;
interface Props {
message: MessageObject
finish: () => void
message: MessageObject;
finish: () => void;
}
export default function MessageEditor({ message, finish }: Props) {
const [ content, setContent ] = useState(message.content as string ?? '');
const { focusTaken } = useContext(IntermediateContext);
const { openScreen } = useIntermediate();
const client = useContext(AppContext);
const [content, setContent] = useState((message.content as string) ?? "");
const { focusTaken } = useContext(IntermediateContext);
const { openScreen } = useIntermediate();
const client = useContext(AppContext);
async function save() {
finish();
async function save() {
finish();
if (content.length === 0) {
// @ts-expect-error
openScreen({ id: 'special_prompt', type: 'delete_message', target: message });
} else if (content !== message.content) {
await client.channels.editMessage(
message.channel,
message._id,
{ content }
);
}
}
if (content.length === 0) {
openScreen({
id: "special_prompt",
// @ts-expect-error
type: "delete_message",
// @ts-expect-error
target: message,
});
} else if (content !== message.content) {
await client.channels.editMessage(message.channel, message._id, {
content,
});
}
}
// ? Stop editing when pressing ESC.
useEffect(() => {
function keyUp(e: KeyboardEvent) {
if (e.key === "Escape" && !focusTaken) {
finish();
}
}
// ? Stop editing when pressing ESC.
useEffect(() => {
function keyUp(e: KeyboardEvent) {
if (e.key === "Escape" && !focusTaken) {
finish();
}
}
document.body.addEventListener("keyup", keyUp);
return () => document.body.removeEventListener("keyup", keyUp);
}, [focusTaken]);
document.body.addEventListener("keyup", keyUp);
return () => document.body.removeEventListener("keyup", keyUp);
}, [focusTaken]);
const { onChange, onKeyUp, onKeyDown, onFocus, onBlur, ...autoCompleteProps } =
useAutoComplete(v => setContent(v ?? ''), {
users: { type: 'all' }
});
const {
onChange,
onKeyUp,
onKeyDown,
onFocus,
onBlur,
...autoCompleteProps
} = useAutoComplete((v) => setContent(v ?? ""), {
users: { type: "all" },
});
return (
<EditorBase>
<AutoComplete detached {...autoCompleteProps} />
<TextAreaAutoSize
forceFocus
maxRows={3}
padding={12}
value={content}
maxLength={2000}
onChange={ev => {
onChange(ev);
setContent(ev.currentTarget.value)
}}
onKeyDown={e => {
if (onKeyDown(e)) return;
return (
<EditorBase>
<AutoComplete detached {...autoCompleteProps} />
<TextAreaAutoSize
forceFocus
maxRows={3}
padding={12}
value={content}
maxLength={2000}
onChange={(ev) => {
onChange(ev);
setContent(ev.currentTarget.value);
}}
onKeyDown={(e) => {
if (onKeyDown(e)) return;
if (
!e.shiftKey &&
e.key === "Enter" &&
!isTouchscreenDevice
) {
e.preventDefault();
save();
}
}}
onKeyUp={onKeyUp}
onFocus={onFocus}
onBlur={onBlur}
/>
<span className="caption">
escape to <a onClick={finish}>cancel</a> &middot;
enter to <a onClick={save}>save</a>
</span>
</EditorBase>
)
if (
!e.shiftKey &&
e.key === "Enter" &&
!isTouchscreenDevice
) {
e.preventDefault();
save();
}
}}
onKeyUp={onKeyUp}
onFocus={onFocus}
onBlur={onBlur}
/>
<span className="caption">
escape to <a onClick={finish}>cancel</a> &middot; enter to{" "}
<a onClick={save}>save</a>
</span>
</EditorBase>
);
}

View File

@@ -1,198 +1,215 @@
import { decodeTime } from "ulid";
import { memo } from "preact/compat";
import styled from "styled-components";
import MessageEditor from "./MessageEditor";
import { Children } from "../../../types/Preact";
import { Users } from "revolt.js/dist/api/objects";
import { X } from "@styled-icons/boxicons-regular";
import ConversationStart from "./ConversationStart";
import { connectState } from "../../../redux/connector";
import Preloader from "../../../components/ui/Preloader";
import { RenderState } from "../../../lib/renderer/types";
import DateDivider from "../../../components/ui/DateDivider";
import { QueuedMessage } from "../../../redux/reducers/queue";
import { Users } from "revolt.js/dist/api/objects";
import styled from "styled-components";
import { decodeTime } from "ulid";
import { memo } from "preact/compat";
import { useContext, useEffect, useState } from "preact/hooks";
import { MessageObject } from "../../../context/revoltjs/util";
import Message from "../../../components/common/messaging/Message";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
import { RenderState } from "../../../lib/renderer/types";
import { connectState } from "../../../redux/connector";
import { QueuedMessage } from "../../../redux/reducers/queue";
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { MessageObject } from "../../../context/revoltjs/util";
import Message from "../../../components/common/messaging/Message";
import { SystemMessage } from "../../../components/common/messaging/SystemMessage";
import DateDivider from "../../../components/ui/DateDivider";
import Preloader from "../../../components/ui/Preloader";
import { Children } from "../../../types/Preact";
import ConversationStart from "./ConversationStart";
import MessageEditor from "./MessageEditor";
interface Props {
id: string;
state: RenderState;
queue: QueuedMessage[];
id: string;
state: RenderState;
queue: QueuedMessage[];
}
const BlockedMessage = styled.div`
font-size: 0.8em;
margin-top: 6px;
padding: 4px 64px;
color: var(--tertiary-foreground);
font-size: 0.8em;
margin-top: 6px;
padding: 4px 64px;
color: var(--tertiary-foreground);
&:hover {
background: var(--hover);
}
&:hover {
background: var(--hover);
}
`;
function MessageRenderer({ id, state, queue }: Props) {
if (state.type !== 'RENDER') return null;
if (state.type !== "RENDER") return null;
const client = useContext(AppContext);
const userId = client.user!._id;
const client = useContext(AppContext);
const userId = client.user!._id;
const [editing, setEditing] = useState<string | undefined>(undefined);
const stopEditing = () => {
setEditing(undefined);
internalEmit("TextArea", "focus", "message");
};
const [editing, setEditing] = useState<string | undefined>(undefined);
const stopEditing = () => {
setEditing(undefined);
internalEmit("TextArea", "focus", "message");
};
useEffect(() => {
function editLast() {
if (state.type !== 'RENDER') return;
for (let i = state.messages.length - 1; i >= 0; i--) {
if (state.messages[i].author === userId) {
setEditing(state.messages[i]._id);
return;
}
}
}
useEffect(() => {
function editLast() {
if (state.type !== "RENDER") return;
for (let i = state.messages.length - 1; i >= 0; i--) {
if (state.messages[i].author === userId) {
setEditing(state.messages[i]._id);
return;
}
}
}
const subs = [
internalSubscribe("MessageRenderer", "edit_last", editLast),
internalSubscribe("MessageRenderer", "edit_message", setEditing)
]
const subs = [
internalSubscribe("MessageRenderer", "edit_last", editLast),
internalSubscribe("MessageRenderer", "edit_message", setEditing),
];
return () => subs.forEach(unsub => unsub());
}, [state.messages]);
return () => subs.forEach((unsub) => unsub());
}, [state.messages]);
let render: Children[] = [],
previous: MessageObject | undefined;
let render: Children[] = [],
previous: MessageObject | undefined;
if (state.atTop) {
render.push(<ConversationStart id={id} />);
} else {
render.push(
<RequiresOnline>
<Preloader type="ring" />
</RequiresOnline>
);
}
if (state.atTop) {
render.push(<ConversationStart id={id} />);
} else {
render.push(
<RequiresOnline>
<Preloader type="ring" />
</RequiresOnline>,
);
}
let head = true;
function compare(
current: string,
curAuthor: string,
previous: string,
prevAuthor: string
) {
const atime = decodeTime(current),
adate = new Date(atime),
btime = decodeTime(previous),
bdate = new Date(btime);
let head = true;
function compare(
current: string,
curAuthor: string,
previous: string,
prevAuthor: string,
) {
const atime = decodeTime(current),
adate = new Date(atime),
btime = decodeTime(previous),
bdate = new Date(btime);
if (
adate.getFullYear() !== bdate.getFullYear() ||
adate.getMonth() !== bdate.getMonth() ||
adate.getDate() !== bdate.getDate()
) {
render.push(<DateDivider date={adate} />);
head = true;
}
if (
adate.getFullYear() !== bdate.getFullYear() ||
adate.getMonth() !== bdate.getMonth() ||
adate.getDate() !== bdate.getDate()
) {
render.push(<DateDivider date={adate} />);
head = true;
}
head = curAuthor !== prevAuthor || Math.abs(btime - atime) >= 420000;
}
head = curAuthor !== prevAuthor || Math.abs(btime - atime) >= 420000;
}
let blocked = 0;
function pushBlocked() {
render.push(<BlockedMessage><X size={16} /> { blocked } blocked messages</BlockedMessage>);
blocked = 0;
}
let blocked = 0;
function pushBlocked() {
render.push(
<BlockedMessage>
<X size={16} /> {blocked} blocked messages
</BlockedMessage>,
);
blocked = 0;
}
for (const message of state.messages) {
if (previous) {
compare(
message._id,
message.author,
previous._id,
previous.author
);
}
for (const message of state.messages) {
if (previous) {
compare(message._id, message.author, previous._id, previous.author);
}
if (message.author === "00000000000000000000000000") {
render.push(<SystemMessage key={message._id} message={message} attachContext />);
} else {
// ! FIXME: temp solution
if (client.users.get(message.author)?.relationship === Users.Relationship.Blocked) {
blocked++;
} else {
if (blocked > 0) pushBlocked();
if (message.author === "00000000000000000000000000") {
render.push(
<SystemMessage
key={message._id}
message={message}
attachContext
/>,
);
} else {
// ! FIXME: temp solution
if (
client.users.get(message.author)?.relationship ===
Users.Relationship.Blocked
) {
blocked++;
} else {
if (blocked > 0) pushBlocked();
render.push(
<Message message={message}
key={message._id}
head={head}
content={
editing === message._id ?
<MessageEditor message={message} finish={stopEditing} />
: undefined
}
attachContext />
);
}
}
render.push(
<Message
message={message}
key={message._id}
head={head}
content={
editing === message._id ? (
<MessageEditor
message={message}
finish={stopEditing}
/>
) : undefined
}
attachContext
/>,
);
}
}
previous = message;
}
if (blocked > 0) pushBlocked();
previous = message;
}
const nonces = state.messages.map(x => x.nonce);
if (state.atBottom) {
for (const msg of queue) {
if (msg.channel !== id) continue;
if (nonces.includes(msg.id)) continue;
if (blocked > 0) pushBlocked();
if (previous) {
compare(
msg.id,
userId!,
previous._id,
previous.author
);
previous = {
_id: msg.id,
data: { author: userId! }
} as any;
}
const nonces = state.messages.map((x) => x.nonce);
if (state.atBottom) {
for (const msg of queue) {
if (msg.channel !== id) continue;
if (nonces.includes(msg.id)) continue;
render.push(
<Message
message={{
...msg.data,
replies: msg.data.replies.map(x => x.id)
}}
key={msg.id}
queued={msg}
head={head}
attachContext />
);
}
} else {
render.push(
<RequiresOnline>
<Preloader type="ring" />
</RequiresOnline>
);
}
if (previous) {
compare(msg.id, userId!, previous._id, previous.author);
return <>{ render }</>;
previous = {
_id: msg.id,
data: { author: userId! },
} as any;
}
render.push(
<Message
message={{
...msg.data,
replies: msg.data.replies.map((x) => x.id),
}}
key={msg.id}
queued={msg}
head={head}
attachContext
/>,
);
}
} else {
render.push(
<RequiresOnline>
<Preloader type="ring" />
</RequiresOnline>,
);
}
return <>{render}</>;
}
export default memo(connectState<Omit<Props, 'queue'>>(MessageRenderer, state => {
return {
queue: state.queue
};
}));
export default memo(
connectState<Omit<Props, "queue">>(MessageRenderer, (state) => {
return {
queue: state.queue,
};
}),
);