summaryrefslogtreecommitdiffstats
path: root/ui/src/components/comment-form.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'ui/src/components/comment-form.tsx')
-rw-r--r--ui/src/components/comment-form.tsx265
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);