diff options
Diffstat (limited to 'ui/src/components/comment-form.tsx')
-rw-r--r-- | ui/src/components/comment-form.tsx | 265 |
1 files changed, 139 insertions, 126 deletions
diff --git a/ui/src/components/comment-form.tsx b/ui/src/components/comment-form.tsx index 61ee3d77..04720cbb 100644 --- a/ui/src/components/comment-form.tsx +++ b/ui/src/components/comment-form.tsx @@ -1,4 +1,5 @@ import { Component, linkEvent } from 'inferno'; +import { Link } from 'inferno-router'; import { Subscription } from 'rxjs'; import { retryWhen, delay, take } from 'rxjs/operators'; import { Prompt } from 'inferno-router'; @@ -17,7 +18,6 @@ import { toast, setupTribute, wsJsonToRes, - emojiPicker, pictrsDeleteToast, } from '../utils'; import { WebSocketService, UserService } from '../services'; @@ -25,6 +25,7 @@ import autosize from 'autosize'; import Tribute from 'tributejs/src/Tribute.js'; import emojiShortName from 'emoji-short-name'; import { i18n } from '../i18next'; +import { T } from 'inferno-i18next'; interface CommentFormProps { postId?: number; @@ -72,7 +73,6 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { super(props, context); this.tribute = setupTribute(); - this.setupEmojiPicker(); this.state = this.emptyState; @@ -98,18 +98,45 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { } componentDidMount() { - var textarea: any = document.getElementById(this.id); - autosize(textarea); - this.tribute.attach(textarea); - textarea.addEventListener('tribute-replaced', () => { - this.state.commentForm.content = textarea.value; - this.setState(this.state); - autosize.update(textarea); - }); + let textarea: any = document.getElementById(this.id); + if (textarea) { + autosize(textarea); + this.tribute.attach(textarea); + textarea.addEventListener('tribute-replaced', () => { + this.state.commentForm.content = textarea.value; + this.setState(this.state); + autosize.update(textarea); + }); + + // Quoting of selected text + let selectedText = window.getSelection().toString(); + if (selectedText) { + let quotedText = + selectedText + .split('\n') + .map(t => `> ${t}`) + .join('\n') + '\n\n'; + this.state.commentForm.content = quotedText; + this.setState(this.state); + // Not sure why this needs a delay + setTimeout(() => autosize.update(textarea), 10); + } + + textarea.focus(); + } + } + + componentDidUpdate() { + if (this.state.commentForm.content) { + window.onbeforeunload = () => true; + } else { + window.onbeforeunload = undefined; + } } componentWillUnmount() { this.subscription.unsubscribe(); + window.onbeforeunload = null; } render() { @@ -119,133 +146,123 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { when={this.state.commentForm.content} message={i18n.t('block_leaving')} /> - <form - id={this.formId} - onSubmit={linkEvent(this, this.handleCommentSubmit)} - > - <div class="form-group row"> - <div className={`col-sm-12`}> - <textarea - id={this.id} - className={`form-control ${this.state.previewMode && 'd-none'}`} - value={this.state.commentForm.content} - onInput={linkEvent(this, this.handleCommentContentChange)} - onPaste={linkEvent(this, this.handleImageUploadPaste)} - required - disabled={this.props.disabled} - rows={2} - maxLength={10000} - /> - {this.state.previewMode && ( - <div - className="card card-body md-div" - dangerouslySetInnerHTML={mdToHtml( - this.state.commentForm.content - )} + {UserService.Instance.user ? ( + <form + id={this.formId} + onSubmit={linkEvent(this, this.handleCommentSubmit)} + > + <div class="form-group row"> + <div className={`col-sm-12`}> + <textarea + id={this.id} + className={`form-control ${ + this.state.previewMode && 'd-none' + }`} + value={this.state.commentForm.content} + onInput={linkEvent(this, this.handleCommentContentChange)} + onPaste={linkEvent(this, this.handleImageUploadPaste)} + required + disabled={this.props.disabled} + rows={2} + maxLength={10000} /> - )} - </div> - </div> - <div class="row"> - <div class="col-sm-12"> - <button - type="submit" - class="btn btn-sm btn-secondary mr-2" - disabled={this.props.disabled || this.state.loading} - > - {this.state.loading ? ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> - ) : ( - <span>{this.state.buttonTitle}</span> + {this.state.previewMode && ( + <div + className="card card-body md-div" + dangerouslySetInnerHTML={mdToHtml( + this.state.commentForm.content + )} + /> )} - </button> - {this.state.commentForm.content && ( - <button - className={`btn btn-sm mr-2 btn-secondary ${ - this.state.previewMode && 'active' - }`} - onClick={linkEvent(this, this.handlePreviewToggle)} - > - {i18n.t('preview')} - </button> - )} - {this.props.node && ( + </div> + </div> + <div class="row"> + <div class="col-sm-12"> <button - type="button" + type="submit" class="btn btn-sm btn-secondary mr-2" - onClick={linkEvent(this, this.handleReplyCancel)} + disabled={this.props.disabled || this.state.loading} > - {i18n.t('cancel')} + {this.state.loading ? ( + <svg class="icon icon-spinner spin"> + <use xlinkHref="#icon-spinner"></use> + </svg> + ) : ( + <span>{this.state.buttonTitle}</span> + )} </button> - )} - <a - href={markdownHelpUrl} - target="_blank" - class="d-inline-block float-right text-muted font-weight-bold" - title={i18n.t('formatting_help')} - rel="noopener" - > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-help-circle"></use> - </svg> - </a> - <form class="d-inline-block mr-3 float-right text-muted font-weight-bold"> - <label - htmlFor={`file-upload-${this.id}`} - className={`${UserService.Instance.user && 'pointer'}`} - data-tippy-content={i18n.t('upload_image')} + {this.state.commentForm.content && ( + <button + className={`btn btn-sm mr-2 btn-secondary ${ + this.state.previewMode && 'active' + }`} + onClick={linkEvent(this, this.handlePreviewToggle)} + > + {i18n.t('preview')} + </button> + )} + {this.props.node && ( + <button + type="button" + class="btn btn-sm btn-secondary mr-2" + onClick={linkEvent(this, this.handleReplyCancel)} + > + {i18n.t('cancel')} + </button> + )} + <a + href={markdownHelpUrl} + target="_blank" + class="d-inline-block float-right text-muted font-weight-bold" + title={i18n.t('formatting_help')} + rel="noopener" > <svg class="icon icon-inline"> - <use xlinkHref="#icon-image"></use> + <use xlinkHref="#icon-help-circle"></use> </svg> - </label> - <input - id={`file-upload-${this.id}`} - type="file" - accept="image/*,video/*" - name="file" - class="d-none" - disabled={!UserService.Instance.user} - onChange={linkEvent(this, this.handleImageUpload)} - /> - </form> - {this.state.imageLoading && ( - <svg class="icon icon-spinner spin"> - <use xlinkHref="#icon-spinner"></use> - </svg> - )} - <span - onClick={linkEvent(this, this.handleEmojiPickerClick)} - class="pointer unselectable d-inline-block mr-3 float-right text-muted font-weight-bold" - data-tippy-content={i18n.t('emoji_picker')} - > - <svg class="icon icon-inline"> - <use xlinkHref="#icon-smile"></use> - </svg> - </span> + </a> + <form class="d-inline-block mr-3 float-right text-muted font-weight-bold"> + <label + htmlFor={`file-upload-${this.id}`} + className={`${UserService.Instance.user && 'pointer'}`} + data-tippy-content={i18n.t('upload_image')} + > + <svg class="icon icon-inline"> + <use xlinkHref="#icon-image"></use> + </svg> + </label> + <input + id={`file-upload-${this.id}`} + type="file" + accept="image/*,video/*" + name="file" + class="d-none" + disabled={!UserService.Instance.user} + onChange={linkEvent(this, this.handleImageUpload)} + /> + </form> + {this.state.imageLoading && ( + <svg class="icon icon-spinner spin"> + <use xlinkHref="#icon-spinner"></use> + </svg> + )} + </div> </div> + </form> + ) : ( + <div class="alert alert-warning" role="alert"> + <svg class="icon icon-inline mr-2"> + <use xlinkHref="#icon-alert-triangle"></use> + </svg> + <T i18nKey="must_login" class="d-inline"> + #<Link to="/login">#</Link> + </T> </div> - </form> + )} </div> ); } - setupEmojiPicker() { - emojiPicker.on('emoji', twemojiHtmlStr => { - if (this.state.commentForm.content == null) { - this.state.commentForm.content = ''; - } - var el = document.createElement('div'); - el.innerHTML = twemojiHtmlStr; - let nativeUnicode = (el.childNodes[0] as HTMLElement).getAttribute('alt'); - let shortName = `:${emojiShortName[nativeUnicode]}:`; - this.state.commentForm.content += shortName; - this.setState(this.state); - }); - } - handleFinished(op: UserOperation, data: CommentResponse) { let isReply = this.props.node !== undefined && data.comment.parent_id !== null; @@ -293,10 +310,6 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> { i.setState(i.state); } - handleEmojiPickerClick(_i: CommentForm, event: any) { - emojiPicker.togglePicker(event.target); - } - handleCommentContentChange(i: CommentForm, event: any) { i.state.commentForm.content = event.target.value; i.setState(i.state); |