diff --git a/chat/.env.template b/chat/.env.template deleted file mode 100644 index 66ceacfec..000000000 --- a/chat/.env.template +++ /dev/null @@ -1,6 +0,0 @@ -FEATURES_ACTIVITY=false -DEBUG=false -NODE_ENV="production" -EMOJI_INPUT_TOKEN=":" -QUICK_EMOJIS_MAX_COUNT = 20 -APPROXIMATE_CHARACTER_WIDTH = 8 \ No newline at end of file diff --git a/chat/build.js b/chat/build.js deleted file mode 100644 index 6d455164f..000000000 --- a/chat/build.js +++ /dev/null @@ -1,22 +0,0 @@ -require('dotenv').config() -const package = require("./package.json"); -const path = require("path"); -const { build } = require("esbuild"); - -const options = { - entryPoints: ["./src/index.tsx"], - outfile: path.resolve(__dirname, "../files/assets/js/chat_done.js"), - bundle: true, - minify: process.env.NODE_ENV === "production", - define: { - "process.env.VERSION": `"${package.version}"`, - "process.env.NODE_ENV": `"${process.env.NODE_ENV}"`, - "process.env.DEBUG": process.env.DEBUG, - "process.env.FEATURES_ACTIVITY": process.env.FEATURES_ACTIVITY, - "process.env.EMOJI_INPUT_TOKEN": `"${process.env.EMOJI_INPUT_TOKEN}"`, - "process.env.QUICK_EMOJIS_MAX_COUNT": process.env.QUICK_EMOJIS_MAX_COUNT, - "process.env.APPROXIMATE_CHARACTER_WIDTH": process.env.APPROXIMATE_CHARACTER_WIDTH, - }, -}; - -build(options).catch(() => process.exit(1)); diff --git a/chat/global.d.ts b/chat/global.d.ts deleted file mode 100644 index a2e584d33..000000000 --- a/chat/global.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -declare const process: { - env: Record; -}; - -declare interface IChatMessage { - id: string; - username: string; - user_id?: string; - avatar: string; - hat: string; - namecolor: string; - text: string; - base_text_censored: string; - text_censored: string; - text_html: string; - time: number; - quotes: null | string; - dm: boolean; -} - -declare interface EmojiModSelection { - large: boolean; - mirror: boolean; - pat: boolean; -} diff --git a/chat/package.json b/chat/package.json deleted file mode 100644 index 1af1fcef4..000000000 --- a/chat/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "chat", - "version": "0.1.27", - "main": "./src/index.tsx", - "license": "MIT", - "dependencies": { - "@types/humanize-duration": "^3.27.1", - "@types/lodash.clonedeep": "^4.5.7", - "@types/lodash.debounce": "^4.0.7", - "@types/lodash.throttle": "^4.1.7", - "@types/react": "^18.0.21", - "@types/react-dom": "^18.0.7", - "@types/react-virtualized-auto-sizer": "^1.0.1", - "@types/react-window": "^1.8.5", - "classnames": "^2.3.2", - "dotenv": "^16.0.3", - "esbuild": "^0.15.11", - "humanize-duration": "^3.27.3", - "lodash.clonedeep": "^4.5.0", - "lodash.debounce": "^4.0.8", - "lodash.throttle": "^4.1.1", - "react": "^18.2.0", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", - "react-dom": "^18.2.0", - "react-virtualized-auto-sizer": "^1.0.7", - "react-window": "^1.8.8", - "run-when-changed": "^2.1.0", - "socket.io-client": "^4.5.3", - "typescript": "^4.8.4", - "weak-key": "^1.0.2" - }, - "scripts": { - "chat": "yarn check && yarn build && yarn css:move", - "chat:watch": "run-when-changed --watch \"**/*.*\" --exec \"yarn chat\"", - "check": "tsc", - "build": "node ./build", - "css:move": "mv ../files/assets/js/chat_done.css ../files/assets/css/chat_done.css" - } -} diff --git a/chat/src/App.css b/chat/src/App.css deleted file mode 100644 index e1bfdc87d..000000000 --- a/chat/src/App.css +++ /dev/null @@ -1,134 +0,0 @@ -html, -body { - overscroll-behavior-y: none; -} - -html { - height: -webkit-fill-available; -} - -body { - min-height: 100vh; - min-height: calc(var(--vh, 1vh) * 100); - overflow: hidden; - /* mobile viewport bug fix */ - min-height: -webkit-fill-available; -} - -.App { - position: fixed; - width: 100vw; - display: flex; - overflow: hidden; -} - -.App-wrapper { - flex: 1; - overflow: hidden; - display: flex; - flex-direction: column; - justify-content: center; - margin: 0 2rem; -} - -.App-heading { - flex-basis: 3rem; - display: flex; - align-items: center; -} - -.App-heading small { - opacity: 0.2; - font-size: 10px; -} - -.App-side { - height: 100%; - flex: 1; - background: var(--gray-500); - position: relative; -} - -.App-content { - position: relative; - flex: 3; - height: 62vh; - height: calc(var(--vh, 1vh) * 72); - max-height: 1000px; - overflow: auto; - -ms-overflow-style: none; - scrollbar-width: none; - display: flex; - flex-direction: column; -} - -.App-content::-webkit-scrollbar { - display: none; -} - -.App-drawer { - z-index: 2; - display: flex; - background: rgb(var(--background)); - height: 100%; -} - - -.App-center { - display: flex; - align-items: flex-start; -} - -.App-bottom-wrapper { - display: flex; - align-items: flex-start; -} - -.App-bottom { - flex: 3; -} - -.App-bottom-dummy { - flex: 1; -} - -.App-bottom-extra { - padding: .25rem; -} - -/* On mobile, hide the sidebar and make the input full-width. */ -@media screen and (max-width: 1100px) { - .App-wrapper { - margin: 0 auto; - } - - .App-heading { - padding: 0 1rem; - } - - .App-side { - display: none; - } - - .App-bottom-dummy { - display: none; - } - - .App-bottom-wrapper { - padding-right: 1rem; - padding-left: 1rem; - } -} - -lite-youtube { - min-width: min(80vw, 500px); -} - -.btn-secondary { - border: none !important; -} - -.btn-secondary:focus { - border: none !important; - box-shadow: none !important; -} diff --git a/chat/src/App.tsx b/chat/src/App.tsx deleted file mode 100644 index 0b30f18db..000000000 --- a/chat/src/App.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import cx from "classnames"; -import throttle from "lodash.throttle"; -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { DndProvider, useDrop } from "react-dnd"; -import { HTML5Backend } from "react-dnd-html5-backend"; -import "./App.css"; -import { - ChatHeading, - ChatMessageList, - QuotedMessage, - UserInput, - UserList, - UsersTyping, -} from "./features"; -import { ChatProvider, DrawerProvider, useChat, useDrawer } from "./hooks"; - -const SCROLL_CANCEL_THRESHOLD = 500; -const WINDOW_RESIZE_THROTTLE_WAIT = 250; - -export function App() { - return ( - - - - - - - - ); -} - -function AppInner() { - const [_, dropRef] = useDrop({ - accept: "drawer", - }); - const { open, config } = useDrawer(); - const contentWrapper = useRef(null); - const initiallyScrolledDown = useRef(false); - const { messages, quote, userToDm, updateUserToDm } = useChat(); - const [focused, setFocused] = useState(false); - const toggleFocus = useCallback(() => { - setTimeout(() => { - setFocused(prev => !prev); - }, 0); - }, []); - - // See: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ - useEffect(() => { - const updateViewportHeightUnit = () => { - const vh = window.innerHeight * 0.01; - document.documentElement.style.setProperty("--vh", `${vh}px`); - }; - const throttledResizeHandler = throttle( - updateViewportHeightUnit, - WINDOW_RESIZE_THROTTLE_WAIT - ); - - throttledResizeHandler(); - - window.addEventListener("resize", throttledResizeHandler); - - return () => { - window.removeEventListener("resize", throttledResizeHandler); - }; - }, []); - - useEffect(() => { - if (messages.length > 0) { - if (initiallyScrolledDown.current) { - /* We only want to scroll back down on a new message - if the user is not scrolled up looking at previous messages. */ - const scrollableDistance = - contentWrapper.current.scrollHeight - - contentWrapper.current.clientHeight; - const scrolledDistance = contentWrapper.current.scrollTop; - const hasScrolledEnough = - scrollableDistance - scrolledDistance >= SCROLL_CANCEL_THRESHOLD; - - if (hasScrolledEnough) { - return; - } - } else { - // Always scroll to the bottom on first load. - initiallyScrolledDown.current = true; - } - - contentWrapper.current.scrollTop = contentWrapper.current.scrollHeight; - } - }, [messages]); - - useEffect(() => { - if (!open) { - // Scroll to the bottom after any drawer closes. - contentWrapper.current.scrollTop = contentWrapper.current.scrollHeight; - } - }, [open]); - - return ( -
-
-
- v{process.env.VERSION} - -
-
-
- {open ? ( -
{config.content}
- ) : ( - - )} -
-
- -
-
-
-
- {quote && ( -
- -
- )} - {userToDm && ( -
- Directly messaging @{userToDm.username} - -
- )} - - -
-
-
-
-
- ); -} diff --git a/chat/src/drawers/BaseDrawer.css b/chat/src/drawers/BaseDrawer.css deleted file mode 100644 index 5d76f8c9a..000000000 --- a/chat/src/drawers/BaseDrawer.css +++ /dev/null @@ -1,5 +0,0 @@ -.BaseDrawer { - flex: 1; - padding-right: 2rem; - overflow: hidden; -} diff --git a/chat/src/drawers/BaseDrawer.tsx b/chat/src/drawers/BaseDrawer.tsx deleted file mode 100644 index f1fdedb49..000000000 --- a/chat/src/drawers/BaseDrawer.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React, { PropsWithChildren, useEffect } from "react"; -import "./BaseDrawer.css"; - -interface Props extends PropsWithChildren { - onClose?(): void; -} - -export function BaseDrawer({ onClose, children }: Props) { - return
{children}
; -} diff --git a/chat/src/drawers/index.ts b/chat/src/drawers/index.ts deleted file mode 100644 index 38fdad714..000000000 --- a/chat/src/drawers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./BaseDrawer" \ No newline at end of file diff --git a/chat/src/features/chat/ActivityList.css b/chat/src/features/chat/ActivityList.css deleted file mode 100644 index 763807152..000000000 --- a/chat/src/features/chat/ActivityList.css +++ /dev/null @@ -1,35 +0,0 @@ -.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 0.4s ease-in-out; - padding: 0 1rem; -} - -.ActivityList-activity:hover { - cursor: pointer; - background: #ffffff05; -} \ No newline at end of file diff --git a/chat/src/features/chat/ActivityList.tsx b/chat/src/features/chat/ActivityList.tsx deleted file mode 100644 index de2ce30c0..000000000 --- a/chat/src/features/chat/ActivityList.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from "react"; -import cx from 'classnames' -import { useDrawer } from "../../hooks"; -import "./ActivityList.css"; - -const ACTIVITIES = [ - { - game: "Poker", - description: "Know when to hold 'em.", - icon: "fas fa-cards", - }, - { - game: "Roulette", - description: "Table go brrrr.", - icon: "fas fa-circle", - }, - { - game: "Slots", - description: "Is today your lucky day?", - icon: "fas fa-dollar-sign", - }, - { - game: "Blackjack", - description: "Twenty one ways to change your life.", - icon: "fas fa-cards", - }, - { - game: "Racing", - description: "Look at 'em go.", - icon: "fas fa-cards", - }, - { - game: "Crossing", - description: "A simple life.", - icon: "fas fa-cards", - }, - { - game: "Lottershe", - description: "Can't win if you don't play.", - icon: "fas fa-ticket", - }, -]; - -export function ActivityList() { - const { toggle } = useDrawer(); - - return ( -
-

