forked from MarseyWorld/MarseyWorld
Don't allow multiple sets of actions open
parent
fbcd2e9f74
commit
249d510f23
|
@ -1,10 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "chat",
|
"name": "chat",
|
||||||
"version": "0.0.24",
|
"version": "0.1.0",
|
||||||
"main": "./src/index.tsx",
|
"main": "./src/index.tsx",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/humanize-duration": "^3.27.1",
|
"@types/humanize-duration": "^3.27.1",
|
||||||
|
"@types/lodash.clonedeep": "^4.5.7",
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
"@types/lozad": "^1.16.1",
|
"@types/lozad": "^1.16.1",
|
||||||
"@types/react": "^18.0.20",
|
"@types/react": "^18.0.20",
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
"dotenv": "^16.0.2",
|
"dotenv": "^16.0.2",
|
||||||
"esbuild": "^0.15.7",
|
"esbuild": "^0.15.7",
|
||||||
"humanize-duration": "^3.27.3",
|
"humanize-duration": "^3.27.3",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lozad": "^1.16.0",
|
"lozad": "^1.16.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|
|
@ -18,14 +18,6 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ChatMessage__showingUser:not(:first-child) {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatMessage:not(.ChatMessage__showingUser) {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ChatMessage .btn {
|
.ChatMessage .btn {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
@ -102,4 +94,14 @@
|
||||||
.ChatMessageList {
|
.ChatMessageList {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ChatMessageList-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0.3rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ChatMessageList-group:nth-child(even) {
|
||||||
|
background: rgba(255, 255, 255, 0.025);
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import key from "weak-key";
|
import key from "weak-key";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { Username } from "./Username";
|
import { Username } from "./Username";
|
||||||
import { useChat, useRootContext } from "../../hooks";
|
import { useChat, useRootContext } from "../../hooks";
|
||||||
import { QuotedMessageLink } from "./QuotedMessageLink";
|
import { QuotedMessageLink } from "./QuotedMessageLink";
|
||||||
|
@ -11,6 +12,8 @@ interface ChatMessageProps {
|
||||||
message: IChatMessage;
|
message: IChatMessage;
|
||||||
timestampUpdates: number;
|
timestampUpdates: number;
|
||||||
showUser?: boolean;
|
showUser?: boolean;
|
||||||
|
actionsOpen: boolean;
|
||||||
|
onToggleActions(messageId: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMESTAMP_UPDATE_INTERVAL = 20000;
|
const TIMESTAMP_UPDATE_INTERVAL = 20000;
|
||||||
|
@ -19,6 +22,8 @@ export function ChatMessage({
|
||||||
message,
|
message,
|
||||||
showUser = true,
|
showUser = true,
|
||||||
timestampUpdates,
|
timestampUpdates,
|
||||||
|
actionsOpen,
|
||||||
|
onToggleActions
|
||||||
}: ChatMessageProps) {
|
}: ChatMessageProps) {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
|
@ -36,7 +41,6 @@ export function ChatMessage({
|
||||||
const { messageLookup, deleteMessage, quoteMessage } = useChat();
|
const { messageLookup, deleteMessage, quoteMessage } = useChat();
|
||||||
const quotedMessage = messageLookup[quotes];
|
const quotedMessage = messageLookup[quotes];
|
||||||
const content = censored ? text_censored : text_html;
|
const content = censored ? text_censored : text_html;
|
||||||
const [showingActions, setShowingActions] = useState(false);
|
|
||||||
const [confirmedDelete, setConfirmedDelete] = useState(false);
|
const [confirmedDelete, setConfirmedDelete] = useState(false);
|
||||||
const timestamp = useMemo(
|
const timestamp = useMemo(
|
||||||
() => formatTimeAgo(time),
|
() => formatTimeAgo(time),
|
||||||
|
@ -49,20 +53,16 @@ export function ChatMessage({
|
||||||
setConfirmedDelete(true);
|
setConfirmedDelete(true);
|
||||||
}
|
}
|
||||||
}, [text, confirmedDelete]);
|
}, [text, confirmedDelete]);
|
||||||
const toggleMessageActions = useCallback(
|
|
||||||
() => setShowingActions((prev) => !prev),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const handleQuoteMessage = useCallback(() => {
|
const handleQuoteMessage = useCallback(() => {
|
||||||
quoteMessage(message);
|
quoteMessage(message);
|
||||||
setShowingActions(false);
|
onToggleActions(message.id);
|
||||||
}, [message]);
|
}, [message, onToggleActions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showingActions) {
|
if (!actionsOpen) {
|
||||||
setConfirmedDelete(false);
|
setConfirmedDelete(false);
|
||||||
}
|
}
|
||||||
}, [showingActions]);
|
}, [actionsOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -71,15 +71,15 @@ export function ChatMessage({
|
||||||
})}
|
})}
|
||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
{!showingActions && (
|
{!actionsOpen && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary ChatMessage-actions-button"
|
className="btn btn-secondary ChatMessage-actions-button"
|
||||||
onClick={toggleMessageActions}
|
onClick={() => onToggleActions(id)}
|
||||||
>
|
>
|
||||||
...
|
...
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{showingActions && (
|
{actionsOpen && (
|
||||||
<div className="ChatMessage-actions">
|
<div className="ChatMessage-actions">
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary ChatMessage-button"
|
className="btn btn-secondary ChatMessage-button"
|
||||||
|
@ -100,7 +100,7 @@ export function ChatMessage({
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary ChatMessage-button"
|
className="btn btn-secondary ChatMessage-button"
|
||||||
onClick={toggleMessageActions}
|
onClick={() => onToggleActions(id)}
|
||||||
>
|
>
|
||||||
<i>X</i> Close
|
<i>X</i> Close
|
||||||
</button>
|
</button>
|
||||||
|
@ -136,6 +136,17 @@ export function ChatMessage({
|
||||||
export function ChatMessageList() {
|
export function ChatMessageList() {
|
||||||
const { messages } = useChat();
|
const { messages } = useChat();
|
||||||
const [timestampUpdates, setTimestampUpdates] = useState(0);
|
const [timestampUpdates, setTimestampUpdates] = useState(0);
|
||||||
|
const groupedMessages = useMemo(() => groupMessages(messages), [messages]);
|
||||||
|
const [actionsOpenForMessage, setActionsOpenForMessage] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
|
const handleToggleActionsForMessage = useCallback(
|
||||||
|
(messageId: string) =>
|
||||||
|
setActionsOpenForMessage(
|
||||||
|
messageId === actionsOpenForMessage ? null : messageId
|
||||||
|
),
|
||||||
|
[actionsOpenForMessage]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updatingTimestamps = setInterval(
|
const updatingTimestamps = setInterval(
|
||||||
|
@ -150,13 +161,19 @@ export function ChatMessageList() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ChatMessageList">
|
<div className="ChatMessageList">
|
||||||
{messages.map((message, index) => (
|
{groupedMessages.map((group) => (
|
||||||
<ChatMessage
|
<div key={key(group)} className="ChatMessageList-group">
|
||||||
key={key(message)}
|
{group.map((message, index) => (
|
||||||
message={message}
|
<ChatMessage
|
||||||
timestampUpdates={timestampUpdates}
|
key={key(message)}
|
||||||
showUser={message.username !== messages[index - 1]?.username}
|
message={message}
|
||||||
/>
|
timestampUpdates={timestampUpdates}
|
||||||
|
showUser={index === 0}
|
||||||
|
actionsOpen={actionsOpenForMessage === message.id}
|
||||||
|
onToggleActions={handleToggleActionsForMessage}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -188,3 +205,29 @@ function formatTimeAgo(time: number) {
|
||||||
|
|
||||||
return humanized === "0s ago" ? "just now" : humanized;
|
return humanized === "0s ago" ? "just now" : humanized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function groupMessages(messages: IChatMessage[]) {
|
||||||
|
const grouped: IChatMessage[][] = [];
|
||||||
|
let lastUsername = "";
|
||||||
|
let temp: IChatMessage[] = [];
|
||||||
|
|
||||||
|
for (const message of messages) {
|
||||||
|
if (!lastUsername) {
|
||||||
|
lastUsername = message.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.username === lastUsername) {
|
||||||
|
temp.push(message);
|
||||||
|
} else {
|
||||||
|
grouped.push(cloneDeep(temp));
|
||||||
|
lastUsername = message.username;
|
||||||
|
temp = [message];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temp.length > 0) {
|
||||||
|
grouped.push(cloneDeep(temp));
|
||||||
|
}
|
||||||
|
|
||||||
|
return grouped;
|
||||||
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export function QuotedMessageLink({ message }: { message: IChatMessage }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a className="QuotedMessageLink" href="#" onClick={handleLinkClick}>
|
<a className="QuotedMessageLink" href="#" onClick={handleLinkClick}>
|
||||||
Replying to @{message.username}:{" "}
|
<i className="fas fa-reply" /> @{message.username}:{" "}
|
||||||
<em>"{replyText}"</em>
|
<em>"{replyText}"</em>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|
|
@ -39,6 +39,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.1.tgz#f14740d1f585a0a8e3f46359b62fda8b0eaa31e7"
|
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.1.tgz#f14740d1f585a0a8e3f46359b62fda8b0eaa31e7"
|
||||||
integrity sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w==
|
integrity sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w==
|
||||||
|
|
||||||
|
"@types/lodash.clonedeep@^4.5.7":
|
||||||
|
version "4.5.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.7.tgz#0e119f582ed6f9e6b373c04a644651763214f197"
|
||||||
|
integrity sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==
|
||||||
|
dependencies:
|
||||||
|
"@types/lodash" "*"
|
||||||
|
|
||||||
"@types/lodash.debounce@^4.0.7":
|
"@types/lodash.debounce@^4.0.7":
|
||||||
version "4.0.7"
|
version "4.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz#0285879defb7cdb156ae633cecd62d5680eded9f"
|
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz#0285879defb7cdb156ae633cecd62d5680eded9f"
|
||||||
|
@ -390,6 +397,11 @@ inherits@2:
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||||
|
|
||||||
|
lodash.clonedeep@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||||
|
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
|
||||||
|
|
||||||
lodash.debounce@^4.0.8:
|
lodash.debounce@^4.0.8:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
|
|
Loading…
Reference in New Issue