react text-based send message input

This commit is contained in:
Michael Peters 2021-12-26 17:23:24 -06:00
parent 26139af512
commit ccba0f1057
9 changed files with 2286 additions and 2330 deletions

4471
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,8 +12,10 @@
"colors": "^1.4.0",
"file-type": "^16.5.3",
"image-size": "^1.0.0",
"markdown": "^0.5.0",
"moment": "^2.29.1",
"pg": "^8.7.1",
"react-contenteditable": "^3.3.6",
"sass": "^1.43.4",
"sharp": "^0.29.2",
"socket.io": "^4.3.1",

View File

@ -15,4 +15,4 @@
padding: 8px;
border-radius: 4px;
}
}
}

View File

@ -10,6 +10,7 @@ import ConnectionInfo from './sections/connection-info';
import GuildTitle from './sections/guild-title';
import UI from '../ui';
import ChannelList from './lists/channel-list';
import SendMessage from './sections/send-message';
export function mountBaseComponents() {
// guild-list
@ -37,10 +38,13 @@ export function mountGuildComponents(q: Q, ui: UI, guild: CombinedGuild) {
export function mountGuildChannelComponents(q: Q, guild: CombinedGuild, channel: Channel) {
// channel-title pieces
ElementsUtil.unmountReactComponent(q.$('.channel-title-anchor'));
ElementsUtil.mountReactComponent(q.$('.channel-title-anchor'), <ChannelTitle channel={channel} />)
ElementsUtil.mountReactComponent(q.$('.channel-title-anchor'), <ChannelTitle channel={channel} />);
// message-list
// Up Next: Figure out why this is empty
ElementsUtil.unmountReactComponent(q.$('.message-list-anchor'));
ElementsUtil.mountReactComponent(q.$('.message-list-anchor'), <MessageList guild={guild} channel={channel} />)
ElementsUtil.mountReactComponent(q.$('.message-list-anchor'), <MessageList guild={guild} channel={channel} />);
// send-message
ElementsUtil.unmountReactComponent(q.$('.send-message-input-wrapper-anchor'));
ElementsUtil.mountReactComponent(q.$('.send-message-input-wrapper-anchor'), <SendMessage guild={guild} channel={channel} />);
}

View File

@ -245,6 +245,13 @@ export default class BaseElements {
Z` }
}
static SEND_MESSAGE_ATTACH = (
// Yoinked directly from Discord
<svg viewBox="0 0 24 24">
<path fill="currentColor" d="M12 2.00098C6.486 2.00098 2 6.48698 2 12.001C2 17.515 6.486 22.001 12 22.001C17.514 22.001 22 17.515 22 12.001C22 6.48698 17.514 2.00098 12 2.00098ZM17 13.001H13V17.001H11V13.001H7V11.001H11V7.00098H13V11.001H17V13.001Z" />
</svg>
);
static createContextMenu(document: Document, content: JSX.Element): HTMLElementWithRemoveSelf {
const element = ReactHelper.createElementFromJSX(
<div className="context">

View File

@ -0,0 +1,76 @@
import * as electronRemote from '@electron/remote';
const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import React, { FC, FormEvent, KeyboardEvent, RefObject, useCallback, useMemo, useRef, useState } from 'react'
import { Channel } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import BaseElements from '../require/base-elements';
import ReactHelper from '../require/react-helper';
export interface SendMessageProps {
guild: CombinedGuild;
channel: Channel;
}
const SendMessage: FC<SendMessageProps> = (props: SendMessageProps) => {
const { guild, channel } = props;
const contentEditableRef = useRef<HTMLDivElement>(null);
const [ text, setText ] = useState<string>('');
const [ enabled, setEnabled ] = useState<boolean>(true);
const [ sendCallable ] = ReactHelper.useAsyncVoidCallback(
async (isMounted: RefObject<boolean>) => {
if (!enabled) return;
setEnabled(false);
// TODO: Deal with errors (toasts are probably the best way)
await guild.requestSendMessage(channel.id, text);
if (!isMounted.current) return;
if (contentEditableRef.current) contentEditableRef.current.innerText = '';
setEnabled(true);
if (contentEditableRef.current) contentEditableRef.current.focus();
},
[ enabled, guild, channel, text ]
);
const onTextInput = useCallback((e: FormEvent<HTMLDivElement>) => {
setText(e.currentTarget.innerText);
}, []);
const onTextKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendCallable();
}
}, [ text ]);
// WARNING: The types on this are funky because of react's lack of explicit support for 'plaintext-only'
const contentEditableType = useMemo(() => {
if (enabled) return 'plaintext-only' as 'true'; // WARNING: we have to do this to trick react's bad typings
else return 'false';
}, [ enabled ]);
const textInputClassName = useMemo(() => {
return enabled ? 'text-input' : 'text-input disabled';
}, [ enabled ]);
return (
<div className="send-message-input-wrapper">
<div className="send-message-input">
<div className="resource-input-button">{BaseElements.SEND_MESSAGE_ATTACH}</div>
<div
ref={contentEditableRef}
className={textInputClassName} contentEditable={contentEditableType}
data-placeholder={`Message #${channel.name}`}
onInput={onTextInput} onKeyDown={onTextKeyDown}
/>
</div>
</div>
);
}
export default SendMessage;

View File

@ -54,6 +54,7 @@
<div id="channel-content">
<div id="channel-feed-wrapper">
<div class="message-list-anchor"></div>
<div class="send-message-input-wrapper-anchor"></div>
<div id="channel-feed-input-wrapper">
<div id="channel-feed-input">
<div id="input-bar">

View File

@ -3,6 +3,48 @@
$scrollbarBottom: 4px;
$borderRadius: 8px;
.send-message-input-wrapper {
position: relative;
height: 0;
.send-message-input {
display: flex;
align-items: flex-start;
position: absolute;
left: 0;
bottom: 0;
margin: 0 16px 16px 16px;
box-sizing: border-box;
width: calc(100% - 32px);
color: $text-normal;
background-color: $channeltextarea-background;
border-radius: $borderRadius;
.resource-input-button {
cursor: pointer;
margin: 12px;
svg {
width: 24px;
height: 24px;
}
}
.text-input {
flex: 1;
padding: 12px 12px 12px 0;
max-height: 300px;
overflow-y: scroll;
overflow-wrap: anywhere;
white-space: pre;
&.disabled {
color: $text-sending;
}
}
}
}
#channel-content {
flex: 1;
display: flex;
@ -16,6 +58,7 @@ $borderRadius: 8px;
}
#channel-feed-input-wrapper {
display: none; // for testing
position: relative;
height: 0;
}

View File

@ -34,3 +34,7 @@ body {
cursor: text;
color: $text-muted;
}
[contenteditable=plaintext-only]:focus {
outline: none;
}