-
- Activities -

-
- {ACTIVITIES.map(({ game, description, icon }) => ( -
-
-
- -
{game}
{description}
-
- - 0 -
-
- ))} -
-
- ); -} diff --git a/chat/src/features/chat/ChatHeading.css b/chat/src/features/chat/ChatHeading.css deleted file mode 100644 index 5c13a474a..000000000 --- a/chat/src/features/chat/ChatHeading.css +++ /dev/null @@ -1,10 +0,0 @@ -.ChatHeading { - flex: 1; - display: flex; - align-items: center; - justify-content: space-between; -} - -.ChatHeading i { - margin-right: 0.5rem; -} \ No newline at end of file diff --git a/chat/src/features/chat/ChatHeading.tsx b/chat/src/features/chat/ChatHeading.tsx deleted file mode 100644 index 07205d141..000000000 --- a/chat/src/features/chat/ChatHeading.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { useCallback } from "react"; -import { useChat, useDrawer } from "../../hooks"; -import { UserList } from "./UserList"; -import "./ChatHeading.css"; - -export function ChatHeading() { - const { open, hide, reveal } = useDrawer(); - const { online } = useChat(); - const handleToggleUserListDrawer = useCallback(() => { - if (open) { - hide(); - } else { - reveal({ - title: "Users in chat", - content: , - }); - } - }, [open]); - - return ( -
-
-
- {open ? ( - - ) : ( - <> - - {online.length} users online - - )} -
-
- ); -} diff --git a/chat/src/features/chat/ChatMessage.css b/chat/src/features/chat/ChatMessage.css deleted file mode 100644 index 2a2b33396..000000000 --- a/chat/src/features/chat/ChatMessage.css +++ /dev/null @@ -1,126 +0,0 @@ -.ChatMessage { - position: relative; - padding-right: 1.5rem; - max-height: 300px; - overflow: scroll; -} - -.ChatMessage__isDm { - background: var(--gray-800); - border-top: 1px dashed var(--primary); - border-bottom: 1px dashed var(--primary); -} - -.ChatMessage__isOptimistic { - opacity: 0.5; -} - -.ChatMessage p { - margin: 0; -} - -.ChatMessage .btn { - border: none !important; -} - -.ChatMessage-top { - display: flex; - align-items: center; -} - -.ChatMessage-timestamp { - margin-left: 0.5rem; - opacity: 0.5; - font-size: 10px; -} - -.ChatMessage-bottom { - display: flex; - align-items: center; - justify-content: space-between; - padding-left: 30px; - overflow: hidden; -} - -.ChatMessage-content { - margin-right: 0.5rem; - word-wrap: break-word; - display: inline-block; -} - -.ChatMessage-button { - margin: 0 0.5rem; -} - -.ChatMessage-button i { - margin-right: 0.5rem; -} - -.ChatMessage-button__confirmed { - color: red !important; -} - -.ChatMessage-quoted-link { - padding-left: 2rem; -} - -.ChatMessage-actions-button { - position: absolute; - top: 0; - right: 0; - cursor: pointer; - z-index: 5; - background: none !important; - border: none !important; - box-shadow: none !important; - display: flex; - align-items: center; -} - -.ChatMessage-actions-button button { - background: none !important; - border: none !important; - padding: 0 !important; -} - -.ChatMessage-actions-button button i { - position: relative; - top: 3px; - margin-right: 1rem; -} - -.ChatMessage-actions { - z-index: 1; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background: rgba(20, 20, 20, 0.85); - display: flex; - align-items: center; - justify-content: flex-end; - padding: 1rem; - padding-right: 3rem; - animation: fading-in 0.3s ease-in-out forwards; -} - -.ChatMessage-actions button { - font-size: 10px; - background: none !important; -} - -/* List */ -.ChatMessageList { - flex: 1; -} - -.ChatMessageList-group { - margin-bottom: 1rem; - padding: 0.3rem; - border-radius: 8px; -} - -.ChatMessageList-group:nth-child(even) { - background: rgba(255, 255, 255, 0.025); -} diff --git a/chat/src/features/chat/ChatMessage.tsx b/chat/src/features/chat/ChatMessage.tsx deleted file mode 100644 index 5a172e798..000000000 --- a/chat/src/features/chat/ChatMessage.tsx +++ /dev/null @@ -1,337 +0,0 @@ -import React, { - useCallback, - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from "react"; -import cx from "classnames"; -import key from "weak-key"; -import humanizeDuration from "humanize-duration"; -import cloneDeep from "lodash.clonedeep"; -import { Username } from "./Username"; -import { - DIRECT_MESSAGE_ID, - OPTIMISTIC_MESSAGE_ID, - useChat, - useRootContext, -} from "../../hooks"; -import { QuotedMessageLink } from "./QuotedMessageLink"; -import "./ChatMessage.css"; - -interface ChatMessageProps { - message: IChatMessage; - timestampUpdates: number; - showUser?: boolean; - actionsOpen: boolean; - onToggleActions(messageId: string): void; -} - -const TIMESTAMP_UPDATE_INTERVAL = 20000; - -export function ChatMessage({ - message, - showUser = true, - timestampUpdates, - actionsOpen, - onToggleActions, -}: ChatMessageProps) { - const { - id, - user_id, - avatar, - namecolor, - username, - hat, - text, - text_html, - text_censored, - time, - quotes, - dm, - } = message; - const { - id: userId, - username: userUsername, - admin, - censored, - themeColor, - } = useRootContext(); - const { - messageLookup, - userToDm, - quote, - deleteMessage, - quoteMessage, - updateUserToDm, - } = useChat(); - const [confirmedDelete, setConfirmedDelete] = useState(false); - const quotedMessage = messageLookup[quotes]; - const content = censored ? text_censored : text_html; - const isMention = - quotedMessage?.username === userUsername || - (text_html.includes(`/id/${userId}`) && - userUsername && - username !== userUsername); - const isDirect = id === DIRECT_MESSAGE_ID; - const isOptimistic = id === OPTIMISTIC_MESSAGE_ID; - const timestamp = useMemo( - () => formatTimeAgo(time), - [time, timestampUpdates] - ); - const handleDeleteMessage = useCallback(() => { - if (confirmedDelete) { - deleteMessage(text); - } else { - setConfirmedDelete(true); - } - }, [text, confirmedDelete]); - const handleQuoteMessageAction = useCallback(() => { - updateUserToDm(null); - quoteMessage(message); - onToggleActions(message.id); - }, [message, onToggleActions]); - const handleDirectMessage = useCallback( - (toggle?: boolean) => { - const userId = message.user_id ?? ""; - - if (userToDm && userToDm.id === userId) { - updateUserToDm(null); - } else if (userId) { - updateUserToDm({ - id: userId, - username: message.username, - }); - - quoteMessage(null); - - if (toggle) { - onToggleActions(message.id); - } - } - }, - [userToDm, message.id, message.user_id, message.username] - ); - - useEffect(() => { - if (!actionsOpen) { - setConfirmedDelete(false); - } - }, [actionsOpen]); - - return ( -
- {!isDirect && !isOptimistic && !actionsOpen && ( -
- - -
- )} - {!isDirect && !isOptimistic && actionsOpen && ( -
- {userId && parseInt(userId) !== parseInt(user_id) && ( - - )} - - {admin && ( - - )} - -
- )} - {showUser && ( -
- -
{timestamp}
-
- )} - {quotes && quotedMessage && ( -
- -
- )} - {!isDirect && dm && ( - - (Sent only to you) - - )} -
-
- -
-
-
- ); -} - -export function ChatMessageList() { - const listRef = useRef(null); - const { messages } = useChat(); - 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(() => { - const updatingTimestamps = setInterval( - () => setTimestampUpdates((prev) => prev + 1), - TIMESTAMP_UPDATE_INTERVAL - ); - - return () => { - clearInterval(updatingTimestamps); - }; - }, []); - - useLayoutEffect(() => { - const images = Array.from( - listRef.current.getElementsByTagName("img") - ).filter((image) => image.dataset.src); - - for (const image of images) { - image.src = image.dataset.src; - } - }, [messages]); - - return ( -
- {groupedMessages.map((group) => ( -
- {group.map((message, index) => ( - - ))} -
- ))} -
- ); -} - -function formatTimeAgo(time: number) { - const shortEnglishHumanizer = humanizeDuration.humanizer({ - language: "shortEn", - languages: { - shortEn: { - y: () => "y", - mo: () => "mo", - w: () => "w", - d: () => "d", - h: () => "h", - m: () => "m", - s: () => "s", - ms: () => "ms", - }, - }, - round: true, - units: ["h", "m", "s"], - largest: 2, - spacer: "", - delimiter: ", ", - }); - const now = new Date().getTime(); - const humanized = `${shortEnglishHumanizer(time * 1000 - now)} ago`; - - 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; -} diff --git a/chat/src/features/chat/QuotedMessage.css b/chat/src/features/chat/QuotedMessage.css deleted file mode 100644 index 41b149432..000000000 --- a/chat/src/features/chat/QuotedMessage.css +++ /dev/null @@ -1,15 +0,0 @@ -.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; -} diff --git a/chat/src/features/chat/QuotedMessage.tsx b/chat/src/features/chat/QuotedMessage.tsx deleted file mode 100644 index ff9c53888..000000000 --- a/chat/src/features/chat/QuotedMessage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import { useChat } from "../../hooks"; -import "./QuotedMessage.css"; -import { QuotedMessageLink } from "./QuotedMessageLink"; - -export function QuotedMessage() { - const { quote, quoteMessage } = useChat(); - - return ( -
- - -
- ); -} diff --git a/chat/src/features/chat/QuotedMessageLink.css b/chat/src/features/chat/QuotedMessageLink.css deleted file mode 100644 index e6667b8d5..000000000 --- a/chat/src/features/chat/QuotedMessageLink.css +++ /dev/null @@ -1,3 +0,0 @@ -.QuotedMessageLink { - font-size: 10px; -} \ No newline at end of file diff --git a/chat/src/features/chat/QuotedMessageLink.tsx b/chat/src/features/chat/QuotedMessageLink.tsx deleted file mode 100644 index 1e598a436..000000000 --- a/chat/src/features/chat/QuotedMessageLink.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useCallback, useMemo } from "react"; -import { useRootContext } from "../../hooks"; -import "./QuotedMessageLink.css"; - -const SCROLL_TO_QUOTED_OVERFLOW = 250; -const QUOTED_MESSAGE_CONTEXTUAL_HIGHLIGHTING_DURATION = 2500; -const QUOTED_MESSAGE_CONTEXTUAL_SNIPPET_LENGTH = 30; - -export function QuotedMessageLink({ message }: { message: IChatMessage }) { - const { censored, themeColor } = useRootContext(); - const handleLinkClick = useCallback(() => { - const element = document.getElementById(message.id); - - 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; - } - } - }, []); - const replyText = useMemo(() => { - const textToUse = censored ? message.base_text_censored || message.text : message.text; - const slicedText = textToUse.slice( - 0, - QUOTED_MESSAGE_CONTEXTUAL_SNIPPET_LENGTH - ); - - return textToUse.length >= QUOTED_MESSAGE_CONTEXTUAL_SNIPPET_LENGTH - ? `${slicedText}...` - : slicedText; - }, [message, censored]); - - return ( - - @{message.username}:{" "} - "{replyText}" - - ); -} diff --git a/chat/src/features/chat/UserInput.css b/chat/src/features/chat/UserInput.css deleted file mode 100644 index e44d174b0..000000000 --- a/chat/src/features/chat/UserInput.css +++ /dev/null @@ -1,28 +0,0 @@ -.UserInput { - position: relative; - display: flex; - align-items: center; -} - -.UserInput-input { - flex: 1; - margin-right: 2rem; - min-height: 50px; - height: 50px; - max-height: 50px; -} - -@media (min-width: 768px) { - .UserInput-input { - min-height: 100px !important; - height: 100px !important; - max-height: 100px !important; - } -} - -.UserInput-emoji { - cursor: pointer; - font-size: 20px; - transform: rotateY(180deg); - margin-right: 1rem; -} diff --git a/chat/src/features/chat/UserInput.tsx b/chat/src/features/chat/UserInput.tsx deleted file mode 100644 index f296a7160..000000000 --- a/chat/src/features/chat/UserInput.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import React, { - ChangeEvent, - KeyboardEvent, - FormEvent, - useCallback, - useRef, - useMemo, - useState, - useEffect, -} from "react"; -import cx from "classnames"; -import { useChat, useEmojis } from "../../hooks"; -import { QuickEmojis } from "../emoji"; -import "./UserInput.css"; - -interface Props { - large?: boolean; - onFocus(): void; - onBlur(): void; -} - -export function UserInput({ large = false, onFocus, onBlur }: Props) { - const { draft, userToDm, sendMessage, updateDraft } = useChat(); - const builtChatInput = useRef(null); - const { visible, addQuery } = useEmojis(); - const form = useRef(null); - const [typingOffset, setTypingOffset] = useState(0); - const quickEmojis = useMemo( - () => visible.slice(0, process.env.QUICK_EMOJIS_MAX_COUNT), - [visible] - ); - const handleChange = useCallback( - (event: ChangeEvent) => { - const input = event.target.value; - const [openEmojiToken, closeEmojiToken] = locateEmojiTokens(input); - const emojiSegment = input.slice(openEmojiToken + 1, closeEmojiToken + 1); - - updateDraft(input); - addQuery( - openEmojiToken === -1 || emojiSegment.includes(" ") ? "" : emojiSegment - ); - setTypingOffset( - emojiSegment.length * process.env.APPROXIMATE_CHARACTER_WIDTH - ); - }, - [] - ); - const handleSendMessage = useCallback( - (event?: FormEvent) => { - event?.preventDefault(); - sendMessage(); - }, - [sendMessage] - ); - const handleKeyUp = useCallback( - (event: KeyboardEvent) => { - if (event.key === "Enter" && !event.shiftKey) { - handleSendMessage(); - } - }, - [handleSendMessage] - ); - const handleInsertQuickEmoji = useCallback( - (emoji: string) => { - const [openEmojiToken, closeEmojiToken] = locateEmojiTokens(draft); - const toReplace = draft.slice(openEmojiToken, closeEmojiToken + 1); - - updateDraft((prev) => prev.replace(toReplace, `:${emoji}: `)); - addQuery(""); - - builtChatInput.current?.focus(); - }, - [draft] - ); - const handleFocus = useCallback(() => { - builtChatInput.current?.scrollIntoView({ behavior: "smooth" }); - onFocus(); - }, [onFocus]); - - // Listen for changes from the Emoji Modal and reflect them in draft - useEffect(() => { - const handleEmojiInsert = (event: CustomEvent<{ emoji: string }>) => - updateDraft((prev) => `${prev} ${event.detail.emoji} `); - - document.addEventListener("emojiInserted", handleEmojiInsert); - - return () => { - document.removeEventListener("emojiInserted", handleEmojiInsert); - } - }, []); - - useEffect(() => { - if (userToDm) { - builtChatInput.current?.focus(); - } - }, [userToDm]) - - return ( -
- {quickEmojis.length > 0 && ( -
- -
- )} -