aboutsummaryrefslogtreecommitdiff
path: root/client/js
diff options
context:
space:
mode:
authorrr-2016-12-24 21:49:39 +0100
committerrr-2016-12-25 21:49:39 +0100
commitfe0ba63f191b56853c8b57d9538a39c57ad91165 (patch)
tree085ea88c7280624049cd552b7d1a0b665b00f6bf /client/js
parentf0573be715ca3d7aab66a055d0eb7d4555ce2549 (diff)
client/comments: rework comments appearance and UX
Diffstat (limited to 'client/js')
-rw-r--r--client/js/controllers/post_detail_controller.js1
-rw-r--r--client/js/controllers/post_main_controller.js16
-rw-r--r--client/js/controls/comment_control.js180
-rw-r--r--client/js/controls/comment_form_control.js141
-rw-r--r--client/js/controls/comment_list_control.js2
-rw-r--r--client/js/models/comment.js2
-rw-r--r--client/js/views/post_main_view.js7
7 files changed, 173 insertions, 176 deletions
diff --git a/client/js/controllers/post_detail_controller.js b/client/js/controllers/post_detail_controller.js
index cdff681..8b64aee 100644
--- a/client/js/controllers/post_detail_controller.js
+++ b/client/js/controllers/post_detail_controller.js
@@ -4,7 +4,6 @@ const router = require('../router.js');
const api = require('../api.js');
const misc = require('../util/misc.js');
const settings = require('../models/settings.js');
-const Comment = require('../models/comment.js');
const Post = require('../models/post.js');
const PostList = require('../models/post_list.js');
const PostDetailView = require('../views/post_detail_view.js');
diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js
index 561ecb4..fb280e4 100644
--- a/client/js/controllers/post_main_controller.js
+++ b/client/js/controllers/post_main_controller.js
@@ -69,10 +69,10 @@ class PostMainController extends BasePostController {
'merge', e => this._evtMergePost(e));
}
- if (this._view.commentFormControl) {
- this._view.commentFormControl.addEventListener(
+ if (this._view.commentControl) {
+ this._view.commentControl.addEventListener(
'change', e => this._evtCommentChange(e));
- this._view.commentFormControl.addEventListener(
+ this._view.commentControl.addEventListener(
'submit', e => this._evtCreateComment(e));
}
@@ -183,18 +183,18 @@ class PostMainController extends BasePostController {
}
_evtCreateComment(e) {
- // TODO: disable form
+ this._view.commentControl.disableForm();
const comment = Comment.create(this._post.id);
comment.text = e.detail.text;
comment.save()
.then(() => {
this._post.comments.add(comment);
- this._view.commentFormControl.setText('');
- // TODO: enable form
+ this._view.commentControl.exitEditMode();
+ this._view.commentControl.enableForm();
misc.disableExitConfirmation();
}, errorMessage => {
- this._view.commentFormControl.showError(errorMessage);
- // TODO: enable form
+ this._view.commentControl.showError(errorMessage);
+ this._view.commentControl.enableForm();
});
}
diff --git a/client/js/controls/comment_control.js b/client/js/controls/comment_control.js
index 63216e7..849dd4d 100644
--- a/client/js/controls/comment_control.js
+++ b/client/js/controls/comment_control.js
@@ -1,55 +1,87 @@
'use strict';
const api = require('../api.js');
+const misc = require('../util/misc.js');
const events = require('../events.js');
const views = require('../util/views.js');
-const CommentFormControl = require('../controls/comment_form_control.js');
const template = views.getTemplate('comment');
const scoreTemplate = views.getTemplate('score');
class CommentControl extends events.EventTarget {
- constructor(hostNode, comment) {
+ constructor(hostNode, comment, onlyEditing) {
super();
this._hostNode = hostNode;
this._comment = comment;
+ this._onlyEditing = onlyEditing;
- comment.addEventListener('change', e => this._evtChange(e));
- comment.addEventListener('changeScore', e => this._evtChangeScore(e));
+ if (comment) {
+ comment.addEventListener(
+ 'change', e => this._evtChange(e));
+ comment.addEventListener(
+ 'changeScore', e => this._evtChangeScore(e));
+ }
- const isLoggedIn = api.isLoggedIn(this._comment.user);
+ const isLoggedIn = comment && api.isLoggedIn(comment.user);
const infix = isLoggedIn ? 'own' : 'any';
views.replaceContent(this._hostNode, template({
- comment: this._comment,
+ comment: comment,
+ user: comment ? comment.user : api.user,
canViewUsers: api.hasPrivilege('users:view'),
canEditComment: api.hasPrivilege(`comments:edit:${infix}`),
canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`),
+ onlyEditing: onlyEditing,
}));
- if (this._editButtonNode) {
- this._editButtonNode.addEventListener(
- 'click', e => this._evtEditClick(e));
+ if (this._editButtonNodes) {
+ for (let node of this._editButtonNodes) {
+ node.addEventListener('click', e => this._evtEditClick(e));
+ }
}
if (this._deleteButtonNode) {
this._deleteButtonNode.addEventListener(
'click', e => this._evtDeleteClick(e));
}
- this._formControl = new CommentFormControl(
- this._hostNode.querySelector('.comment-form-container'),
- this._comment,
- true);
- events.proxyEvent(this._formControl, this, 'submit');
+ if (this._previewEditingButtonNode) {
+ this._previewEditingButtonNode.addEventListener(
+ 'click', e => this._evtPreviewEditingClick(e));
+ }
+
+ if (this._saveChangesButtonNode) {
+ this._saveChangesButtonNode.addEventListener(
+ 'click', e => this._evtSaveChangesClick(e));
+ }
+
+ if (this._cancelEditingButtonNode) {
+ this._cancelEditingButtonNode.addEventListener(
+ 'click', e => this._evtCancelEditingClick(e));
+ }
this._installScore();
+ if (onlyEditing) {
+ this._selectNav('edit');
+ this._selectTab('edit');
+ } else {
+ this._selectNav('readonly');
+ this._selectTab('preview');
+ }
+ }
+
+ get _formNode() {
+ return this._hostNode.querySelector('form');
}
get _scoreContainerNode() {
return this._hostNode.querySelector('.score-container');
}
- get _editButtonNode() {
- return this._hostNode.querySelector('.edit');
+ get _editButtonNodes() {
+ return this._hostNode.querySelectorAll('li.edit>a, a.edit');
+ }
+
+ get _previewEditingButtonNode() {
+ return this._hostNode.querySelector('li.preview>a');
}
get _deleteButtonNode() {
@@ -64,12 +96,32 @@ class CommentControl extends events.EventTarget {
return this._hostNode.querySelector('.downvote');
}
+ get _saveChangesButtonNode() {
+ return this._hostNode.querySelector('.save-changes');
+ }
+
+ get _cancelEditingButtonNode() {
+ return this._hostNode.querySelector('.cancel-editing');
+ }
+
+ get _textareaNode() {
+ return this._hostNode.querySelector('.tab.edit textarea');
+ }
+
+ get _contentNode() {
+ return this._hostNode.querySelector('.tab.preview .comment-content');
+ }
+
+ get _heightKeeperNode() {
+ return this._hostNode.querySelector('.keep-height');
+ }
+
_installScore() {
views.replaceContent(
this._scoreContainerNode,
scoreTemplate({
- score: this._comment.score,
- ownScore: this._comment.ownScore,
+ score: this._comment ? this._comment.score : 0,
+ ownScore: this._comment ? this._comment.ownScore : 0,
canScore: api.hasPrivilege('comments:score'),
}));
@@ -83,9 +135,40 @@ class CommentControl extends events.EventTarget {
}
}
+ enterEditMode() {
+ this._selectNav('edit');
+ this._selectTab('edit');
+ }
+
+ exitEditMode() {
+ if (this._onlyEditing) {
+ this._selectNav('edit');
+ this._selectTab('edit');
+ this._setText('');
+ } else {
+ this._selectNav('readonly');
+ this._selectTab('preview');
+ this._setText(this._comment.text);
+ }
+ this._forgetHeight();
+ views.clearMessages(this._hostNode);
+ }
+
+ enableForm() {
+ views.enableForm(this._formNode);
+ }
+
+ disableForm() {
+ views.disableForm(this._formNode);
+ }
+
+ showError(message) {
+ views.showError(this._hostNode, message);
+ }
+
_evtEditClick(e) {
e.preventDefault();
- this._formControl.enterEditMode();
+ this.enterEditMode();
}
_evtScoreClick(e, score) {
@@ -114,12 +197,69 @@ class CommentControl extends events.EventTarget {
}
_evtChange(e) {
- this._formControl.exitEditMode();
+ this.exitEditMode();
}
_evtChangeScore(e) {
this._installScore();
}
+
+ _evtPreviewEditingClick(e) {
+ e.preventDefault();
+ this._contentNode.innerHTML =
+ misc.formatMarkdown(this._textareaNode.value);
+ this._selectTab('edit');
+ this._selectTab('preview');
+ }
+
+ _evtEditClick(e) {
+ e.preventDefault();
+ this.enterEditMode();
+ }
+
+ _evtSaveChangesClick(e) {
+ e.preventDefault();
+ this.dispatchEvent(new CustomEvent('submit', {
+ detail: {
+ target: this,
+ comment: this._comment,
+ text: this._textareaNode.value,
+ },
+ }));
+ }
+
+ _evtCancelEditingClick(e) {
+ e.preventDefault();
+ this.exitEditMode();
+ }
+
+ _setText(text) {
+ this._textareaNode.value = text;
+ this._contentNode.innerHTML = misc.formatMarkdown(text);
+ }
+
+ _selectNav(modeName) {
+ for (let node of this._hostNode.querySelectorAll('nav')) {
+ node.classList.toggle('active', node.classList.contains(modeName));
+ }
+ }
+
+ _selectTab(tabName) {
+ this._ensureHeight();
+
+ for (let node of this._hostNode.querySelectorAll('.tab, .tabs li')) {
+ node.classList.toggle('active', node.classList.contains(tabName));
+ }
+ }
+
+ _ensureHeight() {
+ this._heightKeeperNode.style.minHeight =
+ this._heightKeeperNode.getBoundingClientRect().height + 'px';
+ }
+
+ _forgetHeight() {
+ this._heightKeeperNode.style.minHeight = null;
+ }
};
module.exports = CommentControl;
diff --git a/client/js/controls/comment_form_control.js b/client/js/controls/comment_form_control.js
deleted file mode 100644
index 8a611c5..0000000
--- a/client/js/controls/comment_form_control.js
+++ /dev/null
@@ -1,141 +0,0 @@
-'use strict';
-
-const events = require('../events.js');
-const misc = require('../util/misc.js');
-const views = require('../util/views.js');
-
-const template = views.getTemplate('comment-form');
-
-class CommentFormControl extends events.EventTarget {
- constructor(hostNode, comment, canCancel, minHeight) {
- super();
- this._hostNode = hostNode;
- this._comment = comment || {text: ''};
- this._canCancel = canCancel;
- this._minHeight = minHeight || 150;
-
- const sourceNode = template({
- comment: this._comment,
- });
-
- const previewTabButton = sourceNode.querySelector('.buttons .preview');
- const editTabButton = sourceNode.querySelector('.buttons .edit');
- const formNode = sourceNode.querySelector('form');
- const cancelButton = sourceNode.querySelector('.cancel');
- const textareaNode = sourceNode.querySelector('form textarea');
-
- previewTabButton.addEventListener(
- 'click', e => this._evtPreviewClick(e));
- editTabButton.addEventListener(
- 'click', e => this._evtEditClick(e));
-
- formNode.addEventListener('submit', e => this._evtSaveClick(e));
-
- if (this._canCancel) {
- cancelButton
- .addEventListener('click', e => this._evtCancelClick(e));
- } else {
- cancelButton.style.display = 'none';
- }
-
- for (let event of ['cut', 'paste', 'drop', 'keydown']) {
- textareaNode.addEventListener(event, e => {
- window.setTimeout(() => this._growTextArea(), 0);
- });
- }
- textareaNode.addEventListener('change', e => {
- this.dispatchEvent(new CustomEvent('change', {
- detail: {
- target: this,
- },
- }));
- this._growTextArea();
- });
-
- views.replaceContent(this._hostNode, sourceNode);
- }
-
- enterEditMode() {
- this._freezeTabHeights();
- this._hostNode.classList.add('editing');
- this._selectTab('edit');
- this._growTextArea();
- }
-
- exitEditMode() {
- this._hostNode.classList.remove('editing');
- this._hostNode.querySelector('.tab-wrapper').style.minHeight = null;
- views.clearMessages(this._hostNode);
- this.setText(this._comment.text);
- }
-
- get _textareaNode() {
- return this._hostNode.querySelector('.edit.tab textarea');
- }
-
- get _contentNode() {
- return this._hostNode.querySelector('.preview.tab .comment-content');
- }
-
- setText(text) {
- this._textareaNode.value = text;
- this._contentNode.innerHTML = misc.formatMarkdown(text);
- }
-
- showError(message) {
- views.showError(this._hostNode, message);
- }
-
- _evtPreviewClick(e) {
- e.preventDefault();
- this._contentNode.innerHTML =
- misc.formatMarkdown(this._textareaNode.value);
- this._freezeTabHeights();
- this._selectTab('preview');
- }
-
- _evtEditClick(e) {
- e.preventDefault();
- this.enterEditMode();
- }
-
- _evtSaveClick(e) {
- e.preventDefault();
- this.dispatchEvent(new CustomEvent('submit', {
- detail: {
- target: this,
- comment: this._comment,
- text: this._textareaNode.value,
- },
- }));
- }
-
- _evtCancelClick(e) {
- e.preventDefault();
- this.exitEditMode();
- }
-
- _selectTab(tabName) {
- this._freezeTabHeights();
- const tabWrapperNode = this._hostNode.querySelector('.tab-wrapper');
- tabWrapperNode.setAttribute('data-tab', tabName);
- for (let tab of this._hostNode.querySelectorAll('.tab, .buttons li')) {
- tab.classList.toggle('active', tab.classList.contains(tabName));
- }
- }
-
- _freezeTabHeights() {
- const tabsNode = this._hostNode.querySelector('.tab-wrapper');
- const tabsHeight = tabsNode.getBoundingClientRect().height;
- tabsNode.style.minHeight = tabsHeight + 'px';
- }
-
- _growTextArea() {
- this._textareaNode.style.height =
- Math.max(
- this._minHeight || 0,
- this._textareaNode.scrollHeight) + 'px';
- }
-};
-
-module.exports = CommentFormControl;
diff --git a/client/js/controls/comment_list_control.js b/client/js/controls/comment_list_control.js
index e09fffc..ddef71f 100644
--- a/client/js/controls/comment_list_control.js
+++ b/client/js/controls/comment_list_control.js
@@ -34,7 +34,7 @@ class CommentListControl extends events.EventTarget {
_installCommentNode(comment) {
const commentListItemNode = document.createElement('li');
const commentControl = new CommentControl(
- commentListItemNode, comment);
+ commentListItemNode, comment, false);
events.proxyEvent(commentControl, this, 'submit');
events.proxyEvent(commentControl, this, 'score');
events.proxyEvent(commentControl, this, 'delete');
diff --git a/client/js/models/comment.js b/client/js/models/comment.js
index 2ddc334..6407597 100644
--- a/client/js/models/comment.js
+++ b/client/js/models/comment.js
@@ -23,7 +23,7 @@ class Comment extends events.EventTarget {
get id() { return this._id; }
get postId() { return this._postId; }
- get text() { return this._text; }
+ get text() { return this._text || ''; }
get user() { return this._user; }
get creationTime() { return this._creationTime; }
get lastEditTime() { return this._lastEditTime; }
diff --git a/client/js/views/post_main_view.js b/client/js/views/post_main_view.js
index 446297d..7c115db 100644
--- a/client/js/views/post_main_view.js
+++ b/client/js/views/post_main_view.js
@@ -10,8 +10,8 @@ const PostReadonlySidebarControl =
require('../controls/post_readonly_sidebar_control.js');
const PostEditSidebarControl =
require('../controls/post_edit_sidebar_control.js');
+const CommentControl = require('../controls/comment_control.js');
const CommentListControl = require('../controls/comment_list_control.js');
-const CommentFormControl = require('../controls/comment_form_control.js');
const template = views.getTemplate('post-main');
@@ -101,9 +101,8 @@ class PostMainView {
return;
}
- this.commentFormControl = new CommentFormControl(
- commentFormContainer, null, false, 150);
- this.commentFormControl.enterEditMode();
+ this.commentControl = new CommentControl(
+ commentFormContainer, null, true);
}
_installComments(comments) {

© 2015 - 2026 Jakob L. Kreuze