forked from rDrama/rDrama
Add typing debounce/enable quoting
parent
ee204817ae
commit
695159052b
|
@ -2,7 +2,8 @@ declare var process: {
|
||||||
env: Record<string, any>;
|
env: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare interface ChatSpeakResponse {
|
declare interface IChatMessage {
|
||||||
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
hat: string;
|
hat: string;
|
||||||
|
@ -11,6 +12,7 @@ declare interface ChatSpeakResponse {
|
||||||
text_censored: string;
|
text_censored: string;
|
||||||
text_html: string;
|
text_html: string;
|
||||||
time: number;
|
time: number;
|
||||||
|
quotes: null | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface EmojiModSelection {
|
declare interface EmojiModSelection {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "chat",
|
"name": "chat",
|
||||||
"version": "0.0.9",
|
"version": "0.0.10",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
.Activity {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Activity > section {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Activity i {
|
|
||||||
margin-right: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Activity > section:not(:last-child) {
|
|
||||||
margin-right: 2rem;
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import "./Activity.css";
|
|
||||||
|
|
||||||
const ACTIVITIES = [
|
|
||||||
{
|
|
||||||
icon: "circle",
|
|
||||||
title: "Roulette",
|
|
||||||
description: "Round and round the wheel of fate turns.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "cards",
|
|
||||||
title: "Blackjack",
|
|
||||||
description: "Twenty one ways to change your life.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "dollar-sign",
|
|
||||||
title: "Slots",
|
|
||||||
description: "Today's your lucky day.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "dollar-sign",
|
|
||||||
title: "Racing",
|
|
||||||
description: "Make it all back at the track.",
|
|
||||||
},
|
|
||||||
{ icon: "dollar-sign", title: "Crossing", description: "Take a load off." },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function Activity() {
|
|
||||||
return (
|
|
||||||
<div className="Activity">
|
|
||||||
{ACTIVITIES.map((activity) => (
|
|
||||||
<section key={activity.title}>
|
|
||||||
<i className={`fas fa-${activity.icon}`}></i>
|
|
||||||
<h4>{activity.title}</h4>
|
|
||||||
</section>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./Activity";
|
|
|
@ -1,56 +0,0 @@
|
||||||
.Chat {
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 350px;
|
|
||||||
max-width: 1200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Chat-mobile-top {
|
|
||||||
display: none;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Chat-mobile-top i {
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
position: relative;
|
|
||||||
top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Chat-mobile-top span {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Chat-side {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 1180px) {
|
|
||||||
.Chat-mobile-top {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.Chat-side {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.Chat-drawer {
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Chat-typing {
|
|
||||||
height: 18px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.Chat-window {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { useChat, useDrawer } from "../../hooks";
|
|
||||||
import { ActivityList } from "./ActivityList";
|
|
||||||
import { ChatMessageList } from "./ChatMessage";
|
|
||||||
import { QuotedMessage } from "./QuotedMessage";
|
|
||||||
import { UserInput } from "./UserInput";
|
|
||||||
import { UserList } from "./UserList";
|
|
||||||
import "./Chat.css";
|
|
||||||
|
|
||||||
export function Chat() {
|
|
||||||
const { reveal } = useDrawer();
|
|
||||||
const { online, quote } = useChat();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="Chat" id="chatWrapper">
|
|
||||||
<div className="Chat-window">
|
|
||||||
<div className="Chat-mobile-top">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary"
|
|
||||||
onClick={() =>
|
|
||||||
reveal({
|
|
||||||
title: "Users in chat",
|
|
||||||
content: (
|
|
||||||
<div className="Chat-drawer">
|
|
||||||
<UserList fluid={true} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<i className="far fa-user" /> <span>{online.length}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<ChatMessageList />
|
|
||||||
{quote && <QuotedMessage />}
|
|
||||||
<UserInput />
|
|
||||||
</div>
|
|
||||||
<div className="Chat-side">
|
|
||||||
<UserList />
|
|
||||||
{process.env.FEATURES_ACTIVITY && <ActivityList />}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -6,13 +6,17 @@ import { Username } from "./Username";
|
||||||
import { useChat, useRootContext } from "../../hooks";
|
import { useChat, useRootContext } from "../../hooks";
|
||||||
import "./ChatMessage.css";
|
import "./ChatMessage.css";
|
||||||
|
|
||||||
interface ChatMessageProps extends ChatSpeakResponse {
|
interface ChatMessageProps extends IChatMessage {
|
||||||
showUser?: boolean;
|
showUser?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMESTAMP_UPDATE_INTERVAL = 20000;
|
const TIMESTAMP_UPDATE_INTERVAL = 20000;
|
||||||
|
const SCROLL_TO_QUOTED_OVERFLOW = 250;
|
||||||
|
const QUOTED_MESSAGE_CONTEXTUAL_HIGHLIGHTING_DURATION = 2500;
|
||||||
|
const QUOTED_MESSAGE_CONTEXTUAL_SNIPPET_LENGTH = 30;
|
||||||
|
|
||||||
export function ChatMessage({
|
export function ChatMessage({
|
||||||
|
id,
|
||||||
avatar,
|
avatar,
|
||||||
showUser = true,
|
showUser = true,
|
||||||
namecolor,
|
namecolor,
|
||||||
|
@ -22,8 +26,10 @@ export function ChatMessage({
|
||||||
text_html,
|
text_html,
|
||||||
text_censored,
|
text_censored,
|
||||||
time,
|
time,
|
||||||
|
quotes,
|
||||||
}: ChatMessageProps) {
|
}: ChatMessageProps) {
|
||||||
const message = {
|
const message = {
|
||||||
|
id,
|
||||||
avatar,
|
avatar,
|
||||||
namecolor,
|
namecolor,
|
||||||
username,
|
username,
|
||||||
|
@ -32,6 +38,7 @@ export function ChatMessage({
|
||||||
text_html,
|
text_html,
|
||||||
text_censored,
|
text_censored,
|
||||||
time,
|
time,
|
||||||
|
quotes,
|
||||||
};
|
};
|
||||||
const {
|
const {
|
||||||
username: loggedInUsername,
|
username: loggedInUsername,
|
||||||
|
@ -39,17 +46,12 @@ export function ChatMessage({
|
||||||
censored,
|
censored,
|
||||||
themeColor,
|
themeColor,
|
||||||
} = useRootContext();
|
} = useRootContext();
|
||||||
const { quote, deleteMessage, quoteMessage } = useChat();
|
const { quote, messageLookup, deleteMessage, quoteMessage } = useChat();
|
||||||
const content = censored ? text_censored : text_html;
|
const content = censored ? text_censored : text_html;
|
||||||
const hasMention = content.includes(loggedInUsername);
|
const hasMention = content.includes(loggedInUsername);
|
||||||
const mentionStyle = hasMention
|
const mentionStyle = hasMention
|
||||||
? { backgroundColor: `#${themeColor}55` }
|
? { backgroundColor: `#${themeColor}55` }
|
||||||
: {};
|
: {};
|
||||||
const quoteStyle =
|
|
||||||
quote?.username === username && quote?.text === text && quote?.time === time
|
|
||||||
? { borderLeft: `2px solid #${themeColor}` }
|
|
||||||
: {};
|
|
||||||
const style = { ...mentionStyle, ...quoteStyle };
|
|
||||||
const [confirmedDelete, setConfirmedDelete] = useState(false);
|
const [confirmedDelete, setConfirmedDelete] = useState(false);
|
||||||
const handleDeleteMessage = useCallback(() => {
|
const handleDeleteMessage = useCallback(() => {
|
||||||
if (confirmedDelete) {
|
if (confirmedDelete) {
|
||||||
|
@ -72,7 +74,7 @@ export function ChatMessage({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"ChatMessage"} style={style}>
|
<div className="ChatMessage" style={mentionStyle} id={id}>
|
||||||
{showUser && (
|
{showUser && (
|
||||||
<div className="ChatMessage-top">
|
<div className="ChatMessage-top">
|
||||||
<Username
|
<Username
|
||||||
|
@ -84,6 +86,45 @@ export function ChatMessage({
|
||||||
<div className="ChatMessage-timestamp">{timestamp}</div>
|
<div className="ChatMessage-timestamp">{timestamp}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{quotes && (
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={() => {
|
||||||
|
const element = document.getElementById(quotes);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView();
|
||||||
|
element.style.background = `#${themeColor}33`;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
element.style.background = "unset";
|
||||||
|
}, QUOTED_MESSAGE_CONTEXTUAL_HIGHLIGHTING_DURATION);
|
||||||
|
|
||||||
|
const [appContent] = Array.from(
|
||||||
|
document.getElementsByClassName("App-content")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (appContent) {
|
||||||
|
appContent.scrollTop -= SCROLL_TO_QUOTED_OVERFLOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Replying to @{messageLookup[quotes]?.username}:{" "}
|
||||||
|
<em>
|
||||||
|
"
|
||||||
|
{messageLookup[quotes]?.text.slice(
|
||||||
|
0,
|
||||||
|
QUOTED_MESSAGE_CONTEXTUAL_SNIPPET_LENGTH
|
||||||
|
)}
|
||||||
|
{messageLookup[quotes]?.text.length >=
|
||||||
|
QUOTED_MESSAGE_CONTEXTUAL_SNIPPET_LENGTH
|
||||||
|
? "..."
|
||||||
|
: ""}
|
||||||
|
"
|
||||||
|
</em>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
<div className="ChatMessage-bottom">
|
<div className="ChatMessage-bottom">
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { EmojiDrawer, QuickEmojis } from "../emoji";
|
||||||
import "./UserInput.css";
|
import "./UserInput.css";
|
||||||
|
|
||||||
export function UserInput() {
|
export function UserInput() {
|
||||||
const { draft, sendMessage, updateDraft } = useChat();
|
const { messages, draft, sendMessage, updateDraft } = useChat();
|
||||||
const { reveal, hide, open } = useDrawer();
|
const { reveal, hide, open } = useDrawer();
|
||||||
const builtChatInput = useRef<HTMLTextAreaElement>(null);
|
const builtChatInput = useRef<HTMLTextAreaElement>(null);
|
||||||
const { visible, addQuery } = useEmojis();
|
const { visible, addQuery } = useEmojis();
|
||||||
|
@ -29,7 +29,9 @@ export function UserInput() {
|
||||||
const emojiSegment = input.slice(openEmojiToken + 1, closeEmojiToken + 1);
|
const emojiSegment = input.slice(openEmojiToken + 1, closeEmojiToken + 1);
|
||||||
|
|
||||||
updateDraft(input);
|
updateDraft(input);
|
||||||
addQuery(openEmojiToken === -1 ? "" : emojiSegment);
|
addQuery(
|
||||||
|
openEmojiToken === -1 || emojiSegment.includes(" ") ? "" : emojiSegment
|
||||||
|
);
|
||||||
setTypingOffset(
|
setTypingOffset(
|
||||||
emojiSegment.length * process.env.APPROXIMATE_CHARACTER_WIDTH
|
emojiSegment.length * process.env.APPROXIMATE_CHARACTER_WIDTH
|
||||||
);
|
);
|
||||||
|
@ -68,7 +70,6 @@ export function UserInput() {
|
||||||
builtChatInput.current?.focus();
|
builtChatInput.current?.focus();
|
||||||
hide();
|
hide();
|
||||||
}, [hide]);
|
}, [hide]);
|
||||||
|
|
||||||
const handleSelectEmoji = useCallback((emoji: string) => {
|
const handleSelectEmoji = useCallback((emoji: string) => {
|
||||||
updateDraft((prev) => `${prev} :${emoji}: `);
|
updateDraft((prev) => `${prev} :${emoji}: `);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
export * from "./ActivityList";
|
|
||||||
export * from "./Chat";
|
|
||||||
export * from "./ChatHeading";
|
export * from "./ChatHeading";
|
||||||
export * from "./ChatMessage";
|
export * from "./ChatMessage";
|
||||||
export * from "./QuotedMessage";
|
export * from "./QuotedMessage";
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from "./activity";
|
|
||||||
export * from "./chat";
|
export * from "./chat";
|
||||||
export * from "./emoji";
|
export * from "./emoji";
|
||||||
|
|
|
@ -10,6 +10,7 @@ import React, {
|
||||||
} from "react";
|
} from "react";
|
||||||
import { io, Socket } from "socket.io-client";
|
import { io, Socket } from "socket.io-client";
|
||||||
import lozad from "lozad";
|
import lozad from "lozad";
|
||||||
|
import debounce from "lodash.debounce";
|
||||||
import { useRootContext } from "./useRootContext";
|
import { useRootContext } from "./useRootContext";
|
||||||
import { useWindowFocus } from "./useWindowFocus";
|
import { useWindowFocus } from "./useWindowFocus";
|
||||||
|
|
||||||
|
@ -25,12 +26,13 @@ enum ChatHandlers {
|
||||||
interface ChatProviderContext {
|
interface ChatProviderContext {
|
||||||
online: string[];
|
online: string[];
|
||||||
typing: string[];
|
typing: string[];
|
||||||
messages: ChatSpeakResponse[];
|
messages: IChatMessage[];
|
||||||
draft: string;
|
draft: string;
|
||||||
quote: null | ChatSpeakResponse;
|
quote: null | IChatMessage;
|
||||||
|
messageLookup: Record<string, IChatMessage>;
|
||||||
updateDraft: React.Dispatch<React.SetStateAction<string>>;
|
updateDraft: React.Dispatch<React.SetStateAction<string>>;
|
||||||
sendMessage(): void;
|
sendMessage(): void;
|
||||||
quoteMessage(message: null | ChatSpeakResponse): void;
|
quoteMessage(message: null | IChatMessage): void;
|
||||||
deleteMessage(withText: string): void;
|
deleteMessage(withText: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,23 +42,28 @@ const ChatContext = createContext<ChatProviderContext>({
|
||||||
messages: [],
|
messages: [],
|
||||||
draft: "",
|
draft: "",
|
||||||
quote: null,
|
quote: null,
|
||||||
|
messageLookup: {},
|
||||||
updateDraft() {},
|
updateDraft() {},
|
||||||
sendMessage() {},
|
sendMessage() {},
|
||||||
quoteMessage() {},
|
quoteMessage() {},
|
||||||
deleteMessage() {},
|
deleteMessage() {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const MINIMUM_TYPING_UPDATE_INTERVAL = 250;
|
||||||
|
|
||||||
export function ChatProvider({ children }: PropsWithChildren) {
|
export function ChatProvider({ children }: PropsWithChildren) {
|
||||||
const { username, siteName } = useRootContext();
|
const { username, siteName } = useRootContext();
|
||||||
const socket = useRef<null | Socket>(null);
|
const socket = useRef<null | Socket>(null);
|
||||||
const [online, setOnline] = useState<string[]>([]);
|
const [online, setOnline] = useState<string[]>([]);
|
||||||
const [typing, setTyping] = useState<string[]>([]);
|
const [typing, setTyping] = useState<string[]>([]);
|
||||||
const [messages, setMessages] = useState<ChatSpeakResponse[]>([]);
|
const [messages, setMessages] = useState<IChatMessage[]>([]);
|
||||||
const [draft, setDraft] = useState("");
|
const [draft, setDraft] = useState("");
|
||||||
const [quote, setQuote] = useState<null | ChatSpeakResponse>(null);
|
const lastDraft = useRef("");
|
||||||
|
const [quote, setQuote] = useState<null | IChatMessage>(null);
|
||||||
const focused = useWindowFocus();
|
const focused = useWindowFocus();
|
||||||
const [notifications, setNotifications] = useState<number>(0);
|
const [notifications, setNotifications] = useState<number>(0);
|
||||||
const addMessage = useCallback((message: ChatSpeakResponse) => {
|
const [messageLookup, setMessageLookup] = useState({});
|
||||||
|
const addMessage = useCallback((message: IChatMessage) => {
|
||||||
setMessages((prev) => prev.concat(message));
|
setMessages((prev) => prev.concat(message));
|
||||||
|
|
||||||
if (message.username !== username && !document.hasFocus()) {
|
if (message.username !== username && !document.hasFocus()) {
|
||||||
|
@ -64,14 +71,14 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const sendMessage = useCallback(() => {
|
const sendMessage = useCallback(() => {
|
||||||
const message = quote
|
socket.current?.emit(ChatHandlers.SPEAK, {
|
||||||
? `> ${quote.text}\n@${quote.username}<br /><br />${draft}`
|
message: draft,
|
||||||
: draft;
|
quotes: quote?.id ?? null,
|
||||||
socket.current?.emit(ChatHandlers.SPEAK, message);
|
});
|
||||||
|
|
||||||
setQuote(null);
|
setQuote(null);
|
||||||
setDraft("");
|
setDraft("");
|
||||||
}, [draft]);
|
}, [draft, quote]);
|
||||||
const requestDeleteMessage = useCallback((withText: string) => {
|
const requestDeleteMessage = useCallback((withText: string) => {
|
||||||
socket.current?.emit(ChatHandlers.DELETE, withText);
|
socket.current?.emit(ChatHandlers.DELETE, withText);
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -84,7 +91,7 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
||||||
setQuote(null);
|
setQuote(null);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const quoteMessage = useCallback((message: ChatSpeakResponse) => {
|
const quoteMessage = useCallback((message: IChatMessage) => {
|
||||||
setQuote(message);
|
setQuote(message);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -98,6 +105,7 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
||||||
messages,
|
messages,
|
||||||
draft,
|
draft,
|
||||||
quote,
|
quote,
|
||||||
|
messageLookup,
|
||||||
quoteMessage,
|
quoteMessage,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
deleteMessage: requestDeleteMessage,
|
deleteMessage: requestDeleteMessage,
|
||||||
|
@ -109,6 +117,7 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
||||||
messages,
|
messages,
|
||||||
draft,
|
draft,
|
||||||
quote,
|
quote,
|
||||||
|
messageLookup,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
deleteMessage,
|
deleteMessage,
|
||||||
quoteMessage,
|
quoteMessage,
|
||||||
|
@ -128,8 +137,18 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const debouncedTypingUpdater = useMemo(
|
||||||
|
() =>
|
||||||
|
debounce(
|
||||||
|
() => socket.current?.emit(ChatHandlers.TYPING, lastDraft.current),
|
||||||
|
MINIMUM_TYPING_UPDATE_INTERVAL
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.current?.emit(ChatHandlers.TYPING, draft);
|
lastDraft.current = draft;
|
||||||
|
debouncedTypingUpdater();
|
||||||
}, [draft]);
|
}, [draft]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -138,6 +157,15 @@ export function ChatProvider({ children }: PropsWithChildren) {
|
||||||
}
|
}
|
||||||
}, [focused]);
|
}, [focused]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMessageLookup(
|
||||||
|
messages.reduce((prev, next) => {
|
||||||
|
prev[next.id] = next;
|
||||||
|
return prev;
|
||||||
|
}, {} as Record<string, IChatMessage>)
|
||||||
|
);
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
// Display e.g. [+2] Chat when notifications occur when you're away.
|
// Display e.g. [+2] Chat when notifications occur when you're away.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const title = document.getElementsByTagName("title")[0];
|
const title = document.getElementsByTagName("title")[0];
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
.Activity{display:flex;align-items:center;padding:1rem;margin:0 4rem}.Activity>section{display:flex;align-items:center;justify-content:space-between;padding:0 1rem}.Activity i{margin-right:3rem}.Activity>section:not(:last-child){margin-right:2rem}.ActivityList{margin-left:2rem}.ActivityList h4{display:flex;align-items:center;justify-content:space-between}.ActivityList h4 hr{flex:1;margin-right:1rem}.ActivityList-activity{display:flex;align-items:center;justify-content:space-between;cursor:pointer}.ActivityList-activity-icon{margin-right:1rem}.ActivityList-activity{transition:background .4s ease-in-out;padding:0 1rem}.ActivityList-activity:hover{cursor:pointer;background:#ffffff05}.Username{display:inline-flex;align-items:center}.Username>a{font-weight:700;margin-left:8px}@keyframes fading-in{0%{opacity:0}to{opacity:1}}.ChatMessage{padding:.5rem 3rem .5rem .5rem;position:relative;animation:fading-in .3s ease-in-out forwards}.ChatMessage-top{display:flex;align-items:center}.ChatMessage-timestamp{margin-left:.5rem}.ChatMessage-bottom{display:flex;align-items:center;justify-content:space-between;padding-left:30px}.ChatMessage-content{margin-right:.5rem;word-break:break-all;display:inline-block}.ChatMessage-button{background:transparent!important}.ChatMessage-button__confirmed i{color:red!important}.ChatMessage-delete{position:absolute;top:4px;right:4px}.ChatMessageList{flex:1}.QuotedMessage{display:flex;align-items:center;justify-content:space-between}.QuotedMessage-content{margin-left:1rem;flex:1;max-width:420px;max-height:40px;overflow:hidden;text-overflow:ellipsis;margin-right:1rem}.BaseDrawer{flex:1;padding-right:2rem;overflow:hidden}.EmojiDrawer-options{display:flex;align-items:center;justify-content:space-between}.UserInput{position:relative}.UserInput-emoji{cursor:pointer;position:absolute;top:12px;right:12px;font-size:20px}.UserList{padding:1rem;border-left:1px dotted var(--primary);flex:1;position:absolute;top:0;left:0;width:100%;height:100%;overflow:auto}.UserList-heading{display:flex;align-items:center;justify-content:space-between}.UserList-heading h5{margin-right:2rem}.UserList ul::-webkit-scrollbar{display:none}.Chat{display:flex;align-items:stretch;justify-content:center;position:relative;overflow:hidden;width:100%;min-width:350px;max-width:1200px}.Chat-mobile-top{display:none;align-items:center;justify-content:flex-end;margin-bottom:1rem}.Chat-mobile-top i{margin-right:.25rem;position:relative;top:-1px}.Chat-mobile-top span{font-size:18px}.Chat-side{display:flex;flex-direction:column;width:40%}@media screen and (max-width: 1180px){.Chat-mobile-top{display:flex}.Chat-side{display:none}}.Chat-drawer{padding-right:1rem}.Chat-typing{height:18px;display:inline-block}.Chat-window{width:100%}.ChatHeading{flex:1;display:flex;align-items:center;justify-content:space-between}.ChatHeading i{margin-right:.5rem}.UsersTyping{height:18px;display:inline-block}html,body{overscroll-behavior-y:none}.App{position:fixed;width:100vw;display:flex;overflow:hidden}.App-wrapper{flex:1;overflow:hidden;display:flex;flex-direction:column;margin:0 auto;max-width:1000px}.App-heading{flex-basis:3rem;border-bottom:1px dashed var(--primary);display:flex;align-items:center}.App-heading small{opacity:.2}.App-side{height:100%;flex:1;background:var(--gray-500);position:relative}.App-content{position:relative;flex:3;height:60vh;max-height:1000px;overflow:auto;-ms-overflow-style:none;scrollbar-width:none;display:flex;flex-direction:column;border-bottom:1px dashed var(--primary)}.App-content::-webkit-scrollbar{display:none}.App-drawer{z-index:2;display:flex;background:var(--background);height:100%}.App-center,.App-bottom-wrapper{display:flex;align-items:flex-start}.App-bottom{flex:3}.App-bottom-dummy{flex:1}.App-bottom-extra{padding:1rem;height:64px}@media screen and (max-width: 1100px){.App-side,.App-bottom-dummy{display:none}.App-bottom-wrapper{padding-right:1rem;padding-left:1rem}}lite-youtube{min-width:min(80vw,500px)}
|
|
|
@ -1,4 +1,5 @@
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
from files.helpers.jinja2 import timestamp
|
from files.helpers.jinja2 import timestamp
|
||||||
from files.helpers.wrappers import *
|
from files.helpers.wrappers import *
|
||||||
from files.helpers.sanitize import sanitize
|
from files.helpers.sanitize import sanitize
|
||||||
|
@ -54,11 +55,17 @@ def speak(data, v):
|
||||||
global messages, total
|
global messages, total
|
||||||
|
|
||||||
if SITE == 'rdrama.net': text = data[:200].strip()
|
if SITE == 'rdrama.net': text = data[:200].strip()
|
||||||
else: text = data[:1000].strip()
|
else: text = data['message'][:1000].strip()
|
||||||
|
|
||||||
if not text: return '', 403
|
if not text: return '', 403
|
||||||
text_html = sanitize(text, count_marseys=True)
|
text_html = sanitize(text, count_marseys=True)
|
||||||
|
print("\n\n\n\n\n\n\n\n\n")
|
||||||
|
print("\n\n\n\n\n\n\n\n\n")
|
||||||
|
print(data)
|
||||||
|
quotes = data['quotes']
|
||||||
data={
|
data={
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"quotes": quotes,
|
||||||
"avatar": v.profile_url,
|
"avatar": v.profile_url,
|
||||||
"hat": v.hat_active,
|
"hat": v.hat_active,
|
||||||
"username": v.username,
|
"username": v.username,
|
||||||
|
|
Loading…
Reference in New Issue