diff options
| author | rr- | 2016-12-24 21:49:39 +0100 |
|---|---|---|
| committer | rr- | 2016-12-25 21:49:39 +0100 |
| commit | fe0ba63f191b56853c8b57d9538a39c57ad91165 (patch) | |
| tree | 085ea88c7280624049cd552b7d1a0b665b00f6bf /client/js | |
| parent | f0573be715ca3d7aab66a055d0eb7d4555ce2549 (diff) | |
client/comments: rework comments appearance and UX
Diffstat (limited to 'client/js')
| -rw-r--r-- | client/js/controllers/post_detail_controller.js | 1 | ||||
| -rw-r--r-- | client/js/controllers/post_main_controller.js | 16 | ||||
| -rw-r--r-- | client/js/controls/comment_control.js | 180 | ||||
| -rw-r--r-- | client/js/controls/comment_form_control.js | 141 | ||||
| -rw-r--r-- | client/js/controls/comment_list_control.js | 2 | ||||
| -rw-r--r-- | client/js/models/comment.js | 2 | ||||
| -rw-r--r-- | client/js/views/post_main_view.js | 7 |
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) { |