diff --git a/web/components/chat/ChatTextField/ChatTextField.module.scss b/web/components/chat/ChatTextField/ChatTextField.module.scss
index 45ee52886..9e8e94c62 100644
--- a/web/components/chat/ChatTextField/ChatTextField.module.scss
+++ b/web/components/chat/ChatTextField/ChatTextField.module.scss
@@ -25,22 +25,30 @@
background-color: var(--theme-color-components-form-field-background);
box-shadow: inset 0px 0px 2px 2px var(--theme-color-palette-3);
}
+
+ // Size of custom emoji.
+ img {
+ width: 20px;
+ height: 20px;
+ }
}
.maxCharacters {
border-style: solid;
- border-width: 1px;
+ border-width: 2px;
border-color: red;
}
div[role='textbox'] {
font-size: 13px;
- font-weight: 400;
+ font-weight: 400;
padding: 0.3rem;
background-color: inherit;
border-color: var(--theme-color-components-form-field-border);
box-shadow: 0;
transition: box-shadow 50ms ease-in-out;
+ max-height: 40px; // 2 lines of text
+ min-height: 30px;
&:focus {
outline: 1px solid var(--color-owncast-gray-500) !important;
}
@@ -48,13 +56,20 @@
margin: 0px;
}
}
+
+ // Placeholder styling
+ :empty:before {
+ content: attr(placeholder);
+ display: block;
+ color: #aaa;
+ }
}
.emojiButton {
border: none;
background: none;
cursor: pointer;
- padding: 0 .25rem;
+ padding: 0 0.25rem;
}
.sendButton {
diff --git a/web/components/chat/ChatTextField/ChatTextField.stories.tsx b/web/components/chat/ChatTextField/ChatTextField.stories.tsx
index b89801b2c..5f1c7aea7 100644
--- a/web/components/chat/ChatTextField/ChatTextField.stories.tsx
+++ b/web/components/chat/ChatTextField/ChatTextField.stories.tsx
@@ -44,8 +44,7 @@ export default {
component: `
- This is a element using \`contentEditable\` in order to support rendering emoji images inline.
- Emoji button shows emoji picker.
-- Should show one line by default, but grow to two lines as needed.
-- The Send button should be hidden for desktop layouts and be shown for mobile layouts.`,
+- Should show one line by default, but grow to two lines as needed.`,
},
},
},
diff --git a/web/components/chat/ChatTextField/ChatTextField.tsx b/web/components/chat/ChatTextField/ChatTextField.tsx
index ae78a7821..29725d819 100644
--- a/web/components/chat/ChatTextField/ChatTextField.tsx
+++ b/web/components/chat/ChatTextField/ChatTextField.tsx
@@ -1,16 +1,7 @@
import { Popover } from 'antd';
-import React, { FC, useMemo, useState } from 'react';
+import React, { FC, useReducer, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
-import { Transforms, createEditor, BaseEditor, Text, Descendant, Editor } from 'slate';
-import {
- Slate,
- DefaultPlaceholder,
- Editable,
- withReact,
- ReactEditor,
- useSelected,
- useFocused,
-} from 'slate-react';
+import ContentEditable from 'react-contenteditable';
import dynamic from 'next/dynamic';
import classNames from 'classnames';
import WebsocketService from '../../../services/websocket-service';
@@ -32,102 +23,6 @@ const SmileOutlined = dynamic(() => import('@ant-design/icons/SmileOutlined'), {
ssr: false,
});
-type CustomElement = { type: 'paragraph' | 'span'; children: CustomText[] } | ImageNode;
-type CustomText = { text: string };
-
-type EmptyText = {
- text: string;
-};
-
-type ImageNode = {
- type: 'image';
- alt: string;
- src: string;
- name: string;
- children: EmptyText[];
-};
-
-declare module 'slate' {
- interface CustomTypes {
- Editor: BaseEditor & ReactEditor;
- Element: CustomElement;
- Text: CustomText;
- }
-}
-
-const Image = p => {
- const { attributes, element, children } = p;
-
- const selected = useSelected();
- const focused = useFocused();
- return (
-
-
- {children}
-
- );
-};
-
-const withImages = editor => {
- const { isVoid } = editor;
-
- // eslint-disable-next-line no-param-reassign
- editor.isVoid = element => (element.type === 'image' ? true : isVoid(element));
- // eslint-disable-next-line no-param-reassign
- editor.isInline = element => element.type === 'image';
-
- return editor;
-};
-
-const serialize = node => {
- if (Text.isText(node)) {
- const string = node.text;
- return string;
- }
-
- let children;
- if (node.children.length === 0) {
- children = [{ text: '' }];
- } else {
- children = node.children?.map(n => serialize(n)).join('');
- }
-
- switch (node.type) {
- case 'paragraph':
- return `
${children}
`; - case 'image': - return `` (from IE). + value = value.replace(/
/gi, '\n');
+
+ // Cleanup the emoji titles.
+ value = value.replace(/\u200C{2}/gi, '');
+
+ // Trim each line.
+ value = value
+ .split('\n')
+ .map((line = '') => line.trim())
+ .join('\n');
+
+ // No more than 2x newline, per "paragraph".
+ value = value.replace(/\n\n+/g, '\n\n');
+
+ // Clean up spaces.
+ value = value.replace(/[ ]+/g, ' ');
+ value = value.trim();
+
+ // Expose string.
+ return value;
+}
+
export const ChatTextField: FC tags.
message = message.replace(/^ |<\/p>$/g, '');
websocketService.send({ type: MessageType.CHAT, body: message });
- // Clear the editor.
- Transforms.delete(editor, {
- at: {
- anchor: Editor.start(editor, []),
- focus: Editor.end(editor, []),
- },
- });
+ // Clear the input.
+ text.current = '';
setCharacterCount(0);
+ forceUpdate();
};
- const createImageNode = (alt, src, name): ImageNode => ({
- type: 'image',
- alt,
- src,
- name,
- children: [{ text: '' }],
- });
+ const insertTextAtCursor = (textToInsert: string) => {
+ const output = [
+ text.current.slice(0, savedCursorLocation),
+ textToInsert,
+ text.current.slice(savedCursorLocation),
+ ].join('');
+ text.current = output;
+ forceUpdate();
+ };
- const insertImage = (url, name) => {
- if (!url) return;
+ const convertOnPaste = (event: React.ClipboardEvent) => {
+ // Prevent paste.
+ event.preventDefault();
- const image = createImageNode(name, url, name);
+ // Set later.
+ let value = '';
- Transforms.insertNodes(editor, image);
- Editor.normalize(editor, { force: true });
+ // Does method exist?
+ const hasEventClipboard = !!(
+ event.clipboardData &&
+ typeof event.clipboardData === 'object' &&
+ typeof event.clipboardData.getData === 'function'
+ );
+
+ // Get clipboard data?
+ if (hasEventClipboard) {
+ value = event.clipboardData.getData('text/plain');
+ }
+
+ // Insert into temp `