js/controllers/WriteCommentViewController.js
import ViewController from '~/controllers/ViewController';
import MarkdownTemplate from '~/template/MarkdownTemplate';
import FormConstraint from '~/controllers/Form/FormConstraint';
import LabelGroup from '~/template/Form/LabelGroup';
import ButtonTemplate, { ButtonColor } from '~/template/ButtonTemplate';
import * as MarkdownControls from '~/controllers/MarkdownControls';
import WriteComment from '~/models/Request/WriteComment';
import Analytics, { EventType } from '~/models/Analytics';
import ErrorManager from '~/helpers/ErrorManager';
import KeyManager from '~/models/KeyManager';
import Comment from '~/models/Comment';
import Answer from '~/models/Answer';
import Auth from '~/models/Auth';
import Post from '~/models/Post';
import Data from '~/models/Data';
export const CommentError = Symbol('WriteComment.Error.submit');
export const CommentOwnerTypeError = Symbol('WriteComment.Error.ownerType');
export const CommentLengthBounds = [
+Data.shared.envValueForKey('MIN_COMMENT_LENGTH'),
+Data.shared.envValueForKey('MAX_COMMENT_LENGTH')
];
/**
* Manages a "Write Comment" button
*/
export default class WriteCommentViewController extends ViewController {
/**
* Creates the write commemt button
* @param {HTMLElement} button The <a> init tag.
* @param {Post|Answer|Comment} owner The owner
* @param {CommentListViewController} parentList The list view which to place comment in.
*/
constructor(button, owner, parentList) {
super(button);
this._node = button;
/** @private */
this.commentText = new LabelGroup(
'Comment Body',
new MarkdownTemplate({
tooltip: 'Markdown supported.',
placeholder: 'Write comment...',
controls: [
new MarkdownControls.MarkdownBoldControl(),
new MarkdownControls.MarkdownItalicControl(),
new MarkdownControls.MarkdownStrikethroughControl()
],
hasShadow: false
}),
{
hideLabel: true,
liveConstraint: new FormConstraint()
.length(CommentLengthBounds[0], CommentLengthBounds[1])
}
);
this.commentText.padHorizontal = false;
this.cancel = new ButtonTemplate({
text: 'cancel',
color: ButtonColor.plain
});
this.submitButton = new ButtonTemplate({
text: 'Submit',
color: ButtonColor.blue
});
this._writingBox = (
<div class="comment-writer">
<h5>Write Comment</h5>
{ this.commentText.unique() }
<div class="comment-submit">
{ this.submitButton.unique() }
{ this.cancel.unique() }
</div>
</div>
);
this._keyBinding = null;
this._displayingWritingBox = false;
/** @type {Post|Answer|Comment} */
this.owner = owner;
/** @type {CommentListViewController} */
this.parentList = parentList;
this._node.addEventListener("click", ::this.toggleState);
this.submitButton.setIsDisabled(true, `No comment text entered`);
this.commentText.validationDelegate.didSetStateTo = (_, state) => {
this.submitButton.setIsDisabled(!state, `Comment is either too long or too short`);
};
this.cancel.delegate.didSetStateTo = (_, state) => { this.toggleState() };
this.submitButton.delegate.didSetStateTo = async (_, state) => {
await this.submit();
};
}
/**
* Submits this form
*/
async submit() {
const text = this.commentText.value;
if (text.length < CommentLengthBounds[0] || text.length > CommentLengthBounds[1]) {
Analytics.shared.report(EventType.commentTooShort);
// Display error message
return;
}
const type = this.owner instanceof Comment ? this.owner.type : this.owner.endpoint;
const sourceId = this.owner instanceof Comment ? this.owner.sourceId : this.owner.id;
const instance = this.parentList.createLoadingInstance("Posting comment...");
const parentComment = this.owner instanceof Comment ? this.owner.id : null;
Analytics.shared.report(EventType.commentWrite(this.owner));
try {
let commentPost = new WriteComment({
type: type,
id: sourceId,
value: text,
parentComment: parentComment
});
const comment = await commentPost.run();
// Reset the box
this.commentText.value = "";
this.toggleState();
await instance.destroy();
await this.parentList.createCommentInstance(comment);
} catch (error) {
// TODO: handle error
let errorMessage = {
[400]: `Internal error in comment layout`,
[401]: `You must be authorized to vote`,
[500]: `Internal server error.`,
}[error.response?.status] || `Unexpected error posting comment.`;
await instance.destroy();
this.parentList.createErrorInstance(errorMessage);
ErrorManager.silent(error, errorMessage);
}
}
_submitHandler = null;
/**
* Toggles between writing box and "add comment" dialogue.
*/
async toggleState() {
if (this._displayingWritingBox) {
// Hide writing box
Analytics.shared.report(EventType.commentWriteClose(this.owner));
this._writingBox.parentNode.replaceChild(this._node, this._writingBox);
this._displayingWritingBox = false;
// Remove Escape handler
this._keyBinding?.();
this._keyBinding = null;
// Removes the Ctrl+Enter handler
this._submitHandler?.();
} else {
// When we try to display box
const auth = Auth.shared;
if (!await auth.ensureLoggedIn()) return;
Analytics.shared.report(EventType.commentWriteOpen(this.owner));
this._node.parentNode.replaceChild(this._writingBox, this._node);
this._displayingWritingBox = true;
this.commentText.input.userInput?.focus();
this._keyBinding = KeyManager.shared.register('Escape', () => {
this.toggleState();
});
// Remove previous Ctrl+Enter handler.f
this._submitHandler?.();
this._submitHandler = KeyManager.shared.registerMeta('Enter', () => {
this.submitButton.trigger();
});
}
}
}