diff --git a/static/app-strings.json b/static/app-strings.json index 756121275..e014df315 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2319,5 +2319,7 @@ "Your hub has blocked this content because it subscribes to the following blocking channel:": "Your hub has blocked this content because it subscribes to the following blocking channel:", "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.": "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.", "Autoplay Next is on.": "Autoplay Next is on.", + "This will be visible in a few minutes after you submit this form.": "This will be visible in a few minutes after you submit this form.", + "Anon --[used in <%anonymous% Reposted>]--": "Anon", "--end--": "--end--" } diff --git a/ui/component/channelForm/view.jsx b/ui/component/channelForm/view.jsx index 084fbbe64..236d42ae5 100644 --- a/ui/component/channelForm/view.jsx +++ b/ui/component/channelForm/view.jsx @@ -3,7 +3,7 @@ import * as MODALS from 'constants/modal_types'; import * as ICONS from 'constants/icons'; import React from 'react'; import classnames from 'classnames'; -import { FormField } from 'component/common/form'; +import { FormField, FormFieldAreaAdvanced } from 'component/common/form'; import Button from 'component/button'; import TagsSearch from 'component/tagsSearch'; import ErrorText from 'component/common/error-text'; @@ -376,7 +376,7 @@ function ChannelForm(props: Props) { onChange={(e) => setParams({ ...params, title: e.target.value })} maxLength={MAX_TITLE_LEN} /> - - {isEditing ? (
- void, + advanced: boolean, +}; + +export default function CommentCreateHeader(props: Props) { + const { isReply, advancedHandler, advanced } = props; + + return ( +
+
+ {(isReply ? __('Replying as') : __('Comment as')) + ' '} + +
+
+
+
+ ); +} diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx index 8512fa3b1..afbeac4b6 100644 --- a/ui/component/commentCreate/view.jsx +++ b/ui/component/commentCreate/view.jsx @@ -4,7 +4,7 @@ import 'scss/component/_comment-create.scss'; import { buildValidSticker } from 'util/comments'; import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field'; -import { FormField, Form } from 'component/common/form'; +import { FormFieldAreaAdvanced, Form } from 'component/common/form'; import { getChannelIdFromClaim } from 'util/claim'; import { Lbryio } from 'lbryinc'; import { useHistory } from 'react-router'; @@ -22,8 +22,8 @@ import I18nMessage from 'component/i18nMessage'; import Icon from 'component/common/icon'; import OptimizedImage from 'component/optimizedImage'; import React from 'react'; -import SelectChannel from 'component/selectChannel'; import StickerSelector from './sticker-selector'; +import CommentCreateHeader from './comment-create-header'; import type { ElementRef } from 'react'; import UriIndicator from 'component/uriIndicator'; import usePersistedState from 'effects/use-persisted-state'; @@ -409,7 +409,11 @@ export function CommentCreate(props: Props) { push(pathPlusRedirect); }} > - +
@@ -420,22 +424,22 @@ export function CommentCreate(props: Props) { return ( {}} - className={classnames('commentCreate', { - 'commentCreate--reply': isReply, - 'commentCreate--nestedReply': isNested, - 'commentCreate--bottom': bottom, + className={classnames('comment-create', { + 'comment-create--reply': isReply, + 'comment-create--nestedReply': isNested, + 'comment-create--bottom': bottom, })} > {/* Input Box/Preview Box */} {stickerSelector ? ( handleSelectSticker(sticker)} claimIsMine={claimIsMine} /> ) : isReviewingStickerComment && activeChannelClaim && selectedSticker ? ( -
-
+
+
-
+
{/* figure out lbc sticker prices */} @@ -447,15 +451,15 @@ export function CommentCreate(props: Props) { )}
) : isReviewingSupportComment && activeChannelClaim ? ( -
+
-
+
{commentValue}
@@ -470,23 +474,22 @@ export function CommentCreate(props: Props) { /> )} - - {(isReply ? __('Replying as') : __('Comment as')) + ' '} - -
+ header={ + setAdvancedEditor(!advancedEditor)} + /> } name={isReply ? 'content_reply' : 'content_description'} - quickActionLabel={isReply ? undefined : advancedEditor ? __('Simple Editor') : __('Advanced Editor')} ref={formFieldRef} onChange={handleCommentChange} openEmoteMenu={() => setShowEmotes(!showEmotes)} - quickActionHandler={() => setAdvancedEditor(!advancedEditor)} onFocus={onTextareaFocus} onBlur={onTextareaBlur} placeholder={__('Say something about this...')} @@ -654,7 +657,7 @@ export function CommentCreate(props: Props) { {/* Help Text */} {deletedComment &&
{__('This comment has been deleted.')}
} {!!minAmount && ( -
+
}}> {minTip ? 'Comment min: %lbc%' : minSuper ? 'HyperChat min: %lbc%' : ''} diff --git a/ui/component/commentsList/view.jsx b/ui/component/commentsList/view.jsx index 8698c5b2f..aab41c2a6 100644 --- a/ui/component/commentsList/view.jsx +++ b/ui/component/commentsList/view.jsx @@ -383,7 +383,7 @@ const CommentActionButtons = (actionButtonsProps: ActionButtonsProps) => {
{allServers.length >= 2 && ( -
+
any, + openEmoteMenu?: () => void, + quickActionHandler?: (any) => any, + render?: () => React$Node, + header?: React$Node, +}; + +export class FormFieldAreaAdvanced extends React.PureComponent { + static defaultProps = { labelOnLeft: false, blockWrap: true }; + + input: { current: ElementRef }; + + constructor(props: Props) { + super(props); + this.input = React.createRef(); + } + + componentDidMount() { + const { autoFocus } = this.props; + const input = this.input.current; + + if (input && autoFocus) input.focus(); + } + + render() { + const { + autoFocus, + blockWrap, + charCount, + children, + helper, + hideSuggestions, + isLivestream, + label, + header, + labelOnLeft, + name, + noEmojis, + quickActionLabel, + textAreaMaxLength, + type, + openEmoteMenu, + quickActionHandler, + render, + ...inputProps + } = this.props; + + // 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 quickAction = + quickActionLabel && quickActionHandler ? ( +
+
+ ) : null; + + const input = () => { + switch (type) { + case 'markdown': + const handleEvents = { contextmenu: openEditorMenu }; + + 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.substring(0, str.length - delta); + changes.update(changes.from, changes.to, str.split('\n')); + } + } + }); + + // "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://)'; + + // 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 ( +
+ + {!header && ( +
+
+ +
+ {quickAction} +
+ )} + {!!header &&
{header}
} + ; + return ReactDOMServer.renderToString(preview); + }, + }} + /> + {countInfo} +
+
+ ); + case 'textarea': + return ( + + {!header && (label || quickAction) && ( +
+ + {quickAction} +
+ )} + {!!header &&
{header}
} + {hideSuggestions ? ( +