From d45bc32d06a6d21e3f849c7e9fafa4db7277642a Mon Sep 17 00:00:00 2001 From: saltrafael Date: Mon, 25 Oct 2021 15:24:02 -0300 Subject: [PATCH] Squashed commit of the following: commit 9112fea472d46e33381e19ef4ed5b2ac1b3cf4da Author: saltrafael Date: Mon Oct 25 15:03:59 2021 -0300 Add Emote Selector and Emote Comment creation ability commit cb8e7901ab2fd6a39e89a49f5575404596eee081 Author: saltrafael Date: Mon Oct 25 12:54:54 2021 -0300 Add new emote menu commit ba894b1d4306e3cae827ddab31e86f920f6ee737 Author: saltrafael Date: Mon Oct 25 12:26:11 2021 -0300 Refactor form-field --- ui/component/commentCreate/emote-selector.jsx | 64 +++ ui/component/commentCreate/view.jsx | 18 +- .../common/form-components/form-field.jsx | 393 ++++++++---------- ui/component/common/icon-custom.jsx | 8 + ui/component/common/markdown-preview.jsx | 11 + ui/component/optimizedImage/view.jsx | 6 +- ui/constants/emotes.js | 80 ++++ ui/constants/icons.js | 1 + ui/scss/component/_button.scss | 12 +- ui/scss/component/_comments.scss | 42 +- ui/scss/component/_form-field.scss | 8 - ui/util/remark-emote.js | 112 +++++ .../themes/odysee/component/_form-field.scss | 2 - 13 files changed, 509 insertions(+), 248 deletions(-) create mode 100644 ui/component/commentCreate/emote-selector.jsx create mode 100644 ui/constants/emotes.js create mode 100644 ui/util/remark-emote.js diff --git a/ui/component/commentCreate/emote-selector.jsx b/ui/component/commentCreate/emote-selector.jsx new file mode 100644 index 00000000000..3a4e4bd6853 --- /dev/null +++ b/ui/component/commentCreate/emote-selector.jsx @@ -0,0 +1,64 @@ +// @flow +import React from 'react'; +import emoji from 'emoji-dictionary'; +import Button from 'component/button'; +import OptimizedImage from 'component/optimizedImage'; +import * as EMOTES from 'constants/emotes'; + +const OLD_QUICK_EMOJIS = [ + emoji.getUnicode('rocket'), + emoji.getUnicode('jeans'), + emoji.getUnicode('fire'), + emoji.getUnicode('heart'), + emoji.getUnicode('open_mouth'), +]; + +type Props = { commentValue: string, setCommentValue: (string) => void }; + +export default function EmoteSelector(props: Props) { + const { commentValue, setCommentValue } = props; + + function addEmoteToComment(emote: string) { + setCommentValue( + commentValue + (commentValue && commentValue.charAt(commentValue.length - 1) !== ' ' ? ` ${emote} ` : `${emote} `) + ); + } + + return ( +
+
+
+
{__('Old')}
+
+ {OLD_QUICK_EMOJIS.map((emoji) => ( +
+
+ +
+
{__('Global')}
+
+ {Object.keys(EMOTES).map((emote) => ( + + ))} +
+
+
+
+ ); +} diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 046b2372b96..b6441e4e502 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -17,6 +17,7 @@ import FilePrice from 'component/filePrice'; import I18nMessage from 'component/i18nMessage'; import Icon from 'component/common/icon'; import OptimizedImage from 'component/optimizedImage'; +import EmoteSelector from './emote-selector'; import React from 'react'; import SelectChannel from 'component/selectChannel'; import type { ElementRef } from 'react'; @@ -119,6 +120,7 @@ export function CommentCreate(props: Props) { const [deletedComment, setDeletedComment] = React.useState(false); const [pauseQuickSend, setPauseQuickSend] = React.useState(false); const [disableReviewButton, setDisableReviewButton] = React.useState(); + const [showEmotes, setShowEmotes] = React.useState(false); const selectedMentionIndex = commentValue.indexOf('@', selectionIndex) === selectionIndex @@ -208,12 +210,6 @@ export function CommentCreate(props: Props) { window.removeEventListener('keydown', altEnterListener); } - function handleSubmit() { - if (activeChannelClaim && commentValue.length) { - handleCreateComment(); - } - } - function handleSupportComment() { if (!activeChannelClaim) return; @@ -421,7 +417,6 @@ export function CommentCreate(props: Props) { return (
) : ( <> + {showEmotes && } + {!advancedEditor && ( !SIMPLE_SITE && setAdvancedEditor(!advancedEditor)} + openEmoteMenu={() => setShowEmotes(!showEmotes)} onFocus={onTextareaFocus} onBlur={onTextareaBlur} placeholder={__('Say something about this...')} @@ -571,6 +569,12 @@ export function CommentCreate(props: Props) { : __('Comment --[button to submit something]--') } requiresAuth + onClick={() => { + if (activeChannelClaim && commentValue.length) { + handleCreateComment(); + setShowEmotes(false); + } + }} /> ) )} diff --git a/ui/component/common/form-components/form-field.jsx b/ui/component/common/form-components/form-field.jsx index 83e01f85d06..57ede1dcefa 100644 --- a/ui/component/common/form-components/form-field.jsx +++ b/ui/component/common/form-components/form-field.jsx @@ -1,33 +1,23 @@ // @flow -import type { ElementRef, Node } from 'react'; +import 'easymde/dist/easymde.min.css'; +import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field'; +import { openEditorMenu, stopContextMenu } from 'util/context-menu'; +import * as ICONS from 'constants/icons'; +import Button from 'component/button'; +import MarkdownPreview from 'component/common/markdown-preview'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import SimpleMDE from 'react-simplemde-editor'; -import MarkdownPreview from 'component/common/markdown-preview'; -import { openEditorMenu, stopContextMenu } from 'util/context-menu'; -import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field'; -import 'easymde/dist/easymde.min.css'; -import Button from 'component/button'; -import emoji from 'emoji-dictionary'; - -const QUICK_EMOJIS = [ - emoji.getUnicode('rocket'), - emoji.getUnicode('jeans'), - emoji.getUnicode('fire'), - emoji.getUnicode('heart'), - emoji.getUnicode('open_mouth'), -]; +import type { ElementRef, Node } from 'react'; type Props = { name: string, label?: string | Node, - render?: () => React$Node, prefix?: string, postfix?: string, error?: string | boolean, helper?: string | React$Node, type?: string, - onChange?: (any) => any, defaultValue?: string | number, placeholder?: string | number, children?: React$Node, @@ -43,18 +33,17 @@ type Props = { min?: number, max?: number, quickActionLabel?: string, - quickActionHandler?: (any) => any, disabled?: boolean, - onChange: (any) => void, value?: string | number, noEmojis?: boolean, + render?: () => React$Node, + onChange?: (any) => any, + quickActionHandler?: (any) => any, + openEmoteMenu?: () => void, }; export class FormField extends React.PureComponent { - static defaultProps = { - labelOnLeft: false, - blockWrap: true, - }; + static defaultProps = { labelOnLeft: false, blockWrap: true }; input: { current: ElementRef }; @@ -67,14 +56,11 @@ export class FormField extends React.PureComponent { const { autoFocus } = this.props; const input = this.input.current; - if (input && autoFocus) { - input.focus(); - } + if (input && autoFocus) input.focus(); } render() { const { - render, label, prefix, postfix, @@ -92,11 +78,24 @@ export class FormField extends React.PureComponent { charCount, textAreaMaxLength, quickActionLabel, - quickActionHandler, noEmojis, + render, + quickActionHandler, + openEmoteMenu, ...inputProps } = this.props; + const errorMessage = typeof error === 'object' ? error.message : error; + + // Ideally, the character count should (and can) be appended to the + // SimpleMDE's "options::status" bar. However, I couldn't figure out how + // to pass the current value to it's callback, nor query the current + // text length from the callback. So, we'll use our own widget. + const hasCharCount = charCount !== undefined && charCount >= 0; + const countInfo = hasCharCount && textAreaMaxLength !== undefined && ( + {`${charCount || '0'}/${textAreaMaxLength}`} + ); + const Wrapper = blockWrap ? ({ children: innerChildren }) => {innerChildren} : ({ children: innerChildren }) => {innerChildren}; @@ -108,207 +107,163 @@ export class FormField extends React.PureComponent { ) : null; - let input; - if (type) { - if (type === 'radio') { - input = ( - - - - - ); - } else if (type === 'checkbox') { - input = ( -
- - -
- ); - } else if (type === 'range') { - input = ( -
- - -
- ); - } else if (type === 'select') { - input = ( - - {(label || errorMessage) && ( - - )} - - - ); - } else if (type === 'select-tiny') { - input = ( - - {(label || errorMessage) && ( - - )} - - - ); - } else if (type === 'markdown') { - const handleEvents = { - contextmenu: openEditorMenu, - }; + const inputSimple = (type: string) => ( + + + + + ); - const getInstance = (editor) => { - // SimpleMDE max char check - editor.codemirror.on('beforeChange', (instance, changes) => { - if (textAreaMaxLength && changes.update) { - var str = changes.text.join('\n'); - var delta = str.length - (instance.indexFromPos(changes.to) - instance.indexFromPos(changes.from)); - if (delta <= 0) { - return; - } - delta = instance.getValue().length + delta - textAreaMaxLength; - if (delta > 0) { - str = str.substr(0, str.length - delta); - changes.update(changes.from, changes.to, str.split('\n')); - } - } - }); + const inputSelect = (selectClass: string) => ( + + {(label || errorMessage) && ( + + )} + + + ); - // "Create Link (Ctrl-K)": highlight URL instead of label: - editor.codemirror.on('changes', (instance, changes) => { - try { - // Grab the last change from the buffered list. I assume the - // buffered one ('changes', instead of 'change') is more efficient, - // and that "Create Link" will always end up last in the list. - const lastChange = changes[changes.length - 1]; - if (lastChange.origin === '+input') { - // https://github.com/Ionaru/easy-markdown-editor/blob/8fa54c496f98621d5f45f57577ce630bee8c41ee/src/js/easymde.js#L765 - const EASYMDE_URL_PLACEHOLDER = '(https://)'; + const input = () => { + switch (type) { + case 'radio': + return inputSimple('radio'); + case 'checkbox': + return inputSimple('checkbox'); + case 'range': + return inputSimple('range'); + case 'select': + return inputSelect(''); + case 'select-tiny': + return inputSelect('select--slim'); + case 'markdown': + const handleEvents = { contextmenu: openEditorMenu }; - // The URL placeholder is always placed last, so just look at the - // last text in the array to also cover the multi-line case: - const urlLineText = lastChange.text[lastChange.text.length - 1]; + const getInstance = (editor) => { + // SimpleMDE max char check + editor.codemirror.on('beforeChange', (instance, changes) => { + if (textAreaMaxLength && changes.update) { + var str = changes.text.join('\n'); + var delta = str.length - (instance.indexFromPos(changes.to) - instance.indexFromPos(changes.from)); - if (urlLineText.endsWith(EASYMDE_URL_PLACEHOLDER) && urlLineText !== '[]' + EASYMDE_URL_PLACEHOLDER) { - const from = lastChange.from; - const to = lastChange.to; - const isSelectionMultiline = lastChange.text.length > 1; - const baseIndex = isSelectionMultiline ? 0 : from.ch; + if (delta <= 0) return; - // Everything works fine for the [Ctrl-K] case, but for the - // [Button] case, this handler happens before the original - // code, thus our change got wiped out. - // Add a small delay to handle that case. - setTimeout(() => { - instance.setSelection( - { line: to.line, ch: baseIndex + urlLineText.lastIndexOf('(') + 1 }, - { line: to.line, ch: baseIndex + urlLineText.lastIndexOf(')') } - ); - }, 25); + delta = instance.getValue().length + delta - textAreaMaxLength; + if (delta > 0) { + str = str.substr(0, str.length - delta); + changes.update(changes.from, changes.to, str.split('\n')); } } - } catch (err) { - // Do nothing (revert to original behavior) - } - }); - }; + }); - // Ideally, the character count should (and can) be appended to the - // SimpleMDE's "options::status" bar. However, I couldn't figure out how - // to pass the current value to it's callback, nor query the current - // text length from the callback. So, we'll use our own widget. - const hasCharCount = charCount !== undefined && charCount >= 0; - const countInfo = hasCharCount && textAreaMaxLength !== undefined && ( - {`${charCount || '0'}/${textAreaMaxLength}`} - ); + // "Create Link (Ctrl-K)": highlight URL instead of label: + editor.codemirror.on('changes', (instance, changes) => { + try { + // Grab the last change from the buffered list. I assume the + // buffered one ('changes', instead of 'change') is more efficient, + // and that "Create Link" will always end up last in the list. + const lastChange = changes[changes.length - 1]; + if (lastChange.origin === '+input') { + // https://github.com/Ionaru/easy-markdown-editor/blob/8fa54c496f98621d5f45f57577ce630bee8c41ee/src/js/easymde.js#L765 + const EASYMDE_URL_PLACEHOLDER = '(https://)'; - input = ( -
+ // The URL placeholder is always placed last, so just look at the + // last text in the array to also cover the multi-line case: + const urlLineText = lastChange.text[lastChange.text.length - 1]; + + if (urlLineText.endsWith(EASYMDE_URL_PLACEHOLDER) && urlLineText !== '[]' + EASYMDE_URL_PLACEHOLDER) { + const from = lastChange.from; + const to = lastChange.to; + const isSelectionMultiline = lastChange.text.length > 1; + const baseIndex = isSelectionMultiline ? 0 : from.ch; + + // Everything works fine for the [Ctrl-K] case, but for the + // [Button] case, this handler happens before the original + // code, thus our change got wiped out. + // Add a small delay to handle that case. + setTimeout(() => { + instance.setSelection( + { line: to.line, ch: baseIndex + urlLineText.lastIndexOf('(') + 1 }, + { line: to.line, ch: baseIndex + urlLineText.lastIndexOf(')') } + ); + }, 25); + } + } + } catch (e) {} // Do nothing (revert to original behavior) + }); + }; + + return ( +
+ +
+
+ +
+ {quickAction} +
+ ; + return ReactDOMServer.renderToString(preview); + }, + }} + /> + {countInfo} +
+
+ ); + case 'textarea': + return ( -
-
+ {(label || quickAction) && ( +
+ {quickAction}
- {quickAction} -
- ; - return ReactDOMServer.renderToString(preview); - }, - }} + maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT} + ref={this.input} + {...inputProps} /> - {countInfo} - -
- ); - } else if (type === 'textarea') { - const hasCharCount = charCount !== undefined && charCount >= 0; - const countInfo = hasCharCount && textAreaMaxLength !== undefined && ( - {`${charCount || '0'}/${textAreaMaxLength}`} - ); - input = ( - - {(label || quickAction) && ( -
-
- -
- {quickAction} +
+ {!noEmojis && ( +
- )} -